@travisennis/acai 0.0.11 → 0.0.13

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 (292) hide show
  1. package/README.md +5 -5
  2. package/dist/agent/index.d.ts.map +1 -1
  3. package/dist/agent/index.js +2 -5
  4. package/dist/commands/history/types.d.ts +3 -2
  5. package/dist/commands/history/types.d.ts.map +1 -1
  6. package/dist/commands/init-project/utils.d.ts +0 -1
  7. package/dist/commands/init-project/utils.d.ts.map +1 -1
  8. package/dist/commands/init-project/utils.js +1 -12
  9. package/dist/commands/manager.d.ts.map +1 -1
  10. package/dist/commands/manager.js +6 -19
  11. package/dist/commands/resources/index.d.ts.map +1 -1
  12. package/dist/commands/resources/index.js +4 -1
  13. package/dist/commands/session/index.d.ts.map +1 -1
  14. package/dist/commands/session/index.js +6 -0
  15. package/dist/commands/session/types.d.ts +1 -0
  16. package/dist/commands/session/types.d.ts.map +1 -1
  17. package/dist/commands/share/html-renderer.d.ts.map +1 -1
  18. package/dist/commands/share/html-renderer.js +54 -48
  19. package/dist/commands/tools/index.d.ts +3 -0
  20. package/dist/commands/tools/index.d.ts.map +1 -0
  21. package/dist/commands/tools/index.js +191 -0
  22. package/dist/commands/tools/templates.d.ts +6 -0
  23. package/dist/commands/tools/templates.d.ts.map +1 -0
  24. package/dist/commands/tools/templates.js +97 -0
  25. package/dist/config/index.d.ts +5 -2
  26. package/dist/config/index.d.ts.map +1 -1
  27. package/dist/config/index.js +42 -6
  28. package/dist/index.d.ts +14 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +98 -68
  31. package/dist/models/anthropic-provider.d.ts +1 -1
  32. package/dist/models/deepseek-provider.d.ts +3 -3
  33. package/dist/models/deepseek-provider.js +17 -17
  34. package/dist/models/google-provider.d.ts +2 -4
  35. package/dist/models/google-provider.d.ts.map +1 -1
  36. package/dist/models/google-provider.js +2 -17
  37. package/dist/models/groq-provider.d.ts +2 -4
  38. package/dist/models/groq-provider.d.ts.map +1 -1
  39. package/dist/models/groq-provider.js +3 -21
  40. package/dist/models/manager.d.ts +1 -10
  41. package/dist/models/manager.d.ts.map +1 -1
  42. package/dist/models/manager.js +2 -2
  43. package/dist/models/opencode-go-provider.d.ts +11 -1
  44. package/dist/models/opencode-go-provider.d.ts.map +1 -1
  45. package/dist/models/opencode-go-provider.js +130 -0
  46. package/dist/models/opencode-zen-provider.d.ts +3 -3
  47. package/dist/models/opencode-zen-provider.d.ts.map +1 -1
  48. package/dist/models/opencode-zen-provider.js +26 -32
  49. package/dist/models/openrouter-provider.d.ts +4 -15
  50. package/dist/models/openrouter-provider.d.ts.map +1 -1
  51. package/dist/models/openrouter-provider.js +26 -169
  52. package/dist/models/providers.d.ts +1 -1
  53. package/dist/models/providers.d.ts.map +1 -1
  54. package/dist/models/xai-provider.d.ts +1 -2
  55. package/dist/models/xai-provider.d.ts.map +1 -1
  56. package/dist/models/xai-provider.js +0 -13
  57. package/dist/prompts/manager.d.ts.map +1 -1
  58. package/dist/prompts/manager.js +5 -1
  59. package/dist/prompts/system-prompt.d.ts +1 -2
  60. package/dist/prompts/system-prompt.d.ts.map +1 -1
  61. package/dist/prompts/system-prompt.js +26 -15
  62. package/dist/repl/index.d.ts +28 -13
  63. package/dist/repl/index.d.ts.map +1 -1
  64. package/dist/repl/index.js +242 -305
  65. package/dist/skills/activated-tracker.d.ts +11 -0
  66. package/dist/skills/activated-tracker.d.ts.map +1 -0
  67. package/dist/skills/activated-tracker.js +16 -0
  68. package/dist/skills/index.d.ts +12 -2
  69. package/dist/skills/index.d.ts.map +1 -1
  70. package/dist/skills/index.js +127 -71
  71. package/dist/terminal/control.d.ts +1 -21
  72. package/dist/terminal/control.d.ts.map +1 -1
  73. package/dist/terminal/control.js +1 -32
  74. package/dist/terminal/formatting.d.ts +0 -33
  75. package/dist/terminal/formatting.d.ts.map +1 -1
  76. package/dist/terminal/formatting.js +0 -91
  77. package/dist/terminal/highlight/theme.d.ts +0 -37
  78. package/dist/terminal/highlight/theme.d.ts.map +1 -1
  79. package/dist/terminal/highlight/theme.js +1 -79
  80. package/dist/terminal/keys.d.ts +0 -97
  81. package/dist/terminal/keys.d.ts.map +1 -1
  82. package/dist/terminal/keys.js +0 -194
  83. package/dist/terminal/select-prompt.d.ts.map +1 -1
  84. package/dist/terminal/select-prompt.js +65 -51
  85. package/dist/terminal/style.d.ts +1 -1
  86. package/dist/terminal/style.d.ts.map +1 -1
  87. package/dist/terminal/supports-color.d.ts.map +1 -1
  88. package/dist/terminal/supports-color.js +38 -20
  89. package/dist/terminal/supports-hyperlinks.d.ts +3 -0
  90. package/dist/terminal/supports-hyperlinks.d.ts.map +1 -1
  91. package/dist/terminal/supports-hyperlinks.js +72 -31
  92. package/dist/terminal/table/layout-manager.d.ts +0 -20
  93. package/dist/terminal/table/layout-manager.d.ts.map +1 -1
  94. package/dist/terminal/table/layout-manager.js +68 -44
  95. package/dist/terminal/table/utils.d.ts +0 -1
  96. package/dist/terminal/table/utils.d.ts.map +1 -1
  97. package/dist/terminal/table/utils.js +2 -4
  98. package/dist/tools/apply-patch.d.ts +5 -3
  99. package/dist/tools/apply-patch.d.ts.map +1 -1
  100. package/dist/tools/apply-patch.js +154 -123
  101. package/dist/tools/bash.d.ts +4 -4
  102. package/dist/tools/bash.d.ts.map +1 -1
  103. package/dist/tools/bash.js +27 -13
  104. package/dist/tools/dynamic-tool-loader.d.ts +17 -3
  105. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  106. package/dist/tools/dynamic-tool-loader.js +360 -81
  107. package/dist/tools/edit-file.d.ts +2 -2
  108. package/dist/tools/index.d.ts +27 -151
  109. package/dist/tools/index.d.ts.map +1 -1
  110. package/dist/tools/index.js +16 -21
  111. package/dist/tools/read-file.d.ts +8 -8
  112. package/dist/tools/read-file.d.ts.map +1 -1
  113. package/dist/tools/read-file.js +32 -13
  114. package/dist/tools/save-file.d.ts +4 -4
  115. package/dist/tools/skill.d.ts +2 -1
  116. package/dist/tools/skill.d.ts.map +1 -1
  117. package/dist/tools/skill.js +64 -14
  118. package/dist/tools/types.d.ts +8 -2
  119. package/dist/tools/types.d.ts.map +1 -1
  120. package/dist/tools/web-fetch.d.ts +8 -12
  121. package/dist/tools/web-fetch.d.ts.map +1 -1
  122. package/dist/tools/web-fetch.js +86 -44
  123. package/dist/tools/web-search.d.ts +4 -8
  124. package/dist/tools/web-search.d.ts.map +1 -1
  125. package/dist/tools/web-search.js +41 -33
  126. package/dist/tui/autocomplete/utils.d.ts +0 -15
  127. package/dist/tui/autocomplete/utils.d.ts.map +1 -1
  128. package/dist/tui/autocomplete/utils.js +0 -85
  129. package/dist/tui/autocomplete.d.ts +1 -1
  130. package/dist/tui/autocomplete.d.ts.map +1 -1
  131. package/dist/tui/autocomplete.js +1 -1
  132. package/dist/tui/components/editor.d.ts +12 -0
  133. package/dist/tui/components/editor.d.ts.map +1 -1
  134. package/dist/tui/components/editor.js +181 -195
  135. package/dist/tui/components/footer.d.ts +0 -2
  136. package/dist/tui/components/footer.d.ts.map +1 -1
  137. package/dist/tui/components/footer.js +1 -17
  138. package/dist/tui/components/input.d.ts +4 -0
  139. package/dist/tui/components/input.d.ts.map +1 -1
  140. package/dist/tui/components/input.js +33 -31
  141. package/dist/tui/components/markdown.d.ts +30 -0
  142. package/dist/tui/components/markdown.d.ts.map +1 -1
  143. package/dist/tui/components/markdown.js +274 -242
  144. package/dist/tui/components/modal.d.ts.map +1 -1
  145. package/dist/tui/components/modal.js +11 -11
  146. package/dist/tui/components/select-list.d.ts +5 -0
  147. package/dist/tui/components/select-list.d.ts.map +1 -1
  148. package/dist/tui/components/select-list.js +69 -98
  149. package/dist/tui/components/table.d.ts +6 -0
  150. package/dist/tui/components/table.d.ts.map +1 -1
  151. package/dist/tui/components/table.js +61 -52
  152. package/dist/tui/index.d.ts +1 -8
  153. package/dist/tui/index.d.ts.map +1 -1
  154. package/dist/tui/index.js +0 -8
  155. package/dist/tui/tui.d.ts +9 -0
  156. package/dist/tui/tui.d.ts.map +1 -1
  157. package/dist/tui/tui.js +78 -48
  158. package/dist/tui/utils.d.ts +7 -0
  159. package/dist/tui/utils.d.ts.map +1 -1
  160. package/dist/tui/utils.js +117 -53
  161. package/dist/utils/bash.d.ts.map +1 -1
  162. package/dist/utils/bash.js +121 -104
  163. package/dist/utils/binary-output.d.ts +32 -0
  164. package/dist/utils/binary-output.d.ts.map +1 -0
  165. package/dist/utils/binary-output.js +127 -0
  166. package/dist/utils/command-protection.d.ts +3 -2
  167. package/dist/utils/command-protection.d.ts.map +1 -1
  168. package/dist/utils/command-protection.js +121 -29
  169. package/dist/utils/filesystem/operations.d.ts +0 -15
  170. package/dist/utils/filesystem/operations.d.ts.map +1 -1
  171. package/dist/utils/filesystem/operations.js +1 -38
  172. package/dist/utils/filesystem/security.d.ts.map +1 -1
  173. package/dist/utils/filesystem/security.js +22 -15
  174. package/dist/utils/filetype-detection.d.ts +0 -1
  175. package/dist/utils/filetype-detection.d.ts.map +1 -1
  176. package/dist/utils/filetype-detection.js +0 -12
  177. package/dist/utils/formatting.d.ts +0 -30
  178. package/dist/utils/formatting.d.ts.map +1 -1
  179. package/dist/utils/formatting.js +0 -44
  180. package/dist/utils/git.d.ts +4 -6
  181. package/dist/utils/git.d.ts.map +1 -1
  182. package/dist/utils/git.js +36 -46
  183. package/dist/utils/ignore.d.ts +1 -1
  184. package/dist/utils/ignore.d.ts.map +1 -1
  185. package/dist/utils/ignore.js +1 -1
  186. package/dist/utils/parsing.d.ts +1 -1
  187. package/dist/utils/parsing.d.ts.map +1 -1
  188. package/dist/utils/process.d.ts.map +1 -1
  189. package/dist/utils/process.js +77 -79
  190. package/dist/utils/yaml.d.ts +0 -1
  191. package/dist/utils/yaml.d.ts.map +1 -1
  192. package/dist/utils/yaml.js +80 -89
  193. package/dist/utils/zod.d.ts +0 -3
  194. package/dist/utils/zod.d.ts.map +1 -1
  195. package/dist/utils/zod.js +0 -7
  196. package/package.json +32 -29
  197. package/dist/agent/sub-agent.d.ts +0 -23
  198. package/dist/agent/sub-agent.d.ts.map +0 -1
  199. package/dist/agent/sub-agent.js +0 -109
  200. package/dist/commands/add-directory/index.d.ts +0 -3
  201. package/dist/commands/add-directory/index.d.ts.map +0 -1
  202. package/dist/commands/add-directory/index.js +0 -50
  203. package/dist/commands/add-directory/utils.d.ts +0 -3
  204. package/dist/commands/add-directory/utils.d.ts.map +0 -1
  205. package/dist/commands/add-directory/utils.js +0 -15
  206. package/dist/commands/clear/index.d.ts +0 -3
  207. package/dist/commands/clear/index.d.ts.map +0 -1
  208. package/dist/commands/clear/index.js +0 -13
  209. package/dist/commands/generate-rules/index.d.ts +0 -3
  210. package/dist/commands/generate-rules/index.d.ts.map +0 -1
  211. package/dist/commands/generate-rules/index.js +0 -206
  212. package/dist/commands/generate-rules/service.d.ts +0 -22
  213. package/dist/commands/generate-rules/service.d.ts.map +0 -1
  214. package/dist/commands/generate-rules/service.js +0 -103
  215. package/dist/commands/generate-rules/utils.d.ts +0 -5
  216. package/dist/commands/generate-rules/utils.d.ts.map +0 -1
  217. package/dist/commands/generate-rules/utils.js +0 -25
  218. package/dist/commands/handoff/index.d.ts +0 -3
  219. package/dist/commands/handoff/index.d.ts.map +0 -1
  220. package/dist/commands/handoff/index.js +0 -97
  221. package/dist/commands/handoff/utils.d.ts +0 -4
  222. package/dist/commands/handoff/utils.d.ts.map +0 -1
  223. package/dist/commands/handoff/utils.js +0 -123
  224. package/dist/commands/list-directories/index.d.ts +0 -3
  225. package/dist/commands/list-directories/index.d.ts.map +0 -1
  226. package/dist/commands/list-directories/index.js +0 -35
  227. package/dist/commands/pickup/index.d.ts +0 -3
  228. package/dist/commands/pickup/index.d.ts.map +0 -1
  229. package/dist/commands/pickup/index.js +0 -141
  230. package/dist/commands/pickup/types.d.ts +0 -6
  231. package/dist/commands/pickup/types.d.ts.map +0 -1
  232. package/dist/commands/pickup/types.js +0 -1
  233. package/dist/commands/pickup/utils.d.ts +0 -7
  234. package/dist/commands/pickup/utils.d.ts.map +0 -1
  235. package/dist/commands/pickup/utils.js +0 -56
  236. package/dist/commands/remove-directory/index.d.ts +0 -3
  237. package/dist/commands/remove-directory/index.d.ts.map +0 -1
  238. package/dist/commands/remove-directory/index.js +0 -55
  239. package/dist/commands/review/index.d.ts +0 -3
  240. package/dist/commands/review/index.d.ts.map +0 -1
  241. package/dist/commands/review/index.js +0 -12
  242. package/dist/commands/review/review-panel.d.ts +0 -3
  243. package/dist/commands/review/review-panel.d.ts.map +0 -1
  244. package/dist/commands/review/review-panel.js +0 -186
  245. package/dist/commands/review/utils.d.ts +0 -18
  246. package/dist/commands/review/utils.d.ts.map +0 -1
  247. package/dist/commands/review/utils.js +0 -146
  248. package/dist/commands/shell/index.d.ts +0 -3
  249. package/dist/commands/shell/index.d.ts.map +0 -1
  250. package/dist/commands/shell/index.js +0 -96
  251. package/dist/modes/manager.d.ts +0 -24
  252. package/dist/modes/manager.d.ts.map +0 -1
  253. package/dist/modes/manager.js +0 -77
  254. package/dist/modes/prompts.d.ts +0 -2
  255. package/dist/modes/prompts.d.ts.map +0 -1
  256. package/dist/modes/prompts.js +0 -142
  257. package/dist/subagents/index.d.ts +0 -16
  258. package/dist/subagents/index.d.ts.map +0 -1
  259. package/dist/subagents/index.js +0 -231
  260. package/dist/terminal/index.d.ts +0 -9
  261. package/dist/terminal/index.d.ts.map +0 -1
  262. package/dist/terminal/index.js +0 -8
  263. package/dist/tools/agent.d.ts +0 -27
  264. package/dist/tools/agent.d.ts.map +0 -1
  265. package/dist/tools/agent.js +0 -81
  266. package/dist/tools/directory-tree.d.ts +0 -28
  267. package/dist/tools/directory-tree.d.ts.map +0 -1
  268. package/dist/tools/directory-tree.js +0 -152
  269. package/dist/tools/glob.d.ts +0 -35
  270. package/dist/tools/glob.d.ts.map +0 -1
  271. package/dist/tools/glob.js +0 -159
  272. package/dist/tools/grep.d.ts +0 -100
  273. package/dist/tools/grep.d.ts.map +0 -1
  274. package/dist/tools/grep.js +0 -601
  275. package/dist/tools/ls.d.ts +0 -26
  276. package/dist/tools/ls.d.ts.map +0 -1
  277. package/dist/tools/ls.js +0 -82
  278. package/dist/tui/components/header.d.ts +0 -21
  279. package/dist/tui/components/header.d.ts.map +0 -1
  280. package/dist/tui/components/header.js +0 -63
  281. package/dist/utils/bash/parse.d.ts +0 -19
  282. package/dist/utils/bash/parse.d.ts.map +0 -1
  283. package/dist/utils/bash/parse.js +0 -223
  284. package/dist/utils/bash/quote.d.ts +0 -6
  285. package/dist/utils/bash/quote.d.ts.map +0 -1
  286. package/dist/utils/bash/quote.js +0 -23
  287. package/dist/utils/generators.d.ts +0 -3
  288. package/dist/utils/generators.d.ts.map +0 -1
  289. package/dist/utils/generators.js +0 -25
  290. package/dist/utils/glob.d.ts +0 -52
  291. package/dist/utils/glob.d.ts.map +0 -1
  292. package/dist/utils/glob.js +0 -376
