diff options
| author | christine betts <[email protected]> | 2025-08-21 22:29:15 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-21 22:29:15 +0000 |
| commit | 10286934e6a549dcad557adecfc087552e13c983 (patch) | |
| tree | fe6a61cf3158918253d18d891dab52f4c9b7a33b /packages/cli/src | |
| parent | 679acc45b222986032a01aebbffcddf756573f14 (diff) | |
Introduce initial screen reader mode handling and flag (#6653)
Diffstat (limited to 'packages/cli/src')
| -rw-r--r-- | packages/cli/src/config/config.ts | 14 | ||||
| -rw-r--r-- | packages/cli/src/config/settings.ts | 1 | ||||
| -rw-r--r-- | packages/cli/src/config/settingsSchema.ts | 10 | ||||
| -rw-r--r-- | packages/cli/src/gemini.tsx | 2 | ||||
| -rw-r--r-- | packages/cli/src/ui/App.test.tsx | 2 | ||||
| -rw-r--r-- | packages/cli/src/ui/App.tsx | 12 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/InputPrompt.tsx | 8 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/messages/CompressionMessage.tsx | 2 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/messages/GeminiMessage.tsx | 8 | ||||
| -rw-r--r-- | packages/cli/src/ui/components/messages/UserMessage.tsx | 5 | ||||
| -rw-r--r-- | packages/cli/src/ui/constants.ts | 4 |
11 files changed, 59 insertions, 9 deletions
diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 0b21ff2e..aaaf293d 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -73,6 +73,7 @@ export interface CliArgs { listExtensions: boolean | undefined; proxy: string | undefined; includeDirectories: string[] | undefined; + screenReader: boolean | undefined; } export async function parseArguments(): Promise<CliArgs> { @@ -229,6 +230,11 @@ export async function parseArguments(): Promise<CliArgs> { // Handle comma-separated values dirs.flatMap((dir) => dir.split(',').map((d) => d.trim())), }) + .option('screen-reader', { + type: 'boolean', + description: 'Enable screen reader mode for accessibility.', + default: false, + }) .check((argv) => { if (argv.prompt && argv['promptInteractive']) { @@ -465,6 +471,9 @@ export async function loadCliConfig( const sandboxConfig = await loadSandboxConfig(settings, argv); + // The screen reader argument takes precedence over the accessibility setting. + const screenReader = + argv.screenReader ?? settings.accessibility?.screenReader ?? false; return new Config({ sessionId, embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL, @@ -490,7 +499,10 @@ export async function loadCliConfig( argv.show_memory_usage || settings.showMemoryUsage || false, - accessibility: settings.accessibility, + accessibility: { + ...settings.accessibility, + screenReader, + }, telemetry: { enabled: argv.telemetry ?? settings.telemetry?.enabled, target: (argv.telemetryTarget ?? diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index 3f94fe65..5f8c706d 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -58,6 +58,7 @@ export interface SummarizeToolOutputSettings { export interface AccessibilitySettings { disableLoadingPhrases?: boolean; + screenReader?: boolean; } export interface SettingsError { diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 7f28b698..5f939b56 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -206,6 +206,16 @@ export const SETTINGS_SCHEMA = { description: 'Disable loading phrases for accessibility', showInDialog: true, }, + screenReader: { + type: 'boolean', + label: 'Screen Reader Mode', + category: 'Accessibility', + requiresRestart: true, + default: false, + description: + 'Render output in plain-text to be more screen reader accessible', + showInDialog: true, + }, }, }, checkpointing: { diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 6661d3ef..b285a5af 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -316,7 +316,7 @@ export async function main() { /> </SettingsContext.Provider> </React.StrictMode>, - { exitOnCtrlC: false }, + { exitOnCtrlC: false, isScreenReaderEnabled: config.getScreenReader() }, ); checkForUpdates() diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index 9f8a681f..d5f2ba82 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -87,6 +87,7 @@ interface MockServerConfig { getGeminiClient: Mock<() => GeminiClient | undefined>; getUserTier: Mock<() => Promise<string | undefined>>; getIdeClient: Mock<() => { getCurrentIde: Mock<() => string | undefined> }>; + getScreenReader: Mock<() => boolean>; } // Mock @google/gemini-cli-core and its Config class @@ -168,6 +169,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { getConnectionStatus: vi.fn(() => 'connected'), })), isTrustedFolder: vi.fn(() => true), + getScreenReader: vi.fn(() => false), }; }); diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 6bf4f770..b4ec6a57 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -923,10 +923,12 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { key={staticKey} items={[ <Box flexDirection="column" key="header"> - {!settings.merged.hideBanner && ( + {!(settings.merged.hideBanner || config.getScreenReader()) && ( <Header version={version} nightly={nightly} /> )} - {!settings.merged.hideTips && <Tips config={config} />} + {!(settings.merged.hideTips || config.getScreenReader()) && ( + <Tips config={config} /> + )} </Box>, ...history.map((h) => ( <HistoryItemDisplay @@ -1093,12 +1095,14 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { <LoadingIndicator thought={ streamingState === StreamingState.WaitingForConfirmation || - config.getAccessibility()?.disableLoadingPhrases + config.getAccessibility()?.disableLoadingPhrases || + config.getScreenReader() ? undefined : thought } currentLoadingPhrase={ - config.getAccessibility()?.disableLoadingPhrases + config.getAccessibility()?.disableLoadingPhrases || + config.getScreenReader() ? undefined : currentLoadingPhrase } diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index 01666c66..215962ce 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -26,6 +26,7 @@ import { cleanupOldClipboardImages, } from '../utils/clipboardUtils.js'; import * as path from 'path'; +import { SCREEN_READER_USER_PREFIX } from '../constants.js'; export interface InputPromptProps { buffer: TextBuffer; @@ -688,7 +689,12 @@ export const InputPrompt: React.FC<InputPromptProps> = ({ > {shellModeActive ? ( reverseSearchActive ? ( - <Text color={theme.text.link}>(r:) </Text> + <Text + color={theme.text.link} + aria-label={SCREEN_READER_USER_PREFIX} + > + (r:){' '} + </Text> ) : ( '! ' ) diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx index c7ef122b..6aededbb 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx @@ -9,6 +9,7 @@ import { Box, Text } from 'ink'; import { CompressionProps } from '../../types.js'; import Spinner from 'ink-spinner'; import { Colors } from '../../colors.js'; +import { SCREEN_READER_MODEL_PREFIX } from '../../constants.js'; export interface CompressionDisplayProps { compression: CompressionProps; @@ -40,6 +41,7 @@ export const CompressionMessage: React.FC<CompressionDisplayProps> = ({ color={ compression.isPending ? Colors.AccentPurple : Colors.AccentGreen } + aria-label={SCREEN_READER_MODEL_PREFIX} > {text} </Text> diff --git a/packages/cli/src/ui/components/messages/GeminiMessage.tsx b/packages/cli/src/ui/components/messages/GeminiMessage.tsx index 9863acd6..cfc3a297 100644 --- a/packages/cli/src/ui/components/messages/GeminiMessage.tsx +++ b/packages/cli/src/ui/components/messages/GeminiMessage.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { Text, Box } from 'ink'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; import { Colors } from '../../colors.js'; +import { SCREEN_READER_MODEL_PREFIX } from '../../constants.js'; interface GeminiMessageProps { text: string; @@ -28,7 +29,12 @@ export const GeminiMessage: React.FC<GeminiMessageProps> = ({ return ( <Box flexDirection="row"> <Box width={prefixWidth}> - <Text color={Colors.AccentPurple}>{prefix}</Text> + <Text + color={Colors.AccentPurple} + aria-label={SCREEN_READER_MODEL_PREFIX} + > + {prefix} + </Text> </Box> <Box flexGrow={1} flexDirection="column"> <MarkdownDisplay diff --git a/packages/cli/src/ui/components/messages/UserMessage.tsx b/packages/cli/src/ui/components/messages/UserMessage.tsx index 332cb0f4..1be41be9 100644 --- a/packages/cli/src/ui/components/messages/UserMessage.tsx +++ b/packages/cli/src/ui/components/messages/UserMessage.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { Text, Box } from 'ink'; import { Colors } from '../../colors.js'; +import { SCREEN_READER_USER_PREFIX } from '../../constants.js'; interface UserMessageProps { text: string; @@ -31,7 +32,9 @@ export const UserMessage: React.FC<UserMessageProps> = ({ text }) => { alignSelf="flex-start" > <Box width={prefixWidth}> - <Text color={textColor}>{prefix}</Text> + <Text color={textColor} aria-label={SCREEN_READER_USER_PREFIX}> + {prefix} + </Text> </Box> <Box flexGrow={1}> <Text wrap="wrap" color={textColor}> diff --git a/packages/cli/src/ui/constants.ts b/packages/cli/src/ui/constants.ts index 6a77631c..6a1047dc 100644 --- a/packages/cli/src/ui/constants.ts +++ b/packages/cli/src/ui/constants.ts @@ -15,3 +15,7 @@ export const UI_WIDTH = export const STREAM_DEBOUNCE_MS = 100; export const SHELL_COMMAND_NAME = 'Shell Command'; + +export const SCREEN_READER_USER_PREFIX = 'User: '; + +export const SCREEN_READER_MODEL_PREFIX = 'Model: '; |
