@knightcodeai/cli 0.17.4 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/CHANGELOG.md +1303 -0
  2. package/README.md +2 -2
  3. package/dist/bundle/chunks/anthropic-messages-A2IH2IY3.js +6 -0
  4. package/dist/bundle/chunks/anthropic.js +101 -0
  5. package/dist/bundle/chunks/azure-openai-responses-XZILQZOG.js +2 -0
  6. package/dist/bundle/chunks/bedrock-converse-stream.js +89 -0
  7. package/dist/bundle/chunks/chunk-253N4337.js +7 -0
  8. package/dist/bundle/chunks/chunk-2QIKI63P.js +108 -0
  9. package/dist/bundle/chunks/chunk-3LX7U45C.js +130 -0
  10. package/dist/bundle/chunks/chunk-4CRI7BMY.js +2 -0
  11. package/dist/bundle/chunks/chunk-57BPR4HF.js +2 -0
  12. package/dist/bundle/chunks/chunk-A5LJINMT.js +2 -0
  13. package/dist/bundle/chunks/chunk-AANBXVWQ.js +2 -0
  14. package/dist/bundle/chunks/chunk-AHB7JV5B.js +2 -0
  15. package/dist/bundle/chunks/chunk-B2FT7RH5.js +2 -0
  16. package/dist/bundle/chunks/chunk-BPNRVFVQ.js +2 -0
  17. package/dist/bundle/chunks/chunk-BRKUE2MD.js +2 -0
  18. package/dist/bundle/chunks/chunk-BXZL5C3N.js +58 -0
  19. package/dist/bundle/chunks/chunk-DGACKL4S.js +30 -0
  20. package/dist/bundle/chunks/chunk-DKOURXGU.js +2 -0
  21. package/dist/bundle/chunks/chunk-DVFCXHT4.js +2 -0
  22. package/dist/bundle/chunks/chunk-ETOMYYZC.js +2 -0
  23. package/dist/bundle/chunks/chunk-FA32X2UJ.js +7 -0
  24. package/dist/bundle/chunks/chunk-FHM6KUY3.js +44 -0
  25. package/dist/bundle/chunks/chunk-FO7CEQB3.js +11 -0
  26. package/dist/bundle/chunks/chunk-FXS5MU6B.js +2 -0
  27. package/dist/bundle/chunks/chunk-HHWP56PR.js +2 -0
  28. package/dist/bundle/chunks/chunk-HSL72VZI.js +2 -0
  29. package/dist/bundle/chunks/chunk-HUQB436V.js +2 -0
  30. package/dist/bundle/chunks/chunk-HWXGL4YB.js +2 -0
  31. package/dist/bundle/chunks/chunk-KQKUEQJA.js +6 -0
  32. package/dist/bundle/chunks/chunk-LROWZ6UF.js +1333 -0
  33. package/dist/bundle/chunks/chunk-MOALMQWH.js +9 -0
  34. package/dist/bundle/chunks/chunk-MXIWLMNT.js +8 -0
  35. package/dist/bundle/chunks/chunk-N6VDWLND.js +2 -0
  36. package/dist/bundle/chunks/chunk-NCIBB66G.js +2 -0
  37. package/dist/bundle/chunks/chunk-NDQ5OW6U.js +2 -0
  38. package/dist/bundle/chunks/chunk-O6JSP6YG.js +6 -0
  39. package/dist/bundle/chunks/chunk-OFST7FDG.js +2 -0
  40. package/dist/bundle/chunks/chunk-SPAUY3RD.js +12 -0
  41. package/dist/bundle/chunks/chunk-UZWP3NHW.js +2 -0
  42. package/dist/bundle/chunks/chunk-V5ANFNKF.js +11 -0
  43. package/dist/bundle/chunks/chunk-VJJ6J35N.js +20 -0
  44. package/dist/bundle/chunks/chunk-ZW7FVRHT.js +31 -0
  45. package/dist/bundle/chunks/cli-DFJTSQI6.js +51 -0
  46. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-6E2UM7IG.js +2 -0
  47. package/dist/bundle/chunks/codemode-worker.js +478 -0
  48. package/dist/bundle/chunks/dist-DKI7Q6GY.js +13 -0
  49. package/dist/bundle/chunks/easter-egg-3d-7O2AHLAQ.js +2 -0
  50. package/dist/bundle/chunks/execute-4CLOTQIS.js +499 -0
  51. package/dist/bundle/chunks/github-copilot.js +2 -0
  52. package/dist/bundle/chunks/google-generative-ai-CSWR3ORZ.js +2 -0
  53. package/dist/bundle/chunks/google-vertex-QIDRFOFB.js +2 -0
  54. package/dist/bundle/chunks/image-resize-worker.js +2 -0
  55. package/dist/bundle/chunks/jiti-loader-BXVX3Y2R.js +21 -0
  56. package/dist/bundle/chunks/jiti-static-loader-NNBCFAO5.js +2 -0
  57. package/dist/bundle/chunks/kimi-coding.js +2 -0
  58. package/dist/bundle/chunks/knightcode-messages-3GNOTMSI.js +8 -0
  59. package/dist/bundle/chunks/lib-IHWMWJDR.js +9 -0
  60. package/dist/bundle/chunks/llama-cpp-classify-HRPRKOZA.js +22 -0
  61. package/dist/bundle/chunks/meta.js +2 -0
  62. package/dist/bundle/chunks/mistral-conversations-46TK5OBA.js +5 -0
  63. package/dist/bundle/chunks/multipart-parser-VTTMF2EO.js +3 -0
  64. package/dist/bundle/chunks/node-7S3GFMOZ.js +16 -0
  65. package/dist/bundle/chunks/node-MAEICQNS.js +2 -0
  66. package/dist/bundle/chunks/openai-chatgpt.js +101 -0
  67. package/dist/bundle/chunks/openai-codex-responses-IT7VEKUU.js +10 -0
  68. package/dist/bundle/chunks/openai-codex.js +101 -0
  69. package/dist/bundle/chunks/openai-completions-LOKIW33V.js +7 -0
  70. package/dist/bundle/chunks/openai-decisions-4F3LWHMG.js +5 -0
  71. package/dist/bundle/chunks/openai-responses-OK36GJXP.js +3 -0
  72. package/dist/bundle/chunks/openrouter-images-ZI7LLZ4Y.js +2 -0
  73. package/dist/bundle/chunks/openrouter.js +101 -0
  74. package/dist/bundle/chunks/radius.js +101 -0
  75. package/dist/bundle/chunks/runtime-IN7ZPKFY.js +2 -0
  76. package/dist/bundle/chunks/src-ETRMT73D.js +4 -0
  77. package/dist/bundle/chunks/typesafe-system-one-J2CJBWEH.js +2 -0
  78. package/dist/bundle/chunks/virtual-modules-7VXI3TVN.js +2 -0
  79. package/dist/bundle/chunks/xai.js +2 -0
  80. package/dist/bundle/cli-runtime.js +3 -0
  81. package/dist/bundle/cli.js +16 -0
  82. package/dist/bundle/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  83. package/dist/bundle/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  84. package/dist/bundle/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  85. package/dist/bundle/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  86. package/dist/bundle/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  87. package/dist/bundle/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  88. package/dist/core/export-html/template.css +1015 -0
  89. package/dist/core/export-html/template.html +55 -0
  90. package/dist/core/export-html/template.js +1929 -0
  91. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  92. package/dist/core/export-html/vendor/marked.min.js +78 -0
  93. package/dist/modes/interactive/theme/dark.json +74 -0
  94. package/dist/modes/interactive/theme/light.json +74 -0
  95. package/dist/modes/interactive/theme/system-theme.ts +579 -0
  96. package/dist/modes/interactive/theme/theme-controller.ts +253 -0
  97. package/dist/modes/interactive/theme/theme-schema.ts +145 -0
  98. package/dist/modes/interactive/theme/theme-tokens.ts +145 -0
  99. package/dist/modes/interactive/theme/theme.ts +1076 -0
  100. package/docs/app-server.md +479 -0
  101. package/docs/app-server.schema.json +8128 -0
  102. package/docs/cli-integration.md +106 -0
  103. package/docs/cli.md +393 -0
  104. package/docs/codemode.md +230 -0
  105. package/docs/compaction.md +463 -0
  106. package/docs/configuration.md +48 -0
  107. package/docs/containerization.md +143 -0
  108. package/docs/custom-provider.md +225 -0
  109. package/docs/desktop.md +73 -0
  110. package/docs/docs.json +236 -0
  111. package/docs/environment-variables.md +138 -0
  112. package/docs/extensions.md +420 -0
  113. package/docs/how-knightcode-works.md +49 -0
  114. package/docs/images/doom-extension.png +0 -0
  115. package/docs/images/interactive-mode.png +0 -0
  116. package/docs/images/tree-view.png +0 -0
  117. package/docs/index.md +39 -0
  118. package/docs/json.md +226 -0
  119. package/docs/keybindings.md +197 -0
  120. package/docs/llama-cpp.md +121 -0
  121. package/docs/mcp.md +272 -0
  122. package/docs/message-types.md +279 -0
  123. package/docs/models-configuration.md +187 -0
  124. package/docs/models.md +221 -0
  125. package/docs/nvidia-rtx-llama-cpp.md +96 -0
  126. package/docs/packages.md +129 -0
  127. package/docs/plan-mode.md +121 -0
  128. package/docs/prompt-templates.md +59 -0
  129. package/docs/providers.md +282 -0
  130. package/docs/quickstart.md +128 -0
  131. package/docs/remote.md +87 -0
  132. package/docs/rpc-commands.md +860 -0
  133. package/docs/rpc-extension-ui.md +214 -0
  134. package/docs/rpc.md +193 -0
  135. package/docs/sdk.md +197 -0
  136. package/docs/security.md +99 -0
  137. package/docs/session-format.md +358 -0
  138. package/docs/sessions.md +135 -0
  139. package/docs/settings.md +220 -0
  140. package/docs/shell-aliases.md +93 -0
  141. package/docs/skills.md +93 -0
  142. package/docs/slash-commands.md +78 -0
  143. package/docs/terminal-setup.md +242 -0
  144. package/docs/termux.md +117 -0
  145. package/docs/themes.md +140 -0
  146. package/docs/tmux.md +55 -0
  147. package/docs/tui.md +195 -0
  148. package/docs/usage.md +167 -0
  149. package/docs/virtual-models.md +114 -0
  150. package/docs/windows.md +67 -0
  151. package/examples/README.md +36 -0
  152. package/examples/extensions/README.md +214 -0
  153. package/examples/extensions/auto-commit-on-exit.ts +55 -0
  154. package/examples/extensions/bash-spawn-hook.ts +30 -0
  155. package/examples/extensions/bookmark.ts +50 -0
  156. package/examples/extensions/border-status-editor.ts +151 -0
  157. package/examples/extensions/built-in-tool-renderer.ts +225 -0
  158. package/examples/extensions/claude-rules.ts +89 -0
  159. package/examples/extensions/commands.ts +72 -0
  160. package/examples/extensions/confirm-destructive.ts +60 -0
  161. package/examples/extensions/custom-compaction.ts +118 -0
  162. package/examples/extensions/custom-footer.ts +66 -0
  163. package/examples/extensions/custom-header.ts +73 -0
  164. package/examples/extensions/custom-provider-anthropic/index.ts +617 -0
  165. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  166. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  167. package/examples/extensions/custom-provider-gitlab-duo/index.ts +399 -0
  168. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  169. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  170. package/examples/extensions/debug-provider.ts +90 -0
  171. package/examples/extensions/dirty-repo-guard.ts +56 -0
  172. package/examples/extensions/doom-overlay/README.md +46 -0
  173. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  174. package/examples/extensions/doom-overlay/doom/doomgeneric_knightcode.c +72 -0
  175. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  176. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  177. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  178. package/examples/extensions/doom-overlay/index.ts +74 -0
  179. package/examples/extensions/doom-overlay/wad-finder.ts +55 -0
  180. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  181. package/examples/extensions/dynamic-resources/dynamic.json +75 -0
  182. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  183. package/examples/extensions/dynamic-resources/index.ts +15 -0
  184. package/examples/extensions/dynamic-tools.ts +74 -0
  185. package/examples/extensions/entry-renderer.ts +41 -0
  186. package/examples/extensions/event-bus.ts +43 -0
  187. package/examples/extensions/file-trigger.ts +41 -0
  188. package/examples/extensions/git-checkpoint.ts +53 -0
  189. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  190. package/examples/extensions/github-issue-autocomplete.ts +185 -0
  191. package/examples/extensions/gondolin/index.ts +531 -0
  192. package/examples/extensions/gondolin/package-lock.json +185 -0
  193. package/examples/extensions/gondolin/package.json +19 -0
  194. package/examples/extensions/handoff.ts +190 -0
  195. package/examples/extensions/hello.ts +26 -0
  196. package/examples/extensions/hidden-thinking-label.ts +53 -0
  197. package/examples/extensions/inline-bash.ts +94 -0
  198. package/examples/extensions/input-transform-streaming.ts +39 -0
  199. package/examples/extensions/input-transform.ts +43 -0
  200. package/examples/extensions/interactive-shell.ts +196 -0
  201. package/examples/extensions/jev-router.ts +113 -0
  202. package/examples/extensions/mac-system-theme.ts +47 -0
  203. package/examples/extensions/message-renderer.ts +59 -0
  204. package/examples/extensions/minimal-mode.ts +353 -0
  205. package/examples/extensions/modal-editor.ts +85 -0
  206. package/examples/extensions/model-status.ts +31 -0
  207. package/examples/extensions/notify.ts +57 -0
  208. package/examples/extensions/overlay-qa-tests.ts +1450 -0
  209. package/examples/extensions/overlay-test.ts +153 -0
  210. package/examples/extensions/permission-gate.ts +34 -0
  211. package/examples/extensions/pirate.ts +47 -0
  212. package/examples/extensions/plan-mode/README.md +66 -0
  213. package/examples/extensions/plan-mode/index.ts +387 -0
  214. package/examples/extensions/plan-mode/utils.ts +168 -0
  215. package/examples/extensions/preset.ts +436 -0
  216. package/examples/extensions/project-trust.ts +64 -0
  217. package/examples/extensions/prompt-customizer.ts +49 -0
  218. package/examples/extensions/protected-paths.ts +30 -0
  219. package/examples/extensions/provider-payload.ts +18 -0
  220. package/examples/extensions/qna.ts +118 -0
  221. package/examples/extensions/question.ts +278 -0
  222. package/examples/extensions/questionnaire.ts +440 -0
  223. package/examples/extensions/rainbow-editor.ts +88 -0
  224. package/examples/extensions/reload-runtime.ts +37 -0
  225. package/examples/extensions/rpc-demo.ts +118 -0
  226. package/examples/extensions/sandbox/index.ts +321 -0
  227. package/examples/extensions/sandbox/package-lock.json +92 -0
  228. package/examples/extensions/sandbox/package.json +19 -0
  229. package/examples/extensions/send-user-message.ts +97 -0
  230. package/examples/extensions/session-name.ts +27 -0
  231. package/examples/extensions/shutdown-command.ts +63 -0
  232. package/examples/extensions/snake.ts +343 -0
  233. package/examples/extensions/space-invaders.ts +560 -0
  234. package/examples/extensions/ssh.ts +220 -0
  235. package/examples/extensions/status-line.ts +32 -0
  236. package/examples/extensions/structured-output.ts +65 -0
  237. package/examples/extensions/subagent/README.md +177 -0
  238. package/examples/extensions/subagent/agents/planner.md +37 -0
  239. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  240. package/examples/extensions/subagent/agents/scout.md +50 -0
  241. package/examples/extensions/subagent/agents/worker.md +24 -0
  242. package/examples/extensions/subagent/agents.ts +157 -0
  243. package/examples/extensions/subagent/index.ts +1010 -0
  244. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  245. package/examples/extensions/subagent/prompts/implement.md +10 -0
  246. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  247. package/examples/extensions/summarize.ts +199 -0
  248. package/examples/extensions/system-prompt-header.ts +17 -0
  249. package/examples/extensions/tic-tac-toe.ts +1004 -0
  250. package/examples/extensions/timed-confirm.ts +70 -0
  251. package/examples/extensions/titlebar-spinner.ts +58 -0
  252. package/examples/extensions/todo.ts +294 -0
  253. package/examples/extensions/tool-override.ts +144 -0
  254. package/examples/extensions/tools.ts +146 -0
  255. package/examples/extensions/trigger-compact.ts +50 -0
  256. package/examples/extensions/truncated-tool.ts +195 -0
  257. package/examples/extensions/widget-placement.ts +9 -0
  258. package/examples/extensions/with-deps/index.ts +32 -0
  259. package/examples/extensions/with-deps/package-lock.json +31 -0
  260. package/examples/extensions/with-deps/package.json +22 -0
  261. package/examples/extensions/working-indicator.ts +121 -0
  262. package/examples/extensions/working-message-test.ts +25 -0
  263. package/examples/rpc-client.ts +35 -0
  264. package/examples/rpc-extension-ui.ts +661 -0
  265. package/examples/sdk/01-minimal.ts +26 -0
  266. package/examples/sdk/02-custom-model.ts +49 -0
  267. package/examples/sdk/03-custom-prompt.ts +70 -0
  268. package/examples/sdk/04-skills.ts +55 -0
  269. package/examples/sdk/05-tools.ts +48 -0
  270. package/examples/sdk/06-extensions.ts +94 -0
  271. package/examples/sdk/07-context-files.ts +42 -0
  272. package/examples/sdk/08-prompt-templates.ts +51 -0
  273. package/examples/sdk/09-api-keys-and-oauth.ts +34 -0
  274. package/examples/sdk/10-settings.ts +53 -0
  275. package/examples/sdk/11-sessions.ts +52 -0
  276. package/examples/sdk/12-full-control.ts +74 -0
  277. package/examples/sdk/13-session-runtime.ts +67 -0
  278. package/examples/sdk/14-codemode-mcp.ts +61 -0
  279. package/examples/sdk/README.md +141 -0
  280. package/package.json +15 -15
  281. package/bin/knightcode +0 -62
