@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,327 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { type CheerioAPI, load } from "cheerio";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { logger } from "../logger.ts";
|
|
6
|
+
import type { TokenCounter } from "../token-utils.ts";
|
|
7
|
+
import type { SendData } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
export const WebFetchTool = {
|
|
10
|
+
name: "webFetch" as const,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const createWebFetchTool = (options: {
|
|
14
|
+
sendData?: SendData | undefined;
|
|
15
|
+
tokenCounter: TokenCounter;
|
|
16
|
+
}) => {
|
|
17
|
+
const { sendData } = options;
|
|
18
|
+
return {
|
|
19
|
+
[WebFetchTool.name]: tool({
|
|
20
|
+
description:
|
|
21
|
+
"Fetches the content of a given URL. It intelligently handles HTML content by attempting to use a specialized service for cleaner extraction, falling back to local cleaning if needed. For non-HTML content (like plain text or markdown), it fetches the raw content directly. IMPORTANT: Does not retrieve binary files.",
|
|
22
|
+
inputSchema: z.object({
|
|
23
|
+
url: z.string().describe("The URL to fetch content from."),
|
|
24
|
+
}),
|
|
25
|
+
execute: async ({ url }, { toolCallId, abortSignal }) => {
|
|
26
|
+
try {
|
|
27
|
+
sendData?.({
|
|
28
|
+
event: "tool-init",
|
|
29
|
+
id: toolCallId,
|
|
30
|
+
data: `Reading URL: ${chalk.cyan(url)}`,
|
|
31
|
+
});
|
|
32
|
+
logger.info(`Initiating fetch for URL: ${url}`);
|
|
33
|
+
const result = await readUrl(url, abortSignal);
|
|
34
|
+
const urlContent = result.data;
|
|
35
|
+
const tokenCount = options.tokenCounter.count(urlContent);
|
|
36
|
+
sendData?.({
|
|
37
|
+
event: "tool-completion",
|
|
38
|
+
id: toolCallId,
|
|
39
|
+
data: `Read URL successfully (${tokenCount} tokens)`,
|
|
40
|
+
});
|
|
41
|
+
logger.info(`Successfully read URL: ${url} (${tokenCount} tokens)`);
|
|
42
|
+
return urlContent;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const errorMessage = (error as Error).message;
|
|
45
|
+
sendData?.({
|
|
46
|
+
event: "tool-error",
|
|
47
|
+
id: toolCallId,
|
|
48
|
+
data: `Error reading URL ${url}: ${errorMessage}`,
|
|
49
|
+
});
|
|
50
|
+
logger.error(`Error reading URL ${url}: ${errorMessage}`);
|
|
51
|
+
// Return the error message so the LLM knows the tool failed.
|
|
52
|
+
return `Failed to read URL: ${errorMessage}`;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type ContentType =
|
|
60
|
+
| "text/plain"
|
|
61
|
+
| "text/html"
|
|
62
|
+
| "text/markdown"
|
|
63
|
+
| "application/json"
|
|
64
|
+
| "application/xml"
|
|
65
|
+
| "application/pdf"
|
|
66
|
+
| "image/png"
|
|
67
|
+
| "image/jpeg"
|
|
68
|
+
| "image/gif"
|
|
69
|
+
| "image/webp"
|
|
70
|
+
| "image/svg+xml"
|
|
71
|
+
| "audio/mpeg"
|
|
72
|
+
| "audio/wav"
|
|
73
|
+
| "video/mp4"
|
|
74
|
+
| "video/webm"
|
|
75
|
+
| "application/zip"
|
|
76
|
+
| "application/octet-stream";
|
|
77
|
+
|
|
78
|
+
export type ReadUrlResult = { contentType: ContentType; data: string };
|
|
79
|
+
|
|
80
|
+
export async function readUrl(
|
|
81
|
+
url: string,
|
|
82
|
+
abortSignal?: AbortSignal | undefined,
|
|
83
|
+
): Promise<ReadUrlResult> {
|
|
84
|
+
let initialResponse: Response;
|
|
85
|
+
try {
|
|
86
|
+
// Initial fetch to check content type and potentially use directly
|
|
87
|
+
logger.debug(`Performing initial fetch for: ${url}`);
|
|
88
|
+
initialResponse = await fetch(url, { signal: abortSignal });
|
|
89
|
+
if (!initialResponse.ok) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`HTTP error! status: ${initialResponse.status} ${initialResponse.statusText}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
logger.debug(
|
|
95
|
+
`Initial fetch successful for: ${url}, Status: ${initialResponse.status}`,
|
|
96
|
+
);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// If the initial fetch fails entirely, rethrow
|
|
99
|
+
logger.error(`Initial fetch failed for ${url}: ${error}`);
|
|
100
|
+
throw new Error(`Error fetching initial data for ${url}: ${error}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const contentType: ContentType =
|
|
104
|
+
(initialResponse.headers.get("content-type") as ContentType) ??
|
|
105
|
+
"text/plain";
|
|
106
|
+
logger.debug(`Content-Type for ${url}: ${contentType}`);
|
|
107
|
+
|
|
108
|
+
// If content type is HTML, try Jina first
|
|
109
|
+
if (contentType.includes("text/html")) {
|
|
110
|
+
logger.info(`Detected HTML content for ${url}. Attempting Jina AI fetch.`);
|
|
111
|
+
try {
|
|
112
|
+
const apiKey = process.env["JINA_READER_API_KEY"];
|
|
113
|
+
if (!apiKey) {
|
|
114
|
+
logger.warn("JINA_READER_API_KEY not set. Skipping Jina fetch.");
|
|
115
|
+
throw new Error("Jina API key not available"); // Skip to fallback
|
|
116
|
+
}
|
|
117
|
+
const jinaReadUrl = `https://r.jina.ai/${url}`;
|
|
118
|
+
logger.debug(`Fetching with Jina: ${jinaReadUrl}`);
|
|
119
|
+
const jinaResponse = await fetch(jinaReadUrl, {
|
|
120
|
+
method: "GET",
|
|
121
|
+
headers: {
|
|
122
|
+
// biome-ignore lint/style/useNamingConvention: API requirement
|
|
123
|
+
Authorization: `Bearer ${apiKey}`,
|
|
124
|
+
"X-With-Generated-Alt": "true", // Optional: Ask Jina to include image descriptions
|
|
125
|
+
"X-With-Links-Summary": "true", // Optional: Ask Jina for a summary of links
|
|
126
|
+
},
|
|
127
|
+
signal: abortSignal,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (jinaResponse.ok) {
|
|
131
|
+
const data = await jinaResponse.text();
|
|
132
|
+
logger.info(
|
|
133
|
+
`Successfully fetched and processed HTML URL with Jina: ${url}`,
|
|
134
|
+
);
|
|
135
|
+
return {
|
|
136
|
+
contentType,
|
|
137
|
+
data,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
logger.warn(
|
|
141
|
+
`Jina fetch failed for ${url} with status ${jinaResponse.status}: ${jinaResponse.statusText}. Falling back to direct fetch and clean.`,
|
|
142
|
+
);
|
|
143
|
+
// Fall through to use the initialResponse if Jina fails
|
|
144
|
+
} catch (error) {
|
|
145
|
+
logger.warn(
|
|
146
|
+
`Error fetching from Jina for ${url}: ${(error as Error).message}. Falling back to direct fetch and clean.`,
|
|
147
|
+
);
|
|
148
|
+
// Fall through to use the initialResponse if Jina fails
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Fallback for HTML: Use the initial response and clean it
|
|
152
|
+
try {
|
|
153
|
+
logger.warn(
|
|
154
|
+
`Falling back to direct fetch and cleaning for HTML URL: ${url}`,
|
|
155
|
+
);
|
|
156
|
+
const htmlText = await initialResponse.text();
|
|
157
|
+
logger.debug(
|
|
158
|
+
`Cleaning HTML content for ${url} (length: ${htmlText.length})`,
|
|
159
|
+
);
|
|
160
|
+
const cleaner = HtmlCleaner.new(htmlText);
|
|
161
|
+
const processedText = cleaner.clean();
|
|
162
|
+
logger.info(
|
|
163
|
+
`Successfully cleaned HTML content for ${url} (length: ${processedText.length})`,
|
|
164
|
+
);
|
|
165
|
+
return {
|
|
166
|
+
contentType,
|
|
167
|
+
data: processedText,
|
|
168
|
+
};
|
|
169
|
+
} catch (cleanError) {
|
|
170
|
+
logger.error(
|
|
171
|
+
`Error cleaning HTML from fallback fetch for ${url}: ${cleanError}`,
|
|
172
|
+
);
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Error cleaning HTML from fallback fetch for ${url}: ${cleanError}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
// If not HTML, return the text directly from the initial response
|
|
179
|
+
logger.info(
|
|
180
|
+
`Fetched non-HTML content directly: ${url} (Content-Type: ${contentType})`,
|
|
181
|
+
);
|
|
182
|
+
try {
|
|
183
|
+
if (contentType.startsWith("image/")) {
|
|
184
|
+
const arrayBuffer = await initialResponse.arrayBuffer();
|
|
185
|
+
const base64 = Buffer.from(arrayBuffer).toString("base64");
|
|
186
|
+
const base64Url = `data:${contentType};base64,${base64}`;
|
|
187
|
+
logger.debug(
|
|
188
|
+
`Returning base64 image data for ${url} (length: ${base64.length})`,
|
|
189
|
+
);
|
|
190
|
+
return {
|
|
191
|
+
contentType,
|
|
192
|
+
data: base64Url,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const textContent = await initialResponse.text();
|
|
196
|
+
logger.debug(
|
|
197
|
+
`Returning raw text content for ${url} (length: ${textContent.length})`,
|
|
198
|
+
);
|
|
199
|
+
return {
|
|
200
|
+
contentType,
|
|
201
|
+
data: textContent,
|
|
202
|
+
};
|
|
203
|
+
} catch (textError) {
|
|
204
|
+
logger.error(`Error reading response for ${url}: ${textError}`);
|
|
205
|
+
throw new Error(`Error reading response for ${url}: ${textError}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export class HtmlCleaner {
|
|
211
|
+
static new(html: string): HtmlCleaner {
|
|
212
|
+
return new HtmlCleaner(html);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private html: string;
|
|
216
|
+
|
|
217
|
+
private constructor(html: string) {
|
|
218
|
+
this.html = html;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Cleans HTML content by removing unnecessary elements and simplifying structure
|
|
223
|
+
* @param {Object} [options] - Configuration options for cleaning
|
|
224
|
+
* @param {boolean} [options.simplify=true] - Whether to simplify HTML structure by removing redundant elements
|
|
225
|
+
* @param {boolean} [options.empty=true] - Whether to remove empty elements from the HTML
|
|
226
|
+
* @returns {string} Cleaned HTML content with removed whitespace and line breaks
|
|
227
|
+
*/
|
|
228
|
+
clean(options?: { simplify?: boolean; empty?: boolean }): string {
|
|
229
|
+
const { simplify = true, empty = true } = options ?? {};
|
|
230
|
+
|
|
231
|
+
const $ = load(this.html);
|
|
232
|
+
|
|
233
|
+
// Remove scripts, styles, and comments
|
|
234
|
+
this.removeUnnecessaryElements($);
|
|
235
|
+
|
|
236
|
+
// Simplify HTML structure
|
|
237
|
+
if (simplify) {
|
|
238
|
+
this.simplifyStructure($);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Remove empty elements
|
|
242
|
+
if (empty) {
|
|
243
|
+
this.removeEmptyElements($);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Get cleaned HTML
|
|
247
|
+
return $.html()
|
|
248
|
+
.trim()
|
|
249
|
+
.replace(/^\s*[\r\n]/gm, "");
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Removes scripts, styles, and comments from HTML
|
|
253
|
+
*/
|
|
254
|
+
private removeUnnecessaryElements($: CheerioAPI): void {
|
|
255
|
+
// Remove all script tags
|
|
256
|
+
$("script").remove();
|
|
257
|
+
|
|
258
|
+
// Remove all noscript tags
|
|
259
|
+
$("noscript").remove();
|
|
260
|
+
|
|
261
|
+
// Remove all style tags
|
|
262
|
+
$("style").remove();
|
|
263
|
+
|
|
264
|
+
// Remove all link tags (external stylesheets)
|
|
265
|
+
$('link[rel="stylesheet"]').remove();
|
|
266
|
+
|
|
267
|
+
// Remove all preload link tags
|
|
268
|
+
$('link[rel="preload"]').remove();
|
|
269
|
+
|
|
270
|
+
// Remove all link tags
|
|
271
|
+
$("link").remove();
|
|
272
|
+
|
|
273
|
+
// Remove all forms
|
|
274
|
+
$("form").remove();
|
|
275
|
+
|
|
276
|
+
// Remove comments
|
|
277
|
+
$("*")
|
|
278
|
+
.contents()
|
|
279
|
+
.each((_, element) => {
|
|
280
|
+
if (element.type === "comment") {
|
|
281
|
+
$(element).remove();
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Remove all inline styles
|
|
286
|
+
$("[style]").removeAttr("style");
|
|
287
|
+
|
|
288
|
+
// Remove all class attributes
|
|
289
|
+
$("[class]").removeAttr("class");
|
|
290
|
+
|
|
291
|
+
// Remove all id attributes
|
|
292
|
+
$("[id]").removeAttr("id");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Simplifies HTML structure by merging redundant tags
|
|
297
|
+
*/
|
|
298
|
+
private simplifyStructure($: CheerioAPI): void {
|
|
299
|
+
// Merge nested divs
|
|
300
|
+
$("div div").each((_, element) => {
|
|
301
|
+
const $element = $(element);
|
|
302
|
+
const parent = $element.parent();
|
|
303
|
+
|
|
304
|
+
if (parent.children().length === 1 && parent.get(0)?.tagName === "div") {
|
|
305
|
+
$element.unwrap();
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Remove redundant spans
|
|
310
|
+
$("span").each((_, element) => {
|
|
311
|
+
const $element = $(element);
|
|
312
|
+
if (!$element.attr() || Object.keys($element.attr() ?? {}).length === 0) {
|
|
313
|
+
const h = $element.html();
|
|
314
|
+
if (h) {
|
|
315
|
+
$element.replaceWith(h);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Removes empty elements from HTML
|
|
323
|
+
*/
|
|
324
|
+
private removeEmptyElements($: CheerioAPI): void {
|
|
325
|
+
$(":empty").remove();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { SafeSearchType, type SearchResult, search } from "duck-duck-scrape";
|
|
4
|
+
import Exa from "exa-js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { TokenCounter } from "../token-utils.ts";
|
|
7
|
+
import type { SendData } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
export const WebSearchTool = {
|
|
10
|
+
name: "webSearch" as const,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const createWebSearchTool = ({
|
|
14
|
+
sendData,
|
|
15
|
+
tokenCounter,
|
|
16
|
+
}: {
|
|
17
|
+
sendData?: SendData;
|
|
18
|
+
tokenCounter: TokenCounter;
|
|
19
|
+
}) => {
|
|
20
|
+
return {
|
|
21
|
+
[WebSearchTool.name]: tool({
|
|
22
|
+
description:
|
|
23
|
+
"Searches the web and returns match documents with their title, url, and text content. The query should be formulated as a natural language question.",
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
query: z.string().describe("The search query."),
|
|
26
|
+
}),
|
|
27
|
+
execute: async ({ query }, { toolCallId }) => {
|
|
28
|
+
sendData?.({
|
|
29
|
+
id: toolCallId,
|
|
30
|
+
event: "tool-init",
|
|
31
|
+
data: `Web search: ${chalk.cyan(query)}`,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const result = await performSearch(query);
|
|
35
|
+
|
|
36
|
+
const sources = result.results.map(
|
|
37
|
+
(source) =>
|
|
38
|
+
`## ${source.title}\nURL: ${source.url}\n\n${source.text}`,
|
|
39
|
+
);
|
|
40
|
+
const resultText = `# Search Results:\n\n${sources.join("\n\n")}`;
|
|
41
|
+
const tokenCount = tokenCounter.count(resultText);
|
|
42
|
+
|
|
43
|
+
sendData?.({
|
|
44
|
+
id: toolCallId,
|
|
45
|
+
event: "tool-completion",
|
|
46
|
+
data: `Found ${result.results.length} results. (${tokenCount} tokens)`,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return resultText;
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
async function performSearch(query: string) {
|
|
56
|
+
// Check if EXA API key is available
|
|
57
|
+
const hasExaApiKey =
|
|
58
|
+
process.env["EXA_API_KEY"] && process.env["EXA_API_KEY"].trim() !== "";
|
|
59
|
+
|
|
60
|
+
if (hasExaApiKey) {
|
|
61
|
+
// Use Exa search
|
|
62
|
+
try {
|
|
63
|
+
const exa = new Exa(process.env["EXA_API_KEY"]);
|
|
64
|
+
const result = await exa.searchAndContents(query, {
|
|
65
|
+
numResults: 5,
|
|
66
|
+
text: true,
|
|
67
|
+
});
|
|
68
|
+
return result;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// If Exa fails, fall back to duck duck scrape
|
|
71
|
+
console.info("Exa search failed, falling back to DuckDuckGo:", error);
|
|
72
|
+
return await searchWithDuckDuckGo(query);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
// Use DuckDuckGo search as fallback
|
|
76
|
+
console.info("EXA_API_KEY not set, using DuckDuckGo search");
|
|
77
|
+
return await searchWithDuckDuckGo(query);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function searchWithDuckDuckGo(query: string) {
|
|
82
|
+
try {
|
|
83
|
+
const searchResults = await search(query, {
|
|
84
|
+
safeSearch: SafeSearchType.MODERATE,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Transform duck-duck-scrape results to match Exa format
|
|
88
|
+
// Take only first 5 results to match Exa behavior
|
|
89
|
+
const results = searchResults.results
|
|
90
|
+
.slice(0, 5)
|
|
91
|
+
.map((result: SearchResult) => ({
|
|
92
|
+
title: result.title,
|
|
93
|
+
url: result.url,
|
|
94
|
+
text: result.description || "",
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
return { results };
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throw new Error(`Failed to perform web search: ${error}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { isUndefined } from "@travisennis/stdlib/typeguards";
|
|
3
|
+
|
|
4
|
+
const MS_IN_SECOND = 1000;
|
|
5
|
+
const SECONDS_IN_MINUTE = 60;
|
|
6
|
+
const DEFAULT_TIMEOUT = 2 * SECONDS_IN_MINUTE * MS_IN_SECOND;
|
|
7
|
+
|
|
8
|
+
interface ExecuteOptions {
|
|
9
|
+
/** Working directory where the command will be executed */
|
|
10
|
+
cwd?: string;
|
|
11
|
+
/** Timeout in milliseconds before killing the process */
|
|
12
|
+
timeout?: number;
|
|
13
|
+
/** AbortSignal to cancel the execution */
|
|
14
|
+
abortSignal?: AbortSignal;
|
|
15
|
+
/** Whether to use shell syntax (defaults to false) */
|
|
16
|
+
shell?: boolean;
|
|
17
|
+
/** Whether to throw an error on non-zero exit codes (defaults to false) */
|
|
18
|
+
throwOnError?: boolean;
|
|
19
|
+
/** Whether to include stdout/stderr in the result even when there's an error (defaults to true) */
|
|
20
|
+
preserveOutputOnError?: boolean;
|
|
21
|
+
/** Maximum buffer size in bytes (defaults to 1MB) */
|
|
22
|
+
maxBuffer?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ExecuteResult {
|
|
26
|
+
/** Standard output from the command */
|
|
27
|
+
stdout: string;
|
|
28
|
+
/** Standard error from the command */
|
|
29
|
+
stderr: string;
|
|
30
|
+
/** Exit code (0 for success, non-zero for errors) */
|
|
31
|
+
code: number;
|
|
32
|
+
/** The signal that terminated the process, if any */
|
|
33
|
+
signal?: NodeJS.Signals;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Executes a command and returns the result, providing unified error handling
|
|
38
|
+
*
|
|
39
|
+
* @param command Either a string command with arguments or an array where the first item is the command
|
|
40
|
+
* and the rest are arguments
|
|
41
|
+
* @param options Execution options
|
|
42
|
+
* @returns Promise resolving to an object containing stdout, stderr, and exit code
|
|
43
|
+
*/
|
|
44
|
+
export function executeCommand(
|
|
45
|
+
command: string | [string, ...string[]],
|
|
46
|
+
options?: ExecuteOptions,
|
|
47
|
+
): Promise<ExecuteResult> {
|
|
48
|
+
const {
|
|
49
|
+
cwd = process.cwd(),
|
|
50
|
+
timeout = DEFAULT_TIMEOUT,
|
|
51
|
+
abortSignal,
|
|
52
|
+
shell = false,
|
|
53
|
+
throwOnError = false,
|
|
54
|
+
preserveOutputOnError = true,
|
|
55
|
+
maxBuffer = 1_000_000,
|
|
56
|
+
} = options || {};
|
|
57
|
+
|
|
58
|
+
let cmd: string;
|
|
59
|
+
let args: string[];
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(command)) {
|
|
62
|
+
[cmd, ...args] = command;
|
|
63
|
+
} else {
|
|
64
|
+
const parts = command.split(" ");
|
|
65
|
+
cmd = parts[0] ?? "";
|
|
66
|
+
args = parts.slice(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isUndefined(cmd) || cmd.trim() === "") {
|
|
70
|
+
const result: ExecuteResult = {
|
|
71
|
+
stdout: "",
|
|
72
|
+
stderr: "Missing command",
|
|
73
|
+
code: 1,
|
|
74
|
+
};
|
|
75
|
+
return throwOnError
|
|
76
|
+
? Promise.reject(new Error("Missing command"))
|
|
77
|
+
: Promise.resolve(result);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return new Promise<ExecuteResult>((resolve, reject) => {
|
|
81
|
+
try {
|
|
82
|
+
execFile(
|
|
83
|
+
cmd,
|
|
84
|
+
args,
|
|
85
|
+
{
|
|
86
|
+
cwd,
|
|
87
|
+
timeout,
|
|
88
|
+
signal: abortSignal,
|
|
89
|
+
shell,
|
|
90
|
+
maxBuffer,
|
|
91
|
+
},
|
|
92
|
+
(error, stdout, stderr) => {
|
|
93
|
+
if (error) {
|
|
94
|
+
const errorCode = typeof error.code === "number" ? error.code : 1;
|
|
95
|
+
const result: ExecuteResult = {
|
|
96
|
+
stdout: preserveOutputOnError ? stdout : "",
|
|
97
|
+
stderr: preserveOutputOnError ? stderr : "",
|
|
98
|
+
code: errorCode,
|
|
99
|
+
signal: error.signal ?? undefined,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (throwOnError) {
|
|
103
|
+
reject(Object.assign(error, { result }));
|
|
104
|
+
} else {
|
|
105
|
+
resolve(result);
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
resolve({ stdout, stderr, code: 0 });
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const result: ExecuteResult = { stdout: "", stderr: "", code: 1 };
|
|
114
|
+
if (throwOnError) {
|
|
115
|
+
reject(error);
|
|
116
|
+
} else {
|
|
117
|
+
resolve(result);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "@travisennis/stdlib/desm";
|
|
3
|
+
|
|
4
|
+
export function getPackageVersion(fallback = "unavailable"): string {
|
|
5
|
+
try {
|
|
6
|
+
const pkgPath = join(import.meta.url, "..", "package.json");
|
|
7
|
+
const pkgRaw = readFileSync(pkgPath, "utf8");
|
|
8
|
+
const parsed = JSON.parse(pkgRaw) as { version?: unknown };
|
|
9
|
+
const v = typeof parsed.version === "string" ? parsed.version : undefined;
|
|
10
|
+
if (v && v.length > 0) {
|
|
11
|
+
return v;
|
|
12
|
+
}
|
|
13
|
+
} catch {
|
|
14
|
+
// ignore
|
|
15
|
+
}
|
|
16
|
+
const envV = process.env["npm_package_version"];
|
|
17
|
+
if (typeof envV === "string" && envV.length > 0) {
|
|
18
|
+
return envV;
|
|
19
|
+
}
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import type { ModelMessage, TextPart } from "ai";
|
|
4
|
+
import { copyCommand } from "../../source/commands/copy-command.ts";
|
|
5
|
+
import type { CommandOptions } from "../../source/commands/types.ts";
|
|
6
|
+
|
|
7
|
+
function makeAssistant(text: string): ModelMessage {
|
|
8
|
+
return {
|
|
9
|
+
role: "assistant",
|
|
10
|
+
content: [{ type: "text", text } as TextPart],
|
|
11
|
+
} as ModelMessage;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function makeUser(text: string): ModelMessage {
|
|
15
|
+
return {
|
|
16
|
+
role: "user",
|
|
17
|
+
content: [{ type: "text", text } as TextPart],
|
|
18
|
+
} as ModelMessage;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("/copy command", () => {
|
|
22
|
+
it("returns info when no assistant response exists", async () => {
|
|
23
|
+
const outputs: string[] = [];
|
|
24
|
+
const options = {
|
|
25
|
+
terminal: {
|
|
26
|
+
info: (msg: string) => outputs.push(`info:${msg}`),
|
|
27
|
+
success: (_msg: string) => outputs.push("success"),
|
|
28
|
+
error: (_msg: string) => outputs.push("error"),
|
|
29
|
+
},
|
|
30
|
+
messageHistory: {
|
|
31
|
+
get: () => [makeUser("hello")] as ModelMessage[],
|
|
32
|
+
},
|
|
33
|
+
} as unknown as CommandOptions;
|
|
34
|
+
|
|
35
|
+
const cmd = copyCommand(options);
|
|
36
|
+
await cmd.execute([]);
|
|
37
|
+
|
|
38
|
+
assert(outputs.some((o) => o.startsWith("info:")));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("copies last assistant text via clipboard and reports success", async (_t) => {
|
|
42
|
+
// Temporarily replace writeToClipboard by monkey-patching module function via dynamic import cache
|
|
43
|
+
const outputs: string[] = [];
|
|
44
|
+
const options = {
|
|
45
|
+
terminal: {
|
|
46
|
+
info: (msg: string) => outputs.push(`info:${msg}`),
|
|
47
|
+
success: (msg: string) => outputs.push(`success:${msg}`),
|
|
48
|
+
error: (msg: string) => outputs.push(`error:${msg}`),
|
|
49
|
+
},
|
|
50
|
+
messageHistory: {
|
|
51
|
+
get: () =>
|
|
52
|
+
[makeUser("hello"), makeAssistant("world")] as ModelMessage[],
|
|
53
|
+
},
|
|
54
|
+
} as unknown as CommandOptions;
|
|
55
|
+
|
|
56
|
+
// We cannot easily mock child_process spawn without a mocking framework; this test focuses on flow.
|
|
57
|
+
// Execute should attempt clipboard; since environment may not have tools, we only assert it didn't crash synchronously.
|
|
58
|
+
const cmd = copyCommand(options);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await cmd.execute([]);
|
|
62
|
+
} catch {
|
|
63
|
+
// Ignore runtime env clipboard errors
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Should either success or error, but not be silent
|
|
67
|
+
assert(outputs.length > 0);
|
|
68
|
+
});
|
|
69
|
+
});
|