@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,118 @@
1
+ /**
2
+ * Put the Genesis MCP server into the user's agents. Each client is found by its own config home; nothing is written
3
+ * until the user has seen exactly which file or command changes and said yes. JSON files keep every other server,
4
+ * are written atomically, and the previous file is kept beside it as <name>.genesis-backup. A file we cannot parse is
5
+ * never touched.
6
+ *
7
+ * GUI apps (Claude Desktop) do not inherit the shell's PATH, so they get the absolute path of the npx next to the
8
+ * Node that is running this CLI; terminal clients get plain `npx`.
9
+ */
10
+ import { execFile } from 'node:child_process';
11
+ import { access, chmod, copyFile, mkdir, readFile, realpath, rename, stat, writeFile } from 'node:fs/promises';
12
+ import { homedir } from 'node:os';
13
+ import { dirname, join } from 'node:path';
14
+ export function defaultMcpEnv(version) {
15
+ return { home: homedir(), platform: process.platform, appData: process.env.APPDATA, execPath: process.execPath, version,
16
+ run: (cmd, args) => new Promise((resolve) => { try {
17
+ execFile(cmd, args, { timeout: 15_000, windowsHide: true }, (e, out) => resolve({ code: !e ? 0 : e.code === 'ENOENT' ? 127 : typeof e.code === 'number' ? e.code : 1, stdout: String(out ?? '') }));
18
+ }
19
+ catch {
20
+ resolve({ code: 127, stdout: '' });
21
+ } }) };
22
+ }
23
+ /** The npm package of the MCP server: the one place setup names the package scope. */
24
+ export const MCP_PACKAGE = '@onesi/genesis-mcp';
25
+ const exists = (p) => access(p).then(() => true, () => false);
26
+ const pkg = (e) => `${MCP_PACKAGE}@${e.version}`;
27
+ function appSupport(e, ...rest) {
28
+ if (e.platform === 'darwin')
29
+ return join(e.home, 'Library', 'Application Support', ...rest);
30
+ if (e.platform === 'win32')
31
+ return join(e.appData ?? join(e.home, 'AppData', 'Roaming'), ...rest);
32
+ return join(e.home, '.config', ...rest);
33
+ }
34
+ export function paths(e) {
35
+ return {
36
+ 'claude-desktop': join(appSupport(e, 'Claude'), 'claude_desktop_config.json'),
37
+ cursor: join(e.home, '.cursor', 'mcp.json'),
38
+ vscode: join(appSupport(e, 'Code', 'User'), 'mcp.json'),
39
+ windsurf: join(e.home, '.codeium', 'windsurf', 'mcp_config.json'),
40
+ codex: join(e.home, '.codex', 'config.toml'),
41
+ };
42
+ }
43
+ /** An absolute npx for GUI clients, when one sits next to this Node; otherwise plain npx. */
44
+ export async function npxFor(e, gui) {
45
+ if (!gui)
46
+ return 'npx';
47
+ const candidate = join(dirname(e.execPath), e.platform === 'win32' ? 'npx.cmd' : 'npx');
48
+ return (await exists(candidate)) ? candidate : 'npx';
49
+ }
50
+ async function readJson(path) {
51
+ try {
52
+ const text = await readFile(path, 'utf8');
53
+ if (!text.trim())
54
+ return { ok: true, value: {} };
55
+ const v = JSON.parse(text);
56
+ return v && typeof v === 'object' && !Array.isArray(v) ? { ok: true, value: v } : { ok: false };
57
+ }
58
+ catch (err) {
59
+ return err.code === 'ENOENT' ? { ok: true, value: {} } : { ok: false };
60
+ }
61
+ }
62
+ export async function detectClients(e) {
63
+ const p = paths(e);
64
+ const claudeCode = await e.run('claude', ['mcp', 'get', 'genesis']);
65
+ const has = async (file, key) => { const j = await readJson(file); return j.ok && !!j.value[key]?.genesis; };
66
+ const codexText = await readFile(p.codex, 'utf8').catch(() => '');
67
+ return [
68
+ { id: 'claude-code', name: 'Claude Code', target: `claude mcp add --scope user genesis -- npx -y ${pkg(e)}`,
69
+ present: claudeCode.code !== 127, installed: claudeCode.code === 0 },
70
+ { id: 'claude-desktop', name: 'Claude Desktop', target: p['claude-desktop'], present: await exists(dirname(p['claude-desktop'])), installed: await has(p['claude-desktop'], 'mcpServers') },
71
+ { id: 'cursor', name: 'Cursor', target: p.cursor, present: await exists(dirname(p.cursor)), installed: await has(p.cursor, 'mcpServers') },
72
+ { id: 'vscode', name: 'VS Code', target: p.vscode, present: await exists(dirname(p.vscode)), installed: await has(p.vscode, 'servers') },
73
+ { id: 'windsurf', name: 'Windsurf', target: p.windsurf, present: await exists(dirname(p.windsurf)), installed: await has(p.windsurf, 'mcpServers') },
74
+ { id: 'codex', name: 'Codex', target: p.codex, present: await exists(dirname(p.codex)), installed: /^\[mcp_servers\.genesis\]/m.test(codexText) },
75
+ ];
76
+ }
77
+ /** Write beside the real file (a dotfiles symlink stays a symlink) and keep the file's own mode; new files get 0600. */
78
+ async function writeAtomic(path, text) {
79
+ await mkdir(dirname(path), { recursive: true });
80
+ const target = await realpath(path).catch(() => path);
81
+ const mode = await stat(target).then((s) => s.mode & 0o777, () => 0o600);
82
+ if (await exists(target))
83
+ await copyFile(target, `${target}.genesis-backup`);
84
+ const tmp = `${target}.${process.pid}.tmp`;
85
+ await writeFile(tmp, text, { mode });
86
+ await chmod(tmp, mode);
87
+ await rename(tmp, target);
88
+ }
89
+ /** Install into one client. Returns a sentence for the summary, or throws with the reason. */
90
+ export async function install(e, id) {
91
+ const p = paths(e);
92
+ const args = ['-y', pkg(e)];
93
+ if (id === 'claude-code') {
94
+ const r = await e.run('claude', ['mcp', 'add', '--scope', 'user', 'genesis', '--', 'npx', ...args]);
95
+ if (r.code !== 0)
96
+ throw new Error('`claude mcp add` did not succeed');
97
+ return 'added with `claude mcp add` (user scope)';
98
+ }
99
+ if (id === 'codex') {
100
+ const text = await readFile(p.codex, 'utf8').catch((err) => { if (err.code === 'ENOENT')
101
+ return ''; throw err; });
102
+ if (/^\[mcp_servers\.genesis\]/m.test(text))
103
+ return 'already there';
104
+ const block = `\n[mcp_servers.genesis]\ncommand = "npx"\nargs = [${args.map((a) => JSON.stringify(a)).join(', ')}]\n`;
105
+ await writeAtomic(p.codex, text.replace(/\n*$/, '\n') + block);
106
+ return `added to ${p.codex}`;
107
+ }
108
+ const file = p[id];
109
+ const j = await readJson(file);
110
+ if (!j.ok)
111
+ throw new Error(`${file} is not valid JSON; left it untouched`);
112
+ const command = await npxFor(e, id === 'claude-desktop');
113
+ const entry = id === 'vscode' ? { type: 'stdio', command, args } : { command, args };
114
+ const key = id === 'vscode' ? 'servers' : 'mcpServers';
115
+ const next = { ...j.value, [key]: { ...(j.value[key] ?? {}), genesis: entry } };
116
+ await writeAtomic(file, JSON.stringify(next, null, 2) + '\n');
117
+ return `added to ${file}`;
118
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * A tiny vector rasterizer: SVG paths (M L H V Q A Z, absolute and relative) flattened to polygons, filled with
3
+ * analytic-quality anti-aliasing by supersampling scanlines (S×S samples a pixel, crossings sorted once a row).
4
+ * The canvas is premultiplied linear-light RGBA, so blended edges come out the way a browser draws the same SVG.
5
+ */
6
+ export type Pt = [number, number];
7
+ export type Poly = Pt[];
8
+ export type Rule = 'nonzero' | 'evenodd';
9
+ /** Affine transform [a, b, c, d, e, f]: x' = a x + c y + e, y' = b x + d y + f. */
10
+ export type Mat = [number, number, number, number, number, number];
11
+ export declare const IDENTITY: Mat;
12
+ export declare const mul: (m: Mat, n: Mat) => Mat;
13
+ export declare const translate: (x: number, y: number) => Mat;
14
+ export declare const scale: (sx: number, sy?: number) => Mat;
15
+ /** Flatten a path to polygons in its own units. Arcs and curves get enough segments to stay smooth at 8× zoom. */
16
+ export declare function parsePath(d: string): Poly[];
17
+ /** A rounded rectangle centred on (cx, cy), as a polygon. */
18
+ export declare function roundRect(cx: number, cy: number, w: number, h: number, r: number): Poly;
19
+ export declare function circle(cx: number, cy: number, r: number, reverse?: boolean): Poly;
20
+ export interface Color {
21
+ r: number;
22
+ g: number;
23
+ b: number;
24
+ }
25
+ export declare function hex(h: string): Color;
26
+ export declare const mix: (a: Color, b: Color, t: number) => Color;
27
+ /** Premultiplied linear RGBA. */
28
+ export declare class Canvas {
29
+ readonly width: number;
30
+ readonly height: number;
31
+ readonly samples: number;
32
+ readonly data: Float32Array;
33
+ constructor(width: number, height: number, samples?: number);
34
+ /** Fill polygons (already in pixel space after `m`) with a colour at opacity `alpha`. */
35
+ fill(polys: Poly[], color: Color, m?: Mat, rule?: Rule, alpha?: number): this;
36
+ /** Per-pixel coverage 0..1 of the polygons, S×S samples a pixel, crossings found once per sample row. */
37
+ coverage(polys: Poly[], rule: Rule): Float32Array;
38
+ /** The pixel as straight sRGB and alpha. */
39
+ pixel(x: number, y: number): {
40
+ c: Color;
41
+ a: number;
42
+ };
43
+ }
@@ -0,0 +1,253 @@
1
+ /**
2
+ * A tiny vector rasterizer: SVG paths (M L H V Q A Z, absolute and relative) flattened to polygons, filled with
3
+ * analytic-quality anti-aliasing by supersampling scanlines (S×S samples a pixel, crossings sorted once a row).
4
+ * The canvas is premultiplied linear-light RGBA, so blended edges come out the way a browser draws the same SVG.
5
+ */
6
+ export const IDENTITY = [1, 0, 0, 1, 0, 0];
7
+ export const mul = (m, n) => [
8
+ m[0] * n[0] + m[2] * n[1], m[1] * n[0] + m[3] * n[1],
9
+ m[0] * n[2] + m[2] * n[3], m[1] * n[2] + m[3] * n[3],
10
+ m[0] * n[4] + m[2] * n[5] + m[4], m[1] * n[4] + m[3] * n[5] + m[5],
11
+ ];
12
+ export const translate = (x, y) => [1, 0, 0, 1, x, y];
13
+ export const scale = (sx, sy = sx) => [sx, 0, 0, sy, 0, 0];
14
+ const cache = new Map();
15
+ /** Flatten a path to polygons in its own units. Arcs and curves get enough segments to stay smooth at 8× zoom. */
16
+ export function parsePath(d) {
17
+ const hit = cache.get(d);
18
+ if (hit)
19
+ return hit;
20
+ const tokens = d.match(/[a-zA-Z]|-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/g) ?? [];
21
+ const polys = [];
22
+ let cur = [];
23
+ let i = 0, cmd = '', x = 0, y = 0, sx = 0, sy = 0;
24
+ const num = () => Number(tokens[i++]);
25
+ const close = () => { if (cur.length > 2)
26
+ polys.push(cur); cur = []; };
27
+ while (i < tokens.length) {
28
+ if (/[a-zA-Z]/.test(tokens[i]))
29
+ cmd = tokens[i++];
30
+ const rel = cmd === cmd.toLowerCase();
31
+ switch (cmd.toUpperCase()) {
32
+ case 'M': {
33
+ close();
34
+ const nx = num(), ny = num();
35
+ x = rel ? x + nx : nx;
36
+ y = rel ? y + ny : ny;
37
+ sx = x;
38
+ sy = y;
39
+ cur.push([x, y]);
40
+ cmd = rel ? 'l' : 'L';
41
+ break;
42
+ }
43
+ case 'L': {
44
+ const nx = num(), ny = num();
45
+ x = rel ? x + nx : nx;
46
+ y = rel ? y + ny : ny;
47
+ cur.push([x, y]);
48
+ break;
49
+ }
50
+ case 'H': {
51
+ const nx = num();
52
+ x = rel ? x + nx : nx;
53
+ cur.push([x, y]);
54
+ break;
55
+ }
56
+ case 'V': {
57
+ const ny = num();
58
+ y = rel ? y + ny : ny;
59
+ cur.push([x, y]);
60
+ break;
61
+ }
62
+ case 'Q': {
63
+ let cx = num(), cy = num(), ex = num(), ey = num();
64
+ if (rel) {
65
+ cx += x;
66
+ cy += y;
67
+ ex += x;
68
+ ey += y;
69
+ }
70
+ for (let k = 1; k <= 12; k++) {
71
+ const t = k / 12, u = 1 - t;
72
+ cur.push([u * u * x + 2 * u * t * cx + t * t * ex, u * u * y + 2 * u * t * cy + t * t * ey]);
73
+ }
74
+ x = ex;
75
+ y = ey;
76
+ break;
77
+ }
78
+ case 'A': {
79
+ const rx = num(), ry = num(), rot = num(), large = num(), sweep = num();
80
+ let ex = num(), ey = num();
81
+ if (rel) {
82
+ ex += x;
83
+ ey += y;
84
+ }
85
+ cur.push(...arc(x, y, rx, ry, rot, large, sweep, ex, ey));
86
+ x = ex;
87
+ y = ey;
88
+ break;
89
+ }
90
+ case 'Z': {
91
+ x = sx;
92
+ y = sy;
93
+ close();
94
+ break;
95
+ }
96
+ default: i++; // unknown token: skip rather than loop forever
97
+ }
98
+ }
99
+ close();
100
+ cache.set(d, polys);
101
+ return polys;
102
+ }
103
+ /** SVG endpoint arc → points (SVG 1.1 F.6.5), excluding the start point. */
104
+ function arc(x1, y1, rx, ry, rotDeg, large, sweep, x2, y2) {
105
+ if (rx === 0 || ry === 0)
106
+ return [[x2, y2]];
107
+ const phi = (rotDeg * Math.PI) / 180, cos = Math.cos(phi), sin = Math.sin(phi);
108
+ const dx = (x1 - x2) / 2, dy = (y1 - y2) / 2;
109
+ const x1p = cos * dx + sin * dy, y1p = -sin * dx + cos * dy;
110
+ rx = Math.abs(rx);
111
+ ry = Math.abs(ry);
112
+ const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
113
+ if (lambda > 1) {
114
+ rx *= Math.sqrt(lambda);
115
+ ry *= Math.sqrt(lambda);
116
+ }
117
+ const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
118
+ const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
119
+ const coef = (large === sweep ? -1 : 1) * Math.sqrt(Math.max(0, num / den));
120
+ const cxp = (coef * rx * y1p) / ry, cyp = (-coef * ry * x1p) / rx;
121
+ const cx = cos * cxp - sin * cyp + (x1 + x2) / 2, cy = sin * cxp + cos * cyp + (y1 + y2) / 2;
122
+ const ang = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
123
+ const t1 = ang(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
124
+ let dt = ang((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
125
+ if (!sweep && dt > 0)
126
+ dt -= 2 * Math.PI;
127
+ else if (sweep && dt < 0)
128
+ dt += 2 * Math.PI;
129
+ const n = Math.max(4, Math.ceil(Math.abs(dt) / (Math.PI / 48)));
130
+ const pts = [];
131
+ for (let k = 1; k <= n; k++) {
132
+ const t = t1 + (dt * k) / n;
133
+ pts.push([cx + rx * Math.cos(t) * cos - ry * Math.sin(t) * sin, cy + rx * Math.cos(t) * sin + ry * Math.sin(t) * cos]);
134
+ }
135
+ pts[pts.length - 1] = [x2, y2];
136
+ return pts;
137
+ }
138
+ /** A rounded rectangle centred on (cx, cy), as a polygon. */
139
+ export function roundRect(cx, cy, w, h, r) {
140
+ r = Math.max(0, Math.min(r, w / 2, h / 2));
141
+ const pts = [], seg = 6;
142
+ const corner = (ox, oy, a0) => {
143
+ for (let k = 0; k <= seg; k++) {
144
+ const a = a0 + (k / seg) * (Math.PI / 2);
145
+ pts.push([ox + r * Math.cos(a), oy + r * Math.sin(a)]);
146
+ }
147
+ };
148
+ corner(cx + w / 2 - r, cy - h / 2 + r, -Math.PI / 2);
149
+ corner(cx + w / 2 - r, cy + h / 2 - r, 0);
150
+ corner(cx - w / 2 + r, cy + h / 2 - r, Math.PI / 2);
151
+ corner(cx - w / 2 + r, cy - h / 2 + r, Math.PI);
152
+ return pts;
153
+ }
154
+ export function circle(cx, cy, r, reverse = false) {
155
+ const n = Math.max(24, Math.ceil(r * 6)), pts = [];
156
+ for (let k = 0; k < n; k++) {
157
+ const a = ((reverse ? -k : k) / n) * Math.PI * 2;
158
+ pts.push([cx + r * Math.sin(a), cy - r * Math.cos(a)]);
159
+ }
160
+ return pts;
161
+ }
162
+ export function hex(h) {
163
+ const v = Number.parseInt(h.replace('#', ''), 16);
164
+ return { r: (v >> 16) & 255, g: (v >> 8) & 255, b: v & 255 };
165
+ }
166
+ const toLin = (c) => { const s = c / 255; return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; };
167
+ const toSrgb = (l) => Math.round(255 * (l <= 0.0031308 ? l * 12.92 : 1.055 * l ** (1 / 2.4) - 0.055));
168
+ export const mix = (a, b, t) => ({
169
+ r: toSrgb(toLin(a.r) + (toLin(b.r) - toLin(a.r)) * t), g: toSrgb(toLin(a.g) + (toLin(b.g) - toLin(a.g)) * t), b: toSrgb(toLin(a.b) + (toLin(b.b) - toLin(a.b)) * t),
170
+ });
171
+ /** Premultiplied linear RGBA. */
172
+ export class Canvas {
173
+ width;
174
+ height;
175
+ samples;
176
+ data;
177
+ constructor(width, height, samples = 4) {
178
+ this.width = width;
179
+ this.height = height;
180
+ this.samples = samples;
181
+ this.data = new Float32Array(width * height * 4);
182
+ }
183
+ /** Fill polygons (already in pixel space after `m`) with a colour at opacity `alpha`. */
184
+ fill(polys, color, m = IDENTITY, rule = 'nonzero', alpha = 1) {
185
+ const cov = this.coverage(polys.map((p) => p.map(([x, y]) => [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]])), rule);
186
+ const lr = toLin(color.r), lg = toLin(color.g), lb = toLin(color.b);
187
+ for (let i = 0; i < cov.length; i++) {
188
+ const a = cov[i] * alpha;
189
+ if (a <= 0)
190
+ continue;
191
+ const o = i * 4, k = 1 - a;
192
+ this.data[o] = lr * a + this.data[o] * k;
193
+ this.data[o + 1] = lg * a + this.data[o + 1] * k;
194
+ this.data[o + 2] = lb * a + this.data[o + 2] * k;
195
+ this.data[o + 3] = a + this.data[o + 3] * k;
196
+ }
197
+ return this;
198
+ }
199
+ /** Per-pixel coverage 0..1 of the polygons, S×S samples a pixel, crossings found once per sample row. */
200
+ coverage(polys, rule) {
201
+ const { width: W, height: H, samples: S } = this;
202
+ const out = new Float32Array(W * H);
203
+ const edges = [];
204
+ let minY = Infinity, maxY = -Infinity;
205
+ for (const p of polys)
206
+ for (let k = 0; k < p.length; k++) {
207
+ const [x0, y0] = p[k], [x1, y1] = p[(k + 1) % p.length];
208
+ if (y0 === y1)
209
+ continue;
210
+ edges.push(y0 < y1 ? [x0, y0, x1, y1, 1] : [x1, y1, x0, y0, -1]);
211
+ minY = Math.min(minY, y0, y1);
212
+ maxY = Math.max(maxY, y0, y1);
213
+ }
214
+ if (!edges.length)
215
+ return out;
216
+ const inc = 1 / (S * S);
217
+ const y0 = Math.max(0, Math.floor(minY * S)), y1 = Math.min(H * S - 1, Math.ceil(maxY * S));
218
+ const xs = [];
219
+ for (let sy = y0; sy <= y1; sy++) {
220
+ const yy = (sy + 0.5) / S;
221
+ xs.length = 0;
222
+ for (const [ax, ay, bx, by, w] of edges)
223
+ if (yy >= ay && yy < by)
224
+ xs.push([ax + ((yy - ay) / (by - ay)) * (bx - ax), w]);
225
+ if (xs.length < 2)
226
+ continue;
227
+ xs.sort((a, b) => a[0] - b[0]);
228
+ const row = Math.floor(sy / S) * W;
229
+ let wind = 0;
230
+ for (let k = 0; k < xs.length - 1; k++) {
231
+ wind += xs[k][1];
232
+ const inside = rule === 'evenodd' ? (k + 1) % 2 === 1 : wind !== 0;
233
+ if (!inside)
234
+ continue;
235
+ // samples sx with centre (sx+.5)/S in [xs[k], xs[k+1])
236
+ const from = Math.max(0, Math.ceil(xs[k][0] * S - 0.5)), to = Math.min(W * S - 1, Math.ceil(xs[k + 1][0] * S - 0.5) - 1);
237
+ for (let sx = from; sx <= to; sx++)
238
+ out[row + Math.floor(sx / S)] += inc;
239
+ }
240
+ }
241
+ for (let i = 0; i < out.length; i++)
242
+ if (out[i] > 1)
243
+ out[i] = 1;
244
+ return out;
245
+ }
246
+ /** The pixel as straight sRGB and alpha. */
247
+ pixel(x, y) {
248
+ const o = (y * this.width + x) * 4, a = this.data[o + 3];
249
+ if (a <= 1e-4)
250
+ return { c: { r: 0, g: 0, b: 0 }, a: 0 };
251
+ return { c: { r: toSrgb(this.data[o] / a), g: toSrgb(this.data[o + 1] / a), b: toSrgb(this.data[o + 2] / a) }, a: Math.min(1, a) };
252
+ }
253
+ }
@@ -0,0 +1,2 @@
1
+ export declare function chime(rate?: number): Buffer;
2
+ export declare function play(): Promise<void>;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The one sound setup can make, only with --sound or GENESIS_SOUND=1: a soft two-note chime (a fifth, 660 → 990 Hz)
3
+ * synthesised here as a 0.5 s WAV and played by the system's own player (afplay, paplay or aplay). If none is
4
+ * present, there is simply no sound.
5
+ */
6
+ import { execFile } from 'node:child_process';
7
+ import { writeFile, unlink } from 'node:fs/promises';
8
+ import { tmpdir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ export function chime(rate = 22050) {
11
+ const n = Math.floor(rate * 0.55), data = Buffer.alloc(n * 2);
12
+ for (let i = 0; i < n; i++) {
13
+ const t = i / rate;
14
+ const note = (f, at) => { const u = t - at; if (u < 0)
15
+ return 0; const env = Math.min(1, u / 0.006) * Math.exp(-u * 7); return env * (Math.sin(2 * Math.PI * f * u) + 0.18 * Math.sin(4 * Math.PI * f * u)); };
16
+ const v = 0.16 * (note(660, 0) + 0.85 * note(990, 0.09));
17
+ data.writeInt16LE(Math.max(-32767, Math.min(32767, Math.round(v * 32767))), i * 2);
18
+ }
19
+ const h = Buffer.alloc(44);
20
+ h.write('RIFF', 0);
21
+ h.writeUInt32LE(36 + data.length, 4);
22
+ h.write('WAVE', 8);
23
+ h.write('fmt ', 12);
24
+ h.writeUInt32LE(16, 16);
25
+ h.writeUInt16LE(1, 20);
26
+ h.writeUInt16LE(1, 22);
27
+ h.writeUInt32LE(rate, 24);
28
+ h.writeUInt32LE(rate * 2, 28);
29
+ h.writeUInt16LE(2, 32);
30
+ h.writeUInt16LE(16, 34);
31
+ h.write('data', 36);
32
+ h.writeUInt32LE(data.length, 40);
33
+ return Buffer.concat([h, data]);
34
+ }
35
+ export async function play() {
36
+ const file = join(tmpdir(), `genesis-chime-${process.pid}.wav`);
37
+ try {
38
+ await writeFile(file, chime());
39
+ const players = process.platform === 'darwin' ? [['afplay', [file]]] : [['paplay', [file]], ['aplay', ['-q', file]]];
40
+ for (const [cmd, args] of players) {
41
+ const ok = await new Promise((r) => { try {
42
+ execFile(cmd, args, { timeout: 3000 }, (e) => r(!e));
43
+ }
44
+ catch {
45
+ r(false);
46
+ } });
47
+ if (ok)
48
+ break;
49
+ }
50
+ }
51
+ finally {
52
+ await unlink(file).catch(() => { });
53
+ }
54
+ }
@@ -0,0 +1,24 @@
1
+ export declare const STEPS: readonly ["signin", "context", "key", "mcp", "try", "next"];
2
+ export type StepId = typeof STEPS[number];
3
+ export type StepState = 'done' | 'skipped';
4
+ export interface SetupState {
5
+ version: 1;
6
+ steps: Partial<Record<StepId, {
7
+ state: StepState;
8
+ at: string;
9
+ note?: string;
10
+ }>>;
11
+ welcomed?: string;
12
+ }
13
+ export declare function configDir(env: Record<string, string | undefined>, platform?: NodeJS.Platform, home?: string): string;
14
+ export declare class SetupStore {
15
+ readonly path: string;
16
+ constructor(path: string);
17
+ static for(env: Record<string, string | undefined>): SetupStore;
18
+ read(): Promise<SetupState>;
19
+ write(s: SetupState): Promise<void>;
20
+ mark(step: StepId, state: StepState, note?: string): Promise<void>;
21
+ }
22
+ /** The first step not yet done (skipped steps are offered again), or null when setup is complete. */
23
+ export declare function nextStep(s: SetupState): StepId | null;
24
+ export declare const complete: (s: SetupState) => boolean;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Where setup is up to, so it can stop at any step and pick up there: a small JSON file in the user's config dir
3
+ * (XDG_CONFIG_HOME/genesis, ~/.config/genesis, or %APPDATA%\genesis). It never holds a credential; the login stays
4
+ * in the OS keychain and an API key is shown once or kept in Blinds.
5
+ */
6
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
7
+ import { homedir } from 'node:os';
8
+ import { dirname, join } from 'node:path';
9
+ export const STEPS = ['signin', 'context', 'key', 'mcp', 'try', 'next'];
10
+ export function configDir(env, platform = process.platform, home = homedir()) {
11
+ if (env.GENESIS_CONFIG_DIR)
12
+ return env.GENESIS_CONFIG_DIR;
13
+ if (platform === 'win32' && env.APPDATA)
14
+ return join(env.APPDATA, 'genesis');
15
+ return join(env.XDG_CONFIG_HOME || join(home, '.config'), 'genesis');
16
+ }
17
+ export class SetupStore {
18
+ path;
19
+ constructor(path) {
20
+ this.path = path;
21
+ }
22
+ static for(env) { return new SetupStore(join(configDir(env), 'setup.json')); }
23
+ async read() {
24
+ try {
25
+ const v = JSON.parse(await readFile(this.path, 'utf8'));
26
+ if (v && v.version === 1 && v.steps && typeof v.steps === 'object')
27
+ return v;
28
+ }
29
+ catch { /* first run, or unreadable: start fresh */ }
30
+ return { version: 1, steps: {} };
31
+ }
32
+ async write(s) {
33
+ await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
34
+ const tmp = `${this.path}.${process.pid}.tmp`;
35
+ await writeFile(tmp, JSON.stringify(s, null, 2) + '\n', { mode: 0o600 });
36
+ await rename(tmp, this.path);
37
+ }
38
+ async mark(step, state, note) {
39
+ const s = await this.read();
40
+ s.steps[step] = { state, at: new Date().toISOString(), ...(note ? { note } : {}) };
41
+ await this.write(s);
42
+ }
43
+ }
44
+ /** The first step not yet done (skipped steps are offered again), or null when setup is complete. */
45
+ export function nextStep(s) {
46
+ return STEPS.find((id) => s.steps[id]?.state !== 'done') ?? null;
47
+ }
48
+ export const complete = (s) => STEPS.every((id) => s.steps[id]);
@@ -0,0 +1,69 @@
1
+ import type { Caps } from './caps.js';
2
+ export declare const strip: (s: string) => string;
3
+ /** Display width: wide East Asian and emoji count two. Enough for our own copy and server names passed through safe(). */
4
+ export declare function width(s: string): number;
5
+ export type Key = {
6
+ name: string;
7
+ ch?: string;
8
+ ctrl?: boolean;
9
+ };
10
+ export declare function parseKeys(data: string): Key[];
11
+ export declare class Interrupted extends Error {
12
+ constructor();
13
+ }
14
+ export declare class Term {
15
+ readonly caps: Caps;
16
+ readonly stdout: NodeJS.WriteStream;
17
+ readonly stdin: NodeJS.ReadStream;
18
+ private live;
19
+ private liveWidth;
20
+ private liveEmpty;
21
+ private cleanups;
22
+ private keyWaiters;
23
+ private onData;
24
+ /** Every line setup draws sits two columns in from the edge; a step's own lines sit four in, under its header. */
25
+ margin: string;
26
+ focused: boolean;
27
+ focusListeners: Set<(focused: boolean) => void>;
28
+ /** Ctrl-C while nothing reads keys (a network wait). Setup replaces it to say where it will pick up. */
29
+ onInterrupt: () => void;
30
+ private restored;
31
+ constructor(caps: Caps, stdout: NodeJS.WriteStream, stdin: NodeJS.ReadStream);
32
+ write(s: string): void;
33
+ get columns(): number;
34
+ /** Start the session: raw keys, focus events, bracketed paste, hidden cursor, our title pushed onto the stack. */
35
+ start(title: string): void;
36
+ title(t: string): void;
37
+ /** Tab/taskbar progress (OSC 9;4): 0 hidden, 1 value, 3 indeterminate. */
38
+ progress(state: 0 | 1 | 3, value?: number): void;
39
+ /** A desktop notification, only when the window is not in front (the user went to the browser). */
40
+ notify(title: string, body: string): void;
41
+ /** DECSCUSR: 6 steady bar (the emblem's bar as the caret), 0 the user's default. */
42
+ cursorShape(n: 0 | 2 | 6): void;
43
+ cursor(show: boolean): void;
44
+ /** OSC 8 where the terminal supports it; elsewhere the label and the address, so the URL is never hidden. */
45
+ link(url: string, text?: string): string;
46
+ /**
47
+ * Replace the live region with `lines`, in one synchronized frame. The region shrinks and grows as needed; lines are
48
+ * clipped to the window so a narrow terminal never wraps a frame into garbage.
49
+ */
50
+ frame(lines: string[]): void;
51
+ /** Freeze the live region into scrollback; the next frame starts below it. */
52
+ commit(): void;
53
+ /** Plain append, for screen readers and still mode: never rewrites what was said. */
54
+ say(line?: string): void;
55
+ /** In screencast mode, every identifier (h_…, o_…, w_…, pr_…, sess_…, keys) and email that setup prints is masked. */
56
+ mask(line: string): string;
57
+ private dispatch;
58
+ /** The next key; Ctrl-C rejects with Interrupted so every step can stop cleanly and be resumed. */
59
+ key(signal?: AbortSignal): Promise<Key>;
60
+ /** Any pending key, without waiting (for "press any key to skip" during animations). */
61
+ pollKey(): Promise<Key>;
62
+ restore(): void;
63
+ }
64
+ /** Masks the text between escape sequences, so an identifier right after a colour code (…m o_…) is still found. */
65
+ export declare function maskIdentifiers(line: string): string;
66
+ /** Clip to `cols` display columns, keeping escape sequences intact and closing any open style and link. */
67
+ export declare function clip(line: string, cols: number): string;
68
+ /** A steady clock: calls `draw(t)` at `fps` until it returns false, sleeping the remainder of each frame. */
69
+ export declare function animate(fps: number, draw: (t: number) => boolean | Promise<boolean>, signal?: AbortSignal): Promise<void>;