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
@@ -5,8 +5,10 @@ import { Alert } from '@inkjs/ui';
5
5
  import TextInput from 'ink-text-input';
6
6
  import { getSystemPromptConfig, saveSystemPromptConfig, } from '../../utils/apiConfig.js';
7
7
  import { useI18n } from '../../i18n/index.js';
8
+ import { useTheme } from '../contexts/ThemeContext.js';
8
9
  export default function SystemPromptConfigScreen({ onBack }) {
9
10
  const { t } = useI18n();
11
+ const { theme } = useTheme();
10
12
  const [config, setConfig] = useState(() => {
11
13
  return (getSystemPromptConfig() || {
12
14
  active: '',
@@ -231,26 +233,26 @@ export default function SystemPromptConfigScreen({ onBack }) {
231
233
  if (view === 'list') {
232
234
  const activePrompt = config.prompts.find(p => p.id === config.active);
233
235
  return (React.createElement(Box, { flexDirection: "column", padding: 1 },
234
- React.createElement(Box, { marginBottom: 1, borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 },
236
+ React.createElement(Box, { marginBottom: 1, borderStyle: "round", borderColor: theme.colors.menuInfo, paddingX: 2, paddingY: 1 },
235
237
  React.createElement(Box, { flexDirection: "column" },
236
238
  React.createElement(Gradient, { name: "rainbow" }, t.systemPromptConfig.title),
237
- React.createElement(Text, { color: "gray", dimColor: true }, t.systemPromptConfig.subtitle))),
239
+ React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.systemPromptConfig.subtitle))),
238
240
  error && (React.createElement(Box, { marginBottom: 1 },
239
241
  React.createElement(Alert, { variant: "error" }, error))),
240
242
  React.createElement(Box, { marginBottom: 1 },
241
243
  React.createElement(Text, { bold: true },
242
244
  t.systemPromptConfig.activePrompt,
243
245
  ' ',
244
- React.createElement(Text, { color: "green" }, activePrompt?.name || t.systemPromptConfig.none))),
246
+ React.createElement(Text, { color: theme.colors.success }, activePrompt?.name || t.systemPromptConfig.none))),
245
247
  config.prompts.length === 0 ? (React.createElement(Box, { marginBottom: 1 },
246
- React.createElement(Text, { color: "yellow" }, t.systemPromptConfig.noPromptsConfigured))) : (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
247
- React.createElement(Text, { bold: true, color: "cyan" }, t.systemPromptConfig.availablePrompts),
248
+ React.createElement(Text, { color: theme.colors.warning }, t.systemPromptConfig.noPromptsConfigured))) : (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
249
+ React.createElement(Text, { bold: true, color: theme.colors.menuInfo }, t.systemPromptConfig.availablePrompts),
248
250
  config.prompts.map((prompt, index) => (React.createElement(Box, { key: prompt.id, marginLeft: 2 },
249
251
  React.createElement(Text, { color: index === selectedIndex
250
- ? 'green'
252
+ ? theme.colors.menuSelected
251
253
  : prompt.id === config.active
252
- ? 'cyan'
253
- : 'white' },
254
+ ? theme.colors.menuInfo
255
+ : theme.colors.menuNormal },
254
256
  index === selectedIndex ? '❯ ' : ' ',
255
257
  prompt.id === config.active ? '✓ ' : ' ',
256
258
  prompt.name,
@@ -260,8 +262,8 @@ export default function SystemPromptConfigScreen({ onBack }) {
260
262
  prompt.content.substring(0, 50),
261
263
  prompt.content.length > 50 ? '...' : '')))))))),
262
264
  React.createElement(Box, { marginBottom: 1 },
263
- React.createElement(Text, { bold: true, color: "cyan" }, t.systemPromptConfig.actions)),
264
- React.createElement(Box, { flexDirection: "column", marginBottom: 1, marginLeft: 2 }, actions.map(action => (React.createElement(Text, { key: action, color: currentAction === action ? 'green' : 'gray', bold: currentAction === action },
265
+ React.createElement(Text, { bold: true, color: theme.colors.menuInfo }, t.systemPromptConfig.actions)),
266
+ React.createElement(Box, { flexDirection: "column", marginBottom: 1, marginLeft: 2 }, actions.map(action => (React.createElement(Text, { key: action, color: currentAction === action ? theme.colors.menuSelected : theme.colors.menuSecondary, bold: currentAction === action },
265
267
  currentAction === action ? '❯ ' : ' ',
266
268
  action === 'activate' && t.systemPromptConfig.activate,
267
269
  action === 'deactivate' && t.systemPromptConfig.deactivate,
@@ -270,12 +272,12 @@ export default function SystemPromptConfigScreen({ onBack }) {
270
272
  action === 'add' && t.systemPromptConfig.addNew,
271
273
  action === 'back' && t.systemPromptConfig.escBack)))),
272
274
  React.createElement(Box, { marginTop: 1 },
273
- React.createElement(Text, { color: "gray", dimColor: true }, t.systemPromptConfig.navigationHint))));
275
+ React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.systemPromptConfig.navigationHint))));
274
276
  }
