claude-nomad 0.34.1 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +31 -0
- package/dist/nomad.mjs +594 -354
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -327,7 +327,7 @@ var init_settings_keys = __esm({
|
|
|
327
327
|
|
|
328
328
|
// src/config.ts
|
|
329
329
|
import { homedir, hostname } from "node:os";
|
|
330
|
-
import { resolve } from "node:path";
|
|
330
|
+
import { join, resolve } from "node:path";
|
|
331
331
|
function allSharedLinks(map) {
|
|
332
332
|
const extras = [];
|
|
333
333
|
for (const entry of map.sharedDirs ?? []) {
|
|
@@ -341,7 +341,7 @@ function allSharedLinks(map) {
|
|
|
341
341
|
}
|
|
342
342
|
return [...SHARED_LINKS, ...extras];
|
|
343
343
|
}
|
|
344
|
-
var HOME, CLAUDE_HOME, REPO_HOME, SETTINGS_SCHEMA_URL, GITLEAKS_PINNED_VERSION, HOST, SHARED_LINKS, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
344
|
+
var HOME, CLAUDE_HOME, BACKUP_BASE, REPO_HOME, SETTINGS_SCHEMA_URL, GITLEAKS_PINNED_VERSION, HOST, SHARED_LINKS, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
345
345
|
var init_config = __esm({
|
|
346
346
|
"src/config.ts"() {
|
|
347
347
|
"use strict";
|
|
@@ -350,6 +350,7 @@ var init_config = __esm({
|
|
|
350
350
|
init_settings_keys();
|
|
351
351
|
HOME = homedir();
|
|
352
352
|
CLAUDE_HOME = resolve(HOME, ".claude");
|
|
353
|
+
BACKUP_BASE = join(HOME, ".cache", "claude-nomad", "backup");
|
|
353
354
|
REPO_HOME = process.env.NOMAD_REPO || resolve(HOME, "claude-nomad");
|
|
354
355
|
SETTINGS_SCHEMA_URL = "https://json.schemastore.org/claude-code-settings.json";
|
|
355
356
|
GITLEAKS_PINNED_VERSION = "8.30.1";
|
|
@@ -463,7 +464,7 @@ import {
|
|
|
463
464
|
symlinkSync,
|
|
464
465
|
writeFileSync
|
|
465
466
|
} from "node:fs";
|
|
466
|
-
import { dirname, join, relative } from "node:path";
|
|
467
|
+
import { dirname, join as join2, relative } from "node:path";
|
|
467
468
|
function writeJsonAtomic(path, data) {
|
|
468
469
|
const mode = existsSync(path) ? statSync(path).mode & 511 : 384;
|
|
469
470
|
const tmp = `${path}.tmp.${process.pid}`;
|
|
@@ -489,9 +490,9 @@ function nowTimestamp() {
|
|
|
489
490
|
}
|
|
490
491
|
function freshBackupTs(backupRoot) {
|
|
491
492
|
const base = nowTimestamp();
|
|
492
|
-
if (!existsSync(
|
|
493
|
+
if (!existsSync(join2(backupRoot, base))) return base;
|
|
493
494
|
let n = 1;
|
|
494
|
-
while (existsSync(
|
|
495
|
+
while (existsSync(join2(backupRoot, `${base}-${n}`))) n++;
|
|
495
496
|
return `${base}-${n}`;
|
|
496
497
|
}
|
|
497
498
|
function ensureSymlink(linkPath, target) {
|
|
@@ -507,8 +508,8 @@ function backupBeforeWrite(absPath, ts) {
|
|
|
507
508
|
if (!existsSync(absPath)) return;
|
|
508
509
|
const rel = relative(CLAUDE_HOME, absPath);
|
|
509
510
|
if (rel.startsWith("..") || rel === "") return;
|
|
510
|
-
const backupRoot =
|
|
511
|
-
const dst =
|
|
511
|
+
const backupRoot = join2(HOME, ".cache", "claude-nomad", "backup", ts);
|
|
512
|
+
const dst = join2(backupRoot, rel);
|
|
512
513
|
mkdirSync(dirname(dst), { recursive: true });
|
|
513
514
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
514
515
|
}
|
|
@@ -516,8 +517,8 @@ function backupRepoWrite(absPath, ts, repoHome) {
|
|
|
516
517
|
if (!existsSync(absPath)) return;
|
|
517
518
|
const rel = relative(repoHome, absPath);
|
|
518
519
|
if (rel.startsWith("..") || rel === "") return;
|
|
519
|
-
const backupRoot =
|
|
520
|
-
const dst =
|
|
520
|
+
const backupRoot = join2(HOME, ".cache", "claude-nomad", "backup", ts, "repo");
|
|
521
|
+
const dst = join2(backupRoot, rel);
|
|
521
522
|
mkdirSync(dirname(dst), { recursive: true });
|
|
522
523
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
523
524
|
}
|
|
@@ -525,8 +526,8 @@ function backupExtrasWrite(absPath, ts, projectRoot) {
|
|
|
525
526
|
if (!existsSync(absPath)) return;
|
|
526
527
|
const rel = relative(projectRoot, absPath);
|
|
527
528
|
if (rel.startsWith("..") || rel === "") return;
|
|
528
|
-
const backupRoot =
|
|
529
|
-
const dst =
|
|
529
|
+
const backupRoot = join2(HOME, ".cache", "claude-nomad", "backup", ts, "extras");
|
|
530
|
+
const dst = join2(backupRoot, encodePath(projectRoot), rel);
|
|
530
531
|
mkdirSync(dirname(dst), { recursive: true });
|
|
531
532
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
532
533
|
}
|
|
@@ -541,15 +542,15 @@ var init_utils_fs = __esm({
|
|
|
541
542
|
|
|
542
543
|
// src/push-gitleaks.scan.ts
|
|
543
544
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
544
|
-
import { existsSync as
|
|
545
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync3 } from "node:fs";
|
|
545
546
|
import { homedir as homedir2 } from "node:os";
|
|
546
|
-
import { join as
|
|
547
|
+
import { join as join9 } from "node:path";
|
|
547
548
|
import { fileURLToPath } from "node:url";
|
|
548
549
|
function resolveTomlPath() {
|
|
549
|
-
const repoToml =
|
|
550
|
-
if (
|
|
550
|
+
const repoToml = join9(REPO_HOME, ".gitleaks.toml");
|
|
551
|
+
if (existsSync8(repoToml)) return repoToml;
|
|
551
552
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
552
|
-
return
|
|
553
|
+
return existsSync8(bundled) ? bundled : null;
|
|
553
554
|
}
|
|
554
555
|
function readGitleaksReport(reportPath) {
|
|
555
556
|
try {
|
|
@@ -562,9 +563,9 @@ function readGitleaksReport(reportPath) {
|
|
|
562
563
|
}
|
|
563
564
|
}
|
|
564
565
|
function scanStagedTree(repoDir, forwardStreams = false) {
|
|
565
|
-
const cacheDir =
|
|
566
|
+
const cacheDir = join9(homedir2(), ".cache", "claude-nomad");
|
|
566
567
|
mkdirSync2(cacheDir, { recursive: true });
|
|
567
|
-
const reportPath =
|
|
568
|
+
const reportPath = join9(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
|
|
568
569
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
569
570
|
const args = [
|
|
570
571
|
"protect",
|
|
@@ -591,14 +592,14 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
591
592
|
}
|
|
592
593
|
return report;
|
|
593
594
|
} finally {
|
|
594
|
-
if (tempPath !== null)
|
|
595
|
-
|
|
595
|
+
if (tempPath !== null) rmSync3(tempPath, { recursive: true, force: true });
|
|
596
|
+
rmSync3(reportPath, { force: true });
|
|
596
597
|
}
|
|
597
598
|
}
|
|
598
599
|
function scanFile(filePath, forwardStreams = false) {
|
|
599
|
-
const cacheDir =
|
|
600
|
+
const cacheDir = join9(homedir2(), ".cache", "claude-nomad");
|
|
600
601
|
mkdirSync2(cacheDir, { recursive: true });
|
|
601
|
-
const reportPath =
|
|
602
|
+
const reportPath = join9(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
|
|
602
603
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
603
604
|
const args = [
|
|
604
605
|
"detect",
|
|
@@ -623,8 +624,8 @@ function scanFile(filePath, forwardStreams = false) {
|
|
|
623
624
|
}
|
|
624
625
|
return report;
|
|
625
626
|
} finally {
|
|
626
|
-
if (tempPath !== null)
|
|
627
|
-
|
|
627
|
+
if (tempPath !== null) rmSync3(tempPath, { recursive: true, force: true });
|
|
628
|
+
rmSync3(reportPath, { force: true });
|
|
628
629
|
}
|
|
629
630
|
}
|
|
630
631
|
var init_push_gitleaks_scan = __esm({
|
|
@@ -637,24 +638,24 @@ var init_push_gitleaks_scan = __esm({
|
|
|
637
638
|
});
|
|
638
639
|
|
|
639
640
|
// src/push-gitleaks.config.ts
|
|
640
|
-
import { existsSync as
|
|
641
|
+
import { existsSync as existsSync9, mkdtempSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
641
642
|
import { tmpdir } from "node:os";
|
|
642
|
-
import { join as
|
|
643
|
+
import { join as join10 } from "node:path";
|
|
643
644
|
function buildOverlayTempConfig(overlayBody, bundled) {
|
|
644
645
|
const tempBody = `[extend]
|
|
645
646
|
path = ${JSON.stringify(bundled)}
|
|
646
647
|
|
|
647
648
|
${overlayBody}`;
|
|
648
|
-
const tempPath = mkdtempSync(
|
|
649
|
-
const configPath =
|
|
649
|
+
const tempPath = mkdtempSync(join10(tmpdir(), "nomad-gitleaks-cfg-"));
|
|
650
|
+
const configPath = join10(tempPath, "config.toml");
|
|
650
651
|
writeFileSync2(configPath, tempBody, { mode: 384, flag: "wx" });
|
|
651
652
|
return { configPath, tempPath };
|
|
652
653
|
}
|
|
653
654
|
function resolveTomlConfig() {
|
|
654
|
-
const overlayPath =
|
|
655
|
-
const repoToml =
|
|
655
|
+
const overlayPath = join10(REPO_HOME, ".gitleaks.overlay.toml");
|
|
656
|
+
const repoToml = join10(REPO_HOME, ".gitleaks.toml");
|
|
656
657
|
const bundled = resolveTomlPath();
|
|
657
|
-
if (!
|
|
658
|
+
if (!existsSync9(overlayPath)) {
|
|
658
659
|
return { path: bundled, tempPath: null };
|
|
659
660
|
}
|
|
660
661
|
if (bundled === repoToml) {
|
|
@@ -696,9 +697,9 @@ var init_push_gitleaks_config = __esm({
|
|
|
696
697
|
|
|
697
698
|
// src/push-checks.ts
|
|
698
699
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
699
|
-
import { readdirSync as
|
|
700
|
+
import { readdirSync as readdirSync3, rmSync as rmSync4 } from "node:fs";
|
|
700
701
|
import { homedir as homedir3, platform } from "node:os";
|
|
701
|
-
import { join as
|
|
702
|
+
import { join as join11 } from "node:path";
|
|
702
703
|
function gitleaksInstallHint() {
|
|
703
704
|
const head = "gitleaks not on PATH (required for nomad push). Install:";
|
|
704
705
|
const plat = platform();
|
|
@@ -736,12 +737,12 @@ function findGitlinks(dir) {
|
|
|
736
737
|
function walk(current) {
|
|
737
738
|
let entries;
|
|
738
739
|
try {
|
|
739
|
-
entries =
|
|
740
|
+
entries = readdirSync3(current, { withFileTypes: true });
|
|
740
741
|
} catch {
|
|
741
742
|
return;
|
|
742
743
|
}
|
|
743
744
|
for (const e of entries) {
|
|
744
|
-
const p =
|
|
745
|
+
const p = join11(current, e.name);
|
|
745
746
|
if (e.name === ".git") {
|
|
746
747
|
hits.push(p);
|
|
747
748
|
continue;
|
|
@@ -763,7 +764,7 @@ function probeGitleaks() {
|
|
|
763
764
|
if (e.code === "ENOENT") throw new NomadFatal(gitleaksInstallHint());
|
|
764
765
|
throw new NomadFatal(`gitleaks --version failed: ${e.message}`);
|
|
765
766
|
} finally {
|
|
766
|
-
if (tempPath !== null)
|
|
767
|
+
if (tempPath !== null) rmSync4(tempPath, { recursive: true, force: true });
|
|
767
768
|
}
|
|
768
769
|
}
|
|
769
770
|
function rebaseBeforePush() {
|
|
@@ -949,7 +950,7 @@ init_utils();
|
|
|
949
950
|
init_utils_fs();
|
|
950
951
|
init_utils_json();
|
|
951
952
|
import { cpSync as cpSync2, existsSync as existsSync2, lstatSync as lstatSync2, rmSync } from "node:fs";
|
|
952
|
-
import { join as
|
|
953
|
+
import { join as join3 } from "node:path";
|
|
953
954
|
var ADOPT_PUSH_HINT = "run `nomad push` to share with other hosts";
|
|
954
955
|
function lexists(p) {
|
|
955
956
|
try {
|
|
@@ -960,7 +961,7 @@ function lexists(p) {
|
|
|
960
961
|
}
|
|
961
962
|
}
|
|
962
963
|
function readMapIfPresent(repoHome) {
|
|
963
|
-
const mapPath =
|
|
964
|
+
const mapPath = join3(repoHome, "path-map.json");
|
|
964
965
|
return existsSync2(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
965
966
|
}
|
|
966
967
|
function isConfiguredTarget(name, map) {
|
|
@@ -971,13 +972,13 @@ function isValidAdoptName(name) {
|
|
|
971
972
|
return isValidSharedDir(name);
|
|
972
973
|
}
|
|
973
974
|
function performAdoptMove(name, linkPath, sharedTarget) {
|
|
974
|
-
const backupBase =
|
|
975
|
+
const backupBase = join3(HOME, ".cache", "claude-nomad", "backup");
|
|
975
976
|
const ts = freshBackupTs(backupBase);
|
|
976
977
|
backupBeforeWrite(linkPath, ts);
|
|
977
978
|
cpSync2(linkPath, sharedTarget, { recursive: true, force: true, preserveTimestamps: true });
|
|
978
979
|
rmSync(linkPath, { recursive: true, force: true });
|
|
979
980
|
ensureSymlink(linkPath, sharedTarget);
|
|
980
|
-
const rel =
|
|
981
|
+
const rel = join3("shared", name);
|
|
981
982
|
gitOrFatal(["add", "--", rel], `git add shared/${name}`, REPO_HOME);
|
|
982
983
|
log(`adopted ${name}; ${ADOPT_PUSH_HINT}`);
|
|
983
984
|
}
|
|
@@ -994,8 +995,8 @@ function cmdAdopt(name, opts = {}) {
|
|
|
994
995
|
);
|
|
995
996
|
process.exit(1);
|
|
996
997
|
}
|
|
997
|
-
const linkPath =
|
|
998
|
-
const sharedTarget =
|
|
998
|
+
const linkPath = join3(CLAUDE_HOME, name);
|
|
999
|
+
const sharedTarget = join3(REPO_HOME, "shared", name);
|
|
999
1000
|
if (!existsSync2(linkPath)) {
|
|
1000
1001
|
log(`${name}: nothing to adopt (not present in ~/.claude/)`);
|
|
1001
1002
|
return;
|
|
@@ -1009,7 +1010,7 @@ function cmdAdopt(name, opts = {}) {
|
|
|
1009
1010
|
process.exit(1);
|
|
1010
1011
|
}
|
|
1011
1012
|
if (dryRun) {
|
|
1012
|
-
const backupBase =
|
|
1013
|
+
const backupBase = join3(HOME, ".cache", "claude-nomad", "backup");
|
|
1013
1014
|
const ts = freshBackupTs(backupBase);
|
|
1014
1015
|
log(`would backup: ${linkPath} -> backup/${ts}/${name}`);
|
|
1015
1016
|
log(`would move: ${linkPath} -> shared/${name}`);
|
|
@@ -1025,15 +1026,79 @@ function cmdAdopt(name, opts = {}) {
|
|
|
1025
1026
|
}
|
|
1026
1027
|
}
|
|
1027
1028
|
|
|
1029
|
+
// src/commands.clean.ts
|
|
1030
|
+
init_config();
|
|
1031
|
+
init_utils();
|
|
1032
|
+
import { existsSync as existsSync3, lstatSync as lstatSync3, readdirSync, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
|
|
1033
|
+
import { join as join4 } from "node:path";
|
|
1034
|
+
var TS_SHAPE = /^\d{8}-\d{6}(-\d+)?$/;
|
|
1035
|
+
var DURATION_RE = /^(\d+)([dhm])$/;
|
|
1036
|
+
var UNIT_MS = { d: 864e5, h: 36e5, m: 6e4 };
|
|
1037
|
+
var CLEAN_DEFAULT_OLDER_THAN_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
1038
|
+
function isTsDir(name) {
|
|
1039
|
+
return TS_SHAPE.test(name);
|
|
1040
|
+
}
|
|
1041
|
+
function parseDuration(s) {
|
|
1042
|
+
const m = DURATION_RE.exec(s);
|
|
1043
|
+
if (!m) return null;
|
|
1044
|
+
return Number(m[1]) * UNIT_MS[m[2]];
|
|
1045
|
+
}
|
|
1046
|
+
function listBackupDirs(backupBase) {
|
|
1047
|
+
if (!existsSync3(backupBase)) return [];
|
|
1048
|
+
return readdirSync(backupBase).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(join4(backupBase, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1049
|
+
}
|
|
1050
|
+
function prunableByAge(dirs, olderThanMs, nowMs) {
|
|
1051
|
+
return dirs.filter((d) => nowMs - d.mtimeMs > olderThanMs).map((d) => d.name);
|
|
1052
|
+
}
|
|
1053
|
+
function prunableByCount(dirs, keep) {
|
|
1054
|
+
return dirs.slice(keep).map((d) => d.name);
|
|
1055
|
+
}
|
|
1056
|
+
function safeDelete(backupBase, name) {
|
|
1057
|
+
if (!isTsDir(name)) return;
|
|
1058
|
+
const full = join4(backupBase, name);
|
|
1059
|
+
const st = lstatSync3(full, { throwIfNoEntry: false });
|
|
1060
|
+
if (!st || st.isSymbolicLink() || !st.isDirectory()) return;
|
|
1061
|
+
rmSync2(full, { recursive: true, force: true });
|
|
1062
|
+
}
|
|
1063
|
+
function resolveTargets(dirs, olderThanMs, keep) {
|
|
1064
|
+
if (keep !== void 0) return prunableByCount(dirs, keep);
|
|
1065
|
+
return prunableByAge(dirs, olderThanMs, Date.now());
|
|
1066
|
+
}
|
|
1067
|
+
function cmdClean(opts, backupBase = BACKUP_BASE) {
|
|
1068
|
+
const { dryRun, olderThan, keep } = opts;
|
|
1069
|
+
if (olderThan !== void 0 && keep !== void 0) {
|
|
1070
|
+
fail("--older-than and --keep are mutually exclusive");
|
|
1071
|
+
process.exit(1);
|
|
1072
|
+
}
|
|
1073
|
+
let olderThanMs = CLEAN_DEFAULT_OLDER_THAN_MS;
|
|
1074
|
+
if (olderThan !== void 0) {
|
|
1075
|
+
const parsed = parseDuration(olderThan);
|
|
1076
|
+
if (parsed === null) {
|
|
1077
|
+
fail(`invalid --older-than duration: ${olderThan} (expected e.g. 14d, 24h, 30m)`);
|
|
1078
|
+
process.exit(1);
|
|
1079
|
+
}
|
|
1080
|
+
olderThanMs = parsed;
|
|
1081
|
+
}
|
|
1082
|
+
const dirs = listBackupDirs(backupBase);
|
|
1083
|
+
const targets = resolveTargets(dirs, olderThanMs, keep);
|
|
1084
|
+
if (dryRun) {
|
|
1085
|
+
for (const name of targets) log(`would remove ${name}`);
|
|
1086
|
+
log(`dry-run: ${targets.length} backup(s) would be removed`);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
for (const name of targets) safeDelete(backupBase, name);
|
|
1090
|
+
log(`removed ${targets.length} backup(s)`);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1028
1093
|
// src/commands.doctor.ts
|
|
1029
|
-
import { existsSync as
|
|
1030
|
-
import { join as
|
|
1094
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
1095
|
+
import { join as join21 } from "node:path";
|
|
1031
1096
|
|
|
1032
1097
|
// src/commands.doctor.checks.repo.ts
|
|
1033
1098
|
init_color();
|
|
1034
1099
|
init_config();
|
|
1035
|
-
import { existsSync as
|
|
1036
|
-
import { join as
|
|
1100
|
+
import { existsSync as existsSync5, lstatSync as lstatSync4, statSync as statSync3 } from "node:fs";
|
|
1101
|
+
import { join as join6 } from "node:path";
|
|
1037
1102
|
|
|
1038
1103
|
// src/commands.doctor.format.ts
|
|
1039
1104
|
init_color();
|
|
@@ -1083,15 +1148,15 @@ function readJsonSafe(path, label, section2) {
|
|
|
1083
1148
|
// src/init.classify.ts
|
|
1084
1149
|
init_config();
|
|
1085
1150
|
init_utils_json();
|
|
1086
|
-
import { existsSync as
|
|
1087
|
-
import { join as
|
|
1151
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1152
|
+
import { join as join5 } from "node:path";
|
|
1088
1153
|
function classifyRepoState(repoHome, host) {
|
|
1089
|
-
const basePath =
|
|
1090
|
-
const mapPath =
|
|
1091
|
-
const hostPath =
|
|
1092
|
-
const hasBase =
|
|
1093
|
-
const hasMap =
|
|
1094
|
-
const hasHost =
|
|
1154
|
+
const basePath = join5(repoHome, "shared", "settings.base.json");
|
|
1155
|
+
const mapPath = join5(repoHome, "path-map.json");
|
|
1156
|
+
const hostPath = join5(repoHome, "hosts", `${host}.json`);
|
|
1157
|
+
const hasBase = existsSync4(basePath);
|
|
1158
|
+
const hasMap = existsSync4(mapPath);
|
|
1159
|
+
const hasHost = existsSync4(hostPath);
|
|
1095
1160
|
let mapEntryCount = 0;
|
|
1096
1161
|
if (hasMap) {
|
|
1097
1162
|
try {
|
|
@@ -1106,11 +1171,11 @@ function classifyRepoState(repoHome, host) {
|
|
|
1106
1171
|
return "partial";
|
|
1107
1172
|
}
|
|
1108
1173
|
function reasonForPartial(repoHome, host) {
|
|
1109
|
-
const basePath =
|
|
1110
|
-
const mapPath =
|
|
1111
|
-
const hostPath =
|
|
1112
|
-
if (!
|
|
1113
|
-
if (!
|
|
1174
|
+
const basePath = join5(repoHome, "shared", "settings.base.json");
|
|
1175
|
+
const mapPath = join5(repoHome, "path-map.json");
|
|
1176
|
+
const hostPath = join5(repoHome, "hosts", `${host}.json`);
|
|
1177
|
+
if (!existsSync4(basePath)) return "- shared/settings.base.json missing";
|
|
1178
|
+
if (!existsSync4(mapPath)) return "- path-map.json missing";
|
|
1114
1179
|
let mapEntryCount;
|
|
1115
1180
|
try {
|
|
1116
1181
|
const map = readJson(mapPath);
|
|
@@ -1119,7 +1184,7 @@ function reasonForPartial(repoHome, host) {
|
|
|
1119
1184
|
mapEntryCount = 0;
|
|
1120
1185
|
}
|
|
1121
1186
|
if (mapEntryCount === 0) return "- path-map.json.projects has no entries";
|
|
1122
|
-
if (!
|
|
1187
|
+
if (!existsSync4(hostPath)) return `- hosts/${host}.json missing`;
|
|
1123
1188
|
return "- partial state (unknown gap)";
|
|
1124
1189
|
}
|
|
1125
1190
|
|
|
@@ -1131,11 +1196,11 @@ function reportHostAndPaths(section2) {
|
|
|
1131
1196
|
addItem(section2, `${dim(infoGlyph)} host: ${cyan(HOST)}`);
|
|
1132
1197
|
addItem(
|
|
1133
1198
|
section2,
|
|
1134
|
-
`${
|
|
1199
|
+
`${existsSync5(REPO_HOME) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(REPO_HOME)}`
|
|
1135
1200
|
);
|
|
1136
1201
|
addItem(
|
|
1137
1202
|
section2,
|
|
1138
|
-
`${
|
|
1203
|
+
`${existsSync5(CLAUDE_HOME) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(CLAUDE_HOME)}`
|
|
1139
1204
|
);
|
|
1140
1205
|
}
|
|
1141
1206
|
function reportRepoState(section2) {
|
|
@@ -1157,12 +1222,12 @@ function reportRepoState(section2) {
|
|
|
1157
1222
|
}
|
|
1158
1223
|
}
|
|
1159
1224
|
function repoHasSharedSource(name) {
|
|
1160
|
-
return
|
|
1225
|
+
return existsSync5(join6(REPO_HOME, "shared", name));
|
|
1161
1226
|
}
|
|
1162
1227
|
function classifySharedLink(name, p) {
|
|
1163
1228
|
let stat;
|
|
1164
1229
|
try {
|
|
1165
|
-
stat =
|
|
1230
|
+
stat = lstatSync4(p);
|
|
1166
1231
|
} catch (err) {
|
|
1167
1232
|
const code = err.code;
|
|
1168
1233
|
if (code === "ENOENT") {
|
|
@@ -1183,7 +1248,7 @@ function classifySharedLink(name, p) {
|
|
|
1183
1248
|
}
|
|
1184
1249
|
function classifySymlinkTarget(name, p) {
|
|
1185
1250
|
try {
|
|
1186
|
-
|
|
1251
|
+
statSync3(p);
|
|
1187
1252
|
return { line: `${green(okGlyph)} ${name}: symlink`, fail: false };
|
|
1188
1253
|
} catch (err) {
|
|
1189
1254
|
const code = err.code;
|
|
@@ -1204,7 +1269,7 @@ function classifySymlinkTarget(name, p) {
|
|
|
1204
1269
|
}
|
|
1205
1270
|
function reportSharedLinks(section2, map) {
|
|
1206
1271
|
for (const name of allSharedLinks(map)) {
|
|
1207
|
-
const p =
|
|
1272
|
+
const p = join6(CLAUDE_HOME, name);
|
|
1208
1273
|
const { line, fail: fail2 } = classifySharedLink(name, p);
|
|
1209
1274
|
addItem(section2, line);
|
|
1210
1275
|
if (fail2) process.exitCode = 1;
|
|
@@ -1214,11 +1279,11 @@ function reportSharedLinks(section2, map) {
|
|
|
1214
1279
|
// src/commands.doctor.checks.settings.ts
|
|
1215
1280
|
init_color();
|
|
1216
1281
|
init_config();
|
|
1217
|
-
import { existsSync as
|
|
1218
|
-
import { join as
|
|
1282
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
|
|
1283
|
+
import { join as join7 } from "node:path";
|
|
1219
1284
|
function loadBaseSettings(section2) {
|
|
1220
|
-
const basePath =
|
|
1221
|
-
if (!
|
|
1285
|
+
const basePath = join7(REPO_HOME, "shared", "settings.base.json");
|
|
1286
|
+
if (!existsSync6(basePath)) {
|
|
1222
1287
|
addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
|
|
1223
1288
|
process.exitCode = 1;
|
|
1224
1289
|
return null;
|
|
@@ -1226,8 +1291,8 @@ function loadBaseSettings(section2) {
|
|
|
1226
1291
|
return readJsonSafe(basePath, basePath, section2);
|
|
1227
1292
|
}
|
|
1228
1293
|
function loadAndReportSettings(section2) {
|
|
1229
|
-
const settingsPath =
|
|
1230
|
-
if (!
|
|
1294
|
+
const settingsPath = join7(CLAUDE_HOME, "settings.json");
|
|
1295
|
+
if (!existsSync6(settingsPath)) return null;
|
|
1231
1296
|
const settings = readJsonSafe(settingsPath, settingsPath, section2);
|
|
1232
1297
|
if (settings === null) return null;
|
|
1233
1298
|
const unknownKeys = Object.keys(settings).filter((k) => !KNOWN_SETTINGS_KEYS.has(k));
|
|
@@ -1242,13 +1307,13 @@ function loadAndReportSettings(section2) {
|
|
|
1242
1307
|
return settings;
|
|
1243
1308
|
}
|
|
1244
1309
|
function reportHostOverrides(section2, base, settings) {
|
|
1245
|
-
const hostFile =
|
|
1310
|
+
const hostFile = join7(REPO_HOME, "hosts", `${HOST}.json`);
|
|
1246
1311
|
let drift = [];
|
|
1247
1312
|
if (base !== null && settings !== null) {
|
|
1248
1313
|
const baseKeys = new Set(Object.keys(base));
|
|
1249
1314
|
drift = Object.keys(settings).filter((k) => !baseKeys.has(k));
|
|
1250
1315
|
}
|
|
1251
|
-
if (
|
|
1316
|
+
if (existsSync6(hostFile)) {
|
|
1252
1317
|
if (readJsonSafe(hostFile, hostFile, section2) !== null) {
|
|
1253
1318
|
addItem(section2, `${green(okGlyph)} host overrides: ${blue(hostFile)}`);
|
|
1254
1319
|
}
|
|
@@ -1257,9 +1322,9 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
1257
1322
|
section2,
|
|
1258
1323
|
`${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
|
|
1259
1324
|
);
|
|
1260
|
-
const hostsDir =
|
|
1261
|
-
if (
|
|
1262
|
-
const cands =
|
|
1325
|
+
const hostsDir = join7(REPO_HOME, "hosts");
|
|
1326
|
+
if (existsSync6(hostsDir)) {
|
|
1327
|
+
const cands = readdirSync2(hostsDir).filter((f) => f.endsWith(".json"));
|
|
1263
1328
|
if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
|
|
1264
1329
|
}
|
|
1265
1330
|
process.exitCode = 1;
|
|
@@ -1274,8 +1339,8 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
1274
1339
|
// src/commands.doctor.checks.pathmap.ts
|
|
1275
1340
|
init_color();
|
|
1276
1341
|
init_config();
|
|
1277
|
-
import { existsSync as
|
|
1278
|
-
import { join as
|
|
1342
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1343
|
+
import { join as join8 } from "node:path";
|
|
1279
1344
|
init_utils_json();
|
|
1280
1345
|
function reportMappedProjects(section2, map) {
|
|
1281
1346
|
const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
|
|
@@ -1310,8 +1375,8 @@ function reportPathCollisions(section2, map) {
|
|
|
1310
1375
|
else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
|
|
1311
1376
|
}
|
|
1312
1377
|
function reportPathMap(section2) {
|
|
1313
|
-
const mapPath =
|
|
1314
|
-
if (!
|
|
1378
|
+
const mapPath = join8(REPO_HOME, "path-map.json");
|
|
1379
|
+
if (!existsSync7(mapPath)) {
|
|
1315
1380
|
addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
|
|
1316
1381
|
process.exitCode = 1;
|
|
1317
1382
|
return;
|
|
@@ -1358,8 +1423,8 @@ function reportNeverSync(section2) {
|
|
|
1358
1423
|
init_color();
|
|
1359
1424
|
init_config();
|
|
1360
1425
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
1361
|
-
import { existsSync as
|
|
1362
|
-
import { join as
|
|
1426
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
1427
|
+
import { join as join12, relative as relative2 } from "node:path";
|
|
1363
1428
|
init_push_checks();
|
|
1364
1429
|
init_utils();
|
|
1365
1430
|
function reportGitleaksProbe(section2) {
|
|
@@ -1378,8 +1443,8 @@ function reportGitleaksProbe(section2) {
|
|
|
1378
1443
|
}
|
|
1379
1444
|
}
|
|
1380
1445
|
function reportGitlinks(section2) {
|
|
1381
|
-
const sharedDir =
|
|
1382
|
-
if (
|
|
1446
|
+
const sharedDir = join12(REPO_HOME, "shared");
|
|
1447
|
+
if (existsSync10(sharedDir)) {
|
|
1383
1448
|
const gitlinks = findGitlinks(sharedDir);
|
|
1384
1449
|
for (const p of gitlinks) {
|
|
1385
1450
|
const rel = relative2(REPO_HOME, p);
|
|
@@ -1419,11 +1484,57 @@ function reportRebaseClean(section2) {
|
|
|
1419
1484
|
}
|
|
1420
1485
|
}
|
|
1421
1486
|
|
|
1487
|
+
// src/commands.doctor.checks.backups.ts
|
|
1488
|
+
init_color();
|
|
1489
|
+
import { existsSync as existsSync11, lstatSync as lstatSync5, readdirSync as readdirSync4 } from "node:fs";
|
|
1490
|
+
import { join as join13 } from "node:path";
|
|
1491
|
+
init_config();
|
|
1492
|
+
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
1493
|
+
function safeReaddir(dir) {
|
|
1494
|
+
try {
|
|
1495
|
+
return readdirSync4(dir);
|
|
1496
|
+
} catch {
|
|
1497
|
+
return [];
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
var DOCTOR_BACKUP_COUNT_WARN = 20;
|
|
1501
|
+
var DOCTOR_BACKUP_SIZE_WARN_MB = 200;
|
|
1502
|
+
var BYTES_PER_MB = 1024 * 1024;
|
|
1503
|
+
function dirSizeBytes(dir) {
|
|
1504
|
+
let bytes = 0;
|
|
1505
|
+
for (const entry of safeReaddir(dir)) {
|
|
1506
|
+
const full = join13(dir, entry);
|
|
1507
|
+
const st = lstatSync5(full, { throwIfNoEntry: false });
|
|
1508
|
+
if (!st) continue;
|
|
1509
|
+
if (st.isSymbolicLink()) continue;
|
|
1510
|
+
if (st.isDirectory()) bytes += dirSizeBytes(full);
|
|
1511
|
+
else bytes += st.size;
|
|
1512
|
+
}
|
|
1513
|
+
return bytes;
|
|
1514
|
+
}
|
|
1515
|
+
function totalSizeMb(backupBase, dirs) {
|
|
1516
|
+
let bytes = 0;
|
|
1517
|
+
for (const name of dirs) bytes += dirSizeBytes(join13(backupBase, name));
|
|
1518
|
+
return bytes / BYTES_PER_MB;
|
|
1519
|
+
}
|
|
1520
|
+
function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
|
|
1521
|
+
if (!existsSync11(backupBase)) return;
|
|
1522
|
+
const dirs = safeReaddir(backupBase).filter((n) => TS_SHAPE2.test(n));
|
|
1523
|
+
const count = dirs.length;
|
|
1524
|
+
const sizeMb = totalSizeMb(backupBase, dirs);
|
|
1525
|
+
if (count > DOCTOR_BACKUP_COUNT_WARN || sizeMb > DOCTOR_BACKUP_SIZE_WARN_MB) {
|
|
1526
|
+
addItem(
|
|
1527
|
+
section2,
|
|
1528
|
+
`${yellow(warnGlyph)} backups: ${count} dirs / ${sizeMb.toFixed(1)} MB (run 'nomad clean --backups')`
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1422
1533
|
// src/commands.doctor.check-schema.ts
|
|
1423
1534
|
init_color();
|
|
1424
1535
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
1425
|
-
import { existsSync as
|
|
1426
|
-
import { join as
|
|
1536
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
1537
|
+
import { join as join14 } from "node:path";
|
|
1427
1538
|
init_config();
|
|
1428
1539
|
function fetchSchemaKeys() {
|
|
1429
1540
|
try {
|
|
@@ -1438,8 +1549,8 @@ function fetchSchemaKeys() {
|
|
|
1438
1549
|
}
|
|
1439
1550
|
}
|
|
1440
1551
|
function reportCheckSchema(section2) {
|
|
1441
|
-
const settingsPath =
|
|
1442
|
-
if (!
|
|
1552
|
+
const settingsPath = join14(CLAUDE_HOME, "settings.json");
|
|
1553
|
+
if (!existsSync12(settingsPath)) {
|
|
1443
1554
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
1444
1555
|
return;
|
|
1445
1556
|
}
|
|
@@ -1469,18 +1580,18 @@ function reportCheckSchema(section2) {
|
|
|
1469
1580
|
init_color();
|
|
1470
1581
|
import { randomBytes } from "node:crypto";
|
|
1471
1582
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
1472
|
-
import { existsSync as
|
|
1583
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync4, readdirSync as readdirSync6, rmSync as rmSync6 } from "node:fs";
|
|
1473
1584
|
import { homedir as homedir4 } from "node:os";
|
|
1474
|
-
import { join as
|
|
1585
|
+
import { join as join17 } from "node:path";
|
|
1475
1586
|
|
|
1476
1587
|
// src/commands.doctor.check-shared.scan.ts
|
|
1477
1588
|
init_color();
|
|
1478
|
-
import { join as
|
|
1589
|
+
import { join as join15 } from "node:path";
|
|
1479
1590
|
init_config();
|
|
1480
1591
|
init_push_gitleaks();
|
|
1481
1592
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
1482
1593
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
1483
|
-
return
|
|
1594
|
+
return join15(CLAUDE_HOME, "projects", encoded, `${sid}.jsonl`);
|
|
1484
1595
|
}
|
|
1485
1596
|
function reportSessionFindings(section2, bySession) {
|
|
1486
1597
|
for (const [sid, counts] of bySession) {
|
|
@@ -1572,14 +1683,14 @@ init_config();
|
|
|
1572
1683
|
init_utils();
|
|
1573
1684
|
init_utils_fs();
|
|
1574
1685
|
init_utils_json();
|
|
1575
|
-
import { cpSync as cpSync3, existsSync as
|
|
1576
|
-
import { join as
|
|
1686
|
+
import { cpSync as cpSync3, existsSync as existsSync13, mkdirSync as mkdirSync3, readdirSync as readdirSync5, rmSync as rmSync5, statSync as statSync4 } from "node:fs";
|
|
1687
|
+
import { join as join16, relative as relative3, sep } from "node:path";
|
|
1577
1688
|
function copyDir(src, dst) {
|
|
1578
|
-
|
|
1689
|
+
rmSync5(dst, { recursive: true, force: true });
|
|
1579
1690
|
cpSync3(src, dst, { recursive: true, force: true });
|
|
1580
1691
|
}
|
|
1581
1692
|
function copyDirJsonlOnly(src, dst) {
|
|
1582
|
-
|
|
1693
|
+
rmSync5(dst, { recursive: true, force: true });
|
|
1583
1694
|
cpSync3(src, dst, {
|
|
1584
1695
|
recursive: true,
|
|
1585
1696
|
force: true,
|
|
@@ -1587,7 +1698,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
1587
1698
|
const rel = relative3(src, srcPath);
|
|
1588
1699
|
if (rel === "") return true;
|
|
1589
1700
|
if (rel.split(sep).length > 1) return true;
|
|
1590
|
-
if (
|
|
1701
|
+
if (statSync4(srcPath).isDirectory()) return true;
|
|
1591
1702
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
1592
1703
|
log(`skip ${rel}: extension not in allowlist`);
|
|
1593
1704
|
return false;
|
|
@@ -1599,14 +1710,14 @@ function remapPull(ts, opts = {}) {
|
|
|
1599
1710
|
let unmapped = 0;
|
|
1600
1711
|
const pulled = [];
|
|
1601
1712
|
const wouldPull = [];
|
|
1602
|
-
const mapPath =
|
|
1603
|
-
const repoProjects =
|
|
1604
|
-
if (!
|
|
1713
|
+
const mapPath = join16(REPO_HOME, "path-map.json");
|
|
1714
|
+
const repoProjects = join16(REPO_HOME, "shared", "projects");
|
|
1715
|
+
if (!existsSync13(mapPath) || !existsSync13(repoProjects)) {
|
|
1605
1716
|
log("no path-map or repo projects dir; skipping session remap");
|
|
1606
1717
|
return { unmapped: 0, pulled, wouldPull };
|
|
1607
1718
|
}
|
|
1608
1719
|
const map = readJson(mapPath);
|
|
1609
|
-
const localProjects =
|
|
1720
|
+
const localProjects = join16(CLAUDE_HOME, "projects");
|
|
1610
1721
|
if (!dryRun) mkdirSync3(localProjects, { recursive: true });
|
|
1611
1722
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
1612
1723
|
assertSafeLogical(logical);
|
|
@@ -1615,9 +1726,9 @@ function remapPull(ts, opts = {}) {
|
|
|
1615
1726
|
unmapped++;
|
|
1616
1727
|
continue;
|
|
1617
1728
|
}
|
|
1618
|
-
const src =
|
|
1619
|
-
if (!
|
|
1620
|
-
const dst =
|
|
1729
|
+
const src = join16(repoProjects, logical);
|
|
1730
|
+
if (!existsSync13(src)) continue;
|
|
1731
|
+
const dst = join16(localProjects, encodePath(localPath));
|
|
1621
1732
|
if (dryRun) {
|
|
1622
1733
|
wouldPull.push(logical);
|
|
1623
1734
|
log(`would overwrite: ${dst} (from ${src})`);
|
|
@@ -1658,30 +1769,30 @@ function remapPush(ts, opts = {}) {
|
|
|
1658
1769
|
let unmapped = 0;
|
|
1659
1770
|
const pushed = [];
|
|
1660
1771
|
const wouldPush = [];
|
|
1661
|
-
const mapPath =
|
|
1662
|
-
if (!
|
|
1772
|
+
const mapPath = join16(REPO_HOME, "path-map.json");
|
|
1773
|
+
if (!existsSync13(mapPath)) {
|
|
1663
1774
|
log("no path-map.json; skipping session export");
|
|
1664
1775
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
1665
1776
|
}
|
|
1666
1777
|
const map = readJson(mapPath);
|
|
1667
|
-
const localProjects =
|
|
1668
|
-
const repoProjects =
|
|
1778
|
+
const localProjects = join16(CLAUDE_HOME, "projects");
|
|
1779
|
+
const repoProjects = join16(REPO_HOME, "shared", "projects");
|
|
1669
1780
|
const reverse = buildReverseMap(map);
|
|
1670
|
-
if (!
|
|
1781
|
+
if (!existsSync13(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
1671
1782
|
if (!dryRun) mkdirSync3(repoProjects, { recursive: true });
|
|
1672
|
-
for (const dir of
|
|
1783
|
+
for (const dir of readdirSync5(localProjects)) {
|
|
1673
1784
|
const logical = reverse.get(dir);
|
|
1674
1785
|
if (!logical) {
|
|
1675
1786
|
unmapped++;
|
|
1676
1787
|
continue;
|
|
1677
1788
|
}
|
|
1678
|
-
const repoDst =
|
|
1789
|
+
const repoDst = join16(repoProjects, logical);
|
|
1679
1790
|
if (dryRun) {
|
|
1680
1791
|
wouldPush.push(logical);
|
|
1681
1792
|
continue;
|
|
1682
1793
|
}
|
|
1683
1794
|
backupRepoWrite(repoDst, ts, REPO_HOME);
|
|
1684
|
-
copyDirJsonlOnly(
|
|
1795
|
+
copyDirJsonlOnly(join16(localProjects, dir), repoDst);
|
|
1685
1796
|
pushed.push(logical);
|
|
1686
1797
|
}
|
|
1687
1798
|
return { unmapped, collisions: 0, pushed, wouldPush };
|
|
@@ -1693,8 +1804,8 @@ init_utils_json();
|
|
|
1693
1804
|
function buildScanTree(tmpRoot) {
|
|
1694
1805
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
1695
1806
|
let staged = 0;
|
|
1696
|
-
const mapPath =
|
|
1697
|
-
if (!
|
|
1807
|
+
const mapPath = join17(REPO_HOME, "path-map.json");
|
|
1808
|
+
if (!existsSync14(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
1698
1809
|
let map;
|
|
1699
1810
|
try {
|
|
1700
1811
|
map = readJson(mapPath);
|
|
@@ -1711,12 +1822,12 @@ function buildScanTree(tmpRoot) {
|
|
|
1711
1822
|
if (!p || p === "TBD") continue;
|
|
1712
1823
|
reverse.set(encodePath(p), logical);
|
|
1713
1824
|
}
|
|
1714
|
-
const localProjects =
|
|
1715
|
-
if (!
|
|
1716
|
-
for (const dir of
|
|
1825
|
+
const localProjects = join17(CLAUDE_HOME, "projects");
|
|
1826
|
+
if (!existsSync14(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
1827
|
+
for (const dir of readdirSync6(localProjects)) {
|
|
1717
1828
|
const logical = reverse.get(dir);
|
|
1718
1829
|
if (!logical) continue;
|
|
1719
|
-
copyDirJsonlOnly(
|
|
1830
|
+
copyDirJsonlOnly(join17(localProjects, dir), join17(tmpRoot, "shared", "projects", logical));
|
|
1720
1831
|
logicalToEncoded.set(logical, dir);
|
|
1721
1832
|
staged++;
|
|
1722
1833
|
}
|
|
@@ -1747,11 +1858,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
1747
1858
|
}
|
|
1748
1859
|
function reportCheckShared(section2, gitleaksReady) {
|
|
1749
1860
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
1750
|
-
const cacheDir =
|
|
1861
|
+
const cacheDir = join17(homedir4(), ".cache", "claude-nomad");
|
|
1751
1862
|
mkdirSync4(cacheDir, { recursive: true });
|
|
1752
1863
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
1753
|
-
const reportPath =
|
|
1754
|
-
const tmpRoot =
|
|
1864
|
+
const reportPath = join17(cacheDir, `check-shared-${stamp}.json`);
|
|
1865
|
+
const tmpRoot = join17(cacheDir, `check-shared-tree-${stamp}`);
|
|
1755
1866
|
try {
|
|
1756
1867
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
1757
1868
|
if (malformed) {
|
|
@@ -1765,15 +1876,109 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
1765
1876
|
}
|
|
1766
1877
|
scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
|
|
1767
1878
|
} finally {
|
|
1768
|
-
|
|
1769
|
-
|
|
1879
|
+
rmSync6(reportPath, { force: true });
|
|
1880
|
+
rmSync6(tmpRoot, { recursive: true, force: true });
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// src/commands.doctor.checks.hooks.scope.ts
|
|
1885
|
+
init_color();
|
|
1886
|
+
import { existsSync as existsSync15, readFileSync as readFileSync4, readdirSync as readdirSync7, realpathSync } from "node:fs";
|
|
1887
|
+
import { dirname as dirname2, extname, join as join18 } from "node:path";
|
|
1888
|
+
init_config();
|
|
1889
|
+
function typeFromPackageJson(pkgPath) {
|
|
1890
|
+
try {
|
|
1891
|
+
const parsed = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
1892
|
+
return parsed.type === "module" ? "esm" : "cjs";
|
|
1893
|
+
} catch {
|
|
1894
|
+
return "cjs";
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
function effectiveType(hookPath) {
|
|
1898
|
+
const ext = extname(hookPath);
|
|
1899
|
+
if (ext === ".mjs") return "esm";
|
|
1900
|
+
if (ext === ".cjs") return "cjs";
|
|
1901
|
+
let real;
|
|
1902
|
+
try {
|
|
1903
|
+
real = realpathSync(hookPath);
|
|
1904
|
+
} catch {
|
|
1905
|
+
return null;
|
|
1906
|
+
}
|
|
1907
|
+
let dir = dirname2(real);
|
|
1908
|
+
for (; ; ) {
|
|
1909
|
+
const pkg = join18(dir, "package.json");
|
|
1910
|
+
if (existsSync15(pkg)) return typeFromPackageJson(pkg);
|
|
1911
|
+
const parent = dirname2(dir);
|
|
1912
|
+
if (parent === dir) return "cjs";
|
|
1913
|
+
dir = parent;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
function stripCommentsAndStrings(src) {
|
|
1917
|
+
return src.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g, (match) => {
|
|
1918
|
+
const open = match[0];
|
|
1919
|
+
if (open === "'") return "''";
|
|
1920
|
+
if (open === '"') return '""';
|
|
1921
|
+
if (open === "`") return "``";
|
|
1922
|
+
return " ";
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
function classifySource(src) {
|
|
1926
|
+
const code = stripCommentsAndStrings(src);
|
|
1927
|
+
const cjs = /\brequire\s*\(/.test(code) || /\bmodule\.exports\b/.test(code) || /\bexports\.\w/.test(code);
|
|
1928
|
+
const esm = /^[^\S\r\n]*import\s/m.test(code) || /^[^\S\r\n]*export\s/m.test(code) || /\bimport\.meta\b/.test(code);
|
|
1929
|
+
if (cjs && !esm) return "cjs";
|
|
1930
|
+
if (esm && !cjs) return "esm";
|
|
1931
|
+
if (cjs && esm) return "cjs";
|
|
1932
|
+
return "unknown";
|
|
1933
|
+
}
|
|
1934
|
+
function remedy(family) {
|
|
1935
|
+
return family === "cjs" ? 'rename to .cjs, or add { "type": "commonjs" } to the hooks dir' : "rename to .mjs (a synced hooks/ dir treats .js as CommonJS)";
|
|
1936
|
+
}
|
|
1937
|
+
function safeReaddir2(dir) {
|
|
1938
|
+
try {
|
|
1939
|
+
return readdirSync7(dir);
|
|
1940
|
+
} catch {
|
|
1941
|
+
return [];
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
function safeRead(path) {
|
|
1945
|
+
try {
|
|
1946
|
+
return readFileSync4(path, "utf8");
|
|
1947
|
+
} catch {
|
|
1948
|
+
return null;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
function reportHookScopeCheck(section2) {
|
|
1952
|
+
const hooksDir = join18(CLAUDE_HOME, "hooks");
|
|
1953
|
+
if (!existsSync15(hooksDir)) {
|
|
1954
|
+
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
let anyWarn = false;
|
|
1958
|
+
for (const name of safeReaddir2(hooksDir)) {
|
|
1959
|
+
if (extname(name) !== ".js") continue;
|
|
1960
|
+
const abs = join18(hooksDir, name);
|
|
1961
|
+
const eff = effectiveType(abs);
|
|
1962
|
+
if (eff === null) continue;
|
|
1963
|
+
const src = safeRead(abs);
|
|
1964
|
+
if (src === null) continue;
|
|
1965
|
+
const fam = classifySource(src);
|
|
1966
|
+
if (fam === "unknown" || fam === eff) continue;
|
|
1967
|
+
addItem(
|
|
1968
|
+
section2,
|
|
1969
|
+
`${yellow(warnGlyph)} hooks/${name}: ${fam} source loads as ${eff} (${remedy(fam)})`
|
|
1970
|
+
);
|
|
1971
|
+
anyWarn = true;
|
|
1972
|
+
}
|
|
1973
|
+
if (!anyWarn) {
|
|
1974
|
+
addItem(section2, `${green(okGlyph)} hooks: module type consistent`);
|
|
1770
1975
|
}
|
|
1771
1976
|
}
|
|
1772
1977
|
|
|
1773
1978
|
// src/commands.doctor.checks.hooks.ts
|
|
1774
1979
|
init_color();
|
|
1775
|
-
import { existsSync as
|
|
1776
|
-
import { join as
|
|
1980
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
1981
|
+
import { join as join19 } from "node:path";
|
|
1777
1982
|
init_config();
|
|
1778
1983
|
function expandHome(token) {
|
|
1779
1984
|
return token.replace(/^\$\{HOME\}/, HOME).replace(/^\$HOME/, HOME).replace(/^~/, HOME);
|
|
@@ -1811,7 +2016,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
1811
2016
|
for (const group of groups) {
|
|
1812
2017
|
for (const cmd of commandsFromGroup(group)) {
|
|
1813
2018
|
for (const resolved of claudePathsIn(cmd)) {
|
|
1814
|
-
if (
|
|
2019
|
+
if (existsSync16(resolved)) continue;
|
|
1815
2020
|
addItem(section2, `${red(failGlyph)} hooks/${event}: command target missing: ${resolved}`);
|
|
1816
2021
|
process.exitCode = 1;
|
|
1817
2022
|
anyFail = true;
|
|
@@ -1821,8 +2026,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
1821
2026
|
return anyFail;
|
|
1822
2027
|
}
|
|
1823
2028
|
function reportHooksTargetCheck(section2) {
|
|
1824
|
-
const settingsPath =
|
|
1825
|
-
if (!
|
|
2029
|
+
const settingsPath = join19(CLAUDE_HOME, "settings.json");
|
|
2030
|
+
if (!existsSync16(settingsPath)) {
|
|
1826
2031
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
1827
2032
|
return;
|
|
1828
2033
|
}
|
|
@@ -1848,18 +2053,14 @@ init_config();
|
|
|
1848
2053
|
|
|
1849
2054
|
// src/commands.doctor.engine.ts
|
|
1850
2055
|
init_color();
|
|
1851
|
-
import { readFileSync as
|
|
2056
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1852
2057
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1853
2058
|
|
|
1854
2059
|
// src/commands.doctor.version.ts
|
|
1855
2060
|
init_color();
|
|
1856
2061
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
1857
|
-
import {
|
|
1858
|
-
import { dirname as dirname2, join as join16 } from "node:path";
|
|
2062
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1859
2063
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1860
|
-
init_config();
|
|
1861
|
-
var CACHE_PATH = join16(HOME, ".cache", "claude-nomad", "version-check.json");
|
|
1862
|
-
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
1863
2064
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
1864
2065
|
var STRICT_SEMVER_PREFIX = /^(\d+\.\d+\.\d+)(?:[-+]|$)/;
|
|
1865
2066
|
function compareSemver(a, b) {
|
|
@@ -1874,7 +2075,7 @@ function compareSemver(a, b) {
|
|
|
1874
2075
|
function readLocalVersion() {
|
|
1875
2076
|
try {
|
|
1876
2077
|
const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
|
|
1877
|
-
const parsed = JSON.parse(
|
|
2078
|
+
const parsed = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
1878
2079
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
1879
2080
|
return parsed.version;
|
|
1880
2081
|
}
|
|
@@ -1883,28 +2084,6 @@ function readLocalVersion() {
|
|
|
1883
2084
|
return null;
|
|
1884
2085
|
}
|
|
1885
2086
|
}
|
|
1886
|
-
function loadCache() {
|
|
1887
|
-
try {
|
|
1888
|
-
if (!existsSync14(CACHE_PATH)) return null;
|
|
1889
|
-
const parsed = JSON.parse(readFileSync4(CACHE_PATH, "utf8"));
|
|
1890
|
-
if (typeof parsed.checked_at !== "number" || !Number.isFinite(parsed.checked_at)) {
|
|
1891
|
-
return null;
|
|
1892
|
-
}
|
|
1893
|
-
if (typeof parsed.latest !== "string" || !STRICT_SEMVER.test(parsed.latest)) {
|
|
1894
|
-
return null;
|
|
1895
|
-
}
|
|
1896
|
-
return { checked_at: parsed.checked_at, latest: parsed.latest };
|
|
1897
|
-
} catch {
|
|
1898
|
-
return null;
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
function saveCache(latest) {
|
|
1902
|
-
try {
|
|
1903
|
-
mkdirSync5(dirname2(CACHE_PATH), { recursive: true });
|
|
1904
|
-
writeFileSync3(CACHE_PATH, JSON.stringify({ checked_at: Date.now(), latest }));
|
|
1905
|
-
} catch {
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
2087
|
function fetchLatestVersion() {
|
|
1909
2088
|
try {
|
|
1910
2089
|
const url = "https://registry.npmjs.org/claude-nomad/latest";
|
|
@@ -1924,16 +2103,8 @@ function reportVersionCheck(section2) {
|
|
|
1924
2103
|
if (local === null) return;
|
|
1925
2104
|
const localPure = STRICT_SEMVER_PREFIX.exec(local)?.[1] ?? null;
|
|
1926
2105
|
if (localPure === null) return;
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
if (cached !== null && Date.now() - cached.checked_at < CACHE_TTL_MS) {
|
|
1930
|
-
latest = cached.latest;
|
|
1931
|
-
}
|
|
1932
|
-
if (latest === null) {
|
|
1933
|
-
latest = fetchLatestVersion();
|
|
1934
|
-
if (latest === null) return;
|
|
1935
|
-
saveCache(latest);
|
|
1936
|
-
}
|
|
2106
|
+
const latest = fetchLatestVersion();
|
|
2107
|
+
if (latest === null) return;
|
|
1937
2108
|
const cmp = compareSemver(localPure, latest);
|
|
1938
2109
|
if (cmp === 0) {
|
|
1939
2110
|
addItem(section2, `${green(okGlyph)} claude-nomad: ${local} (latest)`);
|
|
@@ -1959,7 +2130,7 @@ function parseMinVersion(spec) {
|
|
|
1959
2130
|
function readEnginesNode() {
|
|
1960
2131
|
try {
|
|
1961
2132
|
const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
|
|
1962
|
-
const parsed = JSON.parse(
|
|
2133
|
+
const parsed = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
1963
2134
|
const node = parsed.engines?.node;
|
|
1964
2135
|
if (typeof node === "string" && node.length > 0) return node;
|
|
1965
2136
|
return null;
|
|
@@ -1988,8 +2159,8 @@ function reportNodeEngineCheck(section2) {
|
|
|
1988
2159
|
// src/commands.doctor.gitleaks-version.ts
|
|
1989
2160
|
init_color();
|
|
1990
2161
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
1991
|
-
import { existsSync as
|
|
1992
|
-
import { join as
|
|
2162
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
2163
|
+
import { join as join20 } from "node:path";
|
|
1993
2164
|
init_config();
|
|
1994
2165
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
1995
2166
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -1998,7 +2169,7 @@ function majorMinorOf(value) {
|
|
|
1998
2169
|
return m === null ? null : [m[1], m[2]];
|
|
1999
2170
|
}
|
|
2000
2171
|
function readGitleaksVersion(run, tomlExists) {
|
|
2001
|
-
const tomlPath =
|
|
2172
|
+
const tomlPath = join20(REPO_HOME, ".gitleaks.toml");
|
|
2002
2173
|
const args = ["version"];
|
|
2003
2174
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
2004
2175
|
try {
|
|
@@ -2010,7 +2181,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
2010
2181
|
return null;
|
|
2011
2182
|
}
|
|
2012
2183
|
}
|
|
2013
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync8, tomlExists =
|
|
2184
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync8, tomlExists = existsSync17) {
|
|
2014
2185
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
2015
2186
|
if (raw === null) return;
|
|
2016
2187
|
const local = majorMinorOf(raw);
|
|
@@ -2172,12 +2343,13 @@ function cmdDoctor(opts = {}) {
|
|
|
2172
2343
|
reportHostAndPaths(host);
|
|
2173
2344
|
reportRepoState(host);
|
|
2174
2345
|
const links = section("Shared links");
|
|
2175
|
-
const mapPath =
|
|
2176
|
-
const rawMap =
|
|
2346
|
+
const mapPath = join21(REPO_HOME, "path-map.json");
|
|
2347
|
+
const rawMap = existsSync18(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
2177
2348
|
const map = rawMap ?? { projects: {} };
|
|
2178
2349
|
reportSharedLinks(links, map);
|
|
2179
2350
|
const hooksScan = section("Hook targets");
|
|
2180
2351
|
reportHooksTargetCheck(hooksScan);
|
|
2352
|
+
reportHookScopeCheck(hooksScan);
|
|
2181
2353
|
const settings = section("Settings");
|
|
2182
2354
|
const base = loadBaseSettings(settings);
|
|
2183
2355
|
const parsedSettings = loadAndReportSettings(settings);
|
|
@@ -2197,6 +2369,7 @@ function cmdDoctor(opts = {}) {
|
|
|
2197
2369
|
reportNodeEngineCheck(version);
|
|
2198
2370
|
reportGitleaksVersionCheck(version);
|
|
2199
2371
|
reportOptionalDeps(version);
|
|
2372
|
+
reportBackupsCheck(version);
|
|
2200
2373
|
const sharedScan = section("Shared scan");
|
|
2201
2374
|
if (opts.checkShared === true) reportCheckShared(sharedScan, gitleaksReady);
|
|
2202
2375
|
const schemaScan = section("Schema scan");
|
|
@@ -2218,8 +2391,8 @@ function cmdDoctor(opts = {}) {
|
|
|
2218
2391
|
// src/commands.drop-session.ts
|
|
2219
2392
|
init_config();
|
|
2220
2393
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
2221
|
-
import { existsSync as
|
|
2222
|
-
import { join as
|
|
2394
|
+
import { existsSync as existsSync20, readdirSync as readdirSync8, statSync as statSync5 } from "node:fs";
|
|
2395
|
+
import { join as join24, relative as relative4 } from "node:path";
|
|
2223
2396
|
|
|
2224
2397
|
// src/commands.drop-session.git.ts
|
|
2225
2398
|
init_config();
|
|
@@ -2262,8 +2435,8 @@ function isInIndex(rel) {
|
|
|
2262
2435
|
init_config();
|
|
2263
2436
|
init_utils();
|
|
2264
2437
|
init_utils_json();
|
|
2265
|
-
import { existsSync as
|
|
2266
|
-
import { join as
|
|
2438
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
2439
|
+
import { join as join22 } from "node:path";
|
|
2267
2440
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
2268
2441
|
function reportScrubHint(id, matches) {
|
|
2269
2442
|
const live = resolveLiveTranscript(id, matches);
|
|
@@ -2279,16 +2452,16 @@ function reportScrubHint(id, matches) {
|
|
|
2279
2452
|
}
|
|
2280
2453
|
function resolveLiveTranscript(id, matches) {
|
|
2281
2454
|
try {
|
|
2282
|
-
const mapPath =
|
|
2283
|
-
if (!
|
|
2455
|
+
const mapPath = join22(REPO_HOME, "path-map.json");
|
|
2456
|
+
if (!existsSync19(mapPath)) return null;
|
|
2284
2457
|
const projects = readJson(mapPath).projects;
|
|
2285
2458
|
for (const rel of matches) {
|
|
2286
2459
|
const logical = SHARED_PROJECT_LOGICAL.exec(rel)?.[1];
|
|
2287
2460
|
if (logical === void 0) continue;
|
|
2288
2461
|
const abs = projects[logical]?.[HOST];
|
|
2289
2462
|
if (abs === void 0) continue;
|
|
2290
|
-
const live =
|
|
2291
|
-
if (
|
|
2463
|
+
const live = join22(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
|
|
2464
|
+
if (existsSync19(live)) return live;
|
|
2292
2465
|
}
|
|
2293
2466
|
return null;
|
|
2294
2467
|
} catch {
|
|
@@ -2302,15 +2475,15 @@ init_utils();
|
|
|
2302
2475
|
// src/utils.lockfile.ts
|
|
2303
2476
|
init_config();
|
|
2304
2477
|
init_utils();
|
|
2305
|
-
import { closeSync as closeSync2, mkdirSync as
|
|
2306
|
-
import { dirname as dirname3, join as
|
|
2307
|
-
var LOCK_PATH =
|
|
2478
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2479
|
+
import { dirname as dirname3, join as join23 } from "node:path";
|
|
2480
|
+
var LOCK_PATH = join23(HOME, ".cache", "claude-nomad", "nomad.lock");
|
|
2308
2481
|
function acquireLock(verb) {
|
|
2309
|
-
|
|
2482
|
+
mkdirSync5(dirname3(LOCK_PATH), { recursive: true });
|
|
2310
2483
|
try {
|
|
2311
2484
|
const fd = openSync2(LOCK_PATH, "wx");
|
|
2312
2485
|
try {
|
|
2313
|
-
|
|
2486
|
+
writeFileSync3(fd, String(process.pid));
|
|
2314
2487
|
} catch (writeErr) {
|
|
2315
2488
|
try {
|
|
2316
2489
|
closeSync2(fd);
|
|
@@ -2344,7 +2517,7 @@ function releaseLock(handle) {
|
|
|
2344
2517
|
function unlinkIfSamePid(expectedPidStr) {
|
|
2345
2518
|
let current;
|
|
2346
2519
|
try {
|
|
2347
|
-
current =
|
|
2520
|
+
current = readFileSync7(LOCK_PATH, "utf8").trim();
|
|
2348
2521
|
} catch {
|
|
2349
2522
|
return false;
|
|
2350
2523
|
}
|
|
@@ -2359,7 +2532,7 @@ function unlinkIfSamePid(expectedPidStr) {
|
|
|
2359
2532
|
function checkStaleAndRetry(verb) {
|
|
2360
2533
|
let pidStr;
|
|
2361
2534
|
try {
|
|
2362
|
-
pidStr =
|
|
2535
|
+
pidStr = readFileSync7(LOCK_PATH, "utf8").trim();
|
|
2363
2536
|
} catch {
|
|
2364
2537
|
pidStr = "";
|
|
2365
2538
|
}
|
|
@@ -2388,7 +2561,7 @@ function retryOnce(verb) {
|
|
|
2388
2561
|
try {
|
|
2389
2562
|
const fd = openSync2(LOCK_PATH, "wx");
|
|
2390
2563
|
try {
|
|
2391
|
-
|
|
2564
|
+
writeFileSync3(fd, String(process.pid));
|
|
2392
2565
|
} catch {
|
|
2393
2566
|
try {
|
|
2394
2567
|
closeSync2(fd);
|
|
@@ -2414,12 +2587,12 @@ function cmdDropSession(id) {
|
|
|
2414
2587
|
fail(`invalid session id: ${id}`);
|
|
2415
2588
|
process.exit(1);
|
|
2416
2589
|
}
|
|
2417
|
-
if (!
|
|
2590
|
+
if (!existsSync20(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
2418
2591
|
const handle = acquireLock("drop-session");
|
|
2419
2592
|
if (handle === null) process.exit(0);
|
|
2420
2593
|
try {
|
|
2421
|
-
const repoProjects =
|
|
2422
|
-
if (!
|
|
2594
|
+
const repoProjects = join24(REPO_HOME, "shared", "projects");
|
|
2595
|
+
if (!existsSync20(repoProjects)) {
|
|
2423
2596
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
2424
2597
|
}
|
|
2425
2598
|
const matches = collectMatches(repoProjects, id);
|
|
@@ -2440,13 +2613,13 @@ function cmdDropSession(id) {
|
|
|
2440
2613
|
}
|
|
2441
2614
|
function collectMatches(repoProjects, id) {
|
|
2442
2615
|
const matches = [];
|
|
2443
|
-
for (const logical of
|
|
2444
|
-
const candidate =
|
|
2445
|
-
if (
|
|
2616
|
+
for (const logical of readdirSync8(repoProjects)) {
|
|
2617
|
+
const candidate = join24(repoProjects, logical, `${id}.jsonl`);
|
|
2618
|
+
if (existsSync20(candidate)) {
|
|
2446
2619
|
matches.push(relative4(REPO_HOME, candidate));
|
|
2447
2620
|
}
|
|
2448
|
-
const dir =
|
|
2449
|
-
if (
|
|
2621
|
+
const dir = join24(repoProjects, logical, id);
|
|
2622
|
+
if (existsSync20(dir) && statSync5(dir).isDirectory()) {
|
|
2450
2623
|
const dirRel = relative4(REPO_HOME, dir);
|
|
2451
2624
|
const staged = expandStagedDir(dirRel);
|
|
2452
2625
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -2482,13 +2655,13 @@ function unstageOne(rel) {
|
|
|
2482
2655
|
|
|
2483
2656
|
// src/commands.redact.ts
|
|
2484
2657
|
init_config();
|
|
2485
|
-
import { existsSync as
|
|
2486
|
-
import { join as
|
|
2658
|
+
import { existsSync as existsSync21, readFileSync as readFileSync8, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2659
|
+
import { join as join26 } from "node:path";
|
|
2487
2660
|
|
|
2488
2661
|
// src/commands.redact.core.ts
|
|
2489
2662
|
init_config();
|
|
2490
2663
|
import { appendFileSync } from "node:fs";
|
|
2491
|
-
import { join as
|
|
2664
|
+
import { join as join25 } from "node:path";
|
|
2492
2665
|
function collectMatchIntervals(content, findings) {
|
|
2493
2666
|
const intervals = [];
|
|
2494
2667
|
for (const f of findings) {
|
|
@@ -2538,7 +2711,7 @@ function isRecentlyModified(mtimeMs, nowMs, thresholdMs = 5 * 60 * 1e3) {
|
|
|
2538
2711
|
return nowMs - mtimeMs < thresholdMs;
|
|
2539
2712
|
}
|
|
2540
2713
|
function appendGitleaksIgnore(fingerprint) {
|
|
2541
|
-
appendFileSync(
|
|
2714
|
+
appendFileSync(join25(REPO_HOME, ".gitleaksignore"), formatFingerprint(fingerprint), "utf8");
|
|
2542
2715
|
}
|
|
2543
2716
|
|
|
2544
2717
|
// src/commands.redact.ts
|
|
@@ -2548,14 +2721,14 @@ init_utils_json();
|
|
|
2548
2721
|
init_utils();
|
|
2549
2722
|
function resolveLiveTranscript2(id) {
|
|
2550
2723
|
try {
|
|
2551
|
-
const mapPath =
|
|
2552
|
-
if (!
|
|
2724
|
+
const mapPath = join26(REPO_HOME, "path-map.json");
|
|
2725
|
+
if (!existsSync21(mapPath)) return null;
|
|
2553
2726
|
const projects = readJson(mapPath).projects;
|
|
2554
2727
|
for (const hostMap of Object.values(projects)) {
|
|
2555
2728
|
const abs = hostMap[HOST];
|
|
2556
2729
|
if (abs === void 0) continue;
|
|
2557
|
-
const live =
|
|
2558
|
-
if (
|
|
2730
|
+
const live = join26(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
|
|
2731
|
+
if (existsSync21(live)) return live;
|
|
2559
2732
|
}
|
|
2560
2733
|
return null;
|
|
2561
2734
|
} catch {
|
|
@@ -2573,17 +2746,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
2573
2746
|
fail(`invalid session id: ${id}`);
|
|
2574
2747
|
process.exit(1);
|
|
2575
2748
|
}
|
|
2576
|
-
if (!
|
|
2749
|
+
if (!existsSync21(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
2577
2750
|
const handle = acquireLock("redact");
|
|
2578
2751
|
if (handle === null) process.exit(0);
|
|
2579
2752
|
try {
|
|
2580
2753
|
const localPath = resolveLiveTranscript2(id);
|
|
2581
|
-
if (localPath === null || !
|
|
2754
|
+
if (localPath === null || !existsSync21(localPath)) {
|
|
2582
2755
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
2583
2756
|
process.exitCode = 1;
|
|
2584
2757
|
return;
|
|
2585
2758
|
}
|
|
2586
|
-
const mtimeMs =
|
|
2759
|
+
const mtimeMs = statSync6(localPath).mtimeMs;
|
|
2587
2760
|
if (isRecentlyModified(mtimeMs, nowMs())) {
|
|
2588
2761
|
log(
|
|
2589
2762
|
`session ${id} was modified recently and may be active.
|
|
@@ -2612,12 +2785,12 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
2612
2785
|
);
|
|
2613
2786
|
return;
|
|
2614
2787
|
}
|
|
2615
|
-
const backupBase =
|
|
2788
|
+
const backupBase = join26(HOME, ".cache", "claude-nomad", "backup");
|
|
2616
2789
|
const ts = freshBackupTs(backupBase);
|
|
2617
2790
|
backupBeforeWrite(localPath, ts);
|
|
2618
|
-
const original =
|
|
2791
|
+
const original = readFileSync8(localPath, "utf8");
|
|
2619
2792
|
const redacted = applyRedactions(original, findings);
|
|
2620
|
-
|
|
2793
|
+
writeFileSync4(localPath, redacted, "utf8");
|
|
2621
2794
|
log(`redacted ${findings.length} finding(s) in ${localPath} (backup: ${ts})`);
|
|
2622
2795
|
} catch (err) {
|
|
2623
2796
|
if (!(err instanceof NomadFatal)) {
|
|
@@ -2631,8 +2804,8 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
2631
2804
|
}
|
|
2632
2805
|
|
|
2633
2806
|
// src/commands.pull.ts
|
|
2634
|
-
import { existsSync as
|
|
2635
|
-
import { join as
|
|
2807
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync7 } from "node:fs";
|
|
2808
|
+
import { join as join32 } from "node:path";
|
|
2636
2809
|
|
|
2637
2810
|
// src/commands.push.sections.ts
|
|
2638
2811
|
init_color();
|
|
@@ -2728,8 +2901,8 @@ init_config();
|
|
|
2728
2901
|
|
|
2729
2902
|
// src/extras-sync.ts
|
|
2730
2903
|
init_config();
|
|
2731
|
-
import { existsSync as
|
|
2732
|
-
import { join as
|
|
2904
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
2905
|
+
import { join as join29 } from "node:path";
|
|
2733
2906
|
|
|
2734
2907
|
// src/extras-sync.diff.ts
|
|
2735
2908
|
init_utils();
|
|
@@ -2756,8 +2929,8 @@ function listDivergingFiles(a, b) {
|
|
|
2756
2929
|
|
|
2757
2930
|
// src/extras-sync.core.ts
|
|
2758
2931
|
init_config();
|
|
2759
|
-
import { cpSync as cpSync4, existsSync as
|
|
2760
|
-
import { join as
|
|
2932
|
+
import { cpSync as cpSync4, existsSync as existsSync22, rmSync as rmSync7 } from "node:fs";
|
|
2933
|
+
import { join as join27 } from "node:path";
|
|
2761
2934
|
|
|
2762
2935
|
// src/extras-sync.guards.ts
|
|
2763
2936
|
init_utils();
|
|
@@ -2780,9 +2953,9 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
2780
2953
|
init_utils();
|
|
2781
2954
|
init_utils_json();
|
|
2782
2955
|
function loadValidatedExtras(opts) {
|
|
2783
|
-
const mapPath =
|
|
2784
|
-
const repoExtras =
|
|
2785
|
-
if (!
|
|
2956
|
+
const mapPath = join27(REPO_HOME, "path-map.json");
|
|
2957
|
+
const repoExtras = join27(REPO_HOME, "shared", "extras");
|
|
2958
|
+
if (!existsSync22(mapPath) || opts.requireRepoExtras === true && !existsSync22(repoExtras)) {
|
|
2786
2959
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
2787
2960
|
return null;
|
|
2788
2961
|
}
|
|
@@ -2814,7 +2987,7 @@ function* eachExtrasTarget(v, counts) {
|
|
|
2814
2987
|
}
|
|
2815
2988
|
}
|
|
2816
2989
|
function copyExtras(src, dst) {
|
|
2817
|
-
|
|
2990
|
+
rmSync7(dst, { recursive: true, force: true });
|
|
2818
2991
|
cpSync4(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
2819
2992
|
}
|
|
2820
2993
|
|
|
@@ -2824,8 +2997,8 @@ init_utils_json();
|
|
|
2824
2997
|
|
|
2825
2998
|
// src/extras-sync.remap.ts
|
|
2826
2999
|
init_config();
|
|
2827
|
-
import { existsSync as
|
|
2828
|
-
import { join as
|
|
3000
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3001
|
+
import { join as join28 } from "node:path";
|
|
2829
3002
|
init_utils_fs();
|
|
2830
3003
|
function runExtrasOp(v, dryRun, paths, backup) {
|
|
2831
3004
|
const counts = { unmapped: 0, skipped: 0 };
|
|
@@ -2833,7 +3006,7 @@ function runExtrasOp(v, dryRun, paths, backup) {
|
|
|
2833
3006
|
const would = [];
|
|
2834
3007
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
2835
3008
|
const { src, dst } = paths(t);
|
|
2836
|
-
if (!
|
|
3009
|
+
if (!existsSync23(src)) continue;
|
|
2837
3010
|
const item = `${t.logical}/${t.dirname}`;
|
|
2838
3011
|
if (dryRun) {
|
|
2839
3012
|
would.push(item);
|
|
@@ -2849,14 +3022,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
2849
3022
|
const dryRun = opts.dryRun === true;
|
|
2850
3023
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
2851
3024
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
2852
|
-
const repoExtras =
|
|
2853
|
-
if (!dryRun)
|
|
3025
|
+
const repoExtras = join28(REPO_HOME, "shared", "extras");
|
|
3026
|
+
if (!dryRun) mkdirSync6(repoExtras, { recursive: true });
|
|
2854
3027
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
2855
3028
|
v,
|
|
2856
3029
|
dryRun,
|
|
2857
3030
|
({ localRoot, logical, dirname: dirname4 }) => ({
|
|
2858
|
-
src:
|
|
2859
|
-
dst:
|
|
3031
|
+
src: join28(localRoot, dirname4),
|
|
3032
|
+
dst: join28(repoExtras, logical, dirname4)
|
|
2860
3033
|
}),
|
|
2861
3034
|
(dst) => backupRepoWrite(dst, ts, REPO_HOME)
|
|
2862
3035
|
);
|
|
@@ -2869,13 +3042,13 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
2869
3042
|
missingMsg: "no path-map or repo extras dir; skipping extras remap"
|
|
2870
3043
|
});
|
|
2871
3044
|
if (v === null) return { unmapped: 0, skipped: 0, pulled: [], wouldPull: [] };
|
|
2872
|
-
const repoExtras =
|
|
3045
|
+
const repoExtras = join28(REPO_HOME, "shared", "extras");
|
|
2873
3046
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
2874
3047
|
v,
|
|
2875
3048
|
dryRun,
|
|
2876
3049
|
({ localRoot, logical, dirname: dirname4 }) => ({
|
|
2877
|
-
src:
|
|
2878
|
-
dst:
|
|
3050
|
+
src: join28(repoExtras, logical, dirname4),
|
|
3051
|
+
dst: join28(localRoot, dirname4)
|
|
2879
3052
|
}),
|
|
2880
3053
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
2881
3054
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -2889,14 +3062,14 @@ function divergenceCheckExtras(ts) {
|
|
|
2889
3062
|
const v = loadValidatedExtras({});
|
|
2890
3063
|
if (v === null) return;
|
|
2891
3064
|
const counts = { unmapped: 0, skipped: 0 };
|
|
2892
|
-
const backupRoot =
|
|
3065
|
+
const backupRoot = join29(HOME, ".cache", "claude-nomad", "backup", ts, "extras");
|
|
2893
3066
|
for (const { logical, localRoot, dirname: dirname4 } of eachExtrasTarget(v, counts)) {
|
|
2894
|
-
const local =
|
|
2895
|
-
const repo =
|
|
2896
|
-
if (!
|
|
3067
|
+
const local = join29(localRoot, dirname4);
|
|
3068
|
+
const repo = join29(REPO_HOME, "shared", "extras", logical, dirname4);
|
|
3069
|
+
if (!existsSync24(local) || !existsSync24(repo)) continue;
|
|
2897
3070
|
const diff = listDivergingFiles(local, repo);
|
|
2898
3071
|
if (diff.length === 0) continue;
|
|
2899
|
-
const projectBackupRoot =
|
|
3072
|
+
const projectBackupRoot = join29(backupRoot, encodePath(localRoot));
|
|
2900
3073
|
warn(
|
|
2901
3074
|
`local ${dirname4} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will overwrite them (backups at ${projectBackupRoot}/)`
|
|
2902
3075
|
);
|
|
@@ -2909,47 +3082,47 @@ init_config();
|
|
|
2909
3082
|
init_utils();
|
|
2910
3083
|
init_utils_fs();
|
|
2911
3084
|
init_utils_json();
|
|
2912
|
-
import { existsSync as
|
|
2913
|
-
import { join as
|
|
3085
|
+
import { existsSync as existsSync25, lstatSync as lstatSync6, rmSync as rmSync8 } from "node:fs";
|
|
3086
|
+
import { join as join30 } from "node:path";
|
|
2914
3087
|
function applySharedLinks(ts, map, opts = {}) {
|
|
2915
3088
|
const dryRun = opts.dryRun === true;
|
|
2916
3089
|
const linkNames = allSharedLinks(map);
|
|
2917
3090
|
for (const name of linkNames) {
|
|
2918
|
-
const linkPath =
|
|
2919
|
-
const target =
|
|
2920
|
-
if (!
|
|
2921
|
-
if (
|
|
2922
|
-
if (!
|
|
3091
|
+
const linkPath = join30(CLAUDE_HOME, name);
|
|
3092
|
+
const target = join30(REPO_HOME, "shared", name);
|
|
3093
|
+
if (!existsSync25(linkPath)) continue;
|
|
3094
|
+
if (lstatSync6(linkPath).isSymbolicLink()) continue;
|
|
3095
|
+
if (!existsSync25(target)) continue;
|
|
2923
3096
|
if (dryRun) {
|
|
2924
3097
|
log(`would auto-move non-symlink: ${linkPath} -> backup/${ts}/${name}`);
|
|
2925
3098
|
continue;
|
|
2926
3099
|
}
|
|
2927
3100
|
backupBeforeWrite(linkPath, ts);
|
|
2928
|
-
|
|
3101
|
+
rmSync8(linkPath, { recursive: true, force: true });
|
|
2929
3102
|
}
|
|
2930
3103
|
for (const name of linkNames) {
|
|
2931
|
-
const target =
|
|
2932
|
-
if (!
|
|
3104
|
+
const target = join30(REPO_HOME, "shared", name);
|
|
3105
|
+
if (!existsSync25(target)) continue;
|
|
2933
3106
|
if (dryRun) {
|
|
2934
|
-
log(`would create symlink: ${
|
|
3107
|
+
log(`would create symlink: ${join30(CLAUDE_HOME, name)} -> ${target}`);
|
|
2935
3108
|
continue;
|
|
2936
3109
|
}
|
|
2937
|
-
ensureSymlink(
|
|
3110
|
+
ensureSymlink(join30(CLAUDE_HOME, name), target);
|
|
2938
3111
|
}
|
|
2939
3112
|
}
|
|
2940
3113
|
function regenerateSettings(ts, opts = {}) {
|
|
2941
3114
|
const dryRun = opts.dryRun === true;
|
|
2942
|
-
const basePath =
|
|
2943
|
-
const hostPath =
|
|
2944
|
-
if (!
|
|
3115
|
+
const basePath = join30(REPO_HOME, "shared", "settings.base.json");
|
|
3116
|
+
const hostPath = join30(REPO_HOME, "hosts", `${HOST}.json`);
|
|
3117
|
+
if (!existsSync25(basePath)) {
|
|
2945
3118
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
2946
3119
|
}
|
|
2947
3120
|
const base = readJson(basePath);
|
|
2948
|
-
const hasOverrides =
|
|
3121
|
+
const hasOverrides = existsSync25(hostPath);
|
|
2949
3122
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
2950
3123
|
const merged = deepMerge(base, overrides);
|
|
2951
|
-
const settingsPath =
|
|
2952
|
-
if (!hasOverrides &&
|
|
3124
|
+
const settingsPath = join30(CLAUDE_HOME, "settings.json");
|
|
3125
|
+
if (!hasOverrides && existsSync25(settingsPath)) {
|
|
2953
3126
|
try {
|
|
2954
3127
|
const existing = readJson(settingsPath);
|
|
2955
3128
|
const baseKeys = new Set(Object.keys(base));
|
|
@@ -2975,8 +3148,8 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
2975
3148
|
|
|
2976
3149
|
// src/preview.ts
|
|
2977
3150
|
init_config();
|
|
2978
|
-
import { existsSync as
|
|
2979
|
-
import { join as
|
|
3151
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
3152
|
+
import { join as join31 } from "node:path";
|
|
2980
3153
|
|
|
2981
3154
|
// node_modules/diff/libesm/diff/base.js
|
|
2982
3155
|
var Diff = class {
|
|
@@ -3262,7 +3435,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
3262
3435
|
return lines.join("\n");
|
|
3263
3436
|
}
|
|
3264
3437
|
function readJsonOrNull(path) {
|
|
3265
|
-
if (!
|
|
3438
|
+
if (!existsSync26(path)) return null;
|
|
3266
3439
|
try {
|
|
3267
3440
|
return readJson(path);
|
|
3268
3441
|
} catch {
|
|
@@ -3276,12 +3449,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
3276
3449
|
return;
|
|
3277
3450
|
}
|
|
3278
3451
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
3279
|
-
if (hostOverrides === null &&
|
|
3452
|
+
if (hostOverrides === null && existsSync26(hostPath)) {
|
|
3280
3453
|
log(`settings.json: malformed hosts/${HOST}.json; ignoring overrides`);
|
|
3281
3454
|
}
|
|
3282
3455
|
const merged = deepMerge(base, hostOverrides ?? {});
|
|
3283
3456
|
const current = readJsonOrNull(settingsPath);
|
|
3284
|
-
if (current === null &&
|
|
3457
|
+
if (current === null && existsSync26(settingsPath)) {
|
|
3285
3458
|
log("settings.json: malformed; skipping diff");
|
|
3286
3459
|
return;
|
|
3287
3460
|
}
|
|
@@ -3300,9 +3473,9 @@ function computePreview(ts, map) {
|
|
|
3300
3473
|
log(`would pull on host=${HOST} (dry-run; no mutation)`);
|
|
3301
3474
|
applySharedLinks(ts, map, { dryRun: true });
|
|
3302
3475
|
previewSettings(
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3476
|
+
join31(REPO_HOME, "shared", "settings.base.json"),
|
|
3477
|
+
join31(REPO_HOME, "hosts", `${HOST}.json`),
|
|
3478
|
+
join31(CLAUDE_HOME, "settings.json")
|
|
3306
3479
|
);
|
|
3307
3480
|
const remapResult = remapPull(ts, { dryRun: true });
|
|
3308
3481
|
return { unmapped: remapResult.unmapped, collisions: 0 };
|
|
@@ -3331,19 +3504,18 @@ function applyWetPull(ts, map) {
|
|
|
3331
3504
|
}
|
|
3332
3505
|
function cmdPull(opts = {}) {
|
|
3333
3506
|
const dryRun = opts.dryRun === true;
|
|
3334
|
-
if (!
|
|
3335
|
-
if (!
|
|
3507
|
+
if (!existsSync27(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
3508
|
+
if (!existsSync27(join32(REPO_HOME, "shared", "settings.base.json"))) {
|
|
3336
3509
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
3337
3510
|
}
|
|
3338
3511
|
const handle = acquireLock("pull");
|
|
3339
3512
|
if (handle === null) process.exit(0);
|
|
3340
3513
|
try {
|
|
3341
|
-
const
|
|
3342
|
-
const ts = freshBackupTs(backupBase);
|
|
3514
|
+
const ts = freshBackupTs(BACKUP_BASE);
|
|
3343
3515
|
if (!dryRun) {
|
|
3344
|
-
const backupRoot =
|
|
3516
|
+
const backupRoot = join32(BACKUP_BASE, ts);
|
|
3345
3517
|
try {
|
|
3346
|
-
|
|
3518
|
+
mkdirSync7(backupRoot, { recursive: true });
|
|
3347
3519
|
} catch (err) {
|
|
3348
3520
|
die(`could not create backup dir: ${err.message}`);
|
|
3349
3521
|
}
|
|
@@ -3352,8 +3524,8 @@ function cmdPull(opts = {}) {
|
|
|
3352
3524
|
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
3353
3525
|
);
|
|
3354
3526
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", REPO_HOME);
|
|
3355
|
-
const mapPath =
|
|
3356
|
-
const map =
|
|
3527
|
+
const mapPath = join32(REPO_HOME, "path-map.json");
|
|
3528
|
+
const map = existsSync27(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
3357
3529
|
divergenceCheckExtras(ts);
|
|
3358
3530
|
if (dryRun) {
|
|
3359
3531
|
const previewResult = computePreview(ts, map);
|
|
@@ -3376,8 +3548,8 @@ function cmdPull(opts = {}) {
|
|
|
3376
3548
|
|
|
3377
3549
|
// src/commands.push.ts
|
|
3378
3550
|
init_config();
|
|
3379
|
-
import { existsSync as
|
|
3380
|
-
import { join as
|
|
3551
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
3552
|
+
import { join as join36, relative as relative5 } from "node:path";
|
|
3381
3553
|
|
|
3382
3554
|
// src/commands.push.allowlist.ts
|
|
3383
3555
|
init_config();
|
|
@@ -3454,8 +3626,8 @@ import { createInterface } from "node:readline/promises";
|
|
|
3454
3626
|
|
|
3455
3627
|
// src/commands.push.recovery.redact.ts
|
|
3456
3628
|
init_config();
|
|
3457
|
-
import { cpSync as cpSync5, readFileSync as
|
|
3458
|
-
import { join as
|
|
3629
|
+
import { cpSync as cpSync5, readFileSync as readFileSync9, statSync as statSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3630
|
+
import { join as join33, sep as sep2 } from "node:path";
|
|
3459
3631
|
init_push_gitleaks_scan();
|
|
3460
3632
|
init_utils_fs();
|
|
3461
3633
|
init_utils_json();
|
|
@@ -3499,7 +3671,7 @@ function applyRedact(f, allFindings, ts, map, nowMs, scan = scanFile) {
|
|
|
3499
3671
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
3500
3672
|
);
|
|
3501
3673
|
}
|
|
3502
|
-
if (isRecentlyModified(
|
|
3674
|
+
if (isRecentlyModified(statSync7(localPath).mtimeMs, nowMs())) {
|
|
3503
3675
|
return refuse(
|
|
3504
3676
|
`session ${sid} looks active (modified within the last 5 minutes); refusing to redact, no changes made.
|
|
3505
3677
|
End the session and choose Redact again, or choose Drop session (holds this session back from the push, local copy kept) or Skip.`
|
|
@@ -3515,13 +3687,13 @@ function applyRedact(f, allFindings, ts, map, nowMs, scan = scanFile) {
|
|
|
3515
3687
|
);
|
|
3516
3688
|
}
|
|
3517
3689
|
backupBeforeWrite(localPath, ts);
|
|
3518
|
-
|
|
3690
|
+
writeFileSync5(localPath, applyRedactions(readFileSync9(localPath, "utf8"), realFindings), "utf8");
|
|
3519
3691
|
let copied = false;
|
|
3520
3692
|
for (const [logical, hostMap] of Object.entries(map.projects)) {
|
|
3521
3693
|
const abs = hostMap[HOST];
|
|
3522
3694
|
if (abs === void 0) continue;
|
|
3523
|
-
if (localPath.startsWith(
|
|
3524
|
-
cpSync5(localPath,
|
|
3695
|
+
if (localPath.startsWith(join33(CLAUDE_HOME, "projects", encodePath(abs)) + sep2)) {
|
|
3696
|
+
cpSync5(localPath, join33(REPO_HOME, "shared", "projects", logical, `${sid}.jsonl`), {
|
|
3525
3697
|
force: true
|
|
3526
3698
|
});
|
|
3527
3699
|
copied = true;
|
|
@@ -3538,16 +3710,16 @@ function applyRedact(f, allFindings, ts, map, nowMs, scan = scanFile) {
|
|
|
3538
3710
|
|
|
3539
3711
|
// src/commands.push.recovery.drop.ts
|
|
3540
3712
|
init_config();
|
|
3541
|
-
import { rmSync as
|
|
3542
|
-
import { join as
|
|
3713
|
+
import { rmSync as rmSync9 } from "node:fs";
|
|
3714
|
+
import { join as join34 } from "node:path";
|
|
3543
3715
|
function dropSessionFromStaged(sid, map) {
|
|
3544
3716
|
const logicals = Object.keys(map.projects);
|
|
3545
3717
|
if (logicals.length === 0) return false;
|
|
3546
3718
|
for (const logical of logicals) {
|
|
3547
|
-
const jsonl =
|
|
3548
|
-
const dir =
|
|
3549
|
-
|
|
3550
|
-
|
|
3719
|
+
const jsonl = join34(REPO_HOME, "shared", "projects", logical, `${sid}.jsonl`);
|
|
3720
|
+
const dir = join34(REPO_HOME, "shared", "projects", logical, sid);
|
|
3721
|
+
rmSync9(jsonl, { force: true });
|
|
3722
|
+
rmSync9(dir, { recursive: true, force: true });
|
|
3551
3723
|
}
|
|
3552
3724
|
return true;
|
|
3553
3725
|
}
|
|
@@ -3702,9 +3874,9 @@ init_push_checks();
|
|
|
3702
3874
|
init_color();
|
|
3703
3875
|
init_config();
|
|
3704
3876
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
3705
|
-
import { existsSync as
|
|
3877
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync8, readdirSync as readdirSync9, rmSync as rmSync10 } from "node:fs";
|
|
3706
3878
|
import { homedir as homedir5 } from "node:os";
|
|
3707
|
-
import { join as
|
|
3879
|
+
import { join as join35 } from "node:path";
|
|
3708
3880
|
init_push_leak_verdict();
|
|
3709
3881
|
init_push_gitleaks();
|
|
3710
3882
|
init_utils_fs();
|
|
@@ -3719,13 +3891,13 @@ function stageSessions(tmpRoot, map) {
|
|
|
3719
3891
|
if (!p || p === "TBD") continue;
|
|
3720
3892
|
reverse.set(encodePath(p), logical);
|
|
3721
3893
|
}
|
|
3722
|
-
const localProjects =
|
|
3723
|
-
if (!
|
|
3894
|
+
const localProjects = join35(CLAUDE_HOME, "projects");
|
|
3895
|
+
if (!existsSync28(localProjects)) return 0;
|
|
3724
3896
|
let staged = 0;
|
|
3725
|
-
for (const dir of
|
|
3897
|
+
for (const dir of readdirSync9(localProjects)) {
|
|
3726
3898
|
const logical = reverse.get(dir);
|
|
3727
3899
|
if (!logical) continue;
|
|
3728
|
-
copyDirJsonlOnly(
|
|
3900
|
+
copyDirJsonlOnly(join35(localProjects, dir), join35(tmpRoot, "shared", "projects", logical));
|
|
3729
3901
|
staged++;
|
|
3730
3902
|
}
|
|
3731
3903
|
return staged;
|
|
@@ -3741,9 +3913,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
3741
3913
|
if (!localRoot || localRoot === "TBD") continue;
|
|
3742
3914
|
for (const dirname4 of dirnames) {
|
|
3743
3915
|
if (!whitelist.includes(dirname4)) continue;
|
|
3744
|
-
const src =
|
|
3745
|
-
if (!
|
|
3746
|
-
const dst =
|
|
3916
|
+
const src = join35(localRoot, dirname4);
|
|
3917
|
+
if (!existsSync28(src)) continue;
|
|
3918
|
+
const dst = join35(tmpRoot, "shared", "extras", logical, dirname4);
|
|
3747
3919
|
copyExtras(src, dst);
|
|
3748
3920
|
staged++;
|
|
3749
3921
|
}
|
|
@@ -3751,10 +3923,10 @@ function stageExtras(tmpRoot, map) {
|
|
|
3751
3923
|
return staged;
|
|
3752
3924
|
}
|
|
3753
3925
|
function previewPushLeaks(map) {
|
|
3754
|
-
const cacheDir =
|
|
3755
|
-
|
|
3926
|
+
const cacheDir = join35(homedir5(), ".cache", "claude-nomad");
|
|
3927
|
+
mkdirSync8(cacheDir, { recursive: true });
|
|
3756
3928
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
3757
|
-
const tmpRoot =
|
|
3929
|
+
const tmpRoot = join35(cacheDir, `push-preview-tree-${stamp}`);
|
|
3758
3930
|
try {
|
|
3759
3931
|
const sessionCount = stageSessions(tmpRoot, map);
|
|
3760
3932
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
@@ -3772,7 +3944,7 @@ function previewPushLeaks(map) {
|
|
|
3772
3944
|
}
|
|
3773
3945
|
return verdictFromFindings(findings);
|
|
3774
3946
|
} finally {
|
|
3775
|
-
|
|
3947
|
+
rmSync10(tmpRoot, { recursive: true, force: true });
|
|
3776
3948
|
}
|
|
3777
3949
|
}
|
|
3778
3950
|
|
|
@@ -3781,7 +3953,7 @@ init_utils();
|
|
|
3781
3953
|
init_utils_fs();
|
|
3782
3954
|
init_utils_json();
|
|
3783
3955
|
function guardGitlinks() {
|
|
3784
|
-
const gitlinks = findGitlinks(
|
|
3956
|
+
const gitlinks = findGitlinks(join36(REPO_HOME, "shared"));
|
|
3785
3957
|
if (gitlinks.length === 0) return;
|
|
3786
3958
|
for (const p of gitlinks) {
|
|
3787
3959
|
const rel = relative5(REPO_HOME, p);
|
|
@@ -3815,15 +3987,14 @@ function runDryRunPreview(st, map) {
|
|
|
3815
3987
|
async function cmdPush(opts = {}) {
|
|
3816
3988
|
const dryRun = opts.dryRun === true;
|
|
3817
3989
|
const redactAll = opts.redactAll === true;
|
|
3818
|
-
if (!
|
|
3990
|
+
if (!existsSync29(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
3819
3991
|
const handle = acquireLock("push");
|
|
3820
3992
|
if (handle === null) process.exit(0);
|
|
3821
3993
|
try {
|
|
3822
3994
|
console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
|
|
3823
3995
|
probeGitleaks();
|
|
3824
3996
|
rebaseBeforePush();
|
|
3825
|
-
const
|
|
3826
|
-
const ts = freshBackupTs(backupBase);
|
|
3997
|
+
const ts = freshBackupTs(BACKUP_BASE);
|
|
3827
3998
|
const remap = remapPush(ts, { dryRun });
|
|
3828
3999
|
const extras = remapExtrasPush(ts, { dryRun });
|
|
3829
4000
|
const st = { dryRun, remap, extras };
|
|
@@ -3834,8 +4005,8 @@ async function cmdPush(opts = {}) {
|
|
|
3834
4005
|
renderNoScanTree(st);
|
|
3835
4006
|
return;
|
|
3836
4007
|
}
|
|
3837
|
-
const mapPath =
|
|
3838
|
-
if (!
|
|
4008
|
+
const mapPath = join36(REPO_HOME, "path-map.json");
|
|
4009
|
+
if (!existsSync29(mapPath)) {
|
|
3839
4010
|
if (dryRun) return runDryRunPreview(st, null);
|
|
3840
4011
|
die("path-map.json missing, cannot enforce push allow-list");
|
|
3841
4012
|
}
|
|
@@ -3875,18 +4046,17 @@ init_config();
|
|
|
3875
4046
|
|
|
3876
4047
|
// src/diff.ts
|
|
3877
4048
|
init_config();
|
|
3878
|
-
import { existsSync as
|
|
3879
|
-
import { join as
|
|
4049
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
4050
|
+
import { join as join37 } from "node:path";
|
|
3880
4051
|
init_utils();
|
|
3881
4052
|
init_utils_fs();
|
|
3882
4053
|
init_utils_json();
|
|
3883
4054
|
function cmdDiff() {
|
|
3884
4055
|
try {
|
|
3885
|
-
if (!
|
|
3886
|
-
const
|
|
3887
|
-
const
|
|
3888
|
-
const
|
|
3889
|
-
const map = existsSync28(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
4056
|
+
if (!existsSync30(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
4057
|
+
const ts = freshBackupTs(BACKUP_BASE);
|
|
4058
|
+
const mapPath = join37(REPO_HOME, "path-map.json");
|
|
4059
|
+
const map = existsSync30(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
3890
4060
|
const result = computePreview(ts, map);
|
|
3891
4061
|
emitSummary("diff", result.unmapped);
|
|
3892
4062
|
} catch (err) {
|
|
@@ -3901,8 +4071,8 @@ function cmdDiff() {
|
|
|
3901
4071
|
|
|
3902
4072
|
// src/init.ts
|
|
3903
4073
|
init_config();
|
|
3904
|
-
import { existsSync as
|
|
3905
|
-
import { join as
|
|
4074
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4075
|
+
import { join as join39 } from "node:path";
|
|
3906
4076
|
|
|
3907
4077
|
// src/init.gh-onboard.ts
|
|
3908
4078
|
init_config();
|
|
@@ -3983,31 +4153,31 @@ init_config();
|
|
|
3983
4153
|
init_utils();
|
|
3984
4154
|
init_utils_fs();
|
|
3985
4155
|
init_utils_json();
|
|
3986
|
-
import { copyFileSync, cpSync as cpSync6, existsSync as
|
|
3987
|
-
import { join as
|
|
4156
|
+
import { copyFileSync, cpSync as cpSync6, existsSync as existsSync31, rmSync as rmSync11, statSync as statSync8 } from "node:fs";
|
|
4157
|
+
import { join as join38 } from "node:path";
|
|
3988
4158
|
function snapshotIntoShared(map) {
|
|
3989
4159
|
for (const name of allSharedLinks(map)) {
|
|
3990
|
-
const src =
|
|
3991
|
-
if (!
|
|
3992
|
-
const dst =
|
|
3993
|
-
if (
|
|
3994
|
-
const gk =
|
|
3995
|
-
if (
|
|
4160
|
+
const src = join38(CLAUDE_HOME, name);
|
|
4161
|
+
if (!existsSync31(src)) continue;
|
|
4162
|
+
const dst = join38(REPO_HOME, "shared", name);
|
|
4163
|
+
if (statSync8(src).isDirectory()) {
|
|
4164
|
+
const gk = join38(dst, ".gitkeep");
|
|
4165
|
+
if (existsSync31(gk)) rmSync11(gk);
|
|
3996
4166
|
cpSync6(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
3997
4167
|
} else {
|
|
3998
4168
|
copyFileSync(src, dst);
|
|
3999
4169
|
}
|
|
4000
4170
|
log(`snapshotted shared/${name} from ${src}`);
|
|
4001
4171
|
}
|
|
4002
|
-
const userSettings =
|
|
4003
|
-
if (
|
|
4172
|
+
const userSettings = join38(CLAUDE_HOME, "settings.json");
|
|
4173
|
+
if (existsSync31(userSettings)) {
|
|
4004
4174
|
let parsed;
|
|
4005
4175
|
try {
|
|
4006
4176
|
parsed = readJson(userSettings);
|
|
4007
4177
|
} catch (err) {
|
|
4008
4178
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
4009
4179
|
}
|
|
4010
|
-
const hostFile =
|
|
4180
|
+
const hostFile = join38(REPO_HOME, "hosts", `${HOST}.json`);
|
|
4011
4181
|
writeJsonAtomic(hostFile, parsed);
|
|
4012
4182
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
4013
4183
|
}
|
|
@@ -4020,45 +4190,45 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
4020
4190
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
4021
4191
|
function preflightConflict(repoHome) {
|
|
4022
4192
|
const candidates = [
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4193
|
+
join39(repoHome, "shared", "settings.base.json"),
|
|
4194
|
+
join39(repoHome, "shared", "CLAUDE.md"),
|
|
4195
|
+
join39(repoHome, "path-map.json"),
|
|
4196
|
+
join39(repoHome, "hosts"),
|
|
4197
|
+
join39(repoHome, "shared")
|
|
4028
4198
|
];
|
|
4029
4199
|
for (const c of candidates) {
|
|
4030
|
-
if (
|
|
4200
|
+
if (existsSync32(c)) return c;
|
|
4031
4201
|
}
|
|
4032
4202
|
return null;
|
|
4033
4203
|
}
|
|
4034
4204
|
function cmdInit(opts = {}) {
|
|
4035
4205
|
const snapshot = opts.snapshot === true;
|
|
4036
4206
|
const keepActions = opts.keepActions === true;
|
|
4037
|
-
|
|
4207
|
+
mkdirSync9(REPO_HOME, { recursive: true });
|
|
4038
4208
|
const conflict = preflightConflict(REPO_HOME);
|
|
4039
4209
|
if (conflict !== null) {
|
|
4040
4210
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
4041
4211
|
}
|
|
4042
4212
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
4043
|
-
|
|
4044
|
-
|
|
4213
|
+
mkdirSync9(join39(REPO_HOME, "shared"), { recursive: true });
|
|
4214
|
+
mkdirSync9(join39(REPO_HOME, "hosts"), { recursive: true });
|
|
4045
4215
|
for (const name of SHARED_KEEP_DIRS) {
|
|
4046
|
-
|
|
4216
|
+
mkdirSync9(join39(REPO_HOME, "shared", name), { recursive: true });
|
|
4047
4217
|
}
|
|
4048
|
-
const userClaudeMd =
|
|
4049
|
-
if (!snapshot || !
|
|
4050
|
-
|
|
4218
|
+
const userClaudeMd = join39(CLAUDE_HOME, "CLAUDE.md");
|
|
4219
|
+
if (!snapshot || !existsSync32(userClaudeMd)) {
|
|
4220
|
+
writeFileSync6(join39(REPO_HOME, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
4051
4221
|
log("created shared/CLAUDE.md");
|
|
4052
4222
|
}
|
|
4053
4223
|
for (const name of SHARED_KEEP_DIRS) {
|
|
4054
|
-
|
|
4224
|
+
writeFileSync6(join39(REPO_HOME, "shared", name, ".gitkeep"), "");
|
|
4055
4225
|
log(`created shared/${name}/.gitkeep`);
|
|
4056
4226
|
}
|
|
4057
|
-
|
|
4227
|
+
writeFileSync6(join39(REPO_HOME, "hosts", ".gitkeep"), "");
|
|
4058
4228
|
log("created hosts/.gitkeep");
|
|
4059
|
-
writeJsonAtomic(
|
|
4229
|
+
writeJsonAtomic(join39(REPO_HOME, "shared", "settings.base.json"), {});
|
|
4060
4230
|
log("created shared/settings.base.json");
|
|
4061
|
-
writeJsonAtomic(
|
|
4231
|
+
writeJsonAtomic(join39(REPO_HOME, "path-map.json"), { projects: {} });
|
|
4062
4232
|
log("created path-map.json");
|
|
4063
4233
|
if (snapshot) {
|
|
4064
4234
|
snapshotIntoShared({ projects: {} });
|
|
@@ -4210,10 +4380,63 @@ function parseRedactArgs(argv) {
|
|
|
4210
4380
|
return { id, rule, dryRun };
|
|
4211
4381
|
}
|
|
4212
4382
|
|
|
4383
|
+
// src/nomad.dispatch.clean.ts
|
|
4384
|
+
var REJECT = { ok: false, advance: 0 };
|
|
4385
|
+
function applyOlderThan(argv, i, st) {
|
|
4386
|
+
if (st.olderThan !== void 0) return REJECT;
|
|
4387
|
+
const val = extractFlagValue(argv, i);
|
|
4388
|
+
if (val === null) return REJECT;
|
|
4389
|
+
st.olderThan = val;
|
|
4390
|
+
return { ok: true, advance: 2 };
|
|
4391
|
+
}
|
|
4392
|
+
function applyKeep(argv, i, st) {
|
|
4393
|
+
if (st.keep !== void 0) return REJECT;
|
|
4394
|
+
const val = extractFlagValue(argv, i);
|
|
4395
|
+
if (val === null || !/^\d+$/.test(val)) return REJECT;
|
|
4396
|
+
st.keep = Number(val);
|
|
4397
|
+
return { ok: true, advance: 2 };
|
|
4398
|
+
}
|
|
4399
|
+
function applyBool(seen, set) {
|
|
4400
|
+
if (seen) return REJECT;
|
|
4401
|
+
set();
|
|
4402
|
+
return { ok: true, advance: 1 };
|
|
4403
|
+
}
|
|
4404
|
+
function applyCleanToken(argv, i, st) {
|
|
4405
|
+
switch (argv[i]) {
|
|
4406
|
+
case "--backups":
|
|
4407
|
+
return applyBool(st.backups, () => st.backups = true);
|
|
4408
|
+
case "--dry-run":
|
|
4409
|
+
return applyBool(st.dryRun, () => st.dryRun = true);
|
|
4410
|
+
case "--older-than":
|
|
4411
|
+
return applyOlderThan(argv, i, st);
|
|
4412
|
+
case "--keep":
|
|
4413
|
+
return applyKeep(argv, i, st);
|
|
4414
|
+
default:
|
|
4415
|
+
return REJECT;
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
function parseCleanArgs(argv) {
|
|
4419
|
+
const st = {
|
|
4420
|
+
backups: false,
|
|
4421
|
+
dryRun: false,
|
|
4422
|
+
olderThan: void 0,
|
|
4423
|
+
keep: void 0
|
|
4424
|
+
};
|
|
4425
|
+
let i = 3;
|
|
4426
|
+
while (i < argv.length) {
|
|
4427
|
+
const { ok: ok2, advance } = applyCleanToken(argv, i, st);
|
|
4428
|
+
if (!ok2) return null;
|
|
4429
|
+
i += advance;
|
|
4430
|
+
}
|
|
4431
|
+
if (!st.backups) return null;
|
|
4432
|
+
if (st.olderThan !== void 0 && st.keep !== void 0) return null;
|
|
4433
|
+
return { dryRun: st.dryRun, olderThan: st.olderThan, keep: st.keep };
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4213
4436
|
// package.json
|
|
4214
4437
|
var package_default = {
|
|
4215
4438
|
name: "claude-nomad",
|
|
4216
|
-
version: "0.
|
|
4439
|
+
version: "0.35.0",
|
|
4217
4440
|
type: "module",
|
|
4218
4441
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
4219
4442
|
keywords: [
|
|
@@ -4363,6 +4586,14 @@ var DEFAULT_HELP = [
|
|
|
4363
4586
|
"",
|
|
4364
4587
|
row(" update", "Update the claude-nomad CLI to the latest npm release."),
|
|
4365
4588
|
"",
|
|
4589
|
+
row(" clean", "Prune old backup snapshots under ~/.cache/claude-nomad/backup/."),
|
|
4590
|
+
row(" --backups", "Required: confirm backup pruning is the intended target."),
|
|
4591
|
+
row(" --dry-run", "List the snapshots that would be removed without deleting."),
|
|
4592
|
+
row(" --older-than <dur>", "Delete snapshots older than a duration (e.g. 14d, 24h, 30m)."),
|
|
4593
|
+
cont("Default when no retention flag is given: 14d."),
|
|
4594
|
+
row(" --keep <N>", "Keep the N most-recent snapshots, delete the rest. Mutually"),
|
|
4595
|
+
cont("exclusive with --older-than."),
|
|
4596
|
+
"",
|
|
4366
4597
|
row(" --version", "Print the installed CLI version as bare semver to stdout; exits 0."),
|
|
4367
4598
|
"",
|
|
4368
4599
|
"Run `nomad doctor` to validate your setup. Edit shared/ or hosts/<HOST>.json",
|
|
@@ -4374,15 +4605,15 @@ var DEFAULT_HELP = [
|
|
|
4374
4605
|
init_config();
|
|
4375
4606
|
init_utils();
|
|
4376
4607
|
init_utils_json();
|
|
4377
|
-
import { existsSync as
|
|
4378
|
-
import { join as
|
|
4608
|
+
import { existsSync as existsSync33, readFileSync as readFileSync10, readdirSync as readdirSync10 } from "node:fs";
|
|
4609
|
+
import { join as join40 } from "node:path";
|
|
4379
4610
|
function resumeCmd(sessionId) {
|
|
4380
4611
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
4381
4612
|
fail(`invalid session id: ${sessionId}`);
|
|
4382
4613
|
process.exit(1);
|
|
4383
4614
|
}
|
|
4384
|
-
const projectsRoot =
|
|
4385
|
-
if (!
|
|
4615
|
+
const projectsRoot = join40(CLAUDE_HOME, "projects");
|
|
4616
|
+
if (!existsSync33(projectsRoot)) {
|
|
4386
4617
|
fail(`${projectsRoot} does not exist`);
|
|
4387
4618
|
process.exit(1);
|
|
4388
4619
|
}
|
|
@@ -4396,8 +4627,8 @@ function resumeCmd(sessionId) {
|
|
|
4396
4627
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
4397
4628
|
process.exit(1);
|
|
4398
4629
|
}
|
|
4399
|
-
const mapPath =
|
|
4400
|
-
if (!
|
|
4630
|
+
const mapPath = join40(REPO_HOME, "path-map.json");
|
|
4631
|
+
if (!existsSync33(mapPath)) {
|
|
4401
4632
|
fail("path-map.json missing");
|
|
4402
4633
|
process.exit(1);
|
|
4403
4634
|
}
|
|
@@ -4419,14 +4650,14 @@ function resumeCmd(sessionId) {
|
|
|
4419
4650
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
4420
4651
|
}
|
|
4421
4652
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
4422
|
-
for (const dir of
|
|
4423
|
-
const candidate =
|
|
4424
|
-
if (
|
|
4653
|
+
for (const dir of readdirSync10(projectsRoot)) {
|
|
4654
|
+
const candidate = join40(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
4655
|
+
if (existsSync33(candidate)) return candidate;
|
|
4425
4656
|
}
|
|
4426
4657
|
return null;
|
|
4427
4658
|
}
|
|
4428
4659
|
function extractRecordedCwd(jsonlPath) {
|
|
4429
|
-
for (const line of
|
|
4660
|
+
for (const line of readFileSync10(jsonlPath, "utf8").split("\n")) {
|
|
4430
4661
|
if (!line.trim()) continue;
|
|
4431
4662
|
try {
|
|
4432
4663
|
const obj = JSON.parse(line);
|
|
@@ -4590,6 +4821,15 @@ try {
|
|
|
4590
4821
|
cmdRedact(redactArgs);
|
|
4591
4822
|
break;
|
|
4592
4823
|
}
|
|
4824
|
+
case "clean": {
|
|
4825
|
+
const cleanArgs = parseCleanArgs(process.argv);
|
|
4826
|
+
if (cleanArgs === null) {
|
|
4827
|
+
console.error("usage: nomad clean --backups [--dry-run] [--older-than <dur> | --keep <N>]");
|
|
4828
|
+
process.exit(1);
|
|
4829
|
+
}
|
|
4830
|
+
cmdClean(cleanArgs);
|
|
4831
|
+
break;
|
|
4832
|
+
}
|
|
4593
4833
|
default:
|
|
4594
4834
|
console.error(DEFAULT_HELP);
|
|
4595
4835
|
process.exit(1);
|