amai 0.0.21 → 0.0.22

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/server.js CHANGED
@@ -1,1856 +1,23 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
  import WebSocket2 from 'ws';
3
- import { z } from 'zod';
4
- import path10 from 'path';
5
- import fs8, { readdirSync } from 'fs';
6
- import * as fsp from 'fs/promises';
7
- import fsp__default, { mkdir, unlink } from 'fs/promises';
8
- import os3 from 'os';
9
- import pc3 from 'picocolors';
10
- import { Hono } from 'hono';
11
- import { serve } from '@hono/node-server';
12
- import { cors } from 'hono/cors';
13
- import { exec, spawn } from 'child_process';
14
- import { promisify } from 'util';
15
-
16
- var MUTATING_TOOLS = /* @__PURE__ */ new Set([
17
- "editFile",
18
- "deleteFile",
19
- "stringReplace",
20
- "bash"
21
- ]);
22
- function isMutatingTool(toolName) {
23
- return MUTATING_TOOLS.has(toolName);
24
- }
25
- function isPathWithinProject(filePath, projectCwd) {
26
- try {
27
- const resolvedCwd = safeRealpath(projectCwd);
28
- const resolved = path10.resolve(resolvedCwd, filePath);
29
- const resolvedTarget = safeRealpath(resolved);
30
- const rel = path10.relative(resolvedCwd, resolvedTarget);
31
- if (rel.startsWith("..") || path10.isAbsolute(rel)) {
32
- return false;
33
- }
34
- return true;
35
- } catch {
36
- return false;
37
- }
38
- }
39
- function safeRealpath(p) {
40
- try {
41
- return fs8.realpathSync(p);
42
- } catch {
43
- const parent = path10.dirname(p);
44
- try {
45
- const realParent = fs8.realpathSync(parent);
46
- return path10.join(realParent, path10.basename(p));
47
- } catch {
48
- return path10.resolve(p);
49
- }
50
- }
51
- }
52
- function validatePath(filePath, projectCwd) {
53
- if (!projectCwd) {
54
- return {
55
- valid: false,
56
- error: "ACCESS_DENIED: No project context provided"
57
- };
58
- }
59
- try {
60
- if (!isPathWithinProject(filePath, projectCwd)) {
61
- return {
62
- valid: false,
63
- error: `ACCESS_DENIED: Path "${filePath}" is outside project directory "${projectCwd}"`
64
- };
65
- }
66
- const resolvedCwd = safeRealpath(projectCwd);
67
- const resolvedPath = path10.resolve(resolvedCwd, filePath);
68
- return {
69
- valid: true,
70
- resolvedPath
71
- };
72
- } catch (error) {
73
- return {
74
- valid: false,
75
- error: `ACCESS_DENIED: Invalid path "${filePath}"`
76
- };
77
- }
78
- }
79
- function resolveProjectPath(filePath, projectCwd) {
80
- return path10.resolve(projectCwd, filePath);
81
- }
82
- function requireProjectCwd(toolName, projectCwd) {
83
- if (!projectCwd && isMutatingTool(toolName)) {
84
- return {
85
- allowed: false,
86
- error: `ACCESS_DENIED: Tool "${toolName}" requires a project context (projectCwd) but none was provided`
87
- };
88
- }
89
- return { allowed: true };
90
- }
91
-
92
- // src/tools/read-file.ts
93
- var MAX_FILE_SIZE = 10 * 1024 * 1024;
94
- var MAX_LINES_RETURNED = 2e3;
95
- var MAX_LINE_LENGTH = 2e3;
96
- var MAX_LINE_SUFFIX = `... (line truncated to ${MAX_LINE_LENGTH} chars)`;
97
- var MAX_BYTES = 50 * 1024;
98
- var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
99
- ".zip",
100
- ".tar",
101
- ".gz",
102
- ".exe",
103
- ".dll",
104
- ".so",
105
- ".class",
106
- ".jar",
107
- ".war",
108
- ".7z",
109
- ".doc",
110
- ".docx",
111
- ".xls",
112
- ".xlsx",
113
- ".ppt",
114
- ".pptx",
115
- ".odt",
116
- ".ods",
117
- ".odp",
118
- ".bin",
119
- ".dat",
120
- ".obj",
121
- ".o",
122
- ".a",
123
- ".lib",
124
- ".wasm",
125
- ".pyc",
126
- ".pyo",
127
- ".ico",
128
- ".bmp",
129
- ".ttf",
130
- ".woff",
131
- ".woff2",
132
- ".eot",
133
- ".mp3",
134
- ".mp4",
135
- ".avi",
136
- ".mov",
137
- ".flv"
138
- ]);
139
- async function isBinaryFile(filepath, fileSize) {
140
- const ext = path10.extname(filepath).toLowerCase();
141
- if (BINARY_EXTENSIONS.has(ext)) return true;
142
- if (fileSize === 0) return false;
143
- try {
144
- const fh = await fsp.open(filepath, "r");
145
- try {
146
- const sampleSize = Math.min(4096, fileSize);
147
- const bytes = Buffer.alloc(sampleSize);
148
- const result = await fh.read(bytes, 0, sampleSize, 0);
149
- if (result.bytesRead === 0) return false;
150
- let nonPrintableCount = 0;
151
- for (let i = 0; i < result.bytesRead; i++) {
152
- if (bytes[i] === 0) return true;
153
- if (bytes[i] < 9 || bytes[i] > 13 && bytes[i] < 32) {
154
- nonPrintableCount++;
155
- }
156
- }
157
- return nonPrintableCount / result.bytesRead > 0.3;
158
- } finally {
159
- await fh.close();
160
- }
161
- } catch {
162
- return false;
163
- }
164
- }
165
- async function findSimilarFiles(filepath) {
166
- const dir = path10.dirname(filepath);
167
- const base = path10.basename(filepath).toLowerCase();
168
- try {
169
- const entries = await fsp.readdir(dir);
170
- return entries.filter(
171
- (entry) => entry.toLowerCase().includes(base) || base.includes(entry.toLowerCase())
172
- ).map((entry) => path10.join(dir, entry)).slice(0, 3);
173
- } catch {
174
- return [];
175
- }
176
- }
177
- z.object({
178
- relative_file_path: z.string().describe("The path to the file or directory to read."),
179
- should_read_entire_file: z.boolean().describe("Whether to read the entire file.").optional().default(true),
180
- start_line_one_indexed: z.number().optional().describe("The one-indexed line number to start reading from (inclusive). Alias: offset."),
181
- end_line_one_indexed: z.number().optional().describe("The one-indexed line number to end reading at (inclusive). Alias: offset + limit.")
182
- });
183
- async function readFileContent(absolute_file_path, relative_file_path, should_read_entire_file, start_line_one_indexed, end_line_one_indexed) {
184
- let stat2;
185
- try {
186
- stat2 = fs8.statSync(absolute_file_path);
187
- } catch {
188
- const suggestions = await findSimilarFiles(absolute_file_path);
189
- let message = `File not found: ${relative_file_path}`;
190
- if (suggestions.length > 0) {
191
- message += `
192
-
193
- Did you mean one of these?
194
- ${suggestions.join("\n")}`;
195
- }
196
- return {
197
- success: false,
198
- message,
199
- error: "FILE_NOT_FOUND"
200
- };
201
- }
202
- if (stat2.isDirectory()) {
203
- try {
204
- const dirents = await fsp.readdir(absolute_file_path, { withFileTypes: true });
205
- const entries = await Promise.all(
206
- dirents.map(async (dirent) => {
207
- if (dirent.isDirectory()) return dirent.name + "/";
208
- if (dirent.isSymbolicLink()) {
209
- const target = await fsp.stat(path10.join(absolute_file_path, dirent.name)).catch(() => void 0);
210
- if (target?.isDirectory()) return dirent.name + "/";
211
- }
212
- return dirent.name;
213
- })
214
- );
215
- entries.sort((a, b) => a.localeCompare(b));
216
- const truncated = entries.length > MAX_LINES_RETURNED;
217
- const sliced = entries.slice(0, MAX_LINES_RETURNED);
218
- const output = [
219
- `<path>${absolute_file_path}</path>`,
220
- `<type>directory</type>`,
221
- `<entries>`,
222
- sliced.join("\n"),
223
- truncated ? `
224
- (Showing ${sliced.length} of ${entries.length} entries)` : `
225
- (${entries.length} entries)`,
226
- `</entries>`
227
- ].join("\n");
228
- return {
229
- success: true,
230
- message: `Listed directory: ${relative_file_path} (${entries.length} entries)`,
231
- content: output,
232
- totalLines: entries.length,
233
- truncated
234
- };
235
- } catch (err) {
236
- return {
237
- success: false,
238
- message: `Failed to list directory: ${relative_file_path}`,
239
- error: "READ_ERROR"
240
- };
241
- }
242
- }
243
- try {
244
- if (stat2.size > MAX_FILE_SIZE) {
245
- return {
246
- success: false,
247
- message: `File too large (${Math.round(stat2.size / 1024 / 1024)}MB). Maximum is ${MAX_FILE_SIZE / 1024 / 1024}MB. Use line ranges to read portions.`,
248
- error: "FILE_TOO_LARGE"
249
- };
250
- }
251
- const binary = await isBinaryFile(absolute_file_path, stat2.size);
252
- if (binary) {
253
- return {
254
- success: false,
255
- message: `Cannot read binary file: ${relative_file_path}`,
256
- error: "BINARY_FILE"
257
- };
258
- }
259
- const fileContent = await Bun.file(absolute_file_path).text();
260
- const lines = fileContent.split(/\r?\n/);
261
- const totalLines = lines.length;
262
- const start = should_read_entire_file ? 0 : (start_line_one_indexed ?? 1) - 1;
263
- const end = should_read_entire_file ? Math.min(totalLines, MAX_LINES_RETURNED) : Math.min(end_line_one_indexed ?? totalLines, totalLines);
264
- if (start >= totalLines && !(totalLines === 0 && start === 0)) {
265
- return {
266
- success: false,
267
- message: `Offset ${start + 1} is out of range for this file (${totalLines} lines)`,
268
- error: "INVALID_LINE_RANGE"
269
- };
270
- }
271
- const outputLines = [];
272
- let bytes = 0;
273
- let truncatedByBytes = false;
274
- let actualEnd = start;
275
- for (let i = start; i < end; i++) {
276
- let line = lines[i];
277
- if (line.length > MAX_LINE_LENGTH) {
278
- line = line.substring(0, MAX_LINE_LENGTH) + MAX_LINE_SUFFIX;
279
- }
280
- const numberedLine = `${i + 1}: ${line}`;
281
- const lineBytes = Buffer.byteLength(numberedLine, "utf-8") + (outputLines.length > 0 ? 1 : 0);
282
- if (bytes + lineBytes > MAX_BYTES && outputLines.length > 0) {
283
- truncatedByBytes = true;
284
- break;
285
- }
286
- outputLines.push(numberedLine);
287
- bytes += lineBytes;
288
- actualEnd = i + 1;
289
- }
290
- const hasMoreLines = actualEnd < totalLines;
291
- const truncated = truncatedByBytes || hasMoreLines || should_read_entire_file && totalLines > MAX_LINES_RETURNED;
292
- let output = `<path>${absolute_file_path}</path>
293
- <type>file</type>
294
- <content>
295
- `;
296
- output += outputLines.join("\n");
297
- if (truncatedByBytes) {
298
- output += `
299
-
300
- (Output capped at ${MAX_BYTES / 1024} KB. Showing lines ${start + 1}-${actualEnd}. Use start_line_one_indexed=${actualEnd + 1} to continue.)`;
301
- } else if (hasMoreLines && !should_read_entire_file) {
302
- output += `
303
-
304
- (Showing lines ${start + 1}-${actualEnd} of ${totalLines}. Use start_line_one_indexed=${actualEnd + 1} to continue.)`;
305
- } else {
306
- output += `
307
-
308
- (End of file - total ${totalLines} lines)`;
309
- }
310
- output += "\n</content>";
311
- return {
312
- success: true,
313
- message: truncated ? `Read lines ${start + 1}-${actualEnd} of ${totalLines} from: ${relative_file_path} (truncated)` : `Successfully read file: ${relative_file_path} (${totalLines} lines)`,
314
- content: output,
315
- totalLines,
316
- truncated
317
- };
318
- } catch (error) {
319
- return {
320
- success: false,
321
- message: `Failed to read file: ${relative_file_path}`,
322
- error: "READ_ERROR"
323
- };
324
- }
325
- }
326
- var read_file = async function(input, projectCwd) {
327
- const { relative_file_path, should_read_entire_file = true, start_line_one_indexed, end_line_one_indexed } = input;
328
- try {
329
- if (!relative_file_path) {
330
- return {
331
- success: false,
332
- message: "Missing required parameter: relative_file_path",
333
- error: "MISSING_TARGET_FILE"
334
- };
335
- }
336
- if (!should_read_entire_file) {
337
- if (start_line_one_indexed === void 0 || end_line_one_indexed === void 0) {
338
- return {
339
- success: false,
340
- message: "start_line_one_indexed and end_line_one_indexed are required when should_read_entire_file is false",
341
- error: "MISSING_LINE_RANGE"
342
- };
343
- }
344
- if (!Number.isInteger(start_line_one_indexed) || start_line_one_indexed < 1) {
345
- return {
346
- success: false,
347
- message: "start_line_one_indexed must be a positive integer (1-indexed)",
348
- error: "INVALID_START_LINE"
349
- };
350
- }
351
- if (!Number.isInteger(end_line_one_indexed) || end_line_one_indexed < 1) {
352
- return {
353
- success: false,
354
- message: "end_line_one_indexed must be a positive integer (1-indexed)",
355
- error: "INVALID_END_LINE"
356
- };
357
- }
358
- if (end_line_one_indexed < start_line_one_indexed) {
359
- return {
360
- success: false,
361
- message: "end_line_one_indexed must be greater than or equal to start_line_one_indexed",
362
- error: "INVALID_LINE_RANGE"
363
- };
364
- }
365
- }
366
- let absolute_file_path;
367
- if (projectCwd) {
368
- const validation = validatePath(relative_file_path, projectCwd);
369
- if (!validation.valid) {
370
- return {
371
- success: false,
372
- message: validation.error || "Path validation failed",
373
- error: "ACCESS_DENIED"
374
- };
375
- }
376
- absolute_file_path = validation.resolvedPath;
377
- } else {
378
- absolute_file_path = path10.resolve(relative_file_path);
379
- }
380
- return await readFileContent(
381
- absolute_file_path,
382
- relative_file_path,
383
- should_read_entire_file,
384
- start_line_one_indexed,
385
- end_line_one_indexed
386
- );
387
- } catch {
388
- return {
389
- success: false,
390
- message: `Failed to read file: ${relative_file_path}`,
391
- error: "READ_ERROR"
392
- };
393
- }
394
- };
395
-
396
- // ../../node_modules/.bun/diff@8.0.2/node_modules/diff/libesm/diff/base.js
397
- var Diff = class {
398
- diff(oldStr, newStr, options = {}) {
399
- let callback;
400
- if (typeof options === "function") {
401
- callback = options;
402
- options = {};
403
- } else if ("callback" in options) {
404
- callback = options.callback;
405
- }
406
- const oldString = this.castInput(oldStr, options);
407
- const newString = this.castInput(newStr, options);
408
- const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
409
- const newTokens = this.removeEmpty(this.tokenize(newString, options));
410
- return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
411
- }
412
- diffWithOptionsObj(oldTokens, newTokens, options, callback) {
413
- var _a;
414
- const done = (value) => {
415
- value = this.postProcess(value, options);
416
- if (callback) {
417
- setTimeout(function() {
418
- callback(value);
419
- }, 0);
420
- return void 0;
421
- } else {
422
- return value;
423
- }
424
- };
425
- const newLen = newTokens.length, oldLen = oldTokens.length;
426
- let editLength = 1;
427
- let maxEditLength = newLen + oldLen;
428
- if (options.maxEditLength != null) {
429
- maxEditLength = Math.min(maxEditLength, options.maxEditLength);
430
- }
431
- const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
432
- const abortAfterTimestamp = Date.now() + maxExecutionTime;
433
- const bestPath = [{ oldPos: -1, lastComponent: void 0 }];
434
- let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
435
- if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
436
- return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
437
- }
438
- let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
439
- const execEditLength = () => {
440
- for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
441
- let basePath;
442
- const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
443
- if (removePath) {
444
- bestPath[diagonalPath - 1] = void 0;
445
- }
446
- let canAdd = false;
447
- if (addPath) {
448
- const addPathNewPos = addPath.oldPos - diagonalPath;
449
- canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
450
- }
451
- const canRemove = removePath && removePath.oldPos + 1 < oldLen;
452
- if (!canAdd && !canRemove) {
453
- bestPath[diagonalPath] = void 0;
454
- continue;
455
- }
456
- if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
457
- basePath = this.addToPath(addPath, true, false, 0, options);
458
- } else {
459
- basePath = this.addToPath(removePath, false, true, 1, options);
460
- }
461
- newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
462
- if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
463
- return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
464
- } else {
465
- bestPath[diagonalPath] = basePath;
466
- if (basePath.oldPos + 1 >= oldLen) {
467
- maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
468
- }
469
- if (newPos + 1 >= newLen) {
470
- minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
471
- }
472
- }
473
- }
474
- editLength++;
475
- };
476
- if (callback) {
477
- (function exec2() {
478
- setTimeout(function() {
479
- if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
480
- return callback(void 0);
481
- }
482
- if (!execEditLength()) {
483
- exec2();
484
- }
485
- }, 0);
486
- })();
487
- } else {
488
- while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
489
- const ret = execEditLength();
490
- if (ret) {
491
- return ret;
492
- }
493
- }
494
- }
495
- }
496
- addToPath(path14, added, removed, oldPosInc, options) {
497
- const last = path14.lastComponent;
498
- if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
499
- return {
500
- oldPos: path14.oldPos + oldPosInc,
501
- lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
502
- };
503
- } else {
504
- return {
505
- oldPos: path14.oldPos + oldPosInc,
506
- lastComponent: { count: 1, added, removed, previousComponent: last }
507
- };
508
- }
509
- }
510
- extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
511
- const newLen = newTokens.length, oldLen = oldTokens.length;
512
- let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
513
- while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
514
- newPos++;
515
- oldPos++;
516
- commonCount++;
517
- if (options.oneChangePerToken) {
518
- basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
519
- }
520
- }
521
- if (commonCount && !options.oneChangePerToken) {
522
- basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
523
- }
524
- basePath.oldPos = oldPos;
525
- return newPos;
526
- }
527
- equals(left, right, options) {
528
- if (options.comparator) {
529
- return options.comparator(left, right);
530
- } else {
531
- return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
532
- }
533
- }
534
- removeEmpty(array) {
535
- const ret = [];
536
- for (let i = 0; i < array.length; i++) {
537
- if (array[i]) {
538
- ret.push(array[i]);
539
- }
540
- }
541
- return ret;
542
- }
543
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
544
- castInput(value, options) {
545
- return value;
546
- }
547
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
548
- tokenize(value, options) {
549
- return Array.from(value);
550
- }
551
- join(chars) {
552
- return chars.join("");
553
- }
554
- postProcess(changeObjects, options) {
555
- return changeObjects;
556
- }
557
- get useLongestToken() {
558
- return false;
559
- }
560
- buildValues(lastComponent, newTokens, oldTokens) {
561
- const components = [];
562
- let nextComponent;
563
- while (lastComponent) {
564
- components.push(lastComponent);
565
- nextComponent = lastComponent.previousComponent;
566
- delete lastComponent.previousComponent;
567
- lastComponent = nextComponent;
568
- }
569
- components.reverse();
570
- const componentLen = components.length;
571
- let componentPos = 0, newPos = 0, oldPos = 0;
572
- for (; componentPos < componentLen; componentPos++) {
573
- const component = components[componentPos];
574
- if (!component.removed) {
575
- if (!component.added && this.useLongestToken) {
576
- let value = newTokens.slice(newPos, newPos + component.count);
577
- value = value.map(function(value2, i) {
578
- const oldValue = oldTokens[oldPos + i];
579
- return oldValue.length > value2.length ? oldValue : value2;
580
- });
581
- component.value = this.join(value);
582
- } else {
583
- component.value = this.join(newTokens.slice(newPos, newPos + component.count));
584
- }
585
- newPos += component.count;
586
- if (!component.added) {
587
- oldPos += component.count;
588
- }
589
- } else {
590
- component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
591
- oldPos += component.count;
592
- }
593
- }
594
- return components;
595
- }
596
- };
597
-
598
- // ../../node_modules/.bun/diff@8.0.2/node_modules/diff/libesm/diff/line.js
599
- var LineDiff = class extends Diff {
600
- constructor() {
601
- super(...arguments);
602
- this.tokenize = tokenize;
603
- }
604
- equals(left, right, options) {
605
- if (options.ignoreWhitespace) {
606
- if (!options.newlineIsToken || !left.includes("\n")) {
607
- left = left.trim();
608
- }
609
- if (!options.newlineIsToken || !right.includes("\n")) {
610
- right = right.trim();
611
- }
612
- } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
613
- if (left.endsWith("\n")) {
614
- left = left.slice(0, -1);
615
- }
616
- if (right.endsWith("\n")) {
617
- right = right.slice(0, -1);
618
- }
619
- }
620
- return super.equals(left, right, options);
621
- }
622
- };
623
- var lineDiff = new LineDiff();
624
- function diffLines(oldStr, newStr, options) {
625
- return lineDiff.diff(oldStr, newStr, options);
626
- }
627
- function tokenize(value, options) {
628
- if (options.stripTrailingCr) {
629
- value = value.replace(/\r\n/g, "\n");
630
- }
631
- const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
632
- if (!linesAndNewlines[linesAndNewlines.length - 1]) {
633
- linesAndNewlines.pop();
634
- }
635
- for (let i = 0; i < linesAndNewlines.length; i++) {
636
- const line = linesAndNewlines[i];
637
- if (i % 2 && !options.newlineIsToken) {
638
- retLines[retLines.length - 1] += line;
639
- } else {
640
- retLines.push(line);
641
- }
642
- }
643
- return retLines;
644
- }
645
-
646
- // src/lib/diff.ts
647
- function calculateDiffStats(oldContent, newContent) {
648
- const changes = diffLines(oldContent, newContent);
649
- let linesAdded = 0;
650
- let linesRemoved = 0;
651
- for (const change of changes) {
652
- if (change.added) {
653
- linesAdded += change.count || 0;
654
- } else if (change.removed) {
655
- linesRemoved += change.count || 0;
656
- }
657
- }
658
- return { linesAdded, linesRemoved };
659
- }
660
-
661
- // src/tools/stringReplace.ts
662
- z.object({
663
- file_path: z.string().describe("The path to the file you want to search and replace in. You can use either a relative path in the workspace or an absolute path. If an absolute path is provided, it will be preserved as is"),
664
- new_string: z.string().describe("The edited text to replace the old_string (must be different from the old_string)"),
665
- old_string: z.string().describe("The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)"),
666
- replaceAll: z.boolean().optional().describe("Replace all occurrences of old_string (default false)")
667
- });
668
- function levenshtein(a, b) {
669
- if (a === "" || b === "") return Math.max(a.length, b.length);
670
- const matrix = Array.from(
671
- { length: a.length + 1 },
672
- (_, i) => Array.from({ length: b.length + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
673
- );
674
- for (let i = 1; i <= a.length; i++) {
675
- for (let j = 1; j <= b.length; j++) {
676
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
677
- matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
678
- }
679
- }
680
- return matrix[a.length][b.length];
681
- }
682
- var SINGLE_CANDIDATE_SIMILARITY_THRESHOLD = 0;
683
- var MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD = 0.3;
684
- var SimpleReplacer = function* (_content, find) {
685
- yield find;
686
- };
687
- var LineTrimmedReplacer = function* (content, find) {
688
- const originalLines = content.split("\n");
689
- const searchLines = find.split("\n");
690
- if (searchLines[searchLines.length - 1] === "") searchLines.pop();
691
- for (let i = 0; i <= originalLines.length - searchLines.length; i++) {
692
- let matches = true;
693
- for (let j = 0; j < searchLines.length; j++) {
694
- if (originalLines[i + j].trim() !== searchLines[j].trim()) {
695
- matches = false;
696
- break;
697
- }
698
- }
699
- if (matches) {
700
- let matchStartIndex = 0;
701
- for (let k = 0; k < i; k++) matchStartIndex += originalLines[k].length + 1;
702
- let matchEndIndex = matchStartIndex;
703
- for (let k = 0; k < searchLines.length; k++) {
704
- matchEndIndex += originalLines[i + k].length;
705
- if (k < searchLines.length - 1) matchEndIndex += 1;
706
- }
707
- yield content.substring(matchStartIndex, matchEndIndex);
708
- }
709
- }
710
- };
711
- var BlockAnchorReplacer = function* (content, find) {
712
- const originalLines = content.split("\n");
713
- const searchLines = find.split("\n");
714
- if (searchLines.length < 3) return;
715
- if (searchLines[searchLines.length - 1] === "") searchLines.pop();
716
- const firstLineSearch = searchLines[0].trim();
717
- const lastLineSearch = searchLines[searchLines.length - 1].trim();
718
- const searchBlockSize = searchLines.length;
719
- const candidates = [];
720
- for (let i = 0; i < originalLines.length; i++) {
721
- if (originalLines[i].trim() !== firstLineSearch) continue;
722
- for (let j = i + 2; j < originalLines.length; j++) {
723
- if (originalLines[j].trim() === lastLineSearch) {
724
- candidates.push({ startLine: i, endLine: j });
725
- break;
726
- }
727
- }
728
- }
729
- if (candidates.length === 0) return;
730
- const computeSimilarity = (startLine, endLine) => {
731
- const actualBlockSize = endLine - startLine + 1;
732
- const linesToCheck = Math.min(searchBlockSize - 2, actualBlockSize - 2);
733
- if (linesToCheck <= 0) return 1;
734
- let similarity = 0;
735
- for (let j = 1; j < searchBlockSize - 1 && j < actualBlockSize - 1; j++) {
736
- const originalLine = originalLines[startLine + j].trim();
737
- const searchLine = searchLines[j].trim();
738
- const maxLen = Math.max(originalLine.length, searchLine.length);
739
- if (maxLen === 0) continue;
740
- const distance = levenshtein(originalLine, searchLine);
741
- similarity += (1 - distance / maxLen) / linesToCheck;
742
- }
743
- return similarity;
744
- };
745
- const extractBlock = (startLine, endLine) => {
746
- let matchStartIndex = 0;
747
- for (let k = 0; k < startLine; k++) matchStartIndex += originalLines[k].length + 1;
748
- let matchEndIndex = matchStartIndex;
749
- for (let k = startLine; k <= endLine; k++) {
750
- matchEndIndex += originalLines[k].length;
751
- if (k < endLine) matchEndIndex += 1;
752
- }
753
- return content.substring(matchStartIndex, matchEndIndex);
754
- };
755
- if (candidates.length === 1) {
756
- const { startLine, endLine } = candidates[0];
757
- if (computeSimilarity(startLine, endLine) >= SINGLE_CANDIDATE_SIMILARITY_THRESHOLD) {
758
- yield extractBlock(startLine, endLine);
759
- }
760
- return;
761
- }
762
- let bestMatch = null;
763
- let maxSimilarity = -1;
764
- for (const candidate of candidates) {
765
- const similarity = computeSimilarity(candidate.startLine, candidate.endLine);
766
- if (similarity > maxSimilarity) {
767
- maxSimilarity = similarity;
768
- bestMatch = candidate;
769
- }
770
- }
771
- if (maxSimilarity >= MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD && bestMatch) {
772
- yield extractBlock(bestMatch.startLine, bestMatch.endLine);
773
- }
774
- };
775
- var WhitespaceNormalizedReplacer = function* (content, find) {
776
- const normalizeWhitespace = (text) => text.replace(/\s+/g, " ").trim();
777
- const normalizedFind = normalizeWhitespace(find);
778
- const lines = content.split("\n");
779
- for (let i = 0; i < lines.length; i++) {
780
- const line = lines[i];
781
- if (normalizeWhitespace(line) === normalizedFind) {
782
- yield line;
783
- } else {
784
- const normalizedLine = normalizeWhitespace(line);
785
- if (normalizedLine.includes(normalizedFind)) {
786
- const words = find.trim().split(/\s+/);
787
- if (words.length > 0) {
788
- const pattern = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
789
- try {
790
- const regex = new RegExp(pattern);
791
- const match = line.match(regex);
792
- if (match) yield match[0];
793
- } catch {
794
- }
795
- }
796
- }
797
- }
798
- }
799
- const findLines = find.split("\n");
800
- if (findLines.length > 1) {
801
- for (let i = 0; i <= lines.length - findLines.length; i++) {
802
- const block = lines.slice(i, i + findLines.length);
803
- if (normalizeWhitespace(block.join("\n")) === normalizedFind) {
804
- yield block.join("\n");
805
- }
806
- }
807
- }
808
- };
809
- var IndentationFlexibleReplacer = function* (content, find) {
810
- const removeIndentation = (text) => {
811
- const lines = text.split("\n");
812
- const nonEmptyLines = lines.filter((line) => line.trim().length > 0);
813
- if (nonEmptyLines.length === 0) return text;
814
- const minIndent = Math.min(
815
- ...nonEmptyLines.map((line) => {
816
- const match = line.match(/^(\s*)/);
817
- return match ? match[1].length : 0;
818
- })
819
- );
820
- return lines.map((line) => line.trim().length === 0 ? line : line.slice(minIndent)).join("\n");
821
- };
822
- const normalizedFind = removeIndentation(find);
823
- const contentLines = content.split("\n");
824
- const findLines = find.split("\n");
825
- for (let i = 0; i <= contentLines.length - findLines.length; i++) {
826
- const block = contentLines.slice(i, i + findLines.length).join("\n");
827
- if (removeIndentation(block) === normalizedFind) {
828
- yield block;
829
- }
830
- }
831
- };
832
- var EscapeNormalizedReplacer = function* (content, find) {
833
- const unescapeString = (str) => {
834
- return str.replace(/\\(n|t|r|'|"|`|\\|\n|\$)/g, (match, capturedChar) => {
835
- switch (capturedChar) {
836
- case "n":
837
- return "\n";
838
- case "t":
839
- return " ";
840
- case "r":
841
- return "\r";
842
- case "'":
843
- return "'";
844
- case '"':
845
- return '"';
846
- case "`":
847
- return "`";
848
- case "\\":
849
- return "\\";
850
- case "\n":
851
- return "\n";
852
- case "$":
853
- return "$";
854
- default:
855
- return match;
856
- }
857
- });
858
- };
859
- const unescapedFind = unescapeString(find);
860
- if (content.includes(unescapedFind)) {
861
- yield unescapedFind;
862
- }
863
- const lines = content.split("\n");
864
- const findLines = unescapedFind.split("\n");
865
- for (let i = 0; i <= lines.length - findLines.length; i++) {
866
- const block = lines.slice(i, i + findLines.length).join("\n");
867
- if (unescapeString(block) === unescapedFind) {
868
- yield block;
869
- }
870
- }
871
- };
872
- var TrimmedBoundaryReplacer = function* (content, find) {
873
- const trimmedFind = find.trim();
874
- if (trimmedFind === find) return;
875
- if (content.includes(trimmedFind)) {
876
- yield trimmedFind;
877
- }
878
- const lines = content.split("\n");
879
- const findLines = find.split("\n");
880
- for (let i = 0; i <= lines.length - findLines.length; i++) {
881
- const block = lines.slice(i, i + findLines.length).join("\n");
882
- if (block.trim() === trimmedFind) {
883
- yield block;
884
- }
885
- }
886
- };
887
- var ContextAwareReplacer = function* (content, find) {
888
- const findLines = find.split("\n");
889
- if (findLines.length < 3) return;
890
- if (findLines[findLines.length - 1] === "") findLines.pop();
891
- const contentLines = content.split("\n");
892
- const firstLine = findLines[0].trim();
893
- const lastLine = findLines[findLines.length - 1].trim();
894
- for (let i = 0; i < contentLines.length; i++) {
895
- if (contentLines[i].trim() !== firstLine) continue;
896
- for (let j = i + 2; j < contentLines.length; j++) {
897
- if (contentLines[j].trim() === lastLine) {
898
- const blockLines = contentLines.slice(i, j + 1);
899
- if (blockLines.length === findLines.length) {
900
- let matchingLines = 0;
901
- let totalNonEmptyLines = 0;
902
- for (let k = 1; k < blockLines.length - 1; k++) {
903
- const blockLine = blockLines[k].trim();
904
- const findLine = findLines[k].trim();
905
- if (blockLine.length > 0 || findLine.length > 0) {
906
- totalNonEmptyLines++;
907
- if (blockLine === findLine) matchingLines++;
908
- }
909
- }
910
- if (totalNonEmptyLines === 0 || matchingLines / totalNonEmptyLines >= 0.5) {
911
- yield blockLines.join("\n");
912
- break;
913
- }
914
- }
915
- break;
916
- }
917
- }
918
- }
919
- };
920
- var MultiOccurrenceReplacer = function* (content, find) {
921
- let startIndex = 0;
922
- while (true) {
923
- const index = content.indexOf(find, startIndex);
924
- if (index === -1) break;
925
- yield find;
926
- startIndex = index + find.length;
927
- }
928
- };
929
- var REPLACERS = [
930
- SimpleReplacer,
931
- LineTrimmedReplacer,
932
- BlockAnchorReplacer,
933
- WhitespaceNormalizedReplacer,
934
- IndentationFlexibleReplacer,
935
- EscapeNormalizedReplacer,
936
- TrimmedBoundaryReplacer,
937
- ContextAwareReplacer,
938
- MultiOccurrenceReplacer
939
- ];
940
- function smartReplace(content, oldString, newString, replaceAll = false) {
941
- if (oldString === newString) {
942
- throw new Error("No changes to apply: oldString and newString are identical.");
943
- }
944
- let notFound = true;
945
- for (const replacer of REPLACERS) {
946
- for (const search of replacer(content, oldString)) {
947
- const index = content.indexOf(search);
948
- if (index === -1) continue;
949
- notFound = false;
950
- if (replaceAll) {
951
- return content.replaceAll(search, newString);
952
- }
953
- const lastIndex = content.lastIndexOf(search);
954
- if (index !== lastIndex) continue;
955
- return content.substring(0, index) + newString + content.substring(index + search.length);
956
- }
957
- }
958
- if (notFound) {
959
- throw new Error(
960
- "oldString not found in content. It must match the file contents exactly, including whitespace, indentation, and line endings."
961
- );
962
- }
963
- throw new Error(
964
- "Found multiple matches for oldString. Provide more surrounding lines in oldString to identify the correct match."
965
- );
966
- }
967
- var apply_patch = async function(input, projectCwd) {
968
- const { file_path, new_string, old_string, replaceAll: shouldReplaceAll = false } = input;
969
- try {
970
- if (!file_path) {
971
- return {
972
- success: false,
973
- message: "Missing required parameter: file_path",
974
- error: "MISSING_FILE_PATH"
975
- };
976
- }
977
- if (old_string === void 0 || old_string === null) {
978
- return {
979
- success: false,
980
- message: "Missing required parameter: old_string",
981
- error: "MISSING_OLD_STRING"
982
- };
983
- }
984
- if (new_string === void 0 || new_string === null) {
985
- return {
986
- success: false,
987
- message: "Missing required parameter: new_string",
988
- error: "MISSING_NEW_STRING"
989
- };
990
- }
991
- if (old_string === new_string) {
992
- return {
993
- success: false,
994
- message: "old_string and new_string must be different",
995
- error: "STRINGS_IDENTICAL"
996
- };
997
- }
998
- if (projectCwd) {
999
- const validation = validatePath(file_path, projectCwd);
1000
- if (!validation.valid) {
1001
- return {
1002
- success: false,
1003
- message: validation.error || "Path validation failed",
1004
- error: "ACCESS_DENIED"
1005
- };
1006
- }
1007
- }
1008
- const basePath = projectCwd || process.cwd();
1009
- const absolute_file_path = resolveProjectPath(file_path, basePath);
1010
- const file = Bun.file(absolute_file_path);
1011
- const exists = await file.exists();
1012
- if (!exists) {
1013
- if (old_string === "") {
1014
- const { mkdir: mkdir2 } = await import('fs/promises');
1015
- const path14 = await import('path');
1016
- await mkdir2(path14.dirname(absolute_file_path), { recursive: true });
1017
- await Bun.write(absolute_file_path, new_string);
1018
- const diffStats = calculateDiffStats("", new_string);
1019
- return {
1020
- success: true,
1021
- isNewFile: true,
1022
- old_string: "",
1023
- new_string,
1024
- linesAdded: diffStats.linesAdded,
1025
- linesRemoved: diffStats.linesRemoved,
1026
- message: `Created new file: ${file_path}`
1027
- };
1028
- }
1029
- return {
1030
- success: false,
1031
- message: `File not found: ${file_path}`,
1032
- error: "FILE_NOT_FOUND"
1033
- };
1034
- }
1035
- let fileContent;
1036
- try {
1037
- fileContent = await file.text();
1038
- } catch (error) {
1039
- return {
1040
- success: false,
1041
- message: `Failed to read file: ${file_path}`,
1042
- error: "READ_ERROR"
1043
- };
1044
- }
1045
- let newContent;
1046
- try {
1047
- newContent = smartReplace(fileContent, old_string, new_string, shouldReplaceAll);
1048
- } catch (err) {
1049
- if (err.message.includes("not found")) {
1050
- return {
1051
- success: false,
1052
- message: `old_string not found in file: ${file_path}. Ensure it matches the file contents exactly, including whitespace and indentation.`,
1053
- error: "STRING_NOT_FOUND"
1054
- };
1055
- }
1056
- if (err.message.includes("multiple matches")) {
1057
- const occurrences = fileContent.split(old_string).length - 1;
1058
- return {
1059
- success: false,
1060
- message: `old_string appears ${occurrences > 1 ? occurrences + " times" : "multiple times (via fuzzy match)"} in the file. Provide more surrounding context to make it unique, or set replaceAll to true.`,
1061
- error: "STRING_NOT_UNIQUE"
1062
- };
1063
- }
1064
- return {
1065
- success: false,
1066
- message: err.message,
1067
- error: "REPLACE_ERROR"
1068
- };
1069
- }
1070
- try {
1071
- await Bun.write(absolute_file_path, newContent);
1072
- const diffStats = calculateDiffStats(fileContent, newContent);
1073
- return {
1074
- success: true,
1075
- old_string,
1076
- new_string,
1077
- linesAdded: diffStats.linesAdded,
1078
- linesRemoved: diffStats.linesRemoved,
1079
- message: `Successfully replaced string in file: ${file_path}`
1080
- };
1081
- } catch (error) {
1082
- return {
1083
- success: false,
1084
- message: `Failed to write to file: ${file_path}`,
1085
- error: "WRITE_ERROR"
1086
- };
1087
- }
1088
- } catch (error) {
1089
- return {
1090
- success: false,
1091
- message: `Unexpected error: ${error.message}`,
1092
- error: "UNEXPECTED_ERROR"
1093
- };
1094
- }
1095
- };
1096
- var DEFAULT_SERVER_URL = "wss://bridge.ama.shujan.xyz";
1097
- var AMA_DIR = path10.join(os3.homedir(), ".amai");
1098
- path10.join(AMA_DIR, "code");
1099
- path10.join(AMA_DIR, "storage");
1100
- z.object({
1101
- target_file: z.string().describe("The relative path to the file to modify. The tool will create any directories in the path that don't exist"),
1102
- content: z.string().describe("The full content to write to the file"),
1103
- providedNewFile: z.boolean().describe("Whether this is a new file (true) or an edit to an existing file (false). Auto-detected if omitted.").optional()
1104
- });
1105
- var editFiles = async function(input, projectCwd) {
1106
- const { target_file, content, providedNewFile } = input;
1107
- if (!target_file) {
1108
- return {
1109
- success: false,
1110
- error: "Missing required parameter: target_file",
1111
- message: "target_file is required"
1112
- };
1113
- }
1114
- try {
1115
- if (projectCwd) {
1116
- const validation = validatePath(target_file, projectCwd);
1117
- if (!validation.valid) {
1118
- return {
1119
- success: false,
1120
- error: validation.error || "Path validation failed",
1121
- message: `Failed to edit file: ${target_file}`
1122
- };
1123
- }
1124
- }
1125
- const basePath = projectCwd || process.cwd();
1126
- const filePath = resolveProjectPath(target_file, basePath);
1127
- const dirPath = path10.dirname(filePath);
1128
- await mkdir(dirPath, { recursive: true });
1129
- let isNewFile = providedNewFile;
1130
- let existingContent = "";
1131
- const file = Bun.file(filePath);
1132
- if (isNewFile === void 0) {
1133
- const exists = await file.exists();
1134
- if (exists) {
1135
- existingContent = await file.text();
1136
- isNewFile = false;
1137
- } else {
1138
- isNewFile = true;
1139
- }
1140
- } else if (!isNewFile) {
1141
- const exists = await file.exists();
1142
- if (exists) {
1143
- existingContent = await file.text();
1144
- } else {
1145
- isNewFile = true;
1146
- }
1147
- }
1148
- if (!isNewFile && existingContent === content) {
1149
- return {
1150
- success: true,
1151
- isNewFile: false,
1152
- message: `No changes needed: ${target_file} (content identical)`,
1153
- linesAdded: 0,
1154
- linesRemoved: 0
1155
- };
1156
- }
1157
- await Bun.write(filePath, content);
1158
- const diffStats = calculateDiffStats(existingContent, content);
1159
- if (isNewFile) {
1160
- return {
1161
- success: true,
1162
- isNewFile: true,
1163
- old_string: "",
1164
- new_string: content,
1165
- message: `Created new file: ${target_file} (+${diffStats.linesAdded} lines)`,
1166
- linesAdded: diffStats.linesAdded,
1167
- linesRemoved: diffStats.linesRemoved
1168
- };
1169
- } else {
1170
- return {
1171
- success: true,
1172
- isNewFile: false,
1173
- old_string: existingContent,
1174
- new_string: content,
1175
- message: `Modified file: ${target_file} (+${diffStats.linesAdded} -${diffStats.linesRemoved} lines)`,
1176
- linesAdded: diffStats.linesAdded,
1177
- linesRemoved: diffStats.linesRemoved
1178
- };
1179
- }
1180
- } catch (error) {
1181
- return {
1182
- success: false,
1183
- error: error instanceof Error ? error.message : "Unknown error",
1184
- message: `Failed to edit file: ${target_file}`
1185
- };
1186
- }
1187
- };
1188
- z.object({
1189
- path: z.string().describe("Relative file path to delete")
1190
- });
1191
- var deleteFile = async function(input, projectCwd) {
1192
- const { path: realPath } = input;
1193
- if (!realPath) {
1194
- return {
1195
- success: false,
1196
- message: "Missing required parameter: path",
1197
- error: "MISSING_PATH"
1198
- };
1199
- }
1200
- if (projectCwd) {
1201
- const validation = validatePath(realPath, projectCwd);
1202
- if (!validation.valid) {
1203
- return {
1204
- success: false,
1205
- message: validation.error || "Path validation failed",
1206
- error: "ACCESS_DENIED"
1207
- };
1208
- }
1209
- }
1210
- try {
1211
- const basePath = projectCwd || process.cwd();
1212
- const absolute_file_path = resolveProjectPath(realPath, basePath);
1213
- if (!absolute_file_path) {
1214
- return {
1215
- success: false,
1216
- message: "Invalid file path",
1217
- error: "INVALID_FILE_PATH"
1218
- };
1219
- }
1220
- const file = Bun.file(absolute_file_path);
1221
- const exists = await file.exists();
1222
- if (!exists) {
1223
- return {
1224
- success: false,
1225
- message: `File not found: ${realPath}`,
1226
- error: "FILE_NOT_FOUND"
1227
- };
1228
- }
1229
- let originalContent;
1230
- try {
1231
- originalContent = await file.text();
1232
- } catch {
1233
- return {
1234
- success: false,
1235
- message: `Failed to read file before deletion: ${realPath}`,
1236
- error: "READ_ERROR"
1237
- };
1238
- }
1239
- try {
1240
- await unlink(absolute_file_path);
1241
- } catch {
1242
- return {
1243
- success: false,
1244
- message: `Failed to delete file: ${realPath}`,
1245
- error: "DELETE_ERROR"
1246
- };
1247
- }
1248
- return {
1249
- success: true,
1250
- message: `Successfully deleted file: ${realPath}`,
1251
- content: originalContent
1252
- };
1253
- } catch (error) {
1254
- return {
1255
- success: false,
1256
- message: `Failed to delete file: ${realPath}`,
1257
- error: "DELETE_ERROR"
1258
- };
1259
- }
1260
- };
1261
- var GREP_LIMITS = {
1262
- DEFAULT_MAX_MATCHES: 200,
1263
- MAX_LINE_LENGTH: 2e3,
1264
- // aligned with OpenCode's 2000-char truncation
1265
- MAX_TOTAL_OUTPUT_SIZE: 1 * 1024 * 1024,
1266
- EXECUTION_TIMEOUT_MS: 15e3,
1267
- TRUNCATION_MESSAGE: "\n[Results truncated due to size limits. Use more specific patterns or file filters to narrow your search.]"
1268
- };
1269
- z.object({
1270
- query: z.string().describe("The regex pattern to search for"),
1271
- options: z.object({
1272
- includePattern: z.string().optional().describe('Glob pattern for files to include (e.g., "*.ts", "*.{ts,tsx}")'),
1273
- excludePattern: z.string().optional().describe("Glob pattern for files to exclude"),
1274
- caseSensitive: z.boolean().optional().describe("Whether the search should be case sensitive"),
1275
- path: z.string().optional().describe("Subdirectory to search in"),
1276
- sortByMtime: z.boolean().optional().describe("Sort results by file modification time (default: true)")
1277
- }).optional()
1278
- });
1279
- var _cachedRgPath = null;
1280
- async function getRipgrepPath() {
1281
- if (_cachedRgPath) return _cachedRgPath;
1282
- const paths = [
1283
- "/opt/homebrew/bin/rg",
1284
- "/usr/local/bin/rg",
1285
- "/usr/bin/rg"
1286
- ];
1287
- for (const rgPath of paths) {
1288
- if (fs8.existsSync(rgPath)) {
1289
- _cachedRgPath = rgPath;
1290
- return rgPath;
1291
- }
1292
- }
1293
- _cachedRgPath = "rg";
1294
- return "rg";
1295
- }
1296
- getRipgrepPath();
1297
- async function getMtimesBatched(files) {
1298
- const mtimeMap = /* @__PURE__ */ new Map();
1299
- const BATCH_SIZE = 50;
1300
- for (let i = 0; i < files.length; i += BATCH_SIZE) {
1301
- const batch = files.slice(i, i + BATCH_SIZE);
1302
- const results = await Promise.all(
1303
- batch.map(async (filePath) => {
1304
- const mtime = await Bun.file(filePath).stat().then((stats) => stats.mtime.getTime()).catch(() => 0);
1305
- return { path: filePath, mtime };
1306
- })
1307
- );
1308
- results.forEach(({ path: path14, mtime }) => mtimeMap.set(path14, mtime));
1309
- }
1310
- return mtimeMap;
1311
- }
1312
- var grepTool = async function(input, projectCwd) {
1313
- const { query, options } = input;
1314
- if (!query || query.trim() === "") {
1315
- return {
1316
- success: false,
1317
- message: "Missing required parameter: query",
1318
- error: "MISSING_QUERY"
1319
- };
1320
- }
1321
- try {
1322
- const { includePattern, excludePattern, caseSensitive, path: subPath, sortByMtime = true } = options || {};
1323
- let searchDir = projectCwd || process.cwd();
1324
- if (subPath) {
1325
- searchDir = path10.isAbsolute(subPath) ? subPath : path10.resolve(searchDir, subPath);
1326
- if (projectCwd) {
1327
- const validation = validatePath(subPath, projectCwd);
1328
- if (!validation.valid) {
1329
- return {
1330
- success: false,
1331
- message: validation.error || "Path validation failed",
1332
- error: "ACCESS_DENIED"
1333
- };
1334
- }
1335
- }
1336
- }
1337
- if (!fs8.existsSync(searchDir)) {
1338
- return {
1339
- success: false,
1340
- message: `Directory not found: ${searchDir}`,
1341
- error: "DIR_NOT_FOUND"
1342
- };
1343
- }
1344
- const rgPath = await getRipgrepPath();
1345
- const args = [
1346
- "-nH",
1347
- // line numbers + filename (compact form, matching OpenCode)
1348
- "--hidden",
1349
- // search hidden files (aligned with OpenCode)
1350
- "--no-messages",
1351
- // suppress error messages for unreadable files
1352
- "--color=never",
1353
- "--max-count=100",
1354
- "--max-columns=2000"
1355
- ];
1356
- if (!caseSensitive) {
1357
- args.push("-i");
1358
- }
1359
- if (includePattern) {
1360
- args.push("--glob", includePattern);
1361
- }
1362
- if (excludePattern) {
1363
- args.push("--glob", `!${excludePattern}`);
1364
- }
1365
- args.push("--glob", "!node_modules/**");
1366
- args.push("--glob", "!.git/**");
1367
- args.push("--glob", "!dist/**");
1368
- args.push("--glob", "!build/**");
1369
- args.push("--glob", "!*.min.js");
1370
- args.push("--glob", "!*.min.css");
1371
- args.push("--glob", "!package-lock.json");
1372
- args.push("--glob", "!yarn.lock");
1373
- args.push("--glob", "!bun.lockb");
1374
- args.push("--glob", "!pnpm-lock.yaml");
1375
- args.push("--regexp", query);
1376
- args.push(searchDir);
1377
- const proc = Bun.spawn([rgPath, ...args], {
1378
- stdout: "pipe",
1379
- stderr: "pipe"
1380
- });
1381
- let timedOut = false;
1382
- const timeoutId = setTimeout(() => {
1383
- timedOut = true;
1384
- proc.kill();
1385
- }, GREP_LIMITS.EXECUTION_TIMEOUT_MS);
1386
- const stdout = await new Response(proc.stdout).text();
1387
- const stderr = await new Response(proc.stderr).text();
1388
- const exitCode = await proc.exited;
1389
- clearTimeout(timeoutId);
1390
- if (timedOut) {
1391
- return {
1392
- success: false,
1393
- message: `Search timed out after ${GREP_LIMITS.EXECUTION_TIMEOUT_MS}ms. Use more specific patterns.`,
1394
- error: "GREP_TIMEOUT"
1395
- };
1396
- }
1397
- if (exitCode === 1) {
1398
- return {
1399
- success: true,
1400
- matches: [],
1401
- detailedMatches: [],
1402
- query,
1403
- matchCount: 0,
1404
- message: `No matches found for pattern: ${query}`
1405
- };
1406
- }
1407
- if (exitCode !== 0 && exitCode !== 2) {
1408
- return {
1409
- success: false,
1410
- message: `Ripgrep error: ${stderr || "Unknown error"}`,
1411
- error: "GREP_EXEC_ERROR"
1412
- };
1413
- }
1414
- const lines = stdout.trim().split("\n").filter((line) => line.length > 0);
1415
- const rawMatches = [];
1416
- const uniqueFiles = /* @__PURE__ */ new Set();
1417
- for (const line of lines) {
1418
- const firstColon = line.indexOf(":");
1419
- const secondColon = line.indexOf(":", firstColon + 1);
1420
- if (firstColon > 0 && secondColon > firstColon) {
1421
- const file = line.substring(0, firstColon);
1422
- const lineNumber = parseInt(line.substring(firstColon + 1, secondColon), 10);
1423
- let content = line.substring(secondColon + 1);
1424
- if (content.length > GREP_LIMITS.MAX_LINE_LENGTH) {
1425
- content = content.substring(0, GREP_LIMITS.MAX_LINE_LENGTH) + "...";
1426
- }
1427
- rawMatches.push({
1428
- file,
1429
- lineNumber,
1430
- content: content.trim(),
1431
- mtime: 0
1432
- });
1433
- uniqueFiles.add(file);
1434
- }
1435
- }
1436
- if (sortByMtime && uniqueFiles.size > 0) {
1437
- const mtimeMap = await getMtimesBatched(Array.from(uniqueFiles));
1438
- for (const match of rawMatches) {
1439
- match.mtime = mtimeMap.get(match.file) || 0;
1440
- }
1441
- rawMatches.sort((a, b) => {
1442
- if (b.mtime !== a.mtime) return b.mtime - a.mtime;
1443
- return a.file.localeCompare(b.file);
1444
- });
1445
- }
1446
- const truncated = rawMatches.length > GREP_LIMITS.DEFAULT_MAX_MATCHES;
1447
- const finalMatches = truncated ? rawMatches.slice(0, GREP_LIMITS.DEFAULT_MAX_MATCHES) : rawMatches;
1448
- const detailedMatches = finalMatches.map((m) => ({
1449
- file: m.file,
1450
- lineNumber: m.lineNumber,
1451
- content: m.content
1452
- }));
1453
- const matches = finalMatches.map(
1454
- (m) => `${m.file}:${m.lineNumber}:${m.content}`
1455
- );
1456
- const groupedOutput = [`Found ${finalMatches.length} matches`];
1457
- let currentFile = "";
1458
- for (const match of finalMatches) {
1459
- if (currentFile !== match.file) {
1460
- if (currentFile !== "") {
1461
- groupedOutput.push("");
1462
- }
1463
- currentFile = match.file;
1464
- groupedOutput.push(`${match.file}:`);
1465
- }
1466
- groupedOutput.push(` Line ${match.lineNumber}: ${match.content}`);
1467
- }
1468
- if (truncated) {
1469
- groupedOutput.push("");
1470
- groupedOutput.push(GREP_LIMITS.TRUNCATION_MESSAGE);
1471
- }
1472
- return {
1473
- success: true,
1474
- matches,
1475
- detailedMatches,
1476
- query,
1477
- matchCount: finalMatches.length,
1478
- truncated,
1479
- message: `Found ${finalMatches.length} matches for pattern: ${query}`,
1480
- content: groupedOutput.join("\n")
1481
- };
1482
- } catch (error) {
1483
- console.error("[grep] error:", error);
1484
- return {
1485
- success: false,
1486
- message: error?.message || String(error),
1487
- error: "GREP_EXEC_ERROR"
1488
- };
1489
- }
1490
- };
1491
- z.object({
1492
- pattern: z.string().describe('Glob pattern to match files (e.g., "**/*.js", "src/**/*.ts", "*.json"). Supports standard glob syntax with *, **, and ? wildcards'),
1493
- path: z.string().optional().describe("Optional directory path to limit the search scope. If not provided, searches from the project root")
1494
- });
1495
- var RESULT_LIMIT = 100;
1496
- var MTIME_BATCH_SIZE = 50;
1497
- async function getMtimesBatched2(files) {
1498
- const results = [];
1499
- for (let i = 0; i < files.length; i += MTIME_BATCH_SIZE) {
1500
- const batch = files.slice(i, i + MTIME_BATCH_SIZE);
1501
- const batchResults = await Promise.all(
1502
- batch.map(async (filePath) => {
1503
- const mtime = await Bun.file(filePath).stat().then((stats) => stats.mtime.getTime()).catch(() => 0);
1504
- return { path: filePath, mtime };
1505
- })
1506
- );
1507
- results.push(...batchResults);
1508
- }
1509
- return results;
1510
- }
1511
- var globTool = async function(input, projectCwd) {
1512
- const { pattern, path: inputPath } = input;
1513
- if (!pattern) {
1514
- return {
1515
- success: false,
1516
- message: "Missing required parameter: pattern",
1517
- error: "MISSING_PATTERN"
1518
- };
1519
- }
1520
- try {
1521
- const basePath = projectCwd || process.cwd();
1522
- const searchPath = inputPath ? resolveProjectPath(inputPath, basePath) : basePath;
1523
- if (!fs8.existsSync(searchPath)) {
1524
- return {
1525
- success: false,
1526
- message: `Directory not found: ${searchPath}`,
1527
- error: "DIR_NOT_FOUND"
1528
- };
1529
- }
1530
- if (projectCwd && inputPath) {
1531
- const validation = validatePath(inputPath, projectCwd);
1532
- if (!validation.valid) {
1533
- return {
1534
- success: false,
1535
- message: validation.error || "Path validation failed",
1536
- error: "ACCESS_DENIED"
1537
- };
1538
- }
1539
- }
1540
- const glob = new Bun.Glob(pattern);
1541
- const files = [];
1542
- let truncated = false;
1543
- for await (const match of glob.scan({
1544
- cwd: searchPath,
1545
- absolute: true,
1546
- onlyFiles: true,
1547
- followSymlinks: false
1548
- })) {
1549
- if (match.includes("/node_modules/") || match.includes("/.git/")) {
1550
- continue;
1551
- }
1552
- if (files.length >= RESULT_LIMIT) {
1553
- truncated = true;
1554
- break;
1555
- }
1556
- files.push(match);
1557
- }
1558
- let sortedFiles;
1559
- if (files.length > 0) {
1560
- const filesWithMtime = await getMtimesBatched2(files);
1561
- filesWithMtime.sort((a, b) => b.mtime - a.mtime);
1562
- sortedFiles = filesWithMtime.map((f) => f.path);
1563
- } else {
1564
- sortedFiles = files;
1565
- }
1566
- const output = [];
1567
- if (sortedFiles.length === 0) {
1568
- output.push("No files found");
1569
- } else {
1570
- output.push(...sortedFiles);
1571
- if (truncated) {
1572
- output.push("");
1573
- output.push("(Results are truncated. Consider using a more specific path or pattern.)");
1574
- }
1575
- }
1576
- const searchLocation = inputPath ? ` in "${inputPath}"` : " in current directory";
1577
- const message = `Found ${sortedFiles.length} matches for pattern "${pattern}"${searchLocation}`;
1578
- return {
1579
- success: true,
1580
- message,
1581
- metadata: {
1582
- count: sortedFiles.length,
1583
- truncated
1584
- },
1585
- content: output.join("\n")
1586
- };
1587
- } catch (error) {
1588
- console.error("[glob] error:", error);
1589
- return {
1590
- success: false,
1591
- message: `Failed to find files matching pattern: ${pattern}`,
1592
- error: "GLOB_ERROR"
1593
- };
1594
- }
1595
- };
1596
- var DEFAULT_IGNORE_PATTERNS = [
1597
- "node_modules",
1598
- "__pycache__",
1599
- ".git",
1600
- "dist",
1601
- "build",
1602
- "target",
1603
- "vendor",
1604
- "bin",
1605
- "obj",
1606
- ".idea",
1607
- ".vscode",
1608
- ".zig-cache",
1609
- "zig-out",
1610
- ".coverage",
1611
- "coverage",
1612
- "tmp",
1613
- "temp",
1614
- ".cache",
1615
- "cache",
1616
- "logs",
1617
- ".venv",
1618
- "venv",
1619
- "env",
1620
- ".next",
1621
- ".turbo",
1622
- ".vercel",
1623
- ".output"
1624
- ];
1625
- var RESULT_LIMIT2 = 500;
1626
- var MTIME_BATCH_SIZE2 = 50;
1627
- z.object({
1628
- path: z.string().optional().describe("Path to the directory to list"),
1629
- recursive: z.boolean().optional().describe("Whether to list files recursively (default: true)"),
1630
- maxDepth: z.number().optional().describe("Maximum recursion depth (default: 3)"),
1631
- pattern: z.string().optional().describe("File extension (e.g., '.ts') or glob-like pattern"),
1632
- showHidden: z.boolean().optional().describe("Whether to show hidden files (default: false)"),
1633
- includeMetadata: z.boolean().optional().describe("Whether to fetch file metadata like mtime (default: false -- faster without I/O)"),
1634
- ignore: z.array(z.string()).optional().describe("Additional glob patterns to ignore (added to default ignore list)")
1635
- });
1636
- function shouldIgnore(name, showHidden, ignoreSet) {
1637
- if (!showHidden && name.startsWith(".") && name !== ".") {
1638
- return true;
1639
- }
1640
- return ignoreSet.has(name);
1641
- }
1642
- function matchPattern(name, pattern) {
1643
- if (!pattern) return true;
1644
- if (pattern.startsWith(".") && !pattern.includes("*")) {
1645
- return name.endsWith(pattern);
1646
- }
1647
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1648
- const regex = new RegExp(`^${escaped}$`, "i");
1649
- return regex.test(name);
1650
- }
1651
- async function getMtimesBatched3(entries) {
1652
- for (let i = 0; i < entries.length; i += MTIME_BATCH_SIZE2) {
1653
- const batch = entries.slice(i, i + MTIME_BATCH_SIZE2);
1654
- await Promise.all(
1655
- batch.map(async (entry) => {
1656
- entry.mtime = await Bun.file(entry.absolutePath).stat().then((stats) => stats.mtime.getTime()).catch(() => 0);
1657
- })
1658
- );
1659
- }
1660
- }
1661
- function buildTreeOutput(entries, basePath) {
1662
- const tree = /* @__PURE__ */ new Map();
1663
- for (const entry of entries) {
1664
- const dir = path10.dirname(entry.relativePath);
1665
- const dirKey = dir === "." ? "" : dir;
1666
- if (!tree.has(dirKey)) {
1667
- tree.set(dirKey, []);
1668
- }
1669
- tree.get(dirKey).push(entry);
1670
- }
1671
- for (const [, items] of tree) {
1672
- items.sort((a, b) => {
1673
- if (a.type !== b.type) {
1674
- return a.type === "directory" ? -1 : 1;
1675
- }
1676
- return a.name.localeCompare(b.name);
1677
- });
1678
- }
1679
- const lines = [`${basePath}/`];
1680
- function renderLevel(dirPath, indent) {
1681
- const items = tree.get(dirPath) || [];
1682
- for (let i = 0; i < items.length; i++) {
1683
- const item = items[i];
1684
- const isLast = i === items.length - 1;
1685
- const prefix = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
1686
- const childIndent = indent + (isLast ? " " : "\u2502 ");
1687
- if (item.type === "directory") {
1688
- lines.push(`${indent}${prefix}${item.name}/`);
1689
- const childPath = dirPath ? `${dirPath}/${item.name}` : item.name;
1690
- renderLevel(childPath, childIndent);
1691
- } else {
1692
- lines.push(`${indent}${prefix}${item.name}`);
1693
- }
1694
- }
1695
- }
1696
- renderLevel("", "");
1697
- return lines.join("\n");
1698
- }
1699
- var list = async function(input, projectCwd) {
1700
- const {
1701
- path: relativePath,
1702
- recursive = true,
1703
- maxDepth = 3,
1704
- pattern,
1705
- showHidden = false,
1706
- includeMetadata = false,
1707
- ignore: extraIgnore
1708
- } = input;
1709
- if (maxDepth !== void 0 && (!Number.isInteger(maxDepth) || maxDepth < 0)) {
1710
- return {
1711
- success: false,
1712
- message: "maxDepth must be a non-negative integer",
1713
- error: "INVALID_MAX_DEPTH"
1714
- };
1715
- }
1716
- try {
1717
- const basePath = projectCwd || process.cwd();
1718
- const absolutePath = relativePath ? resolveProjectPath(relativePath, basePath) : basePath;
1719
- if (projectCwd && relativePath) {
1720
- const validation = validatePath(relativePath, projectCwd);
1721
- if (!validation.valid) {
1722
- return {
1723
- success: false,
1724
- message: validation.error || "Path validation failed",
1725
- error: "ACCESS_DENIED"
1726
- };
1727
- }
1728
- }
1729
- if (!fs8.existsSync(absolutePath)) {
1730
- return {
1731
- success: false,
1732
- message: `Directory not found: ${absolutePath}`,
1733
- error: "DIR_NOT_FOUND"
1734
- };
1735
- }
1736
- const stats = fs8.statSync(absolutePath);
1737
- if (!stats.isDirectory()) {
1738
- return {
1739
- success: false,
1740
- message: `Path is not a directory: ${absolutePath}`,
1741
- error: "NOT_A_DIRECTORY"
1742
- };
1743
- }
1744
- const ignoreSet = new Set(DEFAULT_IGNORE_PATTERNS);
1745
- if (extraIgnore && extraIgnore.length > 0) {
1746
- for (const pat of extraIgnore) {
1747
- ignoreSet.add(pat);
1748
- }
1749
- }
1750
- const collected = [];
1751
- let truncated = false;
1752
- const walk = async (currentDir, depth) => {
1753
- if (collected.length >= RESULT_LIMIT2) {
1754
- truncated = true;
1755
- return;
1756
- }
1757
- let entries;
1758
- try {
1759
- entries = fs8.readdirSync(currentDir, { withFileTypes: true });
1760
- } catch {
1761
- return;
1762
- }
1763
- entries.sort((a, b) => {
1764
- if (a.isDirectory() !== b.isDirectory()) {
1765
- return a.isDirectory() ? -1 : 1;
1766
- }
1767
- return a.name.localeCompare(b.name);
1768
- });
1769
- for (const entry of entries) {
1770
- if (collected.length >= RESULT_LIMIT2) {
1771
- truncated = true;
1772
- break;
1773
- }
1774
- if (shouldIgnore(entry.name, showHidden, ignoreSet)) {
1775
- continue;
1776
- }
1777
- const entryAbsolutePath = path10.join(currentDir, entry.name);
1778
- const entryRelativePath = path10.relative(absolutePath, entryAbsolutePath);
1779
- if (entry.isDirectory()) {
1780
- collected.push({
1781
- name: entry.name,
1782
- absolutePath: entryAbsolutePath,
1783
- relativePath: entryRelativePath,
1784
- type: "directory",
1785
- mtime: 0,
1786
- depth
1787
- });
1788
- if (recursive && depth < maxDepth) {
1789
- await walk(entryAbsolutePath, depth + 1);
1790
- }
1791
- } else if (entry.isFile()) {
1792
- if (matchPattern(entry.name, pattern)) {
1793
- collected.push({
1794
- name: entry.name,
1795
- absolutePath: entryAbsolutePath,
1796
- relativePath: entryRelativePath,
1797
- type: "file",
1798
- mtime: 0,
1799
- depth
1800
- });
1801
- }
1802
- }
1803
- }
1804
- };
1805
- await walk(absolutePath, 0);
1806
- if (includeMetadata) {
1807
- await getMtimesBatched3(collected);
1808
- }
1809
- const totalFiles = collected.filter((item) => item.type === "file").length;
1810
- const totalDirectories = collected.filter((item) => item.type === "directory").length;
1811
- const treeOutput = buildTreeOutput(collected, relativePath || path10.basename(absolutePath));
1812
- let message = `Listed ${collected.length} items`;
1813
- if (relativePath) {
1814
- message += ` in "${relativePath}"`;
1815
- }
1816
- message += ` (${totalFiles} files, ${totalDirectories} directories)`;
1817
- if (recursive) {
1818
- message += ` [depth: ${maxDepth}]`;
1819
- }
1820
- if (pattern) {
1821
- message += ` [filter: ${pattern}]`;
1822
- }
1823
- if (truncated) {
1824
- message += ` [TRUNCATED at ${RESULT_LIMIT2} items]`;
1825
- }
1826
- const files = collected.map((item) => ({
1827
- name: item.name,
1828
- path: item.relativePath,
1829
- type: item.type
1830
- }));
1831
- return {
1832
- success: true,
1833
- message,
1834
- metadata: {
1835
- totalFiles,
1836
- totalDirectories,
1837
- totalItems: collected.length,
1838
- truncated,
1839
- maxDepth,
1840
- recursive
1841
- },
1842
- files,
1843
- content: treeOutput
1844
- };
1845
- } catch (error) {
1846
- console.error("[list] error:", error);
1847
- return {
1848
- success: false,
1849
- message: `Failed to list directory: ${error}`,
1850
- error: "LIST_ERROR"
1851
- };
1852
- }
1853
- };
3
+ import path6 from 'path';
4
+ import os3 from 'os';
5
+ import pc3 from 'picocolors';
6
+ import { Hono } from 'hono';
7
+ import { serve } from '@hono/node-server';
8
+ import { cors } from 'hono/cors';
9
+ import fs4, { readdirSync } from 'fs';
10
+ import { exec, spawn } from 'child_process';
11
+ import { promisify } from 'util';
12
+ import fs5 from 'fs/promises';
13
+ import { z } from 'zod';
14
+ import { createServer } from 'http';
15
+ import { batchTool, bashTool, apply_patch, read_file, list, globTool, grepTool, deleteFile, editFiles } from '@ama/agent';
16
+
17
+ var DEFAULT_SERVER_URL = "wss://bridge.ama.shujan.xyz";
18
+ var AMA_DIR = path6.join(os3.homedir(), ".amai");
19
+ path6.join(AMA_DIR, "code");
20
+ path6.join(AMA_DIR, "storage");
1854
21
  var startHttpServer = () => {
1855
22
  const app = new Hono();
1856
23
  app.use(cors());
@@ -1866,22 +33,22 @@ var startHttpServer = () => {
1866
33
  }
1867
34
  });
1868
35
  };
