@travisennis/acai 0.0.1 → 0.0.3

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 (324) hide show
  1. package/README.md +3 -4
  2. package/dist/commands/health-command.d.ts +2 -0
  3. package/dist/commands/health-command.js +59 -0
  4. package/dist/commands/manager.js +2 -0
  5. package/dist/commands/paste-command.d.ts +1 -1
  6. package/dist/commands/paste-command.js +155 -11
  7. package/dist/commands/reset-command.js +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +2 -1
  10. package/dist/models/openrouter-provider.d.ts +4 -1
  11. package/dist/models/openrouter-provider.js +46 -4
  12. package/dist/models/providers.d.ts +1 -1
  13. package/dist/prompts/manager.d.ts +1 -0
  14. package/dist/prompts/manager.js +10 -0
  15. package/dist/prompts.js +8 -6
  16. package/dist/repl.js +49 -26
  17. package/dist/terminal/formatting.d.ts +16 -5
  18. package/dist/terminal/formatting.js +40 -6
  19. package/dist/terminal/index.d.ts +1 -1
  20. package/dist/terminal/index.js +54 -14
  21. package/dist/terminal/markdown.js +0 -1
  22. package/dist/terminal/supports-color.d.ts +16 -0
  23. package/dist/terminal/supports-color.js +121 -0
  24. package/dist/terminal/supports-hyperlinks.d.ts +7 -0
  25. package/dist/terminal/supports-hyperlinks.js +98 -0
  26. package/dist/tools/bash.js +95 -117
  27. package/dist/tools/code-interpreter.js +11 -1
  28. package/dist/tools/command-validation.d.ts +7 -3
  29. package/dist/tools/command-validation.js +67 -23
  30. package/dist/tools/delete-file.d.ts +4 -1
  31. package/dist/tools/delete-file.js +47 -3
  32. package/dist/tools/git-utils.d.ts +6 -0
  33. package/dist/tools/git-utils.js +89 -12
  34. package/dist/tools/grep.d.ts +20 -0
  35. package/dist/tools/grep.js +128 -40
  36. package/dist/tools/index.d.ts +2 -18
  37. package/dist/tools/index.js +4 -18
  38. package/package.json +30 -20
  39. package/.acai/acai.json +0 -9
  40. package/.acai/prompts/add-openrouter-model.md +0 -13
  41. package/.acai/prompts/project-status.md +0 -4
  42. package/.acai/prompts/update-architecture-document.md +0 -9
  43. package/.acai/rules/learned-rules.md +0 -9
  44. package/.ai/docs/available-tools.txt +0 -3
  45. package/.ai/docs/cognitive_complexity_refactoring_progress.md +0 -65
  46. package/.ai/docs/deleted_tools.md +0 -168
  47. package/.ai/docs/deleted_tools_88ced9ef.md +0 -56
  48. package/.ai/docs/image-pasting.md +0 -46
  49. package/.ai/docs/initialize-app.md +0 -117
  50. package/.ai/docs/issue-4-plan.md +0 -44
  51. package/.ai/docs/marked-renderer-debug.md +0 -15
  52. package/.ai/docs/marked-renderer-refactor-plan.md +0 -64
  53. package/.ai/docs/memory-use-cases.md +0 -55
  54. package/.ai/docs/prompt-consistency.md +0 -31
  55. package/.ai/docs/refactoring-tools.md +0 -98
  56. package/.ai/docs/system-prompt-update.md +0 -174
  57. package/.ai/docs/system_prompt.txt +0 -210
  58. package/.ai/docs/tasks.md +0 -49
  59. package/.ai/plan.md +0 -131
  60. package/.ai/prompt.md +0 -1
  61. package/.ai/scripts/fetch_models.js +0 -27
  62. package/.ai/scripts/generateSystemPrompt.ts +0 -15
  63. package/.ai/scripts/list-tools.mjs +0 -4
  64. package/.ai/scripts/p5_geometric_shapes.js +0 -149
  65. package/.husky/commit-msg +0 -1
  66. package/.husky/pre-commit +0 -3
  67. package/.husky/pre-push +0 -1
  68. package/.ignore +0 -4
  69. package/AGENTS.md +0 -25
  70. package/ARCHITECTURE.md +0 -304
  71. package/TODO.md +0 -2
  72. package/biome.json +0 -61
  73. package/commitlint.config.js +0 -3
  74. package/dist/source/cli.d.ts +0 -19
  75. package/dist/source/cli.js +0 -116
  76. package/dist/source/commands/application-log-command.d.ts +0 -2
  77. package/dist/source/commands/application-log-command.js +0 -43
  78. package/dist/source/commands/clear-command.d.ts +0 -2
  79. package/dist/source/commands/clear-command.js +0 -12
  80. package/dist/source/commands/compact-command.d.ts +0 -2
  81. package/dist/source/commands/compact-command.js +0 -51
  82. package/dist/source/commands/copy-command.d.ts +0 -2
  83. package/dist/source/commands/copy-command.js +0 -51
  84. package/dist/source/commands/edit-command.d.ts +0 -2
  85. package/dist/source/commands/edit-command.js +0 -53
  86. package/dist/source/commands/edit-prompt-command.d.ts +0 -2
  87. package/dist/source/commands/edit-prompt-command.js +0 -25
  88. package/dist/source/commands/exit-command.d.ts +0 -2
  89. package/dist/source/commands/exit-command.js +0 -14
  90. package/dist/source/commands/files-command.d.ts +0 -2
  91. package/dist/source/commands/files-command.js +0 -63
  92. package/dist/source/commands/generate-rules-command.d.ts +0 -2
  93. package/dist/source/commands/generate-rules-command.js +0 -61
  94. package/dist/source/commands/help-command.d.ts +0 -2
  95. package/dist/source/commands/help-command.js +0 -19
  96. package/dist/source/commands/init-command.d.ts +0 -2
  97. package/dist/source/commands/init-command.js +0 -40
  98. package/dist/source/commands/last-log-command.d.ts +0 -2
  99. package/dist/source/commands/last-log-command.js +0 -76
  100. package/dist/source/commands/manager.d.ts +0 -22
  101. package/dist/source/commands/manager.js +0 -123
  102. package/dist/source/commands/model-command.d.ts +0 -2
  103. package/dist/source/commands/model-command.js +0 -84
  104. package/dist/source/commands/paste-command.d.ts +0 -2
  105. package/dist/source/commands/paste-command.js +0 -40
  106. package/dist/source/commands/prompt-command.d.ts +0 -2
  107. package/dist/source/commands/prompt-command.js +0 -111
  108. package/dist/source/commands/reset-command.d.ts +0 -2
  109. package/dist/source/commands/reset-command.js +0 -16
  110. package/dist/source/commands/rules-command.d.ts +0 -2
  111. package/dist/source/commands/rules-command.js +0 -68
  112. package/dist/source/commands/save-command.d.ts +0 -2
  113. package/dist/source/commands/save-command.js +0 -14
  114. package/dist/source/commands/types.d.ts +0 -26
  115. package/dist/source/commands/types.js +0 -1
  116. package/dist/source/commands/usage-command.d.ts +0 -2
  117. package/dist/source/commands/usage-command.js +0 -21
  118. package/dist/source/config.d.ts +0 -60
  119. package/dist/source/config.js +0 -193
  120. package/dist/source/conversation-analyzer.d.ts +0 -10
  121. package/dist/source/conversation-analyzer.js +0 -88
  122. package/dist/source/dedent.d.ts +0 -3
  123. package/dist/source/dedent.js +0 -38
  124. package/dist/source/formatting.d.ts +0 -17
  125. package/dist/source/formatting.js +0 -103
  126. package/dist/source/index.d.ts +0 -18
  127. package/dist/source/index.js +0 -213
  128. package/dist/source/logger.d.ts +0 -2
  129. package/dist/source/logger.js +0 -24
  130. package/dist/source/mentions.d.ts +0 -9
  131. package/dist/source/mentions.js +0 -182
  132. package/dist/source/messages.d.ts +0 -69
  133. package/dist/source/messages.js +0 -261
  134. package/dist/source/middleware/audit-message.d.ts +0 -5
  135. package/dist/source/middleware/audit-message.js +0 -95
  136. package/dist/source/middleware/index.d.ts +0 -2
  137. package/dist/source/middleware/index.js +0 -2
  138. package/dist/source/middleware/rate-limit.d.ts +0 -4
  139. package/dist/source/middleware/rate-limit.js +0 -17
  140. package/dist/source/models/ai-config.d.ts +0 -12
  141. package/dist/source/models/ai-config.js +0 -87
  142. package/dist/source/models/anthropic-provider.d.ts +0 -25
  143. package/dist/source/models/anthropic-provider.js +0 -184
  144. package/dist/source/models/deepseek-provider.d.ts +0 -20
  145. package/dist/source/models/deepseek-provider.js +0 -42
  146. package/dist/source/models/google-provider.d.ts +0 -19
  147. package/dist/source/models/google-provider.js +0 -56
  148. package/dist/source/models/manager.d.ts +0 -15
  149. package/dist/source/models/manager.js +0 -48
  150. package/dist/source/models/openai-provider.d.ts +0 -22
  151. package/dist/source/models/openai-provider.js +0 -70
  152. package/dist/source/models/openrouter-provider.d.ts +0 -36
  153. package/dist/source/models/openrouter-provider.js +0 -276
  154. package/dist/source/models/providers.d.ts +0 -33
  155. package/dist/source/models/providers.js +0 -116
  156. package/dist/source/models/xai-provider.d.ts +0 -20
  157. package/dist/source/models/xai-provider.js +0 -47
  158. package/dist/source/parsing.d.ts +0 -2
  159. package/dist/source/parsing.js +0 -18
  160. package/dist/source/prompts/manager.d.ts +0 -19
  161. package/dist/source/prompts/manager.js +0 -71
  162. package/dist/source/prompts.d.ts +0 -4
  163. package/dist/source/prompts.js +0 -158
  164. package/dist/source/repl-prompt.d.ts +0 -14
  165. package/dist/source/repl-prompt.js +0 -147
  166. package/dist/source/repl.d.ts +0 -27
  167. package/dist/source/repl.js +0 -431
  168. package/dist/source/terminal/formatting.d.ts +0 -37
  169. package/dist/source/terminal/formatting.js +0 -106
  170. package/dist/source/terminal/index.d.ts +0 -94
  171. package/dist/source/terminal/index.js +0 -420
  172. package/dist/source/terminal/markdown-utils.d.ts +0 -2
  173. package/dist/source/terminal/markdown-utils.js +0 -81
  174. package/dist/source/terminal/markdown.d.ts +0 -1
  175. package/dist/source/terminal/markdown.js +0 -111
  176. package/dist/source/terminal/types.d.ts +0 -71
  177. package/dist/source/terminal/types.js +0 -1
  178. package/dist/source/terminal-output.d.ts +0 -8
  179. package/dist/source/terminal-output.js +0 -213
  180. package/dist/source/terminal-output.test.d.ts +0 -8
  181. package/dist/source/terminal-output.test.js +0 -213
  182. package/dist/source/token-tracker.d.ts +0 -14
  183. package/dist/source/token-tracker.js +0 -53
  184. package/dist/source/token-utils.d.ts +0 -7
  185. package/dist/source/token-utils.js +0 -13
  186. package/dist/source/tools/agent.d.ts +0 -17
  187. package/dist/source/tools/agent.js +0 -87
  188. package/dist/source/tools/bash.d.ts +0 -19
  189. package/dist/source/tools/bash.js +0 -294
  190. package/dist/source/tools/code-interpreter.d.ts +0 -12
  191. package/dist/source/tools/code-interpreter.js +0 -131
  192. package/dist/source/tools/command-validation.d.ts +0 -8
  193. package/dist/source/tools/command-validation.js +0 -69
  194. package/dist/source/tools/delete-file.d.ts +0 -12
  195. package/dist/source/tools/delete-file.js +0 -56
  196. package/dist/source/tools/directory-tree.d.ts +0 -12
  197. package/dist/source/tools/directory-tree.js +0 -38
  198. package/dist/source/tools/edit-file.d.ts +0 -19
  199. package/dist/source/tools/edit-file.js +0 -107
  200. package/dist/source/tools/filesystem-utils.d.ts +0 -22
  201. package/dist/source/tools/filesystem-utils.js +0 -191
  202. package/dist/source/tools/git-utils.d.ts +0 -14
  203. package/dist/source/tools/git-utils.js +0 -64
  204. package/dist/source/tools/grep.d.ts +0 -17
  205. package/dist/source/tools/grep.js +0 -138
  206. package/dist/source/tools/index.d.ts +0 -161
  207. package/dist/source/tools/index.js +0 -209
  208. package/dist/source/tools/memory-read.d.ts +0 -13
  209. package/dist/source/tools/memory-read.js +0 -135
  210. package/dist/source/tools/memory-write.d.ts +0 -12
  211. package/dist/source/tools/memory-write.js +0 -83
  212. package/dist/source/tools/move-file.d.ts +0 -13
  213. package/dist/source/tools/move-file.js +0 -44
  214. package/dist/source/tools/read-file.d.ts +0 -17
  215. package/dist/source/tools/read-file.js +0 -86
  216. package/dist/source/tools/read-multiple-files.d.ts +0 -14
  217. package/dist/source/tools/read-multiple-files.js +0 -55
  218. package/dist/source/tools/save-file.d.ts +0 -17
  219. package/dist/source/tools/save-file.js +0 -98
  220. package/dist/source/tools/think.d.ts +0 -11
  221. package/dist/source/tools/think.js +0 -45
  222. package/dist/source/tools/types.d.ts +0 -29
  223. package/dist/source/tools/types.js +0 -14
  224. package/dist/source/tools/web-fetch.d.ts +0 -47
  225. package/dist/source/tools/web-fetch.js +0 -246
  226. package/dist/source/tools/web-search.d.ts +0 -13
  227. package/dist/source/tools/web-search.js +0 -80
  228. package/dist/source/utils/process.d.ts +0 -36
  229. package/dist/source/utils/process.js +0 -75
  230. package/dist/source/version.d.ts +0 -1
  231. package/dist/source/version.js +0 -21
  232. package/dist/terminal-output.d.ts +0 -8
  233. package/dist/terminal-output.js +0 -213
  234. package/dist/tools/memory-read.d.ts +0 -13
  235. package/dist/tools/memory-read.js +0 -135
  236. package/dist/tools/memory-write.d.ts +0 -12
  237. package/dist/tools/memory-write.js +0 -83
  238. package/knip.json +0 -5
  239. package/source/cli.ts +0 -172
  240. package/source/commands/application-log-command.ts +0 -53
  241. package/source/commands/clear-command.ts +0 -14
  242. package/source/commands/compact-command.ts +0 -64
  243. package/source/commands/copy-command.ts +0 -55
  244. package/source/commands/edit-command.ts +0 -63
  245. package/source/commands/edit-prompt-command.ts +0 -31
  246. package/source/commands/exit-command.ts +0 -18
  247. package/source/commands/files-command.ts +0 -85
  248. package/source/commands/generate-rules-command.ts +0 -82
  249. package/source/commands/help-command.ts +0 -27
  250. package/source/commands/init-command.ts +0 -48
  251. package/source/commands/last-log-command.ts +0 -88
  252. package/source/commands/manager.ts +0 -151
  253. package/source/commands/model-command.ts +0 -123
  254. package/source/commands/paste-command.ts +0 -62
  255. package/source/commands/prompt-command.ts +0 -150
  256. package/source/commands/reset-command.ts +0 -22
  257. package/source/commands/rules-command.ts +0 -76
  258. package/source/commands/save-command.ts +0 -20
  259. package/source/commands/types.ts +0 -28
  260. package/source/commands/usage-command.ts +0 -26
  261. package/source/config.ts +0 -223
  262. package/source/conversation-analyzer.ts +0 -115
  263. package/source/dedent.ts +0 -53
  264. package/source/formatting.ts +0 -132
  265. package/source/index.ts +0 -240
  266. package/source/logger.ts +0 -29
  267. package/source/mentions.ts +0 -227
  268. package/source/messages.ts +0 -360
  269. package/source/middleware/audit-message.ts +0 -133
  270. package/source/middleware/index.ts +0 -2
  271. package/source/middleware/rate-limit.ts +0 -24
  272. package/source/models/ai-config.ts +0 -109
  273. package/source/models/anthropic-provider.ts +0 -199
  274. package/source/models/deepseek-provider.ts +0 -53
  275. package/source/models/google-provider.ts +0 -68
  276. package/source/models/manager.ts +0 -84
  277. package/source/models/openai-provider.ts +0 -81
  278. package/source/models/openrouter-provider.ts +0 -288
  279. package/source/models/providers.ts +0 -197
  280. package/source/models/xai-provider.ts +0 -59
  281. package/source/parsing.ts +0 -20
  282. package/source/prompts/manager.ts +0 -90
  283. package/source/prompts.ts +0 -172
  284. package/source/repl-prompt.ts +0 -196
  285. package/source/repl.ts +0 -572
  286. package/source/terminal/formatting.ts +0 -121
  287. package/source/terminal/index.ts +0 -518
  288. package/source/terminal/markdown-utils.ts +0 -89
  289. package/source/terminal/markdown.ts +0 -155
  290. package/source/terminal/types.ts +0 -84
  291. package/source/terminal-output.test.ts +0 -266
  292. package/source/token-tracker.ts +0 -78
  293. package/source/token-utils.ts +0 -17
  294. package/source/tools/agent.ts +0 -107
  295. package/source/tools/bash.ts +0 -367
  296. package/source/tools/code-interpreter.ts +0 -172
  297. package/source/tools/command-validation.ts +0 -81
  298. package/source/tools/delete-file.ts +0 -71
  299. package/source/tools/directory-tree.ts +0 -54
  300. package/source/tools/edit-file.ts +0 -155
  301. package/source/tools/filesystem-utils.ts +0 -265
  302. package/source/tools/git-utils.ts +0 -70
  303. package/source/tools/grep.ts +0 -184
  304. package/source/tools/index.ts +0 -278
  305. package/source/tools/memory-read.ts +0 -174
  306. package/source/tools/memory-write.ts +0 -105
  307. package/source/tools/move-file.ts +0 -59
  308. package/source/tools/read-file.ts +0 -129
  309. package/source/tools/read-multiple-files.ts +0 -80
  310. package/source/tools/save-file.ts +0 -147
  311. package/source/tools/think.ts +0 -51
  312. package/source/tools/types.ts +0 -58
  313. package/source/tools/web-fetch.ts +0 -327
  314. package/source/tools/web-search.ts +0 -101
  315. package/source/utils/process.ts +0 -121
  316. package/source/version.ts +0 -21
  317. package/test/commands/copy-command.test.ts +0 -69
  318. package/test/config.test.ts +0 -200
  319. package/test/terminal/markdown-utils.test.ts +0 -124
  320. package/test/tools/bash-tool.test.ts +0 -58
  321. package/test/tools/code-interpreter.test.ts +0 -91
  322. package/test/tools/command-validation.test.ts +0 -48
  323. package/tsconfig.build.json +0 -9
  324. package/tsconfig.json +0 -30
