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.
- package/index.js +200 -6
- package/package.json +1 -1
- package/sol-mcp.js +1743 -164
- 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
|
|
1183
|
+
tool
|
|
1122
1184
|
});
|
|
1123
|
-
|
|
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", "
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 =
|
|
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 (!
|
|
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
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (!
|
|
7481
|
+
if (!existsSync16(p))
|
|
6444
7482
|
return { views: [] };
|
|
6445
7483
|
try {
|
|
6446
|
-
return JSON.parse(
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
8019
|
-
import { join as
|
|
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 ||
|
|
8056
|
-
if (!
|
|
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
|
|
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
|
|
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 (!
|
|
9433
|
+
if (!existsSync19(CRED_PATH))
|
|
8222
9434
|
return;
|
|
8223
9435
|
let creds;
|
|
8224
9436
|
try {
|
|
8225
|
-
creds = JSON.parse(
|
|
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
|
-
|
|
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(
|
|
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
|
|
9497
|
+
function resolveRemote2(solDir2) {
|
|
8286
9498
|
const cfg = loadRemote(solDir2);
|
|
8287
9499
|
if (!cfg)
|
|
8288
9500
|
return;
|
|
8289
|
-
return cfg.url ? cfg : { ...cfg, 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(
|
|
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 (${
|
|
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 =
|
|
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) &&
|
|
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 =
|
|
8891
|
-
if (
|
|
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
|
-
|
|
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 ||
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
9133
|
-
const parent2 =
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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 (
|
|
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 =
|
|
9737
|
-
const lead =
|
|
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 =
|
|
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
|
-
|
|
11267
|
+
writeFileSync16(join18(solDir, "ops.jsonl"), res.ops.map((o) => JSON.stringify(o)).join(`
|
|
9854
11268
|
`) + (res.ops.length ? `
|
|
9855
11269
|
` : ""));
|
|
9856
|
-
|
|
9857
|
-
if (
|
|
9858
|
-
const refs = JSON.parse(
|
|
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 =
|
|
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 ||
|
|
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 =
|
|
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 =
|
|
10017
|
-
if (!
|
|
11430
|
+
const abs = join18(cwd, path);
|
|
11431
|
+
if (!existsSync19(abs))
|
|
10018
11432
|
die("no such file: " + path);
|
|
10019
|
-
content =
|
|
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 ||
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
10672
|
-
|
|
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 (
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
12167
|
+
if (!existsSync19(CRED_PATH))
|
|
10743
12168
|
die("run `sol auth login` first");
|
|
10744
|
-
const creds = JSON.parse(
|
|
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 =
|
|
10803
|
-
const ddir =
|
|
10804
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
10825
|
-
const fdir =
|
|
10826
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
10844
|
-
|
|
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:
|
|
10866
|
-
console.log(`remote set: ${
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (
|
|
10907
|
-
const refs = JSON.parse(
|
|
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 (
|
|
10972
|
-
const refs = JSON.parse(
|
|
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 =
|
|
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 =
|
|
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 &&
|
|
11007
|
-
const lr = JSON.parse(
|
|
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 (!
|
|
12457
|
+
if (!existsSync19(refsPath()))
|
|
11032
12458
|
return;
|
|
11033
|
-
const refs = JSON.parse(
|
|
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 &&
|
|
11052
|
-
const lr = JSON.parse(
|
|
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
|
-
|
|
12526
|
+
writeFileSync16(join18(cwd, c.path), blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
|
|
11101
12527
|
}
|
|
11102
|
-
|
|
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 (!
|
|
12567
|
+
if (!existsSync19(solDir))
|
|
11118
12568
|
die("not a sol repo");
|
|
11119
|
-
const cfg =
|
|
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 =
|
|
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 =
|
|
11133
|
-
const fdir =
|
|
11134
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
11160
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 = () =>
|
|
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(
|
|
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 (
|
|
11446
|
-
const refs = JSON.parse(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
11479
|
-
const target =
|
|
11480
|
-
const fdir =
|
|
11481
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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] ||
|
|
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 =
|
|
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 =
|
|
11530
|
-
const viewDir = rest[1] ?
|
|
11531
|
-
if (
|
|
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 (!
|
|
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 (!
|
|
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 ||
|
|
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 =
|
|
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,
|
|
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 =
|
|
11815
|
-
|
|
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 {
|
|
11998
|
-
import {
|
|
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
|
-
|
|
12040
|
-
const repoRoot2 =
|
|
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 ||
|
|
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
|
|
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
|
|
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 (!
|
|
13717
|
+
if (!existsSync20(CRED_PATH2))
|
|
12132
13718
|
return;
|
|
12133
13719
|
let creds;
|
|
12134
13720
|
try {
|
|
12135
|
-
creds = JSON.parse(
|
|
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
|
-
|
|
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(
|
|
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
|
|
13781
|
+
function resolveRemote3(solDir2) {
|
|
12196
13782
|
const cfg = loadRemote(solDir2);
|
|
12197
13783
|
if (!cfg)
|
|
12198
13784
|
return;
|
|
12199
|
-
return cfg.url ? cfg : { ...cfg, url:
|
|
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(
|
|
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 (${
|
|
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 =
|
|
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) &&
|
|
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 =
|
|
12801
|
-
if (
|
|
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
|
-
|
|
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 ||
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
13043
|
-
const parent2 =
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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 (
|
|
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 =
|
|
13647
|
-
const lead =
|
|
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 =
|
|
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
|
-
|
|
15551
|
+
writeFileSync17(join20(solDir, "ops.jsonl"), res.ops.map((o) => JSON.stringify(o)).join(`
|
|
13764
15552
|
`) + (res.ops.length ? `
|
|
13765
15553
|
` : ""));
|
|
13766
|
-
|
|
13767
|
-
if (
|
|
13768
|
-
const refs = JSON.parse(
|
|
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 =
|
|
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 ||
|
|
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 =
|
|
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 =
|
|
13927
|
-
if (!
|
|
15714
|
+
const abs = join20(cwd, path);
|
|
15715
|
+
if (!existsSync20(abs))
|
|
13928
15716
|
die("no such file: " + path);
|
|
13929
|
-
content =
|
|
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 ||
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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 (!
|
|
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
|
-
|
|
14582
|
-
|
|
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 (
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
16451
|
+
if (!existsSync20(CRED_PATH2))
|
|
14653
16452
|
die("run `sol auth login` first");
|
|
14654
|
-
const creds = JSON.parse(
|
|
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 =
|
|
14713
|
-
const ddir =
|
|
14714
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
14735
|
-
const fdir =
|
|
14736
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
14754
|
-
|
|
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:
|
|
14776
|
-
console.log(`remote set: ${
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (
|
|
14817
|
-
const refs = JSON.parse(
|
|
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,
|
|
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 (
|
|
14882
|
-
const refs = JSON.parse(
|
|
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,
|
|
16701
|
+
reportSemanticGate2(gateMerge(solDir, cfgDir(), cwd, res.head), args.includes("--json"));
|
|
14902
16702
|
}
|
|
14903
16703
|
break;
|
|
14904
16704
|
}
|
|
14905
|
-
const cfg =
|
|
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 =
|
|
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 &&
|
|
14917
|
-
const lr = JSON.parse(
|
|
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 (!
|
|
16741
|
+
if (!existsSync20(refsPath()))
|
|
14942
16742
|
return;
|
|
14943
|
-
const refs = JSON.parse(
|
|
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 &&
|
|
14962
|
-
const lr = JSON.parse(
|
|
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
|
-
|
|
16810
|
+
writeFileSync17(join20(cwd, c.path), blob.encoding === "base64" ? Buffer.from(blob.content, "base64") : blob.content);
|
|
15011
16811
|
}
|
|
15012
|
-
|
|
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,
|
|
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 (!
|
|
16851
|
+
if (!existsSync20(solDir))
|
|
15028
16852
|
die("not a sol repo");
|
|
15029
|
-
const cfg =
|
|
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 =
|
|
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 =
|
|
15043
|
-
const fdir =
|
|
15044
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
15070
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 = () =>
|
|
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(
|
|
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 (
|
|
15356
|
-
const refs = JSON.parse(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
15389
|
-
const target =
|
|
15390
|
-
const fdir =
|
|
15391
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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] ||
|
|
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 =
|
|
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 =
|
|
15440
|
-
const viewDir = rest[1] ?
|
|
15441
|
-
if (
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 ||
|
|
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 =
|
|
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,
|
|
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 =
|
|
15725
|
-
|
|
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
|
|
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(
|
|
17676
|
+
return JSON.parse(readFileSync21(new URL("./package.json", import.meta.url), "utf8")).version || "dev";
|
|
15736
17677
|
} catch {
|
|
15737
17678
|
return "dev";
|
|
15738
17679
|
}
|