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