claude-nomad 0.44.0 → 0.44.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/nomad.mjs +381 -318
- 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);
|
|
@@ -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,
|
|
@@ -2784,7 +2811,7 @@ function majorMinorOf(value) {
|
|
|
2784
2811
|
return m === null ? null : [m[1], m[2]];
|
|
2785
2812
|
}
|
|
2786
2813
|
function readGitleaksVersion(run, tomlExists) {
|
|
2787
|
-
const tomlPath = join25(
|
|
2814
|
+
const tomlPath = join25(repoHome(), ".gitleaks.toml");
|
|
2788
2815
|
const args = ["version"];
|
|
2789
2816
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
2790
2817
|
try {
|
|
@@ -2935,7 +2962,7 @@ function readOriginRemote(cwd, run = execFileSync9) {
|
|
|
2935
2962
|
function reportActionsDrift(section2, run = execFileSync10) {
|
|
2936
2963
|
let remote;
|
|
2937
2964
|
try {
|
|
2938
|
-
remote = readOriginRemote(
|
|
2965
|
+
remote = readOriginRemote(repoHome(), run);
|
|
2939
2966
|
} catch {
|
|
2940
2967
|
return;
|
|
2941
2968
|
}
|
|
@@ -2965,15 +2992,15 @@ function reportActionsDrift(section2, run = execFileSync10) {
|
|
|
2965
2992
|
|
|
2966
2993
|
// src/commands.doctor.verdict.ts
|
|
2967
2994
|
init_color();
|
|
2968
|
-
function isFailLine(
|
|
2969
|
-
return
|
|
2995
|
+
function isFailLine(item2) {
|
|
2996
|
+
return item2.includes(failGlyph);
|
|
2970
2997
|
}
|
|
2971
|
-
function isWarnLine(
|
|
2972
|
-
return !isFailLine(
|
|
2998
|
+
function isWarnLine(item2) {
|
|
2999
|
+
return !isFailLine(item2) && item2.includes(warnGlyph);
|
|
2973
3000
|
}
|
|
2974
3001
|
function buildVerdictSection(sections) {
|
|
2975
3002
|
const summary = section("Summary");
|
|
2976
|
-
const lines = sections.flatMap((s) => s.items).map((
|
|
3003
|
+
const lines = sections.flatMap((s) => s.items).map((item2) => item2.replace(/^\t/, ""));
|
|
2977
3004
|
const failures = lines.filter(isFailLine);
|
|
2978
3005
|
const warnings = lines.filter(isWarnLine);
|
|
2979
3006
|
for (const line of [...failures, ...warnings]) addItem(summary, line);
|
|
@@ -2996,7 +3023,7 @@ function cmdDoctor(opts = {}) {
|
|
|
2996
3023
|
reportHostAndPaths(host);
|
|
2997
3024
|
reportRepoState(host);
|
|
2998
3025
|
const links = section("Shared links");
|
|
2999
|
-
const mapPath = join26(
|
|
3026
|
+
const mapPath = join26(repoHome(), "path-map.json");
|
|
3000
3027
|
const rawMap = existsSync22(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
3001
3028
|
const map = rawMap ?? { projects: {} };
|
|
3002
3029
|
reportSharedLinks(links, map);
|
|
@@ -3055,12 +3082,11 @@ import { existsSync as existsSync24, readdirSync as readdirSync9, statSync as st
|
|
|
3055
3082
|
import { join as join29, relative as relative4 } from "node:path";
|
|
3056
3083
|
|
|
3057
3084
|
// src/commands.drop-session.git.ts
|
|
3058
|
-
init_config();
|
|
3059
3085
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
3060
|
-
function expandStagedDir(dirRel) {
|
|
3086
|
+
function expandStagedDir(dirRel, repo) {
|
|
3061
3087
|
try {
|
|
3062
3088
|
const out = execFileSync11("git", ["ls-files", "-z", "--", dirRel], {
|
|
3063
|
-
cwd:
|
|
3089
|
+
cwd: repo,
|
|
3064
3090
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3065
3091
|
});
|
|
3066
3092
|
return out.toString().split("\0").filter((p) => p !== "");
|
|
@@ -3068,10 +3094,10 @@ function expandStagedDir(dirRel) {
|
|
|
3068
3094
|
return [];
|
|
3069
3095
|
}
|
|
3070
3096
|
}
|
|
3071
|
-
function isTrackedInHead(rel) {
|
|
3097
|
+
function isTrackedInHead(rel, repo) {
|
|
3072
3098
|
try {
|
|
3073
3099
|
execFileSync11("git", ["cat-file", "-e", `HEAD:${rel}`], {
|
|
3074
|
-
cwd:
|
|
3100
|
+
cwd: repo,
|
|
3075
3101
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3076
3102
|
});
|
|
3077
3103
|
return true;
|
|
@@ -3079,10 +3105,10 @@ function isTrackedInHead(rel) {
|
|
|
3079
3105
|
return false;
|
|
3080
3106
|
}
|
|
3081
3107
|
}
|
|
3082
|
-
function isInIndex(rel) {
|
|
3108
|
+
function isInIndex(rel, repo) {
|
|
3083
3109
|
try {
|
|
3084
3110
|
const out = execFileSync11("git", ["ls-files", "--", rel], {
|
|
3085
|
-
cwd:
|
|
3111
|
+
cwd: repo,
|
|
3086
3112
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3087
3113
|
});
|
|
3088
3114
|
return out.toString().trim() !== "";
|
|
@@ -3112,15 +3138,16 @@ function reportScrubHint(id, matches) {
|
|
|
3112
3138
|
}
|
|
3113
3139
|
function resolveLiveTranscript(id, matches) {
|
|
3114
3140
|
try {
|
|
3115
|
-
const mapPath = join27(
|
|
3141
|
+
const mapPath = join27(repoHome(), "path-map.json");
|
|
3116
3142
|
if (!existsSync23(mapPath)) return null;
|
|
3117
3143
|
const projects = readJson(mapPath).projects;
|
|
3144
|
+
const claude = claudeHome();
|
|
3118
3145
|
for (const rel of matches) {
|
|
3119
3146
|
const logical = SHARED_PROJECT_LOGICAL.exec(rel)?.[1];
|
|
3120
3147
|
if (logical === void 0) continue;
|
|
3121
3148
|
const abs = projects[logical]?.[HOST];
|
|
3122
3149
|
if (abs === void 0) continue;
|
|
3123
|
-
const live = join27(
|
|
3150
|
+
const live = join27(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
3124
3151
|
if (existsSync23(live)) return live;
|
|
3125
3152
|
}
|
|
3126
3153
|
return null;
|
|
@@ -3137,11 +3164,14 @@ init_config();
|
|
|
3137
3164
|
init_utils();
|
|
3138
3165
|
import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3139
3166
|
import { dirname as dirname4, join as join28 } from "node:path";
|
|
3140
|
-
|
|
3167
|
+
function lockFilePath() {
|
|
3168
|
+
return join28(home(), ".cache", "claude-nomad", "nomad.lock");
|
|
3169
|
+
}
|
|
3141
3170
|
function acquireLock(verb) {
|
|
3142
|
-
|
|
3171
|
+
const lp = lockFilePath();
|
|
3172
|
+
mkdirSync5(dirname4(lp), { recursive: true });
|
|
3143
3173
|
try {
|
|
3144
|
-
const fd = openSync3(
|
|
3174
|
+
const fd = openSync3(lp, "wx");
|
|
3145
3175
|
try {
|
|
3146
3176
|
writeFileSync3(fd, String(process.pid));
|
|
3147
3177
|
} catch (writeErr) {
|
|
@@ -3150,55 +3180,56 @@ function acquireLock(verb) {
|
|
|
3150
3180
|
} catch {
|
|
3151
3181
|
}
|
|
3152
3182
|
try {
|
|
3153
|
-
unlinkSync(
|
|
3183
|
+
unlinkSync(lp);
|
|
3154
3184
|
} catch {
|
|
3155
3185
|
}
|
|
3156
3186
|
throw writeErr;
|
|
3157
3187
|
}
|
|
3158
|
-
return { fd };
|
|
3188
|
+
return { fd, path: lp };
|
|
3159
3189
|
} catch (err) {
|
|
3160
3190
|
const code = err.code;
|
|
3161
3191
|
if (code !== "EEXIST") throw err;
|
|
3162
|
-
return checkStaleAndRetry(verb);
|
|
3192
|
+
return checkStaleAndRetry(verb, lp);
|
|
3163
3193
|
}
|
|
3164
3194
|
}
|
|
3165
3195
|
function releaseLock(handle) {
|
|
3166
3196
|
if (handle === null) return;
|
|
3197
|
+
const lp = handle.path;
|
|
3167
3198
|
try {
|
|
3168
3199
|
closeSync3(handle.fd);
|
|
3169
3200
|
} catch {
|
|
3170
3201
|
}
|
|
3171
3202
|
try {
|
|
3172
|
-
unlinkSync(
|
|
3203
|
+
unlinkSync(lp);
|
|
3173
3204
|
} catch (err) {
|
|
3174
3205
|
if (err.code !== "ENOENT") throw err;
|
|
3175
3206
|
}
|
|
3176
3207
|
}
|
|
3177
|
-
function unlinkIfSamePid(expectedPidStr) {
|
|
3208
|
+
function unlinkIfSamePid(expectedPidStr, lp) {
|
|
3178
3209
|
let current;
|
|
3179
3210
|
try {
|
|
3180
|
-
current = readFileSync9(
|
|
3211
|
+
current = readFileSync9(lp, "utf8").trim();
|
|
3181
3212
|
} catch {
|
|
3182
3213
|
return false;
|
|
3183
3214
|
}
|
|
3184
3215
|
if (current !== expectedPidStr) return false;
|
|
3185
3216
|
try {
|
|
3186
|
-
unlinkSync(
|
|
3217
|
+
unlinkSync(lp);
|
|
3187
3218
|
return true;
|
|
3188
3219
|
} catch {
|
|
3189
3220
|
return false;
|
|
3190
3221
|
}
|
|
3191
3222
|
}
|
|
3192
|
-
function checkStaleAndRetry(verb) {
|
|
3223
|
+
function checkStaleAndRetry(verb, lp) {
|
|
3193
3224
|
let pidStr;
|
|
3194
3225
|
try {
|
|
3195
|
-
pidStr = readFileSync9(
|
|
3226
|
+
pidStr = readFileSync9(lp, "utf8").trim();
|
|
3196
3227
|
} catch {
|
|
3197
3228
|
pidStr = "";
|
|
3198
3229
|
}
|
|
3199
3230
|
const pid = Number.parseInt(pidStr, 10);
|
|
3200
3231
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
3201
|
-
if (unlinkIfSamePid(pidStr)) return retryOnce(verb);
|
|
3232
|
+
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
3202
3233
|
warn(`another nomad ${verb} running, skipping`);
|
|
3203
3234
|
return null;
|
|
3204
3235
|
}
|
|
@@ -3209,7 +3240,7 @@ function checkStaleAndRetry(verb) {
|
|
|
3209
3240
|
} catch (err) {
|
|
3210
3241
|
const code = err.code;
|
|
3211
3242
|
if (code === "ESRCH") {
|
|
3212
|
-
if (unlinkIfSamePid(pidStr)) return retryOnce(verb);
|
|
3243
|
+
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
3213
3244
|
warn(`another nomad ${verb} running, skipping`);
|
|
3214
3245
|
return null;
|
|
3215
3246
|
}
|
|
@@ -3217,9 +3248,9 @@ function checkStaleAndRetry(verb) {
|
|
|
3217
3248
|
return null;
|
|
3218
3249
|
}
|
|
3219
3250
|
}
|
|
3220
|
-
function retryOnce(verb) {
|
|
3251
|
+
function retryOnce(verb, lp) {
|
|
3221
3252
|
try {
|
|
3222
|
-
const fd = openSync3(
|
|
3253
|
+
const fd = openSync3(lp, "wx");
|
|
3223
3254
|
try {
|
|
3224
3255
|
writeFileSync3(fd, String(process.pid));
|
|
3225
3256
|
} catch {
|
|
@@ -3228,13 +3259,13 @@ function retryOnce(verb) {
|
|
|
3228
3259
|
} catch {
|
|
3229
3260
|
}
|
|
3230
3261
|
try {
|
|
3231
|
-
unlinkSync(
|
|
3262
|
+
unlinkSync(lp);
|
|
3232
3263
|
} catch {
|
|
3233
3264
|
}
|
|
3234
3265
|
warn(`another nomad ${verb} running, skipping`);
|
|
3235
3266
|
return null;
|
|
3236
3267
|
}
|
|
3237
|
-
return { fd };
|
|
3268
|
+
return { fd, path: lp };
|
|
3238
3269
|
} catch {
|
|
3239
3270
|
warn(`another nomad ${verb} running, skipping`);
|
|
3240
3271
|
return null;
|
|
@@ -3247,19 +3278,20 @@ function cmdDropSession(id) {
|
|
|
3247
3278
|
fail(`invalid session id: ${id}`);
|
|
3248
3279
|
process.exit(1);
|
|
3249
3280
|
}
|
|
3250
|
-
|
|
3281
|
+
const repo = repoHome();
|
|
3282
|
+
if (!existsSync24(repo)) die(`repo not cloned at ${repo}`);
|
|
3251
3283
|
const handle = acquireLock("drop-session");
|
|
3252
3284
|
if (handle === null) process.exit(0);
|
|
3253
3285
|
try {
|
|
3254
|
-
const repoProjects = join29(
|
|
3286
|
+
const repoProjects = join29(repo, "shared", "projects");
|
|
3255
3287
|
if (!existsSync24(repoProjects)) {
|
|
3256
3288
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
3257
3289
|
}
|
|
3258
|
-
const matches = collectMatches(repoProjects, id);
|
|
3290
|
+
const matches = collectMatches(repoProjects, id, repo);
|
|
3259
3291
|
if (matches.length === 0) {
|
|
3260
3292
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
3261
3293
|
}
|
|
3262
|
-
for (const rel of matches) unstageOne(rel);
|
|
3294
|
+
for (const rel of matches) unstageOne(rel, repo);
|
|
3263
3295
|
reportScrubHint(id, matches);
|
|
3264
3296
|
} catch (err) {
|
|
3265
3297
|
if (!(err instanceof NomadFatal)) {
|
|
@@ -3271,37 +3303,37 @@ function cmdDropSession(id) {
|
|
|
3271
3303
|
releaseLock(handle);
|
|
3272
3304
|
}
|
|
3273
3305
|
}
|
|
3274
|
-
function collectMatches(repoProjects, id) {
|
|
3306
|
+
function collectMatches(repoProjects, id, repo) {
|
|
3275
3307
|
const matches = [];
|
|
3276
3308
|
for (const logical of readdirSync9(repoProjects)) {
|
|
3277
3309
|
const candidate = join29(repoProjects, logical, `${id}.jsonl`);
|
|
3278
3310
|
if (existsSync24(candidate)) {
|
|
3279
|
-
matches.push(relative4(
|
|
3311
|
+
matches.push(relative4(repo, candidate));
|
|
3280
3312
|
}
|
|
3281
3313
|
const dir = join29(repoProjects, logical, id);
|
|
3282
3314
|
if (existsSync24(dir) && statSync5(dir).isDirectory()) {
|
|
3283
|
-
const dirRel = relative4(
|
|
3284
|
-
const staged = expandStagedDir(dirRel);
|
|
3315
|
+
const dirRel = relative4(repo, dir);
|
|
3316
|
+
const staged = expandStagedDir(dirRel, repo);
|
|
3285
3317
|
if (staged.length > 0) matches.push(...staged);
|
|
3286
3318
|
else matches.push(dirRel);
|
|
3287
3319
|
}
|
|
3288
3320
|
}
|
|
3289
3321
|
return matches;
|
|
3290
3322
|
}
|
|
3291
|
-
function unstageOne(rel) {
|
|
3292
|
-
if (!isInIndex(rel)) {
|
|
3293
|
-
|
|
3323
|
+
function unstageOne(rel, repo) {
|
|
3324
|
+
if (!isInIndex(rel, repo)) {
|
|
3325
|
+
item(`dropped ${rel} (already absent from index)`);
|
|
3294
3326
|
return;
|
|
3295
3327
|
}
|
|
3296
3328
|
try {
|
|
3297
|
-
if (isTrackedInHead(rel)) {
|
|
3329
|
+
if (isTrackedInHead(rel, repo)) {
|
|
3298
3330
|
execFileSync12("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
3299
|
-
cwd:
|
|
3331
|
+
cwd: repo,
|
|
3300
3332
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3301
3333
|
});
|
|
3302
3334
|
} else {
|
|
3303
3335
|
execFileSync12("git", ["rm", "--cached", "-f", "--", rel], {
|
|
3304
|
-
cwd:
|
|
3336
|
+
cwd: repo,
|
|
3305
3337
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3306
3338
|
});
|
|
3307
3339
|
}
|
|
@@ -3310,7 +3342,7 @@ function unstageOne(rel) {
|
|
|
3310
3342
|
const detail = e.stderr?.toString().trim() ?? e.message;
|
|
3311
3343
|
throw new NomadFatal(`git failed to unstage ${rel}: ${detail}`);
|
|
3312
3344
|
}
|
|
3313
|
-
|
|
3345
|
+
item(`dropped ${rel}`);
|
|
3314
3346
|
}
|
|
3315
3347
|
|
|
3316
3348
|
// src/commands.redact.ts
|
|
@@ -3377,13 +3409,14 @@ init_utils_json();
|
|
|
3377
3409
|
init_utils();
|
|
3378
3410
|
function resolveLiveTranscript2(id) {
|
|
3379
3411
|
try {
|
|
3380
|
-
const mapPath = join31(
|
|
3412
|
+
const mapPath = join31(repoHome(), "path-map.json");
|
|
3381
3413
|
if (!existsSync26(mapPath)) return null;
|
|
3382
3414
|
const projects = readJson(mapPath).projects;
|
|
3415
|
+
const claude = claudeHome();
|
|
3383
3416
|
for (const hostMap of Object.values(projects)) {
|
|
3384
3417
|
const abs = hostMap[HOST];
|
|
3385
3418
|
if (abs === void 0) continue;
|
|
3386
|
-
const live = join31(
|
|
3419
|
+
const live = join31(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
3387
3420
|
if (existsSync26(live)) return live;
|
|
3388
3421
|
}
|
|
3389
3422
|
return null;
|
|
@@ -3402,7 +3435,9 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
3402
3435
|
fail(`invalid session id: ${id}`);
|
|
3403
3436
|
process.exit(1);
|
|
3404
3437
|
}
|
|
3405
|
-
|
|
3438
|
+
const repo = repoHome();
|
|
3439
|
+
const backup = backupBase();
|
|
3440
|
+
if (!existsSync26(repo)) die(`repo not cloned at ${repo}`);
|
|
3406
3441
|
const handle = acquireLock("redact");
|
|
3407
3442
|
if (handle === null) process.exit(0);
|
|
3408
3443
|
try {
|
|
@@ -3431,7 +3466,7 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
3431
3466
|
process.exitCode = 1;
|
|
3432
3467
|
return;
|
|
3433
3468
|
}
|
|
3434
|
-
const ts = freshBackupTs(
|
|
3469
|
+
const ts = freshBackupTs(backup);
|
|
3435
3470
|
const { total: totalCount, dirty } = applySubtreeRedactions(
|
|
3436
3471
|
localPath,
|
|
3437
3472
|
mainFindings,
|
|
@@ -3618,8 +3653,9 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
3618
3653
|
init_utils();
|
|
3619
3654
|
init_utils_json();
|
|
3620
3655
|
function loadValidatedExtras(opts) {
|
|
3621
|
-
const
|
|
3622
|
-
const
|
|
3656
|
+
const repo = repoHome();
|
|
3657
|
+
const mapPath = join32(repo, "path-map.json");
|
|
3658
|
+
const repoExtras = join32(repo, "shared", "extras");
|
|
3623
3659
|
if (!existsSync27(mapPath) || opts.requireRepoExtras === true && !existsSync27(repoExtras)) {
|
|
3624
3660
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
3625
3661
|
return null;
|
|
@@ -3672,14 +3708,14 @@ function runExtrasOp(v, dryRun, paths, backup) {
|
|
|
3672
3708
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
3673
3709
|
const { src, dst } = paths(t);
|
|
3674
3710
|
if (!existsSync28(src)) continue;
|
|
3675
|
-
const
|
|
3711
|
+
const item2 = `${t.logical}/${t.dirname}`;
|
|
3676
3712
|
if (dryRun) {
|
|
3677
|
-
would.push(
|
|
3713
|
+
would.push(item2);
|
|
3678
3714
|
continue;
|
|
3679
3715
|
}
|
|
3680
3716
|
backup(dst, t.localRoot);
|
|
3681
3717
|
copyExtras(src, dst);
|
|
3682
|
-
done.push(
|
|
3718
|
+
done.push(item2);
|
|
3683
3719
|
}
|
|
3684
3720
|
return { ...counts, done, would };
|
|
3685
3721
|
}
|
|
@@ -3687,7 +3723,8 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
3687
3723
|
const dryRun = opts.dryRun === true;
|
|
3688
3724
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
3689
3725
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
3690
|
-
const
|
|
3726
|
+
const repo = repoHome();
|
|
3727
|
+
const repoExtras = join33(repo, "shared", "extras");
|
|
3691
3728
|
if (!dryRun) mkdirSync6(repoExtras, { recursive: true });
|
|
3692
3729
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
3693
3730
|
v,
|
|
@@ -3696,7 +3733,7 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
3696
3733
|
src: join33(localRoot, dirname7),
|
|
3697
3734
|
dst: join33(repoExtras, logical, dirname7)
|
|
3698
3735
|
}),
|
|
3699
|
-
(dst) => backupRepoWrite(dst, ts,
|
|
3736
|
+
(dst) => backupRepoWrite(dst, ts, repo)
|
|
3700
3737
|
);
|
|
3701
3738
|
return { unmapped, skipped, pushed: done, wouldPush: would };
|
|
3702
3739
|
}
|
|
@@ -3707,7 +3744,7 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
3707
3744
|
missingMsg: "no path-map or repo extras dir; skipping extras remap"
|
|
3708
3745
|
});
|
|
3709
3746
|
if (v === null) return { unmapped: 0, skipped: 0, pulled: [], wouldPull: [] };
|
|
3710
|
-
const repoExtras = join33(
|
|
3747
|
+
const repoExtras = join33(repoHome(), "shared", "extras");
|
|
3711
3748
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
3712
3749
|
v,
|
|
3713
3750
|
dryRun,
|
|
@@ -3727,12 +3764,13 @@ function divergenceCheckExtras(ts) {
|
|
|
3727
3764
|
const v = loadValidatedExtras({});
|
|
3728
3765
|
if (v === null) return;
|
|
3729
3766
|
const counts = { unmapped: 0, skipped: 0 };
|
|
3730
|
-
const backupRoot = join34(
|
|
3767
|
+
const backupRoot = join34(backupBase(), ts, "extras");
|
|
3768
|
+
const repo = repoHome();
|
|
3731
3769
|
for (const { logical, localRoot, dirname: dirname7 } of eachExtrasTarget(v, counts)) {
|
|
3732
3770
|
const local = join34(localRoot, dirname7);
|
|
3733
|
-
const
|
|
3734
|
-
if (!existsSync29(local) || !existsSync29(
|
|
3735
|
-
const diff = listDivergingFiles(local,
|
|
3771
|
+
const repoEntry = join34(repo, "shared", "extras", logical, dirname7);
|
|
3772
|
+
if (!existsSync29(local) || !existsSync29(repoEntry)) continue;
|
|
3773
|
+
const diff = listDivergingFiles(local, repoEntry);
|
|
3736
3774
|
if (diff.length === 0) continue;
|
|
3737
3775
|
const projectBackupRoot = join34(backupRoot, encodePath(localRoot));
|
|
3738
3776
|
warn(
|
|
@@ -3765,10 +3803,12 @@ function emitCreate(onPreview, from, to) {
|
|
|
3765
3803
|
}
|
|
3766
3804
|
function applySharedLinks(ts, map, opts = {}) {
|
|
3767
3805
|
const dryRun = opts.dryRun === true;
|
|
3806
|
+
const claude = claudeHome();
|
|
3807
|
+
const repo = repoHome();
|
|
3768
3808
|
const linkNames = allSharedLinks(map);
|
|
3769
3809
|
for (const name of linkNames) {
|
|
3770
|
-
const linkPath = join35(
|
|
3771
|
-
const target = join35(
|
|
3810
|
+
const linkPath = join35(claude, name);
|
|
3811
|
+
const target = join35(repo, "shared", name);
|
|
3772
3812
|
if (!existsSync30(linkPath)) continue;
|
|
3773
3813
|
if (lstatSync8(linkPath).isSymbolicLink()) continue;
|
|
3774
3814
|
if (!existsSync30(target)) continue;
|
|
@@ -3780,19 +3820,21 @@ function applySharedLinks(ts, map, opts = {}) {
|
|
|
3780
3820
|
rmSync9(linkPath, { recursive: true, force: true });
|
|
3781
3821
|
}
|
|
3782
3822
|
for (const name of linkNames) {
|
|
3783
|
-
const target = join35(
|
|
3823
|
+
const target = join35(repo, "shared", name);
|
|
3784
3824
|
if (!existsSync30(target)) continue;
|
|
3785
3825
|
if (dryRun) {
|
|
3786
|
-
emitCreate(opts.onPreview, join35(
|
|
3826
|
+
emitCreate(opts.onPreview, join35(claude, name), target);
|
|
3787
3827
|
continue;
|
|
3788
3828
|
}
|
|
3789
|
-
ensureSymlink(join35(
|
|
3829
|
+
ensureSymlink(join35(claude, name), target);
|
|
3790
3830
|
}
|
|
3791
3831
|
}
|
|
3792
3832
|
function regenerateSettings(ts, opts = {}) {
|
|
3793
3833
|
const dryRun = opts.dryRun === true;
|
|
3794
|
-
const
|
|
3795
|
-
const
|
|
3834
|
+
const repo = repoHome();
|
|
3835
|
+
const claude = claudeHome();
|
|
3836
|
+
const basePath = join35(repo, "shared", "settings.base.json");
|
|
3837
|
+
const hostPath = join35(repo, "hosts", `${HOST}.json`);
|
|
3796
3838
|
if (!existsSync30(basePath)) {
|
|
3797
3839
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
3798
3840
|
}
|
|
@@ -3800,7 +3842,7 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
3800
3842
|
const hasOverrides = existsSync30(hostPath);
|
|
3801
3843
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
3802
3844
|
const merged = deepMerge(base, overrides);
|
|
3803
|
-
const settingsPath = join35(
|
|
3845
|
+
const settingsPath = join35(claude, "settings.json");
|
|
3804
3846
|
if (!hasOverrides && existsSync30(settingsPath)) {
|
|
3805
3847
|
try {
|
|
3806
3848
|
const existing = readJson(settingsPath);
|
|
@@ -4163,6 +4205,8 @@ function buildSettingsSectionForPreview(result) {
|
|
|
4163
4205
|
return s;
|
|
4164
4206
|
}
|
|
4165
4207
|
function computePreview(ts, map, verb = "pull") {
|
|
4208
|
+
const repo = repoHome();
|
|
4209
|
+
const claude = claudeHome();
|
|
4166
4210
|
console.log(`would pull on host=${HOST} (dry-run; no mutation)`);
|
|
4167
4211
|
console.log("");
|
|
4168
4212
|
const links = section("Symlinks");
|
|
@@ -4171,9 +4215,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
4171
4215
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
4172
4216
|
});
|
|
4173
4217
|
const settingsResult = previewSettings(
|
|
4174
|
-
join36(
|
|
4175
|
-
join36(
|
|
4176
|
-
join36(
|
|
4218
|
+
join36(repo, "shared", "settings.base.json"),
|
|
4219
|
+
join36(repo, "hosts", `${HOST}.json`),
|
|
4220
|
+
join36(claude, "settings.json")
|
|
4177
4221
|
);
|
|
4178
4222
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
4179
4223
|
const sessions = section("Sessions");
|
|
@@ -4229,13 +4273,13 @@ function parseAction(raw) {
|
|
|
4229
4273
|
}
|
|
4230
4274
|
|
|
4231
4275
|
// src/commands.push.recovery.redact.ts
|
|
4232
|
-
function resolveStagedDir(localPath, map) {
|
|
4276
|
+
function resolveStagedDir(localPath, map, claude, repo) {
|
|
4233
4277
|
for (const [logical, hostMap] of Object.entries(map.projects)) {
|
|
4234
4278
|
assertSafeLogical(logical);
|
|
4235
4279
|
const abs = hostMap[HOST];
|
|
4236
4280
|
if (abs === void 0) continue;
|
|
4237
|
-
if (localPath.startsWith(join37(
|
|
4238
|
-
return join37(
|
|
4281
|
+
if (localPath.startsWith(join37(claude, "projects", encodePath(abs)) + sep3)) {
|
|
4282
|
+
return join37(repo, "shared", "projects", logical);
|
|
4239
4283
|
}
|
|
4240
4284
|
}
|
|
4241
4285
|
return null;
|
|
@@ -4245,6 +4289,8 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4245
4289
|
log(msg);
|
|
4246
4290
|
return false;
|
|
4247
4291
|
};
|
|
4292
|
+
const claude = claudeHome();
|
|
4293
|
+
const repo = repoHome();
|
|
4248
4294
|
const sid = sessionIdFromFinding(f);
|
|
4249
4295
|
if (sid === null) {
|
|
4250
4296
|
return refuse(
|
|
@@ -4266,7 +4312,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4266
4312
|
End the session and choose Redact again, or choose Drop session (holds this session back from the push, local copy kept) or Skip.`
|
|
4267
4313
|
);
|
|
4268
4314
|
}
|
|
4269
|
-
const stagedProjectDir = resolveStagedDir(localPath, map);
|
|
4315
|
+
const stagedProjectDir = resolveStagedDir(localPath, map, claude, repo);
|
|
4270
4316
|
if (stagedProjectDir === null) {
|
|
4271
4317
|
return refuse(
|
|
4272
4318
|
`could not map the local transcript for session ${sid} to a staged copy; choose Drop session or Skip.`
|
|
@@ -4305,9 +4351,10 @@ import { join as join38 } from "node:path";
|
|
|
4305
4351
|
function dropSessionFromStaged(sid, map) {
|
|
4306
4352
|
const logicals = Object.keys(map.projects);
|
|
4307
4353
|
if (logicals.length === 0) return false;
|
|
4354
|
+
const repo = repoHome();
|
|
4308
4355
|
for (const logical of logicals) {
|
|
4309
|
-
const jsonl = join38(
|
|
4310
|
-
const dir = join38(
|
|
4356
|
+
const jsonl = join38(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
4357
|
+
const dir = join38(repo, "shared", "projects", logical, sid);
|
|
4311
4358
|
rmSync10(jsonl, { force: true });
|
|
4312
4359
|
rmSync10(dir, { recursive: true, force: true });
|
|
4313
4360
|
}
|
|
@@ -4317,19 +4364,19 @@ function dropSessionFromStaged(sid, map) {
|
|
|
4317
4364
|
// src/commands.push.recovery.actions.ts
|
|
4318
4365
|
init_push_gitleaks_scan();
|
|
4319
4366
|
init_utils();
|
|
4320
|
-
function applyAllow(f) {
|
|
4321
|
-
appendGitleaksIgnore(f.Fingerprint);
|
|
4367
|
+
function applyAllow(f, repo) {
|
|
4368
|
+
appendGitleaksIgnore(f.Fingerprint, repo);
|
|
4322
4369
|
}
|
|
4323
|
-
function allowAllFindings(findings) {
|
|
4370
|
+
function allowAllFindings(findings, repo) {
|
|
4324
4371
|
for (const f of findings) {
|
|
4325
|
-
appendGitleaksIgnore(f.Fingerprint);
|
|
4372
|
+
appendGitleaksIgnore(f.Fingerprint, repo);
|
|
4326
4373
|
}
|
|
4327
4374
|
}
|
|
4328
|
-
function allowFindingsByRule(findings, ruleId) {
|
|
4375
|
+
function allowFindingsByRule(findings, ruleId, repo) {
|
|
4329
4376
|
let count = 0;
|
|
4330
4377
|
for (const f of findings) {
|
|
4331
4378
|
if (f.RuleID === ruleId) {
|
|
4332
|
-
appendGitleaksIgnore(f.Fingerprint);
|
|
4379
|
+
appendGitleaksIgnore(f.Fingerprint, repo);
|
|
4333
4380
|
count++;
|
|
4334
4381
|
}
|
|
4335
4382
|
}
|
|
@@ -4351,7 +4398,7 @@ function dispatchOne(f, ctx) {
|
|
|
4351
4398
|
const sid = sessionIdFromFinding(f);
|
|
4352
4399
|
if (sid !== null && ctx.droppedSids.has(sid)) return;
|
|
4353
4400
|
if (action === "allow") {
|
|
4354
|
-
applyAllow(f);
|
|
4401
|
+
applyAllow(f, ctx.repo);
|
|
4355
4402
|
return;
|
|
4356
4403
|
}
|
|
4357
4404
|
if (sid === null) return;
|
|
@@ -4368,12 +4415,14 @@ function dispatchOne(f, ctx) {
|
|
|
4368
4415
|
if (applyRedact(f, ctx.ts, ctx.map, ctx.nowMs, ctx.scan)) ctx.redactedSids.add(sid);
|
|
4369
4416
|
}
|
|
4370
4417
|
}
|
|
4371
|
-
function dispatchActions(findings, actions,
|
|
4418
|
+
function dispatchActions(findings, actions, opts) {
|
|
4419
|
+
const { ts, map, nowMs, repo, scan = scanFile, drop = dropSessionFromStaged } = opts;
|
|
4372
4420
|
const ctx = {
|
|
4373
4421
|
actions,
|
|
4374
4422
|
ts,
|
|
4375
4423
|
map,
|
|
4376
4424
|
nowMs,
|
|
4425
|
+
repo,
|
|
4377
4426
|
scan,
|
|
4378
4427
|
drop,
|
|
4379
4428
|
redactedSids: /* @__PURE__ */ new Set(),
|
|
@@ -4415,17 +4464,17 @@ function printRecoveryLegend(print = console.log) {
|
|
|
4415
4464
|
print(" Skip - leave unresolved (the push aborts)");
|
|
4416
4465
|
print("");
|
|
4417
4466
|
}
|
|
4418
|
-
function applyThenRescan(scanVerdict,
|
|
4419
|
-
gitOrFatal(["add", "-A"], "git add",
|
|
4420
|
-
const next = scanVerdict();
|
|
4467
|
+
function applyThenRescan(scanVerdict, repoHome2) {
|
|
4468
|
+
gitOrFatal(["add", "-A"], "git add", repoHome2);
|
|
4469
|
+
const next = scanVerdict(repoHome2);
|
|
4421
4470
|
if (next.leak) {
|
|
4422
4471
|
const { bySession, other } = partitionFindings(next.findings);
|
|
4423
4472
|
throw new NomadFatal(buildSessionAwareFatal(bySession, other));
|
|
4424
4473
|
}
|
|
4425
4474
|
return next;
|
|
4426
4475
|
}
|
|
4427
|
-
function allowThenRescan(append, scanVerdict,
|
|
4428
|
-
const ignPath = join39(
|
|
4476
|
+
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
4477
|
+
const ignPath = join39(repoHome2, ".gitleaksignore");
|
|
4429
4478
|
let before;
|
|
4430
4479
|
try {
|
|
4431
4480
|
before = readFileSync11(ignPath, "utf8");
|
|
@@ -4434,7 +4483,7 @@ function allowThenRescan(append, scanVerdict, repoHome) {
|
|
|
4434
4483
|
}
|
|
4435
4484
|
append();
|
|
4436
4485
|
try {
|
|
4437
|
-
return applyThenRescan(scanVerdict,
|
|
4486
|
+
return applyThenRescan(scanVerdict, repoHome2);
|
|
4438
4487
|
} catch (err) {
|
|
4439
4488
|
if (before === null) rmSync11(ignPath, { force: true });
|
|
4440
4489
|
else writeFileSync5(ignPath, before, "utf8");
|
|
@@ -4467,22 +4516,23 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
|
|
|
4467
4516
|
printLegend = printRecoveryLegend
|
|
4468
4517
|
} = deps;
|
|
4469
4518
|
const scanVerdict = deps.scanVerdict ?? (await Promise.resolve().then(() => (init_push_leak_verdict(), push_leak_verdict_exports))).scanPushVerdict;
|
|
4519
|
+
const repo = repoHome();
|
|
4470
4520
|
let current = verdict;
|
|
4471
4521
|
if (redactAll) {
|
|
4472
4522
|
redactAllFindings(current.findings, ts, map, nowMs, scan);
|
|
4473
|
-
return applyThenRescan(scanVerdict,
|
|
4523
|
+
return applyThenRescan(scanVerdict, repo);
|
|
4474
4524
|
}
|
|
4475
4525
|
if (allowAll) {
|
|
4476
|
-
return allowThenRescan(() => allowAllFindings(current.findings), scanVerdict,
|
|
4526
|
+
return allowThenRescan(() => allowAllFindings(current.findings, repo), scanVerdict, repo);
|
|
4477
4527
|
}
|
|
4478
4528
|
if (allowRule !== void 0) {
|
|
4479
4529
|
return allowThenRescan(
|
|
4480
4530
|
() => {
|
|
4481
|
-
const matched = allowFindingsByRule(current.findings, allowRule);
|
|
4531
|
+
const matched = allowFindingsByRule(current.findings, allowRule, repo);
|
|
4482
4532
|
if (matched === 0) log(`no findings matched rule ${allowRule}; re-scanning`);
|
|
4483
4533
|
},
|
|
4484
4534
|
scanVerdict,
|
|
4485
|
-
|
|
4535
|
+
repo
|
|
4486
4536
|
);
|
|
4487
4537
|
}
|
|
4488
4538
|
if (!isTTYCheck()) {
|
|
@@ -4497,9 +4547,9 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
|
|
|
4497
4547
|
const { bySession, other } = partitionFindings(unresolved);
|
|
4498
4548
|
throw new NomadFatal(buildSessionAwareFatal(bySession, other));
|
|
4499
4549
|
}
|
|
4500
|
-
dispatchActions(current.findings, actions, ts, map, nowMs, scan);
|
|
4501
|
-
gitOrFatal(["add", "-A"], "git add",
|
|
4502
|
-
current = scanVerdict();
|
|
4550
|
+
dispatchActions(current.findings, actions, { ts, map, nowMs, repo, scan });
|
|
4551
|
+
gitOrFatal(["add", "-A"], "git add", repo);
|
|
4552
|
+
current = scanVerdict(repo);
|
|
4503
4553
|
}
|
|
4504
4554
|
return current;
|
|
4505
4555
|
}
|
|
@@ -4729,17 +4779,19 @@ function handleWedge(repo, forceRemote) {
|
|
|
4729
4779
|
function cmdPull(opts = {}) {
|
|
4730
4780
|
const dryRun = opts.dryRun === true;
|
|
4731
4781
|
const forceRemote = opts.forceRemote === true;
|
|
4732
|
-
|
|
4733
|
-
|
|
4782
|
+
const repo = repoHome();
|
|
4783
|
+
const backup = backupBase();
|
|
4784
|
+
if (!existsSync34(repo)) die(`repo not cloned at ${repo}`);
|
|
4785
|
+
if (!existsSync34(join40(repo, "shared", "settings.base.json"))) {
|
|
4734
4786
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
4735
4787
|
}
|
|
4736
4788
|
const handle = acquireLock("pull");
|
|
4737
4789
|
if (handle === null) process.exit(0);
|
|
4738
4790
|
try {
|
|
4739
|
-
const ts = freshBackupTs(
|
|
4740
|
-
handleWedge(
|
|
4791
|
+
const ts = freshBackupTs(backup);
|
|
4792
|
+
handleWedge(repo, forceRemote);
|
|
4741
4793
|
if (!dryRun) {
|
|
4742
|
-
const backupRoot = join40(
|
|
4794
|
+
const backupRoot = join40(backup, ts);
|
|
4743
4795
|
try {
|
|
4744
4796
|
mkdirSync8(backupRoot, { recursive: true });
|
|
4745
4797
|
} catch (err) {
|
|
@@ -4749,8 +4801,8 @@ function cmdPull(opts = {}) {
|
|
|
4749
4801
|
log(
|
|
4750
4802
|
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
4751
4803
|
);
|
|
4752
|
-
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase",
|
|
4753
|
-
const mapPath = join40(
|
|
4804
|
+
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
4805
|
+
const mapPath = join40(repo, "path-map.json");
|
|
4754
4806
|
const map = existsSync34(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
4755
4807
|
divergenceCheckExtras(ts);
|
|
4756
4808
|
if (dryRun) {
|
|
@@ -4871,7 +4923,7 @@ function stageSessions(tmpRoot, map) {
|
|
|
4871
4923
|
if (!p || p === "TBD") continue;
|
|
4872
4924
|
reverse.set(encodePath(p), logical);
|
|
4873
4925
|
}
|
|
4874
|
-
const localProjects = join41(
|
|
4926
|
+
const localProjects = join41(claudeHome(), "projects");
|
|
4875
4927
|
if (!existsSync35(localProjects)) return 0;
|
|
4876
4928
|
let staged = 0;
|
|
4877
4929
|
for (const dir of readdirSync11(localProjects)) {
|
|
@@ -4913,7 +4965,7 @@ function previewPushLeaks(map) {
|
|
|
4913
4965
|
if (sessionCount + extrasCount === 0) {
|
|
4914
4966
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
4915
4967
|
}
|
|
4916
|
-
const ignoreFile = join41(
|
|
4968
|
+
const ignoreFile = join41(repoHome(), ".gitleaksignore");
|
|
4917
4969
|
if (existsSync35(ignoreFile)) {
|
|
4918
4970
|
copyFileSync(ignoreFile, join41(tmpRoot, ".gitleaksignore"));
|
|
4919
4971
|
}
|
|
@@ -4936,11 +4988,11 @@ function previewPushLeaks(map) {
|
|
|
4936
4988
|
init_utils();
|
|
4937
4989
|
init_utils_fs();
|
|
4938
4990
|
init_utils_json();
|
|
4939
|
-
function guardGitlinks() {
|
|
4940
|
-
const gitlinks = findGitlinks(join42(
|
|
4991
|
+
function guardGitlinks(repo) {
|
|
4992
|
+
const gitlinks = findGitlinks(join42(repo, "shared"));
|
|
4941
4993
|
if (gitlinks.length === 0) return;
|
|
4942
4994
|
for (const p of gitlinks) {
|
|
4943
|
-
const rel = relative5(
|
|
4995
|
+
const rel = relative5(repo, p);
|
|
4944
4996
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
4945
4997
|
}
|
|
4946
4998
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -4948,15 +5000,15 @@ function guardGitlinks() {
|
|
|
4948
5000
|
`gitlink trap: ${gitlinks.length} nested .git ${noun} in shared/; remove before retry`
|
|
4949
5001
|
);
|
|
4950
5002
|
}
|
|
4951
|
-
async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule) {
|
|
4952
|
-
gitOrFatal(["add", "-A"], "git add",
|
|
4953
|
-
let verdict = withSpinner("Scanning for secrets", scanPushVerdict);
|
|
5003
|
+
async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo) {
|
|
5004
|
+
gitOrFatal(["add", "-A"], "git add", repo);
|
|
5005
|
+
let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
|
|
4954
5006
|
if (verdict.leak) {
|
|
4955
5007
|
renderPushTree(st, verdict);
|
|
4956
5008
|
verdict = await resolveLeakFindings(verdict, ts, map, { redactAll, allowAll, allowRule });
|
|
4957
5009
|
}
|
|
4958
|
-
gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit",
|
|
4959
|
-
withSpinner("Pushing", () => gitOrFatal(["push"], "git push",
|
|
5010
|
+
gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit", repo);
|
|
5011
|
+
withSpinner("Pushing", () => gitOrFatal(["push"], "git push", repo));
|
|
4960
5012
|
renderPushTree(st, verdict);
|
|
4961
5013
|
}
|
|
4962
5014
|
function runDryRunPreview(st, map) {
|
|
@@ -4989,25 +5041,27 @@ async function cmdPush(opts = {}) {
|
|
|
4989
5041
|
const allowAll = opts.allowAll === true;
|
|
4990
5042
|
const allowRule = opts.allowRule;
|
|
4991
5043
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
4992
|
-
|
|
5044
|
+
const repo = repoHome();
|
|
5045
|
+
const backup = backupBase();
|
|
5046
|
+
if (!existsSync36(repo)) die(`repo not cloned at ${repo}`);
|
|
4993
5047
|
const handle = acquireLock("push");
|
|
4994
5048
|
if (handle === null) process.exit(0);
|
|
4995
5049
|
try {
|
|
4996
5050
|
console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
|
|
4997
5051
|
probeGitleaks();
|
|
4998
|
-
withSpinner("Rebasing onto origin", rebaseBeforePush);
|
|
4999
|
-
const ts = freshBackupTs(
|
|
5052
|
+
withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
|
|
5053
|
+
const ts = freshBackupTs(backup);
|
|
5000
5054
|
const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun }));
|
|
5001
5055
|
const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
|
|
5002
5056
|
const st = { dryRun, remap, extras };
|
|
5003
|
-
guardGitlinks();
|
|
5004
|
-
const status = gitStatusPorcelainZ(
|
|
5057
|
+
guardGitlinks(repo);
|
|
5058
|
+
const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
|
|
5005
5059
|
if (!dryRun && !status) {
|
|
5006
5060
|
log("nothing to commit");
|
|
5007
5061
|
renderNoScanTree(st);
|
|
5008
5062
|
return;
|
|
5009
5063
|
}
|
|
5010
|
-
const mapPath = join42(
|
|
5064
|
+
const mapPath = join42(repo, "path-map.json");
|
|
5011
5065
|
if (!existsSync36(mapPath)) {
|
|
5012
5066
|
if (dryRun) return runDryRunPreview(st, null);
|
|
5013
5067
|
die("path-map.json missing, cannot enforce push allow-list");
|
|
@@ -5015,7 +5069,7 @@ async function cmdPush(opts = {}) {
|
|
|
5015
5069
|
const map = readPathMap(mapPath);
|
|
5016
5070
|
if (status) enforceAllowList(status, map);
|
|
5017
5071
|
if (dryRun) return runDryRunPreview(st, map);
|
|
5018
|
-
await commitAndPush(st, ts, map, redactAll, allowAll, allowRule);
|
|
5072
|
+
await commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo);
|
|
5019
5073
|
} catch (err) {
|
|
5020
5074
|
if (err instanceof NomadFatal) {
|
|
5021
5075
|
fail(err.message);
|
|
@@ -5055,9 +5109,10 @@ init_utils_fs();
|
|
|
5055
5109
|
init_utils_json();
|
|
5056
5110
|
function cmdDiff() {
|
|
5057
5111
|
try {
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
const
|
|
5112
|
+
const repo = repoHome();
|
|
5113
|
+
if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
|
|
5114
|
+
const ts = freshBackupTs(backupBase());
|
|
5115
|
+
const mapPath = join43(repo, "path-map.json");
|
|
5061
5116
|
const map = existsSync37(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
5062
5117
|
computePreview(ts, map, "diff");
|
|
5063
5118
|
} catch (err) {
|
|
@@ -5090,8 +5145,9 @@ function ensureOriginRepo(repoName, run = execFileSync16) {
|
|
|
5090
5145
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
5091
5146
|
);
|
|
5092
5147
|
}
|
|
5148
|
+
const repo = repoHome();
|
|
5093
5149
|
try {
|
|
5094
|
-
readOriginRemote(
|
|
5150
|
+
readOriginRemote(repo, run);
|
|
5095
5151
|
return;
|
|
5096
5152
|
} catch {
|
|
5097
5153
|
}
|
|
@@ -5106,7 +5162,7 @@ function ensureOriginRepo(repoName, run = execFileSync16) {
|
|
|
5106
5162
|
die("gh CLI is not authenticated. Run `gh auth login` and retry.");
|
|
5107
5163
|
}
|
|
5108
5164
|
try {
|
|
5109
|
-
run("git", ["init", "-b", "main"], { cwd:
|
|
5165
|
+
run("git", ["init", "-b", "main"], { cwd: repo, stdio: ["ignore", "ignore", "pipe"] });
|
|
5110
5166
|
} catch (err) {
|
|
5111
5167
|
const e = err;
|
|
5112
5168
|
throw new NomadFatal(`git init failed: ${e.message}`);
|
|
@@ -5139,7 +5195,7 @@ function ensureOriginRepo(repoName, run = execFileSync16) {
|
|
|
5139
5195
|
}
|
|
5140
5196
|
try {
|
|
5141
5197
|
run("git", ["remote", "add", "origin", `git@github.com:${owner}/${repoName}.git`], {
|
|
5142
|
-
cwd:
|
|
5198
|
+
cwd: repo,
|
|
5143
5199
|
stdio: ["ignore", "ignore", "pipe"]
|
|
5144
5200
|
});
|
|
5145
5201
|
} catch (err) {
|
|
@@ -5157,10 +5213,12 @@ init_utils_json();
|
|
|
5157
5213
|
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync38, rmSync as rmSync13, statSync as statSync9 } from "node:fs";
|
|
5158
5214
|
import { join as join44 } from "node:path";
|
|
5159
5215
|
function snapshotIntoShared(map) {
|
|
5216
|
+
const repo = repoHome();
|
|
5217
|
+
const claude = claudeHome();
|
|
5160
5218
|
for (const name of allSharedLinks(map)) {
|
|
5161
|
-
const src = join44(
|
|
5219
|
+
const src = join44(claude, name);
|
|
5162
5220
|
if (!existsSync38(src)) continue;
|
|
5163
|
-
const dst = join44(
|
|
5221
|
+
const dst = join44(repo, "shared", name);
|
|
5164
5222
|
if (statSync9(src).isDirectory()) {
|
|
5165
5223
|
const gk = join44(dst, ".gitkeep");
|
|
5166
5224
|
if (existsSync38(gk)) rmSync13(gk);
|
|
@@ -5170,7 +5228,7 @@ function snapshotIntoShared(map) {
|
|
|
5170
5228
|
}
|
|
5171
5229
|
log(`snapshotted shared/${name} from ${src}`);
|
|
5172
5230
|
}
|
|
5173
|
-
const userSettings = join44(
|
|
5231
|
+
const userSettings = join44(claude, "settings.json");
|
|
5174
5232
|
if (existsSync38(userSettings)) {
|
|
5175
5233
|
let parsed;
|
|
5176
5234
|
try {
|
|
@@ -5178,7 +5236,7 @@ function snapshotIntoShared(map) {
|
|
|
5178
5236
|
} catch (err) {
|
|
5179
5237
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
5180
5238
|
}
|
|
5181
|
-
const hostFile = join44(
|
|
5239
|
+
const hostFile = join44(repo, "hosts", `${HOST}.json`);
|
|
5182
5240
|
writeJsonAtomic(hostFile, parsed);
|
|
5183
5241
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
5184
5242
|
}
|
|
@@ -5189,13 +5247,13 @@ init_utils();
|
|
|
5189
5247
|
init_utils_fs();
|
|
5190
5248
|
var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.claude/CLAUDE.md by nomad pull -->\n";
|
|
5191
5249
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
5192
|
-
function preflightConflict(
|
|
5250
|
+
function preflightConflict(repoHome2) {
|
|
5193
5251
|
const candidates = [
|
|
5194
|
-
join45(
|
|
5195
|
-
join45(
|
|
5196
|
-
join45(
|
|
5197
|
-
join45(
|
|
5198
|
-
join45(
|
|
5252
|
+
join45(repoHome2, "shared", "settings.base.json"),
|
|
5253
|
+
join45(repoHome2, "shared", "CLAUDE.md"),
|
|
5254
|
+
join45(repoHome2, "path-map.json"),
|
|
5255
|
+
join45(repoHome2, "hosts"),
|
|
5256
|
+
join45(repoHome2, "shared")
|
|
5199
5257
|
];
|
|
5200
5258
|
for (const c of candidates) {
|
|
5201
5259
|
if (existsSync39(c)) return c;
|
|
@@ -5205,46 +5263,48 @@ function preflightConflict(repoHome) {
|
|
|
5205
5263
|
function cmdInit(opts = {}) {
|
|
5206
5264
|
const snapshot = opts.snapshot === true;
|
|
5207
5265
|
const keepActions = opts.keepActions === true;
|
|
5208
|
-
|
|
5209
|
-
const
|
|
5266
|
+
const repo = repoHome();
|
|
5267
|
+
const claude = claudeHome();
|
|
5268
|
+
mkdirSync10(repo, { recursive: true });
|
|
5269
|
+
const conflict = preflightConflict(repo);
|
|
5210
5270
|
if (conflict !== null) {
|
|
5211
5271
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
5212
5272
|
}
|
|
5213
5273
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
5214
|
-
mkdirSync10(join45(
|
|
5215
|
-
mkdirSync10(join45(
|
|
5274
|
+
mkdirSync10(join45(repo, "shared"), { recursive: true });
|
|
5275
|
+
mkdirSync10(join45(repo, "hosts"), { recursive: true });
|
|
5216
5276
|
for (const name of SHARED_KEEP_DIRS) {
|
|
5217
|
-
mkdirSync10(join45(
|
|
5277
|
+
mkdirSync10(join45(repo, "shared", name), { recursive: true });
|
|
5218
5278
|
}
|
|
5219
|
-
const userClaudeMd = join45(
|
|
5279
|
+
const userClaudeMd = join45(claude, "CLAUDE.md");
|
|
5220
5280
|
if (!snapshot || !existsSync39(userClaudeMd)) {
|
|
5221
|
-
writeFileSync6(join45(
|
|
5222
|
-
|
|
5281
|
+
writeFileSync6(join45(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
5282
|
+
item("created shared/CLAUDE.md");
|
|
5223
5283
|
}
|
|
5224
5284
|
for (const name of SHARED_KEEP_DIRS) {
|
|
5225
|
-
writeFileSync6(join45(
|
|
5226
|
-
|
|
5227
|
-
}
|
|
5228
|
-
writeFileSync6(join45(
|
|
5229
|
-
|
|
5230
|
-
writeJsonAtomic(join45(
|
|
5231
|
-
|
|
5232
|
-
writeJsonAtomic(join45(
|
|
5233
|
-
|
|
5285
|
+
writeFileSync6(join45(repo, "shared", name, ".gitkeep"), "");
|
|
5286
|
+
item(`created shared/${name}/.gitkeep`);
|
|
5287
|
+
}
|
|
5288
|
+
writeFileSync6(join45(repo, "hosts", ".gitkeep"), "");
|
|
5289
|
+
item("created hosts/.gitkeep");
|
|
5290
|
+
writeJsonAtomic(join45(repo, "shared", "settings.base.json"), {});
|
|
5291
|
+
item("created shared/settings.base.json");
|
|
5292
|
+
writeJsonAtomic(join45(repo, "path-map.json"), { projects: {} });
|
|
5293
|
+
item("created path-map.json");
|
|
5234
5294
|
if (snapshot) {
|
|
5235
5295
|
snapshotIntoShared({ projects: {} });
|
|
5236
5296
|
log(`snapshot staged in shared/; review, then 'nomad push' to share with other hosts.`);
|
|
5237
5297
|
log("~/.claude/ originals were NOT removed.");
|
|
5238
5298
|
}
|
|
5239
5299
|
if (!keepActions) {
|
|
5240
|
-
maybeDisableRepoActions(
|
|
5300
|
+
maybeDisableRepoActions(repo, opts.run);
|
|
5241
5301
|
}
|
|
5242
5302
|
log("init complete");
|
|
5243
5303
|
}
|
|
5244
|
-
function maybeDisableRepoActions(
|
|
5304
|
+
function maybeDisableRepoActions(repoHome2, run) {
|
|
5245
5305
|
let remote;
|
|
5246
5306
|
try {
|
|
5247
|
-
remote = readOriginRemote(
|
|
5307
|
+
remote = readOriginRemote(repoHome2, run);
|
|
5248
5308
|
} catch {
|
|
5249
5309
|
return;
|
|
5250
5310
|
}
|
|
@@ -5530,7 +5590,7 @@ function parsePushArgs(argv) {
|
|
|
5530
5590
|
// package.json
|
|
5531
5591
|
var package_default = {
|
|
5532
5592
|
name: "claude-nomad",
|
|
5533
|
-
version: "0.44.
|
|
5593
|
+
version: "0.44.1",
|
|
5534
5594
|
type: "module",
|
|
5535
5595
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
5536
5596
|
keywords: [
|
|
@@ -5597,6 +5657,8 @@ var package_default = {
|
|
|
5597
5657
|
"@commitlint/cli": "^21.0.1",
|
|
5598
5658
|
"@commitlint/config-conventional": "^21.0.1",
|
|
5599
5659
|
"@eslint/js": "^10.0.1",
|
|
5660
|
+
"@stryker-mutator/core": "9.6.1",
|
|
5661
|
+
"@stryker-mutator/vitest-runner": "9.6.1",
|
|
5600
5662
|
"@types/node": "^22.0.0",
|
|
5601
5663
|
"@vitest/coverage-v8": "^4.1.6",
|
|
5602
5664
|
diff: "^9.0.0",
|
|
@@ -5737,7 +5799,7 @@ function resumeCmd(sessionId) {
|
|
|
5737
5799
|
fail(`invalid session id: ${sessionId}`);
|
|
5738
5800
|
process.exit(1);
|
|
5739
5801
|
}
|
|
5740
|
-
const projectsRoot = join46(
|
|
5802
|
+
const projectsRoot = join46(claudeHome(), "projects");
|
|
5741
5803
|
if (!existsSync40(projectsRoot)) {
|
|
5742
5804
|
fail(`${projectsRoot} does not exist`);
|
|
5743
5805
|
process.exit(1);
|
|
@@ -5752,7 +5814,7 @@ function resumeCmd(sessionId) {
|
|
|
5752
5814
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
5753
5815
|
process.exit(1);
|
|
5754
5816
|
}
|
|
5755
|
-
const mapPath = join46(
|
|
5817
|
+
const mapPath = join46(repoHome(), "path-map.json");
|
|
5756
5818
|
if (!existsSync40(mapPath)) {
|
|
5757
5819
|
fail("path-map.json missing");
|
|
5758
5820
|
process.exit(1);
|
|
@@ -5832,7 +5894,8 @@ function shQuote(s) {
|
|
|
5832
5894
|
|
|
5833
5895
|
// src/nomad.ts
|
|
5834
5896
|
init_utils();
|
|
5835
|
-
|
|
5897
|
+
var h = home();
|
|
5898
|
+
if (!h) {
|
|
5836
5899
|
fail(
|
|
5837
5900
|
"could not determine home directory (HOME env unset and no uid mapping). Set HOME and retry."
|
|
5838
5901
|
);
|