diff options
Diffstat (limited to 'packages/cli/src')
| -rw-r--r-- | packages/cli/src/ui/App.tsx | 13 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/HistoryItemDisplay.tsx | 2 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/InputPrompt.tsx | 22 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/messages/UserShellMessage.tsx | 25 | ||||
| -rw-r--r-- | packages/cli/src/ui/hooks/shellCommandProcessor.ts | 5 | ||||
| -rw-r--r-- | packages/cli/src/ui/types.ts | 6 |
6 files changed, 68 insertions, 5 deletions
diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index eb34fb87..3a747ae8 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -61,6 +61,7 @@ export const App = ({ const [themeError, setThemeError] = useState<string | null>(null); const [footerHeight, setFooterHeight] = useState<number>(0); const [corgiMode, setCorgiMode] = useState(false); + const [shellModeActive, setShellModeActive] = useState(false); const toggleCorgiMode = useCallback(() => { setCorgiMode((prev) => !prev); @@ -152,10 +153,16 @@ export const App = ({ (submittedValue: string) => { const trimmedValue = submittedValue.trim(); if (trimmedValue.length > 0) { - submitQuery(submittedValue); + if (shellModeActive && !trimmedValue.startsWith('!')) { + // TODO: Don't prefix (hack) and properly submit pass throughs to a dedicated hook: + // https://b.corp.google.com/issues/418509745 + submitQuery(`!${trimmedValue}`); + } else { + submitQuery(trimmedValue); + } } }, - [submitQuery], + [submitQuery, shellModeActive], ); const userMessages = useMemo( @@ -364,6 +371,8 @@ export const App = ({ resetCompletion={completion.resetCompletionState} setEditorState={setEditorState} onClearScreen={handleClearScreen} // Added onClearScreen prop + shellModeActive={shellModeActive} + setShellModeActive={setShellModeActive} /> {completion.showSuggestions && ( <Box> diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.tsx index 0a5ba7d1..0b61fc04 100644 --- a/packages/cli/src/ui/components/HistoryItemDisplay.tsx +++ b/packages/cli/src/ui/components/HistoryItemDisplay.tsx @@ -7,6 +7,7 @@ import React from 'react'; import type { HistoryItem } from '../types.js'; import { UserMessage } from './messages/UserMessage.js'; +import { UserShellMessage } from './messages/UserShellMessage.js'; import { GeminiMessage } from './messages/GeminiMessage.js'; import { InfoMessage } from './messages/InfoMessage.js'; import { ErrorMessage } from './messages/ErrorMessage.js'; @@ -28,6 +29,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({ <Box flexDirection="column" key={item.id}> {/* Render standard message types */} {item.type === 'user' && <UserMessage text={item.text} />} + {item.type === 'user_shell' && <UserShellMessage text={item.text} />} {item.type === 'gemini' && ( <GeminiMessage text={item.text} diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index b1e05554..c9ebaf14 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -26,6 +26,8 @@ interface InputPromptProps { navigateSuggestionDown: () => void; setEditorState: (updater: (prevState: EditorState) => EditorState) => void; onClearScreen: () => void; + shellModeActive: boolean; + setShellModeActive: (value: boolean) => void; } export interface EditorState { @@ -48,6 +50,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({ resetCompletion, setEditorState, onClearScreen, + shellModeActive, + setShellModeActive, }) => { const handleSubmit = useCallback( (submittedValue: string) => { @@ -116,6 +120,11 @@ export const InputPrompt: React.FC<InputPromptProps> = ({ _currentText?: string, _cursorOffset?: number, ) => { + if (input === '!' && query === '' && !showSuggestions) { + setShellModeActive(!shellModeActive); + onChangeAndMoveCursor(''); // Clear the '!' from input + return true; + } if (showSuggestions) { if (key.upArrow) { navigateSuggestionUp(); @@ -186,12 +195,21 @@ export const InputPrompt: React.FC<InputPromptProps> = ({ inputHistory, setEditorState, onClearScreen, + shellModeActive, + setShellModeActive, + onChangeAndMoveCursor, ], ); return ( - <Box borderStyle="round" borderColor={Colors.AccentBlue} paddingX={1}> - <Text color={Colors.AccentPurple}>> </Text> + <Box + borderStyle="round" + borderColor={shellModeActive ? Colors.AccentYellow : Colors.AccentBlue} + paddingX={1} + > + <Text color={shellModeActive ? Colors.AccentYellow : Colors.AccentPurple}> + {shellModeActive ? '! ' : '> '} + </Text> <Box flexGrow={1}> <MultilineTextEditor key={editorState.key.toString()} diff --git a/packages/cli/src/ui/components/messages/UserShellMessage.tsx b/packages/cli/src/ui/components/messages/UserShellMessage.tsx new file mode 100644 index 00000000..946ca7e7 --- /dev/null +++ b/packages/cli/src/ui/components/messages/UserShellMessage.tsx @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { Box, Text } from 'ink'; +import { Colors } from '../../colors.js'; + +interface UserShellMessageProps { + text: string; +} + +export const UserShellMessage: React.FC<UserShellMessageProps> = ({ text }) => { + // Remove leading '!' if present, as App.tsx adds it for the processor. + const commandToDisplay = text.startsWith('!') ? text.substring(1) : text; + + return ( + <Box> + <Text color={Colors.AccentCyan}>$ </Text> + <Text>{commandToDisplay}</Text> + </Box> + ); +}; diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.ts b/packages/cli/src/ui/hooks/shellCommandProcessor.ts index 980a6572..543c7f24 100644 --- a/packages/cli/src/ui/hooks/shellCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/shellCommandProcessor.ts @@ -38,7 +38,10 @@ export const useShellCommandProcessor = ( const commandToExecute = rawQuery.trim().slice(1).trimStart(); const userMessageTimestamp = Date.now(); - addItemToHistory({ type: 'user', text: rawQuery }, userMessageTimestamp); + addItemToHistory( + { type: 'user_shell', text: rawQuery }, + userMessageTimestamp, + ); if (!commandToExecute) { addItemToHistory( diff --git a/packages/cli/src/ui/types.ts b/packages/cli/src/ui/types.ts index 60508c05..ff7515a5 100644 --- a/packages/cli/src/ui/types.ts +++ b/packages/cli/src/ui/types.ts @@ -85,12 +85,18 @@ export type HistoryItemToolGroup = HistoryItemBase & { tools: IndividualToolCallDisplay[]; }; +export type HistoryItemUserShell = HistoryItemBase & { + type: 'user_shell'; + text: string; +}; + // Using Omit<HistoryItem, 'id'> seems to have some issues with typescript's // type inference e.g. historyItem.type === 'tool_group' isn't auto-inferring that // 'tools' in historyItem. // Individually exported types extending HistoryItemBase export type HistoryItemWithoutId = | HistoryItemUser + | HistoryItemUserShell | HistoryItemGemini | HistoryItemGeminiContent | HistoryItemInfo |