@@ -0,0 +1,253 @@
1
+ import type { RgbColor, TerminalColors, TUI } from "@knightcode/tui";
2
+ import type { SettingsManager } from "../../../core/settings-manager.ts";
3
+ import {
4
+ getTerminalTheme,
5
+ initTheme,
6
+ markTerminalColorsPending,
7
+ parseAutoThemeSetting,
8
+ resolveThemeSetting,
9
+ SYSTEM_THEME_NAME,
10
+ setTerminalColorScheme,
11
+ setTerminalColors,
12
+ setTheme,
13
+ setThemeInstance,
14
+ type TerminalTheme,
15
+ type Theme,
16
+ } from "./theme.ts";
17
+
18
+ type ThemeResult = { success: boolean; error?: string };
19
+
20
+ /**
21
+ * How long the system theme stays grayscale before falling back to palette indices. Terminals answer
22
+ * the trailing DA1 request right after the color replies, so this only matters for terminals that
23
+ * answer neither. Replies arriving later still apply.
24
+ */
25
+ const TERMINAL_QUERY_TIMEOUT_MS = 100;
26
+
27
+ /**
28
+ * Query the terminal's colors and pass them to `apply` when the query completes or times out, and again
29
+ * if the terminal answers after the timeout. A failed query applies no colors. Settles after the first apply.
30
+ */
31
+ export function requestTerminalColors(ui: TUI, apply: (colors: TerminalColors) => void): Promise<void> {
32
+ let query: Promise<TerminalColors>;
33
+ try {
34
+ query = ui.queryTerminalColors({ timeoutMs: TERMINAL_QUERY_TIMEOUT_MS, onLateReply: apply });
35
+ } catch {
36
+ // Treat a failed query like a terminal that does not report colors.
37
+ query = Promise.resolve({});
38
+ }
39
+ return query.then(apply, () => apply({}));
40
+ }
41
+
42
+ function sameRgb(a: RgbColor | undefined, b: RgbColor | undefined): boolean {
43
+ return a === b || (a !== undefined && b !== undefined && a.r === b.r && a.g === b.g && a.b === b.b);
44
+ }
45
+
46
+ function sameTerminalColors(a: TerminalColors, b: TerminalColors): boolean {
47
+ if (!sameRgb(a.foreground, b.foreground) || !sameRgb(a.background, b.background)) return false;
48
+ if (a.palette === b.palette) return true;
49
+ if (!a.palette || !b.palette || a.palette.length !== b.palette.length) return false;
50
+ return a.palette.every((color, index) => sameRgb(color, b.palette?.[index]));
51
+ }
52
+
53
+ /**
54
+ * Applies the theme setting and keeps it in sync with the terminal. The theme applies immediately, and the
55
+ * terminal's colors update it when they arrive; the system theme renders in grayscale until then. Callers
56
+ * that bake theme colors into content can wait for the colors with `waitForTerminalColors()`.
57
+ */
58
+ export class InteractiveThemeController {
59
+ private readonly ui: TUI;
60
+ private readonly getSettingsManager: () => SettingsManager;
61
+ private readonly showError: (message: string) => void;
62
+ private readonly onChanged: () => void;
63
+ private currentThemeSetting: string | undefined;
64
+ // Last reported colors; a query that times out keeps them instead of erasing them.
65
+ private terminalColors: TerminalColors | undefined;
66
+ private activeThemeName: string | undefined;
67
+ private autoSyncEnabled = false;
68
+ private terminalColorSchemeUnsubscribe: (() => void) | undefined;
69
+ // Settles when the latest color query completed or timed out, and its colors applied.
70
+ private terminalColorQuery: Promise<void> = Promise.resolve();
71
+
72
+ constructor(
73
+ ui: TUI,
74
+ options: {
75
+ getSettingsManager: () => SettingsManager;
76
+ showError: (message: string) => void;
77
+ onChanged: () => void;
78
+ initialThemeSetting?: string;
79
+ },
80
+ ) {
81
+ this.ui = ui;
82
+ this.getSettingsManager = options.getSettingsManager;
83
+ this.showError = options.showError;
84
+ this.onChanged = options.onChanged;
85
+ this.currentThemeSetting = options.initialThemeSetting;
86
+ this.activeThemeName = this.resolveThemeName();
87
+ // The system theme starts in grayscale; color follows once the terminal reports its colors.
88
+ markTerminalColorsPending();
89
+ initTheme(this.activeThemeName, true);
90
+ this.bindTerminalColorSchemeListener();
91
+ }
92
+
93
+ rebindTui(): void {
94
+ this.terminalColorSchemeUnsubscribe?.();
95
+ this.bindTerminalColorSchemeListener();
96
+ this.ui.setTerminalColorSchemeNotifications(this.autoSyncEnabled);
97
+ }
98
+
99
+ /**
100
+ * Apply the theme setting now and query the terminal's colors, which update the theme when they arrive.
101
+ * Theme pairs and the system theme follow terminal appearance changes.
102
+ */
103
+ applyFromSettings(): void {
104
+ const themeSetting = this.getThemeSetting();
105
+ const themeName = this.resolveThemeName();
106
+ this.setAutoSync(parseAutoThemeSetting(themeSetting) !== undefined || themeName === SYSTEM_THEME_NAME);
107
+ this.applyThemeName(themeName, themeSetting !== undefined);
108
+ this.queryTerminalColors();
109
+ }
110
+
111
+ /**
112
+ * Wait until the latest color query completed or timed out. Content that bakes theme colors into
113
+ * strings, such as the startup header, should be built after this. Terminals answer the DA1 request
114
+ * right after the color replies, so this only takes the full timeout when a terminal answers nothing.
115
+ */
116
+ waitForTerminalColors(): Promise<void> {
117
+ return this.terminalColorQuery;
118
+ }
119
+
120
+ getThemeSelection(): string | undefined {
121
+ return this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting() ?? this.activeThemeName;
122
+ }
123
+
124
+ setThemeName(themeName: string, showError = false): ThemeResult {
125
+ this.setAutoSync(themeName === SYSTEM_THEME_NAME);
126
+ const result = this.applyThemeName(themeName, showError);
127
+ if (result.success) {
128
+ this.currentThemeSetting = themeName;
129
+ }
130
+ return result;
131
+ }
132
+
133
+ setThemeSetting(themeSetting: string): void {
134
+ this.currentThemeSetting = themeSetting;
135
+ this.applyFromSettings();
136
+ }
137
+
138
+ setThemeInstance(themeInstance: Theme): ThemeResult {
139
+ this.setAutoSync(false);
140
+ setThemeInstance(themeInstance);
141
+ this.activeThemeName = "<in-memory>";
142
+ this.notifyChanged();
143
+ return { success: true };
144
+ }
145
+
146
+ preview(themeSettingOrName: string): void {
147
+ const themeName = resolveThemeSetting(themeSettingOrName, getTerminalTheme()) ?? this.activeThemeName;
148
+ if (!themeName) return;
149
+ if (setTheme(themeName, true).success) {
150
+ this.ui.invalidate();
151
+ this.ui.requestRender();
152
+ }
153
+ }
154
+
155
+ disableAutoSync(): void {
156
+ this.setAutoSync(false);
157
+ }
158
+
159
+ dispose(): void {
160
+ this.setAutoSync(false);
161
+ this.terminalColorSchemeUnsubscribe?.();
162
+ this.terminalColorSchemeUnsubscribe = undefined;
163
+ }
164
+
165
+ getTerminalTheme(): TerminalTheme {
166
+ return getTerminalTheme();
167
+ }
168
+
169
+ private getThemeSetting(): string | undefined {
170
+ return this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting();
171
+ }
172
+
173
+ /** The theme for the current setting and terminal appearance. Without a setting, KnightCode uses the system theme. */
174
+ private resolveThemeName(): string {
175
+ return resolveThemeSetting(this.getThemeSetting(), getTerminalTheme()) ?? SYSTEM_THEME_NAME;
176
+ }
177
+
178
+ private applyThemeName(themeName: string, showError = false): ThemeResult {
179
+ const result = setTheme(themeName, true);
180
+ this.activeThemeName = result.success ? themeName : SYSTEM_THEME_NAME;
181
+ this.notifyChanged();
182
+ if (!result.success && showError) {
183
+ this.showError(`Failed to load theme "${themeName}": ${result.error}\nFell back to the system theme.`);
184
+ }
185
+ return result;
186
+ }
187
+
188
+ /** Query the terminal's colors without waiting for them; `waitForTerminalColors()` waits for this query. */
189
+ private queryTerminalColors(): void {
190
+ this.terminalColorQuery = requestTerminalColors(this.ui, (colors) => this.applyTerminalColors(colors));
191
+ }
192
+
193
+ /**
194
+ * Record reported colors: themes use the default colors for tokens set to "", the system theme is
195
+ * generated from all of them, and light/dark detection uses them. Re-renders only when they changed.
196
+ */
197
+ private applyTerminalColors(reported: TerminalColors): void {
198
+ const previous = this.terminalColors;
199
+ const next: TerminalColors = {
200
+ foreground: reported.foreground ?? previous?.foreground,
201
+ background: reported.background ?? previous?.background,
202
+ palette: reported.palette ?? previous?.palette,
203
+ };
204
+ // Re-rendering rebuilds every component, so skip it when nothing changed (including timeouts).
205
+ if (previous && sameTerminalColors(previous, next)) return;
206
+ this.terminalColors = next;
207
+ setTerminalColors(next);
208
+ this.reapplyForTerminal();
209
+ this.ui.invalidate();
210
+ this.ui.requestRender();
211
+ }
212
+
213
+ /**
214
+ * Re-apply the setting after the terminal's colors or appearance changed: regenerate the system theme,
215
+ * or switch the theme of a pair. Themes set through extensions or previews are left alone.
216
+ */
217
+ private reapplyForTerminal(): void {
218
+ if (this.activeThemeName === "<in-memory>") return;
219
+ const themeName = this.resolveThemeName();
220
+ if (themeName === SYSTEM_THEME_NAME || themeName !== this.activeThemeName) {
221
+ this.applyThemeName(themeName);
222
+ }
223
+ }
224
+
225
+ private setAutoSync(enabled: boolean): void {
226
+ if (this.autoSyncEnabled === enabled) return;
227
+ this.autoSyncEnabled = enabled;
228
+ this.ui.setTerminalColorSchemeNotifications(enabled);
229
+ }
230
+
231
+ private bindTerminalColorSchemeListener(): void {
232
+ this.terminalColorSchemeUnsubscribe = this.ui.onTerminalColorSchemeChange((terminalTheme) =>
233
+ this.applyTerminalColorSchemeChange(terminalTheme),
234
+ );
235
+ }
236
+
237
+ /**
238
+ * The terminal reported a light/dark switch. Its colors changed too, so query them again: they decide
239
+ * the appearance. The reported scheme only matters for terminals that do not report their background.
240
+ */
241
+ private applyTerminalColorSchemeChange(terminalTheme: TerminalTheme): void {
242
+ if (!this.autoSyncEnabled) return;
243
+ const previous = getTerminalTheme();
244
+ setTerminalColorScheme(terminalTheme);
245
+ if (getTerminalTheme() !== previous) this.reapplyForTerminal();
246
+ this.queryTerminalColors();
247
+ }
248
+
249
+ private notifyChanged(): void {
250
+ this.ui.invalidate();
251
+ this.onChanged();
252
+ }
253
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Theme JSON validation, kept out of `theme.ts` on purpose.
3
+ *
4
+ * Validating user-authored theme files needs typebox, which costs ~17 MB of module graph to import.
5
+ * Palette lookup does not, so a presentation that only uses built-in themes should never pay for it.
6
+ * `main.ts` installs this validator before runtime resource loading; other consumers can opt in with
7
+ * `setThemeJsonValidator()`. Built-in themes do not need validation.
8
+ */
9
+
10
+ import { type Static, type TProperties, Type } from "typebox";
11
+ import { Compile } from "typebox/compile";
12
+ import { THEME_TOKENS, type ThemeColorValues, type ThemeTokenDescriptors } from "./theme-tokens.ts";
13
+
14
+ function colorValue(description?: string) {
15
+ return Type.Union(
16
+ [
17
+ Type.String({
18
+ description:
19
+ "Hex color (#RGB or #RRGGBB), OKLCH or OKHSL color, variable reference, or empty string for terminal default",
20
+ }),
21
+ Type.Integer({
22
+ minimum: 0,
23
+ maximum: 255,
24
+ description: "256-color palette index (0-255)",
25
+ }),
26
+ ],
27
+ description ? { description } : {},
28
+ );
29
+ }
30
+
31
+ export const ColorValueSchema = colorValue();
32
+
33
+ export type ThemeColorValue = Static<typeof ColorValueSchema>;
34
+
35
+ function themeTokenProperties(tokens: ThemeTokenDescriptors): TProperties {
36
+ const properties: TProperties = {};
37
+ for (const [name, descriptor] of Object.entries(tokens)) {
38
+ const schema = colorValue(descriptor.description);
39
+ properties[name] = descriptor.fallback === undefined ? schema : Type.Optional(schema);
40
+ }
41
+ return properties;
42
+ }
43
+
44
+ const ThemeColorsSchema = Type.Unsafe<ThemeColorValues<ThemeColorValue>>(
45
+ Type.Object(themeTokenProperties(THEME_TOKENS), {
46
+ description:
47
+ "Theme color definitions (scrollbar, thinking level borders, and search highlight colors are optional and use compatible fallbacks)",
48
+ additionalProperties: false,
49
+ }),
50
+ );
51
+
52
+ export const ThemeJsonSchema = Type.Object(
53
+ {
54
+ $schema: Type.Optional(Type.String({ description: "JSON schema reference" })),
55
+ name: Type.String({
56
+ pattern: "^[^/]+$",
57
+ description: "Theme name. Must not contain '/' because it is reserved for automatic light/dark theme settings.",
58
+ }),
59
+ appearance: Type.Optional(
60
+ Type.Union([Type.Literal("dark"), Type.Literal("light")], {
61
+ description: "Background the theme is designed for. Detected from the theme colors when omitted.",
62
+ }),
63
+ ),
64
+ vars: Type.Optional(
65
+ Type.Record(Type.String(), ColorValueSchema, {
66
+ description: "Reusable color variables",
67
+ }),
68
+ ),
69
+ colors: ThemeColorsSchema,
70
+ export: Type.Optional(
71
+ Type.Object(
72
+ {
73
+ pageBg: Type.Optional(colorValue("Page background color")),
74
+ cardBg: Type.Optional(colorValue("Card/container background color")),
75
+ infoBg: Type.Optional(colorValue("Info sections background (system prompt, notices)")),
76
+ },
77
+ {
78
+ description: "Optional colors for HTML export (defaults derived from userMessageBg if not specified)",
79
+ additionalProperties: false,
80
+ },
81
+ ),
82
+ ),
83
+ },
84
+ {
85
+ title: "KnightCode Coding Agent Theme",
86
+ description: "Theme schema for KnightCode coding agent",
87
+ additionalProperties: false,
88
+ },
89
+ );
90
+
91
+ const compiledThemeSchema = Compile(ThemeJsonSchema);
92
+
93
+ export type ValidatedThemeJson = Static<typeof ThemeJsonSchema>;
94
+
95
+ /** Validate one theme document, throwing a message that names the offending tokens. */
96
+ export function validateThemeJson(label: string, json: unknown): ValidatedThemeJson {
97
+ if (
98
+ typeof json === "object" &&
99
+ json !== null &&
100
+ "name" in json &&
101
+ typeof json.name === "string" &&
102
+ json.name.includes("/")
103
+ ) {
104
+ throw new Error(
105
+ `Invalid theme name "${json.name}": theme names cannot contain "/" because it is reserved for automatic light/dark theme settings.`,
106
+ );
107
+ }
108
+
109
+ if (!compiledThemeSchema.Check(json)) {
110
+ const errors = Array.from(compiledThemeSchema.Errors(json));
111
+ const missingColors = new Set<string>();
112
+ const otherErrors: string[] = [];
113
+
114
+ for (const error of errors) {
115
+ if (error.keyword === "required" && error.instancePath === "/colors") {
116
+ const requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;
117
+ for (const requiredProperty of requiredProperties ?? []) {
118
+ missingColors.add(requiredProperty);
119
+ }
120
+ continue;
121
+ }
122
+
123
+ const path = error.instancePath || "/";
124
+ otherErrors.push(` - ${path}: ${error.message}`);
125
+ }
126
+
127
+ let errorMessage = `Invalid theme "${label}":\n`;
128
+ if (missingColors.size > 0) {
129
+ errorMessage += "\nMissing required color tokens:\n";
130
+ errorMessage += Array.from(missingColors)
131
+ .sort()
132
+ .map((color) => ` - ${color}`)
133
+ .join("\n");
134
+ errorMessage += '\n\nPlease add these colors to your theme\'s "colors" object.';
135
+ errorMessage += "\nSee the built-in themes (dark.json, light.json) for reference values.";
136
+ }
137
+ if (otherErrors.length > 0) {
138
+ errorMessage += `\n\nOther errors:\n${otherErrors.join("\n")}`;
139
+ }
140
+
141
+ throw new Error(errorMessage);
142
+ }
143
+
144
+ return json as ValidatedThemeJson;
145
+ }
@@ -0,0 +1,145 @@
1
+ export interface ThemeTokenDescriptor {
2
+ slot: "foreground" | "background";
3
+ description: string;
4
+ fallback?: string;
5
+ }
6
+
7
+ export type ThemeTokenDescriptors = Readonly<Record<string, ThemeTokenDescriptor>>;
8
+
9
+ function defineThemeTokens<const T extends ThemeTokenDescriptors>(tokens: T): T {
10
+ for (const [name, descriptor] of Object.entries(tokens)) {
11
+ if (descriptor.fallback === undefined) continue;
12
+ const fallback = tokens[descriptor.fallback];
13
+ if (fallback === undefined) {
14
+ throw new Error(`Theme token ${name} has unknown fallback ${descriptor.fallback}`);
15
+ }
16
+ if (fallback.slot !== descriptor.slot) {
17
+ throw new Error(`Theme token ${name} has fallback ${descriptor.fallback} in a different color slot`);
18
+ }
19
+ }
20
+ return tokens;
21
+ }
22
+
23
+ /** Theme token metadata in the same order used by theme files and the published JSON Schema. */
24
+ export const THEME_TOKENS = defineThemeTokens({
25
+ accent: { slot: "foreground", description: "Primary accent color (logo, selected items, cursor)" },
26
+ border: { slot: "foreground", description: "Normal borders" },
27
+ borderAccent: { slot: "foreground", description: "Highlighted borders" },
28
+ borderMuted: { slot: "foreground", description: "Subtle borders" },
29
+ success: { slot: "foreground", description: "Success states" },
30
+ error: { slot: "foreground", description: "Error states" },
31
+ warning: { slot: "foreground", description: "Warning states" },
32
+ muted: { slot: "foreground", description: "Secondary/dimmed text" },
33
+ dim: { slot: "foreground", description: "Very dimmed text (more subtle than muted)" },
34
+ text: { slot: "foreground", description: "Default text color (usually empty string)" },
35
+ thinkingText: { slot: "foreground", description: "Thinking block text color" },
36
+ scrollbarTrack: {
37
+ slot: "foreground",
38
+ description: "Fullscreen scrollbar track foreground (falls back to muted when omitted)",
39
+ fallback: "muted",
40
+ },
41
+ scrollbarThumb: {
42
+ slot: "foreground",
43
+ description: "Fullscreen scrollbar thumb foreground (falls back to text when omitted)",
44
+ fallback: "text",
45
+ },
46
+ selectedBg: { slot: "background", description: "Selected item background" },
47
+ searchMatchBg: {
48
+ slot: "background",
49
+ description: "Transcript search match background and current-match text (falls back to selectedBg when omitted)",
50
+ fallback: "selectedBg",
51
+ },
52
+ searchMatchText: {
53
+ slot: "foreground",
54
+ description: "Transcript search match text and current-match background (falls back to text when omitted)",
55
+ fallback: "text",
56
+ },
57
+ userMessageBg: { slot: "background", description: "User message background" },
58
+ userMessageText: { slot: "foreground", description: "User message text color" },
59
+ customMessageBg: { slot: "background", description: "Custom message background (hook-injected messages)" },
60
+ customMessageText: { slot: "foreground", description: "Custom message text color" },
61
+ customMessageLabel: { slot: "foreground", description: "Custom message type label color" },
62
+ toolPendingBg: { slot: "background", description: "Tool execution box (pending state)" },
63
+ toolSuccessBg: { slot: "background", description: "Tool execution box (success state)" },
64
+ toolErrorBg: { slot: "background", description: "Tool execution box (error state)" },
65
+ toolTitle: { slot: "foreground", description: "Tool execution box title color" },
66
+ toolOutput: { slot: "foreground", description: "Tool execution box output text color" },
67
+ mdHeading: { slot: "foreground", description: "Markdown heading text" },
68
+ mdLink: { slot: "foreground", description: "Markdown link text" },
69
+ mdLinkUrl: { slot: "foreground", description: "Markdown link URL" },
70
+ mdCode: { slot: "foreground", description: "Markdown inline code" },
71
+ mdCodeBlock: { slot: "foreground", description: "Markdown code block content" },
72
+ mdCodeBlockBorder: { slot: "foreground", description: "Markdown code block fences" },
73
+ mdQuote: { slot: "foreground", description: "Markdown blockquote text" },
74
+ mdQuoteBorder: { slot: "foreground", description: "Markdown blockquote border" },
75
+ mdHr: { slot: "foreground", description: "Markdown horizontal rule" },
76
+ mdListBullet: { slot: "foreground", description: "Markdown list bullets/numbers" },
77
+ toolDiffAdded: { slot: "foreground", description: "Added lines in tool diffs" },
78
+ toolDiffRemoved: { slot: "foreground", description: "Removed lines in tool diffs" },
79
+ toolDiffContext: { slot: "foreground", description: "Context lines in tool diffs" },
80
+ syntaxComment: { slot: "foreground", description: "Syntax highlighting: comments" },
81
+ syntaxKeyword: { slot: "foreground", description: "Syntax highlighting: keywords" },
82
+ syntaxFunction: { slot: "foreground", description: "Syntax highlighting: function names" },
83
+ syntaxVariable: { slot: "foreground", description: "Syntax highlighting: variable names" },
84
+ syntaxString: { slot: "foreground", description: "Syntax highlighting: string literals" },
85
+ syntaxNumber: { slot: "foreground", description: "Syntax highlighting: number literals" },
86
+ syntaxType: { slot: "foreground", description: "Syntax highlighting: type names" },
87
+ syntaxOperator: { slot: "foreground", description: "Syntax highlighting: operators" },
88
+ syntaxPunctuation: { slot: "foreground", description: "Syntax highlighting: punctuation" },
89
+ thinkingOff: {
90
+ slot: "foreground",
91
+ description: "Thinking level border: off (falls back to border when omitted)",
92
+ fallback: "border",
93
+ },
94
+ thinkingMinimal: {
95
+ slot: "foreground",
96
+ description: "Thinking level border: minimal (falls back to thinkingOff when omitted)",
97
+ fallback: "thinkingOff",
98
+ },
99
+ thinkingLow: {
100
+ slot: "foreground",
101
+ description: "Thinking level border: low (falls back to thinkingMinimal when omitted)",
102
+ fallback: "thinkingMinimal",
103
+ },
104
+ thinkingMedium: {
105
+ slot: "foreground",
106
+ description: "Thinking level border: medium (falls back to thinkingLow when omitted)",
107
+ fallback: "thinkingLow",
108
+ },
109
+ thinkingHigh: {
110
+ slot: "foreground",
111
+ description: "Thinking level border: high (falls back to thinkingMedium when omitted)",
112
+ fallback: "thinkingMedium",
113
+ },
114
+ thinkingXhigh: {
115
+ slot: "foreground",
116
+ description: "Thinking level border: xhigh (falls back to thinkingHigh when omitted)",
117
+ fallback: "thinkingHigh",
118
+ },
119
+ thinkingMax: {
120
+ slot: "foreground",
121
+ description: "Thinking level border: max (falls back to thinkingXhigh when omitted)",
122
+ fallback: "thinkingXhigh",
123
+ },
124
+ bashMode: { slot: "foreground", description: "Editor border color in bash mode" },
125
+ });
126
+
127
+ export type ThemeToken = keyof typeof THEME_TOKENS;
128
+ export type ThemeColor = {
129
+ [K in ThemeToken]: (typeof THEME_TOKENS)[K]["slot"] extends "foreground" ? K : never;
130
+ }[ThemeToken];
131
+ export type ThemeBg = Exclude<ThemeToken, ThemeColor>;
132
+
133
+ type OptionalToken = {
134
+ [K in ThemeToken]: (typeof THEME_TOKENS)[K] extends { fallback: string } ? K : never;
135
+ }[ThemeToken];
136
+
137
+ type RequiredToken = Exclude<ThemeToken, OptionalToken>;
138
+
139
+ export type ThemeColorValues<Value> = {
140
+ [K in RequiredToken]: Value;
141
+ } & {
142
+ [K in OptionalToken]?: Value;
143
+ };
144
+
145
+ export type ResolvedThemeColorValues<Value> = Record<ThemeToken, Value>;