@ai-agent-forge/agent-forge 0.86.7 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/README.md +10 -11
- package/dist/artifacts/artifact-store.d.ts.map +1 -1
- package/dist/artifacts/artifact-store.js +9 -5
- package/dist/artifacts/artifact-store.js.map +1 -1
- package/dist/bundle/.build-manifest.json +430 -413
- package/dist/bundle/chunks/{chunk-A7GUOX2H.js → chunk-6ZKDNZHA.js} +5 -1
- package/dist/bundle/chunks/chunk-HLJEVRWP.js +13 -0
- package/dist/bundle/chunks/chunk-YGHQME4V.js +1456 -0
- package/dist/bundle/chunks/image-resize-worker.js +12 -1
- package/dist/bundle/chunks/{virtual-modules-3IZ4MYSQ.js → virtual-modules-KZ5DRH7Q.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/client.js +1 -1
- package/dist/bundle/index.js +2 -2
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +231 -185
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/auth-check.d.ts.map +1 -1
- package/dist/cli/auth-check.js +17 -4
- package/dist/cli/auth-check.js.map +1 -1
- package/dist/cli/auth-command.d.ts.map +1 -1
- package/dist/cli/auth-command.js +15 -9
- package/dist/cli/auth-command.js.map +1 -1
- package/dist/cli/credential-print.d.ts.map +1 -1
- package/dist/cli/credential-print.js +14 -7
- package/dist/cli/credential-print.js.map +1 -1
- package/dist/cli/experimental/command.d.ts.map +1 -1
- package/dist/cli/experimental/command.js +6 -5
- package/dist/cli/experimental/command.js.map +1 -1
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +9 -8
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/plugin-manager-controller.d.ts.map +1 -1
- package/dist/cli/plugin-manager-controller.js +2 -1
- package/dist/cli/plugin-manager-controller.js.map +1 -1
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +8 -5
- package/dist/cli/serve.js.map +1 -1
- package/dist/client/remote-session.d.ts.map +1 -1
- package/dist/client/remote-session.js +16 -9
- package/dist/client/remote-session.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-capability-contract.d.ts +0 -7
- package/dist/core/agent-capability-contract.d.ts.map +1 -1
- package/dist/core/agent-capability-contract.js +72 -68
- package/dist/core/agent-capability-contract.js.map +1 -1
- package/dist/core/agent-session-bash.d.ts.map +1 -1
- package/dist/core/agent-session-bash.js +5 -4
- package/dist/core/agent-session-bash.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts +6 -10
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +35 -133
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-context.d.ts.map +1 -1
- package/dist/core/agent-session-context.js +18 -13
- package/dist/core/agent-session-context.js.map +1 -1
- package/dist/core/agent-session-internal.d.ts +0 -6
- package/dist/core/agent-session-internal.d.ts.map +1 -1
- package/dist/core/agent-session-internal.js.map +1 -1
- package/dist/core/agent-session-model.d.ts.map +1 -1
- package/dist/core/agent-session-model.js +2 -1
- package/dist/core/agent-session-model.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +3 -3
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +7 -6
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-tools.d.ts.map +1 -1
- package/dist/core/agent-session-tools.js +3 -2
- package/dist/core/agent-session-tools.js.map +1 -1
- package/dist/core/agent-session.d.ts +40 -22
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +147 -67
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-guidance.d.ts.map +1 -1
- package/dist/core/auth-guidance.js +11 -5
- package/dist/core/auth-guidance.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +9 -6
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +2 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/embedded-sdk.d.ts.map +1 -1
- package/dist/core/embedded-sdk.js +10 -6
- package/dist/core/embedded-sdk.js.map +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +9 -4
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/extensions/agent-composition.d.ts.map +1 -1
- package/dist/core/extensions/agent-composition.js +26 -19
- package/dist/core/extensions/agent-composition.js.map +1 -1
- package/dist/core/extensions/bootstrap-agent-loop.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-agent-loop.js +3 -2
- package/dist/core/extensions/bootstrap-agent-loop.js.map +1 -1
- package/dist/core/extensions/bootstrap-compaction-policy.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-compaction-policy.js +0 -34
- package/dist/core/extensions/bootstrap-compaction-policy.js.map +1 -1
- package/dist/core/extensions/bootstrap-lifecycle.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-lifecycle.js +20 -17
- package/dist/core/extensions/bootstrap-lifecycle.js.map +1 -1
- package/dist/core/extensions/bootstrap-overflow-policy.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-overflow-policy.js +2 -1
- package/dist/core/extensions/bootstrap-overflow-policy.js.map +1 -1
- package/dist/core/extensions/bootstrap-retry-policy.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-retry-policy.js +4 -3
- package/dist/core/extensions/bootstrap-retry-policy.js.map +1 -1
- package/dist/core/extensions/bootstrap-skills.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-skills.js +7 -6
- package/dist/core/extensions/bootstrap-skills.js.map +1 -1
- package/dist/core/extensions/bootstrap-tools.d.ts.map +1 -1
- package/dist/core/extensions/bootstrap-tools.js +2 -1
- package/dist/core/extensions/bootstrap-tools.js.map +1 -1
- package/dist/core/extensions/capability-plugin.d.ts.map +1 -1
- package/dist/core/extensions/capability-plugin.js +37 -13
- package/dist/core/extensions/capability-plugin.js.map +1 -1
- package/dist/core/extensions/capability-runtime-events.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-events.js +17 -13
- package/dist/core/extensions/capability-runtime-events.js.map +1 -1
- package/dist/core/extensions/capability-runtime-guards.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-guards.js +16 -15
- package/dist/core/extensions/capability-runtime-guards.js.map +1 -1
- package/dist/core/extensions/capability-runtime-manifest.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-manifest.js +52 -30
- package/dist/core/extensions/capability-runtime-manifest.js.map +1 -1
- package/dist/core/extensions/capability-runtime-validation.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime-validation.js +66 -65
- package/dist/core/extensions/capability-runtime-validation.js.map +1 -1
- package/dist/core/extensions/capability-runtime.d.ts.map +1 -1
- package/dist/core/extensions/capability-runtime.js +147 -127
- package/dist/core/extensions/capability-runtime.js.map +1 -1
- package/dist/core/extensions/compaction-memory-failure-mode.d.ts.map +1 -1
- package/dist/core/extensions/compaction-memory-failure-mode.js +3 -2
- package/dist/core/extensions/compaction-memory-failure-mode.js.map +1 -1
- package/dist/core/extensions/context-strategy-binding.d.ts.map +1 -1
- package/dist/core/extensions/context-strategy-binding.js +7 -4
- package/dist/core/extensions/context-strategy-binding.js.map +1 -1
- package/dist/core/extensions/lifecycle-catalog.d.ts.map +1 -1
- package/dist/core/extensions/lifecycle-catalog.js +290 -279
- package/dist/core/extensions/lifecycle-catalog.js.map +1 -1
- package/dist/core/extensions/lifecycle-delivery-queue.d.ts.map +1 -1
- package/dist/core/extensions/lifecycle-delivery-queue.js +3 -2
- package/dist/core/extensions/lifecycle-delivery-queue.js.map +1 -1
- package/dist/core/extensions/provider-failover.d.ts +7 -3
- package/dist/core/extensions/provider-failover.d.ts.map +1 -1
- package/dist/core/extensions/provider-failover.js +9 -8
- package/dist/core/extensions/provider-failover.js.map +1 -1
- package/dist/core/facade/coding-agent-facade.d.ts +6 -6
- package/dist/core/facade/coding-agent-facade.d.ts.map +1 -1
- package/dist/core/facade/coding-agent-facade.js +2 -3
- package/dist/core/facade/coding-agent-facade.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -1
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/i18n/index.d.ts +14 -0
- package/dist/core/i18n/index.d.ts.map +1 -0
- package/dist/core/i18n/index.js +67 -0
- package/dist/core/i18n/index.js.map +1 -0
- package/dist/core/i18n/locales/en/example.d.ts +7 -0
- package/dist/core/i18n/locales/en/example.d.ts.map +1 -0
- package/dist/core/i18n/locales/en/example.js +7 -0
- package/dist/core/i18n/locales/en/example.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/cli.d.ts +8 -0
- package/dist/core/i18n/locales/zh-cn/cli.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/cli.js +229 -0
- package/dist/core/i18n/locales/zh-cn/cli.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/common.d.ts +6 -0
- package/dist/core/i18n/locales/zh-cn/common.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/common.js +6 -0
- package/dist/core/i18n/locales/zh-cn/common.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-core.d.ts +13 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-core.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-core.js +173 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-core.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-extensions.d.ts +12 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-extensions.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-extensions.js +582 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-extensions.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-final.d.ts +9 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-final.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-final.js +55 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-final.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.d.ts +11 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.js +455 -0
- package/dist/core/i18n/locales/zh-cn/core-contract-plugins.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.d.ts +9 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.js +191 -0
- package/dist/core/i18n/locales/zh-cn/core-errors.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/example.d.ts +7 -0
- package/dist/core/i18n/locales/zh-cn/example.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/example.js +10 -0
- package/dist/core/i18n/locales/zh-cn/example.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/index.d.ts +2 -0
- package/dist/core/i18n/locales/zh-cn/index.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/index.js +41 -0
- package/dist/core/i18n/locales/zh-cn/index.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts +10 -0
- package/dist/core/i18n/locales/zh-cn/interactive-mode.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js +267 -0
- package/dist/core/i18n/locales/zh-cn/interactive-mode.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/keybindings.d.ts +11 -0
- package/dist/core/i18n/locales/zh-cn/keybindings.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/keybindings.js +67 -0
- package/dist/core/i18n/locales/zh-cn/keybindings.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/slash-commands.d.ts +7 -0
- package/dist/core/i18n/locales/zh-cn/slash-commands.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/slash-commands.js +39 -0
- package/dist/core/i18n/locales/zh-cn/slash-commands.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-builtin.d.ts +10 -0
- package/dist/core/i18n/locales/zh-cn/tui-builtin.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-builtin.js +15 -0
- package/dist/core/i18n/locales/zh-cn/tui-builtin.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.d.ts +13 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.js +102 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-misc.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.d.ts +12 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js +286 -0
- package/dist/core/i18n/locales/zh-cn/tui-components-selectors.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.d.ts +9 -0
- package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.js +79 -0
- package/dist/core/i18n/locales/zh-cn/tui-interactive-misc.js.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-self-owned.d.ts +8 -0
- package/dist/core/i18n/locales/zh-cn/tui-self-owned.d.ts.map +1 -0
- package/dist/core/i18n/locales/zh-cn/tui-self-owned.js +72 -0
- package/dist/core/i18n/locales/zh-cn/tui-self-owned.js.map +1 -0
- package/dist/core/keybindings.d.ts +15 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +18 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +5 -1
- package/dist/core/logger.js.map +1 -1
- package/dist/core/model-config.d.ts +0 -1
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +9 -1
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +11 -7
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-install-host.d.ts.map +1 -1
- package/dist/core/package-install-host.js +4 -3
- package/dist/core/package-install-host.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +15 -10
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-resource-collection.d.ts.map +1 -1
- package/dist/core/package-resource-collection.js +2 -1
- package/dist/core/package-resource-collection.js.map +1 -1
- package/dist/core/package-source.d.ts.map +1 -1
- package/dist/core/package-source.js +3 -2
- package/dist/core/package-source.js.map +1 -1
- package/dist/core/provider-composer.d.ts +2 -2
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +29 -21
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +6 -2
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +15 -5
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/sdk.d.ts +27 -5
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +80 -55
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-cwd.d.ts.map +1 -1
- package/dist/core/session-cwd.js +5 -2
- package/dist/core/session-cwd.js.map +1 -1
- package/dist/core/session-export.d.ts.map +1 -1
- package/dist/core/session-export.js +2 -1
- package/dist/core/session-export.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +39 -29
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-storage.d.ts.map +1 -1
- package/dist/core/session-storage.js +3 -3
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/settings-manager.d.ts +20 -20
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +47 -59
- 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 +53 -32
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +25 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +11 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +9 -4
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +4 -3
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +3 -2
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +2 -1
- 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 +6 -2
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/durable/durable-service.d.ts.map +1 -1
- package/dist/durable/durable-service.js +25 -18
- package/dist/durable/durable-service.js.map +1 -1
- package/dist/host/checkpoint-commit.d.ts.map +1 -1
- package/dist/host/checkpoint-commit.js +2 -1
- package/dist/host/checkpoint-commit.js.map +1 -1
- package/dist/host/manual-compaction.d.ts.map +1 -1
- package/dist/host/manual-compaction.js +2 -1
- package/dist/host/manual-compaction.js.map +1 -1
- package/dist/host/output-projection.d.ts +0 -13
- package/dist/host/output-projection.d.ts.map +1 -1
- package/dist/host/output-projection.js +16 -2
- package/dist/host/output-projection.js.map +1 -1
- package/dist/host/summary-retry.d.ts.map +1 -1
- package/dist/host/summary-retry.js +3 -2
- package/dist/host/summary-retry.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +97 -60
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/agents-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/agents-view.js +18 -15
- package/dist/modes/interactive/components/agents-view.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +6 -5
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +12 -7
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +2 -1
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +6 -5
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +9 -5
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +18 -15
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-entry.js +2 -1
- package/dist/modes/interactive/components/custom-entry.js.map +1 -1
- package/dist/modes/interactive/components/dialog-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/dialog-editor.js +5 -4
- package/dist/modes/interactive/components/dialog-editor.js.map +1 -1
- package/dist/modes/interactive/components/dialog-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/dialog-input.js +2 -1
- package/dist/modes/interactive/components/dialog-input.js.map +1 -1
- package/dist/modes/interactive/components/dialog-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/dialog-selector.js +4 -3
- package/dist/modes/interactive/components/dialog-selector.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +11 -10
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +0 -2
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -10
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +3 -2
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +12 -11
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/mermaid.d.ts.map +1 -1
- package/dist/modes/interactive/components/mermaid.js +6 -2
- package/dist/modes/interactive/components/mermaid.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +28 -16
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +10 -9
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/plugin-manager-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/plugin-manager-entry.js +22 -13
- package/dist/modes/interactive/components/plugin-manager-entry.js.map +1 -1
- package/dist/modes/interactive/components/plugin-selection-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/plugin-selection-entry.js +27 -12
- package/dist/modes/interactive/components/plugin-selection-entry.js.map +1 -1
- package/dist/modes/interactive/components/queue-manager-selector.d.ts +60 -0
- package/dist/modes/interactive/components/queue-manager-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/queue-manager-selector.js +166 -0
- package/dist/modes/interactive/components/queue-manager-selector.js.map +1 -0
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +16 -14
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +43 -31
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -6
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +115 -120
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.js +5 -4
- package/dist/modes/interactive/components/settings-submenu.js.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.js +3 -2
- package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +4 -3
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-indicator.js +14 -5
- package/dist/modes/interactive/components/status-indicator.js.map +1 -1
- package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/theme-selector.js +2 -1
- package/dist/modes/interactive/components/theme-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +11 -4
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +8 -3
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-list.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-list.js +7 -6
- package/dist/modes/interactive/components/tree-list.js.map +1 -1
- package/dist/modes/interactive/components/tree-render.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-render.js +28 -23
- package/dist/modes/interactive/components/tree-render.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +9 -7
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +5 -4
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/workflow-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/workflow-view.js +36 -21
- package/dist/modes/interactive/components/workflow-view.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +2 -1
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-auth.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-auth.js +71 -40
- package/dist/modes/interactive/interactive-auth.js.map +1 -1
- package/dist/modes/interactive/interactive-commands.d.ts +1 -0
- package/dist/modes/interactive/interactive-commands.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-commands.js +3 -0
- package/dist/modes/interactive/interactive-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-dialog-ui.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-dialog-ui.js +5 -5
- package/dist/modes/interactive/interactive-dialog-ui.js.map +1 -1
- package/dist/modes/interactive/interactive-format.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-format.js +34 -18
- package/dist/modes/interactive/interactive-format.js.map +1 -1
- package/dist/modes/interactive/interactive-helpers.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-helpers.js +7 -2
- package/dist/modes/interactive/interactive-helpers.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +350 -279
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/interactive-selectors.d.ts +9 -0
- package/dist/modes/interactive/interactive-selectors.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-selectors.js +79 -25
- package/dist/modes/interactive/interactive-selectors.js.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +5 -1
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +26 -23
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +3 -2
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +3 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +0 -8
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +3 -14
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +0 -9
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +0 -19
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +28 -13
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/plugins/ecosystem/catalog.d.ts.map +1 -1
- package/dist/plugins/ecosystem/catalog.js +3 -2
- package/dist/plugins/ecosystem/catalog.js.map +1 -1
- package/dist/plugins/ecosystem/cli.d.ts.map +1 -1
- package/dist/plugins/ecosystem/cli.js +2 -1
- package/dist/plugins/ecosystem/cli.js.map +1 -1
- package/dist/plugins/ecosystem/default-plugin-market.d.ts.map +1 -1
- package/dist/plugins/ecosystem/default-plugin-market.js +3 -2
- package/dist/plugins/ecosystem/default-plugin-market.js.map +1 -1
- package/dist/plugins/ecosystem/distribution.d.ts.map +1 -1
- package/dist/plugins/ecosystem/distribution.js +2 -1
- package/dist/plugins/ecosystem/distribution.js.map +1 -1
- package/dist/plugins/ecosystem/installer.d.ts.map +1 -1
- package/dist/plugins/ecosystem/installer.js +3 -2
- package/dist/plugins/ecosystem/installer.js.map +1 -1
- package/dist/plugins/ecosystem/node-disk-registry-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-disk-registry-source.js +20 -17
- package/dist/plugins/ecosystem/node-disk-registry-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-distribution-host.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-distribution-host.js +35 -34
- package/dist/plugins/ecosystem/node-distribution-host.js.map +1 -1
- package/dist/plugins/ecosystem/node-npm-registry-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-npm-registry-source.js +35 -34
- package/dist/plugins/ecosystem/node-npm-registry-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-host.js +9 -8
- package/dist/plugins/ecosystem/node-package-install-host.js.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-persistence.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-package-install-persistence.js +4 -3
- package/dist/plugins/ecosystem/node-package-install-persistence.js.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts +9 -2
- package/dist/plugins/ecosystem/node-plugin-bootstrap.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js +44 -5
- package/dist/plugins/ecosystem/node-plugin-bootstrap.js.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-trust-root.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-plugin-trust-root.js +10 -9
- package/dist/plugins/ecosystem/node-plugin-trust-root.js.map +1 -1
- package/dist/plugins/ecosystem/node-registry-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-registry-source.js +31 -30
- package/dist/plugins/ecosystem/node-registry-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-release-package-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-release-package-source.js +30 -29
- package/dist/plugins/ecosystem/node-release-package-source.js.map +1 -1
- package/dist/plugins/ecosystem/node-tar-writer.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-tar-writer.js +11 -10
- package/dist/plugins/ecosystem/node-tar-writer.js.map +1 -1
- package/dist/plugins/ecosystem/node-url-package-source.d.ts.map +1 -1
- package/dist/plugins/ecosystem/node-url-package-source.js +21 -20
- package/dist/plugins/ecosystem/node-url-package-source.js.map +1 -1
- package/dist/plugins/ecosystem/plugin-acquisition.d.ts.map +1 -1
- package/dist/plugins/ecosystem/plugin-acquisition.js +3 -2
- package/dist/plugins/ecosystem/plugin-acquisition.js.map +1 -1
- package/dist/plugins/ecosystem/plugin-bootstrap.d.ts +9 -1
- package/dist/plugins/ecosystem/plugin-bootstrap.d.ts.map +1 -1
- package/dist/plugins/ecosystem/plugin-bootstrap.js +5 -0
- package/dist/plugins/ecosystem/plugin-bootstrap.js.map +1 -1
- package/dist/plugins/ecosystem/registry-cli-adapter.d.ts.map +1 -1
- package/dist/plugins/ecosystem/registry-cli-adapter.js +2 -1
- package/dist/plugins/ecosystem/registry-cli-adapter.js.map +1 -1
- package/dist/plugins/ecosystem/registry.d.ts.map +1 -1
- package/dist/plugins/ecosystem/registry.js +35 -32
- package/dist/plugins/ecosystem/registry.js.map +1 -1
- package/dist/plugins/ecosystem/resolver.d.ts.map +1 -1
- package/dist/plugins/ecosystem/resolver.js +3 -2
- package/dist/plugins/ecosystem/resolver.js.map +1 -1
- package/dist/plugins/ecosystem/selection-store.d.ts.map +1 -1
- package/dist/plugins/ecosystem/selection-store.js +11 -10
- package/dist/plugins/ecosystem/selection-store.js.map +1 -1
- package/dist/plugins/ecosystem/selection.d.ts.map +1 -1
- package/dist/plugins/ecosystem/selection.js +8 -7
- package/dist/plugins/ecosystem/selection.js.map +1 -1
- package/dist/plugins/ecosystem/state.d.ts.map +1 -1
- package/dist/plugins/ecosystem/state.js +24 -23
- package/dist/plugins/ecosystem/state.js.map +1 -1
- package/dist/plugins/ecosystem/updates.d.ts.map +1 -1
- package/dist/plugins/ecosystem/updates.js +5 -4
- package/dist/plugins/ecosystem/updates.js.map +1 -1
- package/dist/plugins/mcp-http.d.ts +0 -17
- package/dist/plugins/mcp-http.d.ts.map +1 -1
- package/dist/plugins/mcp-http.js +30 -7
- package/dist/plugins/mcp-http.js.map +1 -1
- package/dist/plugins/plugin-supervisor.d.ts.map +1 -1
- package/dist/plugins/plugin-supervisor.js +19 -1
- package/dist/plugins/plugin-supervisor.js.map +1 -1
- package/dist/profile/first-roles.d.ts.map +1 -1
- package/dist/profile/first-roles.js +10 -1
- package/dist/profile/first-roles.js.map +1 -1
- package/dist/profile/goal-plugin.d.ts.map +1 -1
- package/dist/profile/goal-plugin.js +24 -5
- package/dist/profile/goal-plugin.js.map +1 -1
- package/dist/profile/identity.d.ts.map +1 -1
- package/dist/profile/identity.js +7 -6
- package/dist/profile/identity.js.map +1 -1
- package/dist/profile/lock.d.ts.map +1 -1
- package/dist/profile/lock.js +15 -1
- package/dist/profile/lock.js.map +1 -1
- package/dist/profile/plan-plugin.d.ts.map +1 -1
- package/dist/profile/plan-plugin.js +26 -13
- package/dist/profile/plan-plugin.js.map +1 -1
- package/dist/profile/plugin-profiles.d.ts.map +1 -1
- package/dist/profile/plugin-profiles.js +2 -1
- package/dist/profile/plugin-profiles.js.map +1 -1
- package/dist/profile/policy-manifest.d.ts.map +1 -1
- package/dist/profile/policy-manifest.js +5 -4
- package/dist/profile/policy-manifest.js.map +1 -1
- package/dist/profile/profile-registry.d.ts +0 -12
- package/dist/profile/profile-registry.d.ts.map +1 -1
- package/dist/profile/profile-registry.js +14 -1
- package/dist/profile/profile-registry.js.map +1 -1
- package/dist/profile/role-catalog.d.ts +0 -1
- package/dist/profile/role-catalog.d.ts.map +1 -1
- package/dist/profile/role-catalog.js +17 -15
- package/dist/profile/role-catalog.js.map +1 -1
- package/dist/profile/role-orchestrator.d.ts.map +1 -1
- package/dist/profile/role-orchestrator.js +8 -7
- package/dist/profile/role-orchestrator.js.map +1 -1
- package/dist/profile/role-runner.d.ts.map +1 -1
- package/dist/profile/role-runner.js +3 -2
- package/dist/profile/role-runner.js.map +1 -1
- package/dist/profile/root-facade.d.ts.map +1 -1
- package/dist/profile/root-facade.js +2 -1
- package/dist/profile/root-facade.js.map +1 -1
- package/dist/profile/skill-discovery-wiring.d.ts.map +1 -1
- package/dist/profile/skill-discovery-wiring.js +7 -2
- package/dist/profile/skill-discovery-wiring.js.map +1 -1
- package/dist/profile/state-store.d.ts.map +1 -1
- package/dist/profile/state-store.js +13 -9
- package/dist/profile/state-store.js.map +1 -1
- package/dist/profile/todo-plugin.d.ts.map +1 -1
- package/dist/profile/todo-plugin.js +9 -4
- package/dist/profile/todo-plugin.js.map +1 -1
- package/dist/profiles/builtin-suites.d.ts +13 -6
- package/dist/profiles/builtin-suites.d.ts.map +1 -1
- package/dist/profiles/builtin-suites.js +15 -6
- package/dist/profiles/builtin-suites.js.map +1 -1
- package/dist/profiles/suite-loader.d.ts +25 -1
- package/dist/profiles/suite-loader.d.ts.map +1 -1
- package/dist/profiles/suite-loader.js +34 -9
- package/dist/profiles/suite-loader.js.map +1 -1
- package/dist/remote/server-entry.d.ts.map +1 -1
- package/dist/remote/server-entry.js +3 -2
- package/dist/remote/server-entry.js.map +1 -1
- package/dist/remote/session-source.d.ts.map +1 -1
- package/dist/remote/session-source.js +8 -4
- package/dist/remote/session-source.js.map +1 -1
- package/dist/share/session-share.d.ts.map +1 -1
- package/dist/share/session-share.js +14 -2
- package/dist/share/session-share.js.map +1 -1
- package/dist/testing/plugin-testkit.d.ts.map +1 -1
- package/dist/testing/plugin-testkit.js +4 -3
- package/dist/testing/plugin-testkit.js.map +1 -1
- package/dist/testing/runtime-delivery-queue.d.ts.map +1 -1
- package/dist/testing/runtime-delivery-queue.js +2 -1
- package/dist/testing/runtime-delivery-queue.js.map +1 -1
- package/dist/testing/session-plugin-testkit.d.ts.map +1 -1
- package/dist/testing/session-plugin-testkit.js +10 -7
- package/dist/testing/session-plugin-testkit.js.map +1 -1
- package/dist/utils/ansi.d.ts.map +1 -1
- package/dist/utils/ansi.js +2 -1
- package/dist/utils/ansi.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +7 -6
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/image-resize-worker.d.ts.map +1 -1
- package/dist/utils/image-resize-worker.js +3 -2
- package/dist/utils/image-resize-worker.js.map +1 -1
- package/dist/utils/photon.d.ts +2 -2
- package/dist/utils/photon.d.ts.map +1 -1
- package/dist/utils/photon.js +2 -2
- package/dist/utils/photon.js.map +1 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +9 -8
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +23 -13
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +5 -1
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +1 -3
- package/docs/docs.json +4 -0
- package/docs/index.md +1 -0
- package/docs/keybindings.md +3 -0
- package/docs/mcp.md +103 -0
- package/docs/models.md +14 -22
- package/docs/providers.md +0 -1
- package/docs/quickstart.md +1 -1
- package/docs/rpc.md +1 -26
- package/docs/sdk.md +4 -6
- package/docs/settings.md +10 -12
- package/docs/skills.md +1 -9
- package/docs/terminal-setup.md +2 -3
- package/docs/usage.md +7 -6
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/extensions/with-deps/plugin.json +1 -1
- package/examples/sdk/10-settings.ts +2 -4
- package/examples/sdk/12-full-control.ts +1 -2
- package/package.json +1 -3
- package/dist/bundle/chunks/chunk-EALJ5DDX.js +0 -1452
- package/dist/core/extensions/microcompact.d.ts +0 -80
- package/dist/core/extensions/microcompact.d.ts.map +0 -1
- package/dist/core/extensions/microcompact.js +0 -134
- package/dist/core/extensions/microcompact.js.map +0 -1
- package/dist/server/create-harness.d.ts +0 -26
- package/dist/server/create-harness.d.ts.map +0 -1
- package/dist/server/create-harness.js +0 -106
- package/dist/server/create-harness.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t } from "../../../core/i18n/index.js";
|
|
1
2
|
import { detectTerminalBackgroundFromEnv, detectTerminalBackgroundTheme, detectTerminalThemeForAuto, initTheme, parseAutoThemeSetting, resolveThemeSetting, setTheme, setThemeInstance, } from "./theme.js";
|
|
2
3
|
export class InteractiveThemeController {
|
|
3
4
|
ui;
|
|
@@ -90,7 +91,10 @@ export class InteractiveThemeController {
|
|
|
90
91
|
this.activeThemeName = result.success ? themeName : "dark";
|
|
91
92
|
this.notifyChanged();
|
|
92
93
|
if (!result.success && showError) {
|
|
93
|
-
this.showError(
|
|
94
|
+
this.showError(`${t('Failed to load theme "{name}": {error}', {
|
|
95
|
+
name: themeName,
|
|
96
|
+
error: result.error ?? "unknown",
|
|
97
|
+
})}\n${t("Fell back to dark theme.")}`);
|
|
94
98
|
}
|
|
95
99
|
return result;
|
|
96
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-controller.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TUI } from \"@agent-forge/tui\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport {\n\tdetectTerminalBackgroundFromEnv,\n\tdetectTerminalBackgroundTheme,\n\tdetectTerminalThemeForAuto,\n\tinitTheme,\n\tparseAutoThemeSetting,\n\tresolveThemeSetting,\n\tsetTheme,\n\tsetThemeInstance,\n\ttype TerminalTheme,\n\ttype Theme,\n} from \"./theme.ts\";\n\ntype ThemeResult = { success: boolean; error?: string };\n\nexport class InteractiveThemeController {\n\tprivate readonly ui: TUI;\n\tprivate readonly getSettingsManager: () => SettingsManager;\n\tprivate readonly showError: (message: string) => void;\n\tprivate readonly onChanged: () => void;\n\tprivate currentThemeSetting: string | undefined;\n\tprivate terminalTheme: TerminalTheme = detectTerminalBackgroundFromEnv().theme;\n\tprivate activeThemeName: string | undefined;\n\tprivate autoSyncEnabled = false;\n\tprivate terminalColorSchemeUnsubscribe: (() => void) | undefined;\n\n\tconstructor(\n\t\tui: TUI,\n\t\toptions: {\n\t\t\tgetSettingsManager: () => SettingsManager;\n\t\t\tshowError: (message: string) => void;\n\t\t\tonChanged: () => void;\n\t\t\tinitialThemeSetting?: string;\n\t\t},\n\t) {\n\t\tthis.ui = ui;\n\t\tthis.getSettingsManager = options.getSettingsManager;\n\t\tthis.showError = options.showError;\n\t\tthis.onChanged = options.onChanged;\n\t\tthis.currentThemeSetting = options.initialThemeSetting;\n\t\tthis.activeThemeName = resolveThemeSetting(\n\t\t\tthis.currentThemeSetting ?? this.getSettingsManager().getThemeSetting(),\n\t\t\tthis.terminalTheme,\n\t\t);\n\t\tinitTheme(this.activeThemeName, true);\n\t\tthis.bindTerminalColorSchemeListener();\n\t}\n\n\trebindTui(): void {\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.bindTerminalColorSchemeListener();\n\t\tthis.ui.setTerminalColorSchemeNotifications(this.autoSyncEnabled);\n\t}\n\n\tasync applyFromSettings(): Promise<void> {\n\t\tconst settingsManager = this.getSettingsManager();\n\t\tconst themeSetting = this.currentThemeSetting ?? settingsManager.getThemeSetting();\n\t\tconst autoTheme = parseAutoThemeSetting(themeSetting);\n\t\tif (autoTheme) {\n\t\t\tthis.terminalTheme = await detectTerminalThemeForAuto({ ui: this.ui, timeoutMs: 100 });\n\t\t\tthis.setAutoSync(true);\n\t\t\tthis.applyThemeName(this.terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme, true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAutoSync(false);\n\t\tif (themeSetting !== undefined) {\n\t\t\tthis.applyThemeName(themeSetting, true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst detection = await detectTerminalBackgroundTheme({ ui: this.ui, timeoutMs: 100 });\n\t\tthis.terminalTheme = detection.theme;\n\t\tif (!this.applyThemeName(detection.theme).success) return;\n\t\tif (detection.confidence === \"high\") {\n\t\t\tsettingsManager.setTheme(detection.theme);\n\t\t\tawait settingsManager.flush();\n\t\t}\n\t}\n\n\tgetThemeSelection(): string | undefined {\n\t\treturn this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting() ?? this.activeThemeName;\n\t}\n\n\tsetThemeName(themeName: string, showError = false): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tconst result = this.applyThemeName(themeName, showError);\n\t\tif (result.success) {\n\t\t\tthis.currentThemeSetting = themeName;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync setThemeSetting(themeSetting: string): Promise<void> {\n\t\tthis.currentThemeSetting = themeSetting;\n\t\tawait this.applyFromSettings();\n\t}\n\n\tsetThemeInstance(themeInstance: Theme): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tsetThemeInstance(themeInstance);\n\t\tthis.activeThemeName = \"<in-memory>\";\n\t\tthis.notifyChanged();\n\t\treturn { success: true };\n\t}\n\n\tpreview(themeSettingOrName: string): void {\n\t\tconst themeName = resolveThemeSetting(themeSettingOrName, this.terminalTheme) ?? this.activeThemeName;\n\t\tif (!themeName) return;\n\t\tif (setTheme(themeName, true).success) {\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tdisableAutoSync(): void {\n\t\tthis.setAutoSync(false);\n\t}\n\n\tgetTerminalTheme(): TerminalTheme {\n\t\treturn this.terminalTheme;\n\t}\n\n\tprivate applyThemeName(themeName: string, showError = false): ThemeResult {\n\t\tconst result = setTheme(themeName, true);\n\t\tthis.activeThemeName = result.success ? themeName : \"dark\";\n\t\tthis.notifyChanged();\n\t\tif (!result.success && showError) {\n\t\t\tthis.showError(`Failed to load theme \"${themeName}\": ${result.error}\\nFell back to dark theme.`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate notifyChanged(): void {\n\t\tthis.ui.invalidate();\n\t\tthis.onChanged();\n\t}\n\n\tprivate setAutoSync(enabled: boolean): void {\n\t\tif (this.autoSyncEnabled === enabled) return;\n\t\tthis.autoSyncEnabled = enabled;\n\t\tthis.ui.setTerminalColorSchemeNotifications(enabled);\n\t}\n\n\tprivate bindTerminalColorSchemeListener(): void {\n\t\tthis.terminalColorSchemeUnsubscribe = this.ui.onTerminalColorSchemeChange((terminalTheme) =>\n\t\t\tthis.applyTerminalTheme(terminalTheme),\n\t\t);\n\t}\n\n\tprivate applyTerminalTheme(terminalTheme: TerminalTheme): void {\n\t\tif (!this.autoSyncEnabled) return;\n\t\tthis.terminalTheme = terminalTheme;\n\t\tconst autoTheme = parseAutoThemeSetting(this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting());\n\t\t// v8 ignore next -- auto sync is only enabled for an auto theme setting, which always parses\n\t\tif (!autoTheme) {\n\t\t\tthis.setAutoSync(false);\n\t\t\treturn;\n\t\t}\n\t\tconst themeName = terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme;\n\t\tif (themeName !== this.activeThemeName) {\n\t\t\tthis.applyThemeName(themeName);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"theme-controller.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TUI } from \"@agent-forge/tui\";\nimport { t } from \"../../../core/i18n/index.ts\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport {\n\tdetectTerminalBackgroundFromEnv,\n\tdetectTerminalBackgroundTheme,\n\tdetectTerminalThemeForAuto,\n\tinitTheme,\n\tparseAutoThemeSetting,\n\tresolveThemeSetting,\n\tsetTheme,\n\tsetThemeInstance,\n\ttype TerminalTheme,\n\ttype Theme,\n} from \"./theme.ts\";\n\ntype ThemeResult = { success: boolean; error?: string };\n\nexport class InteractiveThemeController {\n\tprivate readonly ui: TUI;\n\tprivate readonly getSettingsManager: () => SettingsManager;\n\tprivate readonly showError: (message: string) => void;\n\tprivate readonly onChanged: () => void;\n\tprivate currentThemeSetting: string | undefined;\n\tprivate terminalTheme: TerminalTheme = detectTerminalBackgroundFromEnv().theme;\n\tprivate activeThemeName: string | undefined;\n\tprivate autoSyncEnabled = false;\n\tprivate terminalColorSchemeUnsubscribe: (() => void) | undefined;\n\n\tconstructor(\n\t\tui: TUI,\n\t\toptions: {\n\t\t\tgetSettingsManager: () => SettingsManager;\n\t\t\tshowError: (message: string) => void;\n\t\t\tonChanged: () => void;\n\t\t\tinitialThemeSetting?: string;\n\t\t},\n\t) {\n\t\tthis.ui = ui;\n\t\tthis.getSettingsManager = options.getSettingsManager;\n\t\tthis.showError = options.showError;\n\t\tthis.onChanged = options.onChanged;\n\t\tthis.currentThemeSetting = options.initialThemeSetting;\n\t\tthis.activeThemeName = resolveThemeSetting(\n\t\t\tthis.currentThemeSetting ?? this.getSettingsManager().getThemeSetting(),\n\t\t\tthis.terminalTheme,\n\t\t);\n\t\tinitTheme(this.activeThemeName, true);\n\t\tthis.bindTerminalColorSchemeListener();\n\t}\n\n\trebindTui(): void {\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.bindTerminalColorSchemeListener();\n\t\tthis.ui.setTerminalColorSchemeNotifications(this.autoSyncEnabled);\n\t}\n\n\tasync applyFromSettings(): Promise<void> {\n\t\tconst settingsManager = this.getSettingsManager();\n\t\tconst themeSetting = this.currentThemeSetting ?? settingsManager.getThemeSetting();\n\t\tconst autoTheme = parseAutoThemeSetting(themeSetting);\n\t\tif (autoTheme) {\n\t\t\tthis.terminalTheme = await detectTerminalThemeForAuto({ ui: this.ui, timeoutMs: 100 });\n\t\t\tthis.setAutoSync(true);\n\t\t\tthis.applyThemeName(this.terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme, true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAutoSync(false);\n\t\tif (themeSetting !== undefined) {\n\t\t\tthis.applyThemeName(themeSetting, true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst detection = await detectTerminalBackgroundTheme({ ui: this.ui, timeoutMs: 100 });\n\t\tthis.terminalTheme = detection.theme;\n\t\tif (!this.applyThemeName(detection.theme).success) return;\n\t\tif (detection.confidence === \"high\") {\n\t\t\tsettingsManager.setTheme(detection.theme);\n\t\t\tawait settingsManager.flush();\n\t\t}\n\t}\n\n\tgetThemeSelection(): string | undefined {\n\t\treturn this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting() ?? this.activeThemeName;\n\t}\n\n\tsetThemeName(themeName: string, showError = false): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tconst result = this.applyThemeName(themeName, showError);\n\t\tif (result.success) {\n\t\t\tthis.currentThemeSetting = themeName;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync setThemeSetting(themeSetting: string): Promise<void> {\n\t\tthis.currentThemeSetting = themeSetting;\n\t\tawait this.applyFromSettings();\n\t}\n\n\tsetThemeInstance(themeInstance: Theme): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tsetThemeInstance(themeInstance);\n\t\tthis.activeThemeName = \"<in-memory>\";\n\t\tthis.notifyChanged();\n\t\treturn { success: true };\n\t}\n\n\tpreview(themeSettingOrName: string): void {\n\t\tconst themeName = resolveThemeSetting(themeSettingOrName, this.terminalTheme) ?? this.activeThemeName;\n\t\tif (!themeName) return;\n\t\tif (setTheme(themeName, true).success) {\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tdisableAutoSync(): void {\n\t\tthis.setAutoSync(false);\n\t}\n\n\tgetTerminalTheme(): TerminalTheme {\n\t\treturn this.terminalTheme;\n\t}\n\n\tprivate applyThemeName(themeName: string, showError = false): ThemeResult {\n\t\tconst result = setTheme(themeName, true);\n\t\tthis.activeThemeName = result.success ? themeName : \"dark\";\n\t\tthis.notifyChanged();\n\t\tif (!result.success && showError) {\n\t\t\tthis.showError(\n\t\t\t\t`${t('Failed to load theme \"{name}\": {error}', {\n\t\t\t\t\tname: themeName,\n\t\t\t\t\terror: result.error ?? \"unknown\",\n\t\t\t\t})}\\n${t(\"Fell back to dark theme.\")}`,\n\t\t\t);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate notifyChanged(): void {\n\t\tthis.ui.invalidate();\n\t\tthis.onChanged();\n\t}\n\n\tprivate setAutoSync(enabled: boolean): void {\n\t\tif (this.autoSyncEnabled === enabled) return;\n\t\tthis.autoSyncEnabled = enabled;\n\t\tthis.ui.setTerminalColorSchemeNotifications(enabled);\n\t}\n\n\tprivate bindTerminalColorSchemeListener(): void {\n\t\tthis.terminalColorSchemeUnsubscribe = this.ui.onTerminalColorSchemeChange((terminalTheme) =>\n\t\t\tthis.applyTerminalTheme(terminalTheme),\n\t\t);\n\t}\n\n\tprivate applyTerminalTheme(terminalTheme: TerminalTheme): void {\n\t\tif (!this.autoSyncEnabled) return;\n\t\tthis.terminalTheme = terminalTheme;\n\t\tconst autoTheme = parseAutoThemeSetting(this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting());\n\t\t// v8 ignore next -- auto sync is only enabled for an auto theme setting, which always parses\n\t\tif (!autoTheme) {\n\t\t\tthis.setAutoSync(false);\n\t\t\treturn;\n\t\t}\n\t\tconst themeName = terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme;\n\t\tif (themeName !== this.activeThemeName) {\n\t\t\tthis.applyThemeName(themeName);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { ThinkingLevel } from \"@agent-forge/agent-core\";\nimport {\n\ttype EditorTheme,\n\tgetCapabilities,\n\ttype MarkdownTheme,\n\ttype RgbColor,\n\ttype SelectListTheme,\n\ttype SettingsListTheme,\n} from \"@agent-forge/tui\";\nimport chalk from \"chalk\";\nimport { type Static, Type } from \"typebox\";\nimport { Compile } from \"typebox/compile\";\nimport { getCustomThemesDir, getThemesDir } from \"../../../config.ts\";\nimport type { SourceInfo } from \"../../../core/source-info.ts\";\nimport { closeWatcher, watchWithErrorHandler } from \"../../../utils/fs-watch.ts\";\nimport { highlight, supportsLanguage } from \"../../../utils/syntax-highlight.ts\";\nimport { stripBom } from \"../../../utils/text.ts\";\n\n// ============================================================================\n// Types & Schema\n// ============================================================================\n\nconst ColorValueSchema = Type.Union([\n\tType.String(), // hex \"#ff0000\", var ref \"primary\", or empty \"\"\n\tType.Integer({ minimum: 0, maximum: 255 }), // 256-color index\n]);\n\ntype ColorValue = Static<typeof ColorValueSchema>;\n\nconst ThemeJsonSchema = Type.Object({\n\t$schema: Type.Optional(Type.String()),\n\tname: Type.String(),\n\tvars: Type.Optional(Type.Record(Type.String(), ColorValueSchema)),\n\tcolors: Type.Object({\n\t\t// Core UI (10 colors)\n\t\taccent: ColorValueSchema,\n\t\tborder: ColorValueSchema,\n\t\tborderAccent: ColorValueSchema,\n\t\tborderMuted: ColorValueSchema,\n\t\tsuccess: ColorValueSchema,\n\t\terror: ColorValueSchema,\n\t\twarning: ColorValueSchema,\n\t\tmuted: ColorValueSchema,\n\t\tdim: ColorValueSchema,\n\t\ttext: ColorValueSchema,\n\t\tthinkingText: ColorValueSchema,\n\t\t// Backgrounds & Content Text (11 required, 3 optional)\n\t\tselectedBg: ColorValueSchema,\n\t\tscrollbarThumb: Type.Optional(ColorValueSchema),\n\t\tsearchMatchBg: Type.Optional(ColorValueSchema),\n\t\tsearchMatchText: Type.Optional(ColorValueSchema),\n\t\tuserMessageBg: ColorValueSchema,\n\t\tuserMessageText: ColorValueSchema,\n\t\tcustomMessageBg: ColorValueSchema,\n\t\tcustomMessageText: ColorValueSchema,\n\t\tcustomMessageLabel: ColorValueSchema,\n\t\ttoolPendingBg: ColorValueSchema,\n\t\ttoolSuccessBg: ColorValueSchema,\n\t\ttoolErrorBg: ColorValueSchema,\n\t\ttoolTitle: ColorValueSchema,\n\t\ttoolOutput: ColorValueSchema,\n\t\t// Markdown (10 colors)\n\t\tmdHeading: ColorValueSchema,\n\t\tmdLink: ColorValueSchema,\n\t\tmdLinkUrl: ColorValueSchema,\n\t\tmdCode: ColorValueSchema,\n\t\tmdCodeBlock: ColorValueSchema,\n\t\tmdCodeBlockBorder: ColorValueSchema,\n\t\tmdQuote: ColorValueSchema,\n\t\tmdQuoteBorder: ColorValueSchema,\n\t\tmdHr: ColorValueSchema,\n\t\tmdListBullet: ColorValueSchema,\n\t\t// Tool Diffs (3 colors)\n\t\ttoolDiffAdded: ColorValueSchema,\n\t\ttoolDiffRemoved: ColorValueSchema,\n\t\ttoolDiffContext: ColorValueSchema,\n\t\t// Syntax Highlighting (9 colors)\n\t\tsyntaxComment: ColorValueSchema,\n\t\tsyntaxKeyword: ColorValueSchema,\n\t\tsyntaxFunction: ColorValueSchema,\n\t\tsyntaxVariable: ColorValueSchema,\n\t\tsyntaxString: ColorValueSchema,\n\t\tsyntaxNumber: ColorValueSchema,\n\t\tsyntaxType: ColorValueSchema,\n\t\tsyntaxOperator: ColorValueSchema,\n\t\tsyntaxPunctuation: ColorValueSchema,\n\t\t// Thinking Level Borders (6 colors)\n\t\tthinkingOff: ColorValueSchema,\n\t\tthinkingMinimal: ColorValueSchema,\n\t\tthinkingLow: ColorValueSchema,\n\t\tthinkingMedium: ColorValueSchema,\n\t\tthinkingHigh: ColorValueSchema,\n\t\tthinkingXhigh: ColorValueSchema,\n\t\tthinkingMax: Type.Optional(ColorValueSchema),\n\t\t// Bash Mode (1 color)\n\t\tbashMode: ColorValueSchema,\n\t}),\n\texport: Type.Optional(\n\t\tType.Object({\n\t\t\tpageBg: Type.Optional(ColorValueSchema),\n\t\t\tcardBg: Type.Optional(ColorValueSchema),\n\t\t\tinfoBg: Type.Optional(ColorValueSchema),\n\t\t}),\n\t),\n});\n\ntype ThemeJson = Static<typeof ThemeJsonSchema>;\n\nconst validateThemeJson = Compile(ThemeJsonSchema);\n\nexport type ThemeColor =\n\t| \"accent\"\n\t| \"border\"\n\t| \"borderAccent\"\n\t| \"borderMuted\"\n\t| \"success\"\n\t| \"error\"\n\t| \"warning\"\n\t| \"muted\"\n\t| \"dim\"\n\t| \"text\"\n\t| \"thinkingText\"\n\t| \"searchMatchText\"\n\t| \"userMessageText\"\n\t| \"customMessageText\"\n\t| \"customMessageLabel\"\n\t| \"toolTitle\"\n\t| \"toolOutput\"\n\t| \"mdHeading\"\n\t| \"mdLink\"\n\t| \"mdLinkUrl\"\n\t| \"mdCode\"\n\t| \"mdCodeBlock\"\n\t| \"mdCodeBlockBorder\"\n\t| \"mdQuote\"\n\t| \"mdQuoteBorder\"\n\t| \"mdHr\"\n\t| \"mdListBullet\"\n\t| \"toolDiffAdded\"\n\t| \"toolDiffRemoved\"\n\t| \"toolDiffContext\"\n\t| \"syntaxComment\"\n\t| \"syntaxKeyword\"\n\t| \"syntaxFunction\"\n\t| \"syntaxVariable\"\n\t| \"syntaxString\"\n\t| \"syntaxNumber\"\n\t| \"syntaxType\"\n\t| \"syntaxOperator\"\n\t| \"syntaxPunctuation\"\n\t| \"thinkingOff\"\n\t| \"thinkingMinimal\"\n\t| \"thinkingLow\"\n\t| \"thinkingMedium\"\n\t| \"thinkingHigh\"\n\t| \"thinkingXhigh\"\n\t| \"thinkingMax\"\n\t| \"bashMode\";\n\nexport type ThemeBg =\n\t| \"selectedBg\"\n\t| \"scrollbarThumb\"\n\t| \"searchMatchBg\"\n\t| \"userMessageBg\"\n\t| \"customMessageBg\"\n\t| \"toolPendingBg\"\n\t| \"toolSuccessBg\"\n\t| \"toolErrorBg\";\n\ntype OptionalThemeColor = \"thinkingMax\" | \"searchMatchText\";\ntype OptionalThemeBg = \"scrollbarThumb\" | \"searchMatchBg\";\n\ntype ColorMode = \"truecolor\" | \"256color\";\n\n// ============================================================================\n// Color Utilities\n// ============================================================================\n\nfunction hexToRgb(hex: string): { r: number; g: number; b: number } {\n\tconst cleaned = hex.replace(\"#\", \"\");\n\tif (cleaned.length !== 6) {\n\t\tthrow new Error(`Invalid hex color: ${hex}`);\n\t}\n\tconst r = parseInt(cleaned.substring(0, 2), 16);\n\tconst g = parseInt(cleaned.substring(2, 4), 16);\n\tconst b = parseInt(cleaned.substring(4, 6), 16);\n\tif (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {\n\t\tthrow new Error(`Invalid hex color: ${hex}`);\n\t}\n\treturn { r, g, b };\n}\n\n// The 6x6x6 color cube channel values (indices 0-5)\nconst CUBE_VALUES = [0, 95, 135, 175, 215, 255];\n\n// Grayscale ramp values (indices 232-255, 24 grays from 8 to 238)\nconst GRAY_VALUES = Array.from({ length: 24 }, (_, i) => 8 + i * 10);\n\nfunction findClosestCubeIndex(value: number): number {\n\tlet minDist = Infinity;\n\tlet minIdx = 0;\n\tfor (let i = 0; i < CUBE_VALUES.length; i++) {\n\t\tconst dist = Math.abs(value - CUBE_VALUES[i]);\n\t\tif (dist < minDist) {\n\t\t\tminDist = dist;\n\t\t\tminIdx = i;\n\t\t}\n\t}\n\treturn minIdx;\n}\n\nfunction findClosestGrayIndex(gray: number): number {\n\tlet minDist = Infinity;\n\tlet minIdx = 0;\n\tfor (let i = 0; i < GRAY_VALUES.length; i++) {\n\t\tconst dist = Math.abs(gray - GRAY_VALUES[i]);\n\t\tif (dist < minDist) {\n\t\t\tminDist = dist;\n\t\t\tminIdx = i;\n\t\t}\n\t}\n\treturn minIdx;\n}\n\nfunction colorDistance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number {\n\t// Weighted Euclidean distance (human eye is more sensitive to green)\n\tconst dr = r1 - r2;\n\tconst dg = g1 - g2;\n\tconst db = b1 - b2;\n\treturn dr * dr * 0.299 + dg * dg * 0.587 + db * db * 0.114;\n}\n\nfunction rgbTo256(r: number, g: number, b: number): number {\n\t// Find closest color in the 6x6x6 cube\n\tconst rIdx = findClosestCubeIndex(r);\n\tconst gIdx = findClosestCubeIndex(g);\n\tconst bIdx = findClosestCubeIndex(b);\n\tconst cubeR = CUBE_VALUES[rIdx];\n\tconst cubeG = CUBE_VALUES[gIdx];\n\tconst cubeB = CUBE_VALUES[bIdx];\n\tconst cubeIndex = 16 + 36 * rIdx + 6 * gIdx + bIdx;\n\tconst cubeDist = colorDistance(r, g, b, cubeR, cubeG, cubeB);\n\n\t// Find closest grayscale\n\tconst gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);\n\tconst grayIdx = findClosestGrayIndex(gray);\n\tconst grayValue = GRAY_VALUES[grayIdx];\n\tconst grayIndex = 232 + grayIdx;\n\tconst grayDist = colorDistance(r, g, b, grayValue, grayValue, grayValue);\n\n\t// Check if color has noticeable saturation (hue matters)\n\t// If max-min spread is significant, prefer cube to preserve tint\n\tconst maxC = Math.max(r, g, b);\n\tconst minC = Math.min(r, g, b);\n\tconst spread = maxC - minC;\n\n\t// Only consider grayscale if color is nearly neutral (spread < 10)\n\t// AND grayscale is actually closer\n\tif (spread < 10 && grayDist < cubeDist) {\n\t\treturn grayIndex;\n\t}\n\n\treturn cubeIndex;\n}\n\nfunction hexTo256(hex: string): number {\n\tconst { r, g, b } = hexToRgb(hex);\n\treturn rgbTo256(r, g, b);\n}\n\nfunction fgAnsi(color: string | number, mode: ColorMode): string {\n\tif (color === \"\") return \"\\x1b[39m\";\n\tif (typeof color === \"number\") return `\\x1b[38;5;${color}m`;\n\tif (color.startsWith(\"#\")) {\n\t\tif (mode === \"truecolor\") {\n\t\t\tconst { r, g, b } = hexToRgb(color);\n\t\t\treturn `\\x1b[38;2;${r};${g};${b}m`;\n\t\t} else {\n\t\t\tconst index = hexTo256(color);\n\t\t\treturn `\\x1b[38;5;${index}m`;\n\t\t}\n\t}\n\tthrow new Error(`Invalid color value: ${color}`);\n}\n\nfunction bgAnsi(color: string | number, mode: ColorMode): string {\n\tif (color === \"\") return \"\\x1b[49m\";\n\tif (typeof color === \"number\") return `\\x1b[48;5;${color}m`;\n\tif (color.startsWith(\"#\")) {\n\t\tif (mode === \"truecolor\") {\n\t\t\tconst { r, g, b } = hexToRgb(color);\n\t\t\treturn `\\x1b[48;2;${r};${g};${b}m`;\n\t\t} else {\n\t\t\tconst index = hexTo256(color);\n\t\t\treturn `\\x1b[48;5;${index}m`;\n\t\t}\n\t}\n\tthrow new Error(`Invalid color value: ${color}`);\n}\n\nfunction resolveVarRefs(\n\tvalue: ColorValue,\n\tvars: Record<string, ColorValue>,\n\tvisited = new Set<string>(),\n): string | number {\n\tif (typeof value === \"number\" || value === \"\" || value.startsWith(\"#\")) {\n\t\treturn value;\n\t}\n\tif (visited.has(value)) {\n\t\tthrow new Error(`Circular variable reference detected: ${value}`);\n\t}\n\tif (!(value in vars)) {\n\t\tthrow new Error(`Variable reference not found: ${value}`);\n\t}\n\tvisited.add(value);\n\treturn resolveVarRefs(vars[value], vars, visited);\n}\n\nfunction resolveThemeColors<T extends Record<string, ColorValue>>(\n\tcolors: T,\n\tvars: Record<string, ColorValue> = {},\n): Record<keyof T, string | number> {\n\tconst resolved: Record<string, string | number> = {};\n\tfor (const [key, value] of Object.entries(colors)) {\n\t\tresolved[key] = resolveVarRefs(value, vars);\n\t}\n\treturn resolved as Record<keyof T, string | number>;\n}\n\nfunction withThemeColorFallbacks(colors: ThemeJson[\"colors\"]): ThemeJson[\"colors\"] & {\n\tthinkingMax: ColorValue;\n\tscrollbarThumb: ColorValue;\n\tsearchMatchBg: ColorValue;\n\tsearchMatchText: ColorValue;\n} {\n\treturn {\n\t\t...colors,\n\t\tthinkingMax: colors.thinkingMax ?? colors.thinkingXhigh,\n\t\tscrollbarThumb: colors.scrollbarThumb ?? colors.selectedBg,\n\t\tsearchMatchBg: colors.searchMatchBg ?? colors.selectedBg,\n\t\tsearchMatchText: colors.searchMatchText ?? colors.text,\n\t};\n}\n\n// ============================================================================\n// Theme Class\n// ============================================================================\n\nexport class Theme {\n\treadonly name?: string;\n\treadonly sourcePath?: string;\n\tsourceInfo?: SourceInfo;\n\tprivate fgColors: Map<ThemeColor, string>;\n\tprivate bgColors: Map<ThemeBg, string>;\n\tprivate mode: ColorMode;\n\n\tconstructor(\n\t\tfgColors: Record<Exclude<ThemeColor, OptionalThemeColor>, string | number> &\n\t\t\tPartial<Record<OptionalThemeColor, string | number>>,\n\t\tbgColors: Record<Exclude<ThemeBg, OptionalThemeBg>, string | number> &\n\t\t\tPartial<Record<OptionalThemeBg, string | number>>,\n\t\tmode: ColorMode,\n\t\toptions: { name?: string; sourcePath?: string; sourceInfo?: SourceInfo } = {},\n\t) {\n\t\tthis.name = options.name;\n\t\tthis.sourcePath = options.sourcePath;\n\t\tthis.sourceInfo = options.sourceInfo;\n\t\tthis.mode = mode;\n\t\tthis.fgColors = new Map();\n\t\tconst colors = {\n\t\t\t...fgColors,\n\t\t\tthinkingMax: fgColors.thinkingMax ?? fgColors.thinkingXhigh,\n\t\t\tsearchMatchText: fgColors.searchMatchText ?? fgColors.text,\n\t\t};\n\t\tfor (const [key, value] of Object.entries(colors) as [ThemeColor, string | number][]) {\n\t\t\tthis.fgColors.set(key, fgAnsi(value, mode));\n\t\t}\n\t\tthis.bgColors = new Map();\n\t\tconst backgrounds = {\n\t\t\t...bgColors,\n\t\t\tscrollbarThumb: bgColors.scrollbarThumb ?? bgColors.selectedBg,\n\t\t\tsearchMatchBg: bgColors.searchMatchBg ?? bgColors.selectedBg,\n\t\t};\n\t\tfor (const [key, value] of Object.entries(backgrounds) as [ThemeBg, string | number][]) {\n\t\t\tthis.bgColors.set(key, bgAnsi(value, mode));\n\t\t}\n\t}\n\n\tfg(color: ThemeColor, text: string): string {\n\t\tconst ansi = this.fgColors.get(color);\n\t\tif (!ansi) throw new Error(`Unknown theme color: ${color}`);\n\t\treturn `${ansi}${text}\\x1b[39m`; // Reset only foreground color\n\t}\n\n\tbg(color: ThemeBg, text: string): string {\n\t\tconst ansi = this.bgColors.get(color);\n\t\tif (!ansi) throw new Error(`Unknown theme background color: ${color}`);\n\t\treturn `${ansi}${text}\\x1b[49m`; // Reset only background color\n\t}\n\n\tbold(text: string): string {\n\t\treturn chalk.bold(text);\n\t}\n\n\titalic(text: string): string {\n\t\treturn chalk.italic(text);\n\t}\n\n\tunderline(text: string): string {\n\t\treturn chalk.underline(text);\n\t}\n\n\tinverse(text: string): string {\n\t\treturn chalk.inverse(text);\n\t}\n\n\tstrikethrough(text: string): string {\n\t\treturn chalk.strikethrough(text);\n\t}\n\n\tgetFgAnsi(color: ThemeColor): string {\n\t\tconst ansi = this.fgColors.get(color);\n\t\tif (!ansi) throw new Error(`Unknown theme color: ${color}`);\n\t\treturn ansi;\n\t}\n\n\tgetBgAnsi(color: ThemeBg): string {\n\t\tconst ansi = this.bgColors.get(color);\n\t\tif (!ansi) throw new Error(`Unknown theme background color: ${color}`);\n\t\treturn ansi;\n\t}\n\n\tgetColorMode(): ColorMode {\n\t\treturn this.mode;\n\t}\n\n\tgetThinkingBorderColor(level: ThinkingLevel): (str: string) => string {\n\t\t// Map thinking levels to dedicated theme colors\n\t\tswitch (level) {\n\t\t\tcase \"off\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingOff\", str);\n\t\t\tcase \"minimal\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingMinimal\", str);\n\t\t\tcase \"low\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingLow\", str);\n\t\t\tcase \"medium\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingMedium\", str);\n\t\t\tcase \"high\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingHigh\", str);\n\t\t\tcase \"xhigh\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingXhigh\", str);\n\t\t\tcase \"max\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingMax\", str);\n\t\t\tdefault:\n\t\t\t\treturn (str: string) => this.fg(\"thinkingOff\", str);\n\t\t}\n\t}\n\n\tgetBashModeBorderColor(): (str: string) => string {\n\t\treturn (str: string) => this.fg(\"bashMode\", str);\n\t}\n}\n\n// ============================================================================\n// Theme Loading\n// ============================================================================\n\nlet BUILTIN_THEMES: Record<string, ThemeJson> | undefined;\n\nfunction getBuiltinThemes(): Record<string, ThemeJson> {\n\tif (!BUILTIN_THEMES) {\n\t\tconst themesDir = getThemesDir();\n\t\tconst darkPath = path.join(themesDir, \"dark.json\");\n\t\tconst lightPath = path.join(themesDir, \"light.json\");\n\t\tBUILTIN_THEMES = {\n\t\t\tdark: JSON.parse(stripBom(fs.readFileSync(darkPath, \"utf-8\"))) as ThemeJson,\n\t\t\tlight: JSON.parse(stripBom(fs.readFileSync(lightPath, \"utf-8\"))) as ThemeJson,\n\t\t};\n\t}\n\treturn BUILTIN_THEMES;\n}\n\nexport function getAvailableThemes(): string[] {\n\treturn getAvailableThemesWithPaths().map(({ name }) => name);\n}\n\nexport interface ThemeInfo {\n\tname: string;\n\tpath: string | undefined;\n}\n\nexport function getAvailableThemesWithPaths(): ThemeInfo[] {\n\tconst themesDir = getThemesDir();\n\tconst result: ThemeInfo[] = [];\n\tconst seen = new Set<string>();\n\tconst addTheme = (themeInfo: ThemeInfo) => {\n\t\tif (seen.has(themeInfo.name)) {\n\t\t\treturn;\n\t\t}\n\t\tseen.add(themeInfo.name);\n\t\tresult.push(themeInfo);\n\t};\n\n\t// Built-in themes\n\tfor (const name of Object.keys(getBuiltinThemes())) {\n\t\taddTheme({ name, path: path.join(themesDir, `${name}.json`) });\n\t}\n\n\t// Custom themes\n\tfor (const themeInfo of getCustomThemeInfos()) {\n\t\taddTheme(themeInfo);\n\t}\n\n\tfor (const [name, theme] of registeredThemes.entries()) {\n\t\taddTheme({ name, path: theme.sourcePath });\n\t}\n\n\treturn result.sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction getCustomThemeInfos(): ThemeInfo[] {\n\tconst customThemesDir = getCustomThemesDir();\n\tconst result: ThemeInfo[] = [];\n\tif (!fs.existsSync(customThemesDir)) {\n\t\treturn result;\n\t}\n\n\tfor (const file of fs.readdirSync(customThemesDir)) {\n\t\tif (!file.endsWith(\".json\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst themePath = path.join(customThemesDir, file);\n\t\ttry {\n\t\t\tconst customTheme = loadThemeFromPath(themePath);\n\t\t\tif (customTheme.name) {\n\t\t\t\tresult.push({ name: customTheme.name, path: themePath });\n\t\t\t}\n\t\t} catch {\n\t\t\t// Invalid themes are ignored here; the resource loader reports them\n\t\t\t// during normal startup/reload.\n\t\t}\n\t}\n\treturn result;\n}\n\nfunction assertThemeNameIsValid(name: string): void {\n\tif (name.includes(\"/\")) {\n\t\tthrow new Error(\n\t\t\t`Invalid theme name \"${name}\": theme names cannot contain \"/\" because it is reserved for automatic light/dark theme settings.`,\n\t\t);\n\t}\n}\n\nfunction parseThemeJson(label: string, json: unknown): ThemeJson {\n\tif (!validateThemeJson.Check(json)) {\n\t\tconst errors = Array.from(validateThemeJson.Errors(json));\n\t\tconst missingColors = new Set<string>();\n\t\tconst otherErrors: string[] = [];\n\n\t\tfor (const error of errors) {\n\t\t\tif (error.keyword === \"required\" && error.instancePath === \"/colors\") {\n\t\t\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\t\t\tfor (const requiredProperty of requiredProperties ?? []) {\n\t\t\t\t\tmissingColors.add(requiredProperty);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst path = error.instancePath || \"/\";\n\t\t\totherErrors.push(` - ${path}: ${error.message}`);\n\t\t}\n\n\t\tlet errorMessage = `Invalid theme \"${label}\":\\n`;\n\t\tif (missingColors.size > 0) {\n\t\t\terrorMessage += \"\\nMissing required color tokens:\\n\";\n\t\t\terrorMessage += Array.from(missingColors)\n\t\t\t\t.sort()\n\t\t\t\t.map((color) => ` - ${color}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\terrorMessage += '\\n\\nPlease add these colors to your theme\\'s \"colors\" object.';\n\t\t\terrorMessage += \"\\nSee the built-in themes (dark.json, light.json) for reference values.\";\n\t\t}\n\t\tif (otherErrors.length > 0) {\n\t\t\terrorMessage += `\\n\\nOther errors:\\n${otherErrors.join(\"\\n\")}`;\n\t\t}\n\n\t\tthrow new Error(errorMessage);\n\t}\n\n\tconst themeJson = json as ThemeJson;\n\tassertThemeNameIsValid(themeJson.name);\n\treturn themeJson;\n}\n\nfunction parseThemeJsonContent(label: string, content: string): ThemeJson {\n\tlet json: unknown;\n\ttry {\n\t\tjson = JSON.parse(stripBom(content));\n\t} catch (error) {\n\t\tthrow new Error(`Failed to parse theme ${label}: ${error}`);\n\t}\n\treturn parseThemeJson(label, json);\n}\n\nfunction loadThemeJson(name: string): ThemeJson {\n\tconst builtinThemes = getBuiltinThemes();\n\tif (name in builtinThemes) {\n\t\treturn builtinThemes[name];\n\t}\n\tconst registeredTheme = registeredThemes.get(name);\n\tif (registeredTheme?.sourcePath) {\n\t\tconst content = fs.readFileSync(registeredTheme.sourcePath, \"utf-8\");\n\t\treturn parseThemeJsonContent(registeredTheme.sourcePath, content);\n\t}\n\tif (registeredTheme) {\n\t\tthrow new Error(`Theme \"${name}\" does not have a source path for export`);\n\t}\n\tconst customThemesDir = getCustomThemesDir();\n\tconst themePath = path.join(customThemesDir, `${name}.json`);\n\tif (!fs.existsSync(themePath)) {\n\t\tthrow new Error(`Theme not found: ${name}`);\n\t}\n\tconst content = fs.readFileSync(themePath, \"utf-8\");\n\treturn parseThemeJsonContent(name, content);\n}\n\nfunction createTheme(themeJson: ThemeJson, mode?: ColorMode, sourcePath?: string): Theme {\n\tconst colorMode = mode ?? (getCapabilities().trueColor ? \"truecolor\" : \"256color\");\n\tconst resolvedColors = resolveThemeColors(withThemeColorFallbacks(themeJson.colors), themeJson.vars);\n\tconst fgColors: Record<ThemeColor, string | number> = {} as Record<ThemeColor, string | number>;\n\tconst bgColors: Record<ThemeBg, string | number> = {} as Record<ThemeBg, string | number>;\n\tconst bgColorKeys: Set<string> = new Set([\n\t\t\"selectedBg\",\n\t\t\"scrollbarThumb\",\n\t\t\"searchMatchBg\",\n\t\t\"userMessageBg\",\n\t\t\"customMessageBg\",\n\t\t\"toolPendingBg\",\n\t\t\"toolSuccessBg\",\n\t\t\"toolErrorBg\",\n\t]);\n\tfor (const [key, value] of Object.entries(resolvedColors)) {\n\t\tif (bgColorKeys.has(key)) {\n\t\t\tbgColors[key as ThemeBg] = value;\n\t\t} else {\n\t\t\tfgColors[key as ThemeColor] = value;\n\t\t}\n\t}\n\treturn new Theme(fgColors, bgColors, colorMode, {\n\t\tname: themeJson.name,\n\t\tsourcePath,\n\t});\n}\n\nexport function loadThemeFromPath(themePath: string, mode?: ColorMode): Theme {\n\tconst content = fs.readFileSync(themePath, \"utf-8\");\n\tconst themeJson = parseThemeJsonContent(themePath, content);\n\treturn createTheme(themeJson, mode, themePath);\n}\n\nfunction loadTheme(name: string, mode?: ColorMode): Theme {\n\tconst registeredTheme = registeredThemes.get(name);\n\tif (registeredTheme) {\n\t\treturn registeredTheme;\n\t}\n\tconst themeJson = loadThemeJson(name);\n\treturn createTheme(themeJson, mode);\n}\n\nexport function getThemeByName(name: string): Theme | undefined {\n\ttry {\n\t\treturn loadTheme(name);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport type TerminalTheme = \"dark\" | \"light\";\n\nexport function parseAutoThemeSetting(\n\tthemeSetting: string | undefined,\n): { lightTheme: string; darkTheme: string } | undefined {\n\tif (!themeSetting) return undefined;\n\tconst slashIndex = themeSetting.indexOf(\"/\");\n\tif (slashIndex === -1 || themeSetting.indexOf(\"/\", slashIndex + 1) !== -1) {\n\t\treturn undefined;\n\t}\n\n\tconst lightTheme = themeSetting.slice(0, slashIndex).trim();\n\tconst darkTheme = themeSetting.slice(slashIndex + 1).trim();\n\tif (!lightTheme || !darkTheme) {\n\t\treturn undefined;\n\t}\n\treturn { lightTheme, darkTheme };\n}\n\nexport function resolveThemeSetting(\n\tthemeSetting: string | undefined,\n\tterminalTheme: TerminalTheme,\n): string | undefined {\n\tconst autoTheme = parseAutoThemeSetting(themeSetting);\n\tif (autoTheme) {\n\t\treturn terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme;\n\t}\n\tif (themeSetting?.includes(\"/\")) return undefined;\n\tif (typeof themeSetting === \"string\") return themeSetting;\n\treturn undefined;\n}\n\nexport interface TerminalThemeDetection {\n\ttheme: TerminalTheme;\n\tsource: \"terminal background\" | \"COLORFGBG\" | \"fallback\";\n\tdetail: string;\n\tconfidence: \"high\" | \"low\";\n}\n\nexport interface TerminalThemeDetectionOptions {\n\tenv?: NodeJS.ProcessEnv;\n}\n\nexport interface TerminalBackgroundThemeDetector {\n\tqueryTerminalBackgroundColor({ timeoutMs }: { timeoutMs: number }): Promise<RgbColor | undefined>;\n}\n\nexport interface TerminalAutoThemeDetector extends TerminalBackgroundThemeDetector {\n\tqueryTerminalColorScheme?({ timeoutMs }: { timeoutMs: number }): Promise<TerminalTheme | undefined>;\n}\n\nexport interface TerminalBackgroundThemeDetectionOptions extends TerminalThemeDetectionOptions {\n\tui: TerminalBackgroundThemeDetector;\n\ttimeoutMs: number;\n}\n\nexport interface TerminalAutoThemeDetectionOptions extends TerminalThemeDetectionOptions {\n\tui: TerminalAutoThemeDetector;\n\ttimeoutMs: number;\n}\n\nfunction getColorFgBgBackgroundIndex(colorfgbg: string): number | undefined {\n\tconst parts = colorfgbg.split(\";\");\n\tfor (let i = parts.length - 1; i >= 0; i--) {\n\t\tconst bg = parseInt(parts[i].trim(), 10);\n\t\tif (Number.isInteger(bg) && bg >= 0 && bg <= 255) {\n\t\t\treturn bg;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction getRgbColorLuminance({ r, g, b }: RgbColor): number {\n\tconst toLinear = (channel: number) => {\n\t\tconst value = channel / 255;\n\t\treturn value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;\n\t};\n\treturn 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n}\n\nfunction getAnsiColorLuminance(index: number): number {\n\treturn getRgbColorLuminance(hexToRgb(ansi256ToHex(index)));\n}\n\nexport function getThemeForRgbColor(rgb: RgbColor): TerminalTheme {\n\treturn getRgbColorLuminance(rgb) >= 0.5 ? \"light\" : \"dark\";\n}\n\nexport function detectTerminalBackgroundFromEnv(options: TerminalThemeDetectionOptions = {}): TerminalThemeDetection {\n\tconst env = options.env ?? process.env;\n\tconst colorfgbg = env.COLORFGBG || \"\";\n\tconst bg = getColorFgBgBackgroundIndex(colorfgbg);\n\tif (bg !== undefined) {\n\t\treturn {\n\t\t\ttheme: getAnsiColorLuminance(bg) >= 0.5 ? \"light\" : \"dark\",\n\t\t\tsource: \"COLORFGBG\",\n\t\t\tdetail: `background color index ${bg}`,\n\t\t\tconfidence: \"high\",\n\t\t};\n\t}\n\n\treturn {\n\t\ttheme: \"dark\",\n\t\tsource: \"fallback\",\n\t\tdetail: \"no terminal background hint found\",\n\t\tconfidence: \"low\",\n\t};\n}\n\nexport async function detectTerminalBackgroundTheme({\n\tui,\n\ttimeoutMs,\n\tenv,\n}: TerminalBackgroundThemeDetectionOptions): Promise<TerminalThemeDetection> {\n\ttry {\n\t\tconst rgb = await ui.queryTerminalBackgroundColor({ timeoutMs });\n\t\tif (rgb) {\n\t\t\treturn {\n\t\t\t\ttheme: getThemeForRgbColor(rgb),\n\t\t\t\tsource: \"terminal background\",\n\t\t\t\tdetail: `OSC 11 background rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,\n\t\t\t\tconfidence: \"high\",\n\t\t\t};\n\t\t}\n\t} catch {\n\t\t// Fall back to environment-based detection when the terminal query fails.\n\t}\n\n\treturn detectTerminalBackgroundFromEnv({ env });\n}\n\nexport async function detectTerminalThemeForAuto({\n\tui,\n\ttimeoutMs,\n\tenv,\n}: TerminalAutoThemeDetectionOptions): Promise<TerminalTheme> {\n\tlet colorSchemePromise: Promise<TerminalTheme | undefined> | undefined;\n\ttry {\n\t\tcolorSchemePromise = ui.queryTerminalColorScheme?.({ timeoutMs });\n\t} catch {\n\t\t// Fall back to OSC 11 / COLORFGBG detection when starting the color-scheme query fails.\n\t}\n\tconst backgroundThemePromise = detectTerminalBackgroundTheme({ ui, timeoutMs, env });\n\n\ttry {\n\t\tconst colorScheme = await colorSchemePromise;\n\t\tif (colorScheme) return colorScheme;\n\t} catch {\n\t\t// Fall back to the concurrently queried OSC 11 / COLORFGBG detection.\n\t}\n\treturn (await backgroundThemePromise).theme;\n}\n\nexport function getDefaultTheme(): string {\n\treturn detectTerminalBackgroundFromEnv().theme;\n}\n\n// ============================================================================\n// Global Theme Instance\n// ============================================================================\n\n// Use globalThis to share theme across module loaders (tsx + jiti in dev mode)\nconst THEME_KEY = Symbol.for(\"@agent-forge/agent-forge:theme\");\nconst THEME_KEY_OLD = Symbol.for(\"@mariozechner/pi-coding-agent:theme\");\n\n// Export theme as a getter that reads from globalThis\n// This ensures all module instances (tsx, jiti) see the same theme\nexport const theme: Theme = new Proxy({} as Theme, {\n\tget(_target, prop) {\n\t\tconst t = (globalThis as Record<symbol, Theme>)[THEME_KEY];\n\t\tif (!t) throw new Error(\"Theme not initialized. Call initTheme() first.\");\n\t\treturn (t as unknown as Record<string | symbol, unknown>)[prop];\n\t},\n});\n\nfunction setGlobalTheme(t: Theme): void {\n\t(globalThis as Record<symbol, Theme>)[THEME_KEY] = t;\n\t(globalThis as Record<symbol, Theme>)[THEME_KEY_OLD] = t;\n}\n\nlet currentThemeName: string | undefined;\nlet themeWatcher: fs.FSWatcher | undefined;\nlet themeReloadTimer: NodeJS.Timeout | undefined;\nlet onThemeChangeCallback: (() => void) | undefined;\nconst registeredThemes = new Map<string, Theme>();\n\nexport function setRegisteredThemes(themes: Theme[]): void {\n\tregisteredThemes.clear();\n\tfor (const theme of themes) {\n\t\tif (theme.name) {\n\t\t\tassertThemeNameIsValid(theme.name);\n\t\t\tregisteredThemes.set(theme.name, theme);\n\t\t}\n\t}\n}\n\nexport function initTheme(themeName?: string, enableWatcher: boolean = false): void {\n\tconst name = themeName ?? getDefaultTheme();\n\tcurrentThemeName = name;\n\ttry {\n\t\tsetGlobalTheme(loadTheme(name));\n\t\tif (enableWatcher) {\n\t\t\tstartThemeWatcher();\n\t\t}\n\t} catch (_error) {\n\t\t// Theme is invalid - fall back to dark theme silently\n\t\tcurrentThemeName = \"dark\";\n\t\tsetGlobalTheme(loadTheme(\"dark\"));\n\t\t// Don't start watcher for fallback theme\n\t}\n}\n\nexport function setTheme(name: string, enableWatcher: boolean = false): { success: boolean; error?: string } {\n\tcurrentThemeName = name;\n\ttry {\n\t\tsetGlobalTheme(loadTheme(name));\n\t\tif (enableWatcher) {\n\t\t\tstartThemeWatcher();\n\t\t}\n\t\tif (onThemeChangeCallback) {\n\t\t\tonThemeChangeCallback();\n\t\t}\n\t\treturn { success: true };\n\t} catch (error) {\n\t\t// Theme is invalid - fall back to dark theme\n\t\tcurrentThemeName = \"dark\";\n\t\tsetGlobalTheme(loadTheme(\"dark\"));\n\t\t// Don't start watcher for fallback theme\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\t// v8 ignore next -- loadTheme rejections are always Error instances\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t};\n\t}\n}\n\nexport function setThemeInstance(themeInstance: Theme): void {\n\tsetGlobalTheme(themeInstance);\n\tcurrentThemeName = \"<in-memory>\";\n\tstopThemeWatcher(); // Can't watch a direct instance\n\tif (onThemeChangeCallback) {\n\t\tonThemeChangeCallback();\n\t}\n}\n\nexport function onThemeChange(callback: () => void): void {\n\tonThemeChangeCallback = callback;\n}\n\nfunction startThemeWatcher(): void {\n\tstopThemeWatcher();\n\n\t// Only watch if it's a custom theme (not built-in)\n\tif (!currentThemeName || currentThemeName === \"dark\" || currentThemeName === \"light\") {\n\t\treturn;\n\t}\n\n\tconst customThemesDir = getCustomThemesDir();\n\tconst watchedThemeName = currentThemeName;\n\tconst watchedFileName = `${watchedThemeName}.json`;\n\tconst themeFile = path.join(customThemesDir, watchedFileName);\n\n\t// Only watch if the file exists\n\tif (!fs.existsSync(themeFile)) {\n\t\treturn;\n\t}\n\n\tconst scheduleReload = () => {\n\t\tif (themeReloadTimer) {\n\t\t\tclearTimeout(themeReloadTimer);\n\t\t}\n\t\tthemeReloadTimer = setTimeout(() => {\n\t\t\tthemeReloadTimer = undefined;\n\n\t\t\t// Ignore stale timers after switching themes or stopping the watcher\n\t\t\tif (currentThemeName !== watchedThemeName) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Keep the last successfully loaded theme active if the file is temporarily missing\n\t\t\tif (!fs.existsSync(themeFile)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Reload the theme from disk and refresh the registry cache\n\t\t\t\tconst reloadedTheme = loadThemeFromPath(themeFile);\n\t\t\t\tregisteredThemes.set(watchedThemeName, reloadedTheme);\n\t\t\t\tsetGlobalTheme(reloadedTheme);\n\t\t\t\t// Notify callback (to invalidate UI)\n\t\t\t\tif (onThemeChangeCallback) {\n\t\t\t\t\tonThemeChangeCallback();\n\t\t\t\t}\n\t\t\t} catch (_error) {\n\t\t\t\t// Ignore errors (file might be in invalid state while being edited)\n\t\t\t}\n\t\t}, 100);\n\t};\n\n\tthemeWatcher =\n\t\twatchWithErrorHandler(\n\t\t\tcustomThemesDir,\n\t\t\t(_eventType, filename) => {\n\t\t\t\tif (currentThemeName !== watchedThemeName) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!filename) {\n\t\t\t\t\tscheduleReload();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (filename !== watchedFileName) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tscheduleReload();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tcloseWatcher(themeWatcher);\n\t\t\t\tthemeWatcher = undefined;\n\t\t\t},\n\t\t) ?? undefined;\n}\n\nexport function stopThemeWatcher(): void {\n\tif (themeReloadTimer) {\n\t\tclearTimeout(themeReloadTimer);\n\t\tthemeReloadTimer = undefined;\n\t}\n\tcloseWatcher(themeWatcher);\n\tthemeWatcher = undefined;\n}\n\n// ============================================================================\n// HTML Export Helpers\n// ============================================================================\n\n/**\n * Convert a 256-color index to hex string.\n * Indices 0-15: basic colors (approximate)\n * Indices 16-231: 6x6x6 color cube\n * Indices 232-255: grayscale ramp\n */\nfunction ansi256ToHex(index: number): string {\n\t// Basic colors (0-15) - approximate common terminal values\n\tconst basicColors = [\n\t\t\"#000000\",\n\t\t\"#800000\",\n\t\t\"#008000\",\n\t\t\"#808000\",\n\t\t\"#000080\",\n\t\t\"#800080\",\n\t\t\"#008080\",\n\t\t\"#c0c0c0\",\n\t\t\"#808080\",\n\t\t\"#ff0000\",\n\t\t\"#00ff00\",\n\t\t\"#ffff00\",\n\t\t\"#0000ff\",\n\t\t\"#ff00ff\",\n\t\t\"#00ffff\",\n\t\t\"#ffffff\",\n\t];\n\tif (index < 16) {\n\t\treturn basicColors[index];\n\t}\n\n\t// Color cube (16-231): 6x6x6 = 216 colors\n\tif (index < 232) {\n\t\tconst cubeIndex = index - 16;\n\t\tconst r = Math.floor(cubeIndex / 36);\n\t\tconst g = Math.floor((cubeIndex % 36) / 6);\n\t\tconst b = cubeIndex % 6;\n\t\tconst toHex = (n: number) => (n === 0 ? 0 : 55 + n * 40).toString(16).padStart(2, \"0\");\n\t\treturn `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n\t}\n\n\t// Grayscale (232-255): 24 shades\n\tconst gray = 8 + (index - 232) * 10;\n\tconst grayHex = gray.toString(16).padStart(2, \"0\");\n\treturn `#${grayHex}${grayHex}${grayHex}`;\n}\n\n/**\n * Get resolved theme colors as CSS-compatible hex strings.\n * Used by HTML export to generate CSS custom properties.\n */\nexport function getResolvedThemeColors(themeName?: string): Record<string, string> {\n\tconst name = themeName ?? currentThemeName ?? getDefaultTheme();\n\tconst isLight = name === \"light\";\n\tconst themeJson = loadThemeJson(name);\n\tconst resolved = resolveThemeColors(withThemeColorFallbacks(themeJson.colors), themeJson.vars);\n\n\t// Default text color for empty values (terminal uses default fg color)\n\tconst defaultText = isLight ? \"#000000\" : \"#e5e5e7\";\n\n\tconst cssColors: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(resolved)) {\n\t\tif (typeof value === \"number\") {\n\t\t\tcssColors[key] = ansi256ToHex(value);\n\t\t} else if (value === \"\") {\n\t\t\t// Empty means default terminal color - use sensible fallback for HTML\n\t\t\tcssColors[key] = defaultText;\n\t\t} else {\n\t\t\tcssColors[key] = value;\n\t\t}\n\t}\n\treturn cssColors;\n}\n\n/**\n * Check if a theme is a \"light\" theme (for CSS that needs light/dark variants).\n */\nexport function isLightTheme(themeName?: string): boolean {\n\t// Currently just check the name - could be extended to analyze colors\n\treturn themeName === \"light\";\n}\n\n/**\n * Get explicit export colors from theme JSON, if specified.\n * Returns undefined for each color that isn't explicitly set.\n */\nexport function getThemeExportColors(themeName?: string): {\n\tpageBg?: string;\n\tcardBg?: string;\n\tinfoBg?: string;\n} {\n\tconst name = themeName ?? currentThemeName ?? getDefaultTheme();\n\ttry {\n\t\tconst themeJson = loadThemeJson(name);\n\t\tconst exportSection = themeJson.export;\n\t\tif (!exportSection) return {};\n\n\t\tconst vars = themeJson.vars ?? {};\n\t\tconst resolve = (value: ColorValue | undefined): string | undefined => {\n\t\t\tif (value === undefined) return undefined;\n\t\t\tconst resolved = resolveVarRefs(value, vars);\n\t\t\tif (typeof resolved === \"number\") return ansi256ToHex(resolved);\n\t\t\tif (resolved === \"\") return undefined;\n\t\t\treturn resolved;\n\t\t};\n\n\t\treturn {\n\t\t\tpageBg: resolve(exportSection.pageBg),\n\t\t\tcardBg: resolve(exportSection.cardBg),\n\t\t\tinfoBg: resolve(exportSection.infoBg),\n\t\t};\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n// ============================================================================\n// TUI Helpers\n// ============================================================================\n\ntype CliHighlightTheme = Record<string, (s: string) => string>;\n\nlet cachedHighlightThemeFor: Theme | undefined;\nlet cachedCliHighlightTheme: CliHighlightTheme | undefined;\n\nfunction buildCliHighlightTheme(t: Theme): CliHighlightTheme {\n\treturn {\n\t\tkeyword: (s: string) => t.fg(\"syntaxKeyword\", s),\n\t\tbuilt_in: (s: string) => t.fg(\"syntaxType\", s),\n\t\tliteral: (s: string) => t.fg(\"syntaxNumber\", s),\n\t\tnumber: (s: string) => t.fg(\"syntaxNumber\", s),\n\t\tregexp: (s: string) => t.fg(\"syntaxString\", s),\n\t\tstring: (s: string) => t.fg(\"syntaxString\", s),\n\t\tcomment: (s: string) => t.fg(\"syntaxComment\", s),\n\t\tdoctag: (s: string) => t.fg(\"syntaxComment\", s),\n\t\tmeta: (s: string) => t.fg(\"muted\", s),\n\t\tfunction: (s: string) => t.fg(\"syntaxFunction\", s),\n\t\ttitle: (s: string) => t.fg(\"syntaxFunction\", s),\n\t\tclass: (s: string) => t.fg(\"syntaxType\", s),\n\t\ttype: (s: string) => t.fg(\"syntaxType\", s),\n\t\ttag: (s: string) => t.fg(\"syntaxPunctuation\", s),\n\t\tname: (s: string) => t.fg(\"syntaxKeyword\", s),\n\t\tattr: (s: string) => t.fg(\"syntaxVariable\", s),\n\t\tvariable: (s: string) => t.fg(\"syntaxVariable\", s),\n\t\tparams: (s: string) => t.fg(\"syntaxVariable\", s),\n\t\toperator: (s: string) => t.fg(\"syntaxOperator\", s),\n\t\tpunctuation: (s: string) => t.fg(\"syntaxPunctuation\", s),\n\t\temphasis: (s: string) => t.italic(s),\n\t\tstrong: (s: string) => t.bold(s),\n\t\tlink: (s: string) => t.underline(s),\n\t\taddition: (s: string) => t.fg(\"toolDiffAdded\", s),\n\t\tdeletion: (s: string) => t.fg(\"toolDiffRemoved\", s),\n\t};\n}\n\nfunction getCliHighlightTheme(t: Theme): CliHighlightTheme {\n\tif (cachedHighlightThemeFor !== t || !cachedCliHighlightTheme) {\n\t\tcachedHighlightThemeFor = t;\n\t\tcachedCliHighlightTheme = buildCliHighlightTheme(t);\n\t}\n\treturn cachedCliHighlightTheme;\n}\n\n/**\n * Highlight code with syntax coloring based on file extension or language.\n * Returns array of highlighted lines.\n */\nexport function highlightCode(code: string, lang?: string): string[] {\n\t// Validate language before highlighting to avoid stderr spam from cli-highlight\n\tconst validLang = lang && supportsLanguage(lang) ? lang : undefined;\n\t// Skip highlighting when no valid language is specified. cli-highlight's\n\t// auto-detection is unreliable and can misidentify prose as AppleScript,\n\t// LiveCodeServer, etc., coloring random English words as keywords.\n\tif (!validLang) {\n\t\treturn code.split(\"\\n\").map((line) => theme.fg(\"mdCodeBlock\", line));\n\t}\n\tconst opts = {\n\t\tlanguage: validLang,\n\t\tignoreIllegals: true,\n\t\ttheme: getCliHighlightTheme(theme),\n\t};\n\ttry {\n\t\treturn highlight(code, opts).split(\"\\n\");\n\t} catch {\n\t\treturn code.split(\"\\n\");\n\t}\n}\n\n/**\n * Get language identifier from file path extension.\n */\nexport function getLanguageFromPath(filePath: string): string | undefined {\n\tconst ext = filePath.split(\".\").pop()?.toLowerCase();\n\tif (!ext) return undefined;\n\n\tconst extToLang: Record<string, string> = {\n\t\tts: \"typescript\",\n\t\ttsx: \"typescript\",\n\t\tjs: \"javascript\",\n\t\tjsx: \"javascript\",\n\t\tmjs: \"javascript\",\n\t\tcjs: \"javascript\",\n\t\tpy: \"python\",\n\t\trb: \"ruby\",\n\t\trs: \"rust\",\n\t\tgo: \"go\",\n\t\tjava: \"java\",\n\t\tkt: \"kotlin\",\n\t\tswift: \"swift\",\n\t\tc: \"c\",\n\t\th: \"c\",\n\t\tcpp: \"cpp\",\n\t\tcc: \"cpp\",\n\t\tcxx: \"cpp\",\n\t\thpp: \"cpp\",\n\t\tcs: \"csharp\",\n\t\tphp: \"php\",\n\t\tsh: \"bash\",\n\t\tbash: \"bash\",\n\t\tzsh: \"bash\",\n\t\tfish: \"fish\",\n\t\tps1: \"powershell\",\n\t\tsql: \"sql\",\n\t\thtml: \"html\",\n\t\thtm: \"html\",\n\t\tcss: \"css\",\n\t\tscss: \"scss\",\n\t\tsass: \"sass\",\n\t\tless: \"less\",\n\t\tjson: \"json\",\n\t\tyaml: \"yaml\",\n\t\tyml: \"yaml\",\n\t\ttoml: \"toml\",\n\t\txml: \"xml\",\n\t\tmd: \"markdown\",\n\t\tmarkdown: \"markdown\",\n\t\tdockerfile: \"dockerfile\",\n\t\tmakefile: \"makefile\",\n\t\tcmake: \"cmake\",\n\t\tlua: \"lua\",\n\t\tperl: \"perl\",\n\t\tr: \"r\",\n\t\tscala: \"scala\",\n\t\tclj: \"clojure\",\n\t\tex: \"elixir\",\n\t\texs: \"elixir\",\n\t\terl: \"erlang\",\n\t\ths: \"haskell\",\n\t\tml: \"ocaml\",\n\t\tvim: \"vim\",\n\t\tgraphql: \"graphql\",\n\t\tproto: \"protobuf\",\n\t\ttf: \"hcl\",\n\t\thcl: \"hcl\",\n\t};\n\n\treturn extToLang[ext];\n}\n\nexport function getMarkdownTheme(): MarkdownTheme {\n\treturn {\n\t\theading: (text: string) => theme.fg(\"mdHeading\", text),\n\t\tlink: (text: string) => theme.fg(\"mdLink\", text),\n\t\tlinkUrl: (text: string) => theme.fg(\"mdLinkUrl\", text),\n\t\tcode: (text: string) => theme.fg(\"mdCode\", text),\n\t\tcodeBlock: (text: string) => theme.fg(\"mdCodeBlock\", text),\n\t\tcodeBlockBorder: (text: string) => theme.fg(\"mdCodeBlockBorder\", text),\n\t\tquote: (text: string) => theme.fg(\"mdQuote\", text),\n\t\tquoteBorder: (text: string) => theme.fg(\"mdQuoteBorder\", text),\n\t\thr: (text: string) => theme.fg(\"mdHr\", text),\n\t\tlistBullet: (text: string) => theme.fg(\"mdListBullet\", text),\n\t\tbold: (text: string) => theme.bold(text),\n\t\titalic: (text: string) => theme.italic(text),\n\t\tunderline: (text: string) => theme.underline(text),\n\t\tstrikethrough: (text: string) => chalk.strikethrough(text),\n\t\thighlightCode: (code: string, lang?: string): string[] => {\n\t\t\t// Validate language before highlighting to avoid stderr spam from cli-highlight\n\t\t\tconst validLang = lang && supportsLanguage(lang) ? lang : undefined;\n\t\t\t// Skip highlighting when no valid language is specified. cli-highlight's\n\t\t\t// auto-detection is unreliable and can misidentify prose as AppleScript,\n\t\t\t// LiveCodeServer, etc., coloring random English words as keywords.\n\t\t\tif (!validLang) {\n\t\t\t\treturn code.split(\"\\n\").map((line) => theme.fg(\"mdCodeBlock\", line));\n\t\t\t}\n\t\t\tconst opts = {\n\t\t\t\tlanguage: validLang,\n\t\t\t\tignoreIllegals: true,\n\t\t\t\ttheme: getCliHighlightTheme(theme),\n\t\t\t};\n\t\t\ttry {\n\t\t\t\treturn highlight(code, opts).split(\"\\n\");\n\t\t\t} catch {\n\t\t\t\treturn code.split(\"\\n\").map((line) => theme.fg(\"mdCodeBlock\", line));\n\t\t\t}\n\t\t},\n\t};\n}\n\nexport function getSelectListTheme(): SelectListTheme {\n\treturn {\n\t\tselectedPrefix: (text: string) => theme.fg(\"accent\", text),\n\t\tselectedText: (text: string) => theme.fg(\"accent\", text),\n\t\tdescription: (text: string) => theme.fg(\"muted\", text),\n\t\tscrollInfo: (text: string) => theme.fg(\"muted\", text),\n\t\tnoMatch: (text: string) => theme.fg(\"muted\", text),\n\t};\n}\n\nexport function getEditorTheme(): EditorTheme {\n\treturn {\n\t\tborderColor: (text: string) => theme.fg(\"borderMuted\", text),\n\t\tselectList: getSelectListTheme(),\n\t};\n}\n\nexport function getSettingsListTheme(): SettingsListTheme {\n\treturn {\n\t\tlabel: (text: string, selected: boolean) => (selected ? theme.fg(\"accent\", text) : text),\n\t\tvalue: (text: string, selected: boolean) => (selected ? theme.fg(\"accent\", text) : theme.fg(\"muted\", text)),\n\t\tdescription: (text: string) => theme.fg(\"dim\", text),\n\t\tcursor: theme.fg(\"accent\", \"→ \"),\n\t\thint: (text: string) => theme.fg(\"dim\", text),\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { ThinkingLevel } from \"@agent-forge/agent-core\";\nimport {\n\ttype EditorTheme,\n\tgetCapabilities,\n\ttype MarkdownTheme,\n\ttype RgbColor,\n\ttype SelectListTheme,\n\ttype SettingsListTheme,\n} from \"@agent-forge/tui\";\nimport chalk from \"chalk\";\nimport { type Static, Type } from \"typebox\";\nimport { Compile } from \"typebox/compile\";\nimport { getCustomThemesDir, getThemesDir } from \"../../../config.ts\";\nimport { t } from \"../../../core/i18n/index.ts\";\nimport type { SourceInfo } from \"../../../core/source-info.ts\";\nimport { closeWatcher, watchWithErrorHandler } from \"../../../utils/fs-watch.ts\";\nimport { highlight, supportsLanguage } from \"../../../utils/syntax-highlight.ts\";\nimport { stripBom } from \"../../../utils/text.ts\";\n\n// ============================================================================\n// Types & Schema\n// ============================================================================\n\nconst ColorValueSchema = Type.Union([\n\tType.String(), // hex \"#ff0000\", var ref \"primary\", or empty \"\"\n\tType.Integer({ minimum: 0, maximum: 255 }), // 256-color index\n]);\n\ntype ColorValue = Static<typeof ColorValueSchema>;\n\nconst ThemeJsonSchema = Type.Object({\n\t$schema: Type.Optional(Type.String()),\n\tname: Type.String(),\n\tvars: Type.Optional(Type.Record(Type.String(), ColorValueSchema)),\n\tcolors: Type.Object({\n\t\t// Core UI (10 colors)\n\t\taccent: ColorValueSchema,\n\t\tborder: ColorValueSchema,\n\t\tborderAccent: ColorValueSchema,\n\t\tborderMuted: ColorValueSchema,\n\t\tsuccess: ColorValueSchema,\n\t\terror: ColorValueSchema,\n\t\twarning: ColorValueSchema,\n\t\tmuted: ColorValueSchema,\n\t\tdim: ColorValueSchema,\n\t\ttext: ColorValueSchema,\n\t\tthinkingText: ColorValueSchema,\n\t\t// Backgrounds & Content Text (11 required, 3 optional)\n\t\tselectedBg: ColorValueSchema,\n\t\tscrollbarThumb: Type.Optional(ColorValueSchema),\n\t\tsearchMatchBg: Type.Optional(ColorValueSchema),\n\t\tsearchMatchText: Type.Optional(ColorValueSchema),\n\t\tuserMessageBg: ColorValueSchema,\n\t\tuserMessageText: ColorValueSchema,\n\t\tcustomMessageBg: ColorValueSchema,\n\t\tcustomMessageText: ColorValueSchema,\n\t\tcustomMessageLabel: ColorValueSchema,\n\t\ttoolPendingBg: ColorValueSchema,\n\t\ttoolSuccessBg: ColorValueSchema,\n\t\ttoolErrorBg: ColorValueSchema,\n\t\ttoolTitle: ColorValueSchema,\n\t\ttoolOutput: ColorValueSchema,\n\t\t// Markdown (10 colors)\n\t\tmdHeading: ColorValueSchema,\n\t\tmdLink: ColorValueSchema,\n\t\tmdLinkUrl: ColorValueSchema,\n\t\tmdCode: ColorValueSchema,\n\t\tmdCodeBlock: ColorValueSchema,\n\t\tmdCodeBlockBorder: ColorValueSchema,\n\t\tmdQuote: ColorValueSchema,\n\t\tmdQuoteBorder: ColorValueSchema,\n\t\tmdHr: ColorValueSchema,\n\t\tmdListBullet: ColorValueSchema,\n\t\t// Tool Diffs (3 colors)\n\t\ttoolDiffAdded: ColorValueSchema,\n\t\ttoolDiffRemoved: ColorValueSchema,\n\t\ttoolDiffContext: ColorValueSchema,\n\t\t// Syntax Highlighting (9 colors)\n\t\tsyntaxComment: ColorValueSchema,\n\t\tsyntaxKeyword: ColorValueSchema,\n\t\tsyntaxFunction: ColorValueSchema,\n\t\tsyntaxVariable: ColorValueSchema,\n\t\tsyntaxString: ColorValueSchema,\n\t\tsyntaxNumber: ColorValueSchema,\n\t\tsyntaxType: ColorValueSchema,\n\t\tsyntaxOperator: ColorValueSchema,\n\t\tsyntaxPunctuation: ColorValueSchema,\n\t\t// Thinking Level Borders (6 colors)\n\t\tthinkingOff: ColorValueSchema,\n\t\tthinkingMinimal: ColorValueSchema,\n\t\tthinkingLow: ColorValueSchema,\n\t\tthinkingMedium: ColorValueSchema,\n\t\tthinkingHigh: ColorValueSchema,\n\t\tthinkingXhigh: ColorValueSchema,\n\t\tthinkingMax: Type.Optional(ColorValueSchema),\n\t\t// Bash Mode (1 color)\n\t\tbashMode: ColorValueSchema,\n\t}),\n\texport: Type.Optional(\n\t\tType.Object({\n\t\t\tpageBg: Type.Optional(ColorValueSchema),\n\t\t\tcardBg: Type.Optional(ColorValueSchema),\n\t\t\tinfoBg: Type.Optional(ColorValueSchema),\n\t\t}),\n\t),\n});\n\ntype ThemeJson = Static<typeof ThemeJsonSchema>;\n\nconst validateThemeJson = Compile(ThemeJsonSchema);\n\nexport type ThemeColor =\n\t| \"accent\"\n\t| \"border\"\n\t| \"borderAccent\"\n\t| \"borderMuted\"\n\t| \"success\"\n\t| \"error\"\n\t| \"warning\"\n\t| \"muted\"\n\t| \"dim\"\n\t| \"text\"\n\t| \"thinkingText\"\n\t| \"searchMatchText\"\n\t| \"userMessageText\"\n\t| \"customMessageText\"\n\t| \"customMessageLabel\"\n\t| \"toolTitle\"\n\t| \"toolOutput\"\n\t| \"mdHeading\"\n\t| \"mdLink\"\n\t| \"mdLinkUrl\"\n\t| \"mdCode\"\n\t| \"mdCodeBlock\"\n\t| \"mdCodeBlockBorder\"\n\t| \"mdQuote\"\n\t| \"mdQuoteBorder\"\n\t| \"mdHr\"\n\t| \"mdListBullet\"\n\t| \"toolDiffAdded\"\n\t| \"toolDiffRemoved\"\n\t| \"toolDiffContext\"\n\t| \"syntaxComment\"\n\t| \"syntaxKeyword\"\n\t| \"syntaxFunction\"\n\t| \"syntaxVariable\"\n\t| \"syntaxString\"\n\t| \"syntaxNumber\"\n\t| \"syntaxType\"\n\t| \"syntaxOperator\"\n\t| \"syntaxPunctuation\"\n\t| \"thinkingOff\"\n\t| \"thinkingMinimal\"\n\t| \"thinkingLow\"\n\t| \"thinkingMedium\"\n\t| \"thinkingHigh\"\n\t| \"thinkingXhigh\"\n\t| \"thinkingMax\"\n\t| \"bashMode\";\n\nexport type ThemeBg =\n\t| \"selectedBg\"\n\t| \"scrollbarThumb\"\n\t| \"searchMatchBg\"\n\t| \"userMessageBg\"\n\t| \"customMessageBg\"\n\t| \"toolPendingBg\"\n\t| \"toolSuccessBg\"\n\t| \"toolErrorBg\";\n\ntype OptionalThemeColor = \"thinkingMax\" | \"searchMatchText\";\ntype OptionalThemeBg = \"scrollbarThumb\" | \"searchMatchBg\";\n\ntype ColorMode = \"truecolor\" | \"256color\";\n\n// ============================================================================\n// Color Utilities\n// ============================================================================\n\nfunction hexToRgb(hex: string): { r: number; g: number; b: number } {\n\tconst cleaned = hex.replace(\"#\", \"\");\n\tif (cleaned.length !== 6) {\n\t\tthrow new Error(t(\"Invalid hex color: {hex}\", { hex }));\n\t}\n\tconst r = parseInt(cleaned.substring(0, 2), 16);\n\tconst g = parseInt(cleaned.substring(2, 4), 16);\n\tconst b = parseInt(cleaned.substring(4, 6), 16);\n\tif (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {\n\t\tthrow new Error(t(\"Invalid hex color: {hex}\", { hex }));\n\t}\n\treturn { r, g, b };\n}\n\n// The 6x6x6 color cube channel values (indices 0-5)\nconst CUBE_VALUES = [0, 95, 135, 175, 215, 255];\n\n// Grayscale ramp values (indices 232-255, 24 grays from 8 to 238)\nconst GRAY_VALUES = Array.from({ length: 24 }, (_, i) => 8 + i * 10);\n\nfunction findClosestCubeIndex(value: number): number {\n\tlet minDist = Infinity;\n\tlet minIdx = 0;\n\tfor (let i = 0; i < CUBE_VALUES.length; i++) {\n\t\tconst dist = Math.abs(value - CUBE_VALUES[i]);\n\t\tif (dist < minDist) {\n\t\t\tminDist = dist;\n\t\t\tminIdx = i;\n\t\t}\n\t}\n\treturn minIdx;\n}\n\nfunction findClosestGrayIndex(gray: number): number {\n\tlet minDist = Infinity;\n\tlet minIdx = 0;\n\tfor (let i = 0; i < GRAY_VALUES.length; i++) {\n\t\tconst dist = Math.abs(gray - GRAY_VALUES[i]);\n\t\tif (dist < minDist) {\n\t\t\tminDist = dist;\n\t\t\tminIdx = i;\n\t\t}\n\t}\n\treturn minIdx;\n}\n\nfunction colorDistance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number {\n\t// Weighted Euclidean distance (human eye is more sensitive to green)\n\tconst dr = r1 - r2;\n\tconst dg = g1 - g2;\n\tconst db = b1 - b2;\n\treturn dr * dr * 0.299 + dg * dg * 0.587 + db * db * 0.114;\n}\n\nfunction rgbTo256(r: number, g: number, b: number): number {\n\t// Find closest color in the 6x6x6 cube\n\tconst rIdx = findClosestCubeIndex(r);\n\tconst gIdx = findClosestCubeIndex(g);\n\tconst bIdx = findClosestCubeIndex(b);\n\tconst cubeR = CUBE_VALUES[rIdx];\n\tconst cubeG = CUBE_VALUES[gIdx];\n\tconst cubeB = CUBE_VALUES[bIdx];\n\tconst cubeIndex = 16 + 36 * rIdx + 6 * gIdx + bIdx;\n\tconst cubeDist = colorDistance(r, g, b, cubeR, cubeG, cubeB);\n\n\t// Find closest grayscale\n\tconst gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);\n\tconst grayIdx = findClosestGrayIndex(gray);\n\tconst grayValue = GRAY_VALUES[grayIdx];\n\tconst grayIndex = 232 + grayIdx;\n\tconst grayDist = colorDistance(r, g, b, grayValue, grayValue, grayValue);\n\n\t// Check if color has noticeable saturation (hue matters)\n\t// If max-min spread is significant, prefer cube to preserve tint\n\tconst maxC = Math.max(r, g, b);\n\tconst minC = Math.min(r, g, b);\n\tconst spread = maxC - minC;\n\n\t// Only consider grayscale if color is nearly neutral (spread < 10)\n\t// AND grayscale is actually closer\n\tif (spread < 10 && grayDist < cubeDist) {\n\t\treturn grayIndex;\n\t}\n\n\treturn cubeIndex;\n}\n\nfunction hexTo256(hex: string): number {\n\tconst { r, g, b } = hexToRgb(hex);\n\treturn rgbTo256(r, g, b);\n}\n\nfunction fgAnsi(color: string | number, mode: ColorMode): string {\n\tif (color === \"\") return \"\\x1b[39m\";\n\tif (typeof color === \"number\") return `\\x1b[38;5;${color}m`;\n\tif (color.startsWith(\"#\")) {\n\t\tif (mode === \"truecolor\") {\n\t\t\tconst { r, g, b } = hexToRgb(color);\n\t\t\treturn `\\x1b[38;2;${r};${g};${b}m`;\n\t\t} else {\n\t\t\tconst index = hexTo256(color);\n\t\t\treturn `\\x1b[38;5;${index}m`;\n\t\t}\n\t}\n\tthrow new Error(t(\"Invalid color value: {color}\", { color }));\n}\n\nfunction bgAnsi(color: string | number, mode: ColorMode): string {\n\tif (color === \"\") return \"\\x1b[49m\";\n\tif (typeof color === \"number\") return `\\x1b[48;5;${color}m`;\n\tif (color.startsWith(\"#\")) {\n\t\tif (mode === \"truecolor\") {\n\t\t\tconst { r, g, b } = hexToRgb(color);\n\t\t\treturn `\\x1b[48;2;${r};${g};${b}m`;\n\t\t} else {\n\t\t\tconst index = hexTo256(color);\n\t\t\treturn `\\x1b[48;5;${index}m`;\n\t\t}\n\t}\n\tthrow new Error(t(\"Invalid color value: {color}\", { color }));\n}\n\nfunction resolveVarRefs(\n\tvalue: ColorValue,\n\tvars: Record<string, ColorValue>,\n\tvisited = new Set<string>(),\n): string | number {\n\tif (typeof value === \"number\" || value === \"\" || value.startsWith(\"#\")) {\n\t\treturn value;\n\t}\n\tif (visited.has(value)) {\n\t\tthrow new Error(t(\"Circular variable reference detected: {value}\", { value }));\n\t}\n\tif (!(value in vars)) {\n\t\tthrow new Error(t(\"Variable reference not found: {value}\", { value }));\n\t}\n\tvisited.add(value);\n\treturn resolveVarRefs(vars[value], vars, visited);\n}\n\nfunction resolveThemeColors<T extends Record<string, ColorValue>>(\n\tcolors: T,\n\tvars: Record<string, ColorValue> = {},\n): Record<keyof T, string | number> {\n\tconst resolved: Record<string, string | number> = {};\n\tfor (const [key, value] of Object.entries(colors)) {\n\t\tresolved[key] = resolveVarRefs(value, vars);\n\t}\n\treturn resolved as Record<keyof T, string | number>;\n}\n\nfunction withThemeColorFallbacks(colors: ThemeJson[\"colors\"]): ThemeJson[\"colors\"] & {\n\tthinkingMax: ColorValue;\n\tscrollbarThumb: ColorValue;\n\tsearchMatchBg: ColorValue;\n\tsearchMatchText: ColorValue;\n} {\n\treturn {\n\t\t...colors,\n\t\tthinkingMax: colors.thinkingMax ?? colors.thinkingXhigh,\n\t\tscrollbarThumb: colors.scrollbarThumb ?? colors.selectedBg,\n\t\tsearchMatchBg: colors.searchMatchBg ?? colors.selectedBg,\n\t\tsearchMatchText: colors.searchMatchText ?? colors.text,\n\t};\n}\n\n// ============================================================================\n// Theme Class\n// ============================================================================\n\nexport class Theme {\n\treadonly name?: string;\n\treadonly sourcePath?: string;\n\tsourceInfo?: SourceInfo;\n\tprivate fgColors: Map<ThemeColor, string>;\n\tprivate bgColors: Map<ThemeBg, string>;\n\tprivate mode: ColorMode;\n\n\tconstructor(\n\t\tfgColors: Record<Exclude<ThemeColor, OptionalThemeColor>, string | number> &\n\t\t\tPartial<Record<OptionalThemeColor, string | number>>,\n\t\tbgColors: Record<Exclude<ThemeBg, OptionalThemeBg>, string | number> &\n\t\t\tPartial<Record<OptionalThemeBg, string | number>>,\n\t\tmode: ColorMode,\n\t\toptions: { name?: string; sourcePath?: string; sourceInfo?: SourceInfo } = {},\n\t) {\n\t\tthis.name = options.name;\n\t\tthis.sourcePath = options.sourcePath;\n\t\tthis.sourceInfo = options.sourceInfo;\n\t\tthis.mode = mode;\n\t\tthis.fgColors = new Map();\n\t\tconst colors = {\n\t\t\t...fgColors,\n\t\t\tthinkingMax: fgColors.thinkingMax ?? fgColors.thinkingXhigh,\n\t\t\tsearchMatchText: fgColors.searchMatchText ?? fgColors.text,\n\t\t};\n\t\tfor (const [key, value] of Object.entries(colors) as [ThemeColor, string | number][]) {\n\t\t\tthis.fgColors.set(key, fgAnsi(value, mode));\n\t\t}\n\t\tthis.bgColors = new Map();\n\t\tconst backgrounds = {\n\t\t\t...bgColors,\n\t\t\tscrollbarThumb: bgColors.scrollbarThumb ?? bgColors.selectedBg,\n\t\t\tsearchMatchBg: bgColors.searchMatchBg ?? bgColors.selectedBg,\n\t\t};\n\t\tfor (const [key, value] of Object.entries(backgrounds) as [ThemeBg, string | number][]) {\n\t\t\tthis.bgColors.set(key, bgAnsi(value, mode));\n\t\t}\n\t}\n\n\tfg(color: ThemeColor, text: string): string {\n\t\tconst ansi = this.fgColors.get(color);\n\t\tif (!ansi) throw new Error(t(\"Unknown theme color: {color}\", { color }));\n\t\treturn `${ansi}${text}\\x1b[39m`; // Reset only foreground color\n\t}\n\n\tbg(color: ThemeBg, text: string): string {\n\t\tconst ansi = this.bgColors.get(color);\n\t\tif (!ansi) throw new Error(t(\"Unknown theme background color: {color}\", { color }));\n\t\treturn `${ansi}${text}\\x1b[49m`; // Reset only background color\n\t}\n\n\tbold(text: string): string {\n\t\treturn chalk.bold(text);\n\t}\n\n\titalic(text: string): string {\n\t\treturn chalk.italic(text);\n\t}\n\n\tunderline(text: string): string {\n\t\treturn chalk.underline(text);\n\t}\n\n\tinverse(text: string): string {\n\t\treturn chalk.inverse(text);\n\t}\n\n\tstrikethrough(text: string): string {\n\t\treturn chalk.strikethrough(text);\n\t}\n\n\tgetFgAnsi(color: ThemeColor): string {\n\t\tconst ansi = this.fgColors.get(color);\n\t\tif (!ansi) throw new Error(t(\"Unknown theme color: {color}\", { color }));\n\t\treturn ansi;\n\t}\n\n\tgetBgAnsi(color: ThemeBg): string {\n\t\tconst ansi = this.bgColors.get(color);\n\t\tif (!ansi) throw new Error(t(\"Unknown theme background color: {color}\", { color }));\n\t\treturn ansi;\n\t}\n\n\tgetColorMode(): ColorMode {\n\t\treturn this.mode;\n\t}\n\n\tgetThinkingBorderColor(level: ThinkingLevel): (str: string) => string {\n\t\t// Map thinking levels to dedicated theme colors\n\t\tswitch (level) {\n\t\t\tcase \"off\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingOff\", str);\n\t\t\tcase \"minimal\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingMinimal\", str);\n\t\t\tcase \"low\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingLow\", str);\n\t\t\tcase \"medium\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingMedium\", str);\n\t\t\tcase \"high\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingHigh\", str);\n\t\t\tcase \"xhigh\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingXhigh\", str);\n\t\t\tcase \"max\":\n\t\t\t\treturn (str: string) => this.fg(\"thinkingMax\", str);\n\t\t\tdefault:\n\t\t\t\treturn (str: string) => this.fg(\"thinkingOff\", str);\n\t\t}\n\t}\n\n\tgetBashModeBorderColor(): (str: string) => string {\n\t\treturn (str: string) => this.fg(\"bashMode\", str);\n\t}\n}\n\n// ============================================================================\n// Theme Loading\n// ============================================================================\n\nlet BUILTIN_THEMES: Record<string, ThemeJson> | undefined;\n\nfunction getBuiltinThemes(): Record<string, ThemeJson> {\n\tif (!BUILTIN_THEMES) {\n\t\tconst themesDir = getThemesDir();\n\t\tconst darkPath = path.join(themesDir, \"dark.json\");\n\t\tconst lightPath = path.join(themesDir, \"light.json\");\n\t\tBUILTIN_THEMES = {\n\t\t\tdark: JSON.parse(stripBom(fs.readFileSync(darkPath, \"utf-8\"))) as ThemeJson,\n\t\t\tlight: JSON.parse(stripBom(fs.readFileSync(lightPath, \"utf-8\"))) as ThemeJson,\n\t\t};\n\t}\n\treturn BUILTIN_THEMES;\n}\n\nexport function getAvailableThemes(): string[] {\n\treturn getAvailableThemesWithPaths().map(({ name }) => name);\n}\n\nexport interface ThemeInfo {\n\tname: string;\n\tpath: string | undefined;\n}\n\nexport function getAvailableThemesWithPaths(): ThemeInfo[] {\n\tconst themesDir = getThemesDir();\n\tconst result: ThemeInfo[] = [];\n\tconst seen = new Set<string>();\n\tconst addTheme = (themeInfo: ThemeInfo) => {\n\t\tif (seen.has(themeInfo.name)) {\n\t\t\treturn;\n\t\t}\n\t\tseen.add(themeInfo.name);\n\t\tresult.push(themeInfo);\n\t};\n\n\t// Built-in themes\n\tfor (const name of Object.keys(getBuiltinThemes())) {\n\t\taddTheme({ name, path: path.join(themesDir, `${name}.json`) });\n\t}\n\n\t// Custom themes\n\tfor (const themeInfo of getCustomThemeInfos()) {\n\t\taddTheme(themeInfo);\n\t}\n\n\tfor (const [name, theme] of registeredThemes.entries()) {\n\t\taddTheme({ name, path: theme.sourcePath });\n\t}\n\n\treturn result.sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction getCustomThemeInfos(): ThemeInfo[] {\n\tconst customThemesDir = getCustomThemesDir();\n\tconst result: ThemeInfo[] = [];\n\tif (!fs.existsSync(customThemesDir)) {\n\t\treturn result;\n\t}\n\n\tfor (const file of fs.readdirSync(customThemesDir)) {\n\t\tif (!file.endsWith(\".json\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst themePath = path.join(customThemesDir, file);\n\t\ttry {\n\t\t\tconst customTheme = loadThemeFromPath(themePath);\n\t\t\tif (customTheme.name) {\n\t\t\t\tresult.push({ name: customTheme.name, path: themePath });\n\t\t\t}\n\t\t} catch {\n\t\t\t// Invalid themes are ignored here; the resource loader reports them\n\t\t\t// during normal startup/reload.\n\t\t}\n\t}\n\treturn result;\n}\n\nfunction assertThemeNameIsValid(name: string): void {\n\tif (name.includes(\"/\")) {\n\t\tthrow new Error(\n\t\t\tt(\n\t\t\t\t'Invalid theme name \"{name}\": theme names cannot contain \"/\" because it is reserved for automatic light/dark theme settings.',\n\t\t\t\t{\n\t\t\t\t\tname,\n\t\t\t\t},\n\t\t\t),\n\t\t);\n\t}\n}\n\nfunction parseThemeJson(label: string, json: unknown): ThemeJson {\n\tif (!validateThemeJson.Check(json)) {\n\t\tconst errors = Array.from(validateThemeJson.Errors(json));\n\t\tconst missingColors = new Set<string>();\n\t\tconst otherErrors: string[] = [];\n\n\t\tfor (const error of errors) {\n\t\t\tif (error.keyword === \"required\" && error.instancePath === \"/colors\") {\n\t\t\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\t\t\tfor (const requiredProperty of requiredProperties ?? []) {\n\t\t\t\t\tmissingColors.add(requiredProperty);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst path = error.instancePath || \"/\";\n\t\t\totherErrors.push(` - ${path}: ${error.message}`);\n\t\t}\n\n\t\tlet errorMessage = `${t('Invalid theme \"{label}\":', { label })}\\n`;\n\t\tif (missingColors.size > 0) {\n\t\t\terrorMessage += `\\n${t(\"Missing required color tokens:\")}\\n`;\n\t\t\terrorMessage += Array.from(missingColors)\n\t\t\t\t.sort()\n\t\t\t\t.map((color) => ` - ${color}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\terrorMessage += `\\n\\n${t('Please add these colors to your theme\\'s \"colors\" object.')}`;\n\t\t\terrorMessage += `\\n${t(\"See the built-in themes (dark.json, light.json) for reference values.\")}`;\n\t\t}\n\t\tif (otherErrors.length > 0) {\n\t\t\terrorMessage += `\\n\\n${t(\"Other errors:\")}\\n${otherErrors.join(\"\\n\")}`;\n\t\t}\n\n\t\tthrow new Error(errorMessage);\n\t}\n\n\tconst themeJson = json as ThemeJson;\n\tassertThemeNameIsValid(themeJson.name);\n\treturn themeJson;\n}\n\nfunction parseThemeJsonContent(label: string, content: string): ThemeJson {\n\tlet json: unknown;\n\ttry {\n\t\tjson = JSON.parse(stripBom(content));\n\t} catch (error) {\n\t\tthrow new Error(t(\"Failed to parse theme {label}: {error}\", { label, error: String(error) }));\n\t}\n\treturn parseThemeJson(label, json);\n}\n\nfunction loadThemeJson(name: string): ThemeJson {\n\tconst builtinThemes = getBuiltinThemes();\n\tif (name in builtinThemes) {\n\t\treturn builtinThemes[name];\n\t}\n\tconst registeredTheme = registeredThemes.get(name);\n\tif (registeredTheme?.sourcePath) {\n\t\tconst content = fs.readFileSync(registeredTheme.sourcePath, \"utf-8\");\n\t\treturn parseThemeJsonContent(registeredTheme.sourcePath, content);\n\t}\n\tif (registeredTheme) {\n\t\tthrow new Error(t('Theme \"{name}\" does not have a source path for export', { name }));\n\t}\n\tconst customThemesDir = getCustomThemesDir();\n\tconst themePath = path.join(customThemesDir, `${name}.json`);\n\tif (!fs.existsSync(themePath)) {\n\t\tthrow new Error(t(\"Theme not found: {name}\", { name }));\n\t}\n\tconst content = fs.readFileSync(themePath, \"utf-8\");\n\treturn parseThemeJsonContent(name, content);\n}\n\nfunction createTheme(themeJson: ThemeJson, mode?: ColorMode, sourcePath?: string): Theme {\n\tconst colorMode = mode ?? (getCapabilities().trueColor ? \"truecolor\" : \"256color\");\n\tconst resolvedColors = resolveThemeColors(withThemeColorFallbacks(themeJson.colors), themeJson.vars);\n\tconst fgColors: Record<ThemeColor, string | number> = {} as Record<ThemeColor, string | number>;\n\tconst bgColors: Record<ThemeBg, string | number> = {} as Record<ThemeBg, string | number>;\n\tconst bgColorKeys: Set<string> = new Set([\n\t\t\"selectedBg\",\n\t\t\"scrollbarThumb\",\n\t\t\"searchMatchBg\",\n\t\t\"userMessageBg\",\n\t\t\"customMessageBg\",\n\t\t\"toolPendingBg\",\n\t\t\"toolSuccessBg\",\n\t\t\"toolErrorBg\",\n\t]);\n\tfor (const [key, value] of Object.entries(resolvedColors)) {\n\t\tif (bgColorKeys.has(key)) {\n\t\t\tbgColors[key as ThemeBg] = value;\n\t\t} else {\n\t\t\tfgColors[key as ThemeColor] = value;\n\t\t}\n\t}\n\treturn new Theme(fgColors, bgColors, colorMode, {\n\t\tname: themeJson.name,\n\t\tsourcePath,\n\t});\n}\n\nexport function loadThemeFromPath(themePath: string, mode?: ColorMode): Theme {\n\tconst content = fs.readFileSync(themePath, \"utf-8\");\n\tconst themeJson = parseThemeJsonContent(themePath, content);\n\treturn createTheme(themeJson, mode, themePath);\n}\n\nfunction loadTheme(name: string, mode?: ColorMode): Theme {\n\tconst registeredTheme = registeredThemes.get(name);\n\tif (registeredTheme) {\n\t\treturn registeredTheme;\n\t}\n\tconst themeJson = loadThemeJson(name);\n\treturn createTheme(themeJson, mode);\n}\n\nexport function getThemeByName(name: string): Theme | undefined {\n\ttry {\n\t\treturn loadTheme(name);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport type TerminalTheme = \"dark\" | \"light\";\n\nexport function parseAutoThemeSetting(\n\tthemeSetting: string | undefined,\n): { lightTheme: string; darkTheme: string } | undefined {\n\tif (!themeSetting) return undefined;\n\tconst slashIndex = themeSetting.indexOf(\"/\");\n\tif (slashIndex === -1 || themeSetting.indexOf(\"/\", slashIndex + 1) !== -1) {\n\t\treturn undefined;\n\t}\n\n\tconst lightTheme = themeSetting.slice(0, slashIndex).trim();\n\tconst darkTheme = themeSetting.slice(slashIndex + 1).trim();\n\tif (!lightTheme || !darkTheme) {\n\t\treturn undefined;\n\t}\n\treturn { lightTheme, darkTheme };\n}\n\nexport function resolveThemeSetting(\n\tthemeSetting: string | undefined,\n\tterminalTheme: TerminalTheme,\n): string | undefined {\n\tconst autoTheme = parseAutoThemeSetting(themeSetting);\n\tif (autoTheme) {\n\t\treturn terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme;\n\t}\n\tif (themeSetting?.includes(\"/\")) return undefined;\n\tif (typeof themeSetting === \"string\") return themeSetting;\n\treturn undefined;\n}\n\nexport interface TerminalThemeDetection {\n\ttheme: TerminalTheme;\n\tsource: \"terminal background\" | \"COLORFGBG\" | \"fallback\";\n\tdetail: string;\n\tconfidence: \"high\" | \"low\";\n}\n\nexport interface TerminalThemeDetectionOptions {\n\tenv?: NodeJS.ProcessEnv;\n}\n\nexport interface TerminalBackgroundThemeDetector {\n\tqueryTerminalBackgroundColor({ timeoutMs }: { timeoutMs: number }): Promise<RgbColor | undefined>;\n}\n\nexport interface TerminalAutoThemeDetector extends TerminalBackgroundThemeDetector {\n\tqueryTerminalColorScheme?({ timeoutMs }: { timeoutMs: number }): Promise<TerminalTheme | undefined>;\n}\n\nexport interface TerminalBackgroundThemeDetectionOptions extends TerminalThemeDetectionOptions {\n\tui: TerminalBackgroundThemeDetector;\n\ttimeoutMs: number;\n}\n\nexport interface TerminalAutoThemeDetectionOptions extends TerminalThemeDetectionOptions {\n\tui: TerminalAutoThemeDetector;\n\ttimeoutMs: number;\n}\n\nfunction getColorFgBgBackgroundIndex(colorfgbg: string): number | undefined {\n\tconst parts = colorfgbg.split(\";\");\n\tfor (let i = parts.length - 1; i >= 0; i--) {\n\t\tconst bg = parseInt(parts[i].trim(), 10);\n\t\tif (Number.isInteger(bg) && bg >= 0 && bg <= 255) {\n\t\t\treturn bg;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction getRgbColorLuminance({ r, g, b }: RgbColor): number {\n\tconst toLinear = (channel: number) => {\n\t\tconst value = channel / 255;\n\t\treturn value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;\n\t};\n\treturn 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n}\n\nfunction getAnsiColorLuminance(index: number): number {\n\treturn getRgbColorLuminance(hexToRgb(ansi256ToHex(index)));\n}\n\nexport function getThemeForRgbColor(rgb: RgbColor): TerminalTheme {\n\treturn getRgbColorLuminance(rgb) >= 0.5 ? \"light\" : \"dark\";\n}\n\nexport function detectTerminalBackgroundFromEnv(options: TerminalThemeDetectionOptions = {}): TerminalThemeDetection {\n\tconst env = options.env ?? process.env;\n\tconst colorfgbg = env.COLORFGBG || \"\";\n\tconst bg = getColorFgBgBackgroundIndex(colorfgbg);\n\tif (bg !== undefined) {\n\t\treturn {\n\t\t\ttheme: getAnsiColorLuminance(bg) >= 0.5 ? \"light\" : \"dark\",\n\t\t\tsource: \"COLORFGBG\",\n\t\t\tdetail: `background color index ${bg}`,\n\t\t\tconfidence: \"high\",\n\t\t};\n\t}\n\n\treturn {\n\t\ttheme: \"dark\",\n\t\tsource: \"fallback\",\n\t\tdetail: \"no terminal background hint found\",\n\t\tconfidence: \"low\",\n\t};\n}\n\nexport async function detectTerminalBackgroundTheme({\n\tui,\n\ttimeoutMs,\n\tenv,\n}: TerminalBackgroundThemeDetectionOptions): Promise<TerminalThemeDetection> {\n\ttry {\n\t\tconst rgb = await ui.queryTerminalBackgroundColor({ timeoutMs });\n\t\tif (rgb) {\n\t\t\treturn {\n\t\t\t\ttheme: getThemeForRgbColor(rgb),\n\t\t\t\tsource: \"terminal background\",\n\t\t\t\tdetail: `OSC 11 background rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,\n\t\t\t\tconfidence: \"high\",\n\t\t\t};\n\t\t}\n\t} catch {\n\t\t// Fall back to environment-based detection when the terminal query fails.\n\t}\n\n\treturn detectTerminalBackgroundFromEnv({ env });\n}\n\nexport async function detectTerminalThemeForAuto({\n\tui,\n\ttimeoutMs,\n\tenv,\n}: TerminalAutoThemeDetectionOptions): Promise<TerminalTheme> {\n\tlet colorSchemePromise: Promise<TerminalTheme | undefined> | undefined;\n\ttry {\n\t\tcolorSchemePromise = ui.queryTerminalColorScheme?.({ timeoutMs });\n\t} catch {\n\t\t// Fall back to OSC 11 / COLORFGBG detection when starting the color-scheme query fails.\n\t}\n\tconst backgroundThemePromise = detectTerminalBackgroundTheme({ ui, timeoutMs, env });\n\n\ttry {\n\t\tconst colorScheme = await colorSchemePromise;\n\t\tif (colorScheme) return colorScheme;\n\t} catch {\n\t\t// Fall back to the concurrently queried OSC 11 / COLORFGBG detection.\n\t}\n\treturn (await backgroundThemePromise).theme;\n}\n\nexport function getDefaultTheme(): string {\n\treturn detectTerminalBackgroundFromEnv().theme;\n}\n\n// ============================================================================\n// Global Theme Instance\n// ============================================================================\n\n// Use globalThis to share theme across module loaders (tsx + jiti in dev mode)\nconst THEME_KEY = Symbol.for(\"@agent-forge/agent-forge:theme\");\nconst THEME_KEY_OLD = Symbol.for(\"@mariozechner/pi-coding-agent:theme\");\n\n// Export theme as a getter that reads from globalThis\n// This ensures all module instances (tsx, jiti) see the same theme\nexport const theme: Theme = new Proxy({} as Theme, {\n\tget(_target, prop) {\n\t\tconst active = (globalThis as Record<symbol, Theme>)[THEME_KEY];\n\t\tif (!active) throw new Error(t(\"Theme not initialized. Call initTheme() first.\"));\n\t\treturn (active as unknown as Record<string | symbol, unknown>)[prop];\n\t},\n});\n\nfunction setGlobalTheme(t: Theme): void {\n\t(globalThis as Record<symbol, Theme>)[THEME_KEY] = t;\n\t(globalThis as Record<symbol, Theme>)[THEME_KEY_OLD] = t;\n}\n\nlet currentThemeName: string | undefined;\nlet themeWatcher: fs.FSWatcher | undefined;\nlet themeReloadTimer: NodeJS.Timeout | undefined;\nlet onThemeChangeCallback: (() => void) | undefined;\nconst registeredThemes = new Map<string, Theme>();\n\nexport function setRegisteredThemes(themes: Theme[]): void {\n\tregisteredThemes.clear();\n\tfor (const theme of themes) {\n\t\tif (theme.name) {\n\t\t\tassertThemeNameIsValid(theme.name);\n\t\t\tregisteredThemes.set(theme.name, theme);\n\t\t}\n\t}\n}\n\nexport function initTheme(themeName?: string, enableWatcher: boolean = false): void {\n\tconst name = themeName ?? getDefaultTheme();\n\tcurrentThemeName = name;\n\ttry {\n\t\tsetGlobalTheme(loadTheme(name));\n\t\tif (enableWatcher) {\n\t\t\tstartThemeWatcher();\n\t\t}\n\t} catch (_error) {\n\t\t// Theme is invalid - fall back to dark theme silently\n\t\tcurrentThemeName = \"dark\";\n\t\tsetGlobalTheme(loadTheme(\"dark\"));\n\t\t// Don't start watcher for fallback theme\n\t}\n}\n\nexport function setTheme(name: string, enableWatcher: boolean = false): { success: boolean; error?: string } {\n\tcurrentThemeName = name;\n\ttry {\n\t\tsetGlobalTheme(loadTheme(name));\n\t\tif (enableWatcher) {\n\t\t\tstartThemeWatcher();\n\t\t}\n\t\tif (onThemeChangeCallback) {\n\t\t\tonThemeChangeCallback();\n\t\t}\n\t\treturn { success: true };\n\t} catch (error) {\n\t\t// Theme is invalid - fall back to dark theme\n\t\tcurrentThemeName = \"dark\";\n\t\tsetGlobalTheme(loadTheme(\"dark\"));\n\t\t// Don't start watcher for fallback theme\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\t// v8 ignore next -- loadTheme rejections are always Error instances\n\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t};\n\t}\n}\n\nexport function setThemeInstance(themeInstance: Theme): void {\n\tsetGlobalTheme(themeInstance);\n\tcurrentThemeName = \"<in-memory>\";\n\tstopThemeWatcher(); // Can't watch a direct instance\n\tif (onThemeChangeCallback) {\n\t\tonThemeChangeCallback();\n\t}\n}\n\nexport function onThemeChange(callback: () => void): void {\n\tonThemeChangeCallback = callback;\n}\n\nfunction startThemeWatcher(): void {\n\tstopThemeWatcher();\n\n\t// Only watch if it's a custom theme (not built-in)\n\tif (!currentThemeName || currentThemeName === \"dark\" || currentThemeName === \"light\") {\n\t\treturn;\n\t}\n\n\tconst customThemesDir = getCustomThemesDir();\n\tconst watchedThemeName = currentThemeName;\n\tconst watchedFileName = `${watchedThemeName}.json`;\n\tconst themeFile = path.join(customThemesDir, watchedFileName);\n\n\t// Only watch if the file exists\n\tif (!fs.existsSync(themeFile)) {\n\t\treturn;\n\t}\n\n\tconst scheduleReload = () => {\n\t\tif (themeReloadTimer) {\n\t\t\tclearTimeout(themeReloadTimer);\n\t\t}\n\t\tthemeReloadTimer = setTimeout(() => {\n\t\t\tthemeReloadTimer = undefined;\n\n\t\t\t// Ignore stale timers after switching themes or stopping the watcher\n\t\t\tif (currentThemeName !== watchedThemeName) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Keep the last successfully loaded theme active if the file is temporarily missing\n\t\t\tif (!fs.existsSync(themeFile)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Reload the theme from disk and refresh the registry cache\n\t\t\t\tconst reloadedTheme = loadThemeFromPath(themeFile);\n\t\t\t\tregisteredThemes.set(watchedThemeName, reloadedTheme);\n\t\t\t\tsetGlobalTheme(reloadedTheme);\n\t\t\t\t// Notify callback (to invalidate UI)\n\t\t\t\tif (onThemeChangeCallback) {\n\t\t\t\t\tonThemeChangeCallback();\n\t\t\t\t}\n\t\t\t} catch (_error) {\n\t\t\t\t// Ignore errors (file might be in invalid state while being edited)\n\t\t\t}\n\t\t}, 100);\n\t};\n\n\tthemeWatcher =\n\t\twatchWithErrorHandler(\n\t\t\tcustomThemesDir,\n\t\t\t(_eventType, filename) => {\n\t\t\t\tif (currentThemeName !== watchedThemeName) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!filename) {\n\t\t\t\t\tscheduleReload();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (filename !== watchedFileName) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tscheduleReload();\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tcloseWatcher(themeWatcher);\n\t\t\t\tthemeWatcher = undefined;\n\t\t\t},\n\t\t) ?? undefined;\n}\n\nexport function stopThemeWatcher(): void {\n\tif (themeReloadTimer) {\n\t\tclearTimeout(themeReloadTimer);\n\t\tthemeReloadTimer = undefined;\n\t}\n\tcloseWatcher(themeWatcher);\n\tthemeWatcher = undefined;\n}\n\n// ============================================================================\n// HTML Export Helpers\n// ============================================================================\n\n/**\n * Convert a 256-color index to hex string.\n * Indices 0-15: basic colors (approximate)\n * Indices 16-231: 6x6x6 color cube\n * Indices 232-255: grayscale ramp\n */\nfunction ansi256ToHex(index: number): string {\n\t// Basic colors (0-15) - approximate common terminal values\n\tconst basicColors = [\n\t\t\"#000000\",\n\t\t\"#800000\",\n\t\t\"#008000\",\n\t\t\"#808000\",\n\t\t\"#000080\",\n\t\t\"#800080\",\n\t\t\"#008080\",\n\t\t\"#c0c0c0\",\n\t\t\"#808080\",\n\t\t\"#ff0000\",\n\t\t\"#00ff00\",\n\t\t\"#ffff00\",\n\t\t\"#0000ff\",\n\t\t\"#ff00ff\",\n\t\t\"#00ffff\",\n\t\t\"#ffffff\",\n\t];\n\tif (index < 16) {\n\t\treturn basicColors[index];\n\t}\n\n\t// Color cube (16-231): 6x6x6 = 216 colors\n\tif (index < 232) {\n\t\tconst cubeIndex = index - 16;\n\t\tconst r = Math.floor(cubeIndex / 36);\n\t\tconst g = Math.floor((cubeIndex % 36) / 6);\n\t\tconst b = cubeIndex % 6;\n\t\tconst toHex = (n: number) => (n === 0 ? 0 : 55 + n * 40).toString(16).padStart(2, \"0\");\n\t\treturn `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n\t}\n\n\t// Grayscale (232-255): 24 shades\n\tconst gray = 8 + (index - 232) * 10;\n\tconst grayHex = gray.toString(16).padStart(2, \"0\");\n\treturn `#${grayHex}${grayHex}${grayHex}`;\n}\n\n/**\n * Get resolved theme colors as CSS-compatible hex strings.\n * Used by HTML export to generate CSS custom properties.\n */\nexport function getResolvedThemeColors(themeName?: string): Record<string, string> {\n\tconst name = themeName ?? currentThemeName ?? getDefaultTheme();\n\tconst isLight = name === \"light\";\n\tconst themeJson = loadThemeJson(name);\n\tconst resolved = resolveThemeColors(withThemeColorFallbacks(themeJson.colors), themeJson.vars);\n\n\t// Default text color for empty values (terminal uses default fg color)\n\tconst defaultText = isLight ? \"#000000\" : \"#e5e5e7\";\n\n\tconst cssColors: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(resolved)) {\n\t\tif (typeof value === \"number\") {\n\t\t\tcssColors[key] = ansi256ToHex(value);\n\t\t} else if (value === \"\") {\n\t\t\t// Empty means default terminal color - use sensible fallback for HTML\n\t\t\tcssColors[key] = defaultText;\n\t\t} else {\n\t\t\tcssColors[key] = value;\n\t\t}\n\t}\n\treturn cssColors;\n}\n\n/**\n * Check if a theme is a \"light\" theme (for CSS that needs light/dark variants).\n */\nexport function isLightTheme(themeName?: string): boolean {\n\t// Currently just check the name - could be extended to analyze colors\n\treturn themeName === \"light\";\n}\n\n/**\n * Get explicit export colors from theme JSON, if specified.\n * Returns undefined for each color that isn't explicitly set.\n */\nexport function getThemeExportColors(themeName?: string): {\n\tpageBg?: string;\n\tcardBg?: string;\n\tinfoBg?: string;\n} {\n\tconst name = themeName ?? currentThemeName ?? getDefaultTheme();\n\ttry {\n\t\tconst themeJson = loadThemeJson(name);\n\t\tconst exportSection = themeJson.export;\n\t\tif (!exportSection) return {};\n\n\t\tconst vars = themeJson.vars ?? {};\n\t\tconst resolve = (value: ColorValue | undefined): string | undefined => {\n\t\t\tif (value === undefined) return undefined;\n\t\t\tconst resolved = resolveVarRefs(value, vars);\n\t\t\tif (typeof resolved === \"number\") return ansi256ToHex(resolved);\n\t\t\tif (resolved === \"\") return undefined;\n\t\t\treturn resolved;\n\t\t};\n\n\t\treturn {\n\t\t\tpageBg: resolve(exportSection.pageBg),\n\t\t\tcardBg: resolve(exportSection.cardBg),\n\t\t\tinfoBg: resolve(exportSection.infoBg),\n\t\t};\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n// ============================================================================\n// TUI Helpers\n// ============================================================================\n\ntype CliHighlightTheme = Record<string, (s: string) => string>;\n\nlet cachedHighlightThemeFor: Theme | undefined;\nlet cachedCliHighlightTheme: CliHighlightTheme | undefined;\n\nfunction buildCliHighlightTheme(t: Theme): CliHighlightTheme {\n\treturn {\n\t\tkeyword: (s: string) => t.fg(\"syntaxKeyword\", s),\n\t\tbuilt_in: (s: string) => t.fg(\"syntaxType\", s),\n\t\tliteral: (s: string) => t.fg(\"syntaxNumber\", s),\n\t\tnumber: (s: string) => t.fg(\"syntaxNumber\", s),\n\t\tregexp: (s: string) => t.fg(\"syntaxString\", s),\n\t\tstring: (s: string) => t.fg(\"syntaxString\", s),\n\t\tcomment: (s: string) => t.fg(\"syntaxComment\", s),\n\t\tdoctag: (s: string) => t.fg(\"syntaxComment\", s),\n\t\tmeta: (s: string) => t.fg(\"muted\", s),\n\t\tfunction: (s: string) => t.fg(\"syntaxFunction\", s),\n\t\ttitle: (s: string) => t.fg(\"syntaxFunction\", s),\n\t\tclass: (s: string) => t.fg(\"syntaxType\", s),\n\t\ttype: (s: string) => t.fg(\"syntaxType\", s),\n\t\ttag: (s: string) => t.fg(\"syntaxPunctuation\", s),\n\t\tname: (s: string) => t.fg(\"syntaxKeyword\", s),\n\t\tattr: (s: string) => t.fg(\"syntaxVariable\", s),\n\t\tvariable: (s: string) => t.fg(\"syntaxVariable\", s),\n\t\tparams: (s: string) => t.fg(\"syntaxVariable\", s),\n\t\toperator: (s: string) => t.fg(\"syntaxOperator\", s),\n\t\tpunctuation: (s: string) => t.fg(\"syntaxPunctuation\", s),\n\t\temphasis: (s: string) => t.italic(s),\n\t\tstrong: (s: string) => t.bold(s),\n\t\tlink: (s: string) => t.underline(s),\n\t\taddition: (s: string) => t.fg(\"toolDiffAdded\", s),\n\t\tdeletion: (s: string) => t.fg(\"toolDiffRemoved\", s),\n\t};\n}\n\nfunction getCliHighlightTheme(t: Theme): CliHighlightTheme {\n\tif (cachedHighlightThemeFor !== t || !cachedCliHighlightTheme) {\n\t\tcachedHighlightThemeFor = t;\n\t\tcachedCliHighlightTheme = buildCliHighlightTheme(t);\n\t}\n\treturn cachedCliHighlightTheme;\n}\n\n/**\n * Highlight code with syntax coloring based on file extension or language.\n * Returns array of highlighted lines.\n */\nexport function highlightCode(code: string, lang?: string): string[] {\n\t// Validate language before highlighting to avoid stderr spam from cli-highlight\n\tconst validLang = lang && supportsLanguage(lang) ? lang : undefined;\n\t// Skip highlighting when no valid language is specified. cli-highlight's\n\t// auto-detection is unreliable and can misidentify prose as AppleScript,\n\t// LiveCodeServer, etc., coloring random English words as keywords.\n\tif (!validLang) {\n\t\treturn code.split(\"\\n\").map((line) => theme.fg(\"mdCodeBlock\", line));\n\t}\n\tconst opts = {\n\t\tlanguage: validLang,\n\t\tignoreIllegals: true,\n\t\ttheme: getCliHighlightTheme(theme),\n\t};\n\ttry {\n\t\treturn highlight(code, opts).split(\"\\n\");\n\t} catch {\n\t\treturn code.split(\"\\n\");\n\t}\n}\n\n/**\n * Get language identifier from file path extension.\n */\nexport function getLanguageFromPath(filePath: string): string | undefined {\n\tconst ext = filePath.split(\".\").pop()?.toLowerCase();\n\tif (!ext) return undefined;\n\n\tconst extToLang: Record<string, string> = {\n\t\tts: \"typescript\",\n\t\ttsx: \"typescript\",\n\t\tjs: \"javascript\",\n\t\tjsx: \"javascript\",\n\t\tmjs: \"javascript\",\n\t\tcjs: \"javascript\",\n\t\tpy: \"python\",\n\t\trb: \"ruby\",\n\t\trs: \"rust\",\n\t\tgo: \"go\",\n\t\tjava: \"java\",\n\t\tkt: \"kotlin\",\n\t\tswift: \"swift\",\n\t\tc: \"c\",\n\t\th: \"c\",\n\t\tcpp: \"cpp\",\n\t\tcc: \"cpp\",\n\t\tcxx: \"cpp\",\n\t\thpp: \"cpp\",\n\t\tcs: \"csharp\",\n\t\tphp: \"php\",\n\t\tsh: \"bash\",\n\t\tbash: \"bash\",\n\t\tzsh: \"bash\",\n\t\tfish: \"fish\",\n\t\tps1: \"powershell\",\n\t\tsql: \"sql\",\n\t\thtml: \"html\",\n\t\thtm: \"html\",\n\t\tcss: \"css\",\n\t\tscss: \"scss\",\n\t\tsass: \"sass\",\n\t\tless: \"less\",\n\t\tjson: \"json\",\n\t\tyaml: \"yaml\",\n\t\tyml: \"yaml\",\n\t\ttoml: \"toml\",\n\t\txml: \"xml\",\n\t\tmd: \"markdown\",\n\t\tmarkdown: \"markdown\",\n\t\tdockerfile: \"dockerfile\",\n\t\tmakefile: \"makefile\",\n\t\tcmake: \"cmake\",\n\t\tlua: \"lua\",\n\t\tperl: \"perl\",\n\t\tr: \"r\",\n\t\tscala: \"scala\",\n\t\tclj: \"clojure\",\n\t\tex: \"elixir\",\n\t\texs: \"elixir\",\n\t\terl: \"erlang\",\n\t\ths: \"haskell\",\n\t\tml: \"ocaml\",\n\t\tvim: \"vim\",\n\t\tgraphql: \"graphql\",\n\t\tproto: \"protobuf\",\n\t\ttf: \"hcl\",\n\t\thcl: \"hcl\",\n\t};\n\n\treturn extToLang[ext];\n}\n\nexport function getMarkdownTheme(): MarkdownTheme {\n\treturn {\n\t\theading: (text: string) => theme.fg(\"mdHeading\", text),\n\t\tlink: (text: string) => theme.fg(\"mdLink\", text),\n\t\tlinkUrl: (text: string) => theme.fg(\"mdLinkUrl\", text),\n\t\tcode: (text: string) => theme.fg(\"mdCode\", text),\n\t\tcodeBlock: (text: string) => theme.fg(\"mdCodeBlock\", text),\n\t\tcodeBlockBorder: (text: string) => theme.fg(\"mdCodeBlockBorder\", text),\n\t\tquote: (text: string) => theme.fg(\"mdQuote\", text),\n\t\tquoteBorder: (text: string) => theme.fg(\"mdQuoteBorder\", text),\n\t\thr: (text: string) => theme.fg(\"mdHr\", text),\n\t\tlistBullet: (text: string) => theme.fg(\"mdListBullet\", text),\n\t\tbold: (text: string) => theme.bold(text),\n\t\titalic: (text: string) => theme.italic(text),\n\t\tunderline: (text: string) => theme.underline(text),\n\t\tstrikethrough: (text: string) => chalk.strikethrough(text),\n\t\thighlightCode: (code: string, lang?: string): string[] => {\n\t\t\t// Validate language before highlighting to avoid stderr spam from cli-highlight\n\t\t\tconst validLang = lang && supportsLanguage(lang) ? lang : undefined;\n\t\t\t// Skip highlighting when no valid language is specified. cli-highlight's\n\t\t\t// auto-detection is unreliable and can misidentify prose as AppleScript,\n\t\t\t// LiveCodeServer, etc., coloring random English words as keywords.\n\t\t\tif (!validLang) {\n\t\t\t\treturn code.split(\"\\n\").map((line) => theme.fg(\"mdCodeBlock\", line));\n\t\t\t}\n\t\t\tconst opts = {\n\t\t\t\tlanguage: validLang,\n\t\t\t\tignoreIllegals: true,\n\t\t\t\ttheme: getCliHighlightTheme(theme),\n\t\t\t};\n\t\t\ttry {\n\t\t\t\treturn highlight(code, opts).split(\"\\n\");\n\t\t\t} catch {\n\t\t\t\treturn code.split(\"\\n\").map((line) => theme.fg(\"mdCodeBlock\", line));\n\t\t\t}\n\t\t},\n\t};\n}\n\nexport function getSelectListTheme(): SelectListTheme {\n\treturn {\n\t\tselectedPrefix: (text: string) => theme.fg(\"accent\", text),\n\t\tselectedText: (text: string) => theme.fg(\"accent\", text),\n\t\tdescription: (text: string) => theme.fg(\"muted\", text),\n\t\tscrollInfo: (text: string) => theme.fg(\"muted\", text),\n\t\tnoMatch: (text: string) => theme.fg(\"muted\", text),\n\t};\n}\n\nexport function getEditorTheme(): EditorTheme {\n\treturn {\n\t\tborderColor: (text: string) => theme.fg(\"borderMuted\", text),\n\t\tselectList: getSelectListTheme(),\n\t};\n}\n\nexport function getSettingsListTheme(): SettingsListTheme {\n\treturn {\n\t\tlabel: (text: string, selected: boolean) => (selected ? theme.fg(\"accent\", text) : text),\n\t\tvalue: (text: string, selected: boolean) => (selected ? theme.fg(\"accent\", text) : theme.fg(\"muted\", text)),\n\t\tdescription: (text: string) => theme.fg(\"dim\", text),\n\t\tcursor: theme.fg(\"accent\", \"→ \"),\n\t\thint: (text: string) => theme.fg(\"dim\", text),\n\t};\n}\n"]}
|
|
@@ -5,6 +5,7 @@ import chalk from "chalk";
|
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import { Compile } from "typebox/compile";
|
|
7
7
|
import { getCustomThemesDir, getThemesDir } from "../../../config.js";
|
|
8
|
+
import { t } from "../../../core/i18n/index.js";
|
|
8
9
|
import { closeWatcher, watchWithErrorHandler } from "../../../utils/fs-watch.js";
|
|
9
10
|
import { highlight, supportsLanguage } from "../../../utils/syntax-highlight.js";
|
|
10
11
|
import { stripBom } from "../../../utils/text.js";
|
|
@@ -96,13 +97,13 @@ const validateThemeJson = Compile(ThemeJsonSchema);
|
|
|
96
97
|
function hexToRgb(hex) {
|
|
97
98
|
const cleaned = hex.replace("#", "");
|
|
98
99
|
if (cleaned.length !== 6) {
|
|
99
|
-
throw new Error(
|
|
100
|
+
throw new Error(t("Invalid hex color: {hex}", { hex }));
|
|
100
101
|
}
|
|
101
102
|
const r = parseInt(cleaned.substring(0, 2), 16);
|
|
102
103
|
const g = parseInt(cleaned.substring(2, 4), 16);
|
|
103
104
|
const b = parseInt(cleaned.substring(4, 6), 16);
|
|
104
105
|
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
105
|
-
throw new Error(
|
|
106
|
+
throw new Error(t("Invalid hex color: {hex}", { hex }));
|
|
106
107
|
}
|
|
107
108
|
return { r, g, b };
|
|
108
109
|
}
|
|
@@ -188,7 +189,7 @@ function fgAnsi(color, mode) {
|
|
|
188
189
|
return `\x1b[38;5;${index}m`;
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
|
-
throw new Error(
|
|
192
|
+
throw new Error(t("Invalid color value: {color}", { color }));
|
|
192
193
|
}
|
|
193
194
|
function bgAnsi(color, mode) {
|
|
194
195
|
if (color === "")
|
|
@@ -205,17 +206,17 @@ function bgAnsi(color, mode) {
|
|
|
205
206
|
return `\x1b[48;5;${index}m`;
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
|
-
throw new Error(
|
|
209
|
+
throw new Error(t("Invalid color value: {color}", { color }));
|
|
209
210
|
}
|
|
210
211
|
function resolveVarRefs(value, vars, visited = new Set()) {
|
|
211
212
|
if (typeof value === "number" || value === "" || value.startsWith("#")) {
|
|
212
213
|
return value;
|
|
213
214
|
}
|
|
214
215
|
if (visited.has(value)) {
|
|
215
|
-
throw new Error(
|
|
216
|
+
throw new Error(t("Circular variable reference detected: {value}", { value }));
|
|
216
217
|
}
|
|
217
218
|
if (!(value in vars)) {
|
|
218
|
-
throw new Error(
|
|
219
|
+
throw new Error(t("Variable reference not found: {value}", { value }));
|
|
219
220
|
}
|
|
220
221
|
visited.add(value);
|
|
221
222
|
return resolveVarRefs(vars[value], vars, visited);
|
|
@@ -273,13 +274,13 @@ export class Theme {
|
|
|
273
274
|
fg(color, text) {
|
|
274
275
|
const ansi = this.fgColors.get(color);
|
|
275
276
|
if (!ansi)
|
|
276
|
-
throw new Error(
|
|
277
|
+
throw new Error(t("Unknown theme color: {color}", { color }));
|
|
277
278
|
return `${ansi}${text}\x1b[39m`; // Reset only foreground color
|
|
278
279
|
}
|
|
279
280
|
bg(color, text) {
|
|
280
281
|
const ansi = this.bgColors.get(color);
|
|
281
282
|
if (!ansi)
|
|
282
|
-
throw new Error(
|
|
283
|
+
throw new Error(t("Unknown theme background color: {color}", { color }));
|
|
283
284
|
return `${ansi}${text}\x1b[49m`; // Reset only background color
|
|
284
285
|
}
|
|
285
286
|
bold(text) {
|
|
@@ -300,13 +301,13 @@ export class Theme {
|
|
|
300
301
|
getFgAnsi(color) {
|
|
301
302
|
const ansi = this.fgColors.get(color);
|
|
302
303
|
if (!ansi)
|
|
303
|
-
throw new Error(
|
|
304
|
+
throw new Error(t("Unknown theme color: {color}", { color }));
|
|
304
305
|
return ansi;
|
|
305
306
|
}
|
|
306
307
|
getBgAnsi(color) {
|
|
307
308
|
const ansi = this.bgColors.get(color);
|
|
308
309
|
if (!ansi)
|
|
309
|
-
throw new Error(
|
|
310
|
+
throw new Error(t("Unknown theme background color: {color}", { color }));
|
|
310
311
|
return ansi;
|
|
311
312
|
}
|
|
312
313
|
getColorMode() {
|
|
@@ -406,7 +407,9 @@ function getCustomThemeInfos() {
|
|
|
406
407
|
}
|
|
407
408
|
function assertThemeNameIsValid(name) {
|
|
408
409
|
if (name.includes("/")) {
|
|
409
|
-
throw new Error(
|
|
410
|
+
throw new Error(t('Invalid theme name "{name}": theme names cannot contain "/" because it is reserved for automatic light/dark theme settings.', {
|
|
411
|
+
name,
|
|
412
|
+
}));
|
|
410
413
|
}
|
|
411
414
|
}
|
|
412
415
|
function parseThemeJson(label, json) {
|
|
@@ -425,18 +428,18 @@ function parseThemeJson(label, json) {
|
|
|
425
428
|
const path = error.instancePath || "/";
|
|
426
429
|
otherErrors.push(` - ${path}: ${error.message}`);
|
|
427
430
|
}
|
|
428
|
-
let errorMessage =
|
|
431
|
+
let errorMessage = `${t('Invalid theme "{label}":', { label })}\n`;
|
|
429
432
|
if (missingColors.size > 0) {
|
|
430
|
-
errorMessage += "
|
|
433
|
+
errorMessage += `\n${t("Missing required color tokens:")}\n`;
|
|
431
434
|
errorMessage += Array.from(missingColors)
|
|
432
435
|
.sort()
|
|
433
436
|
.map((color) => ` - ${color}`)
|
|
434
437
|
.join("\n");
|
|
435
|
-
errorMessage +=
|
|
436
|
-
errorMessage += "
|
|
438
|
+
errorMessage += `\n\n${t('Please add these colors to your theme\'s "colors" object.')}`;
|
|
439
|
+
errorMessage += `\n${t("See the built-in themes (dark.json, light.json) for reference values.")}`;
|
|
437
440
|
}
|
|
438
441
|
if (otherErrors.length > 0) {
|
|
439
|
-
errorMessage += `\n\
|
|
442
|
+
errorMessage += `\n\n${t("Other errors:")}\n${otherErrors.join("\n")}`;
|
|
440
443
|
}
|
|
441
444
|
throw new Error(errorMessage);
|
|
442
445
|
}
|
|
@@ -450,7 +453,7 @@ function parseThemeJsonContent(label, content) {
|
|
|
450
453
|
json = JSON.parse(stripBom(content));
|
|
451
454
|
}
|
|
452
455
|
catch (error) {
|
|
453
|
-
throw new Error(
|
|
456
|
+
throw new Error(t("Failed to parse theme {label}: {error}", { label, error: String(error) }));
|
|
454
457
|
}
|
|
455
458
|
return parseThemeJson(label, json);
|
|
456
459
|
}
|
|
@@ -465,12 +468,12 @@ function loadThemeJson(name) {
|
|
|
465
468
|
return parseThemeJsonContent(registeredTheme.sourcePath, content);
|
|
466
469
|
}
|
|
467
470
|
if (registeredTheme) {
|
|
468
|
-
throw new Error(
|
|
471
|
+
throw new Error(t('Theme "{name}" does not have a source path for export', { name }));
|
|
469
472
|
}
|
|
470
473
|
const customThemesDir = getCustomThemesDir();
|
|
471
474
|
const themePath = path.join(customThemesDir, `${name}.json`);
|
|
472
475
|
if (!fs.existsSync(themePath)) {
|
|
473
|
-
throw new Error(
|
|
476
|
+
throw new Error(t("Theme not found: {name}", { name }));
|
|
474
477
|
}
|
|
475
478
|
const content = fs.readFileSync(themePath, "utf-8");
|
|
476
479
|
return parseThemeJsonContent(name, content);
|
|
@@ -640,10 +643,10 @@ const THEME_KEY_OLD = Symbol.for("@mariozechner/pi-coding-agent:theme");
|
|
|
640
643
|
// This ensures all module instances (tsx, jiti) see the same theme
|
|
641
644
|
export const theme = new Proxy({}, {
|
|
642
645
|
get(_target, prop) {
|
|
643
|
-
const
|
|
644
|
-
if (!
|
|
645
|
-
throw new Error("Theme not initialized. Call initTheme() first.");
|
|
646
|
-
return
|
|
646
|
+
const active = globalThis[THEME_KEY];
|
|
647
|
+
if (!active)
|
|
648
|
+
throw new Error(t("Theme not initialized. Call initTheme() first."));
|
|
649
|
+
return active[prop];
|
|
647
650
|
},
|
|
648
651
|
});
|
|
649
652
|
function setGlobalTheme(t) {
|