@caupulican/pi-adaptative 0.81.31 → 0.81.35
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 +28 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +69 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +53 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +1282 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +1453 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/builder-validator.json +14 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/claude-trio.json +32 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/full-provider-review.json +15 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/provider-prompt-smoke.json +14 -0
- package/dist/bundled-resources/prompts/extensionify.md +5 -4
- package/dist/bundled-resources/prompts/skillify.md +5 -5
- package/dist/bundled-resources/skills/pi-harness-learning/SKILL.md +6 -3
- package/dist/config.d.ts +5 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -2
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +7 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +25 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +12 -3
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +67 -26
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +7 -3
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/context/context-store-retention.d.ts +4 -1
- package/dist/core/context/context-store-retention.d.ts.map +1 -1
- package/dist/core/context/context-store-retention.js +42 -151
- package/dist/core/context/context-store-retention.js.map +1 -1
- package/dist/core/context-pipeline.d.ts.map +1 -1
- package/dist/core/context-pipeline.js +6 -6
- package/dist/core/context-pipeline.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +25 -39
- package/dist/core/exec.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +9 -2
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +105 -21
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +2 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/goals/goal-state.d.ts +10 -0
- package/dist/core/goals/goal-state.d.ts.map +1 -1
- package/dist/core/goals/goal-state.js +37 -0
- package/dist/core/goals/goal-state.js.map +1 -1
- package/dist/core/goals/goal-tool-core.d.ts +11 -0
- package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
- package/dist/core/goals/goal-tool-core.js +51 -1
- package/dist/core/goals/goal-tool-core.js.map +1 -1
- package/dist/core/improvement-loop.d.ts.map +1 -1
- package/dist/core/improvement-loop.js +26 -5
- package/dist/core/improvement-loop.js.map +1 -1
- package/dist/core/models/fitness-store.d.ts.map +1 -1
- package/dist/core/models/fitness-store.js +1 -1
- package/dist/core/models/fitness-store.js.map +1 -1
- package/dist/core/models/local-runtime.d.ts +6 -4
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +108 -36
- package/dist/core/models/local-runtime.js.map +1 -1
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +23 -8
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +44 -59
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/profile-resource-selection.d.ts +16 -0
- package/dist/core/profile-resource-selection.d.ts.map +1 -1
- package/dist/core/profile-resource-selection.js +41 -0
- package/dist/core/profile-resource-selection.js.map +1 -1
- package/dist/core/reflection-controller.d.ts.map +1 -1
- package/dist/core/reflection-controller.js +18 -1
- package/dist/core/reflection-controller.js.map +1 -1
- package/dist/core/reload-blockers.d.ts +1 -0
- package/dist/core/reload-blockers.d.ts.map +1 -1
- package/dist/core/reload-blockers.js +6 -2
- package/dist/core/reload-blockers.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +50 -28
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/toolkit/script-runner.d.ts.map +1 -1
- package/dist/core/toolkit/script-runner.js +58 -24
- package/dist/core/toolkit/script-runner.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +13 -26
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/extensionify.d.ts +2 -1
- package/dist/core/tools/extensionify.d.ts.map +1 -1
- package/dist/core/tools/extensionify.js +11 -8
- package/dist/core/tools/extensionify.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +54 -48
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/git-filter.d.ts.map +1 -1
- package/dist/core/tools/git-filter.js +20 -32
- package/dist/core/tools/git-filter.js.map +1 -1
- package/dist/core/tools/goal.d.ts +1 -1
- package/dist/core/tools/goal.d.ts.map +1 -1
- package/dist/core/tools/goal.js +10 -3
- package/dist/core/tools/goal.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +89 -80
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +2 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +7 -4
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -3
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +23 -4
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +18 -4
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.d.ts +10 -0
- package/dist/modes/interactive/auth-dialogs-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.js +94 -27
- package/dist/modes/interactive/auth-dialogs-controller.js.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.js +2 -1
- package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +1 -0
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +8 -0
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +6 -15
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +3 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +20 -8
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts +4 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.js +22 -39
- package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/config-backup.d.ts +1 -0
- package/dist/modes/interactive/config-backup.d.ts.map +1 -1
- package/dist/modes/interactive/config-backup.js +14 -3
- package/dist/modes/interactive/config-backup.js.map +1 -1
- package/dist/modes/interactive/editor-overlay-host.d.ts +12 -0
- package/dist/modes/interactive/editor-overlay-host.d.ts.map +1 -1
- package/dist/modes/interactive/editor-overlay-host.js +55 -0
- package/dist/modes/interactive/editor-overlay-host.js.map +1 -1
- package/dist/modes/interactive/extension-ui-host.d.ts +2 -1
- package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
- package/dist/modes/interactive/extension-ui-host.js +161 -96
- package/dist/modes/interactive/extension-ui-host.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +23 -28
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +14 -8
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.d.ts +5 -1
- package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.js +118 -24
- package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
- package/dist/modes/interactive/resource-shell-commands.d.ts +1 -0
- package/dist/modes/interactive/resource-shell-commands.d.ts.map +1 -1
- package/dist/modes/interactive/resource-shell-commands.js +5 -1
- package/dist/modes/interactive/resource-shell-commands.js.map +1 -1
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-flow-commands.js +100 -7
- package/dist/modes/interactive/session-flow-commands.js.map +1 -1
- package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-io-commands.js +80 -50
- package/dist/modes/interactive/session-io-commands.js.map +1 -1
- package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
- package/dist/modes/interactive/startup-checks.js +14 -17
- package/dist/modes/interactive/startup-checks.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +20 -19
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +10 -19
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/child-process.d.ts +13 -7
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +56 -3
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +3 -2
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +17 -10
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/external-editor-command.d.ts +8 -0
- package/dist/utils/external-editor-command.d.ts.map +1 -0
- package/dist/utils/external-editor-command.js +60 -0
- package/dist/utils/external-editor-command.js.map +1 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +10 -2
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +23 -10
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/work-directory.d.ts +36 -0
- package/dist/utils/work-directory.d.ts.map +1 -0
- package/dist/utils/work-directory.js +462 -0
- package/dist/utils/work-directory.js.map +1 -0
- package/docs/index.md +2 -0
- package/docs/resources.md +2 -2
- package/docs/rpc.md +1 -1
- package/docs/tmux-agent-manager.md +48 -0
- package/docs/windows.md +100 -7
- package/docs/work-directory.md +41 -0
- 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/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +42 -42
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
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 \"@caupulican/pi-tui\";\nimport type { Theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint } from \"./keybinding-hints.ts\";\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} else if (\"stop\" in this.loader && typeof this.loader.stop === \"function\") {\n\t\t\tthis.loader.stop();\n\t\t}\n\t}\n}\n"]}
|
|
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,oBAAoB,CAAC;AAElG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,mDAAmD;AACnD,MAAM,OAAO,cAAe,SAAQ,SAAS;IACpC,MAAM,CAA6B;IACnC,WAAW,CAAU;IACrB,gBAAgB,CAAmB;IAE3C,YAAY,GAAQ,EAAE,KAAY,EAAE,OAAe,EAAE,OAAmC,EAAE;QACzF,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,IAAI,CAAC;QAChD,MAAM,WAAW,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;QAC9C,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,MAAM,GAAG,IAAI,iBAAiB,CAClC,GAAG,EACH,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAC5B,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAC3B,OAAO,CACP,CAAC;QACH,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,gBAAgB,GAAG,IAAI,eAAe,EAAE,CAAC;YAC9C,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CACvB,GAAG,EACH,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAC5B,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAC3B,OAAO,CACP,CAAC;QACH,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;IAAA,CAC9C;IAED,IAAI,MAAM,GAAgB;QACzB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,OAAQ,IAAI,CAAC,MAA4B,CAAC,MAAM,CAAC;QAClD,CAAC;QACD,OAAO,IAAI,CAAC,gBAAgB,EAAE,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC,MAAM,CAAC;IAAA,CACrE;IAED,IAAI,OAAO,CAAC,EAA4B,EAAE;QACzC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,IAAI,CAAC,MAA4B,CAAC,OAAO,GAAG,EAAE,CAAC;QACjD,CAAC;IAAA,CACD;IAED,WAAW,CAAC,IAAY,EAAQ;QAC/B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,IAAI,CAAC,MAA4B,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACtD,CAAC;IAAA,CACD;IAED,MAAM,GAAS;QACd,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,IAAI,CAAC,MAA4B,CAAC,KAAK,EAAE,CAAC;QAC5C,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,gBAAgB,EAAE,KAAK,EAAE,CAAC;QAChC,CAAC;IAAA,CACD;IAED,OAAO,GAAS;QACf,IAAI,SAAS,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YAC3E,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;aAAM,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC5E,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,CAAC;IAAA,CACD;CACD","sourcesContent":["import { CancellableLoader, Container, Loader, Spacer, Text, type TUI } from \"@caupulican/pi-tui\";\nimport type { Theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint } from \"./keybinding-hints.ts\";\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\tcancel(): void {\n\t\tif (this.cancellable) {\n\t\t\t(this.loader as CancellableLoader).abort();\n\t\t} else {\n\t\t\tthis.signalController?.abort();\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} else if (\"stop\" in this.loader && typeof this.loader.stop === \"function\") {\n\t\t\tthis.loader.stop();\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-editor.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"extension-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-editor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EACN,SAAS,EAET,KAAK,aAAa,EAClB,KAAK,SAAS,EAId,KAAK,GAAG,EACR,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAOvE,qBAAa,wBAAyB,SAAQ,SAAU,YAAW,SAAS;IAC3E,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,WAAW,CAAqB;IAExC,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YACC,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,kBAAkB,EAC/B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,QAAQ,EAAE,MAAM,IAAI,EACpB,OAAO,CAAC,EAAE,aAAa,EA6CvB;IAED,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAgBjC;YAEa,kBAAkB;CAkChC","sourcesContent":["/**\n * Multi-line editor component for extensions.\n * Supports Ctrl+G for external editor.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n\tContainer,\n\tEditor,\n\ttype EditorOptions,\n\ttype Focusable,\n\tgetKeybindings,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../../config.ts\";\nimport type { KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { runExternalEditor } from \"../../../utils/external-editor-command.ts\";\nimport { getProcessWorkRun } from \"../../../utils/work-directory.ts\";\nimport { getEditorTheme, theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint } from \"./keybinding-hints.ts\";\n\nexport class ExtensionEditorComponent extends Container implements Focusable {\n\tprivate editor: Editor;\n\tprivate onSubmitCallback: (value: string) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate tui: TUI;\n\tprivate keybindings: KeybindingsManager;\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.editor.focused = value;\n\t}\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tkeybindings: KeybindingsManager,\n\t\ttitle: string,\n\t\tprefill: string | undefined,\n\t\tonSubmit: (value: string) => void,\n\t\tonCancel: () => void,\n\t\toptions?: EditorOptions,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.keybindings = keybindings;\n\t\tthis.onSubmitCallback = onSubmit;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add title\n\t\tthis.addChild(new Text(theme.fg(\"accent\", title), 1, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create editor\n\t\tthis.editor = new Editor(tui, getEditorTheme(), options);\n\t\tif (prefill) {\n\t\t\tthis.editor.setText(prefill);\n\t\t}\n\t\t// Wire up Enter to submit (Shift+Enter for newlines, like the main editor)\n\t\tthis.editor.onSubmit = (text: string) => {\n\t\t\tthis.onSubmitCallback(text);\n\t\t};\n\t\tthis.addChild(this.editor);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add hint\n\t\tconst hasExternalEditor = !!(process.env.VISUAL || process.env.EDITOR);\n\t\tconst hint =\n\t\t\tkeyHint(\"tui.select.confirm\", \"submit\") +\n\t\t\t\" \" +\n\t\t\tkeyHint(\"tui.input.newLine\", \"newline\") +\n\t\t\t\" \" +\n\t\t\tkeyHint(\"tui.select.cancel\", \"cancel\") +\n\t\t\t(hasExternalEditor ? ` ${keyHint(\"app.editor.external\", \"external editor\")}` : \"\");\n\t\tthis.addChild(new Text(hint, 1, 0));\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\t// Escape or Ctrl+C to cancel\n\t\tif (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t\treturn;\n\t\t}\n\n\t\t// External editor (app keybinding)\n\t\tif (this.keybindings.matches(keyData, \"app.editor.external\")) {\n\t\t\tthis.openExternalEditor();\n\t\t\treturn;\n\t\t}\n\n\t\t// Forward to editor\n\t\tthis.editor.handleInput(keyData);\n\t}\n\n\tprivate async openExternalEditor(): Promise<void> {\n\t\tconst editorCmd = process.env.VISUAL || process.env.EDITOR;\n\t\tif (!editorCmd) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentText = this.editor.getText();\n\t\tconst tmpFile = path.join(\n\t\t\tgetProcessWorkRun(getAgentDir(), \"editors\", \"extensions\").path,\n\t\t\t`pi-extension-editor-${randomUUID()}.md`,\n\t\t);\n\n\t\ttry {\n\t\t\tfs.writeFileSync(tmpFile, currentText, \"utf-8\");\n\t\t\tthis.tui.stop();\n\n\t\t\tprocess.stdout.write(`Launching external editor: ${editorCmd}\\nPi will resume when the editor exits.\\n`);\n\t\t\tconst status = await runExternalEditor(editorCmd, tmpFile);\n\n\t\t\tif (status === 0) {\n\t\t\t\tconst newContent = fs.readFileSync(tmpFile, \"utf-8\").replace(/\\n$/, \"\");\n\t\t\t\tthis.editor.setText(newContent);\n\t\t\t}\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tfs.unlinkSync(tmpFile);\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t\tthis.tui.start();\n\t\t\t// Force full re-render since external editor uses alternate screen\n\t\t\tthis.tui.requestRender(true);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* Multi-line editor component for extensions.
|
|
3
3
|
* Supports Ctrl+G for external editor.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
|
-
import * as os from "node:os";
|
|
8
7
|
import * as path from "node:path";
|
|
9
8
|
import { Container, Editor, getKeybindings, Spacer, Text, } from "@caupulican/pi-tui";
|
|
9
|
+
import { getAgentDir } from "../../../config.js";
|
|
10
|
+
import { runExternalEditor } from "../../../utils/external-editor-command.js";
|
|
11
|
+
import { getProcessWorkRun } from "../../../utils/work-directory.js";
|
|
10
12
|
import { getEditorTheme, theme } from "../theme/theme.js";
|
|
11
13
|
import { DynamicBorder } from "./dynamic-border.js";
|
|
12
14
|
import { keyHint } from "./keybinding-hints.js";
|
|
@@ -81,23 +83,12 @@ export class ExtensionEditorComponent extends Container {
|
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
83
85
|
const currentText = this.editor.getText();
|
|
84
|
-
const tmpFile = path.join(
|
|
86
|
+
const tmpFile = path.join(getProcessWorkRun(getAgentDir(), "editors", "extensions").path, `pi-extension-editor-${randomUUID()}.md`);
|
|
85
87
|
try {
|
|
86
88
|
fs.writeFileSync(tmpFile, currentText, "utf-8");
|
|
87
89
|
this.tui.stop();
|
|
88
|
-
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
89
90
|
process.stdout.write(`Launching external editor: ${editorCmd}\nPi will resume when the editor exits.\n`);
|
|
90
|
-
|
|
91
|
-
// Node/libuv's console input read active after tui.stop() pauses stdin, racing
|
|
92
|
-
// vim/nvim for the console input buffer until Ctrl+C cancels the pending read.
|
|
93
|
-
const status = await new Promise((resolve) => {
|
|
94
|
-
const child = spawn(editor, [...editorArgs, tmpFile], {
|
|
95
|
-
stdio: "inherit",
|
|
96
|
-
shell: process.platform === "win32",
|
|
97
|
-
});
|
|
98
|
-
child.on("error", () => resolve(null));
|
|
99
|
-
child.on("close", (code) => resolve(code));
|
|
100
|
-
});
|
|
91
|
+
const status = await runExternalEditor(editorCmd, tmpFile);
|
|
101
92
|
if (status === 0) {
|
|
102
93
|
const newContent = fs.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
|
|
103
94
|
this.editor.setText(newContent);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-editor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"extension-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-editor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Multi-line editor component for extensions.\n * Supports Ctrl+G for external editor.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n\tContainer,\n\tEditor,\n\ttype EditorOptions,\n\ttype Focusable,\n\tgetKeybindings,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@caupulican/pi-tui\";\nimport { getAgentDir } from \"../../../config.ts\";\nimport type { KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { runExternalEditor } from \"../../../utils/external-editor-command.ts\";\nimport { getProcessWorkRun } from \"../../../utils/work-directory.ts\";\nimport { getEditorTheme, theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint } from \"./keybinding-hints.ts\";\n\nexport class ExtensionEditorComponent extends Container implements Focusable {\n\tprivate editor: Editor;\n\tprivate onSubmitCallback: (value: string) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate tui: TUI;\n\tprivate keybindings: KeybindingsManager;\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.editor.focused = value;\n\t}\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tkeybindings: KeybindingsManager,\n\t\ttitle: string,\n\t\tprefill: string | undefined,\n\t\tonSubmit: (value: string) => void,\n\t\tonCancel: () => void,\n\t\toptions?: EditorOptions,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.keybindings = keybindings;\n\t\tthis.onSubmitCallback = onSubmit;\n\t\tthis.onCancelCallback = onCancel;\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add title\n\t\tthis.addChild(new Text(theme.fg(\"accent\", title), 1, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create editor\n\t\tthis.editor = new Editor(tui, getEditorTheme(), options);\n\t\tif (prefill) {\n\t\t\tthis.editor.setText(prefill);\n\t\t}\n\t\t// Wire up Enter to submit (Shift+Enter for newlines, like the main editor)\n\t\tthis.editor.onSubmit = (text: string) => {\n\t\t\tthis.onSubmitCallback(text);\n\t\t};\n\t\tthis.addChild(this.editor);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add hint\n\t\tconst hasExternalEditor = !!(process.env.VISUAL || process.env.EDITOR);\n\t\tconst hint =\n\t\t\tkeyHint(\"tui.select.confirm\", \"submit\") +\n\t\t\t\" \" +\n\t\t\tkeyHint(\"tui.input.newLine\", \"newline\") +\n\t\t\t\" \" +\n\t\t\tkeyHint(\"tui.select.cancel\", \"cancel\") +\n\t\t\t(hasExternalEditor ? ` ${keyHint(\"app.editor.external\", \"external editor\")}` : \"\");\n\t\tthis.addChild(new Text(hint, 1, 0));\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\t// Escape or Ctrl+C to cancel\n\t\tif (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t\treturn;\n\t\t}\n\n\t\t// External editor (app keybinding)\n\t\tif (this.keybindings.matches(keyData, \"app.editor.external\")) {\n\t\t\tthis.openExternalEditor();\n\t\t\treturn;\n\t\t}\n\n\t\t// Forward to editor\n\t\tthis.editor.handleInput(keyData);\n\t}\n\n\tprivate async openExternalEditor(): Promise<void> {\n\t\tconst editorCmd = process.env.VISUAL || process.env.EDITOR;\n\t\tif (!editorCmd) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentText = this.editor.getText();\n\t\tconst tmpFile = path.join(\n\t\t\tgetProcessWorkRun(getAgentDir(), \"editors\", \"extensions\").path,\n\t\t\t`pi-extension-editor-${randomUUID()}.md`,\n\t\t);\n\n\t\ttry {\n\t\t\tfs.writeFileSync(tmpFile, currentText, \"utf-8\");\n\t\t\tthis.tui.stop();\n\n\t\t\tprocess.stdout.write(`Launching external editor: ${editorCmd}\\nPi will resume when the editor exits.\\n`);\n\t\t\tconst status = await runExternalEditor(editorCmd, tmpFile);\n\n\t\t\tif (status === 0) {\n\t\t\t\tconst newContent = fs.readFileSync(tmpFile, \"utf-8\").replace(/\\n$/, \"\");\n\t\t\t\tthis.editor.setText(newContent);\n\t\t\t}\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tfs.unlinkSync(tmpFile);\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t\tthis.tui.start();\n\t\t\t// Force full re-render since external editor uses alternate screen\n\t\t\tthis.tui.requestRender(true);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -11,12 +11,14 @@ export declare class LoginDialogComponent extends Container implements Focusable
|
|
|
11
11
|
private inputResolver?;
|
|
12
12
|
private inputRejecter?;
|
|
13
13
|
private onComplete;
|
|
14
|
+
private completed;
|
|
14
15
|
private _focused;
|
|
15
16
|
get focused(): boolean;
|
|
16
17
|
set focused(value: boolean);
|
|
17
18
|
constructor(tui: TUI, providerId: string, onComplete: (success: boolean, message?: string) => void, providerNameOverride?: string, titleOverride?: string);
|
|
18
19
|
get signal(): AbortSignal;
|
|
19
|
-
private
|
|
20
|
+
private waitForInput;
|
|
21
|
+
cancel(): void;
|
|
20
22
|
/**
|
|
21
23
|
* Called by onAuth callback - show URL and optional instructions
|
|
22
24
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-dialog.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/login-dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAuC,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAM9G;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAU,YAAW,SAAS;IACvE,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,aAAa,CAAC,CAA0B;IAChD,OAAO,CAAC,aAAa,CAAC,CAAyB;IAC/C,OAAO,CAAC,UAAU,CAA+C;
|
|
1
|
+
{"version":3,"file":"login-dialog.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/login-dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAuC,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAM9G;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAU,YAAW,SAAS;IACvE,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,aAAa,CAAC,CAA0B;IAChD,OAAO,CAAC,aAAa,CAAC,CAAyB;IAC/C,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,SAAS,CAAS;IAG1B,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YACC,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,EACxD,oBAAoB,CAAC,EAAE,MAAM,EAC7B,aAAa,CAAC,EAAE,MAAM,EAmCtB;IAED,IAAI,MAAM,IAAI,WAAW,CAExB;IAED,OAAO,CAAC,YAAY;IAcpB,MAAM,IAAI,IAAI,CAUb;IAED;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAiBjD;IAED;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAc9C;IAED;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ/C;IAED;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBjE;IAED;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAS9B;IAED;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAKjC;IAED;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGlC;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAU9B;CACD","sourcesContent":["import { getOAuthProviders, type OAuthDeviceCodeInfo } from \"@caupulican/pi-ai/oauth\";\nimport { Container, type Focusable, getKeybindings, Input, Spacer, Text, type TUI } from \"@caupulican/pi-tui\";\nimport { openBrowser } from \"../../../utils/open-browser.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint } from \"./keybinding-hints.ts\";\n\n/**\n * Login dialog component - replaces editor during OAuth login flow\n */\nexport class LoginDialogComponent extends Container implements Focusable {\n\tprivate contentContainer: Container;\n\tprivate input: Input;\n\tprivate tui: TUI;\n\tprivate abortController = new AbortController();\n\tprivate inputResolver?: (value: string) => void;\n\tprivate inputRejecter?: (error: Error) => void;\n\tprivate onComplete: (success: boolean, message?: string) => void;\n\tprivate completed = false;\n\n\t// Focusable implementation - propagate to input for IME cursor positioning\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.input.focused = value;\n\t}\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tproviderId: string,\n\t\tonComplete: (success: boolean, message?: string) => void,\n\t\tproviderNameOverride?: string,\n\t\ttitleOverride?: string,\n\t) {\n\t\tsuper();\n\t\tthis.tui = tui;\n\t\tthis.onComplete = onComplete;\n\n\t\tconst providerInfo = getOAuthProviders().find((p) => p.id === providerId);\n\t\tconst providerName = providerNameOverride || providerInfo?.name || providerId;\n\t\tconst title = titleOverride ?? `Login to ${providerName}`;\n\n\t\t// Top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0));\n\n\t\t// Dynamic content area\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\t// Input (always present, used when needed)\n\t\tthis.input = new Input();\n\t\tthis.input.onSubmit = () => {\n\t\t\tif (this.inputResolver) {\n\t\t\t\tthis.inputResolver(this.input.getValue());\n\t\t\t\tthis.inputResolver = undefined;\n\t\t\t\tthis.inputRejecter = undefined;\n\t\t\t}\n\t\t};\n\t\tthis.input.onEscape = () => {\n\t\t\tthis.cancel();\n\t\t};\n\n\t\t// Bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tget signal(): AbortSignal {\n\t\treturn this.abortController.signal;\n\t}\n\n\tprivate waitForInput(): Promise<string> {\n\t\tif (this.completed) return Promise.reject(new Error(\"Login cancelled\"));\n\t\tif (this.inputRejecter) {\n\t\t\tconst rejectPrevious = this.inputRejecter;\n\t\t\tthis.inputResolver = undefined;\n\t\t\tthis.inputRejecter = undefined;\n\t\t\trejectPrevious(new Error(\"Login input superseded\"));\n\t\t}\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.inputResolver = resolve;\n\t\t\tthis.inputRejecter = reject;\n\t\t});\n\t}\n\n\tcancel(): void {\n\t\tif (this.completed) return;\n\t\tthis.completed = true;\n\t\tthis.abortController.abort();\n\t\tif (this.inputRejecter) {\n\t\t\tthis.inputRejecter(new Error(\"Login cancelled\"));\n\t\t\tthis.inputResolver = undefined;\n\t\t\tthis.inputRejecter = undefined;\n\t\t}\n\t\tthis.onComplete(false, \"Login cancelled\");\n\t}\n\n\t/**\n\t * Called by onAuth callback - show URL and optional instructions\n\t */\n\tshowAuth(url: string, instructions?: string): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tconst linkedUrl = `\\x1b]8;;${url}\\x07${url}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"accent\", linkedUrl), 1, 0));\n\n\t\tconst clickHint = process.platform === \"darwin\" ? \"Cmd+click to open\" : \"Ctrl+click to open\";\n\t\tconst hyperlink = `\\x1b]8;;${url}\\x07${clickHint}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", hyperlink), 1, 0));\n\n\t\tif (instructions) {\n\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"warning\", instructions), 1, 0));\n\t\t}\n\n\t\topenBrowser(url);\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Called by onDeviceCode callback - show URL and user code.\n\t */\n\tshowDeviceCode(info: OAuthDeviceCodeInfo): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tconst linkedUrl = `\\x1b]8;;${info.verificationUri}\\x07${info.verificationUri}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"accent\", linkedUrl), 1, 0));\n\n\t\tconst clickHint = process.platform === \"darwin\" ? \"Cmd+click to open\" : \"Ctrl+click to open\";\n\t\tconst hyperlink = `\\x1b]8;;${info.verificationUri}\\x07${clickHint}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", hyperlink), 1, 0));\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"warning\", `Enter code: ${info.userCode}`), 1, 0));\n\n\t\topenBrowser(info.verificationUri);\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show input for manual code/URL entry (for callback server providers)\n\t */\n\tshowManualInput(prompt: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", prompt), 1, 0));\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\n\t\treturn this.waitForInput();\n\t}\n\n\t/**\n\t * Called by onPrompt callback - show prompt and wait for input\n\t * Note: Does NOT clear content, appends to existing (preserves URL from showAuth)\n\t */\n\tshowPrompt(message: string, placeholder?: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"text\", message), 1, 0));\n\t\tif (placeholder) {\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", `e.g., ${placeholder}`), 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(\n\t\t\tnew Text(\n\t\t\t\t`(${keyHint(\"tui.select.cancel\", \"to cancel,\")} ${keyHint(\"tui.select.confirm\", \"to submit\")})`,\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\n\t\tthis.input.setValue(\"\");\n\t\tthis.tui.requestRender();\n\n\t\treturn this.waitForInput();\n\t}\n\n\t/**\n\t * Show informational text without prompting for input.\n\t */\n\tshowInfo(lines: string[]): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tfor (const line of lines) {\n\t\t\tthis.contentContainer.addChild(new Text(line, 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to close\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show waiting message (for polling flows like GitHub Copilot)\n\t */\n\tshowWaiting(message: string): void {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Called by onProgress callback\n\t */\n\tshowProgress(message: string): void {\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.cancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to input\n\t\tthis.input.handleInput(data);\n\t}\n}\n"]}
|
|
@@ -15,6 +15,7 @@ export class LoginDialogComponent extends Container {
|
|
|
15
15
|
inputResolver;
|
|
16
16
|
inputRejecter;
|
|
17
17
|
onComplete;
|
|
18
|
+
completed = false;
|
|
18
19
|
// Focusable implementation - propagate to input for IME cursor positioning
|
|
19
20
|
_focused = false;
|
|
20
21
|
get focused() {
|
|
@@ -56,7 +57,24 @@ export class LoginDialogComponent extends Container {
|
|
|
56
57
|
get signal() {
|
|
57
58
|
return this.abortController.signal;
|
|
58
59
|
}
|
|
60
|
+
waitForInput() {
|
|
61
|
+
if (this.completed)
|
|
62
|
+
return Promise.reject(new Error("Login cancelled"));
|
|
63
|
+
if (this.inputRejecter) {
|
|
64
|
+
const rejectPrevious = this.inputRejecter;
|
|
65
|
+
this.inputResolver = undefined;
|
|
66
|
+
this.inputRejecter = undefined;
|
|
67
|
+
rejectPrevious(new Error("Login input superseded"));
|
|
68
|
+
}
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
this.inputResolver = resolve;
|
|
71
|
+
this.inputRejecter = reject;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
59
74
|
cancel() {
|
|
75
|
+
if (this.completed)
|
|
76
|
+
return;
|
|
77
|
+
this.completed = true;
|
|
60
78
|
this.abortController.abort();
|
|
61
79
|
if (this.inputRejecter) {
|
|
62
80
|
this.inputRejecter(new Error("Login cancelled"));
|
|
@@ -108,10 +126,7 @@ export class LoginDialogComponent extends Container {
|
|
|
108
126
|
this.contentContainer.addChild(this.input);
|
|
109
127
|
this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "to cancel")})`, 1, 0));
|
|
110
128
|
this.tui.requestRender();
|
|
111
|
-
return
|
|
112
|
-
this.inputResolver = resolve;
|
|
113
|
-
this.inputRejecter = reject;
|
|
114
|
-
});
|
|
129
|
+
return this.waitForInput();
|
|
115
130
|
}
|
|
116
131
|
/**
|
|
117
132
|
* Called by onPrompt callback - show prompt and wait for input
|
|
@@ -127,10 +142,7 @@ export class LoginDialogComponent extends Container {
|
|
|
127
142
|
this.contentContainer.addChild(new Text(`(${keyHint("tui.select.cancel", "to cancel,")} ${keyHint("tui.select.confirm", "to submit")})`, 1, 0));
|
|
128
143
|
this.input.setValue("");
|
|
129
144
|
this.tui.requestRender();
|
|
130
|
-
return
|
|
131
|
-
this.inputResolver = resolve;
|
|
132
|
-
this.inputRejecter = reject;
|
|
133
|
-
});
|
|
145
|
+
return this.waitForInput();
|
|
134
146
|
}
|
|
135
147
|
/**
|
|
136
148
|
* Show informational text without prompting for input.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-dialog.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/login-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["import { getOAuthProviders, type OAuthDeviceCodeInfo } from \"@caupulican/pi-ai/oauth\";\nimport { Container, type Focusable, getKeybindings, Input, Spacer, Text, type TUI } from \"@caupulican/pi-tui\";\nimport { openBrowser } from \"../../../utils/open-browser.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint } from \"./keybinding-hints.ts\";\n\n/**\n * Login dialog component - replaces editor during OAuth login flow\n */\nexport class LoginDialogComponent extends Container implements Focusable {\n\tprivate contentContainer: Container;\n\tprivate input: Input;\n\tprivate tui: TUI;\n\tprivate abortController = new AbortController();\n\tprivate inputResolver?: (value: string) => void;\n\tprivate inputRejecter?: (error: Error) => void;\n\tprivate onComplete: (success: boolean, message?: string) => void;\n\n\t// Focusable implementation - propagate to input for IME cursor positioning\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.input.focused = value;\n\t}\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tproviderId: string,\n\t\tonComplete: (success: boolean, message?: string) => void,\n\t\tproviderNameOverride?: string,\n\t\ttitleOverride?: string,\n\t) {\n\t\tsuper();\n\t\tthis.tui = tui;\n\t\tthis.onComplete = onComplete;\n\n\t\tconst providerInfo = getOAuthProviders().find((p) => p.id === providerId);\n\t\tconst providerName = providerNameOverride || providerInfo?.name || providerId;\n\t\tconst title = titleOverride ?? `Login to ${providerName}`;\n\n\t\t// Top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0));\n\n\t\t// Dynamic content area\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\t// Input (always present, used when needed)\n\t\tthis.input = new Input();\n\t\tthis.input.onSubmit = () => {\n\t\t\tif (this.inputResolver) {\n\t\t\t\tthis.inputResolver(this.input.getValue());\n\t\t\t\tthis.inputResolver = undefined;\n\t\t\t\tthis.inputRejecter = undefined;\n\t\t\t}\n\t\t};\n\t\tthis.input.onEscape = () => {\n\t\t\tthis.cancel();\n\t\t};\n\n\t\t// Bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tget signal(): AbortSignal {\n\t\treturn this.abortController.signal;\n\t}\n\n\tprivate cancel(): void {\n\t\tthis.abortController.abort();\n\t\tif (this.inputRejecter) {\n\t\t\tthis.inputRejecter(new Error(\"Login cancelled\"));\n\t\t\tthis.inputResolver = undefined;\n\t\t\tthis.inputRejecter = undefined;\n\t\t}\n\t\tthis.onComplete(false, \"Login cancelled\");\n\t}\n\n\t/**\n\t * Called by onAuth callback - show URL and optional instructions\n\t */\n\tshowAuth(url: string, instructions?: string): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tconst linkedUrl = `\\x1b]8;;${url}\\x07${url}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"accent\", linkedUrl), 1, 0));\n\n\t\tconst clickHint = process.platform === \"darwin\" ? \"Cmd+click to open\" : \"Ctrl+click to open\";\n\t\tconst hyperlink = `\\x1b]8;;${url}\\x07${clickHint}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", hyperlink), 1, 0));\n\n\t\tif (instructions) {\n\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"warning\", instructions), 1, 0));\n\t\t}\n\n\t\topenBrowser(url);\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Called by onDeviceCode callback - show URL and user code.\n\t */\n\tshowDeviceCode(info: OAuthDeviceCodeInfo): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tconst linkedUrl = `\\x1b]8;;${info.verificationUri}\\x07${info.verificationUri}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"accent\", linkedUrl), 1, 0));\n\n\t\tconst clickHint = process.platform === \"darwin\" ? \"Cmd+click to open\" : \"Ctrl+click to open\";\n\t\tconst hyperlink = `\\x1b]8;;${info.verificationUri}\\x07${clickHint}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", hyperlink), 1, 0));\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"warning\", `Enter code: ${info.userCode}`), 1, 0));\n\n\t\topenBrowser(info.verificationUri);\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show input for manual code/URL entry (for callback server providers)\n\t */\n\tshowManualInput(prompt: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", prompt), 1, 0));\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.inputResolver = resolve;\n\t\t\tthis.inputRejecter = reject;\n\t\t});\n\t}\n\n\t/**\n\t * Called by onPrompt callback - show prompt and wait for input\n\t * Note: Does NOT clear content, appends to existing (preserves URL from showAuth)\n\t */\n\tshowPrompt(message: string, placeholder?: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"text\", message), 1, 0));\n\t\tif (placeholder) {\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", `e.g., ${placeholder}`), 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(\n\t\t\tnew Text(\n\t\t\t\t`(${keyHint(\"tui.select.cancel\", \"to cancel,\")} ${keyHint(\"tui.select.confirm\", \"to submit\")})`,\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\n\t\tthis.input.setValue(\"\");\n\t\tthis.tui.requestRender();\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.inputResolver = resolve;\n\t\t\tthis.inputRejecter = reject;\n\t\t});\n\t}\n\n\t/**\n\t * Show informational text without prompting for input.\n\t */\n\tshowInfo(lines: string[]): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tfor (const line of lines) {\n\t\t\tthis.contentContainer.addChild(new Text(line, 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to close\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show waiting message (for polling flows like GitHub Copilot)\n\t */\n\tshowWaiting(message: string): void {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Called by onProgress callback\n\t */\n\tshowProgress(message: string): void {\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.cancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to input\n\t\tthis.input.handleInput(data);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"login-dialog.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/login-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["import { getOAuthProviders, type OAuthDeviceCodeInfo } from \"@caupulican/pi-ai/oauth\";\nimport { Container, type Focusable, getKeybindings, Input, Spacer, Text, type TUI } from \"@caupulican/pi-tui\";\nimport { openBrowser } from \"../../../utils/open-browser.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyHint } from \"./keybinding-hints.ts\";\n\n/**\n * Login dialog component - replaces editor during OAuth login flow\n */\nexport class LoginDialogComponent extends Container implements Focusable {\n\tprivate contentContainer: Container;\n\tprivate input: Input;\n\tprivate tui: TUI;\n\tprivate abortController = new AbortController();\n\tprivate inputResolver?: (value: string) => void;\n\tprivate inputRejecter?: (error: Error) => void;\n\tprivate onComplete: (success: boolean, message?: string) => void;\n\tprivate completed = false;\n\n\t// Focusable implementation - propagate to input for IME cursor positioning\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.input.focused = value;\n\t}\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tproviderId: string,\n\t\tonComplete: (success: boolean, message?: string) => void,\n\t\tproviderNameOverride?: string,\n\t\ttitleOverride?: string,\n\t) {\n\t\tsuper();\n\t\tthis.tui = tui;\n\t\tthis.onComplete = onComplete;\n\n\t\tconst providerInfo = getOAuthProviders().find((p) => p.id === providerId);\n\t\tconst providerName = providerNameOverride || providerInfo?.name || providerId;\n\t\tconst title = titleOverride ?? `Login to ${providerName}`;\n\n\t\t// Top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0));\n\n\t\t// Dynamic content area\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\t// Input (always present, used when needed)\n\t\tthis.input = new Input();\n\t\tthis.input.onSubmit = () => {\n\t\t\tif (this.inputResolver) {\n\t\t\t\tthis.inputResolver(this.input.getValue());\n\t\t\t\tthis.inputResolver = undefined;\n\t\t\t\tthis.inputRejecter = undefined;\n\t\t\t}\n\t\t};\n\t\tthis.input.onEscape = () => {\n\t\t\tthis.cancel();\n\t\t};\n\n\t\t// Bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tget signal(): AbortSignal {\n\t\treturn this.abortController.signal;\n\t}\n\n\tprivate waitForInput(): Promise<string> {\n\t\tif (this.completed) return Promise.reject(new Error(\"Login cancelled\"));\n\t\tif (this.inputRejecter) {\n\t\t\tconst rejectPrevious = this.inputRejecter;\n\t\t\tthis.inputResolver = undefined;\n\t\t\tthis.inputRejecter = undefined;\n\t\t\trejectPrevious(new Error(\"Login input superseded\"));\n\t\t}\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.inputResolver = resolve;\n\t\t\tthis.inputRejecter = reject;\n\t\t});\n\t}\n\n\tcancel(): void {\n\t\tif (this.completed) return;\n\t\tthis.completed = true;\n\t\tthis.abortController.abort();\n\t\tif (this.inputRejecter) {\n\t\t\tthis.inputRejecter(new Error(\"Login cancelled\"));\n\t\t\tthis.inputResolver = undefined;\n\t\t\tthis.inputRejecter = undefined;\n\t\t}\n\t\tthis.onComplete(false, \"Login cancelled\");\n\t}\n\n\t/**\n\t * Called by onAuth callback - show URL and optional instructions\n\t */\n\tshowAuth(url: string, instructions?: string): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tconst linkedUrl = `\\x1b]8;;${url}\\x07${url}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"accent\", linkedUrl), 1, 0));\n\n\t\tconst clickHint = process.platform === \"darwin\" ? \"Cmd+click to open\" : \"Ctrl+click to open\";\n\t\tconst hyperlink = `\\x1b]8;;${url}\\x07${clickHint}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", hyperlink), 1, 0));\n\n\t\tif (instructions) {\n\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"warning\", instructions), 1, 0));\n\t\t}\n\n\t\topenBrowser(url);\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Called by onDeviceCode callback - show URL and user code.\n\t */\n\tshowDeviceCode(info: OAuthDeviceCodeInfo): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tconst linkedUrl = `\\x1b]8;;${info.verificationUri}\\x07${info.verificationUri}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"accent\", linkedUrl), 1, 0));\n\n\t\tconst clickHint = process.platform === \"darwin\" ? \"Cmd+click to open\" : \"Ctrl+click to open\";\n\t\tconst hyperlink = `\\x1b]8;;${info.verificationUri}\\x07${clickHint}\\x1b]8;;\\x07`;\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", hyperlink), 1, 0));\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"warning\", `Enter code: ${info.userCode}`), 1, 0));\n\n\t\topenBrowser(info.verificationUri);\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show input for manual code/URL entry (for callback server providers)\n\t */\n\tshowManualInput(prompt: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", prompt), 1, 0));\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\n\t\treturn this.waitForInput();\n\t}\n\n\t/**\n\t * Called by onPrompt callback - show prompt and wait for input\n\t * Note: Does NOT clear content, appends to existing (preserves URL from showAuth)\n\t */\n\tshowPrompt(message: string, placeholder?: string): Promise<string> {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"text\", message), 1, 0));\n\t\tif (placeholder) {\n\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", `e.g., ${placeholder}`), 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(this.input);\n\t\tthis.contentContainer.addChild(\n\t\t\tnew Text(\n\t\t\t\t`(${keyHint(\"tui.select.cancel\", \"to cancel,\")} ${keyHint(\"tui.select.confirm\", \"to submit\")})`,\n\t\t\t\t1,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\n\t\tthis.input.setValue(\"\");\n\t\tthis.tui.requestRender();\n\n\t\treturn this.waitForInput();\n\t}\n\n\t/**\n\t * Show informational text without prompting for input.\n\t */\n\tshowInfo(lines: string[]): void {\n\t\tthis.contentContainer.clear();\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tfor (const line of lines) {\n\t\t\tthis.contentContainer.addChild(new Text(line, 1, 0));\n\t\t}\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to close\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Show waiting message (for polling flows like GitHub Copilot)\n\t */\n\tshowWaiting(message: string): void {\n\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.contentContainer.addChild(new Text(`(${keyHint(\"tui.select.cancel\", \"to cancel\")})`, 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\t/**\n\t * Called by onProgress callback\n\t */\n\tshowProgress(message: string): void {\n\t\tthis.contentContainer.addChild(new Text(theme.fg(\"dim\", message), 1, 0));\n\t\tthis.tui.requestRender();\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.cancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass to input\n\t\tthis.input.handleInput(data);\n\t}\n}\n"]}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Container, type Focusable, Input } from "@caupulican/pi-tui";
|
|
2
|
-
import type
|
|
2
|
+
import { type ResourceProfileKind, type ResourceProfileSettings } from "../../../core/settings-manager.ts";
|
|
3
3
|
export interface ProfileResourceItem {
|
|
4
|
+
/** Collision-safe value persisted into the profile filter. */
|
|
4
5
|
id: string;
|
|
6
|
+
/** Human-facing name; defaults to id. */
|
|
7
|
+
label?: string;
|
|
5
8
|
path?: string;
|
|
6
9
|
description?: string;
|
|
7
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile-resource-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/profile-resource-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { isAbsolute, resolve } from \"node:path\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tKey,\n\tmatchesKey,\n\tSpacer,\n\tText,\n} from \"@caupulican/pi-tui\";\nimport {\n\tdecodeResourceSelection,\n\tdetectResourceFraming,\n\tencodeResourceSelectionWithFraming,\n\ttype ResourceFraming,\n} from \"../../../core/profile-resource-selection.ts\";\nimport type {\n\tResourceProfileFilterSettings,\n\tResourceProfileKind,\n\tResourceProfileSettings,\n} from \"../../../core/settings-manager.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyText } from \"./keybinding-hints.ts\";\n\nexport interface ProfileResourceItem {\n\tid: string;\n\tpath?: string;\n\tdescription?: string;\n}\n\nexport interface ProfileResourceEditorKind {\n\tkind: ResourceProfileKind; // \"tools\" | \"skills\" | \"extensions\" | \"agents\" | \"prompts\" | \"themes\"\n\tlabel: string; // display label, e.g. \"Tools\"\n\titems: ProfileResourceItem[]; // the available items of this kind\n}\n\nexport interface ProfileResourceEditorOptions {\n\tprofileName: string;\n\tprofileScope: string; // e.g. \"directory\" | \"project\" | \"global\" | \"session\" | \"reusable-file\"\n\tinitialResources: ResourceProfileSettings; // existing profile.resources; may be {}\n\tkinds: ProfileResourceEditorKind[]; // the six kinds, with their universes\n\tonSave: (resources: ResourceProfileSettings) => void; // called on ctrl+s with the encoded result\n\tonCancel: () => void; // called on esc\n\tonScopeChange?: () => void;\n\tonEdit?: (id: string, path: string, kind: ResourceProfileKind) => void;\n\tcwd?: string;\n\tagentDir?: string;\n\texternalResourceRoots?: string[];\n}\n\ninterface ResourceItem {\n\tid: string;\n\tenabled: boolean;\n\tpath?: string;\n\tdescription?: string;\n\tsourceLabel?: \"catalog\" | \"user\" | \"project\" | \"bundled\";\n\tisMissing?: boolean;\n}\n\nconst TOOL_DESCRIPTIONS: Record<string, string> = {\n\tread: \"Read files from the filesystem.\",\n\tbash: \"Execute arbitrary commands in the shell.\",\n\tedit: \"Edit files surgically.\",\n\twrite: \"Create new files or overwrite existing files.\",\n\tgrep: \"Search for text patterns using ripgrep.\",\n\tfind: \"Locate files matching a search query.\",\n\tls: \"List files and directories.\",\n\tskill_audit: \"Inspect and audit local skills.\",\n\tskillify: \"Convert a set of files/instructions into a skill.\",\n\textensionify: \"Package a tool or script as an extension.\",\n};\n\nexport function resolveResourceEditPath(\n\t_id: string,\n\tfilePath: string | undefined,\n\tkind: ResourceProfileKind,\n): string | undefined {\n\tif (kind === \"tools\") {\n\t\treturn undefined;\n\t}\n\n\tif (!filePath) {\n\t\treturn undefined;\n\t}\n\n\tif (kind === \"extensions\") {\n\t\ttry {\n\t\t\tconst stats = fs.statSync(filePath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tconst indexTs = path.join(filePath, \"index.ts\");\n\t\t\t\tif (fs.existsSync(indexTs)) {\n\t\t\t\t\treturn indexTs;\n\t\t\t\t}\n\t\t\t\tconst indexJs = path.join(filePath, \"index.js\");\n\t\t\t\tif (fs.existsSync(indexJs)) {\n\t\t\t\t\treturn indexJs;\n\t\t\t\t}\n\t\t\t\tconst pkgJsonPath = path.join(filePath, \"package.json\");\n\t\t\t\tif (fs.existsSync(pkgJsonPath)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst pkg = JSON.parse(fs.readFileSync(pkgJsonPath, \"utf-8\"));\n\t\t\t\t\t\tif (typeof pkg.main === \"string\") {\n\t\t\t\t\t\t\tconst resolvedMain = path.resolve(filePath, pkg.main);\n\t\t\t\t\t\t\tif (fs.existsSync(resolvedMain)) {\n\t\t\t\t\t\t\t\treturn resolvedMain;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t\treturn indexTs;\n\t\t\t}\n\t\t} catch {}\n\t\treturn filePath;\n\t}\n\n\tif (kind === \"skills\") {\n\t\ttry {\n\t\t\tconst stats = fs.statSync(filePath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tconst skillMd = path.join(filePath, \"SKILL.md\");\n\t\t\t\tif (fs.existsSync(skillMd)) {\n\t\t\t\t\treturn skillMd;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {}\n\t\treturn filePath;\n\t}\n\n\treturn filePath;\n}\n\nexport function classifyResourceSource(\n\tfilePath: string | undefined,\n\tcwd: string,\n\tagentDir: string,\n\texternalRoots: string[],\n): \"catalog\" | \"user\" | \"project\" | \"bundled\" {\n\tif (!filePath) {\n\t\treturn \"bundled\";\n\t}\n\n\tconst absolutePath = isAbsolute(filePath) ? filePath : resolve(cwd, filePath);\n\tconst normPath = resolve(absolutePath);\n\tconst normCwd = resolve(cwd);\n\tconst normAgentDir = agentDir ? resolve(agentDir) : \"\";\n\n\tfor (const extRoot of externalRoots) {\n\t\tconst normExt = resolve(extRoot);\n\t\tif (normPath.startsWith(normExt)) {\n\t\t\treturn \"catalog\";\n\t\t}\n\t}\n\n\tif (normAgentDir && normPath.startsWith(normAgentDir)) {\n\t\treturn \"user\";\n\t}\n\n\tif (normPath.startsWith(normCwd)) {\n\t\treturn \"project\";\n\t}\n\n\tif (normPath.includes(\"node_modules\")) {\n\t\treturn \"bundled\";\n\t}\n\n\treturn \"bundled\";\n}\n\nexport class ProfileResourceEditorComponent extends Container implements Focusable {\n\tprivate profileName: string;\n\tprivate profileScope: string;\n\tprivate kinds: ProfileResourceEditorKind[];\n\tprivate enabledByKind: Map<ResourceProfileKind, Set<string>> = new Map();\n\tprivate framingByKind: Map<ResourceProfileKind, ResourceFraming> = new Map();\n\tprivate missingIdsByKind: Map<ResourceProfileKind, Set<string>> = new Map();\n\t/** The filter each kind was decoded from, so an all-enabled save preserves an enumerated grant\n\t * (or an already-wildcard grant) instead of widening it to allow:[\"*\"]. */\n\tprivate originalFilterByKind: Map<ResourceProfileKind, ResourceProfileFilterSettings | undefined> = new Map();\n\tprivate currentKindIndex = 0;\n\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\tprivate externalResourceRoots: string[];\n\n\tprivate filteredItems: ResourceItem[] = [];\n\tprivate selectedIndex = 0;\n\tprivate searchInput: Input;\n\tprivate kindHeaderText: Text;\n\tprivate listContainer: Container;\n\tprivate descriptionText: Text;\n\tprivate footerText: Text;\n\tprivate isDirty = false;\n\tprivate maxVisible = 8;\n\n\tprivate _focused = false;\n\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\tprivate onSave: (resources: ResourceProfileSettings) => void;\n\tprivate onCancel: () => void;\n\tprivate onScopeChange?: () => void;\n\tprivate onEdit?: (id: string, path: string, kind: ResourceProfileKind) => void;\n\n\tconstructor(options: ProfileResourceEditorOptions) {\n\t\tsuper();\n\t\tthis.profileName = options.profileName;\n\t\tthis.profileScope = options.profileScope;\n\t\tthis.kinds = options.kinds;\n\t\tthis.onSave = options.onSave;\n\t\tthis.onCancel = options.onCancel;\n\t\tthis.onScopeChange = options.onScopeChange;\n\t\tthis.onEdit = options.onEdit;\n\t\tthis.cwd = options.cwd || process.cwd();\n\t\tthis.agentDir = options.agentDir || \"\";\n\t\tthis.externalResourceRoots = options.externalResourceRoots || [];\n\n\t\t// Initialize enabled, framing, and missing sets for each kind\n\t\tfor (const kind of this.kinds) {\n\t\t\tconst filter = options.initialResources[kind.kind];\n\t\t\tconst allIds = kind.items.map((item) => item.id);\n\n\t\t\t// Compute missing items\n\t\t\tconst mentionedIds = new Set<string>();\n\t\t\tif (filter) {\n\t\t\t\tif (filter.allow) {\n\t\t\t\t\tfor (const id of filter.allow) {\n\t\t\t\t\t\t// \"*\" is a grant-all framing marker, not a resource id — the block loop below\n\t\t\t\t\t\t// already excludes it; the allow loop must too, or it leaks into missingSet as\n\t\t\t\t\t\t// a fake \"missing item\" and rides along as a phantom enabled member through\n\t\t\t\t\t\t// decode/encode.\n\t\t\t\t\t\tif (id !== \"*\") {\n\t\t\t\t\t\t\tmentionedIds.add(id);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (filter.block) {\n\t\t\t\t\tfor (const id of filter.block) {\n\t\t\t\t\t\tif (id !== \"*\") {\n\t\t\t\t\t\t\tmentionedIds.add(id);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst availableIds = new Set(allIds);\n\t\t\tconst missingSet = new Set<string>();\n\t\t\tfor (const id of mentionedIds) {\n\t\t\t\tif (!availableIds.has(id)) {\n\t\t\t\t\tmissingSet.add(id);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.missingIdsByKind.set(kind.kind, missingSet);\n\n\t\t\t// Decode against the DISCOVERABLE universe plus every id the profile itself mentions:\n\t\t\t// decoding only against the discoverable set silently drops grants for anything the\n\t\t\t// current environment cannot see (offline external root, or a collapsed universe),\n\t\t\t// and the next save would then corrupt the profile.\n\t\t\tconst enabledSet = decodeResourceSelection(filter, [...allIds, ...missingSet]);\n\t\t\tthis.enabledByKind.set(kind.kind, enabledSet);\n\t\t\tthis.framingByKind.set(kind.kind, detectResourceFraming(filter));\n\t\t\tthis.originalFilterByKind.set(kind.kind, filter);\n\t\t}\n\n\t\t// Header\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\ttheme.fg(\"accent\", theme.bold(`Library — editing \"${this.profileName}\" (${this.profileScope})`)),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\ttheme.fg(\n\t\t\t\t\t\"muted\",\n\t\t\t\t\t`Navigate kinds: ${keyText(\"tui.input.tab\")}. Toggle: ${keyText(\"tui.select.confirm\")}. Mode: ${theme.fg(\"accent\", \"a\")} allow / ${theme.fg(\"accent\", \"b\")} block. Scope: ${theme.fg(\"accent\", \"s\")} change. Edit: ${theme.fg(\"accent\", \"e\")}.`,\n\t\t\t\t),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Kind selector header\n\t\tthis.kindHeaderText = new Text(this.getKindHeaderText(), 0, 0);\n\t\tthis.addChild(this.kindHeaderText);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Search input\n\t\tthis.searchInput = new Input();\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// List container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\t// Description area\n\t\tthis.descriptionText = new Text(\"\", 0, 0);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.descriptionText);\n\n\t\t// Footer hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.footerText = new Text(this.getFooterText(), 0, 0);\n\t\tthis.addChild(this.footerText);\n\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.refresh();\n\t}\n\n\tprivate getKindHeaderText(): string {\n\t\tconst kind = this.kinds[this.currentKindIndex]!;\n\t\tconst enabledSet = this.enabledByKind.get(kind.kind)!;\n\t\tconst framing = this.framingByKind.get(kind.kind)!;\n\t\tconst framingText = framing === \"allow\" ? \"allow-list\" : \"block-list\";\n\t\tconst countText = `${enabledSet.size}/${kind.items.length} enabled (${framingText})`;\n\t\tconst kindIndicator = this.kinds\n\t\t\t.map((k, i) => {\n\t\t\t\tconst marker = i === this.currentKindIndex ? \"●\" : \"○\";\n\t\t\t\treturn theme.fg(i === this.currentKindIndex ? \"accent\" : \"muted\", `${marker} ${k.label}`);\n\t\t\t})\n\t\t\t.join(\" \");\n\t\treturn `${kindIndicator} ${theme.fg(\"muted\", countText)}`;\n\t}\n\n\tprivate getCurrentKind(): ProfileResourceEditorKind {\n\t\treturn this.kinds[this.currentKindIndex]!;\n\t}\n\n\tprivate getCurrentEnabledSet(): Set<string> {\n\t\tconst kind = this.getCurrentKind();\n\t\treturn this.enabledByKind.get(kind.kind)!;\n\t}\n\n\tprivate buildItems(): ResourceItem[] {\n\t\tconst kind = this.getCurrentKind();\n\t\tconst enabledSet = this.getCurrentEnabledSet();\n\n\t\tconst items: ResourceItem[] = [];\n\n\t\t// Add all available items\n\t\tfor (const availableItem of kind.items) {\n\t\t\tconst isEnabled = enabledSet.has(availableItem.id);\n\t\t\tlet desc = availableItem.description;\n\t\t\tif (kind.kind === \"tools\" && !desc) {\n\t\t\t\tdesc = TOOL_DESCRIPTIONS[availableItem.id];\n\t\t\t}\n\t\t\titems.push({\n\t\t\t\tid: availableItem.id,\n\t\t\t\tenabled: isEnabled,\n\t\t\t\tpath: availableItem.path,\n\t\t\t\tdescription: desc,\n\t\t\t\tsourceLabel: classifyResourceSource(\n\t\t\t\t\tavailableItem.path,\n\t\t\t\t\tthis.cwd,\n\t\t\t\t\tthis.agentDir,\n\t\t\t\t\tthis.externalResourceRoots,\n\t\t\t\t),\n\t\t\t\tisMissing: false,\n\t\t\t});\n\t\t}\n\n\t\t// Add missing items\n\t\tconst missingSet = this.missingIdsByKind.get(kind.kind) || new Set<string>();\n\t\tfor (const missingId of missingSet) {\n\t\t\tconst isEnabled = enabledSet.has(missingId);\n\t\t\titems.push({\n\t\t\t\tid: missingId,\n\t\t\t\tenabled: isEnabled,\n\t\t\t\tisMissing: true,\n\t\t\t\tdescription: \"Referenced in profile but missing from available resources.\",\n\t\t\t});\n\t\t}\n\n\t\t// Sort: enabled first, then disabled\n\t\tconst enabled: ResourceItem[] = [];\n\t\tconst disabled: ResourceItem[] = [];\n\t\tfor (const item of items) {\n\t\t\tif (item.enabled) {\n\t\t\t\tenabled.push(item);\n\t\t\t} else {\n\t\t\t\tdisabled.push(item);\n\t\t\t}\n\t\t}\n\t\treturn [...enabled, ...disabled];\n\t}\n\n\tprivate getFooterText(): string {\n\t\tconst kind = this.getCurrentKind();\n\t\tconst enabledSet = this.getCurrentEnabledSet();\n\t\tconst countText = `${enabledSet.size}/${kind.items.length} enabled`;\n\t\tconst parts = [\n\t\t\t`${keyText(\"tui.select.confirm\")} toggle`,\n\t\t\t`${keyText(\"tui.input.tab\")} kind`,\n\t\t\t`${keyText(\"app.models.save\")} save`,\n\t\t\t`${theme.fg(\"accent\", \"e\")} edit`,\n\t\t\t`${theme.fg(\"accent\", \"Ctrl+Q\")}/${keyText(\"app.interrupt\")} cancel`,\n\t\t\tcountText,\n\t\t];\n\t\treturn this.isDirty\n\t\t\t? theme.fg(\"dim\", ` ${parts.join(\" · \")} `) + theme.fg(\"warning\", \"(unsaved)\")\n\t\t\t: theme.fg(\"dim\", ` ${parts.join(\" · \")}`);\n\t}\n\n\tprivate refresh(): void {\n\t\tconst query = this.searchInput.getValue();\n\t\tconst items = this.buildItems();\n\t\tthis.filteredItems = query ? fuzzyFilter(items, query, (i) => `${i.id} ${i.description ?? \"\"}`) : items;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));\n\t\tthis.updateList();\n\t\tthis.footerText.setText(this.getFooterText());\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tconst kind = this.getCurrentKind();\n\t\tconst missingSet = this.missingIdsByKind.get(kind.kind) || new Set<string>();\n\t\tconst totalAvailableCount = kind.items.length + missingSet.size;\n\n\t\tif (totalAvailableCount === 0) {\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(\"muted\", \" (none available — add a Source or install resources)\"), 0, 0),\n\t\t\t);\n\t\t\tthis.descriptionText.setText(theme.fg(\"muted\", \" No description available\"));\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", \" No matching resources\"), 0, 0));\n\t\t\tthis.descriptionText.setText(theme.fg(\"muted\", \" No description available\"));\n\t\t\treturn;\n\t\t}\n\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 item = this.filteredItems[i]!;\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"→ \") : \" \";\n\t\t\tlet resourceText = isSelected ? theme.fg(\"accent\", item.id) : item.id;\n\n\t\t\tif (item.isMissing) {\n\t\t\t\tresourceText = theme.fg(\"muted\", `${item.id} [missing]`);\n\t\t\t} else if (item.sourceLabel) {\n\t\t\t\tconst labelColor =\n\t\t\t\t\titem.sourceLabel === \"catalog\"\n\t\t\t\t\t\t? \"thinkingText\"\n\t\t\t\t\t\t: item.sourceLabel === \"project\"\n\t\t\t\t\t\t\t? \"success\"\n\t\t\t\t\t\t\t: item.sourceLabel === \"user\"\n\t\t\t\t\t\t\t\t? \"warning\"\n\t\t\t\t\t\t\t\t: \"muted\";\n\t\t\t\tconst labelText = theme.fg(labelColor, ` [${item.sourceLabel}]`);\n\t\t\t\tresourceText = `${resourceText}${labelText}`;\n\t\t\t}\n\n\t\t\tconst status = item.enabled ? theme.fg(\"success\", \" ✓\") : theme.fg(\"dim\", \" ✗\");\n\t\t\tthis.listContainer.addChild(new Text(`${prefix}${resourceText}${status}`, 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`), 0, 0),\n\t\t\t);\n\t\t}\n\n\t\t// Update description area for current selection\n\t\tconst selectedItem = this.filteredItems[this.selectedIndex];\n\t\tif (selectedItem?.description) {\n\t\t\tthis.descriptionText.setText(theme.fg(\"muted\", ` Description: ${selectedItem.description}`));\n\t\t} else {\n\t\t\tthis.descriptionText.setText(theme.fg(\"muted\", \" No description available\"));\n\t\t}\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\t// Navigation within list\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredItems.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredItems.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\n\t\t// Switch kind with Tab (cycles forward)\n\t\tif (kb.matches(data, \"tui.input.tab\")) {\n\t\t\tthis.currentKindIndex = (this.currentKindIndex + 1) % this.kinds.length;\n\t\t\tthis.selectedIndex = 0;\n\t\t\tthis.searchInput.setValue(\"\");\n\t\t\tthis.kindHeaderText.setText(this.getKindHeaderText());\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Switch framing: Allow-list\n\t\tif (matchesKey(data, Key.ctrl(\"a\")) || (this.searchInput.getValue() === \"\" && data === \"a\")) {\n\t\t\tthis.framingByKind.set(this.getCurrentKind().kind, \"allow\");\n\t\t\tthis.isDirty = true;\n\t\t\tthis.kindHeaderText.setText(this.getKindHeaderText());\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Switch framing: Block-list\n\t\tif (matchesKey(data, Key.ctrl(\"b\")) || (this.searchInput.getValue() === \"\" && data === \"b\")) {\n\t\t\tthis.framingByKind.set(this.getCurrentKind().kind, \"block\");\n\t\t\tthis.isDirty = true;\n\t\t\tthis.kindHeaderText.setText(this.getKindHeaderText());\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Edit highlighted resource\n\t\tif (data === \"e\" && this.searchInput.getValue() === \"\") {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item && !item.isMissing && item.path) {\n\t\t\t\tconst kind = this.getCurrentKind().kind;\n\t\t\t\tif (this.onEdit) {\n\t\t\t\t\tthis.onEdit(item.id, item.path, kind);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Switch scope: Scope selector\n\t\tif (matchesKey(data, Key.ctrl(\"o\")) || (this.searchInput.getValue() === \"\" && data === \"s\")) {\n\t\t\tif (this.onScopeChange) {\n\t\t\t\tthis.onScopeChange();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Toggle on space/enter\n\t\tif (kb.matches(data, \"tui.select.confirm\")) {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item) {\n\t\t\t\tconst enabledSet = this.getCurrentEnabledSet();\n\t\t\t\tif (enabledSet.has(item.id)) {\n\t\t\t\t\tenabledSet.delete(item.id);\n\t\t\t\t} else {\n\t\t\t\t\tenabledSet.add(item.id);\n\t\t\t\t}\n\t\t\t\tthis.isDirty = true;\n\t\t\t\tthis.refresh();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Save/persist to settings\n\t\tif (kb.matches(data, \"app.models.save\")) {\n\t\t\tthis.persistChanges();\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+C - clear search or cancel if empty\n\t\tif (matchesKey(data, Key.ctrl(\"c\"))) {\n\t\t\tif (this.searchInput.getValue()) {\n\t\t\t\tthis.searchInput.setValue(\"\");\n\t\t\t\tthis.refresh();\n\t\t\t} else {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Escape / Ctrl+Q - cancel (unconditional)\n\t\tif (\n\t\t\tkb.matches(data, \"app.interrupt\") ||\n\t\t\tmatchesKey(data, Key.escape) ||\n\t\t\tmatchesKey(data, Key.esc) ||\n\t\t\tmatchesKey(data, \"ctrl+q\")\n\t\t) {\n\t\t\tthis.onCancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass everything else to search input\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.refresh();\n\t}\n\n\tprivate persistChanges(): void {\n\t\tconst resources: ResourceProfileSettings = {};\n\t\tfor (const kind of this.kinds) {\n\t\t\tconst enabledSet = this.enabledByKind.get(kind.kind)!;\n\t\t\tconst framing = this.framingByKind.get(kind.kind)!;\n\t\t\t// Persist against the same widened universe used at decode time: profile-referenced\n\t\t\t// ids that are not currently discoverable must survive a save untouched.\n\t\t\tconst missingSet = this.missingIdsByKind.get(kind.kind) ?? new Set<string>();\n\t\t\tconst allIds = [...new Set([...kind.items.map((item) => item.id), ...missingSet])];\n\t\t\t// Pass the original filter so an all-enabled save preserves an enumerated grant (and only\n\t\t\t// keeps a wildcard when the grant was already a wildcard) — never widening it silently.\n\t\t\tconst encoded = encodeResourceSelectionWithFraming(enabledSet, allIds, framing, {\n\t\t\t\toriginalFilter: this.originalFilterByKind.get(kind.kind),\n\t\t\t});\n\t\t\tif (encoded !== undefined) {\n\t\t\t\tresources[kind.kind] = encoded;\n\t\t\t}\n\t\t}\n\t\tthis.onSave(resources);\n\t\tthis.isDirty = false;\n\t\tthis.footerText.setText(this.getFooterText());\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"profile-resource-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/profile-resource-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { isAbsolute, resolve } from \"node:path\";\nimport {\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tgetKeybindings,\n\tInput,\n\tKey,\n\tmatchesKey,\n\tSpacer,\n\tText,\n} from \"@caupulican/pi-tui\";\nimport {\n\tdecodeResourceSelection,\n\tdetectResourceFraming,\n\tencodeResourceSelectionWithFraming,\n\ttype ResourceFraming,\n\tremapResourceSelectionFilter,\n} from \"../../../core/profile-resource-selection.ts\";\nimport {\n\tmatchesResourceProfilePattern,\n\ttype ResourceProfileFilterSettings,\n\ttype ResourceProfileKind,\n\ttype ResourceProfileSettings,\n} from \"../../../core/settings-manager.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { keyText } from \"./keybinding-hints.ts\";\n\nexport interface ProfileResourceItem {\n\t/** Collision-safe value persisted into the profile filter. */\n\tid: string;\n\t/** Human-facing name; defaults to id. */\n\tlabel?: string;\n\tpath?: string;\n\tdescription?: string;\n}\n\nexport interface ProfileResourceEditorKind {\n\tkind: ResourceProfileKind; // \"tools\" | \"skills\" | \"extensions\" | \"agents\" | \"prompts\" | \"themes\"\n\tlabel: string; // display label, e.g. \"Tools\"\n\titems: ProfileResourceItem[]; // the available items of this kind\n}\n\nexport interface ProfileResourceEditorOptions {\n\tprofileName: string;\n\tprofileScope: string; // e.g. \"directory\" | \"project\" | \"global\" | \"session\" | \"reusable-file\"\n\tinitialResources: ResourceProfileSettings; // existing profile.resources; may be {}\n\tkinds: ProfileResourceEditorKind[]; // the six kinds, with their universes\n\tonSave: (resources: ResourceProfileSettings) => void; // called on ctrl+s with the encoded result\n\tonCancel: () => void; // called on esc\n\tonScopeChange?: () => void;\n\tonEdit?: (id: string, path: string, kind: ResourceProfileKind) => void;\n\tcwd?: string;\n\tagentDir?: string;\n\texternalResourceRoots?: string[];\n}\n\ninterface ResourceItem {\n\tid: string;\n\tlabel: string;\n\tenabled: boolean;\n\tpath?: string;\n\tdescription?: string;\n\tsourceLabel?: \"catalog\" | \"user\" | \"project\" | \"bundled\";\n\tisMissing?: boolean;\n}\n\nconst TOOL_DESCRIPTIONS: Record<string, string> = {\n\tread: \"Read files from the filesystem.\",\n\tbash: \"Execute arbitrary commands in the shell.\",\n\tedit: \"Edit files surgically.\",\n\twrite: \"Create new files or overwrite existing files.\",\n\tgrep: \"Search for text patterns using ripgrep.\",\n\tfind: \"Locate files matching a search query.\",\n\tls: \"List files and directories.\",\n\tskill_audit: \"Inspect and audit local skills.\",\n\tskillify: \"Convert a set of files/instructions into a skill.\",\n\textensionify: \"Package a tool or script as an extension.\",\n};\n\nexport function resolveResourceEditPath(\n\t_id: string,\n\tfilePath: string | undefined,\n\tkind: ResourceProfileKind,\n): string | undefined {\n\tif (kind === \"tools\") {\n\t\treturn undefined;\n\t}\n\n\tif (!filePath) {\n\t\treturn undefined;\n\t}\n\n\tif (kind === \"extensions\") {\n\t\ttry {\n\t\t\tconst stats = fs.statSync(filePath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tconst indexTs = path.join(filePath, \"index.ts\");\n\t\t\t\tif (fs.existsSync(indexTs)) {\n\t\t\t\t\treturn indexTs;\n\t\t\t\t}\n\t\t\t\tconst indexJs = path.join(filePath, \"index.js\");\n\t\t\t\tif (fs.existsSync(indexJs)) {\n\t\t\t\t\treturn indexJs;\n\t\t\t\t}\n\t\t\t\tconst pkgJsonPath = path.join(filePath, \"package.json\");\n\t\t\t\tif (fs.existsSync(pkgJsonPath)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst pkg = JSON.parse(fs.readFileSync(pkgJsonPath, \"utf-8\"));\n\t\t\t\t\t\tif (typeof pkg.main === \"string\") {\n\t\t\t\t\t\t\tconst resolvedMain = path.resolve(filePath, pkg.main);\n\t\t\t\t\t\t\tif (fs.existsSync(resolvedMain)) {\n\t\t\t\t\t\t\t\treturn resolvedMain;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t\treturn indexTs;\n\t\t\t}\n\t\t} catch {}\n\t\treturn filePath;\n\t}\n\n\tif (kind === \"skills\") {\n\t\ttry {\n\t\t\tconst stats = fs.statSync(filePath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tconst skillMd = path.join(filePath, \"SKILL.md\");\n\t\t\t\tif (fs.existsSync(skillMd)) {\n\t\t\t\t\treturn skillMd;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {}\n\t\treturn filePath;\n\t}\n\n\treturn filePath;\n}\n\nexport function classifyResourceSource(\n\tfilePath: string | undefined,\n\tcwd: string,\n\tagentDir: string,\n\texternalRoots: string[],\n): \"catalog\" | \"user\" | \"project\" | \"bundled\" {\n\tif (!filePath) {\n\t\treturn \"bundled\";\n\t}\n\n\tconst absolutePath = isAbsolute(filePath) ? filePath : resolve(cwd, filePath);\n\tconst normPath = resolve(absolutePath);\n\tconst normCwd = resolve(cwd);\n\tconst normAgentDir = agentDir ? resolve(agentDir) : \"\";\n\n\tfor (const extRoot of externalRoots) {\n\t\tconst normExt = resolve(extRoot);\n\t\tif (normPath.startsWith(normExt)) {\n\t\t\treturn \"catalog\";\n\t\t}\n\t}\n\n\tif (normAgentDir && normPath.startsWith(normAgentDir)) {\n\t\treturn \"user\";\n\t}\n\n\tif (normPath.startsWith(normCwd)) {\n\t\treturn \"project\";\n\t}\n\n\tif (normPath.includes(\"node_modules\")) {\n\t\treturn \"bundled\";\n\t}\n\n\treturn \"bundled\";\n}\n\nexport class ProfileResourceEditorComponent extends Container implements Focusable {\n\tprivate profileName: string;\n\tprivate profileScope: string;\n\tprivate kinds: ProfileResourceEditorKind[];\n\tprivate enabledByKind: Map<ResourceProfileKind, Set<string>> = new Map();\n\tprivate framingByKind: Map<ResourceProfileKind, ResourceFraming> = new Map();\n\tprivate missingIdsByKind: Map<ResourceProfileKind, Set<string>> = new Map();\n\t/** The filter each kind was decoded from, so an all-enabled save preserves an enumerated grant\n\t * (or an already-wildcard grant) instead of widening it to allow:[\"*\"]. */\n\tprivate originalFilterByKind: Map<ResourceProfileKind, ResourceProfileFilterSettings | undefined> = new Map();\n\tprivate currentKindIndex = 0;\n\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\tprivate externalResourceRoots: string[];\n\n\tprivate filteredItems: ResourceItem[] = [];\n\tprivate selectedIndex = 0;\n\tprivate searchInput: Input;\n\tprivate kindHeaderText: Text;\n\tprivate listContainer: Container;\n\tprivate descriptionText: Text;\n\tprivate footerText: Text;\n\tprivate isDirty = false;\n\tprivate maxVisible = 8;\n\n\tprivate _focused = false;\n\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\tprivate onSave: (resources: ResourceProfileSettings) => void;\n\tprivate onCancel: () => void;\n\tprivate onScopeChange?: () => void;\n\tprivate onEdit?: (id: string, path: string, kind: ResourceProfileKind) => void;\n\n\tconstructor(options: ProfileResourceEditorOptions) {\n\t\tsuper();\n\t\tthis.profileName = options.profileName;\n\t\tthis.profileScope = options.profileScope;\n\t\tthis.kinds = options.kinds;\n\t\tthis.onSave = options.onSave;\n\t\tthis.onCancel = options.onCancel;\n\t\tthis.onScopeChange = options.onScopeChange;\n\t\tthis.onEdit = options.onEdit;\n\t\tthis.cwd = options.cwd || process.cwd();\n\t\tthis.agentDir = options.agentDir || \"\";\n\t\tthis.externalResourceRoots = options.externalResourceRoots || [];\n\n\t\t// Initialize enabled, framing, and missing sets for each kind\n\t\tfor (const kind of this.kinds) {\n\t\t\tconst filter = options.initialResources[kind.kind];\n\t\t\tconst remapped = remapResourceSelectionFilter(filter, kind.items, (item, pattern) => {\n\t\t\t\tif (item.id === pattern) return true;\n\t\t\t\treturn item.path ? matchesResourceProfilePattern(item.path, [pattern], this.cwd) : false;\n\t\t\t});\n\t\t\tthis.missingIdsByKind.set(kind.kind, remapped.missingIds);\n\n\t\t\t// Decode against concrete collision-safe ids plus genuinely missing profile entries. Legacy\n\t\t\t// basename/parent/glob patterns are expanded to the resources they currently match, so saving\n\t\t\t// migrates ambiguous patterns such as \"index.ts\" instead of retaining a multi-extension toggle.\n\t\t\tconst allIds = kind.items.map((item) => item.id);\n\t\t\tconst enabledSet = decodeResourceSelection(remapped.filter, [...allIds, ...remapped.missingIds]);\n\t\t\tthis.enabledByKind.set(kind.kind, enabledSet);\n\t\t\tthis.framingByKind.set(kind.kind, detectResourceFraming(filter));\n\t\t\tthis.originalFilterByKind.set(kind.kind, filter);\n\t\t}\n\n\t\t// Header\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\ttheme.fg(\"accent\", theme.bold(`Library — editing \"${this.profileName}\" (${this.profileScope})`)),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(\n\t\t\tnew Text(\n\t\t\t\ttheme.fg(\n\t\t\t\t\t\"muted\",\n\t\t\t\t\t`Navigate kinds: ${keyText(\"tui.input.tab\")}. Toggle: ${keyText(\"tui.select.confirm\")}. Mode: ${theme.fg(\"accent\", \"a\")} allow / ${theme.fg(\"accent\", \"b\")} block. Scope: ${theme.fg(\"accent\", \"s\")} change. Edit: ${theme.fg(\"accent\", \"e\")}.`,\n\t\t\t\t),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Kind selector header\n\t\tthis.kindHeaderText = new Text(this.getKindHeaderText(), 0, 0);\n\t\tthis.addChild(this.kindHeaderText);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Search input\n\t\tthis.searchInput = new Input();\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// List container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\t// Description area\n\t\tthis.descriptionText = new Text(\"\", 0, 0);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.descriptionText);\n\n\t\t// Footer hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.footerText = new Text(this.getFooterText(), 0, 0);\n\t\tthis.addChild(this.footerText);\n\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.refresh();\n\t}\n\n\tprivate getKindHeaderText(): string {\n\t\tconst kind = this.kinds[this.currentKindIndex]!;\n\t\tconst enabledSet = this.enabledByKind.get(kind.kind)!;\n\t\tconst framing = this.framingByKind.get(kind.kind)!;\n\t\tconst framingText = framing === \"allow\" ? \"allow-list\" : \"block-list\";\n\t\tconst countText = `${enabledSet.size}/${kind.items.length} enabled (${framingText})`;\n\t\tconst kindIndicator = this.kinds\n\t\t\t.map((k, i) => {\n\t\t\t\tconst marker = i === this.currentKindIndex ? \"●\" : \"○\";\n\t\t\t\treturn theme.fg(i === this.currentKindIndex ? \"accent\" : \"muted\", `${marker} ${k.label}`);\n\t\t\t})\n\t\t\t.join(\" \");\n\t\treturn `${kindIndicator} ${theme.fg(\"muted\", countText)}`;\n\t}\n\n\tprivate getCurrentKind(): ProfileResourceEditorKind {\n\t\treturn this.kinds[this.currentKindIndex]!;\n\t}\n\n\tprivate getCurrentEnabledSet(): Set<string> {\n\t\tconst kind = this.getCurrentKind();\n\t\treturn this.enabledByKind.get(kind.kind)!;\n\t}\n\n\tprivate buildItems(): ResourceItem[] {\n\t\tconst kind = this.getCurrentKind();\n\t\tconst enabledSet = this.getCurrentEnabledSet();\n\n\t\tconst items: ResourceItem[] = [];\n\n\t\t// Add all available items\n\t\tfor (const availableItem of kind.items) {\n\t\t\tconst isEnabled = enabledSet.has(availableItem.id);\n\t\t\tlet desc = availableItem.description;\n\t\t\tif (kind.kind === \"tools\" && !desc) {\n\t\t\t\tdesc = TOOL_DESCRIPTIONS[availableItem.id];\n\t\t\t}\n\t\t\titems.push({\n\t\t\t\tid: availableItem.id,\n\t\t\t\tlabel: availableItem.label ?? availableItem.id,\n\t\t\t\tenabled: isEnabled,\n\t\t\t\tpath: availableItem.path,\n\t\t\t\tdescription: desc,\n\t\t\t\tsourceLabel: classifyResourceSource(\n\t\t\t\t\tavailableItem.path,\n\t\t\t\t\tthis.cwd,\n\t\t\t\t\tthis.agentDir,\n\t\t\t\t\tthis.externalResourceRoots,\n\t\t\t\t),\n\t\t\t\tisMissing: false,\n\t\t\t});\n\t\t}\n\n\t\t// Add missing items\n\t\tconst missingSet = this.missingIdsByKind.get(kind.kind) || new Set<string>();\n\t\tfor (const missingId of missingSet) {\n\t\t\tconst isEnabled = enabledSet.has(missingId);\n\t\t\titems.push({\n\t\t\t\tid: missingId,\n\t\t\t\tlabel: missingId,\n\t\t\t\tenabled: isEnabled,\n\t\t\t\tisMissing: true,\n\t\t\t\tdescription: \"Referenced in profile but missing from available resources.\",\n\t\t\t});\n\t\t}\n\n\t\t// Sort: enabled first, then disabled\n\t\tconst enabled: ResourceItem[] = [];\n\t\tconst disabled: ResourceItem[] = [];\n\t\tfor (const item of items) {\n\t\t\tif (item.enabled) {\n\t\t\t\tenabled.push(item);\n\t\t\t} else {\n\t\t\t\tdisabled.push(item);\n\t\t\t}\n\t\t}\n\t\treturn [...enabled, ...disabled];\n\t}\n\n\tprivate getFooterText(): string {\n\t\tconst kind = this.getCurrentKind();\n\t\tconst enabledSet = this.getCurrentEnabledSet();\n\t\tconst countText = `${enabledSet.size}/${kind.items.length} enabled`;\n\t\tconst parts = [\n\t\t\t`${keyText(\"tui.select.confirm\")} toggle`,\n\t\t\t`${keyText(\"tui.input.tab\")} kind`,\n\t\t\t`${keyText(\"app.models.save\")} save`,\n\t\t\t`${theme.fg(\"accent\", \"e\")} edit`,\n\t\t\t`${theme.fg(\"accent\", \"Ctrl+Q\")}/${keyText(\"app.interrupt\")} cancel`,\n\t\t\tcountText,\n\t\t];\n\t\treturn this.isDirty\n\t\t\t? theme.fg(\"dim\", ` ${parts.join(\" · \")} `) + theme.fg(\"warning\", \"(unsaved)\")\n\t\t\t: theme.fg(\"dim\", ` ${parts.join(\" · \")}`);\n\t}\n\n\tprivate refresh(): void {\n\t\tconst query = this.searchInput.getValue();\n\t\tconst items = this.buildItems();\n\t\tthis.filteredItems = query\n\t\t\t? fuzzyFilter(items, query, (item) => `${item.label} ${item.id} ${item.description ?? \"\"}`)\n\t\t\t: items;\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));\n\t\tthis.updateList();\n\t\tthis.footerText.setText(this.getFooterText());\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tconst kind = this.getCurrentKind();\n\t\tconst missingSet = this.missingIdsByKind.get(kind.kind) || new Set<string>();\n\t\tconst totalAvailableCount = kind.items.length + missingSet.size;\n\n\t\tif (totalAvailableCount === 0) {\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(\"muted\", \" (none available — add a Source or install resources)\"), 0, 0),\n\t\t\t);\n\t\t\tthis.descriptionText.setText(theme.fg(\"muted\", \" No description available\"));\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", \" No matching resources\"), 0, 0));\n\t\t\tthis.descriptionText.setText(theme.fg(\"muted\", \" No description available\"));\n\t\t\treturn;\n\t\t}\n\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 item = this.filteredItems[i]!;\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"→ \") : \" \";\n\t\t\tlet resourceText = isSelected ? theme.fg(\"accent\", item.label) : item.label;\n\n\t\t\tif (item.isMissing) {\n\t\t\t\tresourceText = theme.fg(\"muted\", `${item.label} [missing]`);\n\t\t\t} else if (item.sourceLabel) {\n\t\t\t\tconst labelColor =\n\t\t\t\t\titem.sourceLabel === \"catalog\"\n\t\t\t\t\t\t? \"thinkingText\"\n\t\t\t\t\t\t: item.sourceLabel === \"project\"\n\t\t\t\t\t\t\t? \"success\"\n\t\t\t\t\t\t\t: item.sourceLabel === \"user\"\n\t\t\t\t\t\t\t\t? \"warning\"\n\t\t\t\t\t\t\t\t: \"muted\";\n\t\t\t\tconst labelText = theme.fg(labelColor, ` [${item.sourceLabel}]`);\n\t\t\t\tresourceText = `${resourceText}${labelText}`;\n\t\t\t}\n\n\t\t\tconst status = item.enabled ? theme.fg(\"success\", \" ✓\") : theme.fg(\"dim\", \" ✗\");\n\t\t\tthis.listContainer.addChild(new Text(`${prefix}${resourceText}${status}`, 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew Text(theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`), 0, 0),\n\t\t\t);\n\t\t}\n\n\t\t// Update description area for current selection\n\t\tconst selectedItem = this.filteredItems[this.selectedIndex];\n\t\tif (selectedItem?.description) {\n\t\t\tthis.descriptionText.setText(theme.fg(\"muted\", ` Description: ${selectedItem.description}`));\n\t\t} else if (selectedItem && selectedItem.label !== selectedItem.id) {\n\t\t\tthis.descriptionText.setText(theme.fg(\"muted\", ` Resource: ${selectedItem.id}`));\n\t\t} else {\n\t\t\tthis.descriptionText.setText(theme.fg(\"muted\", \" No description available\"));\n\t\t}\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\t// Navigation within list\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredItems.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredItems.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\n\t\t// Switch kind with Tab (cycles forward)\n\t\tif (kb.matches(data, \"tui.input.tab\")) {\n\t\t\tthis.currentKindIndex = (this.currentKindIndex + 1) % this.kinds.length;\n\t\t\tthis.selectedIndex = 0;\n\t\t\tthis.searchInput.setValue(\"\");\n\t\t\tthis.kindHeaderText.setText(this.getKindHeaderText());\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Switch framing: Allow-list\n\t\tif (matchesKey(data, Key.ctrl(\"a\")) || (this.searchInput.getValue() === \"\" && data === \"a\")) {\n\t\t\tthis.framingByKind.set(this.getCurrentKind().kind, \"allow\");\n\t\t\tthis.isDirty = true;\n\t\t\tthis.kindHeaderText.setText(this.getKindHeaderText());\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Switch framing: Block-list\n\t\tif (matchesKey(data, Key.ctrl(\"b\")) || (this.searchInput.getValue() === \"\" && data === \"b\")) {\n\t\t\tthis.framingByKind.set(this.getCurrentKind().kind, \"block\");\n\t\t\tthis.isDirty = true;\n\t\t\tthis.kindHeaderText.setText(this.getKindHeaderText());\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\n\t\t// Edit highlighted resource\n\t\tif (data === \"e\" && this.searchInput.getValue() === \"\") {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item && !item.isMissing && item.path) {\n\t\t\t\tconst kind = this.getCurrentKind().kind;\n\t\t\t\tif (this.onEdit) {\n\t\t\t\t\tthis.onEdit(item.id, item.path, kind);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Switch scope: Scope selector\n\t\tif (matchesKey(data, Key.ctrl(\"o\")) || (this.searchInput.getValue() === \"\" && data === \"s\")) {\n\t\t\tif (this.onScopeChange) {\n\t\t\t\tthis.onScopeChange();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Toggle on space/enter\n\t\tif (kb.matches(data, \"tui.select.confirm\")) {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item) {\n\t\t\t\tconst enabledSet = this.getCurrentEnabledSet();\n\t\t\t\tif (enabledSet.has(item.id)) {\n\t\t\t\t\tenabledSet.delete(item.id);\n\t\t\t\t} else {\n\t\t\t\t\tenabledSet.add(item.id);\n\t\t\t\t}\n\t\t\t\tthis.isDirty = true;\n\t\t\t\tthis.refresh();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Save/persist to settings\n\t\tif (kb.matches(data, \"app.models.save\")) {\n\t\t\tthis.persistChanges();\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+C - clear search or cancel if empty\n\t\tif (matchesKey(data, Key.ctrl(\"c\"))) {\n\t\t\tif (this.searchInput.getValue()) {\n\t\t\t\tthis.searchInput.setValue(\"\");\n\t\t\t\tthis.refresh();\n\t\t\t} else {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Escape / Ctrl+Q - cancel (unconditional)\n\t\tif (\n\t\t\tkb.matches(data, \"app.interrupt\") ||\n\t\t\tmatchesKey(data, Key.escape) ||\n\t\t\tmatchesKey(data, Key.esc) ||\n\t\t\tmatchesKey(data, \"ctrl+q\")\n\t\t) {\n\t\t\tthis.onCancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass everything else to search input\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.refresh();\n\t}\n\n\tprivate persistChanges(): void {\n\t\tconst resources: ResourceProfileSettings = {};\n\t\tfor (const kind of this.kinds) {\n\t\t\tconst enabledSet = this.enabledByKind.get(kind.kind)!;\n\t\t\tconst framing = this.framingByKind.get(kind.kind)!;\n\t\t\t// Persist against the same widened universe used at decode time: profile-referenced\n\t\t\t// ids that are not currently discoverable must survive a save untouched.\n\t\t\tconst missingSet = this.missingIdsByKind.get(kind.kind) ?? new Set<string>();\n\t\t\tconst allIds = [...new Set([...kind.items.map((item) => item.id), ...missingSet])];\n\t\t\t// Pass the original filter so an all-enabled save preserves an enumerated grant (and only\n\t\t\t// keeps a wildcard when the grant was already a wildcard) — never widening it silently.\n\t\t\tconst encoded = encodeResourceSelectionWithFraming(enabledSet, allIds, framing, {\n\t\t\t\toriginalFilter: this.originalFilterByKind.get(kind.kind),\n\t\t\t});\n\t\t\tif (encoded !== undefined) {\n\t\t\t\tresources[kind.kind] = encoded;\n\t\t\t}\n\t\t}\n\t\tthis.onSave(resources);\n\t\tthis.isDirty = false;\n\t\tthis.footerText.setText(this.getFooterText());\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
|
|
@@ -2,7 +2,8 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { isAbsolute, resolve } from "node:path";
|
|
4
4
|
import { Container, fuzzyFilter, getKeybindings, Input, Key, matchesKey, Spacer, Text, } from "@caupulican/pi-tui";
|
|
5
|
-
import { decodeResourceSelection, detectResourceFraming, encodeResourceSelectionWithFraming, } from "../../../core/profile-resource-selection.js";
|
|
5
|
+
import { decodeResourceSelection, detectResourceFraming, encodeResourceSelectionWithFraming, remapResourceSelectionFilter, } from "../../../core/profile-resource-selection.js";
|
|
6
|
+
import { matchesResourceProfilePattern, } from "../../../core/settings-manager.js";
|
|
6
7
|
import { theme } from "../theme/theme.js";
|
|
7
8
|
import { DynamicBorder } from "./dynamic-border.js";
|
|
8
9
|
import { keyText } from "./keybinding-hints.js";
|
|
@@ -146,42 +147,17 @@ export class ProfileResourceEditorComponent extends Container {
|
|
|
146
147
|
// Initialize enabled, framing, and missing sets for each kind
|
|
147
148
|
for (const kind of this.kinds) {
|
|
148
149
|
const filter = options.initialResources[kind.kind];
|
|
150
|
+
const remapped = remapResourceSelectionFilter(filter, kind.items, (item, pattern) => {
|
|
151
|
+
if (item.id === pattern)
|
|
152
|
+
return true;
|
|
153
|
+
return item.path ? matchesResourceProfilePattern(item.path, [pattern], this.cwd) : false;
|
|
154
|
+
});
|
|
155
|
+
this.missingIdsByKind.set(kind.kind, remapped.missingIds);
|
|
156
|
+
// Decode against concrete collision-safe ids plus genuinely missing profile entries. Legacy
|
|
157
|
+
// basename/parent/glob patterns are expanded to the resources they currently match, so saving
|
|
158
|
+
// migrates ambiguous patterns such as "index.ts" instead of retaining a multi-extension toggle.
|
|
149
159
|
const allIds = kind.items.map((item) => item.id);
|
|
150
|
-
|
|
151
|
-
const mentionedIds = new Set();
|
|
152
|
-
if (filter) {
|
|
153
|
-
if (filter.allow) {
|
|
154
|
-
for (const id of filter.allow) {
|
|
155
|
-
// "*" is a grant-all framing marker, not a resource id — the block loop below
|
|
156
|
-
// already excludes it; the allow loop must too, or it leaks into missingSet as
|
|
157
|
-
// a fake "missing item" and rides along as a phantom enabled member through
|
|
158
|
-
// decode/encode.
|
|
159
|
-
if (id !== "*") {
|
|
160
|
-
mentionedIds.add(id);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (filter.block) {
|
|
165
|
-
for (const id of filter.block) {
|
|
166
|
-
if (id !== "*") {
|
|
167
|
-
mentionedIds.add(id);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const availableIds = new Set(allIds);
|
|
173
|
-
const missingSet = new Set();
|
|
174
|
-
for (const id of mentionedIds) {
|
|
175
|
-
if (!availableIds.has(id)) {
|
|
176
|
-
missingSet.add(id);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
this.missingIdsByKind.set(kind.kind, missingSet);
|
|
180
|
-
// Decode against the DISCOVERABLE universe plus every id the profile itself mentions:
|
|
181
|
-
// decoding only against the discoverable set silently drops grants for anything the
|
|
182
|
-
// current environment cannot see (offline external root, or a collapsed universe),
|
|
183
|
-
// and the next save would then corrupt the profile.
|
|
184
|
-
const enabledSet = decodeResourceSelection(filter, [...allIds, ...missingSet]);
|
|
160
|
+
const enabledSet = decodeResourceSelection(remapped.filter, [...allIds, ...remapped.missingIds]);
|
|
185
161
|
this.enabledByKind.set(kind.kind, enabledSet);
|
|
186
162
|
this.framingByKind.set(kind.kind, detectResourceFraming(filter));
|
|
187
163
|
this.originalFilterByKind.set(kind.kind, filter);
|
|
@@ -247,6 +223,7 @@ export class ProfileResourceEditorComponent extends Container {
|
|
|
247
223
|
}
|
|
248
224
|
items.push({
|
|
249
225
|
id: availableItem.id,
|
|
226
|
+
label: availableItem.label ?? availableItem.id,
|
|
250
227
|
enabled: isEnabled,
|
|
251
228
|
path: availableItem.path,
|
|
252
229
|
description: desc,
|
|
@@ -260,6 +237,7 @@ export class ProfileResourceEditorComponent extends Container {
|
|
|
260
237
|
const isEnabled = enabledSet.has(missingId);
|
|
261
238
|
items.push({
|
|
262
239
|
id: missingId,
|
|
240
|
+
label: missingId,
|
|
263
241
|
enabled: isEnabled,
|
|
264
242
|
isMissing: true,
|
|
265
243
|
description: "Referenced in profile but missing from available resources.",
|
|
@@ -297,7 +275,9 @@ export class ProfileResourceEditorComponent extends Container {
|
|
|
297
275
|
refresh() {
|
|
298
276
|
const query = this.searchInput.getValue();
|
|
299
277
|
const items = this.buildItems();
|
|
300
|
-
this.filteredItems = query
|
|
278
|
+
this.filteredItems = query
|
|
279
|
+
? fuzzyFilter(items, query, (item) => `${item.label} ${item.id} ${item.description ?? ""}`)
|
|
280
|
+
: items;
|
|
301
281
|
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));
|
|
302
282
|
this.updateList();
|
|
303
283
|
this.footerText.setText(this.getFooterText());
|
|
@@ -323,9 +303,9 @@ export class ProfileResourceEditorComponent extends Container {
|
|
|
323
303
|
const item = this.filteredItems[i];
|
|
324
304
|
const isSelected = i === this.selectedIndex;
|
|
325
305
|
const prefix = isSelected ? theme.fg("accent", "→ ") : " ";
|
|
326
|
-
let resourceText = isSelected ? theme.fg("accent", item.
|
|
306
|
+
let resourceText = isSelected ? theme.fg("accent", item.label) : item.label;
|
|
327
307
|
if (item.isMissing) {
|
|
328
|
-
resourceText = theme.fg("muted", `${item.
|
|
308
|
+
resourceText = theme.fg("muted", `${item.label} [missing]`);
|
|
329
309
|
}
|
|
330
310
|
else if (item.sourceLabel) {
|
|
331
311
|
const labelColor = item.sourceLabel === "catalog"
|
|
@@ -350,6 +330,9 @@ export class ProfileResourceEditorComponent extends Container {
|
|
|
350
330
|
if (selectedItem?.description) {
|
|
351
331
|
this.descriptionText.setText(theme.fg("muted", ` Description: ${selectedItem.description}`));
|
|
352
332
|
}
|
|
333
|
+
else if (selectedItem && selectedItem.label !== selectedItem.id) {
|
|
334
|
+
this.descriptionText.setText(theme.fg("muted", ` Resource: ${selectedItem.id}`));
|
|
335
|
+
}
|
|
353
336
|
else {
|
|
354
337
|
this.descriptionText.setText(theme.fg("muted", " No description available"));
|
|
355
338
|
}
|