snow-ai 0.4.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/app.js +30 -12
  2. package/dist/cli.js +6 -0
  3. package/dist/i18n/lang/en.js +21 -0
  4. package/dist/i18n/lang/es.js +21 -0
  5. package/dist/i18n/lang/ja.js +21 -0
  6. package/dist/i18n/lang/ko.js +21 -0
  7. package/dist/i18n/lang/zh-TW.js +21 -0
  8. package/dist/i18n/lang/zh.js +21 -0
  9. package/dist/i18n/types.d.ts +21 -0
  10. package/dist/mcp/todo.js +1 -1
  11. package/dist/ui/components/AgentPickerPanel.js +8 -6
  12. package/dist/ui/components/ChatInput.js +23 -21
  13. package/dist/ui/components/CommandPanel.js +7 -5
  14. package/dist/ui/components/DiffViewer.js +6 -4
  15. package/dist/ui/components/FileList.js +8 -6
  16. package/dist/ui/components/Menu.d.ts +1 -1
  17. package/dist/ui/components/Menu.js +8 -6
  18. package/dist/ui/components/PendingMessages.js +7 -5
  19. package/dist/ui/components/TodoPickerPanel.js +12 -10
  20. package/dist/ui/components/TodoTree.js +7 -5
  21. package/dist/ui/components/ToolConfirmation.js +14 -12
  22. package/dist/ui/components/ToolResultPreview.js +17 -3
  23. package/dist/ui/contexts/ThemeContext.d.ts +13 -0
  24. package/dist/ui/contexts/ThemeContext.js +28 -0
  25. package/dist/ui/pages/ChatScreen.js +21 -19
  26. package/dist/ui/pages/CodeBaseConfigScreen.js +30 -28
  27. package/dist/ui/pages/ConfigScreen.js +76 -74
  28. package/dist/ui/pages/CustomHeadersScreen.js +33 -31
  29. package/dist/ui/pages/LanguageSettingsScreen.js +6 -4
  30. package/dist/ui/pages/ProxyConfigScreen.js +15 -13
  31. package/dist/ui/pages/SensitiveCommandConfigScreen.js +12 -10
  32. package/dist/ui/pages/SubAgentConfigScreen.js +12 -10
  33. package/dist/ui/pages/SubAgentListScreen.js +11 -9
  34. package/dist/ui/pages/SystemPromptConfigScreen.js +21 -19
  35. package/dist/ui/pages/ThemeSettingsScreen.d.ts +7 -0
  36. package/dist/ui/pages/ThemeSettingsScreen.js +106 -0
  37. package/dist/ui/pages/WelcomeScreen.js +11 -1
  38. package/dist/ui/themes/index.d.ts +23 -0
  39. package/dist/ui/themes/index.js +140 -0
  40. package/dist/utils/configManager.js +11 -3
  41. package/dist/utils/logger.d.ts +9 -3
  42. package/dist/utils/logger.js +28 -3
  43. package/dist/utils/mcpToolsManager.d.ts +1 -1
  44. package/dist/utils/mcpToolsManager.js +13 -9
  45. package/dist/utils/themeConfig.d.ts +21 -0
  46. package/dist/utils/themeConfig.js +61 -0
  47. package/dist/utils/toolExecutor.js +11 -1
  48. package/package.json +3 -2
package/dist/app.js CHANGED
@@ -1,16 +1,19 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect, Suspense } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import { Alert } from '@inkjs/ui';
4
+ import Spinner from 'ink-spinner';
4
5
  import WelcomeScreen from './ui/pages/WelcomeScreen.js';
5
- import MCPConfigScreen from './ui/pages/MCPConfigScreen.js';
6
- import SystemPromptConfigScreen from './ui/pages/SystemPromptConfigScreen.js';
7
- import CustomHeadersScreen from './ui/pages/CustomHeadersScreen.js';
8
- import ChatScreen from './ui/pages/ChatScreen.js';
9
- import HeadlessModeScreen from './ui/pages/HeadlessModeScreen.js';
6
+ // Lazy load heavy components to improve startup time
7
+ const ChatScreen = React.lazy(() => import('./ui/pages/ChatScreen.js'));
8
+ const HeadlessModeScreen = React.lazy(() => import('./ui/pages/HeadlessModeScreen.js'));
9
+ const MCPConfigScreen = React.lazy(() => import('./ui/pages/MCPConfigScreen.js'));
10
+ const SystemPromptConfigScreen = React.lazy(() => import('./ui/pages/SystemPromptConfigScreen.js'));
11
+ const CustomHeadersScreen = React.lazy(() => import('./ui/pages/CustomHeadersScreen.js'));
10
12
  import { useGlobalExit, } from './hooks/useGlobalExit.js';
11
13
  import { onNavigate } from './hooks/useGlobalNavigation.js';
12
14
  import { useTerminalSize } from './hooks/useTerminalSize.js';
13
15
  import { I18nProvider } from './i18n/index.js';
16
+ import { ThemeProvider } from './ui/contexts/ThemeContext.js';
14
17
  // Inner component that uses I18n context
15
18
  function AppContent({ version, skipWelcome, }) {
16
19
  const [currentView, setCurrentView] = useState(skipWelcome ? 'chat' : 'welcome');
@@ -55,21 +58,29 @@ function AppContent({ version, skipWelcome, }) {
55
58
  }
56
59
  };
