snow-ai 0.1.12 → 0.2.2

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 (97) hide show
  1. package/dist/api/chat.d.ts +65 -2
  2. package/dist/api/chat.js +299 -16
  3. package/dist/api/responses.d.ts +52 -0
  4. package/dist/api/responses.js +541 -0
  5. package/dist/api/systemPrompt.d.ts +4 -0
  6. package/dist/api/systemPrompt.js +43 -0
  7. package/dist/app.js +15 -4
  8. package/dist/cli.js +38 -2
  9. package/dist/hooks/useConversation.d.ts +32 -0
  10. package/dist/hooks/useConversation.js +403 -0
  11. package/dist/hooks/useGlobalNavigation.d.ts +6 -0
  12. package/dist/hooks/useGlobalNavigation.js +15 -0
  13. package/dist/hooks/useSessionManagement.d.ts +10 -0
  14. package/dist/hooks/useSessionManagement.js +43 -0
  15. package/dist/hooks/useSessionSave.d.ts +8 -0
  16. package/dist/hooks/useSessionSave.js +52 -0
  17. package/dist/hooks/useToolConfirmation.d.ts +18 -0
  18. package/dist/hooks/useToolConfirmation.js +49 -0
  19. package/dist/mcp/bash.d.ts +57 -0
  20. package/dist/mcp/bash.js +138 -0
  21. package/dist/mcp/filesystem.d.ts +307 -0
  22. package/dist/mcp/filesystem.js +520 -0
  23. package/dist/mcp/todo.d.ts +55 -0
  24. package/dist/mcp/todo.js +329 -0
  25. package/dist/test/logger-test.d.ts +1 -0
  26. package/dist/test/logger-test.js +7 -0
  27. package/dist/types/index.d.ts +1 -1
  28. package/dist/ui/components/ChatInput.d.ts +15 -2
  29. package/dist/ui/components/ChatInput.js +445 -59
  30. package/dist/ui/components/CommandPanel.d.ts +2 -2
  31. package/dist/ui/components/CommandPanel.js +11 -7
  32. package/dist/ui/components/DiffViewer.d.ts +9 -0
  33. package/dist/ui/components/DiffViewer.js +93 -0
  34. package/dist/ui/components/FileList.d.ts +14 -0
  35. package/dist/ui/components/FileList.js +131 -0
  36. package/dist/ui/components/MCPInfoPanel.d.ts +2 -0
  37. package/dist/ui/components/MCPInfoPanel.js +74 -0
  38. package/dist/ui/components/MCPInfoScreen.d.ts +7 -0
  39. package/dist/ui/components/MCPInfoScreen.js +27 -0
  40. package/dist/ui/components/MarkdownRenderer.d.ts +7 -0
  41. package/dist/ui/components/MarkdownRenderer.js +110 -0
  42. package/dist/ui/components/Menu.d.ts +5 -2
  43. package/dist/ui/components/Menu.js +60 -9
  44. package/dist/ui/components/MessageList.d.ts +30 -2
  45. package/dist/ui/components/MessageList.js +64 -12
  46. package/dist/ui/components/PendingMessages.js +1 -1
  47. package/dist/ui/components/ScrollableSelectInput.d.ts +29 -0
  48. package/dist/ui/components/ScrollableSelectInput.js +157 -0
  49. package/dist/ui/components/SessionListScreen.d.ts +7 -0
  50. package/dist/ui/components/SessionListScreen.js +196 -0
  51. package/dist/ui/components/SessionListScreenWrapper.d.ts +7 -0
  52. package/dist/ui/components/SessionListScreenWrapper.js +14 -0
  53. package/dist/ui/components/TodoTree.d.ts +15 -0
  54. package/dist/ui/components/TodoTree.js +60 -0
  55. package/dist/ui/components/ToolConfirmation.d.ts +8 -0
  56. package/dist/ui/components/ToolConfirmation.js +38 -0
  57. package/dist/ui/components/ToolResultPreview.d.ts +12 -0
  58. package/dist/ui/components/ToolResultPreview.js +115 -0
  59. package/dist/ui/pages/ChatScreen.d.ts +4 -0
  60. package/dist/ui/pages/ChatScreen.js +385 -196
  61. package/dist/ui/pages/MCPConfigScreen.d.ts +6 -0
  62. package/dist/ui/pages/MCPConfigScreen.js +55 -0
  63. package/dist/ui/pages/ModelConfigScreen.js +73 -12
  64. package/dist/ui/pages/WelcomeScreen.js +17 -11
  65. package/dist/utils/apiConfig.d.ts +12 -0
  66. package/dist/utils/apiConfig.js +95 -9
  67. package/dist/utils/commandExecutor.d.ts +2 -1
  68. package/dist/utils/commands/init.d.ts +2 -0
  69. package/dist/utils/commands/init.js +93 -0
  70. package/dist/utils/commands/mcp.d.ts +2 -0
  71. package/dist/utils/commands/mcp.js +12 -0
  72. package/dist/utils/commands/resume.d.ts +2 -0
  73. package/dist/utils/commands/resume.js +12 -0
  74. package/dist/utils/commands/yolo.d.ts +2 -0
  75. package/dist/utils/commands/yolo.js +12 -0
  76. package/dist/utils/fileUtils.d.ts +44 -0
  77. package/dist/utils/fileUtils.js +222 -0
  78. package/dist/utils/index.d.ts +4 -0
  79. package/dist/utils/index.js +6 -0
  80. package/dist/utils/logger.d.ts +31 -0
  81. package/dist/utils/logger.js +97 -0
  82. package/dist/utils/mcpToolsManager.d.ts +47 -0
  83. package/dist/utils/mcpToolsManager.js +476 -0
  84. package/dist/utils/messageFormatter.d.ts +12 -0
  85. package/dist/utils/messageFormatter.js +32 -0
  86. package/dist/utils/sessionConverter.d.ts +6 -0
  87. package/dist/utils/sessionConverter.js +61 -0
  88. package/dist/utils/sessionManager.d.ts +39 -0
  89. package/dist/utils/sessionManager.js +141 -0
  90. package/dist/utils/textBuffer.d.ts +36 -7
  91. package/dist/utils/textBuffer.js +265 -179
  92. package/dist/utils/todoPreprocessor.d.ts +5 -0
  93. package/dist/utils/todoPreprocessor.js +19 -0
  94. package/dist/utils/toolExecutor.d.ts +21 -0
  95. package/dist/utils/toolExecutor.js +28 -0
  96. package/package.json +12 -3
  97. package/readme.md +2 -2
@@ -0,0 +1,6 @@
1
+ type Props = {
2
+ onBack: () => void;
3
+ onSave: () => void;
4
+ };
5
+ export default function MCPConfigScreen({ onBack }: Props): null;
6
+ export {};
@@ -0,0 +1,55 @@
1
+ import { useEffect } from 'react';
2
+ import { useApp } from 'ink';
3
+ import { spawn } from 'child_process';
4
+ import { writeFileSync, readFileSync, existsSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { homedir, platform } from 'os';
7
+ import { getMCPConfig, validateMCPConfig, } from '../../utils/apiConfig.js';
8
+ const CONFIG_DIR = join(homedir(), '.snow');
9
+ const MCP_CONFIG_FILE = join(CONFIG_DIR, 'mcp-config.json');
10
+ function getSystemEditor() {
11
+ if (platform() === 'win32') {
12
+ return 'notepad';
13
+ }
14
+ return process.env['EDITOR'] || 'vim';
15
+ }
16
+ export default function MCPConfigScreen({ onBack }) {
17
+ const { exit } = useApp();
18
+ useEffect(() => {
19
+ const openEditor = async () => {
20
+ const config = getMCPConfig();
21
+ writeFileSync(MCP_CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
22
+ const editor = getSystemEditor();
23
+ exit();
24
+ const child = spawn(editor, [MCP_CONFIG_FILE], {
25
+ stdio: 'inherit'
26
+ });
27
+ child.on('close', () => {
28
+ // 读取编辑后的配置
29
+ if (existsSync(MCP_CONFIG_FILE)) {
30
+ try {
31
+ const editedContent = readFileSync(MCP_CONFIG_FILE, 'utf8');
32
+ const parsedConfig = JSON.parse(editedContent);
33
+ const validationErrors = validateMCPConfig(parsedConfig);
34
+ if (validationErrors.length === 0) {
35
+ console.log('MCP configuration saved successfully ! Please use `snow` restart!');
36
+ }
37
+ else {
38
+ console.error('Configuration errors:', validationErrors.join(', '));
39
+ }
40
+ }
41
+ catch (parseError) {
42
+ console.error('Invalid JSON format');
43
+ }
44
+ }
45
+ process.exit(0);
46
+ });
47
+ child.on('error', (error) => {
48
+ console.error('Failed to open editor:', error.message);
49
+ process.exit(1);
50
+ });
51
+ };
52
+ openEditor();
53
+ }, [exit, onBack]);
54
+ return null;
55
+ }
@@ -12,9 +12,10 @@ export default function ModelConfigScreen({ onBack, onSave }) {
12
12
  const [isEditing, setIsEditing] = useState(false);
13
13
  const [models, setModels] = useState([]);
14
14
  const [loading, setLoading] = useState(false);
15
- const [error, setError] = useState('');
16
15
  const [baseUrlMissing, setBaseUrlMissing] = useState(false);
17
16
  const [searchTerm, setSearchTerm] = useState('');
17
+ const [manualInputMode, setManualInputMode] = useState(false);
18
+ const [manualInputValue, setManualInputValue] = useState('');
18
19
  useEffect(() => {
19
20
  const config = getOpenAiConfig();
20
21
  setAdvancedModel(config.advancedModel || '');
@@ -27,13 +28,13 @@ export default function ModelConfigScreen({ onBack, onSave }) {
27
28
  }, []);
28
29
  const loadModels = async () => {
29
30
  setLoading(true);
30
- setError('');
31
31
  try {
32
32
  const fetchedModels = await fetchAvailableModels();
33
33
  setModels(fetchedModels);
34
34
  }
35
35
  catch (err) {
36
- setError(err instanceof Error ? err.message : 'Failed to load models');
36
+ // 加载失败时抛出错误,由调用方处理
37
+ throw err;
37
38
  }
38
39
  finally {
39
40
  setLoading(false);
@@ -41,10 +42,15 @@ export default function ModelConfigScreen({ onBack, onSave }) {
41
42
  };
42
43
  const getCurrentOptions = () => {
43
44
  const filteredModels = filterModels(models, searchTerm);
44
- return filteredModels.map(model => ({
45
+ const modelOptions = filteredModels.map(model => ({
45
46
  label: model.id,
46
47
  value: model.id,
47
48
  }));
49
+ // 添加手动输入选项
50
+ return [
51
+ { label: 'Manual Input (Enter model name)', value: '__MANUAL_INPUT__' },
52
+ ...modelOptions,
53
+ ];
48
54
  };
49
55
  const getCurrentValue = () => {
50
56
  if (currentField === 'advancedModel')
@@ -54,6 +60,12 @@ export default function ModelConfigScreen({ onBack, onSave }) {
54
60
  return maxContextTokens.toString();
55
61
  };
56
62
  const handleModelChange = (value) => {
63
+ // 如果选择了手动输入选项
64
+ if (value === '__MANUAL_INPUT__') {
65
+ setManualInputMode(true);
66
+ setManualInputValue('');
67
+ return;
68
+ }
57
69
  if (currentField === 'advancedModel') {
58
70
  setAdvancedModel(value);
59
71
  }
@@ -76,6 +88,36 @@ export default function ModelConfigScreen({ onBack, onSave }) {
76
88
  }
77
89
  return;
78
90
  }
91
+ // 处理手动输入模式
92
+ if (manualInputMode) {
93
+ if (key.return) {
94
+ // 确认输入
95
+ if (manualInputValue.trim()) {
96
+ if (currentField === 'advancedModel') {
97
+ setAdvancedModel(manualInputValue.trim());
98
+ }
99
+ else if (currentField === 'basicModel') {
100
+ setBasicModel(manualInputValue.trim());
101
+ }
102
+ }
103
+ setManualInputMode(false);
104
+ setManualInputValue('');
105
+ setIsEditing(false);
106
+ setSearchTerm('');
107
+ }
108
+ else if (key.escape) {
109
+ // 取消输入
110
+ setManualInputMode(false);
111
+ setManualInputValue('');
112
+ }
113
+ else if (key.backspace || key.delete) {
114
+ setManualInputValue(prev => prev.slice(0, -1));
115
+ }
116
+ else if (input && input.match(/[a-zA-Z0-9-_./:]/)) {
117
+ setManualInputValue(prev => prev + input);
118
+ }
119
+ return;
120
+ }
79
121
  // Don't handle input when Select component is active
80
122
  if (isEditing) {
81
123
  if (currentField === 'maxContextTokens') {
@@ -143,9 +185,20 @@ export default function ModelConfigScreen({ onBack, onSave }) {
143
185
  else {
144
186
  loadModels().then(() => {
145
187
  setIsEditing(true);
188
+ }).catch(() => {
189
+ // 如果加载模型失败,直接进入手动输入模式
190
+ setManualInputMode(true);
191
+ setManualInputValue(getCurrentValue());
146
192
  });
147
193
  }
148
194
  }
195
+ else if (input === 'm') {
196
+ // 快捷键:按 'm' 直接进入手动输入模式
197
+ if (currentField !== 'maxContextTokens') {
198
+ setManualInputMode(true);
199
+ setManualInputValue(getCurrentValue());
200
+ }
201
+ }
149
202
  else if (key.upArrow) {
150
203
  if (currentField === 'basicModel') {
151
204
  setCurrentField('advancedModel');
@@ -181,16 +234,24 @@ export default function ModelConfigScreen({ onBack, onSave }) {
181
234
  React.createElement(Gradient, { name: "rainbow" }, "Model Configuration"),
182
235
  React.createElement(Text, { color: "gray", dimColor: true }, "Loading available models...")))));
183
236
  }
184
- if (error) {
237
+ // 手动输入模式的界面
238
+ if (manualInputMode) {
185
239
  return (React.createElement(Box, { flexDirection: "column", padding: 1 },
186
- React.createElement(Box, { marginBottom: 2, borderStyle: "double", borderColor: "red", paddingX: 2, paddingY: 1 },
240
+ React.createElement(Box, { marginBottom: 2, borderStyle: "double", borderColor: "cyan", paddingX: 2, paddingY: 1 },
187
241
  React.createElement(Box, { flexDirection: "column" },
188
- React.createElement(Gradient, { name: 'rainbow' }, "Model Configuration"),
189
- React.createElement(Text, { color: "gray", dimColor: true }, "Configure AI models for different tasks"))),
190
- React.createElement(Box, { marginBottom: 2 },
191
- React.createElement(Alert, { variant: "error" }, error)),
242
+ React.createElement(Gradient, { name: 'rainbow' }, "Manual Input Model"),
243
+ React.createElement(Text, { color: "gray", dimColor: true }, "Enter model name manually"))),
244
+ React.createElement(Box, { flexDirection: "column", marginBottom: 2 },
245
+ React.createElement(Text, { color: "cyan" },
246
+ currentField === 'advancedModel' ? 'Advanced Model' : 'Basic Model',
247
+ ":"),
248
+ React.createElement(Box, { marginLeft: 2, marginTop: 1 },
249
+ React.createElement(Text, { color: "green" },
250
+ `> ${manualInputValue}`,
251
+ React.createElement(Text, { color: "white" }, "_")))),
192
252
  React.createElement(Box, { flexDirection: "column" },
193
- React.createElement(Alert, { variant: "info" }, "Press Esc to return to main menu"))));
253
+ React.createElement(Alert, { variant: "info" }, "Type model name (e.g., gpt-4o, claude-3-5-sonnet-20241022)"),
254
+ React.createElement(Alert, { variant: "info" }, "Press Enter to confirm, Esc to cancel"))));
194
255
  }
195
256
  return (React.createElement(Box, { flexDirection: "column", padding: 1 },
196
257
  React.createElement(Box, { marginBottom: 2, borderStyle: "double", borderColor: "cyan", paddingX: 2, paddingY: 1 },
@@ -235,5 +296,5 @@ export default function ModelConfigScreen({ onBack, onSave }) {
235
296
  React.createElement(Text, { color: "gray" }, maxContextTokens)))))),
236
297
  React.createElement(Box, { flexDirection: "column" }, isEditing ? (React.createElement(React.Fragment, null,
237
298
  React.createElement(Alert, { variant: "info" }, "Editing mode: Type to filter models, \u2191\u2193 to select, Enter to confirm"))) : (React.createElement(React.Fragment, null,
238
- React.createElement(Alert, { variant: "info" }, "Use \u2191\u2193 to navigate, Enter to edit, Ctrl+S or Esc to save"))))));
299
+ React.createElement(Alert, { variant: "info" }, "Use \u2191\u2193 to navigate, Enter to edit, M for manual input, Ctrl+S or Esc to save"))))));
239
300
  }
@@ -1,15 +1,16 @@
1
- import React, { useState } from 'react';
1
+ import React, { useState, useMemo, useCallback } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import { Alert } from '@inkjs/ui';
4
4
  import Gradient from 'ink-gradient';
5
5
  import Menu from '../components/Menu.js';
6
6
  export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
7
7
  const [infoText, setInfoText] = useState('Start a new chat conversation');
8
- const menuOptions = [
8
+ const menuOptions = useMemo(() => [
9
9
  {
10
10
  label: 'Start',
11
11
  value: 'chat',
12
- infoText: 'Start a new chat conversation'
12
+ infoText: 'Start a new chat conversation',
13
+ clearTerminal: true
13
14
  },
14
15
  {
15
16
  label: 'API Settings',
@@ -21,27 +22,32 @@ export default function WelcomeScreen({ version = '1.0.0', onMenuSelect, }) {
21
22
  value: 'models',
22
23
  infoText: 'Configure AI models for different tasks',
23
24
  },
25
+ {
26
+ label: 'MCP Settings',
27
+ value: 'mcp',
28
+ infoText: 'Configure Model Context Protocol servers',
29
+ },
24
30
  {
25
31
  label: 'Exit',
26
32
  value: 'exit',
27
33
  color: 'rgb(232, 131, 136)',
28
34
  infoText: 'Exit the application',
29
35
  }
30
- ];
31
- const handleSelectionChange = (newInfoText) => {
36
+ ], []);
37
+ const handleSelectionChange = useCallback((newInfoText) => {
32
38
  setInfoText(newInfoText);
33
- };
39
+ }, []);
34
40
  return (React.createElement(Box, { flexDirection: "column", padding: 1 },
35
- React.createElement(Box, { marginBottom: 2, borderStyle: "double", paddingX: 2, paddingY: 1, borderColor: 'cyan' },
41
+ React.createElement(Box, { borderStyle: "double", paddingX: 1, paddingY: 1, borderColor: 'cyan' },
36
42
  React.createElement(Box, { flexDirection: "column" },
37
- React.createElement(Gradient, { name: "rainbow" },
38
- React.createElement(Text, { bold: true }, "A I B O T P R O")),
39
- React.createElement(Text, { color: "blue" }, "C L I"),
43
+ React.createElement(Text, { color: "white", bold: true },
44
+ React.createElement(Text, { color: "cyan" }, "\u2746 "),
45
+ React.createElement(Gradient, { name: "rainbow" }, "SNOW AI CLI")),
40
46
  React.createElement(Text, { color: "gray", dimColor: true }, "Intelligent Command Line Assistant"),
41
47
  React.createElement(Text, { color: "magenta", dimColor: true },
42
48
  "Version ",
43
49
  version))),
44
- onMenuSelect && (React.createElement(Box, { marginBottom: 0 },
50
+ onMenuSelect && (React.createElement(Box, null,
45
51
  React.createElement(Menu, { options: menuOptions, onSelect: onMenuSelect, onSelectionChange: handleSelectionChange }))),
46
52
  React.createElement(Box, { justifyContent: "space-between" },
47
53
  React.createElement(Alert, { variant: 'info' }, infoText))));
@@ -7,6 +7,15 @@ export interface ApiConfig {
7
7
  basicModel?: string;
8
8
  maxContextTokens?: number;
9
9
  }
10
+ export interface MCPServer {
11
+ url?: string;
12
+ command?: string;
13
+ args?: string[];
14
+ env?: Record<string, string>;
15
+ }
16
+ export interface MCPConfig {
17
+ mcpServers: Record<string, MCPServer>;
18
+ }
10
19
  export interface AppConfig {
11
20
  openai: ApiConfig;
12
21
  }
@@ -15,3 +24,6 @@ export declare function saveConfig(config: AppConfig): void;
15
24
  export declare function updateOpenAiConfig(apiConfig: Partial<ApiConfig>): void;
16
25
  export declare function getOpenAiConfig(): ApiConfig;
17
26
  export declare function validateApiConfig(config: Partial<ApiConfig>): string[];
27
+ export declare function updateMCPConfig(mcpConfig: MCPConfig): void;
28
+ export declare function getMCPConfig(): MCPConfig;
29
+ export declare function validateMCPConfig(config: Partial<MCPConfig>): string[];
@@ -11,13 +11,31 @@ const DEFAULT_CONFIG = {
11
11
  maxContextTokens: 4000,
12
12
  },
13
13
  };
14
- const CONFIG_DIR = join(homedir(), '.aibotpro');
14
+ const DEFAULT_MCP_CONFIG = {
15
+ mcpServers: {},
16
+ };
17
+ const CONFIG_DIR = join(homedir(), '.snow');
18
+ function normalizeRequestMethod(method) {
19
+ if (method === 'chat' || method === 'responses') {
20
+ return method;
21
+ }
22
+ if (method === 'completions') {
23
+ return 'chat';
24
+ }
25
+ return DEFAULT_CONFIG.openai.requestMethod;
26
+ }
15
27
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
28
+ const MCP_CONFIG_FILE = join(CONFIG_DIR, 'mcp-config.json');
16
29
  function ensureConfigDirectory() {
17
30
  if (!existsSync(CONFIG_DIR)) {
18
31
  mkdirSync(CONFIG_DIR, { recursive: true });
19
32
  }
20
33
  }
34
+ function cloneDefaultMCPConfig() {
35
+ return {
36
+ mcpServers: { ...DEFAULT_MCP_CONFIG.mcpServers },
37
+ };
38
+ }
21
39
  export function loadConfig() {
22
40
  ensureConfigDirectory();
23
41
  if (!existsSync(CONFIG_FILE)) {
@@ -26,17 +44,23 @@ export function loadConfig() {
26
44
  }
27
45
  try {
28
46
  const configData = readFileSync(CONFIG_FILE, 'utf8');
29
- const config = JSON.parse(configData);
30
- // Ensure backward compatibility by adding default requestMethod if missing
47
+ const parsedConfig = JSON.parse(configData);
48
+ const { mcp: legacyMcp, ...restConfig } = parsedConfig;
49
+ const configWithoutMcp = restConfig;
50
+ const mergedOpenai = {
51
+ ...DEFAULT_CONFIG.openai,
52
+ ...configWithoutMcp.openai,
53
+ requestMethod: DEFAULT_CONFIG.openai.requestMethod,
54
+ };
55
+ mergedOpenai.requestMethod = normalizeRequestMethod(configWithoutMcp.openai?.requestMethod);
31
56
  const mergedConfig = {
32
57
  ...DEFAULT_CONFIG,
33
- ...config,
34
- openai: {
35
- ...DEFAULT_CONFIG.openai,
36
- ...config.openai,
37
- requestMethod: (config.openai?.requestMethod === 'completions' ? 'chat' : config.openai?.requestMethod) || DEFAULT_CONFIG.openai.requestMethod,
38
- },
58
+ ...configWithoutMcp,
59
+ openai: mergedOpenai,
39
60
  };
61
+ if (legacyMcp !== undefined) {
62
+ saveConfig(mergedConfig);
63
+ }
40
64
  return mergedConfig;
41
65
  }
42
66
  catch (error) {
@@ -84,3 +108,65 @@ function isValidUrl(url) {
84
108
  return false;
85
109
  }
86
110
  }
111
+ export function updateMCPConfig(mcpConfig) {
112
+ ensureConfigDirectory();
113
+ try {
114
+ const configData = JSON.stringify(mcpConfig, null, 2);
115
+ writeFileSync(MCP_CONFIG_FILE, configData, 'utf8');
116
+ }
117
+ catch (error) {
118
+ throw new Error(`Failed to save MCP configuration: ${error}`);
119
+ }
120
+ }
121
+ export function getMCPConfig() {
122
+ ensureConfigDirectory();
123
+ if (!existsSync(MCP_CONFIG_FILE)) {
124
+ const defaultMCPConfig = cloneDefaultMCPConfig();
125
+ updateMCPConfig(defaultMCPConfig);
126
+ return defaultMCPConfig;
127
+ }
128
+ try {
129
+ const configData = readFileSync(MCP_CONFIG_FILE, 'utf8');
130
+ const config = JSON.parse(configData);
131
+ return config;
132
+ }
133
+ catch (error) {
134
+ const defaultMCPConfig = cloneDefaultMCPConfig();
135
+ updateMCPConfig(defaultMCPConfig);
136
+ return defaultMCPConfig;
137
+ }
138
+ }
139
+ export function validateMCPConfig(config) {
140
+ const errors = [];
141
+ if (config.mcpServers) {
142
+ Object.entries(config.mcpServers).forEach(([name, server]) => {
143
+ if (!name.trim()) {
144
+ errors.push('Server name cannot be empty');
145
+ }
146
+ if (server.url && !isValidUrl(server.url)) {
147
+ const urlWithEnvReplaced = server.url.replace(/\$\{[^}]+\}|\$[A-Za-z_][A-Za-z0-9_]*/g, 'placeholder');
148
+ if (!isValidUrl(urlWithEnvReplaced)) {
149
+ errors.push(`Invalid URL format for server "${name}"`);
150
+ }
151
+ }
152
+ if (server.command && !server.command.trim()) {
153
+ errors.push(`Command cannot be empty for server "${name}"`);
154
+ }
155
+ if (!server.url && !server.command) {
156
+ errors.push(`Server "${name}" must have either a URL or command`);
157
+ }
158
+ // 验证环境变量格式
159
+ if (server.env) {
160
+ Object.entries(server.env).forEach(([envName, envValue]) => {
161
+ if (!envName.trim()) {
162
+ errors.push(`Environment variable name cannot be empty for server "${name}"`);
163
+ }
164
+ if (typeof envValue !== 'string') {
165
+ errors.push(`Environment variable "${envName}" must be a string for server "${name}"`);
166
+ }
167
+ });
168
+ }
169
+ });
170
+ }
171
+ return errors;
172
+ }
@@ -1,7 +1,8 @@
1
1
  export interface CommandResult {
2
2
  success: boolean;
3
3
  message?: string;
4
- action?: 'clear' | 'info';
4
+ action?: 'clear' | 'resume' | 'info' | 'showMcpInfo' | 'goHome' | 'toggleYolo' | 'initProject';
5
+ prompt?: string;
5
6
  }
6
7
  export interface CommandHandler {
7
8
  execute: () => Promise<CommandResult> | CommandResult;
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,93 @@
1
+ import { registerCommand } from '../commandExecutor.js';
2
+ // Init command handler - Triggers AI to analyze current project and generate SNOW.md
3
+ registerCommand('init', {
4
+ execute: () => {
5
+ return {
6
+ success: true,
7
+ action: 'initProject',
8
+ message: 'Starting project initialization...',
9
+ // Pass the optimized English prompt for AI
10
+ prompt: `You are an expert technical documentation specialist. Analyze the current project directory comprehensively and generate or update a SNOW.md file.
11
+
12
+ **Your tasks:**
13
+ 1. Use ALL available MCP tools (filesystem, terminal) to thoroughly explore the project structure
14
+ 2. Read key files: package.json, README.md, tsconfig.json, configuration files
15
+ 3. Identify the project type, technologies, frameworks, and architecture
16
+ 4. Examine the source code structure and main modules
17
+ 5. Check for existing documentation files
18
+ 6. Generate or update SNOW.md with the following structure:
19
+
20
+ # SNOW.md Structure
21
+
22
+ ## Project Name
23
+ Brief one-line description
24
+
25
+ ## Overview
26
+ 2-3 paragraph summary of what this project does and its purpose
27
+
28
+ ## Technology Stack
29
+ - Language/Runtime
30
+ - Framework(s)
31
+ - Key Dependencies
32
+ - Build Tools
33
+
34
+ ## Project Structure
35
+ \`\`\`
36
+ directory tree with explanations
37
+ \`\`\`
38
+
39
+ ## Key Features
40
+ - Feature 1
41
+ - Feature 2
42
+ - ...
43
+
44
+ ## Getting Started
45
+
46
+ ### Prerequisites
47
+ Required software/tools
48
+
49
+ ### Installation
50
+ \`\`\`bash
51
+ step by step commands
52
+ \`\`\`
53
+
54
+ ### Usage
55
+ Basic usage examples and commands
56
+
57
+ ## Development
58
+
59
+ ### Available Scripts
60
+ Describe npm scripts or make targets
61
+
62
+ ### Development Workflow
63
+ How to develop, test, and build
64
+
65
+ ## Configuration
66
+ Explain configuration files and environment variables
67
+
68
+ ## Architecture
69
+ High-level architecture overview (if complex project)
70
+
71
+ ## Contributing
72
+ Guidelines for contributors (if applicable)
73
+
74
+ ## License
75
+ License information (check package.json or LICENSE file)
76
+
77
+ ---
78
+
79
+ **Important instructions:**
80
+ - Use filesystem-list to explore directories recursively
81
+ - Use filesystem-read to read important files
82
+ - Use terminal-execute to run commands like 'npm run' to discover available scripts
83
+ - Be thorough but concise - focus on essential information
84
+ - If SNOW.md already exists, read it first and UPDATE it rather than replace
85
+ - Format with proper Markdown syntax
86
+ - After generating content, use filesystem-write or filesystem-create to save SNOW.md in the project root
87
+ - Confirm completion with a brief summary
88
+
89
+ Begin your analysis now. Use every tool at your disposal to understand this project completely.`
90
+ };
91
+ }
92
+ });
93
+ export default {};
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { registerCommand } from '../commandExecutor.js';
2
+ // MCP info command handler
3
+ registerCommand('mcp', {
4
+ execute: () => {
5
+ return {
6
+ success: true,
7
+ action: 'showMcpInfo',
8
+ message: 'Opening MCP services overview'
9
+ };
10
+ }
11
+ });
12
+ export default {};
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { registerCommand } from '../commandExecutor.js';
2
+ // Resume command handler
3
+ registerCommand('resume', {
4
+ execute: () => {
5
+ return {
6
+ success: true,
7
+ action: 'resume',
8
+ message: 'Opening session selection'
9
+ };
10
+ }
11
+ });
12
+ export default {};
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { registerCommand } from '../commandExecutor.js';
2
+ // YOLO command handler - toggles unattended mode
3
+ registerCommand('yolo', {
4
+ execute: () => {
5
+ return {
6
+ success: true,
7
+ action: 'toggleYolo',
8
+ message: 'Toggling YOLO mode'
9
+ };
10
+ }
11
+ });
12
+ export default {};
@@ -0,0 +1,44 @@
1
+ export interface SelectedFile {
2
+ path: string;
3
+ lineCount: number;
4
+ exists: boolean;
5
+ isImage?: boolean;
6
+ imageData?: string;
7
+ mimeType?: string;
8
+ }
9
+ /**
10
+ * Get line count for a file
11
+ */
12
+ export declare function getFileLineCount(filePath: string): Promise<number>;
13
+ /**
14
+ * Get file information including line count
15
+ */
16
+ export declare function getFileInfo(filePath: string): Promise<SelectedFile>;
17
+ /**
18
+ * Format file tree display for messages
19
+ */
20
+ export declare function formatFileTree(files: SelectedFile[]): string;
21
+ /**
22
+ * Parse @file references from message content and check if they exist
23
+ * Also supports direct file paths (pasted from VSCode drag & drop)
24
+ */
25
+ export declare function parseAndValidateFileReferences(content: string): Promise<{
26
+ cleanContent: string;
27
+ validFiles: SelectedFile[];
28
+ }>;
29
+ /**
30
+ * Create message with file read instructions for AI
31
+ */
32
+ export declare function createMessageWithFileInstructions(content: string, files: SelectedFile[], systemInfo?: {
33
+ platform: string;
34
+ shell: string;
35
+ workingDirectory: string;
36
+ }): string;
37
+ /**
38
+ * Get system information (OS, shell, working directory)
39
+ */
40
+ export declare function getSystemInfo(): {
41
+ platform: string;
42
+ shell: string;
43
+ workingDirectory: string;
44
+ };