@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
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { distance } from 'fastest-levenshtein';
|
|
2
|
+
import { capture } from '../utils/capture.js';
|
|
3
|
+
/**
|
|
4
|
+
* Recursively finds the closest match to a query string within text using fuzzy matching
|
|
5
|
+
* @param text The text to search within
|
|
6
|
+
* @param query The query string to find
|
|
7
|
+
* @param start Start index in the text (default: 0)
|
|
8
|
+
* @param end End index in the text (default: text.length)
|
|
9
|
+
* @param parentDistance Best distance found so far (default: Infinity)
|
|
10
|
+
* @returns Object with start and end indices, matched value, and Levenshtein distance
|
|
11
|
+
*/
|
|
12
|
+
export function recursiveFuzzyIndexOf(text, query, start = 0, end = null, parentDistance = Infinity, depth = 0) {
|
|
13
|
+
// For debugging and performance tracking purposes
|
|
14
|
+
if (depth === 0) {
|
|
15
|
+
const startTime = performance.now();
|
|
16
|
+
const result = recursiveFuzzyIndexOf(text, query, start, end, parentDistance, depth + 1);
|
|
17
|
+
const executionTime = performance.now() - startTime;
|
|
18
|
+
// Capture detailed metrics for the recursive search for in-depth analysis
|
|
19
|
+
capture('fuzzy_search_recursive_metrics', {
|
|
20
|
+
execution_time_ms: executionTime,
|
|
21
|
+
text_length: text.length,
|
|
22
|
+
query_length: query.length,
|
|
23
|
+
result_distance: result.distance
|
|
24
|
+
});
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
if (end === null)
|
|
28
|
+
end = text.length;
|
|
29
|
+
// For small text segments, use iterative approach
|
|
30
|
+
if (end - start <= 2 * query.length) {
|
|
31
|
+
return iterativeReduction(text, query, start, end, parentDistance);
|
|
32
|
+
}
|
|
33
|
+
let midPoint = start + Math.floor((end - start) / 2);
|
|
34
|
+
let leftEnd = Math.min(end, midPoint + query.length); // Include query length to cover overlaps
|
|
35
|
+
let rightStart = Math.max(start, midPoint - query.length); // Include query length to cover overlaps
|
|
36
|
+
// Calculate distance for current segments
|
|
37
|
+
let leftDistance = distance(text.substring(start, leftEnd), query);
|
|
38
|
+
let rightDistance = distance(text.substring(rightStart, end), query);
|
|
39
|
+
let bestDistance = Math.min(leftDistance, parentDistance, rightDistance);
|
|
40
|
+
// If parent distance is already the best, use iterative approach
|
|
41
|
+
if (parentDistance === bestDistance) {
|
|
42
|
+
return iterativeReduction(text, query, start, end, parentDistance);
|
|
43
|
+
}
|
|
44
|
+
// Recursively search the better half
|
|
45
|
+
if (leftDistance < rightDistance) {
|
|
46
|
+
return recursiveFuzzyIndexOf(text, query, start, leftEnd, bestDistance, depth + 1);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
return recursiveFuzzyIndexOf(text, query, rightStart, end, bestDistance, depth + 1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Iteratively refines the best match by reducing the search area
|
|
54
|
+
* @param text The text to search within
|
|
55
|
+
* @param query The query string to find
|
|
56
|
+
* @param start Start index in the text
|
|
57
|
+
* @param end End index in the text
|
|
58
|
+
* @param parentDistance Best distance found so far
|
|
59
|
+
* @returns Object with start and end indices, matched value, and Levenshtein distance
|
|
60
|
+
*/
|
|
61
|
+
function iterativeReduction(text, query, start, end, parentDistance) {
|
|
62
|
+
const startTime = performance.now();
|
|
63
|
+
let iterations = 0;
|
|
64
|
+
let bestDistance = parentDistance;
|
|
65
|
+
let bestStart = start;
|
|
66
|
+
let bestEnd = end;
|
|
67
|
+
// Improve start position
|
|
68
|
+
let nextDistance = distance(text.substring(bestStart + 1, bestEnd), query);
|
|
69
|
+
while (nextDistance < bestDistance) {
|
|
70
|
+
bestDistance = nextDistance;
|
|
71
|
+
bestStart++;
|
|
72
|
+
const smallerString = text.substring(bestStart + 1, bestEnd);
|
|
73
|
+
nextDistance = distance(smallerString, query);
|
|
74
|
+
iterations++;
|
|
75
|
+
}
|
|
76
|
+
// Improve end position
|
|
77
|
+
nextDistance = distance(text.substring(bestStart, bestEnd - 1), query);
|
|
78
|
+
while (nextDistance < bestDistance) {
|
|
79
|
+
bestDistance = nextDistance;
|
|
80
|
+
bestEnd--;
|
|
81
|
+
const smallerString = text.substring(bestStart, bestEnd - 1);
|
|
82
|
+
nextDistance = distance(smallerString, query);
|
|
83
|
+
iterations++;
|
|
84
|
+
}
|
|
85
|
+
const executionTime = performance.now() - startTime;
|
|
86
|
+
// Capture metrics for the iterative refinement phase
|
|
87
|
+
capture('fuzzy_search_iterative_metrics', {
|
|
88
|
+
execution_time_ms: executionTime,
|
|
89
|
+
iterations: iterations,
|
|
90
|
+
segment_length: end - start,
|
|
91
|
+
query_length: query.length,
|
|
92
|
+
final_distance: bestDistance
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
start: bestStart,
|
|
96
|
+
end: bestEnd,
|
|
97
|
+
value: text.substring(bestStart, bestEnd),
|
|
98
|
+
distance: bestDistance
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Calculates the similarity ratio between two strings
|
|
103
|
+
* @param a First string
|
|
104
|
+
* @param b Second string
|
|
105
|
+
* @returns Similarity ratio (0-1)
|
|
106
|
+
*/
|
|
107
|
+
export function getSimilarityRatio(a, b) {
|
|
108
|
+
const maxLength = Math.max(a.length, b.length);
|
|
109
|
+
if (maxLength === 0)
|
|
110
|
+
return 1; // Both strings are empty
|
|
111
|
+
const levenshteinDistance = distance(a, b);
|
|
112
|
+
return 1 - (levenshteinDistance / maxLength);
|
|
113
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FileResult } from './filesystem.js';
|
|
2
|
+
export declare enum PDFOutputFormat {
|
|
3
|
+
TEXT = "text",
|
|
4
|
+
MARKDOWN = "markdown",
|
|
5
|
+
HTML = "html"
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Reads a PDF file and returns its content in the specified format
|
|
9
|
+
* @param filePath Path to the PDF file
|
|
10
|
+
* @param outputFormat Desired output format (text, markdown, or html)
|
|
11
|
+
* @returns FileResult object with the parsed content
|
|
12
|
+
*/
|
|
13
|
+
export declare function readPDFFile(filePath: string, outputFormat?: PDFOutputFormat): Promise<FileResult>;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { capture } from '../utils.js';
|
|
4
|
+
// Direct imports
|
|
5
|
+
import pdfParse from 'pdf-parse';
|
|
6
|
+
import TurndownService from 'turndown';
|
|
7
|
+
// PDF output formats
|
|
8
|
+
export var PDFOutputFormat;
|
|
9
|
+
(function (PDFOutputFormat) {
|
|
10
|
+
PDFOutputFormat["TEXT"] = "text";
|
|
11
|
+
PDFOutputFormat["MARKDOWN"] = "markdown";
|
|
12
|
+
PDFOutputFormat["HTML"] = "html";
|
|
13
|
+
})(PDFOutputFormat || (PDFOutputFormat = {}));
|
|
14
|
+
/**
|
|
15
|
+
* Reads a PDF file and returns its content in the specified format
|
|
16
|
+
* @param filePath Path to the PDF file
|
|
17
|
+
* @param outputFormat Desired output format (text, markdown, or html)
|
|
18
|
+
* @returns FileResult object with the parsed content
|
|
19
|
+
*/
|
|
20
|
+
export async function readPDFFile(filePath, outputFormat = PDFOutputFormat.TEXT) {
|
|
21
|
+
try {
|
|
22
|
+
// Read the PDF file as a buffer
|
|
23
|
+
const dataBuffer = await fs.readFile(filePath);
|
|
24
|
+
// Parse the PDF
|
|
25
|
+
const pdfData = await pdfParse(dataBuffer);
|
|
26
|
+
// Extract the raw text
|
|
27
|
+
const rawText = pdfData.text;
|
|
28
|
+
let content;
|
|
29
|
+
let mimeType;
|
|
30
|
+
// Convert to the requested format
|
|
31
|
+
switch (outputFormat) {
|
|
32
|
+
case PDFOutputFormat.HTML:
|
|
33
|
+
// Convert to simple HTML (using paragraphs for line breaks)
|
|
34
|
+
content = convertPDFToHTML(rawText, pdfData, filePath);
|
|
35
|
+
mimeType = 'text/html';
|
|
36
|
+
break;
|
|
37
|
+
case PDFOutputFormat.MARKDOWN:
|
|
38
|
+
try {
|
|
39
|
+
// First convert to HTML, then to Markdown
|
|
40
|
+
const htmlContent = convertPDFToHTML(rawText, pdfData, filePath);
|
|
41
|
+
const turndownService = new TurndownService({
|
|
42
|
+
headingStyle: 'atx',
|
|
43
|
+
codeBlockStyle: 'fenced',
|
|
44
|
+
emDelimiter: '*'
|
|
45
|
+
});
|
|
46
|
+
content = turndownService.turndown(htmlContent);
|
|
47
|
+
mimeType = 'text/markdown';
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
capture('server_turndown_error', {
|
|
51
|
+
error: error instanceof Error ? error.message : String(error)
|
|
52
|
+
});
|
|
53
|
+
// Fallback to basic markdown conversion if turndown has an issue
|
|
54
|
+
content = convertPDFToBasicMarkdown(rawText, pdfData, filePath);
|
|
55
|
+
mimeType = 'text/markdown';
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
// First convert to HTML, then to Markdown
|
|
59
|
+
const htmlContent = convertPDFToHTML(rawText, pdfData, filePath);
|
|
60
|
+
const turndownService = new TurndownService({
|
|
61
|
+
headingStyle: 'atx',
|
|
62
|
+
codeBlockStyle: 'fenced',
|
|
63
|
+
emDelimiter: '*'
|
|
64
|
+
});
|
|
65
|
+
content = turndownService.turndown(htmlContent);
|
|
66
|
+
mimeType = 'text/markdown';
|
|
67
|
+
break;
|
|
68
|
+
case PDFOutputFormat.TEXT:
|
|
69
|
+
default:
|
|
70
|
+
// Format as readable text
|
|
71
|
+
content = formatPDFText(rawText, pdfData, filePath);
|
|
72
|
+
mimeType = 'text/plain';
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
content,
|
|
77
|
+
mimeType,
|
|
78
|
+
isImage: false
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
capture('server_pdf_read_error', {
|
|
83
|
+
error: error instanceof Error ? error.name : 'Unknown',
|
|
84
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
85
|
+
});
|
|
86
|
+
throw new Error(`Failed to read PDF file: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Format PDF text to make it more readable as plain text
|
|
91
|
+
* @param text Raw text from PDF
|
|
92
|
+
* @param pdfData PDF data
|
|
93
|
+
* @param filePath Original file path (for filename)
|
|
94
|
+
* @returns Formatted text
|
|
95
|
+
*/
|
|
96
|
+
function formatPDFText(text, pdfData, filePath) {
|
|
97
|
+
const fileName = path.basename(filePath);
|
|
98
|
+
let output = `Document: ${fileName}\n`;
|
|
99
|
+
output += `Pages: ${pdfData.numpages}\n\n`;
|
|
100
|
+
// Add PDF metadata if available
|
|
101
|
+
if (pdfData.info) {
|
|
102
|
+
if (pdfData.info.Title)
|
|
103
|
+
output += `Title: ${pdfData.info.Title}\n`;
|
|
104
|
+
if (pdfData.info.Author)
|
|
105
|
+
output += `Author: ${pdfData.info.Author}\n`;
|
|
106
|
+
if (pdfData.info.Subject)
|
|
107
|
+
output += `Subject: ${pdfData.info.Subject}\n`;
|
|
108
|
+
if (pdfData.info.Keywords)
|
|
109
|
+
output += `Keywords: ${pdfData.info.Keywords}\n`;
|
|
110
|
+
if (pdfData.info.CreationDate)
|
|
111
|
+
output += `Creation Date: ${pdfData.info.CreationDate}\n`;
|
|
112
|
+
output += '\n';
|
|
113
|
+
}
|
|
114
|
+
// Format the actual text content
|
|
115
|
+
// Split by double line breaks to identify paragraphs
|
|
116
|
+
const paragraphs = text.split(/\n\s*\n/);
|
|
117
|
+
output += paragraphs.join('\n\n');
|
|
118
|
+
return output;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Convert PDF text to basic markdown format without using turndown
|
|
122
|
+
* @param text Raw text from PDF
|
|
123
|
+
* @param pdfData PDF data
|
|
124
|
+
* @param filePath Original file path (for filename)
|
|
125
|
+
* @returns Markdown text
|
|
126
|
+
*/
|
|
127
|
+
function convertPDFToBasicMarkdown(text, pdfData, filePath) {
|
|
128
|
+
const fileName = path.basename(filePath);
|
|
129
|
+
let output = `# ${fileName}\n\n`;
|
|
130
|
+
// Add PDF metadata if available
|
|
131
|
+
if (pdfData.info) {
|
|
132
|
+
if (pdfData.info.Title)
|
|
133
|
+
output += `**Title:** ${pdfData.info.Title}\n\n`;
|
|
134
|
+
if (pdfData.info.Author)
|
|
135
|
+
output += `**Author:** ${pdfData.info.Author}\n\n`;
|
|
136
|
+
if (pdfData.info.Subject)
|
|
137
|
+
output += `**Subject:** ${pdfData.info.Subject}\n\n`;
|
|
138
|
+
}
|
|
139
|
+
// Split by double line breaks to identify paragraphs
|
|
140
|
+
const paragraphs = text.split(/\n\s*\n/);
|
|
141
|
+
// Process each paragraph
|
|
142
|
+
paragraphs.forEach(paragraph => {
|
|
143
|
+
const trimmed = paragraph.trim();
|
|
144
|
+
if (trimmed) {
|
|
145
|
+
// Check if this looks like a heading (shorter, ends without punctuation)
|
|
146
|
+
if (trimmed.length < 80 && !trimmed.match(/[.,:;?!]$/)) {
|
|
147
|
+
output += `## ${trimmed}\n\n`;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
output += `${trimmed}\n\n`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return output;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Convert PDF text to a simple HTML representation
|
|
158
|
+
* @param text Raw text from PDF
|
|
159
|
+
* @param pdfData PDF data
|
|
160
|
+
* @param filePath Original file path (for filename)
|
|
161
|
+
* @returns HTML string
|
|
162
|
+
*/
|
|
163
|
+
function convertPDFToHTML(text, pdfData, filePath) {
|
|
164
|
+
// This is a basic conversion that preserves paragraphs
|
|
165
|
+
const fileName = path.basename(filePath);
|
|
166
|
+
const title = pdfData.info && pdfData.info.Title ? pdfData.info.Title : fileName;
|
|
167
|
+
// Split by double line breaks to identify paragraphs
|
|
168
|
+
const paragraphs = text.split(/\n\s*\n/);
|
|
169
|
+
// Build HTML structure
|
|
170
|
+
let html = `<!DOCTYPE html>
|
|
171
|
+
<html>
|
|
172
|
+
<head>
|
|
173
|
+
<title>${title}</title>
|
|
174
|
+
<style>
|
|
175
|
+
body { font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }
|
|
176
|
+
h1 { border-bottom: 1px solid #eee; padding-bottom: 10px; }
|
|
177
|
+
h2 { margin-top: 24px; }
|
|
178
|
+
pre { background-color: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
|
179
|
+
.metadata { color: #666; font-size: 0.9em; margin-bottom: 24px; }
|
|
180
|
+
</style>
|
|
181
|
+
</head>
|
|
182
|
+
<body>
|
|
183
|
+
<h1>${title}</h1>
|
|
184
|
+
`;
|
|
185
|
+
// Add metadata if available
|
|
186
|
+
if (pdfData.info) {
|
|
187
|
+
html += ' <div class="metadata">\n';
|
|
188
|
+
if (pdfData.info.Author)
|
|
189
|
+
html += ` <p><strong>Author:</strong> ${pdfData.info.Author}</p>\n`;
|
|
190
|
+
if (pdfData.info.Subject)
|
|
191
|
+
html += ` <p><strong>Subject:</strong> ${pdfData.info.Subject}</p>\n`;
|
|
192
|
+
if (pdfData.info.Keywords)
|
|
193
|
+
html += ` <p><strong>Keywords:</strong> ${pdfData.info.Keywords}</p>\n`;
|
|
194
|
+
if (pdfData.numpages)
|
|
195
|
+
html += ` <p><strong>Pages:</strong> ${pdfData.numpages}</p>\n`;
|
|
196
|
+
html += ' </div>\n';
|
|
197
|
+
}
|
|
198
|
+
// Add paragraphs
|
|
199
|
+
paragraphs.forEach(paragraph => {
|
|
200
|
+
const trimmed = paragraph.trim();
|
|
201
|
+
if (trimmed) {
|
|
202
|
+
// Check if this looks like a heading (shorter, ends without punctuation)
|
|
203
|
+
if (trimmed.length < 100 && !trimmed.match(/[.,:;?!]$/)) {
|
|
204
|
+
html += ` <h2>${trimmed}</h2>\n`;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
html += ` <p>${trimmed}</p>\n`;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
html += `</body>
|
|
212
|
+
</html>`;
|
|
213
|
+
return html;
|
|
214
|
+
}
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export declare const GetConfigArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
3
|
+
export declare const SetConfigValueArgsSchema: z.ZodObject<{
|
|
4
|
+
key: z.ZodString;
|
|
5
|
+
value: z.ZodAny;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
key: string;
|
|
8
|
+
value?: any;
|
|
9
|
+
}, {
|
|
10
|
+
key: string;
|
|
11
|
+
value?: any;
|
|
12
|
+
}>;
|
|
13
|
+
export declare const ListProcessesArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
2
14
|
export declare const ExecuteCommandArgsSchema: z.ZodObject<{
|
|
3
15
|
command: z.ZodString;
|
|
4
16
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
shell: z.ZodOptional<z.ZodString>;
|
|
5
18
|
}, "strip", z.ZodTypeAny, {
|
|
6
19
|
command: string;
|
|
7
20
|
timeout_ms?: number | undefined;
|
|
21
|
+
shell?: string | undefined;
|
|
8
22
|
}, {
|
|
9
23
|
command: string;
|
|
10
24
|
timeout_ms?: number | undefined;
|
|
25
|
+
shell?: string | undefined;
|
|
11
26
|
}>;
|
|
12
27
|
export declare const ReadOutputArgsSchema: z.ZodObject<{
|
|
13
28
|
pid: z.ZodNumber;
|
|
@@ -31,20 +46,6 @@ export declare const KillProcessArgsSchema: z.ZodObject<{
|
|
|
31
46
|
}, {
|
|
32
47
|
pid: number;
|
|
33
48
|
}>;
|
|
34
|
-
export declare const BlockCommandArgsSchema: z.ZodObject<{
|
|
35
|
-
command: z.ZodString;
|
|
36
|
-
}, "strip", z.ZodTypeAny, {
|
|
37
|
-
command: string;
|
|
38
|
-
}, {
|
|
39
|
-
command: string;
|
|
40
|
-
}>;
|
|
41
|
-
export declare const UnblockCommandArgsSchema: z.ZodObject<{
|
|
42
|
-
command: z.ZodString;
|
|
43
|
-
}, "strip", z.ZodTypeAny, {
|
|
44
|
-
command: string;
|
|
45
|
-
}, {
|
|
46
|
-
command: string;
|
|
47
|
-
}>;
|
|
48
49
|
export declare const ReadFileArgsSchema: z.ZodObject<{
|
|
49
50
|
path: z.ZodString;
|
|
50
51
|
isUrl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
@@ -145,9 +146,18 @@ export declare const SearchCodeArgsSchema: z.ZodObject<{
|
|
|
145
146
|
contextLines?: number | undefined;
|
|
146
147
|
}>;
|
|
147
148
|
export declare const EditBlockArgsSchema: z.ZodObject<{
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
file_path: z.ZodString;
|
|
150
|
+
old_string: z.ZodString;
|
|
151
|
+
new_string: z.ZodString;
|
|
152
|
+
expected_replacements: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
153
|
+
}, "strip", z.ZodTypeAny, {
|
|
154
|
+
file_path: string;
|
|
155
|
+
old_string: string;
|
|
156
|
+
new_string: string;
|
|
157
|
+
expected_replacements: number;
|
|
158
|
+
}, {
|
|
159
|
+
file_path: string;
|
|
160
|
+
old_string: string;
|
|
161
|
+
new_string: string;
|
|
162
|
+
expected_replacements?: number | undefined;
|
|
153
163
|
}>;
|
package/dist/tools/schemas.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
console.error("Loading schemas.ts");
|
|
3
|
+
// Config tools schemas
|
|
4
|
+
export const GetConfigArgsSchema = z.object({});
|
|
5
|
+
export const SetConfigValueArgsSchema = z.object({
|
|
6
|
+
key: z.string(),
|
|
7
|
+
value: z.any(),
|
|
8
|
+
});
|
|
9
|
+
// Empty schemas
|
|
10
|
+
export const ListProcessesArgsSchema = z.object({});
|
|
2
11
|
// Terminal tools schemas
|
|
3
12
|
export const ExecuteCommandArgsSchema = z.object({
|
|
4
13
|
command: z.string(),
|
|
5
14
|
timeout_ms: z.number().optional(),
|
|
15
|
+
shell: z.string().optional(),
|
|
6
16
|
});
|
|
7
17
|
export const ReadOutputArgsSchema = z.object({
|
|
8
18
|
pid: z.number(),
|
|
@@ -14,12 +24,6 @@ export const ListSessionsArgsSchema = z.object({});
|
|
|
14
24
|
export const KillProcessArgsSchema = z.object({
|
|
15
25
|
pid: z.number(),
|
|
16
26
|
});
|
|
17
|
-
export const BlockCommandArgsSchema = z.object({
|
|
18
|
-
command: z.string(),
|
|
19
|
-
});
|
|
20
|
-
export const UnblockCommandArgsSchema = z.object({
|
|
21
|
-
command: z.string(),
|
|
22
|
-
});
|
|
23
27
|
// Filesystem tools schemas
|
|
24
28
|
export const ReadFileArgsSchema = z.object({
|
|
25
29
|
path: z.string(),
|
|
@@ -61,7 +65,10 @@ export const SearchCodeArgsSchema = z.object({
|
|
|
61
65
|
contextLines: z.number().optional(),
|
|
62
66
|
timeoutMs: z.number().optional(),
|
|
63
67
|
});
|
|
64
|
-
// Edit tools
|
|
68
|
+
// Edit tools schema
|
|
65
69
|
export const EditBlockArgsSchema = z.object({
|
|
66
|
-
|
|
70
|
+
file_path: z.string(),
|
|
71
|
+
old_string: z.string(),
|
|
72
|
+
new_string: z.string(),
|
|
73
|
+
expected_replacements: z.number().optional().default(1),
|
|
67
74
|
});
|
package/dist/tools/search.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import { validatePath } from './filesystem.js';
|
|
5
5
|
import { rgPath } from '@vscode/ripgrep';
|
|
6
|
+
import { capture } from "../utils/capture.js";
|
|
6
7
|
// Function to search file contents using ripgrep
|
|
7
8
|
export async function searchCode(options) {
|
|
8
9
|
const { rootPath, pattern, filePattern, ignoreCase = true, maxResults = 1000, includeHidden = false, contextLines = 0 } = options;
|
|
@@ -76,9 +77,10 @@ export async function searchCode(options) {
|
|
|
76
77
|
});
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
catch (
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
catch (error) {
|
|
81
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
82
|
+
capture('server_request_error', { error: `Error parsing ripgrep output: ${errorMessage}` });
|
|
83
|
+
console.error(`Error parsing ripgrep output: ${errorMessage}`);
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
86
|
resolve(results);
|
|
@@ -164,7 +166,6 @@ export async function searchTextInFiles(options) {
|
|
|
164
166
|
return await searchCode(options);
|
|
165
167
|
}
|
|
166
168
|
catch (error) {
|
|
167
|
-
console.error('Ripgrep search failed, falling back to native implementation:', error);
|
|
168
169
|
return searchCodeFallback({
|
|
169
170
|
...options,
|
|
170
171
|
excludeDirs: ['node_modules', '.git', 'dist']
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizes error objects to remove potentially sensitive information like file paths
|
|
3
|
+
* @param error Error object or string to sanitize
|
|
4
|
+
* @returns An object with sanitized message and optional error code
|
|
5
|
+
*/
|
|
6
|
+
export declare function sanitizeError(error: any): {
|
|
7
|
+
message: string;
|
|
8
|
+
code?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Send an event to Google Analytics
|
|
12
|
+
* @param event Event name
|
|
13
|
+
* @param properties Optional event properties
|
|
14
|
+
*/
|
|
15
|
+
export declare const capture: (event: string, properties?: any) => Promise<void>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { platform } from 'os';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import * as https from 'https';
|
|
4
|
+
import { configManager } from '../config-manager.js';
|
|
5
|
+
let VERSION = 'unknown';
|
|
6
|
+
try {
|
|
7
|
+
const versionModule = await import('../version.js');
|
|
8
|
+
VERSION = versionModule.VERSION;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Continue without version info if not available
|
|
12
|
+
}
|
|
13
|
+
// Configuration
|
|
14
|
+
const GA_MEASUREMENT_ID = 'G-NGGDNL0K4L'; // Replace with your GA4 Measurement ID
|
|
15
|
+
const GA_API_SECRET = '5M0mC--2S_6t94m8WrI60A'; // Replace with your GA4 API Secret
|
|
16
|
+
const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
17
|
+
const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
18
|
+
// Will be initialized when needed
|
|
19
|
+
let uniqueUserId = 'unknown';
|
|
20
|
+
// Function to get or create a persistent UUID
|
|
21
|
+
async function getOrCreateUUID() {
|
|
22
|
+
try {
|
|
23
|
+
// Try to get the UUID from the config
|
|
24
|
+
let clientId = await configManager.getValue('clientId');
|
|
25
|
+
// If it doesn't exist, create a new one and save it
|
|
26
|
+
if (!clientId) {
|
|
27
|
+
clientId = randomUUID();
|
|
28
|
+
await configManager.setValue('clientId', clientId);
|
|
29
|
+
}
|
|
30
|
+
return clientId;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// Fallback to a random UUID if config operations fail
|
|
34
|
+
return randomUUID();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Sanitizes error objects to remove potentially sensitive information like file paths
|
|
39
|
+
* @param error Error object or string to sanitize
|
|
40
|
+
* @returns An object with sanitized message and optional error code
|
|
41
|
+
*/
|
|
42
|
+
export function sanitizeError(error) {
|
|
43
|
+
let errorMessage = '';
|
|
44
|
+
let errorCode = undefined;
|
|
45
|
+
if (error instanceof Error) {
|
|
46
|
+
// Extract just the error name and message without stack trace
|
|
47
|
+
errorMessage = error.name + ': ' + error.message;
|
|
48
|
+
// Extract error code if available (common in Node.js errors)
|
|
49
|
+
if ('code' in error) {
|
|
50
|
+
errorCode = error.code;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (typeof error === 'string') {
|
|
54
|
+
errorMessage = error;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
errorMessage = 'Unknown error';
|
|
58
|
+
}
|
|
59
|
+
// Remove any file paths using regex
|
|
60
|
+
// This pattern matches common path formats including Windows and Unix-style paths
|
|
61
|
+
errorMessage = errorMessage.replace(/(?:\/|\\)[\w\d_.-\/\\]+/g, '[PATH]');
|
|
62
|
+
errorMessage = errorMessage.replace(/[A-Za-z]:\\[\w\d_.-\/\\]+/g, '[PATH]');
|
|
63
|
+
return {
|
|
64
|
+
message: errorMessage,
|
|
65
|
+
code: errorCode
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Send an event to Google Analytics
|
|
70
|
+
* @param event Event name
|
|
71
|
+
* @param properties Optional event properties
|
|
72
|
+
*/
|
|
73
|
+
export const capture = async (event, properties) => {
|
|
74
|
+
try {
|
|
75
|
+
// Check if telemetry is enabled in config (defaults to true if not set)
|
|
76
|
+
const telemetryEnabled = await configManager.getValue('telemetryEnabled');
|
|
77
|
+
// If telemetry is explicitly disabled or GA credentials are missing, don't send
|
|
78
|
+
if (telemetryEnabled === false || !GA_MEASUREMENT_ID || !GA_API_SECRET) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Get or create the client ID if not already initialized
|
|
82
|
+
if (uniqueUserId === 'unknown') {
|
|
83
|
+
uniqueUserId = await getOrCreateUUID();
|
|
84
|
+
}
|
|
85
|
+
// Create a deep copy of properties to avoid modifying the original objects
|
|
86
|
+
// This ensures we don't alter error objects that are also returned to the AI
|
|
87
|
+
let sanitizedProperties;
|
|
88
|
+
try {
|
|
89
|
+
sanitizedProperties = properties ? JSON.parse(JSON.stringify(properties)) : {};
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
sanitizedProperties = {};
|
|
93
|
+
}
|
|
94
|
+
// Sanitize error objects if present
|
|
95
|
+
if (sanitizedProperties.error) {
|
|
96
|
+
// Handle different types of error objects
|
|
97
|
+
if (typeof sanitizedProperties.error === 'object' && sanitizedProperties.error !== null) {
|
|
98
|
+
const sanitized = sanitizeError(sanitizedProperties.error);
|
|
99
|
+
sanitizedProperties.error = sanitized.message;
|
|
100
|
+
if (sanitized.code) {
|
|
101
|
+
sanitizedProperties.errorCode = sanitized.code;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (typeof sanitizedProperties.error === 'string') {
|
|
105
|
+
sanitizedProperties.error = sanitizeError(sanitizedProperties.error).message;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Remove any properties that might contain paths
|
|
109
|
+
const sensitiveKeys = ['path', 'filePath', 'directory', 'file_path', 'sourcePath', 'destinationPath', 'fullPath', 'rootPath'];
|
|
110
|
+
for (const key of Object.keys(sanitizedProperties)) {
|
|
111
|
+
const lowerKey = key.toLowerCase();
|
|
112
|
+
if (sensitiveKeys.some(sensitiveKey => lowerKey.includes(sensitiveKey)) &&
|
|
113
|
+
lowerKey !== 'fileextension') { // keep fileExtension as it's safe
|
|
114
|
+
delete sanitizedProperties[key];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Prepare standard properties
|
|
118
|
+
const baseProperties = {
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
platform: platform(),
|
|
121
|
+
app_version: VERSION,
|
|
122
|
+
engagement_time_msec: "100"
|
|
123
|
+
};
|
|
124
|
+
// Combine with sanitized properties
|
|
125
|
+
const eventProperties = {
|
|
126
|
+
...baseProperties,
|
|
127
|
+
...sanitizedProperties
|
|
128
|
+
};
|
|
129
|
+
// Prepare GA4 payload
|
|
130
|
+
const payload = {
|
|
131
|
+
client_id: uniqueUserId,
|
|
132
|
+
non_personalized_ads: false,
|
|
133
|
+
timestamp_micros: Date.now() * 1000,
|
|
134
|
+
events: [{
|
|
135
|
+
name: event,
|
|
136
|
+
params: eventProperties
|
|
137
|
+
}]
|
|
138
|
+
};
|
|
139
|
+
// Send data to Google Analytics
|
|
140
|
+
const postData = JSON.stringify(payload);
|
|
141
|
+
const options = {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: {
|
|
144
|
+
'Content-Type': 'application/json',
|
|
145
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const req = https.request(GA_BASE_URL, options, (res) => {
|
|
149
|
+
// Response handling (optional)
|
|
150
|
+
let data = '';
|
|
151
|
+
res.on('data', (chunk) => {
|
|
152
|
+
data += chunk;
|
|
153
|
+
});
|
|
154
|
+
res.on('end', () => {
|
|
155
|
+
if (res.statusCode !== 200 && res.statusCode !== 204) {
|
|
156
|
+
// Optional debug logging
|
|
157
|
+
// console.debug(`GA tracking error: ${res.statusCode} ${data}`);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
req.on('error', () => {
|
|
162
|
+
// Silently fail - we don't want analytics issues to break functionality
|
|
163
|
+
});
|
|
164
|
+
// Set timeout to prevent blocking the app
|
|
165
|
+
req.setTimeout(3000, () => {
|
|
166
|
+
req.destroy();
|
|
167
|
+
});
|
|
168
|
+
// Send data
|
|
169
|
+
req.write(postData);
|
|
170
|
+
req.end();
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Silently fail - we don't want analytics issues to break functionality
|
|
174
|
+
}
|
|
175
|
+
};
|