claude-nomad 0.52.4 → 0.53.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitleaks.toml +5 -1
- package/CHANGELOG.md +40 -0
- package/README.md +3 -1
- package/dist/nomad.mjs +161 -50
- package/package.json +1 -1
package/.gitleaks.toml
CHANGED
|
@@ -14,10 +14,14 @@ useDefault = true
|
|
|
14
14
|
# context anywhere in the repo. Scoping to `shared/projects/<logical>/.../*.jsonl`
|
|
15
15
|
# with `condition = "AND"` (matching every other block below) confines the
|
|
16
16
|
# suppression to synced transcripts: a real secret in a source file still fires.
|
|
17
|
+
# The Sonar-issue-key regex is length-bounded to the real key shape (`AY` + 17-22
|
|
18
|
+
# base64url chars = 19-24 total, matching the sibling `key:`/`rule:` block) and
|
|
19
|
+
# token-anchored with `\b` on both ends, so it cannot suppress a longer or
|
|
20
|
+
# embedded `AY`-prefixed credential as a substring (an unbounded `{20,}` could).
|
|
17
21
|
[[allowlists]]
|
|
18
22
|
description = "claude-nomad: structurally-distinguishable tool-output noise in synced session transcripts"
|
|
19
23
|
regexes = [
|
|
20
|
-
'''
|
|
24
|
+
'''\bAY[A-Za-z0-9_-]{17,22}\b''',
|
|
21
25
|
'''[\w./-]+\.[A-Za-z0-9]+:[\w-]+:\d+''',
|
|
22
26
|
'''"id"\s*:\s*"[a-f0-9]{40,64}"''',
|
|
23
27
|
'''key=[a-f0-9]{8,} [\w./-]+\.\w+:\d+''',
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.53.2](https://github.com/funkadelic/claude-nomad/compare/v0.53.1...v0.53.2) (2026-06-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
* **security:** close pull-side path-write and push-side scan-bypass vectors ([#339](https://github.com/funkadelic/claude-nomad/issues/339)) ([99f0320](https://github.com/funkadelic/claude-nomad/commit/99f0320d1181333fc00ea983fda7e675377def47))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* bump actions/checkout from 6.0.3 to 7.0.0 ([#336](https://github.com/funkadelic/claude-nomad/issues/336)) ([6df79cf](https://github.com/funkadelic/claude-nomad/commit/6df79cfcfe9bd5eaa72130d15fdcb074019822a8))
|
|
14
|
+
* bump the dev-dependencies group with 3 updates ([#337](https://github.com/funkadelic/claude-nomad/issues/337)) ([dd5a8ea](https://github.com/funkadelic/claude-nomad/commit/dd5a8eaffe70fe96f9d6f0921eaead8c3caf323b))
|
|
15
|
+
|
|
16
|
+
## [0.53.1](https://github.com/funkadelic/claude-nomad/compare/v0.53.0...v0.53.1) (2026-06-21)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Testing
|
|
20
|
+
|
|
21
|
+
* **doctor:** assert okGlyph in verbose tree so release publish passes ([#334](https://github.com/funkadelic/claude-nomad/issues/334)) ([b587e99](https://github.com/funkadelic/claude-nomad/commit/b587e99bcadb4802a6cf9edcc9a139d84b479e86))
|
|
22
|
+
|
|
23
|
+
## [0.53.0](https://github.com/funkadelic/claude-nomad/compare/v0.52.4...v0.53.0) (2026-06-21)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
* **doctor:** compact default output with --verbose/--all/-v flag ([#327](https://github.com/funkadelic/claude-nomad/issues/327)) ([654dc91](https://github.com/funkadelic/claude-nomad/commit/654dc9174459efe0d89af2b92771bf68057e13a9))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
* **docs:** add docs-sync gate requiring docs updates with CLI changes ([#328](https://github.com/funkadelic/claude-nomad/issues/328)) ([da2ffe9](https://github.com/funkadelic/claude-nomad/commit/da2ffe9c53824a847bb12d25b7dc879695623c13))
|
|
34
|
+
* drop dead exports flagged by fallow and knip; add knip config ([#330](https://github.com/funkadelic/claude-nomad/issues/330)) ([da8bab5](https://github.com/funkadelic/claude-nomad/commit/da8bab52bb9c5e457cda552b46a1dedabb00a805))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Documentation
|
|
38
|
+
|
|
39
|
+
* **plugin:** add marketplace description and plugin keywords ([#331](https://github.com/funkadelic/claude-nomad/issues/331)) ([51ce1f8](https://github.com/funkadelic/claude-nomad/commit/51ce1f8604002dd15a9812d0411b5d0ac17e84eb))
|
|
40
|
+
* **plugin:** sync plugin and marketplace descriptions ([#332](https://github.com/funkadelic/claude-nomad/issues/332)) ([33c58b2](https://github.com/funkadelic/claude-nomad/commit/33c58b21f0fcb68320cf9c051b67fde119c94b68))
|
|
41
|
+
* **site:** add privacy page ([#333](https://github.com/funkadelic/claude-nomad/issues/333)) ([f8f17da](https://github.com/funkadelic/claude-nomad/commit/f8f17da5bcd06d25eb6a26f328133b7083371e99))
|
|
42
|
+
|
|
3
43
|
## [0.52.4](https://github.com/funkadelic/claude-nomad/compare/v0.52.3...v0.52.4) (2026-06-20)
|
|
4
44
|
|
|
5
45
|
|
package/README.md
CHANGED
|
@@ -49,7 +49,9 @@ survives different file paths and your secrets never ride along.
|
|
|
49
49
|
directions: keys present in the repo merge but absent from your live `settings.json` (behind; the
|
|
50
50
|
next `nomad pull` will restore them, fix: `nomad pull`) and keys present locally but not yet in
|
|
51
51
|
the repo (ahead; local-only additions, fix: `nomad capture-settings`). Each issue includes a fix
|
|
52
|
-
hint.
|
|
52
|
+
hint. By default the report is compact: it shows only checks that need action plus a one-line
|
|
53
|
+
verdict. Add `--verbose` (or `--all` / `-v`) to see the full per-check tree, including everything
|
|
54
|
+
that passed.
|
|
53
55
|
- **Self-healing sync.** Every overwrite is backed up first, and `nomad pull --force-remote`
|
|
54
56
|
recovers two kinds of stuck sync repo: a repo stuck mid-rebase or mid-merge (aborts the operation,
|
|
55
57
|
parks stranded work on a branch, refuses if shared config is at risk), and a repo where the rebase
|
package/dist/nomad.mjs
CHANGED
|
@@ -42,7 +42,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
42
42
|
));
|
|
43
43
|
|
|
44
44
|
// src/config.never-sync.ts
|
|
45
|
-
|
|
45
|
+
function isSecretFileName(name) {
|
|
46
|
+
return SECRET_FILE_PATTERNS.some((re) => re.test(name));
|
|
47
|
+
}
|
|
48
|
+
function isDeniedName(blockSet, name) {
|
|
49
|
+
return blockSet.has(name) || blockSet.has(name.toLowerCase()) || isSecretFileName(name);
|
|
50
|
+
}
|
|
51
|
+
var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, SECRET_FILE_PATTERNS;
|
|
46
52
|
var init_config_never_sync = __esm({
|
|
47
53
|
"src/config.never-sync.ts"() {
|
|
48
54
|
"use strict";
|
|
@@ -72,6 +78,20 @@ var init_config_never_sync = __esm({
|
|
|
72
78
|
"sessions"
|
|
73
79
|
]);
|
|
74
80
|
CLAUDE_EXTRA_NEVER_SYNC = /* @__PURE__ */ new Set([...NEVER_SYNC, "projects"]);
|
|
81
|
+
SECRET_FILE_PATTERNS = [
|
|
82
|
+
/^\.env(\..+)?$/i,
|
|
83
|
+
// .env, .env.local, .env.production
|
|
84
|
+
/\.pem$/i,
|
|
85
|
+
/\.key$/i,
|
|
86
|
+
/\.p12$/i,
|
|
87
|
+
/\.pfx$/i,
|
|
88
|
+
/^id_(rsa|dsa|ecdsa|ed25519)$/i,
|
|
89
|
+
/^\.netrc$/i,
|
|
90
|
+
/^\.npmrc$/i,
|
|
91
|
+
/^\.pgpass$/i,
|
|
92
|
+
/^\.git-credentials$/i,
|
|
93
|
+
/^credentials$/i
|
|
94
|
+
];
|
|
75
95
|
}
|
|
76
96
|
});
|
|
77
97
|
|
|
@@ -667,6 +687,37 @@ function resolveTomlPath(repo = repoHome()) {
|
|
|
667
687
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
668
688
|
return existsSync13(bundled) ? bundled : null;
|
|
669
689
|
}
|
|
690
|
+
function assertOverlayAllowlistsScoped(overlayBody) {
|
|
691
|
+
if (OVERLAY_INLINE_ALLOWLIST_RE.test(overlayBody)) {
|
|
692
|
+
throw new NomadFatal(
|
|
693
|
+
".gitleaks.overlay.toml must declare allowlists as [[allowlists]] table blocks, not the dotted-key (allowlist.x = ...) or inline-table (allowlist = { ... }) form, which bypasses path-scope validation. Use a [[allowlists]] block with a `paths` entry."
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
let inAllowlist = false;
|
|
697
|
+
let hasPaths = false;
|
|
698
|
+
const closeBlock = () => {
|
|
699
|
+
if (inAllowlist && !hasPaths) {
|
|
700
|
+
throw new NomadFatal(
|
|
701
|
+
".gitleaks.overlay.toml allowlist must be path-scoped: every [[allowlists]] block needs a `paths` entry so it cannot suppress findings repo-wide. Anchor `paths` to the files you intend to allow (e.g. shared/projects/<logical>/...jsonl)."
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
for (const line of overlayBody.split("\n")) {
|
|
706
|
+
if (TABLE_HEADER_RE.test(line)) {
|
|
707
|
+
closeBlock();
|
|
708
|
+
inAllowlist = OVERLAY_ALLOWLIST_HEADER_RE.test(line);
|
|
709
|
+
hasPaths = false;
|
|
710
|
+
} else if (inAllowlist) {
|
|
711
|
+
if (PATHS_KEY_RE.test(line)) hasPaths = true;
|
|
712
|
+
if (CATCH_ALL_RE.test(line)) {
|
|
713
|
+
throw new NomadFatal(
|
|
714
|
+
".gitleaks.overlay.toml allowlist contains a catch-all pattern (.* or .+) that would suppress every finding. Replace it with a specific, anchored pattern."
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
closeBlock();
|
|
720
|
+
}
|
|
670
721
|
function buildOverlayTempConfig(overlayBody, bundled) {
|
|
671
722
|
const tempBody = `[extend]
|
|
672
723
|
path = ${JSON.stringify(bundled)}
|
|
@@ -701,6 +752,7 @@ function resolveTomlConfig() {
|
|
|
701
752
|
".gitleaks.overlay.toml must not contain an [extend] block; it is generated automatically. Remove the [extend] section and retry."
|
|
702
753
|
);
|
|
703
754
|
}
|
|
755
|
+
assertOverlayAllowlistsScoped(overlayBody);
|
|
704
756
|
const { configPath, tempPath } = buildOverlayTempConfig(overlayBody, bundled);
|
|
705
757
|
return { path: configPath, tempPath };
|
|
706
758
|
} catch (err) {
|
|
@@ -711,13 +763,18 @@ function resolveTomlConfig() {
|
|
|
711
763
|
return { path: bundled, tempPath: null };
|
|
712
764
|
}
|
|
713
765
|
}
|
|
714
|
-
var OVERLAY_EXTEND_RE;
|
|
766
|
+
var OVERLAY_EXTEND_RE, TABLE_HEADER_RE, OVERLAY_ALLOWLIST_HEADER_RE, OVERLAY_INLINE_ALLOWLIST_RE, PATHS_KEY_RE, CATCH_ALL_RE;
|
|
715
767
|
var init_push_gitleaks_config = __esm({
|
|
716
768
|
"src/push-gitleaks.config.ts"() {
|
|
717
769
|
"use strict";
|
|
718
770
|
init_config();
|
|
719
771
|
init_utils();
|
|
720
772
|
OVERLAY_EXTEND_RE = /^\s*(?:\[\s*extend\s*\]|extend\s*[.=])/m;
|
|
773
|
+
TABLE_HEADER_RE = /^\s*\[/;
|
|
774
|
+
OVERLAY_ALLOWLIST_HEADER_RE = /^\s*\[\[?\s*allowlists?\s*\]\]?/;
|
|
775
|
+
OVERLAY_INLINE_ALLOWLIST_RE = /^[ \t]*allowlists?[ \t]*[.=]/m;
|
|
776
|
+
PATHS_KEY_RE = /^\s*paths\s*=/;
|
|
777
|
+
CATCH_ALL_RE = /('''|"""|'|")\^?\(?\.[*+]\)?\$?\1/;
|
|
721
778
|
}
|
|
722
779
|
});
|
|
723
780
|
|
|
@@ -2700,12 +2757,31 @@ init_config();
|
|
|
2700
2757
|
|
|
2701
2758
|
// src/remap.ts
|
|
2702
2759
|
init_config_sharedDirs_guard();
|
|
2760
|
+
import { cpSync as cpSync4, existsSync as existsSync17, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
|
|
2761
|
+
import { join as join22, relative as relative3, sep as sep3 } from "node:path";
|
|
2762
|
+
|
|
2763
|
+
// src/extras-sync.guards.ts
|
|
2764
|
+
init_utils();
|
|
2765
|
+
init_config_sharedDirs_guard();
|
|
2766
|
+
import { isAbsolute, normalize } from "node:path";
|
|
2767
|
+
function assertSafeLocalRoot(localRoot, logical) {
|
|
2768
|
+
if (!isAbsolute(localRoot)) {
|
|
2769
|
+
throw new NomadFatal(
|
|
2770
|
+
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2773
|
+
if (localRoot !== normalize(localRoot)) {
|
|
2774
|
+
throw new NomadFatal(
|
|
2775
|
+
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
|
|
2776
|
+
);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// src/remap.ts
|
|
2703
2781
|
init_config();
|
|
2704
2782
|
init_utils();
|
|
2705
2783
|
init_utils_fs();
|
|
2706
2784
|
init_utils_json();
|
|
2707
|
-
import { cpSync as cpSync4, existsSync as existsSync17, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
|
|
2708
|
-
import { join as join22, relative as relative3, sep as sep3 } from "node:path";
|
|
2709
2785
|
var TMP_SUFFIX = ".nomad-tmp";
|
|
2710
2786
|
function atomicMirror(src, dst, options) {
|
|
2711
2787
|
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
@@ -2760,6 +2836,7 @@ function remapPull(ts, opts = {}) {
|
|
|
2760
2836
|
unmapped++;
|
|
2761
2837
|
continue;
|
|
2762
2838
|
}
|
|
2839
|
+
assertSafeLocalRoot(localPath, logical);
|
|
2763
2840
|
const src = join22(repoProjects, logical);
|
|
2764
2841
|
if (!existsSync17(src)) continue;
|
|
2765
2842
|
const dst = join22(localProjects, encodePath(localPath));
|
|
@@ -3530,7 +3607,7 @@ import { createInterface as createInterface2 } from "node:readline/promises";
|
|
|
3530
3607
|
// src/commands.push.recovery.actions.ts
|
|
3531
3608
|
init_config();
|
|
3532
3609
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
3533
|
-
import { isAbsolute, resolve as resolve3, sep as sep5 } from "node:path";
|
|
3610
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
|
|
3534
3611
|
|
|
3535
3612
|
// src/commands.push.recovery.redact.ts
|
|
3536
3613
|
init_config();
|
|
@@ -3937,7 +4014,7 @@ function makeDefaultReadLine(repo) {
|
|
|
3937
4014
|
try {
|
|
3938
4015
|
const repoRoot = resolve3(repo);
|
|
3939
4016
|
const target = resolve3(repoRoot, file);
|
|
3940
|
-
if (
|
|
4017
|
+
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
3941
4018
|
return null;
|
|
3942
4019
|
}
|
|
3943
4020
|
const content = readFileSync12(target, "utf8");
|
|
@@ -4440,6 +4517,25 @@ function buildVerdictSection(sections) {
|
|
|
4440
4517
|
return summary;
|
|
4441
4518
|
}
|
|
4442
4519
|
|
|
4520
|
+
// src/commands.doctor.compact.ts
|
|
4521
|
+
init_color();
|
|
4522
|
+
var ALWAYS_FULL = /* @__PURE__ */ new Set(["Nomad Version", "Summary", "Shared scan", "Schema scan"]);
|
|
4523
|
+
function isProblem(item2) {
|
|
4524
|
+
return item2.includes(failGlyph) || item2.includes(warnGlyph);
|
|
4525
|
+
}
|
|
4526
|
+
function isRepoStateLine(item2) {
|
|
4527
|
+
return item2.includes("repo state:");
|
|
4528
|
+
}
|
|
4529
|
+
function compactSections(sections) {
|
|
4530
|
+
return sections.map((s) => {
|
|
4531
|
+
if (ALWAYS_FULL.has(s.header)) return s;
|
|
4532
|
+
if (s.header === "Environment") {
|
|
4533
|
+
return { ...s, items: s.items.filter((it) => isRepoStateLine(it) || isProblem(it)) };
|
|
4534
|
+
}
|
|
4535
|
+
return { ...s, items: s.items.filter(isProblem) };
|
|
4536
|
+
});
|
|
4537
|
+
}
|
|
4538
|
+
|
|
4443
4539
|
// src/commands.doctor.ts
|
|
4444
4540
|
function gatherDoctorSections(opts) {
|
|
4445
4541
|
const host = section("Environment");
|
|
@@ -4510,7 +4606,26 @@ function cmdDoctor(opts = {}) {
|
|
|
4510
4606
|
} finally {
|
|
4511
4607
|
sp.stop();
|
|
4512
4608
|
}
|
|
4513
|
-
renderDoctor(report);
|
|
4609
|
+
renderDoctor(opts.verbose === true ? report : compactSections(report));
|
|
4610
|
+
}
|
|
4611
|
+
|
|
4612
|
+
// src/nomad.dispatch.doctor.ts
|
|
4613
|
+
function parseDoctorArgs(args) {
|
|
4614
|
+
if (args[0] === "--resume-cmd") {
|
|
4615
|
+
const id = args[1];
|
|
4616
|
+
if (args.length !== 2 || id.length === 0) return { kind: "error" };
|
|
4617
|
+
return { kind: "resume", id };
|
|
4618
|
+
}
|
|
4619
|
+
let checkShared = false;
|
|
4620
|
+
let checkSchema = false;
|
|
4621
|
+
let verbose = false;
|
|
4622
|
+
for (const arg of args) {
|
|
4623
|
+
if (arg === "--check-shared") checkShared = true;
|
|
4624
|
+
else if (arg === "--check-schema") checkSchema = true;
|
|
4625
|
+
else if (arg === "--verbose" || arg === "--all" || arg === "-v") verbose = true;
|
|
4626
|
+
else return { kind: "error" };
|
|
4627
|
+
}
|
|
4628
|
+
return { kind: "run", checkShared, checkSchema, verbose };
|
|
4514
4629
|
}
|
|
4515
4630
|
|
|
4516
4631
|
// src/commands.drop-session.ts
|
|
@@ -4808,25 +4923,6 @@ function listDivergingFiles(a, b) {
|
|
|
4808
4923
|
init_config();
|
|
4809
4924
|
import { cpSync as cpSync6, existsSync as existsSync32, lstatSync as lstatSync9, readdirSync as readdirSync11, rmSync as rmSync11 } from "node:fs";
|
|
4810
4925
|
import { basename, join as join38 } from "node:path";
|
|
4811
|
-
|
|
4812
|
-
// src/extras-sync.guards.ts
|
|
4813
|
-
init_utils();
|
|
4814
|
-
init_config_sharedDirs_guard();
|
|
4815
|
-
import { isAbsolute as isAbsolute2, normalize } from "node:path";
|
|
4816
|
-
function assertSafeLocalRoot(localRoot, logical) {
|
|
4817
|
-
if (!isAbsolute2(localRoot)) {
|
|
4818
|
-
throw new NomadFatal(
|
|
4819
|
-
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
|
|
4820
|
-
);
|
|
4821
|
-
}
|
|
4822
|
-
if (localRoot !== normalize(localRoot)) {
|
|
4823
|
-
throw new NomadFatal(
|
|
4824
|
-
`invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
|
|
4825
|
-
);
|
|
4826
|
-
}
|
|
4827
|
-
}
|
|
4828
|
-
|
|
4829
|
-
// src/extras-sync.core.ts
|
|
4830
4926
|
init_utils();
|
|
4831
4927
|
init_utils_json();
|
|
4832
4928
|
function loadValidatedExtras(opts) {
|
|
@@ -4864,13 +4960,28 @@ function* eachExtrasTarget(v, counts) {
|
|
|
4864
4960
|
}
|
|
4865
4961
|
}
|
|
4866
4962
|
}
|
|
4963
|
+
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
4964
|
+
if (!existsSync32(dst)) return;
|
|
4965
|
+
for (const name of readdirSync11(src)) {
|
|
4966
|
+
if (isExcluded(name)) continue;
|
|
4967
|
+
const dstPath = join38(dst, name);
|
|
4968
|
+
const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
|
|
4969
|
+
if (dstStat === void 0) continue;
|
|
4970
|
+
if (dstStat.isSymbolicLink()) {
|
|
4971
|
+
rmSync11(dstPath, { recursive: true, force: true });
|
|
4972
|
+
} else if (dstStat.isDirectory() && lstatSync9(join38(src, name)).isDirectory()) {
|
|
4973
|
+
stripCollidingDstSymlinks(join38(src, name), dstPath, isExcluded);
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4867
4977
|
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
4978
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
4868
4979
|
try {
|
|
4869
4980
|
cpSync6(src, dst, {
|
|
4870
4981
|
recursive: true,
|
|
4871
4982
|
force: true,
|
|
4872
4983
|
verbatimSymlinks: true,
|
|
4873
|
-
filter: (srcEntry) => srcEntry === src || !blockSet
|
|
4984
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
4874
4985
|
});
|
|
4875
4986
|
} catch (err) {
|
|
4876
4987
|
const e = err;
|
|
@@ -4895,12 +5006,12 @@ function copyExtrasFiltered(src, dst, blockSet) {
|
|
|
4895
5006
|
recursive: true,
|
|
4896
5007
|
force: true,
|
|
4897
5008
|
verbatimSymlinks: true,
|
|
4898
|
-
filter: (srcEntry) => srcEntry === src || !blockSet
|
|
5009
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
4899
5010
|
});
|
|
4900
5011
|
}
|
|
4901
5012
|
function prunePreservingDenied(src, dst, blockSet) {
|
|
4902
5013
|
for (const name of readdirSync11(dst)) {
|
|
4903
|
-
if (blockSet
|
|
5014
|
+
if (isDeniedName(blockSet, name)) continue;
|
|
4904
5015
|
const dstPath = join38(dst, name);
|
|
4905
5016
|
const srcStat = lstatSync9(join38(src, name), { throwIfNoEntry: false });
|
|
4906
5017
|
if (srcStat === void 0) {
|
|
@@ -4921,11 +5032,12 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
4921
5032
|
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
4922
5033
|
else rmSync11(dst, { recursive: true, force: true });
|
|
4923
5034
|
}
|
|
5035
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
4924
5036
|
cpSync6(src, dst, {
|
|
4925
5037
|
recursive: true,
|
|
4926
5038
|
force: true,
|
|
4927
5039
|
verbatimSymlinks: true,
|
|
4928
|
-
filter: (srcEntry) => srcEntry === src || !blockSet
|
|
5040
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
4929
5041
|
});
|
|
4930
5042
|
}
|
|
4931
5043
|
function prunePreservingBy(src, dst, isPreserved) {
|
|
@@ -4951,6 +5063,7 @@ function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
|
4951
5063
|
if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
|
|
4952
5064
|
else rmSync11(dst, { recursive: true, force: true });
|
|
4953
5065
|
}
|
|
5066
|
+
stripCollidingDstSymlinks(src, dst, isPreserved);
|
|
4954
5067
|
cpSync6(src, dst, {
|
|
4955
5068
|
recursive: true,
|
|
4956
5069
|
force: true,
|
|
@@ -5228,7 +5341,7 @@ function isGsdOwned(name) {
|
|
|
5228
5341
|
return name.startsWith(GSD_PREFIX);
|
|
5229
5342
|
}
|
|
5230
5343
|
function isSkillExcluded(name) {
|
|
5231
|
-
return isGsdOwned(name) || ALWAYS_NEVER_SYNC
|
|
5344
|
+
return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
|
|
5232
5345
|
}
|
|
5233
5346
|
function copySkillsPush(src, dst) {
|
|
5234
5347
|
const srcNames = readdirSync13(src, { encoding: "utf8" });
|
|
@@ -5887,7 +6000,7 @@ function isNeverSync(path) {
|
|
|
5887
6000
|
const blockSet = blockSetFor(segments);
|
|
5888
6001
|
const scan = segments[0] === "shared" && segments[1] === "extras" ? segments.slice(4) : segments;
|
|
5889
6002
|
for (const segment of scan) {
|
|
5890
|
-
if (blockSet
|
|
6003
|
+
if (isDeniedName(blockSet, segment)) return true;
|
|
5891
6004
|
}
|
|
5892
6005
|
return false;
|
|
5893
6006
|
}
|
|
@@ -6805,7 +6918,7 @@ function parsePushArgs(argv) {
|
|
|
6805
6918
|
// package.json
|
|
6806
6919
|
var package_default = {
|
|
6807
6920
|
name: "claude-nomad",
|
|
6808
|
-
version: "0.
|
|
6921
|
+
version: "0.53.2",
|
|
6809
6922
|
type: "module",
|
|
6810
6923
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
6811
6924
|
keywords: [
|
|
@@ -6945,7 +7058,8 @@ var DEFAULT_HELP = [
|
|
|
6945
7058
|
),
|
|
6946
7059
|
"",
|
|
6947
7060
|
row(" doctor", "Read-only health check (symlinks, host file, path-map,"),
|
|
6948
|
-
cont("gitleaks, gitlinks)."),
|
|
7061
|
+
cont("gitleaks, gitlinks). Compact by default: shows problems plus a verdict."),
|
|
7062
|
+
row(" --verbose, --all, -v", "Show the full per-check tree, including passing checks."),
|
|
6949
7063
|
row(" --check-shared", "Preflight gitleaks scan of the session transcripts a"),
|
|
6950
7064
|
cont("`nomad push` would stage (a temp copy, never the live dir)."),
|
|
6951
7065
|
row(" --check-schema", "Flag settings.json keys absent from the live published"),
|
|
@@ -7205,27 +7319,24 @@ try {
|
|
|
7205
7319
|
});
|
|
7206
7320
|
break;
|
|
7207
7321
|
}
|
|
7208
|
-
case "doctor":
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
} else if (process.argv[3] === "--check-shared" && process.argv.length === 4) {
|
|
7212
|
-
cmdDoctor({ checkShared: true });
|
|
7213
|
-
} else if (process.argv[3] === "--check-schema" && process.argv.length === 4) {
|
|
7214
|
-
cmdDoctor({ checkSchema: true });
|
|
7215
|
-
} else if (process.argv[3] === "--resume-cmd") {
|
|
7216
|
-
const id = process.argv[4];
|
|
7217
|
-
if (process.argv.length !== 5 || typeof id !== "string" || id.length === 0) {
|
|
7218
|
-
console.error("usage: nomad doctor --resume-cmd <session-id>");
|
|
7219
|
-
process.exit(1);
|
|
7220
|
-
}
|
|
7221
|
-
resumeCmd(id);
|
|
7222
|
-
} else {
|
|
7322
|
+
case "doctor": {
|
|
7323
|
+
const parsed = parseDoctorArgs(process.argv.slice(3));
|
|
7324
|
+
if (parsed.kind === "error") {
|
|
7223
7325
|
console.error(
|
|
7224
|
-
"usage: nomad doctor [--check-shared
|
|
7326
|
+
"usage: nomad doctor [--check-shared] [--check-schema] [--verbose|--all|-v] | --resume-cmd <session-id>"
|
|
7225
7327
|
);
|
|
7226
7328
|
process.exit(1);
|
|
7329
|
+
} else if (parsed.kind === "resume") {
|
|
7330
|
+
resumeCmd(parsed.id);
|
|
7331
|
+
} else {
|
|
7332
|
+
cmdDoctor({
|
|
7333
|
+
checkShared: parsed.checkShared,
|
|
7334
|
+
checkSchema: parsed.checkSchema,
|
|
7335
|
+
verbose: parsed.verbose
|
|
7336
|
+
});
|
|
7227
7337
|
}
|
|
7228
7338
|
break;
|
|
7339
|
+
}
|
|
7229
7340
|
case "drop-session": {
|
|
7230
7341
|
const id = process.argv[3];
|
|
7231
7342
|
if (process.argv.length !== 4 || typeof id !== "string" || !/^\w[\w-]{0,127}$/.test(id)) {
|
package/package.json
CHANGED