amai 0.0.1

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.
@@ -0,0 +1,1578 @@
1
+ #!/usr/bin/env node
2
+ import WebSocket from 'ws';
3
+ import { z } from 'zod';
4
+ import { readFile, writeFile, stat, access, readdir, glob, unlink, mkdir } from 'fs/promises';
5
+ import path9 from 'path';
6
+ import fs3, { readdirSync } from 'fs';
7
+ import os2 from 'os';
8
+ import { exec, spawn } from 'child_process';
9
+ import { promisify } from 'util';
10
+ import pc2 from 'picocolors';
11
+ import { Hono } from 'hono';
12
+ import { serve } from '@hono/node-server';
13
+ import { cors } from 'hono/cors';
14
+
15
+ var DEFAULT_SERVER_URL = "wss://ama-production-a628.up.railway.app";
16
+ var AMA_DIR = path9.join(os2.homedir(), ".ama");
17
+ var CODE_DIR = path9.join(AMA_DIR, "code");
18
+ var STORAGE_DIR = path9.join(AMA_DIR, "storage");
19
+
20
+ // src/lib/project-registry.ts
21
+ var REGISTRY_FILE = path9.join(AMA_DIR, "projects.json");
22
+ var ProjectRegistry = class {
23
+ projects = /* @__PURE__ */ new Map();
24
+ constructor() {
25
+ this.load();
26
+ }
27
+ load() {
28
+ try {
29
+ if (fs3.existsSync(REGISTRY_FILE)) {
30
+ const data = fs3.readFileSync(REGISTRY_FILE, "utf8");
31
+ const parsed = JSON.parse(data);
32
+ if (!Array.isArray(parsed)) {
33
+ console.error("Invalid project registry format: expected array, got", typeof parsed);
34
+ const backupFile = REGISTRY_FILE + ".backup." + Date.now();
35
+ fs3.copyFileSync(REGISTRY_FILE, backupFile);
36
+ fs3.unlinkSync(REGISTRY_FILE);
37
+ return;
38
+ }
39
+ const projects = parsed;
40
+ this.projects.clear();
41
+ projects.forEach((project) => {
42
+ if (project && typeof project === "object" && project.id && project.cwd) {
43
+ this.projects.set(project.id, project);
44
+ }
45
+ });
46
+ }
47
+ } catch (error) {
48
+ console.error("Failed to load project registry:", error);
49
+ if (fs3.existsSync(REGISTRY_FILE)) {
50
+ try {
51
+ const backupFile = REGISTRY_FILE + ".backup." + Date.now();
52
+ fs3.copyFileSync(REGISTRY_FILE, backupFile);
53
+ fs3.unlinkSync(REGISTRY_FILE);
54
+ console.log("Corrupted registry file backed up and removed. Starting fresh.");
55
+ } catch (backupError) {
56
+ }
57
+ }
58
+ }
59
+ }
60
+ save() {
61
+ try {
62
+ if (!fs3.existsSync(AMA_DIR)) {
63
+ fs3.mkdirSync(AMA_DIR, { recursive: true });
64
+ }
65
+ const projects = Array.from(this.projects.values());
66
+ fs3.writeFileSync(REGISTRY_FILE, JSON.stringify(projects, null, 2), "utf8");
67
+ } catch (error) {
68
+ console.error("Failed to save project registry:", error);
69
+ }
70
+ }
71
+ register(projectId, cwd, name) {
72
+ const normalizedCwd = path9.normalize(path9.resolve(cwd));
73
+ this.projects.set(projectId, {
74
+ id: projectId,
75
+ cwd: normalizedCwd,
76
+ name: name || path9.basename(normalizedCwd),
77
+ active: true
78
+ });
79
+ this.save();
80
+ }
81
+ unregister(projectId) {
82
+ this.projects.delete(projectId);
83
+ this.save();
84
+ }
85
+ getProjectCwd(projectId) {
86
+ const project = this.projects.get(projectId);
87
+ return project?.cwd || null;
88
+ }
89
+ isPathAllowed(projectId, targetPath) {
90
+ const projectCwd = this.getProjectCwd(projectId);
91
+ if (!projectCwd) {
92
+ return false;
93
+ }
94
+ return isPathWithinProject(targetPath, projectCwd);
95
+ }
96
+ list() {
97
+ return Array.from(this.projects.values());
98
+ }
99
+ getProject(projectId) {
100
+ return this.projects.get(projectId) || null;
101
+ }
102
+ };
103
+ var projectRegistry = new ProjectRegistry();
104
+ function isPathWithinProject(filePath, projectCwd) {
105
+ try {
106
+ const resolved = path9.resolve(projectCwd, filePath);
107
+ const normalized = path9.normalize(resolved);
108
+ const normalizedCwd = path9.normalize(projectCwd);
109
+ return normalized.startsWith(normalizedCwd);
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ // src/lib/sandbox.ts
116
+ function validatePath(filePath, projectCwd) {
117
+ if (!projectCwd) {
118
+ return {
119
+ valid: false,
120
+ error: "ACCESS_DENIED: No project context provided"
121
+ };
122
+ }
123
+ try {
124
+ const resolvedPath = path9.resolve(projectCwd, filePath);
125
+ if (!isPathWithinProject(filePath, projectCwd)) {
126
+ return {
127
+ valid: false,
128
+ error: `ACCESS_DENIED: Path "${filePath}" is outside project directory "${projectCwd}"`
129
+ };
130
+ }
131
+ return {
132
+ valid: true,
133
+ resolvedPath
134
+ };
135
+ } catch (error) {
136
+ return {
137
+ valid: false,
138
+ error: `ACCESS_DENIED: Invalid path "${filePath}"`
139
+ };
140
+ }
141
+ }
142
+ function resolveProjectPath(filePath, projectCwd) {
143
+ return path9.resolve(projectCwd, filePath);
144
+ }
145
+
146
+ // src/tools/read-file.ts
147
+ z.object({
148
+ relative_file_path: z.string().describe("The relative path to the file to read."),
149
+ should_read_entire_file: z.boolean().describe("Whether to read the entire file."),
150
+ start_line_one_indexed: z.number().optional().describe(
151
+ "The one-indexed line number to start reading from (inclusive)."
152
+ ),
153
+ end_line_one_indexed: z.number().optional().describe("The one-indexed line number to end reading at (inclusive).")
154
+ });
155
+ var read_file = async function(input, projectCwd) {
156
+ const { relative_file_path, should_read_entire_file, start_line_one_indexed, end_line_one_indexed } = input;
157
+ try {
158
+ if (!relative_file_path) {
159
+ return {
160
+ success: false,
161
+ message: "Missing required parameter: target_file",
162
+ error: "MISSING_TARGET_FILE"
163
+ };
164
+ }
165
+ if (!should_read_entire_file) {
166
+ if (start_line_one_indexed === void 0 || end_line_one_indexed === void 0) {
167
+ return {
168
+ success: false,
169
+ message: "start_line_one_indexed and end_line_one_indexed are required when should_read_entire_file is false",
170
+ error: "MISSING_LINE_RANGE"
171
+ };
172
+ }
173
+ if (!Number.isInteger(start_line_one_indexed) || start_line_one_indexed < 1) {
174
+ return {
175
+ success: false,
176
+ message: "start_line_one_indexed must be a positive integer (1-indexed)",
177
+ error: "INVALID_START_LINE"
178
+ };
179
+ }
180
+ if (!Number.isInteger(end_line_one_indexed) || end_line_one_indexed < 1) {
181
+ return {
182
+ success: false,
183
+ message: "end_line_one_indexed must be a positive integer (1-indexed)",
184
+ error: "INVALID_END_LINE"
185
+ };
186
+ }
187
+ if (end_line_one_indexed < start_line_one_indexed) {
188
+ return {
189
+ success: false,
190
+ message: "end_line_one_indexed must be greater than or equal to start_line_one_indexed",
191
+ error: "INVALID_LINE_RANGE"
192
+ };
193
+ }
194
+ }
195
+ if (projectCwd) {
196
+ const validation = validatePath(relative_file_path, projectCwd);
197
+ if (!validation.valid) {
198
+ return {
199
+ success: false,
200
+ message: validation.error || "Path validation failed",
201
+ error: "ACCESS_DENIED"
202
+ };
203
+ }
204
+ const absolute_file_path = validation.resolvedPath;
205
+ try {
206
+ const fileStats = await stat(absolute_file_path);
207
+ if (!fileStats.isFile()) {
208
+ return {
209
+ success: false,
210
+ message: `Path is not a file: ${relative_file_path}`,
211
+ error: "NOT_A_FILE"
212
+ };
213
+ }
214
+ } catch (error) {
215
+ if (error?.code === "ENOENT") {
216
+ return {
217
+ success: false,
218
+ message: `File not found: ${relative_file_path}`,
219
+ error: "FILE_NOT_FOUND"
220
+ };
221
+ }
222
+ return {
223
+ success: false,
224
+ message: `Failed to access file: ${relative_file_path}`,
225
+ error: "READ_ERROR"
226
+ };
227
+ }
228
+ try {
229
+ const fileContent = await readFile(absolute_file_path, "utf-8");
230
+ const lines = fileContent.split(/\r?\n/);
231
+ const totalLines = lines.length;
232
+ if (should_read_entire_file) {
233
+ return {
234
+ success: true,
235
+ message: `Successfully read entire file: ${relative_file_path} (${totalLines} lines)`,
236
+ content: fileContent,
237
+ totalLines
238
+ };
239
+ }
240
+ const startIndex = start_line_one_indexed - 1;
241
+ if (startIndex >= totalLines) {
242
+ return {
243
+ success: false,
244
+ message: "start_line_one_indexed must be less than or equal to the total number of lines in the file",
245
+ error: "INVALID_LINE_RANGE"
246
+ };
247
+ }
248
+ const normalizedEnd = Math.min(end_line_one_indexed, totalLines);
249
+ const selectedLines = lines.slice(startIndex, normalizedEnd).join("\n");
250
+ const linesRead = normalizedEnd - start_line_one_indexed + 1;
251
+ return {
252
+ success: true,
253
+ message: `Successfully read lines ${start_line_one_indexed}-${normalizedEnd} from file: ${relative_file_path} (${linesRead} lines of ${totalLines} total)`,
254
+ content: selectedLines,
255
+ totalLines
256
+ };
257
+ } catch {
258
+ return {
259
+ success: false,
260
+ message: `Failed to read file: ${relative_file_path}`,
261
+ error: "READ_ERROR"
262
+ };
263
+ }
264
+ } else {
265
+ const absolute_file_path = path9.resolve(relative_file_path);
266
+ try {
267
+ const fileStats = await stat(absolute_file_path);
268
+ if (!fileStats.isFile()) {
269
+ return {
270
+ success: false,
271
+ message: `Path is not a file: ${relative_file_path}`,
272
+ error: "NOT_A_FILE"
273
+ };
274
+ }
275
+ } catch (error) {
276
+ if (error?.code === "ENOENT") {
277
+ return {
278
+ success: false,
279
+ message: `File not found: ${relative_file_path}`,
280
+ error: "FILE_NOT_FOUND"
281
+ };
282
+ }
283
+ return {
284
+ success: false,
285
+ message: `Failed to access file: ${relative_file_path}`,
286
+ error: "READ_ERROR"
287
+ };
288
+ }
289
+ try {
290
+ const fileContent = await readFile(absolute_file_path, "utf-8");
291
+ const lines = fileContent.split(/\r?\n/);
292
+ const totalLines = lines.length;
293
+ if (should_read_entire_file) {
294
+ return {
295
+ success: true,
296
+ message: `Successfully read entire file: ${relative_file_path} (${totalLines} lines)`,
297
+ content: fileContent,
298
+ totalLines
299
+ };
300
+ }
301
+ const startIndex = start_line_one_indexed - 1;
302
+ if (startIndex >= totalLines) {
303
+ return {
304
+ success: false,
305
+ message: "start_line_one_indexed must be less than or equal to the total number of lines in the file",
306
+ error: "INVALID_LINE_RANGE"
307
+ };
308
+ }
309
+ const normalizedEnd = Math.min(end_line_one_indexed, totalLines);
310
+ const selectedLines = lines.slice(startIndex, normalizedEnd).join("\n");
311
+ const linesRead = normalizedEnd - start_line_one_indexed + 1;
312
+ return {
313
+ success: true,
314
+ message: `Successfully read lines ${start_line_one_indexed}-${normalizedEnd} from file: ${relative_file_path} (${linesRead} lines of ${totalLines} total)`,
315
+ content: selectedLines,
316
+ totalLines
317
+ };
318
+ } catch {
319
+ return {
320
+ success: false,
321
+ message: `Failed to read file: ${relative_file_path}`,
322
+ error: "READ_ERROR"
323
+ };
324
+ }
325
+ }
326
+ } catch {
327
+ return {
328
+ success: false,
329
+ message: `Failed to read file: ${relative_file_path}`,
330
+ error: "READ_ERROR"
331
+ };
332
+ }
333
+ };
334
+
335
+ // ../../node_modules/.bun/diff@8.0.2/node_modules/diff/libesm/diff/base.js
336
+ var Diff = class {
337
+ diff(oldStr, newStr, options = {}) {
338
+ let callback;
339
+ if (typeof options === "function") {
340
+ callback = options;
341
+ options = {};
342
+ } else if ("callback" in options) {
343
+ callback = options.callback;
344
+ }
345
+ const oldString = this.castInput(oldStr, options);
346
+ const newString = this.castInput(newStr, options);
347
+ const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
348
+ const newTokens = this.removeEmpty(this.tokenize(newString, options));
349
+ return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
350
+ }
351
+ diffWithOptionsObj(oldTokens, newTokens, options, callback) {
352
+ var _a;
353
+ const done = (value) => {
354
+ value = this.postProcess(value, options);
355
+ if (callback) {
356
+ setTimeout(function() {
357
+ callback(value);
358
+ }, 0);
359
+ return void 0;
360
+ } else {
361
+ return value;
362
+ }
363
+ };
364
+ const newLen = newTokens.length, oldLen = oldTokens.length;
365
+ let editLength = 1;
366
+ let maxEditLength = newLen + oldLen;
367
+ if (options.maxEditLength != null) {
368
+ maxEditLength = Math.min(maxEditLength, options.maxEditLength);
369
+ }
370
+ const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
371
+ const abortAfterTimestamp = Date.now() + maxExecutionTime;
372
+ const bestPath = [{ oldPos: -1, lastComponent: void 0 }];
373
+ let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
374
+ if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
375
+ return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
376
+ }
377
+ let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
378
+ const execEditLength = () => {
379
+ for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
380
+ let basePath;
381
+ const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
382
+ if (removePath) {
383
+ bestPath[diagonalPath - 1] = void 0;
384
+ }
385
+ let canAdd = false;
386
+ if (addPath) {
387
+ const addPathNewPos = addPath.oldPos - diagonalPath;
388
+ canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
389
+ }
390
+ const canRemove = removePath && removePath.oldPos + 1 < oldLen;
391
+ if (!canAdd && !canRemove) {
392
+ bestPath[diagonalPath] = void 0;
393
+ continue;
394
+ }
395
+ if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
396
+ basePath = this.addToPath(addPath, true, false, 0, options);
397
+ } else {
398
+ basePath = this.addToPath(removePath, false, true, 1, options);
399
+ }
400
+ newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
401
+ if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
402
+ return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
403
+ } else {
404
+ bestPath[diagonalPath] = basePath;
405
+ if (basePath.oldPos + 1 >= oldLen) {
406
+ maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
407
+ }
408
+ if (newPos + 1 >= newLen) {
409
+ minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
410
+ }
411
+ }
412
+ }
413
+ editLength++;
414
+ };
415
+ if (callback) {
416
+ (function exec3() {
417
+ setTimeout(function() {
418
+ if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
419
+ return callback(void 0);
420
+ }
421
+ if (!execEditLength()) {
422
+ exec3();
423
+ }
424
+ }, 0);
425
+ })();
426
+ } else {
427
+ while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
428
+ const ret = execEditLength();
429
+ if (ret) {
430
+ return ret;
431
+ }
432
+ }
433
+ }
434
+ }
435
+ addToPath(path13, added, removed, oldPosInc, options) {
436
+ const last = path13.lastComponent;
437
+ if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
438
+ return {
439
+ oldPos: path13.oldPos + oldPosInc,
440
+ lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
441
+ };
442
+ } else {
443
+ return {
444
+ oldPos: path13.oldPos + oldPosInc,
445
+ lastComponent: { count: 1, added, removed, previousComponent: last }
446
+ };
447
+ }
448
+ }
449
+ extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
450
+ const newLen = newTokens.length, oldLen = oldTokens.length;
451
+ let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
452
+ while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
453
+ newPos++;
454
+ oldPos++;
455
+ commonCount++;
456
+ if (options.oneChangePerToken) {
457
+ basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
458
+ }
459
+ }
460
+ if (commonCount && !options.oneChangePerToken) {
461
+ basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
462
+ }
463
+ basePath.oldPos = oldPos;
464
+ return newPos;
465
+ }
466
+ equals(left, right, options) {
467
+ if (options.comparator) {
468
+ return options.comparator(left, right);
469
+ } else {
470
+ return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
471
+ }
472
+ }
473
+ removeEmpty(array) {
474
+ const ret = [];
475
+ for (let i = 0; i < array.length; i++) {
476
+ if (array[i]) {
477
+ ret.push(array[i]);
478
+ }
479
+ }
480
+ return ret;
481
+ }
482
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
483
+ castInput(value, options) {
484
+ return value;
485
+ }
486
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
487
+ tokenize(value, options) {
488
+ return Array.from(value);
489
+ }
490
+ join(chars) {
491
+ return chars.join("");
492
+ }
493
+ postProcess(changeObjects, options) {
494
+ return changeObjects;
495
+ }
496
+ get useLongestToken() {
497
+ return false;
498
+ }
499
+ buildValues(lastComponent, newTokens, oldTokens) {
500
+ const components = [];
501
+ let nextComponent;
502
+ while (lastComponent) {
503
+ components.push(lastComponent);
504
+ nextComponent = lastComponent.previousComponent;
505
+ delete lastComponent.previousComponent;
506
+ lastComponent = nextComponent;
507
+ }
508
+ components.reverse();
509
+ const componentLen = components.length;
510
+ let componentPos = 0, newPos = 0, oldPos = 0;
511
+ for (; componentPos < componentLen; componentPos++) {
512
+ const component = components[componentPos];
513
+ if (!component.removed) {
514
+ if (!component.added && this.useLongestToken) {
515
+ let value = newTokens.slice(newPos, newPos + component.count);
516
+ value = value.map(function(value2, i) {
517
+ const oldValue = oldTokens[oldPos + i];
518
+ return oldValue.length > value2.length ? oldValue : value2;
519
+ });
520
+ component.value = this.join(value);
521
+ } else {
522
+ component.value = this.join(newTokens.slice(newPos, newPos + component.count));
523
+ }
524
+ newPos += component.count;
525
+ if (!component.added) {
526
+ oldPos += component.count;
527
+ }
528
+ } else {
529
+ component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
530
+ oldPos += component.count;
531
+ }
532
+ }
533
+ return components;
534
+ }
535
+ };
536
+
537
+ // ../../node_modules/.bun/diff@8.0.2/node_modules/diff/libesm/diff/line.js
538
+ var LineDiff = class extends Diff {
539
+ constructor() {
540
+ super(...arguments);
541
+ this.tokenize = tokenize;
542
+ }
543
+ equals(left, right, options) {
544
+ if (options.ignoreWhitespace) {
545
+ if (!options.newlineIsToken || !left.includes("\n")) {
546
+ left = left.trim();
547
+ }
548
+ if (!options.newlineIsToken || !right.includes("\n")) {
549
+ right = right.trim();
550
+ }
551
+ } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
552
+ if (left.endsWith("\n")) {
553
+ left = left.slice(0, -1);
554
+ }
555
+ if (right.endsWith("\n")) {
556
+ right = right.slice(0, -1);
557
+ }
558
+ }
559
+ return super.equals(left, right, options);
560
+ }
561
+ };
562
+ var lineDiff = new LineDiff();
563
+ function diffLines(oldStr, newStr, options) {
564
+ return lineDiff.diff(oldStr, newStr, options);
565
+ }
566
+ function tokenize(value, options) {
567
+ if (options.stripTrailingCr) {
568
+ value = value.replace(/\r\n/g, "\n");
569
+ }
570
+ const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
571
+ if (!linesAndNewlines[linesAndNewlines.length - 1]) {
572
+ linesAndNewlines.pop();
573
+ }
574
+ for (let i = 0; i < linesAndNewlines.length; i++) {
575
+ const line = linesAndNewlines[i];
576
+ if (i % 2 && !options.newlineIsToken) {
577
+ retLines[retLines.length - 1] += line;
578
+ } else {
579
+ retLines.push(line);
580
+ }
581
+ }
582
+ return retLines;
583
+ }
584
+
585
+ // src/lib/diff.ts
586
+ function calculateDiffStats(oldContent, newContent) {
587
+ const changes = diffLines(oldContent, newContent);
588
+ let linesAdded = 0;
589
+ let linesRemoved = 0;
590
+ for (const change of changes) {
591
+ if (change.added) {
592
+ linesAdded += change.count || 0;
593
+ } else if (change.removed) {
594
+ linesRemoved += change.count || 0;
595
+ }
596
+ }
597
+ return { linesAdded, linesRemoved };
598
+ }
599
+
600
+ // src/tools/apply-patch.ts
601
+ z.object({
602
+ 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"),
603
+ new_string: z.string().describe("The edited text to replace the old_string (must be different from the old_string)"),
604
+ 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)")
605
+ });
606
+ var apply_patch = async function(input, projectCwd) {
607
+ const { file_path, new_string, old_string } = input;
608
+ try {
609
+ if (!file_path) {
610
+ return {
611
+ success: false,
612
+ message: "Missing required parameter: file_path",
613
+ error: "MISSING_FILE_PATH"
614
+ };
615
+ }
616
+ if (old_string === void 0 || old_string === null) {
617
+ return {
618
+ success: false,
619
+ message: "Missing required parameter: old_string",
620
+ error: "MISSING_OLD_STRING"
621
+ };
622
+ }
623
+ if (new_string === void 0 || new_string === null) {
624
+ return {
625
+ success: false,
626
+ message: "Missing required parameter: new_string",
627
+ error: "MISSING_NEW_STRING"
628
+ };
629
+ }
630
+ if (old_string === new_string) {
631
+ return {
632
+ success: false,
633
+ message: "old_string and new_string must be different",
634
+ error: "STRINGS_IDENTICAL"
635
+ };
636
+ }
637
+ if (projectCwd) {
638
+ const validation = validatePath(file_path, projectCwd);
639
+ if (!validation.valid) {
640
+ return {
641
+ success: false,
642
+ message: validation.error || "Path validation failed",
643
+ error: "ACCESS_DENIED"
644
+ };
645
+ }
646
+ }
647
+ const basePath = projectCwd || process.cwd();
648
+ const absolute_file_path = resolveProjectPath(file_path, basePath);
649
+ let fileContent;
650
+ try {
651
+ fileContent = await readFile(absolute_file_path, "utf-8");
652
+ } catch (error) {
653
+ if (error?.code === "ENOENT") {
654
+ return {
655
+ success: false,
656
+ message: `File not found: ${file_path}`,
657
+ error: "FILE_NOT_FOUND"
658
+ };
659
+ }
660
+ return {
661
+ success: false,
662
+ message: `Failed to read file: ${file_path}`,
663
+ error: "READ_ERROR"
664
+ };
665
+ }
666
+ if (!fileContent.includes(old_string)) {
667
+ return {
668
+ success: false,
669
+ message: `old_string not found in file: ${file_path}`,
670
+ error: "STRING_NOT_FOUND"
671
+ };
672
+ }
673
+ const occurrences = fileContent.split(old_string).length - 1;
674
+ if (occurrences > 1) {
675
+ return {
676
+ success: false,
677
+ message: `old_string appears ${occurrences} times in the file. It must be unique. Please include more context to make it unique.`,
678
+ error: "STRING_NOT_UNIQUE"
679
+ };
680
+ }
681
+ const newContent = fileContent.replace(old_string, new_string);
682
+ try {
683
+ await writeFile(absolute_file_path, newContent, "utf-8");
684
+ const diffStats = calculateDiffStats(fileContent, newContent);
685
+ return {
686
+ success: true,
687
+ old_string,
688
+ new_string,
689
+ linesAdded: diffStats.linesAdded,
690
+ linesRemoved: diffStats.linesRemoved,
691
+ message: `Successfully replaced string in file: ${file_path}`
692
+ };
693
+ } catch (error) {
694
+ return {
695
+ success: false,
696
+ message: `Failed to write to file: ${file_path}`,
697
+ error: "WRITE_ERROR"
698
+ };
699
+ }
700
+ } catch (error) {
701
+ return {
702
+ success: false,
703
+ message: `Unexpected error: ${error.message}`,
704
+ error: "UNEXPECTED_ERROR"
705
+ };
706
+ }
707
+ };
708
+ z.object({
709
+ 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"),
710
+ content: z.string().describe("The content to write to the file"),
711
+ providedNewFile: z.boolean().describe("The new file content to write to the file").optional()
712
+ });
713
+ var editFiles = async function(input, projectCwd) {
714
+ const { target_file, content, providedNewFile } = input;
715
+ try {
716
+ if (projectCwd) {
717
+ const validation = validatePath(target_file, projectCwd);
718
+ if (!validation.valid) {
719
+ return {
720
+ success: false,
721
+ error: validation.error || "Path validation failed",
722
+ message: `Failed to edit file: ${target_file}`
723
+ };
724
+ }
725
+ }
726
+ const basePath = projectCwd || process.cwd();
727
+ const filePath = resolveProjectPath(target_file, basePath);
728
+ const dirPath = path9.dirname(filePath);
729
+ await mkdir(dirPath, { recursive: true });
730
+ let isNewFile = providedNewFile;
731
+ let existingContent = "";
732
+ if (isNewFile === void 0) {
733
+ try {
734
+ existingContent = await fs3.promises.readFile(filePath, "utf-8");
735
+ isNewFile = false;
736
+ } catch (error) {
737
+ isNewFile = true;
738
+ }
739
+ } else if (!isNewFile) {
740
+ try {
741
+ existingContent = await fs3.promises.readFile(filePath, "utf-8");
742
+ } catch (error) {
743
+ isNewFile = true;
744
+ }
745
+ }
746
+ await fs3.promises.writeFile(filePath, content);
747
+ const diffStats = calculateDiffStats(existingContent, content);
748
+ if (isNewFile) {
749
+ return {
750
+ success: true,
751
+ isNewFile: true,
752
+ old_string: "",
753
+ new_string: content,
754
+ message: `Created new file: ${target_file}`,
755
+ linesAdded: diffStats.linesAdded,
756
+ linesRemoved: diffStats.linesRemoved
757
+ };
758
+ } else {
759
+ return {
760
+ success: true,
761
+ isNewFile: false,
762
+ old_string: existingContent,
763
+ new_string: content,
764
+ message: `Modified file: ${target_file}`,
765
+ linesAdded: diffStats.linesAdded,
766
+ linesRemoved: diffStats.linesRemoved
767
+ };
768
+ }
769
+ } catch (error) {
770
+ return {
771
+ success: false,
772
+ error: error instanceof Error ? error.message : "Unknown error",
773
+ message: `Failed to edit file: ${target_file}`
774
+ };
775
+ }
776
+ };
777
+ z.object({
778
+ path: z.string().describe("Relative file path to delete")
779
+ });
780
+ var deleteFile = async function(input, projectCwd) {
781
+ const { path: realPath } = input;
782
+ if (!realPath) {
783
+ return {
784
+ success: false,
785
+ message: "Missing required parameter: path",
786
+ error: "MISSING_PATH"
787
+ };
788
+ }
789
+ if (projectCwd) {
790
+ const validation = validatePath(realPath, projectCwd);
791
+ if (!validation.valid) {
792
+ return {
793
+ success: false,
794
+ message: validation.error || "Path validation failed",
795
+ error: "ACCESS_DENIED"
796
+ };
797
+ }
798
+ }
799
+ try {
800
+ const basePath = projectCwd || process.cwd();
801
+ const absolute_file_path = resolveProjectPath(realPath, basePath);
802
+ if (!absolute_file_path) {
803
+ return {
804
+ success: false,
805
+ message: "Invalid file path",
806
+ error: "INVALID_FILE_PATH"
807
+ };
808
+ }
809
+ const originalContent = await readFile(absolute_file_path);
810
+ if (originalContent === void 0) {
811
+ return {
812
+ success: false,
813
+ message: `Failed to read file before deletion: ${realPath}`,
814
+ error: "READ_ERROR"
815
+ };
816
+ }
817
+ const deleteResult = await unlink(absolute_file_path).catch(() => {
818
+ return {
819
+ success: false,
820
+ message: `Failed to read file before deletion: ${realPath}`,
821
+ error: "DELETE_ERROR"
822
+ };
823
+ });
824
+ if (!deleteResult?.success) {
825
+ return {
826
+ success: false,
827
+ message: `Failed to delete file before deletion: ${realPath}`,
828
+ error: "DELETE_ERROR"
829
+ };
830
+ }
831
+ return {
832
+ success: true,
833
+ message: `Successfully deleted file: ${realPath}`,
834
+ content: originalContent
835
+ };
836
+ } catch (error) {
837
+ return {
838
+ success: false,
839
+ message: `Failed to delete file: ${realPath}`,
840
+ error: "DELETE_ERROR"
841
+ };
842
+ }
843
+ };
844
+ z.object({
845
+ query: z.string().describe("The regex pattern to search for"),
846
+ options: z.object({
847
+ includePattern: z.string().optional().describe('Glob pattern for files to include (e.g., "*.ts")'),
848
+ excludePattern: z.string().optional().describe("Glob pattern for files to exclude"),
849
+ caseSensitive: z.boolean().optional().describe("Whether the search should be case sensitive")
850
+ })
851
+ });
852
+ var execAsync = promisify(exec);
853
+ var grepTool = async function(input, projectCwd) {
854
+ const { query, options } = input;
855
+ try {
856
+ const { includePattern, excludePattern: excludePattern2, caseSensitive } = options || {};
857
+ const searchDir = projectCwd || process.cwd();
858
+ if (projectCwd && !path9.isAbsolute(projectCwd)) {
859
+ return {
860
+ success: false,
861
+ message: "Invalid project directory",
862
+ error: "INVALID_PROJECT_DIR"
863
+ };
864
+ }
865
+ let command = `rg -n --with-filename "${query}"`;
866
+ if (caseSensitive) {
867
+ command += " -i";
868
+ }
869
+ if (includePattern) {
870
+ command += ` --glob "${includePattern}"`;
871
+ }
872
+ if (excludePattern2) {
873
+ command += ` --glob "!${excludePattern2}"`;
874
+ }
875
+ command += ` --max-count 50`;
876
+ command += ` "${searchDir}"`;
877
+ const { stdout } = await execAsync(command);
878
+ const rawMatches = stdout.trim().split("\n").filter((line) => line.length > 0);
879
+ const detailedMatches = [];
880
+ const matches = [];
881
+ for (const rawMatch of rawMatches) {
882
+ const colonIndex = rawMatch.indexOf(":");
883
+ const secondColonIndex = rawMatch.indexOf(":", colonIndex + 1);
884
+ if (colonIndex > 0 && secondColonIndex > colonIndex) {
885
+ const file = rawMatch.substring(0, colonIndex);
886
+ const lineNumber = parseInt(rawMatch.substring(colonIndex + 1, secondColonIndex), 10);
887
+ let content = rawMatch.substring(secondColonIndex + 1);
888
+ if (content.length > 250) {
889
+ content = content.substring(0, 250) + "...";
890
+ }
891
+ detailedMatches.push({
892
+ file,
893
+ lineNumber,
894
+ content
895
+ });
896
+ matches.push(`${file}:${lineNumber}:${content}`);
897
+ } else {
898
+ matches.push(rawMatch);
899
+ }
900
+ }
901
+ return {
902
+ success: true,
903
+ matches,
904
+ detailedMatches,
905
+ query,
906
+ matchCount: matches.length,
907
+ message: `Found ${matches.length} matches for pattern: ${query}`
908
+ };
909
+ } catch (error) {
910
+ return {
911
+ success: false,
912
+ message: error?.message || String(error),
913
+ error: "GREP_EXEC_ERROR"
914
+ };
915
+ }
916
+ };
917
+ z.object({
918
+ pattern: z.string().describe('Glob pattern (e.g., "**/*.js")'),
919
+ path: z.string().optional().describe("Relative directory path to search in")
920
+ });
921
+ var globTool = async function(input, projectCwd) {
922
+ const { pattern, path: inputPath } = input;
923
+ if (!pattern) {
924
+ return {
925
+ success: false,
926
+ message: "Missing required parameter: pattern",
927
+ error: "MISSING_PATTERN"
928
+ };
929
+ }
930
+ try {
931
+ const basePath = projectCwd || process.cwd();
932
+ const searchPath = inputPath ? resolveProjectPath(inputPath, basePath) : basePath;
933
+ if (projectCwd && inputPath) {
934
+ const validation = validatePath(inputPath, projectCwd);
935
+ if (!validation.valid) {
936
+ return {
937
+ success: false,
938
+ message: validation.error || "Path validation failed",
939
+ error: "ACCESS_DENIED"
940
+ };
941
+ }
942
+ }
943
+ const filesGenerator = glob(pattern, {
944
+ cwd: searchPath
945
+ });
946
+ const files = [];
947
+ for await (const file of filesGenerator) {
948
+ files.push(file);
949
+ }
950
+ const searchLocation = inputPath ? ` in "${inputPath}"` : " in current directory";
951
+ const message = `Found ${files.length} matches for pattern "${pattern}"${searchLocation}`;
952
+ return {
953
+ success: true,
954
+ message,
955
+ content: files
956
+ };
957
+ } catch (error) {
958
+ return {
959
+ success: false,
960
+ message: `Failed to find files matching pattern: ${pattern}`,
961
+ error: "GLOB_ERROR"
962
+ };
963
+ }
964
+ };
965
+ var excludePatterns = [
966
+ "node_modules",
967
+ "dist",
968
+ "build",
969
+ "coverage",
970
+ "logs",
971
+ "tmp"
972
+ ];
973
+ var excludePattern = excludePatterns.join("|");
974
+ z.object({
975
+ path: z.string().optional(),
976
+ recursive: z.boolean().optional().describe("Whether to list files recursively"),
977
+ maxDepth: z.number().optional().describe("Maximum recursion depth (default: unlimited)"),
978
+ pattern: z.string().optional().describe("File extension (e.g., '.ts') or glob-like pattern"),
979
+ includeDirectories: z.boolean().optional().describe("Whether to include directories in results (default: true)"),
980
+ includeFiles: z.boolean().optional().describe("Whether to include files in results (default: true)")
981
+ });
982
+ var list = async function(input, projectCwd) {
983
+ const { path: relativePath, recursive, maxDepth, pattern, includeDirectories, includeFiles } = input;
984
+ if (maxDepth !== void 0) {
985
+ if (!Number.isInteger(maxDepth) || maxDepth < 0) {
986
+ return {
987
+ success: false,
988
+ message: "maxDepth must be a non-negative integer",
989
+ error: "INVALID_MAX_DEPTH"
990
+ };
991
+ }
992
+ }
993
+ const includeFilesNormalized = includeFiles ?? true;
994
+ const includeDirectoriesNormalized = includeDirectories ?? true;
995
+ if (!includeFilesNormalized && !includeDirectoriesNormalized) {
996
+ return {
997
+ success: false,
998
+ message: "At least one of includeFiles or includeDirectories must be true",
999
+ error: "INVALID_INCLUDE_OPTIONS"
1000
+ };
1001
+ }
1002
+ try {
1003
+ const basePath = projectCwd || process.cwd();
1004
+ const absolutePath = relativePath ? resolveProjectPath(relativePath, basePath) : basePath;
1005
+ if (projectCwd && relativePath) {
1006
+ const validation = validatePath(relativePath, projectCwd);
1007
+ if (!validation.valid) {
1008
+ return {
1009
+ success: false,
1010
+ message: validation.error || "Path validation failed",
1011
+ error: "ACCESS_DENIED"
1012
+ };
1013
+ }
1014
+ }
1015
+ try {
1016
+ await access(absolutePath);
1017
+ } catch {
1018
+ return {
1019
+ success: false,
1020
+ message: `File does not exist: ${absolutePath}`,
1021
+ error: "FILE_DOES_NOT_EXIST"
1022
+ };
1023
+ }
1024
+ const isDir = (await stat(absolutePath)).isDirectory();
1025
+ if (!isDir) {
1026
+ return {
1027
+ success: false,
1028
+ message: `File is not a directory: ${absolutePath}`,
1029
+ error: "FILE_IS_NOT_A_DIRECTORY"
1030
+ };
1031
+ }
1032
+ const collected = [];
1033
+ const patternMatcher = (() => {
1034
+ if (!pattern) return null;
1035
+ if (pattern.startsWith(".") && !pattern.includes("*") && !pattern.includes("?")) {
1036
+ return (entryName) => entryName.endsWith(pattern);
1037
+ }
1038
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1039
+ const regex = new RegExp(`^${escaped}$`);
1040
+ return (entryName) => regex.test(entryName);
1041
+ })();
1042
+ const matchPattern = (entryName) => {
1043
+ if (!patternMatcher) return true;
1044
+ return patternMatcher(entryName);
1045
+ };
1046
+ const maxDepthNormalized = recursive ? maxDepth ?? Infinity : 0;
1047
+ const walk = async (currentDir, depth) => {
1048
+ const entries = await readdir(currentDir, { withFileTypes: true });
1049
+ for (const entry of entries) {
1050
+ const entryAbsolutePath = path9.join(currentDir, entry.name);
1051
+ const entryRelativePath = path9.relative(absolutePath, entryAbsolutePath) || ".";
1052
+ if (entry.isDirectory()) {
1053
+ const isExcluded = entry.name.match(excludePattern);
1054
+ if (includeDirectoriesNormalized && matchPattern(entry.name) && !isExcluded) {
1055
+ collected.push({
1056
+ name: entry.name,
1057
+ absolutePath: entryAbsolutePath,
1058
+ relativePath: entryRelativePath,
1059
+ type: "directory"
1060
+ });
1061
+ }
1062
+ if (recursive && depth < maxDepthNormalized && !isExcluded) {
1063
+ await walk(entryAbsolutePath, depth + 1);
1064
+ }
1065
+ } else if (entry.isFile()) {
1066
+ if (includeFilesNormalized && matchPattern(entry.name) && !entry.name.match(excludePattern)) {
1067
+ collected.push({
1068
+ name: entry.name,
1069
+ absolutePath: entryAbsolutePath,
1070
+ relativePath: entryRelativePath,
1071
+ type: "file"
1072
+ });
1073
+ }
1074
+ }
1075
+ }
1076
+ };
1077
+ await walk(absolutePath, 0);
1078
+ const totalFiles = collected.filter((item) => item.type === "file").length;
1079
+ const totalDirectories = collected.filter((item) => item.type === "directory").length;
1080
+ let message = `Successfully listed ${collected.length} items in: ${relativePath ?? absolutePath}`;
1081
+ if (recursive) {
1082
+ message += ` (recursive${maxDepth !== void 0 ? `, max depth ${maxDepth}` : ""})`;
1083
+ }
1084
+ if (pattern) {
1085
+ message += ` (filtered by pattern: ${pattern})`;
1086
+ }
1087
+ message += ` - ${totalFiles} files, ${totalDirectories} directories`;
1088
+ return {
1089
+ success: true,
1090
+ message,
1091
+ files: collected
1092
+ };
1093
+ } catch (error) {
1094
+ return {
1095
+ success: false,
1096
+ message: `Failed to list files: ${error}`,
1097
+ error: "LIST_ERROR"
1098
+ };
1099
+ }
1100
+ };
1101
+ var ignoreFiles = ["node_modules", ".git", ".next", ".env", ".env.local", ".env.development.local", ".env.test.local", ".env.production.local"];
1102
+ var getContext = (dir, base = dir, allFiles = []) => {
1103
+ const filePath = readdirSync(dir, { withFileTypes: true });
1104
+ for (const file of filePath) {
1105
+ if (ignoreFiles.includes(file.name)) continue;
1106
+ const fullPath = path9.join(dir, file.name);
1107
+ if (file.isDirectory()) {
1108
+ getContext(fullPath, base, allFiles);
1109
+ } else {
1110
+ allFiles.push(path9.relative(base, fullPath));
1111
+ }
1112
+ }
1113
+ return allFiles;
1114
+ };
1115
+ var HOME = os2.homedir();
1116
+ var IDE_PROJECTS_PATHS = {
1117
+ vscode: path9.join(HOME, ".vscode", "projects"),
1118
+ cursor: path9.join(HOME, ".cursor", "projects"),
1119
+ claude: path9.join(HOME, ".claude", "projects")
1120
+ };
1121
+ function getWorkspaceStoragePath(ide) {
1122
+ const platform = os2.platform();
1123
+ const appName = "Cursor" ;
1124
+ if (platform === "darwin") {
1125
+ return path9.join(HOME, "Library", "Application Support", appName, "User", "workspaceStorage");
1126
+ } else if (platform === "win32") {
1127
+ return path9.join(process.env.APPDATA || "", appName, "User", "workspaceStorage");
1128
+ } else {
1129
+ return path9.join(HOME, ".config", appName, "User", "workspaceStorage");
1130
+ }
1131
+ }
1132
+ function scanWorkspaceStorage(ide) {
1133
+ const projects = [];
1134
+ const storagePath = getWorkspaceStoragePath();
1135
+ if (!fs3.existsSync(storagePath)) {
1136
+ return projects;
1137
+ }
1138
+ try {
1139
+ const workspaces = fs3.readdirSync(storagePath);
1140
+ for (const workspace of workspaces) {
1141
+ const workspaceJsonPath = path9.join(storagePath, workspace, "workspace.json");
1142
+ if (fs3.existsSync(workspaceJsonPath)) {
1143
+ try {
1144
+ const content = fs3.readFileSync(workspaceJsonPath, "utf-8");
1145
+ const data = JSON.parse(content);
1146
+ if (data.folder && typeof data.folder === "string") {
1147
+ let projectPath = data.folder;
1148
+ if (projectPath.startsWith("file://")) {
1149
+ projectPath = projectPath.replace("file://", "");
1150
+ projectPath = decodeURIComponent(projectPath);
1151
+ }
1152
+ if (fs3.existsSync(projectPath) && fs3.statSync(projectPath).isDirectory()) {
1153
+ projects.push({
1154
+ name: path9.basename(projectPath),
1155
+ path: projectPath,
1156
+ type: ide
1157
+ });
1158
+ }
1159
+ }
1160
+ } catch (err) {
1161
+ console.debug(`Error reading workspace.json at ${workspaceJsonPath}: ${err}`);
1162
+ }
1163
+ }
1164
+ }
1165
+ } catch (err) {
1166
+ console.debug(`Error scanning workspaceStorage for ${ide}: ${err}`);
1167
+ }
1168
+ return projects;
1169
+ }
1170
+ var scanIdeProjects = async () => {
1171
+ try {
1172
+ const allProjects = [];
1173
+ const seenPaths = /* @__PURE__ */ new Set();
1174
+ const addProject = (projectPath, ide) => {
1175
+ try {
1176
+ const resolvedPath = fs3.realpathSync(projectPath);
1177
+ if (fs3.existsSync(resolvedPath) && fs3.statSync(resolvedPath).isDirectory() && !seenPaths.has(resolvedPath)) {
1178
+ const isIdeProjectsDir = Object.values(IDE_PROJECTS_PATHS).some((ideDir) => {
1179
+ try {
1180
+ return fs3.realpathSync(ideDir) === resolvedPath;
1181
+ } catch {
1182
+ return false;
1183
+ }
1184
+ });
1185
+ if (!isIdeProjectsDir) {
1186
+ seenPaths.add(resolvedPath);
1187
+ allProjects.push({
1188
+ name: path9.basename(resolvedPath),
1189
+ path: resolvedPath,
1190
+ type: ide
1191
+ });
1192
+ }
1193
+ }
1194
+ } catch {
1195
+ }
1196
+ };
1197
+ const cursorProjects = scanWorkspaceStorage("cursor");
1198
+ for (const project of cursorProjects) {
1199
+ addProject(project.path, "cursor");
1200
+ }
1201
+ for (const [ide, dirPath] of Object.entries(IDE_PROJECTS_PATHS)) {
1202
+ if (ide === "cursor") continue;
1203
+ if (fs3.existsSync(dirPath)) {
1204
+ const projects = fs3.readdirSync(dirPath);
1205
+ projects.forEach((project) => {
1206
+ const projectPath = path9.join(dirPath, project);
1207
+ try {
1208
+ const stats = fs3.lstatSync(projectPath);
1209
+ let actualPath = null;
1210
+ if (stats.isSymbolicLink()) {
1211
+ actualPath = fs3.realpathSync(projectPath);
1212
+ } else if (stats.isFile()) {
1213
+ try {
1214
+ let content = fs3.readFileSync(projectPath, "utf-8").trim();
1215
+ if (content.startsWith("~/") || content === "~") {
1216
+ content = content.replace(/^~/, HOME);
1217
+ }
1218
+ const resolvedContent = path9.isAbsolute(content) ? content : path9.resolve(path9.dirname(projectPath), content);
1219
+ if (fs3.existsSync(resolvedContent) && fs3.statSync(resolvedContent).isDirectory()) {
1220
+ actualPath = fs3.realpathSync(resolvedContent);
1221
+ }
1222
+ } catch {
1223
+ return;
1224
+ }
1225
+ } else if (stats.isDirectory()) {
1226
+ actualPath = fs3.realpathSync(projectPath);
1227
+ }
1228
+ if (actualPath) {
1229
+ addProject(actualPath, ide);
1230
+ }
1231
+ } catch {
1232
+ }
1233
+ });
1234
+ }
1235
+ }
1236
+ return allProjects;
1237
+ } catch (error) {
1238
+ console.debug(`Error scanning IDE projects: ${error}`);
1239
+ return [];
1240
+ }
1241
+ };
1242
+ var wsConnection = null;
1243
+ var startHttpServer = (connection) => {
1244
+ if (connection) {
1245
+ wsConnection = connection;
1246
+ }
1247
+ const app = new Hono();
1248
+ app.use(cors());
1249
+ app.post("/daemon/status/stream", (c) => {
1250
+ const status = wsConnection ? getConnectionStatus(wsConnection) : "closed";
1251
+ return c.json({ connected: status === "open" });
1252
+ });
1253
+ app.get("context", async (c) => {
1254
+ const context = getContext(process.cwd());
1255
+ return c.body(JSON.stringify(context));
1256
+ });
1257
+ app.get("/ide-projects", async (c) => {
1258
+ try {
1259
+ const projects = await scanIdeProjects();
1260
+ if (!projects) {
1261
+ return c.json({ error: "No projects found" }, 500);
1262
+ }
1263
+ return c.json({ projects });
1264
+ } catch (error) {
1265
+ return c.json({ error: "Failed to scan IDE projects" }, 500);
1266
+ }
1267
+ });
1268
+ app.post("/projects/register", async (c) => {
1269
+ try {
1270
+ const { projectId, cwd, name } = await c.req.json();
1271
+ if (!projectId || !cwd) {
1272
+ return c.json({ error: "projectId and cwd are required" }, 400);
1273
+ }
1274
+ projectRegistry.register(projectId, cwd, name);
1275
+ return c.json({ success: true, projectId, cwd });
1276
+ } catch (error) {
1277
+ return c.json({ error: error.message || "Failed to register project" }, 500);
1278
+ }
1279
+ });
1280
+ app.post("/revert", async (c) => {
1281
+ try {
1282
+ const { filePath, oldString, newString, projectCwd } = await c.req.json();
1283
+ if (!filePath || oldString === void 0) {
1284
+ return c.json({ error: "filePath and oldString required" }, 400);
1285
+ }
1286
+ let resolved;
1287
+ if (projectCwd) {
1288
+ resolved = path9.isAbsolute(filePath) ? filePath : path9.resolve(projectCwd, filePath);
1289
+ const normalizedResolved = path9.normalize(resolved);
1290
+ const normalizedCwd = path9.normalize(projectCwd);
1291
+ if (!normalizedResolved.startsWith(normalizedCwd)) {
1292
+ return c.json({ error: "Path is outside project directory" }, 403);
1293
+ }
1294
+ } else {
1295
+ resolved = path9.isAbsolute(filePath) ? filePath : path9.join(process.cwd(), filePath);
1296
+ }
1297
+ let currentContent;
1298
+ try {
1299
+ currentContent = await readFile(resolved, "utf-8");
1300
+ } catch (error) {
1301
+ if (error?.code === "ENOENT") {
1302
+ return c.json({ error: `File not found: ${filePath}` }, 404);
1303
+ }
1304
+ return c.json({ error: `Failed to read file: ${error.message}` }, 500);
1305
+ }
1306
+ let finalContent;
1307
+ if (newString && newString !== oldString) {
1308
+ if (!currentContent.includes(newString)) {
1309
+ return c.json({ error: "Cannot revert: the new content is not found in the current file. The file may have been modified." }, 400);
1310
+ }
1311
+ const occurrences = currentContent.split(newString).length - 1;
1312
+ if (occurrences > 1) {
1313
+ return c.json({ error: "Cannot revert: the new content appears multiple times in the file" }, 400);
1314
+ }
1315
+ finalContent = currentContent.replace(newString, oldString);
1316
+ } else {
1317
+ finalContent = oldString;
1318
+ }
1319
+ await writeFile(resolved, finalContent, "utf-8");
1320
+ return c.json({ success: true });
1321
+ } catch (error) {
1322
+ return c.json({ error: error.message }, 500);
1323
+ }
1324
+ });
1325
+ app.get("/projects", (c) => {
1326
+ const projects = projectRegistry.list();
1327
+ return c.json({ projects });
1328
+ });
1329
+ app.get("/projects/:projectId", (c) => {
1330
+ const projectId = c.req.param("projectId");
1331
+ const project = projectRegistry.getProject(projectId);
1332
+ if (!project) {
1333
+ return c.json({ error: "Project not found" }, 404);
1334
+ }
1335
+ return c.json({ project });
1336
+ });
1337
+ app.delete("/projects/:projectId", (c) => {
1338
+ const projectId = c.req.param("projectId");
1339
+ projectRegistry.unregister(projectId);
1340
+ return c.json({ success: true });
1341
+ });
1342
+ serve({ fetch: app.fetch, port: 3456 });
1343
+ };
1344
+ var CREDENTIALS_DIR = path9.join(os2.homedir(), ".ama");
1345
+ var CREDENTIALS_PATH = path9.join(CREDENTIALS_DIR, "credentials.json");
1346
+ function getTokens() {
1347
+ if (!fs3.existsSync(CREDENTIALS_PATH)) {
1348
+ return null;
1349
+ }
1350
+ const raw = fs3.readFileSync(CREDENTIALS_PATH, "utf8");
1351
+ const data = JSON.parse(raw);
1352
+ return data;
1353
+ }
1354
+
1355
+ // src/server.ts
1356
+ var toolExecutors = {
1357
+ editFile: editFiles,
1358
+ deleteFile,
1359
+ grep: grepTool,
1360
+ glob: globTool,
1361
+ listDirectory: list,
1362
+ readFile: read_file,
1363
+ stringReplace: apply_patch
1364
+ };
1365
+ function getConnectionStatus(ws) {
1366
+ return ws.readyState === WebSocket.CONNECTING ? "connecting" : ws.readyState === WebSocket.OPEN ? "open" : ws.readyState === WebSocket.CLOSING ? "closing" : "closed";
1367
+ }
1368
+ function connectToServer2(serverUrl = DEFAULT_SERVER_URL) {
1369
+ const tokens = getTokens();
1370
+ if (!tokens) {
1371
+ throw new Error("No tokens found");
1372
+ }
1373
+ const wsUrl = `${serverUrl}/agent-streams`;
1374
+ const ws = new WebSocket(wsUrl, {
1375
+ headers: {
1376
+ "Authorization": `Bearer ${tokens.access_token}`
1377
+ }
1378
+ });
1379
+ ws.on("open", () => {
1380
+ console.log(pc2.green("Connected to server agent streams"));
1381
+ });
1382
+ ws.on("message", async (data) => {
1383
+ const message = JSON.parse(data.toString());
1384
+ if (message.type === "tool_call") {
1385
+ console.log(`Executing tool: ${message.tool}${message.projectCwd ? ` (project: ${message.projectCwd})` : ""}`);
1386
+ try {
1387
+ const executor = toolExecutors[message.tool];
1388
+ if (!executor) {
1389
+ throw new Error(`Unknown tool: ${message.tool}`);
1390
+ }
1391
+ const result = await executor(message.args, message.projectCwd);
1392
+ ws.send(JSON.stringify({
1393
+ type: "tool_result",
1394
+ id: message.id,
1395
+ result
1396
+ }));
1397
+ console.log(pc2.green(`Tool completed: ${message.tool}`));
1398
+ } catch (error) {
1399
+ ws.send(JSON.stringify({
1400
+ type: "tool_result",
1401
+ id: message.id,
1402
+ error: error.message
1403
+ }));
1404
+ console.error(pc2.red(`Tool failed: ${message.tool} ${error.message}`));
1405
+ }
1406
+ }
1407
+ });
1408
+ ws.on("close", () => {
1409
+ console.log(pc2.red("Disconnected from server. Reconnecting in 5s..."));
1410
+ setTimeout(() => connectToServer2(serverUrl), 5e3);
1411
+ });
1412
+ ws.on("error", (error) => {
1413
+ console.error(pc2.red(`WebSocket error: ${error.message}`));
1414
+ });
1415
+ return ws;
1416
+ }
1417
+ async function main() {
1418
+ const serverUrl = DEFAULT_SERVER_URL;
1419
+ console.log(pc2.green("Starting local ama-agent..."));
1420
+ console.log(pc2.gray(`Connecting to server at ${serverUrl}`));
1421
+ const connection = connectToServer2(serverUrl);
1422
+ startHttpServer(connection);
1423
+ }
1424
+ var execAsync2 = promisify(exec);
1425
+ var CODE_SERVER_VERSION = "4.96.4";
1426
+ function getPlatformInfo() {
1427
+ const platform = process.platform;
1428
+ const arch = process.arch;
1429
+ if (platform === "darwin") {
1430
+ return {
1431
+ platform: "macos",
1432
+ arch: arch === "arm64" ? "arm64" : "amd64",
1433
+ ext: "tar.gz"
1434
+ };
1435
+ } else if (platform === "linux") {
1436
+ return {
1437
+ platform: "linux",
1438
+ arch: arch === "arm64" ? "arm64" : "amd64",
1439
+ ext: "tar.gz"
1440
+ };
1441
+ } else {
1442
+ throw new Error(`Unsupported platform: ${platform}`);
1443
+ }
1444
+ }
1445
+ function getDownloadUrl() {
1446
+ const { platform, arch, ext } = getPlatformInfo();
1447
+ return `https://github.com/coder/code-server/releases/download/v${CODE_SERVER_VERSION}/code-server-${CODE_SERVER_VERSION}-${platform}-${arch}.${ext}`;
1448
+ }
1449
+ function getCodeServerDir() {
1450
+ const { platform, arch } = getPlatformInfo();
1451
+ return path9.join(CODE_DIR, `code-server-${CODE_SERVER_VERSION}-${platform}-${arch}`);
1452
+ }
1453
+ function getCodeServerBin() {
1454
+ return path9.join(getCodeServerDir(), "bin", "code-server");
1455
+ }
1456
+ function isCodeServerInstalled() {
1457
+ const binPath = getCodeServerBin();
1458
+ return fs3.existsSync(binPath);
1459
+ }
1460
+ async function installCodeServer() {
1461
+ const { ext } = getPlatformInfo();
1462
+ const downloadUrl = getDownloadUrl();
1463
+ const tarballPath = path9.join(AMA_DIR, `code-server.${ext}`);
1464
+ if (!fs3.existsSync(AMA_DIR)) {
1465
+ fs3.mkdirSync(AMA_DIR, { recursive: true });
1466
+ }
1467
+ if (!fs3.existsSync(CODE_DIR)) {
1468
+ fs3.mkdirSync(CODE_DIR, { recursive: true });
1469
+ }
1470
+ if (!fs3.existsSync(STORAGE_DIR)) {
1471
+ fs3.mkdirSync(STORAGE_DIR, { recursive: true });
1472
+ }
1473
+ console.log(pc2.cyan(`Downloading code-server v${CODE_SERVER_VERSION}...`));
1474
+ console.log(pc2.gray(downloadUrl));
1475
+ const response = await fetch(downloadUrl);
1476
+ if (!response.ok) {
1477
+ throw new Error(`Failed to download code-server: ${response.statusText}`);
1478
+ }
1479
+ const buffer = await response.arrayBuffer();
1480
+ await fs3.promises.writeFile(tarballPath, Buffer.from(buffer));
1481
+ console.log(pc2.cyan("Extracting code-server..."));
1482
+ await execAsync2(`tar -xzf ${tarballPath} -C ${CODE_DIR}`);
1483
+ await fs3.promises.unlink(tarballPath);
1484
+ const binPath = getCodeServerBin();
1485
+ if (fs3.existsSync(binPath)) {
1486
+ await fs3.promises.chmod(binPath, 493);
1487
+ }
1488
+ console.log(pc2.green("\u2713 code-server installed successfully"));
1489
+ }
1490
+ async function killExistingCodeServer() {
1491
+ try {
1492
+ if (process.platform === "win32") {
1493
+ await execAsync2("netstat -ano | findstr :8081 | findstr LISTENING").then(async ({ stdout }) => {
1494
+ const pid = stdout.trim().split(/\s+/).pop();
1495
+ if (pid) await execAsync2(`taskkill /PID ${pid} /F`);
1496
+ }).catch(() => {
1497
+ });
1498
+ } else {
1499
+ await execAsync2("lsof -ti:8081").then(async ({ stdout }) => {
1500
+ const pid = stdout.trim();
1501
+ if (pid) await execAsync2(`kill -9 ${pid}`);
1502
+ }).catch(() => {
1503
+ });
1504
+ }
1505
+ } catch {
1506
+ }
1507
+ }
1508
+ async function startCodeServer(cwd) {
1509
+ const binPath = getCodeServerBin();
1510
+ const workDir = cwd || process.cwd();
1511
+ if (!fs3.existsSync(binPath)) {
1512
+ throw new Error("code-server is not installed. Run installCodeServer() first.");
1513
+ }
1514
+ await killExistingCodeServer();
1515
+ const workspaceStoragePath = path9.join(STORAGE_DIR, "User", "workspaceStorage");
1516
+ path9.join(STORAGE_DIR, "User", "globalStorage");
1517
+ try {
1518
+ if (fs3.existsSync(workspaceStoragePath)) {
1519
+ await fs3.promises.rm(workspaceStoragePath, { recursive: true, force: true });
1520
+ }
1521
+ const stateDbPath = path9.join(STORAGE_DIR, "User", "globalStorage", "state.vscdb");
1522
+ if (fs3.existsSync(stateDbPath)) {
1523
+ await fs3.promises.unlink(stateDbPath);
1524
+ }
1525
+ } catch {
1526
+ }
1527
+ console.log(pc2.cyan(`Starting code-server in ${workDir}...`));
1528
+ const codeServer = spawn(
1529
+ binPath,
1530
+ [
1531
+ "--port",
1532
+ "8081",
1533
+ "--host",
1534
+ "0.0.0.0",
1535
+ "--auth",
1536
+ "none",
1537
+ "--user-data-dir",
1538
+ STORAGE_DIR,
1539
+ "--disable-workspace-trust",
1540
+ workDir
1541
+ ],
1542
+ {
1543
+ stdio: ["ignore", "pipe", "pipe"]
1544
+ }
1545
+ );
1546
+ console.log(pc2.green(`\u2713 code-server running at http://localhost:8081/?folder=${encodeURIComponent(workDir)}`));
1547
+ return codeServer;
1548
+ }
1549
+ if (process.env.AMA_DAEMON === "1") {
1550
+ (async () => {
1551
+ try {
1552
+ if (!isCodeServerInstalled()) {
1553
+ console.log(pc2.cyan("First run detected. Setting up code-server..."));
1554
+ try {
1555
+ await installCodeServer();
1556
+ } catch (error) {
1557
+ console.error(pc2.red(`Failed to install code-server: ${error.message}`));
1558
+ console.log(pc2.yellow("Continuing without code-server..."));
1559
+ }
1560
+ }
1561
+ if (isCodeServerInstalled()) {
1562
+ try {
1563
+ const projectDir = process.cwd() || os2.homedir();
1564
+ await startCodeServer(projectDir);
1565
+ } catch (error) {
1566
+ console.error(pc2.red(`Failed to start code-server: ${error.message}`));
1567
+ }
1568
+ }
1569
+ await main();
1570
+ } catch (error) {
1571
+ console.error("Daemon error:", error);
1572
+ process.exit(1);
1573
+ }
1574
+ })();
1575
+ } else {
1576
+ console.error("This script should only be run as a daemon");
1577
+ process.exit(1);
1578
+ }