claude-nomad 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nomad.mjs CHANGED
@@ -561,31 +561,31 @@ var init_utils_fs = __esm({
561
561
  });
562
562
 
563
563
  // src/push-gitleaks.config.ts
564
- import { existsSync as existsSync10, mkdtempSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
564
+ import { existsSync as existsSync11, mkdtempSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
565
565
  import { tmpdir } from "node:os";
566
- import { join as join11 } from "node:path";
566
+ import { join as join12 } from "node:path";
567
567
  import { fileURLToPath } from "node:url";
568
568
  function resolveTomlPath() {
569
- const repoToml = join11(REPO_HOME, ".gitleaks.toml");
570
- if (existsSync10(repoToml)) return repoToml;
569
+ const repoToml = join12(REPO_HOME, ".gitleaks.toml");
570
+ if (existsSync11(repoToml)) return repoToml;
571
571
  const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
572
- return existsSync10(bundled) ? bundled : null;
572
+ return existsSync11(bundled) ? bundled : null;
573
573
  }
574
574
  function buildOverlayTempConfig(overlayBody, bundled) {
575
575
  const tempBody = `[extend]
576
576
  path = ${JSON.stringify(bundled)}
577
577
 
578
578
  ${overlayBody}`;
579
- const tempPath = mkdtempSync(join11(tmpdir(), "nomad-gitleaks-cfg-"));
580
- const configPath = join11(tempPath, "config.toml");
579
+ const tempPath = mkdtempSync(join12(tmpdir(), "nomad-gitleaks-cfg-"));
580
+ const configPath = join12(tempPath, "config.toml");
581
581
  writeFileSync2(configPath, tempBody, { mode: 384, flag: "wx" });
582
582
  return { configPath, tempPath };
583
583
  }
584
584
  function resolveTomlConfig() {
585
- const overlayPath = join11(REPO_HOME, ".gitleaks.overlay.toml");
586
- const repoToml = join11(REPO_HOME, ".gitleaks.toml");
585
+ const overlayPath = join12(REPO_HOME, ".gitleaks.overlay.toml");
586
+ const repoToml = join12(REPO_HOME, ".gitleaks.toml");
587
587
  const bundled = resolveTomlPath();
588
- if (!existsSync10(overlayPath)) {
588
+ if (!existsSync11(overlayPath)) {
589
589
  return { path: bundled, tempPath: null };
590
590
  }
591
591
  if (bundled === repoToml) {
@@ -626,9 +626,9 @@ var init_push_gitleaks_config = __esm({
626
626
 
627
627
  // src/push-checks.ts
628
628
  import { execFileSync as execFileSync2 } from "node:child_process";
629
- import { readdirSync as readdirSync3, rmSync as rmSync3 } from "node:fs";
629
+ import { readdirSync as readdirSync4, rmSync as rmSync4 } from "node:fs";
630
630
  import { homedir as homedir2, platform } from "node:os";
631
- import { join as join12 } from "node:path";
631
+ import { join as join13 } from "node:path";
632
632
  function gitleaksInstallHint() {
633
633
  const head = "gitleaks not on PATH (required for nomad push). Install:";
634
634
  const plat = platform();
@@ -666,12 +666,12 @@ function findGitlinks(dir) {
666
666
  function walk(current) {
667
667
  let entries;
668
668
  try {
669
- entries = readdirSync3(current, { withFileTypes: true });
669
+ entries = readdirSync4(current, { withFileTypes: true });
670
670
  } catch {
671
671
  return;
672
672
  }
673
673
  for (const e of entries) {
674
- const p = join12(current, e.name);
674
+ const p = join13(current, e.name);
675
675
  if (e.name === ".git") {
676
676
  hits.push(p);
677
677
  continue;
@@ -693,7 +693,7 @@ function probeGitleaks() {
693
693
  if (e.code === "ENOENT") throw new NomadFatal(gitleaksInstallHint());
694
694
  throw new NomadFatal(`gitleaks --version failed: ${e.message}`);
695
695
  } finally {
696
- if (tempPath !== null) rmSync3(tempPath, { recursive: true, force: true });
696
+ if (tempPath !== null) rmSync4(tempPath, { recursive: true, force: true });
697
697
  }
698
698
  }
699
699
  function rebaseBeforePush() {
@@ -721,9 +721,9 @@ var init_push_checks = __esm({
721
721
 
722
722
  // src/push-gitleaks.scan.ts
723
723
  import { execFileSync as execFileSync5 } from "node:child_process";
724
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync4 } from "node:fs";
724
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync5 } from "node:fs";
725
725
  import { homedir as homedir3 } from "node:os";
726
- import { join as join16 } from "node:path";
726
+ import { join as join17 } from "node:path";
727
727
  function readGitleaksReport(reportPath) {
728
728
  try {
729
729
  const raw = readFileSync4(reportPath, "utf8");
@@ -735,9 +735,9 @@ function readGitleaksReport(reportPath) {
735
735
  }
736
736
  }
737
737
  function scanStagedTree(repoDir, forwardStreams = false) {
738
- const cacheDir = join16(homedir3(), ".cache", "claude-nomad");
738
+ const cacheDir = join17(homedir3(), ".cache", "claude-nomad");
739
739
  mkdirSync2(cacheDir, { recursive: true });
740
- const reportPath = join16(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
740
+ const reportPath = join17(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
741
741
  const { path: toml, tempPath } = resolveTomlConfig();
742
742
  const args = [
743
743
  "protect",
@@ -764,14 +764,14 @@ function scanStagedTree(repoDir, forwardStreams = false) {
764
764
  }
765
765
  return report;
766
766
  } finally {
767
- if (tempPath !== null) rmSync4(tempPath, { recursive: true, force: true });
768
- rmSync4(reportPath, { force: true });
767
+ if (tempPath !== null) rmSync5(tempPath, { recursive: true, force: true });
768
+ rmSync5(reportPath, { force: true });
769
769
  }
770
770
  }
771
771
  function scanFile(filePath, forwardStreams = false) {
772
- const cacheDir = join16(homedir3(), ".cache", "claude-nomad");
772
+ const cacheDir = join17(homedir3(), ".cache", "claude-nomad");
773
773
  mkdirSync2(cacheDir, { recursive: true });
774
- const reportPath = join16(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
774
+ const reportPath = join17(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
775
775
  const { path: toml, tempPath } = resolveTomlConfig();
776
776
  const args = [
777
777
  "detect",
@@ -796,8 +796,8 @@ function scanFile(filePath, forwardStreams = false) {
796
796
  }
797
797
  return report;
798
798
  } finally {
799
- if (tempPath !== null) rmSync4(tempPath, { recursive: true, force: true });
800
- rmSync4(reportPath, { force: true });
799
+ if (tempPath !== null) rmSync5(tempPath, { recursive: true, force: true });
800
+ rmSync5(reportPath, { force: true });
801
801
  }
802
802
  }
803
803
  var init_push_gitleaks_scan = __esm({
@@ -1219,15 +1219,178 @@ function cmdClean(opts, backupBase = BACKUP_BASE) {
1219
1219
  log(`removed ${targets.length} backup(s)`);
1220
1220
  }
1221
1221
 
1222
+ // src/commands.eject.ts
1223
+ init_config();
1224
+ init_utils();
1225
+ init_utils_json();
1226
+ import { cpSync as cpSync3, existsSync as existsSync5, lstatSync as lstatSync4, realpathSync, renameSync as renameSync2, rmSync as rmSync3 } from "node:fs";
1227
+ import { join as join6, sep } from "node:path";
1228
+ var EJECT_CHECKLIST = [
1229
+ "Manual steps remaining to finish leaving claude-nomad on this host:",
1230
+ ` 1. Uninstall the CLI: npm uninstall -g claude-nomad`,
1231
+ ` 2. Remove NOMAD_HOST and NOMAD_REPO from your shell rc (~/.zshrc or ~/.bashrc)`,
1232
+ ` 3. Optionally delete the local sync checkout: rm -rf ${REPO_HOME}`,
1233
+ ` 4. Optionally delete the private sync repo on GitHub`,
1234
+ ` 5. Optionally delete the backup cache: rm -rf ${BACKUP_BASE}`
1235
+ ].join("\n");
1236
+ var DEFAULT_ROOTS = { claudeHome: CLAUDE_HOME, repoHome: REPO_HOME };
1237
+ function errMessage(err) {
1238
+ return err instanceof Error ? err.message : String(err);
1239
+ }
1240
+ function lexists2(p) {
1241
+ try {
1242
+ lstatSync4(p);
1243
+ return true;
1244
+ } catch {
1245
+ return false;
1246
+ }
1247
+ }
1248
+ function readMapIfPresent2(repoHome) {
1249
+ const mapPath = join6(repoHome, "path-map.json");
1250
+ return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
1251
+ }
1252
+ function classifyName(linkPath) {
1253
+ if (!lexists2(linkPath)) return "absent";
1254
+ if (!lstatSync4(linkPath).isSymbolicLink()) return "skip-real";
1255
+ if (!existsSync5(linkPath)) return "dangling";
1256
+ return "materialize";
1257
+ }
1258
+ function resolveSharedRoot(repoHome) {
1259
+ try {
1260
+ return realpathSync(join6(repoHome, "shared"));
1261
+ } catch {
1262
+ return die(
1263
+ `cannot resolve ${join6(repoHome, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
1264
+ );
1265
+ }
1266
+ }
1267
+ function isManagedTarget(target, sharedRoot) {
1268
+ return target.startsWith(sharedRoot + sep);
1269
+ }
1270
+ function materializeOne(name, linkPath, sharedRoot) {
1271
+ const target = realpathSync(linkPath);
1272
+ if (!isManagedTarget(target, sharedRoot)) {
1273
+ log(`skipped (not a nomad-managed target): ${name} -> ${target}`);
1274
+ return false;
1275
+ }
1276
+ const tmp = `${linkPath}.eject.tmp.${process.pid}.${Date.now()}`;
1277
+ try {
1278
+ rmSync3(tmp, { recursive: true, force: true });
1279
+ cpSync3(target, tmp, {
1280
+ recursive: true,
1281
+ force: true,
1282
+ dereference: true,
1283
+ preserveTimestamps: true
1284
+ });
1285
+ rmSync3(linkPath, { force: true });
1286
+ renameSync2(tmp, linkPath);
1287
+ log(`ejected: ${name}`);
1288
+ return true;
1289
+ } catch (err) {
1290
+ try {
1291
+ rmSync3(tmp, { recursive: true, force: true });
1292
+ } catch {
1293
+ }
1294
+ throw err;
1295
+ }
1296
+ }
1297
+ function previewDryRun(names, classifications, claudeHome, sharedRoot) {
1298
+ for (const name of names) {
1299
+ const cls = classifications.get(name);
1300
+ const linkPath = join6(claudeHome, name);
1301
+ if (cls === "absent") {
1302
+ log(`skipped (absent): ${name}`);
1303
+ } else if (cls === "skip-real") {
1304
+ log(`skipped (not a symlink): ${name}`);
1305
+ } else {
1306
+ previewMaterialize(name, linkPath, sharedRoot);
1307
+ }
1308
+ }
1309
+ log(EJECT_CHECKLIST);
1310
+ }
1311
+ function previewMaterialize(name, linkPath, sharedRoot) {
1312
+ let target;
1313
+ try {
1314
+ target = realpathSync(linkPath);
1315
+ } catch {
1316
+ log(`would materialize: ${name} (target now unresolvable; re-run to re-classify)`);
1317
+ return;
1318
+ }
1319
+ if (!isManagedTarget(target, sharedRoot)) {
1320
+ log(`skipped (not a nomad-managed target): ${name} -> ${target}`);
1321
+ return;
1322
+ }
1323
+ log(`would materialize: ${name} (copy ${target} -> ${linkPath})`);
1324
+ }
1325
+ function runLiveEject(names, classifications, claudeHome, sharedRoot) {
1326
+ const done = [];
1327
+ let skipped = 0;
1328
+ for (const name of names) {
1329
+ const cls = classifications.get(name);
1330
+ const linkPath = join6(claudeHome, name);
1331
+ if (cls === "absent") {
1332
+ log(`skipped (absent): ${name}`);
1333
+ skipped++;
1334
+ } else if (cls === "skip-real") {
1335
+ log(`skipped (not a symlink): ${name}`);
1336
+ skipped++;
1337
+ } else if (materializeOneOrDie(name, linkPath, sharedRoot, done)) {
1338
+ done.push(name);
1339
+ } else {
1340
+ skipped++;
1341
+ }
1342
+ }
1343
+ log(`materialized ${done.length}, skipped ${skipped}`);
1344
+ log(EJECT_CHECKLIST);
1345
+ }
1346
+ function materializeOneOrDie(name, linkPath, sharedRoot, done) {
1347
+ try {
1348
+ return materializeOne(name, linkPath, sharedRoot);
1349
+ } catch (err) {
1350
+ const msg = errMessage(err);
1351
+ return die(
1352
+ `failed to materialize ${name}: ${msg}. already materialized: ${done.join(", ") || "(none)"}. the remaining names are still symlinks; do NOT delete ${REPO_HOME} yet, fix the cause and re-run \`nomad eject\` (it is idempotent on already-real names)`
1353
+ );
1354
+ }
1355
+ }
1356
+ function cmdEject(opts = {}, roots = DEFAULT_ROOTS) {
1357
+ const dryRun = opts.dryRun === true;
1358
+ const { claudeHome, repoHome } = roots;
1359
+ const map = readMapIfPresent2(repoHome);
1360
+ const names = allSharedLinks(map);
1361
+ const classifications = /* @__PURE__ */ new Map();
1362
+ for (const name of names) {
1363
+ classifications.set(name, classifyName(join6(claudeHome, name)));
1364
+ }
1365
+ const dangling = names.filter((n) => classifications.get(n) === "dangling");
1366
+ if (dangling.length > 0) {
1367
+ fail(
1368
+ `dangling symlink(s): ${dangling.join(", ")}. run \`nomad pull\` first to restore the missing target, then re-run \`nomad eject\``
1369
+ );
1370
+ process.exit(1);
1371
+ }
1372
+ const sharedRoot = resolveSharedRoot(repoHome);
1373
+ if (dryRun) {
1374
+ previewDryRun(names, classifications, claudeHome, sharedRoot);
1375
+ return;
1376
+ }
1377
+ try {
1378
+ runLiveEject(names, classifications, claudeHome, sharedRoot);
1379
+ } catch (err) {
1380
+ fail(errMessage(err));
1381
+ process.exit(1);
1382
+ }
1383
+ }
1384
+
1222
1385
  // src/commands.doctor.ts
1223
- import { existsSync as existsSync19 } from "node:fs";
1224
- import { join as join23 } from "node:path";
1386
+ import { existsSync as existsSync22 } from "node:fs";
1387
+ import { join as join26 } from "node:path";
1225
1388
 
1226
1389
  // src/commands.doctor.checks.repo.ts
1227
1390
  init_color();
1228
1391
  init_config();
1229
- import { existsSync as existsSync6, lstatSync as lstatSync4, statSync as statSync3 } from "node:fs";
1230
- import { join as join7 } from "node:path";
1392
+ import { existsSync as existsSync7, lstatSync as lstatSync5, statSync as statSync3 } from "node:fs";
1393
+ import { join as join8 } from "node:path";
1231
1394
 
1232
1395
  // src/commands.doctor.format.ts
1233
1396
  init_color();
@@ -1241,6 +1404,9 @@ function section(header, raw = false) {
1241
1404
  function addItem(s, text) {
1242
1405
  s.items.push(text);
1243
1406
  }
1407
+ function addChildItem(s, text) {
1408
+ s.items.push(` ${text}`);
1409
+ }
1244
1410
  function sectionFailed(s) {
1245
1411
  return s.items.some((line) => line.includes(failGlyph));
1246
1412
  }
@@ -1257,12 +1423,27 @@ function renderSection(s) {
1257
1423
  }
1258
1424
  const header = sectionFailed(s) ? `${red(FAIL_GLYPH_BARE)} ${s.header}` : s.header;
1259
1425
  console.log(header);
1260
- const lastContent = s.items.reduce((acc, item, j) => item === "" ? acc : j, -1);
1426
+ const lastContent = s.items.reduce(
1427
+ (acc, item, j) => item === "" || isChild(item) ? acc : j,
1428
+ -1
1429
+ );
1261
1430
  for (let j = 0; j < s.items.length; j++) {
1262
- if (s.items[j] === "") console.log("");
1263
- else console.log(`${j === lastContent ? " \u2514 " : " \u251C "}${s.items[j]}`);
1431
+ const item = s.items[j];
1432
+ if (item === "") console.log("");
1433
+ else if (isChild(item)) console.log(renderChildLine(s.items, j));
1434
+ else console.log(`${j === lastContent ? " \u2514 " : " \u251C "}${item}`);
1264
1435
  }
1265
1436
  }
1437
+ function isChild(item) {
1438
+ return item.startsWith(" ");
1439
+ }
1440
+ function renderChildLine(items, j) {
1441
+ const parentContinues = items.some((it, k) => k > j && it !== "" && !isChild(it));
1442
+ const gutter = parentContinues ? " \u2502 " : " ";
1443
+ const next = items[j + 1];
1444
+ const elbow = next === void 0 || !isChild(next) ? "\u2514 " : "\u251C ";
1445
+ return `${gutter} ${elbow}${items[j].slice(1)}`;
1446
+ }
1266
1447
  function renderTree(sections) {
1267
1448
  const visible = sections.filter((s) => s.items.length > 0);
1268
1449
  for (let i = 0; i < visible.length; i++) {
@@ -1287,15 +1468,15 @@ function readJsonSafe(path, label, section2) {
1287
1468
  // src/init.classify.ts
1288
1469
  init_config();
1289
1470
  init_utils_json();
1290
- import { existsSync as existsSync5 } from "node:fs";
1291
- import { join as join6 } from "node:path";
1471
+ import { existsSync as existsSync6 } from "node:fs";
1472
+ import { join as join7 } from "node:path";
1292
1473
  function classifyRepoState(repoHome, host) {
1293
- const basePath = join6(repoHome, "shared", "settings.base.json");
1294
- const mapPath = join6(repoHome, "path-map.json");
1295
- const hostPath = join6(repoHome, "hosts", `${host}.json`);
1296
- const hasBase = existsSync5(basePath);
1297
- const hasMap = existsSync5(mapPath);
1298
- const hasHost = existsSync5(hostPath);
1474
+ const basePath = join7(repoHome, "shared", "settings.base.json");
1475
+ const mapPath = join7(repoHome, "path-map.json");
1476
+ const hostPath = join7(repoHome, "hosts", `${host}.json`);
1477
+ const hasBase = existsSync6(basePath);
1478
+ const hasMap = existsSync6(mapPath);
1479
+ const hasHost = existsSync6(hostPath);
1299
1480
  let mapEntryCount = 0;
1300
1481
  if (hasMap) {
1301
1482
  try {
@@ -1310,11 +1491,11 @@ function classifyRepoState(repoHome, host) {
1310
1491
  return "partial";
1311
1492
  }
1312
1493
  function reasonForPartial(repoHome, host) {
1313
- const basePath = join6(repoHome, "shared", "settings.base.json");
1314
- const mapPath = join6(repoHome, "path-map.json");
1315
- const hostPath = join6(repoHome, "hosts", `${host}.json`);
1316
- if (!existsSync5(basePath)) return "- shared/settings.base.json missing";
1317
- if (!existsSync5(mapPath)) return "- path-map.json missing";
1494
+ const basePath = join7(repoHome, "shared", "settings.base.json");
1495
+ const mapPath = join7(repoHome, "path-map.json");
1496
+ const hostPath = join7(repoHome, "hosts", `${host}.json`);
1497
+ if (!existsSync6(basePath)) return "- shared/settings.base.json missing";
1498
+ if (!existsSync6(mapPath)) return "- path-map.json missing";
1318
1499
  let mapEntryCount;
1319
1500
  try {
1320
1501
  const map = readJson(mapPath);
@@ -1323,7 +1504,7 @@ function reasonForPartial(repoHome, host) {
1323
1504
  mapEntryCount = 0;
1324
1505
  }
1325
1506
  if (mapEntryCount === 0) return "- path-map.json.projects has no entries";
1326
- if (!existsSync5(hostPath)) return `- hosts/${host}.json missing`;
1507
+ if (!existsSync6(hostPath)) return `- hosts/${host}.json missing`;
1327
1508
  return "- partial state (unknown gap)";
1328
1509
  }
1329
1510
 
@@ -1332,14 +1513,18 @@ function isOverrideActive() {
1332
1513
  return Boolean(process.env.NOMAD_REPO);
1333
1514
  }
1334
1515
  function reportHostAndPaths(section2) {
1335
- addItem(section2, `${dim(infoGlyph)} host: ${cyan(HOST)}`);
1516
+ const unsetHint = process.env.NOMAD_HOST ? "" : dim(" (env unset, using hostname)");
1517
+ addItem(section2, `${dim(infoGlyph)} NOMAD_HOST: ${cyan(HOST)}${unsetHint}`);
1518
+ if (isOverrideActive()) {
1519
+ addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(REPO_HOME)}`);
1520
+ }
1336
1521
  addItem(
1337
1522
  section2,
1338
- `${existsSync6(REPO_HOME) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(REPO_HOME)}`
1523
+ `${existsSync7(REPO_HOME) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(REPO_HOME)}`
1339
1524
  );
1340
1525
  addItem(
1341
1526
  section2,
1342
- `${existsSync6(CLAUDE_HOME) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(CLAUDE_HOME)}`
1527
+ `${existsSync7(CLAUDE_HOME) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(CLAUDE_HOME)}`
1343
1528
  );
1344
1529
  }
1345
1530
  function reportRepoState(section2) {
@@ -1361,12 +1546,12 @@ function reportRepoState(section2) {
1361
1546
  }
1362
1547
  }
1363
1548
  function repoHasSharedSource(name) {
1364
- return existsSync6(join7(REPO_HOME, "shared", name));
1549
+ return existsSync7(join8(REPO_HOME, "shared", name));
1365
1550
  }
1366
1551
  function classifySharedLink(name, p) {
1367
1552
  let stat;
1368
1553
  try {
1369
- stat = lstatSync4(p);
1554
+ stat = lstatSync5(p);
1370
1555
  } catch (err) {
1371
1556
  const code = err.code;
1372
1557
  if (code === "ENOENT") {
@@ -1408,7 +1593,7 @@ function classifySymlinkTarget(name, p) {
1408
1593
  }
1409
1594
  function reportSharedLinks(section2, map) {
1410
1595
  for (const name of allSharedLinks(map)) {
1411
- const p = join7(CLAUDE_HOME, name);
1596
+ const p = join8(CLAUDE_HOME, name);
1412
1597
  const { line, fail: fail2 } = classifySharedLink(name, p);
1413
1598
  addItem(section2, line);
1414
1599
  if (fail2) process.exitCode = 1;
@@ -1418,11 +1603,11 @@ function reportSharedLinks(section2, map) {
1418
1603
  // src/commands.doctor.checks.settings.ts
1419
1604
  init_color();
1420
1605
  init_config();
1421
- import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
1422
- import { join as join8 } from "node:path";
1606
+ import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
1607
+ import { join as join9 } from "node:path";
1423
1608
  function loadBaseSettings(section2) {
1424
- const basePath = join8(REPO_HOME, "shared", "settings.base.json");
1425
- if (!existsSync7(basePath)) {
1609
+ const basePath = join9(REPO_HOME, "shared", "settings.base.json");
1610
+ if (!existsSync8(basePath)) {
1426
1611
  addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
1427
1612
  process.exitCode = 1;
1428
1613
  return null;
@@ -1430,15 +1615,15 @@ function loadBaseSettings(section2) {
1430
1615
  return readJsonSafe(basePath, basePath, section2);
1431
1616
  }
1432
1617
  function loadAndReportSettings(section2) {
1433
- const settingsPath = join8(CLAUDE_HOME, "settings.json");
1434
- if (!existsSync7(settingsPath)) return null;
1618
+ const settingsPath = join9(CLAUDE_HOME, "settings.json");
1619
+ if (!existsSync8(settingsPath)) return null;
1435
1620
  const settings = readJsonSafe(settingsPath, settingsPath, section2);
1436
1621
  if (settings === null) return null;
1437
1622
  const unknownKeys = Object.keys(settings).filter((k) => !KNOWN_SETTINGS_KEYS.has(k));
1438
1623
  if (unknownKeys.length > 0) {
1439
1624
  addItem(
1440
1625
  section2,
1441
- `${yellow(warnGlyph)} settings.json has unknown keys (schema drift?): ${unknownKeys.join(", ")}`
1626
+ `${yellow(warnGlyph)} settings.json has unknown keys (schema drift?): ${unknownKeys.join(", ")} (verify: nomad doctor --check-schema)`
1442
1627
  );
1443
1628
  } else {
1444
1629
  addItem(section2, `${green(okGlyph)} settings.json schema: known keys only`);
@@ -1446,13 +1631,13 @@ function loadAndReportSettings(section2) {
1446
1631
  return settings;
1447
1632
  }
1448
1633
  function reportHostOverrides(section2, base, settings) {
1449
- const hostFile = join8(REPO_HOME, "hosts", `${HOST}.json`);
1634
+ const hostFile = join9(REPO_HOME, "hosts", `${HOST}.json`);
1450
1635
  let drift = [];
1451
1636
  if (base !== null && settings !== null) {
1452
1637
  const baseKeys = new Set(Object.keys(base));
1453
1638
  drift = Object.keys(settings).filter((k) => !baseKeys.has(k));
1454
1639
  }
1455
- if (existsSync7(hostFile)) {
1640
+ if (existsSync8(hostFile)) {
1456
1641
  if (readJsonSafe(hostFile, hostFile, section2) !== null) {
1457
1642
  addItem(section2, `${green(okGlyph)} host overrides: ${blue(hostFile)}`);
1458
1643
  }
@@ -1461,8 +1646,8 @@ function reportHostOverrides(section2, base, settings) {
1461
1646
  section2,
1462
1647
  `${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
1463
1648
  );
1464
- const hostsDir = join8(REPO_HOME, "hosts");
1465
- if (existsSync7(hostsDir)) {
1649
+ const hostsDir = join9(REPO_HOME, "hosts");
1650
+ if (existsSync8(hostsDir)) {
1466
1651
  const cands = readdirSync2(hostsDir).filter((f) => f.endsWith(".json"));
1467
1652
  if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
1468
1653
  }
@@ -1478,17 +1663,33 @@ function reportHostOverrides(section2, base, settings) {
1478
1663
  // src/commands.doctor.checks.pathmap.ts
1479
1664
  init_color();
1480
1665
  init_config();
1481
- import { existsSync as existsSync8 } from "node:fs";
1482
- import { join as join9 } from "node:path";
1666
+ import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
1667
+ import { join as join10 } from "node:path";
1483
1668
  init_utils_json();
1484
1669
  function reportMappedProjects(section2, map) {
1485
1670
  const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
1486
- addItem(
1487
- section2,
1488
- `${dim(infoGlyph)} mapped projects for ${cyan(HOST)}: ${dim(String(mapped.length))}`
1489
- );
1671
+ addItem(section2, `Mapped projects for ${cyan(HOST)}: ${dim(String(mapped.length))}`);
1490
1672
  for (const [name, hosts] of mapped) {
1491
- addItem(section2, ` ${name} -> ${blue(hosts[HOST])}`);
1673
+ addChildItem(section2, `${name} -> ${blue(hosts[HOST])}`);
1674
+ }
1675
+ }
1676
+ function reportUnmappedProjects(section2, map) {
1677
+ const localProjects = join10(CLAUDE_HOME, "projects");
1678
+ if (!existsSync9(localProjects)) return;
1679
+ let localDirs;
1680
+ try {
1681
+ localDirs = readdirSync3(localProjects);
1682
+ } catch {
1683
+ return;
1684
+ }
1685
+ const mappedEncodings = new Set(
1686
+ Object.values(map.projects).map((hosts) => hosts[HOST]).filter(Boolean).map((abspath) => encodePath(abspath))
1687
+ );
1688
+ const unmapped = localDirs.filter((dir) => !mappedEncodings.has(dir));
1689
+ if (unmapped.length === 0) return;
1690
+ addItem(section2, `Unmapped local projects (not synced): ${dim(String(unmapped.length))}`);
1691
+ for (const dir of unmapped) {
1692
+ addChildItem(section2, dim(dir));
1492
1693
  }
1493
1694
  }
1494
1695
  function reportPathCollisions(section2, map) {
@@ -1514,8 +1715,8 @@ function reportPathCollisions(section2, map) {
1514
1715
  else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
1515
1716
  }
1516
1717
  function reportPathMap(section2) {
1517
- const mapPath = join9(REPO_HOME, "path-map.json");
1518
- if (!existsSync8(mapPath)) {
1718
+ const mapPath = join10(REPO_HOME, "path-map.json");
1719
+ if (!existsSync9(mapPath)) {
1519
1720
  addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
1520
1721
  process.exitCode = 1;
1521
1722
  return;
@@ -1552,26 +1753,32 @@ function reportPathMap(section2) {
1552
1753
  }
1553
1754
  }
1554
1755
  reportMappedProjects(section2, map);
1756
+ reportUnmappedProjects(section2, map);
1555
1757
  reportPathCollisions(section2, map);
1556
1758
  }
1557
1759
  function reportNeverSync(section2) {
1558
- addItem(section2, `${dim(infoGlyph)} never-sync items: ${[...NEVER_SYNC].join(", ")}`);
1760
+ addItem(
1761
+ section2,
1762
+ `${dim(infoGlyph)} never-sync items: ${NEVER_SYNC.size} protected ${dim(
1763
+ "(https://funkadelic.github.io/claude-nomad/how-it-works/)"
1764
+ )}`
1765
+ );
1559
1766
  }
1560
1767
 
1561
1768
  // src/commands.doctor.checks.repository.ts
1562
1769
  init_color();
1563
1770
  init_config();
1564
1771
  import { execFileSync as execFileSync3 } from "node:child_process";
1565
- import { existsSync as existsSync11 } from "node:fs";
1566
- import { join as join13, relative as relative2 } from "node:path";
1772
+ import { existsSync as existsSync12 } from "node:fs";
1773
+ import { join as join14, relative as relative2 } from "node:path";
1567
1774
 
1568
1775
  // src/commands.pull.wedge.ts
1569
- import { existsSync as existsSync9 } from "node:fs";
1570
- import { join as join10 } from "node:path";
1776
+ import { existsSync as existsSync10 } from "node:fs";
1777
+ import { join as join11 } from "node:path";
1571
1778
  function detectWedge(repo) {
1572
- const g = join10(repo, ".git");
1573
- if (existsSync9(join10(g, "rebase-merge")) || existsSync9(join10(g, "rebase-apply"))) return "rebase";
1574
- if (existsSync9(join10(g, "MERGE_HEAD"))) return "merge";
1779
+ const g = join11(repo, ".git");
1780
+ if (existsSync10(join11(g, "rebase-merge")) || existsSync10(join11(g, "rebase-apply"))) return "rebase";
1781
+ if (existsSync10(join11(g, "MERGE_HEAD"))) return "merge";
1575
1782
  return null;
1576
1783
  }
1577
1784
 
@@ -1580,12 +1787,14 @@ init_push_checks();
1580
1787
  init_utils();
1581
1788
  function reportGitleaksProbe(section2) {
1582
1789
  try {
1583
- const v = execFileSync3("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
1584
- addItem(section2, `${green(okGlyph)} gitleaks: ${dim(v)}`);
1790
+ execFileSync3("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
1585
1791
  return true;
1586
1792
  } catch (err) {
1587
1793
  if (err.code === "ENOENT") {
1588
- addItem(section2, `${yellow(warnGlyph)} gitleaks: not on PATH (required for nomad push)`);
1794
+ addItem(
1795
+ section2,
1796
+ `${yellow(warnGlyph)} gitleaks: not on PATH (required for nomad push; install: https://github.com/gitleaks/gitleaks)`
1797
+ );
1589
1798
  } else {
1590
1799
  addItem(section2, `${red(failGlyph)} gitleaks: probe failed: ${err.message}`);
1591
1800
  process.exitCode = 1;
@@ -1594,8 +1803,8 @@ function reportGitleaksProbe(section2) {
1594
1803
  }
1595
1804
  }
1596
1805
  function reportGitlinks(section2) {
1597
- const sharedDir = join13(REPO_HOME, "shared");
1598
- if (existsSync11(sharedDir)) {
1806
+ const sharedDir = join14(REPO_HOME, "shared");
1807
+ if (existsSync12(sharedDir)) {
1599
1808
  const gitlinks = findGitlinks(sharedDir);
1600
1809
  for (const p of gitlinks) {
1601
1810
  const rel = relative2(REPO_HOME, p);
@@ -1651,13 +1860,13 @@ function reportRebaseState(section2) {
1651
1860
 
1652
1861
  // src/commands.doctor.checks.backups.ts
1653
1862
  init_color();
1654
- import { existsSync as existsSync12, lstatSync as lstatSync5, readdirSync as readdirSync4 } from "node:fs";
1655
- import { join as join14 } from "node:path";
1863
+ import { existsSync as existsSync13, lstatSync as lstatSync6, readdirSync as readdirSync5 } from "node:fs";
1864
+ import { join as join15 } from "node:path";
1656
1865
  init_config();
1657
1866
  var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
1658
1867
  function safeReaddir(dir) {
1659
1868
  try {
1660
- return readdirSync4(dir);
1869
+ return readdirSync5(dir);
1661
1870
  } catch {
1662
1871
  return [];
1663
1872
  }
@@ -1668,8 +1877,8 @@ var BYTES_PER_MB = 1024 * 1024;
1668
1877
  function dirSizeBytes(dir) {
1669
1878
  let bytes = 0;
1670
1879
  for (const entry of safeReaddir(dir)) {
1671
- const full = join14(dir, entry);
1672
- const st = lstatSync5(full, { throwIfNoEntry: false });
1880
+ const full = join15(dir, entry);
1881
+ const st = lstatSync6(full, { throwIfNoEntry: false });
1673
1882
  if (!st) continue;
1674
1883
  if (st.isSymbolicLink()) continue;
1675
1884
  if (st.isDirectory()) bytes += dirSizeBytes(full);
@@ -1679,11 +1888,11 @@ function dirSizeBytes(dir) {
1679
1888
  }
1680
1889
  function totalSizeMb(backupBase, dirs) {
1681
1890
  let bytes = 0;
1682
- for (const name of dirs) bytes += dirSizeBytes(join14(backupBase, name));
1891
+ for (const name of dirs) bytes += dirSizeBytes(join15(backupBase, name));
1683
1892
  return bytes / BYTES_PER_MB;
1684
1893
  }
1685
1894
  function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
1686
- if (!existsSync12(backupBase)) return;
1895
+ if (!existsSync13(backupBase)) return;
1687
1896
  const dirs = safeReaddir(backupBase).filter((n) => TS_SHAPE2.test(n));
1688
1897
  const count = dirs.length;
1689
1898
  const sizeMb = totalSizeMb(backupBase, dirs);
@@ -1697,8 +1906,8 @@ function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
1697
1906
 
1698
1907
  // src/commands.doctor.check-schema.ts
1699
1908
  init_color();
1700
- import { existsSync as existsSync13 } from "node:fs";
1701
- import { join as join15 } from "node:path";
1909
+ import { existsSync as existsSync14 } from "node:fs";
1910
+ import { join as join16 } from "node:path";
1702
1911
  init_config();
1703
1912
 
1704
1913
  // src/http-fetch.ts
@@ -1735,8 +1944,8 @@ function fetchSchemaKeys() {
1735
1944
  }
1736
1945
  }
1737
1946
  function reportCheckSchema(section2) {
1738
- const settingsPath = join15(CLAUDE_HOME, "settings.json");
1739
- if (!existsSync13(settingsPath)) {
1947
+ const settingsPath = join16(CLAUDE_HOME, "settings.json");
1948
+ if (!existsSync14(settingsPath)) {
1740
1949
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
1741
1950
  return;
1742
1951
  }
@@ -1766,18 +1975,18 @@ function reportCheckSchema(section2) {
1766
1975
  init_color();
1767
1976
  import { randomBytes } from "node:crypto";
1768
1977
  import { execFileSync as execFileSync6 } from "node:child_process";
1769
- import { existsSync as existsSync15, mkdirSync as mkdirSync4, readdirSync as readdirSync6, rmSync as rmSync6 } from "node:fs";
1978
+ import { existsSync as existsSync16, mkdirSync as mkdirSync4, readdirSync as readdirSync7, rmSync as rmSync7 } from "node:fs";
1770
1979
  import { homedir as homedir4 } from "node:os";
1771
- import { join as join19 } from "node:path";
1980
+ import { join as join20 } from "node:path";
1772
1981
 
1773
1982
  // src/commands.doctor.check-shared.scan.ts
1774
1983
  init_color();
1775
- import { join as join17 } from "node:path";
1984
+ import { join as join18 } from "node:path";
1776
1985
  init_config();
1777
1986
  init_push_gitleaks();
1778
1987
  function scrubPath(logical, sid, logicalToEncoded) {
1779
1988
  const encoded = logicalToEncoded.get(logical) ?? logical;
1780
- return join17(CLAUDE_HOME, "projects", encoded, `${sid}.jsonl`);
1989
+ return join18(CLAUDE_HOME, "projects", encoded, `${sid}.jsonl`);
1781
1990
  }
1782
1991
  function reportSessionFindings(section2, bySession) {
1783
1992
  for (const [sid, counts] of bySession) {
@@ -1869,21 +2078,21 @@ init_config();
1869
2078
  init_utils();
1870
2079
  init_utils_fs();
1871
2080
  init_utils_json();
1872
- import { cpSync as cpSync3, existsSync as existsSync14, mkdirSync as mkdirSync3, readdirSync as readdirSync5, rmSync as rmSync5, statSync as statSync4 } from "node:fs";
1873
- import { join as join18, relative as relative3, sep } from "node:path";
2081
+ import { cpSync as cpSync4, existsSync as existsSync15, mkdirSync as mkdirSync3, readdirSync as readdirSync6, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
2082
+ import { join as join19, relative as relative3, sep as sep2 } from "node:path";
1874
2083
  function copyDir(src, dst) {
1875
- rmSync5(dst, { recursive: true, force: true });
1876
- cpSync3(src, dst, { recursive: true, force: true });
2084
+ rmSync6(dst, { recursive: true, force: true });
2085
+ cpSync4(src, dst, { recursive: true, force: true });
1877
2086
  }
1878
2087
  function copyDirJsonlOnly(src, dst) {
1879
- rmSync5(dst, { recursive: true, force: true });
1880
- cpSync3(src, dst, {
2088
+ rmSync6(dst, { recursive: true, force: true });
2089
+ cpSync4(src, dst, {
1881
2090
  recursive: true,
1882
2091
  force: true,
1883
2092
  filter: (srcPath) => {
1884
2093
  const rel = relative3(src, srcPath);
1885
2094
  if (rel === "") return true;
1886
- if (rel.split(sep).length > 1) return true;
2095
+ if (rel.split(sep2).length > 1) return true;
1887
2096
  if (statSync4(srcPath).isDirectory()) return true;
1888
2097
  if (srcPath.endsWith(".jsonl")) return true;
1889
2098
  log(`skip ${rel}: extension not in allowlist`);
@@ -1900,15 +2109,15 @@ function remapPull(ts, opts = {}) {
1900
2109
  let unmapped = 0;
1901
2110
  const pulled = [];
1902
2111
  const wouldPull = [];
1903
- const mapPath = join18(REPO_HOME, "path-map.json");
1904
- const repoProjects = join18(REPO_HOME, "shared", "projects");
1905
- if (!existsSync14(mapPath) || !existsSync14(repoProjects)) {
2112
+ const mapPath = join19(REPO_HOME, "path-map.json");
2113
+ const repoProjects = join19(REPO_HOME, "shared", "projects");
2114
+ if (!existsSync15(mapPath) || !existsSync15(repoProjects)) {
1906
2115
  const text = "no path-map or repo projects dir; skipping session remap";
1907
2116
  emitPreview(opts.onPreview, { kind: "note", text }, text);
1908
2117
  return { unmapped: 0, pulled, wouldPull };
1909
2118
  }
1910
2119
  const map = readJson(mapPath);
1911
- const localProjects = join18(CLAUDE_HOME, "projects");
2120
+ const localProjects = join19(CLAUDE_HOME, "projects");
1912
2121
  if (!dryRun) mkdirSync3(localProjects, { recursive: true });
1913
2122
  for (const [logical, hosts] of Object.entries(map.projects)) {
1914
2123
  assertSafeLogical(logical);
@@ -1917,9 +2126,9 @@ function remapPull(ts, opts = {}) {
1917
2126
  unmapped++;
1918
2127
  continue;
1919
2128
  }
1920
- const src = join18(repoProjects, logical);
1921
- if (!existsSync14(src)) continue;
1922
- const dst = join18(localProjects, encodePath(localPath));
2129
+ const src = join19(repoProjects, logical);
2130
+ if (!existsSync15(src)) continue;
2131
+ const dst = join19(localProjects, encodePath(localPath));
1923
2132
  if (dryRun) {
1924
2133
  wouldPull.push(logical);
1925
2134
  emitPreview(
@@ -1964,30 +2173,30 @@ function remapPush(ts, opts = {}) {
1964
2173
  let unmapped = 0;
1965
2174
  const pushed = [];
1966
2175
  const wouldPush = [];
1967
- const mapPath = join18(REPO_HOME, "path-map.json");
1968
- if (!existsSync14(mapPath)) {
2176
+ const mapPath = join19(REPO_HOME, "path-map.json");
2177
+ if (!existsSync15(mapPath)) {
1969
2178
  log("no path-map.json; skipping session export");
1970
2179
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
1971
2180
  }
1972
2181
  const map = readJson(mapPath);
1973
- const localProjects = join18(CLAUDE_HOME, "projects");
1974
- const repoProjects = join18(REPO_HOME, "shared", "projects");
2182
+ const localProjects = join19(CLAUDE_HOME, "projects");
2183
+ const repoProjects = join19(REPO_HOME, "shared", "projects");
1975
2184
  const reverse = buildReverseMap(map);
1976
- if (!existsSync14(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
2185
+ if (!existsSync15(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
1977
2186
  if (!dryRun) mkdirSync3(repoProjects, { recursive: true });
1978
- for (const dir of readdirSync5(localProjects)) {
2187
+ for (const dir of readdirSync6(localProjects)) {
1979
2188
  const logical = reverse.get(dir);
1980
2189
  if (!logical) {
1981
2190
  unmapped++;
1982
2191
  continue;
1983
2192
  }
1984
- const repoDst = join18(repoProjects, logical);
2193
+ const repoDst = join19(repoProjects, logical);
1985
2194
  if (dryRun) {
1986
2195
  wouldPush.push(logical);
1987
2196
  continue;
1988
2197
  }
1989
2198
  backupRepoWrite(repoDst, ts, REPO_HOME);
1990
- copyDirJsonlOnly(join18(localProjects, dir), repoDst);
2199
+ copyDirJsonlOnly(join19(localProjects, dir), repoDst);
1991
2200
  pushed.push(logical);
1992
2201
  }
1993
2202
  return { unmapped, collisions: 0, pushed, wouldPush };
@@ -1999,8 +2208,8 @@ init_utils_json();
1999
2208
  function buildScanTree(tmpRoot) {
2000
2209
  const logicalToEncoded = /* @__PURE__ */ new Map();
2001
2210
  let staged = 0;
2002
- const mapPath = join19(REPO_HOME, "path-map.json");
2003
- if (!existsSync15(mapPath)) return { logicalToEncoded, staged, malformed: false };
2211
+ const mapPath = join20(REPO_HOME, "path-map.json");
2212
+ if (!existsSync16(mapPath)) return { logicalToEncoded, staged, malformed: false };
2004
2213
  let map;
2005
2214
  try {
2006
2215
  map = readJson(mapPath);
@@ -2017,12 +2226,12 @@ function buildScanTree(tmpRoot) {
2017
2226
  if (!p || p === "TBD") continue;
2018
2227
  reverse.set(encodePath(p), logical);
2019
2228
  }
2020
- const localProjects = join19(CLAUDE_HOME, "projects");
2021
- if (!existsSync15(localProjects)) return { logicalToEncoded, staged, malformed: false };
2022
- for (const dir of readdirSync6(localProjects)) {
2229
+ const localProjects = join20(CLAUDE_HOME, "projects");
2230
+ if (!existsSync16(localProjects)) return { logicalToEncoded, staged, malformed: false };
2231
+ for (const dir of readdirSync7(localProjects)) {
2023
2232
  const logical = reverse.get(dir);
2024
2233
  if (!logical) continue;
2025
- copyDirJsonlOnly(join19(localProjects, dir), join19(tmpRoot, "shared", "projects", logical));
2234
+ copyDirJsonlOnly(join20(localProjects, dir), join20(tmpRoot, "shared", "projects", logical));
2026
2235
  logicalToEncoded.set(logical, dir);
2027
2236
  staged++;
2028
2237
  }
@@ -2053,11 +2262,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
2053
2262
  }
2054
2263
  function reportCheckShared(section2, gitleaksReady) {
2055
2264
  if (!ensureGitleaksReady(section2, gitleaksReady)) return;
2056
- const cacheDir = join19(homedir4(), ".cache", "claude-nomad");
2265
+ const cacheDir = join20(homedir4(), ".cache", "claude-nomad");
2057
2266
  mkdirSync4(cacheDir, { recursive: true });
2058
2267
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
2059
- const reportPath = join19(cacheDir, `check-shared-${stamp}.json`);
2060
- const tmpRoot = join19(cacheDir, `check-shared-tree-${stamp}`);
2268
+ const reportPath = join20(cacheDir, `check-shared-${stamp}.json`);
2269
+ const tmpRoot = join20(cacheDir, `check-shared-tree-${stamp}`);
2061
2270
  try {
2062
2271
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
2063
2272
  if (malformed) {
@@ -2071,15 +2280,15 @@ function reportCheckShared(section2, gitleaksReady) {
2071
2280
  }
2072
2281
  scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
2073
2282
  } finally {
2074
- rmSync6(reportPath, { force: true });
2075
- rmSync6(tmpRoot, { recursive: true, force: true });
2283
+ rmSync7(reportPath, { force: true });
2284
+ rmSync7(tmpRoot, { recursive: true, force: true });
2076
2285
  }
2077
2286
  }
2078
2287
 
2079
2288
  // src/commands.doctor.checks.hooks.scope.ts
2080
2289
  init_color();
2081
- import { existsSync as existsSync16, readFileSync as readFileSync5, readdirSync as readdirSync7, realpathSync } from "node:fs";
2082
- import { dirname as dirname2, extname, join as join20 } from "node:path";
2290
+ import { existsSync as existsSync17, readFileSync as readFileSync5, readdirSync as readdirSync8, realpathSync as realpathSync2 } from "node:fs";
2291
+ import { dirname as dirname2, extname, join as join21 } from "node:path";
2083
2292
  init_config();
2084
2293
  function typeFromPackageJson(pkgPath) {
2085
2294
  try {
@@ -2095,14 +2304,14 @@ function effectiveType(hookPath) {
2095
2304
  if (ext === ".cjs") return "cjs";
2096
2305
  let real;
2097
2306
  try {
2098
- real = realpathSync(hookPath);
2307
+ real = realpathSync2(hookPath);
2099
2308
  } catch {
2100
2309
  return null;
2101
2310
  }
2102
2311
  let dir = dirname2(real);
2103
2312
  for (; ; ) {
2104
- const pkg = join20(dir, "package.json");
2105
- if (existsSync16(pkg)) return typeFromPackageJson(pkg);
2313
+ const pkg = join21(dir, "package.json");
2314
+ if (existsSync17(pkg)) return typeFromPackageJson(pkg);
2106
2315
  const parent = dirname2(dir);
2107
2316
  if (parent === dir) return "cjs";
2108
2317
  dir = parent;
@@ -2131,7 +2340,7 @@ function remedy(family) {
2131
2340
  }
2132
2341
  function safeReaddir2(dir) {
2133
2342
  try {
2134
- return readdirSync7(dir);
2343
+ return readdirSync8(dir);
2135
2344
  } catch {
2136
2345
  return [];
2137
2346
  }
@@ -2144,15 +2353,15 @@ function safeRead(path) {
2144
2353
  }
2145
2354
  }
2146
2355
  function reportHookScopeCheck(section2) {
2147
- const hooksDir = join20(CLAUDE_HOME, "hooks");
2148
- if (!existsSync16(hooksDir)) {
2356
+ const hooksDir = join21(CLAUDE_HOME, "hooks");
2357
+ if (!existsSync17(hooksDir)) {
2149
2358
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
2150
2359
  return;
2151
2360
  }
2152
2361
  let anyWarn = false;
2153
2362
  for (const name of safeReaddir2(hooksDir)) {
2154
2363
  if (extname(name) !== ".js") continue;
2155
- const abs = join20(hooksDir, name);
2364
+ const abs = join21(hooksDir, name);
2156
2365
  const eff = effectiveType(abs);
2157
2366
  if (eff === null) continue;
2158
2367
  const src = safeRead(abs);
@@ -2172,8 +2381,8 @@ function reportHookScopeCheck(section2) {
2172
2381
 
2173
2382
  // src/commands.doctor.checks.hooks.ts
2174
2383
  init_color();
2175
- import { existsSync as existsSync17 } from "node:fs";
2176
- import { join as join21 } from "node:path";
2384
+ import { existsSync as existsSync18 } from "node:fs";
2385
+ import { join as join22 } from "node:path";
2177
2386
  init_config();
2178
2387
  function expandHome(token) {
2179
2388
  return token.replace(/^\$\{HOME\}/, HOME).replace(/^\$HOME/, HOME).replace(/^~/, HOME);
@@ -2211,8 +2420,11 @@ function checkEventGroups(section2, event, groups) {
2211
2420
  for (const group of groups) {
2212
2421
  for (const cmd of commandsFromGroup(group)) {
2213
2422
  for (const resolved of claudePathsIn(cmd)) {
2214
- if (existsSync17(resolved)) continue;
2215
- addItem(section2, `${red(failGlyph)} hooks/${event}: command target missing: ${resolved}`);
2423
+ if (existsSync18(resolved)) continue;
2424
+ addItem(
2425
+ section2,
2426
+ `${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
2427
+ );
2216
2428
  process.exitCode = 1;
2217
2429
  anyFail = true;
2218
2430
  }
@@ -2221,8 +2433,8 @@ function checkEventGroups(section2, event, groups) {
2221
2433
  return anyFail;
2222
2434
  }
2223
2435
  function reportHooksTargetCheck(section2) {
2224
- const settingsPath = join21(CLAUDE_HOME, "settings.json");
2225
- if (!existsSync17(settingsPath)) {
2436
+ const settingsPath = join22(CLAUDE_HOME, "settings.json");
2437
+ if (!existsSync18(settingsPath)) {
2226
2438
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
2227
2439
  return;
2228
2440
  }
@@ -2243,17 +2455,221 @@ function reportHooksTargetCheck(section2) {
2243
2455
  }
2244
2456
  }
2245
2457
 
2458
+ // src/commands.doctor.checks.hooks.preserve-symlinks.ts
2459
+ init_color();
2460
+ import { existsSync as existsSync20, readFileSync as readFileSync6 } from "node:fs";
2461
+ import { join as join24 } from "node:path";
2462
+
2463
+ // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
2464
+ import { closeSync as closeSync2, existsSync as existsSync19, openSync as openSync2, readSync, realpathSync as realpathSync3 } from "node:fs";
2465
+ import { dirname as dirname3, join as join23, resolve as resolve2 } from "node:path";
2466
+ function suppressedRanges(src) {
2467
+ const ranges = [];
2468
+ const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
2469
+ let m;
2470
+ while ((m = re.exec(src)) !== null) {
2471
+ ranges.push([m.index, m.index + m[0].length]);
2472
+ }
2473
+ return ranges;
2474
+ }
2475
+ function inSuppressedRange(pos, ranges) {
2476
+ for (const [start, end] of ranges) {
2477
+ if (pos >= start && pos < end) return true;
2478
+ }
2479
+ return false;
2480
+ }
2481
+ function topRelativeSpecifiers(src) {
2482
+ const ranges = suppressedRanges(src);
2483
+ const specifiers = [];
2484
+ const reqRe = /\brequire\s*\(\s*(['"])(\.\.?\/[^'"]*)\1\s*\)/g;
2485
+ let m;
2486
+ while ((m = reqRe.exec(src)) !== null) {
2487
+ if (!inSuppressedRange(m.index, ranges)) specifiers.push(m[2]);
2488
+ }
2489
+ const impRe = /\bfrom\s+(['"])(\.\.?\/[^'"]*)\1/g;
2490
+ while ((m = impRe.exec(src)) !== null) {
2491
+ if (!inSuppressedRange(m.index, ranges)) specifiers.push(m[2]);
2492
+ }
2493
+ return specifiers;
2494
+ }
2495
+ function specifierIsMissing(specifier, baseDir) {
2496
+ const base = resolve2(baseDir, specifier);
2497
+ if (existsSync19(base)) return false;
2498
+ for (const ext of [".js", ".cjs", ".mjs"]) {
2499
+ if (existsSync19(base + ext)) return false;
2500
+ }
2501
+ for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
2502
+ if (existsSync19(join23(base, idx))) return false;
2503
+ }
2504
+ return true;
2505
+ }
2506
+ function relativeRequireTargetsBroken(scriptPath) {
2507
+ let realPath;
2508
+ try {
2509
+ realPath = realpathSync3(scriptPath);
2510
+ } catch {
2511
+ return false;
2512
+ }
2513
+ let raw;
2514
+ try {
2515
+ const fd = openSync2(realPath, "r");
2516
+ try {
2517
+ const buf = Buffer.alloc(65536);
2518
+ const bytesRead = readSync(fd, buf, 0, 65536, 0);
2519
+ raw = buf.toString("utf8", 0, bytesRead);
2520
+ } finally {
2521
+ closeSync2(fd);
2522
+ }
2523
+ } catch {
2524
+ return false;
2525
+ }
2526
+ const specifiers = topRelativeSpecifiers(raw);
2527
+ if (specifiers.length === 0) return false;
2528
+ const baseDir = dirname3(realPath);
2529
+ for (const spec of specifiers) {
2530
+ if (specifierIsMissing(spec, baseDir)) return true;
2531
+ }
2532
+ return false;
2533
+ }
2534
+
2535
+ // src/commands.doctor.checks.hooks.preserve-symlinks.ts
2536
+ init_config();
2537
+ function expandHome2(token) {
2538
+ return token.replace(/^\$\{HOME\}/, HOME).replace(/^\$HOME/, HOME).replace(/^~/, HOME);
2539
+ }
2540
+ function stripShellPunct(token) {
2541
+ const head = token.replace(/^['"]+/, "");
2542
+ let end = head.length;
2543
+ while (end > 0 && "'\"`;)|&>".includes(head[end - 1])) end--;
2544
+ return head.slice(0, end);
2545
+ }
2546
+ function commandTokens(command) {
2547
+ const tokens = [];
2548
+ for (const seg of command.split(/&&|\|\||;|\|/)) {
2549
+ for (const raw of seg.trim().split(/\s+/).filter(Boolean)) {
2550
+ tokens.push(expandHome2(stripShellPunct(raw)));
2551
+ }
2552
+ }
2553
+ return tokens;
2554
+ }
2555
+ function readPathMapSafe() {
2556
+ const mapPath = join24(REPO_HOME, "path-map.json");
2557
+ if (!existsSync20(mapPath)) return { projects: {} };
2558
+ try {
2559
+ return JSON.parse(readFileSync6(mapPath, "utf8"));
2560
+ } catch {
2561
+ return { projects: {} };
2562
+ }
2563
+ }
2564
+ function resolvesUnderSymlinkedShared(scriptPath, sharedLinkNames) {
2565
+ for (const name of sharedLinkNames) {
2566
+ const prefix = `${CLAUDE_HOME}/${name}/`;
2567
+ if (scriptPath.startsWith(prefix)) return true;
2568
+ }
2569
+ return false;
2570
+ }
2571
+ function nodeScriptArg(tokens, nodeIdx) {
2572
+ for (let i = nodeIdx + 1; i < tokens.length; i++) {
2573
+ const t = tokens[i];
2574
+ if (t.startsWith("-")) continue;
2575
+ if (t.endsWith(".js") || t.endsWith(".cjs")) return t;
2576
+ break;
2577
+ }
2578
+ return null;
2579
+ }
2580
+ function hasPreserveSymlinksMain(tokens, nodeIdx) {
2581
+ for (let i = nodeIdx + 1; i < tokens.length; i++) {
2582
+ if (tokens[i] === "--preserve-symlinks-main") return true;
2583
+ if (!tokens[i].startsWith("-")) break;
2584
+ }
2585
+ return false;
2586
+ }
2587
+ function* commandsFromFlatEntries(entries) {
2588
+ for (const entry of entries) {
2589
+ if (typeof entry !== "object" || entry === null) continue;
2590
+ const e = entry;
2591
+ if (e.type === "command" && typeof e.command === "string") yield e.command;
2592
+ }
2593
+ }
2594
+ function* commandsFromOneGroup(group) {
2595
+ if (typeof group !== "object" || group === null) return;
2596
+ const g = group;
2597
+ if (Array.isArray(g.hooks)) {
2598
+ yield* commandsFromFlatEntries(g.hooks);
2599
+ return;
2600
+ }
2601
+ if (g.type === "command" && typeof g.command === "string") yield g.command;
2602
+ }
2603
+ function flaggedScript(command, sharedLinkNames) {
2604
+ const tokens = commandTokens(command);
2605
+ const nodeIdx = tokens.indexOf("node");
2606
+ if (nodeIdx < 0) return null;
2607
+ if (hasPreserveSymlinksMain(tokens, nodeIdx)) return null;
2608
+ const script = nodeScriptArg(tokens, nodeIdx);
2609
+ if (script === null) return null;
2610
+ if (!resolvesUnderSymlinkedShared(script, sharedLinkNames)) return null;
2611
+ if (!relativeRequireTargetsBroken(script)) return null;
2612
+ return script;
2613
+ }
2614
+ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames) {
2615
+ let anyWarn = false;
2616
+ for (const group of groups) {
2617
+ for (const cmd of commandsFromOneGroup(group)) {
2618
+ const script = flaggedScript(cmd, sharedLinkNames);
2619
+ if (script === null) continue;
2620
+ addItem(
2621
+ section2,
2622
+ `${yellow(warnGlyph)} hooks/${event}: node ${script} needs --preserve-symlinks-main (add it to the hook command in shared/settings.base.json)`
2623
+ );
2624
+ anyWarn = true;
2625
+ }
2626
+ }
2627
+ return anyWarn;
2628
+ }
2629
+ function reportPreserveSymlinksCheck(section2) {
2630
+ const settingsPath = join24(CLAUDE_HOME, "settings.json");
2631
+ if (!existsSync20(settingsPath)) {
2632
+ addItem(
2633
+ section2,
2634
+ `${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
2635
+ );
2636
+ return;
2637
+ }
2638
+ let settings;
2639
+ try {
2640
+ settings = JSON.parse(readFileSync6(settingsPath, "utf8"));
2641
+ } catch {
2642
+ return;
2643
+ }
2644
+ if (settings === null) return;
2645
+ const hooks = settings.hooks;
2646
+ if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) {
2647
+ addItem(section2, `${green(okGlyph)} hooks: preserve-symlinks-main not needed`);
2648
+ return;
2649
+ }
2650
+ const map = readPathMapSafe();
2651
+ const sharedLinkNames = allSharedLinks(map);
2652
+ let anyWarn = false;
2653
+ for (const [event, groups] of Object.entries(hooks)) {
2654
+ if (!Array.isArray(groups)) continue;
2655
+ if (checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)) anyWarn = true;
2656
+ }
2657
+ if (!anyWarn) {
2658
+ addItem(section2, `${green(okGlyph)} hooks: preserve-symlinks-main not needed`);
2659
+ }
2660
+ }
2661
+
2246
2662
  // src/commands.doctor.ts
2247
2663
  init_config();
2248
2664
 
2249
2665
  // src/commands.doctor.engine.ts
2250
2666
  init_color();
2251
- import { readFileSync as readFileSync7 } from "node:fs";
2667
+ import { readFileSync as readFileSync8 } from "node:fs";
2252
2668
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2253
2669
 
2254
2670
  // src/commands.doctor.version.ts
2255
2671
  init_color();
2256
- import { readFileSync as readFileSync6 } from "node:fs";
2672
+ import { readFileSync as readFileSync7 } from "node:fs";
2257
2673
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2258
2674
  init_config();
2259
2675
  var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
@@ -2270,7 +2686,7 @@ function compareSemver(a, b) {
2270
2686
  function readLocalVersion() {
2271
2687
  try {
2272
2688
  const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
2273
- const parsed = JSON.parse(readFileSync6(pkgPath, "utf8"));
2689
+ const parsed = JSON.parse(readFileSync7(pkgPath, "utf8"));
2274
2690
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
2275
2691
  return parsed.version;
2276
2692
  }
@@ -2297,7 +2713,13 @@ function reportVersionCheck(section2) {
2297
2713
  const localPure = STRICT_SEMVER_PREFIX.exec(local)?.[1] ?? null;
2298
2714
  if (localPure === null) return;
2299
2715
  const latest = fetchLatestVersion();
2300
- if (latest === null) return;
2716
+ if (latest === null) {
2717
+ addItem(
2718
+ section2,
2719
+ `${dim(infoGlyph)} claude-nomad: ${local} (version check skipped: could not determine latest version)`
2720
+ );
2721
+ return;
2722
+ }
2301
2723
  const cmp = compareSemver(localPure, latest);
2302
2724
  if (cmp === 0) {
2303
2725
  addItem(section2, `${green(okGlyph)} claude-nomad: ${local} (latest)`);
@@ -2323,7 +2745,7 @@ function parseMinVersion(spec) {
2323
2745
  function readEnginesNode() {
2324
2746
  try {
2325
2747
  const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
2326
- const parsed = JSON.parse(readFileSync7(pkgPath, "utf8"));
2748
+ const parsed = JSON.parse(readFileSync8(pkgPath, "utf8"));
2327
2749
  const node = parsed.engines?.node;
2328
2750
  if (typeof node === "string" && node.length > 0) return node;
2329
2751
  return null;
@@ -2352,8 +2774,8 @@ function reportNodeEngineCheck(section2) {
2352
2774
  // src/commands.doctor.gitleaks-version.ts
2353
2775
  init_color();
2354
2776
  import { execFileSync as execFileSync7 } from "node:child_process";
2355
- import { existsSync as existsSync18 } from "node:fs";
2356
- import { join as join22 } from "node:path";
2777
+ import { existsSync as existsSync21 } from "node:fs";
2778
+ import { join as join25 } from "node:path";
2357
2779
  init_config();
2358
2780
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
2359
2781
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -2362,7 +2784,7 @@ function majorMinorOf(value) {
2362
2784
  return m === null ? null : [m[1], m[2]];
2363
2785
  }
2364
2786
  function readGitleaksVersion(run, tomlExists) {
2365
- const tomlPath = join22(REPO_HOME, ".gitleaks.toml");
2787
+ const tomlPath = join25(REPO_HOME, ".gitleaks.toml");
2366
2788
  const args = ["version"];
2367
2789
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
2368
2790
  try {
@@ -2374,7 +2796,7 @@ function readGitleaksVersion(run, tomlExists) {
2374
2796
  return null;
2375
2797
  }
2376
2798
  }
2377
- function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync18) {
2799
+ function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync21) {
2378
2800
  const raw = readGitleaksVersion(run, tomlExists);
2379
2801
  if (raw === null) return;
2380
2802
  const local = majorMinorOf(raw);
@@ -2541,19 +2963,47 @@ function reportActionsDrift(section2, run = execFileSync10) {
2541
2963
  );
2542
2964
  }
2543
2965
 
2966
+ // src/commands.doctor.verdict.ts
2967
+ init_color();
2968
+ function isFailLine(item) {
2969
+ return item.includes(failGlyph);
2970
+ }
2971
+ function isWarnLine(item) {
2972
+ return !isFailLine(item) && item.includes(warnGlyph);
2973
+ }
2974
+ function buildVerdictSection(sections) {
2975
+ const summary = section("Summary");
2976
+ const lines = sections.flatMap((s) => s.items).map((item) => item.replace(/^\t/, ""));
2977
+ const failures = lines.filter(isFailLine);
2978
+ const warnings = lines.filter(isWarnLine);
2979
+ for (const line of [...failures, ...warnings]) addItem(summary, line);
2980
+ if (failures.length > 0) {
2981
+ addItem(
2982
+ summary,
2983
+ `${red(failGlyph)} ${failures.length} failure(s), ${warnings.length} warning(s)`
2984
+ );
2985
+ } else if (warnings.length > 0) {
2986
+ addItem(summary, `${yellow(warnGlyph)} ${warnings.length} warning(s)`);
2987
+ } else {
2988
+ addItem(summary, `${green(okGlyph)} healthy`);
2989
+ }
2990
+ return summary;
2991
+ }
2992
+
2544
2993
  // src/commands.doctor.ts
2545
2994
  function cmdDoctor(opts = {}) {
2546
- const host = section("Host");
2995
+ const host = section("Environment");
2547
2996
  reportHostAndPaths(host);
2548
2997
  reportRepoState(host);
2549
2998
  const links = section("Shared links");
2550
- const mapPath = join23(REPO_HOME, "path-map.json");
2551
- const rawMap = existsSync19(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
2999
+ const mapPath = join26(REPO_HOME, "path-map.json");
3000
+ const rawMap = existsSync22(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
2552
3001
  const map = rawMap ?? { projects: {} };
2553
3002
  reportSharedLinks(links, map);
2554
3003
  const hooksScan = section("Hook targets");
2555
3004
  reportHooksTargetCheck(hooksScan);
2556
3005
  reportHookScopeCheck(hooksScan);
3006
+ reportPreserveSymlinksCheck(hooksScan);
2557
3007
  const settings = section("Settings");
2558
3008
  const base = loadBaseSettings(settings);
2559
3009
  const parsedSettings = loadAndReportSettings(settings);
@@ -2571,7 +3021,8 @@ function cmdDoctor(opts = {}) {
2571
3021
  reportActionsDrift(repository);
2572
3022
  const nomadVersion = section("Nomad Version");
2573
3023
  reportVersionCheck(nomadVersion);
2574
- reportBackupsCheck(nomadVersion);
3024
+ const housekeeping = section("Housekeeping");
3025
+ reportBackupsCheck(housekeeping);
2575
3026
  const depVersions = section("Dependency Versions");
2576
3027
  reportNodeEngineCheck(depVersions);
2577
3028
  reportGitleaksVersionCheck(depVersions);
@@ -2580,7 +3031,7 @@ function cmdDoctor(opts = {}) {
2580
3031
  if (opts.checkShared === true) reportCheckShared(sharedScan, gitleaksReady);
2581
3032
  const schemaScan = section("Schema scan");
2582
3033
  if (opts.checkSchema === true) reportCheckSchema(schemaScan);
2583
- renderDoctor([
3034
+ const body = [
2584
3035
  nomadVersion,
2585
3036
  depVersions,
2586
3037
  host,
@@ -2590,16 +3041,18 @@ function cmdDoctor(opts = {}) {
2590
3041
  pathMap,
2591
3042
  neverSync,
2592
3043
  repository,
3044
+ housekeeping,
2593
3045
  sharedScan,
2594
3046
  schemaScan
2595
- ]);
3047
+ ];
3048
+ renderDoctor([...body, buildVerdictSection(body)]);
2596
3049
  }
2597
3050
 
2598
3051
  // src/commands.drop-session.ts
2599
3052
  init_config();
2600
3053
  import { execFileSync as execFileSync12 } from "node:child_process";
2601
- import { existsSync as existsSync21, readdirSync as readdirSync8, statSync as statSync5 } from "node:fs";
2602
- import { join as join26, relative as relative4 } from "node:path";
3054
+ import { existsSync as existsSync24, readdirSync as readdirSync9, statSync as statSync5 } from "node:fs";
3055
+ import { join as join29, relative as relative4 } from "node:path";
2603
3056
 
2604
3057
  // src/commands.drop-session.git.ts
2605
3058
  init_config();
@@ -2642,8 +3095,8 @@ function isInIndex(rel) {
2642
3095
  init_config();
2643
3096
  init_utils();
2644
3097
  init_utils_json();
2645
- import { existsSync as existsSync20 } from "node:fs";
2646
- import { join as join24 } from "node:path";
3098
+ import { existsSync as existsSync23 } from "node:fs";
3099
+ import { join as join27 } from "node:path";
2647
3100
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
2648
3101
  function reportScrubHint(id, matches) {
2649
3102
  const live = resolveLiveTranscript(id, matches);
@@ -2659,16 +3112,16 @@ function reportScrubHint(id, matches) {
2659
3112
  }
2660
3113
  function resolveLiveTranscript(id, matches) {
2661
3114
  try {
2662
- const mapPath = join24(REPO_HOME, "path-map.json");
2663
- if (!existsSync20(mapPath)) return null;
3115
+ const mapPath = join27(REPO_HOME, "path-map.json");
3116
+ if (!existsSync23(mapPath)) return null;
2664
3117
  const projects = readJson(mapPath).projects;
2665
3118
  for (const rel of matches) {
2666
3119
  const logical = SHARED_PROJECT_LOGICAL.exec(rel)?.[1];
2667
3120
  if (logical === void 0) continue;
2668
3121
  const abs = projects[logical]?.[HOST];
2669
3122
  if (abs === void 0) continue;
2670
- const live = join24(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
2671
- if (existsSync20(live)) return live;
3123
+ const live = join27(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
3124
+ if (existsSync23(live)) return live;
2672
3125
  }
2673
3126
  return null;
2674
3127
  } catch {
@@ -2682,18 +3135,18 @@ init_utils();
2682
3135
  // src/utils.lockfile.ts
2683
3136
  init_config();
2684
3137
  init_utils();
2685
- import { closeSync as closeSync2, mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
2686
- import { dirname as dirname3, join as join25 } from "node:path";
2687
- var LOCK_PATH = join25(HOME, ".cache", "claude-nomad", "nomad.lock");
3138
+ import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
3139
+ import { dirname as dirname4, join as join28 } from "node:path";
3140
+ var LOCK_PATH = join28(HOME, ".cache", "claude-nomad", "nomad.lock");
2688
3141
  function acquireLock(verb) {
2689
- mkdirSync5(dirname3(LOCK_PATH), { recursive: true });
3142
+ mkdirSync5(dirname4(LOCK_PATH), { recursive: true });
2690
3143
  try {
2691
- const fd = openSync2(LOCK_PATH, "wx");
3144
+ const fd = openSync3(LOCK_PATH, "wx");
2692
3145
  try {
2693
3146
  writeFileSync3(fd, String(process.pid));
2694
3147
  } catch (writeErr) {
2695
3148
  try {
2696
- closeSync2(fd);
3149
+ closeSync3(fd);
2697
3150
  } catch {
2698
3151
  }
2699
3152
  try {
@@ -2712,7 +3165,7 @@ function acquireLock(verb) {
2712
3165
  function releaseLock(handle) {
2713
3166
  if (handle === null) return;
2714
3167
  try {
2715
- closeSync2(handle.fd);
3168
+ closeSync3(handle.fd);
2716
3169
  } catch {
2717
3170
  }
2718
3171
  try {
@@ -2724,7 +3177,7 @@ function releaseLock(handle) {
2724
3177
  function unlinkIfSamePid(expectedPidStr) {
2725
3178
  let current;
2726
3179
  try {
2727
- current = readFileSync8(LOCK_PATH, "utf8").trim();
3180
+ current = readFileSync9(LOCK_PATH, "utf8").trim();
2728
3181
  } catch {
2729
3182
  return false;
2730
3183
  }
@@ -2739,7 +3192,7 @@ function unlinkIfSamePid(expectedPidStr) {
2739
3192
  function checkStaleAndRetry(verb) {
2740
3193
  let pidStr;
2741
3194
  try {
2742
- pidStr = readFileSync8(LOCK_PATH, "utf8").trim();
3195
+ pidStr = readFileSync9(LOCK_PATH, "utf8").trim();
2743
3196
  } catch {
2744
3197
  pidStr = "";
2745
3198
  }
@@ -2766,12 +3219,12 @@ function checkStaleAndRetry(verb) {
2766
3219
  }
2767
3220
  function retryOnce(verb) {
2768
3221
  try {
2769
- const fd = openSync2(LOCK_PATH, "wx");
3222
+ const fd = openSync3(LOCK_PATH, "wx");
2770
3223
  try {
2771
3224
  writeFileSync3(fd, String(process.pid));
2772
3225
  } catch {
2773
3226
  try {
2774
- closeSync2(fd);
3227
+ closeSync3(fd);
2775
3228
  } catch {
2776
3229
  }
2777
3230
  try {
@@ -2794,12 +3247,12 @@ function cmdDropSession(id) {
2794
3247
  fail(`invalid session id: ${id}`);
2795
3248
  process.exit(1);
2796
3249
  }
2797
- if (!existsSync21(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
3250
+ if (!existsSync24(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
2798
3251
  const handle = acquireLock("drop-session");
2799
3252
  if (handle === null) process.exit(0);
2800
3253
  try {
2801
- const repoProjects = join26(REPO_HOME, "shared", "projects");
2802
- if (!existsSync21(repoProjects)) {
3254
+ const repoProjects = join29(REPO_HOME, "shared", "projects");
3255
+ if (!existsSync24(repoProjects)) {
2803
3256
  throw new NomadFatal(`no staged session matches ${id}`);
2804
3257
  }
2805
3258
  const matches = collectMatches(repoProjects, id);
@@ -2820,13 +3273,13 @@ function cmdDropSession(id) {
2820
3273
  }
2821
3274
  function collectMatches(repoProjects, id) {
2822
3275
  const matches = [];
2823
- for (const logical of readdirSync8(repoProjects)) {
2824
- const candidate = join26(repoProjects, logical, `${id}.jsonl`);
2825
- if (existsSync21(candidate)) {
3276
+ for (const logical of readdirSync9(repoProjects)) {
3277
+ const candidate = join29(repoProjects, logical, `${id}.jsonl`);
3278
+ if (existsSync24(candidate)) {
2826
3279
  matches.push(relative4(REPO_HOME, candidate));
2827
3280
  }
2828
- const dir = join26(repoProjects, logical, id);
2829
- if (existsSync21(dir) && statSync5(dir).isDirectory()) {
3281
+ const dir = join29(repoProjects, logical, id);
3282
+ if (existsSync24(dir) && statSync5(dir).isDirectory()) {
2830
3283
  const dirRel = relative4(REPO_HOME, dir);
2831
3284
  const staged = expandStagedDir(dirRel);
2832
3285
  if (staged.length > 0) matches.push(...staged);
@@ -2862,20 +3315,20 @@ function unstageOne(rel) {
2862
3315
 
2863
3316
  // src/commands.redact.ts
2864
3317
  init_config();
2865
- import { existsSync as existsSync23, statSync as statSync7 } from "node:fs";
2866
- import { dirname as dirname4, join as join28 } from "node:path";
3318
+ import { existsSync as existsSync26, statSync as statSync7 } from "node:fs";
3319
+ import { dirname as dirname5, join as join31 } from "node:path";
2867
3320
 
2868
3321
  // src/commands.redact.subtree.ts
2869
- import { existsSync as existsSync22, lstatSync as lstatSync6, readFileSync as readFileSync9, readdirSync as readdirSync9, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
2870
- import { join as join27 } from "node:path";
3322
+ import { existsSync as existsSync25, lstatSync as lstatSync7, readFileSync as readFileSync10, readdirSync as readdirSync10, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
3323
+ import { join as join30 } from "node:path";
2871
3324
  init_utils_fs();
2872
3325
  function collectFiles(dir, out) {
2873
- if (!existsSync22(dir)) return;
2874
- const st = lstatSync6(dir);
3326
+ if (!existsSync25(dir)) return;
3327
+ const st = lstatSync7(dir);
2875
3328
  if (!st.isDirectory()) return;
2876
- for (const entry of readdirSync9(dir)) {
2877
- const abs = join27(dir, entry);
2878
- const lst = lstatSync6(abs);
3329
+ for (const entry of readdirSync10(dir)) {
3330
+ const abs = join30(dir, entry);
3331
+ const lst = lstatSync7(abs);
2879
3332
  if (lst.isSymbolicLink()) continue;
2880
3333
  if (lst.isDirectory()) {
2881
3334
  collectFiles(abs, out);
@@ -2911,7 +3364,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
2911
3364
  if (!dryRun && total > 0) {
2912
3365
  for (const { path: filePath, findings } of dirty) {
2913
3366
  backupBeforeWrite(filePath, ts);
2914
- writeFileSync4(filePath, applyRedactions(readFileSync9(filePath, "utf8"), findings), "utf8");
3367
+ writeFileSync4(filePath, applyRedactions(readFileSync10(filePath, "utf8"), findings), "utf8");
2915
3368
  }
2916
3369
  }
2917
3370
  return { total, dirty };
@@ -2924,14 +3377,14 @@ init_utils_json();
2924
3377
  init_utils();
2925
3378
  function resolveLiveTranscript2(id) {
2926
3379
  try {
2927
- const mapPath = join28(REPO_HOME, "path-map.json");
2928
- if (!existsSync23(mapPath)) return null;
3380
+ const mapPath = join31(REPO_HOME, "path-map.json");
3381
+ if (!existsSync26(mapPath)) return null;
2929
3382
  const projects = readJson(mapPath).projects;
2930
3383
  for (const hostMap of Object.values(projects)) {
2931
3384
  const abs = hostMap[HOST];
2932
3385
  if (abs === void 0) continue;
2933
- const live = join28(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
2934
- if (existsSync23(live)) return live;
3386
+ const live = join31(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
3387
+ if (existsSync26(live)) return live;
2935
3388
  }
2936
3389
  return null;
2937
3390
  } catch {
@@ -2949,17 +3402,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
2949
3402
  fail(`invalid session id: ${id}`);
2950
3403
  process.exit(1);
2951
3404
  }
2952
- if (!existsSync23(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
3405
+ if (!existsSync26(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
2953
3406
  const handle = acquireLock("redact");
2954
3407
  if (handle === null) process.exit(0);
2955
3408
  try {
2956
3409
  const localPath = resolveLiveTranscript2(id);
2957
- if (localPath === null || !existsSync23(localPath)) {
3410
+ if (localPath === null || !existsSync26(localPath)) {
2958
3411
  fail(`could not resolve local transcript for session ${id} on this host`);
2959
3412
  process.exitCode = 1;
2960
3413
  return;
2961
3414
  }
2962
- const sessionDir = join28(dirname4(localPath), id);
3415
+ const sessionDir = join31(dirname5(localPath), id);
2963
3416
  const subtreeFiles = listSubtreeFiles(sessionDir);
2964
3417
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
2965
3418
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -3012,8 +3465,8 @@ ${lines}`);
3012
3465
  }
3013
3466
 
3014
3467
  // src/commands.pull.ts
3015
- import { existsSync as existsSync31, mkdirSync as mkdirSync8 } from "node:fs";
3016
- import { join as join37 } from "node:path";
3468
+ import { existsSync as existsSync34, mkdirSync as mkdirSync8 } from "node:fs";
3469
+ import { join as join40 } from "node:path";
3017
3470
 
3018
3471
  // src/commands.push.sections.ts
3019
3472
  init_color();
@@ -3101,8 +3554,8 @@ init_config();
3101
3554
 
3102
3555
  // src/extras-sync.ts
3103
3556
  init_config();
3104
- import { existsSync as existsSync26 } from "node:fs";
3105
- import { join as join31 } from "node:path";
3557
+ import { existsSync as existsSync29 } from "node:fs";
3558
+ import { join as join34 } from "node:path";
3106
3559
 
3107
3560
  // src/extras-sync.diff.ts
3108
3561
  init_utils();
@@ -3141,8 +3594,8 @@ function listDivergingFiles(a, b) {
3141
3594
 
3142
3595
  // src/extras-sync.core.ts
3143
3596
  init_config();
3144
- import { cpSync as cpSync4, existsSync as existsSync24, rmSync as rmSync7 } from "node:fs";
3145
- import { join as join29 } from "node:path";
3597
+ import { cpSync as cpSync5, existsSync as existsSync27, rmSync as rmSync8 } from "node:fs";
3598
+ import { join as join32 } from "node:path";
3146
3599
 
3147
3600
  // src/extras-sync.guards.ts
3148
3601
  init_utils();
@@ -3165,9 +3618,9 @@ function assertSafeLocalRoot(localRoot, logical) {
3165
3618
  init_utils();
3166
3619
  init_utils_json();
3167
3620
  function loadValidatedExtras(opts) {
3168
- const mapPath = join29(REPO_HOME, "path-map.json");
3169
- const repoExtras = join29(REPO_HOME, "shared", "extras");
3170
- if (!existsSync24(mapPath) || opts.requireRepoExtras === true && !existsSync24(repoExtras)) {
3621
+ const mapPath = join32(REPO_HOME, "path-map.json");
3622
+ const repoExtras = join32(REPO_HOME, "shared", "extras");
3623
+ if (!existsSync27(mapPath) || opts.requireRepoExtras === true && !existsSync27(repoExtras)) {
3171
3624
  if (opts.missingMsg !== void 0) log(opts.missingMsg);
3172
3625
  return null;
3173
3626
  }
@@ -3189,18 +3642,18 @@ function* eachExtrasTarget(v, counts) {
3189
3642
  counts.unmapped++;
3190
3643
  continue;
3191
3644
  }
3192
- for (const dirname6 of dirnames) {
3193
- if (!whitelist.includes(dirname6)) {
3645
+ for (const dirname7 of dirnames) {
3646
+ if (!whitelist.includes(dirname7)) {
3194
3647
  counts.skipped++;
3195
3648
  continue;
3196
3649
  }
3197
- yield { logical, localRoot, dirname: dirname6 };
3650
+ yield { logical, localRoot, dirname: dirname7 };
3198
3651
  }
3199
3652
  }
3200
3653
  }
3201
3654
  function copyExtras(src, dst) {
3202
- rmSync7(dst, { recursive: true, force: true });
3203
- cpSync4(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
3655
+ rmSync8(dst, { recursive: true, force: true });
3656
+ cpSync5(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
3204
3657
  }
3205
3658
 
3206
3659
  // src/extras-sync.ts
@@ -3209,8 +3662,8 @@ init_utils_json();
3209
3662
 
3210
3663
  // src/extras-sync.remap.ts
3211
3664
  init_config();
3212
- import { existsSync as existsSync25, mkdirSync as mkdirSync6 } from "node:fs";
3213
- import { join as join30 } from "node:path";
3665
+ import { existsSync as existsSync28, mkdirSync as mkdirSync6 } from "node:fs";
3666
+ import { join as join33 } from "node:path";
3214
3667
  init_utils_fs();
3215
3668
  function runExtrasOp(v, dryRun, paths, backup) {
3216
3669
  const counts = { unmapped: 0, skipped: 0 };
@@ -3218,7 +3671,7 @@ function runExtrasOp(v, dryRun, paths, backup) {
3218
3671
  const would = [];
3219
3672
  for (const t of eachExtrasTarget(v, counts)) {
3220
3673
  const { src, dst } = paths(t);
3221
- if (!existsSync25(src)) continue;
3674
+ if (!existsSync28(src)) continue;
3222
3675
  const item = `${t.logical}/${t.dirname}`;
3223
3676
  if (dryRun) {
3224
3677
  would.push(item);
@@ -3234,14 +3687,14 @@ function remapExtrasPush(ts, opts = {}) {
3234
3687
  const dryRun = opts.dryRun === true;
3235
3688
  const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
3236
3689
  if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
3237
- const repoExtras = join30(REPO_HOME, "shared", "extras");
3690
+ const repoExtras = join33(REPO_HOME, "shared", "extras");
3238
3691
  if (!dryRun) mkdirSync6(repoExtras, { recursive: true });
3239
3692
  const { unmapped, skipped, done, would } = runExtrasOp(
3240
3693
  v,
3241
3694
  dryRun,
3242
- ({ localRoot, logical, dirname: dirname6 }) => ({
3243
- src: join30(localRoot, dirname6),
3244
- dst: join30(repoExtras, logical, dirname6)
3695
+ ({ localRoot, logical, dirname: dirname7 }) => ({
3696
+ src: join33(localRoot, dirname7),
3697
+ dst: join33(repoExtras, logical, dirname7)
3245
3698
  }),
3246
3699
  (dst) => backupRepoWrite(dst, ts, REPO_HOME)
3247
3700
  );
@@ -3254,13 +3707,13 @@ function remapExtrasPull(ts, opts = {}) {
3254
3707
  missingMsg: "no path-map or repo extras dir; skipping extras remap"
3255
3708
  });
3256
3709
  if (v === null) return { unmapped: 0, skipped: 0, pulled: [], wouldPull: [] };
3257
- const repoExtras = join30(REPO_HOME, "shared", "extras");
3710
+ const repoExtras = join33(REPO_HOME, "shared", "extras");
3258
3711
  const { unmapped, skipped, done, would } = runExtrasOp(
3259
3712
  v,
3260
3713
  dryRun,
3261
- ({ localRoot, logical, dirname: dirname6 }) => ({
3262
- src: join30(repoExtras, logical, dirname6),
3263
- dst: join30(localRoot, dirname6)
3714
+ ({ localRoot, logical, dirname: dirname7 }) => ({
3715
+ src: join33(repoExtras, logical, dirname7),
3716
+ dst: join33(localRoot, dirname7)
3264
3717
  }),
3265
3718
  // Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
3266
3719
  // localRoot so the backup tree mirrors the project layout.
@@ -3274,16 +3727,16 @@ function divergenceCheckExtras(ts) {
3274
3727
  const v = loadValidatedExtras({});
3275
3728
  if (v === null) return;
3276
3729
  const counts = { unmapped: 0, skipped: 0 };
3277
- const backupRoot = join31(BACKUP_BASE, ts, "extras");
3278
- for (const { logical, localRoot, dirname: dirname6 } of eachExtrasTarget(v, counts)) {
3279
- const local = join31(localRoot, dirname6);
3280
- const repo = join31(REPO_HOME, "shared", "extras", logical, dirname6);
3281
- if (!existsSync26(local) || !existsSync26(repo)) continue;
3730
+ const backupRoot = join34(BACKUP_BASE, ts, "extras");
3731
+ for (const { logical, localRoot, dirname: dirname7 } of eachExtrasTarget(v, counts)) {
3732
+ const local = join34(localRoot, dirname7);
3733
+ const repo = join34(REPO_HOME, "shared", "extras", logical, dirname7);
3734
+ if (!existsSync29(local) || !existsSync29(repo)) continue;
3282
3735
  const diff = listDivergingFiles(local, repo);
3283
3736
  if (diff.length === 0) continue;
3284
- const projectBackupRoot = join31(backupRoot, encodePath(localRoot));
3737
+ const projectBackupRoot = join34(backupRoot, encodePath(localRoot));
3285
3738
  warn(
3286
- `local ${dirname6} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will overwrite them (backups at ${projectBackupRoot}/)`
3739
+ `local ${dirname7} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will overwrite them (backups at ${projectBackupRoot}/)`
3287
3740
  );
3288
3741
  for (const f of diff) warn(` ${f}`);
3289
3742
  }
@@ -3294,8 +3747,8 @@ init_config();
3294
3747
  init_utils();
3295
3748
  init_utils_fs();
3296
3749
  init_utils_json();
3297
- import { existsSync as existsSync27, lstatSync as lstatSync7, rmSync as rmSync8 } from "node:fs";
3298
- import { join as join32 } from "node:path";
3750
+ import { existsSync as existsSync30, lstatSync as lstatSync8, rmSync as rmSync9 } from "node:fs";
3751
+ import { join as join35 } from "node:path";
3299
3752
  function emitAutoMove(onPreview, linkPath, ts, name) {
3300
3753
  if (onPreview) {
3301
3754
  onPreview({ kind: "auto-move", from: linkPath, to: `backup/${ts}/${name}` });
@@ -3314,41 +3767,41 @@ function applySharedLinks(ts, map, opts = {}) {
3314
3767
  const dryRun = opts.dryRun === true;
3315
3768
  const linkNames = allSharedLinks(map);
3316
3769
  for (const name of linkNames) {
3317
- const linkPath = join32(CLAUDE_HOME, name);
3318
- const target = join32(REPO_HOME, "shared", name);
3319
- if (!existsSync27(linkPath)) continue;
3320
- if (lstatSync7(linkPath).isSymbolicLink()) continue;
3321
- if (!existsSync27(target)) continue;
3770
+ const linkPath = join35(CLAUDE_HOME, name);
3771
+ const target = join35(REPO_HOME, "shared", name);
3772
+ if (!existsSync30(linkPath)) continue;
3773
+ if (lstatSync8(linkPath).isSymbolicLink()) continue;
3774
+ if (!existsSync30(target)) continue;
3322
3775
  if (dryRun) {
3323
3776
  emitAutoMove(opts.onPreview, linkPath, ts, name);
3324
3777
  continue;
3325
3778
  }
3326
3779
  backupBeforeWrite(linkPath, ts);
3327
- rmSync8(linkPath, { recursive: true, force: true });
3780
+ rmSync9(linkPath, { recursive: true, force: true });
3328
3781
  }
3329
3782
  for (const name of linkNames) {
3330
- const target = join32(REPO_HOME, "shared", name);
3331
- if (!existsSync27(target)) continue;
3783
+ const target = join35(REPO_HOME, "shared", name);
3784
+ if (!existsSync30(target)) continue;
3332
3785
  if (dryRun) {
3333
- emitCreate(opts.onPreview, join32(CLAUDE_HOME, name), target);
3786
+ emitCreate(opts.onPreview, join35(CLAUDE_HOME, name), target);
3334
3787
  continue;
3335
3788
  }
3336
- ensureSymlink(join32(CLAUDE_HOME, name), target);
3789
+ ensureSymlink(join35(CLAUDE_HOME, name), target);
3337
3790
  }
3338
3791
  }
3339
3792
  function regenerateSettings(ts, opts = {}) {
3340
3793
  const dryRun = opts.dryRun === true;
3341
- const basePath = join32(REPO_HOME, "shared", "settings.base.json");
3342
- const hostPath = join32(REPO_HOME, "hosts", `${HOST}.json`);
3343
- if (!existsSync27(basePath)) {
3794
+ const basePath = join35(REPO_HOME, "shared", "settings.base.json");
3795
+ const hostPath = join35(REPO_HOME, "hosts", `${HOST}.json`);
3796
+ if (!existsSync30(basePath)) {
3344
3797
  die("repo not initialized; run 'nomad init' to scaffold");
3345
3798
  }
3346
3799
  const base = readJson(basePath);
3347
- const hasOverrides = existsSync27(hostPath);
3800
+ const hasOverrides = existsSync30(hostPath);
3348
3801
  const overrides = hasOverrides ? readJson(hostPath) : {};
3349
3802
  const merged = deepMerge(base, overrides);
3350
- const settingsPath = join32(CLAUDE_HOME, "settings.json");
3351
- if (!hasOverrides && existsSync27(settingsPath)) {
3803
+ const settingsPath = join35(CLAUDE_HOME, "settings.json");
3804
+ if (!hasOverrides && existsSync30(settingsPath)) {
3352
3805
  try {
3353
3806
  const existing = readJson(settingsPath);
3354
3807
  const baseKeys = new Set(Object.keys(base));
@@ -3374,8 +3827,8 @@ function regenerateSettings(ts, opts = {}) {
3374
3827
 
3375
3828
  // src/preview.ts
3376
3829
  init_config();
3377
- import { existsSync as existsSync28 } from "node:fs";
3378
- import { join as join33 } from "node:path";
3830
+ import { existsSync as existsSync31 } from "node:fs";
3831
+ import { join as join36 } from "node:path";
3379
3832
 
3380
3833
  // node_modules/diff/libesm/diff/base.js
3381
3834
  var Diff = class {
@@ -3661,7 +4114,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
3661
4114
  return lines.join("\n");
3662
4115
  }
3663
4116
  function readJsonOrNull(path) {
3664
- if (!existsSync28(path)) return null;
4117
+ if (!existsSync31(path)) return null;
3665
4118
  try {
3666
4119
  return readJson(path);
3667
4120
  } catch {
@@ -3675,12 +4128,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
3675
4128
  }
3676
4129
  const notes = [];
3677
4130
  const hostOverrides = readJsonOrNull(hostPath);
3678
- if (hostOverrides === null && existsSync28(hostPath)) {
4131
+ if (hostOverrides === null && existsSync31(hostPath)) {
3679
4132
  notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
3680
4133
  }
3681
4134
  const merged = deepMerge(base, hostOverrides ?? {});
3682
4135
  const current = readJsonOrNull(settingsPath);
3683
- if (current === null && existsSync28(settingsPath)) {
4136
+ if (current === null && existsSync31(settingsPath)) {
3684
4137
  return { diff: "", notes: [...notes, "malformed; skipping diff"] };
3685
4138
  }
3686
4139
  const rawEqual = JSON.stringify(current ?? {}, null, 2) === JSON.stringify(merged, null, 2);
@@ -3718,9 +4171,9 @@ function computePreview(ts, map, verb = "pull") {
3718
4171
  onPreview: (e) => addItem(links, formatLinkRow(e))
3719
4172
  });
3720
4173
  const settingsResult = previewSettings(
3721
- join33(REPO_HOME, "shared", "settings.base.json"),
3722
- join33(REPO_HOME, "hosts", `${HOST}.json`),
3723
- join33(CLAUDE_HOME, "settings.json")
4174
+ join36(REPO_HOME, "shared", "settings.base.json"),
4175
+ join36(REPO_HOME, "hosts", `${HOST}.json`),
4176
+ join36(CLAUDE_HOME, "settings.json")
3724
4177
  );
3725
4178
  const settingsSection = buildSettingsSectionForPreview(settingsResult);
3726
4179
  const sessions = section("Sessions");
@@ -3736,21 +4189,21 @@ function computePreview(ts, map, verb = "pull") {
3736
4189
 
3737
4190
  // src/spinner.ts
3738
4191
  init_color();
3739
- import { existsSync as existsSync30 } from "node:fs";
4192
+ import { existsSync as existsSync33 } from "node:fs";
3740
4193
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3741
4194
  import { Worker } from "node:worker_threads";
3742
4195
 
3743
4196
  // src/commands.push.recovery.ts
3744
4197
  init_config();
3745
- import { readFileSync as readFileSync10, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
3746
- import { join as join36 } from "node:path";
4198
+ import { readFileSync as readFileSync11, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
4199
+ import { join as join39 } from "node:path";
3747
4200
  import { createInterface } from "node:readline/promises";
3748
4201
 
3749
4202
  // src/commands.push.recovery.redact.ts
3750
4203
  init_config();
3751
4204
  init_config_sharedDirs_guard();
3752
- import { cpSync as cpSync5, existsSync as existsSync29, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
3753
- import { dirname as dirname5, join as join34, sep as sep2 } from "node:path";
4205
+ import { cpSync as cpSync6, existsSync as existsSync32, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4206
+ import { dirname as dirname6, join as join37, sep as sep3 } from "node:path";
3754
4207
  init_push_gitleaks_scan();
3755
4208
  init_utils_json();
3756
4209
  init_utils();
@@ -3781,8 +4234,8 @@ function resolveStagedDir(localPath, map) {
3781
4234
  assertSafeLogical(logical);
3782
4235
  const abs = hostMap[HOST];
3783
4236
  if (abs === void 0) continue;
3784
- if (localPath.startsWith(join34(CLAUDE_HOME, "projects", encodePath(abs)) + sep2)) {
3785
- return join34(REPO_HOME, "shared", "projects", logical);
4237
+ if (localPath.startsWith(join37(CLAUDE_HOME, "projects", encodePath(abs)) + sep3)) {
4238
+ return join37(REPO_HOME, "shared", "projects", logical);
3786
4239
  }
3787
4240
  }
3788
4241
  return null;
@@ -3804,7 +4257,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
3804
4257
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
3805
4258
  );
3806
4259
  }
3807
- const sessionDir = join34(dirname5(localPath), sid);
4260
+ const sessionDir = join37(dirname6(localPath), sid);
3808
4261
  const subtreeFiles = listSubtreeFiles(sessionDir);
3809
4262
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
3810
4263
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -3838,25 +4291,25 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
3838
4291
  );
3839
4292
  }
3840
4293
  mkdirSync7(stagedProjectDir, { recursive: true });
3841
- cpSync5(localPath, join34(stagedProjectDir, `${sid}.jsonl`), { force: true });
3842
- if (existsSync29(sessionDir)) {
3843
- cpSync5(sessionDir, join34(stagedProjectDir, sid), { force: true, recursive: true });
4294
+ cpSync6(localPath, join37(stagedProjectDir, `${sid}.jsonl`), { force: true });
4295
+ if (existsSync32(sessionDir)) {
4296
+ cpSync6(sessionDir, join37(stagedProjectDir, sid), { force: true, recursive: true });
3844
4297
  }
3845
4298
  return true;
3846
4299
  }
3847
4300
 
3848
4301
  // src/commands.push.recovery.drop.ts
3849
4302
  init_config();
3850
- import { rmSync as rmSync9 } from "node:fs";
3851
- import { join as join35 } from "node:path";
4303
+ import { rmSync as rmSync10 } from "node:fs";
4304
+ import { join as join38 } from "node:path";
3852
4305
  function dropSessionFromStaged(sid, map) {
3853
4306
  const logicals = Object.keys(map.projects);
3854
4307
  if (logicals.length === 0) return false;
3855
4308
  for (const logical of logicals) {
3856
- const jsonl = join35(REPO_HOME, "shared", "projects", logical, `${sid}.jsonl`);
3857
- const dir = join35(REPO_HOME, "shared", "projects", logical, sid);
3858
- rmSync9(jsonl, { force: true });
3859
- rmSync9(dir, { recursive: true, force: true });
4309
+ const jsonl = join38(REPO_HOME, "shared", "projects", logical, `${sid}.jsonl`);
4310
+ const dir = join38(REPO_HOME, "shared", "projects", logical, sid);
4311
+ rmSync10(jsonl, { force: true });
4312
+ rmSync10(dir, { recursive: true, force: true });
3860
4313
  }
3861
4314
  return true;
3862
4315
  }
@@ -3972,10 +4425,10 @@ function applyThenRescan(scanVerdict, repoHome) {
3972
4425
  return next;
3973
4426
  }
3974
4427
  function allowThenRescan(append, scanVerdict, repoHome) {
3975
- const ignPath = join36(repoHome, ".gitleaksignore");
4428
+ const ignPath = join39(repoHome, ".gitleaksignore");
3976
4429
  let before;
3977
4430
  try {
3978
- before = readFileSync10(ignPath, "utf8");
4431
+ before = readFileSync11(ignPath, "utf8");
3979
4432
  } catch {
3980
4433
  before = null;
3981
4434
  }
@@ -3983,7 +4436,7 @@ function allowThenRescan(append, scanVerdict, repoHome) {
3983
4436
  try {
3984
4437
  return applyThenRescan(scanVerdict, repoHome);
3985
4438
  } catch (err) {
3986
- if (before === null) rmSync10(ignPath, { force: true });
4439
+ if (before === null) rmSync11(ignPath, { force: true });
3987
4440
  else writeFileSync5(ignPath, before, "utf8");
3988
4441
  throw err;
3989
4442
  }
@@ -4070,7 +4523,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
4070
4523
  `);
4071
4524
  }
4072
4525
  function resolveWorkerPath(deps = {}) {
4073
- const check = deps.existsSyncFn ?? existsSync30;
4526
+ const check = deps.existsSyncFn ?? existsSync33;
4074
4527
  const base = deps.baseUrl ?? import.meta.url;
4075
4528
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
4076
4529
  if (check(mjs)) return mjs;
@@ -4276,8 +4729,8 @@ function handleWedge(repo, forceRemote) {
4276
4729
  function cmdPull(opts = {}) {
4277
4730
  const dryRun = opts.dryRun === true;
4278
4731
  const forceRemote = opts.forceRemote === true;
4279
- if (!existsSync31(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
4280
- if (!existsSync31(join37(REPO_HOME, "shared", "settings.base.json"))) {
4732
+ if (!existsSync34(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
4733
+ if (!existsSync34(join40(REPO_HOME, "shared", "settings.base.json"))) {
4281
4734
  die("repo not initialized; run 'nomad init' to scaffold");
4282
4735
  }
4283
4736
  const handle = acquireLock("pull");
@@ -4286,7 +4739,7 @@ function cmdPull(opts = {}) {
4286
4739
  const ts = freshBackupTs(BACKUP_BASE);
4287
4740
  handleWedge(REPO_HOME, forceRemote);
4288
4741
  if (!dryRun) {
4289
- const backupRoot = join37(BACKUP_BASE, ts);
4742
+ const backupRoot = join40(BACKUP_BASE, ts);
4290
4743
  try {
4291
4744
  mkdirSync8(backupRoot, { recursive: true });
4292
4745
  } catch (err) {
@@ -4297,8 +4750,8 @@ function cmdPull(opts = {}) {
4297
4750
  dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
4298
4751
  );
4299
4752
  gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", REPO_HOME);
4300
- const mapPath = join37(REPO_HOME, "path-map.json");
4301
- const map = existsSync31(mapPath) ? readPathMap(mapPath) : { projects: {} };
4753
+ const mapPath = join40(REPO_HOME, "path-map.json");
4754
+ const map = existsSync34(mapPath) ? readPathMap(mapPath) : { projects: {} };
4302
4755
  divergenceCheckExtras(ts);
4303
4756
  if (dryRun) {
4304
4757
  computePreview(ts, map, "pull");
@@ -4320,8 +4773,8 @@ function cmdPull(opts = {}) {
4320
4773
 
4321
4774
  // src/commands.push.ts
4322
4775
  init_config();
4323
- import { existsSync as existsSync33 } from "node:fs";
4324
- import { join as join39, relative as relative5 } from "node:path";
4776
+ import { existsSync as existsSync36 } from "node:fs";
4777
+ import { join as join42, relative as relative5 } from "node:path";
4325
4778
 
4326
4779
  // src/commands.push.allowlist.ts
4327
4780
  init_config();
@@ -4401,9 +4854,9 @@ init_color();
4401
4854
  init_config();
4402
4855
  init_config_sharedDirs_guard();
4403
4856
  import { randomBytes as randomBytes2 } from "node:crypto";
4404
- import { copyFileSync, existsSync as existsSync32, mkdirSync as mkdirSync9, readdirSync as readdirSync10, rmSync as rmSync11 } from "node:fs";
4857
+ import { copyFileSync, existsSync as existsSync35, mkdirSync as mkdirSync9, readdirSync as readdirSync11, rmSync as rmSync12 } from "node:fs";
4405
4858
  import { homedir as homedir5 } from "node:os";
4406
- import { join as join38 } from "node:path";
4859
+ import { join as join41 } from "node:path";
4407
4860
  init_push_leak_verdict();
4408
4861
  init_push_gitleaks();
4409
4862
  init_utils_fs();
@@ -4418,13 +4871,13 @@ function stageSessions(tmpRoot, map) {
4418
4871
  if (!p || p === "TBD") continue;
4419
4872
  reverse.set(encodePath(p), logical);
4420
4873
  }
4421
- const localProjects = join38(CLAUDE_HOME, "projects");
4422
- if (!existsSync32(localProjects)) return 0;
4874
+ const localProjects = join41(CLAUDE_HOME, "projects");
4875
+ if (!existsSync35(localProjects)) return 0;
4423
4876
  let staged = 0;
4424
- for (const dir of readdirSync10(localProjects)) {
4877
+ for (const dir of readdirSync11(localProjects)) {
4425
4878
  const logical = reverse.get(dir);
4426
4879
  if (!logical) continue;
4427
- copyDirJsonlOnly(join38(localProjects, dir), join38(tmpRoot, "shared", "projects", logical));
4880
+ copyDirJsonlOnly(join41(localProjects, dir), join41(tmpRoot, "shared", "projects", logical));
4428
4881
  staged++;
4429
4882
  }
4430
4883
  return staged;
@@ -4438,11 +4891,11 @@ function stageExtras(tmpRoot, map) {
4438
4891
  assertSafeLogical(logical);
4439
4892
  const localRoot = map.projects[logical]?.[HOST];
4440
4893
  if (!localRoot || localRoot === "TBD") continue;
4441
- for (const dirname6 of dirnames) {
4442
- if (!whitelist.includes(dirname6)) continue;
4443
- const src = join38(localRoot, dirname6);
4444
- if (!existsSync32(src)) continue;
4445
- const dst = join38(tmpRoot, "shared", "extras", logical, dirname6);
4894
+ for (const dirname7 of dirnames) {
4895
+ if (!whitelist.includes(dirname7)) continue;
4896
+ const src = join41(localRoot, dirname7);
4897
+ if (!existsSync35(src)) continue;
4898
+ const dst = join41(tmpRoot, "shared", "extras", logical, dirname7);
4446
4899
  copyExtras(src, dst);
4447
4900
  staged++;
4448
4901
  }
@@ -4450,19 +4903,19 @@ function stageExtras(tmpRoot, map) {
4450
4903
  return staged;
4451
4904
  }
4452
4905
  function previewPushLeaks(map) {
4453
- const cacheDir = join38(homedir5(), ".cache", "claude-nomad");
4906
+ const cacheDir = join41(homedir5(), ".cache", "claude-nomad");
4454
4907
  mkdirSync9(cacheDir, { recursive: true });
4455
4908
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
4456
- const tmpRoot = join38(cacheDir, `push-preview-tree-${stamp}`);
4909
+ const tmpRoot = join41(cacheDir, `push-preview-tree-${stamp}`);
4457
4910
  try {
4458
4911
  const sessionCount = stageSessions(tmpRoot, map);
4459
4912
  const extrasCount = stageExtras(tmpRoot, map);
4460
4913
  if (sessionCount + extrasCount === 0) {
4461
4914
  return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
4462
4915
  }
4463
- const ignoreFile = join38(REPO_HOME, ".gitleaksignore");
4464
- if (existsSync32(ignoreFile)) {
4465
- copyFileSync(ignoreFile, join38(tmpRoot, ".gitleaksignore"));
4916
+ const ignoreFile = join41(REPO_HOME, ".gitleaksignore");
4917
+ if (existsSync35(ignoreFile)) {
4918
+ copyFileSync(ignoreFile, join41(tmpRoot, ".gitleaksignore"));
4466
4919
  }
4467
4920
  let findings;
4468
4921
  try {
@@ -4475,7 +4928,7 @@ function previewPushLeaks(map) {
4475
4928
  }
4476
4929
  return verdictFromFindings(findings);
4477
4930
  } finally {
4478
- rmSync11(tmpRoot, { recursive: true, force: true });
4931
+ rmSync12(tmpRoot, { recursive: true, force: true });
4479
4932
  }
4480
4933
  }
4481
4934
 
@@ -4484,7 +4937,7 @@ init_utils();
4484
4937
  init_utils_fs();
4485
4938
  init_utils_json();
4486
4939
  function guardGitlinks() {
4487
- const gitlinks = findGitlinks(join39(REPO_HOME, "shared"));
4940
+ const gitlinks = findGitlinks(join42(REPO_HOME, "shared"));
4488
4941
  if (gitlinks.length === 0) return;
4489
4942
  for (const p of gitlinks) {
4490
4943
  const rel = relative5(REPO_HOME, p);
@@ -4536,7 +4989,7 @@ async function cmdPush(opts = {}) {
4536
4989
  const allowAll = opts.allowAll === true;
4537
4990
  const allowRule = opts.allowRule;
4538
4991
  guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
4539
- if (!existsSync33(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
4992
+ if (!existsSync36(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
4540
4993
  const handle = acquireLock("push");
4541
4994
  if (handle === null) process.exit(0);
4542
4995
  try {
@@ -4554,8 +5007,8 @@ async function cmdPush(opts = {}) {
4554
5007
  renderNoScanTree(st);
4555
5008
  return;
4556
5009
  }
4557
- const mapPath = join39(REPO_HOME, "path-map.json");
4558
- if (!existsSync33(mapPath)) {
5010
+ const mapPath = join42(REPO_HOME, "path-map.json");
5011
+ if (!existsSync36(mapPath)) {
4559
5012
  if (dryRun) return runDryRunPreview(st, null);
4560
5013
  die("path-map.json missing, cannot enforce push allow-list");
4561
5014
  }
@@ -4595,17 +5048,17 @@ init_config();
4595
5048
 
4596
5049
  // src/diff.ts
4597
5050
  init_config();
4598
- import { existsSync as existsSync34 } from "node:fs";
4599
- import { join as join40 } from "node:path";
5051
+ import { existsSync as existsSync37 } from "node:fs";
5052
+ import { join as join43 } from "node:path";
4600
5053
  init_utils();
4601
5054
  init_utils_fs();
4602
5055
  init_utils_json();
4603
5056
  function cmdDiff() {
4604
5057
  try {
4605
- if (!existsSync34(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
5058
+ if (!existsSync37(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
4606
5059
  const ts = freshBackupTs(BACKUP_BASE);
4607
- const mapPath = join40(REPO_HOME, "path-map.json");
4608
- const map = existsSync34(mapPath) ? readPathMap(mapPath) : { projects: {} };
5060
+ const mapPath = join43(REPO_HOME, "path-map.json");
5061
+ const map = existsSync37(mapPath) ? readPathMap(mapPath) : { projects: {} };
4609
5062
  computePreview(ts, map, "diff");
4610
5063
  } catch (err) {
4611
5064
  if (err instanceof NomadFatal) {
@@ -4619,8 +5072,8 @@ function cmdDiff() {
4619
5072
 
4620
5073
  // src/init.ts
4621
5074
  init_config();
4622
- import { existsSync as existsSync36, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "node:fs";
4623
- import { join as join42 } from "node:path";
5075
+ import { existsSync as existsSync39, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "node:fs";
5076
+ import { join as join45 } from "node:path";
4624
5077
 
4625
5078
  // src/init.gh-onboard.ts
4626
5079
  init_config();
@@ -4701,31 +5154,31 @@ init_config();
4701
5154
  init_utils();
4702
5155
  init_utils_fs();
4703
5156
  init_utils_json();
4704
- import { copyFileSync as copyFileSync2, cpSync as cpSync6, existsSync as existsSync35, rmSync as rmSync12, statSync as statSync9 } from "node:fs";
4705
- import { join as join41 } from "node:path";
5157
+ import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync38, rmSync as rmSync13, statSync as statSync9 } from "node:fs";
5158
+ import { join as join44 } from "node:path";
4706
5159
  function snapshotIntoShared(map) {
4707
5160
  for (const name of allSharedLinks(map)) {
4708
- const src = join41(CLAUDE_HOME, name);
4709
- if (!existsSync35(src)) continue;
4710
- const dst = join41(REPO_HOME, "shared", name);
5161
+ const src = join44(CLAUDE_HOME, name);
5162
+ if (!existsSync38(src)) continue;
5163
+ const dst = join44(REPO_HOME, "shared", name);
4711
5164
  if (statSync9(src).isDirectory()) {
4712
- const gk = join41(dst, ".gitkeep");
4713
- if (existsSync35(gk)) rmSync12(gk);
4714
- cpSync6(src, dst, { recursive: true, force: false, errorOnExist: true });
5165
+ const gk = join44(dst, ".gitkeep");
5166
+ if (existsSync38(gk)) rmSync13(gk);
5167
+ cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
4715
5168
  } else {
4716
5169
  copyFileSync2(src, dst);
4717
5170
  }
4718
5171
  log(`snapshotted shared/${name} from ${src}`);
4719
5172
  }
4720
- const userSettings = join41(CLAUDE_HOME, "settings.json");
4721
- if (existsSync35(userSettings)) {
5173
+ const userSettings = join44(CLAUDE_HOME, "settings.json");
5174
+ if (existsSync38(userSettings)) {
4722
5175
  let parsed;
4723
5176
  try {
4724
5177
  parsed = readJson(userSettings);
4725
5178
  } catch (err) {
4726
5179
  return die(`malformed ${userSettings}: ${err.message}`);
4727
5180
  }
4728
- const hostFile = join41(REPO_HOME, "hosts", `${HOST}.json`);
5181
+ const hostFile = join44(REPO_HOME, "hosts", `${HOST}.json`);
4729
5182
  writeJsonAtomic(hostFile, parsed);
4730
5183
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
4731
5184
  }
@@ -4738,14 +5191,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
4738
5191
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
4739
5192
  function preflightConflict(repoHome) {
4740
5193
  const candidates = [
4741
- join42(repoHome, "shared", "settings.base.json"),
4742
- join42(repoHome, "shared", "CLAUDE.md"),
4743
- join42(repoHome, "path-map.json"),
4744
- join42(repoHome, "hosts"),
4745
- join42(repoHome, "shared")
5194
+ join45(repoHome, "shared", "settings.base.json"),
5195
+ join45(repoHome, "shared", "CLAUDE.md"),
5196
+ join45(repoHome, "path-map.json"),
5197
+ join45(repoHome, "hosts"),
5198
+ join45(repoHome, "shared")
4746
5199
  ];
4747
5200
  for (const c of candidates) {
4748
- if (existsSync36(c)) return c;
5201
+ if (existsSync39(c)) return c;
4749
5202
  }
4750
5203
  return null;
4751
5204
  }
@@ -4758,25 +5211,25 @@ function cmdInit(opts = {}) {
4758
5211
  die(`already initialized; refusing to clobber ${conflict}`);
4759
5212
  }
4760
5213
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
4761
- mkdirSync10(join42(REPO_HOME, "shared"), { recursive: true });
4762
- mkdirSync10(join42(REPO_HOME, "hosts"), { recursive: true });
5214
+ mkdirSync10(join45(REPO_HOME, "shared"), { recursive: true });
5215
+ mkdirSync10(join45(REPO_HOME, "hosts"), { recursive: true });
4763
5216
  for (const name of SHARED_KEEP_DIRS) {
4764
- mkdirSync10(join42(REPO_HOME, "shared", name), { recursive: true });
5217
+ mkdirSync10(join45(REPO_HOME, "shared", name), { recursive: true });
4765
5218
  }
4766
- const userClaudeMd = join42(CLAUDE_HOME, "CLAUDE.md");
4767
- if (!snapshot || !existsSync36(userClaudeMd)) {
4768
- writeFileSync6(join42(REPO_HOME, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
5219
+ const userClaudeMd = join45(CLAUDE_HOME, "CLAUDE.md");
5220
+ if (!snapshot || !existsSync39(userClaudeMd)) {
5221
+ writeFileSync6(join45(REPO_HOME, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
4769
5222
  log("created shared/CLAUDE.md");
4770
5223
  }
4771
5224
  for (const name of SHARED_KEEP_DIRS) {
4772
- writeFileSync6(join42(REPO_HOME, "shared", name, ".gitkeep"), "");
5225
+ writeFileSync6(join45(REPO_HOME, "shared", name, ".gitkeep"), "");
4773
5226
  log(`created shared/${name}/.gitkeep`);
4774
5227
  }
4775
- writeFileSync6(join42(REPO_HOME, "hosts", ".gitkeep"), "");
5228
+ writeFileSync6(join45(REPO_HOME, "hosts", ".gitkeep"), "");
4776
5229
  log("created hosts/.gitkeep");
4777
- writeJsonAtomic(join42(REPO_HOME, "shared", "settings.base.json"), {});
5230
+ writeJsonAtomic(join45(REPO_HOME, "shared", "settings.base.json"), {});
4778
5231
  log("created shared/settings.base.json");
4779
- writeJsonAtomic(join42(REPO_HOME, "path-map.json"), { projects: {} });
5232
+ writeJsonAtomic(join45(REPO_HOME, "path-map.json"), { projects: {} });
4780
5233
  log("created path-map.json");
4781
5234
  if (snapshot) {
4782
5235
  snapshotIntoShared({ projects: {} });
@@ -4970,6 +5423,23 @@ function parseCleanArgs(argv) {
4970
5423
  return { dryRun: st.dryRun, olderThan: st.olderThan, keep: st.keep };
4971
5424
  }
4972
5425
 
5426
+ // src/nomad.dispatch.eject.ts
5427
+ function parseEjectArgs(argv) {
5428
+ let dryRun = false;
5429
+ let i = 3;
5430
+ while (i < argv.length) {
5431
+ const token = argv[i];
5432
+ if (token === "--dry-run") {
5433
+ if (dryRun) return null;
5434
+ dryRun = true;
5435
+ } else {
5436
+ return null;
5437
+ }
5438
+ i++;
5439
+ }
5440
+ return { dryRun };
5441
+ }
5442
+
4973
5443
  // src/nomad.dispatch.allow.ts
4974
5444
  function parseAllowArgs(argv) {
4975
5445
  const positionals = argv.slice(3);
@@ -5060,7 +5530,7 @@ function parsePushArgs(argv) {
5060
5530
  // package.json
5061
5531
  var package_default = {
5062
5532
  name: "claude-nomad",
5063
- version: "0.42.0",
5533
+ version: "0.44.0",
5064
5534
  type: "module",
5065
5535
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
5066
5536
  keywords: [
@@ -5219,6 +5689,11 @@ var DEFAULT_HELP = [
5219
5689
  cont("symlink, and stage for push. <name> must be in SHARED_LINKS or sharedDirs."),
5220
5690
  row(" --dry-run", "Preview backup, move, and git-add without writing."),
5221
5691
  "",
5692
+ row(" eject", "Materialize every managed ~/.claude/ symlink into a real copy so the"),
5693
+ cont("setup keeps working after deleting the sync repo. Prints a"),
5694
+ cont("manual-remainder checklist (uninstall CLI, drop env vars, optional deletes)."),
5695
+ row(" --dry-run", "List what would be materialized without writing anything."),
5696
+ "",
5222
5697
  row(
5223
5698
  " redact <session-id>",
5224
5699
  "Rewrite the secret span in the local source transcript for a session,"
@@ -5255,15 +5730,15 @@ var DEFAULT_HELP = [
5255
5730
  init_config();
5256
5731
  init_utils();
5257
5732
  init_utils_json();
5258
- import { existsSync as existsSync37, readFileSync as readFileSync11, readdirSync as readdirSync11 } from "node:fs";
5259
- import { join as join43 } from "node:path";
5733
+ import { existsSync as existsSync40, readFileSync as readFileSync12, readdirSync as readdirSync12 } from "node:fs";
5734
+ import { join as join46 } from "node:path";
5260
5735
  function resumeCmd(sessionId) {
5261
5736
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
5262
5737
  fail(`invalid session id: ${sessionId}`);
5263
5738
  process.exit(1);
5264
5739
  }
5265
- const projectsRoot = join43(CLAUDE_HOME, "projects");
5266
- if (!existsSync37(projectsRoot)) {
5740
+ const projectsRoot = join46(CLAUDE_HOME, "projects");
5741
+ if (!existsSync40(projectsRoot)) {
5267
5742
  fail(`${projectsRoot} does not exist`);
5268
5743
  process.exit(1);
5269
5744
  }
@@ -5277,8 +5752,8 @@ function resumeCmd(sessionId) {
5277
5752
  fail(`no cwd field found in ${jsonlPath}`);
5278
5753
  process.exit(1);
5279
5754
  }
5280
- const mapPath = join43(REPO_HOME, "path-map.json");
5281
- if (!existsSync37(mapPath)) {
5755
+ const mapPath = join46(REPO_HOME, "path-map.json");
5756
+ if (!existsSync40(mapPath)) {
5282
5757
  fail("path-map.json missing");
5283
5758
  process.exit(1);
5284
5759
  }
@@ -5300,14 +5775,14 @@ function resumeCmd(sessionId) {
5300
5775
  console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
5301
5776
  }
5302
5777
  function findTranscriptPath(projectsRoot, sessionId) {
5303
- for (const dir of readdirSync11(projectsRoot)) {
5304
- const candidate = join43(projectsRoot, dir, `${sessionId}.jsonl`);
5305
- if (existsSync37(candidate)) return candidate;
5778
+ for (const dir of readdirSync12(projectsRoot)) {
5779
+ const candidate = join46(projectsRoot, dir, `${sessionId}.jsonl`);
5780
+ if (existsSync40(candidate)) return candidate;
5306
5781
  }
5307
5782
  return null;
5308
5783
  }
5309
5784
  function extractRecordedCwd(jsonlPath) {
5310
- for (const line of readFileSync11(jsonlPath, "utf8").split("\n")) {
5785
+ for (const line of readFileSync12(jsonlPath, "utf8").split("\n")) {
5311
5786
  if (!line.trim()) continue;
5312
5787
  try {
5313
5788
  const obj = JSON.parse(line);
@@ -5436,6 +5911,15 @@ try {
5436
5911
  cmdAdopt(name, { dryRun: sub === "--dry-run" });
5437
5912
  break;
5438
5913
  }
5914
+ case "eject": {
5915
+ const ejectArgs = parseEjectArgs(process.argv);
5916
+ if (ejectArgs === null) {
5917
+ console.error("usage: nomad eject [--dry-run]");
5918
+ process.exit(1);
5919
+ }
5920
+ cmdEject({ dryRun: ejectArgs.dryRun });
5921
+ break;
5922
+ }
5439
5923
  case "doctor":
5440
5924
  if (process.argv[3] === void 0) {
5441
5925
  cmdDoctor();