@lupaflow/tools 0.1.1 → 0.1.2
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/dist/index.cjs +763 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +235 -0
- package/dist/index.d.ts +235 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/package.json +3 -2
- package/tsconfig.json +0 -8
- package/tsup.config.ts +0 -9
package/dist/index.js
ADDED
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
// src/registry.ts
|
|
2
|
+
import { ToolError } from "@lupaflow/core";
|
|
3
|
+
var ToolRegistry = class {
|
|
4
|
+
tools = /* @__PURE__ */ new Map();
|
|
5
|
+
register(tool) {
|
|
6
|
+
this.tools.set(tool.definition.name, tool);
|
|
7
|
+
}
|
|
8
|
+
get(name) {
|
|
9
|
+
const tool = this.tools.get(name);
|
|
10
|
+
if (!tool) {
|
|
11
|
+
throw new ToolError(name, `Tool not found: "${name}"`);
|
|
12
|
+
}
|
|
13
|
+
return tool;
|
|
14
|
+
}
|
|
15
|
+
has(name) {
|
|
16
|
+
return this.tools.has(name);
|
|
17
|
+
}
|
|
18
|
+
list() {
|
|
19
|
+
return Array.from(this.tools.values());
|
|
20
|
+
}
|
|
21
|
+
remove(name) {
|
|
22
|
+
this.tools.delete(name);
|
|
23
|
+
}
|
|
24
|
+
clear() {
|
|
25
|
+
this.tools.clear();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var toolRegistry = new ToolRegistry();
|
|
29
|
+
|
|
30
|
+
// src/interface.ts
|
|
31
|
+
var BaseTool = class {
|
|
32
|
+
toJSON() {
|
|
33
|
+
return this.definition;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/builtins/calculator.ts
|
|
38
|
+
var CalculatorTool = class extends BaseTool {
|
|
39
|
+
definition = {
|
|
40
|
+
name: "calculator",
|
|
41
|
+
description: "Evaluate mathematical expressions safely",
|
|
42
|
+
parameters: {
|
|
43
|
+
expression: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Mathematical expression to evaluate (e.g., '2 + 2 * 3')"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
required: ["expression"]
|
|
49
|
+
};
|
|
50
|
+
async execute(args) {
|
|
51
|
+
const { expression } = args;
|
|
52
|
+
const sanitized = expression.replace(/[^0-9+\-*/.()^% ]/g, "");
|
|
53
|
+
try {
|
|
54
|
+
const result = Function(`"use strict"; return (${sanitized})`)();
|
|
55
|
+
return { result, expression };
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return { error: err.message, expression };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/builtins/http.ts
|
|
63
|
+
var HTTPTool = class extends BaseTool {
|
|
64
|
+
definition = {
|
|
65
|
+
name: "http",
|
|
66
|
+
description: "Make HTTP requests (GET, POST, PUT, DELETE)",
|
|
67
|
+
parameters: {
|
|
68
|
+
method: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "HTTP method: GET, POST, PUT, DELETE"
|
|
71
|
+
},
|
|
72
|
+
url: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Request URL"
|
|
75
|
+
},
|
|
76
|
+
headers: {
|
|
77
|
+
type: "object",
|
|
78
|
+
description: "Request headers as key-value object"
|
|
79
|
+
},
|
|
80
|
+
body: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Request body (stringified JSON for POST/PUT)"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
required: ["method", "url"]
|
|
86
|
+
};
|
|
87
|
+
async execute(args) {
|
|
88
|
+
const { method, url, headers, body } = args;
|
|
89
|
+
const response = await fetch(url, {
|
|
90
|
+
method: method.toUpperCase(),
|
|
91
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
92
|
+
body: body || void 0
|
|
93
|
+
});
|
|
94
|
+
const text = await response.text();
|
|
95
|
+
let data = text;
|
|
96
|
+
try {
|
|
97
|
+
data = JSON.parse(text);
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
status: response.status,
|
|
102
|
+
statusText: response.statusText,
|
|
103
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
104
|
+
data
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/builtins/search.ts
|
|
110
|
+
var SearchTool = class extends BaseTool {
|
|
111
|
+
definition = {
|
|
112
|
+
name: "search",
|
|
113
|
+
description: "Search the web for information",
|
|
114
|
+
parameters: {
|
|
115
|
+
query: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "Search query"
|
|
118
|
+
},
|
|
119
|
+
maxResults: {
|
|
120
|
+
type: "number",
|
|
121
|
+
description: "Maximum number of results (default: 5)"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
required: ["query"]
|
|
125
|
+
};
|
|
126
|
+
async execute(args) {
|
|
127
|
+
const { query, maxResults } = args;
|
|
128
|
+
const tavilyKey = process.env.TAVILY_API_KEY;
|
|
129
|
+
if (tavilyKey) {
|
|
130
|
+
return this.searchTavily(query, maxResults || 5, tavilyKey);
|
|
131
|
+
}
|
|
132
|
+
return this.searchDuckDuckGo(query, maxResults || 5);
|
|
133
|
+
}
|
|
134
|
+
async searchTavily(query, maxResults, apiKey) {
|
|
135
|
+
try {
|
|
136
|
+
const res = await fetch("https://api.tavily.com/search", {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: { "Content-Type": "application/json" },
|
|
139
|
+
body: JSON.stringify({
|
|
140
|
+
api_key: apiKey,
|
|
141
|
+
query,
|
|
142
|
+
search_depth: "advanced",
|
|
143
|
+
include_answer: true,
|
|
144
|
+
max_results: maxResults
|
|
145
|
+
})
|
|
146
|
+
});
|
|
147
|
+
if (!res.ok) {
|
|
148
|
+
throw new Error(`Tavily API error: ${res.status} ${res.statusText}`);
|
|
149
|
+
}
|
|
150
|
+
const data = await res.json();
|
|
151
|
+
return {
|
|
152
|
+
query,
|
|
153
|
+
results: data.results.map((r) => ({
|
|
154
|
+
title: r.title,
|
|
155
|
+
url: r.url,
|
|
156
|
+
snippet: r.content
|
|
157
|
+
})),
|
|
158
|
+
answer: data.answer,
|
|
159
|
+
source: "tavily"
|
|
160
|
+
};
|
|
161
|
+
} catch (err) {
|
|
162
|
+
return { error: err.message, query, source: "tavily" };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async searchDuckDuckGo(query, maxResults) {
|
|
166
|
+
try {
|
|
167
|
+
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
168
|
+
const response = await fetch(url);
|
|
169
|
+
const html = await response.text();
|
|
170
|
+
const results = [];
|
|
171
|
+
const linkRegex = /<a[^>]+class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/g;
|
|
172
|
+
const snippetRegex = /<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
|
|
173
|
+
let count = 0;
|
|
174
|
+
let linkMatch;
|
|
175
|
+
while ((linkMatch = linkRegex.exec(html)) !== null && count < maxResults) {
|
|
176
|
+
const snippetMatch = snippetRegex.exec(html);
|
|
177
|
+
results.push({
|
|
178
|
+
link: linkMatch[1],
|
|
179
|
+
title: linkMatch[2].replace(/<[^>]*>/g, ""),
|
|
180
|
+
snippet: snippetMatch ? snippetMatch[1].replace(/<[^>]*>/g, "") : ""
|
|
181
|
+
});
|
|
182
|
+
count++;
|
|
183
|
+
}
|
|
184
|
+
return { query, results, source: "duckduckgo" };
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return { error: err.message, query, source: "duckduckgo" };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// src/builtins/readFile.ts
|
|
192
|
+
import { readFile } from "fs/promises";
|
|
193
|
+
import { relative, resolve, isAbsolute } from "path";
|
|
194
|
+
var MAX_FILE_SIZE = 1e4;
|
|
195
|
+
var ReadFileTool = class extends BaseTool {
|
|
196
|
+
definition = {
|
|
197
|
+
name: "readFile",
|
|
198
|
+
description: "Read the contents of a file",
|
|
199
|
+
parameters: {
|
|
200
|
+
path: {
|
|
201
|
+
type: "string",
|
|
202
|
+
description: "Relative path to the file to read"
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
required: ["path"]
|
|
206
|
+
};
|
|
207
|
+
async execute(args) {
|
|
208
|
+
const { path } = args;
|
|
209
|
+
const cwd = process.cwd();
|
|
210
|
+
const resolved = resolve(cwd, path);
|
|
211
|
+
const rel = relative(cwd, resolved);
|
|
212
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
213
|
+
return { error: "Path is outside the project directory" };
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const content = await readFile(resolved, "utf-8");
|
|
217
|
+
if (content.length > MAX_FILE_SIZE) {
|
|
218
|
+
return {
|
|
219
|
+
content: content.slice(0, MAX_FILE_SIZE),
|
|
220
|
+
truncated: true,
|
|
221
|
+
totalLength: content.length
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return { content };
|
|
225
|
+
} catch (err) {
|
|
226
|
+
const msg = err.message?.replace(/^ENOENT:\s*/, "") || String(err);
|
|
227
|
+
return { error: msg };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// src/builtins/writeFile.ts
|
|
233
|
+
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
234
|
+
import { dirname, relative as relative2, resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
|
|
235
|
+
var WriteFileTool = class extends BaseTool {
|
|
236
|
+
definition = {
|
|
237
|
+
name: "writeFile",
|
|
238
|
+
description: "Create a new file or overwrite an existing one",
|
|
239
|
+
parameters: {
|
|
240
|
+
path: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Relative path to write"
|
|
243
|
+
},
|
|
244
|
+
content: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "File contents"
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
required: ["path", "content"]
|
|
250
|
+
};
|
|
251
|
+
async execute(args) {
|
|
252
|
+
const { path, content } = args;
|
|
253
|
+
const cwd = process.cwd();
|
|
254
|
+
const resolved = resolve2(cwd, path);
|
|
255
|
+
const rel = relative2(cwd, resolved);
|
|
256
|
+
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
257
|
+
return { error: "Path is outside the project directory" };
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
let oldContent = null;
|
|
261
|
+
try {
|
|
262
|
+
oldContent = await readFile2(resolved, "utf-8");
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
const backupDir = resolve2(cwd, ".lupaflow/backups");
|
|
266
|
+
if (oldContent !== null) {
|
|
267
|
+
const backupPath = resolve2(backupDir, rel);
|
|
268
|
+
await mkdir(dirname(backupPath), { recursive: true });
|
|
269
|
+
await writeFile(backupPath, oldContent, "utf-8");
|
|
270
|
+
}
|
|
271
|
+
await mkdir(dirname(resolved), { recursive: true });
|
|
272
|
+
await writeFile(resolved, content, "utf-8");
|
|
273
|
+
const result = {
|
|
274
|
+
success: true,
|
|
275
|
+
path: rel
|
|
276
|
+
};
|
|
277
|
+
if (oldContent !== null) {
|
|
278
|
+
result.diff = computeDiff(oldContent, content);
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
} catch (err) {
|
|
282
|
+
return { error: err.message };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
function computeDiff(oldStr, newStr) {
|
|
287
|
+
const oldLines = oldStr.split("\n");
|
|
288
|
+
const newLines = newStr.split("\n");
|
|
289
|
+
const lines = [];
|
|
290
|
+
let i = 0, j = 0;
|
|
291
|
+
while (i < oldLines.length || j < newLines.length) {
|
|
292
|
+
if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) {
|
|
293
|
+
lines.push(` ${oldLines[i]}`);
|
|
294
|
+
i++;
|
|
295
|
+
j++;
|
|
296
|
+
} else {
|
|
297
|
+
let found = false;
|
|
298
|
+
for (let k = 1; k <= 3; k++) {
|
|
299
|
+
if (i + k < oldLines.length && oldLines[i + k] === newLines[j]) {
|
|
300
|
+
for (let l = 0; l < k; l++) lines.push(`-${oldLines[i + l]}`);
|
|
301
|
+
i += k;
|
|
302
|
+
found = true;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
if (j + k < newLines.length && oldLines[i] === newLines[j + k]) {
|
|
306
|
+
for (let l = 0; l < k; l++) lines.push(`+${newLines[j + l]}`);
|
|
307
|
+
j += k;
|
|
308
|
+
found = true;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (!found) {
|
|
313
|
+
if (i < oldLines.length) lines.push(`-${oldLines[i]}`);
|
|
314
|
+
if (j < newLines.length) lines.push(`+${newLines[j]}`);
|
|
315
|
+
i++;
|
|
316
|
+
j++;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return lines.join("\n");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/builtins/editFile.ts
|
|
324
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
325
|
+
import { dirname as dirname2, relative as relative3, resolve as resolve3, isAbsolute as isAbsolute3 } from "path";
|
|
326
|
+
var EditFileTool = class extends BaseTool {
|
|
327
|
+
definition = {
|
|
328
|
+
name: "editFile",
|
|
329
|
+
description: "Replace an exact string match in a file with new content",
|
|
330
|
+
parameters: {
|
|
331
|
+
path: {
|
|
332
|
+
type: "string",
|
|
333
|
+
description: "Relative path to edit"
|
|
334
|
+
},
|
|
335
|
+
oldString: {
|
|
336
|
+
type: "string",
|
|
337
|
+
description: "Exact text to replace; must be unique in the file"
|
|
338
|
+
},
|
|
339
|
+
newString: {
|
|
340
|
+
type: "string",
|
|
341
|
+
description: "Replacement text"
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
required: ["path", "oldString", "newString"]
|
|
345
|
+
};
|
|
346
|
+
async execute(args) {
|
|
347
|
+
const { path, oldString, newString } = args;
|
|
348
|
+
const cwd = process.cwd();
|
|
349
|
+
const resolved = resolve3(cwd, path);
|
|
350
|
+
const rel = relative3(cwd, resolved);
|
|
351
|
+
if (rel.startsWith("..") || isAbsolute3(rel)) {
|
|
352
|
+
return { error: "Path is outside the project directory" };
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const content = await readFile3(resolved, "utf-8");
|
|
356
|
+
const occurrences = content.split(oldString).length - 1;
|
|
357
|
+
if (occurrences === 0) {
|
|
358
|
+
return { error: "oldString not found in file" };
|
|
359
|
+
}
|
|
360
|
+
if (occurrences > 1) {
|
|
361
|
+
return { error: `oldString is ambiguous; found ${occurrences} matches` };
|
|
362
|
+
}
|
|
363
|
+
const backupDir = resolve3(cwd, ".lupaflow/backups");
|
|
364
|
+
const backupPath = resolve3(backupDir, rel);
|
|
365
|
+
await mkdir2(dirname2(backupPath), { recursive: true });
|
|
366
|
+
await writeFile2(backupPath, content, "utf-8");
|
|
367
|
+
const newContent = content.replace(oldString, newString);
|
|
368
|
+
await writeFile2(resolved, newContent, "utf-8");
|
|
369
|
+
return {
|
|
370
|
+
success: true,
|
|
371
|
+
path: rel,
|
|
372
|
+
diff: computeDiff2(content, newContent)
|
|
373
|
+
};
|
|
374
|
+
} catch (err) {
|
|
375
|
+
return { error: err.message };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
function computeDiff2(oldStr, newStr) {
|
|
380
|
+
const oldLines = oldStr.split("\n");
|
|
381
|
+
const newLines = newStr.split("\n");
|
|
382
|
+
const lines = [];
|
|
383
|
+
let i = 0, j = 0;
|
|
384
|
+
while (i < oldLines.length || j < newLines.length) {
|
|
385
|
+
if (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) {
|
|
386
|
+
lines.push(` ${oldLines[i]}`);
|
|
387
|
+
i++;
|
|
388
|
+
j++;
|
|
389
|
+
} else {
|
|
390
|
+
let found = false;
|
|
391
|
+
for (let k = 1; k <= 3; k++) {
|
|
392
|
+
if (i + k < oldLines.length && oldLines[i + k] === newLines[j]) {
|
|
393
|
+
for (let l = 0; l < k; l++) lines.push(`-${oldLines[i + l]}`);
|
|
394
|
+
i += k;
|
|
395
|
+
found = true;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
if (j + k < newLines.length && oldLines[i] === newLines[j + k]) {
|
|
399
|
+
for (let l = 0; l < k; l++) lines.push(`+${newLines[j + l]}`);
|
|
400
|
+
j += k;
|
|
401
|
+
found = true;
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (!found) {
|
|
406
|
+
if (i < oldLines.length) lines.push(`-${oldLines[i]}`);
|
|
407
|
+
if (j < newLines.length) lines.push(`+${newLines[j]}`);
|
|
408
|
+
i++;
|
|
409
|
+
j++;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return lines.join("\n");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/builtins/deleteFile.ts
|
|
417
|
+
import { unlink } from "fs/promises";
|
|
418
|
+
import { relative as relative4, resolve as resolve4, isAbsolute as isAbsolute4 } from "path";
|
|
419
|
+
var DeleteFileTool = class extends BaseTool {
|
|
420
|
+
definition = {
|
|
421
|
+
name: "deleteFile",
|
|
422
|
+
description: "Delete a file",
|
|
423
|
+
parameters: {
|
|
424
|
+
path: {
|
|
425
|
+
type: "string",
|
|
426
|
+
description: "Relative path to the file to delete"
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
required: ["path"]
|
|
430
|
+
};
|
|
431
|
+
async execute(args) {
|
|
432
|
+
const { path } = args;
|
|
433
|
+
const cwd = process.cwd();
|
|
434
|
+
const resolved = resolve4(cwd, path);
|
|
435
|
+
const rel = relative4(cwd, resolved);
|
|
436
|
+
if (rel.startsWith("..") || isAbsolute4(rel)) {
|
|
437
|
+
return { error: "Path is outside the project directory" };
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
await unlink(resolved);
|
|
441
|
+
return { success: true, path: rel };
|
|
442
|
+
} catch (err) {
|
|
443
|
+
return { error: err.message };
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// src/builtins/listDirectory.ts
|
|
449
|
+
import { readdir, stat } from "fs/promises";
|
|
450
|
+
import { join, relative as relative5, resolve as resolve5, isAbsolute as isAbsolute5 } from "path";
|
|
451
|
+
var ListDirectoryTool = class extends BaseTool {
|
|
452
|
+
definition = {
|
|
453
|
+
name: "listDirectory",
|
|
454
|
+
description: "List entries in a directory",
|
|
455
|
+
parameters: {
|
|
456
|
+
path: {
|
|
457
|
+
type: "string",
|
|
458
|
+
description: "Relative directory path to list"
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
required: ["path"]
|
|
462
|
+
};
|
|
463
|
+
async execute(args) {
|
|
464
|
+
const { path } = args;
|
|
465
|
+
const cwd = process.cwd();
|
|
466
|
+
const resolved = resolve5(cwd, path);
|
|
467
|
+
const rel = relative5(cwd, resolved);
|
|
468
|
+
if (rel.startsWith("..") || isAbsolute5(rel)) {
|
|
469
|
+
return { error: "Path is outside the project directory" };
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
const entries = await readdir(resolved);
|
|
473
|
+
const results = [];
|
|
474
|
+
for (const entry of entries) {
|
|
475
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
476
|
+
try {
|
|
477
|
+
const info = await stat(join(resolved, entry));
|
|
478
|
+
results.push({
|
|
479
|
+
name: entry,
|
|
480
|
+
type: info.isDirectory() ? "directory" : "file"
|
|
481
|
+
});
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
results.sort(
|
|
486
|
+
(a, b) => a.type !== b.type ? a.type === "directory" ? -1 : 1 : a.name.localeCompare(b.name)
|
|
487
|
+
);
|
|
488
|
+
return { path: rel || ".", entries: results };
|
|
489
|
+
} catch (err) {
|
|
490
|
+
return { error: err.message };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// src/builtins/glob.ts
|
|
496
|
+
import { relative as relative6, resolve as resolve6, isAbsolute as isAbsolute6 } from "path";
|
|
497
|
+
var MAX_RESULTS = 200;
|
|
498
|
+
var GlobTool = class extends BaseTool {
|
|
499
|
+
definition = {
|
|
500
|
+
name: "glob",
|
|
501
|
+
description: "Find files matching a glob pattern",
|
|
502
|
+
parameters: {
|
|
503
|
+
pattern: {
|
|
504
|
+
type: "string",
|
|
505
|
+
description: "Glob pattern to match files"
|
|
506
|
+
},
|
|
507
|
+
path: {
|
|
508
|
+
type: "string",
|
|
509
|
+
description: "Directory to search from (default: .)"
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
required: ["pattern"]
|
|
513
|
+
};
|
|
514
|
+
async execute(args) {
|
|
515
|
+
const { pattern, path: dir = "." } = args;
|
|
516
|
+
const cwd = process.cwd();
|
|
517
|
+
const resolved = resolve6(cwd, dir);
|
|
518
|
+
const rel = relative6(cwd, resolved);
|
|
519
|
+
if (rel.startsWith("..") || isAbsolute6(rel)) {
|
|
520
|
+
return { error: "Path is outside the project directory" };
|
|
521
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
const glob = new Bun.Glob(pattern);
|
|
524
|
+
const files = [];
|
|
525
|
+
let truncated = false;
|
|
526
|
+
for await (const match of glob.scan({ cwd: resolved, dot: false, onlyFiles: true })) {
|
|
527
|
+
if (match.includes("node_modules")) continue;
|
|
528
|
+
if (files.length >= MAX_RESULTS) {
|
|
529
|
+
truncated = true;
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
files.push(relative6(cwd, resolve6(resolved, match)));
|
|
533
|
+
}
|
|
534
|
+
files.sort();
|
|
535
|
+
const result = { files };
|
|
536
|
+
if (truncated) result.truncated = true;
|
|
537
|
+
return result;
|
|
538
|
+
} catch (err) {
|
|
539
|
+
return { error: err.message };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/builtins/grep.ts
|
|
545
|
+
import { relative as relative7, resolve as resolve7, isAbsolute as isAbsolute7 } from "path";
|
|
546
|
+
var MAX_MATCHES = 50;
|
|
547
|
+
var GrepTool = class extends BaseTool {
|
|
548
|
+
definition = {
|
|
549
|
+
name: "grep",
|
|
550
|
+
description: "Search file contents using a regex pattern",
|
|
551
|
+
parameters: {
|
|
552
|
+
pattern: {
|
|
553
|
+
type: "string",
|
|
554
|
+
description: "Regex pattern to search for"
|
|
555
|
+
},
|
|
556
|
+
path: {
|
|
557
|
+
type: "string",
|
|
558
|
+
description: "Directory to search from (default: .)"
|
|
559
|
+
},
|
|
560
|
+
include: {
|
|
561
|
+
type: "string",
|
|
562
|
+
description: "Optional glob for files to include (e.g. *.ts)"
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
required: ["pattern"]
|
|
566
|
+
};
|
|
567
|
+
async execute(args) {
|
|
568
|
+
const { pattern, path: dir = ".", include } = args;
|
|
569
|
+
const cwd = process.cwd();
|
|
570
|
+
const resolved = resolve7(cwd, dir);
|
|
571
|
+
const rel = relative7(cwd, resolved);
|
|
572
|
+
if (rel.startsWith("..") || isAbsolute7(rel)) {
|
|
573
|
+
return { error: "Path is outside the project directory" };
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
const grepArgs = [
|
|
577
|
+
"-rn",
|
|
578
|
+
"--color=never",
|
|
579
|
+
"--exclude-dir=node_modules",
|
|
580
|
+
"--exclude-dir=.git",
|
|
581
|
+
"-E"
|
|
582
|
+
];
|
|
583
|
+
if (include) grepArgs.push(`--include=${include}`);
|
|
584
|
+
grepArgs.push(pattern, resolved);
|
|
585
|
+
const proc = Bun.spawn(["grep", ...grepArgs], {
|
|
586
|
+
cwd,
|
|
587
|
+
stdout: "pipe",
|
|
588
|
+
stderr: "pipe"
|
|
589
|
+
});
|
|
590
|
+
const [stdout, stderr] = await Promise.all([
|
|
591
|
+
new Response(proc.stdout).text(),
|
|
592
|
+
new Response(proc.stderr).text()
|
|
593
|
+
]);
|
|
594
|
+
const exitCode = await proc.exited;
|
|
595
|
+
if (exitCode !== 0 && exitCode !== 1) {
|
|
596
|
+
const msg = stderr.trim() || `grep exited with code ${exitCode}`;
|
|
597
|
+
return { error: msg };
|
|
598
|
+
}
|
|
599
|
+
const trimmed = stderr.trim();
|
|
600
|
+
if (trimmed) {
|
|
601
|
+
return { error: trimmed };
|
|
602
|
+
}
|
|
603
|
+
const lines = stdout.trim().split("\n");
|
|
604
|
+
if (!lines[0]) {
|
|
605
|
+
return { matches: [], message: "No matches found" };
|
|
606
|
+
}
|
|
607
|
+
const matches = [];
|
|
608
|
+
let truncated = false;
|
|
609
|
+
for (const line of lines) {
|
|
610
|
+
if (matches.length >= MAX_MATCHES) {
|
|
611
|
+
truncated = true;
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
615
|
+
if (match) {
|
|
616
|
+
matches.push({
|
|
617
|
+
file: relative7(cwd, match[1]),
|
|
618
|
+
line: Number(match[2]),
|
|
619
|
+
content: match[3]
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const result = { matches };
|
|
624
|
+
if (truncated) {
|
|
625
|
+
result.truncated = true;
|
|
626
|
+
result.totalMatches = lines.length;
|
|
627
|
+
}
|
|
628
|
+
return result;
|
|
629
|
+
} catch (err) {
|
|
630
|
+
return { error: err.message || String(err) };
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// src/builtins/shell.ts
|
|
636
|
+
var MAX_OUTPUT = 2e4;
|
|
637
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
638
|
+
var ShellTool = class extends BaseTool {
|
|
639
|
+
definition = {
|
|
640
|
+
name: "shell",
|
|
641
|
+
description: "Execute a shell command",
|
|
642
|
+
parameters: {
|
|
643
|
+
command: {
|
|
644
|
+
type: "string",
|
|
645
|
+
description: "Shell command to run"
|
|
646
|
+
},
|
|
647
|
+
description: {
|
|
648
|
+
type: "string",
|
|
649
|
+
description: "Short description of what the command does"
|
|
650
|
+
},
|
|
651
|
+
timeout: {
|
|
652
|
+
type: "number",
|
|
653
|
+
description: "Timeout in milliseconds (default: 30000)"
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
required: ["command"]
|
|
657
|
+
};
|
|
658
|
+
async execute(args) {
|
|
659
|
+
const { command, timeout = DEFAULT_TIMEOUT } = args;
|
|
660
|
+
try {
|
|
661
|
+
const isWindows = process.platform === "win32";
|
|
662
|
+
const shell = isWindows ? "cmd.exe" : "bash";
|
|
663
|
+
const shellFlag = isWindows ? "/c" : "-c";
|
|
664
|
+
const env = isWindows ? { ...process.env } : { ...process.env, TERM: "dumb" };
|
|
665
|
+
const proc = Bun.spawn([shell, shellFlag, command], {
|
|
666
|
+
cwd: process.cwd(),
|
|
667
|
+
stdout: "pipe",
|
|
668
|
+
stderr: "pipe",
|
|
669
|
+
env
|
|
670
|
+
});
|
|
671
|
+
const timer = setTimeout(() => proc.kill(), timeout);
|
|
672
|
+
const [stdout, stderr] = await Promise.all([
|
|
673
|
+
new Response(proc.stdout).text(),
|
|
674
|
+
new Response(proc.stderr).text()
|
|
675
|
+
]);
|
|
676
|
+
const exitCode = await proc.exited;
|
|
677
|
+
clearTimeout(timer);
|
|
678
|
+
return {
|
|
679
|
+
stdout: truncate(stdout, MAX_OUTPUT),
|
|
680
|
+
stderr: truncate(stderr, MAX_OUTPUT),
|
|
681
|
+
exitCode
|
|
682
|
+
};
|
|
683
|
+
} catch (err) {
|
|
684
|
+
return {
|
|
685
|
+
stdout: "",
|
|
686
|
+
stderr: err.message || String(err),
|
|
687
|
+
exitCode: 1
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
function truncate(value, limit) {
|
|
693
|
+
return value.length > limit ? `${value.slice(0, limit)}
|
|
694
|
+
... (truncated, ${value.length} total chars)` : value;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// src/index.ts
|
|
698
|
+
toolRegistry.register(new CalculatorTool());
|
|
699
|
+
toolRegistry.register(new HTTPTool());
|
|
700
|
+
toolRegistry.register(new SearchTool());
|
|
701
|
+
toolRegistry.register(new ReadFileTool());
|
|
702
|
+
toolRegistry.register(new WriteFileTool());
|
|
703
|
+
toolRegistry.register(new EditFileTool());
|
|
704
|
+
toolRegistry.register(new DeleteFileTool());
|
|
705
|
+
toolRegistry.register(new ListDirectoryTool());
|
|
706
|
+
toolRegistry.register(new GlobTool());
|
|
707
|
+
toolRegistry.register(new GrepTool());
|
|
708
|
+
toolRegistry.register(new ShellTool());
|
|
709
|
+
export {
|
|
710
|
+
BaseTool,
|
|
711
|
+
CalculatorTool,
|
|
712
|
+
DeleteFileTool,
|
|
713
|
+
EditFileTool,
|
|
714
|
+
GlobTool,
|
|
715
|
+
GrepTool,
|
|
716
|
+
HTTPTool,
|
|
717
|
+
ListDirectoryTool,
|
|
718
|
+
ReadFileTool,
|
|
719
|
+
SearchTool,
|
|
720
|
+
ShellTool,
|
|
721
|
+
WriteFileTool,
|
|
722
|
+
toolRegistry
|
|
723
|
+
};
|
|
724
|
+
//# sourceMappingURL=index.js.map
|