@alchemy.run/sigil 0.0.0-alpha.1 → 0.0.0-alpha.11

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 (223) hide show
  1. package/README.md +499 -313
  2. package/THIRD_PARTY_NOTICES.md +98 -23
  3. package/dist/Text-DV9CuzAT.d.ts +452 -0
  4. package/dist/ansi.d.ts +217 -0
  5. package/dist/ansi.js +87 -0
  6. package/dist/capabilities.d.ts +5 -0
  7. package/dist/capabilities.js +3 -0
  8. package/dist/cell-_ZVhbfl0.js +44 -0
  9. package/dist/color-CkbalRqK.js +2 -0
  10. package/dist/color-policy-BAC9-TZX.js +592 -0
  11. package/dist/color-policy-CCxuHIdD.d.ts +22 -0
  12. package/dist/color-profile-CyeHnG1T.d.ts +97 -0
  13. package/dist/color-profile-DHhQHY55.js +36 -0
  14. package/dist/color.d.ts +21 -0
  15. package/dist/color.js +3 -0
  16. package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
  17. package/dist/detect-B3dL4Q11.js +374 -0
  18. package/dist/detect-Db6GbKOm.d.ts +195 -0
  19. package/dist/{devtools-QpCMm9JH.mjs → devtools-DbthxoD1.js} +23 -24
  20. package/dist/env-YVw64yZS.js +9 -0
  21. package/dist/escapes-CB_6CWOE.d.ts +72 -0
  22. package/dist/geometry-BxXOzJgo.d.ts +11 -0
  23. package/dist/index-D48vQhhe.d.ts +21 -0
  24. package/dist/index-DDVME65c.d.ts +919 -0
  25. package/dist/index.d.ts +1310 -0
  26. package/dist/index.js +3211 -0
  27. package/dist/jsx-dev-runtime.d.ts +2 -0
  28. package/dist/jsx-dev-runtime.js +227 -0
  29. package/dist/jsx-runtime-XtwIVFsg.js +255 -0
  30. package/dist/jsx-runtime.d.ts +2 -0
  31. package/dist/jsx-runtime.js +8 -0
  32. package/dist/osc-BFKKSqpg.js +71 -0
  33. package/dist/osc-Cn0fw77g.d.ts +23 -0
  34. package/dist/paint-Cx-zC_sX.d.ts +81 -0
  35. package/dist/query-BNc2B8GD.d.ts +152 -0
  36. package/dist/react-CTZ_8dwh.js +1197 -0
  37. package/dist/react.d.ts +2 -0
  38. package/dist/react.js +49 -0
  39. package/dist/router.d.ts +392 -0
  40. package/dist/router.js +711 -0
  41. package/dist/sample-Cqw1bjUL.js +445 -0
  42. package/dist/screen-CgC2WlVM.d.ts +49 -0
  43. package/dist/screen-CiPytswf.js +342 -0
  44. package/dist/screen.d.ts +5 -0
  45. package/dist/screen.js +5 -0
  46. package/dist/semantic-text-style-DIMzC7xt.js +91 -0
  47. package/dist/serialize-BTkAZgw1.js +79 -0
  48. package/dist/session-DDQ5V300.js +723 -0
  49. package/dist/sgr-BhwaWAJB.js +246 -0
  50. package/dist/store-C1P5fOUi.d.ts +72 -0
  51. package/dist/string-width-CijQwpIk.js +69 -0
  52. package/dist/strip-BvU4toXG.js +6 -0
  53. package/dist/terminal.d.ts +121 -0
  54. package/dist/terminal.js +2 -0
  55. package/dist/tokenize-AjqbvtiT.js +1242 -0
  56. package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
  57. package/dist/truncate-D31fhU6i.js +562 -0
  58. package/dist/use-focus-B5npZrJU.js +18736 -0
  59. package/dist/yoga-5jKhYCJC.js +3465 -0
  60. package/dist/yoga.d.ts +2 -0
  61. package/dist/yoga.js +2 -0
  62. package/package.json +89 -29
  63. package/src/ansi/chalk.ts +138 -0
  64. package/src/ansi/cursor.ts +46 -0
  65. package/src/ansi/east-asian-width.ts +259 -0
  66. package/src/ansi/escapes.ts +120 -0
  67. package/src/ansi/graphemes.ts +8 -0
  68. package/src/ansi/hyperlink.ts +44 -0
  69. package/src/ansi/index.ts +27 -0
  70. package/src/ansi/osc.ts +77 -0
  71. package/src/ansi/sgr.ts +234 -0
  72. package/src/ansi/slice.ts +43 -0
  73. package/src/ansi/string-width.ts +121 -0
  74. package/src/ansi/strip.ts +13 -0
  75. package/src/ansi/tokenize.ts +380 -0
  76. package/src/ansi/truncate.ts +196 -0
  77. package/src/ansi/wrap.ts +765 -0
  78. package/src/ansi-tokenizer.ts +510 -0
  79. package/src/capabilities/color-policy.ts +34 -0
  80. package/src/capabilities/detect.ts +608 -0
  81. package/src/capabilities/index.ts +37 -0
  82. package/src/capabilities/query.ts +679 -0
  83. package/src/capabilities/store.ts +394 -0
  84. package/src/code-excerpt.ts +39 -0
  85. package/src/color/index.ts +3 -0
  86. package/src/color/paint.ts +169 -0
  87. package/src/color/palette.ts +48 -0
  88. package/src/color/sample.ts +323 -0
  89. package/src/color.ts +1 -0
  90. package/src/components/AccessibilityContext.ts +5 -0
  91. package/src/components/AnimationContext.ts +24 -0
  92. package/src/components/AnsiText.tsx +42 -0
  93. package/src/components/App.tsx +878 -0
  94. package/src/components/AppContext.ts +111 -0
  95. package/src/components/BackgroundContext.ts +7 -0
  96. package/src/components/Box.tsx +100 -0
  97. package/src/components/CursorContext.ts +19 -0
  98. package/src/components/ErrorBoundary.tsx +39 -0
  99. package/src/components/ErrorOverview.tsx +135 -0
  100. package/src/components/FocusContext.ts +30 -0
  101. package/src/components/Hyperlink.tsx +56 -0
  102. package/src/components/Newline.tsx +16 -0
  103. package/src/components/Spacer.tsx +11 -0
  104. package/src/components/Static.tsx +60 -0
  105. package/src/components/StderrContext.ts +24 -0
  106. package/src/components/StdinContext.ts +48 -0
  107. package/src/components/StdoutContext.ts +26 -0
  108. package/src/components/TerminalOscContext.ts +25 -0
  109. package/src/components/Text.tsx +122 -0
  110. package/src/components/Transform.tsx +38 -0
  111. package/src/components/VirtualList.tsx +128 -0
  112. package/src/cursor-position.ts +103 -0
  113. package/src/devtools.ts +103 -0
  114. package/src/dom.ts +301 -0
  115. package/src/env.ts +12 -0
  116. package/src/get-max-width.ts +11 -0
  117. package/src/global.d.ts +38 -0
  118. package/src/glyphs.ts +99 -0
  119. package/src/hooks/use-animation.ts +142 -0
  120. package/src/hooks/use-app.ts +8 -0
  121. package/src/hooks/use-box-metrics.ts +134 -0
  122. package/src/hooks/use-capabilities.ts +73 -0
  123. package/src/hooks/use-cursor.ts +33 -0
  124. package/src/hooks/use-focus-manager.ts +62 -0
  125. package/src/hooks/use-focus.ts +82 -0
  126. package/src/hooks/use-input.ts +267 -0
  127. package/src/hooks/use-is-screen-reader-enabled.ts +12 -0
  128. package/src/hooks/use-paste.ts +78 -0
  129. package/src/hooks/use-stderr.ts +8 -0
  130. package/src/hooks/use-stdin.ts +10 -0
  131. package/src/hooks/use-stdout.ts +8 -0
  132. package/src/hooks/use-terminal-osc.ts +59 -0
  133. package/src/hooks/use-virtual-scroll.ts +84 -0
  134. package/src/hooks/use-window-size.ts +37 -0
  135. package/src/index.ts +96 -0
  136. package/src/ink.tsx +1452 -0
  137. package/src/input-parser.ts +303 -0
  138. package/src/instances.ts +9 -0
  139. package/src/jsx-dev-runtime.ts +3 -0
  140. package/src/jsx-runtime.ts +6 -0
  141. package/src/kitty-keyboard.ts +185 -0
  142. package/src/measure-element.ts +62 -0
  143. package/src/measure-text.ts +31 -0
  144. package/src/paint-tree.ts +220 -0
  145. package/src/parse-keypress.ts +515 -0
  146. package/src/parse-stack-line.ts +138 -0
  147. package/src/patch-console.ts +106 -0
  148. package/src/quick-lru.ts +85 -0
  149. package/src/react.ts +53 -0
  150. package/src/reconciler.ts +476 -0
  151. package/src/render-background.ts +59 -0
  152. package/src/render-border.ts +167 -0
  153. package/src/render-frame.ts +83 -0
  154. package/src/render-to-string.ts +146 -0
  155. package/src/render.ts +284 -0
  156. package/src/router/components.tsx +343 -0
  157. package/src/router/context.ts +41 -0
  158. package/src/router/history.ts +194 -0
  159. package/src/router/hooks.tsx +391 -0
  160. package/src/router/index.ts +34 -0
  161. package/src/router/matcher.ts +571 -0
  162. package/src/sanitize-ansi.ts +33 -0
  163. package/src/screen/ansi.ts +184 -0
  164. package/src/screen/canvas.ts +160 -0
  165. package/src/screen/cell.ts +138 -0
  166. package/src/screen/color-profile.ts +47 -0
  167. package/src/screen/geometry.ts +9 -0
  168. package/src/screen/index.ts +6 -0
  169. package/src/screen/screen.ts +305 -0
  170. package/src/screen/serialize.ts +129 -0
  171. package/src/screen.ts +1 -0
  172. package/src/semantic-text-style.ts +118 -0
  173. package/src/signal-exit.ts +106 -0
  174. package/src/squash-text-nodes.ts +37 -0
  175. package/src/stream.ts +28 -0
  176. package/src/structured-text.ts +325 -0
  177. package/src/styles.ts +753 -0
  178. package/src/terminal/index.ts +2 -0
  179. package/src/terminal/inline-presenter.ts +120 -0
  180. package/src/terminal/input.ts +92 -0
  181. package/src/terminal/render-scheduler.ts +37 -0
  182. package/src/terminal/screen-presenter.ts +242 -0
  183. package/src/terminal/session.ts +408 -0
  184. package/src/terminal-size.ts +58 -0
  185. package/src/terminal.ts +1 -0
  186. package/src/testing/browser.ts +588 -0
  187. package/src/testing/emulators.ts +205 -0
  188. package/src/testing/explorer-app/index.html +12 -0
  189. package/src/testing/explorer-app/main.ts +381 -0
  190. package/src/testing/explorer-app/style.css +194 -0
  191. package/src/testing/explorer-app/tsconfig.json +15 -0
  192. package/src/testing/explorer-app/vite-env.d.ts +1 -0
  193. package/src/testing/index.ts +26 -0
  194. package/src/testing/keys.ts +56 -0
  195. package/src/testing/live.ts +85 -0
  196. package/src/testing/matchers.ts +70 -0
  197. package/src/testing/public.ts +94 -0
  198. package/src/testing/terminal.ts +361 -0
  199. package/src/testing/vitest.ts +157 -0
  200. package/src/throttle.ts +73 -0
  201. package/src/transform-adapter.ts +14 -0
  202. package/src/types.ts +10 -0
  203. package/src/virtual-scroll.ts +133 -0
  204. package/src/wrap-text.ts +54 -0
  205. package/src/yoga/config.ts +57 -0
  206. package/src/yoga/core/absoluteLayout.ts +626 -0
  207. package/src/yoga/core/baseline.ts +66 -0
  208. package/src/yoga/core/cache.ts +136 -0
  209. package/src/yoga/core/calculateLayout.ts +2926 -0
  210. package/src/yoga/core/config.ts +104 -0
  211. package/src/yoga/core/flexLine.ts +177 -0
  212. package/src/yoga/core/helpers.ts +293 -0
  213. package/src/yoga/core/layoutResults.ts +167 -0
  214. package/src/yoga/core/node.ts +611 -0
  215. package/src/yoga/core/numeric.ts +44 -0
  216. package/src/yoga/core/pixelGrid.ts +151 -0
  217. package/src/yoga/core/style.ts +887 -0
  218. package/src/yoga/core/types.ts +224 -0
  219. package/src/yoga/generated/YGEnums.ts +263 -0
  220. package/src/yoga/index.ts +19 -0
  221. package/src/yoga/node.ts +1140 -0
  222. package/dist/index.d.mts +0 -2379
  223. package/dist/index.mjs +0 -10072
