githolon 0.1.2

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 (3) hide show
  1. package/LICENSE.md +36 -0
  2. package/dist/cli.mjs +1149 -0
  3. package/package.json +39 -0
package/LICENSE.md ADDED
@@ -0,0 +1,36 @@
1
+ # Nomos Pre-Release License (v1)
2
+
3
+ Copyright © 2026 Captain App Ltd. All rights reserved.
4
+
5
+ This is a pre-release of Nomos. This license gives you what you need to BUILD
6
+ with it; we keep the rest for now.
7
+
8
+ ## You may
9
+
10
+ - install and use these packages to author Nomos domains, compile them, and
11
+ deploy them to Nomos Cloud or any Nomos instance Captain App operates or
12
+ authorizes;
13
+ - build, run, and ship applications on top of them — including commercial ones;
14
+ - keep everything that's yours: code you write, and everything these tools
15
+ generate FOR you (scaffolds from `create-githolon` / `githolon generate`, generated
16
+ clients, compiled domain packages) carries NO restriction from us — it is
17
+ yours outright.
18
+
19
+ ## You may not
20
+
21
+ - redistribute these packages or their source, in whole or in part, outside
22
+ your team;
23
+ - modify them or build derivative tools, SDKs, or runtimes from their source;
24
+ - offer the Nomos runtime, or anything materially similar, as a hosted service;
25
+ - reverse-engineer the holon wasm runtime.
26
+
27
+ ## The rest
28
+
29
+ Provided **as is**, with no warranty of any kind; to the maximum extent
30
+ permitted by law, Captain App Ltd accepts no liability arising from your use.
31
+ This license terminates automatically if you breach it.
32
+
33
+ Want the kernel source, broader rights, or to do something this doesn't cover?
34
+ **Ask: jack@captainapp.co.uk.** The plan is to open Nomos up gradually, with
35
+ the people actually using it — telling us what you're building is how that
36
+ happens faster.
package/dist/cli.mjs ADDED
@@ -0,0 +1,1149 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { spawnSync as spawnSync3 } from "node:child_process";
5
+ import { createRequire } from "node:module";
6
+ import { dirname as dirname2, join as join4 } from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+
9
+ // src/generate.ts
10
+ import { mkdirSync, existsSync, writeFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+
13
+ // src/naming.ts
14
+ function words(raw) {
15
+ return raw.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-\s]+/g, " ").trim().toLowerCase().split(" ").filter((w) => w.length > 0);
16
+ }
17
+ var cap = (w) => w.length === 0 ? w : w[0].toUpperCase() + w.slice(1);
18
+ function pascalCase(raw) {
19
+ return words(raw).map(cap).join("");
20
+ }
21
+ function camelCase(raw) {
22
+ return words(raw).map((w, i) => i === 0 ? w : cap(w)).join("");
23
+ }
24
+ function snakeCase(raw) {
25
+ return words(raw).join("_");
26
+ }
27
+ function screamingSnakeCase(raw) {
28
+ return words(raw).join("_").toUpperCase();
29
+ }
30
+ function assertValidName(raw) {
31
+ const ws = words(raw);
32
+ if (ws.length === 0) {
33
+ throw new Error(`Invalid name: '${raw}' \u2014 give at least one word, e.g. 'work_order'.`);
34
+ }
35
+ for (const w of ws) {
36
+ if (!/^[a-z][a-z0-9]*$/.test(w)) {
37
+ throw new Error(
38
+ `Invalid name token '${w}' in '${raw}' \u2014 words must be alphanumeric and start with a letter (got after normalisation: ${ws.join(", ")}).`
39
+ );
40
+ }
41
+ }
42
+ return ws;
43
+ }
44
+
45
+ // src/templates.ts
46
+ function namesFor(raw) {
47
+ return {
48
+ raw,
49
+ pascal: pascalCase(raw),
50
+ camel: camelCase(raw),
51
+ statusesConst: `${screamingSnakeCase(raw)}_STATUSES`
52
+ };
53
+ }
54
+ var HEADER = (raw, kind) => `/**
55
+ * The \`${raw}\` ${kind} \u2014 scaffolded by \`githolon generate\`.
56
+ *
57
+ * Authoring rules (see @githolon/dsl): write ONLY aggregates (typed fields, each
58
+ * tagged with a merge Driver) and directives (Zod payload + declarative plan).
59
+ * You never write apply/fold/merge \u2014 the kernel owns the merge algebra.
60
+ *
61
+ * Convention: the fail-closed \`Conflict\` driver is the default for scalars.
62
+ * Relax only what differs: \`.merge(Lww)\`, \`.merge(AddWins)\`, \`.merge(MapOf(Lww))\`.
63
+ */`;
64
+ function aggregateBlock(n) {
65
+ return `export const ${n.statusesConst} = ["active", "retired"] as const;
66
+
67
+ export const ${n.pascal} = aggregate("${n.pascal}", {
68
+ // Default driver is the fail-closed \`Conflict\`: concurrent edits surface
69
+ // as a conflict rather than silently coalescing. Declare only what differs.
70
+ label: t.string().merge(Conflict),
71
+ // Overrides \u2014 last-write-wins / set-union / per-key map:
72
+ pos: t.int().merge(Lww),
73
+ status: t.enum(${n.statusesConst}).merge(Lww),
74
+ tags: t.set(t.string()).merge(AddWins),
75
+ customFields: t.map(t.string()).merge(MapOf(Lww)),
76
+ });`;
77
+ }
78
+ function domainTemplate(raw) {
79
+ const n = namesFor(raw);
80
+ return `${HEADER(raw, "domain")}
81
+ import { z } from "zod";
82
+ import {
83
+ aggregate,
84
+ directive,
85
+ t,
86
+ Conflict,
87
+ Lww,
88
+ AddWins,
89
+ MapOf,
90
+ set,
91
+ addToSet,
92
+ setEntry,
93
+ } from "@githolon/dsl";
94
+
95
+ ${aggregateBlock(n)}
96
+
97
+ /** create${n.pascal} \u2014 .creates the aggregate; seeds label/pos/status. */
98
+ export const create${n.pascal} = directive("create${n.pascal}")
99
+ .creates(${n.pascal})
100
+ .payload(
101
+ z.object({
102
+ label: z.string(),
103
+ pos: z.number().int(),
104
+ status: z.enum(${n.statusesConst}),
105
+ }),
106
+ )
107
+ .plan((p) => [
108
+ set(${n.pascal}, "label", p.label),
109
+ set(${n.pascal}, "pos", p.pos),
110
+ set(${n.pascal}, "status", p.status),
111
+ ]);
112
+
113
+ /** move${n.pascal} \u2014 .mutates pos. */
114
+ export const move${n.pascal} = directive("move${n.pascal}")
115
+ .mutates(${n.pascal})
116
+ .payload(z.object({ pos: z.number().int() }))
117
+ .plan((p) => [set(${n.pascal}, "pos", p.pos)]);
118
+
119
+ /** tag${n.pascal} \u2014 .mutates tags (add-wins union). */
120
+ export const tag${n.pascal} = directive("tag${n.pascal}")
121
+ .mutates(${n.pascal})
122
+ .payload(z.object({ tags: z.array(z.string()) }))
123
+ .plan((p) => [addToSet(${n.pascal}, "tags", p.tags)]);
124
+
125
+ /** setCustomFieldOn${n.pascal} \u2014 .mutates customFields (a map entry). */
126
+ export const setCustomFieldOn${n.pascal} = directive("setCustomFieldOn${n.pascal}")
127
+ .mutates(${n.pascal})
128
+ .payload(z.object({ key: z.string(), value: z.string() }))
129
+ .plan((p) => [setEntry(${n.pascal}, "customFields", p.key, p.value)]);
130
+ `;
131
+ }
132
+ function aggregateTemplate(raw) {
133
+ const n = namesFor(raw);
134
+ return `${HEADER(raw, "aggregate")}
135
+ import { aggregate, t, Conflict, Lww, AddWins, MapOf } from "@githolon/dsl";
136
+
137
+ ${aggregateBlock(n)}
138
+ `;
139
+ }
140
+ function intentTemplate(raw) {
141
+ const n = namesFor(raw);
142
+ return `${HEADER(raw, "intent (directive)")}
143
+ import { z } from "zod";
144
+ import { aggregate, directive, t, Conflict, set } from "@githolon/dsl";
145
+
146
+ // Stub target \u2014 in a real domain, import the existing aggregate handle instead.
147
+ export const ${n.pascal} = aggregate("${n.pascal}", {
148
+ label: t.string().merge(Conflict),
149
+ });
150
+
151
+ /** ${n.camel} \u2014 .mutates the target aggregate. */
152
+ export const ${n.camel} = directive("${n.camel}")
153
+ .mutates(${n.pascal})
154
+ .payload(z.object({ label: z.string() }))
155
+ .plan((p) => [set(${n.pascal}, "label", p.label)]);
156
+ `;
157
+ }
158
+
159
+ // src/generate.ts
160
+ var RENDERERS = {
161
+ domain: domainTemplate,
162
+ aggregate: aggregateTemplate,
163
+ intent: intentTemplate
164
+ };
165
+ function renderScaffold(kind, name) {
166
+ assertValidName(name);
167
+ const render = RENDERERS[kind];
168
+ if (!render) {
169
+ throw new Error(`Unknown generator kind '${kind}'. Use: domain | aggregate | intent.`);
170
+ }
171
+ return render(name);
172
+ }
173
+ function generate(opts) {
174
+ const contents = renderScaffold(opts.kind, opts.name);
175
+ const fileName = `${snakeCase(opts.name)}.ts`;
176
+ const path = join(opts.outDir, fileName);
177
+ if (existsSync(path) && !opts.force) {
178
+ throw new Error(
179
+ `Refusing to overwrite existing file: ${path}
180
+ Re-run with --force to replace it.`
181
+ );
182
+ }
183
+ mkdirSync(opts.outDir, { recursive: true });
184
+ writeFileSync(path, contents, "utf8");
185
+ return { path, contents };
186
+ }
187
+
188
+ // src/cloud.ts
189
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
190
+ import { join as join2 } from "node:path";
191
+ import { homedir } from "node:os";
192
+ var DEFAULT_CLOUD = "https://nomos.captainapp.co.uk";
193
+ var DEFAULT_AUTH_URL = "https://kjbcjkihxskuwwfdqklt.supabase.co";
194
+ var DEFAULT_AUTH_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtqYmNqa2loeHNrdXd3ZmRxa2x0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTA3MDU2OTAsImV4cCI6MjA2NjI4MTY5MH0.V9e7XsuTlTOLqefOIedTqlBiTxUSn4O5FZSPWwAxiSI";
195
+ function cloudBase(flag) {
196
+ return (flag ?? process.env["NOMOS_CLOUD"] ?? DEFAULT_CLOUD).replace(/\/+$/, "");
197
+ }
198
+ function authUrl() {
199
+ return (process.env["HOLON_AUTH_URL"] ?? DEFAULT_AUTH_URL).replace(/\/+$/, "");
200
+ }
201
+ function authAnonKey() {
202
+ return process.env["HOLON_AUTH_ANON_KEY"] ?? DEFAULT_AUTH_ANON_KEY;
203
+ }
204
+ function configDir() {
205
+ return process.env["HOLON_CONFIG_DIR"] ?? join2(homedir(), ".holon");
206
+ }
207
+ function credsPath() {
208
+ return join2(configDir(), "credentials.json");
209
+ }
210
+ function loadCreds() {
211
+ if (!existsSync2(credsPath())) return { version: 1, secrets: {} };
212
+ return JSON.parse(readFileSync(credsPath(), "utf8"));
213
+ }
214
+ function saveCreds(c) {
215
+ mkdirSync2(configDir(), { recursive: true });
216
+ writeFileSync2(credsPath(), JSON.stringify(c, null, 2) + "\n", "utf8");
217
+ chmodSync(credsPath(), 384);
218
+ }
219
+ var secretKey = (cloud, ws) => `${cloud} ${ws}`;
220
+ function getSecret(cloud, ws) {
221
+ return loadCreds().secrets[secretKey(cloud, ws)];
222
+ }
223
+ function setSecret(cloud, ws, secret) {
224
+ const c = loadCreds();
225
+ c.secrets[secretKey(cloud, ws)] = secret;
226
+ saveCreds(c);
227
+ }
228
+ function setPrincipal(principal) {
229
+ const c = loadCreds();
230
+ c.principal = principal;
231
+ saveCreds(c);
232
+ }
233
+ function principalHeaders(principal) {
234
+ return /^[\w-]+\.[\w-]+\.[\w-]+$/.test(principal) ? { "x-nomos-auth": principal } : { "x-nomos-principal": principal };
235
+ }
236
+ function sessionFromGotrue(d) {
237
+ if (typeof d.access_token !== "string" || typeof d.refresh_token !== "string") return void 0;
238
+ return {
239
+ url: authUrl(),
240
+ uid: d.user?.id ?? "",
241
+ anonymous: d.user?.is_anonymous === true,
242
+ accessToken: d.access_token,
243
+ refreshToken: d.refresh_token,
244
+ expiresAt: d.expires_at ?? Math.floor(Date.now() / 1e3) + (d.expires_in ?? 3600)
245
+ };
246
+ }
247
+ async function gotrue(path, body) {
248
+ const r = await fetch(`${authUrl()}${path}`, {
249
+ method: "POST",
250
+ headers: { apikey: authAnonKey(), "content-type": "application/json" },
251
+ body
252
+ });
253
+ return await r.json().catch(() => ({ error: `auth provider returned ${r.status} with no body` }));
254
+ }
255
+ function saveSession(s) {
256
+ const c = loadCreds();
257
+ c.session = s;
258
+ saveCreds(c);
259
+ }
260
+ async function sessionToken() {
261
+ const c = loadCreds();
262
+ if (c.session === void 0) return void 0;
263
+ if (c.session.expiresAt - 60 > Math.floor(Date.now() / 1e3)) return c.session.accessToken;
264
+ const d = await gotrue("/auth/v1/token?grant_type=refresh_token", JSON.stringify({ refresh_token: c.session.refreshToken }));
265
+ const s = sessionFromGotrue(d);
266
+ if (s === void 0) {
267
+ throw new Error(
268
+ `session refresh failed (${d.error_description ?? d.error ?? d.msg ?? "unknown"}) \u2014 githolon login --agent (or --token) again`
269
+ );
270
+ }
271
+ saveSession(s);
272
+ return s.accessToken;
273
+ }
274
+ function discoverDeployJson(cwd, explicit) {
275
+ if (explicit !== void 0) {
276
+ if (!existsSync2(explicit)) throw new Error(`deploy body not found: ${explicit}`);
277
+ return explicit;
278
+ }
279
+ const buildDir = join2(cwd, "build");
280
+ if (!existsSync2(buildDir)) {
281
+ throw new Error("no build/ directory \u2014 run `githolon compile` first (or pass --file <path>)");
282
+ }
283
+ const bodies = readdirSync(buildDir).filter((f) => f.endsWith(".deploy.json"));
284
+ if (bodies.length === 0) {
285
+ throw new Error("no build/*.deploy.json \u2014 run `githolon compile` first (or pass --file <path>)");
286
+ }
287
+ if (bodies.length > 1) {
288
+ throw new Error(`multiple deploy bodies in build/ (${bodies.join(", ")}) \u2014 pass --file <path>`);
289
+ }
290
+ return join2(buildDir, bodies[0]);
291
+ }
292
+ var out = (s) => void process.stdout.write(s + "\n");
293
+ var err = (s) => void process.stderr.write("error: " + s + "\n");
294
+ async function login(opts) {
295
+ if (opts.agent !== true && opts.token === void 0) {
296
+ err(
297
+ "browser login is not wired up yet \u2014 use one of:\n githolon login --agent anonymous sign-in (verified identity, linkable later)\n githolon login --token <refresh> adopt an existing captain app session"
298
+ );
299
+ return 1;
300
+ }
301
+ const d = opts.agent === true ? await gotrue("/auth/v1/signup", "{}") : await gotrue("/auth/v1/token?grant_type=refresh_token", JSON.stringify({ refresh_token: opts.token }));
302
+ const s = sessionFromGotrue(d);
303
+ if (s === void 0) {
304
+ err(`login failed: ${d.error_description ?? d.error ?? d.msg ?? JSON.stringify(d)}`);
305
+ return 1;
306
+ }
307
+ saveSession(s);
308
+ out(`\u2713 logged in${s.anonymous ? " (anonymous agent identity \u2014 linkable to a full account later)" : ""}`);
309
+ out(` principal uid ${s.uid}`);
310
+ out(` session saved \u2192 ${credsPath()} (auto-refreshed; births now ride x-nomos-auth)`);
311
+ return 0;
312
+ }
313
+ async function whoami() {
314
+ const c = loadCreds();
315
+ if (c.session !== void 0) {
316
+ out(`session: ${c.session.anonymous ? "anonymous agent" : "account"} uid ${c.session.uid} (auth ${c.session.url})`);
317
+ return 0;
318
+ }
319
+ if (c.principal !== void 0) {
320
+ out(`bare principal (transitional, unverified): ${c.principal}`);
321
+ return 0;
322
+ }
323
+ out("not logged in \u2014 githolon login --agent, or pass --principal <uid> per birth");
324
+ return 0;
325
+ }
326
+ async function logout() {
327
+ const c = loadCreds();
328
+ delete c.session;
329
+ saveCreds(c);
330
+ out("\u2713 session cleared (stored workspace secrets kept)");
331
+ return 0;
332
+ }
333
+ async function wsCreate(name, opts) {
334
+ const cloud = cloudBase(opts.cloud);
335
+ const principal = opts.principal ?? await sessionToken().catch((e) => (err(e.message), void 0)) ?? loadCreds().principal;
336
+ if (principal === void 0) {
337
+ err(
338
+ "a birth needs a principal \u2014 githolon login --agent (self-onboarding),\nor pass --principal <your-auth-uid or access token> (saved as the default)"
339
+ );
340
+ return 1;
341
+ }
342
+ const r = await fetch(`${cloud}/v1/workspaces/${name}`, {
343
+ method: "POST",
344
+ headers: principalHeaders(principal)
345
+ });
346
+ const d = await r.json().catch(() => void 0);
347
+ if (!r.ok || d?.ok !== true) {
348
+ err(`create '${name}' refused (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
349
+ return 1;
350
+ }
351
+ if (opts.principal !== void 0) setPrincipal(opts.principal);
352
+ if (typeof d.workspaceSecret === "string") {
353
+ setSecret(cloud, name, d.workspaceSecret);
354
+ out(`\u2713 workspace ${name} created on ${cloud}`);
355
+ out(`\u2713 workspaceSecret saved \u2192 ${credsPath()} (githolon deploy uses it automatically)`);
356
+ } else {
357
+ out(`\u2713 workspace ${name} already exists on ${cloud} (no new secret issued)`);
358
+ if (getSecret(cloud, name) === void 0) {
359
+ out(` no stored secret for it here \u2014 if you own it: githolon secret set ${name} <secret>`);
360
+ }
361
+ }
362
+ return 0;
363
+ }
364
+ async function wsStatus(name, opts) {
365
+ const cloud = cloudBase(opts.cloud);
366
+ const r = await fetch(`${cloud}/v1/workspaces/${name}`);
367
+ const text = await r.text();
368
+ if (!r.ok) {
369
+ err(`status '${name}' (${r.status}): ${text}`);
370
+ return 1;
371
+ }
372
+ out(text);
373
+ return 0;
374
+ }
375
+ async function deploy(ws, opts) {
376
+ const cloud = cloudBase(opts.cloud);
377
+ let file;
378
+ try {
379
+ file = discoverDeployJson(process.cwd(), opts.file);
380
+ } catch (e) {
381
+ err(e.message);
382
+ return 1;
383
+ }
384
+ const secret = getSecret(cloud, ws);
385
+ if (secret === void 0) {
386
+ err(
387
+ `no stored secret for ${ws} on ${cloud}
388
+ githolon ws create ${ws} (a fresh workspace saves its secret here)
389
+ githolon secret set ${ws} <secret> (import one you already hold)`
390
+ );
391
+ return 1;
392
+ }
393
+ const r = await fetch(`${cloud}/v1/workspaces/${ws}/domains`, {
394
+ method: "POST",
395
+ headers: { "content-type": "application/json", authorization: `Bearer ${secret}` },
396
+ body: readFileSync(file, "utf8")
397
+ });
398
+ const d = await r.json().catch(() => void 0);
399
+ if (r.status === 401) {
400
+ err(`deploy refused (401) \u2014 the stored secret for ${ws} is not the workspace's. githolon secret set ${ws} <secret>`);
401
+ return 1;
402
+ }
403
+ if (!r.ok || d?.ok !== true) {
404
+ err(`deploy '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
405
+ return 1;
406
+ }
407
+ const phase = d.installation?.[0]?.data?.["status.phase"];
408
+ out(`\u2713 deployed ${file.split("/").pop()} \u2192 ${ws}${typeof phase === "string" ? ` (${phase})` : ""}`);
409
+ if (typeof d.domainHash === "string") out(` law hash ${d.domainHash}`);
410
+ return 0;
411
+ }
412
+ async function secretSet(ws, secret, opts) {
413
+ setSecret(cloudBase(opts.cloud), ws, secret);
414
+ out(`\u2713 secret for ${ws} saved \u2192 ${credsPath()}`);
415
+ return 0;
416
+ }
417
+ async function secretRotate(ws, opts) {
418
+ const cloud = cloudBase(opts.cloud);
419
+ const current = getSecret(cloud, ws);
420
+ if (current === void 0) {
421
+ err(`no stored secret for ${ws} on ${cloud} \u2014 githolon secret set ${ws} <secret> first`);
422
+ return 1;
423
+ }
424
+ const r = await fetch(`${cloud}/v1/workspaces/${ws}/rotate-secret`, {
425
+ method: "POST",
426
+ headers: { authorization: `Bearer ${current}` }
427
+ });
428
+ const d = await r.json().catch(() => void 0);
429
+ if (!r.ok || d?.ok !== true || typeof d.workspaceSecret !== "string") {
430
+ err(`rotate '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
431
+ return 1;
432
+ }
433
+ setSecret(cloud, ws, d.workspaceSecret);
434
+ out(`\u2713 secret for ${ws} rotated and saved \u2192 ${credsPath()}`);
435
+ return 0;
436
+ }
437
+
438
+ // src/git.ts
439
+ import { spawnSync } from "node:child_process";
440
+ import { randomBytes } from "node:crypto";
441
+ import { readFileSync as readFileSync2 } from "node:fs";
442
+ var out2 = (s) => void process.stdout.write(s + "\n");
443
+ var err2 = (s) => void process.stderr.write("error: " + s + "\n");
444
+ function git(args, opts = {}) {
445
+ const r = spawnSync("git", args, { stdio: opts.quiet === true ? "ignore" : "inherit" });
446
+ return r.status ?? 1;
447
+ }
448
+ function ensureSecret(cloud, ws) {
449
+ const existing = getSecret(cloud, ws);
450
+ if (existing !== void 0) return existing;
451
+ const minted = `holon_v1_${randomBytes(24).toString("hex")}`;
452
+ setSecret(cloud, ws, minted);
453
+ return minted;
454
+ }
455
+ function parseCredentialInput(input) {
456
+ const kv = {};
457
+ for (const line of input.split("\n")) {
458
+ if (line === "") break;
459
+ const i = line.indexOf("=");
460
+ if (i > 0) kv[line.slice(0, i)] = line.slice(i + 1);
461
+ }
462
+ return kv;
463
+ }
464
+ function workspaceFromPath(path) {
465
+ const m = (path ?? "").match(/^v1\/workspaces\/([^/]+)\/git(\/|$)/);
466
+ return m?.[1];
467
+ }
468
+ async function gitCredential(action) {
469
+ if (action !== "get") return 0;
470
+ const kv = parseCredentialInput(readFileSync2(0, "utf8"));
471
+ const ws = workspaceFromPath(kv["path"]);
472
+ if (kv["protocol"] !== "https" || ws === void 0) return 0;
473
+ const cloud = `https://${kv["host"]}`;
474
+ const token = await sessionToken().catch(() => void 0);
475
+ out2(`username=${token ?? "nomos"}`);
476
+ out2(`password=${ensureSecret(cloud, ws)}`);
477
+ out2("");
478
+ return 0;
479
+ }
480
+ async function gitSetup(opts) {
481
+ const cloud = cloudBase(opts.cloud);
482
+ const self = process.argv[1];
483
+ if (self === void 0) {
484
+ err2("cannot locate the githolon CLI entry to install as a credential helper");
485
+ return 1;
486
+ }
487
+ const helper = `!"${process.execPath}" "${self}" git-credential`;
488
+ if (git(["config", "--global", `credential.${cloud}.helper`, helper]) !== 0 || git(["config", "--global", `credential.${cloud}.useHttpPath`, "true"]) !== 0) {
489
+ err2("git config failed \u2014 is git installed?");
490
+ return 1;
491
+ }
492
+ out2(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
493
+ out2(` pushes/clones of ${cloud}/v1/workspaces/<ws>/git now authenticate from ~/.holon`);
494
+ return 0;
495
+ }
496
+ async function gitRemote(ws, name, opts) {
497
+ const cloud = cloudBase(opts.cloud);
498
+ if (git(["rev-parse", "--git-dir"], { quiet: true }) !== 0) {
499
+ err2("not a git repository \u2014 run inside your repo (git init first)");
500
+ return 1;
501
+ }
502
+ const setupRc = await gitSetup(opts);
503
+ if (setupRc !== 0) return setupRc;
504
+ ensureSecret(cloud, ws);
505
+ const url = `${cloud}/v1/workspaces/${ws}/git`;
506
+ if (git(["remote", "add", name, url], { quiet: true }) !== 0) {
507
+ if (git(["remote", "set-url", name, url], { quiet: true }) !== 0) {
508
+ err2(`could not add or update remote '${name}'`);
509
+ return 1;
510
+ }
511
+ }
512
+ const token = await sessionToken().catch(() => void 0);
513
+ if (token === void 0) {
514
+ const principal = loadCreds().principal;
515
+ if (principal === void 0) {
516
+ err2(
517
+ `remote '${name}' \u2192 ${url} is wired, but a BIRTH push needs a principal:
518
+ githolon login --agent (then just push)
519
+ \u2014 or re-run after githolon ws create/login so a principal is on file`
520
+ );
521
+ return 1;
522
+ }
523
+ if (git(["config", `http.${url}.extraHeader`, `x-nomos-principal: ${principal}`]) !== 0) {
524
+ err2("git config failed setting the principal header");
525
+ return 1;
526
+ }
527
+ }
528
+ out2(`\u2713 remote '${name}' \u2192 ${url}`);
529
+ out2(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
530
+ out2(` birth/deploy by push: git push ${name} main`);
531
+ return 0;
532
+ }
533
+
534
+ // src/ledger.ts
535
+ import { spawnSync as spawnSync2 } from "node:child_process";
536
+ import { randomBytes as randomBytes2 } from "node:crypto";
537
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync3 } from "node:fs";
538
+ import { dirname, join as join3 } from "node:path";
539
+ import { File as File2, Directory as Directory2 } from "@bjorn3/browser_wasi_shim";
540
+
541
+ // vendor/engine/engine.mjs
542
+ import { WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
543
+ import git2 from "isomorphic-git";
544
+ import http from "isomorphic-git/http/web";
545
+
546
+ // vendor/engine/git-fs.mjs
547
+ function fsErr(code, p) {
548
+ const e = new Error(`${code}: ${p}`);
549
+ e.code = code;
550
+ return e;
551
+ }
552
+ function makeGitFs(preopen, mount, { File: File3, Directory: Directory3 }) {
553
+ const root = preopen.dir;
554
+ const isDir = (n) => n && n.contents instanceof Map;
555
+ function parts(p) {
556
+ if (p === mount) return [];
557
+ if (!p.startsWith(mount + "/")) throw fsErr("ENOENT", p);
558
+ const rel = p.slice(mount.length + 1).replace(/\/+$/, "");
559
+ return rel === "" ? [] : rel.split("/");
560
+ }
561
+ function resolve(ps) {
562
+ let cur = root;
563
+ for (const part of ps) {
564
+ if (!isDir(cur)) return null;
565
+ const next = cur.contents.get(part);
566
+ if (next === void 0) return null;
567
+ cur = next;
568
+ }
569
+ return cur;
570
+ }
571
+ const parent = (ps) => resolve(ps.slice(0, -1));
572
+ const leaf = (ps) => ps[ps.length - 1];
573
+ const promises = {
574
+ async readFile(p, opts) {
575
+ const n = resolve(parts(p));
576
+ if (n === null) throw fsErr("ENOENT", p);
577
+ if (isDir(n)) throw fsErr("EISDIR", p);
578
+ const data = n.data ?? new Uint8Array(0);
579
+ const encoding = typeof opts === "string" ? opts : opts && opts.encoding;
580
+ return encoding ? new TextDecoder().decode(data) : new Uint8Array(data);
581
+ },
582
+ async writeFile(p, data) {
583
+ const ps = parts(p);
584
+ const par = parent(ps);
585
+ if (!isDir(par)) throw fsErr("ENOENT", p);
586
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : new Uint8Array(data);
587
+ par.contents.set(leaf(ps), new File3(bytes));
588
+ },
589
+ async unlink(p) {
590
+ const ps = parts(p), par = parent(ps);
591
+ if (!isDir(par) || !par.contents.delete(leaf(ps))) throw fsErr("ENOENT", p);
592
+ },
593
+ async readdir(p) {
594
+ const n = resolve(parts(p));
595
+ if (n === null) throw fsErr("ENOENT", p);
596
+ if (!isDir(n)) throw fsErr("ENOTDIR", p);
597
+ return [...n.contents.keys()];
598
+ },
599
+ async mkdir(p) {
600
+ const ps = parts(p), par = parent(ps);
601
+ if (!isDir(par)) throw fsErr("ENOENT", p);
602
+ if (par.contents.has(leaf(ps))) throw fsErr("EEXIST", p);
603
+ par.contents.set(leaf(ps), new Directory3(/* @__PURE__ */ new Map()));
604
+ },
605
+ async rmdir(p) {
606
+ const ps = parts(p), par = parent(ps);
607
+ if (!isDir(par)) throw fsErr("ENOENT", p);
608
+ par.contents.delete(leaf(ps));
609
+ },
610
+ async stat(p) {
611
+ return promises.lstat(p);
612
+ },
613
+ async lstat(p) {
614
+ const n = resolve(parts(p));
615
+ if (n === null) throw fsErr("ENOENT", p);
616
+ const dir = isDir(n);
617
+ return {
618
+ type: dir ? "dir" : "file",
619
+ mode: dir ? 16384 : 33188,
620
+ size: dir ? 0 : n.data ? n.data.length : 0,
621
+ ino: 0,
622
+ mtimeMs: 0,
623
+ ctimeMs: 0,
624
+ uid: 1,
625
+ gid: 1,
626
+ dev: 1,
627
+ isFile: () => !dir,
628
+ isDirectory: () => dir,
629
+ isSymbolicLink: () => false
630
+ };
631
+ },
632
+ async readlink(p) {
633
+ throw fsErr("EINVAL", p);
634
+ },
635
+ async symlink() {
636
+ throw fsErr("EPERM", "symlink unsupported");
637
+ },
638
+ async chmod() {
639
+ }
640
+ };
641
+ return { promises };
642
+ }
643
+
644
+ // vendor/engine/tree.mjs
645
+ var enc = new TextEncoder();
646
+ var dec = new TextDecoder();
647
+
648
+ // vendor/engine/engine.mjs
649
+ var enc2 = new TextEncoder();
650
+ var dec2 = new TextDecoder();
651
+ var BRANCH = "main";
652
+ var repoArgOf = (ws) => `/work/ws/${ws}`;
653
+ var gitdirOf = (ws) => `/work/ws/${ws}/nomos.git`;
654
+ var unpack = (p) => {
655
+ const v = typeof p === "bigint" ? p : BigInt(p);
656
+ return { ptr: Number(v >> 32n), len: Number(v & 0xffffffffn) };
657
+ };
658
+ async function sha256hex(t) {
659
+ const b = await crypto.subtle.digest("SHA-256", enc2.encode(t));
660
+ return [...new Uint8Array(b)].map((x) => x.toString(16).padStart(2, "0")).join("");
661
+ }
662
+ function osEntropyBuffer(n) {
663
+ const bytes = new Uint8Array(n * 8);
664
+ crypto.getRandomValues(bytes);
665
+ const out4 = new Array(n), T = 2 ** 53;
666
+ for (let i = 0; i < n; i++) {
667
+ let z = 0n;
668
+ for (let b = 7; b >= 0; b--) z = z << 8n | BigInt(bytes[i * 8 + b]);
669
+ out4[i] = Number(z >> 11n) / T;
670
+ }
671
+ return out4;
672
+ }
673
+ function stringifyBig(o) {
674
+ return JSON.stringify(o, (_k, v) => typeof v === "bigint" ? `@@B:${v}@@` : v).replace(/"@@B:(\d+)@@"/g, "$1");
675
+ }
676
+ function call(ex, mode, fields, STDERR) {
677
+ const req = enc2.encode(JSON.stringify({ mode, ...fields }));
678
+ const reqPtr = ex.git_holon_alloc(req.length);
679
+ try {
680
+ new Uint8Array(ex.memory.buffer, reqPtr, req.length).set(req);
681
+ const { ptr, len } = unpack(ex.git_holon_call(reqPtr, req.length));
682
+ try {
683
+ const e = JSON.parse(dec2.decode(new Uint8Array(ex.memory.buffer, ptr, len).slice()));
684
+ if (!e.ok) throw new Error((e.error || "holon error") + (STDERR.length ? ` | ${STDERR.slice(-4).join(" / ")}` : ""));
685
+ return e.result;
686
+ } finally {
687
+ ex.git_holon_dealloc(ptr, len);
688
+ }
689
+ } finally {
690
+ ex.git_holon_dealloc(reqPtr, req.length);
691
+ }
692
+ }
693
+ function seedManifests(m, strings) {
694
+ m.set("domain_manifests.json", new File(enc2.encode(strings.read)));
695
+ m.set("identity-manifests.json", new File(enc2.encode(strings.identity)));
696
+ }
697
+ var installPayload = (hash, usda, installedBy) => ({ domainHash: hash, packageUsda: usda, installedBy, authorityScope: "workspace/root", dependencies: [], finalizers: [] });
698
+ async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, manifestStrings, replica, installedBy = "nomos-cloud" }) {
699
+ const hashes = { bootstrap: await sha256hex(bootstrapPkg), nomos: await sha256hex(nomosPkg) };
700
+ const root = /* @__PURE__ */ new Map();
701
+ seedManifests(root, manifestStrings);
702
+ root.set("domain.package.usda", new File(enc2.encode(bootstrapPkg)));
703
+ root.set("ws", new Directory(/* @__PURE__ */ new Map()));
704
+ const preopen = new PreopenDirectory("/work", root);
705
+ const STDERR = [];
706
+ const fds = [new OpenFile(new File([])), ConsoleStdout.lineBuffered(() => {
707
+ }), ConsoleStdout.lineBuffered((l) => STDERR.push(l)), preopen];
708
+ const wasi = new WASI(["wasm_git_holon", "reactor"], [], fds, { debug: false });
709
+ const inst = await WebAssembly.instantiate(wasmModule, { wasi_snapshot_preview1: wasi.wasiImport });
710
+ const code = wasi.start(inst);
711
+ if (code !== 0) throw new Error(`reactor init exited ${code}; stderr=${STDERR.join("\n")}`);
712
+ for (const n of ["memory", "git_holon_alloc", "git_holon_dealloc", "git_holon_call"]) if (!inst.exports[n]) throw new Error(`missing export ${n}`);
713
+ const eng = {
714
+ ex: inst.exports,
715
+ preopen,
716
+ STDERR,
717
+ seq: 0,
718
+ replica,
719
+ hashes,
720
+ bootstrapPkg,
721
+ nomosPkg,
722
+ manifestStrings,
723
+ installedBy,
724
+ fs: makeGitFs(preopen, "/work", { File, Directory }),
725
+ mounted: /* @__PURE__ */ new Map(),
726
+ // ws -> { restored, remoteMain, restoreError }
727
+ mainIntents: /* @__PURE__ */ new Map(),
728
+ // ws -> { head, ids:Set, oids:Set } (admission idempotence)
729
+ knownDomainsCache: null
730
+ };
731
+ return eng;
732
+ }
733
+ async function mountWorkspace(eng, ws, ledger) {
734
+ if (eng.mounted.has(ws)) return eng.mounted.get(ws);
735
+ const wsContents = /* @__PURE__ */ new Map();
736
+ seedManifests(wsContents, eng.manifestStrings);
737
+ eng.preopen.dir.contents.get("ws").contents.set(ws, new Directory(wsContents));
738
+ const gitdir = gitdirOf(ws);
739
+ let restored = false, remoteMain = null, restoreError = null;
740
+ try {
741
+ const info = await git2.getRemoteInfo({ http, url: ledger.remote, headers: ledger.headers });
742
+ remoteMain = info.refs && info.refs.heads && info.refs.heads.main || null;
743
+ if (remoteMain) {
744
+ await git2.init({ fs: eng.fs, gitdir, bare: true, defaultBranch: "main" });
745
+ await git2.addRemote({ fs: eng.fs, gitdir, remote: "origin", url: ledger.remote, force: true });
746
+ await git2.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref: "main", singleBranch: true, headers: ledger.headers });
747
+ await git2.writeRef({ fs: eng.fs, gitdir, ref: "refs/heads/main", value: remoteMain, force: true });
748
+ await git2.writeRef({ fs: eng.fs, gitdir, ref: "HEAD", value: "ref: refs/heads/main", force: true, symbolic: true });
749
+ restored = true;
750
+ }
751
+ } catch (e) {
752
+ restoreError = String(e && e.stack || e).split("\n").slice(0, 5).join(" | ");
753
+ }
754
+ const m = { restored, remoteMain, restoreError };
755
+ eng.mounted.set(ws, m);
756
+ return m;
757
+ }
758
+ function writeWork(eng, name, bytes) {
759
+ eng.preopen.dir.contents.set(name, new File(bytes));
760
+ }
761
+ function author(eng, ws, domain, directiveId, payload, controllerHash) {
762
+ const seq = eng.seq++;
763
+ const envelope = { payload: { domain, directiveId, payload }, captured_ports: { clock: { physical: Date.now(), logical: 0, replica: eng.replica }, rng: osEntropyBuffer(64) }, policy_version: 1, policy_domain: "Nomos", policy_gas: 0, policy_memory: 0 };
764
+ writeWork(eng, `payload-${seq}.json`, enc2.encode(JSON.stringify(payload)));
765
+ writeWork(eng, `envelope-${seq}.json`, enc2.encode(stringifyBig(envelope)));
766
+ const genesis = !controllerHash;
767
+ return JSON.parse(call(eng.ex, "author", { repoArg: repoArgOf(ws), workspace: ws, domain, directiveId, payloadFile: `/work/payload-${seq}.json`, envelopeFile: `/work/envelope-${seq}.json`, seq, actor: "", domainFile: genesis ? "/work/domain.package.usda" : "", domainHash: genesis ? "" : controllerHash, branch: BRANCH }, eng.STDERR));
768
+ }
769
+ function verifyChainLocal(eng, ws) {
770
+ return JSON.parse(call(eng.ex, "verify_chain", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
771
+ }
772
+
773
+ // src/ledger.ts
774
+ var out3 = (s) => void process.stdout.write(s + "\n");
775
+ var err3 = (s) => void process.stderr.write("error: " + s + "\n");
776
+ var WS = "local";
777
+ async function fetchJsonCached(url, cacheFile) {
778
+ try {
779
+ const r = await fetch(url);
780
+ if (!r.ok) throw new Error(`${url} \u2192 ${r.status}`);
781
+ const text = await r.text();
782
+ mkdirSync3(dirname(cacheFile), { recursive: true });
783
+ writeFileSync3(cacheFile, text, "utf8");
784
+ return JSON.parse(text);
785
+ } catch (e) {
786
+ if (existsSync3(cacheFile)) return JSON.parse(readFileSync3(cacheFile, "utf8"));
787
+ throw e;
788
+ }
789
+ }
790
+ async function fetchRuntime(cloud) {
791
+ const cache = join3(configDir(), "runtime");
792
+ let wasmBytes;
793
+ const wasmCache = join3(cache, "holon.wasm");
794
+ try {
795
+ const r = await fetch(`${cloud}/v1/runtime/holon.wasm`);
796
+ if (!r.ok) throw new Error(`runtime wasm \u2192 ${r.status}`);
797
+ wasmBytes = new Uint8Array(await r.arrayBuffer());
798
+ mkdirSync3(cache, { recursive: true });
799
+ writeFileSync3(wasmCache, wasmBytes);
800
+ } catch (e) {
801
+ if (!existsSync3(wasmCache)) throw e;
802
+ wasmBytes = readFileSync3(wasmCache);
803
+ }
804
+ const manifests = await fetchJsonCached(`${cloud}/v1/runtime/manifests`, join3(cache, "manifests.json"));
805
+ const packages = await fetchJsonCached(`${cloud}/v1/runtime/packages`, join3(cache, "packages.json"));
806
+ return { wasmBytes, manifests, packages };
807
+ }
808
+ function mergeManifests(baked, overlay) {
809
+ const read = JSON.parse(baked.domainManifests);
810
+ const identity = JSON.parse(baked.identityManifests);
811
+ for (const [k, v] of Object.entries(overlay.identityManifests ?? {})) identity[k] = v;
812
+ const r = overlay.readManifest;
813
+ if (r !== void 0) {
814
+ for (const [t, schema] of Object.entries(r["aggregateFieldKinds"] ?? {})) read["aggregateFieldKinds"][t] = schema;
815
+ for (const key of ["queries", "counts", "spatials", "deriveds", "combineds"]) {
816
+ for (const d of r[key] ?? []) {
817
+ const list = read[key] = read[key] ?? [];
818
+ if (!list.some((x) => JSON.stringify(x) === JSON.stringify(d))) list.push(d);
819
+ }
820
+ }
821
+ }
822
+ return { read: JSON.stringify(read), identity: JSON.stringify(identity) };
823
+ }
824
+ function writeTreeToDisk(dir, diskPath) {
825
+ mkdirSync3(diskPath, { recursive: true });
826
+ for (const [name, inode] of dir.contents) {
827
+ const p = join3(diskPath, name);
828
+ if (inode.contents instanceof Map) writeTreeToDisk(inode, p);
829
+ else writeFileSync3(p, inode.data ?? new Uint8Array(0));
830
+ }
831
+ }
832
+ function readTreeFromDisk(diskPath) {
833
+ const contents = /* @__PURE__ */ new Map();
834
+ for (const name of readdirSync2(diskPath)) {
835
+ const p = join3(diskPath, name);
836
+ if (statSync(p).isDirectory()) contents.set(name, readTreeFromDisk(p));
837
+ else contents.set(name, new File2(readFileSync3(p)));
838
+ }
839
+ return new Directory2(contents);
840
+ }
841
+ async function engineFor(cloud, overlay, installedBy) {
842
+ const runtime = await fetchRuntime(cloud);
843
+ const manifestStrings = overlay === void 0 ? { read: runtime.manifests.domainManifests, identity: runtime.manifests.identityManifests } : mergeManifests(runtime.manifests, overlay);
844
+ const replica = BigInt(`0x${randomBytes2(8).toString("hex")}`) & (1n << 63n) - 1n;
845
+ const eng = await createEngine({
846
+ wasmModule: await WebAssembly.compile(runtime.wasmBytes),
847
+ bootstrapPkg: runtime.packages.bootstrap,
848
+ nomosPkg: runtime.packages.nomos,
849
+ manifestStrings,
850
+ replica,
851
+ installedBy
852
+ });
853
+ await mountWorkspace(eng, WS, { remote: "https://unborn.invalid/", headers: {} });
854
+ return { eng, runtime };
855
+ }
856
+ function printVerdict(v) {
857
+ if (v["valid"] === true) {
858
+ out3(`\u2713 chain VALID \u2014 head ${String(v["head"]).slice(0, 12)}\u2026, ${v["intents"]} intent(s), ${v["plansRerun"]} plan(s) re-run`);
859
+ out3(` controller ${String(v["controllerHash"]).slice(0, 12)}\u2026 | installed: ${v["installedDomains"]?.map((h) => h.slice(0, 12) + "\u2026").join(", ") ?? "\u2014"}`);
860
+ return true;
861
+ }
862
+ err3(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
863
+ return false;
864
+ }
865
+ async function ledgerInit(dir, opts) {
866
+ const cloud = cloudBase(opts.cloud);
867
+ if (existsSync3(dir) && readdirSync2(dir).length > 0) {
868
+ err3(`refusing to mint into non-empty directory: ${dir}`);
869
+ return 1;
870
+ }
871
+ let deployFile;
872
+ try {
873
+ deployFile = discoverDeployJson(process.cwd(), opts.file);
874
+ } catch (e) {
875
+ err3(e.message);
876
+ return 1;
877
+ }
878
+ const deployBody = JSON.parse(readFileSync3(deployFile, "utf8"));
879
+ const domainHash = await sha256hex(deployBody.packageUsda);
880
+ const c = loadCreds();
881
+ const principal = await sessionToken().catch(() => void 0) !== void 0 ? c.session.uid : c.principal;
882
+ if (principal === void 0) {
883
+ err3("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
884
+ return 1;
885
+ }
886
+ out3(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
887
+ const { eng } = await engineFor(cloud, deployBody, principal);
888
+ author(eng, WS, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, principal), "");
889
+ author(eng, WS, "nomos", "installDomain", installPayload(domainHash, deployBody.packageUsda, principal), eng.hashes.nomos);
890
+ const verdict = verifyChainLocal(eng, WS);
891
+ if (!printVerdict(verdict)) return 1;
892
+ const gitTree = eng.preopen.dir.contents.get("ws").contents.get(WS).contents.get("nomos.git");
893
+ const gitDir = join3(dir, ".git");
894
+ writeTreeToDisk(gitTree, gitDir);
895
+ const cfgPath = join3(gitDir, "config");
896
+ writeFileSync3(cfgPath, readFileSync3(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
897
+ spawnSync2("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
898
+ out3(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
899
+ out3(`
900
+ Birth it on Nomos Cloud (the cloud re-derives this exact verdict):`);
901
+ out3(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
902
+ return 0;
903
+ }
904
+ async function ledgerVerify(dir, opts) {
905
+ const gitDir = existsSync3(join3(dir, ".git")) ? join3(dir, ".git") : dir;
906
+ if (!existsSync3(join3(gitDir, "HEAD"))) {
907
+ err3(`${dir} is not a git repo (no HEAD)`);
908
+ return 1;
909
+ }
910
+ const { eng } = await engineFor(cloudBase(opts.cloud), void 0, "verify");
911
+ const wsDir = eng.preopen.dir.contents.get("ws").contents.get(WS);
912
+ wsDir.contents.set("nomos.git", readTreeFromDisk(gitDir));
913
+ return printVerdict(verifyChainLocal(eng, WS)) ? 0 : 1;
914
+ }
915
+
916
+ // src/cli.ts
917
+ var KINDS = ["domain", "aggregate", "intent"];
918
+ var DEFAULT_OUT = "src/domains";
919
+ var USAGE = `githolon \u2014 the Nomos developer CLI
920
+
921
+ Authoring:
922
+ githolon compile [compiler args] compile the package by ./nomos.package.mjs
923
+ githolon generate <domain|aggregate|intent> <name> [options]
924
+ githolon g <domain|aggregate|intent> <name> [options]
925
+
926
+ Identity (~/.holon/credentials.json \u2014 sessions auto-refresh):
927
+ githolon login --agent anonymous sign-in: a verified self-onboarded identity
928
+ githolon login --token <refresh_token> adopt an existing captain app session
929
+ githolon whoami show the principal births will ride
930
+ githolon logout clear the session (workspace secrets kept)
931
+
932
+ Nomos Cloud (secrets live in ~/.holon/credentials.json \u2014 never copy one by hand):
933
+ githolon ws create <name> [--principal <uid|token>] birth a workspace; SAVES its one-time secret
934
+ githolon ws status <name> workspace status (lineage, phase, verdicts)
935
+ githolon deploy <ws> [--file <deploy.json>] POST build/*.deploy.json with the stored secret
936
+ githolon secret set <ws> <secret> import a secret you already hold
937
+ githolon secret rotate <ws> rotate (and re-save) the workspace secret
938
+
939
+ Git lane (stock git push as deploy/birth \u2014 see also: push-to-create):
940
+ githolon git setup install the credential helper for the cloud host
941
+ githolon git remote <ws> [<remoteName>] wire this repo to a workspace (default name: nomos)
942
+ then: git push nomos main
943
+
944
+ The local-first lane (mint + assert a holon on YOUR machine, under YOUR identity):
945
+ githolon ledger init <dir> genesis + your compiled law, verified by the SAME
946
+ wasm gate the cloud runs, written as a git repo
947
+ githolon ledger verify <dir> re-run the chain's self-verification from disk
948
+
949
+ Options:
950
+ --cloud <url> target cloud (default: $NOMOS_CLOUD or https://nomos.captainapp.co.uk)
951
+ --out <dir> generate: target directory (default: ${DEFAULT_OUT})
952
+ --force generate: overwrite an existing file
953
+ --dry-run generate: print to stdout, write nothing
954
+ -h, --help show this help
955
+ `;
956
+ function cloudArgs(rest) {
957
+ const pos = [];
958
+ const opts = {};
959
+ for (let i = 0; i < rest.length; i++) {
960
+ const a = rest[i];
961
+ if (a === "--cloud" || a === "--principal" || a === "--file") {
962
+ const v = rest[++i];
963
+ if (v === void 0) return { pos, opts, bad: `${a} requires a value` };
964
+ if (a === "--cloud") opts.cloud = v;
965
+ else if (a === "--principal") opts.principal = v;
966
+ else opts.file = v;
967
+ } else if (a.startsWith("--")) {
968
+ return { pos, opts, bad: `unknown flag '${a}'` };
969
+ } else {
970
+ pos.push(a);
971
+ }
972
+ }
973
+ return { pos, opts };
974
+ }
975
+ async function runCloud(argv) {
976
+ const [command, ...rest] = argv;
977
+ const { pos, opts, bad } = cloudArgs(rest);
978
+ const usageFail = (m) => {
979
+ process.stderr.write(`error: ${m}
980
+
981
+ ${USAGE}`);
982
+ return 1;
983
+ };
984
+ if (bad !== void 0) return usageFail(bad);
985
+ if (command === "ws") {
986
+ const [sub2, name] = pos;
987
+ if (sub2 === "create" && name !== void 0) return wsCreate(name, opts);
988
+ if (sub2 === "status" && name !== void 0) return wsStatus(name, opts);
989
+ return usageFail("expected: githolon ws <create|status> <name>");
990
+ }
991
+ if (command === "deploy") {
992
+ const [ws2] = pos;
993
+ if (ws2 === void 0) return usageFail("expected: githolon deploy <ws>");
994
+ return deploy(ws2, opts);
995
+ }
996
+ const [sub, ws, secret] = pos;
997
+ if (sub === "set" && ws !== void 0 && secret !== void 0) return secretSet(ws, secret, opts);
998
+ if (sub === "rotate" && ws !== void 0) return secretRotate(ws, opts);
999
+ return usageFail("expected: githolon secret <set <ws> <secret> | rotate <ws>>");
1000
+ }
1001
+ function isKind(s) {
1002
+ return s !== void 0 && KINDS.includes(s);
1003
+ }
1004
+ function runCompile(args) {
1005
+ const resolveDslPkg = (fromDir) => {
1006
+ try {
1007
+ const req = fromDir === void 0 ? createRequire(import.meta.url) : createRequire(pathToFileURL(join4(fromDir, "noop.js")));
1008
+ return req.resolve("@githolon/dsl/package.json");
1009
+ } catch {
1010
+ return void 0;
1011
+ }
1012
+ };
1013
+ const dslPkg = resolveDslPkg(process.cwd()) ?? resolveDslPkg(void 0);
1014
+ if (dslPkg === void 0) {
1015
+ process.stderr.write("error: @githolon/dsl not found \u2014 add it to your project's dependencies\n");
1016
+ return 1;
1017
+ }
1018
+ const launcher = join4(dirname2(dslPkg), "compile_package.mjs");
1019
+ const r = spawnSync3(process.execPath, [launcher, ...args], { stdio: "inherit", cwd: process.cwd() });
1020
+ return r.status ?? 1;
1021
+ }
1022
+ function parseArgs(argv) {
1023
+ const [command, ...rest] = argv;
1024
+ if (command !== "generate" && command !== "g") {
1025
+ throw new Error(
1026
+ `Unknown command '${command ?? "(none)"}'. Expected compile | generate | ws | deploy | secret.`
1027
+ );
1028
+ }
1029
+ let outDir = DEFAULT_OUT;
1030
+ let force = false;
1031
+ let dryRun = false;
1032
+ const positionals = [];
1033
+ for (let i = 0; i < rest.length; i++) {
1034
+ const arg = rest[i];
1035
+ switch (arg) {
1036
+ case "--out": {
1037
+ const next = rest[++i];
1038
+ if (next === void 0) throw new Error("--out requires a directory argument.");
1039
+ outDir = next;
1040
+ break;
1041
+ }
1042
+ case "--force":
1043
+ force = true;
1044
+ break;
1045
+ case "--dry-run":
1046
+ dryRun = true;
1047
+ break;
1048
+ default:
1049
+ if (arg.startsWith("--")) throw new Error(`Unknown flag '${arg}'.`);
1050
+ positionals.push(arg);
1051
+ }
1052
+ }
1053
+ const [kind, name] = positionals;
1054
+ if (!isKind(kind)) {
1055
+ throw new Error(
1056
+ `Expected a generator kind (${KINDS.join(" | ")}), got '${kind ?? "(none)"}'.`
1057
+ );
1058
+ }
1059
+ if (name === void 0) {
1060
+ throw new Error(`Expected a <name> after '${kind}', e.g. \`githolon generate ${kind} work_order\`.`);
1061
+ }
1062
+ return { kind, name, outDir, force, dryRun };
1063
+ }
1064
+ async function main(argv) {
1065
+ if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
1066
+ process.stdout.write(USAGE);
1067
+ return 0;
1068
+ }
1069
+ if (argv[0] === "compile") {
1070
+ return runCompile(argv.slice(1));
1071
+ }
1072
+ if (argv[0] === "ws" || argv[0] === "deploy" || argv[0] === "secret") {
1073
+ return runCloud(argv);
1074
+ }
1075
+ if (argv[0] === "login") {
1076
+ const agent = argv.includes("--agent");
1077
+ const tokenAt = argv.indexOf("--token");
1078
+ const token = tokenAt >= 0 ? argv[tokenAt + 1] : void 0;
1079
+ if (tokenAt >= 0 && token === void 0) {
1080
+ process.stderr.write("error: --token requires a value\n");
1081
+ return 1;
1082
+ }
1083
+ return login({ agent, ...token !== void 0 ? { token } : {} });
1084
+ }
1085
+ if (argv[0] === "whoami") return whoami();
1086
+ if (argv[0] === "logout") return logout();
1087
+ if (argv[0] === "ledger") {
1088
+ const { pos, opts, bad } = cloudArgs(argv.slice(1));
1089
+ if (bad !== void 0) {
1090
+ process.stderr.write(`error: ${bad}
1091
+
1092
+ ${USAGE}`);
1093
+ return 1;
1094
+ }
1095
+ const [sub, dir] = pos;
1096
+ if (sub === "init" && dir !== void 0) return ledgerInit(dir, opts);
1097
+ if (sub === "verify" && dir !== void 0) return ledgerVerify(dir, opts);
1098
+ process.stderr.write(`error: expected: githolon ledger <init|verify> <dir>
1099
+
1100
+ ${USAGE}`);
1101
+ return 1;
1102
+ }
1103
+ if (argv[0] === "git-credential") return gitCredential(argv[1]);
1104
+ if (argv[0] === "git") {
1105
+ const { pos, opts, bad } = cloudArgs(argv.slice(1));
1106
+ if (bad !== void 0) {
1107
+ process.stderr.write(`error: ${bad}
1108
+
1109
+ ${USAGE}`);
1110
+ return 1;
1111
+ }
1112
+ const [sub, ws, remoteName] = pos;
1113
+ if (sub === "setup") return gitSetup(opts);
1114
+ if (sub === "remote" && ws !== void 0) return gitRemote(ws, remoteName ?? "nomos", opts);
1115
+ process.stderr.write(`error: expected: githolon git <setup | remote <ws> [name]>
1116
+
1117
+ ${USAGE}`);
1118
+ return 1;
1119
+ }
1120
+ let parsed;
1121
+ try {
1122
+ parsed = parseArgs(argv);
1123
+ } catch (err4) {
1124
+ process.stderr.write(`error: ${err4.message}
1125
+
1126
+ ${USAGE}`);
1127
+ return 1;
1128
+ }
1129
+ try {
1130
+ if (parsed.dryRun) {
1131
+ process.stdout.write(renderScaffold(parsed.kind, parsed.name));
1132
+ return 0;
1133
+ }
1134
+ const { path } = generate({
1135
+ kind: parsed.kind,
1136
+ name: parsed.name,
1137
+ outDir: parsed.outDir,
1138
+ force: parsed.force
1139
+ });
1140
+ process.stdout.write(`created ${path}
1141
+ `);
1142
+ return 0;
1143
+ } catch (err4) {
1144
+ process.stderr.write(`error: ${err4.message}
1145
+ `);
1146
+ return 1;
1147
+ }
1148
+ }
1149
+ process.exit(await main(process.argv.slice(2)));
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "githolon",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "description": "githolon — the Nomos developer CLI: Rails-style generators for @githolon/dsl domains + the package compiler. Kernel-independent.",
6
+ "license": "SEE LICENSE IN LICENSE.md",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Captain-App/nomos2.git",
10
+ "directory": "cli"
11
+ },
12
+ "bin": {
13
+ "githolon": "./dist/cli.mjs"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "build": "node build.mjs",
23
+ "prepare": "npm run build",
24
+ "holon": "tsx src/cli.ts",
25
+ "test": "vitest run",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "@bjorn3/browser_wasi_shim": "0.4.2",
30
+ "@githolon/dsl": "^0.1.2",
31
+ "isomorphic-git": "^1.38.4"
32
+ },
33
+ "devDependencies": {
34
+ "esbuild": "0.24.2",
35
+ "tsx": "^4.19.2",
36
+ "typescript": "^5.6.3",
37
+ "vitest": "^2.1.8"
38
+ }
39
+ }