1869
- var CREDENTIALS_DIR = path10.join(os3.homedir(), ".amai");
1870
- var CREDENTIALS_PATH = path10.join(CREDENTIALS_DIR, "credentials.json");
36
+ var CREDENTIALS_DIR = path6.join(os3.homedir(), ".amai");
37
+ var CREDENTIALS_PATH = path6.join(CREDENTIALS_DIR, "credentials.json");
1871
38
  function getTokens() {
1872
- if (!fs8.existsSync(CREDENTIALS_PATH)) {
39
+ if (!fs4.existsSync(CREDENTIALS_PATH)) {
1873
40
  return null;
1874
41
  }
1875
- const raw = fs8.readFileSync(CREDENTIALS_PATH, "utf8");
42
+ const raw = fs4.readFileSync(CREDENTIALS_PATH, "utf8");
1876
43
  const data = JSON.parse(raw);
1877
44
  return data;
1878
45
  }
1879
46
  var getUserId = () => {
1880
47
  try {
1881
- if (!fs8.existsSync(CREDENTIALS_PATH)) {
48
+ if (!fs4.existsSync(CREDENTIALS_PATH)) {
1882
49
  return;
1883
50
  }
1884
- const raw = fs8.readFileSync(CREDENTIALS_PATH, "utf8");
51
+ const raw = fs4.readFileSync(CREDENTIALS_PATH, "utf8");
1885
52
  const data = JSON.parse(raw);
1886
53
  const fromUserObject = data.user?.id;
1887
54
  const fromTopLevel = data.sub ?? data.user_id;
@@ -1906,168 +73,54 @@ var getUserId = () => {
1906
73
  throw new Error("Error while getting userId");
1907
74
  }
1908
75
  };
1909
- var ExplanationSchema = z.object({
1910
- description: z.string().describe(
1911
- "Clear, concise description of what this command does in 5-10 words. Examples:\nInput: ls\nOutput: Lists files in current directory\n\nInput: git status\nOutput: Shows working tree status\n\nInput: npm install\nOutput: Installs package dependencies\n\nInput: mkdir foo\nOutput: Creates directory 'foo'"
1912
- )
1913
- });
1914
- var BLOCKED_PATTERNS = [
1915
- /\brm\s+(-\w+\s+)*(\/ |\/\s*$|~|\/\*|\*)/,
1916
- /\bdd\s+.*of=\/dev\//,
1917
- /\bmkfs\b/,
1918
- /:\(\)\{.*\|.*&\}\s*;?\s*:/,
1919
- /\bchmod\s+.*-R.*\s+\/\s*$/,
1920
- /\bchown\s+.*-R.*\s+\/\s*$/,
1921
- /\b(curl|wget)\s+.*\|\s*(ba)?sh/,
1922
- /\bmv\s+(\/|\*)\s/,
1923
- /\bcat\s+\/dev\/(u?random|zero)\s*>\s*\/dev\//,
1924
- /\bformat\s+[A-Z]:/i,
1925
- /\bdiskpart\b/i,
1926
- /\bcipher\s+\/w:/i
1927
- ];
1928
- var DANGEROUS_FLAGS = [
1929
- /--no-preserve-root/,
1930
- /\bgit\s+push\s+.*--force\b/,
1931
- /\bgit\s+push\s+-f\b/
1932
- ];
1933
- var MAX_OUTPUT_SIZE = 1 * 1024 * 1024;
1934
- var DEFAULT_TIMEOUT = 12e4;
1935
- function evaluateCommandSafety(command) {
1936
- const trimmed = command.trim();
1937
- for (const pattern of BLOCKED_PATTERNS) {
1938
- if (pattern.test(trimmed)) {
1939
- return { safe: false, reason: `Blocked by safety policy: matches destructive pattern` };
1940
- }
1941
- }
1942
- for (const flag of DANGEROUS_FLAGS) {
1943
- if (flag.test(trimmed)) {
1944
- return { safe: false, reason: `Blocked by safety policy: dangerous flag detected` };
1945
- }
1946
- }
1947
- return { safe: true };
76
+ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
77
+ "editFile",
78
+ "deleteFile",
79
+ "stringReplace",
80
+ "bash"
81
+ ]);
82
+ function isMutatingTool(toolName) {
83
+ return MUTATING_TOOLS.has(toolName);
1948
84
  }
1949
- z.object({
1950
- command: z.string().describe("The terminal command to execute"),
1951
- is_background: z.boolean().optional().default(false).describe("Whether the command should be run in the background"),
1952
- timeout: z.number().optional().describe("Optional timeout in milliseconds. If not specified, commands will time out after 120000ms (2 minutes)."),
1953
- workdir: z.string().optional().describe("The working directory to run the command in. Defaults to the project directory. Use this instead of 'cd' commands.")
1954
- }).merge(ExplanationSchema);
1955
- var runSecureTerminalCommand = async (command, timeout, cwd) => {
85
+ function isPathWithinProject(filePath, projectCwd) {
1956
86
  try {
1957
- const safety = evaluateCommandSafety(command);
1958
- if (!safety.safe) {
1959
- console.log(`[CLI] Blocked command: ${command} \u2014 ${safety.reason}`);
1960
- return {
1961
- success: false,
1962
- message: safety.reason,
1963
- error: "BLOCKED_COMMAND"
1964
- };
1965
- }
1966
- const proc = Bun.spawn(["sh", "-c", command], {
1967
- cwd: cwd || process.cwd(),
1968
- stdout: "pipe",
1969
- stderr: "pipe"
1970
- });
1971
- let timedOut = false;
1972
- let timeoutId = null;
1973
- if (timeout > 0) {
1974
- timeoutId = setTimeout(() => {
1975
- timedOut = true;
1976
- proc.kill();
1977
- }, timeout);
1978
- }
1979
- const [stdout, stderr, exitCode] = await Promise.all([
1980
- new Response(proc.stdout).text(),
1981
- new Response(proc.stderr).text(),
1982
- proc.exited
1983
- ]);
1984
- if (timeoutId) {
1985
- clearTimeout(timeoutId);
1986
- }
1987
- if (timedOut) {
1988
- return {
1989
- success: false,
1990
- message: `Command timed out after ${timeout}ms`,
1991
- error: "TIMEOUT",
1992
- stdout: stdout.slice(0, MAX_OUTPUT_SIZE),
1993
- stderr: stderr.slice(0, MAX_OUTPUT_SIZE)
1994
- };
87
+ const resolvedCwd = safeRealpath(projectCwd);
88
+ const resolved = path6.resolve(resolvedCwd, filePath);
89
+ const resolvedTarget = safeRealpath(resolved);
90
+ const rel = path6.relative(resolvedCwd, resolvedTarget);
91
+ if (rel.startsWith("..") || path6.isAbsolute(rel)) {
92
+ return false;
1995
93
  }
1996
- return {
1997
- stdout: stdout.slice(0, MAX_OUTPUT_SIZE),
1998
- stderr: stderr.slice(0, MAX_OUTPUT_SIZE),
1999
- exitCode
2000
- };
2001
- } catch (error) {
2002
- console.error("Error while executing the securedShell command", error);
2003
- return {
2004
- success: false,
2005
- message: "Error while executing the securedShell command",
2006
- error: error.message
2007
- };
94
+ return true;
95
+ } catch {
96
+ return false;
2008
97
  }
2009
- };
2010
- var bashTool = async (input, projectCwd) => {
98
+ }
99
+ function safeRealpath(p) {
2011
100
  try {
2012
- const safety = evaluateCommandSafety(input.command);
2013
- if (!safety.safe) {
2014
- console.log(`[CLI] Blocked command: ${input.command} \u2014 ${safety.reason}`);
2015
- return {
2016
- success: false,
2017
- message: safety.reason,
2018
- error: "BLOCKED_COMMAND"
2019
- };
2020
- }
2021
- if (input.timeout !== void 0 && input.timeout < 0) {
2022
- return {
2023
- success: false,
2024
- message: `Invalid timeout value: ${input.timeout}. Timeout must be a positive number.`,
2025
- error: "INVALID_TIMEOUT"
2026
- };
2027
- }
2028
- const cwd = input.workdir || projectCwd || process.cwd();
2029
- const timeout = input.timeout ?? DEFAULT_TIMEOUT;
2030
- if (input?.is_background) {
2031
- const proc = Bun.spawn(["sh", "-c", input.command], {
2032
- cwd,
2033
- stdout: "ignore",
2034
- stderr: "ignore"
2035
- });
2036
- proc.unref();
2037
- console.log(`[LOCAL] Background command started: ${input.command}`);
2038
- return {
2039
- success: true,
2040
- message: `Background command started: ${input.command}`,
2041
- isBackground: true
2042
- };
2043
- } else {
2044
- const result = await runSecureTerminalCommand(
2045
- input.command,
2046
- timeout,
2047
- cwd
2048
- );
2049
- if (result?.error && !result?.exitCode) {
2050
- return result;
2051
- }
2052
- const success = result?.exitCode === 0;
2053
- return {
2054
- success,
2055
- stdout: result?.stdout?.trim(),
2056
- stderr: result?.stderr?.trim(),
2057
- exitCode: result?.exitCode,
2058
- message: success ? `Command executed successfully: ${input.command}` : `Command failed with exit code ${result?.exitCode}: ${input.command}`
2059
- };
101
+ return fs4.realpathSync(p);
102
+ } catch {
103
+ const parent = path6.dirname(p);
104
+ try {
105
+ const realParent = fs4.realpathSync(parent);
106
+ return path6.join(realParent, path6.basename(p));
107
+ } catch {
108
+ return path6.resolve(p);
2060
109
  }
2061
- } catch (error) {
2062
- console.error("Error while executing the terminal command", error);
110
+ }
111
+ }
112
+ function requireProjectCwd(toolName, projectCwd) {
113
+ if (!projectCwd && isMutatingTool(toolName)) {
2063
114
  return {
2064
- success: false,
2065
- message: "Error while executing the terminal command",
2066
- error: error.message
115
+ allowed: false,
116
+ error: `ACCESS_DENIED: Tool "${toolName}" requires a project context (projectCwd) but none was provided`
2067
117
  };
2068
118
  }
2069
- };
2070
- var REGISTRY_FILE = path10.join(AMA_DIR, "projects.json");
119
+ return { allowed: true };
120
+ }
121
+
122
+ // src/lib/project-registry.ts
123
+ var REGISTRY_FILE = path6.join(AMA_DIR, "projects.json");
2071
124
  var ProjectRegistry = class {
2072
125
  projects = /* @__PURE__ */ new Map();
2073
126
  constructor() {
@@ -2075,14 +128,14 @@ var ProjectRegistry = class {
2075
128
  }
2076
129
  load() {
2077
130
  try {
2078
- if (fs8.existsSync(REGISTRY_FILE)) {
2079
- const data = fs8.readFileSync(REGISTRY_FILE, "utf8");
131
+ if (fs4.existsSync(REGISTRY_FILE)) {
132
+ const data = fs4.readFileSync(REGISTRY_FILE, "utf8");
2080
133
  const parsed = JSON.parse(data);
2081
134
  if (!Array.isArray(parsed)) {
2082
135
  console.error("Invalid project registry format: expected array, got", typeof parsed);
2083
136
  const backupFile = REGISTRY_FILE + ".backup." + Date.now();
2084
- fs8.copyFileSync(REGISTRY_FILE, backupFile);
2085
- fs8.unlinkSync(REGISTRY_FILE);
137
+ fs4.copyFileSync(REGISTRY_FILE, backupFile);
138
+ fs4.unlinkSync(REGISTRY_FILE);
2086
139
  return;
2087
140
  }
2088
141
  const projects = parsed;
@@ -2095,11 +148,11 @@ var ProjectRegistry = class {
2095
148
  }
2096
149
  } catch (error) {
2097
150
  console.error("Failed to load project registry:", error);
2098
- if (fs8.existsSync(REGISTRY_FILE)) {
151
+ if (fs4.existsSync(REGISTRY_FILE)) {
2099
152
  try {
2100
153
  const backupFile = REGISTRY_FILE + ".backup." + Date.now();
2101
- fs8.copyFileSync(REGISTRY_FILE, backupFile);
2102
- fs8.unlinkSync(REGISTRY_FILE);
154
+ fs4.copyFileSync(REGISTRY_FILE, backupFile);
155
+ fs4.unlinkSync(REGISTRY_FILE);
2103
156
  console.log("Corrupted registry file backed up and removed. Starting fresh.");
2104
157
  } catch (backupError) {
2105
158
  }
@@ -2108,21 +161,21 @@ var ProjectRegistry = class {
2108
161
  }
2109
162
  save() {
2110
163
  try {
2111
- if (!fs8.existsSync(AMA_DIR)) {
2112
- fs8.mkdirSync(AMA_DIR, { recursive: true });
164
+ if (!fs4.existsSync(AMA_DIR)) {
165
+ fs4.mkdirSync(AMA_DIR, { recursive: true });
2113
166
  }
2114
167
  const projects = Array.from(this.projects.values());
2115
- fs8.writeFileSync(REGISTRY_FILE, JSON.stringify(projects, null, 2), "utf8");
168
+ fs4.writeFileSync(REGISTRY_FILE, JSON.stringify(projects, null, 2), "utf8");
2116
169
  } catch (error) {
2117
170
  console.error("Failed to save project registry:", error);
2118
171
  }
2119
172
  }
2120
173
  register(projectId, cwd, name) {
2121
- const normalizedCwd = path10.normalize(path10.resolve(cwd));
174
+ const normalizedCwd = path6.normalize(path6.resolve(cwd));
2122
175
  this.projects.set(projectId, {
2123
176
  id: projectId,
2124
177
  cwd: normalizedCwd,
2125
- name: name || path10.basename(normalizedCwd),
178
+ name: name || path6.basename(normalizedCwd),
2126
179
  active: true
2127
180
  });
2128
181
  this.save();
@@ -2155,33 +208,33 @@ var getContext = (dir, base = dir, allFiles = []) => {
2155
208
  const filePath = readdirSync(dir, { withFileTypes: true });
2156
209
  for (const file of filePath) {
2157
210
  if (ignoreFiles.includes(file.name)) continue;
2158
- const fullPath = path10.join(dir, file.name);
211
+ const fullPath = path6.join(dir, file.name);
2159
212
  if (file.isDirectory()) {
2160
213
  getContext(fullPath, base, allFiles);
2161
214
  } else {
2162
- allFiles.push(path10.relative(base, fullPath));
215
+ allFiles.push(path6.relative(base, fullPath));
2163
216
  }
2164
217
  }
2165
218
  return allFiles;
2166
219
  };
2167
220
  var HOME = os3.homedir();
2168
221
  var IDE_PROJECTS_PATHS = {
2169
- vscode: path10.join(HOME, ".vscode", "projects"),
2170
- cursor: path10.join(HOME, ".cursor", "projects"),
2171
- claude: path10.join(HOME, ".claude", "projects")
222
+ vscode: path6.join(HOME, ".vscode", "projects"),
223
+ cursor: path6.join(HOME, ".cursor", "projects"),
224
+ claude: path6.join(HOME, ".claude", "projects")
2172
225
  };
2173
226
  function getWorkspaceStoragePath(ide) {
2174
227
  const platform = os3.platform();
2175
228
  const appName = ide === "cursor" ? "Cursor" : "Code";
2176
229
  const appNameLower = appName.toLowerCase();
2177
230
  if (platform === "darwin") {
2178
- return path10.join(HOME, "Library", "Application Support", appName, "User", "workspaceStorage");
231
+ return path6.join(HOME, "Library", "Application Support", appName, "User", "workspaceStorage");
2179
232
  } else if (platform === "win32") {
2180
- return path10.join(process.env.APPDATA || "", appName, "User", "workspaceStorage");
233
+ return path6.join(process.env.APPDATA || "", appName, "User", "workspaceStorage");
2181
234
  } else {
2182
- const capitalizedPath = path10.join(HOME, ".config", appName, "User", "workspaceStorage");
2183
- const lowercasePath = path10.join(HOME, ".config", appNameLower, "User", "workspaceStorage");
2184
- if (fs8.existsSync(capitalizedPath)) {
235
+ const capitalizedPath = path6.join(HOME, ".config", appName, "User", "workspaceStorage");
236
+ const lowercasePath = path6.join(HOME, ".config", appNameLower, "User", "workspaceStorage");
237
+ if (fs4.existsSync(capitalizedPath)) {
2185
238
  return capitalizedPath;
2186
239
  }
2187
240
  return lowercasePath;
@@ -2190,16 +243,16 @@ function getWorkspaceStoragePath(ide) {
2190
243
  function scanWorkspaceStorage(ide) {
2191
244
  const projects = [];
2192
245
  const storagePath = getWorkspaceStoragePath(ide);
2193
- if (!fs8.existsSync(storagePath)) {
246
+ if (!fs4.existsSync(storagePath)) {
2194
247
  return projects;
2195
248
  }
2196
249
  try {
2197
- const workspaces = fs8.readdirSync(storagePath);
250
+ const workspaces = fs4.readdirSync(storagePath);
2198
251
  for (const workspace of workspaces) {
2199
- const workspaceJsonPath = path10.join(storagePath, workspace, "workspace.json");
2200
- if (fs8.existsSync(workspaceJsonPath)) {
252
+ const workspaceJsonPath = path6.join(storagePath, workspace, "workspace.json");
253
+ if (fs4.existsSync(workspaceJsonPath)) {
2201
254
  try {
2202
- const content = fs8.readFileSync(workspaceJsonPath, "utf-8");
255
+ const content = fs4.readFileSync(workspaceJsonPath, "utf-8");
2203
256
  const data = JSON.parse(content);
2204
257
  if (data.folder && typeof data.folder === "string") {
2205
258
  let projectPath = data.folder;
@@ -2207,9 +260,9 @@ function scanWorkspaceStorage(ide) {
2207
260
  projectPath = projectPath.replace("file://", "");
2208
261
  projectPath = decodeURIComponent(projectPath);
2209
262
  }
2210
- if (fs8.existsSync(projectPath) && fs8.statSync(projectPath).isDirectory()) {
263
+ if (fs4.existsSync(projectPath) && fs4.statSync(projectPath).isDirectory()) {
2211
264
  projects.push({
2212
- name: path10.basename(projectPath),
265
+ name: path6.basename(projectPath),
2213
266
  path: projectPath,
2214
267
  type: ide
2215
268
  });
@@ -2231,11 +284,11 @@ var scanIdeProjects = async () => {
2231
284
  const seenPaths = /* @__PURE__ */ new Set();
2232
285
  const addProject = (projectPath, ide) => {
2233
286
  try {
2234
- const resolvedPath = fs8.realpathSync(projectPath);
2235
- if (fs8.existsSync(resolvedPath) && fs8.statSync(resolvedPath).isDirectory() && !seenPaths.has(resolvedPath)) {
287
+ const resolvedPath = fs4.realpathSync(projectPath);
288
+ if (fs4.existsSync(resolvedPath) && fs4.statSync(resolvedPath).isDirectory() && !seenPaths.has(resolvedPath)) {
2236
289
  const isIdeProjectsDir = Object.values(IDE_PROJECTS_PATHS).some((ideDir) => {
2237
290
  try {
2238
- return fs8.realpathSync(ideDir) === resolvedPath;
291
+ return fs4.realpathSync(ideDir) === resolvedPath;
2239
292
  } catch {
2240
293
  return false;
2241
294
  }
@@ -2243,7 +296,7 @@ var scanIdeProjects = async () => {
2243
296
  if (!isIdeProjectsDir) {
2244
297
  seenPaths.add(resolvedPath);
2245
298
  allProjects.push({
2246
- name: path10.basename(resolvedPath),
299
+ name: path6.basename(resolvedPath),
2247
300
  path: resolvedPath,
2248
301
  type: ide
2249
302
  });
@@ -2262,30 +315,30 @@ var scanIdeProjects = async () => {
2262
315
  }
2263
316
  for (const [ide, dirPath] of Object.entries(IDE_PROJECTS_PATHS)) {
2264
317
  if (ide === "cursor" || ide === "vscode") continue;
2265
- if (fs8.existsSync(dirPath)) {
2266
- const projects = fs8.readdirSync(dirPath);
318
+ if (fs4.existsSync(dirPath)) {
319
+ const projects = fs4.readdirSync(dirPath);
2267
320
  projects.forEach((project) => {
2268
- const projectPath = path10.join(dirPath, project);
321
+ const projectPath = path6.join(dirPath, project);
2269
322
  try {
2270
- const stats = fs8.lstatSync(projectPath);
323
+ const stats = fs4.lstatSync(projectPath);
2271
324
  let actualPath = null;
2272
325
  if (stats.isSymbolicLink()) {
2273
- actualPath = fs8.realpathSync(projectPath);
326
+ actualPath = fs4.realpathSync(projectPath);
2274
327
  } else if (stats.isFile()) {
2275
328
  try {
2276
- let content = fs8.readFileSync(projectPath, "utf-8").trim();
329
+ let content = fs4.readFileSync(projectPath, "utf-8").trim();
2277
330
  if (content.startsWith("~/") || content === "~") {
2278
331
  content = content.replace(/^~/, HOME);
2279
332
  }
2280
- const resolvedContent = path10.isAbsolute(content) ? content : path10.resolve(path10.dirname(projectPath), content);
2281
- if (fs8.existsSync(resolvedContent) && fs8.statSync(resolvedContent).isDirectory()) {
2282
- actualPath = fs8.realpathSync(resolvedContent);
333
+ const resolvedContent = path6.isAbsolute(content) ? content : path6.resolve(path6.dirname(projectPath), content);
334
+ if (fs4.existsSync(resolvedContent) && fs4.statSync(resolvedContent).isDirectory()) {
335
+ actualPath = fs4.realpathSync(resolvedContent);
2283
336
  }
2284
337
  } catch {
2285
338
  return;
2286
339
  }
2287
340
  } else if (stats.isDirectory()) {
2288
- actualPath = fs8.realpathSync(projectPath);
341
+ actualPath = fs4.realpathSync(projectPath);
2289
342
  }
2290
343
  if (actualPath) {
2291
344
  addProject(actualPath, ide);
@@ -2304,7 +357,7 @@ var scanIdeProjects = async () => {
2304
357
  var Global;
2305
358
  ((Global2) => {
2306
359
  ((Path2) => {
2307
- Path2.data = path10.join(AMA_DIR, "data");
360
+ Path2.data = path6.join(AMA_DIR, "data");
2308
361
  })(Global2.Path || (Global2.Path = {}));
2309
362
  })(Global || (Global = {}));
2310
363
 
@@ -2343,8 +396,8 @@ var Snapshot;
2343
396
  const worktree = project.cwd;
2344
397
  const git = gitdir(projectId);
2345
398
  try {
2346
- await fsp__default.mkdir(git, { recursive: true });
2347
- const gitExists = await fsp__default.access(path10.join(git, "HEAD")).then(() => true).catch(() => false);
399
+ await fs5.mkdir(git, { recursive: true });
400
+ const gitExists = await fs5.access(path6.join(git, "HEAD")).then(() => true).catch(() => false);
2348
401
  if (!gitExists) {
2349
402
  await runGit(`git init`, {
2350
403
  env: { GIT_DIR: git, GIT_WORK_TREE: worktree }
@@ -2385,7 +438,7 @@ var Snapshot;
2385
438
  const files = result.stdout;
2386
439
  return {
2387
440
  hash,
2388
- files: files.trim().split("\n").map((x) => x.trim()).filter(Boolean).map((x) => path10.join(worktree, x))
441
+ files: files.trim().split("\n").map((x) => x.trim()).filter(Boolean).map((x) => path6.join(worktree, x))
2389
442
  };
2390
443
  }
2391
444
  Snapshot2.patch = patch;
@@ -2427,9 +480,9 @@ var Snapshot;
2427
480
  if (diffResult.exitCode === 0 && diffResult.stdout.trim()) {
2428
481
  const newFiles = diffResult.stdout.trim().split("\n").filter(Boolean);
2429
482
  for (const file of newFiles) {
2430
- const fullPath = path10.join(worktree, file);
483
+ const fullPath = path6.join(worktree, file);
2431
484
  try {
2432
- await fsp__default.unlink(fullPath);
485
+ await fs5.unlink(fullPath);
2433
486
  log.info("deleted newly created file", { file: fullPath });
2434
487
  } catch {
2435
488
  }
@@ -2457,7 +510,7 @@ var Snapshot;
2457
510
  { cwd: worktree }
2458
511
  );
2459
512
  if (result.exitCode !== 0) {
2460
- const relativePath = path10.relative(worktree, file);
513
+ const relativePath = path6.relative(worktree, file);
2461
514
  const checkTree = await runGit(
2462
515
  `git --git-dir "${git}" --work-tree "${worktree}" ls-tree ${item.hash} -- "${relativePath}"`,
2463
516
  { cwd: worktree }
@@ -2466,7 +519,7 @@ var Snapshot;
2466
519
  log.info("file existed in snapshot but checkout failed, keeping", { file });
2467
520
  } else {
2468
521
  log.info("file did not exist in snapshot, deleting", { file });
2469
- await fsp__default.unlink(file).catch(() => {
522
+ await fs5.unlink(file).catch(() => {
2470
523
  });
2471
524
  }
2472
525
  }
@@ -2520,10 +573,10 @@ var Snapshot;
2520
573
  const lines = numstatResult.stdout.trim().split("\n").filter(Boolean);
2521
574
  for (const line of lines) {
2522
575
  const [additions, deletions, file] = line.split(" ");
2523
- const isBinaryFile2 = additions === "-" && deletions === "-";
576
+ const isBinaryFile = additions === "-" && deletions === "-";
2524
577
  let before = "";
2525
578
  let after = "";
2526
- if (!isBinaryFile2) {
579
+ if (!isBinaryFile) {
2527
580
  const beforeResult = await runGit(
2528
581
  `git -c core.autocrlf=false --git-dir "${git}" --work-tree "${worktree}" show ${from}:${file}`,
2529
582
  { cwd: worktree }
@@ -2547,13 +600,13 @@ var Snapshot;
2547
600
  }
2548
601
  Snapshot2.diffFull = diffFull;
2549
602
  function gitdir(projectId) {
2550
- return path10.join(Global.Path.data, "snapshot", projectId);
603
+ return path6.join(Global.Path.data, "snapshot", projectId);
2551
604
  }
2552
605
  })(Snapshot || (Snapshot = {}));
2553
606
  var CLIENT_ID2 = "app_EMoamEEZ73f0CkXaXp7hrann";
2554
607
  var ISSUER = "https://auth.openai.com";
2555
608
  var OAUTH_PORT = 1455;
2556
- var CREDENTIALS_PATH2 = path10.join(AMA_DIR, "codex-credentials.json");
609
+ var CREDENTIALS_PATH2 = path6.join(AMA_DIR, "codex-credentials.json");
2557
610
  var CALLBACK_PATH = "/auth/callback";
2558
611
  var OAUTH_TIMEOUT_MS = 5 * 60 * 1e3;
2559
612
  var REFRESH_BUFFER_MS = 60 * 1e3;
@@ -2648,19 +701,19 @@ var HTML_ERROR = (error) => `<!doctype html>
2648
701
  </body>
2649
702
  </html>`;
2650
703
  function ensureCredentialsDir() {
2651
- if (!fs8.existsSync(AMA_DIR)) {
2652
- fs8.mkdirSync(AMA_DIR, { recursive: true });
704
+ if (!fs4.existsSync(AMA_DIR)) {
705
+ fs4.mkdirSync(AMA_DIR, { recursive: true });
2653
706
  }
2654
707
  }
2655
708
  function saveCredentials(credentials) {
2656
709
  ensureCredentialsDir();
2657
- fs8.writeFileSync(CREDENTIALS_PATH2, JSON.stringify(credentials, null, 2), "utf8");
710
+ fs4.writeFileSync(CREDENTIALS_PATH2, JSON.stringify(credentials, null, 2), "utf8");
2658
711
  }
2659
712
  function readCredentials() {
2660
- if (!fs8.existsSync(CREDENTIALS_PATH2)) {
713
+ if (!fs4.existsSync(CREDENTIALS_PATH2)) {
2661
714
  return null;
2662
715
  }
2663
- const raw = fs8.readFileSync(CREDENTIALS_PATH2, "utf8");
716
+ const raw = fs4.readFileSync(CREDENTIALS_PATH2, "utf8");
2664
717
  const parsed = JSON.parse(raw);
2665
718
  if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string" || typeof parsed.accountId !== "string" || typeof parsed.expiresAt !== "number") {
2666
719
  return null;
@@ -2768,56 +821,66 @@ async function startOAuthServer() {
2768
821
  if (oauthServer) {
2769
822
  return { redirectUri: `http://localhost:${OAUTH_PORT}${CALLBACK_PATH}` };
2770
823
  }
2771
- oauthServer = Bun.serve({
2772
- port: OAUTH_PORT,
2773
- fetch(request) {
2774
- const url = new URL(request.url);
2775
- if (url.pathname !== CALLBACK_PATH) {
2776
- return new Response("Not found", { status: 404 });
2777
- }
2778
- const code = url.searchParams.get("code");
2779
- const state = url.searchParams.get("state");
2780
- const error = url.searchParams.get("error");
2781
- const errorDescription = url.searchParams.get("error_description");
2782
- if (error) {
2783
- const message = errorDescription || error;
2784
- pendingOAuth?.reject(new Error(message));
2785
- pendingOAuth = void 0;
2786
- return new Response(HTML_ERROR(message), {
2787
- headers: { "Content-Type": "text/html" }
2788
- });
2789
- }
2790
- if (!code) {
2791
- const message = "Missing authorization code";
2792
- pendingOAuth?.reject(new Error(message));
2793
- pendingOAuth = void 0;
2794
- return new Response(HTML_ERROR(message), {
2795
- status: 400,
2796
- headers: { "Content-Type": "text/html" }
2797
- });
2798
- }
2799
- if (!pendingOAuth || state !== pendingOAuth.state) {
2800
- const message = "Invalid state - potential CSRF attack";
2801
- pendingOAuth?.reject(new Error(message));
2802
- pendingOAuth = void 0;
2803
- return new Response(HTML_ERROR(message), {
2804
- status: 400,
2805
- headers: { "Content-Type": "text/html" }
2806
- });
2807
- }
2808
- const current = pendingOAuth;
824
+ oauthServer = createServer((req, res) => {
825
+ const requestUrl = new URL(req.url || "/", `http://localhost:${OAUTH_PORT}`);
826
+ if (requestUrl.pathname !== CALLBACK_PATH) {
827
+ res.statusCode = 404;
828
+ res.end("Not found");
829
+ return;
830
+ }
831
+ const code = requestUrl.searchParams.get("code");
832
+ const state = requestUrl.searchParams.get("state");
833
+ const error = requestUrl.searchParams.get("error");
834
+ const errorDescription = requestUrl.searchParams.get("error_description");
835
+ const sendHtml = (html, statusCode = 200) => {
836
+ res.statusCode = statusCode;
837
+ res.setHeader("Content-Type", "text/html");
838
+ res.end(html);
839
+ };
840
+ if (error) {
841
+ const message = errorDescription || error;
842
+ pendingOAuth?.reject(new Error(message));
2809
843
  pendingOAuth = void 0;
2810
- exchangeCodeForTokens(code, `http://localhost:${OAUTH_PORT}${CALLBACK_PATH}`, current.pkce).then((tokens) => current.resolve(tokens)).catch((err) => current.reject(err));
2811
- return new Response(HTML_SUCCESS, {
2812
- headers: { "Content-Type": "text/html" }
2813
- });
844
+ sendHtml(HTML_ERROR(message));
845
+ return;
846
+ }
847
+ if (!code) {
848
+ const message = "Missing authorization code";
849
+ pendingOAuth?.reject(new Error(message));
850
+ pendingOAuth = void 0;
851
+ sendHtml(HTML_ERROR(message), 400);
852
+ return;
853
+ }
854
+ if (!pendingOAuth || state !== pendingOAuth.state) {
855
+ const message = "Invalid state - potential CSRF attack";
856
+ pendingOAuth?.reject(new Error(message));
857
+ pendingOAuth = void 0;
858
+ sendHtml(HTML_ERROR(message), 400);
859
+ return;
2814
860
  }
861
+ const current = pendingOAuth;
862
+ pendingOAuth = void 0;
863
+ exchangeCodeForTokens(code, `http://localhost:${OAUTH_PORT}${CALLBACK_PATH}`, current.pkce).then((tokens) => current.resolve(tokens)).catch((err) => current.reject(err));
864
+ sendHtml(HTML_SUCCESS);
2815
865
  });
866
+ try {
867
+ await new Promise((resolve, reject) => {
868
+ oauthServer?.once("error", reject);
869
+ oauthServer?.listen(OAUTH_PORT, "127.0.0.1", () => {
870
+ oauthServer?.off("error", reject);
871
+ resolve();
872
+ });
873
+ });
874
+ } catch (error) {
875
+ oauthServer?.close();
876
+ oauthServer = void 0;
877
+ throw error;
878
+ }
2816
879
  return { redirectUri: `http://localhost:${OAUTH_PORT}${CALLBACK_PATH}` };
2817
880
  }
2818
881
  function stopOAuthServer() {
2819
882
  if (oauthServer) {
2820
- oauthServer.stop();
883
+ oauthServer.close();
2821
884
  oauthServer = void 0;
2822
885
  }
2823
886
  }
@@ -2936,8 +999,8 @@ async function getCodexStatus() {
2936
999
  async function codexLogout() {
2937
1000
  pendingOAuth = void 0;
2938
1001
  stopOAuthServer();
2939
- if (fs8.existsSync(CREDENTIALS_PATH2)) {
2940
- fs8.unlinkSync(CREDENTIALS_PATH2);
1002
+ if (fs4.existsSync(CREDENTIALS_PATH2)) {
1003
+ fs4.unlinkSync(CREDENTIALS_PATH2);
2941
1004
  }
2942
1005
  }
2943
1006
 
@@ -3220,125 +1283,6 @@ var connectToUserStreams = async (serverUrl) => {
3220
1283
  return ws;
3221
1284
  };
3222
1285
  var toolCallSchema = z.object({
3223
- tool: z.string().describe("The name of the tool to execute"),
3224
- parameters: z.record(z.string(), z.unknown()).describe("Parameters for the tool")
3225
- });
3226
- z.object({
3227
- tool_calls: z.array(toolCallSchema).min(1, "Provide at least one tool call").max(25, "Maximum of 25 tools allowed in batch").describe("Array of tool calls to execute in parallel")
3228
- });
3229
- var DISALLOWED_TOOLS = /* @__PURE__ */ new Set(["batch"]);
3230
- var MAX_CONCURRENCY = 5;
3231
- var PER_CALL_TIMEOUT = 3e4;
3232
- var MAX_BATCH_SIZE = 25;
3233
- var batchableToolExecutors = {
3234
- deleteFile,
3235
- grep: grepTool,
3236
- glob: globTool,
3237
- listDirectory: list,
3238
- readFile: read_file,
3239
- bash: bashTool,
3240
- stringReplace: apply_patch,
3241
- editFile: editFiles
3242
- };
3243
- function withTimeout(promise, ms) {
3244
- return new Promise((resolve, reject) => {
3245
- const timer = setTimeout(() => {
3246
- reject(new Error(`BATCH_CALL_TIMEOUT: exceeded ${ms}ms`));
3247
- }, ms);
3248
- promise.then((v) => {
3249
- clearTimeout(timer);
3250
- resolve(v);
3251
- }).catch((e) => {
3252
- clearTimeout(timer);
3253
- reject(e);
3254
- });
3255
- });
3256
- }
3257
- async function runWithConcurrencyLimit(tasks, limit) {
3258
- const results = new Array(tasks.length);
3259
- let nextIndex = 0;
3260
- async function worker() {
3261
- while (nextIndex < tasks.length) {
3262
- const idx = nextIndex++;
3263
- results[idx] = await tasks[idx]();
3264
- }
3265
- }
3266
- const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => worker());
3267
- await Promise.all(workers);
3268
- return results;
3269
- }
3270
- var batchTool = async function(input, projectCwd) {
3271
- const { tool_calls } = input;
3272
- const callsToExecute = tool_calls.slice(0, MAX_BATCH_SIZE);
3273
- const discardedCalls = tool_calls.slice(MAX_BATCH_SIZE);
3274
- const executeCall = async (call) => {
3275
- const start = performance.now();
3276
- try {
3277
- if (DISALLOWED_TOOLS.has(call.tool)) {
3278
- return {
3279
- tool: call.tool,
3280
- success: false,
3281
- error: `Tool '${call.tool}' is not allowed in batch. Disallowed tools: ${Array.from(DISALLOWED_TOOLS).join(", ")}`,
3282
- durationMs: 0
3283
- };
3284
- }
3285
- const executor = batchableToolExecutors[call.tool];
3286
- if (!executor) {
3287
- const availableTools = Object.keys(batchableToolExecutors).join(", ");
3288
- return {
3289
- tool: call.tool,
3290
- success: false,
3291
- error: `Tool '${call.tool}' not found. Available tools for batching: ${availableTools}`,
3292
- durationMs: 0
3293
- };
3294
- }
3295
- const result = await withTimeout(executor(call.parameters, projectCwd), PER_CALL_TIMEOUT);
3296
- const durationMs = Math.round(performance.now() - start);
3297
- return {
3298
- tool: call.tool,
3299
- success: result.success !== false,
3300
- result,
3301
- durationMs
3302
- };
3303
- } catch (error) {
3304
- const durationMs = Math.round(performance.now() - start);
3305
- const timedOut = error.message?.includes("BATCH_CALL_TIMEOUT");
3306
- return {
3307
- tool: call.tool,
3308
- success: false,
3309
- error: error.message || String(error),
3310
- durationMs,
3311
- timedOut
3312
- };
3313
- }
3314
- };
3315
- const tasks = callsToExecute.map(
3316
- (call) => () => executeCall(call)
3317
- );
3318
- const results = await runWithConcurrencyLimit(tasks, MAX_CONCURRENCY);
3319
- for (const call of discardedCalls) {
3320
- results.push({
3321
- tool: call.tool,
3322
- success: false,
3323
- error: `Maximum of ${MAX_BATCH_SIZE} tools allowed in batch`,
3324
- durationMs: 0
3325
- });
3326
- }
3327
- const successfulCalls = results.filter((r) => r.success).length;
3328
- const failedCalls = results.length - successfulCalls;
3329
- const outputMessage = failedCalls > 0 ? `Executed ${successfulCalls}/${results.length} tools successfully. ${failedCalls} failed.` : `All ${successfulCalls} tools executed successfully.
3330
-
3331
- Keep using the batch tool for optimal performance in your next response!`;
3332
- return {
3333
- success: failedCalls === 0,
3334
- message: outputMessage,
3335
- totalCalls: results.length,
3336
- successful: successfulCalls,
3337
- failed: failedCalls,
3338
- results
3339
- };
3340
- };
3341
- var toolCallSchema2 = z.object({
3342
1286
  type: z.literal("tool_call"),
3343
1287
  id: z.string(),
3344
1288
  tool: z.string(),
@@ -3361,7 +1305,7 @@ var TOOL_TIMEOUTS = {
3361
1305
  function getTimeoutForTool(tool) {
3362
1306
  return TOOL_TIMEOUTS[tool] ?? DEFAULT_TIMEOUT_MS;
3363
1307
  }
3364
- function withTimeout2(promise, ms, tool) {
1308
+ function withTimeout(promise, ms, tool) {
3365
1309
  return new Promise((resolve, reject) => {
3366
1310
  const timer = setTimeout(() => {
3367
1311
  reject(new ToolTimeoutError(tool, ms));
@@ -3411,7 +1355,7 @@ async function executeTool(toolName, args, projectCwd, executors) {
3411
1355
  }
3412
1356
  try {
3413
1357
  const timeoutMs = getTimeoutForTool(toolName);
3414
- const result = await withTimeout2(executor(args, projectCwd), timeoutMs, toolName);
1358
+ const result = await withTimeout(executor(args, projectCwd), timeoutMs, toolName);
3415
1359
  const durationMs = Math.round(performance.now() - start);
3416
1360
  return {
3417
1361
  success: result?.success !== false,
@@ -3438,7 +1382,7 @@ async function executeTool(toolName, args, projectCwd, executors) {
3438
1382
  }
3439
1383
  }
3440
1384
  function parseToolCall(raw) {
3441
- const result = toolCallSchema2.safeParse(raw);
1385
+ const result = toolCallSchema.safeParse(raw);
3442
1386
  if (!result.success) {
3443
1387
  return new ValidationError(
3444
1388
  `Invalid tool_call payload: ${result.error.issues.map((i) => i.message).join(", ")}`