275
277
  // Render add/edit view
276
278
  if (view === 'add' || view === 'edit') {
277
279
  return (React.createElement(Box, { flexDirection: "column", padding: 1 },
278
- React.createElement(Box, { marginBottom: 1, borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 },
280
+ React.createElement(Box, { marginBottom: 1, borderStyle: "round", borderColor: theme.colors.menuInfo, paddingX: 2, paddingY: 1 },
279
281
  React.createElement(Gradient, { name: "rainbow" }, view === 'add'
280
282
  ? t.systemPromptConfig.addNewTitle
281
283
  : t.systemPromptConfig.editTitle)),
@@ -284,27 +286,27 @@ export default function SystemPromptConfigScreen({ onBack }) {
284
286
  React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
285
287
  React.createElement(Box, { marginBottom: 1 },
286
288
  React.createElement(Box, { flexDirection: "column" },
287
- React.createElement(Text, { color: editingField === 'name' ? 'green' : 'white' },
289
+ React.createElement(Text, { color: editingField === 'name' ? theme.colors.menuSelected : theme.colors.menuNormal },
288
290
  editingField === 'name' ? '❯ ' : ' ',
289
291
  t.systemPromptConfig.nameLabel),
290
292
  editingField === 'name' && isEditing && (React.createElement(Box, { marginLeft: 3 },
291
293
  React.createElement(TextInput, { value: editName, onChange: setEditName, placeholder: t.systemPromptConfig.enterPromptName }))),
292
294
  (!isEditing || editingField !== 'name') && (React.createElement(Box, { marginLeft: 3 },
293
- React.createElement(Text, { color: "gray" }, editName || t.systemPromptConfig.notSet))))),
295
+ React.createElement(Text, { color: theme.colors.menuSecondary }, editName || t.systemPromptConfig.notSet))))),
294
296
  React.createElement(Box, { marginBottom: 1 },
295
297
  React.createElement(Box, { flexDirection: "column" },
296
- React.createElement(Text, { color: editingField === 'content' ? 'green' : 'white' },
298
+ React.createElement(Text, { color: editingField === 'content' ? theme.colors.menuSelected : theme.colors.menuNormal },
297
299
  editingField === 'content' ? '❯ ' : ' ',
298
300
  t.systemPromptConfig.contentLabel),
299
301
  editingField === 'content' && isEditing && (React.createElement(Box, { marginLeft: 3 },
300
302
  React.createElement(TextInput, { value: editContent, onChange: setEditContent, placeholder: t.systemPromptConfig.enterPromptContent }))),
301
303
  (!isEditing || editingField !== 'content') && (React.createElement(Box, { marginLeft: 3 },
302
- React.createElement(Text, { color: "gray" }, editContent
304
+ React.createElement(Text, { color: theme.colors.menuSecondary }, editContent
303
305
  ? editContent.substring(0, 100) +
304
306
  (editContent.length > 100 ? '...' : '')
305
307
  : t.systemPromptConfig.notSet)))))),
306
308
  React.createElement(Box, { marginTop: 1 },
307
- React.createElement(Text, { color: "gray", dimColor: true }, t.systemPromptConfig.editingHint))));
309
+ React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.systemPromptConfig.editingHint))));
308
310
  }
