mu-agent 0.1.7 → 0.1.8
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 +25 -0
- package/README.md +5 -4
- package/dist/bundle/chunks/anthropic-messages-MI4QHRXW.js +6 -0
- package/dist/bundle/chunks/anthropic.js +2 -2
- package/dist/bundle/chunks/azure-openai-responses-E3QGN4VN.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
- package/dist/bundle/chunks/chunk-33KL6IIQ.js +20 -0
- package/dist/bundle/chunks/chunk-4OBXYPH4.js +4 -0
- package/dist/bundle/chunks/chunk-C47CLQZK.js +2 -0
- package/dist/bundle/chunks/chunk-F5ZABL6Z.js +20 -0
- package/dist/bundle/chunks/chunk-H3QXYIF7.js +31 -0
- package/dist/bundle/chunks/chunk-HQHD6PZI.js +2 -0
- package/dist/bundle/chunks/chunk-KJUDFE6Y.js +11 -0
- package/dist/bundle/chunks/chunk-LD2P6SFZ.js +2 -0
- package/dist/bundle/chunks/chunk-NG4PO5FP.js +1204 -0
- package/dist/bundle/chunks/chunk-NTSWBG4X.js +2 -0
- package/dist/bundle/chunks/chunk-RLZVFGW4.js +127 -0
- package/dist/bundle/chunks/{chunk-XDUWOHPL.js → chunk-S3GGRYIO.js} +2 -2
- package/dist/bundle/chunks/{chunk-4O2SCVYF.js → chunk-TAOL3JFA.js} +1 -1
- package/dist/bundle/chunks/chunk-TE3GUV6H.js +9 -0
- package/dist/bundle/chunks/chunk-VIV3CWXG.js +2 -0
- package/dist/bundle/chunks/{chunk-V7IXECST.js → chunk-WE4MXUQS.js} +3 -3
- package/dist/bundle/chunks/chunk-Y2PP32XV.js +2 -0
- package/dist/bundle/chunks/chunk-YLY6CKPK.js +132 -0
- package/dist/bundle/chunks/chunk-ZE5FMF3V.js +99 -0
- package/dist/bundle/chunks/cli-AOVD3SSZ.js +51 -0
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-NM3JRRWQ.js +2 -0
- package/dist/bundle/chunks/codemode-worker.js +374 -0
- package/dist/bundle/chunks/easter-egg-3d-G2LUBEMZ.js +2 -0
- package/dist/bundle/chunks/execute-MZVUC6BB.js +388 -0
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{google-generative-ai-C44KRRZ4.js → google-generative-ai-XMFHGQC5.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-25DUXPTJ.js → google-vertex-4XRRNP74.js} +1 -1
- package/dist/bundle/chunks/{lib-HGQMNYVT.js → lib-TEDK3E5I.js} +2 -2
- package/dist/bundle/chunks/llama-cpp-classify-XT44PI3C.js +22 -0
- package/dist/bundle/chunks/mistral-conversations-Q22M3IBG.js +5 -0
- package/dist/bundle/chunks/node-N5BSDOA5.js +16 -0
- package/dist/bundle/chunks/openai-chatgpt.js +86 -0
- package/dist/bundle/chunks/{openai-codex-responses-3UAYOSSR.js → openai-codex-responses-4MYQSO5E.js} +1 -1
- package/dist/bundle/chunks/openai-codex.js +2 -2
- package/dist/bundle/chunks/openai-completions-OSRCWAAA.js +7 -0
- package/dist/bundle/chunks/openai-responses-UHF3DHNG.js +3 -0
- package/dist/bundle/chunks/{openrouter-images-JVEJN44Q.js → openrouter-images-ZVXWCSIM.js} +1 -1
- package/dist/bundle/chunks/openrouter.js +2 -2
- package/dist/bundle/chunks/radius.js +2 -2
- package/dist/bundle/chunks/runtime-VJMMDLVH.js +2 -0
- package/dist/bundle/chunks/typesafe-system-one-PRR5K6CI.js +2 -0
- package/dist/bundle/chunks/virtual-modules-RJXJGCPI.js +2 -0
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/dist/bundle/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/export-html/template.js +16 -0
- package/dist/modes/interactive/theme/dark.json +59 -72
- package/dist/modes/interactive/theme/light.json +57 -69
- package/dist/modes/interactive/theme/system-theme.d.ts +55 -0
- package/dist/modes/interactive/theme/system-theme.d.ts.map +1 -0
- package/dist/modes/interactive/theme/system-theme.js +546 -0
- package/dist/modes/interactive/theme/system-theme.js.map +1 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +43 -9
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +121 -79
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-json.js +1 -1
- package/dist/modes/interactive/theme/theme-json.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +2 -2
- package/dist/modes/interactive/theme/theme.d.ts +34 -40
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +139 -126
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/dist/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/docs/cli.md +60 -7
- package/docs/codemode.md +207 -0
- package/docs/configuration.md +2 -0
- package/docs/docs.json +13 -1
- package/docs/environment-variables.md +2 -2
- package/docs/extensions.md +53 -0
- package/docs/index.md +1 -1
- package/docs/keybindings.md +2 -2
- package/docs/llama-cpp.md +13 -0
- package/docs/mcp.md +270 -0
- package/docs/models.md +83 -1
- package/docs/packages.md +1 -1
- package/docs/providers.md +19 -6
- package/docs/quickstart.md +16 -2
- package/docs/rpc-commands.md +9 -3
- package/docs/rpc.md +3 -1
- package/docs/sdk.md +8 -3
- package/docs/security.md +2 -0
- package/docs/session-format.md +5 -1
- package/docs/settings.md +24 -5
- package/docs/themes.md +28 -6
- package/docs/usage.md +1 -1
- package/docs/virtual-models.md +114 -0
- package/docs/windows.md +2 -0
- package/examples/README.md +1 -1
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/built-in-tool-renderer.ts +16 -40
- 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/custom-provider-gitlab-duo/test.ts +5 -5
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/jev-router.ts +113 -0
- package/examples/extensions/minimal-mode.ts +24 -96
- 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/rpc-client.ts +1 -1
- package/examples/rpc-extension-ui.ts +1 -1
- package/examples/sdk/14-codemode-mcp.ts +61 -0
- package/examples/sdk/README.md +2 -1
- package/judge/THIRD_PARTY_NOTICES.md +63 -0
- package/judge/dist/auth.js +128 -113
- package/judge/dist/import.js +10 -9
- package/judge/dist/kyrn-judge.js +5444 -5354
- package/judge/dist/setup.js +138 -12
- package/judge/manifest.json +414 -102
- package/kyrn/bin/kyrn-doctor +61 -11
- package/kyrn/bin/mu.mjs +84 -69
- package/package.json +4 -4
- package/dist/bundle/chunks/anthropic-messages-VMESUHQ7.js +0 -6
- package/dist/bundle/chunks/azure-openai-responses-K232MQFF.js +0 -2
- package/dist/bundle/chunks/chunk-AYFRR3OD.js +0 -2
- package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +0 -42
- package/dist/bundle/chunks/chunk-FU4XQUSL.js +0 -11
- package/dist/bundle/chunks/chunk-GNYZK6XN.js +0 -2
- package/dist/bundle/chunks/chunk-HEMXN7VJ.js +0 -1653
- package/dist/bundle/chunks/chunk-MVPXOVOJ.js +0 -2
- package/dist/bundle/chunks/chunk-NUHFSC37.js +0 -25
- package/dist/bundle/chunks/chunk-XNGRGP62.js +0 -2
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-NR2TDCXU.js +0 -2
- package/dist/bundle/chunks/mistral-conversations-6IHQOR4R.js +0 -5
- package/dist/bundle/chunks/node-O3RIBJRH.js +0 -16
- package/dist/bundle/chunks/openai-completions-FIEQGWAH.js +0 -7
- package/dist/bundle/chunks/openai-responses-OZJXLHFC.js +0 -2
- package/dist/bundle/chunks/typesafe-system-one-VKYX4OGY.js +0 -2
- package/dist/bundle/chunks/virtual-modules-V7SXVVLP.js +0 -2
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { backgroundAnsi, colorToHex, colorToOklch,
|
|
3
|
+
import { backgroundAnsi, colorToHex, colorToOklch, foregroundAnsi, getTerminalColorMode, indexedColor, mixColors, parseColor, rgbColor, styleTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { getCustomThemesDir, getThemesDir } from "../../../config.js";
|
|
6
6
|
import { closeWatcher, watchWithErrorHandler } from "../../../utils/fs-watch.js";
|
|
7
7
|
import { highlight, supportsLanguage } from "../../../utils/syntax-highlight.js";
|
|
8
8
|
import { stripBom } from "../../../utils/text.js";
|
|
9
|
+
import { generateSystemThemeColors, SYSTEM_THEME_NAME, terminalAppearance } from "./system-theme.js";
|
|
10
|
+
export { SYSTEM_THEME_NAME } from "./system-theme.js";
|
|
9
11
|
let themeJsonValidator;
|
|
10
12
|
/**
|
|
11
13
|
* Install full theme validation. Without it, documents are accepted as-is, which is what built-in
|
|
@@ -19,7 +21,7 @@ export function setThemeJsonValidator(validator) {
|
|
|
19
21
|
// Color Utilities
|
|
20
22
|
// ============================================================================
|
|
21
23
|
function resolveVarRefs(value, vars, visited = new Set()) {
|
|
22
|
-
if (typeof value === "number" || value === "" || value.startsWith("#") || /^
|
|
24
|
+
if (typeof value === "number" || value === "" || value.startsWith("#") || /^ok(lch|hsl)\(/i.test(value)) {
|
|
23
25
|
return value;
|
|
24
26
|
}
|
|
25
27
|
if (visited.has(value)) {
|
|
@@ -48,12 +50,28 @@ function withThemeColorFallbacks(colors) {
|
|
|
48
50
|
searchMatchText: colors.searchMatchText ?? colors.text,
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
|
-
// Replaced (never mutated) on update, so themes can cache resolved colors
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
// The terminal's reported colors. Replaced (never mutated) on update, so themes can cache resolved colors
|
|
54
|
+
// by identity.
|
|
55
|
+
let terminalColors = {};
|
|
56
|
+
// While the terminal color query is in flight, the system theme renders in grayscale.
|
|
57
|
+
let terminalColorsPending = false;
|
|
58
|
+
// The terminal's last light/dark report (mode 2031). Only used while it has not reported a background.
|
|
59
|
+
let terminalColorScheme;
|
|
60
|
+
/**
|
|
61
|
+
* Record the terminal's reported colors. Themes use the default colors for tokens set to "" (terminal
|
|
62
|
+
* default); the system theme is generated from all of them. Ends the pending state.
|
|
63
|
+
*/
|
|
64
|
+
export function setTerminalColors(colors) {
|
|
65
|
+
terminalColors = { ...colors };
|
|
66
|
+
terminalColorsPending = false;
|
|
67
|
+
}
|
|
68
|
+
/** Record the terminal's light/dark report, the fallback for terminals that do not report their background. */
|
|
69
|
+
export function setTerminalColorScheme(scheme) {
|
|
70
|
+
terminalColorScheme = scheme;
|
|
71
|
+
}
|
|
72
|
+
/** Render the system theme in grayscale until `setTerminalColors()` reports the terminal's colors. */
|
|
73
|
+
export function markTerminalColorsPending() {
|
|
74
|
+
terminalColorsPending = true;
|
|
57
75
|
}
|
|
58
76
|
/** Assumed terminal default colors when the terminal does not report them. */
|
|
59
77
|
const GUESSED_DEFAULT_COLORS = {
|
|
@@ -94,6 +112,8 @@ export class Theme {
|
|
|
94
112
|
concreteColors = {};
|
|
95
113
|
defaultForegroundTokens = [];
|
|
96
114
|
defaultBackgroundTokens = [];
|
|
115
|
+
// Foreground tokens rendered faint (SGR 2) on top of their color.
|
|
116
|
+
dimTokens;
|
|
97
117
|
ownAppearance;
|
|
98
118
|
resolvedColors;
|
|
99
119
|
constructor(fgColors, bgColors, mode, options = {}) {
|
|
@@ -101,6 +121,7 @@ export class Theme {
|
|
|
101
121
|
this.sourcePath = options.sourcePath;
|
|
102
122
|
this.sourceInfo = options.sourceInfo;
|
|
103
123
|
this.mode = mode;
|
|
124
|
+
this.dimTokens = new Set(options.dim);
|
|
104
125
|
const foregrounds = {
|
|
105
126
|
...fgColors,
|
|
106
127
|
scrollbarTrack: fgColors.scrollbarTrack ?? fgColors.muted,
|
|
@@ -132,33 +153,41 @@ export class Theme {
|
|
|
132
153
|
}
|
|
133
154
|
/**
|
|
134
155
|
* The background the theme is designed for: declared in the theme JSON, detected from its colors,
|
|
135
|
-
* or, for themes without usable colors,
|
|
156
|
+
* or, for themes without usable colors, the terminal's appearance.
|
|
136
157
|
*/
|
|
137
158
|
get appearance() {
|
|
138
|
-
|
|
139
|
-
return this.ownAppearance;
|
|
140
|
-
const background = terminalDefaultColors.background;
|
|
141
|
-
return background ? getThemeForRgbColor(colorToRgb(background)) : "dark";
|
|
159
|
+
return this.ownAppearance ?? getTerminalTheme();
|
|
142
160
|
}
|
|
143
161
|
/**
|
|
144
162
|
* Concrete colors for all tokens. Tokens set to "" (terminal default) use the terminal's reported
|
|
145
|
-
* default colors, or a guess based on `appearance` when the terminal did not report them.
|
|
163
|
+
* default colors, or a guess based on `appearance` when the terminal did not report them. Faint
|
|
164
|
+
* tokens are approximated by mixing their color toward the background.
|
|
146
165
|
*/
|
|
147
166
|
get colors() {
|
|
148
|
-
const terminal =
|
|
167
|
+
const terminal = terminalColors;
|
|
149
168
|
if (this.resolvedColors?.terminal !== terminal) {
|
|
150
169
|
const guess = GUESSED_DEFAULT_COLORS[this.appearance];
|
|
170
|
+
const toColor = (rgb, fallback) => rgb ? rgbColor(rgb.r, rgb.g, rgb.b) : fallback;
|
|
171
|
+
const foreground = toColor(terminal.foreground, guess.foreground);
|
|
172
|
+
const background = toColor(terminal.background, guess.background);
|
|
151
173
|
const colors = { ...this.concreteColors };
|
|
152
174
|
for (const token of this.defaultForegroundTokens)
|
|
153
|
-
colors[token] =
|
|
175
|
+
colors[token] = foreground;
|
|
154
176
|
for (const token of this.defaultBackgroundTokens)
|
|
155
|
-
colors[token] =
|
|
177
|
+
colors[token] = background;
|
|
178
|
+
for (const token of this.dimTokens) {
|
|
179
|
+
const color = colors[token];
|
|
180
|
+
if (color)
|
|
181
|
+
colors[token] = mixColors(color, background, 0.4);
|
|
182
|
+
}
|
|
156
183
|
this.resolvedColors = { terminal, colors: Object.freeze(colors) };
|
|
157
184
|
}
|
|
158
185
|
return this.resolvedColors.colors;
|
|
159
186
|
}
|
|
160
187
|
style(text, options) {
|
|
161
188
|
const { fg, bg } = options;
|
|
189
|
+
if (typeof fg === "string" && this.dimTokens.has(fg))
|
|
190
|
+
options = { ...options, dim: true };
|
|
162
191
|
return styleTextWithAnsi(text, fg === undefined
|
|
163
192
|
? undefined
|
|
164
193
|
: typeof fg === "string"
|
|
@@ -171,6 +200,8 @@ export class Theme {
|
|
|
171
200
|
}
|
|
172
201
|
fg(color, text) {
|
|
173
202
|
const ansi = this.tokenAnsi(this.fgAnsi, color);
|
|
203
|
+
if (this.dimTokens.has(color))
|
|
204
|
+
return `${ansi}\x1b[2m${text}\x1b[22;39m`;
|
|
174
205
|
return `${ansi}${text}\x1b[39m`;
|
|
175
206
|
}
|
|
176
207
|
bg(color, text) {
|
|
@@ -198,8 +229,10 @@ export class Theme {
|
|
|
198
229
|
strikethrough(text) {
|
|
199
230
|
return chalk.strikethrough(text);
|
|
200
231
|
}
|
|
232
|
+
/** Opening escape sequence for a foreground token. Faint tokens include SGR 2, which `\x1b[22m` closes. */
|
|
201
233
|
getFgAnsi(color) {
|
|
202
|
-
|
|
234
|
+
const ansi = this.tokenAnsi(this.fgAnsi, color);
|
|
235
|
+
return this.dimTokens.has(color) ? `${ansi}\x1b[2m` : ansi;
|
|
203
236
|
}
|
|
204
237
|
getBgAnsi(color) {
|
|
205
238
|
return this.tokenAnsi(this.bgAnsi, color);
|
|
@@ -262,7 +295,8 @@ export function getAvailableThemesWithPaths() {
|
|
|
262
295
|
seen.add(themeInfo.name);
|
|
263
296
|
result.push(themeInfo);
|
|
264
297
|
};
|
|
265
|
-
// Built-in themes
|
|
298
|
+
// Built-in themes. The system theme is generated, so it has no file.
|
|
299
|
+
addTheme({ name: SYSTEM_THEME_NAME, path: undefined });
|
|
266
300
|
for (const name of Object.keys(getBuiltinThemes())) {
|
|
267
301
|
addTheme({ name, path: path.join(themesDir, `${name}.json`) });
|
|
268
302
|
}
|
|
@@ -273,7 +307,8 @@ export function getAvailableThemesWithPaths() {
|
|
|
273
307
|
for (const [name, theme] of registeredThemes.entries()) {
|
|
274
308
|
addTheme({ name, path: theme.sourcePath });
|
|
275
309
|
}
|
|
276
|
-
|
|
310
|
+
// The system theme comes first: it is the default and adapts to every terminal.
|
|
311
|
+
return result.sort((a, b) => a.name === SYSTEM_THEME_NAME ? -1 : b.name === SYSTEM_THEME_NAME ? 1 : a.name.localeCompare(b.name));
|
|
277
312
|
}
|
|
278
313
|
function getCustomThemeInfos() {
|
|
279
314
|
const customThemesDir = getCustomThemesDir();
|
|
@@ -343,40 +378,61 @@ function loadThemeJson(name) {
|
|
|
343
378
|
const content = fs.readFileSync(themePath, "utf-8");
|
|
344
379
|
return parseThemeJsonContent(name, content);
|
|
345
380
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
381
|
+
const BACKGROUND_TOKENS = new Set([
|
|
382
|
+
"selectedBg",
|
|
383
|
+
"searchMatchBg",
|
|
384
|
+
"userMessageBg",
|
|
385
|
+
"customMessageBg",
|
|
386
|
+
"toolPendingBg",
|
|
387
|
+
"toolSuccessBg",
|
|
388
|
+
"toolErrorBg",
|
|
389
|
+
]);
|
|
390
|
+
function splitThemeColors(colors) {
|
|
349
391
|
const fgColors = {};
|
|
350
392
|
const bgColors = {};
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
"searchMatchBg",
|
|
354
|
-
"userMessageBg",
|
|
355
|
-
"customMessageBg",
|
|
356
|
-
"toolPendingBg",
|
|
357
|
-
"toolSuccessBg",
|
|
358
|
-
"toolErrorBg",
|
|
359
|
-
]);
|
|
360
|
-
for (const [key, value] of Object.entries(resolvedColors)) {
|
|
361
|
-
if (bgColorKeys.has(key)) {
|
|
393
|
+
for (const [key, value] of Object.entries(colors)) {
|
|
394
|
+
if (BACKGROUND_TOKENS.has(key)) {
|
|
362
395
|
bgColors[key] = value;
|
|
363
396
|
}
|
|
364
397
|
else {
|
|
365
398
|
fgColors[key] = value;
|
|
366
399
|
}
|
|
367
400
|
}
|
|
401
|
+
return { fgColors, bgColors };
|
|
402
|
+
}
|
|
403
|
+
function createTheme(themeJson, mode, sourcePath) {
|
|
404
|
+
const colorMode = mode ?? getTerminalColorMode();
|
|
405
|
+
const resolvedColors = resolveThemeColors(withThemeColorFallbacks(themeJson.colors), themeJson.vars);
|
|
406
|
+
const { fgColors, bgColors } = splitThemeColors(resolvedColors);
|
|
368
407
|
return new Theme(fgColors, bgColors, colorMode, {
|
|
369
408
|
name: themeJson.name,
|
|
370
409
|
sourcePath,
|
|
371
410
|
appearance: themeJson.appearance,
|
|
372
411
|
});
|
|
373
412
|
}
|
|
413
|
+
/** Generate the system theme from the terminal's reported colors (grayscale while they are pending). */
|
|
414
|
+
function createSystemTheme(mode) {
|
|
415
|
+
const generated = generateSystemThemeColors({
|
|
416
|
+
...terminalColors,
|
|
417
|
+
saturation: terminalColorsPending ? 0 : 1,
|
|
418
|
+
appearanceHint: getTerminalTheme(),
|
|
419
|
+
});
|
|
420
|
+
const { fgColors, bgColors } = splitThemeColors(generated.colors);
|
|
421
|
+
return new Theme(fgColors, bgColors, mode ?? getTerminalColorMode(), {
|
|
422
|
+
name: SYSTEM_THEME_NAME,
|
|
423
|
+
appearance: generated.appearance,
|
|
424
|
+
dim: generated.dim,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
374
427
|
export function loadThemeFromPath(themePath, mode) {
|
|
375
428
|
const content = fs.readFileSync(themePath, "utf-8");
|
|
376
429
|
const themeJson = parseThemeJsonContent(themePath, content);
|
|
377
430
|
return createTheme(themeJson, mode, themePath);
|
|
378
431
|
}
|
|
379
432
|
function loadTheme(name, mode) {
|
|
433
|
+
// The system theme name is reserved: it takes precedence over custom themes of the same name.
|
|
434
|
+
if (name === SYSTEM_THEME_NAME)
|
|
435
|
+
return createSystemTheme(mode);
|
|
380
436
|
const registeredTheme = registeredThemes.get(name);
|
|
381
437
|
if (registeredTheme) {
|
|
382
438
|
return registeredTheme;
|
|
@@ -417,95 +473,45 @@ export function resolveThemeSetting(themeSetting, terminalTheme) {
|
|
|
417
473
|
return themeSetting;
|
|
418
474
|
return undefined;
|
|
419
475
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
return
|
|
434
|
-
|
|
435
|
-
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
436
|
-
}
|
|
437
|
-
function getAnsiColorLuminance(index) {
|
|
438
|
-
return getRgbColorLuminance(colorToRgb(indexedColor(index)));
|
|
439
|
-
}
|
|
440
|
-
export function getThemeForRgbColor(rgb) {
|
|
441
|
-
return getRgbColorLuminance(rgb) >= 0.5 ? "light" : "dark";
|
|
442
|
-
}
|
|
443
|
-
export function detectTerminalBackgroundFromEnv(options = {}) {
|
|
444
|
-
const env = options.env ?? process.env;
|
|
445
|
-
const colorfgbg = env.COLORFGBG || "";
|
|
446
|
-
const bg = getColorFgBgBackgroundIndex(colorfgbg);
|
|
447
|
-
if (bg !== undefined) {
|
|
448
|
-
return {
|
|
449
|
-
theme: getAnsiColorLuminance(bg) >= 0.5 ? "light" : "dark",
|
|
450
|
-
source: "COLORFGBG",
|
|
451
|
-
detail: `background color index ${bg}`,
|
|
452
|
-
confidence: "high",
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
return {
|
|
456
|
-
theme: "dark",
|
|
457
|
-
source: "fallback",
|
|
458
|
-
detail: "no terminal background hint found",
|
|
459
|
-
confidence: "low",
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
export async function detectTerminalBackgroundTheme({ ui, timeoutMs, env, }) {
|
|
463
|
-
try {
|
|
464
|
-
const rgb = await ui.queryTerminalBackgroundColor({ timeoutMs });
|
|
465
|
-
if (rgb) {
|
|
466
|
-
return {
|
|
467
|
-
theme: getThemeForRgbColor(rgb),
|
|
468
|
-
source: "terminal background",
|
|
469
|
-
detail: `OSC 11 background rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
|
|
470
|
-
confidence: "high",
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
catch {
|
|
475
|
-
// Fall back to environment-based detection when the terminal query fails.
|
|
476
|
-
}
|
|
477
|
-
return detectTerminalBackgroundFromEnv({ env });
|
|
476
|
+
/**
|
|
477
|
+
* Dark or light from the `COLORFGBG` environment variable some terminals set, or undefined without a
|
|
478
|
+
* usable background index. The value is `fg;bg` or `fg;xpm;bg` (rxvt), where a field is an ANSI color
|
|
479
|
+
* index or `default` when the color is not in the palette. The index refers to the terminal's own palette,
|
|
480
|
+
* whose colors are unknown here, so it is classified by index like Vim does: 0-6 and 8 (bright black, e.g.
|
|
481
|
+
* Solarized Dark's background) are dark, 7 and 9-15 are light.
|
|
482
|
+
*/
|
|
483
|
+
export function detectColorFgBgTheme(env = process.env) {
|
|
484
|
+
const bg = env.COLORFGBG?.split(";").at(-1)?.trim();
|
|
485
|
+
if (!bg || !/^\d{1,2}$/.test(bg))
|
|
486
|
+
return undefined;
|
|
487
|
+
const index = Number(bg);
|
|
488
|
+
if (index > 15)
|
|
489
|
+
return undefined;
|
|
490
|
+
return index <= 6 || index === 8 ? "dark" : "light";
|
|
478
491
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const colorScheme = await colorSchemePromise;
|
|
490
|
-
if (colorScheme)
|
|
491
|
-
return colorScheme;
|
|
492
|
-
}
|
|
493
|
-
catch {
|
|
494
|
-
// Fall back to the concurrently queried OSC 11 / COLORFGBG detection.
|
|
495
|
-
}
|
|
496
|
-
return (await backgroundThemePromise).theme;
|
|
492
|
+
/**
|
|
493
|
+
* Whether the terminal is dark or light. The background it renders decides, classified the same way the
|
|
494
|
+
* system theme does. Without a reported background: the terminal's light/dark report, then COLORFGBG,
|
|
495
|
+
* then dark.
|
|
496
|
+
*/
|
|
497
|
+
export function detectTerminalTheme(colors = {}, reportedScheme, env = process.env) {
|
|
498
|
+
const { background, foreground } = colors;
|
|
499
|
+
if (background)
|
|
500
|
+
return terminalAppearance(background, foreground);
|
|
501
|
+
return reportedScheme ?? detectColorFgBgTheme(env) ?? "dark";
|
|
497
502
|
}
|
|
498
|
-
|
|
499
|
-
|
|
503
|
+
/** Whether the terminal is dark or light, from everything it reported so far. See `detectTerminalTheme()`. */
|
|
504
|
+
export function getTerminalTheme() {
|
|
505
|
+
return detectTerminalTheme(terminalColors, terminalColorScheme);
|
|
500
506
|
}
|
|
501
507
|
// ============================================================================
|
|
502
508
|
// Global Theme Instance
|
|
503
509
|
// ============================================================================
|
|
504
|
-
// Use globalThis to share theme across module loaders (
|
|
510
|
+
// Use globalThis to share theme across module loaders (node + jiti in dev mode)
|
|
505
511
|
const THEME_KEY = Symbol.for("@earendil-works/pi-coding-agent:theme");
|
|
506
512
|
const THEME_KEY_OLD = Symbol.for("@mariozechner/pi-coding-agent:theme");
|
|
507
513
|
// Export theme as a getter that reads from globalThis
|
|
508
|
-
// This ensures all module instances (
|
|
514
|
+
// This ensures all module instances (node, jiti) see the same theme
|
|
509
515
|
export const theme = new Proxy({}, {
|
|
510
516
|
get(_target, prop) {
|
|
511
517
|
const t = globalThis[THEME_KEY];
|
|
@@ -533,7 +539,7 @@ export function setRegisteredThemes(themes) {
|
|
|
533
539
|
}
|
|
534
540
|
}
|
|
535
541
|
export function initTheme(themeName, enableWatcher = false) {
|
|
536
|
-
const name = themeName ??
|
|
542
|
+
const name = themeName ?? SYSTEM_THEME_NAME;
|
|
537
543
|
currentThemeName = name;
|
|
538
544
|
try {
|
|
539
545
|
setGlobalTheme(loadTheme(name));
|
|
@@ -542,9 +548,9 @@ export function initTheme(themeName, enableWatcher = false) {
|
|
|
542
548
|
}
|
|
543
549
|
}
|
|
544
550
|
catch (_error) {
|
|
545
|
-
// Theme is invalid - fall back to
|
|
546
|
-
currentThemeName =
|
|
547
|
-
setGlobalTheme(loadTheme(
|
|
551
|
+
// Theme is invalid - fall back to the system theme silently
|
|
552
|
+
currentThemeName = SYSTEM_THEME_NAME;
|
|
553
|
+
setGlobalTheme(loadTheme(SYSTEM_THEME_NAME));
|
|
548
554
|
// Don't start watcher for fallback theme
|
|
549
555
|
}
|
|
550
556
|
}
|
|
@@ -561,9 +567,9 @@ export function setTheme(name, enableWatcher = false) {
|
|
|
561
567
|
return { success: true };
|
|
562
568
|
}
|
|
563
569
|
catch (error) {
|
|
564
|
-
// Theme is invalid - fall back to
|
|
565
|
-
currentThemeName =
|
|
566
|
-
setGlobalTheme(loadTheme(
|
|
570
|
+
// Theme is invalid - fall back to the system theme
|
|
571
|
+
currentThemeName = SYSTEM_THEME_NAME;
|
|
572
|
+
setGlobalTheme(loadTheme(SYSTEM_THEME_NAME));
|
|
567
573
|
// Don't start watcher for fallback theme
|
|
568
574
|
return {
|
|
569
575
|
success: false,
|
|
@@ -585,7 +591,10 @@ export function onThemeChange(callback) {
|
|
|
585
591
|
function startThemeWatcher() {
|
|
586
592
|
stopThemeWatcher();
|
|
587
593
|
// Only watch if it's a custom theme (not built-in)
|
|
588
|
-
if (!currentThemeName ||
|
|
594
|
+
if (!currentThemeName ||
|
|
595
|
+
currentThemeName === "dark" ||
|
|
596
|
+
currentThemeName === "light" ||
|
|
597
|
+
currentThemeName === SYSTEM_THEME_NAME) {
|
|
589
598
|
return;
|
|
590
599
|
}
|
|
591
600
|
const customThemesDir = getCustomThemesDir();
|
|
@@ -659,28 +668,30 @@ export function stopThemeWatcher() {
|
|
|
659
668
|
* Used by HTML export to generate CSS custom properties.
|
|
660
669
|
*/
|
|
661
670
|
export function getResolvedThemeColors(themeName) {
|
|
662
|
-
const colors = loadTheme(themeName ?? currentThemeName ??
|
|
671
|
+
const colors = loadTheme(themeName ?? currentThemeName ?? SYSTEM_THEME_NAME).colors;
|
|
663
672
|
return Object.fromEntries(Object.entries(colors).map(([token, color]) => [token, colorToHex(color)]));
|
|
664
673
|
}
|
|
665
674
|
/**
|
|
666
675
|
* Check if a theme is a "light" theme (for CSS that needs light/dark variants).
|
|
667
676
|
*/
|
|
668
677
|
export function isLightTheme(themeName) {
|
|
669
|
-
return loadTheme(themeName ?? currentThemeName ??
|
|
678
|
+
return loadTheme(themeName ?? currentThemeName ?? SYSTEM_THEME_NAME).appearance === "light";
|
|
670
679
|
}
|
|
671
680
|
/**
|
|
672
681
|
* Get explicit export colors from theme JSON, if specified.
|
|
673
682
|
* Returns undefined for each color that isn't explicitly set.
|
|
674
683
|
*/
|
|
675
684
|
export function getThemeExportColors(themeName) {
|
|
676
|
-
const name = themeName ?? currentThemeName ??
|
|
685
|
+
const name = themeName ?? currentThemeName ?? SYSTEM_THEME_NAME;
|
|
686
|
+
if (name === SYSTEM_THEME_NAME)
|
|
687
|
+
return {};
|
|
677
688
|
try {
|
|
678
689
|
const themeJson = loadThemeJson(name);
|
|
679
690
|
const exportSection = themeJson.export;
|
|
680
691
|
if (!exportSection)
|
|
681
692
|
return {};
|
|
682
693
|
const vars = themeJson.vars ?? {};
|
|
683
|
-
// Export colors end up in CSS, which understands hex
|
|
694
|
+
// Export colors end up in CSS, which understands hex and oklch() values directly but not okhsl().
|
|
684
695
|
const resolve = (value) => {
|
|
685
696
|
if (value === undefined)
|
|
686
697
|
return undefined;
|
|
@@ -689,6 +700,8 @@ export function getThemeExportColors(themeName) {
|
|
|
689
700
|
return colorToHex(indexedColor(resolved));
|
|
690
701
|
if (resolved === "")
|
|
691
702
|
return undefined;
|
|
703
|
+
if (/^okhsl\(/i.test(resolved))
|
|
704
|
+
return colorToHex(parseColor(resolved));
|
|
692
705
|
return resolved;
|
|
693
706
|
};
|
|
694
707
|
return {
|