@@ -9,69 +9,6 @@ import { CommandValidation } from "./command-validation.js";
9
9
  export const BashTool = {
10
10
  name: "bash",
11
11
  };
12
- function tokenize(inputStr) {
13
- const tokens = [];
14
- let current = "";
15
- let inSingle = false;
16
- let inDouble = false;
17
- for (let i = 0; i < inputStr.length; i++) {
18
- const ch = inputStr[i];
19
- if (ch === "'" && !inDouble) {
20
- inSingle = !inSingle;
21
- current += ch;
22
- continue;
23
- }
24
- if (ch === '"' && !inSingle) {
25
- inDouble = !inDouble;
26
- current += ch;
27
- continue;
28
- }
29
- if (!inSingle && !inDouble && /\s/.test(ch)) {
30
- if (current.length > 0) {
31
- const raw = current;
32
- const unquoted = raw.replace(/^['"]|['"]$/g, "");
33
- tokens.push({ raw, unquoted });
34
- current = "";
35
- }
36
- continue;
37
- }
38
- if (ch === "\\" && inDouble && i + 1 < inputStr.length) {
39
- const next = inputStr[i + 1];
40
- if (next === '"' || next === "\\") {
41
- current += next;
42
- i++;
43
- continue;
44
- }
45
- }
46
- current += ch;
47
- }
48
- if (current.length > 0) {
49
- const raw = current;
50
- const unquoted = raw.replace(/^['"]|['"]$/g, "");
51
- tokens.push({ raw, unquoted });
52
- }
53
- return tokens;
54
- }
55
- function shouldSkipPathValidation(tokens, index) {
56
- if (index === 0)
57
- return false;
58
- const cmd = tokens[0]?.unquoted;
59
- if (cmd !== "git")
60
- return false;
61
- const sub = tokens[1]?.unquoted;
62
- if (sub !== "commit" &&
63
- sub !== "tag" &&
64
- !(sub === "notes" && tokens[2]?.unquoted === "add")) {
65
- return false;
66
- }
67
- const prev = tokens[index - 1]?.unquoted;
68
- if (prev === "-m" || prev === "--message")
69
- return true;
70
- return false;
71
- }
72
- function looksLikeUrl(str) {
73
- return str.startsWith("http://") || str.startsWith("https://");
74
- }
75
12
  // Whitelist of allowed commands
76
13
  const ALLOWED_COMMANDS = [
77
14
  "chmod",
@@ -104,23 +41,84 @@ const ALLOWED_COMMANDS = [
104
41
  ];
105
42
  // Command execution timeout in milliseconds
106
43
  const DEFAULT_TIMEOUT = 1.5 * 60 * 1000; // 1.5 minutes
107
- // Initialize command validator with allowed commands
108
- const commandValidator = new CommandValidation(ALLOWED_COMMANDS);
109
44
  // Ensure path is within base directory
110
45
  function isPathWithinBaseDir(requestedPath, baseDir) {
111
46
  const normalizedRequestedPath = path.normalize(requestedPath);
112
47
  const normalizedBaseDir = path.normalize(baseDir);
113
48
  return normalizedRequestedPath.startsWith(normalizedBaseDir);
114
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
115
  export const createBashTool = ({ baseDir, sendData, tokenCounter, terminal, autoAcceptAll, }) => {
116
116
  let autoAcceptCommands = autoAcceptAll;
117
117
  return {
118
118
  [BashTool.name]: tool({
119
119
  description: `Execute bash commands and return their output. Limited to a whitelist of safe commands: ${ALLOWED_COMMANDS.join(", ")}. Commands will only execute within the project directory for security. Always specify absolute paths to avoid errors.`,
120
120
  inputSchema: z.object({
121
- command: z
122
- .string()
123
- .describe("Full CLI command to execute. Must be from the allowed list without chaining operators."),
121
+ command: z.string().describe("Full CLI command to execute."),
124
122
  cwd: z
125
123
  .string()
126
124
  .nullable()
@@ -139,61 +137,41 @@ export const createBashTool = ({ baseDir, sendData, tokenCounter, terminal, auto
139
137
  id: toolCallId,
140
138
  data: `Executing: ${chalk.cyan(command)} in ${chalk.cyan(safeCwd)}`,
141
139
  });
142
- // Validate command using CommandValidation
143
- if (!commandValidator.isValid(command)) {
144
- const errorMsg = `Command not allowed. Ensure all sub-commands are in the approved list: ${ALLOWED_COMMANDS.join(", ")} and no unsafe operators (>, <, \`, $()) are used.`;
145
- sendData?.({ event: "tool-error", id: toolCallId, data: errorMsg });
146
- return errorMsg;
147
- }
148
140
  // Validate working directory
149
141
  if (!isPathWithinBaseDir(safeCwd, baseDir)) {
150
142
  const errorMsg = `Working directory must be within the project directory: ${baseDir}`;
151
143
  sendData?.({ event: "tool-error", id: toolCallId, data: errorMsg });
152
144
  return errorMsg;
153
145
  }
154
- // Validate command arguments for paths outside baseDir
155
- const tokens = tokenize(command);
156
- for (let i = 0; i < tokens.length; i++) {
157
- const token = tokens[i];
158
- if (token == null)
159
- continue;
160
- const part = token.unquoted;
161
- if (shouldSkipPathValidation(tokens, i)) {
162
- continue;
163
- }
164
- if (looksLikeUrl(part)) {
165
- continue;
166
- }
167
- if (part.startsWith("/") ||
168
- part.includes("../") ||
169
- part.includes("./") ||
170
- (part.includes("/") && !part.startsWith("-"))) {
171
- try {
172
- const resolvedPath = path.resolve(safeCwd, part);
173
- if (!isPathWithinBaseDir(resolvedPath, baseDir)) {
174
- const errorMsg = `Command argument references path outside the project directory: ${part} (resolved to ${resolvedPath})`;
175
- sendData?.({
176
- event: "tool-error",
177
- id: toolCallId,
178
- data: errorMsg,
179
- });
180
- return errorMsg;
181
- }
182
- }
183
- catch (e) {
184
- console.info(`Could not resolve potential path argument: ${part}`, e);
185
- }
186
- }
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) {
159
+ sendData?.({
160
+ event: "tool-error",
161
+ id: toolCallId,
162
+ data: pathValidation.error ?? "Unknown error.",
163
+ });
164
+ return pathValidation.error ?? "Unknown error.";
187
165
  }
188
166
  // Prompt user for command execution approval (only in interactive mode)
189
167
  if (terminal) {
190
168
  if (!autoAcceptCommands) {
191
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();
192
174
  }
193
- terminal.lineBreak();
194
- terminal.writeln(`${chalk.blue.bold("●")} About to execute command: ${chalk.cyan(command)}`);
195
- terminal.writeln(`${chalk.gray("Working directory:")} ${safeCwd}`);
196
- terminal.lineBreak();
197
175
  let userChoice;
198
176
  if (autoAcceptCommands) {
199
177
  terminal.writeln(chalk.green("✓ Auto-accepting command (all future commands will be accepted)"));
@@ -231,6 +209,7 @@ export const createBashTool = ({ baseDir, sendData, tokenCounter, terminal, auto
231
209
  return rejectionMsg;
232
210
  }
233
211
  }
212
+ // Execute command
234
213
  try {
235
214
  const result = await executeCommand(command, {
236
215
  cwd: safeCwd,
@@ -262,28 +241,27 @@ export const createBashTool = ({ baseDir, sendData, tokenCounter, terminal, auto
262
241
  }
263
242
  catch (tokenError) {
264
243
  console.error("Error calculating token count:", tokenError);
265
- // Log or handle error, but don't block file return
266
244
  }
267
245
  const maxTokens = (await config.readProjectConfig()).tools.maxTokens;
268
- // Adjust max token check message if line selection was used
269
- const maxTokenMessage = `Output of commmand (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}). Please adjust how you call the command to get back more specific results`;
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`;
270
247
  const finalResult = tokenCount <= maxTokens ? formattedResult : maxTokenMessage;
271
248
  sendData?.({
272
249
  event: "tool-completion",
273
250
  id: toolCallId,
274
251
  data: tokenCount <= maxTokens
275
252
  ? "Command executed successfully."
276
- : `Output of commmand (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}).`,
253
+ : `Output of command (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}).`,
277
254
  });
278
255
  return finalResult;
279
256
  }
280
257
  catch (error) {
258
+ const errorMsg = `Command failed: ${error.message}`;
281
259
  sendData?.({
282
260
  event: "tool-error",
283
261
  id: toolCallId,
284
- data: `Command failed: ${error.message}`,
262
+ data: errorMsg,
285
263
  });
286
- return `Command failed: ${error.message}`;
264
+ return errorMsg;
287
265
  }
288
266
  },
289
267
  }),
@@ -8,10 +8,20 @@ import { z } from "zod";
8
8
  export const CodeInterpreterTool = {
9
9
  name: "codeInterpreter",
10
10
  };
11
+ const toolDescription = `Executes JavaScript code in a separate Node.js process using Node's Permission Model. By default, the child process has no permissions except read/write within the current working directory. The tool returns stdout, stderr, and exitCode. Use console.log/console.error to produce output.
12
+
13
+ ⚠️ **IMPORTANT**: This tool uses ES Modules (ESM) only.
14
+ - Use \`import\` statements, NOT \`require()\`
15
+ - Examples: \`import fs from 'node:fs'\` NOT \`const fs = require('fs')\`
16
+ - Add file extensions for relative imports: \`import { utils } from './utils.js'\`
17
+
18
+ These scripts are run in the \`${process.cwd}/.acai-ci-tmp\`. Keep this in mind if you intend to import or reference files from this project in your script.
19
+
20
+ Timeout defaults to 5 seconds and can be extended up to 60 seconds.`;
11
21
  export const createCodeInterpreterTool = ({ sendData, }) => {
12
22
  return {
13
23
  [CodeInterpreterTool.name]: tool({
14
- description: "Executes JavaScript code in a separate Node.js process using Node's Permission Model. By default, the child process has no permissions except read/write within the current working directory. The tool returns stdout, stderr, and exitCode. Use console.log/console.error to produce output. Timeout defaults to 5 seconds and can be extended up to 60 seconds.",
24
+ description: toolDescription,
15
25
  inputSchema: z.object({
16
26
  code: z.string().describe("JavaScript code to be executed."),
17
27
  timeoutSeconds: z
@@ -1,8 +1,12 @@
1
1
  export declare class CommandValidation {
2
2
  private readonly allowedCommands;
3
- private readonly unsafeOperatorPatterns;
3
+ private readonly dangerousPatterns;
4
4
  constructor(allowedCommands: string[]);
5
5
  private isCommandAllowed;
6
- private hasUnsafeOperators;
7
- isValid(command: string): boolean;
6
+ private hasDangerousPatterns;
7
+ isValid(command: string): {
8
+ isValid: boolean;
9
+ error?: string;
10
+ };
11
+ private splitOnPipes;
8
12
  }
@@ -1,35 +1,40 @@
1
1
  export class CommandValidation {
2
2
  allowedCommands;
3
- unsafeOperatorPatterns;
3
+ dangerousPatterns;
4
4
  constructor(allowedCommands) {
5
5
  this.allowedCommands = allowedCommands;
6
- this.unsafeOperatorPatterns = [
7
- /`/, // backticks
8
- /\$\(/, // $(
9
- />/, // redirect out
10
- /</, // redirect in
6
+ // Only block truly dangerous patterns, not useful shell operations
7
+ this.dangerousPatterns = [
8
+ /`/, // backticks (command substitution)
9
+ /\$\(/, // $() command substitution
10
+ /&&\s*rm\s+-rf/, // dangerous rm chains
11
+ /;\s*rm\s+-rf/, // dangerous rm chains
11
12
  ];
12
13
  }
13
14
  isCommandAllowed(command) {
14
15
  const baseCommand = command.split(" ")[0] || "";
15
16
  return this.allowedCommands.includes(baseCommand);
16
17
  }
17
- hasUnsafeOperators(command) {
18
+ hasDangerousPatterns(command) {
18
19
  // Remove all quoted segments first
19
20
  const stripped = command
20
21
  .replace(/'([^'\\]|\\.)*'/g, "")
21
22
  .replace(/"([^"\\]|\\.)*"/g, "");
22
- // Check for unsafe operators only in unquoted portions
23
- return this.unsafeOperatorPatterns.some((re) => re.test(stripped));
23
+ // Check for dangerous patterns only in unquoted portions
24
+ return this.dangerousPatterns.some((re) => re.test(stripped));
24
25
  }
25
26
  isValid(command) {
26
- if (!command.trim())
27
- return false;
28
- // First check for unsafe operators in unquoted portions
29
- if (this.hasUnsafeOperators(command)) {
30
- return false;
27
+ if (!command.trim()) {
28
+ return { isValid: false, error: "Command cannot be empty" };
31
29
  }
32
- // Process command while preserving quoted strings
30
+ // First check for dangerous patterns
31
+ if (this.hasDangerousPatterns(command)) {
32
+ return {
33
+ isValid: false,
34
+ error: "Command contains dangerous patterns (command substitution or unsafe rm chains)",
35
+ };
36
+ }
37
+ // Process command while preserving quoted strings to extract sub-commands
33
38
  const subCommands = [];
34
39
  let currentSegment = "";
35
40
  let inSingleQuote = false;
@@ -41,17 +46,16 @@ export class CommandValidation {
41
46
  inSingleQuote = !inSingleQuote;
42
47
  if (char === '"' && !inSingleQuote)
43
48
  inDoubleQuote = !inDoubleQuote;
44
- // Split on operators only when not in quotes
45
- if (!inSingleQuote &&
46
- !inDoubleQuote &&
47
- (char === "&" || char === "|" || char === ";")) {
49
+ // Split on command separators only when not in quotes
50
+ // Note: We allow pipes (|) and redirects (>, <) but split on command separators
51
+ if (!inSingleQuote && !inDoubleQuote && (char === "&" || char === ";")) {
48
52
  if (currentSegment.trim()) {
49
53
  subCommands.push(currentSegment.trim());
50
54
  currentSegment = "";
51
55
  }
52
- // Skip the operator and any subsequent same operators (like && or ||)
56
+ // Skip the operator and any subsequent same operators (like &&)
53
57
  while (i + 1 < command.length &&
54
- ["&", "|", ";"].includes(command[i + 1] ?? "")) {
58
+ ["&", ";"].includes(command[i + 1] ?? "")) {
55
59
  i++;
56
60
  }
57
61
  }
@@ -63,7 +67,47 @@ export class CommandValidation {
63
67
  if (currentSegment.trim()) {
64
68
  subCommands.push(currentSegment.trim());
65
69
  }
66
- // Validate all sub-commands
67
- return subCommands.every((cmd) => this.isCommandAllowed(cmd));
70
+ // Validate all sub-commands (but be smart about pipes)
71
+ for (const subCmd of subCommands) {
72
+ // For piped commands, validate each part of the pipe
73
+ const pipeParts = this.splitOnPipes(subCmd);
74
+ for (const part of pipeParts) {
75
+ const trimmedPart = part.trim();
76
+ if (trimmedPart && !this.isCommandAllowed(trimmedPart)) {
77
+ const baseCmd = trimmedPart.split(" ")[0] || "";
78
+ return {
79
+ isValid: false,
80
+ error: `Command '${baseCmd}' is not allowed. Allowed commands: ${this.allowedCommands.join(", ")}`,
81
+ };
82
+ }
83
+ }
84
+ }
85
+ return { isValid: true };
86
+ }
87
+ splitOnPipes(command) {
88
+ const parts = [];
89
+ let current = "";
90
+ let inSingleQuote = false;
91
+ let inDoubleQuote = false;
92
+ for (let i = 0; i < command.length; i++) {
93
+ const char = command[i];
94
+ if (char === "'" && !inDoubleQuote)
95
+ inSingleQuote = !inSingleQuote;
96
+ if (char === '"' && !inSingleQuote)
97
+ inDoubleQuote = !inDoubleQuote;
98
+ if (char === "|" && !inSingleQuote && !inDoubleQuote) {
99
+ if (current.trim()) {
100
+ parts.push(current.trim());
101
+ current = "";
102
+ }
103
+ }
104
+ else {
105
+ current += char;
106
+ }
107
+ }
108
+ if (current.trim()) {
109
+ parts.push(current.trim());
110
+ }
111
+ return parts;
68
112
  }
69
113
  }
@@ -1,10 +1,13 @@
1
+ import type { Terminal } from "../terminal/index.ts";
1
2
  import type { SendData } from "./types.ts";
2
3
  export declare const DeleteFileTool: {
3
4
  name: "deleteFile";
4
5
  };
5
- export declare const createDeleteFileTool: ({ workingDir, sendData, }: {
6
+ export declare const createDeleteFileTool: ({ workingDir, sendData, terminal, autoAcceptAll, }: {
6
7
  workingDir: string;
7
8
  sendData?: SendData;
9
+ terminal?: Terminal;
10
+ autoAcceptAll?: boolean;
8
11
  }) => Promise<{
9
12
  deleteFile: import("ai").Tool<{
10
13
  path: string;
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
+ import { input, select } from "@inquirer/prompts";
3
4
  import { tool } from "ai";
4
5
  import chalk from "chalk";
5
6
  import { z } from "zod";
@@ -7,8 +8,9 @@ import { joinWorkingDir, validatePath } from "./filesystem-utils.js";
7
8
  export const DeleteFileTool = {
8
9
  name: "deleteFile",
9
10
  };
10
- export const createDeleteFileTool = async ({ workingDir, sendData, }) => {
11
+ export const createDeleteFileTool = async ({ workingDir, sendData, terminal, autoAcceptAll, }) => {
11
12
  const allowedDirectory = workingDir;
13
+ let autoAcceptDeletes = autoAcceptAll ?? false;
12
14
  return {
13
15
  [DeleteFileTool.name]: tool({
14
16
  description: "Delete a file permanently.",
@@ -32,12 +34,54 @@ export const createDeleteFileTool = async ({ workingDir, sendData, }) => {
32
34
  if (stats.isDirectory()) {
33
35
  throw new Error(`Path is a directory, not a file: ${filePath}`);
34
36
  }
35
- // Delete the original file
37
+ if (terminal) {
38
+ terminal.writeln(`\n${chalk.red.bold("●")} Proposing file deletion: ${chalk.cyan(userPath)}`);
39
+ terminal.lineBreak();
40
+ terminal.writeln("This action cannot be undone.");
41
+ terminal.lineBreak();
42
+ let userChoice;
43
+ if (autoAcceptDeletes) {
44
+ terminal.writeln(chalk.green("✓ Auto-accepting deletions (all future deletions will be accepted)"));
45
+ userChoice = "accept";
46
+ }
47
+ else {
48
+ userChoice = await select({
49
+ message: "What would you like to do with this file?",
50
+ choices: [
51
+ { name: "Accept and delete this file", value: "accept" },
52
+ {
53
+ name: "Accept all future deletions (including this)",
54
+ value: "accept-all",
55
+ },
56
+ { name: "Reject this deletion", value: "reject" },
57
+ ],
58
+ default: "accept",
59
+ });
60
+ }
61
+ terminal.lineBreak();
62
+ if (userChoice === "accept-all") {
63
+ autoAcceptDeletes = true;
64
+ terminal.writeln(chalk.yellow("✓ Auto-accept mode enabled for all future deletions"));
65
+ terminal.lineBreak();
66
+ }
67
+ if (userChoice === "reject") {
68
+ const reason = await input({ message: "Feedback: " });
69
+ terminal.lineBreak();
70
+ sendData?.({
71
+ id: toolCallId,
72
+ event: "tool-completion",
73
+ data: `Deletion rejected by user. Reason: ${reason}`,
74
+ });
75
+ return `The user rejected this deletion. Reason: ${reason}`;
76
+ }
77
+ // If accepted, proceed to delete file
78
+ }
79
+ // Delete the file
36
80
  await fs.unlink(filePath);
37
81
  sendData?.({
38
82
  id: toolCallId,
39
83
  event: "tool-completion",
40
- data: `File deleted successfully: ${userPath}`,
84
+ data: "File deleted successfully",
41
85
  });
42
86
  return `Successfully deleted ${filePath}`;
43
87
  }
@@ -3,6 +3,12 @@ export declare function getDiffStat(): Promise<{
3
3
  insertions: number;
4
4
  deletions: number;
5
5
  }>;
6
+ export declare function getGitStatus(): Promise<{
7
+ added: number;
8
+ modified: number;
9
+ deleted: number;
10
+ untracked: number;
11
+ }>;
6
12
  export declare const inGitDirectory: () => Promise<boolean>;
7
13
  /**
8
14
  * Check if there are uncommitted changes