snow-ai 0.2.12 → 0.2.13
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/anthropic.js +15 -4
- package/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +149 -40
- package/dist/hooks/useConversation.d.ts +1 -1
- package/dist/hooks/useConversation.js +5 -3
- package/dist/hooks/useGlobalNavigation.js +2 -0
- package/dist/hooks/useToolConfirmation.d.ts +2 -1
- package/dist/hooks/useToolConfirmation.js +2 -1
- package/dist/mcp/filesystem.d.ts +16 -1
- package/dist/mcp/filesystem.js +193 -89
- package/dist/mcp/multiLanguageASTParser.d.ts +67 -0
- package/dist/mcp/multiLanguageASTParser.js +360 -0
- package/dist/mcp/todo.d.ts +1 -1
- package/dist/mcp/todo.js +21 -26
- package/dist/ui/components/ChatInput.d.ts +1 -1
- package/dist/ui/components/ChatInput.js +84 -45
- package/dist/ui/components/DiffViewer.d.ts +1 -2
- package/dist/ui/components/DiffViewer.js +65 -65
- package/dist/ui/components/MCPInfoPanel.js +1 -2
- package/dist/ui/components/TodoTree.js +1 -1
- package/dist/ui/components/ToolConfirmation.d.ts +11 -1
- package/dist/ui/components/ToolConfirmation.js +86 -6
- package/dist/ui/pages/ChatScreen.js +223 -111
- package/dist/utils/commands/ide.js +18 -1
- package/dist/utils/mcpToolsManager.d.ts +1 -1
- package/dist/utils/mcpToolsManager.js +45 -36
- package/dist/utils/textBuffer.d.ts +5 -0
- package/dist/utils/textBuffer.js +23 -2
- package/dist/utils/vscodeConnection.js +10 -1
- package/package.json +13 -1
- package/readme.md +12 -2
|
@@ -3,6 +3,7 @@ import { vscodeConnection } from '../vscodeConnection.js';
|
|
|
3
3
|
// IDE connection command handler
|
|
4
4
|
registerCommand('ide', {
|
|
5
5
|
execute: async () => {
|
|
6
|
+
// Check if already connected
|
|
6
7
|
if (vscodeConnection.isConnected()) {
|
|
7
8
|
return {
|
|
8
9
|
success: true,
|
|
@@ -10,15 +11,31 @@ registerCommand('ide', {
|
|
|
10
11
|
message: 'Already connected to VSCode editor'
|
|
11
12
|
};
|
|
12
13
|
}
|
|
14
|
+
// Check if server is already running (but not connected yet)
|
|
15
|
+
if (vscodeConnection.isServerRunning()) {
|
|
16
|
+
return {
|
|
17
|
+
success: true,
|
|
18
|
+
action: 'info',
|
|
19
|
+
message: `VSCode connection server is already running on port ${vscodeConnection.getPort()}\nWaiting for VSCode extension to connect...`
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// Start the server
|
|
13
23
|
try {
|
|
14
24
|
await vscodeConnection.start();
|
|
15
25
|
return {
|
|
16
26
|
success: true,
|
|
17
27
|
action: 'info',
|
|
18
|
-
message: `VSCode connection server started on port ${vscodeConnection.getPort()}\
|
|
28
|
+
message: `VSCode connection server started on port ${vscodeConnection.getPort()}\nWaiting for VSCode extension to connect...`
|
|
19
29
|
};
|
|
20
30
|
}
|
|
21
31
|
catch (error) {
|
|
32
|
+
// Handle EADDRINUSE error specifically
|
|
33
|
+
if (error instanceof Error && 'code' in error && error.code === 'EADDRINUSE') {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
message: `Port ${vscodeConnection.getPort()} is already in use. Please restart Snow CLI to reset the connection.`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
22
39
|
return {
|
|
23
40
|
success: false,
|
|
24
41
|
message: error instanceof Error ? error.message : 'Failed to start IDE connection'
|
|
@@ -42,7 +42,7 @@ function isCacheValid() {
|
|
|
42
42
|
if (!toolsCache)
|
|
43
43
|
return false;
|
|
44
44
|
const now = Date.now();
|
|
45
|
-
const isExpired =
|
|
45
|
+
const isExpired = now - toolsCache.lastUpdate > CACHE_DURATION;
|
|
46
46
|
const configChanged = toolsCache.configHash !== generateConfigHash();
|
|
47
47
|
return !isExpired && !configChanged;
|
|
48
48
|
}
|
|
@@ -66,44 +66,44 @@ async function refreshToolsCache() {
|
|
|
66
66
|
const filesystemServiceTools = filesystemTools.map(tool => ({
|
|
67
67
|
name: tool.name.replace('filesystem_', ''),
|
|
68
68
|
description: tool.description,
|
|
69
|
-
inputSchema: tool.inputSchema
|
|
69
|
+
inputSchema: tool.inputSchema,
|
|
70
70
|
}));
|
|
71
71
|
servicesInfo.push({
|
|
72
72
|
serviceName: 'filesystem',
|
|
73
73
|
tools: filesystemServiceTools,
|
|
74
74
|
isBuiltIn: true,
|
|
75
|
-
connected: true
|
|
75
|
+
connected: true,
|
|
76
76
|
});
|
|
77
77
|
for (const tool of filesystemTools) {
|
|
78
78
|
allTools.push({
|
|
79
|
-
type:
|
|
79
|
+
type: 'function',
|
|
80
80
|
function: {
|
|
81
81
|
name: `filesystem-${tool.name.replace('filesystem_', '')}`,
|
|
82
82
|
description: tool.description,
|
|
83
|
-
parameters: tool.inputSchema
|
|
84
|
-
}
|
|
83
|
+
parameters: tool.inputSchema,
|
|
84
|
+
},
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
// Add built-in terminal tools (always available)
|
|
88
88
|
const terminalServiceTools = terminalTools.map(tool => ({
|
|
89
89
|
name: tool.name.replace('terminal_', ''),
|
|
90
90
|
description: tool.description,
|
|
91
|
-
inputSchema: tool.inputSchema
|
|
91
|
+
inputSchema: tool.inputSchema,
|
|
92
92
|
}));
|
|
93
93
|
servicesInfo.push({
|
|
94
94
|
serviceName: 'terminal',
|
|
95
95
|
tools: terminalServiceTools,
|
|
96
96
|
isBuiltIn: true,
|
|
97
|
-
connected: true
|
|
97
|
+
connected: true,
|
|
98
98
|
});
|
|
99
99
|
for (const tool of terminalTools) {
|
|
100
100
|
allTools.push({
|
|
101
|
-
type:
|
|
101
|
+
type: 'function',
|
|
102
102
|
function: {
|
|
103
103
|
name: `terminal-${tool.name.replace('terminal_', '')}`,
|
|
104
104
|
description: tool.description,
|
|
105
|
-
parameters: tool.inputSchema
|
|
106
|
-
}
|
|
105
|
+
parameters: tool.inputSchema,
|
|
106
|
+
},
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
// Add built-in TODO tools (always available)
|
|
@@ -112,22 +112,22 @@ async function refreshToolsCache() {
|
|
|
112
112
|
const todoServiceTools = todoTools.map(tool => ({
|
|
113
113
|
name: tool.name.replace('todo-', ''),
|
|
114
114
|
description: tool.description || '',
|
|
115
|
-
inputSchema: tool.inputSchema
|
|
115
|
+
inputSchema: tool.inputSchema,
|
|
116
116
|
}));
|
|
117
117
|
servicesInfo.push({
|
|
118
118
|
serviceName: 'todo',
|
|
119
119
|
tools: todoServiceTools,
|
|
120
120
|
isBuiltIn: true,
|
|
121
|
-
connected: true
|
|
121
|
+
connected: true,
|
|
122
122
|
});
|
|
123
123
|
for (const tool of todoTools) {
|
|
124
124
|
allTools.push({
|
|
125
|
-
type:
|
|
125
|
+
type: 'function',
|
|
126
126
|
function: {
|
|
127
127
|
name: tool.name,
|
|
128
128
|
description: tool.description || '',
|
|
129
|
-
parameters: tool.inputSchema
|
|
130
|
-
}
|
|
129
|
+
parameters: tool.inputSchema,
|
|
130
|
+
},
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
// Add user-configured MCP server tools (probe for availability but don't maintain connections)
|
|
@@ -140,16 +140,16 @@ async function refreshToolsCache() {
|
|
|
140
140
|
serviceName,
|
|
141
141
|
tools: serviceTools,
|
|
142
142
|
isBuiltIn: false,
|
|
143
|
-
connected: true
|
|
143
|
+
connected: true,
|
|
144
144
|
});
|
|
145
145
|
for (const tool of serviceTools) {
|
|
146
146
|
allTools.push({
|
|
147
|
-
type:
|
|
147
|
+
type: 'function',
|
|
148
148
|
function: {
|
|
149
149
|
name: `${serviceName}-${tool.name}`,
|
|
150
150
|
description: tool.description,
|
|
151
|
-
parameters: tool.inputSchema
|
|
152
|
-
}
|
|
151
|
+
parameters: tool.inputSchema,
|
|
152
|
+
},
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -159,7 +159,7 @@ async function refreshToolsCache() {
|
|
|
159
159
|
tools: [],
|
|
160
160
|
isBuiltIn: false,
|
|
161
161
|
connected: false,
|
|
162
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
162
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
}
|
|
@@ -172,7 +172,7 @@ async function refreshToolsCache() {
|
|
|
172
172
|
tools: allTools,
|
|
173
173
|
servicesInfo,
|
|
174
174
|
lastUpdate: Date.now(),
|
|
175
|
-
configHash: generateConfigHash()
|
|
175
|
+
configHash: generateConfigHash(),
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
178
|
/**
|
|
@@ -226,7 +226,7 @@ async function connectAndGetTools(serviceName, server, timeoutMs = 10000) {
|
|
|
226
226
|
name: `snow-cli-${serviceName}`,
|
|
227
227
|
version: '1.0.0',
|
|
228
228
|
}, {
|
|
229
|
-
capabilities: {}
|
|
229
|
+
capabilities: {},
|
|
230
230
|
});
|
|
231
231
|
// Create transport based on server configuration
|
|
232
232
|
if (server.url) {
|
|
@@ -260,11 +260,11 @@ async function connectAndGetTools(serviceName, server, timeoutMs = 10000) {
|
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
262
|
transport = new StreamableHTTPClientTransport(url, {
|
|
263
|
-
requestInit: { headers }
|
|
263
|
+
requestInit: { headers },
|
|
264
264
|
});
|
|
265
265
|
await Promise.race([
|
|
266
266
|
client.connect(transport),
|
|
267
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('HTTP connection timeout')), timeoutMs))
|
|
267
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('HTTP connection timeout')), timeoutMs)),
|
|
268
268
|
]);
|
|
269
269
|
}
|
|
270
270
|
catch (httpError) {
|
|
@@ -277,12 +277,12 @@ async function connectAndGetTools(serviceName, server, timeoutMs = 10000) {
|
|
|
277
277
|
name: `snow-cli-${serviceName}`,
|
|
278
278
|
version: '1.0.0',
|
|
279
279
|
}, {
|
|
280
|
-
capabilities: {}
|
|
280
|
+
capabilities: {},
|
|
281
281
|
});
|
|
282
282
|
transport = new SSEClientTransport(url);
|
|
283
283
|
await Promise.race([
|
|
284
284
|
client.connect(transport),
|
|
285
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('SSE connection timeout')), timeoutMs))
|
|
285
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('SSE connection timeout')), timeoutMs)),
|
|
286
286
|
]);
|
|
287
287
|
}
|
|
288
288
|
}
|
|
@@ -309,13 +309,13 @@ async function connectAndGetTools(serviceName, server, timeoutMs = 10000) {
|
|
|
309
309
|
// Get tools from the service
|
|
310
310
|
const toolsResult = await Promise.race([
|
|
311
311
|
client.listTools(),
|
|
312
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('ListTools timeout')), timeoutMs))
|
|
312
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('ListTools timeout')), timeoutMs)),
|
|
313
313
|
]);
|
|
314
|
-
return toolsResult.tools?.map(tool => ({
|
|
314
|
+
return (toolsResult.tools?.map(tool => ({
|
|
315
315
|
name: tool.name,
|
|
316
316
|
description: tool.description || '',
|
|
317
|
-
inputSchema: tool.inputSchema
|
|
318
|
-
})) || [];
|
|
317
|
+
inputSchema: tool.inputSchema,
|
|
318
|
+
})) || []);
|
|
319
319
|
}
|
|
320
320
|
finally {
|
|
321
321
|
try {
|
|
@@ -391,8 +391,15 @@ export async function executeMCPTool(toolName, args) {
|
|
|
391
391
|
return await filesystemService.getFileInfo(args.filePath);
|
|
392
392
|
case 'edit':
|
|
393
393
|
return await filesystemService.editFile(args.filePath, args.startLine, args.endLine, args.newContent, args.contextLines);
|
|
394
|
-
case 'search':
|
|
395
|
-
|
|
394
|
+
case 'search': {
|
|
395
|
+
// 兼容性处理:如果 searchMode 不存在或无效,默认使用 'text'
|
|
396
|
+
const validSearchModes = ['text', 'regex', 'ast'];
|
|
397
|
+
let searchMode = 'text';
|
|
398
|
+
if (args.searchMode && validSearchModes.includes(args.searchMode)) {
|
|
399
|
+
searchMode = args.searchMode;
|
|
400
|
+
}
|
|
401
|
+
return await filesystemService.searchCode(args.query, args.dirPath, args.fileExtensions, args.caseSensitive, args.maxResults, searchMode);
|
|
402
|
+
}
|
|
396
403
|
default:
|
|
397
404
|
throw new Error(`Unknown filesystem tool: ${actualToolName}`);
|
|
398
405
|
}
|
|
@@ -430,7 +437,7 @@ async function executeOnExternalMCPService(serviceName, server, toolName, args)
|
|
|
430
437
|
name: `snow-cli-${serviceName}`,
|
|
431
438
|
version: '1.0.0',
|
|
432
439
|
}, {
|
|
433
|
-
capabilities: {}
|
|
440
|
+
capabilities: {},
|
|
434
441
|
});
|
|
435
442
|
// Setup transport (similar to getServiceTools)
|
|
436
443
|
let transport;
|
|
@@ -450,7 +457,9 @@ async function executeOnExternalMCPService(serviceName, server, toolName, args)
|
|
|
450
457
|
transport = new StdioClientTransport({
|
|
451
458
|
command: server.command,
|
|
452
459
|
args: server.args || [],
|
|
453
|
-
env: server.env
|
|
460
|
+
env: server.env
|
|
461
|
+
? { ...process.env, ...server.env }
|
|
462
|
+
: process.env,
|
|
454
463
|
});
|
|
455
464
|
}
|
|
456
465
|
await client.connect(transport);
|
|
@@ -458,7 +467,7 @@ async function executeOnExternalMCPService(serviceName, server, toolName, args)
|
|
|
458
467
|
// Execute the tool with the original tool name (not prefixed)
|
|
459
468
|
const result = await client.callTool({
|
|
460
469
|
name: toolName,
|
|
461
|
-
arguments: args
|
|
470
|
+
arguments: args,
|
|
462
471
|
});
|
|
463
472
|
logger.debug(`result from ${serviceName} tool ${toolName}:`, result);
|
|
464
473
|
return result.content;
|
|
@@ -28,11 +28,16 @@ export declare class TextBuffer {
|
|
|
28
28
|
private pasteTimer;
|
|
29
29
|
private pastePlaceholderPosition;
|
|
30
30
|
private onUpdateCallback?;
|
|
31
|
+
private isDestroyed;
|
|
31
32
|
private visualLines;
|
|
32
33
|
private visualLineStarts;
|
|
33
34
|
private visualCursorPos;
|
|
34
35
|
private preferredVisualCol;
|
|
35
36
|
constructor(viewport: Viewport, onUpdate?: () => void);
|
|
37
|
+
/**
|
|
38
|
+
* Cleanup method to be called when the buffer is no longer needed
|
|
39
|
+
*/
|
|
40
|
+
destroy(): void;
|
|
36
41
|
get text(): string;
|
|
37
42
|
/**
|
|
38
43
|
* 获取完整文本,包括替换占位符为原始内容
|
package/dist/utils/textBuffer.js
CHANGED
|
@@ -79,6 +79,12 @@ export class TextBuffer {
|
|
|
79
79
|
writable: true,
|
|
80
80
|
value: void 0
|
|
81
81
|
}); // 更新回调函数
|
|
82
|
+
Object.defineProperty(this, "isDestroyed", {
|
|
83
|
+
enumerable: true,
|
|
84
|
+
configurable: true,
|
|
85
|
+
writable: true,
|
|
86
|
+
value: false
|
|
87
|
+
}); // 标记是否已销毁
|
|
82
88
|
Object.defineProperty(this, "visualLines", {
|
|
83
89
|
enumerable: true,
|
|
84
90
|
configurable: true,
|
|
@@ -107,6 +113,19 @@ export class TextBuffer {
|
|
|
107
113
|
this.onUpdateCallback = onUpdate;
|
|
108
114
|
this.recalculateVisualState();
|
|
109
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Cleanup method to be called when the buffer is no longer needed
|
|
118
|
+
*/
|
|
119
|
+
destroy() {
|
|
120
|
+
this.isDestroyed = true;
|
|
121
|
+
if (this.pasteTimer) {
|
|
122
|
+
clearTimeout(this.pasteTimer);
|
|
123
|
+
this.pasteTimer = null;
|
|
124
|
+
}
|
|
125
|
+
this.pasteStorage.clear();
|
|
126
|
+
this.imageStorage.clear();
|
|
127
|
+
this.onUpdateCallback = undefined;
|
|
128
|
+
}
|
|
110
129
|
get text() {
|
|
111
130
|
return this.content;
|
|
112
131
|
}
|
|
@@ -136,7 +155,7 @@ export class TextBuffer {
|
|
|
136
155
|
}
|
|
137
156
|
scheduleUpdate() {
|
|
138
157
|
// Notify external components of updates
|
|
139
|
-
if (this.onUpdateCallback) {
|
|
158
|
+
if (!this.isDestroyed && this.onUpdateCallback) {
|
|
140
159
|
this.onUpdateCallback();
|
|
141
160
|
}
|
|
142
161
|
}
|
|
@@ -191,7 +210,9 @@ export class TextBuffer {
|
|
|
191
210
|
this.cursorIndex = this.pastePlaceholderPosition + cpLen(tempPlaceholder);
|
|
192
211
|
// 设置150ms的定时器,如果150ms内没有新数据,则认为粘贴完成
|
|
193
212
|
this.pasteTimer = setTimeout(() => {
|
|
194
|
-
this.
|
|
213
|
+
if (!this.isDestroyed) {
|
|
214
|
+
this.finalizePaste();
|
|
215
|
+
}
|
|
195
216
|
}, 150);
|
|
196
217
|
this.recalculateVisualState();
|
|
197
218
|
this.scheduleUpdate();
|
|
@@ -41,6 +41,10 @@ class VSCodeConnectionManager {
|
|
|
41
41
|
try {
|
|
42
42
|
this.server = new WebSocketServer({ port: this.port });
|
|
43
43
|
this.server.on('connection', (ws) => {
|
|
44
|
+
// Close old client if exists
|
|
45
|
+
if (this.client && this.client !== ws) {
|
|
46
|
+
this.client.close();
|
|
47
|
+
}
|
|
44
48
|
this.client = ws;
|
|
45
49
|
ws.on('message', (message) => {
|
|
46
50
|
try {
|
|
@@ -52,7 +56,12 @@ class VSCodeConnectionManager {
|
|
|
52
56
|
}
|
|
53
57
|
});
|
|
54
58
|
ws.on('close', () => {
|
|
55
|
-
this.client
|
|
59
|
+
if (this.client === ws) {
|
|
60
|
+
this.client = null;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
ws.on('error', () => {
|
|
64
|
+
// Silently handle errors
|
|
56
65
|
});
|
|
57
66
|
});
|
|
58
67
|
this.server.on('listening', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "Intelligent Command Line Assistant powered by AI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -59,6 +59,18 @@
|
|
|
59
59
|
"react": "^18.2.0",
|
|
60
60
|
"string-width": "^7.2.0",
|
|
61
61
|
"tiktoken": "^1.0.22",
|
|
62
|
+
"tree-sitter": "^0.25.0",
|
|
63
|
+
"tree-sitter-c": "^0.24.1",
|
|
64
|
+
"tree-sitter-c-sharp": "^0.23.1",
|
|
65
|
+
"tree-sitter-cpp": "^0.23.4",
|
|
66
|
+
"tree-sitter-go": "^0.25.0",
|
|
67
|
+
"tree-sitter-java": "^0.23.5",
|
|
68
|
+
"tree-sitter-javascript": "^0.25.0",
|
|
69
|
+
"tree-sitter-php": "^0.24.2",
|
|
70
|
+
"tree-sitter-python": "^0.25.0",
|
|
71
|
+
"tree-sitter-ruby": "^0.23.1",
|
|
72
|
+
"tree-sitter-rust": "^0.24.0",
|
|
73
|
+
"tree-sitter-typescript": "^0.23.2",
|
|
62
74
|
"ws": "^8.14.2"
|
|
63
75
|
},
|
|
64
76
|
"devDependencies": {
|
package/readme.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="logo.png" alt="Snow AI CLI Logo" width="200"/>
|
|
4
|
+
|
|
1
5
|
# snow-ai
|
|
2
6
|
|
|
3
|
-
English | [中文](readme_zh.md)
|
|
7
|
+
**English** | [中文](readme_zh.md)
|
|
8
|
+
|
|
9
|
+
*An intelligent AI-powered CLI tool for developers*
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
4
14
|
|
|
5
15
|
|
|
6
16
|
## Install
|
|
@@ -66,7 +76,7 @@ $ npm uninstall --global snow-ai
|
|
|
66
76
|
* **Commands**
|
|
67
77
|
|
|
68
78
|

|
|
69
|
-
- /clear
|
|
79
|
+
- /clear - Create a new session
|
|
70
80
|
|
|
71
81
|
- /resume - The recovery history has
|
|
72
82
|
|