mu-agent 0.1.3 → 0.1.6

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 (120) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +7 -3
  3. package/dist/bundle/chunks/anthropic-messages-VMESUHQ7.js +6 -0
  4. package/dist/bundle/chunks/azure-openai-responses-K232MQFF.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
  6. package/dist/bundle/chunks/chunk-2X3AO33C.js +2 -0
  7. package/dist/bundle/chunks/{chunk-FUXEF6JQ.js → chunk-4O2SCVYF.js} +1 -1
  8. package/dist/bundle/chunks/chunk-E3TC4YNV.js +2 -0
  9. package/dist/bundle/chunks/chunk-FU4XQUSL.js +11 -0
  10. package/dist/bundle/chunks/{chunk-S7SZN6Z3.js → chunk-GNYZK6XN.js} +1 -1
  11. package/dist/bundle/chunks/chunk-MVPXOVOJ.js +2 -0
  12. package/dist/bundle/chunks/{chunk-AIR4ZKSM.js → chunk-TUBB3BLE.js} +71 -64
  13. package/dist/bundle/chunks/chunk-UTB4DTYQ.js +2 -0
  14. package/dist/bundle/chunks/{chunk-WIUZI2CJ.js → chunk-V7IXECST.js} +2 -2
  15. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-NR2TDCXU.js +2 -0
  16. package/dist/bundle/chunks/github-copilot.js +1 -1
  17. package/dist/bundle/chunks/google-generative-ai-C44KRRZ4.js +2 -0
  18. package/dist/bundle/chunks/google-vertex-25DUXPTJ.js +2 -0
  19. package/dist/bundle/chunks/meta.js +2 -0
  20. package/dist/bundle/chunks/mistral-conversations-6IHQOR4R.js +5 -0
  21. package/dist/bundle/chunks/{openai-codex-responses-QDSOTBBO.js → openai-codex-responses-3UAYOSSR.js} +2 -2
  22. package/dist/bundle/chunks/{openai-completions-KQPOPEVM.js → openai-completions-FIEQGWAH.js} +3 -3
  23. package/dist/bundle/chunks/openai-responses-OZJXLHFC.js +2 -0
  24. package/dist/bundle/chunks/{openrouter-images-N4XVVFSD.js → openrouter-images-JVEJN44Q.js} +1 -1
  25. package/dist/bundle/chunks/{pi-messages-GFKBWJHZ.js → pi-messages-TM5SOOIW.js} +2 -2
  26. package/dist/bundle/chunks/typesafe-system-one-VKYX4OGY.js +2 -0
  27. package/dist/bundle/chunks/{virtual-modules-I4HUN7PP.js → virtual-modules-IOLJJ5KK.js} +1 -1
  28. package/dist/bundle/cli-runtime.js +3 -0
  29. package/dist/bundle/cli.js +4 -2
  30. package/dist/bundle/index.js +1 -1
  31. package/dist/bundle/rpc-entry.js +1 -1
  32. package/dist/core/export-html/template.css +10 -0
  33. package/dist/core/export-html/template.js +56 -18
  34. package/dist/modes/interactive/theme/theme-controller.d.ts +6 -0
  35. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  36. package/dist/modes/interactive/theme/theme-controller.js +41 -3
  37. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  38. package/dist/modes/interactive/theme/theme-json.d.ts +1 -0
  39. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -1
  40. package/dist/modes/interactive/theme/theme-json.js +3 -1
  41. package/dist/modes/interactive/theme/theme-json.js.map +1 -1
  42. package/dist/modes/interactive/theme/theme-schema.json +7 -2
  43. package/dist/modes/interactive/theme/theme.d.ts +40 -7
  44. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  45. package/dist/modes/interactive/theme/theme.js +122 -214
  46. package/dist/modes/interactive/theme/theme.js.map +1 -1
  47. package/docs/cli-integration.md +106 -0
  48. package/docs/cli.md +268 -0
  49. package/docs/compaction.md +45 -26
  50. package/docs/configuration.md +45 -0
  51. package/docs/containerization.md +109 -82
  52. package/docs/custom-provider.md +109 -722
  53. package/docs/docs.json +139 -99
  54. package/docs/environment-variables.md +3 -5
  55. package/docs/extensions.md +137 -2953
  56. package/docs/how-pi-works.md +49 -0
  57. package/docs/images/interactive-mode.png +0 -0
  58. package/docs/index.md +24 -69
  59. package/docs/json.md +193 -65
  60. package/docs/keybindings.md +57 -102
  61. package/docs/llama-cpp.md +3 -3
  62. package/docs/message-types.md +261 -0
  63. package/docs/models.md +64 -546
  64. package/docs/packages.md +67 -166
  65. package/docs/prompt-templates.md +31 -68
  66. package/docs/providers.md +102 -232
  67. package/docs/quickstart.md +61 -106
  68. package/docs/rpc-commands.md +854 -0
  69. package/docs/rpc-extension-ui.md +200 -0
  70. package/docs/rpc.md +129 -1556
  71. package/docs/sdk.md +76 -1160
  72. package/docs/security.md +70 -32
  73. package/docs/session-format.md +25 -216
  74. package/docs/sessions.md +35 -141
  75. package/docs/settings.md +109 -387
  76. package/docs/shell-aliases.md +85 -5
  77. package/docs/skills.md +51 -190
  78. package/docs/slash-commands.md +60 -0
  79. package/docs/terminal-setup.md +105 -78
  80. package/docs/termux.md +74 -83
  81. package/docs/themes.md +71 -279
  82. package/docs/tmux.md +31 -39
  83. package/docs/tui.md +74 -910
  84. package/docs/usage.md +54 -272
  85. package/docs/windows.md +43 -17
  86. package/examples/README.md +13 -2
  87. package/examples/extensions/README.md +1 -0
  88. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  89. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  90. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  91. package/examples/extensions/debug-provider.ts +90 -0
  92. package/examples/extensions/gondolin/package-lock.json +2 -2
  93. package/examples/extensions/gondolin/package.json +1 -1
  94. package/examples/extensions/sandbox/package-lock.json +2 -2
  95. package/examples/extensions/sandbox/package.json +1 -1
  96. package/examples/extensions/with-deps/package-lock.json +2 -2
  97. package/examples/extensions/with-deps/package.json +1 -1
  98. package/examples/plugins/pi-example-plugin/src/session.ts +3 -2
  99. package/examples/rpc-client.ts +35 -0
  100. package/examples/rpc-extension-ui.ts +25 -5
  101. package/examples/sdk/README.md +1 -1
  102. package/judge/dist/auth.js +166 -28
  103. package/judge/dist/chrome.js +12 -1
  104. package/judge/dist/kyrn-judge.js +2144 -968
  105. package/judge/dist/setup.js +2051 -0
  106. package/judge/manifest.json +117 -13
  107. package/judge/skills/mu-browser/SKILL.md +1 -0
  108. package/kyrn/bin/kyrn-doctor +107 -18
  109. package/kyrn/bin/mu.mjs +296 -19
  110. package/package.json +4 -4
  111. package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +0 -6
  112. package/dist/bundle/chunks/azure-openai-responses-HUSQ3GP2.js +0 -2
  113. package/dist/bundle/chunks/chunk-6R7UI4IY.js +0 -2
  114. package/dist/bundle/chunks/chunk-RCZIEVGO.js +0 -11
  115. package/dist/bundle/chunks/chunk-WSSPR443.js +0 -2
  116. package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +0 -2
  117. package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +0 -2
  118. package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +0 -5
  119. package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +0 -2
  120. package/docs/development.md +0 -90
@@ -300,6 +300,12 @@
300
300
  border-color: var(--borderAccent);
301
301
  }
302
302
 
303
+ .header-toggle-btn[aria-pressed="true"] {
304
+ color: var(--accent);
305
+ border-color: var(--accent);
306
+ font-weight: bold;
307
+ }
308
+
303
309
  /* Header */
304
310
  .header {
305
311
  background: var(--container-bg);
@@ -786,6 +792,10 @@
786
792
  border-radius: 4px;
787
793
  }
788
794
 
795
+ body:not(.show-hidden-messages) .hook-message-hidden {
796
+ display: none;
797
+ }
798
+
789
799
  .hook-type {
790
800
  color: var(--customMessageLabel);
791
801
  font-weight: bold;
@@ -357,6 +357,9 @@
357
357
  case 'thinking_level_change':
358
358
  parts.push('thinking', entry.thinkingLevel);
359
359
  break;
360
+ case 'context_edit':
361
+ parts.push('context edit', entry.replacement === null ? 'omit' : 'replace', entry.targetId);
362
+ break;
360
363
  }
361
364
 
362
365
  return parts.join(' ').toLowerCase();
@@ -385,7 +388,7 @@
385
388
  }
386
389
 
387
390
  // Apply filter mode
388
- const isSettingsEntry = ['label', 'custom', 'model_change', 'thinking_level_change'].includes(entry.type);
391
+ const isSettingsEntry = ['label', 'custom', 'context_edit', 'model_change', 'thinking_level_change'].includes(entry.type);
389
392
  let passesFilter = true;
390
393
 
391
394
  switch (filterMode) {
@@ -696,6 +699,8 @@
696
699
  return labelHtml + `<span class="tree-muted">[model: ${escapeHtml(entry.modelId)}]</span>`;
697
700
  case 'thinking_level_change':
698
701
  return labelHtml + `<span class="tree-muted">[thinking: ${escapeHtml(entry.thinkingLevel)}]</span>`;
702
+ case 'context_edit':
703
+ return labelHtml + `<span class="tree-muted">[context ${entry.replacement === null ? 'omit' : 'replace'}: ${escapeHtml(entry.targetId)}]</span>`;
699
704
  default:
700
705
  return labelHtml + `<span class="tree-muted">[${escapeHtml(entry.type)}]</span>`;
701
706
  }
@@ -1306,9 +1311,10 @@
1306
1311
  </div>`;
1307
1312
  }
1308
1313
 
1309
- if (entry.type === 'custom_message' && entry.display) {
1310
- return `<div class="hook-message" id="${entryDomId}">${tsHtml}
1311
- <div class="hook-type">[${escapeHtml(entry.customType)}]</div>
1314
+ if (entry.type === 'custom_message') {
1315
+ const hidden = entry.display === false;
1316
+ return `<div class="hook-message${hidden ? ' hook-message-hidden' : ''}" id="${entryDomId}">${tsHtml}
1317
+ <div class="hook-type">[${escapeHtml(entry.customType)}]${hidden ? ' · Hidden in terminal' : ''}</div>
1312
1318
  <div class="markdown-content">${safeMarkedParse(typeof entry.content === 'string' ? entry.content : JSON.stringify(entry.content))}</div>
1313
1319
  </div>`;
1314
1320
  }
@@ -1384,10 +1390,11 @@
1384
1390
  <div class="header">
1385
1391
  <h1>Session: ${escapeHtml(header?.id || 'unknown')}</h1>
1386
1392
  <div class="help-bar">
1387
- <span class="help-hint">T toggle thinking · O toggle tools</span>
1393
+ <span class="help-hint">T toggle thinking · O toggle tools · H toggle hidden messages</span>
1388
1394
  <div class="help-actions">
1389
- <button type="button" class="header-toggle-btn" data-action="toggle-thinking" title="Toggle thinking (T)">Toggle thinking</button>
1390
- <button type="button" class="header-toggle-btn" data-action="toggle-tools" title="Toggle tools (O)">Toggle tools</button>
1395
+ <button type="button" class="header-toggle-btn" data-action="toggle-thinking" aria-pressed="${thinkingExpanded}" title="Toggle thinking (T)">Toggle thinking</button>
1396
+ <button type="button" class="header-toggle-btn" data-action="toggle-tools" aria-pressed="${toolOutputsExpanded}" title="Toggle tools (O)">Toggle tools</button>
1397
+ <button type="button" class="header-toggle-btn" data-action="toggle-hidden-messages" aria-pressed="${showHiddenMessages}" title="Show custom messages marked as hidden in the terminal (H).">${showHiddenMessages ? 'Hide hidden messages' : 'Show hidden messages'}</button>
1391
1398
  <button type="button" class="download-json-btn" onclick="downloadSessionJson()" title="Download session as JSONL">↓ JSONL</button>
