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/CHANGELOG.md +31 -0
- package/README.md +26 -0
- package/dist/nomad.mjs +843 -359
- package/package.json +1 -1
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
|
|
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
|
|
566
|
+
import { join as join12 } from "node:path";
|
|
567
567
|
import { fileURLToPath } from "node:url";
|
|
568
568
|
function resolveTomlPath() {
|
|
569
|
-
const repoToml =
|
|
570
|
-
if (
|
|
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
|
|
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(
|
|
580
|
-
const configPath =
|
|
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 =
|
|
586
|
-
const repoToml =
|
|
585
|
+
const overlayPath = join12(REPO_HOME, ".gitleaks.overlay.toml");
|
|
586
|
+
const repoToml = join12(REPO_HOME, ".gitleaks.toml");
|
|
587
587
|
const bundled = resolveTomlPath();
|
|
588
|
-
if (!
|
|
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
|
|
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
|
|
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 =
|
|
669
|
+
entries = readdirSync4(current, { withFileTypes: true });
|
|
670
670
|
} catch {
|
|
671
671
|
return;
|
|
672
672
|
}
|
|
673
673
|
for (const e of entries) {
|
|
674
|
-
const p =
|
|
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)
|
|
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
|
|
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
|
|
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 =
|
|
738
|
+
const cacheDir = join17(homedir3(), ".cache", "claude-nomad");
|
|
739
739
|
mkdirSync2(cacheDir, { recursive: true });
|
|
740
|
-
const reportPath =
|
|
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)
|
|
768
|
-
|
|
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 =
|
|
772
|
+
const cacheDir = join17(homedir3(), ".cache", "claude-nomad");
|
|
773
773
|
mkdirSync2(cacheDir, { recursive: true });
|
|
774
|
-
const reportPath =
|
|
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)
|
|
800
|
-
|
|
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
|
|
1224
|
-
import { join as
|
|
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
|
|
1230
|
-
import { join as
|
|
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(
|
|
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
|
-
|
|
1263
|
-
|
|
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
|
|
1291
|
-
import { join as
|
|
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 =
|
|
1294
|
-
const mapPath =
|
|
1295
|
-
const hostPath =
|
|
1296
|
-
const hasBase =
|
|
1297
|
-
const hasMap =
|
|
1298
|
-
const hasHost =
|
|
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 =
|
|
1314
|
-
const mapPath =
|
|
1315
|
-
const hostPath =
|
|
1316
|
-
if (!
|
|
1317
|
-
if (!
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
`${
|
|
1523
|
+
`${existsSync7(REPO_HOME) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(REPO_HOME)}`
|
|
1339
1524
|
);
|
|
1340
1525
|
addItem(
|
|
1341
1526
|
section2,
|
|
1342
|
-
`${
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
1422
|
-
import { join as
|
|
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 =
|
|
1425
|
-
if (!
|
|
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 =
|
|
1434
|
-
if (!
|
|
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 =
|
|
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 (
|
|
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 =
|
|
1465
|
-
if (
|
|
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
|
|
1482
|
-
import { join as
|
|
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
|
-
|
|
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 =
|
|
1518
|
-
if (!
|
|
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(
|
|
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
|
|
1566
|
-
import { join as
|
|
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
|
|
1570
|
-
import { join as
|
|
1776
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
1777
|
+
import { join as join11 } from "node:path";
|
|
1571
1778
|
function detectWedge(repo) {
|
|
1572
|
-
const g =
|
|
1573
|
-
if (
|
|
1574
|
-
if (
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
1598
|
-
if (
|
|
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
|
|
1655
|
-
import { join as
|
|
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
|
|
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 =
|
|
1672
|
-
const st =
|
|
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(
|
|
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 (!
|
|
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
|
|
1701
|
-
import { join as
|
|
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 =
|
|
1739
|
-
if (!
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1873
|
-
import { join as
|
|
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
|
-
|
|
1876
|
-
|
|
2084
|
+
rmSync6(dst, { recursive: true, force: true });
|
|
2085
|
+
cpSync4(src, dst, { recursive: true, force: true });
|
|
1877
2086
|
}
|
|
1878
2087
|
function copyDirJsonlOnly(src, dst) {
|
|
1879
|
-
|
|
1880
|
-
|
|
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(
|
|
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 =
|
|
1904
|
-
const repoProjects =
|
|
1905
|
-
if (!
|
|
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 =
|
|
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 =
|
|
1921
|
-
if (!
|
|
1922
|
-
const dst =
|
|
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 =
|
|
1968
|
-
if (!
|
|
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 =
|
|
1974
|
-
const repoProjects =
|
|
2182
|
+
const localProjects = join19(CLAUDE_HOME, "projects");
|
|
2183
|
+
const repoProjects = join19(REPO_HOME, "shared", "projects");
|
|
1975
2184
|
const reverse = buildReverseMap(map);
|
|
1976
|
-
if (!
|
|
2185
|
+
if (!existsSync15(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
1977
2186
|
if (!dryRun) mkdirSync3(repoProjects, { recursive: true });
|
|
1978
|
-
for (const dir of
|
|
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 =
|
|
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(
|
|
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 =
|
|
2003
|
-
if (!
|
|
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 =
|
|
2021
|
-
if (!
|
|
2022
|
-
for (const dir of
|
|
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(
|
|
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 =
|
|
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 =
|
|
2060
|
-
const tmpRoot =
|
|
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
|
-
|
|
2075
|
-
|
|
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
|
|
2082
|
-
import { dirname as dirname2, extname, join as
|
|
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 =
|
|
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 =
|
|
2105
|
-
if (
|
|
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
|
|
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 =
|
|
2148
|
-
if (!
|
|
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 =
|
|
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
|
|
2176
|
-
import { join as
|
|
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 (
|
|
2215
|
-
addItem(
|
|
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 =
|
|
2225
|
-
if (!
|
|
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
|
|
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
|
|
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(
|
|
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)
|
|
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(
|
|
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
|
|
2356
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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("
|
|
2995
|
+
const host = section("Environment");
|
|
2547
2996
|
reportHostAndPaths(host);
|
|
2548
2997
|
reportRepoState(host);
|
|
2549
2998
|
const links = section("Shared links");
|
|
2550
|
-
const mapPath =
|
|
2551
|
-
const rawMap =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2602
|
-
import { join as
|
|
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
|
|
2646
|
-
import { join as
|
|
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 =
|
|
2663
|
-
if (!
|
|
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 =
|
|
2671
|
-
if (
|
|
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
|
|
2686
|
-
import { dirname as
|
|
2687
|
-
var LOCK_PATH =
|
|
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(
|
|
3142
|
+
mkdirSync5(dirname4(LOCK_PATH), { recursive: true });
|
|
2690
3143
|
try {
|
|
2691
|
-
const fd =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3222
|
+
const fd = openSync3(LOCK_PATH, "wx");
|
|
2770
3223
|
try {
|
|
2771
3224
|
writeFileSync3(fd, String(process.pid));
|
|
2772
3225
|
} catch {
|
|
2773
3226
|
try {
|
|
2774
|
-
|
|
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 (!
|
|
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 =
|
|
2802
|
-
if (!
|
|
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
|
|
2824
|
-
const candidate =
|
|
2825
|
-
if (
|
|
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 =
|
|
2829
|
-
if (
|
|
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
|
|
2866
|
-
import { dirname as
|
|
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
|
|
2870
|
-
import { join as
|
|
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 (!
|
|
2874
|
-
const st =
|
|
3326
|
+
if (!existsSync25(dir)) return;
|
|
3327
|
+
const st = lstatSync7(dir);
|
|
2875
3328
|
if (!st.isDirectory()) return;
|
|
2876
|
-
for (const entry of
|
|
2877
|
-
const abs =
|
|
2878
|
-
const lst =
|
|
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(
|
|
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 =
|
|
2928
|
-
if (!
|
|
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 =
|
|
2934
|
-
if (
|
|
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 (!
|
|
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 || !
|
|
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 =
|
|
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
|
|
3016
|
-
import { join as
|
|
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
|
|
3105
|
-
import { join as
|
|
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
|
|
3145
|
-
import { join as
|
|
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 =
|
|
3169
|
-
const repoExtras =
|
|
3170
|
-
if (!
|
|
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
|
|
3193
|
-
if (!whitelist.includes(
|
|
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:
|
|
3650
|
+
yield { logical, localRoot, dirname: dirname7 };
|
|
3198
3651
|
}
|
|
3199
3652
|
}
|
|
3200
3653
|
}
|
|
3201
3654
|
function copyExtras(src, dst) {
|
|
3202
|
-
|
|
3203
|
-
|
|
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
|
|
3213
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
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:
|
|
3243
|
-
src:
|
|
3244
|
-
dst:
|
|
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 =
|
|
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:
|
|
3262
|
-
src:
|
|
3263
|
-
dst:
|
|
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 =
|
|
3278
|
-
for (const { logical, localRoot, dirname:
|
|
3279
|
-
const local =
|
|
3280
|
-
const repo =
|
|
3281
|
-
if (!
|
|
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 =
|
|
3737
|
+
const projectBackupRoot = join34(backupRoot, encodePath(localRoot));
|
|
3285
3738
|
warn(
|
|
3286
|
-
`local ${
|
|
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
|
|
3298
|
-
import { join as
|
|
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 =
|
|
3318
|
-
const target =
|
|
3319
|
-
if (!
|
|
3320
|
-
if (
|
|
3321
|
-
if (!
|
|
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
|
-
|
|
3780
|
+
rmSync9(linkPath, { recursive: true, force: true });
|
|
3328
3781
|
}
|
|
3329
3782
|
for (const name of linkNames) {
|
|
3330
|
-
const target =
|
|
3331
|
-
if (!
|
|
3783
|
+
const target = join35(REPO_HOME, "shared", name);
|
|
3784
|
+
if (!existsSync30(target)) continue;
|
|
3332
3785
|
if (dryRun) {
|
|
3333
|
-
emitCreate(opts.onPreview,
|
|
3786
|
+
emitCreate(opts.onPreview, join35(CLAUDE_HOME, name), target);
|
|
3334
3787
|
continue;
|
|
3335
3788
|
}
|
|
3336
|
-
ensureSymlink(
|
|
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 =
|
|
3342
|
-
const hostPath =
|
|
3343
|
-
if (!
|
|
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 =
|
|
3800
|
+
const hasOverrides = existsSync30(hostPath);
|
|
3348
3801
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
3349
3802
|
const merged = deepMerge(base, overrides);
|
|
3350
|
-
const settingsPath =
|
|
3351
|
-
if (!hasOverrides &&
|
|
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
|
|
3378
|
-
import { join as
|
|
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 (!
|
|
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 &&
|
|
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 &&
|
|
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
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
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
|
|
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
|
|
3746
|
-
import { join as
|
|
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
|
|
3753
|
-
import { dirname as
|
|
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(
|
|
3785
|
-
return
|
|
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 =
|
|
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
|
-
|
|
3842
|
-
if (
|
|
3843
|
-
|
|
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
|
|
3851
|
-
import { join as
|
|
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 =
|
|
3857
|
-
const dir =
|
|
3858
|
-
|
|
3859
|
-
|
|
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 =
|
|
4428
|
+
const ignPath = join39(repoHome, ".gitleaksignore");
|
|
3976
4429
|
let before;
|
|
3977
4430
|
try {
|
|
3978
|
-
before =
|
|
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)
|
|
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 ??
|
|
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 (!
|
|
4280
|
-
if (!
|
|
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 =
|
|
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 =
|
|
4301
|
-
const map =
|
|
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
|
|
4324
|
-
import { join as
|
|
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
|
|
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
|
|
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 =
|
|
4422
|
-
if (!
|
|
4874
|
+
const localProjects = join41(CLAUDE_HOME, "projects");
|
|
4875
|
+
if (!existsSync35(localProjects)) return 0;
|
|
4423
4876
|
let staged = 0;
|
|
4424
|
-
for (const dir of
|
|
4877
|
+
for (const dir of readdirSync11(localProjects)) {
|
|
4425
4878
|
const logical = reverse.get(dir);
|
|
4426
4879
|
if (!logical) continue;
|
|
4427
|
-
copyDirJsonlOnly(
|
|
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
|
|
4442
|
-
if (!whitelist.includes(
|
|
4443
|
-
const src =
|
|
4444
|
-
if (!
|
|
4445
|
-
const dst =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4464
|
-
if (
|
|
4465
|
-
copyFileSync(ignoreFile,
|
|
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
|
-
|
|
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(
|
|
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 (!
|
|
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 =
|
|
4558
|
-
if (!
|
|
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
|
|
4599
|
-
import { join as
|
|
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 (!
|
|
5058
|
+
if (!existsSync37(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
4606
5059
|
const ts = freshBackupTs(BACKUP_BASE);
|
|
4607
|
-
const mapPath =
|
|
4608
|
-
const map =
|
|
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
|
|
4623
|
-
import { join as
|
|
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
|
|
4705
|
-
import { join as
|
|
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 =
|
|
4709
|
-
if (!
|
|
4710
|
-
const dst =
|
|
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 =
|
|
4713
|
-
if (
|
|
4714
|
-
|
|
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 =
|
|
4721
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
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 (
|
|
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(
|
|
4762
|
-
mkdirSync10(
|
|
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(
|
|
5217
|
+
mkdirSync10(join45(REPO_HOME, "shared", name), { recursive: true });
|
|
4765
5218
|
}
|
|
4766
|
-
const userClaudeMd =
|
|
4767
|
-
if (!snapshot || !
|
|
4768
|
-
writeFileSync6(
|
|
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(
|
|
5225
|
+
writeFileSync6(join45(REPO_HOME, "shared", name, ".gitkeep"), "");
|
|
4773
5226
|
log(`created shared/${name}/.gitkeep`);
|
|
4774
5227
|
}
|
|
4775
|
-
writeFileSync6(
|
|
5228
|
+
writeFileSync6(join45(REPO_HOME, "hosts", ".gitkeep"), "");
|
|
4776
5229
|
log("created hosts/.gitkeep");
|
|
4777
|
-
writeJsonAtomic(
|
|
5230
|
+
writeJsonAtomic(join45(REPO_HOME, "shared", "settings.base.json"), {});
|
|
4778
5231
|
log("created shared/settings.base.json");
|
|
4779
|
-
writeJsonAtomic(
|
|
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.
|
|
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
|
|
5259
|
-
import { join as
|
|
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 =
|
|
5266
|
-
if (!
|
|
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 =
|
|
5281
|
-
if (!
|
|
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
|
|
5304
|
-
const candidate =
|
|
5305
|
-
if (
|
|
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
|
|
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();
|