@travisennis/acai 0.0.10 → 0.0.12

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 (179) hide show
  1. package/README.md +7 -4
  2. package/dist/agent/index.d.ts.map +1 -1
  3. package/dist/agent/index.js +29 -27
  4. package/dist/cli/stdin.d.ts +2 -1
  5. package/dist/cli/stdin.d.ts.map +1 -1
  6. package/dist/commands/generate-rules/service.d.ts +3 -2
  7. package/dist/commands/generate-rules/service.d.ts.map +1 -1
  8. package/dist/commands/health/utils.d.ts +3 -2
  9. package/dist/commands/health/utils.d.ts.map +1 -1
  10. package/dist/commands/init-project/utils.d.ts +2 -1
  11. package/dist/commands/init-project/utils.d.ts.map +1 -1
  12. package/dist/commands/init-project/utils.js +0 -11
  13. package/dist/commands/manager.d.ts.map +1 -1
  14. package/dist/commands/manager.js +6 -1
  15. package/dist/commands/resources/index.d.ts.map +1 -1
  16. package/dist/commands/resources/index.js +4 -1
  17. package/dist/commands/review/utils.d.ts +6 -1
  18. package/dist/commands/review/utils.d.ts.map +1 -1
  19. package/dist/commands/session/index.d.ts.map +1 -1
  20. package/dist/commands/session/index.js +6 -0
  21. package/dist/commands/session/types.d.ts +1 -0
  22. package/dist/commands/session/types.d.ts.map +1 -1
  23. package/dist/commands/tools/index.d.ts +3 -0
  24. package/dist/commands/tools/index.d.ts.map +1 -0
  25. package/dist/commands/tools/index.js +190 -0
  26. package/dist/commands/tools/templates.d.ts +6 -0
  27. package/dist/commands/tools/templates.d.ts.map +1 -0
  28. package/dist/commands/tools/templates.js +97 -0
  29. package/dist/config/index.d.ts +5 -0
  30. package/dist/config/index.d.ts.map +1 -1
  31. package/dist/config/index.js +41 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +15 -3
  34. package/dist/models/anthropic-provider.d.ts +1 -1
  35. package/dist/models/deepseek-provider.d.ts +3 -3
  36. package/dist/models/deepseek-provider.js +17 -17
  37. package/dist/models/google-provider.d.ts +2 -4
  38. package/dist/models/google-provider.d.ts.map +1 -1
  39. package/dist/models/google-provider.js +2 -17
  40. package/dist/models/groq-provider.d.ts +2 -4
  41. package/dist/models/groq-provider.d.ts.map +1 -1
  42. package/dist/models/groq-provider.js +3 -21
  43. package/dist/models/opencode-go-provider.d.ts +35 -0
  44. package/dist/models/opencode-go-provider.d.ts.map +1 -0
  45. package/dist/models/opencode-go-provider.js +214 -0
  46. package/dist/models/opencode-zen-provider.d.ts +5 -5
  47. package/dist/models/opencode-zen-provider.d.ts.map +1 -1
  48. package/dist/models/opencode-zen-provider.js +41 -47
  49. package/dist/models/openrouter-provider.d.ts +5 -13
  50. package/dist/models/openrouter-provider.d.ts.map +1 -1
  51. package/dist/models/openrouter-provider.js +34 -138
  52. package/dist/models/providers.d.ts +3 -3
  53. package/dist/models/providers.d.ts.map +1 -1
  54. package/dist/models/providers.js +6 -0
  55. package/dist/models/xai-provider.d.ts +1 -2
  56. package/dist/models/xai-provider.d.ts.map +1 -1
  57. package/dist/models/xai-provider.js +0 -13
  58. package/dist/prompts/manager.d.ts.map +1 -1
  59. package/dist/prompts/manager.js +5 -1
  60. package/dist/prompts/mentions.d.ts.map +1 -1
  61. package/dist/prompts/mentions.js +35 -6
  62. package/dist/prompts/system-prompt.d.ts +1 -0
  63. package/dist/prompts/system-prompt.d.ts.map +1 -1
  64. package/dist/prompts/system-prompt.js +20 -5
  65. package/dist/repl/index.d.ts +1 -2
  66. package/dist/repl/index.d.ts.map +1 -1
  67. package/dist/repl/index.js +14 -53
  68. package/dist/sessions/manager.d.ts +3 -3
  69. package/dist/sessions/manager.d.ts.map +1 -1
  70. package/dist/sessions/manager.js +1 -1
  71. package/dist/skills/activated-tracker.d.ts +11 -0
  72. package/dist/skills/activated-tracker.d.ts.map +1 -0
  73. package/dist/skills/activated-tracker.js +16 -0
  74. package/dist/skills/index.d.ts +3 -2
  75. package/dist/skills/index.d.ts.map +1 -1
  76. package/dist/skills/index.js +7 -1
  77. package/dist/subagents/index.d.ts +2 -1
  78. package/dist/subagents/index.d.ts.map +1 -1
  79. package/dist/terminal/table/utils.d.ts +1 -1
  80. package/dist/terminal/table/utils.d.ts.map +1 -1
  81. package/dist/terminal/wrap-ansi.js +2 -2
  82. package/dist/tools/agent.js +1 -1
  83. package/dist/tools/apply-patch.d.ts +62 -0
  84. package/dist/tools/apply-patch.d.ts.map +1 -0
  85. package/dist/tools/apply-patch.js +377 -0
  86. package/dist/tools/bash.d.ts +4 -4
  87. package/dist/tools/bash.d.ts.map +1 -1
  88. package/dist/tools/bash.js +40 -8
  89. package/dist/tools/directory-tree.d.ts +4 -4
  90. package/dist/tools/directory-tree.d.ts.map +1 -1
  91. package/dist/tools/directory-tree.js +3 -1
  92. package/dist/tools/dynamic-tool-loader.d.ts +12 -3
  93. package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
  94. package/dist/tools/dynamic-tool-loader.js +299 -39
  95. package/dist/tools/edit-file.d.ts +2 -2
  96. package/dist/tools/edit-file.d.ts.map +1 -1
  97. package/dist/tools/edit-file.js +188 -79
  98. package/dist/tools/glob.d.ts +16 -16
  99. package/dist/tools/glob.d.ts.map +1 -1
  100. package/dist/tools/glob.js +30 -15
  101. package/dist/tools/grep.d.ts +14 -14
  102. package/dist/tools/grep.d.ts.map +1 -1
  103. package/dist/tools/grep.js +50 -29
  104. package/dist/tools/index.d.ts +57 -84
  105. package/dist/tools/index.d.ts.map +1 -1
  106. package/dist/tools/index.js +20 -5
  107. package/dist/tools/ls.d.ts +2 -2
  108. package/dist/tools/ls.d.ts.map +1 -1
  109. package/dist/tools/ls.js +2 -1
  110. package/dist/tools/read-file.d.ts +9 -11
  111. package/dist/tools/read-file.d.ts.map +1 -1
  112. package/dist/tools/read-file.js +21 -16
  113. package/dist/tools/save-file.d.ts +4 -4
  114. package/dist/tools/save-file.d.ts.map +1 -1
  115. package/dist/tools/save-file.js +26 -21
  116. package/dist/tools/skill.d.ts +2 -1
  117. package/dist/tools/skill.d.ts.map +1 -1
  118. package/dist/tools/skill.js +55 -12
  119. package/dist/tools/types.d.ts +8 -2
  120. package/dist/tools/types.d.ts.map +1 -1
  121. package/dist/tools/web-fetch.d.ts +6 -18
  122. package/dist/tools/web-fetch.d.ts.map +1 -1
  123. package/dist/tools/web-fetch.js +45 -9
  124. package/dist/tools/web-search.d.ts +4 -22
  125. package/dist/tools/web-search.d.ts.map +1 -1
  126. package/dist/tools/web-search.js +1 -1
  127. package/dist/tui/autocomplete/file-search-provider.js +1 -1
  128. package/dist/tui/autocomplete/utils.d.ts +2 -1
  129. package/dist/tui/autocomplete/utils.d.ts.map +1 -1
  130. package/dist/tui/autocomplete/utils.js +25 -23
  131. package/dist/tui/components/editor.d.ts +2 -1
  132. package/dist/tui/components/editor.d.ts.map +1 -1
  133. package/dist/tui/components/editor.js +1 -1
  134. package/dist/tui/components/footer.d.ts +0 -2
  135. package/dist/tui/components/footer.d.ts.map +1 -1
  136. package/dist/tui/components/footer.js +1 -17
  137. package/dist/tui/components/markdown.d.ts +2 -2
  138. package/dist/tui/components/markdown.d.ts.map +1 -1
  139. package/dist/tui/components/welcome.d.ts +2 -1
  140. package/dist/tui/components/welcome.d.ts.map +1 -1
  141. package/dist/tui/editor-launcher.d.ts +3 -2
  142. package/dist/tui/editor-launcher.d.ts.map +1 -1
  143. package/dist/tui/index.d.ts +0 -1
  144. package/dist/tui/index.d.ts.map +1 -1
  145. package/dist/tui/tui.d.ts +1 -0
  146. package/dist/tui/tui.d.ts.map +1 -1
  147. package/dist/tui/tui.js +9 -0
  148. package/dist/tui/utils.d.ts +1 -5
  149. package/dist/tui/utils.d.ts.map +1 -1
  150. package/dist/tui/utils.js +271 -44
  151. package/dist/utils/binary-output.d.ts +32 -0
  152. package/dist/utils/binary-output.d.ts.map +1 -0
  153. package/dist/utils/binary-output.js +127 -0
  154. package/dist/utils/command-protection.d.ts.map +1 -1
  155. package/dist/utils/command-protection.js +92 -9
  156. package/dist/utils/parsing.d.ts +1 -1
  157. package/dist/utils/parsing.d.ts.map +1 -1
  158. package/package.json +28 -26
  159. package/dist/commands/add-directory/types.d.ts +0 -6
  160. package/dist/commands/add-directory/types.d.ts.map +0 -1
  161. package/dist/commands/add-directory/types.js +0 -1
  162. package/dist/commands/copy/types.d.ts +0 -3
  163. package/dist/commands/copy/types.d.ts.map +0 -1
  164. package/dist/commands/copy/types.js +0 -1
  165. package/dist/commands/review/types.d.ts +0 -12
  166. package/dist/commands/review/types.d.ts.map +0 -1
  167. package/dist/commands/review/types.js +0 -1
  168. package/dist/modes/manager.d.ts +0 -23
  169. package/dist/modes/manager.d.ts.map +0 -1
  170. package/dist/modes/manager.js +0 -77
  171. package/dist/modes/prompts.d.ts +0 -2
  172. package/dist/modes/prompts.d.ts.map +0 -1
  173. package/dist/modes/prompts.js +0 -143
  174. package/dist/tools/code-search.d.ts +0 -41
  175. package/dist/tools/code-search.d.ts.map +0 -1
  176. package/dist/tools/code-search.js +0 -195
  177. package/dist/utils/iterables.d.ts +0 -2
  178. package/dist/utils/iterables.d.ts.map +0 -1
  179. package/dist/utils/iterables.js +0 -6
