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