@travisennis/acai 0.0.4 → 0.0.5

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.
Files changed (316) hide show
  1. package/README.md +225 -6
  2. package/dist/api/exa/index.d.ts +177 -0
  3. package/dist/api/exa/index.d.ts.map +1 -0
  4. package/dist/api/exa/index.js +439 -0
  5. package/dist/cli.d.ts +3 -2
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/commands/application-log-command.d.ts +1 -0
  8. package/dist/commands/application-log-command.d.ts.map +1 -0
  9. package/dist/commands/application-log-command.js +5 -3
  10. package/dist/commands/clear-command.d.ts +1 -0
  11. package/dist/commands/clear-command.d.ts.map +1 -0
  12. package/dist/commands/clear-command.js +2 -3
  13. package/dist/commands/compact-command.d.ts +1 -0
  14. package/dist/commands/compact-command.d.ts.map +1 -0
  15. package/dist/commands/compact-command.js +1 -1
  16. package/dist/commands/copy-command.d.ts +1 -0
  17. package/dist/commands/copy-command.d.ts.map +1 -0
  18. package/dist/commands/copy-command.js +3 -2
  19. package/dist/commands/edit-command.d.ts +1 -0
  20. package/dist/commands/edit-command.d.ts.map +1 -0
  21. package/dist/commands/edit-command.js +7 -5
  22. package/dist/commands/edit-prompt-command.d.ts +2 -1
  23. package/dist/commands/edit-prompt-command.d.ts.map +1 -0
  24. package/dist/commands/edit-prompt-command.js +15 -7
  25. package/dist/commands/exit-command.d.ts +13 -2
  26. package/dist/commands/exit-command.d.ts.map +1 -0
  27. package/dist/commands/exit-command.js +14 -2
  28. package/dist/commands/files-command.d.ts +1 -0
  29. package/dist/commands/files-command.d.ts.map +1 -0
  30. package/dist/commands/files-command.js +9 -8
  31. package/dist/commands/generate-rules-command.d.ts +1 -0
  32. package/dist/commands/generate-rules-command.d.ts.map +1 -0
  33. package/dist/commands/generate-rules-command.js +4 -3
  34. package/dist/commands/health-command.d.ts +3 -1
  35. package/dist/commands/health-command.d.ts.map +1 -0
  36. package/dist/commands/health-command.js +42 -5
  37. package/dist/commands/help-command.d.ts +1 -0
  38. package/dist/commands/help-command.d.ts.map +1 -0
  39. package/dist/commands/help-command.js +2 -3
  40. package/dist/commands/init-command.d.ts +1 -0
  41. package/dist/commands/init-command.d.ts.map +1 -0
  42. package/dist/commands/init-command.js +1 -2
  43. package/dist/commands/last-log-command.d.ts +1 -0
  44. package/dist/commands/last-log-command.d.ts.map +1 -0
  45. package/dist/commands/last-log-command.js +12 -17
  46. package/dist/commands/list-tools-command.d.ts +3 -0
  47. package/dist/commands/list-tools-command.d.ts.map +1 -0
  48. package/dist/commands/list-tools-command.js +61 -0
  49. package/dist/commands/manager.d.ts +7 -2
  50. package/dist/commands/manager.d.ts.map +1 -0
  51. package/dist/commands/manager.js +43 -6
  52. package/dist/commands/model-command.d.ts +1 -0
  53. package/dist/commands/model-command.d.ts.map +1 -0
  54. package/dist/commands/model-command.js +5 -5
  55. package/dist/commands/paste-command.d.ts +1 -0
  56. package/dist/commands/paste-command.d.ts.map +1 -0
  57. package/dist/commands/paste-command.js +6 -5
  58. package/dist/commands/prompt-command.d.ts +2 -1
  59. package/dist/commands/prompt-command.d.ts.map +1 -0
  60. package/dist/commands/prompt-command.js +62 -8
  61. package/dist/commands/reset-command.d.ts +1 -0
  62. package/dist/commands/reset-command.d.ts.map +1 -0
  63. package/dist/commands/reset-command.js +1 -1
  64. package/dist/commands/rules-command.d.ts +1 -0
  65. package/dist/commands/rules-command.d.ts.map +1 -0
  66. package/dist/commands/rules-command.js +5 -3
  67. package/dist/commands/save-command.d.ts +1 -0
  68. package/dist/commands/save-command.d.ts.map +1 -0
  69. package/dist/commands/save-command.js +1 -1
  70. package/dist/commands/shell-command.d.ts +3 -0
  71. package/dist/commands/shell-command.d.ts.map +1 -0
  72. package/dist/commands/shell-command.js +60 -0
  73. package/dist/commands/types.d.ts +9 -6
  74. package/dist/commands/types.d.ts.map +1 -0
  75. package/dist/commands/usage-command.d.ts +1 -0
  76. package/dist/commands/usage-command.d.ts.map +1 -0
  77. package/dist/commands/usage-command.js +2 -3
  78. package/dist/config.d.ts +22 -34
  79. package/dist/config.d.ts.map +1 -0
  80. package/dist/config.js +61 -15
  81. package/dist/conversation-analyzer.d.ts +2 -1
  82. package/dist/conversation-analyzer.d.ts.map +1 -0
  83. package/dist/dedent.d.ts +1 -0
  84. package/dist/dedent.d.ts.map +1 -0
  85. package/dist/execution/index.d.ts +112 -0
  86. package/dist/execution/index.d.ts.map +1 -0
  87. package/dist/execution/index.js +432 -0
  88. package/dist/formatting.d.ts +2 -13
  89. package/dist/formatting.d.ts.map +1 -0
  90. package/dist/formatting.js +5 -64
  91. package/dist/index.d.ts +1 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +14 -4
  94. package/dist/logger.d.ts +1 -0
  95. package/dist/logger.d.ts.map +1 -0
  96. package/dist/mentions.d.ts +4 -0
  97. package/dist/mentions.d.ts.map +1 -0
  98. package/dist/mentions.js +42 -10
  99. package/dist/messages.d.ts +8 -20
  100. package/dist/messages.d.ts.map +1 -0
  101. package/dist/messages.js +33 -53
  102. package/dist/middleware/audit-message.d.ts +1 -0
  103. package/dist/middleware/audit-message.d.ts.map +1 -0
  104. package/dist/middleware/index.d.ts +1 -0
  105. package/dist/middleware/index.d.ts.map +1 -0
  106. package/dist/middleware/rate-limit.d.ts +1 -0
  107. package/dist/middleware/rate-limit.d.ts.map +1 -0
  108. package/dist/models/ai-config.d.ts +1 -0
  109. package/dist/models/ai-config.d.ts.map +1 -0
  110. package/dist/models/anthropic-provider.d.ts +1 -0
  111. package/dist/models/anthropic-provider.d.ts.map +1 -0
  112. package/dist/models/deepseek-provider.d.ts +1 -0
  113. package/dist/models/deepseek-provider.d.ts.map +1 -0
  114. package/dist/models/google-provider.d.ts +1 -0
  115. package/dist/models/google-provider.d.ts.map +1 -0
  116. package/dist/models/groq-provider.d.ts +20 -0
  117. package/dist/models/groq-provider.d.ts.map +1 -0
  118. package/dist/models/groq-provider.js +31 -0
  119. package/dist/models/manager.d.ts +1 -0
  120. package/dist/models/manager.d.ts.map +1 -0
  121. package/dist/models/openai-provider.d.ts +2 -1
  122. package/dist/models/openai-provider.d.ts.map +1 -0
  123. package/dist/models/openrouter-provider.d.ts +31 -22
  124. package/dist/models/openrouter-provider.d.ts.map +1 -0
  125. package/dist/models/openrouter-provider.js +115 -1
  126. package/dist/models/providers.d.ts +4 -5
  127. package/dist/models/providers.d.ts.map +1 -0
  128. package/dist/models/providers.js +7 -3
  129. package/dist/models/xai-provider.d.ts +1 -0
  130. package/dist/models/xai-provider.d.ts.map +1 -0
  131. package/dist/parsing.d.ts +2 -1
  132. package/dist/parsing.d.ts.map +1 -0
  133. package/dist/prompts/manager.d.ts +14 -2
  134. package/dist/prompts/manager.d.ts.map +1 -0
  135. package/dist/prompts.d.ts +1 -0
  136. package/dist/prompts.d.ts.map +1 -0
  137. package/dist/prompts.js +15 -11
  138. package/dist/repl/display-tool-messages.d.ts +4 -0
  139. package/dist/repl/display-tool-messages.d.ts.map +1 -0
  140. package/dist/repl/display-tool-messages.js +55 -0
  141. package/dist/repl/display-tool-use.d.ts +14 -0
  142. package/dist/repl/display-tool-use.d.ts.map +1 -0
  143. package/dist/repl/display-tool-use.js +63 -0
  144. package/dist/repl/get-prompt-header.d.ts +8 -0
  145. package/dist/repl/get-prompt-header.d.ts.map +1 -0
  146. package/dist/repl/get-prompt-header.js +38 -0
  147. package/dist/repl/tool-call-repair.d.ts +4 -0
  148. package/dist/repl/tool-call-repair.d.ts.map +1 -0
  149. package/dist/repl/tool-call-repair.js +50 -0
  150. package/dist/repl-prompt.d.ts +1 -0
  151. package/dist/repl-prompt.d.ts.map +1 -0
  152. package/dist/repl.d.ts +8 -4
  153. package/dist/repl.d.ts.map +1 -0
  154. package/dist/repl.js +108 -252
  155. package/dist/terminal/ansi-styles.d.ts +77 -0
  156. package/dist/terminal/ansi-styles.d.ts.map +1 -0
  157. package/dist/terminal/ansi-styles.js +215 -0
  158. package/dist/terminal/checkbox-prompt.d.ts +36 -0
  159. package/dist/terminal/checkbox-prompt.d.ts.map +1 -0
  160. package/dist/terminal/checkbox-prompt.js +362 -0
  161. package/dist/terminal/default-theme.d.ts +6 -0
  162. package/dist/terminal/default-theme.d.ts.map +1 -0
  163. package/dist/terminal/default-theme.js +182 -0
  164. package/dist/terminal/east-asian-width.d.ts +8 -0
  165. package/dist/terminal/east-asian-width.d.ts.map +1 -0
  166. package/dist/terminal/east-asian-width.js +409 -0
  167. package/dist/terminal/editor-prompt.d.ts +10 -0
  168. package/dist/terminal/editor-prompt.d.ts.map +1 -0
  169. package/dist/terminal/editor-prompt.js +61 -0
  170. package/dist/terminal/errors.d.ts +19 -0
  171. package/dist/terminal/errors.d.ts.map +1 -0
  172. package/dist/terminal/errors.js +37 -0
  173. package/dist/terminal/formatting.d.ts +1 -11
  174. package/dist/terminal/formatting.d.ts.map +1 -0
  175. package/dist/terminal/formatting.js +4 -20
  176. package/dist/terminal/highlight/index.d.ts +53 -0
  177. package/dist/terminal/highlight/index.d.ts.map +1 -0
  178. package/dist/terminal/highlight/index.js +90 -0
  179. package/dist/terminal/highlight/theme.d.ts +233 -0
  180. package/dist/terminal/highlight/theme.d.ts.map +1 -0
  181. package/dist/terminal/highlight/theme.js +83 -0
  182. package/dist/terminal/index.d.ts +16 -9
  183. package/dist/terminal/index.d.ts.map +1 -0
  184. package/dist/terminal/index.js +42 -126
  185. package/dist/terminal/input-prompt.d.ts +16 -0
  186. package/dist/terminal/input-prompt.d.ts.map +1 -0
  187. package/dist/terminal/input-prompt.js +181 -0
  188. package/dist/terminal/markdown-utils.d.ts +1 -0
  189. package/dist/terminal/markdown-utils.d.ts.map +1 -0
  190. package/dist/terminal/markdown.d.ts +1 -0
  191. package/dist/terminal/markdown.d.ts.map +1 -0
  192. package/dist/terminal/markdown.js +17 -12
  193. package/dist/terminal/search-prompt.d.ts +20 -0
  194. package/dist/terminal/search-prompt.d.ts.map +1 -0
  195. package/dist/terminal/search-prompt.js +279 -0
  196. package/dist/terminal/select-prompt.d.ts +26 -0
  197. package/dist/terminal/select-prompt.d.ts.map +1 -0
  198. package/dist/terminal/select-prompt.js +298 -0
  199. package/dist/terminal/string-width.d.ts +7 -0
  200. package/dist/terminal/string-width.d.ts.map +1 -0
  201. package/dist/terminal/string-width.js +61 -0
  202. package/dist/terminal/strip-ansi.d.ts +2 -0
  203. package/dist/terminal/strip-ansi.d.ts.map +1 -0
  204. package/dist/terminal/strip-ansi.js +20 -0
  205. package/dist/terminal/style.d.ts +191 -0
  206. package/dist/terminal/style.d.ts.map +1 -0
  207. package/dist/terminal/style.js +259 -0
  208. package/dist/terminal/supports-color.d.ts +1 -0
  209. package/dist/terminal/supports-color.d.ts.map +1 -0
  210. package/dist/terminal/supports-hyperlinks.d.ts +1 -3
  211. package/dist/terminal/supports-hyperlinks.d.ts.map +1 -0
  212. package/dist/terminal/supports-hyperlinks.js +1 -1
  213. package/dist/terminal/types.d.ts +1 -37
  214. package/dist/terminal/types.d.ts.map +1 -0
  215. package/dist/terminal/wrap-ansi.d.ts +8 -0
  216. package/dist/terminal/wrap-ansi.d.ts.map +1 -0
  217. package/dist/terminal/wrap-ansi.js +190 -0
  218. package/dist/{token-utils.d.ts → tokens/counter.d.ts} +1 -0
  219. package/dist/tokens/counter.d.ts.map +1 -0
  220. package/dist/{token-utils.js → tokens/counter.js} +1 -1
  221. package/dist/tokens/manage-output.d.ts +34 -0
  222. package/dist/tokens/manage-output.d.ts.map +1 -0
  223. package/dist/tokens/manage-output.js +44 -0
  224. package/dist/{token-tracker.d.ts → tokens/tracker.d.ts} +1 -0
  225. package/dist/tokens/tracker.d.ts.map +1 -0
  226. package/dist/tool-executor.d.ts +28 -0
  227. package/dist/tool-executor.d.ts.map +1 -0
  228. package/dist/tool-executor.js +74 -0
  229. package/dist/tools/agent.d.ts +3 -2
  230. package/dist/tools/agent.d.ts.map +1 -0
  231. package/dist/tools/agent.js +7 -4
  232. package/dist/tools/bash-utils.d.ts +7 -0
  233. package/dist/tools/bash-utils.d.ts.map +1 -0
  234. package/dist/tools/bash-utils.js +212 -0
  235. package/dist/tools/bash.d.ts +9 -7
  236. package/dist/tools/bash.d.ts.map +1 -0
  237. package/dist/tools/bash.js +95 -212
  238. package/dist/tools/code-interpreter.d.ts +1 -1
  239. package/dist/tools/code-interpreter.d.ts.map +1 -0
  240. package/dist/tools/code-interpreter.js +31 -96
  241. package/dist/tools/delete-file.d.ts +5 -3
  242. package/dist/tools/delete-file.d.ts.map +1 -0
  243. package/dist/tools/delete-file.js +47 -33
  244. package/dist/tools/directory-tree.d.ts +10 -1
  245. package/dist/tools/directory-tree.d.ts.map +1 -0
  246. package/dist/tools/directory-tree.js +91 -8
  247. package/dist/tools/dynamic-tool-loader.d.ts +12 -0
  248. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -0
  249. package/dist/tools/dynamic-tool-loader.js +280 -0
  250. package/dist/tools/dynamic-tool-parser.d.ts +20 -0
  251. package/dist/tools/dynamic-tool-parser.d.ts.map +1 -0
  252. package/dist/tools/dynamic-tool-parser.js +21 -0
  253. package/dist/tools/edit-file.d.ts +10 -2
  254. package/dist/tools/edit-file.d.ts.map +1 -0
  255. package/dist/tools/edit-file.js +117 -40
  256. package/dist/tools/file-editing-utils.d.ts +2 -0
  257. package/dist/tools/file-editing-utils.d.ts.map +1 -0
  258. package/dist/tools/file-editing-utils.js +135 -0
  259. package/dist/tools/filesystem-utils.d.ts +6 -21
  260. package/dist/tools/filesystem-utils.d.ts.map +1 -0
  261. package/dist/tools/filesystem-utils.js +96 -148
  262. package/dist/tools/git-utils.d.ts +1 -0
  263. package/dist/tools/git-utils.d.ts.map +1 -0
  264. package/dist/tools/grep.d.ts +5 -3
  265. package/dist/tools/grep.d.ts.map +1 -0
  266. package/dist/tools/grep.js +67 -27
  267. package/dist/tools/index.d.ts +10 -16
  268. package/dist/tools/index.d.ts.map +1 -0
  269. package/dist/tools/index.js +33 -22
  270. package/dist/tools/move-file.d.ts +1 -0
  271. package/dist/tools/move-file.d.ts.map +1 -0
  272. package/dist/tools/move-file.js +12 -5
  273. package/dist/tools/read-file.d.ts +2 -1
  274. package/dist/tools/read-file.d.ts.map +1 -0
  275. package/dist/tools/read-file.js +13 -6
  276. package/dist/tools/read-multiple-files.d.ts +2 -1
  277. package/dist/tools/read-multiple-files.d.ts.map +1 -0
  278. package/dist/tools/read-multiple-files.js +90 -9
  279. package/dist/tools/save-file.d.ts +5 -3
  280. package/dist/tools/save-file.d.ts.map +1 -0
  281. package/dist/tools/save-file.js +64 -36
  282. package/dist/tools/think.d.ts +1 -0
  283. package/dist/tools/think.d.ts.map +1 -0
  284. package/dist/tools/think.js +5 -1
  285. package/dist/tools/types.d.ts +14 -1
  286. package/dist/tools/types.d.ts.map +1 -0
  287. package/dist/tools/web-fetch.d.ts +4 -2
  288. package/dist/tools/web-fetch.d.ts.map +1 -0
  289. package/dist/tools/web-fetch.js +2 -2
  290. package/dist/tools/web-search.d.ts +2 -1
  291. package/dist/tools/web-search.d.ts.map +1 -0
  292. package/dist/tools/web-search.js +46 -11
  293. package/dist/utils/filesystem.d.ts +23 -0
  294. package/dist/utils/filesystem.d.ts.map +1 -0
  295. package/dist/utils/filesystem.js +140 -0
  296. package/dist/utils/filetype-detection.d.ts +3 -0
  297. package/dist/utils/filetype-detection.d.ts.map +1 -0
  298. package/dist/utils/filetype-detection.js +112 -0
  299. package/dist/utils/glob.d.ts +52 -0
  300. package/dist/utils/glob.d.ts.map +1 -0
  301. package/dist/utils/glob.js +376 -0
  302. package/dist/utils/ignore.d.ts +104 -0
  303. package/dist/utils/ignore.d.ts.map +1 -0
  304. package/dist/utils/ignore.js +649 -0
  305. package/dist/utils/process.d.ts +3 -2
  306. package/dist/utils/process.d.ts.map +1 -0
  307. package/dist/utils/process.js +17 -2
  308. package/dist/utils/zod-utils.d.ts +4 -0
  309. package/dist/utils/zod-utils.d.ts.map +1 -0
  310. package/dist/utils/zod-utils.js +7 -0
  311. package/dist/version.d.ts +1 -0
  312. package/dist/version.d.ts.map +1 -0
  313. package/package.json +32 -30
  314. package/dist/tools/command-validation.d.ts +0 -11
  315. package/dist/tools/command-validation.js +0 -45
  316. /package/dist/{token-tracker.js → tokens/tracker.js} +0 -0
