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