1392
1399
  </div>
1393
1400
  </div>
@@ -1495,6 +1502,10 @@
1495
1502
  function navigateTo(targetId, scrollMode = 'target', scrollToEntryId = null) {
1496
1503
  currentLeafId = targetId;
1497
1504
  currentTargetId = scrollToEntryId || targetId;
1505
+ const targetEntry = byId.get(currentTargetId);
1506
+ if (scrollMode === 'target' && targetEntry?.type === 'custom_message' && targetEntry.display === false) {
1507
+ setHiddenMessagesVisible(true);
1508
+ }
1498
1509
  const path = getPath(targetId);
1499
1510
 
1500
1511
  renderTree();
@@ -1516,6 +1527,10 @@
1516
1527
  messagesEl.innerHTML = '';
1517
1528
  messagesEl.appendChild(fragment);
1518
1529
 
1530
+ // Cached nodes contain their initial presentation; reapply the viewer's toggle states.
1531
+ setThinkingExpanded(thinkingExpanded);
1532
+ setToolOutputsExpanded(toolOutputsExpanded);
1533
+
1519
1534
  // Attach click handlers for copy-link buttons
1520
1535
  messagesEl.querySelectorAll('.copy-link-btn').forEach(btn => {
1521
1536
  btn.addEventListener('click', (e) => {
@@ -1788,19 +1803,32 @@
1788
1803
  // Toggle states
1789
1804
  let thinkingExpanded = true;
1790
1805
  let toolOutputsExpanded = false;
1806
+ let showHiddenMessages = false;
1807
+
1808
+ function setHiddenMessagesVisible(visible) {
1809
+ showHiddenMessages = visible;
1810
+ document.body.classList.toggle('show-hidden-messages', visible);
1811
+ const button = document.querySelector('[data-action="toggle-hidden-messages"]');
1812
+ if (button) {
1813
+ button.setAttribute('aria-pressed', String(visible));
1814
+ button.textContent = visible ? 'Hide hidden messages' : 'Show hidden messages';
1815
+ }
1816
+ }
1791
1817
 
1792
- const toggleThinking = () => {
1793
- thinkingExpanded = !thinkingExpanded;
1818
+ function setThinkingExpanded(expanded) {
1819
+ thinkingExpanded = expanded;
1820
+ document.querySelector('[data-action="toggle-thinking"]')?.setAttribute('aria-pressed', String(expanded));
1794
1821
  document.querySelectorAll('.thinking-text').forEach(el => {
1795
1822
  el.style.display = thinkingExpanded ? '' : 'none';
1796
1823
  });
1797
1824
  document.querySelectorAll('.thinking-collapsed').forEach(el => {
1798
1825
  el.style.display = thinkingExpanded ? 'none' : 'block';
1799
1826
  });
1800
- };
1827
+ }
1801
1828
 
1802
- const toggleToolOutputs = () => {
1803
- toolOutputsExpanded = !toolOutputsExpanded;
1829
+ function setToolOutputsExpanded(expanded) {
1830
+ toolOutputsExpanded = expanded;
1831
+ document.querySelector('[data-action="toggle-tools"]')?.setAttribute('aria-pressed', String(expanded));
1804
1832
  document.querySelectorAll('.tool-output.expandable').forEach(el => {
1805
1833
  el.classList.toggle('expanded', toolOutputsExpanded);
1806
1834
  });
@@ -1810,11 +1838,18 @@
1810
1838
  document.querySelectorAll('.skill-invocation').forEach(el => {
1811
1839
  el.classList.toggle('expanded', toolOutputsExpanded);
1812
1840
  });
1813
- };
1841
+ }
1814
1842
 
1815
1843
  const attachHeaderHandlers = () => {
1816
- document.querySelector('[data-action="toggle-thinking"]')?.addEventListener('click', toggleThinking);
1817
- document.querySelector('[data-action="toggle-tools"]')?.addEventListener('click', toggleToolOutputs);
1844
+ document.querySelector('[data-action="toggle-thinking"]')?.addEventListener('click', () => {
1845
+ setThinkingExpanded(!thinkingExpanded);
1846
+ });
1847
+ document.querySelector('[data-action="toggle-tools"]')?.addEventListener('click', () => {
1848
+ setToolOutputsExpanded(!toolOutputsExpanded);
1849
+ });
1850
+ document.querySelector('[data-action="toggle-hidden-messages"]')?.addEventListener('click', () => {
1851
+ setHiddenMessagesVisible(!showHiddenMessages);
1852
+ });
1818
1853
  };
1819
1854
 
1820
1855
  const isEditableTarget = (element) => {
@@ -1834,17 +1869,20 @@
1834
1869
  navigateTo(leafId, 'bottom');
1835
1870
  }
1836
1871
 
1837
- if (isEditableTarget(document.activeElement)) {
1872
+ if (e.ctrlKey || e.metaKey || e.altKey || isEditableTarget(document.activeElement)) {
1838
1873
  return;
1839
1874
  }
1840
1875
 
1841
1876
  const key = e.key.toLowerCase();
1842
1877
  if (key === 't') {
1843
1878
  e.preventDefault();
1844
- toggleThinking();
1879
+ setThinkingExpanded(!thinkingExpanded);
1845
1880
  } else if (key === 'o') {
1846
1881
  e.preventDefault();
1847
- toggleToolOutputs();
1882
+ setToolOutputsExpanded(!toolOutputsExpanded);
1883
+ } else if (key === 'h') {
1884
+ e.preventDefault();
1885
+ setHiddenMessagesVisible(!showHiddenMessages);
1848
1886
  }
1849
1887
  });
1850
1888
 
@@ -12,6 +12,7 @@ export declare class InteractiveThemeController {
12
12
  private readonly onChanged;
13
13
  private currentThemeSetting;
14
14
  private terminalTheme;
15
+ private terminalColors;
15
16
  private activeThemeName;
16
17
  private autoSyncEnabled;
17
18
  private terminalColorSchemeUnsubscribe;
@@ -32,6 +33,11 @@ export declare class InteractiveThemeController {
32
33
  dispose(): void;
33
34
  getTerminalTheme(): TerminalTheme;
34
35
  private applyThemeName;
36
+ /**
37
+ * Query the terminal's default colors, which themes use for tokens set to "". Startup does not
38
+ * wait for the replies; the UI re-renders when they arrive. Returns the background reply.
39
+ */
40
+ private queryTerminalDefaultColors;
35
41
  private notifyChanged;
36
42
  private setAutoSync;
37
43
  private bindTerminalColorSchemeListener;
@@ -1 +1 @@
1
- {"version":3,"file":"theme-controller.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EASN,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,MAAM,YAAY,CAAC;AAEpB,KAAK,WAAW,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,qBAAa,0BAA0B;IACtC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAM;IACzB,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,aAAa,CAA0D;IAC/E,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,8BAA8B,CAA2B;IAEjE,YACC,EAAE,EAAE,GAAG,EACP,OAAO,EAAE;QACR,kBAAkB,EAAE,MAAM,eAAe,CAAC;QAC1C,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;QACrC,SAAS,EAAE,MAAM,IAAI,CAAC;QACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC7B,EAaD;IAED,SAAS,IAAI,IAAI,CAIhB;IAEK,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAwBvC;IAED,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,WAAW,CAO9D;IAEK,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGzD;IAED,gBAAgB,CAAC,aAAa,EAAE,KAAK,GAAG,WAAW,CAMlD;IAED,OAAO,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAOxC;IAED,eAAe,IAAI,IAAI,CAEtB;IAED,OAAO,IAAI,IAAI,CAId;IAED,gBAAgB,IAAI,aAAa,CAEhC;IAED,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,+BAA+B;IAMvC,OAAO,CAAC,kBAAkB;CAa1B","sourcesContent":["import type { TUI } from \"@earendil-works/pi-tui\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport {\n\tdetectTerminalBackgroundFromEnv,\n\tdetectTerminalBackgroundTheme,\n\tdetectTerminalThemeForAuto,\n\tinitTheme,\n\tparseAutoThemeSetting,\n\tresolveThemeSetting,\n\tsetTheme,\n\tsetThemeInstance,\n\ttype TerminalTheme,\n\ttype Theme,\n} from \"./theme.ts\";\n\ntype ThemeResult = { success: boolean; error?: string };\n\nexport class InteractiveThemeController {\n\tprivate readonly ui: TUI;\n\tprivate readonly getSettingsManager: () => SettingsManager;\n\tprivate readonly showError: (message: string) => void;\n\tprivate readonly onChanged: () => void;\n\tprivate currentThemeSetting: string | undefined;\n\tprivate terminalTheme: TerminalTheme = detectTerminalBackgroundFromEnv().theme;\n\tprivate activeThemeName: string | undefined;\n\tprivate autoSyncEnabled = false;\n\tprivate terminalColorSchemeUnsubscribe: (() => void) | undefined;\n\n\tconstructor(\n\t\tui: TUI,\n\t\toptions: {\n\t\t\tgetSettingsManager: () => SettingsManager;\n\t\t\tshowError: (message: string) => void;\n\t\t\tonChanged: () => void;\n\t\t\tinitialThemeSetting?: string;\n\t\t},\n\t) {\n\t\tthis.ui = ui;\n\t\tthis.getSettingsManager = options.getSettingsManager;\n\t\tthis.showError = options.showError;\n\t\tthis.onChanged = options.onChanged;\n\t\tthis.currentThemeSetting = options.initialThemeSetting;\n\t\tthis.activeThemeName = resolveThemeSetting(\n\t\t\tthis.currentThemeSetting ?? this.getSettingsManager().getThemeSetting(),\n\t\t\tthis.terminalTheme,\n\t\t);\n\t\tinitTheme(this.activeThemeName, true);\n\t\tthis.bindTerminalColorSchemeListener();\n\t}\n\n\trebindTui(): void {\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.bindTerminalColorSchemeListener();\n\t\tthis.ui.setTerminalColorSchemeNotifications(this.autoSyncEnabled);\n\t}\n\n\tasync applyFromSettings(): Promise<void> {\n\t\tconst settingsManager = this.getSettingsManager();\n\t\tconst themeSetting = this.currentThemeSetting ?? settingsManager.getThemeSetting();\n\t\tconst autoTheme = parseAutoThemeSetting(themeSetting);\n\t\tif (autoTheme) {\n\t\t\tthis.terminalTheme = await detectTerminalThemeForAuto({ ui: this.ui, timeoutMs: 100 });\n\t\t\tthis.setAutoSync(true);\n\t\t\tthis.applyThemeName(this.terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme, true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAutoSync(false);\n\t\tif (themeSetting !== undefined) {\n\t\t\tthis.applyThemeName(themeSetting, true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst detection = await detectTerminalBackgroundTheme({ ui: this.ui, timeoutMs: 100 });\n\t\tthis.terminalTheme = detection.theme;\n\t\tif (!this.applyThemeName(detection.theme).success) return;\n\t\tif (detection.confidence === \"high\") {\n\t\t\tsettingsManager.setTheme(detection.theme);\n\t\t\tawait settingsManager.flush();\n\t\t}\n\t}\n\n\tgetThemeSelection(): string | undefined {\n\t\treturn this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting() ?? this.activeThemeName;\n\t}\n\n\tsetThemeName(themeName: string, showError = false): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tconst result = this.applyThemeName(themeName, showError);\n\t\tif (result.success) {\n\t\t\tthis.currentThemeSetting = themeName;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync setThemeSetting(themeSetting: string): Promise<void> {\n\t\tthis.currentThemeSetting = themeSetting;\n\t\tawait this.applyFromSettings();\n\t}\n\n\tsetThemeInstance(themeInstance: Theme): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tsetThemeInstance(themeInstance);\n\t\tthis.activeThemeName = \"<in-memory>\";\n\t\tthis.notifyChanged();\n\t\treturn { success: true };\n\t}\n\n\tpreview(themeSettingOrName: string): void {\n\t\tconst themeName = resolveThemeSetting(themeSettingOrName, this.terminalTheme) ?? this.activeThemeName;\n\t\tif (!themeName) return;\n\t\tif (setTheme(themeName, true).success) {\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tdisableAutoSync(): void {\n\t\tthis.setAutoSync(false);\n\t}\n\n\tdispose(): void {\n\t\tthis.setAutoSync(false);\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.terminalColorSchemeUnsubscribe = undefined;\n\t}\n\n\tgetTerminalTheme(): TerminalTheme {\n\t\treturn this.terminalTheme;\n\t}\n\n\tprivate applyThemeName(themeName: string, showError = false): ThemeResult {\n\t\tconst result = setTheme(themeName, true);\n\t\tthis.activeThemeName = result.success ? themeName : \"dark\";\n\t\tthis.notifyChanged();\n\t\tif (!result.success && showError) {\n\t\t\tthis.showError(`Failed to load theme \"${themeName}\": ${result.error}\\nFell back to dark theme.`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate notifyChanged(): void {\n\t\tthis.ui.invalidate();\n\t\tthis.onChanged();\n\t}\n\n\tprivate setAutoSync(enabled: boolean): void {\n\t\tif (this.autoSyncEnabled === enabled) return;\n\t\tthis.autoSyncEnabled = enabled;\n\t\tthis.ui.setTerminalColorSchemeNotifications(enabled);\n\t}\n\n\tprivate bindTerminalColorSchemeListener(): void {\n\t\tthis.terminalColorSchemeUnsubscribe = this.ui.onTerminalColorSchemeChange((terminalTheme) =>\n\t\t\tthis.applyTerminalTheme(terminalTheme),\n\t\t);\n\t}\n\n\tprivate applyTerminalTheme(terminalTheme: TerminalTheme): void {\n\t\tif (!this.autoSyncEnabled) return;\n\t\tthis.terminalTheme = terminalTheme;\n\t\tconst autoTheme = parseAutoThemeSetting(this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting());\n\t\tif (!autoTheme) {\n\t\t\tthis.setAutoSync(false);\n\t\t\treturn;\n\t\t}\n\t\tconst themeName = terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme;\n\t\tif (themeName !== this.activeThemeName) {\n\t\t\tthis.applyThemeName(themeName);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"theme-controller.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAUN,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,MAAM,YAAY,CAAC;AAEpB,KAAK,WAAW,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAQxD,qBAAa,0BAA0B;IACtC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAM;IACzB,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,aAAa,CAA0D;IAE/E,OAAO,CAAC,cAAc,CAAwD;IAC9E,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,8BAA8B,CAA2B;IAEjE,YACC,EAAE,EAAE,GAAG,EACP,OAAO,EAAE;QACR,kBAAkB,EAAE,MAAM,eAAe,CAAC;QAC1C,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;QACrC,SAAS,EAAE,MAAM,IAAI,CAAC;QACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC7B,EAaD;IAED,SAAS,IAAI,IAAI,CAIhB;IAEK,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA8BvC;IAED,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAEtC;IAED,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,WAAW,CAO9D;IAEK,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGzD;IAED,gBAAgB,CAAC,aAAa,EAAE,KAAK,GAAG,WAAW,CAMlD;IAED,OAAO,CAAC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAOxC;IAED,eAAe,IAAI,IAAI,CAEtB;IAED,OAAO,IAAI,IAAI,CAId;IAED,gBAAgB,IAAI,aAAa,CAEhC;IAED,OAAO,CAAC,cAAc;IAUtB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAoBlC,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,+BAA+B;IAMvC,OAAO,CAAC,kBAAkB;CAe1B","sourcesContent":["import type { RgbColor, TUI } from \"@earendil-works/pi-tui\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport {\n\tdetectTerminalBackgroundFromEnv,\n\tdetectTerminalBackgroundTheme,\n\tdetectTerminalThemeForAuto,\n\tinitTheme,\n\tparseAutoThemeSetting,\n\tresolveThemeSetting,\n\tsetTerminalDefaultColors,\n\tsetTheme,\n\tsetThemeInstance,\n\ttype TerminalTheme,\n\ttype Theme,\n} from \"./theme.ts\";\n\ntype ThemeResult = { success: boolean; error?: string };\n\nconst TERMINAL_QUERY_TIMEOUT_MS = 100;\n\nfunction sameRgb(a: RgbColor | undefined, b: RgbColor | undefined): boolean {\n\treturn a === b || (a !== undefined && b !== undefined && a.r === b.r && a.g === b.g && a.b === b.b);\n}\n\nexport class InteractiveThemeController {\n\tprivate readonly ui: TUI;\n\tprivate readonly getSettingsManager: () => SettingsManager;\n\tprivate readonly showError: (message: string) => void;\n\tprivate readonly onChanged: () => void;\n\tprivate currentThemeSetting: string | undefined;\n\tprivate terminalTheme: TerminalTheme = detectTerminalBackgroundFromEnv().theme;\n\t// Last reported default colors; a query that times out keeps them instead of erasing them.\n\tprivate terminalColors: { foreground?: RgbColor; background?: RgbColor } = {};\n\tprivate activeThemeName: string | undefined;\n\tprivate autoSyncEnabled = false;\n\tprivate terminalColorSchemeUnsubscribe: (() => void) | undefined;\n\n\tconstructor(\n\t\tui: TUI,\n\t\toptions: {\n\t\t\tgetSettingsManager: () => SettingsManager;\n\t\t\tshowError: (message: string) => void;\n\t\t\tonChanged: () => void;\n\t\t\tinitialThemeSetting?: string;\n\t\t},\n\t) {\n\t\tthis.ui = ui;\n\t\tthis.getSettingsManager = options.getSettingsManager;\n\t\tthis.showError = options.showError;\n\t\tthis.onChanged = options.onChanged;\n\t\tthis.currentThemeSetting = options.initialThemeSetting;\n\t\tthis.activeThemeName = resolveThemeSetting(\n\t\t\tthis.currentThemeSetting ?? this.getSettingsManager().getThemeSetting(),\n\t\t\tthis.terminalTheme,\n\t\t);\n\t\tinitTheme(this.activeThemeName, true);\n\t\tthis.bindTerminalColorSchemeListener();\n\t}\n\n\trebindTui(): void {\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.bindTerminalColorSchemeListener();\n\t\tthis.ui.setTerminalColorSchemeNotifications(this.autoSyncEnabled);\n\t}\n\n\tasync applyFromSettings(): Promise<void> {\n\t\tconst settingsManager = this.getSettingsManager();\n\t\tconst themeSetting = this.currentThemeSetting ?? settingsManager.getThemeSetting();\n\t\tconst autoTheme = parseAutoThemeSetting(themeSetting);\n\t\t// Theme detection reuses the background reply of the default color query.\n\t\tconst background = this.queryTerminalDefaultColors();\n\t\tconst detector = {\n\t\t\tqueryTerminalBackgroundColor: () => background,\n\t\t\tqueryTerminalColorScheme: (options: { timeoutMs: number }) => this.ui.queryTerminalColorScheme(options),\n\t\t};\n\t\tif (autoTheme) {\n\t\t\tthis.terminalTheme = await detectTerminalThemeForAuto({ ui: detector, timeoutMs: TERMINAL_QUERY_TIMEOUT_MS });\n\t\t\tthis.setAutoSync(true);\n\t\t\tthis.applyThemeName(this.terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme, true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAutoSync(false);\n\t\tif (themeSetting !== undefined) {\n\t\t\tthis.applyThemeName(themeSetting, true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst detection = await detectTerminalBackgroundTheme({ ui: detector, timeoutMs: TERMINAL_QUERY_TIMEOUT_MS });\n\t\tthis.terminalTheme = detection.theme;\n\t\tif (!this.applyThemeName(detection.theme).success) return;\n\t\tif (detection.confidence === \"high\") {\n\t\t\tsettingsManager.setTheme(detection.theme);\n\t\t\tawait settingsManager.flush();\n\t\t}\n\t}\n\n\tgetThemeSelection(): string | undefined {\n\t\treturn this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting() ?? this.activeThemeName;\n\t}\n\n\tsetThemeName(themeName: string, showError = false): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tconst result = this.applyThemeName(themeName, showError);\n\t\tif (result.success) {\n\t\t\tthis.currentThemeSetting = themeName;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync setThemeSetting(themeSetting: string): Promise<void> {\n\t\tthis.currentThemeSetting = themeSetting;\n\t\tawait this.applyFromSettings();\n\t}\n\n\tsetThemeInstance(themeInstance: Theme): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tsetThemeInstance(themeInstance);\n\t\tthis.activeThemeName = \"<in-memory>\";\n\t\tthis.notifyChanged();\n\t\treturn { success: true };\n\t}\n\n\tpreview(themeSettingOrName: string): void {\n\t\tconst themeName = resolveThemeSetting(themeSettingOrName, this.terminalTheme) ?? this.activeThemeName;\n\t\tif (!themeName) return;\n\t\tif (setTheme(themeName, true).success) {\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tdisableAutoSync(): void {\n\t\tthis.setAutoSync(false);\n\t}\n\n\tdispose(): void {\n\t\tthis.setAutoSync(false);\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.terminalColorSchemeUnsubscribe = undefined;\n\t}\n\n\tgetTerminalTheme(): TerminalTheme {\n\t\treturn this.terminalTheme;\n\t}\n\n\tprivate applyThemeName(themeName: string, showError = false): ThemeResult {\n\t\tconst result = setTheme(themeName, true);\n\t\tthis.activeThemeName = result.success ? themeName : \"dark\";\n\t\tthis.notifyChanged();\n\t\tif (!result.success && showError) {\n\t\t\tthis.showError(`Failed to load theme \"${themeName}\": ${result.error}\\nFell back to dark theme.`);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Query the terminal's default colors, which themes use for tokens set to \"\". Startup does not\n\t * wait for the replies; the UI re-renders when they arrive. Returns the background reply.\n\t */\n\tprivate queryTerminalDefaultColors(): Promise<RgbColor | undefined> {\n\t\tconst timeoutMs = TERMINAL_QUERY_TIMEOUT_MS;\n\t\tconst foreground = this.ui.queryTerminalForegroundColor({ timeoutMs });\n\t\tconst background = this.ui.queryTerminalBackgroundColor({ timeoutMs });\n\t\tvoid Promise.all([foreground, background]).then(([foregroundColor, backgroundColor]) => {\n\t\t\tconst previous = this.terminalColors;\n\t\t\tconst next = {\n\t\t\t\tforeground: foregroundColor ?? previous.foreground,\n\t\t\t\tbackground: backgroundColor ?? previous.background,\n\t\t\t};\n\t\t\t// Re-rendering rebuilds every component, so skip it when nothing changed (including timeouts).\n\t\t\tif (sameRgb(next.foreground, previous.foreground) && sameRgb(next.background, previous.background)) return;\n\t\t\tthis.terminalColors = next;\n\t\t\tsetTerminalDefaultColors(next);\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t});\n\t\treturn background;\n\t}\n\n\tprivate notifyChanged(): void {\n\t\tthis.ui.invalidate();\n\t\tthis.onChanged();\n\t}\n\n\tprivate setAutoSync(enabled: boolean): void {\n\t\tif (this.autoSyncEnabled === enabled) return;\n\t\tthis.autoSyncEnabled = enabled;\n\t\tthis.ui.setTerminalColorSchemeNotifications(enabled);\n\t}\n\n\tprivate bindTerminalColorSchemeListener(): void {\n\t\tthis.terminalColorSchemeUnsubscribe = this.ui.onTerminalColorSchemeChange((terminalTheme) =>\n\t\t\tthis.applyTerminalTheme(terminalTheme),\n\t\t);\n\t}\n\n\tprivate applyTerminalTheme(terminalTheme: TerminalTheme): void {\n\t\tif (!this.autoSyncEnabled) return;\n\t\tthis.terminalTheme = terminalTheme;\n\t\t// Switching light/dark changes the terminal's default colors too.\n\t\tvoid this.queryTerminalDefaultColors();\n\t\tconst autoTheme = parseAutoThemeSetting(this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting());\n\t\tif (!autoTheme) {\n\t\t\tthis.setAutoSync(false);\n\t\t\treturn;\n\t\t}\n\t\tconst themeName = terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme;\n\t\tif (themeName !== this.activeThemeName) {\n\t\t\tthis.applyThemeName(themeName);\n\t\t}\n\t}\n}\n"]}
@@ -1,4 +1,8 @@
1
- import { detectTerminalBackgroundFromEnv, detectTerminalBackgroundTheme, detectTerminalThemeForAuto, initTheme, parseAutoThemeSetting, resolveThemeSetting, setTheme, setThemeInstance, } from "./theme.js";
1
+ import { detectTerminalBackgroundFromEnv, detectTerminalBackgroundTheme, detectTerminalThemeForAuto, initTheme, parseAutoThemeSetting, resolveThemeSetting, setTerminalDefaultColors, setTheme, setThemeInstance, } from "./theme.js";
2
+ const TERMINAL_QUERY_TIMEOUT_MS = 100;
3
+ function sameRgb(a, b) {
4
+ return a === b || (a !== undefined && b !== undefined && a.r === b.r && a.g === b.g && a.b === b.b);
5
+ }
2
6
  export class InteractiveThemeController {
3
7
  ui;
4
8
  getSettingsManager;
@@ -6,6 +10,8 @@ export class InteractiveThemeController {
6
10
  onChanged;
7
11
  currentThemeSetting;
8
12
  terminalTheme = detectTerminalBackgroundFromEnv().theme;
13
+ // Last reported default colors; a query that times out keeps them instead of erasing them.
14
+ terminalColors = {};
9
15
  activeThemeName;
10
16
  autoSyncEnabled = false;
11
17
  terminalColorSchemeUnsubscribe;
@@ -28,8 +34,14 @@ export class InteractiveThemeController {
28
34
  const settingsManager = this.getSettingsManager();
29
35
  const themeSetting = this.currentThemeSetting ?? settingsManager.getThemeSetting();
30
36
  const autoTheme = parseAutoThemeSetting(themeSetting);
37
+ // Theme detection reuses the background reply of the default color query.
38
+ const background = this.queryTerminalDefaultColors();
39
+ const detector = {
40
+ queryTerminalBackgroundColor: () => background,
41
+ queryTerminalColorScheme: (options) => this.ui.queryTerminalColorScheme(options),
42
+ };
31
43
  if (autoTheme) {
32
- this.terminalTheme = await detectTerminalThemeForAuto({ ui: this.ui, timeoutMs: 100 });
44
+ this.terminalTheme = await detectTerminalThemeForAuto({ ui: detector, timeoutMs: TERMINAL_QUERY_TIMEOUT_MS });
33
45
  this.setAutoSync(true);
34
46
  this.applyThemeName(this.terminalTheme === "light" ? autoTheme.lightTheme : autoTheme.darkTheme, true);
35
47
  return;
@@ -39,7 +51,7 @@ export class InteractiveThemeController {
39
51
  this.applyThemeName(themeSetting, true);
40
52
  return;
41
53
  }
42
- const detection = await detectTerminalBackgroundTheme({ ui: this.ui, timeoutMs: 100 });
54
+ const detection = await detectTerminalBackgroundTheme({ ui: detector, timeoutMs: TERMINAL_QUERY_TIMEOUT_MS });
43
55
  this.terminalTheme = detection.theme;
44
56
  if (!this.applyThemeName(detection.theme).success)
45
57
  return;
@@ -99,6 +111,30 @@ export class InteractiveThemeController {
99
111
  }
100
112
  return result;
101
113
  }
114
+ /**
115
+ * Query the terminal's default colors, which themes use for tokens set to "". Startup does not
116
+ * wait for the replies; the UI re-renders when they arrive. Returns the background reply.
117
+ */
118
+ queryTerminalDefaultColors() {
119
+ const timeoutMs = TERMINAL_QUERY_TIMEOUT_MS;
120
+ const foreground = this.ui.queryTerminalForegroundColor({ timeoutMs });
121
+ const background = this.ui.queryTerminalBackgroundColor({ timeoutMs });
122
+ void Promise.all([foreground, background]).then(([foregroundColor, backgroundColor]) => {
123
+ const previous = this.terminalColors;
124
+ const next = {
125
+ foreground: foregroundColor ?? previous.foreground,
126
+ background: backgroundColor ?? previous.background,
127
+ };
128
+ // Re-rendering rebuilds every component, so skip it when nothing changed (including timeouts).
129
+ if (sameRgb(next.foreground, previous.foreground) && sameRgb(next.background, previous.background))
130
+ return;
131
+ this.terminalColors = next;
132
+ setTerminalDefaultColors(next);
133
+ this.ui.invalidate();
134
+ this.ui.requestRender();
135
+ });
136
+ return background;
137
+ }
102
138
  notifyChanged() {
103
139
  this.ui.invalidate();
104
140
  this.onChanged();
@@ -116,6 +152,8 @@ export class InteractiveThemeController {
116
152
  if (!this.autoSyncEnabled)
117
153
  return;
118
154
  this.terminalTheme = terminalTheme;
155
+ // Switching light/dark changes the terminal's default colors too.
156
+ void this.queryTerminalDefaultColors();
119
157
  const autoTheme = parseAutoThemeSetting(this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting());
120
158
  if (!autoTheme) {
121
159
  this.setAutoSync(false);
@@ -1 +1 @@
1
- {"version":3,"file":"theme-controller.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TUI } from \"@earendil-works/pi-tui\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport {\n\tdetectTerminalBackgroundFromEnv,\n\tdetectTerminalBackgroundTheme,\n\tdetectTerminalThemeForAuto,\n\tinitTheme,\n\tparseAutoThemeSetting,\n\tresolveThemeSetting,\n\tsetTheme,\n\tsetThemeInstance,\n\ttype TerminalTheme,\n\ttype Theme,\n} from \"./theme.ts\";\n\ntype ThemeResult = { success: boolean; error?: string };\n\nexport class InteractiveThemeController {\n\tprivate readonly ui: TUI;\n\tprivate readonly getSettingsManager: () => SettingsManager;\n\tprivate readonly showError: (message: string) => void;\n\tprivate readonly onChanged: () => void;\n\tprivate currentThemeSetting: string | undefined;\n\tprivate terminalTheme: TerminalTheme = detectTerminalBackgroundFromEnv().theme;\n\tprivate activeThemeName: string | undefined;\n\tprivate autoSyncEnabled = false;\n\tprivate terminalColorSchemeUnsubscribe: (() => void) | undefined;\n\n\tconstructor(\n\t\tui: TUI,\n\t\toptions: {\n\t\t\tgetSettingsManager: () => SettingsManager;\n\t\t\tshowError: (message: string) => void;\n\t\t\tonChanged: () => void;\n\t\t\tinitialThemeSetting?: string;\n\t\t},\n\t) {\n\t\tthis.ui = ui;\n\t\tthis.getSettingsManager = options.getSettingsManager;\n\t\tthis.showError = options.showError;\n\t\tthis.onChanged = options.onChanged;\n\t\tthis.currentThemeSetting = options.initialThemeSetting;\n\t\tthis.activeThemeName = resolveThemeSetting(\n\t\t\tthis.currentThemeSetting ?? this.getSettingsManager().getThemeSetting(),\n\t\t\tthis.terminalTheme,\n\t\t);\n\t\tinitTheme(this.activeThemeName, true);\n\t\tthis.bindTerminalColorSchemeListener();\n\t}\n\n\trebindTui(): void {\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.bindTerminalColorSchemeListener();\n\t\tthis.ui.setTerminalColorSchemeNotifications(this.autoSyncEnabled);\n\t}\n\n\tasync applyFromSettings(): Promise<void> {\n\t\tconst settingsManager = this.getSettingsManager();\n\t\tconst themeSetting = this.currentThemeSetting ?? settingsManager.getThemeSetting();\n\t\tconst autoTheme = parseAutoThemeSetting(themeSetting);\n\t\tif (autoTheme) {\n\t\t\tthis.terminalTheme = await detectTerminalThemeForAuto({ ui: this.ui, timeoutMs: 100 });\n\t\t\tthis.setAutoSync(true);\n\t\t\tthis.applyThemeName(this.terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme, true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAutoSync(false);\n\t\tif (themeSetting !== undefined) {\n\t\t\tthis.applyThemeName(themeSetting, true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst detection = await detectTerminalBackgroundTheme({ ui: this.ui, timeoutMs: 100 });\n\t\tthis.terminalTheme = detection.theme;\n\t\tif (!this.applyThemeName(detection.theme).success) return;\n\t\tif (detection.confidence === \"high\") {\n\t\t\tsettingsManager.setTheme(detection.theme);\n\t\t\tawait settingsManager.flush();\n\t\t}\n\t}\n\n\tgetThemeSelection(): string | undefined {\n\t\treturn this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting() ?? this.activeThemeName;\n\t}\n\n\tsetThemeName(themeName: string, showError = false): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tconst result = this.applyThemeName(themeName, showError);\n\t\tif (result.success) {\n\t\t\tthis.currentThemeSetting = themeName;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync setThemeSetting(themeSetting: string): Promise<void> {\n\t\tthis.currentThemeSetting = themeSetting;\n\t\tawait this.applyFromSettings();\n\t}\n\n\tsetThemeInstance(themeInstance: Theme): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tsetThemeInstance(themeInstance);\n\t\tthis.activeThemeName = \"<in-memory>\";\n\t\tthis.notifyChanged();\n\t\treturn { success: true };\n\t}\n\n\tpreview(themeSettingOrName: string): void {\n\t\tconst themeName = resolveThemeSetting(themeSettingOrName, this.terminalTheme) ?? this.activeThemeName;\n\t\tif (!themeName) return;\n\t\tif (setTheme(themeName, true).success) {\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tdisableAutoSync(): void {\n\t\tthis.setAutoSync(false);\n\t}\n\n\tdispose(): void {\n\t\tthis.setAutoSync(false);\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.terminalColorSchemeUnsubscribe = undefined;\n\t}\n\n\tgetTerminalTheme(): TerminalTheme {\n\t\treturn this.terminalTheme;\n\t}\n\n\tprivate applyThemeName(themeName: string, showError = false): ThemeResult {\n\t\tconst result = setTheme(themeName, true);\n\t\tthis.activeThemeName = result.success ? themeName : \"dark\";\n\t\tthis.notifyChanged();\n\t\tif (!result.success && showError) {\n\t\t\tthis.showError(`Failed to load theme \"${themeName}\": ${result.error}\\nFell back to dark theme.`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate notifyChanged(): void {\n\t\tthis.ui.invalidate();\n\t\tthis.onChanged();\n\t}\n\n\tprivate setAutoSync(enabled: boolean): void {\n\t\tif (this.autoSyncEnabled === enabled) return;\n\t\tthis.autoSyncEnabled = enabled;\n\t\tthis.ui.setTerminalColorSchemeNotifications(enabled);\n\t}\n\n\tprivate bindTerminalColorSchemeListener(): void {\n\t\tthis.terminalColorSchemeUnsubscribe = this.ui.onTerminalColorSchemeChange((terminalTheme) =>\n\t\t\tthis.applyTerminalTheme(terminalTheme),\n\t\t);\n\t}\n\n\tprivate applyTerminalTheme(terminalTheme: TerminalTheme): void {\n\t\tif (!this.autoSyncEnabled) return;\n\t\tthis.terminalTheme = terminalTheme;\n\t\tconst autoTheme = parseAutoThemeSetting(this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting());\n\t\tif (!autoTheme) {\n\t\t\tthis.setAutoSync(false);\n\t\t\treturn;\n\t\t}\n\t\tconst themeName = terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme;\n\t\tif (themeName !== this.activeThemeName) {\n\t\t\tthis.applyThemeName(themeName);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"theme-controller.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RgbColor, TUI } from \"@earendil-works/pi-tui\";\nimport type { SettingsManager } from \"../../../core/settings-manager.ts\";\nimport {\n\tdetectTerminalBackgroundFromEnv,\n\tdetectTerminalBackgroundTheme,\n\tdetectTerminalThemeForAuto,\n\tinitTheme,\n\tparseAutoThemeSetting,\n\tresolveThemeSetting,\n\tsetTerminalDefaultColors,\n\tsetTheme,\n\tsetThemeInstance,\n\ttype TerminalTheme,\n\ttype Theme,\n} from \"./theme.ts\";\n\ntype ThemeResult = { success: boolean; error?: string };\n\nconst TERMINAL_QUERY_TIMEOUT_MS = 100;\n\nfunction sameRgb(a: RgbColor | undefined, b: RgbColor | undefined): boolean {\n\treturn a === b || (a !== undefined && b !== undefined && a.r === b.r && a.g === b.g && a.b === b.b);\n}\n\nexport class InteractiveThemeController {\n\tprivate readonly ui: TUI;\n\tprivate readonly getSettingsManager: () => SettingsManager;\n\tprivate readonly showError: (message: string) => void;\n\tprivate readonly onChanged: () => void;\n\tprivate currentThemeSetting: string | undefined;\n\tprivate terminalTheme: TerminalTheme = detectTerminalBackgroundFromEnv().theme;\n\t// Last reported default colors; a query that times out keeps them instead of erasing them.\n\tprivate terminalColors: { foreground?: RgbColor; background?: RgbColor } = {};\n\tprivate activeThemeName: string | undefined;\n\tprivate autoSyncEnabled = false;\n\tprivate terminalColorSchemeUnsubscribe: (() => void) | undefined;\n\n\tconstructor(\n\t\tui: TUI,\n\t\toptions: {\n\t\t\tgetSettingsManager: () => SettingsManager;\n\t\t\tshowError: (message: string) => void;\n\t\t\tonChanged: () => void;\n\t\t\tinitialThemeSetting?: string;\n\t\t},\n\t) {\n\t\tthis.ui = ui;\n\t\tthis.getSettingsManager = options.getSettingsManager;\n\t\tthis.showError = options.showError;\n\t\tthis.onChanged = options.onChanged;\n\t\tthis.currentThemeSetting = options.initialThemeSetting;\n\t\tthis.activeThemeName = resolveThemeSetting(\n\t\t\tthis.currentThemeSetting ?? this.getSettingsManager().getThemeSetting(),\n\t\t\tthis.terminalTheme,\n\t\t);\n\t\tinitTheme(this.activeThemeName, true);\n\t\tthis.bindTerminalColorSchemeListener();\n\t}\n\n\trebindTui(): void {\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.bindTerminalColorSchemeListener();\n\t\tthis.ui.setTerminalColorSchemeNotifications(this.autoSyncEnabled);\n\t}\n\n\tasync applyFromSettings(): Promise<void> {\n\t\tconst settingsManager = this.getSettingsManager();\n\t\tconst themeSetting = this.currentThemeSetting ?? settingsManager.getThemeSetting();\n\t\tconst autoTheme = parseAutoThemeSetting(themeSetting);\n\t\t// Theme detection reuses the background reply of the default color query.\n\t\tconst background = this.queryTerminalDefaultColors();\n\t\tconst detector = {\n\t\t\tqueryTerminalBackgroundColor: () => background,\n\t\t\tqueryTerminalColorScheme: (options: { timeoutMs: number }) => this.ui.queryTerminalColorScheme(options),\n\t\t};\n\t\tif (autoTheme) {\n\t\t\tthis.terminalTheme = await detectTerminalThemeForAuto({ ui: detector, timeoutMs: TERMINAL_QUERY_TIMEOUT_MS });\n\t\t\tthis.setAutoSync(true);\n\t\t\tthis.applyThemeName(this.terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme, true);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setAutoSync(false);\n\t\tif (themeSetting !== undefined) {\n\t\t\tthis.applyThemeName(themeSetting, true);\n\t\t\treturn;\n\t\t}\n\n\t\tconst detection = await detectTerminalBackgroundTheme({ ui: detector, timeoutMs: TERMINAL_QUERY_TIMEOUT_MS });\n\t\tthis.terminalTheme = detection.theme;\n\t\tif (!this.applyThemeName(detection.theme).success) return;\n\t\tif (detection.confidence === \"high\") {\n\t\t\tsettingsManager.setTheme(detection.theme);\n\t\t\tawait settingsManager.flush();\n\t\t}\n\t}\n\n\tgetThemeSelection(): string | undefined {\n\t\treturn this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting() ?? this.activeThemeName;\n\t}\n\n\tsetThemeName(themeName: string, showError = false): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tconst result = this.applyThemeName(themeName, showError);\n\t\tif (result.success) {\n\t\t\tthis.currentThemeSetting = themeName;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync setThemeSetting(themeSetting: string): Promise<void> {\n\t\tthis.currentThemeSetting = themeSetting;\n\t\tawait this.applyFromSettings();\n\t}\n\n\tsetThemeInstance(themeInstance: Theme): ThemeResult {\n\t\tthis.setAutoSync(false);\n\t\tsetThemeInstance(themeInstance);\n\t\tthis.activeThemeName = \"<in-memory>\";\n\t\tthis.notifyChanged();\n\t\treturn { success: true };\n\t}\n\n\tpreview(themeSettingOrName: string): void {\n\t\tconst themeName = resolveThemeSetting(themeSettingOrName, this.terminalTheme) ?? this.activeThemeName;\n\t\tif (!themeName) return;\n\t\tif (setTheme(themeName, true).success) {\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t}\n\t}\n\n\tdisableAutoSync(): void {\n\t\tthis.setAutoSync(false);\n\t}\n\n\tdispose(): void {\n\t\tthis.setAutoSync(false);\n\t\tthis.terminalColorSchemeUnsubscribe?.();\n\t\tthis.terminalColorSchemeUnsubscribe = undefined;\n\t}\n\n\tgetTerminalTheme(): TerminalTheme {\n\t\treturn this.terminalTheme;\n\t}\n\n\tprivate applyThemeName(themeName: string, showError = false): ThemeResult {\n\t\tconst result = setTheme(themeName, true);\n\t\tthis.activeThemeName = result.success ? themeName : \"dark\";\n\t\tthis.notifyChanged();\n\t\tif (!result.success && showError) {\n\t\t\tthis.showError(`Failed to load theme \"${themeName}\": ${result.error}\\nFell back to dark theme.`);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Query the terminal's default colors, which themes use for tokens set to \"\". Startup does not\n\t * wait for the replies; the UI re-renders when they arrive. Returns the background reply.\n\t */\n\tprivate queryTerminalDefaultColors(): Promise<RgbColor | undefined> {\n\t\tconst timeoutMs = TERMINAL_QUERY_TIMEOUT_MS;\n\t\tconst foreground = this.ui.queryTerminalForegroundColor({ timeoutMs });\n\t\tconst background = this.ui.queryTerminalBackgroundColor({ timeoutMs });\n\t\tvoid Promise.all([foreground, background]).then(([foregroundColor, backgroundColor]) => {\n\t\t\tconst previous = this.terminalColors;\n\t\t\tconst next = {\n\t\t\t\tforeground: foregroundColor ?? previous.foreground,\n\t\t\t\tbackground: backgroundColor ?? previous.background,\n\t\t\t};\n\t\t\t// Re-rendering rebuilds every component, so skip it when nothing changed (including timeouts).\n\t\t\tif (sameRgb(next.foreground, previous.foreground) && sameRgb(next.background, previous.background)) return;\n\t\t\tthis.terminalColors = next;\n\t\t\tsetTerminalDefaultColors(next);\n\t\t\tthis.ui.invalidate();\n\t\t\tthis.ui.requestRender();\n\t\t});\n\t\treturn background;\n\t}\n\n\tprivate notifyChanged(): void {\n\t\tthis.ui.invalidate();\n\t\tthis.onChanged();\n\t}\n\n\tprivate setAutoSync(enabled: boolean): void {\n\t\tif (this.autoSyncEnabled === enabled) return;\n\t\tthis.autoSyncEnabled = enabled;\n\t\tthis.ui.setTerminalColorSchemeNotifications(enabled);\n\t}\n\n\tprivate bindTerminalColorSchemeListener(): void {\n\t\tthis.terminalColorSchemeUnsubscribe = this.ui.onTerminalColorSchemeChange((terminalTheme) =>\n\t\t\tthis.applyTerminalTheme(terminalTheme),\n\t\t);\n\t}\n\n\tprivate applyTerminalTheme(terminalTheme: TerminalTheme): void {\n\t\tif (!this.autoSyncEnabled) return;\n\t\tthis.terminalTheme = terminalTheme;\n\t\t// Switching light/dark changes the terminal's default colors too.\n\t\tvoid this.queryTerminalDefaultColors();\n\t\tconst autoTheme = parseAutoThemeSetting(this.currentThemeSetting ?? this.getSettingsManager().getThemeSetting());\n\t\tif (!autoTheme) {\n\t\t\tthis.setAutoSync(false);\n\t\t\treturn;\n\t\t}\n\t\tconst themeName = terminalTheme === \"light\" ? autoTheme.lightTheme : autoTheme.darkTheme;\n\t\tif (themeName !== this.activeThemeName) {\n\t\t\tthis.applyThemeName(themeName);\n\t\t}\n\t}\n}\n"]}
@@ -11,6 +11,7 @@ declare const ColorValueSchema: Type.TUnion<[Type.TString, Type.TInteger]>;
11
11
  declare const ThemeJsonSchema: Type.TObject<{
12
12
  $schema: Type.TOptional<Type.TString>;
13
13
  name: Type.TString;
14
+ appearance: Type.TOptional<Type.TUnion<[Type.TLiteral<"dark">, Type.TLiteral<"light">]>>;
14
15
  vars: Type.TOptional<Type.TRecord<"^.*$", Type.TUnion<[Type.TString, Type.TInteger]>>>;
15
16
  colors: Type.TObject<{
16
17
  accent: Type.TUnion<[Type.TString, Type.TInteger]>;
@@ -1 +1 @@
1
- {"version":3,"file":"theme-json.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-json.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,QAAA,MAAM,gBAAgB,4CAGpB,CAAC;AAEH,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6EnB,CAAC;AAIH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC9D,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAEhE,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,kBAAkB,CA2ClF","sourcesContent":["/**\n * Theme JSON validation, kept out of `theme.ts` on purpose.\n *\n * Validating user-authored theme files needs typebox, which costs ~17 MB of module graph to import.\n * Palette lookup does not, so a presentation that only uses built-in themes should never pay for it.\n * `interactive-mode.ts` installs this validator; anything that does not simply skips validation, as\n * built-in themes already do.\n */\n\nimport { type Static, Type } from \"typebox\";\nimport { Compile } from \"typebox/compile\";\n\nconst ColorValueSchema = Type.Union([\n\tType.String(), // hex \"#ff0000\", var ref \"primary\", or empty \"\"\n\tType.Integer({ minimum: 0, maximum: 255 }), // 256-color index\n]);\n\nconst ThemeJsonSchema = Type.Object({\n\t$schema: Type.Optional(Type.String()),\n\tname: Type.String(),\n\tvars: Type.Optional(Type.Record(Type.String(), ColorValueSchema)),\n\tcolors: Type.Object({\n\t\t// Core UI (11 colors)\n\t\taccent: ColorValueSchema,\n\t\tborder: ColorValueSchema,\n\t\tborderAccent: ColorValueSchema,\n\t\tborderMuted: ColorValueSchema,\n\t\tsuccess: ColorValueSchema,\n\t\terror: ColorValueSchema,\n\t\twarning: ColorValueSchema,\n\t\tmuted: ColorValueSchema,\n\t\tdim: ColorValueSchema,\n\t\ttext: ColorValueSchema,\n\t\tthinkingText: ColorValueSchema,\n\t\t// Scrollbar (2 optional colors)\n\t\tscrollbarTrack: Type.Optional(ColorValueSchema),\n\t\tscrollbarThumb: Type.Optional(ColorValueSchema),\n\t\t// Backgrounds & Content Text (11 required, 2 optional)\n\t\tselectedBg: ColorValueSchema,\n\t\tsearchMatchBg: Type.Optional(ColorValueSchema),\n\t\tsearchMatchText: Type.Optional(ColorValueSchema),\n\t\tuserMessageBg: ColorValueSchema,\n\t\tuserMessageText: ColorValueSchema,\n\t\tcustomMessageBg: ColorValueSchema,\n\t\tcustomMessageText: ColorValueSchema,\n\t\tcustomMessageLabel: ColorValueSchema,\n\t\ttoolPendingBg: ColorValueSchema,\n\t\ttoolSuccessBg: ColorValueSchema,\n\t\ttoolErrorBg: ColorValueSchema,\n\t\ttoolTitle: ColorValueSchema,\n\t\ttoolOutput: ColorValueSchema,\n\t\t// Markdown (10 colors)\n\t\tmdHeading: ColorValueSchema,\n\t\tmdLink: ColorValueSchema,\n\t\tmdLinkUrl: ColorValueSchema,\n\t\tmdCode: ColorValueSchema,\n\t\tmdCodeBlock: ColorValueSchema,\n\t\tmdCodeBlockBorder: ColorValueSchema,\n\t\tmdQuote: ColorValueSchema,\n\t\tmdQuoteBorder: ColorValueSchema,\n\t\tmdHr: ColorValueSchema,\n\t\tmdListBullet: ColorValueSchema,\n\t\t// Tool Diffs (3 colors)\n\t\ttoolDiffAdded: ColorValueSchema,\n\t\ttoolDiffRemoved: ColorValueSchema,\n\t\ttoolDiffContext: ColorValueSchema,\n\t\t// Syntax Highlighting (9 colors)\n\t\tsyntaxComment: ColorValueSchema,\n\t\tsyntaxKeyword: ColorValueSchema,\n\t\tsyntaxFunction: ColorValueSchema,\n\t\tsyntaxVariable: ColorValueSchema,\n\t\tsyntaxString: ColorValueSchema,\n\t\tsyntaxNumber: ColorValueSchema,\n\t\tsyntaxType: ColorValueSchema,\n\t\tsyntaxOperator: ColorValueSchema,\n\t\tsyntaxPunctuation: ColorValueSchema,\n\t\t// Thinking Level Borders (6 colors)\n\t\tthinkingOff: ColorValueSchema,\n\t\tthinkingMinimal: ColorValueSchema,\n\t\tthinkingLow: ColorValueSchema,\n\t\tthinkingMedium: ColorValueSchema,\n\t\tthinkingHigh: ColorValueSchema,\n\t\tthinkingXhigh: ColorValueSchema,\n\t\tthinkingMax: Type.Optional(ColorValueSchema),\n\t\t// Bash Mode (1 color)\n\t\tbashMode: ColorValueSchema,\n\t}),\n\texport: Type.Optional(\n\t\tType.Object({\n\t\t\tpageBg: Type.Optional(ColorValueSchema),\n\t\t\tcardBg: Type.Optional(ColorValueSchema),\n\t\t\tinfoBg: Type.Optional(ColorValueSchema),\n\t\t}),\n\t),\n});\n\nconst compiledThemeSchema = Compile(ThemeJsonSchema);\n\nexport type ThemeColorValue = Static<typeof ColorValueSchema>;\nexport type ValidatedThemeJson = Static<typeof ThemeJsonSchema>;\n\n/** Validate one theme document, throwing a message that names the offending tokens. */\nexport function validateThemeJson(label: string, json: unknown): ValidatedThemeJson {\n\tif (!compiledThemeSchema.Check(json)) {\n\t\tconst errors = Array.from(compiledThemeSchema.Errors(json));\n\t\tconst missingColors = new Set<string>();\n\t\tconst otherErrors: string[] = [];\n\n\t\tfor (const error of errors) {\n\t\t\tif (error.keyword === \"required\" && error.instancePath === \"/colors\") {\n\t\t\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\t\t\tfor (const requiredProperty of requiredProperties ?? []) {\n\t\t\t\t\tmissingColors.add(requiredProperty);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst path = error.instancePath || \"/\";\n\t\t\totherErrors.push(` - ${path}: ${error.message}`);\n\t\t}\n\n\t\tlet errorMessage = `Invalid theme \"${label}\":\\n`;\n\t\tif (missingColors.size > 0) {\n\t\t\terrorMessage += \"\\nMissing required color tokens:\\n\";\n\t\t\terrorMessage += Array.from(missingColors)\n\t\t\t\t.sort()\n\t\t\t\t.map((color) => ` - ${color}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\terrorMessage += '\\n\\nPlease add these colors to your theme\\'s \"colors\" object.';\n\t\t\terrorMessage += \"\\nSee the built-in themes (dark.json, light.json) for reference values.\";\n\t\t}\n\t\tif (otherErrors.length > 0) {\n\t\t\terrorMessage += `\\n\\nOther errors:\\n${otherErrors.join(\"\\n\")}`;\n\t\t}\n\n\t\tthrow new Error(errorMessage);\n\t}\n\n\tconst themeJson = json as ValidatedThemeJson;\n\tif (themeJson.name.includes(\"/\")) {\n\t\tthrow new Error(\n\t\t\t`Invalid theme name \"${themeJson.name}\": theme names cannot contain \"/\" because it is reserved for automatic light/dark theme settings.`,\n\t\t);\n\t}\n\treturn themeJson;\n}\n"]}
1
+ {"version":3,"file":"theme-json.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-json.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,QAAA,MAAM,gBAAgB,4CAGpB,CAAC;AAEH,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+EnB,CAAC;AAIH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC9D,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,eAAe,CAAC,CAAC;AAEhE,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,kBAAkB,CA2ClF","sourcesContent":["/**\n * Theme JSON validation, kept out of `theme.ts` on purpose.\n *\n * Validating user-authored theme files needs typebox, which costs ~17 MB of module graph to import.\n * Palette lookup does not, so a presentation that only uses built-in themes should never pay for it.\n * `interactive-mode.ts` installs this validator; anything that does not simply skips validation, as\n * built-in themes already do.\n */\n\nimport { type Static, Type } from \"typebox\";\nimport { Compile } from \"typebox/compile\";\n\nconst ColorValueSchema = Type.Union([\n\tType.String(), // hex, OKLCH, var ref \"primary\", or empty \"\"\n\tType.Integer({ minimum: 0, maximum: 255 }), // 256-color index\n]);\n\nconst ThemeJsonSchema = Type.Object({\n\t$schema: Type.Optional(Type.String()),\n\tname: Type.String(),\n\t// Background the theme is designed for. Detected from the theme colors when omitted.\n\tappearance: Type.Optional(Type.Union([Type.Literal(\"dark\"), Type.Literal(\"light\")])),\n\tvars: Type.Optional(Type.Record(Type.String(), ColorValueSchema)),\n\tcolors: Type.Object({\n\t\t// Core UI (11 colors)\n\t\taccent: ColorValueSchema,\n\t\tborder: ColorValueSchema,\n\t\tborderAccent: ColorValueSchema,\n\t\tborderMuted: ColorValueSchema,\n\t\tsuccess: ColorValueSchema,\n\t\terror: ColorValueSchema,\n\t\twarning: ColorValueSchema,\n\t\tmuted: ColorValueSchema,\n\t\tdim: ColorValueSchema,\n\t\ttext: ColorValueSchema,\n\t\tthinkingText: ColorValueSchema,\n\t\t// Scrollbar (2 optional colors)\n\t\tscrollbarTrack: Type.Optional(ColorValueSchema),\n\t\tscrollbarThumb: Type.Optional(ColorValueSchema),\n\t\t// Backgrounds & Content Text (11 required, 2 optional)\n\t\tselectedBg: ColorValueSchema,\n\t\tsearchMatchBg: Type.Optional(ColorValueSchema),\n\t\tsearchMatchText: Type.Optional(ColorValueSchema),\n\t\tuserMessageBg: ColorValueSchema,\n\t\tuserMessageText: ColorValueSchema,\n\t\tcustomMessageBg: ColorValueSchema,\n\t\tcustomMessageText: ColorValueSchema,\n\t\tcustomMessageLabel: ColorValueSchema,\n\t\ttoolPendingBg: ColorValueSchema,\n\t\ttoolSuccessBg: ColorValueSchema,\n\t\ttoolErrorBg: ColorValueSchema,\n\t\ttoolTitle: ColorValueSchema,\n\t\ttoolOutput: ColorValueSchema,\n\t\t// Markdown (10 colors)\n\t\tmdHeading: ColorValueSchema,\n\t\tmdLink: ColorValueSchema,\n\t\tmdLinkUrl: ColorValueSchema,\n\t\tmdCode: ColorValueSchema,\n\t\tmdCodeBlock: ColorValueSchema,\n\t\tmdCodeBlockBorder: ColorValueSchema,\n\t\tmdQuote: ColorValueSchema,\n\t\tmdQuoteBorder: ColorValueSchema,\n\t\tmdHr: ColorValueSchema,\n\t\tmdListBullet: ColorValueSchema,\n\t\t// Tool Diffs (3 colors)\n\t\ttoolDiffAdded: ColorValueSchema,\n\t\ttoolDiffRemoved: ColorValueSchema,\n\t\ttoolDiffContext: ColorValueSchema,\n\t\t// Syntax Highlighting (9 colors)\n\t\tsyntaxComment: ColorValueSchema,\n\t\tsyntaxKeyword: ColorValueSchema,\n\t\tsyntaxFunction: ColorValueSchema,\n\t\tsyntaxVariable: ColorValueSchema,\n\t\tsyntaxString: ColorValueSchema,\n\t\tsyntaxNumber: ColorValueSchema,\n\t\tsyntaxType: ColorValueSchema,\n\t\tsyntaxOperator: ColorValueSchema,\n\t\tsyntaxPunctuation: ColorValueSchema,\n\t\t// Thinking Level Borders (6 colors)\n\t\tthinkingOff: ColorValueSchema,\n\t\tthinkingMinimal: ColorValueSchema,\n\t\tthinkingLow: ColorValueSchema,\n\t\tthinkingMedium: ColorValueSchema,\n\t\tthinkingHigh: ColorValueSchema,\n\t\tthinkingXhigh: ColorValueSchema,\n\t\tthinkingMax: Type.Optional(ColorValueSchema),\n\t\t// Bash Mode (1 color)\n\t\tbashMode: ColorValueSchema,\n\t}),\n\texport: Type.Optional(\n\t\tType.Object({\n\t\t\tpageBg: Type.Optional(ColorValueSchema),\n\t\t\tcardBg: Type.Optional(ColorValueSchema),\n\t\t\tinfoBg: Type.Optional(ColorValueSchema),\n\t\t}),\n\t),\n});\n\nconst compiledThemeSchema = Compile(ThemeJsonSchema);\n\nexport type ThemeColorValue = Static<typeof ColorValueSchema>;\nexport type ValidatedThemeJson = Static<typeof ThemeJsonSchema>;\n\n/** Validate one theme document, throwing a message that names the offending tokens. */\nexport function validateThemeJson(label: string, json: unknown): ValidatedThemeJson {\n\tif (!compiledThemeSchema.Check(json)) {\n\t\tconst errors = Array.from(compiledThemeSchema.Errors(json));\n\t\tconst missingColors = new Set<string>();\n\t\tconst otherErrors: string[] = [];\n\n\t\tfor (const error of errors) {\n\t\t\tif (error.keyword === \"required\" && error.instancePath === \"/colors\") {\n\t\t\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\t\t\tfor (const requiredProperty of requiredProperties ?? []) {\n\t\t\t\t\tmissingColors.add(requiredProperty);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst path = error.instancePath || \"/\";\n\t\t\totherErrors.push(` - ${path}: ${error.message}`);\n\t\t}\n\n\t\tlet errorMessage = `Invalid theme \"${label}\":\\n`;\n\t\tif (missingColors.size > 0) {\n\t\t\terrorMessage += \"\\nMissing required color tokens:\\n\";\n\t\t\terrorMessage += Array.from(missingColors)\n\t\t\t\t.sort()\n\t\t\t\t.map((color) => ` - ${color}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\terrorMessage += '\\n\\nPlease add these colors to your theme\\'s \"colors\" object.';\n\t\t\terrorMessage += \"\\nSee the built-in themes (dark.json, light.json) for reference values.\";\n\t\t}\n\t\tif (otherErrors.length > 0) {\n\t\t\terrorMessage += `\\n\\nOther errors:\\n${otherErrors.join(\"\\n\")}`;\n\t\t}\n\n\t\tthrow new Error(errorMessage);\n\t}\n\n\tconst themeJson = json as ValidatedThemeJson;\n\tif (themeJson.name.includes(\"/\")) {\n\t\tthrow new Error(\n\t\t\t`Invalid theme name \"${themeJson.name}\": theme names cannot contain \"/\" because it is reserved for automatic light/dark theme settings.`,\n\t\t);\n\t}\n\treturn themeJson;\n}\n"]}
@@ -9,12 +9,14 @@
9
9
  import { Type } from "typebox";
10
10
  import { Compile } from "typebox/compile";
11
11
  const ColorValueSchema = Type.Union([
12
- Type.String(), // hex "#ff0000", var ref "primary", or empty ""
12
+ Type.String(), // hex, OKLCH, var ref "primary", or empty ""
13
13
  Type.Integer({ minimum: 0, maximum: 255 }), // 256-color index
14
14
  ]);
15
15
  const ThemeJsonSchema = Type.Object({
16
16
  $schema: Type.Optional(Type.String()),
17
17
  name: Type.String(),
18
+ // Background the theme is designed for. Detected from the theme colors when omitted.
19
+ appearance: Type.Optional(Type.Union([Type.Literal("dark"), Type.Literal("light")])),
18
20
  vars: Type.Optional(Type.Record(Type.String(), ColorValueSchema)),
19
21
  colors: Type.Object({
20
22
  // Core UI (11 colors)
@@ -1 +1 @@
1
- {"version":3,"file":"theme-json.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-json.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Theme JSON validation, kept out of `theme.ts` on purpose.\n *\n * Validating user-authored theme files needs typebox, which costs ~17 MB of module graph to import.\n * Palette lookup does not, so a presentation that only uses built-in themes should never pay for it.\n * `interactive-mode.ts` installs this validator; anything that does not simply skips validation, as\n * built-in themes already do.\n */\n\nimport { type Static, Type } from \"typebox\";\nimport { Compile } from \"typebox/compile\";\n\nconst ColorValueSchema = Type.Union([\n\tType.String(), // hex \"#ff0000\", var ref \"primary\", or empty \"\"\n\tType.Integer({ minimum: 0, maximum: 255 }), // 256-color index\n]);\n\nconst ThemeJsonSchema = Type.Object({\n\t$schema: Type.Optional(Type.String()),\n\tname: Type.String(),\n\tvars: Type.Optional(Type.Record(Type.String(), ColorValueSchema)),\n\tcolors: Type.Object({\n\t\t// Core UI (11 colors)\n\t\taccent: ColorValueSchema,\n\t\tborder: ColorValueSchema,\n\t\tborderAccent: ColorValueSchema,\n\t\tborderMuted: ColorValueSchema,\n\t\tsuccess: ColorValueSchema,\n\t\terror: ColorValueSchema,\n\t\twarning: ColorValueSchema,\n\t\tmuted: ColorValueSchema,\n\t\tdim: ColorValueSchema,\n\t\ttext: ColorValueSchema,\n\t\tthinkingText: ColorValueSchema,\n\t\t// Scrollbar (2 optional colors)\n\t\tscrollbarTrack: Type.Optional(ColorValueSchema),\n\t\tscrollbarThumb: Type.Optional(ColorValueSchema),\n\t\t// Backgrounds & Content Text (11 required, 2 optional)\n\t\tselectedBg: ColorValueSchema,\n\t\tsearchMatchBg: Type.Optional(ColorValueSchema),\n\t\tsearchMatchText: Type.Optional(ColorValueSchema),\n\t\tuserMessageBg: ColorValueSchema,\n\t\tuserMessageText: ColorValueSchema,\n\t\tcustomMessageBg: ColorValueSchema,\n\t\tcustomMessageText: ColorValueSchema,\n\t\tcustomMessageLabel: ColorValueSchema,\n\t\ttoolPendingBg: ColorValueSchema,\n\t\ttoolSuccessBg: ColorValueSchema,\n\t\ttoolErrorBg: ColorValueSchema,\n\t\ttoolTitle: ColorValueSchema,\n\t\ttoolOutput: ColorValueSchema,\n\t\t// Markdown (10 colors)\n\t\tmdHeading: ColorValueSchema,\n\t\tmdLink: ColorValueSchema,\n\t\tmdLinkUrl: ColorValueSchema,\n\t\tmdCode: ColorValueSchema,\n\t\tmdCodeBlock: ColorValueSchema,\n\t\tmdCodeBlockBorder: ColorValueSchema,\n\t\tmdQuote: ColorValueSchema,\n\t\tmdQuoteBorder: ColorValueSchema,\n\t\tmdHr: ColorValueSchema,\n\t\tmdListBullet: ColorValueSchema,\n\t\t// Tool Diffs (3 colors)\n\t\ttoolDiffAdded: ColorValueSchema,\n\t\ttoolDiffRemoved: ColorValueSchema,\n\t\ttoolDiffContext: ColorValueSchema,\n\t\t// Syntax Highlighting (9 colors)\n\t\tsyntaxComment: ColorValueSchema,\n\t\tsyntaxKeyword: ColorValueSchema,\n\t\tsyntaxFunction: ColorValueSchema,\n\t\tsyntaxVariable: ColorValueSchema,\n\t\tsyntaxString: ColorValueSchema,\n\t\tsyntaxNumber: ColorValueSchema,\n\t\tsyntaxType: ColorValueSchema,\n\t\tsyntaxOperator: ColorValueSchema,\n\t\tsyntaxPunctuation: ColorValueSchema,\n\t\t// Thinking Level Borders (6 colors)\n\t\tthinkingOff: ColorValueSchema,\n\t\tthinkingMinimal: ColorValueSchema,\n\t\tthinkingLow: ColorValueSchema,\n\t\tthinkingMedium: ColorValueSchema,\n\t\tthinkingHigh: ColorValueSchema,\n\t\tthinkingXhigh: ColorValueSchema,\n\t\tthinkingMax: Type.Optional(ColorValueSchema),\n\t\t// Bash Mode (1 color)\n\t\tbashMode: ColorValueSchema,\n\t}),\n\texport: Type.Optional(\n\t\tType.Object({\n\t\t\tpageBg: Type.Optional(ColorValueSchema),\n\t\t\tcardBg: Type.Optional(ColorValueSchema),\n\t\t\tinfoBg: Type.Optional(ColorValueSchema),\n\t\t}),\n\t),\n});\n\nconst compiledThemeSchema = Compile(ThemeJsonSchema);\n\nexport type ThemeColorValue = Static<typeof ColorValueSchema>;\nexport type ValidatedThemeJson = Static<typeof ThemeJsonSchema>;\n\n/** Validate one theme document, throwing a message that names the offending tokens. */\nexport function validateThemeJson(label: string, json: unknown): ValidatedThemeJson {\n\tif (!compiledThemeSchema.Check(json)) {\n\t\tconst errors = Array.from(compiledThemeSchema.Errors(json));\n\t\tconst missingColors = new Set<string>();\n\t\tconst otherErrors: string[] = [];\n\n\t\tfor (const error of errors) {\n\t\t\tif (error.keyword === \"required\" && error.instancePath === \"/colors\") {\n\t\t\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\t\t\tfor (const requiredProperty of requiredProperties ?? []) {\n\t\t\t\t\tmissingColors.add(requiredProperty);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst path = error.instancePath || \"/\";\n\t\t\totherErrors.push(` - ${path}: ${error.message}`);\n\t\t}\n\n\t\tlet errorMessage = `Invalid theme \"${label}\":\\n`;\n\t\tif (missingColors.size > 0) {\n\t\t\terrorMessage += \"\\nMissing required color tokens:\\n\";\n\t\t\terrorMessage += Array.from(missingColors)\n\t\t\t\t.sort()\n\t\t\t\t.map((color) => ` - ${color}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\terrorMessage += '\\n\\nPlease add these colors to your theme\\'s \"colors\" object.';\n\t\t\terrorMessage += \"\\nSee the built-in themes (dark.json, light.json) for reference values.\";\n\t\t}\n\t\tif (otherErrors.length > 0) {\n\t\t\terrorMessage += `\\n\\nOther errors:\\n${otherErrors.join(\"\\n\")}`;\n\t\t}\n\n\t\tthrow new Error(errorMessage);\n\t}\n\n\tconst themeJson = json as ValidatedThemeJson;\n\tif (themeJson.name.includes(\"/\")) {\n\t\tthrow new Error(\n\t\t\t`Invalid theme name \"${themeJson.name}\": theme names cannot contain \"/\" because it is reserved for automatic light/dark theme settings.`,\n\t\t);\n\t}\n\treturn themeJson;\n}\n"]}
1
+ {"version":3,"file":"theme-json.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/theme-json.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Theme JSON validation, kept out of `theme.ts` on purpose.\n *\n * Validating user-authored theme files needs typebox, which costs ~17 MB of module graph to import.\n * Palette lookup does not, so a presentation that only uses built-in themes should never pay for it.\n * `interactive-mode.ts` installs this validator; anything that does not simply skips validation, as\n * built-in themes already do.\n */\n\nimport { type Static, Type } from \"typebox\";\nimport { Compile } from \"typebox/compile\";\n\nconst ColorValueSchema = Type.Union([\n\tType.String(), // hex, OKLCH, var ref \"primary\", or empty \"\"\n\tType.Integer({ minimum: 0, maximum: 255 }), // 256-color index\n]);\n\nconst ThemeJsonSchema = Type.Object({\n\t$schema: Type.Optional(Type.String()),\n\tname: Type.String(),\n\t// Background the theme is designed for. Detected from the theme colors when omitted.\n\tappearance: Type.Optional(Type.Union([Type.Literal(\"dark\"), Type.Literal(\"light\")])),\n\tvars: Type.Optional(Type.Record(Type.String(), ColorValueSchema)),\n\tcolors: Type.Object({\n\t\t// Core UI (11 colors)\n\t\taccent: ColorValueSchema,\n\t\tborder: ColorValueSchema,\n\t\tborderAccent: ColorValueSchema,\n\t\tborderMuted: ColorValueSchema,\n\t\tsuccess: ColorValueSchema,\n\t\terror: ColorValueSchema,\n\t\twarning: ColorValueSchema,\n\t\tmuted: ColorValueSchema,\n\t\tdim: ColorValueSchema,\n\t\ttext: ColorValueSchema,\n\t\tthinkingText: ColorValueSchema,\n\t\t// Scrollbar (2 optional colors)\n\t\tscrollbarTrack: Type.Optional(ColorValueSchema),\n\t\tscrollbarThumb: Type.Optional(ColorValueSchema),\n\t\t// Backgrounds & Content Text (11 required, 2 optional)\n\t\tselectedBg: ColorValueSchema,\n\t\tsearchMatchBg: Type.Optional(ColorValueSchema),\n\t\tsearchMatchText: Type.Optional(ColorValueSchema),\n\t\tuserMessageBg: ColorValueSchema,\n\t\tuserMessageText: ColorValueSchema,\n\t\tcustomMessageBg: ColorValueSchema,\n\t\tcustomMessageText: ColorValueSchema,\n\t\tcustomMessageLabel: ColorValueSchema,\n\t\ttoolPendingBg: ColorValueSchema,\n\t\ttoolSuccessBg: ColorValueSchema,\n\t\ttoolErrorBg: ColorValueSchema,\n\t\ttoolTitle: ColorValueSchema,\n\t\ttoolOutput: ColorValueSchema,\n\t\t// Markdown (10 colors)\n\t\tmdHeading: ColorValueSchema,\n\t\tmdLink: ColorValueSchema,\n\t\tmdLinkUrl: ColorValueSchema,\n\t\tmdCode: ColorValueSchema,\n\t\tmdCodeBlock: ColorValueSchema,\n\t\tmdCodeBlockBorder: ColorValueSchema,\n\t\tmdQuote: ColorValueSchema,\n\t\tmdQuoteBorder: ColorValueSchema,\n\t\tmdHr: ColorValueSchema,\n\t\tmdListBullet: ColorValueSchema,\n\t\t// Tool Diffs (3 colors)\n\t\ttoolDiffAdded: ColorValueSchema,\n\t\ttoolDiffRemoved: ColorValueSchema,\n\t\ttoolDiffContext: ColorValueSchema,\n\t\t// Syntax Highlighting (9 colors)\n\t\tsyntaxComment: ColorValueSchema,\n\t\tsyntaxKeyword: ColorValueSchema,\n\t\tsyntaxFunction: ColorValueSchema,\n\t\tsyntaxVariable: ColorValueSchema,\n\t\tsyntaxString: ColorValueSchema,\n\t\tsyntaxNumber: ColorValueSchema,\n\t\tsyntaxType: ColorValueSchema,\n\t\tsyntaxOperator: ColorValueSchema,\n\t\tsyntaxPunctuation: ColorValueSchema,\n\t\t// Thinking Level Borders (6 colors)\n\t\tthinkingOff: ColorValueSchema,\n\t\tthinkingMinimal: ColorValueSchema,\n\t\tthinkingLow: ColorValueSchema,\n\t\tthinkingMedium: ColorValueSchema,\n\t\tthinkingHigh: ColorValueSchema,\n\t\tthinkingXhigh: ColorValueSchema,\n\t\tthinkingMax: Type.Optional(ColorValueSchema),\n\t\t// Bash Mode (1 color)\n\t\tbashMode: ColorValueSchema,\n\t}),\n\texport: Type.Optional(\n\t\tType.Object({\n\t\t\tpageBg: Type.Optional(ColorValueSchema),\n\t\t\tcardBg: Type.Optional(ColorValueSchema),\n\t\t\tinfoBg: Type.Optional(ColorValueSchema),\n\t\t}),\n\t),\n});\n\nconst compiledThemeSchema = Compile(ThemeJsonSchema);\n\nexport type ThemeColorValue = Static<typeof ColorValueSchema>;\nexport type ValidatedThemeJson = Static<typeof ThemeJsonSchema>;\n\n/** Validate one theme document, throwing a message that names the offending tokens. */\nexport function validateThemeJson(label: string, json: unknown): ValidatedThemeJson {\n\tif (!compiledThemeSchema.Check(json)) {\n\t\tconst errors = Array.from(compiledThemeSchema.Errors(json));\n\t\tconst missingColors = new Set<string>();\n\t\tconst otherErrors: string[] = [];\n\n\t\tfor (const error of errors) {\n\t\t\tif (error.keyword === \"required\" && error.instancePath === \"/colors\") {\n\t\t\t\tconst requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;\n\t\t\t\tfor (const requiredProperty of requiredProperties ?? []) {\n\t\t\t\t\tmissingColors.add(requiredProperty);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst path = error.instancePath || \"/\";\n\t\t\totherErrors.push(` - ${path}: ${error.message}`);\n\t\t}\n\n\t\tlet errorMessage = `Invalid theme \"${label}\":\\n`;\n\t\tif (missingColors.size > 0) {\n\t\t\terrorMessage += \"\\nMissing required color tokens:\\n\";\n\t\t\terrorMessage += Array.from(missingColors)\n\t\t\t\t.sort()\n\t\t\t\t.map((color) => ` - ${color}`)\n\t\t\t\t.join(\"\\n\");\n\t\t\terrorMessage += '\\n\\nPlease add these colors to your theme\\'s \"colors\" object.';\n\t\t\terrorMessage += \"\\nSee the built-in themes (dark.json, light.json) for reference values.\";\n\t\t}\n\t\tif (otherErrors.length > 0) {\n\t\t\terrorMessage += `\\n\\nOther errors:\\n${otherErrors.join(\"\\n\")}`;\n\t\t}\n\n\t\tthrow new Error(errorMessage);\n\t}\n\n\tconst themeJson = json as ValidatedThemeJson;\n\tif (themeJson.name.includes(\"/\")) {\n\t\tthrow new Error(\n\t\t\t`Invalid theme name \"${themeJson.name}\": theme names cannot contain \"/\" because it is reserved for automatic light/dark theme settings.`,\n\t\t);\n\t}\n\treturn themeJson;\n}\n"]}
@@ -14,6 +14,11 @@
14
14
  "pattern": "^[^/]+$",
15
15
  "description": "Theme name. Must not contain '/' because it is reserved for automatic light/dark theme settings."
16
16
  },
17
+ "appearance": {
18
+ "type": "string",
19
+ "enum": ["dark", "light"],
20
+ "description": "Background the theme is designed for. Detected from the theme colors when omitted."
21
+ },
17
22
  "vars": {
18
23
  "type": "object",
19
24
  "description": "Reusable color variables",
@@ -21,7 +26,7 @@
21
26
  "oneOf": [
22
27
  {
23
28
  "type": "string",
24
- "description": "Hex color (#RRGGBB), variable reference, or empty string for terminal default"
29
+ "description": "Hex color (#RGB or #RRGGBB), OKLCH color, variable reference, or empty string for terminal default"
25
30
  },
26
31
  {
27
32
  "type": "integer",
@@ -342,7 +347,7 @@
342
347
  "oneOf": [
343
348
  {
344
349
  "type": "string",
345
- "description": "Hex color (#RRGGBB), variable reference, or empty string for terminal default"
350
+ "description": "Hex color (#RGB or #RRGGBB), OKLCH color, variable reference, or empty string for terminal default"
346
351
  },
347
352
  {
348
353
  "type": "integer",
@@ -1,5 +1,5 @@
1
1
  import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
- import { type EditorTheme, type MarkdownTheme, type RgbColor, type SelectListTheme, type SettingsListTheme } from "@earendil-works/pi-tui";
2
+ import { type Color, type EditorTheme, type MarkdownTheme, type RgbColor, type SelectListTheme, type SettingsListTheme, type TerminalColorMode, type TextAttributes } from "@earendil-works/pi-tui";
3
3
  import type { SourceInfo } from "../../../core/source-info.ts";
4
4
  /** The schema that validates this shape lives in `theme-json.ts`; importing the type is free. */
5
5
  import type { ValidatedThemeJson as ThemeJson } from "./theme-json.ts";
@@ -13,23 +13,56 @@ export type ThemeJsonValidator = (label: string, json: unknown) => ThemeJson;
13
13
  export declare function setThemeJsonValidator(validator: ThemeJsonValidator): void;
14
14
  export type ThemeColor = "accent" | "border" | "borderAccent" | "borderMuted" | "success" | "error" | "warning" | "muted" | "dim" | "text" | "thinkingText" | "scrollbarTrack" | "scrollbarThumb" | "searchMatchText" | "userMessageText" | "customMessageText" | "customMessageLabel" | "toolTitle" | "toolOutput" | "mdHeading" | "mdLink" | "mdLinkUrl" | "mdCode" | "mdCodeBlock" | "mdCodeBlockBorder" | "mdQuote" | "mdQuoteBorder" | "mdHr" | "mdListBullet" | "toolDiffAdded" | "toolDiffRemoved" | "toolDiffContext" | "syntaxComment" | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator" | "syntaxPunctuation" | "thinkingOff" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium" | "thinkingHigh" | "thinkingXhigh" | "thinkingMax" | "bashMode";
15
15
  export type ThemeBg = "selectedBg" | "searchMatchBg" | "userMessageBg" | "customMessageBg" | "toolPendingBg" | "toolSuccessBg" | "toolErrorBg";
16
+ export type ThemeToken = ThemeColor | ThemeBg;
17
+ /**
18
+ * Tokens are only accepted in their own slot, because "" (terminal default) means the default foreground
19
+ * or background depending on the slot. Use `theme.colors[token]` to use a token's color in the other slot.
20
+ */
21
+ export interface ThemeStyle extends TextAttributes {
22
+ fg?: ThemeColor | Color;
23
+ bg?: ThemeBg | Color;
24
+ }
16
25
  type OptionalThemeColor = "scrollbarTrack" | "scrollbarThumb" | "thinkingMax" | "searchMatchText";
17
26
  type OptionalThemeBg = "searchMatchBg";
18
- type ColorMode = "truecolor" | "256color";
27
+ /** The background a theme is designed for. */
28
+ export type ThemeAppearance = TerminalTheme;
29
+ /** Record the terminal's reported default colors. Themes use them for tokens set to "" (terminal default). */
30
+ export declare function setTerminalDefaultColors(colors: {
31
+ foreground?: RgbColor;
32
+ background?: RgbColor;
33
+ }): void;
19
34
  export declare class Theme {
20
35
  readonly name?: string;
21
36
  readonly sourcePath?: string;
22
37
  sourceInfo?: SourceInfo;
23
- private fgColors;
24
- private bgColors;
25
38
  private mode;
26
- constructor(fgColors: Record<Exclude<ThemeColor, OptionalThemeColor>, string | number> & Partial<Record<OptionalThemeColor, string | number>>, bgColors: Record<Exclude<ThemeBg, OptionalThemeBg>, string | number> & Partial<Record<OptionalThemeBg, string | number>>, mode: ColorMode, options?: {
39
+ private readonly fgAnsi;
40
+ private readonly bgAnsi;
41
+ private readonly concreteColors;
42
+ private readonly defaultForegroundTokens;
43
+ private readonly defaultBackgroundTokens;
44
+ private readonly ownAppearance;
45
+ private resolvedColors;
46
+ constructor(fgColors: Record<Exclude<ThemeColor, OptionalThemeColor>, string | number> & Partial<Record<OptionalThemeColor, string | number>>, bgColors: Record<Exclude<ThemeBg, OptionalThemeBg>, string | number> & Partial<Record<OptionalThemeBg, string | number>>, mode: TerminalColorMode, options?: {
27
47
  name?: string;
28
48
  sourcePath?: string;
29
49
  sourceInfo?: SourceInfo;
50
+ appearance?: ThemeAppearance;
30
51
  });
52
+ /**
53
+ * The background the theme is designed for: declared in the theme JSON, detected from its colors,
54
+ * or, for themes without usable colors, taken from the terminal background.
55
+ */
56
+ get appearance(): ThemeAppearance;
57
+ /**
58
+ * Concrete colors for all tokens. Tokens set to "" (terminal default) use the terminal's reported
59
+ * default colors, or a guess based on `appearance` when the terminal did not report them.
60
+ */
61
+ get colors(): Readonly<Record<ThemeToken, Color>>;
62
+ style(text: string, options: ThemeStyle): string;
31
63
  fg(color: ThemeColor, text: string): string;
32
64
  bg(color: ThemeBg, text: string): string;
65
+ private tokenAnsi;
33
66
  bold(text: string): string;
34
67
  italic(text: string): string;
35
68
  underline(text: string): string;
@@ -37,7 +70,7 @@ export declare class Theme {
37
70
  strikethrough(text: string): string;
38
71
  getFgAnsi(color: ThemeColor): string;
39
72
  getBgAnsi(color: ThemeBg): string;
40
- getColorMode(): ColorMode;
73
+ getColorMode(): TerminalColorMode;
41
74
  getThinkingBorderColor(level: ThinkingLevel): (str: string) => string;
42
75
  getBashModeBorderColor(): (str: string) => string;
43
76
  }
@@ -47,7 +80,7 @@ export interface ThemeInfo {
47
80
  path: string | undefined;
48
81
  }
49
82
  export declare function getAvailableThemesWithPaths(): ThemeInfo[];
50
- export declare function loadThemeFromPath(themePath: string, mode?: ColorMode): Theme;
83
+ export declare function loadThemeFromPath(themePath: string, mode?: TerminalColorMode): Theme;
51
84
  export declare function getThemeByName(name: string): Theme | undefined;
52
85
  export type TerminalTheme = "dark" | "light";
53
86
  export declare function parseAutoThemeSetting(themeSetting: string | undefined): {