claude-nomad 0.25.2 → 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.
- package/CHANGELOG.md +20 -0
- package/README.md +78 -66
- package/package.json +1 -1
- package/src/commands.doctor.version.ts +7 -7
- package/src/config.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
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
|
+
|
|
10
|
+
## [0.25.3](https://github.com/funkadelic/claude-nomad/compare/v0.25.2...v0.25.3) (2026-05-26)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
* **config:** add skipAutoPermissionPrompt to KNOWN_SETTINGS_KEYS ([#142](https://github.com/funkadelic/claude-nomad/issues/142)) ([09b4d7c](https://github.com/funkadelic/claude-nomad/commit/09b4d7c27d74cc922f9c516296ca6123e1ee84f5))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
|
|
20
|
+
* document runtime deps and foreground the security posture ([#145](https://github.com/funkadelic/claude-nomad/issues/145)) ([fb029ee](https://github.com/funkadelic/claude-nomad/commit/fb029eec1a6d915224a39cc2755ff76e42618365))
|
|
21
|
+
* refresh hero.svg tagline and doctor mockup ([#144](https://github.com/funkadelic/claude-nomad/issues/144)) ([58a0176](https://github.com/funkadelic/claude-nomad/commit/58a01760e8d977ce4cf46dcd3137c02f3bc6541c))
|
|
22
|
+
|
|
3
23
|
## [0.25.2](https://github.com/funkadelic/claude-nomad/compare/v0.25.1...v0.25.2) (2026-05-26)
|
|
4
24
|
|
|
5
25
|
|
package/README.md
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
**Your entire Claude Code setup, on every machine. History included,
|
|
9
|
+
**Your entire Claude Code setup, on every machine. History included, every push secret-scanned.**
|
|
10
10
|
|
|
11
11
|
Open Claude Code on a second machine and it is a blank slate: none of your custom agents, slash commands, tuned settings, or past conversations. claude-nomad keeps all of it in sync through a private Git repo you control. `nomad push` on one machine, `nomad pull` on the next, and everything is there, conversations included.
|
|
12
12
|
|
|
13
13
|
- **Resume your sessions on any machine.** Start a conversation on your desktop and pick it up on your laptop. claude-nomad remaps the file paths Claude Code embeds in every transcript, so your history follows you instead of getting stranded on the box where it started.
|
|
14
|
-
- **
|
|
14
|
+
- **Secret-scanned, private by default.** Your `~/.claude/` also holds OAuth tokens, MCP credentials, and the full text of every conversation, so claude-nomad is deliberate about what leaves your machine: credentials and ephemeral state never sync, only an explicit allow-list of paths is pushed, and everything that does go up is scanned by [gitleaks](https://github.com/gitleaks/gitleaks) before it leaves your machine; the push aborts on any hit. `nomad init` also disables Actions on your private mirror by default, so transcripts can't leak through CI logs.
|
|
15
15
|
- **One setup, every machine.** Your agents, skills, slash commands, and settings live in one place and follow you everywhere. Per-machine tweaks like model choice, MCP URLs, and env vars merge on top instead of clobbering your shared defaults.
|
|
16
16
|
|
|
17
17
|
Not dotfiles, not rsync. claude-nomad understands Claude Code's state, so your session history survives different file paths and your secrets never ride along.
|
|
@@ -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
|
-
"
|
|
154
|
-
"<your-mac>": "/Users/you/code/
|
|
155
|
-
"<your-wsl-host>": "/home/you/code/
|
|
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
|
-
"
|
|
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-
|
|
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
|
|
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)
|
|
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,18 +219,24 @@ 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
|
|
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)
|
|
218
223
|
- A **private** GitHub repo (or any Git remote you control)
|
|
219
224
|
|
|
225
|
+
**Optional:**
|
|
226
|
+
|
|
227
|
+
- `gh` (GitHub CLI), used only by `nomad init` to auto-disable Actions on the private repo; if it is missing or unauthenticated, init prints a manual fallback tip and continues
|
|
228
|
+
- `curl`, used only by the version/update check (the `nomad doctor` latest-release line and the post-`nomad update` check); it degrades silently when curl is absent or offline, so the rest of the CLI works without it
|
|
229
|
+
|
|
220
230
|
## Setup
|
|
221
231
|
|
|
222
232
|
**Why not just fork?** GitHub doesn't let you flip a public fork to private, and your config (especially session transcripts) must stay private. So the bootstrap is a one-time mirror-push into a fresh private repo, not a fork.
|
|
223
233
|
|
|
224
234
|
### Privacy by default
|
|
225
235
|
|
|
226
|
-
|
|
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:
|
|
227
237
|
|
|
228
|
-
1.
|
|
229
|
-
2.
|
|
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.
|
|
230
240
|
|
|
231
241
|
Pass `--keep-actions` to either form of init to skip step 2 (for example, when your org already enforces an Actions policy upstream).
|
|
232
242
|
|
|
@@ -235,34 +245,32 @@ Pass `--keep-actions` to either form of init to skip step 2 (for example, when y
|
|
|
235
245
|
|
|
236
246
|
### Bootstrap
|
|
237
247
|
|
|
238
|
-
Steps 1-2 are once-ever across all hosts;
|
|
248
|
+
Steps 1-2 are once-ever across all hosts; steps 3-4 repeat on every host:
|
|
239
249
|
|
|
240
250
|
```bash
|
|
241
251
|
# 1. Create the private repo (or use the GitHub UI). Once, ever.
|
|
242
|
-
gh repo create <your-username>/claude-nomad --private
|
|
252
|
+
$ gh repo create <your-username>/claude-nomad --private
|
|
243
253
|
|
|
244
|
-
# 2.
|
|
245
|
-
#
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
|
250
261
|
|
|
251
262
|
# 3. Install the CLI globally and clone your private copy. Repeat on every host.
|
|
252
|
-
npm i -g claude-nomad
|
|
253
|
-
git clone git@github.com:<your-username>/claude-nomad.git ~/claude-nomad
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
`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.
|
|
257
|
-
|
|
258
|
-
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).
|
|
263
|
+
$ npm i -g claude-nomad
|
|
264
|
+
$ git clone git@github.com:<your-username>/claude-nomad.git ~/claude-nomad
|
|
259
265
|
|
|
260
|
-
Add to
|
|
261
|
-
|
|
262
|
-
```bash
|
|
266
|
+
# 4. Add a stable host label to your shell rc (~/.zshrc or ~/.bashrc). Repeat on every host.
|
|
263
267
|
export NOMAD_HOST=<your-host-label> # any short, stable label; nomad reads this instead of os.hostname()
|
|
264
268
|
```
|
|
265
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
|
+
|
|
266
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.
|
|
267
275
|
|
|
268
276
|
### Initialize the repo layout
|
|
@@ -271,15 +279,15 @@ First host only; subsequent hosts just clone and `nomad pull`. Both forms below
|
|
|
271
279
|
|
|
272
280
|
```bash
|
|
273
281
|
# Fresh start: scaffold an empty shared/, hosts/, path-map.json skeleton.
|
|
274
|
-
nomad init
|
|
282
|
+
$ nomad init
|
|
275
283
|
|
|
276
284
|
# Already have ~/.claude/ populated on this host? Capture it as the
|
|
277
285
|
# starting point. Stages shared/ and writes hosts/<NOMAD_HOST>.json from
|
|
278
286
|
# your current ~/.claude/settings.json. Does NOT touch the originals.
|
|
279
|
-
nomad init --snapshot
|
|
287
|
+
$ nomad init --snapshot
|
|
280
288
|
|
|
281
289
|
# Either form accepts --keep-actions to skip the auto-disable.
|
|
282
|
-
nomad init --keep-actions
|
|
290
|
+
$ nomad init --keep-actions
|
|
283
291
|
```
|
|
284
292
|
|
|
285
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.
|
|
@@ -287,11 +295,11 @@ nomad init --keep-actions
|
|
|
287
295
|
Edit `path-map.json` to add your logical projects (see [Path remapping](#path-remapping)), then:
|
|
288
296
|
|
|
289
297
|
```bash
|
|
290
|
-
nomad doctor # read-only state check; reports host, repo state, every check as ✓ (pass) / ✗ (fail) / ⚠︎ (warn)
|
|
291
|
-
nomad doctor --check-shared # read-only gitleaks preflight over the session transcripts a push would stage
|
|
292
|
-
nomad diff # preview what nomad pull would change on this host; no lock, no network, no mutation
|
|
293
|
-
nomad push # send current state to the private remote
|
|
294
|
-
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)
|
|
295
303
|
```
|
|
296
304
|
|
|
297
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.
|
|
@@ -304,11 +312,11 @@ If a host already has real files at `~/.claude/{CLAUDE.md, agents/, skills/, ...
|
|
|
304
312
|
|
|
305
313
|
```bash
|
|
306
314
|
# From the host that has the canonical config (the originals are not modified):
|
|
307
|
-
nomad init --snapshot # stages shared/ and writes hosts/<NOMAD_HOST>.json from ~/.claude/
|
|
308
|
-
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
|
|
309
317
|
|
|
310
318
|
# Then, on this host or any other host that has the private remote checked out:
|
|
311
|
-
nomad pull # materializes the symlinks
|
|
319
|
+
$ nomad pull # materializes the symlinks
|
|
312
320
|
```
|
|
313
321
|
|
|
314
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.
|
|
@@ -319,38 +327,42 @@ Prefer an explicit tarball rollback and a confirmation prompt before any deletio
|
|
|
319
327
|
|
|
320
328
|
## Upgrading the tool
|
|
321
329
|
|
|
322
|
-
Two
|
|
330
|
+
Two different things can fall behind, and they update independently:
|
|
331
|
+
|
|
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.
|
|
323
334
|
|
|
324
|
-
|
|
325
|
-
- **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.
|
|
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).
|
|
326
336
|
|
|
327
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:
|
|
328
338
|
|
|
329
339
|
```bash
|
|
330
|
-
cd ~/claude-nomad
|
|
331
|
-
nomad update
|
|
340
|
+
$ cd ~/claude-nomad
|
|
341
|
+
$ nomad update
|
|
332
342
|
```
|
|
333
343
|
|
|
334
|
-
`nomad update`
|
|
344
|
+
`nomad update` detects which layout your `~/claude-nomad/` uses and does the right thing:
|
|
335
345
|
|
|
336
346
|
- **vanilla** (`origin` points at the public repo): `git pull --ff-only origin main`.
|
|
337
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`.
|
|
338
348
|
|
|
339
|
-
Pre-flight checks run before any mutation: `REPO_HOME` exists, topology resolves to `vanilla` or `fork`, current branch is `main`, working tree is clean
|
|
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.
|
|
340
352
|
|
|
341
353
|
Common cases:
|
|
342
354
|
|
|
343
355
|
```bash
|
|
344
|
-
nomad update # the usual path
|
|
345
|
-
nomad update --dry-run # detect topology + pre-flight, print would-be git commands only
|
|
346
|
-
nomad update --push-origin # fork topology: push merge to origin/main without prompting
|
|
347
|
-
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
|
|
348
360
|
```
|
|
349
361
|
|
|
350
362
|
One-time setup if you're running a fork layout and don't have the `upstream` remote yet:
|
|
351
363
|
|
|
352
364
|
```bash
|
|
353
|
-
git remote add upstream git@github.com:funkadelic/claude-nomad.git
|
|
365
|
+
$ git remote add upstream git@github.com:funkadelic/claude-nomad.git
|
|
354
366
|
```
|
|
355
367
|
|
|
356
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).
|
|
@@ -364,7 +376,7 @@ If you installed an earlier version via `./install.sh` and a shell alias (the pr
|
|
|
364
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)). |
|
|
365
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`. |
|
|
366
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. |
|
|
367
|
-
| `nomad pull` | `git pull --rebase --autostash`, apply symlinks, regenerate `settings.json`, remap session paths, and pull opted-in per-project extras.
|
|
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. |
|
|
368
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. |
|
|
369
381
|
| `nomad diff` | Offline, lockless twin of `pull --dry-run`. No network, no lock. Works against the current local repo state. |
|
|
370
382
|
| `nomad push` | Export local sessions and opted-in per-project extras to logical names, commit (`chore: sync from <NOMAD_HOST>`), push. |
|
|
@@ -397,7 +409,7 @@ Every `nomad pull`, `nomad push`, and `nomad diff` run ends with a single `summa
|
|
|
397
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.
|
|
398
410
|
|
|
399
411
|
```bash
|
|
400
|
-
nomad drop-session <id>
|
|
412
|
+
$ nomad drop-session <id>
|
|
401
413
|
```
|
|
402
414
|
|
|
403
415
|
Single positional id (the session filename minus `.jsonl`). Anything else (missing id, leading dash, extra arg) exits 1 with a `usage:` line.
|
|
@@ -413,7 +425,7 @@ What it does NOT do: touch the local `~/.claude/projects/<encoded>/<id>.jsonl` f
|
|
|
413
425
|
|
|
414
426
|
### Recovery flow: gitleaks FATAL on a session JSONL
|
|
415
427
|
|
|
416
|
-
`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
|
|
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:
|
|
417
429
|
|
|
418
430
|
```text
|
|
419
431
|
✗ gitleaks detected secrets in 1 session transcript(s).
|
|
@@ -468,16 +480,16 @@ Claude Code embeds the original `cwd` in each session transcript. When you resum
|
|
|
468
480
|
Run this instead:
|
|
469
481
|
|
|
470
482
|
```bash
|
|
471
|
-
eval "$(nomad doctor --resume-cmd <session-id>)"
|
|
483
|
+
$ eval "$(nomad doctor --resume-cmd <session-id>)"
|
|
472
484
|
```
|
|
473
485
|
|
|
474
486
|
Or pipe through bash:
|
|
475
487
|
|
|
476
488
|
```bash
|
|
477
|
-
nomad doctor --resume-cmd <session-id> | bash
|
|
489
|
+
$ nomad doctor --resume-cmd <session-id> | bash
|
|
478
490
|
```
|
|
479
491
|
|
|
480
|
-
`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
|
|
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.
|
|
481
493
|
|
|
482
494
|
If the session isn't mapped on this host, you'll see:
|
|
483
495
|
|
|
@@ -490,6 +502,6 @@ Other fatal surfaces: missing `~/.claude/projects/`, session id absent from ever
|
|
|
490
502
|
## Run tests
|
|
491
503
|
|
|
492
504
|
```bash
|
|
493
|
-
npm install
|
|
494
|
-
npx vitest run
|
|
505
|
+
$ npm install
|
|
506
|
+
$ npx vitest run
|
|
495
507
|
```
|
package/package.json
CHANGED
|
@@ -123,13 +123,13 @@ function saveCache(latest: string): void {
|
|
|
123
123
|
/**
|
|
124
124
|
* Fetch the latest release tag from the upstream GitHub releases API. Uses
|
|
125
125
|
* `execFileSync('curl', ...)` rather than `node:https` because curl honors
|
|
126
|
-
* system proxies
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* redirects (`-L`). Returns `null` on ANY failure path
|
|
130
|
-
* `tag_name` field or a tag that fails
|
|
131
|
-
* leading `v` strip. Release tags ship as
|
|
132
|
-
* `release-please-config.json`'s `include-v-in-tag: true`.
|
|
126
|
+
* system proxies and respects the `-m` timeout reliably. curl is optional, not
|
|
127
|
+
* a hard dependency: this is its only consumer, so a host without curl simply
|
|
128
|
+
* skips the version line. 3-second timeout, fail-fast on non-2xx (`-f`), silent
|
|
129
|
+
* (`-s`), follow redirects (`-L`). Returns `null` on ANY failure path (curl
|
|
130
|
+
* missing from PATH, a missing `tag_name` field, or a tag that fails
|
|
131
|
+
* strict-semver validation after the leading `v` strip). Release tags ship as
|
|
132
|
+
* `v<semver>` per `release-please-config.json`'s `include-v-in-tag: true`.
|
|
133
133
|
*/
|
|
134
134
|
function fetchLatestTag(): string | null {
|
|
135
135
|
try {
|