@onesi/genesis-cli 0.1.13 → 0.2.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.
Files changed (63) hide show
  1. package/README.md +29 -2
  2. package/dist/beacons.js +68 -0
  3. package/dist/dials.js +7 -4
  4. package/dist/fleets.js +30 -0
  5. package/dist/flocks.d.ts +7 -1
  6. package/dist/flocks.js +257 -3
  7. package/dist/henry.d.ts +5 -1
  8. package/dist/henry.js +60 -1
  9. package/dist/hives-coverage.d.ts +12 -0
  10. package/dist/hives-coverage.js +116 -0
  11. package/dist/hives-node-transport.js +3 -0
  12. package/dist/hives-operations.d.ts +1 -0
  13. package/dist/hives-operations.js +2 -0
  14. package/dist/hives-platform.d.ts +13 -0
  15. package/dist/hives-platform.js +133 -0
  16. package/dist/iam-directory.d.ts +15 -0
  17. package/dist/iam-directory.js +71 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +255 -23
  20. package/dist/minds.d.ts +1 -1
  21. package/dist/minds.js +37 -2
  22. package/dist/onboard/art.d.ts +50 -0
  23. package/dist/onboard/art.js +139 -0
  24. package/dist/onboard/brand.d.ts +56 -0
  25. package/dist/onboard/brand.js +134 -0
  26. package/dist/onboard/caps.d.ts +90 -0
  27. package/dist/onboard/caps.js +232 -0
  28. package/dist/onboard/encode.d.ts +23 -0
  29. package/dist/onboard/encode.js +235 -0
  30. package/dist/onboard/eye.d.ts +103 -0
  31. package/dist/onboard/eye.js +147 -0
  32. package/dist/onboard/index.d.ts +61 -0
  33. package/dist/onboard/index.js +562 -0
  34. package/dist/onboard/mcp.d.ts +35 -0
  35. package/dist/onboard/mcp.js +118 -0
  36. package/dist/onboard/raster.d.ts +43 -0
  37. package/dist/onboard/raster.js +253 -0
  38. package/dist/onboard/sound.d.ts +2 -0
  39. package/dist/onboard/sound.js +54 -0
  40. package/dist/onboard/state.d.ts +24 -0
  41. package/dist/onboard/state.js +48 -0
  42. package/dist/onboard/term.d.ts +69 -0
  43. package/dist/onboard/term.js +292 -0
  44. package/dist/onboard/ui.d.ts +97 -0
  45. package/dist/onboard/ui.js +615 -0
  46. package/dist/orgs.d.ts +16 -0
  47. package/dist/orgs.js +89 -0
  48. package/dist/products.d.ts +6 -2
  49. package/dist/products.js +238 -12
  50. package/dist/relays-commands/certificates.d.ts +3 -0
  51. package/dist/relays-commands/certificates.js +20 -0
  52. package/dist/relays-commands/domains.d.ts +3 -0
  53. package/dist/relays-commands/domains.js +23 -0
  54. package/dist/relays-commands/index.d.ts +4 -0
  55. package/dist/relays-commands/index.js +10 -0
  56. package/dist/relays-commands/monitoring.d.ts +3 -0
  57. package/dist/relays-commands/monitoring.js +41 -0
  58. package/dist/relays-commands/telemetry.d.ts +3 -0
  59. package/dist/relays-commands/telemetry.js +28 -0
  60. package/dist/relays-commands/zones.d.ts +5 -0
  61. package/dist/relays-commands/zones.js +9 -0
  62. package/dist/runner.js +2 -0
  63. package/package.json +2 -2