309
311
  // Render delete confirmation
310
312
  if (view === 'confirmDelete') {
@@ -315,10 +317,10 @@ export default function SystemPromptConfigScreen({ onBack }) {
315
317
  React.createElement(Text, null,
316
318
  t.systemPromptConfig.deleteConfirmMessage,
317
319
  " \"",
318
- React.createElement(Text, { bold: true, color: "yellow" }, promptToDelete?.name),
320
+ React.createElement(Text, { bold: true, color: theme.colors.warning }, promptToDelete?.name),
319
321
  "\"?")),
320
322
  React.createElement(Box, { marginTop: 1 },
321
- React.createElement(Text, { color: "gray", dimColor: true }, t.systemPromptConfig.confirmHint))));
323
+ React.createElement(Text, { color: theme.colors.menuSecondary, dimColor: true }, t.systemPromptConfig.confirmHint))));
322
324
  }
323
325
  return null;
324
326
  }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ onBack: () => void;
4
+ inlineMode?: boolean;
5
+ };
6
+ export default function ThemeSettingsScreen({ onBack, inlineMode, }: Props): React.JSX.Element;
7
+ export {};
@@ -0,0 +1,106 @@
1
+ import React, { useMemo, useCallback, useState } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { Alert } from '@inkjs/ui';
4
+ import Menu from '../components/Menu.js';
5
+ import DiffViewer from '../components/DiffViewer.js';
6
+ import { useTheme } from '../contexts/ThemeContext.js';
7
+ import { useI18n } from '../../i18n/index.js';
8
+ const sampleOldCode = `function greet(name) {
9
+ console.log("Hello " + name);
10
+ return "Welcome!";
11
+ }`;
12
+ const sampleNewCode = `function greet(name: string): string {
13
+ console.log(\`Hello \${name}\`);
14
+ return \`Welcome, \${name}!\`;
15
+ }`;
16
+ export default function ThemeSettingsScreen({ onBack, inlineMode = false, }) {
17
+ const { themeType, setThemeType } = useTheme();
18
+ const { t } = useI18n();
19
+ // Use themeType from context which is already loaded from config
20
+ const [selectedTheme, setSelectedTheme] = useState(themeType);
21
+ const [infoText, setInfoText] = useState('');
22
+ const themeOptions = useMemo(() => [
23
+ {
24
+ label: selectedTheme === 'dark' ? `✓ ${t.themeSettings.darkTheme}` : t.themeSettings.darkTheme,
25
+ value: 'dark',
26
+ infoText: t.themeSettings.darkThemeInfo,
27
+ },
28
+ {
29
+ label: selectedTheme === 'light' ? `✓ ${t.themeSettings.lightTheme}` : t.themeSettings.lightTheme,
30
+ value: 'light',
31
+ infoText: t.themeSettings.lightThemeInfo,
32
+ },
33
+ {
34
+ label: selectedTheme === 'github-dark' ? `✓ ${t.themeSettings.githubDark}` : t.themeSettings.githubDark,
35
+ value: 'github-dark',
36
+ infoText: t.themeSettings.githubDarkInfo,
37
+ },
38
+ {
39
+ label: selectedTheme === 'rainbow' ? `✓ ${t.themeSettings.rainbow}` : t.themeSettings.rainbow,
40
+ value: 'rainbow',
41
+ infoText: t.themeSettings.rainbowInfo,
42
+ },
43
+ {
44
+ label: selectedTheme === 'solarized-dark' ? `✓ ${t.themeSettings.solarizedDark}` : t.themeSettings.solarizedDark,
45
+ value: 'solarized-dark',
46
+ infoText: t.themeSettings.solarizedDarkInfo,
47
+ },
48
+ {
49
+ label: selectedTheme === 'nord' ? `✓ ${t.themeSettings.nord}` : t.themeSettings.nord,
50
+ value: 'nord',
51
+ infoText: t.themeSettings.nordInfo,
52
+ },
53
+ {
54
+ label: t.themeSettings.back,
55
+ value: 'back',
56
+ color: 'gray',
57
+ infoText: t.themeSettings.backInfo,
58
+ },
59
+ ], [selectedTheme, t]);
60
+ const handleSelect = useCallback((value) => {
61
+ if (value === 'back') {
62
+ // Restore original theme if cancelled
63
+ setThemeType(selectedTheme);
64
+ onBack();
65
+ }
66
+ else {
67
+ // Confirm and apply the theme (Enter pressed)
68
+ const newTheme = value;
69
+ setSelectedTheme(newTheme);
70
+ setThemeType(newTheme);
71
+ }
72
+ }, [onBack, setThemeType, selectedTheme]);
73
+ const handleSelectionChange = useCallback((newInfoText, value) => {
74
+ setInfoText(newInfoText);
75
+ // Preview theme on selection change (navigation)
76
+ if (value === 'back') {
77
+ // Restore to selected theme when hovering on "Back"
78
+ setThemeType(selectedTheme);
79
+ }
80
+ else {
81
+ // Preview the theme
82
+ setThemeType(value);
83
+ }
84
+ }, [setThemeType, selectedTheme]);
85
+ useInput((_input, key) => {
86
+ if (key.escape) {
87
+ // Restore original theme on ESC
88
+ setThemeType(selectedTheme);
89
+ onBack();
90
+ }
91
+ });
92
+ return (React.createElement(Box, { flexDirection: "column" },
93
+ !inlineMode && (React.createElement(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1 },
94
+ React.createElement(Text, { bold: true, color: "cyan" }, t.themeSettings.title))),
95
+ React.createElement(Box, { flexDirection: "column", paddingX: 1 },
96
+ React.createElement(Text, { color: "gray", dimColor: true },
97
+ t.themeSettings.current,
98
+ " ",
99
+ themeOptions.find(opt => opt.value === selectedTheme)?.label.replace('✓ ', '') || selectedTheme)),
100
+ React.createElement(Menu, { options: themeOptions, onSelect: handleSelect, onSelectionChange: handleSelectionChange }),
101
+ React.createElement(Box, { flexDirection: "column", paddingX: 1 },
102
+ React.createElement(Text, { color: "gray", dimColor: true }, t.themeSettings.preview),
103
+ React.createElement(DiffViewer, { oldContent: sampleOldCode, newContent: sampleNewCode, filename: "example.ts" })),
104
+ infoText && (React.createElement(Box, { paddingX: 1 },
105
+ React.createElement(Alert, { variant: "info" }, infoText)))));
106
+ }
@@ -14,6 +14,7 @@ import CodeBaseConfigScreen from './CodeBaseConfigScreen.js';
14
14
  import SystemPromptConfigScreen from './SystemPromptConfigScreen.js';
