claude-nomad 0.25.3 → 0.25.4

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +71 -65
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.25.4](https://github.com/funkadelic/claude-nomad/compare/v0.25.3...v0.25.4) (2026-05-27)
4
+
5
+
6
+ ### Documentation
7
+
8
+ * **readme:** clarify setup docs, reduce jargon, add $ command prompts ([#147](https://github.com/funkadelic/claude-nomad/issues/147)) ([d590833](https://github.com/funkadelic/claude-nomad/commit/d590833e9a2cc1503651b2a4468ca6e8269fd57c))
9
+
3
10
  ## [0.25.3](https://github.com/funkadelic/claude-nomad/compare/v0.25.2...v0.25.3) (2026-05-26)
4
11
 
5
12
 
package/README.md CHANGED
@@ -50,12 +50,12 @@ For anyone running Claude Code on more than one machine: a laptop and a desktop,
50
50
  If you already have a private claude-nomad mirror (see [Setup](#setup) for the one-time bootstrap), adding a new host is three steps:
51
51
 
52
52
  ```bash
53
- npm i -g claude-nomad
53
+ $ npm i -g claude-nomad
54
54
  ```
55
55
 
56
56
  ```bash
57
57
  # Clone your private mirror so nomad has a repo to sync into.
58
- git clone git@github.com:<your-username>/claude-nomad.git ~/claude-nomad
58
+ $ git clone git@github.com:<your-username>/claude-nomad.git ~/claude-nomad
59
59
 
60
60
  # Add to ~/.zshrc or ~/.bashrc:
61
61
  export NOMAD_HOST=<your-host-label>
@@ -68,9 +68,9 @@ export NOMAD_HOST=<your-host-label>
68
68
  Then the everyday loop:
69
69
 
70
70
  ```bash
71
- nomad doctor # confirm setup
72
- nomad pull # apply config to ~/.claude/
73
- nomad push # publish local changes (sessions, settings)
71
+ $ nomad doctor # confirm setup
72
+ $ nomad pull # apply config to ~/.claude/
73
+ $ nomad push # publish local changes (sessions, settings)
74
74
  ```
75
75
 
76
76
  First-host bootstrap and the safe-migration sequence for a populated `~/.claude/` are in [Setup](#setup) and [Migrating an existing ~/.claude/](#migrating-an-existing-claude).
@@ -150,14 +150,14 @@ The hard problem: Claude Code stores sessions in `~/.claude/projects/<encoded-pa
150
150
  ```json
151
151
  {
152
152
  "projects": {
153
- "ha-acwd": {
154
- "<your-mac>": "/Users/you/code/ha-acwd",
155
- "<your-wsl-host>": "/home/you/code/ha-acwd",
153
+ "my-example-repo": {
154
+ "<your-mac>": "/Users/you/code/my-example-repo",
155
+ "<your-wsl-host>": "/home/you/code/my-example-repo",
156
156
  "<your-nuc>": "TBD"
157
157
  }
158
158
  },
159
159
  "extras": {
160
- "ha-acwd": [".planning", "CLAUDE.md"]
160
+ "my-example-repo": [".planning", "CLAUDE.md"]
161
161
  }
162
162
  }
163
163
  ```
@@ -167,9 +167,13 @@ The hard problem: Claude Code stores sessions in `~/.claude/projects/<encoded-pa
167
167
 
168
168
  Use the literal string `"TBD"` for hosts you haven't onboarded yet; `remapPull` skips TBD entries cleanly instead of creating an orphan `~/.claude/projects/TBD/`. Replace each `"TBD"` with the real path when you bring up that host.
169
169
 
170
- On `push`, sessions in `~/.claude/projects/-Users-you-code-ha-acwd/` get copied to `shared/projects/ha-acwd/`. On `pull` on another machine, they get copied to that host's encoded path. `claude --resume` then finds them (see [What does NOT sync (deliberate trade-offs)](#what-does-not-sync-deliberate-trade-offs) for the cross-OS cwd-binding gotcha).
170
+ On `push`, sessions in `~/.claude/projects/-Users-you-code-my-example-repo/` get copied to `shared/projects/my-example-repo/`. On `pull` on another machine, they get copied to that host's encoded path. `claude --resume` then finds them (see [What does NOT sync (deliberate trade-offs)](#what-does-not-sync-deliberate-trade-offs) for the cross-OS cwd-binding gotcha).
171
171
 
172
- The `extras` block is additive and back-compatible: legacy `path-map.json` files without it continue to work unchanged. Each value is an array of directory or root-file names (e.g. `.planning`, `CLAUDE.md`) validated against `SUPPORTED_EXTRAS` in `src/config.ts`; values outside the whitelist are skipped with a log line so an unrecognized name cannot widen the sync surface. On `push`, opted-in content at `<localRoot>/<name>` (a directory subtree or a single file) is copied to `shared/extras/<logical>/<name>` and inherits the staged-tree gitleaks scan. On `pull`, the reverse copy runs after `git pull --rebase`; just before it overwrites your working tree, a divergence check compares the incoming content against your local copy and emits a per-file WARN naming the diverging files. The existing local content is backed up to `~/.cache/claude-nomad/backup/<ts>/extras/<encoded-localRoot>/<rel>/` before the pull copy lands (`<encoded-localRoot>` is the `localRoot` with `/` rewritten as `-`, so two opted-in projects with the same relative extras path do not collide in one backup run).
172
+ The `extras` block is additive and back-compatible: legacy `path-map.json` files without it keep working unchanged. Each value is an array of directory or root-file names (e.g. `.planning`, `CLAUDE.md`) checked against `SUPPORTED_EXTRAS` in `src/config.ts`; anything outside that whitelist is skipped with a log line, so an unrecognized name cannot widen the sync surface.
173
+
174
+ On `push`, opted-in content at `<localRoot>/<name>` (a directory subtree or a single file) is copied to `shared/extras/<logical>/<name>` and goes through the same staged-tree gitleaks scan as everything else. On `pull`, the reverse copy runs after `git pull --rebase`, and just before it overwrites your working tree a divergence check compares the incoming content against your local copy and prints a per-file WARN naming anything that differs.
175
+
176
+ Your existing local content is backed up under `~/.cache/claude-nomad/backup/<ts>/extras/` before the pull copy lands, so an unexpected overwrite is always recoverable.
173
177
 
174
178
  ## Per-host overrides
175
179
 
@@ -206,7 +210,7 @@ Read these before adopting so you opt in with eyes open.
206
210
  - **Manual push/pull.** No file watcher. Shell hooks recommended.
207
211
  - **OAuth doesn't sync.** You'll log in once per host. Intentional.
208
212
  - **Only sessions in `path-map.json` are remapped.** Drive-by sessions on un-mapped paths are left alone.
209
- - **Extras are opt-in and whitelisted.** Projects without an `extras` entry in `path-map.json` are unaffected. Names (a directory or a single root file) outside `SUPPORTED_EXTRAS` are skipped with a `skip ... not in SUPPORTED_EXTRAS` log line so an unrecognized name cannot widen the sync surface. Unsafe path-map values (path-traversal in `logical` keys, non-absolute or unnormalized `localRoot` values) FATAL before any filesystem mutation via `assertSafeLogical` / `assertSafeLocalRoot` in `src/extras-sync.ts`.
213
+ - **Extras are opt-in and whitelisted.** Projects without an `extras` entry in `path-map.json` are unaffected. Names (a directory or a single root file) outside `SUPPORTED_EXTRAS` are skipped with a `skip ... not in SUPPORTED_EXTRAS` log line so an unrecognized name cannot widen the sync surface. Unsafe path-map values (path-traversal in `logical` keys, non-absolute or unnormalized `localRoot` values) abort the run before any file is touched, so a malformed entry fails loudly instead of corrupting state.
210
214
  - **Cross-OS `claude --resume` cwd binding.** Sessions embed the cwd where they were created, so the picker's `cd ... && claude --resume <id>` line fails on a different host. Use `nomad doctor --resume-cmd <id>` for a host-local equivalent (see [Cross-OS resume](#cross-os-resume)). The sidecar approach preserves transcript byte-equality.
211
215
  - **Empty directories don't survive sync.** Git doesn't track empty dirs; `nomad doctor` reports them as `missing` (benign). Drop a `.gitkeep` to force materialization.
212
216
 
@@ -215,7 +219,7 @@ Read these before adopting so you opt in with eyes open.
215
219
  - Node.js 22.22.1 or newer (24 LTS recommended; the npm `engines` field declares the 22.22.1 floor and surfaces a warning on older runtimes - npm only blocks the install when `engine-strict=true` is configured)
216
220
  - `tsx` (ships as a runtime dependency of the published package; no separate global install required)
217
221
  - Git
218
- - [`gitleaks`](https://github.com/gitleaks/gitleaks) (required for `nomad push`, which fail-fasts if it is not on PATH; `nomad doctor` also checks it against the pinned 8.30.x and warns when it is absent or mismatched)
222
+ - [`gitleaks`](https://github.com/gitleaks/gitleaks) (required for `nomad push`, which exits with an error if it is not on PATH; `nomad doctor` also checks it against the pinned 8.30.x and warns when it is absent or mismatched)
219
223
  - A **private** GitHub repo (or any Git remote you control)
220
224
 
221
225
  **Optional:**
@@ -229,10 +233,10 @@ Read these before adopting so you opt in with eyes open.
229
233
 
230
234
  ### Privacy by default
231
235
 
232
- Your private mirror has two layers of defense against leaking transcripts via CI, both applied automatically:
236
+ When you mirror-push the tool into your repo, you copy its automation along with its code: the `.github/workflows/` directory holds the public project's own CI (running its test suite, linting, secret and code scanning, release tagging, and npm publishing). That CI is meant for the public project, not your config; if it ran on your private mirror, a job could echo transcript contents into build logs. So your mirror gets two independent layers of defense against that, both applied automatically:
233
237
 
234
- 1. Every workflow under `.github/workflows/` is gated on `${{ !github.event.repository.private }}`, so they skip on private repos and only run on public ones.
235
- 2. `nomad init` calls `gh api -X PUT repos/<owner>/<repo>/actions/permissions -F enabled=false` on first run, turning Actions off at the repo level. Requires `gh` CLI authed; if missing or unauthed, init logs a manual fallback tip and continues.
238
+ 1. **The workflows are written to skip private repos.** Each one carries the run condition `${{ !github.event.repository.private }}` (in plain terms: "run only when this repo is NOT private"), so even with Actions enabled the jobs do not run on your mirror.
239
+ 2. **`nomad init` turns Actions off for the whole repo** on first run, via the GitHub API call `gh api -X PUT repos/<owner>/<repo>/actions/permissions -F enabled=false`. This needs the `gh` CLI installed and authed; if it is missing or unauthed, init logs a manual fallback tip and continues.
236
240
 
237
241
  Pass `--keep-actions` to either form of init to skip step 2 (for example, when your org already enforces an Actions policy upstream).
238
242
 
@@ -241,34 +245,32 @@ Pass `--keep-actions` to either form of init to skip step 2 (for example, when y
241
245
 
242
246
  ### Bootstrap
243
247
 
244
- Steps 1-2 are once-ever across all hosts; step 3 repeats per host:
248
+ Steps 1-2 are once-ever across all hosts; steps 3-4 repeat on every host:
245
249
 
246
250
  ```bash
247
251
  # 1. Create the private repo (or use the GitHub UI). Once, ever.
248
- gh repo create <your-username>/claude-nomad --private
252
+ $ gh repo create <your-username>/claude-nomad --private
249
253
 
250
- # 2. Mirror the public tool into it. This severs the fork relationship,
251
- # so your repo is independent of upstream. Once, ever.
252
- git clone --bare git@github.com:funkadelic/claude-nomad.git /tmp/cn.git
253
- cd /tmp/cn.git
254
- git push --mirror git@github.com:<your-username>/claude-nomad.git
255
- cd .. && rm -rf /tmp/cn.git
254
+ # 2. Copy the public tool into your private repo. A bare clone followed by a
255
+ # mirror push makes a complete, independent copy (every branch and tag) with
256
+ # no fork link back to upstream, which is what lets you keep it private. Once, ever.
257
+ $ git clone --bare git@github.com:funkadelic/claude-nomad.git /tmp/cn.git # download a full copy
258
+ $ cd /tmp/cn.git
259
+ $ git push --mirror git@github.com:<your-username>/claude-nomad.git # upload it to your private repo
260
+ $ cd .. && rm -rf /tmp/cn.git
256
261
 
257
262
  # 3. Install the CLI globally and clone your private copy. Repeat on every host.
258
- npm i -g claude-nomad
259
- git clone git@github.com:<your-username>/claude-nomad.git ~/claude-nomad
260
- ```
261
-
262
- `npm i -g claude-nomad` puts a `nomad` binary on your PATH. The bin shim is the existing `src/nomad.ts` entrypoint resolved through tsx (a runtime dependency); no compile step. The npm `engines` field declares the 22.22.1 floor and surfaces a warning on older runtimes; npm only blocks the install when `engine-strict=true` is configured.
263
-
264
- On every additional host you only repeat step 3 (the global install is per-host; your private repo already exists on the remote from step 2).
265
-
266
- Add to `~/.zshrc` or `~/.bashrc`:
263
+ $ npm i -g claude-nomad
264
+ $ git clone git@github.com:<your-username>/claude-nomad.git ~/claude-nomad
267
265
 
268
- ```bash
266
+ # 4. Add a stable host label to your shell rc (~/.zshrc or ~/.bashrc). Repeat on every host.
269
267
  export NOMAD_HOST=<your-host-label> # any short, stable label; nomad reads this instead of os.hostname()
270
268
  ```
271
269
 
270
+ `npm i -g claude-nomad` puts a `nomad` binary on your PATH. The bin shim is the existing `src/nomad.ts` entrypoint resolved through tsx (a runtime dependency); no compile step. (The Node version floor and the `engine-strict` caveat are in [Requirements](#requirements).)
271
+
272
+ On every additional host you repeat only steps 3-4; steps 1-2 are already done, since your private repo lives on the remote from step 2.
273
+
272
274
  `NOMAD_HOST` overrides `os.hostname()`, which returns noisy values like `WINDOWS-I5NT6OH` on WSL or `<name>.local` on macOS. Pick a clean label per machine (e.g., `wsl-laptop`, `macbook`, `homelab-nuc`). `nomad doctor` reports the resolved host so you can confirm.
273
275
 
274
276
  ### Initialize the repo layout
@@ -277,15 +279,15 @@ First host only; subsequent hosts just clone and `nomad pull`. Both forms below
277
279
 
278
280
  ```bash
279
281
  # Fresh start: scaffold an empty shared/, hosts/, path-map.json skeleton.
280
- nomad init
282
+ $ nomad init
281
283
 
282
284
  # Already have ~/.claude/ populated on this host? Capture it as the
283
285
  # starting point. Stages shared/ and writes hosts/<NOMAD_HOST>.json from
284
286
  # your current ~/.claude/settings.json. Does NOT touch the originals.
285
- nomad init --snapshot
287
+ $ nomad init --snapshot
286
288
 
287
289
  # Either form accepts --keep-actions to skip the auto-disable.
288
- nomad init --keep-actions
290
+ $ nomad init --keep-actions
289
291
  ```
290
292
 
291
293
  `nomad init` refuses to clobber existing scaffold artifacts, so re-running on a populated repo is a safe no-op (it errors out naming the offender). `nomad pull` against an unscaffolded repo fails fast with `FATAL: repo not initialized; run 'nomad init' to scaffold` instead of silently leaving a half-state.
@@ -293,11 +295,11 @@ nomad init --keep-actions
293
295
  Edit `path-map.json` to add your logical projects (see [Path remapping](#path-remapping)), then:
294
296
 
295
297
  ```bash
296
- nomad doctor # read-only state check; reports host, repo state, every check as ✓ (pass) / ✗ (fail) / ⚠︎ (warn)
297
- nomad doctor --check-shared # read-only gitleaks preflight over the session transcripts a push would stage
298
- nomad diff # preview what nomad pull would change on this host; no lock, no network, no mutation
299
- nomad push # send current state to the private remote
300
- nomad pull # apply on another host (or this one after a remote update)
298
+ $ nomad doctor # read-only state check; reports host, repo state, every check as ✓ (pass) / ✗ (fail) / ⚠︎ (warn)
299
+ $ nomad doctor --check-shared # read-only gitleaks preflight over the session transcripts a push would stage
300
+ $ nomad diff # preview what nomad pull would change on this host; no lock, no network, no mutation
301
+ $ nomad push # send current state to the private remote
302
+ $ nomad pull # apply on another host (or this one after a remote update)
301
303
  ```
302
304
 
303
305
  `nomad pull --dry-run` is the network-aware twin of `nomad diff`: it acquires the lock and runs `git pull` so you see what the next real pull would do given the latest remote, then exits without mutating.
@@ -310,11 +312,11 @@ If a host already has real files at `~/.claude/{CLAUDE.md, agents/, skills/, ...
310
312
 
311
313
  ```bash
312
314
  # From the host that has the canonical config (the originals are not modified):
313
- nomad init --snapshot # stages shared/ and writes hosts/<NOMAD_HOST>.json from ~/.claude/
314
- nomad push # publish the captured state to the private remote
315
+ $ nomad init --snapshot # stages shared/ and writes hosts/<NOMAD_HOST>.json from ~/.claude/
316
+ $ nomad push # publish the captured state to the private remote
315
317
 
316
318
  # Then, on this host or any other host that has the private remote checked out:
317
- nomad pull # materializes the symlinks
319
+ $ nomad pull # materializes the symlinks
318
320
  ```
319
321
 
320
322
  `nomad pull` is what actually migrates the host. `applySharedLinks` runs a two-pass scan: any pre-existing non-symlink at a `SHARED_LINKS` path whose counterpart exists under `shared/` is renamed into `~/.cache/claude-nomad/backup/<ts>/` first, then the symlink is created. Your originals are preserved under that timestamped backup directory, not deleted. Paths whose `shared/<name>` is absent from the remote are left untouched, so a partial publish does not delete data on the destination host.
@@ -325,38 +327,42 @@ Prefer an explicit tarball rollback and a confirmation prompt before any deletio
325
327
 
326
328
  ## Upgrading the tool
327
329
 
328
- Two upgrade paths, depending on how you installed:
330
+ Two different things can fall behind, and they update independently:
329
331
 
330
- - **Global install (`npm i -g claude-nomad`):** `npm update -g claude-nomad`. This refreshes only the `nomad` CLI binary on PATH; your private `~/claude-nomad/` repo is untouched.
331
- - **Source-checkout developer workflow:** `nomad update` (run from `~/claude-nomad/`). Topology-aware: detects vanilla vs fork remotes, pulls or merges upstream, and re-runs `npm install` when `package-lock.json` shifted.
332
+ - **The `nomad` CLI binary** (what runs when you type `nomad`). If you installed it with `npm i -g claude-nomad`, upgrade it with `npm update -g claude-nomad`. This refreshes only the binary on your PATH; it does not touch anything inside your private `~/claude-nomad/` repo.
333
+ - **The synced tool files inside your private repo:** `src/`, `.gitleaks.toml` (the secret-scan allowlist), and the `.github/workflows/` privacy gating. These were copied from the public repo at bootstrap and then froze, so `npm update -g` does not refresh them. `nomad update`, run from `~/claude-nomad/`, is what pulls newer versions of these files in. Topology-aware: detects vanilla vs fork remotes, pulls or merges upstream, and re-runs `npm install` when `package-lock.json` shifted.
334
+
335
+ Most people who followed the Quickstart need both: `npm update -g` for the binary, and an occasional `nomad update` for the repo files (notably to receive `.gitleaks.toml` allowlist changes and any update to the privacy gating itself). The mirror-push bootstrap leaves your repo with `origin` on your private mirror and no `upstream` remote; that becomes the "fork" topology `nomad update` expects once you add the upstream remote (the one-time `git remote add upstream ...` step is below).
332
336
 
333
337
  Your private repo is not a fork, so GitHub's "Sync fork" UI doesn't apply. The shortcut on a source-checkout host is:
334
338
 
335
339
  ```bash
336
- cd ~/claude-nomad
337
- nomad update
340
+ $ cd ~/claude-nomad
341
+ $ nomad update
338
342
  ```
339
343
 
340
- `nomad update` (see `cmdUpdate` in `src/commands.update.ts`) detects which layout your `~/claude-nomad/` uses and does the right thing:
344
+ `nomad update` detects which layout your `~/claude-nomad/` uses and does the right thing:
341
345
 
342
346
  - **vanilla** (`origin` points at the public repo): `git pull --ff-only origin main`.
343
347
  - **fork** (`upstream` points at the public repo, `origin` points at your private mirror): `git fetch upstream`, then (before merging) commit any whitelisted `shared/extras/` content that is still untracked locally so an overlap with upstream becomes a normal file merge instead of an untracked-overwrite abort, `git merge upstream/main`, then prompt before pushing the merge to `origin/main`. Pass `--push-origin` to skip the prompt. When the merge is a no-op (HEAD unchanged, nothing new to push) the prompt is skipped entirely and `nomad update` logs `already in sync with origin/main`.
344
348
 
345
- Pre-flight checks run before any mutation: `REPO_HOME` exists, topology resolves to `vanilla` or `fork`, current branch is `main`, working tree is clean per `git status --porcelain -z` (override with `--force`), and `--push-origin` is rejected on vanilla topology. After the merge or pull, `nomad update` re-runs `npm install` only when `package-lock.json` actually shifted, commits the regenerated `package-lock.json` (fork topology) if the reinstall changed it, then invokes `nomad doctor`. The trailing version-check is non-fatal: `✓` when local matches the latest release, `⚠︎` when behind, an informational `ℹ︎ ... ahead of latest release` line when ahead (e.g. a `-dev` build between releases), and silent on network failures.
349
+ Pre-flight checks run before any mutation: `REPO_HOME` exists, the topology resolves to `vanilla` or `fork`, the current branch is `main`, the working tree is clean (override with `--force`), and `--push-origin` is rejected on vanilla topology.
350
+
351
+ After the merge or pull, `nomad update` re-runs `npm install` only when `package-lock.json` actually shifted, commits the regenerated `package-lock.json` (fork topology) if the reinstall changed it, then invokes `nomad doctor`. The trailing version-check is non-fatal: `✓` when local matches the latest release, `⚠︎` when behind, an informational `ℹ︎ ... ahead of latest release` line when ahead (e.g. a `-dev` build between releases), and silent on network failures.
346
352
 
347
353
  Common cases:
348
354
 
349
355
  ```bash
350
- nomad update # the usual path
351
- nomad update --dry-run # detect topology + pre-flight, print would-be git commands only
352
- nomad update --push-origin # fork topology: push merge to origin/main without prompting
353
- nomad update --force # proceed past a dirty working tree
356
+ $ nomad update # the usual path
357
+ $ nomad update --dry-run # detect topology + pre-flight, print would-be git commands only
358
+ $ nomad update --push-origin # fork topology: push merge to origin/main without prompting
359
+ $ nomad update --force # proceed past a dirty working tree
354
360
  ```
355
361
 
356
362
  One-time setup if you're running a fork layout and don't have the `upstream` remote yet:
357
363
 
358
364
  ```bash
359
- git remote add upstream git@github.com:funkadelic/claude-nomad.git
365
+ $ git remote add upstream git@github.com:funkadelic/claude-nomad.git
360
366
  ```
361
367
 
362
368
  To pin to a specific release (`vX.Y.Z`, tagged by release-please) instead of tracking `main`, fetch tags from the public repo and check out the tag (detached HEAD). On vanilla topology that's `origin`; on fork topology that's `upstream` (the private mirror at `origin` does not accumulate upstream release tags). Example: `git fetch upstream --tags && git switch --detach vX.Y.Z` (substitute `origin` for vanilla; use `git checkout vX.Y.Z` on older Git).
@@ -370,7 +376,7 @@ If you installed an earlier version via `./install.sh` and a shell alias (the pr
370
376
  | `nomad init` | Scaffold empty `shared/`, `hosts/`, `path-map.json` on a fresh clone. Refuses to clobber existing scaffold. Auto-disables Actions on a detected private GitHub mirror (see [Privacy by default](#privacy-by-default)). |
371
377
  | `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`. |
372
378
  | `nomad init --keep-actions` | Skip the auto-disable. Combinable with `--snapshot`. Use when an upstream org policy already governs Actions, or you intentionally want CI on the private mirror. |
373
- | `nomad pull` | `git pull --rebase --autostash`, apply symlinks, regenerate `settings.json`, remap session paths, and pull opted-in per-project extras. FATAL if scaffold missing. |
379
+ | `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. |
374
380
  | `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. |
375
381
  | `nomad diff` | Offline, lockless twin of `pull --dry-run`. No network, no lock. Works against the current local repo state. |
376
382
  | `nomad push` | Export local sessions and opted-in per-project extras to logical names, commit (`chore: sync from <NOMAD_HOST>`), push. |
@@ -403,7 +409,7 @@ Every `nomad pull`, `nomad push`, and `nomad diff` run ends with a single `summa
403
409
  Surgically unstages every `shared/projects/*/<id>.jsonl` plus the sibling `shared/projects/*/<id>/` subagent directory (whose nested transcripts are keyed by the same session id) from the staged tree of `~/claude-nomad/`. The local `~/.claude/projects/<encoded>/<id>.jsonl` and the local `<id>/` tree are never touched.
404
410
 
405
411
  ```bash
406
- nomad drop-session <id>
412
+ $ nomad drop-session <id>
407
413
  ```
408
414
 
409
415
  Single positional id (the session filename minus `.jsonl`). Anything else (missing id, leading dash, extra arg) exits 1 with a `usage:` line.
@@ -419,7 +425,7 @@ What it does NOT do: touch the local `~/.claude/projects/<encoded>/<id>.jsonl` f
419
425
 
420
426
  ### Recovery flow: gitleaks FATAL on a session JSONL
421
427
 
422
- `nomad push` runs `gitleaks protect --staged` before commit. To catch the same findings before you push (and without mutating anything), run the read-only preflight `nomad doctor --check-shared`, which stages and scans the exact transcripts a push would publish. When findings live in a session transcript, the push FATAL names every affected session id and the recovery command:
428
+ `nomad push` runs `gitleaks protect --staged` before commit. To catch the same findings before you push (and without mutating anything), run the read-only preflight `nomad doctor --check-shared`, which stages and scans the exact transcripts a push would publish. When findings live in a session transcript, the push aborts and names every affected session id and the recovery command:
423
429
 
424
430
  ```text
425
431
  ✗ gitleaks detected secrets in 1 session transcript(s).
@@ -474,16 +480,16 @@ Claude Code embeds the original `cwd` in each session transcript. When you resum
474
480
  Run this instead:
475
481
 
476
482
  ```bash
477
- eval "$(nomad doctor --resume-cmd <session-id>)"
483
+ $ eval "$(nomad doctor --resume-cmd <session-id>)"
478
484
  ```
479
485
 
480
486
  Or pipe through bash:
481
487
 
482
488
  ```bash
483
- nomad doctor --resume-cmd <session-id> | bash
489
+ $ nomad doctor --resume-cmd <session-id> | bash
484
490
  ```
485
491
 
486
- `nomad doctor --resume-cmd <id>` reads the `.jsonl`'s recorded `cwd`, reverse-looks up the logical project in `path-map.json`, finds your current host's abspath for that logical, and prints `cd <local-abspath> && claude --resume <id>` to stdout. The command is read-only: it never modifies any transcript byte (Phase 1's sha256 byte-equality invariant is preserved).
492
+ `nomad doctor --resume-cmd <id>` reads the `.jsonl`'s recorded `cwd`, reverse-looks up the logical project in `path-map.json`, finds your current host's abspath for that logical, and prints `cd <local-abspath> && claude --resume <id>` to stdout. The command is read-only: it never modifies any transcript byte.
487
493
 
488
494
  If the session isn't mapped on this host, you'll see:
489
495
 
@@ -496,6 +502,6 @@ Other fatal surfaces: missing `~/.claude/projects/`, session id absent from ever
496
502
  ## Run tests
497
503
 
498
504
  ```bash
499
- npm install
500
- npx vitest run
505
+ $ npm install
506
+ $ npx vitest run
501
507
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.25.3",
3
+ "version": "0.25.4",
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": [