githolon 0.2.2 → 0.3.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 (2) hide show
  1. package/dist/cli.mjs +1257 -214
  2. package/package.json +2 -2
package/dist/cli.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { spawnSync as spawnSync3 } from "node:child_process";
5
- import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4 } from "node:fs";
6
- import { createRequire } from "node:module";
7
- import { dirname as dirname2, join as join4, resolve } from "node:path";
8
- import { pathToFileURL } from "node:url";
4
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "node:fs";
5
+ import { spawnSync as spawnSync4 } from "node:child_process";
6
+ import { createRequire as createRequire2 } from "node:module";
7
+ import { dirname as dirname4, join as join9, resolve as resolve2 } from "node:path";
8
+ import { pathToFileURL as pathToFileURL4 } from "node:url";
9
9
 
10
10
  // src/generate.ts
11
11
  import { mkdirSync, existsSync, writeFileSync } from "node:fs";
@@ -430,6 +430,7 @@ async function deploy(ws, opts) {
430
430
  );
431
431
  return 1;
432
432
  }
433
+ const before = await fetch(`${cloud}/v1/workspaces/${ws}/domains`).then((br) => br.json()).then((bd) => bd.ok === true ? bd.domains ?? [] : []).catch(() => []);
433
434
  const r = await fetch(`${cloud}/v1/workspaces/${ws}/domains`, {
434
435
  method: "POST",
435
436
  headers: { "content-type": "application/json", authorization: `Bearer ${secret}` },
@@ -446,9 +447,17 @@ async function deploy(ws, opts) {
446
447
  }
447
448
  const phase = d.installation?.[0]?.data?.["status.phase"];
448
449
  out(`\u2713 deployed ${file.split("/").pop()} \u2192 ${ws}${typeof phase === "string" ? ` (${phase})` : ""}`);
449
- if (typeof d.domainHash === "string") out(` law hash ${d.domainHash}`);
450
+ if (typeof d.domainHash === "string") {
451
+ out(` law ${describeLawMove(before, d.domainHash)}`);
452
+ }
450
453
  return 0;
451
454
  }
455
+ function describeLawMove(before, newHash) {
456
+ const activeBefore = before.filter((x) => x.phase === "Active" && typeof x.domainHash === "string").map((x) => x.domainHash);
457
+ if (activeBefore.includes(newHash)) return `unchanged \u2014 ${newHash.slice(0, 16)}\u2026 was already installed (idempotent)`;
458
+ const prior = activeBefore.length > 0 ? activeBefore[activeBefore.length - 1] : void 0;
459
+ return prior !== void 0 ? `moved ${prior.slice(0, 16)}\u2026 \u2192 ${newHash.slice(0, 16)}\u2026` : `installed ${newHash.slice(0, 16)}\u2026 (the first law on this workspace)`;
460
+ }
452
461
  async function secretSet(ws, secret, opts) {
453
462
  setSecret(cloudBase(opts.cloud), ws, secret);
454
463
  out(`\u2713 secret for ${ws} saved \u2192 ${credsPath()}`);
@@ -475,112 +484,63 @@ async function secretRotate(ws, opts) {
475
484
  return 0;
476
485
  }
477
486
 
478
- // src/git.ts
479
- import { spawnSync } from "node:child_process";
480
- import { randomBytes } from "node:crypto";
481
- import { readFileSync as readFileSync2 } from "node:fs";
482
- var out2 = (s) => void process.stdout.write(s + "\n");
483
- var err2 = (s) => void process.stderr.write("error: " + s + "\n");
484
- function git(args, opts = {}) {
485
- const r = spawnSync("git", args, { stdio: opts.quiet === true ? "ignore" : "inherit" });
486
- return r.status ?? 1;
487
- }
488
- function ensureSecret(cloud, ws) {
489
- const existing = getSecret(cloud, ws);
490
- if (existing !== void 0) return existing;
491
- const minted = `holon_v1_${randomBytes(24).toString("hex")}`;
492
- setSecret(cloud, ws, minted);
493
- return minted;
494
- }
495
- function parseCredentialInput(input) {
496
- const kv = {};
497
- for (const line of input.split("\n")) {
498
- if (line === "") break;
499
- const i = line.indexOf("=");
500
- if (i > 0) kv[line.slice(0, i)] = line.slice(i + 1);
501
- }
502
- return kv;
503
- }
504
- function workspaceFromPath(path) {
505
- const m = (path ?? "").match(/^v1\/workspaces\/([^/]+)\/git(\/|$)/);
506
- return m?.[1];
507
- }
508
- async function gitCredential(action) {
509
- if (action !== "get") return 0;
510
- const kv = parseCredentialInput(readFileSync2(0, "utf8"));
511
- const ws = workspaceFromPath(kv["path"]);
512
- if (kv["protocol"] !== "https" || ws === void 0) return 0;
513
- const cloud = `https://${kv["host"]}`;
514
- const token = await sessionToken().catch(() => void 0);
515
- out2(`username=${token ?? "nomos"}`);
516
- out2(`password=${ensureSecret(cloud, ws)}`);
517
- out2("");
518
- return 0;
487
+ // src/compile.ts
488
+ import { spawn, spawnSync } from "node:child_process";
489
+ import { createRequire } from "node:module";
490
+ import { dirname, join as join3 } from "node:path";
491
+ import { pathToFileURL } from "node:url";
492
+ function resolveCompileLauncher(cwd) {
493
+ const resolveDslPkg = (fromDir) => {
494
+ try {
495
+ const req = fromDir === void 0 ? createRequire(import.meta.url) : createRequire(pathToFileURL(join3(fromDir, "noop.js")));
496
+ return req.resolve("@githolon/dsl/package.json");
497
+ } catch {
498
+ return void 0;
499
+ }
500
+ };
501
+ const dslPkg = resolveDslPkg(cwd) ?? resolveDslPkg(void 0);
502
+ return dslPkg === void 0 ? void 0 : join3(dirname(dslPkg), "compile_package.mjs");
519
503
  }
520
- async function gitSetup(opts) {
521
- const cloud = cloudBase(opts.cloud);
522
- const self = process.argv[1];
523
- if (self === void 0) {
524
- err2("cannot locate the githolon CLI entry to install as a credential helper");
525
- return 1;
526
- }
527
- const helper = `!"${process.execPath}" "${self}" git-credential`;
528
- if (git(["config", "--global", `credential.${cloud}.helper`, helper]) !== 0 || git(["config", "--global", `credential.${cloud}.useHttpPath`, "true"]) !== 0) {
529
- err2("git config failed \u2014 is git installed?");
504
+ var NO_DSL_REMEDY = "@githolon/dsl not found \u2014 add it to your project's dependencies";
505
+ function runCompile(args) {
506
+ const launcher = resolveCompileLauncher(process.cwd());
507
+ if (launcher === void 0) {
508
+ process.stderr.write(`error: ${NO_DSL_REMEDY}
509
+ `);
530
510
  return 1;
531
511
  }
532
- out2(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
533
- out2(` pushes/clones of ${cloud}/v1/workspaces/<ws>/git now authenticate from ~/.holon`);
534
- return 0;
512
+ const r = spawnSync(process.execPath, [launcher, ...args], { stdio: "inherit", cwd: process.cwd() });
513
+ return r.status ?? 1;
535
514
  }
536
- async function gitRemote(ws, name, opts) {
537
- const cloud = cloudBase(opts.cloud);
538
- if (git(["rev-parse", "--git-dir"], { quiet: true }) !== 0) {
539
- err2("not a git repository \u2014 run inside your repo (git init first)");
540
- return 1;
541
- }
542
- const setupRc = await gitSetup(opts);
543
- if (setupRc !== 0) return setupRc;
544
- ensureSecret(cloud, ws);
545
- const url = `${cloud}/v1/workspaces/${ws}/git`;
546
- if (git(["remote", "add", name, url], { quiet: true }) !== 0) {
547
- if (git(["remote", "set-url", name, url], { quiet: true }) !== 0) {
548
- err2(`could not add or update remote '${name}'`);
549
- return 1;
550
- }
551
- }
552
- const token = await sessionToken().catch(() => void 0);
553
- if (token === void 0) {
554
- const principal = loadCreds().principal;
555
- if (principal === void 0) {
556
- err2(
557
- `remote '${name}' \u2192 ${url} is wired, but a BIRTH push needs a principal:
558
- githolon login --agent (then just push)
559
- \u2014 or re-run after githolon ws create/login so a principal is on file`
560
- );
561
- return 1;
562
- }
563
- if (git(["config", `http.${url}.extraHeader`, `x-nomos-principal: ${principal}`]) !== 0) {
564
- err2("git config failed setting the principal header");
565
- return 1;
566
- }
567
- }
568
- out2(`\u2713 remote '${name}' \u2192 ${url}`);
569
- out2(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
570
- out2(` birth/deploy by push: git push ${name} main`);
571
- return 0;
515
+ function compileAsync(cwd, args = []) {
516
+ const launcher = resolveCompileLauncher(cwd);
517
+ const t0 = performance.now();
518
+ if (launcher === void 0) {
519
+ return Promise.resolve({ ok: false, ms: 0, output: NO_DSL_REMEDY });
520
+ }
521
+ return new Promise((resolveRun) => {
522
+ const chunks = [];
523
+ const dec3 = new TextDecoder();
524
+ const child = spawn(process.execPath, [launcher, ...args], { cwd, stdio: ["ignore", "pipe", "pipe"] });
525
+ child.stdout?.on("data", (c) => chunks.push(dec3.decode(c)));
526
+ child.stderr?.on("data", (c) => chunks.push(dec3.decode(c)));
527
+ child.on("error", (e) => resolveRun({ ok: false, ms: performance.now() - t0, output: String(e) }));
528
+ child.on(
529
+ "close",
530
+ (code) => resolveRun({ ok: code === 0, ms: performance.now() - t0, output: chunks.join("") })
531
+ );
532
+ });
572
533
  }
573
534
 
574
- // src/ledger.ts
575
- import { spawnSync as spawnSync2 } from "node:child_process";
576
- import { randomBytes as randomBytes2 } from "node:crypto";
577
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync3 } from "node:fs";
578
- import { dirname, join as join3 } from "node:path";
579
- import { File as File2, Directory as Directory2 } from "@bjorn3/browser_wasi_shim";
535
+ // src/dev.ts
536
+ import { existsSync as existsSync5, readFileSync as readFileSync3, watch } from "node:fs";
537
+ import { createServer } from "node:http";
538
+ import { dirname as dirname3, join as join6, resolve } from "node:path";
539
+ import { pathToFileURL as pathToFileURL3 } from "node:url";
580
540
 
581
541
  // vendor/engine/engine.mjs
582
542
  import { WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
583
- import git2 from "isomorphic-git";
543
+ import git from "isomorphic-git";
584
544
  import http from "isomorphic-git/http/web";
585
545
 
586
546
  // vendor/engine/git-fs.mjs
@@ -598,7 +558,7 @@ function makeGitFs(preopen, mount, { File: File3, Directory: Directory3 }) {
598
558
  const rel = p.slice(mount.length + 1).replace(/\/+$/, "");
599
559
  return rel === "" ? [] : rel.split("/");
600
560
  }
601
- function resolve2(ps) {
561
+ function resolve3(ps) {
602
562
  let cur = root;
603
563
  for (const part of ps) {
604
564
  if (!isDir(cur)) return null;
@@ -608,11 +568,11 @@ function makeGitFs(preopen, mount, { File: File3, Directory: Directory3 }) {
608
568
  }
609
569
  return cur;
610
570
  }
611
- const parent = (ps) => resolve2(ps.slice(0, -1));
571
+ const parent = (ps) => resolve3(ps.slice(0, -1));
612
572
  const leaf = (ps) => ps[ps.length - 1];
613
573
  const promises = {
614
574
  async readFile(p, opts) {
615
- const n = resolve2(parts(p));
575
+ const n = resolve3(parts(p));
616
576
  if (n === null) throw fsErr("ENOENT", p);
617
577
  if (isDir(n)) throw fsErr("EISDIR", p);
618
578
  const data = n.data ?? new Uint8Array(0);
@@ -631,7 +591,7 @@ function makeGitFs(preopen, mount, { File: File3, Directory: Directory3 }) {
631
591
  if (!isDir(par) || !par.contents.delete(leaf(ps))) throw fsErr("ENOENT", p);
632
592
  },
633
593
  async readdir(p) {
634
- const n = resolve2(parts(p));
594
+ const n = resolve3(parts(p));
635
595
  if (n === null) throw fsErr("ENOENT", p);
636
596
  if (!isDir(n)) throw fsErr("ENOTDIR", p);
637
597
  return [...n.contents.keys()];
@@ -651,7 +611,7 @@ function makeGitFs(preopen, mount, { File: File3, Directory: Directory3 }) {
651
611
  return promises.lstat(p);
652
612
  },
653
613
  async lstat(p) {
654
- const n = resolve2(parts(p));
614
+ const n = resolve3(parts(p));
655
615
  if (n === null) throw fsErr("ENOENT", p);
656
616
  const dir = isDir(n);
657
617
  return {
@@ -701,14 +661,14 @@ async function sha256hex(t) {
701
661
  }
702
662
  function osEntropyBuffer(n) {
703
663
  const bytes = new Uint8Array(n * 8);
704
- crypto.getRandomValues(bytes);
705
- const out4 = new Array(n), T = 2 ** 53;
664
+ for (let o = 0; o < bytes.length; o += 65536) crypto.getRandomValues(bytes.subarray(o, Math.min(o + 65536, bytes.length)));
665
+ const out8 = new Array(n), T = 2 ** 53;
706
666
  for (let i = 0; i < n; i++) {
707
667
  let z = 0n;
708
668
  for (let b = 7; b >= 0; b--) z = z << 8n | BigInt(bytes[i * 8 + b]);
709
- out4[i] = Number(z >> 11n) / T;
669
+ out8[i] = Number(z >> 11n) / T;
710
670
  }
711
- return out4;
671
+ return out8;
712
672
  }
713
673
  function stringifyBig(o) {
714
674
  return JSON.stringify(o, (_k, v) => typeof v === "bigint" ? `@@B:${v}@@` : v).replace(/"@@B:(\d+)@@"/g, "$1");
@@ -770,6 +730,22 @@ async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, manifestString
770
730
  };
771
731
  return eng;
772
732
  }
733
+ function setManifests(eng, strings) {
734
+ eng.manifestStrings = strings;
735
+ eng.knownDomainsCache = null;
736
+ eng.directiveRoutesCache = null;
737
+ eng.scopedReadsCache = null;
738
+ eng.placementDirectivesCache = null;
739
+ eng.tenantTypesCache = null;
740
+ eng.globalAggregatesCache = null;
741
+ const root = eng.preopen.dir.contents;
742
+ seedManifests(root, strings);
743
+ const wsRoot = root.get("ws");
744
+ for (const ws of eng.mounted.keys()) {
745
+ const wsDir = wsRoot.contents.get(ws);
746
+ if (wsDir) seedManifests(wsDir.contents, strings);
747
+ }
748
+ }
773
749
  async function mountWorkspace(eng, ws, ledger) {
774
750
  if (eng.mounted.has(ws)) return eng.mounted.get(ws);
775
751
  const wsContents = /* @__PURE__ */ new Map();
@@ -778,14 +754,14 @@ async function mountWorkspace(eng, ws, ledger) {
778
754
  const gitdir = gitdirOf(ws);
779
755
  let restored = false, remoteMain = null, restoreError = null;
780
756
  try {
781
- const info = await git2.getRemoteInfo({ http, url: ledger.remote, headers: ledger.headers });
757
+ const info = await git.getRemoteInfo({ http, url: ledger.remote, headers: ledger.headers });
782
758
  remoteMain = info.refs && info.refs.heads && info.refs.heads.main || null;
783
759
  if (remoteMain) {
784
- await git2.init({ fs: eng.fs, gitdir, bare: true, defaultBranch: "main" });
785
- await git2.addRemote({ fs: eng.fs, gitdir, remote: "origin", url: ledger.remote, force: true });
786
- await git2.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref: "main", singleBranch: true, headers: ledger.headers });
787
- await git2.writeRef({ fs: eng.fs, gitdir, ref: "refs/heads/main", value: remoteMain, force: true });
788
- await git2.writeRef({ fs: eng.fs, gitdir, ref: "HEAD", value: "ref: refs/heads/main", force: true, symbolic: true });
760
+ await git.init({ fs: eng.fs, gitdir, bare: true, defaultBranch: "main" });
761
+ await git.addRemote({ fs: eng.fs, gitdir, remote: "origin", url: ledger.remote, force: true });
762
+ await git.fetch({ fs: eng.fs, http, gitdir, remote: "origin", ref: "main", singleBranch: true, headers: ledger.headers });
763
+ await git.writeRef({ fs: eng.fs, gitdir, ref: "refs/heads/main", value: remoteMain, force: true });
764
+ await git.writeRef({ fs: eng.fs, gitdir, ref: "HEAD", value: "ref: refs/heads/main", force: true, symbolic: true });
789
765
  restored = true;
790
766
  }
791
767
  } catch (e) {
@@ -795,42 +771,63 @@ async function mountWorkspace(eng, ws, ledger) {
795
771
  eng.mounted.set(ws, m);
796
772
  return m;
797
773
  }
774
+ function unmount(eng, ws) {
775
+ eng.mounted.delete(ws);
776
+ eng.mainIntents.delete(ws);
777
+ const wsRoot = eng.preopen.dir.contents.get("ws");
778
+ if (wsRoot) wsRoot.contents.delete(ws);
779
+ }
798
780
  function writeWork(eng, name, bytes) {
799
781
  eng.preopen.dir.contents.set(name, new File(bytes));
800
782
  }
801
- function author(eng, ws, domain, directiveId, payload, controllerHash) {
783
+ function author(eng, ws, domain, directiveId, payload, controllerHash, opts = {}) {
802
784
  const seq = eng.seq++;
803
- 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 };
785
+ const envelope = { payload: { domain, directiveId, payload }, captured_ports: { clock: { physical: Date.now(), logical: 0, replica: eng.replica }, rng: osEntropyBuffer(opts.rngDraws ?? 64) }, policy_version: 1, policy_domain: "Nomos", policy_gas: 0, policy_memory: 0 };
804
786
  writeWork(eng, `payload-${seq}.json`, enc2.encode(JSON.stringify(payload)));
805
787
  writeWork(eng, `envelope-${seq}.json`, enc2.encode(stringifyBig(envelope)));
806
788
  const genesis = !controllerHash;
807
- 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));
789
+ const defer = opts.deferProjection !== false;
790
+ const res = 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, ...defer ? { deferProjection: true } : {} }, eng.STDERR));
791
+ if (defer && res.ok) (eng.pendingProjection ??= /* @__PURE__ */ new Set()).add(ws);
792
+ return res;
793
+ }
794
+ var qById = (eng, ws, id) => JSON.parse(call(eng.ex, "query_by_id", { repoArg: repoArgOf(ws), workspace: ws, aggregateId: id, branch: BRANCH }, eng.STDERR));
795
+ var query = (eng, ws, queryId, paramsJson) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryId, paramsJson, branch: BRANCH }, eng.STDERR));
796
+ var count = (eng, ws, countId, groupKey) => JSON.parse(call(eng.ex, "count", { repoArg: repoArgOf(ws), workspace: ws, countId, groupKey, branch: BRANCH }, eng.STDERR));
797
+ var sum = (eng, ws, sumId, groupKey) => JSON.parse(call(eng.ex, "sum", { repoArg: repoArgOf(ws), workspace: ws, sumId, groupKey, branch: BRANCH }, eng.STDERR));
798
+ function applyIntentBytes(eng, ws, bytes) {
799
+ const name = `intent-in-${eng.seq++}.json`;
800
+ writeWork(eng, name, bytes);
801
+ return JSON.parse(call(eng.ex, "apply_intent", { repoArg: repoArgOf(ws), workspace: ws, intentFile: `/work/${name}`, branch: BRANCH }, eng.STDERR));
808
802
  }
809
803
  function verifyChainLocal(eng, ws) {
810
804
  return JSON.parse(call(eng.ex, "verify_chain", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
811
805
  }
812
806
 
813
- // src/ledger.ts
814
- var out3 = (s) => void process.stdout.write(s + "\n");
815
- var err3 = (s) => void process.stderr.write("error: " + s + "\n");
816
- var WS = "local";
807
+ // src/local_holon.ts
808
+ import { randomBytes } from "node:crypto";
809
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync3 } from "node:fs";
810
+ import { dirname as dirname2, join as join4 } from "node:path";
811
+ import { File as File2, Directory as Directory2 } from "@bjorn3/browser_wasi_shim";
812
+ var out2 = (s) => void process.stdout.write(s + "\n");
813
+ var err2 = (s) => void process.stderr.write("error: " + s + "\n");
817
814
  async function fetchJsonCached(url, cacheFile) {
818
815
  try {
819
816
  const r = await fetch(url);
820
817
  if (!r.ok) throw new Error(`${url} \u2192 ${r.status}`);
821
818
  const text = await r.text();
822
- mkdirSync3(dirname(cacheFile), { recursive: true });
819
+ mkdirSync3(dirname2(cacheFile), { recursive: true });
823
820
  writeFileSync3(cacheFile, text, "utf8");
824
821
  return JSON.parse(text);
825
822
  } catch (e) {
826
- if (existsSync3(cacheFile)) return JSON.parse(readFileSync3(cacheFile, "utf8"));
823
+ if (existsSync3(cacheFile)) return JSON.parse(readFileSync2(cacheFile, "utf8"));
827
824
  throw e;
828
825
  }
829
826
  }
830
827
  async function fetchRuntime(cloud) {
831
- const cache = join3(configDir(), "runtime");
828
+ const cache = join4(configDir(), "runtime");
832
829
  let wasmBytes;
833
- const wasmCache = join3(cache, "holon.wasm");
830
+ const wasmCache = join4(cache, "holon.wasm");
834
831
  try {
835
832
  const r = await fetch(`${cloud}/v1/runtime/holon.wasm`);
836
833
  if (!r.ok) throw new Error(`runtime wasm \u2192 ${r.status}`);
@@ -839,10 +836,10 @@ async function fetchRuntime(cloud) {
839
836
  writeFileSync3(wasmCache, wasmBytes);
840
837
  } catch (e) {
841
838
  if (!existsSync3(wasmCache)) throw e;
842
- wasmBytes = readFileSync3(wasmCache);
839
+ wasmBytes = readFileSync2(wasmCache);
843
840
  }
844
- const manifests = await fetchJsonCached(`${cloud}/v1/runtime/manifests`, join3(cache, "manifests.json"));
845
- const packages = await fetchJsonCached(`${cloud}/v1/runtime/packages`, join3(cache, "packages.json"));
841
+ const manifests = await fetchJsonCached(`${cloud}/v1/runtime/manifests`, join4(cache, "manifests.json"));
842
+ const packages = await fetchJsonCached(`${cloud}/v1/runtime/packages`, join4(cache, "packages.json"));
846
843
  return { wasmBytes, manifests, packages };
847
844
  }
848
845
  function mergeManifests(baked, overlay) {
@@ -852,7 +849,7 @@ function mergeManifests(baked, overlay) {
852
849
  const r = overlay.readManifest;
853
850
  if (r !== void 0) {
854
851
  for (const [t, schema] of Object.entries(r["aggregateFieldKinds"] ?? {})) read["aggregateFieldKinds"][t] = schema;
855
- for (const key of ["queries", "counts", "spatials", "deriveds", "combineds"]) {
852
+ for (const key of ["queries", "counts", "sums", "mins", "maxes", "spatials", "deriveds", "combineds"]) {
856
853
  for (const d of r[key] ?? []) {
857
854
  const list = read[key] = read[key] ?? [];
858
855
  if (!list.some((x) => JSON.stringify(x) === JSON.stringify(d))) list.push(d);
@@ -864,7 +861,7 @@ function mergeManifests(baked, overlay) {
864
861
  function writeTreeToDisk(dir, diskPath) {
865
862
  mkdirSync3(diskPath, { recursive: true });
866
863
  for (const [name, inode] of dir.contents) {
867
- const p = join3(diskPath, name);
864
+ const p = join4(diskPath, name);
868
865
  if (inode.contents instanceof Map) writeTreeToDisk(inode, p);
869
866
  else writeFileSync3(p, inode.data ?? new Uint8Array(0));
870
867
  }
@@ -872,16 +869,16 @@ function writeTreeToDisk(dir, diskPath) {
872
869
  function readTreeFromDisk(diskPath) {
873
870
  const contents = /* @__PURE__ */ new Map();
874
871
  for (const name of readdirSync2(diskPath)) {
875
- const p = join3(diskPath, name);
872
+ const p = join4(diskPath, name);
876
873
  if (statSync(p).isDirectory()) contents.set(name, readTreeFromDisk(p));
877
- else contents.set(name, new File2(readFileSync3(p)));
874
+ else contents.set(name, new File2(readFileSync2(p)));
878
875
  }
879
876
  return new Directory2(contents);
880
877
  }
881
- async function engineFor(cloud, overlay, installedBy) {
878
+ async function holonEngine(cloud, overlay, installedBy) {
882
879
  const runtime = await fetchRuntime(cloud);
883
880
  const manifestStrings = overlay === void 0 ? { read: runtime.manifests.domainManifests, identity: runtime.manifests.identityManifests } : mergeManifests(runtime.manifests, overlay);
884
- const replica = BigInt(`0x${randomBytes2(8).toString("hex")}`) & (1n << 63n) - 1n;
881
+ const replica = BigInt(`0x${randomBytes(8).toString("hex")}`) & (1n << 63n) - 1n;
885
882
  const eng = await createEngine({
886
883
  wasmModule: await WebAssembly.compile(runtime.wasmBytes),
887
884
  bootstrapPkg: runtime.packages.bootstrap,
@@ -890,67 +887,1022 @@ async function engineFor(cloud, overlay, installedBy) {
890
887
  replica,
891
888
  installedBy
892
889
  });
893
- await mountWorkspace(eng, WS, { remote: "https://unborn.invalid/", headers: {} });
894
890
  return { eng, runtime };
895
891
  }
892
+ async function mountFresh(eng, ws) {
893
+ await mountWorkspace(eng, ws, { remote: "https://unborn.invalid/", headers: {} });
894
+ }
895
+ function sealedIntentOf(eng, ws, res) {
896
+ if (typeof res.intentOut !== "string") return void 0;
897
+ const f = eng.preopen.dir.contents.get("ws").contents.get(ws)?.contents.get(res.intentOut);
898
+ if (f === void 0 || !(f.data instanceof Uint8Array)) return void 0;
899
+ try {
900
+ return JSON.parse(new TextDecoder().decode(f.data));
901
+ } catch {
902
+ return void 0;
903
+ }
904
+ }
896
905
  function printVerdict(v) {
897
906
  if (v["valid"] === true) {
898
- out3(`\u2713 chain VALID \u2014 head ${String(v["head"]).slice(0, 12)}\u2026, ${v["intents"]} intent(s), ${v["plansRerun"]} plan(s) re-run`);
899
- out3(` controller ${String(v["controllerHash"]).slice(0, 12)}\u2026 | installed: ${v["installedDomains"]?.map((h) => h.slice(0, 12) + "\u2026").join(", ") ?? "\u2014"}`);
907
+ out2(`\u2713 chain VALID \u2014 head ${String(v["head"]).slice(0, 12)}\u2026, ${v["intents"]} intent(s), ${v["plansRerun"]} plan(s) re-run`);
908
+ out2(` controller ${String(v["controllerHash"]).slice(0, 12)}\u2026 | installed: ${v["installedDomains"]?.map((h) => h.slice(0, 12) + "\u2026").join(", ") ?? "\u2014"}`);
900
909
  return true;
901
910
  }
902
- err3(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
911
+ err2(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
903
912
  return false;
904
913
  }
914
+
915
+ // src/project.ts
916
+ import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
917
+ import { join as join5 } from "node:path";
918
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
919
+ var CONFIG_FILE = "nomos.package.mjs";
920
+ async function loadProject(cwd, opts = {}) {
921
+ const configPath = join5(cwd, CONFIG_FILE);
922
+ if (!existsSync4(configPath)) return void 0;
923
+ const href = pathToFileURL2(configPath).href + (opts.bust === true ? `?v=${statSync2(configPath).mtimeMs}-${Date.now()}` : "");
924
+ const mod = await import(href);
925
+ const cfg = mod.default;
926
+ if (cfg === void 0 || typeof cfg.name !== "string" || cfg.name.length === 0) {
927
+ throw new Error(`${CONFIG_FILE} must default-export { name, domains } \u2014 see \`githolon compile --help\``);
928
+ }
929
+ const workspace = parseWorkspaceDecl(cfg.workspace, configPath);
930
+ return { name: cfg.name, ...workspace !== void 0 ? { workspace } : {}, configPath };
931
+ }
932
+ function parseWorkspaceDecl(raw, where) {
933
+ if (raw === void 0 || raw === null) return void 0;
934
+ if (typeof raw === "string") {
935
+ if (raw.length === 0) throw new Error(`'workspace' in ${where} is an empty string \u2014 name a workspace, or remove the key`);
936
+ return raw;
937
+ }
938
+ if (typeof raw === "object" && !Array.isArray(raw)) {
939
+ const entries = Object.entries(raw);
940
+ if (entries.length === 0) throw new Error(`'workspace' in ${where} is an empty object \u2014 declare targets, e.g. workspace: { dev: "my-ws-dev" }`);
941
+ for (const [target, ws] of entries) {
942
+ if (typeof ws !== "string" || ws.length === 0) {
943
+ throw new Error(`'workspace.${target}' in ${where} must be a workspace name (a string) \u2014 got ${JSON.stringify(ws)}`);
944
+ }
945
+ }
946
+ return raw;
947
+ }
948
+ throw new Error(`'workspace' in ${where} must be a string or { target: workspace } object \u2014 got ${JSON.stringify(raw)}`);
949
+ }
950
+ function resolveWorkspaceDecl(decl, target, where) {
951
+ if (decl === void 0) {
952
+ throw new Error(
953
+ `no 'workspace' bound in ${where} \u2014 add one line:
954
+ workspace: "<your-workspace>", (or: workspace: { dev: "...", prod: "..." })
955
+ or name the workspace explicitly on the command line`
956
+ );
957
+ }
958
+ if (typeof decl === "string") {
959
+ if (target !== void 0) {
960
+ throw new Error(
961
+ `--target ${target} given, but 'workspace' in ${where} is the single workspace "${decl}" \u2014
962
+ use the object form to declare targets: workspace: { ${target}: "<ws>" }`
963
+ );
964
+ }
965
+ return decl;
966
+ }
967
+ const targets = Object.keys(decl);
968
+ if (target === void 0) {
969
+ if (targets.length === 1) return decl[targets[0]];
970
+ throw new Error(
971
+ `'workspace' in ${where} declares ${targets.length} targets (${targets.join(", ")}) \u2014 pick one with --target <name>`
972
+ );
973
+ }
974
+ const ws = decl[target];
975
+ if (ws === void 0) {
976
+ throw new Error(
977
+ `--target ${target} is not declared in ${where} \u2014 its targets: ${targets.join(", ")}`
978
+ );
979
+ }
980
+ return ws;
981
+ }
982
+ async function resolveWorkspace(explicit, cwd, target, usageHint) {
983
+ if (explicit !== void 0) {
984
+ if (target !== void 0) {
985
+ throw new Error(`--target picks from the project's 'workspace' targets \u2014 drop the explicit workspace argument, or drop --target`);
986
+ }
987
+ return explicit;
988
+ }
989
+ const project = await loadProject(cwd);
990
+ if (project === void 0) {
991
+ throw new Error(
992
+ `no workspace named and no ${CONFIG_FILE} here (${cwd}) \u2014
993
+ ${usageHint}
994
+ or run inside a project whose ${CONFIG_FILE} binds one: workspace: "<ws>"`
995
+ );
996
+ }
997
+ return resolveWorkspaceDecl(project.workspace, target, project.configPath);
998
+ }
999
+
1000
+ // src/proof_offline.ts
1001
+ function parseOfflineLegs(proofSource) {
1002
+ if (!proofSource.includes("AUTO-GENERATED by nomos-compile")) {
1003
+ throw new Error("not a generated proof (missing the AUTO-GENERATED header) \u2014 run `githolon compile` to (re)generate build/<name>.proof.mts");
1004
+ }
1005
+ const dispatch = proofSource.match(/^await app\.(\w+)\((\{.*\})\);$/m);
1006
+ if (dispatch === null) {
1007
+ throw new Error("generated proof has no `await app.<directive>({...})` dispatch \u2014 regenerate with `githolon compile` (a creating directive is required)");
1008
+ }
1009
+ const directiveId = dispatch[1];
1010
+ const payload = JSON.parse(dispatch[2]);
1011
+ const dom = proofSource.match(/dispatch (\w+)\/(\w+) — offline write/);
1012
+ if (dom === null || dom[2] !== directiveId) {
1013
+ throw new Error("generated proof names no `dispatch <domain>/<directive> \u2014 offline write` \u2014 regenerate with `githolon compile`");
1014
+ }
1015
+ const domain = dom[1];
1016
+ const autoStamped = [];
1017
+ const stampLine = proofSource.split("\n").find((l) => l.includes("omitted \u2014 the client auto-stamps ISO now()"));
1018
+ if (stampLine !== void 0) {
1019
+ for (const m of stampLine.matchAll(/`(\w+)`/g)) autoStamped.push(m[1]);
1020
+ }
1021
+ let queryLeg;
1022
+ const q = proofSource.match(/local declared query (\w+):/);
1023
+ if (q !== null) {
1024
+ const call2 = proofSource.match(/^const rows = await app\.\w+\((\{.*\})\);$/m);
1025
+ if (call2 === null) throw new Error(`generated proof names query '${q[1]}' but its accessor call is missing \u2014 regenerate with \`githolon compile\``);
1026
+ queryLeg = { id: q[1], params: JSON.parse(call2[1]) };
1027
+ }
1028
+ let countLeg;
1029
+ const c = proofSource.match(/local declared count (\w+):/);
1030
+ if (c !== null) {
1031
+ const call2 = proofSource.match(/^const localCount = await app\.\w+\((.*?)\);$/m);
1032
+ if (call2 === null) throw new Error(`generated proof names count '${c[1]}' but its accessor call is missing \u2014 regenerate with \`githolon compile\``);
1033
+ const arg = call2[1].trim();
1034
+ countLeg = { id: c[1], group: arg.length === 0 ? "" : JSON.parse(arg) };
1035
+ }
1036
+ return {
1037
+ domain,
1038
+ directiveId,
1039
+ payload,
1040
+ autoStamped,
1041
+ ...queryLeg !== void 0 ? { query: queryLeg } : {},
1042
+ ...countLeg !== void 0 ? { count: countLeg } : {}
1043
+ };
1044
+ }
1045
+ function runOfflineLegs(eng, ws, lawHash, legs) {
1046
+ const t0 = performance.now();
1047
+ const done = (ok, lines2, error) => ({
1048
+ ok,
1049
+ ms: performance.now() - t0,
1050
+ legs: lines2,
1051
+ ...error !== void 0 ? { error } : {}
1052
+ });
1053
+ const lines = [];
1054
+ const payload = { ...legs.payload };
1055
+ const stamp = (/* @__PURE__ */ new Date()).toISOString();
1056
+ for (const f of legs.autoStamped) payload[f] = stamp;
1057
+ const res = author(eng, ws, legs.domain, legs.directiveId, payload, lawHash);
1058
+ if (res.ok === false) {
1059
+ return done(false, lines, `dispatch ${legs.domain}/${legs.directiveId} refused: ${res.error ?? "unknown"} \u2014 the law itself rejected its own synthesized sample; check the directive's plan/payload schema`);
1060
+ }
1061
+ const createdId = (sealedIntentOf(eng, ws, res)?.events ?? []).find((e) => e.marker === "Create")?.aggregate;
1062
+ lines.push(`\u2713 dispatch ${legs.domain}/${legs.directiveId} \u2014 offline write under the new law${createdId !== void 0 ? ` (${createdId.slice(0, 28)}\u2026)` : ""}`);
1063
+ if (legs.query !== void 0) {
1064
+ const rows = query(eng, ws, legs.query.id, JSON.stringify(legs.query.params));
1065
+ if (rows.length !== 1 || createdId !== void 0 && rows[0].id !== createdId) {
1066
+ return done(false, lines, `declared query ${legs.query.id} expected the 1 created row, got ${JSON.stringify(rows.map((r) => r.id))} \u2014 check the query's key fields against the directive's payload`);
1067
+ }
1068
+ lines.push(`\u2713 declared query ${legs.query.id} answers locally \u2014 1 row`);
1069
+ }
1070
+ if (legs.count !== void 0) {
1071
+ const c = count(eng, ws, legs.count.id, legs.count.group).count;
1072
+ if (c !== 1) {
1073
+ return done(false, lines, `declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) expected 1, got ${c} \u2014 check the count's grouping against the created row`);
1074
+ }
1075
+ lines.push(`\u2713 declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) = 1 locally`);
1076
+ }
1077
+ return done(true, lines);
1078
+ }
1079
+
1080
+ // src/dev.ts
1081
+ var out3 = (s) => void process.stdout.write(s + "\n");
1082
+ var err3 = (s) => void process.stderr.write("error: " + s + "\n");
1083
+ var WS = "dev";
1084
+ var DEFAULT_PORT = 7700;
1085
+ var DEBOUNCE_MS = 200;
1086
+ function statusScreen(s) {
1087
+ const proofLine = s.proof === void 0 ? "\u2013 (no generated proof in build/ \u2014 every compile emits one when the law has a creating directive)" : s.proof.ok ? `\u2713 offline legs green in ${s.proof.ms.toFixed(0)} ms (${s.proof.legs.length} leg(s))` : `\u2717 RED \u2014 ${s.proof.error}`;
1088
+ const lines = [
1089
+ ``,
1090
+ `githolon dev \u2014 ${s.packageName} (cycle ${s.cycle}${s.lastError !== void 0 ? " \u2014 LAST COMPILE FAILED, serving the previous law" : ""})`,
1091
+ ` law ${s.lawHash.slice(0, 16)}\u2026 LIVE in the local holon`,
1092
+ ` compile ${s.compileMs.toFixed(0)} ms law-install ${s.installMs.toFixed(0)} ms`,
1093
+ ` proof ${proofLine}`,
1094
+ ` serving http://127.0.0.1:${s.port} (GET /query /counts/:id /sums/:id /aggregates/:id /status \xB7 POST /dispatch/:domain/:directive)`,
1095
+ ` watching ${s.watchRoots.join(" \xB7 ")}`,
1096
+ ` data ${s.intents} intent(s) on the dev chain \u2014 rows SURVIVE law edits`,
1097
+ ``
1098
+ ];
1099
+ if (s.lastError !== void 0) lines.splice(5, 0, ` jam ${s.lastError.split("\n")[0]}`);
1100
+ return lines.join("\n");
1101
+ }
1102
+ function startServer(s) {
1103
+ return new Promise((resolveSrv, rejectSrv) => {
1104
+ const server = createServer((req, res) => {
1105
+ const respond = (status2, body) => {
1106
+ res.writeHead(status2, { "content-type": "application/json", "access-control-allow-origin": "*" });
1107
+ res.end(JSON.stringify(body));
1108
+ };
1109
+ try {
1110
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${s.port}`);
1111
+ const p = url.pathname;
1112
+ if (p === "/health") return respond(200, { ok: true });
1113
+ if (p === "/status") {
1114
+ return respond(200, {
1115
+ ok: true,
1116
+ package: s.packageName,
1117
+ lawHash: s.lawHash,
1118
+ cycle: s.cycle,
1119
+ compileMs: Math.round(s.compileMs),
1120
+ proof: s.proof === void 0 ? null : { ok: s.proof.ok, ms: Math.round(s.proof.ms), error: s.proof.error ?? null },
1121
+ intents: s.intents,
1122
+ watching: s.watchRoots
1123
+ });
1124
+ }
1125
+ if (p === "/query") {
1126
+ const queryId = url.searchParams.get("queryId");
1127
+ if (queryId === null) return respond(400, { ok: false, error: "pass ?queryId=<id>&params=<json>" });
1128
+ return respond(200, { ok: true, rows: query(s.eng, WS, queryId, url.searchParams.get("params") ?? "{}") });
1129
+ }
1130
+ const counts = p.match(/^\/counts\/([^/]+)$/);
1131
+ if (counts !== null) return respond(200, { ok: true, count: count(s.eng, WS, decodeURIComponent(counts[1]), url.searchParams.get("group") ?? "").count });
1132
+ const sums = p.match(/^\/sums\/([^/]+)$/);
1133
+ if (sums !== null) return respond(200, { ok: true, sum: sum(s.eng, WS, decodeURIComponent(sums[1]), url.searchParams.get("group") ?? "").sum });
1134
+ const byId = p.match(/^\/aggregates\/(.+)$/);
1135
+ if (byId !== null) return respond(200, { ok: true, rows: qById(s.eng, WS, decodeURIComponent(byId[1])) });
1136
+ const dispatch = p.match(/^\/dispatch\/([^/]+)\/([^/]+)$/);
1137
+ if (dispatch !== null && req.method === "POST") {
1138
+ const chunks = [];
1139
+ req.on("data", (c) => chunks.push(c));
1140
+ req.on("end", () => {
1141
+ try {
1142
+ const dec3 = new TextDecoder();
1143
+ const body = chunks.map((c) => dec3.decode(c)).join("") || "{}";
1144
+ const r = author(s.eng, WS, decodeURIComponent(dispatch[1]), decodeURIComponent(dispatch[2]), JSON.parse(body), s.lawHash);
1145
+ if (r["ok"] === false) return respond(422, { ok: false, error: r["error"] });
1146
+ s.intents++;
1147
+ return respond(200, { ok: true, events: sealedIntentOf(s.eng, WS, r)?.events ?? [] });
1148
+ } catch (e) {
1149
+ return respond(422, { ok: false, error: String(e.message ?? e) });
1150
+ }
1151
+ });
1152
+ return;
1153
+ }
1154
+ return respond(404, { ok: false, error: `no such route \u2014 GET /status /query /counts/:id /sums/:id /aggregates/:id, POST /dispatch/:domain/:directive` });
1155
+ } catch (e) {
1156
+ return respond(500, { ok: false, error: String(e.message ?? e) });
1157
+ }
1158
+ });
1159
+ server.on("error", (e) => {
1160
+ rejectSrv(new Error(`local server failed on port ${s.port}: ${e.code ?? e.message} \u2014 pick another with --port <n>`));
1161
+ });
1162
+ server.listen(s.port, "127.0.0.1", () => resolveSrv({ close: () => server.close() }));
1163
+ });
1164
+ }
1165
+ function readDeployBody(cwd, packageName) {
1166
+ const file = join6(cwd, "build", `${packageName}.deploy.json`);
1167
+ if (!existsSync5(file)) {
1168
+ throw new Error(`compile produced no ${file} \u2014 check the compile output above`);
1169
+ }
1170
+ return JSON.parse(readFileSync3(file, "utf8"));
1171
+ }
1172
+ async function installLaw(s, deployBody, installedBy) {
1173
+ const newHash = await sha256hex(deployBody.packageUsda);
1174
+ if (newHash === s.lawHash) return { ok: true };
1175
+ const previous = { ...s.eng.manifestStrings };
1176
+ const t0 = performance.now();
1177
+ setManifests(s.eng, mergeManifests(s.runtime.manifests, deployBody));
1178
+ const res = author(s.eng, WS, "nomos", "installDomain", installPayload(newHash, deployBody.packageUsda, installedBy), s.eng.hashes.nomos);
1179
+ if (res.ok === false) {
1180
+ setManifests(s.eng, previous);
1181
+ return { ok: false, error: `law install refused: ${res.error ?? "unknown"} \u2014 the previous law (${s.lawHash.slice(0, 12)}\u2026) keeps serving; fix and save again` };
1182
+ }
1183
+ s.lawHash = newHash;
1184
+ s.intents++;
1185
+ s.installMs = performance.now() - t0;
1186
+ return { ok: true };
1187
+ }
1188
+ async function runProofCycle(s, cwd, deployBody, installedBy) {
1189
+ const proofPath = join6(cwd, "build", `${s.packageName}.proof.mts`);
1190
+ if (!existsSync5(proofPath)) return void 0;
1191
+ const scratch = `proof-${s.cycle}`;
1192
+ try {
1193
+ const legs = parseOfflineLegs(readFileSync3(proofPath, "utf8"));
1194
+ await mountFresh(s.eng, scratch);
1195
+ author(s.eng, scratch, "bootstrap", "installDomain", installPayload(s.eng.hashes.nomos, s.eng.nomosPkg, installedBy), "");
1196
+ author(s.eng, scratch, "nomos", "installDomain", installPayload(s.lawHash, deployBody.packageUsda, installedBy), s.eng.hashes.nomos);
1197
+ return runOfflineLegs(s.eng, scratch, s.lawHash, legs);
1198
+ } catch (e) {
1199
+ return { ok: false, ms: 0, legs: [], error: String(e.message ?? e) };
1200
+ } finally {
1201
+ unmount(s.eng, scratch);
1202
+ }
1203
+ }
1204
+ async function dev(opts) {
1205
+ const cwd = process.cwd();
1206
+ const cloud = cloudBase(opts.cloud);
1207
+ const project = await loadProject(cwd).catch((e) => {
1208
+ err3(e.message);
1209
+ return void 0;
1210
+ });
1211
+ if (project === void 0) {
1212
+ if (!existsSync5(join6(cwd, CONFIG_FILE))) {
1213
+ err3(`no ${CONFIG_FILE} here (${cwd}) \u2014 \`githolon dev\` runs inside a project; scaffold one from examples/guestbook or \`githolon generate domain <name>\``);
1214
+ }
1215
+ return 1;
1216
+ }
1217
+ const creds = loadCreds();
1218
+ const installedBy = creds.session?.uid ?? creds.principal ?? "githolon-dev";
1219
+ out3(`githolon dev \u2014 ${project.name}: compiling\u2026`);
1220
+ const firstCompile = await compileAsync(cwd);
1221
+ if (!firstCompile.ok) {
1222
+ err3(`compile failed \u2014 the dev loop needs one green compile to mint the holon:
1223
+ ${firstCompile.output}`);
1224
+ return 1;
1225
+ }
1226
+ let deployBody;
1227
+ try {
1228
+ deployBody = readDeployBody(cwd, project.name);
1229
+ } catch (e) {
1230
+ err3(e.message);
1231
+ return 1;
1232
+ }
1233
+ let engBoot;
1234
+ try {
1235
+ engBoot = await holonEngine(cloud, deployBody, installedBy);
1236
+ } catch (e) {
1237
+ err3(`cannot boot the local holon: ${String(e.message ?? e)}
1238
+ the runtime rides ${cloud}/v1/runtime/* and is cached in ~/.holon/runtime after one fetch \u2014 connect once, then dev runs offline`);
1239
+ return 1;
1240
+ }
1241
+ const s = {
1242
+ eng: engBoot.eng,
1243
+ runtime: engBoot.runtime,
1244
+ packageName: project.name,
1245
+ lawHash: "",
1246
+ compileMs: firstCompile.ms,
1247
+ installMs: 0,
1248
+ proof: void 0,
1249
+ cycle: 1,
1250
+ intents: 0,
1251
+ watchRoots: [],
1252
+ port: opts.port ?? DEFAULT_PORT,
1253
+ lastError: void 0
1254
+ };
1255
+ await mountFresh(s.eng, WS);
1256
+ author(s.eng, WS, "bootstrap", "installDomain", installPayload(s.eng.hashes.nomos, s.eng.nomosPkg, installedBy), "");
1257
+ s.intents++;
1258
+ const installed = await installLaw(s, deployBody, installedBy);
1259
+ if (!installed.ok) {
1260
+ err3(installed.error ?? "law install failed");
1261
+ return 1;
1262
+ }
1263
+ s.proof = await runProofCycle(s, cwd, deployBody, installedBy);
1264
+ if (opts.once === true) {
1265
+ out3(statusScreen({ ...s, watchRoots: ["(--once: no watch)"] }));
1266
+ return s.proof === void 0 || s.proof.ok ? 0 : 1;
1267
+ }
1268
+ let server;
1269
+ try {
1270
+ server = await startServer(s);
1271
+ } catch (e) {
1272
+ err3(e.message);
1273
+ return 1;
1274
+ }
1275
+ const moduleDirs = /* @__PURE__ */ new Set();
1276
+ try {
1277
+ const cfgModule = await import(pathToFileURL3(project.configPath).href);
1278
+ for (const d of cfgModule.default?.domains ?? []) {
1279
+ for (const m of d.modules ?? []) moduleDirs.add(dirname3(resolve(cwd, m)));
1280
+ }
1281
+ } catch {
1282
+ moduleDirs.add(join6(cwd, "domains"));
1283
+ }
1284
+ if (moduleDirs.size === 0) moduleDirs.add(join6(cwd, "domains"));
1285
+ let timer;
1286
+ let cycling = false;
1287
+ let queued = false;
1288
+ const cycle = async () => {
1289
+ if (cycling) {
1290
+ queued = true;
1291
+ return;
1292
+ }
1293
+ cycling = true;
1294
+ s.cycle++;
1295
+ const t0 = performance.now();
1296
+ const run = await compileAsync(cwd);
1297
+ s.compileMs = run.ms;
1298
+ if (!run.ok) {
1299
+ s.lastError = run.output.trim() || "compile failed";
1300
+ out3(`\u2717 cycle ${s.cycle}: compile failed \u2014 the previous law keeps serving
1301
+ ${run.output}`);
1302
+ } else {
1303
+ s.lastError = void 0;
1304
+ try {
1305
+ const body = readDeployBody(cwd, s.packageName);
1306
+ const inst = await installLaw(s, body, installedBy);
1307
+ if (!inst.ok) {
1308
+ s.lastError = inst.error ?? "install failed";
1309
+ out3(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
1310
+ } else {
1311
+ s.proof = await runProofCycle(s, cwd, body, installedBy);
1312
+ const total = performance.now() - t0;
1313
+ out3(statusScreen(s));
1314
+ out3(` cycle edit \u2192 law-live \u2192 proof in ${total.toFixed(0)} ms total`);
1315
+ }
1316
+ } catch (e) {
1317
+ s.lastError = String(e.message ?? e);
1318
+ out3(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
1319
+ }
1320
+ }
1321
+ cycling = false;
1322
+ if (queued) {
1323
+ queued = false;
1324
+ void cycle();
1325
+ }
1326
+ };
1327
+ const onChange = (file) => {
1328
+ if (file !== null && (file.startsWith("build") || file.includes("/build/"))) return;
1329
+ clearTimeout(timer);
1330
+ timer = setTimeout(() => void cycle(), DEBOUNCE_MS);
1331
+ };
1332
+ const watchers = [];
1333
+ for (const d of moduleDirs) {
1334
+ if (!existsSync5(d)) continue;
1335
+ watchers.push(watch(d, { recursive: true }, (_evt, f) => onChange(f)));
1336
+ s.watchRoots.push(d.startsWith(cwd) ? d.slice(cwd.length + 1) + "/" : d);
1337
+ }
1338
+ watchers.push(
1339
+ watch(cwd, { recursive: false }, (_evt, f) => {
1340
+ if (f === CONFIG_FILE) onChange(f);
1341
+ })
1342
+ );
1343
+ s.watchRoots.push(CONFIG_FILE);
1344
+ out3(statusScreen(s));
1345
+ out3(" (ctrl-c stops; edits in the watched paths recompile + re-prove automatically)");
1346
+ return new Promise((resolveExit) => {
1347
+ const stop = () => {
1348
+ for (const w of watchers) w.close();
1349
+ server.close();
1350
+ resolveExit(0);
1351
+ };
1352
+ process.on("SIGINT", stop);
1353
+ process.on("SIGTERM", stop);
1354
+ });
1355
+ }
1356
+
1357
+ // src/git.ts
1358
+ import { spawnSync as spawnSync2 } from "node:child_process";
1359
+ import { randomBytes as randomBytes2 } from "node:crypto";
1360
+ import { readFileSync as readFileSync4 } from "node:fs";
1361
+ var out4 = (s) => void process.stdout.write(s + "\n");
1362
+ var err4 = (s) => void process.stderr.write("error: " + s + "\n");
1363
+ function git2(args, opts = {}) {
1364
+ const r = spawnSync2("git", args, { stdio: opts.quiet === true ? "ignore" : "inherit" });
1365
+ return r.status ?? 1;
1366
+ }
1367
+ function ensureSecret(cloud, ws) {
1368
+ const existing = getSecret(cloud, ws);
1369
+ if (existing !== void 0) return existing;
1370
+ const minted = `holon_v1_${randomBytes2(24).toString("hex")}`;
1371
+ setSecret(cloud, ws, minted);
1372
+ return minted;
1373
+ }
1374
+ function parseCredentialInput(input) {
1375
+ const kv = {};
1376
+ for (const line of input.split("\n")) {
1377
+ if (line === "") break;
1378
+ const i = line.indexOf("=");
1379
+ if (i > 0) kv[line.slice(0, i)] = line.slice(i + 1);
1380
+ }
1381
+ return kv;
1382
+ }
1383
+ function workspaceFromPath(path) {
1384
+ const m = (path ?? "").match(/^v1\/workspaces\/([^/]+)\/git(\/|$)/);
1385
+ return m?.[1];
1386
+ }
1387
+ async function gitCredential(action) {
1388
+ if (action !== "get") return 0;
1389
+ const kv = parseCredentialInput(readFileSync4(0, "utf8"));
1390
+ const ws = workspaceFromPath(kv["path"]);
1391
+ if (kv["protocol"] !== "https" || ws === void 0) return 0;
1392
+ const cloud = `https://${kv["host"]}`;
1393
+ const token = await sessionToken().catch(() => void 0);
1394
+ out4(`username=${token ?? "nomos"}`);
1395
+ out4(`password=${ensureSecret(cloud, ws)}`);
1396
+ out4("");
1397
+ return 0;
1398
+ }
1399
+ async function gitSetup(opts) {
1400
+ const cloud = cloudBase(opts.cloud);
1401
+ const self = process.argv[1];
1402
+ if (self === void 0) {
1403
+ err4("cannot locate the githolon CLI entry to install as a credential helper");
1404
+ return 1;
1405
+ }
1406
+ const helper = `!"${process.execPath}" "${self}" git-credential`;
1407
+ if (git2(["config", "--global", `credential.${cloud}.helper`, helper]) !== 0 || git2(["config", "--global", `credential.${cloud}.useHttpPath`, "true"]) !== 0) {
1408
+ err4("git config failed \u2014 is git installed?");
1409
+ return 1;
1410
+ }
1411
+ out4(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
1412
+ out4(` pushes/clones of ${cloud}/v1/workspaces/<ws>/git now authenticate from ~/.holon`);
1413
+ return 0;
1414
+ }
1415
+ async function gitRemote(ws, name, opts) {
1416
+ const cloud = cloudBase(opts.cloud);
1417
+ if (git2(["rev-parse", "--git-dir"], { quiet: true }) !== 0) {
1418
+ err4("not a git repository \u2014 run inside your repo (git init first)");
1419
+ return 1;
1420
+ }
1421
+ const setupRc = await gitSetup(opts);
1422
+ if (setupRc !== 0) return setupRc;
1423
+ ensureSecret(cloud, ws);
1424
+ const url = `${cloud}/v1/workspaces/${ws}/git`;
1425
+ if (git2(["remote", "add", name, url], { quiet: true }) !== 0) {
1426
+ if (git2(["remote", "set-url", name, url], { quiet: true }) !== 0) {
1427
+ err4(`could not add or update remote '${name}'`);
1428
+ return 1;
1429
+ }
1430
+ }
1431
+ const token = await sessionToken().catch(() => void 0);
1432
+ if (token === void 0) {
1433
+ const principal = loadCreds().principal;
1434
+ if (principal === void 0) {
1435
+ err4(
1436
+ `remote '${name}' \u2192 ${url} is wired, but a BIRTH push needs a principal:
1437
+ githolon login --agent (then just push)
1438
+ \u2014 or re-run after githolon ws create/login so a principal is on file`
1439
+ );
1440
+ return 1;
1441
+ }
1442
+ if (git2(["config", `http.${url}.extraHeader`, `x-nomos-principal: ${principal}`]) !== 0) {
1443
+ err4("git config failed setting the principal header");
1444
+ return 1;
1445
+ }
1446
+ }
1447
+ out4(`\u2713 remote '${name}' \u2192 ${url}`);
1448
+ out4(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
1449
+ out4(` birth/deploy by push: git push ${name} main`);
1450
+ return 0;
1451
+ }
1452
+
1453
+ // src/help.ts
1454
+ var HELP = {
1455
+ compile: {
1456
+ usage: "githolon compile [config] [--layered]",
1457
+ what: "Compile the package by ./nomos.package.mjs (the @githolon/dsl launcher): ONE deterministic\n.package.usda (sha256 = the law's content hash) + read/identity manifests + the deploy body +\nthe typed TS client + the generated proof \u2014 all into build/.",
1458
+ flags: [
1459
+ ["[config]", "an alternate config file (default: nomos.package.mjs)"],
1460
+ ["--layered", "also emit the per-domain layered USD view"]
1461
+ ],
1462
+ examples: ["githolon compile", "githolon compile nomos.package.mjs --layered"]
1463
+ },
1464
+ proof: {
1465
+ usage: "githolon proof [build/<name>.proof.mts]",
1466
+ what: "Run the proof GENERATED from your law, live against the cloud (throwaway workspace \u2192\ndeploy \u2192 offline write \u2192 admission \u2192 cloud reads \u2192 convergence). ALL GREEN or it names the jam.\nThe offline half of the same proof reruns automatically inside `githolon dev`.",
1467
+ examples: ["githolon proof", "githolon proof build/guestbook.proof.mts"]
1468
+ },
1469
+ dev: {
1470
+ usage: "githolon dev [--port <n>] [--cloud <url>] [--once]",
1471
+ what: "THE INNER LOOP: watch domains/ + nomos.package.mjs, recompile on save, install the law into\na LOCAL holon (the same engine plane the cloud edge runs \u2014 your data SURVIVES law edits),\nrerun the generated proof's OFFLINE legs, and serve local reads + dispatch on localhost.\nFully offline after the first runtime fetch (~/.holon/runtime).",
1472
+ flags: [
1473
+ ["--port <n>", "the local read server's port (default: 7700)"],
1474
+ ["--cloud <url>", "where the runtime artifacts come from (default: $NOMOS_CLOUD or the public cloud)"],
1475
+ ["--once", "one cycle (compile \u2192 law live \u2192 proof) then exit \u2014 the CI/smoke lane"]
1476
+ ],
1477
+ examples: [
1478
+ "githolon dev",
1479
+ "curl 'http://127.0.0.1:7700/status'",
1480
+ `curl -X POST http://127.0.0.1:7700/dispatch/guestbook/signGuestbook -d '{"author":"ada","message":"hi","mood":"happy","signedAt":"2026-01-01T00:00:00.000Z"}'`,
1481
+ "curl 'http://127.0.0.1:7700/query?queryId=entriesByAuthor&params=%7B%22author%22%3A%22ada%22%7D'"
1482
+ ]
1483
+ },
1484
+ replay: {
1485
+ usage: "githolon replay <ws|dir> [--step N] [--json] [--cloud <url>]",
1486
+ what: "The ledger as film: walk a chain (cloud workspace or local holon directory) and re-apply it\nintent by intent through the SAME wasm gate edge admission runs \u2014 per step: directive, payload,\nthe rows it changed, running per-type tallies. Finale: the chain's verify_chain verdict.\nOn a TTY it steps interactively (enter = next, a = run to end, q = quit).",
1487
+ flags: [
1488
+ ["<ws|dir>", "a workspace name on the cloud, or a local holon directory (githolon ledger init)"],
1489
+ ["--step N", "non-interactive; print the running tallies every N intents"],
1490
+ ["--json", "one JSON line per step + the final verdict (pipe it)"],
1491
+ ["--file <deploy.json>", "stage this deploy body's read manifests (default: the one in ./build)"]
1492
+ ],
1493
+ examples: ["githolon replay my-guestbook", "githolon replay ./my-holon --step 10", "githolon replay my-guestbook --json | jq .directive"]
1494
+ },
1495
+ status: {
1496
+ usage: "githolon status [<ws>] [--target <name>] [--file <deploy.json>]",
1497
+ what: "IS WHAT I BUILT WHAT IS DEPLOYED? Compares the local law hash (build/*.deploy.json) against\nthe workspace's installed law and names the remedy when they differ.\nExit codes: 0 in sync \xB7 2 drift \xB7 1 error.",
1498
+ flags: [
1499
+ ["[<ws>]", "explicit workspace; omitted \u21D2 the project's `workspace` binding in nomos.package.mjs"],
1500
+ ["--target <name>", "pick among named targets (workspace: { dev: \u2026, prod: \u2026 })"],
1501
+ ["--file <path>", "an explicit deploy body (default: the one build/*.deploy.json)"]
1502
+ ],
1503
+ examples: ["githolon status", "githolon status my-guestbook", "githolon status --target prod"]
1504
+ },
1505
+ generate: {
1506
+ usage: "githolon generate <domain|aggregate|intent> <name> [--out <dir>] [--force] [--dry-run]",
1507
+ what: "Rails-style scaffolds on @githolon/dsl: a full domain (aggregate + create/mutate directives),\na lone aggregate, or a lone directive. Names arrive in any casing.",
1508
+ flags: [
1509
+ ["--out <dir>", "target directory (default: ./domains)"],
1510
+ ["--force", "overwrite an existing file"],
1511
+ ["--dry-run", "print the scaffold to stdout, write nothing"]
1512
+ ],
1513
+ examples: ["githolon generate domain work_order", "githolon g aggregate TrackableAsset --dry-run"]
1514
+ },
1515
+ login: {
1516
+ usage: "githolon login --agent | --token <refresh_token>",
1517
+ what: "Establish the principal births ride. --agent self-onboards an anonymous VERIFIED identity\n(no human, no browser; linkable later); --token adopts an existing captain app session.\nSessions live in ~/.holon/credentials.json and auto-refresh.",
1518
+ examples: ["githolon login --agent", "githolon login --token <refresh_token>"]
1519
+ },
1520
+ whoami: {
1521
+ usage: "githolon whoami",
1522
+ what: "Show the principal births will ride (session uid, or the transitional bare uid)."
1523
+ },
1524
+ logout: {
1525
+ usage: "githolon logout",
1526
+ what: "Clear the session. Stored workspace secrets are KEPT."
1527
+ },
1528
+ ws: {
1529
+ usage: "githolon ws <create|status|retire> [<name>] [--principal <uid|token>] [--cloud <url>] [--target <name>]",
1530
+ what: "Workspace lifecycle on Nomos Cloud. `create` births one (saves its ONE-TIME secret to\n~/.holon); `status` shows lineage/phase/verdicts; `retire` frees the quota slot (owner; nothing\nis deleted). `status` with no <name> resolves the project's `workspace` binding.",
1531
+ flags: [
1532
+ ["--principal <p>", "the birth's principal (uid or access token; remembered as the default)"],
1533
+ ["--cloud <url>", "target cloud (default: $NOMOS_CLOUD or the public cloud)"],
1534
+ ["--target <name>", "ws status: pick among the project's named workspace targets"]
1535
+ ],
1536
+ examples: ["githolon ws create my-guestbook", "githolon ws status", "githolon ws retire my-guestbook"]
1537
+ },
1538
+ deploy: {
1539
+ usage: "githolon deploy [<ws>] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
1540
+ what: "POST build/*.deploy.json with the stored workspace secret. Prints what MOVED (old \u2192 new law\nhash). With no <ws>, the project's `workspace` binding in nomos.package.mjs resolves it.",
1541
+ flags: [
1542
+ ["--file <path>", "an explicit deploy body (default: the one build/*.deploy.json)"],
1543
+ ["--target <name>", "pick among named targets (workspace: { dev: \u2026, prod: \u2026 })"],
1544
+ ["--cloud <url>", "target cloud"]
1545
+ ],
1546
+ examples: ["githolon deploy", "githolon deploy my-guestbook", "githolon deploy --target prod"]
1547
+ },
1548
+ secret: {
1549
+ usage: "githolon secret <set <ws> <secret> | rotate <ws>> [--cloud <url>]",
1550
+ what: "The workspace-secret store (~/.holon/credentials.json, 0600). `set` imports one you already\nhold; `rotate` mints a fresh one (and claims a legacy workspace).",
1551
+ examples: ["githolon secret set my-ws nws_v1_\u2026", "githolon secret rotate my-ws"]
1552
+ },
1553
+ git: {
1554
+ usage: "githolon git <setup | remote [<ws>] [<remoteName>]> [--cloud <url>] [--target <name>]",
1555
+ what: "Stock git push as deploy/birth. `setup` installs the credential helper for the cloud host;\n`remote` wires this repo to a workspace (then: git push nomos main). With no <ws>, the\nproject's `workspace` binding resolves it.",
1556
+ examples: ["githolon git setup", "githolon git remote my-guestbook", "git push nomos main"]
1557
+ },
1558
+ ledger: {
1559
+ usage: "githolon ledger <init|verify> <dir> [--file <deploy.json>] [--cloud <url>]",
1560
+ what: "Mint + assert a holon ON YOUR MACHINE. `init` writes genesis + your compiled law as a normal\ngit repo, verified by the SAME wasm gate the cloud runs; `verify` re-runs the chain's\nself-verification from disk. Push the repo to an unborn workspace to birth it.",
1561
+ examples: ["githolon ledger init ./my-holon", "githolon ledger verify ./my-holon"]
1562
+ }
1563
+ };
1564
+ function commandHelp(cmd) {
1565
+ const h = cmd !== void 0 ? HELP[cmd === "g" ? "generate" : cmd] : void 0;
1566
+ if (h === void 0) return void 0;
1567
+ const lines = [`usage: ${h.usage}`, ``, h.what];
1568
+ if (h.flags !== void 0 && h.flags.length > 0) {
1569
+ lines.push(``, `flags:`);
1570
+ const w = Math.max(...h.flags.map(([f]) => f.length));
1571
+ for (const [flag, what] of h.flags) lines.push(` ${flag.padEnd(w)} ${what}`);
1572
+ }
1573
+ if (h.examples !== void 0 && h.examples.length > 0) {
1574
+ lines.push(``, `examples:`);
1575
+ for (const ex of h.examples) lines.push(` ${ex}`);
1576
+ }
1577
+ return lines.join("\n") + "\n";
1578
+ }
1579
+
1580
+ // src/ledger.ts
1581
+ import { spawnSync as spawnSync3 } from "node:child_process";
1582
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
1583
+ import { join as join7 } from "node:path";
1584
+ var out5 = (s) => void process.stdout.write(s + "\n");
1585
+ var err5 = (s) => void process.stderr.write("error: " + s + "\n");
1586
+ var WS2 = "local";
905
1587
  async function ledgerInit(dir, opts) {
906
1588
  const cloud = cloudBase(opts.cloud);
907
- if (existsSync3(dir) && readdirSync2(dir).length > 0) {
908
- err3(`refusing to mint into non-empty directory: ${dir}`);
1589
+ if (existsSync6(dir) && readdirSync3(dir).length > 0) {
1590
+ err5(`refusing to mint into non-empty directory: ${dir}`);
909
1591
  return 1;
910
1592
  }
911
1593
  let deployFile;
912
1594
  try {
913
1595
  deployFile = discoverDeployJson(process.cwd(), opts.file);
914
1596
  } catch (e) {
915
- err3(e.message);
1597
+ err5(e.message);
916
1598
  return 1;
917
1599
  }
918
- const deployBody = JSON.parse(readFileSync3(deployFile, "utf8"));
1600
+ const deployBody = JSON.parse(readFileSync5(deployFile, "utf8"));
919
1601
  const domainHash = await sha256hex(deployBody.packageUsda);
920
1602
  const c = loadCreds();
921
1603
  const principal = await sessionToken().catch(() => void 0) !== void 0 ? c.session.uid : c.principal;
922
1604
  if (principal === void 0) {
923
- err3("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
1605
+ err5("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
924
1606
  return 1;
925
1607
  }
926
- out3(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
927
- const { eng } = await engineFor(cloud, deployBody, principal);
928
- author(eng, WS, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, principal), "");
929
- author(eng, WS, "nomos", "installDomain", installPayload(domainHash, deployBody.packageUsda, principal), eng.hashes.nomos);
930
- const verdict = verifyChainLocal(eng, WS);
1608
+ out5(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
1609
+ const { eng } = await holonEngine(cloud, deployBody, principal);
1610
+ await mountFresh(eng, WS2);
1611
+ author(eng, WS2, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, principal), "");
1612
+ author(eng, WS2, "nomos", "installDomain", installPayload(domainHash, deployBody.packageUsda, principal), eng.hashes.nomos);
1613
+ const verdict = verifyChainLocal(eng, WS2);
931
1614
  if (!printVerdict(verdict)) return 1;
932
- const gitTree = eng.preopen.dir.contents.get("ws").contents.get(WS).contents.get("nomos.git");
933
- const gitDir = join3(dir, ".git");
1615
+ const gitTree = eng.preopen.dir.contents.get("ws").contents.get(WS2).contents.get("nomos.git");
1616
+ const gitDir = join7(dir, ".git");
934
1617
  writeTreeToDisk(gitTree, gitDir);
935
- const cfgPath = join3(gitDir, "config");
936
- writeFileSync3(cfgPath, readFileSync3(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
937
- spawnSync2("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
938
- out3(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
939
- out3(`
1618
+ const cfgPath = join7(gitDir, "config");
1619
+ writeFileSync4(cfgPath, readFileSync5(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
1620
+ spawnSync3("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
1621
+ out5(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
1622
+ out5(`
940
1623
  Birth it on Nomos Cloud (the cloud re-derives this exact verdict):`);
941
- out3(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
1624
+ out5(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
942
1625
  return 0;
943
1626
  }
944
1627
  async function ledgerVerify(dir, opts) {
945
- const gitDir = existsSync3(join3(dir, ".git")) ? join3(dir, ".git") : dir;
946
- if (!existsSync3(join3(gitDir, "HEAD"))) {
947
- err3(`${dir} is not a git repo (no HEAD)`);
1628
+ const gitDir = existsSync6(join7(dir, ".git")) ? join7(dir, ".git") : dir;
1629
+ if (!existsSync6(join7(gitDir, "HEAD"))) {
1630
+ err5(`${dir} is not a git repo (no HEAD)`);
948
1631
  return 1;
949
1632
  }
950
- const { eng } = await engineFor(cloudBase(opts.cloud), void 0, "verify");
951
- const wsDir = eng.preopen.dir.contents.get("ws").contents.get(WS);
1633
+ const { eng } = await holonEngine(cloudBase(opts.cloud), void 0, "verify");
1634
+ await mountFresh(eng, WS2);
1635
+ const wsDir = eng.preopen.dir.contents.get("ws").contents.get(WS2);
952
1636
  wsDir.contents.set("nomos.git", readTreeFromDisk(gitDir));
953
- return printVerdict(verifyChainLocal(eng, WS)) ? 0 : 1;
1637
+ return printVerdict(verifyChainLocal(eng, WS2)) ? 0 : 1;
1638
+ }
1639
+
1640
+ // src/replay.ts
1641
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
1642
+ import { join as join8 } from "node:path";
1643
+ import git3 from "isomorphic-git";
1644
+ var out6 = (s) => void process.stdout.write(s + "\n");
1645
+ var err6 = (s) => void process.stderr.write("error: " + s + "\n");
1646
+ var SOURCE = "source";
1647
+ var REPLAY = "replay";
1648
+ function summarizePayload(payload, max = 100) {
1649
+ if (payload === void 0) return "\u2014";
1650
+ const parts = [];
1651
+ for (const [k, v] of Object.entries(payload)) {
1652
+ const lit = typeof v === "string" ? v : JSON.stringify(v) ?? "undefined";
1653
+ parts.push(`${k}=${lit.length > 24 ? lit.slice(0, 21) + "\u2026" : lit}`);
1654
+ }
1655
+ const s = parts.join(" ");
1656
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1657
+ }
1658
+ function unwrapValue(v) {
1659
+ if (typeof v === "object" && v !== null && !Array.isArray(v)) {
1660
+ const keys = Object.keys(v);
1661
+ if (keys.length === 1 && /^[A-Z]/.test(keys[0])) return v[keys[0]];
1662
+ }
1663
+ return v;
1664
+ }
1665
+ function summarizeOps(ev, max = 100) {
1666
+ const parts = [];
1667
+ for (const o of ev.ops ?? []) {
1668
+ if (o.field === "__type" || o.field === "__id") continue;
1669
+ const kind = Object.keys(o.op ?? {})[0] ?? "?";
1670
+ const val = JSON.stringify(unwrapValue(Object.values(o.op ?? {})[0]));
1671
+ const lit = val !== void 0 && val.length > 20 ? val.slice(0, 17) + "\u2026" : val ?? "";
1672
+ parts.push(kind === "Set" ? `${o.field}\u2190${lit}` : `${kind}(${o.field}${lit ? ` ${lit}` : ""})`);
1673
+ }
1674
+ const s = parts.join(" ");
1675
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1676
+ }
1677
+ function capJsonStrings(v, max = 2048) {
1678
+ if (typeof v === "string") return v.length > max ? `${v.slice(0, max)}\u2026(${v.length} chars)` : v;
1679
+ if (Array.isArray(v)) return v.map((x) => capJsonStrings(x, max));
1680
+ if (typeof v === "object" && v !== null) {
1681
+ const out8 = {};
1682
+ for (const [k, x] of Object.entries(v)) out8[k] = capJsonStrings(x, max);
1683
+ return out8;
1684
+ }
1685
+ return v;
1686
+ }
1687
+ function typeOf(ev) {
1688
+ for (const o of ev.ops ?? []) {
1689
+ if (o.field === "__type") {
1690
+ const v = o.op?.["Set"]?.Str;
1691
+ if (typeof v === "string") return v;
1692
+ }
1693
+ }
1694
+ return (ev.aggregate ?? "?").split(/[_:]/)[0] ?? "?";
1695
+ }
1696
+ async function walkChain(eng, ws) {
1697
+ const gitdir = gitdirOf(ws);
1698
+ const dec3 = new TextDecoder();
1699
+ const entries = [];
1700
+ for (const c of await git3.log({ fs: eng.fs, gitdir, ref: "main" })) {
1701
+ try {
1702
+ const { blob } = await git3.readBlob({ fs: eng.fs, gitdir, oid: c.oid, filepath: "intent.json" });
1703
+ entries.push({ oid: c.oid, bytes: blob, doc: JSON.parse(dec3.decode(blob)) });
1704
+ } catch {
1705
+ }
1706
+ }
1707
+ entries.reverse();
1708
+ return entries;
1709
+ }
1710
+ function readKey() {
1711
+ return new Promise((resolveKey) => {
1712
+ const stdin = process.stdin;
1713
+ stdin.setRawMode?.(true);
1714
+ stdin.resume();
1715
+ stdin.setEncoding("utf8");
1716
+ const onData = (ch) => {
1717
+ stdin.removeListener("data", onData);
1718
+ stdin.setRawMode?.(false);
1719
+ stdin.pause();
1720
+ if (ch === "q" || ch === "") resolveKey("quit");
1721
+ else if (ch === "a") resolveKey("all");
1722
+ else resolveKey("next");
1723
+ };
1724
+ stdin.on("data", onData);
1725
+ });
1726
+ }
1727
+ async function replay(target, opts) {
1728
+ const cloud = cloudBase(opts.cloud);
1729
+ const json = opts.json === true;
1730
+ const emit = (o) => out6(JSON.stringify(o));
1731
+ let overlay;
1732
+ try {
1733
+ overlay = JSON.parse(readFileSync6(discoverDeployJson(process.cwd(), opts.file), "utf8"));
1734
+ } catch {
1735
+ overlay = void 0;
1736
+ }
1737
+ let eng;
1738
+ try {
1739
+ ({ eng } = await holonEngine(cloud, overlay, "githolon-replay"));
1740
+ } catch (e) {
1741
+ err6(`cannot boot the local holon: ${String(e.message ?? e)}
1742
+ the runtime rides ${cloud}/v1/runtime/* (cached in ~/.holon/runtime after one fetch)`);
1743
+ return 1;
1744
+ }
1745
+ const isDir = existsSync7(target) || target.includes("/") || target.startsWith(".");
1746
+ if (isDir) {
1747
+ const gitDir = existsSync7(join8(target, ".git")) ? join8(target, ".git") : target;
1748
+ if (!existsSync7(join8(gitDir, "HEAD"))) {
1749
+ err6(`${target} is not a git repo (no HEAD) \u2014 pass a holon directory (githolon ledger init <dir>) or a workspace name`);
1750
+ return 1;
1751
+ }
1752
+ await mountFresh(eng, SOURCE);
1753
+ eng.preopen.dir.contents.get("ws").contents.get(SOURCE).contents.set("nomos.git", readTreeFromDisk(gitDir));
1754
+ } else {
1755
+ const m = await mountWorkspace(eng, SOURCE, { remote: `${cloud}/v1/workspaces/${target}/git`, headers: {} });
1756
+ if (m.restored !== true) {
1757
+ err6(
1758
+ `workspace '${target}' has no refs/heads/main on ${cloud} \u2014 nothing to replay` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : "") + `
1759
+ check the name (githolon ws status ${target}), or pass a local holon directory instead`
1760
+ );
1761
+ return 1;
1762
+ }
1763
+ }
1764
+ const chain = await walkChain(eng, SOURCE);
1765
+ if (chain.length === 0) {
1766
+ err6(`the chain on '${target}' carries no intents \u2014 nothing to replay`);
1767
+ return 1;
1768
+ }
1769
+ await mountFresh(eng, REPLAY);
1770
+ const interactive = !json && opts.step === void 0 && process.stdout.isTTY === true && process.stdin.isTTY === true;
1771
+ const stepEvery = opts.step ?? (json ? 0 : 0);
1772
+ const tallies = /* @__PURE__ */ new Map();
1773
+ let autoRun = !interactive;
1774
+ if (!json) {
1775
+ out6(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 every step re-admitted through the wasm gate`);
1776
+ if (interactive) out6(" keys: enter = next intent \xB7 a = run to the end \xB7 q = quit\n");
1777
+ }
1778
+ for (let i = 0; i < chain.length; i++) {
1779
+ const { oid, bytes, doc } = chain[i];
1780
+ const domain = doc.payload?.domain ?? "?";
1781
+ const directive = doc.payload?.directiveId ?? "?";
1782
+ const touched = (doc.events ?? []).map((e) => e.aggregate).filter((a) => typeof a === "string");
1783
+ const before = new Map(touched.map((id) => [id, qById(eng, REPLAY, id)[0]?.data]));
1784
+ const t0 = performance.now();
1785
+ const res = applyIntentBytes(eng, REPLAY, bytes);
1786
+ const ms = performance.now() - t0;
1787
+ if (res.ok === false) {
1788
+ const msg = `replay REFUSED at intent ${i} (${domain}/${directive}, ${oid.slice(0, 10)}): ${res.error ?? "unknown"}`;
1789
+ if (json) emit({ step: i, oid, domain, directive, refused: true, error: res.error ?? "unknown" });
1790
+ else err6(msg);
1791
+ err6("the chain's own gate rejects this entry on a fresh fold \u2014 the source it came from would refuse it too (verify below names the check)");
1792
+ printVerdict(verifyChainLocal(eng, SOURCE));
1793
+ return 1;
1794
+ }
1795
+ for (const ev of doc.events ?? []) {
1796
+ if (ev.marker === "Create") tallies.set(typeOf(ev), (tallies.get(typeOf(ev)) ?? 0) + 1);
1797
+ if (ev.marker === "Delete") tallies.set(typeOf(ev), (tallies.get(typeOf(ev)) ?? 1) - 1);
1798
+ }
1799
+ if (json) {
1800
+ emit({
1801
+ step: i,
1802
+ oid,
1803
+ intentId: doc.id ?? null,
1804
+ domain,
1805
+ directive,
1806
+ payload: capJsonStrings(doc.payload?.payload ?? null),
1807
+ events: (doc.events ?? []).map((e) => ({ aggregate: e.aggregate, marker: e.marker, ops: summarizeOps(e) })),
1808
+ applyMs: +ms.toFixed(1),
1809
+ tallies: Object.fromEntries(tallies)
1810
+ });
1811
+ } else {
1812
+ out6(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)}, ${ms.toFixed(0)} ms)`);
1813
+ out6(` payload ${summarizePayload(doc.payload?.payload)}`);
1814
+ for (const ev of doc.events ?? []) {
1815
+ const id = ev.aggregate ?? "?";
1816
+ const was = before.get(id);
1817
+ const verb = ev.marker === "Create" ? "+ created" : ev.marker === "Delete" ? "- deleted" : was === void 0 ? "~ touched" : "~ updated";
1818
+ out6(` ${verb} ${id}`);
1819
+ const ops = summarizeOps(ev);
1820
+ if (ops.length > 0) out6(` ${ops}`);
1821
+ }
1822
+ const tallyLine = [...tallies.entries()].map(([t, n]) => `${t}:${n}`).join(" ");
1823
+ const showTally = stepEvery > 0 ? (i + 1) % stepEvery === 0 || i === chain.length - 1 : true;
1824
+ if (showTally && tallyLine.length > 0) out6(` rows ${tallyLine}`);
1825
+ }
1826
+ if (interactive && !autoRun && i < chain.length - 1) {
1827
+ const key = await readKey();
1828
+ if (key === "quit") {
1829
+ out6(`
1830
+ stopped at intent ${i + 1}/${chain.length} \u2014 the verify verdict below covers the WHOLE chain as delivered`);
1831
+ break;
1832
+ }
1833
+ if (key === "all") autoRun = true;
1834
+ }
1835
+ }
1836
+ if (!json) out6("");
1837
+ const verdict = verifyChainLocal(eng, SOURCE);
1838
+ if (json) {
1839
+ emit({ finale: true, verdict });
1840
+ return verdict["valid"] === true ? 0 : 1;
1841
+ }
1842
+ return printVerdict(verdict) ? 0 : 1;
1843
+ }
1844
+
1845
+ // src/status.ts
1846
+ import { readFileSync as readFileSync7 } from "node:fs";
1847
+ var out7 = (s) => void process.stdout.write(s + "\n");
1848
+ var err7 = (s) => void process.stderr.write("error: " + s + "\n");
1849
+ function lawDiffVerdict(localHash, installed, ws) {
1850
+ const active = installed.filter((d) => d.phase === "Active" && typeof d.domainHash === "string");
1851
+ const match = active.find((d) => d.domainHash === localHash);
1852
+ if (match !== void 0) {
1853
+ return { inSync: true, lines: [`\u2713 in sync \u2014 the deployed law IS your local build (${localHash.slice(0, 16)}\u2026)`] };
1854
+ }
1855
+ const lines = [];
1856
+ if (active.length === 0) {
1857
+ lines.push(`\u2717 workspace '${ws}' has NO active tenant law \u2014 your local build is not deployed`);
1858
+ } else {
1859
+ lines.push(`\u2717 local law differs from every active installation on '${ws}':`);
1860
+ for (const d of active) lines.push(` deployed ${d.domainHash.slice(0, 16)}\u2026 (${d.installedBy ?? "?"})`);
1861
+ }
1862
+ lines.push(` remedy: githolon deploy ${ws} (installs ${localHash.slice(0, 16)}\u2026)`);
1863
+ return { inSync: false, lines };
1864
+ }
1865
+ async function status(wsArg, opts) {
1866
+ const cloud = cloudBase(opts.cloud);
1867
+ let ws;
1868
+ try {
1869
+ ws = await resolveWorkspace(wsArg, process.cwd(), opts.target, "githolon status <ws>");
1870
+ } catch (e) {
1871
+ err7(e.message);
1872
+ return 1;
1873
+ }
1874
+ let localHash;
1875
+ try {
1876
+ const body = JSON.parse(readFileSync7(discoverDeployJson(process.cwd(), opts.file), "utf8"));
1877
+ if (typeof body.packageUsda === "string") localHash = await sha256hex(body.packageUsda);
1878
+ } catch {
1879
+ localHash = void 0;
1880
+ }
1881
+ const r = await fetch(`${cloud}/v1/workspaces/${ws}/domains`).catch(() => void 0);
1882
+ if (r === void 0) {
1883
+ err7(`cannot reach ${cloud} \u2014 githolon status compares against the DEPLOYED law and needs the cloud (local-only: githolon dev)`);
1884
+ return 1;
1885
+ }
1886
+ const d = await r.json().catch(() => void 0);
1887
+ if (!r.ok || d?.ok !== true) {
1888
+ err7(`status '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}
1889
+ is the workspace born? githolon ws create ${ws}`);
1890
+ return 1;
1891
+ }
1892
+ out7(`workspace ${ws} on ${cloud}`);
1893
+ const domains = d.domains ?? [];
1894
+ if (domains.length === 0) out7(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
1895
+ for (const dom of domains) {
1896
+ out7(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}`);
1897
+ }
1898
+ if (localHash === void 0) {
1899
+ out7(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
1900
+ return 0;
1901
+ }
1902
+ out7(`local ${localHash.slice(0, 16)}\u2026 (build/*.deploy.json)`);
1903
+ const verdict = lawDiffVerdict(localHash, domains, ws);
1904
+ for (const line of verdict.lines) out7(line);
1905
+ return verdict.inSync ? 0 : 2;
954
1906
  }
955
1907
 
956
1908
  // src/cli.ts
@@ -958,6 +1910,14 @@ var KINDS = ["domain", "aggregate", "intent"];
958
1910
  var DEFAULT_OUT = "domains";
959
1911
  var USAGE = `githolon \u2014 the Nomos developer CLI
960
1912
 
1913
+ The inner loop:
1914
+ githolon dev watch \u2192 recompile \u2192 law LIVE in a local holon \u2192
1915
+ the generated proof's offline legs rerun \u2192 local
1916
+ reads served \u2014 sub-second, offline after warmup
1917
+ githolon status [<ws>] local law hash vs the DEPLOYED law, remedy named
1918
+ githolon replay <ws|dir> the ledger as film: step the chain intent-by-
1919
+ intent; finale = the verify_chain verdict
1920
+
961
1921
  Authoring:
962
1922
  githolon compile [compiler args] compile the package by ./nomos.package.mjs
963
1923
  githolon proof [build/<name>.proof.mts] run the proof GENERATED from your law, live
@@ -971,19 +1931,21 @@ Identity (~/.holon/credentials.json \u2014 sessions auto-refresh):
971
1931
  githolon whoami show the principal births will ride
972
1932
  githolon logout clear the session (workspace secrets kept)
973
1933
 
974
- Nomos Cloud (secrets live in ~/.holon/credentials.json \u2014 never copy one by hand):
1934
+ Nomos Cloud (secrets live in ~/.holon/credentials.json \u2014 never copy one by hand;
1935
+ a bare <ws> resolves from nomos.package.mjs's optional \`workspace\` binding):
975
1936
  githolon ws create <name> [--principal <uid|token>] birth a workspace; SAVES its one-time secret
976
1937
  (no identity on file? it self-onboards an agent login)
977
- githolon ws status <name> workspace status (lineage, phase, verdicts)
1938
+ githolon ws status [<name>] workspace status (lineage, phase, verdicts)
978
1939
  githolon ws retire <name> retire a workspace (owner) \u2014 quota freed; data retained
979
1940
  per the license; reads keep serving
980
- githolon deploy <ws> [--file <deploy.json>] POST build/*.deploy.json with the stored secret
1941
+ githolon deploy [<ws>] [--file <deploy.json>] POST build/*.deploy.json with the stored secret;
1942
+ prints what moved (old \u2192 new law hash)
981
1943
  githolon secret set <ws> <secret> import a secret you already hold
982
1944
  githolon secret rotate <ws> rotate (and re-save) the workspace secret
983
1945
 
984
1946
  Git lane (stock git push as deploy/birth \u2014 see also: push-to-create):
985
1947
  githolon git setup install the credential helper for the cloud host
986
- githolon git remote <ws> [<remoteName>] wire this repo to a workspace (default name: nomos)
1948
+ githolon git remote [<ws>] [<remoteName>] wire this repo to a workspace (default name: nomos)
987
1949
  then: git push nomos main
988
1950
 
989
1951
  The local-first lane (mint + assert a holon on YOUR machine, under YOUR identity):
@@ -993,21 +1955,23 @@ The local-first lane (mint + assert a holon on YOUR machine, under YOUR identity
993
1955
 
994
1956
  Options:
995
1957
  --cloud <url> target cloud (default: $NOMOS_CLOUD or https://nomos.captainapp.co.uk)
1958
+ --target <name> pick among the project's named workspace targets (workspace: { dev: \u2026, prod: \u2026 })
996
1959
  --out <dir> generate: target directory (default: ${DEFAULT_OUT})
997
1960
  --force generate: overwrite an existing file
998
1961
  --dry-run generate: print to stdout, write nothing
999
- -h, --help show this help
1962
+ -h, --help top level here; \`githolon <command> --help\` for that command's flags + examples
1000
1963
  `;
1001
1964
  function cloudArgs(rest) {
1002
1965
  const pos = [];
1003
1966
  const opts = {};
1004
1967
  for (let i = 0; i < rest.length; i++) {
1005
1968
  const a = rest[i];
1006
- if (a === "--cloud" || a === "--principal" || a === "--file") {
1969
+ if (a === "--cloud" || a === "--principal" || a === "--file" || a === "--target") {
1007
1970
  const v = rest[++i];
1008
1971
  if (v === void 0) return { pos, opts, bad: `${a} requires a value` };
1009
1972
  if (a === "--cloud") opts.cloud = v;
1010
1973
  else if (a === "--principal") opts.principal = v;
1974
+ else if (a === "--target") opts.target = v;
1011
1975
  else opts.file = v;
1012
1976
  } else if (a.startsWith("--")) {
1013
1977
  return { pos, opts, bad: `unknown flag '${a}'` };
@@ -1027,17 +1991,30 @@ ${USAGE}`);
1027
1991
  return 1;
1028
1992
  };
1029
1993
  if (bad !== void 0) return usageFail(bad);
1994
+ const boundWs = async (explicit, hint) => resolveWorkspace(explicit, process.cwd(), opts.target, hint);
1030
1995
  if (command === "ws") {
1031
1996
  const [sub2, name] = pos;
1032
1997
  if (sub2 === "create" && name !== void 0) return wsCreate(name, opts);
1033
- if (sub2 === "status" && name !== void 0) return wsStatus(name, opts);
1998
+ if (sub2 === "status") {
1999
+ try {
2000
+ return await wsStatus(await boundWs(name, `githolon ws status <name>`), opts);
2001
+ } catch (e) {
2002
+ process.stderr.write(`error: ${e.message}
2003
+ `);
2004
+ return 1;
2005
+ }
2006
+ }
1034
2007
  if (sub2 === "retire" && name !== void 0) return wsRetire(name, opts);
1035
2008
  return usageFail("expected: githolon ws <create|status|retire> <name>");
1036
2009
  }
1037
2010
  if (command === "deploy") {
1038
- const [ws2] = pos;
1039
- if (ws2 === void 0) return usageFail("expected: githolon deploy <ws>");
1040
- return deploy(ws2, opts);
2011
+ try {
2012
+ return await deploy(await boundWs(pos[0], `githolon deploy <ws>`), opts);
2013
+ } catch (e) {
2014
+ process.stderr.write(`error: ${e.message}
2015
+ `);
2016
+ return 1;
2017
+ }
1041
2018
  }
1042
2019
  const [sub, ws, secret] = pos;
1043
2020
  if (sub === "set" && ws !== void 0 && secret !== void 0) return secretSet(ws, secret, opts);
@@ -1047,24 +2024,6 @@ ${USAGE}`);
1047
2024
  function isKind(s) {
1048
2025
  return s !== void 0 && KINDS.includes(s);
1049
2026
  }
1050
- function runCompile(args) {
1051
- const resolveDslPkg = (fromDir) => {
1052
- try {
1053
- const req = fromDir === void 0 ? createRequire(import.meta.url) : createRequire(pathToFileURL(join4(fromDir, "noop.js")));
1054
- return req.resolve("@githolon/dsl/package.json");
1055
- } catch {
1056
- return void 0;
1057
- }
1058
- };
1059
- const dslPkg = resolveDslPkg(process.cwd()) ?? resolveDslPkg(void 0);
1060
- if (dslPkg === void 0) {
1061
- process.stderr.write("error: @githolon/dsl not found \u2014 add it to your project's dependencies\n");
1062
- return 1;
1063
- }
1064
- const launcher = join4(dirname2(dslPkg), "compile_package.mjs");
1065
- const r = spawnSync3(process.execPath, [launcher, ...args], { stdio: "inherit", cwd: process.cwd() });
1066
- return r.status ?? 1;
1067
- }
1068
2027
  function runProof(args) {
1069
2028
  const refuse = (m) => {
1070
2029
  process.stderr.write(`error: ${m}
@@ -1074,35 +2033,35 @@ function runProof(args) {
1074
2033
  const explicit = args.find((a) => !a.startsWith("--"));
1075
2034
  let proofPath;
1076
2035
  if (explicit !== void 0) {
1077
- proofPath = resolve(process.cwd(), explicit);
1078
- if (!existsSync4(proofPath)) return refuse(`proof not found: ${proofPath} \u2014 run \`githolon compile\` to (re)generate it`);
2036
+ proofPath = resolve2(process.cwd(), explicit);
2037
+ if (!existsSync8(proofPath)) return refuse(`proof not found: ${proofPath} \u2014 run \`githolon compile\` to (re)generate it`);
1079
2038
  } else {
1080
- const buildDir = join4(process.cwd(), "build");
1081
- if (!existsSync4(buildDir)) {
2039
+ const buildDir = join9(process.cwd(), "build");
2040
+ if (!existsSync8(buildDir)) {
1082
2041
  return refuse("no build/ directory here \u2014 run `githolon compile` first (every compile generates build/<name>.proof.mts from your law)");
1083
2042
  }
1084
- const proofs = readdirSync3(buildDir).filter((f) => f.endsWith(".proof.mts"));
2043
+ const proofs = readdirSync4(buildDir).filter((f) => f.endsWith(".proof.mts"));
1085
2044
  if (proofs.length === 0) {
1086
2045
  return refuse("no build/*.proof.mts found \u2014 run `githolon compile` (every compile generates the proof from your law; the compile summary says why when it can't)");
1087
2046
  }
1088
2047
  if (proofs.length > 1) {
1089
2048
  return refuse(`found ${proofs.length} proofs (${proofs.join(", ")}) \u2014 name one: githolon proof build/<name>.proof.mts`);
1090
2049
  }
1091
- proofPath = join4(buildDir, proofs[0]);
2050
+ proofPath = join9(buildDir, proofs[0]);
1092
2051
  }
1093
2052
  const resolvePkgDir = (name, fromDir) => {
1094
2053
  try {
1095
- return dirname2(createRequire(pathToFileURL(join4(fromDir, "noop.js"))).resolve(`${name}/package.json`));
2054
+ return dirname4(createRequire2(pathToFileURL4(join9(fromDir, "noop.js"))).resolve(`${name}/package.json`));
1096
2055
  } catch {
1097
2056
  return void 0;
1098
2057
  }
1099
2058
  };
1100
2059
  const dslDir = (() => {
1101
2060
  try {
1102
- return dirname2(createRequire(pathToFileURL(join4(process.cwd(), "noop.js"))).resolve("@githolon/dsl/package.json"));
2061
+ return dirname4(createRequire2(pathToFileURL4(join9(process.cwd(), "noop.js"))).resolve("@githolon/dsl/package.json"));
1103
2062
  } catch {
1104
2063
  try {
1105
- return dirname2(createRequire(import.meta.url).resolve("@githolon/dsl/package.json"));
2064
+ return dirname4(createRequire2(import.meta.url).resolve("@githolon/dsl/package.json"));
1106
2065
  } catch {
1107
2066
  return void 0;
1108
2067
  }
@@ -1112,17 +2071,17 @@ function runProof(args) {
1112
2071
  if (tsxDir === void 0) {
1113
2072
  return refuse("tsx not found \u2014 add @githolon/dsl to your project's dependencies (tsx rides along) and npm install");
1114
2073
  }
1115
- const tsxPkg = JSON.parse(readFileSync4(join4(tsxDir, "package.json"), "utf8"));
2074
+ const tsxPkg = JSON.parse(readFileSync8(join9(tsxDir, "package.json"), "utf8"));
1116
2075
  const tsxBinRel = typeof tsxPkg.bin === "string" ? tsxPkg.bin : tsxPkg.bin?.["tsx"];
1117
- const tsxCli = join4(tsxDir, tsxBinRel ?? "dist/cli.mjs");
1118
- const r = spawnSync3(process.execPath, [tsxCli, proofPath], { stdio: "inherit", cwd: process.cwd() });
2076
+ const tsxCli = join9(tsxDir, tsxBinRel ?? "dist/cli.mjs");
2077
+ const r = spawnSync4(process.execPath, [tsxCli, proofPath], { stdio: "inherit", cwd: process.cwd() });
1119
2078
  return r.status ?? 1;
1120
2079
  }
1121
2080
  function parseArgs(argv) {
1122
2081
  const [command, ...rest] = argv;
1123
2082
  if (command !== "generate" && command !== "g") {
1124
2083
  throw new Error(
1125
- `Unknown command '${command ?? "(none)"}'. Expected compile | proof | generate | ws | deploy | secret.`
2084
+ `Unknown command '${command ?? "(none)"}'. Expected dev | compile | proof | replay | status | generate | ws | deploy | secret.`
1126
2085
  );
1127
2086
  }
1128
2087
  let outDir = DEFAULT_OUT;
@@ -1161,7 +2120,11 @@ function parseArgs(argv) {
1161
2120
  return { kind, name, outDir, force, dryRun };
1162
2121
  }
1163
2122
  async function main(argv) {
1164
- if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
2123
+ if (argv.includes("-h") || argv.includes("--help")) {
2124
+ process.stdout.write(commandHelp(argv[0]) ?? USAGE);
2125
+ return 0;
2126
+ }
2127
+ if (argv.length === 0) {
1165
2128
  process.stdout.write(USAGE);
1166
2129
  return 0;
1167
2130
  }
@@ -1171,6 +2134,78 @@ async function main(argv) {
1171
2134
  if (argv[0] === "proof") {
1172
2135
  return runProof(argv.slice(1));
1173
2136
  }
2137
+ if (argv[0] === "dev") {
2138
+ const rest = argv.slice(1);
2139
+ let cloud;
2140
+ let port;
2141
+ let once = false;
2142
+ for (let i = 0; i < rest.length; i++) {
2143
+ const a = rest[i];
2144
+ if (a === "--once") once = true;
2145
+ else if (a === "--cloud") {
2146
+ cloud = rest[++i];
2147
+ if (cloud === void 0) {
2148
+ process.stderr.write("error: --cloud requires a value\n");
2149
+ return 1;
2150
+ }
2151
+ } else if (a === "--port") {
2152
+ port = Number(rest[++i]);
2153
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
2154
+ process.stderr.write("error: --port requires a port number (1-65535)\n");
2155
+ return 1;
2156
+ }
2157
+ } else {
2158
+ process.stderr.write(`error: unknown argument '${a}'
2159
+
2160
+ ${commandHelp("dev")}`);
2161
+ return 1;
2162
+ }
2163
+ }
2164
+ return dev({
2165
+ ...cloud !== void 0 ? { cloud } : {},
2166
+ ...port !== void 0 ? { port } : {},
2167
+ ...once ? { once: true } : {}
2168
+ });
2169
+ }
2170
+ if (argv[0] === "replay") {
2171
+ const rest = argv.slice(1);
2172
+ let step;
2173
+ const sAt = rest.indexOf("--step");
2174
+ if (sAt >= 0) {
2175
+ step = Number(rest[sAt + 1]);
2176
+ if (!Number.isInteger(step) || step <= 0) {
2177
+ process.stderr.write("error: --step requires a positive intent count\n");
2178
+ return 1;
2179
+ }
2180
+ }
2181
+ const json = rest.includes("--json");
2182
+ const cleaned = rest.filter((a, i) => a !== "--json" && a !== "--step" && !(sAt >= 0 && i === sAt + 1));
2183
+ const { pos, opts, bad } = cloudArgs(cleaned);
2184
+ if (bad !== void 0) {
2185
+ process.stderr.write(`error: ${bad}
2186
+
2187
+ ${commandHelp("replay")}`);
2188
+ return 1;
2189
+ }
2190
+ const target = pos[0];
2191
+ if (target === void 0) {
2192
+ process.stderr.write(`error: expected: githolon replay <ws|dir>
2193
+
2194
+ ${commandHelp("replay")}`);
2195
+ return 1;
2196
+ }
2197
+ return replay(target, { ...opts, ...step !== void 0 ? { step } : {}, ...json ? { json: true } : {} });
2198
+ }
2199
+ if (argv[0] === "status") {
2200
+ const { pos, opts, bad } = cloudArgs(argv.slice(1));
2201
+ if (bad !== void 0) {
2202
+ process.stderr.write(`error: ${bad}
2203
+
2204
+ ${commandHelp("status")}`);
2205
+ return 1;
2206
+ }
2207
+ return status(pos[0], opts);
2208
+ }
1174
2209
  if (argv[0] === "ws" || argv[0] === "deploy" || argv[0] === "secret") {
1175
2210
  return runCloud(argv);
1176
2211
  }
@@ -1213,8 +2248,16 @@ ${USAGE}`);
1213
2248
  }
1214
2249
  const [sub, ws, remoteName] = pos;
1215
2250
  if (sub === "setup") return gitSetup(opts);
1216
- if (sub === "remote" && ws !== void 0) return gitRemote(ws, remoteName ?? "nomos", opts);
1217
- process.stderr.write(`error: expected: githolon git <setup | remote <ws> [name]>
2251
+ if (sub === "remote") {
2252
+ try {
2253
+ return await gitRemote(await resolveWorkspace(ws, process.cwd(), opts.target, "githolon git remote <ws>"), remoteName ?? "nomos", opts);
2254
+ } catch (e) {
2255
+ process.stderr.write(`error: ${e.message}
2256
+ `);
2257
+ return 1;
2258
+ }
2259
+ }
2260
+ process.stderr.write(`error: expected: githolon git <setup | remote [<ws>] [name]>
1218
2261
 
1219
2262
  ${USAGE}`);
1220
2263
  return 1;
@@ -1222,8 +2265,8 @@ ${USAGE}`);
1222
2265
  let parsed;
1223
2266
  try {
1224
2267
  parsed = parseArgs(argv);
1225
- } catch (err4) {
1226
- process.stderr.write(`error: ${err4.message}
2268
+ } catch (err8) {
2269
+ process.stderr.write(`error: ${err8.message}
1227
2270
 
1228
2271
  ${USAGE}`);
1229
2272
  return 1;
@@ -1242,8 +2285,8 @@ ${USAGE}`);
1242
2285
  process.stdout.write(`created ${path}
1243
2286
  `);
1244
2287
  return 0;
1245
- } catch (err4) {
1246
- process.stderr.write(`error: ${err4.message}
2288
+ } catch (err8) {
2289
+ process.stderr.write(`error: ${err8.message}
1247
2290
  `);
1248
2291
  return 1;
1249
2292
  }