mu-agent 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +7 -3
  3. package/dist/bundle/chunks/anthropic-messages-VMESUHQ7.js +6 -0
  4. package/dist/bundle/chunks/azure-openai-responses-K232MQFF.js +2 -0
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
  6. package/dist/bundle/chunks/chunk-2X3AO33C.js +2 -0
  7. package/dist/bundle/chunks/{chunk-FUXEF6JQ.js → chunk-4O2SCVYF.js} +1 -1
  8. package/dist/bundle/chunks/chunk-E3TC4YNV.js +2 -0
  9. package/dist/bundle/chunks/chunk-FU4XQUSL.js +11 -0
  10. package/dist/bundle/chunks/{chunk-S7SZN6Z3.js → chunk-GNYZK6XN.js} +1 -1
  11. package/dist/bundle/chunks/chunk-MVPXOVOJ.js +2 -0
  12. package/dist/bundle/chunks/{chunk-XOBBIK6P.js → chunk-TUBB3BLE.js} +71 -64
  13. package/dist/bundle/chunks/chunk-UTB4DTYQ.js +2 -0
  14. package/dist/bundle/chunks/{chunk-WIUZI2CJ.js → chunk-V7IXECST.js} +2 -2
  15. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-NR2TDCXU.js +2 -0
  16. package/dist/bundle/chunks/github-copilot.js +1 -1
  17. package/dist/bundle/chunks/google-generative-ai-C44KRRZ4.js +2 -0
  18. package/dist/bundle/chunks/google-vertex-25DUXPTJ.js +2 -0
  19. package/dist/bundle/chunks/meta.js +2 -0
  20. package/dist/bundle/chunks/mistral-conversations-6IHQOR4R.js +5 -0
  21. package/dist/bundle/chunks/{openai-codex-responses-QDSOTBBO.js → openai-codex-responses-3UAYOSSR.js} +2 -2
  22. package/dist/bundle/chunks/{openai-completions-KQPOPEVM.js → openai-completions-FIEQGWAH.js} +3 -3
  23. package/dist/bundle/chunks/openai-responses-OZJXLHFC.js +2 -0
  24. package/dist/bundle/chunks/{openrouter-images-N4XVVFSD.js → openrouter-images-JVEJN44Q.js} +1 -1
  25. package/dist/bundle/chunks/{pi-messages-GFKBWJHZ.js → pi-messages-TM5SOOIW.js} +2 -2
  26. package/dist/bundle/chunks/typesafe-system-one-VKYX4OGY.js +2 -0
  27. package/dist/bundle/chunks/{virtual-modules-MYFARBUK.js → virtual-modules-IOLJJ5KK.js} +1 -1
  28. package/dist/bundle/cli-runtime.js +3 -0
  29. package/dist/bundle/cli.js +4 -2
  30. package/dist/bundle/index.js +1 -1
  31. package/dist/bundle/rpc-entry.js +1 -1
  32. package/dist/core/export-html/template.css +10 -0
  33. package/dist/core/export-html/template.js +56 -18
  34. package/dist/modes/interactive/theme/theme-controller.d.ts +6 -0
  35. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  36. package/dist/modes/interactive/theme/theme-controller.js +41 -3
  37. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  38. package/dist/modes/interactive/theme/theme-json.d.ts +1 -0
  39. package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -1
  40. package/dist/modes/interactive/theme/theme-json.js +3 -1
  41. package/dist/modes/interactive/theme/theme-json.js.map +1 -1
  42. package/dist/modes/interactive/theme/theme-schema.json +7 -2
  43. package/dist/modes/interactive/theme/theme.d.ts +40 -7
  44. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  45. package/dist/modes/interactive/theme/theme.js +122 -214
  46. package/dist/modes/interactive/theme/theme.js.map +1 -1
  47. package/docs/cli-integration.md +106 -0
  48. package/docs/cli.md +268 -0
  49. package/docs/compaction.md +45 -26
  50. package/docs/configuration.md +45 -0
  51. package/docs/containerization.md +109 -82
  52. package/docs/custom-provider.md +109 -722
  53. package/docs/docs.json +139 -99
  54. package/docs/environment-variables.md +3 -5
  55. package/docs/extensions.md +137 -2953
  56. package/docs/how-pi-works.md +49 -0
  57. package/docs/images/interactive-mode.png +0 -0
  58. package/docs/index.md +24 -69
  59. package/docs/json.md +193 -65
  60. package/docs/keybindings.md +57 -102
  61. package/docs/llama-cpp.md +3 -3
  62. package/docs/message-types.md +261 -0
  63. package/docs/models.md +64 -546
  64. package/docs/packages.md +67 -166
  65. package/docs/prompt-templates.md +31 -68
  66. package/docs/providers.md +102 -232
  67. package/docs/quickstart.md +61 -106
  68. package/docs/rpc-commands.md +854 -0
  69. package/docs/rpc-extension-ui.md +200 -0
  70. package/docs/rpc.md +129 -1556
  71. package/docs/sdk.md +76 -1160
  72. package/docs/security.md +70 -32
  73. package/docs/session-format.md +25 -216
  74. package/docs/sessions.md +35 -141
  75. package/docs/settings.md +109 -387
  76. package/docs/shell-aliases.md +85 -5
  77. package/docs/skills.md +51 -190
  78. package/docs/slash-commands.md +60 -0
  79. package/docs/terminal-setup.md +105 -78
  80. package/docs/termux.md +74 -83
  81. package/docs/themes.md +71 -279
  82. package/docs/tmux.md +31 -39
  83. package/docs/tui.md +74 -910
  84. package/docs/usage.md +54 -272
  85. package/docs/windows.md +43 -17
  86. package/examples/README.md +13 -2
  87. package/examples/extensions/README.md +1 -0
  88. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  89. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  90. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  91. package/examples/extensions/debug-provider.ts +90 -0
  92. package/examples/extensions/gondolin/package-lock.json +2 -2
  93. package/examples/extensions/gondolin/package.json +1 -1
  94. package/examples/extensions/sandbox/package-lock.json +2 -2
  95. package/examples/extensions/sandbox/package.json +1 -1
  96. package/examples/extensions/with-deps/package-lock.json +2 -2
  97. package/examples/extensions/with-deps/package.json +1 -1
  98. package/examples/plugins/pi-example-plugin/src/session.ts +3 -2
  99. package/examples/rpc-client.ts +35 -0
  100. package/examples/rpc-extension-ui.ts +25 -5
  101. package/examples/sdk/README.md +1 -1
  102. package/judge/dist/auth.js +166 -28
  103. package/judge/dist/chrome.js +12 -1
  104. package/judge/dist/kyrn-judge.js +1424 -589
  105. package/judge/dist/setup.js +2051 -0
  106. package/judge/manifest.json +13 -13
  107. package/judge/skills/mu-browser/SKILL.md +1 -0
  108. package/kyrn/bin/kyrn-doctor +107 -18
  109. package/kyrn/bin/mu.mjs +293 -17
  110. package/package.json +4 -4
  111. package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +0 -6
  112. package/dist/bundle/chunks/azure-openai-responses-HUSQ3GP2.js +0 -2
  113. package/dist/bundle/chunks/chunk-6R7UI4IY.js +0 -2
  114. package/dist/bundle/chunks/chunk-CK5Z65DM.js +0 -2
  115. package/dist/bundle/chunks/chunk-RCZIEVGO.js +0 -11
  116. package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +0 -2
  117. package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +0 -2
  118. package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +0 -5
  119. package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +0 -2
  120. package/docs/development.md +0 -90
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { getCapabilities, } from "@earendil-works/pi-tui";
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
- this.fgColors = new Map();
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
- for (const [key, value] of Object.entries(colors)) {
189
- this.fgColors.set(key, fgAnsi(value, mode));
190
- }
191
- this.bgColors = new Map();
192
- const backgrounds = {
193
- ...bgColors,
194
- searchMatchBg: bgColors.searchMatchBg ?? bgColors.selectedBg,
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 [key, value] of Object.entries(backgrounds)) {
197
- this.bgColors.set(key, bgAnsi(value, mode));
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.fgColors.get(color);
202
- if (!ansi)
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.bgColors.get(color);
208
- if (!ansi)
209
- throw new Error(`Unknown theme background color: ${color}`);
210
- return `${ansi}${text}\x1b[49m`; // Reset only background color
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
- const ansi = this.fgColors.get(color);
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
- const ansi = this.bgColors.get(color);
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 ?? (getCapabilities().trueColor ? "truecolor" : "256color");
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(hexToRgb(ansi256ToHex(index)));
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 name = themeName ?? currentThemeName ?? getDefaultTheme();
737
- const isLight = name === "light";
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
- // Currently just check the name - could be extended to analyze colors
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 ansi256ToHex(resolved);
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) => chalk.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;