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