From 3c0af3654ac5491e79c6f9b55de5debf0e1e13c1 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Fri, 15 Aug 2025 15:39:54 -0700 Subject: [PATCH] Update semantic color tokens (#6253) Co-authored-by: jacob314 --- packages/cli/src/ui/App.tsx | 28 +++-- packages/cli/src/ui/commands/chatCommand.ts | 4 +- packages/cli/src/ui/components/AboutBox.tsx | 36 +++--- packages/cli/src/ui/components/AuthDialog.tsx | 22 ++-- .../cli/src/ui/components/AuthInProgress.tsx | 8 +- .../src/ui/components/AutoAcceptIndicator.tsx | 8 +- .../ui/components/ConsoleSummaryDisplay.tsx | 6 +- .../ui/components/ContextSummaryDisplay.tsx | 10 +- .../src/ui/components/ContextUsageDisplay.tsx | 4 +- .../cli/src/ui/components/DebugProfiler.tsx | 4 +- .../ui/components/DetailedMessagesDisplay.tsx | 19 +-- .../ui/components/EditorSettingsDialog.tsx | 16 +-- .../src/ui/components/FolderTrustDialog.tsx | 10 +- packages/cli/src/ui/components/Footer.tsx | 8 +- .../ui/components/GeminiRespondingSpinner.tsx | 3 +- .../cli/src/ui/components/Header.test.tsx | 16 +-- packages/cli/src/ui/components/Header.tsx | 30 ++--- packages/cli/src/ui/components/Help.tsx | 84 ++++++------- .../cli/src/ui/components/InputPrompt.tsx | 9 +- .../src/ui/components/LoadingIndicator.tsx | 10 +- .../src/ui/components/MemoryUsageDisplay.tsx | 12 +- .../src/ui/components/ModelStatsDisplay.tsx | 30 +++-- .../cli/src/ui/components/PrepareLabel.tsx | 6 +- .../cli/src/ui/components/SettingsDialog.tsx | 36 +++--- .../ui/components/ShellConfirmationDialog.tsx | 18 +-- .../src/ui/components/ShellModeIndicator.tsx | 6 +- .../cli/src/ui/components/ShowMoreLines.tsx | 4 +- .../src/ui/components/StatsDisplay.test.tsx | 2 +- .../cli/src/ui/components/StatsDisplay.tsx | 115 ++++++++++-------- .../src/ui/components/SuggestionsDisplay.tsx | 14 ++- .../cli/src/ui/components/ThemeDialog.tsx | 37 ++++-- packages/cli/src/ui/components/Tips.tsx | 16 +-- .../src/ui/components/ToolStatsDisplay.tsx | 61 ++++++---- .../src/ui/components/UpdateNotification.tsx | 6 +- .../SessionSummaryDisplay.test.tsx.snap | 5 - .../__snapshots__/StatsDisplay.test.tsx.snap | 22 +--- .../messages/CompressionMessage.tsx | 6 +- .../ui/components/messages/DiffRenderer.tsx | 47 ++++--- .../ui/components/messages/ErrorMessage.tsx | 6 +- .../ui/components/messages/GeminiMessage.tsx | 4 +- .../ui/components/messages/InfoMessage.tsx | 6 +- .../messages/ToolConfirmationMessage.tsx | 27 ++-- .../components/messages/ToolGroupMessage.tsx | 4 +- .../ui/components/messages/ToolMessage.tsx | 27 ++-- .../ui/components/messages/UserMessage.tsx | 6 +- .../components/messages/UserShellMessage.tsx | 6 +- .../src/ui/components/shared/MaxSizedBox.tsx | 7 +- .../components/shared/RadioButtonSelect.tsx | 32 +++-- .../src/ui/privacy/CloudFreePrivacyNotice.tsx | 30 ++--- .../src/ui/privacy/CloudPaidPrivacyNotice.tsx | 20 +-- .../src/ui/privacy/GeminiPrivacyNotice.tsx | 34 +++--- packages/cli/src/ui/themes/no-color.ts | 2 - packages/cli/src/ui/themes/semantic-tokens.ts | 8 -- packages/cli/src/ui/themes/theme.ts | 4 +- packages/cli/src/ui/utils/CodeColorizer.tsx | 5 +- .../src/ui/utils/InlineMarkdownRenderer.tsx | 38 +++--- packages/cli/src/ui/utils/MarkdownDisplay.tsx | 28 +++-- packages/cli/src/ui/utils/TableRenderer.tsx | 10 +- .../cli/src/ui/utils/displayUtils.test.ts | 18 +-- packages/cli/src/ui/utils/displayUtils.ts | 8 +- 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 && ( {startupWarnings.map((warning, index) => ( - + {warning} ))} @@ -991,7 +991,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { {themeError && ( - {themeError} + {themeError} )} { {editorError && ( - {editorError} + {editorError} )} { > {process.env.GEMINI_SYSTEM_MD && ( - |⌐■_■| + |⌐■_■| )} {ctrlCPressedOnce ? ( - + Press Ctrl+C again to exit. ) : ctrlDPressedOnce ? ( - + Press Ctrl+D again to exit. ) : showEscapePrompt ? ( - Press Esc again to clear. + + Press Esc again to clear. + ) : ( { {initError && streamingState !== StreamingState.Responding && ( @@ -1172,7 +1174,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { (item) => item.type === 'error' && item.text?.includes(initError), )?.text ? ( - + { history.find( (item) => @@ -1182,10 +1184,10 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { ) : ( <> - + Initialization Error: {initError} - + {' '} Please check API key and configuration. 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 = ({ }) => ( - + About Gemini CLI - + CLI Version - {cliVersion} + {cliVersion} {GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO) && ( - + Git Commit - {GIT_COMMIT_INFO} + {GIT_COMMIT_INFO} )} - + Model - {modelVersion} + {modelVersion} - + Sandbox - {sandboxEnv} + {sandboxEnv} - + OS - {osVersion} + {osVersion} - + Auth Method - + {selectedAuthType.startsWith('oauth') ? 'OAuth' : selectedAuthType} @@ -108,19 +108,19 @@ export const AboutBox: React.FC = ({ {gcpProject && ( - + GCP Project - {gcpProject} + {gcpProject} )} {ideClient && ( - + IDE Client 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 ( - Get started + + Get started + - How would you like to authenticate for this project? + + How would you like to authenticate for this project? + {errorMessage && ( - {errorMessage} + {errorMessage} )} - (Use Enter to select) + (Use Enter to select) - Terms of Services and Privacy Notice for Gemini CLI + + Terms of Services and Privacy Notice for Gemini CLI + - + { 'https://github.com/google-gemini/gemini-cli/blob/main/docs/tos-privacy.md' } diff --git a/packages/cli/src/ui/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 ( {timedOut ? ( - + Authentication timed out. Please try again. ) : ( - + Waiting for auth... (Press ESC or CTRL+C to cancel) 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 = ({ switch (approvalMode) { case ApprovalMode.AUTO_EDIT: - textColor = Colors.AccentGreen; + textColor = theme.status.success; textContent = 'accepting edits'; subText = ' (shift + tab to toggle)'; break; case ApprovalMode.YOLO: - textColor = Colors.AccentRed; + textColor = theme.status.error; textContent = 'YOLO mode'; subText = ' (ctrl + y to toggle)'; break; @@ -40,7 +40,7 @@ export const AutoAcceptIndicator: React.FC = ({ {textContent} - {subText && {subText}} + {subText && {subText}} ); diff --git a/packages/cli/src/ui/components/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 = ({ return ( {errorCount > 0 && ( - + {errorIcon} {errorCount} error{errorCount > 1 ? 's' : ''}{' '} - (ctrl+o for details) + (ctrl+o for details) )} diff --git a/packages/cli/src/ui/components/ContextSummaryDisplay.tsx b/packages/cli/src/ui/components/ContextSummaryDisplay.tsx index 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 = ({ if (isNarrow) { return ( - Using: + Using: {summaryParts.map((part, index) => ( - + {' '}- {part} ))} @@ -111,7 +111,9 @@ export const ContextSummaryDisplay: React.FC = ({ return ( - Using: {summaryParts.join(' | ')} + + Using: {summaryParts.join(' | ')} + ); }; diff --git a/packages/cli/src/ui/components/ContextUsageDisplay.tsx b/packages/cli/src/ui/components/ContextUsageDisplay.tsx index 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 ( - + ({((1 - percentage) * 100).toFixed(0)}% context left) ); diff --git a/packages/cli/src/ui/components/DebugProfiler.tsx b/packages/cli/src/ui/components/DebugProfiler.tsx index 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 ( - Renders: {numRenders.current} + Renders: {numRenders.current} ); }; diff --git a/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx b/packages/cli/src/ui/components/DetailedMessagesDisplay.tsx index 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} > - - Debug Console (ctrl+o to close) + + Debug Console{' '} + (ctrl+o to close) {messages.map((msg, index) => { - let textColor = Colors.Foreground; + let textColor = theme.text.primary; let icon = '\u2139'; // Information source (ℹ) switch (msg.type) { case 'warn': - textColor = Colors.AccentYellow; + textColor = theme.status.warning; icon = '\u26A0'; // Warning sign (⚠) break; case 'error': - textColor = Colors.AccentRed; + textColor = theme.status.error; icon = '\u2716'; // Heavy multiplication x (✖) break; case 'debug': - textColor = Colors.Gray; // Or Colors.Gray + textColor = theme.text.secondary; icon = '\u1F50D'; // Left-pointing magnifying glass (????) break; case 'log': @@ -70,7 +71,7 @@ export const DetailedMessagesDisplay: React.FC< {msg.content} {msg.count && msg.count > 1 && ( - (x{msg.count}) + (x{msg.count}) )} diff --git a/packages/cli/src/ui/components/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 ( {focusedSection === 'editor' ? '> ' : ' '}Select Editor{' '} - {otherScopeModifiedMessage} + {otherScopeModifiedMessage} ({ @@ -138,7 +138,7 @@ export function EditorSettingsDialog({ - + (Use Enter to select, Tab to change focus) @@ -147,17 +147,17 @@ export function EditorSettingsDialog({ Editor Preference - + These editors are currently supported. Please note that some editors cannot be used in sandbox mode. - + Your preferred editor is:{' '} diff --git a/packages/cli/src/ui/components/FolderTrustDialog.tsx b/packages/cli/src/ui/components/FolderTrustDialog.tsx index 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 = ({ - Do you trust this folder? - + + Do you trust this folder? + + Trusting a folder allows Gemini to execute commands it suggests. This is a security feature to prevent accidental execution in untrusted directories. 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 = ({ {vimMode && [{vimMode}] } {nightly ? ( - + {displayPath} {branchName && ({branchName}*)} @@ -132,8 +132,8 @@ export const Footer: React.FC = ({ /> {corgiMode && ( - - | + + | @@ -143,7 +143,7 @@ export const Footer: React.FC = ({ )} {!showErrorDetails && errorCount > 0 && ( - | + | )} 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 ; } else if (nonRespondingDisplay) { - return {nonRespondingDisplay}; + return {nonRespondingDisplay}; } 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('
', () => { - beforeEach(() => {}); - it('renders the long logo on a wide terminal', () => { vi.spyOn(useTerminalSize, 'useTerminalSize').mockReturnValue({ columns: 120, rows: 20, }); const { lastFrame } = render(
); - expect(lastFrame()).toContain(longAsciiLogo); + expect(lastFrame()?.trim()).toContain(longAsciiLogo.trim()); }); it('renders custom ASCII art when provided', () => { @@ -29,16 +27,20 @@ describe('
', () => { const { lastFrame } = render(
, ); - expect(lastFrame()).toContain(customArt); + expect(lastFrame()?.trim()).toContain(customArt); }); it('displays the version number when nightly is true', () => { const { lastFrame } = render(
); - 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(
); - 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 = {children}; + if (theme.ui.gradient && theme.ui.gradient.length > 0) { + return {textElement}; + } + return textElement; +}; + export const Header: React.FC = ({ customAsciiArt, version, @@ -47,22 +57,12 @@ export const Header: React.FC = ({ flexShrink={0} flexDirection="column" > - {Colors.GradientColors ? ( - - {displayTitle} - - ) : ( - {displayTitle} - )} + + {displayTitle} + {nightly && ( - {Colors.GradientColors ? ( - - v{version} - - ) : ( - v{version} - )} + v{version} )} 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 = ({ commands }) => ( {/* Basics */} - + Basics: - - + + Add context : Use{' '} - + @ {' '} to specify files for context (e.g.,{' '} - + @src/myFile.ts ) to target specific files or folders. - - + + Shell mode : Execute shell commands via{' '} - + ! {' '} (e.g.,{' '} - + !npm run start ) or use natural language (e.g.{' '} - + start server ). @@ -61,15 +61,15 @@ export const Help: React.FC = ({ commands }) => ( {/* Commands */} - + Commands: {commands .filter((command) => command.description) .map((command: SlashCommand) => ( - - + + {' '} /{command.name} @@ -77,8 +77,8 @@ export const Help: React.FC = ({ commands }) => ( {command.subCommands && command.subCommands.map((subCommand) => ( - - + + {' '} {subCommand.name} @@ -87,8 +87,8 @@ export const Help: React.FC = ({ commands }) => ( ))} ))} - - + + {' '} !{' '} @@ -98,75 +98,75 @@ export const Help: React.FC = ({ commands }) => ( {/* Shortcuts */} - + Keyboard Shortcuts: - - + + Alt+Left/Right {' '} - Jump through words in the input - - + + Ctrl+C {' '} - Quit application - - + + {process.platform === 'win32' ? 'Ctrl+Enter' : 'Ctrl+J'} {' '} {process.platform === 'linux' ? '- New line (Alt+Enter works for certain linux distros)' : '- New line'} - - + + Ctrl+L {' '} - Clear the screen - - + + {process.platform === 'darwin' ? 'Ctrl+X / Meta+Enter' : 'Ctrl+X'} {' '} - Open input in external editor - - + + Ctrl+Y {' '} - Toggle YOLO mode - - + + Enter {' '} - Send message - - + + Esc {' '} - Cancel operation - - + + Shift+Tab {' '} - Toggle auto-accepting edits - - + + Up/Down {' '} - Cycle through your prompt history - + For a full list of shortcuts, see{' '} - + docs/keyboard-shortcuts.md diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index 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 = ({ {buffer.text.length === 0 && placeholder ? ( focus ? ( - + {chalk.inverse(placeholder.slice(0, 1))} {placeholder.slice(1)} @@ -600,7 +600,12 @@ export const InputPrompt: React.FC = ({ } } return ( - {display} + + {display} + ); }) )} 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 = ({ } /> - {primaryText && ( - {primaryText} - )} + {primaryText && {primaryText}} {!isNarrow && cancelAndTimerContent && ( - {cancelAndTimerContent} + {cancelAndTimerContent} )} {!isNarrow && {/* Spacer */}} @@ -73,7 +71,7 @@ export const LoadingIndicator: React.FC = ({ {isNarrow && cancelAndTimerContent && ( - {cancelAndTimerContent} + {cancelAndTimerContent} )} {isNarrow && rightContent && {rightContent}} diff --git a/packages/cli/src/ui/components/MemoryUsageDisplay.tsx b/packages/cli/src/ui/components/MemoryUsageDisplay.tsx index 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(''); - const [memoryUsageColor, setMemoryUsageColor] = useState(Colors.Gray); + const [memoryUsageColor, setMemoryUsageColor] = useState( + theme.text.secondary, + ); useEffect(() => { const updateMemory = () => { const usage = process.memoryUsage().rss; setMemoryUsage(formatMemoryUsage(usage)); setMemoryUsageColor( - usage >= 2 * 1024 * 1024 * 1024 ? Colors.AccentRed : Colors.Gray, + usage >= 2 * 1024 * 1024 * 1024 + ? theme.status.error + : theme.text.secondary, ); }; const intervalId = setInterval(updateMemory, 2000); @@ -29,7 +33,7 @@ export const MemoryUsageDisplay: React.FC = () => { return ( - | + | {memoryUsage} ); diff --git a/packages/cli/src/ui/components/ModelStatsDisplay.tsx b/packages/cli/src/ui/components/ModelStatsDisplay.tsx index 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 = ({ }) => ( - + {isSubtle ? ` ↳ ${title}` : title} {values.map((value, index) => ( - {value} + {value} ))} @@ -56,11 +56,13 @@ export const ModelStatsDisplay: React.FC = () => { return ( - No API calls have been made in this session. + + No API calls have been made in this session. + ); } @@ -82,12 +84,12 @@ export const ModelStatsDisplay: React.FC = () => { return ( - + Model Stats For Nerds @@ -95,11 +97,15 @@ export const ModelStatsDisplay: React.FC = () => { {/* Header */} - Metric + + Metric + {modelNames.map((name) => ( - {name} + + {name} + ))} @@ -126,7 +132,7 @@ export const ModelStatsDisplay: React.FC = () => { return ( 0 ? Colors.AccentRed : Colors.Foreground + m.api.totalErrors > 0 ? theme.status.error : theme.text.primary } > {m.api.totalErrors.toLocaleString()} ({errorRate.toFixed(1)}%) @@ -149,7 +155,7 @@ export const ModelStatsDisplay: React.FC = () => { ( - + {m.tokens.total.toLocaleString()} ))} @@ -166,7 +172,7 @@ export const ModelStatsDisplay: React.FC = () => { values={getModelValues((m) => { const cacheHitRate = calculateCacheHitRate(m); return ( - + {m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%) ); diff --git a/packages/cli/src/ui/components/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 = ({ matchedIndex, userInput, textColor, - highlightColor = Colors.AccentYellow, + highlightColor = theme.status.warning, }) => { if ( matchedIndex === undefined || @@ -37,7 +37,7 @@ export const PrepareLabel: React.FC = ({ const end = label.slice(matchedIndex + userInput.length); return ( - + {start} {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 ( - + Settings - {showScrollUp && } + {showScrollUp && } {visibleItems.map((item, idx) => { const isActive = focusSection === 'settings' && @@ -405,17 +405,21 @@ export function SettingsDialog({ - + {isActive ? '●' : ''} {item.label} {scopeMessage && ( - {scopeMessage} + {scopeMessage} )} @@ -423,10 +427,10 @@ export function SettingsDialog({ {displayValue} @@ -436,12 +440,16 @@ export function SettingsDialog({ ); })} - {showScrollDown && } + {showScrollDown && } - + {focusSection === 'scope' ? '> ' : ' '}Apply To - + (Use Enter to select, Tab to change focus) {showRestartPrompt && ( - + To see changes, Gemini CLI must be restarted. Press r to exit and apply changes now. diff --git a/packages/cli/src/ui/components/ShellConfirmationDialog.tsx b/packages/cli/src/ui/components/ShellConfirmationDialog.tsx index 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< - Shell Command Execution - A custom command wants to run the following shell commands: + + Shell Command Execution + + + A custom command wants to run the following shell commands: + {commands.map((cmd) => ( - + {cmd} ))} @@ -93,7 +97,7 @@ export const ShellConfirmationDialog: React.FC< - Do you want to proceed? + Do you want to proceed? diff --git a/packages/cli/src/ui/components/ShellModeIndicator.tsx b/packages/cli/src/ui/components/ShellModeIndicator.tsx index 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 = () => ( - + shell mode enabled - (esc to disable) + (esc to disable) ); diff --git a/packages/cli/src/ui/components/ShowMoreLines.tsx b/packages/cli/src/ui/components/ShowMoreLines.tsx index 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 ( - + Press ctrl-s to show more lines 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('', () => { 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 = ({ title, children }) => ( {/* Fixed width for the label creates a clean "gutter" for alignment */} - {title} + {title} {children} @@ -45,7 +45,7 @@ const SubStatRow: React.FC = ({ title, children }) => ( {/* Adjust width for the "» " prefix */} - » {title} + » {title} {children} @@ -59,7 +59,9 @@ interface SectionProps { const Section: React.FC = ({ title, children }) => ( - {title} + + {title} + {children} ); @@ -79,16 +81,24 @@ const ModelUsageTable: React.FC<{ {/* Header */} - Model Usage + + Model Usage + - Reqs + + Reqs + - Input Tokens + + Input Tokens + - Output Tokens + + Output Tokens + {/* Divider */} @@ -98,6 +108,7 @@ const ModelUsageTable: React.FC<{ borderTop={false} borderLeft={false} borderRight={false} + borderColor={theme.border.default} width={nameWidth + requestsWidth + inputTokensWidth + outputTokensWidth} > @@ -105,18 +116,20 @@ const ModelUsageTable: React.FC<{ {Object.entries(models).map(([name, modelMetrics]) => ( - {name.replace('-001', '')} + {name.replace('-001', '')} - {modelMetrics.api.totalRequests} + + {modelMetrics.api.totalRequests} + - + {modelMetrics.tokens.prompt.toLocaleString()} - + {modelMetrics.tokens.candidates.toLocaleString()} @@ -124,13 +137,13 @@ const ModelUsageTable: React.FC<{ ))} {cacheEfficiency > 0 && ( - - Savings Highlight:{' '} + + Savings Highlight:{' '} {totalCachedTokens.toLocaleString()} ({cacheEfficiency.toFixed(1)} %) of input tokens were served from the cache, reducing costs. - + » Tip: For a full token breakdown, run `/stats model`. @@ -169,18 +182,18 @@ export const StatsDisplay: React.FC = ({ const renderTitle = () => { if (title) { - return Colors.GradientColors && Colors.GradientColors.length > 0 ? ( - + return theme.ui.gradient && theme.ui.gradient.length > 0 ? ( + {title} ) : ( - + {title} ); } return ( - + Session Stats ); @@ -189,7 +202,7 @@ export const StatsDisplay: React.FC = ({ return ( = ({ {renderTitle()} -
- - {stats.sessionId} - - - - {tools.totalCalls} ({' '} - ✔ {tools.totalSuccess}{' '} - ✖ {tools.totalFail} ) - - - - {computed.successRate.toFixed(1)}% - - {computed.totalDecisions > 0 && ( - - - {computed.agreementRate.toFixed(1)}%{' '} - - ({computed.totalDecisions} reviewed) - + {tools.totalCalls > 0 && ( +
+ + {stats.sessionId} + + + + {tools.totalCalls} ({' '} + ✔ {tools.totalSuccess}{' '} + ✖ {tools.totalFail} ) - )} -
+ + {computed.successRate.toFixed(1)}% + + {computed.totalDecisions > 0 && ( + + + {computed.agreementRate.toFixed(1)}%{' '} + + ({computed.totalDecisions} reviewed) + + + + )} +
+ )}
- {duration} + {duration} - {formatDuration(computed.agentActiveTime)} + + {formatDuration(computed.agentActiveTime)} + - + {formatDuration(computed.totalApiTime)}{' '} - + ({computed.apiTimePercent.toFixed(1)}%) - + {formatDuration(computed.totalToolTime)}{' '} - + ({computed.toolTimePercent.toFixed(1)}%) diff --git a/packages/cli/src/ui/components/SuggestionsDisplay.tsx b/packages/cli/src/ui/components/SuggestionsDisplay.tsx index 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 ( - Loading suggestions... + Loading suggestions... ); } @@ -54,12 +54,12 @@ export function SuggestionsDisplay({ return ( - {scrollOffset > 0 && } + {scrollOffset > 0 && } {visibleSuggestions.map((suggestion, index) => { const originalIndex = startIndex + index; const isActive = originalIndex === activeIndex; - const textColor = isActive ? Colors.AccentPurple : Colors.Gray; + const textColor = isActive ? theme.text.accent : theme.text.secondary; const labelElement = ( ); })} - {endIndex < suggestions.length && } + {endIndex < suggestions.length && ( + + )} {suggestions.length > MAX_SUGGESTIONS_TO_SHOW && ( - + ({activeIndex + 1}/{suggestions.length}) )} 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 ( {/* Left Column: Selection */} - + {currentFocusedSection === 'theme' ? '> ' : ' '}Select Theme{' '} - {otherScopeModifiedMessage} + + {otherScopeModifiedMessage} + - + {currentFocusedSection === 'scope' ? '> ' : ' '}Apply To - Preview + + Preview + {/* 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 ( - + (Use Enter to select {showScopeSelection ? ', Tab to change focus' : ''}) 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 = ({ config }) => { const geminiMdFileCount = config.getGeminiMdFileCount(); return ( - Tips for getting started: - + Tips for getting started: + 1. Ask questions, edit files, or run commands. - + 2. Be specific for the best results. {geminiMdFileCount === 0 && ( - + 3. Create{' '} - + GEMINI.md {' '} files to customize your interactions with Gemini. )} - + {geminiMdFileCount === 0 ? '4.' : '3.'}{' '} - + /help {' '} for more information. diff --git a/packages/cli/src/ui/components/ToolStatsDisplay.tsx b/packages/cli/src/ui/components/ToolStatsDisplay.tsx index 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 ( - {name} + {name} - {stats.count} + {stats.count} {successRate.toFixed(1)}% - {formatDuration(avgDuration)} + {formatDuration(avgDuration)} ); @@ -63,11 +63,13 @@ export const ToolStatsDisplay: React.FC = () => { return ( - No tool calls have been made in this session. + + No tool calls have been made in this session. + ); } @@ -94,13 +96,13 @@ export const ToolStatsDisplay: React.FC = () => { return ( - + Tool Stats For Nerds @@ -108,16 +110,24 @@ export const ToolStatsDisplay: React.FC = () => { {/* Header */} - Tool Name + + Tool Name + - Calls + + Calls + - Success Rate + + Success Rate + - Avg Duration + + Avg Duration + @@ -139,45 +149,47 @@ export const ToolStatsDisplay: React.FC = () => { {/* User Decision Summary */} - User Decision Summary + + User Decision Summary + - Total Reviewed Suggestions: + Total Reviewed Suggestions: - {totalReviewed} + {totalReviewed} - » Accepted: + » Accepted: - {totalDecisions.accept} + {totalDecisions.accept} - » Rejected: + » Rejected: - {totalDecisions.reject} + {totalDecisions.reject} - » Modified: + » Modified: - {totalDecisions.modify} + {totalDecisions.modify} @@ -195,10 +207,13 @@ export const ToolStatsDisplay: React.FC = () => { - Overall Agreement Rate: + Overall Agreement Rate: - 0 ? agreementColor : undefined}> + 0 ? agreementColor : theme.text.primary} + > {totalReviewed > 0 ? `${agreementRate.toFixed(1)}%` : '--'} 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) => ( - {message} + {message} ); 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[` > 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[` > 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[` > 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[` > 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[` > 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[` > 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 = ({ {compression.isPending ? ( ) : ( - + )} {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 = ({ theme, }) => { if (!diffContent || typeof diffContent !== 'string') { - return No diff content.; + return ( + No diff content. + ); } const parsedLines = parseDiffWithLineNumbers(diffContent); if (parsedLines.length === 0) { return ( - + No changes detected. ); @@ -158,6 +162,7 @@ export const DiffRenderer: React.FC = ({ 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 ( - + No changes detected. ); @@ -248,7 +258,10 @@ const renderDiffContent = ( ) { acc.push( - + {'═'.repeat(terminalWidth)} , @@ -289,12 +302,12 @@ const renderDiffContent = ( acc.push( @@ -302,30 +315,32 @@ const renderDiffContent = ( {line.type === 'context' ? ( <> - {prefixSymbol} + + {prefixSymbol}{' '} + - {colorizeLine(displayContent, language)} + {colorizeLine(displayContent, language, theme)} ) : ( {prefixSymbol} {' '} - {colorizeLine(displayContent, language)} + {colorizeLine(displayContent, language, theme)} )} , 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 = ({ text }) => { return ( - {prefix} + {prefix} - + {text} diff --git a/packages/cli/src/ui/components/messages/GeminiMessage.tsx b/packages/cli/src/ui/components/messages/GeminiMessage.tsx index 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 = ({ return ( - {prefix} + {prefix} = ({ text }) => { return ( - {prefix} + {prefix} - + {text} 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< - Modify in progress: - + Modify in progress: + Save and close external editor to continue @@ -192,7 +192,7 @@ export const ToolConfirmationMessage: React.FC< maxWidth={Math.max(childWidth - 4, 1)} > - {executionProps.command} + {executionProps.command} @@ -222,12 +222,15 @@ export const ToolConfirmationMessage: React.FC< bodyContent = ( - {infoProps.prompt} + {infoProps.prompt} {displayUrls && infoProps.urls && infoProps.urls.length > 0 && ( - URLs to fetch: + URLs to fetch: {infoProps.urls.map((url) => ( - - {url} + + {' '} + - {url} + ))} )} @@ -239,8 +242,8 @@ export const ToolConfirmationMessage: React.FC< bodyContent = ( - MCP Server: {mcpProps.serverName} - Tool: {mcpProps.toolName} + MCP Server: {mcpProps.serverName} + Tool: {mcpProps.toolName} ); @@ -275,7 +278,9 @@ export const ToolConfirmationMessage: React.FC< {/* Confirmation Question */} - {question} + + {question} + {/* Select Input for Options */} diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx index 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 = ({ ); 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 = ({ {typeof resultDisplay === 'string' && !renderOutputAsMarkdown && ( - {resultDisplay} + + {resultDisplay} + )} @@ -118,7 +120,7 @@ const ToolStatusIndicator: React.FC = ({ }) => ( {status === ToolCallStatus.Pending && ( - o + o )} {status === ToolCallStatus.Executing && ( = ({ /> )} {status === ToolCallStatus.Success && ( - + )} {status === ToolCallStatus.Confirming && ( - ? + ? )} {status === ToolCallStatus.Canceled && ( - + - )} {status === ToolCallStatus.Error && ( - + x )} @@ -160,11 +162,11 @@ const ToolInfo: React.FC = ({ const nameColor = React.useMemo(() => { switch (emphasis) { case 'high': - return Colors.Foreground; + return theme.text.primary; case 'medium': - return Colors.Foreground; + return theme.text.primary; case 'low': - return Colors.Gray; + return theme.text.secondary; default: { const exhaustiveCheck: never = emphasis; return exhaustiveCheck; @@ -176,18 +178,19 @@ const ToolInfo: React.FC = ({ {name} {' '} - {description} + {description} ); }; const TrailingIndicator: React.FC = () => ( - + {' '} ← diff --git a/packages/cli/src/ui/components/messages/UserMessage.tsx b/packages/cli/src/ui/components/messages/UserMessage.tsx index 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 = ({ 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 ( = ({ text }) => { return ( - $ - {commandToDisplay} + $ + {commandToDisplay} ); }; diff --git a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx index 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 = ({ {line.length > 0 ? ( line.map((segment, segIndex) => ( + // Avoid adding color styles to this element, breaks code colorization {segment.text} @@ -186,14 +187,14 @@ export const MaxSizedBox: React.FC = ({ return ( {totalHiddenLines > 0 && overflowDirection === 'top' && ( - + ... first {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '} hidden ... )} {visibleLines} {totalHiddenLines > 0 && overflowDirection === 'bottom' && ( - + ... last {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '} hidden ... diff --git a/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx b/packages/cli/src/ui/components/shared/RadioButtonSelect.tsx index 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({ return ( {showScrollArrows && ( - 0 ? Colors.Foreground : Colors.Gray}> + 0 ? theme.text.primary : theme.text.secondary} + > ▲ )} @@ -172,18 +174,18 @@ export function RadioButtonSelect({ const itemIndex = scrollOffset + index; const isSelected = activeIndex === itemIndex; - let textColor = Colors.Foreground; - let numberColor = Colors.Foreground; + let textColor = theme.text.primary; + let numberColor = theme.text.primary; if (isSelected) { - textColor = Colors.AccentGreen; - numberColor = Colors.AccentGreen; + textColor = theme.status.success; + numberColor = theme.status.success; } else if (item.disabled) { - textColor = Colors.Gray; - numberColor = Colors.Gray; + textColor = theme.text.secondary; + numberColor = theme.text.secondary; } if (!showNumbers) { - numberColor = Colors.Gray; + numberColor = theme.text.secondary; } const numberColumnWidth = String(items.length).length; @@ -194,7 +196,9 @@ export function RadioButtonSelect({ return ( - + {isSelected ? '●' : ' '} @@ -208,7 +212,9 @@ export function RadioButtonSelect({ {item.themeNameDisplay && item.themeTypeDisplay ? ( {item.themeNameDisplay}{' '} - {item.themeTypeDisplay} + + {item.themeTypeDisplay} + ) : ( @@ -222,8 +228,8 @@ export function RadioButtonSelect({ ▼ 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 Loading...; + return Loading...; } if (privacyState.error) { return ( - + Error loading Opt-in settings: {privacyState.error} - Press Esc to exit. + Press Esc to exit. ); } @@ -59,17 +59,17 @@ export const CloudFreePrivacyNotice = ({ return ( - + Gemini Code Assist for Individuals Privacy Notice - + This notice and our Privacy Policy - [1] describe how Gemini Code - Assist handles your data. Please read them carefully. + [1] describe how Gemini Code Assist + handles your data. Please read them carefully. - + When you use Gemini Code Assist for individuals with Gemini CLI, Google collects your prompts, related code, generated output, code edits, related feature usage information, and your feedback to provide, @@ -77,7 +77,7 @@ export const CloudFreePrivacyNotice = ({ technologies. - + To help with quality and improve our products (such as generative machine-learning models), human reviewers may read, annotate, and process the data collected above. We take steps to protect your privacy @@ -90,7 +90,7 @@ export const CloudFreePrivacyNotice = ({ - + Allow Google to use this data to develop and improve our products? - - [1]{' '} + + [1]{' '} https://policies.google.com/privacy - Press Enter to choose an option and exit. + + Press Enter to choose an option and exit. + ); }; diff --git a/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.tsx b/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.tsx index 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 ( - + Vertex AI Notice - - Service Specific Terms[1] are + + Service Specific Terms[1] are incorporated into the agreement under which Google has agreed to provide - Google Cloud Platform[2] to + Google Cloud Platform[2] to Customer (the “Agreement”). If the Agreement authorizes the resale or supply of Google Cloud Platform under a Google Cloud partner or reseller program, then except for in the section entitled “Partner-Specific @@ -44,16 +44,16 @@ export const CloudPaidPrivacyNotice = ({ them in the Agreement. - - [1]{' '} + + [1]{' '} https://cloud.google.com/terms/service-terms - - [2]{' '} + + [2]{' '} https://cloud.google.com/terms/services - Press Esc to exit. + Press Esc to exit. ); }; diff --git a/packages/cli/src/ui/privacy/GeminiPrivacyNotice.tsx b/packages/cli/src/ui/privacy/GeminiPrivacyNotice.tsx index 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 ( - + Gemini API Key Notice - - By using the Gemini API[1], - Google AI Studio - [2], and the other Google + + By using the Gemini API[1], Google + AI Studio + [2], and the other Google developer services that reference these terms (collectively, the "APIs" or "Services"), you are agreeing to Google APIs Terms of Service (the "API Terms") - [3], and the Gemini API + [3], and the Gemini API Additional Terms of Service (the "Additional Terms") - [4]. + [4]. - - [1]{' '} + + [1]{' '} https://ai.google.dev/docs/gemini_api_overview - - [2] https://aistudio.google.com/ + + [2] https://aistudio.google.com/ - - [3]{' '} + + [3]{' '} https://developers.google.com/terms - - [4]{' '} + + [4]{' '} https://ai.google.dev/gemini-api/terms - Press Esc to exit. + Press Esc to exit. ); }; diff --git a/packages/cli/src/ui/themes/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 ( {showLineNumbers && ( - + {`${String(index + 1 + hiddenLinesCount).padStart( padWidth, ' ', @@ -208,7 +209,7 @@ export function colorizeCode( {`${String(index + 1).padStart(padWidth, ' ')} `} )} - {line} + {line} ))} 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 = ({ text }) => { while ((match = inlineRegex.exec(text)) !== null) { if (match.index > lastIndex) { nodes.push( - + {text.slice(lastIndex, match.index)} , ); @@ -48,7 +48,7 @@ const RenderInlineInternal: React.FC = ({ text }) => { fullMatch.length > BOLD_MARKER_LENGTH * 2 ) { renderedNode = ( - + {fullMatch.slice(BOLD_MARKER_LENGTH, -BOLD_MARKER_LENGTH)} ); @@ -60,13 +60,13 @@ const RenderInlineInternal: React.FC = ({ 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 = ( - + {fullMatch.slice(ITALIC_MARKER_LENGTH, -ITALIC_MARKER_LENGTH)} ); @@ -76,7 +76,7 @@ const RenderInlineInternal: React.FC = ({ text }) => { fullMatch.length > STRIKETHROUGH_MARKER_LENGTH * 2 ) { renderedNode = ( - + {fullMatch.slice( STRIKETHROUGH_MARKER_LENGTH, -STRIKETHROUGH_MARKER_LENGTH, @@ -91,7 +91,7 @@ const RenderInlineInternal: React.FC = ({ text }) => { const codeMatch = fullMatch.match(/^(`+)(.+?)\1$/s); if (codeMatch && codeMatch[2]) { renderedNode = ( - + {codeMatch[2]} ); @@ -106,9 +106,9 @@ const RenderInlineInternal: React.FC = ({ text }) => { const linkText = linkMatch[1]; const url = linkMatch[2]; renderedNode = ( - + {linkText} - ({url}) + ({url}) ); } @@ -116,10 +116,10 @@ const RenderInlineInternal: React.FC = ({ text }) => { fullMatch.startsWith('') && fullMatch.endsWith('') && 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 = ( - + {fullMatch.slice( UNDERLINE_TAG_START_LENGTH, -UNDERLINE_TAG_END_LENGTH, @@ -132,12 +132,22 @@ const RenderInlineInternal: React.FC = ({ text }) => { renderedNode = null; } - nodes.push(renderedNode ?? {fullMatch}); + nodes.push( + renderedNode ?? ( + + {fullMatch} + + ), + ); lastIndex = inlineRegex.lastIndex; } if (lastIndex < text.length) { - nodes.push({text.slice(lastIndex)}); + nodes.push( + + {text.slice(lastIndex)} + , + ); } 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 = ({ // Not a table, treat as regular text addContentBlock( - + , @@ -154,7 +154,7 @@ const MarkdownDisplayInternal: React.FC = ({ if (line.trim().length > 0) { addContentBlock( - + , @@ -173,35 +173,35 @@ const MarkdownDisplayInternal: React.FC = ({ switch (level) { case 1: headerNode = ( - + ); break; case 2: headerNode = ( - + ); break; case 3: headerNode = ( - + ); break; case 4: headerNode = ( - + ); break; default: headerNode = ( - + ); @@ -245,7 +245,7 @@ const MarkdownDisplayInternal: React.FC = ({ } else { addContentBlock( - + , @@ -314,7 +314,9 @@ const RenderCodeBlockInternal: React.FC = ({ // Not enough space to even show the message meaningfully return ( - ... code is being written ... + + ... code is being written ... + ); } @@ -330,7 +332,7 @@ const RenderCodeBlockInternal: React.FC = ({ return ( {colorizedTruncatedCode} - ... generating more ... + ... generating more ... ); } @@ -383,10 +385,10 @@ const RenderListItemInternal: React.FC = ({ flexDirection="row" > - {prefix} + {prefix} - + 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 = ({ const paddingNeeded = Math.max(0, contentWidth - actualDisplayWidth); return ( - + {isHeader ? ( - + ) : ( @@ -112,7 +112,7 @@ export const TableRenderer: React.FC = ({ const borderParts = adjustedWidths.map((w) => char.horizontal.repeat(w)); const border = char.left + borderParts.join(char.middle) + char.right; - return {border}; + return {border}; }; // Helper function to render a table row @@ -123,7 +123,7 @@ export const TableRenderer: React.FC = ({ }); return ( - + │{' '} {renderedCells.map((cell, index) => ( diff --git a/packages/cli/src/ui/utils/displayUtils.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; };