diff options
| author | Miguel Solorio <[email protected]> | 2025-08-15 15:39:54 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-15 22:39:54 +0000 |
| commit | 3c0af3654ac5491e79c6f9b55de5debf0e1e13c1 (patch) | |
| tree | 51eabe9544038b06b0c0a78d31dfa25232de3754 /packages/cli/src | |
| parent | 5246aa11f49108a22d4ba306a49b1af79153cac1 (diff) | |
Update semantic color tokens (#6253)
Co-authored-by: jacob314 <[email protected]>
Diffstat (limited to 'packages/cli/src')
60 files changed, 606 insertions, 502 deletions
diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 9773971b..ca391e83 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -38,7 +38,7 @@ import { EditorSettingsDialog } from './components/EditorSettingsDialog.js'; import { FolderTrustDialog } from './components/FolderTrustDialog.js'; import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js'; import { RadioButtonSelect } from './components/shared/RadioButtonSelect.js'; -import { Colors } from './colors.js'; +import { theme } from './semantic-colors.js'; import { loadHierarchicalGeminiMemory } from '../config/config.js'; import { LoadedSettings, SettingScope } from '../config/settings.js'; import { Tips } from './components/Tips.js'; @@ -949,13 +949,13 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { {startupWarnings.length > 0 && ( <Box borderStyle="round" - borderColor={Colors.AccentYellow} + borderColor={theme.status.warning} paddingX={1} marginY={1} flexDirection="column" > {startupWarnings.map((warning, index) => ( - <Text key={index} color={Colors.AccentYellow}> + <Text key={index} color={theme.status.warning}> {warning} </Text> ))} @@ -991,7 +991,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { <Box flexDirection="column"> {themeError && ( <Box marginBottom={1}> - <Text color={Colors.AccentRed}>{themeError}</Text> + <Text color={theme.status.error}>{themeError}</Text> </Box> )} <ThemeDialog @@ -1050,7 +1050,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { <Box flexDirection="column"> {editorError && ( <Box marginBottom={1}> - <Text color={Colors.AccentRed}>{editorError}</Text> + <Text color={theme.status.error}>{editorError}</Text> </Box> )} <EditorSettingsDialog @@ -1090,18 +1090,20 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { > <Box> {process.env.GEMINI_SYSTEM_MD && ( - <Text color={Colors.AccentRed}>|⌐■_■| </Text> + <Text color={theme.status.error}>|⌐■_■| </Text> )} {ctrlCPressedOnce ? ( - <Text color={Colors.AccentYellow}> + <Text color={theme.status.warning}> Press Ctrl+C again to exit. </Text> ) : ctrlDPressedOnce ? ( - <Text color={Colors.AccentYellow}> + <Text color={theme.status.warning}> Press Ctrl+D again to exit. </Text> ) : showEscapePrompt ? ( - <Text color={Colors.Gray}>Press Esc again to clear.</Text> + <Text color={theme.text.secondary}> + Press Esc again to clear. + </Text> ) : ( <ContextSummaryDisplay ideContext={ideContextState} @@ -1164,7 +1166,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { {initError && streamingState !== StreamingState.Responding && ( <Box borderStyle="round" - borderColor={Colors.AccentRed} + borderColor={theme.status.error} paddingX={1} marginBottom={1} > @@ -1172,7 +1174,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { (item) => item.type === 'error' && item.text?.includes(initError), )?.text ? ( - <Text color={Colors.AccentRed}> + <Text color={theme.status.error}> { history.find( (item) => @@ -1182,10 +1184,10 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { </Text> ) : ( <> - <Text color={Colors.AccentRed}> + <Text color={theme.status.error}> Initialization Error: {initError} </Text> - <Text color={Colors.AccentRed}> + <Text color={theme.status.error}> {' '} Please check API key and configuration. </Text> diff --git a/packages/cli/src/ui/commands/chatCommand.ts b/packages/cli/src/ui/commands/chatCommand.ts index 1c9029a9..b614682e 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 'fs/promises'; import React from 'react'; import { Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { CommandContext, SlashCommand, @@ -124,7 +124,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 a0954576..54bcbc3c 100644 --- a/packages/cli/src/ui/components/AboutBox.tsx +++ b/packages/cli/src/ui/components/AboutBox.tsx @@ -6,7 +6,7 @@ import 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<AboutBoxProps> = ({ }) => ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} flexDirection="column" padding={1} marginY={1} width="100%" > <Box marginBottom={1}> - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> About Gemini CLI </Text> </Box> <Box flexDirection="row"> <Box width="35%"> - <Text bold color={Colors.LightBlue}> + <Text bold color={theme.text.link}> CLI Version </Text> </Box> <Box> - <Text>{cliVersion}</Text> + <Text color={theme.text.primary}>{cliVersion}</Text> </Box> </Box> {GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO) && ( <Box flexDirection="row"> <Box width="35%"> - <Text bold color={Colors.LightBlue}> + <Text bold color={theme.text.link}> Git Commit </Text> </Box> <Box> - <Text>{GIT_COMMIT_INFO}</Text> + <Text color={theme.text.primary}>{GIT_COMMIT_INFO}</Text> </Box> </Box> )} <Box flexDirection="row"> <Box width="35%"> - <Text bold color={Colors.LightBlue}> + <Text bold color={theme.text.link}> Model </Text> </Box> <Box> - <Text>{modelVersion}</Text> + <Text color={theme.text.primary}>{modelVersion}</Text> </Box> </Box> <Box flexDirection="row"> <Box width="35%"> - <Text bold color={Colors.LightBlue}> + <Text bold color={theme.text.link}> Sandbox </Text> </Box> <Box> - <Text>{sandboxEnv}</Text> + <Text color={theme.text.primary}>{sandboxEnv}</Text> </Box> </Box> <Box flexDirection="row"> <Box width="35%"> - <Text bold color={Colors.LightBlue}> + <Text bold color={theme.text.link}> OS </Text> </Box> <Box> - <Text>{osVersion}</Text> + <Text color={theme.text.primary}>{osVersion}</Text> </Box> </Box> <Box flexDirection="row"> <Box width="35%"> - <Text bold color={Colors.LightBlue}> + <Text bold color={theme.text.link}> Auth Method </Text> </Box> <Box> - <Text> + <Text color={theme.text.primary}> {selectedAuthType.startsWith('oauth') ? 'OAuth' : selectedAuthType} </Text> </Box> @@ -108,19 +108,19 @@ export const AboutBox: React.FC<AboutBoxProps> = ({ {gcpProject && ( <Box flexDirection="row"> <Box width="35%"> - <Text bold color={Colors.LightBlue}> + <Text bold color={theme.text.link}> GCP Project </Text> </Box> <Box> - <Text>{gcpProject}</Text> + <Text color={theme.text.primary}>{gcpProject}</Text> </Box> </Box> )} {ideClient && ( <Box flexDirection="row"> <Box width="35%"> - <Text bold color={Colors.LightBlue}> + <Text bold color={theme.text.link}> IDE Client </Text> </Box> diff --git a/packages/cli/src/ui/components/AuthDialog.tsx b/packages/cli/src/ui/components/AuthDialog.tsx index c353727c..b9215988 100644 --- a/packages/cli/src/ui/components/AuthDialog.tsx +++ b/packages/cli/src/ui/components/AuthDialog.tsx @@ -6,7 +6,7 @@ import React, { useState } from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { LoadedSettings, SettingScope } from '../../config/settings.js'; import { AuthType } from '@google/gemini-cli-core'; @@ -133,14 +133,18 @@ export function AuthDialog({ return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} flexDirection="column" padding={1} width="100%" > - <Text bold>Get started</Text> + <Text bold color={theme.text.primary}> + Get started + </Text> <Box marginTop={1}> - <Text>How would you like to authenticate for this project?</Text> + <Text color={theme.text.primary}> + How would you like to authenticate for this project? + </Text> </Box> <Box marginTop={1}> <RadioButtonSelect @@ -151,17 +155,19 @@ export function AuthDialog({ </Box> {errorMessage && ( <Box marginTop={1}> - <Text color={Colors.AccentRed}>{errorMessage}</Text> + <Text color={theme.status.error}>{errorMessage}</Text> </Box> )} <Box marginTop={1}> - <Text color={Colors.Gray}>(Use Enter to select)</Text> + <Text color={theme.text.secondary}>(Use Enter to select)</Text> </Box> <Box marginTop={1}> - <Text>Terms of Services and Privacy Notice for Gemini CLI</Text> + <Text color={theme.text.primary}> + Terms of Services and Privacy Notice for Gemini CLI + </Text> </Box> <Box marginTop={1}> - <Text color={Colors.AccentBlue}> + <Text color={theme.text.link}> { 'https://github.com/google-gemini/gemini-cli/blob/main/docs/tos-privacy.md' } diff --git a/packages/cli/src/ui/components/AuthInProgress.tsx b/packages/cli/src/ui/components/AuthInProgress.tsx index 53377c7c..81f98ad5 100644 --- a/packages/cli/src/ui/components/AuthInProgress.tsx +++ b/packages/cli/src/ui/components/AuthInProgress.tsx @@ -7,7 +7,7 @@ import React, { 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 { @@ -40,18 +40,18 @@ export function AuthInProgress({ return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} flexDirection="column" padding={1} width="100%" > {timedOut ? ( - <Text color={Colors.AccentRed}> + <Text color={theme.status.error}> Authentication timed out. Please try again. </Text> ) : ( <Box> - <Text> + <Text color={theme.text.primary}> <Spinner type="dots" /> Waiting for auth... (Press ESC or CTRL+C to cancel) </Text> diff --git a/packages/cli/src/ui/components/AutoAcceptIndicator.tsx b/packages/cli/src/ui/components/AutoAcceptIndicator.tsx index f8d50fd0..a98f036c 100644 --- a/packages/cli/src/ui/components/AutoAcceptIndicator.tsx +++ b/packages/cli/src/ui/components/AutoAcceptIndicator.tsx @@ -6,7 +6,7 @@ import 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<AutoAcceptIndicatorProps> = ({ 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<AutoAcceptIndicatorProps> = ({ <Box> <Text color={textColor}> {textContent} - {subText && <Text color={Colors.Gray}>{subText}</Text>} + {subText && <Text color={theme.text.secondary}>{subText}</Text>} </Text> </Box> ); diff --git a/packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx b/packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx index c79cc096..e992403e 100644 --- a/packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx +++ b/packages/cli/src/ui/components/ConsoleSummaryDisplay.tsx @@ -6,7 +6,7 @@ import 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<ConsoleSummaryDisplayProps> = ({ return ( <Box> {errorCount > 0 && ( - <Text color={Colors.AccentRed}> + <Text color={theme.status.error}> {errorIcon} {errorCount} error{errorCount > 1 ? 's' : ''}{' '} - <Text color={Colors.Gray}>(ctrl+o for details)</Text> + <Text color={theme.text.secondary}>(ctrl+o for details)</Text> </Text> )} </Box> diff --git a/packages/cli/src/ui/components/ContextSummaryDisplay.tsx b/packages/cli/src/ui/components/ContextSummaryDisplay.tsx index 0c946385..ae909a84 100644 --- a/packages/cli/src/ui/components/ContextSummaryDisplay.tsx +++ b/packages/cli/src/ui/components/ContextSummaryDisplay.tsx @@ -6,7 +6,7 @@ import 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<ContextSummaryDisplayProps> = ({ if (isNarrow) { return ( <Box flexDirection="column"> - <Text color={Colors.Gray}>Using:</Text> + <Text color={theme.text.secondary}>Using:</Text> {summaryParts.map((part, index) => ( - <Text key={index} color={Colors.Gray}> + <Text key={index} color={theme.text.secondary}> {' '}- {part} </Text> ))} @@ -111,7 +111,9 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({ return ( <Box> - <Text color={Colors.Gray}>Using: {summaryParts.join(' | ')}</Text> + <Text color={theme.text.secondary}> + Using: {summaryParts.join(' | ')} + </Text> </Box> ); }; diff --git a/packages/cli/src/ui/components/ContextUsageDisplay.tsx b/packages/cli/src/ui/components/ContextUsageDisplay.tsx index 037be333..d7d8c063 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 ( - <Text color={Colors.Gray}> + <Text color={theme.text.secondary}> ({((1 - percentage) * 100).toFixed(0)}% context left) </Text> ); diff --git a/packages/cli/src/ui/components/DebugProfiler.tsx b/packages/cli/src/ui/components/DebugProfiler.tsx index 22c16cfb..4a4d6b4c 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 ( - <Text color={Colors.AccentYellow}>Renders: {numRenders.current} </Text> + <Text color={theme.status.warning}>Renders: {numRenders.current} </Text> ); }; diff --git a/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx b/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx index 2bc5d392..055c87b9 100644 --- a/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx +++ b/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { 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} > <Box marginBottom={1}> - <Text bold color={Colors.Foreground}> - Debug Console <Text color={Colors.Gray}>(ctrl+o to close)</Text> + <Text bold color={theme.text.primary}> + Debug Console{' '} + <Text color={theme.text.secondary}>(ctrl+o to close)</Text> </Text> </Box> <MaxSizedBox maxHeight={maxHeight} maxWidth={width - borderAndPadding}> {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; icon = '\u1F50D'; // Left-pointing magnifying glass (????) break; case 'log': @@ -70,7 +71,7 @@ export const DetailedMessagesDisplay: React.FC< <Text color={textColor} wrap="wrap"> {msg.content} {msg.count && msg.count > 1 && ( - <Text color={Colors.Gray}> (x{msg.count})</Text> + <Text color={theme.text.secondary}> (x{msg.count})</Text> )} </Text> </Box> diff --git a/packages/cli/src/ui/components/EditorSettingsDialog.tsx b/packages/cli/src/ui/components/EditorSettingsDialog.tsx index 3c4c518b..7b58262f 100644 --- a/packages/cli/src/ui/components/EditorSettingsDialog.tsx +++ b/packages/cli/src/ui/components/EditorSettingsDialog.tsx @@ -6,7 +6,7 @@ import React, { useState } from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { EDITOR_DISPLAY_NAMES, editorSettingsManager, @@ -103,7 +103,7 @@ export function EditorSettingsDialog({ return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} flexDirection="row" padding={1} width="100%" @@ -111,7 +111,7 @@ export function EditorSettingsDialog({ <Box flexDirection="column" width="45%" paddingRight={2}> <Text bold={focusedSection === 'editor'}> {focusedSection === 'editor' ? '> ' : ' '}Select Editor{' '} - <Text color={Colors.Gray}>{otherScopeModifiedMessage}</Text> + <Text color={theme.text.secondary}>{otherScopeModifiedMessage}</Text> </Text> <RadioButtonSelect items={editorItems.map((item) => ({ @@ -138,7 +138,7 @@ export function EditorSettingsDialog({ </Box> <Box marginTop={1}> - <Text color={Colors.Gray}> + <Text color={theme.text.secondary}> (Use Enter to select, Tab to change focus) </Text> </Box> @@ -147,17 +147,17 @@ export function EditorSettingsDialog({ <Box flexDirection="column" width="55%" paddingLeft={2}> <Text bold>Editor Preference</Text> <Box flexDirection="column" gap={1} marginTop={1}> - <Text color={Colors.Gray}> + <Text color={theme.text.secondary}> These editors are currently supported. Please note that some editors cannot be used in sandbox mode. </Text> - <Text color={Colors.Gray}> + <Text color={theme.text.secondary}> Your preferred editor is:{' '} <Text color={ mergedEditorName === 'None' - ? Colors.AccentRed - : Colors.AccentCyan + ? theme.status.error + : theme.text.accent } bold > diff --git a/packages/cli/src/ui/components/FolderTrustDialog.tsx b/packages/cli/src/ui/components/FolderTrustDialog.tsx index 30f3ff52..987b92e9 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 React from 'react'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { RadioButtonSelect, RadioSelectItem, @@ -54,14 +54,16 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({ <Box flexDirection="column" borderStyle="round" - borderColor={Colors.AccentYellow} + borderColor={theme.status.warning} padding={1} width="100%" marginLeft={1} > <Box flexDirection="column" marginBottom={1}> - <Text bold>Do you trust this folder?</Text> - <Text> + <Text bold color={theme.text.primary}> + Do you trust this folder? + </Text> + <Text color={theme.text.primary}> Trusting a folder allows Gemini to execute commands it suggests. This is a security feature to prevent accidental execution in untrusted directories. diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx index 09b94ec1..12dee691 100644 --- a/packages/cli/src/ui/components/Footer.tsx +++ b/packages/cli/src/ui/components/Footer.tsx @@ -72,7 +72,7 @@ export const Footer: React.FC<FooterProps> = ({ {vimMode && <Text color={theme.text.secondary}>[{vimMode}] </Text>} {nightly ? ( <Gradient colors={theme.ui.gradient}> - <Text> + <Text color={theme.text.primary}> {displayPath} {branchName && <Text> ({branchName}*)</Text>} </Text> @@ -132,8 +132,8 @@ export const Footer: React.FC<FooterProps> = ({ /> </Text> {corgiMode && ( - <Text> - <Text color={theme.ui.symbol}>| </Text> + <Text color={theme.text.primary}> + <Text color={theme.text.secondary}>| </Text> <Text color={theme.status.error}>▼</Text> <Text color={theme.text.primary}>(´</Text> <Text color={theme.status.error}>ᴥ</Text> @@ -143,7 +143,7 @@ export const Footer: React.FC<FooterProps> = ({ )} {!showErrorDetails && errorCount > 0 && ( <Box> - <Text color={theme.ui.symbol}>| </Text> + <Text color={theme.text.secondary}>| </Text> <ConsoleSummaryDisplay errorCount={errorCount} /> </Box> )} diff --git a/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx b/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx index 97e10cb3..8bafbdcc 100644 --- a/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx +++ b/packages/cli/src/ui/components/GeminiRespondingSpinner.tsx @@ -10,6 +10,7 @@ import Spinner from 'ink-spinner'; import type { SpinnerName } from 'cli-spinners'; import { useStreamingContext } from '../contexts/StreamingContext.js'; import { StreamingState } from '../types.js'; +import { theme } from '../semantic-colors.js'; interface GeminiRespondingSpinnerProps { /** @@ -28,7 +29,7 @@ export const GeminiRespondingSpinner: React.FC< if (streamingState === StreamingState.Responding) { return <Spinner type={spinnerType} />; } else if (nonRespondingDisplay) { - return <Text>{nonRespondingDisplay}</Text>; + return <Text color={theme.text.primary}>{nonRespondingDisplay}</Text>; } return null; }; diff --git a/packages/cli/src/ui/components/Header.test.tsx b/packages/cli/src/ui/components/Header.test.tsx index 95ed3f07..a1b694a6 100644 --- a/packages/cli/src/ui/components/Header.test.tsx +++ b/packages/cli/src/ui/components/Header.test.tsx @@ -5,7 +5,7 @@ */ import { render } from 'ink-testing-library'; -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; import { Header } from './Header.js'; import * as useTerminalSize from '../hooks/useTerminalSize.js'; import { longAsciiLogo } from './AsciiArt.js'; @@ -13,15 +13,13 @@ import { longAsciiLogo } from './AsciiArt.js'; vi.mock('../hooks/useTerminalSize.js'); describe('<Header />', () => { - beforeEach(() => {}); - it('renders the long logo on a wide terminal', () => { vi.spyOn(useTerminalSize, 'useTerminalSize').mockReturnValue({ columns: 120, rows: 20, }); const { lastFrame } = render(<Header version="1.0.0" nightly={false} />); - expect(lastFrame()).toContain(longAsciiLogo); + expect(lastFrame()?.trim()).toContain(longAsciiLogo.trim()); }); it('renders custom ASCII art when provided', () => { @@ -29,16 +27,20 @@ describe('<Header />', () => { const { lastFrame } = render( <Header version="1.0.0" nightly={false} customAsciiArt={customArt} />, ); - expect(lastFrame()).toContain(customArt); + expect(lastFrame()?.trim()).toContain(customArt); }); it('displays the version number when nightly is true', () => { const { lastFrame } = render(<Header version="1.0.0" nightly={true} />); - expect(lastFrame()).toContain('v1.0.0'); + expect(lastFrame()?.trim()).toContain('v1.0.0'); }); it('does not display the version number when nightly is false', () => { + vi.spyOn(useTerminalSize, 'useTerminalSize').mockReturnValue({ + columns: 40, + rows: 20, + }); const { lastFrame } = render(<Header version="1.0.0" nightly={false} />); - expect(lastFrame()).not.toContain('v1.0.0'); + expect(lastFrame()?.trim()).not.toContain('v1.0.0'); }); }); diff --git a/packages/cli/src/ui/components/Header.tsx b/packages/cli/src/ui/components/Header.tsx index 0894ad14..5942e304 100644 --- a/packages/cli/src/ui/components/Header.tsx +++ b/packages/cli/src/ui/components/Header.tsx @@ -7,7 +7,7 @@ import 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'; @@ -18,6 +18,16 @@ interface HeaderProps { nightly: boolean; } +const GradientText: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const textElement = <Text color={theme.text.primary}>{children}</Text>; + if (theme.ui.gradient && theme.ui.gradient.length > 0) { + return <Gradient colors={theme.ui.gradient}>{textElement}</Gradient>; + } + return textElement; +}; + export const Header: React.FC<HeaderProps> = ({ customAsciiArt, version, @@ -47,22 +57,12 @@ export const Header: React.FC<HeaderProps> = ({ flexShrink={0} flexDirection="column" > - {Colors.GradientColors ? ( - <Gradient colors={Colors.GradientColors}> - <Text>{displayTitle}</Text> - </Gradient> - ) : ( - <Text>{displayTitle}</Text> - )} + <Box> + <GradientText>{displayTitle}</GradientText> + </Box> {nightly && ( <Box width="100%" flexDirection="row" justifyContent="flex-end"> - {Colors.GradientColors ? ( - <Gradient colors={Colors.GradientColors}> - <Text>v{version}</Text> - </Gradient> - ) : ( - <Text>v{version}</Text> - )} + <Text color={theme.text.primary}>v{version}</Text> </Box> )} </Box> diff --git a/packages/cli/src/ui/components/Help.tsx b/packages/cli/src/ui/components/Help.tsx index d9f7b4a8..9b6709ac 100644 --- a/packages/cli/src/ui/components/Help.tsx +++ b/packages/cli/src/ui/components/Help.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { SlashCommand } from '../commands/types.js'; interface Help { @@ -17,42 +17,42 @@ export const Help: React.FC<Help> = ({ commands }) => ( <Box flexDirection="column" marginBottom={1} - borderColor={Colors.Gray} + borderColor={theme.border.default} borderStyle="round" padding={1} > {/* Basics */} - <Text bold color={Colors.Foreground}> + <Text bold color={theme.text.primary}> Basics: </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Add context </Text> : Use{' '} - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> @ </Text>{' '} to specify files for context (e.g.,{' '} - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> @src/myFile.ts </Text> ) to target specific files or folders. </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Shell mode </Text> : Execute shell commands via{' '} - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> ! </Text>{' '} (e.g.,{' '} - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> !npm run start </Text> ) or use natural language (e.g.{' '} - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> start server </Text> ). @@ -61,15 +61,15 @@ export const Help: React.FC<Help> = ({ commands }) => ( <Box height={1} /> {/* Commands */} - <Text bold color={Colors.Foreground}> + <Text bold color={theme.text.primary}> Commands: </Text> {commands .filter((command) => command.description) .map((command: SlashCommand) => ( <Box key={command.name} flexDirection="column"> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> {' '} /{command.name} </Text> @@ -77,8 +77,8 @@ export const Help: React.FC<Help> = ({ commands }) => ( </Text> {command.subCommands && command.subCommands.map((subCommand) => ( - <Text key={subCommand.name} color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text key={subCommand.name} color={theme.text.primary}> + <Text bold color={theme.text.accent}> {' '} {subCommand.name} </Text> @@ -87,8 +87,8 @@ export const Help: React.FC<Help> = ({ commands }) => ( ))} </Box> ))} - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> {' '} !{' '} </Text> @@ -98,75 +98,75 @@ export const Help: React.FC<Help> = ({ commands }) => ( <Box height={1} /> {/* Shortcuts */} - <Text bold color={Colors.Foreground}> + <Text bold color={theme.text.primary}> Keyboard Shortcuts: </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Alt+Left/Right </Text>{' '} - Jump through words in the input </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Ctrl+C </Text>{' '} - Quit application </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> {process.platform === 'win32' ? 'Ctrl+Enter' : 'Ctrl+J'} </Text>{' '} {process.platform === 'linux' ? '- New line (Alt+Enter works for certain linux distros)' : '- New line'} </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Ctrl+L </Text>{' '} - Clear the screen </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> {process.platform === 'darwin' ? 'Ctrl+X / Meta+Enter' : 'Ctrl+X'} </Text>{' '} - Open input in external editor </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Ctrl+Y </Text>{' '} - Toggle YOLO mode </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Enter </Text>{' '} - Send message </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Esc </Text>{' '} - Cancel operation </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Shift+Tab </Text>{' '} - Toggle auto-accepting edits </Text> - <Text color={Colors.Foreground}> - <Text bold color={Colors.AccentPurple}> + <Text color={theme.text.primary}> + <Text bold color={theme.text.accent}> Up/Down </Text>{' '} - Cycle through your prompt history </Text> <Box height={1} /> - <Text color={Colors.Foreground}> + <Text color={theme.text.primary}> For a full list of shortcuts, see{' '} - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> docs/keyboard-shortcuts.md </Text> </Text> diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index dcfdace3..3a87b76e 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -559,7 +559,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({ <Box flexGrow={1} flexDirection="column"> {buffer.text.length === 0 && placeholder ? ( focus ? ( - <Text> + <Text color={theme.text.primary}> {chalk.inverse(placeholder.slice(0, 1))} <Text color={theme.text.secondary}>{placeholder.slice(1)}</Text> </Text> @@ -600,7 +600,12 @@ export const InputPrompt: React.FC<InputPromptProps> = ({ } } return ( - <Text key={`line-${visualIdxInRenderedSet}`}>{display}</Text> + <Text + key={`line-${visualIdxInRenderedSet}`} + color={theme.text.primary} + > + {display} + </Text> ); }) )} diff --git a/packages/cli/src/ui/components/LoadingIndicator.tsx b/packages/cli/src/ui/components/LoadingIndicator.tsx index 7ac356dd..fc51a7ef 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.tsx @@ -7,7 +7,7 @@ import { ThoughtSummary } from '@google/gemini-cli-core'; import 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<LoadingIndicatorProps> = ({ } /> </Box> - {primaryText && ( - <Text color={Colors.AccentPurple}>{primaryText}</Text> - )} + {primaryText && <Text color={theme.text.accent}>{primaryText}</Text>} {!isNarrow && cancelAndTimerContent && ( - <Text color={Colors.Gray}> {cancelAndTimerContent}</Text> + <Text color={theme.text.secondary}> {cancelAndTimerContent}</Text> )} </Box> {!isNarrow && <Box flexGrow={1}>{/* Spacer */}</Box>} @@ -73,7 +71,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({ </Box> {isNarrow && cancelAndTimerContent && ( <Box> - <Text color={Colors.Gray}>{cancelAndTimerContent}</Text> + <Text color={theme.text.secondary}>{cancelAndTimerContent}</Text> </Box> )} {isNarrow && rightContent && <Box>{rightContent}</Box>} diff --git a/packages/cli/src/ui/components/MemoryUsageDisplay.tsx b/packages/cli/src/ui/components/MemoryUsageDisplay.tsx index d768445c..55be64cc 100644 --- a/packages/cli/src/ui/components/MemoryUsageDisplay.tsx +++ b/packages/cli/src/ui/components/MemoryUsageDisplay.tsx @@ -6,20 +6,24 @@ import React, { 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<string>(''); - const [memoryUsageColor, setMemoryUsageColor] = useState<string>(Colors.Gray); + const [memoryUsageColor, setMemoryUsageColor] = useState<string>( + 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); @@ -29,7 +33,7 @@ export const MemoryUsageDisplay: React.FC = () => { return ( <Box> - <Text color={Colors.Gray}>| </Text> + <Text color={theme.text.secondary}>| </Text> <Text color={memoryUsageColor}>{memoryUsage}</Text> </Box> ); diff --git a/packages/cli/src/ui/components/ModelStatsDisplay.tsx b/packages/cli/src/ui/components/ModelStatsDisplay.tsx index 1911e757..c3b109a3 100644 --- a/packages/cli/src/ui/components/ModelStatsDisplay.tsx +++ b/packages/cli/src/ui/components/ModelStatsDisplay.tsx @@ -6,7 +6,7 @@ import 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, @@ -33,13 +33,13 @@ const StatRow: React.FC<StatRowProps> = ({ }) => ( <Box> <Box width={METRIC_COL_WIDTH}> - <Text bold={isSection} color={isSection ? undefined : Colors.LightBlue}> + <Text bold color={isSection ? theme.text.primary : theme.text.link}> {isSubtle ? ` ↳ ${title}` : title} </Text> </Box> {values.map((value, index) => ( <Box width={MODEL_COL_WIDTH} key={index}> - <Text>{value}</Text> + <Text color={theme.text.primary}>{value}</Text> </Box> ))} </Box> @@ -56,11 +56,13 @@ export const ModelStatsDisplay: React.FC = () => { return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} paddingY={1} paddingX={2} > - <Text>No API calls have been made in this session.</Text> + <Text color={theme.text.primary}> + No API calls have been made in this session. + </Text> </Box> ); } @@ -82,12 +84,12 @@ export const ModelStatsDisplay: React.FC = () => { return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} flexDirection="column" paddingY={1} paddingX={2} > - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> Model Stats For Nerds </Text> <Box height={1} /> @@ -95,11 +97,15 @@ export const ModelStatsDisplay: React.FC = () => { {/* Header */} <Box> <Box width={METRIC_COL_WIDTH}> - <Text bold>Metric</Text> + <Text bold color={theme.text.primary}> + Metric + </Text> </Box> {modelNames.map((name) => ( <Box width={MODEL_COL_WIDTH} key={name}> - <Text bold>{name}</Text> + <Text bold color={theme.text.primary}> + {name} + </Text> </Box> ))} </Box> @@ -126,7 +132,7 @@ export const ModelStatsDisplay: React.FC = () => { return ( <Text color={ - m.api.totalErrors > 0 ? Colors.AccentRed : Colors.Foreground + m.api.totalErrors > 0 ? theme.status.error : theme.text.primary } > {m.api.totalErrors.toLocaleString()} ({errorRate.toFixed(1)}%) @@ -149,7 +155,7 @@ export const ModelStatsDisplay: React.FC = () => { <StatRow title="Total" values={getModelValues((m) => ( - <Text color={Colors.AccentYellow}> + <Text color={theme.status.warning}> {m.tokens.total.toLocaleString()} </Text> ))} @@ -166,7 +172,7 @@ export const ModelStatsDisplay: React.FC = () => { values={getModelValues((m) => { const cacheHitRate = calculateCacheHitRate(m); return ( - <Text color={Colors.AccentGreen}> + <Text color={theme.status.success}> {m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%) </Text> ); diff --git a/packages/cli/src/ui/components/PrepareLabel.tsx b/packages/cli/src/ui/components/PrepareLabel.tsx index 652a77a6..e255d386 100644 --- a/packages/cli/src/ui/components/PrepareLabel.tsx +++ b/packages/cli/src/ui/components/PrepareLabel.tsx @@ -6,7 +6,7 @@ import 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<PrepareLabelProps> = ({ matchedIndex, userInput, textColor, - highlightColor = Colors.AccentYellow, + highlightColor = theme.status.warning, }) => { if ( matchedIndex === undefined || @@ -37,7 +37,7 @@ export const PrepareLabel: React.FC<PrepareLabelProps> = ({ const end = label.slice(matchedIndex + userInput.length); return ( - <Text> + <Text color={theme.text.primary}> <Text color={textColor}>{start}</Text> <Text color="black" bold backgroundColor={highlightColor}> {match} diff --git a/packages/cli/src/ui/components/SettingsDialog.tsx b/packages/cli/src/ui/components/SettingsDialog.tsx index a09cd76a..ce50e28f 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 { LoadedSettings, SettingScope, @@ -366,18 +366,18 @@ export function SettingsDialog({ return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} flexDirection="row" padding={1} width="100%" height="100%" > <Box flexDirection="column" flexGrow={1}> - <Text bold color={Colors.AccentBlue}> + <Text bold color={theme.text.link}> Settings </Text> <Box height={1} /> - {showScrollUp && <Text color={Colors.Gray}>▲</Text>} + {showScrollUp && <Text color={theme.text.secondary}>▲</Text>} {visibleItems.map((item, idx) => { const isActive = focusSection === 'settings' && @@ -405,17 +405,21 @@ export function SettingsDialog({ <React.Fragment key={item.value}> <Box flexDirection="row" alignItems="center"> <Box minWidth={2} flexShrink={0}> - <Text color={isActive ? Colors.AccentGreen : Colors.Gray}> + <Text + color={ + isActive ? theme.status.success : theme.text.secondary + } + > {isActive ? '●' : ''} </Text> </Box> <Box minWidth={50}> <Text - color={isActive ? Colors.AccentGreen : Colors.Foreground} + color={isActive ? theme.status.success : theme.text.primary} > {item.label} {scopeMessage && ( - <Text color={Colors.Gray}> {scopeMessage}</Text> + <Text color={theme.text.secondary}> {scopeMessage}</Text> )} </Text> </Box> @@ -423,10 +427,10 @@ export function SettingsDialog({ <Text color={ isActive - ? Colors.AccentGreen + ? theme.status.success : shouldBeGreyedOut - ? Colors.Gray - : Colors.Foreground + ? theme.text.secondary + : theme.text.primary } > {displayValue} @@ -436,12 +440,16 @@ export function SettingsDialog({ </React.Fragment> ); })} - {showScrollDown && <Text color={Colors.Gray}>▼</Text>} + {showScrollDown && <Text color={theme.text.secondary}>▼</Text>} <Box height={1} /> <Box marginTop={1} flexDirection="column"> - <Text bold={focusSection === 'scope'} wrap="truncate"> + <Text + bold={focusSection === 'scope'} + wrap="truncate" + color={theme.text.primary} + > {focusSection === 'scope' ? '> ' : ' '}Apply To </Text> <RadioButtonSelect @@ -455,11 +463,11 @@ export function SettingsDialog({ </Box> <Box height={1} /> - <Text color={Colors.Gray}> + <Text color={theme.text.secondary}> (Use Enter to select, Tab to change focus) </Text> {showRestartPrompt && ( - <Text color={Colors.AccentYellow}> + <Text color={theme.status.warning}> To see changes, Gemini CLI must be restarted. Press r to exit and apply changes now. </Text> diff --git a/packages/cli/src/ui/components/ShellConfirmationDialog.tsx b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx index 04e57364..99273593 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 React from 'react'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; import { RadioButtonSelect, RadioSelectItem, @@ -69,23 +69,27 @@ export const ShellConfirmationDialog: React.FC< <Box flexDirection="column" borderStyle="round" - borderColor={Colors.AccentYellow} + borderColor={theme.status.warning} 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> + <Text bold color={theme.text.primary}> + Shell Command Execution + </Text> + <Text color={theme.text.primary}> + A custom command wants to run the following shell commands: + </Text> <Box flexDirection="column" borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} paddingX={1} marginTop={1} > {commands.map((cmd) => ( - <Text key={cmd} color={Colors.AccentCyan}> + <Text key={cmd} color={theme.text.accent}> {cmd} </Text> ))} @@ -93,7 +97,7 @@ export const ShellConfirmationDialog: React.FC< </Box> <Box marginBottom={1}> - <Text>Do you want to proceed?</Text> + <Text color={theme.text.primary}>Do you want to proceed?</Text> </Box> <RadioButtonSelect items={options} onSelect={handleSelect} isFocused /> diff --git a/packages/cli/src/ui/components/ShellModeIndicator.tsx b/packages/cli/src/ui/components/ShellModeIndicator.tsx index f5b11b24..3ad632c8 100644 --- a/packages/cli/src/ui/components/ShellModeIndicator.tsx +++ b/packages/cli/src/ui/components/ShellModeIndicator.tsx @@ -6,13 +6,13 @@ import React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; export const ShellModeIndicator: React.FC = () => ( <Box> - <Text color={Colors.AccentYellow}> + <Text color={theme.status.warning}> shell mode enabled - <Text color={Colors.Gray}> (esc to disable)</Text> + <Text color={theme.text.secondary}> (esc to disable)</Text> </Text> </Box> ); diff --git a/packages/cli/src/ui/components/ShowMoreLines.tsx b/packages/cli/src/ui/components/ShowMoreLines.tsx index 41232d94..8823eee6 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 ( <Box> - <Text color={Colors.Gray} wrap="truncate"> + <Text color={theme.text.secondary} wrap="truncate"> Press ctrl-s to show more lines </Text> </Box> diff --git a/packages/cli/src/ui/components/StatsDisplay.test.tsx b/packages/cli/src/ui/components/StatsDisplay.test.tsx index eed105e3..d186f612 100644 --- a/packages/cli/src/ui/components/StatsDisplay.test.tsx +++ b/packages/cli/src/ui/components/StatsDisplay.test.tsx @@ -43,7 +43,7 @@ describe('<StatsDisplay />', () => { const zeroMetrics: SessionMetrics = { models: {}, tools: { - totalCalls: 0, + totalCalls: 1, totalSuccess: 0, totalFail: 0, totalDurationMs: 0, diff --git a/packages/cli/src/ui/components/StatsDisplay.tsx b/packages/cli/src/ui/components/StatsDisplay.tsx index 71c88aef..03c1fff6 100644 --- a/packages/cli/src/ui/components/StatsDisplay.tsx +++ b/packages/cli/src/ui/components/StatsDisplay.tsx @@ -7,7 +7,7 @@ import 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 { formatDuration } from '../utils/formatters.js'; import { useSessionStats, ModelMetrics } from '../contexts/SessionContext.js'; import { @@ -29,7 +29,7 @@ const StatRow: React.FC<StatRowProps> = ({ title, children }) => ( <Box> {/* Fixed width for the label creates a clean "gutter" for alignment */} <Box width={28}> - <Text color={Colors.LightBlue}>{title}</Text> + <Text color={theme.text.link}>{title}</Text> </Box> {children} </Box> @@ -45,7 +45,7 @@ const SubStatRow: React.FC<SubStatRowProps> = ({ title, children }) => ( <Box paddingLeft={2}> {/* Adjust width for the "» " prefix */} <Box width={26}> - <Text>» {title}</Text> + <Text color={theme.text.secondary}>» {title}</Text> </Box> {children} </Box> @@ -59,7 +59,9 @@ interface SectionProps { const Section: React.FC<SectionProps> = ({ title, children }) => ( <Box flexDirection="column" width="100%" marginBottom={1}> - <Text bold>{title}</Text> + <Text bold color={theme.text.primary}> + {title} + </Text> {children} </Box> ); @@ -79,16 +81,24 @@ const ModelUsageTable: React.FC<{ {/* Header */} <Box> <Box width={nameWidth}> - <Text bold>Model Usage</Text> + <Text bold color={theme.text.primary}> + Model Usage + </Text> </Box> <Box width={requestsWidth} justifyContent="flex-end"> - <Text bold>Reqs</Text> + <Text bold color={theme.text.primary}> + Reqs + </Text> </Box> <Box width={inputTokensWidth} justifyContent="flex-end"> - <Text bold>Input Tokens</Text> + <Text bold color={theme.text.primary}> + Input Tokens + </Text> </Box> <Box width={outputTokensWidth} justifyContent="flex-end"> - <Text bold>Output Tokens</Text> + <Text bold color={theme.text.primary}> + Output Tokens + </Text> </Box> </Box> {/* Divider */} @@ -98,6 +108,7 @@ const ModelUsageTable: React.FC<{ borderTop={false} borderLeft={false} borderRight={false} + borderColor={theme.border.default} width={nameWidth + requestsWidth + inputTokensWidth + outputTokensWidth} ></Box> @@ -105,18 +116,20 @@ const ModelUsageTable: React.FC<{ {Object.entries(models).map(([name, modelMetrics]) => ( <Box key={name}> <Box width={nameWidth}> - <Text>{name.replace('-001', '')}</Text> + <Text color={theme.text.primary}>{name.replace('-001', '')}</Text> </Box> <Box width={requestsWidth} justifyContent="flex-end"> - <Text>{modelMetrics.api.totalRequests}</Text> + <Text color={theme.text.primary}> + {modelMetrics.api.totalRequests} + </Text> </Box> <Box width={inputTokensWidth} justifyContent="flex-end"> - <Text color={Colors.AccentYellow}> + <Text color={theme.status.warning}> {modelMetrics.tokens.prompt.toLocaleString()} </Text> </Box> <Box width={outputTokensWidth} justifyContent="flex-end"> - <Text color={Colors.AccentYellow}> + <Text color={theme.status.warning}> {modelMetrics.tokens.candidates.toLocaleString()} </Text> </Box> @@ -124,13 +137,13 @@ const ModelUsageTable: React.FC<{ ))} {cacheEfficiency > 0 && ( <Box flexDirection="column" marginTop={1}> - <Text> - <Text color={Colors.AccentGreen}>Savings Highlight:</Text>{' '} + <Text color={theme.text.primary}> + <Text color={theme.status.success}>Savings Highlight:</Text>{' '} {totalCachedTokens.toLocaleString()} ({cacheEfficiency.toFixed(1)} %) of input tokens were served from the cache, reducing costs. </Text> <Box height={1} /> - <Text color={Colors.Gray}> + <Text color={theme.text.secondary}> » Tip: For a full token breakdown, run `/stats model`. </Text> </Box> @@ -169,18 +182,18 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({ const renderTitle = () => { if (title) { - return Colors.GradientColors && Colors.GradientColors.length > 0 ? ( - <Gradient colors={Colors.GradientColors}> + return theme.ui.gradient && theme.ui.gradient.length > 0 ? ( + <Gradient colors={theme.ui.gradient}> <Text bold>{title}</Text> </Gradient> ) : ( - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> {title} </Text> ); } return ( - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> Session Stats </Text> ); @@ -189,7 +202,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({ return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} flexDirection="column" paddingY={1} paddingX={2} @@ -197,51 +210,55 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({ {renderTitle()} <Box height={1} /> - <Section title="Interaction Summary"> - <StatRow title="Session ID:"> - <Text>{stats.sessionId}</Text> - </StatRow> - <StatRow title="Tool Calls:"> - <Text> - {tools.totalCalls} ({' '} - <Text color={Colors.AccentGreen}>✔ {tools.totalSuccess}</Text>{' '} - <Text color={Colors.AccentRed}>✖ {tools.totalFail}</Text> ) - </Text> - </StatRow> - <StatRow title="Success Rate:"> - <Text color={successColor}>{computed.successRate.toFixed(1)}%</Text> - </StatRow> - {computed.totalDecisions > 0 && ( - <StatRow title="User Agreement:"> - <Text color={agreementColor}> - {computed.agreementRate.toFixed(1)}%{' '} - <Text color={Colors.Gray}> - ({computed.totalDecisions} reviewed) - </Text> + {tools.totalCalls > 0 && ( + <Section title="Interaction Summary"> + <StatRow title="Session ID:"> + <Text>{stats.sessionId}</Text> + </StatRow> + <StatRow title="Tool Calls:"> + <Text color={theme.text.primary}> + {tools.totalCalls} ({' '} + <Text color={theme.status.success}>✔ {tools.totalSuccess}</Text>{' '} + <Text color={theme.status.error}>✖ {tools.totalFail}</Text> ) </Text> </StatRow> - )} - </Section> + <StatRow title="Success Rate:"> + <Text color={successColor}>{computed.successRate.toFixed(1)}%</Text> + </StatRow> + {computed.totalDecisions > 0 && ( + <StatRow title="User Agreement:"> + <Text color={agreementColor}> + {computed.agreementRate.toFixed(1)}%{' '} + <Text color={theme.text.secondary}> + ({computed.totalDecisions} reviewed) + </Text> + </Text> + </StatRow> + )} + </Section> + )} <Section title="Performance"> <StatRow title="Wall Time:"> - <Text>{duration}</Text> + <Text color={theme.text.primary}>{duration}</Text> </StatRow> <StatRow title="Agent Active:"> - <Text>{formatDuration(computed.agentActiveTime)}</Text> + <Text color={theme.text.primary}> + {formatDuration(computed.agentActiveTime)} + </Text> </StatRow> <SubStatRow title="API Time:"> - <Text> + <Text color={theme.text.primary}> {formatDuration(computed.totalApiTime)}{' '} - <Text color={Colors.Gray}> + <Text color={theme.text.secondary}> ({computed.apiTimePercent.toFixed(1)}%) </Text> </Text> </SubStatRow> <SubStatRow title="Tool Time:"> - <Text> + <Text color={theme.text.primary}> {formatDuration(computed.totalToolTime)}{' '} - <Text color={Colors.Gray}> + <Text color={theme.text.secondary}> ({computed.toolTimePercent.toFixed(1)}%) </Text> </Text> diff --git a/packages/cli/src/ui/components/SuggestionsDisplay.tsx b/packages/cli/src/ui/components/SuggestionsDisplay.tsx index 1275a911..4f18e837 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'; export interface Suggestion { label: string; @@ -35,7 +35,7 @@ export function SuggestionsDisplay({ if (isLoading) { return ( <Box paddingX={1} width={width}> - <Text color="gray">Loading suggestions...</Text> + <Text color={theme.text.secondary}>Loading suggestions...</Text> </Box> ); } @@ -54,12 +54,12 @@ export function SuggestionsDisplay({ return ( <Box flexDirection="column" paddingX={1} width={width}> - {scrollOffset > 0 && <Text color={Colors.Foreground}>▲</Text>} + {scrollOffset > 0 && <Text color={theme.text.primary}>▲</Text>} {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 = ( <PrepareLabel label={suggestion.label} @@ -91,9 +91,11 @@ export function SuggestionsDisplay({ </Box> ); })} - {endIndex < suggestions.length && <Text color="gray">▼</Text>} + {endIndex < suggestions.length && ( + <Text color={theme.text.secondary}>▼</Text> + )} {suggestions.length > MAX_SUGGESTIONS_TO_SHOW && ( - <Text color="gray"> + <Text color={theme.text.secondary}> ({activeIndex + 1}/{suggestions.length}) </Text> )} diff --git a/packages/cli/src/ui/components/ThemeDialog.tsx b/packages/cli/src/ui/components/ThemeDialog.tsx index 16ecfc8f..c26dfa96 100644 --- a/packages/cli/src/ui/components/ThemeDialog.tsx +++ b/packages/cli/src/ui/components/ThemeDialog.tsx @@ -6,7 +6,7 @@ import React, { 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'; @@ -207,7 +207,7 @@ export function ThemeDialog({ return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} flexDirection="column" paddingTop={includePadding ? 1 : 0} paddingBottom={includePadding ? 1 : 0} @@ -218,9 +218,19 @@ export function ThemeDialog({ <Box flexDirection="row"> {/* Left Column: Selection */} <Box flexDirection="column" width="45%" paddingRight={2}> - <Text bold={currentFocusedSection === 'theme'} wrap="truncate"> + <Text + bold={currentFocusedSection === 'theme'} + wrap="truncate" + color={ + currentFocusedSection === 'theme' + ? theme.text.primary + : theme.text.secondary + } + > {currentFocusedSection === 'theme' ? '> ' : ' '}Select Theme{' '} - <Text color={Colors.Gray}>{otherScopeModifiedMessage}</Text> + <Text color={theme.text.secondary}> + {otherScopeModifiedMessage} + </Text> </Text> <RadioButtonSelect key={selectInputKey} @@ -237,7 +247,15 @@ export function ThemeDialog({ {/* Scope Selection */} {showScopeSelection && ( <Box marginTop={1} flexDirection="column"> - <Text bold={currentFocusedSection === 'scope'} wrap="truncate"> + <Text + bold={currentFocusedSection === 'scope'} + wrap="truncate" + color={ + currentFocusedSection === 'scope' + ? theme.text.primary + : theme.text.secondary + } + > {currentFocusedSection === 'scope' ? '> ' : ' '}Apply To </Text> <RadioButtonSelect @@ -254,7 +272,9 @@ export function ThemeDialog({ {/* Right Column: Preview */} <Box flexDirection="column" width="55%" paddingLeft={2}> - <Text bold>Preview</Text> + <Text bold color={theme.text.primary}> + Preview + </Text> {/* Get the Theme object for the highlighted theme, fall back to default if not found */} {(() => { const previewTheme = @@ -264,7 +284,7 @@ export function ThemeDialog({ return ( <Box borderStyle="single" - borderColor={Colors.Gray} + borderColor={theme.border.default} paddingTop={includePadding ? 1 : 0} paddingBottom={includePadding ? 1 : 0} paddingLeft={1} @@ -281,6 +301,7 @@ def fibonacci(n): 'python', codeBlockHeight, colorizeCodeWidth, + previewTheme, )} <Box marginTop={1} /> <DiffRenderer @@ -300,7 +321,7 @@ def fibonacci(n): </Box> </Box> <Box marginTop={1}> - <Text color={Colors.Gray} wrap="truncate"> + <Text color={theme.text.secondary} wrap="truncate"> (Use Enter to select {showScopeSelection ? ', Tab to change focus' : ''}) </Text> diff --git a/packages/cli/src/ui/components/Tips.tsx b/packages/cli/src/ui/components/Tips.tsx index 4aa6c112..6e754e08 100644 --- a/packages/cli/src/ui/components/Tips.tsx +++ b/packages/cli/src/ui/components/Tips.tsx @@ -6,7 +6,7 @@ import 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<TipsProps> = ({ config }) => { const geminiMdFileCount = config.getGeminiMdFileCount(); return ( <Box flexDirection="column"> - <Text color={Colors.Foreground}>Tips for getting started:</Text> - <Text color={Colors.Foreground}> + <Text color={theme.text.primary}>Tips for getting started:</Text> + <Text color={theme.text.primary}> 1. Ask questions, edit files, or run commands. </Text> - <Text color={Colors.Foreground}> + <Text color={theme.text.primary}> 2. Be specific for the best results. </Text> {geminiMdFileCount === 0 && ( - <Text color={Colors.Foreground}> + <Text color={theme.text.primary}> 3. Create{' '} - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> GEMINI.md </Text>{' '} files to customize your interactions with Gemini. </Text> )} - <Text color={Colors.Foreground}> + <Text color={theme.text.primary}> {geminiMdFileCount === 0 ? '4.' : '3.'}{' '} - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> /help </Text>{' '} for more information. diff --git a/packages/cli/src/ui/components/ToolStatsDisplay.tsx b/packages/cli/src/ui/components/ToolStatsDisplay.tsx index f2335d9e..4bcee9fc 100644 --- a/packages/cli/src/ui/components/ToolStatsDisplay.tsx +++ b/packages/cli/src/ui/components/ToolStatsDisplay.tsx @@ -6,7 +6,7 @@ import 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 ( <Box> <Box width={TOOL_NAME_COL_WIDTH}> - <Text color={Colors.LightBlue}>{name}</Text> + <Text color={theme.text.link}>{name}</Text> </Box> <Box width={CALLS_COL_WIDTH} justifyContent="flex-end"> - <Text>{stats.count}</Text> + <Text color={theme.text.primary}>{stats.count}</Text> </Box> <Box width={SUCCESS_RATE_COL_WIDTH} justifyContent="flex-end"> <Text color={successColor}>{successRate.toFixed(1)}%</Text> </Box> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> - <Text>{formatDuration(avgDuration)}</Text> + <Text color={theme.text.primary}>{formatDuration(avgDuration)}</Text> </Box> </Box> ); @@ -63,11 +63,13 @@ export const ToolStatsDisplay: React.FC = () => { return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} paddingY={1} paddingX={2} > - <Text>No tool calls have been made in this session.</Text> + <Text color={theme.text.primary}> + No tool calls have been made in this session. + </Text> </Box> ); } @@ -94,13 +96,13 @@ export const ToolStatsDisplay: React.FC = () => { return ( <Box borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} flexDirection="column" paddingY={1} paddingX={2} width={70} > - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> Tool Stats For Nerds </Text> <Box height={1} /> @@ -108,16 +110,24 @@ export const ToolStatsDisplay: React.FC = () => { {/* Header */} <Box> <Box width={TOOL_NAME_COL_WIDTH}> - <Text bold>Tool Name</Text> + <Text bold color={theme.text.primary}> + Tool Name + </Text> </Box> <Box width={CALLS_COL_WIDTH} justifyContent="flex-end"> - <Text bold>Calls</Text> + <Text bold color={theme.text.primary}> + Calls + </Text> </Box> <Box width={SUCCESS_RATE_COL_WIDTH} justifyContent="flex-end"> - <Text bold>Success Rate</Text> + <Text bold color={theme.text.primary}> + Success Rate + </Text> </Box> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> - <Text bold>Avg Duration</Text> + <Text bold color={theme.text.primary}> + Avg Duration + </Text> </Box> </Box> @@ -139,45 +149,47 @@ export const ToolStatsDisplay: React.FC = () => { <Box height={1} /> {/* User Decision Summary */} - <Text bold>User Decision Summary</Text> + <Text bold color={theme.text.primary}> + User Decision Summary + </Text> <Box> <Box width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} > - <Text color={Colors.LightBlue}>Total Reviewed Suggestions:</Text> + <Text color={theme.text.link}>Total Reviewed Suggestions:</Text> </Box> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> - <Text>{totalReviewed}</Text> + <Text color={theme.text.primary}>{totalReviewed}</Text> </Box> </Box> <Box> <Box width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} > - <Text> » Accepted:</Text> + <Text color={theme.text.primary}> » Accepted:</Text> </Box> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> - <Text color={Colors.AccentGreen}>{totalDecisions.accept}</Text> + <Text color={theme.status.success}>{totalDecisions.accept}</Text> </Box> </Box> <Box> <Box width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} > - <Text> » Rejected:</Text> + <Text color={theme.text.primary}> » Rejected:</Text> </Box> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> - <Text color={Colors.AccentRed}>{totalDecisions.reject}</Text> + <Text color={theme.status.error}>{totalDecisions.reject}</Text> </Box> </Box> <Box> <Box width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} > - <Text> » Modified:</Text> + <Text color={theme.text.primary}> » Modified:</Text> </Box> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> - <Text color={Colors.AccentYellow}>{totalDecisions.modify}</Text> + <Text color={theme.status.warning}>{totalDecisions.modify}</Text> </Box> </Box> @@ -195,10 +207,13 @@ export const ToolStatsDisplay: React.FC = () => { <Box width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} > - <Text> Overall Agreement Rate:</Text> + <Text color={theme.text.primary}> Overall Agreement Rate:</Text> </Box> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> - <Text bold color={totalReviewed > 0 ? agreementColor : undefined}> + <Text + bold + color={totalReviewed > 0 ? agreementColor : theme.text.primary} + > {totalReviewed > 0 ? `${agreementRate.toFixed(1)}%` : '--'} </Text> </Box> diff --git a/packages/cli/src/ui/components/UpdateNotification.tsx b/packages/cli/src/ui/components/UpdateNotification.tsx index b88c9bd5..8142a201 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) => ( <Box borderStyle="round" - borderColor={Colors.AccentYellow} + borderColor={theme.status.warning} paddingX={1} marginY={1} > - <Text color={Colors.AccentYellow}>{message}</Text> + <Text color={theme.status.warning}>{message}</Text> </Box> ); diff --git a/packages/cli/src/ui/components/__snapshots__/SessionSummaryDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/SessionSummaryDisplay.test.tsx.snap index 98e7722e..c9b2bd64 100644 --- a/packages/cli/src/ui/components/__snapshots__/SessionSummaryDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/SessionSummaryDisplay.test.tsx.snap @@ -5,11 +5,6 @@ exports[`<SessionSummaryDisplay /> > renders the summary display with a title 1` │ │ │ Agent powering down. Goodbye! │ │ │ -│ Interaction Summary │ -│ Session ID: │ -│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │ -│ Success Rate: 0.0% │ -│ │ │ Performance │ │ Wall Time: 1h 23m 45s │ │ Agent Active: 50.2s │ diff --git a/packages/cli/src/ui/components/__snapshots__/StatsDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/StatsDisplay.test.tsx.snap index 09202599..bd27804c 100644 --- a/packages/cli/src/ui/components/__snapshots__/StatsDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/StatsDisplay.test.tsx.snap @@ -65,11 +65,6 @@ exports[`<StatsDisplay /> > Conditional Rendering Tests > hides Efficiency secti │ │ │ Session Stats │ │ │ -│ Interaction Summary │ -│ Session ID: test-session-id │ -│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │ -│ Success Rate: 0.0% │ -│ │ │ Performance │ │ Wall Time: 1s │ │ Agent Active: 100ms │ @@ -109,11 +104,6 @@ exports[`<StatsDisplay /> > Title Rendering > renders the custom title when a ti │ │ │ Agent powering down. Goodbye! │ │ │ -│ Interaction Summary │ -│ Session ID: test-session-id │ -│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │ -│ Success Rate: 0.0% │ -│ │ │ Performance │ │ Wall Time: 1s │ │ Agent Active: 0s │ @@ -129,11 +119,6 @@ exports[`<StatsDisplay /> > Title Rendering > renders the default title when no │ │ │ Session Stats │ │ │ -│ Interaction Summary │ -│ Session ID: test-session-id │ -│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │ -│ Success Rate: 0.0% │ -│ │ │ Performance │ │ Wall Time: 1s │ │ Agent Active: 0s │ @@ -149,11 +134,6 @@ exports[`<StatsDisplay /> > renders a table with two models correctly 1`] = ` │ │ │ Session Stats │ │ │ -│ Interaction Summary │ -│ Session ID: test-session-id │ -│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │ -│ Success Rate: 0.0% │ -│ │ │ Performance │ │ Wall Time: 1s │ │ Agent Active: 19.5s │ @@ -209,7 +189,7 @@ exports[`<StatsDisplay /> > renders only the Performance section in its zero sta │ │ │ Interaction Summary │ │ Session ID: test-session-id │ -│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │ +│ Tool Calls: 1 ( ✔ 0 ✖ 0 ) │ │ Success Rate: 0.0% │ │ │ │ Performance │ diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.tsx index c7ef122b..8f46a361 100644 --- a/packages/cli/src/ui/components/messages/CompressionMessage.tsx +++ b/packages/cli/src/ui/components/messages/CompressionMessage.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { Box, Text } from 'ink'; import { CompressionProps } from '../../types.js'; import Spinner from 'ink-spinner'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; export interface CompressionDisplayProps { compression: CompressionProps; @@ -32,13 +32,13 @@ export const CompressionMessage: React.FC<CompressionDisplayProps> = ({ {compression.isPending ? ( <Spinner type="dots" /> ) : ( - <Text color={Colors.AccentPurple}>✦</Text> + <Text color={theme.text.accent}>✦</Text> )} </Box> <Box> <Text color={ - compression.isPending ? Colors.AccentPurple : Colors.AccentGreen + compression.isPending ? theme.text.accent : theme.status.success } > {text} diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.tsx index fda5f1d4..edea9939 100644 --- a/packages/cli/src/ui/components/messages/DiffRenderer.tsx +++ b/packages/cli/src/ui/components/messages/DiffRenderer.tsx @@ -6,11 +6,9 @@ import React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../../colors.js'; import crypto from 'crypto'; import { colorizeCode, colorizeLine } from '../../utils/CodeColorizer.js'; import { MaxSizedBox } from '../shared/MaxSizedBox.js'; -import { theme } from '../../semantic-colors.js'; interface DiffLine { type: 'add' | 'del' | 'context' | 'hunk' | 'other'; @@ -108,14 +106,20 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({ theme, }) => { if (!diffContent || typeof diffContent !== 'string') { - return <Text color={Colors.AccentYellow}>No diff content.</Text>; + return ( + <Text color={theme?.semanticColors.status.warning}>No diff content.</Text> + ); } const parsedLines = parseDiffWithLineNumbers(diffContent); if (parsedLines.length === 0) { return ( - <Box borderStyle="round" borderColor={Colors.Gray} padding={1}> + <Box + borderStyle="round" + borderColor={theme?.semanticColors.border.default} + padding={1} + > <Text dimColor>No changes detected.</Text> </Box> ); @@ -158,6 +162,7 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({ tabWidth, availableTerminalHeight, terminalWidth, + theme, ); } @@ -170,6 +175,7 @@ const renderDiffContent = ( tabWidth = DEFAULT_TAB_WIDTH, availableTerminalHeight: number | undefined, terminalWidth: number, + theme: import('../../themes/theme.js').Theme | undefined, ) => { // 1. Normalize whitespace (replace tabs with spaces) *before* further processing const normalizedLines = parsedLines.map((line) => ({ @@ -184,7 +190,11 @@ const renderDiffContent = ( if (displayableLines.length === 0) { return ( - <Box borderStyle="round" borderColor={Colors.Gray} padding={1}> + <Box + borderStyle="round" + borderColor={theme?.semanticColors.border.default} + padding={1} + > <Text dimColor>No changes detected.</Text> </Box> ); @@ -248,7 +258,10 @@ const renderDiffContent = ( ) { acc.push( <Box key={`gap-${index}`}> - <Text wrap="truncate" color={Colors.Gray}> + <Text + wrap="truncate" + color={theme?.semanticColors.border.default} + > {'═'.repeat(terminalWidth)} </Text> </Box>, @@ -289,12 +302,12 @@ const renderDiffContent = ( acc.push( <Box key={lineKey} flexDirection="row"> <Text - color={theme.text.secondary} + color={theme?.semanticColors.text.secondary} backgroundColor={ line.type === 'add' - ? theme.background.diff.added + ? theme?.semanticColors.background.diff.added : line.type === 'del' - ? theme.background.diff.removed + ? theme?.semanticColors.background.diff.removed : undefined } > @@ -302,30 +315,32 @@ const renderDiffContent = ( </Text> {line.type === 'context' ? ( <> - <Text>{prefixSymbol} </Text> + <Text color={theme?.semanticColors.text.primary}> + {prefixSymbol}{' '} + </Text> <Text wrap="wrap"> - {colorizeLine(displayContent, language)} + {colorizeLine(displayContent, language, theme)} </Text> </> ) : ( <Text backgroundColor={ line.type === 'add' - ? theme.background.diff.added - : theme.background.diff.removed + ? theme?.semanticColors.background.diff.added + : theme?.semanticColors.background.diff.removed } wrap="wrap" > <Text color={ line.type === 'add' - ? theme.status.success - : theme.status.error + ? theme?.semanticColors.status.success + : theme?.semanticColors.status.error } > {prefixSymbol} </Text>{' '} - {colorizeLine(displayContent, language)} + {colorizeLine(displayContent, language, theme)} </Text> )} </Box>, diff --git a/packages/cli/src/ui/components/messages/ErrorMessage.tsx b/packages/cli/src/ui/components/messages/ErrorMessage.tsx index edbea435..bc249acf 100644 --- a/packages/cli/src/ui/components/messages/ErrorMessage.tsx +++ b/packages/cli/src/ui/components/messages/ErrorMessage.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'; interface ErrorMessageProps { text: string; @@ -19,10 +19,10 @@ export const ErrorMessage: React.FC<ErrorMessageProps> = ({ text }) => { return ( <Box flexDirection="row" marginBottom={1}> <Box width={prefixWidth}> - <Text color={Colors.AccentRed}>{prefix}</Text> + <Text color={theme.status.error}>{prefix}</Text> </Box> <Box flexGrow={1}> - <Text wrap="wrap" color={Colors.AccentRed}> + <Text wrap="wrap" color={theme.status.error}> {text} </Text> </Box> diff --git a/packages/cli/src/ui/components/messages/GeminiMessage.tsx b/packages/cli/src/ui/components/messages/GeminiMessage.tsx index 9863acd6..21c2f77d 100644 --- a/packages/cli/src/ui/components/messages/GeminiMessage.tsx +++ b/packages/cli/src/ui/components/messages/GeminiMessage.tsx @@ -7,7 +7,7 @@ import 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'; interface GeminiMessageProps { text: string; @@ -28,7 +28,7 @@ export const GeminiMessage: React.FC<GeminiMessageProps> = ({ return ( <Box flexDirection="row"> <Box width={prefixWidth}> - <Text color={Colors.AccentPurple}>{prefix}</Text> + <Text color={theme.text.accent}>{prefix}</Text> </Box> <Box flexGrow={1} flexDirection="column"> <MarkdownDisplay diff --git a/packages/cli/src/ui/components/messages/InfoMessage.tsx b/packages/cli/src/ui/components/messages/InfoMessage.tsx index c9129999..5b69216d 100644 --- a/packages/cli/src/ui/components/messages/InfoMessage.tsx +++ b/packages/cli/src/ui/components/messages/InfoMessage.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'; interface InfoMessageProps { text: string; @@ -19,10 +19,10 @@ export const InfoMessage: React.FC<InfoMessageProps> = ({ text }) => { return ( <Box flexDirection="row" marginTop={1}> <Box width={prefixWidth}> - <Text color={Colors.AccentYellow}>{prefix}</Text> + <Text color={theme.status.warning}>{prefix}</Text> </Box> <Box flexGrow={1}> - <Text wrap="wrap" color={Colors.AccentYellow}> + <Text wrap="wrap" color={theme.status.warning}> {text} </Text> </Box> diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx index 2f93609e..599b5445 100644 --- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Box, Text } from 'ink'; import { DiffRenderer } from './DiffRenderer.js'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; import { ToolCallConfirmationDetails, ToolConfirmationOutcome, @@ -112,13 +112,13 @@ export const ToolConfirmationMessage: React.FC< <Box minWidth="90%" borderStyle="round" - borderColor={Colors.Gray} + borderColor={theme.border.default} justifyContent="space-around" padding={1} overflow="hidden" > - <Text>Modify in progress: </Text> - <Text color={Colors.AccentGreen}> + <Text color={theme.text.primary}>Modify in progress: </Text> + <Text color={theme.status.success}> Save and close external editor to continue </Text> </Box> @@ -192,7 +192,7 @@ export const ToolConfirmationMessage: React.FC< maxWidth={Math.max(childWidth - 4, 1)} > <Box> - <Text color={Colors.AccentCyan}>{executionProps.command}</Text> + <Text color={theme.text.accent}>{executionProps.command}</Text> </Box> </MaxSizedBox> </Box> @@ -222,12 +222,15 @@ export const ToolConfirmationMessage: React.FC< bodyContent = ( <Box flexDirection="column" paddingX={1} marginLeft={1}> - <Text color={Colors.AccentCyan}>{infoProps.prompt}</Text> + <Text color={theme.text.accent}>{infoProps.prompt}</Text> {displayUrls && infoProps.urls && infoProps.urls.length > 0 && ( <Box flexDirection="column" marginTop={1}> - <Text>URLs to fetch:</Text> + <Text color={theme.text.primary}>URLs to fetch:</Text> {infoProps.urls.map((url) => ( - <Text key={url}> - {url}</Text> + <Text key={url} color={theme.text.primary}> + {' '} + - {url} + </Text> ))} </Box> )} @@ -239,8 +242,8 @@ export const ToolConfirmationMessage: React.FC< bodyContent = ( <Box flexDirection="column" paddingX={1} marginLeft={1}> - <Text color={Colors.AccentCyan}>MCP Server: {mcpProps.serverName}</Text> - <Text color={Colors.AccentCyan}>Tool: {mcpProps.toolName}</Text> + <Text color={theme.text.accent}>MCP Server: {mcpProps.serverName}</Text> + <Text color={theme.text.accent}>Tool: {mcpProps.toolName}</Text> </Box> ); @@ -275,7 +278,9 @@ export const ToolConfirmationMessage: React.FC< {/* Confirmation Question */} <Box marginBottom={1} flexShrink={0}> - <Text wrap="truncate">{question}</Text> + <Text wrap="truncate" color={theme.text.primary}> + {question} + </Text> </Box> {/* 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 e2df3d9c..3e7a317c 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx @@ -9,7 +9,7 @@ import { Box } from 'ink'; import { IndividualToolCallDisplay, ToolCallStatus } from '../../types.js'; import { ToolMessage } from './ToolMessage.js'; import { ToolConfirmationMessage } from './ToolConfirmationMessage.js'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; import { Config } from '@google/gemini-cli-core'; import { SHELL_COMMAND_NAME } from '../../constants.js'; @@ -35,7 +35,7 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({ ); const isShellCommand = toolCalls.some((t) => t.name === SHELL_COMMAND_NAME); const borderColor = - hasPending || isShellCommand ? Colors.AccentYellow : Colors.Gray; + hasPending || isShellCommand ? theme.status.warning : theme.border.default; const staticHeight = /* border */ 2 + /* marginBottom */ 1; // This is a bit of a magic number, but it accounts for the border and diff --git a/packages/cli/src/ui/components/messages/ToolMessage.tsx b/packages/cli/src/ui/components/messages/ToolMessage.tsx index e1eb75b8..bf82c400 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { Box, Text } from 'ink'; import { IndividualToolCallDisplay, ToolCallStatus } from '../../types.js'; import { DiffRenderer } from './DiffRenderer.js'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js'; import { MaxSizedBox } from '../shared/MaxSizedBox.js'; @@ -90,7 +90,9 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({ {typeof resultDisplay === 'string' && !renderOutputAsMarkdown && ( <MaxSizedBox maxHeight={availableHeight} maxWidth={childWidth}> <Box> - <Text wrap="wrap">{resultDisplay}</Text> + <Text wrap="wrap" color={theme.text.primary}> + {resultDisplay} + </Text> </Box> </MaxSizedBox> )} @@ -118,7 +120,7 @@ const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({ }) => ( <Box minWidth={STATUS_INDICATOR_WIDTH}> {status === ToolCallStatus.Pending && ( - <Text color={Colors.AccentGreen}>o</Text> + <Text color={theme.status.success}>o</Text> )} {status === ToolCallStatus.Executing && ( <GeminiRespondingSpinner @@ -127,18 +129,18 @@ const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({ /> )} {status === ToolCallStatus.Success && ( - <Text color={Colors.AccentGreen}>✔</Text> + <Text color={theme.status.success}>✔</Text> )} {status === ToolCallStatus.Confirming && ( - <Text color={Colors.AccentYellow}>?</Text> + <Text color={theme.status.warning}>?</Text> )} {status === ToolCallStatus.Canceled && ( - <Text color={Colors.AccentYellow} bold> + <Text color={theme.status.warning} bold> - </Text> )} {status === ToolCallStatus.Error && ( - <Text color={Colors.AccentRed} bold> + <Text color={theme.status.error} bold> x </Text> )} @@ -160,11 +162,11 @@ const ToolInfo: React.FC<ToolInfo> = ({ const nameColor = React.useMemo<string>(() => { 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; @@ -176,18 +178,19 @@ const ToolInfo: React.FC<ToolInfo> = ({ <Text wrap="truncate-end" strikethrough={status === ToolCallStatus.Canceled} + color={theme.text.primary} > <Text color={nameColor} bold> {name} </Text>{' '} - <Text color={Colors.Gray}>{description}</Text> + <Text color={theme.text.secondary}>{description}</Text> </Text> </Box> ); }; const TrailingIndicator: React.FC = () => ( - <Text color={Colors.Foreground} wrap="truncate"> + <Text color={theme.text.primary} wrap="truncate"> {' '} ← </Text> diff --git a/packages/cli/src/ui/components/messages/UserMessage.tsx b/packages/cli/src/ui/components/messages/UserMessage.tsx index 332cb0f4..f9a6f7a5 100644 --- a/packages/cli/src/ui/components/messages/UserMessage.tsx +++ b/packages/cli/src/ui/components/messages/UserMessage.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'; interface UserMessageProps { text: string; @@ -17,8 +17,8 @@ export const UserMessage: React.FC<UserMessageProps> = ({ text }) => { const prefixWidth = prefix.length; const isSlashCommand = text.startsWith('/'); - 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.border.default; return ( <Box diff --git a/packages/cli/src/ui/components/messages/UserShellMessage.tsx b/packages/cli/src/ui/components/messages/UserShellMessage.tsx index 946ca7e7..57b917d0 100644 --- a/packages/cli/src/ui/components/messages/UserShellMessage.tsx +++ b/packages/cli/src/ui/components/messages/UserShellMessage.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { Box, Text } from 'ink'; -import { Colors } from '../../colors.js'; +import { theme } from '../../semantic-colors.js'; interface UserShellMessageProps { text: string; @@ -18,8 +18,8 @@ export const UserShellMessage: React.FC<UserShellMessageProps> = ({ text }) => { return ( <Box> - <Text color={Colors.AccentCyan}>$ </Text> - <Text>{commandToDisplay}</Text> + <Text color={theme.text.accent}>$ </Text> + <Text color={theme.text.primary}>{commandToDisplay}</Text> </Box> ); }; diff --git a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx index 346472bf..4c8b0862 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'; @@ -173,6 +173,7 @@ export const MaxSizedBox: React.FC<MaxSizedBoxProps> = ({ <Box key={index}> {line.length > 0 ? ( line.map((segment, segIndex) => ( + // Avoid adding color styles to this <Text> element, breaks code colorization <Text key={segIndex} {...segment.props}> {segment.text} </Text> @@ -186,14 +187,14 @@ export const MaxSizedBox: React.FC<MaxSizedBoxProps> = ({ return ( <Box flexDirection="column" width={maxWidth} flexShrink={0}> {totalHiddenLines > 0 && overflowDirection === 'top' && ( - <Text color={Colors.Gray} wrap="truncate"> + <Text color={theme.text.secondary} wrap="truncate"> ... first {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '} hidden ... </Text> )} {visibleLines} {totalHiddenLines > 0 && overflowDirection === 'bottom' && ( - <Text color={Colors.Gray} wrap="truncate"> + <Text color={theme.text.secondary} wrap="truncate"> ... last {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '} hidden ... </Text> diff --git a/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx b/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx index 746744e5..b6c78feb 100644 --- a/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx +++ b/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx @@ -6,7 +6,7 @@ import React, { 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<T>({ return ( <Box flexDirection="column"> {showScrollArrows && ( - <Text color={scrollOffset > 0 ? Colors.Foreground : Colors.Gray}> + <Text + color={scrollOffset > 0 ? theme.text.primary : theme.text.secondary} + > ▲ </Text> )} @@ -172,18 +174,18 @@ export function RadioButtonSelect<T>({ 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; @@ -194,7 +196,9 @@ export function RadioButtonSelect<T>({ return ( <Box key={item.label} alignItems="center"> <Box minWidth={2} flexShrink={0}> - <Text color={isSelected ? Colors.AccentGreen : Colors.Foreground}> + <Text + color={isSelected ? theme.status.success : theme.text.primary} + > {isSelected ? '●' : ' '} </Text> </Box> @@ -208,7 +212,9 @@ export function RadioButtonSelect<T>({ {item.themeNameDisplay && item.themeTypeDisplay ? ( <Text color={textColor} wrap="truncate"> {item.themeNameDisplay}{' '} - <Text color={Colors.Gray}>{item.themeTypeDisplay}</Text> + <Text color={theme.text.secondary}> + {item.themeTypeDisplay} + </Text> </Text> ) : ( <Text color={textColor} wrap="truncate"> @@ -222,8 +228,8 @@ export function RadioButtonSelect<T>({ <Text color={ scrollOffset + maxItemsToShow < items.length - ? Colors.Foreground - : Colors.Gray + ? theme.text.primary + : theme.text.secondary } > ▼ diff --git a/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.tsx b/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.tsx index d4c13097..f79bb4d6 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 { 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 <Text color={Colors.Gray}>Loading...</Text>; + return <Text color={theme.text.secondary}>Loading...</Text>; } if (privacyState.error) { return ( <Box flexDirection="column" marginY={1}> - <Text color={Colors.AccentRed}> + <Text color={theme.status.error}> Error loading Opt-in settings: {privacyState.error} </Text> - <Text color={Colors.Gray}>Press Esc to exit.</Text> + <Text color={theme.text.secondary}>Press Esc to exit.</Text> </Box> ); } @@ -59,17 +59,17 @@ export const CloudFreePrivacyNotice = ({ return ( <Box flexDirection="column" marginY={1}> - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> Gemini Code Assist for Individuals Privacy Notice </Text> <Newline /> - <Text> + <Text color={theme.text.primary}> This notice and our Privacy Policy - <Text color={Colors.AccentBlue}>[1]</Text> describe how Gemini Code - Assist handles your data. Please read them carefully. + <Text color={theme.text.link}>[1]</Text> describe how Gemini Code Assist + handles your data. Please read them carefully. </Text> <Newline /> - <Text> + <Text color={theme.text.primary}> 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. </Text> <Newline /> - <Text> + <Text color={theme.text.primary}> 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 = ({ </Text> <Newline /> <Box flexDirection="column"> - <Text> + <Text color={theme.text.primary}> Allow Google to use this data to develop and improve our products? </Text> <RadioButtonSelect @@ -106,12 +106,14 @@ export const CloudFreePrivacyNotice = ({ /> </Box> <Newline /> - <Text> - <Text color={Colors.AccentBlue}>[1]</Text>{' '} + <Text color={theme.text.primary}> + <Text color={theme.text.link}>[1]</Text>{' '} https://policies.google.com/privacy </Text> <Newline /> - <Text color={Colors.Gray}>Press Enter to choose an option and exit.</Text> + <Text color={theme.text.secondary}> + Press Enter to choose an option and exit. + </Text> </Box> ); }; diff --git a/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.tsx b/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.tsx index f0adbb68..ce640308 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 ( <Box flexDirection="column" marginBottom={1}> - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> Vertex AI Notice </Text> <Newline /> - <Text> - Service Specific Terms<Text color={Colors.AccentBlue}>[1]</Text> are + <Text color={theme.text.primary}> + Service Specific Terms<Text color={theme.text.link}>[1]</Text> are incorporated into the agreement under which Google has agreed to provide - Google Cloud Platform<Text color={Colors.AccentGreen}>[2]</Text> to + Google Cloud Platform<Text color={theme.status.success}>[2]</Text> 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. </Text> <Newline /> - <Text> - <Text color={Colors.AccentBlue}>[1]</Text>{' '} + <Text color={theme.text.primary}> + <Text color={theme.text.link}>[1]</Text>{' '} https://cloud.google.com/terms/service-terms </Text> - <Text> - <Text color={Colors.AccentGreen}>[2]</Text>{' '} + <Text color={theme.text.primary}> + <Text color={theme.status.success}>[2]</Text>{' '} https://cloud.google.com/terms/services </Text> <Newline /> - <Text color={Colors.Gray}>Press Esc to exit.</Text> + <Text color={theme.text.secondary}>Press Esc to exit.</Text> </Box> ); }; diff --git a/packages/cli/src/ui/privacy/GeminiPrivacyNotice.tsx b/packages/cli/src/ui/privacy/GeminiPrivacyNotice.tsx index c0eaa74f..1f4015b5 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 ( <Box flexDirection="column" marginBottom={1}> - <Text bold color={Colors.AccentPurple}> + <Text bold color={theme.text.accent}> Gemini API Key Notice </Text> <Newline /> - <Text> - By using the Gemini API<Text color={Colors.AccentBlue}>[1]</Text>, - Google AI Studio - <Text color={Colors.AccentRed}>[2]</Text>, and the other Google + <Text color={theme.text.primary}> + By using the Gemini API<Text color={theme.text.link}>[1]</Text>, Google + AI Studio + <Text color={theme.status.error}>[2]</Text>, 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") - <Text color={Colors.AccentGreen}>[3]</Text>, and the Gemini API + <Text color={theme.status.success}>[3]</Text>, and the Gemini API Additional Terms of Service (the "Additional Terms") - <Text color={Colors.AccentPurple}>[4]</Text>. + <Text color={theme.text.accent}>[4]</Text>. </Text> <Newline /> - <Text> - <Text color={Colors.AccentBlue}>[1]</Text>{' '} + <Text color={theme.text.primary}> + <Text color={theme.text.link}>[1]</Text>{' '} https://ai.google.dev/docs/gemini_api_overview </Text> - <Text> - <Text color={Colors.AccentRed}>[2]</Text> https://aistudio.google.com/ + <Text color={theme.text.primary}> + <Text color={theme.status.error}>[2]</Text> https://aistudio.google.com/ </Text> - <Text> - <Text color={Colors.AccentGreen}>[3]</Text>{' '} + <Text color={theme.text.primary}> + <Text color={theme.status.success}>[3]</Text>{' '} https://developers.google.com/terms </Text> - <Text> - <Text color={Colors.AccentPurple}>[4]</Text>{' '} + <Text color={theme.text.primary}> + <Text color={theme.text.accent}>[4]</Text>{' '} https://ai.google.dev/gemini-api/terms </Text> <Newline /> - <Text color={Colors.Gray}>Press Esc to exit.</Text> + <Text color={theme.text.secondary}>Press Esc to exit.</Text> </Box> ); }; diff --git a/packages/cli/src/ui/themes/no-color.ts b/packages/cli/src/ui/themes/no-color.ts index 161b407e..4d5dc1bb 100644 --- a/packages/cli/src/ui/themes/no-color.ts +++ b/packages/cli/src/ui/themes/no-color.ts @@ -43,8 +43,6 @@ const noColorSemanticColors: SemanticColors = { focused: '', }, ui: { - comment: '', - symbol: '', gradient: [], }, status: { diff --git a/packages/cli/src/ui/themes/semantic-tokens.ts b/packages/cli/src/ui/themes/semantic-tokens.ts index 56430304..a2079934 100644 --- a/packages/cli/src/ui/themes/semantic-tokens.ts +++ b/packages/cli/src/ui/themes/semantic-tokens.ts @@ -25,8 +25,6 @@ export interface SemanticColors { focused: string; }; ui: { - comment: string; - symbol: string; gradient: string[] | undefined; }; status: { @@ -55,8 +53,6 @@ export const lightSemanticColors: SemanticColors = { focused: lightTheme.AccentBlue, }, ui: { - comment: lightTheme.Comment, - symbol: lightTheme.Gray, gradient: lightTheme.GradientColors, }, status: { @@ -85,8 +81,6 @@ export const darkSemanticColors: SemanticColors = { focused: darkTheme.AccentBlue, }, ui: { - comment: darkTheme.Comment, - symbol: darkTheme.Gray, gradient: darkTheme.GradientColors, }, status: { @@ -115,8 +109,6 @@ export const ansiSemanticColors: SemanticColors = { focused: ansiTheme.AccentBlue, }, ui: { - comment: ansiTheme.Comment, - symbol: ansiTheme.Gray, gradient: ansiTheme.GradientColors, }, status: { diff --git a/packages/cli/src/ui/themes/theme.ts b/packages/cli/src/ui/themes/theme.ts index e46c7f48..40900deb 100644 --- a/packages/cli/src/ui/themes/theme.ts +++ b/packages/cli/src/ui/themes/theme.ts @@ -235,7 +235,7 @@ export function createCustomTheme(customTheme: CustomTheme): Theme { customTheme.background?.diff?.added ?? customTheme.DiffAdded ?? '', DiffRemoved: customTheme.background?.diff?.removed ?? customTheme.DiffRemoved ?? '', - Comment: customTheme.ui?.comment ?? customTheme.Comment ?? '', + Comment: customTheme.text?.secondary ?? customTheme.Comment ?? '', Gray: customTheme.text?.secondary ?? customTheme.Gray ?? '', GradientColors: customTheme.ui?.gradient ?? customTheme.GradientColors, }; @@ -397,8 +397,6 @@ export function createCustomTheme(customTheme: CustomTheme): Theme { focused: colors.AccentBlue, }, ui: { - comment: colors.Comment, - symbol: colors.Gray, gradient: colors.GradientColors, }, status: { diff --git a/packages/cli/src/ui/utils/CodeColorizer.tsx b/packages/cli/src/ui/utils/CodeColorizer.tsx index b183d556..047e7bae 100644 --- a/packages/cli/src/ui/utils/CodeColorizer.tsx +++ b/packages/cli/src/ui/utils/CodeColorizer.tsx @@ -21,6 +21,7 @@ import { MINIMUM_MAX_HEIGHT, } from '../components/shared/MaxSizedBox.js'; import { LoadedSettings } from '../../config/settings.js'; +import { theme as semanticTheme } from '../semantic-colors.js'; // Configure theming and parsing utilities. const lowlight = createLowlight(common); @@ -171,7 +172,7 @@ export function colorizeCode( return ( <Box key={index}> {showLineNumbers && ( - <Text color={activeTheme.colors.Gray}> + <Text color={semanticTheme.text.secondary}> {`${String(index + 1 + hiddenLinesCount).padStart( padWidth, ' ', @@ -208,7 +209,7 @@ export function colorizeCode( {`${String(index + 1).padStart(padWidth, ' ')} `} </Text> )} - <Text color={activeTheme.colors.Gray}>{line}</Text> + <Text color={semanticTheme.text.secondary}>{line}</Text> </Box> ))} </MaxSizedBox> diff --git a/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx b/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx index ff8d6257..28c47b83 100644 --- a/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx +++ b/packages/cli/src/ui/utils/InlineMarkdownRenderer.tsx @@ -6,7 +6,7 @@ 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 @@ -31,7 +31,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => { while ((match = inlineRegex.exec(text)) !== null) { if (match.index > lastIndex) { nodes.push( - <Text key={`t-${lastIndex}`}> + <Text key={`t-${lastIndex}`} color={theme.text.primary}> {text.slice(lastIndex, match.index)} </Text>, ); @@ -48,7 +48,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => { fullMatch.length > BOLD_MARKER_LENGTH * 2 ) { renderedNode = ( - <Text key={key} bold> + <Text key={key} bold color={theme.text.primary}> {fullMatch.slice(BOLD_MARKER_LENGTH, -BOLD_MARKER_LENGTH)} </Text> ); @@ -60,13 +60,13 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => { !/\w/.test( text.substring(inlineRegex.lastIndex, inlineRegex.lastIndex + 1), ) && - !/\S[./\\]/.test(text.substring(match.index - 2, match.index)) && - !/[./\\]\S/.test( + !/\S[./]/.test(text.substring(match.index - 2, match.index)) && + !/[./]\S/.test( text.substring(inlineRegex.lastIndex, inlineRegex.lastIndex + 2), ) ) { renderedNode = ( - <Text key={key} italic> + <Text key={key} italic color={theme.text.primary}> {fullMatch.slice(ITALIC_MARKER_LENGTH, -ITALIC_MARKER_LENGTH)} </Text> ); @@ -76,7 +76,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => { fullMatch.length > STRIKETHROUGH_MARKER_LENGTH * 2 ) { renderedNode = ( - <Text key={key} strikethrough> + <Text key={key} strikethrough color={theme.text.primary}> {fullMatch.slice( STRIKETHROUGH_MARKER_LENGTH, -STRIKETHROUGH_MARKER_LENGTH, @@ -91,7 +91,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => { const codeMatch = fullMatch.match(/^(`+)(.+?)\1$/s); if (codeMatch && codeMatch[2]) { renderedNode = ( - <Text key={key} color={Colors.AccentPurple}> + <Text key={key} color={theme.text.accent}> {codeMatch[2]} </Text> ); @@ -106,9 +106,9 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => { const linkText = linkMatch[1]; const url = linkMatch[2]; renderedNode = ( - <Text key={key}> + <Text key={key} color={theme.text.primary}> {linkText} - <Text color={Colors.AccentBlue}> ({url})</Text> + <Text color={theme.text.link}> ({url})</Text> </Text> ); } @@ -116,10 +116,10 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => { fullMatch.startsWith('<u>') && fullMatch.endsWith('</u>') && fullMatch.length > - UNDERLINE_TAG_START_LENGTH + UNDERLINE_TAG_END_LENGTH - 1 // -1 because length is compared to combined length of start and end tags + UNDERLINE_TAG_START_LENGTH + UNDERLINE_TAG_END_LENGTH - 1 ) { renderedNode = ( - <Text key={key} underline> + <Text key={key} underline color={theme.text.primary}> {fullMatch.slice( UNDERLINE_TAG_START_LENGTH, -UNDERLINE_TAG_END_LENGTH, @@ -132,12 +132,22 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => { renderedNode = null; } - nodes.push(renderedNode ?? <Text key={key}>{fullMatch}</Text>); + nodes.push( + renderedNode ?? ( + <Text key={key} color={theme.text.primary}> + {fullMatch} + </Text> + ), + ); lastIndex = inlineRegex.lastIndex; } if (lastIndex < text.length) { - nodes.push(<Text key={`t-${lastIndex}`}>{text.slice(lastIndex)}</Text>); + nodes.push( + <Text key={`t-${lastIndex}`} color={theme.text.primary}> + {text.slice(lastIndex)} + </Text>, + ); } return <>{nodes.filter((node) => node !== null)}</>; diff --git a/packages/cli/src/ui/utils/MarkdownDisplay.tsx b/packages/cli/src/ui/utils/MarkdownDisplay.tsx index 7568e1f8..d656dbc8 100644 --- a/packages/cli/src/ui/utils/MarkdownDisplay.tsx +++ b/packages/cli/src/ui/utils/MarkdownDisplay.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 { colorizeCode } from './CodeColorizer.js'; import { TableRenderer } from './TableRenderer.js'; import { RenderInline } from './InlineMarkdownRenderer.js'; @@ -115,7 +115,7 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({ // Not a table, treat as regular text addContentBlock( <Box key={key}> - <Text wrap="wrap"> + <Text wrap="wrap" color={theme.text.primary}> <RenderInline text={line} /> </Text> </Box>, @@ -154,7 +154,7 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({ if (line.trim().length > 0) { addContentBlock( <Box key={key}> - <Text wrap="wrap"> + <Text wrap="wrap" color={theme.text.primary}> <RenderInline text={line} /> </Text> </Box>, @@ -173,35 +173,35 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({ switch (level) { case 1: headerNode = ( - <Text bold color={Colors.AccentCyan}> + <Text bold color={theme.text.accent}> <RenderInline text={headerText} /> </Text> ); break; case 2: headerNode = ( - <Text bold color={Colors.AccentBlue}> + <Text bold color={theme.text.link}> <RenderInline text={headerText} /> </Text> ); break; case 3: headerNode = ( - <Text bold> + <Text bold color={theme.text.primary}> <RenderInline text={headerText} /> </Text> ); break; case 4: headerNode = ( - <Text italic color={Colors.Gray}> + <Text italic color={theme.text.secondary}> <RenderInline text={headerText} /> </Text> ); break; default: headerNode = ( - <Text> + <Text color={theme.text.primary}> <RenderInline text={headerText} /> </Text> ); @@ -245,7 +245,7 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({ } else { addContentBlock( <Box key={key}> - <Text wrap="wrap"> + <Text wrap="wrap" color={theme.text.primary}> <RenderInline text={line} /> </Text> </Box>, @@ -314,7 +314,9 @@ const RenderCodeBlockInternal: React.FC<RenderCodeBlockProps> = ({ // Not enough space to even show the message meaningfully return ( <Box paddingLeft={CODE_BLOCK_PREFIX_PADDING}> - <Text color={Colors.Gray}>... code is being written ...</Text> + <Text color={theme.text.secondary}> + ... code is being written ... + </Text> </Box> ); } @@ -330,7 +332,7 @@ const RenderCodeBlockInternal: React.FC<RenderCodeBlockProps> = ({ return ( <Box paddingLeft={CODE_BLOCK_PREFIX_PADDING} flexDirection="column"> {colorizedTruncatedCode} - <Text color={Colors.Gray}>... generating more ...</Text> + <Text color={theme.text.secondary}>... generating more ...</Text> </Box> ); } @@ -383,10 +385,10 @@ const RenderListItemInternal: React.FC<RenderListItemProps> = ({ flexDirection="row" > <Box width={prefixWidth}> - <Text>{prefix}</Text> + <Text color={theme.text.accent}>{prefix}</Text> </Box> <Box flexGrow={LIST_ITEM_TEXT_FLEX_GROW}> - <Text wrap="wrap"> + <Text wrap="wrap" color={theme.text.primary}> <RenderInline text={itemText} /> </Text> </Box> diff --git a/packages/cli/src/ui/utils/TableRenderer.tsx b/packages/cli/src/ui/utils/TableRenderer.tsx index 2ec19549..478a0893 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 { @@ -87,9 +87,9 @@ export const TableRenderer: React.FC<TableRendererProps> = ({ const paddingNeeded = Math.max(0, contentWidth - actualDisplayWidth); return ( - <Text> + <Text color={theme.text.primary}> {isHeader ? ( - <Text bold color={Colors.AccentCyan}> + <Text bold color={theme.text.accent}> <RenderInline text={cellContent} /> </Text> ) : ( @@ -112,7 +112,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({ const borderParts = adjustedWidths.map((w) => char.horizontal.repeat(w)); const border = char.left + borderParts.join(char.middle) + char.right; - return <Text>{border}</Text>; + return <Text color={theme.text.primary}>{border}</Text>; }; // Helper function to render a table row @@ -123,7 +123,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({ }); return ( - <Text> + <Text color={theme.text.primary}> │{' '} {renderedCells.map((cell, index) => ( <React.Fragment key={index}> diff --git a/packages/cli/src/ui/utils/displayUtils.test.ts b/packages/cli/src/ui/utils/displayUtils.test.ts index 7dd9f0e8..64b0a727 100644 --- a/packages/cli/src/ui/utils/displayUtils.test.ts +++ b/packages/cli/src/ui/utils/displayUtils.test.ts @@ -14,7 +14,7 @@ import { CACHE_EFFICIENCY_HIGH, CACHE_EFFICIENCY_MEDIUM, } from './displayUtils.js'; -import { Colors } from '../colors.js'; +import { theme } from '../semantic-colors.js'; describe('displayUtils', () => { describe('getStatusColor', () => { @@ -24,24 +24,24 @@ describe('displayUtils', () => { }; it('should return green for values >= green threshold', () => { - expect(getStatusColor(90, thresholds)).toBe(Colors.AccentGreen); - expect(getStatusColor(80, thresholds)).toBe(Colors.AccentGreen); + expect(getStatusColor(90, thresholds)).toBe(theme.status.success); + expect(getStatusColor(80, thresholds)).toBe(theme.status.success); }); it('should return yellow for values < green and >= yellow threshold', () => { - expect(getStatusColor(79, thresholds)).toBe(Colors.AccentYellow); - expect(getStatusColor(50, thresholds)).toBe(Colors.AccentYellow); + expect(getStatusColor(79, thresholds)).toBe(theme.status.warning); + expect(getStatusColor(50, thresholds)).toBe(theme.status.warning); }); it('should return red for values < yellow threshold', () => { - expect(getStatusColor(49, thresholds)).toBe(Colors.AccentRed); - expect(getStatusColor(0, thresholds)).toBe(Colors.AccentRed); + expect(getStatusColor(49, thresholds)).toBe(theme.status.error); + expect(getStatusColor(0, thresholds)).toBe(theme.status.error); }); it('should return defaultColor for values < yellow threshold when provided', () => { expect( - getStatusColor(49, thresholds, { defaultColor: Colors.Foreground }), - ).toBe(Colors.Foreground); + getStatusColor(49, thresholds, { defaultColor: theme.text.primary }), + ).toBe(theme.text.primary); }); }); diff --git a/packages/cli/src/ui/utils/displayUtils.ts b/packages/cli/src/ui/utils/displayUtils.ts index a52c6ff0..6f6c9209 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; }; |
