amai 0.0.10 → 0.0.11

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,16 +1,16 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  import WebSocket2 from 'ws';
3
3
  import { z } from 'zod';
4
- import fs5, { readFile, writeFile, stat, access, readdir, glob, unlink, mkdir } from 'fs/promises';
5
4
  import path10 from 'path';
6
- import fs4, { readdirSync } from 'fs';
5
+ import fs6, { readdirSync } from 'fs';
7
6
  import os3 from 'os';
8
- import { exec, spawn } from 'child_process';
9
- import { promisify } from 'util';
7
+ import fs7, { unlink, mkdir } from 'fs/promises';
10
8
  import pc3 from 'picocolors';
11
9
  import { Hono } from 'hono';
12
10
  import { serve } from '@hono/node-server';
13
11
  import { cors } from 'hono/cors';
12
+ import { exec } from 'child_process';
13
+ import { promisify } from 'util';
14
14
 
15
15
  var DEFAULT_SERVER_URL = "wss://ama-production-a628.up.railway.app";
16
16
  var AMA_DIR = path10.join(os3.homedir(), ".amai");
@@ -26,14 +26,14 @@ var ProjectRegistry = class {
26
26
  }
27
27
  load() {
28
28
  try {
29
- if (fs4.existsSync(REGISTRY_FILE)) {
30
- const data = fs4.readFileSync(REGISTRY_FILE, "utf8");
29
+ if (fs6.existsSync(REGISTRY_FILE)) {
30
+ const data = fs6.readFileSync(REGISTRY_FILE, "utf8");
31
31
  const parsed = JSON.parse(data);
32
32
  if (!Array.isArray(parsed)) {
33
33
  console.error("Invalid project registry format: expected array, got", typeof parsed);
34
34
  const backupFile = REGISTRY_FILE + ".backup." + Date.now();
35
- fs4.copyFileSync(REGISTRY_FILE, backupFile);
36
- fs4.unlinkSync(REGISTRY_FILE);
35
+ fs6.copyFileSync(REGISTRY_FILE, backupFile);
36
+ fs6.unlinkSync(REGISTRY_FILE);
37
37
  return;
38
38
  }
39
39
  const projects = parsed;
@@ -46,11 +46,11 @@ var ProjectRegistry = class {
46
46
  }
47
47
  } catch (error) {
48
48
  console.error("Failed to load project registry:", error);
49
- if (fs4.existsSync(REGISTRY_FILE)) {
49
+ if (fs6.existsSync(REGISTRY_FILE)) {
50
50
  try {
51
51
  const backupFile = REGISTRY_FILE + ".backup." + Date.now();
52
- fs4.copyFileSync(REGISTRY_FILE, backupFile);
53
- fs4.unlinkSync(REGISTRY_FILE);
52
+ fs6.copyFileSync(REGISTRY_FILE, backupFile);
53
+ fs6.unlinkSync(REGISTRY_FILE);
54
54
  console.log("Corrupted registry file backed up and removed. Starting fresh.");
55
55
  } catch (backupError) {
56
56
  }
@@ -59,11 +59,11 @@ var ProjectRegistry = class {
59
59
  }
60
60
  save() {
61
61
  try {
62
- if (!fs4.existsSync(AMA_DIR)) {
63
- fs4.mkdirSync(AMA_DIR, { recursive: true });
62
+ if (!fs6.existsSync(AMA_DIR)) {
63
+ fs6.mkdirSync(AMA_DIR, { recursive: true });
64
64
  }
65
65
  const projects = Array.from(this.projects.values());
66
- fs4.writeFileSync(REGISTRY_FILE, JSON.stringify(projects, null, 2), "utf8");
66
+ fs6.writeFileSync(REGISTRY_FILE, JSON.stringify(projects, null, 2), "utf8");
67
67
  } catch (error) {
68
68
  console.error("Failed to save project registry:", error);
69
69
  }
@@ -152,6 +152,60 @@ z.object({
152
152
  ),
153
153
  end_line_one_indexed: z.number().optional().describe("The one-indexed line number to end reading at (inclusive).")
154
154
  });
155
+ async function readFileContent(absolute_file_path, relative_file_path, should_read_entire_file, start_line_one_indexed, end_line_one_indexed) {
156
+ const file = Bun.file(absolute_file_path);
157
+ const exists = await file.exists();
158
+ if (!exists) {
159
+ return {
160
+ success: false,
161
+ message: `File not found: ${relative_file_path}`,
162
+ error: "FILE_NOT_FOUND"
163
+ };
164
+ }
165
+ try {
166
+ const fileContent = await file.text();
167
+ const lines = fileContent.split(/\r?\n/);
168
+ const totalLines = lines.length;
169
+ if (should_read_entire_file) {
170
+ return {
171
+ success: true,
172
+ message: `Successfully read entire file: ${relative_file_path} (${totalLines} lines)`,
173
+ content: fileContent,
174
+ totalLines
175
+ };
176
+ }
177
+ const startIndex = start_line_one_indexed - 1;
178
+ if (startIndex >= totalLines) {
179
+ return {
180
+ success: false,
181
+ message: "start_line_one_indexed must be less than or equal to the total number of lines in the file",
182
+ error: "INVALID_LINE_RANGE"
183
+ };
184
+ }
185
+ const normalizedEnd = Math.min(end_line_one_indexed, totalLines);
186
+ const selectedLines = lines.slice(startIndex, normalizedEnd).join("\n");
187
+ const linesRead = normalizedEnd - start_line_one_indexed + 1;
188
+ return {
189
+ success: true,
190
+ message: `Successfully read lines ${start_line_one_indexed}-${normalizedEnd} from file: ${relative_file_path} (${linesRead} lines of ${totalLines} total)`,
191
+ content: selectedLines,
192
+ totalLines
193
+ };
194
+ } catch (error) {
195
+ if (error?.code === "EISDIR") {
196
+ return {
197
+ success: false,
198
+ message: `Path is not a file: ${relative_file_path}`,
199
+ error: "NOT_A_FILE"
200
+ };
201
+ }
202
+ return {
203
+ success: false,
204
+ message: `Failed to read file: ${relative_file_path}`,
205
+ error: "READ_ERROR"
206
+ };
207
+ }
208
+ }
155
209
  var read_file = async function(input, projectCwd) {
156
210
  const { relative_file_path, should_read_entire_file, start_line_one_indexed, end_line_one_indexed } = input;
157
211
  try {
@@ -192,6 +246,7 @@ var read_file = async function(input, projectCwd) {
192
246
  };
193
247
  }
194
248
  }
249
+ let absolute_file_path;
195
250
  if (projectCwd) {
196
251
  const validation = validatePath(relative_file_path, projectCwd);
197
252
  if (!validation.valid) {
@@ -201,128 +256,17 @@ var read_file = async function(input, projectCwd) {
201
256
  error: "ACCESS_DENIED"
202
257
  };
203
258
  }
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
- }
259
+ absolute_file_path = validation.resolvedPath;
264
260
  } else {
265
- const absolute_file_path = path10.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
- }
261
+ absolute_file_path = path10.resolve(relative_file_path);
262
+ }
263
+ return await readFileContent(
264
+ absolute_file_path,
265
+ relative_file_path,
266
+ should_read_entire_file,
267
+ start_line_one_indexed,
268
+ end_line_one_indexed
269
+ );
326
270
  } catch {
327
271
  return {
328
272
  success: false,
@@ -413,13 +357,13 @@ var Diff = class {
413
357
  editLength++;
414
358
  };
415
359
  if (callback) {
416
- (function exec3() {
360
+ (function exec2() {
417
361
  setTimeout(function() {
418
362
  if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
419
363
  return callback(void 0);
420
364
  }
421
365
  if (!execEditLength()) {
422
- exec3();
366
+ exec2();
423
367
  }
424
368
  }, 0);
425
369
  })();
@@ -646,17 +590,19 @@ var apply_patch = async function(input, projectCwd) {
646
590
  }
647
591
  const basePath = projectCwd || process.cwd();
648
592
  const absolute_file_path = resolveProjectPath(file_path, basePath);
593
+ const file = Bun.file(absolute_file_path);
594
+ const exists = await file.exists();
595
+ if (!exists) {
596
+ return {
597
+ success: false,
598
+ message: `File not found: ${file_path}`,
599
+ error: "FILE_NOT_FOUND"
600
+ };
601
+ }
649
602
  let fileContent;
650
603
  try {
651
- fileContent = await readFile(absolute_file_path, "utf-8");
604
+ fileContent = await file.text();
652
605
  } 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
606
  return {
661
607
  success: false,
662
608
  message: `Failed to read file: ${file_path}`,
@@ -680,7 +626,7 @@ var apply_patch = async function(input, projectCwd) {
680
626
  }
681
627
  const newContent = fileContent.replace(old_string, new_string);
682
628
  try {
683
- await writeFile(absolute_file_path, newContent, "utf-8");
629
+ await Bun.write(absolute_file_path, newContent);
684
630
  const diffStats = calculateDiffStats(fileContent, newContent);
685
631
  return {
686
632
  success: true,
@@ -729,25 +675,24 @@ var editFiles = async function(input, projectCwd) {
729
675
  await mkdir(dirPath, { recursive: true });
730
676
  let isNewFile = providedNewFile;
731
677
  let existingContent = "";
678
+ const file = Bun.file(filePath);
732
679
  if (isNewFile === void 0) {
733
- try {
734
- existingContent = await fs4.promises.readFile(filePath, "utf-8");
680
+ const exists = await file.exists();
681
+ if (exists) {
682
+ existingContent = await file.text();
735
683
  isNewFile = false;
736
- } catch (error) {
684
+ } else {
737
685
  isNewFile = true;
738
686
  }
739
687
  } else if (!isNewFile) {
740
- try {
741
- existingContent = await fs4.promises.readFile(filePath, "utf-8");
742
- } catch (error) {
688
+ const exists = await file.exists();
689
+ if (exists) {
690
+ existingContent = await file.text();
691
+ } else {
743
692
  isNewFile = true;
744
693
  }
745
694
  }
746
- try {
747
- await fs4.promises.writeFile(filePath, content);
748
- } catch (writeError) {
749
- throw writeError;
750
- }
695
+ await Bun.write(filePath, content);
751
696
  const diffStats = calculateDiffStats(existingContent, content);
752
697
  if (isNewFile) {
753
698
  return {
@@ -810,25 +755,31 @@ var deleteFile = async function(input, projectCwd) {
810
755
  error: "INVALID_FILE_PATH"
811
756
  };
812
757
  }
813
- const originalContent = await readFile(absolute_file_path);
814
- if (originalContent === void 0) {
758
+ const file = Bun.file(absolute_file_path);
759
+ const exists = await file.exists();
760
+ if (!exists) {
815
761
  return {
816
762
  success: false,
817
- message: `Failed to read file before deletion: ${realPath}`,
818
- error: "READ_ERROR"
763
+ message: `File not found: ${realPath}`,
764
+ error: "FILE_NOT_FOUND"
819
765
  };
820
766
  }
821
- const deleteResult = await unlink(absolute_file_path).catch(() => {
767
+ let originalContent;
768
+ try {
769
+ originalContent = await file.text();
770
+ } catch {
822
771
  return {
823
772
  success: false,
824
773
  message: `Failed to read file before deletion: ${realPath}`,
825
- error: "DELETE_ERROR"
774
+ error: "READ_ERROR"
826
775
  };
827
- });
828
- if (!deleteResult?.success) {
776
+ }
777
+ try {
778
+ await unlink(absolute_file_path);
779
+ } catch {
829
780
  return {
830
781
  success: false,
831
- message: `Failed to delete file before deletion: ${realPath}`,
782
+ message: `Failed to delete file: ${realPath}`,
832
783
  error: "DELETE_ERROR"
833
784
  };
834
785
  }
@@ -845,72 +796,218 @@ var deleteFile = async function(input, projectCwd) {
845
796
  };
846
797
  }
847
798
  };
799
+ var GREP_LIMITS = {
800
+ DEFAULT_MAX_MATCHES: 200,
801
+ MAX_LINE_LENGTH: 500,
802
+ MAX_TOTAL_OUTPUT_SIZE: 1 * 1024 * 1024,
803
+ TRUNCATION_MESSAGE: "\n[Results truncated due to size limits. Use more specific patterns or file filters to narrow your search.]"
804
+ };
848
805
  z.object({
849
806
  query: z.string().describe("The regex pattern to search for"),
850
807
  options: z.object({
851
808
  includePattern: z.string().optional().describe('Glob pattern for files to include (e.g., "*.ts")'),
852
809
  excludePattern: z.string().optional().describe("Glob pattern for files to exclude"),
853
- caseSensitive: z.boolean().optional().describe("Whether the search should be case sensitive")
854
- })
810
+ caseSensitive: z.boolean().optional().describe("Whether the search should be case sensitive"),
811
+ path: z.string().optional().describe("Subdirectory to search in")
812
+ }).optional()
855
813
  });
856
- var execAsync = promisify(exec);
814
+ async function getRipgrepPath() {
815
+ const paths = [
816
+ "/opt/homebrew/bin/rg",
817
+ "/usr/local/bin/rg",
818
+ "/usr/bin/rg",
819
+ "rg"
820
+ // Fallback to PATH
821
+ ];
822
+ for (const rgPath of paths) {
823
+ try {
824
+ const proc = Bun.spawn(["which", rgPath], { stdout: "pipe", stderr: "pipe" });
825
+ await proc.exited;
826
+ if (proc.exitCode === 0) {
827
+ return rgPath;
828
+ }
829
+ } catch {
830
+ continue;
831
+ }
832
+ }
833
+ return "rg";
834
+ }
835
+ async function getMtimesBatched(files) {
836
+ const mtimeMap = /* @__PURE__ */ new Map();
837
+ const BATCH_SIZE = 50;
838
+ for (let i = 0; i < files.length; i += BATCH_SIZE) {
839
+ const batch = files.slice(i, i + BATCH_SIZE);
840
+ const results = await Promise.all(
841
+ batch.map(async (filePath) => {
842
+ const mtime = await Bun.file(filePath).stat().then((stats) => stats.mtime.getTime()).catch(() => 0);
843
+ return { path: filePath, mtime };
844
+ })
845
+ );
846
+ results.forEach(({ path: path13, mtime }) => mtimeMap.set(path13, mtime));
847
+ }
848
+ return mtimeMap;
849
+ }
857
850
  var grepTool = async function(input, projectCwd) {
858
851
  const { query, options } = input;
852
+ if (!query || query.trim() === "") {
853
+ return {
854
+ success: false,
855
+ message: "Missing required parameter: query",
856
+ error: "MISSING_QUERY"
857
+ };
858
+ }
859
859
  try {
860
- const { includePattern, excludePattern: excludePattern2, caseSensitive } = options || {};
861
- const searchDir = projectCwd || process.cwd();
862
- if (projectCwd && !path10.isAbsolute(projectCwd)) {
860
+ const { includePattern, excludePattern, caseSensitive, path: subPath } = options || {};
861
+ let searchDir = projectCwd || process.cwd();
862
+ if (subPath) {
863
+ searchDir = path10.isAbsolute(subPath) ? subPath : path10.resolve(searchDir, subPath);
864
+ if (projectCwd) {
865
+ const validation = validatePath(subPath, projectCwd);
866
+ if (!validation.valid) {
867
+ return {
868
+ success: false,
869
+ message: validation.error || "Path validation failed",
870
+ error: "ACCESS_DENIED"
871
+ };
872
+ }
873
+ }
874
+ }
875
+ if (!fs6.existsSync(searchDir)) {
863
876
  return {
864
877
  success: false,
865
- message: "Invalid project directory",
866
- error: "INVALID_PROJECT_DIR"
878
+ message: `Directory not found: ${searchDir}`,
879
+ error: "DIR_NOT_FOUND"
867
880
  };
868
881
  }
869
- let command = `rg -n --with-filename "${query}"`;
870
- if (caseSensitive) {
871
- command += " -i";
882
+ const rgPath = await getRipgrepPath();
883
+ const args = [
884
+ "-n",
885
+ // Line numbers
886
+ "--with-filename",
887
+ // Always show filename
888
+ "--no-heading",
889
+ // Don't group by file
890
+ "--color=never",
891
+ // No ANSI colors
892
+ "--max-count=100",
893
+ // Max matches per file
894
+ "--max-columns=1000"
895
+ // Truncate long lines
896
+ ];
897
+ if (!caseSensitive) {
898
+ args.push("-i");
872
899
  }
873
900
  if (includePattern) {
874
- command += ` --glob "${includePattern}"`;
875
- }
876
- if (excludePattern2) {
877
- command += ` --glob "!${excludePattern2}"`;
878
- }
879
- command += ` --max-count 50`;
880
- command += ` "${searchDir}"`;
881
- const { stdout } = await execAsync(command);
882
- const rawMatches = stdout.trim().split("\n").filter((line) => line.length > 0);
883
- const detailedMatches = [];
884
- const matches = [];
885
- for (const rawMatch of rawMatches) {
886
- const colonIndex = rawMatch.indexOf(":");
887
- const secondColonIndex = rawMatch.indexOf(":", colonIndex + 1);
888
- if (colonIndex > 0 && secondColonIndex > colonIndex) {
889
- const file = rawMatch.substring(0, colonIndex);
890
- const lineNumber = parseInt(rawMatch.substring(colonIndex + 1, secondColonIndex), 10);
891
- let content = rawMatch.substring(secondColonIndex + 1);
892
- if (content.length > 250) {
893
- content = content.substring(0, 250) + "...";
901
+ args.push("--glob", includePattern);
902
+ }
903
+ if (excludePattern) {
904
+ args.push("--glob", `!${excludePattern}`);
905
+ }
906
+ args.push("--glob", "!node_modules/**");
907
+ args.push("--glob", "!.git/**");
908
+ args.push("--glob", "!dist/**");
909
+ args.push("--glob", "!build/**");
910
+ args.push("--glob", "!*.min.js");
911
+ args.push("--glob", "!*.min.css");
912
+ args.push("--glob", "!package-lock.json");
913
+ args.push("--glob", "!yarn.lock");
914
+ args.push("--glob", "!bun.lockb");
915
+ args.push("--regexp", query);
916
+ args.push(searchDir);
917
+ const proc = Bun.spawn([rgPath, ...args], {
918
+ stdout: "pipe",
919
+ stderr: "pipe"
920
+ });
921
+ const stdout = await new Response(proc.stdout).text();
922
+ const stderr = await new Response(proc.stderr).text();
923
+ const exitCode = await proc.exited;
924
+ if (exitCode === 1) {
925
+ return {
926
+ success: true,
927
+ matches: [],
928
+ detailedMatches: [],
929
+ query,
930
+ matchCount: 0,
931
+ message: `No matches found for pattern: ${query}`
932
+ };
933
+ }
934
+ if (exitCode !== 0) {
935
+ return {
936
+ success: false,
937
+ message: `Ripgrep error: ${stderr || "Unknown error"}`,
938
+ error: "GREP_EXEC_ERROR"
939
+ };
940
+ }
941
+ const lines = stdout.trim().split("\n").filter((line) => line.length > 0);
942
+ const rawMatches = [];
943
+ const uniqueFiles = /* @__PURE__ */ new Set();
944
+ for (const line of lines) {
945
+ const firstColon = line.indexOf(":");
946
+ const secondColon = line.indexOf(":", firstColon + 1);
947
+ if (firstColon > 0 && secondColon > firstColon) {
948
+ const file = line.substring(0, firstColon);
949
+ const lineNumber = parseInt(line.substring(firstColon + 1, secondColon), 10);
950
+ let content = line.substring(secondColon + 1);
951
+ if (content.length > GREP_LIMITS.MAX_LINE_LENGTH) {
952
+ content = content.substring(0, GREP_LIMITS.MAX_LINE_LENGTH) + "...";
894
953
  }
895
- detailedMatches.push({
954
+ rawMatches.push({
896
955
  file,
897
956
  lineNumber,
898
- content
957
+ content: content.trim(),
958
+ mtime: 0
899
959
  });
900
- matches.push(`${file}:${lineNumber}:${content}`);
901
- } else {
902
- matches.push(rawMatch);
960
+ uniqueFiles.add(file);
903
961
  }
904
962
  }
963
+ const mtimeMap = await getMtimesBatched(Array.from(uniqueFiles));
964
+ for (const match of rawMatches) {
965
+ match.mtime = mtimeMap.get(match.file) || 0;
966
+ }
967
+ rawMatches.sort((a, b) => {
968
+ if (b.mtime !== a.mtime) {
969
+ return b.mtime - a.mtime;
970
+ }
971
+ return a.file.localeCompare(b.file);
972
+ });
973
+ const truncated = rawMatches.length > GREP_LIMITS.DEFAULT_MAX_MATCHES;
974
+ const finalMatches = truncated ? rawMatches.slice(0, GREP_LIMITS.DEFAULT_MAX_MATCHES) : rawMatches;
975
+ const detailedMatches = finalMatches.map((m) => ({
976
+ file: m.file,
977
+ lineNumber: m.lineNumber,
978
+ content: m.content
979
+ }));
980
+ const matches = finalMatches.map(
981
+ (m) => `${m.file}:${m.lineNumber}:${m.content}`
982
+ );
983
+ const groupedOutput = [`Found ${finalMatches.length} matches`];
984
+ let currentFile = "";
985
+ for (const match of finalMatches) {
986
+ if (currentFile !== match.file) {
987
+ if (currentFile !== "") {
988
+ groupedOutput.push("");
989
+ }
990
+ currentFile = match.file;
991
+ groupedOutput.push(`${match.file}:`);
992
+ }
993
+ groupedOutput.push(` Line ${match.lineNumber}: ${match.content}`);
994
+ }
995
+ if (truncated) {
996
+ groupedOutput.push("");
997
+ groupedOutput.push(GREP_LIMITS.TRUNCATION_MESSAGE);
998
+ }
905
999
  return {
906
1000
  success: true,
907
1001
  matches,
908
1002
  detailedMatches,
909
1003
  query,
910
- matchCount: matches.length,
911
- message: `Found ${matches.length} matches for pattern: ${query}`
1004
+ matchCount: finalMatches.length,
1005
+ truncated,
1006
+ message: `Found ${finalMatches.length} matches for pattern: ${query}`,
1007
+ content: groupedOutput.join("\n")
912
1008
  };
913
1009
  } catch (error) {
1010
+ console.error("[grep] error:", error);
914
1011
  return {
915
1012
  success: false,
916
1013
  message: error?.message || String(error),
@@ -919,9 +1016,25 @@ var grepTool = async function(input, projectCwd) {
919
1016
  }
920
1017
  };
921
1018
  z.object({
922
- pattern: z.string().describe('Glob pattern (e.g., "**/*.js")'),
923
- path: z.string().optional().describe("Relative directory path to search in")
1019
+ pattern: z.string().describe('Glob pattern to match files (e.g., "**/*.js", "src/**/*.ts", "*.json"). Supports standard glob syntax with *, **, and ? wildcards'),
1020
+ path: z.string().optional().describe("Optional relative directory path within the project to limit the search scope. If not provided, searches from the project root")
924
1021
  });
1022
+ var RESULT_LIMIT = 100;
1023
+ var MTIME_BATCH_SIZE = 50;
1024
+ async function getMtimesBatched2(files) {
1025
+ const results = [];
1026
+ for (let i = 0; i < files.length; i += MTIME_BATCH_SIZE) {
1027
+ const batch = files.slice(i, i + MTIME_BATCH_SIZE);
1028
+ const batchResults = await Promise.all(
1029
+ batch.map(async (filePath) => {
1030
+ const mtime = await Bun.file(filePath).stat().then((stats) => stats.mtime.getTime()).catch(() => 0);
1031
+ return { path: filePath, mtime };
1032
+ })
1033
+ );
1034
+ results.push(...batchResults);
1035
+ }
1036
+ return results;
1037
+ }
925
1038
  var globTool = async function(input, projectCwd) {
926
1039
  const { pattern, path: inputPath } = input;
927
1040
  if (!pattern) {
@@ -934,6 +1047,13 @@ var globTool = async function(input, projectCwd) {
934
1047
  try {
935
1048
  const basePath = projectCwd || process.cwd();
936
1049
  const searchPath = inputPath ? resolveProjectPath(inputPath, basePath) : basePath;
1050
+ if (!fs6.existsSync(searchPath)) {
1051
+ return {
1052
+ success: false,
1053
+ message: `Directory not found: ${searchPath}`,
1054
+ error: "DIR_NOT_FOUND"
1055
+ };
1056
+ }
937
1057
  if (projectCwd && inputPath) {
938
1058
  const validation = validatePath(inputPath, projectCwd);
939
1059
  if (!validation.valid) {
@@ -944,21 +1064,49 @@ var globTool = async function(input, projectCwd) {
944
1064
  };
945
1065
  }
946
1066
  }
947
- const filesGenerator = glob(pattern, {
948
- cwd: searchPath
949
- });
1067
+ const glob = new Bun.Glob(pattern);
950
1068
  const files = [];
951
- for await (const file of filesGenerator) {
952
- files.push(file);
1069
+ let truncated = false;
1070
+ for await (const match of glob.scan({
1071
+ cwd: searchPath,
1072
+ absolute: true,
1073
+ onlyFiles: true,
1074
+ followSymlinks: false
1075
+ })) {
1076
+ if (match.includes("/node_modules/") || match.includes("/.git/")) {
1077
+ continue;
1078
+ }
1079
+ if (files.length >= RESULT_LIMIT) {
1080
+ truncated = true;
1081
+ break;
1082
+ }
1083
+ files.push(match);
1084
+ }
1085
+ const filesWithMtime = await getMtimesBatched2(files);
1086
+ filesWithMtime.sort((a, b) => b.mtime - a.mtime);
1087
+ const output = [];
1088
+ if (filesWithMtime.length === 0) {
1089
+ output.push("No files found");
1090
+ } else {
1091
+ output.push(...filesWithMtime.map((f) => f.path));
1092
+ if (truncated) {
1093
+ output.push("");
1094
+ output.push("(Results are truncated. Consider using a more specific path or pattern.)");
1095
+ }
953
1096
  }
954
1097
  const searchLocation = inputPath ? ` in "${inputPath}"` : " in current directory";
955
- const message = `Found ${files.length} matches for pattern "${pattern}"${searchLocation}`;
1098
+ const message = `Found ${filesWithMtime.length} matches for pattern "${pattern}"${searchLocation}`;
956
1099
  return {
957
1100
  success: true,
958
1101
  message,
959
- content: files
1102
+ metadata: {
1103
+ count: filesWithMtime.length,
1104
+ truncated
1105
+ },
1106
+ content: output.join("\n")
960
1107
  };
961
1108
  } catch (error) {
1109
+ console.error("[glob] error:", error);
962
1110
  return {
963
1111
  success: false,
964
1112
  message: `Failed to find files matching pattern: ${pattern}`,
@@ -966,59 +1114,120 @@ var globTool = async function(input, projectCwd) {
966
1114
  };
967
1115
  }
968
1116
  };
969
- var excludePatterns = [
970
- "node_modules/",
971
- "__pycache__/",
972
- ".git/",
973
- "dist/",
974
- "build/",
975
- "target/",
976
- "vendor/",
977
- "bin/",
978
- "obj/",
979
- ".idea/",
980
- ".vscode/",
981
- ".zig-cache/",
1117
+ var IGNORE_PATTERNS = [
1118
+ "node_modules",
1119
+ "__pycache__",
1120
+ ".git",
1121
+ "dist",
1122
+ "build",
1123
+ "target",
1124
+ "vendor",
1125
+ "bin",
1126
+ "obj",
1127
+ ".idea",
1128
+ ".vscode",
1129
+ ".zig-cache",
982
1130
  "zig-out",
983
1131
  ".coverage",
984
- "coverage/",
985
- "vendor/",
986
- "tmp/",
987
- "temp/",
988
- ".cache/",
989
- "cache/",
990
- "logs/",
991
- ".venv/",
992
- "venv/",
993
- "env/"
1132
+ "coverage",
1133
+ "tmp",
1134
+ "temp",
1135
+ ".cache",
1136
+ "cache",
1137
+ "logs",
1138
+ ".venv",
1139
+ "venv",
1140
+ "env",
1141
+ ".next",
1142
+ ".turbo",
1143
+ ".vercel",
1144
+ ".output"
994
1145
  ];
995
- var excludePattern = excludePatterns.join("|");
1146
+ var RESULT_LIMIT2 = 500;
1147
+ var MTIME_BATCH_SIZE2 = 50;
996
1148
  z.object({
997
- path: z.string().optional(),
998
- recursive: z.boolean().optional().describe("Whether to list files recursively"),
999
- maxDepth: z.number().optional().describe("Maximum recursion depth (default: unlimited)"),
1149
+ path: z.string().optional().describe("Relative path to the directory to list"),
1150
+ recursive: z.boolean().optional().describe("Whether to list files recursively (default: true)"),
1151
+ maxDepth: z.number().optional().describe("Maximum recursion depth (default: 3)"),
1000
1152
  pattern: z.string().optional().describe("File extension (e.g., '.ts') or glob-like pattern"),
1001
- includeDirectories: z.boolean().optional().describe("Whether to include directories in results (default: true)"),
1002
- includeFiles: z.boolean().optional().describe("Whether to include files in results (default: true)")
1153
+ showHidden: z.boolean().optional().describe("Whether to show hidden files (default: false)")
1003
1154
  });
1004
- var list = async function(input, projectCwd) {
1005
- const { path: relativePath, recursive, maxDepth, pattern, includeDirectories, includeFiles } = input;
1006
- if (maxDepth !== void 0) {
1007
- if (!Number.isInteger(maxDepth) || maxDepth < 0) {
1008
- return {
1009
- success: false,
1010
- message: "maxDepth must be a non-negative integer",
1011
- error: "INVALID_MAX_DEPTH"
1012
- };
1155
+ function shouldIgnore(name, showHidden) {
1156
+ if (!showHidden && name.startsWith(".") && name !== ".") {
1157
+ return true;
1158
+ }
1159
+ return IGNORE_PATTERNS.includes(name);
1160
+ }
1161
+ function matchPattern(name, pattern) {
1162
+ if (!pattern) return true;
1163
+ if (pattern.startsWith(".") && !pattern.includes("*")) {
1164
+ return name.endsWith(pattern);
1165
+ }
1166
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1167
+ const regex = new RegExp(`^${escaped}$`, "i");
1168
+ return regex.test(name);
1169
+ }
1170
+ async function getMtimesBatched3(entries) {
1171
+ for (let i = 0; i < entries.length; i += MTIME_BATCH_SIZE2) {
1172
+ const batch = entries.slice(i, i + MTIME_BATCH_SIZE2);
1173
+ await Promise.all(
1174
+ batch.map(async (entry) => {
1175
+ entry.mtime = await Bun.file(entry.absolutePath).stat().then((stats) => stats.mtime.getTime()).catch(() => 0);
1176
+ })
1177
+ );
1178
+ }
1179
+ }
1180
+ function buildTreeOutput(entries, basePath) {
1181
+ const tree = /* @__PURE__ */ new Map();
1182
+ for (const entry of entries) {
1183
+ const dir = path10.dirname(entry.relativePath);
1184
+ const dirKey = dir === "." ? "" : dir;
1185
+ if (!tree.has(dirKey)) {
1186
+ tree.set(dirKey, []);
1187
+ }
1188
+ tree.get(dirKey).push(entry);
1189
+ }
1190
+ for (const [, items] of tree) {
1191
+ items.sort((a, b) => {
1192
+ if (a.type !== b.type) {
1193
+ return a.type === "directory" ? -1 : 1;
1194
+ }
1195
+ return a.name.localeCompare(b.name);
1196
+ });
1197
+ }
1198
+ const lines = [`${basePath}/`];
1199
+ function renderLevel(dirPath, indent) {
1200
+ const items = tree.get(dirPath) || [];
1201
+ for (let i = 0; i < items.length; i++) {
1202
+ const item = items[i];
1203
+ const isLast = i === items.length - 1;
1204
+ const prefix = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
1205
+ const childIndent = indent + (isLast ? " " : "\u2502 ");
1206
+ if (item.type === "directory") {
1207
+ lines.push(`${indent}${prefix}${item.name}/`);
1208
+ const childPath = dirPath ? `${dirPath}/${item.name}` : item.name;
1209
+ renderLevel(childPath, childIndent);
1210
+ } else {
1211
+ lines.push(`${indent}${prefix}${item.name}`);
1212
+ }
1013
1213
  }
1014
1214
  }
1015
- const includeFilesNormalized = includeFiles ?? true;
1016
- const includeDirectoriesNormalized = includeDirectories ?? true;
1017
- if (!includeFilesNormalized && !includeDirectoriesNormalized) {
1215
+ renderLevel("", "");
1216
+ return lines.join("\n");
1217
+ }
1218
+ var list = async function(input, projectCwd) {
1219
+ const {
1220
+ path: relativePath,
1221
+ recursive = true,
1222
+ maxDepth = 3,
1223
+ pattern,
1224
+ showHidden = false
1225
+ } = input;
1226
+ if (maxDepth !== void 0 && (!Number.isInteger(maxDepth) || maxDepth < 0)) {
1018
1227
  return {
1019
1228
  success: false,
1020
- message: "At least one of includeFiles or includeDirectories must be true",
1021
- error: "INVALID_INCLUDE_OPTIONS"
1229
+ message: "maxDepth must be a non-negative integer",
1230
+ error: "INVALID_MAX_DEPTH"
1022
1231
  };
1023
1232
  }
1024
1233
  try {
@@ -1034,88 +1243,119 @@ var list = async function(input, projectCwd) {
1034
1243
  };
1035
1244
  }
1036
1245
  }
1037
- try {
1038
- await access(absolutePath);
1039
- } catch {
1246
+ if (!fs6.existsSync(absolutePath)) {
1040
1247
  return {
1041
1248
  success: false,
1042
- message: `File does not exist: ${absolutePath}`,
1043
- error: "FILE_DOES_NOT_EXIST"
1249
+ message: `Directory not found: ${absolutePath}`,
1250
+ error: "DIR_NOT_FOUND"
1044
1251
  };
1045
1252
  }
1046
- const isDir = (await stat(absolutePath)).isDirectory();
1047
- if (!isDir) {
1253
+ const stats = fs6.statSync(absolutePath);
1254
+ if (!stats.isDirectory()) {
1048
1255
  return {
1049
1256
  success: false,
1050
- message: `File is not a directory: ${absolutePath}`,
1051
- error: "FILE_IS_NOT_A_DIRECTORY"
1257
+ message: `Path is not a directory: ${absolutePath}`,
1258
+ error: "NOT_A_DIRECTORY"
1052
1259
  };
1053
1260
  }
1054
1261
  const collected = [];
1055
- const patternMatcher = (() => {
1056
- if (!pattern) return null;
1057
- if (pattern.startsWith(".") && !pattern.includes("*") && !pattern.includes("?")) {
1058
- return (entryName) => entryName.endsWith(pattern);
1059
- }
1060
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1061
- const regex = new RegExp(`^${escaped}$`);
1062
- return (entryName) => regex.test(entryName);
1063
- })();
1064
- const matchPattern = (entryName) => {
1065
- if (!patternMatcher) return true;
1066
- return patternMatcher(entryName);
1067
- };
1068
- const maxDepthNormalized = recursive ? maxDepth ?? Infinity : 0;
1262
+ let truncated = false;
1069
1263
  const walk = async (currentDir, depth) => {
1070
- const entries = await readdir(currentDir, { withFileTypes: true });
1264
+ if (collected.length >= RESULT_LIMIT2) {
1265
+ truncated = true;
1266
+ return;
1267
+ }
1268
+ let entries;
1269
+ try {
1270
+ entries = fs6.readdirSync(currentDir, { withFileTypes: true });
1271
+ } catch {
1272
+ return;
1273
+ }
1274
+ entries.sort((a, b) => {
1275
+ if (a.isDirectory() !== b.isDirectory()) {
1276
+ return a.isDirectory() ? -1 : 1;
1277
+ }
1278
+ return a.name.localeCompare(b.name);
1279
+ });
1071
1280
  for (const entry of entries) {
1281
+ if (collected.length >= RESULT_LIMIT2) {
1282
+ truncated = true;
1283
+ break;
1284
+ }
1285
+ if (shouldIgnore(entry.name, showHidden)) {
1286
+ continue;
1287
+ }
1072
1288
  const entryAbsolutePath = path10.join(currentDir, entry.name);
1073
- const entryRelativePath = path10.relative(absolutePath, entryAbsolutePath) || ".";
1289
+ const entryRelativePath = path10.relative(absolutePath, entryAbsolutePath);
1074
1290
  if (entry.isDirectory()) {
1075
- const isExcluded = entry.name.match(excludePattern);
1076
- if (includeDirectoriesNormalized && matchPattern(entry.name) && !isExcluded) {
1077
- collected.push({
1078
- name: entry.name,
1079
- absolutePath: entryAbsolutePath,
1080
- relativePath: entryRelativePath,
1081
- type: "directory"
1082
- });
1083
- }
1084
- if (recursive && depth < maxDepthNormalized && !isExcluded) {
1291
+ collected.push({
1292
+ name: entry.name,
1293
+ absolutePath: entryAbsolutePath,
1294
+ relativePath: entryRelativePath,
1295
+ type: "directory",
1296
+ mtime: 0,
1297
+ depth
1298
+ });
1299
+ if (recursive && depth < maxDepth) {
1085
1300
  await walk(entryAbsolutePath, depth + 1);
1086
1301
  }
1087
1302
  } else if (entry.isFile()) {
1088
- if (includeFilesNormalized && matchPattern(entry.name) && !entry.name.match(excludePattern)) {
1303
+ if (matchPattern(entry.name, pattern)) {
1089
1304
  collected.push({
1090
1305
  name: entry.name,
1091
1306
  absolutePath: entryAbsolutePath,
1092
1307
  relativePath: entryRelativePath,
1093
- type: "file"
1308
+ type: "file",
1309
+ mtime: 0,
1310
+ depth
1094
1311
  });
1095
1312
  }
1096
1313
  }
1097
1314
  }
1098
1315
  };
1099
1316
  await walk(absolutePath, 0);
1317
+ await getMtimesBatched3(collected);
1100
1318
  const totalFiles = collected.filter((item) => item.type === "file").length;
1101
1319
  const totalDirectories = collected.filter((item) => item.type === "directory").length;
1102
- let message = `Successfully listed ${collected.length} items in: ${relativePath ?? absolutePath}`;
1320
+ const treeOutput = buildTreeOutput(collected, relativePath || path10.basename(absolutePath));
1321
+ let message = `Listed ${collected.length} items`;
1322
+ if (relativePath) {
1323
+ message += ` in "${relativePath}"`;
1324
+ }
1325
+ message += ` (${totalFiles} files, ${totalDirectories} directories)`;
1103
1326
  if (recursive) {
1104
- message += ` (recursive${maxDepth !== void 0 ? `, max depth ${maxDepth}` : ""})`;
1327
+ message += ` [depth: ${maxDepth}]`;
1105
1328
  }
1106
1329
  if (pattern) {
1107
- message += ` (filtered by pattern: ${pattern})`;
1330
+ message += ` [filter: ${pattern}]`;
1108
1331
  }
1109
- message += ` - ${totalFiles} files, ${totalDirectories} directories`;
1332
+ if (truncated) {
1333
+ message += ` [TRUNCATED at ${RESULT_LIMIT2} items]`;
1334
+ }
1335
+ const files = collected.map((item) => ({
1336
+ name: item.name,
1337
+ path: item.relativePath,
1338
+ type: item.type
1339
+ }));
1110
1340
  return {
1111
1341
  success: true,
1112
1342
  message,
1113
- files: collected
1343
+ metadata: {
1344
+ totalFiles,
1345
+ totalDirectories,
1346
+ totalItems: collected.length,
1347
+ truncated,
1348
+ maxDepth,
1349
+ recursive
1350
+ },
1351
+ files,
1352
+ content: treeOutput
1114
1353
  };
1115
1354
  } catch (error) {
1355
+ console.error("[list] error:", error);
1116
1356
  return {
1117
1357
  success: false,
1118
- message: `Failed to list files: ${error}`,
1358
+ message: `Failed to list directory: ${error}`,
1119
1359
  error: "LIST_ERROR"
1120
1360
  };
1121
1361
  }
@@ -1131,19 +1371,19 @@ var startHttpServer = () => {
1131
1371
  var CREDENTIALS_DIR = path10.join(os3.homedir(), ".amai");
1132
1372
  var CREDENTIALS_PATH = path10.join(CREDENTIALS_DIR, "credentials.json");
1133
1373
  function getTokens() {
1134
- if (!fs4.existsSync(CREDENTIALS_PATH)) {
1374
+ if (!fs6.existsSync(CREDENTIALS_PATH)) {
1135
1375
  return null;
1136
1376
  }
1137
- const raw = fs4.readFileSync(CREDENTIALS_PATH, "utf8");
1377
+ const raw = fs6.readFileSync(CREDENTIALS_PATH, "utf8");
1138
1378
  const data = JSON.parse(raw);
1139
1379
  return data;
1140
1380
  }
1141
1381
  var getUserId = () => {
1142
1382
  try {
1143
- if (!fs4.existsSync(CREDENTIALS_PATH)) {
1383
+ if (!fs6.existsSync(CREDENTIALS_PATH)) {
1144
1384
  return;
1145
1385
  }
1146
- const raw = fs4.readFileSync(CREDENTIALS_PATH, "utf8");
1386
+ const raw = fs6.readFileSync(CREDENTIALS_PATH, "utf8");
1147
1387
  const data = JSON.parse(raw);
1148
1388
  return {
1149
1389
  userId: data.user.id
@@ -1192,7 +1432,7 @@ z.object({
1192
1432
  command: z.string().describe("The terminal command to execute (e.g., 'ls -la', 'pwd', 'echo $HOME')"),
1193
1433
  is_background: z.boolean().describe("Whether the command should be run in the background")
1194
1434
  }).merge(ExplanationSchema);
1195
- var runSecureTerminalCommand = async (command, timeout) => {
1435
+ var runSecureTerminalCommand = async (command, timeout, cwd) => {
1196
1436
  try {
1197
1437
  if (isHarmfulCommand(command)) {
1198
1438
  console.log(`[CLI] Harmful command detected: ${command}`);
@@ -1202,42 +1442,44 @@ var runSecureTerminalCommand = async (command, timeout) => {
1202
1442
  error: "HARMFUL_COMMAND_DETECTED"
1203
1443
  };
1204
1444
  }
1205
- return new Promise((resolve, reject) => {
1206
- const child = spawn(command, {
1207
- cwd: process.cwd(),
1208
- stdio: ["pipe", "pipe", "pipe"],
1209
- shell: true
1210
- });
1211
- let stdout = "";
1212
- let stderr = "";
1213
- let timeoutId = null;
1214
- if (timeoutId > 0) {
1215
- timeoutId = setTimeout(() => {
1216
- child.kill("SIGKILL");
1217
- reject(new Error(`Command timed out after ${timeout}ms`));
1218
- }, timeout);
1219
- }
1220
- child.stdout?.on("data", (data) => {
1221
- stdout += data.toString();
1222
- });
1223
- child.stderr?.on("data", (data) => {
1224
- stderr += data.toString();
1225
- });
1226
- child.stdout.on("close", (code) => {
1227
- if (timeoutId) {
1228
- clearTimeout(timeoutId);
1229
- }
1230
- resolve({ stdout, stderr, exitCode: code || 0 });
1231
- });
1232
- child.stderr.on("error", (error) => {
1233
- if (timeoutId) {
1234
- clearTimeout(timeoutId);
1235
- }
1236
- reject(error);
1237
- });
1445
+ const proc = Bun.spawn(["sh", "-c", command], {
1446
+ cwd: cwd || process.cwd(),
1447
+ stdout: "pipe",
1448
+ stderr: "pipe"
1238
1449
  });
1239
- } catch {
1240
- console.error("Error while ecexuting the securedShell command");
1450
+ let timedOut = false;
1451
+ let timeoutId = null;
1452
+ if (timeout > 0) {
1453
+ timeoutId = setTimeout(() => {
1454
+ timedOut = true;
1455
+ proc.kill();
1456
+ }, timeout);
1457
+ }
1458
+ const [stdout, stderr, exitCode] = await Promise.all([
1459
+ new Response(proc.stdout).text(),
1460
+ new Response(proc.stderr).text(),
1461
+ proc.exited
1462
+ ]);
1463
+ if (timeoutId) {
1464
+ clearTimeout(timeoutId);
1465
+ }
1466
+ if (timedOut) {
1467
+ return {
1468
+ success: false,
1469
+ message: `Command timed out after ${timeout}ms`,
1470
+ error: "TIMEOUT",
1471
+ stdout,
1472
+ stderr
1473
+ };
1474
+ }
1475
+ return { stdout, stderr, exitCode };
1476
+ } catch (error) {
1477
+ console.error("Error while executing the securedShell command", error);
1478
+ return {
1479
+ success: false,
1480
+ message: "Error while executing the securedShell command",
1481
+ error: error.message
1482
+ };
1241
1483
  }
1242
1484
  };
1243
1485
  var runTerminalCommand = async (input, projectCwd) => {
@@ -1251,13 +1493,12 @@ var runTerminalCommand = async (input, projectCwd) => {
1251
1493
  error: "HARMFUL_COMMAND_DETECTED"
1252
1494
  };
1253
1495
  }
1254
- const child = spawn(input.command, {
1255
- cwd: projectCwd,
1256
- detached: true,
1257
- stdio: "ignore",
1258
- shell: true
1496
+ const proc = Bun.spawn(["sh", "-c", input.command], {
1497
+ cwd: projectCwd || process.cwd(),
1498
+ stdout: "ignore",
1499
+ stderr: "ignore"
1259
1500
  });
1260
- child.unref();
1501
+ proc.unref();
1261
1502
  console.log(`[LOCAL] Background command started: ${input.command}`);
1262
1503
  return {
1263
1504
  success: true,
@@ -1267,9 +1508,13 @@ var runTerminalCommand = async (input, projectCwd) => {
1267
1508
  } else {
1268
1509
  const result = await runSecureTerminalCommand(
1269
1510
  input.command,
1270
- 3e4
1511
+ 3e4,
1271
1512
  // 30 second timeout
1513
+ projectCwd
1272
1514
  );
1515
+ if (result?.error && !result?.exitCode) {
1516
+ return result;
1517
+ }
1273
1518
  const success = result?.exitCode === 0;
1274
1519
  return {
1275
1520
  success,
@@ -1288,7 +1533,7 @@ var runTerminalCommand = async (input, projectCwd) => {
1288
1533
  };
1289
1534
  }
1290
1535
  };
1291
- var ignoreFiles = ["node_modules", ".git", ".next", ".env", ".env.local", ".env.development.local", ".env.test.local", ".env.production.local"];
1536
+ var ignoreFiles = ["node_modules", ".git", ".next", ".env", ".env.local", ".env.development.local", ".env.test.local", ".env.production.local", ".output", ".turbo", ".vercel", ".next", ".tanstack", ".nitro", ".wrangler", ".alchemy", ".coverage", ".nyc_output", ".cache", "tmp", "temp", ".idea", ".vscode", ".zig-cache", "zig-out", ".coverage", "coverage", "logs", ".venv", "venv", "env", ".next", ".turbo", ".vercel", ".output", ".tanstack", ".nitro", ".wrangler", ".alchemy", ".coverage", ".nyc_output", ".cache", "tmp", "temp", ".idea", ".vscode", ".zig-cache", "zig-out", ".coverage", "coverage", "logs", ".venv", "venv", "env"];
1292
1537
  var getContext = (dir, base = dir, allFiles = []) => {
1293
1538
  const filePath = readdirSync(dir, { withFileTypes: true });
1294
1539
  for (const file of filePath) {
@@ -1310,28 +1555,34 @@ var IDE_PROJECTS_PATHS = {
1310
1555
  };
1311
1556
  function getWorkspaceStoragePath(ide) {
1312
1557
  const platform = os3.platform();
1313
- const appName = "Cursor" ;
1558
+ const appName = ide === "cursor" ? "Cursor" : "Code";
1559
+ const appNameLower = appName.toLowerCase();
1314
1560
  if (platform === "darwin") {
1315
1561
  return path10.join(HOME, "Library", "Application Support", appName, "User", "workspaceStorage");
1316
1562
  } else if (platform === "win32") {
1317
1563
  return path10.join(process.env.APPDATA || "", appName, "User", "workspaceStorage");
1318
1564
  } else {
1319
- return path10.join(HOME, ".config", appName, "User", "workspaceStorage");
1565
+ const capitalizedPath = path10.join(HOME, ".config", appName, "User", "workspaceStorage");
1566
+ const lowercasePath = path10.join(HOME, ".config", appNameLower, "User", "workspaceStorage");
1567
+ if (fs6.existsSync(capitalizedPath)) {
1568
+ return capitalizedPath;
1569
+ }
1570
+ return lowercasePath;
1320
1571
  }
1321
1572
  }
1322
1573
  function scanWorkspaceStorage(ide) {
1323
1574
  const projects = [];
1324
- const storagePath = getWorkspaceStoragePath();
1325
- if (!fs4.existsSync(storagePath)) {
1575
+ const storagePath = getWorkspaceStoragePath(ide);
1576
+ if (!fs6.existsSync(storagePath)) {
1326
1577
  return projects;
1327
1578
  }
1328
1579
  try {
1329
- const workspaces = fs4.readdirSync(storagePath);
1580
+ const workspaces = fs6.readdirSync(storagePath);
1330
1581
  for (const workspace of workspaces) {
1331
1582
  const workspaceJsonPath = path10.join(storagePath, workspace, "workspace.json");
1332
- if (fs4.existsSync(workspaceJsonPath)) {
1583
+ if (fs6.existsSync(workspaceJsonPath)) {
1333
1584
  try {
1334
- const content = fs4.readFileSync(workspaceJsonPath, "utf-8");
1585
+ const content = fs6.readFileSync(workspaceJsonPath, "utf-8");
1335
1586
  const data = JSON.parse(content);
1336
1587
  if (data.folder && typeof data.folder === "string") {
1337
1588
  let projectPath = data.folder;
@@ -1339,7 +1590,7 @@ function scanWorkspaceStorage(ide) {
1339
1590
  projectPath = projectPath.replace("file://", "");
1340
1591
  projectPath = decodeURIComponent(projectPath);
1341
1592
  }
1342
- if (fs4.existsSync(projectPath) && fs4.statSync(projectPath).isDirectory()) {
1593
+ if (fs6.existsSync(projectPath) && fs6.statSync(projectPath).isDirectory()) {
1343
1594
  projects.push({
1344
1595
  name: path10.basename(projectPath),
1345
1596
  path: projectPath,
@@ -1363,11 +1614,11 @@ var scanIdeProjects = async () => {
1363
1614
  const seenPaths = /* @__PURE__ */ new Set();
1364
1615
  const addProject = (projectPath, ide) => {
1365
1616
  try {
1366
- const resolvedPath = fs4.realpathSync(projectPath);
1367
- if (fs4.existsSync(resolvedPath) && fs4.statSync(resolvedPath).isDirectory() && !seenPaths.has(resolvedPath)) {
1617
+ const resolvedPath = fs6.realpathSync(projectPath);
1618
+ if (fs6.existsSync(resolvedPath) && fs6.statSync(resolvedPath).isDirectory() && !seenPaths.has(resolvedPath)) {
1368
1619
  const isIdeProjectsDir = Object.values(IDE_PROJECTS_PATHS).some((ideDir) => {
1369
1620
  try {
1370
- return fs4.realpathSync(ideDir) === resolvedPath;
1621
+ return fs6.realpathSync(ideDir) === resolvedPath;
1371
1622
  } catch {
1372
1623
  return false;
1373
1624
  }
@@ -1388,32 +1639,36 @@ var scanIdeProjects = async () => {
1388
1639
  for (const project of cursorProjects) {
1389
1640
  addProject(project.path, "cursor");
1390
1641
  }
1642
+ const vscodeProjects = scanWorkspaceStorage("vscode");
1643
+ for (const project of vscodeProjects) {
1644
+ addProject(project.path, "vscode");
1645
+ }
1391
1646
  for (const [ide, dirPath] of Object.entries(IDE_PROJECTS_PATHS)) {
1392
- if (ide === "cursor") continue;
1393
- if (fs4.existsSync(dirPath)) {
1394
- const projects = fs4.readdirSync(dirPath);
1647
+ if (ide === "cursor" || ide === "vscode") continue;
1648
+ if (fs6.existsSync(dirPath)) {
1649
+ const projects = fs6.readdirSync(dirPath);
1395
1650
  projects.forEach((project) => {
1396
1651
  const projectPath = path10.join(dirPath, project);
1397
1652
  try {
1398
- const stats = fs4.lstatSync(projectPath);
1653
+ const stats = fs6.lstatSync(projectPath);
1399
1654
  let actualPath = null;
1400
1655
  if (stats.isSymbolicLink()) {
1401
- actualPath = fs4.realpathSync(projectPath);
1656
+ actualPath = fs6.realpathSync(projectPath);
1402
1657
  } else if (stats.isFile()) {
1403
1658
  try {
1404
- let content = fs4.readFileSync(projectPath, "utf-8").trim();
1659
+ let content = fs6.readFileSync(projectPath, "utf-8").trim();
1405
1660
  if (content.startsWith("~/") || content === "~") {
1406
1661
  content = content.replace(/^~/, HOME);
1407
1662
  }
1408
1663
  const resolvedContent = path10.isAbsolute(content) ? content : path10.resolve(path10.dirname(projectPath), content);
1409
- if (fs4.existsSync(resolvedContent) && fs4.statSync(resolvedContent).isDirectory()) {
1410
- actualPath = fs4.realpathSync(resolvedContent);
1664
+ if (fs6.existsSync(resolvedContent) && fs6.statSync(resolvedContent).isDirectory()) {
1665
+ actualPath = fs6.realpathSync(resolvedContent);
1411
1666
  }
1412
1667
  } catch {
1413
1668
  return;
1414
1669
  }
1415
1670
  } else if (stats.isDirectory()) {
1416
- actualPath = fs4.realpathSync(projectPath);
1671
+ actualPath = fs6.realpathSync(projectPath);
1417
1672
  }
1418
1673
  if (actualPath) {
1419
1674
  addProject(actualPath, ide);
@@ -1437,7 +1692,7 @@ var Global;
1437
1692
  })(Global || (Global = {}));
1438
1693
 
1439
1694
  // src/snapshot/snapshot.ts
1440
- var execAsync2 = promisify(exec);
1695
+ var execAsync = promisify(exec);
1441
1696
  var Snapshot;
1442
1697
  ((Snapshot2) => {
1443
1698
  const log = {
@@ -1447,7 +1702,7 @@ var Snapshot;
1447
1702
  };
1448
1703
  async function runGit(command, options = {}) {
1449
1704
  try {
1450
- const { stdout, stderr } = await execAsync2(command, {
1705
+ const { stdout, stderr } = await execAsync(command, {
1451
1706
  cwd: options.cwd,
1452
1707
  env: { ...process.env, ...options.env },
1453
1708
  encoding: "utf-8",
@@ -1471,8 +1726,8 @@ var Snapshot;
1471
1726
  const worktree = project.cwd;
1472
1727
  const git = gitdir(projectId);
1473
1728
  try {
1474
- await fs5.mkdir(git, { recursive: true });
1475
- const gitExists = await fs5.access(path10.join(git, "HEAD")).then(() => true).catch(() => false);
1729
+ await fs7.mkdir(git, { recursive: true });
1730
+ const gitExists = await fs7.access(path10.join(git, "HEAD")).then(() => true).catch(() => false);
1476
1731
  if (!gitExists) {
1477
1732
  await runGit(`git init`, {
1478
1733
  env: { GIT_DIR: git, GIT_WORK_TREE: worktree }
@@ -1557,7 +1812,7 @@ var Snapshot;
1557
1812
  for (const file of newFiles) {
1558
1813
  const fullPath = path10.join(worktree, file);
1559
1814
  try {
1560
- await fs5.unlink(fullPath);
1815
+ await fs7.unlink(fullPath);
1561
1816
  log.info("deleted newly created file", { file: fullPath });
1562
1817
  } catch {
1563
1818
  }
@@ -1594,7 +1849,7 @@ var Snapshot;
1594
1849
  log.info("file existed in snapshot but checkout failed, keeping", { file });
1595
1850
  } else {
1596
1851
  log.info("file did not exist in snapshot, deleting", { file });
1597
- await fs5.unlink(file).catch(() => {
1852
+ await fs7.unlink(file).catch(() => {
1598
1853
  });
1599
1854
  }
1600
1855
  }