@travisennis/acai 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.acai/acai.json +9 -0
- package/.acai/prompts/add-openrouter-model.md +13 -0
- package/.acai/prompts/project-status.md +4 -0
- package/.acai/prompts/update-architecture-document.md +9 -0
- package/.acai/rules/learned-rules.md +9 -0
- package/.ai/docs/available-tools.txt +3 -0
- package/.ai/docs/cognitive_complexity_refactoring_progress.md +65 -0
- package/.ai/docs/deleted_tools.md +168 -0
- package/.ai/docs/deleted_tools_88ced9ef.md +56 -0
- package/.ai/docs/image-pasting.md +46 -0
- package/.ai/docs/initialize-app.md +117 -0
- package/.ai/docs/issue-4-plan.md +44 -0
- package/.ai/docs/marked-renderer-debug.md +15 -0
- package/.ai/docs/marked-renderer-refactor-plan.md +64 -0
- package/.ai/docs/memory-use-cases.md +55 -0
- package/.ai/docs/prompt-consistency.md +31 -0
- package/.ai/docs/refactoring-tools.md +98 -0
- package/.ai/docs/system-prompt-update.md +174 -0
- package/.ai/docs/system_prompt.txt +210 -0
- package/.ai/docs/tasks.md +49 -0
- package/.ai/plan.md +131 -0
- package/.ai/prompt.md +1 -0
- package/.ai/scripts/fetch_models.js +27 -0
- package/.ai/scripts/generateSystemPrompt.ts +15 -0
- package/.ai/scripts/list-tools.mjs +4 -0
- package/.ai/scripts/p5_geometric_shapes.js +149 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +3 -0
- package/.husky/pre-push +1 -0
- package/.ignore +4 -0
- package/AGENTS.md +25 -0
- package/ARCHITECTURE.md +304 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/TODO.md +2 -0
- package/biome.json +61 -0
- package/commitlint.config.js +3 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +116 -0
- package/dist/commands/application-log-command.d.ts +2 -0
- package/dist/commands/application-log-command.js +43 -0
- package/dist/commands/clear-command.d.ts +2 -0
- package/dist/commands/clear-command.js +12 -0
- package/dist/commands/compact-command.d.ts +2 -0
- package/dist/commands/compact-command.js +51 -0
- package/dist/commands/copy-command.d.ts +2 -0
- package/dist/commands/copy-command.js +51 -0
- package/dist/commands/edit-command.d.ts +2 -0
- package/dist/commands/edit-command.js +53 -0
- package/dist/commands/edit-prompt-command.d.ts +2 -0
- package/dist/commands/edit-prompt-command.js +25 -0
- package/dist/commands/exit-command.d.ts +2 -0
- package/dist/commands/exit-command.js +14 -0
- package/dist/commands/files-command.d.ts +2 -0
- package/dist/commands/files-command.js +63 -0
- package/dist/commands/generate-rules-command.d.ts +2 -0
- package/dist/commands/generate-rules-command.js +61 -0
- package/dist/commands/help-command.d.ts +2 -0
- package/dist/commands/help-command.js +19 -0
- package/dist/commands/init-command.d.ts +2 -0
- package/dist/commands/init-command.js +40 -0
- package/dist/commands/last-log-command.d.ts +2 -0
- package/dist/commands/last-log-command.js +76 -0
- package/dist/commands/manager.d.ts +22 -0
- package/dist/commands/manager.js +123 -0
- package/dist/commands/model-command.d.ts +2 -0
- package/dist/commands/model-command.js +84 -0
- package/dist/commands/paste-command.d.ts +2 -0
- package/dist/commands/paste-command.js +40 -0
- package/dist/commands/prompt-command.d.ts +2 -0
- package/dist/commands/prompt-command.js +111 -0
- package/dist/commands/reset-command.d.ts +2 -0
- package/dist/commands/reset-command.js +16 -0
- package/dist/commands/rules-command.d.ts +2 -0
- package/dist/commands/rules-command.js +68 -0
- package/dist/commands/save-command.d.ts +2 -0
- package/dist/commands/save-command.js +14 -0
- package/dist/commands/types.d.ts +26 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/usage-command.d.ts +2 -0
- package/dist/commands/usage-command.js +21 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.js +193 -0
- package/dist/conversation-analyzer.d.ts +10 -0
- package/dist/conversation-analyzer.js +88 -0
- package/dist/dedent.d.ts +3 -0
- package/dist/dedent.js +38 -0
- package/dist/formatting.d.ts +17 -0
- package/dist/formatting.js +103 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +213 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +24 -0
- package/dist/mentions.d.ts +9 -0
- package/dist/mentions.js +182 -0
- package/dist/messages.d.ts +69 -0
- package/dist/messages.js +261 -0
- package/dist/middleware/audit-message.d.ts +5 -0
- package/dist/middleware/audit-message.js +95 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/rate-limit.d.ts +4 -0
- package/dist/middleware/rate-limit.js +17 -0
- package/dist/models/ai-config.d.ts +12 -0
- package/dist/models/ai-config.js +87 -0
- package/dist/models/anthropic-provider.d.ts +25 -0
- package/dist/models/anthropic-provider.js +184 -0
- package/dist/models/deepseek-provider.d.ts +20 -0
- package/dist/models/deepseek-provider.js +42 -0
- package/dist/models/google-provider.d.ts +19 -0
- package/dist/models/google-provider.js +56 -0
- package/dist/models/manager.d.ts +15 -0
- package/dist/models/manager.js +48 -0
- package/dist/models/openai-provider.d.ts +22 -0
- package/dist/models/openai-provider.js +70 -0
- package/dist/models/openrouter-provider.d.ts +36 -0
- package/dist/models/openrouter-provider.js +276 -0
- package/dist/models/providers.d.ts +33 -0
- package/dist/models/providers.js +116 -0
- package/dist/models/xai-provider.d.ts +20 -0
- package/dist/models/xai-provider.js +47 -0
- package/dist/parsing.d.ts +2 -0
- package/dist/parsing.js +18 -0
- package/dist/prompts/manager.d.ts +19 -0
- package/dist/prompts/manager.js +71 -0
- package/dist/prompts.d.ts +4 -0
- package/dist/prompts.js +158 -0
- package/dist/repl-prompt.d.ts +14 -0
- package/dist/repl-prompt.js +147 -0
- package/dist/repl.d.ts +27 -0
- package/dist/repl.js +431 -0
- package/dist/source/cli.d.ts +19 -0
- package/dist/source/cli.js +116 -0
- package/dist/source/commands/application-log-command.d.ts +2 -0
- package/dist/source/commands/application-log-command.js +43 -0
- package/dist/source/commands/clear-command.d.ts +2 -0
- package/dist/source/commands/clear-command.js +12 -0
- package/dist/source/commands/compact-command.d.ts +2 -0
- package/dist/source/commands/compact-command.js +51 -0
- package/dist/source/commands/copy-command.d.ts +2 -0
- package/dist/source/commands/copy-command.js +51 -0
- package/dist/source/commands/edit-command.d.ts +2 -0
- package/dist/source/commands/edit-command.js +53 -0
- package/dist/source/commands/edit-prompt-command.d.ts +2 -0
- package/dist/source/commands/edit-prompt-command.js +25 -0
- package/dist/source/commands/exit-command.d.ts +2 -0
- package/dist/source/commands/exit-command.js +14 -0
- package/dist/source/commands/files-command.d.ts +2 -0
- package/dist/source/commands/files-command.js +63 -0
- package/dist/source/commands/generate-rules-command.d.ts +2 -0
- package/dist/source/commands/generate-rules-command.js +61 -0
- package/dist/source/commands/help-command.d.ts +2 -0
- package/dist/source/commands/help-command.js +19 -0
- package/dist/source/commands/init-command.d.ts +2 -0
- package/dist/source/commands/init-command.js +40 -0
- package/dist/source/commands/last-log-command.d.ts +2 -0
- package/dist/source/commands/last-log-command.js +76 -0
- package/dist/source/commands/manager.d.ts +22 -0
- package/dist/source/commands/manager.js +123 -0
- package/dist/source/commands/model-command.d.ts +2 -0
- package/dist/source/commands/model-command.js +84 -0
- package/dist/source/commands/paste-command.d.ts +2 -0
- package/dist/source/commands/paste-command.js +40 -0
- package/dist/source/commands/prompt-command.d.ts +2 -0
- package/dist/source/commands/prompt-command.js +111 -0
- package/dist/source/commands/reset-command.d.ts +2 -0
- package/dist/source/commands/reset-command.js +16 -0
- package/dist/source/commands/rules-command.d.ts +2 -0
- package/dist/source/commands/rules-command.js +68 -0
- package/dist/source/commands/save-command.d.ts +2 -0
- package/dist/source/commands/save-command.js +14 -0
- package/dist/source/commands/types.d.ts +26 -0
- package/dist/source/commands/types.js +1 -0
- package/dist/source/commands/usage-command.d.ts +2 -0
- package/dist/source/commands/usage-command.js +21 -0
- package/dist/source/config.d.ts +60 -0
- package/dist/source/config.js +193 -0
- package/dist/source/conversation-analyzer.d.ts +10 -0
- package/dist/source/conversation-analyzer.js +88 -0
- package/dist/source/dedent.d.ts +3 -0
- package/dist/source/dedent.js +38 -0
- package/dist/source/formatting.d.ts +17 -0
- package/dist/source/formatting.js +103 -0
- package/dist/source/index.d.ts +18 -0
- package/dist/source/index.js +213 -0
- package/dist/source/logger.d.ts +2 -0
- package/dist/source/logger.js +24 -0
- package/dist/source/mentions.d.ts +9 -0
- package/dist/source/mentions.js +182 -0
- package/dist/source/messages.d.ts +69 -0
- package/dist/source/messages.js +261 -0
- package/dist/source/middleware/audit-message.d.ts +5 -0
- package/dist/source/middleware/audit-message.js +95 -0
- package/dist/source/middleware/index.d.ts +2 -0
- package/dist/source/middleware/index.js +2 -0
- package/dist/source/middleware/rate-limit.d.ts +4 -0
- package/dist/source/middleware/rate-limit.js +17 -0
- package/dist/source/models/ai-config.d.ts +12 -0
- package/dist/source/models/ai-config.js +87 -0
- package/dist/source/models/anthropic-provider.d.ts +25 -0
- package/dist/source/models/anthropic-provider.js +184 -0
- package/dist/source/models/deepseek-provider.d.ts +20 -0
- package/dist/source/models/deepseek-provider.js +42 -0
- package/dist/source/models/google-provider.d.ts +19 -0
- package/dist/source/models/google-provider.js +56 -0
- package/dist/source/models/manager.d.ts +15 -0
- package/dist/source/models/manager.js +48 -0
- package/dist/source/models/openai-provider.d.ts +22 -0
- package/dist/source/models/openai-provider.js +70 -0
- package/dist/source/models/openrouter-provider.d.ts +36 -0
- package/dist/source/models/openrouter-provider.js +276 -0
- package/dist/source/models/providers.d.ts +33 -0
- package/dist/source/models/providers.js +116 -0
- package/dist/source/models/xai-provider.d.ts +20 -0
- package/dist/source/models/xai-provider.js +47 -0
- package/dist/source/parsing.d.ts +2 -0
- package/dist/source/parsing.js +18 -0
- package/dist/source/prompts/manager.d.ts +19 -0
- package/dist/source/prompts/manager.js +71 -0
- package/dist/source/prompts.d.ts +4 -0
- package/dist/source/prompts.js +158 -0
- package/dist/source/repl-prompt.d.ts +14 -0
- package/dist/source/repl-prompt.js +147 -0
- package/dist/source/repl.d.ts +27 -0
- package/dist/source/repl.js +431 -0
- package/dist/source/terminal/formatting.d.ts +37 -0
- package/dist/source/terminal/formatting.js +106 -0
- package/dist/source/terminal/index.d.ts +94 -0
- package/dist/source/terminal/index.js +420 -0
- package/dist/source/terminal/markdown-utils.d.ts +2 -0
- package/dist/source/terminal/markdown-utils.js +81 -0
- package/dist/source/terminal/markdown.d.ts +1 -0
- package/dist/source/terminal/markdown.js +111 -0
- package/dist/source/terminal/types.d.ts +71 -0
- package/dist/source/terminal/types.js +1 -0
- package/dist/source/terminal-output.d.ts +8 -0
- package/dist/source/terminal-output.js +213 -0
- package/dist/source/terminal-output.test.d.ts +8 -0
- package/dist/source/terminal-output.test.js +213 -0
- package/dist/source/token-tracker.d.ts +14 -0
- package/dist/source/token-tracker.js +53 -0
- package/dist/source/token-utils.d.ts +7 -0
- package/dist/source/token-utils.js +13 -0
- package/dist/source/tools/agent.d.ts +17 -0
- package/dist/source/tools/agent.js +87 -0
- package/dist/source/tools/bash.d.ts +19 -0
- package/dist/source/tools/bash.js +294 -0
- package/dist/source/tools/code-interpreter.d.ts +12 -0
- package/dist/source/tools/code-interpreter.js +131 -0
- package/dist/source/tools/command-validation.d.ts +8 -0
- package/dist/source/tools/command-validation.js +69 -0
- package/dist/source/tools/delete-file.d.ts +12 -0
- package/dist/source/tools/delete-file.js +56 -0
- package/dist/source/tools/directory-tree.d.ts +12 -0
- package/dist/source/tools/directory-tree.js +38 -0
- package/dist/source/tools/edit-file.d.ts +19 -0
- package/dist/source/tools/edit-file.js +107 -0
- package/dist/source/tools/filesystem-utils.d.ts +22 -0
- package/dist/source/tools/filesystem-utils.js +191 -0
- package/dist/source/tools/git-utils.d.ts +14 -0
- package/dist/source/tools/git-utils.js +64 -0
- package/dist/source/tools/grep.d.ts +17 -0
- package/dist/source/tools/grep.js +138 -0
- package/dist/source/tools/index.d.ts +161 -0
- package/dist/source/tools/index.js +209 -0
- package/dist/source/tools/memory-read.d.ts +13 -0
- package/dist/source/tools/memory-read.js +135 -0
- package/dist/source/tools/memory-write.d.ts +12 -0
- package/dist/source/tools/memory-write.js +83 -0
- package/dist/source/tools/move-file.d.ts +13 -0
- package/dist/source/tools/move-file.js +44 -0
- package/dist/source/tools/read-file.d.ts +17 -0
- package/dist/source/tools/read-file.js +86 -0
- package/dist/source/tools/read-multiple-files.d.ts +14 -0
- package/dist/source/tools/read-multiple-files.js +55 -0
- package/dist/source/tools/save-file.d.ts +17 -0
- package/dist/source/tools/save-file.js +98 -0
- package/dist/source/tools/think.d.ts +11 -0
- package/dist/source/tools/think.js +45 -0
- package/dist/source/tools/types.d.ts +29 -0
- package/dist/source/tools/types.js +14 -0
- package/dist/source/tools/web-fetch.d.ts +47 -0
- package/dist/source/tools/web-fetch.js +246 -0
- package/dist/source/tools/web-search.d.ts +13 -0
- package/dist/source/tools/web-search.js +80 -0
- package/dist/source/utils/process.d.ts +36 -0
- package/dist/source/utils/process.js +75 -0
- package/dist/source/version.d.ts +1 -0
- package/dist/source/version.js +21 -0
- package/dist/terminal/formatting.d.ts +37 -0
- package/dist/terminal/formatting.js +106 -0
- package/dist/terminal/index.d.ts +94 -0
- package/dist/terminal/index.js +420 -0
- package/dist/terminal/markdown-utils.d.ts +2 -0
- package/dist/terminal/markdown-utils.js +81 -0
- package/dist/terminal/markdown.d.ts +1 -0
- package/dist/terminal/markdown.js +111 -0
- package/dist/terminal/types.d.ts +71 -0
- package/dist/terminal/types.js +1 -0
- package/dist/terminal-output.d.ts +8 -0
- package/dist/terminal-output.js +213 -0
- package/dist/token-tracker.d.ts +14 -0
- package/dist/token-tracker.js +53 -0
- package/dist/token-utils.d.ts +7 -0
- package/dist/token-utils.js +13 -0
- package/dist/tools/agent.d.ts +17 -0
- package/dist/tools/agent.js +87 -0
- package/dist/tools/bash.d.ts +19 -0
- package/dist/tools/bash.js +294 -0
- package/dist/tools/code-interpreter.d.ts +12 -0
- package/dist/tools/code-interpreter.js +131 -0
- package/dist/tools/command-validation.d.ts +8 -0
- package/dist/tools/command-validation.js +69 -0
- package/dist/tools/delete-file.d.ts +12 -0
- package/dist/tools/delete-file.js +56 -0
- package/dist/tools/directory-tree.d.ts +12 -0
- package/dist/tools/directory-tree.js +38 -0
- package/dist/tools/edit-file.d.ts +19 -0
- package/dist/tools/edit-file.js +107 -0
- package/dist/tools/filesystem-utils.d.ts +22 -0
- package/dist/tools/filesystem-utils.js +191 -0
- package/dist/tools/git-utils.d.ts +14 -0
- package/dist/tools/git-utils.js +64 -0
- package/dist/tools/grep.d.ts +17 -0
- package/dist/tools/grep.js +138 -0
- package/dist/tools/index.d.ts +161 -0
- package/dist/tools/index.js +209 -0
- package/dist/tools/memory-read.d.ts +13 -0
- package/dist/tools/memory-read.js +135 -0
- package/dist/tools/memory-write.d.ts +12 -0
- package/dist/tools/memory-write.js +83 -0
- package/dist/tools/move-file.d.ts +13 -0
- package/dist/tools/move-file.js +44 -0
- package/dist/tools/read-file.d.ts +17 -0
- package/dist/tools/read-file.js +86 -0
- package/dist/tools/read-multiple-files.d.ts +14 -0
- package/dist/tools/read-multiple-files.js +55 -0
- package/dist/tools/save-file.d.ts +17 -0
- package/dist/tools/save-file.js +98 -0
- package/dist/tools/think.d.ts +11 -0
- package/dist/tools/think.js +45 -0
- package/dist/tools/types.d.ts +29 -0
- package/dist/tools/types.js +14 -0
- package/dist/tools/web-fetch.d.ts +47 -0
- package/dist/tools/web-fetch.js +246 -0
- package/dist/tools/web-search.d.ts +13 -0
- package/dist/tools/web-search.js +80 -0
- package/dist/utils/process.d.ts +36 -0
- package/dist/utils/process.js +75 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +21 -0
- package/knip.json +5 -0
- package/package.json +83 -0
- package/source/cli.ts +172 -0
- package/source/commands/application-log-command.ts +53 -0
- package/source/commands/clear-command.ts +14 -0
- package/source/commands/compact-command.ts +64 -0
- package/source/commands/copy-command.ts +55 -0
- package/source/commands/edit-command.ts +63 -0
- package/source/commands/edit-prompt-command.ts +31 -0
- package/source/commands/exit-command.ts +18 -0
- package/source/commands/files-command.ts +85 -0
- package/source/commands/generate-rules-command.ts +82 -0
- package/source/commands/help-command.ts +27 -0
- package/source/commands/init-command.ts +48 -0
- package/source/commands/last-log-command.ts +88 -0
- package/source/commands/manager.ts +151 -0
- package/source/commands/model-command.ts +123 -0
- package/source/commands/paste-command.ts +62 -0
- package/source/commands/prompt-command.ts +150 -0
- package/source/commands/reset-command.ts +22 -0
- package/source/commands/rules-command.ts +76 -0
- package/source/commands/save-command.ts +20 -0
- package/source/commands/types.ts +28 -0
- package/source/commands/usage-command.ts +26 -0
- package/source/config.ts +223 -0
- package/source/conversation-analyzer.ts +115 -0
- package/source/dedent.ts +53 -0
- package/source/formatting.ts +132 -0
- package/source/index.ts +240 -0
- package/source/logger.ts +29 -0
- package/source/mentions.ts +227 -0
- package/source/messages.ts +360 -0
- package/source/middleware/audit-message.ts +133 -0
- package/source/middleware/index.ts +2 -0
- package/source/middleware/rate-limit.ts +24 -0
- package/source/models/ai-config.ts +109 -0
- package/source/models/anthropic-provider.ts +199 -0
- package/source/models/deepseek-provider.ts +53 -0
- package/source/models/google-provider.ts +68 -0
- package/source/models/manager.ts +84 -0
- package/source/models/openai-provider.ts +81 -0
- package/source/models/openrouter-provider.ts +288 -0
- package/source/models/providers.ts +197 -0
- package/source/models/xai-provider.ts +59 -0
- package/source/parsing.ts +20 -0
- package/source/prompts/manager.ts +90 -0
- package/source/prompts.ts +172 -0
- package/source/repl-prompt.ts +196 -0
- package/source/repl.ts +572 -0
- package/source/terminal/formatting.ts +121 -0
- package/source/terminal/index.ts +518 -0
- package/source/terminal/markdown-utils.ts +89 -0
- package/source/terminal/markdown.ts +155 -0
- package/source/terminal/types.ts +84 -0
- package/source/terminal-output.test.ts +266 -0
- package/source/token-tracker.ts +78 -0
- package/source/token-utils.ts +17 -0
- package/source/tools/agent.ts +107 -0
- package/source/tools/bash.ts +367 -0
- package/source/tools/code-interpreter.ts +172 -0
- package/source/tools/command-validation.ts +81 -0
- package/source/tools/delete-file.ts +71 -0
- package/source/tools/directory-tree.ts +54 -0
- package/source/tools/edit-file.ts +155 -0
- package/source/tools/filesystem-utils.ts +265 -0
- package/source/tools/git-utils.ts +70 -0
- package/source/tools/grep.ts +184 -0
- package/source/tools/index.ts +278 -0
- package/source/tools/memory-read.ts +174 -0
- package/source/tools/memory-write.ts +105 -0
- package/source/tools/move-file.ts +59 -0
- package/source/tools/read-file.ts +129 -0
- package/source/tools/read-multiple-files.ts +80 -0
- package/source/tools/save-file.ts +147 -0
- package/source/tools/think.ts +51 -0
- package/source/tools/types.ts +58 -0
- package/source/tools/web-fetch.ts +327 -0
- package/source/tools/web-search.ts +101 -0
- package/source/utils/process.ts +121 -0
- package/source/version.ts +21 -0
- package/test/commands/copy-command.test.ts +69 -0
- package/test/config.test.ts +200 -0
- package/test/terminal/markdown-utils.test.ts +124 -0
- package/test/tools/bash-tool.test.ts +58 -0
- package/test/tools/code-interpreter.test.ts +91 -0
- package/test/tools/command-validation.test.ts +48 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { input, select } from "@inquirer/prompts";
|
|
3
|
+
import { tool } from "ai";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { config } from "../config.ts";
|
|
7
|
+
import type { Terminal } from "../terminal/index.ts";
|
|
8
|
+
import type { TokenCounter } from "../token-utils.ts";
|
|
9
|
+
import { executeCommand } from "../utils/process.ts";
|
|
10
|
+
import { CommandValidation } from "./command-validation.ts";
|
|
11
|
+
import type { SendData } from "./types.ts";
|
|
12
|
+
|
|
13
|
+
export const BashTool = {
|
|
14
|
+
name: "bash" as const,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type Token = { raw: string; unquoted: string };
|
|
18
|
+
|
|
19
|
+
function tokenize(inputStr: string): Token[] {
|
|
20
|
+
const tokens: Token[] = [];
|
|
21
|
+
let current = "";
|
|
22
|
+
let inSingle = false;
|
|
23
|
+
let inDouble = false;
|
|
24
|
+
for (let i = 0; i < inputStr.length; i++) {
|
|
25
|
+
const ch: string = inputStr[i] as string;
|
|
26
|
+
if (ch === "'" && !inDouble) {
|
|
27
|
+
inSingle = !inSingle;
|
|
28
|
+
current += ch;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ch === '"' && !inSingle) {
|
|
32
|
+
inDouble = !inDouble;
|
|
33
|
+
current += ch;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (!inSingle && !inDouble && /\s/.test(ch)) {
|
|
37
|
+
if (current.length > 0) {
|
|
38
|
+
const raw = current;
|
|
39
|
+
const unquoted = raw.replace(/^['"]|['"]$/g, "");
|
|
40
|
+
tokens.push({ raw, unquoted });
|
|
41
|
+
current = "";
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (ch === "\\" && inDouble && i + 1 < inputStr.length) {
|
|
46
|
+
const next = inputStr[i + 1];
|
|
47
|
+
if (next === '"' || next === "\\") {
|
|
48
|
+
current += next;
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
current += ch;
|
|
54
|
+
}
|
|
55
|
+
if (current.length > 0) {
|
|
56
|
+
const raw = current;
|
|
57
|
+
const unquoted = raw.replace(/^['"]|['"]$/g, "");
|
|
58
|
+
tokens.push({ raw, unquoted });
|
|
59
|
+
}
|
|
60
|
+
return tokens;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function shouldSkipPathValidation(tokens: Token[], index: number): boolean {
|
|
64
|
+
if (index === 0) return false;
|
|
65
|
+
const cmd = tokens[0]?.unquoted;
|
|
66
|
+
if (cmd !== "git") return false;
|
|
67
|
+
const sub = tokens[1]?.unquoted;
|
|
68
|
+
if (
|
|
69
|
+
sub !== "commit" &&
|
|
70
|
+
sub !== "tag" &&
|
|
71
|
+
!(sub === "notes" && tokens[2]?.unquoted === "add")
|
|
72
|
+
) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const prev = tokens[index - 1]?.unquoted;
|
|
76
|
+
if (prev === "-m" || prev === "--message") return true;
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function looksLikeUrl(str: string): boolean {
|
|
81
|
+
return str.startsWith("http://") || str.startsWith("https://");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Whitelist of allowed commands
|
|
85
|
+
const ALLOWED_COMMANDS = [
|
|
86
|
+
"chmod",
|
|
87
|
+
"ls",
|
|
88
|
+
"pwd",
|
|
89
|
+
"cat",
|
|
90
|
+
"grep",
|
|
91
|
+
"find",
|
|
92
|
+
"echo",
|
|
93
|
+
"mkdir",
|
|
94
|
+
"touch",
|
|
95
|
+
"cp",
|
|
96
|
+
"mv",
|
|
97
|
+
"pwd",
|
|
98
|
+
"wc",
|
|
99
|
+
"diff",
|
|
100
|
+
"sort",
|
|
101
|
+
"head",
|
|
102
|
+
"tail",
|
|
103
|
+
"sleep",
|
|
104
|
+
"npm",
|
|
105
|
+
"npx",
|
|
106
|
+
"node",
|
|
107
|
+
"git",
|
|
108
|
+
"gh",
|
|
109
|
+
"rg",
|
|
110
|
+
"jq",
|
|
111
|
+
"sed",
|
|
112
|
+
"awk",
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
// Command execution timeout in milliseconds
|
|
116
|
+
const DEFAULT_TIMEOUT = 1.5 * 60 * 1000; // 1.5 minutes
|
|
117
|
+
|
|
118
|
+
// Initialize command validator with allowed commands
|
|
119
|
+
const commandValidator = new CommandValidation(ALLOWED_COMMANDS);
|
|
120
|
+
|
|
121
|
+
// Ensure path is within base directory
|
|
122
|
+
function isPathWithinBaseDir(requestedPath: string, baseDir: string): boolean {
|
|
123
|
+
const normalizedRequestedPath = path.normalize(requestedPath);
|
|
124
|
+
const normalizedBaseDir = path.normalize(baseDir);
|
|
125
|
+
|
|
126
|
+
return normalizedRequestedPath.startsWith(normalizedBaseDir);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const createBashTool = ({
|
|
130
|
+
baseDir,
|
|
131
|
+
sendData,
|
|
132
|
+
tokenCounter,
|
|
133
|
+
terminal,
|
|
134
|
+
autoAcceptAll,
|
|
135
|
+
}: {
|
|
136
|
+
baseDir: string;
|
|
137
|
+
sendData?: SendData | undefined;
|
|
138
|
+
tokenCounter: TokenCounter;
|
|
139
|
+
terminal?: Terminal;
|
|
140
|
+
autoAcceptAll: boolean;
|
|
141
|
+
}) => {
|
|
142
|
+
let autoAcceptCommands = autoAcceptAll;
|
|
143
|
+
return {
|
|
144
|
+
[BashTool.name]: tool({
|
|
145
|
+
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.`,
|
|
146
|
+
inputSchema: z.object({
|
|
147
|
+
command: z
|
|
148
|
+
.string()
|
|
149
|
+
.describe(
|
|
150
|
+
"Full CLI command to execute. Must be from the allowed list without chaining operators.",
|
|
151
|
+
),
|
|
152
|
+
cwd: z
|
|
153
|
+
.string()
|
|
154
|
+
.nullable()
|
|
155
|
+
.describe(
|
|
156
|
+
"Working directory (default: project root). Must be within the project directory.",
|
|
157
|
+
),
|
|
158
|
+
timeout: z
|
|
159
|
+
.number()
|
|
160
|
+
.nullable()
|
|
161
|
+
.describe(
|
|
162
|
+
`Command execution timeout in milliseconds. Default: ${DEFAULT_TIMEOUT}ms`,
|
|
163
|
+
),
|
|
164
|
+
}),
|
|
165
|
+
execute: async ({ command, cwd, timeout }, { toolCallId }) => {
|
|
166
|
+
// Guard against null cwd and timeout
|
|
167
|
+
const safeCwd = cwd == null ? baseDir : cwd;
|
|
168
|
+
const safeTimeout = timeout == null ? DEFAULT_TIMEOUT : timeout;
|
|
169
|
+
|
|
170
|
+
sendData?.({
|
|
171
|
+
event: "tool-init",
|
|
172
|
+
id: toolCallId,
|
|
173
|
+
data: `Executing: ${chalk.cyan(command)} in ${chalk.cyan(safeCwd)}`,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Validate command using CommandValidation
|
|
177
|
+
if (!commandValidator.isValid(command)) {
|
|
178
|
+
const errorMsg = `Command not allowed. Ensure all sub-commands are in the approved list: ${ALLOWED_COMMANDS.join(", ")} and no unsafe operators (>, <, \`, $()) are used.`;
|
|
179
|
+
sendData?.({ event: "tool-error", id: toolCallId, data: errorMsg });
|
|
180
|
+
return errorMsg;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Validate working directory
|
|
184
|
+
if (!isPathWithinBaseDir(safeCwd, baseDir)) {
|
|
185
|
+
const errorMsg = `Working directory must be within the project directory: ${baseDir}`;
|
|
186
|
+
sendData?.({ event: "tool-error", id: toolCallId, data: errorMsg });
|
|
187
|
+
return errorMsg;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Validate command arguments for paths outside baseDir
|
|
191
|
+
const tokens = tokenize(command);
|
|
192
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
193
|
+
const token = tokens[i];
|
|
194
|
+
if (token == null) continue;
|
|
195
|
+
const part = token.unquoted;
|
|
196
|
+
|
|
197
|
+
if (shouldSkipPathValidation(tokens, i)) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (looksLikeUrl(part)) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (
|
|
206
|
+
part.startsWith("/") ||
|
|
207
|
+
part.includes("../") ||
|
|
208
|
+
part.includes("./") ||
|
|
209
|
+
(part.includes("/") && !part.startsWith("-"))
|
|
210
|
+
) {
|
|
211
|
+
try {
|
|
212
|
+
const resolvedPath = path.resolve(safeCwd, part);
|
|
213
|
+
if (!isPathWithinBaseDir(resolvedPath, baseDir)) {
|
|
214
|
+
const errorMsg = `Command argument references path outside the project directory: ${part} (resolved to ${resolvedPath})`;
|
|
215
|
+
sendData?.({
|
|
216
|
+
event: "tool-error",
|
|
217
|
+
id: toolCallId,
|
|
218
|
+
data: errorMsg,
|
|
219
|
+
});
|
|
220
|
+
return errorMsg;
|
|
221
|
+
}
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.info(
|
|
224
|
+
`Could not resolve potential path argument: ${part}`,
|
|
225
|
+
e as unknown as string,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Prompt user for command execution approval (only in interactive mode)
|
|
232
|
+
if (terminal) {
|
|
233
|
+
if (!autoAcceptCommands) {
|
|
234
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
235
|
+
}
|
|
236
|
+
terminal.lineBreak();
|
|
237
|
+
terminal.writeln(
|
|
238
|
+
`${chalk.blue.bold("●")} About to execute command: ${chalk.cyan(command)}`,
|
|
239
|
+
);
|
|
240
|
+
terminal.writeln(`${chalk.gray("Working directory:")} ${safeCwd}`);
|
|
241
|
+
terminal.lineBreak();
|
|
242
|
+
|
|
243
|
+
let userChoice: string;
|
|
244
|
+
if (autoAcceptCommands) {
|
|
245
|
+
terminal.writeln(
|
|
246
|
+
chalk.green(
|
|
247
|
+
"✓ Auto-accepting command (all future commands will be accepted)",
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
userChoice = "accept";
|
|
251
|
+
} else {
|
|
252
|
+
userChoice = await select({
|
|
253
|
+
message: "What would you like to do with this command?",
|
|
254
|
+
choices: [
|
|
255
|
+
{ name: "Execute this command", value: "accept" },
|
|
256
|
+
{
|
|
257
|
+
name: "Execute all future commands (including this)",
|
|
258
|
+
value: "accept-all",
|
|
259
|
+
},
|
|
260
|
+
{ name: "Reject this command", value: "reject" },
|
|
261
|
+
],
|
|
262
|
+
default: "accept",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
terminal.lineBreak();
|
|
267
|
+
|
|
268
|
+
if (userChoice === "accept-all") {
|
|
269
|
+
autoAcceptCommands = true;
|
|
270
|
+
terminal.writeln(
|
|
271
|
+
chalk.yellow(
|
|
272
|
+
"✓ Auto-accept mode enabled for all future commands",
|
|
273
|
+
),
|
|
274
|
+
);
|
|
275
|
+
terminal.lineBreak();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (userChoice === "reject") {
|
|
279
|
+
const reason = await input({ message: "Feedback: " });
|
|
280
|
+
terminal.lineBreak();
|
|
281
|
+
|
|
282
|
+
const rejectionMsg = `Command rejected by user. Reason: ${reason}`;
|
|
283
|
+
sendData?.({
|
|
284
|
+
event: "tool-completion",
|
|
285
|
+
id: toolCallId,
|
|
286
|
+
data: rejectionMsg,
|
|
287
|
+
});
|
|
288
|
+
return rejectionMsg;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const result = await executeCommand(command, {
|
|
294
|
+
cwd: safeCwd,
|
|
295
|
+
timeout: safeTimeout,
|
|
296
|
+
shell: true,
|
|
297
|
+
throwOnError: false,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (result.signal === "SIGTERM") {
|
|
301
|
+
const timeoutMessage = `Command timed out after ${safeTimeout}ms. This might be because the command is waiting for input.`;
|
|
302
|
+
sendData?.({
|
|
303
|
+
event: "tool-error",
|
|
304
|
+
id: toolCallId,
|
|
305
|
+
data: timeoutMessage,
|
|
306
|
+
});
|
|
307
|
+
return timeoutMessage;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const formattedResult = format(result);
|
|
311
|
+
|
|
312
|
+
sendData?.({
|
|
313
|
+
event: "tool-update",
|
|
314
|
+
id: toolCallId,
|
|
315
|
+
data: {
|
|
316
|
+
primary: "Result",
|
|
317
|
+
secondary: formattedResult.split("\n").slice(-5),
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
let tokenCount = 0;
|
|
322
|
+
try {
|
|
323
|
+
tokenCount = tokenCounter.count(formattedResult);
|
|
324
|
+
} catch (tokenError) {
|
|
325
|
+
console.error("Error calculating token count:", tokenError);
|
|
326
|
+
// Log or handle error, but don't block file return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const maxTokens = (await config.readProjectConfig()).tools.maxTokens;
|
|
330
|
+
// Adjust max token check message if line selection was used
|
|
331
|
+
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`;
|
|
332
|
+
|
|
333
|
+
const finalResult =
|
|
334
|
+
tokenCount <= maxTokens ? formattedResult : maxTokenMessage;
|
|
335
|
+
|
|
336
|
+
sendData?.({
|
|
337
|
+
event: "tool-completion",
|
|
338
|
+
id: toolCallId,
|
|
339
|
+
data:
|
|
340
|
+
tokenCount <= maxTokens
|
|
341
|
+
? "Command executed successfully."
|
|
342
|
+
: `Output of commmand (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}).`,
|
|
343
|
+
});
|
|
344
|
+
return finalResult;
|
|
345
|
+
} catch (error) {
|
|
346
|
+
sendData?.({
|
|
347
|
+
event: "tool-error",
|
|
348
|
+
id: toolCallId,
|
|
349
|
+
data: `Command failed: ${(error as Error).message}`,
|
|
350
|
+
});
|
|
351
|
+
return `Command failed: ${(error as Error).message}`;
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
}),
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
function format({
|
|
359
|
+
stdout,
|
|
360
|
+
stderr,
|
|
361
|
+
}: {
|
|
362
|
+
stdout: string;
|
|
363
|
+
stderr: string;
|
|
364
|
+
code: number;
|
|
365
|
+
}) {
|
|
366
|
+
return `${stdout}\n${stderr}`;
|
|
367
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { tool } from "ai";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import type { SendData } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
export const CodeInterpreterTool = {
|
|
11
|
+
name: "codeInterpreter" as const,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const createCodeInterpreterTool = ({
|
|
15
|
+
sendData,
|
|
16
|
+
}: Readonly<{
|
|
17
|
+
sendData?: SendData | undefined;
|
|
18
|
+
}>) => {
|
|
19
|
+
return {
|
|
20
|
+
[CodeInterpreterTool.name]: tool({
|
|
21
|
+
description:
|
|
22
|
+
"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.",
|
|
23
|
+
inputSchema: z.object({
|
|
24
|
+
code: z.string().describe("JavaScript code to be executed."),
|
|
25
|
+
timeoutSeconds: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(60)
|
|
30
|
+
.nullable()
|
|
31
|
+
.describe("Execution timeout in seconds (1-60). Default 5."),
|
|
32
|
+
}),
|
|
33
|
+
execute: async ({ code, timeoutSeconds }, { toolCallId }) => {
|
|
34
|
+
const workingDirectory = process.cwd();
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
sendData?.({
|
|
38
|
+
event: "tool-init",
|
|
39
|
+
id: toolCallId,
|
|
40
|
+
data: "Initializing code interpreter environment",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
sendData?.({
|
|
44
|
+
event: "tool-update",
|
|
45
|
+
id: toolCallId,
|
|
46
|
+
data: {
|
|
47
|
+
primary: "Executing...",
|
|
48
|
+
secondary: [
|
|
49
|
+
`${"`".repeat(3)} javascript\n${code.slice(0, 500)}${"`".repeat(3)}`,
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (code.trim().length === 0) {
|
|
55
|
+
throw new Error("No code provided");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const timeoutMs = Math.min(
|
|
59
|
+
Math.max((timeoutSeconds ?? 5) * 1000, 1000),
|
|
60
|
+
60000,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const tmpBase = join(workingDirectory, ".acai-ci-tmp");
|
|
64
|
+
await mkdir(tmpBase, { recursive: true });
|
|
65
|
+
const scriptPath = join(
|
|
66
|
+
tmpBase,
|
|
67
|
+
`temp_script_${Date.now()}_${randomUUID()}.mjs`,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
await writeFile(scriptPath, code, { encoding: "utf8" });
|
|
71
|
+
|
|
72
|
+
const args = [
|
|
73
|
+
"--permission",
|
|
74
|
+
`--allow-fs-read=${workingDirectory}`,
|
|
75
|
+
`--allow-fs-write=${workingDirectory}`,
|
|
76
|
+
scriptPath,
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const child = spawn(process.execPath, args, {
|
|
80
|
+
cwd: workingDirectory,
|
|
81
|
+
// do not rely solely on spawn's timeout; we implement manual timeout below
|
|
82
|
+
stdio: "pipe",
|
|
83
|
+
env: Object.assign({}, process.env, {
|
|
84
|
+
// biome-ignore lint/style/useNamingConvention: Environment variable keys are uppercase by convention
|
|
85
|
+
NO_COLOR: "true",
|
|
86
|
+
// biome-ignore lint/style/useNamingConvention: Environment variable keys are uppercase by convention
|
|
87
|
+
NODE_OPTIONS: "",
|
|
88
|
+
} as Record<string, string>),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
let stdout = "";
|
|
92
|
+
let stderr = "";
|
|
93
|
+
let timedOut = false;
|
|
94
|
+
|
|
95
|
+
const timer = setTimeout(() => {
|
|
96
|
+
timedOut = true;
|
|
97
|
+
try {
|
|
98
|
+
child.kill("SIGKILL");
|
|
99
|
+
} catch {}
|
|
100
|
+
}, timeoutMs);
|
|
101
|
+
|
|
102
|
+
child.stdout.setEncoding("utf8");
|
|
103
|
+
child.stderr.setEncoding("utf8");
|
|
104
|
+
|
|
105
|
+
child.stdout.on("data", (chunk) => {
|
|
106
|
+
stdout += String(chunk);
|
|
107
|
+
});
|
|
108
|
+
child.stderr.on("data", (chunk) => {
|
|
109
|
+
stderr += String(chunk);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const completed = await new Promise<{
|
|
113
|
+
code: number | null;
|
|
114
|
+
signal: NodeJS.Signals | null;
|
|
115
|
+
}>((resolve, reject) => {
|
|
116
|
+
child.on("error", (err) => reject(err));
|
|
117
|
+
child.on("close", (code, signal) => resolve({ code, signal }));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
|
|
122
|
+
// Cleanup temp file/directory
|
|
123
|
+
await rm(scriptPath, { force: true });
|
|
124
|
+
await rm(tmpBase, { force: true, recursive: true });
|
|
125
|
+
|
|
126
|
+
if (timedOut) {
|
|
127
|
+
throw new Error("Script timed out");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (completed.code === null) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Process terminated by signal ${completed.signal ?? "unknown"}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (completed.code !== 0) {
|
|
137
|
+
const message = `Process exited with code ${completed.code}. Stderr: ${stderr.trim()}`;
|
|
138
|
+
throw new Error(message);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const result = {
|
|
142
|
+
stdout: stdout.trim(),
|
|
143
|
+
stderr: stderr.trim(),
|
|
144
|
+
exitCode: completed.code ?? -1,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
sendData?.({
|
|
148
|
+
event: "tool-completion",
|
|
149
|
+
id: toolCallId,
|
|
150
|
+
data: "Code execution completed successfully",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return JSON.stringify(result, null, 2);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
const errorMessage =
|
|
156
|
+
(err as Error).name === "ETIMEDOUT" ||
|
|
157
|
+
(err as Error).message.includes("timed out")
|
|
158
|
+
? "Script timed out"
|
|
159
|
+
: `Error: ${(err as Error).message}`;
|
|
160
|
+
|
|
161
|
+
sendData?.({
|
|
162
|
+
event: "tool-error",
|
|
163
|
+
id: toolCallId,
|
|
164
|
+
data: errorMessage,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return errorMessage;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
}),
|
|
171
|
+
};
|
|
172
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export class CommandValidation {
|
|
2
|
+
private readonly allowedCommands: string[];
|
|
3
|
+
private readonly unsafeOperatorPatterns: RegExp[];
|
|
4
|
+
|
|
5
|
+
constructor(allowedCommands: string[]) {
|
|
6
|
+
this.allowedCommands = allowedCommands;
|
|
7
|
+
this.unsafeOperatorPatterns = [
|
|
8
|
+
/`/, // backticks
|
|
9
|
+
/\$\(/, // $(
|
|
10
|
+
/>/, // redirect out
|
|
11
|
+
/</, // redirect in
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private isCommandAllowed(command: string): boolean {
|
|
16
|
+
const baseCommand = command.split(" ")[0] || "";
|
|
17
|
+
return this.allowedCommands.includes(baseCommand);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private hasUnsafeOperators(command: string): boolean {
|
|
21
|
+
// Remove all quoted segments first
|
|
22
|
+
const stripped = command
|
|
23
|
+
.replace(/'([^'\\]|\\.)*'/g, "")
|
|
24
|
+
.replace(/"([^"\\]|\\.)*"/g, "");
|
|
25
|
+
|
|
26
|
+
// Check for unsafe operators only in unquoted portions
|
|
27
|
+
return this.unsafeOperatorPatterns.some((re) => re.test(stripped));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
isValid(command: string): boolean {
|
|
31
|
+
if (!command.trim()) return false;
|
|
32
|
+
|
|
33
|
+
// First check for unsafe operators in unquoted portions
|
|
34
|
+
if (this.hasUnsafeOperators(command)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Process command while preserving quoted strings
|
|
39
|
+
const subCommands: string[] = [];
|
|
40
|
+
let currentSegment = "";
|
|
41
|
+
let inSingleQuote = false;
|
|
42
|
+
let inDoubleQuote = false;
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < command.length; i++) {
|
|
45
|
+
const char = command[i];
|
|
46
|
+
|
|
47
|
+
// Handle quote states
|
|
48
|
+
if (char === "'" && !inDoubleQuote) inSingleQuote = !inSingleQuote;
|
|
49
|
+
if (char === '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote;
|
|
50
|
+
|
|
51
|
+
// Split on operators only when not in quotes
|
|
52
|
+
if (
|
|
53
|
+
!inSingleQuote &&
|
|
54
|
+
!inDoubleQuote &&
|
|
55
|
+
(char === "&" || char === "|" || char === ";")
|
|
56
|
+
) {
|
|
57
|
+
if (currentSegment.trim()) {
|
|
58
|
+
subCommands.push(currentSegment.trim());
|
|
59
|
+
currentSegment = "";
|
|
60
|
+
}
|
|
61
|
+
// Skip the operator and any subsequent same operators (like && or ||)
|
|
62
|
+
while (
|
|
63
|
+
i + 1 < command.length &&
|
|
64
|
+
["&", "|", ";"].includes(command[i + 1] ?? "")
|
|
65
|
+
) {
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
currentSegment += char;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Add the last segment
|
|
74
|
+
if (currentSegment.trim()) {
|
|
75
|
+
subCommands.push(currentSegment.trim());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Validate all sub-commands
|
|
79
|
+
return subCommands.every((cmd) => this.isCommandAllowed(cmd));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { tool } from "ai";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { joinWorkingDir, validatePath } from "./filesystem-utils.ts";
|
|
7
|
+
import type { SendData } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
export const DeleteFileTool = {
|
|
10
|
+
name: "deleteFile" as const,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const createDeleteFileTool = async ({
|
|
14
|
+
workingDir,
|
|
15
|
+
sendData,
|
|
16
|
+
}: {
|
|
17
|
+
workingDir: string;
|
|
18
|
+
sendData?: SendData;
|
|
19
|
+
}) => {
|
|
20
|
+
const allowedDirectory = workingDir;
|
|
21
|
+
return {
|
|
22
|
+
[DeleteFileTool.name]: tool({
|
|
23
|
+
description: "Delete a file permanently.",
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
path: z.string().describe("Absolute path to the file to delete"),
|
|
26
|
+
}),
|
|
27
|
+
execute: async ({ path: userPath }, { toolCallId }) => {
|
|
28
|
+
sendData?.({
|
|
29
|
+
id: toolCallId,
|
|
30
|
+
event: "tool-init",
|
|
31
|
+
data: `Deleting file: ${chalk.cyan(userPath)}`,
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const filePath = await validatePath(
|
|
35
|
+
joinWorkingDir(userPath, workingDir),
|
|
36
|
+
allowedDirectory,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Check if file exists before attempting delete
|
|
40
|
+
if (!existsSync(filePath)) {
|
|
41
|
+
throw new Error(`File not found: ${filePath}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Ensure it's a file, not a directory
|
|
45
|
+
const stats = await fs.stat(filePath);
|
|
46
|
+
if (stats.isDirectory()) {
|
|
47
|
+
throw new Error(`Path is a directory, not a file: ${filePath}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Delete the original file
|
|
51
|
+
await fs.unlink(filePath);
|
|
52
|
+
|
|
53
|
+
sendData?.({
|
|
54
|
+
id: toolCallId,
|
|
55
|
+
event: "tool-completion",
|
|
56
|
+
data: `File deleted successfully: ${userPath}`,
|
|
57
|
+
});
|
|
58
|
+
return `Successfully deleted ${filePath}`;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const errorMessage = `Failed to delete file: ${(error as Error).message}`;
|
|
61
|
+
sendData?.({
|
|
62
|
+
id: toolCallId,
|
|
63
|
+
event: "tool-error",
|
|
64
|
+
data: errorMessage,
|
|
65
|
+
});
|
|
66
|
+
return errorMessage;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
};
|