midsummer-sol 0.2.2 → 0.3.1

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