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.
@@ -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()}\nPlease connect from the Snow CLI extension in VSCode`
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'
@@ -1,6 +1,6 @@
1
1
  import { TodoService } from '../mcp/todo.js';
2
2
  export interface MCPTool {
3
- type: "function";
3
+ type: 'function';
4
4
  function: {
5
5
  name: string;
6
6
  description: string;
@@ -42,7 +42,7 @@ function isCacheValid() {
42
42
  if (!toolsCache)
43
43
  return false;
44
44
  const now = Date.now();
45
- const isExpired = (now - toolsCache.lastUpdate) > CACHE_DURATION;
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: "function",
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: "function",
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: "function",
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: "function",
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
- return await filesystemService.searchCode(args.query, args.dirPath, args.fileExtensions, args.caseSensitive, args.maxResults);
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 ? { ...process.env, ...server.env } : process.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
  * 获取完整文本,包括替换占位符为原始内容
@@ -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.finalizePaste();
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 = null;
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.12",
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
  ![alt text](image-2.png)
69
- - /clear —— Create a new session
79
+ - /clear - Create a new session
70
80
 
71
81
  - /resume - The recovery history has
72
82