From e75f0722e7cc780659951baee058095ed2916a9e Mon Sep 17 00:00:00 2001 From: Allen Hutchison Date: Fri, 18 Apr 2025 17:12:14 -0700 Subject: [PATCH] All the pipes (#47) * Bump the character limit to web fetch. * Piped Input Hook. First step in bringing in STDIN piping. * Fix linting errors. * Remove incorrect comment. --- packages/cli/src/tools/web-fetch.tool.ts | 2 +- packages/cli/src/ui/hooks/useStdin.ts | 78 ++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 packages/cli/src/ui/hooks/useStdin.ts diff --git a/packages/cli/src/tools/web-fetch.tool.ts b/packages/cli/src/tools/web-fetch.tool.ts index 8db7aad3..362ca45e 100644 --- a/packages/cli/src/tools/web-fetch.tool.ts +++ b/packages/cli/src/tools/web-fetch.tool.ts @@ -138,7 +138,7 @@ export class WebFetchTool extends BaseTool { const data = await response.text(); let llmContent = ''; // Truncate very large responses for the LLM context - const MAX_LLM_CONTENT_LENGTH = 100000; + const MAX_LLM_CONTENT_LENGTH = 200000; if (data) { llmContent = `Fetched data from ${url}:\n\n${ data.length > MAX_LLM_CONTENT_LENGTH diff --git a/packages/cli/src/ui/hooks/useStdin.ts b/packages/cli/src/ui/hooks/useStdin.ts new file mode 100644 index 00000000..8c741c9b --- /dev/null +++ b/packages/cli/src/ui/hooks/useStdin.ts @@ -0,0 +1,78 @@ +import { useState, useEffect } from 'react'; +import { useStdin } from 'ink'; + +export interface PipedInputState { + data: string | null; // Use null initially to distinguish from empty string + isLoading: boolean; + error: string | null; + isPiped: boolean; // Flag to indicate if input was piped +} + +export function usePipedInput(): PipedInputState { + const { stdin, setRawMode, isRawModeSupported } = useStdin(); + // Keep exit available if needed, e.g., for error handling, but maybe let consumer handle it + // const { exit } = useApp(); + + const [pipedData, setPipedData] = useState(null); + const [isLoading, setIsLoading] = useState(true); // Assume loading until checked + const [error, setError] = useState(null); + const [isPiped, setIsPiped] = useState(false); + + useEffect(() => { + // Determine if input is piped ONLY ONCE + const checkIsPiped = !stdin || !stdin.isTTY; + setIsPiped(checkIsPiped); + + if (checkIsPiped) { + // Piped input detected + if (isRawModeSupported) { + setRawMode(false); // Ensure raw mode is off for stream reading + } + + // Ensure stdin is available (it should be if !isTTY) + if (!stdin) { + setError('Stdin stream is unavailable.'); + setIsLoading(false); + return; // Cannot proceed + } + + let data = ''; + const handleData = (chunk: Buffer) => { + data += chunk.toString(); + }; + + const handleError = (err: Error) => { + setError('Error reading from stdin: ' + err.message); + setIsLoading(false); + // Decide if the hook should trigger exit or just report the error + // exit(); + }; + + const handleEnd = () => { + setPipedData(data); + setIsLoading(false); + // Don't exit here, let the component using the hook decide + }; + + stdin.on('data', handleData); + stdin.on('error', handleError); + stdin.on('end', handleEnd); + + // Cleanup listeners + return () => { + stdin.removeListener('data', handleData); + stdin.removeListener('error', handleError); + stdin.removeListener('end', handleEnd); + }; + } else { + // No piped input (running interactively) + setIsLoading(false); + // Optionally set an 'info' state or just let isLoading=false & isPiped=false suffice + // setError('No piped input detected.'); // Maybe don't treat this as an 'error' + } + + // Intentionally run only once on mount or when stdin theoretically changes + }, [stdin, isRawModeSupported, setRawMode /*, exit */]); + + return { data: pipedData, isLoading, error, isPiped }; +}