57
60
  const renderView = () => {
61
+ const loadingFallback = (React.createElement(Box, null,
62
+ React.createElement(Text, { color: "cyan" },
63
+ React.createElement(Spinner, { type: "dots" })),
64
+ React.createElement(Text, null, " Loading...")));
58
65
  switch (currentView) {
59
66
  case 'welcome':
60
67
  return (React.createElement(WelcomeScreen, { version: version, onMenuSelect: handleMenuSelect }));
61
68
  case 'chat':
62
- return React.createElement(ChatScreen, { key: chatScreenKey, skipWelcome: skipWelcome });
69
+ return (React.createElement(Suspense, { fallback: loadingFallback },
70
+ React.createElement(ChatScreen, { key: chatScreenKey, skipWelcome: skipWelcome })));
63
71
  case 'settings':
64
72
  return (React.createElement(Box, { flexDirection: "column" },
65
73
  React.createElement(Text, { color: "blue" }, "Settings"),
66
74
  React.createElement(Text, { color: "gray" }, "Settings interface would be implemented here")));
67
75
  case 'mcp':
68
- return (React.createElement(MCPConfigScreen, { onBack: () => setCurrentView('welcome'), onSave: () => setCurrentView('welcome') }));
76
+ return (React.createElement(Suspense, { fallback: loadingFallback },
77
+ React.createElement(MCPConfigScreen, { onBack: () => setCurrentView('welcome'), onSave: () => setCurrentView('welcome') })));
69
78
  case 'systemprompt':
70
- return (React.createElement(SystemPromptConfigScreen, { onBack: () => setCurrentView('welcome') }));
79
+ return (React.createElement(Suspense, { fallback: loadingFallback },
80
+ React.createElement(SystemPromptConfigScreen, { onBack: () => setCurrentView('welcome') })));
71
81
  case 'customheaders':
72
- return React.createElement(CustomHeadersScreen, { onBack: () => setCurrentView('welcome') });
82
+ return (React.createElement(Suspense, { fallback: loadingFallback },
83
+ React.createElement(CustomHeadersScreen, { onBack: () => setCurrentView('welcome') })));
73
84
  default:
74
85
  return (React.createElement(WelcomeScreen, { version: version, onMenuSelect: handleMenuSelect }));
75
86
  }
@@ -83,9 +94,16 @@ export default function App({ version, skipWelcome, headlessPrompt }) {
83
94
  // If headless prompt is provided, use headless mode
84
95
  // Wrap in I18nProvider since HeadlessModeScreen might use hooks that depend on it
85
96
  if (headlessPrompt) {
97
+ const loadingFallback = (React.createElement(Box, null,
98
+ React.createElement(Text, { color: "cyan" },
99
+ React.createElement(Spinner, { type: "dots" })),
100
+ React.createElement(Text, null, " Loading...")));
86
101
  return (React.createElement(I18nProvider, null,
87
- React.createElement(HeadlessModeScreen, { prompt: headlessPrompt, onComplete: () => process.exit(0) })));
102
+ React.createElement(ThemeProvider, null,
103
+ React.createElement(Suspense, { fallback: loadingFallback },
104
+ React.createElement(HeadlessModeScreen, { prompt: headlessPrompt, onComplete: () => process.exit(0) })))));
88
105
  }
89
106
  return (React.createElement(I18nProvider, null,
90
- React.createElement(AppContent, { version: version, skipWelcome: skipWelcome })));
107
+ React.createElement(ThemeProvider, null,
108
+ React.createElement(AppContent, { version: version, skipWelcome: skipWelcome }))));
91
109
  }
package/dist/cli.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ // Show loading indicator immediately before any imports
3
+ process.stdout.write('\x1b[?25l'); // Hide cursor
4
+ process.stdout.write('⠋ Loading...\r');
2
5
  import React from 'react';
3
6
  import { render, Text, Box } from 'ink';
4
7
  import Spinner from 'ink-spinner';
@@ -137,6 +140,9 @@ const Startup = ({ version, skipWelcome, headlessPrompt, }) => {
137
140
  };
138
141
  // Disable bracketed paste mode on startup
139
142
  process.stdout.write('\x1b[?2004l');
143
+ // Clear the early loading indicator and show cursor
144
+ process.stdout.write('\x1b[2K\r'); // Clear line
145
+ process.stdout.write('\x1b[?25h'); // Show cursor
140
146
  // Re-enable on exit to avoid polluting parent shell
