@promptctl/rich-js 0.18.0 → 0.18.1

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 (221) hide show
  1. package/dist/core/anchor.d.ts +45 -0
  2. package/dist/core/anchor.js +39 -0
  3. package/dist/core/ansi.d.ts +65 -0
  4. package/dist/core/ansi.js +231 -0
  5. package/dist/core/box.d.ts +143 -0
  6. package/dist/core/box.js +379 -0
  7. package/dist/core/cells.d.ts +159 -0
  8. package/dist/core/cells.js +287 -0
  9. package/dist/core/color.d.ts +246 -0
  10. package/dist/core/color.js +1038 -0
  11. package/dist/core/console.d.ts +165 -0
  12. package/dist/core/console.js +527 -0
  13. package/dist/core/emoji.d.ts +24 -0
  14. package/dist/core/emoji.js +391 -0
  15. package/dist/core/export-html.d.ts +59 -0
  16. package/dist/core/export-html.js +200 -0
  17. package/dist/core/export-lines.d.ts +121 -0
  18. package/dist/core/export-lines.js +141 -0
  19. package/dist/core/highlighter.d.ts +33 -0
  20. package/dist/core/highlighter.js +124 -0
  21. package/dist/core/json.d.ts +27 -0
  22. package/dist/core/json.js +53 -0
  23. package/dist/core/markup.d.ts +119 -0
  24. package/dist/core/markup.js +731 -0
  25. package/dist/core/measure.d.ts +27 -0
  26. package/dist/core/measure.js +62 -0
  27. package/dist/core/oklch.d.ts +161 -0
  28. package/dist/core/oklch.js +312 -0
  29. package/dist/core/osc8.d.ts +68 -0
  30. package/dist/core/osc8.js +90 -0
  31. package/dist/core/place.d.ts +30 -0
  32. package/dist/core/place.js +51 -0
  33. package/dist/core/pretty.d.ts +144 -0
  34. package/dist/core/pretty.js +476 -0
  35. package/dist/core/protocol.d.ts +267 -0
  36. package/dist/core/protocol.js +163 -0
  37. package/dist/core/render.d.ts +84 -0
  38. package/dist/core/render.js +145 -0
  39. package/dist/core/segment.d.ts +161 -0
  40. package/dist/core/segment.js +492 -0
  41. package/dist/core/spinnerData.d.ts +12 -0
  42. package/dist/core/spinnerData.js +117 -0
  43. package/dist/core/strip.d.ts +141 -0
  44. package/dist/core/strip.js +305 -0
  45. package/dist/core/style.d.ts +169 -0
  46. package/dist/core/style.js +695 -0
  47. package/dist/core/subscription.d.ts +12 -0
  48. package/dist/core/subscription.js +12 -0
  49. package/dist/core/text.d.ts +256 -0
  50. package/dist/core/text.js +976 -0
  51. package/dist/core/wrap.d.ts +41 -0
  52. package/dist/core/wrap.js +127 -0
  53. package/dist/host/app.d.ts +121 -0
  54. package/dist/host/app.js +255 -0
  55. package/dist/host/host-environment.d.ts +26 -0
  56. package/dist/host/host-environment.js +44 -0
  57. package/dist/host/index.d.ts +5 -0
  58. package/dist/host/index.js +3 -0
  59. package/dist/host/terminal-host.d.ts +179 -0
  60. package/dist/host/terminal-host.js +186 -0
  61. package/dist/index.d.ts +91 -0
  62. package/dist/index.js +107 -0
  63. package/dist/node/prompt.d.ts +21 -0
  64. package/dist/node/prompt.js +30 -0
  65. package/dist/node/save.d.ts +17 -0
  66. package/dist/node/save.js +21 -0
  67. package/dist/node/terminal-host.d.ts +113 -0
  68. package/dist/node/terminal-host.js +227 -0
  69. package/dist/node/traceback.d.ts +37 -0
  70. package/dist/node/traceback.js +134 -0
  71. package/dist/renderables/align.d.ts +16 -0
  72. package/dist/renderables/align.js +33 -0
  73. package/dist/renderables/columns.d.ts +61 -0
  74. package/dist/renderables/columns.js +152 -0
  75. package/dist/renderables/constrain.d.ts +15 -0
  76. package/dist/renderables/constrain.js +37 -0
  77. package/dist/renderables/embed.d.ts +50 -0
  78. package/dist/renderables/embed.js +71 -0
  79. package/dist/renderables/flexStrip.d.ts +41 -0
  80. package/dist/renderables/flexStrip.js +200 -0
  81. package/dist/renderables/group.d.ts +11 -0
  82. package/dist/renderables/group.js +17 -0
  83. package/dist/renderables/layout.d.ts +92 -0
  84. package/dist/renderables/layout.js +312 -0
  85. package/dist/renderables/live.d.ts +50 -0
  86. package/dist/renderables/live.js +175 -0
  87. package/dist/renderables/markdown.d.ts +24 -0
  88. package/dist/renderables/markdown.js +184 -0
  89. package/dist/renderables/padding.d.ts +38 -0
  90. package/dist/renderables/padding.js +141 -0
  91. package/dist/renderables/panel.d.ts +116 -0
  92. package/dist/renderables/panel.js +307 -0
  93. package/dist/renderables/progress.d.ts +90 -0
  94. package/dist/renderables/progress.js +238 -0
  95. package/dist/renderables/progressBar.d.ts +31 -0
  96. package/dist/renderables/progressBar.js +53 -0
  97. package/dist/renderables/prompt.d.ts +49 -0
  98. package/dist/renderables/prompt.js +125 -0
  99. package/dist/renderables/rule.d.ts +25 -0
  100. package/dist/renderables/rule.js +90 -0
  101. package/dist/renderables/spinner.d.ts +31 -0
  102. package/dist/renderables/spinner.js +70 -0
  103. package/dist/renderables/status.d.ts +23 -0
  104. package/dist/renderables/status.js +60 -0
  105. package/dist/renderables/syntax.d.ts +35 -0
  106. package/dist/renderables/syntax.js +115 -0
  107. package/dist/renderables/table.d.ts +194 -0
  108. package/dist/renderables/table.js +795 -0
  109. package/dist/renderables/traceback.d.ts +31 -0
  110. package/dist/renderables/traceback.js +108 -0
  111. package/dist/renderables/tree.d.ts +68 -0
  112. package/dist/renderables/tree.js +150 -0
  113. package/dist/renderables/viewport.d.ts +114 -0
  114. package/dist/renderables/viewport.js +198 -0
  115. package/dist/template-bindings/color-funcs.d.ts +87 -0
  116. package/dist/template-bindings/color-funcs.js +217 -0
  117. package/dist/template-bindings/helpers.d.ts +24 -0
  118. package/dist/template-bindings/helpers.js +48 -0
  119. package/dist/template-bindings/index.d.ts +107 -0
  120. package/dist/template-bindings/index.js +146 -0
  121. package/dist/template-bindings/palette-funcs.d.ts +94 -0
  122. package/dist/template-bindings/palette-funcs.js +141 -0
  123. package/dist/template-bindings/style-funcs.d.ts +28 -0
  124. package/dist/template-bindings/style-funcs.js +155 -0
  125. package/dist/themes/buildPalette.d.ts +36 -0
  126. package/dist/themes/buildPalette.js +56 -0
  127. package/dist/themes/colorMath.d.ts +82 -0
  128. package/dist/themes/colorMath.js +254 -0
  129. package/dist/themes/colorRef.d.ts +51 -0
  130. package/dist/themes/colorRef.js +96 -0
  131. package/dist/themes/data/ansi-tables.d.ts +14 -0
  132. package/dist/themes/data/ansi-tables.js +49 -0
  133. package/dist/themes/data/atom-one-dark.d.ts +3 -0
  134. package/dist/themes/data/atom-one-dark.js +177 -0
  135. package/dist/themes/data/atom-one-light.d.ts +3 -0
  136. package/dist/themes/data/atom-one-light.js +177 -0
  137. package/dist/themes/data/catppuccin-frappe.d.ts +3 -0
  138. package/dist/themes/data/catppuccin-frappe.js +177 -0
  139. package/dist/themes/data/catppuccin-latte.d.ts +3 -0
  140. package/dist/themes/data/catppuccin-latte.js +177 -0
  141. package/dist/themes/data/catppuccin-macchiato.d.ts +3 -0
  142. package/dist/themes/data/catppuccin-macchiato.js +177 -0
  143. package/dist/themes/data/catppuccin-mocha.d.ts +3 -0
  144. package/dist/themes/data/catppuccin-mocha.js +177 -0
  145. package/dist/themes/data/cyberpunk.d.ts +17 -0
  146. package/dist/themes/data/cyberpunk.js +193 -0
  147. package/dist/themes/data/default.d.ts +3 -0
  148. package/dist/themes/data/default.js +160 -0
  149. package/dist/themes/data/dracula.d.ts +3 -0
  150. package/dist/themes/data/dracula.js +177 -0
  151. package/dist/themes/data/flexoki.d.ts +3 -0
  152. package/dist/themes/data/flexoki.js +177 -0
  153. package/dist/themes/data/gruvbox.d.ts +3 -0
  154. package/dist/themes/data/gruvbox.js +177 -0
  155. package/dist/themes/data/index.d.ts +35 -0
  156. package/dist/themes/data/index.js +64 -0
  157. package/dist/themes/data/monokai.d.ts +3 -0
  158. package/dist/themes/data/monokai.js +177 -0
  159. package/dist/themes/data/nord.d.ts +3 -0
  160. package/dist/themes/data/nord.js +177 -0
  161. package/dist/themes/data/rose-pine-dawn.d.ts +3 -0
  162. package/dist/themes/data/rose-pine-dawn.js +177 -0
  163. package/dist/themes/data/rose-pine-moon.d.ts +3 -0
  164. package/dist/themes/data/rose-pine-moon.js +177 -0
  165. package/dist/themes/data/rose-pine.d.ts +3 -0
  166. package/dist/themes/data/rose-pine.js +177 -0
  167. package/dist/themes/data/solarized-dark.d.ts +3 -0
  168. package/dist/themes/data/solarized-dark.js +177 -0
  169. package/dist/themes/data/solarized-light.d.ts +3 -0
  170. package/dist/themes/data/solarized-light.js +177 -0
  171. package/dist/themes/data/svg-export.d.ts +3 -0
  172. package/dist/themes/data/svg-export.js +177 -0
  173. package/dist/themes/data/textual-ansi.d.ts +3 -0
  174. package/dist/themes/data/textual-ansi.js +160 -0
  175. package/dist/themes/data/textual-dark.d.ts +3 -0
  176. package/dist/themes/data/textual-dark.js +160 -0
  177. package/dist/themes/data/textual-light.d.ts +3 -0
  178. package/dist/themes/data/textual-light.js +160 -0
  179. package/dist/themes/data/tokyo-night.d.ts +3 -0
  180. package/dist/themes/data/tokyo-night.js +177 -0
  181. package/dist/themes/data/types.d.ts +31 -0
  182. package/dist/themes/data/types.js +10 -0
  183. package/dist/themes/palette.d.ts +21 -0
  184. package/dist/themes/palette.js +28 -0
  185. package/dist/themes/ramp.d.ts +83 -0
  186. package/dist/themes/ramp.js +122 -0
  187. package/dist/themes/registry.d.ts +44 -0
  188. package/dist/themes/registry.js +94 -0
  189. package/dist/themes/terminalThemes.d.ts +49 -0
  190. package/dist/themes/terminalThemes.js +63 -0
  191. package/dist/themes/transpose.d.ts +87 -0
  192. package/dist/themes/transpose.js +138 -0
  193. package/dist/widgets/button.d.ts +49 -0
  194. package/dist/widgets/button.js +208 -0
  195. package/dist/widgets/checkbox.d.ts +41 -0
  196. package/dist/widgets/checkbox.js +157 -0
  197. package/dist/widgets/dropdown.d.ts +67 -0
  198. package/dist/widgets/dropdown.js +450 -0
  199. package/dist/widgets/event-router.d.ts +71 -0
  200. package/dist/widgets/event-router.js +629 -0
  201. package/dist/widgets/focus-manager.d.ts +45 -0
  202. package/dist/widgets/focus-manager.js +191 -0
  203. package/dist/widgets/hit.d.ts +64 -0
  204. package/dist/widgets/hit.js +113 -0
  205. package/dist/widgets/index.d.ts +20 -0
  206. package/dist/widgets/index.js +14 -0
  207. package/dist/widgets/slider.d.ts +57 -0
  208. package/dist/widgets/slider.js +270 -0
  209. package/dist/widgets/static-item.d.ts +34 -0
  210. package/dist/widgets/static-item.js +54 -0
  211. package/dist/widgets/text-input.d.ts +274 -0
  212. package/dist/widgets/text-input.js +1165 -0
  213. package/dist/widgets/toggle.d.ts +44 -0
  214. package/dist/widgets/toggle.js +180 -0
  215. package/dist/widgets/types.d.ts +87 -0
  216. package/dist/widgets/types.js +30 -0
  217. package/dist/widgets/widget-app.d.ts +63 -0
  218. package/dist/widgets/widget-app.js +199 -0
  219. package/dist/widgets/widget-base.d.ts +40 -0
  220. package/dist/widgets/widget-base.js +165 -0
  221. package/package.json +6 -2
