@vandeepunk/pi-coding-agent 0.0.1
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 +2564 -0
- package/README.md +555 -0
- package/dist/cli/args.d.ts +47 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +286 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/config-selector.d.ts +14 -0
- package/dist/cli/config-selector.d.ts.map +1 -0
- package/dist/cli/config-selector.js +31 -0
- package/dist/cli/config-selector.js.map +1 -0
- package/dist/cli/file-processor.d.ts +15 -0
- package/dist/cli/file-processor.d.ts.map +1 -0
- package/dist/cli/file-processor.js +79 -0
- package/dist/cli/file-processor.js.map +1 -0
- package/dist/cli/list-models.d.ts +9 -0
- package/dist/cli/list-models.d.ts.map +1 -0
- package/dist/cli/list-models.js +92 -0
- package/dist/cli/list-models.js.map +1 -0
- package/dist/cli/session-picker.d.ts +9 -0
- package/dist/cli/session-picker.d.ts.map +1 -0
- package/dist/cli/session-picker.js +34 -0
- package/dist/cli/session-picker.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +68 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +203 -0
- package/dist/config.js.map +1 -0
- package/dist/core/agent-session.d.ts +574 -0
- package/dist/core/agent-session.d.ts.map +1 -0
- package/dist/core/agent-session.js +2260 -0
- package/dist/core/agent-session.js.map +1 -0
- package/dist/core/auth-storage.d.ts +102 -0
- package/dist/core/auth-storage.d.ts.map +1 -0
- package/dist/core/auth-storage.js +282 -0
- package/dist/core/auth-storage.js.map +1 -0
- package/dist/core/bash-executor.d.ts +47 -0
- package/dist/core/bash-executor.d.ts.map +1 -0
- package/dist/core/bash-executor.js +212 -0
- package/dist/core/bash-executor.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts +86 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/core/compaction/branch-summarization.js +242 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts +121 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -0
- package/dist/core/compaction/compaction.js +607 -0
- package/dist/core/compaction/compaction.js.map +1 -0
- package/dist/core/compaction/index.d.ts +7 -0
- package/dist/core/compaction/index.d.ts.map +1 -0
- package/dist/core/compaction/index.js +7 -0
- package/dist/core/compaction/index.js.map +1 -0
- package/dist/core/compaction/utils.d.ts +35 -0
- package/dist/core/compaction/utils.d.ts.map +1 -0
- package/dist/core/compaction/utils.js +138 -0
- package/dist/core/compaction/utils.js.map +1 -0
- package/dist/core/defaults.d.ts +3 -0
- package/dist/core/defaults.d.ts.map +1 -0
- package/dist/core/defaults.js +2 -0
- package/dist/core/defaults.js.map +1 -0
- package/dist/core/diagnostics.d.ts +15 -0
- package/dist/core/diagnostics.d.ts.map +1 -0
- package/dist/core/diagnostics.js +2 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/event-bus.d.ts +9 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +25 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/exec.d.ts +29 -0
- package/dist/core/exec.d.ts.map +1 -0
- package/dist/core/exec.js +71 -0
- package/dist/core/exec.js.map +1 -0
- package/dist/core/export-html/ansi-to-html.d.ts +22 -0
- package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
- package/dist/core/export-html/ansi-to-html.js +249 -0
- package/dist/core/export-html/ansi-to-html.js.map +1 -0
- package/dist/core/export-html/index.d.ts +34 -0
- package/dist/core/export-html/index.d.ts.map +1 -0
- package/dist/core/export-html/index.js +222 -0
- package/dist/core/export-html/index.js.map +1 -0
- package/dist/core/export-html/template.css +909 -0
- package/dist/core/export-html/template.html +54 -0
- package/dist/core/export-html/template.js +1549 -0
- package/dist/core/export-html/tool-renderer.d.ts +35 -0
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
- package/dist/core/export-html/tool-renderer.js +57 -0
- package/dist/core/export-html/tool-renderer.js.map +1 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +6 -0
- package/dist/core/extensions/index.d.ts +11 -0
- package/dist/core/extensions/index.d.ts.map +1 -0
- package/dist/core/extensions/index.js +9 -0
- package/dist/core/extensions/index.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +25 -0
- package/dist/core/extensions/loader.d.ts.map +1 -0
- package/dist/core/extensions/loader.js +400 -0
- package/dist/core/extensions/loader.js.map +1 -0
- package/dist/core/extensions/runner.d.ts +129 -0
- package/dist/core/extensions/runner.d.ts.map +1 -0
- package/dist/core/extensions/runner.js +576 -0
- package/dist/core/extensions/runner.js.map +1 -0
- package/dist/core/extensions/types.d.ts +928 -0
- package/dist/core/extensions/types.d.ts.map +1 -0
- package/dist/core/extensions/types.js +35 -0
- package/dist/core/extensions/types.js.map +1 -0
- package/dist/core/extensions/wrapper.d.ts +27 -0
- package/dist/core/extensions/wrapper.d.ts.map +1 -0
- package/dist/core/extensions/wrapper.js +102 -0
- package/dist/core/extensions/wrapper.js.map +1 -0
- package/dist/core/footer-data-provider.d.ts +32 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -0
- package/dist/core/footer-data-provider.js +134 -0
- package/dist/core/footer-data-provider.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/keybindings.d.ts +55 -0
- package/dist/core/keybindings.d.ts.map +1 -0
- package/dist/core/keybindings.js +153 -0
- package/dist/core/keybindings.js.map +1 -0
- package/dist/core/messages.d.ts +77 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/messages.js +123 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/model-registry.d.ts +100 -0
- package/dist/core/model-registry.d.ts.map +1 -0
- package/dist/core/model-registry.js +419 -0
- package/dist/core/model-registry.js.map +1 -0
- package/dist/core/model-resolver.d.ts +76 -0
- package/dist/core/model-resolver.d.ts.map +1 -0
- package/dist/core/model-resolver.js +313 -0
- package/dist/core/model-resolver.js.map +1 -0
- package/dist/core/package-manager.d.ts +131 -0
- package/dist/core/package-manager.d.ts.map +1 -0
- package/dist/core/package-manager.js +1290 -0
- package/dist/core/package-manager.js.map +1 -0
- package/dist/core/prompt-templates.d.ts +50 -0
- package/dist/core/prompt-templates.d.ts.map +1 -0
- package/dist/core/prompt-templates.js +251 -0
- package/dist/core/prompt-templates.js.map +1 -0
- package/dist/core/resolve-config-value.d.ts +17 -0
- package/dist/core/resolve-config-value.d.ts.map +1 -0
- package/dist/core/resolve-config-value.js +59 -0
- package/dist/core/resolve-config-value.js.map +1 -0
- package/dist/core/resource-loader.d.ts +184 -0
- package/dist/core/resource-loader.d.ts.map +1 -0
- package/dist/core/resource-loader.js +673 -0
- package/dist/core/resource-loader.js.map +1 -0
- package/dist/core/sdk.d.ts +90 -0
- package/dist/core/sdk.d.ts.map +1 -0
- package/dist/core/sdk.js +234 -0
- package/dist/core/sdk.js.map +1 -0
- package/dist/core/session-manager.d.ts +323 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/core/session-manager.js +1091 -0
- package/dist/core/session-manager.js.map +1 -0
- package/dist/core/settings-manager.d.ts +187 -0
- package/dist/core/settings-manager.d.ts.map +1 -0
- package/dist/core/settings-manager.js +552 -0
- package/dist/core/settings-manager.js.map +1 -0
- package/dist/core/skills.d.ts +58 -0
- package/dist/core/skills.d.ts.map +1 -0
- package/dist/core/skills.js +310 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/slash-commands.d.ts +15 -0
- package/dist/core/slash-commands.d.ts.map +1 -0
- package/dist/core/slash-commands.js +21 -0
- package/dist/core/slash-commands.js.map +1 -0
- package/dist/core/system-prompt.d.ts +24 -0
- package/dist/core/system-prompt.d.ts.map +1 -0
- package/dist/core/system-prompt.js +137 -0
- package/dist/core/system-prompt.js.map +1 -0
- package/dist/core/timings.d.ts +7 -0
- package/dist/core/timings.d.ts.map +1 -0
- package/dist/core/timings.js +25 -0
- package/dist/core/timings.js.map +1 -0
- package/dist/core/tools/bash.d.ts +55 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/core/tools/bash.js +242 -0
- package/dist/core/tools/bash.js.map +1 -0
- package/dist/core/tools/edit-diff.d.ts +63 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -0
- package/dist/core/tools/edit-diff.js +243 -0
- package/dist/core/tools/edit-diff.js.map +1 -0
- package/dist/core/tools/edit.d.ts +39 -0
- package/dist/core/tools/edit.d.ts.map +1 -0
- package/dist/core/tools/edit.js +146 -0
- package/dist/core/tools/edit.js.map +1 -0
- package/dist/core/tools/find.d.ts +39 -0
- package/dist/core/tools/find.d.ts.map +1 -0
- package/dist/core/tools/find.js +206 -0
- package/dist/core/tools/find.js.map +1 -0
- package/dist/core/tools/grep.d.ts +45 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +239 -0
- package/dist/core/tools/grep.js.map +1 -0
- package/dist/core/tools/index.d.ts +73 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +61 -0
- package/dist/core/tools/index.js.map +1 -0
- package/dist/core/tools/ls.d.ts +40 -0
- package/dist/core/tools/ls.d.ts.map +1 -0
- package/dist/core/tools/ls.js +118 -0
- package/dist/core/tools/ls.js.map +1 -0
- package/dist/core/tools/path-utils.d.ts +8 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -0
- package/dist/core/tools/path-utils.js +81 -0
- package/dist/core/tools/path-utils.js.map +1 -0
- package/dist/core/tools/read.d.ts +39 -0
- package/dist/core/tools/read.d.ts.map +1 -0
- package/dist/core/tools/read.js +166 -0
- package/dist/core/tools/read.js.map +1 -0
- package/dist/core/tools/truncate.d.ts +70 -0
- package/dist/core/tools/truncate.d.ts.map +1 -0
- package/dist/core/tools/truncate.js +205 -0
- package/dist/core/tools/truncate.js.map +1 -0
- package/dist/core/tools/write.d.ts +29 -0
- package/dist/core/tools/write.d.ts.map +1 -0
- package/dist/core/tools/write.js +78 -0
- package/dist/core/tools/write.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +8 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +623 -0
- package/dist/main.js.map +1 -0
- package/dist/migrations.d.ts +33 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +261 -0
- package/dist/migrations.js.map +1 -0
- package/dist/modes/index.d.ts +9 -0
- package/dist/modes/index.d.ts.map +1 -0
- package/dist/modes/index.js +8 -0
- package/dist/modes/index.js.map +1 -0
- package/dist/modes/interactive/components/armin.d.ts +34 -0
- package/dist/modes/interactive/components/armin.d.ts.map +1 -0
- package/dist/modes/interactive/components/armin.js +333 -0
- package/dist/modes/interactive/components/armin.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts +16 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/assistant-message.js +91 -0
- package/dist/modes/interactive/components/assistant-message.js.map +1 -0
- package/dist/modes/interactive/components/bash-execution.d.ts +35 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
- package/dist/modes/interactive/components/bash-execution.js +162 -0
- package/dist/modes/interactive/components/bash-execution.js.map +1 -0
- package/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
- package/dist/modes/interactive/components/bordered-loader.js +51 -0
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/branch-summary-message.js +44 -0
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/compaction-summary-message.js +45 -0
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
- package/dist/modes/interactive/components/config-selector.d.ts +71 -0
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/config-selector.js +479 -0
- package/dist/modes/interactive/components/config-selector.js.map +1 -0
- package/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
- package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
- package/dist/modes/interactive/components/countdown-timer.js +33 -0
- package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts +21 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
- package/dist/modes/interactive/components/custom-editor.js +70 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -0
- package/dist/modes/interactive/components/custom-message.d.ts +20 -0
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/custom-message.js +79 -0
- package/dist/modes/interactive/components/custom-message.js.map +1 -0
- package/dist/modes/interactive/components/daxnuts.d.ts +23 -0
- package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -0
- package/dist/modes/interactive/components/daxnuts.js +140 -0
- package/dist/modes/interactive/components/daxnuts.js.map +1 -0
- package/dist/modes/interactive/components/diff.d.ts +12 -0
- package/dist/modes/interactive/components/diff.d.ts.map +1 -0
- package/dist/modes/interactive/components/diff.js +133 -0
- package/dist/modes/interactive/components/diff.js.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
- package/dist/modes/interactive/components/dynamic-border.js +21 -0
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
- package/dist/modes/interactive/components/extension-editor.d.ts +17 -0
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
- package/dist/modes/interactive/components/extension-editor.js +102 -0
- package/dist/modes/interactive/components/extension-editor.js.map +1 -0
- package/dist/modes/interactive/components/extension-input.d.ts +23 -0
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
- package/dist/modes/interactive/components/extension-input.js +61 -0
- package/dist/modes/interactive/components/extension-input.js.map +1 -0
- package/dist/modes/interactive/components/extension-selector.d.ts +24 -0
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/extension-selector.js +78 -0
- package/dist/modes/interactive/components/extension-selector.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +26 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -0
- package/dist/modes/interactive/components/footer.js +220 -0
- package/dist/modes/interactive/components/footer.js.map +1 -0
- package/dist/modes/interactive/components/index.d.ts +32 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -0
- package/dist/modes/interactive/components/index.js +33 -0
- package/dist/modes/interactive/components/index.js.map +1 -0
- package/dist/modes/interactive/components/keybinding-hints.d.ts +41 -0
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
- package/dist/modes/interactive/components/keybinding-hints.js +61 -0
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
- package/dist/modes/interactive/components/login-dialog.d.ts +42 -0
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -0
- package/dist/modes/interactive/components/login-dialog.js +145 -0
- package/dist/modes/interactive/components/login-dialog.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +47 -0
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/model-selector.js +266 -0
- package/dist/modes/interactive/components/model-selector.js.map +1 -0
- package/dist/modes/interactive/components/oauth-selector.d.ts +19 -0
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/oauth-selector.js +97 -0
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +49 -0
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/scoped-models-selector.js +270 -0
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
- package/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
- package/dist/modes/interactive/components/session-selector-search.js +155 -0
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts +95 -0
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/session-selector.js +851 -0
- package/dist/modes/interactive/components/session-selector.js.map +1 -0
- package/dist/modes/interactive/components/settings-selector.d.ts +53 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/settings-selector.js +277 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -0
- package/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
- package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/show-images-selector.js +35 -0
- package/dist/modes/interactive/components/show-images-selector.js.map +1 -0
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
- package/dist/modes/interactive/components/theme-selector.d.ts +11 -0
- package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/theme-selector.js +46 -0
- package/dist/modes/interactive/components/theme-selector.js.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.js +47 -0
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts +70 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-execution.js +621 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -0
- package/dist/modes/interactive/components/tree-selector.d.ts +68 -0
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/tree-selector.js +934 -0
- package/dist/modes/interactive/components/tree-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/user-message-selector.js +113 -0
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message.d.ts +8 -0
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/user-message.js +16 -0
- package/dist/modes/interactive/components/user-message.js.map +1 -0
- package/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
- package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -0
- package/dist/modes/interactive/components/visual-truncate.js +33 -0
- package/dist/modes/interactive/components/visual-truncate.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +313 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-mode.js +3664 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -0
- package/dist/modes/interactive/theme/dark.json +85 -0
- package/dist/modes/interactive/theme/light.json +84 -0
- package/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/dist/modes/interactive/theme/theme.d.ts +78 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme.js +944 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -0
- package/dist/modes/print-mode.d.ts +28 -0
- package/dist/modes/print-mode.d.ts.map +1 -0
- package/dist/modes/print-mode.js +98 -0
- package/dist/modes/print-mode.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +217 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-client.js +405 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -0
- package/dist/modes/rpc/rpc-mode.d.ts +20 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-mode.js +500 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -0
- package/dist/modes/rpc/rpc-types.d.ts +409 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-types.js +8 -0
- package/dist/modes/rpc/rpc-types.js.map +1 -0
- package/dist/utils/changelog.d.ts +21 -0
- package/dist/utils/changelog.d.ts.map +1 -0
- package/dist/utils/changelog.js +87 -0
- package/dist/utils/changelog.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts +11 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -0
- package/dist/utils/clipboard-image.js +162 -0
- package/dist/utils/clipboard-image.js.map +1 -0
- package/dist/utils/clipboard-native.d.ts +7 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -0
- package/dist/utils/clipboard-native.js +14 -0
- package/dist/utils/clipboard-native.js.map +1 -0
- package/dist/utils/clipboard.d.ts +2 -0
- package/dist/utils/clipboard.d.ts.map +1 -0
- package/dist/utils/clipboard.js +67 -0
- package/dist/utils/clipboard.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +8 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +26 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/git.d.ts +2 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +6 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/image-convert.d.ts +9 -0
- package/dist/utils/image-convert.d.ts.map +1 -0
- package/dist/utils/image-convert.js +35 -0
- package/dist/utils/image-convert.js.map +1 -0
- package/dist/utils/image-resize.d.ts +36 -0
- package/dist/utils/image-resize.d.ts.map +1 -0
- package/dist/utils/image-resize.js +181 -0
- package/dist/utils/image-resize.js.map +1 -0
- package/dist/utils/mime.d.ts +2 -0
- package/dist/utils/mime.d.ts.map +1 -0
- package/dist/utils/mime.js +26 -0
- package/dist/utils/mime.js.map +1 -0
- package/dist/utils/photon.d.ts +21 -0
- package/dist/utils/photon.d.ts.map +1 -0
- package/dist/utils/photon.js +121 -0
- package/dist/utils/photon.js.map +1 -0
- package/dist/utils/shell.d.ts +26 -0
- package/dist/utils/shell.d.ts.map +1 -0
- package/dist/utils/shell.js +186 -0
- package/dist/utils/shell.js.map +1 -0
- package/dist/utils/sleep.d.ts +5 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +17 -0
- package/dist/utils/sleep.js.map +1 -0
- package/dist/utils/tools-manager.d.ts +3 -0
- package/dist/utils/tools-manager.d.ts.map +1 -0
- package/dist/utils/tools-manager.js +201 -0
- package/dist/utils/tools-manager.js.map +1 -0
- package/docs/compaction.md +390 -0
- package/docs/custom-provider.md +539 -0
- package/docs/development.md +69 -0
- package/docs/extensions.md +1827 -0
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/exy.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/json.md +79 -0
- package/docs/keybindings.md +174 -0
- package/docs/models.md +254 -0
- package/docs/packages.md +191 -0
- package/docs/prompt-templates.md +67 -0
- package/docs/providers.md +168 -0
- package/docs/rpc.md +1311 -0
- package/docs/sdk.md +957 -0
- package/docs/session.md +412 -0
- package/docs/settings.md +221 -0
- package/docs/shell-aliases.md +13 -0
- package/docs/skills.md +227 -0
- package/docs/terminal-setup.md +70 -0
- package/docs/termux.md +127 -0
- package/docs/themes.md +295 -0
- package/docs/tree.md +219 -0
- package/docs/tui.md +887 -0
- package/docs/windows.md +17 -0
- package/examples/README.md +25 -0
- package/examples/extensions/README.md +202 -0
- package/examples/extensions/antigravity-image-gen.ts +413 -0
- package/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/examples/extensions/bash-spawn-hook.ts +30 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/claude-rules.ts +86 -0
- package/examples/extensions/commands.ts +72 -0
- package/examples/extensions/confirm-destructive.ts +59 -0
- package/examples/extensions/custom-compaction.ts +114 -0
- package/examples/extensions/custom-footer.ts +64 -0
- package/examples/extensions/custom-header.ts +73 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
- package/examples/extensions/custom-provider-qwen-cli/index.ts +345 -0
- package/examples/extensions/custom-provider-qwen-cli/package.json +16 -0
- package/examples/extensions/dirty-repo-guard.ts +56 -0
- package/examples/extensions/doom-overlay/README.md +46 -0
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/examples/extensions/doom-overlay/doom-component.ts +132 -0
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
- package/examples/extensions/doom-overlay/index.ts +74 -0
- package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
- package/examples/extensions/dynamic-resources/SKILL.md +8 -0
- package/examples/extensions/dynamic-resources/dynamic.json +79 -0
- package/examples/extensions/dynamic-resources/dynamic.md +5 -0
- package/examples/extensions/dynamic-resources/index.ts +15 -0
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/file-trigger.ts +41 -0
- package/examples/extensions/git-checkpoint.ts +53 -0
- package/examples/extensions/handoff.ts +150 -0
- package/examples/extensions/hello.ts +25 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/input-transform.ts +43 -0
- package/examples/extensions/interactive-shell.ts +196 -0
- package/examples/extensions/mac-system-theme.ts +47 -0
- package/examples/extensions/message-renderer.ts +59 -0
- package/examples/extensions/minimal-mode.ts +426 -0
- package/examples/extensions/modal-editor.ts +85 -0
- package/examples/extensions/model-status.ts +31 -0
- package/examples/extensions/notify.ts +55 -0
- package/examples/extensions/overlay-qa-tests.ts +881 -0
- package/examples/extensions/overlay-test.ts +150 -0
- package/examples/extensions/permission-gate.ts +34 -0
- package/examples/extensions/pirate.ts +47 -0
- package/examples/extensions/plan-mode/README.md +65 -0
- package/examples/extensions/plan-mode/index.ts +340 -0
- package/examples/extensions/plan-mode/utils.ts +168 -0
- package/examples/extensions/preset.ts +398 -0
- package/examples/extensions/protected-paths.ts +30 -0
- package/examples/extensions/qna.ts +119 -0
- package/examples/extensions/question.ts +264 -0
- package/examples/extensions/questionnaire.ts +427 -0
- package/examples/extensions/rainbow-editor.ts +88 -0
- package/examples/extensions/rpc-demo.ts +124 -0
- package/examples/extensions/sandbox/index.ts +318 -0
- package/examples/extensions/sandbox/package-lock.json +92 -0
- package/examples/extensions/sandbox/package.json +19 -0
- package/examples/extensions/send-user-message.ts +97 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/shutdown-command.ts +63 -0
- package/examples/extensions/snake.ts +343 -0
- package/examples/extensions/space-invaders.ts +560 -0
- package/examples/extensions/ssh.ts +220 -0
- package/examples/extensions/status-line.ts +40 -0
- package/examples/extensions/subagent/README.md +172 -0
- package/examples/extensions/subagent/agents/planner.md +37 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/subagent/agents/scout.md +50 -0
- package/examples/extensions/subagent/agents/worker.md +24 -0
- package/examples/extensions/subagent/agents.ts +127 -0
- package/examples/extensions/subagent/index.ts +963 -0
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/examples/extensions/subagent/prompts/implement.md +10 -0
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/examples/extensions/summarize.ts +195 -0
- package/examples/extensions/system-prompt-header.ts +17 -0
- package/examples/extensions/timed-confirm.ts +70 -0
- package/examples/extensions/titlebar-spinner.ts +58 -0
- package/examples/extensions/todo.ts +299 -0
- package/examples/extensions/tool-override.ts +143 -0
- package/examples/extensions/tools.ts +146 -0
- package/examples/extensions/trigger-compact.ts +40 -0
- package/examples/extensions/truncated-tool.ts +192 -0
- package/examples/extensions/widget-placement.ts +17 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +22 -0
- package/examples/rpc-extension-ui.ts +632 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +55 -0
- package/examples/sdk/04-skills.ts +46 -0
- package/examples/sdk/05-tools.ts +56 -0
- package/examples/sdk/06-extensions.ts +88 -0
- package/examples/sdk/07-context-files.ts +40 -0
- package/examples/sdk/08-prompt-templates.ts +47 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +82 -0
- package/examples/sdk/README.md +144 -0
- package/package.json +97 -0
package/docs/tui.md
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
> pi can create TUI components. Ask it to build one for your use case.
|
|
2
|
+
|
|
3
|
+
# TUI Components
|
|
4
|
+
|
|
5
|
+
Extensions and custom tools can render custom TUI components for interactive user interfaces. This page covers the component system and available building blocks.
|
|
6
|
+
|
|
7
|
+
**Source:** [`@mariozechner/pi-tui`](https://github.com/badlogic/pi-mono/tree/main/packages/tui)
|
|
8
|
+
|
|
9
|
+
## Component Interface
|
|
10
|
+
|
|
11
|
+
All components implement:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
interface Component {
|
|
15
|
+
render(width: number): string[];
|
|
16
|
+
handleInput?(data: string): void;
|
|
17
|
+
wantsKeyRelease?: boolean;
|
|
18
|
+
invalidate(): void;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Method | Description |
|
|
23
|
+
|--------|-------------|
|
|
24
|
+
| `render(width)` | Return array of strings (one per line). Each line **must not exceed `width`**. |
|
|
25
|
+
| `handleInput?(data)` | Receive keyboard input when component has focus. |
|
|
26
|
+
| `wantsKeyRelease?` | If true, component receives key release events (Kitty protocol). Default: false. |
|
|
27
|
+
| `invalidate()` | Clear cached render state. Called on theme changes. |
|
|
28
|
+
|
|
29
|
+
The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
|
|
30
|
+
|
|
31
|
+
## Focusable Interface (IME Support)
|
|
32
|
+
|
|
33
|
+
Components that display a text cursor and need IME (Input Method Editor) support should implement the `Focusable` interface:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { CURSOR_MARKER, type Component, type Focusable } from "@mariozechner/pi-tui";
|
|
37
|
+
|
|
38
|
+
class MyInput implements Component, Focusable {
|
|
39
|
+
focused: boolean = false; // Set by TUI when focus changes
|
|
40
|
+
|
|
41
|
+
render(width: number): string[] {
|
|
42
|
+
const marker = this.focused ? CURSOR_MARKER : "";
|
|
43
|
+
// Emit marker right before the fake cursor
|
|
44
|
+
return [`> ${beforeCursor}${marker}\x1b[7m${atCursor}\x1b[27m${afterCursor}`];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
When a `Focusable` component has focus, TUI:
|
|
50
|
+
1. Sets `focused = true` on the component
|
|
51
|
+
2. Scans rendered output for `CURSOR_MARKER` (a zero-width APC escape sequence)
|
|
52
|
+
3. Positions the hardware terminal cursor at that location
|
|
53
|
+
4. Shows the hardware cursor
|
|
54
|
+
|
|
55
|
+
This enables IME candidate windows to appear at the correct position for CJK input methods. The `Editor` and `Input` built-in components already implement this interface.
|
|
56
|
+
|
|
57
|
+
### Container Components with Embedded Inputs
|
|
58
|
+
|
|
59
|
+
When a container component (dialog, selector, etc.) contains an `Input` or `Editor` child, the container must implement `Focusable` and propagate the focus state to the child. Otherwise, the hardware cursor won't be positioned correctly for IME input.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { Container, type Focusable, Input } from "@mariozechner/pi-tui";
|
|
63
|
+
|
|
64
|
+
class SearchDialog extends Container implements Focusable {
|
|
65
|
+
private searchInput: Input;
|
|
66
|
+
|
|
67
|
+
// Focusable implementation - propagate to child input for IME cursor positioning
|
|
68
|
+
private _focused = false;
|
|
69
|
+
get focused(): boolean {
|
|
70
|
+
return this._focused;
|
|
71
|
+
}
|
|
72
|
+
set focused(value: boolean) {
|
|
73
|
+
this._focused = value;
|
|
74
|
+
this.searchInput.focused = value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
constructor() {
|
|
78
|
+
super();
|
|
79
|
+
this.searchInput = new Input();
|
|
80
|
+
this.addChild(this.searchInput);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Without this propagation, typing with an IME (Chinese, Japanese, Korean, etc.) will show the candidate window in the wrong position on screen.
|
|
86
|
+
|
|
87
|
+
## Using Components
|
|
88
|
+
|
|
89
|
+
**In extensions** via `ctx.ui.custom()`:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
93
|
+
const handle = ctx.ui.custom(myComponent);
|
|
94
|
+
// handle.requestRender() - trigger re-render
|
|
95
|
+
// handle.close() - restore normal UI
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**In custom tools** via `pi.ui.custom()`:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
|
103
|
+
const handle = pi.ui.custom(myComponent);
|
|
104
|
+
// ...
|
|
105
|
+
handle.close();
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Overlays
|
|
110
|
+
|
|
111
|
+
Overlays render components on top of existing content without clearing the screen. Pass `{ overlay: true }` to `ctx.ui.custom()`:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const result = await ctx.ui.custom<string | null>(
|
|
115
|
+
(tui, theme, keybindings, done) => new MyDialog({ onClose: done }),
|
|
116
|
+
{ overlay: true }
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For positioning and sizing, use `overlayOptions`:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const result = await ctx.ui.custom<string | null>(
|
|
124
|
+
(tui, theme, keybindings, done) => new SidePanel({ onClose: done }),
|
|
125
|
+
{
|
|
126
|
+
overlay: true,
|
|
127
|
+
overlayOptions: {
|
|
128
|
+
// Size: number or percentage string
|
|
129
|
+
width: "50%", // 50% of terminal width
|
|
130
|
+
minWidth: 40, // minimum 40 columns
|
|
131
|
+
maxHeight: "80%", // max 80% of terminal height
|
|
132
|
+
|
|
133
|
+
// Position: anchor-based (default: "center")
|
|
134
|
+
anchor: "right-center", // 9 positions: center, top-left, top-center, etc.
|
|
135
|
+
offsetX: -2, // offset from anchor
|
|
136
|
+
offsetY: 0,
|
|
137
|
+
|
|
138
|
+
// Or percentage/absolute positioning
|
|
139
|
+
row: "25%", // 25% from top
|
|
140
|
+
col: 10, // column 10
|
|
141
|
+
|
|
142
|
+
// Margins
|
|
143
|
+
margin: 2, // all sides, or { top, right, bottom, left }
|
|
144
|
+
|
|
145
|
+
// Responsive: hide on narrow terminals
|
|
146
|
+
visible: (termWidth, termHeight) => termWidth >= 80,
|
|
147
|
+
},
|
|
148
|
+
// Get handle for programmatic visibility control
|
|
149
|
+
onHandle: (handle) => {
|
|
150
|
+
// handle.setHidden(true/false) - toggle visibility
|
|
151
|
+
// handle.hide() - permanently remove
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Overlay Lifecycle
|
|
158
|
+
|
|
159
|
+
Overlay components are disposed when closed. Don't reuse references - create fresh instances:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Wrong - stale reference
|
|
163
|
+
let menu: MenuComponent;
|
|
164
|
+
await ctx.ui.custom((_, __, ___, done) => {
|
|
165
|
+
menu = new MenuComponent(done);
|
|
166
|
+
return menu;
|
|
167
|
+
}, { overlay: true });
|
|
168
|
+
setActiveComponent(menu); // Disposed
|
|
169
|
+
|
|
170
|
+
// Correct - re-call to re-show
|
|
171
|
+
const showMenu = () => ctx.ui.custom((_, __, ___, done) =>
|
|
172
|
+
new MenuComponent(done), { overlay: true });
|
|
173
|
+
|
|
174
|
+
await showMenu(); // First show
|
|
175
|
+
await showMenu(); // "Back" = just call again
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
See [overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) for comprehensive examples covering anchors, margins, stacking, responsive visibility, and animation.
|
|
179
|
+
|
|
180
|
+
## Built-in Components
|
|
181
|
+
|
|
182
|
+
Import from `@mariozechner/pi-tui`:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { Text, Box, Container, Spacer, Markdown } from "@mariozechner/pi-tui";
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Text
|
|
189
|
+
|
|
190
|
+
Multi-line text with word wrapping.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const text = new Text(
|
|
194
|
+
"Hello World", // content
|
|
195
|
+
1, // paddingX (default: 1)
|
|
196
|
+
1, // paddingY (default: 1)
|
|
197
|
+
(s) => bgGray(s) // optional background function
|
|
198
|
+
);
|
|
199
|
+
text.setText("Updated");
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Box
|
|
203
|
+
|
|
204
|
+
Container with padding and background color.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const box = new Box(
|
|
208
|
+
1, // paddingX
|
|
209
|
+
1, // paddingY
|
|
210
|
+
(s) => bgGray(s) // background function
|
|
211
|
+
);
|
|
212
|
+
box.addChild(new Text("Content", 0, 0));
|
|
213
|
+
box.setBgFn((s) => bgBlue(s));
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Container
|
|
217
|
+
|
|
218
|
+
Groups child components vertically.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const container = new Container();
|
|
222
|
+
container.addChild(component1);
|
|
223
|
+
container.addChild(component2);
|
|
224
|
+
container.removeChild(component1);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Spacer
|
|
228
|
+
|
|
229
|
+
Empty vertical space.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const spacer = new Spacer(2); // 2 empty lines
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Markdown
|
|
236
|
+
|
|
237
|
+
Renders markdown with syntax highlighting.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
const md = new Markdown(
|
|
241
|
+
"# Title\n\nSome **bold** text",
|
|
242
|
+
1, // paddingX
|
|
243
|
+
1, // paddingY
|
|
244
|
+
theme // MarkdownTheme (see below)
|
|
245
|
+
);
|
|
246
|
+
md.setText("Updated markdown");
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Image
|
|
250
|
+
|
|
251
|
+
Renders images in supported terminals (Kitty, iTerm2, Ghostty, WezTerm).
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const image = new Image(
|
|
255
|
+
base64Data, // base64-encoded image
|
|
256
|
+
"image/png", // MIME type
|
|
257
|
+
theme, // ImageTheme
|
|
258
|
+
{ maxWidthCells: 80, maxHeightCells: 24 }
|
|
259
|
+
);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Keyboard Input
|
|
263
|
+
|
|
264
|
+
Use `matchesKey()` for key detection:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { matchesKey, Key } from "@mariozechner/pi-tui";
|
|
268
|
+
|
|
269
|
+
handleInput(data: string) {
|
|
270
|
+
if (matchesKey(data, Key.up)) {
|
|
271
|
+
this.selectedIndex--;
|
|
272
|
+
} else if (matchesKey(data, Key.enter)) {
|
|
273
|
+
this.onSelect?.(this.selectedIndex);
|
|
274
|
+
} else if (matchesKey(data, Key.escape)) {
|
|
275
|
+
this.onCancel?.();
|
|
276
|
+
} else if (matchesKey(data, Key.ctrl("c"))) {
|
|
277
|
+
// Ctrl+C
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Key identifiers** (use `Key.*` for autocomplete, or string literals):
|
|
283
|
+
- Basic keys: `Key.enter`, `Key.escape`, `Key.tab`, `Key.space`, `Key.backspace`, `Key.delete`, `Key.home`, `Key.end`
|
|
284
|
+
- Arrow keys: `Key.up`, `Key.down`, `Key.left`, `Key.right`
|
|
285
|
+
- With modifiers: `Key.ctrl("c")`, `Key.shift("tab")`, `Key.alt("left")`, `Key.ctrlShift("p")`
|
|
286
|
+
- String format also works: `"enter"`, `"ctrl+c"`, `"shift+tab"`, `"ctrl+shift+p"`
|
|
287
|
+
|
|
288
|
+
## Line Width
|
|
289
|
+
|
|
290
|
+
**Critical:** Each line from `render()` must not exceed the `width` parameter.
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import { visibleWidth, truncateToWidth } from "@mariozechner/pi-tui";
|
|
294
|
+
|
|
295
|
+
render(width: number): string[] {
|
|
296
|
+
// Truncate long lines
|
|
297
|
+
return [truncateToWidth(this.text, width)];
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Utilities:
|
|
302
|
+
- `visibleWidth(str)` - Get display width (ignores ANSI codes)
|
|
303
|
+
- `truncateToWidth(str, width, ellipsis?)` - Truncate with optional ellipsis
|
|
304
|
+
- `wrapTextWithAnsi(str, width)` - Word wrap preserving ANSI codes
|
|
305
|
+
|
|
306
|
+
## Creating Custom Components
|
|
307
|
+
|
|
308
|
+
Example: Interactive selector
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import {
|
|
312
|
+
matchesKey, Key,
|
|
313
|
+
truncateToWidth, visibleWidth
|
|
314
|
+
} from "@mariozechner/pi-tui";
|
|
315
|
+
|
|
316
|
+
class MySelector {
|
|
317
|
+
private items: string[];
|
|
318
|
+
private selected = 0;
|
|
319
|
+
private cachedWidth?: number;
|
|
320
|
+
private cachedLines?: string[];
|
|
321
|
+
|
|
322
|
+
public onSelect?: (item: string) => void;
|
|
323
|
+
public onCancel?: () => void;
|
|
324
|
+
|
|
325
|
+
constructor(items: string[]) {
|
|
326
|
+
this.items = items;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
handleInput(data: string): void {
|
|
330
|
+
if (matchesKey(data, Key.up) && this.selected > 0) {
|
|
331
|
+
this.selected--;
|
|
332
|
+
this.invalidate();
|
|
333
|
+
} else if (matchesKey(data, Key.down) && this.selected < this.items.length - 1) {
|
|
334
|
+
this.selected++;
|
|
335
|
+
this.invalidate();
|
|
336
|
+
} else if (matchesKey(data, Key.enter)) {
|
|
337
|
+
this.onSelect?.(this.items[this.selected]);
|
|
338
|
+
} else if (matchesKey(data, Key.escape)) {
|
|
339
|
+
this.onCancel?.();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
render(width: number): string[] {
|
|
344
|
+
if (this.cachedLines && this.cachedWidth === width) {
|
|
345
|
+
return this.cachedLines;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
this.cachedLines = this.items.map((item, i) => {
|
|
349
|
+
const prefix = i === this.selected ? "> " : " ";
|
|
350
|
+
return truncateToWidth(prefix + item, width);
|
|
351
|
+
});
|
|
352
|
+
this.cachedWidth = width;
|
|
353
|
+
return this.cachedLines;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
invalidate(): void {
|
|
357
|
+
this.cachedWidth = undefined;
|
|
358
|
+
this.cachedLines = undefined;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Usage in an extension:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
pi.registerCommand("pick", {
|
|
367
|
+
description: "Pick an item",
|
|
368
|
+
handler: async (args, ctx) => {
|
|
369
|
+
const items = ["Option A", "Option B", "Option C"];
|
|
370
|
+
const selector = new MySelector(items);
|
|
371
|
+
|
|
372
|
+
let handle: { close: () => void; requestRender: () => void };
|
|
373
|
+
|
|
374
|
+
await new Promise<void>((resolve) => {
|
|
375
|
+
selector.onSelect = (item) => {
|
|
376
|
+
ctx.ui.notify(`Selected: ${item}`, "info");
|
|
377
|
+
handle.close();
|
|
378
|
+
resolve();
|
|
379
|
+
};
|
|
380
|
+
selector.onCancel = () => {
|
|
381
|
+
handle.close();
|
|
382
|
+
resolve();
|
|
383
|
+
};
|
|
384
|
+
handle = ctx.ui.custom(selector);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Theming
|
|
391
|
+
|
|
392
|
+
Components accept theme objects for styling.
|
|
393
|
+
|
|
394
|
+
**In `renderCall`/`renderResult`**, use the `theme` parameter:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
renderResult(result, options, theme) {
|
|
398
|
+
// Use theme.fg() for foreground colors
|
|
399
|
+
return new Text(theme.fg("success", "Done!"), 0, 0);
|
|
400
|
+
|
|
401
|
+
// Use theme.bg() for background colors
|
|
402
|
+
const styled = theme.bg("toolPendingBg", theme.fg("accent", "text"));
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Foreground colors** (`theme.fg(color, text)`):
|
|
407
|
+
|
|
408
|
+
| Category | Colors |
|
|
409
|
+
|----------|--------|
|
|
410
|
+
| General | `text`, `accent`, `muted`, `dim` |
|
|
411
|
+
| Status | `success`, `error`, `warning` |
|
|
412
|
+
| Borders | `border`, `borderAccent`, `borderMuted` |
|
|
413
|
+
| Messages | `userMessageText`, `customMessageText`, `customMessageLabel` |
|
|
414
|
+
| Tools | `toolTitle`, `toolOutput` |
|
|
415
|
+
| Diffs | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |
|
|
416
|
+
| Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |
|
|
417
|
+
| Syntax | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |
|
|
418
|
+
| Thinking | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh` |
|
|
419
|
+
| Modes | `bashMode` |
|
|
420
|
+
|
|
421
|
+
**Background colors** (`theme.bg(color, text)`):
|
|
422
|
+
|
|
423
|
+
`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`
|
|
424
|
+
|
|
425
|
+
**For Markdown**, use `getMarkdownTheme()`:
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
|
|
429
|
+
import { Markdown } from "@mariozechner/pi-tui";
|
|
430
|
+
|
|
431
|
+
renderResult(result, options, theme) {
|
|
432
|
+
const mdTheme = getMarkdownTheme();
|
|
433
|
+
return new Markdown(result.details.markdown, 0, 0, mdTheme);
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**For custom components**, define your own theme interface:
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
interface MyTheme {
|
|
441
|
+
selected: (s: string) => string;
|
|
442
|
+
normal: (s: string) => string;
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Debug logging
|
|
447
|
+
|
|
448
|
+
Set `PI_TUI_WRITE_LOG` to capture the raw ANSI stream written to stdout.
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
PI_TUI_WRITE_LOG=/tmp/tui-ansi.log npx tsx packages/tui/test/chat-simple.ts
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## Performance
|
|
455
|
+
|
|
456
|
+
Cache rendered output when possible:
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
class CachedComponent {
|
|
460
|
+
private cachedWidth?: number;
|
|
461
|
+
private cachedLines?: string[];
|
|
462
|
+
|
|
463
|
+
render(width: number): string[] {
|
|
464
|
+
if (this.cachedLines && this.cachedWidth === width) {
|
|
465
|
+
return this.cachedLines;
|
|
466
|
+
}
|
|
467
|
+
// ... compute lines ...
|
|
468
|
+
this.cachedWidth = width;
|
|
469
|
+
this.cachedLines = lines;
|
|
470
|
+
return lines;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
invalidate(): void {
|
|
474
|
+
this.cachedWidth = undefined;
|
|
475
|
+
this.cachedLines = undefined;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Call `invalidate()` when state changes, then `handle.requestRender()` to trigger re-render.
|
|
481
|
+
|
|
482
|
+
## Invalidation and Theme Changes
|
|
483
|
+
|
|
484
|
+
When the theme changes, the TUI calls `invalidate()` on all components to clear their caches. Components must properly implement `invalidate()` to ensure theme changes take effect.
|
|
485
|
+
|
|
486
|
+
### The Problem
|
|
487
|
+
|
|
488
|
+
If a component pre-bakes theme colors into strings (via `theme.fg()`, `theme.bg()`, etc.) and caches them, the cached strings contain ANSI escape codes from the old theme. Simply clearing the render cache isn't enough if the component stores the themed content separately.
|
|
489
|
+
|
|
490
|
+
**Wrong approach** (theme colors won't update):
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
class BadComponent extends Container {
|
|
494
|
+
private content: Text;
|
|
495
|
+
|
|
496
|
+
constructor(message: string, theme: Theme) {
|
|
497
|
+
super();
|
|
498
|
+
// Pre-baked theme colors stored in Text component
|
|
499
|
+
this.content = new Text(theme.fg("accent", message), 1, 0);
|
|
500
|
+
this.addChild(this.content);
|
|
501
|
+
}
|
|
502
|
+
// No invalidate override - parent's invalidate only clears
|
|
503
|
+
// child render caches, not the pre-baked content
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### The Solution
|
|
508
|
+
|
|
509
|
+
Components that build content with theme colors must rebuild that content when `invalidate()` is called:
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
class GoodComponent extends Container {
|
|
513
|
+
private message: string;
|
|
514
|
+
private content: Text;
|
|
515
|
+
|
|
516
|
+
constructor(message: string) {
|
|
517
|
+
super();
|
|
518
|
+
this.message = message;
|
|
519
|
+
this.content = new Text("", 1, 0);
|
|
520
|
+
this.addChild(this.content);
|
|
521
|
+
this.updateDisplay();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private updateDisplay(): void {
|
|
525
|
+
// Rebuild content with current theme
|
|
526
|
+
this.content.setText(theme.fg("accent", this.message));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
override invalidate(): void {
|
|
530
|
+
super.invalidate(); // Clear child caches
|
|
531
|
+
this.updateDisplay(); // Rebuild with new theme
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Pattern: Rebuild on Invalidate
|
|
537
|
+
|
|
538
|
+
For components with complex content:
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
class ComplexComponent extends Container {
|
|
542
|
+
private data: SomeData;
|
|
543
|
+
|
|
544
|
+
constructor(data: SomeData) {
|
|
545
|
+
super();
|
|
546
|
+
this.data = data;
|
|
547
|
+
this.rebuild();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private rebuild(): void {
|
|
551
|
+
this.clear(); // Remove all children
|
|
552
|
+
|
|
553
|
+
// Build UI with current theme
|
|
554
|
+
this.addChild(new Text(theme.fg("accent", theme.bold("Title")), 1, 0));
|
|
555
|
+
this.addChild(new Spacer(1));
|
|
556
|
+
|
|
557
|
+
for (const item of this.data.items) {
|
|
558
|
+
const color = item.active ? "success" : "muted";
|
|
559
|
+
this.addChild(new Text(theme.fg(color, item.label), 1, 0));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
override invalidate(): void {
|
|
564
|
+
super.invalidate();
|
|
565
|
+
this.rebuild();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### When This Matters
|
|
571
|
+
|
|
572
|
+
This pattern is needed when:
|
|
573
|
+
|
|
574
|
+
1. **Pre-baking theme colors** - Using `theme.fg()` or `theme.bg()` to create styled strings stored in child components
|
|
575
|
+
2. **Syntax highlighting** - Using `highlightCode()` which applies theme-based syntax colors
|
|
576
|
+
3. **Complex layouts** - Building child component trees that embed theme colors
|
|
577
|
+
|
|
578
|
+
This pattern is NOT needed when:
|
|
579
|
+
|
|
580
|
+
1. **Using theme callbacks** - Passing functions like `(text) => theme.fg("accent", text)` that are called during render
|
|
581
|
+
2. **Simple containers** - Just grouping other components without adding themed content
|
|
582
|
+
3. **Stateless render** - Computing themed output fresh in every `render()` call (no caching)
|
|
583
|
+
|
|
584
|
+
## Common Patterns
|
|
585
|
+
|
|
586
|
+
These patterns cover the most common UI needs in extensions. **Copy these patterns instead of building from scratch.**
|
|
587
|
+
|
|
588
|
+
### Pattern 1: Selection Dialog (SelectList)
|
|
589
|
+
|
|
590
|
+
For letting users pick from a list of options. Use `SelectList` from `@mariozechner/pi-tui` with `DynamicBorder` for framing.
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
594
|
+
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
|
595
|
+
import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
|
|
596
|
+
|
|
597
|
+
pi.registerCommand("pick", {
|
|
598
|
+
handler: async (_args, ctx) => {
|
|
599
|
+
const items: SelectItem[] = [
|
|
600
|
+
{ value: "opt1", label: "Option 1", description: "First option" },
|
|
601
|
+
{ value: "opt2", label: "Option 2", description: "Second option" },
|
|
602
|
+
{ value: "opt3", label: "Option 3" }, // description is optional
|
|
603
|
+
];
|
|
604
|
+
|
|
605
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
606
|
+
const container = new Container();
|
|
607
|
+
|
|
608
|
+
// Top border
|
|
609
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
610
|
+
|
|
611
|
+
// Title
|
|
612
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Pick an Option")), 1, 0));
|
|
613
|
+
|
|
614
|
+
// SelectList with theme
|
|
615
|
+
const selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
616
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
617
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
618
|
+
description: (t) => theme.fg("muted", t),
|
|
619
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
620
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
621
|
+
});
|
|
622
|
+
selectList.onSelect = (item) => done(item.value);
|
|
623
|
+
selectList.onCancel = () => done(null);
|
|
624
|
+
container.addChild(selectList);
|
|
625
|
+
|
|
626
|
+
// Help text
|
|
627
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate • enter select • esc cancel"), 1, 0));
|
|
628
|
+
|
|
629
|
+
// Bottom border
|
|
630
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
631
|
+
|
|
632
|
+
return {
|
|
633
|
+
render: (w) => container.render(w),
|
|
634
|
+
invalidate: () => container.invalidate(),
|
|
635
|
+
handleInput: (data) => { selectList.handleInput(data); tui.requestRender(); },
|
|
636
|
+
};
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
if (result) {
|
|
640
|
+
ctx.ui.notify(`Selected: ${result}`, "info");
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
**Examples:** [preset.ts](../examples/extensions/preset.ts), [tools.ts](../examples/extensions/tools.ts)
|
|
647
|
+
|
|
648
|
+
### Pattern 2: Async Operation with Cancel (BorderedLoader)
|
|
649
|
+
|
|
650
|
+
For operations that take time and should be cancellable. `BorderedLoader` shows a spinner and handles escape to cancel.
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
import { BorderedLoader } from "@mariozechner/pi-coding-agent";
|
|
654
|
+
|
|
655
|
+
pi.registerCommand("fetch", {
|
|
656
|
+
handler: async (_args, ctx) => {
|
|
657
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
658
|
+
const loader = new BorderedLoader(tui, theme, "Fetching data...");
|
|
659
|
+
loader.onAbort = () => done(null);
|
|
660
|
+
|
|
661
|
+
// Do async work
|
|
662
|
+
fetchData(loader.signal)
|
|
663
|
+
.then((data) => done(data))
|
|
664
|
+
.catch(() => done(null));
|
|
665
|
+
|
|
666
|
+
return loader;
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
if (result === null) {
|
|
670
|
+
ctx.ui.notify("Cancelled", "info");
|
|
671
|
+
} else {
|
|
672
|
+
ctx.ui.setEditorText(result);
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**Examples:** [qna.ts](../examples/extensions/qna.ts), [handoff.ts](../examples/extensions/handoff.ts)
|
|
679
|
+
|
|
680
|
+
### Pattern 3: Settings/Toggles (SettingsList)
|
|
681
|
+
|
|
682
|
+
For toggling multiple settings. Use `SettingsList` from `@mariozechner/pi-tui` with `getSettingsListTheme()`.
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
import { getSettingsListTheme } from "@mariozechner/pi-coding-agent";
|
|
686
|
+
import { Container, type SettingItem, SettingsList, Text } from "@mariozechner/pi-tui";
|
|
687
|
+
|
|
688
|
+
pi.registerCommand("settings", {
|
|
689
|
+
handler: async (_args, ctx) => {
|
|
690
|
+
const items: SettingItem[] = [
|
|
691
|
+
{ id: "verbose", label: "Verbose mode", currentValue: "off", values: ["on", "off"] },
|
|
692
|
+
{ id: "color", label: "Color output", currentValue: "on", values: ["on", "off"] },
|
|
693
|
+
];
|
|
694
|
+
|
|
695
|
+
await ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
696
|
+
const container = new Container();
|
|
697
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Settings")), 1, 1));
|
|
698
|
+
|
|
699
|
+
const settingsList = new SettingsList(
|
|
700
|
+
items,
|
|
701
|
+
Math.min(items.length + 2, 15),
|
|
702
|
+
getSettingsListTheme(),
|
|
703
|
+
(id, newValue) => {
|
|
704
|
+
// Handle value change
|
|
705
|
+
ctx.ui.notify(`${id} = ${newValue}`, "info");
|
|
706
|
+
},
|
|
707
|
+
() => done(undefined), // On close
|
|
708
|
+
{ enableSearch: true }, // Optional: enable fuzzy search by label
|
|
709
|
+
);
|
|
710
|
+
container.addChild(settingsList);
|
|
711
|
+
|
|
712
|
+
return {
|
|
713
|
+
render: (w) => container.render(w),
|
|
714
|
+
invalidate: () => container.invalidate(),
|
|
715
|
+
handleInput: (data) => settingsList.handleInput?.(data),
|
|
716
|
+
};
|
|
717
|
+
});
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
**Examples:** [tools.ts](../examples/extensions/tools.ts)
|
|
723
|
+
|
|
724
|
+
### Pattern 4: Persistent Status Indicator
|
|
725
|
+
|
|
726
|
+
Show status in the footer that persists across renders. Good for mode indicators.
|
|
727
|
+
|
|
728
|
+
```typescript
|
|
729
|
+
// Set status (shown in footer)
|
|
730
|
+
ctx.ui.setStatus("my-ext", ctx.ui.theme.fg("accent", "● active"));
|
|
731
|
+
|
|
732
|
+
// Clear status
|
|
733
|
+
ctx.ui.setStatus("my-ext", undefined);
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
**Examples:** [status-line.ts](../examples/extensions/status-line.ts), [plan-mode.ts](../examples/extensions/plan-mode.ts), [preset.ts](../examples/extensions/preset.ts)
|
|
737
|
+
|
|
738
|
+
### Pattern 5: Widgets Above/Below Editor
|
|
739
|
+
|
|
740
|
+
Show persistent content above or below the input editor. Good for todo lists, progress.
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
// Simple string array (above editor by default)
|
|
744
|
+
ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]);
|
|
745
|
+
|
|
746
|
+
// Render below the editor
|
|
747
|
+
ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"], { placement: "belowEditor" });
|
|
748
|
+
|
|
749
|
+
// Or with theme
|
|
750
|
+
ctx.ui.setWidget("my-widget", (_tui, theme) => {
|
|
751
|
+
const lines = items.map((item, i) =>
|
|
752
|
+
item.done
|
|
753
|
+
? theme.fg("success", "✓ ") + theme.fg("muted", item.text)
|
|
754
|
+
: theme.fg("dim", "○ ") + item.text
|
|
755
|
+
);
|
|
756
|
+
return {
|
|
757
|
+
render: () => lines,
|
|
758
|
+
invalidate: () => {},
|
|
759
|
+
};
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
// Clear
|
|
763
|
+
ctx.ui.setWidget("my-widget", undefined);
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
**Examples:** [plan-mode.ts](../examples/extensions/plan-mode.ts)
|
|
767
|
+
|
|
768
|
+
### Pattern 6: Custom Footer
|
|
769
|
+
|
|
770
|
+
Replace the footer. `footerData` exposes data not otherwise accessible to extensions.
|
|
771
|
+
|
|
772
|
+
```typescript
|
|
773
|
+
ctx.ui.setFooter((tui, theme, footerData) => ({
|
|
774
|
+
invalidate() {},
|
|
775
|
+
render(width: number): string[] {
|
|
776
|
+
// footerData.getGitBranch(): string | null
|
|
777
|
+
// footerData.getExtensionStatuses(): ReadonlyMap<string, string>
|
|
778
|
+
return [`${ctx.model?.id} (${footerData.getGitBranch() || "no git"})`];
|
|
779
|
+
},
|
|
780
|
+
dispose: footerData.onBranchChange(() => tui.requestRender()), // reactive
|
|
781
|
+
}));
|
|
782
|
+
|
|
783
|
+
ctx.ui.setFooter(undefined); // restore default
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
Token stats available via `ctx.sessionManager.getBranch()` and `ctx.model`.
|
|
787
|
+
|
|
788
|
+
**Examples:** [custom-footer.ts](../examples/extensions/custom-footer.ts)
|
|
789
|
+
|
|
790
|
+
### Pattern 7: Custom Editor (vim mode, etc.)
|
|
791
|
+
|
|
792
|
+
Replace the main input editor with a custom implementation. Useful for modal editing (vim), different keybindings (emacs), or specialized input handling.
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
import { CustomEditor, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
796
|
+
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
797
|
+
|
|
798
|
+
type Mode = "normal" | "insert";
|
|
799
|
+
|
|
800
|
+
class VimEditor extends CustomEditor {
|
|
801
|
+
private mode: Mode = "insert";
|
|
802
|
+
|
|
803
|
+
handleInput(data: string): void {
|
|
804
|
+
// Escape: switch to normal mode, or pass through for app handling
|
|
805
|
+
if (matchesKey(data, "escape")) {
|
|
806
|
+
if (this.mode === "insert") {
|
|
807
|
+
this.mode = "normal";
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
// In normal mode, escape aborts agent (handled by CustomEditor)
|
|
811
|
+
super.handleInput(data);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Insert mode: pass everything to CustomEditor
|
|
816
|
+
if (this.mode === "insert") {
|
|
817
|
+
super.handleInput(data);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Normal mode: vim-style navigation
|
|
822
|
+
switch (data) {
|
|
823
|
+
case "i": this.mode = "insert"; return;
|
|
824
|
+
case "h": super.handleInput("\x1b[D"); return; // Left
|
|
825
|
+
case "j": super.handleInput("\x1b[B"); return; // Down
|
|
826
|
+
case "k": super.handleInput("\x1b[A"); return; // Up
|
|
827
|
+
case "l": super.handleInput("\x1b[C"); return; // Right
|
|
828
|
+
}
|
|
829
|
+
// Pass unhandled keys to super (ctrl+c, etc.), but filter printable chars
|
|
830
|
+
if (data.length === 1 && data.charCodeAt(0) >= 32) return;
|
|
831
|
+
super.handleInput(data);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
render(width: number): string[] {
|
|
835
|
+
const lines = super.render(width);
|
|
836
|
+
// Add mode indicator to bottom border (use truncateToWidth for ANSI-safe truncation)
|
|
837
|
+
if (lines.length > 0) {
|
|
838
|
+
const label = this.mode === "normal" ? " NORMAL " : " INSERT ";
|
|
839
|
+
const lastLine = lines[lines.length - 1]!;
|
|
840
|
+
// Pass "" as ellipsis to avoid adding "..." when truncating
|
|
841
|
+
lines[lines.length - 1] = truncateToWidth(lastLine, width - label.length, "") + label;
|
|
842
|
+
}
|
|
843
|
+
return lines;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
export default function (pi: ExtensionAPI) {
|
|
848
|
+
pi.on("session_start", (_event, ctx) => {
|
|
849
|
+
// Factory receives theme and keybindings from the app
|
|
850
|
+
ctx.ui.setEditorComponent((tui, theme, keybindings) =>
|
|
851
|
+
new VimEditor(theme, keybindings)
|
|
852
|
+
);
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
**Key points:**
|
|
858
|
+
|
|
859
|
+
- **Extend `CustomEditor`** (not base `Editor`) to get app keybindings (escape to abort, ctrl+d to exit, model switching, etc.)
|
|
860
|
+
- **Call `super.handleInput(data)`** for keys you don't handle
|
|
861
|
+
- **Factory pattern**: `setEditorComponent` receives a factory function that gets `tui`, `theme`, and `keybindings`
|
|
862
|
+
- **Pass `undefined`** to restore the default editor: `ctx.ui.setEditorComponent(undefined)`
|
|
863
|
+
|
|
864
|
+
**Examples:** [modal-editor.ts](../examples/extensions/modal-editor.ts)
|
|
865
|
+
|
|
866
|
+
## Key Rules
|
|
867
|
+
|
|
868
|
+
1. **Always use theme from callback** - Don't import theme directly. Use `theme` from the `ctx.ui.custom((tui, theme, keybindings, done) => ...)` callback.
|
|
869
|
+
|
|
870
|
+
2. **Always type DynamicBorder color param** - Write `(s: string) => theme.fg("accent", s)`, not `(s) => theme.fg("accent", s)`.
|
|
871
|
+
|
|
872
|
+
3. **Call tui.requestRender() after state changes** - In `handleInput`, call `tui.requestRender()` after updating state.
|
|
873
|
+
|
|
874
|
+
4. **Return the three-method object** - Custom components need `{ render, invalidate, handleInput }`.
|
|
875
|
+
|
|
876
|
+
5. **Use existing components** - `SelectList`, `SettingsList`, `BorderedLoader` cover 90% of cases. Don't rebuild them.
|
|
877
|
+
|
|
878
|
+
## Examples
|
|
879
|
+
|
|
880
|
+
- **Selection UI**: [examples/extensions/preset.ts](../examples/extensions/preset.ts) - SelectList with DynamicBorder framing
|
|
881
|
+
- **Async with cancel**: [examples/extensions/qna.ts](../examples/extensions/qna.ts) - BorderedLoader for LLM calls
|
|
882
|
+
- **Settings toggles**: [examples/extensions/tools.ts](../examples/extensions/tools.ts) - SettingsList for tool enable/disable
|
|
883
|
+
- **Status indicators**: [examples/extensions/plan-mode.ts](../examples/extensions/plan-mode.ts) - setStatus and setWidget
|
|
884
|
+
- **Custom footer**: [examples/extensions/custom-footer.ts](../examples/extensions/custom-footer.ts) - setFooter with stats
|
|
885
|
+
- **Custom editor**: [examples/extensions/modal-editor.ts](../examples/extensions/modal-editor.ts) - Vim-like modal editing
|
|
886
|
+
- **Snake game**: [examples/extensions/snake.ts](../examples/extensions/snake.ts) - Full game with keyboard input, game loop
|
|
887
|
+
- **Custom tool rendering**: [examples/extensions/todo.ts](../examples/extensions/todo.ts) - renderCall and renderResult
|