@wonderwhy-er/desktop-commander 0.1.38 → 0.2.0
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 +89 -6
- package/dist/REPLSessionManager.d.ts +109 -0
- package/dist/REPLSessionManager.js +364 -0
- package/dist/REPLSessionManager.test.d.ts +1 -0
- package/dist/REPLSessionManager.test.js +75 -0
- package/dist/client/replClient.d.ts +63 -0
- package/dist/client/replClient.js +217 -0
- package/dist/client/sshClient.d.ts +82 -0
- package/dist/client/sshClient.js +200 -0
- package/dist/config-manager.d.ts +2 -0
- package/dist/config-manager.js +3 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +1 -0
- package/dist/handlers/filesystem-handlers.js +37 -3
- package/dist/handlers/fuzzy-search-log-handlers.d.ts +13 -0
- package/dist/handlers/fuzzy-search-log-handlers.js +179 -0
- package/dist/handlers/repl-handlers.d.ts +21 -0
- package/dist/handlers/repl-handlers.js +37 -0
- package/dist/handlers/replCommandHandler.d.ts +125 -0
- package/dist/handlers/replCommandHandler.js +255 -0
- package/dist/handlers/replCommandHandler.test.d.ts +1 -0
- package/dist/handlers/replCommandHandler.test.js +103 -0
- package/dist/repl-manager.d.ts +73 -0
- package/dist/repl-manager.js +407 -0
- package/dist/replIntegration.d.ts +14 -0
- package/dist/replIntegration.js +27 -0
- package/dist/server.js +37 -19
- package/dist/setup-claude-server.js +0 -20
- package/dist/tools/edit.js +129 -29
- package/dist/tools/enhanced-read-output.js +69 -0
- package/dist/tools/enhanced-send-input.js +111 -0
- package/dist/tools/filesystem.d.ts +7 -5
- package/dist/tools/filesystem.js +56 -27
- package/dist/tools/repl.d.ts +21 -0
- package/dist/tools/repl.js +217 -0
- package/dist/tools/schemas.d.ts +9 -0
- package/dist/tools/schemas.js +3 -0
- package/dist/tools/send-input.d.ts +2 -0
- package/dist/tools/send-input.js +45 -0
- package/dist/utils/fuzzySearchLogger.d.ts +30 -0
- package/dist/utils/fuzzySearchLogger.js +126 -0
- package/dist/utils/lineEndingHandler.d.ts +21 -0
- package/dist/utils/lineEndingHandler.js +77 -0
- package/dist/utils/lineEndingHandler_optimized.d.ts +21 -0
- package/dist/utils/lineEndingHandler_optimized.js +77 -0
- package/dist/utils/trackTools.d.ts +6 -0
- package/dist/utils/trackTools.js +54 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -2
package/dist/tools/filesystem.js
CHANGED
|
@@ -208,38 +208,41 @@ export async function readFileFromUrl(url) {
|
|
|
208
208
|
/**
|
|
209
209
|
* Read file content from the local filesystem
|
|
210
210
|
* @param filePath Path to the file
|
|
211
|
-
* @param
|
|
211
|
+
* @param offset Starting line number to read from (default: 0)
|
|
212
|
+
* @param length Maximum number of lines to read (default: from config or 1000)
|
|
212
213
|
* @returns File content or file result with metadata
|
|
213
214
|
*/
|
|
214
|
-
export async function readFileFromDisk(filePath) {
|
|
215
|
+
export async function readFileFromDisk(filePath, offset = 0, length) {
|
|
216
|
+
// Add validation for required parameters
|
|
217
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
218
|
+
throw new Error('Invalid file path provided');
|
|
219
|
+
}
|
|
215
220
|
// Import the MIME type utilities
|
|
216
221
|
const { getMimeType, isImageFile } = await import('./mime-types.js');
|
|
222
|
+
// Get default length from config if not provided
|
|
223
|
+
if (length === undefined) {
|
|
224
|
+
const config = await configManager.getConfig();
|
|
225
|
+
length = config.fileReadLineLimit ?? 1000; // Default to 1000 lines if not set
|
|
226
|
+
}
|
|
217
227
|
const validPath = await validatePath(filePath);
|
|
218
228
|
// Get file extension for telemetry using path module consistently
|
|
219
229
|
const fileExtension = path.extname(validPath).toLowerCase();
|
|
220
230
|
// Check file size before attempting to read
|
|
221
231
|
try {
|
|
222
232
|
const stats = await fs.stat(validPath);
|
|
223
|
-
const MAX_SIZE = 100 * 1024; // 100KB limit
|
|
224
|
-
if (stats.size > MAX_SIZE) {
|
|
225
|
-
const message = `File too large (${(stats.size / 1024).toFixed(2)}KB > ${MAX_SIZE / 1024}KB limit)`;
|
|
226
|
-
// Capture file extension in telemetry without capturing the file path
|
|
227
|
-
capture('server_read_file_large', { fileExtension: fileExtension });
|
|
228
|
-
return {
|
|
229
|
-
content: message,
|
|
230
|
-
mimeType: 'text/plain',
|
|
231
|
-
isImage: false
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
233
|
// Capture file extension in telemetry without capturing the file path
|
|
235
|
-
capture('server_read_file', {
|
|
234
|
+
capture('server_read_file', {
|
|
235
|
+
fileExtension: fileExtension,
|
|
236
|
+
offset: offset,
|
|
237
|
+
length: length,
|
|
238
|
+
fileSize: stats.size
|
|
239
|
+
});
|
|
236
240
|
}
|
|
237
241
|
catch (error) {
|
|
238
242
|
console.error('error catch ' + error);
|
|
239
243
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
240
244
|
capture('server_read_file_error', { error: errorMessage, fileExtension: fileExtension });
|
|
241
245
|
// If we can't stat the file, continue anyway and let the read operation handle errors
|
|
242
|
-
//console.error(`Failed to stat file ${validPath}:`, error);
|
|
243
246
|
}
|
|
244
247
|
// Detect the MIME type based on file extension
|
|
245
248
|
const mimeType = getMimeType(validPath);
|
|
@@ -249,14 +252,30 @@ export async function readFileFromDisk(filePath) {
|
|
|
249
252
|
const readOperation = async () => {
|
|
250
253
|
if (isImage) {
|
|
251
254
|
// For image files, read as Buffer and convert to base64
|
|
255
|
+
// Images are always read in full, ignoring offset and length
|
|
252
256
|
const buffer = await fs.readFile(validPath);
|
|
253
257
|
const content = buffer.toString('base64');
|
|
254
258
|
return { content, mimeType, isImage };
|
|
255
259
|
}
|
|
256
260
|
else {
|
|
257
|
-
// For all other files, try to read as UTF-8 text
|
|
261
|
+
// For all other files, try to read as UTF-8 text with line-based offset and length
|
|
258
262
|
try {
|
|
259
|
-
|
|
263
|
+
// Read the entire file first
|
|
264
|
+
const buffer = await fs.readFile(validPath);
|
|
265
|
+
const fullContent = buffer.toString('utf-8');
|
|
266
|
+
// Split into lines for line-based access
|
|
267
|
+
const lines = fullContent.split('\n');
|
|
268
|
+
const totalLines = lines.length;
|
|
269
|
+
// Apply line-based offset and length
|
|
270
|
+
const startLine = Math.min(offset, totalLines);
|
|
271
|
+
const endLine = Math.min(startLine + length, totalLines);
|
|
272
|
+
const selectedLines = lines.slice(startLine, endLine);
|
|
273
|
+
const truncatedContent = selectedLines.join('\n');
|
|
274
|
+
// Add an informational message if truncated
|
|
275
|
+
let content = truncatedContent;
|
|
276
|
+
if (offset > 0 || endLine < totalLines) {
|
|
277
|
+
content = `[Reading ${endLine - startLine} lines from line ${offset} of ${totalLines} total lines]\n\n${truncatedContent}`;
|
|
278
|
+
}
|
|
260
279
|
return { content, mimeType, isImage };
|
|
261
280
|
}
|
|
262
281
|
catch (error) {
|
|
@@ -278,22 +297,32 @@ export async function readFileFromDisk(filePath) {
|
|
|
278
297
|
/**
|
|
279
298
|
* Read a file from either the local filesystem or a URL
|
|
280
299
|
* @param filePath Path to the file or URL
|
|
281
|
-
* @param returnMetadata Whether to return metadata with the content
|
|
282
300
|
* @param isUrl Whether the path is a URL
|
|
301
|
+
* @param offset Starting line number to read from (default: 0)
|
|
302
|
+
* @param length Maximum number of lines to read (default: from config or 1000)
|
|
283
303
|
* @returns File content or file result with metadata
|
|
284
304
|
*/
|
|
285
|
-
export async function readFile(filePath, isUrl) {
|
|
305
|
+
export async function readFile(filePath, isUrl, offset, length) {
|
|
286
306
|
return isUrl
|
|
287
307
|
? readFileFromUrl(filePath)
|
|
288
|
-
: readFileFromDisk(filePath);
|
|
308
|
+
: readFileFromDisk(filePath, offset, length);
|
|
289
309
|
}
|
|
290
|
-
export async function writeFile(filePath, content) {
|
|
310
|
+
export async function writeFile(filePath, content, mode = 'rewrite') {
|
|
291
311
|
const validPath = await validatePath(filePath);
|
|
292
312
|
// Get file extension for telemetry
|
|
293
313
|
const fileExtension = path.extname(validPath).toLowerCase();
|
|
294
|
-
// Capture file extension in telemetry without capturing the file path
|
|
295
|
-
capture('server_write_file', {
|
|
296
|
-
|
|
314
|
+
// Capture file extension and operation details in telemetry without capturing the file path
|
|
315
|
+
capture('server_write_file', {
|
|
316
|
+
fileExtension: fileExtension,
|
|
317
|
+
mode: mode,
|
|
318
|
+
});
|
|
319
|
+
// Use different fs methods based on mode
|
|
320
|
+
if (mode === 'append') {
|
|
321
|
+
await fs.appendFile(validPath, content);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
await fs.writeFile(validPath, content);
|
|
325
|
+
}
|
|
297
326
|
}
|
|
298
327
|
export async function readMultipleFiles(paths) {
|
|
299
328
|
return Promise.all(paths.map(async (filePath) => {
|
|
@@ -339,9 +368,9 @@ export async function searchFiles(rootPath, pattern) {
|
|
|
339
368
|
}
|
|
340
369
|
catch (error) {
|
|
341
370
|
// Only sanitize for telemetry, not for the returned error
|
|
342
|
-
capture('
|
|
371
|
+
capture('server_search_read_dir_error', {
|
|
343
372
|
errorType: error instanceof Error ? error.name : 'Unknown',
|
|
344
|
-
|
|
373
|
+
error: 'Error reading directory',
|
|
345
374
|
isReadDirError: true
|
|
346
375
|
});
|
|
347
376
|
return; // Skip this directory on error
|
|
@@ -377,7 +406,7 @@ export async function searchFiles(rootPath, pattern) {
|
|
|
377
406
|
// For telemetry only - sanitize error info
|
|
378
407
|
capture('server_search_files_error', {
|
|
379
408
|
errorType: error instanceof Error ? error.name : 'Unknown',
|
|
380
|
-
|
|
409
|
+
error: 'Error with root path',
|
|
381
410
|
isRootPathError: true
|
|
382
411
|
});
|
|
383
412
|
// Re-throw the original error for the caller
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ServerResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a new REPL session
|
|
4
|
+
*/
|
|
5
|
+
export declare function createREPLSession(args: unknown): Promise<ServerResult>;
|
|
6
|
+
/**
|
|
7
|
+
* Execute code in a REPL session
|
|
8
|
+
*/
|
|
9
|
+
export declare function executeREPLCode(args: unknown): Promise<ServerResult>;
|
|
10
|
+
/**
|
|
11
|
+
* Terminate a REPL session
|
|
12
|
+
*/
|
|
13
|
+
export declare function terminateREPLSession(args: unknown): Promise<ServerResult>;
|
|
14
|
+
/**
|
|
15
|
+
* List all active REPL sessions
|
|
16
|
+
*/
|
|
17
|
+
export declare function listREPLSessions(args: unknown): Promise<ServerResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Get information about a specific REPL session
|
|
20
|
+
*/
|
|
21
|
+
export declare function getREPLSessionInfo(args: unknown): Promise<ServerResult>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { replManager } from '../repl-manager.js';
|
|
2
|
+
import { capture } from '../utils/capture.js';
|
|
3
|
+
import { CreateREPLSessionArgsSchema, ExecuteREPLCodeArgsSchema, TerminateREPLSessionArgsSchema, ListREPLSessionsArgsSchema, GetREPLSessionInfoArgsSchema } from './schemas.js';
|
|
4
|
+
/**
|
|
5
|
+
* Create a new REPL session
|
|
6
|
+
*/
|
|
7
|
+
export async function createREPLSession(args) {
|
|
8
|
+
const parsed = CreateREPLSessionArgsSchema.safeParse(args);
|
|
9
|
+
if (!parsed.success) {
|
|
10
|
+
capture('server_create_repl_session_failed', {
|
|
11
|
+
error: 'Invalid arguments'
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: `Error: Invalid arguments for create_repl_session: ${parsed.error}` }],
|
|
15
|
+
isError: true,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const pid = await replManager.createSession(parsed.data.language, parsed.data.timeout);
|
|
20
|
+
return {
|
|
21
|
+
content: [{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: `Successfully created ${parsed.data.language} REPL session with PID ${pid}`
|
|
24
|
+
}],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
29
|
+
capture('server_create_repl_session_error', {
|
|
30
|
+
error: errorMessage
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `Error creating REPL session: ${errorMessage}` }],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Execute code in a REPL session
|
|
40
|
+
*/
|
|
41
|
+
export async function executeREPLCode(args) {
|
|
42
|
+
const parsed = ExecuteREPLCodeArgsSchema.safeParse(args);
|
|
43
|
+
if (!parsed.success) {
|
|
44
|
+
capture('server_execute_repl_code_failed', {
|
|
45
|
+
error: 'Invalid arguments'
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: "text", text: `Error: Invalid arguments for execute_repl_code: ${parsed.error}` }],
|
|
49
|
+
isError: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const result = await replManager.executeCode(parsed.data.pid, parsed.data.code, {
|
|
54
|
+
timeout: parsed.data.timeout,
|
|
55
|
+
waitForPrompt: parsed.data.waitForPrompt,
|
|
56
|
+
ignoreErrors: parsed.data.ignoreErrors,
|
|
57
|
+
multiline: parsed.data.multiline
|
|
58
|
+
});
|
|
59
|
+
if (!result.success) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: result.error
|
|
64
|
+
? `Error executing code: ${result.error}`
|
|
65
|
+
: `Failed to execute code in REPL session ${parsed.data.pid}` +
|
|
66
|
+
(result.timeout ? ` (timed out)` : '') +
|
|
67
|
+
(result.output ? `\nOutput received: ${result.output}` : '')
|
|
68
|
+
}],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
content: [{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: `Code executed in REPL session ${parsed.data.pid}` +
|
|
76
|
+
(result.timeout ? ` (timed out, partial output below)` : '') +
|
|
77
|
+
`\n\nOutput:\n${result.output || '(No output)'}`
|
|
78
|
+
}],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
83
|
+
capture('server_execute_repl_code_error', {
|
|
84
|
+
error: errorMessage
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: `Error executing REPL code: ${errorMessage}` }],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Terminate a REPL session
|
|
94
|
+
*/
|
|
95
|
+
export async function terminateREPLSession(args) {
|
|
96
|
+
const parsed = TerminateREPLSessionArgsSchema.safeParse(args);
|
|
97
|
+
if (!parsed.success) {
|
|
98
|
+
capture('server_terminate_repl_session_failed', {
|
|
99
|
+
error: 'Invalid arguments'
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: `Error: Invalid arguments for terminate_repl_session: ${parsed.error}` }],
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const success = await replManager.terminateSession(parsed.data.pid);
|
|
108
|
+
if (!success) {
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text: `Failed to terminate REPL session ${parsed.data.pid}. The session may have already ended.` }],
|
|
111
|
+
isError: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
content: [{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: `Successfully terminated REPL session ${parsed.data.pid}`
|
|
118
|
+
}],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
123
|
+
capture('server_terminate_repl_session_error', {
|
|
124
|
+
error: errorMessage
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: "text", text: `Error terminating REPL session: ${errorMessage}` }],
|
|
128
|
+
isError: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* List all active REPL sessions
|
|
134
|
+
*/
|
|
135
|
+
export async function listREPLSessions(args) {
|
|
136
|
+
const parsed = ListREPLSessionsArgsSchema.safeParse(args);
|
|
137
|
+
if (!parsed.success) {
|
|
138
|
+
capture('server_list_repl_sessions_failed', {
|
|
139
|
+
error: 'Invalid arguments'
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text", text: `Error: Invalid arguments for list_repl_sessions: ${parsed.error}` }],
|
|
143
|
+
isError: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const sessions = replManager.listSessions();
|
|
148
|
+
if (sessions.length === 0) {
|
|
149
|
+
return {
|
|
150
|
+
content: [{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: `No active REPL sessions found`
|
|
153
|
+
}],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const sessionsText = sessions.map(s => `PID: ${s.pid}, Language: ${s.language}, Runtime: ${Math.round(s.runtime / 1000)}s`).join('\n');
|
|
157
|
+
return {
|
|
158
|
+
content: [{
|
|
159
|
+
type: "text",
|
|
160
|
+
text: `Active REPL sessions:\n${sessionsText}`
|
|
161
|
+
}],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
166
|
+
capture('server_list_repl_sessions_error', {
|
|
167
|
+
error: errorMessage
|
|
168
|
+
});
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: "text", text: `Error listing REPL sessions: ${errorMessage}` }],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get information about a specific REPL session
|
|
177
|
+
*/
|
|
178
|
+
export async function getREPLSessionInfo(args) {
|
|
179
|
+
const parsed = GetREPLSessionInfoArgsSchema.safeParse(args);
|
|
180
|
+
if (!parsed.success) {
|
|
181
|
+
capture('server_get_repl_session_info_failed', {
|
|
182
|
+
error: 'Invalid arguments'
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: "text", text: `Error: Invalid arguments for get_repl_session_info: ${parsed.error}` }],
|
|
186
|
+
isError: true,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const info = replManager.getSessionInfo(parsed.data.pid);
|
|
191
|
+
if (!info) {
|
|
192
|
+
return {
|
|
193
|
+
content: [{
|
|
194
|
+
type: "text",
|
|
195
|
+
text: `No active REPL session found with PID ${parsed.data.pid}`
|
|
196
|
+
}],
|
|
197
|
+
isError: true,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
content: [{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: `REPL Session ${parsed.data.pid}:\nLanguage: ${info.language}\nRuntime: ${Math.round(info.runtime / 1000)}s`
|
|
204
|
+
}],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
209
|
+
capture('server_get_repl_session_info_error', {
|
|
210
|
+
error: errorMessage
|
|
211
|
+
});
|
|
212
|
+
return {
|
|
213
|
+
content: [{ type: "text", text: `Error getting REPL session info: ${errorMessage}` }],
|
|
214
|
+
isError: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -49,12 +49,18 @@ export declare const KillProcessArgsSchema: z.ZodObject<{
|
|
|
49
49
|
export declare const ReadFileArgsSchema: z.ZodObject<{
|
|
50
50
|
path: z.ZodString;
|
|
51
51
|
isUrl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
52
|
+
offset: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
53
|
+
length: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
52
54
|
}, "strip", z.ZodTypeAny, {
|
|
53
55
|
path: string;
|
|
56
|
+
length: number;
|
|
54
57
|
isUrl: boolean;
|
|
58
|
+
offset: number;
|
|
55
59
|
}, {
|
|
56
60
|
path: string;
|
|
61
|
+
length?: number | undefined;
|
|
57
62
|
isUrl?: boolean | undefined;
|
|
63
|
+
offset?: number | undefined;
|
|
58
64
|
}>;
|
|
59
65
|
export declare const ReadMultipleFilesArgsSchema: z.ZodObject<{
|
|
60
66
|
paths: z.ZodArray<z.ZodString, "many">;
|
|
@@ -66,12 +72,15 @@ export declare const ReadMultipleFilesArgsSchema: z.ZodObject<{
|
|
|
66
72
|
export declare const WriteFileArgsSchema: z.ZodObject<{
|
|
67
73
|
path: z.ZodString;
|
|
68
74
|
content: z.ZodString;
|
|
75
|
+
mode: z.ZodDefault<z.ZodEnum<["rewrite", "append"]>>;
|
|
69
76
|
}, "strip", z.ZodTypeAny, {
|
|
70
77
|
path: string;
|
|
71
78
|
content: string;
|
|
79
|
+
mode: "rewrite" | "append";
|
|
72
80
|
}, {
|
|
73
81
|
path: string;
|
|
74
82
|
content: string;
|
|
83
|
+
mode?: "rewrite" | "append" | undefined;
|
|
75
84
|
}>;
|
|
76
85
|
export declare const CreateDirectoryArgsSchema: z.ZodObject<{
|
|
77
86
|
path: z.ZodString;
|
package/dist/tools/schemas.js
CHANGED
|
@@ -28,6 +28,8 @@ export const KillProcessArgsSchema = z.object({
|
|
|
28
28
|
export const ReadFileArgsSchema = z.object({
|
|
29
29
|
path: z.string(),
|
|
30
30
|
isUrl: z.boolean().optional().default(false),
|
|
31
|
+
offset: z.number().optional().default(0),
|
|
32
|
+
length: z.number().optional().default(1000),
|
|
31
33
|
});
|
|
32
34
|
export const ReadMultipleFilesArgsSchema = z.object({
|
|
33
35
|
paths: z.array(z.string()),
|
|
@@ -35,6 +37,7 @@ export const ReadMultipleFilesArgsSchema = z.object({
|
|
|
35
37
|
export const WriteFileArgsSchema = z.object({
|
|
36
38
|
path: z.string(),
|
|
37
39
|
content: z.string(),
|
|
40
|
+
mode: z.enum(['rewrite', 'append']).default('rewrite'),
|
|
38
41
|
});
|
|
39
42
|
export const CreateDirectoryArgsSchema = z.object({
|
|
40
43
|
path: z.string(),
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { terminalManager } from '../terminal-manager.js';
|
|
2
|
+
import { SendInputArgsSchema } from './schemas.js';
|
|
3
|
+
import { capture } from "../utils/capture.js";
|
|
4
|
+
export async function sendInput(args) {
|
|
5
|
+
const parsed = SendInputArgsSchema.safeParse(args);
|
|
6
|
+
if (!parsed.success) {
|
|
7
|
+
capture('server_send_input_failed', {
|
|
8
|
+
error: 'Invalid arguments'
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: "text", text: `Error: Invalid arguments for send_input: ${parsed.error}` }],
|
|
12
|
+
isError: true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
capture('server_send_input', {
|
|
17
|
+
pid: parsed.data.pid,
|
|
18
|
+
inputLength: parsed.data.input.length
|
|
19
|
+
});
|
|
20
|
+
// Try to send input to the process
|
|
21
|
+
const success = terminalManager.sendInputToProcess(parsed.data.pid, parsed.data.input);
|
|
22
|
+
if (!success) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: `Error: Failed to send input to process ${parsed.data.pid}. The process may have exited or doesn't accept input.` }],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: `Successfully sent input to process ${parsed.data.pid}. Use read_output to get the process response.`
|
|
32
|
+
}],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
37
|
+
capture('server_send_input_error', {
|
|
38
|
+
error: errorMessage
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: `Error sending input: ${errorMessage}` }],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface FuzzySearchLogEntry {
|
|
2
|
+
timestamp: Date;
|
|
3
|
+
searchText: string;
|
|
4
|
+
foundText: string;
|
|
5
|
+
similarity: number;
|
|
6
|
+
executionTime: number;
|
|
7
|
+
exactMatchCount: number;
|
|
8
|
+
expectedReplacements: number;
|
|
9
|
+
fuzzyThreshold: number;
|
|
10
|
+
belowThreshold: boolean;
|
|
11
|
+
diff: string;
|
|
12
|
+
searchLength: number;
|
|
13
|
+
foundLength: number;
|
|
14
|
+
fileExtension: string;
|
|
15
|
+
characterCodes: string;
|
|
16
|
+
uniqueCharacterCount: number;
|
|
17
|
+
diffLength: number;
|
|
18
|
+
}
|
|
19
|
+
declare class FuzzySearchLogger {
|
|
20
|
+
private logPath;
|
|
21
|
+
private initialized;
|
|
22
|
+
constructor();
|
|
23
|
+
private ensureLogFile;
|
|
24
|
+
log(entry: FuzzySearchLogEntry): Promise<void>;
|
|
25
|
+
getLogPath(): Promise<string>;
|
|
26
|
+
getRecentLogs(count?: number): Promise<string[]>;
|
|
27
|
+
clearLog(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare const fuzzySearchLogger: FuzzySearchLogger;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
class FuzzySearchLogger {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.initialized = false;
|
|
7
|
+
// Create log file in a dedicated directory
|
|
8
|
+
const logDir = path.join(os.homedir(), '.claude-server-commander-logs');
|
|
9
|
+
this.logPath = path.join(logDir, 'fuzzy-search.log');
|
|
10
|
+
}
|
|
11
|
+
async ensureLogFile() {
|
|
12
|
+
if (this.initialized)
|
|
13
|
+
return;
|
|
14
|
+
try {
|
|
15
|
+
// Create log directory if it doesn't exist
|
|
16
|
+
const logDir = path.dirname(this.logPath);
|
|
17
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
18
|
+
// Check if log file exists, create with headers if not
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(this.logPath);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// File doesn't exist, create with headers
|
|
24
|
+
const headers = [
|
|
25
|
+
'timestamp',
|
|
26
|
+
'searchText',
|
|
27
|
+
'foundText',
|
|
28
|
+
'similarity',
|
|
29
|
+
'executionTime',
|
|
30
|
+
'exactMatchCount',
|
|
31
|
+
'expectedReplacements',
|
|
32
|
+
'fuzzyThreshold',
|
|
33
|
+
'belowThreshold',
|
|
34
|
+
'diff',
|
|
35
|
+
'searchLength',
|
|
36
|
+
'foundLength',
|
|
37
|
+
'fileExtension',
|
|
38
|
+
'characterCodes',
|
|
39
|
+
'uniqueCharacterCount',
|
|
40
|
+
'diffLength'
|
|
41
|
+
].join('\t');
|
|
42
|
+
await fs.writeFile(this.logPath, headers + '\n');
|
|
43
|
+
}
|
|
44
|
+
this.initialized = true;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error('Failed to initialize fuzzy search log file:', error);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async log(entry) {
|
|
52
|
+
try {
|
|
53
|
+
await this.ensureLogFile();
|
|
54
|
+
// Convert entry to tab-separated string
|
|
55
|
+
const logLine = [
|
|
56
|
+
entry.timestamp.toISOString(),
|
|
57
|
+
entry.searchText.replace(/\n/g, '\\n').replace(/\t/g, '\\t'),
|
|
58
|
+
entry.foundText.replace(/\n/g, '\\n').replace(/\t/g, '\\t'),
|
|
59
|
+
entry.similarity.toString(),
|
|
60
|
+
entry.executionTime.toString(),
|
|
61
|
+
entry.exactMatchCount.toString(),
|
|
62
|
+
entry.expectedReplacements.toString(),
|
|
63
|
+
entry.fuzzyThreshold.toString(),
|
|
64
|
+
entry.belowThreshold.toString(),
|
|
65
|
+
entry.diff.replace(/\n/g, '\\n').replace(/\t/g, '\\t'),
|
|
66
|
+
entry.searchLength.toString(),
|
|
67
|
+
entry.foundLength.toString(),
|
|
68
|
+
entry.fileExtension,
|
|
69
|
+
entry.characterCodes,
|
|
70
|
+
entry.uniqueCharacterCount.toString(),
|
|
71
|
+
entry.diffLength.toString()
|
|
72
|
+
].join('\t');
|
|
73
|
+
await fs.appendFile(this.logPath, logLine + '\n');
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('Failed to write to fuzzy search log:', error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async getLogPath() {
|
|
80
|
+
await this.ensureLogFile();
|
|
81
|
+
return this.logPath;
|
|
82
|
+
}
|
|
83
|
+
async getRecentLogs(count = 10) {
|
|
84
|
+
try {
|
|
85
|
+
await this.ensureLogFile();
|
|
86
|
+
const content = await fs.readFile(this.logPath, 'utf-8');
|
|
87
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
88
|
+
// Return last N lines (excluding header)
|
|
89
|
+
return lines.slice(-count - 1, -1);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error('Failed to read fuzzy search logs:', error);
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async clearLog() {
|
|
97
|
+
try {
|
|
98
|
+
// Recreate with just headers
|
|
99
|
+
const headers = [
|
|
100
|
+
'timestamp',
|
|
101
|
+
'searchText',
|
|
102
|
+
'foundText',
|
|
103
|
+
'similarity',
|
|
104
|
+
'executionTime',
|
|
105
|
+
'exactMatchCount',
|
|
106
|
+
'expectedReplacements',
|
|
107
|
+
'fuzzyThreshold',
|
|
108
|
+
'belowThreshold',
|
|
109
|
+
'diff',
|
|
110
|
+
'searchLength',
|
|
111
|
+
'foundLength',
|
|
112
|
+
'fileExtension',
|
|
113
|
+
'characterCodes',
|
|
114
|
+
'uniqueCharacterCount',
|
|
115
|
+
'diffLength'
|
|
116
|
+
].join('\t');
|
|
117
|
+
await fs.writeFile(this.logPath, headers + '\n');
|
|
118
|
+
console.log('Fuzzy search log cleared');
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error('Failed to clear fuzzy search log:', error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Singleton instance
|
|
126
|
+
export const fuzzySearchLogger = new FuzzySearchLogger();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line ending types
|
|
3
|
+
*/
|
|
4
|
+
export type LineEndingStyle = '\r\n' | '\n' | '\r';
|
|
5
|
+
/**
|
|
6
|
+
* Detect the line ending style used in a file - Optimized version
|
|
7
|
+
* This algorithm uses early termination for maximum performance
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectLineEnding(content: string): LineEndingStyle;
|
|
10
|
+
/**
|
|
11
|
+
* Normalize line endings to match the target style
|
|
12
|
+
*/
|
|
13
|
+
export declare function normalizeLineEndings(text: string, targetLineEnding: LineEndingStyle): string;
|
|
14
|
+
/**
|
|
15
|
+
* Analyze line ending usage in content
|
|
16
|
+
*/
|
|
17
|
+
export declare function analyzeLineEndings(content: string): {
|
|
18
|
+
style: LineEndingStyle;
|
|
19
|
+
count: number;
|
|
20
|
+
hasMixed: boolean;
|
|
21
|
+
};
|