@oh-my-pi/pi-tui 18.4.2 → 18.4.4
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 +56 -0
- package/dist/types/apps/cleanse-board.d.ts +17 -0
- package/dist/types/apps/git/help.d.ts +16 -0
- package/dist/types/apps/git/sidebar.d.ts +18 -0
- package/dist/types/apps/live-visualizer.d.ts +10 -0
- package/dist/types/apps/ps-top.d.ts +15 -0
- package/dist/types/apps/standalone-picker.d.ts +3 -0
- package/dist/types/autocomplete.d.ts +8 -0
- package/dist/types/chat/advisor-message.d.ts +12 -2
- package/dist/types/chat/assistant-message.d.ts +20 -0
- package/dist/types/chat/bash-execution.d.ts +9 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +6 -1
- package/dist/types/chat/collab-prompt-message.d.ts +4 -0
- package/dist/types/chat/compaction-summary-message.d.ts +15 -0
- package/dist/types/chat/eval-execution.d.ts +8 -0
- package/dist/types/chat/execution-shared.d.ts +58 -0
- package/dist/types/chat/late-diagnostics-message.d.ts +15 -1
- package/dist/types/chat/read-tool-group.d.ts +15 -0
- package/dist/types/chat/skill-message.d.ts +9 -0
- package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +3 -0
- package/dist/types/chat/thinking-display.d.ts +2 -0
- package/dist/types/chat/todo-reminder.d.ts +4 -1
- package/dist/types/chat/tool-execution.d.ts +25 -1
- package/dist/types/chat/transcript-actions.d.ts +37 -0
- package/dist/types/chat/transcript-entry.d.ts +3 -1
- package/dist/types/chat/user-message.d.ts +25 -0
- package/dist/types/chrome/collab-qrcode.d.ts +7 -0
- package/dist/types/chrome/context-thresholds.d.ts +11 -0
- package/dist/types/chrome/countdown-timer.d.ts +9 -3
- package/dist/types/chrome/diff.d.ts +12 -0
- package/dist/types/chrome/dynamic-border.d.ts +3 -0
- package/dist/types/chrome/format.d.ts +6 -0
- package/dist/types/chrome/message-divider.d.ts +13 -0
- package/dist/types/chrome/message-frame.d.ts +10 -0
- package/dist/types/chrome/message-notice.d.ts +23 -0
- package/dist/types/chrome/overlay-box.d.ts +11 -2
- package/dist/types/chrome/qrcode.d.ts +7 -0
- package/dist/types/chrome/segment-track.d.ts +7 -0
- package/dist/types/chrome/selector-helpers.d.ts +0 -6
- package/dist/types/chrome/status-notice.d.ts +14 -0
- package/dist/types/chrome/streaming-panel.d.ts +3 -0
- package/dist/types/chrome/tool-activity.d.ts +3 -0
- package/dist/types/chrome/transcript-container.d.ts +13 -0
- package/dist/types/chrome/transcript-status.d.ts +15 -3
- package/dist/types/components/box.d.ts +7 -0
- package/dist/types/components/disclosure.d.ts +9 -0
- package/dist/types/components/editor.d.ts +42 -0
- package/dist/types/components/form.d.ts +14 -0
- package/dist/types/components/image.d.ts +7 -0
- package/dist/types/components/input.d.ts +20 -1
- package/dist/types/components/key-value-list.d.ts +3 -0
- package/dist/types/components/layout/geometry.d.ts +16 -0
- package/dist/types/components/layout/row.d.ts +7 -0
- package/dist/types/components/layout/split-pane.d.ts +8 -0
- package/dist/types/components/layout/stack.d.ts +6 -0
- package/dist/types/components/loader.d.ts +48 -0
- package/dist/types/components/markdown.d.ts +8 -0
- package/dist/types/components/metric.d.ts +7 -0
- package/dist/types/components/progress-bar.d.ts +3 -0
- package/dist/types/components/scroll-view.d.ts +6 -0
- package/dist/types/components/section.d.ts +3 -0
- package/dist/types/components/select-list.d.ts +33 -0
- package/dist/types/components/settings-list.d.ts +68 -0
- package/dist/types/components/spacer.d.ts +4 -0
- package/dist/types/components/tab-bar.d.ts +9 -0
- package/dist/types/components/table.d.ts +7 -0
- package/dist/types/components/text.d.ts +6 -0
- package/dist/types/components/tree-view.d.ts +9 -0
- package/dist/types/components/truncated-text.d.ts +3 -0
- package/dist/types/components/wizard-step.d.ts +6 -0
- package/dist/types/hotkeys-markdown.d.ts +24 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/latex-block.d.ts +7 -0
- package/dist/types/loop-watchdog.d.ts +9 -0
- package/dist/types/native/apply.d.ts +39 -0
- package/dist/types/native/backend.d.ts +94 -0
- package/dist/types/native/blobs.d.ts +29 -0
- package/dist/types/native/describe.d.ts +56 -0
- package/dist/types/native/encode.d.ts +50 -0
- package/dist/types/native/icons.d.ts +18 -0
- package/dist/types/native/memo.d.ts +27 -0
- package/dist/types/native/node.d.ts +107 -0
- package/dist/types/native/overlay.d.ts +64 -0
- package/dist/types/native/picker.d.ts +124 -0
- package/dist/types/native/reconcile.d.ts +41 -0
- package/dist/types/native/settle.d.ts +11 -0
- package/dist/types/native/spans.d.ts +21 -0
- package/dist/types/native/state.d.ts +6 -0
- package/dist/types/native/tone.d.ts +24 -0
- package/dist/types/overlays/advisor-config.d.ts +20 -0
- package/dist/types/overlays/agent-hub-renderer.d.ts +27 -11
- package/dist/types/overlays/agent-hub.d.ts +10 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +7 -0
- package/dist/types/overlays/agents-hub.d.ts +8 -0
- package/dist/types/overlays/annotation-overlay.d.ts +3 -0
- package/dist/types/overlays/ask-dialog.d.ts +23 -0
- package/dist/types/overlays/bordered-loader.d.ts +3 -0
- package/dist/types/overlays/btw-history-panel.d.ts +9 -0
- package/dist/types/overlays/btw-panel.d.ts +3 -0
- package/dist/types/overlays/cleanse-panel.d.ts +5 -2
- package/dist/types/overlays/composer-shape-preview.d.ts +7 -1
- package/dist/types/overlays/copy-selector.d.ts +38 -0
- package/dist/types/overlays/error-banner.d.ts +9 -0
- package/dist/types/overlays/extensions/extension-list.d.ts +1 -0
- package/dist/types/overlays/history-search.d.ts +18 -0
- package/dist/types/overlays/hook-editor.d.ts +28 -1
- package/dist/types/overlays/hook-input.d.ts +3 -0
- package/dist/types/overlays/hook-selector.d.ts +10 -0
- package/dist/types/overlays/hub-frame.d.ts +18 -0
- package/dist/types/overlays/jobs-panel.d.ts +24 -0
- package/dist/types/overlays/login-dialog.d.ts +10 -0
- package/dist/types/overlays/logout-account-selector.d.ts +4 -0
- package/dist/types/overlays/mcp-add-wizard.d.ts +4 -0
- package/dist/types/overlays/model-browser.d.ts +69 -0
- package/dist/types/overlays/model-hub.d.ts +7 -0
- package/dist/types/overlays/model-picker.d.ts +9 -1
- package/dist/types/overlays/move-overlay.d.ts +3 -0
- package/dist/types/overlays/oauth-selector.d.ts +4 -0
- package/dist/types/overlays/omfg-panel.d.ts +3 -0
- package/dist/types/overlays/pause-screen.d.ts +12 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +5 -0
- package/dist/types/overlays/plan-save-overlay.d.ts +10 -0
- package/dist/types/overlays/plugin-selector.d.ts +4 -0
- package/dist/types/overlays/plugin-settings.d.ts +30 -0
- package/dist/types/overlays/queue-mode-selector.d.ts +4 -0
- package/dist/types/overlays/reset-usage-selector.d.ts +8 -0
- package/dist/types/overlays/rewind-selector.d.ts +5 -0
- package/dist/types/overlays/session-info-overlay.d.ts +19 -1
- package/dist/types/overlays/session-selector.d.ts +64 -1
- package/dist/types/overlays/settings-defs.d.ts +14 -0
- package/dist/types/overlays/settings-selector.d.ts +21 -0
- package/dist/types/overlays/show-images-selector.d.ts +4 -0
- package/dist/types/overlays/snapcompact-shape-preview.d.ts +2 -0
- package/dist/types/overlays/stats-notice.d.ts +8 -0
- package/dist/types/overlays/theme-selector.d.ts +11 -0
- package/dist/types/overlays/thinking-selector.d.ts +4 -0
- package/dist/types/overlays/tree-selector.d.ts +60 -2
- package/dist/types/overlays/usage-dashboard.d.ts +26 -3
- package/dist/types/overlays/usage-row.d.ts +36 -2
- package/dist/types/prompt/attachment-chips.d.ts +8 -0
- package/dist/types/prompt/composer-attachments.d.ts +15 -3
- package/dist/types/prompt/composer.d.ts +18 -1
- package/dist/types/prompt/custom-editor.d.ts +48 -2
- package/dist/types/prompt/editor-top-gap.d.ts +2 -0
- package/dist/types/prompt/macos-spelling.d.ts +6 -0
- package/dist/types/prompt/magic-keywords.d.ts +9 -0
- package/dist/types/prompt/queued-messages.d.ts +23 -0
- package/dist/types/prompt/skill-tokens.d.ts +4 -2
- package/dist/types/prompt/welcome.d.ts +16 -0
- package/dist/types/render/code-cell.d.ts +10 -0
- package/dist/types/render/file-list.d.ts +8 -0
- package/dist/types/render/hyperlink.d.ts +14 -0
- package/dist/types/render/output-block.d.ts +43 -0
- package/dist/types/render/output-pane.d.ts +17 -0
- package/dist/types/render/tool-card.d.ts +8 -0
- package/dist/types/render/tree-list.d.ts +8 -0
- package/dist/types/render/width-aware-text.d.ts +7 -0
- package/dist/types/setup/scenes/outro.d.ts +7 -0
- package/dist/types/setup/scenes/sign-in.d.ts +16 -9
- package/dist/types/setup/scenes/splash.d.ts +8 -0
- package/dist/types/setup/scenes/types.d.ts +6 -27
- package/dist/types/setup/wizard-overlay.d.ts +9 -0
- package/dist/types/status-line/component.d.ts +49 -5
- package/dist/types/status-line/context-usage.d.ts +20 -0
- package/dist/types/status-line/footer.d.ts +8 -0
- package/dist/types/status-line/metrics.d.ts +1 -2
- package/dist/types/status-line/segments.d.ts +10 -1
- package/dist/types/status-line/types.d.ts +40 -1
- package/dist/types/stdin-buffer.d.ts +10 -7
- package/dist/types/terminal-capabilities.d.ts +1 -1
- package/dist/types/terminal.d.ts +36 -0
- package/dist/types/theme/defaults/index.d.ts +100 -0
- package/dist/types/theme/schema.d.ts +2 -0
- package/dist/types/theme/shimmer.d.ts +8 -0
- package/dist/types/theme/theme-class.d.ts +2 -0
- package/dist/types/theme/theme.d.ts +33 -1
- package/dist/types/tools/ask.d.ts +3 -0
- package/dist/types/tools/ast-edit.d.ts +10 -1
- package/dist/types/tools/ast-grep.d.ts +10 -1
- package/dist/types/tools/autoresearch.d.ts +21 -4
- package/dist/types/tools/bash.d.ts +30 -1
- package/dist/types/tools/cfg-render.d.ts +5 -1
- package/dist/types/tools/debug.d.ts +3 -1
- package/dist/types/tools/default-renderer.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +14 -1
- package/dist/types/tools/eval.d.ts +12 -1
- package/dist/types/tools/fetch.d.ts +16 -1
- package/dist/types/tools/find.d.ts +15 -1
- package/dist/types/tools/github.d.ts +3 -0
- package/dist/types/tools/glob.d.ts +14 -1
- package/dist/types/tools/goal.d.ts +3 -1
- package/dist/types/tools/grep.d.ts +14 -1
- package/dist/types/tools/grouped-file-output.d.ts +58 -0
- package/dist/types/tools/json-tree.d.ts +20 -0
- package/dist/types/tools/lsp.d.ts +3 -10
- package/dist/types/tools/mcp.d.ts +16 -1
- package/dist/types/tools/memory.d.ts +26 -0
- package/dist/types/tools/native-view.d.ts +90 -0
- package/dist/types/tools/proc-render.d.ts +7 -1
- package/dist/types/tools/read.d.ts +23 -1
- package/dist/types/tools/renderer.d.ts +57 -0
- package/dist/types/tools/report-tool-issue.d.ts +3 -0
- package/dist/types/tools/resolve.d.ts +14 -1
- package/dist/types/tools/task.d.ts +3 -0
- package/dist/types/tools/think.d.ts +3 -0
- package/dist/types/tools/todo.d.ts +12 -0
- package/dist/types/tools/vibe.d.ts +3 -1
- package/dist/types/tools/wait.d.ts +3 -0
- package/dist/types/tools/web-search-types.d.ts +90 -0
- package/dist/types/tools/web-search.d.ts +9 -168
- package/dist/types/tools/write.d.ts +14 -1
- package/dist/types/tools/xdev.d.ts +13 -0
- package/dist/types/tui.d.ts +49 -0
- package/package.json +9 -9
- package/src/apps/autoresearch-dashboard.ts +362 -4
- package/src/apps/cleanse-board.ts +219 -1
- package/src/apps/cleanse-picker.ts +9 -1
- package/src/apps/debug/log-viewer.ts +15 -15
- package/src/apps/debug/protocol-probe.ts +2 -20
- package/src/apps/git/avatar.ts +2 -22
- package/src/apps/git/git-tui.ts +406 -59
- package/src/apps/git/help.ts +130 -0
- package/src/apps/git/sidebar.ts +491 -0
- package/src/apps/live-visualizer.ts +65 -0
- package/src/apps/ps-top.ts +448 -17
- package/src/apps/session-picker.ts +1 -0
- package/src/apps/setup-model-picker.ts +5 -1
- package/src/apps/standalone-picker.ts +55 -1
- package/src/autocomplete.ts +44 -1
- package/src/chat/advisor-message.ts +72 -3
- package/src/chat/assistant-message.ts +538 -51
- package/src/chat/background-tan-message.ts +4 -4
- package/src/chat/bash-execution.ts +81 -0
- package/src/chat/cache-invalidation-marker.ts +8 -0
- package/src/chat/chat-transcript-builder.ts +37 -6
- package/src/chat/collab-prompt-message.ts +15 -3
- package/src/chat/compaction-summary-message.ts +74 -1
- package/src/chat/custom-message.ts +3 -1
- package/src/chat/eval-execution.ts +57 -0
- package/src/chat/execution-shared.ts +153 -1
- package/src/chat/hook-message.ts +1 -0
- package/src/chat/late-diagnostics-message.ts +85 -2
- package/src/chat/read-tool-group.ts +341 -1
- package/src/chat/served-model-marker.ts +8 -0
- package/src/chat/skill-message.ts +63 -0
- package/src/chat/stripped-tool-calls-placeholder.ts +18 -10
- package/src/chat/thinking-display.ts +73 -11
- package/src/chat/todo-reminder.ts +34 -4
- package/src/chat/tool-execution.ts +385 -10
- package/src/chat/transcript-actions.ts +38 -0
- package/src/chat/transcript-browser.ts +2 -9
- package/src/chat/transcript-entry.ts +10 -1
- package/src/chat/transcript-render-helpers.ts +16 -15
- package/src/chat/ttsr-notification.ts +38 -1
- package/src/chat/user-message.ts +167 -4
- package/src/chrome/collab-qrcode.ts +27 -2
- package/src/chrome/context-thresholds.ts +34 -0
- package/src/chrome/countdown-timer.ts +24 -1
- package/src/chrome/diff.ts +60 -1
- package/src/chrome/dynamic-border.ts +7 -0
- package/src/chrome/format.ts +12 -0
- package/src/chrome/message-divider.ts +33 -1
- package/src/chrome/message-frame.ts +53 -10
- package/src/chrome/message-notice.ts +71 -0
- package/src/chrome/overlay-box.ts +45 -2
- package/src/chrome/qrcode.ts +28 -0
- package/src/chrome/segment-track.ts +26 -0
- package/src/chrome/selector-helpers.ts +1 -17
- package/src/chrome/status-notice.ts +46 -0
- package/src/chrome/streaming-panel.ts +25 -1
- package/src/chrome/tool-activity.ts +20 -0
- package/src/chrome/transcript-container.ts +139 -17
- package/src/chrome/transcript-status.ts +40 -11
- package/src/components/box.ts +51 -0
- package/src/components/disclosure.ts +33 -0
- package/src/components/editor.ts +239 -36
- package/src/components/form.ts +93 -9
- package/src/components/image.ts +30 -0
- package/src/components/input.ts +77 -26
- package/src/components/key-value-list.ts +18 -0
- package/src/components/layout/geometry.ts +44 -0
- package/src/components/layout/row.ts +30 -0
- package/src/components/layout/split-pane.ts +65 -0
- package/src/components/layout/stack.ts +27 -0
- package/src/components/loader.ts +183 -1
- package/src/components/markdown.ts +131 -56
- package/src/components/metric.ts +39 -0
- package/src/components/progress-bar.ts +28 -0
- package/src/components/scroll-view.ts +23 -0
- package/src/components/section.ts +16 -0
- package/src/components/select-list.ts +174 -25
- package/src/components/settings-list.ts +264 -7
- package/src/components/spacer.ts +17 -0
- package/src/components/tab-bar.ts +42 -0
- package/src/components/table.ts +34 -0
- package/src/components/text.ts +23 -0
- package/src/components/tree-view.ts +65 -0
- package/src/components/truncated-text.ts +14 -0
- package/src/components/wizard-step.ts +18 -9
- package/src/debug-server.ts +14 -7
- package/src/hotkeys-markdown.ts +251 -69
- package/src/index.ts +6 -0
- package/src/latex-block.ts +11 -0
- package/src/loop-watchdog.ts +38 -10
- package/src/native/apply.ts +275 -0
- package/src/native/backend.ts +682 -0
- package/src/native/blobs.ts +70 -0
- package/src/native/describe.ts +124 -0
- package/src/native/encode.ts +233 -0
- package/src/native/icons.ts +184 -0
- package/src/native/memo.ts +64 -0
- package/src/native/node.ts +112 -0
- package/src/native/overlay.ts +131 -0
- package/src/native/picker.ts +333 -0
- package/src/native/reconcile.ts +1063 -0
- package/src/native/settle.ts +23 -0
- package/src/native/spans.ts +308 -0
- package/src/native/state.ts +28 -0
- package/src/native/tone.ts +79 -0
- package/src/overlays/advisor-config.ts +420 -0
- package/src/overlays/agent-hub-renderer.ts +155 -18
- package/src/overlays/agent-hub.ts +1037 -44
- package/src/overlays/agent-transcript-viewer.ts +129 -0
- package/src/overlays/agents-hub.ts +688 -53
- package/src/overlays/annotation-overlay.ts +391 -52
- package/src/overlays/ask-dialog.ts +435 -24
- package/src/overlays/bordered-loader.ts +17 -0
- package/src/overlays/btw-history-panel.ts +182 -0
- package/src/overlays/btw-panel.ts +68 -2
- package/src/overlays/cleanse-panel.ts +105 -15
- package/src/overlays/codex-reset-fireworks.ts +65 -5
- package/src/overlays/composer-shape-preview.ts +26 -1
- package/src/overlays/copy-selector.ts +545 -23
- package/src/overlays/error-banner.ts +44 -0
- package/src/overlays/extensions/extension-list.ts +23 -17
- package/src/overlays/history-search.ts +175 -33
- package/src/overlays/hook-editor.ts +113 -6
- package/src/overlays/hook-input.ts +22 -1
- package/src/overlays/hook-selector.ts +311 -22
- package/src/overlays/hub-frame.ts +80 -0
- package/src/overlays/jobs-panel.ts +112 -0
- package/src/overlays/login-dialog.ts +183 -1
- package/src/overlays/logout-account-selector.ts +102 -4
- package/src/overlays/mcp-add-wizard.ts +232 -53
- package/src/overlays/model-browser.ts +681 -22
- package/src/overlays/model-hub.ts +1402 -78
- package/src/overlays/model-picker.ts +184 -7
- package/src/overlays/move-overlay.ts +45 -0
- package/src/overlays/oauth-selector.ts +227 -23
- package/src/overlays/omfg-panel.ts +86 -2
- package/src/overlays/pause-screen.ts +55 -4
- package/src/overlays/plan-review-overlay.ts +352 -0
- package/src/overlays/plan-save-overlay.ts +45 -0
- package/src/overlays/plugin-selector.ts +21 -1
- package/src/overlays/plugin-settings.ts +195 -5
- package/src/overlays/queue-mode-selector.ts +19 -1
- package/src/overlays/reset-usage-selector.ts +110 -40
- package/src/overlays/rewind-selector.ts +458 -44
- package/src/overlays/session-account-selector.ts +1 -1
- package/src/overlays/session-info-overlay.ts +137 -1
- package/src/overlays/session-selector.ts +807 -46
- package/src/overlays/settings-defs.ts +41 -0
- package/src/overlays/settings-selector.ts +585 -33
- package/src/overlays/show-images-selector.ts +18 -1
- package/src/overlays/snapcompact-shape-preview.ts +48 -3
- package/src/overlays/stats-notice.ts +31 -0
- package/src/overlays/theme-selector.ts +31 -1
- package/src/overlays/thinking-selector.ts +22 -1
- package/src/overlays/tree-selector.ts +570 -110
- package/src/overlays/usage-dashboard.ts +587 -12
- package/src/overlays/usage-row.ts +161 -25
- package/src/prompt/attachment-chips.ts +60 -0
- package/src/prompt/composer-attachments.ts +31 -9
- package/src/prompt/composer.ts +55 -2
- package/src/prompt/custom-editor.ts +302 -4
- package/src/prompt/editor-top-gap.ts +8 -0
- package/src/prompt/macos-spelling.ts +24 -9
- package/src/prompt/magic-keywords.ts +22 -1
- package/src/prompt/model-mention-autocomplete.ts +1 -0
- package/src/prompt/queued-messages.ts +101 -0
- package/src/prompt/skill-tokens.ts +5 -3
- package/src/prompt/welcome.ts +213 -6
- package/src/render/code-cell.ts +103 -2
- package/src/render/file-list.ts +29 -0
- package/src/render/hyperlink.ts +32 -0
- package/src/render/output-block.ts +110 -0
- package/src/render/output-pane.ts +37 -0
- package/src/render/tool-card.ts +137 -0
- package/src/render/tree-list.ts +39 -0
- package/src/render/width-aware-text.ts +20 -0
- package/src/setup/scenes/composer.ts +29 -0
- package/src/setup/scenes/glyph.ts +15 -0
- package/src/setup/scenes/model.ts +36 -8
- package/src/setup/scenes/outro.ts +28 -1
- package/src/setup/scenes/sign-in.ts +138 -24
- package/src/setup/scenes/splash.ts +28 -1
- package/src/setup/scenes/theme.ts +88 -3
- package/src/setup/scenes/types.ts +7 -28
- package/src/setup/startup-splash.ts +46 -9
- package/src/setup/wizard-overlay.ts +140 -23
- package/src/setup/wizard.ts +1 -1
- package/src/status-line/component.ts +414 -18
- package/src/status-line/context-usage.ts +279 -50
- package/src/status-line/footer.ts +94 -1
- package/src/status-line/metrics.ts +3 -6
- package/src/status-line/segments.ts +463 -97
- package/src/status-line/separators.ts +3 -16
- package/src/status-line/types.ts +43 -1
- package/src/stdin-buffer.ts +27 -21
- package/src/terminal-capabilities.ts +7 -0
- package/src/terminal.ts +153 -11
- package/src/theme/defaults/dark-neon-noir.json +100 -0
- package/src/theme/defaults/index.ts +2 -0
- package/src/theme/defaults/light-prism.json +5 -5
- package/src/theme/defaults/onyx.json +62 -62
- package/src/theme/mermaid-cache.ts +10 -1
- package/src/theme/schema.ts +15 -0
- package/src/theme/shimmer.ts +33 -0
- package/src/theme/theme-class.ts +24 -19
- package/src/theme/theme.ts +111 -9
- package/src/theme/tui-adapters.ts +14 -1
- package/src/tools/ask.ts +157 -1
- package/src/tools/ast-edit.ts +174 -1
- package/src/tools/ast-grep.ts +82 -2
- package/src/tools/autoresearch.ts +149 -1
- package/src/tools/bash.ts +233 -62
- package/src/tools/cfg-render.ts +66 -1
- package/src/tools/debug.ts +78 -1
- package/src/tools/default-renderer.ts +35 -1
- package/src/tools/edit.ts +357 -31
- package/src/tools/eval.ts +257 -1
- package/src/tools/fetch.ts +102 -3
- package/src/tools/find.ts +127 -1
- package/src/tools/github.ts +119 -10
- package/src/tools/glob.ts +116 -1
- package/src/tools/goal.ts +56 -1
- package/src/tools/grep.ts +83 -2
- package/src/tools/grouped-file-output.ts +205 -0
- package/src/tools/json-tree.ts +116 -1
- package/src/tools/lsp.ts +318 -1
- package/src/tools/mcp.ts +80 -1
- package/src/tools/memory.ts +93 -11
- package/src/tools/native-view.ts +292 -0
- package/src/tools/proc-render.ts +221 -1
- package/src/tools/read.ts +199 -4
- package/src/tools/renderer.ts +61 -0
- package/src/tools/report-tool-issue.ts +7 -1
- package/src/tools/resolve.ts +63 -1
- package/src/tools/streaming-output.ts +79 -16
- package/src/tools/task.ts +406 -4
- package/src/tools/think.ts +19 -1
- package/src/tools/todo.ts +89 -0
- package/src/tools/vibe.ts +230 -2
- package/src/tools/wait.ts +181 -1
- package/src/tools/web-search-types.ts +99 -0
- package/src/tools/web-search.ts +111 -170
- package/src/tools/write.ts +144 -5
- package/src/tools/xdev.ts +64 -1
- package/src/tui.ts +295 -4
- package/dist/types/setup/scenes/providers.d.ts +0 -3
- package/dist/types/setup/scenes/web-search.d.ts +0 -22
- package/src/setup/scenes/providers.ts +0 -110
- package/src/setup/scenes/web-search.ts +0 -165
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-addressed binary blobs (images) for `image` nodes.
|
|
3
|
+
*
|
|
4
|
+
* `describe()` registers the bytes and puts the returned id in `image.p.blob`;
|
|
5
|
+
* the native backend uploads each referenced blob once per surface with verb
|
|
6
|
+
* `b` before the frame that first references it.
|
|
7
|
+
*/
|
|
8
|
+
import type { TspProps } from "@oh-my-pi/pi-wire";
|
|
9
|
+
import { getImageDimensions } from "../terminal-capabilities";
|
|
10
|
+
import { node } from "./describe";
|
|
11
|
+
import type { NativeNode } from "./node";
|
|
12
|
+
|
|
13
|
+
export interface NativeBlob {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly mime: string;
|
|
16
|
+
readonly bytes: Uint8Array;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const blobs = new Map<string, NativeBlob>();
|
|
20
|
+
// Registration usually repeats with the same bytes object on every describe;
|
|
21
|
+
// the tag skips rehashing it.
|
|
22
|
+
const kBlobId = Symbol("native.blobId");
|
|
23
|
+
|
|
24
|
+
interface BlobTagged {
|
|
25
|
+
[kBlobId]?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Register `bytes` and return their content address (sha256 hex) for `image.p.blob`. */
|
|
29
|
+
export function registerNativeBlob(bytes: Uint8Array, mime: string): string {
|
|
30
|
+
const known = (bytes as BlobTagged)[kBlobId];
|
|
31
|
+
if (known !== undefined) return known;
|
|
32
|
+
const id = new Bun.CryptoHasher("sha256").update(bytes).digest("hex");
|
|
33
|
+
(bytes as BlobTagged)[kBlobId] = id;
|
|
34
|
+
if (!blobs.has(id)) blobs.set(id, { id, mime, bytes });
|
|
35
|
+
return id;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A registered blob by id. */
|
|
39
|
+
export function getNativeBlob(id: string): NativeBlob | undefined {
|
|
40
|
+
return blobs.get(id);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* An `image` node for a base64 payload: the bytes are registered as a blob and
|
|
45
|
+
* the pixel size probed from the header. Decoding and hashing cost real time,
|
|
46
|
+
* so callers cache the node per payload ({@link NativeImageCache}).
|
|
47
|
+
*/
|
|
48
|
+
export function base64ImageNode(
|
|
49
|
+
data: string,
|
|
50
|
+
mimeType: string,
|
|
51
|
+
p?: Omit<TspProps<"image">, "blob" | "w" | "h">,
|
|
52
|
+
key?: string,
|
|
53
|
+
): NativeNode {
|
|
54
|
+
const blob = registerNativeBlob(Buffer.from(data, "base64"), mimeType);
|
|
55
|
+
const size = getImageDimensions(data, mimeType);
|
|
56
|
+
return node("image", size ? { ...p, blob, w: size.widthPx, h: size.heightPx } : { ...p, blob }, undefined, key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** `image` nodes for base64 payloads, one per slot key, rebuilt only when the slot's payload changes. */
|
|
60
|
+
export class NativeImageCache {
|
|
61
|
+
#entries = new Map<string, { data: string; node: NativeNode }>();
|
|
62
|
+
|
|
63
|
+
get(key: string, data: string, mimeType: string): NativeNode {
|
|
64
|
+
const cached = this.#entries.get(key);
|
|
65
|
+
if (cached?.data === data) return cached.node;
|
|
66
|
+
const image = base64ImageNode(data, mimeType, { alt: mimeType }, key);
|
|
67
|
+
this.#entries.set(key, { data, node: image });
|
|
68
|
+
return image;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builders for {@link NativeNode}s, so `describe()` implementations read as
|
|
3
|
+
* a tree instead of object literals.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* describe() {
|
|
7
|
+
* return card({ role: "omp.tool.bash", status: "running", head: [span("Bash", "toolTitle")] }, [
|
|
8
|
+
* ansi(this.#output, { follow: true, preview: { lines: 10 } }),
|
|
9
|
+
* ]);
|
|
10
|
+
* }
|
|
11
|
+
*/
|
|
12
|
+
import type { TspKind, TspProps, TspSpan, TspText } from "@oh-my-pi/pi-wire";
|
|
13
|
+
import type { NativeChild, NativeNode } from "./node";
|
|
14
|
+
|
|
15
|
+
/** A node of any kind. */
|
|
16
|
+
export function node<K extends TspKind>(k: K, p?: TspProps<K>, c?: readonly NativeChild[], key?: string): NativeNode {
|
|
17
|
+
return { k, p, c, key } as NativeNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** One styled span; `s` is a space-separated list of semantic or theme tokens. */
|
|
21
|
+
export function span(t: string, s?: string, extra?: Omit<TspSpan, "t" | "s">): TspSpan {
|
|
22
|
+
return s === undefined && extra === undefined ? { t } : { t, s, ...extra };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Plain or styled text. */
|
|
26
|
+
export function text(content: TspText, p?: Omit<TspProps<"text">, "text" | "spans">): NativeNode {
|
|
27
|
+
return typeof content === "string" ? node("text", { ...p, text: content }) : node("text", { ...p, spans: content });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Vertical stack. */
|
|
31
|
+
export function col(c: readonly NativeChild[], p?: TspProps<"col">): NativeNode {
|
|
32
|
+
return node("col", p, c);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Horizontal flex row. */
|
|
36
|
+
export function row(c: readonly NativeChild[], p?: TspProps<"row">): NativeNode {
|
|
37
|
+
return node("row", p, c);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Card with an optional header, status and collapsible body. */
|
|
41
|
+
export function card(p: TspProps<"card">, c: readonly NativeChild[]): NativeNode {
|
|
42
|
+
return node("card", p, c);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Markdown source in omp's dialect. */
|
|
46
|
+
export function md(source: string, p?: Omit<TspProps<"md">, "text">): NativeNode {
|
|
47
|
+
return node("md", { ...p, text: source });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Highlighted code. */
|
|
51
|
+
export function code(source: string, p?: Omit<TspProps<"code">, "text">): NativeNode {
|
|
52
|
+
return node("code", { ...p, text: source });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Raw terminal output rendered as a mini terminal. */
|
|
56
|
+
export function ansi(output: string, p?: Omit<TspProps<"ansi">, "text">): NativeNode {
|
|
57
|
+
return node("ansi", { ...p, text: output });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Unified diff. */
|
|
61
|
+
export function diff(unified: string, p?: Omit<TspProps<"diff">, "text">): NativeNode {
|
|
62
|
+
return node("diff", { ...p, text: unified });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A mounted, empty, invisible node for components that describe nothing. */
|
|
66
|
+
export const EMPTY_NODE: NativeNode = node("col", { hidden: true });
|
|
67
|
+
|
|
68
|
+
/** `described` with a sibling identity key (the layout builders only take props). */
|
|
69
|
+
export function keyed(described: NativeNode, key: string): NativeNode {
|
|
70
|
+
return { ...described, key };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** `described` mounted but not laid out when `hidden` (cheap visibility toggles keep ids and view state). */
|
|
74
|
+
export function withHidden(described: NativeNode, hidden: boolean): NativeNode {
|
|
75
|
+
if (!hidden) return described;
|
|
76
|
+
return node(described.k, { ...described.p, hidden: true } as TspProps, described.c, described.key);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Stable node key for an arbitrary identity string; keys join into `/` keypaths, so identities are hashed. */
|
|
80
|
+
export function stableKey(identity: string): string {
|
|
81
|
+
return Bun.hash(identity).toString(36);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Drop absent entries (`undefined`, `null`, `false`) from a child list. */
|
|
85
|
+
export function compact<T>(items: readonly (T | undefined | null | false)[]): T[] {
|
|
86
|
+
const out: T[] = [];
|
|
87
|
+
for (const entry of items) if (entry !== undefined && entry !== null && entry !== false) out.push(entry);
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A `kv` grid from label/value pairs with muted labels, skipping empty values; undefined when nothing is left. */
|
|
92
|
+
export function kv(pairs: readonly (readonly [string, TspText | undefined])[]): NativeNode | undefined {
|
|
93
|
+
const items: { k: TspText; v: TspText }[] = [];
|
|
94
|
+
for (const [label, value] of pairs) {
|
|
95
|
+
if (value === undefined || value === "" || (Array.isArray(value) && value.length === 0)) continue;
|
|
96
|
+
items.push({ k: [span(label, "muted")], v: value });
|
|
97
|
+
}
|
|
98
|
+
return items.length > 0 ? node("kv", { items }) : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** One list `item`, keyed by the value select/activate events report back. */
|
|
102
|
+
export function item(key: string, p: TspProps<"item">): NativeNode {
|
|
103
|
+
return node("item", p, undefined, key);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A `list` of items. */
|
|
107
|
+
export function list(items: readonly NativeChild[], p?: TspProps<"list">, key?: string): NativeNode {
|
|
108
|
+
return node("list", p, items, key);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Terminal-clocked elapsed counter: `age` ms already elapsed; `stopped` freezes it there. */
|
|
112
|
+
export function elapsed(age: number, stopped?: boolean): NativeNode {
|
|
113
|
+
const ms = Math.max(0, Math.round(age));
|
|
114
|
+
return node("elapsed", stopped ? { age: ms, stopped: ms, format: "short" } : { age: ms, format: "short" });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Keycap for one key id (`"ctrl+o"` → keys `["ctrl", "o"]`); the terminal
|
|
119
|
+
* draws platform glyphs. A literal `+` key survives (`"+"`, `"ctrl++"`).
|
|
120
|
+
*/
|
|
121
|
+
export function kbd(key: string, nodeKey?: string): NativeNode {
|
|
122
|
+
const keys = key === "+" ? ["+"] : key.endsWith("++") ? [...key.slice(0, -2).split("+"), "+"] : key.split("+");
|
|
123
|
+
return node("kbd", { keys: keys.filter(part => part.length > 0) }, undefined, nodeKey);
|
|
124
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tern Surface Protocol framing: `ESC _ tsp ; <verb> [; k=v]* ; <body> ESC \`.
|
|
3
|
+
*
|
|
4
|
+
* Bodies are UTF-8 JSON (JSON escapes every control character, so a body can
|
|
5
|
+
* never contain ESC or BEL) or base64 for blob chunks. A body larger than the
|
|
6
|
+
* negotiated `apc` limit is split across messages with the same verb, tagged
|
|
7
|
+
* `c=<chunk-id>`, with `m=1` on every chunk but the last; the receiver joins
|
|
8
|
+
* the bodies byte-wise. Chunks split on code-point boundaries, so every chunk
|
|
9
|
+
* is valid UTF-8 on its own and the joined bytes equal the original body.
|
|
10
|
+
*/
|
|
11
|
+
import { isRecord } from "@oh-my-pi/pi-utils/type-guards";
|
|
12
|
+
import {
|
|
13
|
+
TSP_APC_ID,
|
|
14
|
+
TSP_DEFAULT_APC_LIMIT,
|
|
15
|
+
TSP_VERSION,
|
|
16
|
+
type TspEvent,
|
|
17
|
+
type TspReply,
|
|
18
|
+
type TspVerb,
|
|
19
|
+
} from "@oh-my-pi/pi-wire";
|
|
20
|
+
|
|
21
|
+
const APC = "\x1b_";
|
|
22
|
+
const ST = "\x1b\\";
|
|
23
|
+
/** Every TSP message starts with this. */
|
|
24
|
+
export const TSP_PREFIX = `${APC}${TSP_APC_ID};`;
|
|
25
|
+
|
|
26
|
+
/** The terminal's hello reply. */
|
|
27
|
+
export type TspHello = Extract<TspReply, { r: "hello" }>;
|
|
28
|
+
|
|
29
|
+
/** A decoded terminal → program message. */
|
|
30
|
+
export type TspIncoming = { verb: "r"; reply: TspReply } | { verb: "e"; event: TspEvent };
|
|
31
|
+
|
|
32
|
+
/** Message parameters (`k=v`, ASCII, never containing `;`). */
|
|
33
|
+
export type TspParams = Readonly<Record<string, string | number>>;
|
|
34
|
+
|
|
35
|
+
let nextChunkId = 1;
|
|
36
|
+
|
|
37
|
+
/** UTF-8 byte length of one UTF-16 code unit sequence starting at `i`, and its unit count. */
|
|
38
|
+
function codePointBytes(text: string, i: number): { bytes: number; units: number } {
|
|
39
|
+
const c = text.charCodeAt(i);
|
|
40
|
+
if (c < 0x80) return { bytes: 1, units: 1 };
|
|
41
|
+
if (c < 0x800) return { bytes: 2, units: 1 };
|
|
42
|
+
if (c >= 0xd800 && c <= 0xdbff && i + 1 < text.length) {
|
|
43
|
+
const next = text.charCodeAt(i + 1);
|
|
44
|
+
if (next >= 0xdc00 && next <= 0xdfff) return { bytes: 4, units: 2 };
|
|
45
|
+
}
|
|
46
|
+
return { bytes: 3, units: 1 };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Split `body` into pieces of at most `limit` UTF-8 bytes, never inside a code point. */
|
|
50
|
+
export function splitUtf8(body: string, limit: number): string[] {
|
|
51
|
+
const max = Math.max(4, Math.trunc(limit));
|
|
52
|
+
const pieces: string[] = [];
|
|
53
|
+
let start = 0;
|
|
54
|
+
let bytes = 0;
|
|
55
|
+
let i = 0;
|
|
56
|
+
while (i < body.length) {
|
|
57
|
+
const step = codePointBytes(body, i);
|
|
58
|
+
if (bytes + step.bytes > max) {
|
|
59
|
+
pieces.push(body.slice(start, i));
|
|
60
|
+
start = i;
|
|
61
|
+
bytes = 0;
|
|
62
|
+
}
|
|
63
|
+
bytes += step.bytes;
|
|
64
|
+
i += step.units;
|
|
65
|
+
}
|
|
66
|
+
pieces.push(body.slice(start));
|
|
67
|
+
return pieces;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function frame(verb: TspVerb, params: string, body: string): string {
|
|
71
|
+
return `${TSP_PREFIX}${verb}${params};${body}${ST}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function encodeParams(params: TspParams | undefined): string {
|
|
75
|
+
if (!params) return "";
|
|
76
|
+
let out = "";
|
|
77
|
+
for (const key in params) out += `;${key}=${params[key]}`;
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Encode one logical message, chunked when `body` exceeds `limit` UTF-8 bytes.
|
|
83
|
+
* Returns the complete byte string to write (all chunks, in order).
|
|
84
|
+
*/
|
|
85
|
+
export function encodeTspMessage(
|
|
86
|
+
verb: TspVerb,
|
|
87
|
+
body: string,
|
|
88
|
+
params?: TspParams,
|
|
89
|
+
limit: number = TSP_DEFAULT_APC_LIMIT,
|
|
90
|
+
): string {
|
|
91
|
+
const base = encodeParams(params);
|
|
92
|
+
// Fast path: ASCII-length bound first, exact byte count only near the limit.
|
|
93
|
+
if (body.length * 3 <= limit || Buffer.byteLength(body, "utf8") <= limit) return frame(verb, base, body);
|
|
94
|
+
const chunkId = (nextChunkId++).toString(36);
|
|
95
|
+
const pieces = splitUtf8(body, limit);
|
|
96
|
+
let out = "";
|
|
97
|
+
for (let i = 0; i < pieces.length; i++) {
|
|
98
|
+
const more = i < pieces.length - 1 ? ";m=1" : "";
|
|
99
|
+
out += frame(verb, `${base};c=${chunkId}${more}`, pieces[i]!);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Encode a JSON message body. */
|
|
105
|
+
export function encodeTspJson(verb: TspVerb, value: unknown, params?: TspParams, limit?: number): string {
|
|
106
|
+
return encodeTspMessage(verb, JSON.stringify(value), params, limit);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The `hello` query; callers follow it with a DA1 sentinel. */
|
|
110
|
+
export function encodeTspHelloQuery(version?: string): string {
|
|
111
|
+
return encodeTspJson("q", { q: "hello", v: [TSP_VERSION], app: "omp", ver: version });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** One decoded APC message: verb, parameters and raw body. */
|
|
115
|
+
export interface TspRawMessage {
|
|
116
|
+
verb: string;
|
|
117
|
+
params: Record<string, string>;
|
|
118
|
+
body: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const PARAM_PATTERN = /^[A-Za-z0-9_-]+=[\x21-\x3a\x3c-\x7e]*$/;
|
|
122
|
+
|
|
123
|
+
/** Split a complete `ESC _ tsp;… ESC \` string into verb, parameters and body; null when it isn't one. */
|
|
124
|
+
export function splitTspMessage(sequence: string): TspRawMessage | null {
|
|
125
|
+
if (!sequence.startsWith(TSP_PREFIX) || !sequence.endsWith(ST)) return null;
|
|
126
|
+
const inner = sequence.slice(TSP_PREFIX.length, -ST.length);
|
|
127
|
+
let semi = inner.indexOf(";");
|
|
128
|
+
if (semi <= 0) return semi === -1 && inner.length > 0 ? { verb: inner, params: {}, body: "" } : null;
|
|
129
|
+
const verb = inner.slice(0, semi);
|
|
130
|
+
const params: Record<string, string> = {};
|
|
131
|
+
let pos = semi + 1;
|
|
132
|
+
// A segment is a parameter only when another `;` follows it; the body is the rest.
|
|
133
|
+
for (;;) {
|
|
134
|
+
semi = inner.indexOf(";", pos);
|
|
135
|
+
if (semi === -1) break;
|
|
136
|
+
const segment = inner.slice(pos, semi);
|
|
137
|
+
if (!PARAM_PATTERN.test(segment)) break;
|
|
138
|
+
const eq = segment.indexOf("=");
|
|
139
|
+
params[segment.slice(0, eq)] = segment.slice(eq + 1);
|
|
140
|
+
pos = semi + 1;
|
|
141
|
+
}
|
|
142
|
+
return { verb, params, body: inner.slice(pos) };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isStringArray(value: unknown): value is string[] {
|
|
146
|
+
return Array.isArray(value) && value.every(item => typeof item === "string");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function decodeReply(value: Record<string, unknown>): TspReply | null {
|
|
150
|
+
if (value.r === "hello") {
|
|
151
|
+
if (typeof value.v !== "number" || typeof value.term !== "string" || !isStringArray(value.kinds)) return null;
|
|
152
|
+
return value as TspHello;
|
|
153
|
+
}
|
|
154
|
+
if (value.r === "blobs") return isStringArray(value.have) ? (value as TspReply) : null;
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const EVENT_REQUIRED: Readonly<Record<string, Readonly<Record<string, "string" | "number" | "boolean" | "array">>>> = {
|
|
159
|
+
ack: { s: "number" },
|
|
160
|
+
resize: { cols: "number" },
|
|
161
|
+
theme: { dark: "boolean" },
|
|
162
|
+
motion: { reduce: "boolean" },
|
|
163
|
+
visible: { visible: "boolean" },
|
|
164
|
+
toggle: { id: "string", collapsed: "boolean" },
|
|
165
|
+
select: { id: "string", item: "string" },
|
|
166
|
+
activate: { id: "string", item: "string" },
|
|
167
|
+
action: { id: "string", act: "string" },
|
|
168
|
+
error: { msg: "string" },
|
|
169
|
+
gone: { ids: "array" },
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
function decodeEvent(value: Record<string, unknown>): TspEvent | null {
|
|
173
|
+
if (typeof value.ev !== "string") return null;
|
|
174
|
+
const required = EVENT_REQUIRED[value.ev];
|
|
175
|
+
if (!required) return null;
|
|
176
|
+
for (const key in required) {
|
|
177
|
+
const want = required[key];
|
|
178
|
+
const got = value[key];
|
|
179
|
+
if (want === "array" ? !isStringArray(got) : typeof got !== want) return null;
|
|
180
|
+
}
|
|
181
|
+
return value as TspEvent;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Reassembles chunked terminal → program messages and decodes replies and
|
|
186
|
+
* events. Unknown verbs, unknown events and unknown fields are tolerated
|
|
187
|
+
* (ignored or passed through); malformed input yields null.
|
|
188
|
+
*/
|
|
189
|
+
export class TspReader {
|
|
190
|
+
#chunks = new Map<string, string>();
|
|
191
|
+
|
|
192
|
+
/** Feed one complete APC string. Returns the decoded message once complete, else null. */
|
|
193
|
+
feed(sequence: string): TspIncoming | null {
|
|
194
|
+
const raw = splitTspMessage(sequence);
|
|
195
|
+
if (!raw) return null;
|
|
196
|
+
let body = raw.body;
|
|
197
|
+
const chunkId = raw.params.c;
|
|
198
|
+
if (chunkId !== undefined) {
|
|
199
|
+
const joined = (this.#chunks.get(chunkId) ?? "") + body;
|
|
200
|
+
if (raw.params.m === "1") {
|
|
201
|
+
this.#chunks.set(chunkId, joined);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
this.#chunks.delete(chunkId);
|
|
205
|
+
body = joined;
|
|
206
|
+
}
|
|
207
|
+
return decodeTspBody(raw.verb, body);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Decode an unchunked reply/event body. Null for malformed JSON, missing fields or program → terminal verbs. */
|
|
212
|
+
export function decodeTspBody(verb: string, body: string): TspIncoming | null {
|
|
213
|
+
if (verb !== "r" && verb !== "e") return null;
|
|
214
|
+
let value: unknown;
|
|
215
|
+
try {
|
|
216
|
+
value = JSON.parse(body);
|
|
217
|
+
} catch {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
if (!isRecord(value)) return null;
|
|
221
|
+
if (verb === "r") {
|
|
222
|
+
const reply = decodeReply(value);
|
|
223
|
+
return reply ? { verb, reply } : null;
|
|
224
|
+
}
|
|
225
|
+
const event = decodeEvent(value);
|
|
226
|
+
return event ? { verb, event } : null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Decode one complete, unchunked terminal → program message. */
|
|
230
|
+
export function parseTspMessage(sequence: string): TspIncoming | null {
|
|
231
|
+
const raw = splitTspMessage(sequence);
|
|
232
|
+
return raw && raw.params.m !== "1" ? decodeTspBody(raw.verb, raw.body) : null;
|
|
233
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon glyph normalization for native frames.
|
|
3
|
+
*
|
|
4
|
+
* Components style icons as Private Use Area codepoints (Nerd Font glyphs)
|
|
5
|
+
* baked into text with a padding space, e.g. `{t:"<glyph> Model", s:"statusLineModel"}`.
|
|
6
|
+
* The terminal draws icons and spaces them itself, so every PUA run becomes
|
|
7
|
+
* its own span carrying the `icon` token next to the original tokens (colour
|
|
8
|
+
* survives), and the single space that padded it from the neighbouring text
|
|
9
|
+
* is dropped. Applied once, centrally, to every `TspText` field of a node's
|
|
10
|
+
* props before they go on the wire.
|
|
11
|
+
*/
|
|
12
|
+
import type { TspKind, TspSpan, TspText } from "@oh-my-pi/pi-wire";
|
|
13
|
+
|
|
14
|
+
const PUA = /[\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
|
|
15
|
+
const PUA_RUNS = /[\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]+/gu;
|
|
16
|
+
|
|
17
|
+
/** `TspText` props per kind (besides the structured ones handled below). */
|
|
18
|
+
const TEXT_FIELDS: Partial<Record<TspKind, readonly string[]>> = {
|
|
19
|
+
text: ["spans"],
|
|
20
|
+
shimmer: ["spans"],
|
|
21
|
+
seg: ["spans"],
|
|
22
|
+
card: ["head"],
|
|
23
|
+
section: ["head"],
|
|
24
|
+
overlay: ["head"],
|
|
25
|
+
rule: ["label"],
|
|
26
|
+
spinner: ["label"],
|
|
27
|
+
progress: ["label"],
|
|
28
|
+
list: ["empty"],
|
|
29
|
+
item: ["label", "detail", "value"],
|
|
30
|
+
editor: ["prompt"],
|
|
31
|
+
input: ["prompt"],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function withIcon(s: string | undefined): string {
|
|
35
|
+
if (s === undefined || s.length === 0) return "icon";
|
|
36
|
+
return s.split(" ").includes("icon") ? s : `${s} icon`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Spans with every PUA run split into an `icon` span; the same array when there is none. */
|
|
40
|
+
export function normalizeIconSpans(spans: readonly TspSpan[]): readonly TspSpan[] {
|
|
41
|
+
let found = false;
|
|
42
|
+
for (const span of spans) {
|
|
43
|
+
if (PUA.test(span.t)) {
|
|
44
|
+
found = true;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!found) return spans;
|
|
49
|
+
const pieces: { span: TspSpan; icon: boolean }[] = [];
|
|
50
|
+
for (const span of spans) {
|
|
51
|
+
let at = 0;
|
|
52
|
+
for (const match of span.t.matchAll(PUA_RUNS)) {
|
|
53
|
+
if (match.index > at) pieces.push({ span: { ...span, t: span.t.slice(at, match.index) }, icon: false });
|
|
54
|
+
pieces.push({ span: { ...span, t: match[0], s: withIcon(span.s) }, icon: true });
|
|
55
|
+
at = match.index + match[0].length;
|
|
56
|
+
}
|
|
57
|
+
if (at < span.t.length) pieces.push({ span: at === 0 ? span : { ...span, t: span.t.slice(at) }, icon: false });
|
|
58
|
+
}
|
|
59
|
+
// The terminal spaces icons itself: drop the one space on each side that padded them.
|
|
60
|
+
for (let i = 0; i < pieces.length; i++) {
|
|
61
|
+
if (!pieces[i]!.icon) continue;
|
|
62
|
+
const prev = pieces[i - 1];
|
|
63
|
+
if (prev && !prev.icon && prev.span.t.endsWith(" ")) prev.span = { ...prev.span, t: prev.span.t.slice(0, -1) };
|
|
64
|
+
const next = pieces[i + 1];
|
|
65
|
+
if (next && !next.icon && next.span.t.startsWith(" ")) next.span = { ...next.span, t: next.span.t.slice(1) };
|
|
66
|
+
}
|
|
67
|
+
const out: TspSpan[] = [];
|
|
68
|
+
for (const piece of pieces) if (piece.span.t.length > 0) out.push(piece.span);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A `TspText` with icons normalized; plain strings with icons become spans. */
|
|
73
|
+
export function normalizeIconText(text: TspText): TspText {
|
|
74
|
+
if (typeof text === "string") return PUA.test(text) ? normalizeIconSpans([{ t: text }]) : text;
|
|
75
|
+
return normalizeIconSpans(text);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isText(value: unknown): value is TspText {
|
|
79
|
+
return typeof value === "string" || Array.isArray(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Map `fn` over `items`, returning the same array when nothing changed. */
|
|
83
|
+
function mapSame<T>(items: readonly T[], fn: (item: T) => T): readonly T[] {
|
|
84
|
+
let out: T[] | undefined;
|
|
85
|
+
for (let i = 0; i < items.length; i++) {
|
|
86
|
+
const next = fn(items[i]!);
|
|
87
|
+
if (next !== items[i] && !out) out = items.slice(0, i);
|
|
88
|
+
out?.push(next);
|
|
89
|
+
}
|
|
90
|
+
return out ?? items;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface TreeNodeLike {
|
|
94
|
+
label?: unknown;
|
|
95
|
+
children?: readonly TreeNodeLike[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function normalizeTree(node: TreeNodeLike): TreeNodeLike {
|
|
99
|
+
const label = isText(node.label) ? normalizeIconText(node.label) : node.label;
|
|
100
|
+
const children = node.children ? mapSame(node.children, normalizeTree) : node.children;
|
|
101
|
+
return label === node.label && children === node.children ? node : { ...node, label, children };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Props with every `TspText` field's icons normalized; the same object when nothing changed. */
|
|
105
|
+
export function normalizeIconProps(
|
|
106
|
+
kind: TspKind,
|
|
107
|
+
props: Readonly<Record<string, unknown>> | undefined,
|
|
108
|
+
): Readonly<Record<string, unknown>> | undefined {
|
|
109
|
+
if (!props) return props;
|
|
110
|
+
let out: Record<string, unknown> | undefined;
|
|
111
|
+
const put = (key: string, value: unknown): void => {
|
|
112
|
+
if (value === props[key]) return;
|
|
113
|
+
out ??= { ...props };
|
|
114
|
+
out[key] = value;
|
|
115
|
+
};
|
|
116
|
+
// A plain `text` label holding an icon becomes spans.
|
|
117
|
+
if ((kind === "text" || kind === "shimmer") && typeof props.text === "string" && PUA.test(props.text)) {
|
|
118
|
+
put("text", undefined);
|
|
119
|
+
put("spans", normalizeIconSpans([{ t: props.text }]));
|
|
120
|
+
}
|
|
121
|
+
for (const key of TEXT_FIELDS[kind] ?? []) {
|
|
122
|
+
const value = (out ?? props)[key];
|
|
123
|
+
if (isText(value)) put(key, normalizeIconText(value));
|
|
124
|
+
}
|
|
125
|
+
switch (kind) {
|
|
126
|
+
case "tabs":
|
|
127
|
+
case "kv": {
|
|
128
|
+
const items = props.items;
|
|
129
|
+
if (!Array.isArray(items)) break;
|
|
130
|
+
put(
|
|
131
|
+
"items",
|
|
132
|
+
mapSame(items as readonly Record<string, unknown>[], item => {
|
|
133
|
+
let next = item;
|
|
134
|
+
for (const field of kind === "tabs" ? ["label"] : ["k", "v"]) {
|
|
135
|
+
const value = item[field];
|
|
136
|
+
if (!isText(value)) continue;
|
|
137
|
+
const normalized = normalizeIconText(value);
|
|
138
|
+
if (normalized !== value) next = { ...next, [field]: normalized };
|
|
139
|
+
}
|
|
140
|
+
return next;
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "table": {
|
|
146
|
+
const cols = props.cols;
|
|
147
|
+
if (Array.isArray(cols)) {
|
|
148
|
+
put(
|
|
149
|
+
"cols",
|
|
150
|
+
mapSame(cols as readonly Record<string, unknown>[], col => {
|
|
151
|
+
if (!isText(col.head)) return col;
|
|
152
|
+
const head = normalizeIconText(col.head);
|
|
153
|
+
return head === col.head ? col : { ...col, head };
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const rows = props.rows;
|
|
158
|
+
if (Array.isArray(rows)) {
|
|
159
|
+
put(
|
|
160
|
+
"rows",
|
|
161
|
+
mapSame(rows as readonly { cells?: Record<string, unknown> }[], row => {
|
|
162
|
+
const cells = row.cells;
|
|
163
|
+
if (!cells) return row;
|
|
164
|
+
let nextCells: Record<string, unknown> | undefined;
|
|
165
|
+
for (const col in cells) {
|
|
166
|
+
const value = cells[col];
|
|
167
|
+
if (!isText(value)) continue;
|
|
168
|
+
const normalized = normalizeIconText(value);
|
|
169
|
+
if (normalized !== value) (nextCells ??= { ...cells })[col] = normalized;
|
|
170
|
+
}
|
|
171
|
+
return nextCells ? { ...row, cells: nextCells } : row;
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case "tree": {
|
|
178
|
+
const nodes = props.nodes;
|
|
179
|
+
if (Array.isArray(nodes)) put("nodes", mapSame(nodes as readonly TreeNodeLike[], normalizeTree));
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return out ?? props;
|
|
184
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memoization for `describe()`: an unchanged component must hand the
|
|
3
|
+
* reconciler the same node objects, so it can skip the subtree. {@link Memo}
|
|
4
|
+
* is the one primitive; {@link OwnerMemo} keys it by an object for stateless
|
|
5
|
+
* renderers (tool describe hooks) that re-describe the same args/result.
|
|
6
|
+
*/
|
|
7
|
+
import type { NativeNode } from "./node";
|
|
8
|
+
|
|
9
|
+
/** Element-wise identity (`Object.is`) of two arrays; `undefined` never matches. */
|
|
10
|
+
export function sameItems<T>(a: readonly T[] | undefined, b: readonly T[]): boolean {
|
|
11
|
+
if (a === b) return true;
|
|
12
|
+
if (a === undefined || a.length !== b.length) return false;
|
|
13
|
+
for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Shallow equality of two flat prop bags built by successive describe calls. */
|
|
18
|
+
export function sameProps<T extends object>(a: T | undefined, b: T): boolean {
|
|
19
|
+
if (a === undefined) return false;
|
|
20
|
+
for (const key in b) if (a[key] !== b[key]) return false;
|
|
21
|
+
for (const key in a) if (!(key in b)) return false;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Keeps the last built value while every dependency is unchanged (`Object.is` per slot). */
|
|
26
|
+
export class Memo<T = NativeNode> {
|
|
27
|
+
#deps: readonly unknown[] | undefined;
|
|
28
|
+
#value: T | undefined;
|
|
29
|
+
|
|
30
|
+
get(deps: readonly unknown[], build: () => T): T {
|
|
31
|
+
if (this.#deps !== undefined && sameItems(this.#deps, deps)) return this.#value as T;
|
|
32
|
+
const value = build();
|
|
33
|
+
this.#deps = deps;
|
|
34
|
+
this.#value = value;
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Forget the cached value (theme, glyph preset or keybindings changed). */
|
|
39
|
+
clear(): void {
|
|
40
|
+
this.#deps = undefined;
|
|
41
|
+
this.#value = undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* One {@link Memo} per owner object, kept in a symbol slot on the owner; each
|
|
47
|
+
* instance owns its own slot, so call and result views of one object don't
|
|
48
|
+
* collide. Builds uncached without an owner or when the owner is frozen.
|
|
49
|
+
*/
|
|
50
|
+
export class OwnerMemo<T> {
|
|
51
|
+
readonly #slot = Symbol("native.memo");
|
|
52
|
+
|
|
53
|
+
get(owner: object | undefined, deps: readonly unknown[], build: () => T): T {
|
|
54
|
+
if (!owner) return build();
|
|
55
|
+
const tagged = owner as { [slot: symbol]: Memo<T> | undefined };
|
|
56
|
+
let memo = tagged[this.#slot];
|
|
57
|
+
if (!memo) {
|
|
58
|
+
if (!Object.isExtensible(owner)) return build();
|
|
59
|
+
memo = new Memo<T>();
|
|
60
|
+
Object.defineProperty(owner, this.#slot, { value: memo, configurable: true });
|
|
61
|
+
}
|
|
62
|
+
return memo.get(deps, build);
|
|
63
|
+
}
|
|
64
|
+
}
|