midsummer-sol 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.js +200 -6
  2. package/package.json +1 -1
  3. package/sol-mcp.js +1742 -164
  4. package/sol.js +2239 -299
package/sol-mcp.js CHANGED
@@ -60,6 +60,11 @@ function verifyAtt(key, op, sig = op.att) {
60
60
  var init_attest = () => {};
61
61
 
62
62
  // src/errors.ts
63
+ var exports_errors = {};
64
+ __export(exports_errors, {
65
+ CorruptRepoError: () => CorruptRepoError,
66
+ CorruptObjectError: () => CorruptObjectError
67
+ });
63
68
  var CorruptRepoError, CorruptObjectError;
64
69
  var init_errors = __esm(() => {
65
70
  CorruptRepoError = class CorruptRepoError extends Error {
@@ -822,16 +827,74 @@ var init_sign = __esm(() => {
822
827
  });
823
828
 
824
829
  // src/prov.ts
830
+ function encodeTool(p) {
831
+ const surface = p.tool?.trim() || "";
832
+ if (!p.toolCall && !p.testRunId)
833
+ return surface || undefined;
834
+ const segs = [surface, TC_TAG];
835
+ const tc = p.toolCall;
836
+ segs.push(`name=${tc?.name ?? ""}`, `args=${tc?.argsHash ?? ""}`, `inv=${tc?.invocationId ?? ""}`, `test=${p.testRunId ?? ""}`);
837
+ return segs.join(TC_SEP);
838
+ }
839
+ function decodeTool(tool) {
840
+ if (!tool)
841
+ return {};
842
+ const parts = tool.split(TC_SEP);
843
+ if (parts.length < 2 || parts[1] !== TC_TAG)
844
+ return { tool: tool || undefined };
845
+ const surface = parts[0] || undefined;
846
+ const kv = new Map;
847
+ for (const seg of parts.slice(2)) {
848
+ const eq = seg.indexOf("=");
849
+ if (eq >= 0)
850
+ kv.set(seg.slice(0, eq), seg.slice(eq + 1));
851
+ }
852
+ const name = kv.get("name") || "";
853
+ const argsHash = kv.get("args") || "";
854
+ const invocationId = kv.get("inv") || "";
855
+ const testRunId = kv.get("test") || undefined;
856
+ const out = { tool: surface };
857
+ if (name || argsHash || invocationId)
858
+ out.toolCall = { name, argsHash, invocationId };
859
+ if (testRunId)
860
+ out.testRunId = testRunId;
861
+ return out;
862
+ }
863
+ function parseToolCall(raw) {
864
+ if (!raw)
865
+ return;
866
+ let v;
867
+ try {
868
+ v = JSON.parse(raw);
869
+ } catch {
870
+ return;
871
+ }
872
+ if (!v || typeof v !== "object")
873
+ return;
874
+ const o = v;
875
+ const name = typeof o.name === "string" ? o.name : "";
876
+ const argsHash = typeof o.argsHash === "string" ? o.argsHash : "";
877
+ const invocationId = typeof o.invocationId === "string" ? o.invocationId : "";
878
+ if (!name || !argsHash || !invocationId)
879
+ return;
880
+ return { name, argsHash, invocationId };
881
+ }
825
882
  function provenanceFromEnv(env = process.env) {
826
883
  const kind = env.SOL_KIND === "agent" ? "agent" : "human";
884
+ const tool = encodeTool({
885
+ tool: env.SOL_TOOL,
886
+ toolCall: parseToolCall(env.SOL_TOOL_CALL),
887
+ testRunId: env.SOL_TEST_RUN_ID || undefined
888
+ });
827
889
  const node = buildProvNode({
828
890
  agentId: env.SOL_AGENT_ID || (kind === "agent" ? env.SOL_ACTOR : undefined),
829
891
  sessionId: env.SOL_SESSION,
830
892
  model: env.SOL_MODEL,
831
893
  intent: env.SOL_INTENT,
832
- tool: env.SOL_TOOL
894
+ tool
833
895
  });
834
- return node ? { kind, node } : { kind };
896
+ const opRunId = env.SOL_OPERATION_ID || undefined;
897
+ return { kind, ...node ? { node } : {}, ...opRunId ? { opRunId } : {} };
835
898
  }
836
899
  function buildProvNode(fields) {
837
900
  const clean = { kind: "prov" };
@@ -860,6 +923,9 @@ function authorLabel(op, reader) {
860
923
  const bits = [];
861
924
  if (p?.sessionId)
862
925
  bits.push(`session ${p.sessionId}`);
926
+ const tp = p ? decodeTool(p.tool) : {};
927
+ if (tp.toolCall)
928
+ bits.push(`via ${tp.toolCall.name}`);
863
929
  const tail = p?.intent ? ` — intent: ${p.intent}` : "";
864
930
  return `${who}${bits.length ? ` (${bits.join(", ")})` : ""}${tail}`;
865
931
  }
@@ -869,10 +935,21 @@ function provJson(op, reader, key, trust) {
869
935
  return;
870
936
  const out = { kind: op.kind ?? "human" };
871
937
  if (p) {
872
- for (const k of ["agentId", "sessionId", "model", "intent", "tool", "rationale", "parent"])
938
+ for (const k of ["agentId", "sessionId", "model", "intent", "rationale", "parent"])
873
939
  if (p[k])
874
940
  out[k] = p[k];
875
941
  }
942
+ if (p?.tool) {
943
+ const tp = decodeTool(p.tool);
944
+ if (tp.tool)
945
+ out.tool = tp.tool;
946
+ if (tp.toolCall)
947
+ out.toolCall = tp.toolCall;
948
+ if (tp.testRunId)
949
+ out.testRunId = tp.testRunId;
950
+ }
951
+ if (op.opRunId !== undefined)
952
+ out.opRunId = op.opRunId;
876
953
  if (op.sig !== undefined) {
877
954
  const t = trustAuthor(op, trust);
878
955
  out.signed = true;
@@ -930,7 +1007,7 @@ function signTrailerValue(op, trust) {
930
1007
  return `${t.fingerprint} verified`;
931
1008
  }
932
1009
  }
933
- function provTrailers(op, reader, key, trust) {
1010
+ function provTrailers(op, reader, key, trust, reviewCount) {
934
1011
  const p = reader ? provOf(op, reader) : undefined;
935
1012
  const lines = [];
936
1013
  const signed = trustAuthor(op, trust);
@@ -944,6 +1021,13 @@ function provTrailers(op, reader, key, trust) {
944
1021
  lines.push(`Sol-Session: ${p.sessionId}`);
945
1022
  if (p?.model)
946
1023
  lines.push(`Sol-Model: ${p.model}`);
1024
+ const tp = decodeTool(p?.tool);
1025
+ if (tp.toolCall)
1026
+ lines.push(`Sol-ToolCall: ${tp.toolCall.name} args=${tp.toolCall.argsHash} inv=${tp.toolCall.invocationId}`);
1027
+ if (tp.testRunId)
1028
+ lines.push(`Sol-TestRun: ${tp.testRunId}`);
1029
+ if (op.opRunId)
1030
+ lines.push(`Sol-Operation: ${op.opRunId}`);
947
1031
  const intent = p?.intent || (isAgent ? op.message?.trim() || undefined : undefined);
948
1032
  if (intent)
949
1033
  lines.push(`Sol-Intent: ${intent}`);
@@ -952,17 +1036,22 @@ function provTrailers(op, reader, key, trust) {
952
1036
  lines.push(`Sol-Sign: ${sign}`);
953
1037
  if (op.att !== undefined && !(key && !verifyAtt(key, op)))
954
1038
  lines.push(`Sol-Attested: pushedBy=${op.pushedBy ?? "?"}`);
1039
+ if (reviewCount && reviewCount > 0)
1040
+ lines.push(`Sol-Attestations: ${reviewCount}`);
955
1041
  return lines;
956
1042
  }
957
1043
  async function captureProv(sink, env = process.env) {
958
- const { kind, node } = provenanceFromEnv(env);
1044
+ const { kind, node, opRunId } = provenanceFromEnv(env);
959
1045
  const out = {};
960
1046
  if (kind === "agent")
961
1047
  out.kind = "agent";
962
1048
  if (node)
963
1049
  out.prov = await sink.put(node);
1050
+ if (opRunId)
1051
+ out.opRunId = opRunId;
964
1052
  return out;
965
1053
  }
1054
+ var TC_SEP = "\x01", TC_TAG = "tc1";
966
1055
  var init_prov = __esm(() => {
967
1056
  init_attest();
968
1057
  init_sign();
@@ -994,10 +1083,25 @@ class AsyncRepo {
994
1083
  await this.log.append(by === this.actor ? this.sign(entry) : entry);
995
1084
  }
996
1085
  provFor;
1086
+ pendingToolCall;
1087
+ setToolCall(tc) {
1088
+ this.pendingToolCall = tc;
1089
+ }
997
1090
  async provenance(by) {
998
1091
  if (by !== this.actor)
999
1092
  return {};
1000
- return this.provFor ??= captureProv(this.store);
1093
+ const base = await (this.provFor ??= captureProv(this.store));
1094
+ const tc = this.pendingToolCall;
1095
+ if (!tc)
1096
+ return base;
1097
+ this.pendingToolCall = undefined;
1098
+ const { kind, node } = provenanceFromEnv();
1099
+ const session = node ?? { kind: "prov" };
1100
+ const surface = decodeTool(session.tool);
1101
+ const tool = encodeTool({ tool: surface.tool, toolCall: tc, testRunId: surface.testRunId });
1102
+ const merged = { ...session, kind: "prov", tool };
1103
+ const prov = await this.store.put(merged);
1104
+ return { ...kind === "agent" ? { kind: "agent" } : {}, prov, ...base.opRunId ? { opRunId: base.opRunId } : {} };
1001
1105
  }
1002
1106
  async currentRoot() {
1003
1107
  const head = await this.log.head();
@@ -1305,6 +1409,9 @@ class SolWorkspace {
1305
1409
  this.actor = actor;
1306
1410
  this.repo = new AsyncRepo(store, log, actor);
1307
1411
  }
1412
+ setToolCall(tc) {
1413
+ this.repo.setToolCall(tc);
1414
+ }
1308
1415
  async write(path, content) {
1309
1416
  return this.repo.writeFile(safePath(path), content);
1310
1417
  }
@@ -2239,6 +2346,15 @@ var init_diff = __esm(() => {
2239
2346
  });
2240
2347
 
2241
2348
  // src/file-store.ts
2349
+ var exports_file_store = {};
2350
+ __export(exports_file_store, {
2351
+ openLocal: () => openLocal,
2352
+ initLocal: () => initLocal,
2353
+ encodeObject: () => encodeObject2,
2354
+ decodeObject: () => decodeObject2,
2355
+ FileStore: () => FileStore2,
2356
+ FileOpLog: () => FileOpLog2
2357
+ });
2242
2358
  import { appendFileSync as appendFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
2243
2359
  import { join as join2 } from "node:path";
2244
2360
  import { gunzipSync as gunzipSync2, gzipSync as gzipSync2 } from "node:zlib";
@@ -2328,6 +2444,17 @@ class FileOpLog2 {
2328
2444
  return pageOps(all, opts);
2329
2445
  }
2330
2446
  }
2447
+ function initLocal(dir, actor = "anon") {
2448
+ const solDir = join2(dir, ".sol");
2449
+ mkdirSync2(solDir, { recursive: true });
2450
+ const store = new FileStore2(solDir);
2451
+ const log = new FileOpLog2(solDir);
2452
+ return { repo: new AsyncRepo(store, log, actor), store, log, solDir };
2453
+ }
2454
+ function openLocal(dir, actor = "anon") {
2455
+ const solDir = join2(dir, ".sol");
2456
+ return new AsyncRepo(new FileStore2(solDir), new FileOpLog2(solDir), actor);
2457
+ }
2331
2458
  var init_file_store2 = __esm(() => {
2332
2459
  init_async_repo();
2333
2460
  init_chain();
@@ -2845,6 +2972,74 @@ var init_keyring_at_rest = __esm(() => {
2845
2972
  });
2846
2973
 
2847
2974
  // src/bin/lib.ts
2975
+ var exports_lib = {};
2976
+ __export(exports_lib, {
2977
+ writeWorkingIndexAt: () => writeWorkingIndexAt,
2978
+ writeWorkingIndex: () => writeWorkingIndex,
2979
+ workingChanges: () => workingChanges,
2980
+ workingChangeRows: () => workingChangeRows,
2981
+ walkFiles: () => walkFiles,
2982
+ walkEntries: () => walkEntries,
2983
+ viewMetaPath: () => viewMetaPath,
2984
+ unresolvedConflictPaths: () => unresolvedConflictPaths,
2985
+ truncateOpLog: () => truncateOpLog,
2986
+ solDir: () => solDir,
2987
+ snapshotTree: () => snapshotTree,
2988
+ snapshotFile: () => snapshotFile,
2989
+ setSealedDecryptor: () => setSealedDecryptor,
2990
+ setOpLogHead: () => setOpLogHead,
2991
+ saveTrust: () => saveTrust,
2992
+ saveRefs: () => saveRefs,
2993
+ saveKeyRing: () => saveKeyRing,
2994
+ revealedHeadForRender: () => revealedHeadForRender,
2995
+ revealedHead: () => revealedHead,
2996
+ resolveRefSoft: () => resolveRefSoft,
2997
+ resolveRef: () => resolveRef,
2998
+ repoRoot: () => repoRoot,
2999
+ repoRel: () => repoRel,
3000
+ refsPath: () => refsPath,
3001
+ readViewMeta: () => readViewMeta,
3002
+ procCwd: () => procCwd,
3003
+ printWorkingDiff: () => printWorkingDiff,
3004
+ printTreeDiff: () => printTreeDiff,
3005
+ persistTree: () => persistTree,
3006
+ parentSolDir: () => parentSolDir,
3007
+ opsDir: () => opsDir,
3008
+ open: () => open,
3009
+ objectsDir: () => objectsDir,
3010
+ materializeTree: () => materializeTree,
3011
+ materializeInto: () => materializeInto,
3012
+ materializeDiff: () => materializeDiff,
3013
+ materialize: () => materialize,
3014
+ lockDir: () => lockDir,
3015
+ loadWorkingIndex: () => loadWorkingIndex,
3016
+ loadTrust: () => loadTrust,
3017
+ loadStore: () => loadStore,
3018
+ loadSigner: () => loadSigner,
3019
+ loadRefs: () => loadRefs,
3020
+ loadKeyRing: () => loadKeyRing,
3021
+ lexists: () => lexists,
3022
+ isWorkingPath: () => isWorkingPath,
3023
+ isIgnored: () => isIgnored,
3024
+ ignorePatterns: () => ignorePatterns,
3025
+ forceIncluded: () => forceIncluded,
3026
+ fileChange: () => fileChange,
3027
+ die: () => die,
3028
+ cwd: () => cwd,
3029
+ countChanges: () => countChanges,
3030
+ conflictHunks: () => conflictHunks,
3031
+ cfgDir: () => cfgDir,
3032
+ blameFile: () => blameFile,
3033
+ appendFileSync: () => appendFileSync3,
3034
+ appendCommit: () => appendCommit,
3035
+ appendCapture: () => appendCapture,
3036
+ allLocalNodes: () => allLocalNodes,
3037
+ addInclude: () => addInclude,
3038
+ actor: () => actor,
3039
+ acquireLock: () => acquireLock,
3040
+ LazyStore: () => LazyStore,
3041
+ DEFAULT_IGNORE: () => DEFAULT_IGNORE
3042
+ });
2848
3043
  import {
2849
3044
  appendFileSync as appendFileSync3,
2850
3045
  chmodSync as chmodSync3,
@@ -2854,6 +3049,7 @@ import {
2854
3049
  readdirSync as readdirSync3,
2855
3050
  readFileSync as readFileSync5,
2856
3051
  readlinkSync,
3052
+ renameSync as renameSync3,
2857
3053
  symlinkSync,
2858
3054
  unlinkSync,
2859
3055
  writeFileSync as writeFileSync5
@@ -2884,6 +3080,9 @@ function parentSolDir(dir = solDir) {
2884
3080
  const m = readViewMeta(dir);
2885
3081
  return m ? resolve(dir, m.parent) : undefined;
2886
3082
  }
3083
+ function cfgDir(dir = solDir) {
3084
+ return parentSolDir(dir) ?? dir;
3085
+ }
2887
3086
  function repoRel(p) {
2888
3087
  return relative(cwd, resolve(procCwd, p));
2889
3088
  }
@@ -3806,6 +4005,35 @@ function setOpLogHead(head) {
3806
4005
  const cur = existsSync5(hf) ? JSON.parse(readFileSync5(hf, "utf8")) : { seq: 0 };
3807
4006
  writeFileSync5(hf, JSON.stringify({ ...cur, head }));
3808
4007
  }
4008
+ function truncateOpLog(toSeq) {
4009
+ const opsFile = join5(solDir, "ops.jsonl");
4010
+ if (!existsSync5(opsFile))
4011
+ return;
4012
+ const kept = [];
4013
+ let last;
4014
+ for (const l of readFileSync5(opsFile, "utf8").split(`
4015
+ `)) {
4016
+ if (!l)
4017
+ continue;
4018
+ let op;
4019
+ try {
4020
+ op = JSON.parse(l);
4021
+ } catch {
4022
+ break;
4023
+ }
4024
+ if (op.seq > toSeq)
4025
+ break;
4026
+ kept.push(l);
4027
+ last = op;
4028
+ }
4029
+ const tmp = `${opsFile}.tmp`;
4030
+ writeFileSync5(tmp, kept.length ? kept.join(`
4031
+ `) + `
4032
+ ` : "");
4033
+ renameSync3(tmp, opsFile);
4034
+ const hf = join5(solDir, "HEAD");
4035
+ writeFileSync5(hf, JSON.stringify(last ? { head: last.rootAfter, seq: last.seq, logTip: last.entryHash } : { seq: 0 }));
4036
+ }
3809
4037
  async function persistTree(src, dst, head) {
3810
4038
  const node = src.get(head);
3811
4039
  if (!node)
@@ -3877,7 +4105,7 @@ function saveKeyRing(ring) {
3877
4105
  chmodSync3(keysPath(), 384);
3878
4106
  } catch {}
3879
4107
  }
3880
- var procCwd, repoRoot, cwd, solDir, actor, viewMetaPath = (dir = solDir) => join5(dir, "view.json"), viewParent, objectsDir = () => join5(viewParent ?? solDir, "objects"), lockDir = () => solDir, SYMLINK_MODE = 40960, EXEC_MODE = 493, DEFAULT_IGNORE, includePath = () => join5(solDir, "include"), LOCK_STALE_MS = 5000, LazyStore, sealedDecryptor, indent = (s) => s.split(`
4108
+ var procCwd, repoRoot, cwd, solDir, actor, viewMetaPath = (dir = solDir) => join5(dir, "view.json"), viewParent, objectsDir = () => join5(viewParent ?? solDir, "objects"), opsDir = () => solDir, lockDir = () => solDir, SYMLINK_MODE = 40960, EXEC_MODE = 493, DEFAULT_IGNORE, includePath = () => join5(solDir, "include"), LOCK_STALE_MS = 5000, LazyStore, sealedDecryptor, indent = (s) => s.split(`
3881
4109
  `).map((l) => " " + l).join(`
3882
4110
  `), indexPath = () => join5(solDir, "index.json"), linesOf = (content) => content === "" ? [] : content.replace(/\n$/, "").split(`
3883
4111
  `), refsPath = () => join5(solDir, "refs.json"), saveRefs = (refs) => writeFileSync5(refsPath(), JSON.stringify(refs, null, 2)), _signer, trustPath = () => join5(solDir, "trust.json"), keysPath = () => join5(solDir, "keys.json");
@@ -3930,9 +4158,13 @@ __export(exports_remote, {
3930
4158
  writeBundle: () => writeBundle,
3931
4159
  saveRemote: () => saveRemote,
3932
4160
  remoteRefs: () => remoteRefs,
4161
+ remotePushFinalize: () => remotePushFinalize,
4162
+ remotePushChunked: () => remotePushChunked,
4163
+ remotePushBatch: () => remotePushBatch,
3933
4164
  remotePush: () => remotePush,
3934
4165
  remotePromote: () => remotePromote,
3935
4166
  remoteHead: () => remoteHead,
4167
+ remoteGate: () => remoteGate,
3936
4168
  remoteExport: () => remoteExport,
3937
4169
  remoteEnvPush: () => remoteEnvPush,
3938
4170
  remoteEnvPull: () => remoteEnvPull,
@@ -3943,6 +4175,7 @@ __export(exports_remote, {
3943
4175
  policyRemove: () => policyRemove,
3944
4176
  policyGet: () => policyGet,
3945
4177
  policyCheck: () => policyCheck,
4178
+ ownerSet: () => ownerSet,
3946
4179
  mrOpen: () => mrOpen,
3947
4180
  mrList: () => mrList,
3948
4181
  mrGet: () => mrGet,
@@ -3965,6 +4198,44 @@ async function call(cfg, token, path, init) {
3965
4198
  throw new Error(`remote ${path} -> ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}`);
3966
4199
  return res.json();
3967
4200
  }
4201
+ function splitNodes(nodes, batchSizeBytes) {
4202
+ const batches = [];
4203
+ let cur = [];
4204
+ let curBytes = 0;
4205
+ for (const node of nodes) {
4206
+ const nb = sizeOf(node);
4207
+ if (cur.length && curBytes + nb > batchSizeBytes) {
4208
+ batches.push(cur);
4209
+ cur = [];
4210
+ curBytes = 0;
4211
+ }
4212
+ cur.push(node);
4213
+ curBytes += nb;
4214
+ }
4215
+ if (cur.length)
4216
+ batches.push(cur);
4217
+ return batches.length ? batches : [[]];
4218
+ }
4219
+ async function remotePushChunked(cfg, token, body, batchSizeBytes = DEFAULT_BATCH_BYTES) {
4220
+ const totalBytes = sizeOf(body.nodes) + sizeOf(body.ops);
4221
+ if (totalBytes <= batchSizeBytes)
4222
+ return remotePush(cfg, token, body);
4223
+ const stamp = `${body.head ?? "h"}-${Date.now()}`;
4224
+ const nodeBatches = splitNodes(body.nodes, batchSizeBytes);
4225
+ const batchIds = [];
4226
+ try {
4227
+ for (let i = 0;i < nodeBatches.length; i++) {
4228
+ const batchId = `${stamp}-${i}`;
4229
+ batchIds.push(batchId);
4230
+ await remotePushBatch(cfg, token, batchId, nodeBatches[i], i === 0 ? body.ops : [], i);
4231
+ }
4232
+ } catch (e) {
4233
+ if (e instanceof Error && /-> 404/.test(e.message))
4234
+ return remotePush(cfg, token, body);
4235
+ throw e;
4236
+ }
4237
+ return remotePushFinalize(cfg, token, batchIds, body.branch, body.head, body.expectedHead);
4238
+ }
3968
4239
  function loadRemote(solDir2) {
3969
4240
  const p = join6(solDir2, "remote.json");
3970
4241
  return existsSync6(p) ? JSON.parse(readFileSync6(p, "utf8")) : undefined;
@@ -3985,9 +4256,10 @@ async function writeBundle(solDir2, bundle, from = 0) {
3985
4256
  writeFileSync6(join6(solDir2, "HEAD"), JSON.stringify({ head: bundle.head, seq: bundle.seq, logTip: bundle.tip }));
3986
4257
  return fresh.length;
3987
4258
  }
3988
- var endpoint = (cfg, path) => `${cfg.url.replace(/\/+$/, "")}${path}${path.includes("?") ? "&" : "?"}repo=${encodeURIComponent(cfg.repo)}`, remoteHead = (cfg, token) => call(cfg, token, "/head"), remoteExport = (cfg, token) => call(cfg, token, "/export"), remoteRefs = (cfg, token) => call(cfg, token, "/refs"), remotePush = (cfg, token, body) => call(cfg, token, "/push", { method: "POST", body: JSON.stringify(body) }), remotePromote = (cfg, token, branch) => call(cfg, token, "/promote", { method: "POST", body: JSON.stringify({ branch }) }), mrOpen = (cfg, token, body) => call(cfg, token, "/mr", { method: "POST", body: JSON.stringify(body) }), mrList = (cfg, token) => call(cfg, token, "/mrs"), mrDiff = (cfg, token, id) => call(cfg, token, `/mr/diff?id=${id}`), mrGet = (cfg, token, id) => call(cfg, token, `/mr?id=${id}`), mrAction = (cfg, token, action, body) => call(cfg, token, `/mr/${action}`, { method: "POST", body: JSON.stringify(body) }), accessGet = (cfg, token) => call(cfg, token, "/access"), accessSet = (cfg, token, body) => call(cfg, token, "/access", { method: "POST", body: JSON.stringify(body) }), policyCheck = (cfg, token, paths) => call(cfg, token, "/policy/check", { method: "POST", body: JSON.stringify({ paths }) }), policyGet = (cfg, token) => call(cfg, token, "/policy"), policyUpsert = (cfg, token, rule) => call(cfg, token, "/policy?write=1", { method: "POST", body: JSON.stringify({ op: "upsert", rule }) }), policyRemove = (cfg, token, pattern) => call(cfg, token, "/policy?write=1", { method: "POST", body: JSON.stringify({ op: "remove", pattern }) }), recipientsForPath = (cfg, token, path) => call(cfg, token, `/recipients?path=${encodeURIComponent(path)}`), recipientsForAudience = (cfg, token, audience, recovery) => call(cfg, token, `/recipients?audience=${encodeURIComponent(JSON.stringify(audience))}${recovery?.length ? `&recovery=${encodeURIComponent(recovery.join(","))}` : ""}`), remoteEnvPush = (cfg, token, bundle) => call(cfg, token, "/env/push", { method: "POST", body: JSON.stringify(bundle) }), remoteEnvAnchor = (cfg, token) => call(cfg, token, "/env/anchor"), remoteEnvPull = (cfg, token) => call(cfg, token, "/env/pull"), forkMeta = (cfg, token, parent) => call(cfg, token, "/fork-meta", { method: "POST", body: JSON.stringify({ parent }) }), forksList = (cfg, token) => call(cfg, token, "/forks"), saveRemote = (solDir2, cfg) => writeFileSync6(join6(solDir2, "remote.json"), JSON.stringify(cfg, null, 2));
4259
+ var endpoint = (cfg, path) => `${cfg.url.replace(/\/+$/, "")}${path}${path.includes("?") ? "&" : "?"}repo=${encodeURIComponent(cfg.repo)}`, remoteHead = (cfg, token) => call(cfg, token, "/head"), remoteGate = (cfg, token, branch) => call(cfg, token, `/head?gateState=true${branch ? `&branch=${encodeURIComponent(branch)}` : ""}`), remoteExport = (cfg, token) => call(cfg, token, "/export"), remoteRefs = (cfg, token) => call(cfg, token, "/refs"), remotePush = (cfg, token, body) => call(cfg, token, "/push", { method: "POST", body: JSON.stringify(body) }), remotePromote = (cfg, token, branch) => call(cfg, token, "/promote", { method: "POST", body: JSON.stringify({ branch }) }), remotePushBatch = (cfg, token, batchId, nodes, ops, seq) => call(cfg, token, "/push/batch", { method: "POST", body: JSON.stringify({ batchId, nodes, ops, seq }) }), remotePushFinalize = (cfg, token, batchIds, branch, head, expectedHead) => call(cfg, token, "/push/finalize", { method: "POST", body: JSON.stringify({ batchIds, branch, head, expectedHead }) }), DEFAULT_BATCH_BYTES, sizeOf = (v) => JSON.stringify(v).length, mrOpen = (cfg, token, body) => call(cfg, token, "/mr", { method: "POST", body: JSON.stringify(body) }), mrList = (cfg, token) => call(cfg, token, "/mrs"), mrDiff = (cfg, token, id) => call(cfg, token, `/mr/diff?id=${id}`), mrGet = (cfg, token, id) => call(cfg, token, `/mr?id=${id}`), mrAction = (cfg, token, action, body) => call(cfg, token, `/mr/${action}`, { method: "POST", body: JSON.stringify(body) }), accessGet = (cfg, token) => call(cfg, token, "/access"), ownerSet = (cfg, token, ownerHandle) => call(cfg, token, "/owner", { method: "POST", body: JSON.stringify({ ownerHandle }) }), accessSet = (cfg, token, body) => call(cfg, token, "/access", { method: "POST", body: JSON.stringify(body) }), policyCheck = (cfg, token, paths) => call(cfg, token, "/policy/check", { method: "POST", body: JSON.stringify({ paths }) }), policyGet = (cfg, token) => call(cfg, token, "/policy"), policyUpsert = (cfg, token, rule) => call(cfg, token, "/policy?write=1", { method: "POST", body: JSON.stringify({ op: "upsert", rule }) }), policyRemove = (cfg, token, pattern) => call(cfg, token, "/policy?write=1", { method: "POST", body: JSON.stringify({ op: "remove", pattern }) }), recipientsForPath = (cfg, token, path) => call(cfg, token, `/recipients?path=${encodeURIComponent(path)}`), recipientsForAudience = (cfg, token, audience, recovery) => call(cfg, token, `/recipients?audience=${encodeURIComponent(JSON.stringify(audience))}${recovery?.length ? `&recovery=${encodeURIComponent(recovery.join(","))}` : ""}`), remoteEnvPush = (cfg, token, bundle) => call(cfg, token, "/env/push", { method: "POST", body: JSON.stringify(bundle) }), remoteEnvAnchor = (cfg, token) => call(cfg, token, "/env/anchor"), remoteEnvPull = (cfg, token) => call(cfg, token, "/env/pull"), forkMeta = (cfg, token, parent) => call(cfg, token, "/fork-meta", { method: "POST", body: JSON.stringify({ parent }) }), forksList = (cfg, token) => call(cfg, token, "/forks"), saveRemote = (solDir2, cfg) => writeFileSync6(join6(solDir2, "remote.json"), JSON.stringify(cfg, null, 2));
3989
4260
  var init_remote = __esm(() => {
3990
4261
  init_file_store2();
4262
+ DEFAULT_BATCH_BYTES = 5 * 1024 * 1024;
3991
4263
  });
3992
4264
 
3993
4265
  // src/bin/test-gate.ts
@@ -6034,6 +6306,314 @@ var init_sealed_client = __esm(() => {
6034
6306
  init_crypto();
6035
6307
  });
6036
6308
 
6309
+ // src/bin/admin-repair.ts
6310
+ var exports_admin_repair = {};
6311
+ __export(exports_admin_repair, {
6312
+ planRepair: () => planRepair,
6313
+ makeResolves: () => makeResolves,
6314
+ applyRepair: () => applyRepair
6315
+ });
6316
+ async function makeResolves(store2) {
6317
+ const cache = new Map;
6318
+ const walk = async (h, seen) => {
6319
+ if (cache.get(h) === true)
6320
+ return true;
6321
+ if (seen.has(h))
6322
+ return true;
6323
+ seen.add(h);
6324
+ const node = await store2.get(h);
6325
+ if (!node)
6326
+ return false;
6327
+ if (node.kind === "tree") {
6328
+ for (const e of Object.values(node.entries))
6329
+ if (!await walk(e.hash, seen))
6330
+ return false;
6331
+ }
6332
+ return true;
6333
+ };
6334
+ return async (root) => {
6335
+ if (cache.has(root))
6336
+ return cache.get(root);
6337
+ const ok = await walk(root, new Set);
6338
+ cache.set(root, ok);
6339
+ return ok;
6340
+ };
6341
+ }
6342
+ async function lastResolvableRoot(ops, upToIdx, resolves) {
6343
+ for (let i = Math.min(upToIdx, ops.length - 1);i >= 0; i--) {
6344
+ if (await resolves(ops[i].rootAfter))
6345
+ return { op: ops[i], idx: i };
6346
+ }
6347
+ return;
6348
+ }
6349
+ function firstBrokenIdx(ops) {
6350
+ let prev;
6351
+ for (let i = 0;i < ops.length; i++) {
6352
+ if (ops[i].seq !== i + 1)
6353
+ return i;
6354
+ try {
6355
+ verifyChain(ops.slice(0, i + 1));
6356
+ } catch {
6357
+ return i;
6358
+ }
6359
+ prev = ops[i].entryHash;
6360
+ }
6361
+ return -1;
6362
+ }
6363
+ async function planRepair(ops, head, resolves) {
6364
+ const broken = firstBrokenIdx(ops);
6365
+ if (broken === -1) {
6366
+ if (ops.length === 0)
6367
+ return { kind: "noop", note: "", reason: "empty repo — nothing to repair" };
6368
+ const headResolves = head !== undefined && await resolves(head);
6369
+ if (headResolves)
6370
+ return { kind: "noop", note: "", reason: "repo is healthy — chain valid, head resolves" };
6371
+ const found2 = await lastResolvableRoot(ops, ops.length - 1, resolves);
6372
+ if (!found2) {
6373
+ return { kind: "unfixable", note: "", reason: head === undefined ? "head pointer lost and no historical root fully resolves — content is missing locally; rehydrate with `sol recover --from-remote <url> <repo>`" : `head ${head} dangles and no historical root fully resolves — a deep object is missing locally; rehydrate with \`sol recover --from-remote <url> <repo>\`` };
6374
+ }
6375
+ return {
6376
+ kind: "repoint-head",
6377
+ repointHead: found2.op.rootAfter,
6378
+ note: `repair: re-pointed head to ${found2.op.rootAfter} (rootAfter of seq ${found2.op.seq})`,
6379
+ reason: head === undefined ? "head pointer lost — recovered from the op-log" : `head ${head} dangles — recovered to the last fully-resolvable root`
6380
+ };
6381
+ }
6382
+ const keepIdx = broken - 1;
6383
+ if (keepIdx < 0) {
6384
+ return { kind: "truncate-chain", truncateTo: 0, toEmptyTree: true, note: "repair: chain broken at genesis — truncated to an empty repo", reason: `op-log chain broken at seq ${ops[0]?.seq ?? 1} (the first op)` };
6385
+ }
6386
+ const prefix = ops.slice(0, keepIdx + 1);
6387
+ const found = await lastResolvableRoot(prefix, keepIdx, resolves);
6388
+ const truncateTo = prefix[keepIdx].seq;
6389
+ if (!found) {
6390
+ return { kind: "truncate-chain", truncateTo, toEmptyTree: true, note: `repair: truncated to seq ${truncateTo}; no resolvable root in the kept prefix — head -> empty tree`, reason: `op-log chain broken at seq ${ops[broken].seq}; kept prefix has no resolvable root` };
6391
+ }
6392
+ return {
6393
+ kind: "truncate-chain",
6394
+ truncateTo,
6395
+ repointHead: found.op.rootAfter,
6396
+ note: `repair: truncated to seq ${truncateTo} (chain broke at seq ${ops[broken].seq}); re-pointed head to ${found.op.rootAfter}`,
6397
+ reason: `op-log chain broken at seq ${ops[broken].seq} — truncated to the last verified link (seq ${found.op.seq})`
6398
+ };
6399
+ }
6400
+ async function applyRepair(exec, by = "sol-doctor", at = Date.now()) {
6401
+ const ops = await exec.log.history();
6402
+ const head = await exec.log.head();
6403
+ const resolves = await makeResolves(exec.store);
6404
+ const plan = await planRepair(ops, head, resolves);
6405
+ if (plan.kind === "noop" || plan.kind === "unfixable")
6406
+ return plan;
6407
+ if (plan.kind === "truncate-chain" && plan.truncateTo !== undefined)
6408
+ await exec.truncate(plan.truncateTo);
6409
+ const newHead = plan.toEmptyTree ? await emptyRoot(exec.store) : plan.repointHead;
6410
+ if (newHead !== undefined)
6411
+ await exec.setHead(newHead);
6412
+ const repaired = newHead ?? head;
6413
+ const seq = await exec.log.seq() + 1;
6414
+ await exec.log.append({ seq, type: "checkpoint", path: "", rootAfter: repaired ?? await emptyRoot(exec.store), at, by, message: plan.note });
6415
+ return plan;
6416
+ }
6417
+ var init_admin_repair = __esm(() => {
6418
+ init_chain();
6419
+ init_async_tree();
6420
+ });
6421
+
6422
+ // src/bin/admin.ts
6423
+ var exports_admin = {};
6424
+ __export(exports_admin, {
6425
+ runDoctor: () => runDoctor,
6426
+ renderDoctorIntegrity: () => renderDoctorIntegrity,
6427
+ checkOpLog: () => checkOpLog,
6428
+ checkObjects: () => checkObjects
6429
+ });
6430
+ function checkOpLog(ops) {
6431
+ try {
6432
+ for (let i = 0;i < ops.length; i++)
6433
+ if (ops[i].seq !== i + 1)
6434
+ throw new Error(`gap before seq ${i + 1}`);
6435
+ const tip = verifyChain(ops);
6436
+ return { count: ops.length, chainOk: true, tip };
6437
+ } catch (e) {
6438
+ return { count: ops.length, chainOk: false, chainError: String(e?.message ?? e) };
6439
+ }
6440
+ }
6441
+ async function checkObjects(store2, head, ops) {
6442
+ const missing = [];
6443
+ const seen = new Set;
6444
+ if (head === undefined)
6445
+ return { reachable: 0, missing };
6446
+ const seqOfRoot = new Map;
6447
+ for (const op of ops)
6448
+ if (!seqOfRoot.has(op.rootAfter))
6449
+ seqOfRoot.set(op.rootAfter, op.seq);
6450
+ const walk = async (h, refBy) => {
6451
+ if (seen.has(h))
6452
+ return;
6453
+ seen.add(h);
6454
+ const node = await store2.get(h);
6455
+ if (!node) {
6456
+ missing.push({ hash: h, refBy });
6457
+ return;
6458
+ }
6459
+ if (node.kind === "tree")
6460
+ for (const e of Object.values(node.entries))
6461
+ await walk(e.hash, refBy);
6462
+ };
6463
+ await walk(head, seqOfRoot.get(head));
6464
+ return { reachable: seen.size - missing.length, missing };
6465
+ }
6466
+ async function runDoctor(store2, log) {
6467
+ const ops = await log.history();
6468
+ const head = await log.head();
6469
+ const seq = await log.seq();
6470
+ const oplog = checkOpLog(ops);
6471
+ const objects = await checkObjects(store2, head, ops);
6472
+ const headLost = head === undefined && seq > 0;
6473
+ const headDangling = head !== undefined && objects.missing.some((m) => m.hash === head);
6474
+ const ok = oplog.chainOk && objects.missing.length === 0 && !headLost;
6475
+ return { ok, oplog, objects, head, seq, headLost, headDangling };
6476
+ }
6477
+ function renderDoctorIntegrity(r) {
6478
+ const lines = [];
6479
+ const chain = r.oplog.chainOk ? `chain valid${r.oplog.tip ? `, tip: ${r.oplog.tip.slice(0, 16)}` : ""}` : `chain BROKEN — ${r.oplog.chainError}`;
6480
+ lines.push(` op-log: ${r.oplog.count} op(s), ${chain}`);
6481
+ if (r.headLost) {
6482
+ lines.push(` head: LOST (${r.seq} op(s) but no head pointer) — run \`sol fsck --repair\``);
6483
+ } else if (r.headDangling) {
6484
+ lines.push(` head: DANGLING (${r.head?.slice(0, 16)} missing from store) — run \`sol fsck --repair\``);
6485
+ } else {
6486
+ lines.push(` objects: ${r.objects.reachable} reachable, ${r.objects.missing.length} missing${r.objects.missing.length ? " (run: sol fsck --repair)" : ""}`);
6487
+ }
6488
+ for (const m of r.objects.missing)
6489
+ lines.push(` missing ${m.hash}${m.refBy !== undefined ? ` (op seq ${m.refBy})` : ""}`);
6490
+ return lines;
6491
+ }
6492
+ var init_admin = __esm(() => {
6493
+ init_chain();
6494
+ });
6495
+
6496
+ // src/bin/admin-reflog.ts
6497
+ var exports_admin_reflog = {};
6498
+ __export(exports_admin_reflog, {
6499
+ renderReflog: () => renderReflog,
6500
+ formatReflogLine: () => formatReflogLine,
6501
+ filterReflog: () => filterReflog,
6502
+ buildReflog: () => buildReflog
6503
+ });
6504
+ function buildReflog(ops) {
6505
+ const rows = [];
6506
+ let prevRoot;
6507
+ for (const op of ops) {
6508
+ rows.push({ seq: op.seq, by: op.by ?? "?", at: op.at, type: op.type, from: prevRoot, to: op.rootAfter, message: op.message, merge: op.parent2 });
6509
+ prevRoot = op.rootAfter;
6510
+ }
6511
+ return rows;
6512
+ }
6513
+ function filterReflog(rows, f = {}) {
6514
+ return rows.filter((r) => {
6515
+ if (f.actor !== undefined && r.by !== f.actor)
6516
+ return false;
6517
+ if (f.since !== undefined && r.at < f.since)
6518
+ return false;
6519
+ if (f.type !== undefined && r.type !== f.type)
6520
+ return false;
6521
+ return true;
6522
+ });
6523
+ }
6524
+ function formatReflogLine(r, branch) {
6525
+ const when = new Date(r.at).toISOString().replace("T", " ").slice(0, 19);
6526
+ const verb = r.merge ? "merge" : r.type;
6527
+ const move = `${short(r.from)} -> ${short(r.to)}`;
6528
+ const label = branch ? `${branch}: ` : "";
6529
+ const body = r.message ? `${verb} ${label}${r.message}` : `${verb} ${label}`.trimEnd();
6530
+ return `${r.seq} ${r.by} ${when} ${body} (${move})`;
6531
+ }
6532
+ function short(h) {
6533
+ if (!h)
6534
+ return "(empty)";
6535
+ return h.length > 14 ? h.slice(0, 14) : h;
6536
+ }
6537
+ function renderReflog(ops, opts = {}) {
6538
+ let rows = filterReflog(buildReflog(ops), opts);
6539
+ if (opts.branchFilter) {
6540
+ const b = opts.branchFilter;
6541
+ rows = rows.filter((r) => r.type === "checkpoint" && (r.message?.includes(b) ?? false));
6542
+ }
6543
+ return rows.reverse().map((r) => formatReflogLine(r, opts.branch));
6544
+ }
6545
+
6546
+ // src/bin/admin-recover.ts
6547
+ var exports_admin_recover = {};
6548
+ __export(exports_admin_recover, {
6549
+ restoreFileFromHistory: () => restoreFileFromHistory,
6550
+ listAllRoots: () => listAllRoots,
6551
+ dumpLog: () => dumpLog,
6552
+ RECOVER_HELP: () => RECOVER_HELP
6553
+ });
6554
+ async function listAllRoots(ops, has) {
6555
+ const byRoot = new Map;
6556
+ for (const op of ops) {
6557
+ byRoot.set(op.rootAfter, { root: op.rootAfter, seq: op.seq, at: op.at, message: op.message });
6558
+ }
6559
+ const out = [...byRoot.values()].sort((a, b) => b.seq - a.seq);
6560
+ if (has)
6561
+ for (const e of out)
6562
+ e.resolvable = await has(e.root);
6563
+ return out;
6564
+ }
6565
+ async function restoreFileFromHistory(store2, ops, path, from) {
6566
+ let window = ops;
6567
+ if (typeof from === "number")
6568
+ window = ops.filter((o) => o.seq <= from);
6569
+ else if (typeof from === "string") {
6570
+ const bare = from.startsWith("h_") ? from : "h_" + from;
6571
+ const idx = ops.findIndex((o) => o.rootAfter === bare || o.rootAfter.startsWith(bare));
6572
+ window = idx >= 0 ? ops.slice(0, idx + 1) : [];
6573
+ if (idx < 0)
6574
+ return { found: false, path, reason: `no op produced root ${from}` };
6575
+ }
6576
+ const tried = new Set;
6577
+ for (let i = window.length - 1;i >= 0; i--) {
6578
+ const root = window[i].rootAfter;
6579
+ if (tried.has(root))
6580
+ continue;
6581
+ tried.add(root);
6582
+ if (!await store2.has(root))
6583
+ continue;
6584
+ const leaf = await nodeAt(store2, root, path);
6585
+ if (!leaf)
6586
+ continue;
6587
+ if (leaf.kind === "sealed")
6588
+ return { found: true, path, sealed: true, box: leaf.box, fromSeq: window[i].seq, fromRoot: root };
6589
+ const blob = await fileAt(store2, root, path);
6590
+ return { found: true, path, content: leaf.content, encoding: blob?.encoding, fromSeq: window[i].seq, fromRoot: root };
6591
+ }
6592
+ return { found: false, path, reason: from !== undefined ? `path ${path} not found at or before ${from}` : `path ${path} never existed in history` };
6593
+ }
6594
+ function dumpLog(ops, fromSeq, toSeq) {
6595
+ const window = ops.filter((o) => (fromSeq === undefined || o.seq >= fromSeq) && (toSeq === undefined || o.seq <= toSeq));
6596
+ return JSON.stringify(window, null, 2);
6597
+ }
6598
+ var RECOVER_HELP = `sol recover — guide through recovery scenarios
6599
+
6600
+ 1. Rehydrate from the cloud (download ops + objects from the backend)
6601
+ sol recover --from-remote <url> <repo>
6602
+
6603
+ 2. List every root the repo ever landed on (find a lost head)
6604
+ sol recover --list-all-roots
6605
+
6606
+ 3. Restore a file from op history (even if the current tree dropped it)
6607
+ sol recover --file <path> [--from <seq|hash>]
6608
+
6609
+ 4. Dump raw op-log entries as JSON (manual inspection / salvage)
6610
+ sol recover --dump-log [<from-seq> [<to-seq>]]
6611
+
6612
+ after a structural repair, run \`sol doctor\` to confirm the repo is healthy.`;
6613
+ var init_admin_recover = __esm(() => {
6614
+ init_async_tree();
6615
+ });
6616
+
6037
6617
  // src/bin/secret-scrub.ts
6038
6618
  var exports_secret_scrub = {};
6039
6619
  __export(exports_secret_scrub, {
@@ -6440,6 +7020,140 @@ var init_merge = __esm(() => {
6440
7020
  init_tree();
6441
7021
  });
6442
7022
 
7023
+ // src/bin/errors-friendly.ts
7024
+ var exports_errors_friendly = {};
7025
+ __export(exports_errors_friendly, {
7026
+ friendlyError: () => friendlyError,
7027
+ formatSealedError: () => formatSealedError,
7028
+ formatMissingObjectError: () => formatMissingObjectError,
7029
+ formatChainError: () => formatChainError,
7030
+ formatAuthError: () => formatAuthError,
7031
+ classifyAuthError: () => classifyAuthError
7032
+ });
7033
+ function classifyAuthError(status, message = "") {
7034
+ if (status === 401 || / -> 401\b/.test(message) || /\b401\b/.test(message))
7035
+ return "expired";
7036
+ if (status === 403 || / -> 403\b/.test(message) || /\b403\b/.test(message))
7037
+ return "forbidden";
7038
+ if (/invalid token|malformed|not a jwt|jwt/i.test(message))
7039
+ return "invalid";
7040
+ return "unknown";
7041
+ }
7042
+ function formatAuthError(kind, repo) {
7043
+ switch (kind) {
7044
+ case "expired":
7045
+ return "session expired — run `sol auth login` (or set SOL_TOKEN)";
7046
+ case "forbidden":
7047
+ return `you lack access${repo ? ` to ${repo}` : " to this repo"} — ask the owner to grant you access, or check you're logged in as the right account (\`sol auth status\`)`;
7048
+ case "invalid":
7049
+ return "invalid token format — SOL_TOKEN is not a valid session token; re-run `sol auth login` to mint a fresh one";
7050
+ default:
7051
+ return "authentication failed — run `sol auth login` (or set SOL_TOKEN), then try again";
7052
+ }
7053
+ }
7054
+ function formatSealedError(path) {
7055
+ return `sealed file ${path} — you lack a decryption key.
7056
+ -> \`sol keys\` to see your keyring
7057
+ -> ask the owner to re-seal this path to your account (\`sol seal ${path} <you>\`)`;
7058
+ }
7059
+ function formatChainError(err) {
7060
+ const m = err.message;
7061
+ const seq = /seq (\d+)/.exec(m)?.[1];
7062
+ const where = seq ? `op-log chain broken at seq ${seq}` : "op-log chain broken";
7063
+ return `${where}: ${m.replace(/^corrupt repo: /, "")}
7064
+ -> \`sol doctor\` for a full diagnosis
7065
+ -> \`sol fsck --repair\` to recover (truncates to the last verified link)`;
7066
+ }
7067
+ function formatMissingObjectError(hash, seq) {
7068
+ const ref = seq !== undefined ? ` referenced by op seq ${seq}` : "";
7069
+ return `object ${hash}${ref} not found in store.
7070
+ -> \`sol doctor\` then \`sol fsck --repair\` to recover from backup
7071
+ -> or \`sol recover --from-remote <url> <repo>\` to rehydrate from the cloud`;
7072
+ }
7073
+ function friendlyError(err, ctx) {
7074
+ if (err instanceof CorruptObjectError)
7075
+ return formatMissingObjectError(err.hash);
7076
+ if (err instanceof CorruptRepoError) {
7077
+ if (/dangling head|head is lost|missing from the store/.test(err.message)) {
7078
+ return `${err.message.replace(/^corrupt repo: /, "")}
7079
+ -> \`sol doctor\` then \`sol fsck --repair\` to re-point the head`;
7080
+ }
7081
+ return formatChainError(err);
7082
+ }
7083
+ const msg = err instanceof Error ? err.message : String(err);
7084
+ const kind = classifyAuthError(undefined, msg);
7085
+ if (kind !== "unknown")
7086
+ return formatAuthError(kind, ctx?.repo);
7087
+ return msg;
7088
+ }
7089
+ var init_errors_friendly = __esm(() => {
7090
+ init_errors();
7091
+ });
7092
+
7093
+ // src/bin/admin-remote-verify.ts
7094
+ var exports_admin_remote_verify = {};
7095
+ __export(exports_admin_remote_verify, {
7096
+ renderRemoteVerify: () => renderRemoteVerify,
7097
+ remoteVerify: () => remoteVerify,
7098
+ classifyRemoteFailure: () => classifyRemoteFailure
7099
+ });
7100
+ function classifyRemoteFailure(status, message, repo) {
7101
+ if (status === 401 || status === 403)
7102
+ return { kind: "auth", message: formatAuthError(classifyAuthError(status, message), repo) };
7103
+ if (status !== undefined)
7104
+ return { kind: "backend", status, message: `backend error (${status}) — transient, try again: ${message}`.trim() };
7105
+ return { kind: "network", message: `could not reach the remote — connectivity issue: ${message}` };
7106
+ }
7107
+ async function remoteVerify(fetchLike, url, repo, token) {
7108
+ const endpoint2 = `${url.replace(/\/+$/, "")}/fsck?repo=${encodeURIComponent(repo)}`;
7109
+ let res;
7110
+ try {
7111
+ res = await fetchLike(endpoint2, { headers: { authorization: `Bearer ${token}`, "content-type": "application/json" } });
7112
+ } catch (e) {
7113
+ return classifyRemoteFailure(undefined, String(e?.message ?? e), repo);
7114
+ }
7115
+ if (!res.ok) {
7116
+ const body = await res.text().catch(() => "");
7117
+ return classifyRemoteFailure(res.status, body.slice(0, 200), repo);
7118
+ }
7119
+ const fsck = await res.json();
7120
+ return fsck.ok ? { kind: "ok", fsck } : { kind: "corrupt", fsck };
7121
+ }
7122
+ function renderRemoteVerify(r) {
7123
+ switch (r.kind) {
7124
+ case "ok":
7125
+ return {
7126
+ lines: [
7127
+ " checking remote repository...",
7128
+ ` op-log: chain ${r.fsck.chainOk ? "valid" : "BROKEN"}${r.fsck.head ? `, tip: ${r.fsck.head.slice(0, 16)}` : ""}`,
7129
+ ` objects: ${r.fsck.checked} reachable, ${r.fsck.missing.length} missing`,
7130
+ " status: OK"
7131
+ ],
7132
+ exitCode: 0
7133
+ };
7134
+ case "corrupt":
7135
+ return {
7136
+ lines: [
7137
+ " checking remote repository...",
7138
+ ` op-log: chain ${r.fsck.chainOk ? "valid" : `BROKEN — ${r.fsck.chainError ?? "?"}`}`,
7139
+ ` objects: ${r.fsck.checked} reachable, ${r.fsck.missing.length} missing`,
7140
+ ...r.fsck.missing.map((h) => ` missing ${h}`),
7141
+ " status: PROBLEMS FOUND — the remote repository is corrupted"
7142
+ ],
7143
+ exitCode: 1
7144
+ };
7145
+ case "auth":
7146
+ return { lines: [` ${r.message}`], exitCode: 1 };
7147
+ case "backend":
7148
+ return { lines: [` ${r.message}`], exitCode: 1 };
7149
+ case "network":
7150
+ return { lines: [` ${r.message}`], exitCode: 1 };
7151
+ }
7152
+ }
7153
+ var init_admin_remote_verify = __esm(() => {
7154
+ init_errors_friendly();
7155
+ });
7156
+
6443
7157
  // src/bin/local-peer.ts
6444
7158
  var exports_local_peer = {};
6445
7159
  __export(exports_local_peer, {
@@ -6690,12 +7404,333 @@ var init_converge = __esm(() => {
6690
7404
  EMPTY_TREE2 = { kind: "tree", entries: {} };
6691
7405
  });
6692
7406
 
7407
+ // src/bin/sync-watch-fs.ts
7408
+ var exports_sync_watch_fs = {};
7409
+ __export(exports_sync_watch_fs, {
7410
+ buildFsSyncEnv: () => buildFsSyncEnv
7411
+ });
7412
+ import { existsSync as existsSync14, readFileSync as readFileSync14 } from "node:fs";
7413
+ function resolveRemote() {
7414
+ const cfg = loadRemote(solDir);
7415
+ if (!cfg)
7416
+ return;
7417
+ return cfg.url ? cfg : { ...cfg, url: DEFAULT_REMOTE_URL };
7418
+ }
7419
+ function readRefs() {
7420
+ return existsSync14(refsPath()) ? JSON.parse(readFileSync14(refsPath(), "utf8")) : undefined;
7421
+ }
7422
+ async function buildFsSyncEnv(opts) {
7423
+ if (!existsSync14(solDir))
7424
+ throw new Error("not a sol repo — run `sol init` first");
7425
+ const { repo, log } = open();
7426
+ const cfg = resolveRemote() || (() => {
7427
+ throw new Error("no remote — set one with `sol remote <url> <repo>`");
7428
+ })();
7429
+ const token = opts.token;
7430
+ const replica = { store: new FileStore2(solDir, objectsDir()), log };
7431
+ const currentBranch = () => readRefs()?.current ?? "main";
7432
+ const local = {
7433
+ replica,
7434
+ async capture() {
7435
+ const snap = await snapshotTree(repo);
7436
+ if (snap.changed)
7437
+ writeWorkingIndex(await repo.list());
7438
+ return snap.changed;
7439
+ },
7440
+ head: () => log.head(),
7441
+ seq: () => log.seq(),
7442
+ logTip: () => log.logTip(),
7443
+ history: () => log.history(),
7444
+ async nodes() {
7445
+ return allLocalNodes();
7446
+ },
7447
+ branch: currentBranch,
7448
+ forkBase() {
7449
+ const refs = readRefs();
7450
+ return refs?.branches[refs.current]?.remote;
7451
+ },
7452
+ async dirty() {
7453
+ const wc = workingChanges(loadStore(), await log.head() ?? "");
7454
+ return wc.added.length + wc.modified.length + wc.removed.length > 0;
7455
+ },
7456
+ async absorbCanonical(bundle, fromHead) {
7457
+ const head = bundle.head ?? fromHead;
7458
+ const canonBundle = {
7459
+ head,
7460
+ seq: bundle.ops.length ? bundle.ops[bundle.ops.length - 1].seq : 0,
7461
+ tip: bundle.ops.length ? bundle.ops[bundle.ops.length - 1].entryHash : undefined,
7462
+ ops: bundle.ops,
7463
+ nodes: bundle.nodes,
7464
+ refs: bundle.branches ? { branches: bundle.branches, production: readRefs()?.current ?? "main" } : undefined
7465
+ };
7466
+ await writeBundle(solDir, canonBundle, 0);
7467
+ await local.adopt(fromHead, head, bundle.branches);
7468
+ },
7469
+ async adopt(from, to, branches) {
7470
+ const refs = readRefs();
7471
+ if (refs) {
7472
+ if (branches) {
7473
+ for (const [name, h] of Object.entries(branches)) {
7474
+ refs.branches[name] = { head: refs.branches[name]?.head ?? h, base: refs.branches[name]?.base ?? h, remote: h };
7475
+ }
7476
+ }
7477
+ const cur = refs.current;
7478
+ if (refs.branches[cur]) {
7479
+ refs.branches[cur].head = to;
7480
+ refs.branches[cur].remote = to;
7481
+ }
7482
+ saveRefs(refs);
7483
+ }
7484
+ setOpLogHead(to);
7485
+ if (to && to !== from)
7486
+ materializeDiff(loadStore(), from, to);
7487
+ }
7488
+ };
7489
+ const remote = {
7490
+ async head() {
7491
+ const rh = await remoteHead(cfg, token);
7492
+ return { head: rh.head, tip: rh.tip, seq: rh.seq };
7493
+ },
7494
+ async export() {
7495
+ const b = await remoteExport(cfg, token);
7496
+ return { head: b.head, tip: b.tip, seq: b.seq, ops: b.ops, nodes: b.nodes, branches: b.refs?.branches };
7497
+ },
7498
+ async push(body) {
7499
+ const res = await remotePush(cfg, token, body);
7500
+ return { head: res.head, applied: res.applied, merged: res.merged, conflicts: res.conflicts, branches: res.refs?.branches };
7501
+ }
7502
+ };
7503
+ if (!loadRemote(solDir)?.url && cfg.repo)
7504
+ saveRemote(solDir, cfg);
7505
+ const logger = {
7506
+ info: (line) => console.log(line),
7507
+ warn: (line) => console.error("sol: " + line)
7508
+ };
7509
+ const runLocked = async (fn) => {
7510
+ const release = acquireLock();
7511
+ try {
7512
+ return await fn();
7513
+ } finally {
7514
+ release();
7515
+ }
7516
+ };
7517
+ return { local, remote, log: logger, actor, dryRun: !!opts.dryRun, runLocked };
7518
+ }
7519
+ var DEFAULT_REMOTE_URL;
7520
+ var init_sync_watch_fs = __esm(() => {
7521
+ init_file_store2();
7522
+ init_lib();
7523
+ init_remote();
7524
+ DEFAULT_REMOTE_URL = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
7525
+ });
7526
+
7527
+ // src/bin/sync-watch.ts
7528
+ var exports_sync_watch = {};
7529
+ __export(exports_sync_watch, {
7530
+ runSyncWatch: () => runSyncWatch,
7531
+ pushTick: () => pushTick,
7532
+ pollTick: () => pollTick,
7533
+ isFatalSyncError: () => isFatalSyncError,
7534
+ formatTick: () => formatTick,
7535
+ captureTick: () => captureTick
7536
+ });
7537
+ function isFatalSyncError(e) {
7538
+ const m = (e instanceof Error ? e.message : String(e)).toLowerCase();
7539
+ if (/-> 401|-> 403|unauthor|forbidden|session expired|invalid token/.test(m))
7540
+ return true;
7541
+ if (/corrupt|broken chain|chain mismatch|not a sol repo|tamper/.test(m))
7542
+ return true;
7543
+ return false;
7544
+ }
7545
+ async function captureTick(env) {
7546
+ if (env.dryRun)
7547
+ return { changed: 0 };
7548
+ const changed = await env.local.capture();
7549
+ return { changed };
7550
+ }
7551
+ async function pushTick(env) {
7552
+ const { local, remote } = env;
7553
+ const localHead = await local.head();
7554
+ if (!localHead)
7555
+ return { pushed: 0, merged: false, conflicts: [] };
7556
+ const rh = await remote.head();
7557
+ const ops = await local.history();
7558
+ const localSeq = ops.length ? ops[ops.length - 1].seq : 0;
7559
+ const localTip = await local.logTip();
7560
+ if (localSeq === rh.seq && localTip === rh.tip)
7561
+ return { pushed: 0, merged: false, conflicts: [], head: localHead };
7562
+ const branch = local.branch();
7563
+ const forkBase = local.forkBase();
7564
+ const baseOp = forkBase ? ops.find((o) => o.rootAfter === forkBase) : undefined;
7565
+ const fromSeq = baseOp ? baseOp.seq : rh.seq;
7566
+ const delta = ops.filter((o) => o.seq > fromSeq);
7567
+ if (env.dryRun) {
7568
+ env.log.info(`[dry-run] would push ${delta.length} op(s) (branch ${branch} @ ${short2(localHead)})`);
7569
+ return { pushed: 0, merged: false, conflicts: [], head: localHead };
7570
+ }
7571
+ const res = await remote.push({ nodes: await local.nodes(), ops: delta, branch, head: localHead, expectedHead: forkBase });
7572
+ const canon = await remote.export();
7573
+ const convergedHead = res.head ?? canon.branches?.[branch] ?? localHead;
7574
+ await local.absorbCanonical({ head: convergedHead, ops: canon.ops, nodes: canon.nodes, branches: canon.branches }, localHead);
7575
+ return {
7576
+ pushed: res.applied,
7577
+ merged: !!res.merged,
7578
+ conflicts: (res.conflicts ?? []).map((c) => c.path),
7579
+ head: convergedHead
7580
+ };
7581
+ }
7582
+ async function pollTick(env) {
7583
+ const { local, remote } = env;
7584
+ const rh = await remote.head();
7585
+ const localTip = await local.logTip();
7586
+ const localSeq = await local.seq();
7587
+ if (rh.seq <= localSeq && rh.tip === localTip)
7588
+ return { pulled: 0, merged: false, conflicts: [], deferred: false };
7589
+ if (await local.dirty()) {
7590
+ env.log.info("remote moved but the working tree is dirty — pull deferred to the next poll (your edits captured first)");
7591
+ return { pulled: 0, merged: false, conflicts: [], deferred: true };
7592
+ }
7593
+ if (env.dryRun) {
7594
+ env.log.info(`[dry-run] would pull remote @ ${short2(rh.head)} (seq ${rh.seq})`);
7595
+ return { pulled: 0, merged: false, conflicts: [], deferred: false };
7596
+ }
7597
+ const bundle = await remote.export();
7598
+ const prevHead = await local.head() ?? "";
7599
+ const before = (await local.history()).length;
7600
+ const res = await converge(local.replica, {
7601
+ nodes: bundle.nodes,
7602
+ ops: bundle.ops,
7603
+ incomingHead: bundle.head,
7604
+ actor: env.actor
7605
+ });
7606
+ const after = (await local.history()).length;
7607
+ if (res.head !== prevHead)
7608
+ await local.adopt(prevHead, res.head, bundle.branches);
7609
+ return {
7610
+ pulled: after - before,
7611
+ merged: res.merged,
7612
+ conflicts: res.conflicts.map((c) => c.path),
7613
+ deferred: false,
7614
+ head: res.head
7615
+ };
7616
+ }
7617
+ function formatTick(parts) {
7618
+ const segs = [];
7619
+ if (parts.captured)
7620
+ segs.push(`auto-captured ${parts.captured} change${parts.captured === 1 ? "" : "s"}`);
7621
+ if (parts.push && parts.push.pushed) {
7622
+ const note = parts.push.merged ? " (converged)" : "";
7623
+ const conf = parts.push.conflicts.length ? ` [${parts.push.conflicts.length} conflict(s): ${parts.push.conflicts.join(", ")}]` : "";
7624
+ segs.push(`pushed ${parts.push.pushed} op${parts.push.pushed === 1 ? "" : "s"} -> remote${note}${conf}`);
7625
+ }
7626
+ if (parts.poll && parts.poll.pulled) {
7627
+ const note = parts.poll.merged ? " (merged)" : "";
7628
+ const conf = parts.poll.conflicts.length ? ` [${parts.poll.conflicts.length} conflict(s): ${parts.poll.conflicts.join(", ")}]` : "";
7629
+ segs.push(`pulled ${parts.poll.pulled} op${parts.poll.pulled === 1 ? "" : "s"} from remote${note}${conf}`);
7630
+ }
7631
+ if (!segs.length)
7632
+ return;
7633
+ const t = new Date().toLocaleTimeString();
7634
+ return `[${t}] ${segs.join(" | ")}`;
7635
+ }
7636
+ async function runSyncWatch(opts) {
7637
+ const { buildFsSyncEnv: buildFsSyncEnv2 } = await Promise.resolve().then(() => (init_sync_watch_fs(), exports_sync_watch_fs));
7638
+ const env = await buildFsSyncEnv2(opts);
7639
+ const { watch } = await import("node:fs");
7640
+ const { sep: sep2 } = await import("node:path");
7641
+ const { DEFAULT_IGNORE: DEFAULT_IGNORE2 } = await Promise.resolve().then(() => (init_lib(), exports_lib));
7642
+ const pollInterval = opts.pollInterval ?? 5000;
7643
+ const pushImmediate = opts.pushImmediate ?? true;
7644
+ let exiting = false;
7645
+ const fatal = (where, e) => {
7646
+ if (exiting)
7647
+ return;
7648
+ exiting = true;
7649
+ env.log.warn(`FATAL (${where}): ${e instanceof Error ? e.message : e} — exiting. fix it and re-run \`sol sync --watch\`.`);
7650
+ process.exit(1);
7651
+ };
7652
+ let running = false;
7653
+ let pending = false;
7654
+ const captureAndPush = async () => {
7655
+ if (running) {
7656
+ pending = true;
7657
+ return;
7658
+ }
7659
+ running = true;
7660
+ try {
7661
+ do {
7662
+ pending = false;
7663
+ try {
7664
+ const cap = await env.runLocked(async () => {
7665
+ const c = await captureTick(env);
7666
+ let push;
7667
+ if (c.changed && pushImmediate) {
7668
+ try {
7669
+ push = await pushTick(env);
7670
+ } catch (e) {
7671
+ if (isFatalSyncError(e))
7672
+ return fatal("push", e);
7673
+ env.log.warn(`push failed (will retry next cycle): ${e instanceof Error ? e.message : e}`);
7674
+ }
7675
+ }
7676
+ return { captured: c.changed, push };
7677
+ });
7678
+ const line = formatTick(cap);
7679
+ if (line)
7680
+ env.log.info(line);
7681
+ } catch (e) {
7682
+ if (isFatalSyncError(e))
7683
+ return fatal("capture", e);
7684
+ env.log.warn(`sync tick error (retrying): ${e?.message || e}`);
7685
+ }
7686
+ } while (pending);
7687
+ } finally {
7688
+ running = false;
7689
+ }
7690
+ };
7691
+ const pollOnce = async () => {
7692
+ try {
7693
+ const res = await env.runLocked(() => pollTick(env));
7694
+ const line = formatTick({ poll: res });
7695
+ if (line)
7696
+ env.log.info(line);
7697
+ } catch (e) {
7698
+ if (isFatalSyncError(e))
7699
+ return fatal("poll", e);
7700
+ env.log.warn(`poll failed (will retry in ${Math.round(pollInterval / 1000)}s): ${e?.message || e}`);
7701
+ }
7702
+ };
7703
+ await captureAndPush();
7704
+ env.log.info(`sol sync --watch on ${opts.repoDir}
7705
+ every edit is auto-captured + pushed${pushImmediate ? "" : " (push disabled — capture only)"}; safe remote updates pull in every ${Math.round(pollInterval / 1000)}s.
7706
+ ` + ` nothing is lost — all edits are durable locally first. Ctrl-C to stop.${opts.dryRun ? `
7707
+ [DRY RUN — logging intent, mutating nothing]` : ""}`);
7708
+ let timer;
7709
+ watch(opts.repoDir, { recursive: true }, (_e, filename) => {
7710
+ if (!filename)
7711
+ return;
7712
+ const top = String(filename).split(sep2)[0];
7713
+ if (top === ".sol" || DEFAULT_IGNORE2.includes(top))
7714
+ return;
7715
+ clearTimeout(timer);
7716
+ timer = setTimeout(() => void captureAndPush(), 400);
7717
+ });
7718
+ const poller = setInterval(() => void pollOnce(), pollInterval);
7719
+ poller.unref?.();
7720
+ await new Promise(() => {});
7721
+ }
7722
+ var short2 = (h) => (h ?? "").slice(0, 12);
7723
+ var init_sync_watch = __esm(() => {
7724
+ init_converge();
7725
+ });
7726
+
6693
7727
  // src/mr.ts
6694
7728
  var exports_mr = {};
6695
7729
  __export(exports_mr, {
6696
7730
  mrSummary: () => mrSummary,
6697
7731
  mergeGate: () => mergeGate,
6698
- latestVerdicts: () => latestVerdicts
7732
+ latestVerdicts: () => latestVerdicts,
7733
+ isAdvisoryGate: () => isAdvisoryGate
6699
7734
  });
6700
7735
  function latestVerdicts(mr) {
6701
7736
  const m = new Map;
@@ -6703,18 +7738,23 @@ function latestVerdicts(mr) {
6703
7738
  m.set(r.actor, r.verdict);
6704
7739
  return m;
6705
7740
  }
6706
- function mergeGate(mr) {
7741
+ function mergeGate(mr, branchGate) {
6707
7742
  const reasons = [];
6708
7743
  if (mr.status !== "open")
6709
7744
  reasons.push(`MR is ${mr.status}`);
6710
7745
  for (const c of mr.checks)
6711
7746
  if (c.status === "fail")
6712
7747
  reasons.push(`check '${c.name}' failed`);
7748
+ if (branchGate?.verdict === "fail")
7749
+ reasons.push(`branch head gate failed${branchGate.summary ? `: ${branchGate.summary}` : ""}`);
6713
7750
  for (const [actor2, v] of latestVerdicts(mr))
6714
7751
  if (v === "request-changes")
6715
7752
  reasons.push(`${actor2} requested changes`);
6716
7753
  return reasons;
6717
7754
  }
7755
+ function isAdvisoryGate(reason) {
7756
+ return reason.startsWith("check '") || reason.startsWith("branch head gate");
7757
+ }
6718
7758
  function mrSummary(mr) {
6719
7759
  const approvals = [...latestVerdicts(mr).values()].filter((v) => v === "approve").length;
6720
7760
  const checks = mr.checks.length ? `${mr.checks.filter((c) => c.status === "pass").length}/${mr.checks.length} checks pass` : "no checks";
@@ -6729,7 +7769,7 @@ __export(exports_runtime, {
6729
7769
  hydrate: () => hydrate2,
6730
7770
  capture: () => capture
6731
7771
  });
6732
- import { chmodSync as chmodSync4, existsSync as existsSync14, lstatSync as lstatSync2, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync14, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync13 } from "node:fs";
7772
+ import { chmodSync as chmodSync4, existsSync as existsSync15, lstatSync as lstatSync2, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync15, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync13 } from "node:fs";
6733
7773
  import { platform } from "node:os";
6734
7774
  import { dirname as dirname3, join as join14, relative as relative2 } from "node:path";
6735
7775
  function isolateCommand(command, scratch) {
@@ -6793,7 +7833,7 @@ async function capture(repo, dir, keep = []) {
6793
7833
  const pats = ignorePatterns();
6794
7834
  const files = new Set(walkDir(dir, dir, pats));
6795
7835
  for (const k of keep)
6796
- if (existsSync14(join14(dir, k)))
7836
+ if (existsSync15(join14(dir, k)))
6797
7837
  files.add(k);
6798
7838
  const written = [];
6799
7839
  for (const f of files) {
@@ -6808,7 +7848,7 @@ async function capture(repo, dir, keep = []) {
6808
7848
  }
6809
7849
  continue;
6810
7850
  }
6811
- const buf = readFileSync14(abs);
7851
+ const buf = readFileSync15(abs);
6812
7852
  if (buf.includes(0)) {
6813
7853
  const cur = await repo.readBytes(f);
6814
7854
  if (cur === undefined || cur === SEALED || !Buffer.from(cur).equals(buf)) {
@@ -6825,7 +7865,7 @@ async function capture(repo, dir, keep = []) {
6825
7865
  }
6826
7866
  const deleted = [];
6827
7867
  for (const t of await repo.list()) {
6828
- if (!existsSync14(join14(dir, t))) {
7868
+ if (!existsSync15(join14(dir, t))) {
6829
7869
  await repo.deleteFile(t);
6830
7870
  deleted.push(t);
6831
7871
  }
@@ -6939,7 +7979,7 @@ __export(exports_git_adapter, {
6939
7979
  exportHistoryToGit: () => exportHistoryToGit
6940
7980
  });
6941
7981
  import { execFileSync } from "node:child_process";
6942
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync15, unlinkSync as unlinkSync6, writeFileSync as writeFileSync14 } from "node:fs";
7982
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync16, unlinkSync as unlinkSync6, writeFileSync as writeFileSync14 } from "node:fs";
6943
7983
  import { join as join15 } from "node:path";
6944
7984
  import { deflateSync } from "node:zlib";
6945
7985
  async function importGitHead(gitPath, ws) {
@@ -7009,7 +8049,7 @@ async function applyGitFile(repo, path, mode, content) {
7009
8049
  }
7010
8050
  function setHead(fdir, head) {
7011
8051
  const hf = join15(fdir, "HEAD");
7012
- const cur = existsSync15(hf) ? JSON.parse(readFileSync15(hf, "utf8")) : { seq: 0 };
8052
+ const cur = existsSync16(hf) ? JSON.parse(readFileSync16(hf, "utf8")) : { seq: 0 };
7013
8053
  writeFileSync14(hf, JSON.stringify({ ...cur, head }));
7014
8054
  }
7015
8055
  async function importGitRepo(gitPath, fdir) {
@@ -7076,14 +8116,14 @@ function exportToGit(store2, head, gitPath, message) {
7076
8116
  }
7077
8117
  function gitObjectsDir(gitPath) {
7078
8118
  for (const c of [join15(gitPath, ".git", "objects"), join15(gitPath, "objects")])
7079
- if (existsSync15(c))
8119
+ if (existsSync16(c))
7080
8120
  return c;
7081
8121
  return;
7082
8122
  }
7083
8123
  function writeLooseObject(objectsDir2, o) {
7084
8124
  const dir = join15(objectsDir2, o.oid.slice(0, 2));
7085
8125
  const file = join15(dir, o.oid.slice(2));
7086
- if (existsSync15(file))
8126
+ if (existsSync16(file))
7087
8127
  return;
7088
8128
  const header = Buffer.from(`${o.type} ${o.content.length}\x00`);
7089
8129
  const framed = Buffer.concat([header, Buffer.from(o.content)]);
@@ -7200,14 +8240,14 @@ __export(exports_views, {
7200
8240
  loadViewsRegistry: () => loadViewsRegistry,
7201
8241
  createView: () => createView
7202
8242
  });
7203
- import { existsSync as existsSync16, mkdirSync as mkdirSync10, readdirSync as readdirSync8, readFileSync as readFileSync16, rmSync, writeFileSync as writeFileSync15 } from "node:fs";
8243
+ import { existsSync as existsSync17, mkdirSync as mkdirSync10, readdirSync as readdirSync8, readFileSync as readFileSync17, rmSync, writeFileSync as writeFileSync15 } from "node:fs";
7204
8244
  import { join as join16, relative as relative3, resolve as resolve3 } from "node:path";
7205
8245
  function loadViewsRegistry(parentSol) {
7206
8246
  const p = viewsRegistryPath(parentSol);
7207
- if (!existsSync16(p))
8247
+ if (!existsSync17(p))
7208
8248
  return { views: [] };
7209
8249
  try {
7210
- return JSON.parse(readFileSync16(p, "utf8"));
8250
+ return JSON.parse(readFileSync17(p, "utf8"));
7211
8251
  } catch {
7212
8252
  return { views: [] };
7213
8253
  }
@@ -7240,7 +8280,7 @@ async function viewStatuses(parentSol) {
7240
8280
  const out = [];
7241
8281
  for (const v of reg.views) {
7242
8282
  const viewSol = join16(v.dir, ".sol");
7243
- const exists = existsSync16(join16(viewSol, "view.json"));
8283
+ const exists = existsSync17(join16(viewSol, "view.json"));
7244
8284
  let head;
7245
8285
  if (exists) {
7246
8286
  head = await new FileOpLog2(viewSol).head();
@@ -7264,7 +8304,7 @@ function pruneViews(parentSol, opts = {}) {
7264
8304
  const reg = loadViewsRegistry(parentSol);
7265
8305
  const removed = [];
7266
8306
  reg.views = reg.views.filter((v) => {
7267
- const present = existsSync16(join16(v.dir, ".sol", "view.json"));
8307
+ const present = existsSync17(join16(v.dir, ".sol", "view.json"));
7268
8308
  const target = opts.name ? v.name === opts.name : !present;
7269
8309
  if (!target)
7270
8310
  return true;
@@ -7281,7 +8321,7 @@ function pruneViews(parentSol, opts = {}) {
7281
8321
  }
7282
8322
  function sharedObjectCount(parentSol) {
7283
8323
  const dir = join16(parentSol, "objects");
7284
- if (!existsSync16(dir))
8324
+ if (!existsSync17(dir))
7285
8325
  return 0;
7286
8326
  return readdirSync8(dir).filter((n) => !n.endsWith(".tmp")).length;
7287
8327
  }
@@ -7293,6 +8333,169 @@ var init_views = __esm(() => {
7293
8333
  init_lib();
7294
8334
  });
7295
8335
 
8336
+ // src/bin/agent-sessions.ts
8337
+ var exports_agent_sessions = {};
8338
+ __export(exports_agent_sessions, {
8339
+ stopSession: () => stopSession,
8340
+ sessions: () => sessions,
8341
+ sessionPath: () => sessionPath,
8342
+ sessionDir: () => sessionDir,
8343
+ saveSessionsRegistry: () => saveSessionsRegistry,
8344
+ pruneSession: () => pruneSession,
8345
+ loadSessionsRegistry: () => loadSessionsRegistry,
8346
+ fingerprintOfSeed: () => fingerprintOfSeed,
8347
+ createSession: () => createSession
8348
+ });
8349
+ import { chmodSync as chmodSync5, existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync18, rmSync as rmSync2, writeFileSync as writeFileSync16 } from "node:fs";
8350
+ import { randomBytes as randomBytes5 } from "node:crypto";
8351
+ import { basename, dirname as dirname4, join as join17, resolve as resolve4 } from "node:path";
8352
+ function sessionPath(parentSol) {
8353
+ return join17(parentSol, "sessions.json");
8354
+ }
8355
+ function sessionDir(parentSol, sessionId) {
8356
+ return join17(parentSol, "sessions", sessionId);
8357
+ }
8358
+ function loadSessionsRegistry(parentSol) {
8359
+ const p = sessionPath(parentSol);
8360
+ if (!existsSync18(p))
8361
+ return { sessions: [] };
8362
+ try {
8363
+ return JSON.parse(readFileSync18(p, "utf8"));
8364
+ } catch {
8365
+ return { sessions: [] };
8366
+ }
8367
+ }
8368
+ function saveSessionsRegistry(parentSol, reg) {
8369
+ writeFileSync16(sessionPath(parentSol), JSON.stringify(reg, null, 2));
8370
+ }
8371
+ function newSessionId(name) {
8372
+ const slug = name.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 24) || "agent";
8373
+ return `${slug}-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`;
8374
+ }
8375
+ function createSession(opts) {
8376
+ const { parentSol, name, actor: actor2, check, metadata } = opts;
8377
+ if (!existsSync18(parentSol))
8378
+ throw new Error(`not a sol repo: ${parentSol}`);
8379
+ const sessionId = opts.sessionId ?? newSessionId(name);
8380
+ const sdir = sessionDir(parentSol, sessionId);
8381
+ if (existsSync18(sdir)) {
8382
+ if (opts.onExistingKey)
8383
+ opts.onExistingKey(sessionId);
8384
+ else
8385
+ throw new Error(`session already exists: ${sessionId} (${sdir})`);
8386
+ }
8387
+ const repoRoot2 = dirname4(parentSol);
8388
+ const viewDir = opts.dir ? resolve4(opts.dir) : join17(dirname4(repoRoot2), `${basename(repoRoot2)}-${name}`);
8389
+ if (existsSync18(join17(viewDir, ".sol")))
8390
+ throw new Error(`already a sol repo/view: ${viewDir}`);
8391
+ const branch = `view/${name}`;
8392
+ const startHead = headOf(parentSol);
8393
+ createView({ parentSol, viewDir, name, branch, actor: actor2, startHead });
8394
+ const kp = generateKeypair();
8395
+ mkdirSync11(sdir, { recursive: true });
8396
+ const signingKeyPath = join17(sdir, "signing.key");
8397
+ writeFileSync16(signingKeyPath, kp.seed, { mode: 384 });
8398
+ try {
8399
+ chmodSync5(signingKeyPath, 384);
8400
+ } catch {}
8401
+ if (check) {
8402
+ const cfg = loadSolConfig(parentSol);
8403
+ cfg.check = check;
8404
+ saveSolConfig(parentSol, cfg);
8405
+ }
8406
+ const record = {
8407
+ sessionId,
8408
+ name,
8409
+ viewDir: resolve4(viewDir),
8410
+ branch,
8411
+ actor: actor2,
8412
+ signingKeyPath: resolve4(signingKeyPath),
8413
+ fingerprint: kp.fingerprint,
8414
+ startHead,
8415
+ createdAt: Date.now(),
8416
+ ...metadata ? { metadata } : {}
8417
+ };
8418
+ const reg = loadSessionsRegistry(parentSol);
8419
+ reg.sessions = reg.sessions.filter((s) => s.sessionId !== sessionId);
8420
+ reg.sessions.push(record);
8421
+ saveSessionsRegistry(parentSol, reg);
8422
+ return record;
8423
+ }
8424
+ function headOf(parentSol) {
8425
+ const headFile = join17(parentSol, "HEAD");
8426
+ if (existsSync18(headFile)) {
8427
+ try {
8428
+ const h = JSON.parse(readFileSync18(headFile, "utf8"));
8429
+ if (h.head)
8430
+ return h.head;
8431
+ } catch {}
8432
+ }
8433
+ return emptyRoot2(new LazyStore(join17(parentSol, "objects")));
8434
+ }
8435
+ function matches(s, ref) {
8436
+ return s.sessionId === ref || s.name === ref;
8437
+ }
8438
+ function stopSession(parentSol, ref) {
8439
+ const reg = loadSessionsRegistry(parentSol);
8440
+ const s = reg.sessions.find((x) => x.sessionId === ref) ?? reg.sessions.find((x) => matches(x, ref));
8441
+ if (!s)
8442
+ return false;
8443
+ if (!s.stopAt) {
8444
+ s.stopAt = Date.now();
8445
+ saveSessionsRegistry(parentSol, reg);
8446
+ }
8447
+ return true;
8448
+ }
8449
+ async function sessions(parentSol) {
8450
+ const reg = loadSessionsRegistry(parentSol);
8451
+ const out = [];
8452
+ for (const s of reg.sessions) {
8453
+ const viewSol = join17(s.viewDir, ".sol");
8454
+ const exists = existsSync18(join17(viewSol, "view.json"));
8455
+ let head;
8456
+ if (exists)
8457
+ head = await new FileOpLog2(viewSol).head();
8458
+ const state = !exists ? "stale" : s.stopAt ? "stopped" : "running";
8459
+ out.push({ ...s, head, exists, state });
8460
+ }
8461
+ return out;
8462
+ }
8463
+ function pruneSession(parentSol, ref, deleteDir = false) {
8464
+ const reg = loadSessionsRegistry(parentSol);
8465
+ const removed = [];
8466
+ const drop = [];
8467
+ reg.sessions = reg.sessions.filter((s) => {
8468
+ const present = existsSync18(join17(s.viewDir, ".sol", "view.json"));
8469
+ const target = ref ? matches(s, ref) : !present;
8470
+ if (!target)
8471
+ return true;
8472
+ drop.push(s);
8473
+ removed.push(s.sessionId);
8474
+ return false;
8475
+ });
8476
+ for (const s of drop) {
8477
+ try {
8478
+ pruneViews(parentSol, { name: s.name, deleteDir });
8479
+ } catch {}
8480
+ try {
8481
+ rmSync2(sessionDir(parentSol, s.sessionId), { recursive: true, force: true });
8482
+ } catch {}
8483
+ }
8484
+ saveSessionsRegistry(parentSol, reg);
8485
+ return removed;
8486
+ }
8487
+ function fingerprintOfSeed(seed) {
8488
+ return fingerprintOf(publicOf(loadPrivateKey(seed)));
8489
+ }
8490
+ var init_agent_sessions = __esm(() => {
8491
+ init_file_store2();
8492
+ init_sign();
8493
+ init_tree();
8494
+ init_lib();
8495
+ init_test_gate();
8496
+ init_views();
8497
+ });
8498
+
7296
8499
  // src/bin/secret.ts
7297
8500
  var exports_secret2 = {};
7298
8501
  __export(exports_secret2, {
@@ -7300,7 +8503,7 @@ __export(exports_secret2, {
7300
8503
  runEnv: () => runEnv,
7301
8504
  resolveReference: () => resolveReference
7302
8505
  });
7303
- import { readFileSync as readFileSync17, readSync, writeSync } from "node:fs";
8506
+ import { readFileSync as readFileSync19, readSync, writeSync } from "node:fs";
7304
8507
  import { spawnSync as spawnSync2 } from "node:child_process";
7305
8508
  function flag(args, name) {
7306
8509
  const i = args.indexOf(name);
@@ -7672,7 +8875,7 @@ async function envValidate(ctx, args, json) {
7672
8875
  }
7673
8876
  function readSchemaText(solDir2) {
7674
8877
  try {
7675
- return readFileSync17(schemaLockPath(solDir2), "utf8");
8878
+ return readFileSync19(schemaLockPath(solDir2), "utf8");
7676
8879
  } catch {
7677
8880
  return;
7678
8881
  }
@@ -8354,8 +9557,8 @@ var exports_sol_secret_mcp = {};
8354
9557
  __export(exports_sol_secret_mcp, {
8355
9558
  startSecretMcp: () => startSecretMcp
8356
9559
  });
8357
- import { existsSync as existsSync17 } from "node:fs";
8358
- import { join as join17 } from "node:path";
9560
+ import { existsSync as existsSync19 } from "node:fs";
9561
+ import { join as join18 } from "node:path";
8359
9562
  async function buildSecretServer(solDir2) {
8360
9563
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
8361
9564
  const { CallToolRequestSchema, ListToolsRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
@@ -8391,8 +9594,8 @@ async function buildSecretServer(solDir2) {
8391
9594
  return server;
8392
9595
  }
8393
9596
  async function startSecretMcp(opts = {}) {
8394
- const solDir2 = opts.solDir || process.env.SOL_DIR || join17(process.cwd(), ".sol");
8395
- if (!existsSync17(solDir2)) {
9597
+ const solDir2 = opts.solDir || process.env.SOL_DIR || join18(process.cwd(), ".sol");
9598
+ if (!existsSync19(solDir2)) {
8396
9599
  process.stderr.write(`sol-secret-mcp: no .sol at ${solDir2} — run \`sol init\` first (or set SOL_DIR)
8397
9600
  `);
8398
9601
  process.exit(1);
@@ -8420,9 +9623,9 @@ __export(exports_dispatch, {
8420
9623
  dispatch: () => dispatch
8421
9624
  });
8422
9625
  import { execFileSync as execFileSync2 } from "node:child_process";
8423
- import { existsSync as existsSync18, mkdirSync as mkdirSync11, mkdtempSync, readdirSync as readdirSync9, readFileSync as readFileSync18, rmSync as rmSync2, unlinkSync as unlinkSync7, watch, writeFileSync as writeFileSync16 } from "node:fs";
9626
+ import { accessSync, constants as fsConstants, existsSync as existsSync20, mkdirSync as mkdirSync12, mkdtempSync, readdirSync as readdirSync9, readFileSync as readFileSync20, rmSync as rmSync3, unlinkSync as unlinkSync7, watch, writeFileSync as writeFileSync17 } from "node:fs";
8424
9627
  import { homedir as homedir2, hostname, platform as platform2, tmpdir } from "node:os";
8425
- import { basename, dirname as dirname4, join as join18, resolve as resolve4, sep as sep2 } from "node:path";
9628
+ import { basename as basename2, dirname as dirname5, join as join19, resolve as resolve5, sep as sep2 } from "node:path";
8426
9629
  function globCovers(pattern, path) {
8427
9630
  let re = "";
8428
9631
  for (let i = 0;i < pattern.length; i++) {
@@ -8442,6 +9645,14 @@ function globCovers(pattern, path) {
8442
9645
  }
8443
9646
  return new RegExp(`^${re}$`).test(path);
8444
9647
  }
9648
+ function writableTag(path) {
9649
+ try {
9650
+ accessSync(path, fsConstants.R_OK | fsConstants.W_OK);
9651
+ return "RW";
9652
+ } catch {
9653
+ return "READ-ONLY";
9654
+ }
9655
+ }
8445
9656
  function tokenClaims(token) {
8446
9657
  try {
8447
9658
  return JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString());
@@ -8450,11 +9661,11 @@ function tokenClaims(token) {
8450
9661
  }
8451
9662
  }
8452
9663
  async function loadStoredToken() {
8453
- if (!existsSync18(CRED_PATH))
9664
+ if (!existsSync20(CRED_PATH))
8454
9665
  return;
8455
9666
  let creds;
8456
9667
  try {
8457
- creds = JSON.parse(readFileSync18(CRED_PATH, "utf8"));
9668
+ creds = JSON.parse(readFileSync20(CRED_PATH, "utf8"));
8458
9669
  } catch {
8459
9670
  return;
8460
9671
  }
@@ -8469,7 +9680,7 @@ async function loadStoredToken() {
8469
9680
  if (res.ok) {
8470
9681
  const r = await res.json();
8471
9682
  if (r.accessToken) {
8472
- writeFileSync16(CRED_PATH, JSON.stringify({ ...creds, accessToken: r.accessToken, refreshToken: r.refreshToken ?? creds.refreshToken }, null, 2), { mode: 384 });
9683
+ writeFileSync17(CRED_PATH, JSON.stringify({ ...creds, accessToken: r.accessToken, refreshToken: r.refreshToken ?? creds.refreshToken }, null, 2), { mode: 384 });
8473
9684
  return r.accessToken;
8474
9685
  }
8475
9686
  }
@@ -8490,7 +9701,7 @@ function authHost() {
8490
9701
  if (process.env.SOL_AUTH)
8491
9702
  return process.env.SOL_AUTH.replace(/\/+$/, "");
8492
9703
  try {
8493
- const w = JSON.parse(readFileSync18(CRED_PATH, "utf8")).webUrl;
9704
+ const w = JSON.parse(readFileSync20(CRED_PATH, "utf8")).webUrl;
8494
9705
  if (w)
8495
9706
  return w.replace(/\/+$/, "");
8496
9707
  } catch {}
@@ -8514,11 +9725,11 @@ async function resolveMcpHttp(a, label) {
8514
9725
  die(`invalid --port: ${portRaw}`);
8515
9726
  return { token, port, host: flagVal("--host"), label: label ?? (a.includes("--secret") || a.includes("--secrets") ? "sol-secrets" : "sol") };
8516
9727
  }
8517
- function resolveRemote(solDir2) {
9728
+ function resolveRemote2(solDir2) {
8518
9729
  const cfg = loadRemote(solDir2);
8519
9730
  if (!cfg)
8520
9731
  return;
8521
- return cfg.url ? cfg : { ...cfg, url: DEFAULT_REMOTE_URL };
9732
+ return cfg.url ? cfg : { ...cfg, url: DEFAULT_REMOTE_URL2 };
8522
9733
  }
8523
9734
  async function pushEnvState(solDirPath, cfg, token) {
8524
9735
  const { readEnvStateBundle: readEnvStateBundle2 } = await Promise.resolve().then(() => (init_anchor(), exports_anchor));
@@ -8537,6 +9748,17 @@ ENV ANCHOR REJECTED by the remote: ${e?.message ?? String(e)}
8537
9748
  process.exitCode = 1;
8538
9749
  }
8539
9750
  }
9751
+ async function surfaceOwner(cfg, token) {
9752
+ const slash = cfg.repo.indexOf("/");
9753
+ if (slash < 0)
9754
+ return;
9755
+ const owner = cfg.repo.slice(0, slash);
9756
+ try {
9757
+ const res = await ownerSet(cfg, token, owner);
9758
+ if (res.error)
9759
+ console.error(`note: could not set repo owner to '${owner}': ${res.error}`);
9760
+ } catch {}
9761
+ }
8540
9762
  async function pullEnvState(solDirPath, cfg, token) {
8541
9763
  try {
8542
9764
  const { bundle } = await remoteEnvPull(cfg, token);
@@ -8569,7 +9791,7 @@ function cliVersion() {
8569
9791
  if (typeof __SOL_COMPILED_VERSION__ === "string" && __SOL_COMPILED_VERSION__)
8570
9792
  return __SOL_COMPILED_VERSION__;
8571
9793
  try {
8572
- return JSON.parse(readFileSync18(new URL("./package.json", import.meta.url), "utf8")).version || "dev";
9794
+ return JSON.parse(readFileSync20(new URL("./package.json", import.meta.url), "utf8")).version || "dev";
8573
9795
  } catch {
8574
9796
  return "dev";
8575
9797
  }
@@ -8616,7 +9838,7 @@ examples:
8616
9838
  sol commit "add login route"
8617
9839
  sol commit -m "fix parser" src/parse.ts src/lex.ts`,
8618
9840
  push: `sol push push the current branch to the configured remote (converging — never FF-rejected)
8619
- sol push <repo> if no remote is set, use the hosted Sol (${DEFAULT_REMOTE_URL}) + this repo name, then push
9841
+ sol push <repo> if no remote is set, use the hosted Sol (${DEFAULT_REMOTE_URL2}) + this repo name, then push
8620
9842
  sol push --create <repo> same, explicit form (creates the repo on first push)
8621
9843
  sol push --public <repo> create + push + make it public in ONE step (else new repos are private)
8622
9844
 
@@ -8639,6 +9861,21 @@ examples:
8639
9861
  notes:
8640
9862
  refuses to run over a dirty tree — commit or discard first. conflicts land in the working tree with markers.
8641
9863
  refuses to run while UNRESOLVED <<<<<<< markers remain — resolve them and \`sol commit\`, then pull again.`,
9864
+ sync: `sol sync --watch HANDS-OFF bidirectional sync daemon — auto-capture + push every edit,
9865
+ and converge safe remote updates in on a timer. no CLI invocations needed:
9866
+ the working tree stays in sync while every local edit is durably captured.
9867
+ --poll-interval <ms> how often to poll the remote for updates (default 5000)
9868
+ --no-push-immediately capture locally but do NOT auto-push (push later with \`sol push\`)
9869
+ --dry-run log what it WOULD do, mutate nothing
9870
+
9871
+ how it works (three loops over the same core as watch/push/pull):
9872
+ 1. every file change is auto-captured into the op-log (nothing lost, even offline)
9873
+ 2. after each capture it pushes the delta; the server may converge a concurrent commit in
9874
+ 3. it polls the remote; a moved head pulls in via converge() — NEVER clobbering an in-flight edit
9875
+ notes:
9876
+ needs a remote (\`sol remote <url> <repo>\` or \`sol push <repo>\` once) + auth (\`sol auth login\` / SOL_TOKEN).
9877
+ a dirty tree DEFERS a pull to the next poll (your edits are captured first). Ctrl-C exits cleanly.
9878
+ transient network errors are logged + retried; a fatal error (corrupt repo / auth) exits loud.`,
8642
9879
  hide: `sol hide <pattern> [--role write|admin] [--team <id>] [--users a,b] [--escrow] [--no-list] [--hide-names] [--hide-existence] [--strict]
8643
9880
  add/update a VisibilityPolicy RULE — the HEADLINE verb. binds a glob over tree
8644
9881
  paths to an AUDIENCE (who may DECRYPT). the rule is host-visible METADATA (no
@@ -8752,6 +9989,9 @@ examples:
8752
9989
  sol keys verify alice 1a2b-3c4d-... # confirm alice's key out-of-band before sealing to her`,
8753
9990
  remote: `sol remote show the configured remote
8754
9991
  sol remote <url> <repo> set the remote (url + repo name)
9992
+ sol remote verify REMOTE-side health check (the backend's /fsck) — differentiates auth
9993
+ (session expired / no access), backend (5xx, transient), connectivity,
9994
+ and a 200-but-corrupt remote repo. needs SOL_TOKEN (or \`sol auth login\`).
8755
9995
 
8756
9996
  tip: \`sol push <repo>\` configures the hosted remote for you in one step.`,
8757
9997
  branch: `sol branch list branches (* = current)
@@ -8816,6 +10056,27 @@ what it's for (TEST-GATED CONVERGENCE):
8816
10056
  hydrate the current tree to a sandbox, run the command, capture produced files back into a commit.
8817
10057
  --isolate confines the run (no network, writes stay in the sandbox).`,
8818
10058
  promote: "sol promote [branch] point the remote's production branch at <branch> (default: current branch)",
10059
+ doctor: `sol doctor — comprehensive LOCAL health check (exit 0 = OK, 1 = problems found)
10060
+
10061
+ checks: op-log chain integrity + contiguity, object reachability from head, auth (SOL_TOKEN / stored
10062
+ credentials + expiry), .sol file permissions, and the sealed keyring. read-only — it diagnoses but never
10063
+ changes anything. to RECOVER from what it finds: \`sol fsck --repair\` (op-log/head) or \`sol recover\` (cloud/backup).`,
10064
+ fsck: `sol fsck verify the op-log chain + object reachability (exit 0 = OK, 1 = problems)
10065
+ sol fsck --repair recover a corrupt repo:
10066
+ - head pointer lost -> re-point to the last resolvable historical root
10067
+ - head dangling -> re-point to the newest root the store still has
10068
+ - op-log chain broken -> truncate to the last verified link, re-point head
10069
+
10070
+ every repair appends an explicit CHECKPOINT recording what happened (visible in \`sol log\`/\`sol reflog\`),
10071
+ so the recovery is itself in the tamper-evident history. idempotent: re-running on a healthy repo is a no-op.`,
10072
+ recover: `sol recover the recovery guide (the menu below)
10073
+ sol recover --from-remote <url> <repo> rehydrate from the cloud
10074
+ sol recover --list-all-roots every root the repo ever landed on (find a lost head)
10075
+ sol recover --file <path> [--from <seq|hash>] restore a file from history (even if the tree dropped it)
10076
+ sol recover --dump-log [<from> [<to>]] dump raw op-log entries as JSON (manual salvage)`,
10077
+ reflog: `sol reflog [<branch>] [--by <actor>] [--since <YYYY-MM-DD>]
10078
+ the op-log as a ref-move history (git reflog): each line is a head move (from -> to) with seq, actor, time,
10079
+ and message. a merge shows as "merge". a <branch> arg keeps only checkpoints whose message labels it.`,
8819
10080
  git: `sol git import <repo> [dir] import a git repo's HEAD into a new Sol repo
8820
10081
  sol git export <repo> [-b branch] replay the Sol commit DAG as git history (+ provenance trailers), then \`git push\``,
8821
10082
  mr: `sol mr open [--from <branch>] [--to <branch>] [--upstream <repo>] -t "title" [-m body]
@@ -9105,7 +10366,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9105
10366
  try {
9106
10367
  const { loadSelfIdentity: loadSelfIdentity2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
9107
10368
  const { openContent: openContent2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
9108
- const ring = existsSync18(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
10369
+ const ring = existsSync20(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
9109
10370
  const self = loadSelfIdentity2();
9110
10371
  setSealedDecryptor((boxStr) => {
9111
10372
  const box = JSON.parse(boxStr);
@@ -9115,19 +10376,19 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9115
10376
  } catch {}
9116
10377
  }
9117
10378
  const servesMcp = cmd === "mcp" || cmd === "secret" && args[0] === "mcp";
9118
- const release = !servesMcp && new Set(["add", "track", "commit", "checkpoint", "rm", "gc", "branch", "tag", "switch", "merge", "undo", "revert", "pull", "push", "restore", "checkout", "run", "seal", "view", "env", "secret"]).has(cmd) && existsSync18(solDir) ? acquireLock() : undefined;
10379
+ const release = !servesMcp && new Set(["add", "track", "commit", "checkpoint", "rm", "gc", "branch", "tag", "switch", "merge", "undo", "revert", "pull", "push", "restore", "checkout", "run", "seal", "view", "agent", "env", "secret"]).has(cmd) && existsSync20(solDir) ? acquireLock() : undefined;
9119
10380
  try {
9120
10381
  switch (cmd) {
9121
10382
  case "init": {
9122
- const here = join18(procCwd, ".sol");
9123
- if (existsSync18(here))
10383
+ const here = join19(procCwd, ".sol");
10384
+ if (existsSync20(here))
9124
10385
  die("already a sol repo: " + procCwd);
9125
10386
  if (repoRoot && repoRoot !== procCwd && !args.includes("--force")) {
9126
10387
  die(`already inside a Sol repo at ${repoRoot}
9127
10388
  -> just commit into it: \`sol commit ...\` works from here (sol walks up to find the repo)
9128
10389
  -> to nest a NEW repo here anyway: \`sol init --force\``);
9129
10390
  }
9130
- mkdirSync11(here, { recursive: true });
10391
+ mkdirSync12(here, { recursive: true });
9131
10392
  new FileStore2(here);
9132
10393
  console.log(`initialized empty sol repo in ${here}`);
9133
10394
  break;
@@ -9157,7 +10418,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9157
10418
  const h = tok ? identityFromToken(tok)?.handle : undefined;
9158
10419
  return h || die("no account identity — run `sol auth login` (or set SOL_ACCOUNT for self-host)");
9159
10420
  };
9160
- const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL).replace(/\/+$/, "");
10421
+ const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
9161
10422
  const sub = args[0];
9162
10423
  const wantJson = args.includes("--json");
9163
10424
  if (sub === "init") {
@@ -9243,7 +10504,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9243
10504
  if (sub === "import") {
9244
10505
  const passphrase = process.env.SOL_KEYSTORE_PASSPHRASE || die("set SOL_KEYSTORE_PASSPHRASE to decrypt the bundle");
9245
10506
  const file = args[1] || die("usage: sol keys import <bundle.json> (set SOL_KEYSTORE_PASSPHRASE)");
9246
- const bundle = JSON.parse(readFileSync18(file, "utf8"));
10507
+ const bundle = JSON.parse(readFileSync20(file, "utf8"));
9247
10508
  let recovered;
9248
10509
  try {
9249
10510
  recovered = importBundle2(bundle, passphrase);
@@ -9274,7 +10535,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9274
10535
  break;
9275
10536
  }
9276
10537
  case "trust": {
9277
- if (!existsSync18(solDir))
10538
+ if (!existsSync20(solDir))
9278
10539
  die("not a sol repo — run `sol init` first");
9279
10540
  const map = loadTrust();
9280
10541
  if (args[0] === "--remove" || args[0] === "-r") {
@@ -9321,7 +10582,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9321
10582
  }
9322
10583
  case "track":
9323
10584
  case "add": {
9324
- if (!existsSync18(solDir))
10585
+ if (!existsSync20(solDir))
9325
10586
  die("not a sol repo — run `sol init` first");
9326
10587
  const files = args.filter((a) => a !== "." && !a.startsWith("-"));
9327
10588
  if (!files.length) {
@@ -9332,7 +10593,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9332
10593
  let n = 0;
9333
10594
  for (const f of files) {
9334
10595
  const rf = repoRel(f);
9335
- if (!existsSync18(join18(cwd, rf))) {
10596
+ if (!existsSync20(join19(cwd, rf))) {
9336
10597
  console.error("skip (not on disk): " + f);
9337
10598
  continue;
9338
10599
  }
@@ -9361,14 +10622,14 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9361
10622
  if (!message)
9362
10623
  die('commit needs a message: sol commit "what you did" (scoped: sol commit -m "msg" file1 file2)');
9363
10624
  const parentHead = await repo.head();
9364
- const mergeHeadPath = join18(solDir, "MERGE_HEAD");
9365
- const parent2 = existsSync18(mergeHeadPath) ? readFileSync18(mergeHeadPath, "utf8").trim() || undefined : undefined;
10625
+ const mergeHeadPath = join19(solDir, "MERGE_HEAD");
10626
+ const parent2 = existsSync20(mergeHeadPath) ? readFileSync20(mergeHeadPath, "utf8").trim() || undefined : undefined;
9366
10627
  let changed = 0;
9367
10628
  let commitRoot = parentHead;
9368
10629
  if (paths.length) {
9369
10630
  for (const p of paths) {
9370
10631
  const rp = repoRel(p);
9371
- if (existsSync18(join18(cwd, rp))) {
10632
+ if (existsSync20(join19(cwd, rp))) {
9372
10633
  if (await snapshotFile(repo, rp))
9373
10634
  changed++;
9374
10635
  } else if ((await repo.list()).includes(rp)) {
@@ -9423,7 +10684,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9423
10684
  }
9424
10685
  case "status": {
9425
10686
  const { repo, log } = open();
9426
- const refs = existsSync18(refsPath()) ? await loadRefs(log) : undefined;
10687
+ const refs = existsSync20(refsPath()) ? await loadRefs(log) : undefined;
9427
10688
  const head = await repo.head();
9428
10689
  const headOp = head ? [...await log.history()].reverse().find((o) => o.rootAfter === head) : undefined;
9429
10690
  const headBy = headOp?.by ?? "?";
@@ -9513,7 +10774,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9513
10774
  const { listAll: listAll3 } = await Promise.resolve().then(() => (init_tree(), exports_tree));
9514
10775
  const { loadSelfIdentity: loadSelfIdentity2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
9515
10776
  const { openContent: openContent2, UNREADABLE: UNREADABLE2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
9516
- const ring = existsSync18(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
10777
+ const ring = existsSync20(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
9517
10778
  const self = loadSelfIdentity2();
9518
10779
  const decrypt = (boxStr) => {
9519
10780
  try {
@@ -9561,7 +10822,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9561
10822
  const { readFile: readTree, entryKindAt: kindAt } = await Promise.resolve().then(() => (init_tree(), exports_tree));
9562
10823
  const { loadSelfIdentity: loadSelfIdentity2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
9563
10824
  const { openContent: openContent2, UNREADABLE: UNREADABLE2, KeyRing: KeyRing3 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
9564
- const ring = existsSync18(solDir) ? loadKeyRing() : new KeyRing3;
10825
+ const ring = existsSync20(solDir) ? loadKeyRing() : new KeyRing3;
9565
10826
  const self = loadSelfIdentity2();
9566
10827
  const decrypt = (boxStr) => {
9567
10828
  try {
@@ -9660,7 +10921,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9660
10921
  };
9661
10922
  const live = await log.head() ?? "";
9662
10923
  const refArg = args.find((a) => !a.startsWith("-"));
9663
- const lrefs = existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")) : null;
10924
+ const lrefs = existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")) : null;
9664
10925
  let tipRoot = live;
9665
10926
  if (refArg) {
9666
10927
  tipRoot = lrefs?.branches[refArg]?.head ?? refArg;
@@ -9713,7 +10974,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9713
10974
  const path = args[0] || die("rm needs a path");
9714
10975
  let onDisk = false;
9715
10976
  try {
9716
- unlinkSync7(join18(cwd, path));
10977
+ unlinkSync7(join19(cwd, path));
9717
10978
  onDisk = true;
9718
10979
  } catch {}
9719
10980
  if (onDisk) {
@@ -9911,15 +11172,167 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9911
11172
  };
9912
11173
  walk(head);
9913
11174
  }
9914
- const ok = chainOk && missing.length === 0;
11175
+ const headLost = head === undefined && ops.length > 0;
11176
+ const ok = chainOk && missing.length === 0 && !headLost;
11177
+ if (args.includes("--repair")) {
11178
+ if (ok) {
11179
+ console.log("fsck --repair: nothing to repair — the repo is OK");
11180
+ process.exitCode = 0;
11181
+ break;
11182
+ }
11183
+ const { applyRepair: applyRepair2 } = await Promise.resolve().then(() => (init_admin_repair(), exports_admin_repair));
11184
+ const { FileStore: FileStore3 } = await Promise.resolve().then(() => (init_file_store2(), exports_file_store));
11185
+ const fileStore = new FileStore3(solDir, objectsDir());
11186
+ const plan = await applyRepair2({
11187
+ store: fileStore,
11188
+ log,
11189
+ truncate: async (toSeq) => truncateOpLog(toSeq),
11190
+ setHead: async (h) => setOpLogHead(h)
11191
+ }, "sol-doctor");
11192
+ console.log(`fsck --repair: ${plan.reason}`);
11193
+ if (plan.kind === "noop")
11194
+ console.log(" (already healthy — nothing changed)");
11195
+ else if (plan.kind === "unfixable") {
11196
+ console.log(" status: UNFIXABLE — local repair cannot recover this; run `sol recover --from-remote <url> <repo>` to rehydrate");
11197
+ process.exitCode = 1;
11198
+ break;
11199
+ } else {
11200
+ console.log(` ${plan.note}`);
11201
+ console.log(" status: repaired — run `sol status` (or `sol fsck`) to verify");
11202
+ }
11203
+ process.exitCode = 0;
11204
+ break;
11205
+ }
9915
11206
  console.log(`fsck: ${ok ? "OK" : "PROBLEMS FOUND"}`);
9916
11207
  console.log(` op-log: ${ops.length} op(s), chain ${chainOk ? "valid + contiguous" : "BROKEN — " + chainErr}`);
9917
- console.log(` head: ${head ? head.slice(0, 16) : "(empty)"}, ${missing.length} missing object(s)`);
11208
+ if (headLost)
11209
+ console.log(` head: LOST (${ops.length} op(s) but no head pointer) — run \`sol fsck --repair\``);
11210
+ else
11211
+ console.log(` head: ${head ? head.slice(0, 16) : "(empty)"}, ${missing.length} missing object(s)`);
9918
11212
  for (const mh of missing)
9919
11213
  console.log(" missing " + mh);
11214
+ if (!ok)
11215
+ console.log(" -> `sol doctor` for a full diagnosis, `sol fsck --repair` to recover");
9920
11216
  process.exitCode = ok ? 0 : 1;
9921
11217
  break;
9922
11218
  }
11219
+ case "doctor": {
11220
+ const { runDoctor: runDoctor2, renderDoctorIntegrity: renderDoctorIntegrity2 } = await Promise.resolve().then(() => (init_admin(), exports_admin));
11221
+ const { log } = open();
11222
+ const store2 = loadStore();
11223
+ const asyncStore = { get: async (h) => store2.get(h), put: async () => die("doctor is read-only"), has: async (h) => store2.has(h) };
11224
+ const report = await runDoctor2(asyncStore, log);
11225
+ console.log("sol doctor");
11226
+ for (const l of renderDoctorIntegrity2(report))
11227
+ console.log(l);
11228
+ const envTok = process.env.SOL_TOKEN;
11229
+ if (envTok) {
11230
+ const id = identityFromToken(envTok);
11231
+ console.log(` auth: SOL_TOKEN set${id?.handle ? ` (@${id.handle})` : id?.email ? ` (${id.email})` : ""}`);
11232
+ } else if (existsSync20(CRED_PATH)) {
11233
+ try {
11234
+ const creds = JSON.parse(readFileSync20(CRED_PATH, "utf8"));
11235
+ const c = creds.accessToken ? tokenClaims(creds.accessToken) : {};
11236
+ const exp = typeof c.exp === "number" ? new Date(c.exp * 1000).toISOString().slice(0, 10) : "?";
11237
+ const stale = typeof c.exp === "number" && c.exp * 1000 <= Date.now();
11238
+ console.log(` auth: logged in${c.handle ? ` as @${c.handle}` : ""}${stale ? " (EXPIRED — run `sol auth login`)" : ` (expires ${exp})`}`);
11239
+ } catch {
11240
+ console.log(" auth: stored credentials unreadable — run `sol auth login`");
11241
+ }
11242
+ } else {
11243
+ console.log(" auth: not logged in — `sol auth login` (or set SOL_TOKEN) for remote commands");
11244
+ }
11245
+ const objDir = join19(solDir, "objects");
11246
+ const opsFile = join19(solDir, "ops.jsonl");
11247
+ console.log(` files: .sol/objects/ ${writableTag(objDir)}, .sol/ops.jsonl ${existsSync20(opsFile) ? writableTag(opsFile) : "(none yet)"}`);
11248
+ const keyringPath = join19(homedir2(), ".sol", "keyring.json");
11249
+ console.log(` sealed: ${existsSync20(keyringPath) ? "keyring found, ready" : "no keyring (only needed for sealed paths)"}`);
11250
+ console.log(`status: ${report.ok ? "OK" : "PROBLEMS FOUND"}`);
11251
+ if (!report.ok)
11252
+ console.log(" -> `sol fsck --repair` to recover the op-log/head; `sol recover` for cloud/backup rehydrate");
11253
+ process.exitCode = report.ok ? 0 : 1;
11254
+ break;
11255
+ }
11256
+ case "reflog": {
11257
+ const { renderReflog: renderReflog2 } = await Promise.resolve().then(() => exports_admin_reflog);
11258
+ const { log } = open();
11259
+ const ops = await log.history();
11260
+ const flag2 = (name) => {
11261
+ const i = args.indexOf(name);
11262
+ return i >= 0 ? args[i + 1] : undefined;
11263
+ };
11264
+ const branchFilter = args[0] && !args[0].startsWith("--") ? args[0] : undefined;
11265
+ const by = flag2("--by");
11266
+ const sinceStr = flag2("--since");
11267
+ const since = sinceStr ? Date.parse(sinceStr) : undefined;
11268
+ if (sinceStr && Number.isNaN(since))
11269
+ die(`--since: not a date: ${sinceStr} (try YYYY-MM-DD)`);
11270
+ const lines2 = renderReflog2(ops, { branchFilter, actor: by, since });
11271
+ if (!lines2.length)
11272
+ console.log("(no matching ops)");
11273
+ for (const l of lines2)
11274
+ console.log(l);
11275
+ break;
11276
+ }
11277
+ case "recover": {
11278
+ const { RECOVER_HELP: RECOVER_HELP2, listAllRoots: listAllRoots2, restoreFileFromHistory: restoreFileFromHistory2, dumpLog: dumpLog2 } = await Promise.resolve().then(() => (init_admin_recover(), exports_admin_recover));
11279
+ const flag2 = (name) => {
11280
+ const i = args.indexOf(name);
11281
+ return i >= 0 ? args[i + 1] : undefined;
11282
+ };
11283
+ if (!args.length || args[0] === "--help") {
11284
+ console.log(RECOVER_HELP2);
11285
+ break;
11286
+ }
11287
+ if (args.includes("--from-remote")) {
11288
+ const url = args[args.indexOf("--from-remote") + 1];
11289
+ const repoName = args[args.indexOf("--from-remote") + 2];
11290
+ if (!url || !repoName)
11291
+ die("usage: sol recover --from-remote <url> <repo>");
11292
+ console.log(`to rehydrate from the cloud, clone the remote into a fresh dir:
11293
+ sol clone ${url} ${repoName}
11294
+ then copy your uncommitted working files in. (a destructive in-place rehydrate is intentionally manual.)`);
11295
+ break;
11296
+ }
11297
+ const { log } = open();
11298
+ const store2 = loadStore();
11299
+ const ops = await log.history();
11300
+ const asyncStore = { get: async (h) => store2.get(h), put: async () => die("recover is read-only"), has: async (h) => store2.has(h) };
11301
+ if (args.includes("--list-all-roots")) {
11302
+ const roots = await listAllRoots2(ops, async (h) => store2.has(h));
11303
+ for (const r of roots)
11304
+ console.log(` ${r.root.slice(0, 16)} seq ${r.seq} ${r.resolvable ? "resolvable" : "MISSING"}${r.message ? ` ${r.message}` : ""}`);
11305
+ if (!roots.length)
11306
+ console.log(" (no roots — empty repo)");
11307
+ break;
11308
+ }
11309
+ if (args.includes("--file")) {
11310
+ const path = flag2("--file") || die("usage: sol recover --file <path> [--from <seq|hash>]");
11311
+ const fromStr = flag2("--from");
11312
+ const from = fromStr ? /^\d+$/.test(fromStr) ? Number(fromStr) : fromStr : undefined;
11313
+ const rec = await restoreFileFromHistory2(asyncStore, ops, path, from);
11314
+ if (!rec.found)
11315
+ die(rec.reason);
11316
+ if ("sealed" in rec) {
11317
+ console.log(`${path} is sealed at seq ${rec.fromSeq} — recovered the opaque box (open it with your key):`);
11318
+ console.log(rec.box);
11319
+ } else {
11320
+ const out = rec.encoding === "base64" ? Buffer.from(rec.content, "base64") : rec.content;
11321
+ writeFileSync17(join19(cwd, path), out);
11322
+ console.log(`restored ${path} from seq ${rec.fromSeq} (root ${rec.fromRoot.slice(0, 16)}) -> wrote to disk; \`sol add ${path}\` to re-track`);
11323
+ }
11324
+ break;
11325
+ }
11326
+ if (args.includes("--dump-log")) {
11327
+ const i = args.indexOf("--dump-log");
11328
+ const fromSeq = args[i + 1] && /^\d+$/.test(args[i + 1]) ? Number(args[i + 1]) : undefined;
11329
+ const toSeq = args[i + 2] && /^\d+$/.test(args[i + 2]) ? Number(args[i + 2]) : undefined;
11330
+ console.log(dumpLog2(ops, fromSeq, toSeq));
11331
+ break;
11332
+ }
11333
+ console.log(RECOVER_HELP2);
11334
+ break;
11335
+ }
9923
11336
  case "gc": {
9924
11337
  const { log } = open();
9925
11338
  const store2 = loadStore();
@@ -9941,16 +11354,16 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9941
11354
  if (op.prov)
9942
11355
  walk(op.prov);
9943
11356
  }
9944
- const objDir = join18(solDir, "objects");
11357
+ const objDir = join19(solDir, "objects");
9945
11358
  let removed = 0;
9946
11359
  for (const name of readdirSync9(objDir)) {
9947
11360
  if (name.endsWith(".tmp") || !reachable.has(name)) {
9948
- unlinkSync7(join18(objDir, name));
11361
+ unlinkSync7(join19(objDir, name));
9949
11362
  removed++;
9950
11363
  }
9951
11364
  }
9952
11365
  console.log(`gc: kept ${reachable.size} object(s), removed ${removed} unreachable`);
9953
- if (existsSync18(join18(solDir, "env", "seal"))) {
11366
+ if (existsSync20(join19(solDir, "env", "seal"))) {
9954
11367
  const { gcStaleStanzas: gcStaleStanzas2 } = await Promise.resolve().then(() => (init_secret(), exports_secret));
9955
11368
  const st = gcStaleStanzas2(solDir);
9956
11369
  if (st.removed)
@@ -9965,8 +11378,8 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9965
11378
  console.log(p);
9966
11379
  break;
9967
11380
  }
9968
- const f = join18(cwd, ".solignore");
9969
- const lead = existsSync18(f) && !readFileSync18(f, "utf8").endsWith(`
11381
+ const f = join19(cwd, ".solignore");
11382
+ const lead = existsSync20(f) && !readFileSync20(f, "utf8").endsWith(`
9970
11383
  `) ? `
9971
11384
  ` : "";
9972
11385
  appendFileSync3(f, lead + pat + `
@@ -9977,7 +11390,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9977
11390
  case "hide": {
9978
11391
  const { repo } = open();
9979
11392
  const wantJson = args.includes("--json");
9980
- const cfg = resolveRemote(solDir) || die("no remote — `sol push <repo>` (or `sol remote <url> <repo>`) first; the policy lives on the repo");
11393
+ const cfg = resolveRemote2(solDir) || die("no remote — `sol push <repo>` (or `sol remote <url> <repo>`) first; the policy lives on the repo");
9981
11394
  const token = process.env.SOL_TOKEN || authExpired();
9982
11395
  const sub = args[0];
9983
11396
  const flag2 = (name) => {
@@ -10082,12 +11495,12 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10082
11495
  let removed = 0;
10083
11496
  {
10084
11497
  const res = await scrubHistory2(solDir, ops, targets);
10085
- writeFileSync16(join18(solDir, "ops.jsonl"), res.ops.map((o) => JSON.stringify(o)).join(`
11498
+ writeFileSync17(join19(solDir, "ops.jsonl"), res.ops.map((o) => JSON.stringify(o)).join(`
10086
11499
  `) + (res.ops.length ? `
10087
11500
  ` : ""));
10088
- writeFileSync16(join18(solDir, "HEAD"), JSON.stringify({ head: res.newHead, seq: res.newSeq, logTip: res.newLogTip }));
10089
- if (existsSync18(refsPath())) {
10090
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
11501
+ writeFileSync17(join19(solDir, "HEAD"), JSON.stringify({ head: res.newHead, seq: res.newSeq, logTip: res.newLogTip }));
11502
+ if (existsSync20(refsPath())) {
11503
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
10091
11504
  for (const b of Object.values(refs.branches)) {
10092
11505
  if (b.head && res.rootMap.has(b.head))
10093
11506
  b.head = res.rootMap.get(b.head);
@@ -10110,7 +11523,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10110
11523
  break;
10111
11524
  }
10112
11525
  if (reapply) {
10113
- const cfg = resolveRemote(solDir) || die("no remote — the policy lives on the repo; `sol remote <url> <repo>` first");
11526
+ const cfg = resolveRemote2(solDir) || die("no remote — the policy lives on the repo; `sol remote <url> <repo>` first");
10114
11527
  const token = process.env.SOL_TOKEN || authExpired();
10115
11528
  const { SealedClient: SealedClient3 } = await Promise.resolve().then(() => (init_sealed_client(), exports_sealed_client));
10116
11529
  const { UNREADABLE: UNREADABLE2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
@@ -10177,7 +11590,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10177
11590
  const { SealedClient: SealedClient3 } = await Promise.resolve().then(() => (init_sealed_client(), exports_sealed_client));
10178
11591
  const { parseRecipient: parseRecipient3, resolveRecipient: resolveRecipient3, recordAudience: recordAudience3 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
10179
11592
  const { pubFingerprint: pubFingerprint2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
10180
- const dirUrl2 = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL).replace(/\/+$/, "");
11593
+ const dirUrl2 = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
10181
11594
  const explicit = positional.slice(1);
10182
11595
  const recipientPubKeys2 = {};
10183
11596
  const audienceAccounts2 = [];
@@ -10185,7 +11598,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10185
11598
  const crossAccount2 = [];
10186
11599
  let policyApplied2 = false;
10187
11600
  if (!explicit.length) {
10188
- const cfg = resolveRemote(solDir);
11601
+ const cfg = resolveRemote2(solDir);
10189
11602
  const token = process.env.SOL_TOKEN;
10190
11603
  if (cfg && token) {
10191
11604
  const resolved = await recipientsForPath(cfg, token, dir).catch(() => {
@@ -10245,15 +11658,15 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10245
11658
  if (content === SEALED && !args.includes("--hide-names"))
10246
11659
  die("already sealed: " + path);
10247
11660
  if (content === undefined) {
10248
- const abs = join18(cwd, path);
10249
- if (!existsSync18(abs))
11661
+ const abs = join19(cwd, path);
11662
+ if (!existsSync20(abs))
10250
11663
  die("no such file: " + path);
10251
- content = readFileSync18(abs, "utf8");
11664
+ content = readFileSync20(abs, "utf8");
10252
11665
  }
10253
11666
  const ring = loadKeyRing();
10254
11667
  const { SealedClient: SealedClient2 } = await Promise.resolve().then(() => (init_sealed_client(), exports_sealed_client));
10255
11668
  const { parseRecipient: parseRecipient2, resolveRecipient: resolveRecipient2, recordAudience: recordAudience2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
10256
- const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL).replace(/\/+$/, "");
11669
+ const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
10257
11670
  const recipientPubKeys = {};
10258
11671
  const audienceAccounts = [];
10259
11672
  const localRecipients = new Set([actor]);
@@ -10266,7 +11679,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10266
11679
  let hideNamesFromPolicy = false;
10267
11680
  let hideExistenceFromPolicy = false;
10268
11681
  if (!rawRecipients.length) {
10269
- const cfg = resolveRemote(solDir);
11682
+ const cfg = resolveRemote2(solDir);
10270
11683
  const token = process.env.SOL_TOKEN;
10271
11684
  if (cfg && token) {
10272
11685
  const { pubFingerprint: pubFingerprint2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
@@ -10295,7 +11708,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10295
11708
  }
10296
11709
  }
10297
11710
  if (wantEscrow && !escrowSlots.length) {
10298
- const cfg = resolveRemote(solDir);
11711
+ const cfg = resolveRemote2(solDir);
10299
11712
  const token = process.env.SOL_TOKEN;
10300
11713
  if (cfg && token) {
10301
11714
  const { recipientsForAudience: recipientsForAudience2 } = await Promise.resolve().then(() => (init_remote(), exports_remote));
@@ -10501,7 +11914,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10501
11914
  const { repo } = open();
10502
11915
  const wantJson = args.includes("--json");
10503
11916
  if (args.includes("--check")) {
10504
- const cfg = resolveRemote(solDir) || die("no remote — drift is checked against the policy on the repo; `sol remote <url> <repo>` first");
11917
+ const cfg = resolveRemote2(solDir) || die("no remote — drift is checked against the policy on the repo; `sol remote <url> <repo>` first");
10505
11918
  const token = process.env.SOL_TOKEN || authExpired();
10506
11919
  const { policyCheck: policyCheck2 } = await Promise.resolve().then(() => (init_remote(), exports_remote));
10507
11920
  const states = [];
@@ -10558,7 +11971,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10558
11971
  const { openContent: openContent2, UNREADABLE: UNREADABLE2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
10559
11972
  const { parseStruct: parseStruct2 } = await Promise.resolve().then(() => (init_struct(), exports_struct));
10560
11973
  const audiences = loadAudiences2(solDir);
10561
- const ring = existsSync18(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
11974
+ const ring = existsSync20(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
10562
11975
  const self = loadSelfIdentity2();
10563
11976
  const levelOf = (boxStr) => {
10564
11977
  try {
@@ -10753,7 +12166,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10753
12166
  const result = merge2({ store: store2 }, other.base, ours, other.head);
10754
12167
  if (result.conflicts.length) {
10755
12168
  materializeTree(store2, result.head);
10756
- writeFileSync16(join18(solDir, "MERGE_HEAD"), other.head);
12169
+ writeFileSync17(join19(solDir, "MERGE_HEAD"), other.head);
10757
12170
  saveMergeConflicts(solDir, result.conflicts);
10758
12171
  console.log(`merge ${name} -> ${result.conflicts.length} conflict(s), left in your working tree (uncommitted):`);
10759
12172
  for (const c of result.conflicts)
@@ -10854,8 +12267,19 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10854
12267
  break;
10855
12268
  }
10856
12269
  case "remote": {
10857
- if (!existsSync18(solDir))
12270
+ if (!existsSync20(solDir))
10858
12271
  die("not a sol repo");
12272
+ if (args[0] === "verify") {
12273
+ const { remoteVerify: remoteVerify2, renderRemoteVerify: renderRemoteVerify2 } = await Promise.resolve().then(() => (init_admin_remote_verify(), exports_admin_remote_verify));
12274
+ const cfg2 = resolveRemote2(solDir) || die("no remote — `sol remote <url> <repo>` (or `sol clone`) first");
12275
+ const token = process.env.SOL_TOKEN || await loadStoredToken() || authExpired();
12276
+ const result = await remoteVerify2((url, init) => fetch(url, init), cfg2.url, cfg2.repo, token);
12277
+ const { lines: lines2, exitCode } = renderRemoteVerify2(result);
12278
+ for (const l of lines2)
12279
+ console.log(l);
12280
+ process.exitCode = exitCode;
12281
+ break;
12282
+ }
10859
12283
  if (args[0]) {
10860
12284
  const repoName = args[1] || die("usage: sol remote <url> <repo>");
10861
12285
  saveRemote(solDir, { url: args[0], repo: repoName });
@@ -10900,15 +12324,15 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10900
12324
  }
10901
12325
  if (!tokens)
10902
12326
  die("timed out waiting for approval");
10903
- mkdirSync11(dirname4(CRED_PATH), { recursive: true });
10904
- writeFileSync16(CRED_PATH, JSON.stringify({ webUrl, ...tokens }, null, 2), { mode: 384 });
12327
+ mkdirSync12(dirname5(CRED_PATH), { recursive: true });
12328
+ writeFileSync17(CRED_PATH, JSON.stringify({ webUrl, ...tokens }, null, 2), { mode: 384 });
10905
12329
  const c = tokenClaims(tokens.accessToken);
10906
12330
  console.log(`
10907
12331
  Logged in as ${c.handle ? `@${c.handle}` : c.email || "user"}.`);
10908
12332
  if (!c.handle)
10909
12333
  console.log(" (no handle yet — set one in the web app to get your <handle>/<repo> namespace)");
10910
12334
  } else if (sub === "logout") {
10911
- if (existsSync18(CRED_PATH))
12335
+ if (existsSync20(CRED_PATH))
10912
12336
  unlinkSync7(CRED_PATH);
10913
12337
  console.log("logged out");
10914
12338
  } else if (sub === "status" || !sub) {
@@ -10917,11 +12341,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10917
12341
  console.log(`authenticated via SOL_TOKEN (env)${c2.handle ? ` as @${c2.handle}` : ""}`);
10918
12342
  break;
10919
12343
  }
10920
- if (!existsSync18(CRED_PATH)) {
12344
+ if (!existsSync20(CRED_PATH)) {
10921
12345
  console.log("not logged in — run `sol auth login` (or set SOL_TOKEN)");
10922
12346
  break;
10923
12347
  }
10924
- const creds = JSON.parse(readFileSync18(CRED_PATH, "utf8"));
12348
+ const creds = JSON.parse(readFileSync20(CRED_PATH, "utf8"));
10925
12349
  const c = tokenClaims(creds.accessToken || "");
10926
12350
  const stale = typeof c.exp === "number" && c.exp * 1000 < Date.now();
10927
12351
  console.log(`logged in as ${c.handle ? `@${c.handle}` : c.email || "user"} via ${creds.webUrl}${stale ? " (token stale — refreshes on next use)" : ""}`);
@@ -10971,9 +12395,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10971
12395
  console.log("heads-up: changing your handle re-namespaces your repos under the new <handle>/<repo>.");
10972
12396
  console.log(`handle set to @${out.handle}`);
10973
12397
  } else if (sub === "pat") {
10974
- if (!existsSync18(CRED_PATH))
12398
+ if (!existsSync20(CRED_PATH))
10975
12399
  die("run `sol auth login` first");
10976
- const creds = JSON.parse(readFileSync18(CRED_PATH, "utf8"));
12400
+ const creds = JSON.parse(readFileSync20(CRED_PATH, "utf8"));
10977
12401
  const token = await loadStoredToken();
10978
12402
  if (!token || !creds.webUrl)
10979
12403
  die("run `sol auth login` first");
@@ -11031,11 +12455,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11031
12455
  const { converge: converge2 } = await Promise.resolve().then(() => (init_converge(), exports_converge));
11032
12456
  const peer = openPeer2(localSrc);
11033
12457
  const peerHead = await peer.log.head();
11034
- const dest = resolve4(procCwd, args[1] || (args[0].replace(/\/+$/, "").split("/").pop() || "clone") + "-clone");
11035
- const ddir = join18(dest, ".sol");
11036
- if (existsSync18(ddir))
12458
+ const dest = resolve5(procCwd, args[1] || (args[0].replace(/\/+$/, "").split("/").pop() || "clone") + "-clone");
12459
+ const ddir = join19(dest, ".sol");
12460
+ if (existsSync20(ddir))
11037
12461
  die("already a sol repo: " + dest);
11038
- mkdirSync11(ddir, { recursive: true });
12462
+ mkdirSync12(ddir, { recursive: true });
11039
12463
  const peerOps = await peer.log.history();
11040
12464
  const res = await converge2({ store: new FileStore2(ddir), log: new FileOpLog2(ddir) }, { nodes: await peerNodes2(peer, peerHead, peerOps), ops: peerOps, incomingHead: peerHead, actor });
11041
12465
  const dstore = new Store;
@@ -11045,7 +12469,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11045
12469
  const files = (res.head ? listAll2(dstore, res.head) : []).filter((f) => !f.split("/").some(isReservedKey2));
11046
12470
  for (const f of files)
11047
12471
  materializeInto(dstore, res.head, dest, f);
11048
- writeFileSync16(join18(ddir, "refs.json"), JSON.stringify({ current: "main", branches: { main: { head: res.head, base: res.head, remote: res.head } }, tags: {} }, null, 2));
12472
+ writeFileSync17(join19(ddir, "refs.json"), JSON.stringify({ current: "main", branches: { main: { head: res.head, base: res.head, remote: res.head } }, tags: {} }, null, 2));
11049
12473
  writeWorkingIndexAt(ddir, dest, files);
11050
12474
  console.log(`cloned local peer ${args[0]} -> ${dest} (${(await peer.log.history()).length} ops, ${files.length} files)`);
11051
12475
  break;
@@ -11053,13 +12477,13 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11053
12477
  const [url, rest] = remoteUrlArg(args);
11054
12478
  const repoName = rest[0] || die("usage: sol clone [<url>] <owner>/<repo> [dir]");
11055
12479
  const token = process.env.SOL_TOKEN || die("set SOL_TOKEN to the backend bearer token");
11056
- const target = resolve4(cwd, rest[1] || repoName.split("/").pop() || repoName);
11057
- const fdir = join18(target, ".sol");
11058
- if (existsSync18(fdir))
12480
+ const target = resolve5(cwd, rest[1] || repoName.split("/").pop() || repoName);
12481
+ const fdir = join19(target, ".sol");
12482
+ if (existsSync20(fdir))
11059
12483
  die("already a sol repo: " + target);
11060
12484
  const cfg = { url, repo: repoName };
11061
12485
  const bundle = await remoteExport(cfg, token);
11062
- mkdirSync11(fdir, { recursive: true });
12486
+ mkdirSync12(fdir, { recursive: true });
11063
12487
  await writeBundle(fdir, bundle, 0);
11064
12488
  saveRemote(fdir, cfg);
11065
12489
  await pullEnvState(fdir, cfg, token);
@@ -11072,8 +12496,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11072
12496
  cloneBranches[name] = { head: h, base: h, remote: h };
11073
12497
  if (!cloneBranches[onBranch])
11074
12498
  cloneBranches[onBranch] = { head: checkoutHead, base: checkoutHead, remote: checkoutHead };
11075
- writeFileSync16(join18(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
11076
- writeFileSync16(join18(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: bundle.seq, logTip: bundle.tip }));
12499
+ writeFileSync17(join19(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
12500
+ writeFileSync17(join19(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: bundle.seq, logTip: bundle.tip }));
11077
12501
  const store2 = new Store;
11078
12502
  for (const node of bundle.nodes)
11079
12503
  store2.put(node);
@@ -11094,11 +12518,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11094
12518
  if (!loadRemote(solDir)) {
11095
12519
  const repoName = args.find((a) => !a.startsWith("-"));
11096
12520
  if (repoName) {
11097
- saveRemote(solDir, { url: DEFAULT_REMOTE_URL, repo: repoName });
11098
- console.log(`remote set: ${DEFAULT_REMOTE_URL} (repo ${repoName})`);
12521
+ saveRemote(solDir, { url: DEFAULT_REMOTE_URL2, repo: repoName });
12522
+ console.log(`remote set: ${DEFAULT_REMOTE_URL2} (repo ${repoName})`);
11099
12523
  }
11100
12524
  }
11101
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`, or `sol push <repo>` to use the hosted Sol");
12525
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`, or `sol push <repo>` to use the hosted Sol");
11102
12526
  {
11103
12527
  const { pendingScrubPaths: pendingScrubPaths2, historyHasCleartext: historyHasCleartext2 } = await Promise.resolve().then(() => (init_secret_scrub(), exports_secret_scrub));
11104
12528
  const ops0 = await log.history();
@@ -11112,7 +12536,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11112
12536
  const ops = await log.history();
11113
12537
  const localSeq = ops.length ? ops[ops.length - 1].seq : 0;
11114
12538
  const localTip = await log.logTip();
11115
- const localRefs = existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")) : undefined;
12539
+ const localRefs = existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")) : undefined;
11116
12540
  const branch = localRefs?.current ?? "main";
11117
12541
  const branchHead = await log.head() ?? "";
11118
12542
  const remoteWasEmpty = rh.seq === 0 && !rh.tip;
@@ -11124,7 +12548,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11124
12548
  const forkBase = localRefs?.branches[branch]?.remote;
11125
12549
  const baseOp = forkBase ? ops.find((o) => o.rootAfter === forkBase) : undefined;
11126
12550
  const fromSeq = baseOp ? baseOp.seq : rh.seq;
11127
- const res = await remotePush(cfg, token, {
12551
+ const res = await remotePushChunked(cfg, token, {
11128
12552
  nodes: allLocalNodes(),
11129
12553
  ops: ops.filter((o) => o.seq > fromSeq),
11130
12554
  branch,
@@ -11135,8 +12559,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11135
12559
  const prevHead = await log.head() ?? "";
11136
12560
  await writeBundle(solDir, canon, 0);
11137
12561
  const convergedHead = res.head ?? canon.refs?.branches?.[branch] ?? branchHead;
11138
- if (existsSync18(refsPath())) {
11139
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
12562
+ if (existsSync20(refsPath())) {
12563
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
11140
12564
  if (canon.refs) {
11141
12565
  for (const [name, h] of Object.entries(canon.refs.branches)) {
11142
12566
  refs.branches[name] = { head: refs.branches[name]?.head ?? h, base: refs.branches[name]?.base ?? h, remote: h };
@@ -11160,6 +12584,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11160
12584
  console.log(`pushed ${res.applied} op(s) -> remote (branch ${branch} @ ${(convergedHead || "").slice(0, 12)})${mergedNote}${conflictNote}${prod ? `; production=${prod}` : ""}`);
11161
12585
  }
11162
12586
  await pushEnvState(solDir, cfg, token);
12587
+ await surfaceOwner(cfg, token);
11163
12588
  if (wantPublic) {
11164
12589
  const a = await accessSet(cfg, token, { visibility: "public" });
11165
12590
  console.log(`${cfg.repo} is now ${a.visibility}`);
@@ -11200,8 +12625,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11200
12625
  const ownMeta = readViewMeta(solDir);
11201
12626
  const base2 = peerMeta?.startHead ?? ownMeta?.startHead;
11202
12627
  const res = await converge2({ store: new FileStore2(solDir, objectsDir()), log }, { nodes: await peerNodes2(peer, peerHead, peerOps), ops: peerOps, incomingHead: peerHead, actor, ...base2 ? { base: base2 } : {} });
11203
- if (existsSync18(refsPath())) {
11204
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
12628
+ if (existsSync20(refsPath())) {
12629
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
11205
12630
  if (refs.branches[refs.current])
11206
12631
  refs.branches[refs.current].head = res.head;
11207
12632
  saveRefs(refs);
@@ -11224,7 +12649,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11224
12649
  }
11225
12650
  break;
11226
12651
  }
11227
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12652
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
11228
12653
  const token = process.env.SOL_TOKEN || authExpired();
11229
12654
  await surfaceEnvDivergence(solDir, cfg, token);
11230
12655
  await pullEnvState(solDir, cfg, token);
@@ -11232,11 +12657,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11232
12657
  const ops = await log.history();
11233
12658
  const localSeq = ops.length ? ops[ops.length - 1].seq : 0;
11234
12659
  const localTip = await log.logTip();
11235
- const curBranch = existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")).current : bundle.refs?.production || "main";
12660
+ const curBranch = existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")).current : bundle.refs?.production || "main";
11236
12661
  const remoteCurHead = bundle.refs?.branches?.[curBranch] ?? bundle.head ?? "";
11237
12662
  if (bundle.seq === localSeq && bundle.tip === localTip) {
11238
- if (bundle.refs && existsSync18(refsPath())) {
11239
- const lr = JSON.parse(readFileSync18(refsPath(), "utf8"));
12663
+ if (bundle.refs && existsSync20(refsPath())) {
12664
+ const lr = JSON.parse(readFileSync20(refsPath(), "utf8"));
11240
12665
  const before = lr.branches[lr.current]?.head;
11241
12666
  for (const [name, h] of Object.entries(bundle.refs.branches))
11242
12667
  lr.branches[name] = { head: h, base: lr.branches[name]?.base ?? h, remote: h };
@@ -11260,9 +12685,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11260
12685
  break;
11261
12686
  }
11262
12687
  const syncRefHead = (h) => {
11263
- if (!existsSync18(refsPath()))
12688
+ if (!existsSync20(refsPath()))
11264
12689
  return;
11265
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
12690
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
11266
12691
  if (refs.branches[refs.current])
11267
12692
  refs.branches[refs.current].head = h;
11268
12693
  saveRefs(refs);
@@ -11280,8 +12705,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11280
12705
  syncRefHead(remoteCurHead);
11281
12706
  setOpLogHead(remoteCurHead);
11282
12707
  materializeTree(loadStore(), remoteCurHead);
11283
- if (bundle.refs && existsSync18(refsPath())) {
11284
- const lr = JSON.parse(readFileSync18(refsPath(), "utf8"));
12708
+ if (bundle.refs && existsSync20(refsPath())) {
12709
+ const lr = JSON.parse(readFileSync20(refsPath(), "utf8"));
11285
12710
  for (const [name, h] of Object.entries(bundle.refs.branches))
11286
12711
  lr.branches[name] = { head: h, base: lr.branches[name]?.base ?? h, remote: h };
11287
12712
  saveRefs(lr);
@@ -11329,9 +12754,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11329
12754
  for (const c of result.conflicts) {
11330
12755
  const blob = fileAt2(store2, result.head, c.path);
11331
12756
  if (blob)
11332
- writeFileSync16(join18(cwd, c.path), blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
12757
+ writeFileSync17(join19(cwd, c.path), blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
11333
12758
  }
11334
- writeFileSync16(join18(solDir, "MERGE_HEAD"), remoteHead2);
12759
+ writeFileSync17(join19(solDir, "MERGE_HEAD"), remoteHead2);
11335
12760
  saveMergeConflicts(solDir, result.conflicts);
11336
12761
  console.log(`pulled + merged WITH ${result.conflicts.length} conflict(s), left uncommitted in your working tree:`);
11337
12762
  for (const c of result.conflicts)
@@ -11345,12 +12770,36 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11345
12770
  }
11346
12771
  break;
11347
12772
  }
12773
+ case "sync": {
12774
+ if (!existsSync20(solDir))
12775
+ die("not a sol repo — run `sol init` first");
12776
+ if (!args.includes("--watch"))
12777
+ die("usage: sol sync --watch [--poll-interval <ms>] [--no-push-immediately] [--dry-run]");
12778
+ resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`, or `sol push <repo>` first");
12779
+ const token = process.env.SOL_TOKEN || await loadStoredToken() || authExpired();
12780
+ const intFlag = (name, def) => {
12781
+ const i = args.indexOf(name);
12782
+ if (i < 0 || i + 1 >= args.length)
12783
+ return def;
12784
+ const n = Number(args[i + 1]);
12785
+ return Number.isFinite(n) && n > 0 ? n : def;
12786
+ };
12787
+ const { runSyncWatch: runSyncWatch2 } = await Promise.resolve().then(() => (init_sync_watch(), exports_sync_watch));
12788
+ await runSyncWatch2({
12789
+ repoDir: cwd,
12790
+ token,
12791
+ pollInterval: intFlag("--poll-interval", 5000),
12792
+ pushImmediate: !args.includes("--no-push-immediately"),
12793
+ dryRun: args.includes("--dry-run")
12794
+ });
12795
+ break;
12796
+ }
11348
12797
  case "promote": {
11349
- if (!existsSync18(solDir))
12798
+ if (!existsSync20(solDir))
11350
12799
  die("not a sol repo");
11351
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12800
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
11352
12801
  const token = process.env.SOL_TOKEN || authExpired();
11353
- const cur = existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")).current : "main";
12802
+ const cur = existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")).current : "main";
11354
12803
  const branch = args[0] || cur;
11355
12804
  const refs = await remotePromote(cfg, token, branch);
11356
12805
  console.log(`promoted '${branch}' -> production '${refs.production}' now at ${(refs.branches[refs.production] ?? "").slice(0, 12)}`);
@@ -11361,9 +12810,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11361
12810
  const parent = frest[0] || die("usage: sol fork [<url>] <parent-repo> <new-repo> [dir]");
11362
12811
  const newRepo = frest[1] || die("usage: sol fork [<url>] <parent-repo> <new-repo> [dir]");
11363
12812
  const token = process.env.SOL_TOKEN || authExpired();
11364
- const target = resolve4(cwd, frest[2] || newRepo);
11365
- const fdir = join18(target, ".sol");
11366
- if (existsSync18(fdir))
12813
+ const target = resolve5(cwd, frest[2] || newRepo);
12814
+ const fdir = join19(target, ".sol");
12815
+ if (existsSync20(fdir))
11367
12816
  die("already a sol repo: " + target);
11368
12817
  const parentCfg = { url, repo: parent };
11369
12818
  const newCfg = { url, repo: newRepo, forkParent: parent };
@@ -11379,7 +12828,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11379
12828
  }
11380
12829
  await forkMeta(newCfg, token, parent);
11381
12830
  const canon = await remoteExport(newCfg, token);
11382
- mkdirSync11(fdir, { recursive: true });
12831
+ mkdirSync12(fdir, { recursive: true });
11383
12832
  await writeBundle(fdir, canon, 0);
11384
12833
  const srvRefs = canon.refs ?? { branches: { main: canon.head ?? "" }, production: "main" };
11385
12834
  const checkout = canon.checkout ?? { branch: srvRefs.production || "main", head: srvRefs.branches[srvRefs.production] ?? canon.head };
@@ -11388,8 +12837,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11388
12837
  const cloneBranches = {};
11389
12838
  for (const [name, h] of Object.entries(srvRefs.branches))
11390
12839
  cloneBranches[name] = { head: h, base: h, remote: h };
11391
- writeFileSync16(join18(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
11392
- writeFileSync16(join18(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: canon.seq, logTip: canon.tip }));
12840
+ writeFileSync17(join19(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
12841
+ writeFileSync17(join19(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: canon.seq, logTip: canon.tip }));
11393
12842
  saveRemote(fdir, newCfg);
11394
12843
  const store2 = new Store;
11395
12844
  for (const node of canon.nodes)
@@ -11407,7 +12856,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11407
12856
  }
11408
12857
  case "access": {
11409
12858
  const token = process.env.SOL_TOKEN || authExpired();
11410
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12859
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
11411
12860
  const sub = args[0];
11412
12861
  if (!sub || sub === "show") {
11413
12862
  const a = await accessGet(cfg, token);
@@ -11459,7 +12908,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11459
12908
  break;
11460
12909
  }
11461
12910
  case "forks": {
11462
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12911
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
11463
12912
  const token = process.env.SOL_TOKEN || authExpired();
11464
12913
  const { forks } = await forksList(cfg, token);
11465
12914
  if (!forks.length)
@@ -11472,11 +12921,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11472
12921
  break;
11473
12922
  }
11474
12923
  case "mr": {
11475
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12924
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
11476
12925
  const token = process.env.SOL_TOKEN || authExpired();
11477
12926
  const { mrSummary: mrSummary2 } = await Promise.resolve().then(() => exports_mr);
11478
12927
  const sub = args[0];
11479
- const localRefs = () => existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")) : { current: "main", branches: {}, tags: {} };
12928
+ const localRefs = () => existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")) : { current: "main", branches: {}, tags: {} };
11480
12929
  const flag2 = (name) => {
11481
12930
  const i = args.indexOf(name);
11482
12931
  return i >= 0 ? args[i + 1] : undefined;
@@ -11654,7 +13103,7 @@ ${mrSummary2(pr)}`);
11654
13103
  die("usage: sol run [--keep <path>] [--isolate] <command...>");
11655
13104
  const { capture: capture3, hydrate: hydrate3, isolateCommand: isolateCommand2 } = await Promise.resolve().then(() => (init_runtime(), exports_runtime));
11656
13105
  const head = await repo.head();
11657
- const dir = mkdtempSync(join18(tmpdir(), "sol-run-"));
13106
+ const dir = mkdtempSync(join19(tmpdir(), "sol-run-"));
11658
13107
  try {
11659
13108
  const hn = hydrate3(loadStore(), head, dir);
11660
13109
  console.log(`hydrated ${hn} file(s) -> sandbox${isolate ? " (isolated: no network, writes confined to the sandbox)" : ""}`);
@@ -11674,8 +13123,8 @@ ${mrSummary2(pr)}`);
11674
13123
  const { written, deleted } = await capture3(repo, dir, keep);
11675
13124
  if (written.length || deleted.length) {
11676
13125
  await appendCommit(log, await repo.head(), `run: ${command.join(" ")}`, head);
11677
- if (existsSync18(refsPath())) {
11678
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
13126
+ if (existsSync20(refsPath())) {
13127
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
11679
13128
  if (refs.branches[refs.current]) {
11680
13129
  refs.branches[refs.current].head = await repo.head();
11681
13130
  saveRefs(refs);
@@ -11687,7 +13136,7 @@ ${mrSummary2(pr)}`);
11687
13136
  materialize(synced, nh, f);
11688
13137
  for (const f of deleted) {
11689
13138
  try {
11690
- unlinkSync7(join18(cwd, f));
13139
+ unlinkSync7(join19(cwd, f));
11691
13140
  } catch {}
11692
13141
  }
11693
13142
  console.log(`captured ${written.length} written, ${deleted.length} deleted file(s):`);
@@ -11699,7 +13148,7 @@ ${mrSummary2(pr)}`);
11699
13148
  console.log("no files captured (the run produced no tracked changes)");
11700
13149
  }
11701
13150
  } finally {
11702
- rmSync2(dir, { recursive: true, force: true });
13151
+ rmSync3(dir, { recursive: true, force: true });
11703
13152
  }
11704
13153
  break;
11705
13154
  }
@@ -11707,12 +13156,12 @@ ${mrSummary2(pr)}`);
11707
13156
  const [{ exportHistoryToGit: exportHistoryToGit2, importGitRepo: importGitRepo2 }, { hydrate: hydrate3 }] = await Promise.all([Promise.resolve().then(() => (init_git_adapter(), exports_git_adapter)), Promise.resolve().then(() => (init_runtime(), exports_runtime))]);
11708
13157
  const sub = args[0];
11709
13158
  if (sub === "import") {
11710
- const gitPath = resolve4(cwd, args[1] || die("usage: sol git import <git-repo> [dir]"));
11711
- const target = resolve4(cwd, args[2] || basename(gitPath));
11712
- const fdir = join18(target, ".sol");
11713
- if (existsSync18(fdir))
13159
+ const gitPath = resolve5(cwd, args[1] || die("usage: sol git import <git-repo> [dir]"));
13160
+ const target = resolve5(cwd, args[2] || basename2(gitPath));
13161
+ const fdir = join19(target, ".sol");
13162
+ if (existsSync20(fdir))
11714
13163
  die("already a sol repo: " + target);
11715
- mkdirSync11(fdir, { recursive: true });
13164
+ mkdirSync12(fdir, { recursive: true });
11716
13165
  const { commits, branches, head, current } = await importGitRepo2(gitPath, fdir);
11717
13166
  const refsBranches = {};
11718
13167
  for (const b of branches)
@@ -11720,20 +13169,20 @@ ${mrSummary2(pr)}`);
11720
13169
  if (!refsBranches[current])
11721
13170
  refsBranches[current] = { head, base: head };
11722
13171
  refsBranches[current].head = head;
11723
- writeFileSync16(join18(fdir, "refs.json"), JSON.stringify({ current, branches: refsBranches, tags: {} }, null, 2));
13172
+ writeFileSync17(join19(fdir, "refs.json"), JSON.stringify({ current, branches: refsBranches, tags: {} }, null, 2));
11724
13173
  const store2 = new Store;
11725
- for (const name of readdirSync9(join18(fdir, "objects"))) {
13174
+ for (const name of readdirSync9(join19(fdir, "objects"))) {
11726
13175
  if (name.endsWith(".tmp"))
11727
13176
  continue;
11728
13177
  try {
11729
- store2.put(decodeObject2(readFileSync18(join18(fdir, "objects", name))));
13178
+ store2.put(decodeObject2(readFileSync20(join19(fdir, "objects", name))));
11730
13179
  } catch {}
11731
13180
  }
11732
13181
  const onDisk = hydrate3(store2, head, target);
11733
- console.log(`imported ${commits} commit(s), ${branches.length} branch(es) from git -> ${args[2] || basename(gitPath)} (${onDisk} files; on branch ${current})`);
13182
+ console.log(`imported ${commits} commit(s), ${branches.length} branch(es) from git -> ${args[2] || basename2(gitPath)} (${onDisk} files; on branch ${current})`);
11734
13183
  } else if (sub === "export") {
11735
13184
  const { log } = open();
11736
- const gitPath = resolve4(cwd, args[1] || die("usage: sol git export <git-repo> [-b branch]"));
13185
+ const gitPath = resolve5(cwd, args[1] || die("usage: sol git export <git-repo> [-b branch]"));
11737
13186
  const store2 = loadStore();
11738
13187
  const head = await log.head() ?? "";
11739
13188
  if (!head || !listAll2(store2, head).length) {
@@ -11752,15 +13201,111 @@ ${mrSummary2(pr)}`);
11752
13201
  }
11753
13202
  break;
11754
13203
  }
13204
+ case "agent": {
13205
+ if (!existsSync20(solDir))
13206
+ die("not a sol repo — run `sol init` first");
13207
+ const parentSol = parentSolDir(solDir) ?? solDir;
13208
+ const { createSession: createSession2, stopSession: stopSession2, sessions: sessions2, pruneSession: pruneSession2 } = await Promise.resolve().then(() => (init_agent_sessions(), exports_agent_sessions));
13209
+ const sub = args[0];
13210
+ const json = args.includes("--json");
13211
+ const flagVal = (name) => {
13212
+ const i = args.indexOf(name);
13213
+ return i >= 0 ? args[i + 1] : undefined;
13214
+ };
13215
+ switch (sub) {
13216
+ case "start": {
13217
+ const name = args.slice(1).find((a) => !a.startsWith("-")) || die('usage: sol agent start <name> [dir] [--actor <name>] [--check "<cmd>"]');
13218
+ const positionals2 = args.slice(1).filter((a) => !a.startsWith("-"));
13219
+ const sessionActor = flagVal("--actor") || actor;
13220
+ const metaRaw = flagVal("--meta");
13221
+ let metadata;
13222
+ if (metaRaw) {
13223
+ try {
13224
+ metadata = JSON.parse(metaRaw);
13225
+ } catch {
13226
+ die("--meta must be valid JSON");
13227
+ }
13228
+ }
13229
+ const rec = createSession2({
13230
+ parentSol,
13231
+ name,
13232
+ actor: sessionActor,
13233
+ dir: positionals2[1] ? resolve5(procCwd, positionals2[1]) : undefined,
13234
+ check: flagVal("--check"),
13235
+ metadata
13236
+ });
13237
+ if (json) {
13238
+ console.log(JSON.stringify(rec));
13239
+ break;
13240
+ }
13241
+ console.log(`started agent session '${rec.name}' (id ${rec.sessionId})`);
13242
+ console.log(` view ${rec.viewDir} (branch ${rec.branch} @ ${(rec.startHead || "empty").slice(0, 12)})`);
13243
+ console.log(` actor ${rec.actor}`);
13244
+ console.log(` identity ${rec.fingerprint}`);
13245
+ console.log(` -> wire the agent: cd ${rec.viewDir} && export SOL_ACTOR=${rec.actor} SOL_SIGNING_KEY=$(cat ${rec.signingKeyPath})`);
13246
+ console.log(` then edit + \`sol commit -m "..." <files>\`; converge with \`sol pull ${rec.viewDir}\` from the parent.`);
13247
+ console.log(` stop/clean: \`sol agent stop ${rec.sessionId}\` | \`sol agent cleanup ${rec.sessionId} --delete\``);
13248
+ break;
13249
+ }
13250
+ case "stop": {
13251
+ const id = args.slice(1).find((a) => !a.startsWith("-")) || die("usage: sol agent stop <sessionId>");
13252
+ const ok = stopSession2(parentSol, id);
13253
+ if (json)
13254
+ console.log(JSON.stringify({ stopped: ok, sessionId: id }));
13255
+ else
13256
+ console.log(ok ? `stopped session ${id} (the view + key remain — \`sol agent cleanup\` to remove)` : `no such session: ${id}`);
13257
+ if (!ok)
13258
+ process.exitCode = 1;
13259
+ break;
13260
+ }
13261
+ case "status":
13262
+ case undefined: {
13263
+ const id = args.slice(1).find((a) => !a.startsWith("-"));
13264
+ const all = await sessions2(parentSol);
13265
+ const list = id ? all.filter((s) => s.sessionId === id || s.name === id) : all;
13266
+ if (json) {
13267
+ console.log(JSON.stringify({ repo: parentSol, sessions: list }));
13268
+ break;
13269
+ }
13270
+ if (!list.length) {
13271
+ console.log(id ? `no such session: ${id}` : "no agent sessions — start one with `sol agent start <name>`");
13272
+ break;
13273
+ }
13274
+ console.log(`agent sessions of ${parentSol}:`);
13275
+ for (const s of list) {
13276
+ console.log(` ${s.name.padEnd(16)} ${s.sessionId.padEnd(28)} ${(s.head || s.startHead || "empty").slice(0, 12)} ${s.actor.padEnd(12)} ${s.fingerprint} [${s.state}]`);
13277
+ console.log(` ${s.viewDir}`);
13278
+ }
13279
+ console.log(" stop: `sol agent stop <id>` | cleanup: `sol agent cleanup <id> [--delete]` (or bare `cleanup` to prune stale)");
13280
+ break;
13281
+ }
13282
+ case "cleanup": {
13283
+ const id = args.slice(1).find((a) => !a.startsWith("-"));
13284
+ const removed = pruneSession2(parentSol, id, args.includes("--delete"));
13285
+ if (json)
13286
+ console.log(JSON.stringify({ removed }));
13287
+ else
13288
+ console.log(removed.length ? `cleaned ${removed.length} session(s): ${removed.join(", ")}` : id ? `no such session: ${id}` : "no stale sessions to clean");
13289
+ break;
13290
+ }
13291
+ default:
13292
+ die(`unknown: sol agent ${sub}
13293
+ sol agent start <name> [dir] [--actor N] [--check "<cmd>"]
13294
+ sol agent status [<id>]
13295
+ sol agent stop <id>
13296
+ sol agent cleanup [<id>] [--delete]`);
13297
+ }
13298
+ break;
13299
+ }
11755
13300
  case "view": {
11756
13301
  const { log } = open();
11757
13302
  if (readViewMeta(solDir))
11758
13303
  die("already inside a view — create views from the parent repo (its `.sol` owns the shared store + op-log).");
11759
13304
  const name = args.find((a) => !a.startsWith("-")) || die("usage: sol view <name> [dir]");
11760
13305
  const rest = args.filter((a) => !a.startsWith("-"));
11761
- const defaultDir = join18(dirname4(cwd), `${basename(cwd)}-${name}`);
11762
- const viewDir = rest[1] ? resolve4(procCwd, rest[1]) : defaultDir;
11763
- if (existsSync18(join18(viewDir, ".sol")))
13306
+ const defaultDir = join19(dirname5(cwd), `${basename2(cwd)}-${name}`);
13307
+ const viewDir = rest[1] ? resolve5(procCwd, rest[1]) : defaultDir;
13308
+ if (existsSync20(join19(viewDir, ".sol")))
11764
13309
  die("already a sol repo/view: " + viewDir);
11765
13310
  const branch = `view/${name}`;
11766
13311
  const startHead = await log.head() ?? emptyRoot2(loadStore());
@@ -11774,7 +13319,7 @@ ${mrSummary2(pr)}`);
11774
13319
  break;
11775
13320
  }
11776
13321
  case "views": {
11777
- if (!existsSync18(solDir))
13322
+ if (!existsSync20(solDir))
11778
13323
  die("not a sol repo");
11779
13324
  const parentSol = parentSolDir(solDir) ?? solDir;
11780
13325
  const { pruneViews: pruneViews2, viewStatuses: viewStatuses2, sharedObjectCount: sharedObjectCount2 } = await Promise.resolve().then(() => (init_views(), exports_views));
@@ -11870,12 +13415,12 @@ ${mrSummary2(pr)}`);
11870
13415
  await startSecretMcp2({ solDir, http });
11871
13416
  break;
11872
13417
  }
11873
- if (!existsSync18(solDir))
13418
+ if (!existsSync20(solDir))
11874
13419
  die("not a sol repo — run `sol init` first");
11875
13420
  const { runEnv: runEnv2, runSecret: runSecret2, resolveReference: resolveReference2 } = await Promise.resolve().then(() => (init_secret2(), exports_secret2));
11876
13421
  const { loadSelfIdentity: loadSelfIdentity2, loadManageIdentity: loadManageIdentity2, fetchKey: fetchKey2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
11877
13422
  const { loadIdentity: loadIdentity2 } = await Promise.resolve().then(() => (init_identity_store(), exports_identity_store));
11878
- const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL).replace(/\/+$/, "");
13423
+ const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
11879
13424
  const selfPub = (account) => {
11880
13425
  const id = loadIdentity2();
11881
13426
  return id && id.accountId === account ? id.x25519Pub : undefined;
@@ -11887,7 +13432,7 @@ ${mrSummary2(pr)}`);
11887
13432
  };
11888
13433
  const dirEdPub = async (account) => (await fetchKey2(dirUrl, account))?.edPub;
11889
13434
  const remoteAnchorVerify = async () => {
11890
- const rcfg = resolveRemote(solDir);
13435
+ const rcfg = resolveRemote2(solDir);
11891
13436
  const token = process.env.SOL_TOKEN || await loadStoredToken();
11892
13437
  if (!rcfg || !token)
11893
13438
  return;
@@ -11937,6 +13482,11 @@ everyday (examples are copy-safe — use real filenames):
11937
13482
  sol rm old.txt delete a file (from the repo and disk)
11938
13483
  sol ignore "*.tmp" add an ignore pattern (no arg lists the active patterns)
11939
13484
  sol fsck / sol gc verify integrity / drop unreachable objects
13485
+ sol doctor full health check (op-log + objects + auth + files + sealed keyring)
13486
+ sol fsck --repair recover a corrupt repo (re-point a lost head / truncate to the last verified link)
13487
+ sol recover guide through recovery (rehydrate from cloud, list roots, restore a file from history)
13488
+ sol reflog the op-log as a ref-move history (head moves, by actor/branch/date)
13489
+ sol remote verify REMOTE-side health check (the backend's /fsck) with auth/network differentiation
11940
13490
  sol check --set "bun test" gate convergence on a test command: a merge that line-converges but FAILS
11941
13491
  the check is flagged as a SEMANTIC conflict (status state:"SEMANTIC"), not
11942
13492
  silently accepted. \`sol check\` runs it on demand. (sol check --help)
@@ -11973,6 +13523,16 @@ clone-free agent views (the worktree killer — N agents, one shared store on di
11973
13523
  \`sol pull <view>\` converges losslessly. (sol view --help)
11974
13524
  sol views list every view (name, dir, branch, head, author, active/stale) + --prune
11975
13525
 
13526
+ agent sessions (one atomic command provisions a working tree, a signing identity, an actor, and the test-gate):
13527
+ sol agent start <name> create a view + MINT a per-session Ed25519 signing key + wire the actor +
13528
+ inherit/set the test-gate, all at once. prints SOL_SIGNING_KEY / SOL_ACTOR to
13529
+ export so every op the agent authors is cryptographically attributable.
13530
+ (--actor <name>, --check "<cmd>", [dir], --json)
13531
+ sol agent status [<id>] list sessions (name, id, head, actor, fingerprint, running/stopped/stale)
13532
+ sol agent stop <id> mark a session stopped (presence signal; view + key remain)
13533
+ sol agent cleanup [<id>] remove the session record + its key (--delete also removes the view dir;
13534
+ bare \`cleanup\` prunes every stale session)
13535
+
11976
13536
  branches & tags:
11977
13537
  sol branch list branches (sol branch feature creates one at HEAD)
11978
13538
  sol switch feature switch to a branch (captures current work first, never loses it)
@@ -11989,6 +13549,7 @@ auth (sign in once; remote commands then use the cached token, no SOL_TOKEN need
11989
13549
  remotes (self-hostable backend; token in SOL_TOKEN or via sol auth login):
11990
13550
  sol clone [<url>] <owner>/<repo> [dir] clone a remote repo (checks out PRODUCTION); default dir = <repo>
11991
13551
  sol push / sol pull sync your commits with the remote (push registers your branch's head)
13552
+ sol sync --watch hands-off bidirectional sync daemon: auto-capture + push edits, converge safe pulls in
11992
13553
  sol push <repo> one-step share: no remote set? use the hosted Sol + <repo>, then push
11993
13554
  sol push --public <repo> create + push + make public in one step (new repos are private by default)
11994
13555
  sol promote [branch] point the remote's production branch at <branch> (default: current)
@@ -12028,10 +13589,15 @@ async function runCli(argv) {
12028
13589
  const msg = e?.message || String(e);
12029
13590
  if (/ -> 401\b/.test(msg) || /\b401 unauthorized\b/i.test(msg))
12030
13591
  authExpired();
13592
+ const { CorruptObjectError: CorruptObjectError2, CorruptRepoError: CorruptRepoError2 } = await Promise.resolve().then(() => (init_errors(), exports_errors));
13593
+ if (e instanceof CorruptObjectError2 || e instanceof CorruptRepoError2) {
13594
+ const { friendlyError: friendlyError2 } = await Promise.resolve().then(() => (init_errors_friendly(), exports_errors_friendly));
13595
+ die(friendlyError2(e));
13596
+ }
12031
13597
  die(msg);
12032
13598
  }
12033
13599
  }
12034
- var CRED_PATH, DEFAULT_REMOTE_URL, ATTEST_KEY, remoteUrlArg = (a) => /^https?:\/\//.test(a[0] ?? "") ? [a[0].replace(/\/+$/, ""), a.slice(1)] : [DEFAULT_REMOTE_URL, a], cfgDir = () => parentSolDir(solDir) ?? solDir;
13600
+ var CRED_PATH, DEFAULT_REMOTE_URL2, ATTEST_KEY, remoteUrlArg = (a) => /^https?:\/\//.test(a[0] ?? "") ? [a[0].replace(/\/+$/, ""), a.slice(1)] : [DEFAULT_REMOTE_URL2, a];
12035
13601
  var init_dispatch = __esm(() => {
12036
13602
  init_chain();
12037
13603
  init_diff();
@@ -12043,8 +13609,8 @@ var init_dispatch = __esm(() => {
12043
13609
  init_remote();
12044
13610
  init_lib();
12045
13611
  init_test_gate();
12046
- CRED_PATH = join18(homedir2(), ".sol", "credentials");
12047
- DEFAULT_REMOTE_URL = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
13612
+ CRED_PATH = join19(homedir2(), ".sol", "credentials");
13613
+ DEFAULT_REMOTE_URL2 = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
12048
13614
  ATTEST_KEY = process.env.SOL_ATTEST_KEY || undefined;
12049
13615
  });
12050
13616
 
@@ -12224,10 +13790,16 @@ var init_mcp_vcs_tools = __esm(() => {
12224
13790
  // src/bin/sol-mcp.ts
12225
13791
  var exports_sol_mcp = {};
12226
13792
  __export(exports_sol_mcp, {
13793
+ toolCallFor: () => toolCallFor,
12227
13794
  startWorkspaceMcp: () => startWorkspaceMcp
12228
13795
  });
12229
- import { mkdirSync as mkdirSync12 } from "fs";
12230
- import { dirname as dirname5, join as join19 } from "path";
13796
+ import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
13797
+ import { mkdirSync as mkdirSync13 } from "fs";
13798
+ import { dirname as dirname6, join as join20 } from "path";
13799
+ function toolCallFor(name, args) {
13800
+ const argsHash = "h_" + createHash7("sha256").update(JSON.stringify(args ?? {})).digest("hex").slice(0, 16);
13801
+ return { name, argsHash, invocationId: randomUUID3() };
13802
+ }
12231
13803
  async function handle(ws, name, a) {
12232
13804
  switch (name) {
12233
13805
  case "sol_write":
@@ -12268,8 +13840,8 @@ async function handle(ws, name, a) {
12268
13840
  async function buildWorkspaceServer(solDir2) {
12269
13841
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
12270
13842
  const { CallToolRequestSchema, ListToolsRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
12271
- mkdirSync12(solDir2, { recursive: true });
12272
- const repoRoot2 = dirname5(solDir2);
13843
+ mkdirSync13(solDir2, { recursive: true });
13844
+ const repoRoot2 = dirname6(solDir2);
12273
13845
  if (process.cwd() !== repoRoot2) {
12274
13846
  try {
12275
13847
  process.chdir(repoRoot2);
@@ -12283,16 +13855,20 @@ async function buildWorkspaceServer(solDir2) {
12283
13855
  const r = await callVcsTool(req.params.name, req.params.arguments ?? {});
12284
13856
  return { content: [{ type: "text", text: r.text }], isError: r.isError };
12285
13857
  }
13858
+ if (AUTHORING_MUTATORS.has(req.params.name)) {
13859
+ ws.setToolCall(toolCallFor(req.params.name, req.params.arguments ?? {}));
13860
+ }
12286
13861
  try {
12287
13862
  return await handle(ws, req.params.name, req.params.arguments ?? {});
12288
13863
  } catch (e) {
13864
+ ws.setToolCall(undefined);
12289
13865
  return { content: [{ type: "text", text: "sol error: " + (e?.message ?? e) }], isError: true };
12290
13866
  }
12291
13867
  });
12292
13868
  return server;
12293
13869
  }
12294
13870
  async function startWorkspaceMcp(opts = {}) {
12295
- const solDir2 = opts.solDir || process.env.SOL_DIR || join19(process.cwd(), ".sol");
13871
+ const solDir2 = opts.solDir || process.env.SOL_DIR || join20(process.cwd(), ".sol");
12296
13872
  if (opts.http) {
12297
13873
  const { serveMcpHttp: serveMcpHttp3 } = await Promise.resolve().then(() => (init_mcp_http(), exports_mcp_http));
12298
13874
  await serveMcpHttp3(() => buildWorkspaceServer(solDir2), opts.http);
@@ -12302,12 +13878,13 @@ async function startWorkspaceMcp(opts = {}) {
12302
13878
  const server = await buildWorkspaceServer(solDir2);
12303
13879
  await server.connect(new StdioServerTransport);
12304
13880
  }
12305
- var authoringTools, VCS_TOOL_NAMES, tools, text = (s) => ({ content: [{ type: "text", text: s }] });
13881
+ var AUTHORING_MUTATORS, authoringTools, VCS_TOOL_NAMES, tools, text = (s) => ({ content: [{ type: "text", text: s }] });
12306
13882
  var init_sol_mcp = __esm(() => {
12307
13883
  init_file_store();
12308
13884
  init_workspace();
12309
13885
  init_mcp_http();
12310
13886
  init_mcp_vcs_tools();
13887
+ AUTHORING_MUTATORS = new Set(["sol_write", "sol_edit", "sol_move", "sol_rm", "sol_commit", "sol_checkpoint"]);
12311
13888
  authoringTools = [
12312
13889
  { name: "sol_write", description: "Create or overwrite a text file in the sol workspace. Authoring goes here \u2014 not to a disk.", inputSchema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] } },
12313
13890
  { name: "sol_read", description: "Read a text file from the sol workspace.", inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
@@ -12332,5 +13909,6 @@ var init_sol_mcp = __esm(() => {
12332
13909
  init_sol_mcp();
12333
13910
 
12334
13911
  export {
13912
+ toolCallFor,
12335
13913
  startWorkspaceMcp
12336
13914
  };