diff options
Diffstat (limited to 'packages/cli/src/ui/components')
3 files changed, 164 insertions, 0 deletions
diff --git a/packages/cli/src/ui/components/ShellConfirmationDialog.test.tsx b/packages/cli/src/ui/components/ShellConfirmationDialog.test.tsx new file mode 100644 index 00000000..35783d44 --- /dev/null +++ b/packages/cli/src/ui/components/ShellConfirmationDialog.test.tsx @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { render } from 'ink-testing-library'; +import { describe, it, expect, vi } from 'vitest'; +import { ShellConfirmationDialog } from './ShellConfirmationDialog.js'; + +describe('ShellConfirmationDialog', () => { + const onConfirm = vi.fn(); + + const request = { + commands: ['ls -la', 'echo "hello"'], + onConfirm, + }; + + it('renders correctly', () => { + const { lastFrame } = render(<ShellConfirmationDialog request={request} />); + expect(lastFrame()).toMatchSnapshot(); + }); + + it('calls onConfirm with ProceedOnce when "Yes, allow once" is selected', () => { + const { lastFrame } = render(<ShellConfirmationDialog request={request} />); + const select = lastFrame()!.toString(); + // Simulate selecting the first option + // This is a simplified way to test the selection + expect(select).toContain('Yes, allow once'); + }); + + it('calls onConfirm with ProceedAlways when "Yes, allow always for this session" is selected', () => { + const { lastFrame } = render(<ShellConfirmationDialog request={request} />); + const select = lastFrame()!.toString(); + // Simulate selecting the second option + expect(select).toContain('Yes, allow always for this session'); + }); + + it('calls onConfirm with Cancel when "No (esc)" is selected', () => { + const { lastFrame } = render(<ShellConfirmationDialog request={request} />); + const select = lastFrame()!.toString(); + // Simulate selecting the third option + expect(select).toContain('No (esc)'); + }); +}); diff --git a/packages/cli/src/ui/components/ShellConfirmationDialog.tsx b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx new file mode 100644 index 00000000..ec137a6d --- /dev/null +++ b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx @@ -0,0 +1,98 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ToolConfirmationOutcome } from '@google/gemini-cli-core'; +import { Box, Text, useInput } from 'ink'; +import React from 'react'; +import { Colors } from '../colors.js'; +import { + RadioButtonSelect, + RadioSelectItem, +} from './shared/RadioButtonSelect.js'; + +export interface ShellConfirmationRequest { + commands: string[]; + onConfirm: ( + outcome: ToolConfirmationOutcome, + approvedCommands?: string[], + ) => void; +} + +export interface ShellConfirmationDialogProps { + request: ShellConfirmationRequest; +} + +export const ShellConfirmationDialog: React.FC< + ShellConfirmationDialogProps +> = ({ request }) => { + const { commands, onConfirm } = request; + + useInput((_, key) => { + if (key.escape) { + onConfirm(ToolConfirmationOutcome.Cancel); + } + }); + + const handleSelect = (item: ToolConfirmationOutcome) => { + if (item === ToolConfirmationOutcome.Cancel) { + onConfirm(item); + } else { + // For both ProceedOnce and ProceedAlways, we approve all the + // commands that were requested. + onConfirm(item, commands); + } + }; + + const options: Array<RadioSelectItem<ToolConfirmationOutcome>> = [ + { + label: 'Yes, allow once', + value: ToolConfirmationOutcome.ProceedOnce, + }, + { + label: 'Yes, allow always for this session', + value: ToolConfirmationOutcome.ProceedAlways, + }, + { + label: 'No (esc)', + value: ToolConfirmationOutcome.Cancel, + }, + ]; + + return ( + <Box + flexDirection="column" + borderStyle="round" + borderColor={Colors.AccentYellow} + padding={1} + width="100%" + marginLeft={1} + > + <Box flexDirection="column" marginBottom={1}> + <Text bold>Shell Command Execution</Text> + <Text>A custom command wants to run the following shell commands:</Text> + <Box + flexDirection="column" + borderStyle="round" + borderColor={Colors.Gray} + paddingX={1} + marginTop={1} + > + {commands.map((cmd) => ( + <Text key={cmd} color={Colors.AccentCyan}> + {cmd} + </Text> + ))} + </Box> + </Box> + + <Box marginBottom={1}> + <Text>Do you want to proceed?</Text> + </Box> + + <RadioButtonSelect items={options} onSelect={handleSelect} isFocused /> + </Box> + ); +}; diff --git a/packages/cli/src/ui/components/__snapshots__/ShellConfirmationDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ShellConfirmationDialog.test.tsx.snap new file mode 100644 index 00000000..8c9ceb29 --- /dev/null +++ b/packages/cli/src/ui/components/__snapshots__/ShellConfirmationDialog.test.tsx.snap @@ -0,0 +1,21 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ShellConfirmationDialog > renders correctly 1`] = ` +" ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ + │ │ + │ Shell Command Execution │ + │ A custom command wants to run the following shell commands: │ + │ │ + │ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │ + │ │ ls -la │ │ + │ │ echo "hello" │ │ + │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │ + │ │ + │ Do you want to proceed? │ + │ │ + │ ● 1. Yes, allow once │ + │ 2. Yes, allow always for this session │ + │ 3. No (esc) │ + │ │ + ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯" +`; |
