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.
- package/dist/api/chat.d.ts +65 -2
- package/dist/api/chat.js +299 -16
- package/dist/api/responses.d.ts +52 -0
- package/dist/api/responses.js +541 -0
- package/dist/api/systemPrompt.d.ts +4 -0
- package/dist/api/systemPrompt.js +43 -0
- package/dist/app.js +15 -4
- package/dist/cli.js +38 -2
- package/dist/hooks/useConversation.d.ts +32 -0
- package/dist/hooks/useConversation.js +403 -0
- package/dist/hooks/useGlobalNavigation.d.ts +6 -0
- package/dist/hooks/useGlobalNavigation.js +15 -0
- package/dist/hooks/useSessionManagement.d.ts +10 -0
- package/dist/hooks/useSessionManagement.js +43 -0
- package/dist/hooks/useSessionSave.d.ts +8 -0
- package/dist/hooks/useSessionSave.js +52 -0
- package/dist/hooks/useToolConfirmation.d.ts +18 -0
- package/dist/hooks/useToolConfirmation.js +49 -0
- package/dist/mcp/bash.d.ts +57 -0
- package/dist/mcp/bash.js +138 -0
- package/dist/mcp/filesystem.d.ts +307 -0
- package/dist/mcp/filesystem.js +520 -0
- package/dist/mcp/todo.d.ts +55 -0
- package/dist/mcp/todo.js +329 -0
- package/dist/test/logger-test.d.ts +1 -0
- package/dist/test/logger-test.js +7 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/ui/components/ChatInput.d.ts +15 -2
- package/dist/ui/components/ChatInput.js +445 -59
- package/dist/ui/components/CommandPanel.d.ts +2 -2
- package/dist/ui/components/CommandPanel.js +11 -7
- package/dist/ui/components/DiffViewer.d.ts +9 -0
- package/dist/ui/components/DiffViewer.js +93 -0
- package/dist/ui/components/FileList.d.ts +14 -0
- package/dist/ui/components/FileList.js +131 -0
- package/dist/ui/components/MCPInfoPanel.d.ts +2 -0
- package/dist/ui/components/MCPInfoPanel.js +74 -0
- package/dist/ui/components/MCPInfoScreen.d.ts +7 -0
- package/dist/ui/components/MCPInfoScreen.js +27 -0
- package/dist/ui/components/MarkdownRenderer.d.ts +7 -0
- package/dist/ui/components/MarkdownRenderer.js +110 -0
- package/dist/ui/components/Menu.d.ts +5 -2
- package/dist/ui/components/Menu.js +60 -9
- package/dist/ui/components/MessageList.d.ts +30 -2
- package/dist/ui/components/MessageList.js +64 -12
- package/dist/ui/components/PendingMessages.js +1 -1
- package/dist/ui/components/ScrollableSelectInput.d.ts +29 -0
- package/dist/ui/components/ScrollableSelectInput.js +157 -0
- package/dist/ui/components/SessionListScreen.d.ts +7 -0
- package/dist/ui/components/SessionListScreen.js +196 -0
- package/dist/ui/components/SessionListScreenWrapper.d.ts +7 -0
- package/dist/ui/components/SessionListScreenWrapper.js +14 -0
- package/dist/ui/components/TodoTree.d.ts +15 -0
- package/dist/ui/components/TodoTree.js +60 -0
- package/dist/ui/components/ToolConfirmation.d.ts +8 -0
- package/dist/ui/components/ToolConfirmation.js +38 -0
- package/dist/ui/components/ToolResultPreview.d.ts +12 -0
- package/dist/ui/components/ToolResultPreview.js +115 -0
- package/dist/ui/pages/ChatScreen.d.ts +4 -0
- package/dist/ui/pages/ChatScreen.js +385 -196
- package/dist/ui/pages/MCPConfigScreen.d.ts +6 -0
- package/dist/ui/pages/MCPConfigScreen.js +55 -0
- package/dist/ui/pages/ModelConfigScreen.js +73 -12
- package/dist/ui/pages/WelcomeScreen.js +17 -11
- package/dist/utils/apiConfig.d.ts +12 -0
- package/dist/utils/apiConfig.js +95 -9
- package/dist/utils/commandExecutor.d.ts +2 -1
- package/dist/utils/commands/init.d.ts +2 -0
- package/dist/utils/commands/init.js +93 -0
- package/dist/utils/commands/mcp.d.ts +2 -0
- package/dist/utils/commands/mcp.js +12 -0
- package/dist/utils/commands/resume.d.ts +2 -0
- package/dist/utils/commands/resume.js +12 -0
- package/dist/utils/commands/yolo.d.ts +2 -0
- package/dist/utils/commands/yolo.js +12 -0
- package/dist/utils/fileUtils.d.ts +44 -0
- package/dist/utils/fileUtils.js +222 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logger.js +97 -0
- package/dist/utils/mcpToolsManager.d.ts +47 -0
- package/dist/utils/mcpToolsManager.js +476 -0
- package/dist/utils/messageFormatter.d.ts +12 -0
- package/dist/utils/messageFormatter.js +32 -0
- package/dist/utils/sessionConverter.d.ts +6 -0
- package/dist/utils/sessionConverter.js +61 -0
- package/dist/utils/sessionManager.d.ts +39 -0
- package/dist/utils/sessionManager.js +141 -0
- package/dist/utils/textBuffer.d.ts +36 -7
- package/dist/utils/textBuffer.js +265 -179
- package/dist/utils/todoPreprocessor.d.ts +5 -0
- package/dist/utils/todoPreprocessor.js +19 -0
- package/dist/utils/toolExecutor.d.ts +21 -0
- package/dist/utils/toolExecutor.js +28 -0
- package/package.json +12 -3
- package/readme.md +2 -2
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
237
|
+
// 手动输入模式的界面
|
|
238
|
+
if (manualInputMode) {
|
|
185
239
|
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
186
|
-
React.createElement(Box, { marginBottom: 2, borderStyle: "double", borderColor: "
|
|
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
|
|
189
|
-
React.createElement(Text, { color: "gray", dimColor: true }, "
|
|
190
|
-
React.createElement(Box, { marginBottom: 2 },
|
|
191
|
-
React.createElement(
|
|
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" }, "
|
|
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, {
|
|
41
|
+
React.createElement(Box, { borderStyle: "double", paddingX: 1, paddingY: 1, borderColor: 'cyan' },
|
|
36
42
|
React.createElement(Box, { flexDirection: "column" },
|
|
37
|
-
React.createElement(
|
|
38
|
-
React.createElement(Text, {
|
|
39
|
-
|
|
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,
|
|
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[];
|
package/dist/utils/apiConfig.js
CHANGED
|
@@ -11,13 +11,31 @@ const DEFAULT_CONFIG = {
|
|
|
11
11
|
maxContextTokens: 4000,
|
|
12
12
|
},
|
|
13
13
|
};
|
|
14
|
-
const
|
|
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
|
|
30
|
-
|
|
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
|
-
...
|
|
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,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,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,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,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
|
+
};
|