mu-agent 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -0
- package/README.md +7 -3
- package/dist/bundle/chunks/anthropic-messages-VMESUHQ7.js +6 -0
- package/dist/bundle/chunks/azure-openai-responses-K232MQFF.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
- package/dist/bundle/chunks/{chunk-FUXEF6JQ.js → chunk-4O2SCVYF.js} +1 -1
- package/dist/bundle/chunks/chunk-AYFRR3OD.js +2 -0
- package/dist/bundle/chunks/chunk-FU4XQUSL.js +11 -0
- package/dist/bundle/chunks/{chunk-S7SZN6Z3.js → chunk-GNYZK6XN.js} +1 -1
- package/dist/bundle/chunks/{chunk-XOBBIK6P.js → chunk-HEMXN7VJ.js} +75 -67
- package/dist/bundle/chunks/chunk-MVPXOVOJ.js +2 -0
- package/dist/bundle/chunks/chunk-UTB4DTYQ.js +2 -0
- package/dist/bundle/chunks/{chunk-WIUZI2CJ.js → chunk-V7IXECST.js} +2 -2
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-NR2TDCXU.js +2 -0
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/google-generative-ai-C44KRRZ4.js +2 -0
- package/dist/bundle/chunks/google-vertex-25DUXPTJ.js +2 -0
- package/dist/bundle/chunks/meta.js +2 -0
- package/dist/bundle/chunks/mistral-conversations-6IHQOR4R.js +5 -0
- package/dist/bundle/chunks/{openai-codex-responses-QDSOTBBO.js → openai-codex-responses-3UAYOSSR.js} +2 -2
- package/dist/bundle/chunks/{openai-completions-KQPOPEVM.js → openai-completions-FIEQGWAH.js} +3 -3
- package/dist/bundle/chunks/openai-responses-OZJXLHFC.js +2 -0
- package/dist/bundle/chunks/{openrouter-images-N4XVVFSD.js → openrouter-images-JVEJN44Q.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-GFKBWJHZ.js → pi-messages-TM5SOOIW.js} +2 -2
- package/dist/bundle/chunks/typesafe-system-one-VKYX4OGY.js +2 -0
- package/dist/bundle/chunks/{virtual-modules-MYFARBUK.js → virtual-modules-V7SXVVLP.js} +1 -1
- package/dist/bundle/cli-runtime.js +3 -0
- package/dist/bundle/cli.js +4 -2
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/export-html/template.css +10 -0
- package/dist/core/export-html/template.js +56 -18
- package/dist/modes/interactive/theme/theme-controller.d.ts +6 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +41 -3
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-json.d.ts +1 -0
- package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-json.js +3 -1
- package/dist/modes/interactive/theme/theme-json.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +7 -2
- package/dist/modes/interactive/theme/theme.d.ts +40 -7
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +122 -214
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/docs/cli-integration.md +106 -0
- package/docs/cli.md +268 -0
- package/docs/compaction.md +45 -26
- package/docs/configuration.md +45 -0
- package/docs/containerization.md +109 -82
- package/docs/custom-provider.md +109 -722
- package/docs/docs.json +139 -99
- package/docs/environment-variables.md +3 -5
- package/docs/extensions.md +137 -2953
- package/docs/how-pi-works.md +49 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/index.md +24 -69
- package/docs/json.md +193 -65
- package/docs/keybindings.md +57 -102
- package/docs/llama-cpp.md +3 -3
- package/docs/message-types.md +261 -0
- package/docs/models.md +64 -546
- package/docs/packages.md +67 -166
- package/docs/prompt-templates.md +31 -68
- package/docs/providers.md +102 -232
- package/docs/quickstart.md +61 -106
- package/docs/rpc-commands.md +854 -0
- package/docs/rpc-extension-ui.md +200 -0
- package/docs/rpc.md +129 -1556
- package/docs/sdk.md +76 -1160
- package/docs/security.md +70 -32
- package/docs/session-format.md +25 -216
- package/docs/sessions.md +35 -141
- package/docs/settings.md +109 -387
- package/docs/shell-aliases.md +85 -5
- package/docs/skills.md +51 -190
- package/docs/slash-commands.md +60 -0
- package/docs/terminal-setup.md +105 -78
- package/docs/termux.md +74 -83
- package/docs/themes.md +71 -279
- package/docs/tmux.md +31 -39
- package/docs/tui.md +74 -910
- package/docs/usage.md +54 -272
- package/docs/windows.md +43 -17
- package/examples/README.md +13 -2
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/debug-provider.ts +90 -0
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/plugins/pi-example-plugin/src/session.ts +3 -2
- package/examples/rpc-client.ts +35 -0
- package/examples/rpc-extension-ui.ts +25 -5
- package/examples/sdk/README.md +1 -1
- package/judge/dist/auth.js +166 -28
- package/judge/dist/chrome.js +12 -1
- package/judge/dist/kyrn-judge.js +1424 -589
- package/judge/dist/setup.js +2051 -0
- package/judge/manifest.json +13 -13
- package/judge/skills/mu-browser/SKILL.md +1 -0
- package/kyrn/bin/kyrn-doctor +107 -18
- package/kyrn/bin/mu.mjs +389 -29
- package/package.json +4 -4
- package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +0 -6
- package/dist/bundle/chunks/azure-openai-responses-HUSQ3GP2.js +0 -2
- package/dist/bundle/chunks/chunk-6R7UI4IY.js +0 -2
- package/dist/bundle/chunks/chunk-CK5Z65DM.js +0 -2
- package/dist/bundle/chunks/chunk-RCZIEVGO.js +0 -11
- package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +0 -2
- package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +0 -2
- package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +0 -5
- package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +0 -2
- 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'
|
|
1310
|
-
|
|
1311
|
-
|
|
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
|
-
|
|
1793
|
-
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
|
-
|
|
1803
|
-
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',
|
|
1817
|
-
|
|
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
|
-
|
|
1879
|
+
setThinkingExpanded(!thinkingExpanded);
|
|
1845
1880
|
} else if (key === 'o') {
|
|
1846
1881
|
e.preventDefault();
|
|
1847
|
-
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC;IACnC,IAAI,CAAC,MAAM,EAAE,EAAE,
|
|
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
|
-
|
|
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
|
-
|
|
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():
|
|
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?:
|
|
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): {
|