claude-nomad 0.29.1 → 0.30.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.30.0](https://github.com/funkadelic/claude-nomad/compare/v0.29.1...v0.30.0) (2026-05-29)
4
+
5
+
6
+ ### Added
7
+
8
+ * **doctor:** report gh and curl presence in Version Checks ([#175](https://github.com/funkadelic/claude-nomad/issues/175)) ([5163833](https://github.com/funkadelic/claude-nomad/commit/516383301dbd9c64cfc8f1c7a654655b61c29280))
9
+
10
+
11
+ ### Changed
12
+
13
+ * widen npm-publish smoke-test propagation window ([#176](https://github.com/funkadelic/claude-nomad/issues/176)) ([f8b1eef](https://github.com/funkadelic/claude-nomad/commit/f8b1eef18dcee91fb894d2036c42356f82c76f79))
14
+
3
15
  ## [0.29.1](https://github.com/funkadelic/claude-nomad/compare/v0.29.0...v0.29.1) (2026-05-29)
4
16
 
5
17
 
package/README.md CHANGED
@@ -380,10 +380,11 @@ Read these before adopting so you opt in with eyes open.
380
380
 
381
381
  - `gh` ([GitHub CLI](https://cli.github.com/)), used only by `nomad init` to auto-disable Actions on
382
382
  the private repo; if it is missing or unauthenticated, init prints a manual fallback tip and
383
- continues
383
+ continues. `nomad doctor` reports its presence in the Version Checks section.
384
384
  - [curl](https://curl.se/), used by the version/update check (the `nomad doctor` latest-release line
385
385
  and the post-`nomad update` check) and by `nomad doctor --check-schema`; it degrades silently when
386
- curl is absent or offline, so the rest of the CLI works without it
386
+ curl is absent or offline, so the rest of the CLI works without it. `nomad doctor` reports its
387
+ presence in the Version Checks section.
387
388
 
388
389
  ## Setup
389
390
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.29.1",
3
+ "version": "0.30.0",
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": [
@@ -0,0 +1,97 @@
1
+ import { execFileSync } from 'node:child_process';
2
+
3
+ import { green, okGlyph, warnGlyph, yellow } from './color.ts';
4
+ import { addItem, type DoctorSection } from './commands.doctor.format.ts';
5
+ import type { SpawnSyncFn } from './gh-actions.ts';
6
+
7
+ /**
8
+ * Optional-dependency presence reporter for `nomad doctor`. Probes for `gh`
9
+ * and `curl` (both optional CLIs used by nomad features) and emits one row
10
+ * per binary in the Version Checks section:
11
+ * - present with parsed version: `okGlyph gh: X.Y.Z`
12
+ * - present but version unparseable: `okGlyph gh: present`
13
+ * - not installed (ENOENT): `warnGlyph gh: not installed (optional; ...)`
14
+ *
15
+ * This reporter MUST NOT set `process.exitCode`: absent optional deps are
16
+ * informational only (D-02). Both probes always run unconditionally.
17
+ */
18
+
19
+ /**
20
+ * Regex to extract the first X.Y.Z version token from a string. Each segment
21
+ * is bounded (`{1,9}`) rather than `+` so the pattern is provably linear: a
22
+ * `--version` line never has a segment longer than a few digits, and bounding
23
+ * the repetition removes the super-linear backtracking an unbounded
24
+ * `\d+\.\d+\.\d+` carries on a degenerate all-digit input.
25
+ */
26
+ const VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
27
+
28
+ /**
29
+ * Extract the first X.Y.Z-shaped version token from a string.
30
+ *
31
+ * @param line - A single line of --version output (already trimmed).
32
+ * @returns The first version token, or `null` if none found.
33
+ */
34
+ function parseFirstVersion(line: string): string | null {
35
+ const m = VERSION_TOKEN.exec(line);
36
+ return m ? m[1] : null;
37
+ }
38
+
39
+ /** Discriminated union for binary probe results. */
40
+ type DepProbeResult = { status: 'present'; version: string | null } | { status: 'not-installed' };
41
+
42
+ /**
43
+ * Probe a binary by running `bin --version` and parsing the first output line.
44
+ * Returns a DepProbeResult: present (with optional version token) or
45
+ * not-installed (ENOENT). Non-ENOENT errors are treated as present with no
46
+ * version (D-03: "never FAIL on unexpected --version output").
47
+ *
48
+ * @param bin - The binary name to probe (e.g. `gh` or `curl`).
49
+ * @param run - Injectable subprocess runner; defaults to `execFileSync`.
50
+ */
51
+ function probeOptionalDep(bin: string, run: SpawnSyncFn): DepProbeResult {
52
+ try {
53
+ const firstLine = run(bin, ['--version'], { stdio: ['ignore', 'pipe', 'pipe'] })
54
+ .toString()
55
+ .split('\n')[0]
56
+ .trim();
57
+ const version = parseFirstVersion(firstLine);
58
+ return { status: 'present', version };
59
+ } catch (err) {
60
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
61
+ return { status: 'not-installed' };
62
+ }
63
+ // Non-ENOENT: binary may exist but --version misbehaved; report as present.
64
+ return { status: 'present', version: null };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Emit presence rows for the optional `gh` and `curl` CLIs into the given
70
+ * doctor section. Each row shows the binary's install status and version (if
71
+ * parseable). Absent binaries emit a WARN naming the features they enable.
72
+ * Never sets `process.exitCode` (D-02): both deps are optional.
73
+ *
74
+ * @param section - The Version Checks section to append rows to.
75
+ * @param run - Injectable subprocess runner; defaults to `execFileSync`.
76
+ */
77
+ export function reportOptionalDeps(section: DoctorSection, run: SpawnSyncFn = execFileSync): void {
78
+ const gh = probeOptionalDep('gh', run);
79
+ if (gh.status === 'present') {
80
+ addItem(section, `${green(okGlyph)} gh: ${gh.version ?? 'present'}`);
81
+ } else {
82
+ addItem(
83
+ section,
84
+ `${yellow(warnGlyph)} gh: not installed (optional; needed for nomad init Actions auto-disable + mirror-Actions drift check)`,
85
+ );
86
+ }
87
+
88
+ const curl = probeOptionalDep('curl', run);
89
+ if (curl.status === 'present') {
90
+ addItem(section, `${green(okGlyph)} curl: ${curl.version ?? 'present'}`);
91
+ } else {
92
+ addItem(
93
+ section,
94
+ `${yellow(warnGlyph)} curl: not installed (optional; needed for release-version staleness check + nomad doctor --check-schema)`,
95
+ );
96
+ }
97
+ }
@@ -25,6 +25,7 @@ import { REPO_HOME, type PathMap } from './config.ts';
25
25
  import { reportNodeEngineCheck } from './commands.doctor.engine.ts';
26
26
  import { readJsonSafe, renderDoctor, section } from './commands.doctor.format.ts';
27
27
  import { reportGitleaksVersionCheck } from './commands.doctor.gitleaks-version.ts';
28
+ import { reportOptionalDeps } from './commands.doctor.checks.deps.ts';
28
29
  import { reportMirrorActions } from './commands.doctor.mirror-actions.ts';
29
30
  import { reportVersionCheck } from './commands.doctor.version.ts';
30
31
 
@@ -85,6 +86,7 @@ export function cmdDoctor(opts: { checkShared?: boolean; checkSchema?: boolean }
85
86
  reportVersionCheck(version);
86
87
  reportNodeEngineCheck(version);
87
88
  reportGitleaksVersionCheck(version);
89
+ reportOptionalDeps(version);
88
90
 
89
91
  const sharedScan = section('Shared scan');
90
92
  // Reuse the Repository-section readiness probe so reportCheckShared does not