snow-ai 0.2.20 → 0.2.22

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.
@@ -34,18 +34,18 @@ export function useVSCodeState() {
34
34
  if (vscodeConnectionStatus !== 'connecting') {
35
35
  return;
36
36
  }
37
- // Set timeout for connecting state (30 seconds to allow for VSCode extension reconnection)
37
+ // Set timeout for connecting state (30 seconds to allow for IDE plugin reconnection)
38
38
  const connectingTimeout = setTimeout(() => {
39
39
  const isConnected = vscodeConnection.isConnected();
40
- const isServerRunning = vscodeConnection.isServerRunning();
40
+ const isClientRunning = vscodeConnection.isClientRunning();
41
41
  // Only set error if still not connected after timeout
42
42
  if (!isConnected) {
43
- if (isServerRunning) {
44
- // Server is running but no connection - show error with helpful message
43
+ if (isClientRunning) {
44
+ // Client is running but no connection - show error with helpful message
45
45
  setVscodeConnectionStatus('error');
46
46
  }
47
47
  else {
48
- // Server not running - go back to disconnected
48
+ // Client not running - go back to disconnected
49
49
  setVscodeConnectionStatus('disconnected');
50
50
  }
51
51
  }
@@ -177,7 +177,7 @@ const FileList = memo(forwardRef(({ query, selectedIndex, visible, maxItems = 10
177
177
  return (React.createElement(Box, { paddingX: 1, marginTop: 1, flexDirection: "column" },
178
178
  React.createElement(Box, { marginBottom: 1 },
179
179
  React.createElement(Text, { color: "blue", bold: true },
180
- "\uD83D\uDDD0 Files",
180
+ "\u2261 Files",
181
181
  ' ',
182
182
  allFilteredFiles.length > effectiveMaxItems &&
183
183
  `(${selectedIndex + 1}/${allFilteredFiles.length})`)),
@@ -691,12 +691,12 @@ export default function ChatScreen({}) {
691
691
  "\u25CF",
692
692
  ' ',
693
693
  vscodeState.vscodeConnectionStatus === 'connecting'
694
- ? 'Waiting for VSCode extension to connect...'
694
+ ? 'Connecting to IDE...'
695
695
  : vscodeState.vscodeConnectionStatus === 'connected'
696
- ? 'VSCode Connected'
696
+ ? 'IDE Connected'
697
697
  : vscodeState.vscodeConnectionStatus === 'error'
698
- ? 'Connection Failed - Make sure Snow CLI extension is installed and active in VSCode'
699
- : 'VSCode',
698
+ ? 'Connection Failed - Make sure Snow CLI plugin is installed and active in your IDE'
699
+ : 'IDE',
700
700
  vscodeState.vscodeConnectionStatus === 'connected' &&
701
701
  vscodeState.editorContext.activeFile &&
702
702
  ` | ${vscodeState.editorContext.activeFile}`,
@@ -3,42 +3,30 @@ import { vscodeConnection } from '../vscodeConnection.js';
3
3
  // IDE connection command handler
4
4
  registerCommand('ide', {
5
5
  execute: async () => {
6
- // Check if server is already running in THIS process (allow multiple clients to connect)
7
- if (vscodeConnection.isServerRunning()) {
8
- const isConnected = vscodeConnection.isConnected();
6
+ // Check if already connected to IDE plugin
7
+ if (vscodeConnection.isConnected()) {
9
8
  return {
10
9
  success: true,
11
10
  action: 'info',
12
- alreadyConnected: isConnected, // Add this flag to indicate connection status
13
- message: isConnected
14
- ? `Connected to VSCode (server running on port ${vscodeConnection.getPort()})`
15
- : `VSCode connection server is running on port ${vscodeConnection.getPort()}\nWaiting for VSCode extension to connect...`
11
+ alreadyConnected: true,
12
+ message: `Already connected to IDE (port ${vscodeConnection.getPort()})`
16
13
  };
17
14
  }
18
- // Start the server
15
+ // Try to connect to IDE plugin server
19
16
  try {
20
17
  await vscodeConnection.start();
21
18
  return {
22
19
  success: true,
23
20
  action: 'info',
24
- message: `VSCode connection server started on port ${vscodeConnection.getPort()}\nWaiting for VSCode extension to connect...`
21
+ message: `Connected to IDE on port ${vscodeConnection.getPort()}\nMake sure your IDE plugin (VSCode/JetBrains) is active and running.`
25
22
  };
26
23
  }
27
24
  catch (error) {
28
- // Handle EADDRINUSE error specifically
29
- if (error instanceof Error && 'code' in error && error.code === 'EADDRINUSE') {
30
- // Port is in use by another Snow CLI process - this is OK!
31
- // Return success and indicate already connected
32
- return {
33
- success: true,
34
- action: 'info',
35
- alreadyConnected: true, // Treat as already connected since another terminal has the connection
36
- message: `VSCode connection is already active in another Snow CLI terminal.\nNo additional connection needed - context will be shared through VSCode extension.`
37
- };
38
- }
39
25
  return {
40
26
  success: false,
41
- message: error instanceof Error ? error.message : 'Failed to start IDE connection'
27
+ message: error instanceof Error
28
+ ? `Failed to connect to IDE: ${error.message}\nMake sure your IDE plugin is installed and active.`
29
+ : 'Failed to connect to IDE. Make sure your IDE plugin is installed and active.'
42
30
  };
43
31
  }
44
32
  }
@@ -16,26 +16,54 @@ interface Diagnostic {
16
16
  code?: string | number;
17
17
  }
18
18
  declare class VSCodeConnectionManager {
19
- private server;
20
- private clients;
19
+ private client;
20
+ private reconnectTimer;
21
+ private reconnectAttempts;
22
+ private readonly MAX_RECONNECT_ATTEMPTS;
23
+ private readonly BASE_RECONNECT_DELAY;
24
+ private readonly MAX_RECONNECT_DELAY;
25
+ private readonly VSCODE_BASE_PORT;
26
+ private readonly VSCODE_MAX_PORT;
27
+ private readonly JETBRAINS_BASE_PORT;
28
+ private readonly JETBRAINS_MAX_PORT;
21
29
  private port;
22
30
  private editorContext;
23
31
  private listeners;
32
+ private currentWorkingDirectory;
24
33
  start(): Promise<void>;
34
+ /**
35
+ * Normalize path for cross-platform compatibility
36
+ * - Converts Windows backslashes to forward slashes
37
+ * - Converts drive letters to lowercase for consistent comparison
38
+ */
39
+ private normalizePath;
40
+ /**
41
+ * Find the correct port for the current workspace
42
+ */
43
+ private findPortForWorkspace;
44
+ /**
45
+ * Check if we should handle this message based on workspace folder
46
+ */
47
+ private shouldHandleMessage;
48
+ private scheduleReconnect;
25
49
  stop(): void;
26
50
  isConnected(): boolean;
27
- isServerRunning(): boolean;
51
+ isClientRunning(): boolean;
28
52
  getContext(): EditorContext;
29
53
  onContextUpdate(listener: (context: EditorContext) => void): () => void;
30
54
  private handleMessage;
31
55
  private notifyListeners;
32
56
  getPort(): number;
33
57
  /**
34
- * Request diagnostics for a specific file from VS Code
58
+ * Request diagnostics for a specific file from IDE
35
59
  * @param filePath - The file path to get diagnostics for
36
60
  * @returns Promise that resolves with diagnostics array
37
61
  */
38
62
  requestDiagnostics(filePath: string): Promise<Diagnostic[]>;
63
+ /**
64
+ * Reset reconnection attempts (e.g., when user manually triggers reconnect)
65
+ */
66
+ resetReconnectAttempts(): void;
39
67
  }
40
68
  export declare const vscodeConnection: VSCodeConnectionManager;
41
69
  export type { EditorContext, Diagnostic };
@@ -1,17 +1,69 @@
1
- import { WebSocketServer, WebSocket } from 'ws';
1
+ import { WebSocket } from 'ws';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
2
5
  class VSCodeConnectionManager {
3
6
  constructor() {
4
- Object.defineProperty(this, "server", {
7
+ Object.defineProperty(this, "client", {
5
8
  enumerable: true,
6
9
  configurable: true,
7
10
  writable: true,
8
11
  value: null
9
12
  });
10
- Object.defineProperty(this, "clients", {
13
+ Object.defineProperty(this, "reconnectTimer", {
11
14
  enumerable: true,
12
15
  configurable: true,
13
16
  writable: true,
14
- value: new Set()
17
+ value: null
18
+ });
19
+ Object.defineProperty(this, "reconnectAttempts", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: 0
24
+ });
25
+ Object.defineProperty(this, "MAX_RECONNECT_ATTEMPTS", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: 10
30
+ });
31
+ Object.defineProperty(this, "BASE_RECONNECT_DELAY", {
32
+ enumerable: true,
33
+ configurable: true,
34
+ writable: true,
35
+ value: 2000
36
+ }); // 2 seconds
37
+ Object.defineProperty(this, "MAX_RECONNECT_DELAY", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: 30000
42
+ }); // 30 seconds
43
+ // Port ranges: VSCode uses 9527-9537, JetBrains uses 9538-9548
44
+ Object.defineProperty(this, "VSCODE_BASE_PORT", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: 9527
49
+ });
50
+ Object.defineProperty(this, "VSCODE_MAX_PORT", {
51
+ enumerable: true,
52
+ configurable: true,
53
+ writable: true,
54
+ value: 9537
55
+ });
56
+ Object.defineProperty(this, "JETBRAINS_BASE_PORT", {
57
+ enumerable: true,
58
+ configurable: true,
59
+ writable: true,
60
+ value: 9538
61
+ });
62
+ Object.defineProperty(this, "JETBRAINS_MAX_PORT", {
63
+ enumerable: true,
64
+ configurable: true,
65
+ writable: true,
66
+ value: 9548
15
67
  });
16
68
  Object.defineProperty(this, "port", {
17
69
  enumerable: true,
@@ -31,64 +83,162 @@ class VSCodeConnectionManager {
31
83
  writable: true,
32
84
  value: []
33
85
  });
86
+ Object.defineProperty(this, "currentWorkingDirectory", {
87
+ enumerable: true,
88
+ configurable: true,
89
+ writable: true,
90
+ value: process.cwd()
91
+ });
34
92
  }
35
93
  async start() {
36
- // If already running, just return success
37
- if (this.server) {
94
+ // If already connected, just return success
95
+ if (this.client?.readyState === WebSocket.OPEN) {
38
96
  return Promise.resolve();
39
97
  }
98
+ // Try to find the correct port for this workspace
99
+ const targetPort = this.findPortForWorkspace();
40
100
  return new Promise((resolve, reject) => {
41
- try {
42
- this.server = new WebSocketServer({ port: this.port });
43
- this.server.on('connection', ws => {
44
- // Add new client to the set (allow multiple connections)
45
- this.clients.add(ws);
46
- ws.on('message', message => {
101
+ const tryConnect = (port) => {
102
+ // Check both VSCode and JetBrains port ranges
103
+ if (port > this.VSCODE_MAX_PORT && port < this.JETBRAINS_BASE_PORT) {
104
+ // Jump from VSCode range to JetBrains range
105
+ tryConnect(this.JETBRAINS_BASE_PORT);
106
+ return;
107
+ }
108
+ if (port > this.JETBRAINS_MAX_PORT) {
109
+ reject(new Error(`Failed to connect: no IDE server found on ports ${this.VSCODE_BASE_PORT}-${this.VSCODE_MAX_PORT} or ${this.JETBRAINS_BASE_PORT}-${this.JETBRAINS_MAX_PORT}`));
110
+ return;
111
+ }
112
+ try {
113
+ this.client = new WebSocket(`ws://localhost:${port}`);
114
+ this.client.on('open', () => {
115
+ // Reset reconnect attempts on successful connection
116
+ this.reconnectAttempts = 0;
117
+ this.port = port;
118
+ resolve();
119
+ });
120
+ this.client.on('message', message => {
47
121
  try {
48
122
  const data = JSON.parse(message.toString());
49
- this.handleMessage(data);
123
+ // Filter messages by workspace folder
124
+ if (this.shouldHandleMessage(data)) {
125
+ this.handleMessage(data);
126
+ }
50
127
  }
51
128
  catch (error) {
52
129
  // Ignore invalid JSON
53
130
  }
54
131
  });
55
- ws.on('close', () => {
56
- this.clients.delete(ws);
132
+ this.client.on('close', () => {
133
+ this.client = null;
134
+ this.scheduleReconnect();
57
135
  });
58
- ws.on('error', () => {
59
- // Silently handle errors
60
- this.clients.delete(ws);
136
+ this.client.on('error', _error => {
137
+ // On initial connection, try next port
138
+ if (this.reconnectAttempts === 0) {
139
+ this.client = null;
140
+ tryConnect(port + 1);
141
+ }
142
+ // For reconnections, silently handle and let close event trigger reconnect
61
143
  });
62
- });
63
- this.server.on('listening', () => {
64
- resolve();
65
- });
66
- this.server.on('error', error => {
67
- reject(error);
68
- });
69
- }
70
- catch (error) {
71
- reject(error);
72
- }
144
+ }
145
+ catch (error) {
146
+ tryConnect(port + 1);
147
+ }
148
+ };
149
+ tryConnect(targetPort);
73
150
  });
74
151
  }
152
+ /**
153
+ * Normalize path for cross-platform compatibility
154
+ * - Converts Windows backslashes to forward slashes
155
+ * - Converts drive letters to lowercase for consistent comparison
156
+ */
157
+ normalizePath(filePath) {
158
+ let normalized = filePath.replace(/\\/g, '/');
159
+ // Convert Windows drive letter to lowercase (C: -> c:)
160
+ if (/^[A-Z]:/.test(normalized)) {
161
+ normalized = normalized.charAt(0).toLowerCase() + normalized.slice(1);
162
+ }
163
+ return normalized;
164
+ }
165
+ /**
166
+ * Find the correct port for the current workspace
167
+ */
168
+ findPortForWorkspace() {
169
+ try {
170
+ const portInfoPath = path.join(os.tmpdir(), 'snow-cli-ports.json');
171
+ if (fs.existsSync(portInfoPath)) {
172
+ const portInfo = JSON.parse(fs.readFileSync(portInfoPath, 'utf8'));
173
+ // Normalize cwd for consistent comparison
174
+ const cwd = this.normalizePath(this.currentWorkingDirectory);
175
+ // Direct match
176
+ if (portInfo[cwd]) {
177
+ return portInfo[cwd];
178
+ }
179
+ // Check if cwd is within any of the workspace folders
180
+ for (const [workspace, port] of Object.entries(portInfo)) {
181
+ const normalizedWorkspace = this.normalizePath(workspace);
182
+ if (cwd.startsWith(normalizedWorkspace)) {
183
+ return port;
184
+ }
185
+ }
186
+ }
187
+ }
188
+ catch (error) {
189
+ // Ignore errors, will fall back to VSCODE_BASE_PORT
190
+ }
191
+ // Start with VSCode port range by default
192
+ return this.VSCODE_BASE_PORT;
193
+ }
194
+ /**
195
+ * Check if we should handle this message based on workspace folder
196
+ */
197
+ shouldHandleMessage(data) {
198
+ // If no workspace folder in message, accept it (backwards compatibility)
199
+ if (!data.workspaceFolder) {
200
+ return true;
201
+ }
202
+ // Normalize paths for consistent comparison across platforms
203
+ const cwd = this.normalizePath(this.currentWorkingDirectory);
204
+ const workspaceFolder = this.normalizePath(data.workspaceFolder);
205
+ // Bidirectional check: either cwd is within IDE workspace, or IDE workspace is within cwd
206
+ const cwdInWorkspace = cwd.startsWith(workspaceFolder);
207
+ const workspaceInCwd = workspaceFolder.startsWith(cwd);
208
+ const matches = cwdInWorkspace || workspaceInCwd;
209
+ return matches;
210
+ }
211
+ scheduleReconnect() {
212
+ if (this.reconnectTimer) {
213
+ clearTimeout(this.reconnectTimer);
214
+ }
215
+ this.reconnectAttempts++;
216
+ if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
217
+ return;
218
+ }
219
+ const delay = Math.min(this.BASE_RECONNECT_DELAY * Math.pow(1.5, this.reconnectAttempts - 1), this.MAX_RECONNECT_DELAY);
220
+ this.reconnectTimer = setTimeout(() => {
221
+ this.start().catch(() => {
222
+ // Silently handle reconnection failures
223
+ });
224
+ }, delay);
225
+ }
75
226
  stop() {
76
- // Close all connected clients
77
- for (const client of this.clients) {
78
- client.close();
227
+ if (this.reconnectTimer) {
228
+ clearTimeout(this.reconnectTimer);
229
+ this.reconnectTimer = null;
79
230
  }
80
- this.clients.clear();
81
- if (this.server) {
82
- this.server.close();
83
- this.server = null;
231
+ if (this.client) {
232
+ this.client.close();
233
+ this.client = null;
84
234
  }
235
+ this.reconnectAttempts = 0;
85
236
  }
86
237
  isConnected() {
87
- return (this.clients.size > 0 &&
88
- Array.from(this.clients).some(client => client.readyState === WebSocket.OPEN));
238
+ return this.client?.readyState === WebSocket.OPEN;
89
239
  }
90
- isServerRunning() {
91
- return this.server !== null;
240
+ isClientRunning() {
241
+ return this.client !== null;
92
242
  }
93
243
  getContext() {
94
244
  return { ...this.editorContext };
@@ -119,15 +269,13 @@ class VSCodeConnectionManager {
119
269
  return this.port;
120
270
  }
121
271
  /**
122
- * Request diagnostics for a specific file from VS Code
272
+ * Request diagnostics for a specific file from IDE
123
273
  * @param filePath - The file path to get diagnostics for
124
274
  * @returns Promise that resolves with diagnostics array
125
275
  */
126
276
  async requestDiagnostics(filePath) {
127
277
  return new Promise(resolve => {
128
- // Get first connected client
129
- const client = Array.from(this.clients).find(c => c.readyState === WebSocket.OPEN);
130
- if (!client) {
278
+ if (!this.client || this.client.readyState !== WebSocket.OPEN) {
131
279
  resolve([]); // Return empty array if not connected
132
280
  return;
133
281
  }
@@ -150,15 +298,21 @@ class VSCodeConnectionManager {
150
298
  };
151
299
  const cleanup = () => {
152
300
  clearTimeout(timeout);
153
- client?.removeListener('message', handler);
301
+ this.client?.removeListener('message', handler);
154
302
  };
155
- client.on('message', handler);
156
- client.send(JSON.stringify({
303
+ this.client.on('message', handler);
304
+ this.client.send(JSON.stringify({
157
305
  type: 'getDiagnostics',
158
306
  requestId,
159
307
  filePath,
160
308
  }));
161
309
  });
162
310
  }
311
+ /**
312
+ * Reset reconnection attempts (e.g., when user manually triggers reconnect)
313
+ */
314
+ resetReconnectAttempts() {
315
+ this.reconnectAttempts = 0;
316
+ }
163
317
  }
164
318
  export const vscodeConnection = new VSCodeConnectionManager();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {
package/readme.md CHANGED
@@ -57,10 +57,16 @@ $ npm uninstall --global snow-ai
57
57
 
58
58
  ## Install VSCode Extension
59
59
 
60
- * download [VSIX/snow-cli-0.2.6.vsix](https://github.com/MayDay-wpf/snow-cli/blob/main/VSIX/snow-cli-0.2.6.vsix)
60
+ * download [VSIX/snow-cli-x.x.x.vsix](https://github.com/MayDay-wpf/snow-cli/blob/main/VSIX/)
61
61
 
62
62
  * open VSCode, click `Extensions` -> `Install from VSIX...` -> select `snow-cli-0.2.6.vsix`
63
63
 
64
+ ## Install JetBrains plugin
65
+
66
+ * download [JetBrains/build/distributions](https://github.com/MayDay-wpf/snow-cli/tree/main/JetBrains/build/distributions)
67
+
68
+ * File > Settings > Plugins
69
+
64
70
  ## Live View
65
71
  * **Welcome & Settings**
66
72