@codex-infinity/pi-infinity 0.60.1 → 0.61.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 +45 -0
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +1 -0
- package/dist/bun/cli.js.map +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/initial-message.js.map +1 -1
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +2 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +14 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +107 -72
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.js +4 -8
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +7 -3
- package/dist/core/exec.js.map +1 -1
- package/dist/core/export-html/ansi-to-html.js.map +1 -1
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.css +43 -13
- package/dist/core/export-html/template.html +1 -0
- package/dist/core/export-html/template.js +107 -0
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +4 -4
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +62 -56
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +4 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +9 -2
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +92 -20
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +268 -51
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +220 -143
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +29 -21
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +4 -4
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.js +0 -6
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/resource-loader.js +0 -37
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +4 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +5 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +8 -12
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.js +7 -16
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +2 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +12 -10
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +3 -2
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts +6 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/core/tools/file-mutation-queue.js +37 -0
- package/dist/core/tools/file-mutation-queue.js.map +1 -0
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/truncate.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +6 -3
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +10 -5
- package/dist/main.js.map +1 -1
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/armin.js +6 -10
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +0 -4
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +8 -14
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +1 -4
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +3 -5
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +3 -5
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +14 -23
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.js +0 -5
- package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +7 -14
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +1 -6
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/daxnuts.js +6 -8
- package/dist/modes/interactive/components/daxnuts.js.map +1 -1
- package/dist/modes/interactive/components/diff.js.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +0 -1
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +10 -15
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-input.js +7 -13
- package/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +9 -16
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -3
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +3 -36
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +5 -44
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +9 -15
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +20 -28
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +8 -13
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +13 -17
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +65 -93
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +0 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.js +0 -1
- package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +3 -5
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/theme-selector.js +0 -2
- package/dist/modes/interactive/components/theme-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +0 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +7 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +57 -28
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +32 -46
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +9 -12
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +210 -170
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +49 -42
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/jsonl.js.map +1 -1
- package/dist/modes/rpc/rpc-client.js +6 -7
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +4 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/changelog.js.map +1 -1
- package/dist/utils/child-process.d.ts +11 -0
- package/dist/utils/child-process.d.ts.map +1 -0
- package/dist/utils/child-process.js +78 -0
- package/dist/utils/child-process.js.map +1 -0
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +1 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +11 -1
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/exif-orientation.js.map +1 -1
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/image-convert.js.map +1 -1
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/mime.js.map +1 -1
- package/dist/utils/photon.js.map +1 -1
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/sleep.js.map +1 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/extensions.md +42 -5
- package/docs/keybindings.md +101 -112
- package/examples/extensions/antigravity-image-gen.ts +5 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/subagent/index.ts +7 -5
- package/examples/extensions/tool-override.ts +9 -7
- package/examples/extensions/truncated-tool.ts +6 -3
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash-execution.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/bash-execution.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAwB,KAAK,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,EAGN,KAAK,gBAAgB,EAErB,MAAM,iCAAiC,CAAC;AASzC,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,MAAM,CAA6D;IAC3E,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,EAAE,CAAM;IAEhB,YAAY,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,kBAAkB,UAAQ,EAkC/D;IAED;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAgBhC;IAED,WAAW,CACV,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,OAAO,EAClB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,cAAc,CAAC,EAAE,MAAM,GACrB,IAAI,CAcN;IAED,OAAO,CAAC,aAAa;IA4ErB;;OAEG;IACH,SAAS,IAAI,MAAM,CAElB;IAED;;OAEG;IACH,UAAU,IAAI,MAAM,CAEnB;CACD","sourcesContent":["/**\n * Component for displaying bash command execution with streaming output.\n */\n\nimport { Container, Loader, Spacer, Text, type TUI } from \"@mariozechner/pi-tui\";\nimport stripAnsi from \"strip-ansi\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\ttype TruncationResult,\n\ttruncateTail,\n} from \"../../../core/tools/truncate.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"bash-execution.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/bash-execution.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAwB,KAAK,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,EAGN,KAAK,gBAAgB,EAErB,MAAM,iCAAiC,CAAC;AASzC,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,MAAM,CAA6D;IAC3E,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,EAAE,CAAM;IAEhB,YAAY,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,kBAAkB,UAAQ,EAkC/D;IAED;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAgBhC;IAED,WAAW,CACV,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,OAAO,EAClB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,cAAc,CAAC,EAAE,MAAM,GACrB,IAAI,CAcN;IAED,OAAO,CAAC,aAAa;IA4ErB;;OAEG;IACH,SAAS,IAAI,MAAM,CAElB;IAED;;OAEG;IACH,UAAU,IAAI,MAAM,CAEnB;CACD","sourcesContent":["/**\n * Component for displaying bash command execution with streaming output.\n */\n\nimport { Container, Loader, Spacer, Text, type TUI } from \"@mariozechner/pi-tui\";\nimport stripAnsi from \"strip-ansi\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\ttype TruncationResult,\n\ttruncateTail,\n} from \"../../../core/tools/truncate.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyHint, keyText } from \"./keybinding-hints.js\";\nimport { truncateToVisualLines } from \"./visual-truncate.js\";\n\n// Preview line limit when not expanded (matches tool execution behavior)\nconst PREVIEW_LINES = 20;\n\nexport class BashExecutionComponent extends Container {\n\tprivate command: string;\n\tprivate outputLines: string[] = [];\n\tprivate status: \"running\" | \"complete\" | \"cancelled\" | \"error\" = \"running\";\n\tprivate exitCode: number | undefined = undefined;\n\tprivate loader: Loader;\n\tprivate truncationResult?: TruncationResult;\n\tprivate fullOutputPath?: string;\n\tprivate expanded = false;\n\tprivate contentContainer: Container;\n\tprivate ui: TUI;\n\n\tconstructor(command: string, ui: TUI, excludeFromContext = false) {\n\t\tsuper();\n\t\tthis.command = command;\n\t\tthis.ui = ui;\n\n\t\t// Use dim border for excluded-from-context commands (!! prefix)\n\t\tconst colorKey = excludeFromContext ? \"dim\" : \"bashMode\";\n\t\tconst borderColor = (str: string) => theme.fg(colorKey, str);\n\n\t\t// Add spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Top border\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\n\t\t// Content container (holds dynamic content between borders)\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\t// Command header\n\t\tconst header = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), 1, 0);\n\t\tthis.contentContainer.addChild(header);\n\n\t\t// Loader\n\t\tthis.loader = new Loader(\n\t\t\tui,\n\t\t\t(spinner) => theme.fg(colorKey, spinner),\n\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\t`Running... (${keyText(\"tui.select.cancel\")} to cancel)`, // Plain text for loader\n\t\t);\n\t\tthis.contentContainer.addChild(this.loader);\n\n\t\t// Bottom border\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\t}\n\n\t/**\n\t * Set whether the output is expanded (shows full output) or collapsed (preview only).\n\t */\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tappendOutput(chunk: string): void {\n\t\t// Strip ANSI codes and normalize line endings\n\t\t// Note: binary data is already sanitized in tui-renderer.ts executeBashCommand\n\t\tconst clean = stripAnsi(chunk).replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\t\t// Append to output lines\n\t\tconst newLines = clean.split(\"\\n\");\n\t\tif (this.outputLines.length > 0 && newLines.length > 0) {\n\t\t\t// Append first chunk to last line (incomplete line continuation)\n\t\t\tthis.outputLines[this.outputLines.length - 1] += newLines[0];\n\t\t\tthis.outputLines.push(...newLines.slice(1));\n\t\t} else {\n\t\t\tthis.outputLines.push(...newLines);\n\t\t}\n\n\t\tthis.updateDisplay();\n\t}\n\n\tsetComplete(\n\t\texitCode: number | undefined,\n\t\tcancelled: boolean,\n\t\ttruncationResult?: TruncationResult,\n\t\tfullOutputPath?: string,\n\t): void {\n\t\tthis.exitCode = exitCode;\n\t\tthis.status = cancelled\n\t\t\t? \"cancelled\"\n\t\t\t: exitCode !== 0 && exitCode !== undefined && exitCode !== null\n\t\t\t\t? \"error\"\n\t\t\t\t: \"complete\";\n\t\tthis.truncationResult = truncationResult;\n\t\tthis.fullOutputPath = fullOutputPath;\n\n\t\t// Stop loader\n\t\tthis.loader.stop();\n\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\t// Apply truncation for LLM context limits (same limits as bash tool)\n\t\tconst fullOutput = this.outputLines.join(\"\\n\");\n\t\tconst contextTruncation = truncateTail(fullOutput, {\n\t\t\tmaxLines: DEFAULT_MAX_LINES,\n\t\t\tmaxBytes: DEFAULT_MAX_BYTES,\n\t\t});\n\n\t\t// Get the lines to potentially display (after context truncation)\n\t\tconst availableLines = contextTruncation.content ? contextTruncation.content.split(\"\\n\") : [];\n\n\t\t// Apply preview truncation based on expanded state\n\t\tconst previewLogicalLines = availableLines.slice(-PREVIEW_LINES);\n\t\tconst hiddenLineCount = availableLines.length - previewLogicalLines.length;\n\n\t\t// Rebuild content container\n\t\tthis.contentContainer.clear();\n\n\t\t// Command header\n\t\tconst header = new Text(theme.fg(\"bashMode\", theme.bold(`$ ${this.command}`)), 1, 0);\n\t\tthis.contentContainer.addChild(header);\n\n\t\t// Output\n\t\tif (availableLines.length > 0) {\n\t\t\tif (this.expanded) {\n\t\t\t\t// Show all lines\n\t\t\t\tconst displayText = availableLines.map((line) => theme.fg(\"muted\", line)).join(\"\\n\");\n\t\t\t\tthis.contentContainer.addChild(new Text(`\\n${displayText}`, 1, 0));\n\t\t\t} else {\n\t\t\t\t// Use shared visual truncation utility\n\t\t\t\tconst styledOutput = previewLogicalLines.map((line) => theme.fg(\"muted\", line)).join(\"\\n\");\n\t\t\t\tconst { visualLines } = truncateToVisualLines(\n\t\t\t\t\t`\\n${styledOutput}`,\n\t\t\t\t\tPREVIEW_LINES,\n\t\t\t\t\tthis.ui.terminal.columns,\n\t\t\t\t\t1, // padding\n\t\t\t\t);\n\t\t\t\tthis.contentContainer.addChild({ render: () => visualLines, invalidate: () => {} });\n\t\t\t}\n\t\t}\n\n\t\t// Loader or status\n\t\tif (this.status === \"running\") {\n\t\t\tthis.contentContainer.addChild(this.loader);\n\t\t} else {\n\t\t\tconst statusParts: string[] = [];\n\n\t\t\t// Show how many lines are hidden (collapsed preview)\n\t\t\tif (hiddenLineCount > 0) {\n\t\t\t\tif (this.expanded) {\n\t\t\t\t\tstatusParts.push(`(${keyHint(\"app.tools.expand\", \"to collapse\")})`);\n\t\t\t\t} else {\n\t\t\t\t\tstatusParts.push(\n\t\t\t\t\t\t`${theme.fg(\"muted\", `... ${hiddenLineCount} more lines`)} (${keyHint(\"app.tools.expand\", \"to expand\")})`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.status === \"cancelled\") {\n\t\t\t\tstatusParts.push(theme.fg(\"warning\", \"(cancelled)\"));\n\t\t\t} else if (this.status === \"error\") {\n\t\t\t\tstatusParts.push(theme.fg(\"error\", `(exit ${this.exitCode})`));\n\t\t\t}\n\n\t\t\t// Add truncation warning (context truncation, not preview truncation)\n\t\t\tconst wasTruncated = this.truncationResult?.truncated || contextTruncation.truncated;\n\t\t\tif (wasTruncated && this.fullOutputPath) {\n\t\t\t\tstatusParts.push(theme.fg(\"warning\", `Output truncated. Full output: ${this.fullOutputPath}`));\n\t\t\t}\n\n\t\t\tif (statusParts.length > 0) {\n\t\t\t\tthis.contentContainer.addChild(new Text(`\\n${statusParts.join(\"\\n\")}`, 1, 0));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get the raw output for creating BashExecutionMessage.\n\t */\n\tgetOutput(): string {\n\t\treturn this.outputLines.join(\"\\n\");\n\t}\n\n\t/**\n\t * Get the command that was executed.\n\t */\n\tgetCommand(): string {\n\t\treturn this.command;\n\t}\n}\n"]}
|
|
@@ -6,23 +6,17 @@ import stripAnsi from "strip-ansi";
|
|
|
6
6
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, truncateTail, } from "../../../core/tools/truncate.js";
|
|
7
7
|
import { theme } from "../theme/theme.js";
|
|
8
8
|
import { DynamicBorder } from "./dynamic-border.js";
|
|
9
|
-
import {
|
|
9
|
+
import { keyHint, keyText } from "./keybinding-hints.js";
|
|
10
10
|
import { truncateToVisualLines } from "./visual-truncate.js";
|
|
11
11
|
// Preview line limit when not expanded (matches tool execution behavior)
|
|
12
12
|
const PREVIEW_LINES = 20;
|
|
13
13
|
export class BashExecutionComponent extends Container {
|
|
14
|
-
command;
|
|
15
|
-
outputLines = [];
|
|
16
|
-
status = "running";
|
|
17
|
-
exitCode = undefined;
|
|
18
|
-
loader;
|
|
19
|
-
truncationResult;
|
|
20
|
-
fullOutputPath;
|
|
21
|
-
expanded = false;
|
|
22
|
-
contentContainer;
|
|
23
|
-
ui;
|
|
24
14
|
constructor(command, ui, excludeFromContext = false) {
|
|
25
15
|
super();
|
|
16
|
+
this.outputLines = [];
|
|
17
|
+
this.status = "running";
|
|
18
|
+
this.exitCode = undefined;
|
|
19
|
+
this.expanded = false;
|
|
26
20
|
this.command = command;
|
|
27
21
|
this.ui = ui;
|
|
28
22
|
// Use dim border for excluded-from-context commands (!! prefix)
|
|
@@ -39,7 +33,7 @@ export class BashExecutionComponent extends Container {
|
|
|
39
33
|
const header = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), 1, 0);
|
|
40
34
|
this.contentContainer.addChild(header);
|
|
41
35
|
// Loader
|
|
42
|
-
this.loader = new Loader(ui, (spinner) => theme.fg(colorKey, spinner), (text) => theme.fg("muted", text), `Running... (${
|
|
36
|
+
this.loader = new Loader(ui, (spinner) => theme.fg(colorKey, spinner), (text) => theme.fg("muted", text), `Running... (${keyText("tui.select.cancel")} to cancel)`);
|
|
43
37
|
this.contentContainer.addChild(this.loader);
|
|
44
38
|
// Bottom border
|
|
45
39
|
this.addChild(new DynamicBorder(borderColor));
|
|
@@ -124,10 +118,10 @@ export class BashExecutionComponent extends Container {
|
|
|
124
118
|
// Show how many lines are hidden (collapsed preview)
|
|
125
119
|
if (hiddenLineCount > 0) {
|
|
126
120
|
if (this.expanded) {
|
|
127
|
-
statusParts.push(`(${keyHint("
|
|
121
|
+
statusParts.push(`(${keyHint("app.tools.expand", "to collapse")})`);
|
|
128
122
|
}
|
|
129
123
|
else {
|
|
130
|
-
statusParts.push(`${theme.fg("muted", `... ${hiddenLineCount} more lines`)} (${keyHint("
|
|
124
|
+
statusParts.push(`${theme.fg("muted", `... ${hiddenLineCount} more lines`)} (${keyHint("app.tools.expand", "to expand")})`);
|
|
131
125
|
}
|
|
132
126
|
}
|
|
133
127
|
if (this.status === "cancelled") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash-execution.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/bash-execution.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Component for displaying bash command execution with streaming output.\n */\n\nimport { Container, Loader, Spacer, Text, type TUI } from \"@mariozechner/pi-tui\";\nimport stripAnsi from \"strip-ansi\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\ttype TruncationResult,\n\ttruncateTail,\n} from \"../../../core/tools/truncate.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { editorKey, keyHint } from \"./keybinding-hints.js\";\nimport { truncateToVisualLines } from \"./visual-truncate.js\";\n\n// Preview line limit when not expanded (matches tool execution behavior)\nconst PREVIEW_LINES = 20;\n\nexport class BashExecutionComponent extends Container {\n\tprivate command: string;\n\tprivate outputLines: string[] = [];\n\tprivate status: \"running\" | \"complete\" | \"cancelled\" | \"error\" = \"running\";\n\tprivate exitCode: number | undefined = undefined;\n\tprivate loader: Loader;\n\tprivate truncationResult?: TruncationResult;\n\tprivate fullOutputPath?: string;\n\tprivate expanded = false;\n\tprivate contentContainer: Container;\n\tprivate ui: TUI;\n\n\tconstructor(command: string, ui: TUI, excludeFromContext = false) {\n\t\tsuper();\n\t\tthis.command = command;\n\t\tthis.ui = ui;\n\n\t\t// Use dim border for excluded-from-context commands (!! prefix)\n\t\tconst colorKey = excludeFromContext ? \"dim\" : \"bashMode\";\n\t\tconst borderColor = (str: string) => theme.fg(colorKey, str);\n\n\t\t// Add spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Top border\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\n\t\t// Content container (holds dynamic content between borders)\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\t// Command header\n\t\tconst header = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), 1, 0);\n\t\tthis.contentContainer.addChild(header);\n\n\t\t// Loader\n\t\tthis.loader = new Loader(\n\t\t\tui,\n\t\t\t(spinner) => theme.fg(colorKey, spinner),\n\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\t`Running... (${editorKey(\"selectCancel\")} to cancel)`, // Plain text for loader\n\t\t);\n\t\tthis.contentContainer.addChild(this.loader);\n\n\t\t// Bottom border\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\t}\n\n\t/**\n\t * Set whether the output is expanded (shows full output) or collapsed (preview only).\n\t */\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tappendOutput(chunk: string): void {\n\t\t// Strip ANSI codes and normalize line endings\n\t\t// Note: binary data is already sanitized in tui-renderer.ts executeBashCommand\n\t\tconst clean = stripAnsi(chunk).replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\t\t// Append to output lines\n\t\tconst newLines = clean.split(\"\\n\");\n\t\tif (this.outputLines.length > 0 && newLines.length > 0) {\n\t\t\t// Append first chunk to last line (incomplete line continuation)\n\t\t\tthis.outputLines[this.outputLines.length - 1] += newLines[0];\n\t\t\tthis.outputLines.push(...newLines.slice(1));\n\t\t} else {\n\t\t\tthis.outputLines.push(...newLines);\n\t\t}\n\n\t\tthis.updateDisplay();\n\t}\n\n\tsetComplete(\n\t\texitCode: number | undefined,\n\t\tcancelled: boolean,\n\t\ttruncationResult?: TruncationResult,\n\t\tfullOutputPath?: string,\n\t): void {\n\t\tthis.exitCode = exitCode;\n\t\tthis.status = cancelled\n\t\t\t? \"cancelled\"\n\t\t\t: exitCode !== 0 && exitCode !== undefined && exitCode !== null\n\t\t\t\t? \"error\"\n\t\t\t\t: \"complete\";\n\t\tthis.truncationResult = truncationResult;\n\t\tthis.fullOutputPath = fullOutputPath;\n\n\t\t// Stop loader\n\t\tthis.loader.stop();\n\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\t// Apply truncation for LLM context limits (same limits as bash tool)\n\t\tconst fullOutput = this.outputLines.join(\"\\n\");\n\t\tconst contextTruncation = truncateTail(fullOutput, {\n\t\t\tmaxLines: DEFAULT_MAX_LINES,\n\t\t\tmaxBytes: DEFAULT_MAX_BYTES,\n\t\t});\n\n\t\t// Get the lines to potentially display (after context truncation)\n\t\tconst availableLines = contextTruncation.content ? contextTruncation.content.split(\"\\n\") : [];\n\n\t\t// Apply preview truncation based on expanded state\n\t\tconst previewLogicalLines = availableLines.slice(-PREVIEW_LINES);\n\t\tconst hiddenLineCount = availableLines.length - previewLogicalLines.length;\n\n\t\t// Rebuild content container\n\t\tthis.contentContainer.clear();\n\n\t\t// Command header\n\t\tconst header = new Text(theme.fg(\"bashMode\", theme.bold(`$ ${this.command}`)), 1, 0);\n\t\tthis.contentContainer.addChild(header);\n\n\t\t// Output\n\t\tif (availableLines.length > 0) {\n\t\t\tif (this.expanded) {\n\t\t\t\t// Show all lines\n\t\t\t\tconst displayText = availableLines.map((line) => theme.fg(\"muted\", line)).join(\"\\n\");\n\t\t\t\tthis.contentContainer.addChild(new Text(`\\n${displayText}`, 1, 0));\n\t\t\t} else {\n\t\t\t\t// Use shared visual truncation utility\n\t\t\t\tconst styledOutput = previewLogicalLines.map((line) => theme.fg(\"muted\", line)).join(\"\\n\");\n\t\t\t\tconst { visualLines } = truncateToVisualLines(\n\t\t\t\t\t`\\n${styledOutput}`,\n\t\t\t\t\tPREVIEW_LINES,\n\t\t\t\t\tthis.ui.terminal.columns,\n\t\t\t\t\t1, // padding\n\t\t\t\t);\n\t\t\t\tthis.contentContainer.addChild({ render: () => visualLines, invalidate: () => {} });\n\t\t\t}\n\t\t}\n\n\t\t// Loader or status\n\t\tif (this.status === \"running\") {\n\t\t\tthis.contentContainer.addChild(this.loader);\n\t\t} else {\n\t\t\tconst statusParts: string[] = [];\n\n\t\t\t// Show how many lines are hidden (collapsed preview)\n\t\t\tif (hiddenLineCount > 0) {\n\t\t\t\tif (this.expanded) {\n\t\t\t\t\tstatusParts.push(`(${keyHint(\"expandTools\", \"to collapse\")})`);\n\t\t\t\t} else {\n\t\t\t\t\tstatusParts.push(\n\t\t\t\t\t\t`${theme.fg(\"muted\", `... ${hiddenLineCount} more lines`)} (${keyHint(\"expandTools\", \"to expand\")})`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.status === \"cancelled\") {\n\t\t\t\tstatusParts.push(theme.fg(\"warning\", \"(cancelled)\"));\n\t\t\t} else if (this.status === \"error\") {\n\t\t\t\tstatusParts.push(theme.fg(\"error\", `(exit ${this.exitCode})`));\n\t\t\t}\n\n\t\t\t// Add truncation warning (context truncation, not preview truncation)\n\t\t\tconst wasTruncated = this.truncationResult?.truncated || contextTruncation.truncated;\n\t\t\tif (wasTruncated && this.fullOutputPath) {\n\t\t\t\tstatusParts.push(theme.fg(\"warning\", `Output truncated. Full output: ${this.fullOutputPath}`));\n\t\t\t}\n\n\t\t\tif (statusParts.length > 0) {\n\t\t\t\tthis.contentContainer.addChild(new Text(`\\n${statusParts.join(\"\\n\")}`, 1, 0));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get the raw output for creating BashExecutionMessage.\n\t */\n\tgetOutput(): string {\n\t\treturn this.outputLines.join(\"\\n\");\n\t}\n\n\t/**\n\t * Get the command that was executed.\n\t */\n\tgetCommand(): string {\n\t\treturn this.command;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bash-execution.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/bash-execution.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Component for displaying bash command execution with streaming output.\n */\n\nimport { Container, Loader, Spacer, Text, type TUI } from \"@mariozechner/pi-tui\";\nimport stripAnsi from \"strip-ansi\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\ttype TruncationResult,\n\ttruncateTail,\n} from \"../../../core/tools/truncate.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyHint, keyText } from \"./keybinding-hints.js\";\nimport { truncateToVisualLines } from \"./visual-truncate.js\";\n\n// Preview line limit when not expanded (matches tool execution behavior)\nconst PREVIEW_LINES = 20;\n\nexport class BashExecutionComponent extends Container {\n\tprivate command: string;\n\tprivate outputLines: string[] = [];\n\tprivate status: \"running\" | \"complete\" | \"cancelled\" | \"error\" = \"running\";\n\tprivate exitCode: number | undefined = undefined;\n\tprivate loader: Loader;\n\tprivate truncationResult?: TruncationResult;\n\tprivate fullOutputPath?: string;\n\tprivate expanded = false;\n\tprivate contentContainer: Container;\n\tprivate ui: TUI;\n\n\tconstructor(command: string, ui: TUI, excludeFromContext = false) {\n\t\tsuper();\n\t\tthis.command = command;\n\t\tthis.ui = ui;\n\n\t\t// Use dim border for excluded-from-context commands (!! prefix)\n\t\tconst colorKey = excludeFromContext ? \"dim\" : \"bashMode\";\n\t\tconst borderColor = (str: string) => theme.fg(colorKey, str);\n\n\t\t// Add spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Top border\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\n\t\t// Content container (holds dynamic content between borders)\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\t// Command header\n\t\tconst header = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), 1, 0);\n\t\tthis.contentContainer.addChild(header);\n\n\t\t// Loader\n\t\tthis.loader = new Loader(\n\t\t\tui,\n\t\t\t(spinner) => theme.fg(colorKey, spinner),\n\t\t\t(text) => theme.fg(\"muted\", text),\n\t\t\t`Running... (${keyText(\"tui.select.cancel\")} to cancel)`, // Plain text for loader\n\t\t);\n\t\tthis.contentContainer.addChild(this.loader);\n\n\t\t// Bottom border\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\t}\n\n\t/**\n\t * Set whether the output is expanded (shows full output) or collapsed (preview only).\n\t */\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tappendOutput(chunk: string): void {\n\t\t// Strip ANSI codes and normalize line endings\n\t\t// Note: binary data is already sanitized in tui-renderer.ts executeBashCommand\n\t\tconst clean = stripAnsi(chunk).replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\t\t// Append to output lines\n\t\tconst newLines = clean.split(\"\\n\");\n\t\tif (this.outputLines.length > 0 && newLines.length > 0) {\n\t\t\t// Append first chunk to last line (incomplete line continuation)\n\t\t\tthis.outputLines[this.outputLines.length - 1] += newLines[0];\n\t\t\tthis.outputLines.push(...newLines.slice(1));\n\t\t} else {\n\t\t\tthis.outputLines.push(...newLines);\n\t\t}\n\n\t\tthis.updateDisplay();\n\t}\n\n\tsetComplete(\n\t\texitCode: number | undefined,\n\t\tcancelled: boolean,\n\t\ttruncationResult?: TruncationResult,\n\t\tfullOutputPath?: string,\n\t): void {\n\t\tthis.exitCode = exitCode;\n\t\tthis.status = cancelled\n\t\t\t? \"cancelled\"\n\t\t\t: exitCode !== 0 && exitCode !== undefined && exitCode !== null\n\t\t\t\t? \"error\"\n\t\t\t\t: \"complete\";\n\t\tthis.truncationResult = truncationResult;\n\t\tthis.fullOutputPath = fullOutputPath;\n\n\t\t// Stop loader\n\t\tthis.loader.stop();\n\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\t// Apply truncation for LLM context limits (same limits as bash tool)\n\t\tconst fullOutput = this.outputLines.join(\"\\n\");\n\t\tconst contextTruncation = truncateTail(fullOutput, {\n\t\t\tmaxLines: DEFAULT_MAX_LINES,\n\t\t\tmaxBytes: DEFAULT_MAX_BYTES,\n\t\t});\n\n\t\t// Get the lines to potentially display (after context truncation)\n\t\tconst availableLines = contextTruncation.content ? contextTruncation.content.split(\"\\n\") : [];\n\n\t\t// Apply preview truncation based on expanded state\n\t\tconst previewLogicalLines = availableLines.slice(-PREVIEW_LINES);\n\t\tconst hiddenLineCount = availableLines.length - previewLogicalLines.length;\n\n\t\t// Rebuild content container\n\t\tthis.contentContainer.clear();\n\n\t\t// Command header\n\t\tconst header = new Text(theme.fg(\"bashMode\", theme.bold(`$ ${this.command}`)), 1, 0);\n\t\tthis.contentContainer.addChild(header);\n\n\t\t// Output\n\t\tif (availableLines.length > 0) {\n\t\t\tif (this.expanded) {\n\t\t\t\t// Show all lines\n\t\t\t\tconst displayText = availableLines.map((line) => theme.fg(\"muted\", line)).join(\"\\n\");\n\t\t\t\tthis.contentContainer.addChild(new Text(`\\n${displayText}`, 1, 0));\n\t\t\t} else {\n\t\t\t\t// Use shared visual truncation utility\n\t\t\t\tconst styledOutput = previewLogicalLines.map((line) => theme.fg(\"muted\", line)).join(\"\\n\");\n\t\t\t\tconst { visualLines } = truncateToVisualLines(\n\t\t\t\t\t`\\n${styledOutput}`,\n\t\t\t\t\tPREVIEW_LINES,\n\t\t\t\t\tthis.ui.terminal.columns,\n\t\t\t\t\t1, // padding\n\t\t\t\t);\n\t\t\t\tthis.contentContainer.addChild({ render: () => visualLines, invalidate: () => {} });\n\t\t\t}\n\t\t}\n\n\t\t// Loader or status\n\t\tif (this.status === \"running\") {\n\t\t\tthis.contentContainer.addChild(this.loader);\n\t\t} else {\n\t\t\tconst statusParts: string[] = [];\n\n\t\t\t// Show how many lines are hidden (collapsed preview)\n\t\t\tif (hiddenLineCount > 0) {\n\t\t\t\tif (this.expanded) {\n\t\t\t\t\tstatusParts.push(`(${keyHint(\"app.tools.expand\", \"to collapse\")})`);\n\t\t\t\t} else {\n\t\t\t\t\tstatusParts.push(\n\t\t\t\t\t\t`${theme.fg(\"muted\", `... ${hiddenLineCount} more lines`)} (${keyHint(\"app.tools.expand\", \"to expand\")})`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.status === \"cancelled\") {\n\t\t\t\tstatusParts.push(theme.fg(\"warning\", \"(cancelled)\"));\n\t\t\t} else if (this.status === \"error\") {\n\t\t\t\tstatusParts.push(theme.fg(\"error\", `(exit ${this.exitCode})`));\n\t\t\t}\n\n\t\t\t// Add truncation warning (context truncation, not preview truncation)\n\t\t\tconst wasTruncated = this.truncationResult?.truncated || contextTruncation.truncated;\n\t\t\tif (wasTruncated && this.fullOutputPath) {\n\t\t\t\tstatusParts.push(theme.fg(\"warning\", `Output truncated. Full output: ${this.fullOutputPath}`));\n\t\t\t}\n\n\t\t\tif (statusParts.length > 0) {\n\t\t\t\tthis.contentContainer.addChild(new Text(`\\n${statusParts.join(\"\\n\")}`, 1, 0));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get the raw output for creating BashExecutionMessage.\n\t */\n\tgetOutput(): string {\n\t\treturn this.outputLines.join(\"\\n\");\n\t}\n\n\t/**\n\t * Get the command that was executed.\n\t */\n\tgetCommand(): string {\n\t\treturn this.command;\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bordered-loader.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/bordered-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAwB,KAAK,GAAG,EAAE,MAAM,sBAAsB,CAAC;AACpG,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI/C,mDAAmD;AACnD,qBAAa,cAAe,SAAQ,SAAS;IAC5C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAE3C,YAAY,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,EA4BvF;IAED,IAAI,MAAM,IAAI,WAAW,CAKxB;IAED,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,EAIvC;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI9B;IAED,OAAO,IAAI,IAAI,CAId;CACD","sourcesContent":["import { CancellableLoader, Container, Loader, Spacer, Text, type TUI } from \"@mariozechner/pi-tui\";\nimport type { Theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyHint } from \"./keybinding-hints.js\";\n\n/** Loader wrapped with borders for extension UI */\nexport class BorderedLoader extends Container {\n\tprivate loader: CancellableLoader | Loader;\n\tprivate cancellable: boolean;\n\tprivate signalController?: AbortController;\n\n\tconstructor(tui: TUI, theme: Theme, message: string, options?: { cancellable?: boolean }) {\n\t\tsuper();\n\t\tthis.cancellable = options?.cancellable ?? true;\n\t\tconst borderColor = (s: string) => theme.fg(\"border\", s);\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\t\tif (this.cancellable) {\n\t\t\tthis.loader = new CancellableLoader(\n\t\t\t\ttui,\n\t\t\t\t(s) => theme.fg(\"accent\", s),\n\t\t\t\t(s) => theme.fg(\"muted\", s),\n\t\t\t\tmessage,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.signalController = new AbortController();\n\t\t\tthis.loader = new Loader(\n\t\t\t\ttui,\n\t\t\t\t(s) => theme.fg(\"accent\", s),\n\t\t\t\t(s) => theme.fg(\"muted\", s),\n\t\t\t\tmessage,\n\t\t\t);\n\t\t}\n\t\tthis.addChild(this.loader);\n\t\tif (this.cancellable) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(keyHint(\"
|
|
1
|
+
{"version":3,"file":"bordered-loader.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/bordered-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAwB,KAAK,GAAG,EAAE,MAAM,sBAAsB,CAAC;AACpG,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI/C,mDAAmD;AACnD,qBAAa,cAAe,SAAQ,SAAS;IAC5C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAE3C,YAAY,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,EA4BvF;IAED,IAAI,MAAM,IAAI,WAAW,CAKxB;IAED,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,EAIvC;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI9B;IAED,OAAO,IAAI,IAAI,CAId;CACD","sourcesContent":["import { CancellableLoader, Container, Loader, Spacer, Text, type TUI } from \"@mariozechner/pi-tui\";\nimport type { Theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyHint } from \"./keybinding-hints.js\";\n\n/** Loader wrapped with borders for extension UI */\nexport class BorderedLoader extends Container {\n\tprivate loader: CancellableLoader | Loader;\n\tprivate cancellable: boolean;\n\tprivate signalController?: AbortController;\n\n\tconstructor(tui: TUI, theme: Theme, message: string, options?: { cancellable?: boolean }) {\n\t\tsuper();\n\t\tthis.cancellable = options?.cancellable ?? true;\n\t\tconst borderColor = (s: string) => theme.fg(\"border\", s);\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\t\tif (this.cancellable) {\n\t\t\tthis.loader = new CancellableLoader(\n\t\t\t\ttui,\n\t\t\t\t(s) => theme.fg(\"accent\", s),\n\t\t\t\t(s) => theme.fg(\"muted\", s),\n\t\t\t\tmessage,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.signalController = new AbortController();\n\t\t\tthis.loader = new Loader(\n\t\t\t\ttui,\n\t\t\t\t(s) => theme.fg(\"accent\", s),\n\t\t\t\t(s) => theme.fg(\"muted\", s),\n\t\t\t\tmessage,\n\t\t\t);\n\t\t}\n\t\tthis.addChild(this.loader);\n\t\tif (this.cancellable) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(keyHint(\"tui.select.cancel\", \"cancel\"), 1, 0));\n\t\t}\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\t}\n\n\tget signal(): AbortSignal {\n\t\tif (this.cancellable) {\n\t\t\treturn (this.loader as CancellableLoader).signal;\n\t\t}\n\t\treturn this.signalController?.signal ?? new AbortController().signal;\n\t}\n\n\tset onAbort(fn: (() => void) | undefined) {\n\t\tif (this.cancellable) {\n\t\t\t(this.loader as CancellableLoader).onAbort = fn;\n\t\t}\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.cancellable) {\n\t\t\t(this.loader as CancellableLoader).handleInput(data);\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tif (\"dispose\" in this.loader && typeof this.loader.dispose === \"function\") {\n\t\t\tthis.loader.dispose();\n\t\t}\n\t}\n}\n"]}
|
|
@@ -3,9 +3,6 @@ import { DynamicBorder } from "./dynamic-border.js";
|
|
|
3
3
|
import { keyHint } from "./keybinding-hints.js";
|
|
4
4
|
/** Loader wrapped with borders for extension UI */
|
|
5
5
|
export class BorderedLoader extends Container {
|
|
6
|
-
loader;
|
|
7
|
-
cancellable;
|
|
8
|
-
signalController;
|
|
9
6
|
constructor(tui, theme, message, options) {
|
|
10
7
|
super();
|
|
11
8
|
this.cancellable = options?.cancellable ?? true;
|
|
@@ -21,7 +18,7 @@ export class BorderedLoader extends Container {
|
|
|
21
18
|
this.addChild(this.loader);
|
|
22
19
|
if (this.cancellable) {
|
|
23
20
|
this.addChild(new Spacer(1));
|
|
24
|
-
this.addChild(new Text(keyHint("
|
|
21
|
+
this.addChild(new Text(keyHint("tui.select.cancel", "cancel"), 1, 0));
|
|
25
22
|
}
|
|
26
23
|
this.addChild(new Spacer(1));
|
|
27
24
|
this.addChild(new DynamicBorder(borderColor));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bordered-loader.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/bordered-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAY,MAAM,sBAAsB,CAAC;AAEpG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,mDAAmD;AACnD,MAAM,OAAO,cAAe,SAAQ,SAAS;
|
|
1
|
+
{"version":3,"file":"bordered-loader.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/bordered-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { CancellableLoader, Container, Loader, Spacer, Text, type TUI } from \"@mariozechner/pi-tui\";\nimport type { Theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyHint } from \"./keybinding-hints.js\";\n\n/** Loader wrapped with borders for extension UI */\nexport class BorderedLoader extends Container {\n\tprivate loader: CancellableLoader | Loader;\n\tprivate cancellable: boolean;\n\tprivate signalController?: AbortController;\n\n\tconstructor(tui: TUI, theme: Theme, message: string, options?: { cancellable?: boolean }) {\n\t\tsuper();\n\t\tthis.cancellable = options?.cancellable ?? true;\n\t\tconst borderColor = (s: string) => theme.fg(\"border\", s);\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\t\tif (this.cancellable) {\n\t\t\tthis.loader = new CancellableLoader(\n\t\t\t\ttui,\n\t\t\t\t(s) => theme.fg(\"accent\", s),\n\t\t\t\t(s) => theme.fg(\"muted\", s),\n\t\t\t\tmessage,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.signalController = new AbortController();\n\t\t\tthis.loader = new Loader(\n\t\t\t\ttui,\n\t\t\t\t(s) => theme.fg(\"accent\", s),\n\t\t\t\t(s) => theme.fg(\"muted\", s),\n\t\t\t\tmessage,\n\t\t\t);\n\t\t}\n\t\tthis.addChild(this.loader);\n\t\tif (this.cancellable) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(keyHint(\"tui.select.cancel\", \"cancel\"), 1, 0));\n\t\t}\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder(borderColor));\n\t}\n\n\tget signal(): AbortSignal {\n\t\tif (this.cancellable) {\n\t\t\treturn (this.loader as CancellableLoader).signal;\n\t\t}\n\t\treturn this.signalController?.signal ?? new AbortController().signal;\n\t}\n\n\tset onAbort(fn: (() => void) | undefined) {\n\t\tif (this.cancellable) {\n\t\t\t(this.loader as CancellableLoader).onAbort = fn;\n\t\t}\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.cancellable) {\n\t\t\t(this.loader as CancellableLoader).handleInput(data);\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tif (\"dispose\" in this.loader && typeof this.loader.dispose === \"function\") {\n\t\t\tthis.loader.dispose();\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/branch-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAgB,MAAM,sBAAsB,CAAC;AACvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAItE;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,GAAG;IACrD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,OAAO,EAAE,oBAAoB,EAAE,aAAa,GAAE,aAAkC,EAK3F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CA0BrB","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport type { BranchSummaryMessage } from \"../../../core/messages.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"branch-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/branch-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAgB,MAAM,sBAAsB,CAAC;AACvF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAItE;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,GAAG;IACrD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,OAAO,EAAE,oBAAoB,EAAE,aAAa,GAAE,aAAkC,EAK3F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CA0BrB","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport type { BranchSummaryMessage } from \"../../../core/messages.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a branch summary message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class BranchSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: BranchSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[branch]\\x1b[22m`);\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = \"**Branch Summary**\\n\\n\";\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", \"Branch summary (\") +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { Box, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
|
|
2
2
|
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
3
|
-
import {
|
|
3
|
+
import { keyText } from "./keybinding-hints.js";
|
|
4
4
|
/**
|
|
5
5
|
* Component that renders a branch summary message with collapsed/expanded state.
|
|
6
6
|
* Uses same background color as custom messages for visual consistency.
|
|
7
7
|
*/
|
|
8
8
|
export class BranchSummaryMessageComponent extends Box {
|
|
9
|
-
expanded = false;
|
|
10
|
-
message;
|
|
11
|
-
markdownTheme;
|
|
12
9
|
constructor(message, markdownTheme = getMarkdownTheme()) {
|
|
13
10
|
super(1, 1, (t) => theme.bg("customMessageBg", t));
|
|
11
|
+
this.expanded = false;
|
|
14
12
|
this.message = message;
|
|
15
13
|
this.markdownTheme = markdownTheme;
|
|
16
14
|
this.updateDisplay();
|
|
@@ -36,7 +34,7 @@ export class BranchSummaryMessageComponent extends Box {
|
|
|
36
34
|
}
|
|
37
35
|
else {
|
|
38
36
|
this.addChild(new Text(theme.fg("customMessageText", "Branch summary (") +
|
|
39
|
-
theme.fg("dim",
|
|
37
|
+
theme.fg("dim", keyText("app.tools.expand")) +
|
|
40
38
|
theme.fg("customMessageText", " to expand)"), 0, 0));
|
|
41
39
|
}
|
|
42
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/branch-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEvF,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"branch-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/branch-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEvF,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD;;;GAGG;AACH,MAAM,OAAO,6BAA8B,SAAQ,GAAG;IAKrD,YAAY,OAA6B,EAAE,aAAa,GAAkB,gBAAgB,EAAE;QAC3F,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;QAL5C,aAAQ,GAAG,KAAK,CAAC;QAMxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAED,WAAW,CAAC,QAAiB;QAC5B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAEQ,UAAU;QAClB,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAEO,aAAa;QACpB,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC,oBAAoB,EAAE,yBAAyB,CAAC,CAAC;QACxE,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,wBAAwB,CAAC;YACxC,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACrE,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,CACZ,IAAI,IAAI,CACP,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,kBAAkB,CAAC;gBAChD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;gBAC5C,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,aAAa,CAAC,EAC7C,CAAC,EACD,CAAC,CACD,CACD,CAAC;QACH,CAAC;IACF,CAAC;CACD","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport type { BranchSummaryMessage } from \"../../../core/messages.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a branch summary message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class BranchSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: BranchSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[branch]\\x1b[22m`);\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = \"**Branch Summary**\\n\\n\";\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", \"Branch summary (\") +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAgB,MAAM,sBAAsB,CAAC;AACvF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAI1E;;;GAGG;AACH,qBAAa,iCAAkC,SAAQ,GAAG;IACzD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,OAAO,EAAE,wBAAwB,EAAE,aAAa,GAAE,aAAkC,EAK/F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CA2BrB","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport type { CompactionSummaryMessage } from \"../../../core/messages.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"compaction-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAgB,MAAM,sBAAsB,CAAC;AACvF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAI1E;;;GAGG;AACH,qBAAa,iCAAkC,SAAQ,GAAG;IACzD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,OAAO,EAAE,wBAAwB,EAAE,aAAa,GAAE,aAAkC,EAK/F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CA2BrB","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport type { CompactionSummaryMessage } from \"../../../core/messages.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a compaction message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class CompactionSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst tokenStr = this.message.tokensBefore.toLocaleString();\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[compaction]\\x1b[22m`);\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**Compacted from ${tokenStr} tokens**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", `Compacted from ${tokenStr} tokens (`) +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { Box, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
|
|
2
2
|
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
3
|
-
import {
|
|
3
|
+
import { keyText } from "./keybinding-hints.js";
|
|
4
4
|
/**
|
|
5
5
|
* Component that renders a compaction message with collapsed/expanded state.
|
|
6
6
|
* Uses same background color as custom messages for visual consistency.
|
|
7
7
|
*/
|
|
8
8
|
export class CompactionSummaryMessageComponent extends Box {
|
|
9
|
-
expanded = false;
|
|
10
|
-
message;
|
|
11
|
-
markdownTheme;
|
|
12
9
|
constructor(message, markdownTheme = getMarkdownTheme()) {
|
|
13
10
|
super(1, 1, (t) => theme.bg("customMessageBg", t));
|
|
11
|
+
this.expanded = false;
|
|
14
12
|
this.message = message;
|
|
15
13
|
this.markdownTheme = markdownTheme;
|
|
16
14
|
this.updateDisplay();
|
|
@@ -37,7 +35,7 @@ export class CompactionSummaryMessageComponent extends Box {
|
|
|
37
35
|
}
|
|
38
36
|
else {
|
|
39
37
|
this.addChild(new Text(theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (`) +
|
|
40
|
-
theme.fg("dim",
|
|
38
|
+
theme.fg("dim", keyText("app.tools.expand")) +
|
|
41
39
|
theme.fg("customMessageText", " to expand)"), 0, 0));
|
|
42
40
|
}
|
|
43
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEvF,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"compaction-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEvF,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD;;;GAGG;AACH,MAAM,OAAO,iCAAkC,SAAQ,GAAG;IAKzD,YAAY,OAAiC,EAAE,aAAa,GAAkB,gBAAgB,EAAE;QAC/F,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;QAL5C,aAAQ,GAAG,KAAK,CAAC;QAMxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAED,WAAW,CAAC,QAAiB;QAC5B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAEQ,UAAU;QAClB,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAEO,aAAa;QACpB,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAC5D,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC,oBAAoB,EAAE,6BAA6B,CAAC,CAAC;QAC5E,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,oBAAoB,QAAQ,eAAe,CAAC;YAC3D,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACrE,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,CACZ,IAAI,IAAI,CACP,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,kBAAkB,QAAQ,WAAW,CAAC;gBACnE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;gBAC5C,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,aAAa,CAAC,EAC7C,CAAC,EACD,CAAC,CACD,CACD,CAAC;QACH,CAAC;IACF,CAAC;CACD","sourcesContent":["import { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport type { CompactionSummaryMessage } from \"../../../core/messages.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a compaction message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class CompactionSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst tokenStr = this.message.tokensBefore.toLocaleString();\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[compaction]\\x1b[22m`);\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**Compacted from ${tokenStr} tokens**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", `Compacted from ${tokenStr} tokens (`) +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/config-selector.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TUI component for managing package resources (enable/disable)\n */\n\nimport { basename, dirname, join, relative } from \"node:path\";\nimport {\n\ttype Component,\n\tContainer,\n\ttype Focusable,\n\tgetEditorKeybindings,\n\tInput,\n\tmatchesKey,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\nimport { CONFIG_DIR_NAME } from \"../../../config.js\";\nimport type { PathMetadata, ResolvedPaths, ResolvedResource } from \"../../../core/package-manager.js\";\nimport type { PackageSource, SettingsManager } from \"../../../core/settings-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { rawKeyHint } from \"./keybinding-hints.js\";\n\ntype ResourceType = \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\nconst RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {\n\textensions: \"Extensions\",\n\tskills: \"Skills\",\n\tprompts: \"Prompts\",\n\tthemes: \"Themes\",\n};\n\ninterface ResourceItem {\n\tpath: string;\n\tenabled: boolean;\n\tmetadata: PathMetadata;\n\tresourceType: ResourceType;\n\tdisplayName: string;\n\tgroupKey: string;\n\tsubgroupKey: string;\n}\n\ninterface ResourceSubgroup {\n\ttype: ResourceType;\n\tlabel: string;\n\titems: ResourceItem[];\n}\n\ninterface ResourceGroup {\n\tkey: string;\n\tlabel: string;\n\tscope: \"user\" | \"project\" | \"temporary\";\n\torigin: \"package\" | \"top-level\";\n\tsource: string;\n\tsubgroups: ResourceSubgroup[];\n}\n\nfunction getGroupLabel(metadata: PathMetadata): string {\n\tif (metadata.origin === \"package\") {\n\t\treturn `${metadata.source} (${metadata.scope})`;\n\t}\n\t// Top-level resources\n\tif (metadata.source === \"auto\") {\n\t\treturn metadata.scope === \"user\" ? \"User (~/.pi/agent/)\" : \"Project (.pi/)\";\n\t}\n\treturn metadata.scope === \"user\" ? \"User settings\" : \"Project settings\";\n}\n\nfunction buildGroups(resolved: ResolvedPaths): ResourceGroup[] {\n\tconst groupMap = new Map<string, ResourceGroup>();\n\n\tconst addToGroup = (resources: ResolvedResource[], resourceType: ResourceType) => {\n\t\tfor (const res of resources) {\n\t\t\tconst { path, enabled, metadata } = res;\n\t\t\tconst groupKey = `${metadata.origin}:${metadata.scope}:${metadata.source}`;\n\n\t\t\tif (!groupMap.has(groupKey)) {\n\t\t\t\tgroupMap.set(groupKey, {\n\t\t\t\t\tkey: groupKey,\n\t\t\t\t\tlabel: getGroupLabel(metadata),\n\t\t\t\t\tscope: metadata.scope,\n\t\t\t\t\torigin: metadata.origin,\n\t\t\t\t\tsource: metadata.source,\n\t\t\t\t\tsubgroups: [],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst group = groupMap.get(groupKey)!;\n\t\t\tconst subgroupKey = `${groupKey}:${resourceType}`;\n\n\t\t\tlet subgroup = group.subgroups.find((sg) => sg.type === resourceType);\n\t\t\tif (!subgroup) {\n\t\t\t\tsubgroup = {\n\t\t\t\t\ttype: resourceType,\n\t\t\t\t\tlabel: RESOURCE_TYPE_LABELS[resourceType],\n\t\t\t\t\titems: [],\n\t\t\t\t};\n\t\t\t\tgroup.subgroups.push(subgroup);\n\t\t\t}\n\n\t\t\tconst fileName = basename(path);\n\t\t\tconst parentFolder = basename(dirname(path));\n\t\t\tlet displayName: string;\n\t\t\tif (resourceType === \"extensions\" && parentFolder !== \"extensions\") {\n\t\t\t\tdisplayName = `${parentFolder}/${fileName}`;\n\t\t\t} else if (resourceType === \"skills\" && fileName === \"SKILL.md\") {\n\t\t\t\tdisplayName = parentFolder;\n\t\t\t} else {\n\t\t\t\tdisplayName = fileName;\n\t\t\t}\n\t\t\tsubgroup.items.push({\n\t\t\t\tpath,\n\t\t\t\tenabled,\n\t\t\t\tmetadata,\n\t\t\t\tresourceType,\n\t\t\t\tdisplayName,\n\t\t\t\tgroupKey,\n\t\t\t\tsubgroupKey,\n\t\t\t});\n\t\t}\n\t};\n\n\taddToGroup(resolved.extensions, \"extensions\");\n\taddToGroup(resolved.skills, \"skills\");\n\taddToGroup(resolved.prompts, \"prompts\");\n\taddToGroup(resolved.themes, \"themes\");\n\n\t// Sort groups: packages first, then top-level; user before project\n\tconst groups = Array.from(groupMap.values());\n\tgroups.sort((a, b) => {\n\t\tif (a.origin !== b.origin) {\n\t\t\treturn a.origin === \"package\" ? -1 : 1;\n\t\t}\n\t\tif (a.scope !== b.scope) {\n\t\t\treturn a.scope === \"user\" ? -1 : 1;\n\t\t}\n\t\treturn a.source.localeCompare(b.source);\n\t});\n\n\t// Sort subgroups within each group by type order, and items by name\n\tconst typeOrder: Record<ResourceType, number> = { extensions: 0, skills: 1, prompts: 2, themes: 3 };\n\tfor (const group of groups) {\n\t\tgroup.subgroups.sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);\n\t\tfor (const subgroup of group.subgroups) {\n\t\t\tsubgroup.items.sort((a, b) => a.displayName.localeCompare(b.displayName));\n\t\t}\n\t}\n\n\treturn groups;\n}\n\ntype FlatEntry =\n\t| { type: \"group\"; group: ResourceGroup }\n\t| { type: \"subgroup\"; subgroup: ResourceSubgroup; group: ResourceGroup }\n\t| { type: \"item\"; item: ResourceItem };\n\nclass ConfigSelectorHeader implements Component {\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = theme.bold(\"Resource Configuration\");\n\t\tconst sep = theme.fg(\"muted\", \" · \");\n\t\tconst hint = rawKeyHint(\"space\", \"toggle\") + sep + rawKeyHint(\"esc\", \"close\");\n\t\tconst hintWidth = visibleWidth(hint);\n\t\tconst titleWidth = visibleWidth(title);\n\t\tconst spacing = Math.max(1, width - titleWidth - hintWidth);\n\n\t\treturn [\n\t\t\ttruncateToWidth(`${title}${\" \".repeat(spacing)}${hint}`, width, \"\"),\n\t\t\ttheme.fg(\"muted\", \"Type to filter resources\"),\n\t\t];\n\t}\n}\n\nclass ResourceList implements Component, Focusable {\n\tprivate groups: ResourceGroup[];\n\tprivate flatItems: FlatEntry[] = [];\n\tprivate filteredItems: FlatEntry[] = [];\n\tprivate selectedIndex = 0;\n\tprivate searchInput: Input;\n\tprivate maxVisible = 15;\n\tprivate settingsManager: SettingsManager;\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\n\tpublic onCancel?: () => void;\n\tpublic onExit?: () => void;\n\tpublic onToggle?: (item: ResourceItem, newEnabled: boolean) => void;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\n\tconstructor(groups: ResourceGroup[], settingsManager: SettingsManager, cwd: string, agentDir: string) {\n\t\tthis.groups = groups;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.cwd = cwd;\n\t\tthis.agentDir = agentDir;\n\t\tthis.searchInput = new Input();\n\t\tthis.buildFlatList();\n\t\tthis.filteredItems = [...this.flatItems];\n\t}\n\n\tprivate buildFlatList(): void {\n\t\tthis.flatItems = [];\n\t\tfor (const group of this.groups) {\n\t\t\tthis.flatItems.push({ type: \"group\", group });\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tthis.flatItems.push({ type: \"subgroup\", subgroup, group });\n\t\t\t\tfor (const item of subgroup.items) {\n\t\t\t\t\tthis.flatItems.push({ type: \"item\", item });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Start selection on first item (not header)\n\t\tthis.selectedIndex = this.flatItems.findIndex((e) => e.type === \"item\");\n\t\tif (this.selectedIndex < 0) this.selectedIndex = 0;\n\t}\n\n\tprivate findNextItem(fromIndex: number, direction: 1 | -1): number {\n\t\tlet idx = fromIndex + direction;\n\t\twhile (idx >= 0 && idx < this.filteredItems.length) {\n\t\t\tif (this.filteredItems[idx].type === \"item\") {\n\t\t\t\treturn idx;\n\t\t\t}\n\t\t\tidx += direction;\n\t\t}\n\t\treturn fromIndex; // Stay at current if no item found\n\t}\n\n\tprivate filterItems(query: string): void {\n\t\tif (!query.trim()) {\n\t\t\tthis.filteredItems = [...this.flatItems];\n\t\t\tthis.selectFirstItem();\n\t\t\treturn;\n\t\t}\n\n\t\tconst lowerQuery = query.toLowerCase();\n\t\tconst matchingItems = new Set<ResourceItem>();\n\t\tconst matchingSubgroups = new Set<ResourceSubgroup>();\n\t\tconst matchingGroups = new Set<ResourceGroup>();\n\n\t\tfor (const entry of this.flatItems) {\n\t\t\tif (entry.type === \"item\") {\n\t\t\t\tconst item = entry.item;\n\t\t\t\tif (\n\t\t\t\t\titem.displayName.toLowerCase().includes(lowerQuery) ||\n\t\t\t\t\titem.resourceType.toLowerCase().includes(lowerQuery) ||\n\t\t\t\t\titem.path.toLowerCase().includes(lowerQuery)\n\t\t\t\t) {\n\t\t\t\t\tmatchingItems.add(item);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Find which subgroups and groups contain matching items\n\t\tfor (const group of this.groups) {\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tfor (const item of subgroup.items) {\n\t\t\t\t\tif (matchingItems.has(item)) {\n\t\t\t\t\t\tmatchingSubgroups.add(subgroup);\n\t\t\t\t\t\tmatchingGroups.add(group);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.filteredItems = [];\n\t\tfor (const entry of this.flatItems) {\n\t\t\tif (entry.type === \"group\" && matchingGroups.has(entry.group)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t} else if (entry.type === \"subgroup\" && matchingSubgroups.has(entry.subgroup)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t} else if (entry.type === \"item\" && matchingItems.has(entry.item)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t}\n\t\t}\n\n\t\tthis.selectFirstItem();\n\t}\n\n\tprivate selectFirstItem(): void {\n\t\tconst firstItemIndex = this.filteredItems.findIndex((e) => e.type === \"item\");\n\t\tthis.selectedIndex = firstItemIndex >= 0 ? firstItemIndex : 0;\n\t}\n\n\tupdateItem(item: ResourceItem, enabled: boolean): void {\n\t\titem.enabled = enabled;\n\t\t// Update in groups too\n\t\tfor (const group of this.groups) {\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tconst found = subgroup.items.find((i) => i.path === item.path && i.resourceType === item.resourceType);\n\t\t\t\tif (found) {\n\t\t\t\t\tfound.enabled = enabled;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\");\n\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No resources found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst entry = this.filteredItems[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\tif (entry.type === \"group\") {\n\t\t\t\t// Main group header (no cursor)\n\t\t\t\tconst groupLine = theme.fg(\"accent\", theme.bold(entry.group.label));\n\t\t\t\tlines.push(truncateToWidth(` ${groupLine}`, width, \"\"));\n\t\t\t} else if (entry.type === \"subgroup\") {\n\t\t\t\t// Subgroup header (indented, no cursor)\n\t\t\t\tconst subgroupLine = theme.fg(\"muted\", entry.subgroup.label);\n\t\t\t\tlines.push(truncateToWidth(` ${subgroupLine}`, width, \"\"));\n\t\t\t} else {\n\t\t\t\t// Resource item (cursor only on items)\n\t\t\t\tconst item = entry.item;\n\t\t\t\tconst cursor = isSelected ? \"> \" : \" \";\n\t\t\t\tconst checkbox = item.enabled ? theme.fg(\"success\", \"[x]\") : theme.fg(\"dim\", \"[ ]\");\n\t\t\t\tconst name = isSelected ? theme.bold(item.displayName) : item.displayName;\n\t\t\t\tlines.push(truncateToWidth(`${cursor} ${checkbox} ${name}`, width, \"...\"));\n\t\t\t}\n\t\t}\n\n\t\t// Scroll indicator\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tlines.push(theme.fg(\"dim\", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`));\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getEditorKeybindings();\n\n\t\tif (kb.matches(data, \"selectUp\")) {\n\t\t\tthis.selectedIndex = this.findNextItem(this.selectedIndex, -1);\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"selectDown\")) {\n\t\t\tthis.selectedIndex = this.findNextItem(this.selectedIndex, 1);\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"selectPageUp\")) {\n\t\t\t// Jump up by maxVisible, then find nearest item\n\t\t\tlet target = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t\twhile (target < this.filteredItems.length && this.filteredItems[target].type !== \"item\") {\n\t\t\t\ttarget++;\n\t\t\t}\n\t\t\tif (target < this.filteredItems.length) {\n\t\t\t\tthis.selectedIndex = target;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"selectPageDown\")) {\n\t\t\t// Jump down by maxVisible, then find nearest item\n\t\t\tlet target = Math.min(this.filteredItems.length - 1, this.selectedIndex + this.maxVisible);\n\t\t\twhile (target >= 0 && this.filteredItems[target].type !== \"item\") {\n\t\t\t\ttarget--;\n\t\t\t}\n\t\t\tif (target >= 0) {\n\t\t\t\tthis.selectedIndex = target;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"selectCancel\")) {\n\t\t\tthis.onCancel?.();\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.onExit?.();\n\t\t\treturn;\n\t\t}\n\t\tif (data === \" \" || kb.matches(data, \"selectConfirm\")) {\n\t\t\tconst entry = this.filteredItems[this.selectedIndex];\n\t\t\tif (entry?.type === \"item\") {\n\t\t\t\tconst newEnabled = !entry.item.enabled;\n\t\t\t\tthis.toggleResource(entry.item, newEnabled);\n\t\t\t\tthis.updateItem(entry.item, newEnabled);\n\t\t\t\tthis.onToggle?.(entry.item, newEnabled);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to search input\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.filterItems(this.searchInput.getValue());\n\t}\n\n\tprivate toggleResource(item: ResourceItem, enabled: boolean): void {\n\t\tif (item.metadata.origin === \"top-level\") {\n\t\t\tthis.toggleTopLevelResource(item, enabled);\n\t\t} else {\n\t\t\tthis.togglePackageResource(item, enabled);\n\t\t}\n\t}\n\n\tprivate toggleTopLevelResource(item: ResourceItem, enabled: boolean): void {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst settings =\n\t\t\tscope === \"project\" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();\n\n\t\tconst arrayKey = item.resourceType as \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\t\tconst current = (settings[arrayKey] ?? []) as string[];\n\n\t\t// Generate pattern for this resource\n\t\tconst pattern = this.getResourcePattern(item);\n\t\tconst disablePattern = `-${pattern}`;\n\t\tconst enablePattern = `+${pattern}`;\n\n\t\t// Filter out existing patterns for this resource\n\t\tconst updated = current.filter((p) => {\n\t\t\tconst stripped = p.startsWith(\"!\") || p.startsWith(\"+\") || p.startsWith(\"-\") ? p.slice(1) : p;\n\t\t\treturn stripped !== pattern;\n\t\t});\n\n\t\tif (enabled) {\n\t\t\tupdated.push(enablePattern);\n\t\t} else {\n\t\t\tupdated.push(disablePattern);\n\t\t}\n\n\t\tif (scope === \"project\") {\n\t\t\tif (arrayKey === \"extensions\") {\n\t\t\t\tthis.settingsManager.setProjectExtensionPaths(updated);\n\t\t\t} else if (arrayKey === \"skills\") {\n\t\t\t\tthis.settingsManager.setProjectSkillPaths(updated);\n\t\t\t} else if (arrayKey === \"prompts\") {\n\t\t\t\tthis.settingsManager.setProjectPromptTemplatePaths(updated);\n\t\t\t} else if (arrayKey === \"themes\") {\n\t\t\t\tthis.settingsManager.setProjectThemePaths(updated);\n\t\t\t}\n\t\t} else {\n\t\t\tif (arrayKey === \"extensions\") {\n\t\t\t\tthis.settingsManager.setExtensionPaths(updated);\n\t\t\t} else if (arrayKey === \"skills\") {\n\t\t\t\tthis.settingsManager.setSkillPaths(updated);\n\t\t\t} else if (arrayKey === \"prompts\") {\n\t\t\t\tthis.settingsManager.setPromptTemplatePaths(updated);\n\t\t\t} else if (arrayKey === \"themes\") {\n\t\t\t\tthis.settingsManager.setThemePaths(updated);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate togglePackageResource(item: ResourceItem, enabled: boolean): void {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst settings =\n\t\t\tscope === \"project\" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();\n\n\t\tconst packages = [...(settings.packages ?? [])] as PackageSource[];\n\t\tconst pkgIndex = packages.findIndex((pkg) => {\n\t\t\tconst source = typeof pkg === \"string\" ? pkg : pkg.source;\n\t\t\treturn source === item.metadata.source;\n\t\t});\n\n\t\tif (pkgIndex === -1) return;\n\n\t\tlet pkg = packages[pkgIndex];\n\n\t\t// Convert string to object form if needed\n\t\tif (typeof pkg === \"string\") {\n\t\t\tpkg = { source: pkg };\n\t\t\tpackages[pkgIndex] = pkg;\n\t\t}\n\n\t\t// Get the resource array for this type\n\t\tconst arrayKey = item.resourceType as \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\t\tconst current = (pkg[arrayKey] ?? []) as string[];\n\n\t\t// Generate pattern relative to package root\n\t\tconst pattern = this.getPackageResourcePattern(item);\n\t\tconst disablePattern = `-${pattern}`;\n\t\tconst enablePattern = `+${pattern}`;\n\n\t\t// Filter out existing patterns for this resource\n\t\tconst updated = current.filter((p) => {\n\t\t\tconst stripped = p.startsWith(\"!\") || p.startsWith(\"+\") || p.startsWith(\"-\") ? p.slice(1) : p;\n\t\t\treturn stripped !== pattern;\n\t\t});\n\n\t\tif (enabled) {\n\t\t\tupdated.push(enablePattern);\n\t\t} else {\n\t\t\tupdated.push(disablePattern);\n\t\t}\n\n\t\t(pkg as Record<string, unknown>)[arrayKey] = updated.length > 0 ? updated : undefined;\n\n\t\t// Clean up empty filter object\n\t\tconst hasFilters = [\"extensions\", \"skills\", \"prompts\", \"themes\"].some(\n\t\t\t(k) => (pkg as Record<string, unknown>)[k] !== undefined,\n\t\t);\n\t\tif (!hasFilters) {\n\t\t\tpackages[pkgIndex] = (pkg as { source: string }).source;\n\t\t}\n\n\t\tif (scope === \"project\") {\n\t\t\tthis.settingsManager.setProjectPackages(packages);\n\t\t} else {\n\t\t\tthis.settingsManager.setPackages(packages);\n\t\t}\n\t}\n\n\tprivate getTopLevelBaseDir(scope: \"user\" | \"project\"): string {\n\t\treturn scope === \"project\" ? join(this.cwd, CONFIG_DIR_NAME) : this.agentDir;\n\t}\n\n\tprivate getResourcePattern(item: ResourceItem): string {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst baseDir = this.getTopLevelBaseDir(scope);\n\t\treturn relative(baseDir, item.path);\n\t}\n\n\tprivate getPackageResourcePattern(item: ResourceItem): string {\n\t\tconst baseDir = item.metadata.baseDir ?? dirname(item.path);\n\t\treturn relative(baseDir, item.path);\n\t}\n}\n\nexport class ConfigSelectorComponent extends Container implements Focusable {\n\tprivate resourceList: ResourceList;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.resourceList.focused = value;\n\t}\n\n\tconstructor(\n\t\tresolvedPaths: ResolvedPaths,\n\t\tsettingsManager: SettingsManager,\n\t\tcwd: string,\n\t\tagentDir: string,\n\t\tonClose: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst groups = buildGroups(resolvedPaths);\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new ConfigSelectorHeader());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Resource list\n\t\tthis.resourceList = new ResourceList(groups, settingsManager, cwd, agentDir);\n\t\tthis.resourceList.onCancel = onClose;\n\t\tthis.resourceList.onExit = onExit;\n\t\tthis.resourceList.onToggle = () => requestRender();\n\t\tthis.addChild(this.resourceList);\n\n\t\t// Bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetResourceList(): ResourceList {\n\t\treturn this.resourceList;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/config-selector.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EACN,KAAK,SAAS,EACd,SAAS,EACT,KAAK,SAAS,EAOd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAoB,MAAM,kCAAkC,CAAC;AACtG,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAKxF,KAAK,YAAY,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AASnE,UAAU,YAAY;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,gBAAgB;IACzB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,YAAY,EAAE,CAAC;CACtB;AAED,UAAU,aAAa;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;IACxC,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAuHD,cAAM,YAAa,YAAW,SAAS,EAAE,SAAS;IACjD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,aAAa,CAAmB;IACxC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,QAAQ,CAAS;IAElB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;IAEpE,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YAAY,MAAM,EAAE,aAAa,EAAE,EAAE,eAAe,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAQnG;IAED,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,WAAW;IAmDnB,OAAO,CAAC,eAAe;IAKvB,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAYrD;IAED,UAAU,IAAI,IAAI,CAAG;IAErB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA+C9B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAuD9B;IAED,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,sBAAsB;IAgD9B,OAAO,CAAC,qBAAqB;IA2D7B,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,yBAAyB;CAIjC;AAED,qBAAa,uBAAwB,SAAQ,SAAU,YAAW,SAAS;IAC1E,OAAO,CAAC,YAAY,CAAe;IAEnC,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YACC,aAAa,EAAE,aAAa,EAC5B,eAAe,EAAE,eAAe,EAChC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,IAAI,EACnB,MAAM,EAAE,MAAM,IAAI,EAClB,aAAa,EAAE,MAAM,IAAI,EAuBzB;IAED,eAAe,IAAI,YAAY,CAE9B;CACD","sourcesContent":["/**\n * TUI component for managing package resources (enable/disable)\n */\n\nimport { basename, dirname, join, relative } from \"node:path\";\nimport {\n\ttype Component,\n\tContainer,\n\ttype Focusable,\n\tgetKeybindings,\n\tInput,\n\tmatchesKey,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\nimport { CONFIG_DIR_NAME } from \"../../../config.js\";\nimport type { PathMetadata, ResolvedPaths, ResolvedResource } from \"../../../core/package-manager.js\";\nimport type { PackageSource, SettingsManager } from \"../../../core/settings-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { rawKeyHint } from \"./keybinding-hints.js\";\n\ntype ResourceType = \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\nconst RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {\n\textensions: \"Extensions\",\n\tskills: \"Skills\",\n\tprompts: \"Prompts\",\n\tthemes: \"Themes\",\n};\n\ninterface ResourceItem {\n\tpath: string;\n\tenabled: boolean;\n\tmetadata: PathMetadata;\n\tresourceType: ResourceType;\n\tdisplayName: string;\n\tgroupKey: string;\n\tsubgroupKey: string;\n}\n\ninterface ResourceSubgroup {\n\ttype: ResourceType;\n\tlabel: string;\n\titems: ResourceItem[];\n}\n\ninterface ResourceGroup {\n\tkey: string;\n\tlabel: string;\n\tscope: \"user\" | \"project\" | \"temporary\";\n\torigin: \"package\" | \"top-level\";\n\tsource: string;\n\tsubgroups: ResourceSubgroup[];\n}\n\nfunction getGroupLabel(metadata: PathMetadata): string {\n\tif (metadata.origin === \"package\") {\n\t\treturn `${metadata.source} (${metadata.scope})`;\n\t}\n\t// Top-level resources\n\tif (metadata.source === \"auto\") {\n\t\treturn metadata.scope === \"user\" ? \"User (~/.pi/agent/)\" : \"Project (.pi/)\";\n\t}\n\treturn metadata.scope === \"user\" ? \"User settings\" : \"Project settings\";\n}\n\nfunction buildGroups(resolved: ResolvedPaths): ResourceGroup[] {\n\tconst groupMap = new Map<string, ResourceGroup>();\n\n\tconst addToGroup = (resources: ResolvedResource[], resourceType: ResourceType) => {\n\t\tfor (const res of resources) {\n\t\t\tconst { path, enabled, metadata } = res;\n\t\t\tconst groupKey = `${metadata.origin}:${metadata.scope}:${metadata.source}`;\n\n\t\t\tif (!groupMap.has(groupKey)) {\n\t\t\t\tgroupMap.set(groupKey, {\n\t\t\t\t\tkey: groupKey,\n\t\t\t\t\tlabel: getGroupLabel(metadata),\n\t\t\t\t\tscope: metadata.scope,\n\t\t\t\t\torigin: metadata.origin,\n\t\t\t\t\tsource: metadata.source,\n\t\t\t\t\tsubgroups: [],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst group = groupMap.get(groupKey)!;\n\t\t\tconst subgroupKey = `${groupKey}:${resourceType}`;\n\n\t\t\tlet subgroup = group.subgroups.find((sg) => sg.type === resourceType);\n\t\t\tif (!subgroup) {\n\t\t\t\tsubgroup = {\n\t\t\t\t\ttype: resourceType,\n\t\t\t\t\tlabel: RESOURCE_TYPE_LABELS[resourceType],\n\t\t\t\t\titems: [],\n\t\t\t\t};\n\t\t\t\tgroup.subgroups.push(subgroup);\n\t\t\t}\n\n\t\t\tconst fileName = basename(path);\n\t\t\tconst parentFolder = basename(dirname(path));\n\t\t\tlet displayName: string;\n\t\t\tif (resourceType === \"extensions\" && parentFolder !== \"extensions\") {\n\t\t\t\tdisplayName = `${parentFolder}/${fileName}`;\n\t\t\t} else if (resourceType === \"skills\" && fileName === \"SKILL.md\") {\n\t\t\t\tdisplayName = parentFolder;\n\t\t\t} else {\n\t\t\t\tdisplayName = fileName;\n\t\t\t}\n\t\t\tsubgroup.items.push({\n\t\t\t\tpath,\n\t\t\t\tenabled,\n\t\t\t\tmetadata,\n\t\t\t\tresourceType,\n\t\t\t\tdisplayName,\n\t\t\t\tgroupKey,\n\t\t\t\tsubgroupKey,\n\t\t\t});\n\t\t}\n\t};\n\n\taddToGroup(resolved.extensions, \"extensions\");\n\taddToGroup(resolved.skills, \"skills\");\n\taddToGroup(resolved.prompts, \"prompts\");\n\taddToGroup(resolved.themes, \"themes\");\n\n\t// Sort groups: packages first, then top-level; user before project\n\tconst groups = Array.from(groupMap.values());\n\tgroups.sort((a, b) => {\n\t\tif (a.origin !== b.origin) {\n\t\t\treturn a.origin === \"package\" ? -1 : 1;\n\t\t}\n\t\tif (a.scope !== b.scope) {\n\t\t\treturn a.scope === \"user\" ? -1 : 1;\n\t\t}\n\t\treturn a.source.localeCompare(b.source);\n\t});\n\n\t// Sort subgroups within each group by type order, and items by name\n\tconst typeOrder: Record<ResourceType, number> = { extensions: 0, skills: 1, prompts: 2, themes: 3 };\n\tfor (const group of groups) {\n\t\tgroup.subgroups.sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);\n\t\tfor (const subgroup of group.subgroups) {\n\t\t\tsubgroup.items.sort((a, b) => a.displayName.localeCompare(b.displayName));\n\t\t}\n\t}\n\n\treturn groups;\n}\n\ntype FlatEntry =\n\t| { type: \"group\"; group: ResourceGroup }\n\t| { type: \"subgroup\"; subgroup: ResourceSubgroup; group: ResourceGroup }\n\t| { type: \"item\"; item: ResourceItem };\n\nclass ConfigSelectorHeader implements Component {\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = theme.bold(\"Resource Configuration\");\n\t\tconst sep = theme.fg(\"muted\", \" · \");\n\t\tconst hint = rawKeyHint(\"space\", \"toggle\") + sep + rawKeyHint(\"esc\", \"close\");\n\t\tconst hintWidth = visibleWidth(hint);\n\t\tconst titleWidth = visibleWidth(title);\n\t\tconst spacing = Math.max(1, width - titleWidth - hintWidth);\n\n\t\treturn [\n\t\t\ttruncateToWidth(`${title}${\" \".repeat(spacing)}${hint}`, width, \"\"),\n\t\t\ttheme.fg(\"muted\", \"Type to filter resources\"),\n\t\t];\n\t}\n}\n\nclass ResourceList implements Component, Focusable {\n\tprivate groups: ResourceGroup[];\n\tprivate flatItems: FlatEntry[] = [];\n\tprivate filteredItems: FlatEntry[] = [];\n\tprivate selectedIndex = 0;\n\tprivate searchInput: Input;\n\tprivate maxVisible = 15;\n\tprivate settingsManager: SettingsManager;\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\n\tpublic onCancel?: () => void;\n\tpublic onExit?: () => void;\n\tpublic onToggle?: (item: ResourceItem, newEnabled: boolean) => void;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\n\tconstructor(groups: ResourceGroup[], settingsManager: SettingsManager, cwd: string, agentDir: string) {\n\t\tthis.groups = groups;\n\t\tthis.settingsManager = settingsManager;\n\t\tthis.cwd = cwd;\n\t\tthis.agentDir = agentDir;\n\t\tthis.searchInput = new Input();\n\t\tthis.buildFlatList();\n\t\tthis.filteredItems = [...this.flatItems];\n\t}\n\n\tprivate buildFlatList(): void {\n\t\tthis.flatItems = [];\n\t\tfor (const group of this.groups) {\n\t\t\tthis.flatItems.push({ type: \"group\", group });\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tthis.flatItems.push({ type: \"subgroup\", subgroup, group });\n\t\t\t\tfor (const item of subgroup.items) {\n\t\t\t\t\tthis.flatItems.push({ type: \"item\", item });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Start selection on first item (not header)\n\t\tthis.selectedIndex = this.flatItems.findIndex((e) => e.type === \"item\");\n\t\tif (this.selectedIndex < 0) this.selectedIndex = 0;\n\t}\n\n\tprivate findNextItem(fromIndex: number, direction: 1 | -1): number {\n\t\tlet idx = fromIndex + direction;\n\t\twhile (idx >= 0 && idx < this.filteredItems.length) {\n\t\t\tif (this.filteredItems[idx].type === \"item\") {\n\t\t\t\treturn idx;\n\t\t\t}\n\t\t\tidx += direction;\n\t\t}\n\t\treturn fromIndex; // Stay at current if no item found\n\t}\n\n\tprivate filterItems(query: string): void {\n\t\tif (!query.trim()) {\n\t\t\tthis.filteredItems = [...this.flatItems];\n\t\t\tthis.selectFirstItem();\n\t\t\treturn;\n\t\t}\n\n\t\tconst lowerQuery = query.toLowerCase();\n\t\tconst matchingItems = new Set<ResourceItem>();\n\t\tconst matchingSubgroups = new Set<ResourceSubgroup>();\n\t\tconst matchingGroups = new Set<ResourceGroup>();\n\n\t\tfor (const entry of this.flatItems) {\n\t\t\tif (entry.type === \"item\") {\n\t\t\t\tconst item = entry.item;\n\t\t\t\tif (\n\t\t\t\t\titem.displayName.toLowerCase().includes(lowerQuery) ||\n\t\t\t\t\titem.resourceType.toLowerCase().includes(lowerQuery) ||\n\t\t\t\t\titem.path.toLowerCase().includes(lowerQuery)\n\t\t\t\t) {\n\t\t\t\t\tmatchingItems.add(item);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Find which subgroups and groups contain matching items\n\t\tfor (const group of this.groups) {\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tfor (const item of subgroup.items) {\n\t\t\t\t\tif (matchingItems.has(item)) {\n\t\t\t\t\t\tmatchingSubgroups.add(subgroup);\n\t\t\t\t\t\tmatchingGroups.add(group);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.filteredItems = [];\n\t\tfor (const entry of this.flatItems) {\n\t\t\tif (entry.type === \"group\" && matchingGroups.has(entry.group)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t} else if (entry.type === \"subgroup\" && matchingSubgroups.has(entry.subgroup)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t} else if (entry.type === \"item\" && matchingItems.has(entry.item)) {\n\t\t\t\tthis.filteredItems.push(entry);\n\t\t\t}\n\t\t}\n\n\t\tthis.selectFirstItem();\n\t}\n\n\tprivate selectFirstItem(): void {\n\t\tconst firstItemIndex = this.filteredItems.findIndex((e) => e.type === \"item\");\n\t\tthis.selectedIndex = firstItemIndex >= 0 ? firstItemIndex : 0;\n\t}\n\n\tupdateItem(item: ResourceItem, enabled: boolean): void {\n\t\titem.enabled = enabled;\n\t\t// Update in groups too\n\t\tfor (const group of this.groups) {\n\t\t\tfor (const subgroup of group.subgroups) {\n\t\t\t\tconst found = subgroup.items.find((i) => i.path === item.path && i.resourceType === item.resourceType);\n\t\t\t\tif (found) {\n\t\t\t\t\tfound.enabled = enabled;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\");\n\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No resources found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst entry = this.filteredItems[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\tif (entry.type === \"group\") {\n\t\t\t\t// Main group header (no cursor)\n\t\t\t\tconst groupLine = theme.fg(\"accent\", theme.bold(entry.group.label));\n\t\t\t\tlines.push(truncateToWidth(` ${groupLine}`, width, \"\"));\n\t\t\t} else if (entry.type === \"subgroup\") {\n\t\t\t\t// Subgroup header (indented, no cursor)\n\t\t\t\tconst subgroupLine = theme.fg(\"muted\", entry.subgroup.label);\n\t\t\t\tlines.push(truncateToWidth(` ${subgroupLine}`, width, \"\"));\n\t\t\t} else {\n\t\t\t\t// Resource item (cursor only on items)\n\t\t\t\tconst item = entry.item;\n\t\t\t\tconst cursor = isSelected ? \"> \" : \" \";\n\t\t\t\tconst checkbox = item.enabled ? theme.fg(\"success\", \"[x]\") : theme.fg(\"dim\", \"[ ]\");\n\t\t\t\tconst name = isSelected ? theme.bold(item.displayName) : item.displayName;\n\t\t\t\tlines.push(truncateToWidth(`${cursor} ${checkbox} ${name}`, width, \"...\"));\n\t\t\t}\n\t\t}\n\n\t\t// Scroll indicator\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tlines.push(theme.fg(\"dim\", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`));\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tthis.selectedIndex = this.findNextItem(this.selectedIndex, -1);\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tthis.selectedIndex = this.findNextItem(this.selectedIndex, 1);\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.pageUp\")) {\n\t\t\t// Jump up by maxVisible, then find nearest item\n\t\t\tlet target = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t\twhile (target < this.filteredItems.length && this.filteredItems[target].type !== \"item\") {\n\t\t\t\ttarget++;\n\t\t\t}\n\t\t\tif (target < this.filteredItems.length) {\n\t\t\t\tthis.selectedIndex = target;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.pageDown\")) {\n\t\t\t// Jump down by maxVisible, then find nearest item\n\t\t\tlet target = Math.min(this.filteredItems.length - 1, this.selectedIndex + this.maxVisible);\n\t\t\twhile (target >= 0 && this.filteredItems[target].type !== \"item\") {\n\t\t\t\ttarget--;\n\t\t\t}\n\t\t\tif (target >= 0) {\n\t\t\t\tthis.selectedIndex = target;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.onCancel?.();\n\t\t\treturn;\n\t\t}\n\t\tif (matchesKey(data, \"ctrl+c\")) {\n\t\t\tthis.onExit?.();\n\t\t\treturn;\n\t\t}\n\t\tif (data === \" \" || kb.matches(data, \"tui.select.confirm\")) {\n\t\t\tconst entry = this.filteredItems[this.selectedIndex];\n\t\t\tif (entry?.type === \"item\") {\n\t\t\t\tconst newEnabled = !entry.item.enabled;\n\t\t\t\tthis.toggleResource(entry.item, newEnabled);\n\t\t\t\tthis.updateItem(entry.item, newEnabled);\n\t\t\t\tthis.onToggle?.(entry.item, newEnabled);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to search input\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.filterItems(this.searchInput.getValue());\n\t}\n\n\tprivate toggleResource(item: ResourceItem, enabled: boolean): void {\n\t\tif (item.metadata.origin === \"top-level\") {\n\t\t\tthis.toggleTopLevelResource(item, enabled);\n\t\t} else {\n\t\t\tthis.togglePackageResource(item, enabled);\n\t\t}\n\t}\n\n\tprivate toggleTopLevelResource(item: ResourceItem, enabled: boolean): void {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst settings =\n\t\t\tscope === \"project\" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();\n\n\t\tconst arrayKey = item.resourceType as \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\t\tconst current = (settings[arrayKey] ?? []) as string[];\n\n\t\t// Generate pattern for this resource\n\t\tconst pattern = this.getResourcePattern(item);\n\t\tconst disablePattern = `-${pattern}`;\n\t\tconst enablePattern = `+${pattern}`;\n\n\t\t// Filter out existing patterns for this resource\n\t\tconst updated = current.filter((p) => {\n\t\t\tconst stripped = p.startsWith(\"!\") || p.startsWith(\"+\") || p.startsWith(\"-\") ? p.slice(1) : p;\n\t\t\treturn stripped !== pattern;\n\t\t});\n\n\t\tif (enabled) {\n\t\t\tupdated.push(enablePattern);\n\t\t} else {\n\t\t\tupdated.push(disablePattern);\n\t\t}\n\n\t\tif (scope === \"project\") {\n\t\t\tif (arrayKey === \"extensions\") {\n\t\t\t\tthis.settingsManager.setProjectExtensionPaths(updated);\n\t\t\t} else if (arrayKey === \"skills\") {\n\t\t\t\tthis.settingsManager.setProjectSkillPaths(updated);\n\t\t\t} else if (arrayKey === \"prompts\") {\n\t\t\t\tthis.settingsManager.setProjectPromptTemplatePaths(updated);\n\t\t\t} else if (arrayKey === \"themes\") {\n\t\t\t\tthis.settingsManager.setProjectThemePaths(updated);\n\t\t\t}\n\t\t} else {\n\t\t\tif (arrayKey === \"extensions\") {\n\t\t\t\tthis.settingsManager.setExtensionPaths(updated);\n\t\t\t} else if (arrayKey === \"skills\") {\n\t\t\t\tthis.settingsManager.setSkillPaths(updated);\n\t\t\t} else if (arrayKey === \"prompts\") {\n\t\t\t\tthis.settingsManager.setPromptTemplatePaths(updated);\n\t\t\t} else if (arrayKey === \"themes\") {\n\t\t\t\tthis.settingsManager.setThemePaths(updated);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate togglePackageResource(item: ResourceItem, enabled: boolean): void {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst settings =\n\t\t\tscope === \"project\" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();\n\n\t\tconst packages = [...(settings.packages ?? [])] as PackageSource[];\n\t\tconst pkgIndex = packages.findIndex((pkg) => {\n\t\t\tconst source = typeof pkg === \"string\" ? pkg : pkg.source;\n\t\t\treturn source === item.metadata.source;\n\t\t});\n\n\t\tif (pkgIndex === -1) return;\n\n\t\tlet pkg = packages[pkgIndex];\n\n\t\t// Convert string to object form if needed\n\t\tif (typeof pkg === \"string\") {\n\t\t\tpkg = { source: pkg };\n\t\t\tpackages[pkgIndex] = pkg;\n\t\t}\n\n\t\t// Get the resource array for this type\n\t\tconst arrayKey = item.resourceType as \"extensions\" | \"skills\" | \"prompts\" | \"themes\";\n\t\tconst current = (pkg[arrayKey] ?? []) as string[];\n\n\t\t// Generate pattern relative to package root\n\t\tconst pattern = this.getPackageResourcePattern(item);\n\t\tconst disablePattern = `-${pattern}`;\n\t\tconst enablePattern = `+${pattern}`;\n\n\t\t// Filter out existing patterns for this resource\n\t\tconst updated = current.filter((p) => {\n\t\t\tconst stripped = p.startsWith(\"!\") || p.startsWith(\"+\") || p.startsWith(\"-\") ? p.slice(1) : p;\n\t\t\treturn stripped !== pattern;\n\t\t});\n\n\t\tif (enabled) {\n\t\t\tupdated.push(enablePattern);\n\t\t} else {\n\t\t\tupdated.push(disablePattern);\n\t\t}\n\n\t\t(pkg as Record<string, unknown>)[arrayKey] = updated.length > 0 ? updated : undefined;\n\n\t\t// Clean up empty filter object\n\t\tconst hasFilters = [\"extensions\", \"skills\", \"prompts\", \"themes\"].some(\n\t\t\t(k) => (pkg as Record<string, unknown>)[k] !== undefined,\n\t\t);\n\t\tif (!hasFilters) {\n\t\t\tpackages[pkgIndex] = (pkg as { source: string }).source;\n\t\t}\n\n\t\tif (scope === \"project\") {\n\t\t\tthis.settingsManager.setProjectPackages(packages);\n\t\t} else {\n\t\t\tthis.settingsManager.setPackages(packages);\n\t\t}\n\t}\n\n\tprivate getTopLevelBaseDir(scope: \"user\" | \"project\"): string {\n\t\treturn scope === \"project\" ? join(this.cwd, CONFIG_DIR_NAME) : this.agentDir;\n\t}\n\n\tprivate getResourcePattern(item: ResourceItem): string {\n\t\tconst scope = item.metadata.scope as \"user\" | \"project\";\n\t\tconst baseDir = this.getTopLevelBaseDir(scope);\n\t\treturn relative(baseDir, item.path);\n\t}\n\n\tprivate getPackageResourcePattern(item: ResourceItem): string {\n\t\tconst baseDir = item.metadata.baseDir ?? dirname(item.path);\n\t\treturn relative(baseDir, item.path);\n\t}\n}\n\nexport class ConfigSelectorComponent extends Container implements Focusable {\n\tprivate resourceList: ResourceList;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.resourceList.focused = value;\n\t}\n\n\tconstructor(\n\t\tresolvedPaths: ResolvedPaths,\n\t\tsettingsManager: SettingsManager,\n\t\tcwd: string,\n\t\tagentDir: string,\n\t\tonClose: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\n\t\tconst groups = buildGroups(resolvedPaths);\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new ConfigSelectorHeader());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Resource list\n\t\tthis.resourceList = new ResourceList(groups, settingsManager, cwd, agentDir);\n\t\tthis.resourceList.onCancel = onClose;\n\t\tthis.resourceList.onExit = onExit;\n\t\tthis.resourceList.onToggle = () => requestRender();\n\t\tthis.addChild(this.resourceList);\n\n\t\t// Bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetResourceList(): ResourceList {\n\t\treturn this.resourceList;\n\t}\n}\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* TUI component for managing package resources (enable/disable)
|
|
3
3
|
*/
|
|
4
4
|
import { basename, dirname, join, relative } from "node:path";
|
|
5
|
-
import { Container,
|
|
5
|
+
import { Container, getKeybindings, Input, matchesKey, Spacer, truncateToWidth, visibleWidth, } from "@mariozechner/pi-tui";
|
|
6
6
|
import { CONFIG_DIR_NAME } from "../../../config.js";
|
|
7
7
|
import { theme } from "../theme/theme.js";
|
|
8
8
|
import { DynamicBorder } from "./dynamic-border.js";
|
|
@@ -114,19 +114,6 @@ class ConfigSelectorHeader {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
class ResourceList {
|
|
117
|
-
groups;
|
|
118
|
-
flatItems = [];
|
|
119
|
-
filteredItems = [];
|
|
120
|
-
selectedIndex = 0;
|
|
121
|
-
searchInput;
|
|
122
|
-
maxVisible = 15;
|
|
123
|
-
settingsManager;
|
|
124
|
-
cwd;
|
|
125
|
-
agentDir;
|
|
126
|
-
onCancel;
|
|
127
|
-
onExit;
|
|
128
|
-
onToggle;
|
|
129
|
-
_focused = false;
|
|
130
117
|
get focused() {
|
|
131
118
|
return this._focused;
|
|
132
119
|
}
|
|
@@ -135,6 +122,11 @@ class ResourceList {
|
|
|
135
122
|
this.searchInput.focused = value;
|
|
136
123
|
}
|
|
137
124
|
constructor(groups, settingsManager, cwd, agentDir) {
|
|
125
|
+
this.flatItems = [];
|
|
126
|
+
this.filteredItems = [];
|
|
127
|
+
this.selectedIndex = 0;
|
|
128
|
+
this.maxVisible = 15;
|
|
129
|
+
this._focused = false;
|
|
138
130
|
this.groups = groups;
|
|
139
131
|
this.settingsManager = settingsManager;
|
|
140
132
|
this.cwd = cwd;
|
|
@@ -273,16 +265,16 @@ class ResourceList {
|
|
|
273
265
|
return lines;
|
|
274
266
|
}
|
|
275
267
|
handleInput(data) {
|
|
276
|
-
const kb =
|
|
277
|
-
if (kb.matches(data, "
|
|
268
|
+
const kb = getKeybindings();
|
|
269
|
+
if (kb.matches(data, "tui.select.up")) {
|
|
278
270
|
this.selectedIndex = this.findNextItem(this.selectedIndex, -1);
|
|
279
271
|
return;
|
|
280
272
|
}
|
|
281
|
-
if (kb.matches(data, "
|
|
273
|
+
if (kb.matches(data, "tui.select.down")) {
|
|
282
274
|
this.selectedIndex = this.findNextItem(this.selectedIndex, 1);
|
|
283
275
|
return;
|
|
284
276
|
}
|
|
285
|
-
if (kb.matches(data, "
|
|
277
|
+
if (kb.matches(data, "tui.select.pageUp")) {
|
|
286
278
|
// Jump up by maxVisible, then find nearest item
|
|
287
279
|
let target = Math.max(0, this.selectedIndex - this.maxVisible);
|
|
288
280
|
while (target < this.filteredItems.length && this.filteredItems[target].type !== "item") {
|
|
@@ -293,7 +285,7 @@ class ResourceList {
|
|
|
293
285
|
}
|
|
294
286
|
return;
|
|
295
287
|
}
|
|
296
|
-
if (kb.matches(data, "
|
|
288
|
+
if (kb.matches(data, "tui.select.pageDown")) {
|
|
297
289
|
// Jump down by maxVisible, then find nearest item
|
|
298
290
|
let target = Math.min(this.filteredItems.length - 1, this.selectedIndex + this.maxVisible);
|
|
299
291
|
while (target >= 0 && this.filteredItems[target].type !== "item") {
|
|
@@ -304,7 +296,7 @@ class ResourceList {
|
|
|
304
296
|
}
|
|
305
297
|
return;
|
|
306
298
|
}
|
|
307
|
-
if (kb.matches(data, "
|
|
299
|
+
if (kb.matches(data, "tui.select.cancel")) {
|
|
308
300
|
this.onCancel?.();
|
|
309
301
|
return;
|
|
310
302
|
}
|
|
@@ -312,7 +304,7 @@ class ResourceList {
|
|
|
312
304
|
this.onExit?.();
|
|
313
305
|
return;
|
|
314
306
|
}
|
|
315
|
-
if (data === " " || kb.matches(data, "
|
|
307
|
+
if (data === " " || kb.matches(data, "tui.select.confirm")) {
|
|
316
308
|
const entry = this.filteredItems[this.selectedIndex];
|
|
317
309
|
if (entry?.type === "item") {
|
|
318
310
|
const newEnabled = !entry.item.enabled;
|
|
@@ -444,8 +436,6 @@ class ResourceList {
|
|
|
444
436
|
}
|
|
445
437
|
}
|
|
446
438
|
export class ConfigSelectorComponent extends Container {
|
|
447
|
-
resourceList;
|
|
448
|
-
_focused = false;
|
|
449
439
|
get focused() {
|
|
450
440
|
return this._focused;
|
|
451
441
|
}
|
|
@@ -455,6 +445,7 @@ export class ConfigSelectorComponent extends Container {
|
|
|
455
445
|
}
|
|
456
446
|
constructor(resolvedPaths, settingsManager, cwd, agentDir, onClose, onExit, requestRender) {
|
|
457
447
|
super();
|
|
448
|
+
this._focused = false;
|
|
458
449
|
const groups = buildGroups(resolvedPaths);
|
|
459
450
|
// Add header
|
|
460
451
|
this.addChild(new Spacer(1));
|