15
15
  import CustomHeadersScreen from './CustomHeadersScreen.js';
16
16
  import LanguageSettingsScreen from './LanguageSettingsScreen.js';
17
+ import ThemeSettingsScreen from './ThemeSettingsScreen.js';
17
18
  import { useI18n } from '../../i18n/index.js';
18
19
  export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
19
20
  const { t } = useI18n();
@@ -75,6 +76,11 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
75
76
  value: 'language',
76
77
  infoText: t.welcome.languageSettingsInfo,
77
78
  },
79
+ {
80
+ label: t.welcome.themeSettings,
81
+ value: 'theme',
82
+ infoText: t.welcome.themeSettingsInfo,
83
+ },
78
84
  {
79
85
  label: t.welcome.exit,
80
86
  value: 'exit',
@@ -112,6 +118,9 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
112
118
  else if (value === 'language') {
113
119
  setInlineView('language-settings');
114
120
  }
121
+ else if (value === 'theme') {
122
+ setInlineView('theme-settings');
123
+ }
115
124
  else {
116
125
  // Pass through to parent for other actions (chat, exit, etc.)
117
126
  onMenuSelect?.(value);
@@ -185,5 +194,6 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
185
194
  inlineView === 'customheaders' && (React.createElement(Box, { paddingX: 1 },
186
195
  React.createElement(CustomHeadersScreen, { onBack: handleBackToMenu }))),
187
196
  inlineView === 'language-settings' && (React.createElement(Box, { paddingX: 1 },
188
- React.createElement(LanguageSettingsScreen, { onBack: handleBackToMenu, inlineMode: true })))));
197
+ React.createElement(LanguageSettingsScreen, { onBack: handleBackToMenu, inlineMode: true }))),
198
+ inlineView === 'theme-settings' && (React.createElement(ThemeSettingsScreen, { onBack: handleBackToMenu, inlineMode: true }))));
189
199
  }
