oma-coding-agent 1.1.4

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 (1298) hide show
  1. package/CHANGELOG.md +12164 -0
  2. package/README.md +35 -0
  3. package/dist/cli.js +18266 -0
  4. package/examples/README.md +21 -0
  5. package/examples/custom-tools/README.md +104 -0
  6. package/examples/custom-tools/hello/index.ts +20 -0
  7. package/examples/extensions/README.md +142 -0
  8. package/examples/extensions/api-demo.ts +79 -0
  9. package/examples/extensions/chalk-logger.ts +25 -0
  10. package/examples/extensions/hello.ts +31 -0
  11. package/examples/extensions/pirate.ts +43 -0
  12. package/examples/extensions/plan-mode.ts +549 -0
  13. package/examples/extensions/reload-runtime.ts +38 -0
  14. package/examples/extensions/thinking-note.ts +13 -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 +17 -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 +46 -0
  34. package/examples/sdk/04-skills.ts +43 -0
  35. package/examples/sdk/06-extensions.ts +82 -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/12-redis-sessions.ts +54 -0
  43. package/examples/sdk/13-sql-sessions.ts +61 -0
  44. package/examples/sdk/README.md +172 -0
  45. package/package.json +573 -0
  46. package/scripts/bench-guard.ts +71 -0
  47. package/scripts/build-binary.ts +108 -0
  48. package/scripts/bundle-dist.ts +110 -0
  49. package/scripts/embed-mupdf-wasm.ts +67 -0
  50. package/scripts/format-prompts.ts +68 -0
  51. package/scripts/generate-docs-index.ts +56 -0
  52. package/scripts/generate-share-viewer.ts +34 -0
  53. package/scripts/measure-prompt-tokens.ts +63 -0
  54. package/scripts/omp +42 -0
  55. package/scripts/omp.ts +19 -0
  56. package/src/advisor/__tests__/advisor.test.ts +915 -0
  57. package/src/advisor/advise-tool.ts +165 -0
  58. package/src/advisor/index.ts +4 -0
  59. package/src/advisor/runtime.ts +270 -0
  60. package/src/advisor/transcript-recorder.ts +136 -0
  61. package/src/advisor/watchdog.ts +83 -0
  62. package/src/async/index.ts +1 -0
  63. package/src/async/job-manager.ts +674 -0
  64. package/src/auto-thinking/classifier.ts +190 -0
  65. package/src/autolearn/controller.ts +139 -0
  66. package/src/autolearn/managed-skills.ts +255 -0
  67. package/src/autoresearch/command-resume.md +14 -0
  68. package/src/autoresearch/dashboard.ts +436 -0
  69. package/src/autoresearch/git.ts +319 -0
  70. package/src/autoresearch/helpers.ts +218 -0
  71. package/src/autoresearch/index.ts +536 -0
  72. package/src/autoresearch/prompt-setup.md +43 -0
  73. package/src/autoresearch/prompt.md +103 -0
  74. package/src/autoresearch/resume-message.md +10 -0
  75. package/src/autoresearch/state.ts +273 -0
  76. package/src/autoresearch/storage.ts +700 -0
  77. package/src/autoresearch/tools/init-experiment.ts +269 -0
  78. package/src/autoresearch/tools/log-experiment.ts +521 -0
  79. package/src/autoresearch/tools/run-experiment.ts +407 -0
  80. package/src/autoresearch/tools/update-notes.ts +109 -0
  81. package/src/autoresearch/types.ts +168 -0
  82. package/src/capability/context-file.ts +44 -0
  83. package/src/capability/extension-module.ts +34 -0
  84. package/src/capability/extension.ts +47 -0
  85. package/src/capability/fs.ts +117 -0
  86. package/src/capability/hook.ts +40 -0
  87. package/src/capability/index.ts +436 -0
  88. package/src/capability/instruction.ts +37 -0
  89. package/src/capability/mcp.ts +76 -0
  90. package/src/capability/prompt.ts +35 -0
  91. package/src/capability/rule-buckets.ts +66 -0
  92. package/src/capability/rule.ts +261 -0
  93. package/src/capability/settings.ts +34 -0
  94. package/src/capability/skill.ts +63 -0
  95. package/src/capability/slash-command.ts +40 -0
  96. package/src/capability/ssh.ts +41 -0
  97. package/src/capability/system-prompt.ts +34 -0
  98. package/src/capability/tool.ts +38 -0
  99. package/src/capability/types.ts +168 -0
  100. package/src/cli/agents-cli.ts +138 -0
  101. package/src/cli/args.ts +361 -0
  102. package/src/cli/auth-broker-cli.ts +893 -0
  103. package/src/cli/auth-gateway-cli.ts +608 -0
  104. package/src/cli/bench-cli.ts +552 -0
  105. package/src/cli/classify-install-target.ts +76 -0
  106. package/src/cli/claude-trace-cli.ts +795 -0
  107. package/src/cli/commands/init-xdg.ts +27 -0
  108. package/src/cli/completion-gen.ts +550 -0
  109. package/src/cli/config-cli.ts +418 -0
  110. package/src/cli/dry-balance-cli.ts +858 -0
  111. package/src/cli/extension-flags.ts +48 -0
  112. package/src/cli/file-processor.ts +133 -0
  113. package/src/cli/flag-tables.ts +280 -0
  114. package/src/cli/gallery-cli.ts +231 -0
  115. package/src/cli/gallery-fixtures/agentic.ts +407 -0
  116. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  117. package/src/cli/gallery-fixtures/edit.ts +194 -0
  118. package/src/cli/gallery-fixtures/fs.ts +220 -0
  119. package/src/cli/gallery-fixtures/index.ts +40 -0
  120. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  121. package/src/cli/gallery-fixtures/memory.ts +81 -0
  122. package/src/cli/gallery-fixtures/misc.ts +250 -0
  123. package/src/cli/gallery-fixtures/search.ts +213 -0
  124. package/src/cli/gallery-fixtures/shell.ts +167 -0
  125. package/src/cli/gallery-fixtures/types.ts +57 -0
  126. package/src/cli/gallery-fixtures/web.ts +158 -0
  127. package/src/cli/gallery-screenshot.ts +279 -0
  128. package/src/cli/grep-cli.ts +160 -0
  129. package/src/cli/grievances-cli.ts +256 -0
  130. package/src/cli/initial-message.ts +58 -0
  131. package/src/cli/models-cli.ts +427 -0
  132. package/src/cli/plugin-cli.ts +996 -0
  133. package/src/cli/profile-alias.ts +338 -0
  134. package/src/cli/profile-bootstrap.ts +243 -0
  135. package/src/cli/read-cli.ts +57 -0
  136. package/src/cli/session-picker.ts +80 -0
  137. package/src/cli/setup-cli.ts +332 -0
  138. package/src/cli/setup-model-picker.ts +43 -0
  139. package/src/cli/shell-cli.ts +176 -0
  140. package/src/cli/ssh-cli.ts +179 -0
  141. package/src/cli/startup-cwd.ts +58 -0
  142. package/src/cli/stats-cli.ts +229 -0
  143. package/src/cli/tiny-models-cli.ts +127 -0
  144. package/src/cli/ttsr-cli.ts +995 -0
  145. package/src/cli/update-cli.ts +671 -0
  146. package/src/cli/usage-cli.ts +774 -0
  147. package/src/cli/web-search-cli.ts +132 -0
  148. package/src/cli/worktree-cli.ts +291 -0
  149. package/src/cli-commands.ts +85 -0
  150. package/src/cli.ts +326 -0
  151. package/src/collab/crypto.ts +63 -0
  152. package/src/collab/guest.ts +450 -0
  153. package/src/collab/host.ts +577 -0
  154. package/src/collab/protocol.ts +274 -0
  155. package/src/collab/relay-client.ts +216 -0
  156. package/src/commands/acp.ts +24 -0
  157. package/src/commands/agents.ts +57 -0
  158. package/src/commands/auth-broker.ts +99 -0
  159. package/src/commands/auth-gateway.ts +69 -0
  160. package/src/commands/bench.ts +42 -0
  161. package/src/commands/commit.ts +46 -0
  162. package/src/commands/complete.ts +66 -0
  163. package/src/commands/completions.ts +60 -0
  164. package/src/commands/config.ts +51 -0
  165. package/src/commands/dry-balance.ts +43 -0
  166. package/src/commands/gallery.ts +52 -0
  167. package/src/commands/grep.ts +48 -0
  168. package/src/commands/grievances.ts +51 -0
  169. package/src/commands/install.ts +107 -0
  170. package/src/commands/join.ts +39 -0
  171. package/src/commands/launch.ts +182 -0
  172. package/src/commands/models.ts +61 -0
  173. package/src/commands/plugin.ts +78 -0
  174. package/src/commands/read.ts +38 -0
  175. package/src/commands/say.ts +102 -0
  176. package/src/commands/setup.ts +67 -0
  177. package/src/commands/shell.ts +29 -0
  178. package/src/commands/ssh.ts +60 -0
  179. package/src/commands/stats.ts +29 -0
  180. package/src/commands/tiny-models.ts +36 -0
  181. package/src/commands/token.ts +108 -0
  182. package/src/commands/ttsr.ts +125 -0
  183. package/src/commands/update.ts +21 -0
  184. package/src/commands/usage.ts +43 -0
  185. package/src/commands/web-search.ts +42 -0
  186. package/src/commands/worktree.ts +56 -0
  187. package/src/commit/agentic/agent.ts +318 -0
  188. package/src/commit/agentic/fallback.ts +96 -0
  189. package/src/commit/agentic/index.ts +355 -0
  190. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  191. package/src/commit/agentic/prompts/session-user.md +25 -0
  192. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  193. package/src/commit/agentic/prompts/system.md +38 -0
  194. package/src/commit/agentic/state.ts +60 -0
  195. package/src/commit/agentic/tools/analyze-file.ts +149 -0
  196. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  197. package/src/commit/agentic/tools/git-hunk.ts +52 -0
  198. package/src/commit/agentic/tools/git-overview.ts +81 -0
  199. package/src/commit/agentic/tools/index.ts +54 -0
  200. package/src/commit/agentic/tools/propose-changelog.ts +147 -0
  201. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  202. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  203. package/src/commit/agentic/tools/schemas.ts +11 -0
  204. package/src/commit/agentic/tools/split-commit.ts +241 -0
  205. package/src/commit/agentic/topo-sort.ts +44 -0
  206. package/src/commit/agentic/trivial.ts +51 -0
  207. package/src/commit/agentic/validation.ts +183 -0
  208. package/src/commit/analysis/conventional.ts +64 -0
  209. package/src/commit/analysis/index.ts +4 -0
  210. package/src/commit/analysis/scope.ts +242 -0
  211. package/src/commit/analysis/summary.ts +107 -0
  212. package/src/commit/analysis/validation.ts +66 -0
  213. package/src/commit/changelog/detect.ts +40 -0
  214. package/src/commit/changelog/generate.ts +101 -0
  215. package/src/commit/changelog/index.ts +234 -0
  216. package/src/commit/changelog/parse.ts +44 -0
  217. package/src/commit/cli.ts +85 -0
  218. package/src/commit/git/diff.ts +148 -0
  219. package/src/commit/index.ts +5 -0
  220. package/src/commit/map-reduce/index.ts +69 -0
  221. package/src/commit/map-reduce/map-phase.ts +193 -0
  222. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  223. package/src/commit/map-reduce/utils.ts +9 -0
  224. package/src/commit/message.ts +11 -0
  225. package/src/commit/model-selection.ts +89 -0
  226. package/src/commit/pipeline.ts +243 -0
  227. package/src/commit/prompts/analysis-system.md +148 -0
  228. package/src/commit/prompts/analysis-user.md +38 -0
  229. package/src/commit/prompts/changelog-system.md +50 -0
  230. package/src/commit/prompts/changelog-user.md +18 -0
  231. package/src/commit/prompts/file-observer-system.md +24 -0
  232. package/src/commit/prompts/file-observer-user.md +8 -0
  233. package/src/commit/prompts/reduce-system.md +50 -0
  234. package/src/commit/prompts/reduce-user.md +17 -0
  235. package/src/commit/prompts/summary-retry.md +3 -0
  236. package/src/commit/prompts/summary-system.md +38 -0
  237. package/src/commit/prompts/summary-user.md +13 -0
  238. package/src/commit/prompts/types-description.md +2 -0
  239. package/src/commit/shared-llm.ts +70 -0
  240. package/src/commit/types.ts +118 -0
  241. package/src/commit/utils/exclusions.ts +42 -0
  242. package/src/commit/utils.ts +58 -0
  243. package/src/config/api-key-resolver.ts +67 -0
  244. package/src/config/append-only-context-mode.ts +76 -0
  245. package/src/config/config-file.ts +315 -0
  246. package/src/config/file-lock.ts +164 -0
  247. package/src/config/keybindings.ts +634 -0
  248. package/src/config/mcp-schema.json +238 -0
  249. package/src/config/model-discovery.ts +589 -0
  250. package/src/config/model-registry.ts +2260 -0
  251. package/src/config/model-resolver.ts +1819 -0
  252. package/src/config/model-roles.ts +99 -0
  253. package/src/config/models-config-schema.ts +266 -0
  254. package/src/config/models-config.ts +131 -0
  255. package/src/config/prompt-templates.ts +185 -0
  256. package/src/config/resolve-config-value.ts +94 -0
  257. package/src/config/settings-schema.ts +4740 -0
  258. package/src/config/settings.ts +1243 -0
  259. package/src/config.ts +242 -0
  260. package/src/cursor.ts +340 -0
  261. package/src/dap/client.ts +760 -0
  262. package/src/dap/config.ts +189 -0
  263. package/src/dap/defaults.json +212 -0
  264. package/src/dap/index.ts +4 -0
  265. package/src/dap/session.ts +1441 -0
  266. package/src/dap/types.ts +610 -0
  267. package/src/debug/index.ts +559 -0
  268. package/src/debug/log-formatting.ts +58 -0
  269. package/src/debug/log-viewer.ts +908 -0
  270. package/src/debug/profiler.ts +162 -0
  271. package/src/debug/protocol-probe.ts +267 -0
  272. package/src/debug/raw-sse-buffer.ts +294 -0
  273. package/src/debug/raw-sse.ts +292 -0
  274. package/src/debug/remote-debugger.ts +151 -0
  275. package/src/debug/report-bundle.ts +375 -0
  276. package/src/debug/system-info.ts +111 -0
  277. package/src/debug/terminal-info.ts +124 -0
  278. package/src/discovery/agents-md.ts +67 -0
  279. package/src/discovery/agents.ts +230 -0
  280. package/src/discovery/at-imports.ts +273 -0
  281. package/src/discovery/builtin-defaults.ts +39 -0
  282. package/src/discovery/builtin-rules/index.ts +63 -0
  283. package/src/discovery/builtin-rules/low-end/no-hallucinated-apis.md +14 -0
  284. package/src/discovery/builtin-rules/low-end/no-hallucinated-paths.md +14 -0
  285. package/src/discovery/builtin-rules/low-end/no-premature-completion.md +14 -0
  286. package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
  287. package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
  288. package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
  289. package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
  290. package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
  291. package/src/discovery/builtin-rules/rs-result-type.md +19 -0
  292. package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
  293. package/src/discovery/builtin-rules/ts-import-type.md +42 -0
  294. package/src/discovery/builtin-rules/ts-no-any.md +65 -0
  295. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
  296. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
  297. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
  298. package/src/discovery/builtin-rules/ts-no-return-type.md +44 -0
  299. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  300. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  301. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
  302. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  303. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  304. package/src/discovery/builtin.ts +934 -0
  305. package/src/discovery/claude-plugins.ts +386 -0
  306. package/src/discovery/claude.ts +584 -0
  307. package/src/discovery/cline.ts +83 -0
  308. package/src/discovery/codex.ts +522 -0
  309. package/src/discovery/cursor.ts +220 -0
  310. package/src/discovery/gemini.ts +383 -0
  311. package/src/discovery/github.ts +337 -0
  312. package/src/discovery/helpers.ts +1092 -0
  313. package/src/discovery/index.ts +81 -0
  314. package/src/discovery/mcp-json.ts +172 -0
  315. package/src/discovery/omp-extension-roots.ts +190 -0
  316. package/src/discovery/omp-plugins.ts +383 -0
  317. package/src/discovery/opencode.ts +398 -0
  318. package/src/discovery/plugin-dir-roots.ts +28 -0
  319. package/src/discovery/ssh.ts +153 -0
  320. package/src/discovery/substitute-plugin-root.ts +29 -0
  321. package/src/discovery/vscode.ts +105 -0
  322. package/src/discovery/windsurf.ts +147 -0
  323. package/src/edit/apply-patch/index.ts +87 -0
  324. package/src/edit/apply-patch/parser.ts +174 -0
  325. package/src/edit/diff.ts +999 -0
  326. package/src/edit/file-snapshot-store.ts +143 -0
  327. package/src/edit/hashline/block-resolver.ts +33 -0
  328. package/src/edit/hashline/diff.ts +290 -0
  329. package/src/edit/hashline/execute.ts +237 -0
  330. package/src/edit/hashline/filesystem.ts +130 -0
  331. package/src/edit/hashline/index.ts +5 -0
  332. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  333. package/src/edit/hashline/params.ts +19 -0
  334. package/src/edit/index.ts +620 -0
  335. package/src/edit/modes/apply-patch.lark +19 -0
  336. package/src/edit/modes/apply-patch.ts +53 -0
  337. package/src/edit/modes/patch.ts +1888 -0
  338. package/src/edit/modes/replace.ts +1133 -0
  339. package/src/edit/normalize.ts +345 -0
  340. package/src/edit/notebook.ts +242 -0
  341. package/src/edit/read-file.ts +25 -0
  342. package/src/edit/renderer.ts +823 -0
  343. package/src/edit/streaming.ts +517 -0
  344. package/src/eval/__tests__/agent-bridge.test.ts +769 -0
  345. package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
  346. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  347. package/src/eval/__tests__/completion-bridge.test.ts +412 -0
  348. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  349. package/src/eval/__tests__/idle-timeout.test.ts +80 -0
  350. package/src/eval/__tests__/js-context-manager.test.ts +291 -0
  351. package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
  352. package/src/eval/__tests__/prelude-agent.test.ts +73 -0
  353. package/src/eval/agent-bridge.ts +319 -0
  354. package/src/eval/backend.ts +71 -0
  355. package/src/eval/bridge-timeout.ts +44 -0
  356. package/src/eval/budget-bridge.ts +48 -0
  357. package/src/eval/completion-bridge.ts +211 -0
  358. package/src/eval/concurrency-bridge.ts +34 -0
  359. package/src/eval/idle-timeout.ts +91 -0
  360. package/src/eval/index.ts +4 -0
  361. package/src/eval/js/context-manager.ts +621 -0
  362. package/src/eval/js/executor.ts +173 -0
  363. package/src/eval/js/index.ts +51 -0
  364. package/src/eval/js/shared/helpers.ts +283 -0
  365. package/src/eval/js/shared/indirect-eval.ts +30 -0
  366. package/src/eval/js/shared/local-module-loader.ts +342 -0
  367. package/src/eval/js/shared/prelude.ts +2 -0
  368. package/src/eval/js/shared/prelude.txt +307 -0
  369. package/src/eval/js/shared/rewrite-imports.ts +532 -0
  370. package/src/eval/js/shared/runtime.ts +580 -0
  371. package/src/eval/js/shared/types.ts +18 -0
  372. package/src/eval/js/tool-bridge.ts +163 -0
  373. package/src/eval/js/worker-core.ts +151 -0
  374. package/src/eval/js/worker-entry.ts +37 -0
  375. package/src/eval/js/worker-protocol.ts +47 -0
  376. package/src/eval/py/__tests__/prelude.test.ts +19 -0
  377. package/src/eval/py/display.ts +71 -0
  378. package/src/eval/py/executor.ts +742 -0
  379. package/src/eval/py/index.ts +68 -0
  380. package/src/eval/py/kernel.ts +748 -0
  381. package/src/eval/py/prelude.py +683 -0
  382. package/src/eval/py/prelude.ts +3 -0
  383. package/src/eval/py/runner.py +1177 -0
  384. package/src/eval/py/runtime.ts +276 -0
  385. package/src/eval/py/spawn-options.ts +126 -0
  386. package/src/eval/py/tool-bridge.ts +182 -0
  387. package/src/eval/session-id.ts +8 -0
  388. package/src/eval/types.ts +48 -0
  389. package/src/exa/index.ts +2 -0
  390. package/src/exa/mcp-client.ts +370 -0
  391. package/src/exa/types.ts +69 -0
  392. package/src/exec/bash-executor.ts +434 -0
  393. package/src/exec/exec.ts +53 -0
  394. package/src/exec/non-interactive-env.ts +119 -0
  395. package/src/export/custom-share.ts +65 -0
  396. package/src/export/html/index.ts +266 -0
  397. package/src/export/html/share-loader.js +102 -0
  398. package/src/export/html/template.css +1337 -0
  399. package/src/export/html/template.html +49 -0
  400. package/src/export/html/template.js +1626 -0
  401. package/src/export/html/tool-views.generated.js +37 -0
  402. package/src/export/html/vendor/highlight.min.js +1213 -0
  403. package/src/export/html/vendor/marked.min.js +6 -0
  404. package/src/export/share.ts +268 -0
  405. package/src/export/ttsr.ts +583 -0
  406. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  407. package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
  408. package/src/extensibility/custom-commands/index.ts +2 -0
  409. package/src/extensibility/custom-commands/loader.ts +242 -0
  410. package/src/extensibility/custom-commands/types.ts +119 -0
  411. package/src/extensibility/custom-tools/index.ts +7 -0
  412. package/src/extensibility/custom-tools/loader.ts +268 -0
  413. package/src/extensibility/custom-tools/types.ts +277 -0
  414. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  415. package/src/extensibility/extensions/compact-handler.ts +40 -0
  416. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  417. package/src/extensibility/extensions/index.ts +16 -0
  418. package/src/extensibility/extensions/loader.ts +587 -0
  419. package/src/extensibility/extensions/model-api.ts +41 -0
  420. package/src/extensibility/extensions/runner.ts +989 -0
  421. package/src/extensibility/extensions/types.ts +1394 -0
  422. package/src/extensibility/extensions/wrapper.ts +259 -0
  423. package/src/extensibility/hooks/index.ts +6 -0
  424. package/src/extensibility/hooks/loader.ts +262 -0
  425. package/src/extensibility/hooks/runner.ts +425 -0
  426. package/src/extensibility/hooks/tool-wrapper.ts +107 -0
  427. package/src/extensibility/hooks/types.ts +613 -0
  428. package/src/extensibility/legacy-pi-ai-shim.ts +61 -0
  429. package/src/extensibility/legacy-pi-coding-agent-shim.ts +128 -0
  430. package/src/extensibility/plugins/doctor.ts +65 -0
  431. package/src/extensibility/plugins/git-url.ts +367 -0
  432. package/src/extensibility/plugins/index.ts +9 -0
  433. package/src/extensibility/plugins/installer.ts +192 -0
  434. package/src/extensibility/plugins/legacy-pi-compat.ts +712 -0
  435. package/src/extensibility/plugins/loader.ts +458 -0
  436. package/src/extensibility/plugins/manager.ts +1026 -0
  437. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  438. package/src/extensibility/plugins/marketplace/fetcher.ts +315 -0
  439. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  440. package/src/extensibility/plugins/marketplace/manager.ts +770 -0
  441. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  442. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  443. package/src/extensibility/plugins/marketplace/types.ts +191 -0
  444. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  445. package/src/extensibility/plugins/parser.ts +105 -0
  446. package/src/extensibility/plugins/runtime-config.ts +9 -0
  447. package/src/extensibility/plugins/types.ts +194 -0
  448. package/src/extensibility/shared-events.ts +367 -0
  449. package/src/extensibility/skills.ts +408 -0
  450. package/src/extensibility/slash-commands.ts +131 -0
  451. package/src/extensibility/tool-proxy.ts +28 -0
  452. package/src/extensibility/typebox.ts +945 -0
  453. package/src/extensibility/utils.ts +44 -0
  454. package/src/goals/guided-setup.ts +142 -0
  455. package/src/goals/index.ts +3 -0
  456. package/src/goals/runtime.ts +521 -0
  457. package/src/goals/state.ts +37 -0
  458. package/src/goals/tools/goal-tool.ts +251 -0
  459. package/src/hindsight/backend.ts +354 -0
  460. package/src/hindsight/bank.ts +156 -0
  461. package/src/hindsight/client.ts +623 -0
  462. package/src/hindsight/config.ts +175 -0
  463. package/src/hindsight/content.ts +210 -0
  464. package/src/hindsight/index.ts +8 -0
  465. package/src/hindsight/mental-models.ts +429 -0
  466. package/src/hindsight/seeds.json +32 -0
  467. package/src/hindsight/state.ts +492 -0
  468. package/src/hindsight/transcript.ts +71 -0
  469. package/src/index.ts +66 -0
  470. package/src/internal-urls/agent-protocol.ts +146 -0
  471. package/src/internal-urls/artifact-protocol.ts +107 -0
  472. package/src/internal-urls/docs-index.generated.txt +2 -0
  473. package/src/internal-urls/docs-index.ts +102 -0
  474. package/src/internal-urls/history-protocol.ts +118 -0
  475. package/src/internal-urls/index.ts +25 -0
  476. package/src/internal-urls/issue-pr-protocol.ts +594 -0
  477. package/src/internal-urls/json-query.ts +126 -0
  478. package/src/internal-urls/local-protocol.ts +309 -0
  479. package/src/internal-urls/mcp-protocol.ts +151 -0
  480. package/src/internal-urls/memory-protocol.ts +169 -0
  481. package/src/internal-urls/omp-protocol.ts +94 -0
  482. package/src/internal-urls/parse.ts +72 -0
  483. package/src/internal-urls/registry-helpers.ts +25 -0
  484. package/src/internal-urls/router.ts +105 -0
  485. package/src/internal-urls/rule-protocol.ts +45 -0
  486. package/src/internal-urls/skill-protocol.ts +96 -0
  487. package/src/internal-urls/types.ts +152 -0
  488. package/src/internal-urls/vault-protocol.ts +936 -0
  489. package/src/irc/bus.ts +311 -0
  490. package/src/lib/xai-http.ts +124 -0
  491. package/src/lsp/client.ts +1217 -0
  492. package/src/lsp/clients/biome-client.ts +264 -0
  493. package/src/lsp/clients/index.ts +50 -0
  494. package/src/lsp/clients/lsp-linter-client.ts +85 -0
  495. package/src/lsp/clients/swiftlint-client.ts +120 -0
  496. package/src/lsp/config.ts +502 -0
  497. package/src/lsp/defaults.json +499 -0
  498. package/src/lsp/diagnostics-ledger.ts +51 -0
  499. package/src/lsp/edits.ts +267 -0
  500. package/src/lsp/format-options.ts +119 -0
  501. package/src/lsp/index.ts +2480 -0
  502. package/src/lsp/lspmux.ts +233 -0
  503. package/src/lsp/render.ts +668 -0
  504. package/src/lsp/startup-events.ts +13 -0
  505. package/src/lsp/types.ts +444 -0
  506. package/src/lsp/utils.ts +718 -0
  507. package/src/main.ts +1421 -0
  508. package/src/markit/NOTICE +32 -0
  509. package/src/markit/converters/docx.ts +56 -0
  510. package/src/markit/converters/epub.ts +136 -0
  511. package/src/markit/converters/mammoth.d.ts +24 -0
  512. package/src/markit/converters/pdf/columns.ts +103 -0
  513. package/src/markit/converters/pdf/extract.ts +574 -0
  514. package/src/markit/converters/pdf/grid.ts +780 -0
  515. package/src/markit/converters/pdf/headers.ts +106 -0
  516. package/src/markit/converters/pdf/index.ts +146 -0
  517. package/src/markit/converters/pdf/render.ts +501 -0
  518. package/src/markit/converters/pdf/types.ts +84 -0
  519. package/src/markit/converters/pptx.ts +325 -0
  520. package/src/markit/converters/xlsx.ts +173 -0
  521. package/src/markit/index.ts +2 -0
  522. package/src/markit/registry.ts +59 -0
  523. package/src/markit/types.ts +35 -0
  524. package/src/mcp/client.ts +509 -0
  525. package/src/mcp/config-writer.ts +229 -0
  526. package/src/mcp/config.ts +365 -0
  527. package/src/mcp/index.ts +29 -0
  528. package/src/mcp/json-rpc.ts +122 -0
  529. package/src/mcp/loader.ts +124 -0
  530. package/src/mcp/manager.ts +1326 -0
  531. package/src/mcp/oauth-credentials.ts +104 -0
  532. package/src/mcp/oauth-discovery.ts +467 -0
  533. package/src/mcp/oauth-flow.ts +555 -0
  534. package/src/mcp/render.ts +155 -0
  535. package/src/mcp/smithery-auth.ts +104 -0
  536. package/src/mcp/smithery-connect.ts +145 -0
  537. package/src/mcp/smithery-registry.ts +477 -0
  538. package/src/mcp/startup-events.ts +21 -0
  539. package/src/mcp/timeout.ts +59 -0
  540. package/src/mcp/tool-bridge.ts +429 -0
  541. package/src/mcp/tool-cache.ts +117 -0
  542. package/src/mcp/transports/http.ts +519 -0
  543. package/src/mcp/transports/index.ts +6 -0
  544. package/src/mcp/transports/stdio.ts +606 -0
  545. package/src/mcp/types.ts +427 -0
  546. package/src/memories/index.ts +1281 -0
  547. package/src/memories/storage.ts +578 -0
  548. package/src/memory-backend/index.ts +18 -0
  549. package/src/memory-backend/local-backend.ts +45 -0
  550. package/src/memory-backend/off-backend.ts +25 -0
  551. package/src/memory-backend/resolve.ts +25 -0
  552. package/src/memory-backend/runtime.ts +66 -0
  553. package/src/memory-backend/types.ts +166 -0
  554. package/src/mnemopi/backend.ts +612 -0
  555. package/src/mnemopi/config.ts +265 -0
  556. package/src/mnemopi/embed-client.ts +401 -0
  557. package/src/mnemopi/embed-protocol.ts +35 -0
  558. package/src/mnemopi/embed-worker.ts +113 -0
  559. package/src/mnemopi/index.ts +3 -0
  560. package/src/mnemopi/state.ts +657 -0
  561. package/src/modes/acp/acp-agent.ts +2362 -0
  562. package/src/modes/acp/acp-client-bridge.ts +154 -0
  563. package/src/modes/acp/acp-event-mapper.ts +933 -0
  564. package/src/modes/acp/acp-mode.ts +23 -0
  565. package/src/modes/acp/index.ts +2 -0
  566. package/src/modes/acp/terminal-auth.ts +37 -0
  567. package/src/modes/components/__tests__/skill-message.test.ts +92 -0
  568. package/src/modes/components/advisor-message.ts +99 -0
  569. package/src/modes/components/agent-dashboard.ts +1206 -0
  570. package/src/modes/components/agent-hub.ts +566 -0
  571. package/src/modes/components/agent-transcript-viewer.ts +461 -0
  572. package/src/modes/components/assistant-message.ts +612 -0
  573. package/src/modes/components/background-tan-message.ts +36 -0
  574. package/src/modes/components/bash-execution.ts +220 -0
  575. package/src/modes/components/bordered-loader.ts +41 -0
  576. package/src/modes/components/btw-panel.ts +112 -0
  577. package/src/modes/components/cache-invalidation-marker.ts +110 -0
  578. package/src/modes/components/chat-block.ts +111 -0
  579. package/src/modes/components/chat-transcript-builder.ts +476 -0
  580. package/src/modes/components/collab-prompt-message.ts +32 -0
  581. package/src/modes/components/compaction-summary-message.ts +215 -0
  582. package/src/modes/components/copy-selector.ts +206 -0
  583. package/src/modes/components/countdown-timer.ts +75 -0
  584. package/src/modes/components/custom-editor.test.ts +142 -0
  585. package/src/modes/components/custom-editor.ts +620 -0
  586. package/src/modes/components/custom-message.ts +67 -0
  587. package/src/modes/components/diff.ts +254 -0
  588. package/src/modes/components/dynamic-border.ts +34 -0
  589. package/src/modes/components/error-banner.ts +33 -0
  590. package/src/modes/components/eval-execution.ts +158 -0
  591. package/src/modes/components/execution-shared.ts +101 -0
  592. package/src/modes/components/extensions/extension-dashboard.ts +399 -0
  593. package/src/modes/components/extensions/extension-list.ts +502 -0
  594. package/src/modes/components/extensions/index.ts +9 -0
  595. package/src/modes/components/extensions/inspector-panel.ts +321 -0
  596. package/src/modes/components/extensions/state-manager.ts +627 -0
  597. package/src/modes/components/extensions/types.ts +186 -0
  598. package/src/modes/components/footer.ts +275 -0
  599. package/src/modes/components/history-search.ts +280 -0
  600. package/src/modes/components/hook-editor.ts +167 -0
  601. package/src/modes/components/hook-input.ts +87 -0
  602. package/src/modes/components/hook-message.ts +67 -0
  603. package/src/modes/components/hook-selector.ts +659 -0
  604. package/src/modes/components/index.ts +38 -0
  605. package/src/modes/components/keybinding-hints.ts +65 -0
  606. package/src/modes/components/late-diagnostics-message.ts +60 -0
  607. package/src/modes/components/login-dialog.ts +164 -0
  608. package/src/modes/components/logout-account-selector.ts +130 -0
  609. package/src/modes/components/mcp-add-wizard.ts +1360 -0
  610. package/src/modes/components/message-frame.ts +92 -0
  611. package/src/modes/components/model-selector.ts +1315 -0
  612. package/src/modes/components/oauth-selector.ts +457 -0
  613. package/src/modes/components/omfg-panel.ts +141 -0
  614. package/src/modes/components/overlay-box.ts +109 -0
  615. package/src/modes/components/plan-review-overlay.ts +847 -0
  616. package/src/modes/components/plan-toc.ts +138 -0
  617. package/src/modes/components/plugin-selector.ts +95 -0
  618. package/src/modes/components/plugin-settings.ts +739 -0
  619. package/src/modes/components/queue-mode-selector.ts +56 -0
  620. package/src/modes/components/read-tool-group.ts +676 -0
  621. package/src/modes/components/reset-usage-selector.ts +161 -0
  622. package/src/modes/components/segment-track.ts +89 -0
  623. package/src/modes/components/session-selector.ts +631 -0
  624. package/src/modes/components/settings-defs.ts +225 -0
  625. package/src/modes/components/settings-selector.ts +1095 -0
  626. package/src/modes/components/show-images-selector.ts +45 -0
  627. package/src/modes/components/skill-message.ts +110 -0
  628. package/src/modes/components/snapcompact-shape-preview-doc.md +18 -0
  629. package/src/modes/components/snapcompact-shape-preview.ts +192 -0
  630. package/src/modes/components/status-line/component.ts +1001 -0
  631. package/src/modes/components/status-line/context-thresholds.ts +78 -0
  632. package/src/modes/components/status-line/git-utils.ts +42 -0
  633. package/src/modes/components/status-line/index.ts +5 -0
  634. package/src/modes/components/status-line/presets.ts +106 -0
  635. package/src/modes/components/status-line/segments.ts +616 -0
  636. package/src/modes/components/status-line/separators.ts +55 -0
  637. package/src/modes/components/status-line/token-rate.ts +66 -0
  638. package/src/modes/components/status-line/types.ts +124 -0
  639. package/src/modes/components/theme-selector.ts +63 -0
  640. package/src/modes/components/thinking-selector.ts +52 -0
  641. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  642. package/src/modes/components/tips.txt +24 -0
  643. package/src/modes/components/todo-reminder.ts +39 -0
  644. package/src/modes/components/tool-execution.ts +1165 -0
  645. package/src/modes/components/transcript-container.ts +806 -0
  646. package/src/modes/components/tree-selector.ts +994 -0
  647. package/src/modes/components/ttsr-notification.ts +123 -0
  648. package/src/modes/components/usage-row.ts +18 -0
  649. package/src/modes/components/user-message-selector.ts +227 -0
  650. package/src/modes/components/user-message.ts +68 -0
  651. package/src/modes/components/visual-truncate.ts +63 -0
  652. package/src/modes/components/welcome.ts +581 -0
  653. package/src/modes/controllers/btw-controller.ts +173 -0
  654. package/src/modes/controllers/command-controller-shared.ts +109 -0
  655. package/src/modes/controllers/command-controller.ts +1653 -0
  656. package/src/modes/controllers/event-controller.ts +1153 -0
  657. package/src/modes/controllers/extension-ui-controller.ts +893 -0
  658. package/src/modes/controllers/input-controller.ts +1627 -0
  659. package/src/modes/controllers/mcp-command-controller.ts +2162 -0
  660. package/src/modes/controllers/omfg-controller.ts +283 -0
  661. package/src/modes/controllers/omfg-rule.ts +647 -0
  662. package/src/modes/controllers/selector-controller.ts +1285 -0
  663. package/src/modes/controllers/session-focus-controller.ts +112 -0
  664. package/src/modes/controllers/ssh-command-controller.ts +384 -0
  665. package/src/modes/controllers/streaming-reveal.ts +295 -0
  666. package/src/modes/controllers/tan-command-controller.ts +190 -0
  667. package/src/modes/controllers/todo-command-controller.ts +485 -0
  668. package/src/modes/controllers/tool-args-reveal.ts +174 -0
  669. package/src/modes/data/emojis.json +1 -0
  670. package/src/modes/emoji-autocomplete.ts +285 -0
  671. package/src/modes/gradient-highlight.ts +99 -0
  672. package/src/modes/image-references.ts +137 -0
  673. package/src/modes/index.ts +17 -0
  674. package/src/modes/interactive-mode.ts +3940 -0
  675. package/src/modes/internal-url-autocomplete.ts +143 -0
  676. package/src/modes/loop-limit.ts +192 -0
  677. package/src/modes/magic-keywords.ts +42 -0
  678. package/src/modes/markdown-prose.ts +247 -0
  679. package/src/modes/oauth-manual-input.ts +69 -0
  680. package/src/modes/orchestrate.ts +42 -0
  681. package/src/modes/print-mode.ts +130 -0
  682. package/src/modes/prompt-action-autocomplete.ts +260 -0
  683. package/src/modes/rpc/host-tools.ts +186 -0
  684. package/src/modes/rpc/host-uris.ts +235 -0
  685. package/src/modes/rpc/rpc-client.ts +995 -0
  686. package/src/modes/rpc/rpc-mode.ts +1156 -0
  687. package/src/modes/rpc/rpc-subagents.ts +265 -0
  688. package/src/modes/rpc/rpc-types.ts +487 -0
  689. package/src/modes/runtime-init.ts +142 -0
  690. package/src/modes/session-observer-registry.ts +215 -0
  691. package/src/modes/setup-version.ts +11 -0
  692. package/src/modes/setup-wizard/index.ts +101 -0
  693. package/src/modes/setup-wizard/lazy.ts +16 -0
  694. package/src/modes/setup-wizard/scenes/glyph.ts +114 -0
  695. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  696. package/src/modes/setup-wizard/scenes/providers.ts +103 -0
  697. package/src/modes/setup-wizard/scenes/sign-in.ts +286 -0
  698. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  699. package/src/modes/setup-wizard/scenes/theme.ts +326 -0
  700. package/src/modes/setup-wizard/scenes/types.ts +57 -0
  701. package/src/modes/setup-wizard/scenes/web-search.ts +145 -0
  702. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  703. package/src/modes/setup-wizard/wizard-overlay.ts +334 -0
  704. package/src/modes/shared.ts +49 -0
  705. package/src/modes/theme/dark.json +95 -0
  706. package/src/modes/theme/defaults/alabaster.json +93 -0
  707. package/src/modes/theme/defaults/amethyst.json +96 -0
  708. package/src/modes/theme/defaults/anthracite.json +93 -0
  709. package/src/modes/theme/defaults/basalt.json +91 -0
  710. package/src/modes/theme/defaults/birch.json +95 -0
  711. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  712. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  713. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  714. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  715. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  716. package/src/modes/theme/defaults/dark-copper.json +95 -0
  717. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  718. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  719. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  720. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  721. package/src/modes/theme/defaults/dark-ember.json +95 -0
  722. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  723. package/src/modes/theme/defaults/dark-forest.json +96 -0
  724. package/src/modes/theme/defaults/dark-github.json +105 -0
  725. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  726. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  727. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  728. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  729. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  730. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  731. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  732. package/src/modes/theme/defaults/dark-nord.json +97 -0
  733. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  734. package/src/modes/theme/defaults/dark-one.json +100 -0
  735. package/src/modes/theme/defaults/dark-poimandres.json +142 -0
  736. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  737. package/src/modes/theme/defaults/dark-reef.json +91 -0
  738. package/src/modes/theme/defaults/dark-retro.json +92 -0
  739. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  740. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  741. package/src/modes/theme/defaults/dark-slate.json +95 -0
  742. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  743. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  744. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  745. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  746. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  747. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  748. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  749. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  750. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  751. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  752. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  753. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  754. package/src/modes/theme/defaults/graphite.json +92 -0
  755. package/src/modes/theme/defaults/index.ts +199 -0
  756. package/src/modes/theme/defaults/light-arctic.json +107 -0
  757. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  758. package/src/modes/theme/defaults/light-canyon.json +91 -0
  759. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  760. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  761. package/src/modes/theme/defaults/light-coral.json +95 -0
  762. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  763. package/src/modes/theme/defaults/light-dawn.json +90 -0
  764. package/src/modes/theme/defaults/light-dunes.json +91 -0
  765. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  766. package/src/modes/theme/defaults/light-forest.json +100 -0
  767. package/src/modes/theme/defaults/light-frost.json +95 -0
  768. package/src/modes/theme/defaults/light-github.json +115 -0
  769. package/src/modes/theme/defaults/light-glacier.json +91 -0
  770. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  771. package/src/modes/theme/defaults/light-haze.json +90 -0
  772. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  773. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  774. package/src/modes/theme/defaults/light-lavender.json +95 -0
  775. package/src/modes/theme/defaults/light-meadow.json +91 -0
  776. package/src/modes/theme/defaults/light-mint.json +95 -0
  777. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  778. package/src/modes/theme/defaults/light-ocean.json +99 -0
  779. package/src/modes/theme/defaults/light-one.json +99 -0
  780. package/src/modes/theme/defaults/light-opal.json +91 -0
  781. package/src/modes/theme/defaults/light-orchard.json +91 -0
  782. package/src/modes/theme/defaults/light-paper.json +95 -0
  783. package/src/modes/theme/defaults/light-poimandres.json +142 -0
  784. package/src/modes/theme/defaults/light-prism.json +90 -0
  785. package/src/modes/theme/defaults/light-retro.json +98 -0
  786. package/src/modes/theme/defaults/light-sand.json +95 -0
  787. package/src/modes/theme/defaults/light-savanna.json +91 -0
  788. package/src/modes/theme/defaults/light-solarized.json +102 -0
  789. package/src/modes/theme/defaults/light-soleil.json +90 -0
  790. package/src/modes/theme/defaults/light-sunset.json +99 -0
  791. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  792. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  793. package/src/modes/theme/defaults/light-wetland.json +91 -0
  794. package/src/modes/theme/defaults/light-zenith.json +89 -0
  795. package/src/modes/theme/defaults/limestone.json +94 -0
  796. package/src/modes/theme/defaults/mahogany.json +97 -0
  797. package/src/modes/theme/defaults/marble.json +93 -0
  798. package/src/modes/theme/defaults/obsidian.json +91 -0
  799. package/src/modes/theme/defaults/onyx.json +91 -0
  800. package/src/modes/theme/defaults/pearl.json +93 -0
  801. package/src/modes/theme/defaults/porcelain.json +91 -0
  802. package/src/modes/theme/defaults/quartz.json +96 -0
  803. package/src/modes/theme/defaults/sandstone.json +95 -0
  804. package/src/modes/theme/defaults/titanium.json +90 -0
  805. package/src/modes/theme/light.json +93 -0
  806. package/src/modes/theme/mermaid-cache.ts +92 -0
  807. package/src/modes/theme/shimmer.ts +235 -0
  808. package/src/modes/theme/theme-schema.json +459 -0
  809. package/src/modes/theme/theme.ts +2915 -0
  810. package/src/modes/turn-budget.ts +31 -0
  811. package/src/modes/types.ts +406 -0
  812. package/src/modes/ultrathink.ts +41 -0
  813. package/src/modes/utils/context-usage.ts +432 -0
  814. package/src/modes/utils/copy-targets.ts +360 -0
  815. package/src/modes/utils/hotkeys-markdown.ts +62 -0
  816. package/src/modes/utils/keybinding-matchers.ts +51 -0
  817. package/src/modes/utils/tools-markdown.ts +27 -0
  818. package/src/modes/utils/ui-helpers.ts +886 -0
  819. package/src/modes/workflow.ts +42 -0
  820. package/src/plan-mode/approved-plan.ts +186 -0
  821. package/src/plan-mode/plan-handoff.ts +37 -0
  822. package/src/plan-mode/plan-protection.ts +31 -0
  823. package/src/plan-mode/state.ts +6 -0
  824. package/src/priority.json +45 -0
  825. package/src/prompts/advisor/advise-tool.md +3 -0
  826. package/src/prompts/advisor/system.md +113 -0
  827. package/src/prompts/agents/designer.md +74 -0
  828. package/src/prompts/agents/explore.md +58 -0
  829. package/src/prompts/agents/frontmatter.md +11 -0
  830. package/src/prompts/agents/init.md +33 -0
  831. package/src/prompts/agents/librarian.md +119 -0
  832. package/src/prompts/agents/oracle.md +54 -0
  833. package/src/prompts/agents/plan.md +48 -0
  834. package/src/prompts/agents/reviewer.md +139 -0
  835. package/src/prompts/agents/task.md +17 -0
  836. package/src/prompts/bench.md +12 -0
  837. package/src/prompts/ci-green-request.md +36 -0
  838. package/src/prompts/dry-balance-bench.md +8 -0
  839. package/src/prompts/goals/goal-budget-limit.md +16 -0
  840. package/src/prompts/goals/goal-continuation.md +28 -0
  841. package/src/prompts/goals/goal-mode-active.md +23 -0
  842. package/src/prompts/goals/guided-goal-interview.md +8 -0
  843. package/src/prompts/goals/guided-goal-system.md +12 -0
  844. package/src/prompts/low-end/system.md +47 -0
  845. package/src/prompts/memories/consolidation.md +30 -0
  846. package/src/prompts/memories/consolidation_system.md +4 -0
  847. package/src/prompts/memories/read-path.md +17 -0
  848. package/src/prompts/memories/stage_one_input.md +6 -0
  849. package/src/prompts/memories/stage_one_system.md +21 -0
  850. package/src/prompts/review-custom-request.md +22 -0
  851. package/src/prompts/review-headless-request.md +16 -0
  852. package/src/prompts/review-request.md +69 -0
  853. package/src/prompts/steering/user-interjection.md +9 -0
  854. package/src/prompts/system/agent-creation-architect.md +50 -0
  855. package/src/prompts/system/agent-creation-user.md +6 -0
  856. package/src/prompts/system/auto-continue.md +1 -0
  857. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  858. package/src/prompts/system/auto-thinking-difficulty.md +12 -0
  859. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  860. package/src/prompts/system/autolearn-guidance.md +7 -0
  861. package/src/prompts/system/autolearn-nudge.md +3 -0
  862. package/src/prompts/system/background-tan-dispatch.md +8 -0
  863. package/src/prompts/system/btw-user.md +8 -0
  864. package/src/prompts/system/commit-message-system.md +14 -0
  865. package/src/prompts/system/custom-system-prompt.md +64 -0
  866. package/src/prompts/system/eager-task.md +7 -0
  867. package/src/prompts/system/eager-todo.md +18 -0
  868. package/src/prompts/system/empty-stop-retry.md +4 -0
  869. package/src/prompts/system/irc-autoreply.md +6 -0
  870. package/src/prompts/system/irc-incoming.md +7 -0
  871. package/src/prompts/system/manual-continue.md +7 -0
  872. package/src/prompts/system/memory-consolidation-system.md +8 -0
  873. package/src/prompts/system/memory-extraction-system.md +26 -0
  874. package/src/prompts/system/omfg-user.md +50 -0
  875. package/src/prompts/system/orchestrate-notice.md +40 -0
  876. package/src/prompts/system/personalities/default.md +18 -0
  877. package/src/prompts/system/personalities/friendly.md +17 -0
  878. package/src/prompts/system/personalities/pragmatic.md +15 -0
  879. package/src/prompts/system/plan-mode-active.md +109 -0
  880. package/src/prompts/system/plan-mode-approved.md +25 -0
  881. package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
  882. package/src/prompts/system/plan-mode-reference.md +11 -0
  883. package/src/prompts/system/plan-mode-subagent.md +33 -0
  884. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  885. package/src/prompts/system/project-prompt.md +52 -0
  886. package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
  887. package/src/prompts/system/snapcompact-context-stub.md +1 -0
  888. package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
  889. package/src/prompts/system/snapcompact-system-stub.md +1 -0
  890. package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
  891. package/src/prompts/system/subagent-system-prompt.md +71 -0
  892. package/src/prompts/system/subagent-user-prompt.md +3 -0
  893. package/src/prompts/system/subagent-yield-reminder.md +12 -0
  894. package/src/prompts/system/system-prompt.md +251 -0
  895. package/src/prompts/system/tiny-title-system.md +8 -0
  896. package/src/prompts/system/title-marker-instruction.md +1 -0
  897. package/src/prompts/system/title-system-marker.md +16 -0
  898. package/src/prompts/system/title-system.md +16 -0
  899. package/src/prompts/system/ttsr-interrupt.md +7 -0
  900. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  901. package/src/prompts/system/ultrathink-notice.md +3 -0
  902. package/src/prompts/system/unexpected-stop-classifier.md +17 -0
  903. package/src/prompts/system/unexpected-stop-retry.md +4 -0
  904. package/src/prompts/system/web-search.md +25 -0
  905. package/src/prompts/system/workflow-notice.md +70 -0
  906. package/src/prompts/tools/apply-patch.md +65 -0
  907. package/src/prompts/tools/ask.md +22 -0
  908. package/src/prompts/tools/ast-edit.md +22 -0
  909. package/src/prompts/tools/ast-grep.md +25 -0
  910. package/src/prompts/tools/async-result.md +8 -0
  911. package/src/prompts/tools/bash.md +45 -0
  912. package/src/prompts/tools/browser.md +42 -0
  913. package/src/prompts/tools/checkpoint.md +15 -0
  914. package/src/prompts/tools/debug.md +17 -0
  915. package/src/prompts/tools/eval.md +70 -0
  916. package/src/prompts/tools/find.md +19 -0
  917. package/src/prompts/tools/github.md +17 -0
  918. package/src/prompts/tools/goal.md +11 -0
  919. package/src/prompts/tools/image-attachment-describe-system.md +8 -0
  920. package/src/prompts/tools/image-attachment-describe.md +10 -0
  921. package/src/prompts/tools/image-gen.md +7 -0
  922. package/src/prompts/tools/inspect-image-system.md +20 -0
  923. package/src/prompts/tools/inspect-image.md +22 -0
  924. package/src/prompts/tools/irc.md +33 -0
  925. package/src/prompts/tools/job.md +17 -0
  926. package/src/prompts/tools/learn.md +7 -0
  927. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  928. package/src/prompts/tools/lsp.md +39 -0
  929. package/src/prompts/tools/manage-skill.md +9 -0
  930. package/src/prompts/tools/memory-edit.md +8 -0
  931. package/src/prompts/tools/patch.md +57 -0
  932. package/src/prompts/tools/read.md +76 -0
  933. package/src/prompts/tools/recall.md +5 -0
  934. package/src/prompts/tools/reflect.md +5 -0
  935. package/src/prompts/tools/replace.md +29 -0
  936. package/src/prompts/tools/resolve.md +4 -0
  937. package/src/prompts/tools/retain.md +6 -0
  938. package/src/prompts/tools/rewind.md +13 -0
  939. package/src/prompts/tools/search-tool-bm25.md +32 -0
  940. package/src/prompts/tools/search.md +22 -0
  941. package/src/prompts/tools/ssh.md +22 -0
  942. package/src/prompts/tools/task-summary.md +17 -0
  943. package/src/prompts/tools/task.md +91 -0
  944. package/src/prompts/tools/todo.md +39 -0
  945. package/src/prompts/tools/web-search.md +6 -0
  946. package/src/prompts/tools/write.md +14 -0
  947. package/src/registry/agent-lifecycle.ts +270 -0
  948. package/src/registry/agent-registry.ts +190 -0
  949. package/src/sdk.ts +2919 -0
  950. package/src/secrets/index.ts +123 -0
  951. package/src/secrets/obfuscator.ts +298 -0
  952. package/src/secrets/regex.ts +21 -0
  953. package/src/session/agent-session.ts +12539 -0
  954. package/src/session/agent-storage.ts +478 -0
  955. package/src/session/artifacts.ts +153 -0
  956. package/src/session/auth-broker-config.ts +92 -0
  957. package/src/session/auth-storage.ts +24 -0
  958. package/src/session/blob-store.ts +255 -0
  959. package/src/session/client-bridge.ts +85 -0
  960. package/src/session/codex-auto-reset.ts +202 -0
  961. package/src/session/compact-modes.ts +105 -0
  962. package/src/session/history-storage.ts +361 -0
  963. package/src/session/indexed-session-storage.ts +427 -0
  964. package/src/session/messages.ts +546 -0
  965. package/src/session/redis-session-storage.ts +170 -0
  966. package/src/session/session-context.ts +399 -0
  967. package/src/session/session-dump-format.ts +216 -0
  968. package/src/session/session-entries.ts +198 -0
  969. package/src/session/session-history-format.ts +308 -0
  970. package/src/session/session-listing.ts +588 -0
  971. package/src/session/session-loader.ts +93 -0
  972. package/src/session/session-manager.ts +1748 -0
  973. package/src/session/session-migrations.ts +78 -0
  974. package/src/session/session-paths.ts +193 -0
  975. package/src/session/session-persistence.ts +147 -0
  976. package/src/session/session-storage.ts +590 -0
  977. package/src/session/shake-types.ts +43 -0
  978. package/src/session/snapcompact-inline.ts +542 -0
  979. package/src/session/snapcompact-savings-journal.ts +113 -0
  980. package/src/session/sql-session-storage.ts +314 -0
  981. package/src/session/streaming-output.ts +1330 -0
  982. package/src/session/tool-choice-queue.ts +290 -0
  983. package/src/session/unexpected-stop-classifier.ts +129 -0
  984. package/src/session/yield-queue.ts +183 -0
  985. package/src/slash-commands/acp-builtins.ts +70 -0
  986. package/src/slash-commands/available-commands.ts +105 -0
  987. package/src/slash-commands/builtin-registry.ts +2332 -0
  988. package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
  989. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  990. package/src/slash-commands/helpers/context-report.ts +66 -0
  991. package/src/slash-commands/helpers/format.ts +46 -0
  992. package/src/slash-commands/helpers/logout.ts +88 -0
  993. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  994. package/src/slash-commands/helpers/mcp.ts +532 -0
  995. package/src/slash-commands/helpers/parse.ts +85 -0
  996. package/src/slash-commands/helpers/reset-usage.ts +66 -0
  997. package/src/slash-commands/helpers/ssh.ts +195 -0
  998. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  999. package/src/slash-commands/helpers/todo.ts +279 -0
  1000. package/src/slash-commands/helpers/usage-report.ts +128 -0
  1001. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  1002. package/src/slash-commands/types.ts +135 -0
  1003. package/src/ssh/config-writer.ts +183 -0
  1004. package/src/ssh/connection-manager.ts +510 -0
  1005. package/src/ssh/ssh-executor.ts +189 -0
  1006. package/src/ssh/sshfs-mount.ts +140 -0
  1007. package/src/ssh/utils.ts +8 -0
  1008. package/src/startup-splash.ts +19 -0
  1009. package/src/stt/asr-client.ts +521 -0
  1010. package/src/stt/asr-protocol.ts +65 -0
  1011. package/src/stt/asr-worker.ts +790 -0
  1012. package/src/stt/downloader.ts +138 -0
  1013. package/src/stt/endpointer.ts +259 -0
  1014. package/src/stt/index.ts +7 -0
  1015. package/src/stt/models.ts +150 -0
  1016. package/src/stt/recorder.ts +538 -0
  1017. package/src/stt/stt-controller.ts +380 -0
  1018. package/src/stt/transcriber.ts +60 -0
  1019. package/src/stt/wav.ts +173 -0
  1020. package/src/system-prompt.ts +709 -0
  1021. package/src/task/agents.ts +166 -0
  1022. package/src/task/commands.ts +132 -0
  1023. package/src/task/discovery.ts +122 -0
  1024. package/src/task/executor.ts +2356 -0
  1025. package/src/task/index.ts +1580 -0
  1026. package/src/task/name-generator.ts +1577 -0
  1027. package/src/task/omp-command.ts +26 -0
  1028. package/src/task/output-manager.ts +93 -0
  1029. package/src/task/parallel.ts +116 -0
  1030. package/src/task/persisted-revive.ts +128 -0
  1031. package/src/task/render.ts +1558 -0
  1032. package/src/task/repair-args.ts +129 -0
  1033. package/src/task/subprocess-tool-registry.ts +88 -0
  1034. package/src/task/types.ts +401 -0
  1035. package/src/task/worktree.ts +514 -0
  1036. package/src/telemetry-export.ts +144 -0
  1037. package/src/thinking.ts +187 -0
  1038. package/src/tiny/device.ts +111 -0
  1039. package/src/tiny/dtype.ts +101 -0
  1040. package/src/tiny/models.ts +252 -0
  1041. package/src/tiny/text.ts +169 -0
  1042. package/src/tiny/title-client.ts +538 -0
  1043. package/src/tiny/title-protocol.ts +56 -0
  1044. package/src/tiny/worker.ts +491 -0
  1045. package/src/tool-discovery/mode.ts +24 -0
  1046. package/src/tool-discovery/tool-index.ts +271 -0
  1047. package/src/tools/__tests__/json-tree.test.ts +35 -0
  1048. package/src/tools/approval.ts +189 -0
  1049. package/src/tools/ask.ts +977 -0
  1050. package/src/tools/ast-edit.ts +700 -0
  1051. package/src/tools/ast-grep.ts +483 -0
  1052. package/src/tools/auto-generated-guard.ts +322 -0
  1053. package/src/tools/bash-command-fixup.ts +37 -0
  1054. package/src/tools/bash-interactive.ts +408 -0
  1055. package/src/tools/bash-interceptor.ts +67 -0
  1056. package/src/tools/bash-pty-selection.ts +14 -0
  1057. package/src/tools/bash-skill-urls.ts +248 -0
  1058. package/src/tools/bash.ts +1405 -0
  1059. package/src/tools/browser/attach.ts +194 -0
  1060. package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
  1061. package/src/tools/browser/cmux/rpc.ts +156 -0
  1062. package/src/tools/browser/cmux/socket-client.ts +309 -0
  1063. package/src/tools/browser/launch.ts +673 -0
  1064. package/src/tools/browser/readable.ts +112 -0
  1065. package/src/tools/browser/registry.ts +241 -0
  1066. package/src/tools/browser/render.ts +221 -0
  1067. package/src/tools/browser/tab-protocol.ts +107 -0
  1068. package/src/tools/browser/tab-supervisor.ts +799 -0
  1069. package/src/tools/browser/tab-worker-entry.ts +29 -0
  1070. package/src/tools/browser/tab-worker.ts +1226 -0
  1071. package/src/tools/browser.ts +403 -0
  1072. package/src/tools/builtin-names.ts +34 -0
  1073. package/src/tools/checkpoint.ts +136 -0
  1074. package/src/tools/conflict-detect.ts +718 -0
  1075. package/src/tools/context.ts +39 -0
  1076. package/src/tools/debug.ts +1087 -0
  1077. package/src/tools/eval-backends.ts +27 -0
  1078. package/src/tools/eval-render.ts +762 -0
  1079. package/src/tools/eval.ts +600 -0
  1080. package/src/tools/fetch.ts +1902 -0
  1081. package/src/tools/file-recorder.ts +35 -0
  1082. package/src/tools/find.ts +629 -0
  1083. package/src/tools/fs-cache-invalidation.ts +28 -0
  1084. package/src/tools/gh-cache-invalidation.ts +255 -0
  1085. package/src/tools/gh-format.ts +12 -0
  1086. package/src/tools/gh-renderer.ts +481 -0
  1087. package/src/tools/gh.ts +3752 -0
  1088. package/src/tools/github-cache.ts +663 -0
  1089. package/src/tools/grouped-file-output.ts +210 -0
  1090. package/src/tools/image-gen.ts +1586 -0
  1091. package/src/tools/index.ts +649 -0
  1092. package/src/tools/inspect-image-renderer.ts +132 -0
  1093. package/src/tools/inspect-image.ts +260 -0
  1094. package/src/tools/irc.ts +788 -0
  1095. package/src/tools/job.ts +612 -0
  1096. package/src/tools/json-tree.ts +260 -0
  1097. package/src/tools/jtd-to-json-schema.ts +219 -0
  1098. package/src/tools/jtd-to-typescript.ts +136 -0
  1099. package/src/tools/jtd-utils.ts +102 -0
  1100. package/src/tools/learn.ts +141 -0
  1101. package/src/tools/list-limit.ts +40 -0
  1102. package/src/tools/manage-skill.ts +100 -0
  1103. package/src/tools/match-line-format.ts +20 -0
  1104. package/src/tools/memory-edit.ts +59 -0
  1105. package/src/tools/memory-recall.ts +102 -0
  1106. package/src/tools/memory-reflect.ts +88 -0
  1107. package/src/tools/memory-render.ts +202 -0
  1108. package/src/tools/memory-retain.ts +89 -0
  1109. package/src/tools/output-meta.ts +768 -0
  1110. package/src/tools/output-schema-validator.ts +132 -0
  1111. package/src/tools/path-utils.ts +1116 -0
  1112. package/src/tools/plan-mode-guard.ts +142 -0
  1113. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  1114. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  1115. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  1116. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  1117. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  1118. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  1119. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  1120. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  1121. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  1122. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  1123. package/src/tools/puppeteer/10_stealth_plugins.txt +208 -0
  1124. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  1125. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  1126. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  1127. package/src/tools/read.ts +3124 -0
  1128. package/src/tools/render-utils.ts +895 -0
  1129. package/src/tools/renderers.ts +86 -0
  1130. package/src/tools/report-tool-issue.ts +530 -0
  1131. package/src/tools/resolve.ts +302 -0
  1132. package/src/tools/review.ts +251 -0
  1133. package/src/tools/search-tool-bm25.ts +351 -0
  1134. package/src/tools/search.ts +1583 -0
  1135. package/src/tools/sqlite-reader.ts +828 -0
  1136. package/src/tools/ssh.ts +369 -0
  1137. package/src/tools/todo.ts +938 -0
  1138. package/src/tools/tool-errors.ts +62 -0
  1139. package/src/tools/tool-result.ts +102 -0
  1140. package/src/tools/tool-timeouts.ts +30 -0
  1141. package/src/tools/tts.ts +265 -0
  1142. package/src/tools/write.ts +1182 -0
  1143. package/src/tools/yield.ts +269 -0
  1144. package/src/tts/downloader.ts +64 -0
  1145. package/src/tts/index.ts +8 -0
  1146. package/src/tts/models.ts +137 -0
  1147. package/src/tts/player.ts +137 -0
  1148. package/src/tts/runtime.ts +21 -0
  1149. package/src/tts/streaming-player.ts +266 -0
  1150. package/src/tts/tts-client.ts +642 -0
  1151. package/src/tts/tts-protocol.ts +60 -0
  1152. package/src/tts/tts-worker.ts +505 -0
  1153. package/src/tts/vocalizer.ts +162 -0
  1154. package/src/tts/wav.ts +58 -0
  1155. package/src/tui/code-cell.ts +257 -0
  1156. package/src/tui/file-list.ts +55 -0
  1157. package/src/tui/hyperlink.ts +178 -0
  1158. package/src/tui/index.ts +13 -0
  1159. package/src/tui/output-block.ts +240 -0
  1160. package/src/tui/status-line.ts +54 -0
  1161. package/src/tui/tree-list.ts +133 -0
  1162. package/src/tui/types.ts +15 -0
  1163. package/src/tui/utils.ts +103 -0
  1164. package/src/tui/width-aware-text.ts +58 -0
  1165. package/src/utils/block-context.ts +312 -0
  1166. package/src/utils/changelog.ts +132 -0
  1167. package/src/utils/clipboard.ts +262 -0
  1168. package/src/utils/command-args.ts +76 -0
  1169. package/src/utils/commit-message-generator.ts +147 -0
  1170. package/src/utils/edit-mode.ts +41 -0
  1171. package/src/utils/enhanced-paste.ts +230 -0
  1172. package/src/utils/event-bus.ts +33 -0
  1173. package/src/utils/external-editor.ts +78 -0
  1174. package/src/utils/file-display-mode.ts +45 -0
  1175. package/src/utils/file-mentions.ts +284 -0
  1176. package/src/utils/git.ts +1838 -0
  1177. package/src/utils/image-loading.ts +231 -0
  1178. package/src/utils/image-resize.ts +309 -0
  1179. package/src/utils/image-vision-fallback.ts +197 -0
  1180. package/src/utils/ipc.ts +38 -0
  1181. package/src/utils/jj.ts +248 -0
  1182. package/src/utils/lang-from-path.ts +244 -0
  1183. package/src/utils/markit.ts +143 -0
  1184. package/src/utils/mupdf-wasm-embed.ts +12 -0
  1185. package/src/utils/open.ts +55 -0
  1186. package/src/utils/qrcode.ts +535 -0
  1187. package/src/utils/session-color.ts +142 -0
  1188. package/src/utils/shell-snapshot.ts +187 -0
  1189. package/src/utils/sixel.ts +69 -0
  1190. package/src/utils/thinking-display.ts +11 -0
  1191. package/src/utils/title-generator.ts +416 -0
  1192. package/src/utils/tool-choice.ts +49 -0
  1193. package/src/utils/tools-manager.ts +372 -0
  1194. package/src/utils/turndown.ts +83 -0
  1195. package/src/utils/zip.ts +1091 -0
  1196. package/src/web/kagi.ts +304 -0
  1197. package/src/web/parallel.ts +353 -0
  1198. package/src/web/scrapers/artifacthub.ts +207 -0
  1199. package/src/web/scrapers/arxiv.ts +83 -0
  1200. package/src/web/scrapers/aur.ts +162 -0
  1201. package/src/web/scrapers/biorxiv.ts +133 -0
  1202. package/src/web/scrapers/bluesky.ts +262 -0
  1203. package/src/web/scrapers/brew.ts +172 -0
  1204. package/src/web/scrapers/cheatsh.ts +68 -0
  1205. package/src/web/scrapers/chocolatey.ts +196 -0
  1206. package/src/web/scrapers/choosealicense.ts +95 -0
  1207. package/src/web/scrapers/cisa-kev.ts +87 -0
  1208. package/src/web/scrapers/clojars.ts +154 -0
  1209. package/src/web/scrapers/coingecko.ts +177 -0
  1210. package/src/web/scrapers/crates-io.ts +97 -0
  1211. package/src/web/scrapers/crossref.ts +136 -0
  1212. package/src/web/scrapers/devto.ts +147 -0
  1213. package/src/web/scrapers/discogs.ts +306 -0
  1214. package/src/web/scrapers/discourse.ts +197 -0
  1215. package/src/web/scrapers/dockerhub.ts +138 -0
  1216. package/src/web/scrapers/docs-rs.ts +652 -0
  1217. package/src/web/scrapers/fdroid.ts +134 -0
  1218. package/src/web/scrapers/firefox-addons.ts +191 -0
  1219. package/src/web/scrapers/flathub.ts +223 -0
  1220. package/src/web/scrapers/github-gist.ts +58 -0
  1221. package/src/web/scrapers/github.ts +800 -0
  1222. package/src/web/scrapers/gitlab.ts +401 -0
  1223. package/src/web/scrapers/go-pkg.ts +266 -0
  1224. package/src/web/scrapers/hackage.ts +140 -0
  1225. package/src/web/scrapers/hackernews.ts +189 -0
  1226. package/src/web/scrapers/hex.ts +105 -0
  1227. package/src/web/scrapers/huggingface.ts +321 -0
  1228. package/src/web/scrapers/iacr.ts +89 -0
  1229. package/src/web/scrapers/index.ts +252 -0
  1230. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1231. package/src/web/scrapers/lemmy.ts +203 -0
  1232. package/src/web/scrapers/lobsters.ts +175 -0
  1233. package/src/web/scrapers/mastodon.ts +292 -0
  1234. package/src/web/scrapers/maven.ts +138 -0
  1235. package/src/web/scrapers/mdn.ts +173 -0
  1236. package/src/web/scrapers/metacpan.ts +222 -0
  1237. package/src/web/scrapers/musicbrainz.ts +250 -0
  1238. package/src/web/scrapers/npm.ts +98 -0
  1239. package/src/web/scrapers/nuget.ts +183 -0
  1240. package/src/web/scrapers/nvd.ts +222 -0
  1241. package/src/web/scrapers/ollama.ts +239 -0
  1242. package/src/web/scrapers/open-vsx.ts +106 -0
  1243. package/src/web/scrapers/opencorporates.ts +292 -0
  1244. package/src/web/scrapers/openlibrary.ts +336 -0
  1245. package/src/web/scrapers/orcid.ts +286 -0
  1246. package/src/web/scrapers/osv.ts +176 -0
  1247. package/src/web/scrapers/packagist.ts +160 -0
  1248. package/src/web/scrapers/pub-dev.ts +143 -0
  1249. package/src/web/scrapers/pubmed.ts +211 -0
  1250. package/src/web/scrapers/pypi.ts +112 -0
  1251. package/src/web/scrapers/rawg.ts +110 -0
  1252. package/src/web/scrapers/readthedocs.ts +120 -0
  1253. package/src/web/scrapers/reddit.ts +95 -0
  1254. package/src/web/scrapers/repology.ts +251 -0
  1255. package/src/web/scrapers/rfc.ts +201 -0
  1256. package/src/web/scrapers/rubygems.ts +103 -0
  1257. package/src/web/scrapers/searchcode.ts +189 -0
  1258. package/src/web/scrapers/sec-edgar.ts +261 -0
  1259. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1260. package/src/web/scrapers/snapcraft.ts +187 -0
  1261. package/src/web/scrapers/sourcegraph.ts +336 -0
  1262. package/src/web/scrapers/spdx.ts +108 -0
  1263. package/src/web/scrapers/spotify.ts +198 -0
  1264. package/src/web/scrapers/stackoverflow.ts +120 -0
  1265. package/src/web/scrapers/terraform.ts +277 -0
  1266. package/src/web/scrapers/tldr.ts +47 -0
  1267. package/src/web/scrapers/twitter.ts +94 -0
  1268. package/src/web/scrapers/types.ts +354 -0
  1269. package/src/web/scrapers/utils.ts +109 -0
  1270. package/src/web/scrapers/vimeo.ts +133 -0
  1271. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1272. package/src/web/scrapers/w3c.ts +156 -0
  1273. package/src/web/scrapers/wikidata.ts +344 -0
  1274. package/src/web/scrapers/wikipedia.ts +84 -0
  1275. package/src/web/scrapers/youtube.ts +325 -0
  1276. package/src/web/search/index.ts +317 -0
  1277. package/src/web/search/provider.ts +169 -0
  1278. package/src/web/search/providers/anthropic.ts +343 -0
  1279. package/src/web/search/providers/base.ts +90 -0
  1280. package/src/web/search/providers/brave.ts +152 -0
  1281. package/src/web/search/providers/codex.ts +593 -0
  1282. package/src/web/search/providers/exa.ts +400 -0
  1283. package/src/web/search/providers/gemini.ts +518 -0
  1284. package/src/web/search/providers/jina.ts +111 -0
  1285. package/src/web/search/providers/kagi.ts +86 -0
  1286. package/src/web/search/providers/kimi.ts +196 -0
  1287. package/src/web/search/providers/parallel.ts +225 -0
  1288. package/src/web/search/providers/perplexity-auth.ts +133 -0
  1289. package/src/web/search/providers/perplexity.ts +866 -0
  1290. package/src/web/search/providers/searxng.ts +325 -0
  1291. package/src/web/search/providers/synthetic.ts +114 -0
  1292. package/src/web/search/providers/tavily.ts +176 -0
  1293. package/src/web/search/providers/utils.ts +128 -0
  1294. package/src/web/search/providers/zai.ts +333 -0
  1295. package/src/web/search/render.ts +262 -0
  1296. package/src/web/search/types.ts +462 -0
  1297. package/src/web/search/utils.ts +17 -0
  1298. package/src/workspace-tree.ts +326 -0