@@ -18,7 +18,166 @@ const toolMetadataSchema = z.object({
18
18
  })),
19
19
  needsApproval: z.boolean().default(true),
20
20
  });
21
- export function parseToolMetadata(output) {
21
+ const KNOWN_EXTENSIONS = [
22
+ ".js",
23
+ ".mjs",
24
+ ".cjs",
25
+ ".sh",
26
+ ".bash",
27
+ ".zsh",
28
+ ".py",
29
+ ".rb",
30
+ ".tool",
31
+ ];
32
+ const EXTENSION_INTERPRETER_MAP = {
33
+ ".js": process.execPath,
34
+ ".mjs": process.execPath,
35
+ ".cjs": process.execPath,
36
+ ".sh": "/bin/bash",
37
+ ".bash": "/bin/bash",
38
+ ".zsh": "/bin/zsh",
39
+ ".py": "python3",
40
+ ".rb": "ruby",
41
+ };
42
+ export function getShebang(scriptPath) {
43
+ try {
44
+ const fd = fs.openSync(scriptPath, "r");
45
+ const buffer = Buffer.alloc(256);
46
+ const bytesRead = fs.readSync(fd, buffer, 0, 256, 0);
47
+ fs.closeSync(fd);
48
+ const content = buffer.toString("utf8", 0, bytesRead);
49
+ if (content.startsWith("#!")) {
50
+ return content.slice(2).trim().split("\n")[0].trim();
51
+ }
52
+ }
53
+ catch {
54
+ // Can't read file, skip
55
+ }
56
+ return null;
57
+ }
58
+ export function parseShebang(shebang, scriptPath) {
59
+ if (shebang.startsWith("/usr/bin/env ")) {
60
+ const interpreter = shebang
61
+ .slice("/usr/bin/env ".length)
62
+ .trim()
63
+ .split(" ")[0];
64
+ return { command: interpreter, args: [scriptPath] };
65
+ }
66
+ const parts = shebang.split(" ");
67
+ return { command: parts[0], args: [...parts.slice(1), scriptPath] };
68
+ }
69
+ export function resolveToolInterpreter(scriptPath) {
70
+ // 1. Check shebang
71
+ const shebang = getShebang(scriptPath);
72
+ if (shebang) {
73
+ return parseShebang(shebang, scriptPath);
74
+ }
75
+ // 2. Check extension
76
+ const ext = path.extname(scriptPath).toLowerCase();
77
+ if (EXTENSION_INTERPRETER_MAP[ext]) {
78
+ return { command: EXTENSION_INTERPRETER_MAP[ext], args: [scriptPath] };
79
+ }
80
+ // 3. Extensionless executable
81
+ if (!ext) {
82
+ try {
83
+ const stats = fs.statSync(scriptPath);
84
+ if (stats.mode & 0o111) {
85
+ return { command: scriptPath, args: [] };
86
+ }
87
+ }
88
+ catch {
89
+ // File doesn't exist or can't stat, skip
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+ export function parseTextSchema(content) {
95
+ const lines = content
96
+ .trim()
97
+ .split("\n")
98
+ .map((l) => l.trim())
99
+ .filter((l) => l && !l.startsWith("#") && !l.startsWith("//"));
100
+ let name;
101
+ let description;
102
+ const parameters = [];
103
+ for (const line of lines) {
104
+ if (line.startsWith("name:")) {
105
+ name = line.slice(5).trim();
106
+ continue;
107
+ }
108
+ if (line.startsWith("description:")) {
109
+ description = line.slice(12).trim();
110
+ continue;
111
+ }
112
+ const param = parseTextSchemaParameter(line);
113
+ if (param) {
114
+ parameters.push(param);
115
+ }
116
+ }
117
+ if (!name || !description) {
118
+ logger.warn("Text schema missing required name or description");
119
+ return null;
120
+ }
121
+ if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name)) {
122
+ logger.warn(`Invalid tool name: ${name}`);
123
+ return null;
124
+ }
125
+ return {
126
+ name,
127
+ description,
128
+ parameters,
129
+ needsApproval: true,
130
+ };
131
+ }
132
+ /**
133
+ * Parse a single line as a text schema parameter definition.
134
+ * Supports formats:
135
+ * - "paramName: type [optional|required] description"
136
+ * - "paramName: [optional|required] description" (type defaults to string)
137
+ * - "paramName: description" (type defaults to string, required)
138
+ */
139
+ function parseTextSchemaParameter(line) {
140
+ // Parse parameters: "paramName: type [optional|required] description text"
141
+ const fullMatch = line.match(/^(\w+):\s*(string|number|boolean)\s+(optional|required)?\s*(.*)$/);
142
+ if (fullMatch) {
143
+ const [, paramName, paramType, requirement, paramDescription] = fullMatch;
144
+ return {
145
+ name: paramName,
146
+ type: paramType,
147
+ description: paramDescription || `Parameter ${paramName}`,
148
+ required: requirement !== "optional",
149
+ };
150
+ }
151
+ // Handle params without type keyword (default to string)
152
+ const simpleMatch = line.match(/^(\w+):\s*(.*)$/);
153
+ if (!simpleMatch) {
154
+ return null;
155
+ }
156
+ const [, paramName, rest] = simpleMatch;
157
+ const typeMatch = rest.match(/^(string|number|boolean)\s*(.*)$/);
158
+ if (typeMatch) {
159
+ const [, paramType, afterType] = typeMatch;
160
+ const optMatch = afterType.match(/^(optional|required)\s*(.*)$/);
161
+ return {
162
+ name: paramName,
163
+ type: paramType,
164
+ description: optMatch
165
+ ? optMatch[2] || `Parameter ${paramName}`
166
+ : afterType || `Parameter ${paramName}`,
167
+ required: optMatch?.[1] !== "optional",
168
+ };
169
+ }
170
+ const optMatch = rest.match(/^(optional|required)\s*(.*)$/);
171
+ return {
172
+ name: paramName,
173
+ type: "string",
174
+ description: optMatch
175
+ ? optMatch[2] || rest
176
+ : rest || `Parameter ${paramName}`,
177
+ required: optMatch?.[1] !== "optional",
178
+ };
179
+ }
180
+ function parseToolMetadata(output) {
22
181
  try {
23
182
  const parsed = JSON.parse(output.trim());
24
183
  return toolMetadataSchema.parse(parsed);
@@ -58,20 +217,29 @@ function generateZodSchema(parameters) {
58
217
  }
59
218
  return z.object(fields);
60
219
  }
61
- async function getMetadata(scriptPath) {
220
+ async function getMetadata(scriptPath, sessionContext) {
221
+ const interpreter = resolveToolInterpreter(scriptPath);
222
+ if (!interpreter) {
223
+ logger.warn(`No valid interpreter for ${scriptPath}, skipping.`);
224
+ return null;
225
+ }
62
226
  return new Promise((resolve) => {
63
227
  let child;
64
228
  try {
65
- // Find the node executable path
66
- const nodePath = process.execPath;
67
- child = spawn(nodePath, [scriptPath], {
68
- env: {
69
- ...process.env,
70
- // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
71
- TOOL_ACTION: "describe",
72
- // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
73
- NODE_ENV: "production",
74
- },
229
+ const env = {
230
+ ...process.env,
231
+ // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
232
+ TOOL_ACTION: "describe",
233
+ // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
234
+ NODE_ENV: "production",
235
+ };
236
+ if (sessionContext) {
237
+ env["ACAI_SESSION_ID"] = sessionContext.sessionId;
238
+ env["ACAI_PROJECT_DIR"] = sessionContext.projectDir;
239
+ env["ACAI_AGENT_NAME"] = sessionContext.agentName;
240
+ }
241
+ child = spawn(interpreter.command, interpreter.args, {
242
+ env,
75
243
  stdio: ["ignore", "pipe", "pipe"],
76
244
  });
77
245
  }
@@ -96,11 +264,18 @@ async function getMetadata(scriptPath) {
96
264
  }
97
265
  try {
98
266
  const metadata = parseToolMetadata(stdout);
99
- resolve(metadata);
267
+ resolve({ ...metadata, format: "json" });
100
268
  }
101
- catch (e) {
102
- logger.error(`Failed to parse metadata from ${scriptPath}: ${String(e)}`);
103
- resolve(null);
269
+ catch {
270
+ // JSON parse failed, try text format
271
+ const textMetadata = parseTextSchema(stdout);
272
+ if (textMetadata) {
273
+ resolve({ ...textMetadata, format: "text" });
274
+ }
275
+ else {
276
+ logger.error(`Failed to parse metadata from ${scriptPath}: not valid JSON or text format`);
277
+ resolve(null);
278
+ }
104
279
  }
105
280
  });
106
281
  child.on("error", (err) => {
@@ -109,22 +284,70 @@ async function getMetadata(scriptPath) {
109
284
  });
110
285
  });
111
286
  }
112
- async function spawnChildProcess(scriptPath, params, abortSignal) {
113
- const paramsArray = Object.entries(params).map(([name, value]) => ({
114
- name,
115
- value,
116
- }));
117
- const paramsJson = JSON.stringify(paramsArray);
287
+ /**
288
+ * Processes child process stdout/stderr into a result string.
289
+ * Trims, truncates oversized output, falls back to stderr or a placeholder,
290
+ * and attempts JSON parsing for structured output.
291
+ */
292
+ export function processChildOutput(stdout, stderr) {
293
+ const maxOutputBytes = 2_000_000;
294
+ let output = stdout.trim();
295
+ if (output.length > maxOutputBytes) {
296
+ output = `${output.substring(0, maxOutputBytes)}\n[Output truncated]`;
297
+ }
298
+ if (!output) {
299
+ const errText = stderr.trim();
300
+ if (errText) {
301
+ output = errText;
302
+ }
303
+ }
304
+ if (!output) {
305
+ output = "[No output from dynamic tool]";
306
+ }
307
+ if (output.startsWith("{") || output.startsWith("[")) {
308
+ try {
309
+ return JSON.stringify(JSON.parse(output));
310
+ }
311
+ catch {
312
+ return output;
313
+ }
314
+ }
315
+ return output;
316
+ }
317
+ async function spawnChildProcess(scriptPath, params, abortSignal, sessionContext, format = "json") {
318
+ const interpreter = resolveToolInterpreter(scriptPath);
319
+ if (!interpreter) {
320
+ throw new Error(`No valid interpreter for ${scriptPath}`);
321
+ }
322
+ let paramsInput;
323
+ if (format === "text") {
324
+ paramsInput = `${Object.entries(params)
325
+ .map(([key, value]) => `${key}=${value}`)
326
+ .join("\n")}\n`;
327
+ }
328
+ else {
329
+ const paramsArray = Object.entries(params).map(([name, value]) => ({
330
+ name,
331
+ value,
332
+ }));
333
+ paramsInput = `${JSON.stringify(paramsArray)}\n`;
334
+ }
335
+ const env = {
336
+ ...process.env,
337
+ // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
338
+ TOOL_ACTION: "execute",
339
+ // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
340
+ NODE_ENV: "production",
341
+ };
342
+ if (sessionContext) {
343
+ env["ACAI_SESSION_ID"] = sessionContext.sessionId;
344
+ env["ACAI_PROJECT_DIR"] = sessionContext.projectDir;
345
+ env["ACAI_AGENT_NAME"] = sessionContext.agentName;
346
+ }
118
347
  return new Promise((resolve, reject) => {
119
- const child = spawn(process.execPath, [scriptPath], {
348
+ const child = spawn(interpreter.command, interpreter.args, {
120
349
  cwd: path.dirname(scriptPath),
121
- env: {
122
- ...process.env,
123
- // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
124
- TOOL_ACTION: "execute",
125
- // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
126
- NODE_ENV: "production",
127
- },
350
+ env,
128
351
  stdio: ["pipe", "pipe", "pipe"],
129
352
  });
130
353
  let stdout = "";
@@ -135,7 +358,7 @@ async function spawnChildProcess(scriptPath, params, abortSignal) {
135
358
  child.kill();
136
359
  reject(new Error("Execution timed out after 30 seconds"));
137
360
  }, 30000);
138
- child.stdin.write(`${paramsJson}\n`);
361
+ child.stdin.write(paramsInput);
139
362
  child.stdin.end();
140
363
  child.stdout.on("data", (data) => {
141
364
  stdout += data.toString();
@@ -147,32 +370,7 @@ async function spawnChildProcess(scriptPath, params, abortSignal) {
147
370
  clearTimeout(timer);
148
371
  if (hasTimedOut)
149
372
  return;
150
- let output = stdout.trim();
151
- const maxOutputBytes = 2000000;
152
- if (output.length > maxOutputBytes) {
153
- output = `${output.substring(0, maxOutputBytes)}\n[Output truncated]`;
154
- }
155
- // If no stdout, prefer stderr so callers get useful info
156
- const errText = stderr.trim();
157
- if (!output && errText) {
158
- output = errText;
159
- }
160
- // Fallback to a non-empty placeholder to satisfy callers
161
- if (!output) {
162
- output = "[No output from dynamic tool]";
163
- }
164
- // Attempt to parse as JSON if structured
165
- if (output && (output.startsWith("{") || output.startsWith("["))) {
166
- try {
167
- resolve(JSON.stringify(JSON.parse(output)));
168
- }
169
- catch {
170
- resolve(output);
171
- }
172
- }
173
- else {
174
- resolve(output);
175
- }
373
+ resolve(processChildOutput(stdout, stderr));
176
374
  });
177
375
  if (abortSignal) {
178
376
  abortSignal.addEventListener("abort", () => {
@@ -181,9 +379,10 @@ async function spawnChildProcess(scriptPath, params, abortSignal) {
181
379
  }
182
380
  });
183
381
  }
184
- function createDynamicTool(scriptPath, metadata) {
382
+ function createDynamicTool(scriptPath, metadata, sessionContext) {
185
383
  const inputSchema = generateZodSchema(metadata.parameters);
186
384
  const toolName = metadata.name;
385
+ const format = metadata.format;
187
386
  return {
188
387
  [toolName]: {
189
388
  toolDef: {
@@ -193,7 +392,7 @@ function createDynamicTool(scriptPath, metadata) {
193
392
  display() {
194
393
  return "running";
195
394
  },
196
- async execute(input, { abortSignal }) {
395
+ async execute(input, { abortSignal, sessionContext: executionContext }) {
197
396
  if (abortSignal?.aborted) {
198
397
  throw new Error("Execution aborted");
199
398
  }
@@ -203,12 +402,36 @@ function createDynamicTool(scriptPath, metadata) {
203
402
  catch (e) {
204
403
  throw new Error(`Invalid parameters for tool ${metadata.name}: ${e.message}`);
205
404
  }
206
- return spawnChildProcess(scriptPath, input, abortSignal);
405
+ // Prefer execution-time context over load-time context
406
+ const context = executionContext ?? sessionContext;
407
+ return spawnChildProcess(scriptPath, input, abortSignal, context, format);
207
408
  },
208
409
  },
209
410
  };
210
411
  }
211
- export async function loadDynamicTools({ baseDir, existingToolNames = [], }) {
412
+ function findCompanion(dir, baseName) {
413
+ const companionExtensions = [
414
+ ".sh",
415
+ ".bash",
416
+ ".zsh",
417
+ ".py",
418
+ ".rb",
419
+ ".js",
420
+ ".mjs",
421
+ ".cjs",
422
+ "",
423
+ ];
424
+ for (const ext of companionExtensions) {
425
+ const candidate = path.join(dir, baseName + ext);
426
+ if (fs.existsSync(candidate)) {
427
+ const interpreter = resolveToolInterpreter(candidate);
428
+ if (interpreter)
429
+ return candidate;
430
+ }
431
+ }
432
+ return null;
433
+ }
434
+ export async function loadDynamicTools({ baseDir, existingToolNames = [], sessionContext, }) {
212
435
  const projectConfig = await config.getConfig();
213
436
  const dynamicConfig = projectConfig.tools.dynamicTools;
214
437
  if (!dynamicConfig.enabled) {
@@ -218,30 +441,86 @@ export async function loadDynamicTools({ baseDir, existingToolNames = [], }) {
218
441
  const projectToolsDir = path.join(baseDir, ".acai", "tools");
219
442
  const userToolsDir = path.join(os.homedir(), ".acai", "tools");
220
443
  const toolMap = new Map();
221
- const scanDir = async (dir, isProject = false) => {
222
- if (!fs.existsSync(dir))
223
- return;
224
- try {
225
- const files = fs
226
- .readdirSync(dir)
227
- .filter((f) => f.endsWith(".js") || f.endsWith(".mjs"));
228
- for (const file of files) {
229
- const scriptPath = path.join(dir, file);
444
+ const filterEligibleFiles = (dir) => {
445
+ return fs.readdirSync(dir).filter((f) => {
446
+ if (KNOWN_EXTENSIONS.some((ext) => f.endsWith(ext))) {
447
+ return true;
448
+ }
449
+ if (!path.extname(f)) {
450
+ const fullPath = path.join(dir, f);
230
451
  try {
231
- const metadata = await getMetadata(scriptPath);
232
- if (metadata) {
233
- toolMap.set(metadata.name, { path: scriptPath, metadata });
234
- logger.info(`Loaded ${isProject ? "project" : "user"} tool: ${metadata.name}`);
235
- }
236
- else {
237
- logger.warn(`Skipped invalid tool: ${file}`);
238
- }
452
+ const stats = fs.statSync(fullPath);
453
+ if (stats.mode & 0o111)
454
+ return true;
239
455
  }
240
- catch (e) {
241
- logger.error(`Error scanning ${file}: ${e}`);
456
+ catch {
457
+ // Can't stat, skip
458
+ }
459
+ }
460
+ return false;
461
+ });
462
+ };
463
+ const loadScriptTools = async (dir, files, isProject) => {
464
+ for (const file of files) {
465
+ if (file.endsWith(".tool"))
466
+ continue;
467
+ const scriptPath = path.join(dir, file);
468
+ try {
469
+ const metadata = await getMetadata(scriptPath, sessionContext);
470
+ if (metadata) {
471
+ toolMap.set(metadata.name, { path: scriptPath, metadata });
472
+ logger.info(`Loaded ${isProject ? "project" : "user"} tool: ${metadata.name}`);
473
+ }
474
+ else {
475
+ logger.warn(`Skipped invalid tool: ${file}`);
476
+ }
477
+ }
478
+ catch (e) {
479
+ logger.error(`Error scanning ${file}: ${e}`);
480
+ }
481
+ }
482
+ };
483
+ const loadDotToolFiles = async (dir, isProject) => {
484
+ const allDirFiles = fs.readdirSync(dir);
485
+ const allToolFiles = allDirFiles.filter((f) => f.endsWith(".tool"));
486
+ for (const toolFile of allToolFiles) {
487
+ const toolPath = path.join(dir, toolFile);
488
+ try {
489
+ const content = fs.readFileSync(toolPath, "utf8");
490
+ const metadata = parseTextSchema(content);
491
+ if (!metadata) {
492
+ logger.warn(`Failed to parse .tool file: ${toolFile}`);
493
+ continue;
242
494
  }
495
+ const baseName = toolFile.slice(0, -5);
496
+ const companionPath = findCompanion(dir, baseName);
497
+ if (!companionPath) {
498
+ logger.warn(`No companion executable found for ${toolFile}`);
499
+ continue;
500
+ }
501
+ const metadataWithFormat = {
502
+ ...metadata,
503
+ format: "text",
504
+ };
505
+ toolMap.set(metadata.name, {
506
+ path: companionPath,
507
+ metadata: metadataWithFormat,
508
+ });
509
+ logger.info(`Loaded ${isProject ? "project" : "user"} tool: ${metadata.name} (from .tool file)`);
510
+ }
511
+ catch (e) {
512
+ logger.error(`Error reading .tool file ${toolFile}: ${e}`);
243
513
  }
244
514
  }
515
+ };
516
+ const scanDir = async (dir, isProject = false) => {
517
+ if (!fs.existsSync(dir))
518
+ return;
519
+ try {
520
+ const files = filterEligibleFiles(dir);
521
+ await loadScriptTools(dir, files, isProject);
522
+ await loadDotToolFiles(dir, isProject);
523
+ }
245
524
  catch (e) {
246
525
  logger.error(`Error reading dir ${dir}: ${e}`);
247
526
  }
@@ -274,7 +553,7 @@ export async function loadDynamicTools({ baseDir, existingToolNames = [], }) {
274
553
  logger.warn(`Duplicate dynamic tool name '${toolName}' found. Skipping duplicate.`);
275
554
  continue;
276
555
  }
277
- Object.assign(tools, createDynamicTool(path, metadata));
556
+ Object.assign(tools, createDynamicTool(path, metadata, sessionContext));
278
557
  }
279
558
  if (conflictingTools.length > 0) {
280
559
  logger.warn(`Warning: ${conflictingTools.length} dynamic tool(s) skipped due to name conflicts: ${conflictingTools.join(", ")}`);
@@ -6,7 +6,7 @@ export declare const EditFileTool: {
6
6
  };
7
7
  declare const inputSchema: z.ZodObject<{
8
8
  path: z.ZodString;
9
- edits: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodArray<z.ZodObject<{
9
+ edits: z.ZodPreprocess<z.ZodArray<z.ZodObject<{
10
10
  oldText: z.ZodString;
11
11
  newText: z.ZodString;
12
12
  }, z.core.$strip>>>;
@@ -19,7 +19,7 @@ export declare const createEditFileTool: (options: {
19
19
  description: string;
20
20
  inputSchema: z.ZodObject<{
21
21
  path: z.ZodString;
22
- edits: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodArray<z.ZodObject<{
22
+ edits: z.ZodPreprocess<z.ZodArray<z.ZodObject<{
23
23
  oldText: z.ZodString;
24
24
  newText: z.ZodString;
25
25
  }, z.core.$strip>>>;