claude-nomad 0.44.0 → 0.45.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/.gitleaks.toml +21 -0
- package/CHANGELOG.md +40 -0
- package/README.md +3 -1
- package/dist/nomad.mjs +626 -429
- package/package.json +3 -1
package/dist/nomad.mjs
CHANGED
|
@@ -170,7 +170,7 @@ function gitOrFatal(args, context, cwd) {
|
|
|
170
170
|
throw new NomadFatal(`${context} failed`);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
-
var log, warn, fail, NomadFatal, die, gitStatusPorcelainZ;
|
|
173
|
+
var log, warn, fail, item, NomadFatal, die, gitStatusPorcelainZ;
|
|
174
174
|
var init_utils = __esm({
|
|
175
175
|
"src/utils.ts"() {
|
|
176
176
|
"use strict";
|
|
@@ -182,6 +182,7 @@ var init_utils = __esm({
|
|
|
182
182
|
fail = (msg) => {
|
|
183
183
|
console.error(`${red(failGlyph)} ${msg}`);
|
|
184
184
|
};
|
|
185
|
+
item = (msg) => console.log(dim(` ${msg}`));
|
|
185
186
|
NomadFatal = class extends Error {
|
|
186
187
|
constructor(message) {
|
|
187
188
|
super(message);
|
|
@@ -360,6 +361,18 @@ var init_settings_keys = __esm({
|
|
|
360
361
|
// src/config.ts
|
|
361
362
|
import { homedir, hostname } from "node:os";
|
|
362
363
|
import { join, resolve } from "node:path";
|
|
364
|
+
function home() {
|
|
365
|
+
return process.env.HOME || homedir();
|
|
366
|
+
}
|
|
367
|
+
function claudeHome() {
|
|
368
|
+
return resolve(home(), ".claude");
|
|
369
|
+
}
|
|
370
|
+
function repoHome() {
|
|
371
|
+
return process.env.NOMAD_REPO || resolve(home(), "claude-nomad");
|
|
372
|
+
}
|
|
373
|
+
function backupBase() {
|
|
374
|
+
return join(home(), ".cache", "claude-nomad", "backup");
|
|
375
|
+
}
|
|
363
376
|
function allSharedLinks(map) {
|
|
364
377
|
const extras = [];
|
|
365
378
|
for (const entry of map.sharedDirs ?? []) {
|
|
@@ -373,7 +386,7 @@ function allSharedLinks(map) {
|
|
|
373
386
|
}
|
|
374
387
|
return [...SHARED_LINKS, ...extras];
|
|
375
388
|
}
|
|
376
|
-
var
|
|
389
|
+
var SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, HOST, SHARED_LINKS, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
377
390
|
var init_config = __esm({
|
|
378
391
|
"src/config.ts"() {
|
|
379
392
|
"use strict";
|
|
@@ -381,10 +394,6 @@ var init_config = __esm({
|
|
|
381
394
|
init_utils();
|
|
382
395
|
init_config_never_sync();
|
|
383
396
|
init_settings_keys();
|
|
384
|
-
HOME = homedir();
|
|
385
|
-
CLAUDE_HOME = resolve(HOME, ".claude");
|
|
386
|
-
BACKUP_BASE = join(HOME, ".cache", "claude-nomad", "backup");
|
|
387
|
-
REPO_HOME = process.env.NOMAD_REPO || resolve(HOME, "claude-nomad");
|
|
388
397
|
SETTINGS_SCHEMA_URL = "https://json.schemastore.org/claude-code-settings.json";
|
|
389
398
|
NPM_REGISTRY_LATEST_URL = "https://registry.npmjs.org/claude-nomad/latest";
|
|
390
399
|
GITLEAKS_PINNED_VERSION = "8.30.1";
|
|
@@ -526,18 +535,19 @@ function ensureSymlink(linkPath, target) {
|
|
|
526
535
|
}
|
|
527
536
|
function backupBeforeWrite(absPath, ts) {
|
|
528
537
|
if (!existsSync(absPath)) return;
|
|
529
|
-
const
|
|
538
|
+
const claude = claudeHome();
|
|
539
|
+
const rel = relative(claude, absPath);
|
|
530
540
|
if (rel.startsWith("..") || rel === "") return;
|
|
531
|
-
const backupRoot = join2(
|
|
541
|
+
const backupRoot = join2(backupBase(), ts);
|
|
532
542
|
const dst = join2(backupRoot, rel);
|
|
533
543
|
mkdirSync(dirname(dst), { recursive: true });
|
|
534
544
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
535
545
|
}
|
|
536
|
-
function backupRepoWrite(absPath, ts,
|
|
546
|
+
function backupRepoWrite(absPath, ts, repoHome2) {
|
|
537
547
|
if (!existsSync(absPath)) return;
|
|
538
|
-
const rel = relative(
|
|
548
|
+
const rel = relative(repoHome2, absPath);
|
|
539
549
|
if (rel.startsWith("..") || rel === "") return;
|
|
540
|
-
const backupRoot = join2(
|
|
550
|
+
const backupRoot = join2(backupBase(), ts, "repo");
|
|
541
551
|
const dst = join2(backupRoot, rel);
|
|
542
552
|
mkdirSync(dirname(dst), { recursive: true });
|
|
543
553
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
@@ -546,7 +556,7 @@ function backupExtrasWrite(absPath, ts, projectRoot) {
|
|
|
546
556
|
if (!existsSync(absPath)) return;
|
|
547
557
|
const rel = relative(projectRoot, absPath);
|
|
548
558
|
if (rel.startsWith("..") || rel === "") return;
|
|
549
|
-
const backupRoot = join2(
|
|
559
|
+
const backupRoot = join2(backupBase(), ts, "extras");
|
|
550
560
|
const dst = join2(backupRoot, encodePath(projectRoot), rel);
|
|
551
561
|
mkdirSync(dirname(dst), { recursive: true });
|
|
552
562
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
@@ -565,8 +575,8 @@ import { existsSync as existsSync11, mkdtempSync, readFileSync as readFileSync3,
|
|
|
565
575
|
import { tmpdir } from "node:os";
|
|
566
576
|
import { join as join12 } from "node:path";
|
|
567
577
|
import { fileURLToPath } from "node:url";
|
|
568
|
-
function resolveTomlPath() {
|
|
569
|
-
const repoToml = join12(
|
|
578
|
+
function resolveTomlPath(repo = repoHome()) {
|
|
579
|
+
const repoToml = join12(repo, ".gitleaks.toml");
|
|
570
580
|
if (existsSync11(repoToml)) return repoToml;
|
|
571
581
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
572
582
|
return existsSync11(bundled) ? bundled : null;
|
|
@@ -582,9 +592,10 @@ ${overlayBody}`;
|
|
|
582
592
|
return { configPath, tempPath };
|
|
583
593
|
}
|
|
584
594
|
function resolveTomlConfig() {
|
|
585
|
-
const
|
|
586
|
-
const
|
|
587
|
-
const
|
|
595
|
+
const repo = repoHome();
|
|
596
|
+
const overlayPath = join12(repo, ".gitleaks.overlay.toml");
|
|
597
|
+
const repoToml = join12(repo, ".gitleaks.toml");
|
|
598
|
+
const bundled = resolveTomlPath(repo);
|
|
588
599
|
if (!existsSync11(overlayPath)) {
|
|
589
600
|
return { path: bundled, tempPath: null };
|
|
590
601
|
}
|
|
@@ -696,24 +707,23 @@ function probeGitleaks() {
|
|
|
696
707
|
if (tempPath !== null) rmSync4(tempPath, { recursive: true, force: true });
|
|
697
708
|
}
|
|
698
709
|
}
|
|
699
|
-
function rebaseBeforePush() {
|
|
710
|
+
function rebaseBeforePush(repo) {
|
|
700
711
|
try {
|
|
701
712
|
execFileSync2("git", ["pull", "--rebase", "--autostash"], {
|
|
702
|
-
cwd:
|
|
713
|
+
cwd: repo,
|
|
703
714
|
stdio: ["ignore", "pipe", "pipe"]
|
|
704
715
|
});
|
|
705
716
|
} catch (err) {
|
|
706
717
|
const e = err;
|
|
707
718
|
if (e.stderr) process.stderr.write(e.stderr);
|
|
708
719
|
throw new NomadFatal(
|
|
709
|
-
|
|
720
|
+
`rebase failed; if a conflict was reported, resolve it in ${repo} and run "git rebase --continue" (or "git rebase --abort" to give up). Re-run nomad push after resolution.`
|
|
710
721
|
);
|
|
711
722
|
}
|
|
712
723
|
}
|
|
713
724
|
var init_push_checks = __esm({
|
|
714
725
|
"src/push-checks.ts"() {
|
|
715
726
|
"use strict";
|
|
716
|
-
init_config();
|
|
717
727
|
init_push_gitleaks_config();
|
|
718
728
|
init_utils();
|
|
719
729
|
}
|
|
@@ -937,10 +947,10 @@ function verdictScanError(text) {
|
|
|
937
947
|
process.exitCode = 1;
|
|
938
948
|
return { leak: false, verdictRow: failRow(text), recovery: null, findings: [] };
|
|
939
949
|
}
|
|
940
|
-
function scanPushVerdict() {
|
|
950
|
+
function scanPushVerdict(repo) {
|
|
941
951
|
let findings;
|
|
942
952
|
try {
|
|
943
|
-
findings = scanStagedTree(
|
|
953
|
+
findings = scanStagedTree(repo, true);
|
|
944
954
|
} catch (err) {
|
|
945
955
|
if (err.code === "ENOENT") {
|
|
946
956
|
return {
|
|
@@ -970,7 +980,6 @@ var init_push_leak_verdict = __esm({
|
|
|
970
980
|
"src/push-leak-verdict.ts"() {
|
|
971
981
|
"use strict";
|
|
972
982
|
init_color();
|
|
973
|
-
init_config();
|
|
974
983
|
init_push_checks();
|
|
975
984
|
init_push_gitleaks();
|
|
976
985
|
noLeaksRow = () => `${green(okGlyph)} no leaks`;
|
|
@@ -995,8 +1004,8 @@ function lexists(p) {
|
|
|
995
1004
|
return false;
|
|
996
1005
|
}
|
|
997
1006
|
}
|
|
998
|
-
function readMapIfPresent(
|
|
999
|
-
const mapPath = join3(
|
|
1007
|
+
function readMapIfPresent(repoHome2) {
|
|
1008
|
+
const mapPath = join3(repoHome2, "path-map.json");
|
|
1000
1009
|
return existsSync2(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
1001
1010
|
}
|
|
1002
1011
|
function isConfiguredTarget(name, map) {
|
|
@@ -1006,14 +1015,14 @@ function isValidAdoptName(name) {
|
|
|
1006
1015
|
if (SHARED_LINKS.includes(name)) return true;
|
|
1007
1016
|
return isValidSharedDir(name);
|
|
1008
1017
|
}
|
|
1009
|
-
function performAdoptMove(name, linkPath, sharedTarget) {
|
|
1010
|
-
const ts = freshBackupTs(
|
|
1018
|
+
function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
|
|
1019
|
+
const ts = freshBackupTs(backup);
|
|
1011
1020
|
backupBeforeWrite(linkPath, ts);
|
|
1012
1021
|
cpSync2(linkPath, sharedTarget, { recursive: true, force: true, preserveTimestamps: true });
|
|
1013
1022
|
rmSync(linkPath, { recursive: true, force: true });
|
|
1014
1023
|
ensureSymlink(linkPath, sharedTarget);
|
|
1015
1024
|
const rel = join3("shared", name);
|
|
1016
|
-
gitOrFatal(["add", "--", rel], `git add shared/${name}`,
|
|
1025
|
+
gitOrFatal(["add", "--", rel], `git add shared/${name}`, repo);
|
|
1017
1026
|
log(`adopted ${name}; ${ADOPT_PUSH_HINT}`);
|
|
1018
1027
|
}
|
|
1019
1028
|
function cmdAdopt(name, opts = {}) {
|
|
@@ -1022,15 +1031,18 @@ function cmdAdopt(name, opts = {}) {
|
|
|
1022
1031
|
fail(`invalid name: ${JSON.stringify(name)}`);
|
|
1023
1032
|
process.exit(1);
|
|
1024
1033
|
}
|
|
1025
|
-
const
|
|
1034
|
+
const repo = repoHome();
|
|
1035
|
+
const claude = claudeHome();
|
|
1036
|
+
const backup = backupBase();
|
|
1037
|
+
const map = readMapIfPresent(repo);
|
|
1026
1038
|
if (!isConfiguredTarget(name, map)) {
|
|
1027
1039
|
fail(
|
|
1028
1040
|
`${name}: not a configured shared target. Add it to sharedDirs in path-map.json first, then re-run adopt.`
|
|
1029
1041
|
);
|
|
1030
1042
|
process.exit(1);
|
|
1031
1043
|
}
|
|
1032
|
-
const linkPath = join3(
|
|
1033
|
-
const sharedTarget = join3(
|
|
1044
|
+
const linkPath = join3(claude, name);
|
|
1045
|
+
const sharedTarget = join3(repo, "shared", name);
|
|
1034
1046
|
if (!existsSync2(linkPath)) {
|
|
1035
1047
|
log(`${name}: nothing to adopt (not present in ~/.claude/)`);
|
|
1036
1048
|
return;
|
|
@@ -1044,14 +1056,14 @@ function cmdAdopt(name, opts = {}) {
|
|
|
1044
1056
|
process.exit(1);
|
|
1045
1057
|
}
|
|
1046
1058
|
if (dryRun) {
|
|
1047
|
-
const ts = freshBackupTs(
|
|
1059
|
+
const ts = freshBackupTs(backup);
|
|
1048
1060
|
log(`would backup: ${linkPath} -> backup/${ts}/${name}`);
|
|
1049
1061
|
log(`would move: ${linkPath} -> shared/${name}`);
|
|
1050
1062
|
log(`would stage: shared/${name}`);
|
|
1051
1063
|
return;
|
|
1052
1064
|
}
|
|
1053
1065
|
try {
|
|
1054
|
-
performAdoptMove(name, linkPath, sharedTarget);
|
|
1066
|
+
performAdoptMove(name, linkPath, sharedTarget, repo, backup);
|
|
1055
1067
|
} catch (err) {
|
|
1056
1068
|
if (!(err instanceof NomadFatal)) throw err;
|
|
1057
1069
|
fail(err.message);
|
|
@@ -1064,7 +1076,6 @@ init_config();
|
|
|
1064
1076
|
import { existsSync as existsSync3 } from "node:fs";
|
|
1065
1077
|
|
|
1066
1078
|
// src/commands.redact.core.ts
|
|
1067
|
-
init_config();
|
|
1068
1079
|
import { appendFileSync, readFileSync as readFileSync2 } from "node:fs";
|
|
1069
1080
|
import { join as join4 } from "node:path";
|
|
1070
1081
|
function collectMatchIntervals(content, findings) {
|
|
@@ -1121,10 +1132,10 @@ function isValidFingerprint(fingerprint) {
|
|
|
1121
1132
|
function isAlreadyPresent(line, lines) {
|
|
1122
1133
|
return lines.includes(line);
|
|
1123
1134
|
}
|
|
1124
|
-
function appendGitleaksIgnore(fingerprint) {
|
|
1135
|
+
function appendGitleaksIgnore(fingerprint, repo) {
|
|
1125
1136
|
const sanitized = fingerprint.replace(/[\r\n]/g, "").trim();
|
|
1126
1137
|
if (sanitized.length === 0) return;
|
|
1127
|
-
const ignPath = join4(
|
|
1138
|
+
const ignPath = join4(repo, ".gitleaksignore");
|
|
1128
1139
|
let raw;
|
|
1129
1140
|
try {
|
|
1130
1141
|
raw = readFileSync2(ignPath, "utf8");
|
|
@@ -1141,7 +1152,8 @@ function appendGitleaksIgnore(fingerprint) {
|
|
|
1141
1152
|
// src/commands.allow.ts
|
|
1142
1153
|
init_utils();
|
|
1143
1154
|
function cmdAllow(fingerprints) {
|
|
1144
|
-
|
|
1155
|
+
const repo = repoHome();
|
|
1156
|
+
if (!existsSync3(repo)) die(`repo not cloned at ${repo}`);
|
|
1145
1157
|
for (const fp of fingerprints) {
|
|
1146
1158
|
if (!isValidFingerprint(fp)) {
|
|
1147
1159
|
const shown = fp.replaceAll("\r", String.raw`\r`).replaceAll("\n", String.raw`\n`);
|
|
@@ -1150,9 +1162,10 @@ function cmdAllow(fingerprints) {
|
|
|
1150
1162
|
}
|
|
1151
1163
|
}
|
|
1152
1164
|
for (const fp of fingerprints) {
|
|
1153
|
-
appendGitleaksIgnore(fp);
|
|
1154
|
-
|
|
1165
|
+
appendGitleaksIgnore(fp, repo);
|
|
1166
|
+
item(`allowed: ${fp}`);
|
|
1155
1167
|
}
|
|
1168
|
+
log(`allowed ${fingerprints.length} fingerprint(s)`);
|
|
1156
1169
|
}
|
|
1157
1170
|
|
|
1158
1171
|
// src/commands.clean.ts
|
|
@@ -1172,9 +1185,9 @@ function parseDuration(s) {
|
|
|
1172
1185
|
if (!m) return null;
|
|
1173
1186
|
return Number(m[1]) * UNIT_MS[m[2]];
|
|
1174
1187
|
}
|
|
1175
|
-
function listBackupDirs(
|
|
1176
|
-
if (!existsSync4(
|
|
1177
|
-
return readdirSync(
|
|
1188
|
+
function listBackupDirs(backupBase2) {
|
|
1189
|
+
if (!existsSync4(backupBase2)) return [];
|
|
1190
|
+
return readdirSync(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(join5(backupBase2, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1178
1191
|
}
|
|
1179
1192
|
function prunableByAge(dirs, olderThanMs, nowMs) {
|
|
1180
1193
|
return dirs.filter((d) => nowMs - d.mtimeMs > olderThanMs).map((d) => d.name);
|
|
@@ -1182,9 +1195,9 @@ function prunableByAge(dirs, olderThanMs, nowMs) {
|
|
|
1182
1195
|
function prunableByCount(dirs, keep) {
|
|
1183
1196
|
return dirs.slice(keep).map((d) => d.name);
|
|
1184
1197
|
}
|
|
1185
|
-
function safeDelete(
|
|
1198
|
+
function safeDelete(backupBase2, name) {
|
|
1186
1199
|
if (!isTsDir(name)) return;
|
|
1187
|
-
const full = join5(
|
|
1200
|
+
const full = join5(backupBase2, name);
|
|
1188
1201
|
const st = lstatSync3(full, { throwIfNoEntry: false });
|
|
1189
1202
|
if (!st || st.isSymbolicLink() || !st.isDirectory()) return;
|
|
1190
1203
|
rmSync2(full, { recursive: true, force: true });
|
|
@@ -1193,7 +1206,7 @@ function resolveTargets(dirs, olderThanMs, keep) {
|
|
|
1193
1206
|
if (keep !== void 0) return prunableByCount(dirs, keep);
|
|
1194
1207
|
return prunableByAge(dirs, olderThanMs, Date.now());
|
|
1195
1208
|
}
|
|
1196
|
-
function cmdClean(opts,
|
|
1209
|
+
function cmdClean(opts, backupBase2 = backupBase()) {
|
|
1197
1210
|
const { dryRun, olderThan, keep } = opts;
|
|
1198
1211
|
if (olderThan !== void 0 && keep !== void 0) {
|
|
1199
1212
|
fail("--older-than and --keep are mutually exclusive");
|
|
@@ -1208,14 +1221,14 @@ function cmdClean(opts, backupBase = BACKUP_BASE) {
|
|
|
1208
1221
|
}
|
|
1209
1222
|
olderThanMs = parsed;
|
|
1210
1223
|
}
|
|
1211
|
-
const dirs = listBackupDirs(
|
|
1224
|
+
const dirs = listBackupDirs(backupBase2);
|
|
1212
1225
|
const targets = resolveTargets(dirs, olderThanMs, keep);
|
|
1213
1226
|
if (dryRun) {
|
|
1214
|
-
for (const name of targets)
|
|
1227
|
+
for (const name of targets) item(name);
|
|
1215
1228
|
log(`dry-run: ${targets.length} backup(s) would be removed`);
|
|
1216
1229
|
return;
|
|
1217
1230
|
}
|
|
1218
|
-
for (const name of targets) safeDelete(
|
|
1231
|
+
for (const name of targets) safeDelete(backupBase2, name);
|
|
1219
1232
|
log(`removed ${targets.length} backup(s)`);
|
|
1220
1233
|
}
|
|
1221
1234
|
|
|
@@ -1225,15 +1238,16 @@ init_utils();
|
|
|
1225
1238
|
init_utils_json();
|
|
1226
1239
|
import { cpSync as cpSync3, existsSync as existsSync5, lstatSync as lstatSync4, realpathSync, renameSync as renameSync2, rmSync as rmSync3 } from "node:fs";
|
|
1227
1240
|
import { join as join6, sep } from "node:path";
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1241
|
+
function ejectChecklist() {
|
|
1242
|
+
return [
|
|
1243
|
+
"Manual steps remaining to finish leaving claude-nomad on this host:",
|
|
1244
|
+
` 1. Uninstall the CLI: npm uninstall -g claude-nomad`,
|
|
1245
|
+
` 2. Remove NOMAD_HOST and NOMAD_REPO from your shell rc (~/.zshrc or ~/.bashrc)`,
|
|
1246
|
+
` 3. Optionally delete the local sync checkout: rm -rf ${repoHome()}`,
|
|
1247
|
+
` 4. Optionally delete the private sync repo on GitHub`,
|
|
1248
|
+
` 5. Optionally delete the backup cache: rm -rf ${backupBase()}`
|
|
1249
|
+
].join("\n");
|
|
1250
|
+
}
|
|
1237
1251
|
function errMessage(err) {
|
|
1238
1252
|
return err instanceof Error ? err.message : String(err);
|
|
1239
1253
|
}
|
|
@@ -1245,8 +1259,8 @@ function lexists2(p) {
|
|
|
1245
1259
|
return false;
|
|
1246
1260
|
}
|
|
1247
1261
|
}
|
|
1248
|
-
function readMapIfPresent2(
|
|
1249
|
-
const mapPath = join6(
|
|
1262
|
+
function readMapIfPresent2(repoHome2) {
|
|
1263
|
+
const mapPath = join6(repoHome2, "path-map.json");
|
|
1250
1264
|
return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
1251
1265
|
}
|
|
1252
1266
|
function classifyName(linkPath) {
|
|
@@ -1255,12 +1269,12 @@ function classifyName(linkPath) {
|
|
|
1255
1269
|
if (!existsSync5(linkPath)) return "dangling";
|
|
1256
1270
|
return "materialize";
|
|
1257
1271
|
}
|
|
1258
|
-
function resolveSharedRoot(
|
|
1272
|
+
function resolveSharedRoot(repoHome2) {
|
|
1259
1273
|
try {
|
|
1260
|
-
return realpathSync(join6(
|
|
1274
|
+
return realpathSync(join6(repoHome2, "shared"));
|
|
1261
1275
|
} catch {
|
|
1262
1276
|
return die(
|
|
1263
|
-
`cannot resolve ${join6(
|
|
1277
|
+
`cannot resolve ${join6(repoHome2, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
|
|
1264
1278
|
);
|
|
1265
1279
|
}
|
|
1266
1280
|
}
|
|
@@ -1270,7 +1284,7 @@ function isManagedTarget(target, sharedRoot) {
|
|
|
1270
1284
|
function materializeOne(name, linkPath, sharedRoot) {
|
|
1271
1285
|
const target = realpathSync(linkPath);
|
|
1272
1286
|
if (!isManagedTarget(target, sharedRoot)) {
|
|
1273
|
-
|
|
1287
|
+
item(`skipped (not a nomad-managed target): ${name} -> ${target}`);
|
|
1274
1288
|
return false;
|
|
1275
1289
|
}
|
|
1276
1290
|
const tmp = `${linkPath}.eject.tmp.${process.pid}.${Date.now()}`;
|
|
@@ -1284,7 +1298,7 @@ function materializeOne(name, linkPath, sharedRoot) {
|
|
|
1284
1298
|
});
|
|
1285
1299
|
rmSync3(linkPath, { force: true });
|
|
1286
1300
|
renameSync2(tmp, linkPath);
|
|
1287
|
-
|
|
1301
|
+
item(`ejected: ${name}`);
|
|
1288
1302
|
return true;
|
|
1289
1303
|
} catch (err) {
|
|
1290
1304
|
try {
|
|
@@ -1294,45 +1308,45 @@ function materializeOne(name, linkPath, sharedRoot) {
|
|
|
1294
1308
|
throw err;
|
|
1295
1309
|
}
|
|
1296
1310
|
}
|
|
1297
|
-
function previewDryRun(names, classifications,
|
|
1311
|
+
function previewDryRun(names, classifications, claudeHome2, sharedRoot) {
|
|
1298
1312
|
for (const name of names) {
|
|
1299
1313
|
const cls = classifications.get(name);
|
|
1300
|
-
const linkPath = join6(
|
|
1314
|
+
const linkPath = join6(claudeHome2, name);
|
|
1301
1315
|
if (cls === "absent") {
|
|
1302
|
-
|
|
1316
|
+
item(`skipped (absent): ${name}`);
|
|
1303
1317
|
} else if (cls === "skip-real") {
|
|
1304
|
-
|
|
1318
|
+
item(`skipped (not a symlink): ${name}`);
|
|
1305
1319
|
} else {
|
|
1306
1320
|
previewMaterialize(name, linkPath, sharedRoot);
|
|
1307
1321
|
}
|
|
1308
1322
|
}
|
|
1309
|
-
log(
|
|
1323
|
+
log(ejectChecklist());
|
|
1310
1324
|
}
|
|
1311
1325
|
function previewMaterialize(name, linkPath, sharedRoot) {
|
|
1312
1326
|
let target;
|
|
1313
1327
|
try {
|
|
1314
1328
|
target = realpathSync(linkPath);
|
|
1315
1329
|
} catch {
|
|
1316
|
-
|
|
1330
|
+
item(`would materialize: ${name} (target now unresolvable; re-run to re-classify)`);
|
|
1317
1331
|
return;
|
|
1318
1332
|
}
|
|
1319
1333
|
if (!isManagedTarget(target, sharedRoot)) {
|
|
1320
|
-
|
|
1334
|
+
item(`skipped (not a nomad-managed target): ${name} -> ${target}`);
|
|
1321
1335
|
return;
|
|
1322
1336
|
}
|
|
1323
|
-
|
|
1337
|
+
item(`would materialize: ${name} (copy ${target} -> ${linkPath})`);
|
|
1324
1338
|
}
|
|
1325
|
-
function runLiveEject(names, classifications,
|
|
1339
|
+
function runLiveEject(names, classifications, claudeHome2, sharedRoot) {
|
|
1326
1340
|
const done = [];
|
|
1327
1341
|
let skipped = 0;
|
|
1328
1342
|
for (const name of names) {
|
|
1329
1343
|
const cls = classifications.get(name);
|
|
1330
|
-
const linkPath = join6(
|
|
1344
|
+
const linkPath = join6(claudeHome2, name);
|
|
1331
1345
|
if (cls === "absent") {
|
|
1332
|
-
|
|
1346
|
+
item(`skipped (absent): ${name}`);
|
|
1333
1347
|
skipped++;
|
|
1334
1348
|
} else if (cls === "skip-real") {
|
|
1335
|
-
|
|
1349
|
+
item(`skipped (not a symlink): ${name}`);
|
|
1336
1350
|
skipped++;
|
|
1337
1351
|
} else if (materializeOneOrDie(name, linkPath, sharedRoot, done)) {
|
|
1338
1352
|
done.push(name);
|
|
@@ -1341,7 +1355,7 @@ function runLiveEject(names, classifications, claudeHome, sharedRoot) {
|
|
|
1341
1355
|
}
|
|
1342
1356
|
}
|
|
1343
1357
|
log(`materialized ${done.length}, skipped ${skipped}`);
|
|
1344
|
-
log(
|
|
1358
|
+
log(ejectChecklist());
|
|
1345
1359
|
}
|
|
1346
1360
|
function materializeOneOrDie(name, linkPath, sharedRoot, done) {
|
|
1347
1361
|
try {
|
|
@@ -1349,18 +1363,21 @@ function materializeOneOrDie(name, linkPath, sharedRoot, done) {
|
|
|
1349
1363
|
} catch (err) {
|
|
1350
1364
|
const msg = errMessage(err);
|
|
1351
1365
|
return die(
|
|
1352
|
-
`failed to materialize ${name}: ${msg}. already materialized: ${done.join(", ") || "(none)"}. the remaining names are still symlinks; do NOT delete ${
|
|
1366
|
+
`failed to materialize ${name}: ${msg}. already materialized: ${done.join(", ") || "(none)"}. the remaining names are still symlinks; do NOT delete ${repoHome()} yet, fix the cause and re-run \`nomad eject\` (it is idempotent on already-real names)`
|
|
1353
1367
|
);
|
|
1354
1368
|
}
|
|
1355
1369
|
}
|
|
1356
|
-
function
|
|
1370
|
+
function defaultEjectRoots() {
|
|
1371
|
+
return { claudeHome: claudeHome(), repoHome: repoHome() };
|
|
1372
|
+
}
|
|
1373
|
+
function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
1357
1374
|
const dryRun = opts.dryRun === true;
|
|
1358
|
-
const { claudeHome, repoHome } = roots;
|
|
1359
|
-
const map = readMapIfPresent2(
|
|
1375
|
+
const { claudeHome: claudeHome2, repoHome: repoHome2 } = roots;
|
|
1376
|
+
const map = readMapIfPresent2(repoHome2);
|
|
1360
1377
|
const names = allSharedLinks(map);
|
|
1361
1378
|
const classifications = /* @__PURE__ */ new Map();
|
|
1362
1379
|
for (const name of names) {
|
|
1363
|
-
classifications.set(name, classifyName(join6(
|
|
1380
|
+
classifications.set(name, classifyName(join6(claudeHome2, name)));
|
|
1364
1381
|
}
|
|
1365
1382
|
const dangling = names.filter((n) => classifications.get(n) === "dangling");
|
|
1366
1383
|
if (dangling.length > 0) {
|
|
@@ -1369,13 +1386,13 @@ function cmdEject(opts = {}, roots = DEFAULT_ROOTS) {
|
|
|
1369
1386
|
);
|
|
1370
1387
|
process.exit(1);
|
|
1371
1388
|
}
|
|
1372
|
-
const sharedRoot = resolveSharedRoot(
|
|
1389
|
+
const sharedRoot = resolveSharedRoot(repoHome2);
|
|
1373
1390
|
if (dryRun) {
|
|
1374
|
-
previewDryRun(names, classifications,
|
|
1391
|
+
previewDryRun(names, classifications, claudeHome2, sharedRoot);
|
|
1375
1392
|
return;
|
|
1376
1393
|
}
|
|
1377
1394
|
try {
|
|
1378
|
-
runLiveEject(names, classifications,
|
|
1395
|
+
runLiveEject(names, classifications, claudeHome2, sharedRoot);
|
|
1379
1396
|
} catch (err) {
|
|
1380
1397
|
fail(errMessage(err));
|
|
1381
1398
|
process.exit(1);
|
|
@@ -1383,8 +1400,8 @@ function cmdEject(opts = {}, roots = DEFAULT_ROOTS) {
|
|
|
1383
1400
|
}
|
|
1384
1401
|
|
|
1385
1402
|
// src/commands.doctor.ts
|
|
1386
|
-
import { existsSync as
|
|
1387
|
-
import { join as
|
|
1403
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
1404
|
+
import { join as join27 } from "node:path";
|
|
1388
1405
|
|
|
1389
1406
|
// src/commands.doctor.checks.repo.ts
|
|
1390
1407
|
init_color();
|
|
@@ -1411,8 +1428,8 @@ function sectionFailed(s) {
|
|
|
1411
1428
|
return s.items.some((line) => line.includes(failGlyph));
|
|
1412
1429
|
}
|
|
1413
1430
|
function renderRawItems(items) {
|
|
1414
|
-
for (const
|
|
1415
|
-
console.log(
|
|
1431
|
+
for (const item2 of items) {
|
|
1432
|
+
console.log(item2 === "" ? "" : ` ${item2}`);
|
|
1416
1433
|
}
|
|
1417
1434
|
}
|
|
1418
1435
|
function renderSection(s) {
|
|
@@ -1424,18 +1441,18 @@ function renderSection(s) {
|
|
|
1424
1441
|
const header = sectionFailed(s) ? `${red(FAIL_GLYPH_BARE)} ${s.header}` : s.header;
|
|
1425
1442
|
console.log(header);
|
|
1426
1443
|
const lastContent = s.items.reduce(
|
|
1427
|
-
(acc,
|
|
1444
|
+
(acc, item2, j) => item2 === "" || isChild(item2) ? acc : j,
|
|
1428
1445
|
-1
|
|
1429
1446
|
);
|
|
1430
1447
|
for (let j = 0; j < s.items.length; j++) {
|
|
1431
|
-
const
|
|
1432
|
-
if (
|
|
1433
|
-
else if (isChild(
|
|
1434
|
-
else console.log(`${j === lastContent ? " \u2514 " : " \u251C "}${
|
|
1448
|
+
const item2 = s.items[j];
|
|
1449
|
+
if (item2 === "") console.log("");
|
|
1450
|
+
else if (isChild(item2)) console.log(renderChildLine(s.items, j));
|
|
1451
|
+
else console.log(`${j === lastContent ? " \u2514 " : " \u251C "}${item2}`);
|
|
1435
1452
|
}
|
|
1436
1453
|
}
|
|
1437
|
-
function isChild(
|
|
1438
|
-
return
|
|
1454
|
+
function isChild(item2) {
|
|
1455
|
+
return item2.startsWith(" ");
|
|
1439
1456
|
}
|
|
1440
1457
|
function renderChildLine(items, j) {
|
|
1441
1458
|
const parentContinues = items.some((it, k) => k > j && it !== "" && !isChild(it));
|
|
@@ -1470,10 +1487,10 @@ init_config();
|
|
|
1470
1487
|
init_utils_json();
|
|
1471
1488
|
import { existsSync as existsSync6 } from "node:fs";
|
|
1472
1489
|
import { join as join7 } from "node:path";
|
|
1473
|
-
function classifyRepoState(
|
|
1474
|
-
const basePath = join7(
|
|
1475
|
-
const mapPath = join7(
|
|
1476
|
-
const hostPath = join7(
|
|
1490
|
+
function classifyRepoState(repoHome2, host) {
|
|
1491
|
+
const basePath = join7(repoHome2, "shared", "settings.base.json");
|
|
1492
|
+
const mapPath = join7(repoHome2, "path-map.json");
|
|
1493
|
+
const hostPath = join7(repoHome2, "hosts", `${host}.json`);
|
|
1477
1494
|
const hasBase = existsSync6(basePath);
|
|
1478
1495
|
const hasMap = existsSync6(mapPath);
|
|
1479
1496
|
const hasHost = existsSync6(hostPath);
|
|
@@ -1490,10 +1507,10 @@ function classifyRepoState(repoHome, host) {
|
|
|
1490
1507
|
if (hasBase && mapEntryCount > 0 && hasHost) return "populated";
|
|
1491
1508
|
return "partial";
|
|
1492
1509
|
}
|
|
1493
|
-
function reasonForPartial(
|
|
1494
|
-
const basePath = join7(
|
|
1495
|
-
const mapPath = join7(
|
|
1496
|
-
const hostPath = join7(
|
|
1510
|
+
function reasonForPartial(repoHome2, host) {
|
|
1511
|
+
const basePath = join7(repoHome2, "shared", "settings.base.json");
|
|
1512
|
+
const mapPath = join7(repoHome2, "path-map.json");
|
|
1513
|
+
const hostPath = join7(repoHome2, "hosts", `${host}.json`);
|
|
1497
1514
|
if (!existsSync6(basePath)) return "- shared/settings.base.json missing";
|
|
1498
1515
|
if (!existsSync6(mapPath)) return "- path-map.json missing";
|
|
1499
1516
|
let mapEntryCount;
|
|
@@ -1514,28 +1531,28 @@ function isOverrideActive() {
|
|
|
1514
1531
|
}
|
|
1515
1532
|
function reportHostAndPaths(section2) {
|
|
1516
1533
|
const unsetHint = process.env.NOMAD_HOST ? "" : dim(" (env unset, using hostname)");
|
|
1534
|
+
const repo = repoHome();
|
|
1535
|
+
const claude = claudeHome();
|
|
1517
1536
|
addItem(section2, `${dim(infoGlyph)} NOMAD_HOST: ${cyan(HOST)}${unsetHint}`);
|
|
1518
1537
|
if (isOverrideActive()) {
|
|
1519
|
-
addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(
|
|
1538
|
+
addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(repo)}`);
|
|
1520
1539
|
}
|
|
1540
|
+
addItem(section2, `${existsSync7(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
|
|
1521
1541
|
addItem(
|
|
1522
1542
|
section2,
|
|
1523
|
-
`${existsSync7(
|
|
1524
|
-
);
|
|
1525
|
-
addItem(
|
|
1526
|
-
section2,
|
|
1527
|
-
`${existsSync7(CLAUDE_HOME) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(CLAUDE_HOME)}`
|
|
1543
|
+
`${existsSync7(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
|
|
1528
1544
|
);
|
|
1529
1545
|
}
|
|
1530
1546
|
function reportRepoState(section2) {
|
|
1531
|
-
const
|
|
1547
|
+
const repo = repoHome();
|
|
1548
|
+
const state = classifyRepoState(repo, HOST);
|
|
1532
1549
|
const overrideLabel = isOverrideActive() ? " (NOMAD_REPO)" : "";
|
|
1533
1550
|
if (state === "populated") {
|
|
1534
1551
|
addItem(section2, `${green(okGlyph)} repo state: populated${overrideLabel}`);
|
|
1535
1552
|
} else if (state === "partial") {
|
|
1536
1553
|
addItem(
|
|
1537
1554
|
section2,
|
|
1538
|
-
`${yellow(warnGlyph)} repo state: partial ${reasonForPartial(
|
|
1555
|
+
`${yellow(warnGlyph)} repo state: partial ${reasonForPartial(repo, HOST)}${overrideLabel}`
|
|
1539
1556
|
);
|
|
1540
1557
|
} else {
|
|
1541
1558
|
addItem(
|
|
@@ -1546,7 +1563,7 @@ function reportRepoState(section2) {
|
|
|
1546
1563
|
}
|
|
1547
1564
|
}
|
|
1548
1565
|
function repoHasSharedSource(name) {
|
|
1549
|
-
return existsSync7(join8(
|
|
1566
|
+
return existsSync7(join8(repoHome(), "shared", name));
|
|
1550
1567
|
}
|
|
1551
1568
|
function classifySharedLink(name, p) {
|
|
1552
1569
|
let stat;
|
|
@@ -1592,8 +1609,9 @@ function classifySymlinkTarget(name, p) {
|
|
|
1592
1609
|
}
|
|
1593
1610
|
}
|
|
1594
1611
|
function reportSharedLinks(section2, map) {
|
|
1612
|
+
const claude = claudeHome();
|
|
1595
1613
|
for (const name of allSharedLinks(map)) {
|
|
1596
|
-
const p = join8(
|
|
1614
|
+
const p = join8(claude, name);
|
|
1597
1615
|
const { line, fail: fail2 } = classifySharedLink(name, p);
|
|
1598
1616
|
addItem(section2, line);
|
|
1599
1617
|
if (fail2) process.exitCode = 1;
|
|
@@ -1606,7 +1624,7 @@ init_config();
|
|
|
1606
1624
|
import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
|
|
1607
1625
|
import { join as join9 } from "node:path";
|
|
1608
1626
|
function loadBaseSettings(section2) {
|
|
1609
|
-
const basePath = join9(
|
|
1627
|
+
const basePath = join9(repoHome(), "shared", "settings.base.json");
|
|
1610
1628
|
if (!existsSync8(basePath)) {
|
|
1611
1629
|
addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
|
|
1612
1630
|
process.exitCode = 1;
|
|
@@ -1615,7 +1633,7 @@ function loadBaseSettings(section2) {
|
|
|
1615
1633
|
return readJsonSafe(basePath, basePath, section2);
|
|
1616
1634
|
}
|
|
1617
1635
|
function loadAndReportSettings(section2) {
|
|
1618
|
-
const settingsPath = join9(
|
|
1636
|
+
const settingsPath = join9(claudeHome(), "settings.json");
|
|
1619
1637
|
if (!existsSync8(settingsPath)) return null;
|
|
1620
1638
|
const settings = readJsonSafe(settingsPath, settingsPath, section2);
|
|
1621
1639
|
if (settings === null) return null;
|
|
@@ -1631,7 +1649,8 @@ function loadAndReportSettings(section2) {
|
|
|
1631
1649
|
return settings;
|
|
1632
1650
|
}
|
|
1633
1651
|
function reportHostOverrides(section2, base, settings) {
|
|
1634
|
-
const
|
|
1652
|
+
const repo = repoHome();
|
|
1653
|
+
const hostFile = join9(repo, "hosts", `${HOST}.json`);
|
|
1635
1654
|
let drift = [];
|
|
1636
1655
|
if (base !== null && settings !== null) {
|
|
1637
1656
|
const baseKeys = new Set(Object.keys(base));
|
|
@@ -1646,7 +1665,7 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
1646
1665
|
section2,
|
|
1647
1666
|
`${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
|
|
1648
1667
|
);
|
|
1649
|
-
const hostsDir = join9(
|
|
1668
|
+
const hostsDir = join9(repo, "hosts");
|
|
1650
1669
|
if (existsSync8(hostsDir)) {
|
|
1651
1670
|
const cands = readdirSync2(hostsDir).filter((f) => f.endsWith(".json"));
|
|
1652
1671
|
if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
|
|
@@ -1674,7 +1693,7 @@ function reportMappedProjects(section2, map) {
|
|
|
1674
1693
|
}
|
|
1675
1694
|
}
|
|
1676
1695
|
function reportUnmappedProjects(section2, map) {
|
|
1677
|
-
const localProjects = join10(
|
|
1696
|
+
const localProjects = join10(claudeHome(), "projects");
|
|
1678
1697
|
if (!existsSync9(localProjects)) return;
|
|
1679
1698
|
let localDirs;
|
|
1680
1699
|
try {
|
|
@@ -1715,7 +1734,7 @@ function reportPathCollisions(section2, map) {
|
|
|
1715
1734
|
else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
|
|
1716
1735
|
}
|
|
1717
1736
|
function reportPathMap(section2) {
|
|
1718
|
-
const mapPath = join10(
|
|
1737
|
+
const mapPath = join10(repoHome(), "path-map.json");
|
|
1719
1738
|
if (!existsSync9(mapPath)) {
|
|
1720
1739
|
addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
|
|
1721
1740
|
process.exitCode = 1;
|
|
@@ -1803,11 +1822,12 @@ function reportGitleaksProbe(section2) {
|
|
|
1803
1822
|
}
|
|
1804
1823
|
}
|
|
1805
1824
|
function reportGitlinks(section2) {
|
|
1806
|
-
const
|
|
1825
|
+
const repo = repoHome();
|
|
1826
|
+
const sharedDir = join14(repo, "shared");
|
|
1807
1827
|
if (existsSync12(sharedDir)) {
|
|
1808
1828
|
const gitlinks = findGitlinks(sharedDir);
|
|
1809
1829
|
for (const p of gitlinks) {
|
|
1810
|
-
const rel = relative2(
|
|
1830
|
+
const rel = relative2(repo, p);
|
|
1811
1831
|
addItem(
|
|
1812
1832
|
section2,
|
|
1813
1833
|
`${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
|
|
@@ -1823,7 +1843,7 @@ function reportGitlinks(section2) {
|
|
|
1823
1843
|
function reportRemote(section2) {
|
|
1824
1844
|
try {
|
|
1825
1845
|
const url = execFileSync3("git", ["remote", "get-url", "origin"], {
|
|
1826
|
-
cwd:
|
|
1846
|
+
cwd: repoHome(),
|
|
1827
1847
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1828
1848
|
}).toString().trim();
|
|
1829
1849
|
addItem(section2, `${dim(infoGlyph)} remote origin: ${cyan(url)}`);
|
|
@@ -1833,7 +1853,7 @@ function reportRemote(section2) {
|
|
|
1833
1853
|
}
|
|
1834
1854
|
function reportRebaseClean(section2) {
|
|
1835
1855
|
try {
|
|
1836
|
-
const status = gitStatusPorcelainZ(
|
|
1856
|
+
const status = gitStatusPorcelainZ(repoHome());
|
|
1837
1857
|
if (status.length > 0) {
|
|
1838
1858
|
addItem(
|
|
1839
1859
|
section2,
|
|
@@ -1845,7 +1865,7 @@ function reportRebaseClean(section2) {
|
|
|
1845
1865
|
}
|
|
1846
1866
|
function reportRebaseState(section2) {
|
|
1847
1867
|
try {
|
|
1848
|
-
const wedge = detectWedge(
|
|
1868
|
+
const wedge = detectWedge(repoHome());
|
|
1849
1869
|
if (wedge !== null) {
|
|
1850
1870
|
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
1851
1871
|
addItem(
|
|
@@ -1886,16 +1906,16 @@ function dirSizeBytes(dir) {
|
|
|
1886
1906
|
}
|
|
1887
1907
|
return bytes;
|
|
1888
1908
|
}
|
|
1889
|
-
function totalSizeMb(
|
|
1909
|
+
function totalSizeMb(backupBase2, dirs) {
|
|
1890
1910
|
let bytes = 0;
|
|
1891
|
-
for (const name of dirs) bytes += dirSizeBytes(join15(
|
|
1911
|
+
for (const name of dirs) bytes += dirSizeBytes(join15(backupBase2, name));
|
|
1892
1912
|
return bytes / BYTES_PER_MB;
|
|
1893
1913
|
}
|
|
1894
|
-
function reportBackupsCheck(section2,
|
|
1895
|
-
if (!existsSync13(
|
|
1896
|
-
const dirs = safeReaddir(
|
|
1914
|
+
function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
1915
|
+
if (!existsSync13(backupBase2)) return;
|
|
1916
|
+
const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
|
|
1897
1917
|
const count = dirs.length;
|
|
1898
|
-
const sizeMb = totalSizeMb(
|
|
1918
|
+
const sizeMb = totalSizeMb(backupBase2, dirs);
|
|
1899
1919
|
if (count > DOCTOR_BACKUP_COUNT_WARN || sizeMb > DOCTOR_BACKUP_SIZE_WARN_MB) {
|
|
1900
1920
|
addItem(
|
|
1901
1921
|
section2,
|
|
@@ -1944,7 +1964,7 @@ function fetchSchemaKeys() {
|
|
|
1944
1964
|
}
|
|
1945
1965
|
}
|
|
1946
1966
|
function reportCheckSchema(section2) {
|
|
1947
|
-
const settingsPath = join16(
|
|
1967
|
+
const settingsPath = join16(claudeHome(), "settings.json");
|
|
1948
1968
|
if (!existsSync14(settingsPath)) {
|
|
1949
1969
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
1950
1970
|
return;
|
|
@@ -1986,7 +2006,7 @@ init_config();
|
|
|
1986
2006
|
init_push_gitleaks();
|
|
1987
2007
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
1988
2008
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
1989
|
-
return join18(
|
|
2009
|
+
return join18(claudeHome(), "projects", encoded, `${sid}.jsonl`);
|
|
1990
2010
|
}
|
|
1991
2011
|
function reportSessionFindings(section2, bySession) {
|
|
1992
2012
|
for (const [sid, counts] of bySession) {
|
|
@@ -2095,7 +2115,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
2095
2115
|
if (rel.split(sep2).length > 1) return true;
|
|
2096
2116
|
if (statSync4(srcPath).isDirectory()) return true;
|
|
2097
2117
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
2098
|
-
|
|
2118
|
+
item(`skip ${rel}: extension not in allowlist`);
|
|
2099
2119
|
return false;
|
|
2100
2120
|
}
|
|
2101
2121
|
});
|
|
@@ -2109,15 +2129,17 @@ function remapPull(ts, opts = {}) {
|
|
|
2109
2129
|
let unmapped = 0;
|
|
2110
2130
|
const pulled = [];
|
|
2111
2131
|
const wouldPull = [];
|
|
2112
|
-
const
|
|
2113
|
-
const
|
|
2132
|
+
const repo = repoHome();
|
|
2133
|
+
const claude = claudeHome();
|
|
2134
|
+
const mapPath = join19(repo, "path-map.json");
|
|
2135
|
+
const repoProjects = join19(repo, "shared", "projects");
|
|
2114
2136
|
if (!existsSync15(mapPath) || !existsSync15(repoProjects)) {
|
|
2115
2137
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
2116
2138
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
2117
2139
|
return { unmapped: 0, pulled, wouldPull };
|
|
2118
2140
|
}
|
|
2119
2141
|
const map = readJson(mapPath);
|
|
2120
|
-
const localProjects = join19(
|
|
2142
|
+
const localProjects = join19(claude, "projects");
|
|
2121
2143
|
if (!dryRun) mkdirSync3(localProjects, { recursive: true });
|
|
2122
2144
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
2123
2145
|
assertSafeLogical(logical);
|
|
@@ -2173,14 +2195,16 @@ function remapPush(ts, opts = {}) {
|
|
|
2173
2195
|
let unmapped = 0;
|
|
2174
2196
|
const pushed = [];
|
|
2175
2197
|
const wouldPush = [];
|
|
2176
|
-
const
|
|
2198
|
+
const repo = repoHome();
|
|
2199
|
+
const claude = claudeHome();
|
|
2200
|
+
const mapPath = join19(repo, "path-map.json");
|
|
2177
2201
|
if (!existsSync15(mapPath)) {
|
|
2178
2202
|
log("no path-map.json; skipping session export");
|
|
2179
2203
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
2180
2204
|
}
|
|
2181
2205
|
const map = readJson(mapPath);
|
|
2182
|
-
const localProjects = join19(
|
|
2183
|
-
const repoProjects = join19(
|
|
2206
|
+
const localProjects = join19(claude, "projects");
|
|
2207
|
+
const repoProjects = join19(repo, "shared", "projects");
|
|
2184
2208
|
const reverse = buildReverseMap(map);
|
|
2185
2209
|
if (!existsSync15(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
2186
2210
|
if (!dryRun) mkdirSync3(repoProjects, { recursive: true });
|
|
@@ -2195,7 +2219,7 @@ function remapPush(ts, opts = {}) {
|
|
|
2195
2219
|
wouldPush.push(logical);
|
|
2196
2220
|
continue;
|
|
2197
2221
|
}
|
|
2198
|
-
backupRepoWrite(repoDst, ts,
|
|
2222
|
+
backupRepoWrite(repoDst, ts, repo);
|
|
2199
2223
|
copyDirJsonlOnly(join19(localProjects, dir), repoDst);
|
|
2200
2224
|
pushed.push(logical);
|
|
2201
2225
|
}
|
|
@@ -2208,7 +2232,8 @@ init_utils_json();
|
|
|
2208
2232
|
function buildScanTree(tmpRoot) {
|
|
2209
2233
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
2210
2234
|
let staged = 0;
|
|
2211
|
-
const
|
|
2235
|
+
const repo = repoHome();
|
|
2236
|
+
const mapPath = join20(repo, "path-map.json");
|
|
2212
2237
|
if (!existsSync16(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
2213
2238
|
let map;
|
|
2214
2239
|
try {
|
|
@@ -2226,7 +2251,7 @@ function buildScanTree(tmpRoot) {
|
|
|
2226
2251
|
if (!p || p === "TBD") continue;
|
|
2227
2252
|
reverse.set(encodePath(p), logical);
|
|
2228
2253
|
}
|
|
2229
|
-
const localProjects = join20(
|
|
2254
|
+
const localProjects = join20(claudeHome(), "projects");
|
|
2230
2255
|
if (!existsSync16(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
2231
2256
|
for (const dir of readdirSync7(localProjects)) {
|
|
2232
2257
|
const logical = reverse.get(dir);
|
|
@@ -2353,7 +2378,7 @@ function safeRead(path) {
|
|
|
2353
2378
|
}
|
|
2354
2379
|
}
|
|
2355
2380
|
function reportHookScopeCheck(section2) {
|
|
2356
|
-
const hooksDir = join21(
|
|
2381
|
+
const hooksDir = join21(claudeHome(), "hooks");
|
|
2357
2382
|
if (!existsSync17(hooksDir)) {
|
|
2358
2383
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
2359
2384
|
return;
|
|
@@ -2385,13 +2410,14 @@ import { existsSync as existsSync18 } from "node:fs";
|
|
|
2385
2410
|
import { join as join22 } from "node:path";
|
|
2386
2411
|
init_config();
|
|
2387
2412
|
function expandHome(token) {
|
|
2388
|
-
|
|
2413
|
+
const h2 = home();
|
|
2414
|
+
return token.replace(/^\$\{HOME\}/, h2).replace(/^\$HOME/, h2).replace(/^~/, h2);
|
|
2389
2415
|
}
|
|
2390
2416
|
function stripShellPunctuation(token) {
|
|
2391
2417
|
return token.replace(/^['"]+/, "").replace(/['"`;)|&>]+$/, "");
|
|
2392
2418
|
}
|
|
2393
2419
|
function* claudePathsIn(command) {
|
|
2394
|
-
const claudePrefix = `${
|
|
2420
|
+
const claudePrefix = `${claudeHome()}/`;
|
|
2395
2421
|
for (const segment of command.split(/&&|\|\||;|\|/)) {
|
|
2396
2422
|
for (const raw of segment.trim().split(/\s+/).filter(Boolean)) {
|
|
2397
2423
|
const expanded = expandHome(stripShellPunctuation(raw));
|
|
@@ -2433,7 +2459,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
2433
2459
|
return anyFail;
|
|
2434
2460
|
}
|
|
2435
2461
|
function reportHooksTargetCheck(section2) {
|
|
2436
|
-
const settingsPath = join22(
|
|
2462
|
+
const settingsPath = join22(claudeHome(), "settings.json");
|
|
2437
2463
|
if (!existsSync18(settingsPath)) {
|
|
2438
2464
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
2439
2465
|
return;
|
|
@@ -2535,7 +2561,8 @@ function relativeRequireTargetsBroken(scriptPath) {
|
|
|
2535
2561
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
2536
2562
|
init_config();
|
|
2537
2563
|
function expandHome2(token) {
|
|
2538
|
-
|
|
2564
|
+
const h2 = home();
|
|
2565
|
+
return token.replace(/^\$\{HOME\}/, h2).replace(/^\$HOME/, h2).replace(/^~/, h2);
|
|
2539
2566
|
}
|
|
2540
2567
|
function stripShellPunct(token) {
|
|
2541
2568
|
const head = token.replace(/^['"]+/, "");
|
|
@@ -2553,7 +2580,7 @@ function commandTokens(command) {
|
|
|
2553
2580
|
return tokens;
|
|
2554
2581
|
}
|
|
2555
2582
|
function readPathMapSafe() {
|
|
2556
|
-
const mapPath = join24(
|
|
2583
|
+
const mapPath = join24(repoHome(), "path-map.json");
|
|
2557
2584
|
if (!existsSync20(mapPath)) return { projects: {} };
|
|
2558
2585
|
try {
|
|
2559
2586
|
return JSON.parse(readFileSync6(mapPath, "utf8"));
|
|
@@ -2563,7 +2590,7 @@ function readPathMapSafe() {
|
|
|
2563
2590
|
}
|
|
2564
2591
|
function resolvesUnderSymlinkedShared(scriptPath, sharedLinkNames) {
|
|
2565
2592
|
for (const name of sharedLinkNames) {
|
|
2566
|
-
const prefix = `${
|
|
2593
|
+
const prefix = `${claudeHome()}/${name}/`;
|
|
2567
2594
|
if (scriptPath.startsWith(prefix)) return true;
|
|
2568
2595
|
}
|
|
2569
2596
|
return false;
|
|
@@ -2627,7 +2654,7 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
2627
2654
|
return anyWarn;
|
|
2628
2655
|
}
|
|
2629
2656
|
function reportPreserveSymlinksCheck(section2) {
|
|
2630
|
-
const settingsPath = join24(
|
|
2657
|
+
const settingsPath = join24(claudeHome(), "settings.json");
|
|
2631
2658
|
if (!existsSync20(settingsPath)) {
|
|
2632
2659
|
addItem(
|
|
2633
2660
|
section2,
|
|
@@ -2659,17 +2686,150 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
2659
2686
|
}
|
|
2660
2687
|
}
|
|
2661
2688
|
|
|
2689
|
+
// src/commands.doctor.checks.settings-drift.ts
|
|
2690
|
+
init_color();
|
|
2691
|
+
import { existsSync as existsSync21, readFileSync as readFileSync7 } from "node:fs";
|
|
2692
|
+
import { join as join25 } from "node:path";
|
|
2693
|
+
init_config();
|
|
2694
|
+
init_utils_json();
|
|
2695
|
+
function arraysEqual(a, b) {
|
|
2696
|
+
if (a.length !== b.length) return false;
|
|
2697
|
+
for (let i = 0; i < a.length; i++) {
|
|
2698
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
2699
|
+
}
|
|
2700
|
+
return true;
|
|
2701
|
+
}
|
|
2702
|
+
function objectsEqual(a, b) {
|
|
2703
|
+
const aKeys = Object.keys(a);
|
|
2704
|
+
const bKeys = Object.keys(b);
|
|
2705
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
2706
|
+
for (const k of aKeys) {
|
|
2707
|
+
if (!Object.hasOwn(b, k)) return false;
|
|
2708
|
+
if (!deepEqual(a[k], b[k])) return false;
|
|
2709
|
+
}
|
|
2710
|
+
return true;
|
|
2711
|
+
}
|
|
2712
|
+
function deepEqual(a, b) {
|
|
2713
|
+
if (a === b) return true;
|
|
2714
|
+
if (a === null || b === null) return false;
|
|
2715
|
+
if (Array.isArray(a) && Array.isArray(b)) return arraysEqual(a, b);
|
|
2716
|
+
if (Array.isArray(a) || Array.isArray(b)) return false;
|
|
2717
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
2718
|
+
return objectsEqual(a, b);
|
|
2719
|
+
}
|
|
2720
|
+
return false;
|
|
2721
|
+
}
|
|
2722
|
+
function diffMergedSettings(merged, settings) {
|
|
2723
|
+
const missing = [];
|
|
2724
|
+
const changed = [];
|
|
2725
|
+
const extra = [];
|
|
2726
|
+
const settingsKeys = new Set(Object.keys(settings));
|
|
2727
|
+
for (const key of Object.keys(merged)) {
|
|
2728
|
+
if (!settingsKeys.has(key)) {
|
|
2729
|
+
missing.push(key);
|
|
2730
|
+
} else if (!deepEqual(merged[key], settings[key])) {
|
|
2731
|
+
changed.push(key);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
const mergedKeys = new Set(Object.keys(merged));
|
|
2735
|
+
for (const key of Object.keys(settings)) {
|
|
2736
|
+
if (!mergedKeys.has(key)) extra.push(key);
|
|
2737
|
+
}
|
|
2738
|
+
const collator = (a, b) => a.localeCompare(b, "en");
|
|
2739
|
+
return {
|
|
2740
|
+
missing: missing.toSorted(collator),
|
|
2741
|
+
changed: changed.toSorted(collator),
|
|
2742
|
+
extra: extra.toSorted(collator)
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
function tryReadJson(filePath) {
|
|
2746
|
+
try {
|
|
2747
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
2748
|
+
const parsed = JSON.parse(raw);
|
|
2749
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
2750
|
+
return parsed;
|
|
2751
|
+
} catch {
|
|
2752
|
+
return null;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
function reportSettingsDriftCheck(section2) {
|
|
2756
|
+
const claude = claudeHome();
|
|
2757
|
+
const repo = repoHome();
|
|
2758
|
+
const host = HOST;
|
|
2759
|
+
const settingsPath = join25(claude, "settings.json");
|
|
2760
|
+
const basePath = join25(repo, "shared", "settings.base.json");
|
|
2761
|
+
const hostPath = join25(repo, "hosts", `${host}.json`);
|
|
2762
|
+
if (!existsSync21(settingsPath)) {
|
|
2763
|
+
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
2764
|
+
return;
|
|
2765
|
+
}
|
|
2766
|
+
if (!existsSync21(basePath)) {
|
|
2767
|
+
addItem(
|
|
2768
|
+
section2,
|
|
2769
|
+
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
2770
|
+
);
|
|
2771
|
+
return;
|
|
2772
|
+
}
|
|
2773
|
+
const base = tryReadJson(basePath);
|
|
2774
|
+
if (base === null) {
|
|
2775
|
+
addItem(
|
|
2776
|
+
section2,
|
|
2777
|
+
`${dim(infoGlyph)} shared/settings.base.json unparseable; skipping merge-drift check`
|
|
2778
|
+
);
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
const settings = tryReadJson(settingsPath);
|
|
2782
|
+
if (settings === null) {
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
const hostExists = existsSync21(hostPath);
|
|
2786
|
+
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
2787
|
+
if (hostExists && hostObj === null) {
|
|
2788
|
+
addItem(
|
|
2789
|
+
section2,
|
|
2790
|
+
`${yellow(warnGlyph)} hosts/${host}.json unparseable; 'nomad pull' will fail (fix the host file)`
|
|
2791
|
+
);
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
const merged = deepMerge(base, hostObj ?? {});
|
|
2795
|
+
const { missing, changed, extra } = diffMergedSettings(merged, settings);
|
|
2796
|
+
emitDriftRows(section2, missing, changed, extra, host, hostExists);
|
|
2797
|
+
}
|
|
2798
|
+
function emitDriftRows(section2, missing, changed, extra, host, hostFileExists) {
|
|
2799
|
+
if (missing.length > 0) {
|
|
2800
|
+
addItem(
|
|
2801
|
+
section2,
|
|
2802
|
+
`${yellow(warnGlyph)} settings.json drift: merged keys missing locally: ${missing.join(", ")} (external writer clobbered settings.json; run 'nomad pull')`
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2805
|
+
if (changed.length > 0) {
|
|
2806
|
+
addItem(
|
|
2807
|
+
section2,
|
|
2808
|
+
`${yellow(warnGlyph)} settings.json drift: merged keys with changed values: ${changed.join(", ")} (run 'nomad pull')`
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
if (extra.length > 0 && hostFileExists) {
|
|
2812
|
+
addItem(
|
|
2813
|
+
section2,
|
|
2814
|
+
`${dim(infoGlyph)} settings.json has ${extra.length} local-only key(s) not in base+host merge: ${extra.join(", ")} (promotion candidates for shared/settings.base.json or hosts/${host}.json)`
|
|
2815
|
+
);
|
|
2816
|
+
}
|
|
2817
|
+
if (missing.length === 0 && changed.length === 0 && extra.length === 0) {
|
|
2818
|
+
addItem(section2, `${green(okGlyph)} settings.json matches base+host merge`);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2662
2822
|
// src/commands.doctor.ts
|
|
2663
2823
|
init_config();
|
|
2664
2824
|
|
|
2665
2825
|
// src/commands.doctor.engine.ts
|
|
2666
2826
|
init_color();
|
|
2667
|
-
import { readFileSync as
|
|
2827
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
2668
2828
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2669
2829
|
|
|
2670
2830
|
// src/commands.doctor.version.ts
|
|
2671
2831
|
init_color();
|
|
2672
|
-
import { readFileSync as
|
|
2832
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
2673
2833
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2674
2834
|
init_config();
|
|
2675
2835
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
@@ -2686,7 +2846,7 @@ function compareSemver(a, b) {
|
|
|
2686
2846
|
function readLocalVersion() {
|
|
2687
2847
|
try {
|
|
2688
2848
|
const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
|
|
2689
|
-
const parsed = JSON.parse(
|
|
2849
|
+
const parsed = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
2690
2850
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
2691
2851
|
return parsed.version;
|
|
2692
2852
|
}
|
|
@@ -2745,7 +2905,7 @@ function parseMinVersion(spec) {
|
|
|
2745
2905
|
function readEnginesNode() {
|
|
2746
2906
|
try {
|
|
2747
2907
|
const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
|
|
2748
|
-
const parsed = JSON.parse(
|
|
2908
|
+
const parsed = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
2749
2909
|
const node = parsed.engines?.node;
|
|
2750
2910
|
if (typeof node === "string" && node.length > 0) return node;
|
|
2751
2911
|
return null;
|
|
@@ -2774,8 +2934,8 @@ function reportNodeEngineCheck(section2) {
|
|
|
2774
2934
|
// src/commands.doctor.gitleaks-version.ts
|
|
2775
2935
|
init_color();
|
|
2776
2936
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
2777
|
-
import { existsSync as
|
|
2778
|
-
import { join as
|
|
2937
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
2938
|
+
import { join as join26 } from "node:path";
|
|
2779
2939
|
init_config();
|
|
2780
2940
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
2781
2941
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -2784,7 +2944,7 @@ function majorMinorOf(value) {
|
|
|
2784
2944
|
return m === null ? null : [m[1], m[2]];
|
|
2785
2945
|
}
|
|
2786
2946
|
function readGitleaksVersion(run, tomlExists) {
|
|
2787
|
-
const tomlPath =
|
|
2947
|
+
const tomlPath = join26(repoHome(), ".gitleaks.toml");
|
|
2788
2948
|
const args = ["version"];
|
|
2789
2949
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
2790
2950
|
try {
|
|
@@ -2796,7 +2956,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
2796
2956
|
return null;
|
|
2797
2957
|
}
|
|
2798
2958
|
}
|
|
2799
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists =
|
|
2959
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync22) {
|
|
2800
2960
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
2801
2961
|
if (raw === null) return;
|
|
2802
2962
|
const local = majorMinorOf(raw);
|
|
@@ -2935,7 +3095,7 @@ function readOriginRemote(cwd, run = execFileSync9) {
|
|
|
2935
3095
|
function reportActionsDrift(section2, run = execFileSync10) {
|
|
2936
3096
|
let remote;
|
|
2937
3097
|
try {
|
|
2938
|
-
remote = readOriginRemote(
|
|
3098
|
+
remote = readOriginRemote(repoHome(), run);
|
|
2939
3099
|
} catch {
|
|
2940
3100
|
return;
|
|
2941
3101
|
}
|
|
@@ -2965,15 +3125,15 @@ function reportActionsDrift(section2, run = execFileSync10) {
|
|
|
2965
3125
|
|
|
2966
3126
|
// src/commands.doctor.verdict.ts
|
|
2967
3127
|
init_color();
|
|
2968
|
-
function isFailLine(
|
|
2969
|
-
return
|
|
3128
|
+
function isFailLine(item2) {
|
|
3129
|
+
return item2.includes(failGlyph);
|
|
2970
3130
|
}
|
|
2971
|
-
function isWarnLine(
|
|
2972
|
-
return !isFailLine(
|
|
3131
|
+
function isWarnLine(item2) {
|
|
3132
|
+
return !isFailLine(item2) && item2.includes(warnGlyph);
|
|
2973
3133
|
}
|
|
2974
3134
|
function buildVerdictSection(sections) {
|
|
2975
3135
|
const summary = section("Summary");
|
|
2976
|
-
const lines = sections.flatMap((s) => s.items).map((
|
|
3136
|
+
const lines = sections.flatMap((s) => s.items).map((item2) => item2.replace(/^\t/, ""));
|
|
2977
3137
|
const failures = lines.filter(isFailLine);
|
|
2978
3138
|
const warnings = lines.filter(isWarnLine);
|
|
2979
3139
|
for (const line of [...failures, ...warnings]) addItem(summary, line);
|
|
@@ -2996,8 +3156,8 @@ function cmdDoctor(opts = {}) {
|
|
|
2996
3156
|
reportHostAndPaths(host);
|
|
2997
3157
|
reportRepoState(host);
|
|
2998
3158
|
const links = section("Shared links");
|
|
2999
|
-
const mapPath =
|
|
3000
|
-
const rawMap =
|
|
3159
|
+
const mapPath = join27(repoHome(), "path-map.json");
|
|
3160
|
+
const rawMap = existsSync23(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
3001
3161
|
const map = rawMap ?? { projects: {} };
|
|
3002
3162
|
reportSharedLinks(links, map);
|
|
3003
3163
|
const hooksScan = section("Hook targets");
|
|
@@ -3008,6 +3168,7 @@ function cmdDoctor(opts = {}) {
|
|
|
3008
3168
|
const base = loadBaseSettings(settings);
|
|
3009
3169
|
const parsedSettings = loadAndReportSettings(settings);
|
|
3010
3170
|
reportHostOverrides(settings, base, parsedSettings);
|
|
3171
|
+
reportSettingsDriftCheck(settings);
|
|
3011
3172
|
const pathMap = section("Path map");
|
|
3012
3173
|
reportPathMap(pathMap);
|
|
3013
3174
|
const neverSync = section("Never-sync");
|
|
@@ -3051,16 +3212,15 @@ function cmdDoctor(opts = {}) {
|
|
|
3051
3212
|
// src/commands.drop-session.ts
|
|
3052
3213
|
init_config();
|
|
3053
3214
|
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
3054
|
-
import { existsSync as
|
|
3055
|
-
import { join as
|
|
3215
|
+
import { existsSync as existsSync25, readdirSync as readdirSync9, statSync as statSync5 } from "node:fs";
|
|
3216
|
+
import { join as join30, relative as relative4 } from "node:path";
|
|
3056
3217
|
|
|
3057
3218
|
// src/commands.drop-session.git.ts
|
|
3058
|
-
init_config();
|
|
3059
3219
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
3060
|
-
function expandStagedDir(dirRel) {
|
|
3220
|
+
function expandStagedDir(dirRel, repo) {
|
|
3061
3221
|
try {
|
|
3062
3222
|
const out = execFileSync11("git", ["ls-files", "-z", "--", dirRel], {
|
|
3063
|
-
cwd:
|
|
3223
|
+
cwd: repo,
|
|
3064
3224
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3065
3225
|
});
|
|
3066
3226
|
return out.toString().split("\0").filter((p) => p !== "");
|
|
@@ -3068,10 +3228,10 @@ function expandStagedDir(dirRel) {
|
|
|
3068
3228
|
return [];
|
|
3069
3229
|
}
|
|
3070
3230
|
}
|
|
3071
|
-
function isTrackedInHead(rel) {
|
|
3231
|
+
function isTrackedInHead(rel, repo) {
|
|
3072
3232
|
try {
|
|
3073
3233
|
execFileSync11("git", ["cat-file", "-e", `HEAD:${rel}`], {
|
|
3074
|
-
cwd:
|
|
3234
|
+
cwd: repo,
|
|
3075
3235
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3076
3236
|
});
|
|
3077
3237
|
return true;
|
|
@@ -3079,10 +3239,10 @@ function isTrackedInHead(rel) {
|
|
|
3079
3239
|
return false;
|
|
3080
3240
|
}
|
|
3081
3241
|
}
|
|
3082
|
-
function isInIndex(rel) {
|
|
3242
|
+
function isInIndex(rel, repo) {
|
|
3083
3243
|
try {
|
|
3084
3244
|
const out = execFileSync11("git", ["ls-files", "--", rel], {
|
|
3085
|
-
cwd:
|
|
3245
|
+
cwd: repo,
|
|
3086
3246
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3087
3247
|
});
|
|
3088
3248
|
return out.toString().trim() !== "";
|
|
@@ -3095,8 +3255,8 @@ function isInIndex(rel) {
|
|
|
3095
3255
|
init_config();
|
|
3096
3256
|
init_utils();
|
|
3097
3257
|
init_utils_json();
|
|
3098
|
-
import { existsSync as
|
|
3099
|
-
import { join as
|
|
3258
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
3259
|
+
import { join as join28 } from "node:path";
|
|
3100
3260
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
3101
3261
|
function reportScrubHint(id, matches) {
|
|
3102
3262
|
const live = resolveLiveTranscript(id, matches);
|
|
@@ -3112,16 +3272,17 @@ function reportScrubHint(id, matches) {
|
|
|
3112
3272
|
}
|
|
3113
3273
|
function resolveLiveTranscript(id, matches) {
|
|
3114
3274
|
try {
|
|
3115
|
-
const mapPath =
|
|
3116
|
-
if (!
|
|
3275
|
+
const mapPath = join28(repoHome(), "path-map.json");
|
|
3276
|
+
if (!existsSync24(mapPath)) return null;
|
|
3117
3277
|
const projects = readJson(mapPath).projects;
|
|
3278
|
+
const claude = claudeHome();
|
|
3118
3279
|
for (const rel of matches) {
|
|
3119
3280
|
const logical = SHARED_PROJECT_LOGICAL.exec(rel)?.[1];
|
|
3120
3281
|
if (logical === void 0) continue;
|
|
3121
3282
|
const abs = projects[logical]?.[HOST];
|
|
3122
3283
|
if (abs === void 0) continue;
|
|
3123
|
-
const live =
|
|
3124
|
-
if (
|
|
3284
|
+
const live = join28(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
3285
|
+
if (existsSync24(live)) return live;
|
|
3125
3286
|
}
|
|
3126
3287
|
return null;
|
|
3127
3288
|
} catch {
|
|
@@ -3135,13 +3296,16 @@ init_utils();
|
|
|
3135
3296
|
// src/utils.lockfile.ts
|
|
3136
3297
|
init_config();
|
|
3137
3298
|
init_utils();
|
|
3138
|
-
import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as
|
|
3139
|
-
import { dirname as dirname4, join as
|
|
3140
|
-
|
|
3299
|
+
import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3300
|
+
import { dirname as dirname4, join as join29 } from "node:path";
|
|
3301
|
+
function lockFilePath() {
|
|
3302
|
+
return join29(home(), ".cache", "claude-nomad", "nomad.lock");
|
|
3303
|
+
}
|
|
3141
3304
|
function acquireLock(verb) {
|
|
3142
|
-
|
|
3305
|
+
const lp = lockFilePath();
|
|
3306
|
+
mkdirSync5(dirname4(lp), { recursive: true });
|
|
3143
3307
|
try {
|
|
3144
|
-
const fd = openSync3(
|
|
3308
|
+
const fd = openSync3(lp, "wx");
|
|
3145
3309
|
try {
|
|
3146
3310
|
writeFileSync3(fd, String(process.pid));
|
|
3147
3311
|
} catch (writeErr) {
|
|
@@ -3150,55 +3314,56 @@ function acquireLock(verb) {
|
|
|
3150
3314
|
} catch {
|
|
3151
3315
|
}
|
|
3152
3316
|
try {
|
|
3153
|
-
unlinkSync(
|
|
3317
|
+
unlinkSync(lp);
|
|
3154
3318
|
} catch {
|
|
3155
3319
|
}
|
|
3156
3320
|
throw writeErr;
|
|
3157
3321
|
}
|
|
3158
|
-
return { fd };
|
|
3322
|
+
return { fd, path: lp };
|
|
3159
3323
|
} catch (err) {
|
|
3160
3324
|
const code = err.code;
|
|
3161
3325
|
if (code !== "EEXIST") throw err;
|
|
3162
|
-
return checkStaleAndRetry(verb);
|
|
3326
|
+
return checkStaleAndRetry(verb, lp);
|
|
3163
3327
|
}
|
|
3164
3328
|
}
|
|
3165
3329
|
function releaseLock(handle) {
|
|
3166
3330
|
if (handle === null) return;
|
|
3331
|
+
const lp = handle.path;
|
|
3167
3332
|
try {
|
|
3168
3333
|
closeSync3(handle.fd);
|
|
3169
3334
|
} catch {
|
|
3170
3335
|
}
|
|
3171
3336
|
try {
|
|
3172
|
-
unlinkSync(
|
|
3337
|
+
unlinkSync(lp);
|
|
3173
3338
|
} catch (err) {
|
|
3174
3339
|
if (err.code !== "ENOENT") throw err;
|
|
3175
3340
|
}
|
|
3176
3341
|
}
|
|
3177
|
-
function unlinkIfSamePid(expectedPidStr) {
|
|
3342
|
+
function unlinkIfSamePid(expectedPidStr, lp) {
|
|
3178
3343
|
let current;
|
|
3179
3344
|
try {
|
|
3180
|
-
current =
|
|
3345
|
+
current = readFileSync10(lp, "utf8").trim();
|
|
3181
3346
|
} catch {
|
|
3182
3347
|
return false;
|
|
3183
3348
|
}
|
|
3184
3349
|
if (current !== expectedPidStr) return false;
|
|
3185
3350
|
try {
|
|
3186
|
-
unlinkSync(
|
|
3351
|
+
unlinkSync(lp);
|
|
3187
3352
|
return true;
|
|
3188
3353
|
} catch {
|
|
3189
3354
|
return false;
|
|
3190
3355
|
}
|
|
3191
3356
|
}
|
|
3192
|
-
function checkStaleAndRetry(verb) {
|
|
3357
|
+
function checkStaleAndRetry(verb, lp) {
|
|
3193
3358
|
let pidStr;
|
|
3194
3359
|
try {
|
|
3195
|
-
pidStr =
|
|
3360
|
+
pidStr = readFileSync10(lp, "utf8").trim();
|
|
3196
3361
|
} catch {
|
|
3197
3362
|
pidStr = "";
|
|
3198
3363
|
}
|
|
3199
3364
|
const pid = Number.parseInt(pidStr, 10);
|
|
3200
3365
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
3201
|
-
if (unlinkIfSamePid(pidStr)) return retryOnce(verb);
|
|
3366
|
+
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
3202
3367
|
warn(`another nomad ${verb} running, skipping`);
|
|
3203
3368
|
return null;
|
|
3204
3369
|
}
|
|
@@ -3209,7 +3374,7 @@ function checkStaleAndRetry(verb) {
|
|
|
3209
3374
|
} catch (err) {
|
|
3210
3375
|
const code = err.code;
|
|
3211
3376
|
if (code === "ESRCH") {
|
|
3212
|
-
if (unlinkIfSamePid(pidStr)) return retryOnce(verb);
|
|
3377
|
+
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
3213
3378
|
warn(`another nomad ${verb} running, skipping`);
|
|
3214
3379
|
return null;
|
|
3215
3380
|
}
|
|
@@ -3217,9 +3382,9 @@ function checkStaleAndRetry(verb) {
|
|
|
3217
3382
|
return null;
|
|
3218
3383
|
}
|
|
3219
3384
|
}
|
|
3220
|
-
function retryOnce(verb) {
|
|
3385
|
+
function retryOnce(verb, lp) {
|
|
3221
3386
|
try {
|
|
3222
|
-
const fd = openSync3(
|
|
3387
|
+
const fd = openSync3(lp, "wx");
|
|
3223
3388
|
try {
|
|
3224
3389
|
writeFileSync3(fd, String(process.pid));
|
|
3225
3390
|
} catch {
|
|
@@ -3228,13 +3393,13 @@ function retryOnce(verb) {
|
|
|
3228
3393
|
} catch {
|
|
3229
3394
|
}
|
|
3230
3395
|
try {
|
|
3231
|
-
unlinkSync(
|
|
3396
|
+
unlinkSync(lp);
|
|
3232
3397
|
} catch {
|
|
3233
3398
|
}
|
|
3234
3399
|
warn(`another nomad ${verb} running, skipping`);
|
|
3235
3400
|
return null;
|
|
3236
3401
|
}
|
|
3237
|
-
return { fd };
|
|
3402
|
+
return { fd, path: lp };
|
|
3238
3403
|
} catch {
|
|
3239
3404
|
warn(`another nomad ${verb} running, skipping`);
|
|
3240
3405
|
return null;
|
|
@@ -3247,19 +3412,20 @@ function cmdDropSession(id) {
|
|
|
3247
3412
|
fail(`invalid session id: ${id}`);
|
|
3248
3413
|
process.exit(1);
|
|
3249
3414
|
}
|
|
3250
|
-
|
|
3415
|
+
const repo = repoHome();
|
|
3416
|
+
if (!existsSync25(repo)) die(`repo not cloned at ${repo}`);
|
|
3251
3417
|
const handle = acquireLock("drop-session");
|
|
3252
3418
|
if (handle === null) process.exit(0);
|
|
3253
3419
|
try {
|
|
3254
|
-
const repoProjects =
|
|
3255
|
-
if (!
|
|
3420
|
+
const repoProjects = join30(repo, "shared", "projects");
|
|
3421
|
+
if (!existsSync25(repoProjects)) {
|
|
3256
3422
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
3257
3423
|
}
|
|
3258
|
-
const matches = collectMatches(repoProjects, id);
|
|
3424
|
+
const matches = collectMatches(repoProjects, id, repo);
|
|
3259
3425
|
if (matches.length === 0) {
|
|
3260
3426
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
3261
3427
|
}
|
|
3262
|
-
for (const rel of matches) unstageOne(rel);
|
|
3428
|
+
for (const rel of matches) unstageOne(rel, repo);
|
|
3263
3429
|
reportScrubHint(id, matches);
|
|
3264
3430
|
} catch (err) {
|
|
3265
3431
|
if (!(err instanceof NomadFatal)) {
|
|
@@ -3271,37 +3437,37 @@ function cmdDropSession(id) {
|
|
|
3271
3437
|
releaseLock(handle);
|
|
3272
3438
|
}
|
|
3273
3439
|
}
|
|
3274
|
-
function collectMatches(repoProjects, id) {
|
|
3440
|
+
function collectMatches(repoProjects, id, repo) {
|
|
3275
3441
|
const matches = [];
|
|
3276
3442
|
for (const logical of readdirSync9(repoProjects)) {
|
|
3277
|
-
const candidate =
|
|
3278
|
-
if (
|
|
3279
|
-
matches.push(relative4(
|
|
3280
|
-
}
|
|
3281
|
-
const dir =
|
|
3282
|
-
if (
|
|
3283
|
-
const dirRel = relative4(
|
|
3284
|
-
const staged = expandStagedDir(dirRel);
|
|
3443
|
+
const candidate = join30(repoProjects, logical, `${id}.jsonl`);
|
|
3444
|
+
if (existsSync25(candidate)) {
|
|
3445
|
+
matches.push(relative4(repo, candidate));
|
|
3446
|
+
}
|
|
3447
|
+
const dir = join30(repoProjects, logical, id);
|
|
3448
|
+
if (existsSync25(dir) && statSync5(dir).isDirectory()) {
|
|
3449
|
+
const dirRel = relative4(repo, dir);
|
|
3450
|
+
const staged = expandStagedDir(dirRel, repo);
|
|
3285
3451
|
if (staged.length > 0) matches.push(...staged);
|
|
3286
3452
|
else matches.push(dirRel);
|
|
3287
3453
|
}
|
|
3288
3454
|
}
|
|
3289
3455
|
return matches;
|
|
3290
3456
|
}
|
|
3291
|
-
function unstageOne(rel) {
|
|
3292
|
-
if (!isInIndex(rel)) {
|
|
3293
|
-
|
|
3457
|
+
function unstageOne(rel, repo) {
|
|
3458
|
+
if (!isInIndex(rel, repo)) {
|
|
3459
|
+
item(`dropped ${rel} (already absent from index)`);
|
|
3294
3460
|
return;
|
|
3295
3461
|
}
|
|
3296
3462
|
try {
|
|
3297
|
-
if (isTrackedInHead(rel)) {
|
|
3463
|
+
if (isTrackedInHead(rel, repo)) {
|
|
3298
3464
|
execFileSync12("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
3299
|
-
cwd:
|
|
3465
|
+
cwd: repo,
|
|
3300
3466
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3301
3467
|
});
|
|
3302
3468
|
} else {
|
|
3303
3469
|
execFileSync12("git", ["rm", "--cached", "-f", "--", rel], {
|
|
3304
|
-
cwd:
|
|
3470
|
+
cwd: repo,
|
|
3305
3471
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3306
3472
|
});
|
|
3307
3473
|
}
|
|
@@ -3310,24 +3476,24 @@ function unstageOne(rel) {
|
|
|
3310
3476
|
const detail = e.stderr?.toString().trim() ?? e.message;
|
|
3311
3477
|
throw new NomadFatal(`git failed to unstage ${rel}: ${detail}`);
|
|
3312
3478
|
}
|
|
3313
|
-
|
|
3479
|
+
item(`dropped ${rel}`);
|
|
3314
3480
|
}
|
|
3315
3481
|
|
|
3316
3482
|
// src/commands.redact.ts
|
|
3317
3483
|
init_config();
|
|
3318
|
-
import { existsSync as
|
|
3319
|
-
import { dirname as dirname5, join as
|
|
3484
|
+
import { existsSync as existsSync27, statSync as statSync7 } from "node:fs";
|
|
3485
|
+
import { dirname as dirname5, join as join32 } from "node:path";
|
|
3320
3486
|
|
|
3321
3487
|
// src/commands.redact.subtree.ts
|
|
3322
|
-
import { existsSync as
|
|
3323
|
-
import { join as
|
|
3488
|
+
import { existsSync as existsSync26, lstatSync as lstatSync7, readFileSync as readFileSync11, readdirSync as readdirSync10, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3489
|
+
import { join as join31 } from "node:path";
|
|
3324
3490
|
init_utils_fs();
|
|
3325
3491
|
function collectFiles(dir, out) {
|
|
3326
|
-
if (!
|
|
3492
|
+
if (!existsSync26(dir)) return;
|
|
3327
3493
|
const st = lstatSync7(dir);
|
|
3328
3494
|
if (!st.isDirectory()) return;
|
|
3329
3495
|
for (const entry of readdirSync10(dir)) {
|
|
3330
|
-
const abs =
|
|
3496
|
+
const abs = join31(dir, entry);
|
|
3331
3497
|
const lst = lstatSync7(abs);
|
|
3332
3498
|
if (lst.isSymbolicLink()) continue;
|
|
3333
3499
|
if (lst.isDirectory()) {
|
|
@@ -3364,7 +3530,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
3364
3530
|
if (!dryRun && total > 0) {
|
|
3365
3531
|
for (const { path: filePath, findings } of dirty) {
|
|
3366
3532
|
backupBeforeWrite(filePath, ts);
|
|
3367
|
-
writeFileSync4(filePath, applyRedactions(
|
|
3533
|
+
writeFileSync4(filePath, applyRedactions(readFileSync11(filePath, "utf8"), findings), "utf8");
|
|
3368
3534
|
}
|
|
3369
3535
|
}
|
|
3370
3536
|
return { total, dirty };
|
|
@@ -3377,14 +3543,15 @@ init_utils_json();
|
|
|
3377
3543
|
init_utils();
|
|
3378
3544
|
function resolveLiveTranscript2(id) {
|
|
3379
3545
|
try {
|
|
3380
|
-
const mapPath =
|
|
3381
|
-
if (!
|
|
3546
|
+
const mapPath = join32(repoHome(), "path-map.json");
|
|
3547
|
+
if (!existsSync27(mapPath)) return null;
|
|
3382
3548
|
const projects = readJson(mapPath).projects;
|
|
3549
|
+
const claude = claudeHome();
|
|
3383
3550
|
for (const hostMap of Object.values(projects)) {
|
|
3384
3551
|
const abs = hostMap[HOST];
|
|
3385
3552
|
if (abs === void 0) continue;
|
|
3386
|
-
const live =
|
|
3387
|
-
if (
|
|
3553
|
+
const live = join32(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
3554
|
+
if (existsSync27(live)) return live;
|
|
3388
3555
|
}
|
|
3389
3556
|
return null;
|
|
3390
3557
|
} catch {
|
|
@@ -3402,17 +3569,19 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
3402
3569
|
fail(`invalid session id: ${id}`);
|
|
3403
3570
|
process.exit(1);
|
|
3404
3571
|
}
|
|
3405
|
-
|
|
3572
|
+
const repo = repoHome();
|
|
3573
|
+
const backup = backupBase();
|
|
3574
|
+
if (!existsSync27(repo)) die(`repo not cloned at ${repo}`);
|
|
3406
3575
|
const handle = acquireLock("redact");
|
|
3407
3576
|
if (handle === null) process.exit(0);
|
|
3408
3577
|
try {
|
|
3409
3578
|
const localPath = resolveLiveTranscript2(id);
|
|
3410
|
-
if (localPath === null || !
|
|
3579
|
+
if (localPath === null || !existsSync27(localPath)) {
|
|
3411
3580
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
3412
3581
|
process.exitCode = 1;
|
|
3413
3582
|
return;
|
|
3414
3583
|
}
|
|
3415
|
-
const sessionDir =
|
|
3584
|
+
const sessionDir = join32(dirname5(localPath), id);
|
|
3416
3585
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3417
3586
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
3418
3587
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3431,7 +3600,7 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
3431
3600
|
process.exitCode = 1;
|
|
3432
3601
|
return;
|
|
3433
3602
|
}
|
|
3434
|
-
const ts = freshBackupTs(
|
|
3603
|
+
const ts = freshBackupTs(backup);
|
|
3435
3604
|
const { total: totalCount, dirty } = applySubtreeRedactions(
|
|
3436
3605
|
localPath,
|
|
3437
3606
|
mainFindings,
|
|
@@ -3465,8 +3634,8 @@ ${lines}`);
|
|
|
3465
3634
|
}
|
|
3466
3635
|
|
|
3467
3636
|
// src/commands.pull.ts
|
|
3468
|
-
import { existsSync as
|
|
3469
|
-
import { join as
|
|
3637
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync8 } from "node:fs";
|
|
3638
|
+
import { join as join41 } from "node:path";
|
|
3470
3639
|
|
|
3471
3640
|
// src/commands.push.sections.ts
|
|
3472
3641
|
init_color();
|
|
@@ -3554,8 +3723,8 @@ init_config();
|
|
|
3554
3723
|
|
|
3555
3724
|
// src/extras-sync.ts
|
|
3556
3725
|
init_config();
|
|
3557
|
-
import { existsSync as
|
|
3558
|
-
import { join as
|
|
3726
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
3727
|
+
import { join as join35 } from "node:path";
|
|
3559
3728
|
|
|
3560
3729
|
// src/extras-sync.diff.ts
|
|
3561
3730
|
init_utils();
|
|
@@ -3594,8 +3763,8 @@ function listDivergingFiles(a, b) {
|
|
|
3594
3763
|
|
|
3595
3764
|
// src/extras-sync.core.ts
|
|
3596
3765
|
init_config();
|
|
3597
|
-
import { cpSync as cpSync5, existsSync as
|
|
3598
|
-
import { join as
|
|
3766
|
+
import { cpSync as cpSync5, existsSync as existsSync28, rmSync as rmSync8 } from "node:fs";
|
|
3767
|
+
import { join as join33 } from "node:path";
|
|
3599
3768
|
|
|
3600
3769
|
// src/extras-sync.guards.ts
|
|
3601
3770
|
init_utils();
|
|
@@ -3618,9 +3787,10 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
3618
3787
|
init_utils();
|
|
3619
3788
|
init_utils_json();
|
|
3620
3789
|
function loadValidatedExtras(opts) {
|
|
3621
|
-
const
|
|
3622
|
-
const
|
|
3623
|
-
|
|
3790
|
+
const repo = repoHome();
|
|
3791
|
+
const mapPath = join33(repo, "path-map.json");
|
|
3792
|
+
const repoExtras = join33(repo, "shared", "extras");
|
|
3793
|
+
if (!existsSync28(mapPath) || opts.requireRepoExtras === true && !existsSync28(repoExtras)) {
|
|
3624
3794
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
3625
3795
|
return null;
|
|
3626
3796
|
}
|
|
@@ -3662,8 +3832,8 @@ init_utils_json();
|
|
|
3662
3832
|
|
|
3663
3833
|
// src/extras-sync.remap.ts
|
|
3664
3834
|
init_config();
|
|
3665
|
-
import { existsSync as
|
|
3666
|
-
import { join as
|
|
3835
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3836
|
+
import { join as join34 } from "node:path";
|
|
3667
3837
|
init_utils_fs();
|
|
3668
3838
|
function runExtrasOp(v, dryRun, paths, backup) {
|
|
3669
3839
|
const counts = { unmapped: 0, skipped: 0 };
|
|
@@ -3671,15 +3841,15 @@ function runExtrasOp(v, dryRun, paths, backup) {
|
|
|
3671
3841
|
const would = [];
|
|
3672
3842
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
3673
3843
|
const { src, dst } = paths(t);
|
|
3674
|
-
if (!
|
|
3675
|
-
const
|
|
3844
|
+
if (!existsSync29(src)) continue;
|
|
3845
|
+
const item2 = `${t.logical}/${t.dirname}`;
|
|
3676
3846
|
if (dryRun) {
|
|
3677
|
-
would.push(
|
|
3847
|
+
would.push(item2);
|
|
3678
3848
|
continue;
|
|
3679
3849
|
}
|
|
3680
3850
|
backup(dst, t.localRoot);
|
|
3681
3851
|
copyExtras(src, dst);
|
|
3682
|
-
done.push(
|
|
3852
|
+
done.push(item2);
|
|
3683
3853
|
}
|
|
3684
3854
|
return { ...counts, done, would };
|
|
3685
3855
|
}
|
|
@@ -3687,16 +3857,17 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
3687
3857
|
const dryRun = opts.dryRun === true;
|
|
3688
3858
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
3689
3859
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
3690
|
-
const
|
|
3860
|
+
const repo = repoHome();
|
|
3861
|
+
const repoExtras = join34(repo, "shared", "extras");
|
|
3691
3862
|
if (!dryRun) mkdirSync6(repoExtras, { recursive: true });
|
|
3692
3863
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
3693
3864
|
v,
|
|
3694
3865
|
dryRun,
|
|
3695
3866
|
({ localRoot, logical, dirname: dirname7 }) => ({
|
|
3696
|
-
src:
|
|
3697
|
-
dst:
|
|
3867
|
+
src: join34(localRoot, dirname7),
|
|
3868
|
+
dst: join34(repoExtras, logical, dirname7)
|
|
3698
3869
|
}),
|
|
3699
|
-
(dst) => backupRepoWrite(dst, ts,
|
|
3870
|
+
(dst) => backupRepoWrite(dst, ts, repo)
|
|
3700
3871
|
);
|
|
3701
3872
|
return { unmapped, skipped, pushed: done, wouldPush: would };
|
|
3702
3873
|
}
|
|
@@ -3707,13 +3878,13 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
3707
3878
|
missingMsg: "no path-map or repo extras dir; skipping extras remap"
|
|
3708
3879
|
});
|
|
3709
3880
|
if (v === null) return { unmapped: 0, skipped: 0, pulled: [], wouldPull: [] };
|
|
3710
|
-
const repoExtras =
|
|
3881
|
+
const repoExtras = join34(repoHome(), "shared", "extras");
|
|
3711
3882
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
3712
3883
|
v,
|
|
3713
3884
|
dryRun,
|
|
3714
3885
|
({ localRoot, logical, dirname: dirname7 }) => ({
|
|
3715
|
-
src:
|
|
3716
|
-
dst:
|
|
3886
|
+
src: join34(repoExtras, logical, dirname7),
|
|
3887
|
+
dst: join34(localRoot, dirname7)
|
|
3717
3888
|
}),
|
|
3718
3889
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
3719
3890
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -3727,14 +3898,15 @@ function divergenceCheckExtras(ts) {
|
|
|
3727
3898
|
const v = loadValidatedExtras({});
|
|
3728
3899
|
if (v === null) return;
|
|
3729
3900
|
const counts = { unmapped: 0, skipped: 0 };
|
|
3730
|
-
const backupRoot =
|
|
3901
|
+
const backupRoot = join35(backupBase(), ts, "extras");
|
|
3902
|
+
const repo = repoHome();
|
|
3731
3903
|
for (const { logical, localRoot, dirname: dirname7 } of eachExtrasTarget(v, counts)) {
|
|
3732
|
-
const local =
|
|
3733
|
-
const
|
|
3734
|
-
if (!
|
|
3735
|
-
const diff = listDivergingFiles(local,
|
|
3904
|
+
const local = join35(localRoot, dirname7);
|
|
3905
|
+
const repoEntry = join35(repo, "shared", "extras", logical, dirname7);
|
|
3906
|
+
if (!existsSync30(local) || !existsSync30(repoEntry)) continue;
|
|
3907
|
+
const diff = listDivergingFiles(local, repoEntry);
|
|
3736
3908
|
if (diff.length === 0) continue;
|
|
3737
|
-
const projectBackupRoot =
|
|
3909
|
+
const projectBackupRoot = join35(backupRoot, encodePath(localRoot));
|
|
3738
3910
|
warn(
|
|
3739
3911
|
`local ${dirname7} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will overwrite them (backups at ${projectBackupRoot}/)`
|
|
3740
3912
|
);
|
|
@@ -3747,8 +3919,8 @@ init_config();
|
|
|
3747
3919
|
init_utils();
|
|
3748
3920
|
init_utils_fs();
|
|
3749
3921
|
init_utils_json();
|
|
3750
|
-
import { existsSync as
|
|
3751
|
-
import { join as
|
|
3922
|
+
import { existsSync as existsSync31, lstatSync as lstatSync8, rmSync as rmSync9 } from "node:fs";
|
|
3923
|
+
import { join as join36 } from "node:path";
|
|
3752
3924
|
function emitAutoMove(onPreview, linkPath, ts, name) {
|
|
3753
3925
|
if (onPreview) {
|
|
3754
3926
|
onPreview({ kind: "auto-move", from: linkPath, to: `backup/${ts}/${name}` });
|
|
@@ -3765,13 +3937,15 @@ function emitCreate(onPreview, from, to) {
|
|
|
3765
3937
|
}
|
|
3766
3938
|
function applySharedLinks(ts, map, opts = {}) {
|
|
3767
3939
|
const dryRun = opts.dryRun === true;
|
|
3940
|
+
const claude = claudeHome();
|
|
3941
|
+
const repo = repoHome();
|
|
3768
3942
|
const linkNames = allSharedLinks(map);
|
|
3769
3943
|
for (const name of linkNames) {
|
|
3770
|
-
const linkPath =
|
|
3771
|
-
const target =
|
|
3772
|
-
if (!
|
|
3944
|
+
const linkPath = join36(claude, name);
|
|
3945
|
+
const target = join36(repo, "shared", name);
|
|
3946
|
+
if (!existsSync31(linkPath)) continue;
|
|
3773
3947
|
if (lstatSync8(linkPath).isSymbolicLink()) continue;
|
|
3774
|
-
if (!
|
|
3948
|
+
if (!existsSync31(target)) continue;
|
|
3775
3949
|
if (dryRun) {
|
|
3776
3950
|
emitAutoMove(opts.onPreview, linkPath, ts, name);
|
|
3777
3951
|
continue;
|
|
@@ -3780,28 +3954,30 @@ function applySharedLinks(ts, map, opts = {}) {
|
|
|
3780
3954
|
rmSync9(linkPath, { recursive: true, force: true });
|
|
3781
3955
|
}
|
|
3782
3956
|
for (const name of linkNames) {
|
|
3783
|
-
const target =
|
|
3784
|
-
if (!
|
|
3957
|
+
const target = join36(repo, "shared", name);
|
|
3958
|
+
if (!existsSync31(target)) continue;
|
|
3785
3959
|
if (dryRun) {
|
|
3786
|
-
emitCreate(opts.onPreview,
|
|
3960
|
+
emitCreate(opts.onPreview, join36(claude, name), target);
|
|
3787
3961
|
continue;
|
|
3788
3962
|
}
|
|
3789
|
-
ensureSymlink(
|
|
3963
|
+
ensureSymlink(join36(claude, name), target);
|
|
3790
3964
|
}
|
|
3791
3965
|
}
|
|
3792
3966
|
function regenerateSettings(ts, opts = {}) {
|
|
3793
3967
|
const dryRun = opts.dryRun === true;
|
|
3794
|
-
const
|
|
3795
|
-
const
|
|
3796
|
-
|
|
3968
|
+
const repo = repoHome();
|
|
3969
|
+
const claude = claudeHome();
|
|
3970
|
+
const basePath = join36(repo, "shared", "settings.base.json");
|
|
3971
|
+
const hostPath = join36(repo, "hosts", `${HOST}.json`);
|
|
3972
|
+
if (!existsSync31(basePath)) {
|
|
3797
3973
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
3798
3974
|
}
|
|
3799
3975
|
const base = readJson(basePath);
|
|
3800
|
-
const hasOverrides =
|
|
3976
|
+
const hasOverrides = existsSync31(hostPath);
|
|
3801
3977
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
3802
3978
|
const merged = deepMerge(base, overrides);
|
|
3803
|
-
const settingsPath =
|
|
3804
|
-
if (!hasOverrides &&
|
|
3979
|
+
const settingsPath = join36(claude, "settings.json");
|
|
3980
|
+
if (!hasOverrides && existsSync31(settingsPath)) {
|
|
3805
3981
|
try {
|
|
3806
3982
|
const existing = readJson(settingsPath);
|
|
3807
3983
|
const baseKeys = new Set(Object.keys(base));
|
|
@@ -3827,8 +4003,8 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
3827
4003
|
|
|
3828
4004
|
// src/preview.ts
|
|
3829
4005
|
init_config();
|
|
3830
|
-
import { existsSync as
|
|
3831
|
-
import { join as
|
|
4006
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
4007
|
+
import { join as join37 } from "node:path";
|
|
3832
4008
|
|
|
3833
4009
|
// node_modules/diff/libesm/diff/base.js
|
|
3834
4010
|
var Diff = class {
|
|
@@ -4114,7 +4290,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
4114
4290
|
return lines.join("\n");
|
|
4115
4291
|
}
|
|
4116
4292
|
function readJsonOrNull(path) {
|
|
4117
|
-
if (!
|
|
4293
|
+
if (!existsSync32(path)) return null;
|
|
4118
4294
|
try {
|
|
4119
4295
|
return readJson(path);
|
|
4120
4296
|
} catch {
|
|
@@ -4128,12 +4304,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
4128
4304
|
}
|
|
4129
4305
|
const notes = [];
|
|
4130
4306
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
4131
|
-
if (hostOverrides === null &&
|
|
4307
|
+
if (hostOverrides === null && existsSync32(hostPath)) {
|
|
4132
4308
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
4133
4309
|
}
|
|
4134
4310
|
const merged = deepMerge(base, hostOverrides ?? {});
|
|
4135
4311
|
const current = readJsonOrNull(settingsPath);
|
|
4136
|
-
if (current === null &&
|
|
4312
|
+
if (current === null && existsSync32(settingsPath)) {
|
|
4137
4313
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
4138
4314
|
}
|
|
4139
4315
|
const rawEqual = JSON.stringify(current ?? {}, null, 2) === JSON.stringify(merged, null, 2);
|
|
@@ -4163,6 +4339,8 @@ function buildSettingsSectionForPreview(result) {
|
|
|
4163
4339
|
return s;
|
|
4164
4340
|
}
|
|
4165
4341
|
function computePreview(ts, map, verb = "pull") {
|
|
4342
|
+
const repo = repoHome();
|
|
4343
|
+
const claude = claudeHome();
|
|
4166
4344
|
console.log(`would pull on host=${HOST} (dry-run; no mutation)`);
|
|
4167
4345
|
console.log("");
|
|
4168
4346
|
const links = section("Symlinks");
|
|
@@ -4171,9 +4349,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
4171
4349
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
4172
4350
|
});
|
|
4173
4351
|
const settingsResult = previewSettings(
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4352
|
+
join37(repo, "shared", "settings.base.json"),
|
|
4353
|
+
join37(repo, "hosts", `${HOST}.json`),
|
|
4354
|
+
join37(claude, "settings.json")
|
|
4177
4355
|
);
|
|
4178
4356
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
4179
4357
|
const sessions = section("Sessions");
|
|
@@ -4189,21 +4367,21 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
4189
4367
|
|
|
4190
4368
|
// src/spinner.ts
|
|
4191
4369
|
init_color();
|
|
4192
|
-
import { existsSync as
|
|
4370
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
4193
4371
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4194
4372
|
import { Worker } from "node:worker_threads";
|
|
4195
4373
|
|
|
4196
4374
|
// src/commands.push.recovery.ts
|
|
4197
4375
|
init_config();
|
|
4198
|
-
import { readFileSync as
|
|
4199
|
-
import { join as
|
|
4376
|
+
import { readFileSync as readFileSync12, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4377
|
+
import { join as join40 } from "node:path";
|
|
4200
4378
|
import { createInterface } from "node:readline/promises";
|
|
4201
4379
|
|
|
4202
4380
|
// src/commands.push.recovery.redact.ts
|
|
4203
4381
|
init_config();
|
|
4204
4382
|
init_config_sharedDirs_guard();
|
|
4205
|
-
import { cpSync as cpSync6, existsSync as
|
|
4206
|
-
import { dirname as dirname6, join as
|
|
4383
|
+
import { cpSync as cpSync6, existsSync as existsSync33, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
|
|
4384
|
+
import { dirname as dirname6, join as join38, sep as sep3 } from "node:path";
|
|
4207
4385
|
init_push_gitleaks_scan();
|
|
4208
4386
|
init_utils_json();
|
|
4209
4387
|
init_utils();
|
|
@@ -4229,13 +4407,13 @@ function parseAction(raw) {
|
|
|
4229
4407
|
}
|
|
4230
4408
|
|
|
4231
4409
|
// src/commands.push.recovery.redact.ts
|
|
4232
|
-
function resolveStagedDir(localPath, map) {
|
|
4410
|
+
function resolveStagedDir(localPath, map, claude, repo) {
|
|
4233
4411
|
for (const [logical, hostMap] of Object.entries(map.projects)) {
|
|
4234
4412
|
assertSafeLogical(logical);
|
|
4235
4413
|
const abs = hostMap[HOST];
|
|
4236
4414
|
if (abs === void 0) continue;
|
|
4237
|
-
if (localPath.startsWith(
|
|
4238
|
-
return
|
|
4415
|
+
if (localPath.startsWith(join38(claude, "projects", encodePath(abs)) + sep3)) {
|
|
4416
|
+
return join38(repo, "shared", "projects", logical);
|
|
4239
4417
|
}
|
|
4240
4418
|
}
|
|
4241
4419
|
return null;
|
|
@@ -4245,6 +4423,8 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4245
4423
|
log(msg);
|
|
4246
4424
|
return false;
|
|
4247
4425
|
};
|
|
4426
|
+
const claude = claudeHome();
|
|
4427
|
+
const repo = repoHome();
|
|
4248
4428
|
const sid = sessionIdFromFinding(f);
|
|
4249
4429
|
if (sid === null) {
|
|
4250
4430
|
return refuse(
|
|
@@ -4257,7 +4437,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4257
4437
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4258
4438
|
);
|
|
4259
4439
|
}
|
|
4260
|
-
const sessionDir =
|
|
4440
|
+
const sessionDir = join38(dirname6(localPath), sid);
|
|
4261
4441
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4262
4442
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4263
4443
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4266,7 +4446,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4266
4446
|
End the session and choose Redact again, or choose Drop session (holds this session back from the push, local copy kept) or Skip.`
|
|
4267
4447
|
);
|
|
4268
4448
|
}
|
|
4269
|
-
const stagedProjectDir = resolveStagedDir(localPath, map);
|
|
4449
|
+
const stagedProjectDir = resolveStagedDir(localPath, map, claude, repo);
|
|
4270
4450
|
if (stagedProjectDir === null) {
|
|
4271
4451
|
return refuse(
|
|
4272
4452
|
`could not map the local transcript for session ${sid} to a staged copy; choose Drop session or Skip.`
|
|
@@ -4291,9 +4471,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4291
4471
|
);
|
|
4292
4472
|
}
|
|
4293
4473
|
mkdirSync7(stagedProjectDir, { recursive: true });
|
|
4294
|
-
cpSync6(localPath,
|
|
4295
|
-
if (
|
|
4296
|
-
cpSync6(sessionDir,
|
|
4474
|
+
cpSync6(localPath, join38(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
4475
|
+
if (existsSync33(sessionDir)) {
|
|
4476
|
+
cpSync6(sessionDir, join38(stagedProjectDir, sid), { force: true, recursive: true });
|
|
4297
4477
|
}
|
|
4298
4478
|
return true;
|
|
4299
4479
|
}
|
|
@@ -4301,13 +4481,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4301
4481
|
// src/commands.push.recovery.drop.ts
|
|
4302
4482
|
init_config();
|
|
4303
4483
|
import { rmSync as rmSync10 } from "node:fs";
|
|
4304
|
-
import { join as
|
|
4484
|
+
import { join as join39 } from "node:path";
|
|
4305
4485
|
function dropSessionFromStaged(sid, map) {
|
|
4306
4486
|
const logicals = Object.keys(map.projects);
|
|
4307
4487
|
if (logicals.length === 0) return false;
|
|
4488
|
+
const repo = repoHome();
|
|
4308
4489
|
for (const logical of logicals) {
|
|
4309
|
-
const jsonl =
|
|
4310
|
-
const dir =
|
|
4490
|
+
const jsonl = join39(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
4491
|
+
const dir = join39(repo, "shared", "projects", logical, sid);
|
|
4311
4492
|
rmSync10(jsonl, { force: true });
|
|
4312
4493
|
rmSync10(dir, { recursive: true, force: true });
|
|
4313
4494
|
}
|
|
@@ -4317,19 +4498,19 @@ function dropSessionFromStaged(sid, map) {
|
|
|
4317
4498
|
// src/commands.push.recovery.actions.ts
|
|
4318
4499
|
init_push_gitleaks_scan();
|
|
4319
4500
|
init_utils();
|
|
4320
|
-
function applyAllow(f) {
|
|
4321
|
-
appendGitleaksIgnore(f.Fingerprint);
|
|
4501
|
+
function applyAllow(f, repo) {
|
|
4502
|
+
appendGitleaksIgnore(f.Fingerprint, repo);
|
|
4322
4503
|
}
|
|
4323
|
-
function allowAllFindings(findings) {
|
|
4504
|
+
function allowAllFindings(findings, repo) {
|
|
4324
4505
|
for (const f of findings) {
|
|
4325
|
-
appendGitleaksIgnore(f.Fingerprint);
|
|
4506
|
+
appendGitleaksIgnore(f.Fingerprint, repo);
|
|
4326
4507
|
}
|
|
4327
4508
|
}
|
|
4328
|
-
function allowFindingsByRule(findings, ruleId) {
|
|
4509
|
+
function allowFindingsByRule(findings, ruleId, repo) {
|
|
4329
4510
|
let count = 0;
|
|
4330
4511
|
for (const f of findings) {
|
|
4331
4512
|
if (f.RuleID === ruleId) {
|
|
4332
|
-
appendGitleaksIgnore(f.Fingerprint);
|
|
4513
|
+
appendGitleaksIgnore(f.Fingerprint, repo);
|
|
4333
4514
|
count++;
|
|
4334
4515
|
}
|
|
4335
4516
|
}
|
|
@@ -4351,7 +4532,7 @@ function dispatchOne(f, ctx) {
|
|
|
4351
4532
|
const sid = sessionIdFromFinding(f);
|
|
4352
4533
|
if (sid !== null && ctx.droppedSids.has(sid)) return;
|
|
4353
4534
|
if (action === "allow") {
|
|
4354
|
-
applyAllow(f);
|
|
4535
|
+
applyAllow(f, ctx.repo);
|
|
4355
4536
|
return;
|
|
4356
4537
|
}
|
|
4357
4538
|
if (sid === null) return;
|
|
@@ -4368,12 +4549,14 @@ function dispatchOne(f, ctx) {
|
|
|
4368
4549
|
if (applyRedact(f, ctx.ts, ctx.map, ctx.nowMs, ctx.scan)) ctx.redactedSids.add(sid);
|
|
4369
4550
|
}
|
|
4370
4551
|
}
|
|
4371
|
-
function dispatchActions(findings, actions,
|
|
4552
|
+
function dispatchActions(findings, actions, opts) {
|
|
4553
|
+
const { ts, map, nowMs, repo, scan = scanFile, drop = dropSessionFromStaged } = opts;
|
|
4372
4554
|
const ctx = {
|
|
4373
4555
|
actions,
|
|
4374
4556
|
ts,
|
|
4375
4557
|
map,
|
|
4376
4558
|
nowMs,
|
|
4559
|
+
repo,
|
|
4377
4560
|
scan,
|
|
4378
4561
|
drop,
|
|
4379
4562
|
redactedSids: /* @__PURE__ */ new Set(),
|
|
@@ -4415,26 +4598,26 @@ function printRecoveryLegend(print = console.log) {
|
|
|
4415
4598
|
print(" Skip - leave unresolved (the push aborts)");
|
|
4416
4599
|
print("");
|
|
4417
4600
|
}
|
|
4418
|
-
function applyThenRescan(scanVerdict,
|
|
4419
|
-
gitOrFatal(["add", "-A"], "git add",
|
|
4420
|
-
const next = scanVerdict();
|
|
4601
|
+
function applyThenRescan(scanVerdict, repoHome2) {
|
|
4602
|
+
gitOrFatal(["add", "-A"], "git add", repoHome2);
|
|
4603
|
+
const next = scanVerdict(repoHome2);
|
|
4421
4604
|
if (next.leak) {
|
|
4422
4605
|
const { bySession, other } = partitionFindings(next.findings);
|
|
4423
4606
|
throw new NomadFatal(buildSessionAwareFatal(bySession, other));
|
|
4424
4607
|
}
|
|
4425
4608
|
return next;
|
|
4426
4609
|
}
|
|
4427
|
-
function allowThenRescan(append, scanVerdict,
|
|
4428
|
-
const ignPath =
|
|
4610
|
+
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
4611
|
+
const ignPath = join40(repoHome2, ".gitleaksignore");
|
|
4429
4612
|
let before;
|
|
4430
4613
|
try {
|
|
4431
|
-
before =
|
|
4614
|
+
before = readFileSync12(ignPath, "utf8");
|
|
4432
4615
|
} catch {
|
|
4433
4616
|
before = null;
|
|
4434
4617
|
}
|
|
4435
4618
|
append();
|
|
4436
4619
|
try {
|
|
4437
|
-
return applyThenRescan(scanVerdict,
|
|
4620
|
+
return applyThenRescan(scanVerdict, repoHome2);
|
|
4438
4621
|
} catch (err) {
|
|
4439
4622
|
if (before === null) rmSync11(ignPath, { force: true });
|
|
4440
4623
|
else writeFileSync5(ignPath, before, "utf8");
|
|
@@ -4467,22 +4650,23 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
|
|
|
4467
4650
|
printLegend = printRecoveryLegend
|
|
4468
4651
|
} = deps;
|
|
4469
4652
|
const scanVerdict = deps.scanVerdict ?? (await Promise.resolve().then(() => (init_push_leak_verdict(), push_leak_verdict_exports))).scanPushVerdict;
|
|
4653
|
+
const repo = repoHome();
|
|
4470
4654
|
let current = verdict;
|
|
4471
4655
|
if (redactAll) {
|
|
4472
4656
|
redactAllFindings(current.findings, ts, map, nowMs, scan);
|
|
4473
|
-
return applyThenRescan(scanVerdict,
|
|
4657
|
+
return applyThenRescan(scanVerdict, repo);
|
|
4474
4658
|
}
|
|
4475
4659
|
if (allowAll) {
|
|
4476
|
-
return allowThenRescan(() => allowAllFindings(current.findings), scanVerdict,
|
|
4660
|
+
return allowThenRescan(() => allowAllFindings(current.findings, repo), scanVerdict, repo);
|
|
4477
4661
|
}
|
|
4478
4662
|
if (allowRule !== void 0) {
|
|
4479
4663
|
return allowThenRescan(
|
|
4480
4664
|
() => {
|
|
4481
|
-
const matched = allowFindingsByRule(current.findings, allowRule);
|
|
4665
|
+
const matched = allowFindingsByRule(current.findings, allowRule, repo);
|
|
4482
4666
|
if (matched === 0) log(`no findings matched rule ${allowRule}; re-scanning`);
|
|
4483
4667
|
},
|
|
4484
4668
|
scanVerdict,
|
|
4485
|
-
|
|
4669
|
+
repo
|
|
4486
4670
|
);
|
|
4487
4671
|
}
|
|
4488
4672
|
if (!isTTYCheck()) {
|
|
@@ -4497,9 +4681,9 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
|
|
|
4497
4681
|
const { bySession, other } = partitionFindings(unresolved);
|
|
4498
4682
|
throw new NomadFatal(buildSessionAwareFatal(bySession, other));
|
|
4499
4683
|
}
|
|
4500
|
-
dispatchActions(current.findings, actions, ts, map, nowMs, scan);
|
|
4501
|
-
gitOrFatal(["add", "-A"], "git add",
|
|
4502
|
-
current = scanVerdict();
|
|
4684
|
+
dispatchActions(current.findings, actions, { ts, map, nowMs, repo, scan });
|
|
4685
|
+
gitOrFatal(["add", "-A"], "git add", repo);
|
|
4686
|
+
current = scanVerdict(repo);
|
|
4503
4687
|
}
|
|
4504
4688
|
return current;
|
|
4505
4689
|
}
|
|
@@ -4523,7 +4707,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
4523
4707
|
`);
|
|
4524
4708
|
}
|
|
4525
4709
|
function resolveWorkerPath(deps = {}) {
|
|
4526
|
-
const check = deps.existsSyncFn ??
|
|
4710
|
+
const check = deps.existsSyncFn ?? existsSync34;
|
|
4527
4711
|
const base = deps.baseUrl ?? import.meta.url;
|
|
4528
4712
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
4529
4713
|
if (check(mjs)) return mjs;
|
|
@@ -4729,17 +4913,19 @@ function handleWedge(repo, forceRemote) {
|
|
|
4729
4913
|
function cmdPull(opts = {}) {
|
|
4730
4914
|
const dryRun = opts.dryRun === true;
|
|
4731
4915
|
const forceRemote = opts.forceRemote === true;
|
|
4732
|
-
|
|
4733
|
-
|
|
4916
|
+
const repo = repoHome();
|
|
4917
|
+
const backup = backupBase();
|
|
4918
|
+
if (!existsSync35(repo)) die(`repo not cloned at ${repo}`);
|
|
4919
|
+
if (!existsSync35(join41(repo, "shared", "settings.base.json"))) {
|
|
4734
4920
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
4735
4921
|
}
|
|
4736
4922
|
const handle = acquireLock("pull");
|
|
4737
4923
|
if (handle === null) process.exit(0);
|
|
4738
4924
|
try {
|
|
4739
|
-
const ts = freshBackupTs(
|
|
4740
|
-
handleWedge(
|
|
4925
|
+
const ts = freshBackupTs(backup);
|
|
4926
|
+
handleWedge(repo, forceRemote);
|
|
4741
4927
|
if (!dryRun) {
|
|
4742
|
-
const backupRoot =
|
|
4928
|
+
const backupRoot = join41(backup, ts);
|
|
4743
4929
|
try {
|
|
4744
4930
|
mkdirSync8(backupRoot, { recursive: true });
|
|
4745
4931
|
} catch (err) {
|
|
@@ -4749,9 +4935,9 @@ function cmdPull(opts = {}) {
|
|
|
4749
4935
|
log(
|
|
4750
4936
|
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
4751
4937
|
);
|
|
4752
|
-
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase",
|
|
4753
|
-
const mapPath =
|
|
4754
|
-
const map =
|
|
4938
|
+
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
4939
|
+
const mapPath = join41(repo, "path-map.json");
|
|
4940
|
+
const map = existsSync35(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
4755
4941
|
divergenceCheckExtras(ts);
|
|
4756
4942
|
if (dryRun) {
|
|
4757
4943
|
computePreview(ts, map, "pull");
|
|
@@ -4773,8 +4959,8 @@ function cmdPull(opts = {}) {
|
|
|
4773
4959
|
|
|
4774
4960
|
// src/commands.push.ts
|
|
4775
4961
|
init_config();
|
|
4776
|
-
import { existsSync as
|
|
4777
|
-
import { join as
|
|
4962
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
4963
|
+
import { join as join43, relative as relative5 } from "node:path";
|
|
4778
4964
|
|
|
4779
4965
|
// src/commands.push.allowlist.ts
|
|
4780
4966
|
init_config();
|
|
@@ -4854,9 +5040,9 @@ init_color();
|
|
|
4854
5040
|
init_config();
|
|
4855
5041
|
init_config_sharedDirs_guard();
|
|
4856
5042
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
4857
|
-
import { copyFileSync, existsSync as
|
|
5043
|
+
import { copyFileSync, existsSync as existsSync36, mkdirSync as mkdirSync9, readdirSync as readdirSync11, rmSync as rmSync12 } from "node:fs";
|
|
4858
5044
|
import { homedir as homedir5 } from "node:os";
|
|
4859
|
-
import { join as
|
|
5045
|
+
import { join as join42 } from "node:path";
|
|
4860
5046
|
init_push_leak_verdict();
|
|
4861
5047
|
init_push_gitleaks();
|
|
4862
5048
|
init_utils_fs();
|
|
@@ -4871,13 +5057,13 @@ function stageSessions(tmpRoot, map) {
|
|
|
4871
5057
|
if (!p || p === "TBD") continue;
|
|
4872
5058
|
reverse.set(encodePath(p), logical);
|
|
4873
5059
|
}
|
|
4874
|
-
const localProjects =
|
|
4875
|
-
if (!
|
|
5060
|
+
const localProjects = join42(claudeHome(), "projects");
|
|
5061
|
+
if (!existsSync36(localProjects)) return 0;
|
|
4876
5062
|
let staged = 0;
|
|
4877
5063
|
for (const dir of readdirSync11(localProjects)) {
|
|
4878
5064
|
const logical = reverse.get(dir);
|
|
4879
5065
|
if (!logical) continue;
|
|
4880
|
-
copyDirJsonlOnly(
|
|
5066
|
+
copyDirJsonlOnly(join42(localProjects, dir), join42(tmpRoot, "shared", "projects", logical));
|
|
4881
5067
|
staged++;
|
|
4882
5068
|
}
|
|
4883
5069
|
return staged;
|
|
@@ -4893,9 +5079,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
4893
5079
|
if (!localRoot || localRoot === "TBD") continue;
|
|
4894
5080
|
for (const dirname7 of dirnames) {
|
|
4895
5081
|
if (!whitelist.includes(dirname7)) continue;
|
|
4896
|
-
const src =
|
|
4897
|
-
if (!
|
|
4898
|
-
const dst =
|
|
5082
|
+
const src = join42(localRoot, dirname7);
|
|
5083
|
+
if (!existsSync36(src)) continue;
|
|
5084
|
+
const dst = join42(tmpRoot, "shared", "extras", logical, dirname7);
|
|
4899
5085
|
copyExtras(src, dst);
|
|
4900
5086
|
staged++;
|
|
4901
5087
|
}
|
|
@@ -4903,19 +5089,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
4903
5089
|
return staged;
|
|
4904
5090
|
}
|
|
4905
5091
|
function previewPushLeaks(map) {
|
|
4906
|
-
const cacheDir =
|
|
5092
|
+
const cacheDir = join42(homedir5(), ".cache", "claude-nomad");
|
|
4907
5093
|
mkdirSync9(cacheDir, { recursive: true });
|
|
4908
5094
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
4909
|
-
const tmpRoot =
|
|
5095
|
+
const tmpRoot = join42(cacheDir, `push-preview-tree-${stamp}`);
|
|
4910
5096
|
try {
|
|
4911
5097
|
const sessionCount = stageSessions(tmpRoot, map);
|
|
4912
5098
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
4913
5099
|
if (sessionCount + extrasCount === 0) {
|
|
4914
5100
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
4915
5101
|
}
|
|
4916
|
-
const ignoreFile =
|
|
4917
|
-
if (
|
|
4918
|
-
copyFileSync(ignoreFile,
|
|
5102
|
+
const ignoreFile = join42(repoHome(), ".gitleaksignore");
|
|
5103
|
+
if (existsSync36(ignoreFile)) {
|
|
5104
|
+
copyFileSync(ignoreFile, join42(tmpRoot, ".gitleaksignore"));
|
|
4919
5105
|
}
|
|
4920
5106
|
let findings;
|
|
4921
5107
|
try {
|
|
@@ -4936,11 +5122,11 @@ function previewPushLeaks(map) {
|
|
|
4936
5122
|
init_utils();
|
|
4937
5123
|
init_utils_fs();
|
|
4938
5124
|
init_utils_json();
|
|
4939
|
-
function guardGitlinks() {
|
|
4940
|
-
const gitlinks = findGitlinks(
|
|
5125
|
+
function guardGitlinks(repo) {
|
|
5126
|
+
const gitlinks = findGitlinks(join43(repo, "shared"));
|
|
4941
5127
|
if (gitlinks.length === 0) return;
|
|
4942
5128
|
for (const p of gitlinks) {
|
|
4943
|
-
const rel = relative5(
|
|
5129
|
+
const rel = relative5(repo, p);
|
|
4944
5130
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
4945
5131
|
}
|
|
4946
5132
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -4948,15 +5134,15 @@ function guardGitlinks() {
|
|
|
4948
5134
|
`gitlink trap: ${gitlinks.length} nested .git ${noun} in shared/; remove before retry`
|
|
4949
5135
|
);
|
|
4950
5136
|
}
|
|
4951
|
-
async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule) {
|
|
4952
|
-
gitOrFatal(["add", "-A"], "git add",
|
|
4953
|
-
let verdict = withSpinner("Scanning for secrets", scanPushVerdict);
|
|
5137
|
+
async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo) {
|
|
5138
|
+
gitOrFatal(["add", "-A"], "git add", repo);
|
|
5139
|
+
let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
|
|
4954
5140
|
if (verdict.leak) {
|
|
4955
5141
|
renderPushTree(st, verdict);
|
|
4956
5142
|
verdict = await resolveLeakFindings(verdict, ts, map, { redactAll, allowAll, allowRule });
|
|
4957
5143
|
}
|
|
4958
|
-
gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit",
|
|
4959
|
-
withSpinner("Pushing", () => gitOrFatal(["push"], "git push",
|
|
5144
|
+
gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit", repo);
|
|
5145
|
+
withSpinner("Pushing", () => gitOrFatal(["push"], "git push", repo));
|
|
4960
5146
|
renderPushTree(st, verdict);
|
|
4961
5147
|
}
|
|
4962
5148
|
function runDryRunPreview(st, map) {
|
|
@@ -4989,33 +5175,35 @@ async function cmdPush(opts = {}) {
|
|
|
4989
5175
|
const allowAll = opts.allowAll === true;
|
|
4990
5176
|
const allowRule = opts.allowRule;
|
|
4991
5177
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
4992
|
-
|
|
5178
|
+
const repo = repoHome();
|
|
5179
|
+
const backup = backupBase();
|
|
5180
|
+
if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
|
|
4993
5181
|
const handle = acquireLock("push");
|
|
4994
5182
|
if (handle === null) process.exit(0);
|
|
4995
5183
|
try {
|
|
4996
5184
|
console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
|
|
4997
5185
|
probeGitleaks();
|
|
4998
|
-
withSpinner("Rebasing onto origin", rebaseBeforePush);
|
|
4999
|
-
const ts = freshBackupTs(
|
|
5186
|
+
withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
|
|
5187
|
+
const ts = freshBackupTs(backup);
|
|
5000
5188
|
const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun }));
|
|
5001
5189
|
const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
|
|
5002
5190
|
const st = { dryRun, remap, extras };
|
|
5003
|
-
guardGitlinks();
|
|
5004
|
-
const status = gitStatusPorcelainZ(
|
|
5191
|
+
guardGitlinks(repo);
|
|
5192
|
+
const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
|
|
5005
5193
|
if (!dryRun && !status) {
|
|
5006
5194
|
log("nothing to commit");
|
|
5007
5195
|
renderNoScanTree(st);
|
|
5008
5196
|
return;
|
|
5009
5197
|
}
|
|
5010
|
-
const mapPath =
|
|
5011
|
-
if (!
|
|
5198
|
+
const mapPath = join43(repo, "path-map.json");
|
|
5199
|
+
if (!existsSync37(mapPath)) {
|
|
5012
5200
|
if (dryRun) return runDryRunPreview(st, null);
|
|
5013
5201
|
die("path-map.json missing, cannot enforce push allow-list");
|
|
5014
5202
|
}
|
|
5015
5203
|
const map = readPathMap(mapPath);
|
|
5016
5204
|
if (status) enforceAllowList(status, map);
|
|
5017
5205
|
if (dryRun) return runDryRunPreview(st, map);
|
|
5018
|
-
await commitAndPush(st, ts, map, redactAll, allowAll, allowRule);
|
|
5206
|
+
await commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo);
|
|
5019
5207
|
} catch (err) {
|
|
5020
5208
|
if (err instanceof NomadFatal) {
|
|
5021
5209
|
fail(err.message);
|
|
@@ -5048,17 +5236,18 @@ init_config();
|
|
|
5048
5236
|
|
|
5049
5237
|
// src/diff.ts
|
|
5050
5238
|
init_config();
|
|
5051
|
-
import { existsSync as
|
|
5052
|
-
import { join as
|
|
5239
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
5240
|
+
import { join as join44 } from "node:path";
|
|
5053
5241
|
init_utils();
|
|
5054
5242
|
init_utils_fs();
|
|
5055
5243
|
init_utils_json();
|
|
5056
5244
|
function cmdDiff() {
|
|
5057
5245
|
try {
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
const
|
|
5061
|
-
const
|
|
5246
|
+
const repo = repoHome();
|
|
5247
|
+
if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
|
|
5248
|
+
const ts = freshBackupTs(backupBase());
|
|
5249
|
+
const mapPath = join44(repo, "path-map.json");
|
|
5250
|
+
const map = existsSync38(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
5062
5251
|
computePreview(ts, map, "diff");
|
|
5063
5252
|
} catch (err) {
|
|
5064
5253
|
if (err instanceof NomadFatal) {
|
|
@@ -5072,8 +5261,8 @@ function cmdDiff() {
|
|
|
5072
5261
|
|
|
5073
5262
|
// src/init.ts
|
|
5074
5263
|
init_config();
|
|
5075
|
-
import { existsSync as
|
|
5076
|
-
import { join as
|
|
5264
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "node:fs";
|
|
5265
|
+
import { join as join46 } from "node:path";
|
|
5077
5266
|
|
|
5078
5267
|
// src/init.gh-onboard.ts
|
|
5079
5268
|
init_config();
|
|
@@ -5090,8 +5279,9 @@ function ensureOriginRepo(repoName, run = execFileSync16) {
|
|
|
5090
5279
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
5091
5280
|
);
|
|
5092
5281
|
}
|
|
5282
|
+
const repo = repoHome();
|
|
5093
5283
|
try {
|
|
5094
|
-
readOriginRemote(
|
|
5284
|
+
readOriginRemote(repo, run);
|
|
5095
5285
|
return;
|
|
5096
5286
|
} catch {
|
|
5097
5287
|
}
|
|
@@ -5106,7 +5296,7 @@ function ensureOriginRepo(repoName, run = execFileSync16) {
|
|
|
5106
5296
|
die("gh CLI is not authenticated. Run `gh auth login` and retry.");
|
|
5107
5297
|
}
|
|
5108
5298
|
try {
|
|
5109
|
-
run("git", ["init", "-b", "main"], { cwd:
|
|
5299
|
+
run("git", ["init", "-b", "main"], { cwd: repo, stdio: ["ignore", "ignore", "pipe"] });
|
|
5110
5300
|
} catch (err) {
|
|
5111
5301
|
const e = err;
|
|
5112
5302
|
throw new NomadFatal(`git init failed: ${e.message}`);
|
|
@@ -5139,7 +5329,7 @@ function ensureOriginRepo(repoName, run = execFileSync16) {
|
|
|
5139
5329
|
}
|
|
5140
5330
|
try {
|
|
5141
5331
|
run("git", ["remote", "add", "origin", `git@github.com:${owner}/${repoName}.git`], {
|
|
5142
|
-
cwd:
|
|
5332
|
+
cwd: repo,
|
|
5143
5333
|
stdio: ["ignore", "ignore", "pipe"]
|
|
5144
5334
|
});
|
|
5145
5335
|
} catch (err) {
|
|
@@ -5154,31 +5344,33 @@ init_config();
|
|
|
5154
5344
|
init_utils();
|
|
5155
5345
|
init_utils_fs();
|
|
5156
5346
|
init_utils_json();
|
|
5157
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as
|
|
5158
|
-
import { join as
|
|
5347
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync39, rmSync as rmSync13, statSync as statSync9 } from "node:fs";
|
|
5348
|
+
import { join as join45 } from "node:path";
|
|
5159
5349
|
function snapshotIntoShared(map) {
|
|
5350
|
+
const repo = repoHome();
|
|
5351
|
+
const claude = claudeHome();
|
|
5160
5352
|
for (const name of allSharedLinks(map)) {
|
|
5161
|
-
const src =
|
|
5162
|
-
if (!
|
|
5163
|
-
const dst =
|
|
5353
|
+
const src = join45(claude, name);
|
|
5354
|
+
if (!existsSync39(src)) continue;
|
|
5355
|
+
const dst = join45(repo, "shared", name);
|
|
5164
5356
|
if (statSync9(src).isDirectory()) {
|
|
5165
|
-
const gk =
|
|
5166
|
-
if (
|
|
5357
|
+
const gk = join45(dst, ".gitkeep");
|
|
5358
|
+
if (existsSync39(gk)) rmSync13(gk);
|
|
5167
5359
|
cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
5168
5360
|
} else {
|
|
5169
5361
|
copyFileSync2(src, dst);
|
|
5170
5362
|
}
|
|
5171
5363
|
log(`snapshotted shared/${name} from ${src}`);
|
|
5172
5364
|
}
|
|
5173
|
-
const userSettings =
|
|
5174
|
-
if (
|
|
5365
|
+
const userSettings = join45(claude, "settings.json");
|
|
5366
|
+
if (existsSync39(userSettings)) {
|
|
5175
5367
|
let parsed;
|
|
5176
5368
|
try {
|
|
5177
5369
|
parsed = readJson(userSettings);
|
|
5178
5370
|
} catch (err) {
|
|
5179
5371
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
5180
5372
|
}
|
|
5181
|
-
const hostFile =
|
|
5373
|
+
const hostFile = join45(repo, "hosts", `${HOST}.json`);
|
|
5182
5374
|
writeJsonAtomic(hostFile, parsed);
|
|
5183
5375
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
5184
5376
|
}
|
|
@@ -5189,62 +5381,64 @@ init_utils();
|
|
|
5189
5381
|
init_utils_fs();
|
|
5190
5382
|
var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.claude/CLAUDE.md by nomad pull -->\n";
|
|
5191
5383
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
5192
|
-
function preflightConflict(
|
|
5384
|
+
function preflightConflict(repoHome2) {
|
|
5193
5385
|
const candidates = [
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5386
|
+
join46(repoHome2, "shared", "settings.base.json"),
|
|
5387
|
+
join46(repoHome2, "shared", "CLAUDE.md"),
|
|
5388
|
+
join46(repoHome2, "path-map.json"),
|
|
5389
|
+
join46(repoHome2, "hosts"),
|
|
5390
|
+
join46(repoHome2, "shared")
|
|
5199
5391
|
];
|
|
5200
5392
|
for (const c of candidates) {
|
|
5201
|
-
if (
|
|
5393
|
+
if (existsSync40(c)) return c;
|
|
5202
5394
|
}
|
|
5203
5395
|
return null;
|
|
5204
5396
|
}
|
|
5205
5397
|
function cmdInit(opts = {}) {
|
|
5206
5398
|
const snapshot = opts.snapshot === true;
|
|
5207
5399
|
const keepActions = opts.keepActions === true;
|
|
5208
|
-
|
|
5209
|
-
const
|
|
5400
|
+
const repo = repoHome();
|
|
5401
|
+
const claude = claudeHome();
|
|
5402
|
+
mkdirSync10(repo, { recursive: true });
|
|
5403
|
+
const conflict = preflightConflict(repo);
|
|
5210
5404
|
if (conflict !== null) {
|
|
5211
5405
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
5212
5406
|
}
|
|
5213
5407
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
5214
|
-
mkdirSync10(
|
|
5215
|
-
mkdirSync10(
|
|
5408
|
+
mkdirSync10(join46(repo, "shared"), { recursive: true });
|
|
5409
|
+
mkdirSync10(join46(repo, "hosts"), { recursive: true });
|
|
5216
5410
|
for (const name of SHARED_KEEP_DIRS) {
|
|
5217
|
-
mkdirSync10(
|
|
5411
|
+
mkdirSync10(join46(repo, "shared", name), { recursive: true });
|
|
5218
5412
|
}
|
|
5219
|
-
const userClaudeMd =
|
|
5220
|
-
if (!snapshot || !
|
|
5221
|
-
writeFileSync6(
|
|
5222
|
-
|
|
5413
|
+
const userClaudeMd = join46(claude, "CLAUDE.md");
|
|
5414
|
+
if (!snapshot || !existsSync40(userClaudeMd)) {
|
|
5415
|
+
writeFileSync6(join46(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
5416
|
+
item("created shared/CLAUDE.md");
|
|
5223
5417
|
}
|
|
5224
5418
|
for (const name of SHARED_KEEP_DIRS) {
|
|
5225
|
-
writeFileSync6(
|
|
5226
|
-
|
|
5227
|
-
}
|
|
5228
|
-
writeFileSync6(
|
|
5229
|
-
|
|
5230
|
-
writeJsonAtomic(
|
|
5231
|
-
|
|
5232
|
-
writeJsonAtomic(
|
|
5233
|
-
|
|
5419
|
+
writeFileSync6(join46(repo, "shared", name, ".gitkeep"), "");
|
|
5420
|
+
item(`created shared/${name}/.gitkeep`);
|
|
5421
|
+
}
|
|
5422
|
+
writeFileSync6(join46(repo, "hosts", ".gitkeep"), "");
|
|
5423
|
+
item("created hosts/.gitkeep");
|
|
5424
|
+
writeJsonAtomic(join46(repo, "shared", "settings.base.json"), {});
|
|
5425
|
+
item("created shared/settings.base.json");
|
|
5426
|
+
writeJsonAtomic(join46(repo, "path-map.json"), { projects: {} });
|
|
5427
|
+
item("created path-map.json");
|
|
5234
5428
|
if (snapshot) {
|
|
5235
5429
|
snapshotIntoShared({ projects: {} });
|
|
5236
5430
|
log(`snapshot staged in shared/; review, then 'nomad push' to share with other hosts.`);
|
|
5237
5431
|
log("~/.claude/ originals were NOT removed.");
|
|
5238
5432
|
}
|
|
5239
5433
|
if (!keepActions) {
|
|
5240
|
-
maybeDisableRepoActions(
|
|
5434
|
+
maybeDisableRepoActions(repo, opts.run);
|
|
5241
5435
|
}
|
|
5242
5436
|
log("init complete");
|
|
5243
5437
|
}
|
|
5244
|
-
function maybeDisableRepoActions(
|
|
5438
|
+
function maybeDisableRepoActions(repoHome2, run) {
|
|
5245
5439
|
let remote;
|
|
5246
5440
|
try {
|
|
5247
|
-
remote = readOriginRemote(
|
|
5441
|
+
remote = readOriginRemote(repoHome2, run);
|
|
5248
5442
|
} catch {
|
|
5249
5443
|
return;
|
|
5250
5444
|
}
|
|
@@ -5530,7 +5724,7 @@ function parsePushArgs(argv) {
|
|
|
5530
5724
|
// package.json
|
|
5531
5725
|
var package_default = {
|
|
5532
5726
|
name: "claude-nomad",
|
|
5533
|
-
version: "0.
|
|
5727
|
+
version: "0.45.0",
|
|
5534
5728
|
type: "module",
|
|
5535
5729
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
5536
5730
|
keywords: [
|
|
@@ -5597,6 +5791,8 @@ var package_default = {
|
|
|
5597
5791
|
"@commitlint/cli": "^21.0.1",
|
|
5598
5792
|
"@commitlint/config-conventional": "^21.0.1",
|
|
5599
5793
|
"@eslint/js": "^10.0.1",
|
|
5794
|
+
"@stryker-mutator/core": "9.6.1",
|
|
5795
|
+
"@stryker-mutator/vitest-runner": "9.6.1",
|
|
5600
5796
|
"@types/node": "^22.0.0",
|
|
5601
5797
|
"@vitest/coverage-v8": "^4.1.6",
|
|
5602
5798
|
diff: "^9.0.0",
|
|
@@ -5730,15 +5926,15 @@ var DEFAULT_HELP = [
|
|
|
5730
5926
|
init_config();
|
|
5731
5927
|
init_utils();
|
|
5732
5928
|
init_utils_json();
|
|
5733
|
-
import { existsSync as
|
|
5734
|
-
import { join as
|
|
5929
|
+
import { existsSync as existsSync41, readFileSync as readFileSync13, readdirSync as readdirSync12 } from "node:fs";
|
|
5930
|
+
import { join as join47 } from "node:path";
|
|
5735
5931
|
function resumeCmd(sessionId) {
|
|
5736
5932
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
5737
5933
|
fail(`invalid session id: ${sessionId}`);
|
|
5738
5934
|
process.exit(1);
|
|
5739
5935
|
}
|
|
5740
|
-
const projectsRoot =
|
|
5741
|
-
if (!
|
|
5936
|
+
const projectsRoot = join47(claudeHome(), "projects");
|
|
5937
|
+
if (!existsSync41(projectsRoot)) {
|
|
5742
5938
|
fail(`${projectsRoot} does not exist`);
|
|
5743
5939
|
process.exit(1);
|
|
5744
5940
|
}
|
|
@@ -5752,8 +5948,8 @@ function resumeCmd(sessionId) {
|
|
|
5752
5948
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
5753
5949
|
process.exit(1);
|
|
5754
5950
|
}
|
|
5755
|
-
const mapPath =
|
|
5756
|
-
if (!
|
|
5951
|
+
const mapPath = join47(repoHome(), "path-map.json");
|
|
5952
|
+
if (!existsSync41(mapPath)) {
|
|
5757
5953
|
fail("path-map.json missing");
|
|
5758
5954
|
process.exit(1);
|
|
5759
5955
|
}
|
|
@@ -5776,13 +5972,13 @@ function resumeCmd(sessionId) {
|
|
|
5776
5972
|
}
|
|
5777
5973
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
5778
5974
|
for (const dir of readdirSync12(projectsRoot)) {
|
|
5779
|
-
const candidate =
|
|
5780
|
-
if (
|
|
5975
|
+
const candidate = join47(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
5976
|
+
if (existsSync41(candidate)) return candidate;
|
|
5781
5977
|
}
|
|
5782
5978
|
return null;
|
|
5783
5979
|
}
|
|
5784
5980
|
function extractRecordedCwd(jsonlPath) {
|
|
5785
|
-
for (const line of
|
|
5981
|
+
for (const line of readFileSync13(jsonlPath, "utf8").split("\n")) {
|
|
5786
5982
|
if (!line.trim()) continue;
|
|
5787
5983
|
try {
|
|
5788
5984
|
const obj = JSON.parse(line);
|
|
@@ -5832,7 +6028,8 @@ function shQuote(s) {
|
|
|
5832
6028
|
|
|
5833
6029
|
// src/nomad.ts
|
|
5834
6030
|
init_utils();
|
|
5835
|
-
|
|
6031
|
+
var h = home();
|
|
6032
|
+
if (!h) {
|
|
5836
6033
|
fail(
|
|
5837
6034
|
"could not determine home directory (HOME env unset and no uid mapping). Set HOME and retry."
|
|
5838
6035
|
);
|