diff --git a/packages/cli/src/ui/IdeIntegrationNudge.tsx b/packages/cli/src/ui/IdeIntegrationNudge.tsx index d4645d6379..7584e35a8f 100644 --- a/packages/cli/src/ui/IdeIntegrationNudge.tsx +++ b/packages/cli/src/ui/IdeIntegrationNudge.tsx @@ -10,6 +10,7 @@ import { Box, Text } from 'ink'; import type { RadioSelectItem } from './components/shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './components/shared/RadioButtonSelect.js'; import { useKeypress } from './hooks/useKeypress.js'; +import { theme } from './semantic-colors.js'; export type IdeIntegrationNudgeResult = { userSelection: 'yes' | 'no' | 'dismiss'; @@ -79,17 +80,17 @@ export function IdeIntegrationNudge({ - {'> '} + {'> '} {`Do you want to connect ${ideName ?? 'your editor'} to Gemini CLI?`} - {installText} + {installText} diff --git a/packages/cli/src/ui/auth/AuthDialog.tsx b/packages/cli/src/ui/auth/AuthDialog.tsx index 5a3a01ca56..7af7cb9b1c 100644 --- a/packages/cli/src/ui/auth/AuthDialog.tsx +++ b/packages/cli/src/ui/auth/AuthDialog.tsx @@ -7,7 +7,7 @@ import type React from 'react'; import { useCallback } from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js'; import type { LoadedSettings } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js'; @@ -149,14 +149,16 @@ Logging in with Google... Please restart Gemini CLI to continue. return ( Get started - How would you like to authenticate for this project? + + How would you like to authenticate for this project? + {authError && ( - {authError} + {authError} )} - (Use Enter to select) + (Use Enter to select) - Terms of Services and Privacy Notice for Gemini CLI + + Terms of Services and Privacy Notice for Gemini CLI + - + { 'https://github.com/google-gemini/gemini-cli/blob/main/docs/tos-privacy.md' } diff --git a/packages/cli/src/ui/auth/AuthInProgress.tsx b/packages/cli/src/ui/auth/AuthInProgress.tsx index ce8b54435d..6270ecf1fb 100644 --- a/packages/cli/src/ui/auth/AuthInProgress.tsx +++ b/packages/cli/src/ui/auth/AuthInProgress.tsx @@ -8,7 +8,7 @@ import type React from 'react'; import { useState, useEffect } from 'react'; import { Box, Text } from 'ink'; import Spinner from 'ink-spinner'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { useKeypress } from '../hooks/useKeypress.js'; interface AuthInProgressProps { @@ -41,13 +41,13 @@ export function AuthInProgress({ return ( {timedOut ? ( - + Authentication timed out. Please try again. ) : ( diff --git a/packages/cli/src/ui/commands/chatCommand.ts b/packages/cli/src/ui/commands/chatCommand.ts index 216baaac9c..5ef188981c 100644 --- a/packages/cli/src/ui/commands/chatCommand.ts +++ b/packages/cli/src/ui/commands/chatCommand.ts @@ -7,7 +7,7 @@ import * as fsPromises from 'node:fs/promises'; import React from 'react'; import { Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import type { CommandContext, SlashCommand, @@ -126,7 +126,7 @@ const saveCommand: SlashCommand = { Text, null, 'A checkpoint with the tag ', - React.createElement(Text, { color: Colors.AccentPurple }, tag), + React.createElement(Text, { color: theme.text.accent }, tag), ' already exists. Do you want to overwrite it?', ), originalInvocation: { diff --git a/packages/cli/src/ui/components/AboutBox.tsx b/packages/cli/src/ui/components/AboutBox.tsx index 1d58495ded..129ed30351 100644 --- a/packages/cli/src/ui/components/AboutBox.tsx +++ b/packages/cli/src/ui/components/AboutBox.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { GIT_COMMIT_INFO } from '../../generated/git-commit.js'; interface AboutBoxProps { @@ -30,77 +30,77 @@ export const AboutBox: React.FC = ({ }) => ( - + About Gemini CLI - + CLI Version - {cliVersion} + {cliVersion} {GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO) && ( - + Git Commit - {GIT_COMMIT_INFO} + {GIT_COMMIT_INFO} )} - + Model - {modelVersion} + {modelVersion} - + Sandbox - {sandboxEnv} + {sandboxEnv} - + OS - {osVersion} + {osVersion} - + Auth Method - + {selectedAuthType.startsWith('oauth') ? 'OAuth' : selectedAuthType} @@ -108,24 +108,24 @@ export const AboutBox: React.FC = ({ {gcpProject && ( - + GCP Project - {gcpProject} + {gcpProject} )} {ideClient && ( - + IDE Client - {ideClient} + {ideClient} )} diff --git a/packages/cli/src/ui/components/AutoAcceptIndicator.tsx b/packages/cli/src/ui/components/AutoAcceptIndicator.tsx index 273634de61..2fbc20bfdd 100644 --- a/packages/cli/src/ui/components/AutoAcceptIndicator.tsx +++ b/packages/cli/src/ui/components/AutoAcceptIndicator.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { ApprovalMode } from '@google/gemini-cli-core'; interface AutoAcceptIndicatorProps { @@ -22,12 +22,12 @@ export const AutoAcceptIndicator: React.FC = ({ switch (approvalMode) { case ApprovalMode.AUTO_EDIT: - textColor = Colors.AccentGreen; + textColor = theme.status.success; textContent = 'accepting edits'; subText = ' (shift + tab to toggle)'; break; case ApprovalMode.YOLO: - textColor = Colors.AccentRed; + textColor = theme.status.error; textContent = 'YOLO mode'; subText = ' (ctrl + y to toggle)'; break; @@ -40,7 +40,7 @@ export const AutoAcceptIndicator: React.FC = ({ {textContent} - {subText && {subText}} + {subText && {subText}} ); diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index fa5322e4ae..07d9766029 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -14,7 +14,7 @@ import { InputPrompt } from './InputPrompt.js'; import { Footer, type FooterProps } from './Footer.js'; import { ShowMoreLines } from './ShowMoreLines.js'; import { OverflowProvider } from '../contexts/OverflowContext.js'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { isNarrowWidth } from '../utils/isNarrowWidth.js'; import { useUIState } from '../contexts/UIStateContext.js'; import { useUIActions } from '../contexts/UIActionsContext.js'; @@ -112,14 +112,18 @@ export const Composer = () => { > {process.env['GEMINI_SYSTEM_MD'] && ( - |⌐■_■| + |⌐■_■| )} {uiState.ctrlCPressedOnce ? ( - Press Ctrl+C again to exit. + + Press Ctrl+C again to exit. + ) : uiState.ctrlDPressedOnce ? ( - Press Ctrl+D again to exit. + + Press Ctrl+D again to exit. + ) : uiState.showEscapePrompt ? ( - Press Esc again to clear. + Press Esc again to clear. ) : ( !settings.merged.ui?.hideContextSummary && ( { const config = useConfig(); @@ -39,7 +40,7 @@ export const ConfigInitDisplay = () => { return ( - {message} + {message} ); diff --git a/packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx b/packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx index 68700f2704..2f2f8a2a75 100644 --- a/packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx +++ b/packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; interface ConsoleSummaryDisplayProps { errorCount: number; @@ -25,9 +25,9 @@ export const ConsoleSummaryDisplay: React.FC = ({ return ( {errorCount > 0 && ( - + {errorIcon} {errorCount} error{errorCount > 1 ? 's' : ''}{' '} - (ctrl+o for details) + (ctrl+o for details) )} diff --git a/packages/cli/src/ui/components/ContextSummaryDisplay.tsx b/packages/cli/src/ui/components/ContextSummaryDisplay.tsx index b3a9286b12..388bd44edc 100644 --- a/packages/cli/src/ui/components/ContextSummaryDisplay.tsx +++ b/packages/cli/src/ui/components/ContextSummaryDisplay.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { type IdeContext, type MCPServerConfig } from '@google/gemini-cli-core'; import { useTerminalSize } from '../hooks/useTerminalSize.js'; import { isNarrowWidth } from '../utils/isNarrowWidth.js'; @@ -99,9 +99,9 @@ export const ContextSummaryDisplay: React.FC = ({ if (isNarrow) { return ( - Using: + Using: {summaryParts.map((part, index) => ( - + {' '}- {part} ))} @@ -111,7 +111,9 @@ export const ContextSummaryDisplay: React.FC = ({ return ( - Using: {summaryParts.join(' | ')} + + Using: {summaryParts.join(' | ')} + ); }; diff --git a/packages/cli/src/ui/components/ContextUsageDisplay.tsx b/packages/cli/src/ui/components/ContextUsageDisplay.tsx index 037be33398..d7d8c063f0 100644 --- a/packages/cli/src/ui/components/ContextUsageDisplay.tsx +++ b/packages/cli/src/ui/components/ContextUsageDisplay.tsx @@ -5,7 +5,7 @@ */ import { Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { tokenLimit } from '@google/gemini-cli-core'; export const ContextUsageDisplay = ({ @@ -18,7 +18,7 @@ export const ContextUsageDisplay = ({ const percentage = promptTokenCount / tokenLimit(model); return ( - + ({((1 - percentage) * 100).toFixed(0)}% context left) ); diff --git a/packages/cli/src/ui/components/DebugProfiler.tsx b/packages/cli/src/ui/components/DebugProfiler.tsx index 22c16cfb22..4a4d6b4c17 100644 --- a/packages/cli/src/ui/components/DebugProfiler.tsx +++ b/packages/cli/src/ui/components/DebugProfiler.tsx @@ -6,7 +6,7 @@ import { Text } from 'ink'; import { useEffect, useRef, useState } from 'react'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { useKeypress } from '../hooks/useKeypress.js'; export const DebugProfiler = () => { @@ -31,6 +31,6 @@ export const DebugProfiler = () => { } return ( - Renders: {numRenders.current} + Renders: {numRenders.current} ); }; diff --git a/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx b/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx index 454977220a..b31d088005 100644 --- a/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx +++ b/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import type { ConsoleMessageItem } from '../types.js'; import { MaxSizedBox } from './shared/MaxSizedBox.js'; @@ -31,31 +31,32 @@ export const DetailedMessagesDisplay: React.FC< flexDirection="column" marginTop={1} borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} paddingX={1} width={width} > - - Debug Console (ctrl+o to close) + + Debug Console{' '} + (ctrl+o to close) {messages.map((msg, index) => { - let textColor = Colors.Foreground; + let textColor = theme.text.primary; let icon = '\u2139'; // Information source (ℹ) switch (msg.type) { case 'warn': - textColor = Colors.AccentYellow; + textColor = theme.status.warning; icon = '\u26A0'; // Warning sign (⚠) break; case 'error': - textColor = Colors.AccentRed; + textColor = theme.status.error; icon = '\u2716'; // Heavy multiplication x (✖) break; case 'debug': - textColor = Colors.Gray; // Or Colors.Gray + textColor = theme.text.secondary; // Or theme.text.secondary icon = '\u{1F50D}'; // Left-pointing magnifying glass (🔍) break; case 'log': @@ -70,7 +71,7 @@ export const DetailedMessagesDisplay: React.FC< {msg.content} {msg.count && msg.count > 1 && ( - (x{msg.count}) + (x{msg.count}) )} diff --git a/packages/cli/src/ui/components/DialogManager.tsx b/packages/cli/src/ui/components/DialogManager.tsx index 9a79d2eebd..fff3e2f1cc 100644 --- a/packages/cli/src/ui/components/DialogManager.tsx +++ b/packages/cli/src/ui/components/DialogManager.tsx @@ -17,7 +17,7 @@ import { EditorSettingsDialog } from './EditorSettingsDialog.js'; import { PrivacyNotice } from '../privacy/PrivacyNotice.js'; import { WorkspaceMigrationDialog } from './WorkspaceMigrationDialog.js'; import { ProQuotaDialog } from './ProQuotaDialog.js'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { useUIState } from '../contexts/UIStateContext.js'; import { useUIActions } from '../contexts/UIActionsContext.js'; import { useConfig } from '../contexts/ConfigContext.js'; @@ -36,8 +36,8 @@ export const DialogManager = () => { if (uiState.showIdeRestartPrompt) { return ( - - + + Workspace trust has changed. Press 'r' to restart Gemini to apply the changes. @@ -106,7 +106,7 @@ export const DialogManager = () => { {uiState.themeError && ( - {uiState.themeError} + {uiState.themeError} )} { {uiState.editorError && ( - {uiState.editorError} + {uiState.editorError} )} {focusedSection === 'editor' ? '> ' : ' '}Select Editor{' '} - {otherScopeModifiedMessage} + {otherScopeModifiedMessage} ({ @@ -147,7 +147,7 @@ export function EditorSettingsDialog({ - + (Use Enter to select, Tab to change focus) @@ -156,17 +156,17 @@ export function EditorSettingsDialog({ Editor Preference - + These editors are currently supported. Please note that some editors cannot be used in sandbox mode. - + Your preferred editor is:{' '} diff --git a/packages/cli/src/ui/components/FolderTrustDialog.tsx b/packages/cli/src/ui/components/FolderTrustDialog.tsx index ebcadd7a35..83799a3506 100644 --- a/packages/cli/src/ui/components/FolderTrustDialog.tsx +++ b/packages/cli/src/ui/components/FolderTrustDialog.tsx @@ -6,7 +6,7 @@ import { Box, Text } from 'ink'; import type React from 'react'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import type { RadioSelectItem } from './shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { useKeypress } from '../hooks/useKeypress.js'; @@ -69,14 +69,16 @@ export const FolderTrustDialog: React.FC = ({ - Do you trust this folder? - + + Do you trust this folder? + + Trusting a folder allows Gemini to execute commands it suggests. This is a security feature to prevent accidental execution in untrusted directories. @@ -91,7 +93,7 @@ export const FolderTrustDialog: React.FC = ({ {isRestarting && ( - + To see changes, Gemini CLI must be restarted. Press r to exit and apply changes now. diff --git a/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx b/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx index 057faaffc3..cde45a3ec3 100644 --- a/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx +++ b/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx @@ -14,6 +14,7 @@ import { SCREEN_READER_LOADING, SCREEN_READER_RESPONDING, } from '../textConstants.js'; +import { theme } from '../semantic-colors.js'; interface GeminiRespondingSpinnerProps { /** @@ -40,7 +41,7 @@ export const GeminiRespondingSpinner: React.FC< return isScreenReaderEnabled ? ( {SCREEN_READER_LOADING} ) : ( - {nonRespondingDisplay} + {nonRespondingDisplay} ); } return null; @@ -59,6 +60,8 @@ export const GeminiSpinner: React.FC = ({ return isScreenReaderEnabled ? ( {altText} ) : ( - + + + ); }; diff --git a/packages/cli/src/ui/components/Header.tsx b/packages/cli/src/ui/components/Header.tsx index 5d09ec3b10..694752312f 100644 --- a/packages/cli/src/ui/components/Header.tsx +++ b/packages/cli/src/ui/components/Header.tsx @@ -7,7 +7,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; import Gradient from 'ink-gradient'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { shortAsciiLogo, longAsciiLogo, tinyAsciiLogo } from './AsciiArt.js'; import { getAsciiArtWidth } from '../utils/textUtils.js'; import { useTerminalSize } from '../hooks/useTerminalSize.js'; @@ -47,8 +47,8 @@ export const Header: React.FC = ({ flexShrink={0} flexDirection="column" > - {Colors.GradientColors ? ( - + {theme.ui.gradient ? ( + {displayTitle} ) : ( @@ -56,8 +56,8 @@ export const Header: React.FC = ({ )} {nightly && ( - {Colors.GradientColors ? ( - + {theme.ui.gradient ? ( + v{version} ) : ( diff --git a/packages/cli/src/ui/components/Help.tsx b/packages/cli/src/ui/components/Help.tsx index b2a8a4400a..90a6b26a9c 100644 --- a/packages/cli/src/ui/components/Help.tsx +++ b/packages/cli/src/ui/components/Help.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { type SlashCommand, CommandKind } from '../commands/types.js'; interface Help { @@ -17,42 +17,42 @@ export const Help: React.FC = ({ commands }) => ( {/* Basics */} - + Basics: - - + + Add context : Use{' '} - + @ {' '} to specify files for context (e.g.,{' '} - + @src/myFile.ts ) to target specific files or folders. - - + + Shell mode : Execute shell commands via{' '} - + ! {' '} (e.g.,{' '} - + !npm run start ) or use natural language (e.g.{' '} - + start server ). @@ -61,20 +61,20 @@ export const Help: React.FC = ({ commands }) => ( {/* Commands */} - + Commands: {commands .filter((command) => command.description && !command.hidden) .map((command: SlashCommand) => ( - - + + {' '} /{command.name} {command.kind === CommandKind.MCP_PROMPT && ( - [MCP] + [MCP] )} {command.description && ' - ' + command.description} @@ -82,8 +82,8 @@ export const Help: React.FC = ({ commands }) => ( command.subCommands .filter((subCommand) => !subCommand.hidden) .map((subCommand) => ( - - + + {' '} {subCommand.name} @@ -92,90 +92,90 @@ export const Help: React.FC = ({ commands }) => ( ))} ))} - - + + {' '} !{' '} - shell command - - [MCP] - Model Context Protocol command - (from external servers) + + [MCP] - Model Context Protocol + command (from external servers) {/* Shortcuts */} - + Keyboard Shortcuts: - - + + Alt+Left/Right {' '} - Jump through words in the input - - + + Ctrl+C {' '} - Quit application - - + + {process.platform === 'win32' ? 'Ctrl+Enter' : 'Ctrl+J'} {' '} {process.platform === 'linux' ? '- New line (Alt+Enter works for certain linux distros)' : '- New line'} - - + + Ctrl+L {' '} - Clear the screen - - + + {process.platform === 'darwin' ? 'Ctrl+X / Meta+Enter' : 'Ctrl+X'} {' '} - Open input in external editor - - + + Ctrl+Y {' '} - Toggle YOLO mode - - + + Enter {' '} - Send message - - + + Esc {' '} - Cancel operation / Clear input (double press) - - + + Shift+Tab {' '} - Toggle auto-accepting edits - - + + Up/Down {' '} - Cycle through your prompt history - + For a full list of shortcuts, see{' '} - + docs/keyboard-shortcuts.md diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index dc03b4bea5..10bd2e1812 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -714,7 +714,11 @@ export const InputPrompt: React.FC = ({ @@ -797,7 +801,7 @@ export const InputPrompt: React.FC = ({ const color = token.type === 'command' || token.type === 'file' ? theme.text.accent - : undefined; + : theme.text.primary; renderedLine.push( diff --git a/packages/cli/src/ui/components/LoadingIndicator.tsx b/packages/cli/src/ui/components/LoadingIndicator.tsx index b7a88ee47e..b6bb3a1a4b 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.tsx @@ -7,7 +7,7 @@ import type { ThoughtSummary } from '@google/gemini-cli-core'; import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { useStreamingContext } from '../contexts/StreamingContext.js'; import { StreamingState } from '../types.js'; import { GeminiRespondingSpinner } from './GeminiRespondingSpinner.js'; @@ -61,11 +61,9 @@ export const LoadingIndicator: React.FC = ({ } /> - {primaryText && ( - {primaryText} - )} + {primaryText && {primaryText}} {!isNarrow && cancelAndTimerContent && ( - {cancelAndTimerContent} + {cancelAndTimerContent} )} {!isNarrow && {/* Spacer */}} @@ -73,7 +71,7 @@ export const LoadingIndicator: React.FC = ({ {isNarrow && cancelAndTimerContent && ( - {cancelAndTimerContent} + {cancelAndTimerContent} )} {isNarrow && rightContent && {rightContent}} diff --git a/packages/cli/src/ui/components/MemoryUsageDisplay.tsx b/packages/cli/src/ui/components/MemoryUsageDisplay.tsx index a8b449e524..1ea3c10693 100644 --- a/packages/cli/src/ui/components/MemoryUsageDisplay.tsx +++ b/packages/cli/src/ui/components/MemoryUsageDisplay.tsx @@ -7,20 +7,24 @@ import type React from 'react'; import { useEffect, useState } from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import process from 'node:process'; import { formatMemoryUsage } from '../utils/formatters.js'; export const MemoryUsageDisplay: React.FC = () => { const [memoryUsage, setMemoryUsage] = useState(''); - const [memoryUsageColor, setMemoryUsageColor] = useState(Colors.Gray); + const [memoryUsageColor, setMemoryUsageColor] = useState( + theme.text.secondary, + ); useEffect(() => { const updateMemory = () => { const usage = process.memoryUsage().rss; setMemoryUsage(formatMemoryUsage(usage)); setMemoryUsageColor( - usage >= 2 * 1024 * 1024 * 1024 ? Colors.AccentRed : Colors.Gray, + usage >= 2 * 1024 * 1024 * 1024 + ? theme.status.error + : theme.text.secondary, ); }; const intervalId = setInterval(updateMemory, 2000); @@ -30,7 +34,7 @@ export const MemoryUsageDisplay: React.FC = () => { return ( - | + | {memoryUsage} ); diff --git a/packages/cli/src/ui/components/ModelStatsDisplay.tsx b/packages/cli/src/ui/components/ModelStatsDisplay.tsx index 2316cf8f58..95a8fe4605 100644 --- a/packages/cli/src/ui/components/ModelStatsDisplay.tsx +++ b/packages/cli/src/ui/components/ModelStatsDisplay.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { formatDuration } from '../utils/formatters.js'; import { calculateAverageLatency, @@ -34,13 +34,16 @@ const StatRow: React.FC = ({ }) => ( - + {isSubtle ? ` ↳ ${title}` : title} {values.map((value, index) => ( - {value} + {value} ))} @@ -57,11 +60,13 @@ export const ModelStatsDisplay: React.FC = () => { return ( - No API calls have been made in this session. + + No API calls have been made in this session. + ); } @@ -83,12 +88,12 @@ export const ModelStatsDisplay: React.FC = () => { return ( - + Model Stats For Nerds @@ -96,11 +101,15 @@ export const ModelStatsDisplay: React.FC = () => { {/* Header */} - Metric + + Metric + {modelNames.map((name) => ( - {name} + + {name} + ))} @@ -112,6 +121,7 @@ export const ModelStatsDisplay: React.FC = () => { borderTop={false} borderLeft={false} borderRight={false} + borderColor={theme.border.default} /> {/* API Section */} @@ -127,7 +137,7 @@ export const ModelStatsDisplay: React.FC = () => { return ( 0 ? Colors.AccentRed : Colors.Foreground + m.api.totalErrors > 0 ? theme.status.error : theme.text.primary } > {m.api.totalErrors.toLocaleString()} ({errorRate.toFixed(1)}%) @@ -150,7 +160,7 @@ export const ModelStatsDisplay: React.FC = () => { ( - + {m.tokens.total.toLocaleString()} ))} @@ -167,7 +177,7 @@ export const ModelStatsDisplay: React.FC = () => { values={getModelValues((m) => { const cacheHitRate = calculateCacheHitRate(m); return ( - + {m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%) ); diff --git a/packages/cli/src/ui/components/Notifications.tsx b/packages/cli/src/ui/components/Notifications.tsx index 954945d3ad..a287b10524 100644 --- a/packages/cli/src/ui/components/Notifications.tsx +++ b/packages/cli/src/ui/components/Notifications.tsx @@ -7,7 +7,7 @@ import { Box, Text } from 'ink'; import { useAppContext } from '../contexts/AppContext.js'; import { useUIState } from '../contexts/UIStateContext.js'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { StreamingState } from '../types.js'; import { UpdateNotification } from './UpdateNotification.js'; @@ -29,13 +29,13 @@ export const Notifications = () => { {showStartupWarnings && ( {startupWarnings.map((warning, index) => ( - + {warning} ))} @@ -44,14 +44,14 @@ export const Notifications = () => { {showInitError && ( - + Initialization Error: {initError} - + {' '} Please check API key and configuration. diff --git a/packages/cli/src/ui/components/PrepareLabel.tsx b/packages/cli/src/ui/components/PrepareLabel.tsx index d89c1fe480..37ad5a3313 100644 --- a/packages/cli/src/ui/components/PrepareLabel.tsx +++ b/packages/cli/src/ui/components/PrepareLabel.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; interface PrepareLabelProps { label: string; @@ -21,7 +21,7 @@ export const PrepareLabel: React.FC = ({ matchedIndex, userInput, textColor, - highlightColor = Colors.AccentYellow, + highlightColor = theme.status.warning, }) => { if ( matchedIndex === undefined || diff --git a/packages/cli/src/ui/components/ProQuotaDialog.tsx b/packages/cli/src/ui/components/ProQuotaDialog.tsx index c547967508..17e5965e0b 100644 --- a/packages/cli/src/ui/components/ProQuotaDialog.tsx +++ b/packages/cli/src/ui/components/ProQuotaDialog.tsx @@ -7,7 +7,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; interface ProQuotaDialogProps { failedModel: string; @@ -37,7 +37,7 @@ export function ProQuotaDialog({ return ( - + Pro quota limit reached for {failedModel}. diff --git a/packages/cli/src/ui/components/SettingsDialog.tsx b/packages/cli/src/ui/components/SettingsDialog.tsx index 390fc2f611..17a56e2de0 100644 --- a/packages/cli/src/ui/components/SettingsDialog.tsx +++ b/packages/cli/src/ui/components/SettingsDialog.tsx @@ -6,7 +6,7 @@ import React, { useState, useEffect } from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import type { LoadedSettings, Settings } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js'; import { @@ -656,18 +656,18 @@ export function SettingsDialog({ return ( - + Settings - {showScrollUp && } + {showScrollUp && } {visibleItems.map((item, idx) => { const isActive = focusSection === 'settings' && @@ -748,17 +748,21 @@ export function SettingsDialog({ - + {isActive ? '●' : ''} {item.label} {scopeMessage && ( - {scopeMessage} + {scopeMessage} )} @@ -766,10 +770,10 @@ export function SettingsDialog({ {displayValue} @@ -779,7 +783,7 @@ export function SettingsDialog({ ); })} - {showScrollDown && } + {showScrollDown && } @@ -798,11 +802,11 @@ export function SettingsDialog({ - + (Use Enter to select, Tab to change focus) {showRestartPrompt && ( - + To see changes, Gemini CLI must be restarted. Press r to exit and apply changes now. diff --git a/packages/cli/src/ui/components/ShellConfirmationDialog.tsx b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx index 4315995682..bd5ef15400 100644 --- a/packages/cli/src/ui/components/ShellConfirmationDialog.tsx +++ b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx @@ -7,7 +7,7 @@ import { ToolConfirmationOutcome } from '@google/gemini-cli-core'; import { Box, Text } from 'ink'; import type React from 'react'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { RenderInline } from '../utils/InlineMarkdownRenderer.js'; import type { RadioSelectItem } from './shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; @@ -68,23 +68,27 @@ export const ShellConfirmationDialog: React.FC< - Shell Command Execution - A custom command wants to run the following shell commands: + + Shell Command Execution + + + A custom command wants to run the following shell commands: + {commands.map((cmd) => ( - + ))} @@ -92,7 +96,7 @@ export const ShellConfirmationDialog: React.FC< - Do you want to proceed? + Do you want to proceed? diff --git a/packages/cli/src/ui/components/ShellModeIndicator.tsx b/packages/cli/src/ui/components/ShellModeIndicator.tsx index 23d7174017..6fbde05c68 100644 --- a/packages/cli/src/ui/components/ShellModeIndicator.tsx +++ b/packages/cli/src/ui/components/ShellModeIndicator.tsx @@ -6,13 +6,13 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; export const ShellModeIndicator: React.FC = () => ( - + shell mode enabled - (esc to disable) + (esc to disable) ); diff --git a/packages/cli/src/ui/components/ShowMoreLines.tsx b/packages/cli/src/ui/components/ShowMoreLines.tsx index 41232d9483..8823eee620 100644 --- a/packages/cli/src/ui/components/ShowMoreLines.tsx +++ b/packages/cli/src/ui/components/ShowMoreLines.tsx @@ -8,7 +8,7 @@ import { Box, Text } from 'ink'; import { useOverflowState } from '../contexts/OverflowContext.js'; import { useStreamingContext } from '../contexts/StreamingContext.js'; import { StreamingState } from '../types.js'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; interface ShowMoreLinesProps { constrainHeight: boolean; @@ -32,7 +32,7 @@ export const ShowMoreLines = ({ constrainHeight }: ShowMoreLinesProps) => { return ( - + Press ctrl-s to show more lines diff --git a/packages/cli/src/ui/components/StatsDisplay.tsx b/packages/cli/src/ui/components/StatsDisplay.tsx index dd9879d77a..f40d055ad3 100644 --- a/packages/cli/src/ui/components/StatsDisplay.tsx +++ b/packages/cli/src/ui/components/StatsDisplay.tsx @@ -47,7 +47,7 @@ const SubStatRow: React.FC = ({ title, children }) => ( {/* Adjust width for the "» " prefix */} - » {title} + » {title} {/* FIX: Apply the same flexGrow fix here */} {children} @@ -62,7 +62,9 @@ interface SectionProps { const Section: React.FC = ({ title, children }) => ( - {title} + + {title} + {children} ); @@ -82,16 +84,24 @@ const ModelUsageTable: React.FC<{ {/* Header */} - Model Usage + + Model Usage + - Reqs + + Reqs + - Input Tokens + + Input Tokens + - Output Tokens + + Output Tokens + {/* Divider */} @@ -101,6 +111,7 @@ const ModelUsageTable: React.FC<{ borderTop={false} borderLeft={false} borderRight={false} + borderColor={theme.border.default} width={nameWidth + requestsWidth + inputTokensWidth + outputTokensWidth} > @@ -108,10 +119,12 @@ const ModelUsageTable: React.FC<{ {Object.entries(models).map(([name, modelMetrics]) => ( - {name.replace('-001', '')} + {name.replace('-001', '')} - {modelMetrics.api.totalRequests} + + {modelMetrics.api.totalRequests} + @@ -127,7 +140,7 @@ const ModelUsageTable: React.FC<{ ))} {cacheEfficiency > 0 && ( - + Savings Highlight:{' '} {totalCachedTokens.toLocaleString()} ({cacheEfficiency.toFixed(1)} %) of input tokens were served from the cache, reducing costs. @@ -202,10 +215,10 @@ export const StatsDisplay: React.FC = ({
- {stats.sessionId} + {stats.sessionId} - + {tools.totalCalls} ({' '} ✓ {tools.totalSuccess}{' '} x {tools.totalFail} ) @@ -227,7 +240,7 @@ export const StatsDisplay: React.FC = ({ {files && (files.totalLinesAdded > 0 || files.totalLinesRemoved > 0) && ( - + +{files.totalLinesAdded} {' '} @@ -241,13 +254,15 @@ export const StatsDisplay: React.FC = ({
- {duration} + {duration} - {formatDuration(computed.agentActiveTime)} + + {formatDuration(computed.agentActiveTime)} + - + {formatDuration(computed.totalApiTime)}{' '} ({computed.apiTimePercent.toFixed(1)}%) @@ -255,7 +270,7 @@ export const StatsDisplay: React.FC = ({ - + {formatDuration(computed.totalToolTime)}{' '} ({computed.toolTimePercent.toFixed(1)}%) diff --git a/packages/cli/src/ui/components/SuggestionsDisplay.tsx b/packages/cli/src/ui/components/SuggestionsDisplay.tsx index b1be0e255f..0e8bf8e18f 100644 --- a/packages/cli/src/ui/components/SuggestionsDisplay.tsx +++ b/packages/cli/src/ui/components/SuggestionsDisplay.tsx @@ -5,7 +5,7 @@ */ import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { PrepareLabel } from './PrepareLabel.js'; import { CommandKind } from '../commands/types.js'; export interface Suggestion { @@ -56,12 +56,12 @@ export function SuggestionsDisplay({ return ( - {scrollOffset > 0 && } + {scrollOffset > 0 && } {visibleSuggestions.map((suggestion, index) => { const originalIndex = startIndex + index; const isActive = originalIndex === activeIndex; - const textColor = isActive ? Colors.AccentPurple : Colors.Gray; + const textColor = isActive ? theme.text.accent : theme.text.secondary; const labelElement = ( {labelElement} {suggestion.commandKind === CommandKind.MCP_PROMPT && ( - [MCP] + [MCP] )} ) : ( diff --git a/packages/cli/src/ui/components/ThemeDialog.tsx b/packages/cli/src/ui/components/ThemeDialog.tsx index 21fc78161d..c4bc64c302 100644 --- a/packages/cli/src/ui/components/ThemeDialog.tsx +++ b/packages/cli/src/ui/components/ThemeDialog.tsx @@ -7,7 +7,7 @@ import type React from 'react'; import { useCallback, useState } from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { themeManager, DEFAULT_THEME } from '../themes/theme-manager.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { DiffRenderer } from './messages/DiffRenderer.js'; @@ -183,7 +183,7 @@ export function ThemeDialog({ return ( {mode === 'theme' ? '> ' : ' '}Select Theme{' '} - {otherScopeModifiedMessage} + + {otherScopeModifiedMessage} + )} - + (Use Enter to {mode === 'theme' ? 'select' : 'apply scope'}, Tab to{' '} {mode === 'theme' ? 'configure scope' : 'select theme'}) diff --git a/packages/cli/src/ui/components/Tips.tsx b/packages/cli/src/ui/components/Tips.tsx index 7fb9431c3b..576b8494c5 100644 --- a/packages/cli/src/ui/components/Tips.tsx +++ b/packages/cli/src/ui/components/Tips.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { type Config } from '@google/gemini-cli-core'; interface TipsProps { @@ -17,25 +17,25 @@ export const Tips: React.FC = ({ config }) => { const geminiMdFileCount = config.getGeminiMdFileCount(); return ( - Tips for getting started: - + Tips for getting started: + 1. Ask questions, edit files, or run commands. - + 2. Be specific for the best results. {geminiMdFileCount === 0 && ( - + 3. Create{' '} - + GEMINI.md {' '} files to customize your interactions with Gemini. )} - + {geminiMdFileCount === 0 ? '4.' : '3.'}{' '} - + /help {' '} for more information. diff --git a/packages/cli/src/ui/components/ToolStatsDisplay.tsx b/packages/cli/src/ui/components/ToolStatsDisplay.tsx index e77e191c69..64c0d146ce 100644 --- a/packages/cli/src/ui/components/ToolStatsDisplay.tsx +++ b/packages/cli/src/ui/components/ToolStatsDisplay.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { formatDuration } from '../utils/formatters.js'; import { getStatusColor, @@ -37,16 +37,16 @@ const StatRow: React.FC<{ return ( - {name} + {name} - {stats.count} + {stats.count} {successRate.toFixed(1)}% - {formatDuration(avgDuration)} + {formatDuration(avgDuration)} ); @@ -63,11 +63,13 @@ export const ToolStatsDisplay: React.FC = () => { return ( - No tool calls have been made in this session. + + No tool calls have been made in this session. + ); } @@ -94,13 +96,13 @@ export const ToolStatsDisplay: React.FC = () => { return ( - + Tool Stats For Nerds @@ -108,16 +110,24 @@ export const ToolStatsDisplay: React.FC = () => { {/* Header */} - Tool Name + + Tool Name + - Calls + + Calls + - Success Rate + + Success Rate + - Avg Duration + + Avg Duration + @@ -128,6 +138,7 @@ export const ToolStatsDisplay: React.FC = () => { borderTop={false} borderLeft={false} borderRight={false} + borderColor={theme.border.default} width="100%" /> @@ -139,45 +150,47 @@ export const ToolStatsDisplay: React.FC = () => { {/* User Decision Summary */} - User Decision Summary + + User Decision Summary + - Total Reviewed Suggestions: + Total Reviewed Suggestions: - {totalReviewed} + {totalReviewed} - » Accepted: + » Accepted: - {totalDecisions.accept} + {totalDecisions.accept} - » Rejected: + » Rejected: - {totalDecisions.reject} + {totalDecisions.reject} - » Modified: + » Modified: - {totalDecisions.modify} + {totalDecisions.modify} @@ -188,6 +201,7 @@ export const ToolStatsDisplay: React.FC = () => { borderTop={false} borderLeft={false} borderRight={false} + borderColor={theme.border.default} width="100%" /> @@ -195,7 +209,7 @@ export const ToolStatsDisplay: React.FC = () => { - Overall Agreement Rate: + Overall Agreement Rate: 0 ? agreementColor : undefined}> diff --git a/packages/cli/src/ui/components/UpdateNotification.tsx b/packages/cli/src/ui/components/UpdateNotification.tsx index b88c9bd5d0..8142a2018e 100644 --- a/packages/cli/src/ui/components/UpdateNotification.tsx +++ b/packages/cli/src/ui/components/UpdateNotification.tsx @@ -5,7 +5,7 @@ */ import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; interface UpdateNotificationProps { message: string; @@ -14,10 +14,10 @@ interface UpdateNotificationProps { export const UpdateNotification = ({ message }: UpdateNotificationProps) => ( - {message} + {message} ); diff --git a/packages/cli/src/ui/components/WorkspaceMigrationDialog.tsx b/packages/cli/src/ui/components/WorkspaceMigrationDialog.tsx index c53de1cfb4..e642407db5 100644 --- a/packages/cli/src/ui/components/WorkspaceMigrationDialog.tsx +++ b/packages/cli/src/ui/components/WorkspaceMigrationDialog.tsx @@ -10,7 +10,7 @@ import { performWorkspaceExtensionMigration, } from '../../config/extension.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { useState } from 'react'; export function WorkspaceMigrationDialog(props: { @@ -40,15 +40,15 @@ export function WorkspaceMigrationDialog(props: { {failedExtensions.length > 0 ? ( <> - + The following extensions failed to migrate. Please try installing them manually. To see other changes, Gemini CLI must be restarted. - Press {"'q'"} to quit. + Press 'q' to quit. {failedExtensions.map((failed) => ( @@ -57,9 +57,9 @@ export function WorkspaceMigrationDialog(props: { ) : ( - + Migration complete. To see changes, Gemini CLI must be restarted. - Press {"'q'"} to quit. + Press 'q' to quit. )} @@ -70,15 +70,19 @@ export function WorkspaceMigrationDialog(props: { - Workspace-level extensions are deprecated{'\n'} - Would you like to install them at the user level? - + + Workspace-level extensions are deprecated{'\n'} + + + Would you like to install them at the user level? + + The extension definition will remain in your workspace directory. - + If you opt to skip, you can install them manually using the extensions install command. diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx index 7663172e23..929150b5bf 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx @@ -8,7 +8,7 @@ import type React from 'react'; import { Box, Text } from 'ink'; import type { CompressionProps } from '../../types.js'; import Spinner from 'ink-spinner'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js'; export interface CompressionDisplayProps { @@ -33,13 +33,13 @@ export const CompressionMessage: React.FC = ({ {compression.isPending ? ( ) : ( - + )} diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.tsx index f855c97f56..d962d683b8 100644 --- a/packages/cli/src/ui/components/messages/DiffRenderer.tsx +++ b/packages/cli/src/ui/components/messages/DiffRenderer.tsx @@ -6,11 +6,11 @@ import type React from 'react'; import { Box, Text, useIsScreenReaderEnabled } from 'ink'; -import { Colors } from '../../colors.js'; import crypto from 'node:crypto'; import { colorizeCode, colorizeLine } from '../../utils/CodeColorizer.js'; import { MaxSizedBox } from '../shared/MaxSizedBox.js'; -import { theme } from '../../semantic-colors.js'; +import { theme as semanticTheme } from '../../semantic-colors.js'; +import type { Theme } from '../../themes/theme.js'; interface DiffLine { type: 'add' | 'del' | 'context' | 'hunk' | 'other'; @@ -42,18 +42,9 @@ function parseDiffWithLineNumbers(diffContent: string): DiffLine[] { } if (!inHunk) { // Skip standard Git header lines more robustly - if ( - line.startsWith('--- ') || - line.startsWith('+++ ') || - line.startsWith('diff --git') || - line.startsWith('index ') || - line.startsWith('similarity index') || - line.startsWith('rename from') || - line.startsWith('rename to') || - line.startsWith('new file mode') || - line.startsWith('deleted file mode') - ) + if (line.startsWith('--- ')) { continue; + } // If it's not a hunk or header, skip (or handle as 'other' if needed) continue; } @@ -94,7 +85,7 @@ interface DiffRendererProps { tabWidth?: number; availableTerminalHeight?: number; terminalWidth: number; - theme?: import('../../themes/theme.js').Theme; + theme?: Theme; } const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization @@ -109,14 +100,18 @@ export const DiffRenderer: React.FC = ({ }) => { const screenReaderEnabled = useIsScreenReaderEnabled(); if (!diffContent || typeof diffContent !== 'string') { - return No diff content.; + return No diff content.; } const parsedLines = parseDiffWithLineNumbers(diffContent); if (parsedLines.length === 0) { return ( - + No changes detected. ); @@ -196,7 +191,11 @@ const renderDiffContent = ( if (displayableLines.length === 0) { return ( - + No changes detected. ); @@ -260,7 +259,7 @@ const renderDiffContent = ( ) { acc.push( - + {'═'.repeat(terminalWidth)} , @@ -301,12 +300,12 @@ const renderDiffContent = ( acc.push( @@ -323,16 +322,16 @@ const renderDiffContent = ( {prefixSymbol} diff --git a/packages/cli/src/ui/components/messages/ErrorMessage.tsx b/packages/cli/src/ui/components/messages/ErrorMessage.tsx index 52a03a89de..71794ee41c 100644 --- a/packages/cli/src/ui/components/messages/ErrorMessage.tsx +++ b/packages/cli/src/ui/components/messages/ErrorMessage.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Text, Box } from 'ink'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; interface ErrorMessageProps { text: string; @@ -19,10 +19,10 @@ export const ErrorMessage: React.FC = ({ text }) => { return ( - {prefix} + {prefix} - + {text} diff --git a/packages/cli/src/ui/components/messages/GeminiMessage.tsx b/packages/cli/src/ui/components/messages/GeminiMessage.tsx index 9473c12885..389b5ac151 100644 --- a/packages/cli/src/ui/components/messages/GeminiMessage.tsx +++ b/packages/cli/src/ui/components/messages/GeminiMessage.tsx @@ -7,7 +7,7 @@ import type React from 'react'; import { Text, Box } from 'ink'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js'; interface GeminiMessageProps { @@ -29,10 +29,7 @@ export const GeminiMessage: React.FC = ({ return ( - + {prefix} diff --git a/packages/cli/src/ui/components/messages/InfoMessage.tsx b/packages/cli/src/ui/components/messages/InfoMessage.tsx index 3d7866bec0..e8d09d637e 100644 --- a/packages/cli/src/ui/components/messages/InfoMessage.tsx +++ b/packages/cli/src/ui/components/messages/InfoMessage.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Text, Box } from 'ink'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; import { RenderInline } from '../../utils/InlineMarkdownRenderer.js'; interface InfoMessageProps { @@ -20,10 +20,10 @@ export const InfoMessage: React.FC = ({ text }) => { return ( - {prefix} + {prefix} - + diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx index a440e59e85..427e3fb38c 100644 --- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx @@ -7,7 +7,6 @@ import type React from 'react'; import { Box, Text } from 'ink'; import { DiffRenderer } from './DiffRenderer.js'; -import { Colors } from '../../colors.js'; import { RenderInline } from '../../utils/InlineMarkdownRenderer.js'; import type { ToolCallConfirmationDetails, @@ -20,6 +19,7 @@ import type { RadioSelectItem } from '../shared/RadioButtonSelect.js'; import { RadioButtonSelect } from '../shared/RadioButtonSelect.js'; import { MaxSizedBox } from '../shared/MaxSizedBox.js'; import { useKeypress } from '../../hooks/useKeypress.js'; +import { theme } from '../../semantic-colors.js'; export interface ToolConfirmationMessageProps { confirmationDetails: ToolCallConfirmationDetails; @@ -113,13 +113,13 @@ export const ToolConfirmationMessage: React.FC< - Modify in progress: - + Modify in progress: + Save and close external editor to continue @@ -193,7 +193,7 @@ export const ToolConfirmationMessage: React.FC< maxWidth={Math.max(childWidth - 4, 1)} > - {executionProps.command} + {executionProps.command} @@ -223,12 +223,12 @@ export const ToolConfirmationMessage: React.FC< bodyContent = ( - + {displayUrls && infoProps.urls && infoProps.urls.length > 0 && ( - URLs to fetch: + URLs to fetch: {infoProps.urls.map((url) => ( {' '} @@ -245,8 +245,8 @@ export const ToolConfirmationMessage: React.FC< bodyContent = ( - MCP Server: {mcpProps.serverName} - Tool: {mcpProps.toolName} + MCP Server: {mcpProps.serverName} + Tool: {mcpProps.toolName} ); @@ -281,7 +281,9 @@ export const ToolConfirmationMessage: React.FC< {/* Confirmation Question */} - {question} + + {question} + {/* Select Input for Options */} diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx index bfa2db3343..65ac70a58c 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx @@ -122,7 +122,9 @@ export const ToolGroupMessage: React.FC = ({ )} {tool.outputFile && ( - Output too long and was saved to: {tool.outputFile} + + Output too long and was saved to: {tool.outputFile} + )} diff --git a/packages/cli/src/ui/components/messages/ToolMessage.tsx b/packages/cli/src/ui/components/messages/ToolMessage.tsx index c4e5b6baf4..e5e193067c 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.tsx @@ -9,11 +9,11 @@ import { Box, Text } from 'ink'; import type { IndividualToolCallDisplay } from '../../types.js'; import { ToolCallStatus } from '../../types.js'; import { DiffRenderer } from './DiffRenderer.js'; -import { Colors } from '../../colors.js'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js'; import { MaxSizedBox } from '../shared/MaxSizedBox.js'; import { TOOL_STATUS } from '../../constants.js'; +import { theme } from '../../semantic-colors.js'; const STATIC_HEIGHT = 1; const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc. @@ -120,7 +120,7 @@ const ToolStatusIndicator: React.FC = ({ }) => ( {status === ToolCallStatus.Pending && ( - {TOOL_STATUS.PENDING} + {TOOL_STATUS.PENDING} )} {status === ToolCallStatus.Executing && ( = ({ /> )} {status === ToolCallStatus.Success && ( - + {TOOL_STATUS.SUCCESS} )} {status === ToolCallStatus.Confirming && ( - + {TOOL_STATUS.CONFIRMING} )} {status === ToolCallStatus.Canceled && ( - + {TOOL_STATUS.CANCELED} )} {status === ToolCallStatus.Error && ( - + {TOOL_STATUS.ERROR} )} @@ -166,11 +166,11 @@ const ToolInfo: React.FC = ({ const nameColor = React.useMemo(() => { switch (emphasis) { case 'high': - return Colors.Foreground; + return theme.text.primary; case 'medium': - return Colors.Foreground; + return theme.text.primary; case 'low': - return Colors.Gray; + return theme.text.secondary; default: { const exhaustiveCheck: never = emphasis; return exhaustiveCheck; @@ -186,14 +186,14 @@ const ToolInfo: React.FC = ({ {name} {' '} - {description} + {description} ); }; const TrailingIndicator: React.FC = () => ( - + {' '} ← diff --git a/packages/cli/src/ui/components/messages/UserMessage.tsx b/packages/cli/src/ui/components/messages/UserMessage.tsx index 4f279a747f..2de3ed0df8 100644 --- a/packages/cli/src/ui/components/messages/UserMessage.tsx +++ b/packages/cli/src/ui/components/messages/UserMessage.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { Text, Box } from 'ink'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; import { SCREEN_READER_USER_PREFIX } from '../../textConstants.js'; import { isSlashCommand as checkIsSlashCommand } from '../../utils/commandUtils.js'; @@ -19,8 +19,8 @@ export const UserMessage: React.FC = ({ text }) => { const prefixWidth = prefix.length; const isSlashCommand = checkIsSlashCommand(text); - const textColor = isSlashCommand ? Colors.AccentPurple : Colors.Gray; - const borderColor = isSlashCommand ? Colors.AccentPurple : Colors.Gray; + const textColor = isSlashCommand ? theme.text.accent : theme.text.secondary; + const borderColor = isSlashCommand ? theme.text.accent : theme.text.secondary; return ( = ({ text }) => { return ( - $ - {commandToDisplay} + $ + {commandToDisplay} ); }; diff --git a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx index 74092324a7..86b36cd28d 100644 --- a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx +++ b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx @@ -7,7 +7,7 @@ import React, { Fragment, useEffect, useId } from 'react'; import { Box, Text } from 'ink'; import stringWidth from 'string-width'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; import { toCodePoints } from '../../utils/textUtils.js'; import { useOverflowActions } from '../../contexts/OverflowContext.js'; @@ -186,14 +186,14 @@ export const MaxSizedBox: React.FC = ({ return ( {totalHiddenLines > 0 && overflowDirection === 'top' && ( - + ... first {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '} hidden ... )} {visibleLines} {totalHiddenLines > 0 && overflowDirection === 'bottom' && ( - + ... last {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '} hidden ... diff --git a/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx b/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx index 719d263b96..ab62e5d1b1 100644 --- a/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx +++ b/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx @@ -7,7 +7,7 @@ import type React from 'react'; import { useEffect, useState, useRef } from 'react'; import { Text, Box } from 'ink'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; import { useKeypress } from '../../hooks/useKeypress.js'; /** @@ -164,7 +164,9 @@ export function RadioButtonSelect({ return ( {showScrollArrows && ( - 0 ? Colors.Foreground : Colors.Gray}> + 0 ? theme.text.primary : theme.text.secondary} + > ▲ )} @@ -172,18 +174,18 @@ export function RadioButtonSelect({ const itemIndex = scrollOffset + index; const isSelected = activeIndex === itemIndex; - let textColor = Colors.Foreground; - let numberColor = Colors.Foreground; + let textColor = theme.text.primary; + let numberColor = theme.text.primary; if (isSelected) { - textColor = Colors.AccentGreen; - numberColor = Colors.AccentGreen; + textColor = theme.status.success; + numberColor = theme.status.success; } else if (item.disabled) { - textColor = Colors.Gray; - numberColor = Colors.Gray; + textColor = theme.text.secondary; + numberColor = theme.text.secondary; } if (!showNumbers) { - numberColor = Colors.Gray; + numberColor = theme.text.secondary; } const numberColumnWidth = String(items.length).length; @@ -195,7 +197,7 @@ export function RadioButtonSelect({ {isSelected ? '●' : ' '} @@ -212,7 +214,9 @@ export function RadioButtonSelect({ {item.themeNameDisplay && item.themeTypeDisplay ? ( {item.themeNameDisplay}{' '} - {item.themeTypeDisplay} + + {item.themeTypeDisplay} + ) : ( @@ -226,8 +230,8 @@ export function RadioButtonSelect({ ▼ diff --git a/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.tsx b/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.tsx index a1c279304e..b6be9a7269 100644 --- a/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.tsx +++ b/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.tsx @@ -9,7 +9,7 @@ import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js'; import { usePrivacySettings } from '../hooks/usePrivacySettings.js'; import { CloudPaidPrivacyNotice } from './CloudPaidPrivacyNotice.js'; import type { Config } from '@google/gemini-cli-core'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { useKeypress } from '../hooks/useKeypress.js'; interface CloudFreePrivacyNoticeProps { @@ -34,16 +34,16 @@ export const CloudFreePrivacyNotice = ({ ); if (privacyState.isLoading) { - return Loading...; + return Loading...; } if (privacyState.error) { return ( - + Error loading Opt-in settings: {privacyState.error} - Press Esc to exit. + Press Esc to exit. ); } @@ -59,17 +59,17 @@ export const CloudFreePrivacyNotice = ({ return ( - + Gemini Code Assist for Individuals Privacy Notice - + This notice and our Privacy Policy - [1] describe how Gemini Code - Assist handles your data. Please read them carefully. + [1] describe how Gemini Code Assist + handles your data. Please read them carefully. - + When you use Gemini Code Assist for individuals with Gemini CLI, Google collects your prompts, related code, generated output, code edits, related feature usage information, and your feedback to provide, @@ -77,7 +77,7 @@ export const CloudFreePrivacyNotice = ({ technologies. - + To help with quality and improve our products (such as generative machine-learning models), human reviewers may read, annotate, and process the data collected above. We take steps to protect your privacy @@ -90,7 +90,7 @@ export const CloudFreePrivacyNotice = ({ - + Allow Google to use this data to develop and improve our products? - [1]{' '} + [1]{' '} https://policies.google.com/privacy - Press Enter to choose an option and exit. + + Press Enter to choose an option and exit. + ); }; diff --git a/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.tsx b/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.tsx index f0adbb68e2..ce640308ec 100644 --- a/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.tsx +++ b/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.tsx @@ -5,7 +5,7 @@ */ import { Box, Newline, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { useKeypress } from '../hooks/useKeypress.js'; interface CloudPaidPrivacyNoticeProps { @@ -26,14 +26,14 @@ export const CloudPaidPrivacyNotice = ({ return ( - + Vertex AI Notice - - Service Specific Terms[1] are + + Service Specific Terms[1] are incorporated into the agreement under which Google has agreed to provide - Google Cloud Platform[2] to + Google Cloud Platform[2] to Customer (the “Agreement”). If the Agreement authorizes the resale or supply of Google Cloud Platform under a Google Cloud partner or reseller program, then except for in the section entitled “Partner-Specific @@ -44,16 +44,16 @@ export const CloudPaidPrivacyNotice = ({ them in the Agreement. - - [1]{' '} + + [1]{' '} https://cloud.google.com/terms/service-terms - - [2]{' '} + + [2]{' '} https://cloud.google.com/terms/services - Press Esc to exit. + Press Esc to exit. ); }; diff --git a/packages/cli/src/ui/privacy/GeminiPrivacyNotice.tsx b/packages/cli/src/ui/privacy/GeminiPrivacyNotice.tsx index c0eaa74f2d..1f4015b5c2 100644 --- a/packages/cli/src/ui/privacy/GeminiPrivacyNotice.tsx +++ b/packages/cli/src/ui/privacy/GeminiPrivacyNotice.tsx @@ -5,7 +5,7 @@ */ import { Box, Newline, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { useKeypress } from '../hooks/useKeypress.js'; interface GeminiPrivacyNoticeProps { @@ -24,39 +24,39 @@ export const GeminiPrivacyNotice = ({ onExit }: GeminiPrivacyNoticeProps) => { return ( - + Gemini API Key Notice - - By using the Gemini API[1], - Google AI Studio - [2], and the other Google + + By using the Gemini API[1], Google + AI Studio + [2], and the other Google developer services that reference these terms (collectively, the "APIs" or "Services"), you are agreeing to Google APIs Terms of Service (the "API Terms") - [3], and the Gemini API + [3], and the Gemini API Additional Terms of Service (the "Additional Terms") - [4]. + [4]. - - [1]{' '} + + [1]{' '} https://ai.google.dev/docs/gemini_api_overview - - [2] https://aistudio.google.com/ + + [2] https://aistudio.google.com/ - - [3]{' '} + + [3]{' '} https://developers.google.com/terms - - [4]{' '} + + [4]{' '} https://ai.google.dev/gemini-api/terms - Press Esc to exit. + Press Esc to exit. ); }; diff --git a/packages/cli/src/ui/themes/theme.ts b/packages/cli/src/ui/themes/theme.ts index ed6db89714..c04893c669 100644 --- a/packages/cli/src/ui/themes/theme.ts +++ b/packages/cli/src/ui/themes/theme.ts @@ -410,31 +410,31 @@ export function createCustomTheme(customTheme: CustomTheme): Theme { const semanticColors: SemanticColors = { text: { - primary: colors.Foreground, - secondary: colors.Gray, - link: colors.AccentBlue, - accent: colors.AccentPurple, + primary: customTheme.text?.primary ?? colors.Foreground, + secondary: customTheme.text?.secondary ?? colors.Gray, + link: customTheme.text?.link ?? colors.AccentBlue, + accent: customTheme.text?.accent ?? colors.AccentPurple, }, background: { - primary: colors.Background, + primary: customTheme.background?.primary ?? colors.Background, diff: { - added: colors.DiffAdded, - removed: colors.DiffRemoved, + added: customTheme.background?.diff?.added ?? colors.DiffAdded, + removed: customTheme.background?.diff?.removed ?? colors.DiffRemoved, }, }, border: { - default: colors.Gray, - focused: colors.AccentBlue, + default: customTheme.border?.default ?? colors.Gray, + focused: customTheme.border?.focused ?? colors.AccentBlue, }, ui: { - comment: colors.Comment, - symbol: colors.Gray, - gradient: colors.GradientColors, + comment: customTheme.ui?.comment ?? colors.Comment, + symbol: customTheme.ui?.symbol ?? colors.Gray, + gradient: customTheme.ui?.gradient ?? colors.GradientColors, }, status: { - error: colors.AccentRed, - success: colors.AccentGreen, - warning: colors.AccentYellow, + error: customTheme.status?.error ?? colors.AccentRed, + success: customTheme.status?.success ?? colors.AccentGreen, + warning: customTheme.status?.warning ?? colors.AccentYellow, }, }; diff --git a/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx b/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx index 4c05a28f4e..4320c51967 100644 --- a/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx +++ b/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx @@ -6,13 +6,13 @@ import React from 'react'; import { Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import stringWidth from 'string-width'; // Constants for Markdown parsing const BOLD_MARKER_LENGTH = 2; // For "**" const ITALIC_MARKER_LENGTH = 1; // For "*" or "_" -const STRIKETHROUGH_MARKER_LENGTH = 2; // For "~~" +const STRIKETHROUGH_MARKER_LENGTH = 2; // For "~~") const INLINE_CODE_MARKER_LENGTH = 1; // For "`" const UNDERLINE_TAG_START_LENGTH = 3; // For "" const UNDERLINE_TAG_END_LENGTH = 4; // For "" @@ -24,7 +24,7 @@ interface RenderInlineProps { const RenderInlineInternal: React.FC = ({ text }) => { // Early return for plain text without markdown or URLs if (!/[*_~`<[https?:]/.test(text)) { - return {text}; + return {text}; } const nodes: React.ReactNode[] = []; @@ -96,7 +96,7 @@ const RenderInlineInternal: React.FC = ({ text }) => { const codeMatch = fullMatch.match(/^(`+)(.+?)\1$/s); if (codeMatch && codeMatch[2]) { renderedNode = ( - + {codeMatch[2]} ); @@ -113,7 +113,7 @@ const RenderInlineInternal: React.FC = ({ text }) => { renderedNode = ( {linkText} - ({url}) + ({url}) ); } @@ -133,7 +133,7 @@ const RenderInlineInternal: React.FC = ({ text }) => { ); } else if (fullMatch.match(/^https?:\/\//)) { renderedNode = ( - + {fullMatch} ); @@ -168,6 +168,6 @@ export const getPlainTextLength = (text: string): number => { .replace(/~~(.*?)~~/g, '$1') .replace(/`(.*?)`/g, '$1') .replace(/(.*?)<\/u>/g, '$1') - .replace(/\[(.*?)\]\(.*?\)/g, '$1'); + .replace(/.*\[(.*?)\]\(.*\)/g, '$1'); return stringWidth(cleanText); }; diff --git a/packages/cli/src/ui/utils/MarkdownDisplay.tsx b/packages/cli/src/ui/utils/MarkdownDisplay.tsx index f5cbd84b3f..42ea164805 100644 --- a/packages/cli/src/ui/utils/MarkdownDisplay.tsx +++ b/packages/cli/src/ui/utils/MarkdownDisplay.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Text, Box } from 'ink'; import { EOL } from 'node:os'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { colorizeCode } from './CodeColorizer.js'; import { TableRenderer } from './TableRenderer.js'; import { RenderInline } from './InlineMarkdownRenderer.js'; @@ -174,14 +174,14 @@ const MarkdownDisplayInternal: React.FC = ({ switch (level) { case 1: headerNode = ( - + ); break; case 2: headerNode = ( - + ); @@ -195,14 +195,14 @@ const MarkdownDisplayInternal: React.FC = ({ break; case 4: headerNode = ( - + ); break; default: headerNode = ( - + ); @@ -246,7 +246,7 @@ const MarkdownDisplayInternal: React.FC = ({ } else { addContentBlock( - + , @@ -315,7 +315,9 @@ const RenderCodeBlockInternal: React.FC = ({ // Not enough space to even show the message meaningfully return ( - ... code is being written ... + + ... code is being written ... + ); } @@ -331,7 +333,7 @@ const RenderCodeBlockInternal: React.FC = ({ return ( {colorizedTruncatedCode} - ... generating more ... + ... generating more ... ); } diff --git a/packages/cli/src/ui/utils/TableRenderer.tsx b/packages/cli/src/ui/utils/TableRenderer.tsx index 2ec195491d..3c1af38170 100644 --- a/packages/cli/src/ui/utils/TableRenderer.tsx +++ b/packages/cli/src/ui/utils/TableRenderer.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { Text, Box } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { RenderInline, getPlainTextLength } from './InlineMarkdownRenderer.js'; interface TableRendererProps { @@ -89,7 +89,7 @@ export const TableRenderer: React.FC = ({ return ( {isHeader ? ( - + ) : ( @@ -112,7 +112,7 @@ export const TableRenderer: React.FC = ({ const borderParts = adjustedWidths.map((w) => char.horizontal.repeat(w)); const border = char.left + borderParts.join(char.middle) + char.right; - return {border}; + return {border}; }; // Helper function to render a table row @@ -123,7 +123,7 @@ export const TableRenderer: React.FC = ({ }); return ( - + │{' '} {renderedCells.map((cell, index) => ( diff --git a/packages/cli/src/ui/utils/displayUtils.ts b/packages/cli/src/ui/utils/displayUtils.ts index a52c6ff045..6f6c9209db 100644 --- a/packages/cli/src/ui/utils/displayUtils.ts +++ b/packages/cli/src/ui/utils/displayUtils.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; // --- Thresholds --- export const TOOL_SUCCESS_RATE_HIGH = 95; @@ -23,10 +23,10 @@ export const getStatusColor = ( options: { defaultColor?: string } = {}, ) => { if (value >= thresholds.green) { - return Colors.AccentGreen; + return theme.status.success; } if (value >= thresholds.yellow) { - return Colors.AccentYellow; + return theme.status.warning; } - return options.defaultColor || Colors.AccentRed; + return options.defaultColor || theme.status.error; };