@oh-my-pi/pi-coding-agent 6.9.69 → 8.0.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 +219 -51
- package/README.md +1 -1
- package/docs/hooks.md +2 -2
- package/docs/sdk.md +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/package.json +10 -10
- package/scripts/format-prompts.ts +143 -0
- package/scripts/generate-template.ts +1 -1
- package/src/cli/args.ts +3 -3
- package/src/cli/config-cli.ts +4 -4
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +2 -2
- package/src/cli/plugin-cli.ts +3 -3
- package/src/cli/session-picker.ts +2 -2
- package/src/cli/setup-cli.ts +2 -2
- package/src/cli/stats-cli.ts +1 -1
- package/src/cli/update-cli.ts +2 -2
- package/src/{core → config}/keybindings.ts +1 -1
- package/src/{core → config}/model-registry.ts +8 -1
- package/src/{core → config}/model-resolver.ts +3 -3
- package/src/{core → config}/prompt-templates.ts +2 -2
- package/src/{core → config}/settings-manager.ts +6 -6
- package/src/{core/cursor/exec-bridge.ts → cursor.ts} +4 -4
- package/src/discovery/agents-md.ts +4 -4
- package/src/discovery/builtin.ts +17 -17
- package/src/discovery/claude.ts +12 -12
- package/src/discovery/cline.ts +6 -6
- package/src/discovery/codex.ts +21 -21
- package/src/discovery/cursor.ts +9 -9
- package/src/discovery/gemini.ts +9 -9
- package/src/discovery/github.ts +6 -6
- package/src/discovery/helpers.ts +4 -4
- package/src/discovery/index.ts +16 -16
- package/src/discovery/mcp-json.ts +4 -4
- package/src/discovery/ssh.ts +4 -4
- package/src/discovery/vscode.ts +4 -4
- package/src/discovery/windsurf.ts +6 -6
- package/src/{core/tools/exa → exa}/company.ts +2 -3
- package/src/{core/tools/exa → exa}/index.ts +2 -2
- package/src/{core/tools/exa → exa}/linkedin.ts +2 -3
- package/src/{core/tools/exa → exa}/mcp-client.ts +2 -2
- package/src/{core/tools/exa → exa}/render.ts +3 -3
- package/src/{core/tools/exa → exa}/researcher.ts +1 -1
- package/src/{core/tools/exa → exa}/search.ts +2 -10
- package/src/{core/tools/exa → exa}/websets.ts +1 -1
- package/src/{core → exec}/bash-executor.ts +23 -7
- package/src/{core → export}/custom-share.ts +1 -1
- package/src/{core/export-html → export/html}/index.ts +3 -3
- package/src/{core → export}/ttsr.ts +2 -2
- package/src/{core → extensibility}/custom-commands/bundled/review/index.ts +4 -4
- package/src/{core → extensibility}/custom-commands/loader.ts +3 -3
- package/src/{core → extensibility}/custom-commands/types.ts +1 -1
- package/src/{core → extensibility}/custom-tools/loader.ts +9 -9
- package/src/{core → extensibility}/custom-tools/types.ts +6 -6
- package/src/{core → extensibility}/custom-tools/wrapper.ts +1 -1
- package/src/{core → extensibility}/extensions/loader.ts +8 -8
- package/src/{core → extensibility}/extensions/runner.ts +3 -3
- package/src/{core → extensibility}/extensions/types.ts +15 -15
- package/src/{core → extensibility}/extensions/wrapper.ts +1 -1
- package/src/{core → extensibility}/hooks/index.ts +1 -1
- package/src/{core → extensibility}/hooks/loader.ts +7 -7
- package/src/{core → extensibility}/hooks/runner.ts +4 -4
- package/src/{core → extensibility}/hooks/types.ts +9 -9
- package/src/{core → extensibility}/plugins/doctor.ts +1 -1
- package/src/{core → extensibility}/plugins/installer.ts +2 -3
- package/src/{core → extensibility}/plugins/paths.ts +1 -1
- package/src/{core → extensibility}/skills.ts +8 -48
- package/src/{core → extensibility}/slash-commands.ts +6 -6
- package/src/index.ts +127 -128
- package/src/internal-urls/agent-protocol.ts +126 -0
- package/src/internal-urls/artifact-protocol.ts +93 -0
- package/src/internal-urls/index.ts +28 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/router.ts +69 -0
- package/src/internal-urls/rule-protocol.ts +56 -0
- package/src/internal-urls/skill-protocol.ts +112 -0
- package/src/internal-urls/types.ts +48 -0
- package/src/{core/python-executor.ts → ipy/executor.ts} +74 -13
- package/src/{core/python-gateway-coordinator.ts → ipy/gateway-coordinator.ts} +40 -270
- package/src/{core/python-kernel.ts → ipy/kernel.ts} +38 -10
- package/src/{core/python-prelude.py → ipy/prelude.py} +201 -8
- package/src/ipy/prelude.ts +3 -0
- package/src/{core/tools/lsp → lsp}/client.ts +7 -6
- package/src/{core/tools/lsp → lsp}/clients/biome-client.ts +1 -1
- package/src/{core/tools/lsp → lsp}/clients/index.ts +1 -1
- package/src/{core/tools/lsp → lsp}/clients/lsp-linter-client.ts +4 -4
- package/src/{core/tools/lsp → lsp}/config.ts +1 -1
- package/src/{core/tools/lsp → lsp}/index.ts +29 -17
- package/src/{core/tools/lsp → lsp}/render.ts +2 -2
- package/src/{core/tools/lsp → lsp}/types.ts +14 -16
- package/src/{core/tools/lsp → lsp}/utils.ts +1 -1
- package/src/main.ts +12 -12
- package/src/{core/mcp → mcp}/config.ts +8 -8
- package/src/{core/mcp → mcp}/loader.ts +5 -6
- package/src/{core/mcp → mcp}/manager.ts +2 -2
- package/src/{core/mcp → mcp}/tool-bridge.ts +35 -6
- package/src/{core/mcp → mcp}/tool-cache.ts +1 -1
- package/src/{core/mcp → mcp}/transports/http.ts +7 -1
- package/src/{core/mcp → mcp}/transports/stdio.ts +1 -1
- package/src/{core/mcp → mcp}/types.ts +1 -1
- package/src/migrations.ts +2 -2
- package/src/modes/{interactive/components → components}/armin.ts +1 -1
- package/src/modes/{interactive/components → components}/assistant-message.ts +1 -1
- package/src/modes/{interactive/components → components}/bash-execution.ts +37 -29
- package/src/modes/{interactive/components → components}/bordered-loader.ts +1 -1
- package/src/modes/{interactive/components → components}/branch-summary-message.ts +2 -2
- package/src/modes/{interactive/components → components}/compaction-summary-message.ts +2 -2
- package/src/modes/{interactive/components → components}/custom-message.ts +3 -3
- package/src/modes/{interactive/components → components}/diff.ts +1 -1
- package/src/modes/{interactive/components → components}/dynamic-border.ts +1 -1
- package/src/modes/{interactive/components → components}/extensions/extension-dashboard.ts +3 -3
- package/src/modes/{interactive/components → components}/extensions/extension-list.ts +2 -2
- package/src/modes/{interactive/components → components}/extensions/inspector-panel.ts +1 -1
- package/src/modes/{interactive/components → components}/extensions/state-manager.ts +11 -17
- package/src/modes/{interactive/components → components}/extensions/types.ts +1 -1
- package/src/modes/{interactive/components → components}/footer.ts +3 -3
- package/src/modes/{interactive/components → components}/history-search.ts +2 -2
- package/src/modes/{interactive/components → components}/hook-editor.ts +1 -1
- package/src/modes/{interactive/components → components}/hook-input.ts +1 -1
- package/src/modes/{interactive/components → components}/hook-message.ts +3 -3
- package/src/modes/{interactive/components → components}/hook-selector.ts +1 -1
- package/src/modes/{interactive/components → components}/keybinding-hints.ts +2 -2
- package/src/modes/{interactive/components → components}/login-dialog.ts +1 -1
- package/src/modes/{interactive/components → components}/model-selector.ts +5 -5
- package/src/modes/{interactive/components → components}/oauth-selector.ts +2 -2
- package/src/modes/{interactive/components → components}/plugin-settings.ts +3 -3
- package/src/modes/{interactive/components → components}/python-execution.ts +35 -24
- package/src/modes/{interactive/components → components}/queue-mode-selector.ts +1 -1
- package/src/modes/{interactive/components → components}/read-tool-group.ts +2 -2
- package/src/modes/{interactive/components → components}/session-selector.ts +3 -3
- package/src/modes/{interactive/components → components}/settings-defs.ts +2 -2
- package/src/modes/{interactive/components → components}/settings-selector.ts +2 -2
- package/src/modes/{interactive/components → components}/show-images-selector.ts +1 -1
- package/src/modes/{interactive/components → components}/status-line/segments.ts +2 -2
- package/src/modes/{interactive/components → components}/status-line/separators.ts +1 -1
- package/src/modes/{interactive/components → components}/status-line/types.ts +2 -2
- package/src/modes/{interactive/components → components}/status-line-segment-editor.ts +2 -2
- package/src/modes/{interactive/components → components}/status-line.ts +3 -3
- package/src/modes/{interactive/components → components}/theme-selector.ts +1 -1
- package/src/modes/{interactive/components → components}/thinking-selector.ts +1 -1
- package/src/modes/{interactive/components → components}/todo-display.ts +3 -4
- package/src/modes/{interactive/components → components}/todo-reminder.ts +2 -2
- package/src/modes/{interactive/components → components}/tool-execution.ts +8 -8
- package/src/modes/{interactive/components → components}/tree-selector.ts +3 -3
- package/src/modes/{interactive/components → components}/ttsr-notification.ts +2 -2
- package/src/modes/{interactive/components → components}/user-message-selector.ts +1 -1
- package/src/modes/{interactive/components → components}/user-message.ts +1 -1
- package/src/modes/{interactive/components → components}/welcome.ts +2 -2
- package/src/modes/{interactive/controllers → controllers}/command-controller.ts +381 -30
- package/src/modes/{interactive/controllers → controllers}/event-controller.ts +9 -9
- package/src/modes/{interactive/controllers → controllers}/extension-ui-controller.ts +8 -8
- package/src/modes/{interactive/controllers → controllers}/input-controller.ts +61 -13
- package/src/modes/{interactive/controllers → controllers}/selector-controller.ts +16 -16
- package/src/modes/index.ts +1 -1
- package/src/modes/{interactive/interactive-mode.ts → interactive-mode.ts} +20 -15
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +3 -3
- package/src/modes/rpc/rpc-mode.ts +3 -3
- package/src/modes/rpc/rpc-types.ts +3 -3
- package/src/modes/{interactive/theme → theme}/theme.ts +1 -1
- package/src/modes/{interactive/types.ts → types.ts} +10 -10
- package/src/modes/{interactive/utils → utils}/ui-helpers.ts +20 -27
- package/src/{core/tools/patch → patch}/applicator.ts +1 -1
- package/src/{core/tools/patch → patch}/diff.ts +1 -1
- package/src/{core/tools/patch → patch}/index.ts +31 -36
- package/src/{core/tools/patch → patch}/shared.ts +9 -6
- package/src/prompts/agents/explore.md +83 -46
- package/src/prompts/agents/init.md +9 -4
- package/src/prompts/agents/plan.md +8 -7
- package/src/prompts/agents/reviewer.md +36 -18
- package/src/prompts/agents/task.md +4 -4
- package/src/prompts/compaction/branch-summary-preamble.md +0 -1
- package/src/prompts/review-request.md +0 -1
- package/src/prompts/system/custom-system-prompt.md +2 -14
- package/src/prompts/system/file-operations.md +0 -2
- package/src/prompts/system/system-prompt.md +182 -171
- package/src/prompts/system/web-search.md +26 -0
- package/src/prompts/tools/ask.md +31 -24
- package/src/prompts/tools/bash.md +20 -17
- package/src/prompts/tools/calculator.md +9 -5
- package/src/prompts/tools/fetch.md +16 -0
- package/src/prompts/tools/find.md +15 -5
- package/src/prompts/tools/gemini-image.md +21 -6
- package/src/prompts/tools/grep.md +28 -12
- package/src/prompts/tools/lsp.md +35 -14
- package/src/prompts/tools/patch.md +39 -41
- package/src/prompts/tools/python.md +59 -77
- package/src/prompts/tools/read.md +23 -22
- package/src/prompts/tools/replace.md +19 -12
- package/src/prompts/tools/ssh.md +21 -28
- package/src/prompts/tools/task.md +54 -44
- package/src/prompts/tools/todo-write.md +52 -163
- package/src/prompts/tools/web-search.md +16 -9
- package/src/prompts/tools/write.md +13 -2
- package/src/{core/sdk.ts → sdk.ts} +65 -34
- package/src/{core → session}/agent-session.ts +157 -41
- package/src/{core → session}/agent-storage.ts +2 -2
- package/src/session/artifacts.ts +110 -0
- package/src/{core → session}/auth-storage.ts +525 -203
- package/src/{core → session}/compaction/branch-summarization.ts +5 -5
- package/src/{core → session}/compaction/compaction.ts +6 -6
- package/src/{core → session}/compaction/utils.ts +3 -3
- package/src/{core → session}/history-storage.ts +1 -1
- package/src/{core → session}/messages.ts +6 -8
- package/src/{core → session}/session-manager.ts +2 -2
- package/src/{core → session}/storage-migration.ts +2 -2
- package/src/session/streaming-output.ts +177 -0
- package/src/{core/ssh → ssh}/connection-manager.ts +1 -1
- package/src/{core/ssh → ssh}/ssh-executor.ts +19 -4
- package/src/{core/ssh → ssh}/sshfs-mount.ts +1 -1
- package/src/{core/system-prompt.ts → system-prompt.ts} +8 -37
- package/src/{core/tools/task → task}/agents.ts +8 -8
- package/src/{core/tools/task → task}/commands.ts +5 -6
- package/src/{core/tools/task → task}/discovery.ts +3 -3
- package/src/{core/tools/task → task}/executor.ts +34 -44
- package/src/{core/tools/task → task}/index.ts +206 -50
- package/src/{core/tools/task → task}/render.ts +80 -23
- package/src/{core/tools/task → task}/subprocess-tool-registry.ts +1 -1
- package/src/task/template.ts +47 -0
- package/src/{core/tools/task → task}/types.ts +19 -27
- package/src/{core/tools/task → task}/worker-protocol.ts +8 -4
- package/src/{core/tools/task → task}/worker.ts +34 -29
- package/src/task/worktree.ts +166 -0
- package/src/{core/tools → tools}/ask.ts +13 -21
- package/src/{core/tools → tools}/bash-interceptor.ts +1 -1
- package/src/{core/tools → tools}/bash.ts +61 -63
- package/src/{core/tools → tools}/calculator.ts +4 -4
- package/src/{core/tools → tools}/complete.ts +1 -1
- package/src/{core/tools → tools}/context.ts +2 -2
- package/src/{core/tools/web-fetch.ts → tools/fetch.ts} +97 -76
- package/src/{core/tools → tools}/find.ts +96 -107
- package/src/{core/tools → tools}/gemini-image.ts +420 -29
- package/src/{core/tools → tools}/grep.ts +155 -164
- package/src/{core/tools → tools}/index.ts +63 -56
- package/src/tools/list-limit.ts +40 -0
- package/src/{core/tools → tools}/ls.ts +44 -35
- package/src/{core/tools → tools}/notebook.ts +3 -3
- package/src/tools/output-meta.ts +443 -0
- package/src/tools/output-utils.ts +63 -0
- package/src/{core/tools → tools}/python.ts +106 -89
- package/src/tools/read.ts +882 -0
- package/src/{core/tools → tools}/render-utils.ts +1 -1
- package/src/{core/tools → tools}/renderers.ts +8 -10
- package/src/{core/tools → tools}/review.ts +2 -2
- package/src/{core/tools → tools}/ssh.ts +56 -59
- package/src/{core/tools → tools}/todo-write.ts +12 -23
- package/src/tools/tool-errors.ts +95 -0
- package/src/tools/tool-result.ts +92 -0
- package/src/{core/tools → tools}/truncate.ts +2 -2
- package/src/{core/tools → tools}/write.ts +15 -13
- package/src/utils/changelog.ts +1 -1
- package/src/{core → utils}/file-mentions.ts +4 -4
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +1 -1
- package/src/utils/shell.ts +1 -1
- package/src/{core → utils}/title-generator.ts +4 -4
- package/src/utils/tools-manager.ts +1 -1
- package/src/{core/tools/web-scrapers → web/scrapers}/choosealicense.ts +1 -1
- package/src/{core/tools/web-scrapers → web/scrapers}/twitter.ts +3 -2
- package/src/{core/tools/web-scrapers → web/scrapers}/types.ts +4 -2
- package/src/{core/tools/web-scrapers → web/scrapers}/utils.ts +1 -1
- package/src/{core/tools/web-scrapers → web/scrapers}/youtube.ts +14 -13
- package/src/{core/tools/web-search → web/search}/auth.ts +4 -4
- package/src/{core/tools/web-search → web/search}/index.ts +22 -71
- package/src/{core/tools/web-search → web/search}/providers/anthropic.ts +7 -10
- package/src/{core/tools/web-search → web/search}/providers/exa.ts +2 -2
- package/src/{core/tools/web-search → web/search}/providers/perplexity.ts +4 -16
- package/src/{core/tools/web-search → web/search}/render.ts +3 -3
- package/scripts/migrate-sessions.sh +0 -93
- package/src/core/index.ts +0 -56
- package/src/core/python-prelude.ts +0 -3
- package/src/core/ssh-executor.ts +0 -5
- package/src/core/streaming-output.ts +0 -115
- package/src/core/tools/output.ts +0 -519
- package/src/core/tools/read.ts +0 -717
- package/src/core/tools/task/template.ts +0 -37
- package/src/prompts/tools/output.md +0 -47
- package/src/prompts/tools/web-fetch.md +0 -9
- /package/src/{core/tools/exa → exa}/types.ts +0 -0
- /package/src/{core → exec}/exec.ts +0 -0
- /package/src/{core/export-html → export/html}/template.css +0 -0
- /package/src/{core/export-html → export/html}/template.generated.ts +0 -0
- /package/src/{core/export-html → export/html}/template.html +0 -0
- /package/src/{core/export-html → export/html}/template.js +0 -0
- /package/src/{core/export-html → export/html}/template.macro.ts +0 -0
- /package/src/{core/export-html → export/html}/vendor/highlight.min.js +0 -0
- /package/src/{core/export-html → export/html}/vendor/marked.min.js +0 -0
- /package/src/{core → extensibility}/custom-commands/index.ts +0 -0
- /package/src/{core → extensibility}/custom-tools/index.ts +0 -0
- /package/src/{core → extensibility}/extensions/index.ts +0 -0
- /package/src/{core → extensibility}/hooks/tool-wrapper.ts +0 -0
- /package/src/{core → extensibility}/plugins/index.ts +0 -0
- /package/src/{core → extensibility}/plugins/loader.ts +0 -0
- /package/src/{core → extensibility}/plugins/manager.ts +0 -0
- /package/src/{core → extensibility}/plugins/parser.ts +0 -0
- /package/src/{core → extensibility}/plugins/types.ts +0 -0
- /package/src/{core/python-modules.ts → ipy/modules.ts} +0 -0
- /package/src/{core/tools/lsp → lsp}/defaults.json +0 -0
- /package/src/{core/tools/lsp → lsp}/edits.ts +0 -0
- /package/src/{core/tools/lsp → lsp}/lspmux.ts +0 -0
- /package/src/{core/tools/lsp → lsp}/rust-analyzer.ts +0 -0
- /package/src/{core/mcp → mcp}/client.ts +0 -0
- /package/src/{core/mcp → mcp}/index.ts +0 -0
- /package/src/{core/mcp → mcp}/json-rpc.ts +0 -0
- /package/src/{core/mcp → mcp}/transports/index.ts +0 -0
- /package/src/modes/{interactive/components → components}/countdown-timer.ts +0 -0
- /package/src/modes/{interactive/components → components}/custom-editor.ts +0 -0
- /package/src/modes/{interactive/components → components}/extensions/index.ts +0 -0
- /package/src/modes/{interactive/components → components}/index.ts +0 -0
- /package/src/modes/{interactive/components → components}/status-line/index.ts +0 -0
- /package/src/modes/{interactive/components → components}/status-line/presets.ts +0 -0
- /package/src/modes/{interactive/components → components}/visual-truncate.ts +0 -0
- /package/src/modes/{interactive/theme → theme}/dark.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/alabaster.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/amethyst.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/anthracite.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/basalt.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/birch.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-abyss.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-arctic.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-aurora.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-catppuccin.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-cavern.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-copper.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-cosmos.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-cyberpunk.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-dracula.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-eclipse.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-ember.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-equinox.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-forest.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-github.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-gruvbox.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-lavender.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-lunar.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-midnight.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-monochrome.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-monokai.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-nebula.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-nord.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-ocean.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-one.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-rainforest.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-reef.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-retro.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-rose-pine.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-sakura.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-slate.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-solarized.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-solstice.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-starfall.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-sunset.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-swamp.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-synthwave.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-taiga.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-terminal.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-tokyo-night.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-tundra.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-twilight.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/dark-volcanic.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/graphite.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/index.ts +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-arctic.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-aurora-day.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-canyon.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-catppuccin.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-cirrus.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-coral.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-cyberpunk.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-dawn.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-dunes.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-eucalyptus.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-forest.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-frost.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-github.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-glacier.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-gruvbox.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-haze.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-honeycomb.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-lagoon.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-lavender.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-meadow.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-mint.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-monochrome.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-ocean.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-one.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-opal.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-orchard.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-paper.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-prism.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-retro.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-sand.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-savanna.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-solarized.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-soleil.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-sunset.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-synthwave.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-tokyo-night.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-wetland.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/light-zenith.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/limestone.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/mahogany.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/marble.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/obsidian.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/onyx.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/pearl.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/porcelain.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/quartz.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/sandstone.json +0 -0
- /package/src/modes/{interactive/theme → theme}/defaults/titanium.json +0 -0
- /package/src/modes/{interactive/theme → theme}/light.json +0 -0
- /package/src/modes/{interactive/theme → theme}/theme-schema.json +0 -0
- /package/src/{core/tools/patch → patch}/fuzzy.ts +0 -0
- /package/src/{core/tools/patch → patch}/normalize.ts +0 -0
- /package/src/{core/tools/patch → patch}/normative.ts +0 -0
- /package/src/{core/tools/patch → patch}/parser.ts +0 -0
- /package/src/{core/tools/patch → patch}/types.ts +0 -0
- /package/src/{core → session}/compaction/index.ts +0 -0
- /package/src/{core → session}/session-storage.ts +0 -0
- /package/src/{core/tools/task → task}/name-generator.ts +0 -0
- /package/src/{core/tools/task → task}/omp-command.ts +0 -0
- /package/src/{core/tools/task → task}/parallel.ts +0 -0
- /package/src/{core/tools → tools}/jtd-to-json-schema.ts +0 -0
- /package/src/{core/tools → tools}/path-utils.ts +0 -0
- /package/src/{core → utils}/event-bus.ts +0 -0
- /package/src/{core → utils}/frontmatter.ts +0 -0
- /package/src/{core → utils}/terminal-notify.ts +0 -0
- /package/src/{core → utils}/timings.ts +0 -0
- /package/src/{core → utils}/utils.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/artifacthub.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/arxiv.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/aur.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/biorxiv.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/bluesky.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/brew.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/cheatsh.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/chocolatey.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/cisa-kev.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/clojars.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/coingecko.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/crates-io.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/crossref.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/devto.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/discogs.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/discourse.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/dockerhub.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/fdroid.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/firefox-addons.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/flathub.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/github-gist.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/github.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/gitlab.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/go-pkg.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/hackage.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/hackernews.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/hex.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/huggingface.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/iacr.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/index.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/jetbrains-marketplace.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/lemmy.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/lobsters.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/mastodon.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/maven.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/mdn.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/metacpan.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/musicbrainz.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/npm.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/nuget.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/nvd.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/ollama.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/open-vsx.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/opencorporates.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/openlibrary.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/orcid.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/osv.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/packagist.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/pub-dev.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/pubmed.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/pypi.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/rawg.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/readthedocs.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/reddit.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/repology.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/rfc.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/rubygems.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/searchcode.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/sec-edgar.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/semantic-scholar.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/snapcraft.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/sourcegraph.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/spdx.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/spotify.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/stackoverflow.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/terraform.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/tldr.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/vimeo.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/vscode-marketplace.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/w3c.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/wikidata.ts +0 -0
- /package/src/{core/tools/web-scrapers → web/scrapers}/wikipedia.ts +0 -0
- /package/src/{core/tools/web-search → web/search}/types.ts +0 -0
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { mkdir, rm } from "node:fs/promises";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import type { UsageLimit, UsageReport } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import { Loader, Markdown, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
5
6
|
import { $ } from "bun";
|
|
6
7
|
import { nanoid } from "nanoid";
|
|
7
|
-
import { getDebugLogPath } from "
|
|
8
|
-
import { loadCustomShare } from "
|
|
9
|
-
import type { CompactOptions } from "
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
8
|
+
import { getDebugLogPath } from "$c/config";
|
|
9
|
+
import { loadCustomShare } from "$c/export/custom-share";
|
|
10
|
+
import type { CompactOptions } from "$c/extensibility/extensions/types";
|
|
11
|
+
import { getGatewayStatus } from "$c/ipy/gateway-coordinator";
|
|
12
|
+
import { ArminComponent } from "$c/modes/components/armin";
|
|
13
|
+
import { BashExecutionComponent } from "$c/modes/components/bash-execution";
|
|
14
|
+
import { BorderedLoader } from "$c/modes/components/bordered-loader";
|
|
15
|
+
import { DynamicBorder } from "$c/modes/components/dynamic-border";
|
|
16
|
+
import { PythonExecutionComponent } from "$c/modes/components/python-execution";
|
|
17
|
+
import { getMarkdownTheme, getSymbolTheme, theme } from "$c/modes/theme/theme";
|
|
18
|
+
import type { InteractiveModeContext } from "$c/modes/types";
|
|
19
|
+
import { createCompactionSummaryMessage } from "$c/session/messages";
|
|
20
|
+
import { outputMeta } from "$c/tools/output-meta";
|
|
21
|
+
import { getChangelogPath, parseChangelog } from "$c/utils/changelog";
|
|
22
|
+
import { copyToClipboard } from "$c/utils/clipboard";
|
|
22
23
|
|
|
23
24
|
export class CommandController {
|
|
24
25
|
constructor(private readonly ctx: InteractiveModeContext) {}
|
|
@@ -242,11 +243,15 @@ export class CommandController {
|
|
|
242
243
|
const gateway = getGatewayStatus();
|
|
243
244
|
info += `\n${theme.bold("Python Gateway")}\n`;
|
|
244
245
|
if (gateway.active) {
|
|
245
|
-
|
|
246
|
-
info += `${theme.fg("dim", "Status:")} ${theme.fg("success", `Active (${mode})`)}\n`;
|
|
246
|
+
info += `${theme.fg("dim", "Status:")} ${theme.fg("success", "Active (Global)")}\n`;
|
|
247
247
|
info += `${theme.fg("dim", "URL:")} ${gateway.url}\n`;
|
|
248
248
|
info += `${theme.fg("dim", "PID:")} ${gateway.pid}\n`;
|
|
249
|
-
|
|
249
|
+
if (gateway.pythonPath) {
|
|
250
|
+
info += `${theme.fg("dim", "Python:")} ${gateway.pythonPath}\n`;
|
|
251
|
+
}
|
|
252
|
+
if (gateway.venvPath) {
|
|
253
|
+
info += `${theme.fg("dim", "Venv:")} ${gateway.venvPath}\n`;
|
|
254
|
+
}
|
|
250
255
|
if (gateway.uptime !== null) {
|
|
251
256
|
const uptimeSec = Math.floor(gateway.uptime / 1000);
|
|
252
257
|
const mins = Math.floor(uptimeSec / 60);
|
|
@@ -284,6 +289,33 @@ export class CommandController {
|
|
|
284
289
|
this.ctx.ui.requestRender();
|
|
285
290
|
}
|
|
286
291
|
|
|
292
|
+
async handleUsageCommand(reports?: UsageReport[] | null): Promise<void> {
|
|
293
|
+
let usageReports = reports ?? null;
|
|
294
|
+
if (!usageReports) {
|
|
295
|
+
const provider = this.ctx.session as { fetchUsageReports?: () => Promise<UsageReport[] | null> };
|
|
296
|
+
if (!provider.fetchUsageReports) {
|
|
297
|
+
this.ctx.showWarning("Usage reporting is not configured for this session.");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
usageReports = await provider.fetchUsageReports();
|
|
302
|
+
} catch (error) {
|
|
303
|
+
this.ctx.showError(`Failed to fetch usage data: ${error instanceof Error ? error.message : String(error)}`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!usageReports || usageReports.length === 0) {
|
|
309
|
+
this.ctx.showWarning("No usage data available.");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const output = renderUsageReports(usageReports, theme, Date.now());
|
|
314
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
315
|
+
this.ctx.chatContainer.addChild(new Text(output, 1, 0));
|
|
316
|
+
this.ctx.ui.requestRender();
|
|
317
|
+
}
|
|
318
|
+
|
|
287
319
|
handleChangelogCommand(): void {
|
|
288
320
|
const changelogPath = getChangelogPath();
|
|
289
321
|
const allEntries = parseChangelog(changelogPath);
|
|
@@ -456,12 +488,11 @@ export class CommandController {
|
|
|
456
488
|
);
|
|
457
489
|
|
|
458
490
|
if (this.ctx.bashComponent) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
result.
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
);
|
|
491
|
+
const meta = outputMeta().truncationFromSummary(result, { direction: "tail" }).get();
|
|
492
|
+
this.ctx.bashComponent.setComplete(result.exitCode, result.cancelled, {
|
|
493
|
+
output: result.output,
|
|
494
|
+
truncation: meta?.truncation,
|
|
495
|
+
});
|
|
465
496
|
}
|
|
466
497
|
} catch (error) {
|
|
467
498
|
if (this.ctx.bashComponent) {
|
|
@@ -499,12 +530,11 @@ export class CommandController {
|
|
|
499
530
|
);
|
|
500
531
|
|
|
501
532
|
if (this.ctx.pythonComponent) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
result.
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
);
|
|
533
|
+
const meta = outputMeta().truncationFromSummary(result, { direction: "tail" }).get();
|
|
534
|
+
this.ctx.pythonComponent.setComplete(result.exitCode, result.cancelled, {
|
|
535
|
+
output: result.output,
|
|
536
|
+
truncation: meta?.truncation,
|
|
537
|
+
});
|
|
508
538
|
}
|
|
509
539
|
} catch (error) {
|
|
510
540
|
if (this.ctx.pythonComponent) {
|
|
@@ -598,3 +628,324 @@ export class CommandController {
|
|
|
598
628
|
await this.ctx.flushCompactionQueue({ willRetry: false });
|
|
599
629
|
}
|
|
600
630
|
}
|
|
631
|
+
|
|
632
|
+
const BAR_WIDTH = 24;
|
|
633
|
+
const COLUMN_WIDTH = BAR_WIDTH + 2;
|
|
634
|
+
|
|
635
|
+
function formatProviderName(provider: string): string {
|
|
636
|
+
return provider
|
|
637
|
+
.split(/[-_]/g)
|
|
638
|
+
.map((part) => (part ? part[0].toUpperCase() + part.slice(1) : ""))
|
|
639
|
+
.join(" ");
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function formatNumber(value: number, maxFractionDigits = 1): string {
|
|
643
|
+
return new Intl.NumberFormat("en-US", { maximumFractionDigits: maxFractionDigits }).format(value);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function formatUsedAccounts(value: number): string {
|
|
647
|
+
return `${value.toFixed(2)} used`;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function formatDuration(ms: number): string {
|
|
651
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
652
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
653
|
+
const seconds = totalSeconds % 60;
|
|
654
|
+
const hours = Math.floor(minutes / 60);
|
|
655
|
+
const mins = minutes % 60;
|
|
656
|
+
const days = Math.floor(hours / 24);
|
|
657
|
+
const hrs = hours % 24;
|
|
658
|
+
if (days > 0) return `${days}d ${hrs}h`;
|
|
659
|
+
if (hours > 0) return `${hours}h ${mins}m`;
|
|
660
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
661
|
+
return `${seconds}s`;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function formatDurationShort(ms: number): string {
|
|
665
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
666
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
667
|
+
const hours = Math.floor(minutes / 60);
|
|
668
|
+
const mins = minutes % 60;
|
|
669
|
+
const days = Math.floor(hours / 24);
|
|
670
|
+
const hrs = hours % 24;
|
|
671
|
+
if (days > 0) return `${days}d${hrs > 0 ? ` ${hrs}h` : ""}`;
|
|
672
|
+
if (hours > 0) return `${hours}h${mins > 0 ? ` ${mins}m` : ""}`;
|
|
673
|
+
if (minutes > 0) return `${minutes}m`;
|
|
674
|
+
return `${totalSeconds}s`;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function resolveFraction(limit: UsageLimit): number | undefined {
|
|
678
|
+
const amount = limit.amount;
|
|
679
|
+
if (amount.usedFraction !== undefined) return amount.usedFraction;
|
|
680
|
+
if (amount.used !== undefined && amount.limit !== undefined && amount.limit > 0) {
|
|
681
|
+
return amount.used / amount.limit;
|
|
682
|
+
}
|
|
683
|
+
if (amount.unit === "percent" && amount.used !== undefined) {
|
|
684
|
+
return amount.used / 100;
|
|
685
|
+
}
|
|
686
|
+
return undefined;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function resolveProviderUsageTotal(reports: UsageReport[]): number {
|
|
690
|
+
return reports
|
|
691
|
+
.flatMap((report) => report.limits)
|
|
692
|
+
.map((limit) => resolveFraction(limit) ?? 0)
|
|
693
|
+
.reduce((sum, value) => sum + value, 0);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function formatLimitTitle(limit: UsageLimit): string {
|
|
697
|
+
const tier = limit.scope.tier;
|
|
698
|
+
if (tier && !limit.label.toLowerCase().includes(tier.toLowerCase())) {
|
|
699
|
+
return `${limit.label} (${tier})`;
|
|
700
|
+
}
|
|
701
|
+
return limit.label;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function formatWindowSuffix(label: string, windowLabel: string, uiTheme: typeof theme): string {
|
|
705
|
+
const normalizedLabel = label.toLowerCase();
|
|
706
|
+
const normalizedWindow = windowLabel.toLowerCase();
|
|
707
|
+
if (normalizedWindow === "quota window") return "";
|
|
708
|
+
if (normalizedLabel.includes(normalizedWindow)) return "";
|
|
709
|
+
return uiTheme.fg("dim", `(${windowLabel})`);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function formatAccountLabel(limit: UsageLimit, report: UsageReport, index: number): string {
|
|
713
|
+
const email = (report.metadata?.email as string | undefined) ?? limit.scope.accountId;
|
|
714
|
+
if (email) return email;
|
|
715
|
+
const accountId = (report.metadata?.accountId as string | undefined) ?? limit.scope.accountId;
|
|
716
|
+
if (accountId) return accountId;
|
|
717
|
+
return `account ${index + 1}`;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function formatResetShort(limit: UsageLimit, nowMs: number): string | undefined {
|
|
721
|
+
if (limit.window?.resetInMs !== undefined) {
|
|
722
|
+
return formatDurationShort(limit.window.resetInMs);
|
|
723
|
+
}
|
|
724
|
+
if (limit.window?.resetsAt !== undefined) {
|
|
725
|
+
return formatDurationShort(limit.window.resetsAt - nowMs);
|
|
726
|
+
}
|
|
727
|
+
return undefined;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function formatAccountHeader(limit: UsageLimit, report: UsageReport, index: number, nowMs: number): string {
|
|
731
|
+
const label = formatAccountLabel(limit, report, index);
|
|
732
|
+
const reset = formatResetShort(limit, nowMs);
|
|
733
|
+
if (!reset) return label;
|
|
734
|
+
return `${label} (${reset})`;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function padColumn(text: string, width: number): string {
|
|
738
|
+
const visible = visibleWidth(text);
|
|
739
|
+
if (visible >= width) return text;
|
|
740
|
+
return `${text}${" ".repeat(width - visible)}`;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function resolveAggregateStatus(limits: UsageLimit[]): UsageLimit["status"] {
|
|
744
|
+
const hasOk = limits.some((limit) => limit.status === "ok");
|
|
745
|
+
const hasWarning = limits.some((limit) => limit.status === "warning");
|
|
746
|
+
const hasExhausted = limits.some((limit) => limit.status === "exhausted");
|
|
747
|
+
if (!hasOk && !hasWarning && !hasExhausted) return "unknown";
|
|
748
|
+
if (hasOk) {
|
|
749
|
+
return hasWarning || hasExhausted ? "warning" : "ok";
|
|
750
|
+
}
|
|
751
|
+
if (hasWarning) return "warning";
|
|
752
|
+
return "exhausted";
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function isZeroUsage(limit: UsageLimit): boolean {
|
|
756
|
+
const amount = limit.amount;
|
|
757
|
+
if (amount.usedFraction !== undefined) return amount.usedFraction <= 0;
|
|
758
|
+
if (amount.used !== undefined) return amount.used <= 0;
|
|
759
|
+
if (amount.unit === "percent" && amount.used !== undefined) return amount.used <= 0;
|
|
760
|
+
if (amount.remainingFraction !== undefined) return amount.remainingFraction >= 1;
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function isZeroUsageGroup(limits: UsageLimit[]): boolean {
|
|
765
|
+
return limits.length > 0 && limits.every((limit) => isZeroUsage(limit));
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function formatAggregateAmount(limits: UsageLimit[]): string {
|
|
769
|
+
const fractions = limits
|
|
770
|
+
.map((limit) => resolveFraction(limit))
|
|
771
|
+
.filter((value): value is number => value !== undefined);
|
|
772
|
+
if (fractions.length === limits.length && fractions.length > 0) {
|
|
773
|
+
const sum = fractions.reduce((total, value) => total + value, 0);
|
|
774
|
+
const usedPct = Math.max(sum * 100, 0);
|
|
775
|
+
const remainingPct = Math.max(0, limits.length * 100 - usedPct);
|
|
776
|
+
const avgRemaining = limits.length > 0 ? remainingPct / limits.length : remainingPct;
|
|
777
|
+
return `${formatUsedAccounts(sum)} (${formatNumber(avgRemaining)}% left)`;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const amounts = limits
|
|
781
|
+
.map((limit) => limit.amount)
|
|
782
|
+
.filter((amount) => amount.used !== undefined && amount.limit !== undefined && amount.limit > 0);
|
|
783
|
+
if (amounts.length === limits.length && amounts.length > 0) {
|
|
784
|
+
const totalUsed = amounts.reduce((sum, amount) => sum + (amount.used ?? 0), 0);
|
|
785
|
+
const totalLimit = amounts.reduce((sum, amount) => sum + (amount.limit ?? 0), 0);
|
|
786
|
+
const usedPct = totalLimit > 0 ? (totalUsed / totalLimit) * 100 : 0;
|
|
787
|
+
const remainingPct = Math.max(0, 100 - usedPct);
|
|
788
|
+
const usedAccounts = totalLimit > 0 ? (usedPct / 100) * limits.length : 0;
|
|
789
|
+
return `${formatUsedAccounts(usedAccounts)} (${formatNumber(remainingPct)}% left)`;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return `Accounts: ${limits.length}`;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function resolveResetRange(limits: UsageLimit[], nowMs: number): string | null {
|
|
796
|
+
const resets = limits
|
|
797
|
+
.map((limit) => limit.window?.resetInMs ?? undefined)
|
|
798
|
+
.filter((value): value is number => value !== undefined && Number.isFinite(value) && value > 0);
|
|
799
|
+
if (resets.length === 0) {
|
|
800
|
+
const absolute = limits
|
|
801
|
+
.map((limit) => limit.window?.resetsAt)
|
|
802
|
+
.filter((value): value is number => value !== undefined && Number.isFinite(value) && value > nowMs);
|
|
803
|
+
if (absolute.length === 0) return null;
|
|
804
|
+
const earliest = Math.min(...absolute);
|
|
805
|
+
return `resets at ${new Date(earliest).toLocaleString()}`;
|
|
806
|
+
}
|
|
807
|
+
const minReset = Math.min(...resets);
|
|
808
|
+
const maxReset = Math.max(...resets);
|
|
809
|
+
if (maxReset - minReset > 60_000) {
|
|
810
|
+
return `resets in ${formatDuration(minReset)}–${formatDuration(maxReset)}`;
|
|
811
|
+
}
|
|
812
|
+
return `resets in ${formatDuration(minReset)}`;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function resolveStatusIcon(status: UsageLimit["status"], uiTheme: typeof theme): string {
|
|
816
|
+
if (status === "exhausted") return uiTheme.fg("error", uiTheme.status.error);
|
|
817
|
+
if (status === "warning") return uiTheme.fg("warning", uiTheme.status.warning);
|
|
818
|
+
if (status === "ok") return uiTheme.fg("success", uiTheme.status.success);
|
|
819
|
+
return uiTheme.fg("dim", uiTheme.status.pending);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function resolveStatusColor(status: UsageLimit["status"]): "success" | "warning" | "error" | "dim" {
|
|
823
|
+
if (status === "exhausted") return "error";
|
|
824
|
+
if (status === "warning") return "warning";
|
|
825
|
+
if (status === "ok") return "success";
|
|
826
|
+
return "dim";
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function renderUsageBar(limit: UsageLimit, uiTheme: typeof theme): string {
|
|
830
|
+
const fraction = resolveFraction(limit);
|
|
831
|
+
if (fraction === undefined) {
|
|
832
|
+
return uiTheme.fg("dim", `[${"·".repeat(BAR_WIDTH)}]`);
|
|
833
|
+
}
|
|
834
|
+
const clamped = Math.min(Math.max(fraction, 0), 1);
|
|
835
|
+
const filled = Math.round(clamped * BAR_WIDTH);
|
|
836
|
+
const filledBar = "█".repeat(filled);
|
|
837
|
+
const emptyBar = "░".repeat(Math.max(0, BAR_WIDTH - filled));
|
|
838
|
+
const color = resolveStatusColor(limit.status);
|
|
839
|
+
return `${uiTheme.fg("dim", "[")}${uiTheme.fg(color, filledBar)}${uiTheme.fg("dim", emptyBar)}${uiTheme.fg("dim", "]")}`;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function renderUsageReports(reports: UsageReport[], uiTheme: typeof theme, nowMs: number): string {
|
|
843
|
+
const lines: string[] = [];
|
|
844
|
+
const latestFetchedAt = Math.max(...reports.map((report) => report.fetchedAt ?? 0));
|
|
845
|
+
const headerSuffix = latestFetchedAt ? ` (${formatDuration(nowMs - latestFetchedAt)} ago)` : "";
|
|
846
|
+
lines.push(uiTheme.bold(uiTheme.fg("accent", `Usage${headerSuffix}`)));
|
|
847
|
+
const grouped = new Map<string, UsageReport[]>();
|
|
848
|
+
for (const report of reports) {
|
|
849
|
+
const list = grouped.get(report.provider) ?? [];
|
|
850
|
+
list.push(report);
|
|
851
|
+
grouped.set(report.provider, list);
|
|
852
|
+
}
|
|
853
|
+
const providerEntries = Array.from(grouped.entries())
|
|
854
|
+
.map(([provider, providerReports]) => ({
|
|
855
|
+
provider,
|
|
856
|
+
providerReports,
|
|
857
|
+
totalUsage: resolveProviderUsageTotal(providerReports),
|
|
858
|
+
}))
|
|
859
|
+
.sort((a, b) => {
|
|
860
|
+
if (a.totalUsage !== b.totalUsage) return a.totalUsage - b.totalUsage;
|
|
861
|
+
return a.provider.localeCompare(b.provider);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
for (const { provider, providerReports } of providerEntries) {
|
|
865
|
+
lines.push("");
|
|
866
|
+
const providerName = formatProviderName(provider);
|
|
867
|
+
|
|
868
|
+
const limitGroups = new Map<
|
|
869
|
+
string,
|
|
870
|
+
{ label: string; windowLabel: string; limits: UsageLimit[]; reports: UsageReport[] }
|
|
871
|
+
>();
|
|
872
|
+
for (const report of providerReports) {
|
|
873
|
+
for (const limit of report.limits) {
|
|
874
|
+
const windowId = limit.window?.id ?? limit.scope.windowId ?? "default";
|
|
875
|
+
const key = `${formatLimitTitle(limit)}|${windowId}`;
|
|
876
|
+
const windowLabel = limit.window?.label ?? windowId;
|
|
877
|
+
const entry = limitGroups.get(key) ?? {
|
|
878
|
+
label: formatLimitTitle(limit),
|
|
879
|
+
windowLabel,
|
|
880
|
+
limits: [],
|
|
881
|
+
reports: [],
|
|
882
|
+
};
|
|
883
|
+
entry.limits.push(limit);
|
|
884
|
+
entry.reports.push(report);
|
|
885
|
+
limitGroups.set(key, entry);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const providerAllZero = isZeroUsageGroup(Array.from(limitGroups.values()).flatMap((group) => group.limits));
|
|
890
|
+
if (providerAllZero) {
|
|
891
|
+
const providerTitle = `${resolveStatusIcon("ok", uiTheme)} ${uiTheme.fg("accent", `${providerName} (0%)`)}`;
|
|
892
|
+
lines.push(uiTheme.bold(providerTitle));
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
lines.push(uiTheme.bold(uiTheme.fg("accent", providerName)));
|
|
897
|
+
|
|
898
|
+
for (const group of limitGroups.values()) {
|
|
899
|
+
const entries = group.limits.map((limit, index) => ({
|
|
900
|
+
limit,
|
|
901
|
+
report: group.reports[index],
|
|
902
|
+
fraction: resolveFraction(limit),
|
|
903
|
+
index,
|
|
904
|
+
}));
|
|
905
|
+
entries.sort((a, b) => {
|
|
906
|
+
const aFraction = a.fraction ?? -1;
|
|
907
|
+
const bFraction = b.fraction ?? -1;
|
|
908
|
+
if (aFraction !== bFraction) return bFraction - aFraction;
|
|
909
|
+
return a.index - b.index;
|
|
910
|
+
});
|
|
911
|
+
const sortedLimits = entries.map((entry) => entry.limit);
|
|
912
|
+
const sortedReports = entries.map((entry) => entry.report);
|
|
913
|
+
|
|
914
|
+
const status = resolveAggregateStatus(sortedLimits);
|
|
915
|
+
const statusIcon = resolveStatusIcon(status, uiTheme);
|
|
916
|
+
if (isZeroUsageGroup(sortedLimits)) {
|
|
917
|
+
const resetText = resolveResetRange(sortedLimits, nowMs);
|
|
918
|
+
const resetSuffix = resetText ? ` | ${resetText}` : "";
|
|
919
|
+
const windowSuffix = formatWindowSuffix(group.label, group.windowLabel, uiTheme);
|
|
920
|
+
lines.push(
|
|
921
|
+
`${statusIcon} ${uiTheme.bold(group.label)} ${windowSuffix} ${uiTheme.fg(
|
|
922
|
+
"dim",
|
|
923
|
+
`0%${resetSuffix}`,
|
|
924
|
+
)}`.trim(),
|
|
925
|
+
);
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const windowSuffix = formatWindowSuffix(group.label, group.windowLabel, uiTheme);
|
|
930
|
+
lines.push(`${statusIcon} ${uiTheme.bold(group.label)} ${windowSuffix}`.trim());
|
|
931
|
+
const accountLabels = sortedLimits.map((limit, index) =>
|
|
932
|
+
padColumn(formatAccountHeader(limit, sortedReports[index], index, nowMs), COLUMN_WIDTH),
|
|
933
|
+
);
|
|
934
|
+
lines.push(` ${accountLabels.join(" ")}`.trimEnd());
|
|
935
|
+
const bars = sortedLimits.map((limit) => padColumn(renderUsageBar(limit, uiTheme), COLUMN_WIDTH));
|
|
936
|
+
lines.push(` ${bars.join(" ")} ${formatAggregateAmount(sortedLimits)}`.trimEnd());
|
|
937
|
+
const resetText = sortedLimits.length <= 1 ? resolveResetRange(sortedLimits, nowMs) : null;
|
|
938
|
+
if (resetText) {
|
|
939
|
+
lines.push(` ${uiTheme.fg("dim", resetText)}`.trimEnd());
|
|
940
|
+
}
|
|
941
|
+
const notes = sortedLimits.flatMap((limit) => limit.notes ?? []);
|
|
942
|
+
if (notes.length > 0) {
|
|
943
|
+
lines.push(` ${uiTheme.fg("dim", notes.join(" • "))}`.trimEnd());
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// No per-provider footer; global header shows last check.
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return lines.join("\n");
|
|
951
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Loader, Text } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
2
|
+
import { AssistantMessageComponent } from "$c/modes/components/assistant-message";
|
|
3
|
+
import { ReadToolGroupComponent } from "$c/modes/components/read-tool-group";
|
|
4
|
+
import { TodoReminderComponent } from "$c/modes/components/todo-reminder";
|
|
5
|
+
import { ToolExecutionComponent } from "$c/modes/components/tool-execution";
|
|
6
|
+
import { TtsrNotificationComponent } from "$c/modes/components/ttsr-notification";
|
|
7
|
+
import { getSymbolTheme, theme } from "$c/modes/theme/theme";
|
|
8
|
+
import type { InteractiveModeContext, TodoItem } from "$c/modes/types";
|
|
9
|
+
import type { AgentSessionEvent } from "$c/session/agent-session";
|
|
10
|
+
import { detectNotificationProtocol, isNotificationSuppressed, sendNotification } from "$c/utils/terminal-notify";
|
|
11
11
|
|
|
12
12
|
export class EventController {
|
|
13
13
|
private lastReadGroup: ReadToolGroupComponent | undefined = undefined;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { Component, TUI } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { KeybindingsManager } from "$c/config/keybindings";
|
|
4
5
|
import type {
|
|
5
6
|
ExtensionActions,
|
|
6
7
|
ExtensionCommandContextActions,
|
|
7
8
|
ExtensionContextActions,
|
|
8
9
|
ExtensionError,
|
|
9
10
|
ExtensionUIContext,
|
|
10
|
-
} from "
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import type { InteractiveModeContext } from "../types";
|
|
11
|
+
} from "$c/extensibility/extensions/index";
|
|
12
|
+
import { HookEditorComponent } from "$c/modes/components/hook-editor";
|
|
13
|
+
import { HookInputComponent } from "$c/modes/components/hook-input";
|
|
14
|
+
import { HookSelectorComponent } from "$c/modes/components/hook-selector";
|
|
15
|
+
import { getAvailableThemesWithPaths, getThemeByName, setTheme, type Theme, theme } from "$c/modes/theme/theme";
|
|
16
|
+
import type { InteractiveModeContext } from "$c/modes/types";
|
|
17
|
+
import { setTerminalTitle } from "$c/utils/title-generator";
|
|
18
18
|
|
|
19
19
|
export class ExtensionUiController {
|
|
20
20
|
constructor(private ctx: InteractiveModeContext) {}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
import { open, rm } from "node:fs/promises";
|
|
2
4
|
import * as os from "node:os";
|
|
3
5
|
import * as path from "node:path";
|
|
4
6
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
5
7
|
import { nanoid } from "nanoid";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
8
|
+
import { theme } from "$c/modes/theme/theme";
|
|
9
|
+
import type { InteractiveModeContext } from "$c/modes/types";
|
|
10
|
+
import type { AgentSessionEvent } from "$c/session/agent-session";
|
|
11
|
+
import { readImageFromClipboard } from "$c/utils/clipboard";
|
|
12
|
+
import { resizeImage } from "$c/utils/image-resize";
|
|
13
|
+
import { generateSessionTitle, setTerminalTitle } from "$c/utils/title-generator";
|
|
12
14
|
|
|
13
15
|
interface Expandable {
|
|
14
16
|
setExpanded(expanded: boolean): void;
|
|
@@ -143,6 +145,16 @@ export class InputController {
|
|
|
143
145
|
|
|
144
146
|
if (!text) return;
|
|
145
147
|
|
|
148
|
+
// Continue shortcuts: "." or "c" sends empty message (agent continues, no visible message)
|
|
149
|
+
if (text === "." || text === "c") {
|
|
150
|
+
if (this.ctx.onInputCallback) {
|
|
151
|
+
this.ctx.editor.setText("");
|
|
152
|
+
this.ctx.pendingImages = [];
|
|
153
|
+
this.ctx.onInputCallback({ text: "" });
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
146
158
|
const runner = this.ctx.session.extensionRunner;
|
|
147
159
|
let inputImages = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
148
160
|
|
|
@@ -199,6 +211,11 @@ export class InputController {
|
|
|
199
211
|
this.ctx.editor.setText("");
|
|
200
212
|
return;
|
|
201
213
|
}
|
|
214
|
+
if (text === "/usage") {
|
|
215
|
+
await this.ctx.handleUsageCommand();
|
|
216
|
+
this.ctx.editor.setText("");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
202
219
|
if (text === "/changelog") {
|
|
203
220
|
this.ctx.handleChangelogCommand();
|
|
204
221
|
this.ctx.editor.setText("");
|
|
@@ -614,6 +631,25 @@ export class InputController {
|
|
|
614
631
|
this.ctx.showStatus(`Thinking blocks: ${this.ctx.hideThinkingBlock ? "hidden" : "visible"}`);
|
|
615
632
|
}
|
|
616
633
|
|
|
634
|
+
private getEditorTerminalPath(): string | null {
|
|
635
|
+
if (process.platform === "win32") {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
return "/dev/tty";
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
private async openEditorTerminalHandle(): Promise<FileHandle | null> {
|
|
642
|
+
const terminalPath = this.getEditorTerminalPath();
|
|
643
|
+
if (!terminalPath) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
return await open(terminalPath, "r+");
|
|
648
|
+
} catch {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
617
653
|
async openExternalEditor(): Promise<void> {
|
|
618
654
|
// Determine editor (respect $VISUAL, then $EDITOR)
|
|
619
655
|
const editorCmd = process.env.VISUAL || process.env.EDITOR;
|
|
@@ -625,23 +661,27 @@ export class InputController {
|
|
|
625
661
|
const currentText = this.ctx.editor.getText();
|
|
626
662
|
const tmpFile = path.join(os.tmpdir(), `omp-editor-${nanoid()}.omp.md`);
|
|
627
663
|
|
|
664
|
+
let ttyHandle: FileHandle | null = null;
|
|
628
665
|
try {
|
|
629
666
|
// Write current content to temp file
|
|
630
667
|
await Bun.write(tmpFile, currentText);
|
|
631
668
|
|
|
632
669
|
// Stop TUI to release terminal
|
|
670
|
+
ttyHandle = await this.openEditorTerminalHandle();
|
|
633
671
|
this.ctx.ui.stop();
|
|
634
672
|
|
|
635
673
|
// Split by space to support editor arguments (e.g., "code --wait")
|
|
636
674
|
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
637
675
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
676
|
+
const stdio: [number | "inherit", number | "inherit", number | "inherit"] = ttyHandle
|
|
677
|
+
? [ttyHandle.fd, ttyHandle.fd, ttyHandle.fd]
|
|
678
|
+
: ["inherit", "inherit", "inherit"];
|
|
679
|
+
|
|
680
|
+
const child = spawn(editor, [...editorArgs, tmpFile], { stdio });
|
|
681
|
+
const exitCode = await new Promise<number>((resolve, reject) => {
|
|
682
|
+
child.once("exit", (code, signal) => resolve(code ?? (signal ? -1 : 0)));
|
|
683
|
+
child.once("error", (error) => reject(error));
|
|
643
684
|
});
|
|
644
|
-
const exitCode = await child.exited;
|
|
645
685
|
|
|
646
686
|
// On successful exit (exitCode 0), replace editor content
|
|
647
687
|
if (exitCode === 0) {
|
|
@@ -649,6 +689,10 @@ export class InputController {
|
|
|
649
689
|
this.ctx.editor.setText(newContent);
|
|
650
690
|
}
|
|
651
691
|
// On non-zero exit, keep original text (no action needed)
|
|
692
|
+
} catch (error) {
|
|
693
|
+
this.ctx.showWarning(
|
|
694
|
+
`Failed to open external editor: ${error instanceof Error ? error.message : String(error)}`,
|
|
695
|
+
);
|
|
652
696
|
} finally {
|
|
653
697
|
// Clean up temp file
|
|
654
698
|
try {
|
|
@@ -657,6 +701,10 @@ export class InputController {
|
|
|
657
701
|
// Ignore cleanup errors
|
|
658
702
|
}
|
|
659
703
|
|
|
704
|
+
if (ttyHandle) {
|
|
705
|
+
await ttyHandle.close();
|
|
706
|
+
}
|
|
707
|
+
|
|
660
708
|
// Restart TUI
|
|
661
709
|
this.ctx.ui.start();
|
|
662
710
|
this.ctx.ui.requestRender();
|