diff options
| author | Seth Troisi <[email protected]> | 2025-04-30 00:26:07 +0000 |
|---|---|---|
| committer | Seth Troisi <[email protected]> | 2025-04-30 22:17:08 +0000 |
| commit | 5f5edb4c9bac24c4875ffc1a5a97ad8cf11f4436 (patch) | |
| tree | 376f7f0863d0db0c354ec6f2212d16f9c20cd995 /packages/cli/src/ui/hooks/shellCommandProcessor.ts | |
| parent | 68a3020044b3c8567641c8fdcd5a369366dab981 (diff) | |
Added bang(!) commands as a shell passthrough
Diffstat (limited to 'packages/cli/src/ui/hooks/shellCommandProcessor.ts')
| -rw-r--r-- | packages/cli/src/ui/hooks/shellCommandProcessor.ts | 93 |
1 files changed, 93 insertions, 0 deletions
diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.ts b/packages/cli/src/ui/hooks/shellCommandProcessor.ts new file mode 100644 index 00000000..300f21fe --- /dev/null +++ b/packages/cli/src/ui/hooks/shellCommandProcessor.ts @@ -0,0 +1,93 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { exec as _exec } from 'child_process'; +import { useCallback } from 'react'; +import { Config } from '@gemini-code/server'; +import { type PartListUnion } from '@google/genai'; +import { HistoryItem, StreamingState } from '../types.js'; +import { getCommandFromQuery } from '../utils/commandUtils.js'; + +// Helper function (consider moving to a shared util if used elsewhere) +const addHistoryItem = ( + setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>, + itemData: Omit<HistoryItem, 'id'>, + id: number, +) => { + setHistory((prevHistory) => [ + ...prevHistory, + { ...itemData, id } as HistoryItem, + ]); +}; + +export const useShellCommandProcessor = ( + setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>, + setStreamingState: React.Dispatch<React.SetStateAction<StreamingState>>, + setDebugMessage: React.Dispatch<React.SetStateAction<string>>, + getNextMessageId: (baseTimestamp: number) => number, + config: Config, +) => { + const handleShellCommand = useCallback( + (rawQuery: PartListUnion): boolean => { + if (typeof rawQuery !== 'string') { + return false; // Passthrough only works with string commands + } + + const [symbol] = getCommandFromQuery(rawQuery); + if (symbol !== '!' && symbol !== '$') { + return false; + } + // Remove symbol from rawQuery + const trimmed = rawQuery.trim().slice(1); + + // Add user message *before* execution starts + const userMessageTimestamp = Date.now(); + addHistoryItem( + setHistory, + { type: 'user', text: rawQuery }, + userMessageTimestamp, + ); + + // Execute and capture output + const targetDir = config.getTargetDir(); + setDebugMessage(`Executing shell command in ${targetDir}: ${trimmed}`); + const execOptions = { + cwd: targetDir, + }; + + // Set state to Responding while the command runs + setStreamingState(StreamingState.Responding); + + _exec(trimmed, execOptions, (error, stdout, stderr) => { + const timestamp = getNextMessageId(userMessageTimestamp); // Use user message time as base + if (error) { + addHistoryItem( + setHistory, + { type: 'error', text: error.message }, + timestamp, + ); + } else if (stderr) { + // Treat stderr as info for passthrough, as some tools use it for non-error output + addHistoryItem(setHistory, { type: 'info', text: stderr }, timestamp); + } else { + // Add stdout as an info message + addHistoryItem( + setHistory, + { type: 'info', text: stdout || '(Command produced no output)' }, + timestamp, + ); + } + // Set state back to Idle *after* command finishes and output is added + setStreamingState(StreamingState.Idle); + }); + + return true; // Command was handled + }, + [config, setDebugMessage, setHistory, setStreamingState, getNextMessageId], + ); + + return { handleShellCommand }; +}; |