@@ -0,0 +1,23 @@
1
+ export type ThemeType = 'dark' | 'light' | 'github-dark' | 'rainbow' | 'solarized-dark' | 'nord';
2
+ export interface Theme {
3
+ name: string;
4
+ type: ThemeType;
5
+ colors: {
6
+ background: string;
7
+ text: string;
8
+ border: string;
9
+ diffAdded: string;
10
+ diffRemoved: string;
11
+ diffModified: string;
12
+ lineNumber: string;
13
+ lineNumberBorder: string;
14
+ menuSelected: string;
15
+ menuNormal: string;
16
+ menuInfo: string;
17
+ menuSecondary: string;
18
+ error: string;
19
+ warning: string;
20
+ success: string;
21
+ };
22
+ }
23
+ export declare const themes: Record<ThemeType, Theme>;
@@ -0,0 +1,140 @@
1
+ export const themes = {
2
+ dark: {
3
+ name: 'Dark',
4
+ type: 'dark',
5
+ colors: {
6
+ background: '#1e1e1e',
7
+ text: '#d4d4d4',
8
+ border: '#3e3e3e',
9
+ diffAdded: '#0d4d3d',
10
+ diffRemoved: '#5a1f1f',
11
+ diffModified: '#dcdcaa',
12
+ lineNumber: '#858585',
13
+ lineNumberBorder: '#3e3e3e',
14
+ // Menu colors
15
+ menuSelected: 'green',
16
+ menuNormal: 'white',
17
+ menuInfo: 'cyan',
18
+ menuSecondary: 'gray',
19
+ // Status colors
20
+ error: 'red',
21
+ warning: 'yellow',
22
+ success: 'green',
23
+ },
24
+ },
25
+ light: {
26
+ name: 'Light',
27
+ type: 'light',
28
+ colors: {
29
+ background: '#ffffff',
30
+ text: '#ffffff',
31
+ border: '#e0e0e0',
32
+ diffAdded: '#006400',
33
+ diffRemoved: '#8B0000',
34
+ diffModified: '#0000ff',
35
+ lineNumber: '#6e6e6e',
36
+ lineNumberBorder: '#e0e0e0',
37
+ // Menu colors - darker for better visibility
38
+ menuSelected: '#006400',
39
+ menuNormal: '#000000',
40
+ menuInfo: '#0066cc',
41
+ menuSecondary: '#666666',
42
+ // Status colors - darker for better visibility on white background
43
+ error: '#cc0000',
44
+ warning: '#cc6600',
45
+ success: '#006400',
46
+ },
47
+ },
48
+ 'github-dark': {
49
+ name: 'GitHub Dark',
50
+ type: 'github-dark',
51
+ colors: {
52
+ background: '#0d1117',
53
+ text: '#c9d1d9',
54
+ border: '#30363d',
55
+ diffAdded: '#1a4d2e',
56
+ diffRemoved: '#6e1a1a',
57
+ diffModified: '#9e6a03',
58
+ lineNumber: '#6e7681',
59
+ lineNumberBorder: '#21262d',
60
+ // Menu colors
61
+ menuSelected: '#58a6ff',
62
+ menuNormal: '#c9d1d9',
63
+ menuInfo: '#58a6ff',
64
+ menuSecondary: '#8b949e',
65
+ // Status colors
66
+ error: '#f85149',
67
+ warning: '#d29922',
68
+ success: '#3fb950',
69
+ },
70
+ },
71
+ rainbow: {
72
+ name: 'Rainbow',
73
+ type: 'rainbow',
74
+ colors: {
75
+ background: '#1a1a2e',
76
+ text: '#ffffff',
77
+ border: '#ff6b9d',
78
+ diffAdded: '#16697a',
79
+ diffRemoved: '#82204a',
80
+ diffModified: '#5f4b8b',
81
+ lineNumber: '#ffa07a',
82
+ lineNumberBorder: '#ff6b9d',
83
+ // Menu colors - vibrant rainbow colors
84
+ menuSelected: '#ff006e',
85
+ menuNormal: '#00f5ff',
86
+ menuInfo: '#ffbe0b',
87
+ menuSecondary: '#8338ec',
88
+ // Status colors - bright and colorful
89
+ error: '#ff006e',
90
+ warning: '#ffbe0b',
91
+ success: '#06ffa5',
92
+ },
93
+ },
94
+ 'solarized-dark': {
95
+ name: 'Solarized Dark',
96
+ type: 'solarized-dark',
97
+ colors: {
98
+ background: '#002b36',
99
+ text: '#839496',
100
+ border: '#073642',
101
+ diffAdded: '#0a3d2c',
102
+ diffRemoved: '#5c1f1f',
103
+ diffModified: '#5d4f1a',
104
+ lineNumber: '#586e75',
105
+ lineNumberBorder: '#073642',
106
+ // Menu colors
107
+ menuSelected: '#2aa198',
108
+ menuNormal: '#93a1a1',
109
+ menuInfo: '#268bd2',
110
+ menuSecondary: '#657b83',
111
+ // Status colors
112
+ error: '#dc322f',
113
+ warning: '#b58900',
114
+ success: '#859900',
115
+ },
116
+ },
117
+ nord: {
118
+ name: 'Nord',
119
+ type: 'nord',
120
+ colors: {
121
+ background: '#2e3440',
122
+ text: '#d8dee9',
123
+ border: '#3b4252',
124
+ diffAdded: '#1d3a2f',
125
+ diffRemoved: '#5c2a2a',
126
+ diffModified: '#5a4d2f',
127
+ lineNumber: '#4c566a',
128
+ lineNumberBorder: '#3b4252',
129
+ // Menu colors
130
+ menuSelected: '#88c0d0',
131
+ menuNormal: '#d8dee9',
132
+ menuInfo: '#81a1c1',
133
+ menuSecondary: '#616e88',
134
+ // Status colors
135
+ error: '#bf616a',
136
+ warning: '#ebcb8b',
137
+ success: '#a3be8c',
138
+ },
139
+ },
140
+ };
@@ -283,13 +283,21 @@ export function initializeProfiles() {
283
283
  migrateLegacyConfig();
284
284
  // Ensure the active profile exists and is loaded to config.json
285
285
  const activeProfile = getActiveProfileName();
286
- const profileConfig = loadProfile(activeProfile);
286
+ let profileConfig = loadProfile(activeProfile);
287
287
  if (profileConfig) {
288
288
  // Sync the active profile to config.json
289
289
  saveConfig(profileConfig);
290
290
  }
291
291
  else {
292
- // If active profile doesn't exist, switch to default
293
- switchProfile('default');
292
+ // If active profile doesn't exist, create it first
293
+ // This is especially important for first-time installations
294
+ const defaultConfig = loadConfig();
295
+ saveProfile(activeProfile, defaultConfig);
296
+ setActiveProfileName(activeProfile);
297
+ // Now load and sync the newly created profile
298
+ profileConfig = loadProfile(activeProfile);
299
+ if (profileConfig) {
300
+ saveConfig(profileConfig);
301
+ }
294
302
  }
295
303
  }
