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.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 {
@@ -1111,16 +1116,74 @@ var init_sign = __esm(() => {
1111
1116
  });
1112
1117
 
1113
1118
  // src/prov.ts
1119
+ function encodeTool(p) {
1120
+ const surface = p.tool?.trim() || "";
1121
+ if (!p.toolCall && !p.testRunId)
1122
+ return surface || undefined;
1123
+ const segs = [surface, TC_TAG];
1124
+ const tc = p.toolCall;
1125
+ segs.push(`name=${tc?.name ?? ""}`, `args=${tc?.argsHash ?? ""}`, `inv=${tc?.invocationId ?? ""}`, `test=${p.testRunId ?? ""}`);
1126
+ return segs.join(TC_SEP);
1127
+ }
1128
+ function decodeTool(tool) {
1129
+ if (!tool)
1130
+ return {};
1131
+ const parts = tool.split(TC_SEP);
1132
+ if (parts.length < 2 || parts[1] !== TC_TAG)
1133
+ return { tool: tool || undefined };
1134
+ const surface = parts[0] || undefined;
1135
+ const kv = new Map;
1136
+ for (const seg of parts.slice(2)) {
1137
+ const eq = seg.indexOf("=");
1138
+ if (eq >= 0)
1139
+ kv.set(seg.slice(0, eq), seg.slice(eq + 1));
1140
+ }
1141
+ const name = kv.get("name") || "";
1142
+ const argsHash = kv.get("args") || "";
1143
+ const invocationId = kv.get("inv") || "";
1144
+ const testRunId = kv.get("test") || undefined;
1145
+ const out = { tool: surface };
1146
+ if (name || argsHash || invocationId)
1147
+ out.toolCall = { name, argsHash, invocationId };
1148
+ if (testRunId)
1149
+ out.testRunId = testRunId;
1150
+ return out;
1151
+ }
1152
+ function parseToolCall(raw) {
1153
+ if (!raw)
1154
+ return;
1155
+ let v;
1156
+ try {
1157
+ v = JSON.parse(raw);
1158
+ } catch {
1159
+ return;
1160
+ }
1161
+ if (!v || typeof v !== "object")
1162
+ return;
1163
+ const o = v;
1164
+ const name = typeof o.name === "string" ? o.name : "";
1165
+ const argsHash = typeof o.argsHash === "string" ? o.argsHash : "";
1166
+ const invocationId = typeof o.invocationId === "string" ? o.invocationId : "";
1167
+ if (!name || !argsHash || !invocationId)
1168
+ return;
1169
+ return { name, argsHash, invocationId };
1170
+ }
1114
1171
  function provenanceFromEnv(env = process.env) {
1115
1172
  const kind = env.SOL_KIND === "agent" ? "agent" : "human";
1173
+ const tool = encodeTool({
1174
+ tool: env.SOL_TOOL,
1175
+ toolCall: parseToolCall(env.SOL_TOOL_CALL),
1176
+ testRunId: env.SOL_TEST_RUN_ID || undefined
1177
+ });
1116
1178
  const node = buildProvNode({
1117
1179
  agentId: env.SOL_AGENT_ID || (kind === "agent" ? env.SOL_ACTOR : undefined),
1118
1180
  sessionId: env.SOL_SESSION,
1119
1181
  model: env.SOL_MODEL,
1120
1182
  intent: env.SOL_INTENT,
1121
- tool: env.SOL_TOOL
1183
+ tool
1122
1184
  });
1123
- return node ? { kind, node } : { kind };
1185
+ const opRunId = env.SOL_OPERATION_ID || undefined;
1186
+ return { kind, ...node ? { node } : {}, ...opRunId ? { opRunId } : {} };
1124
1187
  }
1125
1188
  function buildProvNode(fields) {
1126
1189
  const clean = { kind: "prov" };
@@ -1149,6 +1212,9 @@ function authorLabel(op, reader) {
1149
1212
  const bits = [];
1150
1213
  if (p?.sessionId)
1151
1214
  bits.push(`session ${p.sessionId}`);
1215
+ const tp = p ? decodeTool(p.tool) : {};
1216
+ if (tp.toolCall)
1217
+ bits.push(`via ${tp.toolCall.name}`);
1152
1218
  const tail = p?.intent ? ` — intent: ${p.intent}` : "";
1153
1219
  return `${who}${bits.length ? ` (${bits.join(", ")})` : ""}${tail}`;
1154
1220
  }
@@ -1158,10 +1224,21 @@ function provJson(op, reader, key, trust) {
1158
1224
  return;
1159
1225
  const out = { kind: op.kind ?? "human" };
1160
1226
  if (p) {
1161
- for (const k of ["agentId", "sessionId", "model", "intent", "tool", "rationale", "parent"])
1227
+ for (const k of ["agentId", "sessionId", "model", "intent", "rationale", "parent"])
1162
1228
  if (p[k])
1163
1229
  out[k] = p[k];
1164
1230
  }
1231
+ if (p?.tool) {
1232
+ const tp = decodeTool(p.tool);
1233
+ if (tp.tool)
1234
+ out.tool = tp.tool;
1235
+ if (tp.toolCall)
1236
+ out.toolCall = tp.toolCall;
1237
+ if (tp.testRunId)
1238
+ out.testRunId = tp.testRunId;
1239
+ }
1240
+ if (op.opRunId !== undefined)
1241
+ out.opRunId = op.opRunId;
1165
1242
  if (op.sig !== undefined) {
1166
1243
  const t = trustAuthor(op, trust);
1167
1244
  out.signed = true;
@@ -1219,7 +1296,7 @@ function signTrailerValue(op, trust) {
1219
1296
  return `${t.fingerprint} verified`;
1220
1297
  }
1221
1298
  }
1222
- function provTrailers(op, reader, key, trust) {
1299
+ function provTrailers(op, reader, key, trust, reviewCount) {
1223
1300
  const p = reader ? provOf(op, reader) : undefined;
1224
1301
  const lines = [];
1225
1302
  const signed = trustAuthor(op, trust);
@@ -1233,6 +1310,13 @@ function provTrailers(op, reader, key, trust) {
1233
1310
  lines.push(`Sol-Session: ${p.sessionId}`);
1234
1311
  if (p?.model)
1235
1312
  lines.push(`Sol-Model: ${p.model}`);
1313
+ const tp = decodeTool(p?.tool);
1314
+ if (tp.toolCall)
1315
+ lines.push(`Sol-ToolCall: ${tp.toolCall.name} args=${tp.toolCall.argsHash} inv=${tp.toolCall.invocationId}`);
1316
+ if (tp.testRunId)
1317
+ lines.push(`Sol-TestRun: ${tp.testRunId}`);
1318
+ if (op.opRunId)
1319
+ lines.push(`Sol-Operation: ${op.opRunId}`);
1236
1320
  const intent = p?.intent || (isAgent ? op.message?.trim() || undefined : undefined);
1237
1321
  if (intent)
1238
1322
  lines.push(`Sol-Intent: ${intent}`);
@@ -1241,17 +1325,22 @@ function provTrailers(op, reader, key, trust) {
1241
1325
  lines.push(`Sol-Sign: ${sign}`);
1242
1326
  if (op.att !== undefined && !(key && !verifyAtt(key, op)))
1243
1327
  lines.push(`Sol-Attested: pushedBy=${op.pushedBy ?? "?"}`);
1328
+ if (reviewCount && reviewCount > 0)
1329
+ lines.push(`Sol-Attestations: ${reviewCount}`);
1244
1330
  return lines;
1245
1331
  }
1246
1332
  async function captureProv(sink, env = process.env) {
1247
- const { kind, node } = provenanceFromEnv(env);
1333
+ const { kind, node, opRunId } = provenanceFromEnv(env);
1248
1334
  const out = {};
1249
1335
  if (kind === "agent")
1250
1336
  out.kind = "agent";
1251
1337
  if (node)
1252
1338
  out.prov = await sink.put(node);
1339
+ if (opRunId)
1340
+ out.opRunId = opRunId;
1253
1341
  return out;
1254
1342
  }
1343
+ var TC_SEP = "\x01", TC_TAG = "tc1";
1255
1344
  var init_prov = __esm(() => {
1256
1345
  init_attest();
1257
1346
  init_sign();
@@ -1283,10 +1372,25 @@ class AsyncRepo {
1283
1372
  await this.log.append(by === this.actor ? this.sign(entry) : entry);
1284
1373
  }
1285
1374
  provFor;
1375
+ pendingToolCall;
1376
+ setToolCall(tc) {
1377
+ this.pendingToolCall = tc;
1378
+ }
1286
1379
  async provenance(by) {
1287
1380
  if (by !== this.actor)
1288
1381
  return {};
1289
- return this.provFor ??= captureProv(this.store);
1382
+ const base = await (this.provFor ??= captureProv(this.store));
1383
+ const tc = this.pendingToolCall;
1384
+ if (!tc)
1385
+ return base;
1386
+ this.pendingToolCall = undefined;
1387
+ const { kind, node } = provenanceFromEnv();
1388
+ const session = node ?? { kind: "prov" };
1389
+ const surface = decodeTool(session.tool);
1390
+ const tool = encodeTool({ tool: surface.tool, toolCall: tc, testRunId: surface.testRunId });
1391
+ const merged = { ...session, kind: "prov", tool };
1392
+ const prov = await this.store.put(merged);
1393
+ return { ...kind === "agent" ? { kind: "agent" } : {}, prov, ...base.opRunId ? { opRunId: base.opRunId } : {} };
1290
1394
  }
1291
1395
  async currentRoot() {
1292
1396
  const head = await this.log.head();
@@ -1475,6 +1579,15 @@ var init_async_repo = __esm(() => {
1475
1579
  });
1476
1580
 
1477
1581
  // src/file-store.ts
1582
+ var exports_file_store = {};
1583
+ __export(exports_file_store, {
1584
+ openLocal: () => openLocal,
1585
+ initLocal: () => initLocal,
1586
+ encodeObject: () => encodeObject,
1587
+ decodeObject: () => decodeObject,
1588
+ FileStore: () => FileStore,
1589
+ FileOpLog: () => FileOpLog
1590
+ });
1478
1591
  import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
1479
1592
  import { join } from "node:path";
1480
1593
  import { gunzipSync, gzipSync } from "node:zlib";
@@ -1564,6 +1677,17 @@ class FileOpLog {
1564
1677
  return pageOps(all, opts);
1565
1678
  }
1566
1679
  }
1680
+ function initLocal(dir, actor = "anon") {
1681
+ const solDir = join(dir, ".sol");
1682
+ mkdirSync(solDir, { recursive: true });
1683
+ const store = new FileStore(solDir);
1684
+ const log = new FileOpLog(solDir);
1685
+ return { repo: new AsyncRepo(store, log, actor), store, log, solDir };
1686
+ }
1687
+ function openLocal(dir, actor = "anon") {
1688
+ const solDir = join(dir, ".sol");
1689
+ return new AsyncRepo(new FileStore(solDir), new FileOpLog(solDir), actor);
1690
+ }
1567
1691
  var init_file_store = __esm(() => {
1568
1692
  init_async_repo();
1569
1693
  init_chain();
@@ -2081,6 +2205,74 @@ var init_keyring_at_rest = __esm(() => {
2081
2205
  });
2082
2206
 
2083
2207
  // src/bin/lib.ts
2208
+ var exports_lib = {};
2209
+ __export(exports_lib, {
2210
+ writeWorkingIndexAt: () => writeWorkingIndexAt,
2211
+ writeWorkingIndex: () => writeWorkingIndex,
2212
+ workingChanges: () => workingChanges,
2213
+ workingChangeRows: () => workingChangeRows,
2214
+ walkFiles: () => walkFiles,
2215
+ walkEntries: () => walkEntries,
2216
+ viewMetaPath: () => viewMetaPath,
2217
+ unresolvedConflictPaths: () => unresolvedConflictPaths,
2218
+ truncateOpLog: () => truncateOpLog,
2219
+ solDir: () => solDir,
2220
+ snapshotTree: () => snapshotTree,
2221
+ snapshotFile: () => snapshotFile,
2222
+ setSealedDecryptor: () => setSealedDecryptor,
2223
+ setOpLogHead: () => setOpLogHead,
2224
+ saveTrust: () => saveTrust,
2225
+ saveRefs: () => saveRefs,
2226
+ saveKeyRing: () => saveKeyRing,
2227
+ revealedHeadForRender: () => revealedHeadForRender,
2228
+ revealedHead: () => revealedHead,
2229
+ resolveRefSoft: () => resolveRefSoft,
2230
+ resolveRef: () => resolveRef,
2231
+ repoRoot: () => repoRoot,
2232
+ repoRel: () => repoRel,
2233
+ refsPath: () => refsPath,
2234
+ readViewMeta: () => readViewMeta,
2235
+ procCwd: () => procCwd,
2236
+ printWorkingDiff: () => printWorkingDiff,
2237
+ printTreeDiff: () => printTreeDiff,
2238
+ persistTree: () => persistTree,
2239
+ parentSolDir: () => parentSolDir,
2240
+ opsDir: () => opsDir,
2241
+ open: () => open,
2242
+ objectsDir: () => objectsDir,
2243
+ materializeTree: () => materializeTree,
2244
+ materializeInto: () => materializeInto,
2245
+ materializeDiff: () => materializeDiff,
2246
+ materialize: () => materialize,
2247
+ lockDir: () => lockDir,
2248
+ loadWorkingIndex: () => loadWorkingIndex,
2249
+ loadTrust: () => loadTrust,
2250
+ loadStore: () => loadStore,
2251
+ loadSigner: () => loadSigner,
2252
+ loadRefs: () => loadRefs,
2253
+ loadKeyRing: () => loadKeyRing,
2254
+ lexists: () => lexists,
2255
+ isWorkingPath: () => isWorkingPath,
2256
+ isIgnored: () => isIgnored,
2257
+ ignorePatterns: () => ignorePatterns,
2258
+ forceIncluded: () => forceIncluded,
2259
+ fileChange: () => fileChange,
2260
+ die: () => die,
2261
+ cwd: () => cwd,
2262
+ countChanges: () => countChanges,
2263
+ conflictHunks: () => conflictHunks,
2264
+ cfgDir: () => cfgDir,
2265
+ blameFile: () => blameFile,
2266
+ appendFileSync: () => appendFileSync2,
2267
+ appendCommit: () => appendCommit,
2268
+ appendCapture: () => appendCapture,
2269
+ allLocalNodes: () => allLocalNodes,
2270
+ addInclude: () => addInclude,
2271
+ actor: () => actor,
2272
+ acquireLock: () => acquireLock,
2273
+ LazyStore: () => LazyStore,
2274
+ DEFAULT_IGNORE: () => DEFAULT_IGNORE
2275
+ });
2084
2276
  import {
2085
2277
  appendFileSync as appendFileSync2,
2086
2278
  chmodSync as chmodSync3,
@@ -2090,6 +2282,7 @@ import {
2090
2282
  readdirSync as readdirSync2,
2091
2283
  readFileSync as readFileSync4,
2092
2284
  readlinkSync,
2285
+ renameSync as renameSync2,
2093
2286
  symlinkSync,
2094
2287
  unlinkSync,
2095
2288
  writeFileSync as writeFileSync4
@@ -2120,6 +2313,9 @@ function parentSolDir(dir = solDir) {
2120
2313
  const m = readViewMeta(dir);
2121
2314
  return m ? resolve(dir, m.parent) : undefined;
2122
2315
  }
2316
+ function cfgDir(dir = solDir) {
2317
+ return parentSolDir(dir) ?? dir;
2318
+ }
2123
2319
  function repoRel(p) {
2124
2320
  return relative(cwd, resolve(procCwd, p));
2125
2321
  }
@@ -3042,6 +3238,35 @@ function setOpLogHead(head) {
3042
3238
  const cur = existsSync4(hf) ? JSON.parse(readFileSync4(hf, "utf8")) : { seq: 0 };
3043
3239
  writeFileSync4(hf, JSON.stringify({ ...cur, head }));
3044
3240
  }
3241
+ function truncateOpLog(toSeq) {
3242
+ const opsFile = join4(solDir, "ops.jsonl");
3243
+ if (!existsSync4(opsFile))
3244
+ return;
3245
+ const kept = [];
3246
+ let last;
3247
+ for (const l of readFileSync4(opsFile, "utf8").split(`
3248
+ `)) {
3249
+ if (!l)
3250
+ continue;
3251
+ let op;
3252
+ try {
3253
+ op = JSON.parse(l);
3254
+ } catch {
3255
+ break;
3256
+ }
3257
+ if (op.seq > toSeq)
3258
+ break;
3259
+ kept.push(l);
3260
+ last = op;
3261
+ }
3262
+ const tmp = `${opsFile}.tmp`;
3263
+ writeFileSync4(tmp, kept.length ? kept.join(`
3264
+ `) + `
3265
+ ` : "");
3266
+ renameSync2(tmp, opsFile);
3267
+ const hf = join4(solDir, "HEAD");
3268
+ writeFileSync4(hf, JSON.stringify(last ? { head: last.rootAfter, seq: last.seq, logTip: last.entryHash } : { seq: 0 }));
3269
+ }
3045
3270
  async function persistTree(src, dst, head) {
3046
3271
  const node = src.get(head);
3047
3272
  if (!node)
@@ -3113,7 +3338,7 @@ function saveKeyRing(ring) {
3113
3338
  chmodSync3(keysPath(), 384);
3114
3339
  } catch {}
3115
3340
  }
3116
- var procCwd, repoRoot, cwd, solDir, actor, viewMetaPath = (dir = solDir) => join4(dir, "view.json"), viewParent, objectsDir = () => join4(viewParent ?? solDir, "objects"), lockDir = () => solDir, SYMLINK_MODE = 40960, EXEC_MODE = 493, DEFAULT_IGNORE, includePath = () => join4(solDir, "include"), LOCK_STALE_MS = 5000, LazyStore, sealedDecryptor, indent = (s) => s.split(`
3341
+ var procCwd, repoRoot, cwd, solDir, actor, viewMetaPath = (dir = solDir) => join4(dir, "view.json"), viewParent, objectsDir = () => join4(viewParent ?? solDir, "objects"), opsDir = () => solDir, lockDir = () => solDir, SYMLINK_MODE = 40960, EXEC_MODE = 493, DEFAULT_IGNORE, includePath = () => join4(solDir, "include"), LOCK_STALE_MS = 5000, LazyStore, sealedDecryptor, indent = (s) => s.split(`
3117
3342
  `).map((l) => " " + l).join(`
3118
3343
  `), indexPath = () => join4(solDir, "index.json"), linesOf = (content) => content === "" ? [] : content.replace(/\n$/, "").split(`
3119
3344
  `), refsPath = () => join4(solDir, "refs.json"), saveRefs = (refs) => writeFileSync4(refsPath(), JSON.stringify(refs, null, 2)), _signer, trustPath = () => join4(solDir, "trust.json"), keysPath = () => join4(solDir, "keys.json");
@@ -3166,9 +3391,13 @@ __export(exports_remote, {
3166
3391
  writeBundle: () => writeBundle,
3167
3392
  saveRemote: () => saveRemote,
3168
3393
  remoteRefs: () => remoteRefs,
3394
+ remotePushFinalize: () => remotePushFinalize,
3395
+ remotePushChunked: () => remotePushChunked,
3396
+ remotePushBatch: () => remotePushBatch,
3169
3397
  remotePush: () => remotePush,
3170
3398
  remotePromote: () => remotePromote,
3171
3399
  remoteHead: () => remoteHead,
3400
+ remoteGate: () => remoteGate,
3172
3401
  remoteExport: () => remoteExport,
3173
3402
  remoteEnvPush: () => remoteEnvPush,
3174
3403
  remoteEnvPull: () => remoteEnvPull,
@@ -3179,6 +3408,7 @@ __export(exports_remote, {
3179
3408
  policyRemove: () => policyRemove,
3180
3409
  policyGet: () => policyGet,
3181
3410
  policyCheck: () => policyCheck,
3411
+ ownerSet: () => ownerSet,
3182
3412
  mrOpen: () => mrOpen,
3183
3413
  mrList: () => mrList,
3184
3414
  mrGet: () => mrGet,
@@ -3201,6 +3431,44 @@ async function call(cfg, token, path, init) {
3201
3431
  throw new Error(`remote ${path} -> ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}`);
3202
3432
  return res.json();
3203
3433
  }
3434
+ function splitNodes(nodes, batchSizeBytes) {
3435
+ const batches = [];
3436
+ let cur = [];
3437
+ let curBytes = 0;
3438
+ for (const node of nodes) {
3439
+ const nb = sizeOf(node);
3440
+ if (cur.length && curBytes + nb > batchSizeBytes) {
3441
+ batches.push(cur);
3442
+ cur = [];
3443
+ curBytes = 0;
3444
+ }
3445
+ cur.push(node);
3446
+ curBytes += nb;
3447
+ }
3448
+ if (cur.length)
3449
+ batches.push(cur);
3450
+ return batches.length ? batches : [[]];
3451
+ }
3452
+ async function remotePushChunked(cfg, token, body, batchSizeBytes = DEFAULT_BATCH_BYTES) {
3453
+ const totalBytes = sizeOf(body.nodes) + sizeOf(body.ops);
3454
+ if (totalBytes <= batchSizeBytes)
3455
+ return remotePush(cfg, token, body);
3456
+ const stamp = `${body.head ?? "h"}-${Date.now()}`;
3457
+ const nodeBatches = splitNodes(body.nodes, batchSizeBytes);
3458
+ const batchIds = [];
3459
+ try {
3460
+ for (let i = 0;i < nodeBatches.length; i++) {
3461
+ const batchId = `${stamp}-${i}`;
3462
+ batchIds.push(batchId);
3463
+ await remotePushBatch(cfg, token, batchId, nodeBatches[i], i === 0 ? body.ops : [], i);
3464
+ }
3465
+ } catch (e) {
3466
+ if (e instanceof Error && /-> 404/.test(e.message))
3467
+ return remotePush(cfg, token, body);
3468
+ throw e;
3469
+ }
3470
+ return remotePushFinalize(cfg, token, batchIds, body.branch, body.head, body.expectedHead);
3471
+ }
3204
3472
  function loadRemote(solDir2) {
3205
3473
  const p = join5(solDir2, "remote.json");
3206
3474
  return existsSync5(p) ? JSON.parse(readFileSync5(p, "utf8")) : undefined;
@@ -3221,9 +3489,10 @@ async function writeBundle(solDir2, bundle, from = 0) {
3221
3489
  writeFileSync5(join5(solDir2, "HEAD"), JSON.stringify({ head: bundle.head, seq: bundle.seq, logTip: bundle.tip }));
3222
3490
  return fresh.length;
3223
3491
  }
3224
- 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) => writeFileSync5(join5(solDir2, "remote.json"), JSON.stringify(cfg, null, 2));
3492
+ 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) => writeFileSync5(join5(solDir2, "remote.json"), JSON.stringify(cfg, null, 2));
3225
3493
  var init_remote = __esm(() => {
3226
3494
  init_file_store();
3495
+ DEFAULT_BATCH_BYTES = 5 * 1024 * 1024;
3227
3496
  });
3228
3497
 
3229
3498
  // src/bin/test-gate.ts
@@ -5270,6 +5539,314 @@ var init_sealed_client = __esm(() => {
5270
5539
  init_crypto();
5271
5540
  });
5272
5541
 
5542
+ // src/bin/admin-repair.ts
5543
+ var exports_admin_repair = {};
5544
+ __export(exports_admin_repair, {
5545
+ planRepair: () => planRepair,
5546
+ makeResolves: () => makeResolves,
5547
+ applyRepair: () => applyRepair
5548
+ });
5549
+ async function makeResolves(store2) {
5550
+ const cache = new Map;
5551
+ const walk = async (h, seen) => {
5552
+ if (cache.get(h) === true)
5553
+ return true;
5554
+ if (seen.has(h))
5555
+ return true;
5556
+ seen.add(h);
5557
+ const node = await store2.get(h);
5558
+ if (!node)
5559
+ return false;
5560
+ if (node.kind === "tree") {
5561
+ for (const e of Object.values(node.entries))
5562
+ if (!await walk(e.hash, seen))
5563
+ return false;
5564
+ }
5565
+ return true;
5566
+ };
5567
+ return async (root) => {
5568
+ if (cache.has(root))
5569
+ return cache.get(root);
5570
+ const ok = await walk(root, new Set);
5571
+ cache.set(root, ok);
5572
+ return ok;
5573
+ };
5574
+ }
5575
+ async function lastResolvableRoot(ops, upToIdx, resolves) {
5576
+ for (let i = Math.min(upToIdx, ops.length - 1);i >= 0; i--) {
5577
+ if (await resolves(ops[i].rootAfter))
5578
+ return { op: ops[i], idx: i };
5579
+ }
5580
+ return;
5581
+ }
5582
+ function firstBrokenIdx(ops) {
5583
+ let prev;
5584
+ for (let i = 0;i < ops.length; i++) {
5585
+ if (ops[i].seq !== i + 1)
5586
+ return i;
5587
+ try {
5588
+ verifyChain(ops.slice(0, i + 1));
5589
+ } catch {
5590
+ return i;
5591
+ }
5592
+ prev = ops[i].entryHash;
5593
+ }
5594
+ return -1;
5595
+ }
5596
+ async function planRepair(ops, head, resolves) {
5597
+ const broken = firstBrokenIdx(ops);
5598
+ if (broken === -1) {
5599
+ if (ops.length === 0)
5600
+ return { kind: "noop", note: "", reason: "empty repo — nothing to repair" };
5601
+ const headResolves = head !== undefined && await resolves(head);
5602
+ if (headResolves)
5603
+ return { kind: "noop", note: "", reason: "repo is healthy — chain valid, head resolves" };
5604
+ const found2 = await lastResolvableRoot(ops, ops.length - 1, resolves);
5605
+ if (!found2) {
5606
+ 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>\`` };
5607
+ }
5608
+ return {
5609
+ kind: "repoint-head",
5610
+ repointHead: found2.op.rootAfter,
5611
+ note: `repair: re-pointed head to ${found2.op.rootAfter} (rootAfter of seq ${found2.op.seq})`,
5612
+ reason: head === undefined ? "head pointer lost — recovered from the op-log" : `head ${head} dangles — recovered to the last fully-resolvable root`
5613
+ };
5614
+ }
5615
+ const keepIdx = broken - 1;
5616
+ if (keepIdx < 0) {
5617
+ 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)` };
5618
+ }
5619
+ const prefix = ops.slice(0, keepIdx + 1);
5620
+ const found = await lastResolvableRoot(prefix, keepIdx, resolves);
5621
+ const truncateTo = prefix[keepIdx].seq;
5622
+ if (!found) {
5623
+ 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` };
5624
+ }
5625
+ return {
5626
+ kind: "truncate-chain",
5627
+ truncateTo,
5628
+ repointHead: found.op.rootAfter,
5629
+ note: `repair: truncated to seq ${truncateTo} (chain broke at seq ${ops[broken].seq}); re-pointed head to ${found.op.rootAfter}`,
5630
+ reason: `op-log chain broken at seq ${ops[broken].seq} — truncated to the last verified link (seq ${found.op.seq})`
5631
+ };
5632
+ }
5633
+ async function applyRepair(exec, by = "sol-doctor", at = Date.now()) {
5634
+ const ops = await exec.log.history();
5635
+ const head = await exec.log.head();
5636
+ const resolves = await makeResolves(exec.store);
5637
+ const plan = await planRepair(ops, head, resolves);
5638
+ if (plan.kind === "noop" || plan.kind === "unfixable")
5639
+ return plan;
5640
+ if (plan.kind === "truncate-chain" && plan.truncateTo !== undefined)
5641
+ await exec.truncate(plan.truncateTo);
5642
+ const newHead = plan.toEmptyTree ? await emptyRoot2(exec.store) : plan.repointHead;
5643
+ if (newHead !== undefined)
5644
+ await exec.setHead(newHead);
5645
+ const repaired = newHead ?? head;
5646
+ const seq = await exec.log.seq() + 1;
5647
+ await exec.log.append({ seq, type: "checkpoint", path: "", rootAfter: repaired ?? await emptyRoot2(exec.store), at, by, message: plan.note });
5648
+ return plan;
5649
+ }
5650
+ var init_admin_repair = __esm(() => {
5651
+ init_chain();
5652
+ init_async_tree();
5653
+ });
5654
+
5655
+ // src/bin/admin.ts
5656
+ var exports_admin = {};
5657
+ __export(exports_admin, {
5658
+ runDoctor: () => runDoctor,
5659
+ renderDoctorIntegrity: () => renderDoctorIntegrity,
5660
+ checkOpLog: () => checkOpLog,
5661
+ checkObjects: () => checkObjects
5662
+ });
5663
+ function checkOpLog(ops) {
5664
+ try {
5665
+ for (let i = 0;i < ops.length; i++)
5666
+ if (ops[i].seq !== i + 1)
5667
+ throw new Error(`gap before seq ${i + 1}`);
5668
+ const tip = verifyChain(ops);
5669
+ return { count: ops.length, chainOk: true, tip };
5670
+ } catch (e) {
5671
+ return { count: ops.length, chainOk: false, chainError: String(e?.message ?? e) };
5672
+ }
5673
+ }
5674
+ async function checkObjects(store2, head, ops) {
5675
+ const missing = [];
5676
+ const seen = new Set;
5677
+ if (head === undefined)
5678
+ return { reachable: 0, missing };
5679
+ const seqOfRoot = new Map;
5680
+ for (const op of ops)
5681
+ if (!seqOfRoot.has(op.rootAfter))
5682
+ seqOfRoot.set(op.rootAfter, op.seq);
5683
+ const walk = async (h, refBy) => {
5684
+ if (seen.has(h))
5685
+ return;
5686
+ seen.add(h);
5687
+ const node = await store2.get(h);
5688
+ if (!node) {
5689
+ missing.push({ hash: h, refBy });
5690
+ return;
5691
+ }
5692
+ if (node.kind === "tree")
5693
+ for (const e of Object.values(node.entries))
5694
+ await walk(e.hash, refBy);
5695
+ };
5696
+ await walk(head, seqOfRoot.get(head));
5697
+ return { reachable: seen.size - missing.length, missing };
5698
+ }
5699
+ async function runDoctor(store2, log) {
5700
+ const ops = await log.history();
5701
+ const head = await log.head();
5702
+ const seq = await log.seq();
5703
+ const oplog = checkOpLog(ops);
5704
+ const objects = await checkObjects(store2, head, ops);
5705
+ const headLost = head === undefined && seq > 0;
5706
+ const headDangling = head !== undefined && objects.missing.some((m) => m.hash === head);
5707
+ const ok = oplog.chainOk && objects.missing.length === 0 && !headLost;
5708
+ return { ok, oplog, objects, head, seq, headLost, headDangling };
5709
+ }
5710
+ function renderDoctorIntegrity(r) {
5711
+ const lines = [];
5712
+ const chain = r.oplog.chainOk ? `chain valid${r.oplog.tip ? `, tip: ${r.oplog.tip.slice(0, 16)}` : ""}` : `chain BROKEN — ${r.oplog.chainError}`;
5713
+ lines.push(` op-log: ${r.oplog.count} op(s), ${chain}`);
5714
+ if (r.headLost) {
5715
+ lines.push(` head: LOST (${r.seq} op(s) but no head pointer) — run \`sol fsck --repair\``);
5716
+ } else if (r.headDangling) {
5717
+ lines.push(` head: DANGLING (${r.head?.slice(0, 16)} missing from store) — run \`sol fsck --repair\``);
5718
+ } else {
5719
+ lines.push(` objects: ${r.objects.reachable} reachable, ${r.objects.missing.length} missing${r.objects.missing.length ? " (run: sol fsck --repair)" : ""}`);
5720
+ }
5721
+ for (const m of r.objects.missing)
5722
+ lines.push(` missing ${m.hash}${m.refBy !== undefined ? ` (op seq ${m.refBy})` : ""}`);
5723
+ return lines;
5724
+ }
5725
+ var init_admin = __esm(() => {
5726
+ init_chain();
5727
+ });
5728
+
5729
+ // src/bin/admin-reflog.ts
5730
+ var exports_admin_reflog = {};
5731
+ __export(exports_admin_reflog, {
5732
+ renderReflog: () => renderReflog,
5733
+ formatReflogLine: () => formatReflogLine,
5734
+ filterReflog: () => filterReflog,
5735
+ buildReflog: () => buildReflog
5736
+ });
5737
+ function buildReflog(ops) {
5738
+ const rows = [];
5739
+ let prevRoot;
5740
+ for (const op of ops) {
5741
+ 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 });
5742
+ prevRoot = op.rootAfter;
5743
+ }
5744
+ return rows;
5745
+ }
5746
+ function filterReflog(rows, f = {}) {
5747
+ return rows.filter((r) => {
5748
+ if (f.actor !== undefined && r.by !== f.actor)
5749
+ return false;
5750
+ if (f.since !== undefined && r.at < f.since)
5751
+ return false;
5752
+ if (f.type !== undefined && r.type !== f.type)
5753
+ return false;
5754
+ return true;
5755
+ });
5756
+ }
5757
+ function formatReflogLine(r, branch) {
5758
+ const when = new Date(r.at).toISOString().replace("T", " ").slice(0, 19);
5759
+ const verb = r.merge ? "merge" : r.type;
5760
+ const move = `${short(r.from)} -> ${short(r.to)}`;
5761
+ const label = branch ? `${branch}: ` : "";
5762
+ const body = r.message ? `${verb} ${label}${r.message}` : `${verb} ${label}`.trimEnd();
5763
+ return `${r.seq} ${r.by} ${when} ${body} (${move})`;
5764
+ }
5765
+ function short(h) {
5766
+ if (!h)
5767
+ return "(empty)";
5768
+ return h.length > 14 ? h.slice(0, 14) : h;
5769
+ }
5770
+ function renderReflog(ops, opts = {}) {
5771
+ let rows = filterReflog(buildReflog(ops), opts);
5772
+ if (opts.branchFilter) {
5773
+ const b = opts.branchFilter;
5774
+ rows = rows.filter((r) => r.type === "checkpoint" && (r.message?.includes(b) ?? false));
5775
+ }
5776
+ return rows.reverse().map((r) => formatReflogLine(r, opts.branch));
5777
+ }
5778
+
5779
+ // src/bin/admin-recover.ts
5780
+ var exports_admin_recover = {};
5781
+ __export(exports_admin_recover, {
5782
+ restoreFileFromHistory: () => restoreFileFromHistory,
5783
+ listAllRoots: () => listAllRoots,
5784
+ dumpLog: () => dumpLog,
5785
+ RECOVER_HELP: () => RECOVER_HELP
5786
+ });
5787
+ async function listAllRoots(ops, has) {
5788
+ const byRoot = new Map;
5789
+ for (const op of ops) {
5790
+ byRoot.set(op.rootAfter, { root: op.rootAfter, seq: op.seq, at: op.at, message: op.message });
5791
+ }
5792
+ const out = [...byRoot.values()].sort((a, b) => b.seq - a.seq);
5793
+ if (has)
5794
+ for (const e of out)
5795
+ e.resolvable = await has(e.root);
5796
+ return out;
5797
+ }
5798
+ async function restoreFileFromHistory(store2, ops, path, from) {
5799
+ let window = ops;
5800
+ if (typeof from === "number")
5801
+ window = ops.filter((o) => o.seq <= from);
5802
+ else if (typeof from === "string") {
5803
+ const bare = from.startsWith("h_") ? from : "h_" + from;
5804
+ const idx = ops.findIndex((o) => o.rootAfter === bare || o.rootAfter.startsWith(bare));
5805
+ window = idx >= 0 ? ops.slice(0, idx + 1) : [];
5806
+ if (idx < 0)
5807
+ return { found: false, path, reason: `no op produced root ${from}` };
5808
+ }
5809
+ const tried = new Set;
5810
+ for (let i = window.length - 1;i >= 0; i--) {
5811
+ const root = window[i].rootAfter;
5812
+ if (tried.has(root))
5813
+ continue;
5814
+ tried.add(root);
5815
+ if (!await store2.has(root))
5816
+ continue;
5817
+ const leaf = await nodeAt(store2, root, path);
5818
+ if (!leaf)
5819
+ continue;
5820
+ if (leaf.kind === "sealed")
5821
+ return { found: true, path, sealed: true, box: leaf.box, fromSeq: window[i].seq, fromRoot: root };
5822
+ const blob = await fileAt2(store2, root, path);
5823
+ return { found: true, path, content: leaf.content, encoding: blob?.encoding, fromSeq: window[i].seq, fromRoot: root };
5824
+ }
5825
+ return { found: false, path, reason: from !== undefined ? `path ${path} not found at or before ${from}` : `path ${path} never existed in history` };
5826
+ }
5827
+ function dumpLog(ops, fromSeq, toSeq) {
5828
+ const window = ops.filter((o) => (fromSeq === undefined || o.seq >= fromSeq) && (toSeq === undefined || o.seq <= toSeq));
5829
+ return JSON.stringify(window, null, 2);
5830
+ }
5831
+ var RECOVER_HELP = `sol recover — guide through recovery scenarios
5832
+
5833
+ 1. Rehydrate from the cloud (download ops + objects from the backend)
5834
+ sol recover --from-remote <url> <repo>
5835
+
5836
+ 2. List every root the repo ever landed on (find a lost head)
5837
+ sol recover --list-all-roots
5838
+
5839
+ 3. Restore a file from op history (even if the current tree dropped it)
5840
+ sol recover --file <path> [--from <seq|hash>]
5841
+
5842
+ 4. Dump raw op-log entries as JSON (manual inspection / salvage)
5843
+ sol recover --dump-log [<from-seq> [<to-seq>]]
5844
+
5845
+ after a structural repair, run \`sol doctor\` to confirm the repo is healthy.`;
5846
+ var init_admin_recover = __esm(() => {
5847
+ init_async_tree();
5848
+ });
5849
+
5273
5850
  // src/bin/secret-scrub.ts
5274
5851
  var exports_secret_scrub = {};
5275
5852
  __export(exports_secret_scrub, {
@@ -5676,6 +6253,140 @@ var init_merge = __esm(() => {
5676
6253
  init_tree();
5677
6254
  });
5678
6255
 
6256
+ // src/bin/errors-friendly.ts
6257
+ var exports_errors_friendly = {};
6258
+ __export(exports_errors_friendly, {
6259
+ friendlyError: () => friendlyError,
6260
+ formatSealedError: () => formatSealedError,
6261
+ formatMissingObjectError: () => formatMissingObjectError,
6262
+ formatChainError: () => formatChainError,
6263
+ formatAuthError: () => formatAuthError,
6264
+ classifyAuthError: () => classifyAuthError
6265
+ });
6266
+ function classifyAuthError(status, message = "") {
6267
+ if (status === 401 || / -> 401\b/.test(message) || /\b401\b/.test(message))
6268
+ return "expired";
6269
+ if (status === 403 || / -> 403\b/.test(message) || /\b403\b/.test(message))
6270
+ return "forbidden";
6271
+ if (/invalid token|malformed|not a jwt|jwt/i.test(message))
6272
+ return "invalid";
6273
+ return "unknown";
6274
+ }
6275
+ function formatAuthError(kind, repo) {
6276
+ switch (kind) {
6277
+ case "expired":
6278
+ return "session expired — run `sol auth login` (or set SOL_TOKEN)";
6279
+ case "forbidden":
6280
+ 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\`)`;
6281
+ case "invalid":
6282
+ return "invalid token format — SOL_TOKEN is not a valid session token; re-run `sol auth login` to mint a fresh one";
6283
+ default:
6284
+ return "authentication failed — run `sol auth login` (or set SOL_TOKEN), then try again";
6285
+ }
6286
+ }
6287
+ function formatSealedError(path) {
6288
+ return `sealed file ${path} — you lack a decryption key.
6289
+ -> \`sol keys\` to see your keyring
6290
+ -> ask the owner to re-seal this path to your account (\`sol seal ${path} <you>\`)`;
6291
+ }
6292
+ function formatChainError(err) {
6293
+ const m = err.message;
6294
+ const seq = /seq (\d+)/.exec(m)?.[1];
6295
+ const where = seq ? `op-log chain broken at seq ${seq}` : "op-log chain broken";
6296
+ return `${where}: ${m.replace(/^corrupt repo: /, "")}
6297
+ -> \`sol doctor\` for a full diagnosis
6298
+ -> \`sol fsck --repair\` to recover (truncates to the last verified link)`;
6299
+ }
6300
+ function formatMissingObjectError(hash, seq) {
6301
+ const ref = seq !== undefined ? ` referenced by op seq ${seq}` : "";
6302
+ return `object ${hash}${ref} not found in store.
6303
+ -> \`sol doctor\` then \`sol fsck --repair\` to recover from backup
6304
+ -> or \`sol recover --from-remote <url> <repo>\` to rehydrate from the cloud`;
6305
+ }
6306
+ function friendlyError(err, ctx) {
6307
+ if (err instanceof CorruptObjectError)
6308
+ return formatMissingObjectError(err.hash);
6309
+ if (err instanceof CorruptRepoError) {
6310
+ if (/dangling head|head is lost|missing from the store/.test(err.message)) {
6311
+ return `${err.message.replace(/^corrupt repo: /, "")}
6312
+ -> \`sol doctor\` then \`sol fsck --repair\` to re-point the head`;
6313
+ }
6314
+ return formatChainError(err);
6315
+ }
6316
+ const msg = err instanceof Error ? err.message : String(err);
6317
+ const kind = classifyAuthError(undefined, msg);
6318
+ if (kind !== "unknown")
6319
+ return formatAuthError(kind, ctx?.repo);
6320
+ return msg;
6321
+ }
6322
+ var init_errors_friendly = __esm(() => {
6323
+ init_errors();
6324
+ });
6325
+
6326
+ // src/bin/admin-remote-verify.ts
6327
+ var exports_admin_remote_verify = {};
6328
+ __export(exports_admin_remote_verify, {
6329
+ renderRemoteVerify: () => renderRemoteVerify,
6330
+ remoteVerify: () => remoteVerify,
6331
+ classifyRemoteFailure: () => classifyRemoteFailure
6332
+ });
6333
+ function classifyRemoteFailure(status, message, repo) {
6334
+ if (status === 401 || status === 403)
6335
+ return { kind: "auth", message: formatAuthError(classifyAuthError(status, message), repo) };
6336
+ if (status !== undefined)
6337
+ return { kind: "backend", status, message: `backend error (${status}) — transient, try again: ${message}`.trim() };
6338
+ return { kind: "network", message: `could not reach the remote — connectivity issue: ${message}` };
6339
+ }
6340
+ async function remoteVerify(fetchLike, url, repo, token) {
6341
+ const endpoint2 = `${url.replace(/\/+$/, "")}/fsck?repo=${encodeURIComponent(repo)}`;
6342
+ let res;
6343
+ try {
6344
+ res = await fetchLike(endpoint2, { headers: { authorization: `Bearer ${token}`, "content-type": "application/json" } });
6345
+ } catch (e) {
6346
+ return classifyRemoteFailure(undefined, String(e?.message ?? e), repo);
6347
+ }
6348
+ if (!res.ok) {
6349
+ const body = await res.text().catch(() => "");
6350
+ return classifyRemoteFailure(res.status, body.slice(0, 200), repo);
6351
+ }
6352
+ const fsck = await res.json();
6353
+ return fsck.ok ? { kind: "ok", fsck } : { kind: "corrupt", fsck };
6354
+ }
6355
+ function renderRemoteVerify(r) {
6356
+ switch (r.kind) {
6357
+ case "ok":
6358
+ return {
6359
+ lines: [
6360
+ " checking remote repository...",
6361
+ ` op-log: chain ${r.fsck.chainOk ? "valid" : "BROKEN"}${r.fsck.head ? `, tip: ${r.fsck.head.slice(0, 16)}` : ""}`,
6362
+ ` objects: ${r.fsck.checked} reachable, ${r.fsck.missing.length} missing`,
6363
+ " status: OK"
6364
+ ],
6365
+ exitCode: 0
6366
+ };
6367
+ case "corrupt":
6368
+ return {
6369
+ lines: [
6370
+ " checking remote repository...",
6371
+ ` op-log: chain ${r.fsck.chainOk ? "valid" : `BROKEN — ${r.fsck.chainError ?? "?"}`}`,
6372
+ ` objects: ${r.fsck.checked} reachable, ${r.fsck.missing.length} missing`,
6373
+ ...r.fsck.missing.map((h) => ` missing ${h}`),
6374
+ " status: PROBLEMS FOUND — the remote repository is corrupted"
6375
+ ],
6376
+ exitCode: 1
6377
+ };
6378
+ case "auth":
6379
+ return { lines: [` ${r.message}`], exitCode: 1 };
6380
+ case "backend":
6381
+ return { lines: [` ${r.message}`], exitCode: 1 };
6382
+ case "network":
6383
+ return { lines: [` ${r.message}`], exitCode: 1 };
6384
+ }
6385
+ }
6386
+ var init_admin_remote_verify = __esm(() => {
6387
+ init_errors_friendly();
6388
+ });
6389
+
5679
6390
  // src/bin/local-peer.ts
5680
6391
  var exports_local_peer = {};
5681
6392
  __export(exports_local_peer, {
@@ -5926,12 +6637,333 @@ var init_converge = __esm(() => {
5926
6637
  EMPTY_TREE2 = { kind: "tree", entries: {} };
5927
6638
  });
5928
6639
 
6640
+ // src/bin/sync-watch-fs.ts
6641
+ var exports_sync_watch_fs = {};
6642
+ __export(exports_sync_watch_fs, {
6643
+ buildFsSyncEnv: () => buildFsSyncEnv
6644
+ });
6645
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "node:fs";
6646
+ function resolveRemote() {
6647
+ const cfg = loadRemote(solDir);
6648
+ if (!cfg)
6649
+ return;
6650
+ return cfg.url ? cfg : { ...cfg, url: DEFAULT_REMOTE_URL };
6651
+ }
6652
+ function readRefs() {
6653
+ return existsSync13(refsPath()) ? JSON.parse(readFileSync13(refsPath(), "utf8")) : undefined;
6654
+ }
6655
+ async function buildFsSyncEnv(opts) {
6656
+ if (!existsSync13(solDir))
6657
+ throw new Error("not a sol repo — run `sol init` first");
6658
+ const { repo, log } = open();
6659
+ const cfg = resolveRemote() || (() => {
6660
+ throw new Error("no remote — set one with `sol remote <url> <repo>`");
6661
+ })();
6662
+ const token = opts.token;
6663
+ const replica = { store: new FileStore(solDir, objectsDir()), log };
6664
+ const currentBranch = () => readRefs()?.current ?? "main";
6665
+ const local = {
6666
+ replica,
6667
+ async capture() {
6668
+ const snap = await snapshotTree(repo);
6669
+ if (snap.changed)
6670
+ writeWorkingIndex(await repo.list());
6671
+ return snap.changed;
6672
+ },
6673
+ head: () => log.head(),
6674
+ seq: () => log.seq(),
6675
+ logTip: () => log.logTip(),
6676
+ history: () => log.history(),
6677
+ async nodes() {
6678
+ return allLocalNodes();
6679
+ },
6680
+ branch: currentBranch,
6681
+ forkBase() {
6682
+ const refs = readRefs();
6683
+ return refs?.branches[refs.current]?.remote;
6684
+ },
6685
+ async dirty() {
6686
+ const wc = workingChanges(loadStore(), await log.head() ?? "");
6687
+ return wc.added.length + wc.modified.length + wc.removed.length > 0;
6688
+ },
6689
+ async absorbCanonical(bundle, fromHead) {
6690
+ const head = bundle.head ?? fromHead;
6691
+ const canonBundle = {
6692
+ head,
6693
+ seq: bundle.ops.length ? bundle.ops[bundle.ops.length - 1].seq : 0,
6694
+ tip: bundle.ops.length ? bundle.ops[bundle.ops.length - 1].entryHash : undefined,
6695
+ ops: bundle.ops,
6696
+ nodes: bundle.nodes,
6697
+ refs: bundle.branches ? { branches: bundle.branches, production: readRefs()?.current ?? "main" } : undefined
6698
+ };
6699
+ await writeBundle(solDir, canonBundle, 0);
6700
+ await local.adopt(fromHead, head, bundle.branches);
6701
+ },
6702
+ async adopt(from, to, branches) {
6703
+ const refs = readRefs();
6704
+ if (refs) {
6705
+ if (branches) {
6706
+ for (const [name, h] of Object.entries(branches)) {
6707
+ refs.branches[name] = { head: refs.branches[name]?.head ?? h, base: refs.branches[name]?.base ?? h, remote: h };
6708
+ }
6709
+ }
6710
+ const cur = refs.current;
6711
+ if (refs.branches[cur]) {
6712
+ refs.branches[cur].head = to;
6713
+ refs.branches[cur].remote = to;
6714
+ }
6715
+ saveRefs(refs);
6716
+ }
6717
+ setOpLogHead(to);
6718
+ if (to && to !== from)
6719
+ materializeDiff(loadStore(), from, to);
6720
+ }
6721
+ };
6722
+ const remote = {
6723
+ async head() {
6724
+ const rh = await remoteHead(cfg, token);
6725
+ return { head: rh.head, tip: rh.tip, seq: rh.seq };
6726
+ },
6727
+ async export() {
6728
+ const b = await remoteExport(cfg, token);
6729
+ return { head: b.head, tip: b.tip, seq: b.seq, ops: b.ops, nodes: b.nodes, branches: b.refs?.branches };
6730
+ },
6731
+ async push(body) {
6732
+ const res = await remotePush(cfg, token, body);
6733
+ return { head: res.head, applied: res.applied, merged: res.merged, conflicts: res.conflicts, branches: res.refs?.branches };
6734
+ }
6735
+ };
6736
+ if (!loadRemote(solDir)?.url && cfg.repo)
6737
+ saveRemote(solDir, cfg);
6738
+ const logger = {
6739
+ info: (line) => console.log(line),
6740
+ warn: (line) => console.error("sol: " + line)
6741
+ };
6742
+ const runLocked = async (fn) => {
6743
+ const release = acquireLock();
6744
+ try {
6745
+ return await fn();
6746
+ } finally {
6747
+ release();
6748
+ }
6749
+ };
6750
+ return { local, remote, log: logger, actor, dryRun: !!opts.dryRun, runLocked };
6751
+ }
6752
+ var DEFAULT_REMOTE_URL;
6753
+ var init_sync_watch_fs = __esm(() => {
6754
+ init_file_store();
6755
+ init_lib();
6756
+ init_remote();
6757
+ DEFAULT_REMOTE_URL = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
6758
+ });
6759
+
6760
+ // src/bin/sync-watch.ts
6761
+ var exports_sync_watch = {};
6762
+ __export(exports_sync_watch, {
6763
+ runSyncWatch: () => runSyncWatch,
6764
+ pushTick: () => pushTick,
6765
+ pollTick: () => pollTick,
6766
+ isFatalSyncError: () => isFatalSyncError,
6767
+ formatTick: () => formatTick,
6768
+ captureTick: () => captureTick
6769
+ });
6770
+ function isFatalSyncError(e) {
6771
+ const m = (e instanceof Error ? e.message : String(e)).toLowerCase();
6772
+ if (/-> 401|-> 403|unauthor|forbidden|session expired|invalid token/.test(m))
6773
+ return true;
6774
+ if (/corrupt|broken chain|chain mismatch|not a sol repo|tamper/.test(m))
6775
+ return true;
6776
+ return false;
6777
+ }
6778
+ async function captureTick(env) {
6779
+ if (env.dryRun)
6780
+ return { changed: 0 };
6781
+ const changed = await env.local.capture();
6782
+ return { changed };
6783
+ }
6784
+ async function pushTick(env) {
6785
+ const { local, remote } = env;
6786
+ const localHead = await local.head();
6787
+ if (!localHead)
6788
+ return { pushed: 0, merged: false, conflicts: [] };
6789
+ const rh = await remote.head();
6790
+ const ops = await local.history();
6791
+ const localSeq = ops.length ? ops[ops.length - 1].seq : 0;
6792
+ const localTip = await local.logTip();
6793
+ if (localSeq === rh.seq && localTip === rh.tip)
6794
+ return { pushed: 0, merged: false, conflicts: [], head: localHead };
6795
+ const branch = local.branch();
6796
+ const forkBase = local.forkBase();
6797
+ const baseOp = forkBase ? ops.find((o) => o.rootAfter === forkBase) : undefined;
6798
+ const fromSeq = baseOp ? baseOp.seq : rh.seq;
6799
+ const delta = ops.filter((o) => o.seq > fromSeq);
6800
+ if (env.dryRun) {
6801
+ env.log.info(`[dry-run] would push ${delta.length} op(s) (branch ${branch} @ ${short2(localHead)})`);
6802
+ return { pushed: 0, merged: false, conflicts: [], head: localHead };
6803
+ }
6804
+ const res = await remote.push({ nodes: await local.nodes(), ops: delta, branch, head: localHead, expectedHead: forkBase });
6805
+ const canon = await remote.export();
6806
+ const convergedHead = res.head ?? canon.branches?.[branch] ?? localHead;
6807
+ await local.absorbCanonical({ head: convergedHead, ops: canon.ops, nodes: canon.nodes, branches: canon.branches }, localHead);
6808
+ return {
6809
+ pushed: res.applied,
6810
+ merged: !!res.merged,
6811
+ conflicts: (res.conflicts ?? []).map((c) => c.path),
6812
+ head: convergedHead
6813
+ };
6814
+ }
6815
+ async function pollTick(env) {
6816
+ const { local, remote } = env;
6817
+ const rh = await remote.head();
6818
+ const localTip = await local.logTip();
6819
+ const localSeq = await local.seq();
6820
+ if (rh.seq <= localSeq && rh.tip === localTip)
6821
+ return { pulled: 0, merged: false, conflicts: [], deferred: false };
6822
+ if (await local.dirty()) {
6823
+ env.log.info("remote moved but the working tree is dirty — pull deferred to the next poll (your edits captured first)");
6824
+ return { pulled: 0, merged: false, conflicts: [], deferred: true };
6825
+ }
6826
+ if (env.dryRun) {
6827
+ env.log.info(`[dry-run] would pull remote @ ${short2(rh.head)} (seq ${rh.seq})`);
6828
+ return { pulled: 0, merged: false, conflicts: [], deferred: false };
6829
+ }
6830
+ const bundle = await remote.export();
6831
+ const prevHead = await local.head() ?? "";
6832
+ const before = (await local.history()).length;
6833
+ const res = await converge(local.replica, {
6834
+ nodes: bundle.nodes,
6835
+ ops: bundle.ops,
6836
+ incomingHead: bundle.head,
6837
+ actor: env.actor
6838
+ });
6839
+ const after = (await local.history()).length;
6840
+ if (res.head !== prevHead)
6841
+ await local.adopt(prevHead, res.head, bundle.branches);
6842
+ return {
6843
+ pulled: after - before,
6844
+ merged: res.merged,
6845
+ conflicts: res.conflicts.map((c) => c.path),
6846
+ deferred: false,
6847
+ head: res.head
6848
+ };
6849
+ }
6850
+ function formatTick(parts) {
6851
+ const segs = [];
6852
+ if (parts.captured)
6853
+ segs.push(`auto-captured ${parts.captured} change${parts.captured === 1 ? "" : "s"}`);
6854
+ if (parts.push && parts.push.pushed) {
6855
+ const note = parts.push.merged ? " (converged)" : "";
6856
+ const conf = parts.push.conflicts.length ? ` [${parts.push.conflicts.length} conflict(s): ${parts.push.conflicts.join(", ")}]` : "";
6857
+ segs.push(`pushed ${parts.push.pushed} op${parts.push.pushed === 1 ? "" : "s"} -> remote${note}${conf}`);
6858
+ }
6859
+ if (parts.poll && parts.poll.pulled) {
6860
+ const note = parts.poll.merged ? " (merged)" : "";
6861
+ const conf = parts.poll.conflicts.length ? ` [${parts.poll.conflicts.length} conflict(s): ${parts.poll.conflicts.join(", ")}]` : "";
6862
+ segs.push(`pulled ${parts.poll.pulled} op${parts.poll.pulled === 1 ? "" : "s"} from remote${note}${conf}`);
6863
+ }
6864
+ if (!segs.length)
6865
+ return;
6866
+ const t = new Date().toLocaleTimeString();
6867
+ return `[${t}] ${segs.join(" | ")}`;
6868
+ }
6869
+ async function runSyncWatch(opts) {
6870
+ const { buildFsSyncEnv: buildFsSyncEnv2 } = await Promise.resolve().then(() => (init_sync_watch_fs(), exports_sync_watch_fs));
6871
+ const env = await buildFsSyncEnv2(opts);
6872
+ const { watch } = await import("node:fs");
6873
+ const { sep: sep2 } = await import("node:path");
6874
+ const { DEFAULT_IGNORE: DEFAULT_IGNORE2 } = await Promise.resolve().then(() => (init_lib(), exports_lib));
6875
+ const pollInterval = opts.pollInterval ?? 5000;
6876
+ const pushImmediate = opts.pushImmediate ?? true;
6877
+ let exiting = false;
6878
+ const fatal = (where, e) => {
6879
+ if (exiting)
6880
+ return;
6881
+ exiting = true;
6882
+ env.log.warn(`FATAL (${where}): ${e instanceof Error ? e.message : e} — exiting. fix it and re-run \`sol sync --watch\`.`);
6883
+ process.exit(1);
6884
+ };
6885
+ let running = false;
6886
+ let pending = false;
6887
+ const captureAndPush = async () => {
6888
+ if (running) {
6889
+ pending = true;
6890
+ return;
6891
+ }
6892
+ running = true;
6893
+ try {
6894
+ do {
6895
+ pending = false;
6896
+ try {
6897
+ const cap = await env.runLocked(async () => {
6898
+ const c = await captureTick(env);
6899
+ let push;
6900
+ if (c.changed && pushImmediate) {
6901
+ try {
6902
+ push = await pushTick(env);
6903
+ } catch (e) {
6904
+ if (isFatalSyncError(e))
6905
+ return fatal("push", e);
6906
+ env.log.warn(`push failed (will retry next cycle): ${e instanceof Error ? e.message : e}`);
6907
+ }
6908
+ }
6909
+ return { captured: c.changed, push };
6910
+ });
6911
+ const line = formatTick(cap);
6912
+ if (line)
6913
+ env.log.info(line);
6914
+ } catch (e) {
6915
+ if (isFatalSyncError(e))
6916
+ return fatal("capture", e);
6917
+ env.log.warn(`sync tick error (retrying): ${e?.message || e}`);
6918
+ }
6919
+ } while (pending);
6920
+ } finally {
6921
+ running = false;
6922
+ }
6923
+ };
6924
+ const pollOnce = async () => {
6925
+ try {
6926
+ const res = await env.runLocked(() => pollTick(env));
6927
+ const line = formatTick({ poll: res });
6928
+ if (line)
6929
+ env.log.info(line);
6930
+ } catch (e) {
6931
+ if (isFatalSyncError(e))
6932
+ return fatal("poll", e);
6933
+ env.log.warn(`poll failed (will retry in ${Math.round(pollInterval / 1000)}s): ${e?.message || e}`);
6934
+ }
6935
+ };
6936
+ await captureAndPush();
6937
+ env.log.info(`sol sync --watch on ${opts.repoDir}
6938
+ every edit is auto-captured + pushed${pushImmediate ? "" : " (push disabled — capture only)"}; safe remote updates pull in every ${Math.round(pollInterval / 1000)}s.
6939
+ ` + ` nothing is lost — all edits are durable locally first. Ctrl-C to stop.${opts.dryRun ? `
6940
+ [DRY RUN — logging intent, mutating nothing]` : ""}`);
6941
+ let timer;
6942
+ watch(opts.repoDir, { recursive: true }, (_e, filename) => {
6943
+ if (!filename)
6944
+ return;
6945
+ const top = String(filename).split(sep2)[0];
6946
+ if (top === ".sol" || DEFAULT_IGNORE2.includes(top))
6947
+ return;
6948
+ clearTimeout(timer);
6949
+ timer = setTimeout(() => void captureAndPush(), 400);
6950
+ });
6951
+ const poller = setInterval(() => void pollOnce(), pollInterval);
6952
+ poller.unref?.();
6953
+ await new Promise(() => {});
6954
+ }
6955
+ var short2 = (h) => (h ?? "").slice(0, 12);
6956
+ var init_sync_watch = __esm(() => {
6957
+ init_converge();
6958
+ });
6959
+
5929
6960
  // src/mr.ts
5930
6961
  var exports_mr = {};
5931
6962
  __export(exports_mr, {
5932
6963
  mrSummary: () => mrSummary,
5933
6964
  mergeGate: () => mergeGate,
5934
- latestVerdicts: () => latestVerdicts
6965
+ latestVerdicts: () => latestVerdicts,
6966
+ isAdvisoryGate: () => isAdvisoryGate
5935
6967
  });
5936
6968
  function latestVerdicts(mr) {
5937
6969
  const m = new Map;
@@ -5939,18 +6971,23 @@ function latestVerdicts(mr) {
5939
6971
  m.set(r.actor, r.verdict);
5940
6972
  return m;
5941
6973
  }
5942
- function mergeGate(mr) {
6974
+ function mergeGate(mr, branchGate) {
5943
6975
  const reasons = [];
5944
6976
  if (mr.status !== "open")
5945
6977
  reasons.push(`MR is ${mr.status}`);
5946
6978
  for (const c of mr.checks)
5947
6979
  if (c.status === "fail")
5948
6980
  reasons.push(`check '${c.name}' failed`);
6981
+ if (branchGate?.verdict === "fail")
6982
+ reasons.push(`branch head gate failed${branchGate.summary ? `: ${branchGate.summary}` : ""}`);
5949
6983
  for (const [actor2, v] of latestVerdicts(mr))
5950
6984
  if (v === "request-changes")
5951
6985
  reasons.push(`${actor2} requested changes`);
5952
6986
  return reasons;
5953
6987
  }
6988
+ function isAdvisoryGate(reason) {
6989
+ return reason.startsWith("check '") || reason.startsWith("branch head gate");
6990
+ }
5954
6991
  function mrSummary(mr) {
5955
6992
  const approvals = [...latestVerdicts(mr).values()].filter((v) => v === "approve").length;
5956
6993
  const checks = mr.checks.length ? `${mr.checks.filter((c) => c.status === "pass").length}/${mr.checks.length} checks pass` : "no checks";
@@ -5965,7 +7002,7 @@ __export(exports_runtime, {
5965
7002
  hydrate: () => hydrate2,
5966
7003
  capture: () => capture
5967
7004
  });
5968
- import { chmodSync as chmodSync4, existsSync as existsSync13, lstatSync as lstatSync2, mkdirSync as mkdirSync7, readdirSync as readdirSync6, readFileSync as readFileSync13, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
7005
+ import { chmodSync as chmodSync4, existsSync as existsSync14, lstatSync as lstatSync2, mkdirSync as mkdirSync7, readdirSync as readdirSync6, readFileSync as readFileSync14, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
5969
7006
  import { platform } from "node:os";
5970
7007
  import { dirname as dirname3, join as join13, relative as relative2 } from "node:path";
5971
7008
  function isolateCommand(command, scratch) {
@@ -6029,7 +7066,7 @@ async function capture(repo, dir, keep = []) {
6029
7066
  const pats = ignorePatterns();
6030
7067
  const files = new Set(walkDir(dir, dir, pats));
6031
7068
  for (const k of keep)
6032
- if (existsSync13(join13(dir, k)))
7069
+ if (existsSync14(join13(dir, k)))
6033
7070
  files.add(k);
6034
7071
  const written = [];
6035
7072
  for (const f of files) {
@@ -6044,7 +7081,7 @@ async function capture(repo, dir, keep = []) {
6044
7081
  }
6045
7082
  continue;
6046
7083
  }
6047
- const buf = readFileSync13(abs);
7084
+ const buf = readFileSync14(abs);
6048
7085
  if (buf.includes(0)) {
6049
7086
  const cur = await repo.readBytes(f);
6050
7087
  if (cur === undefined || cur === SEALED || !Buffer.from(cur).equals(buf)) {
@@ -6061,7 +7098,7 @@ async function capture(repo, dir, keep = []) {
6061
7098
  }
6062
7099
  const deleted = [];
6063
7100
  for (const t of await repo.list()) {
6064
- if (!existsSync13(join13(dir, t))) {
7101
+ if (!existsSync14(join13(dir, t))) {
6065
7102
  await repo.deleteFile(t);
6066
7103
  deleted.push(t);
6067
7104
  }
@@ -6175,7 +7212,7 @@ __export(exports_git_adapter, {
6175
7212
  exportHistoryToGit: () => exportHistoryToGit
6176
7213
  });
6177
7214
  import { execFileSync } from "node:child_process";
6178
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync14, unlinkSync as unlinkSync6, writeFileSync as writeFileSync13 } from "node:fs";
7215
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync15, unlinkSync as unlinkSync6, writeFileSync as writeFileSync13 } from "node:fs";
6179
7216
  import { join as join14 } from "node:path";
6180
7217
  import { deflateSync } from "node:zlib";
6181
7218
  async function importGitHead(gitPath, ws) {
@@ -6245,7 +7282,7 @@ async function applyGitFile(repo, path, mode, content) {
6245
7282
  }
6246
7283
  function setHead(fdir, head) {
6247
7284
  const hf = join14(fdir, "HEAD");
6248
- const cur = existsSync14(hf) ? JSON.parse(readFileSync14(hf, "utf8")) : { seq: 0 };
7285
+ const cur = existsSync15(hf) ? JSON.parse(readFileSync15(hf, "utf8")) : { seq: 0 };
6249
7286
  writeFileSync13(hf, JSON.stringify({ ...cur, head }));
6250
7287
  }
6251
7288
  async function importGitRepo(gitPath, fdir) {
@@ -6312,14 +7349,14 @@ function exportToGit(store2, head, gitPath, message) {
6312
7349
  }
6313
7350
  function gitObjectsDir(gitPath) {
6314
7351
  for (const c of [join14(gitPath, ".git", "objects"), join14(gitPath, "objects")])
6315
- if (existsSync14(c))
7352
+ if (existsSync15(c))
6316
7353
  return c;
6317
7354
  return;
6318
7355
  }
6319
7356
  function writeLooseObject(objectsDir2, o) {
6320
7357
  const dir = join14(objectsDir2, o.oid.slice(0, 2));
6321
7358
  const file = join14(dir, o.oid.slice(2));
6322
- if (existsSync14(file))
7359
+ if (existsSync15(file))
6323
7360
  return;
6324
7361
  const header = Buffer.from(`${o.type} ${o.content.length}\x00`);
6325
7362
  const framed = Buffer.concat([header, Buffer.from(o.content)]);
@@ -6436,14 +7473,14 @@ __export(exports_views, {
6436
7473
  loadViewsRegistry: () => loadViewsRegistry,
6437
7474
  createView: () => createView
6438
7475
  });
6439
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync15, rmSync, writeFileSync as writeFileSync14 } from "node:fs";
7476
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync16, rmSync, writeFileSync as writeFileSync14 } from "node:fs";
6440
7477
  import { join as join15, relative as relative3, resolve as resolve3 } from "node:path";
6441
7478
  function loadViewsRegistry(parentSol) {
6442
7479
  const p = viewsRegistryPath(parentSol);
6443
- if (!existsSync15(p))
7480
+ if (!existsSync16(p))
6444
7481
  return { views: [] };
6445
7482
  try {
6446
- return JSON.parse(readFileSync15(p, "utf8"));
7483
+ return JSON.parse(readFileSync16(p, "utf8"));
6447
7484
  } catch {
6448
7485
  return { views: [] };
6449
7486
  }
@@ -6476,7 +7513,7 @@ async function viewStatuses(parentSol) {
6476
7513
  const out = [];
6477
7514
  for (const v of reg.views) {
6478
7515
  const viewSol = join15(v.dir, ".sol");
6479
- const exists = existsSync15(join15(viewSol, "view.json"));
7516
+ const exists = existsSync16(join15(viewSol, "view.json"));
6480
7517
  let head;
6481
7518
  if (exists) {
6482
7519
  head = await new FileOpLog(viewSol).head();
@@ -6500,7 +7537,7 @@ function pruneViews(parentSol, opts = {}) {
6500
7537
  const reg = loadViewsRegistry(parentSol);
6501
7538
  const removed = [];
6502
7539
  reg.views = reg.views.filter((v) => {
6503
- const present = existsSync15(join15(v.dir, ".sol", "view.json"));
7540
+ const present = existsSync16(join15(v.dir, ".sol", "view.json"));
6504
7541
  const target = opts.name ? v.name === opts.name : !present;
6505
7542
  if (!target)
6506
7543
  return true;
@@ -6517,7 +7554,7 @@ function pruneViews(parentSol, opts = {}) {
6517
7554
  }
6518
7555
  function sharedObjectCount(parentSol) {
6519
7556
  const dir = join15(parentSol, "objects");
6520
- if (!existsSync15(dir))
7557
+ if (!existsSync16(dir))
6521
7558
  return 0;
6522
7559
  return readdirSync7(dir).filter((n) => !n.endsWith(".tmp")).length;
6523
7560
  }
@@ -6529,6 +7566,169 @@ var init_views = __esm(() => {
6529
7566
  init_lib();
6530
7567
  });
6531
7568
 
7569
+ // src/bin/agent-sessions.ts
7570
+ var exports_agent_sessions = {};
7571
+ __export(exports_agent_sessions, {
7572
+ stopSession: () => stopSession,
7573
+ sessions: () => sessions,
7574
+ sessionPath: () => sessionPath,
7575
+ sessionDir: () => sessionDir,
7576
+ saveSessionsRegistry: () => saveSessionsRegistry,
7577
+ pruneSession: () => pruneSession,
7578
+ loadSessionsRegistry: () => loadSessionsRegistry,
7579
+ fingerprintOfSeed: () => fingerprintOfSeed,
7580
+ createSession: () => createSession
7581
+ });
7582
+ import { chmodSync as chmodSync5, existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync17, rmSync as rmSync2, writeFileSync as writeFileSync15 } from "node:fs";
7583
+ import { randomBytes as randomBytes5 } from "node:crypto";
7584
+ import { basename, dirname as dirname4, join as join16, resolve as resolve4 } from "node:path";
7585
+ function sessionPath(parentSol) {
7586
+ return join16(parentSol, "sessions.json");
7587
+ }
7588
+ function sessionDir(parentSol, sessionId) {
7589
+ return join16(parentSol, "sessions", sessionId);
7590
+ }
7591
+ function loadSessionsRegistry(parentSol) {
7592
+ const p = sessionPath(parentSol);
7593
+ if (!existsSync17(p))
7594
+ return { sessions: [] };
7595
+ try {
7596
+ return JSON.parse(readFileSync17(p, "utf8"));
7597
+ } catch {
7598
+ return { sessions: [] };
7599
+ }
7600
+ }
7601
+ function saveSessionsRegistry(parentSol, reg) {
7602
+ writeFileSync15(sessionPath(parentSol), JSON.stringify(reg, null, 2));
7603
+ }
7604
+ function newSessionId(name) {
7605
+ const slug = name.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 24) || "agent";
7606
+ return `${slug}-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`;
7607
+ }
7608
+ function createSession(opts) {
7609
+ const { parentSol, name, actor: actor2, check, metadata } = opts;
7610
+ if (!existsSync17(parentSol))
7611
+ throw new Error(`not a sol repo: ${parentSol}`);
7612
+ const sessionId = opts.sessionId ?? newSessionId(name);
7613
+ const sdir = sessionDir(parentSol, sessionId);
7614
+ if (existsSync17(sdir)) {
7615
+ if (opts.onExistingKey)
7616
+ opts.onExistingKey(sessionId);
7617
+ else
7618
+ throw new Error(`session already exists: ${sessionId} (${sdir})`);
7619
+ }
7620
+ const repoRoot2 = dirname4(parentSol);
7621
+ const viewDir = opts.dir ? resolve4(opts.dir) : join16(dirname4(repoRoot2), `${basename(repoRoot2)}-${name}`);
7622
+ if (existsSync17(join16(viewDir, ".sol")))
7623
+ throw new Error(`already a sol repo/view: ${viewDir}`);
7624
+ const branch = `view/${name}`;
7625
+ const startHead = headOf(parentSol);
7626
+ createView({ parentSol, viewDir, name, branch, actor: actor2, startHead });
7627
+ const kp = generateKeypair();
7628
+ mkdirSync10(sdir, { recursive: true });
7629
+ const signingKeyPath = join16(sdir, "signing.key");
7630
+ writeFileSync15(signingKeyPath, kp.seed, { mode: 384 });
7631
+ try {
7632
+ chmodSync5(signingKeyPath, 384);
7633
+ } catch {}
7634
+ if (check) {
7635
+ const cfg = loadSolConfig(parentSol);
7636
+ cfg.check = check;
7637
+ saveSolConfig(parentSol, cfg);
7638
+ }
7639
+ const record = {
7640
+ sessionId,
7641
+ name,
7642
+ viewDir: resolve4(viewDir),
7643
+ branch,
7644
+ actor: actor2,
7645
+ signingKeyPath: resolve4(signingKeyPath),
7646
+ fingerprint: kp.fingerprint,
7647
+ startHead,
7648
+ createdAt: Date.now(),
7649
+ ...metadata ? { metadata } : {}
7650
+ };
7651
+ const reg = loadSessionsRegistry(parentSol);
7652
+ reg.sessions = reg.sessions.filter((s) => s.sessionId !== sessionId);
7653
+ reg.sessions.push(record);
7654
+ saveSessionsRegistry(parentSol, reg);
7655
+ return record;
7656
+ }
7657
+ function headOf(parentSol) {
7658
+ const headFile = join16(parentSol, "HEAD");
7659
+ if (existsSync17(headFile)) {
7660
+ try {
7661
+ const h = JSON.parse(readFileSync17(headFile, "utf8"));
7662
+ if (h.head)
7663
+ return h.head;
7664
+ } catch {}
7665
+ }
7666
+ return emptyRoot(new LazyStore(join16(parentSol, "objects")));
7667
+ }
7668
+ function matches(s, ref) {
7669
+ return s.sessionId === ref || s.name === ref;
7670
+ }
7671
+ function stopSession(parentSol, ref) {
7672
+ const reg = loadSessionsRegistry(parentSol);
7673
+ const s = reg.sessions.find((x) => x.sessionId === ref) ?? reg.sessions.find((x) => matches(x, ref));
7674
+ if (!s)
7675
+ return false;
7676
+ if (!s.stopAt) {
7677
+ s.stopAt = Date.now();
7678
+ saveSessionsRegistry(parentSol, reg);
7679
+ }
7680
+ return true;
7681
+ }
7682
+ async function sessions(parentSol) {
7683
+ const reg = loadSessionsRegistry(parentSol);
7684
+ const out = [];
7685
+ for (const s of reg.sessions) {
7686
+ const viewSol = join16(s.viewDir, ".sol");
7687
+ const exists = existsSync17(join16(viewSol, "view.json"));
7688
+ let head;
7689
+ if (exists)
7690
+ head = await new FileOpLog(viewSol).head();
7691
+ const state = !exists ? "stale" : s.stopAt ? "stopped" : "running";
7692
+ out.push({ ...s, head, exists, state });
7693
+ }
7694
+ return out;
7695
+ }
7696
+ function pruneSession(parentSol, ref, deleteDir = false) {
7697
+ const reg = loadSessionsRegistry(parentSol);
7698
+ const removed = [];
7699
+ const drop = [];
7700
+ reg.sessions = reg.sessions.filter((s) => {
7701
+ const present = existsSync17(join16(s.viewDir, ".sol", "view.json"));
7702
+ const target = ref ? matches(s, ref) : !present;
7703
+ if (!target)
7704
+ return true;
7705
+ drop.push(s);
7706
+ removed.push(s.sessionId);
7707
+ return false;
7708
+ });
7709
+ for (const s of drop) {
7710
+ try {
7711
+ pruneViews(parentSol, { name: s.name, deleteDir });
7712
+ } catch {}
7713
+ try {
7714
+ rmSync2(sessionDir(parentSol, s.sessionId), { recursive: true, force: true });
7715
+ } catch {}
7716
+ }
7717
+ saveSessionsRegistry(parentSol, reg);
7718
+ return removed;
7719
+ }
7720
+ function fingerprintOfSeed(seed) {
7721
+ return fingerprintOf(publicOf(loadPrivateKey(seed)));
7722
+ }
7723
+ var init_agent_sessions = __esm(() => {
7724
+ init_file_store();
7725
+ init_sign();
7726
+ init_tree();
7727
+ init_lib();
7728
+ init_test_gate();
7729
+ init_views();
7730
+ });
7731
+
6532
7732
  // ../vault-sdk/src/patterns.ts
6533
7733
  function suggestEnvName(baseName) {
6534
7734
  return ENV_NAME_MAP[baseName] ?? baseName;
@@ -6961,7 +8161,7 @@ __export(exports_secret2, {
6961
8161
  runEnv: () => runEnv,
6962
8162
  resolveReference: () => resolveReference
6963
8163
  });
6964
- import { readFileSync as readFileSync16, readSync, writeSync } from "node:fs";
8164
+ import { readFileSync as readFileSync18, readSync, writeSync } from "node:fs";
6965
8165
  import { spawnSync as spawnSync2 } from "node:child_process";
6966
8166
  function flag(args, name) {
6967
8167
  const i = args.indexOf(name);
@@ -7333,7 +8533,7 @@ async function envValidate(ctx, args, json) {
7333
8533
  }
7334
8534
  function readSchemaText(solDir2) {
7335
8535
  try {
7336
- return readFileSync16(schemaLockPath(solDir2), "utf8");
8536
+ return readFileSync18(schemaLockPath(solDir2), "utf8");
7337
8537
  } catch {
7338
8538
  return;
7339
8539
  }
@@ -8015,8 +9215,8 @@ var exports_sol_secret_mcp = {};
8015
9215
  __export(exports_sol_secret_mcp, {
8016
9216
  startSecretMcp: () => startSecretMcp
8017
9217
  });
8018
- import { existsSync as existsSync16 } from "node:fs";
8019
- import { join as join16 } from "node:path";
9218
+ import { existsSync as existsSync18 } from "node:fs";
9219
+ import { join as join17 } from "node:path";
8020
9220
  async function buildSecretServer(solDir2) {
8021
9221
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
8022
9222
  const { CallToolRequestSchema, ListToolsRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
@@ -8052,8 +9252,8 @@ async function buildSecretServer(solDir2) {
8052
9252
  return server;
8053
9253
  }
8054
9254
  async function startSecretMcp(opts = {}) {
8055
- const solDir2 = opts.solDir || process.env.SOL_DIR || join16(process.cwd(), ".sol");
8056
- if (!existsSync16(solDir2)) {
9255
+ const solDir2 = opts.solDir || process.env.SOL_DIR || join17(process.cwd(), ".sol");
9256
+ if (!existsSync18(solDir2)) {
8057
9257
  process.stderr.write(`sol-secret-mcp: no .sol at ${solDir2} — run \`sol init\` first (or set SOL_DIR)
8058
9258
  `);
8059
9259
  process.exit(1);
@@ -8097,6 +9297,9 @@ class SolWorkspace {
8097
9297
  this.actor = actor2;
8098
9298
  this.repo = new AsyncRepo(store2, log, actor2);
8099
9299
  }
9300
+ setToolCall(tc) {
9301
+ this.repo.setToolCall(tc);
9302
+ }
8100
9303
  async write(path, content) {
8101
9304
  return this.repo.writeFile(safePath(path), content);
8102
9305
  }
@@ -8188,9 +9391,9 @@ __export(exports_dispatch, {
8188
9391
  dispatch: () => dispatch
8189
9392
  });
8190
9393
  import { execFileSync as execFileSync2 } from "node:child_process";
8191
- import { existsSync as existsSync17, mkdirSync as mkdirSync10, mkdtempSync, readdirSync as readdirSync8, readFileSync as readFileSync17, rmSync as rmSync2, unlinkSync as unlinkSync7, watch, writeFileSync as writeFileSync15 } from "node:fs";
9394
+ import { accessSync, constants as fsConstants, existsSync as existsSync19, mkdirSync as mkdirSync11, mkdtempSync, readdirSync as readdirSync8, readFileSync as readFileSync19, rmSync as rmSync3, unlinkSync as unlinkSync7, watch, writeFileSync as writeFileSync16 } from "node:fs";
8192
9395
  import { homedir as homedir2, hostname, platform as platform2, tmpdir } from "node:os";
8193
- import { basename, dirname as dirname4, join as join17, resolve as resolve4, sep as sep2 } from "node:path";
9396
+ import { basename as basename2, dirname as dirname5, join as join18, resolve as resolve5, sep as sep2 } from "node:path";
8194
9397
  function globCovers(pattern, path) {
8195
9398
  let re = "";
8196
9399
  for (let i = 0;i < pattern.length; i++) {
@@ -8210,6 +9413,14 @@ function globCovers(pattern, path) {
8210
9413
  }
8211
9414
  return new RegExp(`^${re}$`).test(path);
8212
9415
  }
9416
+ function writableTag(path) {
9417
+ try {
9418
+ accessSync(path, fsConstants.R_OK | fsConstants.W_OK);
9419
+ return "RW";
9420
+ } catch {
9421
+ return "READ-ONLY";
9422
+ }
9423
+ }
8213
9424
  function tokenClaims(token) {
8214
9425
  try {
8215
9426
  return JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString());
@@ -8218,11 +9429,11 @@ function tokenClaims(token) {
8218
9429
  }
8219
9430
  }
8220
9431
  async function loadStoredToken() {
8221
- if (!existsSync17(CRED_PATH))
9432
+ if (!existsSync19(CRED_PATH))
8222
9433
  return;
8223
9434
  let creds;
8224
9435
  try {
8225
- creds = JSON.parse(readFileSync17(CRED_PATH, "utf8"));
9436
+ creds = JSON.parse(readFileSync19(CRED_PATH, "utf8"));
8226
9437
  } catch {
8227
9438
  return;
8228
9439
  }
@@ -8237,7 +9448,7 @@ async function loadStoredToken() {
8237
9448
  if (res.ok) {
8238
9449
  const r = await res.json();
8239
9450
  if (r.accessToken) {
8240
- writeFileSync15(CRED_PATH, JSON.stringify({ ...creds, accessToken: r.accessToken, refreshToken: r.refreshToken ?? creds.refreshToken }, null, 2), { mode: 384 });
9451
+ writeFileSync16(CRED_PATH, JSON.stringify({ ...creds, accessToken: r.accessToken, refreshToken: r.refreshToken ?? creds.refreshToken }, null, 2), { mode: 384 });
8241
9452
  return r.accessToken;
8242
9453
  }
8243
9454
  }
@@ -8258,7 +9469,7 @@ function authHost() {
8258
9469
  if (process.env.SOL_AUTH)
8259
9470
  return process.env.SOL_AUTH.replace(/\/+$/, "");
8260
9471
  try {
8261
- const w = JSON.parse(readFileSync17(CRED_PATH, "utf8")).webUrl;
9472
+ const w = JSON.parse(readFileSync19(CRED_PATH, "utf8")).webUrl;
8262
9473
  if (w)
8263
9474
  return w.replace(/\/+$/, "");
8264
9475
  } catch {}
@@ -8282,11 +9493,11 @@ async function resolveMcpHttp(a, label) {
8282
9493
  die(`invalid --port: ${portRaw}`);
8283
9494
  return { token, port, host: flagVal("--host"), label: label ?? (a.includes("--secret") || a.includes("--secrets") ? "sol-secrets" : "sol") };
8284
9495
  }
8285
- function resolveRemote(solDir2) {
9496
+ function resolveRemote2(solDir2) {
8286
9497
  const cfg = loadRemote(solDir2);
8287
9498
  if (!cfg)
8288
9499
  return;
8289
- return cfg.url ? cfg : { ...cfg, url: DEFAULT_REMOTE_URL };
9500
+ return cfg.url ? cfg : { ...cfg, url: DEFAULT_REMOTE_URL2 };
8290
9501
  }
8291
9502
  async function pushEnvState(solDirPath, cfg, token) {
8292
9503
  const { readEnvStateBundle: readEnvStateBundle2 } = await Promise.resolve().then(() => (init_anchor(), exports_anchor));
@@ -8305,6 +9516,17 @@ ENV ANCHOR REJECTED by the remote: ${e?.message ?? String(e)}
8305
9516
  process.exitCode = 1;
8306
9517
  }
8307
9518
  }
9519
+ async function surfaceOwner(cfg, token) {
9520
+ const slash = cfg.repo.indexOf("/");
9521
+ if (slash < 0)
9522
+ return;
9523
+ const owner = cfg.repo.slice(0, slash);
9524
+ try {
9525
+ const res = await ownerSet(cfg, token, owner);
9526
+ if (res.error)
9527
+ console.error(`note: could not set repo owner to '${owner}': ${res.error}`);
9528
+ } catch {}
9529
+ }
8308
9530
  async function pullEnvState(solDirPath, cfg, token) {
8309
9531
  try {
8310
9532
  const { bundle } = await remoteEnvPull(cfg, token);
@@ -8337,7 +9559,7 @@ function cliVersion() {
8337
9559
  if (typeof __SOL_COMPILED_VERSION__ === "string" && __SOL_COMPILED_VERSION__)
8338
9560
  return __SOL_COMPILED_VERSION__;
8339
9561
  try {
8340
- return JSON.parse(readFileSync17(new URL("./package.json", import.meta.url), "utf8")).version || "dev";
9562
+ return JSON.parse(readFileSync19(new URL("./package.json", import.meta.url), "utf8")).version || "dev";
8341
9563
  } catch {
8342
9564
  return "dev";
8343
9565
  }
@@ -8384,7 +9606,7 @@ examples:
8384
9606
  sol commit "add login route"
8385
9607
  sol commit -m "fix parser" src/parse.ts src/lex.ts`,
8386
9608
  push: `sol push push the current branch to the configured remote (converging — never FF-rejected)
8387
- sol push <repo> if no remote is set, use the hosted Sol (${DEFAULT_REMOTE_URL}) + this repo name, then push
9609
+ sol push <repo> if no remote is set, use the hosted Sol (${DEFAULT_REMOTE_URL2}) + this repo name, then push
8388
9610
  sol push --create <repo> same, explicit form (creates the repo on first push)
8389
9611
  sol push --public <repo> create + push + make it public in ONE step (else new repos are private)
8390
9612
 
@@ -8407,6 +9629,21 @@ examples:
8407
9629
  notes:
8408
9630
  refuses to run over a dirty tree — commit or discard first. conflicts land in the working tree with markers.
8409
9631
  refuses to run while UNRESOLVED <<<<<<< markers remain — resolve them and \`sol commit\`, then pull again.`,
9632
+ sync: `sol sync --watch HANDS-OFF bidirectional sync daemon — auto-capture + push every edit,
9633
+ and converge safe remote updates in on a timer. no CLI invocations needed:
9634
+ the working tree stays in sync while every local edit is durably captured.
9635
+ --poll-interval <ms> how often to poll the remote for updates (default 5000)
9636
+ --no-push-immediately capture locally but do NOT auto-push (push later with \`sol push\`)
9637
+ --dry-run log what it WOULD do, mutate nothing
9638
+
9639
+ how it works (three loops over the same core as watch/push/pull):
9640
+ 1. every file change is auto-captured into the op-log (nothing lost, even offline)
9641
+ 2. after each capture it pushes the delta; the server may converge a concurrent commit in
9642
+ 3. it polls the remote; a moved head pulls in via converge() — NEVER clobbering an in-flight edit
9643
+ notes:
9644
+ needs a remote (\`sol remote <url> <repo>\` or \`sol push <repo>\` once) + auth (\`sol auth login\` / SOL_TOKEN).
9645
+ a dirty tree DEFERS a pull to the next poll (your edits are captured first). Ctrl-C exits cleanly.
9646
+ transient network errors are logged + retried; a fatal error (corrupt repo / auth) exits loud.`,
8410
9647
  hide: `sol hide <pattern> [--role write|admin] [--team <id>] [--users a,b] [--escrow] [--no-list] [--hide-names] [--hide-existence] [--strict]
8411
9648
  add/update a VisibilityPolicy RULE — the HEADLINE verb. binds a glob over tree
8412
9649
  paths to an AUDIENCE (who may DECRYPT). the rule is host-visible METADATA (no
@@ -8520,6 +9757,9 @@ examples:
8520
9757
  sol keys verify alice 1a2b-3c4d-... # confirm alice's key out-of-band before sealing to her`,
8521
9758
  remote: `sol remote show the configured remote
8522
9759
  sol remote <url> <repo> set the remote (url + repo name)
9760
+ sol remote verify REMOTE-side health check (the backend's /fsck) — differentiates auth
9761
+ (session expired / no access), backend (5xx, transient), connectivity,
9762
+ and a 200-but-corrupt remote repo. needs SOL_TOKEN (or \`sol auth login\`).
8523
9763
 
8524
9764
  tip: \`sol push <repo>\` configures the hosted remote for you in one step.`,
8525
9765
  branch: `sol branch list branches (* = current)
@@ -8584,6 +9824,27 @@ what it's for (TEST-GATED CONVERGENCE):
8584
9824
  hydrate the current tree to a sandbox, run the command, capture produced files back into a commit.
8585
9825
  --isolate confines the run (no network, writes stay in the sandbox).`,
8586
9826
  promote: "sol promote [branch] point the remote's production branch at <branch> (default: current branch)",
9827
+ doctor: `sol doctor — comprehensive LOCAL health check (exit 0 = OK, 1 = problems found)
9828
+
9829
+ checks: op-log chain integrity + contiguity, object reachability from head, auth (SOL_TOKEN / stored
9830
+ credentials + expiry), .sol file permissions, and the sealed keyring. read-only — it diagnoses but never
9831
+ changes anything. to RECOVER from what it finds: \`sol fsck --repair\` (op-log/head) or \`sol recover\` (cloud/backup).`,
9832
+ fsck: `sol fsck verify the op-log chain + object reachability (exit 0 = OK, 1 = problems)
9833
+ sol fsck --repair recover a corrupt repo:
9834
+ - head pointer lost -> re-point to the last resolvable historical root
9835
+ - head dangling -> re-point to the newest root the store still has
9836
+ - op-log chain broken -> truncate to the last verified link, re-point head
9837
+
9838
+ every repair appends an explicit CHECKPOINT recording what happened (visible in \`sol log\`/\`sol reflog\`),
9839
+ so the recovery is itself in the tamper-evident history. idempotent: re-running on a healthy repo is a no-op.`,
9840
+ recover: `sol recover the recovery guide (the menu below)
9841
+ sol recover --from-remote <url> <repo> rehydrate from the cloud
9842
+ sol recover --list-all-roots every root the repo ever landed on (find a lost head)
9843
+ sol recover --file <path> [--from <seq|hash>] restore a file from history (even if the tree dropped it)
9844
+ sol recover --dump-log [<from> [<to>]] dump raw op-log entries as JSON (manual salvage)`,
9845
+ reflog: `sol reflog [<branch>] [--by <actor>] [--since <YYYY-MM-DD>]
9846
+ the op-log as a ref-move history (git reflog): each line is a head move (from -> to) with seq, actor, time,
9847
+ and message. a merge shows as "merge". a <branch> arg keeps only checkpoints whose message labels it.`,
8587
9848
  git: `sol git import <repo> [dir] import a git repo's HEAD into a new Sol repo
8588
9849
  sol git export <repo> [-b branch] replay the Sol commit DAG as git history (+ provenance trailers), then \`git push\``,
8589
9850
  mr: `sol mr open [--from <branch>] [--to <branch>] [--upstream <repo>] -t "title" [-m body]
@@ -8873,7 +10134,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
8873
10134
  try {
8874
10135
  const { loadSelfIdentity: loadSelfIdentity2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
8875
10136
  const { openContent: openContent2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
8876
- const ring = existsSync17(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
10137
+ const ring = existsSync19(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
8877
10138
  const self = loadSelfIdentity2();
8878
10139
  setSealedDecryptor((boxStr) => {
8879
10140
  const box = JSON.parse(boxStr);
@@ -8883,19 +10144,19 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
8883
10144
  } catch {}
8884
10145
  }
8885
10146
  const servesMcp = cmd === "mcp" || cmd === "secret" && args[0] === "mcp";
8886
- 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) && existsSync17(solDir) ? acquireLock() : undefined;
10147
+ 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) && existsSync19(solDir) ? acquireLock() : undefined;
8887
10148
  try {
8888
10149
  switch (cmd) {
8889
10150
  case "init": {
8890
- const here = join17(procCwd, ".sol");
8891
- if (existsSync17(here))
10151
+ const here = join18(procCwd, ".sol");
10152
+ if (existsSync19(here))
8892
10153
  die("already a sol repo: " + procCwd);
8893
10154
  if (repoRoot && repoRoot !== procCwd && !args.includes("--force")) {
8894
10155
  die(`already inside a Sol repo at ${repoRoot}
8895
10156
  -> just commit into it: \`sol commit ...\` works from here (sol walks up to find the repo)
8896
10157
  -> to nest a NEW repo here anyway: \`sol init --force\``);
8897
10158
  }
8898
- mkdirSync10(here, { recursive: true });
10159
+ mkdirSync11(here, { recursive: true });
8899
10160
  new FileStore(here);
8900
10161
  console.log(`initialized empty sol repo in ${here}`);
8901
10162
  break;
@@ -8925,7 +10186,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
8925
10186
  const h = tok ? identityFromToken(tok)?.handle : undefined;
8926
10187
  return h || die("no account identity — run `sol auth login` (or set SOL_ACCOUNT for self-host)");
8927
10188
  };
8928
- const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL).replace(/\/+$/, "");
10189
+ const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
8929
10190
  const sub = args[0];
8930
10191
  const wantJson = args.includes("--json");
8931
10192
  if (sub === "init") {
@@ -9011,7 +10272,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9011
10272
  if (sub === "import") {
9012
10273
  const passphrase = process.env.SOL_KEYSTORE_PASSPHRASE || die("set SOL_KEYSTORE_PASSPHRASE to decrypt the bundle");
9013
10274
  const file = args[1] || die("usage: sol keys import <bundle.json> (set SOL_KEYSTORE_PASSPHRASE)");
9014
- const bundle = JSON.parse(readFileSync17(file, "utf8"));
10275
+ const bundle = JSON.parse(readFileSync19(file, "utf8"));
9015
10276
  let recovered;
9016
10277
  try {
9017
10278
  recovered = importBundle2(bundle, passphrase);
@@ -9042,7 +10303,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9042
10303
  break;
9043
10304
  }
9044
10305
  case "trust": {
9045
- if (!existsSync17(solDir))
10306
+ if (!existsSync19(solDir))
9046
10307
  die("not a sol repo — run `sol init` first");
9047
10308
  const map = loadTrust();
9048
10309
  if (args[0] === "--remove" || args[0] === "-r") {
@@ -9089,7 +10350,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9089
10350
  }
9090
10351
  case "track":
9091
10352
  case "add": {
9092
- if (!existsSync17(solDir))
10353
+ if (!existsSync19(solDir))
9093
10354
  die("not a sol repo — run `sol init` first");
9094
10355
  const files = args.filter((a) => a !== "." && !a.startsWith("-"));
9095
10356
  if (!files.length) {
@@ -9100,7 +10361,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9100
10361
  let n = 0;
9101
10362
  for (const f of files) {
9102
10363
  const rf = repoRel(f);
9103
- if (!existsSync17(join17(cwd, rf))) {
10364
+ if (!existsSync19(join18(cwd, rf))) {
9104
10365
  console.error("skip (not on disk): " + f);
9105
10366
  continue;
9106
10367
  }
@@ -9129,14 +10390,14 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9129
10390
  if (!message)
9130
10391
  die('commit needs a message: sol commit "what you did" (scoped: sol commit -m "msg" file1 file2)');
9131
10392
  const parentHead = await repo.head();
9132
- const mergeHeadPath = join17(solDir, "MERGE_HEAD");
9133
- const parent2 = existsSync17(mergeHeadPath) ? readFileSync17(mergeHeadPath, "utf8").trim() || undefined : undefined;
10393
+ const mergeHeadPath = join18(solDir, "MERGE_HEAD");
10394
+ const parent2 = existsSync19(mergeHeadPath) ? readFileSync19(mergeHeadPath, "utf8").trim() || undefined : undefined;
9134
10395
  let changed = 0;
9135
10396
  let commitRoot = parentHead;
9136
10397
  if (paths.length) {
9137
10398
  for (const p of paths) {
9138
10399
  const rp = repoRel(p);
9139
- if (existsSync17(join17(cwd, rp))) {
10400
+ if (existsSync19(join18(cwd, rp))) {
9140
10401
  if (await snapshotFile(repo, rp))
9141
10402
  changed++;
9142
10403
  } else if ((await repo.list()).includes(rp)) {
@@ -9191,7 +10452,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9191
10452
  }
9192
10453
  case "status": {
9193
10454
  const { repo, log } = open();
9194
- const refs = existsSync17(refsPath()) ? await loadRefs(log) : undefined;
10455
+ const refs = existsSync19(refsPath()) ? await loadRefs(log) : undefined;
9195
10456
  const head = await repo.head();
9196
10457
  const headOp = head ? [...await log.history()].reverse().find((o) => o.rootAfter === head) : undefined;
9197
10458
  const headBy = headOp?.by ?? "?";
@@ -9281,7 +10542,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9281
10542
  const { listAll: listAll3 } = await Promise.resolve().then(() => (init_tree(), exports_tree));
9282
10543
  const { loadSelfIdentity: loadSelfIdentity2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
9283
10544
  const { openContent: openContent2, UNREADABLE: UNREADABLE2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
9284
- const ring = existsSync17(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
10545
+ const ring = existsSync19(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
9285
10546
  const self = loadSelfIdentity2();
9286
10547
  const decrypt = (boxStr) => {
9287
10548
  try {
@@ -9329,7 +10590,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9329
10590
  const { readFile: readTree, entryKindAt: kindAt } = await Promise.resolve().then(() => (init_tree(), exports_tree));
9330
10591
  const { loadSelfIdentity: loadSelfIdentity2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
9331
10592
  const { openContent: openContent2, UNREADABLE: UNREADABLE2, KeyRing: KeyRing3 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
9332
- const ring = existsSync17(solDir) ? loadKeyRing() : new KeyRing3;
10593
+ const ring = existsSync19(solDir) ? loadKeyRing() : new KeyRing3;
9333
10594
  const self = loadSelfIdentity2();
9334
10595
  const decrypt = (boxStr) => {
9335
10596
  try {
@@ -9428,7 +10689,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9428
10689
  };
9429
10690
  const live = await log.head() ?? "";
9430
10691
  const refArg = args.find((a) => !a.startsWith("-"));
9431
- const lrefs = existsSync17(refsPath()) ? JSON.parse(readFileSync17(refsPath(), "utf8")) : null;
10692
+ const lrefs = existsSync19(refsPath()) ? JSON.parse(readFileSync19(refsPath(), "utf8")) : null;
9432
10693
  let tipRoot = live;
9433
10694
  if (refArg) {
9434
10695
  tipRoot = lrefs?.branches[refArg]?.head ?? refArg;
@@ -9481,7 +10742,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9481
10742
  const path = args[0] || die("rm needs a path");
9482
10743
  let onDisk = false;
9483
10744
  try {
9484
- unlinkSync7(join17(cwd, path));
10745
+ unlinkSync7(join18(cwd, path));
9485
10746
  onDisk = true;
9486
10747
  } catch {}
9487
10748
  if (onDisk) {
@@ -9679,15 +10940,167 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9679
10940
  };
9680
10941
  walk(head);
9681
10942
  }
9682
- const ok = chainOk && missing.length === 0;
10943
+ const headLost = head === undefined && ops.length > 0;
10944
+ const ok = chainOk && missing.length === 0 && !headLost;
10945
+ if (args.includes("--repair")) {
10946
+ if (ok) {
10947
+ console.log("fsck --repair: nothing to repair — the repo is OK");
10948
+ process.exitCode = 0;
10949
+ break;
10950
+ }
10951
+ const { applyRepair: applyRepair2 } = await Promise.resolve().then(() => (init_admin_repair(), exports_admin_repair));
10952
+ const { FileStore: FileStore2 } = await Promise.resolve().then(() => (init_file_store(), exports_file_store));
10953
+ const fileStore = new FileStore2(solDir, objectsDir());
10954
+ const plan = await applyRepair2({
10955
+ store: fileStore,
10956
+ log,
10957
+ truncate: async (toSeq) => truncateOpLog(toSeq),
10958
+ setHead: async (h) => setOpLogHead(h)
10959
+ }, "sol-doctor");
10960
+ console.log(`fsck --repair: ${plan.reason}`);
10961
+ if (plan.kind === "noop")
10962
+ console.log(" (already healthy — nothing changed)");
10963
+ else if (plan.kind === "unfixable") {
10964
+ console.log(" status: UNFIXABLE — local repair cannot recover this; run `sol recover --from-remote <url> <repo>` to rehydrate");
10965
+ process.exitCode = 1;
10966
+ break;
10967
+ } else {
10968
+ console.log(` ${plan.note}`);
10969
+ console.log(" status: repaired — run `sol status` (or `sol fsck`) to verify");
10970
+ }
10971
+ process.exitCode = 0;
10972
+ break;
10973
+ }
9683
10974
  console.log(`fsck: ${ok ? "OK" : "PROBLEMS FOUND"}`);
9684
10975
  console.log(` op-log: ${ops.length} op(s), chain ${chainOk ? "valid + contiguous" : "BROKEN — " + chainErr}`);
9685
- console.log(` head: ${head ? head.slice(0, 16) : "(empty)"}, ${missing.length} missing object(s)`);
10976
+ if (headLost)
10977
+ console.log(` head: LOST (${ops.length} op(s) but no head pointer) — run \`sol fsck --repair\``);
10978
+ else
10979
+ console.log(` head: ${head ? head.slice(0, 16) : "(empty)"}, ${missing.length} missing object(s)`);
9686
10980
  for (const mh of missing)
9687
10981
  console.log(" missing " + mh);
10982
+ if (!ok)
10983
+ console.log(" -> `sol doctor` for a full diagnosis, `sol fsck --repair` to recover");
9688
10984
  process.exitCode = ok ? 0 : 1;
9689
10985
  break;
9690
10986
  }
10987
+ case "doctor": {
10988
+ const { runDoctor: runDoctor2, renderDoctorIntegrity: renderDoctorIntegrity2 } = await Promise.resolve().then(() => (init_admin(), exports_admin));
10989
+ const { log } = open();
10990
+ const store2 = loadStore();
10991
+ const asyncStore = { get: async (h) => store2.get(h), put: async () => die("doctor is read-only"), has: async (h) => store2.has(h) };
10992
+ const report = await runDoctor2(asyncStore, log);
10993
+ console.log("sol doctor");
10994
+ for (const l of renderDoctorIntegrity2(report))
10995
+ console.log(l);
10996
+ const envTok = process.env.SOL_TOKEN;
10997
+ if (envTok) {
10998
+ const id = identityFromToken(envTok);
10999
+ console.log(` auth: SOL_TOKEN set${id?.handle ? ` (@${id.handle})` : id?.email ? ` (${id.email})` : ""}`);
11000
+ } else if (existsSync19(CRED_PATH)) {
11001
+ try {
11002
+ const creds = JSON.parse(readFileSync19(CRED_PATH, "utf8"));
11003
+ const c = creds.accessToken ? tokenClaims(creds.accessToken) : {};
11004
+ const exp = typeof c.exp === "number" ? new Date(c.exp * 1000).toISOString().slice(0, 10) : "?";
11005
+ const stale = typeof c.exp === "number" && c.exp * 1000 <= Date.now();
11006
+ console.log(` auth: logged in${c.handle ? ` as @${c.handle}` : ""}${stale ? " (EXPIRED — run `sol auth login`)" : ` (expires ${exp})`}`);
11007
+ } catch {
11008
+ console.log(" auth: stored credentials unreadable — run `sol auth login`");
11009
+ }
11010
+ } else {
11011
+ console.log(" auth: not logged in — `sol auth login` (or set SOL_TOKEN) for remote commands");
11012
+ }
11013
+ const objDir = join18(solDir, "objects");
11014
+ const opsFile = join18(solDir, "ops.jsonl");
11015
+ console.log(` files: .sol/objects/ ${writableTag(objDir)}, .sol/ops.jsonl ${existsSync19(opsFile) ? writableTag(opsFile) : "(none yet)"}`);
11016
+ const keyringPath = join18(homedir2(), ".sol", "keyring.json");
11017
+ console.log(` sealed: ${existsSync19(keyringPath) ? "keyring found, ready" : "no keyring (only needed for sealed paths)"}`);
11018
+ console.log(`status: ${report.ok ? "OK" : "PROBLEMS FOUND"}`);
11019
+ if (!report.ok)
11020
+ console.log(" -> `sol fsck --repair` to recover the op-log/head; `sol recover` for cloud/backup rehydrate");
11021
+ process.exitCode = report.ok ? 0 : 1;
11022
+ break;
11023
+ }
11024
+ case "reflog": {
11025
+ const { renderReflog: renderReflog2 } = await Promise.resolve().then(() => exports_admin_reflog);
11026
+ const { log } = open();
11027
+ const ops = await log.history();
11028
+ const flag2 = (name) => {
11029
+ const i = args.indexOf(name);
11030
+ return i >= 0 ? args[i + 1] : undefined;
11031
+ };
11032
+ const branchFilter = args[0] && !args[0].startsWith("--") ? args[0] : undefined;
11033
+ const by = flag2("--by");
11034
+ const sinceStr = flag2("--since");
11035
+ const since = sinceStr ? Date.parse(sinceStr) : undefined;
11036
+ if (sinceStr && Number.isNaN(since))
11037
+ die(`--since: not a date: ${sinceStr} (try YYYY-MM-DD)`);
11038
+ const lines2 = renderReflog2(ops, { branchFilter, actor: by, since });
11039
+ if (!lines2.length)
11040
+ console.log("(no matching ops)");
11041
+ for (const l of lines2)
11042
+ console.log(l);
11043
+ break;
11044
+ }
11045
+ case "recover": {
11046
+ const { RECOVER_HELP: RECOVER_HELP2, listAllRoots: listAllRoots2, restoreFileFromHistory: restoreFileFromHistory2, dumpLog: dumpLog2 } = await Promise.resolve().then(() => (init_admin_recover(), exports_admin_recover));
11047
+ const flag2 = (name) => {
11048
+ const i = args.indexOf(name);
11049
+ return i >= 0 ? args[i + 1] : undefined;
11050
+ };
11051
+ if (!args.length || args[0] === "--help") {
11052
+ console.log(RECOVER_HELP2);
11053
+ break;
11054
+ }
11055
+ if (args.includes("--from-remote")) {
11056
+ const url = args[args.indexOf("--from-remote") + 1];
11057
+ const repoName = args[args.indexOf("--from-remote") + 2];
11058
+ if (!url || !repoName)
11059
+ die("usage: sol recover --from-remote <url> <repo>");
11060
+ console.log(`to rehydrate from the cloud, clone the remote into a fresh dir:
11061
+ sol clone ${url} ${repoName}
11062
+ then copy your uncommitted working files in. (a destructive in-place rehydrate is intentionally manual.)`);
11063
+ break;
11064
+ }
11065
+ const { log } = open();
11066
+ const store2 = loadStore();
11067
+ const ops = await log.history();
11068
+ const asyncStore = { get: async (h) => store2.get(h), put: async () => die("recover is read-only"), has: async (h) => store2.has(h) };
11069
+ if (args.includes("--list-all-roots")) {
11070
+ const roots = await listAllRoots2(ops, async (h) => store2.has(h));
11071
+ for (const r of roots)
11072
+ console.log(` ${r.root.slice(0, 16)} seq ${r.seq} ${r.resolvable ? "resolvable" : "MISSING"}${r.message ? ` ${r.message}` : ""}`);
11073
+ if (!roots.length)
11074
+ console.log(" (no roots — empty repo)");
11075
+ break;
11076
+ }
11077
+ if (args.includes("--file")) {
11078
+ const path = flag2("--file") || die("usage: sol recover --file <path> [--from <seq|hash>]");
11079
+ const fromStr = flag2("--from");
11080
+ const from = fromStr ? /^\d+$/.test(fromStr) ? Number(fromStr) : fromStr : undefined;
11081
+ const rec = await restoreFileFromHistory2(asyncStore, ops, path, from);
11082
+ if (!rec.found)
11083
+ die(rec.reason);
11084
+ if ("sealed" in rec) {
11085
+ console.log(`${path} is sealed at seq ${rec.fromSeq} — recovered the opaque box (open it with your key):`);
11086
+ console.log(rec.box);
11087
+ } else {
11088
+ const out = rec.encoding === "base64" ? Buffer.from(rec.content, "base64") : rec.content;
11089
+ writeFileSync16(join18(cwd, path), out);
11090
+ console.log(`restored ${path} from seq ${rec.fromSeq} (root ${rec.fromRoot.slice(0, 16)}) -> wrote to disk; \`sol add ${path}\` to re-track`);
11091
+ }
11092
+ break;
11093
+ }
11094
+ if (args.includes("--dump-log")) {
11095
+ const i = args.indexOf("--dump-log");
11096
+ const fromSeq = args[i + 1] && /^\d+$/.test(args[i + 1]) ? Number(args[i + 1]) : undefined;
11097
+ const toSeq = args[i + 2] && /^\d+$/.test(args[i + 2]) ? Number(args[i + 2]) : undefined;
11098
+ console.log(dumpLog2(ops, fromSeq, toSeq));
11099
+ break;
11100
+ }
11101
+ console.log(RECOVER_HELP2);
11102
+ break;
11103
+ }
9691
11104
  case "gc": {
9692
11105
  const { log } = open();
9693
11106
  const store2 = loadStore();
@@ -9709,16 +11122,16 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9709
11122
  if (op.prov)
9710
11123
  walk(op.prov);
9711
11124
  }
9712
- const objDir = join17(solDir, "objects");
11125
+ const objDir = join18(solDir, "objects");
9713
11126
  let removed = 0;
9714
11127
  for (const name of readdirSync8(objDir)) {
9715
11128
  if (name.endsWith(".tmp") || !reachable.has(name)) {
9716
- unlinkSync7(join17(objDir, name));
11129
+ unlinkSync7(join18(objDir, name));
9717
11130
  removed++;
9718
11131
  }
9719
11132
  }
9720
11133
  console.log(`gc: kept ${reachable.size} object(s), removed ${removed} unreachable`);
9721
- if (existsSync17(join17(solDir, "env", "seal"))) {
11134
+ if (existsSync19(join18(solDir, "env", "seal"))) {
9722
11135
  const { gcStaleStanzas: gcStaleStanzas2 } = await Promise.resolve().then(() => (init_secret(), exports_secret));
9723
11136
  const st = gcStaleStanzas2(solDir);
9724
11137
  if (st.removed)
@@ -9733,8 +11146,8 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9733
11146
  console.log(p);
9734
11147
  break;
9735
11148
  }
9736
- const f = join17(cwd, ".solignore");
9737
- const lead = existsSync17(f) && !readFileSync17(f, "utf8").endsWith(`
11149
+ const f = join18(cwd, ".solignore");
11150
+ const lead = existsSync19(f) && !readFileSync19(f, "utf8").endsWith(`
9738
11151
  `) ? `
9739
11152
  ` : "";
9740
11153
  appendFileSync2(f, lead + pat + `
@@ -9745,7 +11158,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9745
11158
  case "hide": {
9746
11159
  const { repo } = open();
9747
11160
  const wantJson = args.includes("--json");
9748
- const cfg = resolveRemote(solDir) || die("no remote — `sol push <repo>` (or `sol remote <url> <repo>`) first; the policy lives on the repo");
11161
+ const cfg = resolveRemote2(solDir) || die("no remote — `sol push <repo>` (or `sol remote <url> <repo>`) first; the policy lives on the repo");
9749
11162
  const token = process.env.SOL_TOKEN || authExpired();
9750
11163
  const sub = args[0];
9751
11164
  const flag2 = (name) => {
@@ -9850,12 +11263,12 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9850
11263
  let removed = 0;
9851
11264
  {
9852
11265
  const res = await scrubHistory2(solDir, ops, targets);
9853
- writeFileSync15(join17(solDir, "ops.jsonl"), res.ops.map((o) => JSON.stringify(o)).join(`
11266
+ writeFileSync16(join18(solDir, "ops.jsonl"), res.ops.map((o) => JSON.stringify(o)).join(`
9854
11267
  `) + (res.ops.length ? `
9855
11268
  ` : ""));
9856
- writeFileSync15(join17(solDir, "HEAD"), JSON.stringify({ head: res.newHead, seq: res.newSeq, logTip: res.newLogTip }));
9857
- if (existsSync17(refsPath())) {
9858
- const refs = JSON.parse(readFileSync17(refsPath(), "utf8"));
11269
+ writeFileSync16(join18(solDir, "HEAD"), JSON.stringify({ head: res.newHead, seq: res.newSeq, logTip: res.newLogTip }));
11270
+ if (existsSync19(refsPath())) {
11271
+ const refs = JSON.parse(readFileSync19(refsPath(), "utf8"));
9859
11272
  for (const b of Object.values(refs.branches)) {
9860
11273
  if (b.head && res.rootMap.has(b.head))
9861
11274
  b.head = res.rootMap.get(b.head);
@@ -9878,7 +11291,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9878
11291
  break;
9879
11292
  }
9880
11293
  if (reapply) {
9881
- const cfg = resolveRemote(solDir) || die("no remote — the policy lives on the repo; `sol remote <url> <repo>` first");
11294
+ const cfg = resolveRemote2(solDir) || die("no remote — the policy lives on the repo; `sol remote <url> <repo>` first");
9882
11295
  const token = process.env.SOL_TOKEN || authExpired();
9883
11296
  const { SealedClient: SealedClient3 } = await Promise.resolve().then(() => (init_sealed_client(), exports_sealed_client));
9884
11297
  const { UNREADABLE: UNREADABLE2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
@@ -9945,7 +11358,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9945
11358
  const { SealedClient: SealedClient3 } = await Promise.resolve().then(() => (init_sealed_client(), exports_sealed_client));
9946
11359
  const { parseRecipient: parseRecipient3, resolveRecipient: resolveRecipient3, recordAudience: recordAudience3 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
9947
11360
  const { pubFingerprint: pubFingerprint2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
9948
- const dirUrl2 = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL).replace(/\/+$/, "");
11361
+ const dirUrl2 = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
9949
11362
  const explicit = positional.slice(1);
9950
11363
  const recipientPubKeys2 = {};
9951
11364
  const audienceAccounts2 = [];
@@ -9953,7 +11366,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
9953
11366
  const crossAccount2 = [];
9954
11367
  let policyApplied2 = false;
9955
11368
  if (!explicit.length) {
9956
- const cfg = resolveRemote(solDir);
11369
+ const cfg = resolveRemote2(solDir);
9957
11370
  const token = process.env.SOL_TOKEN;
9958
11371
  if (cfg && token) {
9959
11372
  const resolved = await recipientsForPath(cfg, token, dir).catch(() => {
@@ -10013,15 +11426,15 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10013
11426
  if (content === SEALED && !args.includes("--hide-names"))
10014
11427
  die("already sealed: " + path);
10015
11428
  if (content === undefined) {
10016
- const abs = join17(cwd, path);
10017
- if (!existsSync17(abs))
11429
+ const abs = join18(cwd, path);
11430
+ if (!existsSync19(abs))
10018
11431
  die("no such file: " + path);
10019
- content = readFileSync17(abs, "utf8");
11432
+ content = readFileSync19(abs, "utf8");
10020
11433
  }
10021
11434
  const ring = loadKeyRing();
10022
11435
  const { SealedClient: SealedClient2 } = await Promise.resolve().then(() => (init_sealed_client(), exports_sealed_client));
10023
11436
  const { parseRecipient: parseRecipient2, resolveRecipient: resolveRecipient2, recordAudience: recordAudience2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
10024
- const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL).replace(/\/+$/, "");
11437
+ const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
10025
11438
  const recipientPubKeys = {};
10026
11439
  const audienceAccounts = [];
10027
11440
  const localRecipients = new Set([actor]);
@@ -10034,7 +11447,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10034
11447
  let hideNamesFromPolicy = false;
10035
11448
  let hideExistenceFromPolicy = false;
10036
11449
  if (!rawRecipients.length) {
10037
- const cfg = resolveRemote(solDir);
11450
+ const cfg = resolveRemote2(solDir);
10038
11451
  const token = process.env.SOL_TOKEN;
10039
11452
  if (cfg && token) {
10040
11453
  const { pubFingerprint: pubFingerprint2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
@@ -10063,7 +11476,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
10063
11476
  }
10064
11477
  }
10065
11478
  if (wantEscrow && !escrowSlots.length) {
10066
- const cfg = resolveRemote(solDir);
11479
+ const cfg = resolveRemote2(solDir);
10067
11480
  const token = process.env.SOL_TOKEN;
10068
11481
  if (cfg && token) {
10069
11482
  const { recipientsForAudience: recipientsForAudience2 } = await Promise.resolve().then(() => (init_remote(), exports_remote));
@@ -10269,7 +11682,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10269
11682
  const { repo } = open();
10270
11683
  const wantJson = args.includes("--json");
10271
11684
  if (args.includes("--check")) {
10272
- const cfg = resolveRemote(solDir) || die("no remote — drift is checked against the policy on the repo; `sol remote <url> <repo>` first");
11685
+ const cfg = resolveRemote2(solDir) || die("no remote — drift is checked against the policy on the repo; `sol remote <url> <repo>` first");
10273
11686
  const token = process.env.SOL_TOKEN || authExpired();
10274
11687
  const { policyCheck: policyCheck2 } = await Promise.resolve().then(() => (init_remote(), exports_remote));
10275
11688
  const states = [];
@@ -10326,7 +11739,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10326
11739
  const { openContent: openContent2, UNREADABLE: UNREADABLE2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
10327
11740
  const { parseStruct: parseStruct2 } = await Promise.resolve().then(() => (init_struct(), exports_struct));
10328
11741
  const audiences = loadAudiences2(solDir);
10329
- const ring = existsSync17(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
11742
+ const ring = existsSync19(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
10330
11743
  const self = loadSelfIdentity2();
10331
11744
  const levelOf = (boxStr) => {
10332
11745
  try {
@@ -10521,7 +11934,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10521
11934
  const result = merge2({ store: store2 }, other.base, ours, other.head);
10522
11935
  if (result.conflicts.length) {
10523
11936
  materializeTree(store2, result.head);
10524
- writeFileSync15(join17(solDir, "MERGE_HEAD"), other.head);
11937
+ writeFileSync16(join18(solDir, "MERGE_HEAD"), other.head);
10525
11938
  saveMergeConflicts(solDir, result.conflicts);
10526
11939
  console.log(`merge ${name} -> ${result.conflicts.length} conflict(s), left in your working tree (uncommitted):`);
10527
11940
  for (const c of result.conflicts)
@@ -10622,8 +12035,19 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10622
12035
  break;
10623
12036
  }
10624
12037
  case "remote": {
10625
- if (!existsSync17(solDir))
12038
+ if (!existsSync19(solDir))
10626
12039
  die("not a sol repo");
12040
+ if (args[0] === "verify") {
12041
+ const { remoteVerify: remoteVerify2, renderRemoteVerify: renderRemoteVerify2 } = await Promise.resolve().then(() => (init_admin_remote_verify(), exports_admin_remote_verify));
12042
+ const cfg2 = resolveRemote2(solDir) || die("no remote — `sol remote <url> <repo>` (or `sol clone`) first");
12043
+ const token = process.env.SOL_TOKEN || await loadStoredToken() || authExpired();
12044
+ const result = await remoteVerify2((url, init) => fetch(url, init), cfg2.url, cfg2.repo, token);
12045
+ const { lines: lines2, exitCode } = renderRemoteVerify2(result);
12046
+ for (const l of lines2)
12047
+ console.log(l);
12048
+ process.exitCode = exitCode;
12049
+ break;
12050
+ }
10627
12051
  if (args[0]) {
10628
12052
  const repoName = args[1] || die("usage: sol remote <url> <repo>");
10629
12053
  saveRemote(solDir, { url: args[0], repo: repoName });
@@ -10668,15 +12092,15 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10668
12092
  }
10669
12093
  if (!tokens)
10670
12094
  die("timed out waiting for approval");
10671
- mkdirSync10(dirname4(CRED_PATH), { recursive: true });
10672
- writeFileSync15(CRED_PATH, JSON.stringify({ webUrl, ...tokens }, null, 2), { mode: 384 });
12095
+ mkdirSync11(dirname5(CRED_PATH), { recursive: true });
12096
+ writeFileSync16(CRED_PATH, JSON.stringify({ webUrl, ...tokens }, null, 2), { mode: 384 });
10673
12097
  const c = tokenClaims(tokens.accessToken);
10674
12098
  console.log(`
10675
12099
  Logged in as ${c.handle ? `@${c.handle}` : c.email || "user"}.`);
10676
12100
  if (!c.handle)
10677
12101
  console.log(" (no handle yet — set one in the web app to get your <handle>/<repo> namespace)");
10678
12102
  } else if (sub === "logout") {
10679
- if (existsSync17(CRED_PATH))
12103
+ if (existsSync19(CRED_PATH))
10680
12104
  unlinkSync7(CRED_PATH);
10681
12105
  console.log("logged out");
10682
12106
  } else if (sub === "status" || !sub) {
@@ -10685,11 +12109,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10685
12109
  console.log(`authenticated via SOL_TOKEN (env)${c2.handle ? ` as @${c2.handle}` : ""}`);
10686
12110
  break;
10687
12111
  }
10688
- if (!existsSync17(CRED_PATH)) {
12112
+ if (!existsSync19(CRED_PATH)) {
10689
12113
  console.log("not logged in — run `sol auth login` (or set SOL_TOKEN)");
10690
12114
  break;
10691
12115
  }
10692
- const creds = JSON.parse(readFileSync17(CRED_PATH, "utf8"));
12116
+ const creds = JSON.parse(readFileSync19(CRED_PATH, "utf8"));
10693
12117
  const c = tokenClaims(creds.accessToken || "");
10694
12118
  const stale = typeof c.exp === "number" && c.exp * 1000 < Date.now();
10695
12119
  console.log(`logged in as ${c.handle ? `@${c.handle}` : c.email || "user"} via ${creds.webUrl}${stale ? " (token stale — refreshes on next use)" : ""}`);
@@ -10739,9 +12163,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10739
12163
  console.log("heads-up: changing your handle re-namespaces your repos under the new <handle>/<repo>.");
10740
12164
  console.log(`handle set to @${out.handle}`);
10741
12165
  } else if (sub === "pat") {
10742
- if (!existsSync17(CRED_PATH))
12166
+ if (!existsSync19(CRED_PATH))
10743
12167
  die("run `sol auth login` first");
10744
- const creds = JSON.parse(readFileSync17(CRED_PATH, "utf8"));
12168
+ const creds = JSON.parse(readFileSync19(CRED_PATH, "utf8"));
10745
12169
  const token = await loadStoredToken();
10746
12170
  if (!token || !creds.webUrl)
10747
12171
  die("run `sol auth login` first");
@@ -10799,11 +12223,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10799
12223
  const { converge: converge2 } = await Promise.resolve().then(() => (init_converge(), exports_converge));
10800
12224
  const peer = openPeer2(localSrc);
10801
12225
  const peerHead = await peer.log.head();
10802
- const dest = resolve4(procCwd, args[1] || (args[0].replace(/\/+$/, "").split("/").pop() || "clone") + "-clone");
10803
- const ddir = join17(dest, ".sol");
10804
- if (existsSync17(ddir))
12226
+ const dest = resolve5(procCwd, args[1] || (args[0].replace(/\/+$/, "").split("/").pop() || "clone") + "-clone");
12227
+ const ddir = join18(dest, ".sol");
12228
+ if (existsSync19(ddir))
10805
12229
  die("already a sol repo: " + dest);
10806
- mkdirSync10(ddir, { recursive: true });
12230
+ mkdirSync11(ddir, { recursive: true });
10807
12231
  const peerOps = await peer.log.history();
10808
12232
  const res = await converge2({ store: new FileStore(ddir), log: new FileOpLog(ddir) }, { nodes: await peerNodes2(peer, peerHead, peerOps), ops: peerOps, incomingHead: peerHead, actor });
10809
12233
  const dstore = new Store;
@@ -10813,7 +12237,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10813
12237
  const files = (res.head ? listAll(dstore, res.head) : []).filter((f) => !f.split("/").some(isReservedKey2));
10814
12238
  for (const f of files)
10815
12239
  materializeInto(dstore, res.head, dest, f);
10816
- writeFileSync15(join17(ddir, "refs.json"), JSON.stringify({ current: "main", branches: { main: { head: res.head, base: res.head, remote: res.head } }, tags: {} }, null, 2));
12240
+ writeFileSync16(join18(ddir, "refs.json"), JSON.stringify({ current: "main", branches: { main: { head: res.head, base: res.head, remote: res.head } }, tags: {} }, null, 2));
10817
12241
  writeWorkingIndexAt(ddir, dest, files);
10818
12242
  console.log(`cloned local peer ${args[0]} -> ${dest} (${(await peer.log.history()).length} ops, ${files.length} files)`);
10819
12243
  break;
@@ -10821,13 +12245,13 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10821
12245
  const [url, rest] = remoteUrlArg(args);
10822
12246
  const repoName = rest[0] || die("usage: sol clone [<url>] <owner>/<repo> [dir]");
10823
12247
  const token = process.env.SOL_TOKEN || die("set SOL_TOKEN to the backend bearer token");
10824
- const target = resolve4(cwd, rest[1] || repoName.split("/").pop() || repoName);
10825
- const fdir = join17(target, ".sol");
10826
- if (existsSync17(fdir))
12248
+ const target = resolve5(cwd, rest[1] || repoName.split("/").pop() || repoName);
12249
+ const fdir = join18(target, ".sol");
12250
+ if (existsSync19(fdir))
10827
12251
  die("already a sol repo: " + target);
10828
12252
  const cfg = { url, repo: repoName };
10829
12253
  const bundle = await remoteExport(cfg, token);
10830
- mkdirSync10(fdir, { recursive: true });
12254
+ mkdirSync11(fdir, { recursive: true });
10831
12255
  await writeBundle(fdir, bundle, 0);
10832
12256
  saveRemote(fdir, cfg);
10833
12257
  await pullEnvState(fdir, cfg, token);
@@ -10840,8 +12264,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10840
12264
  cloneBranches[name] = { head: h, base: h, remote: h };
10841
12265
  if (!cloneBranches[onBranch])
10842
12266
  cloneBranches[onBranch] = { head: checkoutHead, base: checkoutHead, remote: checkoutHead };
10843
- writeFileSync15(join17(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
10844
- writeFileSync15(join17(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: bundle.seq, logTip: bundle.tip }));
12267
+ writeFileSync16(join18(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
12268
+ writeFileSync16(join18(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: bundle.seq, logTip: bundle.tip }));
10845
12269
  const store2 = new Store;
10846
12270
  for (const node of bundle.nodes)
10847
12271
  store2.put(node);
@@ -10862,11 +12286,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10862
12286
  if (!loadRemote(solDir)) {
10863
12287
  const repoName = args.find((a) => !a.startsWith("-"));
10864
12288
  if (repoName) {
10865
- saveRemote(solDir, { url: DEFAULT_REMOTE_URL, repo: repoName });
10866
- console.log(`remote set: ${DEFAULT_REMOTE_URL} (repo ${repoName})`);
12289
+ saveRemote(solDir, { url: DEFAULT_REMOTE_URL2, repo: repoName });
12290
+ console.log(`remote set: ${DEFAULT_REMOTE_URL2} (repo ${repoName})`);
10867
12291
  }
10868
12292
  }
10869
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`, or `sol push <repo>` to use the hosted Sol");
12293
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`, or `sol push <repo>` to use the hosted Sol");
10870
12294
  {
10871
12295
  const { pendingScrubPaths: pendingScrubPaths2, historyHasCleartext: historyHasCleartext2 } = await Promise.resolve().then(() => (init_secret_scrub(), exports_secret_scrub));
10872
12296
  const ops0 = await log.history();
@@ -10880,7 +12304,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10880
12304
  const ops = await log.history();
10881
12305
  const localSeq = ops.length ? ops[ops.length - 1].seq : 0;
10882
12306
  const localTip = await log.logTip();
10883
- const localRefs = existsSync17(refsPath()) ? JSON.parse(readFileSync17(refsPath(), "utf8")) : undefined;
12307
+ const localRefs = existsSync19(refsPath()) ? JSON.parse(readFileSync19(refsPath(), "utf8")) : undefined;
10884
12308
  const branch = localRefs?.current ?? "main";
10885
12309
  const branchHead = await log.head() ?? "";
10886
12310
  const remoteWasEmpty = rh.seq === 0 && !rh.tip;
@@ -10892,7 +12316,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10892
12316
  const forkBase = localRefs?.branches[branch]?.remote;
10893
12317
  const baseOp = forkBase ? ops.find((o) => o.rootAfter === forkBase) : undefined;
10894
12318
  const fromSeq = baseOp ? baseOp.seq : rh.seq;
10895
- const res = await remotePush(cfg, token, {
12319
+ const res = await remotePushChunked(cfg, token, {
10896
12320
  nodes: allLocalNodes(),
10897
12321
  ops: ops.filter((o) => o.seq > fromSeq),
10898
12322
  branch,
@@ -10903,8 +12327,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10903
12327
  const prevHead = await log.head() ?? "";
10904
12328
  await writeBundle(solDir, canon, 0);
10905
12329
  const convergedHead = res.head ?? canon.refs?.branches?.[branch] ?? branchHead;
10906
- if (existsSync17(refsPath())) {
10907
- const refs = JSON.parse(readFileSync17(refsPath(), "utf8"));
12330
+ if (existsSync19(refsPath())) {
12331
+ const refs = JSON.parse(readFileSync19(refsPath(), "utf8"));
10908
12332
  if (canon.refs) {
10909
12333
  for (const [name, h] of Object.entries(canon.refs.branches)) {
10910
12334
  refs.branches[name] = { head: refs.branches[name]?.head ?? h, base: refs.branches[name]?.base ?? h, remote: h };
@@ -10928,6 +12352,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10928
12352
  console.log(`pushed ${res.applied} op(s) -> remote (branch ${branch} @ ${(convergedHead || "").slice(0, 12)})${mergedNote}${conflictNote}${prod ? `; production=${prod}` : ""}`);
10929
12353
  }
10930
12354
  await pushEnvState(solDir, cfg, token);
12355
+ await surfaceOwner(cfg, token);
10931
12356
  if (wantPublic) {
10932
12357
  const a = await accessSet(cfg, token, { visibility: "public" });
10933
12358
  console.log(`${cfg.repo} is now ${a.visibility}`);
@@ -10968,8 +12393,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10968
12393
  const ownMeta = readViewMeta(solDir);
10969
12394
  const base2 = peerMeta?.startHead ?? ownMeta?.startHead;
10970
12395
  const res = await converge2({ store: new FileStore(solDir, objectsDir()), log }, { nodes: await peerNodes2(peer, peerHead, peerOps), ops: peerOps, incomingHead: peerHead, actor, ...base2 ? { base: base2 } : {} });
10971
- if (existsSync17(refsPath())) {
10972
- const refs = JSON.parse(readFileSync17(refsPath(), "utf8"));
12396
+ if (existsSync19(refsPath())) {
12397
+ const refs = JSON.parse(readFileSync19(refsPath(), "utf8"));
10973
12398
  if (refs.branches[refs.current])
10974
12399
  refs.branches[refs.current].head = res.head;
10975
12400
  saveRefs(refs);
@@ -10992,7 +12417,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
10992
12417
  }
10993
12418
  break;
10994
12419
  }
10995
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12420
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
10996
12421
  const token = process.env.SOL_TOKEN || authExpired();
10997
12422
  await surfaceEnvDivergence(solDir, cfg, token);
10998
12423
  await pullEnvState(solDir, cfg, token);
@@ -11000,11 +12425,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11000
12425
  const ops = await log.history();
11001
12426
  const localSeq = ops.length ? ops[ops.length - 1].seq : 0;
11002
12427
  const localTip = await log.logTip();
11003
- const curBranch = existsSync17(refsPath()) ? JSON.parse(readFileSync17(refsPath(), "utf8")).current : bundle.refs?.production || "main";
12428
+ const curBranch = existsSync19(refsPath()) ? JSON.parse(readFileSync19(refsPath(), "utf8")).current : bundle.refs?.production || "main";
11004
12429
  const remoteCurHead = bundle.refs?.branches?.[curBranch] ?? bundle.head ?? "";
11005
12430
  if (bundle.seq === localSeq && bundle.tip === localTip) {
11006
- if (bundle.refs && existsSync17(refsPath())) {
11007
- const lr = JSON.parse(readFileSync17(refsPath(), "utf8"));
12431
+ if (bundle.refs && existsSync19(refsPath())) {
12432
+ const lr = JSON.parse(readFileSync19(refsPath(), "utf8"));
11008
12433
  const before = lr.branches[lr.current]?.head;
11009
12434
  for (const [name, h] of Object.entries(bundle.refs.branches))
11010
12435
  lr.branches[name] = { head: h, base: lr.branches[name]?.base ?? h, remote: h };
@@ -11028,9 +12453,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11028
12453
  break;
11029
12454
  }
11030
12455
  const syncRefHead = (h) => {
11031
- if (!existsSync17(refsPath()))
12456
+ if (!existsSync19(refsPath()))
11032
12457
  return;
11033
- const refs = JSON.parse(readFileSync17(refsPath(), "utf8"));
12458
+ const refs = JSON.parse(readFileSync19(refsPath(), "utf8"));
11034
12459
  if (refs.branches[refs.current])
11035
12460
  refs.branches[refs.current].head = h;
11036
12461
  saveRefs(refs);
@@ -11048,8 +12473,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11048
12473
  syncRefHead(remoteCurHead);
11049
12474
  setOpLogHead(remoteCurHead);
11050
12475
  materializeTree(loadStore(), remoteCurHead);
11051
- if (bundle.refs && existsSync17(refsPath())) {
11052
- const lr = JSON.parse(readFileSync17(refsPath(), "utf8"));
12476
+ if (bundle.refs && existsSync19(refsPath())) {
12477
+ const lr = JSON.parse(readFileSync19(refsPath(), "utf8"));
11053
12478
  for (const [name, h] of Object.entries(bundle.refs.branches))
11054
12479
  lr.branches[name] = { head: h, base: lr.branches[name]?.base ?? h, remote: h };
11055
12480
  saveRefs(lr);
@@ -11097,9 +12522,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11097
12522
  for (const c of result.conflicts) {
11098
12523
  const blob = fileAt(store2, result.head, c.path);
11099
12524
  if (blob)
11100
- writeFileSync15(join17(cwd, c.path), blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
12525
+ writeFileSync16(join18(cwd, c.path), blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
11101
12526
  }
11102
- writeFileSync15(join17(solDir, "MERGE_HEAD"), remoteHead2);
12527
+ writeFileSync16(join18(solDir, "MERGE_HEAD"), remoteHead2);
11103
12528
  saveMergeConflicts(solDir, result.conflicts);
11104
12529
  console.log(`pulled + merged WITH ${result.conflicts.length} conflict(s), left uncommitted in your working tree:`);
11105
12530
  for (const c of result.conflicts)
@@ -11113,12 +12538,36 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11113
12538
  }
11114
12539
  break;
11115
12540
  }
12541
+ case "sync": {
12542
+ if (!existsSync19(solDir))
12543
+ die("not a sol repo — run `sol init` first");
12544
+ if (!args.includes("--watch"))
12545
+ die("usage: sol sync --watch [--poll-interval <ms>] [--no-push-immediately] [--dry-run]");
12546
+ resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`, or `sol push <repo>` first");
12547
+ const token = process.env.SOL_TOKEN || await loadStoredToken() || authExpired();
12548
+ const intFlag = (name, def) => {
12549
+ const i = args.indexOf(name);
12550
+ if (i < 0 || i + 1 >= args.length)
12551
+ return def;
12552
+ const n = Number(args[i + 1]);
12553
+ return Number.isFinite(n) && n > 0 ? n : def;
12554
+ };
12555
+ const { runSyncWatch: runSyncWatch2 } = await Promise.resolve().then(() => (init_sync_watch(), exports_sync_watch));
12556
+ await runSyncWatch2({
12557
+ repoDir: cwd,
12558
+ token,
12559
+ pollInterval: intFlag("--poll-interval", 5000),
12560
+ pushImmediate: !args.includes("--no-push-immediately"),
12561
+ dryRun: args.includes("--dry-run")
12562
+ });
12563
+ break;
12564
+ }
11116
12565
  case "promote": {
11117
- if (!existsSync17(solDir))
12566
+ if (!existsSync19(solDir))
11118
12567
  die("not a sol repo");
11119
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12568
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
11120
12569
  const token = process.env.SOL_TOKEN || authExpired();
11121
- const cur = existsSync17(refsPath()) ? JSON.parse(readFileSync17(refsPath(), "utf8")).current : "main";
12570
+ const cur = existsSync19(refsPath()) ? JSON.parse(readFileSync19(refsPath(), "utf8")).current : "main";
11122
12571
  const branch = args[0] || cur;
11123
12572
  const refs = await remotePromote(cfg, token, branch);
11124
12573
  console.log(`promoted '${branch}' -> production '${refs.production}' now at ${(refs.branches[refs.production] ?? "").slice(0, 12)}`);
@@ -11129,9 +12578,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11129
12578
  const parent = frest[0] || die("usage: sol fork [<url>] <parent-repo> <new-repo> [dir]");
11130
12579
  const newRepo = frest[1] || die("usage: sol fork [<url>] <parent-repo> <new-repo> [dir]");
11131
12580
  const token = process.env.SOL_TOKEN || authExpired();
11132
- const target = resolve4(cwd, frest[2] || newRepo);
11133
- const fdir = join17(target, ".sol");
11134
- if (existsSync17(fdir))
12581
+ const target = resolve5(cwd, frest[2] || newRepo);
12582
+ const fdir = join18(target, ".sol");
12583
+ if (existsSync19(fdir))
11135
12584
  die("already a sol repo: " + target);
11136
12585
  const parentCfg = { url, repo: parent };
11137
12586
  const newCfg = { url, repo: newRepo, forkParent: parent };
@@ -11147,7 +12596,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11147
12596
  }
11148
12597
  await forkMeta(newCfg, token, parent);
11149
12598
  const canon = await remoteExport(newCfg, token);
11150
- mkdirSync10(fdir, { recursive: true });
12599
+ mkdirSync11(fdir, { recursive: true });
11151
12600
  await writeBundle(fdir, canon, 0);
11152
12601
  const srvRefs = canon.refs ?? { branches: { main: canon.head ?? "" }, production: "main" };
11153
12602
  const checkout = canon.checkout ?? { branch: srvRefs.production || "main", head: srvRefs.branches[srvRefs.production] ?? canon.head };
@@ -11156,8 +12605,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11156
12605
  const cloneBranches = {};
11157
12606
  for (const [name, h] of Object.entries(srvRefs.branches))
11158
12607
  cloneBranches[name] = { head: h, base: h, remote: h };
11159
- writeFileSync15(join17(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
11160
- writeFileSync15(join17(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: canon.seq, logTip: canon.tip }));
12608
+ writeFileSync16(join18(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
12609
+ writeFileSync16(join18(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: canon.seq, logTip: canon.tip }));
11161
12610
  saveRemote(fdir, newCfg);
11162
12611
  const store2 = new Store;
11163
12612
  for (const node of canon.nodes)
@@ -11175,7 +12624,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11175
12624
  }
11176
12625
  case "access": {
11177
12626
  const token = process.env.SOL_TOKEN || authExpired();
11178
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12627
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
11179
12628
  const sub = args[0];
11180
12629
  if (!sub || sub === "show") {
11181
12630
  const a = await accessGet(cfg, token);
@@ -11227,7 +12676,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11227
12676
  break;
11228
12677
  }
11229
12678
  case "forks": {
11230
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12679
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
11231
12680
  const token = process.env.SOL_TOKEN || authExpired();
11232
12681
  const { forks } = await forksList(cfg, token);
11233
12682
  if (!forks.length)
@@ -11240,11 +12689,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
11240
12689
  break;
11241
12690
  }
11242
12691
  case "mr": {
11243
- const cfg = resolveRemote(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
12692
+ const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
11244
12693
  const token = process.env.SOL_TOKEN || authExpired();
11245
12694
  const { mrSummary: mrSummary2 } = await Promise.resolve().then(() => exports_mr);
11246
12695
  const sub = args[0];
11247
- const localRefs = () => existsSync17(refsPath()) ? JSON.parse(readFileSync17(refsPath(), "utf8")) : { current: "main", branches: {}, tags: {} };
12696
+ const localRefs = () => existsSync19(refsPath()) ? JSON.parse(readFileSync19(refsPath(), "utf8")) : { current: "main", branches: {}, tags: {} };
11248
12697
  const flag2 = (name) => {
11249
12698
  const i = args.indexOf(name);
11250
12699
  return i >= 0 ? args[i + 1] : undefined;
@@ -11422,7 +12871,7 @@ ${mrSummary2(pr)}`);
11422
12871
  die("usage: sol run [--keep <path>] [--isolate] <command...>");
11423
12872
  const { capture: capture3, hydrate: hydrate3, isolateCommand: isolateCommand2 } = await Promise.resolve().then(() => (init_runtime(), exports_runtime));
11424
12873
  const head = await repo.head();
11425
- const dir = mkdtempSync(join17(tmpdir(), "sol-run-"));
12874
+ const dir = mkdtempSync(join18(tmpdir(), "sol-run-"));
11426
12875
  try {
11427
12876
  const hn = hydrate3(loadStore(), head, dir);
11428
12877
  console.log(`hydrated ${hn} file(s) -> sandbox${isolate ? " (isolated: no network, writes confined to the sandbox)" : ""}`);
@@ -11442,8 +12891,8 @@ ${mrSummary2(pr)}`);
11442
12891
  const { written, deleted } = await capture3(repo, dir, keep);
11443
12892
  if (written.length || deleted.length) {
11444
12893
  await appendCommit(log, await repo.head(), `run: ${command.join(" ")}`, head);
11445
- if (existsSync17(refsPath())) {
11446
- const refs = JSON.parse(readFileSync17(refsPath(), "utf8"));
12894
+ if (existsSync19(refsPath())) {
12895
+ const refs = JSON.parse(readFileSync19(refsPath(), "utf8"));
11447
12896
  if (refs.branches[refs.current]) {
11448
12897
  refs.branches[refs.current].head = await repo.head();
11449
12898
  saveRefs(refs);
@@ -11455,7 +12904,7 @@ ${mrSummary2(pr)}`);
11455
12904
  materialize(synced, nh, f);
11456
12905
  for (const f of deleted) {
11457
12906
  try {
11458
- unlinkSync7(join17(cwd, f));
12907
+ unlinkSync7(join18(cwd, f));
11459
12908
  } catch {}
11460
12909
  }
11461
12910
  console.log(`captured ${written.length} written, ${deleted.length} deleted file(s):`);
@@ -11467,7 +12916,7 @@ ${mrSummary2(pr)}`);
11467
12916
  console.log("no files captured (the run produced no tracked changes)");
11468
12917
  }
11469
12918
  } finally {
11470
- rmSync2(dir, { recursive: true, force: true });
12919
+ rmSync3(dir, { recursive: true, force: true });
11471
12920
  }
11472
12921
  break;
11473
12922
  }
@@ -11475,12 +12924,12 @@ ${mrSummary2(pr)}`);
11475
12924
  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))]);
11476
12925
  const sub = args[0];
11477
12926
  if (sub === "import") {
11478
- const gitPath = resolve4(cwd, args[1] || die("usage: sol git import <git-repo> [dir]"));
11479
- const target = resolve4(cwd, args[2] || basename(gitPath));
11480
- const fdir = join17(target, ".sol");
11481
- if (existsSync17(fdir))
12927
+ const gitPath = resolve5(cwd, args[1] || die("usage: sol git import <git-repo> [dir]"));
12928
+ const target = resolve5(cwd, args[2] || basename2(gitPath));
12929
+ const fdir = join18(target, ".sol");
12930
+ if (existsSync19(fdir))
11482
12931
  die("already a sol repo: " + target);
11483
- mkdirSync10(fdir, { recursive: true });
12932
+ mkdirSync11(fdir, { recursive: true });
11484
12933
  const { commits, branches, head, current } = await importGitRepo2(gitPath, fdir);
11485
12934
  const refsBranches = {};
11486
12935
  for (const b of branches)
@@ -11488,20 +12937,20 @@ ${mrSummary2(pr)}`);
11488
12937
  if (!refsBranches[current])
11489
12938
  refsBranches[current] = { head, base: head };
11490
12939
  refsBranches[current].head = head;
11491
- writeFileSync15(join17(fdir, "refs.json"), JSON.stringify({ current, branches: refsBranches, tags: {} }, null, 2));
12940
+ writeFileSync16(join18(fdir, "refs.json"), JSON.stringify({ current, branches: refsBranches, tags: {} }, null, 2));
11492
12941
  const store2 = new Store;
11493
- for (const name of readdirSync8(join17(fdir, "objects"))) {
12942
+ for (const name of readdirSync8(join18(fdir, "objects"))) {
11494
12943
  if (name.endsWith(".tmp"))
11495
12944
  continue;
11496
12945
  try {
11497
- store2.put(decodeObject(readFileSync17(join17(fdir, "objects", name))));
12946
+ store2.put(decodeObject(readFileSync19(join18(fdir, "objects", name))));
11498
12947
  } catch {}
11499
12948
  }
11500
12949
  const onDisk = hydrate3(store2, head, target);
11501
- console.log(`imported ${commits} commit(s), ${branches.length} branch(es) from git -> ${args[2] || basename(gitPath)} (${onDisk} files; on branch ${current})`);
12950
+ console.log(`imported ${commits} commit(s), ${branches.length} branch(es) from git -> ${args[2] || basename2(gitPath)} (${onDisk} files; on branch ${current})`);
11502
12951
  } else if (sub === "export") {
11503
12952
  const { log } = open();
11504
- const gitPath = resolve4(cwd, args[1] || die("usage: sol git export <git-repo> [-b branch]"));
12953
+ const gitPath = resolve5(cwd, args[1] || die("usage: sol git export <git-repo> [-b branch]"));
11505
12954
  const store2 = loadStore();
11506
12955
  const head = await log.head() ?? "";
11507
12956
  if (!head || !listAll(store2, head).length) {
@@ -11520,15 +12969,111 @@ ${mrSummary2(pr)}`);
11520
12969
  }
11521
12970
  break;
11522
12971
  }
12972
+ case "agent": {
12973
+ if (!existsSync19(solDir))
12974
+ die("not a sol repo — run `sol init` first");
12975
+ const parentSol = parentSolDir(solDir) ?? solDir;
12976
+ const { createSession: createSession2, stopSession: stopSession2, sessions: sessions2, pruneSession: pruneSession2 } = await Promise.resolve().then(() => (init_agent_sessions(), exports_agent_sessions));
12977
+ const sub = args[0];
12978
+ const json = args.includes("--json");
12979
+ const flagVal = (name) => {
12980
+ const i = args.indexOf(name);
12981
+ return i >= 0 ? args[i + 1] : undefined;
12982
+ };
12983
+ switch (sub) {
12984
+ case "start": {
12985
+ const name = args.slice(1).find((a) => !a.startsWith("-")) || die('usage: sol agent start <name> [dir] [--actor <name>] [--check "<cmd>"]');
12986
+ const positionals2 = args.slice(1).filter((a) => !a.startsWith("-"));
12987
+ const sessionActor = flagVal("--actor") || actor;
12988
+ const metaRaw = flagVal("--meta");
12989
+ let metadata;
12990
+ if (metaRaw) {
12991
+ try {
12992
+ metadata = JSON.parse(metaRaw);
12993
+ } catch {
12994
+ die("--meta must be valid JSON");
12995
+ }
12996
+ }
12997
+ const rec = createSession2({
12998
+ parentSol,
12999
+ name,
13000
+ actor: sessionActor,
13001
+ dir: positionals2[1] ? resolve5(procCwd, positionals2[1]) : undefined,
13002
+ check: flagVal("--check"),
13003
+ metadata
13004
+ });
13005
+ if (json) {
13006
+ console.log(JSON.stringify(rec));
13007
+ break;
13008
+ }
13009
+ console.log(`started agent session '${rec.name}' (id ${rec.sessionId})`);
13010
+ console.log(` view ${rec.viewDir} (branch ${rec.branch} @ ${(rec.startHead || "empty").slice(0, 12)})`);
13011
+ console.log(` actor ${rec.actor}`);
13012
+ console.log(` identity ${rec.fingerprint}`);
13013
+ console.log(` -> wire the agent: cd ${rec.viewDir} && export SOL_ACTOR=${rec.actor} SOL_SIGNING_KEY=$(cat ${rec.signingKeyPath})`);
13014
+ console.log(` then edit + \`sol commit -m "..." <files>\`; converge with \`sol pull ${rec.viewDir}\` from the parent.`);
13015
+ console.log(` stop/clean: \`sol agent stop ${rec.sessionId}\` | \`sol agent cleanup ${rec.sessionId} --delete\``);
13016
+ break;
13017
+ }
13018
+ case "stop": {
13019
+ const id = args.slice(1).find((a) => !a.startsWith("-")) || die("usage: sol agent stop <sessionId>");
13020
+ const ok = stopSession2(parentSol, id);
13021
+ if (json)
13022
+ console.log(JSON.stringify({ stopped: ok, sessionId: id }));
13023
+ else
13024
+ console.log(ok ? `stopped session ${id} (the view + key remain — \`sol agent cleanup\` to remove)` : `no such session: ${id}`);
13025
+ if (!ok)
13026
+ process.exitCode = 1;
13027
+ break;
13028
+ }
13029
+ case "status":
13030
+ case undefined: {
13031
+ const id = args.slice(1).find((a) => !a.startsWith("-"));
13032
+ const all = await sessions2(parentSol);
13033
+ const list = id ? all.filter((s) => s.sessionId === id || s.name === id) : all;
13034
+ if (json) {
13035
+ console.log(JSON.stringify({ repo: parentSol, sessions: list }));
13036
+ break;
13037
+ }
13038
+ if (!list.length) {
13039
+ console.log(id ? `no such session: ${id}` : "no agent sessions — start one with `sol agent start <name>`");
13040
+ break;
13041
+ }
13042
+ console.log(`agent sessions of ${parentSol}:`);
13043
+ for (const s of list) {
13044
+ 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}]`);
13045
+ console.log(` ${s.viewDir}`);
13046
+ }
13047
+ console.log(" stop: `sol agent stop <id>` | cleanup: `sol agent cleanup <id> [--delete]` (or bare `cleanup` to prune stale)");
13048
+ break;
13049
+ }
13050
+ case "cleanup": {
13051
+ const id = args.slice(1).find((a) => !a.startsWith("-"));
13052
+ const removed = pruneSession2(parentSol, id, args.includes("--delete"));
13053
+ if (json)
13054
+ console.log(JSON.stringify({ removed }));
13055
+ else
13056
+ console.log(removed.length ? `cleaned ${removed.length} session(s): ${removed.join(", ")}` : id ? `no such session: ${id}` : "no stale sessions to clean");
13057
+ break;
13058
+ }
13059
+ default:
13060
+ die(`unknown: sol agent ${sub}
13061
+ sol agent start <name> [dir] [--actor N] [--check "<cmd>"]
13062
+ sol agent status [<id>]
13063
+ sol agent stop <id>
13064
+ sol agent cleanup [<id>] [--delete]`);
13065
+ }
13066
+ break;
13067
+ }
11523
13068
  case "view": {
11524
13069
  const { log } = open();
11525
13070
  if (readViewMeta(solDir))
11526
13071
  die("already inside a view — create views from the parent repo (its `.sol` owns the shared store + op-log).");
11527
13072
  const name = args.find((a) => !a.startsWith("-")) || die("usage: sol view <name> [dir]");
11528
13073
  const rest = args.filter((a) => !a.startsWith("-"));
11529
- const defaultDir = join17(dirname4(cwd), `${basename(cwd)}-${name}`);
11530
- const viewDir = rest[1] ? resolve4(procCwd, rest[1]) : defaultDir;
11531
- if (existsSync17(join17(viewDir, ".sol")))
13074
+ const defaultDir = join18(dirname5(cwd), `${basename2(cwd)}-${name}`);
13075
+ const viewDir = rest[1] ? resolve5(procCwd, rest[1]) : defaultDir;
13076
+ if (existsSync19(join18(viewDir, ".sol")))
11532
13077
  die("already a sol repo/view: " + viewDir);
11533
13078
  const branch = `view/${name}`;
11534
13079
  const startHead = await log.head() ?? emptyRoot(loadStore());
@@ -11542,7 +13087,7 @@ ${mrSummary2(pr)}`);
11542
13087
  break;
11543
13088
  }
11544
13089
  case "views": {
11545
- if (!existsSync17(solDir))
13090
+ if (!existsSync19(solDir))
11546
13091
  die("not a sol repo");
11547
13092
  const parentSol = parentSolDir(solDir) ?? solDir;
11548
13093
  const { pruneViews: pruneViews2, viewStatuses: viewStatuses2, sharedObjectCount: sharedObjectCount2 } = await Promise.resolve().then(() => (init_views(), exports_views));
@@ -11638,12 +13183,12 @@ ${mrSummary2(pr)}`);
11638
13183
  await startSecretMcp2({ solDir, http });
11639
13184
  break;
11640
13185
  }
11641
- if (!existsSync17(solDir))
13186
+ if (!existsSync19(solDir))
11642
13187
  die("not a sol repo — run `sol init` first");
11643
13188
  const { runEnv: runEnv2, runSecret: runSecret2, resolveReference: resolveReference2 } = await Promise.resolve().then(() => (init_secret2(), exports_secret2));
11644
13189
  const { loadSelfIdentity: loadSelfIdentity2, loadManageIdentity: loadManageIdentity2, fetchKey: fetchKey2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
11645
13190
  const { loadIdentity: loadIdentity2 } = await Promise.resolve().then(() => (init_identity_store(), exports_identity_store));
11646
- const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL).replace(/\/+$/, "");
13191
+ const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
11647
13192
  const selfPub = (account) => {
11648
13193
  const id = loadIdentity2();
11649
13194
  return id && id.accountId === account ? id.x25519Pub : undefined;
@@ -11655,7 +13200,7 @@ ${mrSummary2(pr)}`);
11655
13200
  };
11656
13201
  const dirEdPub = async (account) => (await fetchKey2(dirUrl, account))?.edPub;
11657
13202
  const remoteAnchorVerify = async () => {
11658
- const rcfg = resolveRemote(solDir);
13203
+ const rcfg = resolveRemote2(solDir);
11659
13204
  const token = process.env.SOL_TOKEN || await loadStoredToken();
11660
13205
  if (!rcfg || !token)
11661
13206
  return;
@@ -11705,6 +13250,11 @@ everyday (examples are copy-safe — use real filenames):
11705
13250
  sol rm old.txt delete a file (from the repo and disk)
11706
13251
  sol ignore "*.tmp" add an ignore pattern (no arg lists the active patterns)
11707
13252
  sol fsck / sol gc verify integrity / drop unreachable objects
13253
+ sol doctor full health check (op-log + objects + auth + files + sealed keyring)
13254
+ sol fsck --repair recover a corrupt repo (re-point a lost head / truncate to the last verified link)
13255
+ sol recover guide through recovery (rehydrate from cloud, list roots, restore a file from history)
13256
+ sol reflog the op-log as a ref-move history (head moves, by actor/branch/date)
13257
+ sol remote verify REMOTE-side health check (the backend's /fsck) with auth/network differentiation
11708
13258
  sol check --set "bun test" gate convergence on a test command: a merge that line-converges but FAILS
11709
13259
  the check is flagged as a SEMANTIC conflict (status state:"SEMANTIC"), not
11710
13260
  silently accepted. \`sol check\` runs it on demand. (sol check --help)
@@ -11741,6 +13291,16 @@ clone-free agent views (the worktree killer — N agents, one shared store on di
11741
13291
  \`sol pull <view>\` converges losslessly. (sol view --help)
11742
13292
  sol views list every view (name, dir, branch, head, author, active/stale) + --prune
11743
13293
 
13294
+ agent sessions (one atomic command provisions a working tree, a signing identity, an actor, and the test-gate):
13295
+ sol agent start <name> create a view + MINT a per-session Ed25519 signing key + wire the actor +
13296
+ inherit/set the test-gate, all at once. prints SOL_SIGNING_KEY / SOL_ACTOR to
13297
+ export so every op the agent authors is cryptographically attributable.
13298
+ (--actor <name>, --check "<cmd>", [dir], --json)
13299
+ sol agent status [<id>] list sessions (name, id, head, actor, fingerprint, running/stopped/stale)
13300
+ sol agent stop <id> mark a session stopped (presence signal; view + key remain)
13301
+ sol agent cleanup [<id>] remove the session record + its key (--delete also removes the view dir;
13302
+ bare \`cleanup\` prunes every stale session)
13303
+
11744
13304
  branches & tags:
11745
13305
  sol branch list branches (sol branch feature creates one at HEAD)
11746
13306
  sol switch feature switch to a branch (captures current work first, never loses it)
@@ -11757,6 +13317,7 @@ auth (sign in once; remote commands then use the cached token, no SOL_TOKEN need
11757
13317
  remotes (self-hostable backend; token in SOL_TOKEN or via sol auth login):
11758
13318
  sol clone [<url>] <owner>/<repo> [dir] clone a remote repo (checks out PRODUCTION); default dir = <repo>
11759
13319
  sol push / sol pull sync your commits with the remote (push registers your branch's head)
13320
+ sol sync --watch hands-off bidirectional sync daemon: auto-capture + push edits, converge safe pulls in
11760
13321
  sol push <repo> one-step share: no remote set? use the hosted Sol + <repo>, then push
11761
13322
  sol push --public <repo> create + push + make public in one step (new repos are private by default)
11762
13323
  sol promote [branch] point the remote's production branch at <branch> (default: current)
@@ -11796,10 +13357,15 @@ async function runCli(argv) {
11796
13357
  const msg = e?.message || String(e);
11797
13358
  if (/ -> 401\b/.test(msg) || /\b401 unauthorized\b/i.test(msg))
11798
13359
  authExpired();
13360
+ const { CorruptObjectError: CorruptObjectError2, CorruptRepoError: CorruptRepoError2 } = await Promise.resolve().then(() => (init_errors(), exports_errors));
13361
+ if (e instanceof CorruptObjectError2 || e instanceof CorruptRepoError2) {
13362
+ const { friendlyError: friendlyError2 } = await Promise.resolve().then(() => (init_errors_friendly(), exports_errors_friendly));
13363
+ die(friendlyError2(e));
13364
+ }
11799
13365
  die(msg);
11800
13366
  }
11801
13367
  }
11802
- 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;
13368
+ var CRED_PATH, DEFAULT_REMOTE_URL2, ATTEST_KEY, remoteUrlArg = (a) => /^https?:\/\//.test(a[0] ?? "") ? [a[0].replace(/\/+$/, ""), a.slice(1)] : [DEFAULT_REMOTE_URL2, a];
11803
13369
  var init_dispatch = __esm(() => {
11804
13370
  init_chain();
11805
13371
  init_diff();
@@ -11811,8 +13377,8 @@ var init_dispatch = __esm(() => {
11811
13377
  init_remote();
11812
13378
  init_lib();
11813
13379
  init_test_gate();
11814
- CRED_PATH = join17(homedir2(), ".sol", "credentials");
11815
- DEFAULT_REMOTE_URL = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
13380
+ CRED_PATH = join18(homedir2(), ".sol", "credentials");
13381
+ DEFAULT_REMOTE_URL2 = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
11816
13382
  ATTEST_KEY = process.env.SOL_ATTEST_KEY || undefined;
11817
13383
  });
11818
13384
 
@@ -11992,10 +13558,16 @@ var init_mcp_vcs_tools = __esm(() => {
11992
13558
  // src/bin/sol-mcp.ts
11993
13559
  var exports_sol_mcp = {};
11994
13560
  __export(exports_sol_mcp, {
13561
+ toolCallFor: () => toolCallFor,
11995
13562
  startWorkspaceMcp: () => startWorkspaceMcp
11996
13563
  });
11997
- import { mkdirSync as mkdirSync11 } from "node:fs";
11998
- import { dirname as dirname5, join as join18 } from "node:path";
13564
+ import { createHash as createHash7, randomUUID as randomUUID2 } from "node:crypto";
13565
+ import { mkdirSync as mkdirSync12 } from "node:fs";
13566
+ import { dirname as dirname6, join as join19 } from "node:path";
13567
+ function toolCallFor(name, args) {
13568
+ const argsHash = "h_" + createHash7("sha256").update(JSON.stringify(args ?? {})).digest("hex").slice(0, 16);
13569
+ return { name, argsHash, invocationId: randomUUID2() };
13570
+ }
11999
13571
  async function handle(ws, name, a) {
12000
13572
  switch (name) {
12001
13573
  case "sol_write":
@@ -12036,8 +13608,8 @@ async function handle(ws, name, a) {
12036
13608
  async function buildWorkspaceServer(solDir2) {
12037
13609
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
12038
13610
  const { CallToolRequestSchema, ListToolsRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
12039
- mkdirSync11(solDir2, { recursive: true });
12040
- const repoRoot2 = dirname5(solDir2);
13611
+ mkdirSync12(solDir2, { recursive: true });
13612
+ const repoRoot2 = dirname6(solDir2);
12041
13613
  if (process.cwd() !== repoRoot2) {
12042
13614
  try {
12043
13615
  process.chdir(repoRoot2);
@@ -12051,16 +13623,20 @@ async function buildWorkspaceServer(solDir2) {
12051
13623
  const r = await callVcsTool(req.params.name, req.params.arguments ?? {});
12052
13624
  return { content: [{ type: "text", text: r.text }], isError: r.isError };
12053
13625
  }
13626
+ if (AUTHORING_MUTATORS.has(req.params.name)) {
13627
+ ws.setToolCall(toolCallFor(req.params.name, req.params.arguments ?? {}));
13628
+ }
12054
13629
  try {
12055
13630
  return await handle(ws, req.params.name, req.params.arguments ?? {});
12056
13631
  } catch (e) {
13632
+ ws.setToolCall(undefined);
12057
13633
  return { content: [{ type: "text", text: "sol error: " + (e?.message ?? e) }], isError: true };
12058
13634
  }
12059
13635
  });
12060
13636
  return server;
12061
13637
  }
12062
13638
  async function startWorkspaceMcp(opts = {}) {
12063
- const solDir2 = opts.solDir || process.env.SOL_DIR || join18(process.cwd(), ".sol");
13639
+ const solDir2 = opts.solDir || process.env.SOL_DIR || join19(process.cwd(), ".sol");
12064
13640
  if (opts.http) {
12065
13641
  const { serveMcpHttp: serveMcpHttp2 } = await Promise.resolve().then(() => (init_mcp_http(), exports_mcp_http));
12066
13642
  await serveMcpHttp2(() => buildWorkspaceServer(solDir2), opts.http);
@@ -12070,12 +13646,13 @@ async function startWorkspaceMcp(opts = {}) {
12070
13646
  const server = await buildWorkspaceServer(solDir2);
12071
13647
  await server.connect(new StdioServerTransport);
12072
13648
  }
12073
- var authoringTools, VCS_TOOL_NAMES, tools, text = (s) => ({ content: [{ type: "text", text: s }] });
13649
+ var AUTHORING_MUTATORS, authoringTools, VCS_TOOL_NAMES, tools, text = (s) => ({ content: [{ type: "text", text: s }] });
12074
13650
  var init_sol_mcp = __esm(() => {
12075
13651
  init_file_store();
12076
13652
  init_workspace();
12077
13653
  init_mcp_http();
12078
13654
  init_mcp_vcs_tools();
13655
+ AUTHORING_MUTATORS = new Set(["sol_write", "sol_edit", "sol_move", "sol_rm", "sol_commit", "sol_checkpoint"]);
12079
13656
  authoringTools = [
12080
13657
  { name: "sol_write", description: "Create or overwrite a text file in the sol workspace. Authoring goes here — not to a disk.", inputSchema: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"] } },
12081
13658
  { name: "sol_read", description: "Read a text file from the sol workspace.", inputSchema: { type: "object", properties: { path: { type: "string" } }, required: ["path"] } },
@@ -12098,9 +13675,9 @@ __export(exports_dispatch2, {
12098
13675
  dispatch: () => dispatch2
12099
13676
  });
12100
13677
  import { execFileSync as execFileSync3 } from "node:child_process";
12101
- import { existsSync as existsSync18, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync2, readdirSync as readdirSync9, readFileSync as readFileSync18, rmSync as rmSync3, unlinkSync as unlinkSync8, watch as watch2, writeFileSync as writeFileSync16 } from "node:fs";
13678
+ import { accessSync as accessSync2, constants as fsConstants2, existsSync as existsSync20, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync2, readdirSync as readdirSync9, readFileSync as readFileSync20, rmSync as rmSync4, unlinkSync as unlinkSync8, watch as watch2, writeFileSync as writeFileSync17 } from "node:fs";
12102
13679
  import { homedir as homedir3, hostname as hostname2, platform as platform3, tmpdir as tmpdir2 } from "node:os";
12103
- import { basename as basename2, dirname as dirname6, join as join19, resolve as resolve5, sep as sep3 } from "node:path";
13680
+ import { basename as basename3, dirname as dirname7, join as join20, resolve as resolve6, sep as sep3 } from "node:path";
12104
13681
  function globCovers2(pattern, path) {
12105
13682
  let re = "";
12106
13683
  for (let i = 0;i < pattern.length; i++) {
@@ -12120,6 +13697,14 @@ function globCovers2(pattern, path) {
12120
13697
  }
12121
13698
  return new RegExp(`^${re}$`).test(path);
12122
13699
  }
13700
+ function writableTag2(path) {
13701
+ try {
13702
+ accessSync2(path, fsConstants2.R_OK | fsConstants2.W_OK);
13703
+ return "RW";
13704
+ } catch {
13705
+ return "READ-ONLY";
13706
+ }
13707
+ }
12123
13708
  function tokenClaims2(token) {
12124
13709
  try {
12125
13710
  return JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64url").toString());
@@ -12128,11 +13713,11 @@ function tokenClaims2(token) {
12128
13713
  }
12129
13714
  }
12130
13715
  async function loadStoredToken2() {
12131
- if (!existsSync18(CRED_PATH2))
13716
+ if (!existsSync20(CRED_PATH2))
12132
13717
  return;
12133
13718
  let creds;
12134
13719
  try {
12135
- creds = JSON.parse(readFileSync18(CRED_PATH2, "utf8"));
13720
+ creds = JSON.parse(readFileSync20(CRED_PATH2, "utf8"));
12136
13721
  } catch {
12137
13722
  return;
12138
13723
  }
@@ -12147,7 +13732,7 @@ async function loadStoredToken2() {
12147
13732
  if (res.ok) {
12148
13733
  const r = await res.json();
12149
13734
  if (r.accessToken) {
12150
- writeFileSync16(CRED_PATH2, JSON.stringify({ ...creds, accessToken: r.accessToken, refreshToken: r.refreshToken ?? creds.refreshToken }, null, 2), { mode: 384 });
13735
+ writeFileSync17(CRED_PATH2, JSON.stringify({ ...creds, accessToken: r.accessToken, refreshToken: r.refreshToken ?? creds.refreshToken }, null, 2), { mode: 384 });
12151
13736
  return r.accessToken;
12152
13737
  }
12153
13738
  }
@@ -12168,7 +13753,7 @@ function authHost2() {
12168
13753
  if (process.env.SOL_AUTH)
12169
13754
  return process.env.SOL_AUTH.replace(/\/+$/, "");
12170
13755
  try {
12171
- const w = JSON.parse(readFileSync18(CRED_PATH2, "utf8")).webUrl;
13756
+ const w = JSON.parse(readFileSync20(CRED_PATH2, "utf8")).webUrl;
12172
13757
  if (w)
12173
13758
  return w.replace(/\/+$/, "");
12174
13759
  } catch {}
@@ -12192,11 +13777,11 @@ async function resolveMcpHttp2(a, label) {
12192
13777
  die(`invalid --port: ${portRaw}`);
12193
13778
  return { token, port, host: flagVal("--host"), label: label ?? (a.includes("--secret") || a.includes("--secrets") ? "sol-secrets" : "sol") };
12194
13779
  }
12195
- function resolveRemote2(solDir2) {
13780
+ function resolveRemote3(solDir2) {
12196
13781
  const cfg = loadRemote(solDir2);
12197
13782
  if (!cfg)
12198
13783
  return;
12199
- return cfg.url ? cfg : { ...cfg, url: DEFAULT_REMOTE_URL2 };
13784
+ return cfg.url ? cfg : { ...cfg, url: DEFAULT_REMOTE_URL3 };
12200
13785
  }
12201
13786
  async function pushEnvState2(solDirPath, cfg, token) {
12202
13787
  const { readEnvStateBundle: readEnvStateBundle2 } = await Promise.resolve().then(() => (init_anchor(), exports_anchor));
@@ -12215,6 +13800,17 @@ ENV ANCHOR REJECTED by the remote: ${e?.message ?? String(e)}
12215
13800
  process.exitCode = 1;
12216
13801
  }
12217
13802
  }
13803
+ async function surfaceOwner2(cfg, token) {
13804
+ const slash = cfg.repo.indexOf("/");
13805
+ if (slash < 0)
13806
+ return;
13807
+ const owner = cfg.repo.slice(0, slash);
13808
+ try {
13809
+ const res = await ownerSet(cfg, token, owner);
13810
+ if (res.error)
13811
+ console.error(`note: could not set repo owner to '${owner}': ${res.error}`);
13812
+ } catch {}
13813
+ }
12218
13814
  async function pullEnvState2(solDirPath, cfg, token) {
12219
13815
  try {
12220
13816
  const { bundle } = await remoteEnvPull(cfg, token);
@@ -12247,7 +13843,7 @@ function cliVersion2() {
12247
13843
  if (typeof __SOL_COMPILED_VERSION__ === "string" && __SOL_COMPILED_VERSION__)
12248
13844
  return __SOL_COMPILED_VERSION__;
12249
13845
  try {
12250
- return JSON.parse(readFileSync18(new URL("./package.json", import.meta.url), "utf8")).version || "dev";
13846
+ return JSON.parse(readFileSync20(new URL("./package.json", import.meta.url), "utf8")).version || "dev";
12251
13847
  } catch {
12252
13848
  return "dev";
12253
13849
  }
@@ -12294,7 +13890,7 @@ examples:
12294
13890
  sol commit "add login route"
12295
13891
  sol commit -m "fix parser" src/parse.ts src/lex.ts`,
12296
13892
  push: `sol push push the current branch to the configured remote (converging — never FF-rejected)
12297
- sol push <repo> if no remote is set, use the hosted Sol (${DEFAULT_REMOTE_URL2}) + this repo name, then push
13893
+ sol push <repo> if no remote is set, use the hosted Sol (${DEFAULT_REMOTE_URL3}) + this repo name, then push
12298
13894
  sol push --create <repo> same, explicit form (creates the repo on first push)
12299
13895
  sol push --public <repo> create + push + make it public in ONE step (else new repos are private)
12300
13896
 
@@ -12317,6 +13913,21 @@ examples:
12317
13913
  notes:
12318
13914
  refuses to run over a dirty tree — commit or discard first. conflicts land in the working tree with markers.
12319
13915
  refuses to run while UNRESOLVED <<<<<<< markers remain — resolve them and \`sol commit\`, then pull again.`,
13916
+ sync: `sol sync --watch HANDS-OFF bidirectional sync daemon — auto-capture + push every edit,
13917
+ and converge safe remote updates in on a timer. no CLI invocations needed:
13918
+ the working tree stays in sync while every local edit is durably captured.
13919
+ --poll-interval <ms> how often to poll the remote for updates (default 5000)
13920
+ --no-push-immediately capture locally but do NOT auto-push (push later with \`sol push\`)
13921
+ --dry-run log what it WOULD do, mutate nothing
13922
+
13923
+ how it works (three loops over the same core as watch/push/pull):
13924
+ 1. every file change is auto-captured into the op-log (nothing lost, even offline)
13925
+ 2. after each capture it pushes the delta; the server may converge a concurrent commit in
13926
+ 3. it polls the remote; a moved head pulls in via converge() — NEVER clobbering an in-flight edit
13927
+ notes:
13928
+ needs a remote (\`sol remote <url> <repo>\` or \`sol push <repo>\` once) + auth (\`sol auth login\` / SOL_TOKEN).
13929
+ a dirty tree DEFERS a pull to the next poll (your edits are captured first). Ctrl-C exits cleanly.
13930
+ transient network errors are logged + retried; a fatal error (corrupt repo / auth) exits loud.`,
12320
13931
  hide: `sol hide <pattern> [--role write|admin] [--team <id>] [--users a,b] [--escrow] [--no-list] [--hide-names] [--hide-existence] [--strict]
12321
13932
  add/update a VisibilityPolicy RULE — the HEADLINE verb. binds a glob over tree
12322
13933
  paths to an AUDIENCE (who may DECRYPT). the rule is host-visible METADATA (no
@@ -12430,6 +14041,9 @@ examples:
12430
14041
  sol keys verify alice 1a2b-3c4d-... # confirm alice's key out-of-band before sealing to her`,
12431
14042
  remote: `sol remote show the configured remote
12432
14043
  sol remote <url> <repo> set the remote (url + repo name)
14044
+ sol remote verify REMOTE-side health check (the backend's /fsck) — differentiates auth
14045
+ (session expired / no access), backend (5xx, transient), connectivity,
14046
+ and a 200-but-corrupt remote repo. needs SOL_TOKEN (or \`sol auth login\`).
12433
14047
 
12434
14048
  tip: \`sol push <repo>\` configures the hosted remote for you in one step.`,
12435
14049
  branch: `sol branch list branches (* = current)
@@ -12494,6 +14108,27 @@ what it's for (TEST-GATED CONVERGENCE):
12494
14108
  hydrate the current tree to a sandbox, run the command, capture produced files back into a commit.
12495
14109
  --isolate confines the run (no network, writes stay in the sandbox).`,
12496
14110
  promote: "sol promote [branch] point the remote's production branch at <branch> (default: current branch)",
14111
+ doctor: `sol doctor — comprehensive LOCAL health check (exit 0 = OK, 1 = problems found)
14112
+
14113
+ checks: op-log chain integrity + contiguity, object reachability from head, auth (SOL_TOKEN / stored
14114
+ credentials + expiry), .sol file permissions, and the sealed keyring. read-only — it diagnoses but never
14115
+ changes anything. to RECOVER from what it finds: \`sol fsck --repair\` (op-log/head) or \`sol recover\` (cloud/backup).`,
14116
+ fsck: `sol fsck verify the op-log chain + object reachability (exit 0 = OK, 1 = problems)
14117
+ sol fsck --repair recover a corrupt repo:
14118
+ - head pointer lost -> re-point to the last resolvable historical root
14119
+ - head dangling -> re-point to the newest root the store still has
14120
+ - op-log chain broken -> truncate to the last verified link, re-point head
14121
+
14122
+ every repair appends an explicit CHECKPOINT recording what happened (visible in \`sol log\`/\`sol reflog\`),
14123
+ so the recovery is itself in the tamper-evident history. idempotent: re-running on a healthy repo is a no-op.`,
14124
+ recover: `sol recover the recovery guide (the menu below)
14125
+ sol recover --from-remote <url> <repo> rehydrate from the cloud
14126
+ sol recover --list-all-roots every root the repo ever landed on (find a lost head)
14127
+ sol recover --file <path> [--from <seq|hash>] restore a file from history (even if the tree dropped it)
14128
+ sol recover --dump-log [<from> [<to>]] dump raw op-log entries as JSON (manual salvage)`,
14129
+ reflog: `sol reflog [<branch>] [--by <actor>] [--since <YYYY-MM-DD>]
14130
+ the op-log as a ref-move history (git reflog): each line is a head move (from -> to) with seq, actor, time,
14131
+ and message. a merge shows as "merge". a <branch> arg keeps only checkpoints whose message labels it.`,
12497
14132
  git: `sol git import <repo> [dir] import a git repo's HEAD into a new Sol repo
12498
14133
  sol git export <repo> [-b branch] replay the Sol commit DAG as git history (+ provenance trailers), then \`git push\``,
12499
14134
  mr: `sol mr open [--from <branch>] [--to <branch>] [--upstream <repo>] -t "title" [-m body]
@@ -12783,7 +14418,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
12783
14418
  try {
12784
14419
  const { loadSelfIdentity: loadSelfIdentity2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
12785
14420
  const { openContent: openContent2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
12786
- const ring = existsSync18(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
14421
+ const ring = existsSync20(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
12787
14422
  const self = loadSelfIdentity2();
12788
14423
  setSealedDecryptor((boxStr) => {
12789
14424
  const box = JSON.parse(boxStr);
@@ -12793,19 +14428,19 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
12793
14428
  } catch {}
12794
14429
  }
12795
14430
  const servesMcp = cmd === "mcp" || cmd === "secret" && args[0] === "mcp";
12796
- 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;
14431
+ 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;
12797
14432
  try {
12798
14433
  switch (cmd) {
12799
14434
  case "init": {
12800
- const here = join19(procCwd, ".sol");
12801
- if (existsSync18(here))
14435
+ const here = join20(procCwd, ".sol");
14436
+ if (existsSync20(here))
12802
14437
  die("already a sol repo: " + procCwd);
12803
14438
  if (repoRoot && repoRoot !== procCwd && !args.includes("--force")) {
12804
14439
  die(`already inside a Sol repo at ${repoRoot}
12805
14440
  -> just commit into it: \`sol commit ...\` works from here (sol walks up to find the repo)
12806
14441
  -> to nest a NEW repo here anyway: \`sol init --force\``);
12807
14442
  }
12808
- mkdirSync12(here, { recursive: true });
14443
+ mkdirSync13(here, { recursive: true });
12809
14444
  new FileStore(here);
12810
14445
  console.log(`initialized empty sol repo in ${here}`);
12811
14446
  break;
@@ -12835,7 +14470,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
12835
14470
  const h = tok ? identityFromToken2(tok)?.handle : undefined;
12836
14471
  return h || die("no account identity — run `sol auth login` (or set SOL_ACCOUNT for self-host)");
12837
14472
  };
12838
- const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
14473
+ const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL3).replace(/\/+$/, "");
12839
14474
  const sub = args[0];
12840
14475
  const wantJson = args.includes("--json");
12841
14476
  if (sub === "init") {
@@ -12921,7 +14556,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
12921
14556
  if (sub === "import") {
12922
14557
  const passphrase = process.env.SOL_KEYSTORE_PASSPHRASE || die("set SOL_KEYSTORE_PASSPHRASE to decrypt the bundle");
12923
14558
  const file = args[1] || die("usage: sol keys import <bundle.json> (set SOL_KEYSTORE_PASSPHRASE)");
12924
- const bundle = JSON.parse(readFileSync18(file, "utf8"));
14559
+ const bundle = JSON.parse(readFileSync20(file, "utf8"));
12925
14560
  let recovered;
12926
14561
  try {
12927
14562
  recovered = importBundle2(bundle, passphrase);
@@ -12952,7 +14587,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
12952
14587
  break;
12953
14588
  }
12954
14589
  case "trust": {
12955
- if (!existsSync18(solDir))
14590
+ if (!existsSync20(solDir))
12956
14591
  die("not a sol repo — run `sol init` first");
12957
14592
  const map = loadTrust();
12958
14593
  if (args[0] === "--remove" || args[0] === "-r") {
@@ -12999,7 +14634,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
12999
14634
  }
13000
14635
  case "track":
13001
14636
  case "add": {
13002
- if (!existsSync18(solDir))
14637
+ if (!existsSync20(solDir))
13003
14638
  die("not a sol repo — run `sol init` first");
13004
14639
  const files = args.filter((a) => a !== "." && !a.startsWith("-"));
13005
14640
  if (!files.length) {
@@ -13010,7 +14645,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13010
14645
  let n = 0;
13011
14646
  for (const f of files) {
13012
14647
  const rf = repoRel(f);
13013
- if (!existsSync18(join19(cwd, rf))) {
14648
+ if (!existsSync20(join20(cwd, rf))) {
13014
14649
  console.error("skip (not on disk): " + f);
13015
14650
  continue;
13016
14651
  }
@@ -13039,14 +14674,14 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13039
14674
  if (!message)
13040
14675
  die('commit needs a message: sol commit "what you did" (scoped: sol commit -m "msg" file1 file2)');
13041
14676
  const parentHead = await repo.head();
13042
- const mergeHeadPath = join19(solDir, "MERGE_HEAD");
13043
- const parent2 = existsSync18(mergeHeadPath) ? readFileSync18(mergeHeadPath, "utf8").trim() || undefined : undefined;
14677
+ const mergeHeadPath = join20(solDir, "MERGE_HEAD");
14678
+ const parent2 = existsSync20(mergeHeadPath) ? readFileSync20(mergeHeadPath, "utf8").trim() || undefined : undefined;
13044
14679
  let changed = 0;
13045
14680
  let commitRoot = parentHead;
13046
14681
  if (paths.length) {
13047
14682
  for (const p of paths) {
13048
14683
  const rp = repoRel(p);
13049
- if (existsSync18(join19(cwd, rp))) {
14684
+ if (existsSync20(join20(cwd, rp))) {
13050
14685
  if (await snapshotFile(repo, rp))
13051
14686
  changed++;
13052
14687
  } else if ((await repo.list()).includes(rp)) {
@@ -13101,7 +14736,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13101
14736
  }
13102
14737
  case "status": {
13103
14738
  const { repo, log } = open();
13104
- const refs = existsSync18(refsPath()) ? await loadRefs(log) : undefined;
14739
+ const refs = existsSync20(refsPath()) ? await loadRefs(log) : undefined;
13105
14740
  const head = await repo.head();
13106
14741
  const headOp = head ? [...await log.history()].reverse().find((o) => o.rootAfter === head) : undefined;
13107
14742
  const headBy = headOp?.by ?? "?";
@@ -13191,7 +14826,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13191
14826
  const { listAll: listAll3 } = await Promise.resolve().then(() => (init_tree(), exports_tree));
13192
14827
  const { loadSelfIdentity: loadSelfIdentity2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
13193
14828
  const { openContent: openContent2, UNREADABLE: UNREADABLE2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
13194
- const ring = existsSync18(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
14829
+ const ring = existsSync20(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
13195
14830
  const self = loadSelfIdentity2();
13196
14831
  const decrypt = (boxStr) => {
13197
14832
  try {
@@ -13239,7 +14874,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13239
14874
  const { readFile: readTree, entryKindAt: kindAt } = await Promise.resolve().then(() => (init_tree(), exports_tree));
13240
14875
  const { loadSelfIdentity: loadSelfIdentity2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
13241
14876
  const { openContent: openContent2, UNREADABLE: UNREADABLE2, KeyRing: KeyRing3 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
13242
- const ring = existsSync18(solDir) ? loadKeyRing() : new KeyRing3;
14877
+ const ring = existsSync20(solDir) ? loadKeyRing() : new KeyRing3;
13243
14878
  const self = loadSelfIdentity2();
13244
14879
  const decrypt = (boxStr) => {
13245
14880
  try {
@@ -13338,7 +14973,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13338
14973
  };
13339
14974
  const live = await log.head() ?? "";
13340
14975
  const refArg = args.find((a) => !a.startsWith("-"));
13341
- const lrefs = existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")) : null;
14976
+ const lrefs = existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")) : null;
13342
14977
  let tipRoot = live;
13343
14978
  if (refArg) {
13344
14979
  tipRoot = lrefs?.branches[refArg]?.head ?? refArg;
@@ -13391,7 +15026,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13391
15026
  const path = args[0] || die("rm needs a path");
13392
15027
  let onDisk = false;
13393
15028
  try {
13394
- unlinkSync8(join19(cwd, path));
15029
+ unlinkSync8(join20(cwd, path));
13395
15030
  onDisk = true;
13396
15031
  } catch {}
13397
15032
  if (onDisk) {
@@ -13589,15 +15224,167 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13589
15224
  };
13590
15225
  walk(head);
13591
15226
  }
13592
- const ok = chainOk && missing.length === 0;
15227
+ const headLost = head === undefined && ops.length > 0;
15228
+ const ok = chainOk && missing.length === 0 && !headLost;
15229
+ if (args.includes("--repair")) {
15230
+ if (ok) {
15231
+ console.log("fsck --repair: nothing to repair — the repo is OK");
15232
+ process.exitCode = 0;
15233
+ break;
15234
+ }
15235
+ const { applyRepair: applyRepair2 } = await Promise.resolve().then(() => (init_admin_repair(), exports_admin_repair));
15236
+ const { FileStore: FileStore2 } = await Promise.resolve().then(() => (init_file_store(), exports_file_store));
15237
+ const fileStore = new FileStore2(solDir, objectsDir());
15238
+ const plan = await applyRepair2({
15239
+ store: fileStore,
15240
+ log,
15241
+ truncate: async (toSeq) => truncateOpLog(toSeq),
15242
+ setHead: async (h) => setOpLogHead(h)
15243
+ }, "sol-doctor");
15244
+ console.log(`fsck --repair: ${plan.reason}`);
15245
+ if (plan.kind === "noop")
15246
+ console.log(" (already healthy — nothing changed)");
15247
+ else if (plan.kind === "unfixable") {
15248
+ console.log(" status: UNFIXABLE — local repair cannot recover this; run `sol recover --from-remote <url> <repo>` to rehydrate");
15249
+ process.exitCode = 1;
15250
+ break;
15251
+ } else {
15252
+ console.log(` ${plan.note}`);
15253
+ console.log(" status: repaired — run `sol status` (or `sol fsck`) to verify");
15254
+ }
15255
+ process.exitCode = 0;
15256
+ break;
15257
+ }
13593
15258
  console.log(`fsck: ${ok ? "OK" : "PROBLEMS FOUND"}`);
13594
15259
  console.log(` op-log: ${ops.length} op(s), chain ${chainOk ? "valid + contiguous" : "BROKEN — " + chainErr}`);
13595
- console.log(` head: ${head ? head.slice(0, 16) : "(empty)"}, ${missing.length} missing object(s)`);
15260
+ if (headLost)
15261
+ console.log(` head: LOST (${ops.length} op(s) but no head pointer) — run \`sol fsck --repair\``);
15262
+ else
15263
+ console.log(` head: ${head ? head.slice(0, 16) : "(empty)"}, ${missing.length} missing object(s)`);
13596
15264
  for (const mh of missing)
13597
15265
  console.log(" missing " + mh);
15266
+ if (!ok)
15267
+ console.log(" -> `sol doctor` for a full diagnosis, `sol fsck --repair` to recover");
13598
15268
  process.exitCode = ok ? 0 : 1;
13599
15269
  break;
13600
15270
  }
15271
+ case "doctor": {
15272
+ const { runDoctor: runDoctor2, renderDoctorIntegrity: renderDoctorIntegrity2 } = await Promise.resolve().then(() => (init_admin(), exports_admin));
15273
+ const { log } = open();
15274
+ const store2 = loadStore();
15275
+ const asyncStore = { get: async (h) => store2.get(h), put: async () => die("doctor is read-only"), has: async (h) => store2.has(h) };
15276
+ const report = await runDoctor2(asyncStore, log);
15277
+ console.log("sol doctor");
15278
+ for (const l of renderDoctorIntegrity2(report))
15279
+ console.log(l);
15280
+ const envTok = process.env.SOL_TOKEN;
15281
+ if (envTok) {
15282
+ const id = identityFromToken2(envTok);
15283
+ console.log(` auth: SOL_TOKEN set${id?.handle ? ` (@${id.handle})` : id?.email ? ` (${id.email})` : ""}`);
15284
+ } else if (existsSync20(CRED_PATH2)) {
15285
+ try {
15286
+ const creds = JSON.parse(readFileSync20(CRED_PATH2, "utf8"));
15287
+ const c = creds.accessToken ? tokenClaims2(creds.accessToken) : {};
15288
+ const exp = typeof c.exp === "number" ? new Date(c.exp * 1000).toISOString().slice(0, 10) : "?";
15289
+ const stale = typeof c.exp === "number" && c.exp * 1000 <= Date.now();
15290
+ console.log(` auth: logged in${c.handle ? ` as @${c.handle}` : ""}${stale ? " (EXPIRED — run `sol auth login`)" : ` (expires ${exp})`}`);
15291
+ } catch {
15292
+ console.log(" auth: stored credentials unreadable — run `sol auth login`");
15293
+ }
15294
+ } else {
15295
+ console.log(" auth: not logged in — `sol auth login` (or set SOL_TOKEN) for remote commands");
15296
+ }
15297
+ const objDir = join20(solDir, "objects");
15298
+ const opsFile = join20(solDir, "ops.jsonl");
15299
+ console.log(` files: .sol/objects/ ${writableTag2(objDir)}, .sol/ops.jsonl ${existsSync20(opsFile) ? writableTag2(opsFile) : "(none yet)"}`);
15300
+ const keyringPath = join20(homedir3(), ".sol", "keyring.json");
15301
+ console.log(` sealed: ${existsSync20(keyringPath) ? "keyring found, ready" : "no keyring (only needed for sealed paths)"}`);
15302
+ console.log(`status: ${report.ok ? "OK" : "PROBLEMS FOUND"}`);
15303
+ if (!report.ok)
15304
+ console.log(" -> `sol fsck --repair` to recover the op-log/head; `sol recover` for cloud/backup rehydrate");
15305
+ process.exitCode = report.ok ? 0 : 1;
15306
+ break;
15307
+ }
15308
+ case "reflog": {
15309
+ const { renderReflog: renderReflog2 } = await Promise.resolve().then(() => exports_admin_reflog);
15310
+ const { log } = open();
15311
+ const ops = await log.history();
15312
+ const flag2 = (name) => {
15313
+ const i = args.indexOf(name);
15314
+ return i >= 0 ? args[i + 1] : undefined;
15315
+ };
15316
+ const branchFilter = args[0] && !args[0].startsWith("--") ? args[0] : undefined;
15317
+ const by = flag2("--by");
15318
+ const sinceStr = flag2("--since");
15319
+ const since = sinceStr ? Date.parse(sinceStr) : undefined;
15320
+ if (sinceStr && Number.isNaN(since))
15321
+ die(`--since: not a date: ${sinceStr} (try YYYY-MM-DD)`);
15322
+ const lines2 = renderReflog2(ops, { branchFilter, actor: by, since });
15323
+ if (!lines2.length)
15324
+ console.log("(no matching ops)");
15325
+ for (const l of lines2)
15326
+ console.log(l);
15327
+ break;
15328
+ }
15329
+ case "recover": {
15330
+ const { RECOVER_HELP: RECOVER_HELP2, listAllRoots: listAllRoots2, restoreFileFromHistory: restoreFileFromHistory2, dumpLog: dumpLog2 } = await Promise.resolve().then(() => (init_admin_recover(), exports_admin_recover));
15331
+ const flag2 = (name) => {
15332
+ const i = args.indexOf(name);
15333
+ return i >= 0 ? args[i + 1] : undefined;
15334
+ };
15335
+ if (!args.length || args[0] === "--help") {
15336
+ console.log(RECOVER_HELP2);
15337
+ break;
15338
+ }
15339
+ if (args.includes("--from-remote")) {
15340
+ const url = args[args.indexOf("--from-remote") + 1];
15341
+ const repoName = args[args.indexOf("--from-remote") + 2];
15342
+ if (!url || !repoName)
15343
+ die("usage: sol recover --from-remote <url> <repo>");
15344
+ console.log(`to rehydrate from the cloud, clone the remote into a fresh dir:
15345
+ sol clone ${url} ${repoName}
15346
+ then copy your uncommitted working files in. (a destructive in-place rehydrate is intentionally manual.)`);
15347
+ break;
15348
+ }
15349
+ const { log } = open();
15350
+ const store2 = loadStore();
15351
+ const ops = await log.history();
15352
+ const asyncStore = { get: async (h) => store2.get(h), put: async () => die("recover is read-only"), has: async (h) => store2.has(h) };
15353
+ if (args.includes("--list-all-roots")) {
15354
+ const roots = await listAllRoots2(ops, async (h) => store2.has(h));
15355
+ for (const r of roots)
15356
+ console.log(` ${r.root.slice(0, 16)} seq ${r.seq} ${r.resolvable ? "resolvable" : "MISSING"}${r.message ? ` ${r.message}` : ""}`);
15357
+ if (!roots.length)
15358
+ console.log(" (no roots — empty repo)");
15359
+ break;
15360
+ }
15361
+ if (args.includes("--file")) {
15362
+ const path = flag2("--file") || die("usage: sol recover --file <path> [--from <seq|hash>]");
15363
+ const fromStr = flag2("--from");
15364
+ const from = fromStr ? /^\d+$/.test(fromStr) ? Number(fromStr) : fromStr : undefined;
15365
+ const rec = await restoreFileFromHistory2(asyncStore, ops, path, from);
15366
+ if (!rec.found)
15367
+ die(rec.reason);
15368
+ if ("sealed" in rec) {
15369
+ console.log(`${path} is sealed at seq ${rec.fromSeq} — recovered the opaque box (open it with your key):`);
15370
+ console.log(rec.box);
15371
+ } else {
15372
+ const out = rec.encoding === "base64" ? Buffer.from(rec.content, "base64") : rec.content;
15373
+ writeFileSync17(join20(cwd, path), out);
15374
+ console.log(`restored ${path} from seq ${rec.fromSeq} (root ${rec.fromRoot.slice(0, 16)}) -> wrote to disk; \`sol add ${path}\` to re-track`);
15375
+ }
15376
+ break;
15377
+ }
15378
+ if (args.includes("--dump-log")) {
15379
+ const i = args.indexOf("--dump-log");
15380
+ const fromSeq = args[i + 1] && /^\d+$/.test(args[i + 1]) ? Number(args[i + 1]) : undefined;
15381
+ const toSeq = args[i + 2] && /^\d+$/.test(args[i + 2]) ? Number(args[i + 2]) : undefined;
15382
+ console.log(dumpLog2(ops, fromSeq, toSeq));
15383
+ break;
15384
+ }
15385
+ console.log(RECOVER_HELP2);
15386
+ break;
15387
+ }
13601
15388
  case "gc": {
13602
15389
  const { log } = open();
13603
15390
  const store2 = loadStore();
@@ -13619,16 +15406,16 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13619
15406
  if (op.prov)
13620
15407
  walk(op.prov);
13621
15408
  }
13622
- const objDir = join19(solDir, "objects");
15409
+ const objDir = join20(solDir, "objects");
13623
15410
  let removed = 0;
13624
15411
  for (const name of readdirSync9(objDir)) {
13625
15412
  if (name.endsWith(".tmp") || !reachable.has(name)) {
13626
- unlinkSync8(join19(objDir, name));
15413
+ unlinkSync8(join20(objDir, name));
13627
15414
  removed++;
13628
15415
  }
13629
15416
  }
13630
15417
  console.log(`gc: kept ${reachable.size} object(s), removed ${removed} unreachable`);
13631
- if (existsSync18(join19(solDir, "env", "seal"))) {
15418
+ if (existsSync20(join20(solDir, "env", "seal"))) {
13632
15419
  const { gcStaleStanzas: gcStaleStanzas2 } = await Promise.resolve().then(() => (init_secret(), exports_secret));
13633
15420
  const st = gcStaleStanzas2(solDir);
13634
15421
  if (st.removed)
@@ -13643,8 +15430,8 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13643
15430
  console.log(p);
13644
15431
  break;
13645
15432
  }
13646
- const f = join19(cwd, ".solignore");
13647
- const lead = existsSync18(f) && !readFileSync18(f, "utf8").endsWith(`
15433
+ const f = join20(cwd, ".solignore");
15434
+ const lead = existsSync20(f) && !readFileSync20(f, "utf8").endsWith(`
13648
15435
  `) ? `
13649
15436
  ` : "";
13650
15437
  appendFileSync2(f, lead + pat + `
@@ -13655,7 +15442,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13655
15442
  case "hide": {
13656
15443
  const { repo } = open();
13657
15444
  const wantJson = args.includes("--json");
13658
- const cfg = resolveRemote2(solDir) || die("no remote — `sol push <repo>` (or `sol remote <url> <repo>`) first; the policy lives on the repo");
15445
+ const cfg = resolveRemote3(solDir) || die("no remote — `sol push <repo>` (or `sol remote <url> <repo>`) first; the policy lives on the repo");
13659
15446
  const token = process.env.SOL_TOKEN || authExpired2();
13660
15447
  const sub = args[0];
13661
15448
  const flag2 = (name) => {
@@ -13760,12 +15547,12 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13760
15547
  let removed = 0;
13761
15548
  {
13762
15549
  const res = await scrubHistory2(solDir, ops, targets);
13763
- writeFileSync16(join19(solDir, "ops.jsonl"), res.ops.map((o) => JSON.stringify(o)).join(`
15550
+ writeFileSync17(join20(solDir, "ops.jsonl"), res.ops.map((o) => JSON.stringify(o)).join(`
13764
15551
  `) + (res.ops.length ? `
13765
15552
  ` : ""));
13766
- writeFileSync16(join19(solDir, "HEAD"), JSON.stringify({ head: res.newHead, seq: res.newSeq, logTip: res.newLogTip }));
13767
- if (existsSync18(refsPath())) {
13768
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
15553
+ writeFileSync17(join20(solDir, "HEAD"), JSON.stringify({ head: res.newHead, seq: res.newSeq, logTip: res.newLogTip }));
15554
+ if (existsSync20(refsPath())) {
15555
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
13769
15556
  for (const b of Object.values(refs.branches)) {
13770
15557
  if (b.head && res.rootMap.has(b.head))
13771
15558
  b.head = res.rootMap.get(b.head);
@@ -13788,7 +15575,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13788
15575
  break;
13789
15576
  }
13790
15577
  if (reapply) {
13791
- const cfg = resolveRemote2(solDir) || die("no remote — the policy lives on the repo; `sol remote <url> <repo>` first");
15578
+ const cfg = resolveRemote3(solDir) || die("no remote — the policy lives on the repo; `sol remote <url> <repo>` first");
13792
15579
  const token = process.env.SOL_TOKEN || authExpired2();
13793
15580
  const { SealedClient: SealedClient3 } = await Promise.resolve().then(() => (init_sealed_client(), exports_sealed_client));
13794
15581
  const { UNREADABLE: UNREADABLE2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
@@ -13855,7 +15642,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13855
15642
  const { SealedClient: SealedClient3 } = await Promise.resolve().then(() => (init_sealed_client(), exports_sealed_client));
13856
15643
  const { parseRecipient: parseRecipient3, resolveRecipient: resolveRecipient3, recordAudience: recordAudience3 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
13857
15644
  const { pubFingerprint: pubFingerprint2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
13858
- const dirUrl2 = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
15645
+ const dirUrl2 = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL3).replace(/\/+$/, "");
13859
15646
  const explicit = positional.slice(1);
13860
15647
  const recipientPubKeys2 = {};
13861
15648
  const audienceAccounts2 = [];
@@ -13863,7 +15650,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13863
15650
  const crossAccount2 = [];
13864
15651
  let policyApplied2 = false;
13865
15652
  if (!explicit.length) {
13866
- const cfg = resolveRemote2(solDir);
15653
+ const cfg = resolveRemote3(solDir);
13867
15654
  const token = process.env.SOL_TOKEN;
13868
15655
  if (cfg && token) {
13869
15656
  const resolved = await recipientsForPath(cfg, token, dir).catch(() => {
@@ -13923,15 +15710,15 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13923
15710
  if (content === SEALED && !args.includes("--hide-names"))
13924
15711
  die("already sealed: " + path);
13925
15712
  if (content === undefined) {
13926
- const abs = join19(cwd, path);
13927
- if (!existsSync18(abs))
15713
+ const abs = join20(cwd, path);
15714
+ if (!existsSync20(abs))
13928
15715
  die("no such file: " + path);
13929
- content = readFileSync18(abs, "utf8");
15716
+ content = readFileSync20(abs, "utf8");
13930
15717
  }
13931
15718
  const ring = loadKeyRing();
13932
15719
  const { SealedClient: SealedClient2 } = await Promise.resolve().then(() => (init_sealed_client(), exports_sealed_client));
13933
15720
  const { parseRecipient: parseRecipient2, resolveRecipient: resolveRecipient2, recordAudience: recordAudience2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
13934
- const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
15721
+ const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL3).replace(/\/+$/, "");
13935
15722
  const recipientPubKeys = {};
13936
15723
  const audienceAccounts = [];
13937
15724
  const localRecipients = new Set([actor]);
@@ -13944,7 +15731,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13944
15731
  let hideNamesFromPolicy = false;
13945
15732
  let hideExistenceFromPolicy = false;
13946
15733
  if (!rawRecipients.length) {
13947
- const cfg = resolveRemote2(solDir);
15734
+ const cfg = resolveRemote3(solDir);
13948
15735
  const token = process.env.SOL_TOKEN;
13949
15736
  if (cfg && token) {
13950
15737
  const { pubFingerprint: pubFingerprint2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
@@ -13973,7 +15760,7 @@ refused for a non-recipient (host/agent-blind). a missing or malformed reference
13973
15760
  }
13974
15761
  }
13975
15762
  if (wantEscrow && !escrowSlots.length) {
13976
- const cfg = resolveRemote2(solDir);
15763
+ const cfg = resolveRemote3(solDir);
13977
15764
  const token = process.env.SOL_TOKEN;
13978
15765
  if (cfg && token) {
13979
15766
  const { recipientsForAudience: recipientsForAudience2 } = await Promise.resolve().then(() => (init_remote(), exports_remote));
@@ -14179,7 +15966,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14179
15966
  const { repo } = open();
14180
15967
  const wantJson = args.includes("--json");
14181
15968
  if (args.includes("--check")) {
14182
- const cfg = resolveRemote2(solDir) || die("no remote — drift is checked against the policy on the repo; `sol remote <url> <repo>` first");
15969
+ const cfg = resolveRemote3(solDir) || die("no remote — drift is checked against the policy on the repo; `sol remote <url> <repo>` first");
14183
15970
  const token = process.env.SOL_TOKEN || authExpired2();
14184
15971
  const { policyCheck: policyCheck2 } = await Promise.resolve().then(() => (init_remote(), exports_remote));
14185
15972
  const states = [];
@@ -14236,7 +16023,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14236
16023
  const { openContent: openContent2, UNREADABLE: UNREADABLE2 } = await Promise.resolve().then(() => (init_crypto(), exports_crypto));
14237
16024
  const { parseStruct: parseStruct2 } = await Promise.resolve().then(() => (init_struct(), exports_struct));
14238
16025
  const audiences = loadAudiences2(solDir);
14239
- const ring = existsSync18(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
16026
+ const ring = existsSync20(solDir) ? loadKeyRing() : new (await Promise.resolve().then(() => (init_crypto(), exports_crypto))).KeyRing;
14240
16027
  const self = loadSelfIdentity2();
14241
16028
  const levelOf = (boxStr) => {
14242
16029
  try {
@@ -14431,7 +16218,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14431
16218
  const result = merge2({ store: store2 }, other.base, ours, other.head);
14432
16219
  if (result.conflicts.length) {
14433
16220
  materializeTree(store2, result.head);
14434
- writeFileSync16(join19(solDir, "MERGE_HEAD"), other.head);
16221
+ writeFileSync17(join20(solDir, "MERGE_HEAD"), other.head);
14435
16222
  saveMergeConflicts(solDir, result.conflicts);
14436
16223
  console.log(`merge ${name} -> ${result.conflicts.length} conflict(s), left in your working tree (uncommitted):`);
14437
16224
  for (const c of result.conflicts)
@@ -14449,7 +16236,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14449
16236
  materializeTree(store2, result.head);
14450
16237
  clearMergeConflicts(solDir);
14451
16238
  console.log(`merged ${name} into ${refs.current} cleanly -> ${result.head.slice(0, 12)}`);
14452
- reportSemanticGate2(gateMerge(solDir, cfgDir2(), cwd, result.head), args.includes("--json"));
16239
+ reportSemanticGate2(gateMerge(solDir, cfgDir(), cwd, result.head), args.includes("--json"));
14453
16240
  break;
14454
16241
  }
14455
16242
  case "undo": {
@@ -14532,8 +16319,19 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14532
16319
  break;
14533
16320
  }
14534
16321
  case "remote": {
14535
- if (!existsSync18(solDir))
16322
+ if (!existsSync20(solDir))
14536
16323
  die("not a sol repo");
16324
+ if (args[0] === "verify") {
16325
+ const { remoteVerify: remoteVerify2, renderRemoteVerify: renderRemoteVerify2 } = await Promise.resolve().then(() => (init_admin_remote_verify(), exports_admin_remote_verify));
16326
+ const cfg2 = resolveRemote3(solDir) || die("no remote — `sol remote <url> <repo>` (or `sol clone`) first");
16327
+ const token = process.env.SOL_TOKEN || await loadStoredToken2() || authExpired2();
16328
+ const result = await remoteVerify2((url, init) => fetch(url, init), cfg2.url, cfg2.repo, token);
16329
+ const { lines: lines2, exitCode } = renderRemoteVerify2(result);
16330
+ for (const l of lines2)
16331
+ console.log(l);
16332
+ process.exitCode = exitCode;
16333
+ break;
16334
+ }
14537
16335
  if (args[0]) {
14538
16336
  const repoName = args[1] || die("usage: sol remote <url> <repo>");
14539
16337
  saveRemote(solDir, { url: args[0], repo: repoName });
@@ -14578,15 +16376,15 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14578
16376
  }
14579
16377
  if (!tokens)
14580
16378
  die("timed out waiting for approval");
14581
- mkdirSync12(dirname6(CRED_PATH2), { recursive: true });
14582
- writeFileSync16(CRED_PATH2, JSON.stringify({ webUrl, ...tokens }, null, 2), { mode: 384 });
16379
+ mkdirSync13(dirname7(CRED_PATH2), { recursive: true });
16380
+ writeFileSync17(CRED_PATH2, JSON.stringify({ webUrl, ...tokens }, null, 2), { mode: 384 });
14583
16381
  const c = tokenClaims2(tokens.accessToken);
14584
16382
  console.log(`
14585
16383
  Logged in as ${c.handle ? `@${c.handle}` : c.email || "user"}.`);
14586
16384
  if (!c.handle)
14587
16385
  console.log(" (no handle yet — set one in the web app to get your <handle>/<repo> namespace)");
14588
16386
  } else if (sub === "logout") {
14589
- if (existsSync18(CRED_PATH2))
16387
+ if (existsSync20(CRED_PATH2))
14590
16388
  unlinkSync8(CRED_PATH2);
14591
16389
  console.log("logged out");
14592
16390
  } else if (sub === "status" || !sub) {
@@ -14595,11 +16393,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14595
16393
  console.log(`authenticated via SOL_TOKEN (env)${c2.handle ? ` as @${c2.handle}` : ""}`);
14596
16394
  break;
14597
16395
  }
14598
- if (!existsSync18(CRED_PATH2)) {
16396
+ if (!existsSync20(CRED_PATH2)) {
14599
16397
  console.log("not logged in — run `sol auth login` (or set SOL_TOKEN)");
14600
16398
  break;
14601
16399
  }
14602
- const creds = JSON.parse(readFileSync18(CRED_PATH2, "utf8"));
16400
+ const creds = JSON.parse(readFileSync20(CRED_PATH2, "utf8"));
14603
16401
  const c = tokenClaims2(creds.accessToken || "");
14604
16402
  const stale = typeof c.exp === "number" && c.exp * 1000 < Date.now();
14605
16403
  console.log(`logged in as ${c.handle ? `@${c.handle}` : c.email || "user"} via ${creds.webUrl}${stale ? " (token stale — refreshes on next use)" : ""}`);
@@ -14649,9 +16447,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14649
16447
  console.log("heads-up: changing your handle re-namespaces your repos under the new <handle>/<repo>.");
14650
16448
  console.log(`handle set to @${out.handle}`);
14651
16449
  } else if (sub === "pat") {
14652
- if (!existsSync18(CRED_PATH2))
16450
+ if (!existsSync20(CRED_PATH2))
14653
16451
  die("run `sol auth login` first");
14654
- const creds = JSON.parse(readFileSync18(CRED_PATH2, "utf8"));
16452
+ const creds = JSON.parse(readFileSync20(CRED_PATH2, "utf8"));
14655
16453
  const token = await loadStoredToken2();
14656
16454
  if (!token || !creds.webUrl)
14657
16455
  die("run `sol auth login` first");
@@ -14709,11 +16507,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14709
16507
  const { converge: converge2 } = await Promise.resolve().then(() => (init_converge(), exports_converge));
14710
16508
  const peer = openPeer2(localSrc);
14711
16509
  const peerHead = await peer.log.head();
14712
- const dest = resolve5(procCwd, args[1] || (args[0].replace(/\/+$/, "").split("/").pop() || "clone") + "-clone");
14713
- const ddir = join19(dest, ".sol");
14714
- if (existsSync18(ddir))
16510
+ const dest = resolve6(procCwd, args[1] || (args[0].replace(/\/+$/, "").split("/").pop() || "clone") + "-clone");
16511
+ const ddir = join20(dest, ".sol");
16512
+ if (existsSync20(ddir))
14715
16513
  die("already a sol repo: " + dest);
14716
- mkdirSync12(ddir, { recursive: true });
16514
+ mkdirSync13(ddir, { recursive: true });
14717
16515
  const peerOps = await peer.log.history();
14718
16516
  const res = await converge2({ store: new FileStore(ddir), log: new FileOpLog(ddir) }, { nodes: await peerNodes2(peer, peerHead, peerOps), ops: peerOps, incomingHead: peerHead, actor });
14719
16517
  const dstore = new Store;
@@ -14723,7 +16521,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14723
16521
  const files = (res.head ? listAll(dstore, res.head) : []).filter((f) => !f.split("/").some(isReservedKey2));
14724
16522
  for (const f of files)
14725
16523
  materializeInto(dstore, res.head, dest, f);
14726
- writeFileSync16(join19(ddir, "refs.json"), JSON.stringify({ current: "main", branches: { main: { head: res.head, base: res.head, remote: res.head } }, tags: {} }, null, 2));
16524
+ writeFileSync17(join20(ddir, "refs.json"), JSON.stringify({ current: "main", branches: { main: { head: res.head, base: res.head, remote: res.head } }, tags: {} }, null, 2));
14727
16525
  writeWorkingIndexAt(ddir, dest, files);
14728
16526
  console.log(`cloned local peer ${args[0]} -> ${dest} (${(await peer.log.history()).length} ops, ${files.length} files)`);
14729
16527
  break;
@@ -14731,13 +16529,13 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14731
16529
  const [url, rest] = remoteUrlArg2(args);
14732
16530
  const repoName = rest[0] || die("usage: sol clone [<url>] <owner>/<repo> [dir]");
14733
16531
  const token = process.env.SOL_TOKEN || die("set SOL_TOKEN to the backend bearer token");
14734
- const target = resolve5(cwd, rest[1] || repoName.split("/").pop() || repoName);
14735
- const fdir = join19(target, ".sol");
14736
- if (existsSync18(fdir))
16532
+ const target = resolve6(cwd, rest[1] || repoName.split("/").pop() || repoName);
16533
+ const fdir = join20(target, ".sol");
16534
+ if (existsSync20(fdir))
14737
16535
  die("already a sol repo: " + target);
14738
16536
  const cfg = { url, repo: repoName };
14739
16537
  const bundle = await remoteExport(cfg, token);
14740
- mkdirSync12(fdir, { recursive: true });
16538
+ mkdirSync13(fdir, { recursive: true });
14741
16539
  await writeBundle(fdir, bundle, 0);
14742
16540
  saveRemote(fdir, cfg);
14743
16541
  await pullEnvState2(fdir, cfg, token);
@@ -14750,8 +16548,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14750
16548
  cloneBranches[name] = { head: h, base: h, remote: h };
14751
16549
  if (!cloneBranches[onBranch])
14752
16550
  cloneBranches[onBranch] = { head: checkoutHead, base: checkoutHead, remote: checkoutHead };
14753
- writeFileSync16(join19(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
14754
- writeFileSync16(join19(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: bundle.seq, logTip: bundle.tip }));
16551
+ writeFileSync17(join20(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
16552
+ writeFileSync17(join20(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: bundle.seq, logTip: bundle.tip }));
14755
16553
  const store2 = new Store;
14756
16554
  for (const node of bundle.nodes)
14757
16555
  store2.put(node);
@@ -14772,11 +16570,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14772
16570
  if (!loadRemote(solDir)) {
14773
16571
  const repoName = args.find((a) => !a.startsWith("-"));
14774
16572
  if (repoName) {
14775
- saveRemote(solDir, { url: DEFAULT_REMOTE_URL2, repo: repoName });
14776
- console.log(`remote set: ${DEFAULT_REMOTE_URL2} (repo ${repoName})`);
16573
+ saveRemote(solDir, { url: DEFAULT_REMOTE_URL3, repo: repoName });
16574
+ console.log(`remote set: ${DEFAULT_REMOTE_URL3} (repo ${repoName})`);
14777
16575
  }
14778
16576
  }
14779
- const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`, or `sol push <repo>` to use the hosted Sol");
16577
+ const cfg = resolveRemote3(solDir) || die("no remote — set one with `sol remote <url> <repo>`, or `sol push <repo>` to use the hosted Sol");
14780
16578
  {
14781
16579
  const { pendingScrubPaths: pendingScrubPaths2, historyHasCleartext: historyHasCleartext2 } = await Promise.resolve().then(() => (init_secret_scrub(), exports_secret_scrub));
14782
16580
  const ops0 = await log.history();
@@ -14790,7 +16588,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14790
16588
  const ops = await log.history();
14791
16589
  const localSeq = ops.length ? ops[ops.length - 1].seq : 0;
14792
16590
  const localTip = await log.logTip();
14793
- const localRefs = existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")) : undefined;
16591
+ const localRefs = existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")) : undefined;
14794
16592
  const branch = localRefs?.current ?? "main";
14795
16593
  const branchHead = await log.head() ?? "";
14796
16594
  const remoteWasEmpty = rh.seq === 0 && !rh.tip;
@@ -14802,7 +16600,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14802
16600
  const forkBase = localRefs?.branches[branch]?.remote;
14803
16601
  const baseOp = forkBase ? ops.find((o) => o.rootAfter === forkBase) : undefined;
14804
16602
  const fromSeq = baseOp ? baseOp.seq : rh.seq;
14805
- const res = await remotePush(cfg, token, {
16603
+ const res = await remotePushChunked(cfg, token, {
14806
16604
  nodes: allLocalNodes(),
14807
16605
  ops: ops.filter((o) => o.seq > fromSeq),
14808
16606
  branch,
@@ -14813,8 +16611,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14813
16611
  const prevHead = await log.head() ?? "";
14814
16612
  await writeBundle(solDir, canon, 0);
14815
16613
  const convergedHead = res.head ?? canon.refs?.branches?.[branch] ?? branchHead;
14816
- if (existsSync18(refsPath())) {
14817
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
16614
+ if (existsSync20(refsPath())) {
16615
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
14818
16616
  if (canon.refs) {
14819
16617
  for (const [name, h] of Object.entries(canon.refs.branches)) {
14820
16618
  refs.branches[name] = { head: refs.branches[name]?.head ?? h, base: refs.branches[name]?.base ?? h, remote: h };
@@ -14838,12 +16636,13 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14838
16636
  console.log(`pushed ${res.applied} op(s) -> remote (branch ${branch} @ ${(convergedHead || "").slice(0, 12)})${mergedNote}${conflictNote}${prod ? `; production=${prod}` : ""}`);
14839
16637
  }
14840
16638
  await pushEnvState2(solDir, cfg, token);
16639
+ await surfaceOwner2(cfg, token);
14841
16640
  if (wantPublic) {
14842
16641
  const a = await accessSet(cfg, token, { visibility: "public" });
14843
16642
  console.log(`${cfg.repo} is now ${a.visibility}`);
14844
16643
  }
14845
16644
  if (res.merged && !(res.conflicts && res.conflicts.length) && convergedHead) {
14846
- reportSemanticGate2(gateMerge(solDir, cfgDir2(), cwd, convergedHead), args.includes("--json"));
16645
+ reportSemanticGate2(gateMerge(solDir, cfgDir(), cwd, convergedHead), args.includes("--json"));
14847
16646
  }
14848
16647
  break;
14849
16648
  }
@@ -14878,8 +16677,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14878
16677
  const ownMeta = readViewMeta(solDir);
14879
16678
  const base2 = peerMeta?.startHead ?? ownMeta?.startHead;
14880
16679
  const res = await converge2({ store: new FileStore(solDir, objectsDir()), log }, { nodes: await peerNodes2(peer, peerHead, peerOps), ops: peerOps, incomingHead: peerHead, actor, ...base2 ? { base: base2 } : {} });
14881
- if (existsSync18(refsPath())) {
14882
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
16680
+ if (existsSync20(refsPath())) {
16681
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
14883
16682
  if (refs.branches[refs.current])
14884
16683
  refs.branches[refs.current].head = res.head;
14885
16684
  saveRefs(refs);
@@ -14898,11 +16697,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14898
16697
  clearMergeConflicts(solDir);
14899
16698
  console.log(`pulled from ${peerArg} -> ${res.applied} new op(s)${res.merged ? " (converged by merge)" : ""}, now at ${(res.head || "").slice(0, 12)}`);
14900
16699
  if (res.merged)
14901
- reportSemanticGate2(gateMerge(solDir, cfgDir2(), cwd, res.head), args.includes("--json"));
16700
+ reportSemanticGate2(gateMerge(solDir, cfgDir(), cwd, res.head), args.includes("--json"));
14902
16701
  }
14903
16702
  break;
14904
16703
  }
14905
- const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
16704
+ const cfg = resolveRemote3(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
14906
16705
  const token = process.env.SOL_TOKEN || authExpired2();
14907
16706
  await surfaceEnvDivergence2(solDir, cfg, token);
14908
16707
  await pullEnvState2(solDir, cfg, token);
@@ -14910,11 +16709,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14910
16709
  const ops = await log.history();
14911
16710
  const localSeq = ops.length ? ops[ops.length - 1].seq : 0;
14912
16711
  const localTip = await log.logTip();
14913
- const curBranch = existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")).current : bundle.refs?.production || "main";
16712
+ const curBranch = existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")).current : bundle.refs?.production || "main";
14914
16713
  const remoteCurHead = bundle.refs?.branches?.[curBranch] ?? bundle.head ?? "";
14915
16714
  if (bundle.seq === localSeq && bundle.tip === localTip) {
14916
- if (bundle.refs && existsSync18(refsPath())) {
14917
- const lr = JSON.parse(readFileSync18(refsPath(), "utf8"));
16715
+ if (bundle.refs && existsSync20(refsPath())) {
16716
+ const lr = JSON.parse(readFileSync20(refsPath(), "utf8"));
14918
16717
  const before = lr.branches[lr.current]?.head;
14919
16718
  for (const [name, h] of Object.entries(bundle.refs.branches))
14920
16719
  lr.branches[name] = { head: h, base: lr.branches[name]?.base ?? h, remote: h };
@@ -14938,9 +16737,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14938
16737
  break;
14939
16738
  }
14940
16739
  const syncRefHead = (h) => {
14941
- if (!existsSync18(refsPath()))
16740
+ if (!existsSync20(refsPath()))
14942
16741
  return;
14943
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
16742
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
14944
16743
  if (refs.branches[refs.current])
14945
16744
  refs.branches[refs.current].head = h;
14946
16745
  saveRefs(refs);
@@ -14958,8 +16757,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
14958
16757
  syncRefHead(remoteCurHead);
14959
16758
  setOpLogHead(remoteCurHead);
14960
16759
  materializeTree(loadStore(), remoteCurHead);
14961
- if (bundle.refs && existsSync18(refsPath())) {
14962
- const lr = JSON.parse(readFileSync18(refsPath(), "utf8"));
16760
+ if (bundle.refs && existsSync20(refsPath())) {
16761
+ const lr = JSON.parse(readFileSync20(refsPath(), "utf8"));
14963
16762
  for (const [name, h] of Object.entries(bundle.refs.branches))
14964
16763
  lr.branches[name] = { head: h, base: lr.branches[name]?.base ?? h, remote: h };
14965
16764
  saveRefs(lr);
@@ -15007,9 +16806,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
15007
16806
  for (const c of result.conflicts) {
15008
16807
  const blob = fileAt(store2, result.head, c.path);
15009
16808
  if (blob)
15010
- writeFileSync16(join19(cwd, c.path), blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
16809
+ writeFileSync17(join20(cwd, c.path), blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
15011
16810
  }
15012
- writeFileSync16(join19(solDir, "MERGE_HEAD"), remoteHead2);
16811
+ writeFileSync17(join20(solDir, "MERGE_HEAD"), remoteHead2);
15013
16812
  saveMergeConflicts(solDir, result.conflicts);
15014
16813
  console.log(`pulled + merged WITH ${result.conflicts.length} conflict(s), left uncommitted in your working tree:`);
15015
16814
  for (const c of result.conflicts)
@@ -15019,16 +16818,40 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
15019
16818
  } else {
15020
16819
  clearMergeConflicts(solDir);
15021
16820
  console.log(`pulled + merged remote into local -> ${mergedHead.slice(0, 12)} (now run \`sol push\`)`);
15022
- reportSemanticGate2(gateMerge(solDir, cfgDir2(), cwd, mergedHead), args.includes("--json"));
16821
+ reportSemanticGate2(gateMerge(solDir, cfgDir(), cwd, mergedHead), args.includes("--json"));
15023
16822
  }
15024
16823
  break;
15025
16824
  }
16825
+ case "sync": {
16826
+ if (!existsSync20(solDir))
16827
+ die("not a sol repo — run `sol init` first");
16828
+ if (!args.includes("--watch"))
16829
+ die("usage: sol sync --watch [--poll-interval <ms>] [--no-push-immediately] [--dry-run]");
16830
+ resolveRemote3(solDir) || die("no remote — set one with `sol remote <url> <repo>`, or `sol push <repo>` first");
16831
+ const token = process.env.SOL_TOKEN || await loadStoredToken2() || authExpired2();
16832
+ const intFlag = (name, def) => {
16833
+ const i = args.indexOf(name);
16834
+ if (i < 0 || i + 1 >= args.length)
16835
+ return def;
16836
+ const n = Number(args[i + 1]);
16837
+ return Number.isFinite(n) && n > 0 ? n : def;
16838
+ };
16839
+ const { runSyncWatch: runSyncWatch2 } = await Promise.resolve().then(() => (init_sync_watch(), exports_sync_watch));
16840
+ await runSyncWatch2({
16841
+ repoDir: cwd,
16842
+ token,
16843
+ pollInterval: intFlag("--poll-interval", 5000),
16844
+ pushImmediate: !args.includes("--no-push-immediately"),
16845
+ dryRun: args.includes("--dry-run")
16846
+ });
16847
+ break;
16848
+ }
15026
16849
  case "promote": {
15027
- if (!existsSync18(solDir))
16850
+ if (!existsSync20(solDir))
15028
16851
  die("not a sol repo");
15029
- const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
16852
+ const cfg = resolveRemote3(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
15030
16853
  const token = process.env.SOL_TOKEN || authExpired2();
15031
- const cur = existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")).current : "main";
16854
+ const cur = existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")).current : "main";
15032
16855
  const branch = args[0] || cur;
15033
16856
  const refs = await remotePromote(cfg, token, branch);
15034
16857
  console.log(`promoted '${branch}' -> production '${refs.production}' now at ${(refs.branches[refs.production] ?? "").slice(0, 12)}`);
@@ -15039,9 +16862,9 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
15039
16862
  const parent = frest[0] || die("usage: sol fork [<url>] <parent-repo> <new-repo> [dir]");
15040
16863
  const newRepo = frest[1] || die("usage: sol fork [<url>] <parent-repo> <new-repo> [dir]");
15041
16864
  const token = process.env.SOL_TOKEN || authExpired2();
15042
- const target = resolve5(cwd, frest[2] || newRepo);
15043
- const fdir = join19(target, ".sol");
15044
- if (existsSync18(fdir))
16865
+ const target = resolve6(cwd, frest[2] || newRepo);
16866
+ const fdir = join20(target, ".sol");
16867
+ if (existsSync20(fdir))
15045
16868
  die("already a sol repo: " + target);
15046
16869
  const parentCfg = { url, repo: parent };
15047
16870
  const newCfg = { url, repo: newRepo, forkParent: parent };
@@ -15057,7 +16880,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
15057
16880
  }
15058
16881
  await forkMeta(newCfg, token, parent);
15059
16882
  const canon = await remoteExport(newCfg, token);
15060
- mkdirSync12(fdir, { recursive: true });
16883
+ mkdirSync13(fdir, { recursive: true });
15061
16884
  await writeBundle(fdir, canon, 0);
15062
16885
  const srvRefs = canon.refs ?? { branches: { main: canon.head ?? "" }, production: "main" };
15063
16886
  const checkout = canon.checkout ?? { branch: srvRefs.production || "main", head: srvRefs.branches[srvRefs.production] ?? canon.head };
@@ -15066,8 +16889,8 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
15066
16889
  const cloneBranches = {};
15067
16890
  for (const [name, h] of Object.entries(srvRefs.branches))
15068
16891
  cloneBranches[name] = { head: h, base: h, remote: h };
15069
- writeFileSync16(join19(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
15070
- writeFileSync16(join19(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: canon.seq, logTip: canon.tip }));
16892
+ writeFileSync17(join20(fdir, "refs.json"), JSON.stringify({ current: onBranch, branches: cloneBranches, tags: {} }, null, 2));
16893
+ writeFileSync17(join20(fdir, "HEAD"), JSON.stringify({ head: checkoutHead, seq: canon.seq, logTip: canon.tip }));
15071
16894
  saveRemote(fdir, newCfg);
15072
16895
  const store2 = new Store;
15073
16896
  for (const node of canon.nodes)
@@ -15085,7 +16908,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
15085
16908
  }
15086
16909
  case "access": {
15087
16910
  const token = process.env.SOL_TOKEN || authExpired2();
15088
- const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
16911
+ const cfg = resolveRemote3(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
15089
16912
  const sub = args[0];
15090
16913
  if (!sub || sub === "show") {
15091
16914
  const a = await accessGet(cfg, token);
@@ -15137,7 +16960,7 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
15137
16960
  break;
15138
16961
  }
15139
16962
  case "forks": {
15140
- const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
16963
+ const cfg = resolveRemote3(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
15141
16964
  const token = process.env.SOL_TOKEN || authExpired2();
15142
16965
  const { forks } = await forksList(cfg, token);
15143
16966
  if (!forks.length)
@@ -15150,11 +16973,11 @@ WARNING: "${path}" was committed as PLAINTEXT before this seal — the pre-seal
15150
16973
  break;
15151
16974
  }
15152
16975
  case "mr": {
15153
- const cfg = resolveRemote2(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
16976
+ const cfg = resolveRemote3(solDir) || die("no remote — set one with `sol remote <url> <repo>`");
15154
16977
  const token = process.env.SOL_TOKEN || authExpired2();
15155
16978
  const { mrSummary: mrSummary2 } = await Promise.resolve().then(() => exports_mr);
15156
16979
  const sub = args[0];
15157
- const localRefs = () => existsSync18(refsPath()) ? JSON.parse(readFileSync18(refsPath(), "utf8")) : { current: "main", branches: {}, tags: {} };
16980
+ const localRefs = () => existsSync20(refsPath()) ? JSON.parse(readFileSync20(refsPath(), "utf8")) : { current: "main", branches: {}, tags: {} };
15158
16981
  const flag2 = (name) => {
15159
16982
  const i = args.indexOf(name);
15160
16983
  return i >= 0 ? args[i + 1] : undefined;
@@ -15332,7 +17155,7 @@ ${mrSummary2(pr)}`);
15332
17155
  die("usage: sol run [--keep <path>] [--isolate] <command...>");
15333
17156
  const { capture: capture3, hydrate: hydrate3, isolateCommand: isolateCommand2 } = await Promise.resolve().then(() => (init_runtime(), exports_runtime));
15334
17157
  const head = await repo.head();
15335
- const dir = mkdtempSync2(join19(tmpdir2(), "sol-run-"));
17158
+ const dir = mkdtempSync2(join20(tmpdir2(), "sol-run-"));
15336
17159
  try {
15337
17160
  const hn = hydrate3(loadStore(), head, dir);
15338
17161
  console.log(`hydrated ${hn} file(s) -> sandbox${isolate ? " (isolated: no network, writes confined to the sandbox)" : ""}`);
@@ -15352,8 +17175,8 @@ ${mrSummary2(pr)}`);
15352
17175
  const { written, deleted } = await capture3(repo, dir, keep);
15353
17176
  if (written.length || deleted.length) {
15354
17177
  await appendCommit(log, await repo.head(), `run: ${command.join(" ")}`, head);
15355
- if (existsSync18(refsPath())) {
15356
- const refs = JSON.parse(readFileSync18(refsPath(), "utf8"));
17178
+ if (existsSync20(refsPath())) {
17179
+ const refs = JSON.parse(readFileSync20(refsPath(), "utf8"));
15357
17180
  if (refs.branches[refs.current]) {
15358
17181
  refs.branches[refs.current].head = await repo.head();
15359
17182
  saveRefs(refs);
@@ -15365,7 +17188,7 @@ ${mrSummary2(pr)}`);
15365
17188
  materialize(synced, nh, f);
15366
17189
  for (const f of deleted) {
15367
17190
  try {
15368
- unlinkSync8(join19(cwd, f));
17191
+ unlinkSync8(join20(cwd, f));
15369
17192
  } catch {}
15370
17193
  }
15371
17194
  console.log(`captured ${written.length} written, ${deleted.length} deleted file(s):`);
@@ -15377,7 +17200,7 @@ ${mrSummary2(pr)}`);
15377
17200
  console.log("no files captured (the run produced no tracked changes)");
15378
17201
  }
15379
17202
  } finally {
15380
- rmSync3(dir, { recursive: true, force: true });
17203
+ rmSync4(dir, { recursive: true, force: true });
15381
17204
  }
15382
17205
  break;
15383
17206
  }
@@ -15385,12 +17208,12 @@ ${mrSummary2(pr)}`);
15385
17208
  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))]);
15386
17209
  const sub = args[0];
15387
17210
  if (sub === "import") {
15388
- const gitPath = resolve5(cwd, args[1] || die("usage: sol git import <git-repo> [dir]"));
15389
- const target = resolve5(cwd, args[2] || basename2(gitPath));
15390
- const fdir = join19(target, ".sol");
15391
- if (existsSync18(fdir))
17211
+ const gitPath = resolve6(cwd, args[1] || die("usage: sol git import <git-repo> [dir]"));
17212
+ const target = resolve6(cwd, args[2] || basename3(gitPath));
17213
+ const fdir = join20(target, ".sol");
17214
+ if (existsSync20(fdir))
15392
17215
  die("already a sol repo: " + target);
15393
- mkdirSync12(fdir, { recursive: true });
17216
+ mkdirSync13(fdir, { recursive: true });
15394
17217
  const { commits, branches, head, current } = await importGitRepo2(gitPath, fdir);
15395
17218
  const refsBranches = {};
15396
17219
  for (const b of branches)
@@ -15398,20 +17221,20 @@ ${mrSummary2(pr)}`);
15398
17221
  if (!refsBranches[current])
15399
17222
  refsBranches[current] = { head, base: head };
15400
17223
  refsBranches[current].head = head;
15401
- writeFileSync16(join19(fdir, "refs.json"), JSON.stringify({ current, branches: refsBranches, tags: {} }, null, 2));
17224
+ writeFileSync17(join20(fdir, "refs.json"), JSON.stringify({ current, branches: refsBranches, tags: {} }, null, 2));
15402
17225
  const store2 = new Store;
15403
- for (const name of readdirSync9(join19(fdir, "objects"))) {
17226
+ for (const name of readdirSync9(join20(fdir, "objects"))) {
15404
17227
  if (name.endsWith(".tmp"))
15405
17228
  continue;
15406
17229
  try {
15407
- store2.put(decodeObject(readFileSync18(join19(fdir, "objects", name))));
17230
+ store2.put(decodeObject(readFileSync20(join20(fdir, "objects", name))));
15408
17231
  } catch {}
15409
17232
  }
15410
17233
  const onDisk = hydrate3(store2, head, target);
15411
- console.log(`imported ${commits} commit(s), ${branches.length} branch(es) from git -> ${args[2] || basename2(gitPath)} (${onDisk} files; on branch ${current})`);
17234
+ console.log(`imported ${commits} commit(s), ${branches.length} branch(es) from git -> ${args[2] || basename3(gitPath)} (${onDisk} files; on branch ${current})`);
15412
17235
  } else if (sub === "export") {
15413
17236
  const { log } = open();
15414
- const gitPath = resolve5(cwd, args[1] || die("usage: sol git export <git-repo> [-b branch]"));
17237
+ const gitPath = resolve6(cwd, args[1] || die("usage: sol git export <git-repo> [-b branch]"));
15415
17238
  const store2 = loadStore();
15416
17239
  const head = await log.head() ?? "";
15417
17240
  if (!head || !listAll(store2, head).length) {
@@ -15430,15 +17253,111 @@ ${mrSummary2(pr)}`);
15430
17253
  }
15431
17254
  break;
15432
17255
  }
17256
+ case "agent": {
17257
+ if (!existsSync20(solDir))
17258
+ die("not a sol repo — run `sol init` first");
17259
+ const parentSol = parentSolDir(solDir) ?? solDir;
17260
+ const { createSession: createSession2, stopSession: stopSession2, sessions: sessions2, pruneSession: pruneSession2 } = await Promise.resolve().then(() => (init_agent_sessions(), exports_agent_sessions));
17261
+ const sub = args[0];
17262
+ const json = args.includes("--json");
17263
+ const flagVal = (name) => {
17264
+ const i = args.indexOf(name);
17265
+ return i >= 0 ? args[i + 1] : undefined;
17266
+ };
17267
+ switch (sub) {
17268
+ case "start": {
17269
+ const name = args.slice(1).find((a) => !a.startsWith("-")) || die('usage: sol agent start <name> [dir] [--actor <name>] [--check "<cmd>"]');
17270
+ const positionals2 = args.slice(1).filter((a) => !a.startsWith("-"));
17271
+ const sessionActor = flagVal("--actor") || actor;
17272
+ const metaRaw = flagVal("--meta");
17273
+ let metadata;
17274
+ if (metaRaw) {
17275
+ try {
17276
+ metadata = JSON.parse(metaRaw);
17277
+ } catch {
17278
+ die("--meta must be valid JSON");
17279
+ }
17280
+ }
17281
+ const rec = createSession2({
17282
+ parentSol,
17283
+ name,
17284
+ actor: sessionActor,
17285
+ dir: positionals2[1] ? resolve6(procCwd, positionals2[1]) : undefined,
17286
+ check: flagVal("--check"),
17287
+ metadata
17288
+ });
17289
+ if (json) {
17290
+ console.log(JSON.stringify(rec));
17291
+ break;
17292
+ }
17293
+ console.log(`started agent session '${rec.name}' (id ${rec.sessionId})`);
17294
+ console.log(` view ${rec.viewDir} (branch ${rec.branch} @ ${(rec.startHead || "empty").slice(0, 12)})`);
17295
+ console.log(` actor ${rec.actor}`);
17296
+ console.log(` identity ${rec.fingerprint}`);
17297
+ console.log(` -> wire the agent: cd ${rec.viewDir} && export SOL_ACTOR=${rec.actor} SOL_SIGNING_KEY=$(cat ${rec.signingKeyPath})`);
17298
+ console.log(` then edit + \`sol commit -m "..." <files>\`; converge with \`sol pull ${rec.viewDir}\` from the parent.`);
17299
+ console.log(` stop/clean: \`sol agent stop ${rec.sessionId}\` | \`sol agent cleanup ${rec.sessionId} --delete\``);
17300
+ break;
17301
+ }
17302
+ case "stop": {
17303
+ const id = args.slice(1).find((a) => !a.startsWith("-")) || die("usage: sol agent stop <sessionId>");
17304
+ const ok = stopSession2(parentSol, id);
17305
+ if (json)
17306
+ console.log(JSON.stringify({ stopped: ok, sessionId: id }));
17307
+ else
17308
+ console.log(ok ? `stopped session ${id} (the view + key remain — \`sol agent cleanup\` to remove)` : `no such session: ${id}`);
17309
+ if (!ok)
17310
+ process.exitCode = 1;
17311
+ break;
17312
+ }
17313
+ case "status":
17314
+ case undefined: {
17315
+ const id = args.slice(1).find((a) => !a.startsWith("-"));
17316
+ const all = await sessions2(parentSol);
17317
+ const list = id ? all.filter((s) => s.sessionId === id || s.name === id) : all;
17318
+ if (json) {
17319
+ console.log(JSON.stringify({ repo: parentSol, sessions: list }));
17320
+ break;
17321
+ }
17322
+ if (!list.length) {
17323
+ console.log(id ? `no such session: ${id}` : "no agent sessions — start one with `sol agent start <name>`");
17324
+ break;
17325
+ }
17326
+ console.log(`agent sessions of ${parentSol}:`);
17327
+ for (const s of list) {
17328
+ 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}]`);
17329
+ console.log(` ${s.viewDir}`);
17330
+ }
17331
+ console.log(" stop: `sol agent stop <id>` | cleanup: `sol agent cleanup <id> [--delete]` (or bare `cleanup` to prune stale)");
17332
+ break;
17333
+ }
17334
+ case "cleanup": {
17335
+ const id = args.slice(1).find((a) => !a.startsWith("-"));
17336
+ const removed = pruneSession2(parentSol, id, args.includes("--delete"));
17337
+ if (json)
17338
+ console.log(JSON.stringify({ removed }));
17339
+ else
17340
+ console.log(removed.length ? `cleaned ${removed.length} session(s): ${removed.join(", ")}` : id ? `no such session: ${id}` : "no stale sessions to clean");
17341
+ break;
17342
+ }
17343
+ default:
17344
+ die(`unknown: sol agent ${sub}
17345
+ sol agent start <name> [dir] [--actor N] [--check "<cmd>"]
17346
+ sol agent status [<id>]
17347
+ sol agent stop <id>
17348
+ sol agent cleanup [<id>] [--delete]`);
17349
+ }
17350
+ break;
17351
+ }
15433
17352
  case "view": {
15434
17353
  const { log } = open();
15435
17354
  if (readViewMeta(solDir))
15436
17355
  die("already inside a view — create views from the parent repo (its `.sol` owns the shared store + op-log).");
15437
17356
  const name = args.find((a) => !a.startsWith("-")) || die("usage: sol view <name> [dir]");
15438
17357
  const rest = args.filter((a) => !a.startsWith("-"));
15439
- const defaultDir = join19(dirname6(cwd), `${basename2(cwd)}-${name}`);
15440
- const viewDir = rest[1] ? resolve5(procCwd, rest[1]) : defaultDir;
15441
- if (existsSync18(join19(viewDir, ".sol")))
17358
+ const defaultDir = join20(dirname7(cwd), `${basename3(cwd)}-${name}`);
17359
+ const viewDir = rest[1] ? resolve6(procCwd, rest[1]) : defaultDir;
17360
+ if (existsSync20(join20(viewDir, ".sol")))
15442
17361
  die("already a sol repo/view: " + viewDir);
15443
17362
  const branch = `view/${name}`;
15444
17363
  const startHead = await log.head() ?? emptyRoot(loadStore());
@@ -15452,7 +17371,7 @@ ${mrSummary2(pr)}`);
15452
17371
  break;
15453
17372
  }
15454
17373
  case "views": {
15455
- if (!existsSync18(solDir))
17374
+ if (!existsSync20(solDir))
15456
17375
  die("not a sol repo");
15457
17376
  const parentSol = parentSolDir(solDir) ?? solDir;
15458
17377
  const { pruneViews: pruneViews2, viewStatuses: viewStatuses2, sharedObjectCount: sharedObjectCount2 } = await Promise.resolve().then(() => (init_views(), exports_views));
@@ -15482,7 +17401,7 @@ ${mrSummary2(pr)}`);
15482
17401
  }
15483
17402
  case "check": {
15484
17403
  open();
15485
- const cd = cfgDir2();
17404
+ const cd = cfgDir();
15486
17405
  const setIdx = args.indexOf("--set");
15487
17406
  if (setIdx >= 0) {
15488
17407
  const cmd2 = args[setIdx + 1] || die('usage: sol check --set "<command>" (e.g. sol check --set "bun test")');
@@ -15548,12 +17467,12 @@ ${mrSummary2(pr)}`);
15548
17467
  await startSecretMcp2({ solDir, http });
15549
17468
  break;
15550
17469
  }
15551
- if (!existsSync18(solDir))
17470
+ if (!existsSync20(solDir))
15552
17471
  die("not a sol repo — run `sol init` first");
15553
17472
  const { runEnv: runEnv2, runSecret: runSecret2, resolveReference: resolveReference2 } = await Promise.resolve().then(() => (init_secret2(), exports_secret2));
15554
17473
  const { loadSelfIdentity: loadSelfIdentity2, loadManageIdentity: loadManageIdentity2, fetchKey: fetchKey2 } = await Promise.resolve().then(() => (init_seal_audience(), exports_seal_audience));
15555
17474
  const { loadIdentity: loadIdentity2 } = await Promise.resolve().then(() => (init_identity_store(), exports_identity_store));
15556
- const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL2).replace(/\/+$/, "");
17475
+ const dirUrl = (process.env.SOL_REMOTE || DEFAULT_REMOTE_URL3).replace(/\/+$/, "");
15557
17476
  const selfPub = (account) => {
15558
17477
  const id = loadIdentity2();
15559
17478
  return id && id.accountId === account ? id.x25519Pub : undefined;
@@ -15565,7 +17484,7 @@ ${mrSummary2(pr)}`);
15565
17484
  };
15566
17485
  const dirEdPub = async (account) => (await fetchKey2(dirUrl, account))?.edPub;
15567
17486
  const remoteAnchorVerify = async () => {
15568
- const rcfg = resolveRemote2(solDir);
17487
+ const rcfg = resolveRemote3(solDir);
15569
17488
  const token = process.env.SOL_TOKEN || await loadStoredToken2();
15570
17489
  if (!rcfg || !token)
15571
17490
  return;
@@ -15615,6 +17534,11 @@ everyday (examples are copy-safe — use real filenames):
15615
17534
  sol rm old.txt delete a file (from the repo and disk)
15616
17535
  sol ignore "*.tmp" add an ignore pattern (no arg lists the active patterns)
15617
17536
  sol fsck / sol gc verify integrity / drop unreachable objects
17537
+ sol doctor full health check (op-log + objects + auth + files + sealed keyring)
17538
+ sol fsck --repair recover a corrupt repo (re-point a lost head / truncate to the last verified link)
17539
+ sol recover guide through recovery (rehydrate from cloud, list roots, restore a file from history)
17540
+ sol reflog the op-log as a ref-move history (head moves, by actor/branch/date)
17541
+ sol remote verify REMOTE-side health check (the backend's /fsck) with auth/network differentiation
15618
17542
  sol check --set "bun test" gate convergence on a test command: a merge that line-converges but FAILS
15619
17543
  the check is flagged as a SEMANTIC conflict (status state:"SEMANTIC"), not
15620
17544
  silently accepted. \`sol check\` runs it on demand. (sol check --help)
@@ -15651,6 +17575,16 @@ clone-free agent views (the worktree killer — N agents, one shared store on di
15651
17575
  \`sol pull <view>\` converges losslessly. (sol view --help)
15652
17576
  sol views list every view (name, dir, branch, head, author, active/stale) + --prune
15653
17577
 
17578
+ agent sessions (one atomic command provisions a working tree, a signing identity, an actor, and the test-gate):
17579
+ sol agent start <name> create a view + MINT a per-session Ed25519 signing key + wire the actor +
17580
+ inherit/set the test-gate, all at once. prints SOL_SIGNING_KEY / SOL_ACTOR to
17581
+ export so every op the agent authors is cryptographically attributable.
17582
+ (--actor <name>, --check "<cmd>", [dir], --json)
17583
+ sol agent status [<id>] list sessions (name, id, head, actor, fingerprint, running/stopped/stale)
17584
+ sol agent stop <id> mark a session stopped (presence signal; view + key remain)
17585
+ sol agent cleanup [<id>] remove the session record + its key (--delete also removes the view dir;
17586
+ bare \`cleanup\` prunes every stale session)
17587
+
15654
17588
  branches & tags:
15655
17589
  sol branch list branches (sol branch feature creates one at HEAD)
15656
17590
  sol switch feature switch to a branch (captures current work first, never loses it)
@@ -15667,6 +17601,7 @@ auth (sign in once; remote commands then use the cached token, no SOL_TOKEN need
15667
17601
  remotes (self-hostable backend; token in SOL_TOKEN or via sol auth login):
15668
17602
  sol clone [<url>] <owner>/<repo> [dir] clone a remote repo (checks out PRODUCTION); default dir = <repo>
15669
17603
  sol push / sol pull sync your commits with the remote (push registers your branch's head)
17604
+ sol sync --watch hands-off bidirectional sync daemon: auto-capture + push edits, converge safe pulls in
15670
17605
  sol push <repo> one-step share: no remote set? use the hosted Sol + <repo>, then push
15671
17606
  sol push --public <repo> create + push + make public in one step (new repos are private by default)
15672
17607
  sol promote [branch] point the remote's production branch at <branch> (default: current)
@@ -15706,10 +17641,15 @@ async function runCli2(argv) {
15706
17641
  const msg = e?.message || String(e);
15707
17642
  if (/ -> 401\b/.test(msg) || /\b401 unauthorized\b/i.test(msg))
15708
17643
  authExpired2();
17644
+ const { CorruptObjectError: CorruptObjectError2, CorruptRepoError: CorruptRepoError2 } = await Promise.resolve().then(() => (init_errors(), exports_errors));
17645
+ if (e instanceof CorruptObjectError2 || e instanceof CorruptRepoError2) {
17646
+ const { friendlyError: friendlyError2 } = await Promise.resolve().then(() => (init_errors_friendly(), exports_errors_friendly));
17647
+ die(friendlyError2(e));
17648
+ }
15709
17649
  die(msg);
15710
17650
  }
15711
17651
  }
15712
- var CRED_PATH2, DEFAULT_REMOTE_URL2, ATTEST_KEY2, remoteUrlArg2 = (a) => /^https?:\/\//.test(a[0] ?? "") ? [a[0].replace(/\/+$/, ""), a.slice(1)] : [DEFAULT_REMOTE_URL2, a], cfgDir2 = () => parentSolDir(solDir) ?? solDir;
17652
+ var CRED_PATH2, DEFAULT_REMOTE_URL3, ATTEST_KEY2, remoteUrlArg2 = (a) => /^https?:\/\//.test(a[0] ?? "") ? [a[0].replace(/\/+$/, ""), a.slice(1)] : [DEFAULT_REMOTE_URL3, a];
15713
17653
  var init_dispatch2 = __esm(() => {
15714
17654
  init_chain();
15715
17655
  init_diff();
@@ -15721,18 +17661,18 @@ var init_dispatch2 = __esm(() => {
15721
17661
  init_remote();
15722
17662
  init_lib();
15723
17663
  init_test_gate();
15724
- CRED_PATH2 = join19(homedir3(), ".sol", "credentials");
15725
- DEFAULT_REMOTE_URL2 = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
17664
+ CRED_PATH2 = join20(homedir3(), ".sol", "credentials");
17665
+ DEFAULT_REMOTE_URL3 = (process.env.SOL_REMOTE || "https://sol.midsummer.new").replace(/\/+$/, "");
15726
17666
  ATTEST_KEY2 = process.env.SOL_ATTEST_KEY || undefined;
15727
17667
  });
15728
17668
 
15729
17669
  // src/bin/sol.ts
15730
- import { readFileSync as readFileSync19 } from "fs";
17670
+ import { readFileSync as readFileSync21 } from "fs";
15731
17671
  function cliVersion3() {
15732
17672
  if (typeof __SOL_COMPILED_VERSION__ === "string" && __SOL_COMPILED_VERSION__)
15733
17673
  return __SOL_COMPILED_VERSION__;
15734
17674
  try {
15735
- return JSON.parse(readFileSync19(new URL("./package.json", import.meta.url), "utf8")).version || "dev";
17675
+ return JSON.parse(readFileSync21(new URL("./package.json", import.meta.url), "utf8")).version || "dev";
15736
17676
  } catch {
15737
17677
  return "dev";
15738
17678
  }