claude-nomad 0.35.0 → 0.36.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 +19 -0
- package/README.md +31 -29
- package/dist/nomad.mjs +85 -57
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.36.0](https://github.com/funkadelic/claude-nomad/compare/v0.35.0...v0.36.0) (2026-06-01)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
* **doctor:** require an HTTP fetcher (curl or wget) for the version check ([#210](https://github.com/funkadelic/claude-nomad/issues/210)) ([96b5a53](https://github.com/funkadelic/claude-nomad/commit/96b5a532e688cca9acebb1c6780d4106b2f21f5b))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
* **backup:** route backup-path writers through BACKUP_BASE ([#211](https://github.com/funkadelic/claude-nomad/issues/211)) ([7033c29](https://github.com/funkadelic/claude-nomad/commit/7033c29c9af76db029f44d0f1717ea1831ba122e))
|
|
14
|
+
* **tests:** label the test matrix jobs with the Node version ([#208](https://github.com/funkadelic/claude-nomad/issues/208)) ([3ada7e1](https://github.com/funkadelic/claude-nomad/commit/3ada7e12f38fc43f517842435b46c967c9312b25))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Documentation
|
|
18
|
+
|
|
19
|
+
* **hero:** add hooks/, align glyph + terminal, tighten spacing ([#213](https://github.com/funkadelic/claude-nomad/issues/213)) ([d0bf93f](https://github.com/funkadelic/claude-nomad/commit/d0bf93f93fb21f65b8c5893b70bacbbf05d8ebe6))
|
|
20
|
+
* refresh contributor and user docs for recent changes ([#212](https://github.com/funkadelic/claude-nomad/issues/212)) ([d909097](https://github.com/funkadelic/claude-nomad/commit/d9090977fdf445316128ca9a0fefdec966105b58))
|
|
21
|
+
|
|
3
22
|
## [0.35.0](https://github.com/funkadelic/claude-nomad/compare/v0.34.1...v0.35.0) (2026-05-31)
|
|
4
23
|
|
|
5
24
|
|
package/README.md
CHANGED
|
@@ -396,10 +396,12 @@ Read these before adopting so you opt in with eyes open.
|
|
|
396
396
|
|
|
397
397
|
**Optional:**
|
|
398
398
|
|
|
399
|
-
- [curl](https://curl.se/)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
399
|
+
- [curl](https://curl.se/) or [wget](https://www.gnu.org/software/wget/), the HTTP fetcher behind
|
|
400
|
+
the version-staleness check (`nomad doctor` latest-release line) and
|
|
401
|
+
`nomad doctor --check-schema`. curl is tried first and wget is the fallback, so either one works.
|
|
402
|
+
The checks soft-skip (no error, no exit-code change) when neither is present, so the rest of the
|
|
403
|
+
CLI works without it; `nomad doctor` shows a single "HTTP fetcher (curl or wget)" row that is OK
|
|
404
|
+
when either is installed and warns only when both are absent.
|
|
403
405
|
|
|
404
406
|
## Setup
|
|
405
407
|
|
|
@@ -545,31 +547,31 @@ to this host.
|
|
|
545
547
|
|
|
546
548
|
## Commands
|
|
547
549
|
|
|
548
|
-
| Command | Description
|
|
549
|
-
| -------------------------------- |
|
|
550
|
-
| `nomad init` | Create a private GitHub repo via `gh`, wire it as `origin`, disable Actions, scaffold `shared/`, `hosts/`, `path-map.json`, and push. Prompts for a repo name (default: `claude-nomad-config`). `gh` must be installed and authenticated; exits with FATAL otherwise. Refuses to clobber existing scaffold. See [Privacy by default](#privacy-by-default).
|
|
551
|
-
| `nomad init --repo <name>` | Non-interactive: use `<name>` as the private repo name without prompting. Useful in scripts.
|
|
552
|
-
| `nomad init --snapshot` | Overlay current host's `~/.claude/` into `shared/` and write `~/.claude/settings.json` verbatim into `hosts/<NOMAD_HOST>.json`. Originals not modified. Same auto-disable behavior as `nomad init`.
|
|
553
|
-
| `nomad init --keep-actions` | Skip the Actions-disable step. Combinable with `--snapshot` and `--repo`. Use when an org policy already governs Actions, or you intentionally want CI on the private repo.
|
|
554
|
-
| `nomad pull` | `git pull --rebase --autostash`, apply symlinks, regenerate `settings.json`, remap session paths, and pull opted-in per-project extras. Errors out if scaffold missing.
|
|
555
|
-
| `nomad pull --dry-run` | Network-aware preview: acquire lock + `git pull --rebase`, print planned changes (symlink moves, `settings.json` diff, transcript overwrites), exit without writing.
|
|
556
|
-
| `nomad diff` | Offline, lockless twin of `pull --dry-run`. No network, no lock. Works against the current local repo state.
|
|
557
|
-
| `nomad push` | Export local sessions and opted-in per-project extras to logical names, commit (`chore: sync from <NOMAD_HOST>`), push.
|
|
558
|
-
| `nomad push --dry-run` | Run pre-push safety checks (gitleaks probe, rebase, remap preview, gitlink scan, allow-list) and a read-only gitleaks leak preview over a throwaway temp copy of the sessions and extras this host would stage; skip stage, commit, and push. Exits 1 if a leak is found in the preview. Nothing is written to the sync repo.
|
|
559
|
-
| `nomad push --redact-all` | Redact all findings non-interactively (backup written first) without a TTY. Does not auto-Allow findings. After redaction re-stages and re-scans; aborts with the session-aware FATAL if any finding survives. Use this in scripts or when you are confident every finding is a real secret that should be scrubbed. See [Recovery flow: push-time interactive menu](#recovery-flow-push-time-interactive-menu).
|
|
560
|
-
| `nomad drop-session <id>` | Surgically unstage every `shared/projects/*/<id>.jsonl` and the sibling `shared/projects/*/<id>/` subagent directory from the staged tree of `~/claude-nomad/`. Idempotent; the local `~/.claude/projects/<encoded>/<id>.jsonl` and `<id>/` tree are preserved. See [Recovery flows](#recovery-flows).
|
|
561
|
-
| `nomad adopt <name>` | Back up, then move a pre-existing `~/.claude/<name>` directory into `shared/<name>`, recreate the symlink so this host keeps working, and stage the result for push. `<name>` must already be listed in `SHARED_LINKS` or in the `sharedDirs` field of `path-map.json`; adopt is a mover, not a config editor, so it never writes `path-map.json` itself.
|
|
562
|
-
| `nomad adopt <name> --dry-run` | Preview the planned backup, move, and `git add` without touching the filesystem or the git index.
|
|
563
|
-
| `nomad redact <session-id>` | Rewrite the secret span in the local source transcript for a session, backed up to `~/.cache/claude-nomad/backup/`. Refuses to touch a session that was modified recently (potential active session). Safe to re-run. See [`nomad redact <session-id>`](#nomad-redact-session-id).
|
|
564
|
-
| `nomad redact --rule <id>` | Limit redaction to findings of one gitleaks rule id only.
|
|
565
|
-
| `nomad redact --dry-run` | Show what `nomad redact` would change without writing anything.
|
|
566
|
-
| `nomad clean --backups` | Delete old backup snapshots under `~/.cache/claude-nomad/backup/`. By default removes snapshots older than 14 days; pass `--older-than <dur>` (e.g. `7d`, `24h`) to change the age, or `--keep <N>` to keep the N newest and delete the rest (the two flags cannot be combined). Always preview with `--dry-run` first. See [Pruning old backups](#pruning-old-backups).
|
|
567
|
-
| `nomad update` | Update the `nomad` CLI binary from npm (`npm update -g claude-nomad`). Does NOT pull your sync data; run `nomad pull` separately for that. See [Upgrading the CLI](#upgrading-the-cli).
|
|
568
|
-
| `nomad doctor` | Read-only health check. Each line carries a status glyph (`✓` pass, `✗` fail, `⚠︎` warn); any `✗` sets `process.exitCode = 1` (`⚠︎` does not). Includes an offline-tolerant release-version staleness check, a Hook targets check that fails (`✗`, exit 1) when `settings.json` references a hook command whose script under `~/.claude/` is missing on this host, plus
|
|
569
|
-
| `nomad doctor --resume-cmd <id>` | Print a host-local `cd ... && claude --resume <id>` line for a session (see [Cross-OS resume](#cross-os-resume)).
|
|
570
|
-
| `nomad doctor --check-shared` | Read-only gitleaks preflight: stages the session transcripts a `push` would publish into a temp tree and scans them, failing (`✗`, exit 1) per affected session with rotate-and-scrub guidance. Skips with a `⚠︎` when gitleaks is not on PATH. See [Recovery flow: gitleaks FATAL on a session JSONL](#recovery-flow-gitleaks-fatal-on-a-session-jsonl).
|
|
571
|
-
| `nomad doctor --check-schema` | Read-only: fetches the live Claude Code settings schema and lists any `~/.claude/settings.json` key absent from it (candidates for the hand-maintained `APP_ONLY_KEYS` list). Non-fatal and offline-tolerant: skips with a `⚠︎` when curl is
|
|
572
|
-
| `nomad --version` | Print the installed CLI version as bare semver to stdout; exits 0. Used by the npm-publish smoke test and useful for ad-hoc upgrade checks.
|
|
550
|
+
| Command | Description |
|
|
551
|
+
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
552
|
+
| `nomad init` | Create a private GitHub repo via `gh`, wire it as `origin`, disable Actions, scaffold `shared/`, `hosts/`, `path-map.json`, and push. Prompts for a repo name (default: `claude-nomad-config`). `gh` must be installed and authenticated; exits with FATAL otherwise. Refuses to clobber existing scaffold. See [Privacy by default](#privacy-by-default). |
|
|
553
|
+
| `nomad init --repo <name>` | Non-interactive: use `<name>` as the private repo name without prompting. Useful in scripts. |
|
|
554
|
+
| `nomad init --snapshot` | Overlay current host's `~/.claude/` into `shared/` and write `~/.claude/settings.json` verbatim into `hosts/<NOMAD_HOST>.json`. Originals not modified. Same auto-disable behavior as `nomad init`. |
|
|
555
|
+
| `nomad init --keep-actions` | Skip the Actions-disable step. Combinable with `--snapshot` and `--repo`. Use when an org policy already governs Actions, or you intentionally want CI on the private repo. |
|
|
556
|
+
| `nomad pull` | `git pull --rebase --autostash`, apply symlinks, regenerate `settings.json`, remap session paths, and pull opted-in per-project extras. Errors out if scaffold missing. |
|
|
557
|
+
| `nomad pull --dry-run` | Network-aware preview: acquire lock + `git pull --rebase`, print planned changes (symlink moves, `settings.json` diff, transcript overwrites), exit without writing. |
|
|
558
|
+
| `nomad diff` | Offline, lockless twin of `pull --dry-run`. No network, no lock. Works against the current local repo state. |
|
|
559
|
+
| `nomad push` | Export local sessions and opted-in per-project extras to logical names, commit (`chore: sync from <NOMAD_HOST>`), push. |
|
|
560
|
+
| `nomad push --dry-run` | Run pre-push safety checks (gitleaks probe, rebase, remap preview, gitlink scan, allow-list) and a read-only gitleaks leak preview over a throwaway temp copy of the sessions and extras this host would stage; skip stage, commit, and push. Exits 1 if a leak is found in the preview. Nothing is written to the sync repo. |
|
|
561
|
+
| `nomad push --redact-all` | Redact all findings non-interactively (backup written first) without a TTY. Does not auto-Allow findings. After redaction re-stages and re-scans; aborts with the session-aware FATAL if any finding survives. Use this in scripts or when you are confident every finding is a real secret that should be scrubbed. See [Recovery flow: push-time interactive menu](#recovery-flow-push-time-interactive-menu). |
|
|
562
|
+
| `nomad drop-session <id>` | Surgically unstage every `shared/projects/*/<id>.jsonl` and the sibling `shared/projects/*/<id>/` subagent directory from the staged tree of `~/claude-nomad/`. Idempotent; the local `~/.claude/projects/<encoded>/<id>.jsonl` and `<id>/` tree are preserved. See [Recovery flows](#recovery-flows). |
|
|
563
|
+
| `nomad adopt <name>` | Back up, then move a pre-existing `~/.claude/<name>` directory into `shared/<name>`, recreate the symlink so this host keeps working, and stage the result for push. `<name>` must already be listed in `SHARED_LINKS` or in the `sharedDirs` field of `path-map.json`; adopt is a mover, not a config editor, so it never writes `path-map.json` itself. |
|
|
564
|
+
| `nomad adopt <name> --dry-run` | Preview the planned backup, move, and `git add` without touching the filesystem or the git index. |
|
|
565
|
+
| `nomad redact <session-id>` | Rewrite the secret span in the local source transcript for a session, backed up to `~/.cache/claude-nomad/backup/`. Refuses to touch a session that was modified recently (potential active session). Safe to re-run. See [`nomad redact <session-id>`](#nomad-redact-session-id). |
|
|
566
|
+
| `nomad redact --rule <id>` | Limit redaction to findings of one gitleaks rule id only. |
|
|
567
|
+
| `nomad redact --dry-run` | Show what `nomad redact` would change without writing anything. |
|
|
568
|
+
| `nomad clean --backups` | Delete old backup snapshots under `~/.cache/claude-nomad/backup/`. By default removes snapshots older than 14 days; pass `--older-than <dur>` (e.g. `7d`, `24h`) to change the age, or `--keep <N>` to keep the N newest and delete the rest (the two flags cannot be combined). Always preview with `--dry-run` first. See [Pruning old backups](#pruning-old-backups). |
|
|
569
|
+
| `nomad update` | Update the `nomad` CLI binary from npm (`npm update -g claude-nomad`). Does NOT pull your sync data; run `nomad pull` separately for that. See [Upgrading the CLI](#upgrading-the-cli). |
|
|
570
|
+
| `nomad doctor` | Read-only health check. Each line carries a status glyph (`✓` pass, `✗` fail, `⚠︎` warn); any `✗` sets `process.exitCode = 1` (`⚠︎` does not). Includes an offline-tolerant release-version staleness check, a Hook targets check that fails (`✗`, exit 1) when `settings.json` references a hook command whose script under `~/.claude/` is missing on this host, plus a set of `⚠︎`-only checks: gitleaks version drift; on a private GitHub mirror, re-enabled Actions; optional-dependency presence (`gh` and the curl-or-wget HTTP fetcher); a backups-cache size/count nudge toward `nomad clean --backups`; an ESM/CommonJS hook-scope mismatch; and a Node-engine floor check. |
|
|
571
|
+
| `nomad doctor --resume-cmd <id>` | Print a host-local `cd ... && claude --resume <id>` line for a session (see [Cross-OS resume](#cross-os-resume)). |
|
|
572
|
+
| `nomad doctor --check-shared` | Read-only gitleaks preflight: stages the session transcripts a `push` would publish into a temp tree and scans them, failing (`✗`, exit 1) per affected session with rotate-and-scrub guidance. Skips with a `⚠︎` when gitleaks is not on PATH. See [Recovery flow: gitleaks FATAL on a session JSONL](#recovery-flow-gitleaks-fatal-on-a-session-jsonl). |
|
|
573
|
+
| `nomad doctor --check-schema` | Read-only: fetches the live Claude Code settings schema and lists any `~/.claude/settings.json` key absent from it (candidates for the hand-maintained `APP_ONLY_KEYS` list). Non-fatal and offline-tolerant: skips with a `⚠︎` when neither curl nor wget is available or the schema is unreachable. |
|
|
574
|
+
| `nomad --version` | Print the installed CLI version as bare semver to stdout; exits 0. Used by the npm-publish smoke test and useful for ad-hoc upgrade checks. |
|
|
573
575
|
|
|
574
576
|
The version-check emits ``⚠︎ claude-nomad: <local> -> <latest> (run `nomad update`)`` when the local
|
|
575
577
|
install is behind the latest upstream release, and `✓ claude-nomad: <local> (latest)` when current.
|
package/dist/nomad.mjs
CHANGED
|
@@ -341,7 +341,7 @@ function allSharedLinks(map) {
|
|
|
341
341
|
}
|
|
342
342
|
return [...SHARED_LINKS, ...extras];
|
|
343
343
|
}
|
|
344
|
-
var HOME, CLAUDE_HOME, BACKUP_BASE, REPO_HOME, SETTINGS_SCHEMA_URL, GITLEAKS_PINNED_VERSION, HOST, SHARED_LINKS, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
344
|
+
var HOME, CLAUDE_HOME, BACKUP_BASE, REPO_HOME, SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, HOST, SHARED_LINKS, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
345
345
|
var init_config = __esm({
|
|
346
346
|
"src/config.ts"() {
|
|
347
347
|
"use strict";
|
|
@@ -353,6 +353,7 @@ var init_config = __esm({
|
|
|
353
353
|
BACKUP_BASE = join(HOME, ".cache", "claude-nomad", "backup");
|
|
354
354
|
REPO_HOME = process.env.NOMAD_REPO || resolve(HOME, "claude-nomad");
|
|
355
355
|
SETTINGS_SCHEMA_URL = "https://json.schemastore.org/claude-code-settings.json";
|
|
356
|
+
NPM_REGISTRY_LATEST_URL = "https://registry.npmjs.org/claude-nomad/latest";
|
|
356
357
|
GITLEAKS_PINNED_VERSION = "8.30.1";
|
|
357
358
|
HOST = (process.env.NOMAD_HOST || hostname()).toLowerCase();
|
|
358
359
|
SHARED_LINKS = [
|
|
@@ -508,7 +509,7 @@ function backupBeforeWrite(absPath, ts) {
|
|
|
508
509
|
if (!existsSync(absPath)) return;
|
|
509
510
|
const rel = relative(CLAUDE_HOME, absPath);
|
|
510
511
|
if (rel.startsWith("..") || rel === "") return;
|
|
511
|
-
const backupRoot = join2(
|
|
512
|
+
const backupRoot = join2(BACKUP_BASE, ts);
|
|
512
513
|
const dst = join2(backupRoot, rel);
|
|
513
514
|
mkdirSync(dirname(dst), { recursive: true });
|
|
514
515
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
@@ -517,7 +518,7 @@ function backupRepoWrite(absPath, ts, repoHome) {
|
|
|
517
518
|
if (!existsSync(absPath)) return;
|
|
518
519
|
const rel = relative(repoHome, absPath);
|
|
519
520
|
if (rel.startsWith("..") || rel === "") return;
|
|
520
|
-
const backupRoot = join2(
|
|
521
|
+
const backupRoot = join2(BACKUP_BASE, ts, "repo");
|
|
521
522
|
const dst = join2(backupRoot, rel);
|
|
522
523
|
mkdirSync(dirname(dst), { recursive: true });
|
|
523
524
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
@@ -526,7 +527,7 @@ function backupExtrasWrite(absPath, ts, projectRoot) {
|
|
|
526
527
|
if (!existsSync(absPath)) return;
|
|
527
528
|
const rel = relative(projectRoot, absPath);
|
|
528
529
|
if (rel.startsWith("..") || rel === "") return;
|
|
529
|
-
const backupRoot = join2(
|
|
530
|
+
const backupRoot = join2(BACKUP_BASE, ts, "extras");
|
|
530
531
|
const dst = join2(backupRoot, encodePath(projectRoot), rel);
|
|
531
532
|
mkdirSync(dirname(dst), { recursive: true });
|
|
532
533
|
cpSync(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
|
|
@@ -972,8 +973,7 @@ function isValidAdoptName(name) {
|
|
|
972
973
|
return isValidSharedDir(name);
|
|
973
974
|
}
|
|
974
975
|
function performAdoptMove(name, linkPath, sharedTarget) {
|
|
975
|
-
const
|
|
976
|
-
const ts = freshBackupTs(backupBase);
|
|
976
|
+
const ts = freshBackupTs(BACKUP_BASE);
|
|
977
977
|
backupBeforeWrite(linkPath, ts);
|
|
978
978
|
cpSync2(linkPath, sharedTarget, { recursive: true, force: true, preserveTimestamps: true });
|
|
979
979
|
rmSync(linkPath, { recursive: true, force: true });
|
|
@@ -1010,8 +1010,7 @@ function cmdAdopt(name, opts = {}) {
|
|
|
1010
1010
|
process.exit(1);
|
|
1011
1011
|
}
|
|
1012
1012
|
if (dryRun) {
|
|
1013
|
-
const
|
|
1014
|
-
const ts = freshBackupTs(backupBase);
|
|
1013
|
+
const ts = freshBackupTs(BACKUP_BASE);
|
|
1015
1014
|
log(`would backup: ${linkPath} -> backup/${ts}/${name}`);
|
|
1016
1015
|
log(`would move: ${linkPath} -> shared/${name}`);
|
|
1017
1016
|
log(`would stage: shared/${name}`);
|
|
@@ -1118,7 +1117,7 @@ function sectionFailed(s) {
|
|
|
1118
1117
|
function renderSection(s) {
|
|
1119
1118
|
const header = sectionFailed(s) ? `${red(FAIL_GLYPH_BARE)} ${s.header}` : s.header;
|
|
1120
1119
|
console.log(header);
|
|
1121
|
-
const lastContent = s.items.reduce((acc, item, j) => item
|
|
1120
|
+
const lastContent = s.items.reduce((acc, item, j) => item === "" ? acc : j, -1);
|
|
1122
1121
|
for (let j = 0; j < s.items.length; j++) {
|
|
1123
1122
|
if (s.items[j] === "") console.log("");
|
|
1124
1123
|
else console.log(`${j === lastContent ? " \u2514 " : " \u251C "}${s.items[j]}`);
|
|
@@ -1532,15 +1531,36 @@ function reportBackupsCheck(section2, backupBase = BACKUP_BASE) {
|
|
|
1532
1531
|
|
|
1533
1532
|
// src/commands.doctor.check-schema.ts
|
|
1534
1533
|
init_color();
|
|
1535
|
-
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
1536
1534
|
import { existsSync as existsSync12 } from "node:fs";
|
|
1537
1535
|
import { join as join14 } from "node:path";
|
|
1538
1536
|
init_config();
|
|
1539
|
-
|
|
1537
|
+
|
|
1538
|
+
// src/http-fetch.ts
|
|
1539
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
1540
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
1541
|
+
function fetchUrl(url, run = execFileSync5) {
|
|
1540
1542
|
try {
|
|
1541
|
-
|
|
1542
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1543
|
+
return run("curl", ["-fsSL", "-m", "3", url], {
|
|
1544
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1545
|
+
timeout: FETCH_TIMEOUT_MS
|
|
1543
1546
|
}).toString();
|
|
1547
|
+
} catch {
|
|
1548
|
+
try {
|
|
1549
|
+
return run("wget", ["-qO-", "--timeout=3", "--tries=1", url], {
|
|
1550
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1551
|
+
timeout: FETCH_TIMEOUT_MS
|
|
1552
|
+
}).toString();
|
|
1553
|
+
} catch {
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// src/commands.doctor.check-schema.ts
|
|
1560
|
+
function fetchSchemaKeys() {
|
|
1561
|
+
try {
|
|
1562
|
+
const raw = fetchUrl(SETTINGS_SCHEMA_URL);
|
|
1563
|
+
if (raw === null) return null;
|
|
1544
1564
|
const parsed = JSON.parse(raw);
|
|
1545
1565
|
if (typeof parsed.properties !== "object" || parsed.properties === null) return null;
|
|
1546
1566
|
return Object.keys(parsed.properties);
|
|
@@ -1560,7 +1580,7 @@ function reportCheckSchema(section2) {
|
|
|
1560
1580
|
if (liveKeys === null) {
|
|
1561
1581
|
addItem(
|
|
1562
1582
|
section2,
|
|
1563
|
-
`${yellow(warnGlyph)} schema check skipped (offline, curl missing, or schema unreachable)`
|
|
1583
|
+
`${yellow(warnGlyph)} schema check skipped (offline, curl or wget missing, or schema unreachable)`
|
|
1564
1584
|
);
|
|
1565
1585
|
return;
|
|
1566
1586
|
}
|
|
@@ -2058,9 +2078,9 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
|
2058
2078
|
|
|
2059
2079
|
// src/commands.doctor.version.ts
|
|
2060
2080
|
init_color();
|
|
2061
|
-
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
2062
2081
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
2063
2082
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2083
|
+
init_config();
|
|
2064
2084
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
2065
2085
|
var STRICT_SEMVER_PREFIX = /^(\d+\.\d+\.\d+)(?:[-+]|$)/;
|
|
2066
2086
|
function compareSemver(a, b) {
|
|
@@ -2086,10 +2106,8 @@ function readLocalVersion() {
|
|
|
2086
2106
|
}
|
|
2087
2107
|
function fetchLatestVersion() {
|
|
2088
2108
|
try {
|
|
2089
|
-
const
|
|
2090
|
-
|
|
2091
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2092
|
-
}).toString();
|
|
2109
|
+
const raw = fetchUrl(NPM_REGISTRY_LATEST_URL);
|
|
2110
|
+
if (raw === null) return null;
|
|
2093
2111
|
const parsed = JSON.parse(raw);
|
|
2094
2112
|
if (typeof parsed.version !== "string") return null;
|
|
2095
2113
|
if (!STRICT_SEMVER.test(parsed.version)) return null;
|
|
@@ -2158,7 +2176,7 @@ function reportNodeEngineCheck(section2) {
|
|
|
2158
2176
|
|
|
2159
2177
|
// src/commands.doctor.gitleaks-version.ts
|
|
2160
2178
|
init_color();
|
|
2161
|
-
import { execFileSync as
|
|
2179
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
2162
2180
|
import { existsSync as existsSync17 } from "node:fs";
|
|
2163
2181
|
import { join as join20 } from "node:path";
|
|
2164
2182
|
init_config();
|
|
@@ -2181,7 +2199,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
2181
2199
|
return null;
|
|
2182
2200
|
}
|
|
2183
2201
|
}
|
|
2184
|
-
function reportGitleaksVersionCheck(section2, run =
|
|
2202
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync17) {
|
|
2185
2203
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
2186
2204
|
if (raw === null) return;
|
|
2187
2205
|
const local = majorMinorOf(raw);
|
|
@@ -2201,15 +2219,20 @@ function reportGitleaksVersionCheck(section2, run = execFileSync8, tomlExists =
|
|
|
2201
2219
|
|
|
2202
2220
|
// src/commands.doctor.checks.deps.ts
|
|
2203
2221
|
init_color();
|
|
2204
|
-
import { execFileSync as
|
|
2222
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
2205
2223
|
var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
|
|
2224
|
+
var PROBE_TIMEOUT_MS = 3e3;
|
|
2225
|
+
var FETCHER_LABEL = "HTTP fetcher (curl or wget)";
|
|
2206
2226
|
function parseFirstVersion(line) {
|
|
2207
2227
|
const m = VERSION_TOKEN.exec(line);
|
|
2208
2228
|
return m ? m[1] : null;
|
|
2209
2229
|
}
|
|
2210
2230
|
function probeOptionalDep(bin, run) {
|
|
2211
2231
|
try {
|
|
2212
|
-
const firstLine = run(bin, ["--version"], {
|
|
2232
|
+
const firstLine = run(bin, ["--version"], {
|
|
2233
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2234
|
+
timeout: PROBE_TIMEOUT_MS
|
|
2235
|
+
}).toString().split("\n")[0].trim();
|
|
2213
2236
|
const version = parseFirstVersion(firstLine);
|
|
2214
2237
|
return { status: "present", version };
|
|
2215
2238
|
} catch (err) {
|
|
@@ -2219,34 +2242,40 @@ function probeOptionalDep(bin, run) {
|
|
|
2219
2242
|
return { status: "present", version: null };
|
|
2220
2243
|
}
|
|
2221
2244
|
}
|
|
2222
|
-
function
|
|
2223
|
-
const
|
|
2224
|
-
|
|
2225
|
-
|
|
2245
|
+
function reportFetcherRow(section2, run) {
|
|
2246
|
+
const curl = probeOptionalDep("curl", run);
|
|
2247
|
+
const wget = probeOptionalDep("wget", run);
|
|
2248
|
+
if (curl.status === "present") {
|
|
2249
|
+
addItem(section2, `${green(okGlyph)} ${FETCHER_LABEL}: ${curl.version ?? "present"}`);
|
|
2250
|
+
} else if (wget.status === "present") {
|
|
2251
|
+
addItem(section2, `${green(okGlyph)} ${FETCHER_LABEL}: ${wget.version ?? "present"}`);
|
|
2226
2252
|
} else {
|
|
2227
2253
|
addItem(
|
|
2228
2254
|
section2,
|
|
2229
|
-
`${yellow(warnGlyph)}
|
|
2255
|
+
`${yellow(warnGlyph)} ${FETCHER_LABEL}: not installed (optional; needed for release-version staleness check + nomad doctor --check-schema)`
|
|
2230
2256
|
);
|
|
2231
2257
|
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2258
|
+
}
|
|
2259
|
+
function reportOptionalDeps(section2, run = execFileSync8) {
|
|
2260
|
+
const gh = probeOptionalDep("gh", run);
|
|
2261
|
+
if (gh.status === "present") {
|
|
2262
|
+
addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
|
|
2235
2263
|
} else {
|
|
2236
2264
|
addItem(
|
|
2237
2265
|
section2,
|
|
2238
|
-
`${yellow(warnGlyph)}
|
|
2266
|
+
`${yellow(warnGlyph)} gh: not installed (optional; needed for nomad init Actions auto-disable + mirror-Actions drift check)`
|
|
2239
2267
|
);
|
|
2240
2268
|
}
|
|
2269
|
+
reportFetcherRow(section2, run);
|
|
2241
2270
|
}
|
|
2242
2271
|
|
|
2243
2272
|
// src/commands.doctor.mirror-actions.ts
|
|
2244
2273
|
init_color();
|
|
2245
|
-
import { execFileSync as
|
|
2274
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
2246
2275
|
init_config();
|
|
2247
2276
|
|
|
2248
2277
|
// src/gh-actions.ts
|
|
2249
|
-
import { execFileSync as
|
|
2278
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
2250
2279
|
var GH_TIMEOUT_MS = 5e3;
|
|
2251
2280
|
function parseGitHubRemote(remoteUrl) {
|
|
2252
2281
|
const normalized = remoteUrl.trim().replace(/\/$/, "");
|
|
@@ -2254,7 +2283,7 @@ function parseGitHubRemote(remoteUrl) {
|
|
|
2254
2283
|
if (m === null) return null;
|
|
2255
2284
|
return { owner: m[1], repo: m[2] };
|
|
2256
2285
|
}
|
|
2257
|
-
function ghAuthStatus(run =
|
|
2286
|
+
function ghAuthStatus(run = execFileSync9) {
|
|
2258
2287
|
try {
|
|
2259
2288
|
run("gh", ["auth", "status"], {
|
|
2260
2289
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -2268,7 +2297,7 @@ function ghAuthStatus(run = execFileSync10) {
|
|
|
2268
2297
|
return "gh-probe-error";
|
|
2269
2298
|
}
|
|
2270
2299
|
}
|
|
2271
|
-
function isRepoPrivate(ref, run =
|
|
2300
|
+
function isRepoPrivate(ref, run = execFileSync9) {
|
|
2272
2301
|
const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
|
|
2273
2302
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2274
2303
|
timeout: GH_TIMEOUT_MS
|
|
@@ -2276,7 +2305,7 @@ function isRepoPrivate(ref, run = execFileSync10) {
|
|
|
2276
2305
|
const parsed = JSON.parse(out);
|
|
2277
2306
|
return parsed.isPrivate === true;
|
|
2278
2307
|
}
|
|
2279
|
-
function isActionsEnabled(ref, run =
|
|
2308
|
+
function isActionsEnabled(ref, run = execFileSync9) {
|
|
2280
2309
|
const out = run(
|
|
2281
2310
|
"gh",
|
|
2282
2311
|
["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
|
|
@@ -2284,7 +2313,7 @@ function isActionsEnabled(ref, run = execFileSync10) {
|
|
|
2284
2313
|
).toString().trim();
|
|
2285
2314
|
return out === "true";
|
|
2286
2315
|
}
|
|
2287
|
-
function disableActions(ref, run =
|
|
2316
|
+
function disableActions(ref, run = execFileSync9) {
|
|
2288
2317
|
run(
|
|
2289
2318
|
"gh",
|
|
2290
2319
|
[
|
|
@@ -2298,7 +2327,7 @@ function disableActions(ref, run = execFileSync10) {
|
|
|
2298
2327
|
{ stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
|
|
2299
2328
|
);
|
|
2300
2329
|
}
|
|
2301
|
-
function readOriginRemote(cwd, run =
|
|
2330
|
+
function readOriginRemote(cwd, run = execFileSync9) {
|
|
2302
2331
|
return run("git", ["remote", "get-url", "origin"], {
|
|
2303
2332
|
cwd,
|
|
2304
2333
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -2306,7 +2335,7 @@ function readOriginRemote(cwd, run = execFileSync10) {
|
|
|
2306
2335
|
}
|
|
2307
2336
|
|
|
2308
2337
|
// src/commands.doctor.mirror-actions.ts
|
|
2309
|
-
function reportMirrorActions(section2, run =
|
|
2338
|
+
function reportMirrorActions(section2, run = execFileSync10) {
|
|
2310
2339
|
let remote;
|
|
2311
2340
|
try {
|
|
2312
2341
|
remote = readOriginRemote(REPO_HOME, run);
|
|
@@ -2390,16 +2419,16 @@ function cmdDoctor(opts = {}) {
|
|
|
2390
2419
|
|
|
2391
2420
|
// src/commands.drop-session.ts
|
|
2392
2421
|
init_config();
|
|
2393
|
-
import { execFileSync as
|
|
2422
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
2394
2423
|
import { existsSync as existsSync20, readdirSync as readdirSync8, statSync as statSync5 } from "node:fs";
|
|
2395
2424
|
import { join as join24, relative as relative4 } from "node:path";
|
|
2396
2425
|
|
|
2397
2426
|
// src/commands.drop-session.git.ts
|
|
2398
2427
|
init_config();
|
|
2399
|
-
import { execFileSync as
|
|
2428
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
2400
2429
|
function expandStagedDir(dirRel) {
|
|
2401
2430
|
try {
|
|
2402
|
-
const out =
|
|
2431
|
+
const out = execFileSync11("git", ["ls-files", "-z", "--", dirRel], {
|
|
2403
2432
|
cwd: REPO_HOME,
|
|
2404
2433
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2405
2434
|
});
|
|
@@ -2410,7 +2439,7 @@ function expandStagedDir(dirRel) {
|
|
|
2410
2439
|
}
|
|
2411
2440
|
function isTrackedInHead(rel) {
|
|
2412
2441
|
try {
|
|
2413
|
-
|
|
2442
|
+
execFileSync11("git", ["cat-file", "-e", `HEAD:${rel}`], {
|
|
2414
2443
|
cwd: REPO_HOME,
|
|
2415
2444
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2416
2445
|
});
|
|
@@ -2421,7 +2450,7 @@ function isTrackedInHead(rel) {
|
|
|
2421
2450
|
}
|
|
2422
2451
|
function isInIndex(rel) {
|
|
2423
2452
|
try {
|
|
2424
|
-
const out =
|
|
2453
|
+
const out = execFileSync11("git", ["ls-files", "--", rel], {
|
|
2425
2454
|
cwd: REPO_HOME,
|
|
2426
2455
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2427
2456
|
});
|
|
@@ -2635,12 +2664,12 @@ function unstageOne(rel) {
|
|
|
2635
2664
|
}
|
|
2636
2665
|
try {
|
|
2637
2666
|
if (isTrackedInHead(rel)) {
|
|
2638
|
-
|
|
2667
|
+
execFileSync12("git", ["restore", "--staged", "--worktree", "--", rel], {
|
|
2639
2668
|
cwd: REPO_HOME,
|
|
2640
2669
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2641
2670
|
});
|
|
2642
2671
|
} else {
|
|
2643
|
-
|
|
2672
|
+
execFileSync12("git", ["rm", "--cached", "-f", "--", rel], {
|
|
2644
2673
|
cwd: REPO_HOME,
|
|
2645
2674
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2646
2675
|
});
|
|
@@ -2785,8 +2814,7 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
2785
2814
|
);
|
|
2786
2815
|
return;
|
|
2787
2816
|
}
|
|
2788
|
-
const
|
|
2789
|
-
const ts = freshBackupTs(backupBase);
|
|
2817
|
+
const ts = freshBackupTs(BACKUP_BASE);
|
|
2790
2818
|
backupBeforeWrite(localPath, ts);
|
|
2791
2819
|
const original = readFileSync8(localPath, "utf8");
|
|
2792
2820
|
const redacted = applyRedactions(original, findings);
|
|
@@ -2906,10 +2934,10 @@ import { join as join29 } from "node:path";
|
|
|
2906
2934
|
|
|
2907
2935
|
// src/extras-sync.diff.ts
|
|
2908
2936
|
init_utils();
|
|
2909
|
-
import { execFileSync as
|
|
2937
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
2910
2938
|
function listDivergingFiles(a, b) {
|
|
2911
2939
|
try {
|
|
2912
|
-
const stdout =
|
|
2940
|
+
const stdout = execFileSync13("git", ["diff", "--no-index", "--name-only", a, b], {
|
|
2913
2941
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2914
2942
|
}).toString();
|
|
2915
2943
|
return stdout.split("\n").filter((line) => line.length > 0);
|
|
@@ -3062,7 +3090,7 @@ function divergenceCheckExtras(ts) {
|
|
|
3062
3090
|
const v = loadValidatedExtras({});
|
|
3063
3091
|
if (v === null) return;
|
|
3064
3092
|
const counts = { unmapped: 0, skipped: 0 };
|
|
3065
|
-
const backupRoot = join29(
|
|
3093
|
+
const backupRoot = join29(BACKUP_BASE, ts, "extras");
|
|
3066
3094
|
for (const { logical, localRoot, dirname: dirname4 } of eachExtrasTarget(v, counts)) {
|
|
3067
3095
|
const local = join29(localRoot, dirname4);
|
|
3068
3096
|
const repo = join29(REPO_HOME, "shared", "extras", logical, dirname4);
|
|
@@ -4027,9 +4055,9 @@ async function cmdPush(opts = {}) {
|
|
|
4027
4055
|
}
|
|
4028
4056
|
|
|
4029
4057
|
// src/commands.update.ts
|
|
4030
|
-
import { execFileSync as
|
|
4058
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
4031
4059
|
init_utils();
|
|
4032
|
-
function cmdUpdate(run =
|
|
4060
|
+
function cmdUpdate(run = execFileSync14) {
|
|
4033
4061
|
try {
|
|
4034
4062
|
run("npm", ["update", "-g", "claude-nomad"], { stdio: "inherit" });
|
|
4035
4063
|
} catch (err) {
|
|
@@ -4076,14 +4104,14 @@ import { join as join39 } from "node:path";
|
|
|
4076
4104
|
|
|
4077
4105
|
// src/init.gh-onboard.ts
|
|
4078
4106
|
init_config();
|
|
4079
|
-
import { execFileSync as
|
|
4107
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
4080
4108
|
init_utils();
|
|
4081
4109
|
var DEFAULT_REPO_NAME = "claude-nomad-config";
|
|
4082
4110
|
function isValidRepoName(name) {
|
|
4083
4111
|
return /^[A-Za-z0-9._-]{1,100}$/.test(name);
|
|
4084
4112
|
}
|
|
4085
4113
|
var GH_NETWORK_TIMEOUT_MS = 3e4;
|
|
4086
|
-
function ensureOriginRepo(repoName, run =
|
|
4114
|
+
function ensureOriginRepo(repoName, run = execFileSync15) {
|
|
4087
4115
|
if (!isValidRepoName(repoName)) {
|
|
4088
4116
|
die(
|
|
4089
4117
|
`invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
|
|
@@ -4436,7 +4464,7 @@ function parseCleanArgs(argv) {
|
|
|
4436
4464
|
// package.json
|
|
4437
4465
|
var package_default = {
|
|
4438
4466
|
name: "claude-nomad",
|
|
4439
|
-
version: "0.
|
|
4467
|
+
version: "0.36.0",
|
|
4440
4468
|
type: "module",
|
|
4441
4469
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
4442
4470
|
keywords: [
|
package/package.json
CHANGED