claude-nomad 0.40.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 +26 -0
- package/README.md +8 -0
- package/dist/nomad.mjs +551 -330
- package/dist/nomad.worker.mjs +3 -1
- package/package.json +2 -2
package/dist/nomad.mjs
CHANGED
|
@@ -170,13 +170,12 @@ function gitOrFatal(args, context, cwd) {
|
|
|
170
170
|
throw new NomadFatal(`${context} failed`);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
-
var log,
|
|
173
|
+
var log, warn, fail, NomadFatal, die, gitStatusPorcelainZ;
|
|
174
174
|
var init_utils = __esm({
|
|
175
175
|
"src/utils.ts"() {
|
|
176
176
|
"use strict";
|
|
177
177
|
init_color();
|
|
178
178
|
log = (msg) => console.log(`${dim(infoGlyph)} ${msg}`);
|
|
179
|
-
ok = (msg) => console.log(`${green(okGlyph)} ${msg}`);
|
|
180
179
|
warn = (msg) => {
|
|
181
180
|
console.error(`${yellow(warnGlyph)} ${msg}`);
|
|
182
181
|
};
|
|
@@ -450,6 +449,17 @@ function deepMerge(target, source) {
|
|
|
450
449
|
}
|
|
451
450
|
return out;
|
|
452
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
|
+
}
|
|
453
463
|
var encodePath;
|
|
454
464
|
var init_utils_json = __esm({
|
|
455
465
|
"src/utils.json.ts"() {
|
|
@@ -551,31 +561,31 @@ var init_utils_fs = __esm({
|
|
|
551
561
|
});
|
|
552
562
|
|
|
553
563
|
// src/push-gitleaks.config.ts
|
|
554
|
-
import { existsSync as
|
|
564
|
+
import { existsSync as existsSync10, mkdtempSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
555
565
|
import { tmpdir } from "node:os";
|
|
556
|
-
import { join as
|
|
566
|
+
import { join as join11 } from "node:path";
|
|
557
567
|
import { fileURLToPath } from "node:url";
|
|
558
568
|
function resolveTomlPath() {
|
|
559
|
-
const repoToml =
|
|
560
|
-
if (
|
|
569
|
+
const repoToml = join11(REPO_HOME, ".gitleaks.toml");
|
|
570
|
+
if (existsSync10(repoToml)) return repoToml;
|
|
561
571
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
562
|
-
return
|
|
572
|
+
return existsSync10(bundled) ? bundled : null;
|
|
563
573
|
}
|
|
564
574
|
function buildOverlayTempConfig(overlayBody, bundled) {
|
|
565
575
|
const tempBody = `[extend]
|
|
566
576
|
path = ${JSON.stringify(bundled)}
|
|
567
577
|
|
|
568
578
|
${overlayBody}`;
|
|
569
|
-
const tempPath = mkdtempSync(
|
|
570
|
-
const configPath =
|
|
579
|
+
const tempPath = mkdtempSync(join11(tmpdir(), "nomad-gitleaks-cfg-"));
|
|
580
|
+
const configPath = join11(tempPath, "config.toml");
|
|
571
581
|
writeFileSync2(configPath, tempBody, { mode: 384, flag: "wx" });
|
|
572
582
|
return { configPath, tempPath };
|
|
573
583
|
}
|
|
574
584
|
function resolveTomlConfig() {
|
|
575
|
-
const overlayPath =
|
|
576
|
-
const repoToml =
|
|
585
|
+
const overlayPath = join11(REPO_HOME, ".gitleaks.overlay.toml");
|
|
586
|
+
const repoToml = join11(REPO_HOME, ".gitleaks.toml");
|
|
577
587
|
const bundled = resolveTomlPath();
|
|
578
|
-
if (!
|
|
588
|
+
if (!existsSync10(overlayPath)) {
|
|
579
589
|
return { path: bundled, tempPath: null };
|
|
580
590
|
}
|
|
581
591
|
if (bundled === repoToml) {
|
|
@@ -618,7 +628,7 @@ var init_push_gitleaks_config = __esm({
|
|
|
618
628
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
619
629
|
import { readdirSync as readdirSync3, rmSync as rmSync3 } from "node:fs";
|
|
620
630
|
import { homedir as homedir2, platform } from "node:os";
|
|
621
|
-
import { join as
|
|
631
|
+
import { join as join12 } from "node:path";
|
|
622
632
|
function gitleaksInstallHint() {
|
|
623
633
|
const head = "gitleaks not on PATH (required for nomad push). Install:";
|
|
624
634
|
const plat = platform();
|
|
@@ -661,7 +671,7 @@ function findGitlinks(dir) {
|
|
|
661
671
|
return;
|
|
662
672
|
}
|
|
663
673
|
for (const e of entries) {
|
|
664
|
-
const p =
|
|
674
|
+
const p = join12(current, e.name);
|
|
665
675
|
if (e.name === ".git") {
|
|
666
676
|
hits.push(p);
|
|
667
677
|
continue;
|
|
@@ -713,7 +723,7 @@ var init_push_checks = __esm({
|
|
|
713
723
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
714
724
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync4 } from "node:fs";
|
|
715
725
|
import { homedir as homedir3 } from "node:os";
|
|
716
|
-
import { join as
|
|
726
|
+
import { join as join16 } from "node:path";
|
|
717
727
|
function readGitleaksReport(reportPath) {
|
|
718
728
|
try {
|
|
719
729
|
const raw = readFileSync4(reportPath, "utf8");
|
|
@@ -725,9 +735,9 @@ function readGitleaksReport(reportPath) {
|
|
|
725
735
|
}
|
|
726
736
|
}
|
|
727
737
|
function scanStagedTree(repoDir, forwardStreams = false) {
|
|
728
|
-
const cacheDir =
|
|
738
|
+
const cacheDir = join16(homedir3(), ".cache", "claude-nomad");
|
|
729
739
|
mkdirSync2(cacheDir, { recursive: true });
|
|
730
|
-
const reportPath =
|
|
740
|
+
const reportPath = join16(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
|
|
731
741
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
732
742
|
const args = [
|
|
733
743
|
"protect",
|
|
@@ -759,9 +769,9 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
759
769
|
}
|
|
760
770
|
}
|
|
761
771
|
function scanFile(filePath, forwardStreams = false) {
|
|
762
|
-
const cacheDir =
|
|
772
|
+
const cacheDir = join16(homedir3(), ".cache", "claude-nomad");
|
|
763
773
|
mkdirSync2(cacheDir, { recursive: true });
|
|
764
|
-
const reportPath =
|
|
774
|
+
const reportPath = join16(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
|
|
765
775
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
766
776
|
const args = [
|
|
767
777
|
"detect",
|
|
@@ -1210,8 +1220,8 @@ function cmdClean(opts, backupBase = BACKUP_BASE) {
|
|
|
1210
1220
|
}
|
|
1211
1221
|
|
|
1212
1222
|
// src/commands.doctor.ts
|
|
1213
|
-
import { existsSync as
|
|
1214
|
-
import { join as
|
|
1223
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
1224
|
+
import { join as join23 } from "node:path";
|
|
1215
1225
|
|
|
1216
1226
|
// src/commands.doctor.checks.repo.ts
|
|
1217
1227
|
init_color();
|
|
@@ -1225,8 +1235,8 @@ init_color();
|
|
|
1225
1235
|
// src/output-tree.ts
|
|
1226
1236
|
init_color();
|
|
1227
1237
|
var FAIL_GLYPH_BARE = "\u2717";
|
|
1228
|
-
function section(header) {
|
|
1229
|
-
return { header, items: [] };
|
|
1238
|
+
function section(header, raw = false) {
|
|
1239
|
+
return { header, items: [], raw };
|
|
1230
1240
|
}
|
|
1231
1241
|
function addItem(s, text) {
|
|
1232
1242
|
s.items.push(text);
|
|
@@ -1234,7 +1244,17 @@ function addItem(s, text) {
|
|
|
1234
1244
|
function sectionFailed(s) {
|
|
1235
1245
|
return s.items.some((line) => line.includes(failGlyph));
|
|
1236
1246
|
}
|
|
1247
|
+
function renderRawItems(items) {
|
|
1248
|
+
for (const item of items) {
|
|
1249
|
+
console.log(item === "" ? "" : ` ${item}`);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1237
1252
|
function renderSection(s) {
|
|
1253
|
+
if (s.raw) {
|
|
1254
|
+
console.log(s.header);
|
|
1255
|
+
renderRawItems(s.items);
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1238
1258
|
const header = sectionFailed(s) ? `${red(FAIL_GLYPH_BARE)} ${s.header}` : s.header;
|
|
1239
1259
|
console.log(header);
|
|
1240
1260
|
const lastContent = s.items.reduce((acc, item, j) => item === "" ? acc : j, -1);
|
|
@@ -1542,8 +1562,20 @@ function reportNeverSync(section2) {
|
|
|
1542
1562
|
init_color();
|
|
1543
1563
|
init_config();
|
|
1544
1564
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1545
|
-
import { existsSync as
|
|
1546
|
-
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
|
|
1547
1579
|
init_push_checks();
|
|
1548
1580
|
init_utils();
|
|
1549
1581
|
function reportGitleaksProbe(section2) {
|
|
@@ -1562,8 +1594,8 @@ function reportGitleaksProbe(section2) {
|
|
|
1562
1594
|
}
|
|
1563
1595
|
}
|
|
1564
1596
|
function reportGitlinks(section2) {
|
|
1565
|
-
const sharedDir =
|
|
1566
|
-
if (
|
|
1597
|
+
const sharedDir = join13(REPO_HOME, "shared");
|
|
1598
|
+
if (existsSync11(sharedDir)) {
|
|
1567
1599
|
const gitlinks = findGitlinks(sharedDir);
|
|
1568
1600
|
for (const p of gitlinks) {
|
|
1569
1601
|
const rel = relative2(REPO_HOME, p);
|
|
@@ -1602,11 +1634,25 @@ function reportRebaseClean(section2) {
|
|
|
1602
1634
|
} catch {
|
|
1603
1635
|
}
|
|
1604
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
|
+
}
|
|
1605
1651
|
|
|
1606
1652
|
// src/commands.doctor.checks.backups.ts
|
|
1607
1653
|
init_color();
|
|
1608
|
-
import { existsSync as
|
|
1609
|
-
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";
|
|
1610
1656
|
init_config();
|
|
1611
1657
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
1612
1658
|
function safeReaddir(dir) {
|
|
@@ -1622,7 +1668,7 @@ var BYTES_PER_MB = 1024 * 1024;
|
|
|
1622
1668
|
function dirSizeBytes(dir) {
|
|
1623
1669
|
let bytes = 0;
|
|
1624
1670
|
for (const entry of safeReaddir(dir)) {
|
|
1625
|
-
const full =
|
|
1671
|
+
const full = join14(dir, entry);
|
|
1626
1672
|
const st = lstatSync5(full, { throwIfNoEntry: false });
|
|
1627
1673
|
if (!st) continue;
|
|
1628
1674
|
if (st.isSymbolicLink()) continue;
|
|
@@ -1633,11 +1679,11 @@ function dirSizeBytes(dir) {
|
|
|
1633
1679
|
}
|
|
1634
1680
|
function totalSizeMb(backupBase, dirs) {
|
|
1635
1681
|
let bytes = 0;
|
|
1636
|
-
for (const name of dirs) bytes += dirSizeBytes(
|
|
1682
|
+
for (const name of dirs) bytes += dirSizeBytes(join14(backupBase, name));
|
|
1637
1683
|
return bytes / BYTES_PER_MB;
|
|
1638
1684
|
}
|
|
1639
1685
|
function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
|
|
1640
|
-
if (!
|
|
1686
|
+
if (!existsSync12(backupBase)) return;
|
|
1641
1687
|
const dirs = safeReaddir(backupBase).filter((n) => TS_SHAPE2.test(n));
|
|
1642
1688
|
const count = dirs.length;
|
|
1643
1689
|
const sizeMb = totalSizeMb(backupBase, dirs);
|
|
@@ -1651,8 +1697,8 @@ function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
|
|
|
1651
1697
|
|
|
1652
1698
|
// src/commands.doctor.check-schema.ts
|
|
1653
1699
|
init_color();
|
|
1654
|
-
import { existsSync as
|
|
1655
|
-
import { join as
|
|
1700
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
1701
|
+
import { join as join15 } from "node:path";
|
|
1656
1702
|
init_config();
|
|
1657
1703
|
|
|
1658
1704
|
// src/http-fetch.ts
|
|
@@ -1689,8 +1735,8 @@ function fetchSchemaKeys() {
|
|
|
1689
1735
|
}
|
|
1690
1736
|
}
|
|
1691
1737
|
function reportCheckSchema(section2) {
|
|
1692
|
-
const settingsPath =
|
|
1693
|
-
if (!
|
|
1738
|
+
const settingsPath = join15(CLAUDE_HOME, "settings.json");
|
|
1739
|
+
if (!existsSync13(settingsPath)) {
|
|
1694
1740
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
1695
1741
|
return;
|
|
1696
1742
|
}
|
|
@@ -1720,18 +1766,18 @@ function reportCheckSchema(section2) {
|
|
|
1720
1766
|
init_color();
|
|
1721
1767
|
import { randomBytes } from "node:crypto";
|
|
1722
1768
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
1723
|
-
import { existsSync as
|
|
1769
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync4, readdirSync as readdirSync6, rmSync as rmSync6 } from "node:fs";
|
|
1724
1770
|
import { homedir as homedir4 } from "node:os";
|
|
1725
|
-
import { join as
|
|
1771
|
+
import { join as join19 } from "node:path";
|
|
1726
1772
|
|
|
1727
1773
|
// src/commands.doctor.check-shared.scan.ts
|
|
1728
1774
|
init_color();
|
|
1729
|
-
import { join as
|
|
1775
|
+
import { join as join17 } from "node:path";
|
|
1730
1776
|
init_config();
|
|
1731
1777
|
init_push_gitleaks();
|
|
1732
1778
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
1733
1779
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
1734
|
-
return
|
|
1780
|
+
return join17(CLAUDE_HOME, "projects", encoded, `${sid}.jsonl`);
|
|
1735
1781
|
}
|
|
1736
1782
|
function reportSessionFindings(section2, bySession) {
|
|
1737
1783
|
for (const [sid, counts] of bySession) {
|
|
@@ -1823,8 +1869,8 @@ init_config();
|
|
|
1823
1869
|
init_utils();
|
|
1824
1870
|
init_utils_fs();
|
|
1825
1871
|
init_utils_json();
|
|
1826
|
-
import { cpSync as cpSync3, existsSync as
|
|
1827
|
-
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";
|
|
1828
1874
|
function copyDir(src, dst) {
|
|
1829
1875
|
rmSync5(dst, { recursive: true, force: true });
|
|
1830
1876
|
cpSync3(src, dst, { recursive: true, force: true });
|
|
@@ -1845,19 +1891,24 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
1845
1891
|
}
|
|
1846
1892
|
});
|
|
1847
1893
|
}
|
|
1894
|
+
function emitPreview(onPreview, event, fallback) {
|
|
1895
|
+
if (onPreview) onPreview(event);
|
|
1896
|
+
else log(fallback);
|
|
1897
|
+
}
|
|
1848
1898
|
function remapPull(ts, opts = {}) {
|
|
1849
1899
|
const dryRun = opts.dryRun === true;
|
|
1850
1900
|
let unmapped = 0;
|
|
1851
1901
|
const pulled = [];
|
|
1852
1902
|
const wouldPull = [];
|
|
1853
|
-
const mapPath =
|
|
1854
|
-
const repoProjects =
|
|
1855
|
-
if (!
|
|
1856
|
-
|
|
1903
|
+
const mapPath = join18(REPO_HOME, "path-map.json");
|
|
1904
|
+
const repoProjects = join18(REPO_HOME, "shared", "projects");
|
|
1905
|
+
if (!existsSync14(mapPath) || !existsSync14(repoProjects)) {
|
|
1906
|
+
const text = "no path-map or repo projects dir; skipping session remap";
|
|
1907
|
+
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
1857
1908
|
return { unmapped: 0, pulled, wouldPull };
|
|
1858
1909
|
}
|
|
1859
1910
|
const map = readJson(mapPath);
|
|
1860
|
-
const localProjects =
|
|
1911
|
+
const localProjects = join18(CLAUDE_HOME, "projects");
|
|
1861
1912
|
if (!dryRun) mkdirSync3(localProjects, { recursive: true });
|
|
1862
1913
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
1863
1914
|
assertSafeLogical(logical);
|
|
@@ -1866,12 +1917,16 @@ function remapPull(ts, opts = {}) {
|
|
|
1866
1917
|
unmapped++;
|
|
1867
1918
|
continue;
|
|
1868
1919
|
}
|
|
1869
|
-
const src =
|
|
1870
|
-
if (!
|
|
1871
|
-
const dst =
|
|
1920
|
+
const src = join18(repoProjects, logical);
|
|
1921
|
+
if (!existsSync14(src)) continue;
|
|
1922
|
+
const dst = join18(localProjects, encodePath(localPath));
|
|
1872
1923
|
if (dryRun) {
|
|
1873
1924
|
wouldPull.push(logical);
|
|
1874
|
-
|
|
1925
|
+
emitPreview(
|
|
1926
|
+
opts.onPreview,
|
|
1927
|
+
{ kind: "overwrite", dst, src },
|
|
1928
|
+
`would overwrite: ${dst} (from ${src})`
|
|
1929
|
+
);
|
|
1875
1930
|
continue;
|
|
1876
1931
|
}
|
|
1877
1932
|
backupBeforeWrite(dst, ts);
|
|
@@ -1909,16 +1964,16 @@ function remapPush(ts, opts = {}) {
|
|
|
1909
1964
|
let unmapped = 0;
|
|
1910
1965
|
const pushed = [];
|
|
1911
1966
|
const wouldPush = [];
|
|
1912
|
-
const mapPath =
|
|
1913
|
-
if (!
|
|
1967
|
+
const mapPath = join18(REPO_HOME, "path-map.json");
|
|
1968
|
+
if (!existsSync14(mapPath)) {
|
|
1914
1969
|
log("no path-map.json; skipping session export");
|
|
1915
1970
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
1916
1971
|
}
|
|
1917
1972
|
const map = readJson(mapPath);
|
|
1918
|
-
const localProjects =
|
|
1919
|
-
const repoProjects =
|
|
1973
|
+
const localProjects = join18(CLAUDE_HOME, "projects");
|
|
1974
|
+
const repoProjects = join18(REPO_HOME, "shared", "projects");
|
|
1920
1975
|
const reverse = buildReverseMap(map);
|
|
1921
|
-
if (!
|
|
1976
|
+
if (!existsSync14(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
1922
1977
|
if (!dryRun) mkdirSync3(repoProjects, { recursive: true });
|
|
1923
1978
|
for (const dir of readdirSync5(localProjects)) {
|
|
1924
1979
|
const logical = reverse.get(dir);
|
|
@@ -1926,13 +1981,13 @@ function remapPush(ts, opts = {}) {
|
|
|
1926
1981
|
unmapped++;
|
|
1927
1982
|
continue;
|
|
1928
1983
|
}
|
|
1929
|
-
const repoDst =
|
|
1984
|
+
const repoDst = join18(repoProjects, logical);
|
|
1930
1985
|
if (dryRun) {
|
|
1931
1986
|
wouldPush.push(logical);
|
|
1932
1987
|
continue;
|
|
1933
1988
|
}
|
|
1934
1989
|
backupRepoWrite(repoDst, ts, REPO_HOME);
|
|
1935
|
-
copyDirJsonlOnly(
|
|
1990
|
+
copyDirJsonlOnly(join18(localProjects, dir), repoDst);
|
|
1936
1991
|
pushed.push(logical);
|
|
1937
1992
|
}
|
|
1938
1993
|
return { unmapped, collisions: 0, pushed, wouldPush };
|
|
@@ -1944,8 +1999,8 @@ init_utils_json();
|
|
|
1944
1999
|
function buildScanTree(tmpRoot) {
|
|
1945
2000
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
1946
2001
|
let staged = 0;
|
|
1947
|
-
const mapPath =
|
|
1948
|
-
if (!
|
|
2002
|
+
const mapPath = join19(REPO_HOME, "path-map.json");
|
|
2003
|
+
if (!existsSync15(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
1949
2004
|
let map;
|
|
1950
2005
|
try {
|
|
1951
2006
|
map = readJson(mapPath);
|
|
@@ -1962,12 +2017,12 @@ function buildScanTree(tmpRoot) {
|
|
|
1962
2017
|
if (!p || p === "TBD") continue;
|
|
1963
2018
|
reverse.set(encodePath(p), logical);
|
|
1964
2019
|
}
|
|
1965
|
-
const localProjects =
|
|
1966
|
-
if (!
|
|
2020
|
+
const localProjects = join19(CLAUDE_HOME, "projects");
|
|
2021
|
+
if (!existsSync15(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
1967
2022
|
for (const dir of readdirSync6(localProjects)) {
|
|
1968
2023
|
const logical = reverse.get(dir);
|
|
1969
2024
|
if (!logical) continue;
|
|
1970
|
-
copyDirJsonlOnly(
|
|
2025
|
+
copyDirJsonlOnly(join19(localProjects, dir), join19(tmpRoot, "shared", "projects", logical));
|
|
1971
2026
|
logicalToEncoded.set(logical, dir);
|
|
1972
2027
|
staged++;
|
|
1973
2028
|
}
|
|
@@ -1998,11 +2053,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
1998
2053
|
}
|
|
1999
2054
|
function reportCheckShared(section2, gitleaksReady) {
|
|
2000
2055
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
2001
|
-
const cacheDir =
|
|
2056
|
+
const cacheDir = join19(homedir4(), ".cache", "claude-nomad");
|
|
2002
2057
|
mkdirSync4(cacheDir, { recursive: true });
|
|
2003
2058
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
2004
|
-
const reportPath =
|
|
2005
|
-
const tmpRoot =
|
|
2059
|
+
const reportPath = join19(cacheDir, `check-shared-${stamp}.json`);
|
|
2060
|
+
const tmpRoot = join19(cacheDir, `check-shared-tree-${stamp}`);
|
|
2006
2061
|
try {
|
|
2007
2062
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
2008
2063
|
if (malformed) {
|
|
@@ -2023,8 +2078,8 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
2023
2078
|
|
|
2024
2079
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
2025
2080
|
init_color();
|
|
2026
|
-
import { existsSync as
|
|
2027
|
-
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";
|
|
2028
2083
|
init_config();
|
|
2029
2084
|
function typeFromPackageJson(pkgPath) {
|
|
2030
2085
|
try {
|
|
@@ -2046,8 +2101,8 @@ function effectiveType(hookPath) {
|
|
|
2046
2101
|
}
|
|
2047
2102
|
let dir = dirname2(real);
|
|
2048
2103
|
for (; ; ) {
|
|
2049
|
-
const pkg =
|
|
2050
|
-
if (
|
|
2104
|
+
const pkg = join20(dir, "package.json");
|
|
2105
|
+
if (existsSync16(pkg)) return typeFromPackageJson(pkg);
|
|
2051
2106
|
const parent = dirname2(dir);
|
|
2052
2107
|
if (parent === dir) return "cjs";
|
|
2053
2108
|
dir = parent;
|
|
@@ -2089,15 +2144,15 @@ function safeRead(path) {
|
|
|
2089
2144
|
}
|
|
2090
2145
|
}
|
|
2091
2146
|
function reportHookScopeCheck(section2) {
|
|
2092
|
-
const hooksDir =
|
|
2093
|
-
if (!
|
|
2147
|
+
const hooksDir = join20(CLAUDE_HOME, "hooks");
|
|
2148
|
+
if (!existsSync16(hooksDir)) {
|
|
2094
2149
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
2095
2150
|
return;
|
|
2096
2151
|
}
|
|
2097
2152
|
let anyWarn = false;
|
|
2098
2153
|
for (const name of safeReaddir2(hooksDir)) {
|
|
2099
2154
|
if (extname(name) !== ".js") continue;
|
|
2100
|
-
const abs =
|
|
2155
|
+
const abs = join20(hooksDir, name);
|
|
2101
2156
|
const eff = effectiveType(abs);
|
|
2102
2157
|
if (eff === null) continue;
|
|
2103
2158
|
const src = safeRead(abs);
|
|
@@ -2117,8 +2172,8 @@ function reportHookScopeCheck(section2) {
|
|
|
2117
2172
|
|
|
2118
2173
|
// src/commands.doctor.checks.hooks.ts
|
|
2119
2174
|
init_color();
|
|
2120
|
-
import { existsSync as
|
|
2121
|
-
import { join as
|
|
2175
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
2176
|
+
import { join as join21 } from "node:path";
|
|
2122
2177
|
init_config();
|
|
2123
2178
|
function expandHome(token) {
|
|
2124
2179
|
return token.replace(/^\$\{HOME\}/, HOME).replace(/^\$HOME/, HOME).replace(/^~/, HOME);
|
|
@@ -2156,7 +2211,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
2156
2211
|
for (const group of groups) {
|
|
2157
2212
|
for (const cmd of commandsFromGroup(group)) {
|
|
2158
2213
|
for (const resolved of claudePathsIn(cmd)) {
|
|
2159
|
-
if (
|
|
2214
|
+
if (existsSync17(resolved)) continue;
|
|
2160
2215
|
addItem(section2, `${red(failGlyph)} hooks/${event}: command target missing: ${resolved}`);
|
|
2161
2216
|
process.exitCode = 1;
|
|
2162
2217
|
anyFail = true;
|
|
@@ -2166,8 +2221,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
2166
2221
|
return anyFail;
|
|
2167
2222
|
}
|
|
2168
2223
|
function reportHooksTargetCheck(section2) {
|
|
2169
|
-
const settingsPath =
|
|
2170
|
-
if (!
|
|
2224
|
+
const settingsPath = join21(CLAUDE_HOME, "settings.json");
|
|
2225
|
+
if (!existsSync17(settingsPath)) {
|
|
2171
2226
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
2172
2227
|
return;
|
|
2173
2228
|
}
|
|
@@ -2297,8 +2352,8 @@ function reportNodeEngineCheck(section2) {
|
|
|
2297
2352
|
// src/commands.doctor.gitleaks-version.ts
|
|
2298
2353
|
init_color();
|
|
2299
2354
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
2300
|
-
import { existsSync as
|
|
2301
|
-
import { join as
|
|
2355
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
2356
|
+
import { join as join22 } from "node:path";
|
|
2302
2357
|
init_config();
|
|
2303
2358
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
2304
2359
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -2307,7 +2362,7 @@ function majorMinorOf(value) {
|
|
|
2307
2362
|
return m === null ? null : [m[1], m[2]];
|
|
2308
2363
|
}
|
|
2309
2364
|
function readGitleaksVersion(run, tomlExists) {
|
|
2310
|
-
const tomlPath =
|
|
2365
|
+
const tomlPath = join22(REPO_HOME, ".gitleaks.toml");
|
|
2311
2366
|
const args = ["version"];
|
|
2312
2367
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
2313
2368
|
try {
|
|
@@ -2319,7 +2374,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
2319
2374
|
return null;
|
|
2320
2375
|
}
|
|
2321
2376
|
}
|
|
2322
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists =
|
|
2377
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync18) {
|
|
2323
2378
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
2324
2379
|
if (raw === null) return;
|
|
2325
2380
|
const local = majorMinorOf(raw);
|
|
@@ -2492,8 +2547,8 @@ function cmdDoctor(opts = {}) {
|
|
|
2492
2547
|
reportHostAndPaths(host);
|
|
2493
2548
|
reportRepoState(host);
|
|
2494
2549
|
const links = section("Shared links");
|
|
2495
|
-
const mapPath =
|
|
2496
|
-
const rawMap =
|
|
2550
|
+
const mapPath = join23(REPO_HOME, "path-map.json");
|
|
2551
|
+
const rawMap = existsSync19(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
2497
2552
|
const map = rawMap ?? { projects: {} };
|
|
2498
2553
|
reportSharedLinks(links, map);
|
|
2499
2554
|
const hooksScan = section("Hook targets");
|
|
@@ -2512,6 +2567,7 @@ function cmdDoctor(opts = {}) {
|
|
|
2512
2567
|
reportGitlinks(repository);
|
|
2513
2568
|
reportRemote(repository);
|
|
2514
2569
|
reportRebaseClean(repository);
|
|
2570
|
+
reportRebaseState(repository);
|
|
2515
2571
|
reportActionsDrift(repository);
|
|
2516
2572
|
const nomadVersion = section("Nomad Version");
|
|
2517
2573
|
reportVersionCheck(nomadVersion);
|
|
@@ -2542,8 +2598,8 @@ function cmdDoctor(opts = {}) {
|
|
|
2542
2598
|
// src/commands.drop-session.ts
|
|
2543
2599
|
init_config();
|
|
2544
2600
|
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
2545
|
-
import { existsSync as
|
|
2546
|
-
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";
|
|
2547
2603
|
|
|
2548
2604
|
// src/commands.drop-session.git.ts
|
|
2549
2605
|
init_config();
|
|
@@ -2586,8 +2642,8 @@ function isInIndex(rel) {
|
|
|
2586
2642
|
init_config();
|
|
2587
2643
|
init_utils();
|
|
2588
2644
|
init_utils_json();
|
|
2589
|
-
import { existsSync as
|
|
2590
|
-
import { join as
|
|
2645
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
2646
|
+
import { join as join24 } from "node:path";
|
|
2591
2647
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
2592
2648
|
function reportScrubHint(id, matches) {
|
|
2593
2649
|
const live = resolveLiveTranscript(id, matches);
|
|
@@ -2603,16 +2659,16 @@ function reportScrubHint(id, matches) {
|
|
|
2603
2659
|
}
|
|
2604
2660
|
function resolveLiveTranscript(id, matches) {
|
|
2605
2661
|
try {
|
|
2606
|
-
const mapPath =
|
|
2607
|
-
if (!
|
|
2662
|
+
const mapPath = join24(REPO_HOME, "path-map.json");
|
|
2663
|
+
if (!existsSync20(mapPath)) return null;
|
|
2608
2664
|
const projects = readJson(mapPath).projects;
|
|
2609
2665
|
for (const rel of matches) {
|
|
2610
2666
|
const logical = SHARED_PROJECT_LOGICAL.exec(rel)?.[1];
|
|
2611
2667
|
if (logical === void 0) continue;
|
|
2612
2668
|
const abs = projects[logical]?.[HOST];
|
|
2613
2669
|
if (abs === void 0) continue;
|
|
2614
|
-
const live =
|
|
2615
|
-
if (
|
|
2670
|
+
const live = join24(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
|
|
2671
|
+
if (existsSync20(live)) return live;
|
|
2616
2672
|
}
|
|
2617
2673
|
return null;
|
|
2618
2674
|
} catch {
|
|
@@ -2627,8 +2683,8 @@ init_utils();
|
|
|
2627
2683
|
init_config();
|
|
2628
2684
|
init_utils();
|
|
2629
2685
|
import { closeSync as closeSync2, mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync8, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2630
|
-
import { dirname as dirname3, join as
|
|
2631
|
-
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");
|
|
2632
2688
|
function acquireLock(verb) {
|
|
2633
2689
|
mkdirSync5(dirname3(LOCK_PATH), { recursive: true });
|
|
2634
2690
|
try {
|
|
@@ -2738,12 +2794,12 @@ function cmdDropSession(id) {
|
|
|
2738
2794
|
fail(`invalid session id: ${id}`);
|
|
2739
2795
|
process.exit(1);
|
|
2740
2796
|
}
|
|
2741
|
-
if (!
|
|
2797
|
+
if (!existsSync21(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
2742
2798
|
const handle = acquireLock("drop-session");
|
|
2743
2799
|
if (handle === null) process.exit(0);
|
|
2744
2800
|
try {
|
|
2745
|
-
const repoProjects =
|
|
2746
|
-
if (!
|
|
2801
|
+
const repoProjects = join26(REPO_HOME, "shared", "projects");
|
|
2802
|
+
if (!existsSync21(repoProjects)) {
|
|
2747
2803
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
2748
2804
|
}
|
|
2749
2805
|
const matches = collectMatches(repoProjects, id);
|
|
@@ -2765,12 +2821,12 @@ function cmdDropSession(id) {
|
|
|
2765
2821
|
function collectMatches(repoProjects, id) {
|
|
2766
2822
|
const matches = [];
|
|
2767
2823
|
for (const logical of readdirSync8(repoProjects)) {
|
|
2768
|
-
const candidate =
|
|
2769
|
-
if (
|
|
2824
|
+
const candidate = join26(repoProjects, logical, `${id}.jsonl`);
|
|
2825
|
+
if (existsSync21(candidate)) {
|
|
2770
2826
|
matches.push(relative4(REPO_HOME, candidate));
|
|
2771
2827
|
}
|
|
2772
|
-
const dir =
|
|
2773
|
-
if (
|
|
2828
|
+
const dir = join26(repoProjects, logical, id);
|
|
2829
|
+
if (existsSync21(dir) && statSync5(dir).isDirectory()) {
|
|
2774
2830
|
const dirRel = relative4(REPO_HOME, dir);
|
|
2775
2831
|
const staged = expandStagedDir(dirRel);
|
|
2776
2832
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -2806,19 +2862,19 @@ function unstageOne(rel) {
|
|
|
2806
2862
|
|
|
2807
2863
|
// src/commands.redact.ts
|
|
2808
2864
|
init_config();
|
|
2809
|
-
import { existsSync as
|
|
2810
|
-
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";
|
|
2811
2867
|
|
|
2812
2868
|
// src/commands.redact.subtree.ts
|
|
2813
|
-
import { existsSync as
|
|
2814
|
-
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";
|
|
2815
2871
|
init_utils_fs();
|
|
2816
2872
|
function collectFiles(dir, out) {
|
|
2817
|
-
if (!
|
|
2873
|
+
if (!existsSync22(dir)) return;
|
|
2818
2874
|
const st = lstatSync6(dir);
|
|
2819
2875
|
if (!st.isDirectory()) return;
|
|
2820
2876
|
for (const entry of readdirSync9(dir)) {
|
|
2821
|
-
const abs =
|
|
2877
|
+
const abs = join27(dir, entry);
|
|
2822
2878
|
const lst = lstatSync6(abs);
|
|
2823
2879
|
if (lst.isSymbolicLink()) continue;
|
|
2824
2880
|
if (lst.isDirectory()) {
|
|
@@ -2868,14 +2924,14 @@ init_utils_json();
|
|
|
2868
2924
|
init_utils();
|
|
2869
2925
|
function resolveLiveTranscript2(id) {
|
|
2870
2926
|
try {
|
|
2871
|
-
const mapPath =
|
|
2872
|
-
if (!
|
|
2927
|
+
const mapPath = join28(REPO_HOME, "path-map.json");
|
|
2928
|
+
if (!existsSync23(mapPath)) return null;
|
|
2873
2929
|
const projects = readJson(mapPath).projects;
|
|
2874
2930
|
for (const hostMap of Object.values(projects)) {
|
|
2875
2931
|
const abs = hostMap[HOST];
|
|
2876
2932
|
if (abs === void 0) continue;
|
|
2877
|
-
const live =
|
|
2878
|
-
if (
|
|
2933
|
+
const live = join28(CLAUDE_HOME, "projects", encodePath(abs), `${id}.jsonl`);
|
|
2934
|
+
if (existsSync23(live)) return live;
|
|
2879
2935
|
}
|
|
2880
2936
|
return null;
|
|
2881
2937
|
} catch {
|
|
@@ -2893,17 +2949,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
2893
2949
|
fail(`invalid session id: ${id}`);
|
|
2894
2950
|
process.exit(1);
|
|
2895
2951
|
}
|
|
2896
|
-
if (!
|
|
2952
|
+
if (!existsSync23(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
2897
2953
|
const handle = acquireLock("redact");
|
|
2898
2954
|
if (handle === null) process.exit(0);
|
|
2899
2955
|
try {
|
|
2900
2956
|
const localPath = resolveLiveTranscript2(id);
|
|
2901
|
-
if (localPath === null || !
|
|
2957
|
+
if (localPath === null || !existsSync23(localPath)) {
|
|
2902
2958
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
2903
2959
|
process.exitCode = 1;
|
|
2904
2960
|
return;
|
|
2905
2961
|
}
|
|
2906
|
-
const sessionDir =
|
|
2962
|
+
const sessionDir = join28(dirname4(localPath), id);
|
|
2907
2963
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
2908
2964
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
2909
2965
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -2956,8 +3012,8 @@ ${lines}`);
|
|
|
2956
3012
|
}
|
|
2957
3013
|
|
|
2958
3014
|
// src/commands.pull.ts
|
|
2959
|
-
import { existsSync as
|
|
2960
|
-
import { join as
|
|
3015
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync8 } from "node:fs";
|
|
3016
|
+
import { join as join37 } from "node:path";
|
|
2961
3017
|
|
|
2962
3018
|
// src/commands.push.sections.ts
|
|
2963
3019
|
init_color();
|
|
@@ -2986,14 +3042,6 @@ function summaryRow(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
|
2986
3042
|
const { text, clean } = summaryText(verb, unmapped, collisions, extrasSkipped);
|
|
2987
3043
|
return clean ? `${green(okGlyph)} ${text}` : `${yellow(warnGlyph)} ${text}`;
|
|
2988
3044
|
}
|
|
2989
|
-
function emitSummary(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
2990
|
-
const { text, clean } = summaryText(verb, unmapped, collisions, extrasSkipped);
|
|
2991
|
-
if (clean) {
|
|
2992
|
-
ok(text);
|
|
2993
|
-
return;
|
|
2994
|
-
}
|
|
2995
|
-
warn(text);
|
|
2996
|
-
}
|
|
2997
3045
|
|
|
2998
3046
|
// src/commands.push.sections.ts
|
|
2999
3047
|
function collapsedSkipRow(n, noun) {
|
|
@@ -3053,22 +3101,34 @@ init_config();
|
|
|
3053
3101
|
|
|
3054
3102
|
// src/extras-sync.ts
|
|
3055
3103
|
init_config();
|
|
3056
|
-
import { existsSync as
|
|
3057
|
-
import { join as
|
|
3104
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
3105
|
+
import { join as join31 } from "node:path";
|
|
3058
3106
|
|
|
3059
3107
|
// src/extras-sync.diff.ts
|
|
3060
3108
|
init_utils();
|
|
3061
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
|
+
}
|
|
3062
3122
|
function listDivergingFiles(a, b) {
|
|
3063
3123
|
try {
|
|
3064
|
-
const stdout = execFileSync13("git", ["diff", "--no-index", "--name-
|
|
3124
|
+
const stdout = execFileSync13("git", ["diff", "--no-index", "--name-status", a, b], {
|
|
3065
3125
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3066
3126
|
}).toString();
|
|
3067
|
-
return stdout
|
|
3127
|
+
return parseDiffOutput(stdout);
|
|
3068
3128
|
} catch (err) {
|
|
3069
3129
|
const e = err;
|
|
3070
3130
|
if (e.status === 1 && e.stdout !== void 0) {
|
|
3071
|
-
return e.stdout.toString()
|
|
3131
|
+
return parseDiffOutput(e.stdout.toString());
|
|
3072
3132
|
}
|
|
3073
3133
|
if (e.code === "ENOENT") {
|
|
3074
3134
|
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
@@ -3081,8 +3141,8 @@ function listDivergingFiles(a, b) {
|
|
|
3081
3141
|
|
|
3082
3142
|
// src/extras-sync.core.ts
|
|
3083
3143
|
init_config();
|
|
3084
|
-
import { cpSync as cpSync4, existsSync as
|
|
3085
|
-
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";
|
|
3086
3146
|
|
|
3087
3147
|
// src/extras-sync.guards.ts
|
|
3088
3148
|
init_utils();
|
|
@@ -3105,9 +3165,9 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
3105
3165
|
init_utils();
|
|
3106
3166
|
init_utils_json();
|
|
3107
3167
|
function loadValidatedExtras(opts) {
|
|
3108
|
-
const mapPath =
|
|
3109
|
-
const repoExtras =
|
|
3110
|
-
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)) {
|
|
3111
3171
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
3112
3172
|
return null;
|
|
3113
3173
|
}
|
|
@@ -3149,8 +3209,8 @@ init_utils_json();
|
|
|
3149
3209
|
|
|
3150
3210
|
// src/extras-sync.remap.ts
|
|
3151
3211
|
init_config();
|
|
3152
|
-
import { existsSync as
|
|
3153
|
-
import { join as
|
|
3212
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync6 } from "node:fs";
|
|
3213
|
+
import { join as join30 } from "node:path";
|
|
3154
3214
|
init_utils_fs();
|
|
3155
3215
|
function runExtrasOp(v, dryRun, paths, backup) {
|
|
3156
3216
|
const counts = { unmapped: 0, skipped: 0 };
|
|
@@ -3158,7 +3218,7 @@ function runExtrasOp(v, dryRun, paths, backup) {
|
|
|
3158
3218
|
const would = [];
|
|
3159
3219
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
3160
3220
|
const { src, dst } = paths(t);
|
|
3161
|
-
if (!
|
|
3221
|
+
if (!existsSync25(src)) continue;
|
|
3162
3222
|
const item = `${t.logical}/${t.dirname}`;
|
|
3163
3223
|
if (dryRun) {
|
|
3164
3224
|
would.push(item);
|
|
@@ -3174,14 +3234,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
3174
3234
|
const dryRun = opts.dryRun === true;
|
|
3175
3235
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
3176
3236
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
3177
|
-
const repoExtras =
|
|
3237
|
+
const repoExtras = join30(REPO_HOME, "shared", "extras");
|
|
3178
3238
|
if (!dryRun) mkdirSync6(repoExtras, { recursive: true });
|
|
3179
3239
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
3180
3240
|
v,
|
|
3181
3241
|
dryRun,
|
|
3182
3242
|
({ localRoot, logical, dirname: dirname6 }) => ({
|
|
3183
|
-
src:
|
|
3184
|
-
dst:
|
|
3243
|
+
src: join30(localRoot, dirname6),
|
|
3244
|
+
dst: join30(repoExtras, logical, dirname6)
|
|
3185
3245
|
}),
|
|
3186
3246
|
(dst) => backupRepoWrite(dst, ts, REPO_HOME)
|
|
3187
3247
|
);
|
|
@@ -3194,13 +3254,13 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
3194
3254
|
missingMsg: "no path-map or repo extras dir; skipping extras remap"
|
|
3195
3255
|
});
|
|
3196
3256
|
if (v === null) return { unmapped: 0, skipped: 0, pulled: [], wouldPull: [] };
|
|
3197
|
-
const repoExtras =
|
|
3257
|
+
const repoExtras = join30(REPO_HOME, "shared", "extras");
|
|
3198
3258
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
3199
3259
|
v,
|
|
3200
3260
|
dryRun,
|
|
3201
3261
|
({ localRoot, logical, dirname: dirname6 }) => ({
|
|
3202
|
-
src:
|
|
3203
|
-
dst:
|
|
3262
|
+
src: join30(repoExtras, logical, dirname6),
|
|
3263
|
+
dst: join30(localRoot, dirname6)
|
|
3204
3264
|
}),
|
|
3205
3265
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
3206
3266
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -3214,14 +3274,14 @@ function divergenceCheckExtras(ts) {
|
|
|
3214
3274
|
const v = loadValidatedExtras({});
|
|
3215
3275
|
if (v === null) return;
|
|
3216
3276
|
const counts = { unmapped: 0, skipped: 0 };
|
|
3217
|
-
const backupRoot =
|
|
3277
|
+
const backupRoot = join31(BACKUP_BASE, ts, "extras");
|
|
3218
3278
|
for (const { logical, localRoot, dirname: dirname6 } of eachExtrasTarget(v, counts)) {
|
|
3219
|
-
const local =
|
|
3220
|
-
const repo =
|
|
3221
|
-
if (!
|
|
3279
|
+
const local = join31(localRoot, dirname6);
|
|
3280
|
+
const repo = join31(REPO_HOME, "shared", "extras", logical, dirname6);
|
|
3281
|
+
if (!existsSync26(local) || !existsSync26(repo)) continue;
|
|
3222
3282
|
const diff = listDivergingFiles(local, repo);
|
|
3223
3283
|
if (diff.length === 0) continue;
|
|
3224
|
-
const projectBackupRoot =
|
|
3284
|
+
const projectBackupRoot = join31(backupRoot, encodePath(localRoot));
|
|
3225
3285
|
warn(
|
|
3226
3286
|
`local ${dirname6} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will overwrite them (backups at ${projectBackupRoot}/)`
|
|
3227
3287
|
);
|
|
@@ -3234,47 +3294,61 @@ init_config();
|
|
|
3234
3294
|
init_utils();
|
|
3235
3295
|
init_utils_fs();
|
|
3236
3296
|
init_utils_json();
|
|
3237
|
-
import { existsSync as
|
|
3238
|
-
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";
|
|
3299
|
+
function emitAutoMove(onPreview, linkPath, ts, name) {
|
|
3300
|
+
if (onPreview) {
|
|
3301
|
+
onPreview({ kind: "auto-move", from: linkPath, to: `backup/${ts}/${name}` });
|
|
3302
|
+
} else {
|
|
3303
|
+
log(`would auto-move non-symlink: ${linkPath} -> backup/${ts}/${name}`);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
function emitCreate(onPreview, from, to) {
|
|
3307
|
+
if (onPreview) {
|
|
3308
|
+
onPreview({ kind: "create", from, to });
|
|
3309
|
+
} else {
|
|
3310
|
+
log(`would create symlink: ${from} -> ${to}`);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3239
3313
|
function applySharedLinks(ts, map, opts = {}) {
|
|
3240
3314
|
const dryRun = opts.dryRun === true;
|
|
3241
3315
|
const linkNames = allSharedLinks(map);
|
|
3242
3316
|
for (const name of linkNames) {
|
|
3243
|
-
const linkPath =
|
|
3244
|
-
const target =
|
|
3245
|
-
if (!
|
|
3317
|
+
const linkPath = join32(CLAUDE_HOME, name);
|
|
3318
|
+
const target = join32(REPO_HOME, "shared", name);
|
|
3319
|
+
if (!existsSync27(linkPath)) continue;
|
|
3246
3320
|
if (lstatSync7(linkPath).isSymbolicLink()) continue;
|
|
3247
|
-
if (!
|
|
3321
|
+
if (!existsSync27(target)) continue;
|
|
3248
3322
|
if (dryRun) {
|
|
3249
|
-
|
|
3323
|
+
emitAutoMove(opts.onPreview, linkPath, ts, name);
|
|
3250
3324
|
continue;
|
|
3251
3325
|
}
|
|
3252
3326
|
backupBeforeWrite(linkPath, ts);
|
|
3253
3327
|
rmSync8(linkPath, { recursive: true, force: true });
|
|
3254
3328
|
}
|
|
3255
3329
|
for (const name of linkNames) {
|
|
3256
|
-
const target =
|
|
3257
|
-
if (!
|
|
3330
|
+
const target = join32(REPO_HOME, "shared", name);
|
|
3331
|
+
if (!existsSync27(target)) continue;
|
|
3258
3332
|
if (dryRun) {
|
|
3259
|
-
|
|
3333
|
+
emitCreate(opts.onPreview, join32(CLAUDE_HOME, name), target);
|
|
3260
3334
|
continue;
|
|
3261
3335
|
}
|
|
3262
|
-
ensureSymlink(
|
|
3336
|
+
ensureSymlink(join32(CLAUDE_HOME, name), target);
|
|
3263
3337
|
}
|
|
3264
3338
|
}
|
|
3265
3339
|
function regenerateSettings(ts, opts = {}) {
|
|
3266
3340
|
const dryRun = opts.dryRun === true;
|
|
3267
|
-
const basePath =
|
|
3268
|
-
const hostPath =
|
|
3269
|
-
if (!
|
|
3341
|
+
const basePath = join32(REPO_HOME, "shared", "settings.base.json");
|
|
3342
|
+
const hostPath = join32(REPO_HOME, "hosts", `${HOST}.json`);
|
|
3343
|
+
if (!existsSync27(basePath)) {
|
|
3270
3344
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
3271
3345
|
}
|
|
3272
3346
|
const base = readJson(basePath);
|
|
3273
|
-
const hasOverrides =
|
|
3347
|
+
const hasOverrides = existsSync27(hostPath);
|
|
3274
3348
|
const overrides = hasOverrides ? readJson(hostPath) : {};
|
|
3275
3349
|
const merged = deepMerge(base, overrides);
|
|
3276
|
-
const settingsPath =
|
|
3277
|
-
if (!hasOverrides &&
|
|
3350
|
+
const settingsPath = join32(CLAUDE_HOME, "settings.json");
|
|
3351
|
+
if (!hasOverrides && existsSync27(settingsPath)) {
|
|
3278
3352
|
try {
|
|
3279
3353
|
const existing = readJson(settingsPath);
|
|
3280
3354
|
const baseKeys = new Set(Object.keys(base));
|
|
@@ -3300,8 +3374,8 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
3300
3374
|
|
|
3301
3375
|
// src/preview.ts
|
|
3302
3376
|
init_config();
|
|
3303
|
-
import { existsSync as
|
|
3304
|
-
import { join as
|
|
3377
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
3378
|
+
import { join as join33 } from "node:path";
|
|
3305
3379
|
|
|
3306
3380
|
// node_modules/diff/libesm/diff/base.js
|
|
3307
3381
|
var Diff = class {
|
|
@@ -3575,8 +3649,8 @@ function diffLinesToUnified(oldStr, newStr) {
|
|
|
3575
3649
|
}
|
|
3576
3650
|
|
|
3577
3651
|
// src/preview.ts
|
|
3578
|
-
init_utils();
|
|
3579
3652
|
init_utils_json();
|
|
3653
|
+
var CANONICAL_ORDER_NOTE = "settings.json will be rewritten in canonical key order; no value changes";
|
|
3580
3654
|
function diffJsonStrings(currentJsonText, newJsonText) {
|
|
3581
3655
|
if (currentJsonText === newJsonText) return "";
|
|
3582
3656
|
const lines = [
|
|
@@ -3587,7 +3661,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
3587
3661
|
return lines.join("\n");
|
|
3588
3662
|
}
|
|
3589
3663
|
function readJsonOrNull(path) {
|
|
3590
|
-
if (!
|
|
3664
|
+
if (!existsSync28(path)) return null;
|
|
3591
3665
|
try {
|
|
3592
3666
|
return readJson(path);
|
|
3593
3667
|
} catch {
|
|
@@ -3597,59 +3671,86 @@ function readJsonOrNull(path) {
|
|
|
3597
3671
|
function previewSettings(basePath, hostPath, settingsPath) {
|
|
3598
3672
|
const base = readJsonOrNull(basePath);
|
|
3599
3673
|
if (base === null) {
|
|
3600
|
-
|
|
3601
|
-
return;
|
|
3674
|
+
return { diff: "", notes: ["section skipped (base or current missing)"] };
|
|
3602
3675
|
}
|
|
3676
|
+
const notes = [];
|
|
3603
3677
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
3604
|
-
if (hostOverrides === null &&
|
|
3605
|
-
|
|
3678
|
+
if (hostOverrides === null && existsSync28(hostPath)) {
|
|
3679
|
+
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
3606
3680
|
}
|
|
3607
3681
|
const merged = deepMerge(base, hostOverrides ?? {});
|
|
3608
3682
|
const current = readJsonOrNull(settingsPath);
|
|
3609
|
-
if (current === null &&
|
|
3610
|
-
|
|
3611
|
-
return;
|
|
3683
|
+
if (current === null && existsSync28(settingsPath)) {
|
|
3684
|
+
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
3612
3685
|
}
|
|
3686
|
+
const rawEqual = JSON.stringify(current ?? {}, null, 2) === JSON.stringify(merged, null, 2);
|
|
3613
3687
|
const diff = diffJsonStrings(
|
|
3614
|
-
JSON.stringify(current ?? {}, null, 2),
|
|
3615
|
-
JSON.stringify(merged, null, 2)
|
|
3688
|
+
JSON.stringify(sortKeysDeep(current ?? {}), null, 2),
|
|
3689
|
+
JSON.stringify(sortKeysDeep(merged), null, 2)
|
|
3616
3690
|
);
|
|
3617
|
-
if (diff === "")
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3691
|
+
if (diff === "" && !rawEqual) notes.push(CANONICAL_ORDER_NOTE);
|
|
3692
|
+
return { diff, notes };
|
|
3693
|
+
}
|
|
3694
|
+
function formatLinkRow(e) {
|
|
3695
|
+
return `${e.kind} ${e.from} -> ${e.to}`;
|
|
3696
|
+
}
|
|
3697
|
+
function formatSessionRow(e) {
|
|
3698
|
+
return e.kind === "overwrite" ? `overwrite ${e.dst} (from ${e.src})` : e.text;
|
|
3699
|
+
}
|
|
3700
|
+
function buildSettingsSectionForPreview(result) {
|
|
3701
|
+
const s = section("settings.json", true);
|
|
3702
|
+
if (result.diff !== "") {
|
|
3703
|
+
for (const line of result.diff.split("\n")) {
|
|
3704
|
+
addItem(s, line);
|
|
3705
|
+
}
|
|
3622
3706
|
}
|
|
3707
|
+
for (const note of result.notes) {
|
|
3708
|
+
addItem(s, `note: ${note}`);
|
|
3709
|
+
}
|
|
3710
|
+
return s;
|
|
3623
3711
|
}
|
|
3624
|
-
function computePreview(ts, map) {
|
|
3625
|
-
log(`would pull on host=${HOST} (dry-run; no mutation)`);
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3712
|
+
function computePreview(ts, map, verb = "pull") {
|
|
3713
|
+
console.log(`would pull on host=${HOST} (dry-run; no mutation)`);
|
|
3714
|
+
console.log("");
|
|
3715
|
+
const links = section("Symlinks");
|
|
3716
|
+
applySharedLinks(ts, map, {
|
|
3717
|
+
dryRun: true,
|
|
3718
|
+
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
3719
|
+
});
|
|
3720
|
+
const settingsResult = previewSettings(
|
|
3721
|
+
join33(REPO_HOME, "shared", "settings.base.json"),
|
|
3722
|
+
join33(REPO_HOME, "hosts", `${HOST}.json`),
|
|
3723
|
+
join33(CLAUDE_HOME, "settings.json")
|
|
3631
3724
|
);
|
|
3632
|
-
const
|
|
3725
|
+
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
3726
|
+
const sessions = section("Sessions");
|
|
3727
|
+
const remapResult = remapPull(ts, {
|
|
3728
|
+
dryRun: true,
|
|
3729
|
+
onPreview: (e) => addItem(sessions, formatSessionRow(e))
|
|
3730
|
+
});
|
|
3731
|
+
const summary = section("Summary");
|
|
3732
|
+
addItem(summary, summaryRow(verb, remapResult.unmapped));
|
|
3733
|
+
renderTree([links, settingsSection, sessions, summary]);
|
|
3633
3734
|
return { unmapped: remapResult.unmapped, collisions: 0 };
|
|
3634
3735
|
}
|
|
3635
3736
|
|
|
3636
3737
|
// src/spinner.ts
|
|
3637
3738
|
init_color();
|
|
3638
|
-
import { existsSync as
|
|
3739
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
3639
3740
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3640
3741
|
import { Worker } from "node:worker_threads";
|
|
3641
3742
|
|
|
3642
3743
|
// src/commands.push.recovery.ts
|
|
3643
3744
|
init_config();
|
|
3644
3745
|
import { readFileSync as readFileSync10, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3645
|
-
import { join as
|
|
3746
|
+
import { join as join36 } from "node:path";
|
|
3646
3747
|
import { createInterface } from "node:readline/promises";
|
|
3647
3748
|
|
|
3648
3749
|
// src/commands.push.recovery.redact.ts
|
|
3649
3750
|
init_config();
|
|
3650
3751
|
init_config_sharedDirs_guard();
|
|
3651
|
-
import { cpSync as cpSync5, existsSync as
|
|
3652
|
-
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";
|
|
3653
3754
|
init_push_gitleaks_scan();
|
|
3654
3755
|
init_utils_json();
|
|
3655
3756
|
init_utils();
|
|
@@ -3680,8 +3781,8 @@ function resolveStagedDir(localPath, map) {
|
|
|
3680
3781
|
assertSafeLogical(logical);
|
|
3681
3782
|
const abs = hostMap[HOST];
|
|
3682
3783
|
if (abs === void 0) continue;
|
|
3683
|
-
if (localPath.startsWith(
|
|
3684
|
-
return
|
|
3784
|
+
if (localPath.startsWith(join34(CLAUDE_HOME, "projects", encodePath(abs)) + sep2)) {
|
|
3785
|
+
return join34(REPO_HOME, "shared", "projects", logical);
|
|
3685
3786
|
}
|
|
3686
3787
|
}
|
|
3687
3788
|
return null;
|
|
@@ -3703,7 +3804,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3703
3804
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
3704
3805
|
);
|
|
3705
3806
|
}
|
|
3706
|
-
const sessionDir =
|
|
3807
|
+
const sessionDir = join34(dirname5(localPath), sid);
|
|
3707
3808
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
3708
3809
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
3709
3810
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -3737,9 +3838,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3737
3838
|
);
|
|
3738
3839
|
}
|
|
3739
3840
|
mkdirSync7(stagedProjectDir, { recursive: true });
|
|
3740
|
-
cpSync5(localPath,
|
|
3741
|
-
if (
|
|
3742
|
-
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 });
|
|
3743
3844
|
}
|
|
3744
3845
|
return true;
|
|
3745
3846
|
}
|
|
@@ -3747,13 +3848,13 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
3747
3848
|
// src/commands.push.recovery.drop.ts
|
|
3748
3849
|
init_config();
|
|
3749
3850
|
import { rmSync as rmSync9 } from "node:fs";
|
|
3750
|
-
import { join as
|
|
3851
|
+
import { join as join35 } from "node:path";
|
|
3751
3852
|
function dropSessionFromStaged(sid, map) {
|
|
3752
3853
|
const logicals = Object.keys(map.projects);
|
|
3753
3854
|
if (logicals.length === 0) return false;
|
|
3754
3855
|
for (const logical of logicals) {
|
|
3755
|
-
const jsonl =
|
|
3756
|
-
const dir =
|
|
3856
|
+
const jsonl = join35(REPO_HOME, "shared", "projects", logical, `${sid}.jsonl`);
|
|
3857
|
+
const dir = join35(REPO_HOME, "shared", "projects", logical, sid);
|
|
3757
3858
|
rmSync9(jsonl, { force: true });
|
|
3758
3859
|
rmSync9(dir, { recursive: true, force: true });
|
|
3759
3860
|
}
|
|
@@ -3871,7 +3972,7 @@ function applyThenRescan(scanVerdict, repoHome) {
|
|
|
3871
3972
|
return next;
|
|
3872
3973
|
}
|
|
3873
3974
|
function allowThenRescan(append, scanVerdict, repoHome) {
|
|
3874
|
-
const ignPath =
|
|
3975
|
+
const ignPath = join36(repoHome, ".gitleaksignore");
|
|
3875
3976
|
let before;
|
|
3876
3977
|
try {
|
|
3877
3978
|
before = readFileSync10(ignPath, "utf8");
|
|
@@ -3969,7 +4070,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
3969
4070
|
`);
|
|
3970
4071
|
}
|
|
3971
4072
|
function resolveWorkerPath(deps = {}) {
|
|
3972
|
-
const check = deps.existsSyncFn ??
|
|
4073
|
+
const check = deps.existsSyncFn ?? existsSync30;
|
|
3973
4074
|
const base = deps.baseUrl ?? import.meta.url;
|
|
3974
4075
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
3975
4076
|
if (check(mjs)) return mjs;
|
|
@@ -4009,10 +4110,10 @@ function startSpinner(label, deps = {}) {
|
|
|
4009
4110
|
const elapsed = now() - startMs;
|
|
4010
4111
|
if (animate && !degraded && worker !== null) {
|
|
4011
4112
|
worker.postMessage({ type: "pause" });
|
|
4012
|
-
if (success) writeAnimatedDone(out, dl, elapsed, ttyCheck());
|
|
4013
|
-
else out.write("\r\x1B[K");
|
|
4014
4113
|
worker.terminate();
|
|
4015
4114
|
worker = null;
|
|
4115
|
+
if (success) writeAnimatedDone(out, dl, elapsed, ttyCheck());
|
|
4116
|
+
else out.write("\r\x1B[K");
|
|
4016
4117
|
} else if (success) {
|
|
4017
4118
|
writePlainDone(out, dl, elapsed);
|
|
4018
4119
|
}
|
|
@@ -4022,6 +4123,122 @@ function startSpinner(label, deps = {}) {
|
|
|
4022
4123
|
stop: () => finalize(false)
|
|
4023
4124
|
};
|
|
4024
4125
|
}
|
|
4126
|
+
function withSpinner(label, fn, deps) {
|
|
4127
|
+
const sp = startSpinner(label, deps);
|
|
4128
|
+
try {
|
|
4129
|
+
const result = fn();
|
|
4130
|
+
sp.succeed();
|
|
4131
|
+
return result;
|
|
4132
|
+
} finally {
|
|
4133
|
+
sp.stop();
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
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
|
+
}
|
|
4025
4242
|
|
|
4026
4243
|
// src/commands.pull.ts
|
|
4027
4244
|
init_utils();
|
|
@@ -4030,14 +4247,7 @@ init_utils_json();
|
|
|
4030
4247
|
function applyWetPull(ts, map) {
|
|
4031
4248
|
applySharedLinks(ts, map);
|
|
4032
4249
|
const { label } = regenerateSettings(ts);
|
|
4033
|
-
const
|
|
4034
|
-
let remapResult;
|
|
4035
|
-
try {
|
|
4036
|
-
remapResult = remapPull(ts);
|
|
4037
|
-
syncSp.succeed();
|
|
4038
|
-
} finally {
|
|
4039
|
-
syncSp.stop();
|
|
4040
|
-
}
|
|
4250
|
+
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
4041
4251
|
const extrasResult = remapExtrasPull(ts);
|
|
4042
4252
|
const summary = section("Summary");
|
|
4043
4253
|
addItem(
|
|
@@ -4051,18 +4261,32 @@ function applyWetPull(ts, map) {
|
|
|
4051
4261
|
summary
|
|
4052
4262
|
]);
|
|
4053
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
|
+
}
|
|
4054
4276
|
function cmdPull(opts = {}) {
|
|
4055
4277
|
const dryRun = opts.dryRun === true;
|
|
4056
|
-
|
|
4057
|
-
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"))) {
|
|
4058
4281
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
4059
4282
|
}
|
|
4060
4283
|
const handle = acquireLock("pull");
|
|
4061
4284
|
if (handle === null) process.exit(0);
|
|
4062
4285
|
try {
|
|
4063
4286
|
const ts = freshBackupTs(BACKUP_BASE);
|
|
4287
|
+
handleWedge(REPO_HOME, forceRemote);
|
|
4064
4288
|
if (!dryRun) {
|
|
4065
|
-
const backupRoot =
|
|
4289
|
+
const backupRoot = join37(BACKUP_BASE, ts);
|
|
4066
4290
|
try {
|
|
4067
4291
|
mkdirSync8(backupRoot, { recursive: true });
|
|
4068
4292
|
} catch (err) {
|
|
@@ -4073,13 +4297,12 @@ function cmdPull(opts = {}) {
|
|
|
4073
4297
|
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
4074
4298
|
);
|
|
4075
4299
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", REPO_HOME);
|
|
4076
|
-
const mapPath =
|
|
4077
|
-
const map =
|
|
4300
|
+
const mapPath = join37(REPO_HOME, "path-map.json");
|
|
4301
|
+
const map = existsSync31(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
4078
4302
|
divergenceCheckExtras(ts);
|
|
4079
4303
|
if (dryRun) {
|
|
4080
|
-
|
|
4304
|
+
computePreview(ts, map, "pull");
|
|
4081
4305
|
log("dry-run complete; no mutation");
|
|
4082
|
-
emitSummary("pull", previewResult.unmapped);
|
|
4083
4306
|
} else {
|
|
4084
4307
|
applyWetPull(ts, map);
|
|
4085
4308
|
}
|
|
@@ -4097,8 +4320,8 @@ function cmdPull(opts = {}) {
|
|
|
4097
4320
|
|
|
4098
4321
|
// src/commands.push.ts
|
|
4099
4322
|
init_config();
|
|
4100
|
-
import { existsSync as
|
|
4101
|
-
import { join as
|
|
4323
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
4324
|
+
import { join as join39, relative as relative5 } from "node:path";
|
|
4102
4325
|
|
|
4103
4326
|
// src/commands.push.allowlist.ts
|
|
4104
4327
|
init_config();
|
|
@@ -4122,7 +4345,7 @@ function isNeverSync(path) {
|
|
|
4122
4345
|
}
|
|
4123
4346
|
return false;
|
|
4124
4347
|
}
|
|
4125
|
-
function
|
|
4348
|
+
function parsePorcelainZ2(statusPorcelain) {
|
|
4126
4349
|
const records = statusPorcelain.split("\0");
|
|
4127
4350
|
const paths = [];
|
|
4128
4351
|
for (let i = 0; i < records.length; i++) {
|
|
@@ -4152,7 +4375,7 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
4152
4375
|
];
|
|
4153
4376
|
const neverSyncHits = [];
|
|
4154
4377
|
const violations = [];
|
|
4155
|
-
for (const path of
|
|
4378
|
+
for (const path of parsePorcelainZ2(statusPorcelain)) {
|
|
4156
4379
|
if (isNeverSync(path)) {
|
|
4157
4380
|
neverSyncHits.push(path);
|
|
4158
4381
|
} else if (!isAllowed(path, allowed)) {
|
|
@@ -4178,9 +4401,9 @@ init_color();
|
|
|
4178
4401
|
init_config();
|
|
4179
4402
|
init_config_sharedDirs_guard();
|
|
4180
4403
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
4181
|
-
import { copyFileSync, existsSync as
|
|
4404
|
+
import { copyFileSync, existsSync as existsSync32, mkdirSync as mkdirSync9, readdirSync as readdirSync10, rmSync as rmSync11 } from "node:fs";
|
|
4182
4405
|
import { homedir as homedir5 } from "node:os";
|
|
4183
|
-
import { join as
|
|
4406
|
+
import { join as join38 } from "node:path";
|
|
4184
4407
|
init_push_leak_verdict();
|
|
4185
4408
|
init_push_gitleaks();
|
|
4186
4409
|
init_utils_fs();
|
|
@@ -4195,13 +4418,13 @@ function stageSessions(tmpRoot, map) {
|
|
|
4195
4418
|
if (!p || p === "TBD") continue;
|
|
4196
4419
|
reverse.set(encodePath(p), logical);
|
|
4197
4420
|
}
|
|
4198
|
-
const localProjects =
|
|
4199
|
-
if (!
|
|
4421
|
+
const localProjects = join38(CLAUDE_HOME, "projects");
|
|
4422
|
+
if (!existsSync32(localProjects)) return 0;
|
|
4200
4423
|
let staged = 0;
|
|
4201
4424
|
for (const dir of readdirSync10(localProjects)) {
|
|
4202
4425
|
const logical = reverse.get(dir);
|
|
4203
4426
|
if (!logical) continue;
|
|
4204
|
-
copyDirJsonlOnly(
|
|
4427
|
+
copyDirJsonlOnly(join38(localProjects, dir), join38(tmpRoot, "shared", "projects", logical));
|
|
4205
4428
|
staged++;
|
|
4206
4429
|
}
|
|
4207
4430
|
return staged;
|
|
@@ -4217,9 +4440,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
4217
4440
|
if (!localRoot || localRoot === "TBD") continue;
|
|
4218
4441
|
for (const dirname6 of dirnames) {
|
|
4219
4442
|
if (!whitelist.includes(dirname6)) continue;
|
|
4220
|
-
const src =
|
|
4221
|
-
if (!
|
|
4222
|
-
const dst =
|
|
4443
|
+
const src = join38(localRoot, dirname6);
|
|
4444
|
+
if (!existsSync32(src)) continue;
|
|
4445
|
+
const dst = join38(tmpRoot, "shared", "extras", logical, dirname6);
|
|
4223
4446
|
copyExtras(src, dst);
|
|
4224
4447
|
staged++;
|
|
4225
4448
|
}
|
|
@@ -4227,19 +4450,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
4227
4450
|
return staged;
|
|
4228
4451
|
}
|
|
4229
4452
|
function previewPushLeaks(map) {
|
|
4230
|
-
const cacheDir =
|
|
4453
|
+
const cacheDir = join38(homedir5(), ".cache", "claude-nomad");
|
|
4231
4454
|
mkdirSync9(cacheDir, { recursive: true });
|
|
4232
4455
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
4233
|
-
const tmpRoot =
|
|
4456
|
+
const tmpRoot = join38(cacheDir, `push-preview-tree-${stamp}`);
|
|
4234
4457
|
try {
|
|
4235
4458
|
const sessionCount = stageSessions(tmpRoot, map);
|
|
4236
4459
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
4237
4460
|
if (sessionCount + extrasCount === 0) {
|
|
4238
4461
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
4239
4462
|
}
|
|
4240
|
-
const ignoreFile =
|
|
4241
|
-
if (
|
|
4242
|
-
copyFileSync(ignoreFile,
|
|
4463
|
+
const ignoreFile = join38(REPO_HOME, ".gitleaksignore");
|
|
4464
|
+
if (existsSync32(ignoreFile)) {
|
|
4465
|
+
copyFileSync(ignoreFile, join38(tmpRoot, ".gitleaksignore"));
|
|
4243
4466
|
}
|
|
4244
4467
|
let findings;
|
|
4245
4468
|
try {
|
|
@@ -4261,7 +4484,7 @@ init_utils();
|
|
|
4261
4484
|
init_utils_fs();
|
|
4262
4485
|
init_utils_json();
|
|
4263
4486
|
function guardGitlinks() {
|
|
4264
|
-
const gitlinks = findGitlinks(
|
|
4487
|
+
const gitlinks = findGitlinks(join39(REPO_HOME, "shared"));
|
|
4265
4488
|
if (gitlinks.length === 0) return;
|
|
4266
4489
|
for (const p of gitlinks) {
|
|
4267
4490
|
const rel = relative5(REPO_HOME, p);
|
|
@@ -4272,34 +4495,15 @@ function guardGitlinks() {
|
|
|
4272
4495
|
`gitlink trap: ${gitlinks.length} nested .git ${noun} in shared/; remove before retry`
|
|
4273
4496
|
);
|
|
4274
4497
|
}
|
|
4275
|
-
function runScan() {
|
|
4276
|
-
const sp = startSpinner("Scanning for secrets");
|
|
4277
|
-
try {
|
|
4278
|
-
const verdict = scanPushVerdict();
|
|
4279
|
-
sp.succeed();
|
|
4280
|
-
return verdict;
|
|
4281
|
-
} finally {
|
|
4282
|
-
sp.stop();
|
|
4283
|
-
}
|
|
4284
|
-
}
|
|
4285
|
-
function runPush() {
|
|
4286
|
-
const sp = startSpinner("Pushing");
|
|
4287
|
-
try {
|
|
4288
|
-
gitOrFatal(["push"], "git push", REPO_HOME);
|
|
4289
|
-
sp.succeed();
|
|
4290
|
-
} finally {
|
|
4291
|
-
sp.stop();
|
|
4292
|
-
}
|
|
4293
|
-
}
|
|
4294
4498
|
async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule) {
|
|
4295
4499
|
gitOrFatal(["add", "-A"], "git add", REPO_HOME);
|
|
4296
|
-
let verdict =
|
|
4500
|
+
let verdict = withSpinner("Scanning for secrets", scanPushVerdict);
|
|
4297
4501
|
if (verdict.leak) {
|
|
4298
4502
|
renderPushTree(st, verdict);
|
|
4299
4503
|
verdict = await resolveLeakFindings(verdict, ts, map, { redactAll, allowAll, allowRule });
|
|
4300
4504
|
}
|
|
4301
4505
|
gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit", REPO_HOME);
|
|
4302
|
-
|
|
4506
|
+
withSpinner("Pushing", () => gitOrFatal(["push"], "git push", REPO_HOME));
|
|
4303
4507
|
renderPushTree(st, verdict);
|
|
4304
4508
|
}
|
|
4305
4509
|
function runDryRunPreview(st, map) {
|
|
@@ -4332,22 +4536,16 @@ async function cmdPush(opts = {}) {
|
|
|
4332
4536
|
const allowAll = opts.allowAll === true;
|
|
4333
4537
|
const allowRule = opts.allowRule;
|
|
4334
4538
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
4335
|
-
if (!
|
|
4539
|
+
if (!existsSync33(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
4336
4540
|
const handle = acquireLock("push");
|
|
4337
4541
|
if (handle === null) process.exit(0);
|
|
4338
4542
|
try {
|
|
4339
4543
|
console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
|
|
4340
4544
|
probeGitleaks();
|
|
4341
|
-
|
|
4342
|
-
try {
|
|
4343
|
-
rebaseBeforePush();
|
|
4344
|
-
rebaseSp.succeed();
|
|
4345
|
-
} finally {
|
|
4346
|
-
rebaseSp.stop();
|
|
4347
|
-
}
|
|
4545
|
+
withSpinner("Rebasing onto origin", rebaseBeforePush);
|
|
4348
4546
|
const ts = freshBackupTs(BACKUP_BASE);
|
|
4349
|
-
const remap = remapPush(ts, { dryRun });
|
|
4350
|
-
const extras = remapExtrasPush(ts, { dryRun });
|
|
4547
|
+
const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun }));
|
|
4548
|
+
const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
|
|
4351
4549
|
const st = { dryRun, remap, extras };
|
|
4352
4550
|
guardGitlinks();
|
|
4353
4551
|
const status = gitStatusPorcelainZ(REPO_HOME, { untrackedAll: true });
|
|
@@ -4356,8 +4554,8 @@ async function cmdPush(opts = {}) {
|
|
|
4356
4554
|
renderNoScanTree(st);
|
|
4357
4555
|
return;
|
|
4358
4556
|
}
|
|
4359
|
-
const mapPath =
|
|
4360
|
-
if (!
|
|
4557
|
+
const mapPath = join39(REPO_HOME, "path-map.json");
|
|
4558
|
+
if (!existsSync33(mapPath)) {
|
|
4361
4559
|
if (dryRun) return runDryRunPreview(st, null);
|
|
4362
4560
|
die("path-map.json missing, cannot enforce push allow-list");
|
|
4363
4561
|
}
|
|
@@ -4378,9 +4576,9 @@ async function cmdPush(opts = {}) {
|
|
|
4378
4576
|
}
|
|
4379
4577
|
|
|
4380
4578
|
// src/commands.update.ts
|
|
4381
|
-
import { execFileSync as
|
|
4579
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4382
4580
|
init_utils();
|
|
4383
|
-
function cmdUpdate(run =
|
|
4581
|
+
function cmdUpdate(run = execFileSync15) {
|
|
4384
4582
|
try {
|
|
4385
4583
|
run("npm", ["update", "-g", "claude-nomad"], { stdio: "inherit" });
|
|
4386
4584
|
} catch (err) {
|
|
@@ -4397,19 +4595,18 @@ init_config();
|
|
|
4397
4595
|
|
|
4398
4596
|
// src/diff.ts
|
|
4399
4597
|
init_config();
|
|
4400
|
-
import { existsSync as
|
|
4401
|
-
import { join as
|
|
4598
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
4599
|
+
import { join as join40 } from "node:path";
|
|
4402
4600
|
init_utils();
|
|
4403
4601
|
init_utils_fs();
|
|
4404
4602
|
init_utils_json();
|
|
4405
4603
|
function cmdDiff() {
|
|
4406
4604
|
try {
|
|
4407
|
-
if (!
|
|
4605
|
+
if (!existsSync34(REPO_HOME)) die(`repo not cloned at ${REPO_HOME}`);
|
|
4408
4606
|
const ts = freshBackupTs(BACKUP_BASE);
|
|
4409
|
-
const mapPath =
|
|
4410
|
-
const map =
|
|
4411
|
-
|
|
4412
|
-
emitSummary("diff", result.unmapped);
|
|
4607
|
+
const mapPath = join40(REPO_HOME, "path-map.json");
|
|
4608
|
+
const map = existsSync34(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
4609
|
+
computePreview(ts, map, "diff");
|
|
4413
4610
|
} catch (err) {
|
|
4414
4611
|
if (err instanceof NomadFatal) {
|
|
4415
4612
|
fail(err.message);
|
|
@@ -4422,19 +4619,19 @@ function cmdDiff() {
|
|
|
4422
4619
|
|
|
4423
4620
|
// src/init.ts
|
|
4424
4621
|
init_config();
|
|
4425
|
-
import { existsSync as
|
|
4426
|
-
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";
|
|
4427
4624
|
|
|
4428
4625
|
// src/init.gh-onboard.ts
|
|
4429
4626
|
init_config();
|
|
4430
|
-
import { execFileSync as
|
|
4627
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
4431
4628
|
init_utils();
|
|
4432
4629
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
4433
4630
|
function isValidRepoName(name) {
|
|
4434
4631
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
4435
4632
|
}
|
|
4436
4633
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
4437
|
-
function ensureOriginRepo(repoName, run =
|
|
4634
|
+
function ensureOriginRepo(repoName, run = execFileSync16) {
|
|
4438
4635
|
if (!isValidRepoName(repoName)) {
|
|
4439
4636
|
die(
|
|
4440
4637
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -4504,31 +4701,31 @@ init_config();
|
|
|
4504
4701
|
init_utils();
|
|
4505
4702
|
init_utils_fs();
|
|
4506
4703
|
init_utils_json();
|
|
4507
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync6, existsSync as
|
|
4508
|
-
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";
|
|
4509
4706
|
function snapshotIntoShared(map) {
|
|
4510
4707
|
for (const name of allSharedLinks(map)) {
|
|
4511
|
-
const src =
|
|
4512
|
-
if (!
|
|
4513
|
-
const dst =
|
|
4708
|
+
const src = join41(CLAUDE_HOME, name);
|
|
4709
|
+
if (!existsSync35(src)) continue;
|
|
4710
|
+
const dst = join41(REPO_HOME, "shared", name);
|
|
4514
4711
|
if (statSync9(src).isDirectory()) {
|
|
4515
|
-
const gk =
|
|
4516
|
-
if (
|
|
4712
|
+
const gk = join41(dst, ".gitkeep");
|
|
4713
|
+
if (existsSync35(gk)) rmSync12(gk);
|
|
4517
4714
|
cpSync6(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
4518
4715
|
} else {
|
|
4519
4716
|
copyFileSync2(src, dst);
|
|
4520
4717
|
}
|
|
4521
4718
|
log(`snapshotted shared/${name} from ${src}`);
|
|
4522
4719
|
}
|
|
4523
|
-
const userSettings =
|
|
4524
|
-
if (
|
|
4720
|
+
const userSettings = join41(CLAUDE_HOME, "settings.json");
|
|
4721
|
+
if (existsSync35(userSettings)) {
|
|
4525
4722
|
let parsed;
|
|
4526
4723
|
try {
|
|
4527
4724
|
parsed = readJson(userSettings);
|
|
4528
4725
|
} catch (err) {
|
|
4529
4726
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
4530
4727
|
}
|
|
4531
|
-
const hostFile =
|
|
4728
|
+
const hostFile = join41(REPO_HOME, "hosts", `${HOST}.json`);
|
|
4532
4729
|
writeJsonAtomic(hostFile, parsed);
|
|
4533
4730
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
4534
4731
|
}
|
|
@@ -4541,14 +4738,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
4541
4738
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
4542
4739
|
function preflightConflict(repoHome) {
|
|
4543
4740
|
const candidates = [
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
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")
|
|
4549
4746
|
];
|
|
4550
4747
|
for (const c of candidates) {
|
|
4551
|
-
if (
|
|
4748
|
+
if (existsSync36(c)) return c;
|
|
4552
4749
|
}
|
|
4553
4750
|
return null;
|
|
4554
4751
|
}
|
|
@@ -4561,25 +4758,25 @@ function cmdInit(opts = {}) {
|
|
|
4561
4758
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
4562
4759
|
}
|
|
4563
4760
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
4564
|
-
mkdirSync10(
|
|
4565
|
-
mkdirSync10(
|
|
4761
|
+
mkdirSync10(join42(REPO_HOME, "shared"), { recursive: true });
|
|
4762
|
+
mkdirSync10(join42(REPO_HOME, "hosts"), { recursive: true });
|
|
4566
4763
|
for (const name of SHARED_KEEP_DIRS) {
|
|
4567
|
-
mkdirSync10(
|
|
4764
|
+
mkdirSync10(join42(REPO_HOME, "shared", name), { recursive: true });
|
|
4568
4765
|
}
|
|
4569
|
-
const userClaudeMd =
|
|
4570
|
-
if (!snapshot || !
|
|
4571
|
-
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);
|
|
4572
4769
|
log("created shared/CLAUDE.md");
|
|
4573
4770
|
}
|
|
4574
4771
|
for (const name of SHARED_KEEP_DIRS) {
|
|
4575
|
-
writeFileSync6(
|
|
4772
|
+
writeFileSync6(join42(REPO_HOME, "shared", name, ".gitkeep"), "");
|
|
4576
4773
|
log(`created shared/${name}/.gitkeep`);
|
|
4577
4774
|
}
|
|
4578
|
-
writeFileSync6(
|
|
4775
|
+
writeFileSync6(join42(REPO_HOME, "hosts", ".gitkeep"), "");
|
|
4579
4776
|
log("created hosts/.gitkeep");
|
|
4580
|
-
writeJsonAtomic(
|
|
4777
|
+
writeJsonAtomic(join42(REPO_HOME, "shared", "settings.base.json"), {});
|
|
4581
4778
|
log("created shared/settings.base.json");
|
|
4582
|
-
writeJsonAtomic(
|
|
4779
|
+
writeJsonAtomic(join42(REPO_HOME, "path-map.json"), { projects: {} });
|
|
4583
4780
|
log("created path-map.json");
|
|
4584
4781
|
if (snapshot) {
|
|
4585
4782
|
snapshotIntoShared({ projects: {} });
|
|
@@ -4783,6 +4980,28 @@ function parseAllowArgs(argv) {
|
|
|
4783
4980
|
return positionals;
|
|
4784
4981
|
}
|
|
4785
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
|
+
|
|
4786
5005
|
// src/nomad.dispatch.push.ts
|
|
4787
5006
|
var REJECT2 = { ok: false, advance: 0 };
|
|
4788
5007
|
function applyBool2(seen, set) {
|
|
@@ -4841,7 +5060,7 @@ function parsePushArgs(argv) {
|
|
|
4841
5060
|
// package.json
|
|
4842
5061
|
var package_default = {
|
|
4843
5062
|
name: "claude-nomad",
|
|
4844
|
-
version: "0.
|
|
5063
|
+
version: "0.42.0",
|
|
4845
5064
|
type: "module",
|
|
4846
5065
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
4847
5066
|
keywords: [
|
|
@@ -4854,7 +5073,7 @@ var package_default = {
|
|
|
4854
5073
|
type: "git",
|
|
4855
5074
|
url: "git+https://github.com/funkadelic/claude-nomad.git"
|
|
4856
5075
|
},
|
|
4857
|
-
homepage: "https://github.
|
|
5076
|
+
homepage: "https://funkadelic.github.io/claude-nomad/",
|
|
4858
5077
|
bugs: {
|
|
4859
5078
|
url: "https://github.com/funkadelic/claude-nomad/issues"
|
|
4860
5079
|
},
|
|
@@ -4939,6 +5158,11 @@ var DEFAULT_HELP = [
|
|
|
4939
5158
|
"Commands:",
|
|
4940
5159
|
row(" pull", "Sync ~/.claude/ from the shared repo (settings, symlinks, sessions)."),
|
|
4941
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."),
|
|
4942
5166
|
"",
|
|
4943
5167
|
row(" push", "Rebase, run safety checks (gitleaks, gitlinks, allow-list), commit, push."),
|
|
4944
5168
|
row(" --dry-run", "Run pre-checks (rebase, gitleaks probe, gitlink scan) and preview"),
|
|
@@ -5031,15 +5255,15 @@ var DEFAULT_HELP = [
|
|
|
5031
5255
|
init_config();
|
|
5032
5256
|
init_utils();
|
|
5033
5257
|
init_utils_json();
|
|
5034
|
-
import { existsSync as
|
|
5035
|
-
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";
|
|
5036
5260
|
function resumeCmd(sessionId) {
|
|
5037
5261
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
5038
5262
|
fail(`invalid session id: ${sessionId}`);
|
|
5039
5263
|
process.exit(1);
|
|
5040
5264
|
}
|
|
5041
|
-
const projectsRoot =
|
|
5042
|
-
if (!
|
|
5265
|
+
const projectsRoot = join43(CLAUDE_HOME, "projects");
|
|
5266
|
+
if (!existsSync37(projectsRoot)) {
|
|
5043
5267
|
fail(`${projectsRoot} does not exist`);
|
|
5044
5268
|
process.exit(1);
|
|
5045
5269
|
}
|
|
@@ -5053,8 +5277,8 @@ function resumeCmd(sessionId) {
|
|
|
5053
5277
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
5054
5278
|
process.exit(1);
|
|
5055
5279
|
}
|
|
5056
|
-
const mapPath =
|
|
5057
|
-
if (!
|
|
5280
|
+
const mapPath = join43(REPO_HOME, "path-map.json");
|
|
5281
|
+
if (!existsSync37(mapPath)) {
|
|
5058
5282
|
fail("path-map.json missing");
|
|
5059
5283
|
process.exit(1);
|
|
5060
5284
|
}
|
|
@@ -5077,8 +5301,8 @@ function resumeCmd(sessionId) {
|
|
|
5077
5301
|
}
|
|
5078
5302
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
5079
5303
|
for (const dir of readdirSync11(projectsRoot)) {
|
|
5080
|
-
const candidate =
|
|
5081
|
-
if (
|
|
5304
|
+
const candidate = join43(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
5305
|
+
if (existsSync37(candidate)) return candidate;
|
|
5082
5306
|
}
|
|
5083
5307
|
return null;
|
|
5084
5308
|
}
|
|
@@ -5150,15 +5374,12 @@ try {
|
|
|
5150
5374
|
console.log(package_default.version);
|
|
5151
5375
|
break;
|
|
5152
5376
|
case "pull": {
|
|
5153
|
-
const
|
|
5154
|
-
if (
|
|
5155
|
-
|
|
5156
|
-
} else if (sub === "--dry-run" && process.argv.length === 4) {
|
|
5157
|
-
cmdPull({ dryRun: true });
|
|
5158
|
-
} else {
|
|
5159
|
-
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]");
|
|
5160
5380
|
process.exit(1);
|
|
5161
5381
|
}
|
|
5382
|
+
cmdPull({ dryRun: pullArgs.dryRun, forceRemote: pullArgs.forceRemote });
|
|
5162
5383
|
break;
|
|
5163
5384
|
}
|
|
5164
5385
|
case "push": {
|