@@ -0,0 +1,1038 @@
1
+ /**
2
+ * Terminal color representation, parsing, downgrading, and ANSI code generation.
3
+ */
4
+ // --- ColorRgba ---
5
+ function hex2(byte) {
6
+ return byte.toString(16).padStart(2, "0");
7
+ }
8
+ // [LAW:one-source-of-truth] The one statement of a colour byte's range: the
9
+ // constructor asserts it and the string parser reports it, from this predicate.
10
+ function isByte(v) {
11
+ return Number.isInteger(v) && v >= 0 && v <= 255;
12
+ }
13
+ function assertChannel(name, v) {
14
+ if (!isByte(v)) {
15
+ throw new RangeError(`ColorRgba.${name} must be an integer in [0, 255]; got ${v}`);
16
+ }
17
+ }
18
+ /**
19
+ * Immutable RGBA color value. Alpha defaults to 1 (fully opaque).
20
+ *
21
+ * [LAW:single-enforcer] The constructor is the sole validation site for
22
+ * channel/alpha invariants. Float-arithmetic callers (HSL roundtrips, blends,
23
+ * lerps) are responsible for `Math.round` + clamp before construction; the
24
+ * constructor throws rather than silently masking out-of-range input.
25
+ */
26
+ export class ColorRgba {
27
+ red;
28
+ green;
29
+ blue;
30
+ alpha;
31
+ constructor(red, green, blue, alpha = 1) {
32
+ this.red = red;
33
+ this.green = green;
34
+ this.blue = blue;
35
+ this.alpha = alpha;
36
+ assertChannel("red", red);
37
+ assertChannel("green", green);
38
+ assertChannel("blue", blue);
39
+ if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
40
+ throw new RangeError(`ColorRgba.alpha must be a finite number in [0, 1]; got ${alpha}`);
41
+ }
42
+ }
43
+ /** 6-char `#RRGGBB` when fully opaque, 8-char `#RRGGBBAA` otherwise. */
44
+ get hex() {
45
+ const rgb = "#" + hex2(this.red) + hex2(this.green) + hex2(this.blue);
46
+ return this.alpha === 1 ? rgb : rgb + hex2(Math.round(this.alpha * 255));
47
+ }
48
+ /** `rgb(r,g,b)` when fully opaque, `rgba(r,g,b,a)` otherwise. */
49
+ get rgb() {
50
+ return this.alpha === 1
51
+ ? `rgb(${this.red},${this.green},${this.blue})`
52
+ : `rgba(${this.red},${this.green},${this.blue},${this.alpha})`;
53
+ }
54
+ /** [r/255, g/255, b/255, alpha] — alpha is already 0..1, no division. */
55
+ get normalized() {
56
+ return [this.red / 255, this.green / 255, this.blue / 255, this.alpha];
57
+ }
58
+ /**
59
+ * Composite this color over an opaque background. Returns a fully opaque
60
+ * ColorRgba (alpha=1). Per-channel linear interpolation by alpha.
61
+ *
62
+ * [LAW:dataflow-not-control-flow] alpha=1 short-circuits to `this`, so
63
+ * callers can invoke this unconditionally; the data decides whether work
64
+ * happens.
65
+ */
66
+ compositeOver(bg) {
67
+ if (this.alpha === 1)
68
+ return this;
69
+ const t = this.alpha;
70
+ return new ColorRgba(Math.round(bg.red + (this.red - bg.red) * t), Math.round(bg.green + (this.green - bg.green) * t), Math.round(bg.blue + (this.blue - bg.blue) * t), 1);
71
+ }
72
+ }
73
+ /**
74
+ * The surface a terminal draws a translucent colour over. A terminal cannot
75
+ * know what lies under its cells, so the SGR writer (`Style.toSgrCodes`), the
76
+ * strip's seam test and the contrast choosers (`themes/colorMath`) all
77
+ * composite over this one colour. [LAW:one-source-of-truth] One constant, so
78
+ * the colour text is chosen against is the colour the writer draws.
79
+ */
80
+ export const SURFACE_BLACK = new ColorRgba(0, 0, 0);
81
+ /**
82
+ * WCAG 2.x relative luminance (0..1) of an opaque color. The single
83
+ * luminance function in the codebase — `contrastFor`, `contrastRatio`, and
84
+ * any caller that needs to reason about readability all funnel through it.
85
+ * [LAW:one-source-of-truth]
86
+ */
87
+ export function relativeLuminance(c) {
88
+ const ch = (v) => {
89
+ const x = v / 255;
90
+ return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
91
+ };
92
+ return 0.2126 * ch(c.red) + 0.7152 * ch(c.green) + 0.0722 * ch(c.blue);
93
+ }
94
+ /**
95
+ * WCAG 2.x contrast ratio between two colors, in [1, 21]. Symmetric — the
96
+ * order of arguments does not matter. 4.5 is the AA threshold for normal
97
+ * text, 3.0 for large text.
98
+ *
99
+ * Assumes opaque inputs: alpha is ignored, since the displayed contrast of a
100
+ * translucent color depends on what it composites over. For a translucent
101
+ * foreground, flatten it first (or use `ensureContrast`, which does).
102
+ */
103
+ export function contrastRatio(a, b) {
104
+ return luminanceRatio(relativeLuminance(a), relativeLuminance(b));
105
+ }
106
+ // [LAW:single-enforcer] The WCAG ratio over two relative luminances — the one
107
+ // formula `contrastRatio` and `ColorTable.matchReadable` (which caches its
108
+ // entries' luminances) both measure with.
109
+ function luminanceRatio(la, lb) {
110
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
111
+ }
112
+ // --- ColorTable ---
113
+ /**
114
+ * An indexed palette: entry `i` of `colors` is terminal index `firstIndex + i`.
115
+ * `firstIndex` lets a table hold only the part of a palette a downgrade may
116
+ * choose (the 256-colour cube and grey ramp start at 16) while every index it
117
+ * reports is the terminal's own.
118
+ */
119
+ // [LAW:single-enforcer] The one size policy for ColorTable's memos: a key is
120
+ // derived from colours a long-running host computes without end (ramp stops,
121
+ // mixes), so an unbounded map grows with every render. Clearing at the cap is
122
+ // the policy `cellLen` already uses; a refill costs one table scan per key.
123
+ const TABLE_CACHE_MAX = 4096;
124
+ function remember(cache, key, index) {
125
+ if (cache.size >= TABLE_CACHE_MAX)
126
+ cache.clear();
127
+ cache.set(key, index);
128
+ return index;
129
+ }
130
+ // [LAW:one-source-of-truth] The one distance every table scan ranks by, so
131
+ // `matchWhere`'s "nearest by the distance `match` uses" cannot drift.
132
+ function rgbDistance(a, b) {
133
+ const dr = a.red - b.red;
134
+ const dg = a.green - b.green;
135
+ const db = a.blue - b.blue;
136
+ return dr * dr + dg * dg + db * db;
137
+ }
138
+ export class ColorTable {
139
+ colors;
140
+ firstIndex;
141
+ matchCache = new Map();
142
+ readableCache = new Map();
143
+ constructor(colors, firstIndex = 0) {
144
+ this.colors = colors;
145
+ this.firstIndex = firstIndex;
146
+ }
147
+ get(index) {
148
+ return this.colors[index - this.firstIndex];
149
+ }
150
+ luminanceCache;
151
+ /** Each entry's relative luminance, computed once per table. */
152
+ luminances() {
153
+ this.luminanceCache ??= this.colors.map(relativeLuminance);
154
+ return this.luminanceCache;
155
+ }
156
+ /** How many entries the table holds (terminal indices `firstIndex`…). */
157
+ get size() {
158
+ return this.colors.length;
159
+ }
160
+ /**
161
+ * Finds the nearest table index to the given color (Euclidean RGB distance, cached).
162
+ * Alpha is part of the cache key so two values that differ only in alpha
163
+ * don't collide, but is not used in the distance metric.
164
+ */
165
+ match(value) {
166
+ const key = `${value.red},${value.green},${value.blue},${value.alpha}`;
167
+ const cached = this.matchCache.get(key);
168
+ if (cached !== undefined)
169
+ return cached;
170
+ let bestIndex = 0;
171
+ let bestDist = Infinity;
172
+ for (let i = 0; i < this.colors.length; i++) {
173
+ const c = this.colors[i];
174
+ const dist = rgbDistance(c, value);
175
+ if (dist < bestDist) {
176
+ bestDist = dist;
177
+ bestIndex = i;
178
+ }
179
+ }
180
+ return remember(this.matchCache, key, this.firstIndex + bestIndex);
181
+ }
182
+ /**
183
+ * The nearest entry to `value` (the distance `match` uses) among those that
184
+ * clear `minRatio` against `on`; when none does, the entry with the most
185
+ * contrast against `on`. Text downgraded on its own background: `match`
186
+ * moves text and background independently, and two independent roundings
187
+ * can meet in the middle, so a pair that read at 4.5:1 in truecolor can
188
+ * draw at 2:1. [LAW:dataflow-not-control-flow] One scan scores every entry;
189
+ * the ratio decides which one wins.
190
+ */
191
+ matchReadable(value, on, minRatio) {
192
+ const key = `${value.red},${value.green},${value.blue}|${on.red},${on.green},${on.blue}|${minRatio}`;
193
+ const cached = this.readableCache.get(key);
194
+ if (cached !== undefined)
195
+ return cached;
196
+ const lOn = relativeLuminance(on);
197
+ let best = 0;
198
+ let bestPasses = false;
199
+ let bestScore = -Infinity;
200
+ for (let i = 0; i < this.colors.length; i++) {
201
+ const c = this.colors[i];
202
+ const lc = this.luminances()[i];
203
+ const ratio = luminanceRatio(lc, lOn);
204
+ const passes = ratio >= minRatio;
205
+ // A passing entry scores by closeness; a failing one only by contrast,
206
+ // and loses to every passing one.
207
+ const score = passes ? -rgbDistance(c, value) : ratio;
208
+ if ((passes && !bestPasses) ||
209
+ (passes === bestPasses && score > bestScore)) {
210
+ best = i;
211
+ bestPasses = passes;
212
+ bestScore = score;
213
+ }
214
+ }
215
+ return remember(this.readableCache, key, this.firstIndex + best);
216
+ }
217
+ /**
218
+ * The nearest entry to `value` (the distance `match` uses) among those
219
+ * `accept` takes, given each entry's colour and terminal index; `undefined`
220
+ * when it takes none. Entries are offered nearest-first (ties to the lower
221
+ * index) and the first taken wins, so an expensive predicate runs only as
222
+ * far out as the answer. Uncached: the predicate is the caller's, and a
223
+ * closure has no key.
224
+ */
225
+ matchWhere(value, accept) {
226
+ const dist = this.colors.map((c) => rgbDistance(c, value));
227
+ const nearestFirst = [...dist.keys()].sort((a, b) => dist[a] - dist[b]);
228
+ const found = nearestFirst.find((i) => accept(this.colors[i], this.firstIndex + i));
229
+ return found === undefined ? undefined : this.firstIndex + found;
230
+ }
231
+ }
232
+ // --- Enums ---
233
+ export var ColorDepth;
234
+ (function (ColorDepth) {
235
+ ColorDepth[ColorDepth["DEFAULT"] = 0] = "DEFAULT";
236
+ ColorDepth[ColorDepth["STANDARD"] = 1] = "STANDARD";
237
+ ColorDepth[ColorDepth["EIGHT_BIT"] = 2] = "EIGHT_BIT";
238
+ ColorDepth[ColorDepth["TRUECOLOR"] = 3] = "TRUECOLOR";
239
+ ColorDepth[ColorDepth["WINDOWS"] = 4] = "WINDOWS";
240
+ })(ColorDepth || (ColorDepth = {}));
241
+ // [LAW:one-source-of-truth] One table covers every recognized string form
242
+ // that resolves to a ColorDepth: user-facing CLI/config specs, FORCE_COLOR
243
+ // values (the chalk/supports-color convention), TERM_PROGRAM identifiers,
244
+ // and exact TERM names whose color capability is known. Keys are disjoint
245
+ // across these sources, so one table is honest. `null` means "no color".
246
+ // "auto" is intentionally absent — it routes to env detection.
247
+ const STRING_TO_DEPTH = {
248
+ // CLI/config specs
249
+ truecolor: ColorDepth.TRUECOLOR,
250
+ "256": ColorDepth.EIGHT_BIT,
251
+ ansi: ColorDepth.STANDARD,
252
+ none: null,
253
+ // FORCE_COLOR values
254
+ "0": null,
255
+ false: null,
256
+ "1": ColorDepth.STANDARD,
257
+ true: ColorDepth.STANDARD,
258
+ "2": ColorDepth.EIGHT_BIT,
259
+ "3": ColorDepth.TRUECOLOR,
260
+ // TERM_PROGRAM identifiers
261
+ "iTerm.app": ColorDepth.TRUECOLOR,
262
+ Apple_Terminal: ColorDepth.EIGHT_BIT,
263
+ vscode: ColorDepth.TRUECOLOR,
264
+ Tabby: ColorDepth.TRUECOLOR,
265
+ // Known truecolor TERM names
266
+ "xterm-kitty": ColorDepth.TRUECOLOR,
267
+ "xterm-ghostty": ColorDepth.TRUECOLOR,
268
+ wezterm: ColorDepth.TRUECOLOR,
269
+ alacritty: ColorDepth.TRUECOLOR,
270
+ foot: ColorDepth.TRUECOLOR,
271
+ contour: ColorDepth.TRUECOLOR,
272
+ };
273
+ /**
274
+ * Detect the terminal's color capability from env + TTY state.
275
+ *
276
+ * Probes (in priority order): NO_COLOR (per https://no-color.org —
277
+ * any non-empty value disables color), FORCE_COLOR (numeric/boolean override),
278
+ * TTY presence, TERM=dumb/unknown, COLORTERM, known terminal/TERM_PROGRAM
279
+ * names, and TERM regex patterns. Returns `null` for "no color".
280
+ *
281
+ * [LAW:dataflow-not-control-flow] Same probes run every call; variability is
282
+ * in the env values, not in which checks execute.
283
+ */
284
+ export function detectColorSystem(options = {}) {
285
+ // NO_COLOR and FORCE_COLOR are preferences about colour, not facts about the
286
+ // terminal, so they sit outside the probe. NO_COLOR (any non-empty value)
287
+ // beats FORCE_COLOR; FORCE_COLOR beats TTY/TERM detection.
288
+ const noColor = envOf(options)["NO_COLOR"];
289
+ if (noColor !== undefined && noColor !== "")
290
+ return null;
291
+ const forced = forcedColorSystem(envOf(options));
292
+ return forced !== undefined ? forced : terminalColorSystem(options);
293
+ }
294
+ // FORCE_COLOR's depth: `undefined` when unset, `null` for its off values.
295
+ function forcedColorSystem(env) {
296
+ const force = env["FORCE_COLOR"];
297
+ if (force === undefined || force === "")
298
+ return undefined;
299
+ const mapped = STRING_TO_DEPTH[force];
300
+ return mapped !== undefined ? mapped : ColorDepth.STANDARD;
301
+ }
302
+ function envOf(options) {
303
+ return options.env ?? (typeof process !== "undefined" ? process.env : {});
304
+ }
305
+ // What the destination can draw, preferences aside: TTY presence,
306
+ // TERM=dumb/unknown, COLORTERM, known terminal names, TERM patterns. `null`
307
+ // here means the destination takes no escapes at all.
308
+ function terminalColorSystem(options) {
309
+ const env = envOf(options);
310
+ const isTTY = options.isTTY ??
311
+ (typeof process !== "undefined" ? (process.stdout?.isTTY ?? false) : false);
312
+ if (!isTTY)
313
+ return null;
314
+ const term = env["TERM"] ?? "";
315
+ if (term === "dumb" || term === "unknown")
316
+ return null;
317
+ const colorterm = env["COLORTERM"];
318
+ if (colorterm === "truecolor" || colorterm === "24bit") {
319
+ return ColorDepth.TRUECOLOR;
320
+ }
321
+ const termDepth = STRING_TO_DEPTH[term];
322
+ if (termDepth !== undefined)
323
+ return termDepth;
324
+ const termProgram = env["TERM_PROGRAM"];
325
+ if (termProgram !== undefined) {
326
+ const mapped = STRING_TO_DEPTH[termProgram];
327
+ if (mapped !== undefined)
328
+ return mapped;
329
+ }
330
+ if (/-256(color)?$/i.test(term))
331
+ return ColorDepth.EIGHT_BIT;
332
+ if (/-truecolor$/i.test(term))
333
+ return ColorDepth.TRUECOLOR;
334
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(term)) {
335
+ return ColorDepth.STANDARD;
336
+ }
337
+ if (colorterm !== undefined && colorterm !== "")
338
+ return ColorDepth.STANDARD;
339
+ // Default: a terminal exists (isTTY=true) but we couldn't classify it.
340
+ // Assume safe baseline rather than disabling color.
341
+ return ColorDepth.STANDARD;
342
+ }
343
+ /**
344
+ * Resolve a colour spec into a `Destination`.
345
+ *
346
+ * [LAW:single-enforcer] The one place a spec becomes both facts. A hyperlink is
347
+ * not a colour: an explicit depth — `"none"` and `null` included — states a
348
+ * colour choice and keeps links, and under `"auto"` links follow what the
349
+ * terminal can take (no TTY, TERM=dumb: none) while NO_COLOR and FORCE_COLOR=0,
350
+ * colour preferences, leave them alone. A FORCE_COLOR that forces colour on
351
+ * declares the destination takes escapes, so it keeps links on a pipe too.
352
+ */
353
+ export function resolveDestination(spec, options) {
354
+ if (spec !== "auto") {
355
+ return {
356
+ colorSystem: typeof spec === "string" ? resolveColorSystem(spec) : spec,
357
+ hyperlinks: true,
358
+ };
359
+ }
360
+ return {
361
+ colorSystem: detectColorSystem(options),
362
+ hyperlinks: (forcedColorSystem(envOf(options ?? {})) ?? null) !== null ||
363
+ terminalColorSystem(options ?? {}) !== null,
364
+ };
365
+ }
366
+ /**
367
+ * Resolve a string spec into a `ColorDepth` (or `null` for no color).
368
+ *
369
+ * `"auto"` triggers env-based detection; all other recognized specs are
370
+ * direct table lookups against `STRING_TO_DEPTH`. Throws on unknown specs
371
+ * — silent fallback would mask user typos.
372
+ *
373
+ * [LAW:single-enforcer] All string→ColorDepth resolution flows through here.
374
+ */
375
+ export function resolveColorSystem(spec, options) {
376
+ if (spec === "auto")
377
+ return detectColorSystem(options);
378
+ if (Object.hasOwn(STRING_TO_DEPTH, spec))
379
+ return STRING_TO_DEPTH[spec];
380
+ throw new ColorParseError(`Unknown color depth spec: ${JSON.stringify(spec)} (expected "auto", "truecolor", "256", "ansi", or "none")`);
381
+ }
382
+ // --- ColorSpec ---
383
+ export class ColorParseError extends Error {
384
+ constructor(message) {
385
+ super(message);
386
+ this.name = "ColorParseError";
387
+ }
388
+ }
389
+ // [LAW:one-source-of-truth] Parse cache is the single source for parsed ColorSpec instances
390
+ const parseCache = new Map();
391
+ export class ColorSpec {
392
+ name;
393
+ type;
394
+ number;
395
+ value;
396
+ downgradeCache = new Map();
397
+ constructor(name, type, number, value) {
398
+ this.name = name;
399
+ this.type = type;
400
+ this.number = number;
401
+ this.value = value;
402
+ }
403
+ get isDefault() {
404
+ return this.type === ColorDepth.DEFAULT;
405
+ }
406
+ /**
407
+ * The colour every terminal draws this spec as, where that is fixed — before
408
+ * any alpha is flattened, so a translucent value is drawn composited over
409
+ * its ground (`flattenAlpha`) and should be measured that way: a
410
+ * truecolor value, or a 256-colour cube or grey-ramp entry (xterm fixes
411
+ * indices 16–255, and `fromAnsi` types only those as EIGHT_BIT). ANSI 0–15
412
+ * and the default colour are the terminal theme's own, so they have none.
413
+ */
414
+ get fixedValue() {
415
+ switch (this.type) {
416
+ case ColorDepth.TRUECOLOR:
417
+ return this.value;
418
+ case ColorDepth.EIGHT_BIT:
419
+ // The constructor admits an EIGHT_BIT spec at 0–15; those are the
420
+ // theme's own slots whatever depth names them.
421
+ return this.number < 16 ? undefined : EIGHT_BIT_TABLE.get(this.number);
422
+ case ColorDepth.DEFAULT:
423
+ case ColorDepth.STANDARD:
424
+ case ColorDepth.WINDOWS:
425
+ return undefined;
426
+ }
427
+ }
428
+ get isSystemDefined() {
429
+ return (this.type === ColorDepth.STANDARD || this.type === ColorDepth.WINDOWS);
430
+ }
431
+ /**
432
+ * Generates SGR parameter strings for this color.
433
+ */
434
+ getAnsiCodes(foreground = true) {
435
+ switch (this.type) {
436
+ case ColorDepth.DEFAULT:
437
+ return [foreground ? "39" : "49"];
438
+ case ColorDepth.STANDARD: {
439
+ const n = this.number;
440
+ if (foreground) {
441
+ return n < 8 ? [`${30 + n}`] : [`${90 + n - 8}`];
442
+ }
443
+ return n < 8 ? [`${40 + n}`] : [`${100 + n - 8}`];
444
+ }
445
+ case ColorDepth.EIGHT_BIT:
446
+ return [foreground ? "38" : "48", "5", `${this.number}`];
447
+ case ColorDepth.TRUECOLOR: {
448
+ const t = this.value;
449
+ return [
450
+ foreground ? "38" : "48",
451
+ "2",
452
+ `${t.red}`,
453
+ `${t.green}`,
454
+ `${t.blue}`,
455
+ ];
456
+ }
457
+ case ColorDepth.WINDOWS: {
458
+ const n = this.number;
459
+ if (foreground) {
460
+ return n < 8 ? [`${30 + n}`] : [`${90 + n - 8}`];
461
+ }
462
+ return n < 8 ? [`${40 + n}`] : [`${100 + n - 8}`];
463
+ }
464
+ }
465
+ }
466
+ /**
467
+ * Composite this color's alpha (if any) against an opaque background,
468
+ * returning a fully-opaque ColorSpec. Non-truecolor specs (palette
469
+ * indices, default) have no alpha and pass through unchanged.
470
+ *
471
+ * [LAW:single-enforcer] Sole alpha-flattening site for the render
472
+ * pipeline. Run *before* downgrade — otherwise palette matching on the
473
+ * still-translucent RGB silently drops alpha (the "flatten then lose
474
+ * alpha" path the unification ticket forbids).
475
+ *
476
+ * [LAW:dataflow-not-control-flow] `ColorRgba.compositeOver` is a no-op
477
+ * when alpha=1, so callers invoke this unconditionally; the data
478
+ * (alpha value) decides whether work happens.
479
+ */
480
+ flattenAlpha(bg) {
481
+ if (this.type !== ColorDepth.TRUECOLOR || !this.value)
482
+ return this;
483
+ const flat = this.value.compositeOver(bg);
484
+ return flat === this.value ? this : ColorSpec.fromRgba(flat);
485
+ }
486
+ /**
487
+ * Downgrade to a lower-fidelity color depth. Cached.
488
+ */
489
+ downgrade(targetSystem) {
490
+ if (this.type === ColorDepth.DEFAULT)
491
+ return this;
492
+ if (this.type <= targetSystem)
493
+ return this;
494
+ const cached = this.downgradeCache.get(targetSystem);
495
+ if (cached)
496
+ return cached;
497
+ const result = this.performDowngrade(targetSystem);
498
+ this.downgradeCache.set(targetSystem, result);
499
+ return result;
500
+ }
501
+ /**
502
+ * Resolves to an actual RGB(A) value for any color type.
503
+ */
504
+ getTruecolor(theme, foreground = true) {
505
+ switch (this.type) {
506
+ case ColorDepth.TRUECOLOR:
507
+ return this.value;
508
+ case ColorDepth.EIGHT_BIT:
509
+ // Slots 0–15 are the theme's own whatever depth names them, as `fixedValue` says.
510
+ return this.number < 16 ? (theme ?? INTERNAL_DEFAULT_THEME).ansiColors.get(this.number) : EIGHT_BIT_TABLE.get(this.number);
511
+ case ColorDepth.STANDARD: {
512
+ const t = theme ?? INTERNAL_DEFAULT_THEME;
513
+ return t.ansiColors.get(this.number);
514
+ }
515
+ case ColorDepth.DEFAULT: {
516
+ const t = theme ?? INTERNAL_DEFAULT_THEME;
517
+ return foreground ? t.foregroundColor : t.backgroundColor;
518
+ }
519
+ case ColorDepth.WINDOWS: {
520
+ const t = theme ?? INTERNAL_DEFAULT_THEME;
521
+ return t.ansiColors.get(this.number);
522
+ }
523
+ }
524
+ }
525
+ // --- Static factories ---
526
+ static default() {
527
+ return new ColorSpec("default", ColorDepth.DEFAULT);
528
+ }
529
+ static fromAnsi(n) {
530
+ const type = n < 16 ? ColorDepth.STANDARD : ColorDepth.EIGHT_BIT;
531
+ return new ColorSpec(`color(${n})`, type, n);
532
+ }
533
+ static fromRgba(value) {
534
+ return new ColorSpec(value.hex, ColorDepth.TRUECOLOR, undefined, value);
535
+ }
536
+ static fromRgb(r, g, b) {
537
+ return ColorSpec.fromRgba(new ColorRgba(r, g, b));
538
+ }
539
+ /**
540
+ * Parse a color string. Cached — identical strings return the same instance.
541
+ */
542
+ static parse(colorString) {
543
+ const key = colorString.toLowerCase().trim();
544
+ const cached = parseCache.get(key);
545
+ if (cached)
546
+ return cached;
547
+ const result = parseSingle(key);
548
+ parseCache.set(key, result);
549
+ return result;
550
+ }
551
+ // --- private ---
552
+ performDowngrade(targetSystem) {
553
+ // Get the true RGB of this color
554
+ const triplet = this.getTruecolor();
555
+ switch (targetSystem) {
556
+ case ColorDepth.EIGHT_BIT: {
557
+ const index = EIGHT_BIT_DOWNGRADE_TABLE.match(triplet);
558
+ return ColorSpec.fromAnsi(index);
559
+ }
560
+ case ColorDepth.STANDARD: {
561
+ const index = STANDARD_TABLE.match(triplet);
562
+ return new ColorSpec(`color(${index})`, ColorDepth.STANDARD, index);
563
+ }
564
+ // WINDOWS is a detection result (max enum value), not a downgrade target.
565
+ // The `this.type <= targetSystem` guard in downgrade() always short-circuits
566
+ // before reaching this method with WINDOWS, so this case is unreachable.
567
+ case ColorDepth.WINDOWS:
568
+ case ColorDepth.TRUECOLOR:
569
+ return this;
570
+ case ColorDepth.DEFAULT:
571
+ return ColorSpec.default();
572
+ }
573
+ }
574
+ }
575
+ // --- Parsing internals ---
576
+ const HEX_RE = /^#([0-9a-f]{6})$/;
577
+ const HEX_RGBA_RE = /^#([0-9a-f]{8})$/;
578
+ const RGB_RE = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/;
579
+ const COLOR_NUMBER_RE = /^color\((\d+)\)$/;
580
+ function parseSingle(key) {
581
+ if (key === "default" || key === "") {
582
+ return ColorSpec.default();
583
+ }
584
+ // Named color
585
+ const namedIndex = ANSI_COLOR_NAMES[key];
586
+ if (namedIndex !== undefined) {
587
+ const type = namedIndex < 16 ? ColorDepth.STANDARD : ColorDepth.EIGHT_BIT;
588
+ return new ColorSpec(key, type, namedIndex);
589
+ }
590
+ // Hex (8-char with alpha takes precedence over 6-char to avoid the 6-char
591
+ // regex matching a prefix of an 8-char string).
592
+ const hexRgbaMatch = HEX_RGBA_RE.exec(key);
593
+ if (hexRgbaMatch) {
594
+ return new ColorSpec(key, ColorDepth.TRUECOLOR, undefined, parseRgbaHex(hexRgbaMatch[1]));
595
+ }
596
+ const hexMatch = HEX_RE.exec(key);
597
+ if (hexMatch) {
598
+ return new ColorSpec(key, ColorDepth.TRUECOLOR, undefined, parseRgbHex(hexMatch[1]));
599
+ }
600
+ // rgb()
601
+ const rgbMatch = RGB_RE.exec(key);
602
+ if (rgbMatch) {
603
+ const r = parseByte(key, rgbMatch[1], "red");
604
+ const g = parseByte(key, rgbMatch[2], "green");
605
+ const b = parseByte(key, rgbMatch[3], "blue");
606
+ return new ColorSpec(key, ColorDepth.TRUECOLOR, undefined, new ColorRgba(r, g, b));
607
+ }
608
+ // color(N)
609
+ const numMatch = COLOR_NUMBER_RE.exec(key);
610
+ if (numMatch) {
611
+ const n = parseByte(key, numMatch[1], "number");
612
+ const type = n < 16 ? ColorDepth.STANDARD : ColorDepth.EIGHT_BIT;
613
+ return new ColorSpec(key, type, n);
614
+ }
615
+ throw new ColorParseError(`Failed to parse color: "${key}"`);
616
+ }
617
+ // [LAW:single-enforcer] The regexes admit any run of digits and leave the
618
+ // range to this check alone, so every out-of-range byte is a ColorParseError
619
+ // naming the field, never the generic parse failure or the constructor's RangeError.
620
+ function parseByte(key, digits, field) {
621
+ const n = parseInt(digits, 10);
622
+ if (!isByte(n)) {
623
+ throw new ColorParseError(`ColorSpec "${key}": ${field} ${digits} is out of range (0-255)`);
624
+ }
625
+ return n;
626
+ }
627
+ // --- Utility functions ---
628
+ export function parseRgbHex(hex) {
629
+ const r = parseInt(hex.slice(0, 2), 16);
630
+ const g = parseInt(hex.slice(2, 4), 16);
631
+ const b = parseInt(hex.slice(4, 6), 16);
632
+ return new ColorRgba(r, g, b);
633
+ }
634
+ export function parseRgbaHex(hex) {
635
+ const r = parseInt(hex.slice(0, 2), 16);
636
+ const g = parseInt(hex.slice(2, 4), 16);
637
+ const b = parseInt(hex.slice(4, 6), 16);
638
+ const a = parseInt(hex.slice(6, 8), 16) / 255;
639
+ return new ColorRgba(r, g, b, a);
640
+ }
641
+ export function blendRgb(color1, color2, crossFade = 0.5) {
642
+ return new ColorRgba(Math.round(color1.red + (color2.red - color1.red) * crossFade), Math.round(color1.green + (color2.green - color1.green) * crossFade), Math.round(color1.blue + (color2.blue - color1.blue) * crossFade), color1.alpha + (color2.alpha - color1.alpha) * crossFade);
643
+ }
644
+ // --- TerminalTheme ---
645
+ /**
646
+ * A terminal theme — surface/foreground baseline, the ANSI 16/256 LUT, and a
647
+ * semantic palette.
648
+ *
649
+ * **`ansiColors` is the theme's own sixteen colours**, the shades a terminal
650
+ * showing this theme draws `red`, `blue` and the rest in. `ColorSpec.parse("red")`
651
+ * is ANSI colour 1 under every theme; which red that is, is the theme's to say,
652
+ * as it is in Rich, where each `TerminalTheme` carries its own table.
653
+ */
654
+ export class TerminalTheme {
655
+ backgroundColor;
656
+ foregroundColor;
657
+ ansiColors;
658
+ palette;
659
+ constructor(backgroundColor, foregroundColor, ansiColors, palette) {
660
+ this.backgroundColor = backgroundColor;
661
+ this.foregroundColor = foregroundColor;
662
+ this.ansiColors = ansiColors;
663
+ this.palette = palette;
664
+ }
665
+ }
666
+ // --- ColorTable data ---
667
+ function buildStandard16() {
668
+ return [
669
+ new ColorRgba(0, 0, 0), // 0 black
670
+ new ColorRgba(128, 0, 0), // 1 red
671
+ new ColorRgba(0, 128, 0), // 2 green
672
+ new ColorRgba(128, 128, 0), // 3 yellow
673
+ new ColorRgba(0, 0, 128), // 4 blue
674
+ new ColorRgba(128, 0, 128), // 5 magenta
675
+ new ColorRgba(0, 128, 128), // 6 cyan
676
+ new ColorRgba(192, 192, 192), // 7 white
677
+ new ColorRgba(128, 128, 128), // 8 bright_black
678
+ new ColorRgba(255, 0, 0), // 9 bright_red
679
+ new ColorRgba(0, 255, 0), // 10 bright_green
680
+ new ColorRgba(255, 255, 0), // 11 bright_yellow
681
+ new ColorRgba(0, 0, 255), // 12 bright_blue
682
+ new ColorRgba(255, 0, 255), // 13 bright_magenta
683
+ new ColorRgba(0, 255, 255), // 14 bright_cyan
684
+ new ColorRgba(255, 255, 255), // 15 bright_white
685
+ ];
686
+ }
687
+ function build256Table() {
688
+ const colors = buildStandard16();
689
+ // 6x6x6 color cube (indices 16-231)
690
+ const levels = [0, 95, 135, 175, 215, 255];
691
+ for (let r = 0; r < 6; r++) {
692
+ for (let g = 0; g < 6; g++) {
693
+ for (let b = 0; b < 6; b++) {
694
+ colors.push(new ColorRgba(levels[r], levels[g], levels[b]));
695
+ }
696
+ }
697
+ }
698
+ // Grayscale ramp (indices 232-255)
699
+ for (let i = 0; i < 24; i++) {
700
+ const grey = 8 + 10 * i;
701
+ colors.push(new ColorRgba(grey, grey, grey));
702
+ }
703
+ return colors;
704
+ }
705
+ function buildWindowsTable() {
706
+ return [
707
+ new ColorRgba(0, 0, 0), // 0
708
+ new ColorRgba(0, 0, 128), // 1
709
+ new ColorRgba(0, 128, 0), // 2
710
+ new ColorRgba(0, 128, 128), // 3
711
+ new ColorRgba(128, 0, 0), // 4
712
+ new ColorRgba(128, 0, 128), // 5
713
+ new ColorRgba(128, 128, 0), // 6
714
+ new ColorRgba(192, 192, 192), // 7
715
+ new ColorRgba(128, 128, 128), // 8
716
+ new ColorRgba(0, 0, 255), // 9
717
+ new ColorRgba(0, 255, 0), // 10
718
+ new ColorRgba(0, 255, 255), // 11
719
+ new ColorRgba(255, 0, 0), // 12
720
+ new ColorRgba(255, 0, 255), // 13
721
+ new ColorRgba(255, 255, 0), // 14
722
+ new ColorRgba(255, 255, 255), // 15
723
+ ];
724
+ }
725
+ export const STANDARD_TABLE = new ColorTable(buildStandard16());
726
+ export const EIGHT_BIT_TABLE = new ColorTable(build256Table());
727
+ /**
728
+ * What a downgrade to 256 colours may choose: the cube and the grey ramp,
729
+ * indices 16–255. Indices 0–15 are the terminal's own ANSI colours, which
730
+ * every theme redefines, so their RGB is unknown and a match against them is
731
+ * a guess; Python Rich never picks them either.
732
+ */
733
+ export const EIGHT_BIT_DOWNGRADE_TABLE = new ColorTable(build256Table().slice(16), 16);
734
+ export const WINDOWS_TABLE = new ColorTable(buildWindowsTable());
735
+ // --- Internal fallback theme ---
736
+ //
737
+ // [LAW:one-way-deps] Preset themes (DEFAULT_TERMINAL_THEME, MONOKAI, NORD, ...)
738
+ // live in `src/themes/terminalThemes.ts` because they need `buildPalette` from
739
+ // `themes/`. Keeping them out of `core/color.ts` makes the dependency strictly
740
+ // `themes/* -> core/color`, with no runtime back-edge.
741
+ //
742
+ // `getTruecolor()` retains a fallback for callers that omit `theme`. The
743
+ // fallback uses STANDARD_TABLE + black/white + an empty Palette — sufficient
744
+ // for STANDARD/DEFAULT/WINDOWS lookups, which never read `palette`.
745
+ //
746
+ // Runtime-importing `Palette` from `themes/palette.ts` is safe: that module
747
+ // only `import type`s from `core/color.ts`, so there is no runtime cycle.
748
+ import { Palette } from "../themes/palette.js";
749
+ const INTERNAL_DEFAULT_THEME = new TerminalTheme(new ColorRgba(0, 0, 0), new ColorRgba(255, 255, 255), STANDARD_TABLE, new Palette("default", true, new Map()));
750
+ // --- ANSI ColorSpec Names ---
751
+ // [LAW:one-source-of-truth] Single canonical mapping from name → palette index
752
+ /**
753
+ * The table with every `greyNN` name repeated under the `grayNN` spelling.
754
+ *
755
+ * Rich accepts both spellings and the xterm palette only defines one, so the
756
+ * aliases are part of what this table *is* rather than a patch applied to it.
757
+ *
758
+ * [LAW:no-ambient-temporal-coupling] Which is why they arrive as an initializer
759
+ * and not as the loop that used to mutate the finished map: that loop made "has
760
+ * the aliases yet" a fact about module evaluation order, so the table had two
761
+ * shapes and nothing in its type said which one you were holding. It also made
762
+ * this module do work at import time, which is the claim `package.json`'s
763
+ * `sideEffects: false` makes on its behalf — see
764
+ * `test/seam/import-time-effects.ts`.
765
+ */
766
+ function withGrayAliases(names) {
767
+ const aliases = Object.entries(names)
768
+ .filter(([name]) => name.includes("grey"))
769
+ .map(([name, index]) => [name.replace("grey", "gray"), index]);
770
+ return { ...names, ...Object.fromEntries(aliases) };
771
+ }
772
+ export const ANSI_COLOR_NAMES = withGrayAliases({
773
+ // Standard 16
774
+ black: 0,
775
+ red: 1,
776
+ green: 2,
777
+ yellow: 3,
778
+ blue: 4,
779
+ magenta: 5,
780
+ cyan: 6,
781
+ white: 7,
782
+ bright_black: 8,
783
+ bright_red: 9,
784
+ bright_green: 10,
785
+ bright_yellow: 11,
786
+ bright_blue: 12,
787
+ bright_magenta: 13,
788
+ bright_cyan: 14,
789
+ bright_white: 15,
790
+ // 6x6x6 color cube (16-231)
791
+ grey0: 16,
792
+ navy_blue: 17,
793
+ dark_blue: 18,
794
+ blue3: 19,
795
+ blue2: 20,
796
+ blue1: 21,
797
+ dark_green: 22,
798
+ deep_sky_blue4: 23,
799
+ deep_sky_blue5: 24,
800
+ deep_sky_blue6: 25,
801
+ dodger_blue3: 26,
802
+ dodger_blue2: 27,
803
+ green4: 28,
804
+ spring_green4: 29,
805
+ turquoise4: 30,
806
+ deep_sky_blue3: 31,
807
+ deep_sky_blue7: 32,
808
+ dodger_blue1: 33,
809
+ green3: 34,
810
+ spring_green3: 35,
811
+ dark_cyan: 36,
812
+ light_sea_green: 37,
813
+ deep_sky_blue2: 38,
814
+ deep_sky_blue1: 39,
815
+ green5: 40,
816
+ spring_green5: 41,
817
+ spring_green2: 42,
818
+ cyan3: 43,
819
+ dark_turquoise: 44,
820
+ turquoise2: 45,
821
+ green1: 46,
822
+ spring_green6: 47,
823
+ spring_green1: 48,
824
+ medium_spring_green: 49,
825
+ cyan2: 50,
826
+ cyan1: 51,
827
+ dark_red: 52,
828
+ deep_pink4: 53,
829
+ purple4: 54,
830
+ purple5: 55,
831
+ purple3: 56,
832
+ blue_violet: 57,
833
+ orange4: 58,
834
+ grey37: 59,
835
+ medium_purple4: 60,
836
+ slate_blue3: 61,
837
+ slate_blue4: 62,
838
+ royal_blue1: 63,
839
+ chartreuse4: 64,
840
+ dark_sea_green4: 65,
841
+ pale_turquoise4: 66,
842
+ steel_blue: 67,
843
+ steel_blue3: 68,
844
+ cornflower_blue: 69,
845
+ chartreuse3: 70,
846
+ dark_sea_green5: 71,
847
+ cadet_blue: 72,
848
+ cadet_blue2: 73,
849
+ sky_blue3: 74,
850
+ steel_blue1: 75,
851
+ chartreuse5: 76,
852
+ pale_green3: 77,
853
+ sea_green3: 78,
854
+ aquamarine3: 79,
855
+ medium_turquoise: 80,
856
+ steel_blue2: 81,
857
+ chartreuse2: 82,
858
+ sea_green2: 83,
859
+ sea_green1: 84,
860
+ sea_green4: 85,
861
+ aquamarine1: 86,
862
+ dark_slate_gray2: 87,
863
+ dark_red2: 88,
864
+ deep_pink5: 89,
865
+ dark_magenta: 90,
866
+ dark_magenta2: 91,
867
+ dark_violet: 92,
868
+ purple2: 93,
869
+ orange5: 94,
870
+ light_pink4: 95,
871
+ plum4: 96,
872
+ medium_purple3: 97,
873
+ medium_purple5: 98,
874
+ slate_blue1: 99,
875
+ yellow4: 100,
876
+ wheat4: 101,
877
+ grey53: 102,
878
+ light_slate_grey: 103,
879
+ medium_purple: 104,
880
+ light_slate_blue: 105,
881
+ yellow5: 106,
882
+ dark_olive_green3: 107,
883
+ dark_sea_green: 108,
884
+ light_sky_blue3: 109,
885
+ light_sky_blue4: 110,
886
+ sky_blue2: 111,
887
+ chartreuse6: 112,
888
+ dark_olive_green4: 113,
889
+ pale_green4: 114,
890
+ dark_sea_green3: 115,
891
+ dark_slate_gray3: 116,
892
+ sky_blue1: 117,
893
+ chartreuse1: 118,
894
+ light_green: 119,
895
+ light_green2: 120,
896
+ pale_green1: 121,
897
+ aquamarine2: 122,
898
+ dark_slate_gray1: 123,
899
+ red3: 124,
900
+ deep_pink6: 125,
901
+ medium_violet_red: 126,
902
+ magenta3: 127,
903
+ dark_violet2: 128,
904
+ purple: 129,
905
+ dark_orange3: 130,
906
+ indian_red: 131,
907
+ hot_pink3: 132,
908
+ medium_orchid3: 133,
909
+ medium_orchid: 134,
910
+ medium_purple2: 135,
911
+ dark_goldenrod: 136,
912
+ light_salmon3: 137,
913
+ rosy_brown: 138,
914
+ grey63: 139,
915
+ medium_purple6: 140,
916
+ medium_purple1: 141,
917
+ gold3: 142,
918
+ dark_khaki: 143,
919
+ navajo_white3: 144,
920
+ grey69: 145,
921
+ light_steel_blue3: 146,
922
+ light_steel_blue: 147,
923
+ yellow3: 148,
924
+ dark_olive_green5: 149,
925
+ dark_sea_green6: 150,
926
+ dark_sea_green2: 151,
927
+ light_cyan3: 152,
928
+ light_sky_blue1: 153,
929
+ green_yellow: 154,
930
+ dark_olive_green2: 155,
931
+ pale_green2: 156,
932
+ dark_sea_green7: 157,
933
+ dark_sea_green1: 158,
934
+ pale_turquoise1: 159,
935
+ red4: 160,
936
+ deep_pink3: 161,
937
+ deep_pink8: 162,
938
+ magenta4: 163,
939
+ magenta5: 164,
940
+ magenta2: 165,
941
+ dark_orange4: 166,
942
+ indian_red2: 167,
943
+ hot_pink4: 168,
944
+ hot_pink2: 169,
945
+ orchid: 170,
946
+ medium_orchid1: 171,
947
+ orange3: 172,
948
+ light_salmon4: 173,
949
+ light_pink3: 174,
950
+ pink3: 175,
951
+ plum3: 176,
952
+ violet: 177,
953
+ gold4: 178,
954
+ light_goldenrod3: 179,
955
+ tan: 180,
956
+ misty_rose3: 181,
957
+ thistle3: 182,
958
+ plum2: 183,
959
+ yellow6: 184,
960
+ khaki3: 185,
961
+ light_goldenrod2: 186,
962
+ light_yellow3: 187,
963
+ grey84: 188,
964
+ light_steel_blue1: 189,
965
+ yellow2: 190,
966
+ dark_olive_green1: 191,
967
+ dark_olive_green6: 192,
968
+ dark_sea_green8: 193,
969
+ honeydew2: 194,
970
+ light_cyan1: 195,
971
+ red1: 196,
972
+ deep_pink2: 197,
973
+ deep_pink1: 198,
974
+ deep_pink9: 199,
975
+ magenta6: 200,
976
+ magenta1: 201,
977
+ orange_red1: 202,
978
+ indian_red1: 203,
979
+ indian_red3: 204,
980
+ hot_pink5: 205,
981
+ hot_pink: 206,
982
+ medium_orchid2: 207,
983
+ dark_orange: 208,
984
+ salmon1: 209,
985
+ light_coral: 210,
986
+ pale_violet_red1: 211,
987
+ orchid2: 212,
988
+ orchid1: 213,
989
+ orange1: 214,
990
+ sandy_brown: 215,
991
+ light_salmon1: 216,
992
+ light_pink1: 217,
993
+ pink1: 218,
994
+ plum1: 219,
995
+ gold1: 220,
996
+ light_goldenrod4: 221,
997
+ light_goldenrod5: 222,
998
+ navajo_white1: 223,
999
+ misty_rose1: 224,
1000
+ thistle1: 225,
1001
+ yellow1: 226,
1002
+ light_goldenrod1: 227,
1003
+ khaki1: 228,
1004
+ wheat1: 229,
1005
+ cornsilk1: 230,
1006
+ grey100: 231,
1007
+ // Grayscale ramp (232-255)
1008
+ grey3: 232,
1009
+ grey7: 233,
1010
+ grey11: 234,
1011
+ grey15: 235,
1012
+ grey19: 236,
1013
+ grey23: 237,
1014
+ grey27: 238,
1015
+ grey30: 239,
1016
+ grey35: 240,
1017
+ grey39: 241,
1018
+ grey42: 242,
1019
+ grey46: 243,
1020
+ grey50: 244,
1021
+ grey54: 245,
1022
+ grey58: 246,
1023
+ grey62: 247,
1024
+ grey66: 248,
1025
+ grey70: 249,
1026
+ grey74: 250,
1027
+ grey78: 251,
1028
+ grey82: 252,
1029
+ grey85: 253,
1030
+ grey89: 254,
1031
+ grey93: 255,
1032
+ });
1033
+ /**
1034
+ * Every word `ColorSpec.parse` accepts as a name rather than a `#hex`,
1035
+ * `rgb()` or `color(N)` form: `default` and the named palette entries, which
1036
+ * are exactly the two name checks `parseSingle` makes.
1037
+ */
1038
+ export const COLOR_NAMES = ["default", ...Object.keys(ANSI_COLOR_NAMES)];