githolon 0.2.3 → 0.4.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.
- package/dist/cli.mjs +1257 -214
- 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 {
|
|
5
|
-
import {
|
|
6
|
-
import { createRequire } from "node:module";
|
|
7
|
-
import { dirname as
|
|
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")
|
|
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/
|
|
479
|
-
import { spawnSync } from "node:child_process";
|
|
480
|
-
import {
|
|
481
|
-
import {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
const
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
return 0;
|
|
512
|
+
const r = spawnSync(process.execPath, [launcher, ...args], { stdio: "inherit", cwd: process.cwd() });
|
|
513
|
+
return r.status ?? 1;
|
|
535
514
|
}
|
|
536
|
-
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
return
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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/
|
|
575
|
-
import {
|
|
576
|
-
import {
|
|
577
|
-
import {
|
|
578
|
-
import {
|
|
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
|
|
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
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
669
|
+
out8[i] = Number(z >> 11n) / T;
|
|
710
670
|
}
|
|
711
|
-
return
|
|
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
|
|
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
|
|
785
|
-
await
|
|
786
|
-
await
|
|
787
|
-
await
|
|
788
|
-
await
|
|
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
|
-
|
|
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/
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
828
|
+
const cache = join4(configDir(), "runtime");
|
|
832
829
|
let wasmBytes;
|
|
833
|
-
const wasmCache =
|
|
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 =
|
|
839
|
+
wasmBytes = readFileSync2(wasmCache);
|
|
843
840
|
}
|
|
844
|
-
const manifests = await fetchJsonCached(`${cloud}/v1/runtime/manifests`,
|
|
845
|
-
const packages = await fetchJsonCached(`${cloud}/v1/runtime/packages`,
|
|
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 =
|
|
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 =
|
|
872
|
+
const p = join4(diskPath, name);
|
|
876
873
|
if (statSync(p).isDirectory()) contents.set(name, readTreeFromDisk(p));
|
|
877
|
-
else contents.set(name, new File2(
|
|
874
|
+
else contents.set(name, new File2(readFileSync2(p)));
|
|
878
875
|
}
|
|
879
876
|
return new Directory2(contents);
|
|
880
877
|
}
|
|
881
|
-
async function
|
|
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${
|
|
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
|
-
|
|
899
|
-
|
|
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
|
-
|
|
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>¶ms=<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¶ms=%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 (
|
|
908
|
-
|
|
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
|
-
|
|
1597
|
+
err5(e.message);
|
|
916
1598
|
return 1;
|
|
917
1599
|
}
|
|
918
|
-
const deployBody = JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
927
|
-
const { eng } = await
|
|
928
|
-
|
|
929
|
-
author(eng,
|
|
930
|
-
|
|
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(
|
|
933
|
-
const gitDir =
|
|
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 =
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
-
|
|
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 =
|
|
946
|
-
if (!
|
|
947
|
-
|
|
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
|
|
951
|
-
|
|
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,
|
|
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>
|
|
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>]
|
|
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>]
|
|
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
|
|
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"
|
|
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
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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 =
|
|
1078
|
-
if (!
|
|
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 =
|
|
1081
|
-
if (!
|
|
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 =
|
|
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 =
|
|
2050
|
+
proofPath = join9(buildDir, proofs[0]);
|
|
1092
2051
|
}
|
|
1093
2052
|
const resolvePkgDir = (name, fromDir) => {
|
|
1094
2053
|
try {
|
|
1095
|
-
return
|
|
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
|
|
2061
|
+
return dirname4(createRequire2(pathToFileURL4(join9(process.cwd(), "noop.js"))).resolve("@githolon/dsl/package.json"));
|
|
1103
2062
|
} catch {
|
|
1104
2063
|
try {
|
|
1105
|
-
return
|
|
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(
|
|
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 =
|
|
1118
|
-
const r =
|
|
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.
|
|
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"
|
|
1217
|
-
|
|
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 (
|
|
1226
|
-
process.stderr.write(`error: ${
|
|
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 (
|
|
1246
|
-
process.stderr.write(`error: ${
|
|
2288
|
+
} catch (err8) {
|
|
2289
|
+
process.stderr.write(`error: ${err8.message}
|
|
1247
2290
|
`);
|
|
1248
2291
|
return 1;
|
|
1249
2292
|
}
|