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 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
- '''AY[A-Za-z0-9_-]{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
- var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC;
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 (isAbsolute(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
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.has(basename(srcEntry))
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.has(basename(srcEntry))
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.has(name)) continue;
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.has(basename(srcEntry))
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.has(name);
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.has(segment)) return true;
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.52.4",
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
- if (process.argv[3] === void 0) {
7210
- cmdDoctor();
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 | --check-schema | --resume-cmd <session-id>]"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.52.4",
3
+ "version": "0.53.2",
4
4
  "type": "module",
5
5
  "description": "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
6
6
  "keywords": [