141
147
  const cleanup = () => {
142
148
  process.stdout.write('\x1b[?2004l');
@@ -22,6 +22,8 @@ export const en = {
22
22
  sensitiveCommandsInfo: 'Configure commands that require confirmation even in YOLO mode',
23
23
  languageSettings: 'Language Settings',
24
24
  languageSettingsInfo: 'Switch application language',
25
+ themeSettings: 'Theme Settings',
26
+ themeSettingsInfo: 'Configure theme and preview DiffViewer',
25
27
  exit: 'Exit',
26
28
  exitInfo: 'Exit the application',
27
29
  },
@@ -274,6 +276,25 @@ export const en = {
274
276
  // Navigation hints
275
277
  listNavigationHint: '↑↓: Navigate • Space: Toggle • A: Add • D: Delete • R: Reset • Esc: Back',
276
278
  },
279
+ themeSettings: {
280
+ title: 'Theme Settings',
281
+ current: 'Current:',
282
+ preview: 'Preview:',
283
+ back: '← Back',
284
+ backInfo: 'Return to main menu',
285
+ darkTheme: 'Dark Theme',
286
+ darkThemeInfo: 'Classic dark color scheme',
287
+ lightTheme: 'Light Theme',
288
+ lightThemeInfo: 'Classic light color scheme',
289
+ githubDark: 'GitHub Dark',
290
+ githubDarkInfo: 'GitHub inspired dark theme',
291
+ rainbow: 'Rainbow',
292
+ rainbowInfo: 'Vibrant rainbow colors for a fun experience',
293
+ solarizedDark: 'Solarized Dark',
294
+ solarizedDarkInfo: 'Solarized dark theme with precision colors',
295
+ nord: 'Nord',
296
+ nordInfo: 'Arctic, north-bluish color palette',
297
+ },
277
298
  helpPanel: {
278
299
  title: '🔰 Keyboard Shortcuts & Help',
279
300
  textEditingTitle: '📝 Text Editing:',
@@ -22,6 +22,8 @@ export const es = {
22
22
  sensitiveCommandsInfo: 'Configurar comandos que requieren confirmación incluso en modo YOLO',
23
23
  languageSettings: 'Configuración de Idioma',
24
24
  languageSettingsInfo: 'Cambiar el idioma de la aplicación',
25
+ themeSettings: 'Configuración de Tema',
26
+ themeSettingsInfo: 'Configurar tema y vista previa del visor de diferencias',
25
27
  exit: 'Salir',
26
28
  exitInfo: 'Salir de la aplicación',
27
29
  },
@@ -274,6 +276,25 @@ export const es = {
274
276
  // Navigation hints
275
277
  listNavigationHint: '↑↓: Navegar • Espacio: Alternar • A: Agregar • D: Eliminar • R: Restablecer • Esc: Volver',
276
278
  },
279
+ themeSettings: {
280
+ title: 'Configuración de Tema',
281
+ current: 'Actual:',
282
+ preview: 'Vista previa:',
283
+ back: '← Atrás',
284
+ backInfo: 'Volver al menú principal',
285
+ darkTheme: 'Tema Oscuro',
286
+ darkThemeInfo: 'Esquema de colores oscuros clásico',
287
+ lightTheme: 'Tema Claro',
288
+ lightThemeInfo: 'Esquema de colores claros clásico',
289
+ githubDark: 'GitHub Oscuro',
290
+ githubDarkInfo: 'Tema oscuro inspirado en GitHub',
291
+ rainbow: 'Arcoíris',
292
+ rainbowInfo: 'Colores vibrantes del arcoíris para una experiencia divertida',
293
+ solarizedDark: 'Solarized Oscuro',
294
+ solarizedDarkInfo: 'Tema oscuro Solarized con colores precisos',
295
+ nord: 'Nord',
296
+ nordInfo: 'Paleta de colores ártica y azulada del norte',
297
+ },
277
298
  helpPanel: {
278
299
  title: '🔰 Atajos de Teclado y Ayuda',
279
300
  textEditingTitle: '📝 Edición de Texto:',
@@ -22,6 +22,8 @@ export const ja = {
22
22
  sensitiveCommandsInfo: 'YOLOモードでも確認が必要なコマンドを構成',
23
23
  languageSettings: '言語設定',
24
24
  languageSettingsInfo: 'アプリケーションの言語を切り替え',
25
+ themeSettings: 'テーマ設定',
26
+ themeSettingsInfo: 'テーマを設定して差分ビューワーをプレビュー',
25
27
  exit: '終了',
26
28
  exitInfo: 'アプリケーションを終了',
27
29
  },
@@ -274,6 +276,25 @@ export const ja = {
274
276
  // Navigation hints
275
277
  listNavigationHint: '↑↓: 移動 • Space: 切替 • A: 追加 • D: 削除 • R: リセット • Esc: 戻る',
276
278
  },
279
+ themeSettings: {
280
+ title: 'テーマ設定',
281
+ current: '現在:',
282
+ preview: 'プレビュー:',
283
+ back: '← 戻る',
284
+ backInfo: 'メインメニューに戻る',
285
+ darkTheme: 'ダークテーマ',
286
+ darkThemeInfo: 'クラシックダーク配色',
287
+ lightTheme: 'ライトテーマ',
288
+ lightThemeInfo: 'クラシックライト配色',
289
+ githubDark: 'GitHub ダーク',
290
+ githubDarkInfo: 'GitHubにインスパイアされたダークテーマ',
291
+ rainbow: 'レインボー',
292
+ rainbowInfo: '鮮やかな虹色で楽しい体験',
293
+ solarizedDark: 'Solarized ダーク',
294
+ solarizedDarkInfo: '精密な色を持つ Solarized ダークテーマ',
295
+ nord: 'Nord',
296
+ nordInfo: '北極、北方の青みがかった色パレット',
297
+ },
277
298
  helpPanel: {
278
299
  title: '🔰 キーボードショートカットとヘルプ',
279
300
  textEditingTitle: '📝 テキスト編集:',
@@ -22,6 +22,8 @@ export const ko = {
22
22
  sensitiveCommandsInfo: 'YOLO 모드에서도 확인이 필요한 명령 구성',
23
23
  languageSettings: '언어 설정',
24
24
  languageSettingsInfo: '애플리케이션 언어 전환',
25
+ themeSettings: '테마 설정',
26
+ themeSettingsInfo: '테마를 구성하고 차이점 뷰어를 미리 보기',
25
27
  exit: '종료',
26
28
  exitInfo: '애플리케이션 종료',
27
29
  },
@@ -274,6 +276,25 @@ export const ko = {
274
276
  // Navigation hints
275
277
  listNavigationHint: '↑↓: 이동 • 스페이스: 토글 • A: 추가 • D: 삭제 • R: 재설정 • Esc: 뒤로',
276
278
  },
279
+ themeSettings: {
280
+ title: '테마 설정',
281
+ current: '현재:',
282
+ preview: '미리 보기:',
283
+ back: '← 뒤로',
284
+ backInfo: '메인 메뉴로 돌아가기',
285
+ darkTheme: '다크 테마',
286
+ darkThemeInfo: '클래식 다크 컬러 스키마',
287
+ lightTheme: '라이트 테마',
288
+ lightThemeInfo: '클래식 라이트 컬러 스키마',
289
+ githubDark: 'GitHub 다크',
290
+ githubDarkInfo: 'GitHub에서 영감을 받은 다크 테마',
291
+ rainbow: '레인보우',
292
+ rainbowInfo: '재미있는 경험을 위한 생동감 있는 무지개 색상',
293
+ solarizedDark: 'Solarized 다크',
294
+ solarizedDarkInfo: '정밀한 색상을 가진 Solarized 다크 테마',
295
+ nord: 'Nord',
296
+ nordInfo: '북극, 북부 청량 컬러 팔레트',
297
+ },
277
298
  helpPanel: {
278
299
  title: '🔰 키보드 단축키 및 도움말',
279
300
  textEditingTitle: '📝 텍스트 편집:',
@@ -22,6 +22,8 @@ export const zhTW = {
22
22
  sensitiveCommandsInfo: '配置即使在 YOLO 模式下也需要確認的命令',
23
23
  languageSettings: '語言設定',
24
24
  languageSettingsInfo: '切換應用語言',
25
+ themeSettings: '主題設定',
26
+ themeSettingsInfo: '設定主題並預覽差異檢視器',
25
27
  exit: '退出',
26
28
  exitInfo: '退出應用程式',
27
29
  },
@@ -274,6 +276,25 @@ export const zhTW = {
274
276
  // Navigation hints
275
277
  listNavigationHint: '↑↓: 導航 • 空格: 切換 • A: 新增 • D: 刪除 • R: 重設 • Esc: 返回',
276
278
  },
279
+ themeSettings: {
280
+ title: '主題設定',
281
+ current: '目前:',
282
+ preview: '預覽:',
283
+ back: '← 返回',
284
+ backInfo: '返回主選單',
285
+ darkTheme: '深色主題',
286
+ darkThemeInfo: '經典深色配色方案',
287
+ lightTheme: '淺色主題',
288
+ lightThemeInfo: '經典淺色配色方案',
289
+ githubDark: 'GitHub 深色',
290
+ githubDarkInfo: '受 GitHub 啟發的深色主題',
291
+ rainbow: '彩虹',
292
+ rainbowInfo: '生動的彩虹色彩,帶來有趣的體驗',
293
+ solarizedDark: 'Solarized 深色',
294
+ solarizedDarkInfo: '具有精確色彩的 Solarized 深色主題',
295
+ nord: 'Nord',
296
+ nordInfo: '北極、北方藍調色板',
297
+ },
277
298
  helpPanel: {
278
299
  title: '🔰 鍵盤快捷鍵和說明',
279
300
  textEditingTitle: '📝 文字編輯:',
@@ -22,6 +22,8 @@ export const zh = {
22
22
  sensitiveCommandsInfo: '配置即使在 YOLO 模式下也需要确认的命令',
23
23
  languageSettings: '语言设置',
24
24
  languageSettingsInfo: '切换应用语言',
25
+ themeSettings: '主题设置',
26
+ themeSettingsInfo: '配置主题并预览差异查看器',
25
27
  exit: '退出',
26
28
  exitInfo: '退出应用程序',
27
29
  },
@@ -274,6 +276,25 @@ export const zh = {
274
276
  // Navigation hints
275
277
  listNavigationHint: '↑↓: 导航 • 空格: 切换 • A: 添加 • D: 删除 • R: 重置 • Esc: 返回',
276
278
  },
279
+ themeSettings: {
280
+ title: '主题设置',
281
+ current: '当前:',
282
+ preview: '预览:',
283
+ back: '← 返回',
284
+ backInfo: '返回主菜单',
285
+ darkTheme: '深色主题',
286
+ darkThemeInfo: '经典深色配色方案',
287
+ lightTheme: '浅色主题',
288
+ lightThemeInfo: '经典浅色配色方案',
289
+ githubDark: 'GitHub 深色',
290
+ githubDarkInfo: '受 GitHub 启发的深色主题',
291
+ rainbow: '彩虹',
292
+ rainbowInfo: '生动的彩虹色彩,带来有趣的体验',
293
+ solarizedDark: 'Solarized 深色',
294
+ solarizedDarkInfo: '具有精确色彩的 Solarized 深色主题',
295
+ nord: 'Nord',
296
+ nordInfo: '北极、北方蓝调色板',
297
+ },
277
298
  helpPanel: {
278
299
  title: '🔰 键盘快捷键和帮助',
279
300
  textEditingTitle: '📝 文本编辑:',
@@ -23,6 +23,8 @@ export type TranslationKeys = {
23
23
  sensitiveCommandsInfo: string;
24
24
  languageSettings: string;
25
25
  languageSettingsInfo: string;
26
+ themeSettings: string;
27
+ themeSettingsInfo: string;
26
28
  exit: string;
27
29
  exitInfo: string;
28
30
  };
@@ -271,6 +273,25 @@ export type TranslationKeys = {
271
273
  confirmHint: string;
272
274
  listNavigationHint: string;
273
275
  };
276
+ themeSettings: {
277
+ title: string;
278
+ current: string;
279
+ preview: string;
280
+ back: string;
281
+ backInfo: string;
282
+ darkTheme: string;
283
+ darkThemeInfo: string;
284
+ lightTheme: string;
285
+ lightThemeInfo: string;
286
+ githubDark: string;
287
+ githubDarkInfo: string;
288
+ rainbow: string;
289
+ rainbowInfo: string;
290
+ solarizedDark: string;
291
+ solarizedDarkInfo: string;
292
+ nord: string;
293
+ nordInfo: string;
294
+ };
274
295
  helpPanel: {
275
296
  title: string;
276
297
  textEditingTitle: string;
package/dist/mcp/todo.js CHANGED
@@ -267,7 +267,7 @@ This REPLACES the entire TODO list. For adding tasks to existing list, use "todo
267
267
  description: 'Parent TODO ID (optional, for creating subtasks in hierarchical structure)',
268
268
  },
269
269
  },
270
- required: ['content'],
270
+ required: ['content', 'parentId'],
271
271
  },
272
272
  description: 'Complete list of TODO items. Each item must represent a discrete, verifiable unit of work. For programming tasks, typical structure: analyze code → implement changes → test functionality → verify build → commit (if requested).',
273
273
  },
@@ -1,7 +1,9 @@
1
1
  import React, { memo, useMemo } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import { Alert } from '@inkjs/ui';
4
+ import { useTheme } from '../contexts/ThemeContext.js';
4
5
  const AgentPickerPanel = memo(({ agents, selectedIndex, visible, maxHeight }) => {
6
+ const { theme } = useTheme();
5
7
  // Fixed maximum display items to prevent rendering issues
6
8
  const MAX_DISPLAY_ITEMS = 5;
7
9
  const effectiveMaxItems = maxHeight
@@ -39,7 +41,7 @@ const AgentPickerPanel = memo(({ agents, selectedIndex, visible, maxHeight }) =>
39
41
  React.createElement(Box, { width: "100%" },
40
42
  React.createElement(Box, { flexDirection: "column", width: "100%" },
41
43
  React.createElement(Box, null,
42
- React.createElement(Text, { color: "yellow", bold: true }, "Sub-Agent Selection")),
44
+ React.createElement(Text, { color: theme.colors.warning, bold: true }, "Sub-Agent Selection")),
43
45
  React.createElement(Box, { marginTop: 1 },
44
46
  React.createElement(Alert, { variant: "warning" }, "No sub-agents configured. Please configure sub-agents first."))))));
45
47
  }
@@ -47,23 +49,23 @@ const AgentPickerPanel = memo(({ agents, selectedIndex, visible, maxHeight }) =>
47
49
  React.createElement(Box, { width: "100%" },
48
50
  React.createElement(Box, { flexDirection: "column", width: "100%" },
49
51
  React.createElement(Box, null,
50
- React.createElement(Text, { color: "yellow", bold: true },
52
+ React.createElement(Text, { color: theme.colors.warning, bold: true },
51
53
  "Select Sub-Agent",
52
54
  ' ',
53
55
  agents.length > effectiveMaxItems &&
54
56
  `(${selectedIndex + 1}/${agents.length})`),
55
- React.createElement(Text, { color: "gray", dimColor: true }, "(Press ESC to close)")),
57
+ React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, "(Press ESC to close)")),
56
58
  displayedAgents.map((agent, index) => (React.createElement(Box, { key: agent.id, flexDirection: "column", width: "100%" },
57
- React.createElement(Text, { color: index === displayedSelectedIndex ? 'green' : 'gray', bold: true },
59
+ React.createElement(Text, { color: index === displayedSelectedIndex ? theme.colors.success : theme.colors.menuSecondary, bold: true },
58
60
  index === displayedSelectedIndex ? '❯ ' : ' ',
59
61
  "#",
60
62
  agent.name),
61
63
  React.createElement(Box, { marginLeft: 3 },
62
- React.createElement(Text, { color: index === displayedSelectedIndex ? 'green' : 'gray', dimColor: true },
64
+ React.createElement(Text, { color: index === displayedSelectedIndex ? theme.colors.success : theme.colors.menuSecondary, dimColor: true },
63
65
  "\u2514\u2500 ",
64
66
  agent.description || 'No description'))))),
65
67
  agents.length > effectiveMaxItems && (React.createElement(Box, { marginTop: 1 },
66
- React.createElement(Text, { color: "gray", dimColor: true },
68
+ React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
67
69
  "\u2191\u2193 to scroll \u00B7 ",
68
70
  agents.length - effectiveMaxItems,
69
71
  " more hidden")))))));
@@ -16,6 +16,7 @@ import { useTerminalFocus } from '../../hooks/useTerminalFocus.js';
16
16
  import { useAgentPicker } from '../../hooks/useAgentPicker.js';
17
17
  import { useTodoPicker } from '../../hooks/useTodoPicker.js';
18
18
  import { useI18n } from '../../i18n/index.js';
19
+ import { useTheme } from '../contexts/ThemeContext.js';
19
20
  /**
20
21
  * Calculate context usage percentage
21
22
  * This is the same logic used in ChatInput to display usage
@@ -36,6 +37,7 @@ export function calculateContextPercentage(contextUsage) {
36
37
  export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type your message...', disabled = false, isProcessing = false, chatHistory = [], onHistorySelect, yoloMode = false, contextUsage, initialContent = null, onContextPercentageChange, }) {
37
38
  // Use i18n hook for translations
38
39
  const { t } = useI18n();
40
+ const { theme } = useTheme();
39
41
  // Use terminal size hook to listen for resize events
40
42
  const { columns: terminalWidth } = useTerminalSize();
41
43
  const prevTerminalWidthRef = useRef(terminalWidth);
@@ -206,14 +208,14 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
206
208
  // Render cursor based on focus state
207
209
  const renderCursor = useCallback((char) => {
208
210
  if (hasFocus) {
209
- // Focused: solid block cursor
210
- return (React.createElement(Text, { backgroundColor: "white", color: "black" }, char));
211
+ // Focused: solid block cursor (use inverted colors)
212
+ return (React.createElement(Text, { backgroundColor: theme.colors.menuNormal, color: theme.colors.background }, char));
211
213
  }
212
214
  else {
213
215
  // Unfocused: no cursor, just render the character normally
214
216
  return React.createElement(Text, null, char);
215
217
  }
216
- }, [hasFocus]);
218
+ }, [hasFocus, theme]);
217
219
  // Render content with cursor (treat all text including placeholders as plain text)
218
220
  const renderContent = () => {
219
221
  if (buffer.text.length > 0) {
@@ -243,7 +245,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
243
245
  else {
244
246
  return (React.createElement(React.Fragment, null,
245
247
  renderCursor(' '),
246
- React.createElement(Text, { color: disabled ? 'darkGray' : 'gray', dimColor: true }, disabled ? t.chatScreen.waitingForResponse : placeholder)));
248
+ React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, disabled ? t.chatScreen.waitingForResponse : placeholder)));
247
249
  }
248
250
  };
249
251
  return (React.createElement(Box, { flexDirection: "column", paddingX: 1, width: terminalWidth },
@@ -264,7 +266,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
264
266
  const hasMoreAbove = startIndex > 0;
265
267
  const hasMoreBelow = endIndex < userMessages.length;
266
268
  return (React.createElement(React.Fragment, null,
267
- React.createElement(Box, { height: 1 }, hasMoreAbove ? (React.createElement(Text, { color: "gray", dimColor: true }, t.chatScreen.moreAbove.replace('{count}', startIndex.toString()))) : (React.createElement(Text, null, " "))),
269
+ React.createElement(Box, { height: 1 }, hasMoreAbove ? (React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.chatScreen.moreAbove.replace('{count}', startIndex.toString()))) : (React.createElement(Text, null, " "))),
268
270
  visibleMessages.map((message, displayIndex) => {
269
271
  const actualIndex = startIndex + displayIndex;
270
272
  // Ensure single line by removing all newlines and control characters
@@ -280,24 +282,24 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
280
282
  : singleLineLabel;
281
283
  return (React.createElement(Box, { key: message.value, height: 1 },
282
284
  React.createElement(Text, { color: actualIndex === historySelectedIndex
283
- ? 'green'
284
- : 'white', bold: true, wrap: "truncate" },
285
+ ? theme.colors.menuSelected
286
+ : theme.colors.menuNormal, bold: true, wrap: "truncate" },
285
287
  actualIndex === historySelectedIndex ? '❯ ' : ' ',
286
288
  truncatedLabel)));
287
289
  }),
288
- React.createElement(Box, { height: 1 }, hasMoreBelow ? (React.createElement(Text, { color: "gray", dimColor: true }, t.chatScreen.moreBelow.replace('{count}', (userMessages.length - endIndex).toString()))) : (React.createElement(Text, null, " ")))));
290
+ React.createElement(Box, { height: 1 }, hasMoreBelow ? (React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.chatScreen.moreBelow.replace('{count}', (userMessages.length - endIndex).toString()))) : (React.createElement(Text, null, " ")))));
289
291
  })()),
290
292
  React.createElement(Box, { marginBottom: 1 },
291
- React.createElement(Text, { color: "cyan", dimColor: true }, t.chatScreen.historyNavigateHint)))),
293
+ React.createElement(Text, { color: theme.colors.menuInfo, dimColor: true }, t.chatScreen.historyNavigateHint)))),
292
294
  !showHistoryMenu && (React.createElement(React.Fragment, null,
293
295
  React.createElement(Box, { flexDirection: "column", width: terminalWidth - 2 },
294
- React.createElement(Text, { color: "gray" }, '─'.repeat(terminalWidth - 2)),
296
+ React.createElement(Text, { color: theme.colors.menuSecondary }, '─'.repeat(terminalWidth - 2)),
295
297
  React.createElement(Box, { flexDirection: "row" },
296
- React.createElement(Text, { color: "cyan", bold: true },
298
+ React.createElement(Text, { color: theme.colors.menuInfo, bold: true },
297
299
  "\u276F",
298
300
  ' '),
299
301
  React.createElement(Box, { flexGrow: 1 }, renderContent())),
300
- React.createElement(Text, { color: "gray" }, '─'.repeat(terminalWidth - 2))),
302
+ React.createElement(Text, { color: theme.colors.menuSecondary }, '─'.repeat(terminalWidth - 2))),
301
303
  (showCommands && getFilteredCommands().length > 0) ||
302
304
  showFilePicker ? (React.createElement(Box, { marginTop: 1 },
303
305
  React.createElement(Text, null, showCommands && getFilteredCommands().length > 0
@@ -313,9 +315,9 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
313
315
  React.createElement(AgentPickerPanel, { agents: getFilteredAgents(), selectedIndex: agentSelectedIndex, visible: showAgentPicker, maxHeight: 5 }),
314
316
  React.createElement(TodoPickerPanel, { todos: todos, selectedIndex: todoSelectedIndex, selectedTodos: selectedTodos, visible: showTodoPicker, maxHeight: 5, isLoading: todoIsLoading, searchQuery: todoSearchQuery, totalCount: totalTodoCount }),
315
317
  yoloMode && (React.createElement(Box, { marginTop: 1 },
316
- React.createElement(Text, { color: "yellow", dimColor: true }, t.chatScreen.yoloModeActive))),
318
+ React.createElement(Text, { color: theme.colors.warning, dimColor: true }, t.chatScreen.yoloModeActive))),
317
319
  contextUsage && (React.createElement(Box, { marginTop: 1 },
318
- React.createElement(Text, { color: "gray", dimColor: true }, (() => {
320
+ React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, (() => {
319
321
  // Determine which caching system is being used
320
322
  const isAnthropic = (contextUsage.cacheCreationTokens || 0) > 0 ||
321
323
  (contextUsage.cacheReadTokens || 0) > 0;
@@ -330,13 +332,13 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
330
332
  : contextUsage.inputTokens;
331
333
  let color;
332
334
  if (percentage < 50)
333
- color = 'green';
335
+ color = theme.colors.success;
334
336
  else if (percentage < 75)
335
- color = 'yellow';
337
+ color = theme.colors.warning;
336
338
  else if (percentage < 90)
337
- color = 'orange';
339
+ color = theme.colors.warning;
338
340
  else
339
- color = 'red';
341
+ color = theme.colors.error;
340
342
  const formatNumber = (num) => {
341
343
  if (num >= 1000)
342
344
  return `${(num / 1000).toFixed(1)}k`;
@@ -354,7 +356,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
354
356
  React.createElement(Text, null, " \u00B7 "),
355
357
  isAnthropic && (React.createElement(React.Fragment, null,
356
358
  (contextUsage.cacheReadTokens || 0) > 0 && (React.createElement(React.Fragment, null,
357
- React.createElement(Text, { color: "cyan" },
359
+ React.createElement(Text, { color: theme.colors.menuInfo },
358
360
  "\u21AF",
359
361
  ' ',
360
362
  formatNumber(contextUsage.cacheReadTokens || 0),
@@ -362,13 +364,13 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
362
364
  t.chatScreen.cached))),
363
365
  (contextUsage.cacheCreationTokens || 0) > 0 && (React.createElement(React.Fragment, null,
364
366
  (contextUsage.cacheReadTokens || 0) > 0 && (React.createElement(Text, null, " \u00B7 ")),
365
- React.createElement(Text, { color: "magenta" },
367
+ React.createElement(Text, { color: theme.colors.warning },
366
368
  "\u25C6",
367
369
  ' ',
368
370
  formatNumber(contextUsage.cacheCreationTokens || 0),
369
371
  ' ',
370
372
  t.chatScreen.newCache))))),
371
- isOpenAI && (React.createElement(Text, { color: "cyan" },
373
+ isOpenAI && (React.createElement(Text, { color: theme.colors.menuInfo },
372
374
  "\u21AF ",
373
375
  formatNumber(contextUsage.cachedTokens || 0),
374
376
  ' ',
@@ -2,8 +2,10 @@ import React, { memo, useMemo } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import { Alert } from '@inkjs/ui';
4
4
  import { useI18n } from '../../i18n/index.js';
5
+ import { useTheme } from '../contexts/ThemeContext.js';
5
6
  const CommandPanel = memo(({ commands, selectedIndex, visible, maxHeight, isProcessing = false, }) => {
6
7
  const { t } = useI18n();
8
+ const { theme } = useTheme();
7
9
  // Fixed maximum display items to prevent rendering issues
8
10
  const MAX_DISPLAY_ITEMS = 5;
9
11
  const effectiveMaxItems = maxHeight
@@ -41,7 +43,7 @@ const CommandPanel = memo(({ commands, selectedIndex, visible, maxHeight, isProc
41
43
  React.createElement(Box, { width: "100%" },
42
44
  React.createElement(Box, { flexDirection: "column", width: "100%" },
43
45
  React.createElement(Box, null,
44
- React.createElement(Text, { color: "yellow", bold: true }, t.commandPanel.title)),
46
+ React.createElement(Text, { color: theme.colors.warning, bold: true }, t.commandPanel.title)),
45
47
  React.createElement(Box, { marginTop: 1 },
46
48
  React.createElement(Alert, { variant: "info" }, t.commandPanel.processingMessage))))));
47
49
  }
@@ -53,22 +55,22 @@ const CommandPanel = memo(({ commands, selectedIndex, visible, maxHeight, isProc
53
55
  React.createElement(Box, { width: "100%" },
54
56
  React.createElement(Box, { flexDirection: "column", width: "100%" },
55
57
  React.createElement(Box, null,
56
- React.createElement(Text, { color: "yellow", bold: true },
58
+ React.createElement(Text, { color: theme.colors.warning, bold: true },
57
59
  t.commandPanel.availableCommands,
58
60
  ' ',
59
61
  commands.length > effectiveMaxItems &&
60
62
  `(${selectedIndex + 1}/${commands.length})`)),
61
63
  displayedCommands.map((command, index) => (React.createElement(Box, { key: command.name, flexDirection: "column", width: "100%" },
62
- React.createElement(Text, { color: index === displayedSelectedIndex ? 'green' : 'gray', bold: true },
64
+ React.createElement(Text, { color: index === displayedSelectedIndex ? theme.colors.success : theme.colors.menuSecondary, bold: true },
63
65
  index === displayedSelectedIndex ? '❯ ' : ' ',
64
66
  "/",
65
67
  command.name),
66
68
  React.createElement(Box, { marginLeft: 3 },
67
- React.createElement(Text, { color: index === displayedSelectedIndex ? 'green' : 'gray', dimColor: true },
69
+ React.createElement(Text, { color: index === displayedSelectedIndex ? theme.colors.success : theme.colors.menuSecondary, dimColor: true },
68
70
  "\u2514\u2500 ",
69
71
  command.description))))),
70
72
  commands.length > effectiveMaxItems && (React.createElement(Box, { marginTop: 1 },
71
- React.createElement(Text, { color: "gray", dimColor: true },
73
+ React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true },
72
74
  t.commandPanel.scrollHint,
73
75
  " \u00B7",
74
76
  ' ',
@@ -1,6 +1,7 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import * as Diff from 'diff';
4
+ import { useTheme } from '../contexts/ThemeContext.js';
4
5
  // Helper function to strip line numbers from content (format: "123→content")
5
6
  function stripLineNumbers(content) {
6
7
  return content
@@ -13,6 +14,7 @@ function stripLineNumbers(content) {
13
14
  .join('\n');
14
15
  }
15
16
  export default function DiffViewer({ oldContent = '', newContent, filename, completeOldContent, completeNewContent, startLineNumber = 1, }) {
17
+ const { theme } = useTheme();
16
18
  // If complete file contents are provided, use them for intelligent diff
17
19
  const useCompleteContent = completeOldContent && completeNewContent;
18
20
  const diffOldContent = useCompleteContent
@@ -34,10 +36,10 @@ export default function DiffViewer({ oldContent = '', newContent, filename, comp
34
36
  filename && React.createElement(Text, { color: "cyan" },
35
37
  " ",
36
38
  filename)),
37
- React.createElement(Box, { flexDirection: "column" }, allLines.map((line, index) => (React.createElement(Text, { key: index, color: "white", backgroundColor: "#006400" },
39
+ React.createElement(Box, { flexDirection: "column" }, allLines.map((line, index) => (React.createElement(Text, { key: index, color: "white", backgroundColor: theme.colors.diffAdded },
38
40
  "+ ",
39
41
  line))))));
40
- }, [isNewFile, diffNewContent, filename]);
42
+ }, [isNewFile, diffNewContent, filename, theme.colors.text, theme.colors.diffAdded]);
41
43
  if (isNewFile) {
42
44
  return newFileContent;
43
45
  }
@@ -151,13 +153,13 @@ export default function DiffViewer({ oldContent = '', newContent, filename, comp
151
153
  ? String(lineNum).padStart(4, ' ')
152
154
  : ' ';
153
155
  if (change.type === 'added') {
154
- return (React.createElement(Text, { key: changeIndex, color: "white", backgroundColor: "#006400" },
156
+ return (React.createElement(Text, { key: changeIndex, color: "white", backgroundColor: theme.colors.diffAdded },
155
157
  lineNumStr,
156
158
  " + ",
157
159
  change.content));
158
160
  }
159
161
  if (change.type === 'removed') {
160
- return (React.createElement(Text, { key: changeIndex, color: "white", backgroundColor: "#8B0000" },
162
+ return (React.createElement(Text, { key: changeIndex, color: "white", backgroundColor: theme.colors.diffRemoved },
161
163
  lineNumStr,
162
164
  " - ",
163
165
  change.content));