@@ -26,6 +26,12 @@ export declare class Logger {
26
26
  debug(message: string, meta?: any): void;
27
27
  log(level: LogLevel, message: string, meta?: any): void;
28
28
  }
29
- declare const defaultLogger: Logger;
30
- export default defaultLogger;
31
- export { defaultLogger as logger };
29
+ declare const logger: {
30
+ error(message: string, meta?: any): void;
31
+ warn(message: string, meta?: any): void;
32
+ info(message: string, meta?: any): void;
33
+ debug(message: string, meta?: any): void;
34
+ log(level: LogLevel, message: string, meta?: any): void;
35
+ };
36
+ export default logger;
37
+ export { logger };
@@ -92,6 +92,31 @@ export class Logger {
92
92
  this.writeLog(level, message, meta);
93
93
  }
94
94
  }
95
- const defaultLogger = new Logger();
96
- export default defaultLogger;
97
- export { defaultLogger as logger };
95
+ // Lazy initialization to avoid blocking startup
96
+ let _defaultLogger = null;
97
+ function getDefaultLogger() {
98
+ if (!_defaultLogger) {
99
+ _defaultLogger = new Logger();
100
+ }
101
+ return _defaultLogger;
102
+ }
103
+ // Create a proxy object that lazily initializes the logger
104
+ const logger = {
105
+ error(message, meta) {
106
+ getDefaultLogger().error(message, meta);
107
+ },
108
+ warn(message, meta) {
109
+ getDefaultLogger().warn(message, meta);
110
+ },
111
+ info(message, meta) {
112
+ getDefaultLogger().info(message, meta);
113
+ },
114
+ debug(message, meta) {
115
+ getDefaultLogger().debug(message, meta);
116
+ },
117
+ log(level, message, meta) {
118
+ getDefaultLogger().log(level, message, meta);
119
+ },
120
+ };
121
+ export default logger;
122
+ export { logger };
@@ -19,7 +19,7 @@ export interface MCPServiceTools {
19
19
  error?: string;
20
20
  }
