@nghyane/arcane 0.1.0
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/CHANGELOG.md +3 -0
- package/README.md +12 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +109 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/todo/index.ts +206 -0
- package/examples/extensions/README.md +143 -0
- package/examples/extensions/api-demo.ts +89 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +32 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +550 -0
- package/examples/extensions/reload-runtime.ts +37 -0
- package/examples/extensions/todo.ts +296 -0
- package/examples/extensions/tools.ts +144 -0
- package/examples/extensions/with-deps/index.ts +35 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +43 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +80 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +41 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/README.md +150 -0
- package/package.json +464 -0
- package/scripts/format-prompts.ts +184 -0
- package/scripts/generate-docs-index.ts +40 -0
- package/scripts/generate-template.ts +32 -0
- package/src/bun-imports.d.ts +22 -0
- package/src/capability/context-file.ts +39 -0
- package/src/capability/extension-module.ts +33 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +89 -0
- package/src/capability/hook.ts +39 -0
- package/src/capability/index.ts +432 -0
- package/src/capability/instruction.ts +36 -0
- package/src/capability/mcp.ts +60 -0
- package/src/capability/prompt.ts +34 -0
- package/src/capability/rule.ts +223 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +48 -0
- package/src/capability/slash-command.ts +39 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +37 -0
- package/src/capability/types.ts +156 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/config-cli.ts +357 -0
- package/src/cli/file-processor.ts +124 -0
- package/src/cli/grep-cli.ts +152 -0
- package/src/cli/jupyter-cli.ts +106 -0
- package/src/cli/list-models.ts +103 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +42 -0
- package/src/cli/setup-cli.ts +376 -0
- package/src/cli/shell-cli.ts +174 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/stats-cli.ts +197 -0
- package/src/cli/update-cli.ts +286 -0
- package/src/cli/web-search-cli.ts +143 -0
- package/src/cli.ts +65 -0
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +41 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/launch.ts +139 -0
- package/src/commands/plugin.ts +70 -0
- package/src/commands/setup.ts +42 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/ssh.ts +60 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +42 -0
- package/src/commit/agentic/agent.ts +311 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +359 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +69 -0
- package/src/commit/agentic/tools/analyze-file.ts +118 -0
- package/src/commit/agentic/tools/git-file-diff.ts +194 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +84 -0
- package/src/commit/agentic/tools/index.ts +56 -0
- package/src/commit/agentic/tools/propose-changelog.ts +128 -0
- package/src/commit/agentic/tools/propose-commit.ts +154 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/split-commit.ts +280 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +200 -0
- package/src/commit/analysis/conventional.ts +165 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +112 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +37 -0
- package/src/commit/changelog/generate.ts +110 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +93 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/git/errors.ts +9 -0
- package/src/commit/git/index.ts +211 -0
- package/src/commit/git/operations.ts +54 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +64 -0
- package/src/commit/map-reduce/map-phase.ts +178 -0
- package/src/commit/map-reduce/reduce-phase.ts +145 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +69 -0
- package/src/commit/pipeline.ts +243 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/types.ts +109 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/config/file-lock.ts +121 -0
- package/src/config/keybindings.ts +280 -0
- package/src/config/model-registry.ts +1140 -0
- package/src/config/model-resolver.ts +812 -0
- package/src/config/prompt-templates.ts +526 -0
- package/src/config/resolve-config-value.ts +92 -0
- package/src/config/settings-schema.ts +1236 -0
- package/src/config/settings.ts +706 -0
- package/src/config.ts +414 -0
- package/src/cursor.ts +239 -0
- package/src/debug/index.ts +431 -0
- package/src/debug/log-formatting.ts +60 -0
- package/src/debug/log-viewer.ts +903 -0
- package/src/debug/profiler.ts +158 -0
- package/src/debug/report-bundle.ts +366 -0
- package/src/debug/system-info.ts +112 -0
- package/src/discovery/agents-md.ts +68 -0
- package/src/discovery/agents.ts +199 -0
- package/src/discovery/builtin.ts +815 -0
- package/src/discovery/claude-plugins.ts +205 -0
- package/src/discovery/claude.ts +506 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +532 -0
- package/src/discovery/cursor.ts +218 -0
- package/src/discovery/gemini.ts +395 -0
- package/src/discovery/github.ts +117 -0
- package/src/discovery/helpers.ts +698 -0
- package/src/discovery/index.ts +89 -0
- package/src/discovery/mcp-json.ts +156 -0
- package/src/discovery/opencode.ts +394 -0
- package/src/discovery/ssh.ts +160 -0
- package/src/discovery/vscode.ts +103 -0
- package/src/discovery/windsurf.ts +145 -0
- package/src/exa/company.ts +57 -0
- package/src/exa/index.ts +62 -0
- package/src/exa/linkedin.ts +57 -0
- package/src/exa/mcp-client.ts +289 -0
- package/src/exa/render.ts +244 -0
- package/src/exa/researcher.ts +89 -0
- package/src/exa/search.ts +330 -0
- package/src/exa/types.ts +166 -0
- package/src/exa/websets.ts +247 -0
- package/src/exec/bash-executor.ts +184 -0
- package/src/exec/exec.ts +53 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/index.ts +162 -0
- package/src/export/html/template.css +889 -0
- package/src/export/html/template.generated.ts +2 -0
- package/src/export/html/template.html +45 -0
- package/src/export/html/template.js +1329 -0
- package/src/export/html/template.macro.ts +24 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/ttsr.ts +434 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +433 -0
- package/src/extensibility/custom-commands/index.ts +15 -0
- package/src/extensibility/custom-commands/loader.ts +231 -0
- package/src/extensibility/custom-commands/types.ts +111 -0
- package/src/extensibility/custom-tools/index.ts +22 -0
- package/src/extensibility/custom-tools/loader.ts +235 -0
- package/src/extensibility/custom-tools/types.ts +226 -0
- package/src/extensibility/custom-tools/wrapper.ts +45 -0
- package/src/extensibility/extensions/index.ts +136 -0
- package/src/extensibility/extensions/loader.ts +520 -0
- package/src/extensibility/extensions/runner.ts +774 -0
- package/src/extensibility/extensions/types.ts +1293 -0
- package/src/extensibility/extensions/wrapper.ts +188 -0
- package/src/extensibility/hooks/index.ts +16 -0
- package/src/extensibility/hooks/loader.ts +273 -0
- package/src/extensibility/hooks/runner.ts +441 -0
- package/src/extensibility/hooks/tool-wrapper.ts +106 -0
- package/src/extensibility/hooks/types.ts +817 -0
- package/src/extensibility/plugins/doctor.ts +65 -0
- package/src/extensibility/plugins/git-url.ts +281 -0
- package/src/extensibility/plugins/index.ts +33 -0
- package/src/extensibility/plugins/installer.ts +192 -0
- package/src/extensibility/plugins/loader.ts +338 -0
- package/src/extensibility/plugins/manager.ts +716 -0
- package/src/extensibility/plugins/parser.ts +105 -0
- package/src/extensibility/plugins/types.ts +190 -0
- package/src/extensibility/skills.ts +385 -0
- package/src/extensibility/slash-commands.ts +287 -0
- package/src/extensibility/tool-proxy.ts +25 -0
- package/src/index.ts +275 -0
- package/src/internal-urls/agent-protocol.ts +136 -0
- package/src/internal-urls/artifact-protocol.ts +97 -0
- package/src/internal-urls/docs-index.generated.ts +54 -0
- package/src/internal-urls/docs-protocol.ts +84 -0
- package/src/internal-urls/index.ts +31 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/memory-protocol.ts +133 -0
- package/src/internal-urls/router.ts +70 -0
- package/src/internal-urls/rule-protocol.ts +55 -0
- package/src/internal-urls/skill-protocol.ts +111 -0
- package/src/internal-urls/types.ts +52 -0
- package/src/ipy/executor.ts +556 -0
- package/src/ipy/gateway-coordinator.ts +426 -0
- package/src/ipy/kernel.ts +892 -0
- package/src/ipy/modules.ts +109 -0
- package/src/ipy/prelude.py +831 -0
- package/src/ipy/prelude.ts +3 -0
- package/src/ipy/runtime.ts +222 -0
- package/src/lsp/client.ts +867 -0
- package/src/lsp/clients/biome-client.ts +202 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +93 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +397 -0
- package/src/lsp/defaults.json +464 -0
- package/src/lsp/edits.ts +109 -0
- package/src/lsp/index.ts +1268 -0
- package/src/lsp/lspmux.ts +250 -0
- package/src/lsp/render.ts +689 -0
- package/src/lsp/types.ts +414 -0
- package/src/lsp/utils.ts +549 -0
- package/src/main.ts +773 -0
- package/src/mcp/client.ts +239 -0
- package/src/mcp/config-writer.ts +215 -0
- package/src/mcp/config.ts +363 -0
- package/src/mcp/index.ts +55 -0
- package/src/mcp/json-rpc.ts +84 -0
- package/src/mcp/loader.ts +124 -0
- package/src/mcp/manager.ts +490 -0
- package/src/mcp/oauth-discovery.ts +274 -0
- package/src/mcp/oauth-flow.ts +229 -0
- package/src/mcp/render.ts +123 -0
- package/src/mcp/tool-bridge.ts +372 -0
- package/src/mcp/tool-cache.ts +121 -0
- package/src/mcp/transports/http.ts +332 -0
- package/src/mcp/transports/index.ts +6 -0
- package/src/mcp/transports/stdio.ts +281 -0
- package/src/mcp/types.ts +248 -0
- package/src/memories/index.ts +1099 -0
- package/src/memories/storage.ts +563 -0
- package/src/modes/components/agent-dashboard.ts +1130 -0
- package/src/modes/components/assistant-message.ts +144 -0
- package/src/modes/components/bash-execution.ts +218 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/branch-summary-message.ts +45 -0
- package/src/modes/components/codemode-group.ts +369 -0
- package/src/modes/components/compaction-summary-message.ts +51 -0
- package/src/modes/components/countdown-timer.ts +46 -0
- package/src/modes/components/custom-editor.ts +181 -0
- package/src/modes/components/custom-message.ts +91 -0
- package/src/modes/components/diff.ts +186 -0
- package/src/modes/components/dynamic-border.ts +25 -0
- package/src/modes/components/extensions/extension-dashboard.ts +325 -0
- package/src/modes/components/extensions/extension-list.ts +484 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +321 -0
- package/src/modes/components/extensions/state-manager.ts +586 -0
- package/src/modes/components/extensions/types.ts +191 -0
- package/src/modes/components/footer.ts +315 -0
- package/src/modes/components/history-search.ts +157 -0
- package/src/modes/components/hook-editor.ts +101 -0
- package/src/modes/components/hook-input.ts +72 -0
- package/src/modes/components/hook-message.ts +100 -0
- package/src/modes/components/hook-selector.ts +155 -0
- package/src/modes/components/index.ts +41 -0
- package/src/modes/components/keybinding-hints.ts +65 -0
- package/src/modes/components/login-dialog.ts +164 -0
- package/src/modes/components/mcp-add-wizard.ts +1295 -0
- package/src/modes/components/model-selector.ts +625 -0
- package/src/modes/components/oauth-selector.ts +210 -0
- package/src/modes/components/plugin-settings.ts +477 -0
- package/src/modes/components/python-execution.ts +196 -0
- package/src/modes/components/queue-mode-selector.ts +56 -0
- package/src/modes/components/read-tool-group.ts +119 -0
- package/src/modes/components/session-selector.ts +242 -0
- package/src/modes/components/settings-defs.ts +340 -0
- package/src/modes/components/settings-selector.ts +529 -0
- package/src/modes/components/show-images-selector.ts +45 -0
- package/src/modes/components/skill-message.ts +90 -0
- package/src/modes/components/status-line/index.ts +4 -0
- package/src/modes/components/status-line/presets.ts +94 -0
- package/src/modes/components/status-line/segments.ts +352 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/types.ts +75 -0
- package/src/modes/components/status-line-segment-editor.ts +354 -0
- package/src/modes/components/status-line.ts +421 -0
- package/src/modes/components/theme-selector.ts +63 -0
- package/src/modes/components/thinking-selector.ts +64 -0
- package/src/modes/components/todo-display.ts +115 -0
- package/src/modes/components/todo-reminder.ts +40 -0
- package/src/modes/components/tool-execution.ts +703 -0
- package/src/modes/components/tree-selector.ts +904 -0
- package/src/modes/components/ttsr-notification.ts +80 -0
- package/src/modes/components/user-message-selector.ts +146 -0
- package/src/modes/components/user-message.ts +22 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +247 -0
- package/src/modes/controllers/command-controller.ts +1120 -0
- package/src/modes/controllers/event-controller.ts +479 -0
- package/src/modes/controllers/extension-ui-controller.ts +778 -0
- package/src/modes/controllers/input-controller.ts +671 -0
- package/src/modes/controllers/mcp-command-controller.ts +1315 -0
- package/src/modes/controllers/selector-controller.ts +712 -0
- package/src/modes/controllers/ssh-command-controller.ts +452 -0
- package/src/modes/index.ts +15 -0
- package/src/modes/interactive-mode.ts +1027 -0
- package/src/modes/print-mode.ts +191 -0
- package/src/modes/rpc/rpc-client.ts +583 -0
- package/src/modes/rpc/rpc-mode.ts +700 -0
- package/src/modes/rpc/rpc-types.ts +236 -0
- package/src/modes/theme/dark.json +95 -0
- package/src/modes/theme/defaults/alabaster.json +93 -0
- package/src/modes/theme/defaults/amethyst.json +96 -0
- package/src/modes/theme/defaults/anthracite.json +93 -0
- package/src/modes/theme/defaults/basalt.json +91 -0
- package/src/modes/theme/defaults/birch.json +95 -0
- package/src/modes/theme/defaults/dark-abyss.json +91 -0
- package/src/modes/theme/defaults/dark-arctic.json +104 -0
- package/src/modes/theme/defaults/dark-aurora.json +95 -0
- package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
- package/src/modes/theme/defaults/dark-cavern.json +91 -0
- package/src/modes/theme/defaults/dark-copper.json +95 -0
- package/src/modes/theme/defaults/dark-cosmos.json +90 -0
- package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/modes/theme/defaults/dark-dracula.json +98 -0
- package/src/modes/theme/defaults/dark-eclipse.json +91 -0
- package/src/modes/theme/defaults/dark-ember.json +95 -0
- package/src/modes/theme/defaults/dark-equinox.json +90 -0
- package/src/modes/theme/defaults/dark-forest.json +96 -0
- package/src/modes/theme/defaults/dark-github.json +105 -0
- package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
- package/src/modes/theme/defaults/dark-lavender.json +95 -0
- package/src/modes/theme/defaults/dark-lunar.json +89 -0
- package/src/modes/theme/defaults/dark-midnight.json +95 -0
- package/src/modes/theme/defaults/dark-monochrome.json +94 -0
- package/src/modes/theme/defaults/dark-monokai.json +98 -0
- package/src/modes/theme/defaults/dark-nebula.json +90 -0
- package/src/modes/theme/defaults/dark-nord.json +97 -0
- package/src/modes/theme/defaults/dark-ocean.json +101 -0
- package/src/modes/theme/defaults/dark-one.json +100 -0
- package/src/modes/theme/defaults/dark-rainforest.json +91 -0
- package/src/modes/theme/defaults/dark-reef.json +91 -0
- package/src/modes/theme/defaults/dark-retro.json +92 -0
- package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
- package/src/modes/theme/defaults/dark-sakura.json +95 -0
- package/src/modes/theme/defaults/dark-slate.json +95 -0
- package/src/modes/theme/defaults/dark-solarized.json +97 -0
- package/src/modes/theme/defaults/dark-solstice.json +90 -0
- package/src/modes/theme/defaults/dark-starfall.json +91 -0
- package/src/modes/theme/defaults/dark-sunset.json +99 -0
- package/src/modes/theme/defaults/dark-swamp.json +90 -0
- package/src/modes/theme/defaults/dark-synthwave.json +103 -0
- package/src/modes/theme/defaults/dark-taiga.json +91 -0
- package/src/modes/theme/defaults/dark-terminal.json +95 -0
- package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/modes/theme/defaults/dark-tundra.json +91 -0
- package/src/modes/theme/defaults/dark-twilight.json +91 -0
- package/src/modes/theme/defaults/dark-volcanic.json +91 -0
- package/src/modes/theme/defaults/graphite.json +92 -0
- package/src/modes/theme/defaults/index.ts +195 -0
- package/src/modes/theme/defaults/light-arctic.json +107 -0
- package/src/modes/theme/defaults/light-aurora-day.json +91 -0
- package/src/modes/theme/defaults/light-canyon.json +91 -0
- package/src/modes/theme/defaults/light-catppuccin.json +106 -0
- package/src/modes/theme/defaults/light-cirrus.json +90 -0
- package/src/modes/theme/defaults/light-coral.json +95 -0
- package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
- package/src/modes/theme/defaults/light-dawn.json +90 -0
- package/src/modes/theme/defaults/light-dunes.json +91 -0
- package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
- package/src/modes/theme/defaults/light-forest.json +100 -0
- package/src/modes/theme/defaults/light-frost.json +95 -0
- package/src/modes/theme/defaults/light-github.json +115 -0
- package/src/modes/theme/defaults/light-glacier.json +91 -0
- package/src/modes/theme/defaults/light-gruvbox.json +108 -0
- package/src/modes/theme/defaults/light-haze.json +90 -0
- package/src/modes/theme/defaults/light-honeycomb.json +95 -0
- package/src/modes/theme/defaults/light-lagoon.json +91 -0
- package/src/modes/theme/defaults/light-lavender.json +95 -0
- package/src/modes/theme/defaults/light-meadow.json +91 -0
- package/src/modes/theme/defaults/light-mint.json +95 -0
- package/src/modes/theme/defaults/light-monochrome.json +101 -0
- package/src/modes/theme/defaults/light-ocean.json +99 -0
- package/src/modes/theme/defaults/light-one.json +99 -0
- package/src/modes/theme/defaults/light-opal.json +91 -0
- package/src/modes/theme/defaults/light-orchard.json +91 -0
- package/src/modes/theme/defaults/light-paper.json +95 -0
- package/src/modes/theme/defaults/light-prism.json +90 -0
- package/src/modes/theme/defaults/light-retro.json +98 -0
- package/src/modes/theme/defaults/light-sand.json +95 -0
- package/src/modes/theme/defaults/light-savanna.json +91 -0
- package/src/modes/theme/defaults/light-solarized.json +102 -0
- package/src/modes/theme/defaults/light-soleil.json +90 -0
- package/src/modes/theme/defaults/light-sunset.json +99 -0
- package/src/modes/theme/defaults/light-synthwave.json +98 -0
- package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
- package/src/modes/theme/defaults/light-wetland.json +91 -0
- package/src/modes/theme/defaults/light-zenith.json +89 -0
- package/src/modes/theme/defaults/limestone.json +94 -0
- package/src/modes/theme/defaults/mahogany.json +97 -0
- package/src/modes/theme/defaults/marble.json +93 -0
- package/src/modes/theme/defaults/obsidian.json +91 -0
- package/src/modes/theme/defaults/onyx.json +91 -0
- package/src/modes/theme/defaults/pearl.json +93 -0
- package/src/modes/theme/defaults/porcelain.json +91 -0
- package/src/modes/theme/defaults/quartz.json +96 -0
- package/src/modes/theme/defaults/sandstone.json +95 -0
- package/src/modes/theme/defaults/titanium.json +90 -0
- package/src/modes/theme/light.json +93 -0
- package/src/modes/theme/mermaid-cache.ts +111 -0
- package/src/modes/theme/theme-schema.json +429 -0
- package/src/modes/theme/theme.ts +2333 -0
- package/src/modes/types.ts +216 -0
- package/src/modes/utils/ui-helpers.ts +529 -0
- package/src/patch/applicator.ts +1482 -0
- package/src/patch/diff.ts +425 -0
- package/src/patch/fuzzy.ts +784 -0
- package/src/patch/hashline.ts +972 -0
- package/src/patch/index.ts +964 -0
- package/src/patch/normalize.ts +397 -0
- package/src/patch/normative.ts +72 -0
- package/src/patch/parser.ts +532 -0
- package/src/patch/shared.ts +400 -0
- package/src/patch/types.ts +292 -0
- package/src/priority.json +35 -0
- package/src/prompts/agents/explore.md +48 -0
- package/src/prompts/agents/frontmatter.md +9 -0
- package/src/prompts/agents/init.md +36 -0
- package/src/prompts/agents/librarian.md +53 -0
- package/src/prompts/agents/oracle.md +51 -0
- package/src/prompts/agents/reviewer.md +70 -0
- package/src/prompts/agents/task.md +14 -0
- package/src/prompts/compaction/branch-summary-context.md +5 -0
- package/src/prompts/compaction/branch-summary-preamble.md +2 -0
- package/src/prompts/compaction/branch-summary.md +30 -0
- package/src/prompts/compaction/compaction-short-summary.md +9 -0
- package/src/prompts/compaction/compaction-summary-context.md +5 -0
- package/src/prompts/compaction/compaction-summary.md +38 -0
- package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
- package/src/prompts/compaction/compaction-update-summary.md +45 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/read_path.md +11 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/review-request.md +64 -0
- package/src/prompts/system/agent-creation-architect.md +65 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/custom-system-prompt.md +68 -0
- package/src/prompts/system/file-operations.md +10 -0
- package/src/prompts/system/subagent-submit-reminder.md +11 -0
- package/src/prompts/system/subagent-system-prompt.md +31 -0
- package/src/prompts/system/subagent-user-prompt.md +8 -0
- package/src/prompts/system/summarization-system.md +3 -0
- package/src/prompts/system/system-prompt.md +300 -0
- package/src/prompts/system/title-system.md +2 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/web-search.md +28 -0
- package/src/prompts/tools/ask.md +44 -0
- package/src/prompts/tools/bash.md +24 -0
- package/src/prompts/tools/browser.md +33 -0
- package/src/prompts/tools/calculator.md +12 -0
- package/src/prompts/tools/explore.md +29 -0
- package/src/prompts/tools/fetch.md +16 -0
- package/src/prompts/tools/find.md +18 -0
- package/src/prompts/tools/gemini-image.md +23 -0
- package/src/prompts/tools/grep.md +28 -0
- package/src/prompts/tools/hashline.md +232 -0
- package/src/prompts/tools/librarian.md +24 -0
- package/src/prompts/tools/lsp.md +28 -0
- package/src/prompts/tools/oracle.md +26 -0
- package/src/prompts/tools/patch.md +74 -0
- package/src/prompts/tools/python.md +66 -0
- package/src/prompts/tools/read.md +36 -0
- package/src/prompts/tools/replace.md +38 -0
- package/src/prompts/tools/reviewer.md +41 -0
- package/src/prompts/tools/ssh.md +51 -0
- package/src/prompts/tools/task-summary.md +28 -0
- package/src/prompts/tools/task.md +275 -0
- package/src/prompts/tools/todo-write.md +65 -0
- package/src/prompts/tools/undo-edit.md +7 -0
- package/src/prompts/tools/web-search.md +19 -0
- package/src/prompts/tools/write.md +18 -0
- package/src/sdk.ts +1287 -0
- package/src/secrets/index.ts +116 -0
- package/src/secrets/obfuscator.ts +269 -0
- package/src/secrets/regex.ts +21 -0
- package/src/session/agent-session.ts +4669 -0
- package/src/session/agent-storage.ts +621 -0
- package/src/session/artifacts.ts +132 -0
- package/src/session/auth-storage.ts +1433 -0
- package/src/session/blob-store.ts +103 -0
- package/src/session/compaction/branch-summarization.ts +315 -0
- package/src/session/compaction/compaction.ts +864 -0
- package/src/session/compaction/index.ts +7 -0
- package/src/session/compaction/pruning.ts +91 -0
- package/src/session/compaction/utils.ts +171 -0
- package/src/session/history-storage.ts +170 -0
- package/src/session/messages.ts +317 -0
- package/src/session/session-manager.ts +2276 -0
- package/src/session/session-storage.ts +342 -0
- package/src/session/streaming-output.ts +565 -0
- package/src/slash-commands/builtin-registry.ts +439 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +444 -0
- package/src/ssh/ssh-executor.ts +127 -0
- package/src/ssh/sshfs-mount.ts +135 -0
- package/src/stt/downloader.ts +71 -0
- package/src/stt/index.ts +3 -0
- package/src/stt/recorder.ts +351 -0
- package/src/stt/setup.ts +52 -0
- package/src/stt/stt-controller.ts +160 -0
- package/src/stt/transcribe.py +70 -0
- package/src/stt/transcriber.ts +91 -0
- package/src/system-prompt.ts +685 -0
- package/src/task/agents.ts +155 -0
- package/src/task/batch.ts +102 -0
- package/src/task/commands.ts +134 -0
- package/src/task/discovery.ts +126 -0
- package/src/task/executor.ts +908 -0
- package/src/task/index.ts +223 -0
- package/src/task/output-manager.ts +107 -0
- package/src/task/parallel.ts +84 -0
- package/src/task/render.ts +326 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/template.ts +32 -0
- package/src/task/types.ts +144 -0
- package/src/tools/ask.ts +523 -0
- package/src/tools/bash-interactive.ts +419 -0
- package/src/tools/bash-interceptor.ts +105 -0
- package/src/tools/bash-normalize.ts +107 -0
- package/src/tools/bash-skill-urls.ts +177 -0
- package/src/tools/bash.ts +347 -0
- package/src/tools/browser.ts +1374 -0
- package/src/tools/calculator.ts +537 -0
- package/src/tools/context.ts +39 -0
- package/src/tools/explore.ts +23 -0
- package/src/tools/fetch.ts +1091 -0
- package/src/tools/find.ts +540 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gemini-image.ts +907 -0
- package/src/tools/grep.ts +489 -0
- package/src/tools/index.ts +337 -0
- package/src/tools/json-tree.ts +231 -0
- package/src/tools/jtd-to-json-schema.ts +247 -0
- package/src/tools/jtd-to-typescript.ts +198 -0
- package/src/tools/librarian.ts +33 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/notebook.ts +287 -0
- package/src/tools/oracle.ts +40 -0
- package/src/tools/output-meta.ts +459 -0
- package/src/tools/output-utils.ts +63 -0
- package/src/tools/path-utils.ts +116 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
- package/src/tools/python.ts +1118 -0
- package/src/tools/read.ts +1193 -0
- package/src/tools/render-utils.ts +680 -0
- package/src/tools/renderers.ts +60 -0
- package/src/tools/reviewer-tool.ts +41 -0
- package/src/tools/ssh.ts +326 -0
- package/src/tools/subagent-tool.ts +169 -0
- package/src/tools/submit-result.ts +152 -0
- package/src/tools/todo-write.ts +255 -0
- package/src/tools/tool-errors.ts +92 -0
- package/src/tools/tool-result.ts +86 -0
- package/src/tools/undo-edit.ts +145 -0
- package/src/tools/undo-history.ts +22 -0
- package/src/tools/write.ts +274 -0
- package/src/tui/code-cell.ts +108 -0
- package/src/tui/file-list.ts +47 -0
- package/src/tui/index.ts +11 -0
- package/src/tui/output-block.ts +144 -0
- package/src/tui/status-line.ts +39 -0
- package/src/tui/tree-list.ts +53 -0
- package/src/tui/types.ts +16 -0
- package/src/tui/utils.ts +116 -0
- package/src/utils/changelog.ts +98 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +59 -0
- package/src/utils/file-display-mode.ts +36 -0
- package/src/utils/file-mentions.ts +384 -0
- package/src/utils/frontmatter.ts +101 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/ignore-files.ts +119 -0
- package/src/utils/image-convert.ts +27 -0
- package/src/utils/image-resize.ts +236 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/open.ts +20 -0
- package/src/utils/shell-snapshot.ts +199 -0
- package/src/utils/timings.ts +26 -0
- package/src/utils/title-generator.ts +167 -0
- package/src/utils/tools-manager.ts +362 -0
- package/src/web/scrapers/artifacthub.ts +215 -0
- package/src/web/scrapers/arxiv.ts +88 -0
- package/src/web/scrapers/aur.ts +175 -0
- package/src/web/scrapers/biorxiv.ts +141 -0
- package/src/web/scrapers/bluesky.ts +284 -0
- package/src/web/scrapers/brew.ts +177 -0
- package/src/web/scrapers/cheatsh.ts +78 -0
- package/src/web/scrapers/chocolatey.ts +158 -0
- package/src/web/scrapers/choosealicense.ts +110 -0
- package/src/web/scrapers/cisa-kev.ts +100 -0
- package/src/web/scrapers/clojars.ts +180 -0
- package/src/web/scrapers/coingecko.ts +184 -0
- package/src/web/scrapers/crates-io.ts +128 -0
- package/src/web/scrapers/crossref.ts +149 -0
- package/src/web/scrapers/devto.ts +177 -0
- package/src/web/scrapers/discogs.ts +307 -0
- package/src/web/scrapers/discourse.ts +221 -0
- package/src/web/scrapers/dockerhub.ts +160 -0
- package/src/web/scrapers/fdroid.ts +158 -0
- package/src/web/scrapers/firefox-addons.ts +214 -0
- package/src/web/scrapers/flathub.ts +239 -0
- package/src/web/scrapers/github-gist.ts +68 -0
- package/src/web/scrapers/github.ts +490 -0
- package/src/web/scrapers/gitlab.ts +456 -0
- package/src/web/scrapers/go-pkg.ts +275 -0
- package/src/web/scrapers/hackage.ts +94 -0
- package/src/web/scrapers/hackernews.ts +208 -0
- package/src/web/scrapers/hex.ts +121 -0
- package/src/web/scrapers/huggingface.ts +385 -0
- package/src/web/scrapers/iacr.ts +86 -0
- package/src/web/scrapers/index.ts +249 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +169 -0
- package/src/web/scrapers/lemmy.ts +220 -0
- package/src/web/scrapers/lobsters.ts +186 -0
- package/src/web/scrapers/mastodon.ts +310 -0
- package/src/web/scrapers/maven.ts +152 -0
- package/src/web/scrapers/mdn.ts +172 -0
- package/src/web/scrapers/metacpan.ts +253 -0
- package/src/web/scrapers/musicbrainz.ts +272 -0
- package/src/web/scrapers/npm.ts +114 -0
- package/src/web/scrapers/nuget.ts +205 -0
- package/src/web/scrapers/nvd.ts +243 -0
- package/src/web/scrapers/ollama.ts +265 -0
- package/src/web/scrapers/open-vsx.ts +119 -0
- package/src/web/scrapers/opencorporates.ts +275 -0
- package/src/web/scrapers/openlibrary.ts +319 -0
- package/src/web/scrapers/orcid.ts +298 -0
- package/src/web/scrapers/osv.ts +192 -0
- package/src/web/scrapers/packagist.ts +174 -0
- package/src/web/scrapers/pub-dev.ts +185 -0
- package/src/web/scrapers/pubmed.ts +177 -0
- package/src/web/scrapers/pypi.ts +129 -0
- package/src/web/scrapers/rawg.ts +124 -0
- package/src/web/scrapers/readthedocs.ts +125 -0
- package/src/web/scrapers/reddit.ts +104 -0
- package/src/web/scrapers/repology.ts +262 -0
- package/src/web/scrapers/rfc.ts +209 -0
- package/src/web/scrapers/rubygems.ts +117 -0
- package/src/web/scrapers/searchcode.ts +217 -0
- package/src/web/scrapers/sec-edgar.ts +274 -0
- package/src/web/scrapers/semantic-scholar.ts +190 -0
- package/src/web/scrapers/snapcraft.ts +200 -0
- package/src/web/scrapers/sourcegraph.ts +373 -0
- package/src/web/scrapers/spdx.ts +121 -0
- package/src/web/scrapers/spotify.ts +217 -0
- package/src/web/scrapers/stackoverflow.ts +124 -0
- package/src/web/scrapers/terraform.ts +304 -0
- package/src/web/scrapers/tldr.ts +51 -0
- package/src/web/scrapers/twitter.ts +97 -0
- package/src/web/scrapers/types.ts +200 -0
- package/src/web/scrapers/utils.ts +142 -0
- package/src/web/scrapers/vimeo.ts +152 -0
- package/src/web/scrapers/vscode-marketplace.ts +195 -0
- package/src/web/scrapers/w3c.ts +163 -0
- package/src/web/scrapers/wikidata.ts +357 -0
- package/src/web/scrapers/wikipedia.ts +95 -0
- package/src/web/scrapers/youtube.ts +312 -0
- package/src/web/search/auth.ts +178 -0
- package/src/web/search/index.ts +598 -0
- package/src/web/search/provider.ts +77 -0
- package/src/web/search/providers/anthropic.ts +284 -0
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/brave.ts +165 -0
- package/src/web/search/providers/codex.ts +377 -0
- package/src/web/search/providers/exa.ts +158 -0
- package/src/web/search/providers/gemini.ts +437 -0
- package/src/web/search/providers/jina.ts +99 -0
- package/src/web/search/providers/kimi.ts +196 -0
- package/src/web/search/providers/perplexity.ts +546 -0
- package/src/web/search/providers/synthetic.ts +136 -0
- package/src/web/search/providers/zai.ts +352 -0
- package/src/web/search/render.ts +299 -0
- package/src/web/search/types.ts +437 -0
|
@@ -0,0 +1,1120 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
getEnvApiKey,
|
|
6
|
+
getProviderDetails,
|
|
7
|
+
type ProviderDetails,
|
|
8
|
+
type UsageLimit,
|
|
9
|
+
type UsageReport,
|
|
10
|
+
} from "@nghyane/arcane-ai";
|
|
11
|
+
import { copyToClipboard } from "@nghyane/arcane-natives";
|
|
12
|
+
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@nghyane/arcane-tui";
|
|
13
|
+
import { Snowflake } from "@nghyane/arcane-utils";
|
|
14
|
+
import { setProjectDir } from "@nghyane/arcane-utils/dirs";
|
|
15
|
+
import { $ } from "bun";
|
|
16
|
+
import { reset as resetCapabilities } from "../../capability";
|
|
17
|
+
import { loadCustomShare } from "../../export/custom-share";
|
|
18
|
+
import type { CompactOptions } from "../../extensibility/extensions/types";
|
|
19
|
+
import { getGatewayStatus } from "../../ipy/gateway-coordinator";
|
|
20
|
+
import { buildMemoryToolDeveloperInstructions, clearMemoryData, enqueueMemoryConsolidation } from "../../memories";
|
|
21
|
+
import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
22
|
+
import { BorderedLoader } from "../../modes/components/bordered-loader";
|
|
23
|
+
import { DynamicBorder } from "../../modes/components/dynamic-border";
|
|
24
|
+
import { PythonExecutionComponent } from "../../modes/components/python-execution";
|
|
25
|
+
import { getMarkdownTheme, getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
26
|
+
import type { InteractiveModeContext } from "../../modes/types";
|
|
27
|
+
import type { AuthStorage } from "../../session/auth-storage";
|
|
28
|
+
import { createCompactionSummaryMessage } from "../../session/messages";
|
|
29
|
+
import { outputMeta } from "../../tools/output-meta";
|
|
30
|
+
import { resolveToCwd } from "../../tools/path-utils";
|
|
31
|
+
import { getChangelogPath, parseChangelog } from "../../utils/changelog";
|
|
32
|
+
import { openPath } from "../../utils/open";
|
|
33
|
+
|
|
34
|
+
export class CommandController {
|
|
35
|
+
constructor(private readonly ctx: InteractiveModeContext) {}
|
|
36
|
+
|
|
37
|
+
openInBrowser(urlOrPath: string): void {
|
|
38
|
+
openPath(urlOrPath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async handleExportCommand(text: string): Promise<void> {
|
|
42
|
+
const parts = text.split(/\s+/);
|
|
43
|
+
const arg = parts.length > 1 ? parts[1] : undefined;
|
|
44
|
+
|
|
45
|
+
if (arg === "--copy" || arg === "clipboard" || arg === "copy") {
|
|
46
|
+
this.ctx.showWarning("Use /dump to copy the session to clipboard.");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const filePath = await this.ctx.session.exportToHtml(arg);
|
|
52
|
+
this.ctx.showStatus(`Session exported to: ${filePath}`);
|
|
53
|
+
this.openInBrowser(filePath);
|
|
54
|
+
} catch (error: unknown) {
|
|
55
|
+
this.ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async handleDumpCommand(): Promise<void> {
|
|
60
|
+
try {
|
|
61
|
+
const formatted = this.ctx.session.formatSessionAsText();
|
|
62
|
+
if (!formatted) {
|
|
63
|
+
this.ctx.showError("No messages to dump yet.");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
await copyToClipboard(formatted);
|
|
67
|
+
this.ctx.showStatus("Session copied to clipboard");
|
|
68
|
+
} catch (error: unknown) {
|
|
69
|
+
this.ctx.showError(`Failed to copy session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async handleShareCommand(): Promise<void> {
|
|
74
|
+
const tmpFile = path.join(os.tmpdir(), `${Snowflake.next()}.html`);
|
|
75
|
+
const cleanupTempFile = async () => {
|
|
76
|
+
try {
|
|
77
|
+
await fs.rm(tmpFile, { force: true });
|
|
78
|
+
} catch {
|
|
79
|
+
// Ignore cleanup errors
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
await this.ctx.session.exportToHtml(tmpFile);
|
|
84
|
+
} catch (error: unknown) {
|
|
85
|
+
this.ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const customShare = await loadCustomShare();
|
|
91
|
+
if (customShare) {
|
|
92
|
+
const loader = new BorderedLoader(this.ctx.ui, theme, "Sharing...");
|
|
93
|
+
this.ctx.editorContainer.clear();
|
|
94
|
+
this.ctx.editorContainer.addChild(loader);
|
|
95
|
+
this.ctx.ui.setFocus(loader);
|
|
96
|
+
this.ctx.ui.requestRender();
|
|
97
|
+
|
|
98
|
+
const restoreEditor = async () => {
|
|
99
|
+
loader.dispose();
|
|
100
|
+
this.ctx.editorContainer.clear();
|
|
101
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
102
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
103
|
+
await cleanupTempFile();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await customShare.fn(tmpFile);
|
|
108
|
+
await restoreEditor();
|
|
109
|
+
|
|
110
|
+
if (typeof result === "string") {
|
|
111
|
+
this.ctx.showStatus(`Share URL: ${result}`);
|
|
112
|
+
this.openInBrowser(result);
|
|
113
|
+
} else if (result) {
|
|
114
|
+
const parts: string[] = [];
|
|
115
|
+
if (result.url) parts.push(`Share URL: ${result.url}`);
|
|
116
|
+
if (result.message) parts.push(result.message);
|
|
117
|
+
if (parts.length > 0) this.ctx.showStatus(parts.join("\n"));
|
|
118
|
+
if (result.url) this.openInBrowser(result.url);
|
|
119
|
+
} else {
|
|
120
|
+
this.ctx.showStatus("Session shared");
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
await restoreEditor();
|
|
125
|
+
this.ctx.showError(`Custom share failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
await cleanupTempFile();
|
|
131
|
+
this.ctx.showError(err instanceof Error ? err.message : String(err));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const authResult = await $`gh auth status`.quiet().nothrow();
|
|
137
|
+
if (authResult.exitCode !== 0) {
|
|
138
|
+
await cleanupTempFile();
|
|
139
|
+
this.ctx.showError("GitHub CLI is not logged in. Run 'gh auth login' first.");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
await cleanupTempFile();
|
|
144
|
+
this.ctx.showError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com/");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const loader = new BorderedLoader(this.ctx.ui, theme, "Creating gist...");
|
|
149
|
+
this.ctx.editorContainer.clear();
|
|
150
|
+
this.ctx.editorContainer.addChild(loader);
|
|
151
|
+
this.ctx.ui.setFocus(loader);
|
|
152
|
+
this.ctx.ui.requestRender();
|
|
153
|
+
|
|
154
|
+
const restoreEditor = async () => {
|
|
155
|
+
loader.dispose();
|
|
156
|
+
this.ctx.editorContainer.clear();
|
|
157
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
158
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
159
|
+
await cleanupTempFile();
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
loader.onAbort = () => {
|
|
163
|
+
void restoreEditor();
|
|
164
|
+
this.ctx.showStatus("Share cancelled");
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const result = await $`gh gist create --public=false ${tmpFile}`.quiet().nothrow();
|
|
169
|
+
if (loader.signal.aborted) return;
|
|
170
|
+
|
|
171
|
+
await restoreEditor();
|
|
172
|
+
|
|
173
|
+
if (result.exitCode !== 0) {
|
|
174
|
+
const errorMsg = result.stderr.toString("utf-8").trim() || "Unknown error";
|
|
175
|
+
this.ctx.showError(`Failed to create gist: ${errorMsg}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const gistUrl = result.stdout.toString("utf-8").trim();
|
|
180
|
+
const gistId = gistUrl.split("/").pop();
|
|
181
|
+
if (!gistId) {
|
|
182
|
+
this.ctx.showError("Failed to parse gist ID from gh output");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const previewUrl = `https://gistpreview.github.io/?${gistId}`;
|
|
187
|
+
this.ctx.showStatus(`Share URL: ${previewUrl}\nGist: ${gistUrl}`);
|
|
188
|
+
this.openInBrowser(previewUrl);
|
|
189
|
+
} catch (error: unknown) {
|
|
190
|
+
if (!loader.signal.aborted) {
|
|
191
|
+
await restoreEditor();
|
|
192
|
+
this.ctx.showError(`Failed to create gist: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async handleCopyCommand(): Promise<void> {
|
|
198
|
+
const text = this.ctx.session.getLastAssistantText();
|
|
199
|
+
if (!text) {
|
|
200
|
+
this.ctx.showError("No agent messages to copy yet.");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
await copyToClipboard(text);
|
|
206
|
+
this.ctx.showStatus("Copied last agent message to clipboard");
|
|
207
|
+
} catch (error) {
|
|
208
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async handleSessionCommand(): Promise<void> {
|
|
213
|
+
const stats = this.ctx.session.getSessionStats();
|
|
214
|
+
|
|
215
|
+
let info = `${theme.bold("Session Info")}\n\n`;
|
|
216
|
+
info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
|
|
217
|
+
info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
|
|
218
|
+
info += `\n${theme.bold("Provider")}\n`;
|
|
219
|
+
const model = this.ctx.session.model;
|
|
220
|
+
if (!model) {
|
|
221
|
+
info += `${theme.fg("dim", "No model selected")}\n`;
|
|
222
|
+
} else {
|
|
223
|
+
const authMode = resolveProviderAuthMode(this.ctx.session.modelRegistry.authStorage, model.provider);
|
|
224
|
+
const openaiWebsocketSetting = this.ctx.settings.get("providers.openaiWebsockets") ?? "auto";
|
|
225
|
+
const preferOpenAICodexWebsockets =
|
|
226
|
+
openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
|
|
227
|
+
const providerDetails = getProviderDetails({
|
|
228
|
+
model,
|
|
229
|
+
sessionId: stats.sessionId,
|
|
230
|
+
authMode,
|
|
231
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
232
|
+
providerSessionState: this.ctx.session.providerSessionState,
|
|
233
|
+
});
|
|
234
|
+
info += renderProviderSection(providerDetails, theme);
|
|
235
|
+
}
|
|
236
|
+
info += `\n`;
|
|
237
|
+
info += `${theme.bold("Messages")}\n`;
|
|
238
|
+
info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
|
|
239
|
+
info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
|
|
240
|
+
info += `${theme.fg("dim", "Tool Calls:")} ${stats.toolCalls}\n`;
|
|
241
|
+
info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
|
|
242
|
+
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
|
|
243
|
+
info += `${theme.bold("Tokens")}\n`;
|
|
244
|
+
info += `${theme.fg("dim", "Input:")} ${stats.tokens.input.toLocaleString()}\n`;
|
|
245
|
+
info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
|
|
246
|
+
if (stats.tokens.cacheRead > 0) {
|
|
247
|
+
info += `${theme.fg("dim", "Cache Read:")} ${stats.tokens.cacheRead.toLocaleString()}\n`;
|
|
248
|
+
}
|
|
249
|
+
if (stats.tokens.cacheWrite > 0) {
|
|
250
|
+
info += `${theme.fg("dim", "Cache Write:")} ${stats.tokens.cacheWrite.toLocaleString()}\n`;
|
|
251
|
+
}
|
|
252
|
+
info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
253
|
+
|
|
254
|
+
if (stats.cost > 0) {
|
|
255
|
+
info += `\n${theme.bold("Cost")}\n`;
|
|
256
|
+
info += `${theme.fg("dim", "Total:")} ${stats.cost.toFixed(4)}\n`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const gateway = await getGatewayStatus();
|
|
260
|
+
info += `\n${theme.bold("Python Gateway")}\n`;
|
|
261
|
+
if (gateway.active) {
|
|
262
|
+
info += `${theme.fg("dim", "Status:")} ${theme.fg("success", "Active (Global)")}\n`;
|
|
263
|
+
info += `${theme.fg("dim", "URL:")} ${gateway.url}\n`;
|
|
264
|
+
info += `${theme.fg("dim", "PID:")} ${gateway.pid}\n`;
|
|
265
|
+
if (gateway.pythonPath) {
|
|
266
|
+
info += `${theme.fg("dim", "Python:")} ${gateway.pythonPath}\n`;
|
|
267
|
+
}
|
|
268
|
+
if (gateway.venvPath) {
|
|
269
|
+
info += `${theme.fg("dim", "Venv:")} ${gateway.venvPath}\n`;
|
|
270
|
+
}
|
|
271
|
+
if (gateway.uptime !== null) {
|
|
272
|
+
const uptimeSec = Math.floor(gateway.uptime / 1000);
|
|
273
|
+
const mins = Math.floor(uptimeSec / 60);
|
|
274
|
+
const secs = uptimeSec % 60;
|
|
275
|
+
info += `${theme.fg("dim", "Uptime:")} ${mins}m ${secs}s\n`;
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
info += `${theme.fg("dim", "Status:")} ${theme.fg("dim", "Inactive")}\n`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (this.ctx.lspServers && this.ctx.lspServers.length > 0) {
|
|
282
|
+
info += `\n${theme.bold("LSP Servers")}\n`;
|
|
283
|
+
for (const server of this.ctx.lspServers) {
|
|
284
|
+
const statusColor = server.status === "ready" ? "success" : "error";
|
|
285
|
+
const statusText =
|
|
286
|
+
server.status === "error" && server.error ? `${server.status}: ${server.error}` : server.status;
|
|
287
|
+
info += `${theme.fg("dim", `${server.name}:`)} ${theme.fg(statusColor, statusText)} ${theme.fg("dim", `(${server.fileTypes.join(", ")})`)}\n`;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (this.ctx.mcpManager) {
|
|
292
|
+
const mcpServers = this.ctx.mcpManager.getConnectedServers();
|
|
293
|
+
info += `\n${theme.bold("MCP Servers")}\n`;
|
|
294
|
+
if (mcpServers.length === 0) {
|
|
295
|
+
info += `${theme.fg("dim", "None connected")}\n`;
|
|
296
|
+
} else {
|
|
297
|
+
for (const name of mcpServers) {
|
|
298
|
+
const conn = this.ctx.mcpManager.getConnection(name);
|
|
299
|
+
const toolCount = conn?.tools?.length ?? 0;
|
|
300
|
+
info += `${theme.fg("dim", `${name}:`)} ${theme.fg("success", "connected")} ${theme.fg("dim", `(${toolCount} tools)`)}\n`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
306
|
+
this.ctx.chatContainer.addChild(new Text(info, 1, 0));
|
|
307
|
+
this.ctx.ui.requestRender();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async handleUsageCommand(reports?: UsageReport[] | null): Promise<void> {
|
|
311
|
+
let usageReports = reports ?? null;
|
|
312
|
+
if (!usageReports) {
|
|
313
|
+
const provider = this.ctx.session as { fetchUsageReports?: () => Promise<UsageReport[] | null> };
|
|
314
|
+
if (!provider.fetchUsageReports) {
|
|
315
|
+
this.ctx.showWarning("Usage reporting is not configured for this session.");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
usageReports = await provider.fetchUsageReports();
|
|
320
|
+
} catch (error) {
|
|
321
|
+
this.ctx.showError(`Failed to fetch usage data: ${error instanceof Error ? error.message : String(error)}`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!usageReports || usageReports.length === 0) {
|
|
327
|
+
this.ctx.showWarning("No usage data available.");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const output = renderUsageReports(usageReports, theme, Date.now());
|
|
332
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
333
|
+
this.ctx.chatContainer.addChild(new Text(output, 1, 0));
|
|
334
|
+
this.ctx.ui.requestRender();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async handleChangelogCommand(showFull = false): Promise<void> {
|
|
338
|
+
const changelogPath = getChangelogPath();
|
|
339
|
+
const allEntries = await parseChangelog(changelogPath);
|
|
340
|
+
// Default to showing only the latest 3 versions unless --full is specified
|
|
341
|
+
// allEntries comes from parseChangelog with newest first, reverse to show oldest->newest
|
|
342
|
+
const entriesToShow = showFull ? allEntries : allEntries.slice(0, 3);
|
|
343
|
+
const changelogMarkdown =
|
|
344
|
+
entriesToShow.length > 0
|
|
345
|
+
? [...entriesToShow]
|
|
346
|
+
.reverse()
|
|
347
|
+
.map(e => e.content)
|
|
348
|
+
.join("\n\n")
|
|
349
|
+
: "No changelog entries found.";
|
|
350
|
+
const title = showFull ? "Full Changelog" : "Recent Changes";
|
|
351
|
+
const hint = showFull
|
|
352
|
+
? ""
|
|
353
|
+
: `\n\n${theme.fg("dim", "Use")} ${theme.bold("/changelog full")} ${theme.fg("dim", "to view the complete changelog.")}`;
|
|
354
|
+
|
|
355
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
356
|
+
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
357
|
+
this.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", title)), 1, 0));
|
|
358
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
359
|
+
this.ctx.chatContainer.addChild(new Markdown(changelogMarkdown + hint, 1, 1, getMarkdownTheme()));
|
|
360
|
+
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
361
|
+
this.ctx.ui.requestRender();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
handleHotkeysCommand(): void {
|
|
365
|
+
const expandToolsKey = this.ctx.keybindings.getDisplayString("expandTools") || "Ctrl+O";
|
|
366
|
+
const sttKey = this.ctx.keybindings.getDisplayString("toggleSTT") || "Alt+H";
|
|
367
|
+
const hotkeys = `
|
|
368
|
+
**Navigation**
|
|
369
|
+
| Key | Action |
|
|
370
|
+
|-----|--------|
|
|
371
|
+
| \`Arrow keys\` | Move cursor / browse history (Up when empty) |
|
|
372
|
+
| \`Option+Left/Right\` | Move by word |
|
|
373
|
+
| \`Ctrl+A\` / \`Home\` / \`Cmd+Left\` | Start of line |
|
|
374
|
+
| \`Ctrl+E\` / \`End\` / \`Cmd+Right\` | End of line |
|
|
375
|
+
|
|
376
|
+
**Editing**
|
|
377
|
+
| Key | Action |
|
|
378
|
+
|-----|--------|
|
|
379
|
+
| \`Enter\` | Send message |
|
|
380
|
+
| \`Shift+Enter\` / \`Alt+Enter\` | New line |
|
|
381
|
+
| \`Ctrl+W\` / \`Option+Backspace\` | Delete word backwards |
|
|
382
|
+
| \`Ctrl+U\` | Delete to start of line |
|
|
383
|
+
| \`Ctrl+K\` | Delete to end of line |
|
|
384
|
+
|
|
385
|
+
**Other**
|
|
386
|
+
| Key | Action |
|
|
387
|
+
|-----|--------|
|
|
388
|
+
| \`Tab\` | Path completion / accept autocomplete |
|
|
389
|
+
| \`Escape\` | Cancel autocomplete / abort streaming |
|
|
390
|
+
| \`Ctrl+C\` | Clear editor (first) / exit (second) |
|
|
391
|
+
| \`Ctrl+D\` | Exit (when editor is empty) |
|
|
392
|
+
| \`Ctrl+Z\` | Suspend to background |
|
|
393
|
+
| \`Shift+Tab\` | Cycle thinking level |
|
|
394
|
+
| \`Ctrl+P\` | Cycle role models (slow/default/fast) |
|
|
395
|
+
| \`Shift+Ctrl+P\` | Cycle role models (temporary) |
|
|
396
|
+
| \`Alt+P\` | Select model (temporary) |
|
|
397
|
+
| \`Ctrl+L\` | Select model (set roles) |
|
|
398
|
+
| \`Ctrl+R\` | Search prompt history |
|
|
399
|
+
| \`${expandToolsKey}\` | Toggle tool output expansion |
|
|
400
|
+
| \`Ctrl+T\` | Toggle todo list expansion |
|
|
401
|
+
| \`Ctrl+G\` | Edit message in external editor |
|
|
402
|
+
| \`${sttKey}\` | Toggle speech-to-text recording |
|
|
403
|
+
| \`/\` | Slash commands |
|
|
404
|
+
| \`!\` | Run bash command |
|
|
405
|
+
| \`!!\` | Run bash command (excluded from context) |
|
|
406
|
+
| \`$\` | Run Python in shared kernel |
|
|
407
|
+
| \`$$\` | Run Python (excluded from context) |
|
|
408
|
+
`;
|
|
409
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
410
|
+
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
411
|
+
this.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Keyboard Shortcuts")), 1, 0));
|
|
412
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
413
|
+
this.ctx.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, getMarkdownTheme()));
|
|
414
|
+
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
415
|
+
this.ctx.ui.requestRender();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async handleMemoryCommand(text: string): Promise<void> {
|
|
419
|
+
const argumentText = text.slice(7).trim();
|
|
420
|
+
const action = argumentText.split(/\s+/, 1)[0]?.toLowerCase() || "view";
|
|
421
|
+
const agentDir = this.ctx.settings.getAgentDir();
|
|
422
|
+
|
|
423
|
+
if (action === "view") {
|
|
424
|
+
const payload = await buildMemoryToolDeveloperInstructions(agentDir, this.ctx.settings);
|
|
425
|
+
if (!payload) {
|
|
426
|
+
this.ctx.showWarning("Memory payload is empty (memories disabled or no memory summary found).");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
430
|
+
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
431
|
+
this.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Memory Injection Payload")), 1, 0));
|
|
432
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
433
|
+
this.ctx.chatContainer.addChild(new Markdown(payload, 1, 1, getMarkdownTheme()));
|
|
434
|
+
this.ctx.chatContainer.addChild(new DynamicBorder());
|
|
435
|
+
this.ctx.ui.requestRender();
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (action === "reset" || action === "clear") {
|
|
440
|
+
try {
|
|
441
|
+
await clearMemoryData(agentDir, this.ctx.sessionManager.getCwd());
|
|
442
|
+
await this.ctx.session.refreshBaseSystemPrompt();
|
|
443
|
+
this.ctx.showStatus("Memory data cleared and system prompt refreshed.");
|
|
444
|
+
} catch (error) {
|
|
445
|
+
this.ctx.showError(`Memory clear failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (action === "enqueue" || action === "rebuild") {
|
|
451
|
+
try {
|
|
452
|
+
enqueueMemoryConsolidation(agentDir);
|
|
453
|
+
this.ctx.showStatus("Memory consolidation enqueued.");
|
|
454
|
+
} catch (error) {
|
|
455
|
+
this.ctx.showError(`Memory enqueue failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this.ctx.showError("Usage: /memory <view|clear|reset|enqueue|rebuild>");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async handleClearCommand(): Promise<void> {
|
|
464
|
+
if (this.ctx.loadingAnimation) {
|
|
465
|
+
this.ctx.loadingAnimation.stop();
|
|
466
|
+
this.ctx.loadingAnimation = undefined;
|
|
467
|
+
}
|
|
468
|
+
this.ctx.statusContainer.clear();
|
|
469
|
+
|
|
470
|
+
if (this.ctx.session.isCompacting) {
|
|
471
|
+
this.ctx.session.abortCompaction();
|
|
472
|
+
while (this.ctx.session.isCompacting) {
|
|
473
|
+
await Bun.sleep(10);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
await this.ctx.session.newSession();
|
|
477
|
+
|
|
478
|
+
this.ctx.statusLine.invalidate();
|
|
479
|
+
this.ctx.updateEditorTopBorder();
|
|
480
|
+
|
|
481
|
+
this.ctx.chatContainer.clear();
|
|
482
|
+
this.ctx.pendingMessagesContainer.clear();
|
|
483
|
+
this.ctx.compactionQueuedMessages = [];
|
|
484
|
+
this.ctx.streamingComponent = undefined;
|
|
485
|
+
this.ctx.streamingMessage = undefined;
|
|
486
|
+
this.ctx.pendingTools.clear();
|
|
487
|
+
|
|
488
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
489
|
+
this.ctx.chatContainer.addChild(
|
|
490
|
+
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
|
|
491
|
+
);
|
|
492
|
+
await this.ctx.reloadTodos();
|
|
493
|
+
this.ctx.ui.requestRender();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async handleForkCommand(): Promise<void> {
|
|
497
|
+
if (this.ctx.session.isStreaming) {
|
|
498
|
+
this.ctx.showWarning("Wait for the current response to finish or abort it before forking.");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (this.ctx.loadingAnimation) {
|
|
502
|
+
this.ctx.loadingAnimation.stop();
|
|
503
|
+
this.ctx.loadingAnimation = undefined;
|
|
504
|
+
}
|
|
505
|
+
this.ctx.statusContainer.clear();
|
|
506
|
+
|
|
507
|
+
const success = await this.ctx.session.fork();
|
|
508
|
+
if (!success) {
|
|
509
|
+
this.ctx.showError("Fork failed (session not persisted or cancelled)");
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this.ctx.statusLine.invalidate();
|
|
514
|
+
this.ctx.updateEditorTopBorder();
|
|
515
|
+
|
|
516
|
+
const sessionFile = this.ctx.session.sessionFile;
|
|
517
|
+
const shortPath = sessionFile ? sessionFile.split("/").pop() : "new session";
|
|
518
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
519
|
+
this.ctx.chatContainer.addChild(
|
|
520
|
+
new Text(`${theme.fg("accent", `${theme.status.success} Session forked to ${shortPath}`)}`, 1, 1),
|
|
521
|
+
);
|
|
522
|
+
this.ctx.ui.requestRender();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async handleMoveCommand(targetPath: string): Promise<void> {
|
|
526
|
+
if (this.ctx.session.isStreaming) {
|
|
527
|
+
this.ctx.showWarning("Wait for the current response to finish or abort it before moving.");
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const cwd = this.ctx.sessionManager.getCwd();
|
|
532
|
+
const resolvedPath = resolveToCwd(targetPath, cwd);
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
const stat = await fs.stat(resolvedPath);
|
|
536
|
+
if (!stat.isDirectory()) {
|
|
537
|
+
this.ctx.showError(`Not a directory: ${resolvedPath}`);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
} catch {
|
|
541
|
+
this.ctx.showError(`Directory does not exist: ${resolvedPath}`);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
try {
|
|
546
|
+
await this.ctx.sessionManager.flush();
|
|
547
|
+
await this.ctx.sessionManager.moveTo(resolvedPath);
|
|
548
|
+
setProjectDir(resolvedPath);
|
|
549
|
+
resetCapabilities();
|
|
550
|
+
await this.ctx.refreshSlashCommandState(resolvedPath);
|
|
551
|
+
|
|
552
|
+
this.ctx.statusLine.invalidate();
|
|
553
|
+
this.ctx.updateEditorTopBorder();
|
|
554
|
+
|
|
555
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
556
|
+
this.ctx.chatContainer.addChild(
|
|
557
|
+
new Text(`${theme.fg("accent", `${theme.status.success} Session moved to ${resolvedPath}`)}`, 1, 1),
|
|
558
|
+
);
|
|
559
|
+
this.ctx.ui.requestRender();
|
|
560
|
+
} catch (err) {
|
|
561
|
+
this.ctx.showError(`Move failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async handleBashCommand(command: string, excludeFromContext = false): Promise<void> {
|
|
566
|
+
const isDeferred = this.ctx.session.isStreaming;
|
|
567
|
+
this.ctx.bashComponent = new BashExecutionComponent(command, this.ctx.ui, excludeFromContext);
|
|
568
|
+
|
|
569
|
+
if (isDeferred) {
|
|
570
|
+
this.ctx.pendingMessagesContainer.addChild(this.ctx.bashComponent);
|
|
571
|
+
this.ctx.pendingBashComponents.push(this.ctx.bashComponent);
|
|
572
|
+
} else {
|
|
573
|
+
this.ctx.chatContainer.addChild(this.ctx.bashComponent);
|
|
574
|
+
}
|
|
575
|
+
this.ctx.ui.requestRender();
|
|
576
|
+
|
|
577
|
+
try {
|
|
578
|
+
const result = await this.ctx.session.executeBash(
|
|
579
|
+
command,
|
|
580
|
+
chunk => {
|
|
581
|
+
if (this.ctx.bashComponent) {
|
|
582
|
+
this.ctx.bashComponent.appendOutput(chunk);
|
|
583
|
+
this.ctx.ui.requestRender();
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
{ excludeFromContext },
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
if (this.ctx.bashComponent) {
|
|
590
|
+
const meta = outputMeta().truncationFromSummary(result, { direction: "tail" }).get();
|
|
591
|
+
this.ctx.bashComponent.setComplete(result.exitCode, result.cancelled, {
|
|
592
|
+
output: result.output,
|
|
593
|
+
truncation: meta?.truncation,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
} catch (error) {
|
|
597
|
+
if (this.ctx.bashComponent) {
|
|
598
|
+
this.ctx.bashComponent.setComplete(undefined, false);
|
|
599
|
+
}
|
|
600
|
+
this.ctx.showError(`Bash command failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
this.ctx.bashComponent = undefined;
|
|
604
|
+
this.ctx.ui.requestRender();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async handlePythonCommand(code: string, excludeFromContext = false): Promise<void> {
|
|
608
|
+
const isDeferred = this.ctx.session.isStreaming;
|
|
609
|
+
this.ctx.pythonComponent = new PythonExecutionComponent(code, this.ctx.ui, excludeFromContext);
|
|
610
|
+
|
|
611
|
+
if (isDeferred) {
|
|
612
|
+
this.ctx.pendingMessagesContainer.addChild(this.ctx.pythonComponent);
|
|
613
|
+
this.ctx.pendingPythonComponents.push(this.ctx.pythonComponent);
|
|
614
|
+
} else {
|
|
615
|
+
this.ctx.chatContainer.addChild(this.ctx.pythonComponent);
|
|
616
|
+
}
|
|
617
|
+
this.ctx.ui.requestRender();
|
|
618
|
+
|
|
619
|
+
try {
|
|
620
|
+
const result = await this.ctx.session.executePython(
|
|
621
|
+
code,
|
|
622
|
+
chunk => {
|
|
623
|
+
if (this.ctx.pythonComponent) {
|
|
624
|
+
this.ctx.pythonComponent.appendOutput(chunk);
|
|
625
|
+
this.ctx.ui.requestRender();
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
{ excludeFromContext },
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
if (this.ctx.pythonComponent) {
|
|
632
|
+
const meta = outputMeta().truncationFromSummary(result, { direction: "tail" }).get();
|
|
633
|
+
this.ctx.pythonComponent.setComplete(result.exitCode, result.cancelled, {
|
|
634
|
+
output: result.output,
|
|
635
|
+
truncation: meta?.truncation,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
} catch (error) {
|
|
639
|
+
if (this.ctx.pythonComponent) {
|
|
640
|
+
this.ctx.pythonComponent.setComplete(undefined, false);
|
|
641
|
+
}
|
|
642
|
+
this.ctx.showError(`Python execution failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
this.ctx.pythonComponent = undefined;
|
|
646
|
+
this.ctx.ui.requestRender();
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async handleCompactCommand(customInstructions?: string): Promise<void> {
|
|
650
|
+
const entries = this.ctx.sessionManager.getEntries();
|
|
651
|
+
const messageCount = entries.filter(e => e.type === "message").length;
|
|
652
|
+
|
|
653
|
+
if (messageCount < 2) {
|
|
654
|
+
this.ctx.showWarning("Nothing to compact (no messages yet)");
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
await this.executeCompaction(customInstructions, false);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async handleSkillCommand(skillPath: string, args: string): Promise<void> {
|
|
662
|
+
try {
|
|
663
|
+
const content = await Bun.file(skillPath).text();
|
|
664
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
|
|
665
|
+
const metaLines = [`Skill: ${skillPath}`];
|
|
666
|
+
if (args) {
|
|
667
|
+
metaLines.push(`User: ${args}`);
|
|
668
|
+
}
|
|
669
|
+
const message = `${body}\n\n---\n\n${metaLines.join("\n")}`;
|
|
670
|
+
await this.ctx.session.prompt(message);
|
|
671
|
+
} catch (err) {
|
|
672
|
+
this.ctx.showError(`Failed to load skill: ${err instanceof Error ? err.message : String(err)}`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto = false): Promise<void> {
|
|
677
|
+
if (this.ctx.loadingAnimation) {
|
|
678
|
+
this.ctx.loadingAnimation.stop();
|
|
679
|
+
this.ctx.loadingAnimation = undefined;
|
|
680
|
+
}
|
|
681
|
+
this.ctx.statusContainer.clear();
|
|
682
|
+
|
|
683
|
+
const originalOnEscape = this.ctx.editor.onEscape;
|
|
684
|
+
this.ctx.editor.onEscape = () => {
|
|
685
|
+
this.ctx.session.abortCompaction();
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
689
|
+
const label = isAuto ? "Auto-compacting context... (esc to cancel)" : "Compacting context... (esc to cancel)";
|
|
690
|
+
const compactingLoader = new Loader(
|
|
691
|
+
this.ctx.ui,
|
|
692
|
+
spinner => theme.fg("accent", spinner),
|
|
693
|
+
text => theme.fg("muted", text),
|
|
694
|
+
label,
|
|
695
|
+
getSymbolTheme().spinnerFrames,
|
|
696
|
+
);
|
|
697
|
+
this.ctx.statusContainer.addChild(compactingLoader);
|
|
698
|
+
this.ctx.ui.requestRender();
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
const instructions = typeof customInstructionsOrOptions === "string" ? customInstructionsOrOptions : undefined;
|
|
702
|
+
const options =
|
|
703
|
+
customInstructionsOrOptions && typeof customInstructionsOrOptions === "object"
|
|
704
|
+
? customInstructionsOrOptions
|
|
705
|
+
: undefined;
|
|
706
|
+
const result = await this.ctx.session.compact(instructions, options);
|
|
707
|
+
|
|
708
|
+
this.ctx.rebuildChatFromMessages();
|
|
709
|
+
|
|
710
|
+
const msg = createCompactionSummaryMessage(
|
|
711
|
+
result.summary,
|
|
712
|
+
result.tokensBefore,
|
|
713
|
+
new Date().toISOString(),
|
|
714
|
+
result.shortSummary,
|
|
715
|
+
);
|
|
716
|
+
this.ctx.addMessageToChat(msg);
|
|
717
|
+
|
|
718
|
+
this.ctx.statusLine.invalidate();
|
|
719
|
+
this.ctx.updateEditorTopBorder();
|
|
720
|
+
} catch (error) {
|
|
721
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
722
|
+
if (message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError")) {
|
|
723
|
+
this.ctx.showError("Compaction cancelled");
|
|
724
|
+
} else {
|
|
725
|
+
this.ctx.showError(`Compaction failed: ${message}`);
|
|
726
|
+
}
|
|
727
|
+
} finally {
|
|
728
|
+
compactingLoader.stop();
|
|
729
|
+
this.ctx.statusContainer.clear();
|
|
730
|
+
this.ctx.editor.onEscape = originalOnEscape;
|
|
731
|
+
}
|
|
732
|
+
await this.ctx.flushCompactionQueue({ willRetry: false });
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async handleHandoffCommand(customInstructions?: string): Promise<void> {
|
|
736
|
+
const entries = this.ctx.sessionManager.getEntries();
|
|
737
|
+
const messageCount = entries.filter(e => e.type === "message").length;
|
|
738
|
+
|
|
739
|
+
if (messageCount < 2) {
|
|
740
|
+
this.ctx.showWarning("Nothing to hand off (no messages yet)");
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
// The agent will visibly generate the handoff document in chat
|
|
746
|
+
const result = await this.ctx.session.handoff(customInstructions);
|
|
747
|
+
|
|
748
|
+
if (!result) {
|
|
749
|
+
this.ctx.showError("Handoff cancelled");
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Rebuild chat from the new session (which now contains the handoff document)
|
|
754
|
+
this.ctx.rebuildChatFromMessages();
|
|
755
|
+
|
|
756
|
+
this.ctx.statusLine.invalidate();
|
|
757
|
+
this.ctx.updateEditorTopBorder();
|
|
758
|
+
await this.ctx.reloadTodos();
|
|
759
|
+
|
|
760
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
761
|
+
this.ctx.chatContainer.addChild(
|
|
762
|
+
new Text(`${theme.fg("accent", `${theme.status.success} New session started with handoff context`)}`, 1, 1),
|
|
763
|
+
);
|
|
764
|
+
} catch (error) {
|
|
765
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
766
|
+
if (message === "Handoff cancelled" || (error instanceof Error && error.name === "AbortError")) {
|
|
767
|
+
this.ctx.showError("Handoff cancelled");
|
|
768
|
+
} else {
|
|
769
|
+
this.ctx.showError(`Handoff failed: ${message}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
this.ctx.ui.requestRender();
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const BAR_WIDTH = 24;
|
|
777
|
+
const COLUMN_WIDTH = BAR_WIDTH + 2;
|
|
778
|
+
|
|
779
|
+
function formatProviderName(provider: string): string {
|
|
780
|
+
return provider
|
|
781
|
+
.split(/[-_]/g)
|
|
782
|
+
.map(part => (part ? part[0].toUpperCase() + part.slice(1) : ""))
|
|
783
|
+
.join(" ");
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function formatNumber(value: number, maxFractionDigits = 1): string {
|
|
787
|
+
return new Intl.NumberFormat("en-US", { maximumFractionDigits: maxFractionDigits }).format(value);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function formatUsedAccounts(value: number): string {
|
|
791
|
+
return `${value.toFixed(2)} used`;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function formatDuration(ms: number): string {
|
|
795
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
796
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
797
|
+
const seconds = totalSeconds % 60;
|
|
798
|
+
const hours = Math.floor(minutes / 60);
|
|
799
|
+
const mins = minutes % 60;
|
|
800
|
+
const days = Math.floor(hours / 24);
|
|
801
|
+
const hrs = hours % 24;
|
|
802
|
+
if (days > 0) return `${days}d ${hrs}h`;
|
|
803
|
+
if (hours > 0) return `${hours}h ${mins}m`;
|
|
804
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
805
|
+
return `${seconds}s`;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function formatDurationShort(ms: number): string {
|
|
809
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
810
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
811
|
+
const hours = Math.floor(minutes / 60);
|
|
812
|
+
const mins = minutes % 60;
|
|
813
|
+
const days = Math.floor(hours / 24);
|
|
814
|
+
const hrs = hours % 24;
|
|
815
|
+
if (days > 0) return `${days}d${hrs > 0 ? ` ${hrs}h` : ""}`;
|
|
816
|
+
if (hours > 0) return `${hours}h${mins > 0 ? ` ${mins}m` : ""}`;
|
|
817
|
+
if (minutes > 0) return `${minutes}m`;
|
|
818
|
+
return `${totalSeconds}s`;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function resolveProviderAuthMode(authStorage: AuthStorage, provider: string): string {
|
|
822
|
+
if (authStorage.hasOAuth(provider)) {
|
|
823
|
+
return "oauth";
|
|
824
|
+
}
|
|
825
|
+
if (authStorage.has(provider)) {
|
|
826
|
+
return "api key";
|
|
827
|
+
}
|
|
828
|
+
if (getEnvApiKey(provider)) {
|
|
829
|
+
return "env api key";
|
|
830
|
+
}
|
|
831
|
+
if (authStorage.hasAuth(provider)) {
|
|
832
|
+
return "runtime/fallback";
|
|
833
|
+
}
|
|
834
|
+
return "unknown";
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
export function renderProviderSection(details: ProviderDetails, uiTheme: Pick<typeof theme, "fg">): string {
|
|
838
|
+
const lines: string[] = [];
|
|
839
|
+
lines.push(`${uiTheme.fg("dim", "Name:")} ${details.provider}`);
|
|
840
|
+
for (const field of details.fields) {
|
|
841
|
+
lines.push(`${uiTheme.fg("dim", `${field.label}:`)} ${field.value}`);
|
|
842
|
+
}
|
|
843
|
+
return `${lines.join("\n")}\n`;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function resolveFraction(limit: UsageLimit): number | undefined {
|
|
847
|
+
const amount = limit.amount;
|
|
848
|
+
if (amount.usedFraction !== undefined) return amount.usedFraction;
|
|
849
|
+
if (amount.used !== undefined && amount.limit !== undefined && amount.limit > 0) {
|
|
850
|
+
return amount.used / amount.limit;
|
|
851
|
+
}
|
|
852
|
+
if (amount.unit === "percent" && amount.used !== undefined) {
|
|
853
|
+
return amount.used / 100;
|
|
854
|
+
}
|
|
855
|
+
return undefined;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function resolveProviderUsageTotal(reports: UsageReport[]): number {
|
|
859
|
+
return reports
|
|
860
|
+
.flatMap(report => report.limits)
|
|
861
|
+
.map(limit => resolveFraction(limit) ?? 0)
|
|
862
|
+
.reduce((sum, value) => sum + value, 0);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function formatLimitTitle(limit: UsageLimit): string {
|
|
866
|
+
const tier = limit.scope.tier;
|
|
867
|
+
if (tier && !limit.label.toLowerCase().includes(tier.toLowerCase())) {
|
|
868
|
+
return `${limit.label} (${tier})`;
|
|
869
|
+
}
|
|
870
|
+
return limit.label;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function formatWindowSuffix(label: string, windowLabel: string, uiTheme: typeof theme): string {
|
|
874
|
+
const normalizedLabel = label.toLowerCase();
|
|
875
|
+
const normalizedWindow = windowLabel.toLowerCase();
|
|
876
|
+
if (normalizedWindow === "quota window") return "";
|
|
877
|
+
if (normalizedLabel.includes(normalizedWindow)) return "";
|
|
878
|
+
return uiTheme.fg("dim", `(${windowLabel})`);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function formatAccountLabel(limit: UsageLimit, report: UsageReport, index: number): string {
|
|
882
|
+
const email = (report.metadata?.email as string | undefined) ?? limit.scope.accountId;
|
|
883
|
+
if (email) return email;
|
|
884
|
+
const accountId = (report.metadata?.accountId as string | undefined) ?? limit.scope.accountId;
|
|
885
|
+
if (accountId) return accountId;
|
|
886
|
+
return `account ${index + 1}`;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function formatResetShort(limit: UsageLimit, nowMs: number): string | undefined {
|
|
890
|
+
if (limit.window?.resetInMs !== undefined) {
|
|
891
|
+
return formatDurationShort(limit.window.resetInMs);
|
|
892
|
+
}
|
|
893
|
+
if (limit.window?.resetsAt !== undefined) {
|
|
894
|
+
return formatDurationShort(limit.window.resetsAt - nowMs);
|
|
895
|
+
}
|
|
896
|
+
return undefined;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function formatAccountHeader(limit: UsageLimit, report: UsageReport, index: number, nowMs: number): string {
|
|
900
|
+
const label = formatAccountLabel(limit, report, index);
|
|
901
|
+
const reset = formatResetShort(limit, nowMs);
|
|
902
|
+
if (!reset) return label;
|
|
903
|
+
return `${label} (${reset})`;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function padColumn(text: string, width: number): string {
|
|
907
|
+
const visible = visibleWidth(text);
|
|
908
|
+
if (visible >= width) return text;
|
|
909
|
+
return `${text}${padding(width - visible)}`;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function resolveAggregateStatus(limits: UsageLimit[]): UsageLimit["status"] {
|
|
913
|
+
const hasOk = limits.some(limit => limit.status === "ok");
|
|
914
|
+
const hasWarning = limits.some(limit => limit.status === "warning");
|
|
915
|
+
const hasExhausted = limits.some(limit => limit.status === "exhausted");
|
|
916
|
+
if (!hasOk && !hasWarning && !hasExhausted) return "unknown";
|
|
917
|
+
if (hasOk) {
|
|
918
|
+
return hasWarning || hasExhausted ? "warning" : "ok";
|
|
919
|
+
}
|
|
920
|
+
if (hasWarning) return "warning";
|
|
921
|
+
return "exhausted";
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function isZeroUsage(limit: UsageLimit): boolean {
|
|
925
|
+
const amount = limit.amount;
|
|
926
|
+
if (amount.usedFraction !== undefined) return amount.usedFraction <= 0;
|
|
927
|
+
if (amount.used !== undefined) return amount.used <= 0;
|
|
928
|
+
if (amount.unit === "percent" && amount.used !== undefined) return amount.used <= 0;
|
|
929
|
+
if (amount.remainingFraction !== undefined) return amount.remainingFraction >= 1;
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function isZeroUsageGroup(limits: UsageLimit[]): boolean {
|
|
934
|
+
return limits.length > 0 && limits.every(limit => isZeroUsage(limit));
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function formatAggregateAmount(limits: UsageLimit[]): string {
|
|
938
|
+
const fractions = limits
|
|
939
|
+
.map(limit => resolveFraction(limit))
|
|
940
|
+
.filter((value): value is number => value !== undefined);
|
|
941
|
+
if (fractions.length === limits.length && fractions.length > 0) {
|
|
942
|
+
const sum = fractions.reduce((total, value) => total + value, 0);
|
|
943
|
+
const usedPct = Math.max(sum * 100, 0);
|
|
944
|
+
const remainingPct = Math.max(0, limits.length * 100 - usedPct);
|
|
945
|
+
const avgRemaining = limits.length > 0 ? remainingPct / limits.length : remainingPct;
|
|
946
|
+
return `${formatUsedAccounts(sum)} (${formatNumber(avgRemaining)}% left)`;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const amounts = limits
|
|
950
|
+
.map(limit => limit.amount)
|
|
951
|
+
.filter(amount => amount.used !== undefined && amount.limit !== undefined && amount.limit > 0);
|
|
952
|
+
if (amounts.length === limits.length && amounts.length > 0) {
|
|
953
|
+
const totalUsed = amounts.reduce((sum, amount) => sum + (amount.used ?? 0), 0);
|
|
954
|
+
const totalLimit = amounts.reduce((sum, amount) => sum + (amount.limit ?? 0), 0);
|
|
955
|
+
const usedPct = totalLimit > 0 ? (totalUsed / totalLimit) * 100 : 0;
|
|
956
|
+
const remainingPct = Math.max(0, 100 - usedPct);
|
|
957
|
+
const usedAccounts = totalLimit > 0 ? (usedPct / 100) * limits.length : 0;
|
|
958
|
+
return `${formatUsedAccounts(usedAccounts)} (${formatNumber(remainingPct)}% left)`;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
return `Accounts: ${limits.length}`;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function resolveResetRange(limits: UsageLimit[], nowMs: number): string | null {
|
|
965
|
+
const resets = limits
|
|
966
|
+
.map(limit => limit.window?.resetInMs ?? undefined)
|
|
967
|
+
.filter((value): value is number => value !== undefined && Number.isFinite(value) && value > 0);
|
|
968
|
+
if (resets.length === 0) {
|
|
969
|
+
const absolute = limits
|
|
970
|
+
.map(limit => limit.window?.resetsAt)
|
|
971
|
+
.filter((value): value is number => value !== undefined && Number.isFinite(value) && value > nowMs);
|
|
972
|
+
if (absolute.length === 0) return null;
|
|
973
|
+
const earliest = Math.min(...absolute);
|
|
974
|
+
return `resets at ${new Date(earliest).toLocaleString()}`;
|
|
975
|
+
}
|
|
976
|
+
const minReset = Math.min(...resets);
|
|
977
|
+
const maxReset = Math.max(...resets);
|
|
978
|
+
if (maxReset - minReset > 60_000) {
|
|
979
|
+
return `resets in ${formatDuration(minReset)}–${formatDuration(maxReset)}`;
|
|
980
|
+
}
|
|
981
|
+
return `resets in ${formatDuration(minReset)}`;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function resolveStatusIcon(status: UsageLimit["status"], uiTheme: typeof theme): string {
|
|
985
|
+
if (status === "exhausted") return uiTheme.fg("error", uiTheme.status.error);
|
|
986
|
+
if (status === "warning") return uiTheme.fg("warning", uiTheme.status.warning);
|
|
987
|
+
if (status === "ok") return uiTheme.fg("success", uiTheme.status.success);
|
|
988
|
+
return uiTheme.fg("dim", uiTheme.status.pending);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function resolveStatusColor(status: UsageLimit["status"]): "success" | "warning" | "error" | "dim" {
|
|
992
|
+
if (status === "exhausted") return "error";
|
|
993
|
+
if (status === "warning") return "warning";
|
|
994
|
+
if (status === "ok") return "success";
|
|
995
|
+
return "dim";
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function renderUsageBar(limit: UsageLimit, uiTheme: typeof theme): string {
|
|
999
|
+
const fraction = resolveFraction(limit);
|
|
1000
|
+
if (fraction === undefined) {
|
|
1001
|
+
return uiTheme.fg("dim", `[${"·".repeat(BAR_WIDTH)}]`);
|
|
1002
|
+
}
|
|
1003
|
+
const clamped = Math.min(Math.max(fraction, 0), 1);
|
|
1004
|
+
const filled = Math.round(clamped * BAR_WIDTH);
|
|
1005
|
+
const filledBar = "█".repeat(filled);
|
|
1006
|
+
const emptyBar = "░".repeat(Math.max(0, BAR_WIDTH - filled));
|
|
1007
|
+
const color = resolveStatusColor(limit.status);
|
|
1008
|
+
return `${uiTheme.fg("dim", "[")}${uiTheme.fg(color, filledBar)}${uiTheme.fg("dim", emptyBar)}${uiTheme.fg("dim", "]")}`;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function renderUsageReports(reports: UsageReport[], uiTheme: typeof theme, nowMs: number): string {
|
|
1012
|
+
const lines: string[] = [];
|
|
1013
|
+
const latestFetchedAt = Math.max(...reports.map(report => report.fetchedAt ?? 0));
|
|
1014
|
+
const headerSuffix = latestFetchedAt ? ` (${formatDuration(nowMs - latestFetchedAt)} ago)` : "";
|
|
1015
|
+
lines.push(uiTheme.bold(uiTheme.fg("accent", `Usage${headerSuffix}`)));
|
|
1016
|
+
const grouped = new Map<string, UsageReport[]>();
|
|
1017
|
+
for (const report of reports) {
|
|
1018
|
+
const list = grouped.get(report.provider) ?? [];
|
|
1019
|
+
list.push(report);
|
|
1020
|
+
grouped.set(report.provider, list);
|
|
1021
|
+
}
|
|
1022
|
+
const providerEntries = Array.from(grouped.entries())
|
|
1023
|
+
.map(([provider, providerReports]) => ({
|
|
1024
|
+
provider,
|
|
1025
|
+
providerReports,
|
|
1026
|
+
totalUsage: resolveProviderUsageTotal(providerReports),
|
|
1027
|
+
}))
|
|
1028
|
+
.sort((a, b) => {
|
|
1029
|
+
if (a.totalUsage !== b.totalUsage) return a.totalUsage - b.totalUsage;
|
|
1030
|
+
return a.provider.localeCompare(b.provider);
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
for (const { provider, providerReports } of providerEntries) {
|
|
1034
|
+
lines.push("");
|
|
1035
|
+
const providerName = formatProviderName(provider);
|
|
1036
|
+
|
|
1037
|
+
const limitGroups = new Map<
|
|
1038
|
+
string,
|
|
1039
|
+
{ label: string; windowLabel: string; limits: UsageLimit[]; reports: UsageReport[] }
|
|
1040
|
+
>();
|
|
1041
|
+
for (const report of providerReports) {
|
|
1042
|
+
for (const limit of report.limits) {
|
|
1043
|
+
const windowId = limit.window?.id ?? limit.scope.windowId ?? "default";
|
|
1044
|
+
const key = `${formatLimitTitle(limit)}|${windowId}`;
|
|
1045
|
+
const windowLabel = limit.window?.label ?? windowId;
|
|
1046
|
+
const entry = limitGroups.get(key) ?? {
|
|
1047
|
+
label: formatLimitTitle(limit),
|
|
1048
|
+
windowLabel,
|
|
1049
|
+
limits: [],
|
|
1050
|
+
reports: [],
|
|
1051
|
+
};
|
|
1052
|
+
entry.limits.push(limit);
|
|
1053
|
+
entry.reports.push(report);
|
|
1054
|
+
limitGroups.set(key, entry);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const providerAllZero = isZeroUsageGroup(Array.from(limitGroups.values()).flatMap(group => group.limits));
|
|
1059
|
+
if (providerAllZero) {
|
|
1060
|
+
const providerTitle = `${resolveStatusIcon("ok", uiTheme)} ${uiTheme.fg("accent", `${providerName} (0%)`)}`;
|
|
1061
|
+
lines.push(uiTheme.bold(providerTitle));
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
lines.push(uiTheme.bold(uiTheme.fg("accent", providerName)));
|
|
1066
|
+
|
|
1067
|
+
for (const group of limitGroups.values()) {
|
|
1068
|
+
const entries = group.limits.map((limit, index) => ({
|
|
1069
|
+
limit,
|
|
1070
|
+
report: group.reports[index],
|
|
1071
|
+
fraction: resolveFraction(limit),
|
|
1072
|
+
index,
|
|
1073
|
+
}));
|
|
1074
|
+
entries.sort((a, b) => {
|
|
1075
|
+
const aFraction = a.fraction ?? -1;
|
|
1076
|
+
const bFraction = b.fraction ?? -1;
|
|
1077
|
+
if (aFraction !== bFraction) return bFraction - aFraction;
|
|
1078
|
+
return a.index - b.index;
|
|
1079
|
+
});
|
|
1080
|
+
const sortedLimits = entries.map(entry => entry.limit);
|
|
1081
|
+
const sortedReports = entries.map(entry => entry.report);
|
|
1082
|
+
|
|
1083
|
+
const status = resolveAggregateStatus(sortedLimits);
|
|
1084
|
+
const statusIcon = resolveStatusIcon(status, uiTheme);
|
|
1085
|
+
if (isZeroUsageGroup(sortedLimits)) {
|
|
1086
|
+
const resetText = resolveResetRange(sortedLimits, nowMs);
|
|
1087
|
+
const resetSuffix = resetText ? ` | ${resetText}` : "";
|
|
1088
|
+
const windowSuffix = formatWindowSuffix(group.label, group.windowLabel, uiTheme);
|
|
1089
|
+
lines.push(
|
|
1090
|
+
`${statusIcon} ${uiTheme.bold(group.label)} ${windowSuffix} ${uiTheme.fg(
|
|
1091
|
+
"dim",
|
|
1092
|
+
`0%${resetSuffix}`,
|
|
1093
|
+
)}`.trim(),
|
|
1094
|
+
);
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const windowSuffix = formatWindowSuffix(group.label, group.windowLabel, uiTheme);
|
|
1099
|
+
lines.push(`${statusIcon} ${uiTheme.bold(group.label)} ${windowSuffix}`.trim());
|
|
1100
|
+
const accountLabels = sortedLimits.map((limit, index) =>
|
|
1101
|
+
padColumn(formatAccountHeader(limit, sortedReports[index], index, nowMs), COLUMN_WIDTH),
|
|
1102
|
+
);
|
|
1103
|
+
lines.push(` ${accountLabels.join(" ")}`.trimEnd());
|
|
1104
|
+
const bars = sortedLimits.map(limit => padColumn(renderUsageBar(limit, uiTheme), COLUMN_WIDTH));
|
|
1105
|
+
lines.push(` ${bars.join(" ")} ${formatAggregateAmount(sortedLimits)}`.trimEnd());
|
|
1106
|
+
const resetText = sortedLimits.length <= 1 ? resolveResetRange(sortedLimits, nowMs) : null;
|
|
1107
|
+
if (resetText) {
|
|
1108
|
+
lines.push(` ${uiTheme.fg("dim", resetText)}`.trimEnd());
|
|
1109
|
+
}
|
|
1110
|
+
const notes = sortedLimits.flatMap(limit => limit.notes ?? []);
|
|
1111
|
+
if (notes.length > 0) {
|
|
1112
|
+
lines.push(` ${uiTheme.fg("dim", notes.join(" • "))}`.trimEnd());
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// No per-provider footer; global header shows last check.
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
return lines.join("\n");
|
|
1120
|
+
}
|