@@ -0,0 +1,212 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { isPathWithinBaseDir } from "./filesystem-utils.js";
4
+ // Validate path arguments to ensure they're within the project
5
+ export function validatePaths(command, baseDir, cwd) {
6
+ // Simple tokenization - split on spaces but respect quotes
7
+ const tokens = [];
8
+ let current = "";
9
+ let inQuotes = false;
10
+ let quoteChar = "";
11
+ for (let i = 0; i < command.length; i++) {
12
+ const char = command[i] ?? "";
13
+ if ((char === '"' || char === "'") && !inQuotes) {
14
+ inQuotes = true;
15
+ quoteChar = char;
16
+ current += char;
17
+ }
18
+ else if (char === quoteChar && inQuotes) {
19
+ inQuotes = false;
20
+ quoteChar = "";
21
+ current += char;
22
+ }
23
+ else if (char === " " && !inQuotes) {
24
+ if (current) {
25
+ tokens.push(current);
26
+ current = "";
27
+ }
28
+ }
29
+ else {
30
+ current += char;
31
+ }
32
+ }
33
+ if (current)
34
+ tokens.push(current);
35
+ // Check each token that looks like a path
36
+ for (let i = 1; i < tokens.length; i++) {
37
+ // Skip the command itself
38
+ const token = tokens[i];
39
+ if (!token)
40
+ continue;
41
+ // Remove quotes for path checking
42
+ const cleanToken = token.replace(/^['"]|['"]$/g, "");
43
+ // Skip if it's clearly not a path
44
+ if (cleanToken.startsWith("-") ||
45
+ cleanToken.includes("://") ||
46
+ !cleanToken.includes("/")) {
47
+ continue;
48
+ }
49
+ // Skip git commit messages and other special cases
50
+ const prevToken = tokens[i - 1]?.replace(/^['"]|['"]$/g, "");
51
+ if (prevToken === "-m" || prevToken === "--message") {
52
+ continue;
53
+ }
54
+ try {
55
+ const resolvedPath = path.resolve(cwd, cleanToken);
56
+ if (!isPathWithinBaseDir(resolvedPath, baseDir)) {
57
+ return {
58
+ isValid: false,
59
+ error: `Path '${cleanToken}' resolves outside the project directory (${resolvedPath}). All paths must be within ${baseDir}`,
60
+ };
61
+ }
62
+ }
63
+ catch (_e) { }
64
+ }
65
+ return { isValid: true };
66
+ }
67
+ export const resolveCwd = (cwdInput, workingDir) => {
68
+ const projectRootAbs = path.resolve(workingDir);
69
+ let projectRoot = projectRootAbs;
70
+ try {
71
+ projectRoot = fs.realpathSync(projectRootAbs);
72
+ }
73
+ catch {
74
+ // Fallback to resolved path
75
+ }
76
+ const raw = typeof cwdInput === "string" && cwdInput.trim() !== ""
77
+ ? cwdInput.trim()
78
+ : projectRoot;
79
+ const abs = path.isAbsolute(raw) ? raw : path.resolve(projectRoot, raw);
80
+ let target = abs;
81
+ try {
82
+ target = fs.realpathSync(abs);
83
+ }
84
+ catch {
85
+ // If the path doesn't exist entirely, validate intended path
86
+ }
87
+ const rel = path.relative(projectRoot, target);
88
+ const inside = rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
89
+ if (!inside) {
90
+ throw new Error(`Working directory must be within the project directory: ${projectRoot}. Received: ${cwdInput ?? "<default>"} -> ${target}`);
91
+ }
92
+ // Check existence and that it's a directory
93
+ let stats;
94
+ try {
95
+ stats = fs.statSync(target);
96
+ }
97
+ catch (err) {
98
+ const error = err;
99
+ if (error.code === "ENOENT") {
100
+ throw new Error(`Working directory does not exist: ${target} (from ${cwdInput ?? "<default>"})`);
101
+ }
102
+ throw error;
103
+ }
104
+ if (!stats.isDirectory()) {
105
+ throw new Error(`Working directory is not a directory: ${target}`);
106
+ }
107
+ return target;
108
+ };
109
+ export const isMutatingCommand = (rawCommand) => {
110
+ const command = rawCommand.trim();
111
+ // Redirections that write to disk
112
+ if (/>|>>/.test(command)) {
113
+ return true;
114
+ }
115
+ // Normalize whitespace and split into simple segments (does not fully parse shell)
116
+ const segments = command
117
+ .split(/\s*(?:&&|\|\||;|\|)\s*/)
118
+ .map((s) => s.trim())
119
+ .filter((s) => s.length > 0);
120
+ const mutatingBinaries = new Set([
121
+ "rm",
122
+ "mv",
123
+ "cp",
124
+ "mkdir",
125
+ "rmdir",
126
+ "touch",
127
+ "chmod",
128
+ "chown",
129
+ "ln",
130
+ "truncate",
131
+ "dd",
132
+ "tee",
133
+ ]);
134
+ const npmMutating = new Set([
135
+ "install",
136
+ "uninstall",
137
+ "update",
138
+ "ci",
139
+ "publish",
140
+ "link",
141
+ "dedupe",
142
+ "prune",
143
+ "rebuild",
144
+ "add",
145
+ ]);
146
+ const gitMutating = new Set([
147
+ "add",
148
+ "am",
149
+ "apply",
150
+ "branch",
151
+ "checkout",
152
+ "switch",
153
+ "cherry-pick",
154
+ "clean",
155
+ "commit",
156
+ "merge",
157
+ "mv",
158
+ "pull",
159
+ "push",
160
+ "rebase",
161
+ "reset",
162
+ "revert",
163
+ "stash",
164
+ "tag",
165
+ "worktree",
166
+ "submodule",
167
+ "config",
168
+ ]);
169
+ // Generic action words that should be considered mutating when present in the command
170
+ const actionMutating = new Set(["create", "update", "upgrade", "install"]);
171
+ for (const seg of segments) {
172
+ const tokens = seg.split(/\s+/);
173
+ if (tokens.length === 0)
174
+ continue;
175
+ const bin = tokens[0];
176
+ if (!bin)
177
+ continue;
178
+ // If any token is an action-like mutating word, consider mutating
179
+ if (tokens.some((t) => actionMutating.has(t))) {
180
+ return true;
181
+ }
182
+ // sed -i is mutating
183
+ if (bin === "sed") {
184
+ if (tokens.some((t) => /^-i/.test(t))) {
185
+ return true;
186
+ }
187
+ // sed without -i is not mutating
188
+ }
189
+ if (mutatingBinaries.has(bin)) {
190
+ return true;
191
+ }
192
+ if (bin === "git" && tokens.length > 1) {
193
+ const sub = tokens[1];
194
+ if (typeof sub === "string" && gitMutating.has(sub)) {
195
+ return true;
196
+ }
197
+ }
198
+ if (bin === "npm" && tokens.length > 1) {
199
+ const sub = tokens[1];
200
+ if (typeof sub === "string" && npmMutating.has(sub)) {
201
+ return true;
202
+ }
203
+ }
204
+ if ((bin === "pnpm" || bin === "yarn") && tokens.length > 1) {
205
+ const sub = tokens[1];
206
+ if (typeof sub === "string" && npmMutating.has(sub)) {
207
+ return true;
208
+ }
209
+ }
210
+ }
211
+ return false;
212
+ };
@@ -1,19 +1,21 @@
1
1
  import type { Terminal } from "../terminal/index.ts";
2
- import type { TokenCounter } from "../token-utils.ts";
2
+ import type { TokenCounter } from "../tokens/counter.ts";
3
+ import type { ToolExecutor } from "../tool-executor.ts";
3
4
  import type { SendData } from "./types.ts";
4
5
  export declare const BashTool: {
5
6
  name: "bash";
6
7
  };
7
- export declare const createBashTool: ({ baseDir, sendData, tokenCounter, terminal, autoAcceptAll, }: {
8
+ export declare const createBashTool: ({ baseDir, sendData, tokenCounter, terminal, toolExecutor, }: {
8
9
  baseDir: string;
9
- sendData?: SendData | undefined;
10
+ sendData?: SendData;
10
11
  tokenCounter: TokenCounter;
11
12
  terminal?: Terminal;
12
- autoAcceptAll: boolean;
13
- }) => {
13
+ toolExecutor?: ToolExecutor;
14
+ }) => Promise<{
14
15
  bash: import("ai").Tool<{
16
+ command: string;
15
17
  cwd: string | null;
16
18
  timeout: number | null;
17
- command: string;
18
19
  }, string>;
19
- };
20
+ }>;
21
+ //# sourceMappingURL=bash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../source/tools/bash.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAe,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGrE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAKF,eAAO,MAAM,cAAc,GAAU,8DAMlC;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,YAAY,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;;;;;;EAkLA,CAAC"}
@@ -1,272 +1,155 @@
1
- import path from "node:path";
2
- import { input, select } from "@inquirer/prompts";
3
1
  import { tool } from "ai";
4
- import chalk from "chalk";
5
2
  import { z } from "zod";
6
3
  import { config } from "../config.js";
7
- import { executeCommand } from "../utils/process.js";
8
- import { CommandValidation } from "./command-validation.js";
4
+ import { initExecutionEnvironment } from "../execution/index.js";
5
+ import { logger } from "../logger.js";
6
+ import style from "../terminal/style.js";
7
+ import { isMutatingCommand, resolveCwd, validatePaths } from "./bash-utils.js";
8
+ import { isPathWithinBaseDir } from "./filesystem-utils.js";
9
9
  export const BashTool = {
10
10
  name: "bash",
11
11
  };
12
- // Whitelist of allowed commands
13
- const ALLOWED_COMMANDS = [
14
- "chmod",
15
- "ls",
16
- "pwd",
17
- "cat",
18
- "grep",
19
- "find",
20
- "echo",
21
- "mkdir",
22
- "touch",
23
- "cp",
24
- "mv",
25
- "pwd",
26
- "wc",
27
- "diff",
28
- "sort",
29
- "head",
30
- "tail",
31
- "sleep",
32
- "npm",
33
- "npx",
34
- "node",
35
- "git",
36
- "gh",
37
- "rg",
38
- "jq",
39
- "sed",
40
- "awk",
41
- ];
42
12
  // Command execution timeout in milliseconds
43
13
  const DEFAULT_TIMEOUT = 1.5 * 60 * 1000; // 1.5 minutes
44
- // Ensure path is within base directory
45
- function isPathWithinBaseDir(requestedPath, baseDir) {
46
- const normalizedRequestedPath = path.normalize(requestedPath);
47
- const normalizedBaseDir = path.normalize(baseDir);
48
- return normalizedRequestedPath.startsWith(normalizedBaseDir);
49
- }
50
- // Validate path arguments to ensure they're within the project
51
- function validatePaths(command, baseDir, cwd) {
52
- // Simple tokenization - split on spaces but respect quotes
53
- const tokens = [];
54
- let current = "";
55
- let inQuotes = false;
56
- let quoteChar = "";
57
- for (let i = 0; i < command.length; i++) {
58
- const char = command[i];
59
- if ((char === '"' || char === "'") && !inQuotes) {
60
- inQuotes = true;
61
- quoteChar = char;
62
- current += char;
63
- }
64
- else if (char === quoteChar && inQuotes) {
65
- inQuotes = false;
66
- quoteChar = "";
67
- current += char;
68
- }
69
- else if (char === " " && !inQuotes) {
70
- if (current) {
71
- tokens.push(current);
72
- current = "";
73
- }
74
- }
75
- else {
76
- current += char;
77
- }
78
- }
79
- if (current)
80
- tokens.push(current);
81
- // Check each token that looks like a path
82
- for (let i = 1; i < tokens.length; i++) {
83
- // Skip the command itself
84
- const token = tokens[i];
85
- if (!token)
86
- continue;
87
- // Remove quotes for path checking
88
- const cleanToken = token.replace(/^['"]|['"]$/g, "");
89
- // Skip if it's clearly not a path
90
- if (cleanToken.startsWith("-") ||
91
- cleanToken.includes("://") ||
92
- !cleanToken.includes("/")) {
93
- continue;
94
- }
95
- // Skip git commit messages and other special cases
96
- const prevToken = tokens[i - 1]?.replace(/^['"]|['"]$/g, "");
97
- if (prevToken === "-m" || prevToken === "--message") {
98
- continue;
99
- }
100
- try {
101
- const resolvedPath = path.resolve(cwd, cleanToken);
102
- if (!isPathWithinBaseDir(resolvedPath, baseDir)) {
103
- return {
104
- isValid: false,
105
- error: `Path '${cleanToken}' resolves outside the project directory (${resolvedPath}). All paths must be within ${baseDir}`,
106
- };
107
- }
108
- }
109
- catch (_e) { }
110
- }
111
- return { isValid: true };
112
- }
113
- // Initialize command validator with allowed commands
114
- const commandValidator = new CommandValidation(ALLOWED_COMMANDS);
115
- export const createBashTool = ({ baseDir, sendData, tokenCounter, terminal, autoAcceptAll, }) => {
116
- let autoAcceptCommands = autoAcceptAll;
14
+ export const createBashTool = async ({ baseDir, sendData, tokenCounter, terminal, toolExecutor, }) => {
15
+ const execEnv = await initExecutionEnvironment();
117
16
  return {
118
17
  [BashTool.name]: tool({
119
- description: `Execute commands without a shell. Restrictions: no pipes (|), redirects (>, >>, <, <<), chaining (&&, ||, ;), background (&), or command substitution (\`...\`, $()). Pass a single allowed base command with quoted args. Compose by running multiple tool calls or using program flags (e.g., rg, jq). Commands execute only within the project directory. Always use absolute paths. Allowed commands: ${ALLOWED_COMMANDS.join(", ")}.`,
18
+ description: "Execute commands in a shell. Commands execute only within the project directory. Always use absolute paths.",
120
19
  inputSchema: z.object({
121
20
  command: z.string().describe("Full CLI command to execute."),
122
21
  cwd: z
123
22
  .string()
124
23
  .nullable()
125
- .describe("Working directory (default: project root). Must be within the project directory."),
126
- timeout: z
24
+ .describe("Working directory file path (default: project root). Must be within the project directory."),
25
+ timeout: z.coerce
127
26
  .number()
128
27
  .nullable()
129
28
  .describe(`Command execution timeout in milliseconds. Default: ${DEFAULT_TIMEOUT}ms`),
130
29
  }),
131
- execute: async ({ command, cwd, timeout }, { toolCallId }) => {
132
- // Guard against null cwd and timeout
133
- const safeCwd = cwd == null ? baseDir : cwd;
134
- const safeTimeout = timeout == null ? DEFAULT_TIMEOUT : timeout;
135
- sendData?.({
136
- event: "tool-init",
137
- id: toolCallId,
138
- data: `Executing: ${chalk.cyan(command)} in ${chalk.cyan(safeCwd)}`,
139
- });
140
- // Validate working directory
141
- if (!isPathWithinBaseDir(safeCwd, baseDir)) {
142
- const errorMsg = `Working directory must be within the project directory: ${baseDir}`;
143
- sendData?.({ event: "tool-error", id: toolCallId, data: errorMsg });
144
- return errorMsg;
145
- }
146
- // Validate command using improved validation
147
- const commandValidation = commandValidator.isValid(command);
148
- if (!commandValidation.isValid) {
149
- sendData?.({
150
- event: "tool-error",
151
- id: toolCallId,
152
- data: commandValidation.error ?? "Unknown error.",
153
- });
154
- return commandValidation.error ?? "Unknown error.";
155
- }
156
- // Validate paths
157
- const pathValidation = validatePaths(command, baseDir, safeCwd);
158
- if (!pathValidation.isValid) {
30
+ execute: async ({ command, cwd, timeout }, { toolCallId, abortSignal }) => {
31
+ try {
32
+ if (abortSignal?.aborted) {
33
+ throw new Error("Command execution aborted");
34
+ }
35
+ // grok doesn't follow my instructions
36
+ const safeCwd = cwd === "null" ? null : cwd;
37
+ const resolvedCwd = resolveCwd(safeCwd, baseDir);
38
+ const safeTimeout = timeout ?? DEFAULT_TIMEOUT;
159
39
  sendData?.({
160
- event: "tool-error",
40
+ event: "tool-init",
161
41
  id: toolCallId,
162
- data: pathValidation.error ?? "Unknown error.",
42
+ data: `Executing: ${style.cyan(command)} in ${style.cyan(resolvedCwd)}`,
163
43
  });
164
- return pathValidation.error ?? "Unknown error.";
165
- }
166
- // Prompt user for command execution approval (only in interactive mode)
167
- if (terminal) {
168
- if (!autoAcceptCommands) {
169
- await new Promise((resolve) => setTimeout(resolve, 1000));
170
- terminal.lineBreak();
171
- terminal.writeln(`${chalk.blue.bold("●")} About to execute command: ${chalk.cyan(command)}`);
172
- terminal.writeln(`${chalk.gray("Working directory:")} ${safeCwd}`);
173
- terminal.lineBreak();
174
- }
175
- let userChoice;
176
- if (autoAcceptCommands) {
177
- terminal.writeln(chalk.green("✓ Auto-accepting command (all future commands will be accepted)"));
178
- userChoice = "accept";
44
+ if (!isPathWithinBaseDir(resolvedCwd, baseDir)) {
45
+ const errorMsg = `Working directory must be within the project directory: ${baseDir}`;
46
+ sendData?.({ event: "tool-error", id: toolCallId, data: errorMsg });
47
+ return errorMsg;
179
48
  }
180
- else {
181
- userChoice = await select({
182
- message: "What would you like to do with this command?",
183
- choices: [
184
- { name: "Execute this command", value: "accept" },
185
- {
186
- name: "Execute all future commands (including this)",
187
- value: "accept-all",
188
- },
189
- { name: "Reject this command", value: "reject" },
190
- ],
191
- default: "accept",
49
+ const pathValidation = validatePaths(command, baseDir, resolvedCwd);
50
+ if (!pathValidation.isValid) {
51
+ sendData?.({
52
+ event: "tool-error",
53
+ id: toolCallId,
54
+ data: pathValidation.error ?? "Unknown error.",
192
55
  });
56
+ return pathValidation.error ?? "Unknown error.";
193
57
  }
194
- terminal.lineBreak();
195
- if (userChoice === "accept-all") {
196
- autoAcceptCommands = true;
197
- terminal.writeln(chalk.yellow("✓ Auto-accept mode enabled for all future commands"));
58
+ if (terminal) {
59
+ let userResponse;
60
+ // Prompt only for potentially mutating commands when a toolExecutor is present
61
+ if (toolExecutor && isMutatingCommand(command)) {
62
+ // Display if autoAccept is false
63
+ if (!toolExecutor.autoAccept(BashTool.name)) {
64
+ terminal.writeln(`\n${style.blue.bold("●")} Proposing to execute command: ${style.cyan(command)} in ${style.cyan(resolvedCwd)}`);
65
+ terminal.lineBreak();
66
+ }
67
+ const ctx = {
68
+ toolName: BashTool.name,
69
+ toolCallId,
70
+ message: "What would you like to do with this command execution?",
71
+ choices: {
72
+ accept: "Execute this command",
73
+ acceptAll: "Accept all future command executions (including this)",
74
+ reject: "Reject this command execution",
75
+ },
76
+ };
77
+ try {
78
+ userResponse = await toolExecutor.ask(ctx, { abortSignal });
79
+ }
80
+ catch (e) {
81
+ if (e.name === "AbortError") {
82
+ throw new Error("Command execution aborted during user input");
83
+ }
84
+ throw e;
85
+ }
86
+ }
87
+ const { result: userChoice, reason } = userResponse ?? {
88
+ result: "accept",
89
+ };
198
90
  terminal.lineBreak();
91
+ if (userChoice === "accept-all") {
92
+ terminal.writeln(style.yellow("✓ Auto-accept mode enabled for all command executions"));
93
+ terminal.lineBreak();
94
+ }
95
+ if (userChoice === "reject") {
96
+ terminal.lineBreak();
97
+ const rejectionReason = reason || "No reason provided";
98
+ sendData?.({
99
+ event: "tool-completion",
100
+ id: toolCallId,
101
+ data: `Command execution rejected by user. Reason: ${rejectionReason}`,
102
+ });
103
+ return `The user rejected this command execution. Reason: ${rejectionReason}`;
104
+ }
199
105
  }
200
- if (userChoice === "reject") {
201
- const reason = await input({ message: "Feedback: " });
202
- terminal.lineBreak();
203
- const rejectionMsg = `Command rejected by user. Reason: ${reason}`;
204
- sendData?.({
205
- event: "tool-completion",
206
- id: toolCallId,
207
- data: rejectionMsg,
208
- });
209
- return rejectionMsg;
106
+ if (abortSignal?.aborted) {
107
+ throw new Error("Command execution aborted before running the command");
210
108
  }
211
- }
212
- // Execute command
213
- try {
214
- const result = await executeCommand(command, {
215
- cwd: safeCwd,
109
+ terminal?.startProgress();
110
+ const { output, exitCode } = await execEnv.executeCommand(command, {
111
+ cwd: resolvedCwd,
216
112
  timeout: safeTimeout,
217
- shell: false,
113
+ abortSignal,
114
+ preserveOutputOnError: true,
115
+ captureStderr: true,
218
116
  throwOnError: false,
219
117
  });
220
- if (result.signal === "SIGTERM") {
221
- const timeoutMessage = `Command timed out after ${safeTimeout}ms. This might be because the command is waiting for input.`;
222
- sendData?.({
223
- event: "tool-error",
224
- id: toolCallId,
225
- data: timeoutMessage,
226
- });
227
- return timeoutMessage;
228
- }
229
- const formattedResult = format(result);
230
118
  sendData?.({
231
119
  event: "tool-update",
232
120
  id: toolCallId,
233
121
  data: {
234
122
  primary: "Result",
235
- secondary: formattedResult.split("\n").slice(-5),
123
+ secondary: output.trim().split("\n").slice(-20),
236
124
  },
237
125
  });
238
126
  let tokenCount = 0;
239
127
  try {
240
- tokenCount = tokenCounter.count(formattedResult);
128
+ tokenCount = tokenCounter.count(output);
241
129
  }
242
130
  catch (tokenError) {
243
- console.error("Error calculating token count:", tokenError);
131
+ console.info("Error calculating token count:", tokenError);
244
132
  }
245
133
  const maxTokens = (await config.readProjectConfig()).tools.maxTokens;
246
- const maxTokenMessage = `Output of command (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}). Please adjust how you call the command to get back more specific results`;
247
- const finalResult = tokenCount <= maxTokens ? formattedResult : maxTokenMessage;
134
+ const maxTokenMessage = `Output of command (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}). Please adjust how you call the command to get back more specific results.`;
135
+ const finalResult = tokenCount <= maxTokens ? output : maxTokenMessage;
248
136
  sendData?.({
249
137
  event: "tool-completion",
250
138
  id: toolCallId,
251
139
  data: tokenCount <= maxTokens
252
- ? "Command executed successfully."
140
+ ? `Command executed successfully: ${exitCode} (${tokenCount} tokens)`
253
141
  : `Output of command (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}).`,
254
142
  });
143
+ terminal?.stopProgress();
255
144
  return finalResult;
256
145
  }
257
146
  catch (error) {
147
+ logger.error(error, "Bash Tool Error:");
258
148
  const errorMsg = `Command failed: ${error.message}`;
259
- sendData?.({
260
- event: "tool-error",
261
- id: toolCallId,
262
- data: errorMsg,
263
- });
149
+ sendData?.({ event: "tool-error", id: toolCallId, data: errorMsg });
264
150
  return errorMsg;
265
151
  }
266
152
  },
267
153
  }),
268
154
  };
269
155
  };
270
- function format({ stdout, stderr, }) {
271
- return `${stdout}\n${stderr}`;
272
- }
@@ -7,7 +7,7 @@ export declare const createCodeInterpreterTool: ({ sendData, }: Readonly<{
7
7
  }>) => {
8
8
  codeInterpreter: import("ai").Tool<{
9
9
  code: string;
10
- type: "Typescript" | "JavaScript" | null;
11
10
  timeoutSeconds: number | null;
12
11
  }, string>;
13
12
  };
13
+ //# sourceMappingURL=code-interpreter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-interpreter.d.ts","sourceRoot":"","sources":["../../source/tools/code-interpreter.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,mBAAmB;;CAE/B,CAAC;AAmBF,eAAO,MAAM,yBAAyB,GAAI,eAEvC,QAAQ,CAAC;IACV,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACjC,CAAC;;;;;CAiLD,CAAC"}