@@ -0,0 +1,2915 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
4
+ import type { Effort } from "@oh-my-pi/pi-ai";
5
+ import {
6
+ detectMacOSAppearance,
7
+ MacAppearanceObserver,
8
+ type HighlightColors as NativeHighlightColors,
9
+ highlightCode as nativeHighlightCode,
10
+ supportsLanguage as nativeSupportsLanguage,
11
+ } from "@oh-my-pi/pi-natives";
12
+ import type { EditorTheme, MarkdownTheme, SelectListTheme, SettingsListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
13
+ import { adjustHsv, colorLuma, getCustomThemesDir, isEnoent, logger, relativeLuminance } from "@oh-my-pi/pi-utils";
14
+ import { type } from "arktype";
15
+ import chalk from "chalk";
16
+ import { LRUCache } from "lru-cache/raw";
17
+ // Embed theme JSON files at build time
18
+ import darkThemeJson from "./dark.json" with { type: "json" };
19
+ import { defaultThemes } from "./defaults";
20
+ import lightThemeJson from "./light.json" with { type: "json" };
21
+ import { resolveMermaidAscii } 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
+ | "status.done"
47
+ // Navigation
48
+ | "nav.cursor"
49
+ | "nav.selected"
50
+ | "nav.expand"
51
+ | "nav.collapse"
52
+ | "nav.back"
53
+ // Tree Connectors
54
+ | "tree.branch"
55
+ | "tree.last"
56
+ | "tree.vertical"
57
+ | "tree.horizontal"
58
+ | "tree.hook"
59
+ // Box Drawing - Rounded
60
+ | "boxRound.topLeft"
61
+ | "boxRound.topRight"
62
+ | "boxRound.bottomLeft"
63
+ | "boxRound.bottomRight"
64
+ | "boxRound.horizontal"
65
+ | "boxRound.vertical"
66
+ // Box Drawing - Sharp
67
+ | "boxSharp.topLeft"
68
+ | "boxSharp.topRight"
69
+ | "boxSharp.bottomLeft"
70
+ | "boxSharp.bottomRight"
71
+ | "boxSharp.horizontal"
72
+ | "boxSharp.vertical"
73
+ | "boxSharp.cross"
74
+ | "boxSharp.teeDown"
75
+ | "boxSharp.teeUp"
76
+ | "boxSharp.teeRight"
77
+ | "boxSharp.teeLeft"
78
+ // Separators
79
+ | "sep.powerline"
80
+ | "sep.powerlineThin"
81
+ | "sep.powerlineLeft"
82
+ | "sep.powerlineRight"
83
+ | "sep.powerlineThinLeft"
84
+ | "sep.powerlineThinRight"
85
+ | "sep.block"
86
+ | "sep.space"
87
+ | "sep.asciiLeft"
88
+ | "sep.asciiRight"
89
+ | "sep.dot"
90
+ | "sep.slash"
91
+ | "sep.pipe"
92
+ // Icons
93
+ | "icon.model"
94
+ | "icon.plan"
95
+ | "icon.goal"
96
+ | "icon.pause"
97
+ | "icon.loop"
98
+ | "icon.folder"
99
+ | "icon.search"
100
+ | "icon.scratchFolder"
101
+ | "icon.file"
102
+ | "icon.git"
103
+ | "icon.branch"
104
+ | "icon.pr"
105
+ | "icon.tokens"
106
+ | "icon.context"
107
+ | "icon.cost"
108
+ | "icon.time"
109
+ | "icon.pi"
110
+ | "icon.ghost"
111
+ | "icon.agents"
112
+ | "icon.job"
113
+ | "icon.cache"
114
+ | "icon.cacheMiss"
115
+ | "icon.input"
116
+ | "icon.output"
117
+ | "icon.host"
118
+ | "icon.session"
119
+ | "icon.package"
120
+ | "icon.warning"
121
+ | "icon.rewind"
122
+ | "icon.auto"
123
+ | "icon.fast"
124
+ | "icon.extensionSkill"
125
+ | "icon.extensionTool"
126
+ | "icon.extensionSlashCommand"
127
+ | "icon.extensionMcp"
128
+ | "icon.extensionRule"
129
+ | "icon.extensionHook"
130
+ | "icon.extensionPrompt"
131
+ | "icon.extensionContextFile"
132
+ | "icon.extensionInstruction"
133
+ // STT
134
+ | "icon.mic"
135
+ // Compaction divider
136
+ | "icon.camera"
137
+ // Thinking Levels
138
+ | "thinking.minimal"
139
+ | "thinking.low"
140
+ | "thinking.medium"
141
+ | "thinking.high"
142
+ | "thinking.xhigh"
143
+ | "thinking.autoPending"
144
+ // Checkboxes
145
+ | "checkbox.checked"
146
+ | "checkbox.unchecked"
147
+ // Radio (single-choice)
148
+ | "radio.selected"
149
+ | "radio.unselected"
150
+ // Text Formatting
151
+ | "format.bullet"
152
+ | "format.dash"
153
+ | "format.bracketLeft"
154
+ | "format.bracketRight"
155
+ // Markdown-specific
156
+ | "md.quoteBorder"
157
+ | "md.hrChar"
158
+ | "md.bullet"
159
+ | "md.colorSwatch"
160
+ // Language/file type icons
161
+ | "lang.default"
162
+ | "lang.typescript"
163
+ | "lang.javascript"
164
+ | "lang.python"
165
+ | "lang.rust"
166
+ | "lang.go"
167
+ | "lang.java"
168
+ | "lang.c"
169
+ | "lang.cpp"
170
+ | "lang.csharp"
171
+ | "lang.ruby"
172
+ | "lang.php"
173
+ | "lang.swift"
174
+ | "lang.kotlin"
175
+ | "lang.shell"
176
+ | "lang.html"
177
+ | "lang.css"
178
+ | "lang.json"
179
+ | "lang.yaml"
180
+ | "lang.markdown"
181
+ | "lang.sql"
182
+ | "lang.docker"
183
+ | "lang.lua"
184
+ | "lang.text"
185
+ | "lang.env"
186
+ | "lang.toml"
187
+ | "lang.xml"
188
+ | "lang.ini"
189
+ | "lang.conf"
190
+ | "lang.log"
191
+ | "lang.csv"
192
+ | "lang.tsv"
193
+ | "lang.image"
194
+ | "lang.pdf"
195
+ | "lang.archive"
196
+ | "lang.binary"
197
+ // Settings tab icons
198
+ | "tab.appearance"
199
+ | "tab.model"
200
+ | "tab.interaction"
201
+ | "tab.context"
202
+ | "tab.files"
203
+ | "tab.shell"
204
+ | "tab.tools"
205
+ | "tab.memory"
206
+ | "tab.tasks"
207
+ | "tab.providers"
208
+ // Tool identity icons
209
+ | "tool.write"
210
+ | "tool.edit"
211
+ | "tool.bash"
212
+ | "tool.ssh"
213
+ | "tool.lsp"
214
+ | "tool.gh"
215
+ | "tool.webSearch"
216
+ | "tool.exa"
217
+ | "tool.browser"
218
+ | "tool.eval"
219
+ | "tool.debug"
220
+ | "tool.mcp"
221
+ | "tool.job"
222
+ | "tool.task"
223
+ | "tool.todo"
224
+ | "tool.memory"
225
+ | "tool.ask"
226
+ | "tool.resolve"
227
+ | "tool.review"
228
+ | "tool.inspectImage"
229
+ | "tool.goal"
230
+ | "tool.irc";
231
+
232
+ type SymbolMap = Record<SymbolKey, string>;
233
+
234
+ const UNICODE_SYMBOLS: SymbolMap = {
235
+ // Status
236
+ "status.success": "✔",
237
+ "status.error": "✘",
238
+ "status.warning": "⚠",
239
+ "status.info": "ⓘ",
240
+ "status.pending": "⏳",
241
+ "status.disabled": "⦸",
242
+ "status.enabled": "●",
243
+ "status.running": "⟳",
244
+ "status.shadowed": "◌",
245
+ "status.aborted": "⏹",
246
+ "status.done": "•",
247
+ // Navigation
248
+ "nav.cursor": "❯",
249
+ "nav.selected": "➤",
250
+ "nav.expand": "▸",
251
+ "nav.collapse": "▾",
252
+ "nav.back": "⟵",
253
+ // Tree
254
+ "tree.branch": "├─",
255
+ "tree.last": "└─",
256
+ "tree.vertical": "│",
257
+ "tree.horizontal": "─",
258
+ "tree.hook": "└",
259
+ // Box (rounded)
260
+ "boxRound.topLeft": "╭",
261
+ "boxRound.topRight": "╮",
262
+ "boxRound.bottomLeft": "╰",
263
+ "boxRound.bottomRight": "╯",
264
+ "boxRound.horizontal": "─",
265
+ "boxRound.vertical": "│",
266
+ // Box (sharp)
267
+ "boxSharp.topLeft": "┌",
268
+ "boxSharp.topRight": "┐",
269
+ "boxSharp.bottomLeft": "└",
270
+ "boxSharp.bottomRight": "┘",
271
+ "boxSharp.horizontal": "─",
272
+ "boxSharp.vertical": "│",
273
+ "boxSharp.cross": "┼",
274
+ "boxSharp.teeDown": "┬",
275
+ "boxSharp.teeUp": "┴",
276
+ "boxSharp.teeRight": "├",
277
+ "boxSharp.teeLeft": "┤",
278
+ // Separators (powerline-ish, but pure Unicode)
279
+ "sep.powerline": "▕",
280
+ "sep.powerlineThin": "┆",
281
+ "sep.powerlineLeft": "▶",
282
+ "sep.powerlineRight": "◀",
283
+ "sep.powerlineThinLeft": ">",
284
+ "sep.powerlineThinRight": "<",
285
+ "sep.block": "▌",
286
+ "sep.space": " ",
287
+ "sep.asciiLeft": ">",
288
+ "sep.asciiRight": "<",
289
+ "sep.dot": " · ",
290
+ "sep.slash": " / ",
291
+ "sep.pipe": " │ ",
292
+ // Icons
293
+ "icon.model": "⬢",
294
+ "icon.plan": "🗺",
295
+ "icon.goal": "🎯",
296
+ "icon.pause": "⏸",
297
+ "icon.loop": "↻",
298
+ "icon.folder": "📁",
299
+ "icon.search": "🔍",
300
+ "icon.scratchFolder": "🗑",
301
+ "icon.file": "📄",
302
+ "icon.git": "⎇",
303
+ "icon.branch": "⑂",
304
+ "icon.pr": "⤴",
305
+ "icon.tokens": "🪙",
306
+ "icon.context": "◫",
307
+ "icon.cost": "💲",
308
+ "icon.time": "⏱",
309
+ "icon.pi": "π",
310
+ "icon.ghost": "👻",
311
+ "icon.agents": "👥",
312
+ "icon.job": "⚙",
313
+ "icon.cache": "💾",
314
+ "icon.cacheMiss": "⊘",
315
+ "icon.input": "⤵",
316
+ "icon.output": "⤴",
317
+ "icon.host": "🖥",
318
+ "icon.session": "🆔",
319
+ "icon.package": "📦",
320
+ "icon.warning": "⚠",
321
+ "icon.rewind": "↶",
322
+ "icon.auto": "⟲",
323
+ "icon.fast": "⚡",
324
+ "icon.extensionSkill": "✦",
325
+ "icon.extensionTool": "🛠",
326
+ "icon.extensionSlashCommand": "⌘",
327
+ "icon.extensionMcp": "🔌",
328
+ "icon.extensionRule": "⚖",
329
+ "icon.extensionHook": "🪝",
330
+ "icon.extensionPrompt": "✎",
331
+ "icon.extensionContextFile": "📎",
332
+ "icon.extensionInstruction": "📘",
333
+ // STT
334
+ "icon.mic": "🎤",
335
+ // Compaction divider
336
+ "icon.camera": "📷",
337
+ // Thinking levels
338
+ "thinking.minimal": "◔ min",
339
+ "thinking.low": "◑ low",
340
+ "thinking.medium": "◒ med",
341
+ "thinking.high": "◕ high",
342
+ "thinking.xhigh": "◉ xhigh",
343
+ "thinking.autoPending": "⟳",
344
+ // Checkboxes
345
+ "checkbox.checked": "☑",
346
+ "checkbox.unchecked": "☐",
347
+ // Radio (single-choice)
348
+ "radio.selected": "◉",
349
+ "radio.unselected": "○",
350
+ // Formatting
351
+ "format.bullet": "•",
352
+ "format.dash": "—",
353
+ "format.bracketLeft": "⟦",
354
+ "format.bracketRight": "⟧",
355
+ // Markdown
356
+ "md.quoteBorder": "▏",
357
+ "md.hrChar": "─",
358
+ "md.bullet": "•",
359
+ "md.colorSwatch": "■",
360
+ // Language/file icons (emoji-centric, no Nerd Font required)
361
+ "lang.default": "⌘",
362
+ "lang.typescript": "🟦",
363
+ "lang.javascript": "🟨",
364
+ "lang.python": "🐍",
365
+ "lang.rust": "🦀",
366
+ "lang.go": "🐹",
367
+ "lang.java": "☕",
368
+ "lang.c": "Ⓒ",
369
+ "lang.cpp": "➕",
370
+ "lang.csharp": "♯",
371
+ "lang.ruby": "💎",
372
+ "lang.php": "🐘",
373
+ "lang.swift": "🕊",
374
+ "lang.kotlin": "🅺",
375
+ "lang.shell": "💻",
376
+ "lang.html": "🌐",
377
+ "lang.css": "🎨",
378
+ "lang.json": "🧾",
379
+ "lang.yaml": "📋",
380
+ "lang.markdown": "📝",
381
+ "lang.sql": "🗄",
382
+ "lang.docker": "🐳",
383
+ "lang.lua": "🌙",
384
+ "lang.text": "🗒",
385
+ "lang.env": "🔧",
386
+ "lang.toml": "🧾",
387
+ "lang.xml": "⟨⟩",
388
+ "lang.ini": "⚙",
389
+ "lang.conf": "⚙",
390
+ "lang.log": "📜",
391
+ "lang.csv": "📑",
392
+ "lang.tsv": "📑",
393
+ "lang.image": "🖼",
394
+ "lang.pdf": "📕",
395
+ "lang.archive": "🗜",
396
+ "lang.binary": "⚙",
397
+ // Settings tabs
398
+ "tab.appearance": "🎨",
399
+ "tab.model": "🤖",
400
+ "tab.interaction": "⌨",
401
+ "tab.context": "📋",
402
+ "tab.files": "📁",
403
+ "tab.shell": "💻",
404
+ "tab.tools": "🔧",
405
+ "tab.memory": "🧠",
406
+ "tab.tasks": "📦",
407
+ "tab.providers": "🌐",
408
+ // Tool identity icons (per-tool signature glyph on the success header)
409
+ "tool.write": "✎",
410
+ "tool.edit": "✎",
411
+ "tool.bash": "❯",
412
+ "tool.ssh": "⇄",
413
+ "tool.lsp": "💡",
414
+ "tool.gh": "⎇",
415
+ "tool.webSearch": "⌕",
416
+ "tool.exa": "🔭",
417
+ "tool.browser": "🌐",
418
+ "tool.eval": "▶",
419
+ "tool.debug": "🐞",
420
+ "tool.mcp": "🔌",
421
+ "tool.job": "⚙",
422
+ "tool.task": "⇶",
423
+ "tool.todo": "☑",
424
+ "tool.memory": "🧠",
425
+ "tool.ask": "?",
426
+ "tool.resolve": "✓",
427
+ "tool.review": "◉",
428
+ "tool.inspectImage": "🖼",
429
+ "tool.goal": "◎",
430
+ "tool.irc": "✉",
431
+ };
432
+
433
+ const NERD_SYMBOLS: SymbolMap = {
434
+ // Status Indicators
435
+ // pick:  | alt:   
436
+ "status.success": "\uf00c",
437
+ // pick:  | alt:   
438
+ "status.error": "\uf00d",
439
+ // pick:  | alt:  
440
+ "status.warning": "\uf12a",
441
+ // pick:  | alt: 
442
+ "status.info": "\uf129",
443
+ // pick:  | alt:   
444
+ "status.pending": "\uf254",
445
+ // pick:  | alt:  
446
+ "status.disabled": "\uf05e",
447
+ // pick:  | alt:  
448
+ "status.enabled": "\uf111",
449
+ // pick:  | alt:   
450
+ "status.running": "\uf110",
451
+ // pick: ◐ | alt: ◑ ◒ ◓ ◔
452
+ "status.shadowed": "◐",
453
+ // pick:  | alt:  
454
+ "status.aborted": "\uf04d",
455
+ // pick: • | alt: ● ·
456
+ "status.done": "•",
457
+ // Navigation
458
+ // pick:  | alt:  
459
+ "nav.cursor": "\uf054",
460
+ // pick:  | alt:  
461
+ "nav.selected": "\uf178",
462
+ // pick:  | alt:  
463
+ "nav.expand": "\uf0da",
464
+ // pick:  | alt:  
465
+ "nav.collapse": "\uf0d7",
466
+ // pick:  | alt:  
467
+ "nav.back": "\uf060",
468
+ // Tree Connectors (same as unicode)
469
+ // pick: ├─ | alt: ├╴ ├╌ ╠═ ┣━
470
+ "tree.branch": "├─",
471
+ // pick: └─ | alt: └╴ └╌ ╚═ ┗━
472
+ "tree.last": "└─",
473
+ // pick: │ | alt: ┃ ║ ▏ ▕
474
+ "tree.vertical": "│",
475
+ // pick: ─ | alt: ━ ═ ╌ ┄
476
+ "tree.horizontal": "─",
477
+ // pick: └ | alt: ╰ ⎿ ↳
478
+ "tree.hook": "└",
479
+ // Box Drawing - Rounded (same as unicode)
480
+ // pick: ╭ | alt: ┌ ┏ ╔
481
+ "boxRound.topLeft": "╭",
482
+ // pick: ╮ | alt: ┐ ┓ ╗
483
+ "boxRound.topRight": "╮",
484
+ // pick: ╰ | alt: └ ┗ ╚
485
+ "boxRound.bottomLeft": "╰",
486
+ // pick: ╯ | alt: ┘ ┛ ╝
487
+ "boxRound.bottomRight": "╯",
488
+ // pick: ─ | alt: ━ ═ ╌
489
+ "boxRound.horizontal": "─",
490
+ // pick: │ | alt: ┃ ║ ▏
491
+ "boxRound.vertical": "│",
492
+ // Box Drawing - Sharp (same as unicode)
493
+ // pick: ┌ | alt: ┏ ╭ ╔
494
+ "boxSharp.topLeft": "┌",
495
+ // pick: ┐ | alt: ┓ ╮ ╗
496
+ "boxSharp.topRight": "┐",
497
+ // pick: └ | alt: ┗ ╰ ╚
498
+ "boxSharp.bottomLeft": "└",
499
+ // pick: ┘ | alt: ┛ ╯ ╝
500
+ "boxSharp.bottomRight": "┘",
501
+ // pick: ─ | alt: ━ ═ ╌
502
+ "boxSharp.horizontal": "─",
503
+ // pick: │ | alt: ┃ ║ ▏
504
+ "boxSharp.vertical": "│",
505
+ // pick: ┼ | alt: ╋ ╬ ┿
506
+ "boxSharp.cross": "┼",
507
+ // pick: ┬ | alt: ╦ ┯ ┳
508
+ "boxSharp.teeDown": "┬",
509
+ // pick: ┴ | alt: ╩ ┷ ┻
510
+ "boxSharp.teeUp": "┴",
511
+ // pick: ├ | alt: ╠ ┝ ┣
512
+ "boxSharp.teeRight": "├",
513
+ // pick: ┤ | alt: ╣ ┥ ┫
514
+ "boxSharp.teeLeft": "┤",
515
+ // Separators - Nerd Font specific
516
+ // pick:  | alt:   
517
+ "sep.powerline": "\ue0b0",
518
+ // pick:  | alt:  
519
+ "sep.powerlineThin": "\ue0b1",
520
+ // pick:  | alt:  
521
+ "sep.powerlineLeft": "\ue0b0",
522
+ // pick:  | alt:  
523
+ "sep.powerlineRight": "\ue0b2",
524
+ // pick:  | alt: 
525
+ "sep.powerlineThinLeft": "\ue0b1",
526
+ // pick:  | alt: 
527
+ "sep.powerlineThinRight": "\ue0b3",
528
+ // pick: █ | alt: ▓ ▒ ░ ▉ ▌
529
+ "sep.block": "█",
530
+ // pick: space | alt: ␠ ·
531
+ "sep.space": " ",
532
+ // pick: > | alt: › » ▸
533
+ "sep.asciiLeft": ">",
534
+ // pick: < | alt: ‹ « ◂
535
+ "sep.asciiRight": "<",
536
+ // pick: · | alt: • ⋅
537
+ "sep.dot": " · ",
538
+ // pick:  | alt: / ∕ ⁄
539
+ "sep.slash": "\ue0bb",
540
+ // pick:  | alt: │ ┃ |
541
+ "sep.pipe": "\ue0b3",
542
+ // Icons - Nerd Font specific
543
+ // pick:  | alt:   ◆
544
+ "icon.model": "\uec19",
545
+ // pick:  | alt:  
546
+ "icon.plan": "\uf2d2",
547
+ // pick: (nf-fa-bullseye) | alt: (nf-md-target) ◎ ⌖
548
+ "icon.goal": "\uf140",
549
+ // pick: (nf-fa-pause) | alt: ⏸ ||
550
+ "icon.pause": "\uf04c",
551
+ // pick: ↻ | alt: ⟳
552
+ "icon.loop": "\uf021",
553
+ // pick:  | alt:  
554
+ "icon.folder": "\uf115",
555
+ "icon.search": "\uf002",
556
+ // pick: | alt:
557
+ "icon.scratchFolder": "\uf014",
558
+ // pick:  | alt:  
559
+ "icon.file": "\uf15b",
560
+ // pick:  | alt:  ⎇
561
+ "icon.git": "\uf1d3",
562
+ // pick:  | alt:  ⎇
563
+ "icon.branch": "\uf126",
564
+ // pick:  (nf-cod-git_pull_request) | alt: (nf-oct-git_pull_request)
565
+ "icon.pr": "\uea64",
566
+ // pick:  | alt: ⊛ ◍ 
567
+ "icon.tokens": "\ue26b",
568
+ // pick:  | alt: ◫ ▦
569
+ "icon.context": "\ue70f",
570
+ // pick:  | alt: $ ¢
571
+ "icon.cost": "\uf155",
572
+ // pick:  | alt: ◷ ◴
573
+ "icon.time": "\uf017",
574
+ // pick:  | alt: π ∏ ∑
575
+ "icon.pi": "\ue22c",
576
+ // pick: 󰊠 (nf-md-ghost) | alt: 👻
577
+ "icon.ghost": "\u{f02a0}",
578
+ // pick:  | alt: 
579
+ "icon.agents": "\uf0c0",
580
+ // pick: (nf-fa-gear) | alt: ⚙
581
+ "icon.job": "\uf013",
582
+ // pick:  | alt:  
583
+ "icon.cache": "\uf1c0",
584
+ // pick: (fa-ban) | alt: ⊘
585
+ "icon.cacheMiss": "\uf05e",
586
+ // pick:  | alt:  →
587
+ "icon.input": "\uf090",
588
+ // pick:  | alt:  →
589
+ "icon.output": "\uf08b",
590
+ // pick:  | alt:  
591
+ "icon.host": "\uf109",
592
+ // pick:  | alt:  
593
+ "icon.session": "\uf550",
594
+ // pick:  | alt: 
595
+ "icon.package": "\uf487",
596
+ // pick:  | alt:  
597
+ "icon.warning": "\uf071",
598
+ // pick:  | alt:  ↺
599
+ "icon.rewind": "\uf0e2",
600
+ // pick: 󰁨 | alt:   
601
+ "icon.auto": "\u{f0068}",
602
+ "icon.fast": "\uf0e7",
603
+ "icon.extensionSkill": "\uf0eb",
604
+ // pick:  | alt:  
605
+ "icon.extensionTool": "\uf0ad",
606
+ // pick:  | alt: 
607
+ "icon.extensionSlashCommand": "\uf120",
608
+ // pick:  | alt:  
609
+ "icon.extensionMcp": "\uf1e6",
610
+ // pick:  | alt:  
611
+ "icon.extensionRule": "\uf0e3",
612
+ // pick:  | alt: 
613
+ "icon.extensionHook": "\uf0c1",
614
+ // pick:  | alt:  
615
+ "icon.extensionPrompt": "\uf075",
616
+ // pick:  | alt:  
617
+ "icon.extensionContextFile": "\uf0f6",
618
+ // pick:  | alt:  
619
+ "icon.extensionInstruction": "\uf02d",
620
+ // STT - fa-microphone
621
+ "icon.mic": "\uf130",
622
+ // Compaction divider - fa-camera-retro
623
+ "icon.camera": "\uf083",
624
+ // Thinking Levels - emoji labels
625
+ // pick: 🤨 min | alt:  min  min
626
+ "thinking.minimal": "\u{F0E7} min",
627
+ // pick: 🤔 low | alt:  low  low
628
+ "thinking.low": "\u{F10C} low",
629
+ // pick: 🤓 med | alt:  med  med
630
+ "thinking.medium": "\u{F192} med",
631
+ // pick: 🤯 high | alt:  high  high
632
+ "thinking.high": "\u{F111} high",
633
+ // pick: 🧠 xhi | alt:  xhi  xhi
634
+ "thinking.xhigh": "\u{F06D} xhi",
635
+ // pick: (fa-circle-o-notch) | alt: 󰂼 (nf-md-cached) ⟳
636
+ "thinking.autoPending": "\uf1ce",
637
+ // Checkboxes
638
+ // pick:  | alt:  
639
+ "checkbox.checked": "\uf14a",
640
+ // pick:  | alt: 
641
+ "checkbox.unchecked": "\uf096",
642
+ // Radio (single-choice)
643
+ // pick: (fa-dot-circle-o) | alt: ◉
644
+ "radio.selected": "\uf192",
645
+ // pick: (fa-circle-o) | alt: ○
646
+ "radio.unselected": "\uf10c",
647
+ // pick:  | alt:   •
648
+ "format.bullet": "\uf111",
649
+ // pick: – | alt: — ― -
650
+ "format.dash": "–",
651
+ // pick: ⟨ | alt: [ ⟦
652
+ "format.bracketLeft": "⟨",
653
+ // pick: ⟩ | alt: ] ⟧
654
+ "format.bracketRight": "⟩",
655
+ // Markdown-specific
656
+ // pick: │ | alt: ┃ ║
657
+ "md.quoteBorder": "│",
658
+ // pick: ─ | alt: ━ ═
659
+ "md.hrChar": "─",
660
+ // pick:  | alt:  •
661
+ "md.bullet": "\uf111",
662
+ // pick: ■ | alt: (U+F096)
663
+ "md.colorSwatch": "■",
664
+ // Language icons (nerd font devicons)
665
+ "lang.default": "",
666
+ "lang.typescript": "\u{E628}",
667
+ "lang.javascript": "\u{E60C}",
668
+ "lang.python": "\u{E606}",
669
+ "lang.rust": "\u{E7A8}",
670
+ "lang.go": "\u{E627}",
671
+ "lang.java": "\u{E738}",
672
+ "lang.c": "\u{E61E}",
673
+ "lang.cpp": "\u{E61D}",
674
+ "lang.csharp": "\u{E7BC}",
675
+ "lang.ruby": "\u{E791}",
676
+ "lang.php": "\u{E608}",
677
+ "lang.swift": "\u{E755}",
678
+ "lang.kotlin": "\u{E634}",
679
+ "lang.shell": "\u{E795}",
680
+ "lang.html": "\u{E736}",
681
+ "lang.css": "\u{E749}",
682
+ "lang.json": "\u{E60B}",
683
+ "lang.yaml": "\u{E615}",
684
+ "lang.markdown": "\u{E609}",
685
+ "lang.sql": "\u{E706}",
686
+ "lang.docker": "\u{E7B0}",
687
+ "lang.lua": "\u{E620}",
688
+ "lang.text": "\u{E612}",
689
+ "lang.env": "\u{E615}",
690
+ "lang.toml": "\u{E615}",
691
+ "lang.xml": "\u{F05C0}",
692
+ "lang.ini": "\u{E615}",
693
+ "lang.conf": "\u{E615}",
694
+ "lang.log": "\u{F0331}",
695
+ "lang.csv": "\u{F021B}",
696
+ "lang.tsv": "\u{F021B}",
697
+ "lang.image": "\u{F021F}",
698
+ "lang.pdf": "\u{F0226}",
699
+ "lang.archive": "\u{F187}",
700
+ "lang.binary": "\u{F019A}",
701
+ // Settings tab icons
702
+ "tab.appearance": "󰃣",
703
+ "tab.model": "󰚩",
704
+ "tab.interaction": "󰌌",
705
+ "tab.context": "󰘸",
706
+ "tab.files": "󰈔",
707
+ "tab.shell": "󰆍",
708
+ "tab.tools": "󰠭",
709
+ "tab.memory": "󰧑",
710
+ "tab.tasks": "󰐱",
711
+ "tab.providers": "󰖟",
712
+ // Tool identity icons (per-tool signature glyph on the success header)
713
+ "tool.write": "\uEA7F",
714
+ "tool.edit": "\uEA73",
715
+ "tool.bash": "\uEBCA",
716
+ "tool.ssh": "\uEB3A",
717
+ "tool.lsp": "\uEA61",
718
+ "tool.gh": "\uEA84",
719
+ "tool.webSearch": "\uEB01",
720
+ "tool.exa": "\uEB68",
721
+ "tool.browser": "\uEAAE",
722
+ "tool.eval": "\uEBAF",
723
+ "tool.debug": "\uEAD8",
724
+ "tool.mcp": "\uEB2D",
725
+ "tool.job": "\uEBA2",
726
+ "tool.task": "\uf4a0",
727
+ "tool.todo": "\uEAB3",
728
+ "tool.memory": "\uEACE",
729
+ "tool.ask": "\uEAC7",
730
+ "tool.resolve": "\uEBB1",
731
+ "tool.review": "\uEA70",
732
+ "tool.inspectImage": "\uEAEA",
733
+ "tool.goal": "\uEBF8",
734
+ "tool.irc": "\uF086",
735
+ };
736
+
737
+ const ASCII_SYMBOLS: SymbolMap = {
738
+ // Status Indicators
739
+ "status.success": "[ok]",
740
+ "status.error": "[!!]",
741
+ "status.warning": "[!]",
742
+ "status.info": "[i]",
743
+ "status.pending": "[*]",
744
+ "status.disabled": "[ ]",
745
+ "status.enabled": "[x]",
746
+ "status.running": "[~]",
747
+ "status.shadowed": "[/]",
748
+ "status.aborted": "[-]",
749
+ "status.done": "*",
750
+ // Navigation
751
+ "nav.cursor": ">",
752
+ "nav.selected": "->",
753
+ "nav.expand": "+",
754
+ "nav.collapse": "-",
755
+ "nav.back": "<-",
756
+ // Tree Connectors
757
+ "tree.branch": "|--",
758
+ "tree.last": "'--",
759
+ "tree.vertical": "|",
760
+ "tree.horizontal": "-",
761
+ "tree.hook": "`-",
762
+ // Box Drawing - Rounded (ASCII fallback)
763
+ "boxRound.topLeft": "+",
764
+ "boxRound.topRight": "+",
765
+ "boxRound.bottomLeft": "+",
766
+ "boxRound.bottomRight": "+",
767
+ "boxRound.horizontal": "-",
768
+ "boxRound.vertical": "|",
769
+ // Box Drawing - Sharp (ASCII fallback)
770
+ "boxSharp.topLeft": "+",
771
+ "boxSharp.topRight": "+",
772
+ "boxSharp.bottomLeft": "+",
773
+ "boxSharp.bottomRight": "+",
774
+ "boxSharp.horizontal": "-",
775
+ "boxSharp.vertical": "|",
776
+ "boxSharp.cross": "+",
777
+ "boxSharp.teeDown": "+",
778
+ "boxSharp.teeUp": "+",
779
+ "boxSharp.teeRight": "+",
780
+ "boxSharp.teeLeft": "+",
781
+ // Separators
782
+ "sep.powerline": ">",
783
+ "sep.powerlineThin": ">",
784
+ "sep.powerlineLeft": ">",
785
+ "sep.powerlineRight": "<",
786
+ "sep.powerlineThinLeft": ">",
787
+ "sep.powerlineThinRight": "<",
788
+ "sep.block": "#",
789
+ "sep.space": " ",
790
+ "sep.asciiLeft": ">",
791
+ "sep.asciiRight": "<",
792
+ "sep.dot": " - ",
793
+ "sep.slash": " / ",
794
+ "sep.pipe": " | ",
795
+ // Icons
796
+ "icon.model": "[M]",
797
+ "icon.plan": "plan",
798
+ "icon.goal": "goal",
799
+ "icon.pause": "||",
800
+ "icon.loop": "loop",
801
+ "icon.folder": "[D]",
802
+ "icon.search": "[/]",
803
+ "icon.scratchFolder": "[T]",
804
+ "icon.file": "[F]",
805
+ "icon.git": "git:",
806
+ "icon.branch": "@",
807
+ "icon.pr": "PR",
808
+ "icon.tokens": "tok:",
809
+ "icon.context": "ctx:",
810
+ "icon.cost": "$",
811
+ "icon.time": "t:",
812
+ "icon.pi": "pi",
813
+ "icon.ghost": "@",
814
+ "icon.agents": "AG",
815
+ "icon.job": "bg",
816
+ "icon.cache": "cache",
817
+ "icon.cacheMiss": "!",
818
+ "icon.input": "in:",
819
+ "icon.output": "out:",
820
+ "icon.host": "host",
821
+ "icon.session": "id",
822
+ "icon.package": "[P]",
823
+ "icon.warning": "[!]",
824
+ "icon.rewind": "<-",
825
+ "icon.auto": "[A]",
826
+ "icon.fast": ">>",
827
+ "icon.extensionSkill": "SK",
828
+ "icon.extensionTool": "TL",
829
+ "icon.extensionSlashCommand": "/",
830
+ "icon.extensionMcp": "MCP",
831
+ "icon.extensionRule": "RL",
832
+ "icon.extensionHook": "HK",
833
+ "icon.extensionPrompt": "PR",
834
+ "icon.extensionContextFile": "CF",
835
+ "icon.extensionInstruction": "IN",
836
+ // STT
837
+ "icon.mic": "MIC",
838
+ // Compaction divider
839
+ "icon.camera": "[o]",
840
+ // Thinking Levels
841
+ "thinking.minimal": "[min]",
842
+ "thinking.low": "[low]",
843
+ "thinking.medium": "[med]",
844
+ "thinking.high": "[high]",
845
+ "thinking.xhigh": "[xhi]",
846
+ "thinking.autoPending": "[~]",
847
+ // Checkboxes
848
+ "checkbox.checked": "[x]",
849
+ "checkbox.unchecked": "[ ]",
850
+ "radio.selected": "(o)",
851
+ "radio.unselected": "( )",
852
+ "format.bullet": "*",
853
+ "format.dash": "-",
854
+ "format.bracketLeft": "[",
855
+ "format.bracketRight": "]",
856
+ // Markdown-specific
857
+ "md.quoteBorder": "|",
858
+ "md.hrChar": "-",
859
+ "md.bullet": "*",
860
+ "md.colorSwatch": "[]",
861
+ // Language icons (ASCII uses abbreviations)
862
+ "lang.default": "code",
863
+ "lang.typescript": "ts",
864
+ "lang.javascript": "js",
865
+ "lang.python": "py",
866
+ "lang.rust": "rs",
867
+ "lang.go": "go",
868
+ "lang.java": "java",
869
+ "lang.c": "c",
870
+ "lang.cpp": "cpp",
871
+ "lang.csharp": "cs",
872
+ "lang.ruby": "rb",
873
+ "lang.php": "php",
874
+ "lang.swift": "swift",
875
+ "lang.kotlin": "kt",
876
+ "lang.shell": "sh",
877
+ "lang.html": "html",
878
+ "lang.css": "css",
879
+ "lang.json": "json",
880
+ "lang.yaml": "yaml",
881
+ "lang.markdown": "md",
882
+ "lang.sql": "sql",
883
+ "lang.docker": "docker",
884
+ "lang.lua": "lua",
885
+ "lang.text": "txt",
886
+ "lang.env": "env",
887
+ "lang.toml": "toml",
888
+ "lang.xml": "xml",
889
+ "lang.ini": "ini",
890
+ "lang.conf": "conf",
891
+ "lang.log": "log",
892
+ "lang.csv": "csv",
893
+ "lang.tsv": "tsv",
894
+ "lang.image": "img",
895
+ "lang.pdf": "pdf",
896
+ "lang.archive": "zip",
897
+ "lang.binary": "bin",
898
+ // Settings tab icons
899
+ "tab.appearance": "[A]",
900
+ "tab.model": "[M]",
901
+ "tab.interaction": "[I]",
902
+ "tab.context": "[X]",
903
+ "tab.files": "[F]",
904
+ "tab.shell": "[S]",
905
+ "tab.tools": "[T]",
906
+ "tab.memory": "[Y]",
907
+ "tab.tasks": "[K]",
908
+ "tab.providers": "[P]",
909
+ // Tool identity icons (per-tool signature glyph on the success header)
910
+ "tool.write": "+f",
911
+ "tool.edit": "~",
912
+ "tool.bash": "$",
913
+ "tool.ssh": "ssh",
914
+ "tool.lsp": "lsp",
915
+ "tool.gh": "gh",
916
+ "tool.webSearch": "web",
917
+ "tool.exa": "exa",
918
+ "tool.browser": "[w]",
919
+ "tool.eval": ">_",
920
+ "tool.debug": "dbg",
921
+ "tool.mcp": "<>",
922
+ "tool.job": "job",
923
+ "tool.task": ">>>",
924
+ "tool.todo": "[x]",
925
+ "tool.memory": "mem",
926
+ "tool.ask": "[?]",
927
+ "tool.resolve": "[v]",
928
+ "tool.review": "rev",
929
+ "tool.inspectImage": "[i]",
930
+ "tool.goal": "(o)",
931
+ "tool.irc": "irc",
932
+ };
933
+
934
+ const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap> = {
935
+ unicode: UNICODE_SYMBOLS,
936
+ nerd: NERD_SYMBOLS,
937
+ ascii: ASCII_SYMBOLS,
938
+ };
939
+
940
+ export type SpinnerType = "status" | "activity";
941
+
942
+ const SPINNER_FRAMES: Record<SymbolPreset, Record<SpinnerType, string[]>> = {
943
+ unicode: {
944
+ status: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
945
+ activity: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
946
+ },
947
+ nerd: {
948
+ status: ["󱑖", "󱑋", "󱑌", "󱑍", "󱑎", "󱑏", "󱑐", "󱑑", "󱑒", "󱑓", "󱑔", "󱑕"],
949
+ activity: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
950
+ },
951
+ ascii: {
952
+ status: ["|", "/", "-", "\\"],
953
+ activity: ["-", "\\", "|", "/"],
954
+ },
955
+ };
956
+
957
+ /**
958
+ * Shape accepted by `themeJson.symbols.spinnerFrames`. A flat array applies to
959
+ * both spinner types; an object lets a theme override `status` and/or
960
+ * `activity` independently. Anything not specified falls back to the symbol
961
+ * preset's default frames.
962
+ */
963
+ type SpinnerFramesOverride = string[] | { status?: string[]; activity?: string[] };
964
+
965
+ function normalizeSpinnerFramesOverride(
966
+ value: SpinnerFramesOverride | undefined,
967
+ ): Partial<Record<SpinnerType, string[]>> {
968
+ if (value === undefined) return {};
969
+ if (Array.isArray(value)) return { status: value, activity: value };
970
+ const result: Partial<Record<SpinnerType, string[]>> = {};
971
+ if (value.status) result.status = value.status;
972
+ if (value.activity) result.activity = value.activity;
973
+ return result;
974
+ }
975
+
976
+ // ============================================================================
977
+ // Types & Schema
978
+ // ============================================================================
979
+
980
+ type ColorValue = string | number;
981
+
982
+ const themeColorsSchema = type({
983
+ accent: "string | number",
984
+ border: "string | number",
985
+ borderAccent: "string | number",
986
+ borderMuted: "string | number",
987
+ success: "string | number",
988
+ error: "string | number",
989
+ warning: "string | number",
990
+ muted: "string | number",
991
+ dim: "string | number",
992
+ text: "string | number",
993
+ thinkingText: "string | number",
994
+ selectedBg: "string | number",
995
+ userMessageBg: "string | number",
996
+ userMessageText: "string | number",
997
+ customMessageBg: "string | number",
998
+ customMessageText: "string | number",
999
+ customMessageLabel: "string | number",
1000
+ toolPendingBg: "string | number",
1001
+ toolSuccessBg: "string | number",
1002
+ toolErrorBg: "string | number",
1003
+ toolTitle: "string | number",
1004
+ toolOutput: "string | number",
1005
+ mdHeading: "string | number",
1006
+ mdLink: "string | number",
1007
+ mdLinkUrl: "string | number",
1008
+ mdCode: "string | number",
1009
+ mdCodeBlock: "string | number",
1010
+ mdCodeBlockBorder: "string | number",
1011
+ mdQuote: "string | number",
1012
+ mdQuoteBorder: "string | number",
1013
+ mdHr: "string | number",
1014
+ mdListBullet: "string | number",
1015
+ toolDiffAdded: "string | number",
1016
+ toolDiffRemoved: "string | number",
1017
+ toolDiffContext: "string | number",
1018
+ syntaxComment: "string | number",
1019
+ syntaxKeyword: "string | number",
1020
+ syntaxFunction: "string | number",
1021
+ syntaxVariable: "string | number",
1022
+ syntaxString: "string | number",
1023
+ syntaxNumber: "string | number",
1024
+ syntaxType: "string | number",
1025
+ syntaxOperator: "string | number",
1026
+ syntaxPunctuation: "string | number",
1027
+ thinkingOff: "string | number",
1028
+ thinkingMinimal: "string | number",
1029
+ thinkingLow: "string | number",
1030
+ thinkingMedium: "string | number",
1031
+ thinkingHigh: "string | number",
1032
+ thinkingXhigh: "string | number",
1033
+ bashMode: "string | number",
1034
+ pythonMode: "string | number",
1035
+ statusLineBg: "string | number",
1036
+ statusLineSep: "string | number",
1037
+ statusLineModel: "string | number",
1038
+ statusLinePath: "string | number",
1039
+ statusLineGitClean: "string | number",
1040
+ statusLineGitDirty: "string | number",
1041
+ statusLineContext: "string | number",
1042
+ statusLineSpend: "string | number",
1043
+ statusLineStaged: "string | number",
1044
+ statusLineDirty: "string | number",
1045
+ statusLineUntracked: "string | number",
1046
+ statusLineOutput: "string | number",
1047
+ statusLineCost: "string | number",
1048
+ statusLineSubagents: "string | number",
1049
+ });
1050
+ const spinnerFramesSchema = type("unknown").narrow((value): value is SpinnerFramesOverride => {
1051
+ if (Array.isArray(value)) {
1052
+ return value.length >= 1 && value.every(item => typeof item === "string");
1053
+ }
1054
+ if (value && typeof value === "object") {
1055
+ const obj = value as Record<string, unknown>;
1056
+ const status = obj.status;
1057
+ const activity = obj.activity;
1058
+ if (status === undefined && activity === undefined) return false;
1059
+ if (status !== undefined) {
1060
+ if (!Array.isArray(status) || status.length < 1 || !status.every(item => typeof item === "string")) {
1061
+ return false;
1062
+ }
1063
+ }
1064
+ if (activity !== undefined) {
1065
+ if (!Array.isArray(activity) || activity.length < 1 || !activity.every(item => typeof item === "string")) {
1066
+ return false;
1067
+ }
1068
+ }
1069
+ return true;
1070
+ }
1071
+ return false;
1072
+ });
1073
+ const themeJsonSchema = type({
1074
+ "$schema?": "string",
1075
+ name: "string",
1076
+ "vars?": "Record<string, string | number>",
1077
+ colors: themeColorsSchema,
1078
+ "export?": {
1079
+ "pageBg?": "string | number",
1080
+ "cardBg?": "string | number",
1081
+ "infoBg?": "string | number",
1082
+ },
1083
+ "symbols?": {
1084
+ "preset?": "'unicode' | 'nerd' | 'ascii'",
1085
+ "overrides?": "Record<string, string>",
1086
+ "spinnerFrames?": spinnerFramesSchema,
1087
+ },
1088
+ });
1089
+
1090
+ type ThemeJson = typeof themeJsonSchema.infer;
1091
+
1092
+ export type ThemeColor =
1093
+ | "accent"
1094
+ | "border"
1095
+ | "borderAccent"
1096
+ | "borderMuted"
1097
+ | "success"
1098
+ | "error"
1099
+ | "warning"
1100
+ | "muted"
1101
+ | "dim"
1102
+ | "text"
1103
+ | "thinkingText"
1104
+ | "userMessageText"
1105
+ | "customMessageText"
1106
+ | "customMessageLabel"
1107
+ | "toolTitle"
1108
+ | "toolOutput"
1109
+ | "mdHeading"
1110
+ | "mdLink"
1111
+ | "mdLinkUrl"
1112
+ | "mdCode"
1113
+ | "mdCodeBlock"
1114
+ | "mdCodeBlockBorder"
1115
+ | "mdQuote"
1116
+ | "mdQuoteBorder"
1117
+ | "mdHr"
1118
+ | "mdListBullet"
1119
+ | "toolDiffAdded"
1120
+ | "toolDiffRemoved"
1121
+ | "toolDiffContext"
1122
+ | "syntaxComment"
1123
+ | "syntaxKeyword"
1124
+ | "syntaxFunction"
1125
+ | "syntaxVariable"
1126
+ | "syntaxString"
1127
+ | "syntaxNumber"
1128
+ | "syntaxType"
1129
+ | "syntaxOperator"
1130
+ | "syntaxPunctuation"
1131
+ | "thinkingOff"
1132
+ | "thinkingMinimal"
1133
+ | "thinkingLow"
1134
+ | "thinkingMedium"
1135
+ | "thinkingHigh"
1136
+ | "thinkingXhigh"
1137
+ | "bashMode"
1138
+ | "pythonMode"
1139
+ | "statusLineSep"
1140
+ | "statusLineModel"
1141
+ | "statusLinePath"
1142
+ | "statusLineGitClean"
1143
+ | "statusLineGitDirty"
1144
+ | "statusLineContext"
1145
+ | "statusLineSpend"
1146
+ | "statusLineStaged"
1147
+ | "statusLineDirty"
1148
+ | "statusLineUntracked"
1149
+ | "statusLineOutput"
1150
+ | "statusLineCost"
1151
+ | "statusLineSubagents";
1152
+
1153
+ /** Set of all valid ThemeColor string values for runtime validation */
1154
+ const THEME_COLOR_RECORD = {
1155
+ accent: true,
1156
+ border: true,
1157
+ borderAccent: true,
1158
+ borderMuted: true,
1159
+ success: true,
1160
+ error: true,
1161
+ warning: true,
1162
+ muted: true,
1163
+ dim: true,
1164
+ text: true,
1165
+ thinkingText: true,
1166
+ userMessageText: true,
1167
+ customMessageText: true,
1168
+ customMessageLabel: true,
1169
+ toolTitle: true,
1170
+ toolOutput: true,
1171
+ mdHeading: true,
1172
+ mdLink: true,
1173
+ mdLinkUrl: true,
1174
+ mdCode: true,
1175
+ mdCodeBlock: true,
1176
+ mdCodeBlockBorder: true,
1177
+ mdQuote: true,
1178
+ mdQuoteBorder: true,
1179
+ mdHr: true,
1180
+ mdListBullet: true,
1181
+ toolDiffAdded: true,
1182
+ toolDiffRemoved: true,
1183
+ toolDiffContext: true,
1184
+ syntaxComment: true,
1185
+ syntaxKeyword: true,
1186
+ syntaxFunction: true,
1187
+ syntaxVariable: true,
1188
+ syntaxString: true,
1189
+ syntaxNumber: true,
1190
+ syntaxType: true,
1191
+ syntaxOperator: true,
1192
+ syntaxPunctuation: true,
1193
+ thinkingOff: true,
1194
+ thinkingMinimal: true,
1195
+ thinkingLow: true,
1196
+ thinkingMedium: true,
1197
+ thinkingHigh: true,
1198
+ thinkingXhigh: true,
1199
+ bashMode: true,
1200
+ pythonMode: true,
1201
+ statusLineSep: true,
1202
+ statusLineModel: true,
1203
+ statusLinePath: true,
1204
+ statusLineGitClean: true,
1205
+ statusLineGitDirty: true,
1206
+ statusLineContext: true,
1207
+ statusLineSpend: true,
1208
+ statusLineStaged: true,
1209
+ statusLineDirty: true,
1210
+ statusLineUntracked: true,
1211
+ statusLineOutput: true,
1212
+ statusLineCost: true,
1213
+ statusLineSubagents: true,
1214
+ } satisfies Record<ThemeColor, true>;
1215
+
1216
+ const VALID_THEME_COLORS: ReadonlySet<string> = new Set(Object.keys(THEME_COLOR_RECORD));
1217
+
1218
+ /** Check if a string is a valid ThemeColor value */
1219
+ export function isValidThemeColor(color: string): color is ThemeColor {
1220
+ return VALID_THEME_COLORS.has(color);
1221
+ }
1222
+
1223
+ export type ThemeBg =
1224
+ | "selectedBg"
1225
+ | "userMessageBg"
1226
+ | "customMessageBg"
1227
+ | "toolPendingBg"
1228
+ | "toolSuccessBg"
1229
+ | "toolErrorBg"
1230
+ | "statusLineBg";
1231
+
1232
+ type ColorMode = "truecolor" | "256color";
1233
+
1234
+ // ============================================================================
1235
+ // Color Utilities
1236
+ // ============================================================================
1237
+
1238
+ function detectColorMode(): ColorMode {
1239
+ const colorterm = Bun.env.COLORTERM;
1240
+ if (colorterm === "truecolor" || colorterm === "24bit") {
1241
+ return "truecolor";
1242
+ }
1243
+ // Windows Terminal supports truecolor
1244
+ if (Bun.env.WT_SESSION) {
1245
+ return "truecolor";
1246
+ }
1247
+ const term = Bun.env.TERM || "";
1248
+ // Only fall back to 256color for truly limited terminals
1249
+ if (term === "dumb" || term === "" || term === "linux") {
1250
+ return "256color";
1251
+ }
1252
+ // Assume truecolor for everything else - virtually all modern terminals support it
1253
+ return "truecolor";
1254
+ }
1255
+
1256
+ function colorToAnsi(color: string, mode: ColorMode): string {
1257
+ const format = mode === "truecolor" ? "ansi-16m" : "ansi-256";
1258
+ const ansi = Bun.color(color, format);
1259
+ if (ansi === null) {
1260
+ throw new Error(`Invalid color value: ${color}`);
1261
+ }
1262
+ return ansi;
1263
+ }
1264
+
1265
+ function fgAnsi(color: string | number, mode: ColorMode): string {
1266
+ if (color === "") return "\x1b[39m";
1267
+ if (typeof color === "number") return `\x1b[38;5;${color}m`;
1268
+ if (typeof color === "string") {
1269
+ return colorToAnsi(color, mode);
1270
+ }
1271
+ throw new Error(`Invalid color value: ${color}`);
1272
+ }
1273
+
1274
+ function bgAnsi(color: string | number, mode: ColorMode): string {
1275
+ if (color === "") return "\x1b[49m";
1276
+ if (typeof color === "number") return `\x1b[48;5;${color}m`;
1277
+ const ansi = colorToAnsi(color, mode);
1278
+ return ansi.replace("\x1b[38;", "\x1b[48;");
1279
+ }
1280
+
1281
+ function resolveVarRefs(
1282
+ value: ColorValue,
1283
+ vars: Record<string, ColorValue>,
1284
+ visited = new Set<string>(),
1285
+ ): string | number {
1286
+ if (typeof value === "number" || value === "" || value.startsWith("#")) {
1287
+ return value;
1288
+ }
1289
+ if (visited.has(value)) {
1290
+ throw new Error(`Circular variable reference detected: ${value}`);
1291
+ }
1292
+ if (!(value in vars)) {
1293
+ throw new Error(`Variable reference not found: ${value}`);
1294
+ }
1295
+ visited.add(value);
1296
+ return resolveVarRefs(vars[value], vars, visited);
1297
+ }
1298
+
1299
+ function resolveThemeColors<T extends Record<string, ColorValue>>(
1300
+ colors: T,
1301
+ vars: Record<string, ColorValue> = {},
1302
+ ): Record<keyof T, string | number> {
1303
+ const resolved: Record<string, string | number> = {};
1304
+ for (const [key, value] of Object.entries(colors)) {
1305
+ resolved[key] = resolveVarRefs(value, vars);
1306
+ }
1307
+ return resolved as Record<keyof T, string | number>;
1308
+ }
1309
+
1310
+ // ============================================================================
1311
+ // Theme Class
1312
+ // ============================================================================
1313
+
1314
+ const langMap: Record<string, SymbolKey> = {
1315
+ typescript: "lang.typescript",
1316
+ ts: "lang.typescript",
1317
+ tsx: "lang.typescript",
1318
+ javascript: "lang.javascript",
1319
+ js: "lang.javascript",
1320
+ jsx: "lang.javascript",
1321
+ mjs: "lang.javascript",
1322
+ cjs: "lang.javascript",
1323
+ python: "lang.python",
1324
+ py: "lang.python",
1325
+ rust: "lang.rust",
1326
+ rs: "lang.rust",
1327
+ go: "lang.go",
1328
+ java: "lang.java",
1329
+ c: "lang.c",
1330
+ cpp: "lang.cpp",
1331
+ "c++": "lang.cpp",
1332
+ cc: "lang.cpp",
1333
+ cxx: "lang.cpp",
1334
+ csharp: "lang.csharp",
1335
+ cs: "lang.csharp",
1336
+ ruby: "lang.ruby",
1337
+ rb: "lang.ruby",
1338
+ php: "lang.php",
1339
+ swift: "lang.swift",
1340
+ kotlin: "lang.kotlin",
1341
+ kt: "lang.kotlin",
1342
+ bash: "lang.shell",
1343
+ sh: "lang.shell",
1344
+ zsh: "lang.shell",
1345
+ fish: "lang.shell",
1346
+ powershell: "lang.shell",
1347
+ just: "lang.shell",
1348
+ shell: "lang.shell",
1349
+ html: "lang.html",
1350
+ htm: "lang.html",
1351
+ astro: "lang.html",
1352
+ vue: "lang.html",
1353
+ svelte: "lang.html",
1354
+ css: "lang.css",
1355
+ scss: "lang.css",
1356
+ sass: "lang.css",
1357
+ less: "lang.css",
1358
+ json: "lang.json",
1359
+ yaml: "lang.yaml",
1360
+ yml: "lang.yaml",
1361
+ markdown: "lang.markdown",
1362
+ md: "lang.markdown",
1363
+ sql: "lang.sql",
1364
+ dockerfile: "lang.docker",
1365
+ docker: "lang.docker",
1366
+ lua: "lang.lua",
1367
+ text: "lang.text",
1368
+ txt: "lang.text",
1369
+ plain: "lang.text",
1370
+ log: "lang.log",
1371
+ env: "lang.env",
1372
+ dotenv: "lang.env",
1373
+ toml: "lang.toml",
1374
+ xml: "lang.xml",
1375
+ ini: "lang.ini",
1376
+ conf: "lang.conf",
1377
+ cfg: "lang.conf",
1378
+ config: "lang.conf",
1379
+ properties: "lang.conf",
1380
+ csv: "lang.csv",
1381
+ tsv: "lang.tsv",
1382
+ image: "lang.image",
1383
+ img: "lang.image",
1384
+ png: "lang.image",
1385
+ jpg: "lang.image",
1386
+ jpeg: "lang.image",
1387
+ gif: "lang.image",
1388
+ webp: "lang.image",
1389
+ svg: "lang.image",
1390
+ ico: "lang.image",
1391
+ bmp: "lang.image",
1392
+ tiff: "lang.image",
1393
+ pdf: "lang.pdf",
1394
+ zip: "lang.archive",
1395
+ tar: "lang.archive",
1396
+ gz: "lang.archive",
1397
+ tgz: "lang.archive",
1398
+ bz2: "lang.archive",
1399
+ xz: "lang.archive",
1400
+ "7z": "lang.archive",
1401
+ exe: "lang.binary",
1402
+ dll: "lang.binary",
1403
+ so: "lang.binary",
1404
+ dylib: "lang.binary",
1405
+ wasm: "lang.binary",
1406
+ bin: "lang.binary",
1407
+ };
1408
+
1409
+ /**
1410
+ * Resolve a theme color value (hex string or 256-color index) to a CSS hex string.
1411
+ * Empty string represents the default terminal color.
1412
+ */
1413
+ function resolveToHex(value: string | number, isLight: boolean): string {
1414
+ if (typeof value === "number") return ansi256ToHex(value);
1415
+ if (value === "") return isLight ? "#000000" : "#e5e5e7";
1416
+ return value;
1417
+ }
1418
+
1419
+ export class Theme {
1420
+ #fgColors: Record<ThemeColor, string>;
1421
+ #bgColors: Record<ThemeBg, string>;
1422
+ /** Resolved hex strings for foreground colors — populated at construction. */
1423
+ readonly #hexFgColors: Record<ThemeColor, string>;
1424
+ /** Resolved hex strings for background colors — populated at construction. */
1425
+ readonly #hexBgColors: Record<ThemeBg, string>;
1426
+ #symbols: SymbolMap;
1427
+ #spinnerFramesOverrides: Partial<Record<SpinnerType, string[]>>;
1428
+ /**
1429
+ * Perceptual luma (0..1) of the status-line background — used to classify the
1430
+ * theme light/dark. Undefined when it can't be resolved. Classified against the
1431
+ * status line (the surface session accents render on) rather than the chat bubble
1432
+ * (`userMessageBg`), which some themes (e.g. `porcelain`) style dark on an
1433
+ * otherwise-light theme.
1434
+ */
1435
+ readonly statusLineLuminance: number | undefined;
1436
+ /** WCAG relative luminance of the status-line background — basis for accent contrast. */
1437
+ readonly #statusLineContrastLuminance: number | undefined;
1438
+ constructor(
1439
+ fgColors: Record<ThemeColor, string | number>,
1440
+ bgColors: Record<ThemeBg, string | number>,
1441
+ private readonly mode: ColorMode,
1442
+ private readonly symbolPreset: SymbolPreset,
1443
+ symbolOverrides: Partial<Record<SymbolKey, string>>,
1444
+ spinnerFramesOverrides: Partial<Record<SpinnerType, string[]>> = {},
1445
+ ) {
1446
+ this.statusLineLuminance = colorLuma(bgColors.statusLineBg);
1447
+ this.#statusLineContrastLuminance = relativeLuminance(bgColors.statusLineBg);
1448
+ const slIsLight = this.statusLineLuminance !== undefined && this.statusLineLuminance > 0.5;
1449
+
1450
+ this.#fgColors = {} as Record<ThemeColor, string>;
1451
+ this.#hexFgColors = {} as Record<ThemeColor, string>;
1452
+ for (const [key, value] of Object.entries(fgColors) as [ThemeColor, string | number][]) {
1453
+ this.#fgColors[key] = fgAnsi(value, mode);
1454
+ this.#hexFgColors[key] = resolveToHex(value, slIsLight);
1455
+ }
1456
+ this.#bgColors = {} as Record<ThemeBg, string>;
1457
+ this.#hexBgColors = {} as Record<ThemeBg, string>;
1458
+ for (const [key, value] of Object.entries(bgColors) as [ThemeBg, string | number][]) {
1459
+ this.#bgColors[key] = bgAnsi(value, mode);
1460
+ this.#hexBgColors[key] = resolveToHex(value, slIsLight);
1461
+ }
1462
+ // Build symbol map from preset + overrides
1463
+ const baseSymbols = SYMBOL_PRESETS[symbolPreset];
1464
+ this.#symbols = { ...baseSymbols };
1465
+ for (const [key, value] of Object.entries(symbolOverrides)) {
1466
+ if (key in this.#symbols) {
1467
+ this.#symbols[key as SymbolKey] = value;
1468
+ } else {
1469
+ logger.debug("Invalid symbol key in override", { key, availableKeys: Object.keys(this.#symbols) });
1470
+ }
1471
+ }
1472
+ this.#spinnerFramesOverrides = spinnerFramesOverrides;
1473
+ }
1474
+
1475
+ /** True when the active theme has a light status-line background. */
1476
+ get isLight(): boolean {
1477
+ return this.statusLineLuminance !== undefined && this.statusLineLuminance > 0.5;
1478
+ }
1479
+
1480
+ /**
1481
+ * Surface luminance to size session accents against on light themes; undefined on
1482
+ * dark themes so accents stay vivid. Pass straight to `getSessionAccentHex`.
1483
+ */
1484
+ get accentSurfaceLuminance(): number | undefined {
1485
+ return this.isLight ? this.#statusLineContrastLuminance : undefined;
1486
+ }
1487
+
1488
+ /**
1489
+ * Get the resolved CSS hex string for a foreground theme color.
1490
+ */
1491
+ getColorHex(color: ThemeColor): string {
1492
+ const hex = this.#hexFgColors[color];
1493
+ if (hex === undefined) throw new Error(`Unknown theme color: ${color}`);
1494
+ return hex || (this.isLight ? "#000000" : "#e5e5e7");
1495
+ }
1496
+
1497
+ /**
1498
+ * Get all foreground and background theme colors as CSS hex strings.
1499
+ * Skips colors resolved to the default terminal color (unstyled).
1500
+ */
1501
+ getAllThemeColorHexes(): string[] {
1502
+ const hexes: string[] = [];
1503
+ for (const hex of Object.values(this.#hexFgColors)) {
1504
+ if (hex) hexes.push(hex);
1505
+ }
1506
+ for (const hex of Object.values(this.#hexBgColors)) {
1507
+ if (hex) hexes.push(hex);
1508
+ }
1509
+ return hexes;
1510
+ }
1511
+
1512
+ /**
1513
+ * Get the most visually dominant theme colors as CSS hex strings — accent,
1514
+ * border, success, error, warning, heading, link, diff markers, etc.
1515
+ * These are the colors the session accent could visually clash with.
1516
+ * Skips colors resolved to the default terminal color (unstyled).
1517
+ */
1518
+ getMajorThemeColorHexes(): string[] {
1519
+ const majors: ThemeColor[] = [
1520
+ "accent",
1521
+ "border",
1522
+ "borderAccent",
1523
+ "borderMuted",
1524
+ "success",
1525
+ "error",
1526
+ "warning",
1527
+ "mdHeading",
1528
+ "mdLink",
1529
+ "mdCode",
1530
+ "mdCodeBlock",
1531
+ "mdQuoteBorder",
1532
+ "mdListBullet",
1533
+ "toolDiffAdded",
1534
+ "toolDiffRemoved",
1535
+ "customMessageLabel",
1536
+ "thinkingText",
1537
+ ];
1538
+ const hexes: string[] = [];
1539
+ for (const key of majors) {
1540
+ const hex = this.#hexFgColors[key];
1541
+ if (hex) hexes.push(hex);
1542
+ }
1543
+ return hexes;
1544
+ }
1545
+ /**
1546
+ * Get the resolved CSS hex string for the theme's accent color.
1547
+ */
1548
+ getAccentColorHex(): string {
1549
+ return this.getColorHex("accent");
1550
+ }
1551
+
1552
+ fg(color: ThemeColor, text: string): string {
1553
+ const ansi = this.#fgColors[color];
1554
+ if (!ansi) throw new Error(`Unknown theme color: ${color}`);
1555
+ return `${ansi}${text}\x1b[39m`; // Reset only foreground color
1556
+ }
1557
+
1558
+ bg(color: ThemeBg, text: string): string {
1559
+ const ansi = this.#bgColors[color];
1560
+ if (!ansi) throw new Error(`Unknown theme background color: ${color}`);
1561
+ return `${ansi}${text}\x1b[49m`; // Reset only background color
1562
+ }
1563
+
1564
+ bold(text: string): string {
1565
+ return chalk.bold(text);
1566
+ }
1567
+
1568
+ italic(text: string): string {
1569
+ return chalk.italic(text);
1570
+ }
1571
+
1572
+ underline(text: string): string {
1573
+ return chalk.underline(text);
1574
+ }
1575
+
1576
+ strikethrough(text: string): string {
1577
+ return chalk.strikethrough(text);
1578
+ }
1579
+
1580
+ inverse(text: string): string {
1581
+ return chalk.inverse(text);
1582
+ }
1583
+
1584
+ getFgAnsi(color: ThemeColor): string {
1585
+ const ansi = this.#fgColors[color];
1586
+ if (!ansi) throw new Error(`Unknown theme color: ${color}`);
1587
+ return ansi;
1588
+ }
1589
+
1590
+ getBgAnsi(color: ThemeBg): string {
1591
+ const ansi = this.#bgColors[color];
1592
+ if (!ansi) throw new Error(`Unknown theme background color: ${color}`);
1593
+ return ansi;
1594
+ }
1595
+
1596
+ /**
1597
+ * Foreground ANSI for text drawn **on top of** `fillColor` used as a solid
1598
+ * background (e.g. a powerline chip). Picks near-black or near-white by the
1599
+ * fill's perceived luminance (Rec. 601 luma) so the label stays legible on
1600
+ * both bright and dark fills, across light and dark themes.
1601
+ *
1602
+ * Reads the RGB out of the already-resolved truecolor escape; when the fill
1603
+ * is encoded as a 256-palette index (limited terminals) the RGB is
1604
+ * unavailable, so it falls back to the theme `text` color.
1605
+ */
1606
+ getContrastFgAnsi(fillColor: ThemeColor): string {
1607
+ const ansi = this.#fgColors[fillColor];
1608
+ const match = ansi ? /38;2;(\d+);(\d+);(\d+)/.exec(ansi) : null;
1609
+ if (!match) return this.#fgColors.text;
1610
+ const luma = 0.299 * Number(match[1]) + 0.587 * Number(match[2]) + 0.114 * Number(match[3]);
1611
+ return luma > 140 ? "\x1b[38;2;0;0;0m" : "\x1b[38;2;255;255;255m";
1612
+ }
1613
+
1614
+ getColorMode(): ColorMode {
1615
+ return this.mode;
1616
+ }
1617
+
1618
+ getThinkingBorderColor(level: ThinkingLevel | Effort): (str: string) => string {
1619
+ // Map thinking levels to dedicated theme colors
1620
+ switch (level) {
1621
+ case "off":
1622
+ return (str: string) => this.fg("thinkingOff", str);
1623
+ case "minimal":
1624
+ return (str: string) => this.fg("thinkingMinimal", str);
1625
+ case "low":
1626
+ return (str: string) => this.fg("thinkingLow", str);
1627
+ case "medium":
1628
+ return (str: string) => this.fg("thinkingMedium", str);
1629
+ case "high":
1630
+ return (str: string) => this.fg("thinkingHigh", str);
1631
+ case "xhigh":
1632
+ return (str: string) => this.fg("thinkingXhigh", str);
1633
+ default:
1634
+ return (str: string) => this.fg("thinkingOff", str);
1635
+ }
1636
+ }
1637
+
1638
+ getBashModeBorderColor(): (str: string) => string {
1639
+ return (str: string) => this.fg("bashMode", str);
1640
+ }
1641
+
1642
+ getPythonModeBorderColor(): (str: string) => string {
1643
+ return (str: string) => this.fg("pythonMode", str);
1644
+ }
1645
+
1646
+ // ============================================================================
1647
+ // Symbol Methods
1648
+ // ============================================================================
1649
+
1650
+ /**
1651
+ * Get a symbol by key.
1652
+ */
1653
+ symbol(key: SymbolKey): string {
1654
+ return this.#symbols[key];
1655
+ }
1656
+
1657
+ /**
1658
+ * Get a symbol styled with a color.
1659
+ */
1660
+ styledSymbol(key: SymbolKey, color: ThemeColor): string {
1661
+ return this.fg(color, this.#symbols[key]);
1662
+ }
1663
+
1664
+ /**
1665
+ * Get the current symbol preset.
1666
+ */
1667
+ getSymbolPreset(): SymbolPreset {
1668
+ return this.symbolPreset;
1669
+ }
1670
+
1671
+ // ============================================================================
1672
+ // Symbol Category Accessors
1673
+ // ============================================================================
1674
+
1675
+ get status() {
1676
+ return {
1677
+ success: this.#symbols["status.success"],
1678
+ error: this.#symbols["status.error"],
1679
+ warning: this.#symbols["status.warning"],
1680
+ info: this.#symbols["status.info"],
1681
+ pending: this.#symbols["status.pending"],
1682
+ disabled: this.#symbols["status.disabled"],
1683
+ enabled: this.#symbols["status.enabled"],
1684
+ running: this.#symbols["status.running"],
1685
+ shadowed: this.#symbols["status.shadowed"],
1686
+ aborted: this.#symbols["status.aborted"],
1687
+ done: this.#symbols["status.done"],
1688
+ };
1689
+ }
1690
+
1691
+ get nav() {
1692
+ return {
1693
+ cursor: this.#symbols["nav.cursor"],
1694
+ selected: this.#symbols["nav.selected"],
1695
+ expand: this.#symbols["nav.expand"],
1696
+ collapse: this.#symbols["nav.collapse"],
1697
+ back: this.#symbols["nav.back"],
1698
+ };
1699
+ }
1700
+
1701
+ get tree() {
1702
+ return {
1703
+ branch: this.#symbols["tree.branch"],
1704
+ last: this.#symbols["tree.last"],
1705
+ vertical: this.#symbols["tree.vertical"],
1706
+ horizontal: this.#symbols["tree.horizontal"],
1707
+ hook: this.#symbols["tree.hook"],
1708
+ };
1709
+ }
1710
+
1711
+ get boxRound() {
1712
+ return {
1713
+ topLeft: this.#symbols["boxRound.topLeft"],
1714
+ topRight: this.#symbols["boxRound.topRight"],
1715
+ bottomLeft: this.#symbols["boxRound.bottomLeft"],
1716
+ bottomRight: this.#symbols["boxRound.bottomRight"],
1717
+ horizontal: this.#symbols["boxRound.horizontal"],
1718
+ vertical: this.#symbols["boxRound.vertical"],
1719
+ // Junctions have no rounded Unicode variant, so a rounded box reuses the
1720
+ // sharp tee/cross glyphs. Sourcing them from the boxSharp.* tokens keeps a
1721
+ // theme's `boxSharp.tee*` overrides effective for rounded-box dividers.
1722
+ cross: this.#symbols["boxSharp.cross"],
1723
+ teeDown: this.#symbols["boxSharp.teeDown"],
1724
+ teeUp: this.#symbols["boxSharp.teeUp"],
1725
+ teeRight: this.#symbols["boxSharp.teeRight"],
1726
+ teeLeft: this.#symbols["boxSharp.teeLeft"],
1727
+ };
1728
+ }
1729
+
1730
+ get boxSharp() {
1731
+ return {
1732
+ topLeft: this.#symbols["boxSharp.topLeft"],
1733
+ topRight: this.#symbols["boxSharp.topRight"],
1734
+ bottomLeft: this.#symbols["boxSharp.bottomLeft"],
1735
+ bottomRight: this.#symbols["boxSharp.bottomRight"],
1736
+ horizontal: this.#symbols["boxSharp.horizontal"],
1737
+ vertical: this.#symbols["boxSharp.vertical"],
1738
+ cross: this.#symbols["boxSharp.cross"],
1739
+ teeDown: this.#symbols["boxSharp.teeDown"],
1740
+ teeUp: this.#symbols["boxSharp.teeUp"],
1741
+ teeRight: this.#symbols["boxSharp.teeRight"],
1742
+ teeLeft: this.#symbols["boxSharp.teeLeft"],
1743
+ };
1744
+ }
1745
+
1746
+ get sep() {
1747
+ return {
1748
+ powerline: this.#symbols["sep.powerline"],
1749
+ powerlineThin: this.#symbols["sep.powerlineThin"],
1750
+ powerlineLeft: this.#symbols["sep.powerlineLeft"],
1751
+ powerlineRight: this.#symbols["sep.powerlineRight"],
1752
+ powerlineThinLeft: this.#symbols["sep.powerlineThinLeft"],
1753
+ powerlineThinRight: this.#symbols["sep.powerlineThinRight"],
1754
+ block: this.#symbols["sep.block"],
1755
+ space: this.#symbols["sep.space"],
1756
+ asciiLeft: this.#symbols["sep.asciiLeft"],
1757
+ asciiRight: this.#symbols["sep.asciiRight"],
1758
+ dot: this.#symbols["sep.dot"],
1759
+ slash: this.#symbols["sep.slash"],
1760
+ pipe: this.#symbols["sep.pipe"],
1761
+ };
1762
+ }
1763
+
1764
+ get icon() {
1765
+ return {
1766
+ model: this.#symbols["icon.model"],
1767
+ plan: this.#symbols["icon.plan"],
1768
+ goal: this.#symbols["icon.goal"],
1769
+ pause: this.#symbols["icon.pause"],
1770
+ loop: this.#symbols["icon.loop"],
1771
+ folder: this.#symbols["icon.folder"],
1772
+ scratchFolder: this.#symbols["icon.scratchFolder"],
1773
+ file: this.#symbols["icon.file"],
1774
+ git: this.#symbols["icon.git"],
1775
+ branch: this.#symbols["icon.branch"],
1776
+ pr: this.#symbols["icon.pr"],
1777
+ tokens: this.#symbols["icon.tokens"],
1778
+ context: this.#symbols["icon.context"],
1779
+ cost: this.#symbols["icon.cost"],
1780
+ time: this.#symbols["icon.time"],
1781
+ pi: this.#symbols["icon.pi"],
1782
+ ghost: this.#symbols["icon.ghost"],
1783
+ agents: this.#symbols["icon.agents"],
1784
+ job: this.#symbols["icon.job"],
1785
+ cache: this.#symbols["icon.cache"],
1786
+ cacheMiss: this.#symbols["icon.cacheMiss"],
1787
+ input: this.#symbols["icon.input"],
1788
+ output: this.#symbols["icon.output"],
1789
+ host: this.#symbols["icon.host"],
1790
+ session: this.#symbols["icon.session"],
1791
+ package: this.#symbols["icon.package"],
1792
+ warning: this.#symbols["icon.warning"],
1793
+ rewind: this.#symbols["icon.rewind"],
1794
+ auto: this.#symbols["icon.auto"],
1795
+ fast: this.#symbols["icon.fast"],
1796
+ extensionSkill: this.#symbols["icon.extensionSkill"],
1797
+ extensionTool: this.#symbols["icon.extensionTool"],
1798
+ extensionSlashCommand: this.#symbols["icon.extensionSlashCommand"],
1799
+ extensionMcp: this.#symbols["icon.extensionMcp"],
1800
+ extensionRule: this.#symbols["icon.extensionRule"],
1801
+ extensionHook: this.#symbols["icon.extensionHook"],
1802
+ extensionPrompt: this.#symbols["icon.extensionPrompt"],
1803
+ extensionContextFile: this.#symbols["icon.extensionContextFile"],
1804
+ extensionInstruction: this.#symbols["icon.extensionInstruction"],
1805
+ mic: this.#symbols["icon.mic"],
1806
+ camera: this.#symbols["icon.camera"],
1807
+ };
1808
+ }
1809
+
1810
+ get thinking() {
1811
+ return {
1812
+ minimal: this.#symbols["thinking.minimal"],
1813
+ low: this.#symbols["thinking.low"],
1814
+ medium: this.#symbols["thinking.medium"],
1815
+ high: this.#symbols["thinking.high"],
1816
+ xhigh: this.#symbols["thinking.xhigh"],
1817
+ autoPending: this.#symbols["thinking.autoPending"],
1818
+ };
1819
+ }
1820
+
1821
+ get checkbox() {
1822
+ return {
1823
+ checked: this.#symbols["checkbox.checked"],
1824
+ unchecked: this.#symbols["checkbox.unchecked"],
1825
+ };
1826
+ }
1827
+
1828
+ get radio() {
1829
+ return {
1830
+ selected: this.#symbols["radio.selected"],
1831
+ unselected: this.#symbols["radio.unselected"],
1832
+ };
1833
+ }
1834
+
1835
+ get format() {
1836
+ return {
1837
+ bullet: this.#symbols["format.bullet"],
1838
+ dash: this.#symbols["format.dash"],
1839
+ bracketLeft: this.#symbols["format.bracketLeft"],
1840
+ bracketRight: this.#symbols["format.bracketRight"],
1841
+ };
1842
+ }
1843
+
1844
+ get md() {
1845
+ return {
1846
+ quoteBorder: this.#symbols["md.quoteBorder"],
1847
+ hrChar: this.#symbols["md.hrChar"],
1848
+ bullet: this.#symbols["md.bullet"],
1849
+ colorSwatch: this.#symbols["md.colorSwatch"],
1850
+ };
1851
+ }
1852
+
1853
+ /**
1854
+ * Default spinner frames (status spinner).
1855
+ */
1856
+ get spinnerFrames(): string[] {
1857
+ return this.getSpinnerFrames();
1858
+ }
1859
+
1860
+ /**
1861
+ * Get spinner frames by type.
1862
+ */
1863
+ getSpinnerFrames(type: SpinnerType = "status"): string[] {
1864
+ return this.#spinnerFramesOverrides[type] ?? SPINNER_FRAMES[this.symbolPreset][type];
1865
+ }
1866
+
1867
+ /**
1868
+ * Get language icon for a language name.
1869
+ * Maps common language names to their corresponding symbol keys.
1870
+ */
1871
+ getLangIcon(lang: string | undefined): string {
1872
+ if (!lang) return this.#symbols["lang.default"];
1873
+ const normalized = lang.toLowerCase();
1874
+ const key = langMap[normalized];
1875
+ return key ? this.#symbols[key] : this.#symbols["lang.default"];
1876
+ }
1877
+ }
1878
+
1879
+ // ============================================================================
1880
+ // Theme Loading
1881
+ // ============================================================================
1882
+
1883
+ const BUILTIN_THEMES: Record<string, ThemeJson> = {
1884
+ dark: darkThemeJson as ThemeJson,
1885
+ light: lightThemeJson as ThemeJson,
1886
+ ...(defaultThemes as Record<string, ThemeJson>),
1887
+ };
1888
+
1889
+ function getBuiltinThemes(): Record<string, ThemeJson> {
1890
+ return BUILTIN_THEMES;
1891
+ }
1892
+
1893
+ export async function getAvailableThemes(): Promise<string[]> {
1894
+ const themes = new Set<string>(Object.keys(getBuiltinThemes()));
1895
+ const customThemesDir = getCustomThemesDir();
1896
+ try {
1897
+ const files = await fs.promises.readdir(customThemesDir);
1898
+ for (const file of files) {
1899
+ if (file.endsWith(".json")) {
1900
+ themes.add(file.slice(0, -5));
1901
+ }
1902
+ }
1903
+ } catch {
1904
+ // Directory doesn't exist or isn't readable
1905
+ }
1906
+ return Array.from(themes).sort();
1907
+ }
1908
+
1909
+ export interface ThemeInfo {
1910
+ name: string;
1911
+ path: string | undefined;
1912
+ }
1913
+
1914
+ export async function getAvailableThemesWithPaths(): Promise<ThemeInfo[]> {
1915
+ const result: ThemeInfo[] = [];
1916
+
1917
+ // Built-in themes (embedded, no file path)
1918
+ for (const name of Object.keys(getBuiltinThemes())) {
1919
+ result.push({ name, path: undefined });
1920
+ }
1921
+
1922
+ // Custom themes
1923
+ const customThemesDir = getCustomThemesDir();
1924
+ try {
1925
+ const files = await fs.promises.readdir(customThemesDir);
1926
+ for (const file of files) {
1927
+ if (file.endsWith(".json")) {
1928
+ const name = file.slice(0, -5);
1929
+ if (!result.some(themeInfo => themeInfo.name === name)) {
1930
+ result.push({ name, path: path.join(customThemesDir, file) });
1931
+ }
1932
+ }
1933
+ }
1934
+ } catch {
1935
+ // Directory doesn't exist or isn't readable
1936
+ }
1937
+
1938
+ return result.sort((a, b) => a.name.localeCompare(b.name));
1939
+ }
1940
+
1941
+ async function loadThemeJson(name: string): Promise<ThemeJson> {
1942
+ const builtinThemes = getBuiltinThemes();
1943
+ if (name in builtinThemes) {
1944
+ return builtinThemes[name];
1945
+ }
1946
+ const customThemesDir = getCustomThemesDir();
1947
+ const themePath = path.join(customThemesDir, `${name}.json`);
1948
+ let content: string;
1949
+ try {
1950
+ content = await Bun.file(themePath).text();
1951
+ } catch (err) {
1952
+ if (isEnoent(err)) throw new Error(`Theme not found: ${name}`);
1953
+ throw err;
1954
+ }
1955
+ let json: unknown;
1956
+ try {
1957
+ json = JSON.parse(content);
1958
+ } catch (error) {
1959
+ throw new Error(`Failed to parse theme ${name}: ${error}`);
1960
+ }
1961
+ let parsed: ThemeJson;
1962
+ try {
1963
+ parsed = themeJsonSchema(json) as ThemeJson;
1964
+ if (parsed instanceof type.errors) {
1965
+ throw new Error(parsed.summary);
1966
+ }
1967
+ } catch (error) {
1968
+ const errorMessage = error instanceof Error ? error.message : String(error);
1969
+ // Extract color key information if available
1970
+ const missingColorMatch = errorMessage.match(/missing keys: (.+)/i);
1971
+ const missingColors: string[] = missingColorMatch ? missingColorMatch[1].split(",").map(s => s.trim()) : [];
1972
+
1973
+ let fullErrorMessage = `Invalid theme "${name}":\n`;
1974
+ if (missingColors.length > 0) {
1975
+ fullErrorMessage += `\nMissing required color tokens:\n`;
1976
+ fullErrorMessage += missingColors.map(c => ` - ${c}`).join("\n");
1977
+ fullErrorMessage += `\n\nPlease add these colors to your theme's "colors" object.`;
1978
+ fullErrorMessage += `\nSee the built-in themes (dark.json, light.json) for reference values.`;
1979
+ }
1980
+ fullErrorMessage += `\n\nValidation error:\n - ${errorMessage}`;
1981
+
1982
+ throw new Error(fullErrorMessage);
1983
+ }
1984
+ return parsed;
1985
+ }
1986
+
1987
+ interface CreateThemeOptions {
1988
+ mode?: ColorMode;
1989
+ symbolPresetOverride?: SymbolPreset;
1990
+ colorBlindMode?: boolean;
1991
+ }
1992
+
1993
+ /** HSV adjustment to shift green toward blue for colorblind mode (red-green colorblindness) */
1994
+ const COLORBLIND_ADJUSTMENT = { h: 60, s: 0.71 };
1995
+
1996
+ function createTheme(themeJson: ThemeJson, options: CreateThemeOptions = {}): Theme {
1997
+ const { mode, symbolPresetOverride, colorBlindMode } = options;
1998
+ const colorMode = mode ?? detectColorMode();
1999
+ const resolvedColors = resolveThemeColors(themeJson.colors, themeJson.vars);
2000
+
2001
+ if (colorBlindMode) {
2002
+ const added = resolvedColors.toolDiffAdded;
2003
+ if (typeof added === "string" && added.startsWith("#")) {
2004
+ resolvedColors.toolDiffAdded = adjustHsv(added, COLORBLIND_ADJUSTMENT);
2005
+ }
2006
+ }
2007
+
2008
+ const fgColors: Record<ThemeColor, string | number> = {} as Record<ThemeColor, string | number>;
2009
+ const bgColors: Record<ThemeBg, string | number> = {} as Record<ThemeBg, string | number>;
2010
+ const bgColorKeys: Set<string> = new Set([
2011
+ "selectedBg",
2012
+ "userMessageBg",
2013
+ "customMessageBg",
2014
+ "toolPendingBg",
2015
+ "toolSuccessBg",
2016
+ "toolErrorBg",
2017
+ "statusLineBg",
2018
+ ]);
2019
+ for (const [key, value] of Object.entries(resolvedColors)) {
2020
+ if (bgColorKeys.has(key)) {
2021
+ bgColors[key as ThemeBg] = value;
2022
+ } else {
2023
+ fgColors[key as ThemeColor] = value;
2024
+ }
2025
+ }
2026
+ // Extract symbol configuration - settings override takes precedence over theme
2027
+ const symbolPreset: SymbolPreset = symbolPresetOverride ?? themeJson.symbols?.preset ?? "unicode";
2028
+ const symbolOverrides = themeJson.symbols?.overrides ?? {};
2029
+ const spinnerFramesOverrides = normalizeSpinnerFramesOverride(themeJson.symbols?.spinnerFrames);
2030
+ return new Theme(fgColors, bgColors, colorMode, symbolPreset, symbolOverrides, spinnerFramesOverrides);
2031
+ }
2032
+
2033
+ async function loadTheme(name: string, options: CreateThemeOptions = {}): Promise<Theme> {
2034
+ const themeJson = await loadThemeJson(name);
2035
+ return createTheme(themeJson, options);
2036
+ }
2037
+
2038
+ export async function getThemeByName(name: string): Promise<Theme | undefined> {
2039
+ try {
2040
+ return await loadTheme(name);
2041
+ } catch {
2042
+ return undefined;
2043
+ }
2044
+ }
2045
+
2046
+ /** Appearance detected via OSC 11 background color query, or undefined if not yet available. */
2047
+ var terminalReportedAppearance: "dark" | "light" | undefined;
2048
+
2049
+ /** Appearance reported by the macOS fallback observer, or undefined if not yet available. */
2050
+ var macOSReportedAppearance: "dark" | "light" | undefined;
2051
+
2052
+ function shouldUseMacOSAppearanceFallback(): boolean {
2053
+ // Zellij currently breaks OSC 11 passthrough on macOS, so terminal-derived
2054
+ // appearance cannot be trusted there. Fall back to host macOS appearance
2055
+ // without letting it override valid terminal signals elsewhere.
2056
+ return process.platform === "darwin" && !!Bun.env.ZELLIJ;
2057
+ }
2058
+
2059
+ function detectTerminalBackground(): "dark" | "light" {
2060
+ // Tier 1: terminal-reported appearance from OSC 11 luminance.
2061
+ if (!shouldUseMacOSAppearanceFallback() && terminalReportedAppearance) {
2062
+ return terminalReportedAppearance;
2063
+ }
2064
+
2065
+ // Tier 2: COLORFGBG env var (static at process start, but still terminal-derived).
2066
+ const colorfgbg = Bun.env.COLORFGBG || "";
2067
+ if (colorfgbg) {
2068
+ const parts = colorfgbg.split(";");
2069
+ if (parts.length >= 2) {
2070
+ const bg = parseInt(parts[1], 10);
2071
+ if (!Number.isNaN(bg)) return bg < 8 ? "dark" : "light";
2072
+ }
2073
+ }
2074
+
2075
+ // Tier 3: host macOS appearance for known-broken terminal paths only.
2076
+ if (shouldUseMacOSAppearanceFallback()) {
2077
+ const macAppearance = macOSReportedAppearance ?? detectMacOSAppearance();
2078
+ if (macAppearance) return macAppearance;
2079
+ }
2080
+
2081
+ return "dark";
2082
+ }
2083
+
2084
+ function getDefaultTheme(): string {
2085
+ const bg = detectTerminalBackground();
2086
+ return bg === "light" ? autoLightTheme : autoDarkTheme;
2087
+ }
2088
+
2089
+ // ============================================================================
2090
+ // Global Theme Instance
2091
+ // ============================================================================
2092
+
2093
+ export var theme: Theme;
2094
+ var currentThemeName: string | undefined;
2095
+
2096
+ /** Get the name of the currently active theme. */
2097
+ export function getCurrentThemeName(): string | undefined {
2098
+ return currentThemeName;
2099
+ }
2100
+
2101
+ /** Returns unstyled `text` before `initTheme()` assigns the global theme; use only for early-render paths. */
2102
+ export function fgOrPlain(color: ThemeColor, text: string, styledText: string = text): string {
2103
+ return typeof theme === "undefined" ? text : theme.fg(color, styledText);
2104
+ }
2105
+ var currentSymbolPresetOverride: SymbolPreset | undefined;
2106
+ var currentColorBlindMode: boolean = false;
2107
+ var themeWatcher: fs.FSWatcher | undefined;
2108
+ var themeReloadTimer: NodeJS.Timeout | undefined;
2109
+ var sigwinchHandler: (() => void) | undefined;
2110
+ var autoDetectedTheme: boolean = false;
2111
+ var autoDarkTheme: string = "dark";
2112
+ var autoLightTheme: string = "light";
2113
+ var onThemeChangeCallback: (() => void) | undefined;
2114
+ var themeLoadRequestId: number = 0;
2115
+ let themeEpoch = 0;
2116
+
2117
+ function getCurrentThemeOptions(): CreateThemeOptions {
2118
+ return {
2119
+ symbolPresetOverride: currentSymbolPresetOverride,
2120
+ colorBlindMode: currentColorBlindMode,
2121
+ };
2122
+ }
2123
+
2124
+ export async function initTheme(
2125
+ enableWatcher: boolean = false,
2126
+ symbolPreset?: SymbolPreset,
2127
+ colorBlindMode?: boolean,
2128
+ darkTheme?: string,
2129
+ lightTheme?: string,
2130
+ ): Promise<void> {
2131
+ autoDetectedTheme = true;
2132
+ autoDarkTheme = darkTheme ?? "dark";
2133
+ autoLightTheme = lightTheme ?? "light";
2134
+ const name = getDefaultTheme();
2135
+ currentThemeName = name;
2136
+ currentSymbolPresetOverride = symbolPreset;
2137
+ currentColorBlindMode = colorBlindMode ?? false;
2138
+ try {
2139
+ theme = await loadTheme(name, getCurrentThemeOptions());
2140
+ if (enableWatcher) {
2141
+ await startThemeWatcher();
2142
+ startSigwinchListener();
2143
+ }
2144
+ } catch (err) {
2145
+ logger.debug("Theme loading failed, falling back to dark theme", { error: String(err) });
2146
+ currentThemeName = "dark";
2147
+ theme = await loadTheme("dark", getCurrentThemeOptions());
2148
+ // Don't start watcher for fallback theme
2149
+ }
2150
+ }
2151
+
2152
+ export async function setTheme(
2153
+ name: string,
2154
+ enableWatcher: boolean = false,
2155
+ ): Promise<{ success: boolean; error?: string }> {
2156
+ autoDetectedTheme = false;
2157
+ currentThemeName = name;
2158
+ const requestId = ++themeLoadRequestId;
2159
+ try {
2160
+ const loadedTheme = await loadTheme(name, getCurrentThemeOptions());
2161
+ if (requestId !== themeLoadRequestId) {
2162
+ return { success: false, error: "Theme change superseded by a newer request" };
2163
+ }
2164
+ theme = loadedTheme;
2165
+ if (enableWatcher) {
2166
+ await startThemeWatcher();
2167
+ }
2168
+ notifyThemeChange();
2169
+ return { success: true };
2170
+ } catch (error) {
2171
+ if (requestId !== themeLoadRequestId) {
2172
+ return { success: false, error: "Theme change superseded by a newer request" };
2173
+ }
2174
+ // Theme is invalid - fall back to dark theme
2175
+ currentThemeName = "dark";
2176
+ theme = await loadTheme("dark", getCurrentThemeOptions());
2177
+ // The active theme just changed to the fallback — bump the epoch so memoized
2178
+ // renderers (e.g. ToolExecutionComponent) re-shape with the fallback colors
2179
+ // instead of holding the failed theme's stale styling.
2180
+ notifyThemeChange();
2181
+ // Don't start watcher for fallback theme
2182
+ return {
2183
+ success: false,
2184
+ error: error instanceof Error ? error.message : String(error),
2185
+ };
2186
+ }
2187
+ }
2188
+
2189
+ export async function previewTheme(name: string): Promise<{ success: boolean; error?: string }> {
2190
+ const requestId = ++themeLoadRequestId;
2191
+ try {
2192
+ const loadedTheme = await loadTheme(name, getCurrentThemeOptions());
2193
+ if (requestId !== themeLoadRequestId) {
2194
+ return { success: false, error: "Theme preview superseded by a newer request" };
2195
+ }
2196
+ theme = loadedTheme;
2197
+ notifyThemeChange();
2198
+ return { success: true };
2199
+ } catch (error) {
2200
+ if (requestId !== themeLoadRequestId) {
2201
+ return { success: false, error: "Theme preview superseded by a newer request" };
2202
+ }
2203
+ return {
2204
+ success: false,
2205
+ error: error instanceof Error ? error.message : String(error),
2206
+ };
2207
+ }
2208
+ }
2209
+
2210
+ /**
2211
+ * Enable auto-detection mode, switching to the appropriate dark/light theme.
2212
+ */
2213
+ export function enableAutoTheme(): void {
2214
+ autoDetectedTheme = true;
2215
+ reevaluateAutoTheme("enableAutoTheme");
2216
+ }
2217
+
2218
+ /**
2219
+ * Update the theme mappings for auto-detection mode.
2220
+ * When a dark/light mapping changes and auto-detection is active, re-evaluate the theme.
2221
+ */
2222
+ export function setAutoThemeMapping(mode: "dark" | "light", themeName: string): void {
2223
+ if (mode === "dark") autoDarkTheme = themeName;
2224
+ else autoLightTheme = themeName;
2225
+ reevaluateAutoTheme("setAutoThemeMapping");
2226
+ }
2227
+
2228
+ /**
2229
+ * Called when the terminal detects a dark/light appearance change.
2230
+ * The terminal layer queries OSC 11 (background color) and computes luminance;
2231
+ * Mode 2031 notifications trigger re-queries rather than providing the value directly.
2232
+ */
2233
+ export function onTerminalAppearanceChange(mode: "dark" | "light"): void {
2234
+ if (terminalReportedAppearance === mode) return;
2235
+ terminalReportedAppearance = mode;
2236
+ reevaluateAutoTheme("terminal appearance");
2237
+ }
2238
+
2239
+ export function setThemeInstance(themeInstance: Theme): void {
2240
+ autoDetectedTheme = false;
2241
+ theme = themeInstance;
2242
+ currentThemeName = "<in-memory>";
2243
+ stopThemeWatcher();
2244
+ notifyThemeChange();
2245
+ }
2246
+
2247
+ /**
2248
+ * Set the symbol preset override, recreating the theme with the new preset.
2249
+ */
2250
+ export async function setSymbolPreset(preset: SymbolPreset): Promise<void> {
2251
+ currentSymbolPresetOverride = preset;
2252
+ if (!currentThemeName) return;
2253
+
2254
+ const requestId = ++themeLoadRequestId;
2255
+ try {
2256
+ const loadedTheme = await loadTheme(currentThemeName, getCurrentThemeOptions());
2257
+ if (requestId !== themeLoadRequestId) return;
2258
+ theme = loadedTheme;
2259
+ } catch {
2260
+ if (requestId !== themeLoadRequestId) return;
2261
+ // Fall back to dark theme with new preset
2262
+ theme = await loadTheme("dark", getCurrentThemeOptions());
2263
+ if (requestId !== themeLoadRequestId) return;
2264
+ }
2265
+ notifyThemeChange();
2266
+ }
2267
+
2268
+ /**
2269
+ * Get the current symbol preset override.
2270
+ */
2271
+ export function getSymbolPresetOverride(): SymbolPreset | undefined {
2272
+ return currentSymbolPresetOverride;
2273
+ }
2274
+
2275
+ /**
2276
+ * Set color blind mode, recreating the theme with the new setting.
2277
+ * When enabled, uses blue instead of green for diff additions.
2278
+ */
2279
+ export async function setColorBlindMode(enabled: boolean): Promise<void> {
2280
+ currentColorBlindMode = enabled;
2281
+ if (!currentThemeName) return;
2282
+
2283
+ const requestId = ++themeLoadRequestId;
2284
+ try {
2285
+ const loadedTheme = await loadTheme(currentThemeName, getCurrentThemeOptions());
2286
+ if (requestId !== themeLoadRequestId) return;
2287
+ theme = loadedTheme;
2288
+ } catch {
2289
+ if (requestId !== themeLoadRequestId) return;
2290
+ // Fall back to dark theme
2291
+ theme = await loadTheme("dark", getCurrentThemeOptions());
2292
+ if (requestId !== themeLoadRequestId) return;
2293
+ }
2294
+ notifyThemeChange();
2295
+ }
2296
+
2297
+ /**
2298
+ * Get the current color blind mode setting.
2299
+ */
2300
+ export function getColorBlindMode(): boolean {
2301
+ return currentColorBlindMode;
2302
+ }
2303
+
2304
+ export function onThemeChange(callback: () => void): () => void {
2305
+ onThemeChangeCallback = callback;
2306
+ return () => {
2307
+ if (onThemeChangeCallback === callback) {
2308
+ onThemeChangeCallback = undefined;
2309
+ }
2310
+ };
2311
+ }
2312
+
2313
+ /**
2314
+ * Monotonic counter bumped on any theme-affecting change that should invalidate
2315
+ * cached renders: theme swaps and reloads (including the invalid-theme dark
2316
+ * fallback), theme previews, symbol-preset changes, and color-blind-mode
2317
+ * changes — everything that routes through {@link notifyThemeChange}. Consumers
2318
+ * key cached renders on it so the next render re-shapes their output.
2319
+ */
2320
+ export function getThemeEpoch(): number {
2321
+ return themeEpoch;
2322
+ }
2323
+
2324
+ /** Bump the theme epoch and notify the registered theme-change listener. */
2325
+ function notifyThemeChange(): void {
2326
+ themeEpoch++;
2327
+ onThemeChangeCallback?.();
2328
+ }
2329
+
2330
+ /**
2331
+ * Get available symbol presets.
2332
+ */
2333
+ export function getAvailableSymbolPresets(): SymbolPreset[] {
2334
+ return ["unicode", "nerd", "ascii"];
2335
+ }
2336
+
2337
+ /**
2338
+ * Check if a string is a valid symbol preset.
2339
+ */
2340
+ export function isValidSymbolPreset(preset: string): preset is SymbolPreset {
2341
+ return preset === "unicode" || preset === "nerd" || preset === "ascii";
2342
+ }
2343
+
2344
+ async function startThemeWatcher(): Promise<void> {
2345
+ stopThemeWatcher();
2346
+
2347
+ // Only watch if it's a custom theme (not built-in)
2348
+ if (!currentThemeName || currentThemeName === "dark" || currentThemeName === "light") {
2349
+ return;
2350
+ }
2351
+
2352
+ const customThemesDir = getCustomThemesDir();
2353
+ const watchedThemeName = currentThemeName;
2354
+ const watchedFileName = `${watchedThemeName}.json`;
2355
+ const themeFile = path.join(customThemesDir, watchedFileName);
2356
+
2357
+ // Only watch if the file exists
2358
+ if (!fs.existsSync(themeFile)) {
2359
+ return;
2360
+ }
2361
+
2362
+ const scheduleReload = () => {
2363
+ if (themeReloadTimer) {
2364
+ clearTimeout(themeReloadTimer);
2365
+ }
2366
+ themeReloadTimer = setTimeout(() => {
2367
+ themeReloadTimer = undefined;
2368
+
2369
+ // Ignore stale timers after switching themes or stopping the watcher
2370
+ if (currentThemeName !== watchedThemeName) {
2371
+ return;
2372
+ }
2373
+
2374
+ // Keep the last successfully loaded theme active if the file is temporarily missing
2375
+ if (!fs.existsSync(themeFile)) {
2376
+ return;
2377
+ }
2378
+
2379
+ loadTheme(watchedThemeName, getCurrentThemeOptions())
2380
+ .then(loadedTheme => {
2381
+ theme = loadedTheme;
2382
+ notifyThemeChange();
2383
+ })
2384
+ .catch(() => {
2385
+ // Ignore errors (file might be in invalid state while being edited)
2386
+ });
2387
+ }, 100);
2388
+ };
2389
+
2390
+ try {
2391
+ themeWatcher = fs.watch(customThemesDir, (_eventType, filename) => {
2392
+ if (currentThemeName !== watchedThemeName) {
2393
+ return;
2394
+ }
2395
+ if (!filename) {
2396
+ scheduleReload();
2397
+ return;
2398
+ }
2399
+ const changedFile = String(filename);
2400
+ if (changedFile !== watchedFileName) {
2401
+ return;
2402
+ }
2403
+ scheduleReload();
2404
+ });
2405
+ } catch {
2406
+ // Ignore errors starting watcher
2407
+ }
2408
+ }
2409
+
2410
+ /**
2411
+ * Shared logic for re-evaluating the auto-detected theme.
2412
+ * Called from SIGWINCH, terminal appearance change handler, and macOS fallback observer.
2413
+ */
2414
+ function reevaluateAutoTheme(debugLabel: string): void {
2415
+ if (!autoDetectedTheme) return;
2416
+ const resolved = getDefaultTheme();
2417
+ if (resolved === currentThemeName) return;
2418
+ currentThemeName = resolved;
2419
+ loadTheme(resolved, getCurrentThemeOptions())
2420
+ .then(loadedTheme => {
2421
+ theme = loadedTheme;
2422
+ notifyThemeChange();
2423
+ })
2424
+ .catch(err => {
2425
+ logger.debug(`Theme switch on ${debugLabel} failed`, { error: String(err) });
2426
+ });
2427
+ }
2428
+
2429
+ // ============================================================================
2430
+ // macOS Appearance Fallback Observer
2431
+ // ============================================================================
2432
+
2433
+ var macObserver: { stop(): void } | undefined;
2434
+
2435
+ function startMacAppearanceObserver(): void {
2436
+ stopMacAppearanceObserver();
2437
+ if (!shouldUseMacOSAppearanceFallback()) return;
2438
+ try {
2439
+ macOSReportedAppearance = detectMacOSAppearance() ?? undefined;
2440
+ macObserver = MacAppearanceObserver.start((err, appearance) => {
2441
+ if (!err && (appearance === "dark" || appearance === "light")) {
2442
+ macOSReportedAppearance = appearance;
2443
+ reevaluateAutoTheme("macOS fallback");
2444
+ }
2445
+ });
2446
+ } catch (err) {
2447
+ logger.warn("Failed to start macOS appearance observer", { err });
2448
+ }
2449
+ }
2450
+
2451
+ function stopMacAppearanceObserver(): void {
2452
+ if (macObserver) {
2453
+ macObserver.stop();
2454
+ macObserver = undefined;
2455
+ }
2456
+ macOSReportedAppearance = undefined;
2457
+ }
2458
+
2459
+ // ============================================================================
2460
+ // SIGWINCH Listener
2461
+ // ============================================================================
2462
+
2463
+ /** Re-check appearance on SIGWINCH and switch dark/light when using auto-detected theme. */
2464
+ function startSigwinchListener(): void {
2465
+ stopSigwinchListener();
2466
+ sigwinchHandler = () => {
2467
+ reevaluateAutoTheme("SIGWINCH");
2468
+ };
2469
+ process.on("SIGWINCH", sigwinchHandler);
2470
+ startMacAppearanceObserver();
2471
+ }
2472
+
2473
+ function stopSigwinchListener(): void {
2474
+ if (sigwinchHandler) {
2475
+ process.removeListener("SIGWINCH", sigwinchHandler);
2476
+ sigwinchHandler = undefined;
2477
+ }
2478
+ stopMacAppearanceObserver();
2479
+ }
2480
+
2481
+ export function stopThemeWatcher(): void {
2482
+ if (themeReloadTimer) {
2483
+ clearTimeout(themeReloadTimer);
2484
+ themeReloadTimer = undefined;
2485
+ }
2486
+ if (themeWatcher) {
2487
+ themeWatcher.close();
2488
+ themeWatcher = undefined;
2489
+ }
2490
+ stopSigwinchListener();
2491
+ terminalReportedAppearance = undefined;
2492
+ }
2493
+
2494
+ // ============================================================================
2495
+ // HTML Export Helpers
2496
+ // ============================================================================
2497
+
2498
+ /**
2499
+ * Convert a 256-color index to hex string.
2500
+ * Indices 0-15: basic colors (approximate)
2501
+ * Indices 16-231: 6x6x6 color cube
2502
+ * Indices 232-255: grayscale ramp
2503
+ */
2504
+ function ansi256ToHex(index: number): string {
2505
+ // Basic colors (0-15) - approximate common terminal values
2506
+ const basicColors = [
2507
+ "#000000",
2508
+ "#800000",
2509
+ "#008000",
2510
+ "#808000",
2511
+ "#000080",
2512
+ "#800080",
2513
+ "#008080",
2514
+ "#c0c0c0",
2515
+ "#808080",
2516
+ "#ff0000",
2517
+ "#00ff00",
2518
+ "#ffff00",
2519
+ "#0000ff",
2520
+ "#ff00ff",
2521
+ "#00ffff",
2522
+ "#ffffff",
2523
+ ];
2524
+ if (index < 16) {
2525
+ return basicColors[index];
2526
+ }
2527
+
2528
+ // Color cube (16-231): 6x6x6 = 216 colors
2529
+ if (index < 232) {
2530
+ const cubeIndex = index - 16;
2531
+ const r = Math.floor(cubeIndex / 36);
2532
+ const g = Math.floor((cubeIndex % 36) / 6);
2533
+ const b = cubeIndex % 6;
2534
+ const toHex = (n: number) => (n === 0 ? 0 : 55 + n * 40).toString(16).padStart(2, "0");
2535
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
2536
+ }
2537
+
2538
+ // Grayscale (232-255): 24 shades
2539
+ const gray = 8 + (index - 232) * 10;
2540
+ const grayHex = gray.toString(16).padStart(2, "0");
2541
+ return `#${grayHex}${grayHex}${grayHex}`;
2542
+ }
2543
+
2544
+ /**
2545
+ * Classify a parsed theme JSON as light/dark by the perceived luminance of its
2546
+ * status-line background. Mirrors {@link Theme.isLight} so the synchronous
2547
+ * helpers below stay in lockstep with the runtime classifier — see the comment
2548
+ * on `Theme.statusLineLuminance` for why `statusLineBg` is the source of truth
2549
+ * (themes like `porcelain` style a dark chat bubble on an otherwise-light
2550
+ * theme, so `userMessageBg` is unreliable).
2551
+ */
2552
+ function isLightThemeJson(themeJson: ThemeJson): boolean {
2553
+ try {
2554
+ const resolved = resolveVarRefs(themeJson.colors.statusLineBg, themeJson.vars ?? {});
2555
+ const luminance = colorLuma(resolved);
2556
+ return luminance !== undefined && luminance > 0.5;
2557
+ } catch {
2558
+ return false;
2559
+ }
2560
+ }
2561
+
2562
+ function getHtmlDefaultTextForSurface(surface: string | number | undefined): string {
2563
+ const luminance = surface === undefined ? undefined : colorLuma(surface);
2564
+ return luminance !== undefined && luminance > 0.5 ? "#000000" : "#e5e5e7";
2565
+ }
2566
+
2567
+ function resolveThemeExportColors(themeJson: ThemeJson): {
2568
+ pageBg?: string;
2569
+ cardBg?: string;
2570
+ infoBg?: string;
2571
+ } {
2572
+ const exportSection = themeJson.export;
2573
+ if (!exportSection) return {};
2574
+
2575
+ const vars = themeJson.vars ?? {};
2576
+ const resolve = (value: string | number | undefined): string | undefined => {
2577
+ if (value === undefined) return undefined;
2578
+ if (typeof value === "number") return ansi256ToHex(value);
2579
+ if (value === "" || value.startsWith("#")) return value;
2580
+ const varName = value.startsWith("$") ? value.slice(1) : value;
2581
+ if (varName in vars) {
2582
+ const resolved = resolveVarRefs(varName, vars);
2583
+ return typeof resolved === "number" ? ansi256ToHex(resolved) : resolved;
2584
+ }
2585
+ return value;
2586
+ };
2587
+
2588
+ return {
2589
+ pageBg: resolve(exportSection.pageBg),
2590
+ cardBg: resolve(exportSection.cardBg),
2591
+ infoBg: resolve(exportSection.infoBg),
2592
+ };
2593
+ }
2594
+
2595
+ /**
2596
+ * Get resolved theme colors as CSS-compatible hex strings.
2597
+ * Used by HTML export to generate CSS custom properties.
2598
+ */
2599
+ export async function getResolvedThemeColors(themeName?: string): Promise<Record<string, string>> {
2600
+ const name = themeName ?? getDefaultTheme();
2601
+ const themeJson = await loadThemeJson(name);
2602
+ const exportColors = resolveThemeExportColors(themeJson);
2603
+ const resolved = resolveThemeColors(themeJson.colors, themeJson.vars);
2604
+
2605
+ // Empty foreground tokens use the terminal default color. In HTML export,
2606
+ // that default must contrast the export surface, not the TUI status line:
2607
+ // custom light themes can still export dark transcript cards when they omit
2608
+ // `export`, because generateThemeVars derives those cards from userMessageBg.
2609
+ const defaultText = getHtmlDefaultTextForSurface(
2610
+ exportColors.cardBg ?? exportColors.pageBg ?? resolved.userMessageBg,
2611
+ );
2612
+
2613
+ const cssColors: Record<string, string> = {};
2614
+ for (const [key, value] of Object.entries(resolved)) {
2615
+ if (typeof value === "number") {
2616
+ cssColors[key] = ansi256ToHex(value);
2617
+ } else if (value === "") {
2618
+ // Empty means default terminal color - use sensible fallback for HTML
2619
+ cssColors[key] = defaultText;
2620
+ } else {
2621
+ cssColors[key] = value;
2622
+ }
2623
+ }
2624
+ return cssColors;
2625
+ }
2626
+
2627
+ /**
2628
+ * Check if a theme is a "light" theme by analyzing its status-line background
2629
+ * luminance. Loads theme JSON synchronously (built-in or custom file on disk)
2630
+ * for callers in synchronous flows (settings migration, setup wizard).
2631
+ */
2632
+ export function isLightTheme(themeName?: string): boolean {
2633
+ const name = themeName ?? "dark";
2634
+ const builtinThemes = getBuiltinThemes();
2635
+ let themeJson: ThemeJson | undefined;
2636
+ if (name in builtinThemes) {
2637
+ themeJson = builtinThemes[name];
2638
+ } else {
2639
+ try {
2640
+ const customPath = path.join(getCustomThemesDir(), `${name}.json`);
2641
+ const content = fs.readFileSync(customPath, "utf-8");
2642
+ themeJson = JSON.parse(content) as ThemeJson;
2643
+ } catch {
2644
+ return false;
2645
+ }
2646
+ }
2647
+ return isLightThemeJson(themeJson);
2648
+ }
2649
+
2650
+ /**
2651
+ * Get explicit export colors from theme JSON, if specified.
2652
+ * Returns undefined for each color that isn't explicitly set.
2653
+ */
2654
+ export async function getThemeExportColors(themeName?: string): Promise<{
2655
+ pageBg?: string;
2656
+ cardBg?: string;
2657
+ infoBg?: string;
2658
+ }> {
2659
+ const name = themeName ?? getDefaultTheme();
2660
+ try {
2661
+ const themeJson = await loadThemeJson(name);
2662
+ return resolveThemeExportColors(themeJson);
2663
+ } catch {
2664
+ return {};
2665
+ }
2666
+ }
2667
+
2668
+ // ============================================================================
2669
+ // TUI Helpers
2670
+ // ============================================================================
2671
+
2672
+ let cachedHighlightColorsFor: Theme | undefined;
2673
+ let cachedHighlightColors: NativeHighlightColors | undefined;
2674
+
2675
+ function getHighlightColors(t: Theme): NativeHighlightColors {
2676
+ if (cachedHighlightColorsFor !== t || !cachedHighlightColors) {
2677
+ cachedHighlightColorsFor = t;
2678
+ cachedHighlightColors = {
2679
+ comment: t.getFgAnsi("syntaxComment"),
2680
+ keyword: t.getFgAnsi("syntaxKeyword"),
2681
+ function: t.getFgAnsi("syntaxFunction"),
2682
+ variable: t.getFgAnsi("syntaxVariable"),
2683
+ string: t.getFgAnsi("syntaxString"),
2684
+ number: t.getFgAnsi("syntaxNumber"),
2685
+ type: t.getFgAnsi("syntaxType"),
2686
+ operator: t.getFgAnsi("syntaxOperator"),
2687
+ punctuation: t.getFgAnsi("syntaxPunctuation"),
2688
+ inserted: t.getFgAnsi("toolDiffAdded"),
2689
+ deleted: t.getFgAnsi("toolDiffRemoved"),
2690
+ };
2691
+ }
2692
+ return cachedHighlightColors;
2693
+ }
2694
+
2695
+ /**
2696
+ * Memoized native syntax highlight. Returns the joined ANSI string, or `null`
2697
+ * when the native tokenizer throws so callers can apply their own fallback.
2698
+ *
2699
+ * Keyed on `(lang, code)` and reset whenever the active `theme` instance
2700
+ * changes — the ANSI colors are baked into the highlighted output, so a theme
2701
+ * switch (which always reassigns `theme`) must invalidate every entry.
2702
+ *
2703
+ * Why this exists: animated tool blocks (eval/bash) repaint their box on every
2704
+ * ~33ms border-shimmer frame, and markdown re-lexes on every streamed delta.
2705
+ * Without memoization each frame can re-tokenize an unchanged code body through
2706
+ * the Rust FFI — ~26ms for 100 lines, ~40ms for 150 — consuming or overrunning
2707
+ * the 33ms frame budget and starving the spinner/render timers (the "TUI freeze").
2708
+ */
2709
+ const HIGHLIGHT_CACHE_MAX = 256;
2710
+ const highlightCache = new LRUCache<string, string>({ max: HIGHLIGHT_CACHE_MAX });
2711
+ let highlightCacheTheme: Theme | undefined;
2712
+
2713
+ function highlightCached(code: string, validLang: string | undefined, highlightTheme: Theme): string | null {
2714
+ if (highlightCacheTheme !== highlightTheme) {
2715
+ highlightCache.clear();
2716
+ highlightCacheTheme = highlightTheme;
2717
+ }
2718
+ const key = `${validLang ?? ""}\x00${code}`;
2719
+ const hit = highlightCache.get(key);
2720
+ if (hit !== undefined) {
2721
+ return hit;
2722
+ }
2723
+ let highlighted: string;
2724
+ try {
2725
+ highlighted = nativeHighlightCode(code, validLang, getHighlightColors(highlightTheme));
2726
+ } catch {
2727
+ return null;
2728
+ }
2729
+ highlightCache.set(key, highlighted);
2730
+ return highlighted;
2731
+ }
2732
+
2733
+ /**
2734
+ * Highlight code with syntax coloring based on file extension or language.
2735
+ * Returns array of highlighted lines.
2736
+ */
2737
+ export function highlightCode(code: string, lang?: string, highlightTheme: Theme = theme): string[] {
2738
+ const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
2739
+ const highlighted = highlightCached(code, validLang, highlightTheme);
2740
+ // Always return a fresh array: callers (e.g. renderCodeCell) push extra lines
2741
+ // onto the result, which would corrupt the cached string otherwise.
2742
+ return (highlighted ?? code).split("\n");
2743
+ }
2744
+
2745
+ export function getSymbolTheme(): SymbolTheme {
2746
+ // Guard against `theme` being undefined (pre-init or cross-module-instance
2747
+ // plugin calls). Fall back to the ASCII preset so the returned symbols are
2748
+ // usable instead of crashing. See #2998.
2749
+ if (typeof theme === "undefined") {
2750
+ const box = {
2751
+ topLeft: "+",
2752
+ topRight: "+",
2753
+ bottomLeft: "+",
2754
+ bottomRight: "+",
2755
+ horizontal: "-",
2756
+ vertical: "|",
2757
+ cross: "+",
2758
+ teeDown: "+",
2759
+ teeUp: "+",
2760
+ teeLeft: "+",
2761
+ teeRight: "+",
2762
+ };
2763
+ return {
2764
+ cursor: ">",
2765
+ inputCursor: "|",
2766
+ boxRound: box,
2767
+ boxSharp: box,
2768
+ table: box,
2769
+ quoteBorder: "|",
2770
+ hrChar: "-",
2771
+ colorSwatch: "[]",
2772
+ spinnerFrames: ["-", "\\", "|", "/"],
2773
+ };
2774
+ }
2775
+ const preset = theme.getSymbolPreset();
2776
+
2777
+ return {
2778
+ cursor: theme.nav.cursor,
2779
+ inputCursor: preset === "ascii" ? "|" : "▏",
2780
+ boxRound: theme.boxRound,
2781
+ boxSharp: theme.boxSharp,
2782
+ table: theme.boxSharp,
2783
+ quoteBorder: theme.md.quoteBorder,
2784
+ hrChar: theme.md.hrChar,
2785
+ colorSwatch: theme.md.colorSwatch,
2786
+ spinnerFrames: theme.getSpinnerFrames("activity"),
2787
+ };
2788
+ }
2789
+
2790
+ let cachedMarkdownTheme: MarkdownTheme | undefined;
2791
+ let cachedMarkdownThemeRef: Theme | undefined;
2792
+
2793
+ export function getMarkdownTheme(): MarkdownTheme {
2794
+ if (cachedMarkdownTheme !== undefined && cachedMarkdownThemeRef === theme) {
2795
+ return cachedMarkdownTheme;
2796
+ }
2797
+ // Mermaid ASCII diagrams render with the active palette so they read as
2798
+ // content rather than raw monochrome. Roles mirror the SVG renderer's
2799
+ // mapping; `text`/`muted`/`border`/`borderMuted`/`accent` exist in every theme.
2800
+ const mermaidColorMode = theme.getColorMode() === "truecolor" ? "truecolor" : "ansi256";
2801
+ const mermaidTheme = {
2802
+ fg: theme.getColorHex("text"),
2803
+ border: theme.getColorHex("border"),
2804
+ line: theme.getColorHex("muted"),
2805
+ arrow: theme.getColorHex("accent"),
2806
+ corner: theme.getColorHex("muted"),
2807
+ junction: theme.getColorHex("borderMuted"),
2808
+ };
2809
+ const markdownTheme: MarkdownTheme = {
2810
+ heading: (text: string) => theme.fg("mdHeading", text),
2811
+ link: (text: string) => theme.fg("mdLink", text),
2812
+ linkUrl: (text: string) => theme.fg("mdLinkUrl", text),
2813
+ code: (text: string) => theme.fg("mdCode", text),
2814
+ codeBlock: (text: string) => theme.fg("mdCodeBlock", text),
2815
+ codeBlockBorder: (text: string) => theme.fg("mdCodeBlockBorder", text),
2816
+ quote: (text: string) => theme.fg("mdQuote", text),
2817
+ quoteBorder: (text: string) => theme.fg("mdQuoteBorder", text),
2818
+ hr: (text: string) => theme.fg("mdHr", text),
2819
+ listBullet: (text: string) => theme.fg("mdListBullet", text),
2820
+ bold: (text: string) => theme.bold(text),
2821
+ italic: (text: string) => theme.italic(text),
2822
+ underline: (text: string) => theme.underline(text),
2823
+ strikethrough: (text: string) => chalk.strikethrough(text),
2824
+ symbols: getSymbolTheme(),
2825
+ resolveMermaidAscii: (source, maxWidth) =>
2826
+ resolveMermaidAscii(source, { maxWidth, theme: mermaidTheme, colorMode: mermaidColorMode }),
2827
+ highlightCode: (code: string, lang?: string): string[] => {
2828
+ const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
2829
+ const highlighted = highlightCached(code, validLang, theme);
2830
+ if (highlighted !== null) return highlighted.split("\n");
2831
+ return code.split("\n").map(line => theme.fg("mdCodeBlock", line));
2832
+ },
2833
+ };
2834
+ cachedMarkdownTheme = markdownTheme;
2835
+ cachedMarkdownThemeRef = theme;
2836
+ return markdownTheme;
2837
+ }
2838
+
2839
+ export function getSelectListTheme(): SelectListTheme {
2840
+ // Guard against `theme` being undefined (pre-init or cross-module-instance
2841
+ // plugin calls). See #2998.
2842
+ if (typeof theme === "undefined") {
2843
+ return {
2844
+ selectedPrefix: (text: string) => text,
2845
+ selectedText: (text: string) => text,
2846
+ description: (text: string) => text,
2847
+ scrollInfo: (text: string) => text,
2848
+ noMatch: (text: string) => text,
2849
+ symbols: getSymbolTheme(),
2850
+ hovered: (text: string) => text,
2851
+ };
2852
+ }
2853
+ return {
2854
+ selectedPrefix: (text: string) => theme.fg("accent", text),
2855
+ selectedText: (text: string) => theme.fg("accent", text),
2856
+ description: (text: string) => theme.fg("muted", text),
2857
+ scrollInfo: (text: string) => theme.fg("muted", text),
2858
+ noMatch: (text: string) => theme.fg("muted", text),
2859
+ symbols: getSymbolTheme(),
2860
+ hovered: (text: string) => theme.bg("selectedBg", text),
2861
+ };
2862
+ }
2863
+
2864
+ export function getEditorTheme(): EditorTheme {
2865
+ // Guard against `theme` being undefined (pre-init or cross-module-instance
2866
+ // plugin calls). See #2998.
2867
+ if (typeof theme === "undefined") {
2868
+ return {
2869
+ borderColor: (text: string) => text,
2870
+ selectList: getSelectListTheme(),
2871
+ symbols: getSymbolTheme(),
2872
+ hintStyle: (text: string) => text,
2873
+ };
2874
+ }
2875
+ return {
2876
+ borderColor: (text: string) => theme.fg("borderMuted", text),
2877
+ selectList: getSelectListTheme(),
2878
+ symbols: getSymbolTheme(),
2879
+ hintStyle: (text: string) => theme.fg("dim", text),
2880
+ };
2881
+ }
2882
+
2883
+ export function getSettingsListTheme(): SettingsListTheme {
2884
+ // Plugins (e.g. pi-rtk-optimizer) may call this before `initTheme()` assigns
2885
+ // the global `theme`, or from a separate module instance under npm-global
2886
+ // installs where the live binding was never initialized. Fall back to plain
2887
+ // text so the call returns a usable (unstyled) theme instead of crashing with
2888
+ // "undefined is not an object (evaluating 'theme.fg')". See #2998.
2889
+ if (typeof theme === "undefined") {
2890
+ return {
2891
+ label: (text: string) => text,
2892
+ value: (text: string) => text,
2893
+ description: (text: string) => text,
2894
+ cursor: "> ",
2895
+ hint: (text: string) => text,
2896
+ heading: (text: string) => text,
2897
+ section: (text: string) => text,
2898
+ hovered: (text: string) => text,
2899
+ };
2900
+ }
2901
+ return {
2902
+ label: (text: string, selected: boolean, changed: boolean) =>
2903
+ changed ? theme.fg("statusLineGitDirty", text) : selected ? theme.fg("accent", text) : text,
2904
+ value: (text: string, selected: boolean, changed: boolean) =>
2905
+ changed ? theme.fg("statusLineGitDirty", text) : selected ? theme.fg("accent", text) : theme.fg("muted", text),
2906
+ description: (text: string) => theme.fg("dim", text),
2907
+ cursor: theme.fg("accent", `${theme.nav.cursor} `),
2908
+ hint: (text: string) => theme.fg("dim", text),
2909
+ heading: (text: string, dimmed: boolean) =>
2910
+ dimmed ? theme.fg("dim", theme.underline(text)) : theme.fg("muted", theme.bold(theme.underline(text))),
2911
+ section: (text: string, active: boolean) =>
2912
+ active ? theme.fg("accent", theme.bold(text)) : theme.fg("muted", text),
2913
+ hovered: (text: string) => theme.bg("selectedBg", text),
2914
+ };
2915
+ }