@travisennis/acai 0.0.4 → 0.0.6
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/README.md +229 -8
- package/dist/agent/index.d.ts +119 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +406 -0
- package/dist/agent/manual-loop.d.ts +41 -0
- package/dist/agent/manual-loop.d.ts.map +1 -0
- package/dist/agent/manual-loop.js +278 -0
- package/dist/api/exa/index.d.ts +177 -0
- package/dist/api/exa/index.d.ts.map +1 -0
- package/dist/api/exa/index.js +439 -0
- package/dist/cli.d.ts +5 -2
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +27 -33
- package/dist/commands/add-directory-command.d.ts +3 -0
- package/dist/commands/add-directory-command.d.ts.map +1 -0
- package/dist/commands/add-directory-command.js +85 -0
- package/dist/commands/application-log-command.d.ts +1 -0
- package/dist/commands/application-log-command.d.ts.map +1 -0
- package/dist/commands/application-log-command.js +39 -3
- package/dist/commands/clear-command.d.ts +1 -0
- package/dist/commands/clear-command.d.ts.map +1 -0
- package/dist/commands/clear-command.js +10 -3
- package/dist/commands/compact-command.d.ts +1 -0
- package/dist/commands/compact-command.d.ts.map +1 -0
- package/dist/commands/compact-command.js +16 -3
- package/dist/commands/context-command.d.ts +3 -0
- package/dist/commands/context-command.d.ts.map +1 -0
- package/dist/commands/context-command.js +183 -0
- package/dist/commands/copy-command.d.ts +1 -0
- package/dist/commands/copy-command.d.ts.map +1 -0
- package/dist/commands/copy-command.js +31 -2
- package/dist/commands/edit-command.d.ts +1 -0
- package/dist/commands/edit-command.d.ts.map +1 -0
- package/dist/commands/edit-command.js +40 -5
- package/dist/commands/edit-prompt-command.d.ts +2 -1
- package/dist/commands/edit-prompt-command.d.ts.map +1 -0
- package/dist/commands/edit-prompt-command.js +43 -7
- package/dist/commands/exit-command.d.ts +13 -2
- package/dist/commands/exit-command.d.ts.map +1 -0
- package/dist/commands/exit-command.js +34 -2
- package/dist/commands/files-command.d.ts +1 -0
- package/dist/commands/files-command.d.ts.map +1 -0
- package/dist/commands/files-command.js +66 -8
- package/dist/commands/generate-rules-command.d.ts +1 -0
- package/dist/commands/generate-rules-command.d.ts.map +1 -0
- package/dist/commands/generate-rules-command.js +315 -4
- package/dist/commands/handoff-command.d.ts +3 -0
- package/dist/commands/handoff-command.d.ts.map +1 -0
- package/dist/commands/handoff-command.js +202 -0
- package/dist/commands/health-command.d.ts +3 -1
- package/dist/commands/health-command.d.ts.map +1 -0
- package/dist/commands/health-command.js +160 -6
- package/dist/commands/help-command.d.ts +1 -0
- package/dist/commands/help-command.d.ts.map +1 -0
- package/dist/commands/help-command.js +30 -3
- package/dist/commands/history-command.d.ts +3 -0
- package/dist/commands/history-command.d.ts.map +1 -0
- package/dist/commands/history-command.js +534 -0
- package/dist/commands/init-command.d.ts +2 -1
- package/dist/commands/init-command.d.ts.map +1 -0
- package/dist/commands/init-command.js +56 -20
- package/dist/commands/last-log-command.d.ts +1 -0
- package/dist/commands/last-log-command.d.ts.map +1 -0
- package/dist/commands/last-log-command.js +39 -17
- package/dist/commands/list-directories-command.d.ts +3 -0
- package/dist/commands/list-directories-command.d.ts.map +1 -0
- package/dist/commands/list-directories-command.js +48 -0
- package/dist/commands/list-tools-command.d.ts +3 -0
- package/dist/commands/list-tools-command.d.ts.map +1 -0
- package/dist/commands/list-tools-command.js +124 -0
- package/dist/commands/manager.d.ts +20 -3
- package/dist/commands/manager.d.ts.map +1 -0
- package/dist/commands/manager.js +123 -26
- package/dist/commands/model-command.d.ts +23 -0
- package/dist/commands/model-command.d.ts.map +1 -0
- package/dist/commands/model-command.js +261 -5
- package/dist/commands/paste-command.d.ts +1 -0
- package/dist/commands/paste-command.d.ts.map +1 -0
- package/dist/commands/paste-command.js +98 -5
- package/dist/commands/pickup-command.d.ts +3 -0
- package/dist/commands/pickup-command.d.ts.map +1 -0
- package/dist/commands/pickup-command.js +161 -0
- package/dist/commands/prompt-command.d.ts +2 -1
- package/dist/commands/prompt-command.d.ts.map +1 -0
- package/dist/commands/prompt-command.js +178 -9
- package/dist/commands/remove-directory-command.d.ts +3 -0
- package/dist/commands/remove-directory-command.d.ts.map +1 -0
- package/dist/commands/remove-directory-command.js +87 -0
- package/dist/commands/reset-command.d.ts +2 -1
- package/dist/commands/reset-command.d.ts.map +1 -0
- package/dist/commands/reset-command.js +14 -3
- package/dist/commands/rules-command.d.ts +1 -0
- package/dist/commands/rules-command.d.ts.map +1 -0
- package/dist/commands/rules-command.js +70 -3
- package/dist/commands/save-command.d.ts +1 -0
- package/dist/commands/save-command.d.ts.map +1 -0
- package/dist/commands/save-command.js +13 -1
- package/dist/commands/shell-command.d.ts +3 -0
- package/dist/commands/shell-command.d.ts.map +1 -0
- package/dist/commands/shell-command.js +128 -0
- package/dist/commands/types.d.ts +16 -8
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/usage-command.d.ts +1 -0
- package/dist/commands/usage-command.d.ts.map +1 -0
- package/dist/commands/usage-command.js +24 -3
- package/dist/config.d.ts +21 -34
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +55 -15
- package/dist/dedent.d.ts +1 -0
- package/dist/dedent.d.ts.map +1 -0
- package/dist/execution/index.d.ts +112 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +432 -0
- package/dist/formatting.d.ts +107 -10
- package/dist/formatting.d.ts.map +1 -0
- package/dist/formatting.js +150 -62
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +150 -38
- package/dist/logger.d.ts +1 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +47 -18
- package/dist/mentions.d.ts +6 -1
- package/dist/mentions.d.ts.map +1 -0
- package/dist/mentions.js +58 -11
- package/dist/messages.d.ts +16 -20
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +89 -72
- package/dist/middleware/audit-message.d.ts +1 -0
- package/dist/middleware/audit-message.d.ts.map +1 -0
- package/dist/middleware/cache.d.ts +3 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +53 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/rate-limit.d.ts +1 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/models/ai-config.d.ts +5 -2
- package/dist/models/ai-config.d.ts.map +1 -0
- package/dist/models/ai-config.js +12 -2
- package/dist/models/anthropic-provider.d.ts +1 -0
- package/dist/models/anthropic-provider.d.ts.map +1 -0
- package/dist/models/anthropic-provider.js +3 -60
- package/dist/models/deepseek-provider.d.ts +1 -0
- package/dist/models/deepseek-provider.d.ts.map +1 -0
- package/dist/models/google-provider.d.ts +1 -0
- package/dist/models/google-provider.d.ts.map +1 -0
- package/dist/models/groq-provider.d.ts +20 -0
- package/dist/models/groq-provider.d.ts.map +1 -0
- package/dist/models/groq-provider.js +31 -0
- package/dist/models/manager.d.ts +3 -1
- package/dist/models/manager.d.ts.map +1 -0
- package/dist/models/manager.js +26 -2
- package/dist/models/openai-provider.d.ts +2 -1
- package/dist/models/openai-provider.d.ts.map +1 -0
- package/dist/models/openrouter-provider.d.ts +25 -23
- package/dist/models/openrouter-provider.d.ts.map +1 -0
- package/dist/models/openrouter-provider.js +181 -122
- package/dist/models/providers.d.ts +4 -5
- package/dist/models/providers.d.ts.map +1 -0
- package/dist/models/providers.js +7 -3
- package/dist/models/xai-provider.d.ts +1 -0
- package/dist/models/xai-provider.d.ts.map +1 -0
- package/dist/parsing.d.ts +2 -1
- package/dist/parsing.d.ts.map +1 -0
- package/dist/prompts/manager.d.ts +14 -2
- package/dist/prompts/manager.d.ts.map +1 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +65 -12
- package/dist/repl/display-tool-messages.d.ts +4 -0
- package/dist/repl/display-tool-messages.d.ts.map +1 -0
- package/dist/repl/display-tool-messages.js +58 -0
- package/dist/repl/display-tool-use.d.ts +14 -0
- package/dist/repl/display-tool-use.d.ts.map +1 -0
- package/dist/repl/display-tool-use.js +63 -0
- package/dist/repl/get-prompt-header.d.ts +8 -0
- package/dist/repl/get-prompt-header.d.ts.map +1 -0
- package/dist/repl/get-prompt-header.js +9 -0
- package/dist/repl/project-status-line.d.ts +2 -0
- package/dist/repl/project-status-line.d.ts.map +1 -0
- package/dist/repl/project-status-line.js +31 -0
- package/dist/repl/prompt.d.ts +21 -0
- package/dist/repl/prompt.d.ts.map +1 -0
- package/dist/{repl-prompt.js → repl/prompt.js} +119 -22
- package/dist/repl/tool-call-repair.d.ts +4 -0
- package/dist/repl/tool-call-repair.d.ts.map +1 -0
- package/dist/repl/tool-call-repair.js +54 -0
- package/dist/repl-new.d.ts +53 -0
- package/dist/repl-new.d.ts.map +1 -0
- package/dist/repl-new.js +374 -0
- package/dist/repl.d.ts +9 -7
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +142 -378
- package/dist/terminal/ansi-styles.d.ts +77 -0
- package/dist/terminal/ansi-styles.d.ts.map +1 -0
- package/dist/terminal/ansi-styles.js +215 -0
- package/dist/terminal/checkbox-prompt.d.ts +36 -0
- package/dist/terminal/checkbox-prompt.d.ts.map +1 -0
- package/dist/terminal/checkbox-prompt.js +368 -0
- package/dist/terminal/default-theme.d.ts +6 -0
- package/dist/terminal/default-theme.d.ts.map +1 -0
- package/dist/terminal/default-theme.js +182 -0
- package/dist/terminal/east-asian-width.d.ts +8 -0
- package/dist/terminal/east-asian-width.d.ts.map +1 -0
- package/dist/terminal/east-asian-width.js +409 -0
- package/dist/terminal/editor-prompt.d.ts +10 -0
- package/dist/terminal/editor-prompt.d.ts.map +1 -0
- package/dist/terminal/editor-prompt.js +61 -0
- package/dist/terminal/errors.d.ts +19 -0
- package/dist/terminal/errors.d.ts.map +1 -0
- package/dist/terminal/errors.js +37 -0
- package/dist/terminal/formatting.d.ts +1 -11
- package/dist/terminal/formatting.d.ts.map +1 -0
- package/dist/terminal/formatting.js +4 -20
- package/dist/terminal/highlight/index.d.ts +53 -0
- package/dist/terminal/highlight/index.d.ts.map +1 -0
- package/dist/terminal/highlight/index.js +90 -0
- package/dist/terminal/highlight/theme.d.ts +233 -0
- package/dist/terminal/highlight/theme.d.ts.map +1 -0
- package/dist/terminal/highlight/theme.js +83 -0
- package/dist/terminal/index.d.ts +23 -9
- package/dist/terminal/index.d.ts.map +1 -0
- package/dist/terminal/index.js +136 -126
- package/dist/terminal/input-prompt.d.ts +17 -0
- package/dist/terminal/input-prompt.d.ts.map +1 -0
- package/dist/terminal/input-prompt.js +181 -0
- package/dist/terminal/markdown-utils.d.ts +1 -0
- package/dist/terminal/markdown-utils.d.ts.map +1 -0
- package/dist/terminal/markdown.d.ts +1 -0
- package/dist/terminal/markdown.d.ts.map +1 -0
- package/dist/terminal/markdown.js +20 -12
- package/dist/terminal/search-prompt.d.ts +20 -0
- package/dist/terminal/search-prompt.d.ts.map +1 -0
- package/dist/terminal/search-prompt.js +280 -0
- package/dist/terminal/select-prompt.d.ts +26 -0
- package/dist/terminal/select-prompt.d.ts.map +1 -0
- package/dist/terminal/select-prompt.js +306 -0
- package/dist/terminal/string-width.d.ts +7 -0
- package/dist/terminal/string-width.d.ts.map +1 -0
- package/dist/terminal/string-width.js +61 -0
- package/dist/terminal/strip-ansi.d.ts +2 -0
- package/dist/terminal/strip-ansi.d.ts.map +1 -0
- package/dist/terminal/strip-ansi.js +20 -0
- package/dist/terminal/style.d.ts +191 -0
- package/dist/terminal/style.d.ts.map +1 -0
- package/dist/terminal/style.js +259 -0
- package/dist/terminal/supports-color.d.ts +1 -0
- package/dist/terminal/supports-color.d.ts.map +1 -0
- package/dist/terminal/supports-hyperlinks.d.ts +1 -3
- package/dist/terminal/supports-hyperlinks.d.ts.map +1 -0
- package/dist/terminal/supports-hyperlinks.js +1 -1
- package/dist/terminal/types.d.ts +1 -37
- package/dist/terminal/types.d.ts.map +1 -0
- package/dist/terminal/wrap-ansi.d.ts +8 -0
- package/dist/terminal/wrap-ansi.d.ts.map +1 -0
- package/dist/terminal/wrap-ansi.js +190 -0
- package/dist/{token-utils.d.ts → tokens/counter.d.ts} +1 -0
- package/dist/tokens/counter.d.ts.map +1 -0
- package/dist/{token-utils.js → tokens/counter.js} +1 -1
- package/dist/tokens/threshold.d.ts +35 -0
- package/dist/tokens/threshold.d.ts.map +1 -0
- package/dist/tokens/threshold.js +85 -0
- package/dist/{token-tracker.d.ts → tokens/tracker.d.ts} +1 -0
- package/dist/tokens/tracker.d.ts.map +1 -0
- package/dist/tools/advanced-edit-file.d.ts +69 -0
- package/dist/tools/advanced-edit-file.d.ts.map +1 -0
- package/dist/tools/advanced-edit-file.js +281 -0
- package/dist/tools/agent.d.ts +19 -7
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/agent.js +70 -54
- package/dist/tools/bash-utils.d.ts +7 -0
- package/dist/tools/bash-utils.d.ts.map +1 -0
- package/dist/tools/bash-utils.js +220 -0
- package/dist/tools/bash.d.ts +25 -14
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +87 -251
- package/dist/tools/code-interpreter.d.ts +22 -10
- package/dist/tools/code-interpreter.d.ts.map +1 -0
- package/dist/tools/code-interpreter.js +146 -210
- package/dist/tools/delete-file.d.ts +18 -9
- package/dist/tools/delete-file.d.ts.map +1 -0
- package/dist/tools/delete-file.js +55 -85
- package/dist/tools/directory-tree.d.ts +26 -6
- package/dist/tools/directory-tree.d.ts.map +1 -0
- package/dist/tools/directory-tree.js +109 -28
- package/dist/tools/dynamic-tool-loader.d.ts +22 -0
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -0
- package/dist/tools/dynamic-tool-loader.js +272 -0
- package/dist/tools/dynamic-tool-parser.d.ts +21 -0
- package/dist/tools/dynamic-tool-parser.d.ts.map +1 -0
- package/dist/tools/dynamic-tool-parser.js +22 -0
- package/dist/tools/edit-file.d.ts +41 -13
- package/dist/tools/edit-file.d.ts.map +1 -0
- package/dist/tools/edit-file.js +173 -96
- package/dist/tools/filesystem-utils.d.ts +7 -21
- package/dist/tools/filesystem-utils.d.ts.map +1 -0
- package/dist/tools/filesystem-utils.js +111 -149
- package/dist/tools/git-utils.d.ts +1 -0
- package/dist/tools/git-utils.d.ts.map +1 -0
- package/dist/tools/glob.d.ts +36 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +143 -0
- package/dist/tools/grep.d.ts +76 -13
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +420 -135
- package/dist/tools/index.d.ts +207 -133
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +245 -127
- package/dist/tools/llm-edit-fixer.d.ts +25 -0
- package/dist/tools/llm-edit-fixer.d.ts.map +1 -0
- package/dist/tools/llm-edit-fixer.js +150 -0
- package/dist/tools/move-file.d.ts +20 -7
- package/dist/tools/move-file.d.ts.map +1 -0
- package/dist/tools/move-file.js +43 -29
- package/dist/tools/read-file.d.ts +49 -10
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +79 -67
- package/dist/tools/read-multiple-files.d.ts +19 -7
- package/dist/tools/read-multiple-files.d.ts.map +1 -0
- package/dist/tools/read-multiple-files.js +117 -33
- package/dist/tools/save-file.d.ts +46 -11
- package/dist/tools/save-file.d.ts.map +1 -0
- package/dist/tools/save-file.js +63 -78
- package/dist/tools/think.d.ts +16 -7
- package/dist/tools/think.d.ts.map +1 -0
- package/dist/tools/think.js +34 -22
- package/dist/tools/types.d.ts +18 -11
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +9 -0
- package/dist/tools/utils.d.ts +14 -0
- package/dist/tools/utils.d.ts.map +1 -0
- package/dist/tools/utils.js +16 -0
- package/dist/tools/web-fetch.d.ts +15 -6
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +40 -39
- package/dist/tools/web-search.d.ts +17 -7
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +86 -33
- package/dist/tui/autocomplete.d.ts +44 -0
- package/dist/tui/autocomplete.d.ts.map +1 -0
- package/dist/tui/autocomplete.js +466 -0
- package/dist/tui/components/assistant-message.d.ts +18 -0
- package/dist/tui/components/assistant-message.d.ts.map +1 -0
- package/dist/tui/components/assistant-message.js +29 -0
- package/dist/tui/components/editor.d.ts +51 -0
- package/dist/tui/components/editor.d.ts.map +1 -0
- package/dist/tui/components/editor.js +758 -0
- package/dist/tui/components/footer.d.ts +24 -0
- package/dist/tui/components/footer.d.ts.map +1 -0
- package/dist/tui/components/footer.js +197 -0
- package/dist/tui/components/input.d.ts +14 -0
- package/dist/tui/components/input.d.ts.map +1 -0
- package/dist/tui/components/input.js +122 -0
- package/dist/tui/components/loader.d.ts +19 -0
- package/dist/tui/components/loader.d.ts.map +1 -0
- package/dist/tui/components/loader.js +45 -0
- package/dist/tui/components/markdown.d.ts +103 -0
- package/dist/tui/components/markdown.d.ts.map +1 -0
- package/dist/tui/components/markdown.js +533 -0
- package/dist/tui/components/modal.d.ts +40 -0
- package/dist/tui/components/modal.d.ts.map +1 -0
- package/dist/tui/components/modal.js +292 -0
- package/dist/tui/components/prompt-status.d.ts +16 -0
- package/dist/tui/components/prompt-status.d.ts.map +1 -0
- package/dist/tui/components/prompt-status.js +21 -0
- package/dist/tui/components/select-list.d.ts +22 -0
- package/dist/tui/components/select-list.d.ts.map +1 -0
- package/dist/tui/components/select-list.js +143 -0
- package/dist/tui/components/spacer.d.ts +16 -0
- package/dist/tui/components/spacer.d.ts.map +1 -0
- package/dist/tui/components/spacer.js +27 -0
- package/dist/tui/components/text.d.ts +26 -0
- package/dist/tui/components/text.d.ts.map +1 -0
- package/dist/tui/components/text.js +143 -0
- package/dist/tui/components/thinking-block.d.ts +14 -0
- package/dist/tui/components/thinking-block.d.ts.map +1 -0
- package/dist/tui/components/thinking-block.js +30 -0
- package/dist/tui/components/tool-execution.d.ts +17 -0
- package/dist/tui/components/tool-execution.d.ts.map +1 -0
- package/dist/tui/components/tool-execution.js +153 -0
- package/dist/tui/components/user-message.d.ts +9 -0
- package/dist/tui/components/user-message.d.ts.map +1 -0
- package/dist/tui/components/user-message.js +21 -0
- package/dist/tui/components/welcome.d.ts +6 -0
- package/dist/tui/components/welcome.d.ts.map +1 -0
- package/dist/tui/components/welcome.js +30 -0
- package/dist/tui/index.d.ts +14 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +18 -0
- package/dist/tui/terminal.d.ts +37 -0
- package/dist/tui/terminal.d.ts.map +1 -0
- package/dist/tui/terminal.js +104 -0
- package/dist/tui/tui.d.ts +67 -0
- package/dist/tui/tui.d.ts.map +1 -0
- package/dist/tui/tui.js +184 -0
- package/dist/tui/utils.d.ts +19 -0
- package/dist/tui/utils.d.ts.map +1 -0
- package/dist/tui/utils.js +31 -0
- package/dist/utils/filesystem.d.ts +23 -0
- package/dist/utils/filesystem.d.ts.map +1 -0
- package/dist/utils/filesystem.js +140 -0
- package/dist/utils/filetype-detection.d.ts +3 -0
- package/dist/utils/filetype-detection.d.ts.map +1 -0
- package/dist/utils/filetype-detection.js +112 -0
- package/dist/utils/generators.d.ts +3 -0
- package/dist/utils/generators.d.ts.map +1 -0
- package/dist/utils/generators.js +25 -0
- package/dist/utils/glob.d.ts +52 -0
- package/dist/utils/glob.d.ts.map +1 -0
- package/dist/utils/glob.js +376 -0
- package/dist/utils/ignore.d.ts +104 -0
- package/dist/utils/ignore.d.ts.map +1 -0
- package/dist/utils/ignore.js +649 -0
- package/dist/utils/iterables.d.ts +2 -0
- package/dist/utils/iterables.d.ts.map +1 -0
- package/dist/utils/iterables.js +6 -0
- package/dist/utils/process.d.ts +3 -2
- package/dist/utils/process.d.ts.map +1 -0
- package/dist/utils/process.js +17 -2
- package/dist/utils/zod-utils.d.ts +4 -0
- package/dist/utils/zod-utils.d.ts.map +1 -0
- package/dist/utils/zod-utils.js +7 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +34 -32
- package/dist/conversation-analyzer.d.ts +0 -10
- package/dist/conversation-analyzer.js +0 -88
- package/dist/repl-prompt.d.ts +0 -14
- package/dist/tools/command-validation.d.ts +0 -11
- package/dist/tools/command-validation.js +0 -45
- /package/dist/{token-tracker.js → tokens/tracker.js} +0 -0
package/dist/tools/web-search.js
CHANGED
|
@@ -1,68 +1,121 @@
|
|
|
1
|
-
import { tool } from "ai";
|
|
2
|
-
import chalk from "chalk";
|
|
3
1
|
import { SafeSearchType, search } from "duck-duck-scrape";
|
|
4
|
-
import Exa from "exa-js";
|
|
5
2
|
import { z } from "zod";
|
|
3
|
+
import Exa from "../api/exa/index.js";
|
|
4
|
+
import style from "../terminal/style.js";
|
|
5
|
+
import { manageTokenLimit } from "../tokens/threshold.js";
|
|
6
6
|
export const WebSearchTool = {
|
|
7
7
|
name: "webSearch",
|
|
8
8
|
};
|
|
9
|
-
|
|
9
|
+
const inputSchema = z.object({
|
|
10
|
+
query: z.string().describe("The search query."),
|
|
11
|
+
});
|
|
12
|
+
export const createWebSearchTool = ({ tokenCounter, }) => {
|
|
13
|
+
const toolDef = {
|
|
14
|
+
description: "Searches the web and returns match documents with their title, url, and text content. The query should be formulated as a natural language question.",
|
|
15
|
+
inputSchema,
|
|
16
|
+
};
|
|
17
|
+
const execute = async function* ({ query }, { toolCallId, abortSignal }) {
|
|
18
|
+
try {
|
|
19
|
+
// Check if execution has been aborted
|
|
20
|
+
if (abortSignal?.aborted) {
|
|
21
|
+
throw new Error("Web search aborted");
|
|
22
|
+
}
|
|
23
|
+
yield {
|
|
24
|
+
name: WebSearchTool.name,
|
|
25
|
+
event: "tool-init",
|
|
26
|
+
id: toolCallId,
|
|
27
|
+
data: `${style.cyan(query)}`,
|
|
28
|
+
};
|
|
29
|
+
if (abortSignal?.aborted) {
|
|
30
|
+
throw new Error("Web search aborted before search execution");
|
|
31
|
+
}
|
|
32
|
+
const result = await performSearch(query, abortSignal);
|
|
33
|
+
const sources = result.results.map((source) => `## ${source.title}\nURL: ${source.url}\n\n${source.text}`);
|
|
34
|
+
const resultText = `# Search Results:\n\n${sources.join("\n\n")}`;
|
|
35
|
+
const searchResult = await manageTokenLimit(resultText, tokenCounter, "WebSearch", "Use more specific search queries or reduce number of results");
|
|
36
|
+
yield {
|
|
37
|
+
name: WebSearchTool.name,
|
|
38
|
+
event: "tool-completion",
|
|
39
|
+
id: toolCallId,
|
|
40
|
+
data: `Found ${result.results.length} results (${searchResult.tokenCount} tokens)`,
|
|
41
|
+
};
|
|
42
|
+
yield searchResult.content;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
46
|
+
yield {
|
|
47
|
+
name: WebSearchTool.name,
|
|
48
|
+
event: "tool-error",
|
|
49
|
+
id: toolCallId,
|
|
50
|
+
data: errorMessage,
|
|
51
|
+
};
|
|
52
|
+
yield errorMessage;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
10
55
|
return {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
query: z.string().describe("The search query."),
|
|
15
|
-
}),
|
|
16
|
-
execute: async ({ query }, { toolCallId }) => {
|
|
17
|
-
sendData?.({
|
|
18
|
-
id: toolCallId,
|
|
19
|
-
event: "tool-init",
|
|
20
|
-
data: `Web search: ${chalk.cyan(query)}`,
|
|
21
|
-
});
|
|
22
|
-
const result = await performSearch(query);
|
|
23
|
-
const sources = result.results.map((source) => `## ${source.title}\nURL: ${source.url}\n\n${source.text}`);
|
|
24
|
-
const resultText = `# Search Results:\n\n${sources.join("\n\n")}`;
|
|
25
|
-
const tokenCount = tokenCounter.count(resultText);
|
|
26
|
-
sendData?.({
|
|
27
|
-
id: toolCallId,
|
|
28
|
-
event: "tool-completion",
|
|
29
|
-
data: `Found ${result.results.length} results. (${tokenCount} tokens)`,
|
|
30
|
-
});
|
|
31
|
-
return resultText;
|
|
32
|
-
},
|
|
33
|
-
}),
|
|
56
|
+
toolDef,
|
|
57
|
+
execute,
|
|
58
|
+
// No ask method needed for read-only tool
|
|
34
59
|
};
|
|
35
60
|
};
|
|
36
|
-
async function performSearch(query) {
|
|
61
|
+
async function performSearch(query, abortSignal) {
|
|
37
62
|
// Check if EXA API key is available
|
|
38
63
|
const hasExaApiKey = process.env["EXA_API_KEY"] && process.env["EXA_API_KEY"].trim() !== "";
|
|
39
64
|
if (hasExaApiKey) {
|
|
40
65
|
// Use Exa search
|
|
41
66
|
try {
|
|
67
|
+
if (abortSignal?.aborted) {
|
|
68
|
+
throw new Error("Web search aborted before Exa search");
|
|
69
|
+
}
|
|
42
70
|
const exa = new Exa(process.env["EXA_API_KEY"]);
|
|
43
|
-
|
|
71
|
+
// Create a promise that races with the abort signal
|
|
72
|
+
const searchPromise = exa.searchAndContents(query, {
|
|
44
73
|
numResults: 5,
|
|
45
74
|
text: true,
|
|
46
75
|
});
|
|
76
|
+
const result = await Promise.race([
|
|
77
|
+
searchPromise,
|
|
78
|
+
new Promise((_, reject) => {
|
|
79
|
+
if (abortSignal) {
|
|
80
|
+
abortSignal.addEventListener("abort", () => {
|
|
81
|
+
reject(new Error("Web search aborted during Exa search"));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}),
|
|
85
|
+
]);
|
|
47
86
|
return result;
|
|
48
87
|
}
|
|
49
88
|
catch (error) {
|
|
50
89
|
// If Exa fails, fall back to duck duck scrape
|
|
51
90
|
console.info("Exa search failed, falling back to DuckDuckGo:", error);
|
|
52
|
-
return await searchWithDuckDuckGo(query);
|
|
91
|
+
return await searchWithDuckDuckGo(query, abortSignal);
|
|
53
92
|
}
|
|
54
93
|
}
|
|
55
94
|
else {
|
|
56
95
|
// Use DuckDuckGo search as fallback
|
|
57
96
|
console.info("EXA_API_KEY not set, using DuckDuckGo search");
|
|
58
|
-
return await searchWithDuckDuckGo(query);
|
|
97
|
+
return await searchWithDuckDuckGo(query, abortSignal);
|
|
59
98
|
}
|
|
60
99
|
}
|
|
61
|
-
async function searchWithDuckDuckGo(query) {
|
|
100
|
+
async function searchWithDuckDuckGo(query, abortSignal) {
|
|
62
101
|
try {
|
|
63
|
-
|
|
102
|
+
if (abortSignal?.aborted) {
|
|
103
|
+
throw new Error("Web search aborted before DuckDuckGo search");
|
|
104
|
+
}
|
|
105
|
+
// Create a promise that races with the abort signal
|
|
106
|
+
const searchPromise = search(query, {
|
|
64
107
|
safeSearch: SafeSearchType.MODERATE,
|
|
65
108
|
});
|
|
109
|
+
const searchResults = await Promise.race([
|
|
110
|
+
searchPromise,
|
|
111
|
+
new Promise((_, reject) => {
|
|
112
|
+
if (abortSignal) {
|
|
113
|
+
abortSignal.addEventListener("abort", () => {
|
|
114
|
+
reject(new Error("Web search aborted during DuckDuckGo search"));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
|
+
]);
|
|
66
119
|
// Transform duck-duck-scrape results to match Exa format
|
|
67
120
|
// Take only first 5 results to match Exa behavior
|
|
68
121
|
const results = searchResults.results
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface AutocompleteItem {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SlashCommand {
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
|
|
10
|
+
}
|
|
11
|
+
export interface AutocompleteProvider {
|
|
12
|
+
getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
|
|
13
|
+
items: AutocompleteItem[];
|
|
14
|
+
prefix: string;
|
|
15
|
+
} | null>;
|
|
16
|
+
applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
|
|
17
|
+
lines: string[];
|
|
18
|
+
cursorLine: number;
|
|
19
|
+
cursorCol: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
23
|
+
private commands;
|
|
24
|
+
private allowedDirs;
|
|
25
|
+
constructor(commands?: (SlashCommand | AutocompleteItem)[], allowedDirs?: string[]);
|
|
26
|
+
getSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
|
|
27
|
+
items: AutocompleteItem[];
|
|
28
|
+
prefix: string;
|
|
29
|
+
} | null>;
|
|
30
|
+
applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
|
|
31
|
+
lines: string[];
|
|
32
|
+
cursorLine: number;
|
|
33
|
+
cursorCol: number;
|
|
34
|
+
};
|
|
35
|
+
private extractPathPrefix;
|
|
36
|
+
private isPathWithinAllowedDirs;
|
|
37
|
+
private getFileSuggestions;
|
|
38
|
+
getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number): Promise<{
|
|
39
|
+
items: AutocompleteItem[];
|
|
40
|
+
prefix: string;
|
|
41
|
+
} | null>;
|
|
42
|
+
shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=autocomplete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../../source/tui/autocomplete.ts"],"names":[],"mappings":"AAyHA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,sBAAsB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAAC;CAC5E;AAED,MAAM,WAAW,oBAAoB;IAGnC,cAAc,CACZ,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QACT,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,IAAI,CAAC,CAAC;IAIV,eAAe,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACb;QACD,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACvE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,WAAW,CAAW;gBAG5B,QAAQ,GAAE,CAAC,YAAY,GAAG,gBAAgB,CAAC,EAAO,EAClD,WAAW,GAAE,MAAM,EAAoB;IAMnC,cAAc,CAClB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAyEhE,eAAe,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACb;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IA6D7D,OAAO,CAAC,iBAAiB;YA8EX,uBAAuB;YAyBvB,kBAAkB;IAgJ1B,uBAAuB,CAC3B,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAyBhE,2BAA2B,CACzB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO;CAWX"}
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { readdir, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, } from "node:path";
|
|
3
|
+
// Cache for directory listings to improve performance
|
|
4
|
+
class DirectoryCache {
|
|
5
|
+
cache = new Map();
|
|
6
|
+
ttl = 3000; // 3 seconds
|
|
7
|
+
async get(dir) {
|
|
8
|
+
const cached = this.cache.get(dir);
|
|
9
|
+
if (cached && Date.now() - cached.timestamp < this.ttl) {
|
|
10
|
+
return cached.entries;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
set(dir, entries) {
|
|
15
|
+
this.cache.set(dir, { entries, timestamp: Date.now() });
|
|
16
|
+
}
|
|
17
|
+
clear() {
|
|
18
|
+
this.cache.clear();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const directoryCache = new DirectoryCache();
|
|
22
|
+
// Helper function to get directory entries with caching and timeout
|
|
23
|
+
async function getDirectoryEntries(dir) {
|
|
24
|
+
const cached = await directoryCache.get(dir);
|
|
25
|
+
if (cached) {
|
|
26
|
+
return cached;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
// Add timeout to prevent hanging on slow file systems
|
|
30
|
+
const entries = await Promise.race([
|
|
31
|
+
readdir(dir, { withFileTypes: true }),
|
|
32
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Directory read timeout")), 2000)),
|
|
33
|
+
]);
|
|
34
|
+
directoryCache.set(dir, entries);
|
|
35
|
+
return entries;
|
|
36
|
+
}
|
|
37
|
+
catch (_e) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isAttachableFile(filePath) {
|
|
42
|
+
// Check file extension for common text files that might be misidentified
|
|
43
|
+
const textExtensions = [
|
|
44
|
+
".txt",
|
|
45
|
+
".md",
|
|
46
|
+
".markdown",
|
|
47
|
+
".js",
|
|
48
|
+
".ts",
|
|
49
|
+
".tsx",
|
|
50
|
+
".jsx",
|
|
51
|
+
".py",
|
|
52
|
+
".java",
|
|
53
|
+
".c",
|
|
54
|
+
".cpp",
|
|
55
|
+
".h",
|
|
56
|
+
".hpp",
|
|
57
|
+
".cs",
|
|
58
|
+
".php",
|
|
59
|
+
".rb",
|
|
60
|
+
".go",
|
|
61
|
+
".rs",
|
|
62
|
+
".swift",
|
|
63
|
+
".kt",
|
|
64
|
+
".scala",
|
|
65
|
+
".sh",
|
|
66
|
+
".bash",
|
|
67
|
+
".zsh",
|
|
68
|
+
".fish",
|
|
69
|
+
".html",
|
|
70
|
+
".htm",
|
|
71
|
+
".css",
|
|
72
|
+
".scss",
|
|
73
|
+
".sass",
|
|
74
|
+
".less",
|
|
75
|
+
".xml",
|
|
76
|
+
".json",
|
|
77
|
+
".yaml",
|
|
78
|
+
".yml",
|
|
79
|
+
".toml",
|
|
80
|
+
".ini",
|
|
81
|
+
".cfg",
|
|
82
|
+
".conf",
|
|
83
|
+
".log",
|
|
84
|
+
".sql",
|
|
85
|
+
".r",
|
|
86
|
+
".R",
|
|
87
|
+
".m",
|
|
88
|
+
".pl",
|
|
89
|
+
".lua",
|
|
90
|
+
".vim",
|
|
91
|
+
".dockerfile",
|
|
92
|
+
".makefile",
|
|
93
|
+
".cmake",
|
|
94
|
+
".gradle",
|
|
95
|
+
".maven",
|
|
96
|
+
".properties",
|
|
97
|
+
".env",
|
|
98
|
+
];
|
|
99
|
+
const ext = extname(filePath).toLowerCase();
|
|
100
|
+
return textExtensions.includes(ext);
|
|
101
|
+
}
|
|
102
|
+
// Combined provider that handles both slash commands and file paths
|
|
103
|
+
export class CombinedAutocompleteProvider {
|
|
104
|
+
commands;
|
|
105
|
+
allowedDirs;
|
|
106
|
+
constructor(commands = [], allowedDirs = [process.cwd()]) {
|
|
107
|
+
this.commands = commands;
|
|
108
|
+
this.allowedDirs = allowedDirs;
|
|
109
|
+
}
|
|
110
|
+
async getSuggestions(lines, cursorLine, cursorCol) {
|
|
111
|
+
const currentLine = lines[cursorLine] || "";
|
|
112
|
+
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
113
|
+
// Check for slash commands
|
|
114
|
+
if (textBeforeCursor.startsWith("/")) {
|
|
115
|
+
const spaceIndex = textBeforeCursor.indexOf(" ");
|
|
116
|
+
if (spaceIndex === -1) {
|
|
117
|
+
// No space yet - complete command names
|
|
118
|
+
const prefix = textBeforeCursor.slice(1); // Remove the "/"
|
|
119
|
+
const filtered = this.commands
|
|
120
|
+
.filter((cmd) => {
|
|
121
|
+
const name = "name" in cmd ? cmd.name : cmd.value; // Check if SlashCommand or AutocompleteItem
|
|
122
|
+
return name?.toLowerCase().startsWith(prefix.toLowerCase());
|
|
123
|
+
})
|
|
124
|
+
.map((cmd) => ({
|
|
125
|
+
value: "name" in cmd ? cmd.name : cmd.value,
|
|
126
|
+
label: "name" in cmd ? cmd.name : cmd.label,
|
|
127
|
+
...(cmd.description && { description: cmd.description }),
|
|
128
|
+
}));
|
|
129
|
+
if (filtered.length === 0)
|
|
130
|
+
return null;
|
|
131
|
+
return {
|
|
132
|
+
items: filtered,
|
|
133
|
+
prefix: prefix, // Return the actual prefix used for filtering (without "/")
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Space found - complete command arguments
|
|
137
|
+
const commandName = textBeforeCursor.slice(1, spaceIndex); // Command without "/"
|
|
138
|
+
const argumentText = textBeforeCursor.slice(spaceIndex + 1); // Text after space
|
|
139
|
+
const command = this.commands.find((cmd) => {
|
|
140
|
+
const name = "name" in cmd ? cmd.name : cmd.value;
|
|
141
|
+
return name === commandName;
|
|
142
|
+
});
|
|
143
|
+
if (!command ||
|
|
144
|
+
!("getArgumentCompletions" in command) ||
|
|
145
|
+
!command.getArgumentCompletions) {
|
|
146
|
+
return null; // No argument completion for this command
|
|
147
|
+
}
|
|
148
|
+
const argumentSuggestions = command.getArgumentCompletions?.(argumentText);
|
|
149
|
+
if (!argumentSuggestions || argumentSuggestions.length === 0) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
items: argumentSuggestions,
|
|
154
|
+
prefix: argumentText,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Check for file paths - triggered by Tab or if we detect a path pattern
|
|
158
|
+
const pathMatch = this.extractPathPrefix(textBeforeCursor, false);
|
|
159
|
+
if (pathMatch !== null) {
|
|
160
|
+
const suggestions = await this.getFileSuggestions(pathMatch);
|
|
161
|
+
if (suggestions.length === 0)
|
|
162
|
+
return null;
|
|
163
|
+
return {
|
|
164
|
+
items: suggestions,
|
|
165
|
+
prefix: pathMatch,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
|
|
171
|
+
const currentLine = lines[cursorLine] || "";
|
|
172
|
+
const beforePrefix = currentLine.slice(0, cursorCol - prefix.length);
|
|
173
|
+
const afterCursor = currentLine.slice(cursorCol);
|
|
174
|
+
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
175
|
+
// Check if we're completing a slash command (prefix doesn't start with "/" but we're in slash command context)
|
|
176
|
+
if (textBeforeCursor.startsWith("/") && !textBeforeCursor.includes(" ")) {
|
|
177
|
+
// This is a command name completion
|
|
178
|
+
const newLine = `${beforePrefix}${item.value} ${afterCursor}`;
|
|
179
|
+
const newLines = [...lines];
|
|
180
|
+
newLines[cursorLine] = newLine;
|
|
181
|
+
return {
|
|
182
|
+
lines: newLines,
|
|
183
|
+
cursorLine,
|
|
184
|
+
cursorCol: beforePrefix.length + item.value.length + 1, // +1 for space
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
// Check if we're completing a file attachment (prefix starts with "@")
|
|
188
|
+
if (prefix.startsWith("@")) {
|
|
189
|
+
// This is a file attachment completion
|
|
190
|
+
const newLine = `${beforePrefix + item.value} ${afterCursor}`;
|
|
191
|
+
const newLines = [...lines];
|
|
192
|
+
newLines[cursorLine] = newLine;
|
|
193
|
+
return {
|
|
194
|
+
lines: newLines,
|
|
195
|
+
cursorLine,
|
|
196
|
+
cursorCol: beforePrefix.length + item.value.length + 1, // +1 for space
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// Check if we're in a slash command context (beforePrefix contains "/command ")
|
|
200
|
+
if (textBeforeCursor.includes("/") && textBeforeCursor.includes(" ")) {
|
|
201
|
+
// This is likely a command argument completion
|
|
202
|
+
const newLine = beforePrefix + item.value + afterCursor;
|
|
203
|
+
const newLines = [...lines];
|
|
204
|
+
newLines[cursorLine] = newLine;
|
|
205
|
+
return {
|
|
206
|
+
lines: newLines,
|
|
207
|
+
cursorLine,
|
|
208
|
+
cursorCol: beforePrefix.length + item.value.length,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
// For file paths, complete the path
|
|
212
|
+
const newLine = beforePrefix + item.value + afterCursor;
|
|
213
|
+
const newLines = [...lines];
|
|
214
|
+
newLines[cursorLine] = newLine;
|
|
215
|
+
return {
|
|
216
|
+
lines: newLines,
|
|
217
|
+
cursorLine,
|
|
218
|
+
cursorCol: beforePrefix.length + item.value.length,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// Extract a path-like prefix from the text before cursor
|
|
222
|
+
extractPathPrefix(text, forceExtract = false) {
|
|
223
|
+
// Check for @ file attachment syntax first
|
|
224
|
+
const atMatch = text.match(/@([^\s]*)$/);
|
|
225
|
+
if (atMatch) {
|
|
226
|
+
// For forced extraction, always return the @ prefix
|
|
227
|
+
if (forceExtract) {
|
|
228
|
+
return atMatch[0];
|
|
229
|
+
}
|
|
230
|
+
// For natural triggers, always return @ prefixes (they're always file-related)
|
|
231
|
+
return atMatch[0];
|
|
232
|
+
}
|
|
233
|
+
// Match paths - more conservative approach to avoid matching already completed paths
|
|
234
|
+
// This regex captures:
|
|
235
|
+
// - Paths starting from beginning of line or after space
|
|
236
|
+
// - Optional ./ or ../ or ~/ prefix
|
|
237
|
+
// - The path itself (must contain at least one / or start with ./ or ../ or ~/)
|
|
238
|
+
const matches = text.match(/(?:^|\s)((?:\.{1,2}\/|~\/)?(?:[^\s]*\/)*[^\s/]*)$/);
|
|
239
|
+
if (!matches) {
|
|
240
|
+
// If forced extraction and no matches, return empty string to trigger from current dir
|
|
241
|
+
return forceExtract ? "" : null;
|
|
242
|
+
}
|
|
243
|
+
const pathPrefix = matches[1] || "";
|
|
244
|
+
// For forced extraction (Tab key), always return something
|
|
245
|
+
if (forceExtract) {
|
|
246
|
+
// If we're not in a clear path context and we're at the end of a word,
|
|
247
|
+
// return empty string to complete from current directory
|
|
248
|
+
if (!pathPrefix.includes("/") &&
|
|
249
|
+
!pathPrefix.endsWith("/") &&
|
|
250
|
+
!pathPrefix.startsWith(".") &&
|
|
251
|
+
!pathPrefix.startsWith("~/")) {
|
|
252
|
+
// Only return empty string if we're at the beginning or after space
|
|
253
|
+
// This prevents completing "source" as empty string
|
|
254
|
+
if (text === "" || text.endsWith(" ")) {
|
|
255
|
+
return "";
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return pathPrefix;
|
|
259
|
+
}
|
|
260
|
+
// For natural triggers, be more conservative:
|
|
261
|
+
// Only trigger if we have a clear path indicator
|
|
262
|
+
const hasPathIndicator = pathPrefix.includes("/") ||
|
|
263
|
+
pathPrefix.endsWith("/") ||
|
|
264
|
+
pathPrefix.startsWith(".") ||
|
|
265
|
+
pathPrefix.startsWith("~/");
|
|
266
|
+
if (!hasPathIndicator) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
// Additional check: don't trigger if the path looks like it's already completed
|
|
270
|
+
// (i.e., doesn't end with a partial filename)
|
|
271
|
+
// Only apply this check for paths that don't have clear path indicators
|
|
272
|
+
// and look like single directory names (no path separators)
|
|
273
|
+
if (!pathPrefix.includes("/") &&
|
|
274
|
+
!pathPrefix.includes(".") &&
|
|
275
|
+
!pathPrefix.startsWith("./") &&
|
|
276
|
+
!pathPrefix.startsWith("../") &&
|
|
277
|
+
!pathPrefix.startsWith("~/") &&
|
|
278
|
+
pathPrefix.length > 3) {
|
|
279
|
+
// This might be a completed directory name, not a partial path
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
return pathPrefix;
|
|
283
|
+
}
|
|
284
|
+
// Check if a path is within any allowed directory
|
|
285
|
+
async isPathWithinAllowedDirs(requestedPath) {
|
|
286
|
+
for (const allowedDir of this.allowedDirs) {
|
|
287
|
+
// Resolve both paths to handle relative paths and symlinks
|
|
288
|
+
const absRequested = resolve(requestedPath);
|
|
289
|
+
const absAllowed = resolve(allowedDir);
|
|
290
|
+
let target = absRequested;
|
|
291
|
+
try {
|
|
292
|
+
// Try to resolve symlinks for the target path
|
|
293
|
+
target = await realpath(absRequested);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// If target doesn't exist, use the intended path
|
|
297
|
+
}
|
|
298
|
+
const rel = relative(absAllowed, target);
|
|
299
|
+
if (rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
// Get file/directory suggestions for a given path prefix
|
|
306
|
+
async getFileSuggestions(prefix) {
|
|
307
|
+
try {
|
|
308
|
+
let searchDir;
|
|
309
|
+
let searchPrefix;
|
|
310
|
+
let expandedPrefix = prefix;
|
|
311
|
+
let isAtPrefix = false;
|
|
312
|
+
// Handle @ file attachment prefix
|
|
313
|
+
if (prefix.startsWith("@")) {
|
|
314
|
+
isAtPrefix = true;
|
|
315
|
+
expandedPrefix = prefix.slice(1); // Remove the @
|
|
316
|
+
}
|
|
317
|
+
// Remove home directory expansion - not allowed
|
|
318
|
+
if (expandedPrefix.startsWith("~")) {
|
|
319
|
+
// Home directory access not allowed - return empty suggestions
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
// Check if we're trying to access root - not allowed
|
|
323
|
+
if (expandedPrefix.startsWith("/")) {
|
|
324
|
+
// Root access not allowed - return empty suggestions
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
if (expandedPrefix === "" ||
|
|
328
|
+
expandedPrefix === "./" ||
|
|
329
|
+
expandedPrefix === "../" ||
|
|
330
|
+
prefix === "@") {
|
|
331
|
+
// Start from primary directory
|
|
332
|
+
searchDir = this.allowedDirs[0];
|
|
333
|
+
searchPrefix = "";
|
|
334
|
+
}
|
|
335
|
+
else if (expandedPrefix.endsWith("/")) {
|
|
336
|
+
// If prefix ends with /, show contents of that directory
|
|
337
|
+
// Try to find which allowed directory this path belongs to
|
|
338
|
+
const resolvedPath = join(this.allowedDirs[0], expandedPrefix);
|
|
339
|
+
const isWithinAllowed = await this.isPathWithinAllowedDirs(resolvedPath);
|
|
340
|
+
if (!isWithinAllowed) {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
searchDir = resolvedPath;
|
|
344
|
+
searchPrefix = "";
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
// Split into directory and file prefix
|
|
348
|
+
const dir = dirname(expandedPrefix);
|
|
349
|
+
const file = basename(expandedPrefix);
|
|
350
|
+
const resolvedPath = join(this.allowedDirs[0], dir);
|
|
351
|
+
// Check if the resolved path is within allowed directories
|
|
352
|
+
const isWithinAllowed = await this.isPathWithinAllowedDirs(resolvedPath);
|
|
353
|
+
if (!isWithinAllowed) {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
searchDir = resolvedPath;
|
|
357
|
+
searchPrefix = file;
|
|
358
|
+
}
|
|
359
|
+
const entries = await getDirectoryEntries(searchDir);
|
|
360
|
+
const suggestions = [];
|
|
361
|
+
for (const entry of entries) {
|
|
362
|
+
const entryName = entry.name;
|
|
363
|
+
if (!entryName.toLowerCase().startsWith(searchPrefix.toLowerCase())) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
const fullPath = join(searchDir, entryName);
|
|
367
|
+
let isDirectory = false;
|
|
368
|
+
try {
|
|
369
|
+
// Add timeout to prevent hanging on slow file systems
|
|
370
|
+
const stats = await Promise.race([
|
|
371
|
+
stat(fullPath),
|
|
372
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("File stat timeout")), 1000)),
|
|
373
|
+
]);
|
|
374
|
+
isDirectory = stats.isDirectory();
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
// If stat fails or times out, skip this entry
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
// For @ prefix, filter to only show directories and attachable files
|
|
381
|
+
if (isAtPrefix && !isDirectory && !isAttachableFile(fullPath)) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
// Check if the resulting path is within allowed directories
|
|
385
|
+
if (!(await this.isPathWithinAllowedDirs(fullPath))) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
let relativePath;
|
|
389
|
+
// Handle @ prefix path construction
|
|
390
|
+
if (isAtPrefix) {
|
|
391
|
+
const pathWithoutAt = expandedPrefix;
|
|
392
|
+
if (pathWithoutAt.endsWith("/")) {
|
|
393
|
+
relativePath = `@${pathWithoutAt}${entryName}`;
|
|
394
|
+
}
|
|
395
|
+
else if (pathWithoutAt.includes("/")) {
|
|
396
|
+
relativePath = `@${join(dirname(pathWithoutAt), entryName)}`;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
relativePath = `@${entryName}`;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else if (prefix.endsWith("/")) {
|
|
403
|
+
// If prefix ends with /, append entry to the prefix
|
|
404
|
+
relativePath = prefix + entryName;
|
|
405
|
+
}
|
|
406
|
+
else if (prefix.includes("/")) {
|
|
407
|
+
relativePath = join(dirname(prefix), entryName);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
relativePath = entryName;
|
|
411
|
+
}
|
|
412
|
+
suggestions.push({
|
|
413
|
+
value: isDirectory ? `${relativePath}/` : relativePath,
|
|
414
|
+
label: entryName,
|
|
415
|
+
description: isDirectory ? "directory" : "file",
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
// Sort directories first, then alphabetically
|
|
419
|
+
suggestions.sort((a, b) => {
|
|
420
|
+
const aIsDir = a.description === "directory";
|
|
421
|
+
const bIsDir = b.description === "directory";
|
|
422
|
+
if (aIsDir && !bIsDir)
|
|
423
|
+
return -1;
|
|
424
|
+
if (!aIsDir && bIsDir)
|
|
425
|
+
return 1;
|
|
426
|
+
return a.label.localeCompare(b.label);
|
|
427
|
+
});
|
|
428
|
+
return suggestions;
|
|
429
|
+
}
|
|
430
|
+
catch (_e) {
|
|
431
|
+
// Directory doesn't exist or not accessible
|
|
432
|
+
return [];
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Force file completion (called on Tab key) - always returns suggestions
|
|
436
|
+
async getForceFileSuggestions(lines, cursorLine, cursorCol) {
|
|
437
|
+
const currentLine = lines[cursorLine] || "";
|
|
438
|
+
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
439
|
+
// Don't trigger if we're in a slash command
|
|
440
|
+
if (textBeforeCursor.startsWith("/") && !textBeforeCursor.includes(" ")) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
// Force extract path prefix - this will always return something
|
|
444
|
+
const pathMatch = this.extractPathPrefix(textBeforeCursor, true);
|
|
445
|
+
if (pathMatch !== null) {
|
|
446
|
+
const suggestions = await this.getFileSuggestions(pathMatch);
|
|
447
|
+
if (suggestions.length === 0)
|
|
448
|
+
return null;
|
|
449
|
+
return {
|
|
450
|
+
items: suggestions,
|
|
451
|
+
prefix: pathMatch,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
// Check if we should trigger file completion (called on Tab key)
|
|
457
|
+
shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
|
|
458
|
+
const currentLine = lines[cursorLine] || "";
|
|
459
|
+
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
460
|
+
// Don't trigger if we're in a slash command
|
|
461
|
+
if (textBeforeCursor.startsWith("/") && !textBeforeCursor.includes(" ")) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
}
|