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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { backgroundAnsi, colorToHex, colorToOklch, colorToRgb, foregroundAnsi, getTerminalColorMode, indexedColor, 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";
|
|
@@ -18,122 +18,8 @@ export function setThemeJsonValidator(validator) {
|
|
|
18
18
|
// ============================================================================
|
|
19
19
|
// Color Utilities
|
|
20
20
|
// ============================================================================
|
|
21
|
-
function hexToRgb(hex) {
|
|
22
|
-
const cleaned = hex.replace("#", "");
|
|
23
|
-
if (cleaned.length !== 6) {
|
|
24
|
-
throw new Error(`Invalid hex color: ${hex}`);
|
|
25
|
-
}
|
|
26
|
-
const r = parseInt(cleaned.substring(0, 2), 16);
|
|
27
|
-
const g = parseInt(cleaned.substring(2, 4), 16);
|
|
28
|
-
const b = parseInt(cleaned.substring(4, 6), 16);
|
|
29
|
-
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
30
|
-
throw new Error(`Invalid hex color: ${hex}`);
|
|
31
|
-
}
|
|
32
|
-
return { r, g, b };
|
|
33
|
-
}
|
|
34
|
-
// The 6x6x6 color cube channel values (indices 0-5)
|
|
35
|
-
const CUBE_VALUES = [0, 95, 135, 175, 215, 255];
|
|
36
|
-
// Grayscale ramp values (indices 232-255, 24 grays from 8 to 238)
|
|
37
|
-
const GRAY_VALUES = Array.from({ length: 24 }, (_, i) => 8 + i * 10);
|
|
38
|
-
function findClosestCubeIndex(value) {
|
|
39
|
-
let minDist = Infinity;
|
|
40
|
-
let minIdx = 0;
|
|
41
|
-
for (let i = 0; i < CUBE_VALUES.length; i++) {
|
|
42
|
-
const dist = Math.abs(value - CUBE_VALUES[i]);
|
|
43
|
-
if (dist < minDist) {
|
|
44
|
-
minDist = dist;
|
|
45
|
-
minIdx = i;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return minIdx;
|
|
49
|
-
}
|
|
50
|
-
function findClosestGrayIndex(gray) {
|
|
51
|
-
let minDist = Infinity;
|
|
52
|
-
let minIdx = 0;
|
|
53
|
-
for (let i = 0; i < GRAY_VALUES.length; i++) {
|
|
54
|
-
const dist = Math.abs(gray - GRAY_VALUES[i]);
|
|
55
|
-
if (dist < minDist) {
|
|
56
|
-
minDist = dist;
|
|
57
|
-
minIdx = i;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return minIdx;
|
|
61
|
-
}
|
|
62
|
-
function colorDistance(r1, g1, b1, r2, g2, b2) {
|
|
63
|
-
// Weighted Euclidean distance (human eye is more sensitive to green)
|
|
64
|
-
const dr = r1 - r2;
|
|
65
|
-
const dg = g1 - g2;
|
|
66
|
-
const db = b1 - b2;
|
|
67
|
-
return dr * dr * 0.299 + dg * dg * 0.587 + db * db * 0.114;
|
|
68
|
-
}
|
|
69
|
-
function rgbTo256(r, g, b) {
|
|
70
|
-
// Find closest color in the 6x6x6 cube
|
|
71
|
-
const rIdx = findClosestCubeIndex(r);
|
|
72
|
-
const gIdx = findClosestCubeIndex(g);
|
|
73
|
-
const bIdx = findClosestCubeIndex(b);
|
|
74
|
-
const cubeR = CUBE_VALUES[rIdx];
|
|
75
|
-
const cubeG = CUBE_VALUES[gIdx];
|
|
76
|
-
const cubeB = CUBE_VALUES[bIdx];
|
|
77
|
-
const cubeIndex = 16 + 36 * rIdx + 6 * gIdx + bIdx;
|
|
78
|
-
const cubeDist = colorDistance(r, g, b, cubeR, cubeG, cubeB);
|
|
79
|
-
// Find closest grayscale
|
|
80
|
-
const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
|
|
81
|
-
const grayIdx = findClosestGrayIndex(gray);
|
|
82
|
-
const grayValue = GRAY_VALUES[grayIdx];
|
|
83
|
-
const grayIndex = 232 + grayIdx;
|
|
84
|
-
const grayDist = colorDistance(r, g, b, grayValue, grayValue, grayValue);
|
|
85
|
-
// Check if color has noticeable saturation (hue matters)
|
|
86
|
-
// If max-min spread is significant, prefer cube to preserve tint
|
|
87
|
-
const maxC = Math.max(r, g, b);
|
|
88
|
-
const minC = Math.min(r, g, b);
|
|
89
|
-
const spread = maxC - minC;
|
|
90
|
-
// Only consider grayscale if color is nearly neutral (spread < 10)
|
|
91
|
-
// AND grayscale is actually closer
|
|
92
|
-
if (spread < 10 && grayDist < cubeDist) {
|
|
93
|
-
return grayIndex;
|
|
94
|
-
}
|
|
95
|
-
return cubeIndex;
|
|
96
|
-
}
|
|
97
|
-
function hexTo256(hex) {
|
|
98
|
-
const { r, g, b } = hexToRgb(hex);
|
|
99
|
-
return rgbTo256(r, g, b);
|
|
100
|
-
}
|
|
101
|
-
function fgAnsi(color, mode) {
|
|
102
|
-
if (color === "")
|
|
103
|
-
return "\x1b[39m";
|
|
104
|
-
if (typeof color === "number")
|
|
105
|
-
return `\x1b[38;5;${color}m`;
|
|
106
|
-
if (color.startsWith("#")) {
|
|
107
|
-
if (mode === "truecolor") {
|
|
108
|
-
const { r, g, b } = hexToRgb(color);
|
|
109
|
-
return `\x1b[38;2;${r};${g};${b}m`;
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
const index = hexTo256(color);
|
|
113
|
-
return `\x1b[38;5;${index}m`;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
throw new Error(`Invalid color value: ${color}`);
|
|
117
|
-
}
|
|
118
|
-
function bgAnsi(color, mode) {
|
|
119
|
-
if (color === "")
|
|
120
|
-
return "\x1b[49m";
|
|
121
|
-
if (typeof color === "number")
|
|
122
|
-
return `\x1b[48;5;${color}m`;
|
|
123
|
-
if (color.startsWith("#")) {
|
|
124
|
-
if (mode === "truecolor") {
|
|
125
|
-
const { r, g, b } = hexToRgb(color);
|
|
126
|
-
return `\x1b[48;2;${r};${g};${b}m`;
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
const index = hexTo256(color);
|
|
130
|
-
return `\x1b[48;5;${index}m`;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
throw new Error(`Invalid color value: ${color}`);
|
|
134
|
-
}
|
|
135
21
|
function resolveVarRefs(value, vars, visited = new Set()) {
|
|
136
|
-
if (typeof value === "number" || value === "" || value.startsWith("#")) {
|
|
22
|
+
if (typeof value === "number" || value === "" || value.startsWith("#") || /^oklch\(/i.test(value)) {
|
|
137
23
|
return value;
|
|
138
24
|
}
|
|
139
25
|
if (visited.has(value)) {
|
|
@@ -162,6 +48,37 @@ function withThemeColorFallbacks(colors) {
|
|
|
162
48
|
searchMatchText: colors.searchMatchText ?? colors.text,
|
|
163
49
|
};
|
|
164
50
|
}
|
|
51
|
+
// Replaced (never mutated) on update, so themes can cache resolved colors by identity.
|
|
52
|
+
let terminalDefaultColors = {};
|
|
53
|
+
/** Record the terminal's reported default colors. Themes use them for tokens set to "" (terminal default). */
|
|
54
|
+
export function setTerminalDefaultColors(colors) {
|
|
55
|
+
const toColor = (rgb) => rgb && rgbColor(rgb.r, rgb.g, rgb.b);
|
|
56
|
+
terminalDefaultColors = { foreground: toColor(colors.foreground), background: toColor(colors.background) };
|
|
57
|
+
}
|
|
58
|
+
/** Assumed terminal default colors when the terminal does not report them. */
|
|
59
|
+
const GUESSED_DEFAULT_COLORS = {
|
|
60
|
+
dark: { foreground: parseColor("#e5e5e7"), background: parseColor("#000000") },
|
|
61
|
+
light: { foreground: parseColor("#000000"), background: parseColor("#ffffff") },
|
|
62
|
+
};
|
|
63
|
+
function averageLightness(colors) {
|
|
64
|
+
// Palette colors 0-15 follow the user's terminal palette, so they say nothing about the theme.
|
|
65
|
+
const fixed = colors.filter((color) => color.kind !== "indexed" || color.index >= 16);
|
|
66
|
+
if (fixed.length === 0)
|
|
67
|
+
return undefined;
|
|
68
|
+
return fixed.reduce((sum, color) => sum + colorToOklch(color).l, 0) / fixed.length;
|
|
69
|
+
}
|
|
70
|
+
/** Detect the background a theme is designed for from the lightness of its own colors. */
|
|
71
|
+
function detectAppearance(foregrounds, backgrounds) {
|
|
72
|
+
const fg = averageLightness(foregrounds);
|
|
73
|
+
const bg = averageLightness(backgrounds);
|
|
74
|
+
if (fg !== undefined && bg !== undefined)
|
|
75
|
+
return bg < fg ? "dark" : "light";
|
|
76
|
+
if (bg !== undefined)
|
|
77
|
+
return bg < 0.5 ? "dark" : "light";
|
|
78
|
+
if (fg !== undefined)
|
|
79
|
+
return fg > 0.5 ? "dark" : "light";
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
165
82
|
// ============================================================================
|
|
166
83
|
// Theme Class
|
|
167
84
|
// ============================================================================
|
|
@@ -169,45 +86,102 @@ export class Theme {
|
|
|
169
86
|
name;
|
|
170
87
|
sourcePath;
|
|
171
88
|
sourceInfo;
|
|
172
|
-
fgColors;
|
|
173
|
-
bgColors;
|
|
174
89
|
mode;
|
|
90
|
+
// Precomputed escape sequences keep fg()/bg() on the render hot path to a lookup and concat.
|
|
91
|
+
fgAnsi = new Map();
|
|
92
|
+
bgAnsi = new Map();
|
|
93
|
+
// Tokens set to "" have no color of their own; `colors` fills them from the terminal defaults.
|
|
94
|
+
concreteColors = {};
|
|
95
|
+
defaultForegroundTokens = [];
|
|
96
|
+
defaultBackgroundTokens = [];
|
|
97
|
+
ownAppearance;
|
|
98
|
+
resolvedColors;
|
|
175
99
|
constructor(fgColors, bgColors, mode, options = {}) {
|
|
176
100
|
this.name = options.name;
|
|
177
101
|
this.sourcePath = options.sourcePath;
|
|
178
102
|
this.sourceInfo = options.sourceInfo;
|
|
179
103
|
this.mode = mode;
|
|
180
|
-
|
|
181
|
-
const colors = {
|
|
104
|
+
const foregrounds = {
|
|
182
105
|
...fgColors,
|
|
183
106
|
scrollbarTrack: fgColors.scrollbarTrack ?? fgColors.muted,
|
|
184
107
|
scrollbarThumb: fgColors.scrollbarThumb ?? fgColors.text,
|
|
185
108
|
thinkingMax: fgColors.thinkingMax ?? fgColors.thinkingXhigh,
|
|
186
109
|
searchMatchText: fgColors.searchMatchText ?? fgColors.text,
|
|
187
110
|
};
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
111
|
+
const backgrounds = { ...bgColors, searchMatchBg: bgColors.searchMatchBg ?? bgColors.selectedBg };
|
|
112
|
+
const concreteForegrounds = [];
|
|
113
|
+
const concreteBackgrounds = [];
|
|
114
|
+
// Returns the escape sequence for the token's own slot.
|
|
115
|
+
const addToken = (token, value, isBackground) => {
|
|
116
|
+
if (value === "") {
|
|
117
|
+
(isBackground ? this.defaultBackgroundTokens : this.defaultForegroundTokens).push(token);
|
|
118
|
+
return isBackground ? "\x1b[49m" : "\x1b[39m";
|
|
119
|
+
}
|
|
120
|
+
const color = parseColor(value);
|
|
121
|
+
this.concreteColors[token] = color;
|
|
122
|
+
(isBackground ? concreteBackgrounds : concreteForegrounds).push(color);
|
|
123
|
+
return isBackground ? backgroundAnsi(color, mode) : foregroundAnsi(color, mode);
|
|
195
124
|
};
|
|
196
|
-
for (const [
|
|
197
|
-
this.
|
|
125
|
+
for (const [token, value] of Object.entries(foregrounds)) {
|
|
126
|
+
this.fgAnsi.set(token, addToken(token, value, false));
|
|
127
|
+
}
|
|
128
|
+
for (const [token, value] of Object.entries(backgrounds)) {
|
|
129
|
+
this.bgAnsi.set(token, addToken(token, value, true));
|
|
130
|
+
}
|
|
131
|
+
this.ownAppearance = options.appearance ?? detectAppearance(concreteForegrounds, concreteBackgrounds);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The background the theme is designed for: declared in the theme JSON, detected from its colors,
|
|
135
|
+
* or, for themes without usable colors, taken from the terminal background.
|
|
136
|
+
*/
|
|
137
|
+
get appearance() {
|
|
138
|
+
if (this.ownAppearance)
|
|
139
|
+
return this.ownAppearance;
|
|
140
|
+
const background = terminalDefaultColors.background;
|
|
141
|
+
return background ? getThemeForRgbColor(colorToRgb(background)) : "dark";
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 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.
|
|
146
|
+
*/
|
|
147
|
+
get colors() {
|
|
148
|
+
const terminal = terminalDefaultColors;
|
|
149
|
+
if (this.resolvedColors?.terminal !== terminal) {
|
|
150
|
+
const guess = GUESSED_DEFAULT_COLORS[this.appearance];
|
|
151
|
+
const colors = { ...this.concreteColors };
|
|
152
|
+
for (const token of this.defaultForegroundTokens)
|
|
153
|
+
colors[token] = terminal.foreground ?? guess.foreground;
|
|
154
|
+
for (const token of this.defaultBackgroundTokens)
|
|
155
|
+
colors[token] = terminal.background ?? guess.background;
|
|
156
|
+
this.resolvedColors = { terminal, colors: Object.freeze(colors) };
|
|
198
157
|
}
|
|
158
|
+
return this.resolvedColors.colors;
|
|
159
|
+
}
|
|
160
|
+
style(text, options) {
|
|
161
|
+
const { fg, bg } = options;
|
|
162
|
+
return styleTextWithAnsi(text, fg === undefined
|
|
163
|
+
? undefined
|
|
164
|
+
: typeof fg === "string"
|
|
165
|
+
? this.tokenAnsi(this.fgAnsi, fg)
|
|
166
|
+
: foregroundAnsi(fg, this.mode), bg === undefined
|
|
167
|
+
? undefined
|
|
168
|
+
: typeof bg === "string"
|
|
169
|
+
? this.tokenAnsi(this.bgAnsi, bg)
|
|
170
|
+
: backgroundAnsi(bg, this.mode), options);
|
|
199
171
|
}
|
|
200
172
|
fg(color, text) {
|
|
201
|
-
const ansi = this.
|
|
202
|
-
|
|
203
|
-
throw new Error(`Unknown theme color: ${color}`);
|
|
204
|
-
return `${ansi}${text}\x1b[39m`; // Reset only foreground color
|
|
173
|
+
const ansi = this.tokenAnsi(this.fgAnsi, color);
|
|
174
|
+
return `${ansi}${text}\x1b[39m`;
|
|
205
175
|
}
|
|
206
176
|
bg(color, text) {
|
|
207
|
-
const ansi = this.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
177
|
+
const ansi = this.tokenAnsi(this.bgAnsi, color);
|
|
178
|
+
return `${ansi}${text}\x1b[49m`;
|
|
179
|
+
}
|
|
180
|
+
tokenAnsi(ansi, token) {
|
|
181
|
+
const value = ansi.get(token);
|
|
182
|
+
if (value === undefined)
|
|
183
|
+
throw new Error(`Unknown theme color: ${token}`);
|
|
184
|
+
return value;
|
|
211
185
|
}
|
|
212
186
|
bold(text) {
|
|
213
187
|
return chalk.bold(text);
|
|
@@ -225,16 +199,10 @@ export class Theme {
|
|
|
225
199
|
return chalk.strikethrough(text);
|
|
226
200
|
}
|
|
227
201
|
getFgAnsi(color) {
|
|
228
|
-
|
|
229
|
-
if (!ansi)
|
|
230
|
-
throw new Error(`Unknown theme color: ${color}`);
|
|
231
|
-
return ansi;
|
|
202
|
+
return this.tokenAnsi(this.fgAnsi, color);
|
|
232
203
|
}
|
|
233
204
|
getBgAnsi(color) {
|
|
234
|
-
|
|
235
|
-
if (!ansi)
|
|
236
|
-
throw new Error(`Unknown theme background color: ${color}`);
|
|
237
|
-
return ansi;
|
|
205
|
+
return this.tokenAnsi(this.bgAnsi, color);
|
|
238
206
|
}
|
|
239
207
|
getColorMode() {
|
|
240
208
|
return this.mode;
|
|
@@ -376,7 +344,7 @@ function loadThemeJson(name) {
|
|
|
376
344
|
return parseThemeJsonContent(name, content);
|
|
377
345
|
}
|
|
378
346
|
function createTheme(themeJson, mode, sourcePath) {
|
|
379
|
-
const colorMode = mode ?? (
|
|
347
|
+
const colorMode = mode ?? getTerminalColorMode();
|
|
380
348
|
const resolvedColors = resolveThemeColors(withThemeColorFallbacks(themeJson.colors), themeJson.vars);
|
|
381
349
|
const fgColors = {};
|
|
382
350
|
const bgColors = {};
|
|
@@ -400,6 +368,7 @@ function createTheme(themeJson, mode, sourcePath) {
|
|
|
400
368
|
return new Theme(fgColors, bgColors, colorMode, {
|
|
401
369
|
name: themeJson.name,
|
|
402
370
|
sourcePath,
|
|
371
|
+
appearance: themeJson.appearance,
|
|
403
372
|
});
|
|
404
373
|
}
|
|
405
374
|
export function loadThemeFromPath(themePath, mode) {
|
|
@@ -466,7 +435,7 @@ function getRgbColorLuminance({ r, g, b }) {
|
|
|
466
435
|
return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
|
|
467
436
|
}
|
|
468
437
|
function getAnsiColorLuminance(index) {
|
|
469
|
-
return getRgbColorLuminance(
|
|
438
|
+
return getRgbColorLuminance(colorToRgb(indexedColor(index)));
|
|
470
439
|
}
|
|
471
440
|
export function getThemeForRgbColor(rgb) {
|
|
472
441
|
return getRgbColorLuminance(rgb) >= 0.5 ? "light" : "dark";
|
|
@@ -685,81 +654,19 @@ export function stopThemeWatcher() {
|
|
|
685
654
|
// ============================================================================
|
|
686
655
|
// HTML Export Helpers
|
|
687
656
|
// ============================================================================
|
|
688
|
-
/**
|
|
689
|
-
* Convert a 256-color index to hex string.
|
|
690
|
-
* Indices 0-15: basic colors (approximate)
|
|
691
|
-
* Indices 16-231: 6x6x6 color cube
|
|
692
|
-
* Indices 232-255: grayscale ramp
|
|
693
|
-
*/
|
|
694
|
-
function ansi256ToHex(index) {
|
|
695
|
-
// Basic colors (0-15) - approximate common terminal values
|
|
696
|
-
const basicColors = [
|
|
697
|
-
"#000000",
|
|
698
|
-
"#800000",
|
|
699
|
-
"#008000",
|
|
700
|
-
"#808000",
|
|
701
|
-
"#000080",
|
|
702
|
-
"#800080",
|
|
703
|
-
"#008080",
|
|
704
|
-
"#c0c0c0",
|
|
705
|
-
"#808080",
|
|
706
|
-
"#ff0000",
|
|
707
|
-
"#00ff00",
|
|
708
|
-
"#ffff00",
|
|
709
|
-
"#0000ff",
|
|
710
|
-
"#ff00ff",
|
|
711
|
-
"#00ffff",
|
|
712
|
-
"#ffffff",
|
|
713
|
-
];
|
|
714
|
-
if (index < 16) {
|
|
715
|
-
return basicColors[index];
|
|
716
|
-
}
|
|
717
|
-
// Color cube (16-231): 6x6x6 = 216 colors
|
|
718
|
-
if (index < 232) {
|
|
719
|
-
const cubeIndex = index - 16;
|
|
720
|
-
const r = Math.floor(cubeIndex / 36);
|
|
721
|
-
const g = Math.floor((cubeIndex % 36) / 6);
|
|
722
|
-
const b = cubeIndex % 6;
|
|
723
|
-
const toHex = (n) => (n === 0 ? 0 : 55 + n * 40).toString(16).padStart(2, "0");
|
|
724
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
725
|
-
}
|
|
726
|
-
// Grayscale (232-255): 24 shades
|
|
727
|
-
const gray = 8 + (index - 232) * 10;
|
|
728
|
-
const grayHex = gray.toString(16).padStart(2, "0");
|
|
729
|
-
return `#${grayHex}${grayHex}${grayHex}`;
|
|
730
|
-
}
|
|
731
657
|
/**
|
|
732
658
|
* Get resolved theme colors as CSS-compatible hex strings.
|
|
733
659
|
* Used by HTML export to generate CSS custom properties.
|
|
734
660
|
*/
|
|
735
661
|
export function getResolvedThemeColors(themeName) {
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
const themeJson = loadThemeJson(name);
|
|
739
|
-
const resolved = resolveThemeColors(withThemeColorFallbacks(themeJson.colors), themeJson.vars);
|
|
740
|
-
// Default text color for empty values (terminal uses default fg color)
|
|
741
|
-
const defaultText = isLight ? "#000000" : "#e5e5e7";
|
|
742
|
-
const cssColors = {};
|
|
743
|
-
for (const [key, value] of Object.entries(resolved)) {
|
|
744
|
-
if (typeof value === "number") {
|
|
745
|
-
cssColors[key] = ansi256ToHex(value);
|
|
746
|
-
}
|
|
747
|
-
else if (value === "") {
|
|
748
|
-
// Empty means default terminal color - use sensible fallback for HTML
|
|
749
|
-
cssColors[key] = defaultText;
|
|
750
|
-
}
|
|
751
|
-
else {
|
|
752
|
-
cssColors[key] = value;
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return cssColors;
|
|
662
|
+
const colors = loadTheme(themeName ?? currentThemeName ?? getDefaultTheme()).colors;
|
|
663
|
+
return Object.fromEntries(Object.entries(colors).map(([token, color]) => [token, colorToHex(color)]));
|
|
756
664
|
}
|
|
757
665
|
/**
|
|
758
666
|
* Check if a theme is a "light" theme (for CSS that needs light/dark variants).
|
|
759
667
|
*/
|
|
760
668
|
export function isLightTheme(themeName) {
|
|
761
|
-
|
|
762
|
-
return themeName === "light";
|
|
669
|
+
return loadTheme(themeName ?? currentThemeName ?? getDefaultTheme()).appearance === "light";
|
|
763
670
|
}
|
|
764
671
|
/**
|
|
765
672
|
* Get explicit export colors from theme JSON, if specified.
|
|
@@ -773,12 +680,13 @@ export function getThemeExportColors(themeName) {
|
|
|
773
680
|
if (!exportSection)
|
|
774
681
|
return {};
|
|
775
682
|
const vars = themeJson.vars ?? {};
|
|
683
|
+
// Export colors end up in CSS, which understands hex, rgb() and oklch() values directly.
|
|
776
684
|
const resolve = (value) => {
|
|
777
685
|
if (value === undefined)
|
|
778
686
|
return undefined;
|
|
779
687
|
const resolved = resolveVarRefs(value, vars);
|
|
780
688
|
if (typeof resolved === "number")
|
|
781
|
-
return
|
|
689
|
+
return colorToHex(indexedColor(resolved));
|
|
782
690
|
if (resolved === "")
|
|
783
691
|
return undefined;
|
|
784
692
|
return resolved;
|
|
@@ -940,7 +848,7 @@ export function getMarkdownTheme() {
|
|
|
940
848
|
bold: (text) => theme.bold(text),
|
|
941
849
|
italic: (text) => theme.italic(text),
|
|
942
850
|
underline: (text) => theme.underline(text),
|
|
943
|
-
strikethrough: (text) =>
|
|
851
|
+
strikethrough: (text) => theme.strikethrough(text),
|
|
944
852
|
highlightCode: (code, lang) => {
|
|
945
853
|
// Validate language before highlighting to avoid stderr spam from cli-highlight
|
|
946
854
|
const validLang = lang && supportsLanguage(lang) ? lang : undefined;
|