@travisennis/acai 0.0.5 → 0.0.7
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 +190 -19
- package/bin/acai-wrapper.js +26 -0
- package/dist/agent/index.d.ts +132 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +434 -0
- package/dist/api/exa/index.js +1 -1
- package/dist/cli.d.ts +4 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +67 -40
- 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 +54 -0
- package/dist/commands/application-log-command.d.ts +1 -1
- package/dist/commands/application-log-command.d.ts.map +1 -1
- package/dist/commands/application-log-command.js +18 -20
- package/dist/commands/clear-command.d.ts +1 -1
- package/dist/commands/clear-command.d.ts.map +1 -1
- package/dist/commands/clear-command.js +7 -3
- package/dist/commands/compact-command.d.ts.map +1 -1
- package/dist/commands/compact-command.js +9 -5
- 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 +124 -0
- package/dist/commands/copy-command.d.ts.map +1 -1
- package/dist/commands/copy-command.js +14 -5
- package/dist/commands/edit-command.d.ts +1 -1
- package/dist/commands/edit-command.d.ts.map +1 -1
- package/dist/commands/edit-command.js +21 -34
- package/dist/commands/edit-prompt-command.d.ts +1 -1
- package/dist/commands/edit-prompt-command.d.ts.map +1 -1
- package/dist/commands/edit-prompt-command.js +18 -15
- package/dist/commands/exit-command.d.ts +1 -4
- package/dist/commands/exit-command.d.ts.map +1 -1
- package/dist/commands/exit-command.js +9 -5
- package/dist/commands/files-command.d.ts +1 -1
- package/dist/commands/files-command.d.ts.map +1 -1
- package/dist/commands/files-command.js +20 -16
- package/dist/commands/generate-rules-command.d.ts +1 -1
- package/dist/commands/generate-rules-command.d.ts.map +1 -1
- package/dist/commands/generate-rules-command.js +307 -39
- 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 +191 -0
- package/dist/commands/health-command.d.ts +1 -1
- package/dist/commands/health-command.d.ts.map +1 -1
- package/dist/commands/health-command.js +49 -27
- package/dist/commands/help-command.d.ts +1 -1
- package/dist/commands/help-command.d.ts.map +1 -1
- package/dist/commands/help-command.js +25 -5
- 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 +458 -0
- package/dist/commands/init-command.d.ts +1 -1
- package/dist/commands/init-command.d.ts.map +1 -1
- package/dist/commands/init-command.js +40 -22
- package/dist/commands/last-log-command.d.ts +1 -1
- package/dist/commands/last-log-command.d.ts.map +1 -1
- package/dist/commands/last-log-command.js +15 -15
- 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 +35 -0
- package/dist/commands/list-tools-command.d.ts.map +1 -1
- package/dist/commands/list-tools-command.js +61 -21
- package/dist/commands/manager.d.ts +9 -4
- package/dist/commands/manager.d.ts.map +1 -1
- package/dist/commands/manager.js +64 -39
- package/dist/commands/model-command.d.ts.map +1 -1
- package/dist/commands/model-command.js +201 -66
- package/dist/commands/paste-command.d.ts +1 -1
- package/dist/commands/paste-command.d.ts.map +1 -1
- package/dist/commands/paste-command.js +23 -9
- 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 +109 -0
- package/dist/commands/prompt-command.d.ts +19 -1
- package/dist/commands/prompt-command.d.ts.map +1 -1
- package/dist/commands/prompt-command.js +191 -98
- 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 +55 -0
- package/dist/commands/reset-command.d.ts +1 -1
- package/dist/commands/reset-command.d.ts.map +1 -1
- package/dist/commands/reset-command.js +8 -5
- package/dist/commands/rules-command.d.ts +1 -1
- package/dist/commands/rules-command.d.ts.map +1 -1
- package/dist/commands/rules-command.js +25 -22
- package/dist/commands/save-command.d.ts +1 -1
- package/dist/commands/save-command.d.ts.map +1 -1
- package/dist/commands/save-command.js +8 -3
- package/dist/commands/shell-command.d.ts.map +1 -1
- package/dist/commands/shell-command.js +45 -24
- package/dist/commands/types.d.ts +9 -7
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/usage-command.d.ts +1 -1
- package/dist/commands/usage-command.d.ts.map +1 -1
- package/dist/commands/usage-command.js +18 -7
- package/dist/config.d.ts +21 -11
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +90 -63
- package/dist/execution/index.d.ts +17 -2
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +62 -20
- package/dist/formatting.d.ts +127 -0
- package/dist/formatting.d.ts.map +1 -1
- package/dist/formatting.js +201 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +263 -102
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +47 -18
- package/dist/mentions.d.ts +2 -1
- package/dist/mentions.d.ts.map +1 -1
- package/dist/mentions.js +16 -1
- package/dist/messages.d.ts +11 -0
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +122 -21
- 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 +1 -0
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +1 -0
- package/dist/models/ai-config.d.ts +4 -2
- package/dist/models/ai-config.d.ts.map +1 -1
- package/dist/models/ai-config.js +12 -2
- package/dist/models/anthropic-provider.d.ts.map +1 -1
- package/dist/models/anthropic-provider.js +3 -67
- package/dist/models/deepseek-provider.d.ts.map +1 -1
- package/dist/models/deepseek-provider.js +0 -2
- package/dist/models/google-provider.d.ts.map +1 -1
- package/dist/models/google-provider.js +0 -3
- package/dist/models/groq-provider.d.ts.map +1 -1
- package/dist/models/groq-provider.js +0 -1
- package/dist/models/manager.d.ts +2 -1
- package/dist/models/manager.d.ts.map +1 -1
- package/dist/models/manager.js +26 -2
- package/dist/models/openai-provider.d.ts.map +1 -1
- package/dist/models/openai-provider.js +0 -4
- package/dist/models/openrouter-provider.d.ts +16 -22
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +175 -236
- package/dist/models/providers.d.ts +4 -14
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +1 -57
- package/dist/models/xai-provider.d.ts.map +1 -1
- package/dist/models/xai-provider.js +0 -2
- package/dist/prompts.d.ts +10 -4
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +447 -70
- package/dist/repl/project-status-line.d.ts +3 -0
- package/dist/repl/project-status-line.d.ts.map +1 -0
- package/dist/repl/project-status-line.js +61 -0
- package/dist/repl/tool-call-repair.d.ts.map +1 -1
- package/dist/repl/tool-call-repair.js +8 -4
- package/dist/repl-new.d.ts +51 -0
- package/dist/repl-new.d.ts.map +1 -0
- package/dist/repl-new.js +354 -0
- package/dist/skills.d.ts +20 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +192 -0
- package/dist/terminal/control.d.ts +55 -0
- package/dist/terminal/control.d.ts.map +1 -0
- package/dist/terminal/control.js +109 -0
- package/dist/terminal/default-theme.d.ts +1 -1
- package/dist/terminal/default-theme.d.ts.map +1 -1
- package/dist/terminal/default-theme.js +24 -28
- package/dist/terminal/formatting.d.ts +23 -25
- package/dist/terminal/formatting.d.ts.map +1 -1
- package/dist/terminal/formatting.js +35 -52
- package/dist/terminal/highlight/index.d.ts.map +1 -1
- package/dist/terminal/highlight/index.js +3 -6
- package/dist/terminal/highlight/theme.d.ts.map +1 -1
- package/dist/terminal/highlight/theme.js +2 -6
- package/dist/terminal/index.d.ts +2 -94
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +2 -370
- package/dist/terminal/markdown.js +10 -5
- package/dist/terminal/select-prompt.d.ts +2 -2
- package/dist/terminal/select-prompt.d.ts.map +1 -1
- package/dist/terminal/select-prompt.js +47 -39
- package/dist/terminal/strip-ansi.js +4 -4
- package/dist/terminal/table/cell.d.ts +114 -0
- package/dist/terminal/table/cell.d.ts.map +1 -0
- package/dist/terminal/table/cell.js +407 -0
- package/dist/terminal/table/debug.d.ts +15 -0
- package/dist/terminal/table/debug.d.ts.map +1 -0
- package/dist/terminal/table/debug.js +32 -0
- package/dist/terminal/table/index.d.ts +3 -0
- package/dist/terminal/table/index.d.ts.map +1 -0
- package/dist/terminal/table/index.js +2 -0
- package/dist/terminal/table/layout-manager.d.ts +27 -0
- package/dist/terminal/table/layout-manager.d.ts.map +1 -0
- package/dist/terminal/table/layout-manager.js +257 -0
- package/dist/terminal/table/table.d.ts +9 -0
- package/dist/terminal/table/table.d.ts.map +1 -0
- package/dist/terminal/table/table.js +97 -0
- package/dist/terminal/table/utils.d.ts +63 -0
- package/dist/terminal/table/utils.d.ts.map +1 -0
- package/dist/terminal/table/utils.js +326 -0
- package/dist/tokens/threshold.d.ts +20 -0
- package/dist/tokens/threshold.d.ts.map +1 -0
- package/dist/tokens/threshold.js +67 -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 +285 -0
- package/dist/tools/agent.d.ts +16 -5
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/agent.js +86 -59
- package/dist/tools/bash.d.ts +23 -12
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +243 -128
- package/dist/tools/batch.d.ts +34 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +174 -0
- package/dist/tools/code-interpreter.d.ts +21 -9
- package/dist/tools/code-interpreter.d.ts.map +1 -1
- package/dist/tools/code-interpreter.js +151 -134
- package/dist/tools/delete-file.d.ts +17 -10
- package/dist/tools/delete-file.d.ts.map +1 -1
- package/dist/tools/delete-file.js +60 -97
- package/dist/tools/directory-tree.d.ts +17 -12
- package/dist/tools/directory-tree.d.ts.map +1 -1
- package/dist/tools/directory-tree.js +57 -48
- package/dist/tools/dynamic-tool-loader.d.ts +16 -10
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
- package/dist/tools/dynamic-tool-loader.js +122 -130
- package/dist/tools/dynamic-tool-parser.d.ts +1 -0
- package/dist/tools/dynamic-tool-parser.d.ts.map +1 -1
- package/dist/tools/dynamic-tool-parser.js +1 -0
- package/dist/tools/edit-file.d.ts +35 -15
- package/dist/tools/edit-file.d.ts.map +1 -1
- package/dist/tools/edit-file.js +127 -114
- package/dist/tools/glob.d.ts +36 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +154 -0
- package/dist/tools/grep.d.ts +73 -12
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +425 -165
- package/dist/tools/index.d.ts +220 -126
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +284 -135
- package/dist/tools/llm-edit-fixer.d.ts +24 -0
- package/dist/tools/llm-edit-fixer.d.ts.map +1 -0
- package/dist/tools/llm-edit-fixer.js +136 -0
- package/dist/tools/move-file.d.ts +19 -7
- package/dist/tools/move-file.d.ts.map +1 -1
- package/dist/tools/move-file.js +48 -34
- package/dist/tools/read-file.d.ts +47 -9
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +84 -70
- package/dist/tools/read-multiple-files.d.ts +17 -6
- package/dist/tools/read-multiple-files.d.ts.map +1 -1
- package/dist/tools/read-multiple-files.js +132 -72
- package/dist/tools/save-file.d.ts +45 -12
- package/dist/tools/save-file.d.ts.map +1 -1
- package/dist/tools/save-file.js +76 -101
- package/dist/tools/think.d.ts +15 -7
- package/dist/tools/think.d.ts.map +1 -1
- package/dist/tools/think.js +34 -20
- package/dist/tools/types.d.ts +8 -10
- package/dist/tools/types.d.ts.map +1 -1
- 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 +11 -4
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +39 -38
- package/dist/tools/web-search.d.ts +15 -6
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +64 -31
- 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/box.d.ts +20 -0
- package/dist/tui/components/box.d.ts.map +1 -0
- package/dist/tui/components/box.js +81 -0
- package/dist/tui/components/editor.d.ts +106 -0
- package/dist/tui/components/editor.d.ts.map +1 -0
- package/dist/tui/components/editor.js +1220 -0
- package/dist/tui/components/footer.d.ts +12 -0
- package/dist/tui/components/footer.d.ts.map +1 -0
- package/dist/tui/components/footer.js +209 -0
- package/dist/tui/components/header.d.ts +21 -0
- package/dist/tui/components/header.d.ts.map +1 -0
- package/dist/tui/components/header.js +63 -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 +23 -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 +106 -0
- package/dist/tui/components/markdown.d.ts.map +1 -0
- package/dist/tui/components/markdown.js +586 -0
- package/dist/tui/components/modal.d.ts +29 -0
- package/dist/tui/components/modal.d.ts.map +1 -0
- package/dist/tui/components/modal.js +263 -0
- package/dist/tui/components/progress-bar.d.ts +19 -0
- package/dist/tui/components/progress-bar.d.ts.map +1 -0
- package/dist/tui/components/progress-bar.js +78 -0
- package/dist/tui/components/prompt-status.d.ts +17 -0
- package/dist/tui/components/prompt-status.d.ts.map +1 -0
- package/dist/tui/components/prompt-status.js +26 -0
- package/dist/tui/components/select-list.d.ts +48 -0
- package/dist/tui/components/select-list.d.ts.map +1 -0
- package/dist/tui/components/select-list.js +207 -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/table.d.ts +27 -0
- package/dist/tui/components/table.d.ts.map +1 -0
- package/dist/tui/components/table.js +125 -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 +33 -0
- package/dist/tui/components/tool-execution.d.ts +21 -0
- package/dist/tui/components/tool-execution.d.ts.map +1 -0
- package/dist/tui/components/tool-execution.js +161 -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 +23 -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 +18 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +22 -0
- package/dist/tui/terminal.d.ts +38 -0
- package/dist/tui/terminal.d.ts.map +1 -0
- package/dist/tui/terminal.js +94 -0
- package/dist/tui/tui.d.ts +69 -0
- package/dist/tui/tui.d.ts.map +1 -0
- package/dist/tui/tui.js +204 -0
- package/dist/tui/utils.d.ts +24 -0
- package/dist/tui/utils.d.ts.map +1 -0
- package/dist/tui/utils.js +111 -0
- package/dist/utils/bash.d.ts +7 -0
- package/dist/utils/bash.d.ts.map +1 -0
- package/dist/{tools/bash-utils.js → utils/bash.js} +31 -12
- package/dist/utils/{filesystem.d.ts → filesystem/operations.d.ts} +1 -1
- package/dist/utils/filesystem/operations.d.ts.map +1 -0
- package/dist/utils/filesystem/security.d.ts +9 -0
- package/dist/utils/filesystem/security.d.ts.map +1 -0
- package/dist/{tools/filesystem-utils.js → utils/filesystem/security.js} +93 -21
- package/dist/utils/funcs.d.ts +6 -0
- package/dist/utils/funcs.d.ts.map +1 -0
- package/dist/utils/funcs.js +6 -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/{tools/git-utils.d.ts → utils/git.d.ts} +1 -1
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/{tools/git-utils.js → utils/git.js} +0 -6
- package/dist/utils/glob.js +1 -1
- 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/{zod-utils.d.ts → zod.d.ts} +1 -1
- package/dist/utils/zod.d.ts.map +1 -0
- package/package.json +21 -21
- package/dist/conversation-analyzer.d.ts +0 -11
- package/dist/conversation-analyzer.d.ts.map +0 -1
- package/dist/conversation-analyzer.js +0 -88
- package/dist/repl/display-tool-messages.d.ts +0 -4
- package/dist/repl/display-tool-messages.d.ts.map +0 -1
- package/dist/repl/display-tool-messages.js +0 -55
- package/dist/repl/display-tool-use.d.ts +0 -14
- package/dist/repl/display-tool-use.d.ts.map +0 -1
- package/dist/repl/display-tool-use.js +0 -63
- package/dist/repl/get-prompt-header.d.ts +0 -8
- package/dist/repl/get-prompt-header.d.ts.map +0 -1
- package/dist/repl/get-prompt-header.js +0 -38
- package/dist/repl-prompt.d.ts +0 -15
- package/dist/repl-prompt.d.ts.map +0 -1
- package/dist/repl-prompt.js +0 -147
- package/dist/repl.d.ts +0 -31
- package/dist/repl.d.ts.map +0 -1
- package/dist/repl.js +0 -310
- package/dist/terminal/checkbox-prompt.d.ts +0 -36
- package/dist/terminal/checkbox-prompt.d.ts.map +0 -1
- package/dist/terminal/checkbox-prompt.js +0 -362
- package/dist/terminal/editor-prompt.d.ts +0 -10
- package/dist/terminal/editor-prompt.d.ts.map +0 -1
- package/dist/terminal/editor-prompt.js +0 -61
- package/dist/terminal/errors.d.ts +0 -19
- package/dist/terminal/errors.d.ts.map +0 -1
- package/dist/terminal/errors.js +0 -37
- package/dist/terminal/input-prompt.d.ts +0 -16
- package/dist/terminal/input-prompt.d.ts.map +0 -1
- package/dist/terminal/input-prompt.js +0 -181
- package/dist/terminal/search-prompt.d.ts +0 -20
- package/dist/terminal/search-prompt.d.ts.map +0 -1
- package/dist/terminal/search-prompt.js +0 -279
- package/dist/terminal/types.d.ts +0 -35
- package/dist/terminal/types.d.ts.map +0 -1
- package/dist/terminal/types.js +0 -1
- package/dist/tokens/manage-output.d.ts +0 -34
- package/dist/tokens/manage-output.d.ts.map +0 -1
- package/dist/tokens/manage-output.js +0 -44
- package/dist/tool-executor.d.ts +0 -28
- package/dist/tool-executor.d.ts.map +0 -1
- package/dist/tool-executor.js +0 -74
- package/dist/tools/bash-utils.d.ts +0 -7
- package/dist/tools/bash-utils.d.ts.map +0 -1
- package/dist/tools/file-editing-utils.d.ts +0 -2
- package/dist/tools/file-editing-utils.d.ts.map +0 -1
- package/dist/tools/file-editing-utils.js +0 -135
- package/dist/tools/filesystem-utils.d.ts +0 -7
- package/dist/tools/filesystem-utils.d.ts.map +0 -1
- package/dist/tools/git-utils.d.ts.map +0 -1
- package/dist/utils/filesystem.d.ts.map +0 -1
- package/dist/utils/zod-utils.d.ts.map +0 -1
- /package/dist/utils/{filesystem.js → filesystem/operations.js} +0 -0
- /package/dist/utils/{zod-utils.js → zod.js} +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Container } from "../tui.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Component that renders a complete assistant message
|
|
4
|
+
*/
|
|
5
|
+
export declare class AssistantMessageComponent extends Container {
|
|
6
|
+
private contentContainer;
|
|
7
|
+
constructor(message?: {
|
|
8
|
+
type: "message-start" | "message" | "message-end";
|
|
9
|
+
role: "assistant";
|
|
10
|
+
content: string;
|
|
11
|
+
});
|
|
12
|
+
updateContent(message: {
|
|
13
|
+
type: "message-start" | "message" | "message-end";
|
|
14
|
+
role: "assistant";
|
|
15
|
+
content: string;
|
|
16
|
+
}): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=assistant-message.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant-message.d.ts","sourceRoot":"","sources":["../../../source/tui/components/assistant-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAItC;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACtD,OAAO,CAAC,gBAAgB,CAAY;gBAExB,OAAO,CAAC,EAAE;QACpB,IAAI,EAAE,eAAe,GAAG,SAAS,GAAG,aAAa,CAAC;QAClD,IAAI,EAAE,WAAW,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB;IAYD,aAAa,CAAC,OAAO,EAAE;QACrB,IAAI,EAAE,eAAe,GAAG,SAAS,GAAG,aAAa,CAAC;QAClD,IAAI,EAAE,WAAW,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI;CAeT"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Container } from "../tui.js";
|
|
2
|
+
import { Markdown } from "./markdown.js";
|
|
3
|
+
import { Spacer } from "./spacer.js";
|
|
4
|
+
/**
|
|
5
|
+
* Component that renders a complete assistant message
|
|
6
|
+
*/
|
|
7
|
+
export class AssistantMessageComponent extends Container {
|
|
8
|
+
contentContainer;
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super();
|
|
11
|
+
// Container for text/thinking content
|
|
12
|
+
this.contentContainer = new Container();
|
|
13
|
+
this.addChild(this.contentContainer);
|
|
14
|
+
if (message) {
|
|
15
|
+
this.updateContent(message);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
updateContent(message) {
|
|
19
|
+
// Clear content container
|
|
20
|
+
this.contentContainer.clear();
|
|
21
|
+
if (message.content.length > 0) {
|
|
22
|
+
this.contentContainer.addChild(new Spacer(1));
|
|
23
|
+
}
|
|
24
|
+
const content = message.content;
|
|
25
|
+
// Assistant text messages with no background - trim the text
|
|
26
|
+
// Set paddingY=0 to avoid extra spacing before tool executions
|
|
27
|
+
this.contentContainer.addChild(new Markdown(content.trim(), { paddingX: 1, paddingY: 0 }));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Component } from "../tui.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Box component - displays content in a bordered box with header
|
|
4
|
+
*/
|
|
5
|
+
export declare class BoxComponent implements Component {
|
|
6
|
+
private header;
|
|
7
|
+
private content;
|
|
8
|
+
private width;
|
|
9
|
+
private cachedOutput?;
|
|
10
|
+
private cachedHeader?;
|
|
11
|
+
private cachedContent?;
|
|
12
|
+
private cachedWidth?;
|
|
13
|
+
constructor(header: string, content: string, width?: number);
|
|
14
|
+
setHeader(header: string): void;
|
|
15
|
+
setContent(content: string): void;
|
|
16
|
+
setWidth(width: number): void;
|
|
17
|
+
private invalidateCache;
|
|
18
|
+
render(width: number): string[];
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=box.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../../../source/tui/components/box.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C;;GAEG;AACH,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAS;IAGtB,OAAO,CAAC,YAAY,CAAC,CAAW;IAChC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,WAAW,CAAC,CAAS;gBAEjB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;IAM3D,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK/B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKjC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,OAAO,CAAC,eAAe;IAOvB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAuDhC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { getTerminalSize } from "../../terminal/control.js";
|
|
2
|
+
import { applyMarkdown } from "../../terminal/markdown.js";
|
|
3
|
+
import stripAnsi from "../../terminal/strip-ansi.js";
|
|
4
|
+
import wrapAnsi from "../../terminal/wrap-ansi.js";
|
|
5
|
+
/**
|
|
6
|
+
* Box component - displays content in a bordered box with header
|
|
7
|
+
*/
|
|
8
|
+
export class BoxComponent {
|
|
9
|
+
header;
|
|
10
|
+
content;
|
|
11
|
+
width;
|
|
12
|
+
// Cache for rendered output
|
|
13
|
+
cachedOutput;
|
|
14
|
+
cachedHeader;
|
|
15
|
+
cachedContent;
|
|
16
|
+
cachedWidth;
|
|
17
|
+
constructor(header, content, width) {
|
|
18
|
+
this.header = header;
|
|
19
|
+
this.content = content;
|
|
20
|
+
this.width = width || 0;
|
|
21
|
+
}
|
|
22
|
+
setHeader(header) {
|
|
23
|
+
this.header = header;
|
|
24
|
+
this.invalidateCache();
|
|
25
|
+
}
|
|
26
|
+
setContent(content) {
|
|
27
|
+
this.content = content;
|
|
28
|
+
this.invalidateCache();
|
|
29
|
+
}
|
|
30
|
+
setWidth(width) {
|
|
31
|
+
this.width = width;
|
|
32
|
+
this.invalidateCache();
|
|
33
|
+
}
|
|
34
|
+
invalidateCache() {
|
|
35
|
+
this.cachedOutput = undefined;
|
|
36
|
+
this.cachedHeader = undefined;
|
|
37
|
+
this.cachedContent = undefined;
|
|
38
|
+
this.cachedWidth = undefined;
|
|
39
|
+
}
|
|
40
|
+
render(width) {
|
|
41
|
+
// Use provided width if specified, otherwise use component width or terminal size
|
|
42
|
+
let renderWidth = width || this.width;
|
|
43
|
+
if (renderWidth === 0) {
|
|
44
|
+
const { columns } = getTerminalSize();
|
|
45
|
+
const cols = columns > 0 ? columns : 80;
|
|
46
|
+
renderWidth = Math.max(4, cols - 4);
|
|
47
|
+
}
|
|
48
|
+
// Check cache
|
|
49
|
+
if (this.cachedOutput &&
|
|
50
|
+
this.cachedHeader === this.header &&
|
|
51
|
+
this.cachedContent === this.content &&
|
|
52
|
+
this.cachedWidth === renderWidth) {
|
|
53
|
+
return this.cachedOutput;
|
|
54
|
+
}
|
|
55
|
+
const paddedHeader = ` ${this.header} `;
|
|
56
|
+
const headerVisibleLen = stripAnsi(paddedHeader).length;
|
|
57
|
+
const headerStartPos = 1;
|
|
58
|
+
// Top border with header (use visible header length)
|
|
59
|
+
const leftDashes = headerStartPos;
|
|
60
|
+
const rightDashes = Math.max(0, renderWidth - leftDashes - headerVisibleLen);
|
|
61
|
+
const topBorder = `┌${"─".repeat(leftDashes)}${paddedHeader}${"─".repeat(rightDashes)}┐`;
|
|
62
|
+
// Prepare inner content: format markdown first, then wrap to inner width
|
|
63
|
+
const innerWidth = Math.max(1, renderWidth - 2);
|
|
64
|
+
const formatted = applyMarkdown(this.content);
|
|
65
|
+
const wrapped = wrapAnsi(formatted, innerWidth, { trim: false });
|
|
66
|
+
const contentLines = wrapped.split("\n").map((line) => {
|
|
67
|
+
const visibleLen = stripAnsi(line).length;
|
|
68
|
+
const padCount = Math.max(0, innerWidth - visibleLen);
|
|
69
|
+
return `│ ${line}${" ".repeat(padCount)} │`;
|
|
70
|
+
});
|
|
71
|
+
// Bottom border
|
|
72
|
+
const bottomBorder = `└${"─".repeat(renderWidth)}┘`;
|
|
73
|
+
const result = [topBorder, ...contentLines, bottomBorder];
|
|
74
|
+
// Update cache
|
|
75
|
+
this.cachedOutput = result;
|
|
76
|
+
this.cachedHeader = this.header;
|
|
77
|
+
this.cachedContent = this.content;
|
|
78
|
+
this.cachedWidth = renderWidth;
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
}
|