claude-nomad 0.43.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/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 });
@@ -561,31 +571,32 @@ var init_utils_fs = __esm({
561
571
  });
562
572
 
563
573
  // src/push-gitleaks.config.ts
564
- import { existsSync as existsSync10, mkdtempSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
574
+ import { existsSync as existsSync11, mkdtempSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
565
575
  import { tmpdir } from "node:os";
566
- import { join as join11 } from "node:path";
576
+ import { join as join12 } from "node:path";
567
577
  import { fileURLToPath } from "node:url";
568
- function resolveTomlPath() {
569
- const repoToml = join11(REPO_HOME, ".gitleaks.toml");
570
- if (existsSync10(repoToml)) return repoToml;
578
+ function resolveTomlPath(repo = repoHome()) {
579
+ const repoToml = join12(repo, ".gitleaks.toml");
580
+ if (existsSync11(repoToml)) return repoToml;
571
581
  const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
572
- return existsSync10(bundled) ? bundled : null;
582
+ return existsSync11(bundled) ? bundled : null;
573
583
  }
574
584
  function buildOverlayTempConfig(overlayBody, bundled) {
575
585
  const tempBody = `[extend]
576
586
  path = ${JSON.stringify(bundled)}
577
587
 
578
588
  ${overlayBody}`;
579
- const tempPath = mkdtempSync(join11(tmpdir(), "nomad-gitleaks-cfg-"));
580
- const configPath = join11(tempPath, "config.toml");
589
+ const tempPath = mkdtempSync(join12(tmpdir(), "nomad-gitleaks-cfg-"));
590
+ const configPath = join12(tempPath, "config.toml");
581
591
  writeFileSync2(configPath, tempBody, { mode: 384, flag: "wx" });
582
592
  return { configPath, tempPath };
583
593
  }
584
594
  function resolveTomlConfig() {
585
- const overlayPath = join11(REPO_HOME, ".gitleaks.overlay.toml");
586
- const repoToml = join11(REPO_HOME, ".gitleaks.toml");
587
- const bundled = resolveTomlPath();
588
- if (!existsSync10(overlayPath)) {
595
+ const repo = repoHome();
596
+ const overlayPath = join12(repo, ".gitleaks.overlay.toml");
597
+ const repoToml = join12(repo, ".gitleaks.toml");
598
+ const bundled = resolveTomlPath(repo);
599
+ if (!existsSync11(overlayPath)) {
589
600
  return { path: bundled, tempPath: null };
590
601
  }
591
602
  if (bundled === repoToml) {
@@ -626,9 +637,9 @@ var init_push_gitleaks_config = __esm({
626
637
 
627
638
  // src/push-checks.ts
628
639
  import { execFileSync as execFileSync2 } from "node:child_process";
629
- import { readdirSync as readdirSync4, rmSync as rmSync3 } from "node:fs";
640
+ import { readdirSync as readdirSync4, rmSync as rmSync4 } from "node:fs";
630
641
  import { homedir as homedir2, platform } from "node:os";
631
- import { join as join12 } from "node:path";
642
+ import { join as join13 } from "node:path";
632
643
  function gitleaksInstallHint() {
633
644
  const head = "gitleaks not on PATH (required for nomad push). Install:";
634
645
  const plat = platform();
@@ -671,7 +682,7 @@ function findGitlinks(dir) {
671
682
  return;
672
683
  }
673
684
  for (const e of entries) {
674
- const p = join12(current, e.name);
685
+ const p = join13(current, e.name);
675
686
  if (e.name === ".git") {
676
687
  hits.push(p);
677
688
  continue;
@@ -693,27 +704,26 @@ function probeGitleaks() {
693
704
  if (e.code === "ENOENT") throw new NomadFatal(gitleaksInstallHint());
694
705
  throw new NomadFatal(`gitleaks --version failed: ${e.message}`);
695
706
  } finally {
696
- if (tempPath !== null) rmSync3(tempPath, { recursive: true, force: true });
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
  }
@@ -721,9 +731,9 @@ var init_push_checks = __esm({
721
731
 
722
732
  // src/push-gitleaks.scan.ts
723
733
  import { execFileSync as execFileSync5 } from "node:child_process";
724
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync4 } from "node:fs";
734
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync5 } from "node:fs";
725
735
  import { homedir as homedir3 } from "node:os";
726
- import { join as join16 } from "node:path";
736
+ import { join as join17 } from "node:path";
727
737
  function readGitleaksReport(reportPath) {
728
738
  try {
729
739
  const raw = readFileSync4(reportPath, "utf8");
@@ -735,9 +745,9 @@ function readGitleaksReport(reportPath) {
735
745
  }
736
746
  }
737
747
  function scanStagedTree(repoDir, forwardStreams = false) {
738
- const cacheDir = join16(homedir3(), ".cache", "claude-nomad");
748
+ const cacheDir = join17(homedir3(), ".cache", "claude-nomad");
739
749
  mkdirSync2(cacheDir, { recursive: true });
740
- const reportPath = join16(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
750
+ const reportPath = join17(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
741
751
  const { path: toml, tempPath } = resolveTomlConfig();
742
752
  const args = [
743
753
  "protect",
@@ -764,14 +774,14 @@ function scanStagedTree(repoDir, forwardStreams = false) {
764
774
  }
765
775
  return report;
766
776
  } finally {
767
- if (tempPath !== null) rmSync4(tempPath, { recursive: true, force: true });
768
- rmSync4(reportPath, { force: true });
777
+ if (tempPath !== null) rmSync5(tempPath, { recursive: true, force: true });
778
+ rmSync5(reportPath, { force: true });
769
779
  }
770
780
  }
771
781
  function scanFile(filePath, forwardStreams = false) {
772
- const cacheDir = join16(homedir3(), ".cache", "claude-nomad");
782
+ const cacheDir = join17(homedir3(), ".cache", "claude-nomad");
773
783
  mkdirSync2(cacheDir, { recursive: true });
774
- const reportPath = join16(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
784
+ const reportPath = join17(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
775
785
  const { path: toml, tempPath } = resolveTomlConfig();
776
786
  const args = [
777
787
  "detect",
@@ -796,8 +806,8 @@ function scanFile(filePath, forwardStreams = false) {
796
806
  }
797
807
  return report;
798
808
  } finally {
799
- if (tempPath !== null) rmSync4(tempPath, { recursive: true, force: true });
800
- rmSync4(reportPath, { force: true });
809
+ if (tempPath !== null) rmSync5(tempPath, { recursive: true, force: true });
810
+ rmSync5(reportPath, { force: true });
801
811
  }
802
812
  }
803
813
  var init_push_gitleaks_scan = __esm({
@@ -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,26 +1221,193 @@ 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
 
1235
+ // src/commands.eject.ts
1236
+ init_config();
1237
+ init_utils();
1238
+ init_utils_json();
1239
+ import { cpSync as cpSync3, existsSync as existsSync5, lstatSync as lstatSync4, realpathSync, renameSync as renameSync2, rmSync as rmSync3 } from "node:fs";
1240
+ import { join as join6, sep } from "node:path";
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
+ }
1251
+ function errMessage(err) {
1252
+ return err instanceof Error ? err.message : String(err);
1253
+ }
1254
+ function lexists2(p) {
1255
+ try {
1256
+ lstatSync4(p);
1257
+ return true;
1258
+ } catch {
1259
+ return false;
1260
+ }
1261
+ }
1262
+ function readMapIfPresent2(repoHome2) {
1263
+ const mapPath = join6(repoHome2, "path-map.json");
1264
+ return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
1265
+ }
1266
+ function classifyName(linkPath) {
1267
+ if (!lexists2(linkPath)) return "absent";
1268
+ if (!lstatSync4(linkPath).isSymbolicLink()) return "skip-real";
1269
+ if (!existsSync5(linkPath)) return "dangling";
1270
+ return "materialize";
1271
+ }
1272
+ function resolveSharedRoot(repoHome2) {
1273
+ try {
1274
+ return realpathSync(join6(repoHome2, "shared"));
1275
+ } catch {
1276
+ return die(
1277
+ `cannot resolve ${join6(repoHome2, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
1278
+ );
1279
+ }
1280
+ }
1281
+ function isManagedTarget(target, sharedRoot) {
1282
+ return target.startsWith(sharedRoot + sep);
1283
+ }
1284
+ function materializeOne(name, linkPath, sharedRoot) {
1285
+ const target = realpathSync(linkPath);
1286
+ if (!isManagedTarget(target, sharedRoot)) {
1287
+ item(`skipped (not a nomad-managed target): ${name} -> ${target}`);
1288
+ return false;
1289
+ }
1290
+ const tmp = `${linkPath}.eject.tmp.${process.pid}.${Date.now()}`;
1291
+ try {
1292
+ rmSync3(tmp, { recursive: true, force: true });
1293
+ cpSync3(target, tmp, {
1294
+ recursive: true,
1295
+ force: true,
1296
+ dereference: true,
1297
+ preserveTimestamps: true
1298
+ });
1299
+ rmSync3(linkPath, { force: true });
1300
+ renameSync2(tmp, linkPath);
1301
+ item(`ejected: ${name}`);
1302
+ return true;
1303
+ } catch (err) {
1304
+ try {
1305
+ rmSync3(tmp, { recursive: true, force: true });
1306
+ } catch {
1307
+ }
1308
+ throw err;
1309
+ }
1310
+ }
1311
+ function previewDryRun(names, classifications, claudeHome2, sharedRoot) {
1312
+ for (const name of names) {
1313
+ const cls = classifications.get(name);
1314
+ const linkPath = join6(claudeHome2, name);
1315
+ if (cls === "absent") {
1316
+ item(`skipped (absent): ${name}`);
1317
+ } else if (cls === "skip-real") {
1318
+ item(`skipped (not a symlink): ${name}`);
1319
+ } else {
1320
+ previewMaterialize(name, linkPath, sharedRoot);
1321
+ }
1322
+ }
1323
+ log(ejectChecklist());
1324
+ }
1325
+ function previewMaterialize(name, linkPath, sharedRoot) {
1326
+ let target;
1327
+ try {
1328
+ target = realpathSync(linkPath);
1329
+ } catch {
1330
+ item(`would materialize: ${name} (target now unresolvable; re-run to re-classify)`);
1331
+ return;
1332
+ }
1333
+ if (!isManagedTarget(target, sharedRoot)) {
1334
+ item(`skipped (not a nomad-managed target): ${name} -> ${target}`);
1335
+ return;
1336
+ }
1337
+ item(`would materialize: ${name} (copy ${target} -> ${linkPath})`);
1338
+ }
1339
+ function runLiveEject(names, classifications, claudeHome2, sharedRoot) {
1340
+ const done = [];
1341
+ let skipped = 0;
1342
+ for (const name of names) {
1343
+ const cls = classifications.get(name);
1344
+ const linkPath = join6(claudeHome2, name);
1345
+ if (cls === "absent") {
1346
+ item(`skipped (absent): ${name}`);
1347
+ skipped++;
1348
+ } else if (cls === "skip-real") {
1349
+ item(`skipped (not a symlink): ${name}`);
1350
+ skipped++;
1351
+ } else if (materializeOneOrDie(name, linkPath, sharedRoot, done)) {
1352
+ done.push(name);
1353
+ } else {
1354
+ skipped++;
1355
+ }
1356
+ }
1357
+ log(`materialized ${done.length}, skipped ${skipped}`);
1358
+ log(ejectChecklist());
1359
+ }
1360
+ function materializeOneOrDie(name, linkPath, sharedRoot, done) {
1361
+ try {
1362
+ return materializeOne(name, linkPath, sharedRoot);
1363
+ } catch (err) {
1364
+ const msg = errMessage(err);
1365
+ return die(
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)`
1367
+ );
1368
+ }
1369
+ }
1370
+ function defaultEjectRoots() {
1371
+ return { claudeHome: claudeHome(), repoHome: repoHome() };
1372
+ }
1373
+ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
1374
+ const dryRun = opts.dryRun === true;
1375
+ const { claudeHome: claudeHome2, repoHome: repoHome2 } = roots;
1376
+ const map = readMapIfPresent2(repoHome2);
1377
+ const names = allSharedLinks(map);
1378
+ const classifications = /* @__PURE__ */ new Map();
1379
+ for (const name of names) {
1380
+ classifications.set(name, classifyName(join6(claudeHome2, name)));
1381
+ }
1382
+ const dangling = names.filter((n) => classifications.get(n) === "dangling");
1383
+ if (dangling.length > 0) {
1384
+ fail(
1385
+ `dangling symlink(s): ${dangling.join(", ")}. run \`nomad pull\` first to restore the missing target, then re-run \`nomad eject\``
1386
+ );
1387
+ process.exit(1);
1388
+ }
1389
+ const sharedRoot = resolveSharedRoot(repoHome2);
1390
+ if (dryRun) {
1391
+ previewDryRun(names, classifications, claudeHome2, sharedRoot);
1392
+ return;
1393
+ }
1394
+ try {
1395
+ runLiveEject(names, classifications, claudeHome2, sharedRoot);
1396
+ } catch (err) {
1397
+ fail(errMessage(err));
1398
+ process.exit(1);
1399
+ }
1400
+ }
1401
+
1222
1402
  // src/commands.doctor.ts
1223
- import { existsSync as existsSync21 } from "node:fs";
1224
- import { join as join25 } from "node:path";
1403
+ import { existsSync as existsSync22 } from "node:fs";
1404
+ import { join as join26 } from "node:path";
1225
1405
 
1226
1406
  // src/commands.doctor.checks.repo.ts
1227
1407
  init_color();
1228
1408
  init_config();
1229
- import { existsSync as existsSync6, lstatSync as lstatSync4, statSync as statSync3 } from "node:fs";
1230
- import { join as join7 } from "node:path";
1409
+ import { existsSync as existsSync7, lstatSync as lstatSync5, statSync as statSync3 } from "node:fs";
1410
+ import { join as join8 } from "node:path";
1231
1411
 
1232
1412
  // src/commands.doctor.format.ts
1233
1413
  init_color();
@@ -1248,8 +1428,8 @@ function sectionFailed(s) {
1248
1428
  return s.items.some((line) => line.includes(failGlyph));
1249
1429
  }
1250
1430
  function renderRawItems(items) {
1251
- for (const item of items) {
1252
- console.log(item === "" ? "" : ` ${item}`);
1431
+ for (const item2 of items) {
1432
+ console.log(item2 === "" ? "" : ` ${item2}`);
1253
1433
  }
1254
1434
  }
1255
1435
  function renderSection(s) {
@@ -1261,18 +1441,18 @@ function renderSection(s) {
1261
1441
  const header = sectionFailed(s) ? `${red(FAIL_GLYPH_BARE)} ${s.header}` : s.header;
1262
1442
  console.log(header);
1263
1443
  const lastContent = s.items.reduce(
1264
- (acc, item, j) => item === "" || isChild(item) ? acc : j,
1444
+ (acc, item2, j) => item2 === "" || isChild(item2) ? acc : j,
1265
1445
  -1
1266
1446
  );
1267
1447
  for (let j = 0; j < s.items.length; j++) {
1268
- const item = s.items[j];
1269
- if (item === "") console.log("");
1270
- else if (isChild(item)) console.log(renderChildLine(s.items, j));
1271
- 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}`);
1272
1452
  }
1273
1453
  }
1274
- function isChild(item) {
1275
- return item.startsWith(" ");
1454
+ function isChild(item2) {
1455
+ return item2.startsWith(" ");
1276
1456
  }
1277
1457
  function renderChildLine(items, j) {
1278
1458
  const parentContinues = items.some((it, k) => k > j && it !== "" && !isChild(it));
@@ -1305,15 +1485,15 @@ function readJsonSafe(path, label, section2) {
1305
1485
  // src/init.classify.ts
1306
1486
  init_config();
1307
1487
  init_utils_json();
1308
- import { existsSync as existsSync5 } from "node:fs";
1309
- import { join as join6 } from "node:path";
1310
- function classifyRepoState(repoHome, host) {
1311
- const basePath = join6(repoHome, "shared", "settings.base.json");
1312
- const mapPath = join6(repoHome, "path-map.json");
1313
- const hostPath = join6(repoHome, "hosts", `${host}.json`);
1314
- const hasBase = existsSync5(basePath);
1315
- const hasMap = existsSync5(mapPath);
1316
- const hasHost = existsSync5(hostPath);
1488
+ import { existsSync as existsSync6 } from "node:fs";
1489
+ import { join as join7 } from "node:path";
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`);
1494
+ const hasBase = existsSync6(basePath);
1495
+ const hasMap = existsSync6(mapPath);
1496
+ const hasHost = existsSync6(hostPath);
1317
1497
  let mapEntryCount = 0;
1318
1498
  if (hasMap) {
1319
1499
  try {
@@ -1327,12 +1507,12 @@ function classifyRepoState(repoHome, host) {
1327
1507
  if (hasBase && mapEntryCount > 0 && hasHost) return "populated";
1328
1508
  return "partial";
1329
1509
  }
1330
- function reasonForPartial(repoHome, host) {
1331
- const basePath = join6(repoHome, "shared", "settings.base.json");
1332
- const mapPath = join6(repoHome, "path-map.json");
1333
- const hostPath = join6(repoHome, "hosts", `${host}.json`);
1334
- if (!existsSync5(basePath)) return "- shared/settings.base.json missing";
1335
- if (!existsSync5(mapPath)) return "- path-map.json missing";
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`);
1514
+ if (!existsSync6(basePath)) return "- shared/settings.base.json missing";
1515
+ if (!existsSync6(mapPath)) return "- path-map.json missing";
1336
1516
  let mapEntryCount;
1337
1517
  try {
1338
1518
  const map = readJson(mapPath);
@@ -1341,7 +1521,7 @@ function reasonForPartial(repoHome, host) {
1341
1521
  mapEntryCount = 0;
1342
1522
  }
1343
1523
  if (mapEntryCount === 0) return "- path-map.json.projects has no entries";
1344
- if (!existsSync5(hostPath)) return `- hosts/${host}.json missing`;
1524
+ if (!existsSync6(hostPath)) return `- hosts/${host}.json missing`;
1345
1525
  return "- partial state (unknown gap)";
1346
1526
  }
1347
1527
 
@@ -1351,28 +1531,28 @@ function isOverrideActive() {
1351
1531
  }
1352
1532
  function reportHostAndPaths(section2) {
1353
1533
  const unsetHint = process.env.NOMAD_HOST ? "" : dim(" (env unset, using hostname)");
1534
+ const repo = repoHome();
1535
+ const claude = claudeHome();
1354
1536
  addItem(section2, `${dim(infoGlyph)} NOMAD_HOST: ${cyan(HOST)}${unsetHint}`);
1355
1537
  if (isOverrideActive()) {
1356
- addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(REPO_HOME)}`);
1538
+ addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(repo)}`);
1357
1539
  }
1540
+ addItem(section2, `${existsSync7(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
1358
1541
  addItem(
1359
1542
  section2,
1360
- `${existsSync6(REPO_HOME) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(REPO_HOME)}`
1361
- );
1362
- addItem(
1363
- section2,
1364
- `${existsSync6(CLAUDE_HOME) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(CLAUDE_HOME)}`
1543
+ `${existsSync7(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
1365
1544
  );
1366
1545
  }
1367
1546
  function reportRepoState(section2) {
1368
- const state = classifyRepoState(REPO_HOME, HOST);
1547
+ const repo = repoHome();
1548
+ const state = classifyRepoState(repo, HOST);
1369
1549
  const overrideLabel = isOverrideActive() ? " (NOMAD_REPO)" : "";
1370
1550
  if (state === "populated") {
1371
1551
  addItem(section2, `${green(okGlyph)} repo state: populated${overrideLabel}`);
1372
1552
  } else if (state === "partial") {
1373
1553
  addItem(
1374
1554
  section2,
1375
- `${yellow(warnGlyph)} repo state: partial ${reasonForPartial(REPO_HOME, HOST)}${overrideLabel}`
1555
+ `${yellow(warnGlyph)} repo state: partial ${reasonForPartial(repo, HOST)}${overrideLabel}`
1376
1556
  );
1377
1557
  } else {
1378
1558
  addItem(
@@ -1383,12 +1563,12 @@ function reportRepoState(section2) {
1383
1563
  }
1384
1564
  }
1385
1565
  function repoHasSharedSource(name) {
1386
- return existsSync6(join7(REPO_HOME, "shared", name));
1566
+ return existsSync7(join8(repoHome(), "shared", name));
1387
1567
  }
1388
1568
  function classifySharedLink(name, p) {
1389
1569
  let stat;
1390
1570
  try {
1391
- stat = lstatSync4(p);
1571
+ stat = lstatSync5(p);
1392
1572
  } catch (err) {
1393
1573
  const code = err.code;
1394
1574
  if (code === "ENOENT") {
@@ -1429,8 +1609,9 @@ function classifySymlinkTarget(name, p) {
1429
1609
  }
1430
1610
  }
1431
1611
  function reportSharedLinks(section2, map) {
1612
+ const claude = claudeHome();
1432
1613
  for (const name of allSharedLinks(map)) {
1433
- const p = join7(CLAUDE_HOME, name);
1614
+ const p = join8(claude, name);
1434
1615
  const { line, fail: fail2 } = classifySharedLink(name, p);
1435
1616
  addItem(section2, line);
1436
1617
  if (fail2) process.exitCode = 1;
@@ -1440,11 +1621,11 @@ function reportSharedLinks(section2, map) {
1440
1621
  // src/commands.doctor.checks.settings.ts
1441
1622
  init_color();
1442
1623
  init_config();
1443
- import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
1444
- import { join as join8 } from "node:path";
1624
+ import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
1625
+ import { join as join9 } from "node:path";
1445
1626
  function loadBaseSettings(section2) {
1446
- const basePath = join8(REPO_HOME, "shared", "settings.base.json");
1447
- if (!existsSync7(basePath)) {
1627
+ const basePath = join9(repoHome(), "shared", "settings.base.json");
1628
+ if (!existsSync8(basePath)) {
1448
1629
  addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
1449
1630
  process.exitCode = 1;
1450
1631
  return null;
@@ -1452,8 +1633,8 @@ function loadBaseSettings(section2) {
1452
1633
  return readJsonSafe(basePath, basePath, section2);
1453
1634
  }
1454
1635
  function loadAndReportSettings(section2) {
1455
- const settingsPath = join8(CLAUDE_HOME, "settings.json");
1456
- if (!existsSync7(settingsPath)) return null;
1636
+ const settingsPath = join9(claudeHome(), "settings.json");
1637
+ if (!existsSync8(settingsPath)) return null;
1457
1638
  const settings = readJsonSafe(settingsPath, settingsPath, section2);
1458
1639
  if (settings === null) return null;
1459
1640
  const unknownKeys = Object.keys(settings).filter((k) => !KNOWN_SETTINGS_KEYS.has(k));
@@ -1468,13 +1649,14 @@ function loadAndReportSettings(section2) {
1468
1649
  return settings;
1469
1650
  }
1470
1651
  function reportHostOverrides(section2, base, settings) {
1471
- const hostFile = join8(REPO_HOME, "hosts", `${HOST}.json`);
1652
+ const repo = repoHome();
1653
+ const hostFile = join9(repo, "hosts", `${HOST}.json`);
1472
1654
  let drift = [];
1473
1655
  if (base !== null && settings !== null) {
1474
1656
  const baseKeys = new Set(Object.keys(base));
1475
1657
  drift = Object.keys(settings).filter((k) => !baseKeys.has(k));
1476
1658
  }
1477
- if (existsSync7(hostFile)) {
1659
+ if (existsSync8(hostFile)) {
1478
1660
  if (readJsonSafe(hostFile, hostFile, section2) !== null) {
1479
1661
  addItem(section2, `${green(okGlyph)} host overrides: ${blue(hostFile)}`);
1480
1662
  }
@@ -1483,8 +1665,8 @@ function reportHostOverrides(section2, base, settings) {
1483
1665
  section2,
1484
1666
  `${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
1485
1667
  );
1486
- const hostsDir = join8(REPO_HOME, "hosts");
1487
- if (existsSync7(hostsDir)) {
1668
+ const hostsDir = join9(repo, "hosts");
1669
+ if (existsSync8(hostsDir)) {
1488
1670
  const cands = readdirSync2(hostsDir).filter((f) => f.endsWith(".json"));
1489
1671
  if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
1490
1672
  }
@@ -1500,8 +1682,8 @@ function reportHostOverrides(section2, base, settings) {
1500
1682
  // src/commands.doctor.checks.pathmap.ts
1501
1683
  init_color();
1502
1684
  init_config();
1503
- import { existsSync as existsSync8, readdirSync as readdirSync3 } from "node:fs";
1504
- import { join as join9 } from "node:path";
1685
+ import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
1686
+ import { join as join10 } from "node:path";
1505
1687
  init_utils_json();
1506
1688
  function reportMappedProjects(section2, map) {
1507
1689
  const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
@@ -1511,8 +1693,8 @@ function reportMappedProjects(section2, map) {
1511
1693
  }
1512
1694
  }
1513
1695
  function reportUnmappedProjects(section2, map) {
1514
- const localProjects = join9(CLAUDE_HOME, "projects");
1515
- if (!existsSync8(localProjects)) return;
1696
+ const localProjects = join10(claudeHome(), "projects");
1697
+ if (!existsSync9(localProjects)) return;
1516
1698
  let localDirs;
1517
1699
  try {
1518
1700
  localDirs = readdirSync3(localProjects);
@@ -1552,8 +1734,8 @@ function reportPathCollisions(section2, map) {
1552
1734
  else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
1553
1735
  }
1554
1736
  function reportPathMap(section2) {
1555
- const mapPath = join9(REPO_HOME, "path-map.json");
1556
- if (!existsSync8(mapPath)) {
1737
+ const mapPath = join10(repoHome(), "path-map.json");
1738
+ if (!existsSync9(mapPath)) {
1557
1739
  addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
1558
1740
  process.exitCode = 1;
1559
1741
  return;
@@ -1606,16 +1788,16 @@ function reportNeverSync(section2) {
1606
1788
  init_color();
1607
1789
  init_config();
1608
1790
  import { execFileSync as execFileSync3 } from "node:child_process";
1609
- import { existsSync as existsSync11 } from "node:fs";
1610
- import { join as join13, relative as relative2 } from "node:path";
1791
+ import { existsSync as existsSync12 } from "node:fs";
1792
+ import { join as join14, relative as relative2 } from "node:path";
1611
1793
 
1612
1794
  // src/commands.pull.wedge.ts
1613
- import { existsSync as existsSync9 } from "node:fs";
1614
- import { join as join10 } from "node:path";
1795
+ import { existsSync as existsSync10 } from "node:fs";
1796
+ import { join as join11 } from "node:path";
1615
1797
  function detectWedge(repo) {
1616
- const g = join10(repo, ".git");
1617
- if (existsSync9(join10(g, "rebase-merge")) || existsSync9(join10(g, "rebase-apply"))) return "rebase";
1618
- if (existsSync9(join10(g, "MERGE_HEAD"))) return "merge";
1798
+ const g = join11(repo, ".git");
1799
+ if (existsSync10(join11(g, "rebase-merge")) || existsSync10(join11(g, "rebase-apply"))) return "rebase";
1800
+ if (existsSync10(join11(g, "MERGE_HEAD"))) return "merge";
1619
1801
  return null;
1620
1802
  }
1621
1803
 
@@ -1640,11 +1822,12 @@ function reportGitleaksProbe(section2) {
1640
1822
  }
1641
1823
  }
1642
1824
  function reportGitlinks(section2) {
1643
- const sharedDir = join13(REPO_HOME, "shared");
1644
- if (existsSync11(sharedDir)) {
1825
+ const repo = repoHome();
1826
+ const sharedDir = join14(repo, "shared");
1827
+ if (existsSync12(sharedDir)) {
1645
1828
  const gitlinks = findGitlinks(sharedDir);
1646
1829
  for (const p of gitlinks) {
1647
- const rel = relative2(REPO_HOME, p);
1830
+ const rel = relative2(repo, p);
1648
1831
  addItem(
1649
1832
  section2,
1650
1833
  `${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
@@ -1660,7 +1843,7 @@ function reportGitlinks(section2) {
1660
1843
  function reportRemote(section2) {
1661
1844
  try {
1662
1845
  const url = execFileSync3("git", ["remote", "get-url", "origin"], {
1663
- cwd: REPO_HOME,
1846
+ cwd: repoHome(),
1664
1847
  stdio: ["ignore", "pipe", "pipe"]
1665
1848
  }).toString().trim();
1666
1849
  addItem(section2, `${dim(infoGlyph)} remote origin: ${cyan(url)}`);
@@ -1670,7 +1853,7 @@ function reportRemote(section2) {
1670
1853
  }
1671
1854
  function reportRebaseClean(section2) {
1672
1855
  try {
1673
- const status = gitStatusPorcelainZ(REPO_HOME);
1856
+ const status = gitStatusPorcelainZ(repoHome());
1674
1857
  if (status.length > 0) {
1675
1858
  addItem(
1676
1859
  section2,
@@ -1682,7 +1865,7 @@ function reportRebaseClean(section2) {
1682
1865
  }
1683
1866
  function reportRebaseState(section2) {
1684
1867
  try {
1685
- const wedge = detectWedge(REPO_HOME);
1868
+ const wedge = detectWedge(repoHome());
1686
1869
  if (wedge !== null) {
1687
1870
  const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
1688
1871
  addItem(
@@ -1697,8 +1880,8 @@ function reportRebaseState(section2) {
1697
1880
 
1698
1881
  // src/commands.doctor.checks.backups.ts
1699
1882
  init_color();
1700
- import { existsSync as existsSync12, lstatSync as lstatSync5, readdirSync as readdirSync5 } from "node:fs";
1701
- import { join as join14 } from "node:path";
1883
+ import { existsSync as existsSync13, lstatSync as lstatSync6, readdirSync as readdirSync5 } from "node:fs";
1884
+ import { join as join15 } from "node:path";
1702
1885
  init_config();
1703
1886
  var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
1704
1887
  function safeReaddir(dir) {
@@ -1714,8 +1897,8 @@ var BYTES_PER_MB = 1024 * 1024;
1714
1897
  function dirSizeBytes(dir) {
1715
1898
  let bytes = 0;
1716
1899
  for (const entry of safeReaddir(dir)) {
1717
- const full = join14(dir, entry);
1718
- const st = lstatSync5(full, { throwIfNoEntry: false });
1900
+ const full = join15(dir, entry);
1901
+ const st = lstatSync6(full, { throwIfNoEntry: false });
1719
1902
  if (!st) continue;
1720
1903
  if (st.isSymbolicLink()) continue;
1721
1904
  if (st.isDirectory()) bytes += dirSizeBytes(full);
@@ -1723,16 +1906,16 @@ function dirSizeBytes(dir) {
1723
1906
  }
1724
1907
  return bytes;
1725
1908
  }
1726
- function totalSizeMb(backupBase, dirs) {
1909
+ function totalSizeMb(backupBase2, dirs) {
1727
1910
  let bytes = 0;
1728
- for (const name of dirs) bytes += dirSizeBytes(join14(backupBase, name));
1911
+ for (const name of dirs) bytes += dirSizeBytes(join15(backupBase2, name));
1729
1912
  return bytes / BYTES_PER_MB;
1730
1913
  }
1731
- function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
1732
- if (!existsSync12(backupBase)) return;
1733
- 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));
1734
1917
  const count = dirs.length;
1735
- const sizeMb = totalSizeMb(backupBase, dirs);
1918
+ const sizeMb = totalSizeMb(backupBase2, dirs);
1736
1919
  if (count > DOCTOR_BACKUP_COUNT_WARN || sizeMb > DOCTOR_BACKUP_SIZE_WARN_MB) {
1737
1920
  addItem(
1738
1921
  section2,
@@ -1743,8 +1926,8 @@ function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
1743
1926
 
1744
1927
  // src/commands.doctor.check-schema.ts
1745
1928
  init_color();
1746
- import { existsSync as existsSync13 } from "node:fs";
1747
- import { join as join15 } from "node:path";
1929
+ import { existsSync as existsSync14 } from "node:fs";
1930
+ import { join as join16 } from "node:path";
1748
1931
  init_config();
1749
1932
 
1750
1933
  // src/http-fetch.ts
@@ -1781,8 +1964,8 @@ function fetchSchemaKeys() {
1781
1964
  }
1782
1965
  }
1783
1966
  function reportCheckSchema(section2) {
1784
- const settingsPath = join15(CLAUDE_HOME, "settings.json");
1785
- if (!existsSync13(settingsPath)) {
1967
+ const settingsPath = join16(claudeHome(), "settings.json");
1968
+ if (!existsSync14(settingsPath)) {
1786
1969
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
1787
1970
  return;
1788
1971
  }
@@ -1812,18 +1995,18 @@ function reportCheckSchema(section2) {
1812
1995
  init_color();
1813
1996
  import { randomBytes } from "node:crypto";
1814
1997
  import { execFileSync as execFileSync6 } from "node:child_process";
1815
- import { existsSync as existsSync15, mkdirSync as mkdirSync4, readdirSync as readdirSync7, rmSync as rmSync6 } from "node:fs";
1998
+ import { existsSync as existsSync16, mkdirSync as mkdirSync4, readdirSync as readdirSync7, rmSync as rmSync7 } from "node:fs";
1816
1999
  import { homedir as homedir4 } from "node:os";
1817
- import { join as join19 } from "node:path";
2000
+ import { join as join20 } from "node:path";
1818
2001
 
1819
2002
  // src/commands.doctor.check-shared.scan.ts
1820
2003
  init_color();
1821
- import { join as join17 } from "node:path";
2004
+ import { join as join18 } from "node:path";
1822
2005
  init_config();
1823
2006
  init_push_gitleaks();
1824
2007
  function scrubPath(logical, sid, logicalToEncoded) {
1825
2008
  const encoded = logicalToEncoded.get(logical) ?? logical;
1826
- return join17(CLAUDE_HOME, "projects", encoded, `${sid}.jsonl`);
2009
+ return join18(claudeHome(), "projects", encoded, `${sid}.jsonl`);
1827
2010
  }
1828
2011
  function reportSessionFindings(section2, bySession) {
1829
2012
  for (const [sid, counts] of bySession) {
@@ -1915,24 +2098,24 @@ init_config();
1915
2098
  init_utils();
1916
2099
  init_utils_fs();
1917
2100
  init_utils_json();
1918
- import { cpSync as cpSync3, existsSync as existsSync14, mkdirSync as mkdirSync3, readdirSync as readdirSync6, rmSync as rmSync5, statSync as statSync4 } from "node:fs";
1919
- import { join as join18, relative as relative3, sep } from "node:path";
2101
+ import { cpSync as cpSync4, existsSync as existsSync15, mkdirSync as mkdirSync3, readdirSync as readdirSync6, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
2102
+ import { join as join19, relative as relative3, sep as sep2 } from "node:path";
1920
2103
  function copyDir(src, dst) {
1921
- rmSync5(dst, { recursive: true, force: true });
1922
- cpSync3(src, dst, { recursive: true, force: true });
2104
+ rmSync6(dst, { recursive: true, force: true });
2105
+ cpSync4(src, dst, { recursive: true, force: true });
1923
2106
  }
1924
2107
  function copyDirJsonlOnly(src, dst) {
1925
- rmSync5(dst, { recursive: true, force: true });
1926
- cpSync3(src, dst, {
2108
+ rmSync6(dst, { recursive: true, force: true });
2109
+ cpSync4(src, dst, {
1927
2110
  recursive: true,
1928
2111
  force: true,
1929
2112
  filter: (srcPath) => {
1930
2113
  const rel = relative3(src, srcPath);
1931
2114
  if (rel === "") return true;
1932
- if (rel.split(sep).length > 1) return true;
2115
+ if (rel.split(sep2).length > 1) return true;
1933
2116
  if (statSync4(srcPath).isDirectory()) return true;
1934
2117
  if (srcPath.endsWith(".jsonl")) return true;
1935
- log(`skip ${rel}: extension not in allowlist`);
2118
+ item(`skip ${rel}: extension not in allowlist`);
1936
2119
  return false;
1937
2120
  }
1938
2121
  });
@@ -1946,15 +2129,17 @@ function remapPull(ts, opts = {}) {
1946
2129
  let unmapped = 0;
1947
2130
  const pulled = [];
1948
2131
  const wouldPull = [];
1949
- const mapPath = join18(REPO_HOME, "path-map.json");
1950
- const repoProjects = join18(REPO_HOME, "shared", "projects");
1951
- if (!existsSync14(mapPath) || !existsSync14(repoProjects)) {
2132
+ const repo = repoHome();
2133
+ const claude = claudeHome();
2134
+ const mapPath = join19(repo, "path-map.json");
2135
+ const repoProjects = join19(repo, "shared", "projects");
2136
+ if (!existsSync15(mapPath) || !existsSync15(repoProjects)) {
1952
2137
  const text = "no path-map or repo projects dir; skipping session remap";
1953
2138
  emitPreview(opts.onPreview, { kind: "note", text }, text);
1954
2139
  return { unmapped: 0, pulled, wouldPull };
1955
2140
  }
1956
2141
  const map = readJson(mapPath);
1957
- const localProjects = join18(CLAUDE_HOME, "projects");
2142
+ const localProjects = join19(claude, "projects");
1958
2143
  if (!dryRun) mkdirSync3(localProjects, { recursive: true });
1959
2144
  for (const [logical, hosts] of Object.entries(map.projects)) {
1960
2145
  assertSafeLogical(logical);
@@ -1963,9 +2148,9 @@ function remapPull(ts, opts = {}) {
1963
2148
  unmapped++;
1964
2149
  continue;
1965
2150
  }
1966
- const src = join18(repoProjects, logical);
1967
- if (!existsSync14(src)) continue;
1968
- const dst = join18(localProjects, encodePath(localPath));
2151
+ const src = join19(repoProjects, logical);
2152
+ if (!existsSync15(src)) continue;
2153
+ const dst = join19(localProjects, encodePath(localPath));
1969
2154
  if (dryRun) {
1970
2155
  wouldPull.push(logical);
1971
2156
  emitPreview(
@@ -2010,16 +2195,18 @@ function remapPush(ts, opts = {}) {
2010
2195
  let unmapped = 0;
2011
2196
  const pushed = [];
2012
2197
  const wouldPush = [];
2013
- const mapPath = join18(REPO_HOME, "path-map.json");
2014
- if (!existsSync14(mapPath)) {
2198
+ const repo = repoHome();
2199
+ const claude = claudeHome();
2200
+ const mapPath = join19(repo, "path-map.json");
2201
+ if (!existsSync15(mapPath)) {
2015
2202
  log("no path-map.json; skipping session export");
2016
2203
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
2017
2204
  }
2018
2205
  const map = readJson(mapPath);
2019
- const localProjects = join18(CLAUDE_HOME, "projects");
2020
- const repoProjects = join18(REPO_HOME, "shared", "projects");
2206
+ const localProjects = join19(claude, "projects");
2207
+ const repoProjects = join19(repo, "shared", "projects");
2021
2208
  const reverse = buildReverseMap(map);
2022
- if (!existsSync14(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
2209
+ if (!existsSync15(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
2023
2210
  if (!dryRun) mkdirSync3(repoProjects, { recursive: true });
2024
2211
  for (const dir of readdirSync6(localProjects)) {
2025
2212
  const logical = reverse.get(dir);
@@ -2027,13 +2214,13 @@ function remapPush(ts, opts = {}) {
2027
2214
  unmapped++;
2028
2215
  continue;
2029
2216
  }
2030
- const repoDst = join18(repoProjects, logical);
2217
+ const repoDst = join19(repoProjects, logical);
2031
2218
  if (dryRun) {
2032
2219
  wouldPush.push(logical);
2033
2220
  continue;
2034
2221
  }
2035
- backupRepoWrite(repoDst, ts, REPO_HOME);
2036
- copyDirJsonlOnly(join18(localProjects, dir), repoDst);
2222
+ backupRepoWrite(repoDst, ts, repo);
2223
+ copyDirJsonlOnly(join19(localProjects, dir), repoDst);
2037
2224
  pushed.push(logical);
2038
2225
  }
2039
2226
  return { unmapped, collisions: 0, pushed, wouldPush };
@@ -2045,8 +2232,9 @@ init_utils_json();
2045
2232
  function buildScanTree(tmpRoot) {
2046
2233
  const logicalToEncoded = /* @__PURE__ */ new Map();
2047
2234
  let staged = 0;
2048
- const mapPath = join19(REPO_HOME, "path-map.json");
2049
- if (!existsSync15(mapPath)) return { logicalToEncoded, staged, malformed: false };
2235
+ const repo = repoHome();
2236
+ const mapPath = join20(repo, "path-map.json");
2237
+ if (!existsSync16(mapPath)) return { logicalToEncoded, staged, malformed: false };
2050
2238
  let map;
2051
2239
  try {
2052
2240
  map = readJson(mapPath);
@@ -2063,12 +2251,12 @@ function buildScanTree(tmpRoot) {
2063
2251
  if (!p || p === "TBD") continue;
2064
2252
  reverse.set(encodePath(p), logical);
2065
2253
  }
2066
- const localProjects = join19(CLAUDE_HOME, "projects");
2067
- if (!existsSync15(localProjects)) return { logicalToEncoded, staged, malformed: false };
2254
+ const localProjects = join20(claudeHome(), "projects");
2255
+ if (!existsSync16(localProjects)) return { logicalToEncoded, staged, malformed: false };
2068
2256
  for (const dir of readdirSync7(localProjects)) {
2069
2257
  const logical = reverse.get(dir);
2070
2258
  if (!logical) continue;
2071
- copyDirJsonlOnly(join19(localProjects, dir), join19(tmpRoot, "shared", "projects", logical));
2259
+ copyDirJsonlOnly(join20(localProjects, dir), join20(tmpRoot, "shared", "projects", logical));
2072
2260
  logicalToEncoded.set(logical, dir);
2073
2261
  staged++;
2074
2262
  }
@@ -2099,11 +2287,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
2099
2287
  }
2100
2288
  function reportCheckShared(section2, gitleaksReady) {
2101
2289
  if (!ensureGitleaksReady(section2, gitleaksReady)) return;
2102
- const cacheDir = join19(homedir4(), ".cache", "claude-nomad");
2290
+ const cacheDir = join20(homedir4(), ".cache", "claude-nomad");
2103
2291
  mkdirSync4(cacheDir, { recursive: true });
2104
2292
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
2105
- const reportPath = join19(cacheDir, `check-shared-${stamp}.json`);
2106
- const tmpRoot = join19(cacheDir, `check-shared-tree-${stamp}`);
2293
+ const reportPath = join20(cacheDir, `check-shared-${stamp}.json`);
2294
+ const tmpRoot = join20(cacheDir, `check-shared-tree-${stamp}`);
2107
2295
  try {
2108
2296
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
2109
2297
  if (malformed) {
@@ -2117,15 +2305,15 @@ function reportCheckShared(section2, gitleaksReady) {
2117
2305
  }
2118
2306
  scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
2119
2307
  } finally {
2120
- rmSync6(reportPath, { force: true });
2121
- rmSync6(tmpRoot, { recursive: true, force: true });
2308
+ rmSync7(reportPath, { force: true });
2309
+ rmSync7(tmpRoot, { recursive: true, force: true });
2122
2310
  }
2123
2311
  }
2124
2312
 
2125
2313
  // src/commands.doctor.checks.hooks.scope.ts
2126
2314
  init_color();
2127
- import { existsSync as existsSync16, readFileSync as readFileSync5, readdirSync as readdirSync8, realpathSync } from "node:fs";
2128
- import { dirname as dirname2, extname, join as join20 } from "node:path";
2315
+ import { existsSync as existsSync17, readFileSync as readFileSync5, readdirSync as readdirSync8, realpathSync as realpathSync2 } from "node:fs";
2316
+ import { dirname as dirname2, extname, join as join21 } from "node:path";
2129
2317
  init_config();
2130
2318
  function typeFromPackageJson(pkgPath) {
2131
2319
  try {
@@ -2141,14 +2329,14 @@ function effectiveType(hookPath) {
2141
2329
  if (ext === ".cjs") return "cjs";
2142
2330
  let real;
2143
2331
  try {
2144
- real = realpathSync(hookPath);
2332
+ real = realpathSync2(hookPath);
2145
2333
  } catch {
2146
2334
  return null;
2147
2335
  }
2148
2336
  let dir = dirname2(real);
2149
2337
  for (; ; ) {
2150
- const pkg = join20(dir, "package.json");
2151
- if (existsSync16(pkg)) return typeFromPackageJson(pkg);
2338
+ const pkg = join21(dir, "package.json");
2339
+ if (existsSync17(pkg)) return typeFromPackageJson(pkg);
2152
2340
  const parent = dirname2(dir);
2153
2341
  if (parent === dir) return "cjs";
2154
2342
  dir = parent;
@@ -2190,15 +2378,15 @@ function safeRead(path) {
2190
2378
  }
2191
2379
  }
2192
2380
  function reportHookScopeCheck(section2) {
2193
- const hooksDir = join20(CLAUDE_HOME, "hooks");
2194
- if (!existsSync16(hooksDir)) {
2381
+ const hooksDir = join21(claudeHome(), "hooks");
2382
+ if (!existsSync17(hooksDir)) {
2195
2383
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
2196
2384
  return;
2197
2385
  }
2198
2386
  let anyWarn = false;
2199
2387
  for (const name of safeReaddir2(hooksDir)) {
2200
2388
  if (extname(name) !== ".js") continue;
2201
- const abs = join20(hooksDir, name);
2389
+ const abs = join21(hooksDir, name);
2202
2390
  const eff = effectiveType(abs);
2203
2391
  if (eff === null) continue;
2204
2392
  const src = safeRead(abs);
@@ -2218,17 +2406,18 @@ function reportHookScopeCheck(section2) {
2218
2406
 
2219
2407
  // src/commands.doctor.checks.hooks.ts
2220
2408
  init_color();
2221
- import { existsSync as existsSync17 } from "node:fs";
2222
- import { join as join21 } from "node:path";
2409
+ import { existsSync as existsSync18 } from "node:fs";
2410
+ import { join as join22 } from "node:path";
2223
2411
  init_config();
2224
2412
  function expandHome(token) {
2225
- 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);
2226
2415
  }
2227
2416
  function stripShellPunctuation(token) {
2228
2417
  return token.replace(/^['"]+/, "").replace(/['"`;)|&>]+$/, "");
2229
2418
  }
2230
2419
  function* claudePathsIn(command) {
2231
- const claudePrefix = `${CLAUDE_HOME}/`;
2420
+ const claudePrefix = `${claudeHome()}/`;
2232
2421
  for (const segment of command.split(/&&|\|\||;|\|/)) {
2233
2422
  for (const raw of segment.trim().split(/\s+/).filter(Boolean)) {
2234
2423
  const expanded = expandHome(stripShellPunctuation(raw));
@@ -2257,7 +2446,7 @@ function checkEventGroups(section2, event, groups) {
2257
2446
  for (const group of groups) {
2258
2447
  for (const cmd of commandsFromGroup(group)) {
2259
2448
  for (const resolved of claudePathsIn(cmd)) {
2260
- if (existsSync17(resolved)) continue;
2449
+ if (existsSync18(resolved)) continue;
2261
2450
  addItem(
2262
2451
  section2,
2263
2452
  `${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
@@ -2270,8 +2459,8 @@ function checkEventGroups(section2, event, groups) {
2270
2459
  return anyFail;
2271
2460
  }
2272
2461
  function reportHooksTargetCheck(section2) {
2273
- const settingsPath = join21(CLAUDE_HOME, "settings.json");
2274
- if (!existsSync17(settingsPath)) {
2462
+ const settingsPath = join22(claudeHome(), "settings.json");
2463
+ if (!existsSync18(settingsPath)) {
2275
2464
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
2276
2465
  return;
2277
2466
  }
@@ -2294,12 +2483,12 @@ function reportHooksTargetCheck(section2) {
2294
2483
 
2295
2484
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
2296
2485
  init_color();
2297
- import { existsSync as existsSync19, readFileSync as readFileSync6 } from "node:fs";
2298
- import { join as join23 } from "node:path";
2486
+ import { existsSync as existsSync20, readFileSync as readFileSync6 } from "node:fs";
2487
+ import { join as join24 } from "node:path";
2299
2488
 
2300
2489
  // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
2301
- import { closeSync as closeSync2, existsSync as existsSync18, openSync as openSync2, readSync, realpathSync as realpathSync2 } from "node:fs";
2302
- import { dirname as dirname3, join as join22, resolve as resolve2 } from "node:path";
2490
+ import { closeSync as closeSync2, existsSync as existsSync19, openSync as openSync2, readSync, realpathSync as realpathSync3 } from "node:fs";
2491
+ import { dirname as dirname3, join as join23, resolve as resolve2 } from "node:path";
2303
2492
  function suppressedRanges(src) {
2304
2493
  const ranges = [];
2305
2494
  const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
@@ -2331,19 +2520,19 @@ function topRelativeSpecifiers(src) {
2331
2520
  }
2332
2521
  function specifierIsMissing(specifier, baseDir) {
2333
2522
  const base = resolve2(baseDir, specifier);
2334
- if (existsSync18(base)) return false;
2523
+ if (existsSync19(base)) return false;
2335
2524
  for (const ext of [".js", ".cjs", ".mjs"]) {
2336
- if (existsSync18(base + ext)) return false;
2525
+ if (existsSync19(base + ext)) return false;
2337
2526
  }
2338
2527
  for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
2339
- if (existsSync18(join22(base, idx))) return false;
2528
+ if (existsSync19(join23(base, idx))) return false;
2340
2529
  }
2341
2530
  return true;
2342
2531
  }
2343
2532
  function relativeRequireTargetsBroken(scriptPath) {
2344
2533
  let realPath;
2345
2534
  try {
2346
- realPath = realpathSync2(scriptPath);
2535
+ realPath = realpathSync3(scriptPath);
2347
2536
  } catch {
2348
2537
  return false;
2349
2538
  }
@@ -2372,7 +2561,8 @@ function relativeRequireTargetsBroken(scriptPath) {
2372
2561
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
2373
2562
  init_config();
2374
2563
  function expandHome2(token) {
2375
- 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);
2376
2566
  }
2377
2567
  function stripShellPunct(token) {
2378
2568
  const head = token.replace(/^['"]+/, "");
@@ -2390,8 +2580,8 @@ function commandTokens(command) {
2390
2580
  return tokens;
2391
2581
  }
2392
2582
  function readPathMapSafe() {
2393
- const mapPath = join23(REPO_HOME, "path-map.json");
2394
- if (!existsSync19(mapPath)) return { projects: {} };
2583
+ const mapPath = join24(repoHome(), "path-map.json");
2584
+ if (!existsSync20(mapPath)) return { projects: {} };
2395
2585
  try {
2396
2586
  return JSON.parse(readFileSync6(mapPath, "utf8"));
2397
2587
  } catch {
@@ -2400,7 +2590,7 @@ function readPathMapSafe() {
2400
2590
  }
2401
2591
  function resolvesUnderSymlinkedShared(scriptPath, sharedLinkNames) {
2402
2592
  for (const name of sharedLinkNames) {
2403
- const prefix = `${CLAUDE_HOME}/${name}/`;
2593
+ const prefix = `${claudeHome()}/${name}/`;
2404
2594
  if (scriptPath.startsWith(prefix)) return true;
2405
2595
  }
2406
2596
  return false;
@@ -2464,8 +2654,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
2464
2654
  return anyWarn;
2465
2655
  }
2466
2656
  function reportPreserveSymlinksCheck(section2) {
2467
- const settingsPath = join23(CLAUDE_HOME, "settings.json");
2468
- if (!existsSync19(settingsPath)) {
2657
+ const settingsPath = join24(claudeHome(), "settings.json");
2658
+ if (!existsSync20(settingsPath)) {
2469
2659
  addItem(
2470
2660
  section2,
2471
2661
  `${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
@@ -2611,8 +2801,8 @@ function reportNodeEngineCheck(section2) {
2611
2801
  // src/commands.doctor.gitleaks-version.ts
2612
2802
  init_color();
2613
2803
  import { execFileSync as execFileSync7 } from "node:child_process";
2614
- import { existsSync as existsSync20 } from "node:fs";
2615
- import { join as join24 } from "node:path";
2804
+ import { existsSync as existsSync21 } from "node:fs";
2805
+ import { join as join25 } from "node:path";
2616
2806
  init_config();
2617
2807
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
2618
2808
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -2621,7 +2811,7 @@ function majorMinorOf(value) {
2621
2811
  return m === null ? null : [m[1], m[2]];
2622
2812
  }
2623
2813
  function readGitleaksVersion(run, tomlExists) {
2624
- const tomlPath = join24(REPO_HOME, ".gitleaks.toml");
2814
+ const tomlPath = join25(repoHome(), ".gitleaks.toml");
2625
2815
  const args = ["version"];
2626
2816
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
2627
2817
  try {
@@ -2633,7 +2823,7 @@ function readGitleaksVersion(run, tomlExists) {
2633
2823
  return null;
2634
2824
  }
2635
2825
  }
2636
- function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync20) {
2826
+ function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync21) {
2637
2827
  const raw = readGitleaksVersion(run, tomlExists);
2638
2828
  if (raw === null) return;
2639
2829
  const local = majorMinorOf(raw);
@@ -2772,7 +2962,7 @@ function readOriginRemote(cwd, run = execFileSync9) {
2772
2962
  function reportActionsDrift(section2, run = execFileSync10) {
2773
2963
  let remote;
2774
2964
  try {
2775
- remote = readOriginRemote(REPO_HOME, run);
2965
+ remote = readOriginRemote(repoHome(), run);
2776
2966
  } catch {
2777
2967
  return;
2778
2968
  }
@@ -2802,15 +2992,15 @@ function reportActionsDrift(section2, run = execFileSync10) {
2802
2992
 
2803
2993
  // src/commands.doctor.verdict.ts
2804
2994
  init_color();
2805
- function isFailLine(item) {
2806
- return item.includes(failGlyph);
2995
+ function isFailLine(item2) {
2996
+ return item2.includes(failGlyph);
2807
2997
  }
2808
- function isWarnLine(item) {
2809
- return !isFailLine(item) && item.includes(warnGlyph);
2998
+ function isWarnLine(item2) {
2999
+ return !isFailLine(item2) && item2.includes(warnGlyph);
2810
3000
  }
2811
3001
  function buildVerdictSection(sections) {
2812
3002
  const summary = section("Summary");
2813
- 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/, ""));
2814
3004
  const failures = lines.filter(isFailLine);
2815
3005
  const warnings = lines.filter(isWarnLine);
2816
3006
  for (const line of [...failures, ...warnings]) addItem(summary, line);
@@ -2833,8 +3023,8 @@ function cmdDoctor(opts = {}) {
2833
3023
  reportHostAndPaths(host);
2834
3024
  reportRepoState(host);
2835
3025
  const links = section("Shared links");
2836
- const mapPath = join25(REPO_HOME, "path-map.json");
2837
- const rawMap = existsSync21(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
3026
+ const mapPath = join26(repoHome(), "path-map.json");
3027
+ const rawMap = existsSync22(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
2838
3028
  const map = rawMap ?? { projects: {} };
2839
3029
  reportSharedLinks(links, map);
2840
3030
  const hooksScan = section("Hook targets");
@@ -2888,16 +3078,15 @@ function cmdDoctor(opts = {}) {
2888
3078
  // src/commands.drop-session.ts
2889
3079
  init_config();
2890
3080
  import { execFileSync as execFileSync12 } from "node:child_process";
2891
- import { existsSync as existsSync23, readdirSync as readdirSync9, statSync as statSync5 } from "node:fs";
2892
- import { join as join28, relative as relative4 } from "node:path";
3081
+ import { existsSync as existsSync24, readdirSync as readdirSync9, statSync as statSync5 } from "node:fs";
3082
+ import { join as join29, relative as relative4 } from "node:path";
2893
3083
 
2894
3084
  // src/commands.drop-session.git.ts
2895
- init_config();
2896
3085
  import { execFileSync as execFileSync11 } from "node:child_process";
2897
- function expandStagedDir(dirRel) {
3086
+ function expandStagedDir(dirRel, repo) {
2898
3087
  try {
2899
3088
  const out = execFileSync11("git", ["ls-files", "-z", "--", dirRel], {
2900
- cwd: REPO_HOME,
3089
+ cwd: repo,
2901
3090
  stdio: ["ignore", "pipe", "pipe"]
2902
3091
  });
2903
3092
  return out.toString().split("\0").filter((p) => p !== "");
@@ -2905,10 +3094,10 @@ function expandStagedDir(dirRel) {
2905
3094
  return [];
2906
3095
  }
2907
3096
  }
2908
- function isTrackedInHead(rel) {
3097
+ function isTrackedInHead(rel, repo) {
2909
3098
  try {
2910
3099
  execFileSync11("git", ["cat-file", "-e", `HEAD:${rel}`], {
2911
- cwd: REPO_HOME,
3100
+ cwd: repo,
2912
3101
  stdio: ["ignore", "pipe", "pipe"]
2913
3102
  });
2914
3103
  return true;
@@ -2916,10 +3105,10 @@ function isTrackedInHead(rel) {
2916
3105
  return false;
2917
3106
  }
2918
3107
  }
2919
- function isInIndex(rel) {
3108
+ function isInIndex(rel, repo) {
2920
3109
  try {
2921
3110
  const out = execFileSync11("git", ["ls-files", "--", rel], {
2922
- cwd: REPO_HOME,
3111
+ cwd: repo,
2923
3112
  stdio: ["ignore", "pipe", "pipe"]
2924
3113
  });
2925
3114
  return out.toString().trim() !== "";
@@ -2932,8 +3121,8 @@ function isInIndex(rel) {
2932
3121
  init_config();
2933
3122
  init_utils();
2934
3123
  init_utils_json();
2935
- import { existsSync as existsSync22 } from "node:fs";
2936
- import { join as join26 } from "node:path";
3124
+ import { existsSync as existsSync23 } from "node:fs";
3125
+ import { join as join27 } from "node:path";
2937
3126
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
2938
3127
  function reportScrubHint(id, matches) {
2939
3128
  const live = resolveLiveTranscript(id, matches);
@@ -2949,16 +3138,17 @@ function reportScrubHint(id, matches) {
2949
3138
  }
2950
3139
  function resolveLiveTranscript(id, matches) {
2951
3140
  try {
2952
- const mapPath = join26(REPO_HOME, "path-map.json");
2953
- if (!existsSync22(mapPath)) return null;
3141
+ const mapPath = join27(repoHome(), "path-map.json");
3142
+ if (!existsSync23(mapPath)) return null;
2954
3143
  const projects = readJson(mapPath).projects;
3144
+ const claude = claudeHome();
2955
3145
  for (const rel of matches) {
2956
3146
  const logical = SHARED_PROJECT_LOGICAL.exec(rel)?.[1];
2957
3147
  if (logical === void 0) continue;
2958
3148
  const abs = projects[logical]?.[HOST];
2959
3149
  if (abs === void 0) continue;
2960
- const live = join26(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
2961
- if (existsSync22(live)) return live;
3150
+ const live = join27(claude, "projects", encodePath(abs), `${id}.jsonl`);
3151
+ if (existsSync23(live)) return live;
2962
3152
  }
2963
3153
  return null;
2964
3154
  } catch {
@@ -2973,12 +3163,15 @@ init_utils();
2973
3163
  init_config();
2974
3164
  init_utils();
2975
3165
  import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
2976
- import { dirname as dirname4, join as join27 } from "node:path";
2977
- var LOCK_PATH = join27(HOME, ".cache", "claude-nomad", "nomad.lock");
3166
+ import { dirname as dirname4, join as join28 } from "node:path";
3167
+ function lockFilePath() {
3168
+ return join28(home(), ".cache", "claude-nomad", "nomad.lock");
3169
+ }
2978
3170
  function acquireLock(verb) {
2979
- mkdirSync5(dirname4(LOCK_PATH), { recursive: true });
3171
+ const lp = lockFilePath();
3172
+ mkdirSync5(dirname4(lp), { recursive: true });
2980
3173
  try {
2981
- const fd = openSync3(LOCK_PATH, "wx");
3174
+ const fd = openSync3(lp, "wx");
2982
3175
  try {
2983
3176
  writeFileSync3(fd, String(process.pid));
2984
3177
  } catch (writeErr) {
@@ -2987,55 +3180,56 @@ function acquireLock(verb) {
2987
3180
  } catch {
2988
3181
  }
2989
3182
  try {
2990
- unlinkSync(LOCK_PATH);
3183
+ unlinkSync(lp);
2991
3184
  } catch {
2992
3185
  }
2993
3186
  throw writeErr;
2994
3187
  }
2995
- return { fd };
3188
+ return { fd, path: lp };
2996
3189
  } catch (err) {
2997
3190
  const code = err.code;
2998
3191
  if (code !== "EEXIST") throw err;
2999
- return checkStaleAndRetry(verb);
3192
+ return checkStaleAndRetry(verb, lp);
3000
3193
  }
3001
3194
  }
3002
3195
  function releaseLock(handle) {
3003
3196
  if (handle === null) return;
3197
+ const lp = handle.path;
3004
3198
  try {
3005
3199
  closeSync3(handle.fd);
3006
3200
  } catch {
3007
3201
  }
3008
3202
  try {
3009
- unlinkSync(LOCK_PATH);
3203
+ unlinkSync(lp);
3010
3204
  } catch (err) {
3011
3205
  if (err.code !== "ENOENT") throw err;
3012
3206
  }
3013
3207
  }
3014
- function unlinkIfSamePid(expectedPidStr) {
3208
+ function unlinkIfSamePid(expectedPidStr, lp) {
3015
3209
  let current;
3016
3210
  try {
3017
- current = readFileSync9(LOCK_PATH, "utf8").trim();
3211
+ current = readFileSync9(lp, "utf8").trim();
3018
3212
  } catch {
3019
3213
  return false;
3020
3214
  }
3021
3215
  if (current !== expectedPidStr) return false;
3022
3216
  try {
3023
- unlinkSync(LOCK_PATH);
3217
+ unlinkSync(lp);
3024
3218
  return true;
3025
3219
  } catch {
3026
3220
  return false;
3027
3221
  }
3028
3222
  }
3029
- function checkStaleAndRetry(verb) {
3223
+ function checkStaleAndRetry(verb, lp) {
3030
3224
  let pidStr;
3031
3225
  try {
3032
- pidStr = readFileSync9(LOCK_PATH, "utf8").trim();
3226
+ pidStr = readFileSync9(lp, "utf8").trim();
3033
3227
  } catch {
3034
3228
  pidStr = "";
3035
3229
  }
3036
3230
  const pid = Number.parseInt(pidStr, 10);
3037
3231
  if (!Number.isFinite(pid) || pid <= 0) {
3038
- if (unlinkIfSamePid(pidStr)) return retryOnce(verb);
3232
+ if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
3039
3233
  warn(`another nomad ${verb} running, skipping`);
3040
3234
  return null;
3041
3235
  }
@@ -3046,7 +3240,7 @@ function checkStaleAndRetry(verb) {
3046
3240
  } catch (err) {
3047
3241
  const code = err.code;
3048
3242
  if (code === "ESRCH") {
3049
- if (unlinkIfSamePid(pidStr)) return retryOnce(verb);
3243
+ if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
3050
3244
  warn(`another nomad ${verb} running, skipping`);
3051
3245
  return null;
3052
3246
  }
@@ -3054,9 +3248,9 @@ function checkStaleAndRetry(verb) {
3054
3248
  return null;
3055
3249
  }
3056
3250
  }
3057
- function retryOnce(verb) {
3251
+ function retryOnce(verb, lp) {
3058
3252
  try {
3059
- const fd = openSync3(LOCK_PATH, "wx");
3253
+ const fd = openSync3(lp, "wx");
3060
3254
  try {
3061
3255
  writeFileSync3(fd, String(process.pid));
3062
3256
  } catch {
@@ -3065,13 +3259,13 @@ function retryOnce(verb) {
3065
3259
  } catch {
3066
3260
  }
3067
3261
  try {
3068
- unlinkSync(LOCK_PATH);
3262
+ unlinkSync(lp);
3069
3263
  } catch {
3070
3264
  }
3071
3265
  warn(`another nomad ${verb} running, skipping`);
3072
3266
  return null;
3073
3267
  }
3074
- return { fd };
3268
+ return { fd, path: lp };
3075
3269
  } catch {
3076
3270
  warn(`another nomad ${verb} running, skipping`);
3077
3271
  return null;
@@ -3084,19 +3278,20 @@ function cmdDropSession(id) {
3084
3278
  fail(`invalid session id: ${id}`);
3085
3279
  process.exit(1);
3086
3280
  }
3087
- if (!existsSync23(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
3281
+ const repo = repoHome();
3282
+ if (!existsSync24(repo)) die(`repo not cloned at ${repo}`);
3088
3283
  const handle = acquireLock("drop-session");
3089
3284
  if (handle === null) process.exit(0);
3090
3285
  try {
3091
- const repoProjects = join28(REPO_HOME, "shared", "projects");
3092
- if (!existsSync23(repoProjects)) {
3286
+ const repoProjects = join29(repo, "shared", "projects");
3287
+ if (!existsSync24(repoProjects)) {
3093
3288
  throw new NomadFatal(`no staged session matches ${id}`);
3094
3289
  }
3095
- const matches = collectMatches(repoProjects, id);
3290
+ const matches = collectMatches(repoProjects, id, repo);
3096
3291
  if (matches.length === 0) {
3097
3292
  throw new NomadFatal(`no staged session matches ${id}`);
3098
3293
  }
3099
- for (const rel of matches) unstageOne(rel);
3294
+ for (const rel of matches) unstageOne(rel, repo);
3100
3295
  reportScrubHint(id, matches);
3101
3296
  } catch (err) {
3102
3297
  if (!(err instanceof NomadFatal)) {
@@ -3108,37 +3303,37 @@ function cmdDropSession(id) {
3108
3303
  releaseLock(handle);
3109
3304
  }
3110
3305
  }
3111
- function collectMatches(repoProjects, id) {
3306
+ function collectMatches(repoProjects, id, repo) {
3112
3307
  const matches = [];
3113
3308
  for (const logical of readdirSync9(repoProjects)) {
3114
- const candidate = join28(repoProjects, logical, `${id}.jsonl`);
3115
- if (existsSync23(candidate)) {
3116
- matches.push(relative4(REPO_HOME, candidate));
3117
- }
3118
- const dir = join28(repoProjects, logical, id);
3119
- if (existsSync23(dir) && statSync5(dir).isDirectory()) {
3120
- const dirRel = relative4(REPO_HOME, dir);
3121
- const staged = expandStagedDir(dirRel);
3309
+ const candidate = join29(repoProjects, logical, `${id}.jsonl`);
3310
+ if (existsSync24(candidate)) {
3311
+ matches.push(relative4(repo, candidate));
3312
+ }
3313
+ const dir = join29(repoProjects, logical, id);
3314
+ if (existsSync24(dir) && statSync5(dir).isDirectory()) {
3315
+ const dirRel = relative4(repo, dir);
3316
+ const staged = expandStagedDir(dirRel, repo);
3122
3317
  if (staged.length > 0) matches.push(...staged);
3123
3318
  else matches.push(dirRel);
3124
3319
  }
3125
3320
  }
3126
3321
  return matches;
3127
3322
  }
3128
- function unstageOne(rel) {
3129
- if (!isInIndex(rel)) {
3130
- 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)`);
3131
3326
  return;
3132
3327
  }
3133
3328
  try {
3134
- if (isTrackedInHead(rel)) {
3329
+ if (isTrackedInHead(rel, repo)) {
3135
3330
  execFileSync12("git", ["restore", "--staged", "--worktree", "--", rel], {
3136
- cwd: REPO_HOME,
3331
+ cwd: repo,
3137
3332
  stdio: ["ignore", "pipe", "pipe"]
3138
3333
  });
3139
3334
  } else {
3140
3335
  execFileSync12("git", ["rm", "--cached", "-f", "--", rel], {
3141
- cwd: REPO_HOME,
3336
+ cwd: repo,
3142
3337
  stdio: ["ignore", "pipe", "pipe"]
3143
3338
  });
3144
3339
  }
@@ -3147,25 +3342,25 @@ function unstageOne(rel) {
3147
3342
  const detail = e.stderr?.toString().trim() ?? e.message;
3148
3343
  throw new NomadFatal(`git failed to unstage ${rel}: ${detail}`);
3149
3344
  }
3150
- log(`dropped ${rel}`);
3345
+ item(`dropped ${rel}`);
3151
3346
  }
3152
3347
 
3153
3348
  // src/commands.redact.ts
3154
3349
  init_config();
3155
- import { existsSync as existsSync25, statSync as statSync7 } from "node:fs";
3156
- import { dirname as dirname5, join as join30 } from "node:path";
3350
+ import { existsSync as existsSync26, statSync as statSync7 } from "node:fs";
3351
+ import { dirname as dirname5, join as join31 } from "node:path";
3157
3352
 
3158
3353
  // src/commands.redact.subtree.ts
3159
- import { existsSync as existsSync24, lstatSync as lstatSync6, readFileSync as readFileSync10, readdirSync as readdirSync10, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
3160
- import { join as join29 } from "node:path";
3354
+ import { existsSync as existsSync25, lstatSync as lstatSync7, readFileSync as readFileSync10, readdirSync as readdirSync10, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
3355
+ import { join as join30 } from "node:path";
3161
3356
  init_utils_fs();
3162
3357
  function collectFiles(dir, out) {
3163
- if (!existsSync24(dir)) return;
3164
- const st = lstatSync6(dir);
3358
+ if (!existsSync25(dir)) return;
3359
+ const st = lstatSync7(dir);
3165
3360
  if (!st.isDirectory()) return;
3166
3361
  for (const entry of readdirSync10(dir)) {
3167
- const abs = join29(dir, entry);
3168
- const lst = lstatSync6(abs);
3362
+ const abs = join30(dir, entry);
3363
+ const lst = lstatSync7(abs);
3169
3364
  if (lst.isSymbolicLink()) continue;
3170
3365
  if (lst.isDirectory()) {
3171
3366
  collectFiles(abs, out);
@@ -3214,14 +3409,15 @@ init_utils_json();
3214
3409
  init_utils();
3215
3410
  function resolveLiveTranscript2(id) {
3216
3411
  try {
3217
- const mapPath = join30(REPO_HOME, "path-map.json");
3218
- if (!existsSync25(mapPath)) return null;
3412
+ const mapPath = join31(repoHome(), "path-map.json");
3413
+ if (!existsSync26(mapPath)) return null;
3219
3414
  const projects = readJson(mapPath).projects;
3415
+ const claude = claudeHome();
3220
3416
  for (const hostMap of Object.values(projects)) {
3221
3417
  const abs = hostMap[HOST];
3222
3418
  if (abs === void 0) continue;
3223
- const live = join30(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
3224
- if (existsSync25(live)) return live;
3419
+ const live = join31(claude, "projects", encodePath(abs), `${id}.jsonl`);
3420
+ if (existsSync26(live)) return live;
3225
3421
  }
3226
3422
  return null;
3227
3423
  } catch {
@@ -3239,17 +3435,19 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
3239
3435
  fail(`invalid session id: ${id}`);
3240
3436
  process.exit(1);
3241
3437
  }
3242
- if (!existsSync25(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}`);
3243
3441
  const handle = acquireLock("redact");
3244
3442
  if (handle === null) process.exit(0);
3245
3443
  try {
3246
3444
  const localPath = resolveLiveTranscript2(id);
3247
- if (localPath === null || !existsSync25(localPath)) {
3445
+ if (localPath === null || !existsSync26(localPath)) {
3248
3446
  fail(`could not resolve local transcript for session ${id} on this host`);
3249
3447
  process.exitCode = 1;
3250
3448
  return;
3251
3449
  }
3252
- const sessionDir = join30(dirname5(localPath), id);
3450
+ const sessionDir = join31(dirname5(localPath), id);
3253
3451
  const subtreeFiles = listSubtreeFiles(sessionDir);
3254
3452
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
3255
3453
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -3268,7 +3466,7 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
3268
3466
  process.exitCode = 1;
3269
3467
  return;
3270
3468
  }
3271
- const ts = freshBackupTs(BACKUP_BASE);
3469
+ const ts = freshBackupTs(backup);
3272
3470
  const { total: totalCount, dirty } = applySubtreeRedactions(
3273
3471
  localPath,
3274
3472
  mainFindings,
@@ -3302,8 +3500,8 @@ ${lines}`);
3302
3500
  }
3303
3501
 
3304
3502
  // src/commands.pull.ts
3305
- import { existsSync as existsSync33, mkdirSync as mkdirSync8 } from "node:fs";
3306
- import { join as join39 } from "node:path";
3503
+ import { existsSync as existsSync34, mkdirSync as mkdirSync8 } from "node:fs";
3504
+ import { join as join40 } from "node:path";
3307
3505
 
3308
3506
  // src/commands.push.sections.ts
3309
3507
  init_color();
@@ -3391,8 +3589,8 @@ init_config();
3391
3589
 
3392
3590
  // src/extras-sync.ts
3393
3591
  init_config();
3394
- import { existsSync as existsSync28 } from "node:fs";
3395
- import { join as join33 } from "node:path";
3592
+ import { existsSync as existsSync29 } from "node:fs";
3593
+ import { join as join34 } from "node:path";
3396
3594
 
3397
3595
  // src/extras-sync.diff.ts
3398
3596
  init_utils();
@@ -3431,8 +3629,8 @@ function listDivergingFiles(a, b) {
3431
3629
 
3432
3630
  // src/extras-sync.core.ts
3433
3631
  init_config();
3434
- import { cpSync as cpSync4, existsSync as existsSync26, rmSync as rmSync7 } from "node:fs";
3435
- import { join as join31 } from "node:path";
3632
+ import { cpSync as cpSync5, existsSync as existsSync27, rmSync as rmSync8 } from "node:fs";
3633
+ import { join as join32 } from "node:path";
3436
3634
 
3437
3635
  // src/extras-sync.guards.ts
3438
3636
  init_utils();
@@ -3455,9 +3653,10 @@ function assertSafeLocalRoot(localRoot, logical) {
3455
3653
  init_utils();
3456
3654
  init_utils_json();
3457
3655
  function loadValidatedExtras(opts) {
3458
- const mapPath = join31(REPO_HOME, "path-map.json");
3459
- const repoExtras = join31(REPO_HOME, "shared", "extras");
3460
- if (!existsSync26(mapPath) || opts.requireRepoExtras === true && !existsSync26(repoExtras)) {
3656
+ const repo = repoHome();
3657
+ const mapPath = join32(repo, "path-map.json");
3658
+ const repoExtras = join32(repo, "shared", "extras");
3659
+ if (!existsSync27(mapPath) || opts.requireRepoExtras === true && !existsSync27(repoExtras)) {
3461
3660
  if (opts.missingMsg !== void 0) log(opts.missingMsg);
3462
3661
  return null;
3463
3662
  }
@@ -3489,8 +3688,8 @@ function* eachExtrasTarget(v, counts) {
3489
3688
  }
3490
3689
  }
3491
3690
  function copyExtras(src, dst) {
3492
- rmSync7(dst, { recursive: true, force: true });
3493
- cpSync4(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
3691
+ rmSync8(dst, { recursive: true, force: true });
3692
+ cpSync5(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
3494
3693
  }
3495
3694
 
3496
3695
  // src/extras-sync.ts
@@ -3499,8 +3698,8 @@ init_utils_json();
3499
3698
 
3500
3699
  // src/extras-sync.remap.ts
3501
3700
  init_config();
3502
- import { existsSync as existsSync27, mkdirSync as mkdirSync6 } from "node:fs";
3503
- import { join as join32 } from "node:path";
3701
+ import { existsSync as existsSync28, mkdirSync as mkdirSync6 } from "node:fs";
3702
+ import { join as join33 } from "node:path";
3504
3703
  init_utils_fs();
3505
3704
  function runExtrasOp(v, dryRun, paths, backup) {
3506
3705
  const counts = { unmapped: 0, skipped: 0 };
@@ -3508,15 +3707,15 @@ function runExtrasOp(v, dryRun, paths, backup) {
3508
3707
  const would = [];
3509
3708
  for (const t of eachExtrasTarget(v, counts)) {
3510
3709
  const { src, dst } = paths(t);
3511
- if (!existsSync27(src)) continue;
3512
- const item = `${t.logical}/${t.dirname}`;
3710
+ if (!existsSync28(src)) continue;
3711
+ const item2 = `${t.logical}/${t.dirname}`;
3513
3712
  if (dryRun) {
3514
- would.push(item);
3713
+ would.push(item2);
3515
3714
  continue;
3516
3715
  }
3517
3716
  backup(dst, t.localRoot);
3518
3717
  copyExtras(src, dst);
3519
- done.push(item);
3718
+ done.push(item2);
3520
3719
  }
3521
3720
  return { ...counts, done, would };
3522
3721
  }
@@ -3524,16 +3723,17 @@ function remapExtrasPush(ts, opts = {}) {
3524
3723
  const dryRun = opts.dryRun === true;
3525
3724
  const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
3526
3725
  if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
3527
- const repoExtras = join32(REPO_HOME, "shared", "extras");
3726
+ const repo = repoHome();
3727
+ const repoExtras = join33(repo, "shared", "extras");
3528
3728
  if (!dryRun) mkdirSync6(repoExtras, { recursive: true });
3529
3729
  const { unmapped, skipped, done, would } = runExtrasOp(
3530
3730
  v,
3531
3731
  dryRun,
3532
3732
  ({ localRoot, logical, dirname: dirname7 }) => ({
3533
- src: join32(localRoot, dirname7),
3534
- dst: join32(repoExtras, logical, dirname7)
3733
+ src: join33(localRoot, dirname7),
3734
+ dst: join33(repoExtras, logical, dirname7)
3535
3735
  }),
3536
- (dst) => backupRepoWrite(dst, ts, REPO_HOME)
3736
+ (dst) => backupRepoWrite(dst, ts, repo)
3537
3737
  );
3538
3738
  return { unmapped, skipped, pushed: done, wouldPush: would };
3539
3739
  }
@@ -3544,13 +3744,13 @@ function remapExtrasPull(ts, opts = {}) {
3544
3744
  missingMsg: "no path-map or repo extras dir; skipping extras remap"
3545
3745
  });
3546
3746
  if (v === null) return { unmapped: 0, skipped: 0, pulled: [], wouldPull: [] };
3547
- const repoExtras = join32(REPO_HOME, "shared", "extras");
3747
+ const repoExtras = join33(repoHome(), "shared", "extras");
3548
3748
  const { unmapped, skipped, done, would } = runExtrasOp(
3549
3749
  v,
3550
3750
  dryRun,
3551
3751
  ({ localRoot, logical, dirname: dirname7 }) => ({
3552
- src: join32(repoExtras, logical, dirname7),
3553
- dst: join32(localRoot, dirname7)
3752
+ src: join33(repoExtras, logical, dirname7),
3753
+ dst: join33(localRoot, dirname7)
3554
3754
  }),
3555
3755
  // Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
3556
3756
  // localRoot so the backup tree mirrors the project layout.
@@ -3564,14 +3764,15 @@ function divergenceCheckExtras(ts) {
3564
3764
  const v = loadValidatedExtras({});
3565
3765
  if (v === null) return;
3566
3766
  const counts = { unmapped: 0, skipped: 0 };
3567
- const backupRoot = join33(BACKUP_BASE, ts, "extras");
3767
+ const backupRoot = join34(backupBase(), ts, "extras");
3768
+ const repo = repoHome();
3568
3769
  for (const { logical, localRoot, dirname: dirname7 } of eachExtrasTarget(v, counts)) {
3569
- const local = join33(localRoot, dirname7);
3570
- const repo = join33(REPO_HOME, "shared", "extras", logical, dirname7);
3571
- if (!existsSync28(local) || !existsSync28(repo)) continue;
3572
- const diff = listDivergingFiles(local, repo);
3770
+ const local = join34(localRoot, dirname7);
3771
+ const repoEntry = join34(repo, "shared", "extras", logical, dirname7);
3772
+ if (!existsSync29(local) || !existsSync29(repoEntry)) continue;
3773
+ const diff = listDivergingFiles(local, repoEntry);
3573
3774
  if (diff.length === 0) continue;
3574
- const projectBackupRoot = join33(backupRoot, encodePath(localRoot));
3775
+ const projectBackupRoot = join34(backupRoot, encodePath(localRoot));
3575
3776
  warn(
3576
3777
  `local ${dirname7} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will overwrite them (backups at ${projectBackupRoot}/)`
3577
3778
  );
@@ -3584,8 +3785,8 @@ init_config();
3584
3785
  init_utils();
3585
3786
  init_utils_fs();
3586
3787
  init_utils_json();
3587
- import { existsSync as existsSync29, lstatSync as lstatSync7, rmSync as rmSync8 } from "node:fs";
3588
- import { join as join34 } from "node:path";
3788
+ import { existsSync as existsSync30, lstatSync as lstatSync8, rmSync as rmSync9 } from "node:fs";
3789
+ import { join as join35 } from "node:path";
3589
3790
  function emitAutoMove(onPreview, linkPath, ts, name) {
3590
3791
  if (onPreview) {
3591
3792
  onPreview({ kind: "auto-move", from: linkPath, to: `backup/${ts}/${name}` });
@@ -3602,43 +3803,47 @@ function emitCreate(onPreview, from, to) {
3602
3803
  }
3603
3804
  function applySharedLinks(ts, map, opts = {}) {
3604
3805
  const dryRun = opts.dryRun === true;
3806
+ const claude = claudeHome();
3807
+ const repo = repoHome();
3605
3808
  const linkNames = allSharedLinks(map);
3606
3809
  for (const name of linkNames) {
3607
- const linkPath = join34(CLAUDE_HOME, name);
3608
- const target = join34(REPO_HOME, "shared", name);
3609
- if (!existsSync29(linkPath)) continue;
3610
- if (lstatSync7(linkPath).isSymbolicLink()) continue;
3611
- if (!existsSync29(target)) continue;
3810
+ const linkPath = join35(claude, name);
3811
+ const target = join35(repo, "shared", name);
3812
+ if (!existsSync30(linkPath)) continue;
3813
+ if (lstatSync8(linkPath).isSymbolicLink()) continue;
3814
+ if (!existsSync30(target)) continue;
3612
3815
  if (dryRun) {
3613
3816
  emitAutoMove(opts.onPreview, linkPath, ts, name);
3614
3817
  continue;
3615
3818
  }
3616
3819
  backupBeforeWrite(linkPath, ts);
3617
- rmSync8(linkPath, { recursive: true, force: true });
3820
+ rmSync9(linkPath, { recursive: true, force: true });
3618
3821
  }
3619
3822
  for (const name of linkNames) {
3620
- const target = join34(REPO_HOME, "shared", name);
3621
- if (!existsSync29(target)) continue;
3823
+ const target = join35(repo, "shared", name);
3824
+ if (!existsSync30(target)) continue;
3622
3825
  if (dryRun) {
3623
- emitCreate(opts.onPreview, join34(CLAUDE_HOME, name), target);
3826
+ emitCreate(opts.onPreview, join35(claude, name), target);
3624
3827
  continue;
3625
3828
  }
3626
- ensureSymlink(join34(CLAUDE_HOME, name), target);
3829
+ ensureSymlink(join35(claude, name), target);
3627
3830
  }
3628
3831
  }
3629
3832
  function regenerateSettings(ts, opts = {}) {
3630
3833
  const dryRun = opts.dryRun === true;
3631
- const basePath = join34(REPO_HOME, "shared", "settings.base.json");
3632
- const hostPath = join34(REPO_HOME, "hosts", `${HOST}.json`);
3633
- if (!existsSync29(basePath)) {
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`);
3838
+ if (!existsSync30(basePath)) {
3634
3839
  die("repo not initialized; run 'nomad init' to scaffold");
3635
3840
  }
3636
3841
  const base = readJson(basePath);
3637
- const hasOverrides = existsSync29(hostPath);
3842
+ const hasOverrides = existsSync30(hostPath);
3638
3843
  const overrides = hasOverrides ? readJson(hostPath) : {};
3639
3844
  const merged = deepMerge(base, overrides);
3640
- const settingsPath = join34(CLAUDE_HOME, "settings.json");
3641
- if (!hasOverrides && existsSync29(settingsPath)) {
3845
+ const settingsPath = join35(claude, "settings.json");
3846
+ if (!hasOverrides && existsSync30(settingsPath)) {
3642
3847
  try {
3643
3848
  const existing = readJson(settingsPath);
3644
3849
  const baseKeys = new Set(Object.keys(base));
@@ -3664,8 +3869,8 @@ function regenerateSettings(ts, opts = {}) {
3664
3869
 
3665
3870
  // src/preview.ts
3666
3871
  init_config();
3667
- import { existsSync as existsSync30 } from "node:fs";
3668
- import { join as join35 } from "node:path";
3872
+ import { existsSync as existsSync31 } from "node:fs";
3873
+ import { join as join36 } from "node:path";
3669
3874
 
3670
3875
  // node_modules/diff/libesm/diff/base.js
3671
3876
  var Diff = class {
@@ -3951,7 +4156,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
3951
4156
  return lines.join("\n");
3952
4157
  }
3953
4158
  function readJsonOrNull(path) {
3954
- if (!existsSync30(path)) return null;
4159
+ if (!existsSync31(path)) return null;
3955
4160
  try {
3956
4161
  return readJson(path);
3957
4162
  } catch {
@@ -3965,12 +4170,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
3965
4170
  }
3966
4171
  const notes = [];
3967
4172
  const hostOverrides = readJsonOrNull(hostPath);
3968
- if (hostOverrides === null && existsSync30(hostPath)) {
4173
+ if (hostOverrides === null && existsSync31(hostPath)) {
3969
4174
  notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
3970
4175
  }
3971
4176
  const merged = deepMerge(base, hostOverrides ?? {});
3972
4177
  const current = readJsonOrNull(settingsPath);
3973
- if (current === null && existsSync30(settingsPath)) {
4178
+ if (current === null && existsSync31(settingsPath)) {
3974
4179
  return { diff: "", notes: [...notes, "malformed; skipping diff"] };
3975
4180
  }
3976
4181
  const rawEqual = JSON.stringify(current ?? {}, null, 2) === JSON.stringify(merged, null, 2);
@@ -4000,6 +4205,8 @@ function buildSettingsSectionForPreview(result) {
4000
4205
  return s;
4001
4206
  }
4002
4207
  function computePreview(ts, map, verb = "pull") {
4208
+ const repo = repoHome();
4209
+ const claude = claudeHome();
4003
4210
  console.log(`would pull on host=${HOST} (dry-run; no mutation)`);
4004
4211
  console.log("");
4005
4212
  const links = section("Symlinks");
@@ -4008,9 +4215,9 @@ function computePreview(ts, map, verb = "pull") {
4008
4215
  onPreview: (e) => addItem(links, formatLinkRow(e))
4009
4216
  });
4010
4217
  const settingsResult = previewSettings(
4011
- join35(REPO_HOME, "shared", "settings.base.json"),
4012
- join35(REPO_HOME, "hosts", `${HOST}.json`),
4013
- join35(CLAUDE_HOME, "settings.json")
4218
+ join36(repo, "shared", "settings.base.json"),
4219
+ join36(repo, "hosts", `${HOST}.json`),
4220
+ join36(claude, "settings.json")
4014
4221
  );
4015
4222
  const settingsSection = buildSettingsSectionForPreview(settingsResult);
4016
4223
  const sessions = section("Sessions");
@@ -4026,21 +4233,21 @@ function computePreview(ts, map, verb = "pull") {
4026
4233
 
4027
4234
  // src/spinner.ts
4028
4235
  init_color();
4029
- import { existsSync as existsSync32 } from "node:fs";
4236
+ import { existsSync as existsSync33 } from "node:fs";
4030
4237
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4031
4238
  import { Worker } from "node:worker_threads";
4032
4239
 
4033
4240
  // src/commands.push.recovery.ts
4034
4241
  init_config();
4035
- import { readFileSync as readFileSync11, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
4036
- import { join as join38 } from "node:path";
4242
+ import { readFileSync as readFileSync11, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
4243
+ import { join as join39 } from "node:path";
4037
4244
  import { createInterface } from "node:readline/promises";
4038
4245
 
4039
4246
  // src/commands.push.recovery.redact.ts
4040
4247
  init_config();
4041
4248
  init_config_sharedDirs_guard();
4042
- import { cpSync as cpSync5, existsSync as existsSync31, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4043
- import { dirname as dirname6, join as join36, sep as sep2 } from "node:path";
4249
+ import { cpSync as cpSync6, existsSync as existsSync32, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4250
+ import { dirname as dirname6, join as join37, sep as sep3 } from "node:path";
4044
4251
  init_push_gitleaks_scan();
4045
4252
  init_utils_json();
4046
4253
  init_utils();
@@ -4066,13 +4273,13 @@ function parseAction(raw) {
4066
4273
  }
4067
4274
 
4068
4275
  // src/commands.push.recovery.redact.ts
4069
- function resolveStagedDir(localPath, map) {
4276
+ function resolveStagedDir(localPath, map, claude, repo) {
4070
4277
  for (const [logical, hostMap] of Object.entries(map.projects)) {
4071
4278
  assertSafeLogical(logical);
4072
4279
  const abs = hostMap[HOST];
4073
4280
  if (abs === void 0) continue;
4074
- if (localPath.startsWith(join36(CLAUDE_HOME, "projects", encodePath(abs)) + sep2)) {
4075
- return join36(REPO_HOME, "shared", "projects", logical);
4281
+ if (localPath.startsWith(join37(claude, "projects", encodePath(abs)) + sep3)) {
4282
+ return join37(repo, "shared", "projects", logical);
4076
4283
  }
4077
4284
  }
4078
4285
  return null;
@@ -4082,6 +4289,8 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4082
4289
  log(msg);
4083
4290
  return false;
4084
4291
  };
4292
+ const claude = claudeHome();
4293
+ const repo = repoHome();
4085
4294
  const sid = sessionIdFromFinding(f);
4086
4295
  if (sid === null) {
4087
4296
  return refuse(
@@ -4094,7 +4303,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4094
4303
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
4095
4304
  );
4096
4305
  }
4097
- const sessionDir = join36(dirname6(localPath), sid);
4306
+ const sessionDir = join37(dirname6(localPath), sid);
4098
4307
  const subtreeFiles = listSubtreeFiles(sessionDir);
4099
4308
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4100
4309
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4103,7 +4312,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4103
4312
  End the session and choose Redact again, or choose Drop session (holds this session back from the push, local copy kept) or Skip.`
4104
4313
  );
4105
4314
  }
4106
- const stagedProjectDir = resolveStagedDir(localPath, map);
4315
+ const stagedProjectDir = resolveStagedDir(localPath, map, claude, repo);
4107
4316
  if (stagedProjectDir === null) {
4108
4317
  return refuse(
4109
4318
  `could not map the local transcript for session ${sid} to a staged copy; choose Drop session or Skip.`
@@ -4128,25 +4337,26 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4128
4337
  );
4129
4338
  }
4130
4339
  mkdirSync7(stagedProjectDir, { recursive: true });
4131
- cpSync5(localPath, join36(stagedProjectDir, `${sid}.jsonl`), { force: true });
4132
- if (existsSync31(sessionDir)) {
4133
- cpSync5(sessionDir, join36(stagedProjectDir, sid), { force: true, recursive: true });
4340
+ cpSync6(localPath, join37(stagedProjectDir, `${sid}.jsonl`), { force: true });
4341
+ if (existsSync32(sessionDir)) {
4342
+ cpSync6(sessionDir, join37(stagedProjectDir, sid), { force: true, recursive: true });
4134
4343
  }
4135
4344
  return true;
4136
4345
  }
4137
4346
 
4138
4347
  // src/commands.push.recovery.drop.ts
4139
4348
  init_config();
4140
- import { rmSync as rmSync9 } from "node:fs";
4141
- import { join as join37 } from "node:path";
4349
+ import { rmSync as rmSync10 } from "node:fs";
4350
+ import { join as join38 } from "node:path";
4142
4351
  function dropSessionFromStaged(sid, map) {
4143
4352
  const logicals = Object.keys(map.projects);
4144
4353
  if (logicals.length === 0) return false;
4354
+ const repo = repoHome();
4145
4355
  for (const logical of logicals) {
4146
- const jsonl = join37(REPO_HOME, "shared", "projects", logical, `${sid}.jsonl`);
4147
- const dir = join37(REPO_HOME, "shared", "projects", logical, sid);
4148
- rmSync9(jsonl, { force: true });
4149
- rmSync9(dir, { recursive: true, force: true });
4356
+ const jsonl = join38(repo, "shared", "projects", logical, `${sid}.jsonl`);
4357
+ const dir = join38(repo, "shared", "projects", logical, sid);
4358
+ rmSync10(jsonl, { force: true });
4359
+ rmSync10(dir, { recursive: true, force: true });
4150
4360
  }
4151
4361
  return true;
4152
4362
  }
@@ -4154,19 +4364,19 @@ function dropSessionFromStaged(sid, map) {
4154
4364
  // src/commands.push.recovery.actions.ts
4155
4365
  init_push_gitleaks_scan();
4156
4366
  init_utils();
4157
- function applyAllow(f) {
4158
- appendGitleaksIgnore(f.Fingerprint);
4367
+ function applyAllow(f, repo) {
4368
+ appendGitleaksIgnore(f.Fingerprint, repo);
4159
4369
  }
4160
- function allowAllFindings(findings) {
4370
+ function allowAllFindings(findings, repo) {
4161
4371
  for (const f of findings) {
4162
- appendGitleaksIgnore(f.Fingerprint);
4372
+ appendGitleaksIgnore(f.Fingerprint, repo);
4163
4373
  }
4164
4374
  }
4165
- function allowFindingsByRule(findings, ruleId) {
4375
+ function allowFindingsByRule(findings, ruleId, repo) {
4166
4376
  let count = 0;
4167
4377
  for (const f of findings) {
4168
4378
  if (f.RuleID === ruleId) {
4169
- appendGitleaksIgnore(f.Fingerprint);
4379
+ appendGitleaksIgnore(f.Fingerprint, repo);
4170
4380
  count++;
4171
4381
  }
4172
4382
  }
@@ -4188,7 +4398,7 @@ function dispatchOne(f, ctx) {
4188
4398
  const sid = sessionIdFromFinding(f);
4189
4399
  if (sid !== null && ctx.droppedSids.has(sid)) return;
4190
4400
  if (action === "allow") {
4191
- applyAllow(f);
4401
+ applyAllow(f, ctx.repo);
4192
4402
  return;
4193
4403
  }
4194
4404
  if (sid === null) return;
@@ -4205,12 +4415,14 @@ function dispatchOne(f, ctx) {
4205
4415
  if (applyRedact(f, ctx.ts, ctx.map, ctx.nowMs, ctx.scan)) ctx.redactedSids.add(sid);
4206
4416
  }
4207
4417
  }
4208
- 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;
4209
4420
  const ctx = {
4210
4421
  actions,
4211
4422
  ts,
4212
4423
  map,
4213
4424
  nowMs,
4425
+ repo,
4214
4426
  scan,
4215
4427
  drop,
4216
4428
  redactedSids: /* @__PURE__ */ new Set(),
@@ -4252,17 +4464,17 @@ function printRecoveryLegend(print = console.log) {
4252
4464
  print(" Skip - leave unresolved (the push aborts)");
4253
4465
  print("");
4254
4466
  }
4255
- function applyThenRescan(scanVerdict, repoHome) {
4256
- gitOrFatal(["add", "-A"], "git add", repoHome);
4257
- const next = scanVerdict();
4467
+ function applyThenRescan(scanVerdict, repoHome2) {
4468
+ gitOrFatal(["add", "-A"], "git add", repoHome2);
4469
+ const next = scanVerdict(repoHome2);
4258
4470
  if (next.leak) {
4259
4471
  const { bySession, other } = partitionFindings(next.findings);
4260
4472
  throw new NomadFatal(buildSessionAwareFatal(bySession, other));
4261
4473
  }
4262
4474
  return next;
4263
4475
  }
4264
- function allowThenRescan(append, scanVerdict, repoHome) {
4265
- const ignPath = join38(repoHome, ".gitleaksignore");
4476
+ function allowThenRescan(append, scanVerdict, repoHome2) {
4477
+ const ignPath = join39(repoHome2, ".gitleaksignore");
4266
4478
  let before;
4267
4479
  try {
4268
4480
  before = readFileSync11(ignPath, "utf8");
@@ -4271,9 +4483,9 @@ function allowThenRescan(append, scanVerdict, repoHome) {
4271
4483
  }
4272
4484
  append();
4273
4485
  try {
4274
- return applyThenRescan(scanVerdict, repoHome);
4486
+ return applyThenRescan(scanVerdict, repoHome2);
4275
4487
  } catch (err) {
4276
- if (before === null) rmSync10(ignPath, { force: true });
4488
+ if (before === null) rmSync11(ignPath, { force: true });
4277
4489
  else writeFileSync5(ignPath, before, "utf8");
4278
4490
  throw err;
4279
4491
  }
@@ -4304,22 +4516,23 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
4304
4516
  printLegend = printRecoveryLegend
4305
4517
  } = deps;
4306
4518
  const scanVerdict = deps.scanVerdict ?? (await Promise.resolve().then(() => (init_push_leak_verdict(), push_leak_verdict_exports))).scanPushVerdict;
4519
+ const repo = repoHome();
4307
4520
  let current = verdict;
4308
4521
  if (redactAll) {
4309
4522
  redactAllFindings(current.findings, ts, map, nowMs, scan);
4310
- return applyThenRescan(scanVerdict, REPO_HOME);
4523
+ return applyThenRescan(scanVerdict, repo);
4311
4524
  }
4312
4525
  if (allowAll) {
4313
- return allowThenRescan(() => allowAllFindings(current.findings), scanVerdict, REPO_HOME);
4526
+ return allowThenRescan(() => allowAllFindings(current.findings, repo), scanVerdict, repo);
4314
4527
  }
4315
4528
  if (allowRule !== void 0) {
4316
4529
  return allowThenRescan(
4317
4530
  () => {
4318
- const matched = allowFindingsByRule(current.findings, allowRule);
4531
+ const matched = allowFindingsByRule(current.findings, allowRule, repo);
4319
4532
  if (matched === 0) log(`no findings matched rule ${allowRule}; re-scanning`);
4320
4533
  },
4321
4534
  scanVerdict,
4322
- REPO_HOME
4535
+ repo
4323
4536
  );
4324
4537
  }
4325
4538
  if (!isTTYCheck()) {
@@ -4334,9 +4547,9 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
4334
4547
  const { bySession, other } = partitionFindings(unresolved);
4335
4548
  throw new NomadFatal(buildSessionAwareFatal(bySession, other));
4336
4549
  }
4337
- dispatchActions(current.findings, actions, ts, map, nowMs, scan);
4338
- gitOrFatal(["add", "-A"], "git add", REPO_HOME);
4339
- current = scanVerdict();
4550
+ dispatchActions(current.findings, actions, { ts, map, nowMs, repo, scan });
4551
+ gitOrFatal(["add", "-A"], "git add", repo);
4552
+ current = scanVerdict(repo);
4340
4553
  }
4341
4554
  return current;
4342
4555
  }
@@ -4360,7 +4573,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
4360
4573
  `);
4361
4574
  }
4362
4575
  function resolveWorkerPath(deps = {}) {
4363
- const check = deps.existsSyncFn ?? existsSync32;
4576
+ const check = deps.existsSyncFn ?? existsSync33;
4364
4577
  const base = deps.baseUrl ?? import.meta.url;
4365
4578
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
4366
4579
  if (check(mjs)) return mjs;
@@ -4566,17 +4779,19 @@ function handleWedge(repo, forceRemote) {
4566
4779
  function cmdPull(opts = {}) {
4567
4780
  const dryRun = opts.dryRun === true;
4568
4781
  const forceRemote = opts.forceRemote === true;
4569
- if (!existsSync33(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
4570
- if (!existsSync33(join39(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"))) {
4571
4786
  die("repo not initialized; run 'nomad init' to scaffold");
4572
4787
  }
4573
4788
  const handle = acquireLock("pull");
4574
4789
  if (handle === null) process.exit(0);
4575
4790
  try {
4576
- const ts = freshBackupTs(BACKUP_BASE);
4577
- handleWedge(REPO_HOME, forceRemote);
4791
+ const ts = freshBackupTs(backup);
4792
+ handleWedge(repo, forceRemote);
4578
4793
  if (!dryRun) {
4579
- const backupRoot = join39(BACKUP_BASE, ts);
4794
+ const backupRoot = join40(backup, ts);
4580
4795
  try {
4581
4796
  mkdirSync8(backupRoot, { recursive: true });
4582
4797
  } catch (err) {
@@ -4586,9 +4801,9 @@ function cmdPull(opts = {}) {
4586
4801
  log(
4587
4802
  dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
4588
4803
  );
4589
- gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", REPO_HOME);
4590
- const mapPath = join39(REPO_HOME, "path-map.json");
4591
- const map = existsSync33(mapPath) ? readPathMap(mapPath) : { projects: {} };
4804
+ gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
4805
+ const mapPath = join40(repo, "path-map.json");
4806
+ const map = existsSync34(mapPath) ? readPathMap(mapPath) : { projects: {} };
4592
4807
  divergenceCheckExtras(ts);
4593
4808
  if (dryRun) {
4594
4809
  computePreview(ts, map, "pull");
@@ -4610,8 +4825,8 @@ function cmdPull(opts = {}) {
4610
4825
 
4611
4826
  // src/commands.push.ts
4612
4827
  init_config();
4613
- import { existsSync as existsSync35 } from "node:fs";
4614
- import { join as join41, relative as relative5 } from "node:path";
4828
+ import { existsSync as existsSync36 } from "node:fs";
4829
+ import { join as join42, relative as relative5 } from "node:path";
4615
4830
 
4616
4831
  // src/commands.push.allowlist.ts
4617
4832
  init_config();
@@ -4691,9 +4906,9 @@ init_color();
4691
4906
  init_config();
4692
4907
  init_config_sharedDirs_guard();
4693
4908
  import { randomBytes as randomBytes2 } from "node:crypto";
4694
- import { copyFileSync, existsSync as existsSync34, mkdirSync as mkdirSync9, readdirSync as readdirSync11, rmSync as rmSync11 } from "node:fs";
4909
+ import { copyFileSync, existsSync as existsSync35, mkdirSync as mkdirSync9, readdirSync as readdirSync11, rmSync as rmSync12 } from "node:fs";
4695
4910
  import { homedir as homedir5 } from "node:os";
4696
- import { join as join40 } from "node:path";
4911
+ import { join as join41 } from "node:path";
4697
4912
  init_push_leak_verdict();
4698
4913
  init_push_gitleaks();
4699
4914
  init_utils_fs();
@@ -4708,13 +4923,13 @@ function stageSessions(tmpRoot, map) {
4708
4923
  if (!p || p === "TBD") continue;
4709
4924
  reverse.set(encodePath(p), logical);
4710
4925
  }
4711
- const localProjects = join40(CLAUDE_HOME, "projects");
4712
- if (!existsSync34(localProjects)) return 0;
4926
+ const localProjects = join41(claudeHome(), "projects");
4927
+ if (!existsSync35(localProjects)) return 0;
4713
4928
  let staged = 0;
4714
4929
  for (const dir of readdirSync11(localProjects)) {
4715
4930
  const logical = reverse.get(dir);
4716
4931
  if (!logical) continue;
4717
- copyDirJsonlOnly(join40(localProjects, dir), join40(tmpRoot, "shared", "projects", logical));
4932
+ copyDirJsonlOnly(join41(localProjects, dir), join41(tmpRoot, "shared", "projects", logical));
4718
4933
  staged++;
4719
4934
  }
4720
4935
  return staged;
@@ -4730,9 +4945,9 @@ function stageExtras(tmpRoot, map) {
4730
4945
  if (!localRoot || localRoot === "TBD") continue;
4731
4946
  for (const dirname7 of dirnames) {
4732
4947
  if (!whitelist.includes(dirname7)) continue;
4733
- const src = join40(localRoot, dirname7);
4734
- if (!existsSync34(src)) continue;
4735
- const dst = join40(tmpRoot, "shared", "extras", logical, dirname7);
4948
+ const src = join41(localRoot, dirname7);
4949
+ if (!existsSync35(src)) continue;
4950
+ const dst = join41(tmpRoot, "shared", "extras", logical, dirname7);
4736
4951
  copyExtras(src, dst);
4737
4952
  staged++;
4738
4953
  }
@@ -4740,19 +4955,19 @@ function stageExtras(tmpRoot, map) {
4740
4955
  return staged;
4741
4956
  }
4742
4957
  function previewPushLeaks(map) {
4743
- const cacheDir = join40(homedir5(), ".cache", "claude-nomad");
4958
+ const cacheDir = join41(homedir5(), ".cache", "claude-nomad");
4744
4959
  mkdirSync9(cacheDir, { recursive: true });
4745
4960
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
4746
- const tmpRoot = join40(cacheDir, `push-preview-tree-${stamp}`);
4961
+ const tmpRoot = join41(cacheDir, `push-preview-tree-${stamp}`);
4747
4962
  try {
4748
4963
  const sessionCount = stageSessions(tmpRoot, map);
4749
4964
  const extrasCount = stageExtras(tmpRoot, map);
4750
4965
  if (sessionCount + extrasCount === 0) {
4751
4966
  return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
4752
4967
  }
4753
- const ignoreFile = join40(REPO_HOME, ".gitleaksignore");
4754
- if (existsSync34(ignoreFile)) {
4755
- copyFileSync(ignoreFile, join40(tmpRoot, ".gitleaksignore"));
4968
+ const ignoreFile = join41(repoHome(), ".gitleaksignore");
4969
+ if (existsSync35(ignoreFile)) {
4970
+ copyFileSync(ignoreFile, join41(tmpRoot, ".gitleaksignore"));
4756
4971
  }
4757
4972
  let findings;
4758
4973
  try {
@@ -4765,7 +4980,7 @@ function previewPushLeaks(map) {
4765
4980
  }
4766
4981
  return verdictFromFindings(findings);
4767
4982
  } finally {
4768
- rmSync11(tmpRoot, { recursive: true, force: true });
4983
+ rmSync12(tmpRoot, { recursive: true, force: true });
4769
4984
  }
4770
4985
  }
4771
4986
 
@@ -4773,11 +4988,11 @@ function previewPushLeaks(map) {
4773
4988
  init_utils();
4774
4989
  init_utils_fs();
4775
4990
  init_utils_json();
4776
- function guardGitlinks() {
4777
- const gitlinks = findGitlinks(join41(REPO_HOME, "shared"));
4991
+ function guardGitlinks(repo) {
4992
+ const gitlinks = findGitlinks(join42(repo, "shared"));
4778
4993
  if (gitlinks.length === 0) return;
4779
4994
  for (const p of gitlinks) {
4780
- const rel = relative5(REPO_HOME, p);
4995
+ const rel = relative5(repo, p);
4781
4996
  fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
4782
4997
  }
4783
4998
  const noun = gitlinks.length === 1 ? "entry" : "entries";
@@ -4785,15 +5000,15 @@ function guardGitlinks() {
4785
5000
  `gitlink trap: ${gitlinks.length} nested .git ${noun} in shared/; remove before retry`
4786
5001
  );
4787
5002
  }
4788
- async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule) {
4789
- gitOrFatal(["add", "-A"], "git add", REPO_HOME);
4790
- 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));
4791
5006
  if (verdict.leak) {
4792
5007
  renderPushTree(st, verdict);
4793
5008
  verdict = await resolveLeakFindings(verdict, ts, map, { redactAll, allowAll, allowRule });
4794
5009
  }
4795
- gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit", REPO_HOME);
4796
- 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));
4797
5012
  renderPushTree(st, verdict);
4798
5013
  }
4799
5014
  function runDryRunPreview(st, map) {
@@ -4826,33 +5041,35 @@ async function cmdPush(opts = {}) {
4826
5041
  const allowAll = opts.allowAll === true;
4827
5042
  const allowRule = opts.allowRule;
4828
5043
  guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
4829
- if (!existsSync35(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}`);
4830
5047
  const handle = acquireLock("push");
4831
5048
  if (handle === null) process.exit(0);
4832
5049
  try {
4833
5050
  console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
4834
5051
  probeGitleaks();
4835
- withSpinner("Rebasing onto origin", rebaseBeforePush);
4836
- const ts = freshBackupTs(BACKUP_BASE);
5052
+ withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
5053
+ const ts = freshBackupTs(backup);
4837
5054
  const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun }));
4838
5055
  const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
4839
5056
  const st = { dryRun, remap, extras };
4840
- guardGitlinks();
4841
- const status = gitStatusPorcelainZ(REPO_HOME, { untrackedAll: true });
5057
+ guardGitlinks(repo);
5058
+ const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
4842
5059
  if (!dryRun && !status) {
4843
5060
  log("nothing to commit");
4844
5061
  renderNoScanTree(st);
4845
5062
  return;
4846
5063
  }
4847
- const mapPath = join41(REPO_HOME, "path-map.json");
4848
- if (!existsSync35(mapPath)) {
5064
+ const mapPath = join42(repo, "path-map.json");
5065
+ if (!existsSync36(mapPath)) {
4849
5066
  if (dryRun) return runDryRunPreview(st, null);
4850
5067
  die("path-map.json missing, cannot enforce push allow-list");
4851
5068
  }
4852
5069
  const map = readPathMap(mapPath);
4853
5070
  if (status) enforceAllowList(status, map);
4854
5071
  if (dryRun) return runDryRunPreview(st, map);
4855
- await commitAndPush(st, ts, map, redactAll, allowAll, allowRule);
5072
+ await commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo);
4856
5073
  } catch (err) {
4857
5074
  if (err instanceof NomadFatal) {
4858
5075
  fail(err.message);
@@ -4885,17 +5102,18 @@ init_config();
4885
5102
 
4886
5103
  // src/diff.ts
4887
5104
  init_config();
4888
- import { existsSync as existsSync36 } from "node:fs";
4889
- import { join as join42 } from "node:path";
5105
+ import { existsSync as existsSync37 } from "node:fs";
5106
+ import { join as join43 } from "node:path";
4890
5107
  init_utils();
4891
5108
  init_utils_fs();
4892
5109
  init_utils_json();
4893
5110
  function cmdDiff() {
4894
5111
  try {
4895
- if (!existsSync36(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
4896
- const ts = freshBackupTs(BACKUP_BASE);
4897
- const mapPath = join42(REPO_HOME, "path-map.json");
4898
- const map = existsSync36(mapPath) ? readPathMap(mapPath) : { projects: {} };
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");
5116
+ const map = existsSync37(mapPath) ? readPathMap(mapPath) : { projects: {} };
4899
5117
  computePreview(ts, map, "diff");
4900
5118
  } catch (err) {
4901
5119
  if (err instanceof NomadFatal) {
@@ -4909,8 +5127,8 @@ function cmdDiff() {
4909
5127
 
4910
5128
  // src/init.ts
4911
5129
  init_config();
4912
- import { existsSync as existsSync38, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "node:fs";
4913
- import { join as join44 } from "node:path";
5130
+ import { existsSync as existsSync39, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "node:fs";
5131
+ import { join as join45 } from "node:path";
4914
5132
 
4915
5133
  // src/init.gh-onboard.ts
4916
5134
  init_config();
@@ -4927,8 +5145,9 @@ function ensureOriginRepo(repoName, run = execFileSync16) {
4927
5145
  `invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
4928
5146
  );
4929
5147
  }
5148
+ const repo = repoHome();
4930
5149
  try {
4931
- readOriginRemote(REPO_HOME, run);
5150
+ readOriginRemote(repo, run);
4932
5151
  return;
4933
5152
  } catch {
4934
5153
  }
@@ -4943,7 +5162,7 @@ function ensureOriginRepo(repoName, run = execFileSync16) {
4943
5162
  die("gh CLI is not authenticated. Run `gh auth login` and retry.");
4944
5163
  }
4945
5164
  try {
4946
- run("git", ["init", "-b", "main"], { cwd: REPO_HOME, stdio: ["ignore", "ignore", "pipe"] });
5165
+ run("git", ["init", "-b", "main"], { cwd: repo, stdio: ["ignore", "ignore", "pipe"] });
4947
5166
  } catch (err) {
4948
5167
  const e = err;
4949
5168
  throw new NomadFatal(`git init failed: ${e.message}`);
@@ -4976,7 +5195,7 @@ function ensureOriginRepo(repoName, run = execFileSync16) {
4976
5195
  }
4977
5196
  try {
4978
5197
  run("git", ["remote", "add", "origin", `git@github.com:${owner}/${repoName}.git`], {
4979
- cwd: REPO_HOME,
5198
+ cwd: repo,
4980
5199
  stdio: ["ignore", "ignore", "pipe"]
4981
5200
  });
4982
5201
  } catch (err) {
@@ -4991,31 +5210,33 @@ init_config();
4991
5210
  init_utils();
4992
5211
  init_utils_fs();
4993
5212
  init_utils_json();
4994
- import { copyFileSync as copyFileSync2, cpSync as cpSync6, existsSync as existsSync37, rmSync as rmSync12, statSync as statSync9 } from "node:fs";
4995
- import { join as join43 } from "node:path";
5213
+ import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync38, rmSync as rmSync13, statSync as statSync9 } from "node:fs";
5214
+ import { join as join44 } from "node:path";
4996
5215
  function snapshotIntoShared(map) {
5216
+ const repo = repoHome();
5217
+ const claude = claudeHome();
4997
5218
  for (const name of allSharedLinks(map)) {
4998
- const src = join43(CLAUDE_HOME, name);
4999
- if (!existsSync37(src)) continue;
5000
- const dst = join43(REPO_HOME, "shared", name);
5219
+ const src = join44(claude, name);
5220
+ if (!existsSync38(src)) continue;
5221
+ const dst = join44(repo, "shared", name);
5001
5222
  if (statSync9(src).isDirectory()) {
5002
- const gk = join43(dst, ".gitkeep");
5003
- if (existsSync37(gk)) rmSync12(gk);
5004
- cpSync6(src, dst, { recursive: true, force: false, errorOnExist: true });
5223
+ const gk = join44(dst, ".gitkeep");
5224
+ if (existsSync38(gk)) rmSync13(gk);
5225
+ cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
5005
5226
  } else {
5006
5227
  copyFileSync2(src, dst);
5007
5228
  }
5008
5229
  log(`snapshotted shared/${name} from ${src}`);
5009
5230
  }
5010
- const userSettings = join43(CLAUDE_HOME, "settings.json");
5011
- if (existsSync37(userSettings)) {
5231
+ const userSettings = join44(claude, "settings.json");
5232
+ if (existsSync38(userSettings)) {
5012
5233
  let parsed;
5013
5234
  try {
5014
5235
  parsed = readJson(userSettings);
5015
5236
  } catch (err) {
5016
5237
  return die(`malformed ${userSettings}: ${err.message}`);
5017
5238
  }
5018
- const hostFile = join43(REPO_HOME, "hosts", `${HOST}.json`);
5239
+ const hostFile = join44(repo, "hosts", `${HOST}.json`);
5019
5240
  writeJsonAtomic(hostFile, parsed);
5020
5241
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
5021
5242
  }
@@ -5026,62 +5247,64 @@ init_utils();
5026
5247
  init_utils_fs();
5027
5248
  var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.claude/CLAUDE.md by nomad pull -->\n";
5028
5249
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
5029
- function preflightConflict(repoHome) {
5250
+ function preflightConflict(repoHome2) {
5030
5251
  const candidates = [
5031
- join44(repoHome, "shared", "settings.base.json"),
5032
- join44(repoHome, "shared", "CLAUDE.md"),
5033
- join44(repoHome, "path-map.json"),
5034
- join44(repoHome, "hosts"),
5035
- join44(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")
5036
5257
  ];
5037
5258
  for (const c of candidates) {
5038
- if (existsSync38(c)) return c;
5259
+ if (existsSync39(c)) return c;
5039
5260
  }
5040
5261
  return null;
5041
5262
  }
5042
5263
  function cmdInit(opts = {}) {
5043
5264
  const snapshot = opts.snapshot === true;
5044
5265
  const keepActions = opts.keepActions === true;
5045
- mkdirSync10(REPO_HOME, { recursive: true });
5046
- const conflict = preflightConflict(REPO_HOME);
5266
+ const repo = repoHome();
5267
+ const claude = claudeHome();
5268
+ mkdirSync10(repo, { recursive: true });
5269
+ const conflict = preflightConflict(repo);
5047
5270
  if (conflict !== null) {
5048
5271
  die(`already initialized; refusing to clobber ${conflict}`);
5049
5272
  }
5050
5273
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
5051
- mkdirSync10(join44(REPO_HOME, "shared"), { recursive: true });
5052
- mkdirSync10(join44(REPO_HOME, "hosts"), { recursive: true });
5274
+ mkdirSync10(join45(repo, "shared"), { recursive: true });
5275
+ mkdirSync10(join45(repo, "hosts"), { recursive: true });
5053
5276
  for (const name of SHARED_KEEP_DIRS) {
5054
- mkdirSync10(join44(REPO_HOME, "shared", name), { recursive: true });
5277
+ mkdirSync10(join45(repo, "shared", name), { recursive: true });
5055
5278
  }
5056
- const userClaudeMd = join44(CLAUDE_HOME, "CLAUDE.md");
5057
- if (!snapshot || !existsSync38(userClaudeMd)) {
5058
- writeFileSync6(join44(REPO_HOME, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
5059
- log("created shared/CLAUDE.md");
5279
+ const userClaudeMd = join45(claude, "CLAUDE.md");
5280
+ if (!snapshot || !existsSync39(userClaudeMd)) {
5281
+ writeFileSync6(join45(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
5282
+ item("created shared/CLAUDE.md");
5060
5283
  }
5061
5284
  for (const name of SHARED_KEEP_DIRS) {
5062
- writeFileSync6(join44(REPO_HOME, "shared", name, ".gitkeep"), "");
5063
- log(`created shared/${name}/.gitkeep`);
5064
- }
5065
- writeFileSync6(join44(REPO_HOME, "hosts", ".gitkeep"), "");
5066
- log("created hosts/.gitkeep");
5067
- writeJsonAtomic(join44(REPO_HOME, "shared", "settings.base.json"), {});
5068
- log("created shared/settings.base.json");
5069
- writeJsonAtomic(join44(REPO_HOME, "path-map.json"), { projects: {} });
5070
- 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");
5071
5294
  if (snapshot) {
5072
5295
  snapshotIntoShared({ projects: {} });
5073
5296
  log(`snapshot staged in shared/; review, then 'nomad push' to share with other hosts.`);
5074
5297
  log("~/.claude/ originals were NOT removed.");
5075
5298
  }
5076
5299
  if (!keepActions) {
5077
- maybeDisableRepoActions(REPO_HOME, opts.run);
5300
+ maybeDisableRepoActions(repo, opts.run);
5078
5301
  }
5079
5302
  log("init complete");
5080
5303
  }
5081
- function maybeDisableRepoActions(repoHome, run) {
5304
+ function maybeDisableRepoActions(repoHome2, run) {
5082
5305
  let remote;
5083
5306
  try {
5084
- remote = readOriginRemote(repoHome, run);
5307
+ remote = readOriginRemote(repoHome2, run);
5085
5308
  } catch {
5086
5309
  return;
5087
5310
  }
@@ -5260,6 +5483,23 @@ function parseCleanArgs(argv) {
5260
5483
  return { dryRun: st.dryRun, olderThan: st.olderThan, keep: st.keep };
5261
5484
  }
5262
5485
 
5486
+ // src/nomad.dispatch.eject.ts
5487
+ function parseEjectArgs(argv) {
5488
+ let dryRun = false;
5489
+ let i = 3;
5490
+ while (i < argv.length) {
5491
+ const token = argv[i];
5492
+ if (token === "--dry-run") {
5493
+ if (dryRun) return null;
5494
+ dryRun = true;
5495
+ } else {
5496
+ return null;
5497
+ }
5498
+ i++;
5499
+ }
5500
+ return { dryRun };
5501
+ }
5502
+
5263
5503
  // src/nomad.dispatch.allow.ts
5264
5504
  function parseAllowArgs(argv) {
5265
5505
  const positionals = argv.slice(3);
@@ -5350,7 +5590,7 @@ function parsePushArgs(argv) {
5350
5590
  // package.json
5351
5591
  var package_default = {
5352
5592
  name: "claude-nomad",
5353
- version: "0.43.0",
5593
+ version: "0.44.1",
5354
5594
  type: "module",
5355
5595
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
5356
5596
  keywords: [
@@ -5417,6 +5657,8 @@ var package_default = {
5417
5657
  "@commitlint/cli": "^21.0.1",
5418
5658
  "@commitlint/config-conventional": "^21.0.1",
5419
5659
  "@eslint/js": "^10.0.1",
5660
+ "@stryker-mutator/core": "9.6.1",
5661
+ "@stryker-mutator/vitest-runner": "9.6.1",
5420
5662
  "@types/node": "^22.0.0",
5421
5663
  "@vitest/coverage-v8": "^4.1.6",
5422
5664
  diff: "^9.0.0",
@@ -5509,6 +5751,11 @@ var DEFAULT_HELP = [
5509
5751
  cont("symlink, and stage for push. <name> must be in SHARED_LINKS or sharedDirs."),
5510
5752
  row(" --dry-run", "Preview backup, move, and git-add without writing."),
5511
5753
  "",
5754
+ row(" eject", "Materialize every managed ~/.claude/ symlink into a real copy so the"),
5755
+ cont("setup keeps working after deleting the sync repo. Prints a"),
5756
+ cont("manual-remainder checklist (uninstall CLI, drop env vars, optional deletes)."),
5757
+ row(" --dry-run", "List what would be materialized without writing anything."),
5758
+ "",
5512
5759
  row(
5513
5760
  " redact <session-id>",
5514
5761
  "Rewrite the secret span in the local source transcript for a session,"
@@ -5545,15 +5792,15 @@ var DEFAULT_HELP = [
5545
5792
  init_config();
5546
5793
  init_utils();
5547
5794
  init_utils_json();
5548
- import { existsSync as existsSync39, readFileSync as readFileSync12, readdirSync as readdirSync12 } from "node:fs";
5549
- import { join as join45 } from "node:path";
5795
+ import { existsSync as existsSync40, readFileSync as readFileSync12, readdirSync as readdirSync12 } from "node:fs";
5796
+ import { join as join46 } from "node:path";
5550
5797
  function resumeCmd(sessionId) {
5551
5798
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
5552
5799
  fail(`invalid session id: ${sessionId}`);
5553
5800
  process.exit(1);
5554
5801
  }
5555
- const projectsRoot = join45(CLAUDE_HOME, "projects");
5556
- if (!existsSync39(projectsRoot)) {
5802
+ const projectsRoot = join46(claudeHome(), "projects");
5803
+ if (!existsSync40(projectsRoot)) {
5557
5804
  fail(`${projectsRoot} does not exist`);
5558
5805
  process.exit(1);
5559
5806
  }
@@ -5567,8 +5814,8 @@ function resumeCmd(sessionId) {
5567
5814
  fail(`no cwd field found in ${jsonlPath}`);
5568
5815
  process.exit(1);
5569
5816
  }
5570
- const mapPath = join45(REPO_HOME, "path-map.json");
5571
- if (!existsSync39(mapPath)) {
5817
+ const mapPath = join46(repoHome(), "path-map.json");
5818
+ if (!existsSync40(mapPath)) {
5572
5819
  fail("path-map.json missing");
5573
5820
  process.exit(1);
5574
5821
  }
@@ -5591,8 +5838,8 @@ function resumeCmd(sessionId) {
5591
5838
  }
5592
5839
  function findTranscriptPath(projectsRoot, sessionId) {
5593
5840
  for (const dir of readdirSync12(projectsRoot)) {
5594
- const candidate = join45(projectsRoot, dir, `${sessionId}.jsonl`);
5595
- if (existsSync39(candidate)) return candidate;
5841
+ const candidate = join46(projectsRoot, dir, `${sessionId}.jsonl`);
5842
+ if (existsSync40(candidate)) return candidate;
5596
5843
  }
5597
5844
  return null;
5598
5845
  }
@@ -5647,7 +5894,8 @@ function shQuote(s) {
5647
5894
 
5648
5895
  // src/nomad.ts
5649
5896
  init_utils();
5650
- if (!HOME) {
5897
+ var h = home();
5898
+ if (!h) {
5651
5899
  fail(
5652
5900
  "could not determine home directory (HOME env unset and no uid mapping). Set HOME and retry."
5653
5901
  );
@@ -5726,6 +5974,15 @@ try {
5726
5974
  cmdAdopt(name, { dryRun: sub === "--dry-run" });
5727
5975
  break;
5728
5976
  }
5977
+ case "eject": {
5978
+ const ejectArgs = parseEjectArgs(process.argv);
5979
+ if (ejectArgs === null) {
5980
+ console.error("usage: nomad eject [--dry-run]");
5981
+ process.exit(1);
5982
+ }
5983
+ cmdEject({ dryRun: ejectArgs.dryRun });
5984
+ break;
5985
+ }
5729
5986
  case "doctor":
5730
5987
  if (process.argv[3] === void 0) {
5731
5988
  cmdDoctor();