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.
Files changed (3) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/nomad.mjs +381 -318
  3. 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 HOME, CLAUDE_HOME, BACKUP_BASE, REPO_HOME, SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, HOST, SHARED_LINKS, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, PUSH_ALLOWED_STATIC;
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 rel = relative(CLAUDE_HOME, absPath);
538
+ const claude = claudeHome();
539
+ const rel = relative(claude, absPath);
530
540
  if (rel.startsWith("..") || rel === "") return;
531
- const backupRoot = join2(BACKUP_BASE, ts);
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, repoHome) {
546
+ function backupRepoWrite(absPath, ts, repoHome2) {
537
547
  if (!existsSync(absPath)) return;
538
- const rel = relative(repoHome, absPath);
548
+ const rel = relative(repoHome2, absPath);
539
549
  if (rel.startsWith("..") || rel === "") return;
540
- const backupRoot = join2(BACKUP_BASE, ts, "repo");
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(BACKUP_BASE, ts, "extras");
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(REPO_HOME, ".gitleaks.toml");
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 overlayPath = join12(REPO_HOME, ".gitleaks.overlay.toml");
586
- const repoToml = join12(REPO_HOME, ".gitleaks.toml");
587
- const bundled = resolveTomlPath();
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: REPO_HOME,
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
- 'rebase failed; if a conflict was reported, resolve it in ~/claude-nomad/ and run "git rebase --continue" (or "git rebase --abort" to give up). Re-run nomad push after resolution.'
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(REPO_HOME, true);
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(repoHome) {
999
- const mapPath = join3(repoHome, "path-map.json");
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(BACKUP_BASE);
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}`, REPO_HOME);
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 map = readMapIfPresent(REPO_HOME);
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(CLAUDE_HOME, name);
1033
- const sharedTarget = join3(REPO_HOME, "shared", name);
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(BACKUP_BASE);
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(REPO_HOME, ".gitleaksignore");
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
- if (!existsSync3(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
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
- log(`allowed: ${fp}`);
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(backupBase) {
1176
- if (!existsSync4(backupBase)) return [];
1177
- return readdirSync(backupBase).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(join5(backupBase, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
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(backupBase, name) {
1198
+ function safeDelete(backupBase2, name) {
1186
1199
  if (!isTsDir(name)) return;
1187
- const full = join5(backupBase, name);
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, backupBase = BACKUP_BASE) {
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(backupBase);
1224
+ const dirs = listBackupDirs(backupBase2);
1212
1225
  const targets = resolveTargets(dirs, olderThanMs, keep);
1213
1226
  if (dryRun) {
1214
- for (const name of targets) log(`would remove ${name}`);
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(backupBase, name);
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
- var EJECT_CHECKLIST = [
1229
- "Manual steps remaining to finish leaving claude-nomad on this host:",
1230
- ` 1. Uninstall the CLI: npm uninstall -g claude-nomad`,
1231
- ` 2. Remove NOMAD_HOST and NOMAD_REPO from your shell rc (~/.zshrc or ~/.bashrc)`,
1232
- ` 3. Optionally delete the local sync checkout: rm -rf ${REPO_HOME}`,
1233
- ` 4. Optionally delete the private sync repo on GitHub`,
1234
- ` 5. Optionally delete the backup cache: rm -rf ${BACKUP_BASE}`
1235
- ].join("\n");
1236
- var DEFAULT_ROOTS = { claudeHome: CLAUDE_HOME, repoHome: REPO_HOME };
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(repoHome) {
1249
- const mapPath = join6(repoHome, "path-map.json");
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(repoHome) {
1272
+ function resolveSharedRoot(repoHome2) {
1259
1273
  try {
1260
- return realpathSync(join6(repoHome, "shared"));
1274
+ return realpathSync(join6(repoHome2, "shared"));
1261
1275
  } catch {
1262
1276
  return die(
1263
- `cannot resolve ${join6(repoHome, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
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
- log(`skipped (not a nomad-managed target): ${name} -> ${target}`);
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
- log(`ejected: ${name}`);
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, claudeHome, sharedRoot) {
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(claudeHome, name);
1314
+ const linkPath = join6(claudeHome2, name);
1301
1315
  if (cls === "absent") {
1302
- log(`skipped (absent): ${name}`);
1316
+ item(`skipped (absent): ${name}`);
1303
1317
  } else if (cls === "skip-real") {
1304
- log(`skipped (not a symlink): ${name}`);
1318
+ item(`skipped (not a symlink): ${name}`);
1305
1319
  } else {
1306
1320
  previewMaterialize(name, linkPath, sharedRoot);
1307
1321
  }
1308
1322
  }
1309
- log(EJECT_CHECKLIST);
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
- log(`would materialize: ${name} (target now unresolvable; re-run to re-classify)`);
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
- log(`skipped (not a nomad-managed target): ${name} -> ${target}`);
1334
+ item(`skipped (not a nomad-managed target): ${name} -> ${target}`);
1321
1335
  return;
1322
1336
  }
1323
- log(`would materialize: ${name} (copy ${target} -> ${linkPath})`);
1337
+ item(`would materialize: ${name} (copy ${target} -> ${linkPath})`);
1324
1338
  }
1325
- function runLiveEject(names, classifications, claudeHome, sharedRoot) {
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(claudeHome, name);
1344
+ const linkPath = join6(claudeHome2, name);
1331
1345
  if (cls === "absent") {
1332
- log(`skipped (absent): ${name}`);
1346
+ item(`skipped (absent): ${name}`);
1333
1347
  skipped++;
1334
1348
  } else if (cls === "skip-real") {
1335
- log(`skipped (not a symlink): ${name}`);
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(EJECT_CHECKLIST);
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 ${REPO_HOME} yet, fix the cause and re-run \`nomad eject\` (it is idempotent on already-real names)`
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 cmdEject(opts = {}, roots = DEFAULT_ROOTS) {
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(repoHome);
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(claudeHome, name)));
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(repoHome);
1389
+ const sharedRoot = resolveSharedRoot(repoHome2);
1373
1390
  if (dryRun) {
1374
- previewDryRun(names, classifications, claudeHome, sharedRoot);
1391
+ previewDryRun(names, classifications, claudeHome2, sharedRoot);
1375
1392
  return;
1376
1393
  }
1377
1394
  try {
1378
- runLiveEject(names, classifications, claudeHome, sharedRoot);
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 item of items) {
1415
- console.log(item === "" ? "" : ` ${item}`);
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, item, j) => item === "" || isChild(item) ? acc : j,
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 item = s.items[j];
1432
- if (item === "") console.log("");
1433
- else if (isChild(item)) console.log(renderChildLine(s.items, j));
1434
- else console.log(`${j === lastContent ? " \u2514 " : " \u251C "}${item}`);
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(item) {
1438
- return item.startsWith(" ");
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(repoHome, host) {
1474
- const basePath = join7(repoHome, "shared", "settings.base.json");
1475
- const mapPath = join7(repoHome, "path-map.json");
1476
- const hostPath = join7(repoHome, "hosts", `${host}.json`);
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(repoHome, host) {
1494
- const basePath = join7(repoHome, "shared", "settings.base.json");
1495
- const mapPath = join7(repoHome, "path-map.json");
1496
- const hostPath = join7(repoHome, "hosts", `${host}.json`);
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(REPO_HOME)}`);
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(REPO_HOME) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(REPO_HOME)}`
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 state = classifyRepoState(REPO_HOME, HOST);
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(REPO_HOME, HOST)}${overrideLabel}`
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(REPO_HOME, "shared", name));
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(CLAUDE_HOME, name);
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(REPO_HOME, "shared", "settings.base.json");
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(CLAUDE_HOME, "settings.json");
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 hostFile = join9(REPO_HOME, "hosts", `${HOST}.json`);
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(REPO_HOME, "hosts");
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(CLAUDE_HOME, "projects");
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(REPO_HOME, "path-map.json");
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 sharedDir = join14(REPO_HOME, "shared");
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(REPO_HOME, p);
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: REPO_HOME,
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(REPO_HOME);
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(REPO_HOME);
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(backupBase, dirs) {
1909
+ function totalSizeMb(backupBase2, dirs) {
1890
1910
  let bytes = 0;
1891
- for (const name of dirs) bytes += dirSizeBytes(join15(backupBase, name));
1911
+ for (const name of dirs) bytes += dirSizeBytes(join15(backupBase2, name));
1892
1912
  return bytes / BYTES_PER_MB;
1893
1913
  }
1894
- function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
1895
- if (!existsSync13(backupBase)) return;
1896
- const dirs = safeReaddir(backupBase).filter((n) => TS_SHAPE2.test(n));
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(backupBase, dirs);
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(CLAUDE_HOME, "settings.json");
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(CLAUDE_HOME, "projects", encoded, `${sid}.jsonl`);
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
- log(`skip ${rel}: extension not in allowlist`);
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 mapPath = join19(REPO_HOME, "path-map.json");
2113
- const repoProjects = join19(REPO_HOME, "shared", "projects");
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(CLAUDE_HOME, "projects");
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 mapPath = join19(REPO_HOME, "path-map.json");
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(CLAUDE_HOME, "projects");
2183
- const repoProjects = join19(REPO_HOME, "shared", "projects");
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, REPO_HOME);
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 mapPath = join20(REPO_HOME, "path-map.json");
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(CLAUDE_HOME, "projects");
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(CLAUDE_HOME, "hooks");
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
- return token.replace(/^\$\{HOME\}/, HOME).replace(/^\$HOME/, HOME).replace(/^~/, HOME);
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 = `${CLAUDE_HOME}/`;
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(CLAUDE_HOME, "settings.json");
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
- return token.replace(/^\$\{HOME\}/, HOME).replace(/^\$HOME/, HOME).replace(/^~/, HOME);
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(REPO_HOME, "path-map.json");
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 = `${CLAUDE_HOME}/${name}/`;
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(CLAUDE_HOME, "settings.json");
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(REPO_HOME, ".gitleaks.toml");
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(REPO_HOME, run);
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(item) {
2969
- return item.includes(failGlyph);
2995
+ function isFailLine(item2) {
2996
+ return item2.includes(failGlyph);
2970
2997
  }
2971
- function isWarnLine(item) {
2972
- return !isFailLine(item) && item.includes(warnGlyph);
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((item) => item.replace(/^\t/, ""));
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(REPO_HOME, "path-map.json");
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: REPO_HOME,
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: REPO_HOME,
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: REPO_HOME,
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(REPO_HOME, "path-map.json");
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(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
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
- var LOCK_PATH = join28(HOME, ".cache", "claude-nomad", "nomad.lock");
3167
+ function lockFilePath() {
3168
+ return join28(home(), ".cache", "claude-nomad", "nomad.lock");
3169
+ }
3141
3170
  function acquireLock(verb) {
3142
- mkdirSync5(dirname4(LOCK_PATH), { recursive: true });
3171
+ const lp = lockFilePath();
3172
+ mkdirSync5(dirname4(lp), { recursive: true });
3143
3173
  try {
3144
- const fd = openSync3(LOCK_PATH, "wx");
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(LOCK_PATH);
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(LOCK_PATH);
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(LOCK_PATH, "utf8").trim();
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(LOCK_PATH);
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(LOCK_PATH, "utf8").trim();
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(LOCK_PATH, "wx");
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(LOCK_PATH);
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
- if (!existsSync24(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
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(REPO_HOME, "shared", "projects");
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(REPO_HOME, candidate));
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(REPO_HOME, dir);
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
- log(`dropped ${rel} (already absent from index)`);
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: REPO_HOME,
3331
+ cwd: repo,
3300
3332
  stdio: ["ignore", "pipe", "pipe"]
3301
3333
  });
3302
3334
  } else {
3303
3335
  execFileSync12("git", ["rm", "--cached", "-f", "--", rel], {
3304
- cwd: REPO_HOME,
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
- log(`dropped ${rel}`);
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(REPO_HOME, "path-map.json");
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(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
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
- if (!existsSync26(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
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(BACKUP_BASE);
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 mapPath = join32(REPO_HOME, "path-map.json");
3622
- const repoExtras = join32(REPO_HOME, "shared", "extras");
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 item = `${t.logical}/${t.dirname}`;
3711
+ const item2 = `${t.logical}/${t.dirname}`;
3676
3712
  if (dryRun) {
3677
- would.push(item);
3713
+ would.push(item2);
3678
3714
  continue;
3679
3715
  }
3680
3716
  backup(dst, t.localRoot);
3681
3717
  copyExtras(src, dst);
3682
- done.push(item);
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 repoExtras = join33(REPO_HOME, "shared", "extras");
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, REPO_HOME)
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(REPO_HOME, "shared", "extras");
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(BACKUP_BASE, ts, "extras");
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 repo = join34(REPO_HOME, "shared", "extras", logical, dirname7);
3734
- if (!existsSync29(local) || !existsSync29(repo)) continue;
3735
- const diff = listDivergingFiles(local, repo);
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(CLAUDE_HOME, name);
3771
- const target = join35(REPO_HOME, "shared", name);
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(REPO_HOME, "shared", name);
3823
+ const target = join35(repo, "shared", name);
3784
3824
  if (!existsSync30(target)) continue;
3785
3825
  if (dryRun) {
3786
- emitCreate(opts.onPreview, join35(CLAUDE_HOME, name), target);
3826
+ emitCreate(opts.onPreview, join35(claude, name), target);
3787
3827
  continue;
3788
3828
  }
3789
- ensureSymlink(join35(CLAUDE_HOME, name), target);
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 basePath = join35(REPO_HOME, "shared", "settings.base.json");
3795
- const hostPath = join35(REPO_HOME, "hosts", `${HOST}.json`);
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(CLAUDE_HOME, "settings.json");
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(REPO_HOME, "shared", "settings.base.json"),
4175
- join36(REPO_HOME, "hosts", `${HOST}.json`),
4176
- join36(CLAUDE_HOME, "settings.json")
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(CLAUDE_HOME, "projects", encodePath(abs)) + sep3)) {
4238
- return join37(REPO_HOME, "shared", "projects", logical);
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(REPO_HOME, "shared", "projects", logical, `${sid}.jsonl`);
4310
- const dir = join38(REPO_HOME, "shared", "projects", logical, sid);
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, ts, map, nowMs, scan = scanFile, drop = dropSessionFromStaged) {
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, repoHome) {
4419
- gitOrFatal(["add", "-A"], "git add", repoHome);
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, repoHome) {
4428
- const ignPath = join39(repoHome, ".gitleaksignore");
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, repoHome);
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, REPO_HOME);
4523
+ return applyThenRescan(scanVerdict, repo);
4474
4524
  }
4475
4525
  if (allowAll) {
4476
- return allowThenRescan(() => allowAllFindings(current.findings), scanVerdict, REPO_HOME);
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
- REPO_HOME
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", REPO_HOME);
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
- if (!existsSync34(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
4733
- if (!existsSync34(join40(REPO_HOME, "shared", "settings.base.json"))) {
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(BACKUP_BASE);
4740
- handleWedge(REPO_HOME, forceRemote);
4791
+ const ts = freshBackupTs(backup);
4792
+ handleWedge(repo, forceRemote);
4741
4793
  if (!dryRun) {
4742
- const backupRoot = join40(BACKUP_BASE, ts);
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", REPO_HOME);
4753
- const mapPath = join40(REPO_HOME, "path-map.json");
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(CLAUDE_HOME, "projects");
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(REPO_HOME, ".gitleaksignore");
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(REPO_HOME, "shared"));
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(REPO_HOME, p);
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", REPO_HOME);
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", REPO_HOME);
4959
- withSpinner("Pushing", () => gitOrFatal(["push"], "git push", REPO_HOME));
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
- if (!existsSync36(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
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(BACKUP_BASE);
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(REPO_HOME, { untrackedAll: true });
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(REPO_HOME, "path-map.json");
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
- if (!existsSync37(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
5059
- const ts = freshBackupTs(BACKUP_BASE);
5060
- const mapPath = join43(REPO_HOME, "path-map.json");
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(REPO_HOME, run);
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: REPO_HOME, stdio: ["ignore", "ignore", "pipe"] });
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: REPO_HOME,
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(CLAUDE_HOME, name);
5219
+ const src = join44(claude, name);
5162
5220
  if (!existsSync38(src)) continue;
5163
- const dst = join44(REPO_HOME, "shared", name);
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(CLAUDE_HOME, "settings.json");
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(REPO_HOME, "hosts", `${HOST}.json`);
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(repoHome) {
5250
+ function preflightConflict(repoHome2) {
5193
5251
  const candidates = [
5194
- join45(repoHome, "shared", "settings.base.json"),
5195
- join45(repoHome, "shared", "CLAUDE.md"),
5196
- join45(repoHome, "path-map.json"),
5197
- join45(repoHome, "hosts"),
5198
- join45(repoHome, "shared")
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
- mkdirSync10(REPO_HOME, { recursive: true });
5209
- const conflict = preflightConflict(REPO_HOME);
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(REPO_HOME, "shared"), { recursive: true });
5215
- mkdirSync10(join45(REPO_HOME, "hosts"), { recursive: true });
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(REPO_HOME, "shared", name), { recursive: true });
5277
+ mkdirSync10(join45(repo, "shared", name), { recursive: true });
5218
5278
  }
5219
- const userClaudeMd = join45(CLAUDE_HOME, "CLAUDE.md");
5279
+ const userClaudeMd = join45(claude, "CLAUDE.md");
5220
5280
  if (!snapshot || !existsSync39(userClaudeMd)) {
5221
- writeFileSync6(join45(REPO_HOME, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
5222
- log("created shared/CLAUDE.md");
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(REPO_HOME, "shared", name, ".gitkeep"), "");
5226
- log(`created shared/${name}/.gitkeep`);
5227
- }
5228
- writeFileSync6(join45(REPO_HOME, "hosts", ".gitkeep"), "");
5229
- log("created hosts/.gitkeep");
5230
- writeJsonAtomic(join45(REPO_HOME, "shared", "settings.base.json"), {});
5231
- log("created shared/settings.base.json");
5232
- writeJsonAtomic(join45(REPO_HOME, "path-map.json"), { projects: {} });
5233
- log("created path-map.json");
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(REPO_HOME, opts.run);
5300
+ maybeDisableRepoActions(repo, opts.run);
5241
5301
  }
5242
5302
  log("init complete");
5243
5303
  }
5244
- function maybeDisableRepoActions(repoHome, run) {
5304
+ function maybeDisableRepoActions(repoHome2, run) {
5245
5305
  let remote;
5246
5306
  try {
5247
- remote = readOriginRemote(repoHome, run);
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.0",
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(CLAUDE_HOME, "projects");
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(REPO_HOME, "path-map.json");
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
- if (!HOME) {
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
  );