@@ -0,0 +1,139 @@
1
+ /**
2
+ * One's marks as drawings: the emblem (Digital One, the disc with the eye knocked out), the Ones, and the hybrid ONE
3
+ * logotype whose O can close to the disc, wake as the eye, hop through the Ones and open back to the ring (JR-B103's
4
+ * cycle, the One site's components/ui/nav-logotype.tsx). Everything is drawn into a raster.Canvas from brand.ts.
5
+ *
6
+ * Paint rules (one-system.css): the disc is One's black on light and chalk on dark, never outlined or glowing; amber
7
+ * is only ever light inside the eye; the Ones wear their own colours, and those colours mean nothing else.
8
+ */
9
+ import { AMBER, DISC, INK, LOGOTYPE, NIGHT_INK, ONES } from './brand.js';
10
+ import { circle, hex, mix, mul, parsePath, roundRect, scale, translate } from './raster.js';
11
+ import { GEOMETRY, poseToBar, poseToMouth, sample, LIGHT, REST } from './eye.js';
12
+ /**
13
+ * The terminal's own foreground. When the probe could not learn the background we draw ink and Digital One in this
14
+ * colour and the encoder emits SGR 39, so the mark is always the user's own ink: black on light, chalk on dark.
15
+ */
16
+ export const DEFAULT_INK = { r: 1, g: 2, b: 3 };
17
+ export const isDefaultInk = (c) => c.r === 1 && c.g === 2 && c.b === 3;
18
+ export const inkFor = (tone) => tone === 'unknown' ? DEFAULT_INK : hex(tone === 'dark' ? NIGHT_INK : INK);
19
+ const discFor = (tone) => tone === 'unknown' ? DEFAULT_INK : hex(tone === 'dark' ? DISC.chalk : DISC.black);
20
+ /** Clip a convex polygon to y in [y0, y1] (Sutherland–Hodgman on two half-planes). */
21
+ function clipY(poly, y0, y1) {
22
+ const pass = (p, keep, edge) => {
23
+ const out = [];
24
+ for (let i = 0; i < p.length; i++) {
25
+ const a = p[i], b = p[(i + 1) % p.length], ia = keep(a[1]), ib = keep(b[1]);
26
+ if (ia)
27
+ out.push(a);
28
+ if (ia !== ib) {
29
+ const t = (edge - a[1]) / (b[1] - a[1]);
30
+ out.push([a[0] + (b[0] - a[0]) * t, edge]);
31
+ }
32
+ }
33
+ return out;
34
+ };
35
+ return pass(pass(poly, (y) => y >= y0, y0), (y) => y <= y1, y1);
36
+ }
37
+ /** Draw a head into the -100…100 box mapped by `m`. */
38
+ export function drawHead(cv, m, head, tone) {
39
+ const sq = head.squash ?? 1, lift = head.hop ?? 0;
40
+ const pivot = 100; // the head stands on its foot: squash about y = +100
41
+ const body = mul(m, mul(translate(0, pivot + lift), mul(scale(1 / Math.sqrt(sq), sq), translate(0, -pivot))));
42
+ const one = head.one;
43
+ const eyeM = one ? mul(body, mul(translate(one.eye.x, one.eye.y), scale(one.eye.scale))) : body;
44
+ const pose = one ? { ...head.pose, open: head.pose.open * one.eye.openScale, look: head.pose.look * one.eye.lookGain } : head.pose;
45
+ const bar = poseToBar(pose);
46
+ const r = Math.min(GEOMETRY.cornerRadius, bar.h / 2, bar.w / 2);
47
+ const barPoly = roundRect(bar.x, bar.y, bar.w, bar.h, r);
48
+ const mouth = poseToMouth(pose);
49
+ const mouthPoly = mouth ? roundRect(mouth.x, mouth.y, mouth.w, mouth.h, GEOMETRY.cornerRadius) : null;
50
+ const toBody = (p) => p.map(([x, y]) => [eyeM[0] * x + eyeM[2] * y + eyeM[4], eyeM[1] * x + eyeM[3] * y + eyeM[5]]);
51
+ if (!one) {
52
+ // Digital One: the disc with the eye (and smile) knocked out, as the still emblem's even-odd cut.
53
+ const disc = circle(0, 0, 100).map(([x, y]) => [body[0] * x + body[2] * y + body[4], body[1] * x + body[3] * y + body[5]]);
54
+ cv.fill([disc, toBody(barPoly), ...(mouthPoly ? [toBody(mouthPoly)] : [])], discFor(tone), undefined, 'evenodd');
55
+ }
56
+ else {
57
+ cv.fill(parsePath(one.d), hex(one.colour), body);
58
+ cv.fill([toBody(barPoly), ...(mouthPoly ? [toBody(mouthPoly)] : [])], hex(one.eyeColour));
59
+ }
60
+ drawLight(cv, toBody, bar, head.light ?? -1);
61
+ }
62
+ /**
63
+ * The eye fills with light: a band rises up the bar behind a pale front, holds, and leaves through the top
64
+ * (agent-eye's rainbow, in One's amber). Drawn as strips so the light can grade from amber at its foot to soft amber.
65
+ */
66
+ function drawLight(cv, toBody, bar, light) {
67
+ const amount = 1 - Math.min(1, Math.abs(light));
68
+ if (amount <= 0.001)
69
+ return;
70
+ const top = bar.y - bar.h / 2, bottom = bar.y + bar.h / 2, H = bar.h;
71
+ // rising: the band's top climbs from the foot; leaving: its foot follows it out of the top.
72
+ const bandTop = light < 0 ? bottom - H * (1 + light) * 1.02 : top - 2;
73
+ const bandBottom = light < 0 ? bottom + 2 : bottom - H * light * 1.02;
74
+ const shape = roundRect(bar.x, bar.y, bar.w, bar.h, Math.min(GEOMETRY.cornerRadius, bar.h / 2, bar.w / 2));
75
+ const amber = hex(AMBER.amber), soft = hex(AMBER.soft), front = hex('#fff1d6');
76
+ const strips = 10;
77
+ for (let i = 0; i < strips; i++) {
78
+ const y0 = bandTop + ((bandBottom - bandTop) * i) / strips, y1 = bandTop + ((bandBottom - bandTop) * (i + 1)) / strips;
79
+ const part = clipY(shape, Math.max(top, y0), Math.min(bottom, y1));
80
+ if (part.length < 3)
81
+ continue;
82
+ const k = i / (strips - 1);
83
+ const c = light < 0 && i === 0 ? mix(soft, front, 0.7) : mix(soft, amber, k);
84
+ cv.fill([toBody(part)], c, undefined, 'nonzero', Math.min(1, amount * 1.15));
85
+ }
86
+ }
87
+ export const LOGO_ASPECT = LOGOTYPE.width / LOGOTYPE.height;
88
+ export const LOGO_REST = { ring: 1, fill: 0, head: null, n: 1, e: 1 };
89
+ /** The hybrid ONE, `px` tall (cap height plus the O's overshoot), its top-left at (x, y) in canvas pixels. */
90
+ export function drawLogotype(cv, x, y, px, s, tone, hint = px <= 40) {
91
+ // Hinting for text cells: the scale makes a stem (11 units) a whole number of pixels, which also puts the N's second
92
+ // stem (99 units in) on the grid; the cap height starts on a whole pixel; and each letter moves, never more than half
93
+ // a pixel, so its first stem's left edge does too. Stems then read as solid columns instead of two grey halves.
94
+ const k = hint ? Math.max(1, Math.round((11 * px) / LOGOTYPE.height)) / 11 : px / LOGOTYPE.height;
95
+ // …and the cap height is a whole number of pixels too (a vertical squeeze of at most half a pixel), so the baseline
96
+ // is as sharp as the cap line.
97
+ const ky = hint ? Math.round(100 * k) / 100 : k;
98
+ y += hint ? (px - LOGOTYPE.height * ky) / 2 : 0;
99
+ const dy = hint ? Math.round(y - LOGOTYPE.top * ky) - (y - LOGOTYPE.top * ky) : 0;
100
+ const snapX = (ux) => hint ? (Math.round(x + ux * k) - (x + ux * k)) / k : 0;
101
+ const base = mul(translate(x, y + dy), mul(scale(k, ky), translate(0, -LOGOTYPE.top)));
102
+ const ink = inkFor(tone);
103
+ const { cx, cy, r, inner } = LOGOTYPE.O;
104
+ if (s.head) {
105
+ drawHead(cv, mul(base, mul(translate(cx, cy), scale(r / 100))), s.head, tone);
106
+ }
107
+ else if (s.ring > 0) {
108
+ const hole = inner * (1 - s.fill);
109
+ const sweep = Math.min(1, s.ring) * Math.PI * 2;
110
+ const n = 96;
111
+ const ring = [];
112
+ const steps = Math.max(2, Math.ceil((n * sweep) / (Math.PI * 2)));
113
+ for (let i = 0; i <= steps; i++) {
114
+ const a = (sweep * i) / steps;
115
+ ring.push([cx + r * Math.sin(a), cy - r * Math.cos(a)]);
116
+ }
117
+ if (hole > 0.05)
118
+ for (let i = steps; i >= 0; i--) {
119
+ const a = (sweep * i) / steps;
120
+ ring.push([cx + hole * Math.sin(a), cy - hole * Math.cos(a)]);
121
+ }
122
+ else
123
+ ring.push([cx, cy]);
124
+ cv.fill([ring], s.fill > 0.98 ? discFor(tone) : ink, base);
125
+ }
126
+ const letter = (d, ox, t) => {
127
+ if (t <= 0)
128
+ return;
129
+ const rise = (1 - t) * 14;
130
+ cv.fill(parsePath(d), ink, mul(base, translate(ox, rise)), 'nonzero', Math.min(1, t * 1.25));
131
+ };
132
+ letter(LOGOTYPE.N.d, LOGOTYPE.N.x + snapX(LOGOTYPE.N.x), s.n);
133
+ letter(LOGOTYPE.E.d, LOGOTYPE.E.x + snapX(LOGOTYPE.E.x), s.e);
134
+ }
135
+ /** Digital One, `px` across, centred at (cx, cy). */
136
+ export function drawEmblem(cv, cx, cy, px, head, tone) {
137
+ drawHead(cv, mul(translate(cx, cy), scale(px / 200)), { one: null, pose: REST, ...head }, tone);
138
+ }
139
+ export { ONES, LIGHT, sample };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * One's brand geometry and colour, for the terminal. Generated from the One site (l1felabs one-website, worktree
3
+ * one-ones-shapes-20261016 at cdf3a16c): components/ui/logo.tsx (EMBLEM), components/ui/logotype.ts (the hybrid ONE,
4
+ * JR-B103), tests/fixtures/ones-shapes/shapes.json + palette.json (the Ones, life/ones-shapes/v2) and app/one-system.css
5
+ * (amber, the disc, paper and night). Do not edit by hand; regenerate from those files.
6
+ *
7
+ * Rules carried over (one-system.css): amber is One's light, never the interface's; the Ones' colours belong to the
8
+ * characters, never to success, warning or failure; the disc is One's black on light and chalk on dark, with no glow.
9
+ */
10
+ export declare const EMBLEM_PATH = "M16 0A16 16 0 1 1 16 32A16 16 0 1 1 16 0Z";
11
+ export declare const DISC: {
12
+ readonly black: "#141b16";
13
+ readonly chalk: "#f2f1ea";
14
+ readonly eyeLight: "#fbfaf6";
15
+ readonly eyeDark: "#f2f1ea";
16
+ };
17
+ export declare const AMBER: {
18
+ readonly amber: "#c8793a";
19
+ readonly soft: "#e9a867";
20
+ };
21
+ export declare const PAPER = "#f4f2ec", NIGHT = "#0b0c0c", INK = "#191614", NIGHT_INK = "#f2f1ea";
22
+ export declare const LOGOTYPE: {
23
+ readonly width: 340.5;
24
+ readonly height: 103;
25
+ readonly top: -1.5;
26
+ readonly O: {
27
+ readonly cx: 51.5;
28
+ readonly cy: 50;
29
+ readonly r: 51.5;
30
+ readonly inner: 39.84;
31
+ };
32
+ readonly N: {
33
+ readonly x: 119.5;
34
+ readonly d: "M0 0h11v100h-11ZM99 0h11v100h-11ZM16.1 10L31.444 10L93.9 90L78.556 90Z";
35
+ };
36
+ readonly E: {
37
+ readonly x: 248.5;
38
+ readonly d: "M0 0h11v100h-11ZM17 0h72.32v10.12h-72.32ZM0 43.94h76.816v10.12h-76.816ZM17 89.88h75v10.12h-75Z";
39
+ };
40
+ };
41
+ export interface OneShape {
42
+ id: string;
43
+ d: string;
44
+ eye: {
45
+ x: number;
46
+ y: number;
47
+ scale: number;
48
+ openScale: number;
49
+ lookGain: number;
50
+ };
51
+ colour: string;
52
+ eyeColour: string;
53
+ keyline: string | null;
54
+ }
55
+ /** Ones that join the parade (shapes.json parade: true), in agent-eye's -100…100 head box. */
56
+ export declare const ONES: readonly OneShape[];
@@ -0,0 +1,134 @@
1
+ /**
2
+ * One's brand geometry and colour, for the terminal. Generated from the One site (l1felabs one-website, worktree
3
+ * one-ones-shapes-20261016 at cdf3a16c): components/ui/logo.tsx (EMBLEM), components/ui/logotype.ts (the hybrid ONE,
4
+ * JR-B103), tests/fixtures/ones-shapes/shapes.json + palette.json (the Ones, life/ones-shapes/v2) and app/one-system.css
5
+ * (amber, the disc, paper and night). Do not edit by hand; regenerate from those files.
6
+ *
7
+ * Rules carried over (one-system.css): amber is One's light, never the interface's; the Ones' colours belong to the
8
+ * characters, never to success, warning or failure; the disc is One's black on light and chalk on dark, with no glow.
9
+ */
10
+ export const EMBLEM_PATH = "M16 0A16 16 0 1 1 16 32A16 16 0 1 1 16 0Z";
11
+ export const DISC = { black: "#141b16", chalk: "#f2f1ea", eyeLight: "#fbfaf6", eyeDark: "#f2f1ea" };
12
+ export const AMBER = { amber: "#c8793a", soft: "#e9a867" };
13
+ export const PAPER = "#f4f2ec", NIGHT = "#0b0c0c", INK = "#191614", NIGHT_INK = "#f2f1ea";
14
+ export const LOGOTYPE = {
15
+ width: 340.5, height: 103, top: -1.5,
16
+ O: { cx: 51.5, cy: 50, r: 51.5, inner: 39.84 },
17
+ N: { x: 119.5, d: "M0 0h11v100h-11ZM99 0h11v100h-11ZM16.1 10L31.444 10L93.9 90L78.556 90Z" },
18
+ E: { x: 248.5, d: "M0 0h11v100h-11ZM17 0h72.32v10.12h-72.32ZM0 43.94h76.816v10.12h-76.816ZM17 89.88h75v10.12h-75Z" },
19
+ };
20
+ /** Ones that join the parade (shapes.json parade: true), in agent-eye's -100…100 head box. */
21
+ export const ONES = [
22
+ {
23
+ "id": "star",
24
+ "d": "M0 -96.15A21.4 21.4 0 0 1 17.28 -87.38L39.72 -56.69A19.03 19.03 0 0 0 49.15 -49.84L85.28 -37.98A21.4 21.4 0 0 1 95.95 -5.12L73.7 25.72A19.03 19.03 0 0 0 70.1 36.79L69.98 74.82A21.4 21.4 0 0 1 42.03 95.13L5.82 83.49A19.03 19.03 0 0 0 -5.82 83.49L-42.03 95.13A21.4 21.4 0 0 1 -69.98 74.82L-70.1 36.79A19.03 19.03 0 0 0 -73.7 25.72L-95.95 -5.12A21.4 21.4 0 0 1 -85.28 -37.98L-49.15 -49.84A19.03 19.03 0 0 0 -39.72 -56.69L-17.28 -87.38A21.4 21.4 0 0 1 0 -96.15Z",
25
+ "eye": {
26
+ "x": 0,
27
+ "y": 12,
28
+ "scale": 0.8,
29
+ "openScale": 0.8,
30
+ "lookGain": 1
31
+ },
32
+ "colour": "#ffe93f",
33
+ "eyeColour": "#141b16",
34
+ "keyline": "#918312"
35
+ },
36
+ {
37
+ "id": "heart",
38
+ "d": "M0 -83.4A25.58 25.58 0 0 0 11.75 -86.26A60.47 60.47 0 0 1 84.6 7.76L13.87 86.83A18.6 18.6 0 0 1 -13.87 86.83L-84.6 7.76A60.47 60.47 0 0 1 -11.75 -86.26A25.58 25.58 0 0 0 0 -83.4Z",
39
+ "eye": {
40
+ "x": 0,
41
+ "y": -15,
42
+ "scale": 0.8,
43
+ "openScale": 0.8,
44
+ "lookGain": 1
45
+ },
46
+ "colour": "#ff7eb6",
47
+ "eyeColour": "#141b16",
48
+ "keyline": "#d3568f"
49
+ },
50
+ {
51
+ "id": "cloud",
52
+ "d": "M0 -75.75A63.04 63.04 0 0 1 56.25 -18.9A9.78 9.78 0 0 0 64.25 -10.18A43.48 43.48 0 0 1 56.52 76.09H-65.22A34.78 34.78 0 0 1 -75.5 8.08A9.78 9.78 0 0 0 -68.74 -2.85H-68.74A63.04 63.04 0 0 1 0 -75.75Z",
53
+ "eye": {
54
+ "x": 0,
55
+ "y": 6,
56
+ "scale": 0.8,
57
+ "openScale": 0.8,
58
+ "lookGain": 0.5
59
+ },
60
+ "colour": "#e9c8ff",
61
+ "eyeColour": "#141b16",
62
+ "keyline": "#9577a9"
63
+ },
64
+ {
65
+ "id": "droplet",
66
+ "d": "M0 -100.01A10.43 10.43 0 0 1 8.88 -95.05L61.35 -9.8A72.04 72.04 0 0 1 0 100A72.04 72.04 0 0 1 -61.35 -9.8L-8.88 -95.05A10.43 10.43 0 0 1 0 -100.01Z",
67
+ "eye": {
68
+ "x": 0,
69
+ "y": 21,
70
+ "scale": 0.8,
71
+ "openScale": 0.92,
72
+ "lookGain": 0.5
73
+ },
74
+ "colour": "#5cc8ff",
75
+ "eyeColour": "#141b16",
76
+ "keyline": "#148bbd"
77
+ },
78
+ {
79
+ "id": "diamond",
80
+ "d": "M0 -100.01A19.91 19.91 0 0 1 15.1 -93.08L85.22 -11.54A17.7 17.7 0 0 1 85.22 11.54L15.1 93.08A19.91 19.91 0 0 1 -15.1 93.08L-85.22 11.54A17.7 17.7 0 0 1 -85.22 -11.54L-15.1 -93.08A19.91 19.91 0 0 1 0 -100.01Z",
81
+ "eye": {
82
+ "x": 0,
83
+ "y": -1,
84
+ "scale": 0.8,
85
+ "openScale": 0.84,
86
+ "lookGain": 0.5
87
+ },
88
+ "colour": "#2ad4d4",
89
+ "eyeColour": "#141b16",
90
+ "keyline": "#139191"
91
+ },
92
+ {
93
+ "id": "gumdrop",
94
+ "d": "M0 -78A100 100 0 0 1 87.93 69.62A16 16 0 0 1 73.87 78H-73.87A16 16 0 0 1 -87.93 69.62A100 100 0 0 1 0 -78Z",
95
+ "eye": {
96
+ "x": 0,
97
+ "y": 1,
98
+ "scale": 0.8,
99
+ "openScale": 0.94,
100
+ "lookGain": 1
101
+ },
102
+ "colour": "#d27cf2",
103
+ "eyeColour": "#141b16",
104
+ "keyline": "#b35ed2"
105
+ },
106
+ {
107
+ "id": "egg",
108
+ "d": "M0 -100A45.31 45.31 0 0 1 32.04 -86.73A154.69 154.69 0 0 1 77.35 22.65A77.35 77.35 0 0 1 0 100A77.35 77.35 0 0 1 -77.35 22.65A154.69 154.69 0 0 1 -32.04 -86.73A45.31 45.31 0 0 1 0 -100Z",
109
+ "eye": {
110
+ "x": 0,
111
+ "y": 14,
112
+ "scale": 0.8,
113
+ "openScale": 1,
114
+ "lookGain": 1
115
+ },
116
+ "colour": "#ffc2d4",
117
+ "eyeColour": "#141b16",
118
+ "keyline": "#aa7384"
119
+ },
120
+ {
121
+ "id": "blob",
122
+ "d": "M0 -94.11A63.92 63.92 0 0 1 29.45 -66.43A22.68 22.68 0 0 0 48.1 -54.56A47.42 47.42 0 0 1 66.28 35.34H66.28A22.68 22.68 0 0 0 53.6 56.04A43.3 43.3 0 0 1 -25.31 81.32H-25.31A22.68 22.68 0 0 0 -49.38 72.19A35.05 35.05 0 0 1 -85.74 17.07A22.68 22.68 0 0 0 -83.68 -6.92A63.92 63.92 0 0 1 0 -94.11Z",
123
+ "eye": {
124
+ "x": 0,
125
+ "y": 7,
126
+ "scale": 0.8,
127
+ "openScale": 0.84,
128
+ "lookGain": 0.5
129
+ },
130
+ "colour": "#5ee6a8",
131
+ "eyeColour": "#141b16",
132
+ "keyline": "#129564"
133
+ }
134
+ ];
@@ -0,0 +1,90 @@
1
+ export type ColorDepth = 0 | 4 | 8 | 24;
2
+ export type Graphics = 'kitty' | 'iterm' | 'sixel' | null;
3
+ export interface Rgb {
4
+ r: number;
5
+ g: number;
6
+ b: number;
7
+ }
8
+ export interface Caps {
9
+ /** stdin and stdout are both terminals: prompts can be interactive. */
10
+ interactive: boolean;
11
+ color: ColorDepth;
12
+ /** Frame animation is allowed (not reduced motion, not a screen reader, not CI, not dumb). */
13
+ motion: boolean;
14
+ /** Why motion is off, for `genesis setup --explain` and the tests. */
15
+ stillBecause?: string;
16
+ /** Linear, append-only output for screen readers: no redraws, no spinners, numbered choices. */
17
+ screenReader: boolean;
18
+ unicode: boolean;
19
+ hyperlinks: boolean;
20
+ /** Desktop notification escape the terminal understands. */
21
+ notify: 'osc9' | 'osc777' | 'osc99' | null;
22
+ /** Taskbar/tab progress (OSC 9;4). Only where OSC 9 is not a notification. */
23
+ progress: boolean;
24
+ graphics: Graphics;
25
+ imagesAllowed: boolean;
26
+ /** DECSET 2026 answered "supported"; otherwise we still bracket frames (unknown private modes are ignored). */
27
+ sync: boolean | null;
28
+ /** The terminal's real background, when it told us. Edges are blended against it. */
29
+ background: Rgb | null;
30
+ /** One cell in device pixels (CSI 16 t), when the terminal reports it: images are drawn at exactly this grid. */
31
+ cell: {
32
+ width: number;
33
+ height: number;
34
+ } | null;
35
+ dark: boolean;
36
+ sound: boolean;
37
+ /** Recording a demo: account names, emails and identifiers are masked wherever setup prints them. */
38
+ screencast: boolean;
39
+ term: string;
40
+ columns: number;
41
+ rows: number;
42
+ }
43
+ export interface DetectInput {
44
+ env: Record<string, string | undefined>;
45
+ stdoutTTY: boolean;
46
+ stdinTTY: boolean;
47
+ columns?: number;
48
+ rows?: number;
49
+ flags?: {
50
+ animation?: boolean;
51
+ screenReader?: boolean;
52
+ sound?: boolean;
53
+ screencast?: boolean;
54
+ };
55
+ platform?: NodeJS.Platform;
56
+ }
57
+ /** Identify the emulator from what it advertises. tmux and screen hide the outer terminal, so they are their own kind. */
58
+ export declare function terminalKind(env: Record<string, string | undefined>): string;
59
+ export declare function detect(input: DetectInput): Caps;
60
+ /** Parse every reply the probe can receive. Pure, so the tests can feed it bytes from real terminals. */
61
+ export declare function parseReplies(data: string): {
62
+ background?: Rgb;
63
+ sync?: boolean;
64
+ kitty?: boolean;
65
+ sixel?: boolean;
66
+ cell?: {
67
+ width: number;
68
+ height: number;
69
+ };
70
+ da1: boolean;
71
+ };
72
+ /** The probe. The Kitty query is an APC string, so it goes only to emulators known to swallow APC. */
73
+ export declare const probeFor: (term: string) => string;
74
+ export declare const PROBE: string;
75
+ /** Ask the terminal. Resolves on the DA1 reply or after `timeoutMs`, restoring stdin exactly as it was. */
76
+ export declare function probe(caps: Caps, io: {
77
+ stdin: NodeJS.ReadStream;
78
+ stdout: NodeJS.WriteStream;
79
+ }, timeoutMs?: number): Promise<Caps>;
80
+ export declare function luminance({ r, g, b }: Rgb): number;
81
+ /**
82
+ * The desktop's own reduce-motion and screen-reader switches (macOS Accessibility, GNOME). Each answer is a short
83
+ * process with a deadline; a missing tool or a timeout just means "not set".
84
+ */
85
+ export declare function systemPreferences(platform?: NodeJS.Platform, run?: typeof runQuiet): Promise<{
86
+ reducedMotion: boolean;
87
+ screenReader: boolean;
88
+ }>;
89
+ declare function runQuiet(cmd: string, args: string[], timeoutMs?: number): Promise<string | null>;
90
+ export {};
@@ -0,0 +1,232 @@
1
+ /**
2
+ * What this terminal can do, decided once per setup run. Two passes:
3
+ * 1. detect(): environment only, synchronous, no I/O. Every "no" here is final (NO_COLOR, CI, a pipe, TERM=dumb,
4
+ * --no-animation, GENESIS_REDUCED_MOTION, a screen reader).
5
+ * 2. probe(): asks the terminal itself, in one write, with a single deadline: the background colour (OSC 11),
6
+ * synchronized output (DECRQM 2026), the Kitty graphics protocol, and primary device attributes (DA1, which also
7
+ * reports sixel). DA1 goes last; every terminal answers it, so its reply ends the probe without waiting out the
8
+ * deadline. Nothing is asked unless stdin and stdout are both a TTY.
9
+ * Only `genesis setup` (and a bare `genesis` on a TTY) load this module; normal commands never pay for it.
10
+ */
11
+ import { execFile } from 'node:child_process';
12
+ const truthy = (v) => v !== undefined && v !== '' && v !== '0' && v.toLowerCase() !== 'false';
13
+ function version(v) {
14
+ return (v ?? '').split(/[.\-+]/).map((x) => Number.parseInt(x, 10)).filter((n) => Number.isFinite(n));
15
+ }
16
+ function atLeast(v, min) {
17
+ const have = version(v);
18
+ if (!have.length)
19
+ return false;
20
+ for (let i = 0; i < min.length; i++) {
21
+ const a = have[i] ?? 0;
22
+ if (a !== min[i])
23
+ return a > min[i];
24
+ }
25
+ return true;
26
+ }
27
+ /** Identify the emulator from what it advertises. tmux and screen hide the outer terminal, so they are their own kind. */
28
+ export function terminalKind(env) {
29
+ const p = env.TERM_PROGRAM ?? '';
30
+ if (env.TMUX)
31
+ return 'tmux';
32
+ if (/^screen/.test(env.TERM ?? '') && !env.TERM_PROGRAM)
33
+ return 'screen';
34
+ if (env.KITTY_WINDOW_ID || env.TERM === 'xterm-kitty')
35
+ return 'kitty';
36
+ if (p === 'ghostty' || env.GHOSTTY_RESOURCES_DIR || env.TERM === 'xterm-ghostty')
37
+ return 'ghostty';
38
+ if (p === 'WezTerm' || env.WEZTERM_EXECUTABLE)
39
+ return 'wezterm';
40
+ if (p === 'iTerm.app' || env.LC_TERMINAL === 'iTerm2')
41
+ return 'iterm';
42
+ if (p === 'vscode')
43
+ return 'vscode';
44
+ if (p === 'Apple_Terminal')
45
+ return 'apple';
46
+ if (p === 'Hyper')
47
+ return 'hyper';
48
+ if (p === 'rio')
49
+ return 'rio';
50
+ if (env.WT_SESSION)
51
+ return 'windows-terminal';
52
+ if (env.ConEmuANSI === 'ON')
53
+ return 'conemu';
54
+ if (env.ALACRITTY_WINDOW_ID || env.ALACRITTY_LOG || env.TERM === 'alacritty')
55
+ return 'alacritty';
56
+ if (env.TERM === 'foot' || env.TERM === 'foot-extra')
57
+ return 'foot';
58
+ if (env.KONSOLE_VERSION)
59
+ return 'konsole';
60
+ if (env.VTE_VERSION)
61
+ return 'vte';
62
+ if (env.XTERM_VERSION)
63
+ return 'xterm';
64
+ if (env.MLTERM)
65
+ return 'mlterm';
66
+ return 'unknown';
67
+ }
68
+ export function detect(input) {
69
+ const { env } = input;
70
+ const term = terminalKind(env);
71
+ const dumb = env.TERM === 'dumb';
72
+ const ci = truthy(env.CI) || !!env.GITHUB_ACTIONS || !!env.BUILDKITE || !!env.GITLAB_CI || !!env.TF_BUILD;
73
+ const interactive = input.stdoutTTY && input.stdinTTY && !dumb && !ci;
74
+ // Colour: NO_COLOR wins over everything (no-color.org); FORCE_COLOR can raise a pipe, never past NO_COLOR.
75
+ let color = 0;
76
+ if (env.NO_COLOR === undefined || env.NO_COLOR === '') {
77
+ const force = env.FORCE_COLOR;
78
+ if (force !== undefined && force !== '')
79
+ color = force === '0' || force === 'false' ? 0 : force === '1' ? 4 : force === '2' ? 8 : 24;
80
+ else if (input.stdoutTTY && !dumb) {
81
+ const truecolor = /^(truecolor|24bit)$/i.test(env.COLORTERM ?? '')
82
+ || ['kitty', 'ghostty', 'wezterm', 'iterm', 'vscode', 'windows-terminal', 'alacritty', 'foot', 'konsole', 'rio', 'hyper'].includes(term)
83
+ || (term === 'vte' && Number(env.VTE_VERSION) >= 3600);
84
+ color = truecolor ? 24 : /256/.test(env.TERM ?? '') || term === 'apple' ? 8 : 4;
85
+ }
86
+ }
87
+ const screenReader = !!input.flags?.screenReader || truthy(env.GENESIS_SCREEN_READER) || truthy(env.ACCESSIBILITY);
88
+ const reduced = truthy(env.GENESIS_REDUCED_MOTION) || truthy(env.REDUCED_MOTION);
89
+ let stillBecause;
90
+ if (!input.stdoutTTY)
91
+ stillBecause = 'output is not a terminal';
92
+ else if (dumb)
93
+ stillBecause = 'TERM=dumb';
94
+ else if (ci)
95
+ stillBecause = 'CI';
96
+ else if (input.flags?.animation === false || truthy(env.GENESIS_NO_ANIMATION))
97
+ stillBecause = '--no-animation';
98
+ else if (reduced)
99
+ stillBecause = 'reduced motion';
100
+ else if (screenReader)
101
+ stillBecause = 'screen reader';
102
+ else if (color === 0)
103
+ stillBecause = 'NO_COLOR';
104
+ // The first locale variable that is set wins (POSIX order). Unset everywhere means a modern UTF-8 default; only an
105
+ // explicit non-UTF-8 locale (C, POSIX, ISO-8859-1, …) turns Unicode drawing off.
106
+ const lang = env.LC_ALL || env.LC_CTYPE || env.LANG || '';
107
+ const unicode = (input.platform ?? process.platform) === 'win32' ? term === 'windows-terminal' || term === 'vscode'
108
+ : !lang || /UTF-?8/i.test(lang);
109
+ const hyperlinks = input.stdoutTTY && !dumb && (truthy(env.FORCE_HYPERLINK)
110
+ || ['kitty', 'ghostty', 'wezterm', 'vscode', 'windows-terminal', 'foot', 'konsole', 'rio', 'alacritty'].includes(term)
111
+ || (term === 'iterm' && atLeast(env.TERM_PROGRAM_VERSION, [3, 1]))
112
+ || (term === 'vte' && Number(env.VTE_VERSION) >= 5000));
113
+ // OSC 9 is a notification on iTerm2/WezTerm/Ghostty but progress (9;4) on Windows Terminal/ConEmu; never mix them up.
114
+ const notify = !interactive ? null
115
+ : term === 'kitty' ? 'osc99'
116
+ : term === 'iterm' || term === 'wezterm' ? 'osc9'
117
+ : term === 'ghostty' || term === 'foot' || term === 'vte' || term === 'konsole' ? 'osc777' : null;
118
+ const progress = interactive && (term === 'windows-terminal' || term === 'conemu' || term === 'ghostty'
119
+ || (term === 'iterm' && atLeast(env.TERM_PROGRAM_VERSION, [3, 6])));
120
+ // Graphics are a probe result for Kitty/Ghostty (the reply proves it); iTerm2's protocol is advertised by name.
121
+ // Images are opt-in (GENESIS_SETUP_IMAGES=1) until the hand-off is verified on each emulator; text cells are the default.
122
+ const graphics = !interactive || color < 24 || !truthy(env.GENESIS_SETUP_IMAGES) ? null : term === 'iterm' || term === 'wezterm' ? 'iterm' : null;
123
+ return {
124
+ interactive, color, motion: !stillBecause, stillBecause, screenReader, unicode, hyperlinks, notify, progress, graphics,
125
+ imagesAllowed: truthy(env.GENESIS_SETUP_IMAGES),
126
+ sync: null, background: null, cell: null, dark: !/^(?:.*;)?(?:7|15)$/.test(env.COLORFGBG ?? ''), // COLORFGBG "0;15" means light
127
+ sound: !!input.flags?.sound || truthy(env.GENESIS_SOUND), screencast: !!input.flags?.screencast || truthy(env.GENESIS_SCREENCAST), term,
128
+ columns: input.columns ?? 80, rows: input.rows ?? 24,
129
+ };
130
+ }
131
+ /** Parse every reply the probe can receive. Pure, so the tests can feed it bytes from real terminals. */
132
+ export function parseReplies(data) {
133
+ const out = { da1: false };
134
+ const bg = /\x1b\]11;rgba?:([0-9a-f]{1,4})\/([0-9a-f]{1,4})\/([0-9a-f]{1,4})/i.exec(data);
135
+ if (bg) {
136
+ const ch = (h) => Math.round((Number.parseInt(h, 16) / (16 ** h.length - 1)) * 255);
137
+ out.background = { r: ch(bg[1]), g: ch(bg[2]), b: ch(bg[3]) };
138
+ }
139
+ const mode = /\x1b\[\?2026;(\d)\$y/.exec(data);
140
+ if (mode)
141
+ out.sync = mode[1] === '1' || mode[1] === '2';
142
+ if (/\x1b_Gi=31;OK\x1b\\/.test(data))
143
+ out.kitty = true;
144
+ const cell = /\x1b\[6;(\d+);(\d+)t/.exec(data);
145
+ if (cell && Number(cell[1]) > 0 && Number(cell[2]) > 0)
146
+ out.cell = { height: Number(cell[1]), width: Number(cell[2]) };
147
+ const da = /\x1b\[\?([\d;]+)c/.exec(data);
148
+ if (da) {
149
+ out.da1 = true;
150
+ out.sixel = da[1].split(';').includes('4');
151
+ }
152
+ return out;
153
+ }
154
+ const KITTY_QUERY = '\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\';
155
+ /** The probe. The Kitty query is an APC string, so it goes only to emulators known to swallow APC. */
156
+ export const probeFor = (term) => '\x1b]11;?\x1b\\' + '\x1b[?2026$p'
157
+ + (['kitty', 'ghostty', 'wezterm', 'konsole'].includes(term) ? KITTY_QUERY : '') + '\x1b[16t' + '\x1b[c';
158
+ export const PROBE = probeFor('kitty');
159
+ /** Ask the terminal. Resolves on the DA1 reply or after `timeoutMs`, restoring stdin exactly as it was. */
160
+ export async function probe(caps, io, timeoutMs = 180) {
161
+ if (!caps.interactive || caps.term === 'screen')
162
+ return caps;
163
+ const { stdin, stdout } = io;
164
+ const wasRaw = stdin.isRaw;
165
+ let data = '';
166
+ await new Promise((resolve) => {
167
+ const done = () => { clearTimeout(timer); stdin.off('data', onData); resolve(); };
168
+ const onData = (chunk) => { data += chunk.toString('latin1'); if (parseReplies(data).da1)
169
+ done(); };
170
+ const timer = setTimeout(done, timeoutMs);
171
+ try {
172
+ stdin.setRawMode(true);
173
+ }
174
+ catch {
175
+ done();
176
+ return;
177
+ }
178
+ stdin.on('data', onData);
179
+ stdin.resume();
180
+ stdout.write(probeFor(caps.term));
181
+ });
182
+ try {
183
+ stdin.setRawMode(wasRaw);
184
+ }
185
+ catch { /* already closed */ }
186
+ stdin.pause();
187
+ const r = parseReplies(data);
188
+ const background = r.background ?? null;
189
+ const dark = background ? luminance(background) < 0.4 : caps.dark;
190
+ // Inside tmux, images and graphics replies belong to the outer terminal; keep text only.
191
+ const graphics = caps.term === 'tmux' || caps.color < 24 || !caps.imagesAllowed ? null
192
+ : r.kitty ? 'kitty' : caps.graphics ?? (r.sixel && SIXEL_RENDERS.includes(caps.term) ? 'sixel' : null);
193
+ return { ...caps, background, dark, sync: r.sync ?? null, cell: r.cell ?? null,
194
+ graphics: graphics === 'sixel' && !r.cell ? null : graphics };
195
+ }
196
+ /** DA1 "4" claims sixel, but some emulators (xterm.js without its image addon) claim it and draw nothing. Trust it only here. */
197
+ const SIXEL_RENDERS = ['foot', 'konsole', 'windows-terminal', 'xterm', 'mlterm', 'contour'];
198
+ export function luminance({ r, g, b }) {
199
+ const l = (c) => { const s = c / 255; return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; };
200
+ return 0.2126 * l(r) + 0.7152 * l(g) + 0.0722 * l(b);
201
+ }
202
+ /**
203
+ * The desktop's own reduce-motion and screen-reader switches (macOS Accessibility, GNOME). Each answer is a short
204
+ * process with a deadline; a missing tool or a timeout just means "not set".
205
+ */
206
+ export async function systemPreferences(platform = process.platform, run = runQuiet) {
207
+ if (platform === 'darwin') {
208
+ const [motion, vo] = await Promise.all([
209
+ run('defaults', ['read', 'com.apple.universalaccess', 'reduceMotion']),
210
+ run('pgrep', ['-xq', 'VoiceOver']).then((r) => (r === null ? null : 'running')),
211
+ ]);
212
+ return { reducedMotion: motion?.trim() === '1', screenReader: vo === 'running' };
213
+ }
214
+ if (platform === 'linux') {
215
+ const [anim, orca] = await Promise.all([
216
+ run('gsettings', ['get', 'org.gnome.desktop.interface', 'enable-animations']),
217
+ run('gsettings', ['get', 'org.gnome.desktop.a11y.applications', 'screen-reader-enabled']),
218
+ ]);
219
+ return { reducedMotion: anim?.trim() === 'false', screenReader: orca?.trim() === 'true' };
220
+ }
221
+ return { reducedMotion: false, screenReader: false };
222
+ }
223
+ function runQuiet(cmd, args, timeoutMs = 250) {
224
+ return new Promise((resolve) => {
225
+ try {
226
+ execFile(cmd, args, { timeout: timeoutMs, windowsHide: true }, (error, stdout) => resolve(error ? null : String(stdout)));
227
+ }
228
+ catch {
229
+ resolve(null);
230
+ }
231
+ });
232
+ }