@@ -0,0 +1,608 @@
1
+ // The single home for environment-derived terminal detection: identity,
2
+ // color support, hyperlinks, unicode, and the full synchronous capability
3
+ // snapshot. Detection reads environment variables and stream TTY-ness only —
4
+ // never argv. Knowledge about specific terminals lives in one table that
5
+ // every detector derives from.
6
+ //
7
+ // Everything here is a fallback opinion: for facts the terminal itself can
8
+ // answer, the query in terminal-query.ts is authoritative and overrides
9
+ // this via `applyTerminalQuery`.
10
+ //
11
+ // The color-level ladder is derived from `supports-color` and unicode
12
+ // detection from `is-unicode-supported` (both MIT) — see
13
+ // THIRD_PARTY_NOTICES.md.
14
+ import { isInCi, isScreenReader, isWindows } from "#/env.ts";
15
+ import { terminalSize } from "#/terminal-size.ts";
16
+
17
+ /**
18
+ An RGB color with 8-bit channels, as reported by the terminal.
19
+ */
20
+ export type RgbColor = {
21
+ r: number;
22
+ g: number;
23
+ b: number;
24
+ };
25
+
26
+ export type TerminalAppearance = "dark" | "light";
27
+
28
+ export type Multiplexer = "tmux" | "screen" | "zellij";
29
+
30
+ /**
31
+ Color support level: 0 = none, 1 = 16 colors, 2 = 256 colors, 3 = truecolor.
32
+ */
33
+ export type ColorSupportLevel = 0 | 1 | 2 | 3;
34
+
35
+ /**
36
+ The chalk-compatible color support shape.
37
+ */
38
+ export type ColorSupport = {
39
+ readonly level: ColorSupportLevel;
40
+ readonly hasBasic: boolean;
41
+ readonly has256: boolean;
42
+ readonly has16m: boolean;
43
+ };
44
+
45
+ export type ColorInfo = ColorSupport | false;
46
+
47
+ export type TerminalIdentity = {
48
+ /**
49
+ Normalized terminal name (`"kitty"`, `"iterm"`, `"wezterm"`, …), or
50
+ `undefined` when unknown. `queryTerminal` can refine this with the
51
+ terminal's own XTVERSION answer.
52
+ */
53
+ name: string | undefined;
54
+
55
+ /**
56
+ The terminal's version, when the environment reports one.
57
+ */
58
+ version: string | undefined;
59
+
60
+ /**
61
+ The raw `TERM` environment variable.
62
+ */
63
+ term: string | undefined;
64
+
65
+ /**
66
+ The multiplexer the app is running under, if any. Note that inside a
67
+ multiplexer, capabilities reflect the multiplexer — not the outer terminal.
68
+ */
69
+ multiplexer: Multiplexer | undefined;
70
+ };
71
+
72
+ export type PixelGeometry = {
73
+ /**
74
+ Text area size in pixels (XTWINOPS 14).
75
+ */
76
+ textArea: { width: number; height: number } | undefined;
77
+
78
+ /**
79
+ Size of a single character cell in pixels (XTWINOPS 16).
80
+ */
81
+ cell: { width: number; height: number } | undefined;
82
+ };
83
+
84
+ export type Capabilities = {
85
+ /**
86
+ Current terminal dimensions in cells, plus pixel geometry once the
87
+ terminal has answered the query.
88
+
89
+ `source` tells where the cell dimensions come from. `"pty"` is the
90
+ stream's own `columns`/`rows`, updated by the OS on SIGWINCH — which says
91
+ nothing about whether the emulator has finished rewrapping its screen.
92
+ `"terminal"` is the emulator's in-band size report (mode 2048), which
93
+ arrives in the input stream after the rewrap and so describes the screen
94
+ exactly as later output will find it. While the terminal reports, its
95
+ size wins over the stream's.
96
+ */
97
+ size: {
98
+ columns: number;
99
+ rows: number;
100
+ pixels: PixelGeometry | undefined;
101
+ source: "pty" | "terminal";
102
+ };
103
+
104
+ platform: NodeJS.Platform;
105
+
106
+ /**
107
+ Running under a CI provider.
108
+ */
109
+ ci: boolean;
110
+
111
+ /**
112
+ Running over an SSH connection.
113
+ */
114
+ ssh: boolean;
115
+
116
+ screenReader: boolean;
117
+
118
+ /**
119
+ Same detection `render()` uses: stdout is a TTY and not CI.
120
+ */
121
+ interactive: boolean;
122
+
123
+ /**
124
+ Whether the terminal window has focus. Requires focus events (mode 1004),
125
+ which the capabilities store enables automatically while it has
126
+ subscribers; `undefined` until the first focus report arrives.
127
+ */
128
+ focused: boolean | undefined;
129
+
130
+ terminal: TerminalIdentity;
131
+
132
+ color: {
133
+ level: ColorSupportLevel;
134
+
135
+ /**
136
+ The same fact as bits per color: 1, 4, 8, or 24.
137
+ */
138
+ depth: 1 | 4 | 8 | 24;
139
+
140
+ trueColor: boolean;
141
+ };
142
+
143
+ theme: {
144
+ /**
145
+ The terminal's own appearance. After `queryTerminal` this is derived
146
+ from the actual background color's luminance (a dark terminal theme on
147
+ a light OS stays `"dark"`); before that it's a `COLORFGBG` guess.
148
+ */
149
+ appearance: TerminalAppearance | undefined;
150
+
151
+ /**
152
+ The operating system's color preference, from the color scheme report.
153
+ Independent of the terminal's own theme — only available after
154
+ `queryTerminal` on terminals that support the report.
155
+ */
156
+ systemAppearance: TerminalAppearance | undefined;
157
+
158
+ /**
159
+ The user's configured foreground/background/cursor colors and 16-color
160
+ palette. Only available after `queryTerminal`.
161
+ */
162
+ foreground: RgbColor | undefined;
163
+ background: RgbColor | undefined;
164
+ cursor: RgbColor | undefined;
165
+ palette: RgbColor[] | undefined;
166
+ };
167
+
168
+ /**
169
+ Feature support. Fields typed `boolean | undefined` are only knowable by
170
+ asking the terminal — they stay `undefined` until `queryTerminal` has
171
+ answered (see `TerminalQueryResult` for what each means).
172
+ */
173
+ supports: {
174
+ color: boolean;
175
+ hyperlinks: boolean;
176
+ unicode: boolean;
177
+ alternateScreen: boolean;
178
+ kittyKeyboard: boolean | undefined;
179
+ kittyGraphics: boolean | undefined;
180
+ sixel: boolean | undefined;
181
+ focusEvents: boolean | undefined;
182
+ sgrMouse: boolean | undefined;
183
+ sgrPixelMouse: boolean | undefined;
184
+ bracketedPaste: boolean | undefined;
185
+ synchronizedOutput: boolean | undefined;
186
+ graphemeClustering: boolean | undefined;
187
+ colorSchemeUpdates: boolean | undefined;
188
+ inBandResize: boolean | undefined;
189
+ };
190
+ };
191
+
192
+ const { env } = process;
193
+
194
+ // ── Terminal knowledge ──────────────────────────────────────────────────────
195
+ // The one table every detector derives from. A terminal appears here once;
196
+ // color, hyperlink, and unicode detection all consult the same entry.
197
+
198
+ type TerminalKnowledge = {
199
+ /**
200
+ Supports 24-bit color.
201
+ */
202
+ trueColor?: boolean;
203
+
204
+ /**
205
+ Maximum color level for terminals without truecolor.
206
+ */
207
+ colorLevel?: ColorSupportLevel;
208
+
209
+ /**
210
+ OSC 8 hyperlinks: supported outright, or since a specific version.
211
+ */
212
+ hyperlinks?: true | { since: [major: number, minor: number] };
213
+ };
214
+
215
+ const terminalKnowledge: Record<string, TerminalKnowledge> = {
216
+ iterm: { trueColor: true, hyperlinks: { since: [3, 1] } },
217
+ "apple-terminal": { colorLevel: 2 },
218
+ wezterm: { trueColor: true, hyperlinks: true },
219
+ vscode: { trueColor: true, hyperlinks: { since: [1, 72] } },
220
+ ghostty: { trueColor: true, hyperlinks: true },
221
+ kitty: { trueColor: true, hyperlinks: true },
222
+ alacritty: { trueColor: true, hyperlinks: true },
223
+ foot: { trueColor: true, hyperlinks: true },
224
+ contour: { trueColor: true, hyperlinks: true },
225
+ "windows-terminal": { trueColor: true, hyperlinks: true },
226
+ konsole: { trueColor: true, hyperlinks: true },
227
+ vte: { trueColor: true, hyperlinks: { since: [0, 50] } },
228
+ hyper: { trueColor: true },
229
+ tabby: { trueColor: true, hyperlinks: true },
230
+ rio: { trueColor: true, hyperlinks: true },
231
+ };
232
+
233
+ // Maps TERM_PROGRAM values to normalized names.
234
+ const termProgramNames: Record<string, string> = {
235
+ "iTerm.app": "iterm",
236
+ // eslint-disable-next-line @typescript-eslint/naming-convention
237
+ Apple_Terminal: "apple-terminal",
238
+ // eslint-disable-next-line @typescript-eslint/naming-convention
239
+ WezTerm: "wezterm",
240
+ vscode: "vscode",
241
+ ghostty: "ghostty",
242
+ // eslint-disable-next-line @typescript-eslint/naming-convention
243
+ Hyper: "hyper",
244
+ // eslint-disable-next-line @typescript-eslint/naming-convention
245
+ Tabby: "tabby",
246
+ rio: "rio",
247
+ };
248
+
249
+ // Maps TERM values that identify a specific terminal (rather than a generic
250
+ // terminfo entry) to normalized names.
251
+ const termNames: Record<string, string> = {
252
+ "xterm-kitty": "kitty",
253
+ "xterm-ghostty": "ghostty",
254
+ alacritty: "alacritty",
255
+ wezterm: "wezterm",
256
+ foot: "foot",
257
+ contour: "contour",
258
+ };
259
+
260
+ // VTE_VERSION is a plain number like "7802" meaning 0.78.2.
261
+ const parseVteVersion = (raw: string): string | undefined => {
262
+ const numeric = Number.parseInt(raw, 10);
263
+ if (Number.isNaN(numeric)) {
264
+ return undefined;
265
+ }
266
+
267
+ return `${Math.floor(numeric / 10_000)}.${Math.floor(numeric / 100) % 100}.${numeric % 100}`;
268
+ };
269
+
270
+ const versionAtLeast = (version: string | undefined, [major, minor]: [number, number]): boolean => {
271
+ if (!version) {
272
+ return false;
273
+ }
274
+
275
+ const [haveMajor = 0, haveMinor = 0] = version
276
+ .split(".")
277
+ .map((part) => Number.parseInt(part, 10) || 0);
278
+ return haveMajor > major || (haveMajor === major && haveMinor >= minor);
279
+ };
280
+
281
+ export function detectTerminal(): TerminalIdentity {
282
+ const term = env["TERM"];
283
+
284
+ let multiplexer: Multiplexer | undefined;
285
+ if ("TMUX" in env || env["TERM_PROGRAM"] === "tmux") {
286
+ multiplexer = "tmux";
287
+ } else if ("ZELLIJ" in env) {
288
+ multiplexer = "zellij";
289
+ } else if (term?.startsWith("screen")) {
290
+ multiplexer = "screen";
291
+ }
292
+
293
+ let name: string | undefined;
294
+ let version: string | undefined;
295
+
296
+ const termProgram = env["TERM_PROGRAM"];
297
+ if (termProgram && termProgram !== "tmux" && termProgram in termProgramNames) {
298
+ name = termProgramNames[termProgram];
299
+ version = env["TERM_PROGRAM_VERSION"];
300
+ } else if (term && term in termNames) {
301
+ name = termNames[term];
302
+ } else if ("WT_SESSION" in env) {
303
+ name = "windows-terminal";
304
+ } else if ("KONSOLE_VERSION" in env) {
305
+ name = "konsole";
306
+ version = env["KONSOLE_VERSION"];
307
+ } else if ("VTE_VERSION" in env) {
308
+ // GNOME Terminal, Tilix, and other libvte terminals.
309
+ name = "vte";
310
+ version = parseVteVersion(env["VTE_VERSION"]!);
311
+ }
312
+
313
+ return { name, version, term, multiplexer };
314
+ }
315
+
316
+ const knowledgeOf = (identity: TerminalIdentity): TerminalKnowledge | undefined =>
317
+ identity.name === undefined ? undefined : terminalKnowledge[identity.name];
318
+
319
+ // ── Color ───────────────────────────────────────────────────────────────────
320
+
321
+ type DetectStream = {
322
+ isTTY?: boolean;
323
+ };
324
+
325
+ const forcedColorLevel = (): ColorSupportLevel | undefined => {
326
+ const forced = env["FORCE_COLOR"];
327
+ if (forced === undefined) {
328
+ return undefined;
329
+ }
330
+
331
+ if (forced === "true" || forced.length === 0) {
332
+ return 1;
333
+ }
334
+
335
+ if (forced === "false") {
336
+ return 0;
337
+ }
338
+
339
+ return Math.min(Math.max(Number.parseInt(forced, 10) || 0, 0), 3) as ColorSupportLevel;
340
+ };
341
+
342
+ /**
343
+ Detects the color support level for a stream from the environment. Purely
344
+ env-derived — the terminal query can upgrade this to truecolor when the
345
+ terminal confirms it via XTGETTCAP.
346
+ */
347
+ export function detectColorLevel(stream?: DetectStream): ColorSupportLevel {
348
+ const forced = forcedColorLevel();
349
+ if (forced === 0) {
350
+ return 0;
351
+ }
352
+
353
+ // Azure DevOps pipelines are colorful but not TTYs; check before the
354
+ // stream gate.
355
+ if (env["TF_BUILD"] && env["AGENT_NAME"]) {
356
+ return 1;
357
+ }
358
+
359
+ if (stream && !stream.isTTY && forced === undefined) {
360
+ return 0;
361
+ }
362
+
363
+ const minimum = forced ?? 0;
364
+
365
+ if (env["TERM"] === "dumb") {
366
+ return minimum;
367
+ }
368
+
369
+ if (isWindows) {
370
+ // Node 22 requires Windows 10 1809+, which supports truecolor.
371
+ return 3;
372
+ }
373
+
374
+ // An empty CI variable means "not CI" (`CI= cmd` and harness spawns), the
375
+ // same convention `isInCi` uses — presence alone must not kill colors.
376
+ if (env["CI"]) {
377
+ if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
378
+ return 3;
379
+ }
380
+
381
+ if (
382
+ ["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((key) => key in env) ||
383
+ env["CI_NAME"] === "codeship"
384
+ ) {
385
+ return 1;
386
+ }
387
+
388
+ return minimum;
389
+ }
390
+
391
+ if (env["TEAMCITY_VERSION"]) {
392
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env["TEAMCITY_VERSION"]) ? 1 : 0;
393
+ }
394
+
395
+ if (env["COLORTERM"] === "truecolor") {
396
+ return 3;
397
+ }
398
+
399
+ const knowledge = knowledgeOf(detectTerminal());
400
+ if (knowledge) {
401
+ if (knowledge.trueColor) {
402
+ return 3;
403
+ }
404
+
405
+ if (knowledge.colorLevel !== undefined) {
406
+ return knowledge.colorLevel;
407
+ }
408
+ }
409
+
410
+ if (/-256(color)?$/i.test(env["TERM"] ?? "")) {
411
+ return 2;
412
+ }
413
+
414
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env["TERM"] ?? "")) {
415
+ return 1;
416
+ }
417
+
418
+ if ("COLORTERM" in env) {
419
+ return 1;
420
+ }
421
+
422
+ return minimum;
423
+ }
424
+
425
+ /**
426
+ The color level in chalk's `ColorInfo` shape.
427
+ */
428
+ export function createSupportsColor(stream?: DetectStream): ColorInfo {
429
+ const level = detectColorLevel(stream);
430
+ if (level === 0) {
431
+ return false;
432
+ }
433
+
434
+ return {
435
+ level,
436
+ hasBasic: true,
437
+ has256: level >= 2,
438
+ has16m: level >= 3,
439
+ };
440
+ }
441
+
442
+ // ── Hyperlinks ──────────────────────────────────────────────────────────────
443
+
444
+ /**
445
+ Detects OSC 8 hyperlink support for a stream from the environment.
446
+ */
447
+ export function detectHyperlinkSupport(stream?: DetectStream): boolean {
448
+ if ("FORCE_HYPERLINK" in env) {
449
+ return !(
450
+ env["FORCE_HYPERLINK"]!.length > 0 && Number.parseInt(env["FORCE_HYPERLINK"]!, 10) === 0
451
+ );
452
+ }
453
+
454
+ // No color support is a good proxy for a terminal (or pipe) that would
455
+ // print OSC 8 sequences as garbage rather than ignore them.
456
+ if (detectColorLevel(stream) === 0) {
457
+ return false;
458
+ }
459
+
460
+ if (stream && !stream.isTTY) {
461
+ return false;
462
+ }
463
+
464
+ // CI log renderers generally show escape codes literally.
465
+ if (env["CI"] || env["TEAMCITY_VERSION"]) {
466
+ return false;
467
+ }
468
+
469
+ const identity = detectTerminal();
470
+ const hyperlinks = knowledgeOf(identity)?.hyperlinks;
471
+ if (hyperlinks === true) {
472
+ return true;
473
+ }
474
+
475
+ if (hyperlinks) {
476
+ return versionAtLeast(identity.version, hyperlinks.since);
477
+ }
478
+
479
+ return false;
480
+ }
481
+
482
+ // ── Unicode ─────────────────────────────────────────────────────────────────
483
+
484
+ /**
485
+ Detects whether the terminal renders unicode reliably.
486
+ */
487
+ export function detectUnicodeSupport(): boolean {
488
+ if (process.platform !== "win32") {
489
+ // The Linux console (kernel tty) is the lone holdout.
490
+ return env["TERM"] !== "linux";
491
+ }
492
+
493
+ const { name } = detectTerminal();
494
+ return (
495
+ name === "windows-terminal" ||
496
+ name === "vscode" ||
497
+ name === "alacritty" ||
498
+ Boolean(env["TERMINUS_SUBLIME"]) ||
499
+ env["ConEmuTask"] === "{cmd::Cmder}" ||
500
+ env["TERM_PROGRAM"] === "Terminus-Sublime" ||
501
+ env["TERM"] === "xterm-256color" ||
502
+ env["TERMINAL_EMULATOR"] === "JetBrains-JediTerm"
503
+ );
504
+ }
505
+
506
+ // ── Capability snapshot ─────────────────────────────────────────────────────
507
+
508
+ // COLORFGBG looks like "15;0" (foreground;background) using 16-color palette
509
+ // indices. Only 7 and 15 are light backgrounds.
510
+ const appearanceFromColorFgBg = (): TerminalAppearance | undefined => {
511
+ const parts = env["COLORFGBG"]?.split(";");
512
+ const background = parts?.at(-1);
513
+ if (!background || Number.isNaN(Number.parseInt(background, 10))) {
514
+ return undefined;
515
+ }
516
+
517
+ return background === "7" || background === "15" ? "light" : "dark";
518
+ };
519
+
520
+ const colorDepths: Record<ColorSupportLevel, 1 | 4 | 8 | 24> = { 0: 1, 1: 4, 2: 8, 3: 24 };
521
+
522
+ type CapabilityStdout = DetectStream & {
523
+ columns?: number;
524
+ rows?: number;
525
+ };
526
+
527
+ type DetectOptions = {
528
+ stdout?: CapabilityStdout;
529
+ };
530
+
531
+ type StaticCapabilities = Omit<Capabilities, "size" | "theme" | "focused">;
532
+
533
+ // Environment and terminal identity don't change over a process's lifetime;
534
+ // only size and theme are dynamic. Keyed by stream since TTY-ness (and
535
+ // therefore color/hyperlink support) is per-stream.
536
+ const staticCache = new WeakMap<object, StaticCapabilities>();
537
+
538
+ const computeStatic = (stdout: CapabilityStdout): StaticCapabilities => {
539
+ const level = detectColorLevel(stdout);
540
+ const interactive = !isInCi && Boolean(stdout.isTTY);
541
+
542
+ return {
543
+ platform: process.platform,
544
+ ci: isInCi,
545
+ ssh: "SSH_CONNECTION" in env || "SSH_CLIENT" in env || "SSH_TTY" in env,
546
+ screenReader: isScreenReader,
547
+ interactive,
548
+ terminal: detectTerminal(),
549
+ color: {
550
+ level,
551
+ depth: colorDepths[level],
552
+ trueColor: level === 3,
553
+ },
554
+ supports: {
555
+ color: level > 0,
556
+ hyperlinks: detectHyperlinkSupport(stdout),
557
+ unicode: detectUnicodeSupport(),
558
+ alternateScreen: interactive && env["TERM"] !== "dumb",
559
+ kittyKeyboard: undefined,
560
+ kittyGraphics: undefined,
561
+ sixel: undefined,
562
+ focusEvents: undefined,
563
+ sgrMouse: undefined,
564
+ sgrPixelMouse: undefined,
565
+ bracketedPaste: undefined,
566
+ synchronizedOutput: undefined,
567
+ graphemeClustering: undefined,
568
+ colorSchemeUpdates: undefined,
569
+ inBandResize: undefined,
570
+ },
571
+ };
572
+ };
573
+
574
+ /**
575
+ Takes a snapshot of everything knowable about the terminal from streams and
576
+ environment variables. The environment-derived parts are computed once per
577
+ stream and cached; size and theme are read fresh on every call. Fields that
578
+ require asking the terminal itself start as `undefined` — run `queryTerminal`
579
+ and merge with `applyTerminalQuery` to fill them in, or use the
580
+ `useCapabilities` hook which does both.
581
+ */
582
+ export function detectCapabilities({ stdout = process.stdout }: DetectOptions = {}): Capabilities {
583
+ let staticParts = staticCache.get(stdout);
584
+ if (!staticParts) {
585
+ staticParts = computeStatic(stdout);
586
+ staticCache.set(stdout, staticParts);
587
+ }
588
+
589
+ return {
590
+ ...staticParts,
591
+ size: {
592
+ ...(stdout.columns && stdout.rows
593
+ ? { columns: stdout.columns, rows: stdout.rows }
594
+ : terminalSize()),
595
+ pixels: undefined,
596
+ source: "pty",
597
+ },
598
+ focused: undefined,
599
+ theme: {
600
+ appearance: appearanceFromColorFgBg(),
601
+ systemAppearance: undefined,
602
+ foreground: undefined,
603
+ background: undefined,
604
+ cursor: undefined,
605
+ palette: undefined,
606
+ },
607
+ };
608
+ }
@@ -0,0 +1,37 @@
1
+ // Terminal capabilities — framework-free, importable without the React
2
+ // renderer via `@alchemy.run/sigil/capabilities`.
3
+ //
4
+ // - `capabilities` / `getCapabilities`: a live store with `current`,
5
+ // `query()`, and `subscribe()`.
6
+ // - `detect*`: synchronous environment-derived detection.
7
+ // - `queryTerminal` and friends: the raw VT query round-trip.
8
+ export type {
9
+ Capabilities,
10
+ ColorInfo,
11
+ ColorSupport,
12
+ ColorSupportLevel,
13
+ Multiplexer,
14
+ PixelGeometry,
15
+ RgbColor,
16
+ TerminalAppearance,
17
+ TerminalIdentity,
18
+ } from "#/capabilities/detect.ts";
19
+ export {
20
+ createSupportsColor,
21
+ detectCapabilities,
22
+ detectColorLevel,
23
+ detectHyperlinkSupport,
24
+ detectTerminal,
25
+ detectUnicodeSupport,
26
+ } from "#/capabilities/detect.ts";
27
+ export type { PixelSize, TerminalQueryOptions, TerminalQueryResult } from "#/capabilities/query.ts";
28
+ export {
29
+ applyTerminalQuery,
30
+ getTerminalQuery,
31
+ queryTerminal,
32
+ refreshTerminalQuery,
33
+ } from "#/capabilities/query.ts";
34
+ export type { CapabilitiesStore } from "#/capabilities/store.ts";
35
+ export { capabilities, getCapabilities } from "#/capabilities/store.ts";
36
+ export type { ColorPolicy, ColorState } from "#/capabilities/color-policy.ts";
37
+ export { colorState, resolveColorProfile } from "#/capabilities/color-policy.ts";