@wonderwhy-er/desktop-commander 0.1.34 → 0.1.36
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/LICENSE +2 -2
- package/README.md +186 -56
- package/dist/command-manager.d.ts +1 -7
- package/dist/command-manager.js +31 -50
- package/dist/config-manager.d.ts +28 -16
- package/dist/config-manager.js +124 -189
- package/dist/config.d.ts +2 -2
- package/dist/config.js +7 -4
- package/dist/error-handlers.js +4 -0
- package/dist/handlers/edit-search-handlers.d.ts +3 -1
- package/dist/handlers/edit-search-handlers.js +9 -19
- package/dist/handlers/filesystem-handlers.d.ts +0 -4
- package/dist/handlers/filesystem-handlers.js +11 -19
- package/dist/handlers/index.d.ts +0 -1
- package/dist/handlers/index.js +0 -1
- package/dist/index.js +19 -4
- package/dist/polyform-license-src/edit/edit.d.ts +15 -0
- package/dist/polyform-license-src/edit/edit.js +163 -0
- package/dist/polyform-license-src/edit/fuzzySearch.d.ts +30 -0
- package/dist/polyform-license-src/edit/fuzzySearch.js +121 -0
- package/dist/polyform-license-src/edit/handlers.d.ts +16 -0
- package/dist/polyform-license-src/edit/handlers.js +24 -0
- package/dist/polyform-license-src/edit/index.d.ts +12 -0
- package/dist/polyform-license-src/edit/index.js +13 -0
- package/dist/polyform-license-src/edit/schemas.d.ts +25 -0
- package/dist/polyform-license-src/edit/schemas.js +16 -0
- package/dist/polyform-license-src/index.d.ts +9 -0
- package/dist/polyform-license-src/index.js +10 -0
- package/dist/sandbox/index.d.ts +9 -0
- package/dist/sandbox/index.js +50 -0
- package/dist/sandbox/mac-sandbox.d.ts +19 -0
- package/dist/sandbox/mac-sandbox.js +174 -0
- package/dist/server.js +181 -176
- package/dist/setup-claude-server.js +554 -244
- package/dist/terminal-manager.d.ts +1 -1
- package/dist/terminal-manager.js +22 -3
- package/dist/tools/config.d.ts +0 -58
- package/dist/tools/config.js +44 -107
- package/dist/tools/debug-path.d.ts +1 -0
- package/dist/tools/debug-path.js +44 -0
- package/dist/tools/edit.d.ts +8 -6
- package/dist/tools/edit.js +165 -35
- package/dist/tools/execute.js +6 -6
- package/dist/tools/filesystem-fixed.d.ts +22 -0
- package/dist/tools/filesystem-fixed.js +176 -0
- package/dist/tools/filesystem.d.ts +4 -6
- package/dist/tools/filesystem.js +157 -87
- package/dist/tools/fuzzySearch.d.ts +22 -0
- package/dist/tools/fuzzySearch.js +113 -0
- package/dist/tools/pdf-reader.d.ts +13 -0
- package/dist/tools/pdf-reader.js +214 -0
- package/dist/tools/schemas.d.ts +29 -19
- package/dist/tools/schemas.js +15 -8
- package/dist/tools/search.js +5 -4
- package/dist/utils/capture.d.ts +15 -0
- package/dist/utils/capture.js +175 -0
- package/dist/utils/withTimeout.d.ts +11 -0
- package/dist/utils/withTimeout.js +52 -0
- package/dist/utils.d.ts +15 -1
- package/dist/utils.js +174 -41
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -3
|
@@ -9,7 +9,7 @@ interface CompletedSession {
|
|
|
9
9
|
export declare class TerminalManager {
|
|
10
10
|
private sessions;
|
|
11
11
|
private completedSessions;
|
|
12
|
-
executeCommand(command: string, timeoutMs?: number): Promise<CommandExecutionResult>;
|
|
12
|
+
executeCommand(command: string, timeoutMs?: number, shell?: string): Promise<CommandExecutionResult>;
|
|
13
13
|
getNewOutput(pid: number): string | null;
|
|
14
14
|
forceTerminate(pid: number): boolean;
|
|
15
15
|
listActiveSessions(): ActiveSession[];
|
package/dist/terminal-manager.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { DEFAULT_COMMAND_TIMEOUT } from './config.js';
|
|
3
|
+
import { configManager } from './config-manager.js';
|
|
4
|
+
import { capture } from "./utils/capture.js";
|
|
3
5
|
export class TerminalManager {
|
|
4
6
|
constructor() {
|
|
5
7
|
this.sessions = new Map();
|
|
6
8
|
this.completedSessions = new Map();
|
|
7
9
|
}
|
|
8
|
-
async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT) {
|
|
9
|
-
|
|
10
|
+
async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT, shell) {
|
|
11
|
+
// Get the shell from config if not specified
|
|
12
|
+
let shellToUse = shell;
|
|
13
|
+
if (!shellToUse) {
|
|
14
|
+
try {
|
|
15
|
+
const config = await configManager.getConfig();
|
|
16
|
+
shellToUse = config.shell || true;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
// If there's an error getting the config, fall back to default
|
|
20
|
+
shellToUse = true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const spawnOptions = {
|
|
24
|
+
shell: shellToUse
|
|
25
|
+
};
|
|
26
|
+
const process = spawn(command, [], spawnOptions);
|
|
10
27
|
let output = '';
|
|
11
28
|
// Ensure process.pid is defined before proceeding
|
|
12
29
|
if (!process.pid) {
|
|
@@ -101,7 +118,9 @@ export class TerminalManager {
|
|
|
101
118
|
return true;
|
|
102
119
|
}
|
|
103
120
|
catch (error) {
|
|
104
|
-
|
|
121
|
+
// Convert error to string, handling both Error objects and other types
|
|
122
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
123
|
+
capture('server_request_error', { error: errorMessage, message: `Failed to terminate process ${pid}:` });
|
|
105
124
|
return false;
|
|
106
125
|
}
|
|
107
126
|
}
|
package/dist/tools/config.d.ts
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export declare const GetConfigArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
3
|
-
export declare const GetConfigValueArgsSchema: z.ZodObject<{
|
|
4
|
-
key: z.ZodString;
|
|
5
|
-
}, "strip", z.ZodTypeAny, {
|
|
6
|
-
key: string;
|
|
7
|
-
}, {
|
|
8
|
-
key: string;
|
|
9
|
-
}>;
|
|
10
|
-
export declare const SetConfigValueArgsSchema: z.ZodObject<{
|
|
11
|
-
key: z.ZodString;
|
|
12
|
-
value: z.ZodAny;
|
|
13
|
-
}, "strip", z.ZodTypeAny, {
|
|
14
|
-
key: string;
|
|
15
|
-
value?: any;
|
|
16
|
-
}, {
|
|
17
|
-
key: string;
|
|
18
|
-
value?: any;
|
|
19
|
-
}>;
|
|
20
|
-
export declare const UpdateConfigArgsSchema: z.ZodObject<{
|
|
21
|
-
config: z.ZodRecord<z.ZodString, z.ZodAny>;
|
|
22
|
-
}, "strip", z.ZodTypeAny, {
|
|
23
|
-
config: Record<string, any>;
|
|
24
|
-
}, {
|
|
25
|
-
config: Record<string, any>;
|
|
26
|
-
}>;
|
|
27
1
|
/**
|
|
28
2
|
* Get the entire config
|
|
29
3
|
*/
|
|
@@ -33,22 +7,6 @@ export declare function getConfig(): Promise<{
|
|
|
33
7
|
text: string;
|
|
34
8
|
}[];
|
|
35
9
|
}>;
|
|
36
|
-
/**
|
|
37
|
-
* Get a specific config value
|
|
38
|
-
*/
|
|
39
|
-
export declare function getConfigValue(args: unknown): Promise<{
|
|
40
|
-
content: {
|
|
41
|
-
type: string;
|
|
42
|
-
text: string;
|
|
43
|
-
}[];
|
|
44
|
-
isError: boolean;
|
|
45
|
-
} | {
|
|
46
|
-
content: {
|
|
47
|
-
type: string;
|
|
48
|
-
text: string;
|
|
49
|
-
}[];
|
|
50
|
-
isError?: undefined;
|
|
51
|
-
}>;
|
|
52
10
|
/**
|
|
53
11
|
* Set a specific config value
|
|
54
12
|
*/
|
|
@@ -65,19 +23,3 @@ export declare function setConfigValue(args: unknown): Promise<{
|
|
|
65
23
|
}[];
|
|
66
24
|
isError?: undefined;
|
|
67
25
|
}>;
|
|
68
|
-
/**
|
|
69
|
-
* Update multiple config values at once
|
|
70
|
-
*/
|
|
71
|
-
export declare function updateConfig(args: unknown): Promise<{
|
|
72
|
-
content: {
|
|
73
|
-
type: string;
|
|
74
|
-
text: string;
|
|
75
|
-
}[];
|
|
76
|
-
isError: boolean;
|
|
77
|
-
} | {
|
|
78
|
-
content: {
|
|
79
|
-
type: string;
|
|
80
|
-
text: string;
|
|
81
|
-
}[];
|
|
82
|
-
isError?: undefined;
|
|
83
|
-
}>;
|
package/dist/tools/config.js
CHANGED
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import { configManager } from '../config-manager.js';
|
|
3
|
-
|
|
4
|
-
export const GetConfigArgsSchema = z.object({});
|
|
5
|
-
export const GetConfigValueArgsSchema = z.object({
|
|
6
|
-
key: z.string(),
|
|
7
|
-
});
|
|
8
|
-
export const SetConfigValueArgsSchema = z.object({
|
|
9
|
-
key: z.string(),
|
|
10
|
-
value: z.any(),
|
|
11
|
-
});
|
|
12
|
-
export const UpdateConfigArgsSchema = z.object({
|
|
13
|
-
config: z.record(z.any()),
|
|
14
|
-
});
|
|
2
|
+
import { SetConfigValueArgsSchema } from './schemas.js';
|
|
15
3
|
/**
|
|
16
4
|
* Get the entire config
|
|
17
5
|
*/
|
|
@@ -39,46 +27,6 @@ export async function getConfig() {
|
|
|
39
27
|
};
|
|
40
28
|
}
|
|
41
29
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Get a specific config value
|
|
44
|
-
*/
|
|
45
|
-
export async function getConfigValue(args) {
|
|
46
|
-
console.error(`getConfigValue called with args: ${JSON.stringify(args)}`);
|
|
47
|
-
try {
|
|
48
|
-
const parsed = GetConfigValueArgsSchema.safeParse(args);
|
|
49
|
-
if (!parsed.success) {
|
|
50
|
-
console.error(`Invalid arguments for get_config_value: ${parsed.error}`);
|
|
51
|
-
return {
|
|
52
|
-
content: [{
|
|
53
|
-
type: "text",
|
|
54
|
-
text: `Invalid arguments: ${parsed.error}`
|
|
55
|
-
}],
|
|
56
|
-
isError: true
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
const value = await configManager.getValue(parsed.data.key);
|
|
60
|
-
console.error(`getConfigValue result for key ${parsed.data.key}: ${JSON.stringify(value)}`);
|
|
61
|
-
return {
|
|
62
|
-
content: [{
|
|
63
|
-
type: "text",
|
|
64
|
-
text: value !== undefined
|
|
65
|
-
? `Value for ${parsed.data.key}: ${JSON.stringify(value, null, 2)}`
|
|
66
|
-
: `No value found for key: ${parsed.data.key}`
|
|
67
|
-
}],
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
console.error(`Error in getConfigValue: ${error instanceof Error ? error.message : String(error)}`);
|
|
72
|
-
console.error(error instanceof Error && error.stack ? error.stack : 'No stack trace available');
|
|
73
|
-
return {
|
|
74
|
-
content: [{
|
|
75
|
-
type: "text",
|
|
76
|
-
text: `Error retrieving value: ${error instanceof Error ? error.message : String(error)}`
|
|
77
|
-
}],
|
|
78
|
-
isError: true
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
30
|
/**
|
|
83
31
|
* Set a specific config value
|
|
84
32
|
*/
|
|
@@ -97,12 +45,52 @@ export async function setConfigValue(args) {
|
|
|
97
45
|
};
|
|
98
46
|
}
|
|
99
47
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
48
|
+
// Parse string values that should be arrays or objects
|
|
49
|
+
let valueToStore = parsed.data.value;
|
|
50
|
+
// If the value is a string that looks like an array or object, try to parse it
|
|
51
|
+
if (typeof valueToStore === 'string' &&
|
|
52
|
+
(valueToStore.startsWith('[') || valueToStore.startsWith('{'))) {
|
|
53
|
+
try {
|
|
54
|
+
valueToStore = JSON.parse(valueToStore);
|
|
55
|
+
console.error(`Parsed string value to object/array: ${JSON.stringify(valueToStore)}`);
|
|
56
|
+
}
|
|
57
|
+
catch (parseError) {
|
|
58
|
+
console.error(`Failed to parse string as JSON, using as-is: ${parseError}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Special handling for known array configuration keys
|
|
62
|
+
if ((parsed.data.key === 'allowedDirectories' || parsed.data.key === 'blockedCommands') &&
|
|
63
|
+
!Array.isArray(valueToStore)) {
|
|
64
|
+
if (typeof valueToStore === 'string') {
|
|
65
|
+
try {
|
|
66
|
+
valueToStore = JSON.parse(valueToStore);
|
|
67
|
+
}
|
|
68
|
+
catch (parseError) {
|
|
69
|
+
console.error(`Failed to parse string as array for ${parsed.data.key}: ${parseError}`);
|
|
70
|
+
// If parsing failed and it's a single value, convert to an array with one item
|
|
71
|
+
if (!valueToStore.includes('[')) {
|
|
72
|
+
valueToStore = [valueToStore];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// If not a string or array, convert to an array with one item
|
|
78
|
+
valueToStore = [valueToStore];
|
|
79
|
+
}
|
|
80
|
+
// Ensure the value is an array after all our conversions
|
|
81
|
+
if (!Array.isArray(valueToStore)) {
|
|
82
|
+
console.error(`Value for ${parsed.data.key} is still not an array, converting to array`);
|
|
83
|
+
valueToStore = [String(valueToStore)];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
await configManager.setValue(parsed.data.key, valueToStore);
|
|
87
|
+
// Get the updated configuration to show the user
|
|
88
|
+
const updatedConfig = await configManager.getConfig();
|
|
89
|
+
console.error(`setConfigValue: Successfully set ${parsed.data.key} to ${JSON.stringify(valueToStore)}`);
|
|
102
90
|
return {
|
|
103
91
|
content: [{
|
|
104
92
|
type: "text",
|
|
105
|
-
text: `Successfully set ${parsed.data.key} to ${JSON.stringify(
|
|
93
|
+
text: `Successfully set ${parsed.data.key} to ${JSON.stringify(valueToStore, null, 2)}\n\nUpdated configuration:\n${JSON.stringify(updatedConfig, null, 2)}`
|
|
106
94
|
}],
|
|
107
95
|
};
|
|
108
96
|
}
|
|
@@ -130,54 +118,3 @@ export async function setConfigValue(args) {
|
|
|
130
118
|
};
|
|
131
119
|
}
|
|
132
120
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Update multiple config values at once
|
|
135
|
-
*/
|
|
136
|
-
export async function updateConfig(args) {
|
|
137
|
-
console.error(`updateConfig called with args: ${JSON.stringify(args)}`);
|
|
138
|
-
try {
|
|
139
|
-
const parsed = UpdateConfigArgsSchema.safeParse(args);
|
|
140
|
-
if (!parsed.success) {
|
|
141
|
-
console.error(`Invalid arguments for update_config: ${parsed.error}`);
|
|
142
|
-
return {
|
|
143
|
-
content: [{
|
|
144
|
-
type: "text",
|
|
145
|
-
text: `Invalid arguments: ${parsed.error}`
|
|
146
|
-
}],
|
|
147
|
-
isError: true
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
try {
|
|
151
|
-
const updatedConfig = await configManager.updateConfig(parsed.data.config);
|
|
152
|
-
console.error(`updateConfig result: ${JSON.stringify(updatedConfig, null, 2)}`);
|
|
153
|
-
return {
|
|
154
|
-
content: [{
|
|
155
|
-
type: "text",
|
|
156
|
-
text: `Configuration updated successfully.\nNew configuration:\n${JSON.stringify(updatedConfig, null, 2)}`
|
|
157
|
-
}],
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
catch (saveError) {
|
|
161
|
-
console.error(`Error saving updated config: ${saveError.message}`);
|
|
162
|
-
// Return useful response instead of crashing
|
|
163
|
-
return {
|
|
164
|
-
content: [{
|
|
165
|
-
type: "text",
|
|
166
|
-
text: `Configuration updated in memory but couldn't be saved to disk: ${saveError.message}`
|
|
167
|
-
}],
|
|
168
|
-
isError: true
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
console.error(`Error in updateConfig: ${error instanceof Error ? error.message : String(error)}`);
|
|
174
|
-
console.error(error instanceof Error && error.stack ? error.stack : 'No stack trace available');
|
|
175
|
-
return {
|
|
176
|
-
content: [{
|
|
177
|
-
type: "text",
|
|
178
|
-
text: `Error updating configuration: ${error instanceof Error ? error.message : String(error)}`
|
|
179
|
-
}],
|
|
180
|
-
isError: true
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// This is a debug script to help understand how ~ expansion works
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
function expandHome(filepath) {
|
|
5
|
+
console.log(`Expanding ${filepath}`);
|
|
6
|
+
// Handle both '~' alone or '~/' path prefix
|
|
7
|
+
if (filepath === '~') {
|
|
8
|
+
const home = os.homedir();
|
|
9
|
+
console.log(`~ expanded to ${home}`);
|
|
10
|
+
return home;
|
|
11
|
+
}
|
|
12
|
+
else if (filepath.startsWith('~/')) {
|
|
13
|
+
const joinedPath = path.join(os.homedir(), filepath.slice(2));
|
|
14
|
+
console.log(`~/ expanded to ${joinedPath}`);
|
|
15
|
+
return joinedPath;
|
|
16
|
+
}
|
|
17
|
+
return filepath;
|
|
18
|
+
}
|
|
19
|
+
console.log(`Home directory: ${os.homedir()}`);
|
|
20
|
+
console.log(`Current working directory: ${process.cwd()}`);
|
|
21
|
+
console.log(`Expanding ~: ${expandHome('~')}`);
|
|
22
|
+
console.log(`Expanding ~/Documents: ${expandHome('~/Documents')}`);
|
|
23
|
+
console.log(`Expanding /tmp: ${expandHome('/tmp')}`);
|
|
24
|
+
// Create lowercase normalized paths for comparison
|
|
25
|
+
function normalizePath(p) {
|
|
26
|
+
return path.normalize(p).toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
const allowedDir = '~';
|
|
29
|
+
const expandedAllowedDir = expandHome(allowedDir);
|
|
30
|
+
const normalizedAllowedDir = normalizePath(expandedAllowedDir);
|
|
31
|
+
console.log(`Allowed dir: ${allowedDir}`);
|
|
32
|
+
console.log(`Expanded allowed dir: ${expandedAllowedDir}`);
|
|
33
|
+
console.log(`Normalized allowed dir: ${normalizedAllowedDir}`);
|
|
34
|
+
// Example path to check
|
|
35
|
+
const pathToCheck = '~';
|
|
36
|
+
const expandedPathToCheck = expandHome(pathToCheck);
|
|
37
|
+
const normalizedPathToCheck = normalizePath(expandedPathToCheck);
|
|
38
|
+
console.log(`Path to check: ${pathToCheck}`);
|
|
39
|
+
console.log(`Expanded path to check: ${expandedPathToCheck}`);
|
|
40
|
+
console.log(`Normalized path to check: ${normalizedPathToCheck}`);
|
|
41
|
+
// Check if path is allowed
|
|
42
|
+
const isAllowed = normalizedPathToCheck === normalizedAllowedDir ||
|
|
43
|
+
normalizedPathToCheck.startsWith(normalizedAllowedDir + path.sep);
|
|
44
|
+
console.log(`Is path allowed: ${isAllowed}`);
|
package/dist/tools/edit.d.ts
CHANGED
|
@@ -3,10 +3,12 @@ interface SearchReplace {
|
|
|
3
3
|
search: string;
|
|
4
4
|
replace: string;
|
|
5
5
|
}
|
|
6
|
-
export declare function performSearchReplace(filePath: string, block: SearchReplace): Promise<ServerResult>;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
export declare function performSearchReplace(filePath: string, block: SearchReplace, expectedReplacements?: number): Promise<ServerResult>;
|
|
7
|
+
/**
|
|
8
|
+
* Handle edit_block command with enhanced functionality
|
|
9
|
+
* - Supports multiple replacements
|
|
10
|
+
* - Validates expected replacements count
|
|
11
|
+
* - Provides detailed error messages
|
|
12
|
+
*/
|
|
13
|
+
export declare function handleEditBlock(args: unknown): Promise<ServerResult>;
|
|
12
14
|
export {};
|
package/dist/tools/edit.js
CHANGED
|
@@ -1,45 +1,175 @@
|
|
|
1
1
|
import { readFile, writeFile } from './filesystem.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { recursiveFuzzyIndexOf, getSimilarityRatio } from './fuzzySearch.js';
|
|
3
|
+
import { capture } from '../utils/capture.js';
|
|
4
|
+
import { EditBlockArgsSchema } from "./schemas.js";
|
|
5
|
+
import path from 'path';
|
|
6
|
+
/**
|
|
7
|
+
* Threshold for fuzzy matching - similarity must be at least this value to be considered
|
|
8
|
+
* (0-1 scale where 1 is perfect match and 0 is completely different)
|
|
9
|
+
*/
|
|
10
|
+
const FUZZY_THRESHOLD = 0.7;
|
|
11
|
+
export async function performSearchReplace(filePath, block, expectedReplacements = 1) {
|
|
12
|
+
// Check for empty search string to prevent infinite loops
|
|
13
|
+
if (block.search === "") {
|
|
14
|
+
return {
|
|
15
|
+
content: [{
|
|
16
|
+
type: "text",
|
|
17
|
+
text: "Empty search strings are not allowed. Please provide a non-empty string to search for."
|
|
18
|
+
}],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
// Get file extension for telemetry using path module
|
|
22
|
+
const fileExtension = path.extname(filePath).toLowerCase();
|
|
23
|
+
// Capture file extension in telemetry without capturing the file path
|
|
24
|
+
capture('server_edit_block', { fileExtension: fileExtension });
|
|
25
|
+
// Read file as plain string
|
|
26
|
+
const { content } = await readFile(filePath);
|
|
5
27
|
// Make sure content is a string
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
28
|
+
if (typeof content !== 'string') {
|
|
29
|
+
throw new Error('Wrong content for file ' + filePath);
|
|
30
|
+
}
|
|
31
|
+
// First try exact match
|
|
32
|
+
let tempContent = content;
|
|
33
|
+
let count = 0;
|
|
34
|
+
let pos = tempContent.indexOf(block.search);
|
|
35
|
+
while (pos !== -1) {
|
|
36
|
+
count++;
|
|
37
|
+
pos = tempContent.indexOf(block.search, pos + 1);
|
|
38
|
+
}
|
|
39
|
+
// If exact match found and count matches expected replacements, proceed with exact replacement
|
|
40
|
+
if (count > 0 && count === expectedReplacements) {
|
|
41
|
+
// Replace all occurrences
|
|
42
|
+
let newContent = content;
|
|
43
|
+
// If we're only replacing one occurrence, replace it directly
|
|
44
|
+
if (expectedReplacements === 1) {
|
|
45
|
+
const searchIndex = newContent.indexOf(block.search);
|
|
46
|
+
newContent =
|
|
47
|
+
newContent.substring(0, searchIndex) +
|
|
48
|
+
block.replace +
|
|
49
|
+
newContent.substring(searchIndex + block.search.length);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// Replace all occurrences using split and join for multiple replacements
|
|
53
|
+
newContent = newContent.split(block.search).join(block.replace);
|
|
54
|
+
}
|
|
55
|
+
await writeFile(filePath, newContent);
|
|
10
56
|
return {
|
|
11
|
-
content: [{
|
|
57
|
+
content: [{
|
|
58
|
+
type: "text",
|
|
59
|
+
text: `Successfully applied ${expectedReplacements} edit${expectedReplacements > 1 ? 's' : ''} to ${filePath}`
|
|
60
|
+
}],
|
|
12
61
|
};
|
|
13
62
|
}
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
block.replace +
|
|
17
|
-
contentStr.substring(searchIndex + block.search.length);
|
|
18
|
-
await writeFile(filePath, newContent);
|
|
19
|
-
return {
|
|
20
|
-
content: [{ type: "text", text: `Successfully applied edit to ${filePath}` }],
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
export async function parseEditBlock(blockContent) {
|
|
24
|
-
const lines = blockContent.split('\n');
|
|
25
|
-
// First line should be the file path
|
|
26
|
-
const filePath = lines[0].trim();
|
|
27
|
-
// Find the markers
|
|
28
|
-
const searchStart = lines.indexOf('<<<<<<< SEARCH');
|
|
29
|
-
const divider = lines.indexOf('=======');
|
|
30
|
-
const replaceEnd = lines.indexOf('>>>>>>> REPLACE');
|
|
31
|
-
if (searchStart === -1 || divider === -1 || replaceEnd === -1) {
|
|
63
|
+
// If exact match found but count doesn't match expected, inform the user
|
|
64
|
+
if (count > 0 && count !== expectedReplacements) {
|
|
32
65
|
return {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
66
|
+
content: [{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: `Expected ${expectedReplacements} occurrences but found ${count} in ${filePath}. ` +
|
|
69
|
+
`Double check and make sure you understand all occurencies and if you want to replace all ${count} occurrences, set expected_replacements to ${count}. ` +
|
|
70
|
+
`If there are many occurrancies and you want to change some of them and keep the rest. Do it one by one, by adding more lines around each occurrence.` +
|
|
71
|
+
`If you want to replace a specific occurrence, make your search string more unique by adding more lines around search string.`
|
|
72
|
+
}],
|
|
36
73
|
};
|
|
37
74
|
}
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
75
|
+
// If exact match not found, try fuzzy search
|
|
76
|
+
if (count === 0) {
|
|
77
|
+
// Track fuzzy search time
|
|
78
|
+
const startTime = performance.now();
|
|
79
|
+
// Perform fuzzy search
|
|
80
|
+
const fuzzyResult = recursiveFuzzyIndexOf(content, block.search);
|
|
81
|
+
const similarity = getSimilarityRatio(block.search, fuzzyResult.value);
|
|
82
|
+
// Calculate execution time in milliseconds
|
|
83
|
+
const executionTime = performance.now() - startTime;
|
|
84
|
+
// Check if the fuzzy match is "close enough"
|
|
85
|
+
if (similarity >= FUZZY_THRESHOLD) {
|
|
86
|
+
// Format differences for clearer output
|
|
87
|
+
const diff = highlightDifferences(block.search, fuzzyResult.value);
|
|
88
|
+
// Capture the fuzzy search event
|
|
89
|
+
capture('server_fuzzy_search_performed', {
|
|
90
|
+
similarity: similarity,
|
|
91
|
+
execution_time_ms: executionTime,
|
|
92
|
+
search_length: block.search.length,
|
|
93
|
+
file_size: content.length,
|
|
94
|
+
threshold: FUZZY_THRESHOLD,
|
|
95
|
+
found_text_length: fuzzyResult.value.length
|
|
96
|
+
});
|
|
97
|
+
// If we allow fuzzy matches, we would make the replacement here
|
|
98
|
+
// For now, we'll return a detailed message about the fuzzy match
|
|
99
|
+
return {
|
|
100
|
+
content: [{
|
|
101
|
+
type: "text",
|
|
102
|
+
text: `Exact match not found, but found a similar text with ${Math.round(similarity * 100)}% similarity (found in ${executionTime.toFixed(2)}ms):\n\n` +
|
|
103
|
+
`Differences:\n${diff}\n\n` +
|
|
104
|
+
`To replace this text, use the exact text found in the file.`
|
|
105
|
+
}],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// If the fuzzy match isn't close enough
|
|
110
|
+
// Still capture the fuzzy search event even for unsuccessful matches
|
|
111
|
+
capture('server_fuzzy_search_performed', {
|
|
112
|
+
similarity: similarity,
|
|
113
|
+
execution_time_ms: executionTime,
|
|
114
|
+
search_length: block.search.length,
|
|
115
|
+
file_size: content.length,
|
|
116
|
+
threshold: FUZZY_THRESHOLD,
|
|
117
|
+
found_text_length: fuzzyResult.value.length,
|
|
118
|
+
below_threshold: true
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
content: [{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: `Search content not found in ${filePath}. The closest match was "${fuzzyResult.value}" ` +
|
|
124
|
+
`with only ${Math.round(similarity * 100)}% similarity, which is below the ${Math.round(FUZZY_THRESHOLD * 100)}% threshold. ` +
|
|
125
|
+
`(Fuzzy search completed in ${executionTime.toFixed(2)}ms)`
|
|
126
|
+
}],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw new Error("Unexpected error during search and replace operation.");
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Generates a character-level diff using standard {-removed-}{+added+} format
|
|
134
|
+
* @param expected The string that was searched for
|
|
135
|
+
* @param actual The string that was found
|
|
136
|
+
* @returns A formatted string showing character-level differences
|
|
137
|
+
*/
|
|
138
|
+
function highlightDifferences(expected, actual) {
|
|
139
|
+
// Implementation of a simplified character-level diff
|
|
140
|
+
// Find common prefix and suffix
|
|
141
|
+
let prefixLength = 0;
|
|
142
|
+
const minLength = Math.min(expected.length, actual.length);
|
|
143
|
+
// Determine common prefix length
|
|
144
|
+
while (prefixLength < minLength &&
|
|
145
|
+
expected[prefixLength] === actual[prefixLength]) {
|
|
146
|
+
prefixLength++;
|
|
147
|
+
}
|
|
148
|
+
// Determine common suffix length
|
|
149
|
+
let suffixLength = 0;
|
|
150
|
+
while (suffixLength < minLength - prefixLength &&
|
|
151
|
+
expected[expected.length - 1 - suffixLength] === actual[actual.length - 1 - suffixLength]) {
|
|
152
|
+
suffixLength++;
|
|
153
|
+
}
|
|
154
|
+
// Extract the common and different parts
|
|
155
|
+
const commonPrefix = expected.substring(0, prefixLength);
|
|
156
|
+
const commonSuffix = expected.substring(expected.length - suffixLength);
|
|
157
|
+
const expectedDiff = expected.substring(prefixLength, expected.length - suffixLength);
|
|
158
|
+
const actualDiff = actual.substring(prefixLength, actual.length - suffixLength);
|
|
159
|
+
// Format the output as a character-level diff
|
|
160
|
+
return `${commonPrefix}{-${expectedDiff}-}{+${actualDiff}+}${commonSuffix}`;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Handle edit_block command with enhanced functionality
|
|
164
|
+
* - Supports multiple replacements
|
|
165
|
+
* - Validates expected replacements count
|
|
166
|
+
* - Provides detailed error messages
|
|
167
|
+
*/
|
|
168
|
+
export async function handleEditBlock(args) {
|
|
169
|
+
const parsed = EditBlockArgsSchema.parse(args);
|
|
170
|
+
const searchReplace = {
|
|
171
|
+
search: parsed.old_string,
|
|
172
|
+
replace: parsed.new_string
|
|
44
173
|
};
|
|
174
|
+
return performSearchReplace(parsed.file_path, searchReplace, parsed.expected_replacements);
|
|
45
175
|
}
|
package/dist/tools/execute.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { terminalManager } from '../terminal-manager.js';
|
|
2
2
|
import { commandManager } from '../command-manager.js';
|
|
3
3
|
import { ExecuteCommandArgsSchema, ReadOutputArgsSchema, ForceTerminateArgsSchema } from './schemas.js';
|
|
4
|
-
import { capture } from "../utils.js";
|
|
4
|
+
import { capture } from "../utils/capture.js";
|
|
5
5
|
export async function executeCommand(args) {
|
|
6
6
|
const parsed = ExecuteCommandArgsSchema.safeParse(args);
|
|
7
7
|
if (!parsed.success) {
|
|
@@ -13,7 +13,7 @@ export async function executeCommand(args) {
|
|
|
13
13
|
}
|
|
14
14
|
try {
|
|
15
15
|
// Extract all commands for analytics while ensuring execution continues even if parsing fails
|
|
16
|
-
const commands = commandManager.extractCommands(parsed.data.command);
|
|
16
|
+
const commands = commandManager.extractCommands(parsed.data.command).join(', ');
|
|
17
17
|
capture('server_execute_command', {
|
|
18
18
|
command: commandManager.getBaseCommand(parsed.data.command), // Keep original for backward compatibility
|
|
19
19
|
commands: commands // Add the array of all identified commands
|
|
@@ -24,16 +24,16 @@ export async function executeCommand(args) {
|
|
|
24
24
|
capture('server_execute_command', {
|
|
25
25
|
command: commandManager.getBaseCommand(parsed.data.command)
|
|
26
26
|
});
|
|
27
|
-
// Log the error but continue execution
|
|
28
|
-
console.error('Error during command extraction:', error);
|
|
29
27
|
}
|
|
30
|
-
|
|
28
|
+
// Command validation is now async
|
|
29
|
+
const isAllowed = await commandManager.validateCommand(parsed.data.command);
|
|
30
|
+
if (!isAllowed) {
|
|
31
31
|
return {
|
|
32
32
|
content: [{ type: "text", text: `Error: Command not allowed: ${parsed.data.command}` }],
|
|
33
33
|
isError: true,
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms);
|
|
36
|
+
const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms, parsed.data.shell);
|
|
37
37
|
// Check for error condition (pid = -1)
|
|
38
38
|
if (result.pid === -1) {
|
|
39
39
|
return {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a file from either the local filesystem or a URL
|
|
3
|
+
* @param filePath Path to the file or URL
|
|
4
|
+
* @param returnMetadata Whether to return metadata with the content
|
|
5
|
+
* @param isUrl Whether the path is a URL
|
|
6
|
+
* @returns File content or file result with metadata
|
|
7
|
+
*/
|
|
8
|
+
export declare function readFile(filePath: string, returnMetadata?: boolean, isUrl?: boolean): Promise<string | FileResult>;
|
|
9
|
+
export declare function writeFile(filePath: string, content: string): Promise<void>;
|
|
10
|
+
export interface MultiFileResult {
|
|
11
|
+
path: string;
|
|
12
|
+
content?: string;
|
|
13
|
+
mimeType?: string;
|
|
14
|
+
isImage?: boolean;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function readMultipleFiles(paths: string[]): Promise<MultiFileResult[]>;
|
|
18
|
+
export declare function createDirectory(dirPath: string): Promise<void>;
|
|
19
|
+
export declare function listDirectory(dirPath: string): Promise<string[]>;
|
|
20
|
+
export declare function moveFile(sourcePath: string, destinationPath: string): Promise<void>;
|
|
21
|
+
export declare function searchFiles(rootPath: string, pattern: string): Promise<string[]>;
|
|
22
|
+
export declare function getFileInfo(filePath: string): Promise<Record<string, any>>;
|