claude-nomad 0.41.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +8 -0
- package/dist/nomad.mjs +451 -254
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -449,6 +449,17 @@ function deepMerge(target, source) {
|
|
|
449
449
|
}
|
|
450
450
|
return out;
|
|
451
451
|
}
|
|
452
|
+
function sortKeysDeep(value) {
|
|
453
|
+
if (Array.isArray(value)) return value.map(sortKeysDeep);
|
|
454
|
+
if (value !== null && typeof value === "object") {
|
|
455
|
+
const out = {};
|
|
456
|
+
for (const key of Object.keys(value).sort((a, b) => a.localeCompare(b, "en"))) {
|
|
457
|
+
out[key] = sortKeysDeep(value[key]);
|
|
458
|
+
}
|
|
459
|
+
return out;
|
|
460
|
+
}
|
|
461
|
+
return value;
|
|
462
|
+
}
|
|
452
463
|
var encodePath;
|
|
453
464
|
var init_utils_json = __esm({
|
|
454
465
|
"src/utils.json.ts"() {
|
|
@@ -550,31 +561,31 @@ var init_utils_fs = __esm({
|
|
|
550
561
|
});
|
|
551
562
|
|
|
552
563
|
// src/push-gitleaks.config.ts
|
|
553
|
-
import { existsSync as
|
|
564
|
+
import { existsSync as existsSync10, mkdtempSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
554
565
|
import { tmpdir } from "node:os";
|
|
555
|
-
import { join as
|
|
566
|
+
import { join as join11 } from "node:path";
|
|
556
567
|
import { fileURLToPath } from "node:url";
|
|
557
568
|
function resolveTomlPath() {
|
|
558
|
-
const repoToml =
|
|
559
|
-
if (
|
|
569
|
+
const repoToml = join11(REPO_HOME, ".gitleaks.toml");
|
|
570
|
+
if (existsSync10(repoToml)) return repoToml;
|
|
560
571
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
561
|
-
return
|
|
572
|
+
return existsSync10(bundled) ? bundled : null;
|
|
562
573
|
}
|
|
563
574
|
function buildOverlayTempConfig(overlayBody, bundled) {
|
|
564
575
|
const tempBody = `[extend]
|
|
565
576
|
path = ${JSON.stringify(bundled)}
|
|
566
577
|
|
|
567
578
|
${overlayBody}`;
|
|
568
|
-
const tempPath = mkdtempSync(
|
|
569
|
-
const configPath =
|
|
579
|
+
const tempPath = mkdtempSync(join11(tmpdir(), "nomad-gitleaks-cfg-"));
|
|
580
|
+
const configPath = join11(tempPath, "config.toml");
|
|
570
581
|
writeFileSync2(configPath, tempBody, { mode: 384, flag: "wx" });
|
|
571
582
|
return { configPath, tempPath };
|
|
572
583
|
}
|
|
573
584
|
function resolveTomlConfig() {
|
|
574
|
-
const overlayPath =
|
|
575
|
-
const repoToml =
|
|
585
|
+
const overlayPath = join11(REPO_HOME, ".gitleaks.overlay.toml");
|
|
586
|
+
const repoToml = join11(REPO_HOME, ".gitleaks.toml");
|
|
576
587
|
const bundled = resolveTomlPath();
|
|
577
|
-
if (!
|
|
588
|
+
if (!existsSync10(overlayPath)) {
|
|
578
589
|
return { path: bundled, tempPath: null };
|
|
579
590
|
}
|
|
580
591
|
if (bundled === repoToml) {
|
|
@@ -617,7 +628,7 @@ var init_push_gitleaks_config = __esm({
|
|
|
617
628
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
618
629
|
import { readdirSync as readdirSync3, rmSync as rmSync3 } from "node:fs";
|
|
619
630
|
import { homedir as homedir2, platform } from "node:os";
|
|
620
|
-
import { join as
|
|
631
|
+
import { join as join12 } from "node:path";
|
|
621
632
|
function gitleaksInstallHint() {
|
|
622
633
|
const head = "gitleaks not on PATH (required for nomad push). Install:";
|
|
623
634
|
const plat = platform();
|
|
@@ -660,7 +671,7 @@ function findGitlinks(dir) {
|
|
|
660
671
|
return;
|
|
661
672
|
}
|
|
662
673
|
for (const e of entries) {
|
|
663
|
-
const p =
|
|
674
|
+
const p = join12(current, e.name);
|
|
664
675
|
if (e.name === ".git") {
|
|
665
676
|
hits.push(p);
|
|
666
677
|
continue;
|
|
@@ -712,7 +723,7 @@ var init_push_checks = __esm({
|
|
|
712
723
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
713
724
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync4 } from "node:fs";
|
|
714
725
|
import { homedir as homedir3 } from "node:os";
|
|
715
|
-
import { join as
|
|
726
|
+
import { join as join16 } from "node:path";
|
|
716
727
|
function readGitleaksReport(reportPath) {
|
|
717
728
|
try {
|
|
718
729
|
const raw = readFileSync4(reportPath, "utf8");
|
|
@@ -724,9 +735,9 @@ function readGitleaksReport(reportPath) {
|
|
|
724
735
|
}
|
|
725
736
|
}
|
|
726
737
|
function scanStagedTree(repoDir, forwardStreams = false) {
|
|
727
|
-
const cacheDir =
|
|
738
|
+
const cacheDir = join16(homedir3(), ".cache", "claude-nomad");
|
|
728
739
|
mkdirSync2(cacheDir, { recursive: true });
|
|
729
|
-
const reportPath =
|
|
740
|
+
const reportPath = join16(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
|
|
730
741
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
731
742
|
const args = [
|
|
732
743
|
"protect",
|
|
@@ -758,9 +769,9 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
758
769
|
}
|
|
759
770
|
}
|
|
760
771
|
function scanFile(filePath, forwardStreams = false) {
|
|
761
|
-
const cacheDir =
|
|
772
|
+
const cacheDir = join16(homedir3(), ".cache", "claude-nomad");
|
|
762
773
|
mkdirSync2(cacheDir, { recursive: true });
|
|
763
|
-
const reportPath =
|
|
774
|
+
const reportPath = join16(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
|
|
764
775
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
765
776
|
const args = [
|
|
766
777
|
"detect",
|
|
@@ -1209,8 +1220,8 @@ function cmdClean(opts, backupBase = BACKUP_BASE) {
|
|
|
1209
1220
|
}
|
|
1210
1221
|
|
|
1211
1222
|
// src/commands.doctor.ts
|
|
1212
|
-
import { existsSync as
|
|
1213
|
-
import { join as
|
|
1223
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
1224
|
+
import { join as join23 } from "node:path";
|
|
1214
1225
|
|
|
1215
1226
|
// src/commands.doctor.checks.repo.ts
|
|
1216
1227
|
init_color();
|
|
@@ -1551,8 +1562,20 @@ function reportNeverSync(section2) {
|
|
|
1551
1562
|
init_color();
|
|
1552
1563
|
init_config();
|
|
1553
1564
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1554
|
-
import { existsSync as
|
|
1555
|
-
import { join as
|
|
1565
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
1566
|
+
import { join as join13, relative as relative2 } from "node:path";
|
|
1567
|
+
|
|
1568
|
+
// src/commands.pull.wedge.ts
|
|
1569
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
1570
|
+
import { join as join10 } from "node:path";
|
|
1571
|
+
function detectWedge(repo) {
|
|
1572
|
+
const g = join10(repo, ".git");
|
|
1573
|
+
if (existsSync9(join10(g, "rebase-merge")) || existsSync9(join10(g, "rebase-apply"))) return "rebase";
|
|
1574
|
+
if (existsSync9(join10(g, "MERGE_HEAD"))) return "merge";
|
|
1575
|
+
return null;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/commands.doctor.checks.repository.ts
|
|
1556
1579
|
init_push_checks();
|
|
1557
1580
|
init_utils();
|
|
1558
1581
|
function reportGitleaksProbe(section2) {
|
|
@@ -1571,8 +1594,8 @@ function reportGitleaksProbe(section2) {
|
|
|
1571
1594
|
}
|
|
1572
1595
|
}
|
|
1573
1596
|
function reportGitlinks(section2) {
|
|
1574
|
-
const sharedDir =
|
|
1575
|
-
if (
|
|
1597
|
+
const sharedDir = join13(REPO_HOME, "shared");
|
|
1598
|
+
if (existsSync11(sharedDir)) {
|
|
1576
1599
|
const gitlinks = findGitlinks(sharedDir);
|
|
1577
1600
|
for (const p of gitlinks) {
|
|
1578
1601
|
const rel = relative2(REPO_HOME, p);
|
|
@@ -1611,11 +1634,25 @@ function reportRebaseClean(section2) {
|
|
|
1611
1634
|
} catch {
|
|
1612
1635
|
}
|
|
1613
1636
|
}
|
|
1637
|
+
function reportRebaseState(section2) {
|
|
1638
|
+
try {
|
|
1639
|
+
const wedge = detectWedge(REPO_HOME);
|
|
1640
|
+
if (wedge !== null) {
|
|
1641
|
+
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
1642
|
+
addItem(
|
|
1643
|
+
section2,
|
|
1644
|
+
`${red(failGlyph)} repo is ${state}: run 'nomad pull --force-remote' to auto-recover`
|
|
1645
|
+
);
|
|
1646
|
+
process.exitCode = 1;
|
|
1647
|
+
}
|
|
1648
|
+
} catch {
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1614
1651
|
|
|
1615
1652
|
// src/commands.doctor.checks.backups.ts
|
|
1616
1653
|
init_color();
|
|
1617
|
-
import { existsSync as
|
|
1618
|
-
import { join as
|
|
1654
|
+
import { existsSync as existsSync12, lstatSync as lstatSync5, readdirSync as readdirSync4 } from "node:fs";
|
|
1655
|
+
import { join as join14 } from "node:path";
|
|
1619
1656
|
init_config();
|
|
1620
1657
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
1621
1658
|
function safeReaddir(dir) {
|
|
@@ -1631,7 +1668,7 @@ var BYTES_PER_MB = 1024 * 1024;
|
|
|
1631
1668
|
function dirSizeBytes(dir) {
|
|
1632
1669
|
let bytes = 0;
|
|
1633
1670
|
for (const entry of safeReaddir(dir)) {
|
|
1634
|
-
const full =
|
|
1671
|
+
const full = join14(dir, entry);
|
|
1635
1672
|
const st = lstatSync5(full, { throwIfNoEntry: false });
|
|
1636
1673
|
if (!st) continue;
|
|
1637
1674
|
if (st.isSymbolicLink()) continue;
|
|
@@ -1642,11 +1679,11 @@ function dirSizeBytes(dir) {
|
|
|
1642
1679
|
}
|
|
1643
1680
|
function totalSizeMb(backupBase, dirs) {
|
|
1644
1681
|
let bytes = 0;
|
|
1645
|
-
for (const name of dirs) bytes += dirSizeBytes(
|
|
1682
|
+
for (const name of dirs) bytes += dirSizeBytes(join14(backupBase, name));
|
|
1646
1683
|
return bytes / BYTES_PER_MB;
|
|
1647
1684
|
}
|
|
1648
1685
|
function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
|
|
1649
|
-
if (!
|
|
1686
|
+
if (!existsSync12(backupBase)) return;
|
|
1650
1687
|
const dirs = safeReaddir(backupBase).filter((n) => TS_SHAPE2.test(n));
|
|
1651
1688
|
const count = dirs.length;
|
|
1652
1689
|
const sizeMb = totalSizeMb(backupBase, dirs);
|
|
@@ -1660,8 +1697,8 @@ function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
|
|
|
1660
1697
|
|
|
1661
1698
|
// src/commands.doctor.check-schema.ts
|
|
1662
1699
|
init_color();
|
|
1663
|
-
import { existsSync as
|
|
1664
|
-
import { join as
|
|
1700
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
1701
|
+
import { join as join15 } from "node:path";
|
|
1665
1702
|
init_config();
|
|
1666
1703
|
|
|
1667
1704
|
// src/http-fetch.ts
|
|
@@ -1698,8 +1735,8 @@ function fetchSchemaKeys() {
|
|
|
1698
1735
|
}
|
|
1699
1736
|
}
|
|
1700
1737
|
function reportCheckSchema(section2) {
|
|
1701
|
-
const settingsPath =
|
|
1702
|
-
if (!
|
|
1738
|
+
const settingsPath = join15(CLAUDE_HOME, "settings.json");
|
|
1739
|
+
if (!existsSync13(settingsPath)) {
|
|
1703
1740
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
1704
1741
|
return;
|
|
1705
1742
|
}
|
|
@@ -1729,18 +1766,18 @@ function reportCheckSchema(section2) {
|
|
|
1729
1766
|
init_color();
|
|
1730
1767
|
import { randomBytes } from "node:crypto";
|
|
1731
1768
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
1732
|
-
import { existsSync as
|
|
1769
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync4, readdirSync as readdirSync6, rmSync as rmSync6 } from "node:fs";
|
|
1733
1770
|
import { homedir as homedir4 } from "node:os";
|
|
1734
|
-
import { join as
|
|
1771
|
+
import { join as join19 } from "node:path";
|
|
1735
1772
|
|
|
1736
1773
|
// src/commands.doctor.check-shared.scan.ts
|
|
1737
1774
|
init_color();
|
|
1738
|
-
import { join as
|
|
1775
|
+
import { join as join17 } from "node:path";
|
|
1739
1776
|
init_config();
|
|
1740
1777
|
init_push_gitleaks();
|
|
1741
1778
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
1742
1779
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
1743
|
-
return
|
|
1780
|
+
return join17(CLAUDE_HOME, "projects", encoded, `${sid}.jsonl`);
|
|
1744
1781
|
}
|
|
1745
1782
|
function reportSessionFindings(section2, bySession) {
|
|
1746
1783
|
for (const [sid, counts] of bySession) {
|
|
@@ -1832,8 +1869,8 @@ init_config();
|
|
|
1832
1869
|
init_utils();
|
|
1833
1870
|
init_utils_fs();
|
|
1834
1871
|
init_utils_json();
|
|
1835
|
-
import { cpSync as cpSync3, existsSync as
|
|
1836
|
-
import { join as
|
|
1872
|
+
import { cpSync as cpSync3, existsSync as existsSync14, mkdirSync as mkdirSync3, readdirSync as readdirSync5, rmSync as rmSync5, statSync as statSync4 } from "node:fs";
|
|
1873
|
+
import { join as join18, relative as relative3, sep } from "node:path";
|
|
1837
1874
|
function copyDir(src, dst) {
|
|
1838
1875
|
rmSync5(dst, { recursive: true, force: true });
|
|
1839
1876
|
cpSync3(src, dst, { recursive: true, force: true });
|
|
@@ -1863,15 +1900,15 @@ function remapPull(ts, opts = {}) {
|
|
|
1863
1900
|
let unmapped = 0;
|
|
1864
1901
|
const pulled = [];
|
|
1865
1902
|
const wouldPull = [];
|
|
1866
|
-
const mapPath =
|
|
1867
|
-
const repoProjects =
|
|
1868
|
-
if (!
|
|
1903
|
+
const mapPath = join18(REPO_HOME, "path-map.json");
|
|
1904
|
+
const repoProjects = join18(REPO_HOME, "shared", "projects");
|
|
1905
|
+
if (!existsSync14(mapPath) || !existsSync14(repoProjects)) {
|
|
1869
1906
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
1870
1907
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
1871
1908
|
return { unmapped: 0, pulled, wouldPull };
|
|
1872
1909
|
}
|
|
1873
1910
|
const map = readJson(mapPath);
|
|
1874
|
-
const localProjects =
|
|
1911
|
+
const localProjects = join18(CLAUDE_HOME, "projects");
|
|
1875
1912
|
if (!dryRun) mkdirSync3(localProjects, { recursive: true });
|
|
1876
1913
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
1877
1914
|
assertSafeLogical(logical);
|
|
@@ -1880,9 +1917,9 @@ function remapPull(ts, opts = {}) {
|
|
|
1880
1917
|
unmapped++;
|
|
1881
1918
|
continue;
|
|
1882
1919
|
}
|
|
1883
|
-
const src =
|
|
1884
|
-
if (!
|
|
1885
|
-
const dst =
|
|
1920
|
+
const src = join18(repoProjects, logical);
|
|
1921
|
+
if (!existsSync14(src)) continue;
|
|
1922
|
+
const dst = join18(localProjects, encodePath(localPath));
|
|
1886
1923
|
if (dryRun) {
|
|
1887
1924
|
wouldPull.push(logical);
|
|
1888
1925
|
emitPreview(
|
|
@@ -1927,16 +1964,16 @@ function remapPush(ts, opts = {}) {
|
|
|
1927
1964
|
let unmapped = 0;
|
|
1928
1965
|
const pushed = [];
|
|
1929
1966
|
const wouldPush = [];
|
|
1930
|
-
const mapPath =
|
|
1931
|
-
if (!
|
|
1967
|
+
const mapPath = join18(REPO_HOME, "path-map.json");
|
|
1968
|
+
if (!existsSync14(mapPath)) {
|
|
1932
1969
|
log("no path-map.json; skipping session export");
|
|
1933
1970
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
1934
1971
|
}
|
|
1935
1972
|
const map = readJson(mapPath);
|
|
1936
|
-
const localProjects =
|
|
1937
|
-
const repoProjects =
|
|
1973
|
+
const localProjects = join18(CLAUDE_HOME, "projects");
|
|
1974
|
+
const repoProjects = join18(REPO_HOME, "shared", "projects");
|
|
1938
1975
|
const reverse = buildReverseMap(map);
|
|
1939
|
-
if (!
|
|
1976
|
+
if (!existsSync14(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
1940
1977
|
if (!dryRun) mkdirSync3(repoProjects, { recursive: true });
|
|
1941
1978
|
for (const dir of readdirSync5(localProjects)) {
|
|
1942
1979
|
const logical = reverse.get(dir);
|
|
@@ -1944,13 +1981,13 @@ function remapPush(ts, opts = {}) {
|
|
|
1944
1981
|
unmapped++;
|
|
1945
1982
|
continue;
|
|
1946
1983
|
}
|
|
1947
|
-
const repoDst =
|
|
1984
|
+
const repoDst = join18(repoProjects, logical);
|
|
1948
1985
|
if (dryRun) {
|
|
1949
1986
|
wouldPush.push(logical);
|
|
1950
1987
|
continue;
|
|
1951
1988
|
}
|
|
1952
1989
|
backupRepoWrite(repoDst, ts, REPO_HOME);
|
|
1953
|
-
copyDirJsonlOnly(
|
|
1990
|
+
copyDirJsonlOnly(join18(localProjects, dir), repoDst);
|
|
1954
1991
|
pushed.push(logical);
|
|
1955
1992
|
}
|
|
1956
1993
|
return { unmapped, collisions: 0, pushed, wouldPush };
|
|
@@ -1962,8 +1999,8 @@ init_utils_json();
|
|
|
1962
1999
|
function buildScanTree(tmpRoot) {
|
|
1963
2000
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
1964
2001
|
let staged = 0;
|
|
1965
|
-
const mapPath =
|
|
1966
|
-
if (!
|
|
2002
|
+
const mapPath = join19(REPO_HOME, "path-map.json");
|
|
2003
|
+
if (!existsSync15(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
1967
2004
|
let map;
|
|
1968
2005
|
try {
|
|
1969
2006
|
map = readJson(mapPath);
|
|
@@ -1980,12 +2017,12 @@ function buildScanTree(tmpRoot) {
|
|
|
1980
2017
|
if (!p || p === "TBD") continue;
|
|
1981
2018
|
reverse.set(encodePath(p), logical);
|
|
1982
2019
|
}
|
|
1983
|
-
const localProjects =
|
|
1984
|
-
if (!
|
|
2020
|
+
const localProjects = join19(CLAUDE_HOME, "projects");
|
|
2021
|
+
if (!existsSync15(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
1985
2022
|
for (const dir of readdirSync6(localProjects)) {
|
|
1986
2023
|
const logical = reverse.get(dir);
|
|
1987
2024
|
if (!logical) continue;
|
|
1988
|
-
copyDirJsonlOnly(
|
|
2025
|
+
copyDirJsonlOnly(join19(localProjects, dir), join19(tmpRoot, "shared", "projects", logical));
|
|
1989
2026
|
logicalToEncoded.set(logical, dir);
|
|
1990
2027
|
staged++;
|
|
1991
2028
|
}
|
|
@@ -2016,11 +2053,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
2016
2053
|
}
|
|
2017
2054
|
function reportCheckShared(section2, gitleaksReady) {
|
|
2018
2055
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
2019
|
-
const cacheDir =
|
|
2056
|
+
const cacheDir = join19(homedir4(), ".cache", "claude-nomad");
|
|
2020
2057
|
mkdirSync4(cacheDir, { recursive: true });
|
|
2021
2058
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
2022
|
-
const reportPath =
|
|
2023
|
-
const tmpRoot =
|
|
2059
|
+
const reportPath = join19(cacheDir, `check-shared-${stamp}.json`);
|
|
2060
|
+
const tmpRoot = join19(cacheDir, `check-shared-tree-${stamp}`);
|
|
2024
2061
|
try {
|
|
2025
2062
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
2026
2063
|
if (malformed) {
|
|
@@ -2041,8 +2078,8 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
2041
2078
|
|
|
2042
2079
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
2043
2080
|
init_color();
|
|
2044
|
-
import { existsSync as
|
|
2045
|
-
import { dirname as dirname2, extname, join as
|
|
2081
|
+
import { existsSync as existsSync16, readFileSync as readFileSync5, readdirSync as readdirSync7, realpathSync } from "node:fs";
|
|
2082
|
+
import { dirname as dirname2, extname, join as join20 } from "node:path";
|
|
2046
2083
|
init_config();
|
|
2047
2084
|
function typeFromPackageJson(pkgPath) {
|
|
2048
2085
|
try {
|
|
@@ -2064,8 +2101,8 @@ function effectiveType(hookPath) {
|
|
|
2064
2101
|
}
|
|
2065
2102
|
let dir = dirname2(real);
|
|
2066
2103
|
for (; ; ) {
|
|
2067
|
-
const pkg =
|
|
2068
|
-
if (
|
|
2104
|
+
const pkg = join20(dir, "package.json");
|
|
2105
|
+
if (existsSync16(pkg)) return typeFromPackageJson(pkg);
|
|
2069
2106
|
const parent = dirname2(dir);
|
|
2070
2107
|
if (parent === dir) return "cjs";
|
|
2071
2108
|
dir = parent;
|
|
@@ -2107,15 +2144,15 @@ function safeRead(path) {
|
|
|
2107
2144
|
}
|
|
2108
2145
|
}
|
|
2109
2146
|
function reportHookScopeCheck(section2) {
|
|
2110
|
-
const hooksDir =
|
|
2111
|
-
if (!
|
|
2147
|
+
const hooksDir = join20(CLAUDE_HOME, "hooks");
|
|
2148
|
+
if (!existsSync16(hooksDir)) {
|
|
2112
2149
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
2113
2150
|
return;
|
|
2114
2151
|
}
|
|
2115
2152
|
let anyWarn = false;
|
|
2116
2153
|
for (const name of safeReaddir2(hooksDir)) {
|
|
2117
2154
|
if (extname(name) !== ".js") continue;
|
|
2118
|
-
const abs =
|
|
2155
|
+
const abs = join20(hooksDir, name);
|
|
2119
2156
|
const eff = effectiveType(abs);
|
|
2120
2157
|
if (eff === null) continue;
|
|
2121
2158
|
const src = safeRead(abs);
|
|
@@ -2135,8 +2172,8 @@ function reportHookScopeCheck(section2) {
|
|
|
2135
2172
|
|
|
2136
2173
|
// src/commands.doctor.checks.hooks.ts
|
|
2137
2174
|
init_color();
|
|
2138
|
-
import { existsSync as
|
|
2139
|
-
import { join as
|
|
2175
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
2176
|
+
import { join as join21 } from "node:path";
|
|
2140
2177
|
init_config();
|
|
2141
2178
|
function expandHome(token) {
|
|
2142
2179
|
return token.replace(/^\$\{HOME\}/, HOME).replace(/^\$HOME/, HOME).replace(/^~/, HOME);
|
|
@@ -2174,7 +2211,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
2174
2211
|
for (const group of groups) {
|
|
2175
2212
|
for (const cmd of commandsFromGroup(group)) {
|
|
2176
2213
|
for (const resolved of claudePathsIn(cmd)) {
|
|
2177
|
-
if (
|
|
2214
|
+
if (existsSync17(resolved)) continue;
|
|
2178
2215
|
addItem(section2, `${red(failGlyph)} hooks/${event}: command target missing: ${resolved}`);
|
|
2179
2216
|
process.exitCode = 1;
|
|
2180
2217
|
anyFail = true;
|
|
@@ -2184,8 +2221,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
2184
2221
|
return anyFail;
|
|
2185
2222
|
}
|
|
2186
2223
|
function reportHooksTargetCheck(section2) {
|
|
2187
|
-
const settingsPath =
|
|
2188
|
-
if (!
|
|
2224
|
+
const settingsPath = join21(CLAUDE_HOME, "settings.json");
|
|
2225
|
+
if (!existsSync17(settingsPath)) {
|
|
2189
2226
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
2190
2227
|
return;
|
|
2191
2228
|
}
|
|
@@ -2315,8 +2352,8 @@ function reportNodeEngineCheck(section2) {
|
|
|
2315
2352
|
// src/commands.doctor.gitleaks-version.ts
|
|
2316
2353
|
init_color();
|
|
2317
2354
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
2318
|
-
import { existsSync as
|
|
2319
|
-
import { join as
|
|
2355
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
2356
|
+
import { join as join22 } from "node:path";
|
|
2320
2357
|
init_config();
|
|
2321
2358
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
2322
2359
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -2325,7 +2362,7 @@ function majorMinorOf(value) {
|
|
|
2325
2362
|
return m === null ? null : [m[1], m[2]];
|
|
2326
2363
|
}
|
|
2327
2364
|
function readGitleaksVersion(run, tomlExists) {
|
|
2328
|
-
const tomlPath =
|
|
2365
|
+
const tomlPath = join22(REPO_HOME, ".gitleaks.toml");
|
|
2329
2366
|
const args = ["version"];
|
|
2330
2367
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
2331
2368
|
try {
|
|
@@ -2337,7 +2374,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
2337
2374
|
return null;
|
|
2338
2375
|
}
|
|
2339
2376
|
}
|
|
2340
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists =
|
|
2377
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync18) {
|
|
2341
2378
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
2342
2379
|
if (raw === null) return;
|
|
2343
2380
|
const local = majorMinorOf(raw);
|
|
@@ -2510,8 +2547,8 @@ function cmdDoctor(opts = {}) {
|
|
|
2510
2547
|
reportHostAndPaths(host);
|
|
2511
2548
|
reportRepoState(host);
|
|
2512
2549
|
const links = section("Shared links");
|
|
2513
|
-
const mapPath =
|
|
2514
|
-
const rawMap =
|
|
2550
|
+
const mapPath = join23(REPO_HOME, "path-map.json");
|
|
2551
|
+
const rawMap = existsSync19(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
2515
2552
|
const map = rawMap ?? { projects: {} };
|
|
2516
2553
|
reportSharedLinks(links, map);
|
|
2517
2554
|
const hooksScan = section("Hook targets");
|
|
@@ -2530,6 +2567,7 @@ function cmdDoctor(opts = {}) {
|
|
|
2530
2567
|
reportGitlinks(repository);
|
|
2531
2568
|
reportRemote(repository);
|
|
2532
2569
|
reportRebaseClean(repository);
|
|
2570
|
+
reportRebaseState(repository);
|
|
2533
2571
|
reportActionsDrift(repository);
|
|
2534
2572
|
const nomadVersion = section("Nomad Version");
|
|
2535
2573
|
reportVersionCheck(nomadVersion);
|
|
@@ -2560,8 +2598,8 @@ function cmdDoctor(opts = {}) {
|
|
|
2560
2598
|
// src/commands.drop-session.ts
|
|
2561
2599
|
init_config();
|
|
2562
2600
|
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
2563
|
-
import { existsSync as
|
|
2564
|
-
import { join as
|
|
2601
|
+
import { existsSync as existsSync21, readdirSync as readdirSync8, statSync as statSync5 } from "node:fs";
|
|
2602
|
+
import { join as join26, relative as relative4 } from "node:path";
|
|
2565
2603
|
|
|
2566
2604
|
// src/commands.drop-session.git.ts
|
|
2567
2605
|
init_config();
|
|
@@ -2604,8 +2642,8 @@ function isInIndex(rel) {
|
|
|
2604
2642
|
init_config();
|
|
2605
2643
|
init_utils();
|
|
2606
2644
|
init_utils_json();
|
|
2607
|
-
import { existsSync as
|
|
2608
|
-
import { join as
|
|
2645
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
2646
|
+
import { join as join24 } from "node:path";
|
|
2609
2647
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
2610
2648
|
function reportScrubHint(id, matches) {
|
|
2611
2649
|
const live = resolveLiveTranscript(id, matches);
|
|
@@ -2621,16 +2659,16 @@ function reportScrubHint(id, matches) {
|
|
|
2621
2659
|
}
|
|
2622
2660
|
function resolveLiveTranscript(id, matches) {
|
|
2623
2661
|
try {
|
|
2624
|
-
const mapPath =
|
|
2625
|
-
if (!
|
|
2662
|
+
const mapPath = join24(REPO_HOME, "path-map.json");
|
|
2663
|
+
if (!existsSync20(mapPath)) return null;
|
|
2626
2664
|
const projects = readJson(mapPath).projects;
|
|
2627
2665
|
for (const rel of matches) {
|
|
2628
2666
|
const logical = SHARED_PROJECT_LOGICAL.exec(rel)?.[1];
|
|
2629
2667
|
if (logical === void 0) continue;
|
|
2630
2668
|
const abs = projects[logical]?.[HOST];
|
|
2631
2669
|
if (abs === void 0) continue;
|
|
2632
|
-
const live =
|
|
2633
|
-
if (
|
|
2670
|
+
const live = join24(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
|
|
2671
|
+
if (existsSync20(live)) return live;
|
|
2634
2672
|
}
|
|
2635
2673
|
return null;
|
|
2636
2674
|
} catch {
|
|
@@ -2645,8 +2683,8 @@ init_utils();
|
|
|
2645
2683
|
init_config();
|
|
2646
2684
|
init_utils();
|
|
2647
2685
|
import { closeSync as closeSync2, mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2648
|
-
import { dirname as dirname3, join as
|
|
2649
|
-
var LOCK_PATH =
|
|
2686
|
+
import { dirname as dirname3, join as join25 } from "node:path";
|
|
2687
|
+
var LOCK_PATH = join25(HOME, ".cache", "claude-nomad", "nomad.lock");
|
|
2650
2688
|
function acquireLock(verb) {
|
|
2651
2689
|
mkdirSync5(dirname3(LOCK_PATH), { recursive: true });
|
|
2652
2690
|
try {
|
|
@@ -2756,12 +2794,12 @@ function cmdDropSession(id) {
|
|
|
2756
2794
|
fail(`invalid session id: ${id}`);
|
|
2757
2795
|
process.exit(1);
|
|
2758
2796
|
}
|
|
2759
|
-
if (!
|
|
2797
|
+
if (!existsSync21(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
2760
2798
|
const handle = acquireLock("drop-session");
|
|
2761
2799
|
if (handle === null) process.exit(0);
|
|
2762
2800
|
try {
|
|
2763
|
-
const repoProjects =
|
|
2764
|
-
if (!
|
|
2801
|
+
const repoProjects = join26(REPO_HOME, "shared", "projects");
|
|
2802
|
+
if (!existsSync21(repoProjects)) {
|
|
2765
2803
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
2766
2804
|
}
|
|
2767
2805
|
const matches = collectMatches(repoProjects, id);
|
|
@@ -2783,12 +2821,12 @@ function cmdDropSession(id) {
|
|
|
2783
2821
|
function collectMatches(repoProjects, id) {
|
|
2784
2822
|
const matches = [];
|
|
2785
2823
|
for (const logical of readdirSync8(repoProjects)) {
|
|
2786
|
-
const candidate =
|
|
2787
|
-
if (
|
|
2824
|
+
const candidate = join26(repoProjects, logical, `${id}.jsonl`);
|
|
2825
|
+
if (existsSync21(candidate)) {
|
|
2788
2826
|
matches.push(relative4(REPO_HOME, candidate));
|
|
2789
2827
|
}
|
|
2790
|
-
const dir =
|
|
2791
|
-
if (
|
|
2828
|
+
const dir = join26(repoProjects, logical, id);
|
|
2829
|
+
if (existsSync21(dir) && statSync5(dir).isDirectory()) {
|
|
2792
2830
|
const dirRel = relative4(REPO_HOME, dir);
|
|
2793
2831
|
const staged = expandStagedDir(dirRel);
|
|
2794
2832
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -2824,19 +2862,19 @@ function unstageOne(rel) {
|
|
|
2824
2862
|
|
|
2825
2863
|
// src/commands.redact.ts
|
|
2826
2864
|
init_config();
|
|
2827
|
-
import { existsSync as
|
|
2828
|
-
import { dirname as dirname4, join as
|
|
2865
|
+
import { existsSync as existsSync23, statSync as statSync7 } from "node:fs";
|
|
2866
|
+
import { dirname as dirname4, join as join28 } from "node:path";
|
|
2829
2867
|
|
|
2830
2868
|
// src/commands.redact.subtree.ts
|
|
2831
|
-
import { existsSync as
|
|
2832
|
-
import { join as
|
|
2869
|
+
import { existsSync as existsSync22, lstatSync as lstatSync6, readFileSync as readFileSync9, readdirSync as readdirSync9, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2870
|
+
import { join as join27 } from "node:path";
|
|
2833
2871
|
init_utils_fs();
|
|
2834
2872
|
function collectFiles(dir, out) {
|
|
2835
|
-
if (!
|
|
2873
|
+
if (!existsSync22(dir)) return;
|
|
2836
2874
|
const st = lstatSync6(dir);
|
|
2837
2875
|
if (!st.isDirectory()) return;
|
|
2838
2876
|
for (const entry of readdirSync9(dir)) {
|
|
2839
|
-
const abs =
|
|
2877
|
+
const abs = join27(dir, entry);
|
|
2840
2878
|
const lst = lstatSync6(abs);
|
|
2841
2879
|
if (lst.isSymbolicLink()) continue;
|
|
2842
2880
|
if (lst.isDirectory()) {
|
|
@@ -2886,14 +2924,14 @@ init_utils_json();
|
|
|
2886
2924
|
init_utils();
|
|
2887
2925
|
function resolveLiveTranscript2(id) {
|
|
2888
2926
|
try {
|
|
2889
|
-
const mapPath =
|
|
2890
|
-
if (!
|
|
2927
|
+
const mapPath = join28(REPO_HOME, "path-map.json");
|
|
2928
|
+
if (!existsSync23(mapPath)) return null;
|
|
2891
2929
|
const projects = readJson(mapPath).projects;
|
|
2892
2930
|
for (const hostMap of Object.values(projects)) {
|
|
2893
2931
|
const abs = hostMap[HOST];
|
|
2894
2932
|
if (abs === void 0) continue;
|
|
2895
|
-
const live =
|
|
2896
|
-
if (
|
|
2933
|
+
const live = join28(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
|
|
2934
|
+
if (existsSync23(live)) return live;
|
|
2897
2935
|
}
|
|
2898
2936
|
return null;
|
|
2899
2937
|
} catch {
|
|
@@ -2911,17 +2949,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
2911
2949
|
fail(`invalid session id: ${id}`);
|
|
2912
2950
|
process.exit(1);
|
|
2913
2951
|
}
|
|
2914
|
-
if (!
|
|
2952
|
+
if (!existsSync23(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
2915
2953
|
const handle = acquireLock("redact");
|
|
2916
2954
|
if (handle === null) process.exit(0);
|
|
2917
2955
|
try {
|
|
2918
2956
|
const localPath = resolveLiveTranscript2(id);
|
|
2919
|
-
if (localPath === null || !
|
|
2957
|
+
if (localPath === null || !existsSync23(localPath)) {
|
|
2920
2958
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
2921
2959
|
process.exitCode = 1;
|
|
2922
2960
|
return;
|
|
2923
2961
|
}
|
|
2924
|
-
const sessionDir =
|
|
2962
|
+
const sessionDir = join28(dirname4(localPath), id);
|
|
2925
2963
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
2926
2964
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
2927
2965
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -2974,8 +3012,8 @@ ${lines}`);
|
|
|
2974
3012
|
}
|
|
2975
3013
|
|
|
2976
3014
|
// src/commands.pull.ts
|
|
2977
|
-
import { existsSync as
|
|
2978
|
-
import { join as
|
|
3015
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync8 } from "node:fs";
|
|
3016
|
+
import { join as join37 } from "node:path";
|
|
2979
3017
|
|
|
2980
3018
|
// src/commands.push.sections.ts
|
|
2981
3019
|
init_color();
|
|
@@ -3063,22 +3101,34 @@ init_config();
|
|
|
3063
3101
|
|
|
3064
3102
|
// src/extras-sync.ts
|
|
3065
3103
|
init_config();
|
|
3066
|
-
import { existsSync as
|
|
3067
|
-
import { join as
|
|
3104
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
3105
|
+
import { join as join31 } from "node:path";
|
|
3068
3106
|
|
|
3069
3107
|
// src/extras-sync.diff.ts
|
|
3070
3108
|
init_utils();
|
|
3071
3109
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
3110
|
+
function labelDiffLine(line) {
|
|
3111
|
+
const tab = line.indexOf(" ");
|
|
3112
|
+
if (tab === -1) return line;
|
|
3113
|
+
const status = line.slice(0, tab);
|
|
3114
|
+
const path = line.slice(tab + 1);
|
|
3115
|
+
if (status === "D") return `${path} (local only)`;
|
|
3116
|
+
if (status === "A") return `${path} (repo only)`;
|
|
3117
|
+
return path;
|
|
3118
|
+
}
|
|
3119
|
+
function parseDiffOutput(stdout) {
|
|
3120
|
+
return stdout.split("\n").filter((line) => line.length > 0).map(labelDiffLine);
|
|
3121
|
+
}
|
|
3072
3122
|
function listDivergingFiles(a, b) {
|
|
3073
3123
|
try {
|
|
3074
|
-
const stdout = execFileSync13("git", ["diff", "--no-index", "--name-
|
|
3124
|
+
const stdout = execFileSync13("git", ["diff", "--no-index", "--name-status", a, b], {
|
|
3075
3125
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3076
3126
|
}).toString();
|
|
3077
|
-
return stdout
|
|
3127
|
+
return parseDiffOutput(stdout);
|
|
3078
3128
|
} catch (err) {
|
|
3079
3129
|
const e = err;
|
|
3080
3130
|
if (e.status === 1 && e.stdout !== void 0) {
|
|
3081
|
-
return e.stdout.toString()
|
|
3131
|
+
return parseDiffOutput(e.stdout.toString());
|
|
3082
3132
|
}
|
|
3083
3133
|
if (e.code === "ENOENT") {
|
|
3084
3134
|
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
@@ -3091,8 +3141,8 @@ function listDivergingFiles(a, b) {
|
|
|
3091
3141
|
|
|
3092
3142
|
// src/extras-sync.core.ts
|
|
3093
3143
|
init_config();
|
|
3094
|
-
import { cpSync as cpSync4, existsSync as
|
|
3095
|
-
import { join as
|
|
3144
|
+
import { cpSync as cpSync4, existsSync as existsSync24, rmSync as rmSync7 } from "node:fs";
|
|
3145
|
+
import { join as join29 } from "node:path";
|
|
3096
3146
|
|
|
3097
3147
|
// src/extras-sync.guards.ts
|
|
3098
3148
|
init_utils();
|
|
@@ -3115,9 +3165,9 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
3115
3165
|
init_utils();
|
|
3116
3166
|
init_utils_json();
|
|
3117
3167
|
function loadValidatedExtras(opts) {
|
|
3118
|
-
const mapPath =
|
|
3119
|
-
const repoExtras =
|
|
3120
|
-
if (!
|
|
3168
|
+
const mapPath = join29(REPO_HOME, "path-map.json");
|
|
3169
|
+
const repoExtras = join29(REPO_HOME, "shared", "extras");
|
|
3170
|
+
if (!existsSync24(mapPath) || opts.requireRepoExtras === true && !existsSync24(repoExtras)) {
|
|
3121
3171
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
3122
3172
|
return null;
|
|
3123
3173
|
}
|
|
@@ -3159,8 +3209,8 @@ init_utils_json();
|
|
|
3159
3209
|
|
|
3160
3210
|
// src/extras-sync.remap.ts
|
|
3161
3211
|
init_config();
|
|
3162
|
-
import { existsSync as
|
|
3163
|
-
import { join as
|
|
3212
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3213
|
+
import { join as join30 } from "node:path";
|
|
3164
3214
|
init_utils_fs();
|
|
3165
3215
|
function runExtrasOp(v, dryRun, paths, backup) {
|
|
3166
3216
|
const counts = { unmapped: 0, skipped: 0 };
|
|
@@ -3168,7 +3218,7 @@ function runExtrasOp(v, dryRun, paths, backup) {
|
|
|
3168
3218
|
const would = [];
|
|
3169
3219
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
3170
3220
|
const { src, dst } = paths(t);
|
|
3171
|
-
if (!
|
|
3221
|
+
if (!existsSync25(src)) continue;
|
|
3172
3222
|
const item = `${t.logical}/${t.dirname}`;
|
|
3173
3223
|
if (dryRun) {
|
|
3174
3224
|
would.push(item);
|
|
@@ -3184,14 +3234,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
3184
3234
|
const dryRun = opts.dryRun === true;
|
|
3185
3235
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
3186
3236
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
3187
|
-
const repoExtras =
|
|
3237
|
+
const repoExtras = join30(REPO_HOME, "shared", "extras");
|
|
3188
3238
|
if (!dryRun) mkdirSync6(repoExtras, { recursive: true });
|
|
3189
3239
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
3190
3240
|
v,
|
|
3191
3241
|
dryRun,
|
|
3192
3242
|
({ localRoot, logical, dirname: dirname6 }) => ({
|
|
3193
|
-
src:
|
|
3194
|
-
dst:
|
|
3243
|
+
src: join30(localRoot, dirname6),
|
|
3244
|
+
dst: join30(repoExtras, logical, dirname6)
|
|
3195
3245
|
}),
|
|
3196
3246
|
(dst) => backupRepoWrite(dst, ts, REPO_HOME)
|
|
3197
3247
|
);
|
|
@@ -3204,13 +3254,13 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
3204
3254
|
missingMsg: "no path-map or repo extras dir; skipping extras remap"
|
|
3205
3255
|
});
|
|
3206
3256
|
if (v === null) return { unmapped: 0, skipped: 0, pulled: [], wouldPull: [] };
|
|
3207
|
-
const repoExtras =
|
|
3257
|
+
const repoExtras = join30(REPO_HOME, "shared", "extras");
|
|
3208
3258
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
3209
3259
|
v,
|
|
3210
3260
|
dryRun,
|
|
3211
3261
|
({ localRoot, logical, dirname: dirname6 }) => ({
|
|
3212
|
-
src:
|
|
3213
|
-
dst:
|
|
3262
|
+
src: join30(repoExtras, logical, dirname6),
|
|
3263
|
+
dst: join30(localRoot, dirname6)
|
|
3214
3264
|
}),
|
|
3215
3265
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
3216
3266
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -3224,14 +3274,14 @@ function divergenceCheckExtras(ts) {
|
|
|
3224
3274
|
const v = loadValidatedExtras({});
|
|
3225
3275
|
if (v === null) return;
|
|
3226
3276
|
const counts = { unmapped: 0, skipped: 0 };
|
|
3227
|
-
const backupRoot =
|
|
3277
|
+
const backupRoot = join31(BACKUP_BASE, ts, "extras");
|
|
3228
3278
|
for (const { logical, localRoot, dirname: dirname6 } of eachExtrasTarget(v, counts)) {
|
|
3229
|
-
const local =
|
|
3230
|
-
const repo =
|
|
3231
|
-
if (!
|
|
3279
|
+
const local = join31(localRoot, dirname6);
|
|
3280
|
+
const repo = join31(REPO_HOME, "shared", "extras", logical, dirname6);
|
|
3281
|
+
if (!existsSync26(local) || !existsSync26(repo)) continue;
|
|
3232
3282
|
const diff = listDivergingFiles(local, repo);
|
|
3233
3283
|
if (diff.length === 0) continue;
|
|
3234
|
-
const projectBackupRoot =
|
|
3284
|
+
const projectBackupRoot = join31(backupRoot, encodePath(localRoot));
|
|
3235
3285
|
warn(
|
|
3236
3286
|
`local ${dirname6} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will overwrite them (backups at ${projectBackupRoot}/)`
|
|
3237
3287
|
);
|
|
@@ -3244,8 +3294,8 @@ init_config();
|
|
|
3244
3294
|
init_utils();
|
|
3245
3295
|
init_utils_fs();
|
|
3246
3296
|
init_utils_json();
|
|
3247
|
-
import { existsSync as
|
|
3248
|
-
import { join as
|
|
3297
|
+
import { existsSync as existsSync27, lstatSync as lstatSync7, rmSync as rmSync8 } from "node:fs";
|
|
3298
|
+
import { join as join32 } from "node:path";
|
|
3249
3299
|
function emitAutoMove(onPreview, linkPath, ts, name) {
|
|
3250
3300
|
if (onPreview) {
|
|
3251
3301
|
onPreview({ kind: "auto-move", from: linkPath, to: `backup/${ts}/${name}` });
|
|
@@ -3264,11 +3314,11 @@ function applySharedLinks(ts, map, opts = {}) {
|
|
|
3264
3314
|
const dryRun = opts.dryRun === true;
|
|
3265
3315
|
const linkNames = allSharedLinks(map);
|
|
3266
3316
|
for (const name of linkNames) {
|
|
3267
|
-
const linkPath =
|
|
3268
|
-
const target =
|
|
3269
|
-
if (!
|
|
3317
|
+
const linkPath = join32(CLAUDE_HOME, name);
|
|
3318
|
+
const target = join32(REPO_HOME, "shared", name);
|
|
3319
|
+
if (!existsSync27(linkPath)) continue;
|
|
3270
3320
|
if (lstatSync7(linkPath).isSymbolicLink()) continue;
|
|
3271
|
-
if (!
|
|
3321
|
+
if (!existsSync27(target)) continue;
|
|
3272
3322
|
if (dryRun) {
|
|
3273
3323
|
emitAutoMove(opts.onPreview, linkPath, ts, name);
|
|
3274
3324
|
continue;
|
|
@@ -3277,28 +3327,28 @@ function applySharedLinks(ts, map, opts = {}) {
|
|
|
3277
3327
|
rmSync8(linkPath, { recursive: true, force: true });
|
|
3278
3328
|
}
|
|
3279
3329
|
for (const name of linkNames) {
|
|
3280
|
-
const target =
|
|
3281
|
-
if (!
|
|
3330
|
+
const target = join32(REPO_HOME, "shared", name);
|
|
3331
|
+
if (!existsSync27(target)) continue;
|
|
3282
3332
|
if (dryRun) {
|
|
3283
|
-
emitCreate(opts.onPreview,
|
|
3333
|
+
emitCreate(opts.onPreview, join32(CLAUDE_HOME, name), target);
|
|
3284
3334
|
continue;
|
|
3285
3335
|
}
|
|
3286
|
-
ensureSymlink(
|
|
3336
|
+
ensureSymlink(join32(CLAUDE_HOME, name), target);
|
|
3287
3337
|
}
|
|
3288
3338
|
}
|
|
3289
3339
|
function regenerateSettings(ts, opts = {}) {
|
|
3290
3340
|
const dryRun = opts.dryRun === true;
|
|
3291
|
-
const basePath =
|
|
3292
|
-
const hostPath =
|
|
3293
|
-
if (!
|
|
3341
|
+
const basePath = join32(REPO_HOME, "shared", "settings.base.json");
|
|
3342
|
+
const hostPath = join32(REPO_HOME, "hosts", `${HOST}.json`);
|
|
3343
|
+
if (!existsSync27(basePath)) {
|
|
3294
3344
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
3295
3345
|
}
|
|
3296
3346
|
const base = readJson(basePath);
|
|
3297
|
-
const hasOverrides =
|
|
3347
|
+
const hasOverrides = existsSync27(hostPath);
|
|
3298
3348
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
3299
3349
|
const merged = deepMerge(base, overrides);
|
|
3300
|
-
const settingsPath =
|
|
3301
|
-
if (!hasOverrides &&
|
|
3350
|
+
const settingsPath = join32(CLAUDE_HOME, "settings.json");
|
|
3351
|
+
if (!hasOverrides && existsSync27(settingsPath)) {
|
|
3302
3352
|
try {
|
|
3303
3353
|
const existing = readJson(settingsPath);
|
|
3304
3354
|
const baseKeys = new Set(Object.keys(base));
|
|
@@ -3324,8 +3374,8 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
3324
3374
|
|
|
3325
3375
|
// src/preview.ts
|
|
3326
3376
|
init_config();
|
|
3327
|
-
import { existsSync as
|
|
3328
|
-
import { join as
|
|
3377
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
3378
|
+
import { join as join33 } from "node:path";
|
|
3329
3379
|
|
|
3330
3380
|
// node_modules/diff/libesm/diff/base.js
|
|
3331
3381
|
var Diff = class {
|
|
@@ -3600,6 +3650,7 @@ function diffLinesToUnified(oldStr, newStr) {
|
|
|
3600
3650
|
|
|
3601
3651
|
// src/preview.ts
|
|
3602
3652
|
init_utils_json();
|
|
3653
|
+
var CANONICAL_ORDER_NOTE = "settings.json will be rewritten in canonical key order; no value changes";
|
|
3603
3654
|
function diffJsonStrings(currentJsonText, newJsonText) {
|
|
3604
3655
|
if (currentJsonText === newJsonText) return "";
|
|
3605
3656
|
const lines = [
|
|
@@ -3610,7 +3661,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
3610
3661
|
return lines.join("\n");
|
|
3611
3662
|
}
|
|
3612
3663
|
function readJsonOrNull(path) {
|
|
3613
|
-
if (!
|
|
3664
|
+
if (!existsSync28(path)) return null;
|
|
3614
3665
|
try {
|
|
3615
3666
|
return readJson(path);
|
|
3616
3667
|
} catch {
|
|
@@ -3624,18 +3675,20 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
3624
3675
|
}
|
|
3625
3676
|
const notes = [];
|
|
3626
3677
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
3627
|
-
if (hostOverrides === null &&
|
|
3678
|
+
if (hostOverrides === null && existsSync28(hostPath)) {
|
|
3628
3679
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
3629
3680
|
}
|
|
3630
3681
|
const merged = deepMerge(base, hostOverrides ?? {});
|
|
3631
3682
|
const current = readJsonOrNull(settingsPath);
|
|
3632
|
-
if (current === null &&
|
|
3683
|
+
if (current === null && existsSync28(settingsPath)) {
|
|
3633
3684
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
3634
3685
|
}
|
|
3686
|
+
const rawEqual = JSON.stringify(current ?? {}, null, 2) === JSON.stringify(merged, null, 2);
|
|
3635
3687
|
const diff = diffJsonStrings(
|
|
3636
|
-
JSON.stringify(current ?? {}, null, 2),
|
|
3637
|
-
JSON.stringify(merged, null, 2)
|
|
3688
|
+
JSON.stringify(sortKeysDeep(current ?? {}), null, 2),
|
|
3689
|
+
JSON.stringify(sortKeysDeep(merged), null, 2)
|
|
3638
3690
|
);
|
|
3691
|
+
if (diff === "" && !rawEqual) notes.push(CANONICAL_ORDER_NOTE);
|
|
3639
3692
|
return { diff, notes };
|
|
3640
3693
|
}
|
|
3641
3694
|
function formatLinkRow(e) {
|
|
@@ -3665,9 +3718,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
3665
3718
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
3666
3719
|
});
|
|
3667
3720
|
const settingsResult = previewSettings(
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3721
|
+
join33(REPO_HOME, "shared", "settings.base.json"),
|
|
3722
|
+
join33(REPO_HOME, "hosts", `${HOST}.json`),
|
|
3723
|
+
join33(CLAUDE_HOME, "settings.json")
|
|
3671
3724
|
);
|
|
3672
3725
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
3673
3726
|
const sessions = section("Sessions");
|
|
@@ -3683,21 +3736,21 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
3683
3736
|
|
|
3684
3737
|
// src/spinner.ts
|
|
3685
3738
|
init_color();
|
|
3686
|
-
import { existsSync as
|
|
3739
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
3687
3740
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3688
3741
|
import { Worker } from "node:worker_threads";
|
|
3689
3742
|
|
|
3690
3743
|
// src/commands.push.recovery.ts
|
|
3691
3744
|
init_config();
|
|
3692
3745
|
import { readFileSync as readFileSync10, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3693
|
-
import { join as
|
|
3746
|
+
import { join as join36 } from "node:path";
|
|
3694
3747
|
import { createInterface } from "node:readline/promises";
|
|
3695
3748
|
|
|
3696
3749
|
// src/commands.push.recovery.redact.ts
|
|
3697
3750
|
init_config();
|
|
3698
3751
|
init_config_sharedDirs_guard();
|
|
3699
|
-
import { cpSync as cpSync5, existsSync as
|
|
3700
|
-
import { dirname as dirname5, join as
|
|
3752
|
+
import { cpSync as cpSync5, existsSync as existsSync29, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
|
|
3753
|
+
import { dirname as dirname5, join as join34, sep as sep2 } from "node:path";
|
|
3701
3754
|
init_push_gitleaks_scan();
|
|
3702
3755
|
init_utils_json();
|
|
3703
3756
|
init_utils();
|
|
@@ -3728,8 +3781,8 @@ function resolveStagedDir(localPath, map) {
|
|
|
3728
3781
|
assertSafeLogical(logical);
|
|
3729
3782
|
const abs = hostMap[HOST];
|
|
3730
3783
|
if (abs === void 0) continue;
|
|
3731
|
-
if (localPath.startsWith(
|
|
3732
|
-
return
|
|
3784
|
+
if (localPath.startsWith(join34(CLAUDE_HOME, "projects", encodePath(abs)) + sep2)) {
|
|
3785
|
+
return join34(REPO_HOME, "shared", "projects", logical);
|
|
3733
3786
|
}
|
|
3734
3787
|
}
|
|
3735
3788
|
return null;
|
|
@@ -3751,7 +3804,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3751
3804
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
3752
3805
|
);
|
|
3753
3806
|
}
|
|
3754
|
-
const sessionDir =
|
|
3807
|
+
const sessionDir = join34(dirname5(localPath), sid);
|
|
3755
3808
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3756
3809
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
3757
3810
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3785,9 +3838,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3785
3838
|
);
|
|
3786
3839
|
}
|
|
3787
3840
|
mkdirSync7(stagedProjectDir, { recursive: true });
|
|
3788
|
-
cpSync5(localPath,
|
|
3789
|
-
if (
|
|
3790
|
-
cpSync5(sessionDir,
|
|
3841
|
+
cpSync5(localPath, join34(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
3842
|
+
if (existsSync29(sessionDir)) {
|
|
3843
|
+
cpSync5(sessionDir, join34(stagedProjectDir, sid), { force: true, recursive: true });
|
|
3791
3844
|
}
|
|
3792
3845
|
return true;
|
|
3793
3846
|
}
|
|
@@ -3795,13 +3848,13 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3795
3848
|
// src/commands.push.recovery.drop.ts
|
|
3796
3849
|
init_config();
|
|
3797
3850
|
import { rmSync as rmSync9 } from "node:fs";
|
|
3798
|
-
import { join as
|
|
3851
|
+
import { join as join35 } from "node:path";
|
|
3799
3852
|
function dropSessionFromStaged(sid, map) {
|
|
3800
3853
|
const logicals = Object.keys(map.projects);
|
|
3801
3854
|
if (logicals.length === 0) return false;
|
|
3802
3855
|
for (const logical of logicals) {
|
|
3803
|
-
const jsonl =
|
|
3804
|
-
const dir =
|
|
3856
|
+
const jsonl = join35(REPO_HOME, "shared", "projects", logical, `${sid}.jsonl`);
|
|
3857
|
+
const dir = join35(REPO_HOME, "shared", "projects", logical, sid);
|
|
3805
3858
|
rmSync9(jsonl, { force: true });
|
|
3806
3859
|
rmSync9(dir, { recursive: true, force: true });
|
|
3807
3860
|
}
|
|
@@ -3919,7 +3972,7 @@ function applyThenRescan(scanVerdict, repoHome) {
|
|
|
3919
3972
|
return next;
|
|
3920
3973
|
}
|
|
3921
3974
|
function allowThenRescan(append, scanVerdict, repoHome) {
|
|
3922
|
-
const ignPath =
|
|
3975
|
+
const ignPath = join36(repoHome, ".gitleaksignore");
|
|
3923
3976
|
let before;
|
|
3924
3977
|
try {
|
|
3925
3978
|
before = readFileSync10(ignPath, "utf8");
|
|
@@ -4017,7 +4070,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
4017
4070
|
`);
|
|
4018
4071
|
}
|
|
4019
4072
|
function resolveWorkerPath(deps = {}) {
|
|
4020
|
-
const check = deps.existsSyncFn ??
|
|
4073
|
+
const check = deps.existsSyncFn ?? existsSync30;
|
|
4021
4074
|
const base = deps.baseUrl ?? import.meta.url;
|
|
4022
4075
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
4023
4076
|
if (check(mjs)) return mjs;
|
|
@@ -4081,6 +4134,112 @@ function withSpinner(label, fn, deps) {
|
|
|
4081
4134
|
}
|
|
4082
4135
|
}
|
|
4083
4136
|
|
|
4137
|
+
// src/commands.pull.recovery.ts
|
|
4138
|
+
init_config();
|
|
4139
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
4140
|
+
init_utils();
|
|
4141
|
+
init_utils_fs();
|
|
4142
|
+
function gitCapture(args, cwd) {
|
|
4143
|
+
return execFileSync14("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
|
|
4144
|
+
}
|
|
4145
|
+
function isSyncedConfig(path) {
|
|
4146
|
+
return PUSH_ALLOWED_STATIC.some(
|
|
4147
|
+
(entry) => entry.endsWith("/") ? path.startsWith(entry) : path === entry
|
|
4148
|
+
);
|
|
4149
|
+
}
|
|
4150
|
+
function classifyTouched(touched) {
|
|
4151
|
+
const synced = [];
|
|
4152
|
+
const toolSource = [];
|
|
4153
|
+
for (const p of touched) {
|
|
4154
|
+
if (isSyncedConfig(p)) {
|
|
4155
|
+
synced.push(p);
|
|
4156
|
+
} else {
|
|
4157
|
+
toolSource.push(p);
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
return { synced, toolSource };
|
|
4161
|
+
}
|
|
4162
|
+
function parsePorcelainZ(raw) {
|
|
4163
|
+
const tracked = [];
|
|
4164
|
+
const untracked = [];
|
|
4165
|
+
if (!raw) return { tracked, untracked };
|
|
4166
|
+
const records = raw.split("\0");
|
|
4167
|
+
for (let i = 0; i < records.length; i++) {
|
|
4168
|
+
const record = records[i];
|
|
4169
|
+
if (record.length < 3) continue;
|
|
4170
|
+
const xy = record.slice(0, 2);
|
|
4171
|
+
const filePath = record.slice(3);
|
|
4172
|
+
if (xy === "??") {
|
|
4173
|
+
untracked.push(filePath);
|
|
4174
|
+
continue;
|
|
4175
|
+
}
|
|
4176
|
+
tracked.push(filePath);
|
|
4177
|
+
if (xy.startsWith("R") || xy.startsWith("C")) {
|
|
4178
|
+
const src = records[i + 1];
|
|
4179
|
+
if (src) {
|
|
4180
|
+
tracked.push(src);
|
|
4181
|
+
i++;
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
return { tracked, untracked };
|
|
4186
|
+
}
|
|
4187
|
+
function parseDirtyPaths(repo) {
|
|
4188
|
+
return parsePorcelainZ(gitStatusPorcelainZ(repo));
|
|
4189
|
+
}
|
|
4190
|
+
function buildRecoverySummary(branchName, strandedLog, untracked) {
|
|
4191
|
+
const strandedLines = strandedLog.split("\n").filter(Boolean).map((l) => ` ${l}`).join("\n");
|
|
4192
|
+
const parts = [`parked stranded commits on ${branchName}`];
|
|
4193
|
+
if (strandedLines) parts.push(`stranded:
|
|
4194
|
+
${strandedLines}`);
|
|
4195
|
+
if (untracked.length > 0) parts.push(`untracked files preserved: ${untracked.join(", ")}`);
|
|
4196
|
+
parts.push("continuing with normal pull");
|
|
4197
|
+
return parts.join("; ");
|
|
4198
|
+
}
|
|
4199
|
+
function freshStrandedBranch(repo) {
|
|
4200
|
+
const base = `nomad/stranded-${nowTimestamp()}`;
|
|
4201
|
+
const exists = (name) => {
|
|
4202
|
+
try {
|
|
4203
|
+
gitCapture(["rev-parse", "--verify", "--quiet", `refs/heads/${name}`], repo);
|
|
4204
|
+
return true;
|
|
4205
|
+
} catch {
|
|
4206
|
+
return false;
|
|
4207
|
+
}
|
|
4208
|
+
};
|
|
4209
|
+
if (!exists(base)) return base;
|
|
4210
|
+
let n = 1;
|
|
4211
|
+
while (exists(`${base}-${n}`)) n++;
|
|
4212
|
+
return `${base}-${n}`;
|
|
4213
|
+
}
|
|
4214
|
+
function recoverForceRemote(mode, repo) {
|
|
4215
|
+
if (mode === "merge") {
|
|
4216
|
+
gitOrFatal(["merge", "--abort"], "git merge --abort", repo);
|
|
4217
|
+
} else {
|
|
4218
|
+
gitOrFatal(["rebase", "--abort"], "git rebase --abort", repo);
|
|
4219
|
+
}
|
|
4220
|
+
gitOrFatal(["fetch", "origin", "main"], "git fetch origin main", repo);
|
|
4221
|
+
try {
|
|
4222
|
+
gitCapture(["rev-parse", "--verify", "origin/main"], repo);
|
|
4223
|
+
} catch {
|
|
4224
|
+
die("origin/main not found after fetch; check your remote configuration");
|
|
4225
|
+
}
|
|
4226
|
+
const committedRaw = gitCapture(["diff", "--name-only", "-z", "origin/main", "HEAD"], repo);
|
|
4227
|
+
const committedTouched = committedRaw.split("\0").filter(Boolean);
|
|
4228
|
+
const { tracked: dirtyTracked, untracked } = parseDirtyPaths(repo);
|
|
4229
|
+
const allTouched = [...committedTouched, ...dirtyTracked];
|
|
4230
|
+
const { synced } = classifyTouched(allTouched);
|
|
4231
|
+
if (synced.length > 0) {
|
|
4232
|
+
die(
|
|
4233
|
+
"force-remote refused: stranded or dirty tracked changes touch synced config.\nAt-risk paths:\n" + synced.map((p) => ` ${p}`).join("\n") + "\nCopy or cherry-pick those changes out before retrying."
|
|
4234
|
+
);
|
|
4235
|
+
}
|
|
4236
|
+
const branchName = freshStrandedBranch(repo);
|
|
4237
|
+
gitOrFatal(["branch", branchName, "HEAD"], "park stranded commits", repo);
|
|
4238
|
+
gitOrFatal(["reset", "--hard", "origin/main"], "reset to origin/main", repo);
|
|
4239
|
+
const strandedLog = gitCapture(["log", "--oneline", `origin/main..${branchName}`], repo);
|
|
4240
|
+
log(buildRecoverySummary(branchName, strandedLog, untracked));
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4084
4243
|
// src/commands.pull.ts
|
|
4085
4244
|
init_utils();
|
|
4086
4245
|
init_utils_fs();
|
|
@@ -4102,18 +4261,32 @@ function applyWetPull(ts, map) {
|
|
|
4102
4261
|
summary
|
|
4103
4262
|
]);
|
|
4104
4263
|
}
|
|
4264
|
+
function handleWedge(repo, forceRemote) {
|
|
4265
|
+
const wedge = detectWedge(repo);
|
|
4266
|
+
if (wedge === null) return;
|
|
4267
|
+
if (forceRemote) {
|
|
4268
|
+
recoverForceRemote(wedge, repo);
|
|
4269
|
+
return;
|
|
4270
|
+
}
|
|
4271
|
+
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
4272
|
+
die(
|
|
4273
|
+
`repo is ${state} from a previous failed pull; run 'nomad pull --force-remote' to auto-recover, or resolve manually (see FAQ: "Every pull fails with unmerged files")`
|
|
4274
|
+
);
|
|
4275
|
+
}
|
|
4105
4276
|
function cmdPull(opts = {}) {
|
|
4106
4277
|
const dryRun = opts.dryRun === true;
|
|
4107
|
-
|
|
4108
|
-
if (!
|
|
4278
|
+
const forceRemote = opts.forceRemote === true;
|
|
4279
|
+
if (!existsSync31(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
4280
|
+
if (!existsSync31(join37(REPO_HOME, "shared", "settings.base.json"))) {
|
|
4109
4281
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
4110
4282
|
}
|
|
4111
4283
|
const handle = acquireLock("pull");
|
|
4112
4284
|
if (handle === null) process.exit(0);
|
|
4113
4285
|
try {
|
|
4114
4286
|
const ts = freshBackupTs(BACKUP_BASE);
|
|
4287
|
+
handleWedge(REPO_HOME, forceRemote);
|
|
4115
4288
|
if (!dryRun) {
|
|
4116
|
-
const backupRoot =
|
|
4289
|
+
const backupRoot = join37(BACKUP_BASE, ts);
|
|
4117
4290
|
try {
|
|
4118
4291
|
mkdirSync8(backupRoot, { recursive: true });
|
|
4119
4292
|
} catch (err) {
|
|
@@ -4124,8 +4297,8 @@ function cmdPull(opts = {}) {
|
|
|
4124
4297
|
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
4125
4298
|
);
|
|
4126
4299
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", REPO_HOME);
|
|
4127
|
-
const mapPath =
|
|
4128
|
-
const map =
|
|
4300
|
+
const mapPath = join37(REPO_HOME, "path-map.json");
|
|
4301
|
+
const map = existsSync31(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
4129
4302
|
divergenceCheckExtras(ts);
|
|
4130
4303
|
if (dryRun) {
|
|
4131
4304
|
computePreview(ts, map, "pull");
|
|
@@ -4147,8 +4320,8 @@ function cmdPull(opts = {}) {
|
|
|
4147
4320
|
|
|
4148
4321
|
// src/commands.push.ts
|
|
4149
4322
|
init_config();
|
|
4150
|
-
import { existsSync as
|
|
4151
|
-
import { join as
|
|
4323
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
4324
|
+
import { join as join39, relative as relative5 } from "node:path";
|
|
4152
4325
|
|
|
4153
4326
|
// src/commands.push.allowlist.ts
|
|
4154
4327
|
init_config();
|
|
@@ -4172,7 +4345,7 @@ function isNeverSync(path) {
|
|
|
4172
4345
|
}
|
|
4173
4346
|
return false;
|
|
4174
4347
|
}
|
|
4175
|
-
function
|
|
4348
|
+
function parsePorcelainZ2(statusPorcelain) {
|
|
4176
4349
|
const records = statusPorcelain.split("\0");
|
|
4177
4350
|
const paths = [];
|
|
4178
4351
|
for (let i = 0; i < records.length; i++) {
|
|
@@ -4202,7 +4375,7 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
4202
4375
|
];
|
|
4203
4376
|
const neverSyncHits = [];
|
|
4204
4377
|
const violations = [];
|
|
4205
|
-
for (const path of
|
|
4378
|
+
for (const path of parsePorcelainZ2(statusPorcelain)) {
|
|
4206
4379
|
if (isNeverSync(path)) {
|
|
4207
4380
|
neverSyncHits.push(path);
|
|
4208
4381
|
} else if (!isAllowed(path, allowed)) {
|
|
@@ -4228,9 +4401,9 @@ init_color();
|
|
|
4228
4401
|
init_config();
|
|
4229
4402
|
init_config_sharedDirs_guard();
|
|
4230
4403
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
4231
|
-
import { copyFileSync, existsSync as
|
|
4404
|
+
import { copyFileSync, existsSync as existsSync32, mkdirSync as mkdirSync9, readdirSync as readdirSync10, rmSync as rmSync11 } from "node:fs";
|
|
4232
4405
|
import { homedir as homedir5 } from "node:os";
|
|
4233
|
-
import { join as
|
|
4406
|
+
import { join as join38 } from "node:path";
|
|
4234
4407
|
init_push_leak_verdict();
|
|
4235
4408
|
init_push_gitleaks();
|
|
4236
4409
|
init_utils_fs();
|
|
@@ -4245,13 +4418,13 @@ function stageSessions(tmpRoot, map) {
|
|
|
4245
4418
|
if (!p || p === "TBD") continue;
|
|
4246
4419
|
reverse.set(encodePath(p), logical);
|
|
4247
4420
|
}
|
|
4248
|
-
const localProjects =
|
|
4249
|
-
if (!
|
|
4421
|
+
const localProjects = join38(CLAUDE_HOME, "projects");
|
|
4422
|
+
if (!existsSync32(localProjects)) return 0;
|
|
4250
4423
|
let staged = 0;
|
|
4251
4424
|
for (const dir of readdirSync10(localProjects)) {
|
|
4252
4425
|
const logical = reverse.get(dir);
|
|
4253
4426
|
if (!logical) continue;
|
|
4254
|
-
copyDirJsonlOnly(
|
|
4427
|
+
copyDirJsonlOnly(join38(localProjects, dir), join38(tmpRoot, "shared", "projects", logical));
|
|
4255
4428
|
staged++;
|
|
4256
4429
|
}
|
|
4257
4430
|
return staged;
|
|
@@ -4267,9 +4440,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
4267
4440
|
if (!localRoot || localRoot === "TBD") continue;
|
|
4268
4441
|
for (const dirname6 of dirnames) {
|
|
4269
4442
|
if (!whitelist.includes(dirname6)) continue;
|
|
4270
|
-
const src =
|
|
4271
|
-
if (!
|
|
4272
|
-
const dst =
|
|
4443
|
+
const src = join38(localRoot, dirname6);
|
|
4444
|
+
if (!existsSync32(src)) continue;
|
|
4445
|
+
const dst = join38(tmpRoot, "shared", "extras", logical, dirname6);
|
|
4273
4446
|
copyExtras(src, dst);
|
|
4274
4447
|
staged++;
|
|
4275
4448
|
}
|
|
@@ -4277,19 +4450,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
4277
4450
|
return staged;
|
|
4278
4451
|
}
|
|
4279
4452
|
function previewPushLeaks(map) {
|
|
4280
|
-
const cacheDir =
|
|
4453
|
+
const cacheDir = join38(homedir5(), ".cache", "claude-nomad");
|
|
4281
4454
|
mkdirSync9(cacheDir, { recursive: true });
|
|
4282
4455
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
4283
|
-
const tmpRoot =
|
|
4456
|
+
const tmpRoot = join38(cacheDir, `push-preview-tree-${stamp}`);
|
|
4284
4457
|
try {
|
|
4285
4458
|
const sessionCount = stageSessions(tmpRoot, map);
|
|
4286
4459
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
4287
4460
|
if (sessionCount + extrasCount === 0) {
|
|
4288
4461
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
4289
4462
|
}
|
|
4290
|
-
const ignoreFile =
|
|
4291
|
-
if (
|
|
4292
|
-
copyFileSync(ignoreFile,
|
|
4463
|
+
const ignoreFile = join38(REPO_HOME, ".gitleaksignore");
|
|
4464
|
+
if (existsSync32(ignoreFile)) {
|
|
4465
|
+
copyFileSync(ignoreFile, join38(tmpRoot, ".gitleaksignore"));
|
|
4293
4466
|
}
|
|
4294
4467
|
let findings;
|
|
4295
4468
|
try {
|
|
@@ -4311,7 +4484,7 @@ init_utils();
|
|
|
4311
4484
|
init_utils_fs();
|
|
4312
4485
|
init_utils_json();
|
|
4313
4486
|
function guardGitlinks() {
|
|
4314
|
-
const gitlinks = findGitlinks(
|
|
4487
|
+
const gitlinks = findGitlinks(join39(REPO_HOME, "shared"));
|
|
4315
4488
|
if (gitlinks.length === 0) return;
|
|
4316
4489
|
for (const p of gitlinks) {
|
|
4317
4490
|
const rel = relative5(REPO_HOME, p);
|
|
@@ -4363,7 +4536,7 @@ async function cmdPush(opts = {}) {
|
|
|
4363
4536
|
const allowAll = opts.allowAll === true;
|
|
4364
4537
|
const allowRule = opts.allowRule;
|
|
4365
4538
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
4366
|
-
if (!
|
|
4539
|
+
if (!existsSync33(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
4367
4540
|
const handle = acquireLock("push");
|
|
4368
4541
|
if (handle === null) process.exit(0);
|
|
4369
4542
|
try {
|
|
@@ -4381,8 +4554,8 @@ async function cmdPush(opts = {}) {
|
|
|
4381
4554
|
renderNoScanTree(st);
|
|
4382
4555
|
return;
|
|
4383
4556
|
}
|
|
4384
|
-
const mapPath =
|
|
4385
|
-
if (!
|
|
4557
|
+
const mapPath = join39(REPO_HOME, "path-map.json");
|
|
4558
|
+
if (!existsSync33(mapPath)) {
|
|
4386
4559
|
if (dryRun) return runDryRunPreview(st, null);
|
|
4387
4560
|
die("path-map.json missing, cannot enforce push allow-list");
|
|
4388
4561
|
}
|
|
@@ -4403,9 +4576,9 @@ async function cmdPush(opts = {}) {
|
|
|
4403
4576
|
}
|
|
4404
4577
|
|
|
4405
4578
|
// src/commands.update.ts
|
|
4406
|
-
import { execFileSync as
|
|
4579
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4407
4580
|
init_utils();
|
|
4408
|
-
function cmdUpdate(run =
|
|
4581
|
+
function cmdUpdate(run = execFileSync15) {
|
|
4409
4582
|
try {
|
|
4410
4583
|
run("npm", ["update", "-g", "claude-nomad"], { stdio: "inherit" });
|
|
4411
4584
|
} catch (err) {
|
|
@@ -4422,17 +4595,17 @@ init_config();
|
|
|
4422
4595
|
|
|
4423
4596
|
// src/diff.ts
|
|
4424
4597
|
init_config();
|
|
4425
|
-
import { existsSync as
|
|
4426
|
-
import { join as
|
|
4598
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
4599
|
+
import { join as join40 } from "node:path";
|
|
4427
4600
|
init_utils();
|
|
4428
4601
|
init_utils_fs();
|
|
4429
4602
|
init_utils_json();
|
|
4430
4603
|
function cmdDiff() {
|
|
4431
4604
|
try {
|
|
4432
|
-
if (!
|
|
4605
|
+
if (!existsSync34(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
4433
4606
|
const ts = freshBackupTs(BACKUP_BASE);
|
|
4434
|
-
const mapPath =
|
|
4435
|
-
const map =
|
|
4607
|
+
const mapPath = join40(REPO_HOME, "path-map.json");
|
|
4608
|
+
const map = existsSync34(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
4436
4609
|
computePreview(ts, map, "diff");
|
|
4437
4610
|
} catch (err) {
|
|
4438
4611
|
if (err instanceof NomadFatal) {
|
|
@@ -4446,19 +4619,19 @@ function cmdDiff() {
|
|
|
4446
4619
|
|
|
4447
4620
|
// src/init.ts
|
|
4448
4621
|
init_config();
|
|
4449
|
-
import { existsSync as
|
|
4450
|
-
import { join as
|
|
4622
|
+
import { existsSync as existsSync36, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4623
|
+
import { join as join42 } from "node:path";
|
|
4451
4624
|
|
|
4452
4625
|
// src/init.gh-onboard.ts
|
|
4453
4626
|
init_config();
|
|
4454
|
-
import { execFileSync as
|
|
4627
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
4455
4628
|
init_utils();
|
|
4456
4629
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
4457
4630
|
function isValidRepoName(name) {
|
|
4458
4631
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
4459
4632
|
}
|
|
4460
4633
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
4461
|
-
function ensureOriginRepo(repoName, run =
|
|
4634
|
+
function ensureOriginRepo(repoName, run = execFileSync16) {
|
|
4462
4635
|
if (!isValidRepoName(repoName)) {
|
|
4463
4636
|
die(
|
|
4464
4637
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -4528,31 +4701,31 @@ init_config();
|
|
|
4528
4701
|
init_utils();
|
|
4529
4702
|
init_utils_fs();
|
|
4530
4703
|
init_utils_json();
|
|
4531
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync6, existsSync as
|
|
4532
|
-
import { join as
|
|
4704
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync6, existsSync as existsSync35, rmSync as rmSync12, statSync as statSync9 } from "node:fs";
|
|
4705
|
+
import { join as join41 } from "node:path";
|
|
4533
4706
|
function snapshotIntoShared(map) {
|
|
4534
4707
|
for (const name of allSharedLinks(map)) {
|
|
4535
|
-
const src =
|
|
4536
|
-
if (!
|
|
4537
|
-
const dst =
|
|
4708
|
+
const src = join41(CLAUDE_HOME, name);
|
|
4709
|
+
if (!existsSync35(src)) continue;
|
|
4710
|
+
const dst = join41(REPO_HOME, "shared", name);
|
|
4538
4711
|
if (statSync9(src).isDirectory()) {
|
|
4539
|
-
const gk =
|
|
4540
|
-
if (
|
|
4712
|
+
const gk = join41(dst, ".gitkeep");
|
|
4713
|
+
if (existsSync35(gk)) rmSync12(gk);
|
|
4541
4714
|
cpSync6(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
4542
4715
|
} else {
|
|
4543
4716
|
copyFileSync2(src, dst);
|
|
4544
4717
|
}
|
|
4545
4718
|
log(`snapshotted shared/${name} from ${src}`);
|
|
4546
4719
|
}
|
|
4547
|
-
const userSettings =
|
|
4548
|
-
if (
|
|
4720
|
+
const userSettings = join41(CLAUDE_HOME, "settings.json");
|
|
4721
|
+
if (existsSync35(userSettings)) {
|
|
4549
4722
|
let parsed;
|
|
4550
4723
|
try {
|
|
4551
4724
|
parsed = readJson(userSettings);
|
|
4552
4725
|
} catch (err) {
|
|
4553
4726
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
4554
4727
|
}
|
|
4555
|
-
const hostFile =
|
|
4728
|
+
const hostFile = join41(REPO_HOME, "hosts", `${HOST}.json`);
|
|
4556
4729
|
writeJsonAtomic(hostFile, parsed);
|
|
4557
4730
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
4558
4731
|
}
|
|
@@ -4565,14 +4738,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
4565
4738
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
4566
4739
|
function preflightConflict(repoHome) {
|
|
4567
4740
|
const candidates = [
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4741
|
+
join42(repoHome, "shared", "settings.base.json"),
|
|
4742
|
+
join42(repoHome, "shared", "CLAUDE.md"),
|
|
4743
|
+
join42(repoHome, "path-map.json"),
|
|
4744
|
+
join42(repoHome, "hosts"),
|
|
4745
|
+
join42(repoHome, "shared")
|
|
4573
4746
|
];
|
|
4574
4747
|
for (const c of candidates) {
|
|
4575
|
-
if (
|
|
4748
|
+
if (existsSync36(c)) return c;
|
|
4576
4749
|
}
|
|
4577
4750
|
return null;
|
|
4578
4751
|
}
|
|
@@ -4585,25 +4758,25 @@ function cmdInit(opts = {}) {
|
|
|
4585
4758
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
4586
4759
|
}
|
|
4587
4760
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
4588
|
-
mkdirSync10(
|
|
4589
|
-
mkdirSync10(
|
|
4761
|
+
mkdirSync10(join42(REPO_HOME, "shared"), { recursive: true });
|
|
4762
|
+
mkdirSync10(join42(REPO_HOME, "hosts"), { recursive: true });
|
|
4590
4763
|
for (const name of SHARED_KEEP_DIRS) {
|
|
4591
|
-
mkdirSync10(
|
|
4764
|
+
mkdirSync10(join42(REPO_HOME, "shared", name), { recursive: true });
|
|
4592
4765
|
}
|
|
4593
|
-
const userClaudeMd =
|
|
4594
|
-
if (!snapshot || !
|
|
4595
|
-
writeFileSync6(
|
|
4766
|
+
const userClaudeMd = join42(CLAUDE_HOME, "CLAUDE.md");
|
|
4767
|
+
if (!snapshot || !existsSync36(userClaudeMd)) {
|
|
4768
|
+
writeFileSync6(join42(REPO_HOME, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
4596
4769
|
log("created shared/CLAUDE.md");
|
|
4597
4770
|
}
|
|
4598
4771
|
for (const name of SHARED_KEEP_DIRS) {
|
|
4599
|
-
writeFileSync6(
|
|
4772
|
+
writeFileSync6(join42(REPO_HOME, "shared", name, ".gitkeep"), "");
|
|
4600
4773
|
log(`created shared/${name}/.gitkeep`);
|
|
4601
4774
|
}
|
|
4602
|
-
writeFileSync6(
|
|
4775
|
+
writeFileSync6(join42(REPO_HOME, "hosts", ".gitkeep"), "");
|
|
4603
4776
|
log("created hosts/.gitkeep");
|
|
4604
|
-
writeJsonAtomic(
|
|
4777
|
+
writeJsonAtomic(join42(REPO_HOME, "shared", "settings.base.json"), {});
|
|
4605
4778
|
log("created shared/settings.base.json");
|
|
4606
|
-
writeJsonAtomic(
|
|
4779
|
+
writeJsonAtomic(join42(REPO_HOME, "path-map.json"), { projects: {} });
|
|
4607
4780
|
log("created path-map.json");
|
|
4608
4781
|
if (snapshot) {
|
|
4609
4782
|
snapshotIntoShared({ projects: {} });
|
|
@@ -4807,6 +4980,28 @@ function parseAllowArgs(argv) {
|
|
|
4807
4980
|
return positionals;
|
|
4808
4981
|
}
|
|
4809
4982
|
|
|
4983
|
+
// src/nomad.dispatch.pull.ts
|
|
4984
|
+
function parsePullArgs(argv) {
|
|
4985
|
+
let dryRun = false;
|
|
4986
|
+
let forceRemote = false;
|
|
4987
|
+
let i = 3;
|
|
4988
|
+
while (i < argv.length) {
|
|
4989
|
+
const token = argv[i];
|
|
4990
|
+
if (token === "--dry-run") {
|
|
4991
|
+
if (dryRun) return null;
|
|
4992
|
+
dryRun = true;
|
|
4993
|
+
} else if (token === "--force-remote") {
|
|
4994
|
+
if (forceRemote) return null;
|
|
4995
|
+
forceRemote = true;
|
|
4996
|
+
} else {
|
|
4997
|
+
return null;
|
|
4998
|
+
}
|
|
4999
|
+
i++;
|
|
5000
|
+
}
|
|
5001
|
+
if (dryRun && forceRemote) return null;
|
|
5002
|
+
return { dryRun, forceRemote };
|
|
5003
|
+
}
|
|
5004
|
+
|
|
4810
5005
|
// src/nomad.dispatch.push.ts
|
|
4811
5006
|
var REJECT2 = { ok: false, advance: 0 };
|
|
4812
5007
|
function applyBool2(seen, set) {
|
|
@@ -4865,7 +5060,7 @@ function parsePushArgs(argv) {
|
|
|
4865
5060
|
// package.json
|
|
4866
5061
|
var package_default = {
|
|
4867
5062
|
name: "claude-nomad",
|
|
4868
|
-
version: "0.
|
|
5063
|
+
version: "0.42.0",
|
|
4869
5064
|
type: "module",
|
|
4870
5065
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
4871
5066
|
keywords: [
|
|
@@ -4963,6 +5158,11 @@ var DEFAULT_HELP = [
|
|
|
4963
5158
|
"Commands:",
|
|
4964
5159
|
row(" pull", "Sync ~/.claude/ from the shared repo (settings, symlinks, sessions)."),
|
|
4965
5160
|
row(" --dry-run", "Run lock + git pull, then preview every mutation without writing."),
|
|
5161
|
+
row(" --force-remote", "Recover from a wedged repo (stuck mid-rebase or mid-merge):"),
|
|
5162
|
+
cont("abort the in-progress rebase/merge, park stranded commits on"),
|
|
5163
|
+
cont("nomad/stranded-<ts>, reset to origin/main, and re-pull. Refuses"),
|
|
5164
|
+
cont("if stranded or dirty tracked changes touch synced config (shared/,"),
|
|
5165
|
+
cont("hosts/, path-map.json). Cannot combine with --dry-run."),
|
|
4966
5166
|
"",
|
|
4967
5167
|
row(" push", "Rebase, run safety checks (gitleaks, gitlinks, allow-list), commit, push."),
|
|
4968
5168
|
row(" --dry-run", "Run pre-checks (rebase, gitleaks probe, gitlink scan) and preview"),
|
|
@@ -5055,15 +5255,15 @@ var DEFAULT_HELP = [
|
|
|
5055
5255
|
init_config();
|
|
5056
5256
|
init_utils();
|
|
5057
5257
|
init_utils_json();
|
|
5058
|
-
import { existsSync as
|
|
5059
|
-
import { join as
|
|
5258
|
+
import { existsSync as existsSync37, readFileSync as readFileSync11, readdirSync as readdirSync11 } from "node:fs";
|
|
5259
|
+
import { join as join43 } from "node:path";
|
|
5060
5260
|
function resumeCmd(sessionId) {
|
|
5061
5261
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
5062
5262
|
fail(`invalid session id: ${sessionId}`);
|
|
5063
5263
|
process.exit(1);
|
|
5064
5264
|
}
|
|
5065
|
-
const projectsRoot =
|
|
5066
|
-
if (!
|
|
5265
|
+
const projectsRoot = join43(CLAUDE_HOME, "projects");
|
|
5266
|
+
if (!existsSync37(projectsRoot)) {
|
|
5067
5267
|
fail(`${projectsRoot} does not exist`);
|
|
5068
5268
|
process.exit(1);
|
|
5069
5269
|
}
|
|
@@ -5077,8 +5277,8 @@ function resumeCmd(sessionId) {
|
|
|
5077
5277
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
5078
5278
|
process.exit(1);
|
|
5079
5279
|
}
|
|
5080
|
-
const mapPath =
|
|
5081
|
-
if (!
|
|
5280
|
+
const mapPath = join43(REPO_HOME, "path-map.json");
|
|
5281
|
+
if (!existsSync37(mapPath)) {
|
|
5082
5282
|
fail("path-map.json missing");
|
|
5083
5283
|
process.exit(1);
|
|
5084
5284
|
}
|
|
@@ -5101,8 +5301,8 @@ function resumeCmd(sessionId) {
|
|
|
5101
5301
|
}
|
|
5102
5302
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
5103
5303
|
for (const dir of readdirSync11(projectsRoot)) {
|
|
5104
|
-
const candidate =
|
|
5105
|
-
if (
|
|
5304
|
+
const candidate = join43(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
5305
|
+
if (existsSync37(candidate)) return candidate;
|
|
5106
5306
|
}
|
|
5107
5307
|
return null;
|
|
5108
5308
|
}
|
|
@@ -5174,15 +5374,12 @@ try {
|
|
|
5174
5374
|
console.log(package_default.version);
|
|
5175
5375
|
break;
|
|
5176
5376
|
case "pull": {
|
|
5177
|
-
const
|
|
5178
|
-
if (
|
|
5179
|
-
|
|
5180
|
-
} else if (sub === "--dry-run" && process.argv.length === 4) {
|
|
5181
|
-
cmdPull({ dryRun: true });
|
|
5182
|
-
} else {
|
|
5183
|
-
console.error("usage: nomad pull [--dry-run]");
|
|
5377
|
+
const pullArgs = parsePullArgs(process.argv);
|
|
5378
|
+
if (pullArgs === null) {
|
|
5379
|
+
console.error("usage: nomad pull [--dry-run] [--force-remote]");
|
|
5184
5380
|
process.exit(1);
|
|
5185
5381
|
}
|
|
5382
|
+
cmdPull({ dryRun: pullArgs.dryRun, forceRemote: pullArgs.forceRemote });
|
|
5186
5383
|
break;
|
|
5187
5384
|
}
|
|
5188
5385
|
case "push": {
|