feat(ui): Semantic tokens refactor (#8087)

This commit is contained in:
Miguel Solorio
2025-09-10 10:57:07 -07:00
committed by GitHub
parent a3a0e981ee
commit b9b6fe1f73
57 changed files with 509 additions and 424 deletions
+4 -3
View File
@@ -10,6 +10,7 @@ import { Box, Text } from 'ink';
import type { RadioSelectItem } from './components/shared/RadioButtonSelect.js'; import type { RadioSelectItem } from './components/shared/RadioButtonSelect.js';
import { RadioButtonSelect } from './components/shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './components/shared/RadioButtonSelect.js';
import { useKeypress } from './hooks/useKeypress.js'; import { useKeypress } from './hooks/useKeypress.js';
import { theme } from './semantic-colors.js';
export type IdeIntegrationNudgeResult = { export type IdeIntegrationNudgeResult = {
userSelection: 'yes' | 'no' | 'dismiss'; userSelection: 'yes' | 'no' | 'dismiss';
@@ -79,17 +80,17 @@ export function IdeIntegrationNudge({
<Box <Box
flexDirection="column" flexDirection="column"
borderStyle="round" borderStyle="round"
borderColor="yellow" borderColor={theme.status.warning}
padding={1} padding={1}
width="100%" width="100%"
marginLeft={1} marginLeft={1}
> >
<Box marginBottom={1} flexDirection="column"> <Box marginBottom={1} flexDirection="column">
<Text> <Text>
<Text color="yellow">{'> '}</Text> <Text color={theme.status.warning}>{'> '}</Text>
{`Do you want to connect ${ideName ?? 'your editor'} to Gemini CLI?`} {`Do you want to connect ${ideName ?? 'your editor'} to Gemini CLI?`}
</Text> </Text>
<Text dimColor>{installText}</Text> <Text color={theme.text.secondary}>{installText}</Text>
</Box> </Box>
<RadioButtonSelect items={OPTIONS} onSelect={onComplete} /> <RadioButtonSelect items={OPTIONS} onSelect={onComplete} />
</Box> </Box>
+11 -7
View File
@@ -7,7 +7,7 @@
import type React from 'react'; import type React from 'react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js'; import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
import type { LoadedSettings } from '../../config/settings.js'; import type { LoadedSettings } from '../../config/settings.js';
import { SettingScope } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js';
@@ -149,14 +149,16 @@ Logging in with Google... Please restart Gemini CLI to continue.
return ( return (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
flexDirection="column" flexDirection="column"
padding={1} padding={1}
width="100%" width="100%"
> >
<Text bold>Get started</Text> <Text bold>Get started</Text>
<Box marginTop={1}> <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>
<Box marginTop={1}> <Box marginTop={1}>
<RadioButtonSelect <RadioButtonSelect
@@ -167,17 +169,19 @@ Logging in with Google... Please restart Gemini CLI to continue.
</Box> </Box>
{authError && ( {authError && (
<Box marginTop={1}> <Box marginTop={1}>
<Text color={Colors.AccentRed}>{authError}</Text> <Text color={theme.status.error}>{authError}</Text>
</Box> </Box>
)} )}
<Box marginTop={1}> <Box marginTop={1}>
<Text color={Colors.Gray}>(Use Enter to select)</Text> <Text color={theme.text.secondary}>(Use Enter to select)</Text>
</Box> </Box>
<Box marginTop={1}> <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>
<Box marginTop={1}> <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' 'https://github.com/google-gemini/gemini-cli/blob/main/docs/tos-privacy.md'
} }
+3 -3
View File
@@ -8,7 +8,7 @@ import type React from 'react';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import Spinner from 'ink-spinner'; import Spinner from 'ink-spinner';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { useKeypress } from '../hooks/useKeypress.js'; import { useKeypress } from '../hooks/useKeypress.js';
interface AuthInProgressProps { interface AuthInProgressProps {
@@ -41,13 +41,13 @@ export function AuthInProgress({
return ( return (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
flexDirection="column" flexDirection="column"
padding={1} padding={1}
width="100%" width="100%"
> >
{timedOut ? ( {timedOut ? (
<Text color={Colors.AccentRed}> <Text color={theme.status.error}>
Authentication timed out. Please try again. Authentication timed out. Please try again.
</Text> </Text>
) : ( ) : (
+2 -2
View File
@@ -7,7 +7,7 @@
import * as fsPromises from 'node:fs/promises'; import * as fsPromises from 'node:fs/promises';
import React from 'react'; import React from 'react';
import { Text } from 'ink'; import { Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import type { import type {
CommandContext, CommandContext,
SlashCommand, SlashCommand,
@@ -126,7 +126,7 @@ const saveCommand: SlashCommand = {
Text, Text,
null, null,
'A checkpoint with the tag ', '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?', ' already exists. Do you want to overwrite it?',
), ),
originalInvocation: { originalInvocation: {
+19 -19
View File
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; 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'; import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
interface AboutBoxProps { interface AboutBoxProps {
@@ -30,77 +30,77 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
}) => ( }) => (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
flexDirection="column" flexDirection="column"
padding={1} padding={1}
marginY={1} marginY={1}
width="100%" width="100%"
> >
<Box marginBottom={1}> <Box marginBottom={1}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
About Gemini CLI About Gemini CLI
</Text> </Text>
</Box> </Box>
<Box flexDirection="row"> <Box flexDirection="row">
<Box width="35%"> <Box width="35%">
<Text bold color={Colors.LightBlue}> <Text bold color={theme.text.link}>
CLI Version CLI Version
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Text>{cliVersion}</Text> <Text color={theme.text.primary}>{cliVersion}</Text>
</Box> </Box>
</Box> </Box>
{GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO) && ( {GIT_COMMIT_INFO && !['N/A'].includes(GIT_COMMIT_INFO) && (
<Box flexDirection="row"> <Box flexDirection="row">
<Box width="35%"> <Box width="35%">
<Text bold color={Colors.LightBlue}> <Text bold color={theme.text.link}>
Git Commit Git Commit
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Text>{GIT_COMMIT_INFO}</Text> <Text color={theme.text.primary}>{GIT_COMMIT_INFO}</Text>
</Box> </Box>
</Box> </Box>
)} )}
<Box flexDirection="row"> <Box flexDirection="row">
<Box width="35%"> <Box width="35%">
<Text bold color={Colors.LightBlue}> <Text bold color={theme.text.link}>
Model Model
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Text>{modelVersion}</Text> <Text color={theme.text.primary}>{modelVersion}</Text>
</Box> </Box>
</Box> </Box>
<Box flexDirection="row"> <Box flexDirection="row">
<Box width="35%"> <Box width="35%">
<Text bold color={Colors.LightBlue}> <Text bold color={theme.text.link}>
Sandbox Sandbox
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Text>{sandboxEnv}</Text> <Text color={theme.text.primary}>{sandboxEnv}</Text>
</Box> </Box>
</Box> </Box>
<Box flexDirection="row"> <Box flexDirection="row">
<Box width="35%"> <Box width="35%">
<Text bold color={Colors.LightBlue}> <Text bold color={theme.text.link}>
OS OS
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Text>{osVersion}</Text> <Text color={theme.text.primary}>{osVersion}</Text>
</Box> </Box>
</Box> </Box>
<Box flexDirection="row"> <Box flexDirection="row">
<Box width="35%"> <Box width="35%">
<Text bold color={Colors.LightBlue}> <Text bold color={theme.text.link}>
Auth Method Auth Method
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Text> <Text color={theme.text.primary}>
{selectedAuthType.startsWith('oauth') ? 'OAuth' : selectedAuthType} {selectedAuthType.startsWith('oauth') ? 'OAuth' : selectedAuthType}
</Text> </Text>
</Box> </Box>
@@ -108,24 +108,24 @@ export const AboutBox: React.FC<AboutBoxProps> = ({
{gcpProject && ( {gcpProject && (
<Box flexDirection="row"> <Box flexDirection="row">
<Box width="35%"> <Box width="35%">
<Text bold color={Colors.LightBlue}> <Text bold color={theme.text.link}>
GCP Project GCP Project
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Text>{gcpProject}</Text> <Text color={theme.text.primary}>{gcpProject}</Text>
</Box> </Box>
</Box> </Box>
)} )}
{ideClient && ( {ideClient && (
<Box flexDirection="row"> <Box flexDirection="row">
<Box width="35%"> <Box width="35%">
<Text bold color={Colors.LightBlue}> <Text bold color={theme.text.link}>
IDE Client IDE Client
</Text> </Text>
</Box> </Box>
<Box> <Box>
<Text>{ideClient}</Text> <Text color={theme.text.primary}>{ideClient}</Text>
</Box> </Box>
</Box> </Box>
)} )}
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { ApprovalMode } from '@google/gemini-cli-core'; import { ApprovalMode } from '@google/gemini-cli-core';
interface AutoAcceptIndicatorProps { interface AutoAcceptIndicatorProps {
@@ -22,12 +22,12 @@ export const AutoAcceptIndicator: React.FC<AutoAcceptIndicatorProps> = ({
switch (approvalMode) { switch (approvalMode) {
case ApprovalMode.AUTO_EDIT: case ApprovalMode.AUTO_EDIT:
textColor = Colors.AccentGreen; textColor = theme.status.success;
textContent = 'accepting edits'; textContent = 'accepting edits';
subText = ' (shift + tab to toggle)'; subText = ' (shift + tab to toggle)';
break; break;
case ApprovalMode.YOLO: case ApprovalMode.YOLO:
textColor = Colors.AccentRed; textColor = theme.status.error;
textContent = 'YOLO mode'; textContent = 'YOLO mode';
subText = ' (ctrl + y to toggle)'; subText = ' (ctrl + y to toggle)';
break; break;
@@ -40,7 +40,7 @@ export const AutoAcceptIndicator: React.FC<AutoAcceptIndicatorProps> = ({
<Box> <Box>
<Text color={textColor}> <Text color={textColor}>
{textContent} {textContent}
{subText && <Text color={Colors.Gray}>{subText}</Text>} {subText && <Text color={theme.text.secondary}>{subText}</Text>}
</Text> </Text>
</Box> </Box>
); );
+9 -5
View File
@@ -14,7 +14,7 @@ import { InputPrompt } from './InputPrompt.js';
import { Footer, type FooterProps } from './Footer.js'; import { Footer, type FooterProps } from './Footer.js';
import { ShowMoreLines } from './ShowMoreLines.js'; import { ShowMoreLines } from './ShowMoreLines.js';
import { OverflowProvider } from '../contexts/OverflowContext.js'; import { OverflowProvider } from '../contexts/OverflowContext.js';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { isNarrowWidth } from '../utils/isNarrowWidth.js'; import { isNarrowWidth } from '../utils/isNarrowWidth.js';
import { useUIState } from '../contexts/UIStateContext.js'; import { useUIState } from '../contexts/UIStateContext.js';
import { useUIActions } from '../contexts/UIActionsContext.js'; import { useUIActions } from '../contexts/UIActionsContext.js';
@@ -112,14 +112,18 @@ export const Composer = () => {
> >
<Box> <Box>
{process.env['GEMINI_SYSTEM_MD'] && ( {process.env['GEMINI_SYSTEM_MD'] && (
<Text color={Colors.AccentRed}>|_| </Text> <Text color={theme.status.error}>|_| </Text>
)} )}
{uiState.ctrlCPressedOnce ? ( {uiState.ctrlCPressedOnce ? (
<Text color={Colors.AccentYellow}>Press Ctrl+C again to exit.</Text> <Text color={theme.status.warning}>
Press Ctrl+C again to exit.
</Text>
) : uiState.ctrlDPressedOnce ? ( ) : uiState.ctrlDPressedOnce ? (
<Text color={Colors.AccentYellow}>Press Ctrl+D again to exit.</Text> <Text color={theme.status.warning}>
Press Ctrl+D again to exit.
</Text>
) : uiState.showEscapePrompt ? ( ) : uiState.showEscapePrompt ? (
<Text color={Colors.Gray}>Press Esc again to clear.</Text> <Text color={theme.text.secondary}>Press Esc again to clear.</Text>
) : ( ) : (
!settings.merged.ui?.hideContextSummary && ( !settings.merged.ui?.hideContextSummary && (
<ContextSummaryDisplay <ContextSummaryDisplay
@@ -10,6 +10,7 @@ import { Box, Text } from 'ink';
import { useConfig } from '../contexts/ConfigContext.js'; import { useConfig } from '../contexts/ConfigContext.js';
import { type McpClient, MCPServerStatus } from '@google/gemini-cli-core'; import { type McpClient, MCPServerStatus } from '@google/gemini-cli-core';
import { GeminiSpinner } from './GeminiRespondingSpinner.js'; import { GeminiSpinner } from './GeminiRespondingSpinner.js';
import { theme } from '../semantic-colors.js';
export const ConfigInitDisplay = () => { export const ConfigInitDisplay = () => {
const config = useConfig(); const config = useConfig();
@@ -39,7 +40,7 @@ export const ConfigInitDisplay = () => {
return ( return (
<Box marginTop={1}> <Box marginTop={1}>
<Text> <Text>
<GeminiSpinner /> {message} <GeminiSpinner /> <Text color={theme.text.primary}>{message}</Text>
</Text> </Text>
</Box> </Box>
); );
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
interface ConsoleSummaryDisplayProps { interface ConsoleSummaryDisplayProps {
errorCount: number; errorCount: number;
@@ -25,9 +25,9 @@ export const ConsoleSummaryDisplay: React.FC<ConsoleSummaryDisplayProps> = ({
return ( return (
<Box> <Box>
{errorCount > 0 && ( {errorCount > 0 && (
<Text color={Colors.AccentRed}> <Text color={theme.status.error}>
{errorIcon} {errorCount} error{errorCount > 1 ? 's' : ''}{' '} {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> </Text>
)} )}
</Box> </Box>
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; 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 { type IdeContext, type MCPServerConfig } from '@google/gemini-cli-core';
import { useTerminalSize } from '../hooks/useTerminalSize.js'; import { useTerminalSize } from '../hooks/useTerminalSize.js';
import { isNarrowWidth } from '../utils/isNarrowWidth.js'; import { isNarrowWidth } from '../utils/isNarrowWidth.js';
@@ -99,9 +99,9 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
if (isNarrow) { if (isNarrow) {
return ( return (
<Box flexDirection="column"> <Box flexDirection="column">
<Text color={Colors.Gray}>Using:</Text> <Text color={theme.text.secondary}>Using:</Text>
{summaryParts.map((part, index) => ( {summaryParts.map((part, index) => (
<Text key={index} color={Colors.Gray}> <Text key={index} color={theme.text.secondary}>
{' '}- {part} {' '}- {part}
</Text> </Text>
))} ))}
@@ -111,7 +111,9 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
return ( return (
<Box> <Box>
<Text color={Colors.Gray}>Using: {summaryParts.join(' | ')}</Text> <Text color={theme.text.secondary}>
Using: {summaryParts.join(' | ')}
</Text>
</Box> </Box>
); );
}; };
@@ -5,7 +5,7 @@
*/ */
import { Text } from 'ink'; import { Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { tokenLimit } from '@google/gemini-cli-core'; import { tokenLimit } from '@google/gemini-cli-core';
export const ContextUsageDisplay = ({ export const ContextUsageDisplay = ({
@@ -18,7 +18,7 @@ export const ContextUsageDisplay = ({
const percentage = promptTokenCount / tokenLimit(model); const percentage = promptTokenCount / tokenLimit(model);
return ( return (
<Text color={Colors.Gray}> <Text color={theme.text.secondary}>
({((1 - percentage) * 100).toFixed(0)}% context left) ({((1 - percentage) * 100).toFixed(0)}% context left)
</Text> </Text>
); );
@@ -6,7 +6,7 @@
import { Text } from 'ink'; import { Text } from 'ink';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { useKeypress } from '../hooks/useKeypress.js'; import { useKeypress } from '../hooks/useKeypress.js';
export const DebugProfiler = () => { export const DebugProfiler = () => {
@@ -31,6 +31,6 @@ export const DebugProfiler = () => {
} }
return ( return (
<Text color={Colors.AccentYellow}>Renders: {numRenders.current} </Text> <Text color={theme.status.warning}>Renders: {numRenders.current} </Text>
); );
}; };
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import type { ConsoleMessageItem } from '../types.js'; import type { ConsoleMessageItem } from '../types.js';
import { MaxSizedBox } from './shared/MaxSizedBox.js'; import { MaxSizedBox } from './shared/MaxSizedBox.js';
@@ -31,31 +31,32 @@ export const DetailedMessagesDisplay: React.FC<
flexDirection="column" flexDirection="column"
marginTop={1} marginTop={1}
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
paddingX={1} paddingX={1}
width={width} width={width}
> >
<Box marginBottom={1}> <Box marginBottom={1}>
<Text bold color={Colors.Foreground}> <Text bold color={theme.text.primary}>
Debug Console <Text color={Colors.Gray}>(ctrl+o to close)</Text> Debug Console{' '}
<Text color={theme.text.secondary}>(ctrl+o to close)</Text>
</Text> </Text>
</Box> </Box>
<MaxSizedBox maxHeight={maxHeight} maxWidth={width - borderAndPadding}> <MaxSizedBox maxHeight={maxHeight} maxWidth={width - borderAndPadding}>
{messages.map((msg, index) => { {messages.map((msg, index) => {
let textColor = Colors.Foreground; let textColor = theme.text.primary;
let icon = '\u2139'; // Information source () let icon = '\u2139'; // Information source ()
switch (msg.type) { switch (msg.type) {
case 'warn': case 'warn':
textColor = Colors.AccentYellow; textColor = theme.status.warning;
icon = '\u26A0'; // Warning sign (⚠) icon = '\u26A0'; // Warning sign (⚠)
break; break;
case 'error': case 'error':
textColor = Colors.AccentRed; textColor = theme.status.error;
icon = '\u2716'; // Heavy multiplication x (✖) icon = '\u2716'; // Heavy multiplication x (✖)
break; break;
case 'debug': case 'debug':
textColor = Colors.Gray; // Or Colors.Gray textColor = theme.text.secondary; // Or theme.text.secondary
icon = '\u{1F50D}'; // Left-pointing magnifying glass (🔍) icon = '\u{1F50D}'; // Left-pointing magnifying glass (🔍)
break; break;
case 'log': case 'log':
@@ -70,7 +71,7 @@ export const DetailedMessagesDisplay: React.FC<
<Text color={textColor} wrap="wrap"> <Text color={textColor} wrap="wrap">
{msg.content} {msg.content}
{msg.count && msg.count > 1 && ( {msg.count && msg.count > 1 && (
<Text color={Colors.Gray}> (x{msg.count})</Text> <Text color={theme.text.secondary}> (x{msg.count})</Text>
)} )}
</Text> </Text>
</Box> </Box>
@@ -17,7 +17,7 @@ import { EditorSettingsDialog } from './EditorSettingsDialog.js';
import { PrivacyNotice } from '../privacy/PrivacyNotice.js'; import { PrivacyNotice } from '../privacy/PrivacyNotice.js';
import { WorkspaceMigrationDialog } from './WorkspaceMigrationDialog.js'; import { WorkspaceMigrationDialog } from './WorkspaceMigrationDialog.js';
import { ProQuotaDialog } from './ProQuotaDialog.js'; import { ProQuotaDialog } from './ProQuotaDialog.js';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { useUIState } from '../contexts/UIStateContext.js'; import { useUIState } from '../contexts/UIStateContext.js';
import { useUIActions } from '../contexts/UIActionsContext.js'; import { useUIActions } from '../contexts/UIActionsContext.js';
import { useConfig } from '../contexts/ConfigContext.js'; import { useConfig } from '../contexts/ConfigContext.js';
@@ -36,8 +36,8 @@ export const DialogManager = () => {
if (uiState.showIdeRestartPrompt) { if (uiState.showIdeRestartPrompt) {
return ( return (
<Box borderStyle="round" borderColor={Colors.AccentYellow} paddingX={1}> <Box borderStyle="round" borderColor={theme.status.warning} paddingX={1}>
<Text color={Colors.AccentYellow}> <Text color={theme.status.warning}>
Workspace trust has changed. Press &apos;r&apos; to restart Gemini to Workspace trust has changed. Press &apos;r&apos; to restart Gemini to
apply the changes. apply the changes.
</Text> </Text>
@@ -106,7 +106,7 @@ export const DialogManager = () => {
<Box flexDirection="column"> <Box flexDirection="column">
{uiState.themeError && ( {uiState.themeError && (
<Box marginBottom={1}> <Box marginBottom={1}>
<Text color={Colors.AccentRed}>{uiState.themeError}</Text> <Text color={theme.status.error}>{uiState.themeError}</Text>
</Box> </Box>
)} )}
<ThemeDialog <ThemeDialog
@@ -159,7 +159,7 @@ export const DialogManager = () => {
<Box flexDirection="column"> <Box flexDirection="column">
{uiState.editorError && ( {uiState.editorError && (
<Box marginBottom={1}> <Box marginBottom={1}>
<Text color={Colors.AccentRed}>{uiState.editorError}</Text> <Text color={theme.status.error}>{uiState.editorError}</Text>
</Box> </Box>
)} )}
<EditorSettingsDialog <EditorSettingsDialog
@@ -7,7 +7,7 @@
import type React from 'react'; import type React from 'react';
import { useState } from 'react'; import { useState } from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { import {
EDITOR_DISPLAY_NAMES, EDITOR_DISPLAY_NAMES,
editorSettingsManager, editorSettingsManager,
@@ -112,7 +112,7 @@ export function EditorSettingsDialog({
return ( return (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
flexDirection="row" flexDirection="row"
padding={1} padding={1}
width="100%" width="100%"
@@ -120,7 +120,7 @@ export function EditorSettingsDialog({
<Box flexDirection="column" width="45%" paddingRight={2}> <Box flexDirection="column" width="45%" paddingRight={2}>
<Text bold={focusedSection === 'editor'}> <Text bold={focusedSection === 'editor'}>
{focusedSection === 'editor' ? '> ' : ' '}Select Editor{' '} {focusedSection === 'editor' ? '> ' : ' '}Select Editor{' '}
<Text color={Colors.Gray}>{otherScopeModifiedMessage}</Text> <Text color={theme.text.secondary}>{otherScopeModifiedMessage}</Text>
</Text> </Text>
<RadioButtonSelect <RadioButtonSelect
items={editorItems.map((item) => ({ items={editorItems.map((item) => ({
@@ -147,7 +147,7 @@ export function EditorSettingsDialog({
</Box> </Box>
<Box marginTop={1}> <Box marginTop={1}>
<Text color={Colors.Gray}> <Text color={theme.text.secondary}>
(Use Enter to select, Tab to change focus) (Use Enter to select, Tab to change focus)
</Text> </Text>
</Box> </Box>
@@ -156,17 +156,17 @@ export function EditorSettingsDialog({
<Box flexDirection="column" width="55%" paddingLeft={2}> <Box flexDirection="column" width="55%" paddingLeft={2}>
<Text bold>Editor Preference</Text> <Text bold>Editor Preference</Text>
<Box flexDirection="column" gap={1} marginTop={1}> <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 These editors are currently supported. Please note that some editors
cannot be used in sandbox mode. cannot be used in sandbox mode.
</Text> </Text>
<Text color={Colors.Gray}> <Text color={theme.text.secondary}>
Your preferred editor is:{' '} Your preferred editor is:{' '}
<Text <Text
color={ color={
mergedEditorName === 'None' mergedEditorName === 'None'
? Colors.AccentRed ? theme.status.error
: Colors.AccentCyan : theme.text.link
} }
bold bold
> >
@@ -6,7 +6,7 @@
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import type React from 'react'; import type React from 'react';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import type { RadioSelectItem } from './shared/RadioButtonSelect.js'; import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { useKeypress } from '../hooks/useKeypress.js'; import { useKeypress } from '../hooks/useKeypress.js';
@@ -69,14 +69,16 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
<Box <Box
flexDirection="column" flexDirection="column"
borderStyle="round" borderStyle="round"
borderColor={Colors.AccentYellow} borderColor={theme.status.warning}
padding={1} padding={1}
width="100%" width="100%"
marginLeft={1} marginLeft={1}
> >
<Box flexDirection="column" marginBottom={1}> <Box flexDirection="column" marginBottom={1}>
<Text bold>Do you trust this folder?</Text> <Text bold color={theme.text.primary}>
<Text> Do you trust this folder?
</Text>
<Text color={theme.text.primary}>
Trusting a folder allows Gemini to execute commands it suggests. Trusting a folder allows Gemini to execute commands it suggests.
This is a security feature to prevent accidental execution in This is a security feature to prevent accidental execution in
untrusted directories. untrusted directories.
@@ -91,7 +93,7 @@ export const FolderTrustDialog: React.FC<FolderTrustDialogProps> = ({
</Box> </Box>
{isRestarting && ( {isRestarting && (
<Box marginLeft={1} marginTop={1}> <Box marginLeft={1} marginTop={1}>
<Text color={Colors.AccentYellow}> <Text color={theme.status.warning}>
To see changes, Gemini CLI must be restarted. Press r to exit and To see changes, Gemini CLI must be restarted. Press r to exit and
apply changes now. apply changes now.
</Text> </Text>
@@ -14,6 +14,7 @@ import {
SCREEN_READER_LOADING, SCREEN_READER_LOADING,
SCREEN_READER_RESPONDING, SCREEN_READER_RESPONDING,
} from '../textConstants.js'; } from '../textConstants.js';
import { theme } from '../semantic-colors.js';
interface GeminiRespondingSpinnerProps { interface GeminiRespondingSpinnerProps {
/** /**
@@ -40,7 +41,7 @@ export const GeminiRespondingSpinner: React.FC<
return isScreenReaderEnabled ? ( return isScreenReaderEnabled ? (
<Text>{SCREEN_READER_LOADING}</Text> <Text>{SCREEN_READER_LOADING}</Text>
) : ( ) : (
<Text>{nonRespondingDisplay}</Text> <Text color={theme.text.primary}>{nonRespondingDisplay}</Text>
); );
} }
return null; return null;
@@ -59,6 +60,8 @@ export const GeminiSpinner: React.FC<GeminiSpinnerProps> = ({
return isScreenReaderEnabled ? ( return isScreenReaderEnabled ? (
<Text>{altText}</Text> <Text>{altText}</Text>
) : ( ) : (
<Spinner type={spinnerType} /> <Text color={theme.text.primary}>
<Spinner type={spinnerType} />
</Text>
); );
}; };
+5 -5
View File
@@ -7,7 +7,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import Gradient from 'ink-gradient'; import Gradient from 'ink-gradient';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { shortAsciiLogo, longAsciiLogo, tinyAsciiLogo } from './AsciiArt.js'; import { shortAsciiLogo, longAsciiLogo, tinyAsciiLogo } from './AsciiArt.js';
import { getAsciiArtWidth } from '../utils/textUtils.js'; import { getAsciiArtWidth } from '../utils/textUtils.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js'; import { useTerminalSize } from '../hooks/useTerminalSize.js';
@@ -47,8 +47,8 @@ export const Header: React.FC<HeaderProps> = ({
flexShrink={0} flexShrink={0}
flexDirection="column" flexDirection="column"
> >
{Colors.GradientColors ? ( {theme.ui.gradient ? (
<Gradient colors={Colors.GradientColors}> <Gradient colors={theme.ui.gradient}>
<Text>{displayTitle}</Text> <Text>{displayTitle}</Text>
</Gradient> </Gradient>
) : ( ) : (
@@ -56,8 +56,8 @@ export const Header: React.FC<HeaderProps> = ({
)} )}
{nightly && ( {nightly && (
<Box width="100%" flexDirection="row" justifyContent="flex-end"> <Box width="100%" flexDirection="row" justifyContent="flex-end">
{Colors.GradientColors ? ( {theme.ui.gradient ? (
<Gradient colors={Colors.GradientColors}> <Gradient colors={theme.ui.gradient}>
<Text>v{version}</Text> <Text>v{version}</Text>
</Gradient> </Gradient>
) : ( ) : (
+46 -46
View File
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { type SlashCommand, CommandKind } from '../commands/types.js'; import { type SlashCommand, CommandKind } from '../commands/types.js';
interface Help { interface Help {
@@ -17,42 +17,42 @@ export const Help: React.FC<Help> = ({ commands }) => (
<Box <Box
flexDirection="column" flexDirection="column"
marginBottom={1} marginBottom={1}
borderColor={Colors.Gray} borderColor={theme.border.default}
borderStyle="round" borderStyle="round"
padding={1} padding={1}
> >
{/* Basics */} {/* Basics */}
<Text bold color={Colors.Foreground}> <Text bold color={theme.text.primary}>
Basics: Basics:
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Add context Add context
</Text> </Text>
: Use{' '} : Use{' '}
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
@ @
</Text>{' '} </Text>{' '}
to specify files for context (e.g.,{' '} to specify files for context (e.g.,{' '}
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
@src/myFile.ts @src/myFile.ts
</Text> </Text>
) to target specific files or folders. ) to target specific files or folders.
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Shell mode Shell mode
</Text> </Text>
: Execute shell commands via{' '} : Execute shell commands via{' '}
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
! !
</Text>{' '} </Text>{' '}
(e.g.,{' '} (e.g.,{' '}
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
!npm run start !npm run start
</Text> </Text>
) or use natural language (e.g.{' '} ) or use natural language (e.g.{' '}
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
start server start server
</Text> </Text>
). ).
@@ -61,20 +61,20 @@ export const Help: React.FC<Help> = ({ commands }) => (
<Box height={1} /> <Box height={1} />
{/* Commands */} {/* Commands */}
<Text bold color={Colors.Foreground}> <Text bold color={theme.text.primary}>
Commands: Commands:
</Text> </Text>
{commands {commands
.filter((command) => command.description && !command.hidden) .filter((command) => command.description && !command.hidden)
.map((command: SlashCommand) => ( .map((command: SlashCommand) => (
<Box key={command.name} flexDirection="column"> <Box key={command.name} flexDirection="column">
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
{' '} {' '}
/{command.name} /{command.name}
</Text> </Text>
{command.kind === CommandKind.MCP_PROMPT && ( {command.kind === CommandKind.MCP_PROMPT && (
<Text color={Colors.Gray}> [MCP]</Text> <Text color={theme.text.secondary}> [MCP]</Text>
)} )}
{command.description && ' - ' + command.description} {command.description && ' - ' + command.description}
</Text> </Text>
@@ -82,8 +82,8 @@ export const Help: React.FC<Help> = ({ commands }) => (
command.subCommands command.subCommands
.filter((subCommand) => !subCommand.hidden) .filter((subCommand) => !subCommand.hidden)
.map((subCommand) => ( .map((subCommand) => (
<Text key={subCommand.name} color={Colors.Foreground}> <Text key={subCommand.name} color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
{' '} {' '}
{subCommand.name} {subCommand.name}
</Text> </Text>
@@ -92,90 +92,90 @@ export const Help: React.FC<Help> = ({ commands }) => (
))} ))}
</Box> </Box>
))} ))}
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
{' '} {' '}
!{' '} !{' '}
</Text> </Text>
- shell command - shell command
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text color={Colors.Gray}>[MCP]</Text> - Model Context Protocol command <Text color={theme.text.secondary}>[MCP]</Text> - Model Context Protocol
(from external servers) command (from external servers)
</Text> </Text>
<Box height={1} /> <Box height={1} />
{/* Shortcuts */} {/* Shortcuts */}
<Text bold color={Colors.Foreground}> <Text bold color={theme.text.primary}>
Keyboard Shortcuts: Keyboard Shortcuts:
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Alt+Left/Right Alt+Left/Right
</Text>{' '} </Text>{' '}
- Jump through words in the input - Jump through words in the input
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Ctrl+C Ctrl+C
</Text>{' '} </Text>{' '}
- Quit application - Quit application
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
{process.platform === 'win32' ? 'Ctrl+Enter' : 'Ctrl+J'} {process.platform === 'win32' ? 'Ctrl+Enter' : 'Ctrl+J'}
</Text>{' '} </Text>{' '}
{process.platform === 'linux' {process.platform === 'linux'
? '- New line (Alt+Enter works for certain linux distros)' ? '- New line (Alt+Enter works for certain linux distros)'
: '- New line'} : '- New line'}
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Ctrl+L Ctrl+L
</Text>{' '} </Text>{' '}
- Clear the screen - Clear the screen
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
{process.platform === 'darwin' ? 'Ctrl+X / Meta+Enter' : 'Ctrl+X'} {process.platform === 'darwin' ? 'Ctrl+X / Meta+Enter' : 'Ctrl+X'}
</Text>{' '} </Text>{' '}
- Open input in external editor - Open input in external editor
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Ctrl+Y Ctrl+Y
</Text>{' '} </Text>{' '}
- Toggle YOLO mode - Toggle YOLO mode
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Enter Enter
</Text>{' '} </Text>{' '}
- Send message - Send message
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Esc Esc
</Text>{' '} </Text>{' '}
- Cancel operation / Clear input (double press) - Cancel operation / Clear input (double press)
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Shift+Tab Shift+Tab
</Text>{' '} </Text>{' '}
- Toggle auto-accepting edits - Toggle auto-accepting edits
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Up/Down Up/Down
</Text>{' '} </Text>{' '}
- Cycle through your prompt history - Cycle through your prompt history
</Text> </Text>
<Box height={1} /> <Box height={1} />
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
For a full list of shortcuts, see{' '} For a full list of shortcuts, see{' '}
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
docs/keyboard-shortcuts.md docs/keyboard-shortcuts.md
</Text> </Text>
</Text> </Text>
@@ -714,7 +714,11 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={ borderColor={
shellModeActive ? theme.status.warning : theme.border.focused shellModeActive
? theme.status.warning
: focus
? theme.border.focused
: theme.border.default
} }
paddingX={1} paddingX={1}
> >
@@ -797,7 +801,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
const color = const color =
token.type === 'command' || token.type === 'file' token.type === 'command' || token.type === 'file'
? theme.text.accent ? theme.text.accent
: undefined; : theme.text.primary;
renderedLine.push( renderedLine.push(
<Text key={`token-${tokenIdx}`} color={color}> <Text key={`token-${tokenIdx}`} color={color}>
@@ -7,7 +7,7 @@
import type { ThoughtSummary } from '@google/gemini-cli-core'; import type { ThoughtSummary } from '@google/gemini-cli-core';
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { useStreamingContext } from '../contexts/StreamingContext.js'; import { useStreamingContext } from '../contexts/StreamingContext.js';
import { StreamingState } from '../types.js'; import { StreamingState } from '../types.js';
import { GeminiRespondingSpinner } from './GeminiRespondingSpinner.js'; import { GeminiRespondingSpinner } from './GeminiRespondingSpinner.js';
@@ -61,11 +61,9 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
} }
/> />
</Box> </Box>
{primaryText && ( {primaryText && <Text color={theme.text.accent}>{primaryText}</Text>}
<Text color={Colors.AccentPurple}>{primaryText}</Text>
)}
{!isNarrow && cancelAndTimerContent && ( {!isNarrow && cancelAndTimerContent && (
<Text color={Colors.Gray}> {cancelAndTimerContent}</Text> <Text color={theme.text.secondary}> {cancelAndTimerContent}</Text>
)} )}
</Box> </Box>
{!isNarrow && <Box flexGrow={1}>{/* Spacer */}</Box>} {!isNarrow && <Box flexGrow={1}>{/* Spacer */}</Box>}
@@ -73,7 +71,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
</Box> </Box>
{isNarrow && cancelAndTimerContent && ( {isNarrow && cancelAndTimerContent && (
<Box> <Box>
<Text color={Colors.Gray}>{cancelAndTimerContent}</Text> <Text color={theme.text.secondary}>{cancelAndTimerContent}</Text>
</Box> </Box>
)} )}
{isNarrow && rightContent && <Box>{rightContent}</Box>} {isNarrow && rightContent && <Box>{rightContent}</Box>}
@@ -7,20 +7,24 @@
import type React from 'react'; import type React from 'react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import process from 'node:process'; import process from 'node:process';
import { formatMemoryUsage } from '../utils/formatters.js'; import { formatMemoryUsage } from '../utils/formatters.js';
export const MemoryUsageDisplay: React.FC = () => { export const MemoryUsageDisplay: React.FC = () => {
const [memoryUsage, setMemoryUsage] = useState<string>(''); const [memoryUsage, setMemoryUsage] = useState<string>('');
const [memoryUsageColor, setMemoryUsageColor] = useState<string>(Colors.Gray); const [memoryUsageColor, setMemoryUsageColor] = useState<string>(
theme.text.secondary,
);
useEffect(() => { useEffect(() => {
const updateMemory = () => { const updateMemory = () => {
const usage = process.memoryUsage().rss; const usage = process.memoryUsage().rss;
setMemoryUsage(formatMemoryUsage(usage)); setMemoryUsage(formatMemoryUsage(usage));
setMemoryUsageColor( 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); const intervalId = setInterval(updateMemory, 2000);
@@ -30,7 +34,7 @@ export const MemoryUsageDisplay: React.FC = () => {
return ( return (
<Box> <Box>
<Text color={Colors.Gray}> | </Text> <Text color={theme.text.secondary}> | </Text>
<Text color={memoryUsageColor}>{memoryUsage}</Text> <Text color={memoryUsageColor}>{memoryUsage}</Text>
</Box> </Box>
); );
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { formatDuration } from '../utils/formatters.js'; import { formatDuration } from '../utils/formatters.js';
import { import {
calculateAverageLatency, calculateAverageLatency,
@@ -34,13 +34,16 @@ const StatRow: React.FC<StatRowProps> = ({
}) => ( }) => (
<Box> <Box>
<Box width={METRIC_COL_WIDTH}> <Box width={METRIC_COL_WIDTH}>
<Text bold={isSection} color={isSection ? undefined : Colors.LightBlue}> <Text
bold={isSection}
color={isSection ? theme.text.primary : theme.text.link}
>
{isSubtle ? `${title}` : title} {isSubtle ? `${title}` : title}
</Text> </Text>
</Box> </Box>
{values.map((value, index) => ( {values.map((value, index) => (
<Box width={MODEL_COL_WIDTH} key={index}> <Box width={MODEL_COL_WIDTH} key={index}>
<Text>{value}</Text> <Text color={theme.text.primary}>{value}</Text>
</Box> </Box>
))} ))}
</Box> </Box>
@@ -57,11 +60,13 @@ export const ModelStatsDisplay: React.FC = () => {
return ( return (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
paddingY={1} paddingY={1}
paddingX={2} 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> </Box>
); );
} }
@@ -83,12 +88,12 @@ export const ModelStatsDisplay: React.FC = () => {
return ( return (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
flexDirection="column" flexDirection="column"
paddingY={1} paddingY={1}
paddingX={2} paddingX={2}
> >
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Model Stats For Nerds Model Stats For Nerds
</Text> </Text>
<Box height={1} /> <Box height={1} />
@@ -96,11 +101,15 @@ export const ModelStatsDisplay: React.FC = () => {
{/* Header */} {/* Header */}
<Box> <Box>
<Box width={METRIC_COL_WIDTH}> <Box width={METRIC_COL_WIDTH}>
<Text bold>Metric</Text> <Text bold color={theme.text.primary}>
Metric
</Text>
</Box> </Box>
{modelNames.map((name) => ( {modelNames.map((name) => (
<Box width={MODEL_COL_WIDTH} key={name}> <Box width={MODEL_COL_WIDTH} key={name}>
<Text bold>{name}</Text> <Text bold color={theme.text.primary}>
{name}
</Text>
</Box> </Box>
))} ))}
</Box> </Box>
@@ -112,6 +121,7 @@ export const ModelStatsDisplay: React.FC = () => {
borderTop={false} borderTop={false}
borderLeft={false} borderLeft={false}
borderRight={false} borderRight={false}
borderColor={theme.border.default}
/> />
{/* API Section */} {/* API Section */}
@@ -127,7 +137,7 @@ export const ModelStatsDisplay: React.FC = () => {
return ( return (
<Text <Text
color={ 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)}%) {m.api.totalErrors.toLocaleString()} ({errorRate.toFixed(1)}%)
@@ -150,7 +160,7 @@ export const ModelStatsDisplay: React.FC = () => {
<StatRow <StatRow
title="Total" title="Total"
values={getModelValues((m) => ( values={getModelValues((m) => (
<Text color={Colors.AccentYellow}> <Text color={theme.status.warning}>
{m.tokens.total.toLocaleString()} {m.tokens.total.toLocaleString()}
</Text> </Text>
))} ))}
@@ -167,7 +177,7 @@ export const ModelStatsDisplay: React.FC = () => {
values={getModelValues((m) => { values={getModelValues((m) => {
const cacheHitRate = calculateCacheHitRate(m); const cacheHitRate = calculateCacheHitRate(m);
return ( return (
<Text color={Colors.AccentGreen}> <Text color={theme.status.success}>
{m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%) {m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%)
</Text> </Text>
); );
@@ -7,7 +7,7 @@
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { useAppContext } from '../contexts/AppContext.js'; import { useAppContext } from '../contexts/AppContext.js';
import { useUIState } from '../contexts/UIStateContext.js'; import { useUIState } from '../contexts/UIStateContext.js';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { StreamingState } from '../types.js'; import { StreamingState } from '../types.js';
import { UpdateNotification } from './UpdateNotification.js'; import { UpdateNotification } from './UpdateNotification.js';
@@ -29,13 +29,13 @@ export const Notifications = () => {
{showStartupWarnings && ( {showStartupWarnings && (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.AccentYellow} borderColor={theme.status.warning}
paddingX={1} paddingX={1}
marginY={1} marginY={1}
flexDirection="column" flexDirection="column"
> >
{startupWarnings.map((warning, index) => ( {startupWarnings.map((warning, index) => (
<Text key={index} color={Colors.AccentYellow}> <Text key={index} color={theme.status.warning}>
{warning} {warning}
</Text> </Text>
))} ))}
@@ -44,14 +44,14 @@ export const Notifications = () => {
{showInitError && ( {showInitError && (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.AccentRed} borderColor={theme.status.error}
paddingX={1} paddingX={1}
marginBottom={1} marginBottom={1}
> >
<Text color={Colors.AccentRed}> <Text color={theme.status.error}>
Initialization Error: {initError} Initialization Error: {initError}
</Text> </Text>
<Text color={Colors.AccentRed}> <Text color={theme.status.error}>
{' '} {' '}
Please check API key and configuration. Please check API key and configuration.
</Text> </Text>
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Text } from 'ink'; import { Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
interface PrepareLabelProps { interface PrepareLabelProps {
label: string; label: string;
@@ -21,7 +21,7 @@ export const PrepareLabel: React.FC<PrepareLabelProps> = ({
matchedIndex, matchedIndex,
userInput, userInput,
textColor, textColor,
highlightColor = Colors.AccentYellow, highlightColor = theme.status.warning,
}) => { }) => {
if ( if (
matchedIndex === undefined || matchedIndex === undefined ||
@@ -7,7 +7,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
interface ProQuotaDialogProps { interface ProQuotaDialogProps {
failedModel: string; failedModel: string;
@@ -37,7 +37,7 @@ export function ProQuotaDialog({
return ( return (
<Box borderStyle="round" flexDirection="column" paddingX={1}> <Box borderStyle="round" flexDirection="column" paddingX={1}>
<Text bold color={Colors.AccentYellow}> <Text bold color={theme.status.warning}>
Pro quota limit reached for {failedModel}. Pro quota limit reached for {failedModel}.
</Text> </Text>
<Box marginTop={1}> <Box marginTop={1}>
@@ -6,7 +6,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import type { LoadedSettings, Settings } from '../../config/settings.js'; import type { LoadedSettings, Settings } from '../../config/settings.js';
import { SettingScope } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js';
import { import {
@@ -656,18 +656,18 @@ export function SettingsDialog({
return ( return (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
flexDirection="row" flexDirection="row"
padding={1} padding={1}
width="100%" width="100%"
height="100%" height="100%"
> >
<Box flexDirection="column" flexGrow={1}> <Box flexDirection="column" flexGrow={1}>
<Text bold color={Colors.AccentBlue}> <Text bold color={theme.text.link}>
Settings Settings
</Text> </Text>
<Box height={1} /> <Box height={1} />
{showScrollUp && <Text color={Colors.Gray}></Text>} {showScrollUp && <Text color={theme.text.secondary}></Text>}
{visibleItems.map((item, idx) => { {visibleItems.map((item, idx) => {
const isActive = const isActive =
focusSection === 'settings' && focusSection === 'settings' &&
@@ -748,17 +748,21 @@ export function SettingsDialog({
<React.Fragment key={item.value}> <React.Fragment key={item.value}>
<Box flexDirection="row" alignItems="center"> <Box flexDirection="row" alignItems="center">
<Box minWidth={2} flexShrink={0}> <Box minWidth={2} flexShrink={0}>
<Text color={isActive ? Colors.AccentGreen : Colors.Gray}> <Text
color={
isActive ? theme.status.success : theme.text.secondary
}
>
{isActive ? '●' : ''} {isActive ? '●' : ''}
</Text> </Text>
</Box> </Box>
<Box minWidth={50}> <Box minWidth={50}>
<Text <Text
color={isActive ? Colors.AccentGreen : Colors.Foreground} color={isActive ? theme.status.success : theme.text.primary}
> >
{item.label} {item.label}
{scopeMessage && ( {scopeMessage && (
<Text color={Colors.Gray}> {scopeMessage}</Text> <Text color={theme.text.secondary}> {scopeMessage}</Text>
)} )}
</Text> </Text>
</Box> </Box>
@@ -766,10 +770,10 @@ export function SettingsDialog({
<Text <Text
color={ color={
isActive isActive
? Colors.AccentGreen ? theme.status.success
: shouldBeGreyedOut : shouldBeGreyedOut
? Colors.Gray ? theme.text.secondary
: Colors.Foreground : theme.text.primary
} }
> >
{displayValue} {displayValue}
@@ -779,7 +783,7 @@ export function SettingsDialog({
</React.Fragment> </React.Fragment>
); );
})} })}
{showScrollDown && <Text color={Colors.Gray}></Text>} {showScrollDown && <Text color={theme.text.secondary}></Text>}
<Box height={1} /> <Box height={1} />
@@ -798,11 +802,11 @@ export function SettingsDialog({
</Box> </Box>
<Box height={1} /> <Box height={1} />
<Text color={Colors.Gray}> <Text color={theme.text.secondary}>
(Use Enter to select, Tab to change focus) (Use Enter to select, Tab to change focus)
</Text> </Text>
{showRestartPrompt && ( {showRestartPrompt && (
<Text color={Colors.AccentYellow}> <Text color={theme.status.warning}>
To see changes, Gemini CLI must be restarted. Press r to exit and To see changes, Gemini CLI must be restarted. Press r to exit and
apply changes now. apply changes now.
</Text> </Text>
@@ -7,7 +7,7 @@
import { ToolConfirmationOutcome } from '@google/gemini-cli-core'; import { ToolConfirmationOutcome } from '@google/gemini-cli-core';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import type React from 'react'; import type React from 'react';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { RenderInline } from '../utils/InlineMarkdownRenderer.js'; import { RenderInline } from '../utils/InlineMarkdownRenderer.js';
import type { RadioSelectItem } from './shared/RadioButtonSelect.js'; import type { RadioSelectItem } from './shared/RadioButtonSelect.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
@@ -68,23 +68,27 @@ export const ShellConfirmationDialog: React.FC<
<Box <Box
flexDirection="column" flexDirection="column"
borderStyle="round" borderStyle="round"
borderColor={Colors.AccentYellow} borderColor={theme.status.warning}
padding={1} padding={1}
width="100%" width="100%"
marginLeft={1} marginLeft={1}
> >
<Box flexDirection="column" marginBottom={1}> <Box flexDirection="column" marginBottom={1}>
<Text bold>Shell Command Execution</Text> <Text bold color={theme.text.primary}>
<Text>A custom command wants to run the following shell commands:</Text> Shell Command Execution
</Text>
<Text color={theme.text.primary}>
A custom command wants to run the following shell commands:
</Text>
<Box <Box
flexDirection="column" flexDirection="column"
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
paddingX={1} paddingX={1}
marginTop={1} marginTop={1}
> >
{commands.map((cmd) => ( {commands.map((cmd) => (
<Text key={cmd} color={Colors.AccentCyan}> <Text key={cmd} color={theme.text.link}>
<RenderInline text={cmd} /> <RenderInline text={cmd} />
</Text> </Text>
))} ))}
@@ -92,7 +96,7 @@ export const ShellConfirmationDialog: React.FC<
</Box> </Box>
<Box marginBottom={1}> <Box marginBottom={1}>
<Text>Do you want to proceed?</Text> <Text color={theme.text.primary}>Do you want to proceed?</Text>
</Box> </Box>
<RadioButtonSelect items={options} onSelect={handleSelect} isFocused /> <RadioButtonSelect items={options} onSelect={handleSelect} isFocused />
@@ -6,13 +6,13 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
export const ShellModeIndicator: React.FC = () => ( export const ShellModeIndicator: React.FC = () => (
<Box> <Box>
<Text color={Colors.AccentYellow}> <Text color={theme.status.warning}>
shell mode enabled shell mode enabled
<Text color={Colors.Gray}> (esc to disable)</Text> <Text color={theme.text.secondary}> (esc to disable)</Text>
</Text> </Text>
</Box> </Box>
); );
@@ -8,7 +8,7 @@ import { Box, Text } from 'ink';
import { useOverflowState } from '../contexts/OverflowContext.js'; import { useOverflowState } from '../contexts/OverflowContext.js';
import { useStreamingContext } from '../contexts/StreamingContext.js'; import { useStreamingContext } from '../contexts/StreamingContext.js';
import { StreamingState } from '../types.js'; import { StreamingState } from '../types.js';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
interface ShowMoreLinesProps { interface ShowMoreLinesProps {
constrainHeight: boolean; constrainHeight: boolean;
@@ -32,7 +32,7 @@ export const ShowMoreLines = ({ constrainHeight }: ShowMoreLinesProps) => {
return ( return (
<Box> <Box>
<Text color={Colors.Gray} wrap="truncate"> <Text color={theme.text.secondary} wrap="truncate">
Press ctrl-s to show more lines Press ctrl-s to show more lines
</Text> </Text>
</Box> </Box>
+31 -16
View File
@@ -47,7 +47,7 @@ const SubStatRow: React.FC<SubStatRowProps> = ({ title, children }) => (
<Box paddingLeft={2}> <Box paddingLeft={2}>
{/* Adjust width for the "» " prefix */} {/* Adjust width for the "» " prefix */}
<Box width={26}> <Box width={26}>
<Text>» {title}</Text> <Text color={theme.text.secondary}>» {title}</Text>
</Box> </Box>
{/* FIX: Apply the same flexGrow fix here */} {/* FIX: Apply the same flexGrow fix here */}
<Box flexGrow={1}>{children}</Box> <Box flexGrow={1}>{children}</Box>
@@ -62,7 +62,9 @@ interface SectionProps {
const Section: React.FC<SectionProps> = ({ title, children }) => ( const Section: React.FC<SectionProps> = ({ title, children }) => (
<Box flexDirection="column" width="100%" marginBottom={1}> <Box flexDirection="column" width="100%" marginBottom={1}>
<Text bold>{title}</Text> <Text bold color={theme.text.primary}>
{title}
</Text>
{children} {children}
</Box> </Box>
); );
@@ -82,16 +84,24 @@ const ModelUsageTable: React.FC<{
{/* Header */} {/* Header */}
<Box> <Box>
<Box width={nameWidth}> <Box width={nameWidth}>
<Text bold>Model Usage</Text> <Text bold color={theme.text.primary}>
Model Usage
</Text>
</Box> </Box>
<Box width={requestsWidth} justifyContent="flex-end"> <Box width={requestsWidth} justifyContent="flex-end">
<Text bold>Reqs</Text> <Text bold color={theme.text.primary}>
Reqs
</Text>
</Box> </Box>
<Box width={inputTokensWidth} justifyContent="flex-end"> <Box width={inputTokensWidth} justifyContent="flex-end">
<Text bold>Input Tokens</Text> <Text bold color={theme.text.primary}>
Input Tokens
</Text>
</Box> </Box>
<Box width={outputTokensWidth} justifyContent="flex-end"> <Box width={outputTokensWidth} justifyContent="flex-end">
<Text bold>Output Tokens</Text> <Text bold color={theme.text.primary}>
Output Tokens
</Text>
</Box> </Box>
</Box> </Box>
{/* Divider */} {/* Divider */}
@@ -101,6 +111,7 @@ const ModelUsageTable: React.FC<{
borderTop={false} borderTop={false}
borderLeft={false} borderLeft={false}
borderRight={false} borderRight={false}
borderColor={theme.border.default}
width={nameWidth + requestsWidth + inputTokensWidth + outputTokensWidth} width={nameWidth + requestsWidth + inputTokensWidth + outputTokensWidth}
></Box> ></Box>
@@ -108,10 +119,12 @@ const ModelUsageTable: React.FC<{
{Object.entries(models).map(([name, modelMetrics]) => ( {Object.entries(models).map(([name, modelMetrics]) => (
<Box key={name}> <Box key={name}>
<Box width={nameWidth}> <Box width={nameWidth}>
<Text>{name.replace('-001', '')}</Text> <Text color={theme.text.primary}>{name.replace('-001', '')}</Text>
</Box> </Box>
<Box width={requestsWidth} justifyContent="flex-end"> <Box width={requestsWidth} justifyContent="flex-end">
<Text>{modelMetrics.api.totalRequests}</Text> <Text color={theme.text.primary}>
{modelMetrics.api.totalRequests}
</Text>
</Box> </Box>
<Box width={inputTokensWidth} justifyContent="flex-end"> <Box width={inputTokensWidth} justifyContent="flex-end">
<Text color={theme.status.warning}> <Text color={theme.status.warning}>
@@ -127,7 +140,7 @@ const ModelUsageTable: React.FC<{
))} ))}
{cacheEfficiency > 0 && ( {cacheEfficiency > 0 && (
<Box flexDirection="column" marginTop={1}> <Box flexDirection="column" marginTop={1}>
<Text> <Text color={theme.text.primary}>
<Text color={theme.status.success}>Savings Highlight:</Text>{' '} <Text color={theme.status.success}>Savings Highlight:</Text>{' '}
{totalCachedTokens.toLocaleString()} ({cacheEfficiency.toFixed(1)} {totalCachedTokens.toLocaleString()} ({cacheEfficiency.toFixed(1)}
%) of input tokens were served from the cache, reducing costs. %) of input tokens were served from the cache, reducing costs.
@@ -202,10 +215,10 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
<Section title="Interaction Summary"> <Section title="Interaction Summary">
<StatRow title="Session ID:"> <StatRow title="Session ID:">
<Text>{stats.sessionId}</Text> <Text color={theme.text.primary}>{stats.sessionId}</Text>
</StatRow> </StatRow>
<StatRow title="Tool Calls:"> <StatRow title="Tool Calls:">
<Text> <Text color={theme.text.primary}>
{tools.totalCalls} ({' '} {tools.totalCalls} ({' '}
<Text color={theme.status.success}> {tools.totalSuccess}</Text>{' '} <Text color={theme.status.success}> {tools.totalSuccess}</Text>{' '}
<Text color={theme.status.error}>x {tools.totalFail}</Text> ) <Text color={theme.status.error}>x {tools.totalFail}</Text> )
@@ -227,7 +240,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
{files && {files &&
(files.totalLinesAdded > 0 || files.totalLinesRemoved > 0) && ( (files.totalLinesAdded > 0 || files.totalLinesRemoved > 0) && (
<StatRow title="Code Changes:"> <StatRow title="Code Changes:">
<Text> <Text color={theme.text.primary}>
<Text color={theme.status.success}> <Text color={theme.status.success}>
+{files.totalLinesAdded} +{files.totalLinesAdded}
</Text>{' '} </Text>{' '}
@@ -241,13 +254,15 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
<Section title="Performance"> <Section title="Performance">
<StatRow title="Wall Time:"> <StatRow title="Wall Time:">
<Text>{duration}</Text> <Text color={theme.text.primary}>{duration}</Text>
</StatRow> </StatRow>
<StatRow title="Agent Active:"> <StatRow title="Agent Active:">
<Text>{formatDuration(computed.agentActiveTime)}</Text> <Text color={theme.text.primary}>
{formatDuration(computed.agentActiveTime)}
</Text>
</StatRow> </StatRow>
<SubStatRow title="API Time:"> <SubStatRow title="API Time:">
<Text> <Text color={theme.text.primary}>
{formatDuration(computed.totalApiTime)}{' '} {formatDuration(computed.totalApiTime)}{' '}
<Text color={theme.text.secondary}> <Text color={theme.text.secondary}>
({computed.apiTimePercent.toFixed(1)}%) ({computed.apiTimePercent.toFixed(1)}%)
@@ -255,7 +270,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
</Text> </Text>
</SubStatRow> </SubStatRow>
<SubStatRow title="Tool Time:"> <SubStatRow title="Tool Time:">
<Text> <Text color={theme.text.primary}>
{formatDuration(computed.totalToolTime)}{' '} {formatDuration(computed.totalToolTime)}{' '}
<Text color={theme.text.secondary}> <Text color={theme.text.secondary}>
({computed.toolTimePercent.toFixed(1)}%) ({computed.toolTimePercent.toFixed(1)}%)
@@ -5,7 +5,7 @@
*/ */
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { PrepareLabel } from './PrepareLabel.js'; import { PrepareLabel } from './PrepareLabel.js';
import { CommandKind } from '../commands/types.js'; import { CommandKind } from '../commands/types.js';
export interface Suggestion { export interface Suggestion {
@@ -56,12 +56,12 @@ export function SuggestionsDisplay({
return ( return (
<Box flexDirection="column" paddingX={1} width={width}> <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) => { {visibleSuggestions.map((suggestion, index) => {
const originalIndex = startIndex + index; const originalIndex = startIndex + index;
const isActive = originalIndex === activeIndex; const isActive = originalIndex === activeIndex;
const textColor = isActive ? Colors.AccentPurple : Colors.Gray; const textColor = isActive ? theme.text.accent : theme.text.secondary;
const labelElement = ( const labelElement = (
<PrepareLabel <PrepareLabel
label={suggestion.label} label={suggestion.label}
@@ -82,7 +82,7 @@ export function SuggestionsDisplay({
<Box flexShrink={0} paddingRight={2}> <Box flexShrink={0} paddingRight={2}>
{labelElement} {labelElement}
{suggestion.commandKind === CommandKind.MCP_PROMPT && ( {suggestion.commandKind === CommandKind.MCP_PROMPT && (
<Text color={Colors.Gray}> [MCP]</Text> <Text color={theme.text.secondary}> [MCP]</Text>
)} )}
</Box> </Box>
) : ( ) : (
@@ -7,7 +7,7 @@
import type React from 'react'; import type React from 'react';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { Box, Text } from 'ink'; 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 { themeManager, DEFAULT_THEME } from '../themes/theme-manager.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { DiffRenderer } from './messages/DiffRenderer.js'; import { DiffRenderer } from './messages/DiffRenderer.js';
@@ -183,7 +183,7 @@ export function ThemeDialog({
return ( return (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
flexDirection="column" flexDirection="column"
paddingTop={includePadding ? 1 : 0} paddingTop={includePadding ? 1 : 0}
paddingBottom={includePadding ? 1 : 0} paddingBottom={includePadding ? 1 : 0}
@@ -197,7 +197,9 @@ export function ThemeDialog({
<Box flexDirection="column" width="45%" paddingRight={2}> <Box flexDirection="column" width="45%" paddingRight={2}>
<Text bold={mode === 'theme'} wrap="truncate"> <Text bold={mode === 'theme'} wrap="truncate">
{mode === 'theme' ? '> ' : ' '}Select Theme{' '} {mode === 'theme' ? '> ' : ' '}Select Theme{' '}
<Text color={Colors.Gray}>{otherScopeModifiedMessage}</Text> <Text color={theme.text.secondary}>
{otherScopeModifiedMessage}
</Text>
</Text> </Text>
<RadioButtonSelect <RadioButtonSelect
items={themeItems} items={themeItems}
@@ -223,7 +225,7 @@ export function ThemeDialog({
return ( return (
<Box <Box
borderStyle="single" borderStyle="single"
borderColor={Colors.Gray} borderColor={theme.border.default}
paddingTop={includePadding ? 1 : 0} paddingTop={includePadding ? 1 : 0}
paddingBottom={includePadding ? 1 : 0} paddingBottom={includePadding ? 1 : 0}
paddingLeft={1} paddingLeft={1}
@@ -267,7 +269,7 @@ def fibonacci(n):
/> />
)} )}
<Box marginTop={1}> <Box marginTop={1}>
<Text color={Colors.Gray} wrap="truncate"> <Text color={theme.text.secondary} wrap="truncate">
(Use Enter to {mode === 'theme' ? 'select' : 'apply scope'}, Tab to{' '} (Use Enter to {mode === 'theme' ? 'select' : 'apply scope'}, Tab to{' '}
{mode === 'theme' ? 'configure scope' : 'select theme'}) {mode === 'theme' ? 'configure scope' : 'select theme'})
</Text> </Text>
+8 -8
View File
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { type Config } from '@google/gemini-cli-core'; import { type Config } from '@google/gemini-cli-core';
interface TipsProps { interface TipsProps {
@@ -17,25 +17,25 @@ export const Tips: React.FC<TipsProps> = ({ config }) => {
const geminiMdFileCount = config.getGeminiMdFileCount(); const geminiMdFileCount = config.getGeminiMdFileCount();
return ( return (
<Box flexDirection="column"> <Box flexDirection="column">
<Text color={Colors.Foreground}>Tips for getting started:</Text> <Text color={theme.text.primary}>Tips for getting started:</Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
1. Ask questions, edit files, or run commands. 1. Ask questions, edit files, or run commands.
</Text> </Text>
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
2. Be specific for the best results. 2. Be specific for the best results.
</Text> </Text>
{geminiMdFileCount === 0 && ( {geminiMdFileCount === 0 && (
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
3. Create{' '} 3. Create{' '}
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
GEMINI.md GEMINI.md
</Text>{' '} </Text>{' '}
files to customize your interactions with Gemini. files to customize your interactions with Gemini.
</Text> </Text>
)} )}
<Text color={Colors.Foreground}> <Text color={theme.text.primary}>
{geminiMdFileCount === 0 ? '4.' : '3.'}{' '} {geminiMdFileCount === 0 ? '4.' : '3.'}{' '}
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
/help /help
</Text>{' '} </Text>{' '}
for more information. for more information.
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { formatDuration } from '../utils/formatters.js'; import { formatDuration } from '../utils/formatters.js';
import { import {
getStatusColor, getStatusColor,
@@ -37,16 +37,16 @@ const StatRow: React.FC<{
return ( return (
<Box> <Box>
<Box width={TOOL_NAME_COL_WIDTH}> <Box width={TOOL_NAME_COL_WIDTH}>
<Text color={Colors.LightBlue}>{name}</Text> <Text color={theme.text.link}>{name}</Text>
</Box> </Box>
<Box width={CALLS_COL_WIDTH} justifyContent="flex-end"> <Box width={CALLS_COL_WIDTH} justifyContent="flex-end">
<Text>{stats.count}</Text> <Text color={theme.text.primary}>{stats.count}</Text>
</Box> </Box>
<Box width={SUCCESS_RATE_COL_WIDTH} justifyContent="flex-end"> <Box width={SUCCESS_RATE_COL_WIDTH} justifyContent="flex-end">
<Text color={successColor}>{successRate.toFixed(1)}%</Text> <Text color={successColor}>{successRate.toFixed(1)}%</Text>
</Box> </Box>
<Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end">
<Text>{formatDuration(avgDuration)}</Text> <Text color={theme.text.primary}>{formatDuration(avgDuration)}</Text>
</Box> </Box>
</Box> </Box>
); );
@@ -63,11 +63,13 @@ export const ToolStatsDisplay: React.FC = () => {
return ( return (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
paddingY={1} paddingY={1}
paddingX={2} 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> </Box>
); );
} }
@@ -94,13 +96,13 @@ export const ToolStatsDisplay: React.FC = () => {
return ( return (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
flexDirection="column" flexDirection="column"
paddingY={1} paddingY={1}
paddingX={2} paddingX={2}
width={70} width={70}
> >
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Tool Stats For Nerds Tool Stats For Nerds
</Text> </Text>
<Box height={1} /> <Box height={1} />
@@ -108,16 +110,24 @@ export const ToolStatsDisplay: React.FC = () => {
{/* Header */} {/* Header */}
<Box> <Box>
<Box width={TOOL_NAME_COL_WIDTH}> <Box width={TOOL_NAME_COL_WIDTH}>
<Text bold>Tool Name</Text> <Text bold color={theme.text.primary}>
Tool Name
</Text>
</Box> </Box>
<Box width={CALLS_COL_WIDTH} justifyContent="flex-end"> <Box width={CALLS_COL_WIDTH} justifyContent="flex-end">
<Text bold>Calls</Text> <Text bold color={theme.text.primary}>
Calls
</Text>
</Box> </Box>
<Box width={SUCCESS_RATE_COL_WIDTH} justifyContent="flex-end"> <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>
<Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> <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>
</Box> </Box>
@@ -128,6 +138,7 @@ export const ToolStatsDisplay: React.FC = () => {
borderTop={false} borderTop={false}
borderLeft={false} borderLeft={false}
borderRight={false} borderRight={false}
borderColor={theme.border.default}
width="100%" width="100%"
/> />
@@ -139,45 +150,47 @@ export const ToolStatsDisplay: React.FC = () => {
<Box height={1} /> <Box height={1} />
{/* User Decision Summary */} {/* User Decision Summary */}
<Text bold>User Decision Summary</Text> <Text bold color={theme.text.primary}>
User Decision Summary
</Text>
<Box> <Box>
<Box <Box
width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} 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>
<Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end">
<Text>{totalReviewed}</Text> <Text color={theme.text.primary}>{totalReviewed}</Text>
</Box> </Box>
</Box> </Box>
<Box> <Box>
<Box <Box
width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH}
> >
<Text> » Accepted:</Text> <Text color={theme.text.primary}> » Accepted:</Text>
</Box> </Box>
<Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> <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>
<Box> <Box>
<Box <Box
width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH}
> >
<Text> » Rejected:</Text> <Text color={theme.text.primary}> » Rejected:</Text>
</Box> </Box>
<Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> <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>
<Box> <Box>
<Box <Box
width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH}
> >
<Text> » Modified:</Text> <Text color={theme.text.primary}> » Modified:</Text>
</Box> </Box>
<Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> <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>
</Box> </Box>
@@ -188,6 +201,7 @@ export const ToolStatsDisplay: React.FC = () => {
borderTop={false} borderTop={false}
borderLeft={false} borderLeft={false}
borderRight={false} borderRight={false}
borderColor={theme.border.default}
width="100%" width="100%"
/> />
@@ -195,7 +209,7 @@ export const ToolStatsDisplay: React.FC = () => {
<Box <Box
width={TOOL_NAME_COL_WIDTH + CALLS_COL_WIDTH + SUCCESS_RATE_COL_WIDTH} 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>
<Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end"> <Box width={AVG_DURATION_COL_WIDTH} justifyContent="flex-end">
<Text bold color={totalReviewed > 0 ? agreementColor : undefined}> <Text bold color={totalReviewed > 0 ? agreementColor : undefined}>
@@ -5,7 +5,7 @@
*/ */
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
interface UpdateNotificationProps { interface UpdateNotificationProps {
message: string; message: string;
@@ -14,10 +14,10 @@ interface UpdateNotificationProps {
export const UpdateNotification = ({ message }: UpdateNotificationProps) => ( export const UpdateNotification = ({ message }: UpdateNotificationProps) => (
<Box <Box
borderStyle="round" borderStyle="round"
borderColor={Colors.AccentYellow} borderColor={theme.status.warning}
paddingX={1} paddingX={1}
marginY={1} marginY={1}
> >
<Text color={Colors.AccentYellow}>{message}</Text> <Text color={theme.status.warning}>{message}</Text>
</Box> </Box>
); );
@@ -10,7 +10,7 @@ import {
performWorkspaceExtensionMigration, performWorkspaceExtensionMigration,
} from '../../config/extension.js'; } from '../../config/extension.js';
import { RadioButtonSelect } from './shared/RadioButtonSelect.js'; import { RadioButtonSelect } from './shared/RadioButtonSelect.js';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { useState } from 'react'; import { useState } from 'react';
export function WorkspaceMigrationDialog(props: { export function WorkspaceMigrationDialog(props: {
@@ -40,15 +40,15 @@ export function WorkspaceMigrationDialog(props: {
<Box <Box
flexDirection="column" flexDirection="column"
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
padding={1} padding={1}
> >
{failedExtensions.length > 0 ? ( {failedExtensions.length > 0 ? (
<> <>
<Text> <Text color={theme.text.primary}>
The following extensions failed to migrate. Please try installing The following extensions failed to migrate. Please try installing
them manually. To see other changes, Gemini CLI must be restarted. them manually. To see other changes, Gemini CLI must be restarted.
Press {"'q'"} to quit. Press &apos;q&apos; to quit.
</Text> </Text>
<Box flexDirection="column" marginTop={1} marginLeft={2}> <Box flexDirection="column" marginTop={1} marginLeft={2}>
{failedExtensions.map((failed) => ( {failedExtensions.map((failed) => (
@@ -57,9 +57,9 @@ export function WorkspaceMigrationDialog(props: {
</Box> </Box>
</> </>
) : ( ) : (
<Text> <Text color={theme.text.primary}>
Migration complete. To see changes, Gemini CLI must be restarted. Migration complete. To see changes, Gemini CLI must be restarted.
Press {"'q'"} to quit. Press &apos;q&apos; to quit.
</Text> </Text>
)} )}
</Box> </Box>
@@ -70,15 +70,19 @@ export function WorkspaceMigrationDialog(props: {
<Box <Box
flexDirection="column" flexDirection="column"
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
padding={1} padding={1}
> >
<Text bold>Workspace-level extensions are deprecated{'\n'}</Text> <Text bold color={theme.text.primary}>
<Text>Would you like to install them at the user level?</Text> Workspace-level extensions are deprecated{'\n'}
<Text> </Text>
<Text color={theme.text.primary}>
Would you like to install them at the user level?
</Text>
<Text color={theme.text.primary}>
The extension definition will remain in your workspace directory. The extension definition will remain in your workspace directory.
</Text> </Text>
<Text> <Text color={theme.text.primary}>
If you opt to skip, you can install them manually using the extensions If you opt to skip, you can install them manually using the extensions
install command. install command.
</Text> </Text>
@@ -8,7 +8,7 @@ import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import type { CompressionProps } from '../../types.js'; import type { CompressionProps } from '../../types.js';
import Spinner from 'ink-spinner'; import Spinner from 'ink-spinner';
import { Colors } from '../../colors.js'; import { theme } from '../../semantic-colors.js';
import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js'; import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js';
export interface CompressionDisplayProps { export interface CompressionDisplayProps {
@@ -33,13 +33,13 @@ export const CompressionMessage: React.FC<CompressionDisplayProps> = ({
{compression.isPending ? ( {compression.isPending ? (
<Spinner type="dots" /> <Spinner type="dots" />
) : ( ) : (
<Text color={Colors.AccentPurple}></Text> <Text color={theme.text.accent}></Text>
)} )}
</Box> </Box>
<Box> <Box>
<Text <Text
color={ color={
compression.isPending ? Colors.AccentPurple : Colors.AccentGreen compression.isPending ? theme.text.accent : theme.status.success
} }
aria-label={SCREEN_READER_MODEL_PREFIX} aria-label={SCREEN_READER_MODEL_PREFIX}
> >
@@ -6,11 +6,11 @@
import type React from 'react'; import type React from 'react';
import { Box, Text, useIsScreenReaderEnabled } from 'ink'; import { Box, Text, useIsScreenReaderEnabled } from 'ink';
import { Colors } from '../../colors.js';
import crypto from 'node:crypto'; import crypto from 'node:crypto';
import { colorizeCode, colorizeLine } from '../../utils/CodeColorizer.js'; import { colorizeCode, colorizeLine } from '../../utils/CodeColorizer.js';
import { MaxSizedBox } from '../shared/MaxSizedBox.js'; import { MaxSizedBox } from '../shared/MaxSizedBox.js';
import { theme } from '../../semantic-colors.js'; import { theme as semanticTheme } from '../../semantic-colors.js';
import type { Theme } from '../../themes/theme.js';
interface DiffLine { interface DiffLine {
type: 'add' | 'del' | 'context' | 'hunk' | 'other'; type: 'add' | 'del' | 'context' | 'hunk' | 'other';
@@ -42,18 +42,9 @@ function parseDiffWithLineNumbers(diffContent: string): DiffLine[] {
} }
if (!inHunk) { if (!inHunk) {
// Skip standard Git header lines more robustly // Skip standard Git header lines more robustly
if ( if (line.startsWith('--- ')) {
line.startsWith('--- ') ||
line.startsWith('+++ ') ||
line.startsWith('diff --git') ||
line.startsWith('index ') ||
line.startsWith('similarity index') ||
line.startsWith('rename from') ||
line.startsWith('rename to') ||
line.startsWith('new file mode') ||
line.startsWith('deleted file mode')
)
continue; continue;
}
// If it's not a hunk or header, skip (or handle as 'other' if needed) // If it's not a hunk or header, skip (or handle as 'other' if needed)
continue; continue;
} }
@@ -94,7 +85,7 @@ interface DiffRendererProps {
tabWidth?: number; tabWidth?: number;
availableTerminalHeight?: number; availableTerminalHeight?: number;
terminalWidth: number; terminalWidth: number;
theme?: import('../../themes/theme.js').Theme; theme?: Theme;
} }
const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization
@@ -109,14 +100,18 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
}) => { }) => {
const screenReaderEnabled = useIsScreenReaderEnabled(); const screenReaderEnabled = useIsScreenReaderEnabled();
if (!diffContent || typeof diffContent !== 'string') { if (!diffContent || typeof diffContent !== 'string') {
return <Text color={Colors.AccentYellow}>No diff content.</Text>; return <Text color={semanticTheme.status.warning}>No diff content.</Text>;
} }
const parsedLines = parseDiffWithLineNumbers(diffContent); const parsedLines = parseDiffWithLineNumbers(diffContent);
if (parsedLines.length === 0) { if (parsedLines.length === 0) {
return ( return (
<Box borderStyle="round" borderColor={Colors.Gray} padding={1}> <Box
borderStyle="round"
borderColor={semanticTheme.border.default}
padding={1}
>
<Text dimColor>No changes detected.</Text> <Text dimColor>No changes detected.</Text>
</Box> </Box>
); );
@@ -196,7 +191,11 @@ const renderDiffContent = (
if (displayableLines.length === 0) { if (displayableLines.length === 0) {
return ( return (
<Box borderStyle="round" borderColor={Colors.Gray} padding={1}> <Box
borderStyle="round"
borderColor={semanticTheme.border.default}
padding={1}
>
<Text dimColor>No changes detected.</Text> <Text dimColor>No changes detected.</Text>
</Box> </Box>
); );
@@ -260,7 +259,7 @@ const renderDiffContent = (
) { ) {
acc.push( acc.push(
<Box key={`gap-${index}`}> <Box key={`gap-${index}`}>
<Text wrap="truncate" color={Colors.Gray}> <Text wrap="truncate" color={semanticTheme.text.secondary}>
{'═'.repeat(terminalWidth)} {'═'.repeat(terminalWidth)}
</Text> </Text>
</Box>, </Box>,
@@ -301,12 +300,12 @@ const renderDiffContent = (
acc.push( acc.push(
<Box key={lineKey} flexDirection="row"> <Box key={lineKey} flexDirection="row">
<Text <Text
color={theme.text.secondary} color={semanticTheme.text.secondary}
backgroundColor={ backgroundColor={
line.type === 'add' line.type === 'add'
? theme.background.diff.added ? semanticTheme.background.diff.added
: line.type === 'del' : line.type === 'del'
? theme.background.diff.removed ? semanticTheme.background.diff.removed
: undefined : undefined
} }
> >
@@ -323,16 +322,16 @@ const renderDiffContent = (
<Text <Text
backgroundColor={ backgroundColor={
line.type === 'add' line.type === 'add'
? theme.background.diff.added ? semanticTheme.background.diff.added
: theme.background.diff.removed : semanticTheme.background.diff.removed
} }
wrap="wrap" wrap="wrap"
> >
<Text <Text
color={ color={
line.type === 'add' line.type === 'add'
? theme.status.success ? semanticTheme.status.success
: theme.status.error : semanticTheme.status.error
} }
> >
{prefixSymbol} {prefixSymbol}
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Text, Box } from 'ink'; import { Text, Box } from 'ink';
import { Colors } from '../../colors.js'; import { theme } from '../../semantic-colors.js';
interface ErrorMessageProps { interface ErrorMessageProps {
text: string; text: string;
@@ -19,10 +19,10 @@ export const ErrorMessage: React.FC<ErrorMessageProps> = ({ text }) => {
return ( return (
<Box flexDirection="row" marginBottom={1}> <Box flexDirection="row" marginBottom={1}>
<Box width={prefixWidth}> <Box width={prefixWidth}>
<Text color={Colors.AccentRed}>{prefix}</Text> <Text color={theme.status.error}>{prefix}</Text>
</Box> </Box>
<Box flexGrow={1}> <Box flexGrow={1}>
<Text wrap="wrap" color={Colors.AccentRed}> <Text wrap="wrap" color={theme.status.error}>
{text} {text}
</Text> </Text>
</Box> </Box>
@@ -7,7 +7,7 @@
import type React from 'react'; import type React from 'react';
import { Text, Box } from 'ink'; import { Text, Box } from 'ink';
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
import { Colors } from '../../colors.js'; import { theme } from '../../semantic-colors.js';
import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js'; import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js';
interface GeminiMessageProps { interface GeminiMessageProps {
@@ -29,10 +29,7 @@ export const GeminiMessage: React.FC<GeminiMessageProps> = ({
return ( return (
<Box flexDirection="row"> <Box flexDirection="row">
<Box width={prefixWidth}> <Box width={prefixWidth}>
<Text <Text color={theme.text.accent} aria-label={SCREEN_READER_MODEL_PREFIX}>
color={Colors.AccentPurple}
aria-label={SCREEN_READER_MODEL_PREFIX}
>
{prefix} {prefix}
</Text> </Text>
</Box> </Box>
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Text, Box } from 'ink'; import { Text, Box } from 'ink';
import { Colors } from '../../colors.js'; import { theme } from '../../semantic-colors.js';
import { RenderInline } from '../../utils/InlineMarkdownRenderer.js'; import { RenderInline } from '../../utils/InlineMarkdownRenderer.js';
interface InfoMessageProps { interface InfoMessageProps {
@@ -20,10 +20,10 @@ export const InfoMessage: React.FC<InfoMessageProps> = ({ text }) => {
return ( return (
<Box flexDirection="row" marginTop={1}> <Box flexDirection="row" marginTop={1}>
<Box width={prefixWidth}> <Box width={prefixWidth}>
<Text color={Colors.AccentYellow}>{prefix}</Text> <Text color={theme.status.warning}>{prefix}</Text>
</Box> </Box>
<Box flexGrow={1}> <Box flexGrow={1}>
<Text wrap="wrap" color={Colors.AccentYellow}> <Text wrap="wrap" color={theme.status.warning}>
<RenderInline text={text} /> <RenderInline text={text} />
</Text> </Text>
</Box> </Box>
@@ -7,7 +7,6 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { DiffRenderer } from './DiffRenderer.js'; import { DiffRenderer } from './DiffRenderer.js';
import { Colors } from '../../colors.js';
import { RenderInline } from '../../utils/InlineMarkdownRenderer.js'; import { RenderInline } from '../../utils/InlineMarkdownRenderer.js';
import type { import type {
ToolCallConfirmationDetails, ToolCallConfirmationDetails,
@@ -20,6 +19,7 @@ import type { RadioSelectItem } from '../shared/RadioButtonSelect.js';
import { RadioButtonSelect } from '../shared/RadioButtonSelect.js'; import { RadioButtonSelect } from '../shared/RadioButtonSelect.js';
import { MaxSizedBox } from '../shared/MaxSizedBox.js'; import { MaxSizedBox } from '../shared/MaxSizedBox.js';
import { useKeypress } from '../../hooks/useKeypress.js'; import { useKeypress } from '../../hooks/useKeypress.js';
import { theme } from '../../semantic-colors.js';
export interface ToolConfirmationMessageProps { export interface ToolConfirmationMessageProps {
confirmationDetails: ToolCallConfirmationDetails; confirmationDetails: ToolCallConfirmationDetails;
@@ -113,13 +113,13 @@ export const ToolConfirmationMessage: React.FC<
<Box <Box
minWidth="90%" minWidth="90%"
borderStyle="round" borderStyle="round"
borderColor={Colors.Gray} borderColor={theme.border.default}
justifyContent="space-around" justifyContent="space-around"
padding={1} padding={1}
overflow="hidden" overflow="hidden"
> >
<Text>Modify in progress: </Text> <Text color={theme.text.primary}>Modify in progress: </Text>
<Text color={Colors.AccentGreen}> <Text color={theme.status.success}>
Save and close external editor to continue Save and close external editor to continue
</Text> </Text>
</Box> </Box>
@@ -193,7 +193,7 @@ export const ToolConfirmationMessage: React.FC<
maxWidth={Math.max(childWidth - 4, 1)} maxWidth={Math.max(childWidth - 4, 1)}
> >
<Box> <Box>
<Text color={Colors.AccentCyan}>{executionProps.command}</Text> <Text color={theme.text.link}>{executionProps.command}</Text>
</Box> </Box>
</MaxSizedBox> </MaxSizedBox>
</Box> </Box>
@@ -223,12 +223,12 @@ export const ToolConfirmationMessage: React.FC<
bodyContent = ( bodyContent = (
<Box flexDirection="column" paddingX={1} marginLeft={1}> <Box flexDirection="column" paddingX={1} marginLeft={1}>
<Text color={Colors.AccentCyan}> <Text color={theme.text.link}>
<RenderInline text={infoProps.prompt} /> <RenderInline text={infoProps.prompt} />
</Text> </Text>
{displayUrls && infoProps.urls && infoProps.urls.length > 0 && ( {displayUrls && infoProps.urls && infoProps.urls.length > 0 && (
<Box flexDirection="column" marginTop={1}> <Box flexDirection="column" marginTop={1}>
<Text>URLs to fetch:</Text> <Text color={theme.text.primary}>URLs to fetch:</Text>
{infoProps.urls.map((url) => ( {infoProps.urls.map((url) => (
<Text key={url}> <Text key={url}>
{' '} {' '}
@@ -245,8 +245,8 @@ export const ToolConfirmationMessage: React.FC<
bodyContent = ( bodyContent = (
<Box flexDirection="column" paddingX={1} marginLeft={1}> <Box flexDirection="column" paddingX={1} marginLeft={1}>
<Text color={Colors.AccentCyan}>MCP Server: {mcpProps.serverName}</Text> <Text color={theme.text.link}>MCP Server: {mcpProps.serverName}</Text>
<Text color={Colors.AccentCyan}>Tool: {mcpProps.toolName}</Text> <Text color={theme.text.link}>Tool: {mcpProps.toolName}</Text>
</Box> </Box>
); );
@@ -281,7 +281,9 @@ export const ToolConfirmationMessage: React.FC<
{/* Confirmation Question */} {/* Confirmation Question */}
<Box marginBottom={1} flexShrink={0}> <Box marginBottom={1} flexShrink={0}>
<Text wrap="truncate">{question}</Text> <Text color={theme.text.primary} wrap="truncate">
{question}
</Text>
</Box> </Box>
{/* Select Input for Options */} {/* Select Input for Options */}
@@ -122,7 +122,9 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
)} )}
{tool.outputFile && ( {tool.outputFile && (
<Box marginX={1}> <Box marginX={1}>
<Text>Output too long and was saved to: {tool.outputFile}</Text> <Text color={theme.text.primary}>
Output too long and was saved to: {tool.outputFile}
</Text>
</Box> </Box>
)} )}
</Box> </Box>
@@ -9,11 +9,11 @@ import { Box, Text } from 'ink';
import type { IndividualToolCallDisplay } from '../../types.js'; import type { IndividualToolCallDisplay } from '../../types.js';
import { ToolCallStatus } from '../../types.js'; import { ToolCallStatus } from '../../types.js';
import { DiffRenderer } from './DiffRenderer.js'; import { DiffRenderer } from './DiffRenderer.js';
import { Colors } from '../../colors.js';
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js'; import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js';
import { MaxSizedBox } from '../shared/MaxSizedBox.js'; import { MaxSizedBox } from '../shared/MaxSizedBox.js';
import { TOOL_STATUS } from '../../constants.js'; import { TOOL_STATUS } from '../../constants.js';
import { theme } from '../../semantic-colors.js';
const STATIC_HEIGHT = 1; const STATIC_HEIGHT = 1;
const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc. const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc.
@@ -120,7 +120,7 @@ const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
}) => ( }) => (
<Box minWidth={STATUS_INDICATOR_WIDTH}> <Box minWidth={STATUS_INDICATOR_WIDTH}>
{status === ToolCallStatus.Pending && ( {status === ToolCallStatus.Pending && (
<Text color={Colors.AccentGreen}>{TOOL_STATUS.PENDING}</Text> <Text color={theme.status.success}>{TOOL_STATUS.PENDING}</Text>
)} )}
{status === ToolCallStatus.Executing && ( {status === ToolCallStatus.Executing && (
<GeminiRespondingSpinner <GeminiRespondingSpinner
@@ -129,22 +129,22 @@ const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
/> />
)} )}
{status === ToolCallStatus.Success && ( {status === ToolCallStatus.Success && (
<Text color={Colors.AccentGreen} aria-label={'Success:'}> <Text color={theme.status.success} aria-label={'Success:'}>
{TOOL_STATUS.SUCCESS} {TOOL_STATUS.SUCCESS}
</Text> </Text>
)} )}
{status === ToolCallStatus.Confirming && ( {status === ToolCallStatus.Confirming && (
<Text color={Colors.AccentYellow} aria-label={'Confirming:'}> <Text color={theme.status.warning} aria-label={'Confirming:'}>
{TOOL_STATUS.CONFIRMING} {TOOL_STATUS.CONFIRMING}
</Text> </Text>
)} )}
{status === ToolCallStatus.Canceled && ( {status === ToolCallStatus.Canceled && (
<Text color={Colors.AccentYellow} aria-label={'Canceled:'} bold> <Text color={theme.status.warning} aria-label={'Canceled:'} bold>
{TOOL_STATUS.CANCELED} {TOOL_STATUS.CANCELED}
</Text> </Text>
)} )}
{status === ToolCallStatus.Error && ( {status === ToolCallStatus.Error && (
<Text color={Colors.AccentRed} aria-label={'Error:'} bold> <Text color={theme.status.error} aria-label={'Error:'} bold>
{TOOL_STATUS.ERROR} {TOOL_STATUS.ERROR}
</Text> </Text>
)} )}
@@ -166,11 +166,11 @@ const ToolInfo: React.FC<ToolInfo> = ({
const nameColor = React.useMemo<string>(() => { const nameColor = React.useMemo<string>(() => {
switch (emphasis) { switch (emphasis) {
case 'high': case 'high':
return Colors.Foreground; return theme.text.primary;
case 'medium': case 'medium':
return Colors.Foreground; return theme.text.primary;
case 'low': case 'low':
return Colors.Gray; return theme.text.secondary;
default: { default: {
const exhaustiveCheck: never = emphasis; const exhaustiveCheck: never = emphasis;
return exhaustiveCheck; return exhaustiveCheck;
@@ -186,14 +186,14 @@ const ToolInfo: React.FC<ToolInfo> = ({
<Text color={nameColor} bold> <Text color={nameColor} bold>
{name} {name}
</Text>{' '} </Text>{' '}
<Text color={Colors.Gray}>{description}</Text> <Text color={theme.text.secondary}>{description}</Text>
</Text> </Text>
</Box> </Box>
); );
}; };
const TrailingIndicator: React.FC = () => ( const TrailingIndicator: React.FC = () => (
<Text color={Colors.Foreground} wrap="truncate"> <Text color={theme.text.primary} wrap="truncate">
{' '} {' '}
</Text> </Text>
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Text, Box } from 'ink'; import { Text, Box } from 'ink';
import { Colors } from '../../colors.js'; import { theme } from '../../semantic-colors.js';
import { SCREEN_READER_USER_PREFIX } from '../../textConstants.js'; import { SCREEN_READER_USER_PREFIX } from '../../textConstants.js';
import { isSlashCommand as checkIsSlashCommand } from '../../utils/commandUtils.js'; import { isSlashCommand as checkIsSlashCommand } from '../../utils/commandUtils.js';
@@ -19,8 +19,8 @@ export const UserMessage: React.FC<UserMessageProps> = ({ text }) => {
const prefixWidth = prefix.length; const prefixWidth = prefix.length;
const isSlashCommand = checkIsSlashCommand(text); const isSlashCommand = checkIsSlashCommand(text);
const textColor = isSlashCommand ? Colors.AccentPurple : Colors.Gray; const textColor = isSlashCommand ? theme.text.accent : theme.text.secondary;
const borderColor = isSlashCommand ? Colors.AccentPurple : Colors.Gray; const borderColor = isSlashCommand ? theme.text.accent : theme.text.secondary;
return ( return (
<Box <Box
@@ -6,7 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { Colors } from '../../colors.js'; import { theme } from '../../semantic-colors.js';
interface UserShellMessageProps { interface UserShellMessageProps {
text: string; text: string;
@@ -18,8 +18,8 @@ export const UserShellMessage: React.FC<UserShellMessageProps> = ({ text }) => {
return ( return (
<Box> <Box>
<Text color={Colors.AccentCyan}>$ </Text> <Text color={theme.text.link}>$ </Text>
<Text>{commandToDisplay}</Text> <Text color={theme.text.primary}>{commandToDisplay}</Text>
</Box> </Box>
); );
}; };
@@ -7,7 +7,7 @@
import React, { Fragment, useEffect, useId } from 'react'; import React, { Fragment, useEffect, useId } from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import stringWidth from 'string-width'; import stringWidth from 'string-width';
import { Colors } from '../../colors.js'; import { theme } from '../../semantic-colors.js';
import { toCodePoints } from '../../utils/textUtils.js'; import { toCodePoints } from '../../utils/textUtils.js';
import { useOverflowActions } from '../../contexts/OverflowContext.js'; import { useOverflowActions } from '../../contexts/OverflowContext.js';
@@ -186,14 +186,14 @@ export const MaxSizedBox: React.FC<MaxSizedBoxProps> = ({
return ( return (
<Box flexDirection="column" width={maxWidth} flexShrink={0}> <Box flexDirection="column" width={maxWidth} flexShrink={0}>
{totalHiddenLines > 0 && overflowDirection === 'top' && ( {totalHiddenLines > 0 && overflowDirection === 'top' && (
<Text color={Colors.Gray} wrap="truncate"> <Text color={theme.text.secondary} wrap="truncate">
... first {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '} ... first {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '}
hidden ... hidden ...
</Text> </Text>
)} )}
{visibleLines} {visibleLines}
{totalHiddenLines > 0 && overflowDirection === 'bottom' && ( {totalHiddenLines > 0 && overflowDirection === 'bottom' && (
<Text color={Colors.Gray} wrap="truncate"> <Text color={theme.text.secondary} wrap="truncate">
... last {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '} ... last {totalHiddenLines} line{totalHiddenLines === 1 ? '' : 's'}{' '}
hidden ... hidden ...
</Text> </Text>
@@ -7,7 +7,7 @@
import type React from 'react'; import type React from 'react';
import { useEffect, useState, useRef } from 'react'; import { useEffect, useState, useRef } from 'react';
import { Text, Box } from 'ink'; import { Text, Box } from 'ink';
import { Colors } from '../../colors.js'; import { theme } from '../../semantic-colors.js';
import { useKeypress } from '../../hooks/useKeypress.js'; import { useKeypress } from '../../hooks/useKeypress.js';
/** /**
@@ -164,7 +164,9 @@ export function RadioButtonSelect<T>({
return ( return (
<Box flexDirection="column"> <Box flexDirection="column">
{showScrollArrows && ( {showScrollArrows && (
<Text color={scrollOffset > 0 ? Colors.Foreground : Colors.Gray}> <Text
color={scrollOffset > 0 ? theme.text.primary : theme.text.secondary}
>
</Text> </Text>
)} )}
@@ -172,18 +174,18 @@ export function RadioButtonSelect<T>({
const itemIndex = scrollOffset + index; const itemIndex = scrollOffset + index;
const isSelected = activeIndex === itemIndex; const isSelected = activeIndex === itemIndex;
let textColor = Colors.Foreground; let textColor = theme.text.primary;
let numberColor = Colors.Foreground; let numberColor = theme.text.primary;
if (isSelected) { if (isSelected) {
textColor = Colors.AccentGreen; textColor = theme.status.success;
numberColor = Colors.AccentGreen; numberColor = theme.status.success;
} else if (item.disabled) { } else if (item.disabled) {
textColor = Colors.Gray; textColor = theme.text.secondary;
numberColor = Colors.Gray; numberColor = theme.text.secondary;
} }
if (!showNumbers) { if (!showNumbers) {
numberColor = Colors.Gray; numberColor = theme.text.secondary;
} }
const numberColumnWidth = String(items.length).length; const numberColumnWidth = String(items.length).length;
@@ -195,7 +197,7 @@ export function RadioButtonSelect<T>({
<Box key={item.label} alignItems="center"> <Box key={item.label} alignItems="center">
<Box minWidth={2} flexShrink={0}> <Box minWidth={2} flexShrink={0}>
<Text <Text
color={isSelected ? Colors.AccentGreen : Colors.Foreground} color={isSelected ? theme.status.success : theme.text.primary}
aria-hidden aria-hidden
> >
{isSelected ? '●' : ' '} {isSelected ? '●' : ' '}
@@ -212,7 +214,9 @@ export function RadioButtonSelect<T>({
{item.themeNameDisplay && item.themeTypeDisplay ? ( {item.themeNameDisplay && item.themeTypeDisplay ? (
<Text color={textColor} wrap="truncate"> <Text color={textColor} wrap="truncate">
{item.themeNameDisplay}{' '} {item.themeNameDisplay}{' '}
<Text color={Colors.Gray}>{item.themeTypeDisplay}</Text> <Text color={theme.text.secondary}>
{item.themeTypeDisplay}
</Text>
</Text> </Text>
) : ( ) : (
<Text color={textColor} wrap="truncate"> <Text color={textColor} wrap="truncate">
@@ -226,8 +230,8 @@ export function RadioButtonSelect<T>({
<Text <Text
color={ color={
scrollOffset + maxItemsToShow < items.length scrollOffset + maxItemsToShow < items.length
? Colors.Foreground ? theme.text.primary
: Colors.Gray : theme.text.secondary
} }
> >
@@ -9,7 +9,7 @@ import { RadioButtonSelect } from '../components/shared/RadioButtonSelect.js';
import { usePrivacySettings } from '../hooks/usePrivacySettings.js'; import { usePrivacySettings } from '../hooks/usePrivacySettings.js';
import { CloudPaidPrivacyNotice } from './CloudPaidPrivacyNotice.js'; import { CloudPaidPrivacyNotice } from './CloudPaidPrivacyNotice.js';
import type { Config } from '@google/gemini-cli-core'; import type { Config } from '@google/gemini-cli-core';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { useKeypress } from '../hooks/useKeypress.js'; import { useKeypress } from '../hooks/useKeypress.js';
interface CloudFreePrivacyNoticeProps { interface CloudFreePrivacyNoticeProps {
@@ -34,16 +34,16 @@ export const CloudFreePrivacyNotice = ({
); );
if (privacyState.isLoading) { if (privacyState.isLoading) {
return <Text color={Colors.Gray}>Loading...</Text>; return <Text color={theme.text.secondary}>Loading...</Text>;
} }
if (privacyState.error) { if (privacyState.error) {
return ( return (
<Box flexDirection="column" marginY={1}> <Box flexDirection="column" marginY={1}>
<Text color={Colors.AccentRed}> <Text color={theme.status.error}>
Error loading Opt-in settings: {privacyState.error} Error loading Opt-in settings: {privacyState.error}
</Text> </Text>
<Text color={Colors.Gray}>Press Esc to exit.</Text> <Text color={theme.text.secondary}>Press Esc to exit.</Text>
</Box> </Box>
); );
} }
@@ -59,17 +59,17 @@ export const CloudFreePrivacyNotice = ({
return ( return (
<Box flexDirection="column" marginY={1}> <Box flexDirection="column" marginY={1}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Gemini Code Assist for Individuals Privacy Notice Gemini Code Assist for Individuals Privacy Notice
</Text> </Text>
<Newline /> <Newline />
<Text> <Text color={theme.text.primary}>
This notice and our Privacy Policy This notice and our Privacy Policy
<Text color={Colors.AccentBlue}>[1]</Text> describe how Gemini Code <Text color={theme.text.link}>[1]</Text> describe how Gemini Code Assist
Assist handles your data. Please read them carefully. handles your data. Please read them carefully.
</Text> </Text>
<Newline /> <Newline />
<Text> <Text color={theme.text.primary}>
When you use Gemini Code Assist for individuals with Gemini CLI, Google When you use Gemini Code Assist for individuals with Gemini CLI, Google
collects your prompts, related code, generated output, code edits, collects your prompts, related code, generated output, code edits,
related feature usage information, and your feedback to provide, related feature usage information, and your feedback to provide,
@@ -77,7 +77,7 @@ export const CloudFreePrivacyNotice = ({
technologies. technologies.
</Text> </Text>
<Newline /> <Newline />
<Text> <Text color={theme.text.primary}>
To help with quality and improve our products (such as generative To help with quality and improve our products (such as generative
machine-learning models), human reviewers may read, annotate, and machine-learning models), human reviewers may read, annotate, and
process the data collected above. We take steps to protect your privacy process the data collected above. We take steps to protect your privacy
@@ -90,7 +90,7 @@ export const CloudFreePrivacyNotice = ({
</Text> </Text>
<Newline /> <Newline />
<Box flexDirection="column"> <Box flexDirection="column">
<Text> <Text color={theme.text.primary}>
Allow Google to use this data to develop and improve our products? Allow Google to use this data to develop and improve our products?
</Text> </Text>
<RadioButtonSelect <RadioButtonSelect
@@ -107,11 +107,13 @@ export const CloudFreePrivacyNotice = ({
</Box> </Box>
<Newline /> <Newline />
<Text> <Text>
<Text color={Colors.AccentBlue}>[1]</Text>{' '} <Text color={theme.text.link}>[1]</Text>{' '}
https://policies.google.com/privacy https://policies.google.com/privacy
</Text> </Text>
<Newline /> <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> </Box>
); );
}; };
@@ -5,7 +5,7 @@
*/ */
import { Box, Newline, Text } from 'ink'; import { Box, Newline, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { useKeypress } from '../hooks/useKeypress.js'; import { useKeypress } from '../hooks/useKeypress.js';
interface CloudPaidPrivacyNoticeProps { interface CloudPaidPrivacyNoticeProps {
@@ -26,14 +26,14 @@ export const CloudPaidPrivacyNotice = ({
return ( return (
<Box flexDirection="column" marginBottom={1}> <Box flexDirection="column" marginBottom={1}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Vertex AI Notice Vertex AI Notice
</Text> </Text>
<Newline /> <Newline />
<Text> <Text color={theme.text.primary}>
Service Specific Terms<Text color={Colors.AccentBlue}>[1]</Text> are Service Specific Terms<Text color={theme.text.link}>[1]</Text> are
incorporated into the agreement under which Google has agreed to provide 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 Customer (the Agreement). If the Agreement authorizes the resale or
supply of Google Cloud Platform under a Google Cloud partner or reseller supply of Google Cloud Platform under a Google Cloud partner or reseller
program, then except for in the section entitled Partner-Specific program, then except for in the section entitled Partner-Specific
@@ -44,16 +44,16 @@ export const CloudPaidPrivacyNotice = ({
them in the Agreement. them in the Agreement.
</Text> </Text>
<Newline /> <Newline />
<Text> <Text color={theme.text.primary}>
<Text color={Colors.AccentBlue}>[1]</Text>{' '} <Text color={theme.text.link}>[1]</Text>{' '}
https://cloud.google.com/terms/service-terms https://cloud.google.com/terms/service-terms
</Text> </Text>
<Text> <Text color={theme.text.primary}>
<Text color={Colors.AccentGreen}>[2]</Text>{' '} <Text color={theme.status.success}>[2]</Text>{' '}
https://cloud.google.com/terms/services https://cloud.google.com/terms/services
</Text> </Text>
<Newline /> <Newline />
<Text color={Colors.Gray}>Press Esc to exit.</Text> <Text color={theme.text.secondary}>Press Esc to exit.</Text>
</Box> </Box>
); );
}; };
@@ -5,7 +5,7 @@
*/ */
import { Box, Newline, Text } from 'ink'; import { Box, Newline, Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { useKeypress } from '../hooks/useKeypress.js'; import { useKeypress } from '../hooks/useKeypress.js';
interface GeminiPrivacyNoticeProps { interface GeminiPrivacyNoticeProps {
@@ -24,39 +24,39 @@ export const GeminiPrivacyNotice = ({ onExit }: GeminiPrivacyNoticeProps) => {
return ( return (
<Box flexDirection="column" marginBottom={1}> <Box flexDirection="column" marginBottom={1}>
<Text bold color={Colors.AccentPurple}> <Text bold color={theme.text.accent}>
Gemini API Key Notice Gemini API Key Notice
</Text> </Text>
<Newline /> <Newline />
<Text> <Text color={theme.text.primary}>
By using the Gemini API<Text color={Colors.AccentBlue}>[1]</Text>, By using the Gemini API<Text color={theme.text.link}>[1]</Text>, Google
Google AI Studio AI Studio
<Text color={Colors.AccentRed}>[2]</Text>, and the other Google <Text color={theme.status.error}>[2]</Text>, and the other Google
developer services that reference these terms (collectively, the developer services that reference these terms (collectively, the
&quot;APIs&quot; or &quot;Services&quot;), you are agreeing to Google &quot;APIs&quot; or &quot;Services&quot;), you are agreeing to Google
APIs Terms of Service (the &quot;API Terms&quot;) APIs Terms of Service (the &quot;API Terms&quot;)
<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 &quot;Additional Terms&quot;) Additional Terms of Service (the &quot;Additional Terms&quot;)
<Text color={Colors.AccentPurple}>[4]</Text>. <Text color={theme.text.accent}>[4]</Text>.
</Text> </Text>
<Newline /> <Newline />
<Text> <Text color={theme.text.primary}>
<Text color={Colors.AccentBlue}>[1]</Text>{' '} <Text color={theme.text.link}>[1]</Text>{' '}
https://ai.google.dev/docs/gemini_api_overview https://ai.google.dev/docs/gemini_api_overview
</Text> </Text>
<Text> <Text color={theme.text.primary}>
<Text color={Colors.AccentRed}>[2]</Text> https://aistudio.google.com/ <Text color={theme.status.error}>[2]</Text> https://aistudio.google.com/
</Text> </Text>
<Text> <Text color={theme.text.primary}>
<Text color={Colors.AccentGreen}>[3]</Text>{' '} <Text color={theme.status.success}>[3]</Text>{' '}
https://developers.google.com/terms https://developers.google.com/terms
</Text> </Text>
<Text> <Text color={theme.text.primary}>
<Text color={Colors.AccentPurple}>[4]</Text>{' '} <Text color={theme.text.accent}>[4]</Text>{' '}
https://ai.google.dev/gemini-api/terms https://ai.google.dev/gemini-api/terms
</Text> </Text>
<Newline /> <Newline />
<Text color={Colors.Gray}>Press Esc to exit.</Text> <Text color={theme.text.secondary}>Press Esc to exit.</Text>
</Box> </Box>
); );
}; };
+15 -15
View File
@@ -410,31 +410,31 @@ export function createCustomTheme(customTheme: CustomTheme): Theme {
const semanticColors: SemanticColors = { const semanticColors: SemanticColors = {
text: { text: {
primary: colors.Foreground, primary: customTheme.text?.primary ?? colors.Foreground,
secondary: colors.Gray, secondary: customTheme.text?.secondary ?? colors.Gray,
link: colors.AccentBlue, link: customTheme.text?.link ?? colors.AccentBlue,
accent: colors.AccentPurple, accent: customTheme.text?.accent ?? colors.AccentPurple,
}, },
background: { background: {
primary: colors.Background, primary: customTheme.background?.primary ?? colors.Background,
diff: { diff: {
added: colors.DiffAdded, added: customTheme.background?.diff?.added ?? colors.DiffAdded,
removed: colors.DiffRemoved, removed: customTheme.background?.diff?.removed ?? colors.DiffRemoved,
}, },
}, },
border: { border: {
default: colors.Gray, default: customTheme.border?.default ?? colors.Gray,
focused: colors.AccentBlue, focused: customTheme.border?.focused ?? colors.AccentBlue,
}, },
ui: { ui: {
comment: colors.Comment, comment: customTheme.ui?.comment ?? colors.Comment,
symbol: colors.Gray, symbol: customTheme.ui?.symbol ?? colors.Gray,
gradient: colors.GradientColors, gradient: customTheme.ui?.gradient ?? colors.GradientColors,
}, },
status: { status: {
error: colors.AccentRed, error: customTheme.status?.error ?? colors.AccentRed,
success: colors.AccentGreen, success: customTheme.status?.success ?? colors.AccentGreen,
warning: colors.AccentYellow, warning: customTheme.status?.warning ?? colors.AccentYellow,
}, },
}; };
@@ -6,13 +6,13 @@
import React from 'react'; import React from 'react';
import { Text } from 'ink'; import { Text } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import stringWidth from 'string-width'; import stringWidth from 'string-width';
// Constants for Markdown parsing // Constants for Markdown parsing
const BOLD_MARKER_LENGTH = 2; // For "**" const BOLD_MARKER_LENGTH = 2; // For "**"
const ITALIC_MARKER_LENGTH = 1; // For "*" or "_" const ITALIC_MARKER_LENGTH = 1; // For "*" or "_"
const STRIKETHROUGH_MARKER_LENGTH = 2; // For "~~" const STRIKETHROUGH_MARKER_LENGTH = 2; // For "~~")
const INLINE_CODE_MARKER_LENGTH = 1; // For "`" const INLINE_CODE_MARKER_LENGTH = 1; // For "`"
const UNDERLINE_TAG_START_LENGTH = 3; // For "<u>" const UNDERLINE_TAG_START_LENGTH = 3; // For "<u>"
const UNDERLINE_TAG_END_LENGTH = 4; // For "</u>" const UNDERLINE_TAG_END_LENGTH = 4; // For "</u>"
@@ -24,7 +24,7 @@ interface RenderInlineProps {
const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => { const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
// Early return for plain text without markdown or URLs // Early return for plain text without markdown or URLs
if (!/[*_~`<[https?:]/.test(text)) { if (!/[*_~`<[https?:]/.test(text)) {
return <Text>{text}</Text>; return <Text color={theme.text.primary}>{text}</Text>;
} }
const nodes: React.ReactNode[] = []; const nodes: React.ReactNode[] = [];
@@ -96,7 +96,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
const codeMatch = fullMatch.match(/^(`+)(.+?)\1$/s); const codeMatch = fullMatch.match(/^(`+)(.+?)\1$/s);
if (codeMatch && codeMatch[2]) { if (codeMatch && codeMatch[2]) {
renderedNode = ( renderedNode = (
<Text key={key} color={Colors.AccentPurple}> <Text key={key} color={theme.text.accent}>
{codeMatch[2]} {codeMatch[2]}
</Text> </Text>
); );
@@ -113,7 +113,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
renderedNode = ( renderedNode = (
<Text key={key}> <Text key={key}>
{linkText} {linkText}
<Text color={Colors.AccentBlue}> ({url})</Text> <Text color={theme.text.link}> ({url})</Text>
</Text> </Text>
); );
} }
@@ -133,7 +133,7 @@ const RenderInlineInternal: React.FC<RenderInlineProps> = ({ text }) => {
); );
} else if (fullMatch.match(/^https?:\/\//)) { } else if (fullMatch.match(/^https?:\/\//)) {
renderedNode = ( renderedNode = (
<Text key={key} color={Colors.AccentBlue}> <Text key={key} color={theme.text.link}>
{fullMatch} {fullMatch}
</Text> </Text>
); );
@@ -168,6 +168,6 @@ export const getPlainTextLength = (text: string): number => {
.replace(/~~(.*?)~~/g, '$1') .replace(/~~(.*?)~~/g, '$1')
.replace(/`(.*?)`/g, '$1') .replace(/`(.*?)`/g, '$1')
.replace(/<u>(.*?)<\/u>/g, '$1') .replace(/<u>(.*?)<\/u>/g, '$1')
.replace(/\[(.*?)\]\(.*?\)/g, '$1'); .replace(/.*\[(.*?)\]\(.*\)/g, '$1');
return stringWidth(cleanText); return stringWidth(cleanText);
}; };
+10 -8
View File
@@ -7,7 +7,7 @@
import React from 'react'; import React from 'react';
import { Text, Box } from 'ink'; import { Text, Box } from 'ink';
import { EOL } from 'node:os'; import { EOL } from 'node:os';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { colorizeCode } from './CodeColorizer.js'; import { colorizeCode } from './CodeColorizer.js';
import { TableRenderer } from './TableRenderer.js'; import { TableRenderer } from './TableRenderer.js';
import { RenderInline } from './InlineMarkdownRenderer.js'; import { RenderInline } from './InlineMarkdownRenderer.js';
@@ -174,14 +174,14 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
switch (level) { switch (level) {
case 1: case 1:
headerNode = ( headerNode = (
<Text bold color={Colors.AccentCyan}> <Text bold color={theme.text.link}>
<RenderInline text={headerText} /> <RenderInline text={headerText} />
</Text> </Text>
); );
break; break;
case 2: case 2:
headerNode = ( headerNode = (
<Text bold color={Colors.AccentBlue}> <Text bold color={theme.text.link}>
<RenderInline text={headerText} /> <RenderInline text={headerText} />
</Text> </Text>
); );
@@ -195,14 +195,14 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
break; break;
case 4: case 4:
headerNode = ( headerNode = (
<Text italic color={Colors.Gray}> <Text italic color={theme.text.secondary}>
<RenderInline text={headerText} /> <RenderInline text={headerText} />
</Text> </Text>
); );
break; break;
default: default:
headerNode = ( headerNode = (
<Text> <Text color={theme.text.primary}>
<RenderInline text={headerText} /> <RenderInline text={headerText} />
</Text> </Text>
); );
@@ -246,7 +246,7 @@ const MarkdownDisplayInternal: React.FC<MarkdownDisplayProps> = ({
} else { } else {
addContentBlock( addContentBlock(
<Box key={key}> <Box key={key}>
<Text wrap="wrap"> <Text wrap="wrap" color={theme.text.primary}>
<RenderInline text={line} /> <RenderInline text={line} />
</Text> </Text>
</Box>, </Box>,
@@ -315,7 +315,9 @@ const RenderCodeBlockInternal: React.FC<RenderCodeBlockProps> = ({
// Not enough space to even show the message meaningfully // Not enough space to even show the message meaningfully
return ( return (
<Box paddingLeft={CODE_BLOCK_PREFIX_PADDING}> <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> </Box>
); );
} }
@@ -331,7 +333,7 @@ const RenderCodeBlockInternal: React.FC<RenderCodeBlockProps> = ({
return ( return (
<Box paddingLeft={CODE_BLOCK_PREFIX_PADDING} flexDirection="column"> <Box paddingLeft={CODE_BLOCK_PREFIX_PADDING} flexDirection="column">
{colorizedTruncatedCode} {colorizedTruncatedCode}
<Text color={Colors.Gray}>... generating more ...</Text> <Text color={theme.text.secondary}>... generating more ...</Text>
</Box> </Box>
); );
} }
+4 -4
View File
@@ -6,7 +6,7 @@
import React from 'react'; import React from 'react';
import { Text, Box } from 'ink'; import { Text, Box } from 'ink';
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
import { RenderInline, getPlainTextLength } from './InlineMarkdownRenderer.js'; import { RenderInline, getPlainTextLength } from './InlineMarkdownRenderer.js';
interface TableRendererProps { interface TableRendererProps {
@@ -89,7 +89,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
return ( return (
<Text> <Text>
{isHeader ? ( {isHeader ? (
<Text bold color={Colors.AccentCyan}> <Text bold color={theme.text.link}>
<RenderInline text={cellContent} /> <RenderInline text={cellContent} />
</Text> </Text>
) : ( ) : (
@@ -112,7 +112,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
const borderParts = adjustedWidths.map((w) => char.horizontal.repeat(w)); const borderParts = adjustedWidths.map((w) => char.horizontal.repeat(w));
const border = char.left + borderParts.join(char.middle) + char.right; const border = char.left + borderParts.join(char.middle) + char.right;
return <Text>{border}</Text>; return <Text color={theme.border.default}>{border}</Text>;
}; };
// Helper function to render a table row // Helper function to render a table row
@@ -123,7 +123,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
}); });
return ( return (
<Text> <Text color={theme.text.primary}>
{' '} {' '}
{renderedCells.map((cell, index) => ( {renderedCells.map((cell, index) => (
<React.Fragment key={index}> <React.Fragment key={index}>
+4 -4
View File
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import { Colors } from '../colors.js'; import { theme } from '../semantic-colors.js';
// --- Thresholds --- // --- Thresholds ---
export const TOOL_SUCCESS_RATE_HIGH = 95; export const TOOL_SUCCESS_RATE_HIGH = 95;
@@ -23,10 +23,10 @@ export const getStatusColor = (
options: { defaultColor?: string } = {}, options: { defaultColor?: string } = {},
) => { ) => {
if (value >= thresholds.green) { if (value >= thresholds.green) {
return Colors.AccentGreen; return theme.status.success;
} }
if (value >= thresholds.yellow) { if (value >= thresholds.yellow) {
return Colors.AccentYellow; return theme.status.warning;
} }
return options.defaultColor || Colors.AccentRed; return options.defaultColor || theme.status.error;
}; };