claude-nomad 0.50.2 → 0.51.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/.gitleaks.toml +13 -1
- package/CHANGELOG.md +42 -0
- package/README.md +16 -3
- package/dist/nomad.mjs +1147 -862
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -451,16 +451,41 @@ function readJson(path) {
|
|
|
451
451
|
return data;
|
|
452
452
|
}
|
|
453
453
|
function readPathMap(mapPath) {
|
|
454
|
+
let parsed;
|
|
454
455
|
try {
|
|
455
|
-
|
|
456
|
+
parsed = readJson(mapPath);
|
|
456
457
|
} catch (err) {
|
|
457
458
|
const verb = err instanceof SyntaxError ? "parse" : "read";
|
|
458
459
|
throw new NomadFatal(`could not ${verb} path-map.json: ${err.message}`);
|
|
459
460
|
}
|
|
461
|
+
const shapeError = validatePathMapShape(parsed);
|
|
462
|
+
if (shapeError !== null) throw new NomadFatal(shapeError);
|
|
463
|
+
return parsed;
|
|
464
|
+
}
|
|
465
|
+
function validatePathMapShape(raw) {
|
|
466
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
467
|
+
return "path-map.json invalid schema: top-level value must be an object";
|
|
468
|
+
}
|
|
469
|
+
const projects = raw.projects;
|
|
470
|
+
if (projects === null || typeof projects !== "object" || Array.isArray(projects)) {
|
|
471
|
+
return 'path-map.json invalid schema: "projects" must be an object';
|
|
472
|
+
}
|
|
473
|
+
for (const [name, hosts] of Object.entries(projects)) {
|
|
474
|
+
if (hosts === null || typeof hosts !== "object" || Array.isArray(hosts)) {
|
|
475
|
+
return `path-map.json invalid schema: project "${name}" hosts must be an object`;
|
|
476
|
+
}
|
|
477
|
+
for (const [host, value] of Object.entries(hosts)) {
|
|
478
|
+
if (typeof value !== "string") {
|
|
479
|
+
return `path-map.json invalid schema: project "${name}" host "${host}" path must be a string`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return null;
|
|
460
484
|
}
|
|
461
485
|
function deepMerge(target, source) {
|
|
462
486
|
const out = { ...target };
|
|
463
487
|
for (const [key, value] of Object.entries(source)) {
|
|
488
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
464
489
|
const existing = out[key];
|
|
465
490
|
const bothObjects = value !== null && typeof value === "object" && !Array.isArray(value) && existing !== null && typeof existing === "object" && !Array.isArray(existing);
|
|
466
491
|
out[key] = bothObjects ? deepMerge(existing, value) : value;
|
|
@@ -502,7 +527,7 @@ import {
|
|
|
502
527
|
symlinkSync,
|
|
503
528
|
writeFileSync
|
|
504
529
|
} from "node:fs";
|
|
505
|
-
import { dirname, join as join2, relative } from "node:path";
|
|
530
|
+
import { dirname, join as join2, relative, sep } from "node:path";
|
|
506
531
|
function writeJsonAtomic(path, data) {
|
|
507
532
|
const mode = existsSync(path) ? statSync(path).mode & 511 : 384;
|
|
508
533
|
const tmp = `${path}.tmp.${process.pid}`;
|
|
@@ -542,33 +567,22 @@ function ensureSymlink(linkPath, target) {
|
|
|
542
567
|
symlinkSync(target, linkPath);
|
|
543
568
|
log(`linked ${linkPath} -> ${target}`);
|
|
544
569
|
}
|
|
545
|
-
function
|
|
570
|
+
function backupUnder(absPath, anchor, destRoot) {
|
|
546
571
|
if (!existsSync(absPath)) return;
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const backupRoot = join2(backupBase(), ts);
|
|
551
|
-
const dst = join2(backupRoot, rel);
|
|
572
|
+
const rel = relative(anchor, absPath);
|
|
573
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`)) return;
|
|
574
|
+
const dst = join2(destRoot, rel);
|
|
552
575
|
mkdirSync(dirname(dst), { recursive: true });
|
|
553
576
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
554
577
|
}
|
|
578
|
+
function backupBeforeWrite(absPath, ts) {
|
|
579
|
+
backupUnder(absPath, claudeHome(), join2(backupBase(), ts));
|
|
580
|
+
}
|
|
555
581
|
function backupRepoWrite(absPath, ts, repoHome2) {
|
|
556
|
-
|
|
557
|
-
const rel = relative(repoHome2, absPath);
|
|
558
|
-
if (rel.startsWith("..") || rel === "") return;
|
|
559
|
-
const backupRoot = join2(backupBase(), ts, "repo");
|
|
560
|
-
const dst = join2(backupRoot, rel);
|
|
561
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
562
|
-
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
582
|
+
backupUnder(absPath, repoHome2, join2(backupBase(), ts, "repo"));
|
|
563
583
|
}
|
|
564
584
|
function backupExtrasWrite(absPath, ts, projectRoot) {
|
|
565
|
-
|
|
566
|
-
const rel = relative(projectRoot, absPath);
|
|
567
|
-
if (rel.startsWith("..") || rel === "") return;
|
|
568
|
-
const backupRoot = join2(backupBase(), ts, "extras");
|
|
569
|
-
const dst = join2(backupRoot, encodePath(projectRoot), rel);
|
|
570
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
571
|
-
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
585
|
+
backupUnder(absPath, projectRoot, join2(backupBase(), ts, "extras", encodePath(projectRoot)));
|
|
572
586
|
}
|
|
573
587
|
var init_utils_fs = __esm({
|
|
574
588
|
"src/utils.fs.ts"() {
|
|
@@ -581,12 +595,12 @@ var init_utils_fs = __esm({
|
|
|
581
595
|
|
|
582
596
|
// src/commands.pull.wedge.ts
|
|
583
597
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
584
|
-
import { existsSync as
|
|
585
|
-
import { join as
|
|
598
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
599
|
+
import { join as join14 } from "node:path";
|
|
586
600
|
function detectWedge(repo) {
|
|
587
|
-
const g =
|
|
588
|
-
if (
|
|
589
|
-
if (
|
|
601
|
+
const g = join14(repo, ".git");
|
|
602
|
+
if (existsSync12(join14(g, "rebase-merge")) || existsSync12(join14(g, "rebase-apply"))) return "rebase";
|
|
603
|
+
if (existsSync12(join14(g, "MERGE_HEAD"))) return "merge";
|
|
590
604
|
return null;
|
|
591
605
|
}
|
|
592
606
|
function unmergedIndexPresent(repo) {
|
|
@@ -643,32 +657,32 @@ var init_commands_pull_wedge = __esm({
|
|
|
643
657
|
});
|
|
644
658
|
|
|
645
659
|
// src/push-gitleaks.config.ts
|
|
646
|
-
import { existsSync as
|
|
660
|
+
import { existsSync as existsSync13, mkdtempSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
647
661
|
import { tmpdir } from "node:os";
|
|
648
|
-
import { join as
|
|
662
|
+
import { join as join15 } from "node:path";
|
|
649
663
|
import { fileURLToPath } from "node:url";
|
|
650
664
|
function resolveTomlPath(repo = repoHome()) {
|
|
651
|
-
const repoToml =
|
|
652
|
-
if (
|
|
665
|
+
const repoToml = join15(repo, ".gitleaks.toml");
|
|
666
|
+
if (existsSync13(repoToml)) return repoToml;
|
|
653
667
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
654
|
-
return
|
|
668
|
+
return existsSync13(bundled) ? bundled : null;
|
|
655
669
|
}
|
|
656
670
|
function buildOverlayTempConfig(overlayBody, bundled) {
|
|
657
671
|
const tempBody = `[extend]
|
|
658
672
|
path = ${JSON.stringify(bundled)}
|
|
659
673
|
|
|
660
674
|
${overlayBody}`;
|
|
661
|
-
const tempPath = mkdtempSync(
|
|
662
|
-
const configPath =
|
|
663
|
-
|
|
675
|
+
const tempPath = mkdtempSync(join15(tmpdir(), "nomad-gitleaks-cfg-"));
|
|
676
|
+
const configPath = join15(tempPath, "config.toml");
|
|
677
|
+
writeFileSync3(configPath, tempBody, { mode: 384, flag: "wx" });
|
|
664
678
|
return { configPath, tempPath };
|
|
665
679
|
}
|
|
666
680
|
function resolveTomlConfig() {
|
|
667
681
|
const repo = repoHome();
|
|
668
|
-
const overlayPath =
|
|
669
|
-
const repoToml =
|
|
682
|
+
const overlayPath = join15(repo, ".gitleaks.overlay.toml");
|
|
683
|
+
const repoToml = join15(repo, ".gitleaks.toml");
|
|
670
684
|
const bundled = resolveTomlPath(repo);
|
|
671
|
-
if (!
|
|
685
|
+
if (!existsSync13(overlayPath)) {
|
|
672
686
|
return { path: bundled, tempPath: null };
|
|
673
687
|
}
|
|
674
688
|
if (bundled === repoToml) {
|
|
@@ -681,7 +695,7 @@ function resolveTomlConfig() {
|
|
|
681
695
|
return { path: null, tempPath: null };
|
|
682
696
|
}
|
|
683
697
|
try {
|
|
684
|
-
const overlayBody =
|
|
698
|
+
const overlayBody = readFileSync4(overlayPath, "utf8");
|
|
685
699
|
if (OVERLAY_EXTEND_RE.test(overlayBody)) {
|
|
686
700
|
throw new NomadFatal(
|
|
687
701
|
".gitleaks.overlay.toml must not contain an [extend] block; it is generated automatically. Remove the [extend] section and retry."
|
|
@@ -709,9 +723,9 @@ var init_push_gitleaks_config = __esm({
|
|
|
709
723
|
|
|
710
724
|
// src/push-checks.ts
|
|
711
725
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
712
|
-
import { readdirSync as readdirSync4, rmSync as
|
|
726
|
+
import { readdirSync as readdirSync4, rmSync as rmSync5 } from "node:fs";
|
|
713
727
|
import { homedir as homedir2, platform } from "node:os";
|
|
714
|
-
import { join as
|
|
728
|
+
import { join as join16 } from "node:path";
|
|
715
729
|
function gitleaksInstallHint() {
|
|
716
730
|
const head = "gitleaks not on PATH (required for nomad push). Install:";
|
|
717
731
|
const plat = platform();
|
|
@@ -754,7 +768,7 @@ function findGitlinks(dir) {
|
|
|
754
768
|
return;
|
|
755
769
|
}
|
|
756
770
|
for (const e of entries) {
|
|
757
|
-
const p =
|
|
771
|
+
const p = join16(current, e.name);
|
|
758
772
|
if (e.name === ".git") {
|
|
759
773
|
hits.push(p);
|
|
760
774
|
continue;
|
|
@@ -776,7 +790,7 @@ function probeGitleaks() {
|
|
|
776
790
|
if (e.code === "ENOENT") throw new NomadFatal(gitleaksInstallHint());
|
|
777
791
|
throw new NomadFatal(`gitleaks --version failed: ${e.message}`);
|
|
778
792
|
} finally {
|
|
779
|
-
if (tempPath !== null)
|
|
793
|
+
if (tempPath !== null) rmSync5(tempPath, { recursive: true, force: true });
|
|
780
794
|
}
|
|
781
795
|
}
|
|
782
796
|
function wedgePreflight(wedge) {
|
|
@@ -813,12 +827,12 @@ var init_push_checks = __esm({
|
|
|
813
827
|
|
|
814
828
|
// src/push-gitleaks.scan.ts
|
|
815
829
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
816
|
-
import { mkdirSync as
|
|
830
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync6 } from "node:fs";
|
|
817
831
|
import { homedir as homedir3 } from "node:os";
|
|
818
|
-
import { join as
|
|
832
|
+
import { join as join20 } from "node:path";
|
|
819
833
|
function readGitleaksReport(reportPath) {
|
|
820
834
|
try {
|
|
821
|
-
const raw =
|
|
835
|
+
const raw = readFileSync5(reportPath, "utf8");
|
|
822
836
|
const parsed = JSON.parse(raw);
|
|
823
837
|
if (!Array.isArray(parsed)) return null;
|
|
824
838
|
return parsed;
|
|
@@ -827,9 +841,9 @@ function readGitleaksReport(reportPath) {
|
|
|
827
841
|
}
|
|
828
842
|
}
|
|
829
843
|
function scanStagedTree(repoDir, forwardStreams = false) {
|
|
830
|
-
const cacheDir =
|
|
831
|
-
|
|
832
|
-
const reportPath =
|
|
844
|
+
const cacheDir = join20(homedir3(), ".cache", "claude-nomad");
|
|
845
|
+
mkdirSync3(cacheDir, { recursive: true });
|
|
846
|
+
const reportPath = join20(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
|
|
833
847
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
834
848
|
const args = [
|
|
835
849
|
"protect",
|
|
@@ -856,14 +870,14 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
856
870
|
}
|
|
857
871
|
return report;
|
|
858
872
|
} finally {
|
|
859
|
-
if (tempPath !== null)
|
|
860
|
-
|
|
873
|
+
if (tempPath !== null) rmSync6(tempPath, { recursive: true, force: true });
|
|
874
|
+
rmSync6(reportPath, { force: true });
|
|
861
875
|
}
|
|
862
876
|
}
|
|
863
877
|
function scanFile(filePath, forwardStreams = false) {
|
|
864
|
-
const cacheDir =
|
|
865
|
-
|
|
866
|
-
const reportPath =
|
|
878
|
+
const cacheDir = join20(homedir3(), ".cache", "claude-nomad");
|
|
879
|
+
mkdirSync3(cacheDir, { recursive: true });
|
|
880
|
+
const reportPath = join20(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
|
|
867
881
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
868
882
|
const args = [
|
|
869
883
|
"detect",
|
|
@@ -888,8 +902,8 @@ function scanFile(filePath, forwardStreams = false) {
|
|
|
888
902
|
}
|
|
889
903
|
return report;
|
|
890
904
|
} finally {
|
|
891
|
-
if (tempPath !== null)
|
|
892
|
-
|
|
905
|
+
if (tempPath !== null) rmSync6(tempPath, { recursive: true, force: true });
|
|
906
|
+
rmSync6(reportPath, { force: true });
|
|
893
907
|
}
|
|
894
908
|
}
|
|
895
909
|
var init_push_gitleaks_scan = __esm({
|
|
@@ -1250,11 +1264,406 @@ function cmdAllow(fingerprints) {
|
|
|
1250
1264
|
log(`allowed ${fingerprints.length} fingerprint(s)`);
|
|
1251
1265
|
}
|
|
1252
1266
|
|
|
1267
|
+
// src/commands.capture-settings.ts
|
|
1268
|
+
init_config();
|
|
1269
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1270
|
+
import { join as join7 } from "node:path";
|
|
1271
|
+
import { createInterface } from "node:readline/promises";
|
|
1272
|
+
|
|
1273
|
+
// src/commands.capture-settings.core.ts
|
|
1274
|
+
function arraysEqual(a, b) {
|
|
1275
|
+
if (a.length !== b.length) return false;
|
|
1276
|
+
for (let i = 0; i < a.length; i++) {
|
|
1277
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
1278
|
+
}
|
|
1279
|
+
return true;
|
|
1280
|
+
}
|
|
1281
|
+
function objectsEqual(a, b) {
|
|
1282
|
+
const aKeys = Object.keys(a);
|
|
1283
|
+
const bKeys = Object.keys(b);
|
|
1284
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
1285
|
+
for (const k of aKeys) {
|
|
1286
|
+
if (!Object.hasOwn(b, k)) return false;
|
|
1287
|
+
if (!deepEqual(a[k], b[k])) return false;
|
|
1288
|
+
}
|
|
1289
|
+
return true;
|
|
1290
|
+
}
|
|
1291
|
+
function deepEqual(a, b) {
|
|
1292
|
+
if (a === b) return true;
|
|
1293
|
+
if (a === null || b === null) return false;
|
|
1294
|
+
if (Array.isArray(a) && Array.isArray(b)) return arraysEqual(a, b);
|
|
1295
|
+
if (Array.isArray(a) || Array.isArray(b)) return false;
|
|
1296
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
1297
|
+
return objectsEqual(a, b);
|
|
1298
|
+
}
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
function classifySettingsDrift(merged, settings) {
|
|
1302
|
+
const behind = [];
|
|
1303
|
+
const ahead = [];
|
|
1304
|
+
const changed = [];
|
|
1305
|
+
const settingsKeys = new Set(Object.keys(settings));
|
|
1306
|
+
for (const key of Object.keys(merged)) {
|
|
1307
|
+
if (!settingsKeys.has(key)) {
|
|
1308
|
+
behind.push(key);
|
|
1309
|
+
} else if (!deepEqual(merged[key], settings[key])) {
|
|
1310
|
+
changed.push(key);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
const mergedKeys = new Set(Object.keys(merged));
|
|
1314
|
+
for (const key of Object.keys(settings)) {
|
|
1315
|
+
if (!mergedKeys.has(key)) ahead.push(key);
|
|
1316
|
+
}
|
|
1317
|
+
const collator = (a, b) => a.localeCompare(b, "en");
|
|
1318
|
+
return {
|
|
1319
|
+
behind: behind.toSorted(collator),
|
|
1320
|
+
ahead: ahead.toSorted(collator),
|
|
1321
|
+
changed: changed.toSorted(collator)
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
var CAPTURE_EXCLUDED_KEYS = /* @__PURE__ */ new Set([
|
|
1325
|
+
"apiKeyHelper",
|
|
1326
|
+
"awsAuthRefresh",
|
|
1327
|
+
"awsCredentialExport",
|
|
1328
|
+
"otelHeadersHelper",
|
|
1329
|
+
"env"
|
|
1330
|
+
]);
|
|
1331
|
+
function partitionByCaptureExclusion(keys) {
|
|
1332
|
+
const promotable = [];
|
|
1333
|
+
const excluded = [];
|
|
1334
|
+
for (const key of keys) {
|
|
1335
|
+
if (CAPTURE_EXCLUDED_KEYS.has(key)) excluded.push(key);
|
|
1336
|
+
else promotable.push(key);
|
|
1337
|
+
}
|
|
1338
|
+
return { promotable, excluded };
|
|
1339
|
+
}
|
|
1340
|
+
var BIN_NODE_RE = /^(?:[A-Za-z]:)?[\\/](?:.*[\\/])?bin[\\/]node$/;
|
|
1341
|
+
function normalizeNodePathsDeep(value) {
|
|
1342
|
+
if (typeof value === "string") {
|
|
1343
|
+
return BIN_NODE_RE.test(value) ? "node" : value;
|
|
1344
|
+
}
|
|
1345
|
+
if (Array.isArray(value)) {
|
|
1346
|
+
return value.map(normalizeNodePathsDeep);
|
|
1347
|
+
}
|
|
1348
|
+
if (value !== null && typeof value === "object") {
|
|
1349
|
+
const out = {};
|
|
1350
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1351
|
+
out[k] = normalizeNodePathsDeep(v);
|
|
1352
|
+
}
|
|
1353
|
+
return out;
|
|
1354
|
+
}
|
|
1355
|
+
return value;
|
|
1356
|
+
}
|
|
1357
|
+
function buildCaptureSubset(merged, settings, opts) {
|
|
1358
|
+
const { ahead } = classifySettingsDrift(merged, settings);
|
|
1359
|
+
const out = {};
|
|
1360
|
+
for (const key of ahead) {
|
|
1361
|
+
if (CAPTURE_EXCLUDED_KEYS.has(key)) continue;
|
|
1362
|
+
const raw = settings[key];
|
|
1363
|
+
out[key] = opts.normalizeNodePath ? normalizeNodePathsDeep(raw) : raw;
|
|
1364
|
+
}
|
|
1365
|
+
return out;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// src/links.ts
|
|
1369
|
+
init_config();
|
|
1370
|
+
import { existsSync as existsSync4, lstatSync as lstatSync3, rmSync as rmSync2 } from "node:fs";
|
|
1371
|
+
import { join as join5 } from "node:path";
|
|
1372
|
+
init_utils();
|
|
1373
|
+
init_utils_fs();
|
|
1374
|
+
init_utils_json();
|
|
1375
|
+
function emitAutoMove(onPreview, linkPath, ts, name) {
|
|
1376
|
+
if (onPreview) {
|
|
1377
|
+
onPreview({ kind: "auto-move", from: linkPath, to: `backup/${ts}/${name}` });
|
|
1378
|
+
} else {
|
|
1379
|
+
log(`would auto-move non-symlink: ${linkPath} -> backup/${ts}/${name}`);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
function emitCreate(onPreview, from, to) {
|
|
1383
|
+
if (onPreview) {
|
|
1384
|
+
onPreview({ kind: "create", from, to });
|
|
1385
|
+
} else {
|
|
1386
|
+
log(`would create symlink: ${from} -> ${to}`);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
function isAlreadySymlink(linkPath) {
|
|
1390
|
+
return existsSync4(linkPath) && lstatSync3(linkPath).isSymbolicLink();
|
|
1391
|
+
}
|
|
1392
|
+
function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
1393
|
+
for (const name of linkNames) {
|
|
1394
|
+
const linkPath = join5(claude, name);
|
|
1395
|
+
const target = join5(repo, "shared", name);
|
|
1396
|
+
if (!existsSync4(linkPath)) continue;
|
|
1397
|
+
if (lstatSync3(linkPath).isSymbolicLink()) continue;
|
|
1398
|
+
if (!existsSync4(target)) continue;
|
|
1399
|
+
if (dryRun) {
|
|
1400
|
+
emitAutoMove(onPreview, linkPath, ts, name);
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
backupBeforeWrite(linkPath, ts);
|
|
1404
|
+
rmSync2(linkPath, { recursive: true, force: true });
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
function applySharedLinks(ts, map, opts = {}) {
|
|
1408
|
+
const dryRun = opts.dryRun === true;
|
|
1409
|
+
const claude = claudeHome();
|
|
1410
|
+
const repo = repoHome();
|
|
1411
|
+
const linkNames = allSharedLinks(map);
|
|
1412
|
+
runAutoMovePasses(linkNames, claude, repo, ts, dryRun, opts.onPreview);
|
|
1413
|
+
for (const name of linkNames) {
|
|
1414
|
+
const target = join5(repo, "shared", name);
|
|
1415
|
+
if (!existsSync4(target)) continue;
|
|
1416
|
+
const linkPath = join5(claude, name);
|
|
1417
|
+
if (isAlreadySymlink(linkPath)) continue;
|
|
1418
|
+
if (dryRun) {
|
|
1419
|
+
emitCreate(opts.onPreview, linkPath, target);
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
ensureSymlink(linkPath, target);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function regenerateSettings(ts, opts = {}) {
|
|
1426
|
+
const dryRun = opts.dryRun === true;
|
|
1427
|
+
const suppressDriftWarn = opts.suppressDriftWarn === true;
|
|
1428
|
+
const repo = repoHome();
|
|
1429
|
+
const claude = claudeHome();
|
|
1430
|
+
const basePath = join5(repo, "shared", "settings.base.json");
|
|
1431
|
+
const hostPath = join5(repo, "hosts", `${HOST}.json`);
|
|
1432
|
+
if (!existsSync4(basePath)) {
|
|
1433
|
+
die("repo not initialized; run 'nomad init' to scaffold");
|
|
1434
|
+
}
|
|
1435
|
+
const base = readJson(basePath);
|
|
1436
|
+
const hasOverrides = existsSync4(hostPath);
|
|
1437
|
+
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
1438
|
+
const merged = deepMerge(base, overrides);
|
|
1439
|
+
const settingsPath = join5(claude, "settings.json");
|
|
1440
|
+
if (!suppressDriftWarn && existsSync4(settingsPath)) {
|
|
1441
|
+
try {
|
|
1442
|
+
const existing = readJson(settingsPath);
|
|
1443
|
+
const drift = classifySettingsDrift(merged, existing);
|
|
1444
|
+
if (drift.behind.length > 0) {
|
|
1445
|
+
warn(
|
|
1446
|
+
`existing settings.json is missing merged keys ${JSON.stringify(drift.behind)}. Run 'nomad pull' to restore them.`
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
const { promotable } = partitionByCaptureExclusion(drift.ahead);
|
|
1450
|
+
if (promotable.length > 0) {
|
|
1451
|
+
warn(
|
|
1452
|
+
`existing settings.json has local-only keys ${JSON.stringify(promotable)}. Run 'nomad capture-settings' to promote them into the repo before they are overwritten.`
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
} catch {
|
|
1456
|
+
warn("existing settings.json is malformed; skipping drift-check and regenerating.");
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
const overrideLabel = hasOverrides ? `${HOST}.json` : "no host overrides";
|
|
1460
|
+
if (dryRun) {
|
|
1461
|
+
log(`would write settings.json (base + ${overrideLabel})`);
|
|
1462
|
+
return { label: overrideLabel };
|
|
1463
|
+
}
|
|
1464
|
+
backupBeforeWrite(settingsPath, ts);
|
|
1465
|
+
writeJsonAtomic(settingsPath, merged);
|
|
1466
|
+
return { label: overrideLabel };
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/commands.capture-settings.ts
|
|
1470
|
+
init_utils_fs();
|
|
1471
|
+
init_utils_json();
|
|
1472
|
+
|
|
1473
|
+
// src/utils.lockfile.ts
|
|
1474
|
+
init_config();
|
|
1475
|
+
init_utils();
|
|
1476
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1477
|
+
import { dirname as dirname2, join as join6 } from "node:path";
|
|
1478
|
+
function lockFilePath() {
|
|
1479
|
+
return join6(home(), ".cache", "claude-nomad", "nomad.lock");
|
|
1480
|
+
}
|
|
1481
|
+
function acquireLock(verb) {
|
|
1482
|
+
const lp = lockFilePath();
|
|
1483
|
+
mkdirSync2(dirname2(lp), { recursive: true });
|
|
1484
|
+
try {
|
|
1485
|
+
const fd = openSync2(lp, "wx");
|
|
1486
|
+
try {
|
|
1487
|
+
writeFileSync2(fd, String(process.pid));
|
|
1488
|
+
} catch (writeErr) {
|
|
1489
|
+
try {
|
|
1490
|
+
closeSync2(fd);
|
|
1491
|
+
} catch {
|
|
1492
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
unlinkSync(lp);
|
|
1495
|
+
} catch {
|
|
1496
|
+
}
|
|
1497
|
+
throw writeErr;
|
|
1498
|
+
}
|
|
1499
|
+
return { fd, path: lp };
|
|
1500
|
+
} catch (err) {
|
|
1501
|
+
const code = err.code;
|
|
1502
|
+
if (code !== "EEXIST") throw err;
|
|
1503
|
+
return checkStaleAndRetry(verb, lp);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
function releaseLock(handle) {
|
|
1507
|
+
if (handle === null) return;
|
|
1508
|
+
const lp = handle.path;
|
|
1509
|
+
try {
|
|
1510
|
+
closeSync2(handle.fd);
|
|
1511
|
+
} catch {
|
|
1512
|
+
}
|
|
1513
|
+
try {
|
|
1514
|
+
unlinkSync(lp);
|
|
1515
|
+
} catch (err) {
|
|
1516
|
+
if (err.code !== "ENOENT") throw err;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
function unlinkIfSamePid(expectedPidStr, lp) {
|
|
1520
|
+
let current;
|
|
1521
|
+
try {
|
|
1522
|
+
current = readFileSync3(lp, "utf8").trim();
|
|
1523
|
+
} catch {
|
|
1524
|
+
return false;
|
|
1525
|
+
}
|
|
1526
|
+
if (current !== expectedPidStr) return false;
|
|
1527
|
+
try {
|
|
1528
|
+
unlinkSync(lp);
|
|
1529
|
+
return true;
|
|
1530
|
+
} catch {
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
function checkStaleAndRetry(verb, lp) {
|
|
1535
|
+
let pidStr;
|
|
1536
|
+
try {
|
|
1537
|
+
pidStr = readFileSync3(lp, "utf8").trim();
|
|
1538
|
+
} catch {
|
|
1539
|
+
pidStr = "";
|
|
1540
|
+
}
|
|
1541
|
+
const pid = Number.parseInt(pidStr, 10);
|
|
1542
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
1543
|
+
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
1544
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
1545
|
+
return null;
|
|
1546
|
+
}
|
|
1547
|
+
try {
|
|
1548
|
+
process.kill(pid, 0);
|
|
1549
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
1550
|
+
return null;
|
|
1551
|
+
} catch (err) {
|
|
1552
|
+
const code = err.code;
|
|
1553
|
+
if (code === "ESRCH") {
|
|
1554
|
+
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
1555
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
1556
|
+
return null;
|
|
1557
|
+
}
|
|
1558
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
1559
|
+
return null;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
function retryOnce(verb, lp) {
|
|
1563
|
+
try {
|
|
1564
|
+
const fd = openSync2(lp, "wx");
|
|
1565
|
+
try {
|
|
1566
|
+
writeFileSync2(fd, String(process.pid));
|
|
1567
|
+
} catch {
|
|
1568
|
+
try {
|
|
1569
|
+
closeSync2(fd);
|
|
1570
|
+
} catch {
|
|
1571
|
+
}
|
|
1572
|
+
try {
|
|
1573
|
+
unlinkSync(lp);
|
|
1574
|
+
} catch {
|
|
1575
|
+
}
|
|
1576
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
1577
|
+
return null;
|
|
1578
|
+
}
|
|
1579
|
+
return { fd, path: lp };
|
|
1580
|
+
} catch {
|
|
1581
|
+
warn(`another nomad ${verb} running, skipping`);
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// src/commands.capture-settings.ts
|
|
1587
|
+
init_utils();
|
|
1588
|
+
async function confirmCapture(destLabel, keys) {
|
|
1589
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
1590
|
+
warn(
|
|
1591
|
+
`refusing to write ${destLabel} without confirmation in a non-interactive shell; re-run with --yes (or --dry-run to preview)`
|
|
1592
|
+
);
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
log(`About to promote ${keys.length} key(s) into ${destLabel}: ${keys.join(", ")}`);
|
|
1596
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1597
|
+
try {
|
|
1598
|
+
const answer = await rl.question("Proceed? [y/N] ");
|
|
1599
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
1600
|
+
} finally {
|
|
1601
|
+
rl.close();
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
function resolveCaptureDestination(repo, useHost) {
|
|
1605
|
+
const destPath = useHost ? join7(repo, "hosts", `${HOST}.json`) : join7(repo, "shared", "settings.base.json");
|
|
1606
|
+
const existing = existsSync5(destPath) ? readJson(destPath) : {};
|
|
1607
|
+
return { destPath, existing };
|
|
1608
|
+
}
|
|
1609
|
+
async function cmdCaptureSettings(opts) {
|
|
1610
|
+
const { host: useHost, dryRun } = opts;
|
|
1611
|
+
const repo = repoHome();
|
|
1612
|
+
if (!existsSync5(repo)) die(`repo not cloned at ${repo}`);
|
|
1613
|
+
const handle = acquireLock("capture-settings");
|
|
1614
|
+
if (handle === null) process.exit(0);
|
|
1615
|
+
try {
|
|
1616
|
+
const claude = claudeHome();
|
|
1617
|
+
const basePath = join7(repo, "shared", "settings.base.json");
|
|
1618
|
+
if (!existsSync5(basePath)) {
|
|
1619
|
+
die("repo not initialized; run 'nomad init' to scaffold");
|
|
1620
|
+
}
|
|
1621
|
+
const settingsPath = join7(claude, "settings.json");
|
|
1622
|
+
if (!existsSync5(settingsPath)) {
|
|
1623
|
+
log("no ~/.claude/settings.json found; nothing to capture");
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
const base = readJson(basePath);
|
|
1627
|
+
const hostPath = join7(repo, "hosts", `${HOST}.json`);
|
|
1628
|
+
const overrides = existsSync5(hostPath) ? readJson(hostPath) : {};
|
|
1629
|
+
const merged = deepMerge(base, overrides);
|
|
1630
|
+
const settings = readJson(settingsPath);
|
|
1631
|
+
const subset = buildCaptureSubset(merged, settings, { normalizeNodePath: !useHost });
|
|
1632
|
+
if (Object.keys(subset).length === 0) {
|
|
1633
|
+
log("nothing to capture: no local-only keys found");
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
const { destPath, existing } = resolveCaptureDestination(repo, useHost);
|
|
1637
|
+
const newContent = deepMerge(existing, subset);
|
|
1638
|
+
const dest = useHost ? `hosts/${HOST}.json` : "shared/settings.base.json";
|
|
1639
|
+
const keys = Object.keys(subset).sort((a, b) => a.localeCompare(b, "en"));
|
|
1640
|
+
if (dryRun) {
|
|
1641
|
+
log(`dry-run: would write ${dest} with keys: ${keys.join(", ")}`);
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
if (opts.yes !== true) {
|
|
1645
|
+
const confirm = opts.confirm ?? confirmCapture;
|
|
1646
|
+
const proceed = await confirm(dest, keys);
|
|
1647
|
+
if (!proceed) {
|
|
1648
|
+
log("capture aborted; nothing written");
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
const ts = freshBackupTs(backupBase());
|
|
1653
|
+
backupRepoWrite(destPath, ts, repo);
|
|
1654
|
+
writeJsonAtomic(destPath, newContent);
|
|
1655
|
+
regenerateSettings(ts, { suppressDriftWarn: true });
|
|
1656
|
+
log(`captured ${keys.length} key(s) into ${dest} (backup: ${ts})`);
|
|
1657
|
+
} finally {
|
|
1658
|
+
releaseLock(handle);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1253
1662
|
// src/commands.clean.ts
|
|
1254
1663
|
init_config();
|
|
1255
1664
|
init_utils();
|
|
1256
|
-
import { existsSync as
|
|
1257
|
-
import { join as
|
|
1665
|
+
import { existsSync as existsSync6, lstatSync as lstatSync4, readdirSync, rmSync as rmSync3, statSync as statSync2 } from "node:fs";
|
|
1666
|
+
import { join as join8 } from "node:path";
|
|
1258
1667
|
var TS_SHAPE = /^\d{8}-\d{6}(-\d+)?$/;
|
|
1259
1668
|
var DURATION_RE = /^(\d+)([dhm])$/;
|
|
1260
1669
|
var UNIT_MS = { d: 864e5, h: 36e5, m: 6e4 };
|
|
@@ -1268,8 +1677,8 @@ function parseDuration(s) {
|
|
|
1268
1677
|
return Number(m[1]) * UNIT_MS[m[2]];
|
|
1269
1678
|
}
|
|
1270
1679
|
function listBackupDirs(backupBase2) {
|
|
1271
|
-
if (!
|
|
1272
|
-
return readdirSync(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(
|
|
1680
|
+
if (!existsSync6(backupBase2)) return [];
|
|
1681
|
+
return readdirSync(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(join8(backupBase2, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1273
1682
|
}
|
|
1274
1683
|
function prunableByAge(dirs, olderThanMs, nowMs) {
|
|
1275
1684
|
return dirs.filter((d) => nowMs - d.mtimeMs > olderThanMs).map((d) => d.name);
|
|
@@ -1279,10 +1688,10 @@ function prunableByCount(dirs, keep) {
|
|
|
1279
1688
|
}
|
|
1280
1689
|
function safeDelete(backupBase2, name) {
|
|
1281
1690
|
if (!isTsDir(name)) return;
|
|
1282
|
-
const full =
|
|
1283
|
-
const st =
|
|
1691
|
+
const full = join8(backupBase2, name);
|
|
1692
|
+
const st = lstatSync4(full, { throwIfNoEntry: false });
|
|
1284
1693
|
if (!st || st.isSymbolicLink() || !st.isDirectory()) return;
|
|
1285
|
-
|
|
1694
|
+
rmSync3(full, { recursive: true, force: true });
|
|
1286
1695
|
}
|
|
1287
1696
|
function resolveTargets(dirs, olderThanMs, keep) {
|
|
1288
1697
|
if (keep !== void 0) return prunableByCount(dirs, keep);
|
|
@@ -1318,8 +1727,8 @@ function cmdClean(opts, backupBase2 = backupBase()) {
|
|
|
1318
1727
|
init_config();
|
|
1319
1728
|
init_utils();
|
|
1320
1729
|
init_utils_json();
|
|
1321
|
-
import { cpSync as cpSync3, existsSync as
|
|
1322
|
-
import { join as
|
|
1730
|
+
import { cpSync as cpSync3, existsSync as existsSync7, lstatSync as lstatSync5, realpathSync, renameSync as renameSync2, rmSync as rmSync4 } from "node:fs";
|
|
1731
|
+
import { join as join9, sep as sep2 } from "node:path";
|
|
1323
1732
|
function ejectChecklist() {
|
|
1324
1733
|
return [
|
|
1325
1734
|
"Manual steps remaining to finish leaving claude-nomad on this host:",
|
|
@@ -1335,33 +1744,33 @@ function errMessage(err) {
|
|
|
1335
1744
|
}
|
|
1336
1745
|
function lexists2(p) {
|
|
1337
1746
|
try {
|
|
1338
|
-
|
|
1747
|
+
lstatSync5(p);
|
|
1339
1748
|
return true;
|
|
1340
1749
|
} catch {
|
|
1341
1750
|
return false;
|
|
1342
1751
|
}
|
|
1343
1752
|
}
|
|
1344
1753
|
function readMapIfPresent2(repoHome2) {
|
|
1345
|
-
const mapPath =
|
|
1346
|
-
return
|
|
1754
|
+
const mapPath = join9(repoHome2, "path-map.json");
|
|
1755
|
+
return existsSync7(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
1347
1756
|
}
|
|
1348
1757
|
function classifyName(linkPath) {
|
|
1349
1758
|
if (!lexists2(linkPath)) return "absent";
|
|
1350
|
-
if (!
|
|
1351
|
-
if (!
|
|
1759
|
+
if (!lstatSync5(linkPath).isSymbolicLink()) return "skip-real";
|
|
1760
|
+
if (!existsSync7(linkPath)) return "dangling";
|
|
1352
1761
|
return "materialize";
|
|
1353
1762
|
}
|
|
1354
1763
|
function resolveSharedRoot(repoHome2) {
|
|
1355
1764
|
try {
|
|
1356
|
-
return realpathSync(
|
|
1765
|
+
return realpathSync(join9(repoHome2, "shared"));
|
|
1357
1766
|
} catch {
|
|
1358
1767
|
return die(
|
|
1359
|
-
`cannot resolve ${
|
|
1768
|
+
`cannot resolve ${join9(repoHome2, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
|
|
1360
1769
|
);
|
|
1361
1770
|
}
|
|
1362
1771
|
}
|
|
1363
1772
|
function isManagedTarget(target, sharedRoot) {
|
|
1364
|
-
return target.startsWith(sharedRoot +
|
|
1773
|
+
return target.startsWith(sharedRoot + sep2);
|
|
1365
1774
|
}
|
|
1366
1775
|
function materializeOne(name, linkPath, sharedRoot) {
|
|
1367
1776
|
const target = realpathSync(linkPath);
|
|
@@ -1371,20 +1780,20 @@ function materializeOne(name, linkPath, sharedRoot) {
|
|
|
1371
1780
|
}
|
|
1372
1781
|
const tmp = `${linkPath}.eject.tmp.${process.pid}.${Date.now()}`;
|
|
1373
1782
|
try {
|
|
1374
|
-
|
|
1783
|
+
rmSync4(tmp, { recursive: true, force: true });
|
|
1375
1784
|
cpSync3(target, tmp, {
|
|
1376
1785
|
recursive: true,
|
|
1377
1786
|
force: true,
|
|
1378
1787
|
dereference: true,
|
|
1379
1788
|
preserveTimestamps: true
|
|
1380
1789
|
});
|
|
1381
|
-
|
|
1790
|
+
rmSync4(linkPath, { force: true });
|
|
1382
1791
|
renameSync2(tmp, linkPath);
|
|
1383
1792
|
item(`ejected: ${name}`);
|
|
1384
1793
|
return true;
|
|
1385
1794
|
} catch (err) {
|
|
1386
1795
|
try {
|
|
1387
|
-
|
|
1796
|
+
rmSync4(tmp, { recursive: true, force: true });
|
|
1388
1797
|
} catch {
|
|
1389
1798
|
}
|
|
1390
1799
|
throw err;
|
|
@@ -1393,7 +1802,7 @@ function materializeOne(name, linkPath, sharedRoot) {
|
|
|
1393
1802
|
function previewDryRun(names, classifications, claudeHome2, sharedRoot) {
|
|
1394
1803
|
for (const name of names) {
|
|
1395
1804
|
const cls = classifications.get(name);
|
|
1396
|
-
const linkPath =
|
|
1805
|
+
const linkPath = join9(claudeHome2, name);
|
|
1397
1806
|
if (cls === "absent") {
|
|
1398
1807
|
item(`skipped (absent): ${name}`);
|
|
1399
1808
|
} else if (cls === "skip-real") {
|
|
@@ -1423,7 +1832,7 @@ function runLiveEject(names, classifications, claudeHome2, sharedRoot) {
|
|
|
1423
1832
|
let skipped = 0;
|
|
1424
1833
|
for (const name of names) {
|
|
1425
1834
|
const cls = classifications.get(name);
|
|
1426
|
-
const linkPath =
|
|
1835
|
+
const linkPath = join9(claudeHome2, name);
|
|
1427
1836
|
if (cls === "absent") {
|
|
1428
1837
|
item(`skipped (absent): ${name}`);
|
|
1429
1838
|
skipped++;
|
|
@@ -1459,7 +1868,7 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
1459
1868
|
const names = allSharedLinks(map);
|
|
1460
1869
|
const classifications = /* @__PURE__ */ new Map();
|
|
1461
1870
|
for (const name of names) {
|
|
1462
|
-
classifications.set(name, classifyName(
|
|
1871
|
+
classifications.set(name, classifyName(join9(claudeHome2, name)));
|
|
1463
1872
|
}
|
|
1464
1873
|
const dangling = names.filter((n) => classifications.get(n) === "dangling");
|
|
1465
1874
|
if (dangling.length > 0) {
|
|
@@ -1482,14 +1891,14 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
1482
1891
|
}
|
|
1483
1892
|
|
|
1484
1893
|
// src/commands.doctor.ts
|
|
1485
|
-
import { existsSync as
|
|
1486
|
-
import { join as
|
|
1894
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
1895
|
+
import { join as join35 } from "node:path";
|
|
1487
1896
|
|
|
1488
1897
|
// src/commands.doctor.checks.repo.ts
|
|
1489
1898
|
init_color();
|
|
1490
1899
|
init_config();
|
|
1491
|
-
import { existsSync as
|
|
1492
|
-
import { join as
|
|
1900
|
+
import { existsSync as existsSync9, lstatSync as lstatSync6, statSync as statSync3 } from "node:fs";
|
|
1901
|
+
import { join as join11 } from "node:path";
|
|
1493
1902
|
|
|
1494
1903
|
// src/commands.doctor.format.ts
|
|
1495
1904
|
init_color();
|
|
@@ -1567,15 +1976,15 @@ function readJsonSafe(path, label, section2) {
|
|
|
1567
1976
|
// src/init.classify.ts
|
|
1568
1977
|
init_config();
|
|
1569
1978
|
init_utils_json();
|
|
1570
|
-
import { existsSync as
|
|
1571
|
-
import { join as
|
|
1979
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
1980
|
+
import { join as join10 } from "node:path";
|
|
1572
1981
|
function classifyRepoState(repoHome2, host) {
|
|
1573
|
-
const basePath =
|
|
1574
|
-
const mapPath =
|
|
1575
|
-
const hostPath =
|
|
1576
|
-
const hasBase =
|
|
1577
|
-
const hasMap =
|
|
1578
|
-
const hasHost =
|
|
1982
|
+
const basePath = join10(repoHome2, "shared", "settings.base.json");
|
|
1983
|
+
const mapPath = join10(repoHome2, "path-map.json");
|
|
1984
|
+
const hostPath = join10(repoHome2, "hosts", `${host}.json`);
|
|
1985
|
+
const hasBase = existsSync8(basePath);
|
|
1986
|
+
const hasMap = existsSync8(mapPath);
|
|
1987
|
+
const hasHost = existsSync8(hostPath);
|
|
1579
1988
|
let mapEntryCount = 0;
|
|
1580
1989
|
if (hasMap) {
|
|
1581
1990
|
try {
|
|
@@ -1590,11 +1999,11 @@ function classifyRepoState(repoHome2, host) {
|
|
|
1590
1999
|
return "partial";
|
|
1591
2000
|
}
|
|
1592
2001
|
function reasonForPartial(repoHome2, host) {
|
|
1593
|
-
const basePath =
|
|
1594
|
-
const mapPath =
|
|
1595
|
-
const hostPath =
|
|
1596
|
-
if (!
|
|
1597
|
-
if (!
|
|
2002
|
+
const basePath = join10(repoHome2, "shared", "settings.base.json");
|
|
2003
|
+
const mapPath = join10(repoHome2, "path-map.json");
|
|
2004
|
+
const hostPath = join10(repoHome2, "hosts", `${host}.json`);
|
|
2005
|
+
if (!existsSync8(basePath)) return "- shared/settings.base.json missing";
|
|
2006
|
+
if (!existsSync8(mapPath)) return "- path-map.json missing";
|
|
1598
2007
|
let mapEntryCount;
|
|
1599
2008
|
try {
|
|
1600
2009
|
const map = readJson(mapPath);
|
|
@@ -1603,7 +2012,7 @@ function reasonForPartial(repoHome2, host) {
|
|
|
1603
2012
|
mapEntryCount = 0;
|
|
1604
2013
|
}
|
|
1605
2014
|
if (mapEntryCount === 0) return "- path-map.json.projects has no entries";
|
|
1606
|
-
if (!
|
|
2015
|
+
if (!existsSync8(hostPath)) return `- hosts/${host}.json missing`;
|
|
1607
2016
|
return "- partial state (unknown gap)";
|
|
1608
2017
|
}
|
|
1609
2018
|
|
|
@@ -1619,10 +2028,10 @@ function reportHostAndPaths(section2) {
|
|
|
1619
2028
|
if (isOverrideActive()) {
|
|
1620
2029
|
addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(repo)}`);
|
|
1621
2030
|
}
|
|
1622
|
-
addItem(section2, `${
|
|
2031
|
+
addItem(section2, `${existsSync9(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
|
|
1623
2032
|
addItem(
|
|
1624
2033
|
section2,
|
|
1625
|
-
`${
|
|
2034
|
+
`${existsSync9(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
|
|
1626
2035
|
);
|
|
1627
2036
|
}
|
|
1628
2037
|
function reportRepoState(section2) {
|
|
@@ -1645,12 +2054,12 @@ function reportRepoState(section2) {
|
|
|
1645
2054
|
}
|
|
1646
2055
|
}
|
|
1647
2056
|
function repoHasSharedSource(name) {
|
|
1648
|
-
return
|
|
2057
|
+
return existsSync9(join11(repoHome(), "shared", name));
|
|
1649
2058
|
}
|
|
1650
2059
|
function classifySharedLink(name, p) {
|
|
1651
2060
|
let stat;
|
|
1652
2061
|
try {
|
|
1653
|
-
stat =
|
|
2062
|
+
stat = lstatSync6(p);
|
|
1654
2063
|
} catch (err) {
|
|
1655
2064
|
const code = err.code;
|
|
1656
2065
|
if (code === "ENOENT") {
|
|
@@ -1693,7 +2102,7 @@ function classifySymlinkTarget(name, p) {
|
|
|
1693
2102
|
function reportSharedLinks(section2, map) {
|
|
1694
2103
|
const claude = claudeHome();
|
|
1695
2104
|
for (const name of allSharedLinks(map)) {
|
|
1696
|
-
const p =
|
|
2105
|
+
const p = join11(claude, name);
|
|
1697
2106
|
const { line, fail: fail2 } = classifySharedLink(name, p);
|
|
1698
2107
|
addItem(section2, line);
|
|
1699
2108
|
if (fail2) process.exitCode = 1;
|
|
@@ -1702,10 +2111,10 @@ function reportSharedLinks(section2, map) {
|
|
|
1702
2111
|
function reportDroppedNamesMigration(section2) {
|
|
1703
2112
|
const claude = claudeHome();
|
|
1704
2113
|
for (const name of GSD_DROPPED_NAMES) {
|
|
1705
|
-
const p =
|
|
2114
|
+
const p = join11(claude, name);
|
|
1706
2115
|
let stat;
|
|
1707
2116
|
try {
|
|
1708
|
-
stat =
|
|
2117
|
+
stat = lstatSync6(p);
|
|
1709
2118
|
} catch {
|
|
1710
2119
|
continue;
|
|
1711
2120
|
}
|
|
@@ -1720,11 +2129,11 @@ function reportDroppedNamesMigration(section2) {
|
|
|
1720
2129
|
// src/commands.doctor.checks.settings.ts
|
|
1721
2130
|
init_color();
|
|
1722
2131
|
init_config();
|
|
1723
|
-
import { existsSync as
|
|
1724
|
-
import { join as
|
|
2132
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2 } from "node:fs";
|
|
2133
|
+
import { join as join12 } from "node:path";
|
|
1725
2134
|
function loadBaseSettings(section2) {
|
|
1726
|
-
const basePath =
|
|
1727
|
-
if (!
|
|
2135
|
+
const basePath = join12(repoHome(), "shared", "settings.base.json");
|
|
2136
|
+
if (!existsSync10(basePath)) {
|
|
1728
2137
|
addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
|
|
1729
2138
|
process.exitCode = 1;
|
|
1730
2139
|
return null;
|
|
@@ -1732,8 +2141,8 @@ function loadBaseSettings(section2) {
|
|
|
1732
2141
|
return readJsonSafe(basePath, basePath, section2);
|
|
1733
2142
|
}
|
|
1734
2143
|
function loadAndReportSettings(section2) {
|
|
1735
|
-
const settingsPath =
|
|
1736
|
-
if (!
|
|
2144
|
+
const settingsPath = join12(claudeHome(), "settings.json");
|
|
2145
|
+
if (!existsSync10(settingsPath)) return null;
|
|
1737
2146
|
const settings = readJsonSafe(settingsPath, settingsPath, section2);
|
|
1738
2147
|
if (settings === null) return null;
|
|
1739
2148
|
const unknownKeys = Object.keys(settings).filter((k) => !KNOWN_SETTINGS_KEYS.has(k));
|
|
@@ -1749,13 +2158,13 @@ function loadAndReportSettings(section2) {
|
|
|
1749
2158
|
}
|
|
1750
2159
|
function reportHostOverrides(section2, base, settings) {
|
|
1751
2160
|
const repo = repoHome();
|
|
1752
|
-
const hostFile =
|
|
2161
|
+
const hostFile = join12(repo, "hosts", `${HOST}.json`);
|
|
1753
2162
|
let drift = [];
|
|
1754
2163
|
if (base !== null && settings !== null) {
|
|
1755
2164
|
const baseKeys = new Set(Object.keys(base));
|
|
1756
2165
|
drift = Object.keys(settings).filter((k) => !baseKeys.has(k));
|
|
1757
2166
|
}
|
|
1758
|
-
if (
|
|
2167
|
+
if (existsSync10(hostFile)) {
|
|
1759
2168
|
if (readJsonSafe(hostFile, hostFile, section2) !== null) {
|
|
1760
2169
|
addItem(section2, `${green(okGlyph)} host overrides: ${blue(hostFile)}`);
|
|
1761
2170
|
}
|
|
@@ -1764,8 +2173,8 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
1764
2173
|
section2,
|
|
1765
2174
|
`${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
|
|
1766
2175
|
);
|
|
1767
|
-
const hostsDir =
|
|
1768
|
-
if (
|
|
2176
|
+
const hostsDir = join12(repo, "hosts");
|
|
2177
|
+
if (existsSync10(hostsDir)) {
|
|
1769
2178
|
const cands = readdirSync2(hostsDir).filter((f) => f.endsWith(".json"));
|
|
1770
2179
|
if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
|
|
1771
2180
|
}
|
|
@@ -1781,8 +2190,8 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
1781
2190
|
// src/commands.doctor.checks.pathmap.ts
|
|
1782
2191
|
init_color();
|
|
1783
2192
|
init_config();
|
|
1784
|
-
import { existsSync as
|
|
1785
|
-
import { join as
|
|
2193
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3 } from "node:fs";
|
|
2194
|
+
import { join as join13 } from "node:path";
|
|
1786
2195
|
init_utils_json();
|
|
1787
2196
|
function reportMappedProjects(section2, map) {
|
|
1788
2197
|
const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
|
|
@@ -1792,8 +2201,8 @@ function reportMappedProjects(section2, map) {
|
|
|
1792
2201
|
}
|
|
1793
2202
|
}
|
|
1794
2203
|
function reportUnmappedProjects(section2, map) {
|
|
1795
|
-
const localProjects =
|
|
1796
|
-
if (!
|
|
2204
|
+
const localProjects = join13(claudeHome(), "projects");
|
|
2205
|
+
if (!existsSync11(localProjects)) return;
|
|
1797
2206
|
let localDirs;
|
|
1798
2207
|
try {
|
|
1799
2208
|
localDirs = readdirSync3(localProjects);
|
|
@@ -1833,43 +2242,20 @@ function reportPathCollisions(section2, map) {
|
|
|
1833
2242
|
else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
|
|
1834
2243
|
}
|
|
1835
2244
|
function reportPathMap(section2) {
|
|
1836
|
-
const mapPath =
|
|
1837
|
-
if (!
|
|
2245
|
+
const mapPath = join13(repoHome(), "path-map.json");
|
|
2246
|
+
if (!existsSync11(mapPath)) {
|
|
1838
2247
|
addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
|
|
1839
2248
|
process.exitCode = 1;
|
|
1840
2249
|
return;
|
|
1841
2250
|
}
|
|
1842
2251
|
const map = readJsonSafe(mapPath, mapPath, section2);
|
|
1843
2252
|
if (map === null) return;
|
|
1844
|
-
const
|
|
1845
|
-
if (
|
|
1846
|
-
addItem(
|
|
1847
|
-
section2,
|
|
1848
|
-
`${red(failGlyph)} path-map.json invalid schema: "projects" must be an object`
|
|
1849
|
-
);
|
|
2253
|
+
const shapeError = validatePathMapShape(map);
|
|
2254
|
+
if (shapeError !== null) {
|
|
2255
|
+
addItem(section2, `${red(failGlyph)} ${shapeError}`);
|
|
1850
2256
|
process.exitCode = 1;
|
|
1851
2257
|
return;
|
|
1852
2258
|
}
|
|
1853
|
-
for (const [name, hosts] of Object.entries(projects)) {
|
|
1854
|
-
if (hosts === null || typeof hosts !== "object" || Array.isArray(hosts)) {
|
|
1855
|
-
addItem(
|
|
1856
|
-
section2,
|
|
1857
|
-
`${red(failGlyph)} path-map.json invalid schema: project "${name}" hosts must be an object`
|
|
1858
|
-
);
|
|
1859
|
-
process.exitCode = 1;
|
|
1860
|
-
return;
|
|
1861
|
-
}
|
|
1862
|
-
for (const [hostName, mappedPath] of Object.entries(hosts)) {
|
|
1863
|
-
if (typeof mappedPath !== "string") {
|
|
1864
|
-
addItem(
|
|
1865
|
-
section2,
|
|
1866
|
-
`${red(failGlyph)} path-map.json invalid schema: project "${name}" host "${hostName}" path must be a string`
|
|
1867
|
-
);
|
|
1868
|
-
process.exitCode = 1;
|
|
1869
|
-
return;
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
2259
|
reportMappedProjects(section2, map);
|
|
1874
2260
|
reportUnmappedProjects(section2, map);
|
|
1875
2261
|
reportPathCollisions(section2, map);
|
|
@@ -1883,12 +2269,12 @@ function reportNeverSync(section2) {
|
|
|
1883
2269
|
);
|
|
1884
2270
|
}
|
|
1885
2271
|
|
|
1886
|
-
// src/commands.doctor.checks.
|
|
2272
|
+
// src/commands.doctor.checks.git-state.ts
|
|
1887
2273
|
init_color();
|
|
1888
2274
|
init_config();
|
|
1889
2275
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
1890
|
-
import { existsSync as
|
|
1891
|
-
import { join as
|
|
2276
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
2277
|
+
import { join as join17, relative as relative2 } from "node:path";
|
|
1892
2278
|
init_commands_pull_wedge();
|
|
1893
2279
|
init_push_checks();
|
|
1894
2280
|
init_utils();
|
|
@@ -1911,8 +2297,8 @@ function reportGitleaksProbe(section2) {
|
|
|
1911
2297
|
}
|
|
1912
2298
|
function reportGitlinks(section2) {
|
|
1913
2299
|
const repo = repoHome();
|
|
1914
|
-
const sharedDir =
|
|
1915
|
-
if (
|
|
2300
|
+
const sharedDir = join17(repo, "shared");
|
|
2301
|
+
if (existsSync14(sharedDir)) {
|
|
1916
2302
|
const gitlinks = findGitlinks(sharedDir);
|
|
1917
2303
|
for (const p of gitlinks) {
|
|
1918
2304
|
const rel = relative2(repo, p);
|
|
@@ -1985,8 +2371,8 @@ function reportOrphanedAutostash(sec) {
|
|
|
1985
2371
|
|
|
1986
2372
|
// src/commands.doctor.checks.backups.ts
|
|
1987
2373
|
init_color();
|
|
1988
|
-
import { existsSync as
|
|
1989
|
-
import { join as
|
|
2374
|
+
import { existsSync as existsSync15, lstatSync as lstatSync7, readdirSync as readdirSync5 } from "node:fs";
|
|
2375
|
+
import { join as join18 } from "node:path";
|
|
1990
2376
|
init_config();
|
|
1991
2377
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
1992
2378
|
function safeReaddir(dir) {
|
|
@@ -2002,8 +2388,8 @@ var BYTES_PER_MB = 1024 * 1024;
|
|
|
2002
2388
|
function dirSizeBytes(dir) {
|
|
2003
2389
|
let bytes = 0;
|
|
2004
2390
|
for (const entry of safeReaddir(dir)) {
|
|
2005
|
-
const full =
|
|
2006
|
-
const st =
|
|
2391
|
+
const full = join18(dir, entry);
|
|
2392
|
+
const st = lstatSync7(full, { throwIfNoEntry: false });
|
|
2007
2393
|
if (!st) continue;
|
|
2008
2394
|
if (st.isSymbolicLink()) continue;
|
|
2009
2395
|
if (st.isDirectory()) bytes += dirSizeBytes(full);
|
|
@@ -2013,11 +2399,11 @@ function dirSizeBytes(dir) {
|
|
|
2013
2399
|
}
|
|
2014
2400
|
function totalSizeMb(backupBase2, dirs) {
|
|
2015
2401
|
let bytes = 0;
|
|
2016
|
-
for (const name of dirs) bytes += dirSizeBytes(
|
|
2402
|
+
for (const name of dirs) bytes += dirSizeBytes(join18(backupBase2, name));
|
|
2017
2403
|
return bytes / BYTES_PER_MB;
|
|
2018
2404
|
}
|
|
2019
2405
|
function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
2020
|
-
if (!
|
|
2406
|
+
if (!existsSync15(backupBase2)) return;
|
|
2021
2407
|
const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
|
|
2022
2408
|
const count = dirs.length;
|
|
2023
2409
|
const sizeMb = totalSizeMb(backupBase2, dirs);
|
|
@@ -2031,8 +2417,8 @@ function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
|
2031
2417
|
|
|
2032
2418
|
// src/commands.doctor.check-schema.ts
|
|
2033
2419
|
init_color();
|
|
2034
|
-
import { existsSync as
|
|
2035
|
-
import { join as
|
|
2420
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
2421
|
+
import { join as join19 } from "node:path";
|
|
2036
2422
|
init_config();
|
|
2037
2423
|
|
|
2038
2424
|
// src/http-fetch.ts
|
|
@@ -2069,8 +2455,8 @@ function fetchSchemaKeys() {
|
|
|
2069
2455
|
}
|
|
2070
2456
|
}
|
|
2071
2457
|
function reportCheckSchema(section2) {
|
|
2072
|
-
const settingsPath =
|
|
2073
|
-
if (!
|
|
2458
|
+
const settingsPath = join19(claudeHome(), "settings.json");
|
|
2459
|
+
if (!existsSync16(settingsPath)) {
|
|
2074
2460
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
2075
2461
|
return;
|
|
2076
2462
|
}
|
|
@@ -2100,18 +2486,18 @@ function reportCheckSchema(section2) {
|
|
|
2100
2486
|
init_color();
|
|
2101
2487
|
import { randomBytes } from "node:crypto";
|
|
2102
2488
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
2103
|
-
import { existsSync as
|
|
2489
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync5, readdirSync as readdirSync7, rmSync as rmSync8 } from "node:fs";
|
|
2104
2490
|
import { homedir as homedir4 } from "node:os";
|
|
2105
|
-
import { join as
|
|
2491
|
+
import { join as join23 } from "node:path";
|
|
2106
2492
|
|
|
2107
2493
|
// src/commands.doctor.check-shared.scan.ts
|
|
2108
2494
|
init_color();
|
|
2109
|
-
import { join as
|
|
2495
|
+
import { join as join21 } from "node:path";
|
|
2110
2496
|
init_config();
|
|
2111
2497
|
init_push_gitleaks();
|
|
2112
2498
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
2113
2499
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
2114
|
-
return
|
|
2500
|
+
return join21(claudeHome(), "projects", encoded, `${sid}.jsonl`);
|
|
2115
2501
|
}
|
|
2116
2502
|
function reportSessionFindings(section2, bySession) {
|
|
2117
2503
|
for (const [sid, counts] of bySession) {
|
|
@@ -2203,21 +2589,27 @@ init_config();
|
|
|
2203
2589
|
init_utils();
|
|
2204
2590
|
init_utils_fs();
|
|
2205
2591
|
init_utils_json();
|
|
2206
|
-
import { cpSync as cpSync4, existsSync as
|
|
2207
|
-
import { join as
|
|
2592
|
+
import { cpSync as cpSync4, existsSync as existsSync17, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
|
|
2593
|
+
import { join as join22, relative as relative3, sep as sep3 } from "node:path";
|
|
2594
|
+
var TMP_SUFFIX = ".nomad-tmp";
|
|
2595
|
+
function atomicMirror(src, dst, options) {
|
|
2596
|
+
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
2597
|
+
rmSync7(tmp, { recursive: true, force: true });
|
|
2598
|
+
cpSync4(src, tmp, options);
|
|
2599
|
+
rmSync7(dst, { recursive: true, force: true });
|
|
2600
|
+
renameSync3(tmp, dst);
|
|
2601
|
+
}
|
|
2208
2602
|
function copyDir(src, dst) {
|
|
2209
|
-
|
|
2210
|
-
cpSync4(src, dst, { recursive: true, force: true });
|
|
2603
|
+
atomicMirror(src, dst, { recursive: true, force: true });
|
|
2211
2604
|
}
|
|
2212
2605
|
function copyDirJsonlOnly(src, dst) {
|
|
2213
|
-
|
|
2214
|
-
cpSync4(src, dst, {
|
|
2606
|
+
atomicMirror(src, dst, {
|
|
2215
2607
|
recursive: true,
|
|
2216
2608
|
force: true,
|
|
2217
2609
|
filter: (srcPath) => {
|
|
2218
2610
|
const rel = relative3(src, srcPath);
|
|
2219
2611
|
if (rel === "") return true;
|
|
2220
|
-
if (rel.split(
|
|
2612
|
+
if (rel.split(sep3).length > 1) return true;
|
|
2221
2613
|
if (statSync4(srcPath).isDirectory()) return true;
|
|
2222
2614
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
2223
2615
|
item(`skip ${rel}: extension not in allowlist`);
|
|
@@ -2236,16 +2628,16 @@ function remapPull(ts, opts = {}) {
|
|
|
2236
2628
|
const wouldPull = [];
|
|
2237
2629
|
const repo = repoHome();
|
|
2238
2630
|
const claude = claudeHome();
|
|
2239
|
-
const mapPath =
|
|
2240
|
-
const repoProjects =
|
|
2241
|
-
if (!
|
|
2631
|
+
const mapPath = join22(repo, "path-map.json");
|
|
2632
|
+
const repoProjects = join22(repo, "shared", "projects");
|
|
2633
|
+
if (!existsSync17(mapPath) || !existsSync17(repoProjects)) {
|
|
2242
2634
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
2243
2635
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
2244
2636
|
return { unmapped: 0, pulled, wouldPull };
|
|
2245
2637
|
}
|
|
2246
|
-
const map =
|
|
2247
|
-
const localProjects =
|
|
2248
|
-
if (!dryRun)
|
|
2638
|
+
const map = readPathMap(mapPath);
|
|
2639
|
+
const localProjects = join22(claude, "projects");
|
|
2640
|
+
if (!dryRun) mkdirSync4(localProjects, { recursive: true });
|
|
2249
2641
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
2250
2642
|
assertSafeLogical(logical);
|
|
2251
2643
|
const localPath = hosts[HOST];
|
|
@@ -2253,9 +2645,9 @@ function remapPull(ts, opts = {}) {
|
|
|
2253
2645
|
unmapped++;
|
|
2254
2646
|
continue;
|
|
2255
2647
|
}
|
|
2256
|
-
const src =
|
|
2257
|
-
if (!
|
|
2258
|
-
const dst =
|
|
2648
|
+
const src = join22(repoProjects, logical);
|
|
2649
|
+
if (!existsSync17(src)) continue;
|
|
2650
|
+
const dst = join22(localProjects, encodePath(localPath));
|
|
2259
2651
|
if (dryRun) {
|
|
2260
2652
|
wouldPull.push(logical);
|
|
2261
2653
|
emitPreview(
|
|
@@ -2302,30 +2694,31 @@ function remapPush(ts, opts = {}) {
|
|
|
2302
2694
|
const wouldPush = [];
|
|
2303
2695
|
const repo = repoHome();
|
|
2304
2696
|
const claude = claudeHome();
|
|
2305
|
-
const mapPath =
|
|
2306
|
-
if (!
|
|
2697
|
+
const mapPath = join22(repo, "path-map.json");
|
|
2698
|
+
if (!existsSync17(mapPath)) {
|
|
2307
2699
|
log("no path-map.json; skipping session export");
|
|
2308
2700
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
2309
2701
|
}
|
|
2310
|
-
const map =
|
|
2311
|
-
const localProjects =
|
|
2312
|
-
const repoProjects =
|
|
2702
|
+
const map = readPathMap(mapPath);
|
|
2703
|
+
const localProjects = join22(claude, "projects");
|
|
2704
|
+
const repoProjects = join22(repo, "shared", "projects");
|
|
2313
2705
|
const reverse = buildReverseMap(map);
|
|
2314
|
-
if (!
|
|
2315
|
-
if (!dryRun)
|
|
2706
|
+
if (!existsSync17(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
2707
|
+
if (!dryRun) mkdirSync4(repoProjects, { recursive: true });
|
|
2316
2708
|
for (const dir of readdirSync6(localProjects)) {
|
|
2709
|
+
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
2317
2710
|
const logical = reverse.get(dir);
|
|
2318
2711
|
if (!logical) {
|
|
2319
2712
|
unmapped++;
|
|
2320
2713
|
continue;
|
|
2321
2714
|
}
|
|
2322
|
-
const repoDst =
|
|
2715
|
+
const repoDst = join22(repoProjects, logical);
|
|
2323
2716
|
if (dryRun) {
|
|
2324
2717
|
wouldPush.push(logical);
|
|
2325
2718
|
continue;
|
|
2326
2719
|
}
|
|
2327
2720
|
backupRepoWrite(repoDst, ts, repo);
|
|
2328
|
-
copyDirJsonlOnly(
|
|
2721
|
+
copyDirJsonlOnly(join22(localProjects, dir), repoDst);
|
|
2329
2722
|
pushed.push(logical);
|
|
2330
2723
|
}
|
|
2331
2724
|
return { unmapped, collisions: 0, pushed, wouldPush };
|
|
@@ -2338,8 +2731,8 @@ function buildScanTree(tmpRoot) {
|
|
|
2338
2731
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
2339
2732
|
let staged = 0;
|
|
2340
2733
|
const repo = repoHome();
|
|
2341
|
-
const mapPath =
|
|
2342
|
-
if (!
|
|
2734
|
+
const mapPath = join23(repo, "path-map.json");
|
|
2735
|
+
if (!existsSync18(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
2343
2736
|
let map;
|
|
2344
2737
|
try {
|
|
2345
2738
|
map = readJson(mapPath);
|
|
@@ -2356,12 +2749,12 @@ function buildScanTree(tmpRoot) {
|
|
|
2356
2749
|
if (!p || p === "TBD") continue;
|
|
2357
2750
|
reverse.set(encodePath(p), logical);
|
|
2358
2751
|
}
|
|
2359
|
-
const localProjects =
|
|
2360
|
-
if (!
|
|
2752
|
+
const localProjects = join23(claudeHome(), "projects");
|
|
2753
|
+
if (!existsSync18(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
2361
2754
|
for (const dir of readdirSync7(localProjects)) {
|
|
2362
2755
|
const logical = reverse.get(dir);
|
|
2363
2756
|
if (!logical) continue;
|
|
2364
|
-
copyDirJsonlOnly(
|
|
2757
|
+
copyDirJsonlOnly(join23(localProjects, dir), join23(tmpRoot, "shared", "projects", logical));
|
|
2365
2758
|
logicalToEncoded.set(logical, dir);
|
|
2366
2759
|
staged++;
|
|
2367
2760
|
}
|
|
@@ -2392,11 +2785,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
2392
2785
|
}
|
|
2393
2786
|
function reportCheckShared(section2, gitleaksReady) {
|
|
2394
2787
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
2395
|
-
const cacheDir =
|
|
2396
|
-
|
|
2788
|
+
const cacheDir = join23(homedir4(), ".cache", "claude-nomad");
|
|
2789
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
2397
2790
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
2398
|
-
const reportPath =
|
|
2399
|
-
const tmpRoot =
|
|
2791
|
+
const reportPath = join23(cacheDir, `check-shared-${stamp}.json`);
|
|
2792
|
+
const tmpRoot = join23(cacheDir, `check-shared-tree-${stamp}`);
|
|
2400
2793
|
try {
|
|
2401
2794
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
2402
2795
|
if (malformed) {
|
|
@@ -2410,19 +2803,19 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
2410
2803
|
}
|
|
2411
2804
|
scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
|
|
2412
2805
|
} finally {
|
|
2413
|
-
|
|
2414
|
-
|
|
2806
|
+
rmSync8(reportPath, { force: true });
|
|
2807
|
+
rmSync8(tmpRoot, { recursive: true, force: true });
|
|
2415
2808
|
}
|
|
2416
2809
|
}
|
|
2417
2810
|
|
|
2418
2811
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
2419
2812
|
init_color();
|
|
2420
|
-
import { existsSync as
|
|
2421
|
-
import { dirname as
|
|
2813
|
+
import { existsSync as existsSync19, readFileSync as readFileSync6, readdirSync as readdirSync8, realpathSync as realpathSync2 } from "node:fs";
|
|
2814
|
+
import { dirname as dirname3, extname, join as join24 } from "node:path";
|
|
2422
2815
|
init_config();
|
|
2423
2816
|
function typeFromPackageJson(pkgPath) {
|
|
2424
2817
|
try {
|
|
2425
|
-
const parsed = JSON.parse(
|
|
2818
|
+
const parsed = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
2426
2819
|
return parsed.type === "module" ? "esm" : "cjs";
|
|
2427
2820
|
} catch {
|
|
2428
2821
|
return "cjs";
|
|
@@ -2438,11 +2831,11 @@ function effectiveType(hookPath) {
|
|
|
2438
2831
|
} catch {
|
|
2439
2832
|
return null;
|
|
2440
2833
|
}
|
|
2441
|
-
let dir =
|
|
2834
|
+
let dir = dirname3(real);
|
|
2442
2835
|
for (; ; ) {
|
|
2443
|
-
const pkg =
|
|
2444
|
-
if (
|
|
2445
|
-
const parent =
|
|
2836
|
+
const pkg = join24(dir, "package.json");
|
|
2837
|
+
if (existsSync19(pkg)) return typeFromPackageJson(pkg);
|
|
2838
|
+
const parent = dirname3(dir);
|
|
2446
2839
|
if (parent === dir) return "cjs";
|
|
2447
2840
|
dir = parent;
|
|
2448
2841
|
}
|
|
@@ -2477,21 +2870,21 @@ function safeReaddir2(dir) {
|
|
|
2477
2870
|
}
|
|
2478
2871
|
function safeRead(path) {
|
|
2479
2872
|
try {
|
|
2480
|
-
return
|
|
2873
|
+
return readFileSync6(path, "utf8");
|
|
2481
2874
|
} catch {
|
|
2482
2875
|
return null;
|
|
2483
2876
|
}
|
|
2484
2877
|
}
|
|
2485
2878
|
function reportHookScopeCheck(section2) {
|
|
2486
|
-
const hooksDir =
|
|
2487
|
-
if (!
|
|
2879
|
+
const hooksDir = join24(claudeHome(), "hooks");
|
|
2880
|
+
if (!existsSync19(hooksDir)) {
|
|
2488
2881
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
2489
2882
|
return;
|
|
2490
2883
|
}
|
|
2491
2884
|
let anyWarn = false;
|
|
2492
2885
|
for (const name of safeReaddir2(hooksDir)) {
|
|
2493
2886
|
if (extname(name) !== ".js") continue;
|
|
2494
|
-
const abs =
|
|
2887
|
+
const abs = join24(hooksDir, name);
|
|
2495
2888
|
const eff = effectiveType(abs);
|
|
2496
2889
|
if (eff === null) continue;
|
|
2497
2890
|
const src = safeRead(abs);
|
|
@@ -2511,8 +2904,8 @@ function reportHookScopeCheck(section2) {
|
|
|
2511
2904
|
|
|
2512
2905
|
// src/commands.doctor.checks.hooks.ts
|
|
2513
2906
|
init_color();
|
|
2514
|
-
import { existsSync as
|
|
2515
|
-
import { join as
|
|
2907
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
2908
|
+
import { join as join25 } from "node:path";
|
|
2516
2909
|
init_config();
|
|
2517
2910
|
function expandHome(token) {
|
|
2518
2911
|
const h2 = home();
|
|
@@ -2551,7 +2944,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
2551
2944
|
for (const group of groups) {
|
|
2552
2945
|
for (const cmd of commandsFromGroup(group)) {
|
|
2553
2946
|
for (const resolved of claudePathsIn(cmd)) {
|
|
2554
|
-
if (
|
|
2947
|
+
if (existsSync20(resolved)) continue;
|
|
2555
2948
|
addItem(
|
|
2556
2949
|
section2,
|
|
2557
2950
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -2564,8 +2957,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
2564
2957
|
return anyFail;
|
|
2565
2958
|
}
|
|
2566
2959
|
function reportHooksTargetCheck(section2) {
|
|
2567
|
-
const settingsPath =
|
|
2568
|
-
if (!
|
|
2960
|
+
const settingsPath = join25(claudeHome(), "settings.json");
|
|
2961
|
+
if (!existsSync20(settingsPath)) {
|
|
2569
2962
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
2570
2963
|
return;
|
|
2571
2964
|
}
|
|
@@ -2588,12 +2981,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
2588
2981
|
|
|
2589
2982
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
2590
2983
|
init_color();
|
|
2591
|
-
import { existsSync as
|
|
2592
|
-
import { join as
|
|
2984
|
+
import { existsSync as existsSync22, readFileSync as readFileSync7 } from "node:fs";
|
|
2985
|
+
import { join as join27 } from "node:path";
|
|
2593
2986
|
|
|
2594
2987
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
2595
|
-
import { closeSync as
|
|
2596
|
-
import { dirname as
|
|
2988
|
+
import { closeSync as closeSync3, existsSync as existsSync21, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
|
|
2989
|
+
import { dirname as dirname4, join as join26, resolve as resolve2 } from "node:path";
|
|
2597
2990
|
function suppressedRanges(src) {
|
|
2598
2991
|
const ranges = [];
|
|
2599
2992
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -2625,12 +3018,12 @@ function topRelativeSpecifiers(src) {
|
|
|
2625
3018
|
}
|
|
2626
3019
|
function specifierIsMissing(specifier, baseDir) {
|
|
2627
3020
|
const base = resolve2(baseDir, specifier);
|
|
2628
|
-
if (
|
|
3021
|
+
if (existsSync21(base)) return false;
|
|
2629
3022
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
2630
|
-
if (
|
|
3023
|
+
if (existsSync21(base + ext)) return false;
|
|
2631
3024
|
}
|
|
2632
3025
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
2633
|
-
if (
|
|
3026
|
+
if (existsSync21(join26(base, idx))) return false;
|
|
2634
3027
|
}
|
|
2635
3028
|
return true;
|
|
2636
3029
|
}
|
|
@@ -2643,20 +3036,20 @@ function relativeRequireTargetsBroken(scriptPath) {
|
|
|
2643
3036
|
}
|
|
2644
3037
|
let raw;
|
|
2645
3038
|
try {
|
|
2646
|
-
const fd =
|
|
3039
|
+
const fd = openSync3(realPath, "r");
|
|
2647
3040
|
try {
|
|
2648
3041
|
const buf = Buffer.alloc(65536);
|
|
2649
3042
|
const bytesRead = readSync(fd, buf, 0, 65536, 0);
|
|
2650
3043
|
raw = buf.toString("utf8", 0, bytesRead);
|
|
2651
3044
|
} finally {
|
|
2652
|
-
|
|
3045
|
+
closeSync3(fd);
|
|
2653
3046
|
}
|
|
2654
3047
|
} catch {
|
|
2655
3048
|
return false;
|
|
2656
3049
|
}
|
|
2657
3050
|
const specifiers = topRelativeSpecifiers(raw);
|
|
2658
3051
|
if (specifiers.length === 0) return false;
|
|
2659
|
-
const baseDir =
|
|
3052
|
+
const baseDir = dirname4(realPath);
|
|
2660
3053
|
for (const spec of specifiers) {
|
|
2661
3054
|
if (specifierIsMissing(spec, baseDir)) return true;
|
|
2662
3055
|
}
|
|
@@ -2685,10 +3078,10 @@ function commandTokens(command) {
|
|
|
2685
3078
|
return tokens;
|
|
2686
3079
|
}
|
|
2687
3080
|
function readPathMapSafe() {
|
|
2688
|
-
const mapPath =
|
|
2689
|
-
if (!
|
|
3081
|
+
const mapPath = join27(repoHome(), "path-map.json");
|
|
3082
|
+
if (!existsSync22(mapPath)) return { projects: {} };
|
|
2690
3083
|
try {
|
|
2691
|
-
return JSON.parse(
|
|
3084
|
+
return JSON.parse(readFileSync7(mapPath, "utf8"));
|
|
2692
3085
|
} catch {
|
|
2693
3086
|
return { projects: {} };
|
|
2694
3087
|
}
|
|
@@ -2759,8 +3152,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
2759
3152
|
return anyWarn;
|
|
2760
3153
|
}
|
|
2761
3154
|
function reportPreserveSymlinksCheck(section2) {
|
|
2762
|
-
const settingsPath =
|
|
2763
|
-
if (!
|
|
3155
|
+
const settingsPath = join27(claudeHome(), "settings.json");
|
|
3156
|
+
if (!existsSync22(settingsPath)) {
|
|
2764
3157
|
addItem(
|
|
2765
3158
|
section2,
|
|
2766
3159
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -2769,7 +3162,7 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
2769
3162
|
}
|
|
2770
3163
|
let settings;
|
|
2771
3164
|
try {
|
|
2772
|
-
settings = JSON.parse(
|
|
3165
|
+
settings = JSON.parse(readFileSync7(settingsPath, "utf8"));
|
|
2773
3166
|
} catch {
|
|
2774
3167
|
return;
|
|
2775
3168
|
}
|
|
@@ -2781,75 +3174,29 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
2781
3174
|
}
|
|
2782
3175
|
const map = readPathMapSafe();
|
|
2783
3176
|
const sharedLinkNames = allSharedLinks(map);
|
|
2784
|
-
let anyWarn = false;
|
|
2785
|
-
for (const [event, groups] of Object.entries(hooks)) {
|
|
2786
|
-
if (!Array.isArray(groups)) continue;
|
|
2787
|
-
if (checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)) anyWarn = true;
|
|
2788
|
-
}
|
|
2789
|
-
if (!anyWarn) {
|
|
2790
|
-
addItem(section2, `${green(okGlyph)} hooks: preserve-symlinks-main not needed`);
|
|
2791
|
-
}
|
|
2792
|
-
}
|
|
2793
|
-
|
|
2794
|
-
// src/commands.doctor.checks.settings-drift.ts
|
|
2795
|
-
init_color();
|
|
2796
|
-
import { existsSync as existsSync21, readFileSync as readFileSync7 } from "node:fs";
|
|
2797
|
-
import { join as join25 } from "node:path";
|
|
2798
|
-
init_config();
|
|
2799
|
-
init_utils_json();
|
|
2800
|
-
function arraysEqual(a, b) {
|
|
2801
|
-
if (a.length !== b.length) return false;
|
|
2802
|
-
for (let i = 0; i < a.length; i++) {
|
|
2803
|
-
if (!deepEqual(a[i], b[i])) return false;
|
|
2804
|
-
}
|
|
2805
|
-
return true;
|
|
2806
|
-
}
|
|
2807
|
-
function objectsEqual(a, b) {
|
|
2808
|
-
const aKeys = Object.keys(a);
|
|
2809
|
-
const bKeys = Object.keys(b);
|
|
2810
|
-
if (aKeys.length !== bKeys.length) return false;
|
|
2811
|
-
for (const k of aKeys) {
|
|
2812
|
-
if (!Object.hasOwn(b, k)) return false;
|
|
2813
|
-
if (!deepEqual(a[k], b[k])) return false;
|
|
3177
|
+
let anyWarn = false;
|
|
3178
|
+
for (const [event, groups] of Object.entries(hooks)) {
|
|
3179
|
+
if (!Array.isArray(groups)) continue;
|
|
3180
|
+
if (checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)) anyWarn = true;
|
|
2814
3181
|
}
|
|
2815
|
-
|
|
2816
|
-
}
|
|
2817
|
-
function deepEqual(a, b) {
|
|
2818
|
-
if (a === b) return true;
|
|
2819
|
-
if (a === null || b === null) return false;
|
|
2820
|
-
if (Array.isArray(a) && Array.isArray(b)) return arraysEqual(a, b);
|
|
2821
|
-
if (Array.isArray(a) || Array.isArray(b)) return false;
|
|
2822
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
2823
|
-
return objectsEqual(a, b);
|
|
3182
|
+
if (!anyWarn) {
|
|
3183
|
+
addItem(section2, `${green(okGlyph)} hooks: preserve-symlinks-main not needed`);
|
|
2824
3184
|
}
|
|
2825
|
-
return false;
|
|
2826
3185
|
}
|
|
3186
|
+
|
|
3187
|
+
// src/commands.doctor.checks.settings-drift.ts
|
|
3188
|
+
init_color();
|
|
3189
|
+
import { existsSync as existsSync23, readFileSync as readFileSync8 } from "node:fs";
|
|
3190
|
+
import { join as join28 } from "node:path";
|
|
3191
|
+
init_config();
|
|
3192
|
+
init_utils_json();
|
|
2827
3193
|
function diffMergedSettings(merged, settings) {
|
|
2828
|
-
const
|
|
2829
|
-
|
|
2830
|
-
const extra = [];
|
|
2831
|
-
const settingsKeys = new Set(Object.keys(settings));
|
|
2832
|
-
for (const key of Object.keys(merged)) {
|
|
2833
|
-
if (!settingsKeys.has(key)) {
|
|
2834
|
-
missing.push(key);
|
|
2835
|
-
} else if (!deepEqual(merged[key], settings[key])) {
|
|
2836
|
-
changed.push(key);
|
|
2837
|
-
}
|
|
2838
|
-
}
|
|
2839
|
-
const mergedKeys = new Set(Object.keys(merged));
|
|
2840
|
-
for (const key of Object.keys(settings)) {
|
|
2841
|
-
if (!mergedKeys.has(key)) extra.push(key);
|
|
2842
|
-
}
|
|
2843
|
-
const collator = (a, b) => a.localeCompare(b, "en");
|
|
2844
|
-
return {
|
|
2845
|
-
missing: missing.toSorted(collator),
|
|
2846
|
-
changed: changed.toSorted(collator),
|
|
2847
|
-
extra: extra.toSorted(collator)
|
|
2848
|
-
};
|
|
3194
|
+
const { behind, changed, ahead } = classifySettingsDrift(merged, settings);
|
|
3195
|
+
return { missing: behind, changed, extra: ahead };
|
|
2849
3196
|
}
|
|
2850
3197
|
function tryReadJson(filePath) {
|
|
2851
3198
|
try {
|
|
2852
|
-
const raw =
|
|
3199
|
+
const raw = readFileSync8(filePath, "utf8");
|
|
2853
3200
|
const parsed = JSON.parse(raw);
|
|
2854
3201
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
2855
3202
|
return parsed;
|
|
@@ -2861,14 +3208,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
2861
3208
|
const claude = claudeHome();
|
|
2862
3209
|
const repo = repoHome();
|
|
2863
3210
|
const host = HOST;
|
|
2864
|
-
const settingsPath =
|
|
2865
|
-
const basePath =
|
|
2866
|
-
const hostPath =
|
|
2867
|
-
if (!
|
|
3211
|
+
const settingsPath = join28(claude, "settings.json");
|
|
3212
|
+
const basePath = join28(repo, "shared", "settings.base.json");
|
|
3213
|
+
const hostPath = join28(repo, "hosts", `${host}.json`);
|
|
3214
|
+
if (!existsSync23(settingsPath)) {
|
|
2868
3215
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
2869
3216
|
return;
|
|
2870
3217
|
}
|
|
2871
|
-
if (!
|
|
3218
|
+
if (!existsSync23(basePath)) {
|
|
2872
3219
|
addItem(
|
|
2873
3220
|
section2,
|
|
2874
3221
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -2887,7 +3234,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
2887
3234
|
if (settings === null) {
|
|
2888
3235
|
return;
|
|
2889
3236
|
}
|
|
2890
|
-
const hostExists =
|
|
3237
|
+
const hostExists = existsSync23(hostPath);
|
|
2891
3238
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
2892
3239
|
if (hostExists && hostObj === null) {
|
|
2893
3240
|
addItem(
|
|
@@ -2898,9 +3245,10 @@ function reportSettingsDriftCheck(section2) {
|
|
|
2898
3245
|
}
|
|
2899
3246
|
const merged = deepMerge(base, hostObj ?? {});
|
|
2900
3247
|
const { missing, changed, extra } = diffMergedSettings(merged, settings);
|
|
2901
|
-
|
|
3248
|
+
const { promotable, excluded } = partitionByCaptureExclusion(extra);
|
|
3249
|
+
emitDriftRows(section2, missing, changed, promotable, excluded, hostExists);
|
|
2902
3250
|
}
|
|
2903
|
-
function emitDriftRows(section2, missing, changed,
|
|
3251
|
+
function emitDriftRows(section2, missing, changed, promotable, excluded, hostFileExists) {
|
|
2904
3252
|
if (missing.length > 0) {
|
|
2905
3253
|
addItem(
|
|
2906
3254
|
section2,
|
|
@@ -2913,13 +3261,19 @@ function emitDriftRows(section2, missing, changed, extra, host, hostFileExists)
|
|
|
2913
3261
|
`${yellow(warnGlyph)} settings.json drift: merged keys with changed values: ${changed.join(", ")} (run 'nomad pull')`
|
|
2914
3262
|
);
|
|
2915
3263
|
}
|
|
2916
|
-
if (
|
|
3264
|
+
if (promotable.length > 0 && hostFileExists) {
|
|
3265
|
+
addItem(
|
|
3266
|
+
section2,
|
|
3267
|
+
`${dim(infoGlyph)} settings.json has ${promotable.length} local-only key(s) not in base+host merge: ${promotable.join(", ")} (run 'nomad capture-settings' to promote them into the repo)`
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3270
|
+
if (excluded.length > 0 && hostFileExists) {
|
|
2917
3271
|
addItem(
|
|
2918
3272
|
section2,
|
|
2919
|
-
`${dim(infoGlyph)} settings.json has ${
|
|
3273
|
+
`${dim(infoGlyph)} settings.json has ${excluded.length} local-only key(s) outside the sync set (credential or host-local; nomad does not promote these)`
|
|
2920
3274
|
);
|
|
2921
3275
|
}
|
|
2922
|
-
if (missing.length === 0 && changed.length === 0 &&
|
|
3276
|
+
if (missing.length === 0 && changed.length === 0 && promotable.length === 0 && excluded.length === 0) {
|
|
2923
3277
|
addItem(section2, `${green(okGlyph)} settings.json matches base+host merge`);
|
|
2924
3278
|
}
|
|
2925
3279
|
}
|
|
@@ -2929,12 +3283,12 @@ init_config();
|
|
|
2929
3283
|
|
|
2930
3284
|
// src/commands.doctor.engine.ts
|
|
2931
3285
|
init_color();
|
|
2932
|
-
import { readFileSync as
|
|
3286
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
2933
3287
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2934
3288
|
|
|
2935
3289
|
// src/commands.doctor.version.ts
|
|
2936
3290
|
init_color();
|
|
2937
|
-
import { readFileSync as
|
|
3291
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
2938
3292
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2939
3293
|
init_config();
|
|
2940
3294
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
@@ -2951,7 +3305,7 @@ function compareSemver(a, b) {
|
|
|
2951
3305
|
function readLocalVersion() {
|
|
2952
3306
|
try {
|
|
2953
3307
|
const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
|
|
2954
|
-
const parsed = JSON.parse(
|
|
3308
|
+
const parsed = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
2955
3309
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
2956
3310
|
return parsed.version;
|
|
2957
3311
|
}
|
|
@@ -3010,7 +3364,7 @@ function parseMinVersion(spec) {
|
|
|
3010
3364
|
function readEnginesNode() {
|
|
3011
3365
|
try {
|
|
3012
3366
|
const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
|
|
3013
|
-
const parsed = JSON.parse(
|
|
3367
|
+
const parsed = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
3014
3368
|
const node = parsed.engines?.node;
|
|
3015
3369
|
if (typeof node === "string" && node.length > 0) return node;
|
|
3016
3370
|
return null;
|
|
@@ -3038,43 +3392,44 @@ function reportNodeEngineCheck(section2) {
|
|
|
3038
3392
|
|
|
3039
3393
|
// src/spinner.ts
|
|
3040
3394
|
init_color();
|
|
3041
|
-
import { existsSync as
|
|
3395
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
3042
3396
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3043
3397
|
import { Worker } from "node:worker_threads";
|
|
3044
3398
|
|
|
3045
3399
|
// src/commands.push.recovery.ts
|
|
3046
3400
|
init_config();
|
|
3047
|
-
import { readFileSync as readFileSync13, rmSync as
|
|
3048
|
-
import { join as
|
|
3049
|
-
import { createInterface } from "node:readline/promises";
|
|
3401
|
+
import { readFileSync as readFileSync13, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3402
|
+
import { join as join33 } from "node:path";
|
|
3403
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
3050
3404
|
|
|
3051
3405
|
// src/commands.push.recovery.actions.ts
|
|
3052
3406
|
init_config();
|
|
3053
3407
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
3054
|
-
import { isAbsolute, resolve as resolve3, sep as
|
|
3408
|
+
import { isAbsolute, resolve as resolve3, sep as sep5 } from "node:path";
|
|
3055
3409
|
|
|
3056
3410
|
// src/commands.push.recovery.redact.ts
|
|
3057
3411
|
init_config();
|
|
3058
3412
|
init_config_sharedDirs_guard();
|
|
3059
|
-
import { cpSync as cpSync5, existsSync as
|
|
3060
|
-
import { dirname as dirname6, join as
|
|
3413
|
+
import { cpSync as cpSync5, existsSync as existsSync26, mkdirSync as mkdirSync6, statSync as statSync7 } from "node:fs";
|
|
3414
|
+
import { dirname as dirname6, join as join31, sep as sep4 } from "node:path";
|
|
3061
3415
|
|
|
3062
3416
|
// src/commands.redact.ts
|
|
3063
3417
|
init_config();
|
|
3064
|
-
import { existsSync as
|
|
3065
|
-
import { dirname as dirname5, join as
|
|
3418
|
+
import { existsSync as existsSync25, statSync as statSync6 } from "node:fs";
|
|
3419
|
+
import { dirname as dirname5, join as join30 } from "node:path";
|
|
3066
3420
|
|
|
3067
3421
|
// src/commands.redact.subtree.ts
|
|
3068
|
-
import { existsSync as
|
|
3069
|
-
import { join as
|
|
3422
|
+
import { existsSync as existsSync24, lstatSync as lstatSync8, readFileSync as readFileSync11, readdirSync as readdirSync9, statSync as statSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3423
|
+
import { join as join29 } from "node:path";
|
|
3070
3424
|
init_utils_fs();
|
|
3425
|
+
init_utils();
|
|
3071
3426
|
function collectFiles(dir, out) {
|
|
3072
|
-
if (!
|
|
3073
|
-
const st =
|
|
3427
|
+
if (!existsSync24(dir)) return;
|
|
3428
|
+
const st = lstatSync8(dir);
|
|
3074
3429
|
if (!st.isDirectory()) return;
|
|
3075
3430
|
for (const entry of readdirSync9(dir)) {
|
|
3076
|
-
const abs =
|
|
3077
|
-
const lst =
|
|
3431
|
+
const abs = join29(dir, entry);
|
|
3432
|
+
const lst = lstatSync8(abs);
|
|
3078
3433
|
if (lst.isSymbolicLink()) continue;
|
|
3079
3434
|
if (lst.isDirectory()) {
|
|
3080
3435
|
collectFiles(abs, out);
|
|
@@ -3110,143 +3465,83 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
3110
3465
|
if (!dryRun && total > 0) {
|
|
3111
3466
|
for (const { path: filePath, findings } of dirty) {
|
|
3112
3467
|
backupBeforeWrite(filePath, ts);
|
|
3113
|
-
|
|
3468
|
+
const before = readFileSync11(filePath, "utf8");
|
|
3469
|
+
const after = applyRedactions(before, findings);
|
|
3470
|
+
if (after === before) {
|
|
3471
|
+
log(
|
|
3472
|
+
`warning: no redaction applied to ${filePath}: finding match values were not located in the file. Inspect it manually; the push re-scan still blocks a real leak.`
|
|
3473
|
+
);
|
|
3474
|
+
}
|
|
3475
|
+
writeFileSync4(filePath, after, "utf8");
|
|
3114
3476
|
}
|
|
3115
3477
|
}
|
|
3116
3478
|
return { total, dirty };
|
|
3117
3479
|
}
|
|
3118
3480
|
|
|
3119
|
-
// src/commands.
|
|
3120
|
-
init_push_gitleaks_scan();
|
|
3121
|
-
init_utils_fs();
|
|
3122
|
-
init_utils_json();
|
|
3123
|
-
init_utils();
|
|
3124
|
-
|
|
3125
|
-
// src/utils.lockfile.ts
|
|
3126
|
-
init_config();
|
|
3481
|
+
// src/commands.pushed-history.ts
|
|
3127
3482
|
init_utils();
|
|
3128
|
-
import {
|
|
3129
|
-
|
|
3130
|
-
function lockFilePath() {
|
|
3131
|
-
return join27(home(), ".cache", "claude-nomad", "nomad.lock");
|
|
3132
|
-
}
|
|
3133
|
-
function acquireLock(verb) {
|
|
3134
|
-
const lp = lockFilePath();
|
|
3135
|
-
mkdirSync5(dirname4(lp), { recursive: true });
|
|
3136
|
-
try {
|
|
3137
|
-
const fd = openSync3(lp, "wx");
|
|
3138
|
-
try {
|
|
3139
|
-
writeFileSync4(fd, String(process.pid));
|
|
3140
|
-
} catch (writeErr) {
|
|
3141
|
-
try {
|
|
3142
|
-
closeSync3(fd);
|
|
3143
|
-
} catch {
|
|
3144
|
-
}
|
|
3145
|
-
try {
|
|
3146
|
-
unlinkSync(lp);
|
|
3147
|
-
} catch {
|
|
3148
|
-
}
|
|
3149
|
-
throw writeErr;
|
|
3150
|
-
}
|
|
3151
|
-
return { fd, path: lp };
|
|
3152
|
-
} catch (err) {
|
|
3153
|
-
const code = err.code;
|
|
3154
|
-
if (code !== "EEXIST") throw err;
|
|
3155
|
-
return checkStaleAndRetry(verb, lp);
|
|
3156
|
-
}
|
|
3157
|
-
}
|
|
3158
|
-
function releaseLock(handle) {
|
|
3159
|
-
if (handle === null) return;
|
|
3160
|
-
const lp = handle.path;
|
|
3483
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
3484
|
+
function pushedRef(repo) {
|
|
3161
3485
|
try {
|
|
3162
|
-
|
|
3486
|
+
const ref = execFileSync8("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
|
|
3487
|
+
cwd: repo,
|
|
3488
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3489
|
+
}).toString().trim();
|
|
3490
|
+
return ref.length > 0 ? ref : null;
|
|
3163
3491
|
} catch {
|
|
3164
|
-
|
|
3165
|
-
try {
|
|
3166
|
-
unlinkSync(lp);
|
|
3167
|
-
} catch (err) {
|
|
3168
|
-
if (err.code !== "ENOENT") throw err;
|
|
3492
|
+
return null;
|
|
3169
3493
|
}
|
|
3170
3494
|
}
|
|
3171
|
-
function
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
current = readFileSync11(lp, "utf8").trim();
|
|
3175
|
-
} catch {
|
|
3176
|
-
return false;
|
|
3177
|
-
}
|
|
3178
|
-
if (current !== expectedPidStr) return false;
|
|
3495
|
+
function sessionInPushedHistory(id, repo) {
|
|
3496
|
+
const ref = pushedRef(repo);
|
|
3497
|
+
if (ref === null) return false;
|
|
3179
3498
|
try {
|
|
3180
|
-
|
|
3181
|
-
|
|
3499
|
+
const out = execFileSync8(
|
|
3500
|
+
"git",
|
|
3501
|
+
[
|
|
3502
|
+
"log",
|
|
3503
|
+
ref,
|
|
3504
|
+
"--oneline",
|
|
3505
|
+
"-1",
|
|
3506
|
+
"--",
|
|
3507
|
+
`shared/projects/*/${id}.jsonl`,
|
|
3508
|
+
`shared/projects/*/${id}/*`
|
|
3509
|
+
],
|
|
3510
|
+
{ cwd: repo, stdio: ["ignore", "pipe", "ignore"] }
|
|
3511
|
+
).toString().trim();
|
|
3512
|
+
return out.length > 0;
|
|
3182
3513
|
} catch {
|
|
3183
3514
|
return false;
|
|
3184
3515
|
}
|
|
3185
3516
|
}
|
|
3186
|
-
function
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
3197
|
-
return null;
|
|
3198
|
-
}
|
|
3199
|
-
try {
|
|
3200
|
-
process.kill(pid, 0);
|
|
3201
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
3202
|
-
return null;
|
|
3203
|
-
} catch (err) {
|
|
3204
|
-
const code = err.code;
|
|
3205
|
-
if (code === "ESRCH") {
|
|
3206
|
-
if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
|
|
3207
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
3208
|
-
return null;
|
|
3209
|
-
}
|
|
3210
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
3211
|
-
return null;
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
function retryOnce(verb, lp) {
|
|
3215
|
-
try {
|
|
3216
|
-
const fd = openSync3(lp, "wx");
|
|
3217
|
-
try {
|
|
3218
|
-
writeFileSync4(fd, String(process.pid));
|
|
3219
|
-
} catch {
|
|
3220
|
-
try {
|
|
3221
|
-
closeSync3(fd);
|
|
3222
|
-
} catch {
|
|
3223
|
-
}
|
|
3224
|
-
try {
|
|
3225
|
-
unlinkSync(lp);
|
|
3226
|
-
} catch {
|
|
3227
|
-
}
|
|
3228
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
3229
|
-
return null;
|
|
3230
|
-
}
|
|
3231
|
-
return { fd, path: lp };
|
|
3232
|
-
} catch {
|
|
3233
|
-
warn(`another nomad ${verb} running, skipping`);
|
|
3234
|
-
return null;
|
|
3235
|
-
}
|
|
3517
|
+
function warnIfSessionPushed(id, repo) {
|
|
3518
|
+
if (!sessionInPushedHistory(id, repo)) return;
|
|
3519
|
+
log(
|
|
3520
|
+
`warning: session ${id} is already in pushed history (origin).
|
|
3521
|
+
This command only changes your local copy and the next push; it does NOT
|
|
3522
|
+
remove the secret from commits already on the remote.
|
|
3523
|
+
To fully remediate a real secret: rotate the credential, then rewrite
|
|
3524
|
+
history (e.g. with git filter-repo) and force-push, coordinating with
|
|
3525
|
+
anyone else who has cloned the repo.`
|
|
3526
|
+
);
|
|
3236
3527
|
}
|
|
3237
3528
|
|
|
3238
3529
|
// src/commands.redact.ts
|
|
3530
|
+
init_push_gitleaks_scan();
|
|
3531
|
+
init_utils_fs();
|
|
3532
|
+
init_utils_json();
|
|
3533
|
+
init_utils();
|
|
3239
3534
|
function resolveLiveTranscript(id) {
|
|
3240
3535
|
try {
|
|
3241
|
-
const mapPath =
|
|
3242
|
-
if (!
|
|
3536
|
+
const mapPath = join30(repoHome(), "path-map.json");
|
|
3537
|
+
if (!existsSync25(mapPath)) return null;
|
|
3243
3538
|
const projects = readJson(mapPath).projects;
|
|
3244
3539
|
const claude = claudeHome();
|
|
3245
3540
|
for (const hostMap of Object.values(projects)) {
|
|
3246
3541
|
const abs = hostMap[HOST];
|
|
3247
3542
|
if (abs === void 0) continue;
|
|
3248
|
-
const live =
|
|
3249
|
-
if (
|
|
3543
|
+
const live = join30(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
3544
|
+
if (existsSync25(live)) return live;
|
|
3250
3545
|
}
|
|
3251
3546
|
return null;
|
|
3252
3547
|
} catch {
|
|
@@ -3266,17 +3561,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
3266
3561
|
}
|
|
3267
3562
|
const repo = repoHome();
|
|
3268
3563
|
const backup = backupBase();
|
|
3269
|
-
if (!
|
|
3564
|
+
if (!existsSync25(repo)) die(`repo not cloned at ${repo}`);
|
|
3270
3565
|
const handle = acquireLock("redact");
|
|
3271
3566
|
if (handle === null) process.exit(0);
|
|
3272
3567
|
try {
|
|
3273
3568
|
const localPath = resolveLiveTranscript(id);
|
|
3274
|
-
if (localPath === null || !
|
|
3569
|
+
if (localPath === null || !existsSync25(localPath)) {
|
|
3275
3570
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
3276
3571
|
process.exitCode = 1;
|
|
3277
3572
|
return;
|
|
3278
3573
|
}
|
|
3279
|
-
const sessionDir =
|
|
3574
|
+
const sessionDir = join30(dirname5(localPath), id);
|
|
3280
3575
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3281
3576
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync6(p).mtimeMs);
|
|
3282
3577
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3317,6 +3612,7 @@ ${lines}`);
|
|
|
3317
3612
|
return;
|
|
3318
3613
|
}
|
|
3319
3614
|
log(`redacted ${totalCount} finding(s) in ${localPath} (backup: ${ts})`);
|
|
3615
|
+
warnIfSessionPushed(id, repo);
|
|
3320
3616
|
} catch (err) {
|
|
3321
3617
|
if (!(err instanceof NomadFatal)) {
|
|
3322
3618
|
throw err;
|
|
@@ -3391,12 +3687,28 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
3391
3687
|
assertSafeLogical(logical);
|
|
3392
3688
|
const abs = hostMap[HOST];
|
|
3393
3689
|
if (abs === void 0) continue;
|
|
3394
|
-
if (localPath.startsWith(
|
|
3395
|
-
return
|
|
3690
|
+
if (localPath.startsWith(join31(claude, "projects", encodePath(abs)) + sep4)) {
|
|
3691
|
+
return join31(repo, "shared", "projects", logical);
|
|
3396
3692
|
}
|
|
3397
3693
|
}
|
|
3398
3694
|
return null;
|
|
3399
3695
|
}
|
|
3696
|
+
function preflightRedactable(f, map, nowMs) {
|
|
3697
|
+
const sid = sessionIdFromFinding(f);
|
|
3698
|
+
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
3699
|
+
const localPath = resolveLiveTranscript(sid);
|
|
3700
|
+
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
3701
|
+
const sessionDir = join31(dirname6(localPath), sid);
|
|
3702
|
+
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3703
|
+
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
3704
|
+
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
3705
|
+
return `session ${sid}: looks active (modified within the last 5 minutes)`;
|
|
3706
|
+
}
|
|
3707
|
+
if (resolveStagedDir(localPath, map, claudeHome(), repoHome()) === null) {
|
|
3708
|
+
return `session ${sid}: not mapped to a staged copy`;
|
|
3709
|
+
}
|
|
3710
|
+
return null;
|
|
3711
|
+
}
|
|
3400
3712
|
function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
3401
3713
|
const refuse = (msg) => {
|
|
3402
3714
|
log(msg);
|
|
@@ -3416,7 +3728,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3416
3728
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
3417
3729
|
);
|
|
3418
3730
|
}
|
|
3419
|
-
const sessionDir =
|
|
3731
|
+
const sessionDir = join31(dirname6(localPath), sid);
|
|
3420
3732
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3421
3733
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
3422
3734
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3450,26 +3762,26 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3450
3762
|
);
|
|
3451
3763
|
}
|
|
3452
3764
|
mkdirSync6(stagedProjectDir, { recursive: true });
|
|
3453
|
-
cpSync5(localPath,
|
|
3454
|
-
if (
|
|
3455
|
-
cpSync5(sessionDir,
|
|
3765
|
+
cpSync5(localPath, join31(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
3766
|
+
if (existsSync26(sessionDir)) {
|
|
3767
|
+
cpSync5(sessionDir, join31(stagedProjectDir, sid), { force: true, recursive: true });
|
|
3456
3768
|
}
|
|
3457
3769
|
return true;
|
|
3458
3770
|
}
|
|
3459
3771
|
|
|
3460
3772
|
// src/commands.push.recovery.drop.ts
|
|
3461
3773
|
init_config();
|
|
3462
|
-
import { rmSync as
|
|
3463
|
-
import { join as
|
|
3774
|
+
import { rmSync as rmSync9 } from "node:fs";
|
|
3775
|
+
import { join as join32 } from "node:path";
|
|
3464
3776
|
function dropSessionFromStaged(sid, map) {
|
|
3465
3777
|
const logicals = Object.keys(map.projects);
|
|
3466
3778
|
if (logicals.length === 0) return false;
|
|
3467
3779
|
const repo = repoHome();
|
|
3468
3780
|
for (const logical of logicals) {
|
|
3469
|
-
const jsonl =
|
|
3470
|
-
const dir =
|
|
3471
|
-
|
|
3472
|
-
|
|
3781
|
+
const jsonl = join32(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
3782
|
+
const dir = join32(repo, "shared", "projects", logical, sid);
|
|
3783
|
+
rmSync9(jsonl, { force: true });
|
|
3784
|
+
rmSync9(dir, { recursive: true, force: true });
|
|
3473
3785
|
}
|
|
3474
3786
|
return true;
|
|
3475
3787
|
}
|
|
@@ -3500,7 +3812,7 @@ function makeDefaultReadLine(repo) {
|
|
|
3500
3812
|
try {
|
|
3501
3813
|
const repoRoot = resolve3(repo);
|
|
3502
3814
|
const target = resolve3(repoRoot, file);
|
|
3503
|
-
if (isAbsolute(file) || target !== repoRoot && !target.startsWith(repoRoot +
|
|
3815
|
+
if (isAbsolute(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
3504
3816
|
return null;
|
|
3505
3817
|
}
|
|
3506
3818
|
const content = readFileSync12(target, "utf8");
|
|
@@ -3567,6 +3879,22 @@ function dispatchActions(findings, actions, opts) {
|
|
|
3567
3879
|
}
|
|
3568
3880
|
}
|
|
3569
3881
|
function redactAllFindings(findings, ts, map, nowMs, scan = scanFile) {
|
|
3882
|
+
const refusals = [];
|
|
3883
|
+
const preflighted = /* @__PURE__ */ new Set();
|
|
3884
|
+
for (const f of findings) {
|
|
3885
|
+
const dedupeKey = sessionIdFromFinding(f) ?? findingKey(f);
|
|
3886
|
+
if (preflighted.has(dedupeKey)) continue;
|
|
3887
|
+
preflighted.add(dedupeKey);
|
|
3888
|
+
const reason = preflightRedactable(f, map, nowMs);
|
|
3889
|
+
if (reason !== null) refusals.push(reason);
|
|
3890
|
+
}
|
|
3891
|
+
if (refusals.length > 0) {
|
|
3892
|
+
throw new NomadFatal(
|
|
3893
|
+
`--redact-all cannot redact every finding, so no changes were made:
|
|
3894
|
+
` + refusals.map((r) => ` - ${r}`).join("\n") + `
|
|
3895
|
+
Re-run without --redact-all to triage these interactively (Drop session / Skip), or end any active session and retry.`
|
|
3896
|
+
);
|
|
3897
|
+
}
|
|
3570
3898
|
const redactedSids = /* @__PURE__ */ new Set();
|
|
3571
3899
|
for (const f of findings) {
|
|
3572
3900
|
const sid = sessionIdFromFinding(f);
|
|
@@ -3608,7 +3936,7 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
3608
3936
|
return next;
|
|
3609
3937
|
}
|
|
3610
3938
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
3611
|
-
const ignPath =
|
|
3939
|
+
const ignPath = join33(repoHome2, ".gitleaksignore");
|
|
3612
3940
|
let before;
|
|
3613
3941
|
try {
|
|
3614
3942
|
before = readFileSync13(ignPath, "utf8");
|
|
@@ -3619,14 +3947,14 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
|
3619
3947
|
try {
|
|
3620
3948
|
return applyThenRescan(scanVerdict, repoHome2);
|
|
3621
3949
|
} catch (err) {
|
|
3622
|
-
if (before === null)
|
|
3950
|
+
if (before === null) rmSync10(ignPath, { force: true });
|
|
3623
3951
|
else writeFileSync5(ignPath, before, "utf8");
|
|
3624
3952
|
throw err;
|
|
3625
3953
|
}
|
|
3626
3954
|
}
|
|
3627
3955
|
function makeRealPrompt() {
|
|
3628
3956
|
return async (prompt) => {
|
|
3629
|
-
const rl =
|
|
3957
|
+
const rl = createInterface2({
|
|
3630
3958
|
input: process.stdin,
|
|
3631
3959
|
output: process.stdout,
|
|
3632
3960
|
terminal: true
|
|
@@ -3707,7 +4035,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
3707
4035
|
`);
|
|
3708
4036
|
}
|
|
3709
4037
|
function resolveWorkerPath(deps = {}) {
|
|
3710
|
-
const check = deps.existsSyncFn ??
|
|
4038
|
+
const check = deps.existsSyncFn ?? existsSync27;
|
|
3711
4039
|
const base = deps.baseUrl ?? import.meta.url;
|
|
3712
4040
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
3713
4041
|
if (check(mjs)) return mjs;
|
|
@@ -3773,9 +4101,9 @@ function withSpinner(label, fn, deps) {
|
|
|
3773
4101
|
|
|
3774
4102
|
// src/commands.doctor.gitleaks-version.ts
|
|
3775
4103
|
init_color();
|
|
3776
|
-
import { execFileSync as
|
|
3777
|
-
import { existsSync as
|
|
3778
|
-
import { join as
|
|
4104
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
4105
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
4106
|
+
import { join as join34 } from "node:path";
|
|
3779
4107
|
init_config();
|
|
3780
4108
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
3781
4109
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -3784,7 +4112,7 @@ function majorMinorOf(value) {
|
|
|
3784
4112
|
return m === null ? null : [m[1], m[2]];
|
|
3785
4113
|
}
|
|
3786
4114
|
function readGitleaksVersion(run, tomlExists) {
|
|
3787
|
-
const tomlPath =
|
|
4115
|
+
const tomlPath = join34(repoHome(), ".gitleaks.toml");
|
|
3788
4116
|
const args = ["version"];
|
|
3789
4117
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
3790
4118
|
try {
|
|
@@ -3796,7 +4124,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
3796
4124
|
return null;
|
|
3797
4125
|
}
|
|
3798
4126
|
}
|
|
3799
|
-
function reportGitleaksVersionCheck(section2, run =
|
|
4127
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync9, tomlExists = existsSync28) {
|
|
3800
4128
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
3801
4129
|
if (raw === null) return;
|
|
3802
4130
|
const local = majorMinorOf(raw);
|
|
@@ -3816,7 +4144,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync8, tomlExists =
|
|
|
3816
4144
|
|
|
3817
4145
|
// src/commands.doctor.checks.deps.ts
|
|
3818
4146
|
init_color();
|
|
3819
|
-
import { execFileSync as
|
|
4147
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3820
4148
|
var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
|
|
3821
4149
|
var PROBE_TIMEOUT_MS = 3e3;
|
|
3822
4150
|
var FETCHER_BASE = "HTTP fetcher";
|
|
@@ -3853,7 +4181,7 @@ function reportFetcherRow(section2, run) {
|
|
|
3853
4181
|
);
|
|
3854
4182
|
}
|
|
3855
4183
|
}
|
|
3856
|
-
function reportOptionalDeps(section2, run =
|
|
4184
|
+
function reportOptionalDeps(section2, run = execFileSync10) {
|
|
3857
4185
|
const gh = probeOptionalDep("gh", run);
|
|
3858
4186
|
if (gh.status === "present") {
|
|
3859
4187
|
addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
|
|
@@ -3868,11 +4196,11 @@ function reportOptionalDeps(section2, run = execFileSync9) {
|
|
|
3868
4196
|
|
|
3869
4197
|
// src/commands.doctor.actions-drift.ts
|
|
3870
4198
|
init_color();
|
|
3871
|
-
import { execFileSync as
|
|
4199
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
3872
4200
|
init_config();
|
|
3873
4201
|
|
|
3874
4202
|
// src/gh-actions.ts
|
|
3875
|
-
import { execFileSync as
|
|
4203
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
3876
4204
|
var GH_TIMEOUT_MS = 5e3;
|
|
3877
4205
|
function parseGitHubRemote(remoteUrl) {
|
|
3878
4206
|
const normalized = remoteUrl.trim().replace(/\/$/, "");
|
|
@@ -3880,7 +4208,7 @@ function parseGitHubRemote(remoteUrl) {
|
|
|
3880
4208
|
if (m === null) return null;
|
|
3881
4209
|
return { owner: m[1], repo: m[2] };
|
|
3882
4210
|
}
|
|
3883
|
-
function ghAuthStatus(run =
|
|
4211
|
+
function ghAuthStatus(run = execFileSync11) {
|
|
3884
4212
|
try {
|
|
3885
4213
|
run("gh", ["auth", "status"], {
|
|
3886
4214
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -3894,7 +4222,7 @@ function ghAuthStatus(run = execFileSync10) {
|
|
|
3894
4222
|
return "gh-probe-error";
|
|
3895
4223
|
}
|
|
3896
4224
|
}
|
|
3897
|
-
function isRepoPrivate(ref, run =
|
|
4225
|
+
function isRepoPrivate(ref, run = execFileSync11) {
|
|
3898
4226
|
const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
|
|
3899
4227
|
stdio: ["ignore", "pipe", "ignore"],
|
|
3900
4228
|
timeout: GH_TIMEOUT_MS
|
|
@@ -3902,7 +4230,7 @@ function isRepoPrivate(ref, run = execFileSync10) {
|
|
|
3902
4230
|
const parsed = JSON.parse(out);
|
|
3903
4231
|
return parsed.isPrivate === true;
|
|
3904
4232
|
}
|
|
3905
|
-
function isActionsEnabled(ref, run =
|
|
4233
|
+
function isActionsEnabled(ref, run = execFileSync11) {
|
|
3906
4234
|
const out = run(
|
|
3907
4235
|
"gh",
|
|
3908
4236
|
["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
|
|
@@ -3910,7 +4238,7 @@ function isActionsEnabled(ref, run = execFileSync10) {
|
|
|
3910
4238
|
).toString().trim();
|
|
3911
4239
|
return out === "true";
|
|
3912
4240
|
}
|
|
3913
|
-
function disableActions(ref, run =
|
|
4241
|
+
function disableActions(ref, run = execFileSync11) {
|
|
3914
4242
|
run(
|
|
3915
4243
|
"gh",
|
|
3916
4244
|
[
|
|
@@ -3924,7 +4252,7 @@ function disableActions(ref, run = execFileSync10) {
|
|
|
3924
4252
|
{ stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
|
|
3925
4253
|
);
|
|
3926
4254
|
}
|
|
3927
|
-
function readOriginRemote(cwd, run =
|
|
4255
|
+
function readOriginRemote(cwd, run = execFileSync11) {
|
|
3928
4256
|
return run("git", ["remote", "get-url", "origin"], {
|
|
3929
4257
|
cwd,
|
|
3930
4258
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -3932,7 +4260,7 @@ function readOriginRemote(cwd, run = execFileSync10) {
|
|
|
3932
4260
|
}
|
|
3933
4261
|
|
|
3934
4262
|
// src/commands.doctor.actions-drift.ts
|
|
3935
|
-
function reportActionsDrift(section2, run =
|
|
4263
|
+
function reportActionsDrift(section2, run = execFileSync12) {
|
|
3936
4264
|
let remote;
|
|
3937
4265
|
try {
|
|
3938
4266
|
remote = readOriginRemote(repoHome(), run);
|
|
@@ -3993,8 +4321,8 @@ function gatherDoctorSections(opts) {
|
|
|
3993
4321
|
reportHostAndPaths(host);
|
|
3994
4322
|
reportRepoState(host);
|
|
3995
4323
|
const links = section("Shared links");
|
|
3996
|
-
const mapPath =
|
|
3997
|
-
const rawMap =
|
|
4324
|
+
const mapPath = join35(repoHome(), "path-map.json");
|
|
4325
|
+
const rawMap = existsSync29(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
3998
4326
|
const map = rawMap ?? { projects: {} };
|
|
3999
4327
|
reportSharedLinks(links, map);
|
|
4000
4328
|
reportDroppedNamesMigration(links);
|
|
@@ -4061,15 +4389,15 @@ function cmdDoctor(opts = {}) {
|
|
|
4061
4389
|
|
|
4062
4390
|
// src/commands.drop-session.ts
|
|
4063
4391
|
init_config();
|
|
4064
|
-
import { execFileSync as
|
|
4065
|
-
import { existsSync as
|
|
4066
|
-
import { join as
|
|
4392
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
4393
|
+
import { existsSync as existsSync31, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
4394
|
+
import { join as join37, relative as relative4 } from "node:path";
|
|
4067
4395
|
|
|
4068
4396
|
// src/commands.drop-session.git.ts
|
|
4069
|
-
import { execFileSync as
|
|
4397
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
4070
4398
|
function expandStagedDir(dirRel, repo) {
|
|
4071
4399
|
try {
|
|
4072
|
-
const out =
|
|
4400
|
+
const out = execFileSync13("git", ["ls-files", "-z", "--", dirRel], {
|
|
4073
4401
|
cwd: repo,
|
|
4074
4402
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4075
4403
|
});
|
|
@@ -4080,7 +4408,7 @@ function expandStagedDir(dirRel, repo) {
|
|
|
4080
4408
|
}
|
|
4081
4409
|
function isTrackedInHead(rel, repo) {
|
|
4082
4410
|
try {
|
|
4083
|
-
|
|
4411
|
+
execFileSync13("git", ["cat-file", "-e", `HEAD:${rel}`], {
|
|
4084
4412
|
cwd: repo,
|
|
4085
4413
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4086
4414
|
});
|
|
@@ -4091,7 +4419,7 @@ function isTrackedInHead(rel, repo) {
|
|
|
4091
4419
|
}
|
|
4092
4420
|
function isInIndex(rel, repo) {
|
|
4093
4421
|
try {
|
|
4094
|
-
const out =
|
|
4422
|
+
const out = execFileSync13("git", ["ls-files", "--", rel], {
|
|
4095
4423
|
cwd: repo,
|
|
4096
4424
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4097
4425
|
});
|
|
@@ -4105,8 +4433,8 @@ function isInIndex(rel, repo) {
|
|
|
4105
4433
|
init_config();
|
|
4106
4434
|
init_utils();
|
|
4107
4435
|
init_utils_json();
|
|
4108
|
-
import { existsSync as
|
|
4109
|
-
import { join as
|
|
4436
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
4437
|
+
import { join as join36 } from "node:path";
|
|
4110
4438
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
4111
4439
|
function reportScrubHint(id, matches) {
|
|
4112
4440
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -4122,8 +4450,8 @@ function reportScrubHint(id, matches) {
|
|
|
4122
4450
|
}
|
|
4123
4451
|
function resolveLiveTranscript2(id, matches) {
|
|
4124
4452
|
try {
|
|
4125
|
-
const mapPath =
|
|
4126
|
-
if (!
|
|
4453
|
+
const mapPath = join36(repoHome(), "path-map.json");
|
|
4454
|
+
if (!existsSync30(mapPath)) return null;
|
|
4127
4455
|
const projects = readJson(mapPath).projects;
|
|
4128
4456
|
const claude = claudeHome();
|
|
4129
4457
|
for (const rel of matches) {
|
|
@@ -4131,8 +4459,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
4131
4459
|
if (logical === void 0) continue;
|
|
4132
4460
|
const abs = projects[logical]?.[HOST];
|
|
4133
4461
|
if (abs === void 0) continue;
|
|
4134
|
-
const live =
|
|
4135
|
-
if (
|
|
4462
|
+
const live = join36(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
4463
|
+
if (existsSync30(live)) return live;
|
|
4136
4464
|
}
|
|
4137
4465
|
return null;
|
|
4138
4466
|
} catch {
|
|
@@ -4148,12 +4476,12 @@ function cmdDropSession(id) {
|
|
|
4148
4476
|
process.exit(1);
|
|
4149
4477
|
}
|
|
4150
4478
|
const repo = repoHome();
|
|
4151
|
-
if (!
|
|
4479
|
+
if (!existsSync31(repo)) die(`repo not cloned at ${repo}`);
|
|
4152
4480
|
const handle = acquireLock("drop-session");
|
|
4153
4481
|
if (handle === null) process.exit(0);
|
|
4154
4482
|
try {
|
|
4155
|
-
const repoProjects =
|
|
4156
|
-
if (!
|
|
4483
|
+
const repoProjects = join37(repo, "shared", "projects");
|
|
4484
|
+
if (!existsSync31(repoProjects)) {
|
|
4157
4485
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
4158
4486
|
}
|
|
4159
4487
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -4162,6 +4490,7 @@ function cmdDropSession(id) {
|
|
|
4162
4490
|
}
|
|
4163
4491
|
for (const rel of matches) unstageOne(rel, repo);
|
|
4164
4492
|
reportScrubHint(id, matches);
|
|
4493
|
+
warnIfSessionPushed(id, repo);
|
|
4165
4494
|
} catch (err) {
|
|
4166
4495
|
if (!(err instanceof NomadFatal)) {
|
|
4167
4496
|
throw err;
|
|
@@ -4175,12 +4504,12 @@ function cmdDropSession(id) {
|
|
|
4175
4504
|
function collectMatches(repoProjects, id, repo) {
|
|
4176
4505
|
const matches = [];
|
|
4177
4506
|
for (const logical of readdirSync10(repoProjects)) {
|
|
4178
|
-
const candidate =
|
|
4179
|
-
if (
|
|
4507
|
+
const candidate = join37(repoProjects, logical, `${id}.jsonl`);
|
|
4508
|
+
if (existsSync31(candidate)) {
|
|
4180
4509
|
matches.push(relative4(repo, candidate));
|
|
4181
4510
|
}
|
|
4182
|
-
const dir =
|
|
4183
|
-
if (
|
|
4511
|
+
const dir = join37(repoProjects, logical, id);
|
|
4512
|
+
if (existsSync31(dir) && statSync8(dir).isDirectory()) {
|
|
4184
4513
|
const dirRel = relative4(repo, dir);
|
|
4185
4514
|
const staged = expandStagedDir(dirRel, repo);
|
|
4186
4515
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -4196,12 +4525,12 @@ function unstageOne(rel, repo) {
|
|
|
4196
4525
|
}
|
|
4197
4526
|
try {
|
|
4198
4527
|
if (isTrackedInHead(rel, repo)) {
|
|
4199
|
-
|
|
4528
|
+
execFileSync14("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
4200
4529
|
cwd: repo,
|
|
4201
4530
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4202
4531
|
});
|
|
4203
4532
|
} else {
|
|
4204
|
-
|
|
4533
|
+
execFileSync14("git", ["rm", "--cached", "-f", "--", rel], {
|
|
4205
4534
|
cwd: repo,
|
|
4206
4535
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4207
4536
|
});
|
|
@@ -4215,8 +4544,8 @@ function unstageOne(rel, repo) {
|
|
|
4215
4544
|
}
|
|
4216
4545
|
|
|
4217
4546
|
// src/commands.pull.ts
|
|
4218
|
-
import { existsSync as
|
|
4219
|
-
import { join as
|
|
4547
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync9 } from "node:fs";
|
|
4548
|
+
import { join as join44 } from "node:path";
|
|
4220
4549
|
|
|
4221
4550
|
// src/commands.push.sections.ts
|
|
4222
4551
|
init_color();
|
|
@@ -4311,12 +4640,12 @@ init_config();
|
|
|
4311
4640
|
|
|
4312
4641
|
// src/extras-sync.ts
|
|
4313
4642
|
init_config();
|
|
4314
|
-
import { existsSync as
|
|
4315
|
-
import { join as
|
|
4643
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
4644
|
+
import { join as join41 } from "node:path";
|
|
4316
4645
|
|
|
4317
4646
|
// src/extras-sync.diff.ts
|
|
4318
4647
|
init_utils();
|
|
4319
|
-
import { execFileSync as
|
|
4648
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4320
4649
|
function labelDiffLine(line) {
|
|
4321
4650
|
const tab = line.indexOf(" ");
|
|
4322
4651
|
if (tab === -1) return line;
|
|
@@ -4331,7 +4660,7 @@ function parseDiffOutput(stdout) {
|
|
|
4331
4660
|
}
|
|
4332
4661
|
function listDivergingFiles(a, b) {
|
|
4333
4662
|
try {
|
|
4334
|
-
const stdout =
|
|
4663
|
+
const stdout = execFileSync15("git", ["diff", "--no-index", "--name-status", a, b], {
|
|
4335
4664
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4336
4665
|
}).toString();
|
|
4337
4666
|
return parseDiffOutput(stdout);
|
|
@@ -4351,8 +4680,8 @@ function listDivergingFiles(a, b) {
|
|
|
4351
4680
|
|
|
4352
4681
|
// src/extras-sync.core.ts
|
|
4353
4682
|
init_config();
|
|
4354
|
-
import { cpSync as cpSync6, existsSync as
|
|
4355
|
-
import { basename, join as
|
|
4683
|
+
import { cpSync as cpSync6, existsSync as existsSync32, lstatSync as lstatSync9, readdirSync as readdirSync11, rmSync as rmSync11 } from "node:fs";
|
|
4684
|
+
import { basename, join as join38 } from "node:path";
|
|
4356
4685
|
|
|
4357
4686
|
// src/extras-sync.guards.ts
|
|
4358
4687
|
init_utils();
|
|
@@ -4376,9 +4705,9 @@ init_utils();
|
|
|
4376
4705
|
init_utils_json();
|
|
4377
4706
|
function loadValidatedExtras(opts) {
|
|
4378
4707
|
const repo = repoHome();
|
|
4379
|
-
const mapPath =
|
|
4380
|
-
const repoExtras =
|
|
4381
|
-
if (!
|
|
4708
|
+
const mapPath = join38(repo, "path-map.json");
|
|
4709
|
+
const repoExtras = join38(repo, "shared", "extras");
|
|
4710
|
+
if (!existsSync32(mapPath) || opts.requireRepoExtras === true && !existsSync32(repoExtras)) {
|
|
4382
4711
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
4383
4712
|
return null;
|
|
4384
4713
|
}
|
|
@@ -4428,14 +4757,14 @@ function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
|
4428
4757
|
}
|
|
4429
4758
|
}
|
|
4430
4759
|
function copyExtras(src, dst) {
|
|
4431
|
-
|
|
4760
|
+
rmSync11(dst, { recursive: true, force: true });
|
|
4432
4761
|
cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
4433
4762
|
}
|
|
4434
4763
|
function extrasDenySet(dirname8) {
|
|
4435
4764
|
return dirname8 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
4436
4765
|
}
|
|
4437
4766
|
function copyExtrasFiltered(src, dst, blockSet) {
|
|
4438
|
-
|
|
4767
|
+
rmSync11(dst, { recursive: true, force: true });
|
|
4439
4768
|
cpSync6(src, dst, {
|
|
4440
4769
|
recursive: true,
|
|
4441
4770
|
force: true,
|
|
@@ -4446,25 +4775,25 @@ function copyExtrasFiltered(src, dst, blockSet) {
|
|
|
4446
4775
|
function prunePreservingDenied(src, dst, blockSet) {
|
|
4447
4776
|
for (const name of readdirSync11(dst)) {
|
|
4448
4777
|
if (blockSet.has(name)) continue;
|
|
4449
|
-
const dstPath =
|
|
4450
|
-
const srcStat =
|
|
4778
|
+
const dstPath = join38(dst, name);
|
|
4779
|
+
const srcStat = lstatSync9(join38(src, name), { throwIfNoEntry: false });
|
|
4451
4780
|
if (srcStat === void 0) {
|
|
4452
|
-
|
|
4781
|
+
rmSync11(dstPath, { recursive: true, force: true });
|
|
4453
4782
|
continue;
|
|
4454
4783
|
}
|
|
4455
|
-
const dstStat =
|
|
4784
|
+
const dstStat = lstatSync9(dstPath);
|
|
4456
4785
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
4457
|
-
prunePreservingDenied(
|
|
4786
|
+
prunePreservingDenied(join38(src, name), dstPath, blockSet);
|
|
4458
4787
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
4459
|
-
|
|
4788
|
+
rmSync11(dstPath, { recursive: true, force: true });
|
|
4460
4789
|
}
|
|
4461
4790
|
}
|
|
4462
4791
|
}
|
|
4463
4792
|
function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
4464
|
-
const dstStat =
|
|
4793
|
+
const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
|
|
4465
4794
|
if (dstStat !== void 0) {
|
|
4466
4795
|
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
4467
|
-
else
|
|
4796
|
+
else rmSync11(dst, { recursive: true, force: true });
|
|
4468
4797
|
}
|
|
4469
4798
|
cpSync6(src, dst, {
|
|
4470
4799
|
recursive: true,
|
|
@@ -4476,25 +4805,25 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
4476
4805
|
function prunePreservingBy(src, dst, isPreserved) {
|
|
4477
4806
|
for (const name of readdirSync11(dst)) {
|
|
4478
4807
|
if (isPreserved(name)) continue;
|
|
4479
|
-
const dstPath =
|
|
4480
|
-
const srcStat =
|
|
4808
|
+
const dstPath = join38(dst, name);
|
|
4809
|
+
const srcStat = lstatSync9(join38(src, name), { throwIfNoEntry: false });
|
|
4481
4810
|
if (srcStat === void 0) {
|
|
4482
|
-
|
|
4811
|
+
rmSync11(dstPath, { recursive: true, force: true });
|
|
4483
4812
|
continue;
|
|
4484
4813
|
}
|
|
4485
|
-
const dstStat =
|
|
4814
|
+
const dstStat = lstatSync9(dstPath);
|
|
4486
4815
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
4487
|
-
prunePreservingBy(
|
|
4816
|
+
prunePreservingBy(join38(src, name), dstPath, isPreserved);
|
|
4488
4817
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
4489
|
-
|
|
4818
|
+
rmSync11(dstPath, { recursive: true, force: true });
|
|
4490
4819
|
}
|
|
4491
4820
|
}
|
|
4492
4821
|
}
|
|
4493
4822
|
function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
4494
|
-
const dstStat =
|
|
4823
|
+
const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
|
|
4495
4824
|
if (dstStat !== void 0) {
|
|
4496
4825
|
if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
|
|
4497
|
-
else
|
|
4826
|
+
else rmSync11(dst, { recursive: true, force: true });
|
|
4498
4827
|
}
|
|
4499
4828
|
cpSync6(src, dst, {
|
|
4500
4829
|
recursive: true,
|
|
@@ -4510,11 +4839,11 @@ init_utils_json();
|
|
|
4510
4839
|
|
|
4511
4840
|
// src/extras-sync.remap.ts
|
|
4512
4841
|
init_config();
|
|
4513
|
-
import { existsSync as
|
|
4514
|
-
import { dirname as dirname7, join as
|
|
4842
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync7, readdirSync as readdirSync12, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
|
|
4843
|
+
import { dirname as dirname7, join as join40, sep as sep7 } from "node:path";
|
|
4515
4844
|
|
|
4516
4845
|
// src/extras-sync.planning-diff.ts
|
|
4517
|
-
import { join as
|
|
4846
|
+
import { join as join39, normalize as normalize2, sep as sep6 } from "node:path";
|
|
4518
4847
|
init_utils();
|
|
4519
4848
|
function processRecord(fields, i, changed, deleted) {
|
|
4520
4849
|
const status = fields[i];
|
|
@@ -4566,15 +4895,15 @@ function planningDeleteTargets(opts) {
|
|
|
4566
4895
|
const { deleted } = parsePlanningDiff(raw);
|
|
4567
4896
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
4568
4897
|
const prefix = logicalPrefix + ".planning/";
|
|
4569
|
-
const planningRoot =
|
|
4570
|
-
const planningRootBoundary = planningRoot +
|
|
4898
|
+
const planningRoot = join39(localRoot, ".planning");
|
|
4899
|
+
const planningRootBoundary = planningRoot + sep6;
|
|
4571
4900
|
const targets = [];
|
|
4572
4901
|
for (const repoPath of deleted) {
|
|
4573
4902
|
if (!repoPath.startsWith(prefix)) {
|
|
4574
4903
|
continue;
|
|
4575
4904
|
}
|
|
4576
4905
|
const remainder = repoPath.slice(logicalPrefix.length);
|
|
4577
|
-
const candidate =
|
|
4906
|
+
const candidate = join39(localRoot, remainder);
|
|
4578
4907
|
const resolved = normalize2(candidate);
|
|
4579
4908
|
if (!resolved.startsWith(planningRootBoundary)) {
|
|
4580
4909
|
throw new NomadFatal(
|
|
@@ -4595,7 +4924,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
4595
4924
|
const would = [];
|
|
4596
4925
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
4597
4926
|
const { src, dst } = paths(t);
|
|
4598
|
-
if (!
|
|
4927
|
+
if (!existsSync33(src)) continue;
|
|
4599
4928
|
const item2 = `${t.logical}/${t.dirname}`;
|
|
4600
4929
|
if (dryRun) {
|
|
4601
4930
|
would.push(item2);
|
|
@@ -4609,10 +4938,10 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
4609
4938
|
}
|
|
4610
4939
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
4611
4940
|
let dir = dirname7(target);
|
|
4612
|
-
while (dir !== planningRoot && dir.startsWith(planningRoot +
|
|
4941
|
+
while (dir !== planningRoot && dir.startsWith(planningRoot + sep7)) {
|
|
4613
4942
|
try {
|
|
4614
4943
|
if (readdirSync12(dir).length > 0) break;
|
|
4615
|
-
|
|
4944
|
+
rmSync12(dir, { recursive: true, force: true });
|
|
4616
4945
|
} catch {
|
|
4617
4946
|
break;
|
|
4618
4947
|
}
|
|
@@ -4627,20 +4956,20 @@ function tryRealpath(dir) {
|
|
|
4627
4956
|
}
|
|
4628
4957
|
}
|
|
4629
4958
|
function isInsidePlanningRoot(parentReal, rootReal) {
|
|
4630
|
-
return parentReal === rootReal || parentReal.startsWith(rootReal +
|
|
4959
|
+
return parentReal === rootReal || parentReal.startsWith(rootReal + sep7);
|
|
4631
4960
|
}
|
|
4632
4961
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
4633
|
-
if (
|
|
4962
|
+
if (existsSync33(repoCounterpart)) return;
|
|
4634
4963
|
const parentReal = tryRealpath(dirname7(target));
|
|
4635
4964
|
if (parentReal === void 0) return;
|
|
4636
4965
|
const rootReal = tryRealpath(planningRoot);
|
|
4637
4966
|
if (rootReal === void 0) return;
|
|
4638
4967
|
if (!isInsidePlanningRoot(parentReal, rootReal)) return;
|
|
4639
|
-
|
|
4968
|
+
rmSync12(target, { recursive: true, force: true });
|
|
4640
4969
|
pruneEmptyAncestors(target, planningRoot);
|
|
4641
4970
|
}
|
|
4642
4971
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
4643
|
-
const repoExtras =
|
|
4972
|
+
const repoExtras = join40(repo, "shared", "extras");
|
|
4644
4973
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
4645
4974
|
if (t.dirname !== ".planning") continue;
|
|
4646
4975
|
let raw;
|
|
@@ -4666,11 +4995,11 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
4666
4995
|
}
|
|
4667
4996
|
const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
|
|
4668
4997
|
if (targets.length === 0) continue;
|
|
4669
|
-
backupExtrasWrite(
|
|
4670
|
-
const planningRoot =
|
|
4998
|
+
backupExtrasWrite(join40(t.localRoot, t.dirname), ts, t.localRoot);
|
|
4999
|
+
const planningRoot = join40(t.localRoot, ".planning");
|
|
4671
5000
|
for (const target of targets) {
|
|
4672
|
-
const relToLocal = target.slice(t.localRoot.length +
|
|
4673
|
-
deletePlanningTarget(target, planningRoot,
|
|
5001
|
+
const relToLocal = target.slice(t.localRoot.length + sep7.length);
|
|
5002
|
+
deletePlanningTarget(target, planningRoot, join40(repoExtras, t.logical, relToLocal));
|
|
4674
5003
|
}
|
|
4675
5004
|
}
|
|
4676
5005
|
}
|
|
@@ -4679,14 +5008,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
4679
5008
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
4680
5009
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
4681
5010
|
const repo = repoHome();
|
|
4682
|
-
const repoExtras =
|
|
5011
|
+
const repoExtras = join40(repo, "shared", "extras");
|
|
4683
5012
|
if (!dryRun) mkdirSync7(repoExtras, { recursive: true });
|
|
4684
5013
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
4685
5014
|
v,
|
|
4686
5015
|
dryRun,
|
|
4687
5016
|
({ localRoot, logical, dirname: dirname8 }) => ({
|
|
4688
|
-
src:
|
|
4689
|
-
dst:
|
|
5017
|
+
src: join40(localRoot, dirname8),
|
|
5018
|
+
dst: join40(repoExtras, logical, dirname8)
|
|
4690
5019
|
}),
|
|
4691
5020
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
4692
5021
|
// Push copy routing per extra type:
|
|
@@ -4715,8 +5044,8 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
4715
5044
|
v,
|
|
4716
5045
|
dryRun,
|
|
4717
5046
|
({ localRoot, logical, dirname: dirname8 }) => ({
|
|
4718
|
-
src:
|
|
4719
|
-
dst:
|
|
5047
|
+
src: join40(repo, "shared", "extras", logical, dirname8),
|
|
5048
|
+
dst: join40(localRoot, dirname8)
|
|
4720
5049
|
}),
|
|
4721
5050
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
4722
5051
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -4740,129 +5069,34 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
4740
5069
|
if (!dryRun && prePostHeads !== void 0) {
|
|
4741
5070
|
propagatePlanningDeletes(v, ts, prePostHeads, repo);
|
|
4742
5071
|
}
|
|
4743
|
-
return { unmapped, skipped, pulled: done, wouldPull: would };
|
|
4744
|
-
}
|
|
4745
|
-
|
|
4746
|
-
// src/extras-sync.ts
|
|
4747
|
-
function divergenceCheckExtras(ts) {
|
|
4748
|
-
const v = loadValidatedExtras({});
|
|
4749
|
-
if (v === null) return;
|
|
4750
|
-
const counts = { unmapped: 0, skipped: 0 };
|
|
4751
|
-
const backupRoot = join39(backupBase(), ts, "extras");
|
|
4752
|
-
const repo = repoHome();
|
|
4753
|
-
for (const { logical, localRoot, dirname: dirname8 } of eachExtrasTarget(v, counts)) {
|
|
4754
|
-
const local = join39(localRoot, dirname8);
|
|
4755
|
-
const repoEntry = join39(repo, "shared", "extras", logical, dirname8);
|
|
4756
|
-
if (!existsSync32(local) || !existsSync32(repoEntry)) continue;
|
|
4757
|
-
const diff = listDivergingFiles(local, repoEntry);
|
|
4758
|
-
if (diff.length === 0) continue;
|
|
4759
|
-
const projectBackupRoot = join39(backupRoot, encodePath(localRoot));
|
|
4760
|
-
warn(
|
|
4761
|
-
`local ${dirname8} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will merge changes (.planning overlays, .claude/.CLAUDE.md mirror; backups at ${projectBackupRoot}/)`
|
|
4762
|
-
);
|
|
4763
|
-
for (const f of diff) warn(` ${f}`);
|
|
4764
|
-
}
|
|
4765
|
-
}
|
|
4766
|
-
|
|
4767
|
-
// src/links.ts
|
|
4768
|
-
init_config();
|
|
4769
|
-
init_utils();
|
|
4770
|
-
init_utils_fs();
|
|
4771
|
-
init_utils_json();
|
|
4772
|
-
import { existsSync as existsSync33, lstatSync as lstatSync9, rmSync as rmSync12 } from "node:fs";
|
|
4773
|
-
import { join as join40 } from "node:path";
|
|
4774
|
-
function emitAutoMove(onPreview, linkPath, ts, name) {
|
|
4775
|
-
if (onPreview) {
|
|
4776
|
-
onPreview({ kind: "auto-move", from: linkPath, to: `backup/${ts}/${name}` });
|
|
4777
|
-
} else {
|
|
4778
|
-
log(`would auto-move non-symlink: ${linkPath} -> backup/${ts}/${name}`);
|
|
4779
|
-
}
|
|
4780
|
-
}
|
|
4781
|
-
function emitCreate(onPreview, from, to) {
|
|
4782
|
-
if (onPreview) {
|
|
4783
|
-
onPreview({ kind: "create", from, to });
|
|
4784
|
-
} else {
|
|
4785
|
-
log(`would create symlink: ${from} -> ${to}`);
|
|
4786
|
-
}
|
|
4787
|
-
}
|
|
4788
|
-
function isAlreadySymlink(linkPath) {
|
|
4789
|
-
return existsSync33(linkPath) && lstatSync9(linkPath).isSymbolicLink();
|
|
4790
|
-
}
|
|
4791
|
-
function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
4792
|
-
for (const name of linkNames) {
|
|
4793
|
-
const linkPath = join40(claude, name);
|
|
4794
|
-
const target = join40(repo, "shared", name);
|
|
4795
|
-
if (!existsSync33(linkPath)) continue;
|
|
4796
|
-
if (lstatSync9(linkPath).isSymbolicLink()) continue;
|
|
4797
|
-
if (!existsSync33(target)) continue;
|
|
4798
|
-
if (dryRun) {
|
|
4799
|
-
emitAutoMove(onPreview, linkPath, ts, name);
|
|
4800
|
-
continue;
|
|
4801
|
-
}
|
|
4802
|
-
backupBeforeWrite(linkPath, ts);
|
|
4803
|
-
rmSync12(linkPath, { recursive: true, force: true });
|
|
4804
|
-
}
|
|
4805
|
-
}
|
|
4806
|
-
function applySharedLinks(ts, map, opts = {}) {
|
|
4807
|
-
const dryRun = opts.dryRun === true;
|
|
4808
|
-
const claude = claudeHome();
|
|
4809
|
-
const repo = repoHome();
|
|
4810
|
-
const linkNames = allSharedLinks(map);
|
|
4811
|
-
runAutoMovePasses(linkNames, claude, repo, ts, dryRun, opts.onPreview);
|
|
4812
|
-
for (const name of linkNames) {
|
|
4813
|
-
const target = join40(repo, "shared", name);
|
|
4814
|
-
if (!existsSync33(target)) continue;
|
|
4815
|
-
const linkPath = join40(claude, name);
|
|
4816
|
-
if (isAlreadySymlink(linkPath)) continue;
|
|
4817
|
-
if (dryRun) {
|
|
4818
|
-
emitCreate(opts.onPreview, linkPath, target);
|
|
4819
|
-
continue;
|
|
4820
|
-
}
|
|
4821
|
-
ensureSymlink(linkPath, target);
|
|
4822
|
-
}
|
|
5072
|
+
return { unmapped, skipped, pulled: done, wouldPull: would };
|
|
4823
5073
|
}
|
|
4824
|
-
|
|
4825
|
-
|
|
5074
|
+
|
|
5075
|
+
// src/extras-sync.ts
|
|
5076
|
+
function divergenceCheckExtras(ts) {
|
|
5077
|
+
const v = loadValidatedExtras({});
|
|
5078
|
+
if (v === null) return;
|
|
5079
|
+
const counts = { unmapped: 0, skipped: 0 };
|
|
5080
|
+
const backupRoot = join41(backupBase(), ts, "extras");
|
|
4826
5081
|
const repo = repoHome();
|
|
4827
|
-
const
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
if (!hasOverrides && existsSync33(settingsPath)) {
|
|
4839
|
-
try {
|
|
4840
|
-
const existing = readJson(settingsPath);
|
|
4841
|
-
const baseKeys = new Set(Object.keys(base));
|
|
4842
|
-
const drift = Object.keys(existing).filter((k) => !baseKeys.has(k));
|
|
4843
|
-
if (drift.length > 0) {
|
|
4844
|
-
warn(
|
|
4845
|
-
`no hosts/${HOST}.json found; existing settings has unbased keys ${JSON.stringify(drift)}. Set NOMAD_HOST to match a hosts/*.json or rerun 'nomad doctor' for candidates.`
|
|
4846
|
-
);
|
|
4847
|
-
}
|
|
4848
|
-
} catch {
|
|
4849
|
-
warn("existing settings.json is malformed; skipping drift-check and regenerating.");
|
|
4850
|
-
}
|
|
4851
|
-
}
|
|
4852
|
-
const overrideLabel = hasOverrides ? `${HOST}.json` : "no host overrides";
|
|
4853
|
-
if (dryRun) {
|
|
4854
|
-
log(`would write settings.json (base + ${overrideLabel})`);
|
|
4855
|
-
return { label: overrideLabel };
|
|
5082
|
+
for (const { logical, localRoot, dirname: dirname8 } of eachExtrasTarget(v, counts)) {
|
|
5083
|
+
const local = join41(localRoot, dirname8);
|
|
5084
|
+
const repoEntry = join41(repo, "shared", "extras", logical, dirname8);
|
|
5085
|
+
if (!existsSync34(local) || !existsSync34(repoEntry)) continue;
|
|
5086
|
+
const diff = listDivergingFiles(local, repoEntry);
|
|
5087
|
+
if (diff.length === 0) continue;
|
|
5088
|
+
const projectBackupRoot = join41(backupRoot, encodePath(localRoot));
|
|
5089
|
+
warn(
|
|
5090
|
+
`local ${dirname8} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will merge changes (.planning overlays, .claude/.CLAUDE.md mirror; backups at ${projectBackupRoot}/)`
|
|
5091
|
+
);
|
|
5092
|
+
for (const f of diff) warn(` ${f}`);
|
|
4856
5093
|
}
|
|
4857
|
-
backupBeforeWrite(settingsPath, ts);
|
|
4858
|
-
writeJsonAtomic(settingsPath, merged);
|
|
4859
|
-
return { label: overrideLabel };
|
|
4860
5094
|
}
|
|
4861
5095
|
|
|
4862
5096
|
// src/skills-sync.ts
|
|
4863
5097
|
init_config();
|
|
4864
|
-
import { existsSync as
|
|
4865
|
-
import { join as
|
|
5098
|
+
import { existsSync as existsSync35, lstatSync as lstatSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync13, rmSync as rmSync13 } from "node:fs";
|
|
5099
|
+
import { join as join42 } from "node:path";
|
|
4866
5100
|
init_utils_fs();
|
|
4867
5101
|
function isGsdOwned(name) {
|
|
4868
5102
|
return name.startsWith(GSD_PREFIX);
|
|
@@ -4882,9 +5116,9 @@ function copySkillsPull(src, dst) {
|
|
|
4882
5116
|
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
|
|
4883
5117
|
}
|
|
4884
5118
|
function syncSkillsPull(ts) {
|
|
4885
|
-
const sharedSkills =
|
|
4886
|
-
if (!
|
|
4887
|
-
const localSkills =
|
|
5119
|
+
const sharedSkills = join42(repoHome(), "shared", "skills");
|
|
5120
|
+
if (!existsSync35(sharedSkills)) return;
|
|
5121
|
+
const localSkills = join42(claudeHome(), "skills");
|
|
4888
5122
|
const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
4889
5123
|
if (dstStat?.isSymbolicLink() === true) {
|
|
4890
5124
|
backupBeforeWrite(localSkills, ts);
|
|
@@ -4894,18 +5128,18 @@ function syncSkillsPull(ts) {
|
|
|
4894
5128
|
copySkillsPull(sharedSkills, localSkills);
|
|
4895
5129
|
}
|
|
4896
5130
|
function syncSkillsPush() {
|
|
4897
|
-
const localSkills =
|
|
5131
|
+
const localSkills = join42(claudeHome(), "skills");
|
|
4898
5132
|
const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
4899
5133
|
if (stat === void 0) return;
|
|
4900
5134
|
if (stat.isSymbolicLink()) return;
|
|
4901
|
-
const sharedSkills =
|
|
5135
|
+
const sharedSkills = join42(repoHome(), "shared", "skills");
|
|
4902
5136
|
copySkillsPush(localSkills, sharedSkills);
|
|
4903
5137
|
}
|
|
4904
5138
|
|
|
4905
5139
|
// src/preview.ts
|
|
4906
5140
|
init_config();
|
|
4907
|
-
import { existsSync as
|
|
4908
|
-
import { join as
|
|
5141
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
5142
|
+
import { join as join43 } from "node:path";
|
|
4909
5143
|
|
|
4910
5144
|
// node_modules/diff/libesm/diff/base.js
|
|
4911
5145
|
var Diff = class {
|
|
@@ -5191,7 +5425,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
5191
5425
|
return lines.join("\n");
|
|
5192
5426
|
}
|
|
5193
5427
|
function readJsonOrNull(path) {
|
|
5194
|
-
if (!
|
|
5428
|
+
if (!existsSync36(path)) return null;
|
|
5195
5429
|
try {
|
|
5196
5430
|
return readJson(path);
|
|
5197
5431
|
} catch {
|
|
@@ -5205,12 +5439,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
5205
5439
|
}
|
|
5206
5440
|
const notes = [];
|
|
5207
5441
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
5208
|
-
if (hostOverrides === null &&
|
|
5442
|
+
if (hostOverrides === null && existsSync36(hostPath)) {
|
|
5209
5443
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
5210
5444
|
}
|
|
5211
5445
|
const merged = deepMerge(base, hostOverrides ?? {});
|
|
5212
5446
|
const current = readJsonOrNull(settingsPath);
|
|
5213
|
-
if (current === null &&
|
|
5447
|
+
if (current === null && existsSync36(settingsPath)) {
|
|
5214
5448
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
5215
5449
|
}
|
|
5216
5450
|
const rawEqual = JSON.stringify(current ?? {}, null, 2) === JSON.stringify(merged, null, 2);
|
|
@@ -5250,9 +5484,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
5250
5484
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
5251
5485
|
});
|
|
5252
5486
|
const settingsResult = previewSettings(
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5487
|
+
join43(repo, "shared", "settings.base.json"),
|
|
5488
|
+
join43(repo, "hosts", `${HOST}.json`),
|
|
5489
|
+
join43(claude, "settings.json")
|
|
5256
5490
|
);
|
|
5257
5491
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
5258
5492
|
const sessions = section("Sessions");
|
|
@@ -5274,9 +5508,9 @@ init_config();
|
|
|
5274
5508
|
init_commands_pull_wedge();
|
|
5275
5509
|
init_utils();
|
|
5276
5510
|
init_utils_fs();
|
|
5277
|
-
import { execFileSync as
|
|
5511
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
5278
5512
|
function gitCapture(args, cwd) {
|
|
5279
|
-
return
|
|
5513
|
+
return execFileSync16("git", args, {
|
|
5280
5514
|
cwd,
|
|
5281
5515
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5282
5516
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -5453,8 +5687,8 @@ function cmdPull(opts = {}) {
|
|
|
5453
5687
|
const forceRemote = opts.forceRemote === true;
|
|
5454
5688
|
const repo = repoHome();
|
|
5455
5689
|
const backup = backupBase();
|
|
5456
|
-
if (!
|
|
5457
|
-
if (!
|
|
5690
|
+
if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
|
|
5691
|
+
if (!existsSync37(join44(repo, "shared", "settings.base.json"))) {
|
|
5458
5692
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
5459
5693
|
}
|
|
5460
5694
|
const handle = acquireLock("pull");
|
|
@@ -5463,7 +5697,7 @@ function cmdPull(opts = {}) {
|
|
|
5463
5697
|
const ts = freshBackupTs(backup);
|
|
5464
5698
|
handleWedge(repo, forceRemote);
|
|
5465
5699
|
if (!dryRun) {
|
|
5466
|
-
const backupRoot =
|
|
5700
|
+
const backupRoot = join44(backup, ts);
|
|
5467
5701
|
try {
|
|
5468
5702
|
mkdirSync9(backupRoot, { recursive: true });
|
|
5469
5703
|
} catch (err) {
|
|
@@ -5476,8 +5710,8 @@ function cmdPull(opts = {}) {
|
|
|
5476
5710
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
5477
5711
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
5478
5712
|
});
|
|
5479
|
-
const mapPath =
|
|
5480
|
-
const map =
|
|
5713
|
+
const mapPath = join44(repo, "path-map.json");
|
|
5714
|
+
const map = existsSync37(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
5481
5715
|
divergenceCheckExtras(ts);
|
|
5482
5716
|
if (dryRun) {
|
|
5483
5717
|
computePreview(ts, map, "pull");
|
|
@@ -5499,8 +5733,8 @@ function cmdPull(opts = {}) {
|
|
|
5499
5733
|
|
|
5500
5734
|
// src/commands.push.ts
|
|
5501
5735
|
init_config();
|
|
5502
|
-
import { existsSync as
|
|
5503
|
-
import { join as
|
|
5736
|
+
import { existsSync as existsSync39 } from "node:fs";
|
|
5737
|
+
import { join as join46, relative as relative5 } from "node:path";
|
|
5504
5738
|
|
|
5505
5739
|
// src/commands.push.allowlist.ts
|
|
5506
5740
|
init_config();
|
|
@@ -5593,7 +5827,7 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
5593
5827
|
|
|
5594
5828
|
// src/push-global-config.ts
|
|
5595
5829
|
init_config();
|
|
5596
|
-
import { execFileSync as
|
|
5830
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
5597
5831
|
var STATUS_LABELS = {
|
|
5598
5832
|
A: "add",
|
|
5599
5833
|
M: "modify",
|
|
@@ -5633,7 +5867,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
|
|
|
5633
5867
|
}
|
|
5634
5868
|
function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
5635
5869
|
const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
|
|
5636
|
-
const raw =
|
|
5870
|
+
const raw = execFileSync17("git", args, {
|
|
5637
5871
|
cwd: repoHome2,
|
|
5638
5872
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5639
5873
|
}).toString();
|
|
@@ -5672,9 +5906,9 @@ init_color();
|
|
|
5672
5906
|
init_config();
|
|
5673
5907
|
init_config_sharedDirs_guard();
|
|
5674
5908
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
5675
|
-
import { copyFileSync, existsSync as
|
|
5909
|
+
import { copyFileSync, existsSync as existsSync38, mkdirSync as mkdirSync10, readdirSync as readdirSync14, rmSync as rmSync14 } from "node:fs";
|
|
5676
5910
|
import { homedir as homedir5 } from "node:os";
|
|
5677
|
-
import { join as
|
|
5911
|
+
import { join as join45 } from "node:path";
|
|
5678
5912
|
init_push_leak_verdict();
|
|
5679
5913
|
init_push_gitleaks();
|
|
5680
5914
|
init_utils_fs();
|
|
@@ -5689,13 +5923,13 @@ function stageSessions(tmpRoot, map) {
|
|
|
5689
5923
|
if (!p || p === "TBD") continue;
|
|
5690
5924
|
reverse.set(encodePath(p), logical);
|
|
5691
5925
|
}
|
|
5692
|
-
const localProjects =
|
|
5693
|
-
if (!
|
|
5926
|
+
const localProjects = join45(claudeHome(), "projects");
|
|
5927
|
+
if (!existsSync38(localProjects)) return 0;
|
|
5694
5928
|
let staged = 0;
|
|
5695
5929
|
for (const dir of readdirSync14(localProjects)) {
|
|
5696
5930
|
const logical = reverse.get(dir);
|
|
5697
5931
|
if (!logical) continue;
|
|
5698
|
-
copyDirJsonlOnly(
|
|
5932
|
+
copyDirJsonlOnly(join45(localProjects, dir), join45(tmpRoot, "shared", "projects", logical));
|
|
5699
5933
|
staged++;
|
|
5700
5934
|
}
|
|
5701
5935
|
return staged;
|
|
@@ -5711,9 +5945,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
5711
5945
|
if (!localRoot || localRoot === "TBD") continue;
|
|
5712
5946
|
for (const dirname8 of dirnames) {
|
|
5713
5947
|
if (!whitelist.includes(dirname8)) continue;
|
|
5714
|
-
const src =
|
|
5715
|
-
if (!
|
|
5716
|
-
const dst =
|
|
5948
|
+
const src = join45(localRoot, dirname8);
|
|
5949
|
+
if (!existsSync38(src)) continue;
|
|
5950
|
+
const dst = join45(tmpRoot, "shared", "extras", logical, dirname8);
|
|
5717
5951
|
copyExtras(src, dst);
|
|
5718
5952
|
staged++;
|
|
5719
5953
|
}
|
|
@@ -5721,19 +5955,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
5721
5955
|
return staged;
|
|
5722
5956
|
}
|
|
5723
5957
|
function previewPushLeaks(map) {
|
|
5724
|
-
const cacheDir =
|
|
5958
|
+
const cacheDir = join45(homedir5(), ".cache", "claude-nomad");
|
|
5725
5959
|
mkdirSync10(cacheDir, { recursive: true });
|
|
5726
5960
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
5727
|
-
const tmpRoot =
|
|
5961
|
+
const tmpRoot = join45(cacheDir, `push-preview-tree-${stamp}`);
|
|
5728
5962
|
try {
|
|
5729
5963
|
const sessionCount = stageSessions(tmpRoot, map);
|
|
5730
5964
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
5731
5965
|
if (sessionCount + extrasCount === 0) {
|
|
5732
5966
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
5733
5967
|
}
|
|
5734
|
-
const ignoreFile =
|
|
5735
|
-
if (
|
|
5736
|
-
copyFileSync(ignoreFile,
|
|
5968
|
+
const ignoreFile = join45(repoHome(), ".gitleaksignore");
|
|
5969
|
+
if (existsSync38(ignoreFile)) {
|
|
5970
|
+
copyFileSync(ignoreFile, join45(tmpRoot, ".gitleaksignore"));
|
|
5737
5971
|
}
|
|
5738
5972
|
let findings;
|
|
5739
5973
|
try {
|
|
@@ -5754,8 +5988,29 @@ function previewPushLeaks(map) {
|
|
|
5754
5988
|
init_utils();
|
|
5755
5989
|
init_utils_fs();
|
|
5756
5990
|
init_utils_json();
|
|
5991
|
+
function reportSettingsAheadDrift(repo) {
|
|
5992
|
+
const basePath = join46(repo, "shared", "settings.base.json");
|
|
5993
|
+
if (!existsSync39(basePath)) return;
|
|
5994
|
+
const settingsPath = join46(claudeHome(), "settings.json");
|
|
5995
|
+
if (!existsSync39(settingsPath)) return;
|
|
5996
|
+
try {
|
|
5997
|
+
const base = readJson(basePath);
|
|
5998
|
+
const hostPath = join46(repo, "hosts", `${HOST}.json`);
|
|
5999
|
+
const overrides = existsSync39(hostPath) ? readJson(hostPath) : {};
|
|
6000
|
+
const merged = deepMerge(base, overrides);
|
|
6001
|
+
const settings = readJson(settingsPath);
|
|
6002
|
+
const { ahead } = classifySettingsDrift(merged, settings);
|
|
6003
|
+
const { promotable } = partitionByCaptureExclusion(ahead);
|
|
6004
|
+
if (promotable.length === 0) return;
|
|
6005
|
+
const keys = JSON.stringify(promotable);
|
|
6006
|
+
warn(
|
|
6007
|
+
`settings.json has local-only keys ${keys} not in the repo. Run 'nomad capture-settings' to promote them (or 'nomad capture-settings --host' for host-specific values).`
|
|
6008
|
+
);
|
|
6009
|
+
} catch {
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
5757
6012
|
function guardGitlinks(repo) {
|
|
5758
|
-
const gitlinks = findGitlinks(
|
|
6013
|
+
const gitlinks = findGitlinks(join46(repo, "shared"));
|
|
5759
6014
|
if (gitlinks.length === 0) return;
|
|
5760
6015
|
for (const p of gitlinks) {
|
|
5761
6016
|
const rel = relative5(repo, p);
|
|
@@ -5821,11 +6076,12 @@ async function cmdPush(opts = {}) {
|
|
|
5821
6076
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
5822
6077
|
const repo = repoHome();
|
|
5823
6078
|
const backup = backupBase();
|
|
5824
|
-
if (!
|
|
6079
|
+
if (!existsSync39(repo)) die(`repo not cloned at ${repo}`);
|
|
5825
6080
|
const handle = acquireLock("push");
|
|
5826
6081
|
if (handle === null) process.exit(0);
|
|
5827
6082
|
try {
|
|
5828
6083
|
console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
|
|
6084
|
+
reportSettingsAheadDrift(repo);
|
|
5829
6085
|
probeGitleaks();
|
|
5830
6086
|
withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
|
|
5831
6087
|
const ts = freshBackupTs(backup);
|
|
@@ -5840,8 +6096,8 @@ async function cmdPush(opts = {}) {
|
|
|
5840
6096
|
renderNoScanTree(st);
|
|
5841
6097
|
return;
|
|
5842
6098
|
}
|
|
5843
|
-
const mapPath =
|
|
5844
|
-
if (!
|
|
6099
|
+
const mapPath = join46(repo, "path-map.json");
|
|
6100
|
+
if (!existsSync39(mapPath)) {
|
|
5845
6101
|
if (dryRun) return runDryRunPreview(st, null, repo);
|
|
5846
6102
|
die("path-map.json missing, cannot enforce push allow-list");
|
|
5847
6103
|
}
|
|
@@ -5862,16 +6118,16 @@ async function cmdPush(opts = {}) {
|
|
|
5862
6118
|
}
|
|
5863
6119
|
|
|
5864
6120
|
// src/commands.update.ts
|
|
5865
|
-
import { execFileSync as
|
|
6121
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
5866
6122
|
init_utils();
|
|
5867
|
-
function readInstalledVersion(run =
|
|
6123
|
+
function readInstalledVersion(run = execFileSync18) {
|
|
5868
6124
|
try {
|
|
5869
6125
|
return run("nomad", ["--version"], { encoding: "utf8" }).toString().trim() || null;
|
|
5870
6126
|
} catch {
|
|
5871
6127
|
return null;
|
|
5872
6128
|
}
|
|
5873
6129
|
}
|
|
5874
|
-
function cmdUpdate(run =
|
|
6130
|
+
function cmdUpdate(run = execFileSync18) {
|
|
5875
6131
|
console.log("Updating claude-nomad CLI via npm...");
|
|
5876
6132
|
try {
|
|
5877
6133
|
run("npm", ["update", "-g", "claude-nomad"], { stdio: "inherit" });
|
|
@@ -5895,18 +6151,18 @@ init_config();
|
|
|
5895
6151
|
|
|
5896
6152
|
// src/diff.ts
|
|
5897
6153
|
init_config();
|
|
5898
|
-
import { existsSync as
|
|
5899
|
-
import { join as
|
|
6154
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
6155
|
+
import { join as join47 } from "node:path";
|
|
5900
6156
|
init_utils();
|
|
5901
6157
|
init_utils_fs();
|
|
5902
6158
|
init_utils_json();
|
|
5903
6159
|
function cmdDiff() {
|
|
5904
6160
|
try {
|
|
5905
6161
|
const repo = repoHome();
|
|
5906
|
-
if (!
|
|
6162
|
+
if (!existsSync40(repo)) die(`repo not cloned at ${repo}`);
|
|
5907
6163
|
const ts = freshBackupTs(backupBase());
|
|
5908
|
-
const mapPath =
|
|
5909
|
-
const map =
|
|
6164
|
+
const mapPath = join47(repo, "path-map.json");
|
|
6165
|
+
const map = existsSync40(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
5910
6166
|
computePreview(ts, map, "diff");
|
|
5911
6167
|
} catch (err) {
|
|
5912
6168
|
if (err instanceof NomadFatal) {
|
|
@@ -5920,19 +6176,19 @@ function cmdDiff() {
|
|
|
5920
6176
|
|
|
5921
6177
|
// src/init.ts
|
|
5922
6178
|
init_config();
|
|
5923
|
-
import { existsSync as
|
|
5924
|
-
import { join as
|
|
6179
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync11, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6180
|
+
import { join as join49 } from "node:path";
|
|
5925
6181
|
|
|
5926
6182
|
// src/init.gh-onboard.ts
|
|
5927
6183
|
init_config();
|
|
5928
|
-
import { execFileSync as
|
|
6184
|
+
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
5929
6185
|
init_utils();
|
|
5930
6186
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
5931
6187
|
function isValidRepoName(name) {
|
|
5932
6188
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
5933
6189
|
}
|
|
5934
6190
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
5935
|
-
function ensureOriginRepo(repoName, run =
|
|
6191
|
+
function ensureOriginRepo(repoName, run = execFileSync19) {
|
|
5936
6192
|
if (!isValidRepoName(repoName)) {
|
|
5937
6193
|
die(
|
|
5938
6194
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -6003,33 +6259,33 @@ init_config();
|
|
|
6003
6259
|
init_utils();
|
|
6004
6260
|
init_utils_fs();
|
|
6005
6261
|
init_utils_json();
|
|
6006
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as
|
|
6007
|
-
import { join as
|
|
6262
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync41, rmSync as rmSync15, statSync as statSync9 } from "node:fs";
|
|
6263
|
+
import { join as join48 } from "node:path";
|
|
6008
6264
|
function snapshotIntoShared(map) {
|
|
6009
6265
|
const repo = repoHome();
|
|
6010
6266
|
const claude = claudeHome();
|
|
6011
6267
|
for (const name of allSharedLinks(map)) {
|
|
6012
|
-
const src =
|
|
6013
|
-
if (!
|
|
6014
|
-
const dst =
|
|
6268
|
+
const src = join48(claude, name);
|
|
6269
|
+
if (!existsSync41(src)) continue;
|
|
6270
|
+
const dst = join48(repo, "shared", name);
|
|
6015
6271
|
if (statSync9(src).isDirectory()) {
|
|
6016
|
-
const gk =
|
|
6017
|
-
if (
|
|
6272
|
+
const gk = join48(dst, ".gitkeep");
|
|
6273
|
+
if (existsSync41(gk)) rmSync15(gk);
|
|
6018
6274
|
cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
6019
6275
|
} else {
|
|
6020
6276
|
copyFileSync2(src, dst);
|
|
6021
6277
|
}
|
|
6022
6278
|
log(`snapshotted shared/${name} from ${src}`);
|
|
6023
6279
|
}
|
|
6024
|
-
const userSettings =
|
|
6025
|
-
if (
|
|
6280
|
+
const userSettings = join48(claude, "settings.json");
|
|
6281
|
+
if (existsSync41(userSettings)) {
|
|
6026
6282
|
let parsed;
|
|
6027
6283
|
try {
|
|
6028
6284
|
parsed = readJson(userSettings);
|
|
6029
6285
|
} catch (err) {
|
|
6030
6286
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
6031
6287
|
}
|
|
6032
|
-
const hostFile =
|
|
6288
|
+
const hostFile = join48(repo, "hosts", `${HOST}.json`);
|
|
6033
6289
|
writeJsonAtomic(hostFile, parsed);
|
|
6034
6290
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
6035
6291
|
}
|
|
@@ -6042,14 +6298,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
6042
6298
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
6043
6299
|
function preflightConflict(repoHome2) {
|
|
6044
6300
|
const candidates = [
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6301
|
+
join49(repoHome2, "shared", "settings.base.json"),
|
|
6302
|
+
join49(repoHome2, "shared", "CLAUDE.md"),
|
|
6303
|
+
join49(repoHome2, "path-map.json"),
|
|
6304
|
+
join49(repoHome2, "hosts"),
|
|
6305
|
+
join49(repoHome2, "shared")
|
|
6050
6306
|
];
|
|
6051
6307
|
for (const c of candidates) {
|
|
6052
|
-
if (
|
|
6308
|
+
if (existsSync42(c)) return c;
|
|
6053
6309
|
}
|
|
6054
6310
|
return null;
|
|
6055
6311
|
}
|
|
@@ -6064,25 +6320,25 @@ function cmdInit(opts = {}) {
|
|
|
6064
6320
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
6065
6321
|
}
|
|
6066
6322
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
6067
|
-
mkdirSync11(
|
|
6068
|
-
mkdirSync11(
|
|
6323
|
+
mkdirSync11(join49(repo, "shared"), { recursive: true });
|
|
6324
|
+
mkdirSync11(join49(repo, "hosts"), { recursive: true });
|
|
6069
6325
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6070
|
-
mkdirSync11(
|
|
6326
|
+
mkdirSync11(join49(repo, "shared", name), { recursive: true });
|
|
6071
6327
|
}
|
|
6072
|
-
const userClaudeMd =
|
|
6073
|
-
if (!snapshot || !
|
|
6074
|
-
writeFileSync6(
|
|
6328
|
+
const userClaudeMd = join49(claude, "CLAUDE.md");
|
|
6329
|
+
if (!snapshot || !existsSync42(userClaudeMd)) {
|
|
6330
|
+
writeFileSync6(join49(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
6075
6331
|
item("created shared/CLAUDE.md");
|
|
6076
6332
|
}
|
|
6077
6333
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6078
|
-
writeFileSync6(
|
|
6334
|
+
writeFileSync6(join49(repo, "shared", name, ".gitkeep"), "");
|
|
6079
6335
|
item(`created shared/${name}/.gitkeep`);
|
|
6080
6336
|
}
|
|
6081
|
-
writeFileSync6(
|
|
6337
|
+
writeFileSync6(join49(repo, "hosts", ".gitkeep"), "");
|
|
6082
6338
|
item("created hosts/.gitkeep");
|
|
6083
|
-
writeJsonAtomic(
|
|
6339
|
+
writeJsonAtomic(join49(repo, "shared", "settings.base.json"), {});
|
|
6084
6340
|
item("created shared/settings.base.json");
|
|
6085
|
-
writeJsonAtomic(
|
|
6341
|
+
writeJsonAtomic(join49(repo, "path-map.json"), { projects: {} });
|
|
6086
6342
|
item("created path-map.json");
|
|
6087
6343
|
if (snapshot) {
|
|
6088
6344
|
snapshotIntoShared({ projects: {} });
|
|
@@ -6145,86 +6401,20 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
6145
6401
|
}
|
|
6146
6402
|
}
|
|
6147
6403
|
|
|
6148
|
-
// src/nomad.dispatch.ts
|
|
6404
|
+
// src/nomad.dispatch.helpers.ts
|
|
6405
|
+
var REJECT = { ok: false, advance: 0 };
|
|
6406
|
+
function applyBool(seen, set) {
|
|
6407
|
+
if (seen) return REJECT;
|
|
6408
|
+
set();
|
|
6409
|
+
return { ok: true, advance: 1 };
|
|
6410
|
+
}
|
|
6149
6411
|
function extractFlagValue(argv, i) {
|
|
6150
6412
|
const val = argv[i + 1];
|
|
6151
6413
|
if (val === void 0 || val.startsWith("--")) return null;
|
|
6152
6414
|
return val;
|
|
6153
6415
|
}
|
|
6154
|
-
function applyInitToken(argv, i, st) {
|
|
6155
|
-
const token = argv[i];
|
|
6156
|
-
if (token === "--snapshot") {
|
|
6157
|
-
if (st.sawSnapshot) return { ok: false, advance: 0 };
|
|
6158
|
-
st.sawSnapshot = true;
|
|
6159
|
-
st.snapshot = true;
|
|
6160
|
-
return { ok: true, advance: 1 };
|
|
6161
|
-
}
|
|
6162
|
-
if (token === "--keep-actions") {
|
|
6163
|
-
if (st.sawKeepActions) return { ok: false, advance: 0 };
|
|
6164
|
-
st.sawKeepActions = true;
|
|
6165
|
-
st.keepActions = true;
|
|
6166
|
-
return { ok: true, advance: 1 };
|
|
6167
|
-
}
|
|
6168
|
-
if (token === "--repo") {
|
|
6169
|
-
if (st.sawRepo) return { ok: false, advance: 0 };
|
|
6170
|
-
st.sawRepo = true;
|
|
6171
|
-
const val = extractFlagValue(argv, i);
|
|
6172
|
-
if (val === null) return { ok: false, advance: 0 };
|
|
6173
|
-
st.repoName = val;
|
|
6174
|
-
return { ok: true, advance: 2 };
|
|
6175
|
-
}
|
|
6176
|
-
return { ok: false, advance: 0 };
|
|
6177
|
-
}
|
|
6178
|
-
function parseInitArgs(argv) {
|
|
6179
|
-
const st = {
|
|
6180
|
-
snapshot: false,
|
|
6181
|
-
keepActions: false,
|
|
6182
|
-
repoName: void 0,
|
|
6183
|
-
sawSnapshot: false,
|
|
6184
|
-
sawKeepActions: false,
|
|
6185
|
-
sawRepo: false
|
|
6186
|
-
};
|
|
6187
|
-
let i = 3;
|
|
6188
|
-
while (i < argv.length) {
|
|
6189
|
-
const { ok: ok2, advance } = applyInitToken(argv, i, st);
|
|
6190
|
-
if (!ok2) return null;
|
|
6191
|
-
i += advance;
|
|
6192
|
-
}
|
|
6193
|
-
return { snapshot: st.snapshot, keepActions: st.keepActions, repoName: st.repoName };
|
|
6194
|
-
}
|
|
6195
|
-
function parseRedactArgs(argv) {
|
|
6196
|
-
const id = argv[3];
|
|
6197
|
-
if (typeof id !== "string" || !/^\w[\w-]{0,127}$/.test(id)) {
|
|
6198
|
-
return null;
|
|
6199
|
-
}
|
|
6200
|
-
let rule;
|
|
6201
|
-
let dryRun = false;
|
|
6202
|
-
let sawRule = false;
|
|
6203
|
-
let sawDryRun = false;
|
|
6204
|
-
let i = 4;
|
|
6205
|
-
while (i < argv.length) {
|
|
6206
|
-
const token = argv[i];
|
|
6207
|
-
if (token === "--dry-run") {
|
|
6208
|
-
if (sawDryRun) return null;
|
|
6209
|
-
sawDryRun = true;
|
|
6210
|
-
dryRun = true;
|
|
6211
|
-
i++;
|
|
6212
|
-
} else if (token === "--rule") {
|
|
6213
|
-
if (sawRule) return null;
|
|
6214
|
-
sawRule = true;
|
|
6215
|
-
const val = argv[i + 1];
|
|
6216
|
-
if (val === void 0 || val.startsWith("--")) return null;
|
|
6217
|
-
rule = val;
|
|
6218
|
-
i += 2;
|
|
6219
|
-
} else {
|
|
6220
|
-
return null;
|
|
6221
|
-
}
|
|
6222
|
-
}
|
|
6223
|
-
return { id, rule, dryRun };
|
|
6224
|
-
}
|
|
6225
6416
|
|
|
6226
6417
|
// src/nomad.dispatch.clean.ts
|
|
6227
|
-
var REJECT = { ok: false, advance: 0 };
|
|
6228
6418
|
function applyOlderThan(argv, i, st) {
|
|
6229
6419
|
if (st.olderThan !== void 0) return REJECT;
|
|
6230
6420
|
const val = extractFlagValue(argv, i);
|
|
@@ -6239,11 +6429,6 @@ function applyKeep(argv, i, st) {
|
|
|
6239
6429
|
st.keep = Number(val);
|
|
6240
6430
|
return { ok: true, advance: 2 };
|
|
6241
6431
|
}
|
|
6242
|
-
function applyBool(seen, set) {
|
|
6243
|
-
if (seen) return REJECT;
|
|
6244
|
-
set();
|
|
6245
|
-
return { ok: true, advance: 1 };
|
|
6246
|
-
}
|
|
6247
6432
|
function applyCleanToken(argv, i, st) {
|
|
6248
6433
|
switch (argv[i]) {
|
|
6249
6434
|
case "--backups":
|
|
@@ -6276,6 +6461,31 @@ function parseCleanArgs(argv) {
|
|
|
6276
6461
|
return { dryRun: st.dryRun, olderThan: st.olderThan, keep: st.keep };
|
|
6277
6462
|
}
|
|
6278
6463
|
|
|
6464
|
+
// src/nomad.dispatch.capture-settings.ts
|
|
6465
|
+
function parseCaptureSettingsArgs(argv) {
|
|
6466
|
+
let host = false;
|
|
6467
|
+
let dryRun = false;
|
|
6468
|
+
let yes = false;
|
|
6469
|
+
let i = 3;
|
|
6470
|
+
while (i < argv.length) {
|
|
6471
|
+
const token = argv[i];
|
|
6472
|
+
if (token === "--host") {
|
|
6473
|
+
if (host) return null;
|
|
6474
|
+
host = true;
|
|
6475
|
+
} else if (token === "--dry-run") {
|
|
6476
|
+
if (dryRun) return null;
|
|
6477
|
+
dryRun = true;
|
|
6478
|
+
} else if (token === "--yes" || token === "-y") {
|
|
6479
|
+
if (yes) return null;
|
|
6480
|
+
yes = true;
|
|
6481
|
+
} else {
|
|
6482
|
+
return null;
|
|
6483
|
+
}
|
|
6484
|
+
i++;
|
|
6485
|
+
}
|
|
6486
|
+
return { host, dryRun, yes };
|
|
6487
|
+
}
|
|
6488
|
+
|
|
6279
6489
|
// src/nomad.dispatch.eject.ts
|
|
6280
6490
|
function parseEjectArgs(argv) {
|
|
6281
6491
|
let dryRun = false;
|
|
@@ -6293,6 +6503,79 @@ function parseEjectArgs(argv) {
|
|
|
6293
6503
|
return { dryRun };
|
|
6294
6504
|
}
|
|
6295
6505
|
|
|
6506
|
+
// src/nomad.dispatch.ts
|
|
6507
|
+
function applyInitToken(argv, i, st) {
|
|
6508
|
+
const token = argv[i];
|
|
6509
|
+
if (token === "--snapshot") {
|
|
6510
|
+
if (st.sawSnapshot) return REJECT;
|
|
6511
|
+
st.sawSnapshot = true;
|
|
6512
|
+
st.snapshot = true;
|
|
6513
|
+
return { ok: true, advance: 1 };
|
|
6514
|
+
}
|
|
6515
|
+
if (token === "--keep-actions") {
|
|
6516
|
+
if (st.sawKeepActions) return REJECT;
|
|
6517
|
+
st.sawKeepActions = true;
|
|
6518
|
+
st.keepActions = true;
|
|
6519
|
+
return { ok: true, advance: 1 };
|
|
6520
|
+
}
|
|
6521
|
+
if (token === "--repo") {
|
|
6522
|
+
if (st.sawRepo) return REJECT;
|
|
6523
|
+
st.sawRepo = true;
|
|
6524
|
+
const val = extractFlagValue(argv, i);
|
|
6525
|
+
if (val === null) return REJECT;
|
|
6526
|
+
st.repoName = val;
|
|
6527
|
+
return { ok: true, advance: 2 };
|
|
6528
|
+
}
|
|
6529
|
+
return REJECT;
|
|
6530
|
+
}
|
|
6531
|
+
function parseInitArgs(argv) {
|
|
6532
|
+
const st = {
|
|
6533
|
+
snapshot: false,
|
|
6534
|
+
keepActions: false,
|
|
6535
|
+
repoName: void 0,
|
|
6536
|
+
sawSnapshot: false,
|
|
6537
|
+
sawKeepActions: false,
|
|
6538
|
+
sawRepo: false
|
|
6539
|
+
};
|
|
6540
|
+
let i = 3;
|
|
6541
|
+
while (i < argv.length) {
|
|
6542
|
+
const { ok: ok2, advance } = applyInitToken(argv, i, st);
|
|
6543
|
+
if (!ok2) return null;
|
|
6544
|
+
i += advance;
|
|
6545
|
+
}
|
|
6546
|
+
return { snapshot: st.snapshot, keepActions: st.keepActions, repoName: st.repoName };
|
|
6547
|
+
}
|
|
6548
|
+
function parseRedactArgs(argv) {
|
|
6549
|
+
const id = argv[3];
|
|
6550
|
+
if (typeof id !== "string" || !/^\w[\w-]{0,127}$/.test(id)) {
|
|
6551
|
+
return null;
|
|
6552
|
+
}
|
|
6553
|
+
let rule;
|
|
6554
|
+
let dryRun = false;
|
|
6555
|
+
let sawRule = false;
|
|
6556
|
+
let sawDryRun = false;
|
|
6557
|
+
let i = 4;
|
|
6558
|
+
while (i < argv.length) {
|
|
6559
|
+
const token = argv[i];
|
|
6560
|
+
if (token === "--dry-run") {
|
|
6561
|
+
if (sawDryRun) return null;
|
|
6562
|
+
sawDryRun = true;
|
|
6563
|
+
dryRun = true;
|
|
6564
|
+
i++;
|
|
6565
|
+
} else if (token === "--rule") {
|
|
6566
|
+
if (sawRule) return null;
|
|
6567
|
+
sawRule = true;
|
|
6568
|
+
const val = extractFlagValue(argv, i);
|
|
6569
|
+
if (val === null) return null;
|
|
6570
|
+
rule = val;
|
|
6571
|
+
i += 2;
|
|
6572
|
+
} else {
|
|
6573
|
+
return null;
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
return { id, rule, dryRun };
|
|
6577
|
+
}
|
|
6578
|
+
|
|
6296
6579
|
// src/nomad.dispatch.allow.ts
|
|
6297
6580
|
function parseAllowArgs(argv) {
|
|
6298
6581
|
const positionals = argv.slice(3);
|
|
@@ -6326,32 +6609,26 @@ function parsePullArgs(argv) {
|
|
|
6326
6609
|
}
|
|
6327
6610
|
|
|
6328
6611
|
// src/nomad.dispatch.push.ts
|
|
6329
|
-
var REJECT2 = { ok: false, advance: 0 };
|
|
6330
|
-
function applyBool2(seen, set) {
|
|
6331
|
-
if (seen) return REJECT2;
|
|
6332
|
-
set();
|
|
6333
|
-
return { ok: true, advance: 1 };
|
|
6334
|
-
}
|
|
6335
6612
|
var RULE_ID_RE = /^\w[\w-]*$/;
|
|
6336
6613
|
function applyAllow2(argv, i, st) {
|
|
6337
|
-
if (st.allowRule !== void 0) return
|
|
6614
|
+
if (st.allowRule !== void 0) return REJECT;
|
|
6338
6615
|
const val = extractFlagValue(argv, i);
|
|
6339
|
-
if (val === null || !RULE_ID_RE.test(val)) return
|
|
6616
|
+
if (val === null || !RULE_ID_RE.test(val)) return REJECT;
|
|
6340
6617
|
st.allowRule = val;
|
|
6341
6618
|
return { ok: true, advance: 2 };
|
|
6342
6619
|
}
|
|
6343
6620
|
function applyPushToken(argv, i, st) {
|
|
6344
6621
|
switch (argv[i]) {
|
|
6345
6622
|
case "--dry-run":
|
|
6346
|
-
return
|
|
6623
|
+
return applyBool(st.dryRun, () => st.dryRun = true);
|
|
6347
6624
|
case "--redact-all":
|
|
6348
|
-
return
|
|
6625
|
+
return applyBool(st.redactAll, () => st.redactAll = true);
|
|
6349
6626
|
case "--allow-all":
|
|
6350
|
-
return
|
|
6627
|
+
return applyBool(st.allowAll, () => st.allowAll = true);
|
|
6351
6628
|
case "--allow":
|
|
6352
6629
|
return applyAllow2(argv, i, st);
|
|
6353
6630
|
default:
|
|
6354
|
-
return
|
|
6631
|
+
return REJECT;
|
|
6355
6632
|
}
|
|
6356
6633
|
}
|
|
6357
6634
|
function parsePushArgs(argv) {
|
|
@@ -6383,7 +6660,7 @@ function parsePushArgs(argv) {
|
|
|
6383
6660
|
// package.json
|
|
6384
6661
|
var package_default = {
|
|
6385
6662
|
name: "claude-nomad",
|
|
6386
|
-
version: "0.
|
|
6663
|
+
version: "0.51.0",
|
|
6387
6664
|
type: "module",
|
|
6388
6665
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
6389
6666
|
keywords: [
|
|
@@ -6548,6 +6825,21 @@ var DEFAULT_HELP = [
|
|
|
6548
6825
|
cont("manual-remainder checklist (uninstall CLI, drop env vars, optional deletes)."),
|
|
6549
6826
|
row(" --dry-run", "List what would be materialized without writing anything."),
|
|
6550
6827
|
"",
|
|
6828
|
+
row(
|
|
6829
|
+
" capture-settings",
|
|
6830
|
+
"Promote local-only settings.json keys into the shared repo so they survive"
|
|
6831
|
+
),
|
|
6832
|
+
cont("the next pull. Backs up the destination, writes atomically, then regenerates"),
|
|
6833
|
+
cont("settings.json so local matches. Idempotent when no local-only keys remain."),
|
|
6834
|
+
cont("Prompts for confirmation before writing (shows the destination and keys)."),
|
|
6835
|
+
row(" --host", "Write into hosts/<HOST>.json (host-specific values) instead of"),
|
|
6836
|
+
cont("shared/settings.base.json (default; normalizes absolute node launcher paths)."),
|
|
6837
|
+
row(
|
|
6838
|
+
" --dry-run",
|
|
6839
|
+
"Show the destination and keys that would be written without changing anything."
|
|
6840
|
+
),
|
|
6841
|
+
row(" --yes, -y", "Skip the confirmation prompt (required in a non-interactive shell)."),
|
|
6842
|
+
"",
|
|
6551
6843
|
row(
|
|
6552
6844
|
" redact <session-id>",
|
|
6553
6845
|
"Rewrite the secret span in the local source transcript for a session,"
|
|
@@ -6584,15 +6876,15 @@ var DEFAULT_HELP = [
|
|
|
6584
6876
|
init_config();
|
|
6585
6877
|
init_utils();
|
|
6586
6878
|
init_utils_json();
|
|
6587
|
-
import { existsSync as
|
|
6588
|
-
import { join as
|
|
6879
|
+
import { existsSync as existsSync43, readFileSync as readFileSync14, readdirSync as readdirSync15 } from "node:fs";
|
|
6880
|
+
import { join as join50 } from "node:path";
|
|
6589
6881
|
function resumeCmd(sessionId) {
|
|
6590
6882
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
6591
6883
|
fail(`invalid session id: ${sessionId}`);
|
|
6592
6884
|
process.exit(1);
|
|
6593
6885
|
}
|
|
6594
|
-
const projectsRoot =
|
|
6595
|
-
if (!
|
|
6886
|
+
const projectsRoot = join50(claudeHome(), "projects");
|
|
6887
|
+
if (!existsSync43(projectsRoot)) {
|
|
6596
6888
|
fail(`${projectsRoot} does not exist`);
|
|
6597
6889
|
process.exit(1);
|
|
6598
6890
|
}
|
|
@@ -6606,13 +6898,13 @@ function resumeCmd(sessionId) {
|
|
|
6606
6898
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
6607
6899
|
process.exit(1);
|
|
6608
6900
|
}
|
|
6609
|
-
const mapPath =
|
|
6610
|
-
if (!
|
|
6901
|
+
const mapPath = join50(repoHome(), "path-map.json");
|
|
6902
|
+
if (!existsSync43(mapPath)) {
|
|
6611
6903
|
fail("path-map.json missing");
|
|
6612
6904
|
process.exit(1);
|
|
6613
6905
|
}
|
|
6614
6906
|
const map = readJson(mapPath);
|
|
6615
|
-
const schemaError =
|
|
6907
|
+
const schemaError = validatePathMapShape(map);
|
|
6616
6908
|
if (schemaError !== null) {
|
|
6617
6909
|
fail(schemaError);
|
|
6618
6910
|
process.exit(1);
|
|
@@ -6630,8 +6922,8 @@ function resumeCmd(sessionId) {
|
|
|
6630
6922
|
}
|
|
6631
6923
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
6632
6924
|
for (const dir of readdirSync15(projectsRoot)) {
|
|
6633
|
-
const candidate =
|
|
6634
|
-
if (
|
|
6925
|
+
const candidate = join50(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
6926
|
+
if (existsSync43(candidate)) return candidate;
|
|
6635
6927
|
}
|
|
6636
6928
|
return null;
|
|
6637
6929
|
}
|
|
@@ -6647,26 +6939,6 @@ function extractRecordedCwd(jsonlPath) {
|
|
|
6647
6939
|
}
|
|
6648
6940
|
return null;
|
|
6649
6941
|
}
|
|
6650
|
-
function validatePathMap(raw) {
|
|
6651
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6652
|
-
return "path-map.json invalid schema: top-level value must be an object";
|
|
6653
|
-
}
|
|
6654
|
-
const projects = raw.projects;
|
|
6655
|
-
if (projects === null || typeof projects !== "object" || Array.isArray(projects)) {
|
|
6656
|
-
return 'path-map.json invalid schema: "projects" must be an object';
|
|
6657
|
-
}
|
|
6658
|
-
for (const [name, hosts] of Object.entries(projects)) {
|
|
6659
|
-
if (hosts === null || typeof hosts !== "object" || Array.isArray(hosts)) {
|
|
6660
|
-
return `path-map.json invalid schema: project "${name}" hosts must be an object`;
|
|
6661
|
-
}
|
|
6662
|
-
for (const [host, value] of Object.entries(hosts)) {
|
|
6663
|
-
if (typeof value !== "string") {
|
|
6664
|
-
return `path-map.json invalid schema: project "${name}" host "${host}" path must be a string`;
|
|
6665
|
-
}
|
|
6666
|
-
}
|
|
6667
|
-
}
|
|
6668
|
-
return null;
|
|
6669
|
-
}
|
|
6670
6942
|
function lookupLocalPath(map, recordedCwd) {
|
|
6671
6943
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
6672
6944
|
const isUnderMappedPath = Object.values(hosts).some(
|
|
@@ -6775,6 +7047,19 @@ try {
|
|
|
6775
7047
|
cmdEject({ dryRun: ejectArgs.dryRun });
|
|
6776
7048
|
break;
|
|
6777
7049
|
}
|
|
7050
|
+
case "capture-settings": {
|
|
7051
|
+
const captureArgs = parseCaptureSettingsArgs(process.argv);
|
|
7052
|
+
if (captureArgs === null) {
|
|
7053
|
+
console.error("usage: nomad capture-settings [--host] [--dry-run] [--yes]");
|
|
7054
|
+
process.exit(1);
|
|
7055
|
+
}
|
|
7056
|
+
await cmdCaptureSettings({
|
|
7057
|
+
host: captureArgs.host,
|
|
7058
|
+
dryRun: captureArgs.dryRun,
|
|
7059
|
+
yes: captureArgs.yes
|
|
7060
|
+
});
|
|
7061
|
+
break;
|
|
7062
|
+
}
|
|
6778
7063
|
case "doctor":
|
|
6779
7064
|
if (process.argv[3] === void 0) {
|
|
6780
7065
|
cmdDoctor();
|