@wonderwhy-er/desktop-commander 0.1.31 → 0.1.33
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/README.md +21 -5
- package/dist/handlers/command-handlers.d.ts +27 -0
- package/dist/handlers/command-handlers.js +31 -0
- package/dist/handlers/edit-search-handlers.d.ts +18 -0
- package/dist/handlers/edit-search-handlers.js +72 -0
- package/dist/handlers/filesystem-handlers.d.ts +90 -0
- package/dist/handlers/filesystem-handlers.js +179 -0
- package/dist/handlers/index.d.ts +5 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/process-handlers.d.ts +18 -0
- package/dist/handlers/process-handlers.js +15 -0
- package/dist/handlers/terminal-handlers.d.ts +36 -0
- package/dist/handlers/terminal-handlers.js +29 -0
- package/dist/server.js +51 -324
- package/dist/setup-claude-server.js +3 -3
- package/dist/tools/filesystem.d.ts +22 -1
- package/dist/tools/filesystem.js +160 -45
- package/dist/tools/schemas.d.ts +9 -0
- package/dist/tools/schemas.js +3 -0
- package/dist/tools/search.js +9 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +39 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
package/dist/tools/filesystem.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import os from 'os';
|
|
4
|
+
import fetch from 'cross-fetch';
|
|
5
|
+
import { withTimeout } from '../utils.js';
|
|
4
6
|
// Store allowed directories - temporarily allowing all paths
|
|
5
7
|
// TODO: Make this configurable through a configuration file
|
|
6
8
|
const allowedDirectories = [
|
|
@@ -57,53 +59,137 @@ async function validateParentDirectories(directoryPath) {
|
|
|
57
59
|
* @throws Error if the path or its parent directories don't exist
|
|
58
60
|
*/
|
|
59
61
|
export async function validatePath(requestedPath) {
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
const PATH_VALIDATION_TIMEOUT = 10000; // 10 seconds timeout
|
|
63
|
+
const validationOperation = async () => {
|
|
64
|
+
// Expand home directory if present
|
|
65
|
+
const expandedPath = expandHome(requestedPath);
|
|
66
|
+
// Convert to absolute path
|
|
67
|
+
const absolute = path.isAbsolute(expandedPath)
|
|
68
|
+
? path.resolve(expandedPath)
|
|
69
|
+
: path.resolve(process.cwd(), expandedPath);
|
|
70
|
+
// Check if path exists
|
|
71
|
+
try {
|
|
72
|
+
const stats = await fs.stat(absolute);
|
|
73
|
+
// If path exists, resolve any symlinks
|
|
74
|
+
return await fs.realpath(absolute);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
// Path doesn't exist - validate parent directories
|
|
78
|
+
if (await validateParentDirectories(absolute)) {
|
|
79
|
+
// Return the path if a valid parent exists
|
|
80
|
+
// This will be used for folder creation and many other file operations
|
|
81
|
+
return absolute;
|
|
82
|
+
}
|
|
83
|
+
// If no valid parent found, return the absolute path anyway
|
|
84
|
+
return absolute;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
// Execute with timeout
|
|
88
|
+
const result = await withTimeout(validationOperation(), PATH_VALIDATION_TIMEOUT, `Path validation for ${requestedPath}`, null);
|
|
89
|
+
if (result === null) {
|
|
90
|
+
throw new Error(`Path validation timed out after ${PATH_VALIDATION_TIMEOUT / 1000} seconds for: ${requestedPath}`);
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Read file content from a URL
|
|
96
|
+
* @param url URL to fetch content from
|
|
97
|
+
* @param returnMetadata Whether to return metadata with the content
|
|
98
|
+
* @returns File content or file result with metadata
|
|
99
|
+
*/
|
|
100
|
+
export async function readFileFromUrl(url, returnMetadata) {
|
|
101
|
+
// Import the MIME type utilities
|
|
102
|
+
const { isImageFile } = await import('./mime-types.js');
|
|
103
|
+
// Set up fetch with timeout
|
|
104
|
+
const FETCH_TIMEOUT_MS = 30000;
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
67
107
|
try {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
108
|
+
const response = await fetch(url, {
|
|
109
|
+
signal: controller.signal
|
|
110
|
+
});
|
|
111
|
+
// Clear the timeout since fetch completed
|
|
112
|
+
clearTimeout(timeoutId);
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
115
|
+
}
|
|
116
|
+
// Get MIME type from Content-Type header
|
|
117
|
+
const contentType = response.headers.get('content-type') || 'text/plain';
|
|
118
|
+
const isImage = isImageFile(contentType);
|
|
119
|
+
if (isImage) {
|
|
120
|
+
// For images, convert to base64
|
|
121
|
+
const buffer = await response.arrayBuffer();
|
|
122
|
+
const content = Buffer.from(buffer).toString('base64');
|
|
123
|
+
if (returnMetadata === true) {
|
|
124
|
+
return { content, mimeType: contentType, isImage };
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
return content;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// For text content
|
|
132
|
+
const content = await response.text();
|
|
133
|
+
if (returnMetadata === true) {
|
|
134
|
+
return { content, mimeType: contentType, isImage };
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
return content;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
71
140
|
}
|
|
72
141
|
catch (error) {
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
142
|
+
// Clear the timeout to prevent memory leaks
|
|
143
|
+
clearTimeout(timeoutId);
|
|
144
|
+
// Improve error message for timeout/abort cases
|
|
145
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
146
|
+
throw new Error(`URL fetch timed out after ${FETCH_TIMEOUT_MS}ms: ${url}`);
|
|
78
147
|
}
|
|
79
|
-
|
|
80
|
-
// to maintain compatibility with upstream behavior, but log a warning
|
|
81
|
-
console.warn(`Warning: Parent directory does not exist: ${path.dirname(absolute)}`);
|
|
82
|
-
return absolute;
|
|
148
|
+
throw new Error(`Failed to fetch URL: ${error instanceof Error ? error.message : String(error)}`);
|
|
83
149
|
}
|
|
84
150
|
}
|
|
85
|
-
|
|
86
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Read file content from the local filesystem
|
|
153
|
+
* @param filePath Path to the file
|
|
154
|
+
* @param returnMetadata Whether to return metadata with the content
|
|
155
|
+
* @returns File content or file result with metadata
|
|
156
|
+
*/
|
|
157
|
+
export async function readFileFromDisk(filePath, returnMetadata) {
|
|
87
158
|
// Import the MIME type utilities
|
|
88
159
|
const { getMimeType, isImageFile } = await import('./mime-types.js');
|
|
160
|
+
const validPath = await validatePath(filePath);
|
|
161
|
+
// Check file size before attempting to read
|
|
162
|
+
try {
|
|
163
|
+
const stats = await fs.stat(validPath);
|
|
164
|
+
const MAX_SIZE = 100 * 1024; // 100KB limit
|
|
165
|
+
if (stats.size > MAX_SIZE) {
|
|
166
|
+
const message = `File too large (${(stats.size / 1024).toFixed(2)}KB > ${MAX_SIZE / 1024}KB limit)`;
|
|
167
|
+
if (returnMetadata) {
|
|
168
|
+
return {
|
|
169
|
+
content: message,
|
|
170
|
+
mimeType: 'text/plain',
|
|
171
|
+
isImage: false
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
return message;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
// If we can't stat the file, continue anyway and let the read operation handle errors
|
|
181
|
+
console.error(`Failed to stat file ${validPath}:`, error);
|
|
182
|
+
}
|
|
89
183
|
// Detect the MIME type based on file extension
|
|
90
184
|
const mimeType = getMimeType(validPath);
|
|
91
185
|
const isImage = isImageFile(mimeType);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
else {
|
|
100
|
-
return content;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
// For all other files, try to read as UTF-8 text
|
|
105
|
-
try {
|
|
106
|
-
const content = await fs.readFile(validPath, "utf-8");
|
|
186
|
+
const FILE_READ_TIMEOUT = 30000; // 30 seconds timeout for file operations
|
|
187
|
+
// Use withTimeout to handle potential hangs
|
|
188
|
+
const readOperation = async () => {
|
|
189
|
+
if (isImage) {
|
|
190
|
+
// For image files, read as Buffer and convert to base64
|
|
191
|
+
const buffer = await fs.readFile(validPath);
|
|
192
|
+
const content = buffer.toString('base64');
|
|
107
193
|
if (returnMetadata === true) {
|
|
108
194
|
return { content, mimeType, isImage };
|
|
109
195
|
}
|
|
@@ -111,18 +197,47 @@ export async function readFile(filePath, returnMetadata) {
|
|
|
111
197
|
return content;
|
|
112
198
|
}
|
|
113
199
|
}
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
200
|
+
else {
|
|
201
|
+
// For all other files, try to read as UTF-8 text
|
|
202
|
+
try {
|
|
203
|
+
const content = await fs.readFile(validPath, "utf-8");
|
|
204
|
+
if (returnMetadata === true) {
|
|
205
|
+
return { content, mimeType, isImage };
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
return content;
|
|
209
|
+
}
|
|
120
210
|
}
|
|
121
|
-
|
|
122
|
-
return
|
|
211
|
+
catch (error) {
|
|
212
|
+
// If UTF-8 reading fails, treat as binary and return base64 but still as text
|
|
213
|
+
const buffer = await fs.readFile(validPath);
|
|
214
|
+
const content = `Binary file content (base64 encoded):\n${buffer.toString('base64')}`;
|
|
215
|
+
if (returnMetadata === true) {
|
|
216
|
+
return { content, mimeType: 'text/plain', isImage: false };
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
return content;
|
|
220
|
+
}
|
|
123
221
|
}
|
|
124
222
|
}
|
|
125
|
-
}
|
|
223
|
+
};
|
|
224
|
+
// Execute with timeout
|
|
225
|
+
const result = await withTimeout(readOperation(), FILE_READ_TIMEOUT, `Read file operation for ${filePath}`, returnMetadata ?
|
|
226
|
+
{ content: `Operation timed out after ${FILE_READ_TIMEOUT / 1000} seconds`, mimeType: 'text/plain', isImage: false } :
|
|
227
|
+
`Operation timed out after ${FILE_READ_TIMEOUT / 1000} seconds`);
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Read a file from either the local filesystem or a URL
|
|
232
|
+
* @param filePath Path to the file or URL
|
|
233
|
+
* @param returnMetadata Whether to return metadata with the content
|
|
234
|
+
* @param isUrl Whether the path is a URL
|
|
235
|
+
* @returns File content or file result with metadata
|
|
236
|
+
*/
|
|
237
|
+
export async function readFile(filePath, returnMetadata, isUrl) {
|
|
238
|
+
return isUrl
|
|
239
|
+
? readFileFromUrl(filePath, returnMetadata)
|
|
240
|
+
: readFileFromDisk(filePath, returnMetadata);
|
|
126
241
|
}
|
|
127
242
|
export async function writeFile(filePath, content) {
|
|
128
243
|
const validPath = await validatePath(filePath);
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -47,10 +47,13 @@ export declare const UnblockCommandArgsSchema: z.ZodObject<{
|
|
|
47
47
|
}>;
|
|
48
48
|
export declare const ReadFileArgsSchema: z.ZodObject<{
|
|
49
49
|
path: z.ZodString;
|
|
50
|
+
isUrl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
50
51
|
}, "strip", z.ZodTypeAny, {
|
|
51
52
|
path: string;
|
|
53
|
+
isUrl: boolean;
|
|
52
54
|
}, {
|
|
53
55
|
path: string;
|
|
56
|
+
isUrl?: boolean | undefined;
|
|
54
57
|
}>;
|
|
55
58
|
export declare const ReadMultipleFilesArgsSchema: z.ZodObject<{
|
|
56
59
|
paths: z.ZodArray<z.ZodString, "many">;
|
|
@@ -96,12 +99,15 @@ export declare const MoveFileArgsSchema: z.ZodObject<{
|
|
|
96
99
|
export declare const SearchFilesArgsSchema: z.ZodObject<{
|
|
97
100
|
path: z.ZodString;
|
|
98
101
|
pattern: z.ZodString;
|
|
102
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
99
103
|
}, "strip", z.ZodTypeAny, {
|
|
100
104
|
path: string;
|
|
101
105
|
pattern: string;
|
|
106
|
+
timeoutMs?: number | undefined;
|
|
102
107
|
}, {
|
|
103
108
|
path: string;
|
|
104
109
|
pattern: string;
|
|
110
|
+
timeoutMs?: number | undefined;
|
|
105
111
|
}>;
|
|
106
112
|
export declare const GetFileInfoArgsSchema: z.ZodObject<{
|
|
107
113
|
path: z.ZodString;
|
|
@@ -118,9 +124,11 @@ export declare const SearchCodeArgsSchema: z.ZodObject<{
|
|
|
118
124
|
maxResults: z.ZodOptional<z.ZodNumber>;
|
|
119
125
|
includeHidden: z.ZodOptional<z.ZodBoolean>;
|
|
120
126
|
contextLines: z.ZodOptional<z.ZodNumber>;
|
|
127
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
121
128
|
}, "strip", z.ZodTypeAny, {
|
|
122
129
|
path: string;
|
|
123
130
|
pattern: string;
|
|
131
|
+
timeoutMs?: number | undefined;
|
|
124
132
|
filePattern?: string | undefined;
|
|
125
133
|
ignoreCase?: boolean | undefined;
|
|
126
134
|
maxResults?: number | undefined;
|
|
@@ -129,6 +137,7 @@ export declare const SearchCodeArgsSchema: z.ZodObject<{
|
|
|
129
137
|
}, {
|
|
130
138
|
path: string;
|
|
131
139
|
pattern: string;
|
|
140
|
+
timeoutMs?: number | undefined;
|
|
132
141
|
filePattern?: string | undefined;
|
|
133
142
|
ignoreCase?: boolean | undefined;
|
|
134
143
|
maxResults?: number | undefined;
|
package/dist/tools/schemas.js
CHANGED
|
@@ -23,6 +23,7 @@ export const UnblockCommandArgsSchema = z.object({
|
|
|
23
23
|
// Filesystem tools schemas
|
|
24
24
|
export const ReadFileArgsSchema = z.object({
|
|
25
25
|
path: z.string(),
|
|
26
|
+
isUrl: z.boolean().optional().default(false),
|
|
26
27
|
});
|
|
27
28
|
export const ReadMultipleFilesArgsSchema = z.object({
|
|
28
29
|
paths: z.array(z.string()),
|
|
@@ -44,6 +45,7 @@ export const MoveFileArgsSchema = z.object({
|
|
|
44
45
|
export const SearchFilesArgsSchema = z.object({
|
|
45
46
|
path: z.string(),
|
|
46
47
|
pattern: z.string(),
|
|
48
|
+
timeoutMs: z.number().optional(),
|
|
47
49
|
});
|
|
48
50
|
export const GetFileInfoArgsSchema = z.object({
|
|
49
51
|
path: z.string(),
|
|
@@ -57,6 +59,7 @@ export const SearchCodeArgsSchema = z.object({
|
|
|
57
59
|
maxResults: z.number().optional(),
|
|
58
60
|
includeHidden: z.boolean().optional(),
|
|
59
61
|
contextLines: z.number().optional(),
|
|
62
|
+
timeoutMs: z.number().optional(),
|
|
60
63
|
});
|
|
61
64
|
// Edit tools schemas
|
|
62
65
|
export const EditBlockArgsSchema = z.object({
|
package/dist/tools/search.js
CHANGED
|
@@ -35,6 +35,11 @@ export async function searchCode(options) {
|
|
|
35
35
|
const results = [];
|
|
36
36
|
const rg = spawn(rgPath, args);
|
|
37
37
|
let stdoutBuffer = '';
|
|
38
|
+
// Store a reference to the child process for potential termination
|
|
39
|
+
const childProcess = rg;
|
|
40
|
+
// Store in a process list - this could be expanded to a global registry
|
|
41
|
+
// of running search processes if needed for management
|
|
42
|
+
globalThis.currentSearchProcess = childProcess;
|
|
38
43
|
rg.stdout.on('data', (data) => {
|
|
39
44
|
stdoutBuffer += data.toString();
|
|
40
45
|
});
|
|
@@ -42,6 +47,10 @@ export async function searchCode(options) {
|
|
|
42
47
|
console.error(`ripgrep error: ${data}`);
|
|
43
48
|
});
|
|
44
49
|
rg.on('close', (code) => {
|
|
50
|
+
// Clean up the global reference
|
|
51
|
+
if (globalThis.currentSearchProcess === childProcess) {
|
|
52
|
+
delete globalThis.currentSearchProcess;
|
|
53
|
+
}
|
|
45
54
|
if (code === 0 || code === 1) {
|
|
46
55
|
// Process the buffered output
|
|
47
56
|
const lines = stdoutBuffer.trim().split('\n');
|
package/dist/utils.d.ts
CHANGED
|
@@ -1 +1,12 @@
|
|
|
1
1
|
export declare const capture: (event: string, properties?: any) => void;
|
|
2
|
+
/**
|
|
3
|
+
* Executes a promise with a timeout. If the promise doesn't resolve or reject within
|
|
4
|
+
* the specified timeout, returns the provided default value.
|
|
5
|
+
*
|
|
6
|
+
* @param operation The promise to execute
|
|
7
|
+
* @param timeoutMs Timeout in milliseconds
|
|
8
|
+
* @param operationName Name of the operation (for logs)
|
|
9
|
+
* @param defaultValue Value to return if the operation times out
|
|
10
|
+
* @returns Promise that resolves with the operation result or the default value on timeout
|
|
11
|
+
*/
|
|
12
|
+
export declare function withTimeout<T>(operation: Promise<T>, timeoutMs: number, operationName: string, defaultValue: T): Promise<T>;
|
package/dist/utils.js
CHANGED
|
@@ -19,7 +19,7 @@ try {
|
|
|
19
19
|
// Access the default export from the module
|
|
20
20
|
uniqueUserId = machineIdModule.default.machineIdSync();
|
|
21
21
|
if (isTrackingEnabled) {
|
|
22
|
-
posthog = new PostHog('
|
|
22
|
+
posthog = new PostHog('phc_BW8KJ0cajzj2v8qfMhvDQ4dtFdgHPzeYcMRvRFGvQdH', {
|
|
23
23
|
host: 'https://eu.i.posthog.com',
|
|
24
24
|
flushAt: 3, // send all every time
|
|
25
25
|
flushInterval: 5 // send always
|
|
@@ -54,3 +54,41 @@ export const capture = (event, properties) => {
|
|
|
54
54
|
// Silently fail - we don't want analytics issues to break functionality
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* Executes a promise with a timeout. If the promise doesn't resolve or reject within
|
|
59
|
+
* the specified timeout, returns the provided default value.
|
|
60
|
+
*
|
|
61
|
+
* @param operation The promise to execute
|
|
62
|
+
* @param timeoutMs Timeout in milliseconds
|
|
63
|
+
* @param operationName Name of the operation (for logs)
|
|
64
|
+
* @param defaultValue Value to return if the operation times out
|
|
65
|
+
* @returns Promise that resolves with the operation result or the default value on timeout
|
|
66
|
+
*/
|
|
67
|
+
export function withTimeout(operation, timeoutMs, operationName, defaultValue) {
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
let isCompleted = false;
|
|
70
|
+
// Set up timeout
|
|
71
|
+
const timeoutId = setTimeout(() => {
|
|
72
|
+
if (!isCompleted) {
|
|
73
|
+
isCompleted = true;
|
|
74
|
+
resolve(defaultValue);
|
|
75
|
+
}
|
|
76
|
+
}, timeoutMs);
|
|
77
|
+
// Execute the operation
|
|
78
|
+
operation
|
|
79
|
+
.then(result => {
|
|
80
|
+
if (!isCompleted) {
|
|
81
|
+
isCompleted = true;
|
|
82
|
+
clearTimeout(timeoutId);
|
|
83
|
+
resolve(result);
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
.catch(error => {
|
|
87
|
+
if (!isCompleted) {
|
|
88
|
+
isCompleted = true;
|
|
89
|
+
clearTimeout(timeoutId);
|
|
90
|
+
resolve(defaultValue);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.33";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.1.
|
|
1
|
+
export const VERSION = '0.1.33';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wonderwhy-er/desktop-commander",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "MCP server for terminal operations and file editing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eduards Ruzga",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
63
63
|
"@vscode/ripgrep": "^1.15.9",
|
|
64
|
+
"cross-fetch": "^4.1.0",
|
|
64
65
|
"glob": "^10.3.10",
|
|
65
66
|
"node-machine-id": "^1.1.12",
|
|
66
67
|
"posthog-node": "^4.11.1",
|