@bismawy/pi-arnative 0.2.11 → 0.3.0
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 +38 -0
- package/README.md +11 -6
- package/extensions/arnative.ts +359 -0
- package/extensions/footer.ts +332 -213
- package/extensions/section-headers.ts +214 -138
- package/extensions/timestamps.ts +5 -2
- package/extensions/tools.ts +30 -40
- package/extensions/ui-render-tweaks.ts +87 -23
- package/extensions/usage.ts +37 -13
- package/lib/ansi.ts +23 -0
- package/lib/color.ts +118 -0
- package/lib/format.ts +6 -0
- package/lib/usage-store.ts +25 -16
- package/package.json +4 -3
- package/themes/arnative-cyberpunk.json +70 -78
- package/themes/arnative-dracula.json +70 -78
- package/themes/arnative-emerald.json +70 -78
- package/themes/arnative-gruvbox.json +70 -78
- package/themes/arnative-matrix.json +70 -78
- package/themes/arnative-nord.json +70 -78
- package/themes/arnative-sun.json +70 -78
- package/themes/arnative-synthwave.json +70 -78
- package/themes/arnative-violet.json +70 -78
- package/themes/arnative-zinc.json +70 -78
- package/themes/arnative.json +70 -78
- package/themes/gen-themes.mjs +301 -224
package/themes/gen-themes.mjs
CHANGED
|
@@ -1,272 +1,349 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Theme generator — the single source of truth for every `themes/*.json`.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Each theme is a handful of parameters (accent hue, chroma, canvas lightness,
|
|
6
|
+
* neutral tint) plus optional hue overrides for the strong palettes. The
|
|
7
|
+
* renderer derives every color from a shared OKLCH ramp, so lightness and
|
|
8
|
+
* saturation stay consistent across themes and no two roles collapse to the
|
|
9
|
+
* same value by accident.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
* node themes/gen-themes.mjs --check fail when a file differs from the generated output
|
|
12
|
-
*
|
|
13
|
-
* Color validation (contrast, required keys) lives in the self-check in
|
|
14
|
-
* `extensions/ui-render-tweaks.ts` to keep one source of rules.
|
|
11
|
+
* Usage: node themes/gen-themes.mjs [--check]
|
|
15
12
|
*/
|
|
16
|
-
import {
|
|
13
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { hexToOklch, oklchToRgb } from "../lib/color.ts";
|
|
17
|
+
import { assert } from "../lib/check.ts";
|
|
17
18
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
19
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const SCHEMA =
|
|
21
|
+
"https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json";
|
|
21
22
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
];
|
|
33
|
-
const REQUIRED_EXPORT = ["pageBg", "cardBg", "infoBg"];
|
|
23
|
+
// --- color helpers ---------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const hue = (x) => ((x % 360) + 360) % 360;
|
|
26
|
+
const round3 = (x) => +x.toFixed(3);
|
|
27
|
+
|
|
28
|
+
/** Format OKLCH after gamut-reducing chroma, so the written value renders as-is. */
|
|
29
|
+
function oklch(L, C, H) {
|
|
30
|
+
const { chroma } = oklchToRgb(L, C, H);
|
|
31
|
+
return `oklch(${+(L * 100).toFixed(1)}% ${round3(chroma)} ${Math.round(hue(H))})`;
|
|
32
|
+
}
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
// --- shared ramps ----------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
const NEUTRAL = { fg: 0.88, fgMuted: 0.72, fgDim: 0.61, fgFaint: 0.49 };
|
|
37
|
+
const THINKING = { off: 0.4, minimal: 0.47, low: 0.55, medium: 0.63, high: 0.71, xhigh: 0.79 };
|
|
38
|
+
const SEMANTIC = { success: 145, error: 25, warning: 85 };
|
|
39
|
+
const SYNTAX_OFFSET = { keyword: 0, func: 45, variable: -45, string: 120, number: 180, type: 90 };
|
|
40
|
+
|
|
41
|
+
/** Default hue for a role used outside `syntax*` (heading/link/label). */
|
|
42
|
+
const ACCENT_OFFSET = { heading: 0, link: 0, label: 60 };
|
|
43
|
+
|
|
44
|
+
// --- theme parameters ------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* `accent` seeds only the hue; `chroma` is the normalized accent chroma.
|
|
48
|
+
* `hues` overrides the derived hue per role (keyword/func/… /heading/link/label)
|
|
49
|
+
* to keep the strong palettes recognisable.
|
|
50
|
+
*/
|
|
51
|
+
const THEMES = {
|
|
52
|
+
arnative: {
|
|
53
|
+
accent: "#00d7ff",
|
|
54
|
+
chroma: 0.13,
|
|
55
|
+
canvasL: 0.27,
|
|
56
|
+
neutralHue: 232,
|
|
57
|
+
neutralChroma: 0.012,
|
|
58
|
+
hues: { heading: 75, label: 282 },
|
|
59
|
+
},
|
|
38
60
|
"arnative-sun": {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
colors: {
|
|
46
|
-
customMessageLabel: "#9575cd", mdHeading: "#f0c674", mdLink: "#81a2be",
|
|
47
|
-
syntaxComment: "#6A9955", syntaxKeyword: "#569CD6", syntaxFunction: "#DCDCAA",
|
|
48
|
-
syntaxVariable: "#9CDCFE", syntaxString: "#CE9178", syntaxNumber: "#B5CEA8",
|
|
49
|
-
syntaxType: "#4EC9B0", syntaxOperator: "#D4D4D4", syntaxPunctuation: "#D4D4D4",
|
|
50
|
-
thinkingMinimal: "#5f4e30", thinkingLow: "#80673c", thinkingMedium: "#aa8950",
|
|
51
|
-
thinkingHigh: "#e1a741", thinkingXhigh: "#f2b54a",
|
|
52
|
-
},
|
|
53
|
-
export: { pageBg: "#18181e", cardBg: "#1e1e24", infoBg: "#3c3728" },
|
|
61
|
+
accent: "#ffb454",
|
|
62
|
+
chroma: 0.14,
|
|
63
|
+
canvasL: 0.27,
|
|
64
|
+
neutralHue: 70,
|
|
65
|
+
neutralChroma: 0.012,
|
|
66
|
+
hues: { heading: 60, label: 330 },
|
|
54
67
|
},
|
|
55
68
|
"arnative-zinc": {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
colors: {
|
|
63
|
-
customMessageLabel: "#9575cd", mdHeading: "#f0c674", mdLink: "#81a2be",
|
|
64
|
-
syntaxComment: "#6A9955", syntaxKeyword: "#569CD6", syntaxFunction: "#DCDCAA",
|
|
65
|
-
syntaxVariable: "#9CDCFE", syntaxString: "#CE9178", syntaxNumber: "#B5CEA8",
|
|
66
|
-
syntaxType: "#4EC9B0", syntaxOperator: "#D4D4D4", syntaxPunctuation: "#D4D4D4",
|
|
67
|
-
thinkingMinimal: "#3d3d44", thinkingLow: "#595962", thinkingMedium: "#6e6e78",
|
|
68
|
-
thinkingHigh: "#96969f", thinkingXhigh: "#b8b8c2",
|
|
69
|
-
},
|
|
70
|
-
export: { pageBg: "#18181e", cardBg: "#1e1e24", infoBg: "#3c3728" },
|
|
69
|
+
accent: "#a1a1aa",
|
|
70
|
+
chroma: 0.02,
|
|
71
|
+
canvasL: 0.26,
|
|
72
|
+
neutralHue: 285,
|
|
73
|
+
neutralChroma: 0.005,
|
|
74
|
+
hues: { heading: 285, label: 285 },
|
|
71
75
|
},
|
|
72
76
|
"arnative-violet": {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
colors: {
|
|
80
|
-
customMessageLabel: "#9575cd", mdHeading: "#f0c674", mdLink: "#81a2be",
|
|
81
|
-
syntaxComment: "#6A9955", syntaxKeyword: "#569CD6", syntaxFunction: "#DCDCAA",
|
|
82
|
-
syntaxVariable: "#9CDCFE", syntaxString: "#CE9178", syntaxNumber: "#B5CEA8",
|
|
83
|
-
syntaxType: "#4EC9B0", syntaxOperator: "#D4D4D4", syntaxPunctuation: "#D4D4D4",
|
|
84
|
-
thinkingMinimal: "#413364", thinkingLow: "#614899", thinkingMedium: "#765cb2",
|
|
85
|
-
thinkingHigh: "#8f6ef0", thinkingXhigh: "#a98bfa",
|
|
86
|
-
},
|
|
87
|
-
export: { pageBg: "#18181e", cardBg: "#1e1e24", infoBg: "#3c3728" },
|
|
77
|
+
accent: "#a78bfa",
|
|
78
|
+
chroma: 0.14,
|
|
79
|
+
canvasL: 0.27,
|
|
80
|
+
neutralHue: 295,
|
|
81
|
+
neutralChroma: 0.012,
|
|
82
|
+
hues: { heading: 295, label: 330 },
|
|
88
83
|
},
|
|
89
84
|
"arnative-emerald": {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
},
|
|
96
|
-
colors: {
|
|
97
|
-
customMessageLabel: "#9575cd", mdHeading: "#f0c674", mdLink: "#81a2be",
|
|
98
|
-
syntaxComment: "#6A9955", syntaxKeyword: "#569CD6", syntaxFunction: "#DCDCAA",
|
|
99
|
-
syntaxVariable: "#9CDCFE", syntaxString: "#CE9178", syntaxNumber: "#B5CEA8",
|
|
100
|
-
syntaxType: "#4EC9B0", syntaxOperator: "#D4D4D4", syntaxPunctuation: "#D4D4D4",
|
|
101
|
-
thinkingMinimal: "#305f4d", thinkingLow: "#3c8066", thinkingMedium: "#50aa87",
|
|
102
|
-
thinkingHigh: "#41e1a4", thinkingXhigh: "#4af2b2",
|
|
103
|
-
},
|
|
104
|
-
export: { pageBg: "#18181e", cardBg: "#1e1e24", infoBg: "#3c3728" },
|
|
85
|
+
accent: "#6ee7b7",
|
|
86
|
+
chroma: 0.12,
|
|
87
|
+
canvasL: 0.27,
|
|
88
|
+
neutralHue: 165,
|
|
89
|
+
neutralChroma: 0.012,
|
|
90
|
+
hues: { heading: 165, label: 200 },
|
|
105
91
|
},
|
|
106
|
-
|
|
107
|
-
// --- strongly themed ----------------------------------------------------
|
|
108
|
-
// Monochrome phosphor green on a near-black canvas.
|
|
109
92
|
"arnative-matrix": {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
accent: "#00ff41",
|
|
94
|
+
chroma: 0.22,
|
|
95
|
+
canvasL: 0.15,
|
|
96
|
+
neutralHue: 145,
|
|
97
|
+
neutralChroma: 0.02,
|
|
98
|
+
hues: {
|
|
99
|
+
heading: 145, link: 145, label: 145,
|
|
100
|
+
keyword: 145, func: 145, variable: 145, string: 145, number: 145, type: 145,
|
|
115
101
|
},
|
|
116
|
-
colors: {
|
|
117
|
-
customMessageLabel: "#8dffab", mdHeading: "#00ff41", mdLink: "#8dffab",
|
|
118
|
-
syntaxComment: "#4f8f5f", syntaxKeyword: "#00ff41", syntaxFunction: "#9dffb4",
|
|
119
|
-
syntaxVariable: "#d6ffdf", syntaxString: "#2fbf5f", syntaxNumber: "#7dffa0",
|
|
120
|
-
syntaxType: "#3fe07a", syntaxOperator: "#b6ffc4", syntaxPunctuation: "#9dffb4",
|
|
121
|
-
thinkingMinimal: "#1f4a2c", thinkingLow: "#2c7a45", thinkingMedium: "#3fae63",
|
|
122
|
-
thinkingHigh: "#59d97f", thinkingXhigh: "#00ff41",
|
|
123
|
-
},
|
|
124
|
-
export: { pageBg: "#050a06", cardBg: "#0a1410", infoBg: "#123018" },
|
|
125
102
|
},
|
|
126
|
-
// Neon magenta/cyan/yellow on a deep indigo canvas.
|
|
127
103
|
"arnative-cyberpunk": {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
syntaxComment: "#6f6a9a", syntaxKeyword: "#00f0ff", syntaxFunction: "#ffe600",
|
|
137
|
-
syntaxVariable: "#ffd9f2", syntaxString: "#ff2e97", syntaxNumber: "#ff9a3c",
|
|
138
|
-
syntaxType: "#00ff9f", syntaxOperator: "#c8c8e8", syntaxPunctuation: "#9a96c0",
|
|
139
|
-
thinkingMinimal: "#33285c", thinkingLow: "#4d3a94", thinkingMedium: "#7a4fd6",
|
|
140
|
-
thinkingHigh: "#b84fe8", thinkingXhigh: "#ff4fc3",
|
|
104
|
+
accent: "#ff2e97",
|
|
105
|
+
chroma: 0.2,
|
|
106
|
+
canvasL: 0.16,
|
|
107
|
+
neutralHue: 275,
|
|
108
|
+
neutralChroma: 0.02,
|
|
109
|
+
hues: {
|
|
110
|
+
heading: 60, link: 190, label: 190,
|
|
111
|
+
keyword: 190, func: 60, variable: 320, string: 320, number: 30, type: 150,
|
|
141
112
|
},
|
|
142
|
-
export: { pageBg: "#0d0b16", cardBg: "#16131f", infoBg: "#3a2a10" },
|
|
143
113
|
},
|
|
144
|
-
// 80s retro: deep purple, neon pink, cyan.
|
|
145
114
|
"arnative-synthwave": {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
115
|
+
accent: "#ff7edb",
|
|
116
|
+
chroma: 0.17,
|
|
117
|
+
canvasL: 0.22,
|
|
118
|
+
neutralHue: 300,
|
|
119
|
+
neutralChroma: 0.015,
|
|
120
|
+
hues: {
|
|
121
|
+
heading: 55, link: 180, label: 180,
|
|
122
|
+
keyword: 55, func: 180, variable: 280, string: 30, number: 350, type: 320,
|
|
151
123
|
},
|
|
152
|
-
colors: {
|
|
153
|
-
customMessageLabel: "#36f9f6", mdHeading: "#fede5d", mdLink: "#36f9f6",
|
|
154
|
-
syntaxComment: "#848bbd", syntaxKeyword: "#fede5d", syntaxFunction: "#36f9f6",
|
|
155
|
-
syntaxVariable: "#f2eafb", syntaxString: "#ff8b39", syntaxNumber: "#f97e72",
|
|
156
|
-
syntaxType: "#ff7edb", syntaxOperator: "#f2eafb", syntaxPunctuation: "#b8a6e8",
|
|
157
|
-
thinkingMinimal: "#4a3a6b", thinkingLow: "#6b4f9e", thinkingMedium: "#9166d6",
|
|
158
|
-
thinkingHigh: "#d47ee8", thinkingXhigh: "#ff7edb",
|
|
159
|
-
},
|
|
160
|
-
export: { pageBg: "#1f1830", cardBg: "#2a2138", infoBg: "#443a63" },
|
|
161
124
|
},
|
|
162
|
-
// Warm retro: brownish grey canvas, gold + aqua + brick.
|
|
163
125
|
"arnative-gruvbox": {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
syntaxComment: "#928374", syntaxKeyword: "#fb4934", syntaxFunction: "#b8bb26",
|
|
173
|
-
syntaxVariable: "#83a598", syntaxString: "#b8bb26", syntaxNumber: "#d3869b",
|
|
174
|
-
syntaxType: "#fabd2f", syntaxOperator: "#ebdbb2", syntaxPunctuation: "#d5c4a1",
|
|
175
|
-
thinkingMinimal: "#665c54", thinkingLow: "#a89984", thinkingMedium: "#d5c4a1",
|
|
176
|
-
thinkingHigh: "#fabd2f", thinkingXhigh: "#fe8019",
|
|
126
|
+
accent: "#fabd2f",
|
|
127
|
+
chroma: 0.14,
|
|
128
|
+
canvasL: 0.24,
|
|
129
|
+
neutralHue: 70,
|
|
130
|
+
neutralChroma: 0.015,
|
|
131
|
+
hues: {
|
|
132
|
+
heading: 45, link: 200, label: 330,
|
|
133
|
+
keyword: 25, func: 70, variable: 200, string: 70, number: 330, type: 45,
|
|
177
134
|
},
|
|
178
|
-
export: { pageBg: "#1d2021", cardBg: "#282828", infoBg: "#3c3836" },
|
|
179
135
|
},
|
|
180
|
-
// Cool and calm Nord: frost blue + aurora.
|
|
181
136
|
"arnative-nord": {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
137
|
+
accent: "#88c0d0",
|
|
138
|
+
chroma: 0.07,
|
|
139
|
+
canvasL: 0.3,
|
|
140
|
+
neutralHue: 240,
|
|
141
|
+
neutralChroma: 0.01,
|
|
142
|
+
hues: {
|
|
143
|
+
heading: 40, link: 195, label: 310,
|
|
144
|
+
keyword: 220, func: 195, variable: 220, string: 95, number: 310, type: 195,
|
|
187
145
|
},
|
|
188
|
-
colors: {
|
|
189
|
-
customMessageLabel: "#b48ead", mdHeading: "#ebcb8b", mdLink: "#88c0d0",
|
|
190
|
-
syntaxComment: "#64728c", syntaxKeyword: "#81a1c1", syntaxFunction: "#88c0d0",
|
|
191
|
-
syntaxVariable: "#d8dee9", syntaxString: "#a3be8c", syntaxNumber: "#b48ead",
|
|
192
|
-
syntaxType: "#8fbcbb", syntaxOperator: "#81a1c1", syntaxPunctuation: "#eceff4",
|
|
193
|
-
thinkingMinimal: "#4c566a", thinkingLow: "#5e81ac", thinkingMedium: "#81a1c1",
|
|
194
|
-
thinkingHigh: "#88c0d0", thinkingXhigh: "#8fbcbb",
|
|
195
|
-
},
|
|
196
|
-
export: { pageBg: "#272c36", cardBg: "#2e3440", infoBg: "#3b4252" },
|
|
197
146
|
},
|
|
198
|
-
// Classic dark purple: pink + cyan + mint.
|
|
199
147
|
"arnative-dracula": {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
syntaxComment: "#6272a4", syntaxKeyword: "#ff79c6", syntaxFunction: "#50fa7b",
|
|
209
|
-
syntaxVariable: "#8be9fd", syntaxString: "#f1fa8c", syntaxNumber: "#bd93f9",
|
|
210
|
-
syntaxType: "#8be9fd", syntaxOperator: "#ff79c6", syntaxPunctuation: "#f8f8f2",
|
|
211
|
-
thinkingMinimal: "#464a5d", thinkingLow: "#6272a4", thinkingMedium: "#7a6fd0",
|
|
212
|
-
thinkingHigh: "#bd93f9", thinkingXhigh: "#ff79c6",
|
|
148
|
+
accent: "#bd93f9",
|
|
149
|
+
chroma: 0.14,
|
|
150
|
+
canvasL: 0.29,
|
|
151
|
+
neutralHue: 285,
|
|
152
|
+
neutralChroma: 0.012,
|
|
153
|
+
hues: {
|
|
154
|
+
heading: 60, link: 190, label: 330,
|
|
155
|
+
keyword: 330, func: 135, variable: 190, string: 60, number: 265, type: 190,
|
|
213
156
|
},
|
|
214
|
-
export: { pageBg: "#21222c", cardBg: "#282a36", infoBg: "#44475a" },
|
|
215
157
|
},
|
|
216
158
|
};
|
|
217
159
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
160
|
+
// --- role order (matches the pi theme schema) ------------------------------
|
|
161
|
+
|
|
162
|
+
const ROLE_ORDER = [
|
|
163
|
+
"accent", "accentSoft", "border", "borderAccent", "borderMuted", "success", "error", "warning",
|
|
164
|
+
"muted", "dim", "text", "thinkingText",
|
|
165
|
+
"selectedBg", "scrollbarTrack", "scrollbarThumb", "searchMatchBg", "searchMatchText",
|
|
166
|
+
"userMessageBg", "userMessageText", "customMessageBg", "customMessageText", "customMessageLabel",
|
|
167
|
+
"toolPendingBg", "toolSuccessBg", "toolErrorBg", "toolTitle", "toolOutput",
|
|
168
|
+
"mdHeading", "mdLink", "mdLinkUrl", "mdCode", "mdCodeBlock", "mdCodeBlockBorder", "mdQuote",
|
|
169
|
+
"mdQuoteBorder", "mdHr", "mdListBullet",
|
|
170
|
+
"toolDiffAdded", "toolDiffRemoved", "toolDiffContext",
|
|
171
|
+
"syntaxComment", "syntaxKeyword", "syntaxFunction", "syntaxVariable", "syntaxString",
|
|
172
|
+
"syntaxNumber", "syntaxType", "syntaxOperator", "syntaxPunctuation",
|
|
173
|
+
"thinkingOff", "thinkingMinimal", "thinkingLow", "thinkingMedium", "thinkingHigh",
|
|
174
|
+
"thinkingXhigh", "thinkingMax", "bashMode",
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
// --- derivation ------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
function derive(spec) {
|
|
180
|
+
const h = hexToOklch(spec.accent).H;
|
|
181
|
+
const c = spec.chroma;
|
|
182
|
+
const nh = spec.neutralHue;
|
|
183
|
+
const nc = spec.neutralChroma;
|
|
184
|
+
const base = spec.canvasL;
|
|
185
|
+
const H = { ...SYNTAX_OFFSET, ...ACCENT_OFFSET, ...(spec.hues ?? {}) };
|
|
186
|
+
const roleHue = (role) => hue(H[role]);
|
|
187
|
+
const sem = (role) => hue(SEMANTIC[role]);
|
|
188
|
+
|
|
189
|
+
const vars = {
|
|
190
|
+
fg: oklch(NEUTRAL.fg, nc, nh),
|
|
191
|
+
fgMuted: oklch(NEUTRAL.fgMuted, nc, nh),
|
|
192
|
+
fgDim: oklch(NEUTRAL.fgDim, nc, nh),
|
|
193
|
+
fgFaint: oklch(NEUTRAL.fgFaint, nc, nh),
|
|
194
|
+
accent: oklch(0.78, c, h),
|
|
195
|
+
accentSoft: oklch(0.82, c * 0.18, h),
|
|
196
|
+
accentBorder: oklch(0.82, c * 0.6, h),
|
|
197
|
+
success: oklch(0.78, Math.min(c, 0.15), sem("success")),
|
|
198
|
+
error: oklch(0.76, Math.min(c, 0.15), sem("error")),
|
|
199
|
+
warning: oklch(0.86, Math.min(c, 0.14), sem("warning")),
|
|
200
|
+
surfaceMsg: oklch(base, nc * 0.5, nh),
|
|
225
201
|
};
|
|
226
202
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
203
|
+
const accentLiteral = oklch(0.8, c * 0.7, h);
|
|
204
|
+
const roles = {
|
|
205
|
+
accent: "accent",
|
|
206
|
+
accentSoft: "accentSoft",
|
|
207
|
+
border: oklch(0.55, c * 0.35, h),
|
|
208
|
+
borderAccent: "accentBorder",
|
|
209
|
+
borderMuted: "fgFaint",
|
|
210
|
+
success: "success",
|
|
211
|
+
error: "error",
|
|
212
|
+
warning: "warning",
|
|
213
|
+
muted: "fgMuted",
|
|
214
|
+
dim: "fgDim",
|
|
215
|
+
text: "fg",
|
|
216
|
+
thinkingText: "fgMuted",
|
|
217
|
+
|
|
218
|
+
selectedBg: oklch(base + 0.07, c * 0.25, h),
|
|
219
|
+
scrollbarTrack: "fgFaint",
|
|
220
|
+
scrollbarThumb: "accentSoft",
|
|
221
|
+
searchMatchBg: oklch(base + 0.14, c * 0.45, h),
|
|
222
|
+
searchMatchText: "fg",
|
|
223
|
+
userMessageBg: "surfaceMsg",
|
|
224
|
+
userMessageText: "fg",
|
|
225
|
+
customMessageBg: oklch(base + 0.01, c * 0.4, roleHue("label")),
|
|
226
|
+
customMessageText: "fg",
|
|
227
|
+
customMessageLabel: oklch(0.78, c * 0.7, roleHue("label")),
|
|
228
|
+
|
|
229
|
+
toolPendingBg: oklch(base - 0.02, c * 0.3, h),
|
|
230
|
+
toolSuccessBg: oklch(base - 0.03, c * 0.4, sem("success")),
|
|
231
|
+
toolErrorBg: oklch(base - 0.03, c * 0.4, sem("error")),
|
|
232
|
+
toolTitle: "fg",
|
|
233
|
+
toolOutput: "fgMuted",
|
|
234
|
+
|
|
235
|
+
mdHeading: oklch(0.88, c * 0.6, roleHue("heading")),
|
|
236
|
+
mdLink: oklch(0.8, c * 0.7, roleHue("link")),
|
|
237
|
+
mdLinkUrl: "fgDim",
|
|
238
|
+
mdCode: accentLiteral,
|
|
239
|
+
mdCodeBlock: "success",
|
|
240
|
+
mdCodeBlockBorder: "fgMuted",
|
|
241
|
+
mdQuote: "fgMuted",
|
|
242
|
+
mdQuoteBorder: "fgMuted",
|
|
243
|
+
mdHr: "fgFaint",
|
|
244
|
+
mdListBullet: accentLiteral,
|
|
245
|
+
|
|
246
|
+
toolDiffAdded: "success",
|
|
247
|
+
toolDiffRemoved: "error",
|
|
248
|
+
toolDiffContext: "fgMuted",
|
|
249
|
+
|
|
250
|
+
syntaxComment: "fgDim",
|
|
251
|
+
syntaxKeyword: accentLiteral,
|
|
252
|
+
syntaxFunction: oklch(0.86, c * 0.8, roleHue("func")),
|
|
253
|
+
syntaxVariable: oklch(0.84, c * 0.55, roleHue("variable")),
|
|
254
|
+
syntaxString: oklch(0.82, c * 0.7, roleHue("string")),
|
|
255
|
+
syntaxNumber: oklch(0.8, c * 0.7, roleHue("number")),
|
|
256
|
+
syntaxType: oklch(0.8, c * 0.8, roleHue("type")),
|
|
257
|
+
syntaxOperator: "fg",
|
|
258
|
+
syntaxPunctuation: "fgMuted",
|
|
259
|
+
|
|
260
|
+
thinkingOff: oklch(THINKING.off, nc, nh),
|
|
261
|
+
thinkingMinimal: oklch(THINKING.minimal, c * 0.25, h),
|
|
262
|
+
thinkingLow: oklch(THINKING.low, c * 0.4, h),
|
|
263
|
+
thinkingMedium: oklch(THINKING.medium, c * 0.6, h),
|
|
264
|
+
thinkingHigh: oklch(THINKING.high, c * 0.8, h),
|
|
265
|
+
thinkingXhigh: oklch(THINKING.xhigh, c, h),
|
|
266
|
+
thinkingMax: "accent",
|
|
267
|
+
|
|
268
|
+
bashMode: "success",
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const exp = {
|
|
272
|
+
pageBg: oklch(base - 0.06, nc, nh),
|
|
273
|
+
cardBg: oklch(base - 0.03, nc, nh),
|
|
274
|
+
infoBg: oklch(base + 0.05, c * 0.25, h),
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Roles must match ROLE_ORDER exactly and each value must be a var name or a literal.
|
|
278
|
+
const missing = ROLE_ORDER.filter((n) => !(n in roles));
|
|
279
|
+
const extra = Object.keys(roles).filter((n) => !ROLE_ORDER.includes(n));
|
|
280
|
+
assert(missing.length === 0 && extra.length === 0, `roles vs ROLE_ORDER: missing [${missing}] extra [${extra}]`);
|
|
281
|
+
for (const [name, value] of Object.entries(roles)) {
|
|
282
|
+
assert(/^oklch\(/.test(value) || value in vars, `role ${name} is neither a var nor an oklch literal: ${value}`);
|
|
243
283
|
}
|
|
244
|
-
|
|
284
|
+
|
|
285
|
+
// Distinct vars must stay visually distinct (roles may alias on purpose).
|
|
286
|
+
const seen = new Map();
|
|
287
|
+
for (const [name, value] of Object.entries(vars)) {
|
|
288
|
+
const { r, g, b } = oklchToRgb(...parseOklch(value));
|
|
289
|
+
const rgb = `${r},${g},${b}`;
|
|
290
|
+
if (seen.has(rgb)) throw new Error(`vars ${seen.get(rgb)} and ${name} resolve to the same color`);
|
|
291
|
+
seen.set(rgb, name);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return { vars, roles, exp };
|
|
245
295
|
}
|
|
246
296
|
|
|
297
|
+
function parseOklch(value) {
|
|
298
|
+
const m = /\(([\d.]+)% ([\d.]+) ([\d.]+)\)/.exec(value);
|
|
299
|
+
return [Number(m[1]) / 100, Number(m[2]), Number(m[3])];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// --- rendering -------------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
function render(name, { vars, roles, exp }) {
|
|
305
|
+
const lines = [
|
|
306
|
+
"{",
|
|
307
|
+
`\t"$schema": ${JSON.stringify(SCHEMA)},`,
|
|
308
|
+
`\t"name": ${JSON.stringify(name)},`,
|
|
309
|
+
`\t"appearance": "dark",`,
|
|
310
|
+
`\t"vars": {`,
|
|
311
|
+
];
|
|
312
|
+
const entries = Object.entries(vars);
|
|
313
|
+
entries.forEach(([k, v], i) => lines.push(`\t\t${JSON.stringify(k)}: ${JSON.stringify(v)}${i < entries.length - 1 ? "," : ""}`));
|
|
314
|
+
lines.push("\t},", `\t"colors": {`);
|
|
315
|
+
const roleEntries = ROLE_ORDER.map((r) => [r, roles[r]]);
|
|
316
|
+
const blank = new Set(["thinkingText", "toolOutput", "mdListBullet", "syntaxPunctuation"]);
|
|
317
|
+
roleEntries.forEach(([k, v], i) => {
|
|
318
|
+
if (blank.has(k) && i > 0) lines.push("");
|
|
319
|
+
lines.push(`\t\t${JSON.stringify(k)}: ${JSON.stringify(v)}${i < roleEntries.length - 1 ? "," : ""}`);
|
|
320
|
+
});
|
|
321
|
+
lines.push("\t},", `\t"export": {`);
|
|
322
|
+
const expEntries = Object.entries(exp);
|
|
323
|
+
expEntries.forEach(([k, v], i) => lines.push(`\t\t${JSON.stringify(k)}: ${JSON.stringify(v)}${i < expEntries.length - 1 ? "," : ""}`));
|
|
324
|
+
lines.push("\t}", "}");
|
|
325
|
+
return lines.join("\n") + "\n";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// --- main ------------------------------------------------------------------
|
|
329
|
+
|
|
247
330
|
const check = process.argv.includes("--check");
|
|
248
|
-
let
|
|
249
|
-
for (const name of Object.
|
|
250
|
-
const content = render(name,
|
|
251
|
-
const file =
|
|
331
|
+
let drifted = 0;
|
|
332
|
+
for (const [name, spec] of Object.entries(THEMES)) {
|
|
333
|
+
const content = render(name, derive({ ...spec, name }));
|
|
334
|
+
const file = join(HERE, `${name}.json`);
|
|
252
335
|
if (check) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (old !== content) {
|
|
258
|
-
console.error(`DIFFERS: themes/${name}.json`);
|
|
259
|
-
failed++;
|
|
336
|
+
const current = await readFile(file, "utf8").catch(() => "");
|
|
337
|
+
if (current !== content) {
|
|
338
|
+
console.error(`✗ ${name}.json is stale — run: node themes/gen-themes.mjs`);
|
|
339
|
+
drifted++;
|
|
260
340
|
}
|
|
261
341
|
} else {
|
|
262
|
-
|
|
263
|
-
console.log(
|
|
342
|
+
await writeFile(file, content);
|
|
343
|
+
console.log(`✓ ${name}.json`);
|
|
264
344
|
}
|
|
265
345
|
}
|
|
266
346
|
if (check) {
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
console.log(`OK: ${Object.keys(PALETTES).length} themes in sync with the generator`);
|
|
272
|
-
}
|
|
347
|
+
if (drifted) process.exit(1);
|
|
348
|
+
console.log(`✓ ${Object.keys(THEMES).length} themes in sync with gen-themes.mjs`);
|
|
349
|
+
}
|