@@ -18,6 +18,156 @@ const toolMetadataSchema = z.object({
18
18
  })),
19
19
  needsApproval: z.boolean().default(true),
20
20
  });
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
+ // Parse parameters: "paramName: type [optional|required] description text"
113
+ const paramMatch = line.match(/^(\w+):\s*(string|number|boolean)\s+(optional|required)?\s*(.*)$/);
114
+ if (paramMatch) {
115
+ const [, paramName, paramType, requirement, paramDescription] = paramMatch;
116
+ parameters.push({
117
+ name: paramName,
118
+ type: paramType,
119
+ description: paramDescription || `Parameter ${paramName}`,
120
+ required: requirement !== "optional",
121
+ });
122
+ continue;
123
+ }
124
+ // Handle params without type keyword (default to string)
125
+ const simpleMatch = line.match(/^(\w+):\s*(.*)$/);
126
+ if (simpleMatch &&
127
+ !line.startsWith("name:") &&
128
+ !line.startsWith("description:")) {
129
+ const [, paramName, rest] = simpleMatch;
130
+ const typeMatch = rest.match(/^(string|number|boolean)\s*(.*)$/);
131
+ if (typeMatch) {
132
+ const [, paramType, afterType] = typeMatch;
133
+ const optMatch = afterType.match(/^(optional|required)\s*(.*)$/);
134
+ parameters.push({
135
+ name: paramName,
136
+ type: paramType,
137
+ description: optMatch
138
+ ? optMatch[2] || `Parameter ${paramName}`
139
+ : afterType || `Parameter ${paramName}`,
140
+ required: !optMatch || optMatch[1] !== "optional",
141
+ });
142
+ }
143
+ else {
144
+ const optMatch = rest.match(/^(optional|required)\s*(.*)$/);
145
+ parameters.push({
146
+ name: paramName,
147
+ type: "string",
148
+ description: optMatch
149
+ ? optMatch[2] || rest
150
+ : rest || `Parameter ${paramName}`,
151
+ required: !optMatch || optMatch[1] !== "optional",
152
+ });
153
+ }
154
+ }
155
+ }
156
+ if (!name || !description) {
157
+ logger.warn("Text schema missing required name or description");
158
+ return null;
159
+ }
160
+ if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(name)) {
161
+ logger.warn(`Invalid tool name: ${name}`);
162
+ return null;
163
+ }
164
+ return {
165
+ name,
166
+ description,
167
+ parameters,
168
+ needsApproval: true,
169
+ };
170
+ }
21
171
  export function parseToolMetadata(output) {
22
172
  try {
23
173
  const parsed = JSON.parse(output.trim());
@@ -58,20 +208,29 @@ function generateZodSchema(parameters) {
58
208
  }
59
209
  return z.object(fields);
60
210
  }
61
- async function getMetadata(scriptPath) {
211
+ async function getMetadata(scriptPath, sessionContext) {
212
+ const interpreter = resolveToolInterpreter(scriptPath);
213
+ if (!interpreter) {
214
+ logger.warn(`No valid interpreter for ${scriptPath}, skipping.`);
215
+ return null;
216
+ }
62
217
  return new Promise((resolve) => {
63
218
  let child;
64
219
  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
- },
220
+ const env = {
221
+ ...process.env,
222
+ // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
223
+ TOOL_ACTION: "describe",
224
+ // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
225
+ NODE_ENV: "production",
226
+ };
227
+ if (sessionContext) {
228
+ env["ACAI_SESSION_ID"] = sessionContext.sessionId;
229
+ env["ACAI_PROJECT_DIR"] = sessionContext.projectDir;
230
+ env["ACAI_AGENT_NAME"] = sessionContext.agentName;
231
+ }
232
+ child = spawn(interpreter.command, interpreter.args, {
233
+ env,
75
234
  stdio: ["ignore", "pipe", "pipe"],
76
235
  });
77
236
  }
@@ -96,11 +255,18 @@ async function getMetadata(scriptPath) {
96
255
  }
97
256
  try {
98
257
  const metadata = parseToolMetadata(stdout);
99
- resolve(metadata);
258
+ resolve({ ...metadata, format: "json" });
100
259
  }
101
- catch (e) {
102
- logger.error(`Failed to parse metadata from ${scriptPath}: ${String(e)}`);
103
- resolve(null);
260
+ catch {
261
+ // JSON parse failed, try text format
262
+ const textMetadata = parseTextSchema(stdout);
263
+ if (textMetadata) {
264
+ resolve({ ...textMetadata, format: "text" });
265
+ }
266
+ else {
267
+ logger.error(`Failed to parse metadata from ${scriptPath}: not valid JSON or text format`);
268
+ resolve(null);
269
+ }
104
270
  }
105
271
  });
106
272
  child.on("error", (err) => {
@@ -109,22 +275,40 @@ async function getMetadata(scriptPath) {
109
275
  });
110
276
  });
111
277
  }
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);
278
+ async function spawnChildProcess(scriptPath, params, abortSignal, sessionContext, format = "json") {
279
+ const interpreter = resolveToolInterpreter(scriptPath);
280
+ if (!interpreter) {
281
+ throw new Error(`No valid interpreter for ${scriptPath}`);
282
+ }
283
+ let paramsInput;
284
+ if (format === "text") {
285
+ paramsInput = `${Object.entries(params)
286
+ .map(([key, value]) => `${key}=${value}`)
287
+ .join("\n")}\n`;
288
+ }
289
+ else {
290
+ const paramsArray = Object.entries(params).map(([name, value]) => ({
291
+ name,
292
+ value,
293
+ }));
294
+ paramsInput = `${JSON.stringify(paramsArray)}\n`;
295
+ }
296
+ const env = {
297
+ ...process.env,
298
+ // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
299
+ TOOL_ACTION: "execute",
300
+ // biome-ignore lint/style/useNamingConvention: Environment variables are conventionally uppercase
301
+ NODE_ENV: "production",
302
+ };
303
+ if (sessionContext) {
304
+ env["ACAI_SESSION_ID"] = sessionContext.sessionId;
305
+ env["ACAI_PROJECT_DIR"] = sessionContext.projectDir;
306
+ env["ACAI_AGENT_NAME"] = sessionContext.agentName;
307
+ }
118
308
  return new Promise((resolve, reject) => {
119
- const child = spawn(process.execPath, [scriptPath], {
309
+ const child = spawn(interpreter.command, interpreter.args, {
120
310
  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
- },
311
+ env,
128
312
  stdio: ["pipe", "pipe", "pipe"],
129
313
  });
130
314
  let stdout = "";
@@ -135,7 +319,7 @@ async function spawnChildProcess(scriptPath, params, abortSignal) {
135
319
  child.kill();
136
320
  reject(new Error("Execution timed out after 30 seconds"));
137
321
  }, 30000);
138
- child.stdin.write(`${paramsJson}\n`);
322
+ child.stdin.write(paramsInput);
139
323
  child.stdin.end();
140
324
  child.stdout.on("data", (data) => {
141
325
  stdout += data.toString();
@@ -181,9 +365,10 @@ async function spawnChildProcess(scriptPath, params, abortSignal) {
181
365
  }
182
366
  });
183
367
  }
184
- function createDynamicTool(scriptPath, metadata) {
368
+ function createDynamicTool(scriptPath, metadata, sessionContext) {
185
369
  const inputSchema = generateZodSchema(metadata.parameters);
186
370
  const toolName = metadata.name;
371
+ const format = metadata.format;
187
372
  return {
188
373
  [toolName]: {
189
374
  toolDef: {
@@ -193,7 +378,7 @@ function createDynamicTool(scriptPath, metadata) {
193
378
  display() {
194
379
  return "running";
195
380
  },
196
- async execute(input, { abortSignal }) {
381
+ async execute(input, { abortSignal, sessionContext: executionContext }) {
197
382
  if (abortSignal?.aborted) {
198
383
  throw new Error("Execution aborted");
199
384
  }
@@ -203,12 +388,36 @@ function createDynamicTool(scriptPath, metadata) {
203
388
  catch (e) {
204
389
  throw new Error(`Invalid parameters for tool ${metadata.name}: ${e.message}`);
205
390
  }
206
- return spawnChildProcess(scriptPath, input, abortSignal);
391
+ // Prefer execution-time context over load-time context
392
+ const context = executionContext ?? sessionContext;
393
+ return spawnChildProcess(scriptPath, input, abortSignal, context, format);
207
394
  },
208
395
  },
209
396
  };
210
397
  }
211
- export async function loadDynamicTools({ baseDir, existingToolNames = [], }) {
398
+ function findCompanion(dir, baseName) {
399
+ const companionExtensions = [
400
+ ".sh",
401
+ ".bash",
402
+ ".zsh",
403
+ ".py",
404
+ ".rb",
405
+ ".js",
406
+ ".mjs",
407
+ ".cjs",
408
+ "",
409
+ ];
410
+ for (const ext of companionExtensions) {
411
+ const candidate = path.join(dir, baseName + ext);
412
+ if (fs.existsSync(candidate)) {
413
+ const interpreter = resolveToolInterpreter(candidate);
414
+ if (interpreter)
415
+ return candidate;
416
+ }
417
+ }
418
+ return null;
419
+ }
420
+ export async function loadDynamicTools({ baseDir, existingToolNames = [], sessionContext, }) {
212
421
  const projectConfig = await config.getConfig();
213
422
  const dynamicConfig = projectConfig.tools.dynamicTools;
214
423
  if (!dynamicConfig.enabled) {
@@ -222,13 +431,32 @@ export async function loadDynamicTools({ baseDir, existingToolNames = [], }) {
222
431
  if (!fs.existsSync(dir))
223
432
  return;
224
433
  try {
225
- const files = fs
226
- .readdirSync(dir)
227
- .filter((f) => f.endsWith(".js") || f.endsWith(".mjs"));
434
+ const files = fs.readdirSync(dir).filter((f) => {
435
+ // Known extensions
436
+ if (KNOWN_EXTENSIONS.some((ext) => f.endsWith(ext))) {
437
+ return true;
438
+ }
439
+ // Extensionless files that are executable
440
+ if (!path.extname(f)) {
441
+ const fullPath = path.join(dir, f);
442
+ try {
443
+ const stats = fs.statSync(fullPath);
444
+ if (stats.mode & 0o111)
445
+ return true;
446
+ }
447
+ catch {
448
+ // Can't stat, skip
449
+ }
450
+ }
451
+ return false;
452
+ });
228
453
  for (const file of files) {
454
+ // Skip .tool files, they are handled separately
455
+ if (file.endsWith(".tool"))
456
+ continue;
229
457
  const scriptPath = path.join(dir, file);
230
458
  try {
231
- const metadata = await getMetadata(scriptPath);
459
+ const metadata = await getMetadata(scriptPath, sessionContext);
232
460
  if (metadata) {
233
461
  toolMap.set(metadata.name, { path: scriptPath, metadata });
234
462
  logger.info(`Loaded ${isProject ? "project" : "user"} tool: ${metadata.name}`);
@@ -241,6 +469,38 @@ export async function loadDynamicTools({ baseDir, existingToolNames = [], }) {
241
469
  logger.error(`Error scanning ${file}: ${e}`);
242
470
  }
243
471
  }
472
+ // Scan for .tool files (text schema with companion executable)
473
+ const allDirFiles = fs.readdirSync(dir);
474
+ const allToolFiles = allDirFiles.filter((f) => f.endsWith(".tool"));
475
+ for (const toolFile of allToolFiles) {
476
+ const toolPath = path.join(dir, toolFile);
477
+ try {
478
+ const content = fs.readFileSync(toolPath, "utf8");
479
+ const metadata = parseTextSchema(content);
480
+ if (!metadata) {
481
+ logger.warn(`Failed to parse .tool file: ${toolFile}`);
482
+ continue;
483
+ }
484
+ const baseName = toolFile.slice(0, -5); // Remove .tool extension
485
+ const companionPath = findCompanion(dir, baseName);
486
+ if (!companionPath) {
487
+ logger.warn(`No companion executable found for ${toolFile}`);
488
+ continue;
489
+ }
490
+ const metadataWithFormat = {
491
+ ...metadata,
492
+ format: "text",
493
+ };
494
+ toolMap.set(metadata.name, {
495
+ path: companionPath,
496
+ metadata: metadataWithFormat,
497
+ });
498
+ logger.info(`Loaded ${isProject ? "project" : "user"} tool: ${metadata.name} (from .tool file)`);
499
+ }
500
+ catch (e) {
501
+ logger.error(`Error reading .tool file ${toolFile}: ${e}`);
502
+ }
503
+ }
244
504
  }
245
505
  catch (e) {
246
506
  logger.error(`Error reading dir ${dir}: ${e}`);
@@ -274,7 +534,7 @@ export async function loadDynamicTools({ baseDir, existingToolNames = [], }) {
274
534
  logger.warn(`Duplicate dynamic tool name '${toolName}' found. Skipping duplicate.`);
275
535
  continue;
276
536
  }
277
- Object.assign(tools, createDynamicTool(path, metadata));
537
+ Object.assign(tools, createDynamicTool(path, metadata, sessionContext));
278
538
  }
279
539
  if (conflictingTools.length > 0) {
280
540
  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>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"edit-file.d.ts","sourceRoot":"","sources":["../../source/tools/edit-file.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AASpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,eAAO,MAAM,YAAY;;CAExB,CAAC;AAIF,QAAA,MAAM,WAAW;;;;;;iBAqCf,CAAC;AAEH,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEvD,eAAO,MAAM,kBAAkB,GAAU,SAAS;IAChD,SAAS,EAAE,gBAAgB,CAAC;CAC7B;;;;;;;;;;;6BAY4B,mBAAmB;6BAKzB,mBAAmB,mBACnB,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;EA6BrB,CAAC;AAiDF,UAAU,QAAQ;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAwDD,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,UAAQ,EACd,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,MAAM,CAAC,CA6CjB"}
1
+ {"version":3,"file":"edit-file.d.ts","sourceRoot":"","sources":["../../source/tools/edit-file.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AASpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,eAAO,MAAM,YAAY;;CAExB,CAAC;AAIF,QAAA,MAAM,WAAW;;;;;;iBAqCf,CAAC;AAEH,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEvD,eAAO,MAAM,kBAAkB,GAAU,SAAS;IAChD,SAAS,EAAE,gBAAgB,CAAC;CAC7B;;;;;;;;;;;6BAY4B,mBAAmB;6BAKzB,mBAAmB,mBACnB,oBAAoB,GACpC,OAAO,CAAC,MAAM,CAAC;EA6BrB,CAAC;AAiDF,UAAU,QAAQ;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAgQD,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,UAAQ,EACd,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,MAAM,CAAC,CAqEjB"}