summaryrefslogtreecommitdiff
path: root/packages/cli/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src')
-rw-r--r--packages/cli/src/ui/App.tsx13
-rw-r--r--packages/cli/src/ui/components/HistoryItemDisplay.tsx2
-rw-r--r--packages/cli/src/ui/components/InputPrompt.tsx22
-rw-r--r--packages/cli/src/ui/components/messages/UserShellMessage.tsx25
-rw-r--r--packages/cli/src/ui/hooks/shellCommandProcessor.ts5
-rw-r--r--packages/cli/src/ui/types.ts6
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}>&gt; </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