@f5-sales-demo/xcsh 19.51.2

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.
Files changed (899) hide show
  1. package/CHANGELOG.md +7073 -0
  2. package/README.md +14 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +109 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/custom-tools/todo/index.ts +206 -0
  7. package/examples/extensions/README.md +143 -0
  8. package/examples/extensions/api-demo.ts +89 -0
  9. package/examples/extensions/chalk-logger.ts +25 -0
  10. package/examples/extensions/hello.ts +32 -0
  11. package/examples/extensions/pirate.ts +43 -0
  12. package/examples/extensions/plan-mode.ts +551 -0
  13. package/examples/extensions/reload-runtime.ts +37 -0
  14. package/examples/extensions/todo.ts +296 -0
  15. package/examples/extensions/tools.ts +145 -0
  16. package/examples/extensions/with-deps/index.ts +36 -0
  17. package/examples/extensions/with-deps/package-lock.json +31 -0
  18. package/examples/extensions/with-deps/package.json +16 -0
  19. package/examples/hooks/README.md +56 -0
  20. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  21. package/examples/hooks/confirm-destructive.ts +58 -0
  22. package/examples/hooks/custom-compaction.ts +115 -0
  23. package/examples/hooks/dirty-repo-guard.ts +51 -0
  24. package/examples/hooks/file-trigger.ts +40 -0
  25. package/examples/hooks/git-checkpoint.ts +52 -0
  26. package/examples/hooks/handoff.ts +149 -0
  27. package/examples/hooks/permission-gate.ts +33 -0
  28. package/examples/hooks/protected-paths.ts +29 -0
  29. package/examples/hooks/qna.ts +118 -0
  30. package/examples/hooks/status-line.ts +39 -0
  31. package/examples/sdk/01-minimal.ts +21 -0
  32. package/examples/sdk/02-custom-model.ts +49 -0
  33. package/examples/sdk/03-custom-prompt.ts +43 -0
  34. package/examples/sdk/04-skills.ts +43 -0
  35. package/examples/sdk/06-extensions.ts +80 -0
  36. package/examples/sdk/06-hooks.ts +61 -0
  37. package/examples/sdk/07-context-files.ts +35 -0
  38. package/examples/sdk/08-prompt-templates.ts +41 -0
  39. package/examples/sdk/08-slash-commands.ts +46 -0
  40. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  41. package/examples/sdk/11-sessions.ts +47 -0
  42. package/examples/sdk/README.md +172 -0
  43. package/package.json +539 -0
  44. package/scripts/build-info/resolvers.ts +51 -0
  45. package/scripts/capture-ax-fixture.ts +84 -0
  46. package/scripts/capture-login-fixture.ts +30 -0
  47. package/scripts/check-workflow-field-coverage.ts +101 -0
  48. package/scripts/extension-uat-harness.ts +340 -0
  49. package/scripts/format-prompts.ts +68 -0
  50. package/scripts/generate-api-spec-index.ts +580 -0
  51. package/scripts/generate-branding-index.ts +81 -0
  52. package/scripts/generate-build-info.ts +106 -0
  53. package/scripts/generate-console-catalog.ts +88 -0
  54. package/scripts/generate-console-field-metadata.ts +74 -0
  55. package/scripts/generate-docs-index.ts +40 -0
  56. package/scripts/generate-template.ts +32 -0
  57. package/scripts/generate-terraform-index.ts +81 -0
  58. package/scripts/mermaid-gallery.ts +132 -0
  59. package/scripts/uat-matrix-modalities.ts +451 -0
  60. package/scripts/uat-matrix-report.ts +175 -0
  61. package/scripts/uat-matrix.ts +353 -0
  62. package/src/async/index.ts +2 -0
  63. package/src/async/job-manager.ts +416 -0
  64. package/src/async/support.ts +5 -0
  65. package/src/autoresearch/apply-contract-to-state.ts +1 -0
  66. package/src/autoresearch/command-resume.md +17 -0
  67. package/src/autoresearch/contract.ts +205 -0
  68. package/src/autoresearch/dashboard.ts +374 -0
  69. package/src/autoresearch/git.ts +191 -0
  70. package/src/autoresearch/helpers.ts +328 -0
  71. package/src/autoresearch/index.ts +402 -0
  72. package/src/autoresearch/prompt.md +236 -0
  73. package/src/autoresearch/resume-message.md +16 -0
  74. package/src/autoresearch/state.ts +303 -0
  75. package/src/autoresearch/tools/init-experiment.ts +400 -0
  76. package/src/autoresearch/tools/log-experiment.ts +793 -0
  77. package/src/autoresearch/tools/run-experiment.ts +679 -0
  78. package/src/autoresearch/types.ts +114 -0
  79. package/src/browser/acquire.ts +249 -0
  80. package/src/browser/actions.ts +126 -0
  81. package/src/browser/auth.ts +176 -0
  82. package/src/browser/ax.ts +134 -0
  83. package/src/browser/cdp-core.ts +45 -0
  84. package/src/browser/cdp-page-actions.ts +51 -0
  85. package/src/browser/chrome-locate.ts +66 -0
  86. package/src/browser/dom-context.ts +89 -0
  87. package/src/browser/dt-context.ts +100 -0
  88. package/src/browser/extension-bridge.ts +182 -0
  89. package/src/browser/extension-page-actions.ts +397 -0
  90. package/src/browser/extension-provider.ts +373 -0
  91. package/src/browser/index.ts +17 -0
  92. package/src/browser/input-commit.ts +74 -0
  93. package/src/browser/native-messaging.ts +20 -0
  94. package/src/browser/page-actions.ts +18 -0
  95. package/src/browser/provider.ts +155 -0
  96. package/src/browser/resolver.ts +126 -0
  97. package/src/browser/selector.ts +40 -0
  98. package/src/bun-imports.d.ts +22 -0
  99. package/src/capability/context-file.ts +43 -0
  100. package/src/capability/extension-module.ts +34 -0
  101. package/src/capability/extension.ts +47 -0
  102. package/src/capability/fs.ts +107 -0
  103. package/src/capability/hook.ts +40 -0
  104. package/src/capability/index.ts +438 -0
  105. package/src/capability/instruction.ts +37 -0
  106. package/src/capability/mcp.ts +74 -0
  107. package/src/capability/prompt.ts +35 -0
  108. package/src/capability/rule.ts +224 -0
  109. package/src/capability/settings.ts +34 -0
  110. package/src/capability/skill.ts +50 -0
  111. package/src/capability/slash-command.ts +40 -0
  112. package/src/capability/ssh.ts +41 -0
  113. package/src/capability/system-prompt.ts +34 -0
  114. package/src/capability/tool.ts +38 -0
  115. package/src/capability/types.ts +168 -0
  116. package/src/cli/agents-cli.ts +138 -0
  117. package/src/cli/args.ts +286 -0
  118. package/src/cli/chrome-cli.ts +88 -0
  119. package/src/cli/classify-install-target.ts +50 -0
  120. package/src/cli/commands/init-xdg.ts +27 -0
  121. package/src/cli/config-cli.ts +418 -0
  122. package/src/cli/file-processor.ts +121 -0
  123. package/src/cli/grep-cli.ts +160 -0
  124. package/src/cli/grievances-cli.ts +78 -0
  125. package/src/cli/initial-message.ts +58 -0
  126. package/src/cli/jupyter-cli.ts +106 -0
  127. package/src/cli/list-models.ts +128 -0
  128. package/src/cli/plugin-cli.ts +964 -0
  129. package/src/cli/read-cli.ts +68 -0
  130. package/src/cli/session-picker.ts +52 -0
  131. package/src/cli/setup-cli.ts +479 -0
  132. package/src/cli/shell-cli.ts +174 -0
  133. package/src/cli/ssh-cli.ts +179 -0
  134. package/src/cli/stats-cli.ts +182 -0
  135. package/src/cli/update-cli.ts +446 -0
  136. package/src/cli/web-search-cli.ts +203 -0
  137. package/src/cli.ts +123 -0
  138. package/src/commands/agents.ts +57 -0
  139. package/src/commands/chrome-host.ts +71 -0
  140. package/src/commands/chrome.ts +28 -0
  141. package/src/commands/commit.ts +36 -0
  142. package/src/commands/config.ts +51 -0
  143. package/src/commands/grep.ts +48 -0
  144. package/src/commands/grievances.ts +20 -0
  145. package/src/commands/jupyter.ts +32 -0
  146. package/src/commands/launch.ts +144 -0
  147. package/src/commands/plugin.ts +78 -0
  148. package/src/commands/read.ts +33 -0
  149. package/src/commands/setup.ts +42 -0
  150. package/src/commands/shell.ts +29 -0
  151. package/src/commands/ssh.ts +60 -0
  152. package/src/commands/stats.ts +29 -0
  153. package/src/commands/update.ts +21 -0
  154. package/src/commands/web-search.ts +46 -0
  155. package/src/commit/agentic/agent.ts +314 -0
  156. package/src/commit/agentic/fallback.ts +96 -0
  157. package/src/commit/agentic/index.ts +354 -0
  158. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  159. package/src/commit/agentic/prompts/session-user.md +25 -0
  160. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  161. package/src/commit/agentic/prompts/system.md +38 -0
  162. package/src/commit/agentic/state.ts +60 -0
  163. package/src/commit/agentic/tools/analyze-file.ts +127 -0
  164. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  165. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  166. package/src/commit/agentic/tools/git-overview.ts +81 -0
  167. package/src/commit/agentic/tools/index.ts +54 -0
  168. package/src/commit/agentic/tools/propose-changelog.ts +139 -0
  169. package/src/commit/agentic/tools/propose-commit.ts +122 -0
  170. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  171. package/src/commit/agentic/tools/schemas.ts +31 -0
  172. package/src/commit/agentic/tools/split-commit.ts +251 -0
  173. package/src/commit/agentic/topo-sort.ts +44 -0
  174. package/src/commit/agentic/trivial.ts +51 -0
  175. package/src/commit/agentic/validation.ts +183 -0
  176. package/src/commit/analysis/conventional.ts +122 -0
  177. package/src/commit/analysis/index.ts +4 -0
  178. package/src/commit/analysis/scope.ts +242 -0
  179. package/src/commit/analysis/summary.ts +105 -0
  180. package/src/commit/analysis/validation.ts +66 -0
  181. package/src/commit/changelog/detect.ts +40 -0
  182. package/src/commit/changelog/generate.ts +101 -0
  183. package/src/commit/changelog/index.ts +234 -0
  184. package/src/commit/changelog/parse.ts +44 -0
  185. package/src/commit/cli.ts +85 -0
  186. package/src/commit/git/diff.ts +148 -0
  187. package/src/commit/index.ts +5 -0
  188. package/src/commit/map-reduce/index.ts +69 -0
  189. package/src/commit/map-reduce/map-phase.ts +193 -0
  190. package/src/commit/map-reduce/reduce-phase.ts +108 -0
  191. package/src/commit/map-reduce/utils.ts +9 -0
  192. package/src/commit/message.ts +11 -0
  193. package/src/commit/model-selection.ts +66 -0
  194. package/src/commit/pipeline.ts +242 -0
  195. package/src/commit/prompts/analysis-system.md +148 -0
  196. package/src/commit/prompts/analysis-user.md +38 -0
  197. package/src/commit/prompts/changelog-system.md +50 -0
  198. package/src/commit/prompts/changelog-user.md +18 -0
  199. package/src/commit/prompts/file-observer-system.md +24 -0
  200. package/src/commit/prompts/file-observer-user.md +8 -0
  201. package/src/commit/prompts/reduce-system.md +50 -0
  202. package/src/commit/prompts/reduce-user.md +17 -0
  203. package/src/commit/prompts/summary-retry.md +3 -0
  204. package/src/commit/prompts/summary-system.md +38 -0
  205. package/src/commit/prompts/summary-user.md +13 -0
  206. package/src/commit/prompts/types-description.md +2 -0
  207. package/src/commit/types.ts +118 -0
  208. package/src/commit/utils/exclusions.ts +42 -0
  209. package/src/commit/utils.ts +44 -0
  210. package/src/config/auto-config.ts +698 -0
  211. package/src/config/context-schema.json +115 -0
  212. package/src/config/file-lock.ts +121 -0
  213. package/src/config/keybindings.ts +493 -0
  214. package/src/config/mcp-schema.json +230 -0
  215. package/src/config/model-equivalence.ts +675 -0
  216. package/src/config/model-registry.ts +2265 -0
  217. package/src/config/model-resolver.ts +1289 -0
  218. package/src/config/prompt-templates.ts +271 -0
  219. package/src/config/resolve-config-value.ts +110 -0
  220. package/src/config/settings-schema.ts +2155 -0
  221. package/src/config/settings.ts +730 -0
  222. package/src/config.ts +418 -0
  223. package/src/cursor.ts +323 -0
  224. package/src/dap/client.ts +674 -0
  225. package/src/dap/config.ts +150 -0
  226. package/src/dap/defaults.json +211 -0
  227. package/src/dap/index.ts +4 -0
  228. package/src/dap/session.ts +1255 -0
  229. package/src/dap/types.ts +600 -0
  230. package/src/debug/index.ts +440 -0
  231. package/src/debug/log-formatting.ts +58 -0
  232. package/src/debug/log-viewer.ts +908 -0
  233. package/src/debug/profiler.ts +158 -0
  234. package/src/debug/report-bundle.ts +365 -0
  235. package/src/debug/system-info.ts +107 -0
  236. package/src/discovery/agents-md.ts +67 -0
  237. package/src/discovery/agents.ts +219 -0
  238. package/src/discovery/builtin.ts +851 -0
  239. package/src/discovery/claude-plugins.ts +297 -0
  240. package/src/discovery/claude.ts +543 -0
  241. package/src/discovery/cline.ts +83 -0
  242. package/src/discovery/codex.ts +535 -0
  243. package/src/discovery/cursor.ts +220 -0
  244. package/src/discovery/gemini.ts +396 -0
  245. package/src/discovery/github.ts +118 -0
  246. package/src/discovery/helpers.ts +934 -0
  247. package/src/discovery/index.ts +79 -0
  248. package/src/discovery/language.ts +46 -0
  249. package/src/discovery/mcp-json.ts +171 -0
  250. package/src/discovery/opencode.ts +393 -0
  251. package/src/discovery/plugin-dir-roots.ts +28 -0
  252. package/src/discovery/ssh.ts +153 -0
  253. package/src/discovery/substitute-plugin-root.ts +29 -0
  254. package/src/discovery/vscode.ts +105 -0
  255. package/src/discovery/windsurf.ts +147 -0
  256. package/src/edit/diff.ts +818 -0
  257. package/src/edit/index.ts +481 -0
  258. package/src/edit/line-hash.ts +67 -0
  259. package/src/edit/modes/chunk.ts +762 -0
  260. package/src/edit/modes/hashline.ts +1366 -0
  261. package/src/edit/modes/patch.ts +1791 -0
  262. package/src/edit/modes/replace.ts +1115 -0
  263. package/src/edit/normalize.ts +375 -0
  264. package/src/edit/renderer.ts +627 -0
  265. package/src/exa/factory.ts +61 -0
  266. package/src/exa/index.ts +27 -0
  267. package/src/exa/mcp-client.ts +293 -0
  268. package/src/exa/render.ts +232 -0
  269. package/src/exa/researcher.ts +47 -0
  270. package/src/exa/search.ts +86 -0
  271. package/src/exa/types.ts +166 -0
  272. package/src/exa/websets.ts +247 -0
  273. package/src/exec/bash-executor.ts +332 -0
  274. package/src/exec/exec.ts +53 -0
  275. package/src/exec/idle-timeout-watchdog.ts +126 -0
  276. package/src/exec/non-interactive-env.ts +48 -0
  277. package/src/export/custom-share.ts +65 -0
  278. package/src/export/html/index.ts +161 -0
  279. package/src/export/html/template.css +1001 -0
  280. package/src/export/html/template.generated.ts +2 -0
  281. package/src/export/html/template.html +46 -0
  282. package/src/export/html/template.js +1950 -0
  283. package/src/export/html/template.macro.ts +24 -0
  284. package/src/export/html/vendor/highlight.min.js +1213 -0
  285. package/src/export/html/vendor/marked.min.js +6 -0
  286. package/src/export/ttsr.ts +434 -0
  287. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +25 -0
  288. package/src/extensibility/custom-commands/bundled/review/index.ts +456 -0
  289. package/src/extensibility/custom-commands/index.ts +2 -0
  290. package/src/extensibility/custom-commands/loader.ts +236 -0
  291. package/src/extensibility/custom-commands/types.ts +111 -0
  292. package/src/extensibility/custom-tools/index.ts +7 -0
  293. package/src/extensibility/custom-tools/loader.ts +238 -0
  294. package/src/extensibility/custom-tools/types.ts +251 -0
  295. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  296. package/src/extensibility/extensions/index.ts +15 -0
  297. package/src/extensibility/extensions/loader.ts +572 -0
  298. package/src/extensibility/extensions/runner.ts +848 -0
  299. package/src/extensibility/extensions/types.ts +1394 -0
  300. package/src/extensibility/extensions/wrapper.ts +191 -0
  301. package/src/extensibility/hooks/index.ts +5 -0
  302. package/src/extensibility/hooks/loader.ts +255 -0
  303. package/src/extensibility/hooks/runner.ts +425 -0
  304. package/src/extensibility/hooks/tool-wrapper.ts +108 -0
  305. package/src/extensibility/hooks/types.ts +826 -0
  306. package/src/extensibility/plugins/doctor.ts +66 -0
  307. package/src/extensibility/plugins/git-url.ts +281 -0
  308. package/src/extensibility/plugins/index.ts +9 -0
  309. package/src/extensibility/plugins/installer.ts +192 -0
  310. package/src/extensibility/plugins/loader.ts +267 -0
  311. package/src/extensibility/plugins/manager.ts +731 -0
  312. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  313. package/src/extensibility/plugins/marketplace/fetcher.ts +318 -0
  314. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  315. package/src/extensibility/plugins/marketplace/manager.ts +781 -0
  316. package/src/extensibility/plugins/marketplace/prerequisites.ts +255 -0
  317. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  318. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  319. package/src/extensibility/plugins/marketplace/types.ts +201 -0
  320. package/src/extensibility/plugins/parser.ts +105 -0
  321. package/src/extensibility/plugins/types.ts +194 -0
  322. package/src/extensibility/skills.ts +269 -0
  323. package/src/extensibility/slash-commands.ts +246 -0
  324. package/src/extensibility/tool-proxy.ts +25 -0
  325. package/src/extensibility/utils.ts +38 -0
  326. package/src/index.ts +61 -0
  327. package/src/internal-urls/agent-protocol.ts +136 -0
  328. package/src/internal-urls/api-catalog-resolve.ts +495 -0
  329. package/src/internal-urls/api-catalog-types.ts +94 -0
  330. package/src/internal-urls/api-spec-resolve.ts +1036 -0
  331. package/src/internal-urls/api-spec-types.ts +239 -0
  332. package/src/internal-urls/artifact-protocol.ts +97 -0
  333. package/src/internal-urls/branding-index.generated.ts +88 -0
  334. package/src/internal-urls/build-info-runtime.ts +302 -0
  335. package/src/internal-urls/build-info.generated.ts +33 -0
  336. package/src/internal-urls/computer-profile.ts +692 -0
  337. package/src/internal-urls/console-catalog-types.ts +21 -0
  338. package/src/internal-urls/console-catalog.generated.ts +532 -0
  339. package/src/internal-urls/console-field-metadata-types.ts +59 -0
  340. package/src/internal-urls/console-field-metadata.generated.ts +4841 -0
  341. package/src/internal-urls/console-resolve.ts +178 -0
  342. package/src/internal-urls/docs-index.generated.ts +698 -0
  343. package/src/internal-urls/extension-api.md +147 -0
  344. package/src/internal-urls/index.ts +46 -0
  345. package/src/internal-urls/jobs-protocol.ts +119 -0
  346. package/src/internal-urls/json-query.ts +126 -0
  347. package/src/internal-urls/local-protocol.ts +206 -0
  348. package/src/internal-urls/mcp-protocol.ts +156 -0
  349. package/src/internal-urls/memory-protocol.ts +133 -0
  350. package/src/internal-urls/parse.ts +72 -0
  351. package/src/internal-urls/profile-collectors.ts +106 -0
  352. package/src/internal-urls/router.ts +55 -0
  353. package/src/internal-urls/rule-protocol.ts +55 -0
  354. package/src/internal-urls/skill-protocol.ts +111 -0
  355. package/src/internal-urls/terraform-index.generated.ts +1852 -0
  356. package/src/internal-urls/terraform-resolve.ts +184 -0
  357. package/src/internal-urls/terraform-types.ts +45 -0
  358. package/src/internal-urls/types.ts +52 -0
  359. package/src/internal-urls/user-profile.ts +363 -0
  360. package/src/internal-urls/xcsh-protocol.ts +644 -0
  361. package/src/ipy/cancellation.ts +28 -0
  362. package/src/ipy/executor.ts +1155 -0
  363. package/src/ipy/gateway-coordinator.ts +424 -0
  364. package/src/ipy/kernel.ts +1091 -0
  365. package/src/ipy/modules.ts +144 -0
  366. package/src/ipy/prelude.py +849 -0
  367. package/src/ipy/prelude.ts +3 -0
  368. package/src/ipy/runtime.ts +221 -0
  369. package/src/locales/ar.json +512 -0
  370. package/src/locales/de.json +512 -0
  371. package/src/locales/en.json +549 -0
  372. package/src/locales/es.json +512 -0
  373. package/src/locales/fr.json +512 -0
  374. package/src/locales/hi.json +512 -0
  375. package/src/locales/index.ts +29 -0
  376. package/src/locales/it.json +512 -0
  377. package/src/locales/ja.json +512 -0
  378. package/src/locales/ko.json +512 -0
  379. package/src/locales/pt-br.json +512 -0
  380. package/src/locales/th.json +512 -0
  381. package/src/locales/zh-cn.json +512 -0
  382. package/src/locales/zh-tw.json +512 -0
  383. package/src/lsp/client.ts +941 -0
  384. package/src/lsp/clients/biome-client.ts +202 -0
  385. package/src/lsp/clients/index.ts +50 -0
  386. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  387. package/src/lsp/clients/swiftlint-client.ts +120 -0
  388. package/src/lsp/config.ts +418 -0
  389. package/src/lsp/defaults.json +999 -0
  390. package/src/lsp/edits.ts +109 -0
  391. package/src/lsp/index.ts +1746 -0
  392. package/src/lsp/lspmux.ts +233 -0
  393. package/src/lsp/render.ts +692 -0
  394. package/src/lsp/startup-events.ts +13 -0
  395. package/src/lsp/types.ts +444 -0
  396. package/src/lsp/utils.ts +691 -0
  397. package/src/main.ts +944 -0
  398. package/src/mcp/client.ts +482 -0
  399. package/src/mcp/config-writer.ts +225 -0
  400. package/src/mcp/config.ts +365 -0
  401. package/src/mcp/discoverable-tool-metadata.ts +202 -0
  402. package/src/mcp/index.ts +29 -0
  403. package/src/mcp/json-rpc.ts +84 -0
  404. package/src/mcp/loader.ts +124 -0
  405. package/src/mcp/manager.ts +1152 -0
  406. package/src/mcp/oauth-discovery.ts +349 -0
  407. package/src/mcp/oauth-flow.ts +387 -0
  408. package/src/mcp/render.ts +157 -0
  409. package/src/mcp/smithery-auth.ts +104 -0
  410. package/src/mcp/smithery-connect.ts +145 -0
  411. package/src/mcp/smithery-registry.ts +477 -0
  412. package/src/mcp/tool-bridge.ts +417 -0
  413. package/src/mcp/tool-cache.ts +117 -0
  414. package/src/mcp/transports/http.ts +475 -0
  415. package/src/mcp/transports/index.ts +6 -0
  416. package/src/mcp/transports/stdio.ts +325 -0
  417. package/src/mcp/types.ts +423 -0
  418. package/src/memories/index.ts +1115 -0
  419. package/src/memories/storage.ts +577 -0
  420. package/src/modes/acp/acp-agent.ts +1361 -0
  421. package/src/modes/acp/acp-event-mapper.ts +538 -0
  422. package/src/modes/acp/acp-mode.ts +15 -0
  423. package/src/modes/acp/index.ts +2 -0
  424. package/src/modes/components/agent-dashboard.ts +1124 -0
  425. package/src/modes/components/assistant-message.ts +213 -0
  426. package/src/modes/components/bash-execution.ts +345 -0
  427. package/src/modes/components/bordered-loader.ts +41 -0
  428. package/src/modes/components/branch-summary-message.ts +45 -0
  429. package/src/modes/components/btw-panel.ts +103 -0
  430. package/src/modes/components/compaction-summary-message.ts +51 -0
  431. package/src/modes/components/context-add-wizard.ts +534 -0
  432. package/src/modes/components/countdown-timer.ts +75 -0
  433. package/src/modes/components/custom-editor.ts +238 -0
  434. package/src/modes/components/custom-message.ts +91 -0
  435. package/src/modes/components/diff.ts +210 -0
  436. package/src/modes/components/dynamic-border.ts +25 -0
  437. package/src/modes/components/extensions/extension-dashboard.ts +326 -0
  438. package/src/modes/components/extensions/extension-list.ts +492 -0
  439. package/src/modes/components/extensions/index.ts +9 -0
  440. package/src/modes/components/extensions/inspector-panel.ts +317 -0
  441. package/src/modes/components/extensions/state-manager.ts +587 -0
  442. package/src/modes/components/extensions/types.ts +191 -0
  443. package/src/modes/components/footer.ts +262 -0
  444. package/src/modes/components/gutter-block.ts +315 -0
  445. package/src/modes/components/history-search.ts +158 -0
  446. package/src/modes/components/hook-editor.ts +151 -0
  447. package/src/modes/components/hook-input.ts +81 -0
  448. package/src/modes/components/hook-message.ts +100 -0
  449. package/src/modes/components/hook-selector.ts +192 -0
  450. package/src/modes/components/index.ts +36 -0
  451. package/src/modes/components/keybinding-hints.ts +65 -0
  452. package/src/modes/components/login-dialog.ts +164 -0
  453. package/src/modes/components/mcp-add-wizard.ts +1314 -0
  454. package/src/modes/components/model-selector.ts +946 -0
  455. package/src/modes/components/oauth-selector.ts +211 -0
  456. package/src/modes/components/plugin-selector.ts +95 -0
  457. package/src/modes/components/plugin-settings.ts +477 -0
  458. package/src/modes/components/plugins/index.ts +5 -0
  459. package/src/modes/components/plugins/plugin-dashboard.ts +523 -0
  460. package/src/modes/components/plugins/plugin-inspector-pane.ts +84 -0
  461. package/src/modes/components/plugins/plugin-list-pane.ts +109 -0
  462. package/src/modes/components/plugins/state-manager.ts +229 -0
  463. package/src/modes/components/plugins/types.ts +51 -0
  464. package/src/modes/components/plugins/utils.ts +6 -0
  465. package/src/modes/components/python-execution.ts +244 -0
  466. package/src/modes/components/queue-mode-selector.ts +56 -0
  467. package/src/modes/components/read-tool-group.ts +137 -0
  468. package/src/modes/components/session-observer-overlay.ts +483 -0
  469. package/src/modes/components/session-selector.ts +343 -0
  470. package/src/modes/components/settings-defs.ts +568 -0
  471. package/src/modes/components/settings-selector.ts +641 -0
  472. package/src/modes/components/show-images-selector.ts +45 -0
  473. package/src/modes/components/skill-message.ts +90 -0
  474. package/src/modes/components/status-line/context-gradient.ts +64 -0
  475. package/src/modes/components/status-line/git-utils.ts +42 -0
  476. package/src/modes/components/status-line/hex-ansi.ts +29 -0
  477. package/src/modes/components/status-line/index.ts +4 -0
  478. package/src/modes/components/status-line/presets.ts +174 -0
  479. package/src/modes/components/status-line/segments.ts +494 -0
  480. package/src/modes/components/status-line/separators.ts +55 -0
  481. package/src/modes/components/status-line/token-rate.ts +66 -0
  482. package/src/modes/components/status-line/types.ts +96 -0
  483. package/src/modes/components/status-line-segment-editor.ts +361 -0
  484. package/src/modes/components/status-line.ts +729 -0
  485. package/src/modes/components/theme-selector.ts +63 -0
  486. package/src/modes/components/thinking-selector.ts +52 -0
  487. package/src/modes/components/todo-reminder.ts +54 -0
  488. package/src/modes/components/tool-execution.ts +781 -0
  489. package/src/modes/components/tree-selector.ts +908 -0
  490. package/src/modes/components/ttsr-notification.ts +80 -0
  491. package/src/modes/components/user-message-selector.ts +141 -0
  492. package/src/modes/components/user-message.ts +100 -0
  493. package/src/modes/components/visual-truncate.ts +63 -0
  494. package/src/modes/components/welcome-checks.ts +313 -0
  495. package/src/modes/components/welcome.ts +270 -0
  496. package/src/modes/controllers/btw-controller.ts +193 -0
  497. package/src/modes/controllers/chord-routing.ts +37 -0
  498. package/src/modes/controllers/command-controller.ts +1293 -0
  499. package/src/modes/controllers/context-command-controller.ts +61 -0
  500. package/src/modes/controllers/event-controller.ts +743 -0
  501. package/src/modes/controllers/extension-ui-controller.ts +983 -0
  502. package/src/modes/controllers/input-controller.ts +877 -0
  503. package/src/modes/controllers/mcp-command-controller.ts +1940 -0
  504. package/src/modes/controllers/selector-controller.ts +1292 -0
  505. package/src/modes/controllers/ssh-command-controller.ts +421 -0
  506. package/src/modes/index.ts +34 -0
  507. package/src/modes/interactive-mode.ts +1781 -0
  508. package/src/modes/oauth-manual-input.ts +42 -0
  509. package/src/modes/print-mode.ts +196 -0
  510. package/src/modes/prompt-action-autocomplete.ts +231 -0
  511. package/src/modes/rpc/host-tools.ts +186 -0
  512. package/src/modes/rpc/rpc-client.ts +762 -0
  513. package/src/modes/rpc/rpc-mode.ts +877 -0
  514. package/src/modes/rpc/rpc-types.ts +322 -0
  515. package/src/modes/session-observer-registry.ts +146 -0
  516. package/src/modes/shared.ts +30 -0
  517. package/src/modes/theme/defaults/index.ts +7 -0
  518. package/src/modes/theme/defaults/xcsh-dark.json +113 -0
  519. package/src/modes/theme/defaults/xcsh-light.json +115 -0
  520. package/src/modes/theme/mermaid-cache.ts +151 -0
  521. package/src/modes/theme/mermaid-palette.ts +39 -0
  522. package/src/modes/theme/theme-schema.json +553 -0
  523. package/src/modes/theme/theme.ts +2563 -0
  524. package/src/modes/types.ts +273 -0
  525. package/src/modes/utils/hotkeys-markdown.ts +60 -0
  526. package/src/modes/utils/keybinding-matchers.ts +30 -0
  527. package/src/modes/utils/read-group-outcome-aggregator.ts +55 -0
  528. package/src/modes/utils/sanitize-error-message.ts +60 -0
  529. package/src/modes/utils/tools-markdown.ts +28 -0
  530. package/src/modes/utils/ui-helpers.ts +734 -0
  531. package/src/plan-mode/approved-plan.ts +55 -0
  532. package/src/plan-mode/state.ts +6 -0
  533. package/src/priority.json +37 -0
  534. package/src/prompts/agents/designer.md +67 -0
  535. package/src/prompts/agents/explore.md +61 -0
  536. package/src/prompts/agents/frontmatter.md +10 -0
  537. package/src/prompts/agents/init.md +36 -0
  538. package/src/prompts/agents/librarian.md +121 -0
  539. package/src/prompts/agents/plan.md +49 -0
  540. package/src/prompts/agents/reviewer.md +127 -0
  541. package/src/prompts/agents/task.md +16 -0
  542. package/src/prompts/ci-green-request.md +35 -0
  543. package/src/prompts/compaction/branch-summary-context.md +5 -0
  544. package/src/prompts/compaction/branch-summary-preamble.md +2 -0
  545. package/src/prompts/compaction/branch-summary.md +30 -0
  546. package/src/prompts/compaction/compaction-short-summary.md +9 -0
  547. package/src/prompts/compaction/compaction-summary-context.md +5 -0
  548. package/src/prompts/compaction/compaction-summary.md +38 -0
  549. package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
  550. package/src/prompts/compaction/compaction-update-summary.md +45 -0
  551. package/src/prompts/memories/consolidation.md +30 -0
  552. package/src/prompts/memories/read-path.md +11 -0
  553. package/src/prompts/memories/stage_one_input.md +6 -0
  554. package/src/prompts/memories/stage_one_system.md +21 -0
  555. package/src/prompts/review-request.md +70 -0
  556. package/src/prompts/system/agent-creation-architect.md +65 -0
  557. package/src/prompts/system/agent-creation-user.md +6 -0
  558. package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
  559. package/src/prompts/system/btw-user.md +8 -0
  560. package/src/prompts/system/commit-message-system.md +2 -0
  561. package/src/prompts/system/custom-system-prompt.md +82 -0
  562. package/src/prompts/system/eager-todo.md +13 -0
  563. package/src/prompts/system/file-operations.md +10 -0
  564. package/src/prompts/system/handoff-document.md +48 -0
  565. package/src/prompts/system/plan-mode-active.md +114 -0
  566. package/src/prompts/system/plan-mode-approved.md +23 -0
  567. package/src/prompts/system/plan-mode-reference.md +14 -0
  568. package/src/prompts/system/plan-mode-subagent.md +34 -0
  569. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  570. package/src/prompts/system/subagent-submit-reminder.md +7 -0
  571. package/src/prompts/system/subagent-system-prompt.md +36 -0
  572. package/src/prompts/system/subagent-user-prompt.md +12 -0
  573. package/src/prompts/system/summarization-system.md +3 -0
  574. package/src/prompts/system/system-prompt.md +698 -0
  575. package/src/prompts/system/title-system.md +2 -0
  576. package/src/prompts/system/ttsr-interrupt.md +7 -0
  577. package/src/prompts/system/web-search.md +28 -0
  578. package/src/prompts/tools/ask.md +28 -0
  579. package/src/prompts/tools/ast-edit.md +48 -0
  580. package/src/prompts/tools/ast-grep.md +54 -0
  581. package/src/prompts/tools/async-result.md +5 -0
  582. package/src/prompts/tools/bash.md +67 -0
  583. package/src/prompts/tools/browser.md +26 -0
  584. package/src/prompts/tools/calculator.md +10 -0
  585. package/src/prompts/tools/cancel-job.md +5 -0
  586. package/src/prompts/tools/catalog-workflow-runner.md +13 -0
  587. package/src/prompts/tools/checkpoint.md +16 -0
  588. package/src/prompts/tools/chunk-edit.md +279 -0
  589. package/src/prompts/tools/debug.md +43 -0
  590. package/src/prompts/tools/display-image.md +23 -0
  591. package/src/prompts/tools/exit-plan-mode.md +41 -0
  592. package/src/prompts/tools/find.md +26 -0
  593. package/src/prompts/tools/gemini-image.md +7 -0
  594. package/src/prompts/tools/grep.md +29 -0
  595. package/src/prompts/tools/hashline.md +131 -0
  596. package/src/prompts/tools/inspect-image-system.md +20 -0
  597. package/src/prompts/tools/inspect-image.md +32 -0
  598. package/src/prompts/tools/lsp.md +33 -0
  599. package/src/prompts/tools/patch.md +77 -0
  600. package/src/prompts/tools/poll.md +5 -0
  601. package/src/prompts/tools/python.md +60 -0
  602. package/src/prompts/tools/read-chunk.md +35 -0
  603. package/src/prompts/tools/read.md +63 -0
  604. package/src/prompts/tools/render-mermaid.md +9 -0
  605. package/src/prompts/tools/replace.md +36 -0
  606. package/src/prompts/tools/resolve.md +8 -0
  607. package/src/prompts/tools/rewind.md +13 -0
  608. package/src/prompts/tools/search-tool-bm25.md +34 -0
  609. package/src/prompts/tools/ssh.md +44 -0
  610. package/src/prompts/tools/task-summary.md +28 -0
  611. package/src/prompts/tools/task.md +131 -0
  612. package/src/prompts/tools/todo-write.md +89 -0
  613. package/src/prompts/tools/vim.md +98 -0
  614. package/src/prompts/tools/web-search.md +10 -0
  615. package/src/prompts/tools/write.md +14 -0
  616. package/src/prompts/tools/xcsh-api.md +283 -0
  617. package/src/resource-management/index.ts +42 -0
  618. package/src/sdk.ts +2123 -0
  619. package/src/secrets/index.ts +146 -0
  620. package/src/secrets/obfuscator.ts +290 -0
  621. package/src/secrets/regex.ts +21 -0
  622. package/src/services/context-env.ts +102 -0
  623. package/src/services/xcsh-api-client.ts +230 -0
  624. package/src/services/xcsh-context-command.ts +803 -0
  625. package/src/services/xcsh-context-display.ts +49 -0
  626. package/src/services/xcsh-context-indicators.ts +17 -0
  627. package/src/services/xcsh-context-segment.ts +39 -0
  628. package/src/services/xcsh-context.ts +1410 -0
  629. package/src/services/xcsh-env.ts +107 -0
  630. package/src/services/xcsh-knowledge.ts +177 -0
  631. package/src/services/xcsh-table.ts +164 -0
  632. package/src/session/agent-session.ts +6690 -0
  633. package/src/session/agent-storage.ts +451 -0
  634. package/src/session/artifacts.ts +132 -0
  635. package/src/session/auth-storage.ts +17 -0
  636. package/src/session/blob-store.ts +135 -0
  637. package/src/session/compaction/branch-summarization.ts +322 -0
  638. package/src/session/compaction/compaction.ts +1388 -0
  639. package/src/session/compaction/index.ts +7 -0
  640. package/src/session/compaction/pruning.ts +91 -0
  641. package/src/session/compaction/utils.ts +184 -0
  642. package/src/session/history-storage.ts +253 -0
  643. package/src/session/messages.ts +597 -0
  644. package/src/session/session-dump-format.ts +203 -0
  645. package/src/session/session-manager.ts +2816 -0
  646. package/src/session/session-storage.ts +370 -0
  647. package/src/session/streaming-output.ts +770 -0
  648. package/src/session/tool-choice-queue.ts +213 -0
  649. package/src/slash-commands/builtin-registry.ts +1614 -0
  650. package/src/slash-commands/export-command.ts +178 -0
  651. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  652. package/src/slash-commands/resource-commands.ts +250 -0
  653. package/src/ssh/config-writer.ts +183 -0
  654. package/src/ssh/connection-manager.ts +445 -0
  655. package/src/ssh/ssh-executor.ts +131 -0
  656. package/src/ssh/sshfs-mount.ts +137 -0
  657. package/src/ssh/utils.ts +8 -0
  658. package/src/stt/downloader.ts +71 -0
  659. package/src/stt/index.ts +3 -0
  660. package/src/stt/recorder.ts +351 -0
  661. package/src/stt/setup.ts +52 -0
  662. package/src/stt/stt-controller.ts +160 -0
  663. package/src/stt/transcribe.py +70 -0
  664. package/src/stt/transcriber.ts +91 -0
  665. package/src/system-prompt.ts +688 -0
  666. package/src/task/agents.ts +166 -0
  667. package/src/task/commands.ts +131 -0
  668. package/src/task/discovery.ts +126 -0
  669. package/src/task/executor.ts +1324 -0
  670. package/src/task/index.ts +1197 -0
  671. package/src/task/isolation-backend.ts +72 -0
  672. package/src/task/name-generator.ts +1577 -0
  673. package/src/task/output-manager.ts +107 -0
  674. package/src/task/parallel.ts +116 -0
  675. package/src/task/render.ts +1035 -0
  676. package/src/task/subprocess-tool-registry.ts +88 -0
  677. package/src/task/template.ts +33 -0
  678. package/src/task/types.ts +230 -0
  679. package/src/task/worktree.ts +580 -0
  680. package/src/task/xcsh-command.ts +26 -0
  681. package/src/thinking.ts +87 -0
  682. package/src/tools/action-renderer.ts +198 -0
  683. package/src/tools/archive-reader.ts +315 -0
  684. package/src/tools/ask.ts +791 -0
  685. package/src/tools/ast-edit.ts +492 -0
  686. package/src/tools/ast-grep.ts +453 -0
  687. package/src/tools/auto-generated-guard.ts +305 -0
  688. package/src/tools/bash-interactive.ts +383 -0
  689. package/src/tools/bash-interceptor.ts +67 -0
  690. package/src/tools/bash-normalize.ts +107 -0
  691. package/src/tools/bash-skill-urls.ts +246 -0
  692. package/src/tools/bash.ts +878 -0
  693. package/src/tools/browser-renderer.ts +129 -0
  694. package/src/tools/browser.ts +1366 -0
  695. package/src/tools/calculator.ts +543 -0
  696. package/src/tools/cancel-job.ts +100 -0
  697. package/src/tools/catalog-workflow-runner.ts +718 -0
  698. package/src/tools/checkpoint.ts +133 -0
  699. package/src/tools/context.ts +39 -0
  700. package/src/tools/debug.ts +1004 -0
  701. package/src/tools/display-image-renderer.ts +96 -0
  702. package/src/tools/display-image.ts +109 -0
  703. package/src/tools/exit-plan-mode.ts +101 -0
  704. package/src/tools/fetch.ts +1462 -0
  705. package/src/tools/find.ts +437 -0
  706. package/src/tools/fs-cache-invalidation.ts +33 -0
  707. package/src/tools/gemini-image.ts +1004 -0
  708. package/src/tools/grep.ts +705 -0
  709. package/src/tools/index.ts +442 -0
  710. package/src/tools/inspect-image-renderer.ts +101 -0
  711. package/src/tools/inspect-image.ts +176 -0
  712. package/src/tools/json-tree.ts +266 -0
  713. package/src/tools/jtd-to-json-schema.ts +199 -0
  714. package/src/tools/jtd-to-typescript.ts +136 -0
  715. package/src/tools/jtd-utils.ts +102 -0
  716. package/src/tools/list-limit.ts +40 -0
  717. package/src/tools/mermaid-renderer.ts +127 -0
  718. package/src/tools/notebook.ts +288 -0
  719. package/src/tools/output-meta.ts +588 -0
  720. package/src/tools/path-utils.ts +560 -0
  721. package/src/tools/plan-mode-guard.ts +41 -0
  722. package/src/tools/poll-tool.ts +177 -0
  723. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  724. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  725. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  726. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  727. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  728. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  729. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  730. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  731. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  732. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  733. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  734. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  735. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  736. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  737. package/src/tools/python.ts +1184 -0
  738. package/src/tools/read.ts +1591 -0
  739. package/src/tools/render-mermaid.ts +93 -0
  740. package/src/tools/render-utils.ts +677 -0
  741. package/src/tools/renderers.ts +82 -0
  742. package/src/tools/report-tool-issue.ts +80 -0
  743. package/src/tools/resolve.ts +196 -0
  744. package/src/tools/review.ts +238 -0
  745. package/src/tools/search-tool-bm25.ts +288 -0
  746. package/src/tools/sqlite-reader.ts +623 -0
  747. package/src/tools/ssh.ts +322 -0
  748. package/src/tools/submit-result.ts +229 -0
  749. package/src/tools/todo-render.ts +33 -0
  750. package/src/tools/todo-write.ts +440 -0
  751. package/src/tools/tool-errors.ts +62 -0
  752. package/src/tools/tool-result.ts +93 -0
  753. package/src/tools/tool-timeouts.ts +30 -0
  754. package/src/tools/vim.ts +981 -0
  755. package/src/tools/write.ts +671 -0
  756. package/src/tools/xcsh-api-renderer.ts +402 -0
  757. package/src/tools/xcsh-api.ts +887 -0
  758. package/src/tui/code-cell.ts +101 -0
  759. package/src/tui/file-list.ts +47 -0
  760. package/src/tui/index.ts +11 -0
  761. package/src/tui/output-block.ts +175 -0
  762. package/src/tui/status-line.ts +39 -0
  763. package/src/tui/tree-list.ts +84 -0
  764. package/src/tui/types.ts +15 -0
  765. package/src/tui/utils.ts +103 -0
  766. package/src/utils/changelog.ts +98 -0
  767. package/src/utils/clipboard.ts +80 -0
  768. package/src/utils/command-args.ts +76 -0
  769. package/src/utils/commit-message-generator.ts +137 -0
  770. package/src/utils/edit-mode.ts +50 -0
  771. package/src/utils/event-bus.ts +33 -0
  772. package/src/utils/external-editor.ts +65 -0
  773. package/src/utils/file-display-mode.ts +37 -0
  774. package/src/utils/file-mentions.ts +376 -0
  775. package/src/utils/fuzzy.ts +108 -0
  776. package/src/utils/git.ts +1495 -0
  777. package/src/utils/gitstatus.ts +140 -0
  778. package/src/utils/image-convert.ts +27 -0
  779. package/src/utils/image-loading.ts +98 -0
  780. package/src/utils/image-passthrough.ts +118 -0
  781. package/src/utils/image-resize.ts +272 -0
  782. package/src/utils/image-viewer.ts +17 -0
  783. package/src/utils/lang-from-path.ts +239 -0
  784. package/src/utils/markit.ts +81 -0
  785. package/src/utils/open.ts +20 -0
  786. package/src/utils/session-color.ts +55 -0
  787. package/src/utils/shell-snapshot.ts +188 -0
  788. package/src/utils/title-generator.ts +193 -0
  789. package/src/utils/tool-choice.ts +28 -0
  790. package/src/utils/tools-manager.ts +356 -0
  791. package/src/vim/buffer.ts +309 -0
  792. package/src/vim/commands.ts +382 -0
  793. package/src/vim/engine.ts +2426 -0
  794. package/src/vim/parser.ts +151 -0
  795. package/src/vim/render.ts +252 -0
  796. package/src/vim/types.ts +197 -0
  797. package/src/web/kagi.ts +174 -0
  798. package/src/web/parallel.ts +346 -0
  799. package/src/web/scrapers/artifacthub.ts +207 -0
  800. package/src/web/scrapers/arxiv.ts +83 -0
  801. package/src/web/scrapers/aur.ts +162 -0
  802. package/src/web/scrapers/biorxiv.ts +133 -0
  803. package/src/web/scrapers/bluesky.ts +262 -0
  804. package/src/web/scrapers/brew.ts +172 -0
  805. package/src/web/scrapers/cheatsh.ts +68 -0
  806. package/src/web/scrapers/chocolatey.ts +196 -0
  807. package/src/web/scrapers/choosealicense.ts +95 -0
  808. package/src/web/scrapers/cisa-kev.ts +87 -0
  809. package/src/web/scrapers/clojars.ts +154 -0
  810. package/src/web/scrapers/coingecko.ts +177 -0
  811. package/src/web/scrapers/crates-io.ts +110 -0
  812. package/src/web/scrapers/crossref.ts +136 -0
  813. package/src/web/scrapers/devto.ts +147 -0
  814. package/src/web/scrapers/discogs.ts +306 -0
  815. package/src/web/scrapers/discourse.ts +197 -0
  816. package/src/web/scrapers/dockerhub.ts +138 -0
  817. package/src/web/scrapers/docs-rs.ts +653 -0
  818. package/src/web/scrapers/fdroid.ts +134 -0
  819. package/src/web/scrapers/firefox-addons.ts +191 -0
  820. package/src/web/scrapers/flathub.ts +223 -0
  821. package/src/web/scrapers/github-gist.ts +58 -0
  822. package/src/web/scrapers/github.ts +452 -0
  823. package/src/web/scrapers/gitlab.ts +401 -0
  824. package/src/web/scrapers/go-pkg.ts +266 -0
  825. package/src/web/scrapers/hackage.ts +140 -0
  826. package/src/web/scrapers/hackernews.ts +189 -0
  827. package/src/web/scrapers/hex.ts +105 -0
  828. package/src/web/scrapers/huggingface.ts +321 -0
  829. package/src/web/scrapers/iacr.ts +89 -0
  830. package/src/web/scrapers/index.ts +252 -0
  831. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  832. package/src/web/scrapers/lemmy.ts +203 -0
  833. package/src/web/scrapers/lobsters.ts +175 -0
  834. package/src/web/scrapers/mastodon.ts +292 -0
  835. package/src/web/scrapers/maven.ts +138 -0
  836. package/src/web/scrapers/mdn.ts +169 -0
  837. package/src/web/scrapers/metacpan.ts +222 -0
  838. package/src/web/scrapers/musicbrainz.ts +250 -0
  839. package/src/web/scrapers/npm.ts +98 -0
  840. package/src/web/scrapers/nuget.ts +183 -0
  841. package/src/web/scrapers/nvd.ts +222 -0
  842. package/src/web/scrapers/ollama.ts +239 -0
  843. package/src/web/scrapers/open-vsx.ts +106 -0
  844. package/src/web/scrapers/opencorporates.ts +292 -0
  845. package/src/web/scrapers/openlibrary.ts +336 -0
  846. package/src/web/scrapers/orcid.ts +286 -0
  847. package/src/web/scrapers/osv.ts +176 -0
  848. package/src/web/scrapers/packagist.ts +160 -0
  849. package/src/web/scrapers/pub-dev.ts +143 -0
  850. package/src/web/scrapers/pubmed.ts +211 -0
  851. package/src/web/scrapers/pypi.ts +112 -0
  852. package/src/web/scrapers/rawg.ts +110 -0
  853. package/src/web/scrapers/readthedocs.ts +120 -0
  854. package/src/web/scrapers/reddit.ts +95 -0
  855. package/src/web/scrapers/repology.ts +251 -0
  856. package/src/web/scrapers/rfc.ts +201 -0
  857. package/src/web/scrapers/rubygems.ts +103 -0
  858. package/src/web/scrapers/searchcode.ts +190 -0
  859. package/src/web/scrapers/sec-edgar.ts +261 -0
  860. package/src/web/scrapers/semantic-scholar.ts +171 -0
  861. package/src/web/scrapers/snapcraft.ts +187 -0
  862. package/src/web/scrapers/sourcegraph.ts +336 -0
  863. package/src/web/scrapers/spdx.ts +108 -0
  864. package/src/web/scrapers/spotify.ts +198 -0
  865. package/src/web/scrapers/stackoverflow.ts +120 -0
  866. package/src/web/scrapers/terraform.ts +277 -0
  867. package/src/web/scrapers/tldr.ts +47 -0
  868. package/src/web/scrapers/twitter.ts +93 -0
  869. package/src/web/scrapers/types.ts +267 -0
  870. package/src/web/scrapers/utils.ts +109 -0
  871. package/src/web/scrapers/vimeo.ts +133 -0
  872. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  873. package/src/web/scrapers/w3c.ts +156 -0
  874. package/src/web/scrapers/wikidata.ts +344 -0
  875. package/src/web/scrapers/wikipedia.ts +84 -0
  876. package/src/web/scrapers/youtube.ts +313 -0
  877. package/src/web/search/errors.ts +120 -0
  878. package/src/web/search/index.ts +346 -0
  879. package/src/web/search/params.ts +129 -0
  880. package/src/web/search/provider.ts +97 -0
  881. package/src/web/search/providers/anthropic.ts +339 -0
  882. package/src/web/search/providers/base.ts +35 -0
  883. package/src/web/search/providers/brave.ts +150 -0
  884. package/src/web/search/providers/codex.ts +402 -0
  885. package/src/web/search/providers/exa.ts +264 -0
  886. package/src/web/search/providers/firecrawl.ts +115 -0
  887. package/src/web/search/providers/gemini.ts +566 -0
  888. package/src/web/search/providers/jina.ts +99 -0
  889. package/src/web/search/providers/kagi.ts +70 -0
  890. package/src/web/search/providers/kimi.ts +157 -0
  891. package/src/web/search/providers/parallel.ts +63 -0
  892. package/src/web/search/providers/perplexity.ts +546 -0
  893. package/src/web/search/providers/synthetic.ts +113 -0
  894. package/src/web/search/providers/tavily.ts +162 -0
  895. package/src/web/search/providers/utils.ts +36 -0
  896. package/src/web/search/providers/zai.ts +311 -0
  897. package/src/web/search/render.ts +348 -0
  898. package/src/web/search/types.ts +438 -0
  899. package/src/web/search/utils.ts +17 -0