21
21
  /**
22
- * Get the TODO service instance
22
+ * Get the TODO service instance (lazy initialization)
23
23
  */
24
24
  export declare function getTodoService(): TodoService;
25
25
  /**
@@ -19,15 +19,18 @@ import os from 'os';
19
19
  import path from 'path';
20
20
  let toolsCache = null;
21
21
  const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
22
- // Initialize TODO service with sessionManager accessor
23
- const todoService = new TodoService(path.join(os.homedir(), '.snow'), () => {
24
- const session = sessionManager.getCurrentSession();
25
- return session ? session.id : null;
26
- });
22
+ // Lazy initialization of TODO service to avoid circular dependencies
23
+ let todoService = null;
27
24
  /**
28
- * Get the TODO service instance
25
+ * Get the TODO service instance (lazy initialization)
29
26
  */
30
27
  export function getTodoService() {
28
+ if (!todoService) {
29
+ todoService = new TodoService(path.join(os.homedir(), '.snow'), () => {
30
+ const session = sessionManager.getCurrentSession();
31
+ return session ? session.id : null;
32
+ });
33
+ }
31
34
  return todoService;
32
35
  }
33
36
  /**
@@ -123,8 +126,9 @@ async function refreshToolsCache() {
123
126
  });
124
127
  }
125
128
  // Add built-in TODO tools (always available)
126
- await todoService.initialize();
127
- const todoTools = todoService.getTools();
129
+ const todoSvc = getTodoService(); // This will never return null after lazy init
130
+ await todoSvc.initialize();
131
+ const todoTools = todoSvc.getTools();
128
132
  const todoServiceTools = todoTools.map(tool => ({
129
133
  name: tool.name.replace('todo-', ''),
130
134
  description: tool.description || '',
@@ -689,7 +693,7 @@ export async function executeMCPTool(toolName, args, abortSignal, onTokenUpdate)
689
693
  }
690
694
  if (serviceName === 'todo') {
691
695
  // Handle built-in TODO tools (no connection needed)
692
- return await todoService.executeTool(actualToolName, args);
696
+ return await getTodoService().executeTool(actualToolName, args);
693
697
  }
694
698
  else if (serviceName === 'notebook') {
695
699
  // Handle built-in Notebook tools (no connection needed)
@@ -0,0 +1,21 @@
1
+ import type { ThemeType } from '../ui/themes/index.js';
2
+ interface ThemeConfig {
3
+ theme: ThemeType;
4
+ }
5
+ /**
6
+ * Load theme configuration from file system
7
+ */
8
+ export declare function loadThemeConfig(): ThemeConfig;
9
+ /**
10
+ * Save theme configuration to file system
11
+ */
12
+ export declare function saveThemeConfig(config: ThemeConfig): void;
13
+ /**
14
+ * Get current theme setting
15
+ */
16
+ export declare function getCurrentTheme(): ThemeType;
17
+ /**
18
+ * Set theme and persist to file system
19
+ */
20
+ export declare function setCurrentTheme(theme: ThemeType): void;
21
+ export {};
@@ -0,0 +1,61 @@
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
4
+ const CONFIG_DIR = join(homedir(), '.snow');
5
+ const THEME_CONFIG_FILE = join(CONFIG_DIR, 'theme.json');
6
+ const DEFAULT_CONFIG = {
7
+ theme: 'dark',
8
+ };
9
+ function ensureConfigDirectory() {
10
+ if (!existsSync(CONFIG_DIR)) {
11
+ mkdirSync(CONFIG_DIR, { recursive: true });
12
+ }
13
+ }
14
+ /**
15
+ * Load theme configuration from file system
16
+ */
17
+ export function loadThemeConfig() {
18
+ ensureConfigDirectory();
19
+ if (!existsSync(THEME_CONFIG_FILE)) {
20
+ saveThemeConfig(DEFAULT_CONFIG);
21
+ return DEFAULT_CONFIG;
22
+ }
23
+ try {
24
+ const configData = readFileSync(THEME_CONFIG_FILE, 'utf-8');
25
+ const config = JSON.parse(configData);
26
+ return {
27
+ ...DEFAULT_CONFIG,
28
+ ...config,
29
+ };
30
+ }
31
+ catch (error) {
32
+ // If config file is corrupted, return default config
33
+ return DEFAULT_CONFIG;
34
+ }
35
+ }
36
+ /**
37
+ * Save theme configuration to file system
38
+ */
39
+ export function saveThemeConfig(config) {
40
+ ensureConfigDirectory();
41
+ try {
42
+ const configData = JSON.stringify(config, null, 2);
43
+ writeFileSync(THEME_CONFIG_FILE, configData, 'utf-8');
44
+ }
45
+ catch (error) {
46
+ console.error('Failed to save theme config:', error);
47
+ }
48
+ }
49
+ /**
50
+ * Get current theme setting
51
+ */
52
+ export function getCurrentTheme() {
53
+ const config = loadThemeConfig();
54
+ return config.theme;
55
+ }
56
+ /**
57
+ * Set theme and persist to file system
58
+ */
59
+ export function setCurrentTheme(theme) {
60
+ saveThemeConfig({ theme });
61
+ }