@@ -0,0 +1,2563 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { ThinkingLevel } from "@f5xc-salesdemos/pi-agent-core";
4
+ import type { Effort } from "@f5xc-salesdemos/pi-ai";
5
+ import {
6
+ detectMacOSAppearance,
7
+ MacAppearanceObserver,
8
+ type HighlightColors as NativeHighlightColors,
9
+ highlightCode as nativeHighlightCode,
10
+ supportsLanguage as nativeSupportsLanguage,
11
+ } from "@f5xc-salesdemos/pi-natives";
12
+ import type { EditorTheme, MarkdownTheme, SelectListTheme, SymbolTheme } from "@f5xc-salesdemos/pi-tui";
13
+ import { adjustHsv, detectDiagramType, getCustomThemesDir, isEnoent, logger } from "@f5xc-salesdemos/pi-utils";
14
+ import { type Static, Type } from "@sinclair/typebox";
15
+ import { TypeCompiler } from "@sinclair/typebox/compiler";
16
+ import chalk from "chalk";
17
+ import { buildMermaidBlockOptions, diagramTypeLabel } from "../../tools/mermaid-renderer";
18
+ import { renderOutputBlock } from "../../tui";
19
+ // Embed theme JSON files at build time
20
+ import { defaultThemes } from "./defaults";
21
+ import { getMermaidAscii, mermaidThemeSignature, renderMermaidThemed } from "./mermaid-cache";
22
+
23
+ export { getLanguageFromPath } from "../../utils/lang-from-path";
24
+
25
+ // ============================================================================
26
+ // Symbol Presets
27
+ // ============================================================================
28
+
29
+ export type SymbolPreset = "unicode" | "nerd" | "ascii";
30
+
31
+ /**
32
+ * All available symbol keys organized by category.
33
+ */
34
+ export type SymbolKey =
35
+ // Status Indicators
36
+ | "status.success"
37
+ | "status.error"
38
+ | "status.warning"
39
+ | "status.info"
40
+ | "status.pending"
41
+ | "status.disabled"
42
+ | "status.enabled"
43
+ | "status.running"
44
+ | "status.shadowed"
45
+ | "status.aborted"
46
+ // Navigation
47
+ | "nav.cursor"
48
+ | "nav.selected"
49
+ | "nav.expand"
50
+ | "nav.collapse"
51
+ | "nav.back"
52
+ // Tree Connectors
53
+ | "tree.branch"
54
+ | "tree.last"
55
+ | "tree.vertical"
56
+ | "tree.horizontal"
57
+ | "tree.hook"
58
+ // Box Drawing - Rounded
59
+ | "boxRound.topLeft"
60
+ | "boxRound.topRight"
61
+ | "boxRound.bottomLeft"
62
+ | "boxRound.bottomRight"
63
+ | "boxRound.horizontal"
64
+ | "boxRound.vertical"
65
+ // Box Drawing - Sharp
66
+ | "boxSharp.topLeft"
67
+ | "boxSharp.topRight"
68
+ | "boxSharp.bottomLeft"
69
+ | "boxSharp.bottomRight"
70
+ | "boxSharp.horizontal"
71
+ | "boxSharp.vertical"
72
+ | "boxSharp.cross"
73
+ | "boxSharp.teeDown"
74
+ | "boxSharp.teeUp"
75
+ | "boxSharp.teeRight"
76
+ | "boxSharp.teeLeft"
77
+ // Separators
78
+ | "sep.powerline"
79
+ | "sep.powerlineThin"
80
+ | "sep.powerlineLeft"
81
+ | "sep.powerlineRight"
82
+ | "sep.powerlineThinLeft"
83
+ | "sep.powerlineThinRight"
84
+ | "sep.block"
85
+ | "sep.space"
86
+ | "sep.asciiLeft"
87
+ | "sep.asciiRight"
88
+ | "sep.dot"
89
+ | "sep.slash"
90
+ | "sep.pipe"
91
+ // Icons
92
+ | "icon.model"
93
+ | "icon.plan"
94
+ | "icon.folder"
95
+ | "icon.file"
96
+ | "icon.git"
97
+ | "icon.branch"
98
+ | "icon.pr"
99
+ | "icon.tokens"
100
+ | "icon.context"
101
+ | "icon.cost"
102
+ | "icon.time"
103
+ | "icon.pi"
104
+ | "icon.agents"
105
+ | "icon.cache"
106
+ | "icon.input"
107
+ | "icon.output"
108
+ | "icon.host"
109
+ | "icon.session"
110
+ | "icon.package"
111
+ | "icon.warning"
112
+ | "icon.rewind"
113
+ | "icon.auto"
114
+ | "icon.fast"
115
+ | "icon.extensionSkill"
116
+ | "icon.extensionTool"
117
+ | "icon.extensionSlashCommand"
118
+ | "icon.extensionMcp"
119
+ | "icon.extensionRule"
120
+ | "icon.extensionHook"
121
+ | "icon.extensionPrompt"
122
+ | "icon.extensionContextFile"
123
+ | "icon.extensionInstruction"
124
+ // STT
125
+ | "icon.mic"
126
+ // Thinking Levels
127
+ | "thinking.minimal"
128
+ | "thinking.low"
129
+ | "thinking.medium"
130
+ | "thinking.high"
131
+ | "thinking.xhigh"
132
+ // Checkboxes
133
+ | "checkbox.checked"
134
+ | "checkbox.unchecked"
135
+ // Todo status
136
+ | "todo.active"
137
+ | "todo.pending"
138
+ | "todo.done"
139
+ | "todo.abandoned"
140
+ // Text Formatting
141
+ | "format.bullet"
142
+ | "format.dash"
143
+ | "format.bracketLeft"
144
+ | "format.bracketRight"
145
+ // Markdown-specific
146
+ | "md.quoteBorder"
147
+ | "md.hrChar"
148
+ | "md.bullet"
149
+ // Language/file type icons
150
+ | "lang.default"
151
+ | "lang.typescript"
152
+ | "lang.javascript"
153
+ | "lang.python"
154
+ | "lang.rust"
155
+ | "lang.go"
156
+ | "lang.java"
157
+ | "lang.c"
158
+ | "lang.cpp"
159
+ | "lang.csharp"
160
+ | "lang.ruby"
161
+ | "lang.php"
162
+ | "lang.swift"
163
+ | "lang.kotlin"
164
+ | "lang.shell"
165
+ | "lang.html"
166
+ | "lang.css"
167
+ | "lang.json"
168
+ | "lang.yaml"
169
+ | "lang.markdown"
170
+ | "lang.sql"
171
+ | "lang.docker"
172
+ | "lang.lua"
173
+ | "lang.text"
174
+ | "lang.env"
175
+ | "lang.toml"
176
+ | "lang.xml"
177
+ | "lang.ini"
178
+ | "lang.conf"
179
+ | "lang.log"
180
+ | "lang.csv"
181
+ | "lang.tsv"
182
+ | "lang.image"
183
+ | "lang.pdf"
184
+ | "lang.archive"
185
+ | "lang.binary"
186
+ // Settings tab icons
187
+ | "tab.appearance"
188
+ | "tab.model"
189
+ | "tab.interaction"
190
+ | "tab.context"
191
+ | "tab.editing"
192
+ | "tab.tools"
193
+ | "tab.tasks"
194
+ | "tab.providers";
195
+
196
+ type SymbolMap = Record<SymbolKey, string>;
197
+
198
+ const UNICODE_SYMBOLS: SymbolMap = {
199
+ // Status
200
+ "status.success": "✔",
201
+ "status.error": "✘",
202
+ "status.warning": "⚠",
203
+ "status.info": "ⓘ",
204
+ "status.pending": "⏳",
205
+ "status.disabled": "⦸",
206
+ "status.enabled": "●",
207
+ "status.running": "⟳",
208
+ "status.shadowed": "◌",
209
+ "status.aborted": "⏹",
210
+ // Navigation
211
+ "nav.cursor": "❯",
212
+ "nav.selected": "➤",
213
+ "nav.expand": "▸",
214
+ "nav.collapse": "▾",
215
+ "nav.back": "⟵",
216
+ // Tree
217
+ "tree.branch": "├─",
218
+ "tree.last": "└─",
219
+ "tree.vertical": "│",
220
+ "tree.horizontal": "─",
221
+ "tree.hook": "└",
222
+ // Box (rounded)
223
+ "boxRound.topLeft": "╭",
224
+ "boxRound.topRight": "╮",
225
+ "boxRound.bottomLeft": "╰",
226
+ "boxRound.bottomRight": "╯",
227
+ "boxRound.horizontal": "─",
228
+ "boxRound.vertical": "│",
229
+ // Box (sharp)
230
+ "boxSharp.topLeft": "┌",
231
+ "boxSharp.topRight": "┐",
232
+ "boxSharp.bottomLeft": "└",
233
+ "boxSharp.bottomRight": "┘",
234
+ "boxSharp.horizontal": "─",
235
+ "boxSharp.vertical": "│",
236
+ "boxSharp.cross": "┼",
237
+ "boxSharp.teeDown": "┬",
238
+ "boxSharp.teeUp": "┴",
239
+ "boxSharp.teeRight": "├",
240
+ "boxSharp.teeLeft": "┤",
241
+ // Separators (powerline-ish, but pure Unicode)
242
+ "sep.powerline": "▕",
243
+ "sep.powerlineThin": "┆",
244
+ "sep.powerlineLeft": "▶",
245
+ "sep.powerlineRight": "◀",
246
+ "sep.powerlineThinLeft": ">",
247
+ "sep.powerlineThinRight": "<",
248
+ "sep.block": "▌",
249
+ "sep.space": " ",
250
+ "sep.asciiLeft": ">",
251
+ "sep.asciiRight": "<",
252
+ "sep.dot": " · ",
253
+ "sep.slash": " / ",
254
+ "sep.pipe": " │ ",
255
+ // Icons
256
+ "icon.model": "⬢",
257
+ "icon.plan": "🗺",
258
+ "icon.folder": "📁",
259
+ "icon.file": "📄",
260
+ "icon.git": "⎇",
261
+ "icon.branch": "⑂",
262
+ "icon.pr": "⤴",
263
+ "icon.tokens": "🪙",
264
+ "icon.context": "◫",
265
+ "icon.cost": "💲",
266
+ "icon.time": "⏱",
267
+ "icon.pi": "π",
268
+ "icon.agents": "👥",
269
+ "icon.cache": "💾",
270
+ "icon.input": "⤵",
271
+ "icon.output": "⤴",
272
+ "icon.host": "🖥",
273
+ "icon.session": "🆔",
274
+ "icon.package": "📦",
275
+ "icon.warning": "⚠",
276
+ "icon.rewind": "↶",
277
+ "icon.auto": "⟲",
278
+ "icon.fast": "⚡",
279
+ "icon.extensionSkill": "✦",
280
+ "icon.extensionTool": "🛠",
281
+ "icon.extensionSlashCommand": "⌘",
282
+ "icon.extensionMcp": "🔌",
283
+ "icon.extensionRule": "⚖",
284
+ "icon.extensionHook": "🪝",
285
+ "icon.extensionPrompt": "✎",
286
+ "icon.extensionContextFile": "📎",
287
+ "icon.extensionInstruction": "📘",
288
+ // STT
289
+ "icon.mic": "🎤",
290
+ // Thinking levels
291
+ "thinking.minimal": "◔ min",
292
+ "thinking.low": "◑ low",
293
+ "thinking.medium": "◒ med",
294
+ "thinking.high": "◕ high",
295
+ "thinking.xhigh": "◉ xhi",
296
+ // Checkboxes
297
+ "checkbox.checked": "☑",
298
+ "checkbox.unchecked": "☐",
299
+ // Todo status
300
+ "todo.active": "■",
301
+ "todo.pending": "□",
302
+ "todo.done": "✓",
303
+ "todo.abandoned": "✗",
304
+ // Formatting
305
+ "format.bullet": "•",
306
+ "format.dash": "—",
307
+ "format.bracketLeft": "⟦",
308
+ "format.bracketRight": "⟧",
309
+ // Markdown
310
+ "md.quoteBorder": "▏",
311
+ "md.hrChar": "─",
312
+ "md.bullet": "•",
313
+ // Language/file icons (emoji-centric, no Nerd Font required)
314
+ "lang.default": "⌘",
315
+ "lang.typescript": "🟦",
316
+ "lang.javascript": "🟨",
317
+ "lang.python": "🐍",
318
+ "lang.rust": "🦀",
319
+ "lang.go": "🐹",
320
+ "lang.java": "☕",
321
+ "lang.c": "Ⓒ",
322
+ "lang.cpp": "➕",
323
+ "lang.csharp": "♯",
324
+ "lang.ruby": "💎",
325
+ "lang.php": "🐘",
326
+ "lang.swift": "🕊",
327
+ "lang.kotlin": "🅺",
328
+ "lang.shell": "💻",
329
+ "lang.html": "🌐",
330
+ "lang.css": "🎨",
331
+ "lang.json": "🧾",
332
+ "lang.yaml": "📋",
333
+ "lang.markdown": "📝",
334
+ "lang.sql": "🗄",
335
+ "lang.docker": "🐳",
336
+ "lang.lua": "🌙",
337
+ "lang.text": "🗒",
338
+ "lang.env": "🔧",
339
+ "lang.toml": "🧾",
340
+ "lang.xml": "⟨⟩",
341
+ "lang.ini": "⚙",
342
+ "lang.conf": "⚙",
343
+ "lang.log": "📜",
344
+ "lang.csv": "📑",
345
+ "lang.tsv": "📑",
346
+ "lang.image": "🖼",
347
+ "lang.pdf": "📕",
348
+ "lang.archive": "🗜",
349
+ "lang.binary": "⚙",
350
+ // Settings tabs
351
+ "tab.appearance": "🎨",
352
+ "tab.model": "🤖",
353
+ "tab.interaction": "⌨",
354
+ "tab.context": "📋",
355
+ "tab.editing": "💻",
356
+ "tab.tools": "🔧",
357
+ "tab.tasks": "📦",
358
+ "tab.providers": "🌐",
359
+ };
360
+
361
+ const NERD_SYMBOLS: SymbolMap = {
362
+ // Status Indicators
363
+ // pick:  | alt:   
364
+ "status.success": "\uf00c",
365
+ // pick:  | alt:   
366
+ "status.error": "\uf00d",
367
+ // pick:  | alt:  
368
+ "status.warning": "\uf12a",
369
+ // pick:  | alt: 
370
+ "status.info": "\uf129",
371
+ // pick:  | alt:   
372
+ "status.pending": "\uf254",
373
+ // pick:  | alt:  
374
+ "status.disabled": "\uf05e",
375
+ // pick:  | alt:  
376
+ "status.enabled": "\uf111",
377
+ // pick:  | alt:   
378
+ "status.running": "\uf110",
379
+ // pick: ◐ | alt: ◑ ◒ ◓ ◔
380
+ "status.shadowed": "◐",
381
+ // pick:  | alt:  
382
+ "status.aborted": "\uf04d",
383
+ // Navigation
384
+ // pick:  | alt:  
385
+ "nav.cursor": "\uf054",
386
+ // pick:  | alt:  
387
+ "nav.selected": "\uf178",
388
+ // pick:  | alt:  
389
+ "nav.expand": "\uf0da",
390
+ // pick:  | alt:  
391
+ "nav.collapse": "\uf0d7",
392
+ // pick:  | alt:  
393
+ "nav.back": "\uf060",
394
+ // Tree Connectors (same as unicode)
395
+ // pick: ├─ | alt: ├╴ ├╌ ╠═ ┣━
396
+ "tree.branch": "\u251c\u2500",
397
+ // pick: └─ | alt: └╴ └╌ ╚═ ┗━
398
+ "tree.last": "\u2514\u2500",
399
+ // pick: │ | alt: ┃ ║ ▏ ▕
400
+ "tree.vertical": "\u2502",
401
+ // pick: ─ | alt: ━ ═ ╌ ┄
402
+ "tree.horizontal": "\u2500",
403
+ // pick: └ | alt: ╰ ⎿ ↳
404
+ "tree.hook": "\u2514",
405
+ // Box Drawing - Rounded (same as unicode)
406
+ // pick: ╭ | alt: ┌ ┏ ╔
407
+ "boxRound.topLeft": "\u256d",
408
+ // pick: ╮ | alt: ┐ ┓ ╗
409
+ "boxRound.topRight": "\u256e",
410
+ // pick: ╰ | alt: └ ┗ ╚
411
+ "boxRound.bottomLeft": "\u2570",
412
+ // pick: ╯ | alt: ┘ ┛ ╝
413
+ "boxRound.bottomRight": "\u256f",
414
+ // pick: ─ | alt: ━ ═ ╌
415
+ "boxRound.horizontal": "\u2500",
416
+ // pick: │ | alt: ┃ ║ ▏
417
+ "boxRound.vertical": "\u2502",
418
+ // Box Drawing - Sharp (same as unicode)
419
+ // pick: ┌ | alt: ┏ ╭ ╔
420
+ "boxSharp.topLeft": "\u250c",
421
+ // pick: ┐ | alt: ┓ ╮ ╗
422
+ "boxSharp.topRight": "\u2510",
423
+ // pick: └ | alt: ┗ ╰ ╚
424
+ "boxSharp.bottomLeft": "\u2514",
425
+ // pick: ┘ | alt: ┛ ╯ ╝
426
+ "boxSharp.bottomRight": "\u2518",
427
+ // pick: ─ | alt: ━ ═ ╌
428
+ "boxSharp.horizontal": "\u2500",
429
+ // pick: │ | alt: ┃ ║ ▏
430
+ "boxSharp.vertical": "\u2502",
431
+ // pick: ┼ | alt: ╋ ╬ ┿
432
+ "boxSharp.cross": "\u253c",
433
+ // pick: ┬ | alt: ╦ ┯ ┳
434
+ "boxSharp.teeDown": "\u252c",
435
+ // pick: ┴ | alt: ╩ ┷ ┻
436
+ "boxSharp.teeUp": "\u2534",
437
+ // pick: ├ | alt: ╠ ┝ ┣
438
+ "boxSharp.teeRight": "\u251c",
439
+ // pick: ┤ | alt: ╣ ┥ ┫
440
+ "boxSharp.teeLeft": "\u2524",
441
+ // Separators - Nerd Font specific
442
+ // pick:  | alt:   
443
+ "sep.powerline": "\ue0b0",
444
+ // pick:  | alt:  
445
+ "sep.powerlineThin": "\ue0b1",
446
+ // pick:  | alt:  
447
+ "sep.powerlineLeft": "\ue0b0",
448
+ // pick:  | alt:  
449
+ "sep.powerlineRight": "\ue0b2",
450
+ // pick:  | alt: 
451
+ "sep.powerlineThinLeft": "\ue0b1",
452
+ // pick:  | alt: 
453
+ "sep.powerlineThinRight": "\ue0b3",
454
+ // pick: █ | alt: ▓ ▒ ░ ▉ ▌
455
+ "sep.block": "\u2588",
456
+ // pick: space | alt: ␠ ·
457
+ "sep.space": " ",
458
+ // pick: > | alt: › » ▸
459
+ "sep.asciiLeft": ">",
460
+ // pick: < | alt: ‹ « ◂
461
+ "sep.asciiRight": "<",
462
+ // pick: · | alt: • ⋅
463
+ "sep.dot": " \u00b7 ",
464
+ // pick:  | alt: / ∕ ⁄
465
+ "sep.slash": "\ue0bb",
466
+ // pick:  | alt: │ ┃ |
467
+ "sep.pipe": "\ue0b3",
468
+ // Icons - Nerd Font specific
469
+ // pick:  | alt:   ◆
470
+ "icon.model": "\uec19",
471
+ // pick:  | alt:  
472
+ "icon.plan": "\uf2d2",
473
+ // pick:  | alt:  
474
+ "icon.folder": "\uf115",
475
+ // pick:  | alt:  
476
+ "icon.file": "\uf15b",
477
+ // pick:  | alt:  ⎇
478
+ "icon.git": "\uf1d3",
479
+ // pick:  | alt:  ⎇
480
+ "icon.branch": "\uf126",
481
+ // pick:  (nf-cod-git_pull_request) | alt: (nf-oct-git_pull_request)
482
+ "icon.pr": "\uea64",
483
+ // pick:  | alt: ⊛ ◍ 
484
+ "icon.tokens": "\ue26b",
485
+ // pick:  | alt: ◫ ▦
486
+ "icon.context": "\ue70f",
487
+ // pick:  | alt: $ ¢
488
+ "icon.cost": "\uf155",
489
+ // pick:  | alt: ◷ ◴
490
+ "icon.time": "\uf017",
491
+ // pick:  | alt: π ∏ ∑
492
+ "icon.pi": "\ue22c",
493
+ // pick:  | alt: 
494
+ "icon.agents": "\uf0c0",
495
+ // pick:  | alt:  
496
+ "icon.cache": "\uf1c0",
497
+ // pick:  | alt:  →
498
+ "icon.input": "\uf090",
499
+ // pick:  | alt:  →
500
+ "icon.output": "\uf08b",
501
+ // pick:  | alt:  
502
+ "icon.host": "\uf109",
503
+ // pick:  | alt:  
504
+ "icon.session": "\uf550",
505
+ // pick:  | alt: 
506
+ "icon.package": "\uf487",
507
+ // pick:  | alt:  
508
+ "icon.warning": "\uf071",
509
+ // pick:  | alt:  ↺
510
+ "icon.rewind": "\uf0e2",
511
+ // pick: 󰁨 | alt:   
512
+ "icon.auto": "\u{f0068}",
513
+ "icon.fast": "\uf0e7",
514
+ "icon.extensionSkill": "\uf0eb",
515
+ // pick:  | alt:  
516
+ "icon.extensionTool": "\uf0ad",
517
+ // pick:  | alt: 
518
+ "icon.extensionSlashCommand": "\uf120",
519
+ // pick:  | alt:  
520
+ "icon.extensionMcp": "\uf1e6",
521
+ // pick:  | alt:  
522
+ "icon.extensionRule": "\uf0e3",
523
+ // pick:  | alt: 
524
+ "icon.extensionHook": "\uf0c1",
525
+ // pick:  | alt:  
526
+ "icon.extensionPrompt": "\uf075",
527
+ // pick:  | alt:  
528
+ "icon.extensionContextFile": "\uf0f6",
529
+ // pick:  | alt:  
530
+ "icon.extensionInstruction": "\uf02d",
531
+ // STT - fa-microphone
532
+ "icon.mic": "\uf130",
533
+ // Thinking Levels - emoji labels
534
+ // pick: 🤨 min | alt:  min  min
535
+ "thinking.minimal": "\u{F0E7} min",
536
+ // pick: 🤔 low | alt:  low  low
537
+ "thinking.low": "\u{F10C} low",
538
+ // pick: 🤓 med | alt:  med  med
539
+ "thinking.medium": "\u{F192} med",
540
+ // pick: 🤯 high | alt:  high  high
541
+ "thinking.high": "\u{F111} high",
542
+ // pick: 🧠 xhi | alt:  xhi  xhi
543
+ "thinking.xhigh": "\u{F06D} xhi",
544
+ // Checkboxes
545
+ // pick:  | alt:  
546
+ "checkbox.checked": "\uf14a",
547
+ // pick:  | alt: 
548
+ "checkbox.unchecked": "\uf096",
549
+ // Todo status
550
+ // nf-fa-circle (filled)
551
+ "todo.active": "\uf111",
552
+ // nf-fa-circle-o (hollow)
553
+ "todo.pending": "\uf10c",
554
+ // nf-fa-check
555
+ "todo.done": "\uf00c",
556
+ // nf-fa-times
557
+ "todo.abandoned": "\uf00d",
558
+ // pick:  | alt:   •
559
+ "format.bullet": "\uf111",
560
+ // pick: – | alt: — ― -
561
+ "format.dash": "\u2013",
562
+ // pick: ⟨ | alt: [ ⟦
563
+ "format.bracketLeft": "⟨",
564
+ // pick: ⟩ | alt: ] ⟧
565
+ "format.bracketRight": "⟩",
566
+ // Markdown-specific
567
+ // pick: │ | alt: ┃ ║
568
+ "md.quoteBorder": "\u2502",
569
+ // pick: ─ | alt: ━ ═
570
+ "md.hrChar": "\u2500",
571
+ // pick:  | alt:  •
572
+ "md.bullet": "\uf111",
573
+ // Language icons (nerd font devicons)
574
+ "lang.default": "",
575
+ "lang.typescript": "\u{E628}",
576
+ "lang.javascript": "\u{E60C}",
577
+ "lang.python": "\u{E606}",
578
+ "lang.rust": "\u{E7A8}",
579
+ "lang.go": "\u{E627}",
580
+ "lang.java": "\u{E738}",
581
+ "lang.c": "\u{E61E}",
582
+ "lang.cpp": "\u{E61D}",
583
+ "lang.csharp": "\u{E7BC}",
584
+ "lang.ruby": "\u{E791}",
585
+ "lang.php": "\u{E608}",
586
+ "lang.swift": "\u{E755}",
587
+ "lang.kotlin": "\u{E634}",
588
+ "lang.shell": "\u{E795}",
589
+ "lang.html": "\u{E736}",
590
+ "lang.css": "\u{E749}",
591
+ "lang.json": "\u{E60B}",
592
+ "lang.yaml": "\u{E615}",
593
+ "lang.markdown": "\u{E609}",
594
+ "lang.sql": "\u{E706}",
595
+ "lang.docker": "\u{E7B0}",
596
+ "lang.lua": "\u{E620}",
597
+ "lang.text": "\u{E612}",
598
+ "lang.env": "\u{E615}",
599
+ "lang.toml": "\u{E615}",
600
+ "lang.xml": "\u{F05C0}",
601
+ "lang.ini": "\u{E615}",
602
+ "lang.conf": "\u{E615}",
603
+ "lang.log": "\u{F0331}",
604
+ "lang.csv": "\u{F021B}",
605
+ "lang.tsv": "\u{F021B}",
606
+ "lang.image": "\u{F021F}",
607
+ "lang.pdf": "\u{F0226}",
608
+ "lang.archive": "\u{F187}",
609
+ "lang.binary": "\u{F019A}",
610
+ // Settings tab icons
611
+ "tab.appearance": "󰃣",
612
+ "tab.model": "󰚩",
613
+ "tab.interaction": "󰌌",
614
+ "tab.context": "󰘸",
615
+ "tab.editing": "",
616
+ "tab.tools": "󰠭",
617
+ "tab.tasks": "󰐱",
618
+ "tab.providers": "󰖟",
619
+ };
620
+
621
+ const ASCII_SYMBOLS: SymbolMap = {
622
+ // Status Indicators
623
+ "status.success": "[ok]",
624
+ "status.error": "[!!]",
625
+ "status.warning": "[!]",
626
+ "status.info": "[i]",
627
+ "status.pending": "[*]",
628
+ "status.disabled": "[ ]",
629
+ "status.enabled": "[x]",
630
+ "status.running": "[~]",
631
+ "status.shadowed": "[/]",
632
+ "status.aborted": "[-]",
633
+ // Navigation
634
+ "nav.cursor": ">",
635
+ "nav.selected": "->",
636
+ "nav.expand": "+",
637
+ "nav.collapse": "-",
638
+ "nav.back": "<-",
639
+ // Tree Connectors
640
+ "tree.branch": "|--",
641
+ "tree.last": "'--",
642
+ "tree.vertical": "|",
643
+ "tree.horizontal": "-",
644
+ "tree.hook": "`-",
645
+ // Box Drawing - Rounded (ASCII fallback)
646
+ "boxRound.topLeft": "+",
647
+ "boxRound.topRight": "+",
648
+ "boxRound.bottomLeft": "+",
649
+ "boxRound.bottomRight": "+",
650
+ "boxRound.horizontal": "-",
651
+ "boxRound.vertical": "|",
652
+ // Box Drawing - Sharp (ASCII fallback)
653
+ "boxSharp.topLeft": "+",
654
+ "boxSharp.topRight": "+",
655
+ "boxSharp.bottomLeft": "+",
656
+ "boxSharp.bottomRight": "+",
657
+ "boxSharp.horizontal": "-",
658
+ "boxSharp.vertical": "|",
659
+ "boxSharp.cross": "+",
660
+ "boxSharp.teeDown": "+",
661
+ "boxSharp.teeUp": "+",
662
+ "boxSharp.teeRight": "+",
663
+ "boxSharp.teeLeft": "+",
664
+ // Separators
665
+ "sep.powerline": ">",
666
+ "sep.powerlineThin": ">",
667
+ "sep.powerlineLeft": ">",
668
+ "sep.powerlineRight": "<",
669
+ "sep.powerlineThinLeft": ">",
670
+ "sep.powerlineThinRight": "<",
671
+ "sep.block": "#",
672
+ "sep.space": " ",
673
+ "sep.asciiLeft": ">",
674
+ "sep.asciiRight": "<",
675
+ "sep.dot": " - ",
676
+ "sep.slash": " / ",
677
+ "sep.pipe": " | ",
678
+ // Icons
679
+ "icon.model": "[M]",
680
+ "icon.plan": "plan",
681
+ "icon.folder": "[D]",
682
+ "icon.file": "[F]",
683
+ "icon.git": "git:",
684
+ "icon.branch": "@",
685
+ "icon.pr": "PR",
686
+ "icon.tokens": "tok:",
687
+ "icon.context": "ctx:",
688
+ "icon.cost": "$",
689
+ "icon.time": "t:",
690
+ "icon.pi": "pi",
691
+ "icon.agents": "AG",
692
+ "icon.cache": "cache",
693
+ "icon.input": "in:",
694
+ "icon.output": "out:",
695
+ "icon.host": "host",
696
+ "icon.session": "id",
697
+ "icon.package": "[P]",
698
+ "icon.warning": "[!]",
699
+ "icon.rewind": "<-",
700
+ "icon.auto": "[A]",
701
+ "icon.fast": ">>",
702
+ "icon.extensionSkill": "SK",
703
+ "icon.extensionTool": "TL",
704
+ "icon.extensionSlashCommand": "/",
705
+ "icon.extensionMcp": "MCP",
706
+ "icon.extensionRule": "RL",
707
+ "icon.extensionHook": "HK",
708
+ "icon.extensionPrompt": "PR",
709
+ "icon.extensionContextFile": "CF",
710
+ "icon.extensionInstruction": "IN",
711
+ // STT
712
+ "icon.mic": "MIC",
713
+ // Thinking Levels
714
+ "thinking.minimal": "[min]",
715
+ "thinking.low": "[low]",
716
+ "thinking.medium": "[med]",
717
+ "thinking.high": "[high]",
718
+ "thinking.xhigh": "[xhi]",
719
+ // Checkboxes
720
+ "checkbox.checked": "[x]",
721
+ "checkbox.unchecked": "[ ]",
722
+ // Todo status
723
+ "todo.active": "[>]",
724
+ "todo.pending": "[ ]",
725
+ "todo.done": "[x]",
726
+ "todo.abandoned": "[-]",
727
+ "format.bullet": "*",
728
+ "format.dash": "-",
729
+ "format.bracketLeft": "[",
730
+ "format.bracketRight": "]",
731
+ // Markdown-specific
732
+ "md.quoteBorder": "|",
733
+ "md.hrChar": "-",
734
+ "md.bullet": "*",
735
+ // Language icons (ASCII uses abbreviations)
736
+ "lang.default": "code",
737
+ "lang.typescript": "ts",
738
+ "lang.javascript": "js",
739
+ "lang.python": "py",
740
+ "lang.rust": "rs",
741
+ "lang.go": "go",
742
+ "lang.java": "java",
743
+ "lang.c": "c",
744
+ "lang.cpp": "cpp",
745
+ "lang.csharp": "cs",
746
+ "lang.ruby": "rb",
747
+ "lang.php": "php",
748
+ "lang.swift": "swift",
749
+ "lang.kotlin": "kt",
750
+ "lang.shell": "sh",
751
+ "lang.html": "html",
752
+ "lang.css": "css",
753
+ "lang.json": "json",
754
+ "lang.yaml": "yaml",
755
+ "lang.markdown": "md",
756
+ "lang.sql": "sql",
757
+ "lang.docker": "docker",
758
+ "lang.lua": "lua",
759
+ "lang.text": "txt",
760
+ "lang.env": "env",
761
+ "lang.toml": "toml",
762
+ "lang.xml": "xml",
763
+ "lang.ini": "ini",
764
+ "lang.conf": "conf",
765
+ "lang.log": "log",
766
+ "lang.csv": "csv",
767
+ "lang.tsv": "tsv",
768
+ "lang.image": "img",
769
+ "lang.pdf": "pdf",
770
+ "lang.archive": "zip",
771
+ "lang.binary": "bin",
772
+ // Settings tab icons
773
+ "tab.appearance": "[A]",
774
+ "tab.model": "[M]",
775
+ "tab.interaction": "[I]",
776
+ "tab.context": "[X]",
777
+ "tab.editing": "[E]",
778
+ "tab.tools": "[T]",
779
+ "tab.tasks": "[K]",
780
+ "tab.providers": "[P]",
781
+ };
782
+
783
+ const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap> = {
784
+ unicode: UNICODE_SYMBOLS,
785
+ nerd: NERD_SYMBOLS,
786
+ ascii: ASCII_SYMBOLS,
787
+ };
788
+
789
+ export type SpinnerType = "status" | "activity";
790
+
791
+ const SPINNER_FRAMES: Record<SymbolPreset, Record<SpinnerType, string[]>> = {
792
+ unicode: {
793
+ status: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
794
+ activity: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
795
+ },
796
+ nerd: {
797
+ status: ["󱑖", "󱑋", "󱑌", "󱑍", "󱑎", "󱑏", "󱑐", "󱑑", "󱑒", "󱑓", "󱑔", "󱑕"],
798
+ activity: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
799
+ },
800
+ ascii: {
801
+ status: ["|", "/", "-", "\\"],
802
+ activity: ["-", "\\", "|", "/"],
803
+ },
804
+ };
805
+
806
+ // ============================================================================
807
+ // Types & Schema
808
+ // ============================================================================
809
+
810
+ const ColorValueSchema = Type.Union([
811
+ Type.String(), // hex "#ff0000", var ref "primary", or empty ""
812
+ Type.Integer({ minimum: 0, maximum: 255 }), // 256-color index
813
+ ]);
814
+
815
+ type ColorValue = Static<typeof ColorValueSchema>;
816
+
817
+ // Use Type.Union here (not StringEnum) because TypeCompiler doesn't support Type.Unsafe
818
+ const SymbolPresetSchema = Type.Union([Type.Literal("unicode"), Type.Literal("nerd"), Type.Literal("ascii")]);
819
+
820
+ const SymbolsSchema = Type.Optional(
821
+ Type.Object({
822
+ preset: Type.Optional(SymbolPresetSchema),
823
+ overrides: Type.Optional(Type.Record(Type.String(), Type.String())),
824
+ }),
825
+ );
826
+
827
+ const ThemeJsonSchema = Type.Object({
828
+ $schema: Type.Optional(Type.String()),
829
+ name: Type.String(),
830
+ vars: Type.Optional(Type.Record(Type.String(), ColorValueSchema)),
831
+ colors: Type.Object(
832
+ {
833
+ // Core UI (12 colors)
834
+ accent: ColorValueSchema,
835
+ chromeAccent: ColorValueSchema,
836
+ spinnerAccent: ColorValueSchema,
837
+ contentAccent: ColorValueSchema,
838
+ border: ColorValueSchema,
839
+ borderAccent: ColorValueSchema,
840
+ borderMuted: ColorValueSchema,
841
+ success: ColorValueSchema,
842
+ error: ColorValueSchema,
843
+ warning: ColorValueSchema,
844
+ muted: ColorValueSchema,
845
+ dim: ColorValueSchema,
846
+ gutterSuccess: ColorValueSchema,
847
+ gutterError: Type.Optional(ColorValueSchema),
848
+ gutterWarning: ColorValueSchema,
849
+ text: ColorValueSchema,
850
+ thinkingText: ColorValueSchema,
851
+ // Backgrounds & Content Text (11 colors)
852
+ selectedBg: ColorValueSchema,
853
+ userMessageBg: ColorValueSchema,
854
+ userMessageText: ColorValueSchema,
855
+ customMessageBg: ColorValueSchema,
856
+ customMessageText: ColorValueSchema,
857
+ customMessageLabel: ColorValueSchema,
858
+ toolPendingBg: ColorValueSchema,
859
+ toolSuccessBg: ColorValueSchema,
860
+ toolErrorBg: ColorValueSchema,
861
+ toolTitle: ColorValueSchema,
862
+ toolOutput: ColorValueSchema,
863
+ // Markdown (10 colors)
864
+ mdHeading: ColorValueSchema,
865
+ mdLink: ColorValueSchema,
866
+ mdLinkUrl: ColorValueSchema,
867
+ mdCode: ColorValueSchema,
868
+ mdCodeBlock: ColorValueSchema,
869
+ mdCodeBlockBorder: ColorValueSchema,
870
+ mdQuote: ColorValueSchema,
871
+ mdQuoteBorder: ColorValueSchema,
872
+ mdHr: ColorValueSchema,
873
+ mdListBullet: ColorValueSchema,
874
+ // Tool Diffs (3 colors)
875
+ toolDiffAdded: ColorValueSchema,
876
+ toolDiffRemoved: ColorValueSchema,
877
+ toolDiffContext: ColorValueSchema,
878
+ // Syntax Highlighting (9 colors)
879
+ syntaxComment: ColorValueSchema,
880
+ syntaxKeyword: ColorValueSchema,
881
+ syntaxFunction: ColorValueSchema,
882
+ syntaxVariable: ColorValueSchema,
883
+ syntaxString: ColorValueSchema,
884
+ syntaxNumber: ColorValueSchema,
885
+ syntaxType: ColorValueSchema,
886
+ syntaxOperator: ColorValueSchema,
887
+ syntaxPunctuation: ColorValueSchema,
888
+ syntaxControl: ColorValueSchema,
889
+ // Thinking Level Borders (6 colors)
890
+ thinkingOff: ColorValueSchema,
891
+ thinkingMinimal: ColorValueSchema,
892
+ thinkingLow: ColorValueSchema,
893
+ thinkingMedium: ColorValueSchema,
894
+ thinkingHigh: ColorValueSchema,
895
+ thinkingXhigh: ColorValueSchema,
896
+ // Bash Mode (1 color)
897
+ bashMode: ColorValueSchema,
898
+ // Python Mode (1 color)
899
+ pythonMode: ColorValueSchema,
900
+ // Footer Status Line
901
+ statusLineBg: ColorValueSchema,
902
+ statusLineSep: ColorValueSchema,
903
+ statusLineModel: ColorValueSchema,
904
+ statusLinePath: ColorValueSchema,
905
+ statusLineGitClean: ColorValueSchema,
906
+ statusLineGitDirty: ColorValueSchema,
907
+ statusLineContext: ColorValueSchema,
908
+ statusLineSpend: ColorValueSchema,
909
+ statusLineStaged: ColorValueSchema,
910
+ statusLineDirty: ColorValueSchema,
911
+ statusLineUntracked: ColorValueSchema,
912
+ statusLineOutput: ColorValueSchema,
913
+ statusLineCost: ColorValueSchema,
914
+ statusLineSubagents: ColorValueSchema,
915
+ // Powerline segment backgrounds
916
+ statusLineOsIconBg: ColorValueSchema,
917
+ statusLineOsIconFg: ColorValueSchema,
918
+ statusLinePathBg: ColorValueSchema,
919
+ statusLinePathFg: ColorValueSchema,
920
+ statusLineGitCleanBg: ColorValueSchema,
921
+ statusLineGitDirtyBg: ColorValueSchema,
922
+ statusLineGitStagedBg: ColorValueSchema,
923
+ statusLineGitUntrackedBg: ColorValueSchema,
924
+ statusLineGitConflictBg: ColorValueSchema,
925
+ statusLineGitCleanFg: ColorValueSchema,
926
+ statusLineGitDirtyFg: ColorValueSchema,
927
+ statusLineGitStagedFg: ColorValueSchema,
928
+ statusLineGitUntrackedFg: ColorValueSchema,
929
+ statusLineGitConflictFg: ColorValueSchema,
930
+ statusLinePlanModeBg: ColorValueSchema,
931
+ statusLinePlanModeFg: ColorValueSchema,
932
+ statusLineContextXcshBg: ColorValueSchema,
933
+ statusLineContextXcshFg: ColorValueSchema,
934
+ },
935
+ { additionalProperties: false },
936
+ ),
937
+ export: Type.Optional(
938
+ Type.Object({
939
+ pageBg: Type.Optional(ColorValueSchema),
940
+ cardBg: Type.Optional(ColorValueSchema),
941
+ infoBg: Type.Optional(ColorValueSchema),
942
+ }),
943
+ ),
944
+ symbols: SymbolsSchema,
945
+ });
946
+
947
+ type ThemeJson = Static<typeof ThemeJsonSchema>;
948
+
949
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TypeBox CJS/ESM type mismatch
950
+ const validateThemeJson = TypeCompiler.Compile(ThemeJsonSchema as any);
951
+
952
+ export type ThemeColor =
953
+ | "accent"
954
+ | "chromeAccent"
955
+ | "spinnerAccent"
956
+ | "contentAccent"
957
+ | "border"
958
+ | "borderAccent"
959
+ | "borderMuted"
960
+ | "success"
961
+ | "error"
962
+ | "warning"
963
+ | "muted"
964
+ | "dim"
965
+ | "gutterSuccess"
966
+ | "gutterError"
967
+ | "gutterWarning"
968
+ | "text"
969
+ | "thinkingText"
970
+ | "userMessageText"
971
+ | "customMessageText"
972
+ | "customMessageLabel"
973
+ | "toolTitle"
974
+ | "toolOutput"
975
+ | "mdHeading"
976
+ | "mdLink"
977
+ | "mdLinkUrl"
978
+ | "mdCode"
979
+ | "mdCodeBlock"
980
+ | "mdCodeBlockBorder"
981
+ | "mdQuote"
982
+ | "mdQuoteBorder"
983
+ | "mdHr"
984
+ | "mdListBullet"
985
+ | "toolDiffAdded"
986
+ | "toolDiffRemoved"
987
+ | "toolDiffContext"
988
+ | "syntaxComment"
989
+ | "syntaxKeyword"
990
+ | "syntaxFunction"
991
+ | "syntaxVariable"
992
+ | "syntaxString"
993
+ | "syntaxNumber"
994
+ | "syntaxType"
995
+ | "syntaxOperator"
996
+ | "syntaxPunctuation"
997
+ | "syntaxControl"
998
+ | "thinkingOff"
999
+ | "thinkingMinimal"
1000
+ | "thinkingLow"
1001
+ | "thinkingMedium"
1002
+ | "thinkingHigh"
1003
+ | "thinkingXhigh"
1004
+ | "bashMode"
1005
+ | "pythonMode"
1006
+ | "statusLineSep"
1007
+ | "statusLineModel"
1008
+ | "statusLinePath"
1009
+ | "statusLineGitClean"
1010
+ | "statusLineGitDirty"
1011
+ | "statusLineContext"
1012
+ | "statusLineSpend"
1013
+ | "statusLineStaged"
1014
+ | "statusLineDirty"
1015
+ | "statusLineUntracked"
1016
+ | "statusLineOutput"
1017
+ | "statusLineCost"
1018
+ | "statusLineSubagents"
1019
+ | "statusLineOsIconBg"
1020
+ | "statusLineOsIconFg"
1021
+ | "statusLinePathBg"
1022
+ | "statusLinePathFg"
1023
+ | "statusLineGitCleanBg"
1024
+ | "statusLineGitDirtyBg"
1025
+ | "statusLineGitStagedBg"
1026
+ | "statusLineGitUntrackedBg"
1027
+ | "statusLineGitConflictBg"
1028
+ | "statusLineGitCleanFg"
1029
+ | "statusLineGitDirtyFg"
1030
+ | "statusLineGitStagedFg"
1031
+ | "statusLineGitUntrackedFg"
1032
+ | "statusLineGitConflictFg"
1033
+ | "statusLinePlanModeBg"
1034
+ | "statusLinePlanModeFg"
1035
+ | "statusLineContextXcshBg"
1036
+ | "statusLineContextXcshFg";
1037
+
1038
+ /** Set of all valid ThemeColor string values for runtime validation */
1039
+ const THEME_COLOR_RECORD = {
1040
+ accent: true,
1041
+ chromeAccent: true,
1042
+ spinnerAccent: true,
1043
+ contentAccent: true,
1044
+ border: true,
1045
+ borderAccent: true,
1046
+ borderMuted: true,
1047
+ success: true,
1048
+ error: true,
1049
+ warning: true,
1050
+ muted: true,
1051
+ dim: true,
1052
+ gutterSuccess: true,
1053
+ gutterError: true,
1054
+ gutterWarning: true,
1055
+ text: true,
1056
+ thinkingText: true,
1057
+ userMessageText: true,
1058
+ customMessageText: true,
1059
+ customMessageLabel: true,
1060
+ toolTitle: true,
1061
+ toolOutput: true,
1062
+ mdHeading: true,
1063
+ mdLink: true,
1064
+ mdLinkUrl: true,
1065
+ mdCode: true,
1066
+ mdCodeBlock: true,
1067
+ mdCodeBlockBorder: true,
1068
+ mdQuote: true,
1069
+ mdQuoteBorder: true,
1070
+ mdHr: true,
1071
+ mdListBullet: true,
1072
+ toolDiffAdded: true,
1073
+ toolDiffRemoved: true,
1074
+ toolDiffContext: true,
1075
+ syntaxComment: true,
1076
+ syntaxKeyword: true,
1077
+ syntaxFunction: true,
1078
+ syntaxVariable: true,
1079
+ syntaxString: true,
1080
+ syntaxNumber: true,
1081
+ syntaxType: true,
1082
+ syntaxOperator: true,
1083
+ syntaxPunctuation: true,
1084
+ syntaxControl: true,
1085
+ thinkingOff: true,
1086
+ thinkingMinimal: true,
1087
+ thinkingLow: true,
1088
+ thinkingMedium: true,
1089
+ thinkingHigh: true,
1090
+ thinkingXhigh: true,
1091
+ bashMode: true,
1092
+ pythonMode: true,
1093
+ statusLineSep: true,
1094
+ statusLineModel: true,
1095
+ statusLinePath: true,
1096
+ statusLineGitClean: true,
1097
+ statusLineGitDirty: true,
1098
+ statusLineContext: true,
1099
+ statusLineSpend: true,
1100
+ statusLineStaged: true,
1101
+ statusLineDirty: true,
1102
+ statusLineUntracked: true,
1103
+ statusLineOutput: true,
1104
+ statusLineCost: true,
1105
+ statusLineSubagents: true,
1106
+ statusLineOsIconBg: true,
1107
+ statusLineOsIconFg: true,
1108
+ statusLinePathBg: true,
1109
+ statusLinePathFg: true,
1110
+ statusLineGitCleanBg: true,
1111
+ statusLineGitDirtyBg: true,
1112
+ statusLineGitStagedBg: true,
1113
+ statusLineGitUntrackedBg: true,
1114
+ statusLineGitConflictBg: true,
1115
+ statusLineGitCleanFg: true,
1116
+ statusLineGitDirtyFg: true,
1117
+ statusLineGitStagedFg: true,
1118
+ statusLineGitUntrackedFg: true,
1119
+ statusLineGitConflictFg: true,
1120
+ statusLinePlanModeBg: true,
1121
+ statusLinePlanModeFg: true,
1122
+ statusLineContextXcshBg: true,
1123
+ statusLineContextXcshFg: true,
1124
+ } satisfies Record<ThemeColor, true>;
1125
+
1126
+ const VALID_THEME_COLORS: ReadonlySet<string> = new Set(Object.keys(THEME_COLOR_RECORD));
1127
+
1128
+ /** Check if a string is a valid ThemeColor value */
1129
+ export function isValidThemeColor(color: string): color is ThemeColor {
1130
+ return VALID_THEME_COLORS.has(color);
1131
+ }
1132
+
1133
+ export type ThemeBg =
1134
+ | "selectedBg"
1135
+ | "userMessageBg"
1136
+ | "customMessageBg"
1137
+ | "toolPendingBg"
1138
+ | "toolSuccessBg"
1139
+ | "toolErrorBg"
1140
+ | "statusLineBg";
1141
+
1142
+ type ColorMode = "truecolor" | "256color";
1143
+
1144
+ // ============================================================================
1145
+ // Color Utilities
1146
+ // ============================================================================
1147
+
1148
+ function detectColorMode(): ColorMode {
1149
+ const colorterm = Bun.env.COLORTERM;
1150
+ if (colorterm === "truecolor" || colorterm === "24bit") {
1151
+ return "truecolor";
1152
+ }
1153
+ // Windows Terminal supports truecolor
1154
+ if (Bun.env.WT_SESSION) {
1155
+ return "truecolor";
1156
+ }
1157
+ const term = Bun.env.TERM || "";
1158
+ // Only fall back to 256color for truly limited terminals
1159
+ if (term === "dumb" || term === "" || term === "linux") {
1160
+ return "256color";
1161
+ }
1162
+ // Assume truecolor for everything else - virtually all modern terminals support it
1163
+ return "truecolor";
1164
+ }
1165
+
1166
+ function colorToAnsi(color: string, mode: ColorMode): string {
1167
+ const format = mode === "truecolor" ? "ansi-16m" : "ansi-256";
1168
+ const ansi = Bun.color(color, format);
1169
+ if (ansi === null) {
1170
+ throw new Error(`Invalid color value: ${color}`);
1171
+ }
1172
+ return ansi;
1173
+ }
1174
+
1175
+ function fgAnsi(color: string | number, mode: ColorMode): string {
1176
+ if (color === "") return "\x1b[39m";
1177
+ if (typeof color === "number") return `\x1b[38;5;${color}m`;
1178
+ if (typeof color === "string") {
1179
+ return colorToAnsi(color, mode);
1180
+ }
1181
+ throw new Error(`Invalid color value: ${color}`);
1182
+ }
1183
+
1184
+ function bgAnsi(color: string | number, mode: ColorMode): string {
1185
+ if (color === "") return "\x1b[49m";
1186
+ if (typeof color === "number") return `\x1b[48;5;${color}m`;
1187
+ const ansi = colorToAnsi(color, mode);
1188
+ return ansi.replace("\x1b[38;", "\x1b[48;");
1189
+ }
1190
+
1191
+ function resolveVarRefs(
1192
+ value: ColorValue,
1193
+ vars: Record<string, ColorValue>,
1194
+ visited = new Set<string>(),
1195
+ ): string | number {
1196
+ if (typeof value === "number" || value === "" || value.startsWith("#")) {
1197
+ return value;
1198
+ }
1199
+ if (visited.has(value)) {
1200
+ throw new Error(`Circular variable reference detected: ${value}`);
1201
+ }
1202
+ if (!(value in vars)) {
1203
+ throw new Error(`Variable reference not found: ${value}`);
1204
+ }
1205
+ visited.add(value);
1206
+ return resolveVarRefs(vars[value], vars, visited);
1207
+ }
1208
+
1209
+ function resolveThemeColors<T extends Record<string, ColorValue>>(
1210
+ colors: T,
1211
+ vars: Record<string, ColorValue> = {},
1212
+ ): Record<keyof T, string | number> {
1213
+ const resolved: Record<string, string | number> = {};
1214
+ for (const [key, value] of Object.entries(colors)) {
1215
+ resolved[key] = resolveVarRefs(value, vars);
1216
+ }
1217
+ return resolved as Record<keyof T, string | number>;
1218
+ }
1219
+
1220
+ // ============================================================================
1221
+ // Theme Class
1222
+ // ============================================================================
1223
+
1224
+ const langMap: Record<string, SymbolKey> = {
1225
+ typescript: "lang.typescript",
1226
+ ts: "lang.typescript",
1227
+ tsx: "lang.typescript",
1228
+ javascript: "lang.javascript",
1229
+ js: "lang.javascript",
1230
+ jsx: "lang.javascript",
1231
+ mjs: "lang.javascript",
1232
+ cjs: "lang.javascript",
1233
+ python: "lang.python",
1234
+ py: "lang.python",
1235
+ rust: "lang.rust",
1236
+ rs: "lang.rust",
1237
+ go: "lang.go",
1238
+ java: "lang.java",
1239
+ c: "lang.c",
1240
+ cpp: "lang.cpp",
1241
+ "c++": "lang.cpp",
1242
+ cc: "lang.cpp",
1243
+ cxx: "lang.cpp",
1244
+ csharp: "lang.csharp",
1245
+ cs: "lang.csharp",
1246
+ ruby: "lang.ruby",
1247
+ rb: "lang.ruby",
1248
+ php: "lang.php",
1249
+ swift: "lang.swift",
1250
+ kotlin: "lang.kotlin",
1251
+ kt: "lang.kotlin",
1252
+ bash: "lang.shell",
1253
+ sh: "lang.shell",
1254
+ zsh: "lang.shell",
1255
+ fish: "lang.shell",
1256
+ powershell: "lang.shell",
1257
+ just: "lang.shell",
1258
+ shell: "lang.shell",
1259
+ html: "lang.html",
1260
+ htm: "lang.html",
1261
+ astro: "lang.html",
1262
+ vue: "lang.html",
1263
+ svelte: "lang.html",
1264
+ css: "lang.css",
1265
+ scss: "lang.css",
1266
+ sass: "lang.css",
1267
+ less: "lang.css",
1268
+ json: "lang.json",
1269
+ yaml: "lang.yaml",
1270
+ yml: "lang.yaml",
1271
+ markdown: "lang.markdown",
1272
+ md: "lang.markdown",
1273
+ sql: "lang.sql",
1274
+ dockerfile: "lang.docker",
1275
+ docker: "lang.docker",
1276
+ lua: "lang.lua",
1277
+ text: "lang.text",
1278
+ txt: "lang.text",
1279
+ plain: "lang.text",
1280
+ log: "lang.log",
1281
+ env: "lang.env",
1282
+ dotenv: "lang.env",
1283
+ toml: "lang.toml",
1284
+ xml: "lang.xml",
1285
+ ini: "lang.ini",
1286
+ conf: "lang.conf",
1287
+ cfg: "lang.conf",
1288
+ config: "lang.conf",
1289
+ properties: "lang.conf",
1290
+ csv: "lang.csv",
1291
+ tsv: "lang.tsv",
1292
+ image: "lang.image",
1293
+ img: "lang.image",
1294
+ png: "lang.image",
1295
+ jpg: "lang.image",
1296
+ jpeg: "lang.image",
1297
+ gif: "lang.image",
1298
+ webp: "lang.image",
1299
+ svg: "lang.image",
1300
+ ico: "lang.image",
1301
+ bmp: "lang.image",
1302
+ tiff: "lang.image",
1303
+ pdf: "lang.pdf",
1304
+ zip: "lang.archive",
1305
+ tar: "lang.archive",
1306
+ gz: "lang.archive",
1307
+ tgz: "lang.archive",
1308
+ bz2: "lang.archive",
1309
+ xz: "lang.archive",
1310
+ "7z": "lang.archive",
1311
+ exe: "lang.binary",
1312
+ dll: "lang.binary",
1313
+ so: "lang.binary",
1314
+ dylib: "lang.binary",
1315
+ wasm: "lang.binary",
1316
+ bin: "lang.binary",
1317
+ };
1318
+
1319
+ export class Theme {
1320
+ #fgColors: Record<ThemeColor, string>;
1321
+ #fgHex: Partial<Record<ThemeColor, string>>;
1322
+ #bgColors: Record<ThemeBg, string>;
1323
+ #symbols: SymbolMap;
1324
+
1325
+ constructor(
1326
+ fgColors: Record<ThemeColor, string | number>,
1327
+ bgColors: Record<ThemeBg, string | number>,
1328
+ private readonly mode: ColorMode,
1329
+ private readonly symbolPreset: SymbolPreset,
1330
+ symbolOverrides: Partial<Record<SymbolKey, string>>,
1331
+ ) {
1332
+ this.#fgColors = {} as Record<ThemeColor, string>;
1333
+ this.#fgHex = {};
1334
+ for (const [key, value] of Object.entries(fgColors) as [ThemeColor, string | number][]) {
1335
+ this.#fgColors[key] = fgAnsi(value, mode);
1336
+ // Retain the raw hex (when the role resolves to one) for consumers that need
1337
+ // a color value rather than an ANSI escape — e.g. the mermaid AsciiTheme.
1338
+ if (typeof value === "string" && value.startsWith("#")) this.#fgHex[key] = value;
1339
+ }
1340
+ // gutterError remains optional — neither shipped theme carries it
1341
+ this.#fgColors.gutterError ??= this.#fgColors.error;
1342
+ this.#bgColors = {} as Record<ThemeBg, string>;
1343
+ for (const [key, value] of Object.entries(bgColors) as [ThemeBg, string | number][]) {
1344
+ this.#bgColors[key] = bgAnsi(value, mode);
1345
+ }
1346
+ // Build symbol map from preset + overrides
1347
+ const baseSymbols = SYMBOL_PRESETS[symbolPreset];
1348
+ this.#symbols = { ...baseSymbols };
1349
+ for (const [key, value] of Object.entries(symbolOverrides)) {
1350
+ if (key in this.#symbols) {
1351
+ this.#symbols[key as SymbolKey] = value;
1352
+ } else {
1353
+ logger.debug("Invalid symbol key in override", { key, availableKeys: Object.keys(this.#symbols) });
1354
+ }
1355
+ }
1356
+ }
1357
+
1358
+ fg(color: ThemeColor, text: string): string {
1359
+ const ansi = this.#fgColors[color];
1360
+ if (!ansi) throw new Error(`Unknown theme color: ${color}`);
1361
+ return `${ansi}${text}\x1b[39m`; // Reset only foreground color
1362
+ }
1363
+
1364
+ bg(color: ThemeBg, text: string): string {
1365
+ const ansi = this.#bgColors[color];
1366
+ if (!ansi) throw new Error(`Unknown theme background color: ${color}`);
1367
+ return `${ansi}${text}\x1b[49m`; // Reset only background color
1368
+ }
1369
+
1370
+ bold(text: string): string {
1371
+ return chalk.bold(text);
1372
+ }
1373
+
1374
+ italic(text: string): string {
1375
+ return chalk.italic(text);
1376
+ }
1377
+
1378
+ underline(text: string): string {
1379
+ return chalk.underline(text);
1380
+ }
1381
+
1382
+ strikethrough(text: string): string {
1383
+ return chalk.strikethrough(text);
1384
+ }
1385
+
1386
+ inverse(text: string): string {
1387
+ return chalk.inverse(text);
1388
+ }
1389
+
1390
+ getFgAnsi(color: ThemeColor): string {
1391
+ const ansi = this.#fgColors[color];
1392
+ if (!ansi) throw new Error(`Unknown theme color: ${color}`);
1393
+ return ansi;
1394
+ }
1395
+
1396
+ /**
1397
+ * Raw hex (e.g. "#ca260a") for a color role, for consumers that need a color
1398
+ * value rather than an ANSI escape (e.g. the mermaid AsciiTheme). Roles that
1399
+ * resolve to "" (default fg) or an ansi256 index yield `fallback`.
1400
+ */
1401
+ getFgHex(color: ThemeColor, fallback = "#cccccc"): string {
1402
+ return this.#fgHex[color] ?? fallback;
1403
+ }
1404
+
1405
+ /** Convert a ThemeColor's fg ANSI code to a bg ANSI code (swap \x1b[38; → \x1b[48;). */
1406
+ fgColorAsBg(color: ThemeColor): string {
1407
+ return this.getFgAnsi(color).replace("\x1b[38;", "\x1b[48;");
1408
+ }
1409
+
1410
+ getBgAnsi(color: ThemeBg): string {
1411
+ const ansi = this.#bgColors[color];
1412
+ if (!ansi) throw new Error(`Unknown theme background color: ${color}`);
1413
+ return ansi;
1414
+ }
1415
+
1416
+ getColorMode(): ColorMode {
1417
+ return this.mode;
1418
+ }
1419
+
1420
+ getThinkingBorderColor(level: ThinkingLevel | Effort): (str: string) => string {
1421
+ // Map thinking levels to dedicated theme colors
1422
+ switch (level) {
1423
+ case "off":
1424
+ return (str: string) => this.fg("thinkingOff", str);
1425
+ case "minimal":
1426
+ return (str: string) => this.fg("thinkingMinimal", str);
1427
+ case "low":
1428
+ return (str: string) => this.fg("thinkingLow", str);
1429
+ case "medium":
1430
+ return (str: string) => this.fg("thinkingMedium", str);
1431
+ case "high":
1432
+ return (str: string) => this.fg("thinkingHigh", str);
1433
+ case "xhigh":
1434
+ return (str: string) => this.fg("thinkingXhigh", str);
1435
+ default:
1436
+ return (str: string) => this.fg("thinkingOff", str);
1437
+ }
1438
+ }
1439
+
1440
+ getBashModeBorderColor(): (str: string) => string {
1441
+ return (str: string) => this.fg("bashMode", str);
1442
+ }
1443
+
1444
+ getPythonModeBorderColor(): (str: string) => string {
1445
+ return (str: string) => this.fg("pythonMode", str);
1446
+ }
1447
+
1448
+ // ============================================================================
1449
+ // Symbol Methods
1450
+ // ============================================================================
1451
+
1452
+ /**
1453
+ * Get a symbol by key.
1454
+ */
1455
+ symbol(key: SymbolKey): string {
1456
+ return this.#symbols[key];
1457
+ }
1458
+
1459
+ /**
1460
+ * Get a symbol styled with a color.
1461
+ */
1462
+ styledSymbol(key: SymbolKey, color: ThemeColor): string {
1463
+ return this.fg(color, this.#symbols[key]);
1464
+ }
1465
+
1466
+ /**
1467
+ * Get the current symbol preset.
1468
+ */
1469
+ getSymbolPreset(): SymbolPreset {
1470
+ return this.symbolPreset;
1471
+ }
1472
+
1473
+ // ============================================================================
1474
+ // Symbol Category Accessors
1475
+ // ============================================================================
1476
+
1477
+ get status() {
1478
+ return {
1479
+ success: this.#symbols["status.success"],
1480
+ error: this.#symbols["status.error"],
1481
+ warning: this.#symbols["status.warning"],
1482
+ info: this.#symbols["status.info"],
1483
+ pending: this.#symbols["status.pending"],
1484
+ disabled: this.#symbols["status.disabled"],
1485
+ enabled: this.#symbols["status.enabled"],
1486
+ running: this.#symbols["status.running"],
1487
+ shadowed: this.#symbols["status.shadowed"],
1488
+ aborted: this.#symbols["status.aborted"],
1489
+ };
1490
+ }
1491
+
1492
+ get nav() {
1493
+ return {
1494
+ cursor: this.#symbols["nav.cursor"],
1495
+ selected: this.#symbols["nav.selected"],
1496
+ expand: this.#symbols["nav.expand"],
1497
+ collapse: this.#symbols["nav.collapse"],
1498
+ back: this.#symbols["nav.back"],
1499
+ };
1500
+ }
1501
+
1502
+ get tree() {
1503
+ return {
1504
+ branch: this.#symbols["tree.branch"],
1505
+ last: this.#symbols["tree.last"],
1506
+ vertical: this.#symbols["tree.vertical"],
1507
+ horizontal: this.#symbols["tree.horizontal"],
1508
+ hook: this.#symbols["tree.hook"],
1509
+ };
1510
+ }
1511
+
1512
+ get boxRound() {
1513
+ return {
1514
+ topLeft: this.#symbols["boxRound.topLeft"],
1515
+ topRight: this.#symbols["boxRound.topRight"],
1516
+ bottomLeft: this.#symbols["boxRound.bottomLeft"],
1517
+ bottomRight: this.#symbols["boxRound.bottomRight"],
1518
+ horizontal: this.#symbols["boxRound.horizontal"],
1519
+ vertical: this.#symbols["boxRound.vertical"],
1520
+ };
1521
+ }
1522
+
1523
+ get boxSharp() {
1524
+ return {
1525
+ topLeft: this.#symbols["boxSharp.topLeft"],
1526
+ topRight: this.#symbols["boxSharp.topRight"],
1527
+ bottomLeft: this.#symbols["boxSharp.bottomLeft"],
1528
+ bottomRight: this.#symbols["boxSharp.bottomRight"],
1529
+ horizontal: this.#symbols["boxSharp.horizontal"],
1530
+ vertical: this.#symbols["boxSharp.vertical"],
1531
+ cross: this.#symbols["boxSharp.cross"],
1532
+ teeDown: this.#symbols["boxSharp.teeDown"],
1533
+ teeUp: this.#symbols["boxSharp.teeUp"],
1534
+ teeRight: this.#symbols["boxSharp.teeRight"],
1535
+ teeLeft: this.#symbols["boxSharp.teeLeft"],
1536
+ };
1537
+ }
1538
+
1539
+ get sep() {
1540
+ return {
1541
+ powerline: this.#symbols["sep.powerline"],
1542
+ powerlineThin: this.#symbols["sep.powerlineThin"],
1543
+ powerlineLeft: this.#symbols["sep.powerlineLeft"],
1544
+ powerlineRight: this.#symbols["sep.powerlineRight"],
1545
+ powerlineThinLeft: this.#symbols["sep.powerlineThinLeft"],
1546
+ powerlineThinRight: this.#symbols["sep.powerlineThinRight"],
1547
+ block: this.#symbols["sep.block"],
1548
+ space: this.#symbols["sep.space"],
1549
+ asciiLeft: this.#symbols["sep.asciiLeft"],
1550
+ asciiRight: this.#symbols["sep.asciiRight"],
1551
+ dot: this.#symbols["sep.dot"],
1552
+ slash: this.#symbols["sep.slash"],
1553
+ pipe: this.#symbols["sep.pipe"],
1554
+ };
1555
+ }
1556
+
1557
+ get icon() {
1558
+ return {
1559
+ model: this.#symbols["icon.model"],
1560
+ plan: this.#symbols["icon.plan"],
1561
+ folder: this.#symbols["icon.folder"],
1562
+ file: this.#symbols["icon.file"],
1563
+ git: this.#symbols["icon.git"],
1564
+ branch: this.#symbols["icon.branch"],
1565
+ pr: this.#symbols["icon.pr"],
1566
+ tokens: this.#symbols["icon.tokens"],
1567
+ context: this.#symbols["icon.context"],
1568
+ cost: this.#symbols["icon.cost"],
1569
+ time: this.#symbols["icon.time"],
1570
+ pi: this.#symbols["icon.pi"],
1571
+ agents: this.#symbols["icon.agents"],
1572
+ cache: this.#symbols["icon.cache"],
1573
+ input: this.#symbols["icon.input"],
1574
+ output: this.#symbols["icon.output"],
1575
+ host: this.#symbols["icon.host"],
1576
+ session: this.#symbols["icon.session"],
1577
+ package: this.#symbols["icon.package"],
1578
+ warning: this.#symbols["icon.warning"],
1579
+ rewind: this.#symbols["icon.rewind"],
1580
+ auto: this.#symbols["icon.auto"],
1581
+ fast: this.#symbols["icon.fast"],
1582
+ extensionSkill: this.#symbols["icon.extensionSkill"],
1583
+ extensionTool: this.#symbols["icon.extensionTool"],
1584
+ extensionSlashCommand: this.#symbols["icon.extensionSlashCommand"],
1585
+ extensionMcp: this.#symbols["icon.extensionMcp"],
1586
+ extensionRule: this.#symbols["icon.extensionRule"],
1587
+ extensionHook: this.#symbols["icon.extensionHook"],
1588
+ extensionPrompt: this.#symbols["icon.extensionPrompt"],
1589
+ extensionContextFile: this.#symbols["icon.extensionContextFile"],
1590
+ extensionInstruction: this.#symbols["icon.extensionInstruction"],
1591
+ mic: this.#symbols["icon.mic"],
1592
+ };
1593
+ }
1594
+
1595
+ get thinking() {
1596
+ return {
1597
+ minimal: this.#symbols["thinking.minimal"],
1598
+ low: this.#symbols["thinking.low"],
1599
+ medium: this.#symbols["thinking.medium"],
1600
+ high: this.#symbols["thinking.high"],
1601
+ xhigh: this.#symbols["thinking.xhigh"],
1602
+ };
1603
+ }
1604
+
1605
+ get checkbox() {
1606
+ return {
1607
+ checked: this.#symbols["checkbox.checked"],
1608
+ unchecked: this.#symbols["checkbox.unchecked"],
1609
+ };
1610
+ }
1611
+
1612
+ get todo() {
1613
+ return {
1614
+ active: this.#symbols["todo.active"],
1615
+ pending: this.#symbols["todo.pending"],
1616
+ done: this.#symbols["todo.done"],
1617
+ abandoned: this.#symbols["todo.abandoned"],
1618
+ };
1619
+ }
1620
+
1621
+ get format() {
1622
+ return {
1623
+ bullet: this.#symbols["format.bullet"],
1624
+ dash: this.#symbols["format.dash"],
1625
+ bracketLeft: this.#symbols["format.bracketLeft"],
1626
+ bracketRight: this.#symbols["format.bracketRight"],
1627
+ };
1628
+ }
1629
+
1630
+ get md() {
1631
+ return {
1632
+ quoteBorder: this.#symbols["md.quoteBorder"],
1633
+ hrChar: this.#symbols["md.hrChar"],
1634
+ bullet: this.#symbols["md.bullet"],
1635
+ };
1636
+ }
1637
+
1638
+ /**
1639
+ * Default spinner frames (status spinner).
1640
+ */
1641
+ get spinnerFrames(): string[] {
1642
+ return this.getSpinnerFrames();
1643
+ }
1644
+
1645
+ /**
1646
+ * Get spinner frames by type.
1647
+ */
1648
+ getSpinnerFrames(type: SpinnerType = "status"): string[] {
1649
+ return SPINNER_FRAMES[this.symbolPreset][type];
1650
+ }
1651
+
1652
+ /**
1653
+ * Get language icon for a language name.
1654
+ * Maps common language names to their corresponding symbol keys.
1655
+ */
1656
+ getLangIcon(lang: string | undefined): string {
1657
+ if (!lang) return this.#symbols["lang.default"];
1658
+ const normalized = lang.toLowerCase();
1659
+ const key = langMap[normalized];
1660
+ return key ? this.#symbols[key] : this.#symbols["lang.default"];
1661
+ }
1662
+ }
1663
+
1664
+ // ============================================================================
1665
+ // Theme Loading
1666
+ // ============================================================================
1667
+
1668
+ const BUILTIN_THEMES: Record<string, ThemeJson> = {
1669
+ ...(defaultThemes as Record<string, ThemeJson>),
1670
+ };
1671
+
1672
+ function getBuiltinThemes(): Record<string, ThemeJson> {
1673
+ return BUILTIN_THEMES;
1674
+ }
1675
+
1676
+ export async function getAvailableThemes(): Promise<string[]> {
1677
+ const themes = new Set<string>(Object.keys(getBuiltinThemes()));
1678
+ const customThemesDir = getCustomThemesDir();
1679
+ try {
1680
+ const files = await fs.promises.readdir(customThemesDir);
1681
+ for (const file of files) {
1682
+ if (file.endsWith(".json")) {
1683
+ themes.add(file.slice(0, -5));
1684
+ }
1685
+ }
1686
+ } catch {
1687
+ // Directory doesn't exist or isn't readable
1688
+ }
1689
+ return Array.from(themes).sort();
1690
+ }
1691
+
1692
+ export interface ThemeInfo {
1693
+ name: string;
1694
+ path: string | undefined;
1695
+ }
1696
+
1697
+ export async function getAvailableThemesWithPaths(): Promise<ThemeInfo[]> {
1698
+ const result: ThemeInfo[] = [];
1699
+
1700
+ // Built-in themes (embedded, no file path)
1701
+ for (const name of Object.keys(getBuiltinThemes())) {
1702
+ result.push({ name, path: undefined });
1703
+ }
1704
+
1705
+ // Custom themes
1706
+ const customThemesDir = getCustomThemesDir();
1707
+ try {
1708
+ const files = await fs.promises.readdir(customThemesDir);
1709
+ for (const file of files) {
1710
+ if (file.endsWith(".json")) {
1711
+ const name = file.slice(0, -5);
1712
+ if (!result.some(themeInfo => themeInfo.name === name)) {
1713
+ result.push({ name, path: path.join(customThemesDir, file) });
1714
+ }
1715
+ }
1716
+ }
1717
+ } catch {
1718
+ // Directory doesn't exist or isn't readable
1719
+ }
1720
+
1721
+ return result.sort((a, b) => a.name.localeCompare(b.name));
1722
+ }
1723
+
1724
+ async function loadThemeJson(name: string): Promise<ThemeJson> {
1725
+ const builtinThemes = getBuiltinThemes();
1726
+ if (name in builtinThemes) {
1727
+ return builtinThemes[name];
1728
+ }
1729
+ const customThemesDir = getCustomThemesDir();
1730
+ const themePath = path.join(customThemesDir, `${name}.json`);
1731
+ let content: string;
1732
+ try {
1733
+ content = await Bun.file(themePath).text();
1734
+ } catch (err) {
1735
+ if (isEnoent(err)) throw new Error(`Theme not found: ${name}`);
1736
+ throw err;
1737
+ }
1738
+ let json: unknown;
1739
+ try {
1740
+ json = JSON.parse(content);
1741
+ } catch (error) {
1742
+ throw new Error(`Failed to parse theme ${name}: ${error}`);
1743
+ }
1744
+ if (!validateThemeJson.Check(json)) {
1745
+ const errors = Array.from(validateThemeJson.Errors(json));
1746
+ const missingColors: string[] = [];
1747
+ const otherErrors: string[] = [];
1748
+
1749
+ for (const e of errors) {
1750
+ // Check for missing required color properties
1751
+ const match = e.path.match(/^\/colors\/(\w+)$/);
1752
+ if (match && e.message.includes("Required")) {
1753
+ missingColors.push(match[1]);
1754
+ } else {
1755
+ otherErrors.push(` - ${e.path}: ${e.message}`);
1756
+ }
1757
+ }
1758
+
1759
+ let errorMessage = `Invalid theme "${name}":\n`;
1760
+ if (missingColors.length > 0) {
1761
+ errorMessage += `\nMissing required color tokens:\n`;
1762
+ errorMessage += missingColors.map(c => ` - ${c}`).join("\n");
1763
+ errorMessage += `\n\nPlease add these colors to your theme's "colors" object.`;
1764
+ errorMessage += `\nSee the built-in themes (dark.json, light.json) for reference values.`;
1765
+ }
1766
+ if (otherErrors.length > 0) {
1767
+ errorMessage += `\n\nOther errors:\n${otherErrors.join("\n")}`;
1768
+ }
1769
+
1770
+ throw new Error(errorMessage);
1771
+ }
1772
+ return json as ThemeJson;
1773
+ }
1774
+
1775
+ interface CreateThemeOptions {
1776
+ mode?: ColorMode;
1777
+ symbolPresetOverride?: SymbolPreset;
1778
+ colorBlindMode?: boolean;
1779
+ }
1780
+
1781
+ /** HSV adjustment to shift green toward blue for colorblind mode (red-green colorblindness) */
1782
+ const COLORBLIND_ADJUSTMENT = { h: 60, s: 0.71 };
1783
+
1784
+ function createTheme(themeJson: ThemeJson, options: CreateThemeOptions = {}): Theme {
1785
+ const { mode, symbolPresetOverride, colorBlindMode } = options;
1786
+ const colorMode = mode ?? detectColorMode();
1787
+ const resolvedColors = resolveThemeColors(themeJson.colors, themeJson.vars);
1788
+
1789
+ if (colorBlindMode) {
1790
+ const added = resolvedColors.toolDiffAdded;
1791
+ if (typeof added === "string" && added.startsWith("#")) {
1792
+ resolvedColors.toolDiffAdded = adjustHsv(added, COLORBLIND_ADJUSTMENT);
1793
+ }
1794
+ }
1795
+
1796
+ const fgColors: Record<ThemeColor, string | number> = {} as Record<ThemeColor, string | number>;
1797
+ const bgColors: Record<ThemeBg, string | number> = {} as Record<ThemeBg, string | number>;
1798
+ const bgColorKeys: Set<string> = new Set([
1799
+ "selectedBg",
1800
+ "userMessageBg",
1801
+ "customMessageBg",
1802
+ "toolPendingBg",
1803
+ "toolSuccessBg",
1804
+ "toolErrorBg",
1805
+ "statusLineBg",
1806
+ ]);
1807
+ for (const [key, value] of Object.entries(resolvedColors)) {
1808
+ if (bgColorKeys.has(key)) {
1809
+ bgColors[key as ThemeBg] = value;
1810
+ } else {
1811
+ fgColors[key as ThemeColor] = value;
1812
+ }
1813
+ }
1814
+ // Extract symbol configuration - settings override takes precedence over theme
1815
+ const symbolPreset: SymbolPreset = symbolPresetOverride ?? themeJson.symbols?.preset ?? "unicode";
1816
+ const symbolOverrides = themeJson.symbols?.overrides ?? {};
1817
+ return new Theme(fgColors, bgColors, colorMode, symbolPreset, symbolOverrides);
1818
+ }
1819
+
1820
+ async function loadTheme(name: string, options: CreateThemeOptions = {}): Promise<Theme> {
1821
+ const themeJson = await loadThemeJson(name);
1822
+ return createTheme(themeJson, options);
1823
+ }
1824
+
1825
+ export async function getThemeByName(name: string): Promise<Theme | undefined> {
1826
+ try {
1827
+ return await loadTheme(name);
1828
+ } catch {
1829
+ return undefined;
1830
+ }
1831
+ }
1832
+
1833
+ /** Appearance detected via OSC 11 background color query, or undefined if not yet available. */
1834
+ var terminalReportedAppearance: "dark" | "light" | undefined;
1835
+
1836
+ /** Appearance reported by the macOS fallback observer, or undefined if not yet available. */
1837
+ var macOSReportedAppearance: "dark" | "light" | undefined;
1838
+
1839
+ function shouldUseMacOSAppearanceFallback(): boolean {
1840
+ // Zellij currently breaks OSC 11 passthrough on macOS, so terminal-derived
1841
+ // appearance cannot be trusted there. Fall back to host macOS appearance
1842
+ // without letting it override valid terminal signals elsewhere.
1843
+ return process.platform === "darwin" && !!Bun.env.ZELLIJ;
1844
+ }
1845
+
1846
+ function detectTerminalBackground(): "dark" | "light" {
1847
+ // Tier 1: terminal-reported appearance from OSC 11 luminance.
1848
+ if (!shouldUseMacOSAppearanceFallback() && terminalReportedAppearance) {
1849
+ return terminalReportedAppearance;
1850
+ }
1851
+
1852
+ // Tier 2: COLORFGBG env var (static at process start, but still terminal-derived).
1853
+ const colorfgbg = Bun.env.COLORFGBG || "";
1854
+ if (colorfgbg) {
1855
+ const parts = colorfgbg.split(";");
1856
+ if (parts.length >= 2) {
1857
+ const bg = parseInt(parts[1], 10);
1858
+ if (!Number.isNaN(bg)) return bg < 8 ? "dark" : "light";
1859
+ }
1860
+ }
1861
+
1862
+ // Tier 3: host macOS appearance for known-broken terminal paths only.
1863
+ if (shouldUseMacOSAppearanceFallback()) {
1864
+ const macAppearance = macOSReportedAppearance ?? detectMacOSAppearance();
1865
+ if (macAppearance) return macAppearance;
1866
+ }
1867
+
1868
+ return "dark";
1869
+ }
1870
+
1871
+ /**
1872
+ * Theme-slot override mode. `"auto"` uses terminal-appearance detection to
1873
+ * pick between the dark and light slots. `"dark"` / `"light"` skip detection
1874
+ * and always load that slot — useful when running inside tmux where OSC 11
1875
+ * queries and `COLORFGBG` don't reliably surface (see issue #228).
1876
+ */
1877
+ export type ThemeForceSlot = "auto" | "dark" | "light";
1878
+
1879
+ /**
1880
+ * Pure slot-resolution helper. Exported so it can be unit-tested without a
1881
+ * terminal. When `forceSlot` is `"dark"` or `"light"`, returns that slot's
1882
+ * theme name unconditionally. Otherwise maps `detected` bg to the matching
1883
+ * slot.
1884
+ */
1885
+ export function resolveThemeSlot(
1886
+ forceSlot: ThemeForceSlot,
1887
+ detected: "dark" | "light",
1888
+ darkTheme: string,
1889
+ lightTheme: string,
1890
+ ): string {
1891
+ if (forceSlot === "dark") return darkTheme;
1892
+ if (forceSlot === "light") return lightTheme;
1893
+ return detected === "light" ? lightTheme : darkTheme;
1894
+ }
1895
+
1896
+ function getDefaultTheme(): string {
1897
+ return resolveThemeSlot(currentForceSlot, detectTerminalBackground(), autoDarkTheme, autoLightTheme);
1898
+ }
1899
+
1900
+ // ============================================================================
1901
+ // Global Theme Instance
1902
+ // ============================================================================
1903
+
1904
+ export var theme: Theme;
1905
+ var currentThemeName: string | undefined;
1906
+
1907
+ /** Get the name of the currently active theme. */
1908
+ export function getCurrentThemeName(): string | undefined {
1909
+ return currentThemeName;
1910
+ }
1911
+ var currentSymbolPresetOverride: SymbolPreset | undefined;
1912
+ var currentColorBlindMode: boolean = false;
1913
+ var themeWatcher: fs.FSWatcher | undefined;
1914
+ var themeReloadTimer: NodeJS.Timeout | undefined;
1915
+ var sigwinchHandler: (() => void) | undefined;
1916
+ var autoDetectedTheme: boolean = false;
1917
+ var autoDarkTheme: string = "xcsh-dark";
1918
+ var autoLightTheme: string = "xcsh-light";
1919
+ var currentForceSlot: ThemeForceSlot = "auto";
1920
+ var onThemeChangeCallback: (() => void) | undefined;
1921
+ var themeLoadRequestId: number = 0;
1922
+
1923
+ function getCurrentThemeOptions(): CreateThemeOptions {
1924
+ return {
1925
+ symbolPresetOverride: currentSymbolPresetOverride,
1926
+ colorBlindMode: currentColorBlindMode,
1927
+ };
1928
+ }
1929
+
1930
+ export async function initTheme(
1931
+ enableWatcher: boolean = false,
1932
+ symbolPreset?: SymbolPreset,
1933
+ colorBlindMode?: boolean,
1934
+ darkTheme?: string,
1935
+ lightTheme?: string,
1936
+ forceSlot?: ThemeForceSlot,
1937
+ ): Promise<void> {
1938
+ autoDetectedTheme = true;
1939
+ autoDarkTheme = darkTheme ?? "xcsh-dark";
1940
+ autoLightTheme = lightTheme ?? "xcsh-light";
1941
+ currentForceSlot = forceSlot ?? "auto";
1942
+ const name = getDefaultTheme();
1943
+ currentThemeName = name;
1944
+ currentSymbolPresetOverride = symbolPreset;
1945
+ currentColorBlindMode = colorBlindMode ?? false;
1946
+ try {
1947
+ theme = await loadTheme(name, getCurrentThemeOptions());
1948
+ if (enableWatcher) {
1949
+ await startThemeWatcher();
1950
+ startSigwinchListener();
1951
+ }
1952
+ } catch (err) {
1953
+ logger.debug("Theme loading failed, falling back to xcsh-dark theme", { error: String(err) });
1954
+ currentThemeName = "xcsh-dark";
1955
+ theme = await loadTheme("xcsh-dark", getCurrentThemeOptions());
1956
+ // Don't start watcher for fallback theme
1957
+ }
1958
+ }
1959
+
1960
+ export async function setTheme(
1961
+ name: string,
1962
+ enableWatcher: boolean = false,
1963
+ ): Promise<{ success: boolean; error?: string }> {
1964
+ autoDetectedTheme = false;
1965
+ currentThemeName = name;
1966
+ const requestId = ++themeLoadRequestId;
1967
+ try {
1968
+ const loadedTheme = await loadTheme(name, getCurrentThemeOptions());
1969
+ if (requestId !== themeLoadRequestId) {
1970
+ return { success: false, error: "Theme change superseded by a newer request" };
1971
+ }
1972
+ theme = loadedTheme;
1973
+ if (enableWatcher) {
1974
+ await startThemeWatcher();
1975
+ }
1976
+ if (onThemeChangeCallback) {
1977
+ onThemeChangeCallback();
1978
+ }
1979
+ return { success: true };
1980
+ } catch (error) {
1981
+ if (requestId !== themeLoadRequestId) {
1982
+ return { success: false, error: "Theme change superseded by a newer request" };
1983
+ }
1984
+ // Theme is invalid - fall back to xcsh-dark theme
1985
+ currentThemeName = "xcsh-dark";
1986
+ theme = await loadTheme("xcsh-dark", getCurrentThemeOptions());
1987
+ // Don't start watcher for fallback theme
1988
+ return {
1989
+ success: false,
1990
+ error: error instanceof Error ? error.message : String(error),
1991
+ };
1992
+ }
1993
+ }
1994
+
1995
+ export async function previewTheme(name: string): Promise<{ success: boolean; error?: string }> {
1996
+ const requestId = ++themeLoadRequestId;
1997
+ try {
1998
+ const loadedTheme = await loadTheme(name, getCurrentThemeOptions());
1999
+ if (requestId !== themeLoadRequestId) {
2000
+ return { success: false, error: "Theme preview superseded by a newer request" };
2001
+ }
2002
+ theme = loadedTheme;
2003
+ if (onThemeChangeCallback) {
2004
+ onThemeChangeCallback();
2005
+ }
2006
+ return { success: true };
2007
+ } catch (error) {
2008
+ if (requestId !== themeLoadRequestId) {
2009
+ return { success: false, error: "Theme preview superseded by a newer request" };
2010
+ }
2011
+ return {
2012
+ success: false,
2013
+ error: error instanceof Error ? error.message : String(error),
2014
+ };
2015
+ }
2016
+ }
2017
+
2018
+ /**
2019
+ * Enable auto-detection mode, switching to the appropriate dark/light theme.
2020
+ */
2021
+ export function enableAutoTheme(): void {
2022
+ autoDetectedTheme = true;
2023
+ reevaluateAutoTheme("enableAutoTheme");
2024
+ }
2025
+
2026
+ /**
2027
+ * Update the theme mappings for auto-detection mode.
2028
+ * When a dark/light mapping changes and auto-detection is active, re-evaluate the theme.
2029
+ */
2030
+ export function setAutoThemeMapping(mode: "dark" | "light", themeName: string): void {
2031
+ if (mode === "dark") autoDarkTheme = themeName;
2032
+ else autoLightTheme = themeName;
2033
+ reevaluateAutoTheme("setAutoThemeMapping");
2034
+ }
2035
+
2036
+ /**
2037
+ * Update the forced-slot override. Callers pass `"auto"` to restore the
2038
+ * detection chain, or `"dark"` / `"light"` to skip detection entirely and
2039
+ * always load that slot. Re-evaluates the active theme immediately.
2040
+ *
2041
+ * Primary use case: tmux sessions where OSC 11 and COLORFGBG don't surface
2042
+ * a reliable bg signal (issue #228).
2043
+ */
2044
+ export function setForceSlot(slot: ThemeForceSlot): void {
2045
+ currentForceSlot = slot;
2046
+ reevaluateAutoTheme("setForceSlot");
2047
+ }
2048
+
2049
+ /**
2050
+ * Called when the terminal detects a dark/light appearance change.
2051
+ * The terminal layer queries OSC 11 (background color) and computes luminance;
2052
+ * Mode 2031 notifications trigger re-queries rather than providing the value directly.
2053
+ */
2054
+ export function onTerminalAppearanceChange(mode: "dark" | "light"): void {
2055
+ if (terminalReportedAppearance === mode) return;
2056
+ terminalReportedAppearance = mode;
2057
+ reevaluateAutoTheme("terminal appearance");
2058
+ }
2059
+
2060
+ export function setThemeInstance(themeInstance: Theme): void {
2061
+ autoDetectedTheme = false;
2062
+ theme = themeInstance;
2063
+ currentThemeName = "<in-memory>";
2064
+ stopThemeWatcher();
2065
+ if (onThemeChangeCallback) {
2066
+ onThemeChangeCallback();
2067
+ }
2068
+ }
2069
+
2070
+ /**
2071
+ * Set the symbol preset override, recreating the theme with the new preset.
2072
+ */
2073
+ export async function setSymbolPreset(preset: SymbolPreset): Promise<void> {
2074
+ currentSymbolPresetOverride = preset;
2075
+ if (currentThemeName) {
2076
+ try {
2077
+ theme = await loadTheme(currentThemeName, getCurrentThemeOptions());
2078
+ } catch {
2079
+ // Fall back to xcsh-dark theme with new preset
2080
+ theme = await loadTheme("xcsh-dark", getCurrentThemeOptions());
2081
+ }
2082
+ if (onThemeChangeCallback) {
2083
+ onThemeChangeCallback();
2084
+ }
2085
+ }
2086
+ }
2087
+
2088
+ /**
2089
+ * Get the current symbol preset override.
2090
+ */
2091
+ export function getSymbolPresetOverride(): SymbolPreset | undefined {
2092
+ return currentSymbolPresetOverride;
2093
+ }
2094
+
2095
+ /**
2096
+ * Set color blind mode, recreating the theme with the new setting.
2097
+ * When enabled, uses blue instead of green for diff additions.
2098
+ */
2099
+ export async function setColorBlindMode(enabled: boolean): Promise<void> {
2100
+ currentColorBlindMode = enabled;
2101
+ if (currentThemeName) {
2102
+ try {
2103
+ theme = await loadTheme(currentThemeName, getCurrentThemeOptions());
2104
+ } catch {
2105
+ // Fall back to xcsh-dark theme
2106
+ theme = await loadTheme("xcsh-dark", getCurrentThemeOptions());
2107
+ }
2108
+ if (onThemeChangeCallback) {
2109
+ onThemeChangeCallback();
2110
+ }
2111
+ }
2112
+ }
2113
+
2114
+ /**
2115
+ * Get the current color blind mode setting.
2116
+ */
2117
+ export function getColorBlindMode(): boolean {
2118
+ return currentColorBlindMode;
2119
+ }
2120
+
2121
+ export function onThemeChange(callback: () => void): void {
2122
+ onThemeChangeCallback = callback;
2123
+ }
2124
+
2125
+ /**
2126
+ * Get available symbol presets.
2127
+ */
2128
+ export function getAvailableSymbolPresets(): SymbolPreset[] {
2129
+ return ["unicode", "nerd", "ascii"];
2130
+ }
2131
+
2132
+ /**
2133
+ * Check if a string is a valid symbol preset.
2134
+ */
2135
+ export function isValidSymbolPreset(preset: string): preset is SymbolPreset {
2136
+ return preset === "unicode" || preset === "nerd" || preset === "ascii";
2137
+ }
2138
+
2139
+ async function startThemeWatcher(): Promise<void> {
2140
+ stopThemeWatcher();
2141
+
2142
+ // Only watch if it's a custom theme (not built-in)
2143
+ if (!currentThemeName || currentThemeName in BUILTIN_THEMES) {
2144
+ return;
2145
+ }
2146
+
2147
+ const customThemesDir = getCustomThemesDir();
2148
+ const watchedThemeName = currentThemeName;
2149
+ const watchedFileName = `${watchedThemeName}.json`;
2150
+ const themeFile = path.join(customThemesDir, watchedFileName);
2151
+
2152
+ // Only watch if the file exists
2153
+ if (!fs.existsSync(themeFile)) {
2154
+ return;
2155
+ }
2156
+
2157
+ const scheduleReload = () => {
2158
+ if (themeReloadTimer) {
2159
+ clearTimeout(themeReloadTimer);
2160
+ }
2161
+ themeReloadTimer = setTimeout(() => {
2162
+ themeReloadTimer = undefined;
2163
+
2164
+ // Ignore stale timers after switching themes or stopping the watcher
2165
+ if (currentThemeName !== watchedThemeName) {
2166
+ return;
2167
+ }
2168
+
2169
+ // Keep the last successfully loaded theme active if the file is temporarily missing
2170
+ if (!fs.existsSync(themeFile)) {
2171
+ return;
2172
+ }
2173
+
2174
+ loadTheme(watchedThemeName, getCurrentThemeOptions())
2175
+ .then(loadedTheme => {
2176
+ theme = loadedTheme;
2177
+ if (onThemeChangeCallback) {
2178
+ onThemeChangeCallback();
2179
+ }
2180
+ })
2181
+ .catch(() => {
2182
+ // Ignore errors (file might be in invalid state while being edited)
2183
+ });
2184
+ }, 100);
2185
+ };
2186
+
2187
+ try {
2188
+ themeWatcher = fs.watch(customThemesDir, (_eventType, filename) => {
2189
+ if (currentThemeName !== watchedThemeName) {
2190
+ return;
2191
+ }
2192
+ if (!filename) {
2193
+ scheduleReload();
2194
+ return;
2195
+ }
2196
+ const changedFile = String(filename);
2197
+ if (changedFile !== watchedFileName) {
2198
+ return;
2199
+ }
2200
+ scheduleReload();
2201
+ });
2202
+ } catch {
2203
+ // Ignore errors starting watcher
2204
+ }
2205
+ }
2206
+
2207
+ /**
2208
+ * Shared logic for re-evaluating the auto-detected theme.
2209
+ * Called from SIGWINCH, terminal appearance change handler, and macOS fallback observer.
2210
+ */
2211
+ function reevaluateAutoTheme(debugLabel: string): void {
2212
+ if (!autoDetectedTheme) return;
2213
+ const resolved = getDefaultTheme();
2214
+ if (resolved === currentThemeName) return;
2215
+ currentThemeName = resolved;
2216
+ loadTheme(resolved, getCurrentThemeOptions())
2217
+ .then(loadedTheme => {
2218
+ theme = loadedTheme;
2219
+ if (onThemeChangeCallback) {
2220
+ onThemeChangeCallback();
2221
+ }
2222
+ })
2223
+ .catch(err => {
2224
+ logger.debug(`Theme switch on ${debugLabel} failed`, { error: String(err) });
2225
+ });
2226
+ }
2227
+
2228
+ // ============================================================================
2229
+ // macOS Appearance Fallback Observer
2230
+ // ============================================================================
2231
+
2232
+ var macObserver: { stop(): void } | undefined;
2233
+
2234
+ function startMacAppearanceObserver(): void {
2235
+ stopMacAppearanceObserver();
2236
+ if (!shouldUseMacOSAppearanceFallback()) return;
2237
+ try {
2238
+ macOSReportedAppearance = detectMacOSAppearance() ?? undefined;
2239
+ macObserver = MacAppearanceObserver.start((err, appearance) => {
2240
+ if (!err && (appearance === "dark" || appearance === "light")) {
2241
+ macOSReportedAppearance = appearance;
2242
+ reevaluateAutoTheme("macOS fallback");
2243
+ }
2244
+ });
2245
+ } catch (err) {
2246
+ logger.warn("Failed to start macOS appearance observer", { err });
2247
+ }
2248
+ }
2249
+
2250
+ function stopMacAppearanceObserver(): void {
2251
+ if (macObserver) {
2252
+ macObserver.stop();
2253
+ macObserver = undefined;
2254
+ }
2255
+ macOSReportedAppearance = undefined;
2256
+ }
2257
+
2258
+ // ============================================================================
2259
+ // SIGWINCH Listener
2260
+ // ============================================================================
2261
+
2262
+ /** Re-check appearance on SIGWINCH and switch dark/light when using auto-detected theme. */
2263
+ function startSigwinchListener(): void {
2264
+ stopSigwinchListener();
2265
+ sigwinchHandler = () => {
2266
+ reevaluateAutoTheme("SIGWINCH");
2267
+ };
2268
+ process.on("SIGWINCH", sigwinchHandler);
2269
+ startMacAppearanceObserver();
2270
+ }
2271
+
2272
+ function stopSigwinchListener(): void {
2273
+ if (sigwinchHandler) {
2274
+ process.removeListener("SIGWINCH", sigwinchHandler);
2275
+ sigwinchHandler = undefined;
2276
+ }
2277
+ stopMacAppearanceObserver();
2278
+ }
2279
+
2280
+ export function stopThemeWatcher(): void {
2281
+ if (themeReloadTimer) {
2282
+ clearTimeout(themeReloadTimer);
2283
+ themeReloadTimer = undefined;
2284
+ }
2285
+ if (themeWatcher) {
2286
+ themeWatcher.close();
2287
+ themeWatcher = undefined;
2288
+ }
2289
+ stopSigwinchListener();
2290
+ terminalReportedAppearance = undefined;
2291
+ }
2292
+
2293
+ // ============================================================================
2294
+ // HTML Export Helpers
2295
+ // ============================================================================
2296
+
2297
+ /**
2298
+ * Convert a 256-color index to hex string.
2299
+ * Indices 0-15: basic colors (approximate)
2300
+ * Indices 16-231: 6x6x6 color cube
2301
+ * Indices 232-255: grayscale ramp
2302
+ */
2303
+ function ansi256ToHex(index: number): string {
2304
+ // Basic colors (0-15) - approximate common terminal values
2305
+ const basicColors = [
2306
+ "#000000",
2307
+ "#800000",
2308
+ "#008000",
2309
+ "#808000",
2310
+ "#000080",
2311
+ "#800080",
2312
+ "#008080",
2313
+ "#c0c0c0",
2314
+ "#808080",
2315
+ "#ff0000",
2316
+ "#00ff00",
2317
+ "#ffff00",
2318
+ "#0000ff",
2319
+ "#ff00ff",
2320
+ "#00ffff",
2321
+ "#ffffff",
2322
+ ];
2323
+ if (index < 16) {
2324
+ return basicColors[index];
2325
+ }
2326
+
2327
+ // Color cube (16-231): 6x6x6 = 216 colors
2328
+ if (index < 232) {
2329
+ const cubeIndex = index - 16;
2330
+ const r = Math.floor(cubeIndex / 36);
2331
+ const g = Math.floor((cubeIndex % 36) / 6);
2332
+ const b = cubeIndex % 6;
2333
+ const toHex = (n: number) => (n === 0 ? 0 : 55 + n * 40).toString(16).padStart(2, "0");
2334
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
2335
+ }
2336
+
2337
+ // Grayscale (232-255): 24 shades
2338
+ const gray = 8 + (index - 232) * 10;
2339
+ const grayHex = gray.toString(16).padStart(2, "0");
2340
+ return `#${grayHex}${grayHex}${grayHex}`;
2341
+ }
2342
+
2343
+ /**
2344
+ * Get resolved theme colors as CSS-compatible hex strings.
2345
+ * Used by HTML export to generate CSS custom properties.
2346
+ */
2347
+ export async function getResolvedThemeColors(themeName?: string): Promise<Record<string, string>> {
2348
+ const name = themeName ?? getDefaultTheme();
2349
+ const isLight = isLightTheme(name);
2350
+ const themeJson = await loadThemeJson(name);
2351
+ const resolved = resolveThemeColors(themeJson.colors, themeJson.vars);
2352
+
2353
+ // Default text color for empty values (terminal uses default fg color)
2354
+ const defaultText = isLight ? "#000000" : "#e5e5e7";
2355
+
2356
+ const cssColors: Record<string, string> = {};
2357
+ for (const [key, value] of Object.entries(resolved)) {
2358
+ if (typeof value === "number") {
2359
+ cssColors[key] = ansi256ToHex(value);
2360
+ } else if (value === "") {
2361
+ // Empty means default terminal color - use sensible fallback for HTML
2362
+ cssColors[key] = defaultText;
2363
+ } else {
2364
+ cssColors[key] = value;
2365
+ }
2366
+ }
2367
+ return cssColors;
2368
+ }
2369
+
2370
+ /**
2371
+ * Check if a theme is a "light" theme by analyzing its background color luminance.
2372
+ * Loads theme JSON synchronously (built-in or custom file) and resolves userMessageBg.
2373
+ */
2374
+ export function isLightTheme(themeName?: string): boolean {
2375
+ const name = themeName ?? "xcsh-dark";
2376
+ const builtinThemes = getBuiltinThemes();
2377
+ let themeJson: ThemeJson | undefined;
2378
+ if (name in builtinThemes) {
2379
+ themeJson = builtinThemes[name];
2380
+ } else {
2381
+ try {
2382
+ const customPath = path.join(getCustomThemesDir(), `${name}.json`);
2383
+ const content = fs.readFileSync(customPath, "utf-8");
2384
+ themeJson = JSON.parse(content) as ThemeJson;
2385
+ } catch {
2386
+ return false;
2387
+ }
2388
+ }
2389
+ try {
2390
+ const resolved = resolveVarRefs(themeJson.colors.userMessageBg, themeJson.vars ?? {});
2391
+ if (typeof resolved !== "string" || !resolved.startsWith("#") || resolved.length !== 7) return false;
2392
+ const r = parseInt(resolved.slice(1, 3), 16) / 255;
2393
+ const g = parseInt(resolved.slice(3, 5), 16) / 255;
2394
+ const b = parseInt(resolved.slice(5, 7), 16) / 255;
2395
+ // Relative luminance (ITU-R BT.709)
2396
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
2397
+ return luminance > 0.5;
2398
+ } catch {
2399
+ return false;
2400
+ }
2401
+ }
2402
+
2403
+ /**
2404
+ * Get explicit export colors from theme JSON, if specified.
2405
+ * Returns undefined for each color that isn't explicitly set.
2406
+ */
2407
+ export async function getThemeExportColors(themeName?: string): Promise<{
2408
+ pageBg?: string;
2409
+ cardBg?: string;
2410
+ infoBg?: string;
2411
+ }> {
2412
+ const name = themeName ?? getDefaultTheme();
2413
+ try {
2414
+ const themeJson = await loadThemeJson(name);
2415
+ const exportSection = themeJson.export;
2416
+ if (!exportSection) return {};
2417
+
2418
+ const vars = themeJson.vars ?? {};
2419
+ const resolve = (value: string | number | undefined): string | undefined => {
2420
+ if (value === undefined) return undefined;
2421
+ if (typeof value === "number") return ansi256ToHex(value);
2422
+ if (value === "" || value.startsWith("#")) return value;
2423
+ const varName = value.startsWith("$") ? value.slice(1) : value;
2424
+ if (varName in vars) {
2425
+ const resolved = resolveVarRefs(varName, vars);
2426
+ return typeof resolved === "number" ? ansi256ToHex(resolved) : resolved;
2427
+ }
2428
+ return value;
2429
+ };
2430
+
2431
+ return {
2432
+ pageBg: resolve(exportSection.pageBg),
2433
+ cardBg: resolve(exportSection.cardBg),
2434
+ infoBg: resolve(exportSection.infoBg),
2435
+ };
2436
+ } catch {
2437
+ return {};
2438
+ }
2439
+ }
2440
+
2441
+ // ============================================================================
2442
+ // TUI Helpers
2443
+ // ============================================================================
2444
+
2445
+ let cachedHighlightColorsFor: Theme | undefined;
2446
+ let cachedHighlightColors: NativeHighlightColors | undefined;
2447
+
2448
+ function getHighlightColors(t: Theme): NativeHighlightColors {
2449
+ if (cachedHighlightColorsFor !== t || !cachedHighlightColors) {
2450
+ cachedHighlightColorsFor = t;
2451
+ cachedHighlightColors = {
2452
+ comment: t.getFgAnsi("syntaxComment"),
2453
+ keyword: t.getFgAnsi("syntaxKeyword"),
2454
+ function: t.getFgAnsi("syntaxFunction"),
2455
+ variable: t.getFgAnsi("syntaxVariable"),
2456
+ string: t.getFgAnsi("syntaxString"),
2457
+ number: t.getFgAnsi("syntaxNumber"),
2458
+ type: t.getFgAnsi("syntaxType"),
2459
+ operator: t.getFgAnsi("syntaxOperator"),
2460
+ punctuation: t.getFgAnsi("syntaxPunctuation"),
2461
+ control: t.getFgAnsi("syntaxControl"),
2462
+ inserted: t.getFgAnsi("toolDiffAdded"),
2463
+ deleted: t.getFgAnsi("toolDiffRemoved"),
2464
+ };
2465
+ }
2466
+ return cachedHighlightColors;
2467
+ }
2468
+
2469
+ /**
2470
+ * Highlight code with syntax coloring based on file extension or language.
2471
+ * Returns array of highlighted lines.
2472
+ */
2473
+ export function highlightCode(code: string, lang?: string): string[] {
2474
+ const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
2475
+ try {
2476
+ return nativeHighlightCode(code, validLang, getHighlightColors(theme)).split("\n");
2477
+ } catch {
2478
+ return code.split("\n");
2479
+ }
2480
+ }
2481
+
2482
+ export function getSymbolTheme(): SymbolTheme {
2483
+ const preset = theme.getSymbolPreset();
2484
+
2485
+ return {
2486
+ cursor: theme.nav.cursor,
2487
+ inputCursor: preset === "ascii" ? "|" : "▏",
2488
+ boxRound: theme.boxRound,
2489
+ boxSharp: theme.boxSharp,
2490
+ table: theme.boxSharp,
2491
+ quoteBorder: theme.md.quoteBorder,
2492
+ hrChar: theme.md.hrChar,
2493
+ spinnerFrames: theme.getSpinnerFrames("activity"),
2494
+ };
2495
+ }
2496
+
2497
+ export function getMarkdownTheme(): MarkdownTheme {
2498
+ return {
2499
+ heading: (text: string) => theme.fg("mdHeading", text),
2500
+ link: (text: string) => theme.fg("mdLink", text),
2501
+ linkUrl: (text: string) => theme.fg("mdLinkUrl", text),
2502
+ code: (text: string) => theme.fg("mdCode", text),
2503
+ codeBlock: (text: string) => theme.fg("mdCodeBlock", text),
2504
+ codeBlockBorder: (text: string) => theme.fg("mdCodeBlockBorder", text),
2505
+ quote: (text: string) => theme.fg("mdQuote", text),
2506
+ quoteBorder: (text: string) => theme.fg("mdQuoteBorder", text),
2507
+ hr: (text: string) => theme.fg("mdHr", text),
2508
+ listBullet: (text: string) => theme.fg("mdListBullet", text),
2509
+ bold: (text: string) => theme.bold(text),
2510
+ italic: (text: string) => theme.italic(text),
2511
+ underline: (text: string) => theme.underline(text),
2512
+ strikethrough: (text: string) => chalk.strikethrough(text),
2513
+ symbols: getSymbolTheme(),
2514
+ getMermaidAscii: (hash: bigint | number) => getMermaidAscii(hash, mermaidThemeSignature(theme)),
2515
+ renderMermaidBlock: (source: string, width: number): string[] | null => {
2516
+ const diagram = renderMermaidThemed(source, theme, { targetWidth: Math.max(20, width - 4) });
2517
+ if (!diagram) return null;
2518
+ return renderOutputBlock(
2519
+ buildMermaidBlockOptions(diagram, { width, theme, typeLabel: diagramTypeLabel(detectDiagramType(source)) }),
2520
+ theme,
2521
+ );
2522
+ },
2523
+ highlightCode: (code: string, lang?: string): string[] => {
2524
+ const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
2525
+ try {
2526
+ return nativeHighlightCode(code, validLang, getHighlightColors(theme)).split("\n");
2527
+ } catch {
2528
+ return code.split("\n").map(line => theme.fg("mdCodeBlock", line));
2529
+ }
2530
+ },
2531
+ };
2532
+ }
2533
+
2534
+ export function getSelectListTheme(): SelectListTheme {
2535
+ return {
2536
+ selectedPrefix: (text: string) => theme.fg("chromeAccent", text),
2537
+ selectedText: (text: string) => theme.fg("chromeAccent", text),
2538
+ description: (text: string) => theme.fg("muted", text),
2539
+ scrollInfo: (text: string) => theme.fg("muted", text),
2540
+ noMatch: (text: string) => theme.fg("muted", text),
2541
+ symbols: getSymbolTheme(),
2542
+ };
2543
+ }
2544
+
2545
+ export function getEditorTheme(): EditorTheme {
2546
+ return {
2547
+ borderColor: (text: string) => theme.fg("borderMuted", text),
2548
+ selectList: getSelectListTheme(),
2549
+ symbols: getSymbolTheme(),
2550
+ hintStyle: (text: string) => theme.fg("dim", text),
2551
+ };
2552
+ }
2553
+
2554
+ export function getSettingsListTheme(): import("@f5xc-salesdemos/pi-tui").SettingsListTheme {
2555
+ return {
2556
+ label: (text: string, selected: boolean) => (selected ? theme.fg("contentAccent", text) : text),
2557
+ value: (text: string, selected: boolean) =>
2558
+ selected ? theme.fg("contentAccent", text) : theme.fg("muted", text),
2559
+ description: (text: string) => theme.fg("dim", text),
2560
+ cursor: theme.fg("chromeAccent", `${theme.nav.cursor} `),
2561
+ hint: (text: string) => theme.fg("dim", text),
2562
+ };
2563
+ }