claude-nomad 0.35.0 → 0.37.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 +42 -0
- package/README.md +22 -936
- package/dist/nomad.mjs +372 -250
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,58 +16,10 @@ commands, tuned settings, or past conversations. **claude-nomad** keeps all of i
|
|
|
16
16
|
private Git repo you control. `nomad push` on one machine, `nomad pull` on the next, and everything
|
|
17
17
|
is there, conversations included.
|
|
18
18
|
|
|
19
|
-
- **Resume your Claude Code [sessions](https://code.claude.com/docs/en/agent-sdk/sessions) on any
|
|
20
|
-
machine.** Start a conversation on your desktop and pick it up on your laptop. **claude-nomad**
|
|
21
|
-
remaps the file paths Claude Code embeds in every transcript, so your history follows you instead
|
|
22
|
-
of getting stranded on the box where it started.
|
|
23
|
-
- **Secret-scanned, private by default.** Your `~/.claude/` also holds OAuth tokens, MCP
|
|
24
|
-
credentials, and the full text of every conversation, so **claude-nomad** is deliberate about what
|
|
25
|
-
leaves your machine: credentials and ephemeral state never sync, only an explicit allow-list of
|
|
26
|
-
paths is pushed, and everything that does go up is scanned by
|
|
27
|
-
[gitleaks](https://github.com/gitleaks/gitleaks) before it leaves your machine; the push aborts on
|
|
28
|
-
any hit. `nomad init` also disables Actions on your private mirror by default, so transcripts
|
|
29
|
-
can't leak through CI logs.
|
|
30
|
-
- **One setup, every machine.** Your agents, skills, slash commands, and settings live in one place
|
|
31
|
-
and follow you everywhere. Per-machine tweaks like model choice, MCP URLs, and env vars merge on
|
|
32
|
-
top instead of clobbering your shared defaults.
|
|
33
|
-
|
|
34
19
|
Not dotfiles, not rsync. **claude-nomad** understands Claude Code's state, so your session history
|
|
35
20
|
survives different file paths and your secrets never ride along.
|
|
36
21
|
|
|
37
|
-
|
|
38
|
-
box, a personal rig and a work machine. [Get started in three steps.](#quickstart)
|
|
39
|
-
|
|
40
|
-
## Table of contents
|
|
41
|
-
|
|
42
|
-
- [Quickstart](#quickstart)
|
|
43
|
-
- **Concepts**
|
|
44
|
-
- [How it works](#how-it-works)
|
|
45
|
-
- [Repo layout](#repo-layout-what-claude-nomad-looks-like-on-a-configured-host)
|
|
46
|
-
- [What gets synced vs. not](#what-gets-synced-vs-not)
|
|
47
|
-
- [Path remapping](#path-remapping)
|
|
48
|
-
- [Shared support dirs (sharedDirs)](#shared-support-dirs-shareddirs)
|
|
49
|
-
- [Per-host overrides](#per-host-overrides)
|
|
50
|
-
- [What does NOT sync (deliberate trade-offs)](#what-does-not-sync-deliberate-trade-offs)
|
|
51
|
-
- **Getting started**
|
|
52
|
-
- [Requirements](#requirements)
|
|
53
|
-
- [Setup](#setup)
|
|
54
|
-
- [Privacy by default](#privacy-by-default)
|
|
55
|
-
- [First host](#first-host)
|
|
56
|
-
- [Each additional host](#each-additional-host)
|
|
57
|
-
- [Migrating an existing ~/.claude/](#migrating-an-existing-claude)
|
|
58
|
-
- [Upgrading the CLI](#upgrading-the-cli)
|
|
59
|
-
- **Reference**
|
|
60
|
-
- [Commands](#commands)
|
|
61
|
-
- [Recovery flows](#recovery-flows)
|
|
62
|
-
- [Pruning old backups](#pruning-old-backups)
|
|
63
|
-
- [`nomad drop-session <id>`](#nomad-drop-session-id)
|
|
64
|
-
- [`nomad redact <session-id>`](#nomad-redact-session-id)
|
|
65
|
-
- [Recovery flow: gitleaks FATAL on a session JSONL](#recovery-flow-gitleaks-fatal-on-a-session-jsonl)
|
|
66
|
-
- [Recovery flow: push-time interactive menu](#recovery-flow-push-time-interactive-menu)
|
|
67
|
-
- [`.gitleaks.toml` allowlist policy](#gitleakstoml-allowlist-policy)
|
|
68
|
-
- [Customizing the allowlist with an overlay](#customizing-the-allowlist-with-an-overlay)
|
|
69
|
-
- [Cross-OS resume](#cross-os-resume)
|
|
70
|
-
- [Run tests](#run-tests)
|
|
22
|
+
**Full documentation: <https://funkadelic.github.io/claude-nomad/>**
|
|
71
23
|
|
|
72
24
|
## Quickstart
|
|
73
25
|
|
|
@@ -77,10 +29,9 @@ box, a personal rig and a work machine. [Get started in three steps.](#quickstar
|
|
|
77
29
|
# 1. Install the CLI.
|
|
78
30
|
$ npm i -g claude-nomad
|
|
79
31
|
|
|
80
|
-
# 2. Create your private sync repo and scaffold it.
|
|
81
|
-
# create the repo, wire origin, and disable Actions, then scaffolds locally.
|
|
32
|
+
# 2. Create your private sync repo and scaffold it.
|
|
82
33
|
$ nomad init # prompts for a repo name (default: claude-nomad-config)
|
|
83
|
-
$ nomad init --repo my-config # non-interactive
|
|
34
|
+
$ nomad init --repo my-config # non-interactive
|
|
84
35
|
|
|
85
36
|
# 3. Add a stable host label to ~/.zshrc or ~/.bashrc, then reload.
|
|
86
37
|
export NOMAD_HOST=<your-host-label>
|
|
@@ -98,7 +49,7 @@ export NOMAD_HOST=<your-host-label> # add to ~/.zshrc or ~/.bashrc
|
|
|
98
49
|
$ nomad pull
|
|
99
50
|
```
|
|
100
51
|
|
|
101
|
-
|
|
52
|
+
Everyday loop on any host:
|
|
102
53
|
|
|
103
54
|
```bash
|
|
104
55
|
$ nomad doctor # confirm setup
|
|
@@ -106,889 +57,24 @@ $ nomad pull # apply config to ~/.claude/
|
|
|
106
57
|
$ nomad push # publish local changes (sessions, settings)
|
|
107
58
|
```
|
|
108
59
|
|
|
109
|
-
Full walkthrough and the safe-migration sequence for a populated `~/.claude/` are in [Setup](#setup)
|
|
110
|
-
and [Migrating an existing ~/.claude/](#migrating-an-existing-claude).
|
|
111
|
-
|
|
112
|
-
## How it works
|
|
113
|
-
|
|
114
|
-
**claude-nomad** is a **tool**, not a config store. You install the CLI globally
|
|
115
|
-
(`npm i -g claude-nomad`) and keep a separate **private** Git repo that holds only your config:
|
|
116
|
-
`CLAUDE.md`, agents, skills, settings, session transcripts. No tool source code lives in that repo.
|
|
117
|
-
|
|
118
|
-
```text
|
|
119
|
-
your private <your-username>/claude-nomad-config
|
|
120
|
-
├── shared/ (your config, synced to every host)
|
|
121
|
-
│ ├── CLAUDE.md
|
|
122
|
-
│ ├── agents/
|
|
123
|
-
│ ├── skills/
|
|
124
|
-
│ ├── commands/
|
|
125
|
-
│ ├── rules/
|
|
126
|
-
│ ├── hooks/
|
|
127
|
-
│ ├── settings.base.json
|
|
128
|
-
│ └── projects/
|
|
129
|
-
├── hosts/<hostname>.json
|
|
130
|
-
└── path-map.json
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
`nomad init` creates this repo for you (via `gh`) and scaffolds the directory structure in one step.
|
|
134
|
-
Every host after the first installs the CLI, clones your private data repo to `~/claude-nomad/`, and
|
|
135
|
-
runs `nomad pull` to sync.
|
|
136
|
-
|
|
137
|
-
By default the CLI operates on `~/claude-nomad/` (see `REPO_HOME` in `src/config.ts`). Developers
|
|
138
|
-
working from an alternate checkout can `export NOMAD_REPO=/path/to/repo` to point the CLI at their
|
|
139
|
-
working tree without symlink gymnastics; `nomad doctor` surfaces an active override via a trailing
|
|
140
|
-
`(NOMAD_REPO)` annotation on the repo-state line. Empty `NOMAD_REPO` falls through to the default,
|
|
141
|
-
so a clobbered dotfile variable does not break the CLI.
|
|
142
|
-
|
|
143
|
-
## Repo layout (what `~/claude-nomad/` looks like on a configured host)
|
|
144
|
-
|
|
145
|
-
```text
|
|
146
|
-
~/claude-nomad/
|
|
147
|
-
├── shared/ # synced to every machine
|
|
148
|
-
│ ├── CLAUDE.md
|
|
149
|
-
│ ├── settings.base.json # baseline settings
|
|
150
|
-
│ ├── agents/
|
|
151
|
-
│ ├── skills/
|
|
152
|
-
│ ├── commands/
|
|
153
|
-
│ ├── rules/
|
|
154
|
-
│ ├── hooks/ # hook scripts, symlinked into ~/.claude/hooks/
|
|
155
|
-
│ ├── my-statusline.cjs # any script you want symlinked into ~/.claude/
|
|
156
|
-
│ ├── .gitignore # defense-in-depth: blocks .claude.json, settings.local.json, *.token, *.key, *.pem, id_rsa, id_ed25519, .env, .env.*
|
|
157
|
-
│ ├── projects/ # session transcripts under logical names
|
|
158
|
-
│ └── extras/ # opt-in per-project content (materializes when path-map.json declares extras)
|
|
159
|
-
├── hosts/
|
|
160
|
-
│ ├── <your-mac>.json # patches merged over settings.base.json
|
|
161
|
-
│ ├── <your-wsl-host>.json
|
|
162
|
-
│ └── <your-nuc>.json
|
|
163
|
-
└── path-map.json # logical project -> per-host absolute path
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## What gets synced vs. not
|
|
167
|
-
|
|
168
|
-
| Category | Items | Behavior |
|
|
169
|
-
| ----------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
170
|
-
| **Synced** | `CLAUDE.md`, `agents/`, `skills/`, `commands/`, `rules/`, `hooks/`, `my-statusline.cjs` | Symlinked into `~/.claude/` from `shared/`. |
|
|
171
|
-
| **Generated** | `settings.json` | Deep-merge of `settings.base.json` with `hosts/<hostname>.json`; rewritten every pull. |
|
|
172
|
-
| **Remapped** | `projects/` session transcripts | Copied with path translation per `path-map.json`. |
|
|
173
|
-
| **Per-project extras** | Whitelisted dirs like `.planning/`, or a root file like `CLAUDE.md` | Opt-in via the `extras` field in `path-map.json`; mirrored to/from `shared/extras/<logical>/`. |
|
|
174
|
-
| **Shared support dirs** | Opt-in global `~/.claude/` dirs like a tool's `get-shit-done/` | Opt-in via the `sharedDirs` field in `path-map.json`; symlinked into `~/.claude/` from `shared/`. See [Shared support dirs](#shared-support-dirs-shareddirs). |
|
|
175
|
-
| **Never synced** | OAuth and MCP state, shell history, per-host overrides, caches, scratch dirs | Per-host ephemeral state; left untouched in both directions. |
|
|
176
|
-
| **Auto-rehydrated** | `~/.claude/plugins/cache/<plugin>/...` | Re-downloaded by Claude Code from the `enabledPlugins` list; no per-host install. |
|
|
177
|
-
|
|
178
|
-
Pointers and specifics:
|
|
179
|
-
|
|
180
|
-
- **Synced** link names live in `SHARED_LINKS` (and the optional `sharedDirs` field in
|
|
181
|
-
`path-map.json` -- see [Shared support dirs](#shared-support-dirs-shareddirs)), **whitelisted
|
|
182
|
-
extras** names in `SUPPORTED_EXTRAS`, and the full **never-synced** set in `NEVER_SYNC` (all in
|
|
183
|
-
`src/config.ts`).
|
|
184
|
-
- **Never synced**, in full: `~/.claude.json` (OAuth, MCP state), `.credentials.json` (OAuth
|
|
185
|
-
credential store), `history.jsonl`, `settings.local.json` (per-host overrides),
|
|
186
|
-
`stats-cache.json`, `todos/`, `shell-snapshots/`, `debug/`, `file-history/`, `plans/`,
|
|
187
|
-
`session-env/`, `statsig/`, `telemetry/`, `ide/`, plus host-local caches and runtime state
|
|
188
|
-
(`cache/`, `backups/`, `paste-cache/`, `daemon/`, `jobs/`, `tasks/`, `security/`, `sessions/`).
|
|
189
|
-
This set is also the deny-list the `sharedDirs` opt-in is checked against, so one of these names
|
|
190
|
-
cannot be symlinked into the shared repo by mistake.
|
|
191
|
-
- **Per-project extras** run a pre-pull divergence WARN that flags local edits before they get
|
|
192
|
-
overwritten.
|
|
193
|
-
|
|
194
|
-
<!-- prettier-ignore -->
|
|
195
|
-
> [!NOTE]
|
|
196
|
-
> Plugins that depend on host-specific state (external binaries, API keys in env, MCP server
|
|
197
|
-
> URLs) still need that side set up on each host. Put them in `hosts/<host>.json` or the plugin's
|
|
198
|
-
> own per-host config.
|
|
199
|
-
|
|
200
|
-
<!-- prettier-ignore -->
|
|
201
|
-
> [!IMPORTANT]
|
|
202
|
-
> Syncing a tool's `skills/` or `commands/` files copies the command shims, not the engine behind
|
|
203
|
-
> them. If a tool keeps a binary or runtime outside `~/.claude/` (installed with `npm i -g`, a setup
|
|
204
|
-
> script, and so on), nomad does not carry that part, so the synced commands appear on a new host but
|
|
205
|
-
> fail until the tool itself is installed there. Install such tools once per host. For example, if you
|
|
206
|
-
> sync the GSD (`get-shit-done`) skills, run `npm i -g get-shit-done-cc` on each host, pinned to the
|
|
207
|
-
> version that matches your committed skills. Claude Code marketplace plugins (such as superpowers)
|
|
208
|
-
> are the exception: they are listed in `enabledPlugins`, synced via `settings.base.json`, and
|
|
209
|
-
> re-downloaded by Claude Code automatically, so they need no manual install.
|
|
210
|
-
|
|
211
|
-
For the rationale behind these choices, see
|
|
212
|
-
[What does NOT sync (deliberate trade-offs)](#what-does-not-sync-deliberate-trade-offs).
|
|
213
|
-
|
|
214
|
-
## Path remapping
|
|
215
|
-
|
|
216
|
-
The hard problem: Claude Code stores sessions in `~/.claude/projects/<encoded-path>/` where the
|
|
217
|
-
encoded path is the absolute path with `/` replaced by `-`. So the same logical project ends up in
|
|
218
|
-
different directories on each host.
|
|
219
|
-
|
|
220
|
-
`path-map.json` defines logical names and where the repo lives on each host. The optional `extras`
|
|
221
|
-
block opts a project into syncing whitelisted directories (or a single root file) at its root:
|
|
222
|
-
|
|
223
|
-
```json
|
|
224
|
-
{
|
|
225
|
-
"projects": {
|
|
226
|
-
"my-example-repo": {
|
|
227
|
-
"<your-mac>": "/Users/you/code/my-example-repo",
|
|
228
|
-
"<your-wsl-host>": "/home/you/code/my-example-repo",
|
|
229
|
-
"<your-nuc>": "TBD"
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
"extras": {
|
|
233
|
-
"my-example-repo": [".planning", "CLAUDE.md"]
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
<!-- prettier-ignore -->
|
|
239
|
-
> [!IMPORTANT]
|
|
240
|
-
> The host-label keys must match whatever you set `NOMAD_HOST=` to on each host (see
|
|
241
|
-
> [Setup](#setup)). Mismatched labels silently skip remap, so sessions land in the wrong host's
|
|
242
|
-
> encoded dir.
|
|
243
|
-
|
|
244
|
-
Use the literal string `"TBD"` for hosts you haven't onboarded yet; `remapPull` skips TBD entries
|
|
245
|
-
cleanly instead of creating an orphan `~/.claude/projects/TBD/`. Replace each `"TBD"` with the real
|
|
246
|
-
path when you bring up that host.
|
|
247
|
-
|
|
248
|
-
On `push`, sessions in `~/.claude/projects/-Users-you-code-my-example-repo/` get copied to
|
|
249
|
-
`shared/projects/my-example-repo/`. On `nomad pull` on another machine, they get copied to that
|
|
250
|
-
host's encoded path. `claude --resume` then finds them (see
|
|
251
|
-
[What does NOT sync (deliberate trade-offs)](#what-does-not-sync-deliberate-trade-offs) for the
|
|
252
|
-
cross-OS cwd-binding gotcha).
|
|
253
|
-
|
|
254
|
-
The `extras` block is additive and back-compatible: legacy `path-map.json` files without it keep
|
|
255
|
-
working unchanged. Each value is an array of directory or root-file names (e.g. `.planning`,
|
|
256
|
-
`CLAUDE.md`) checked against `SUPPORTED_EXTRAS` in `src/config.ts`; anything outside that whitelist
|
|
257
|
-
is skipped with a log line, so an unrecognized name cannot widen the sync surface.
|
|
258
|
-
|
|
259
|
-
On `nomad push`, opted-in content at `<localRoot>/<name>` (a directory subtree or a single file) is
|
|
260
|
-
copied to `shared/extras/<logical>/<name>` and goes through the same staged-tree gitleaks scan as
|
|
261
|
-
everything else. On `nomad pull`, the reverse copy runs after `git pull --rebase`, and just before
|
|
262
|
-
it overwrites your working tree a divergence check compares the incoming content against your local
|
|
263
|
-
copy and prints a per-file WARN naming anything that differs.
|
|
264
|
-
|
|
265
|
-
Your existing local content is backed up under `~/.cache/claude-nomad/backup/<ts>/extras/` before
|
|
266
|
-
the pull copy lands, so an unexpected overwrite is always recoverable.
|
|
267
|
-
|
|
268
|
-
## Shared support dirs (sharedDirs)
|
|
269
|
-
|
|
270
|
-
Some tools install a `hooks` block into `settings.json` whose commands point at scripts under
|
|
271
|
-
`~/.claude/hooks/` (and sometimes a support directory such as `~/.claude/get-shit-done/`). Because
|
|
272
|
-
`settings.json` is regenerated on every pull, that hook configuration travels to every host, but the
|
|
273
|
-
scripts it points at did not, so hooks broke on a freshly configured host. `~/.claude/hooks/` is now
|
|
274
|
-
a built-in synced link (it rides the same symlink model as `skills/` and `agents/`), so hook scripts
|
|
275
|
-
travel automatically.
|
|
276
|
-
|
|
277
|
-
For any other global `~/.claude/` support directory a tool needs, the optional top-level
|
|
278
|
-
`sharedDirs` field in `path-map.json` opts it into the same symlink sync:
|
|
279
|
-
|
|
280
|
-
```json
|
|
281
|
-
{
|
|
282
|
-
"projects": {
|
|
283
|
-
"my-example-repo": {
|
|
284
|
-
"<your-mac>": "/Users/you/code/my-example-repo"
|
|
285
|
-
}
|
|
286
|
-
},
|
|
287
|
-
"sharedDirs": ["get-shit-done"]
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
What this means for you: each listed name is symlinked from `shared/<name>` into `~/.claude/<name>`
|
|
292
|
-
(the same model as the built-in synced links, not a copy), so editing it on any host updates the one
|
|
293
|
-
shared copy. The field is additive and back-compatible: a `path-map.json` without it behaves exactly
|
|
294
|
-
as before.
|
|
295
|
-
|
|
296
|
-
Entries are validated before anything is linked. A name is accepted only if it is a single path
|
|
297
|
-
segment (no `/`, no `..`), is not one of the never-synced names, and does not collide with a
|
|
298
|
-
reserved `shared/` name (`settings.base.json`, the built-in synced links, `hooks`, `hosts`,
|
|
299
|
-
`path-map.json`). An invalid entry is dropped with a warning rather than aborting the run. The
|
|
300
|
-
contents still go through the same gitleaks scan as everything else on push, so do not point
|
|
301
|
-
`sharedDirs` at a directory that holds credentials.
|
|
302
|
-
|
|
303
|
-
First-time setup on an already-configured repo: a symlink can only form once the directory exists
|
|
304
|
-
under `shared/`. On a fresh repo `nomad init --snapshot` handles this for you. To add `hooks/` (or a
|
|
305
|
-
new `sharedDirs` entry) to a repo that is already set up, move it into `shared/` once on the host
|
|
306
|
-
that has it, then let the normal flow take over:
|
|
307
|
-
|
|
308
|
-
```bash
|
|
309
|
-
$ mv ~/.claude/hooks ~/claude-nomad/shared/hooks # one-time, on the source host
|
|
310
|
-
$ nomad pull # re-creates ~/.claude/hooks as a symlink
|
|
311
|
-
$ nomad push # shares it with your other hosts
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
`nomad pull` never writes back to the remote, so it will not seed `shared/` for you; the one-time
|
|
315
|
-
move is deliberate.
|
|
316
|
-
|
|
317
|
-
## Per-host overrides
|
|
318
|
-
|
|
319
|
-
`settings.base.json` holds portable defaults (model, permissions, plugins).
|
|
320
|
-
`hosts/<NOMAD_HOST>.json` holds machine-specific patches. They're deep-merged on every pull (scalars
|
|
321
|
-
override, objects merge recursively, arrays replace). Keys that used to be force-marked per-host
|
|
322
|
-
because they embedded absolute paths (`statusLine.command`, `hooks`) can live in
|
|
323
|
-
`settings.base.json` if you write the commands with `$HOME` (e.g.
|
|
324
|
-
`"command": "node \"$HOME/.claude/my-statusline.cjs\""`); Claude Code runs them through a shell so
|
|
325
|
-
shell expansion applies. Reserve per-host files for truly machine-specific values (env, MCP URLs,
|
|
326
|
-
host-only model overrides).
|
|
327
|
-
|
|
328
|
-
`shared/settings.base.json`:
|
|
329
|
-
|
|
330
|
-
```json
|
|
331
|
-
{
|
|
332
|
-
"model": "claude-sonnet-4-6",
|
|
333
|
-
"permissions": { "allow": ["Bash(npm run *)", "Bash(git status)"] }
|
|
334
|
-
}
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
`hosts/<your-other-host>.json`:
|
|
338
|
-
|
|
339
|
-
```json
|
|
340
|
-
{
|
|
341
|
-
"model": "claude-opus-4-8",
|
|
342
|
-
"env": { "OLLAMA_HOST": "http://localhost:11434" }
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
Results on `your-other-host`: opus 4.8, the local Ollama env var, plus the shared permissions array.
|
|
347
|
-
|
|
348
|
-
<!-- prettier-ignore -->
|
|
349
|
-
> [!CAUTION]
|
|
350
|
-
> Never hand-edit `~/.claude/settings.json` on a synced host. It's regenerated on every
|
|
351
|
-
> `nomad pull` from base + host, so your edits will be clobbered. Edit the base or host file in the
|
|
352
|
-
> repo instead.
|
|
353
|
-
|
|
354
|
-
`nomad doctor` warns when `settings.json` carries a top-level key it does not recognize (a cue that
|
|
355
|
-
Claude Code added a setting). The recognized set is kept current against Claude Code's published
|
|
356
|
-
settings schema by a weekly automated PR in the public repo, so a periodic `nomad update` (to get
|
|
357
|
-
the latest CLI) is what keeps that warning quiet on your hosts. To check your own `settings.json`
|
|
358
|
-
against the live schema on demand, run `nomad doctor --check-schema`.
|
|
359
|
-
|
|
360
|
-
## What does NOT sync (deliberate trade-offs)
|
|
361
|
-
|
|
362
|
-
Read these before adopting so you opt in with eyes open.
|
|
363
|
-
|
|
364
|
-
- **Last-write-wins on conflicts.** Git surfaces them on merge; no field-level JSON merging.
|
|
365
|
-
- **Manual push/pull.** No file watcher. Shell hooks recommended.
|
|
366
|
-
- **OAuth doesn't sync.** You'll log in once per host. Intentional.
|
|
367
|
-
- **Only sessions in `path-map.json` are remapped.** Drive-by sessions on un-mapped paths are left
|
|
368
|
-
alone.
|
|
369
|
-
- **Extras are opt-in and whitelisted.** Projects without an `extras` entry in `path-map.json` are
|
|
370
|
-
unaffected. Names (a directory or a single root file) outside `SUPPORTED_EXTRAS` are skipped with
|
|
371
|
-
a `skip ... not in SUPPORTED_EXTRAS` log line so an unrecognized name cannot widen the sync
|
|
372
|
-
surface. Unsafe path-map values (path-traversal in `logical` keys, non-absolute or unnormalized
|
|
373
|
-
`localRoot` values) abort the run before any file is touched, so a malformed entry fails loudly
|
|
374
|
-
instead of corrupting state.
|
|
375
|
-
- **Cross-OS `claude --resume` cwd binding.** Sessions embed the cwd where they were created, so
|
|
376
|
-
Claude Code's picker's `cd ... && claude --resume <id>` line fails on a different host. Use
|
|
377
|
-
`nomad doctor --resume-cmd <id>` for a host-local equivalent (see
|
|
378
|
-
[Cross-OS resume](#cross-os-resume)). The sidecar approach preserves transcript byte-equality.
|
|
379
|
-
- **Empty directories don't survive sync.** Git doesn't track empty dirs; `nomad doctor` reports
|
|
380
|
-
them as `missing` (benign). Drop a `.gitkeep` to force materialization.
|
|
381
|
-
|
|
382
60
|
## Requirements
|
|
383
61
|
|
|
384
|
-
- Node.js 22.22.1 or newer (24 LTS recommended
|
|
385
|
-
and surfaces a warning on older runtimes - npm only blocks the install when `engine-strict=true`
|
|
386
|
-
is configured)
|
|
62
|
+
- Node.js 22.22.1 or newer (24 LTS recommended)
|
|
387
63
|
- Git
|
|
388
|
-
- [`gitleaks`](https://github.com/gitleaks/gitleaks) (required for `nomad push
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
### Privacy by default
|
|
407
|
-
|
|
408
|
-
Your private sync repo must stay private. Session transcripts contain the full text of your
|
|
409
|
-
conversations. `nomad init` disables Actions on the new repo as soon as it is created, via the
|
|
410
|
-
GitHub API call `gh api -X PUT repos/<owner>/<repo>/actions/permissions -F enabled=false`. What this
|
|
411
|
-
means for you: CI workflows (which could echo transcript content into build logs) are turned off on
|
|
412
|
-
your private data repo automatically; you do not need to remember to do it.
|
|
413
|
-
|
|
414
|
-
Pass `--keep-actions` to skip the disable step (for example, when your org already enforces an
|
|
415
|
-
Actions policy).
|
|
416
|
-
|
|
417
|
-
<!-- prettier-ignore -->
|
|
418
|
-
> [!WARNING]
|
|
419
|
-
> If you ever make the repo public, your session transcripts (which include conversation content)
|
|
420
|
-
> become world-readable. **Keep it private.**
|
|
421
|
-
|
|
422
|
-
### First host
|
|
423
|
-
|
|
424
|
-
`nomad init` creates the private repo via `gh`, wires it as `origin`, disables Actions, scaffolds
|
|
425
|
-
the directory layout, and pushes. The `gh` CLI must be installed and authenticated before you run
|
|
426
|
-
it.
|
|
427
|
-
|
|
428
|
-
```bash
|
|
429
|
-
# Install the CLI.
|
|
430
|
-
$ npm i -g claude-nomad
|
|
431
|
-
|
|
432
|
-
# Create the private sync repo and scaffold it. You will be prompted for a
|
|
433
|
-
# repo name (default: claude-nomad-config). Pass --repo to skip the prompt.
|
|
434
|
-
$ nomad init
|
|
435
|
-
# or non-interactively:
|
|
436
|
-
$ nomad init --repo my-config
|
|
437
|
-
|
|
438
|
-
# If ~/.claude/ is already populated on this host, capture it as the starting
|
|
439
|
-
# point instead of an empty scaffold. Stages shared/ and writes
|
|
440
|
-
# hosts/<NOMAD_HOST>.json from your current ~/.claude/settings.json.
|
|
441
|
-
# Does NOT touch the originals.
|
|
442
|
-
$ nomad init --snapshot
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
`nomad init` refuses to clobber existing scaffold artifacts, so re-running on a populated repo is a
|
|
446
|
-
safe no-op (it errors out naming the offender). `nomad pull` against an unscaffolded repo fails fast
|
|
447
|
-
with `FATAL: repo not initialized; run 'nomad init' to scaffold` instead of silently leaving a
|
|
448
|
-
half-state.
|
|
449
|
-
|
|
450
|
-
Add a stable host label to your shell rc, then reload it:
|
|
451
|
-
|
|
452
|
-
```bash
|
|
453
|
-
export NOMAD_HOST=<your-host-label> # add to ~/.zshrc or ~/.bashrc
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
`NOMAD_HOST` overrides `os.hostname()`, which returns noisy values like `WINDOWS-I5NT6OH` on WSL or
|
|
457
|
-
`<name>.local` on macOS. Pick a clean label per machine (e.g., `wsl-laptop`, `macbook`,
|
|
458
|
-
`homelab-nuc`). `nomad doctor` reports the resolved host so you can confirm.
|
|
459
|
-
|
|
460
|
-
Edit `path-map.json` to add your logical projects (see [Path remapping](#path-remapping)), then:
|
|
461
|
-
|
|
462
|
-
```bash
|
|
463
|
-
$ nomad doctor # read-only state check; reports host, repo state, every check as ✓ (pass) / ✗ (fail) / ⚠︎ (warn)
|
|
464
|
-
$ nomad doctor --check-shared # read-only gitleaks preflight over the session transcripts a push would stage
|
|
465
|
-
$ nomad diff # preview what nomad pull would change on this host; no lock, no network, no mutation
|
|
466
|
-
$ nomad push # send current state to the private remote
|
|
467
|
-
$ nomad pull # apply on another host (or this one after a remote update)
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
`nomad pull --dry-run` is the network-aware twin of `nomad diff`: it acquires the lock and runs
|
|
471
|
-
`git pull` so you see what the next real pull would do given the latest remote, then exits without
|
|
472
|
-
mutating.
|
|
473
|
-
|
|
474
|
-
If the destination host already has populated `~/.claude/{CLAUDE.md, agents/, ...}`, the first
|
|
475
|
-
`nomad pull` will refuse to overwrite real files. See
|
|
476
|
-
[Migrating an existing ~/.claude/](#migrating-an-existing-claude) for the safe migration flow.
|
|
477
|
-
|
|
478
|
-
### Each additional host
|
|
479
|
-
|
|
480
|
-
```bash
|
|
481
|
-
# Install the CLI.
|
|
482
|
-
$ npm i -g claude-nomad
|
|
483
|
-
|
|
484
|
-
# Clone your private data repo.
|
|
485
|
-
$ gh repo clone <your-username>/claude-nomad-config ~/claude-nomad
|
|
486
|
-
# or with plain git:
|
|
487
|
-
$ git clone git@github.com:<your-username>/claude-nomad-config.git ~/claude-nomad
|
|
488
|
-
|
|
489
|
-
# Add to ~/.zshrc or ~/.bashrc, then reload.
|
|
490
|
-
export NOMAD_HOST=<your-host-label>
|
|
491
|
-
|
|
492
|
-
$ nomad pull # apply config to ~/.claude/
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
`npm i -g claude-nomad` puts a `nomad` binary on your PATH. What this means for you: there is no
|
|
496
|
-
compile step, no extra transpiler to install, and nothing is fetched from the network the first time
|
|
497
|
-
you run `nomad`, so the first run works offline. (The Node version floor and the `engine-strict`
|
|
498
|
-
caveat are in [Requirements](#requirements).)
|
|
499
|
-
|
|
500
|
-
## Migrating an existing ~/.claude/
|
|
501
|
-
|
|
502
|
-
If a host already has real files at `~/.claude/{CLAUDE.md, agents/, skills/, ...}` and you want to
|
|
503
|
-
bring them into the sync, the required sequence is `nomad init --snapshot` → `nomad push` →
|
|
504
|
-
`nomad pull`:
|
|
505
|
-
|
|
506
|
-
```bash
|
|
507
|
-
# From the host that has the canonical config (the originals are not modified):
|
|
508
|
-
$ nomad init --snapshot # stages shared/ and writes hosts/<NOMAD_HOST>.json from ~/.claude/
|
|
509
|
-
$ nomad push # publish the captured state to the private remote
|
|
510
|
-
|
|
511
|
-
# Then, on this host or any other host that has the private remote checked out:
|
|
512
|
-
$ nomad pull # materializes the symlinks
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
`nomad pull` is what actually migrates the host. `applySharedLinks` runs a two-pass scan: any
|
|
516
|
-
pre-existing non-symlink at a `SHARED_LINKS` path whose counterpart exists under `shared/` is
|
|
517
|
-
renamed into `~/.cache/claude-nomad/backup/<ts>/` first, then the symlink is created. Your originals
|
|
518
|
-
are preserved under that timestamped backup directory, not deleted. Paths whose `shared/<name>` is
|
|
519
|
-
absent from the remote are left untouched, so a partial publish does not delete data on the
|
|
520
|
-
destination host.
|
|
521
|
-
|
|
522
|
-
If the remote has not been populated yet (you skipped `nomad init --snapshot` and `nomad push`),
|
|
523
|
-
`nomad pull` is a no-op for SHARED_LINKS: there is nothing on the remote to symlink against, so your
|
|
524
|
-
local `~/.claude/` files stay in place. The auto-move only triggers once the canonical state is
|
|
525
|
-
published.
|
|
526
|
-
|
|
527
|
-
Prefer an explicit tarball rollback and a confirmation prompt before any deletion? Write the
|
|
528
|
-
equivalent under `scripts/`: tar the `SHARED_LINKS` entries under `~/.claude/` first, copy into
|
|
529
|
-
`shared/`, prompt, then `nomad pull`. The auto-move path above is the recommended default.
|
|
530
|
-
|
|
531
|
-
## Upgrading the CLI
|
|
532
|
-
|
|
533
|
-
`nomad update` updates the `nomad` binary from npm:
|
|
534
|
-
|
|
535
|
-
```bash
|
|
536
|
-
$ nomad update
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
What this means for you: it runs `npm update -g claude-nomad` and refreshes the binary on your PATH.
|
|
540
|
-
It does NOT pull your sync data; run `nomad pull` separately when you want to apply remote changes
|
|
541
|
-
to this host.
|
|
542
|
-
|
|
543
|
-
`nomad doctor` reports when your local install is behind the latest npm release:
|
|
544
|
-
`⚠︎ claude-nomad: <local> -> <latest> (run nomad update)`.
|
|
545
|
-
|
|
546
|
-
## Commands
|
|
547
|
-
|
|
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 two `⚠︎`-only drift checks: gitleaks version drift and, on a private GitHub mirror, re-enabled Actions. |
|
|
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 missing or the schema is unreachable. |
|
|
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. |
|
|
573
|
-
|
|
574
|
-
The version-check emits ``⚠︎ claude-nomad: <local> -> <latest> (run `nomad update`)`` when the local
|
|
575
|
-
install is behind the latest upstream release, and `✓ claude-nomad: <local> (latest)` when current.
|
|
576
|
-
It silently skips on network failures.
|
|
577
|
-
|
|
578
|
-
The Hook targets check reads the live `~/.claude/settings.json` `hooks` block and fails (`✗`, exit
|
|
579
|
-
|
|
580
|
-
1. when a hook command points at a script under `~/.claude/` that is missing on this host (the
|
|
581
|
-
freshly-configured-host symptom that motivated syncing `hooks/`). It deliberately skips any
|
|
582
|
-
command it cannot resolve to a `~/.claude/` path (bare binaries like `jq`, unresolved env vars),
|
|
583
|
-
so it never false-fails on a command that does not reference a local script.
|
|
584
|
-
|
|
585
|
-
Two further `⚠︎`-only drift checks run in `nomad doctor`. The gitleaks version-drift line
|
|
586
|
-
`⚠︎ gitleaks: <local> -> <pinned> (...)` fires when the local gitleaks major.minor differs from the
|
|
587
|
-
CI-pinned `GITLEAKS_PINNED_VERSION` (gitleaks rule and allowlist behavior tracks the minor line, so
|
|
588
|
-
a patch-only difference stays `✓`), and is silent when gitleaks is not on PATH. The mirror-Actions
|
|
589
|
-
line (carrying a `gh api -X PUT repos/<owner>/<repo>/actions/permissions -F enabled=false`
|
|
590
|
-
remediation hint) fires when origin is a private GitHub mirror that is gh-authed with Actions
|
|
591
|
-
re-enabled, complementing the auto-disable that runs on `nomad init` (see
|
|
592
|
-
[Privacy by default](#privacy-by-default)); it is silent on every prerequisite miss (non-GitHub
|
|
593
|
-
origin, `gh` unauthed, public repo, or Actions already off).
|
|
594
|
-
|
|
595
|
-
### Reading push and pull output
|
|
596
|
-
|
|
597
|
-
`nomad push` and `nomad pull` print a grouped tree, the same left-gutter layout you already see from
|
|
598
|
-
`nomad doctor`. There is a header line naming the command and host, then a few named sections
|
|
599
|
-
(`Sessions`, `Extras`, and so on), each with its items hanging off `├`/`└` connectors. A status
|
|
600
|
-
glyph leads every line: `✓` green for something that synced, `ℹ︎` dim for an informational count, `⚠︎`
|
|
601
|
-
yellow for a warning, and `✗` red for a failure. What this means for you: instead of one long flat
|
|
602
|
-
list with a line per project, related work is grouped and the noise is collapsed.
|
|
603
|
-
|
|
604
|
-
A clean `nomad push` looks like this (one `✓` row per project whose sessions were copied up, the
|
|
605
|
-
projects this host does not track folded into a single count, then the secret-scan result and a
|
|
606
|
-
one-line summary):
|
|
607
|
-
|
|
608
|
-
```text
|
|
609
|
-
push on host=workstation
|
|
610
|
-
Sessions
|
|
611
|
-
├ ✓ claude-nomad
|
|
612
|
-
├ ✓ my-side-project
|
|
613
|
-
└ ℹ︎ 4 not in path-map (run nomad doctor to list)
|
|
614
|
-
Extras
|
|
615
|
-
└ ✓ claude-nomad/.planning
|
|
616
|
-
Leak scan
|
|
617
|
-
└ ✓ no leaks
|
|
618
|
-
Summary
|
|
619
|
-
└ ✓ summary: clean
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
The `ℹ︎ 4 not in path-map` row is the collapse: rather than printing one line per project that this
|
|
623
|
-
host does not sync, push and pull now show a single count and point you at `nomad doctor`, which
|
|
624
|
-
lists those projects by name if you want the detail. The `Leak scan` section is the secret check
|
|
625
|
-
that runs before anything is published: `✓ no leaks` when the staged transcripts are clean. If a
|
|
626
|
-
secret IS found, that row turns into `✗ gitleaks detected secrets in N session transcript(s)` and
|
|
627
|
-
the full recovery block (which sessions, how to scrub them) still prints below the tree, exactly as
|
|
628
|
-
before (see
|
|
629
|
-
[Recovery flow: gitleaks FATAL on a session JSONL](#recovery-flow-gitleaks-fatal-on-a-session-jsonl)).
|
|
630
|
-
The same `Leak scan` row shows up under `nomad push --dry-run`, which runs that secret scan as a
|
|
631
|
-
read-only preview (nothing is written to the sync repo) and exits non-zero if the preview finds
|
|
632
|
-
anything.
|
|
633
|
-
|
|
634
|
-
A `nomad pull` is the mirror image, leading with the settings file it regenerated and then the
|
|
635
|
-
sessions and extras it copied down for this host:
|
|
636
|
-
|
|
637
|
-
```text
|
|
638
|
-
pull on host=workstation (backup=2026-05-27T14-02-09Z)
|
|
639
|
-
Settings
|
|
640
|
-
└ ✓ settings.json (base + workstation.json)
|
|
641
|
-
Sessions
|
|
642
|
-
├ ✓ claude-nomad
|
|
643
|
-
└ ℹ︎ 2 not in path-map (run nomad doctor to list)
|
|
644
|
-
Extras
|
|
645
|
-
└ ✓ claude-nomad/.planning
|
|
646
|
-
Summary
|
|
647
|
-
└ ✓ summary: clean
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
The `Summary` row is the final verdict for the run. It reads `✓ summary: clean` when everything
|
|
651
|
-
synced, or a `⚠︎` warning naming the counts when something was skipped:
|
|
652
|
-
|
|
653
|
-
```text
|
|
654
|
-
⚠︎ summary: 3 unmapped on pull (run nomad doctor to list)
|
|
655
|
-
⚠︎ summary: 2 unmapped on push, 1 collisions (run nomad doctor to list)
|
|
656
|
-
```
|
|
657
|
-
|
|
658
|
-
`✓` lines go to stdout; `⚠︎` and `✗` lines go to stderr. An early, pre-tree fatal abort (for example
|
|
659
|
-
gitleaks missing when push checks for it, or a rebase conflict before anything is staged) suppresses
|
|
660
|
-
the tree entirely, so you do not see "summary: clean" stacked under an error. A later leak-scan
|
|
661
|
-
finding is different: by then the tree has already been built, so it still renders in full with a
|
|
662
|
-
`✗` Leak scan row and the recovery block below it (see
|
|
663
|
-
[Recovery flow: gitleaks FATAL on a session JSONL](#recovery-flow-gitleaks-fatal-on-a-session-jsonl)).
|
|
664
|
-
Projects with no entry in `path-map.json` for this host count as unmapped and fold into the
|
|
665
|
-
collapsed `ℹ︎ ... not in path-map` count; the hint points at `nomad doctor`, which lists them by
|
|
666
|
-
logical name.
|
|
667
|
-
|
|
668
|
-
`nomad pull --dry-run` keeps its own readable preview format (a unified diff of the `settings.json`
|
|
669
|
-
changes plus the transcripts a real pull would overwrite) rather than the grouped tree, so that
|
|
670
|
-
preview stays easy to scan; only a real `nomad pull` prints the tree above. `nomad diff` is
|
|
671
|
-
unchanged.
|
|
672
|
-
|
|
673
|
-
## Recovery flows
|
|
674
|
-
|
|
675
|
-
### Pruning old backups
|
|
676
|
-
|
|
677
|
-
Every `nomad pull` and `nomad push` keeps you safe by copying any file it is about to overwrite into
|
|
678
|
-
a timestamped snapshot under `~/.cache/claude-nomad/backup/<ts>/`. That is what makes an unexpected
|
|
679
|
-
overwrite recoverable, but the snapshots are never deleted automatically, so over many syncs the
|
|
680
|
-
folder slowly grows. It lives in your local cache and is never synced to the shared repo, so
|
|
681
|
-
cleaning it up is purely local disk housekeeping.
|
|
682
|
-
|
|
683
|
-
`nomad clean --backups` prunes those snapshots. **Always run it with `--dry-run` first** so you can
|
|
684
|
-
see exactly which snapshots it would delete before anything is removed:
|
|
685
|
-
|
|
686
|
-
```bash
|
|
687
|
-
$ nomad clean --backups --dry-run # list what would be deleted, remove nothing
|
|
688
|
-
$ nomad clean --backups # delete snapshots older than 14 days (the default)
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
You choose what counts as "old" in one of two ways (you cannot use both at once):
|
|
692
|
-
|
|
693
|
-
- `--older-than <duration>` deletes snapshots older than the given age. The duration is a number
|
|
694
|
-
plus a unit: `d` for days, `h` for hours, `m` for minutes (for example `7d`, `24h`, `30m`). With
|
|
695
|
-
no retention flag at all, the default is `--older-than 14d`.
|
|
696
|
-
- `--keep <N>` keeps the `N` most recent snapshots and deletes the rest, regardless of age.
|
|
697
|
-
|
|
698
|
-
`nomad clean` only ever touches the timestamped snapshot directories directly inside the backup
|
|
699
|
-
folder; it never follows symlinks out of it and never removes the backup folder itself. As a gentle
|
|
700
|
-
reminder, `nomad doctor` shows a `⚠︎` warning when the backup folder grows past roughly 20 snapshots
|
|
701
|
-
or 200 MB, nudging you to run `nomad clean --backups`. That warning is informational only and never
|
|
702
|
-
changes the doctor exit code.
|
|
703
|
-
|
|
704
|
-
### `nomad drop-session <id>`
|
|
705
|
-
|
|
706
|
-
Surgically unstages every `shared/projects/*/<id>.jsonl` plus the sibling `shared/projects/*/<id>/`
|
|
707
|
-
subagent directory (whose nested transcripts are keyed by the same session id) from the staged tree
|
|
708
|
-
of `~/claude-nomad/`. The local `~/.claude/projects/<encoded>/<id>.jsonl` and the local `<id>/` tree
|
|
709
|
-
are never touched.
|
|
710
|
-
|
|
711
|
-
```bash
|
|
712
|
-
$ nomad drop-session <id>
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
Single positional id (the session filename minus `.jsonl`). Anything else (missing id, leading dash,
|
|
716
|
-
extra arg) exits 1 with a `usage:` line.
|
|
717
|
-
|
|
718
|
-
For each match in the staged tree, `cmdDropSession` (in `src/commands.drop-session.ts`) classifies
|
|
719
|
-
the entry as tracked-in-HEAD vs newly-staged and unstages it via
|
|
720
|
-
`git restore --staged --worktree --` or `git rm --cached -f --` respectively. The `<id>/` subagent
|
|
721
|
-
directory is expanded into its staged entries via `git ls-files -z` so every nested transcript flows
|
|
722
|
-
through the same per-entry classification; a session that has only a subagent directory (no flat
|
|
723
|
-
`<id>.jsonl`) is still droppable. Idempotent: a second run on the same id sees no matching staged
|
|
724
|
-
entries and exits 0.
|
|
725
|
-
|
|
726
|
-
Exit codes:
|
|
727
|
-
|
|
728
|
-
- `0` on any drop, including an idempotent re-run.
|
|
729
|
-
- `1` with `✗ no staged session matches <id>` on stderr when neither a
|
|
730
|
-
`shared/projects/*/<id>.jsonl` nor a `shared/projects/*/<id>/` directory with staged entries
|
|
731
|
-
matches.
|
|
732
|
-
|
|
733
|
-
What it does NOT do: touch the local `~/.claude/projects/<encoded>/<id>.jsonl` file or the local
|
|
734
|
-
`<id>/` subagent tree. The local copies are preserved for `claude --resume`, grep recovery, or
|
|
735
|
-
whatever the user wants. If the underlying secret is real, scrubbing or removing the local files is
|
|
736
|
-
REQUIRED for durability, not optional housekeeping: `remapPush` (in `src/remap.ts`) re-mirrors the
|
|
737
|
-
local content into the staged tree on the next push, so a drop without a local scrub re-stages the
|
|
738
|
-
same secret.
|
|
739
|
-
|
|
740
|
-
A successful drop prints this reminder inline, pointing at the live transcript that still needs
|
|
741
|
-
scrubbing (the exact path when `path-map.json` maps the project to the current host, a generic
|
|
742
|
-
`~/.claude/projects/<encoded>/<id>.jsonl` template otherwise). This is why a
|
|
743
|
-
`nomad doctor --check-shared` run still reports the session after a drop: that scan reads the live
|
|
744
|
-
`~/.claude/projects/` source, not the staged tree, so it keeps flagging the secret until the local
|
|
745
|
-
transcript is scrubbed.
|
|
746
|
-
|
|
747
|
-
### `nomad redact <session-id>`
|
|
748
|
-
|
|
749
|
-
Rewrites the secret span in the local source transcript at
|
|
750
|
-
`~/.claude/projects/<encoded>/<session-id>.jsonl` in place, replacing each flagged span with
|
|
751
|
-
`[REDACTED:<rule>]`. Before rewriting, the original transcript is backed up to
|
|
752
|
-
`~/.cache/claude-nomad/backup/<timestamp>/`.
|
|
753
|
-
|
|
754
|
-
```bash
|
|
755
|
-
$ nomad redact <session-id>
|
|
756
|
-
$ nomad redact <session-id> --rule github-pat # one rule only
|
|
757
|
-
$ nomad redact <session-id> --dry-run # preview without writing
|
|
758
|
-
```
|
|
759
|
-
|
|
760
|
-
What it does: rewrites the LOCAL source transcript (not just the staged copy). This is the durable
|
|
761
|
-
fix for a gitleaks finding: `nomad drop-session` only removes the staged copy, but `remapPush`
|
|
762
|
-
re-copies from local on the next push, so the secret resurfaces. Redacting the local source means
|
|
763
|
-
future pushes carry clean content.
|
|
764
|
-
|
|
765
|
-
What it does NOT do: rotate credentials. Always rotate the secret at its provider first.
|
|
766
|
-
|
|
767
|
-
Safety checks:
|
|
768
|
-
|
|
769
|
-
- A session whose transcript was modified within the last 5 minutes is treated as potentially active
|
|
770
|
-
(Claude Code may still be writing to it). `nomad redact` refuses to touch it and suggests
|
|
771
|
-
`nomad drop-session` or waiting for the session to end.
|
|
772
|
-
- Before every rewrite, a backup is written to `~/.cache/claude-nomad/backup/<timestamp>/`, so the
|
|
773
|
-
original content is recoverable.
|
|
774
|
-
- `--dry-run` prints the planned redactions and writes nothing.
|
|
775
|
-
|
|
776
|
-
This command is safe to re-run: if the span was already redacted (the replacement token is already
|
|
777
|
-
present), the content is unchanged.
|
|
778
|
-
|
|
779
|
-
### Recovery flow: gitleaks FATAL on a session JSONL
|
|
780
|
-
|
|
781
|
-
`nomad push` runs `gitleaks protect --staged` before commit. To catch the same findings before you
|
|
782
|
-
push (and without mutating anything), two read-only options are available:
|
|
783
|
-
`nomad doctor --check-shared` scans the session transcripts a push would publish;
|
|
784
|
-
`nomad push --dry-run` runs the same scan AND also covers opted-in extras (`.planning`,
|
|
785
|
-
`CLAUDE.md`), which `--check-shared` does not. Both stage content into a throwaway temp copy and
|
|
786
|
-
never write to the sync repo. A leak-scan finding is the contrast to an early, pre-tree fatal:
|
|
787
|
-
because the scan runs after the tree is built, the push aborts but the grouped tree still renders in
|
|
788
|
-
full, with a `✗ gitleaks detected secrets in N session transcript(s)` row in its `Leak scan`
|
|
789
|
-
section, and then the full recovery block prints below it, naming every affected session id and the
|
|
790
|
-
recovery command:
|
|
791
|
-
|
|
792
|
-
```text
|
|
793
|
-
✗ gitleaks detected secrets in 1 session transcript(s).
|
|
794
|
-
|
|
795
|
-
Session <sid-aaaa>:
|
|
796
|
-
generic-api-key (14), aws-access-token (1)
|
|
797
|
-
Recover with: nomad drop-session <sid-aaaa>
|
|
798
|
-
|
|
799
|
-
After recovery, re-run nomad push.
|
|
800
|
-
```
|
|
801
|
-
|
|
802
|
-
Two branches from here:
|
|
803
|
-
|
|
804
|
-
1. **Real secret.** Rotate the credential at its provider first (revoke in dashboard, issue
|
|
805
|
-
replacement) before touching anything else. Running `nomad drop-session <sid-aaaa>` clears the
|
|
806
|
-
contaminated copy from the current staged tree, but that alone is NOT durable: `remapPush` (in
|
|
807
|
-
`src/remap.ts`) does a full rm-and-copy mirror of your LOCAL transcripts into `shared/projects/`
|
|
808
|
-
on every push, so the next `nomad push` re-copies the un-scrubbed local file forward and
|
|
809
|
-
re-stages the same secret. The durable fix is to rotate AND scrub the local transcript. The
|
|
810
|
-
easiest way: `nomad redact <sid-aaaa>` (see [`nomad redact`](#nomad-redact-session-id)), which
|
|
811
|
-
rewrites the secret span in place with a backup. Alternatively, remove the local transcript at
|
|
812
|
-
`~/.claude/projects/<encoded>/<sid-aaaa>.jsonl` (plus the sibling `<sid-aaaa>/` subagent
|
|
813
|
-
directory, if present). Do not leave the local file un-scrubbed and expect the staged-tree drop
|
|
814
|
-
to hold.
|
|
815
|
-
|
|
816
|
-
2. **False positive.** Add an allowlist regex to `.gitleaks.toml` at the repo root that matches the
|
|
817
|
-
noise pattern but not real-secret formats, commit it, then re-run `nomad push`. The new allowlist
|
|
818
|
-
propagates to other hosts when they run `nomad update` (CLI upgrade) or when you push the updated
|
|
819
|
-
file to your data repo.
|
|
820
|
-
|
|
821
|
-
`nomad drop-session` only acts on the staged tree of `~/claude-nomad/`. Active Claude Code sessions
|
|
822
|
-
writing to the local file are not disturbed.
|
|
823
|
-
|
|
824
|
-
### Recovery flow: push-time interactive menu
|
|
825
|
-
|
|
826
|
-
When `nomad push` detects a secret and the process is running on an interactive TTY, it presents a
|
|
827
|
-
per-finding menu instead of aborting immediately. Each finding is shown with its rule id, file, and
|
|
828
|
-
line number (the secret value is never printed: the scan uses `--redact`).
|
|
829
|
-
|
|
830
|
-
```text
|
|
831
|
-
Finding: github-pat in shared/projects/my-proj/abc123.jsonl line 42 (session: abc123)
|
|
832
|
-
[R]edact [A]llow [D]rop session [S]kip (default)
|
|
833
|
-
>
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
What the actions do:
|
|
837
|
-
|
|
838
|
-
- **Redact** rewrites the secret span in the LOCAL source transcript in place (same flow as
|
|
839
|
-
`nomad redact`), backs up first, then re-copies the file to the staged tree. Refuses if the
|
|
840
|
-
session was modified in the last 5 minutes (potential active session): choose Drop or Skip instead
|
|
841
|
-
and wait for the session to end.
|
|
842
|
-
- **Allow** appends the finding's fingerprint to `.gitleaksignore` at the repo root. Use this for
|
|
843
|
-
confirmed false positives. The fingerprint format (`file:rule:line`) is tied to the current line,
|
|
844
|
-
so if the content moves gitleaks re-prompts rather than silently suppressing a new hit.
|
|
845
|
-
- **Drop session** excludes this session from the current push by unstaging it from the repo's git
|
|
846
|
-
index (same as `nomad drop-session <id>`). The local `~/.claude/projects/.../` transcript is kept
|
|
847
|
-
intact and any running Claude session is not stopped. Not durable: the next push re-copies from
|
|
848
|
-
local unless you also redact or remove the local transcript.
|
|
849
|
-
- **Skip** (default on bare Enter) leaves the finding unresolved for now.
|
|
850
|
-
|
|
851
|
-
After you respond to every finding, the menu applies your choices. If any finding was Skipped, the
|
|
852
|
-
push aborts with the session-aware FATAL (same exit as a non-interactive push with findings). If all
|
|
853
|
-
findings were resolved, the staged tree is updated and re-scanned. A clean re-scan proceeds to
|
|
854
|
-
commit and push. If new findings appear after the first round of actions, the menu loops on the new
|
|
855
|
-
set.
|
|
856
|
-
|
|
857
|
-
On a non-TTY (CI, piped input, or scripted `nomad push`), the menu never appears and the push aborts
|
|
858
|
-
with the existing session-aware FATAL unchanged.
|
|
859
|
-
|
|
860
|
-
**Batch redact without a TTY:** `nomad push --redact-all` redacts every finding non-interactively
|
|
861
|
-
(backup written first) without prompting and without requiring a TTY. It does not auto-Allow. After
|
|
862
|
-
redaction the staged tree is re-scanned; any surviving finding aborts with the FATAL. Use this in
|
|
863
|
-
scripts or when every finding is a real secret that should be scrubbed. For a single session,
|
|
864
|
-
`nomad redact <session-id>` (see [`nomad redact`](#nomad-redact-session-id)) gives you per-session
|
|
865
|
-
control with `--rule` and `--dry-run` options.
|
|
866
|
-
|
|
867
|
-
### `.gitleaks.toml` allowlist policy
|
|
868
|
-
|
|
869
|
-
`gitleaks protect` runs against the staged tree on every `nomad push` and can flag
|
|
870
|
-
structurally-distinguishable tool-output noise as `generic-api-key`. The repo-root `.gitleaks.toml`
|
|
871
|
-
pre-allows four such patterns so routine pushes are not blocked:
|
|
872
|
-
|
|
873
|
-
- Sonar issue keys (`AY` prefix + 20+ url-safe chars).
|
|
874
|
-
- gitleaks fingerprint format (`<context>:<rule>:<line>` emitted by gitleaks's own reports).
|
|
875
|
-
- npm audit advisory hashes (anchored on the JSON shape `"id":"<40..64 hex>"`).
|
|
876
|
-
- Coverage-report line-keys (`key=<hex> <path>:<line>`).
|
|
877
|
-
|
|
878
|
-
The file extends the default gitleaks ruleset, so real high-entropy secrets like `ghp_*`,
|
|
879
|
-
`sk_live_*`, `xoxb-*`, and `AKIA*` still fire. The allowlist patterns are structurally
|
|
880
|
-
distinguishable from real-secret formats: a malformed credential cannot match an allowlist regex by
|
|
881
|
-
accident.
|
|
882
|
-
|
|
883
|
-
```toml
|
|
884
|
-
[extend]
|
|
885
|
-
useDefault = true
|
|
886
|
-
|
|
887
|
-
[[allowlists]]
|
|
888
|
-
description = "claude-nomad: structurally-distinguishable tool-output noise"
|
|
889
|
-
regexes = [
|
|
890
|
-
'''AY[A-Za-z0-9_-]{20,}''',
|
|
891
|
-
'''[\w-]+:[\w-]+:\d+''',
|
|
892
|
-
# ...see .gitleaks.toml at the repo root for the full list
|
|
893
|
-
]
|
|
894
|
-
```
|
|
895
|
-
|
|
896
|
-
File location: `.gitleaks.toml` ships bundled with the CLI binary. At runtime both `probeGitleaks`
|
|
897
|
-
(in `src/push-checks.ts`) and `runGitleaksScan` (in `src/push-gitleaks.ts`) try
|
|
898
|
-
`<REPO_HOME>/.gitleaks.toml` first and fall back to the package-bundled copy when the repo-level
|
|
899
|
-
file is absent. So when you have no repo-level copy the allowlist tracks the installed binary, and
|
|
900
|
-
running `nomad update` (to get the latest CLI) is enough to receive allowlist updates. If you do
|
|
901
|
-
place a `<REPO_HOME>/.gitleaks.toml`, it takes precedence and `nomad update` will not change it; you
|
|
902
|
-
maintain that file yourself.
|
|
903
|
-
|
|
904
|
-
#### Customizing the allowlist with an overlay
|
|
905
|
-
|
|
906
|
-
What this means for you: if you only want to allow a couple of extra patterns of your own (say, an
|
|
907
|
-
internal tool that emits a structured token that keeps tripping the scan), you do not have to copy
|
|
908
|
-
the whole bundled allowlist into your sync repo and keep it in step by hand. Instead, drop a small
|
|
909
|
-
`<REPO_HOME>/.gitleaks.overlay.toml` containing only your extra `[[allowlists]]` tables (and
|
|
910
|
-
optionally `[[rules]]`). nomad layers your entries on top of the bundled allowlist at scan time, so
|
|
911
|
-
the shipped Sonar / gitleaks / npm-audit / coverage noise allows stay in effect, the gitleaks
|
|
912
|
-
default ruleset stays in effect, and your additions are appended to all of them.
|
|
913
|
-
|
|
914
|
-
Why this is better than a full `.gitleaks.toml`: a full repo-level `.gitleaks.toml` replaces the
|
|
915
|
-
bundled allowlist outright, so the shipped noise allows are lost and `nomad update` can no longer
|
|
916
|
-
refresh them (you own that file). The overlay is additive instead: it never drops the bundled base,
|
|
917
|
-
and because the base still ships with the CLI, `nomad update` keeps the base current while your
|
|
918
|
-
overlay rides on top.
|
|
919
|
-
|
|
920
|
-
How it works, briefly: on `nomad push`, when the overlay is present, nomad generates a throwaway
|
|
921
|
-
config that extends the bundled `.gitleaks.toml` (which itself extends the gitleaks default),
|
|
922
|
-
appends your overlay body, scans with that combined config, then deletes the throwaway file. The
|
|
923
|
-
merge is gitleaks' own `[extend]` append, so your allowlist entries add to the shipped and default
|
|
924
|
-
ones rather than replacing them.
|
|
925
|
-
|
|
926
|
-
Two rules to keep in mind:
|
|
927
|
-
|
|
928
|
-
- Your overlay must NOT contain its own `[extend]` block. nomad writes the `[extend]` line for you;
|
|
929
|
-
if the overlay includes one, the push aborts with a clear error rather than scanning with a config
|
|
930
|
-
you did not intend.
|
|
931
|
-
- If you keep BOTH a full `<REPO_HOME>/.gitleaks.toml` AND an overlay, the full `.gitleaks.toml`
|
|
932
|
-
wins and the overlay is ignored (a full repo toml means you have taken complete manual control).
|
|
933
|
-
Pick one approach: the overlay for additive tweaks, or a full `.gitleaks.toml` for total control.
|
|
934
|
-
|
|
935
|
-
Example `<REPO_HOME>/.gitleaks.overlay.toml` (note: no `[extend]` block):
|
|
936
|
-
|
|
937
|
-
```toml
|
|
938
|
-
[[allowlists]]
|
|
939
|
-
description = "my-org: internal build-token noise"
|
|
940
|
-
regexes = [
|
|
941
|
-
'''BUILDTOK-[A-Za-z0-9]{24}''',
|
|
942
|
-
]
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
The overlay file is push-allowed (it is an exact-name entry in `PUSH_ALLOWED_STATIC` in
|
|
946
|
-
`src/config.ts`, alongside `.gitleaksignore`), so you can commit `.gitleaks.overlay.toml` to your
|
|
947
|
-
sync repo and it travels to your other hosts on the next `nomad pull`.
|
|
948
|
-
|
|
949
|
-
Editing: amend `.gitleaks.toml` in this repo, open a PR, and merge to `main`. Use TOML literal
|
|
950
|
-
strings (triple single quotes, `'''regex'''`) for new regex entries so backslashes do not need
|
|
951
|
-
escaping. Verify the new pattern does not match real-secret formats (`ghp_<36>`, `sk_live_*`,
|
|
952
|
-
`xoxb-*`, `AKIA[A-Z0-9]{16}`, etc.) before merging. The allowlist ships with the binary, so
|
|
953
|
-
`nomad update` on each host picks up the new file.
|
|
954
|
-
|
|
955
|
-
## Cross-OS resume
|
|
956
|
-
|
|
957
|
-
Claude Code embeds the original `cwd` in each session transcript. When you resume on a different
|
|
958
|
-
host where that path doesn't exist, the picker prints a `cd <orig-cwd> && claude --resume <id>` line
|
|
959
|
-
that fails (the source-host path isn't there).
|
|
960
|
-
|
|
961
|
-
Run this instead:
|
|
962
|
-
|
|
963
|
-
```bash
|
|
964
|
-
$ eval "$(nomad doctor --resume-cmd <session-id>)"
|
|
965
|
-
```
|
|
966
|
-
|
|
967
|
-
Or pipe through bash:
|
|
968
|
-
|
|
969
|
-
```bash
|
|
970
|
-
$ nomad doctor --resume-cmd <session-id> | bash
|
|
971
|
-
```
|
|
972
|
-
|
|
973
|
-
`nomad doctor --resume-cmd <id>` reads the `.jsonl`'s recorded `cwd`, reverse-looks up the logical
|
|
974
|
-
project in `path-map.json`, finds your current host's abspath for that logical, and prints
|
|
975
|
-
`cd <local-abspath> && claude --resume <id>` to stdout. The command is read-only: it never modifies
|
|
976
|
-
any transcript byte.
|
|
977
|
-
|
|
978
|
-
If the session isn't mapped on this host, you'll see:
|
|
979
|
-
|
|
980
|
-
```text
|
|
981
|
-
✗ session <id> not mapped on this host; add the logical to path-map.json
|
|
982
|
-
```
|
|
983
|
-
|
|
984
|
-
Other fatal surfaces: missing `~/.claude/projects/`, session id absent from every encoded dir, no
|
|
985
|
-
`cwd` field anywhere in the transcript, missing `path-map.json`, recorded cwd not present in any
|
|
986
|
-
logical's host map. All errors go to stderr prefixed with the red `✗` fail glyph; the success line
|
|
987
|
-
goes to stdout as a bare shell command (no glyph) so `eval` works.
|
|
988
|
-
|
|
989
|
-
## Run tests
|
|
990
|
-
|
|
991
|
-
```bash
|
|
992
|
-
$ npm install
|
|
993
|
-
$ npx vitest run
|
|
994
|
-
```
|
|
64
|
+
- [`gitleaks`](https://github.com/gitleaks/gitleaks) (required for `nomad push`)
|
|
65
|
+
- `gh` ([GitHub CLI](https://cli.github.com/)), required by `nomad init`
|
|
66
|
+
|
|
67
|
+
**Optional:** [curl](https://curl.se/) or [wget](https://www.gnu.org/software/wget/) for the
|
|
68
|
+
version-staleness check and `nomad doctor --check-schema`. The CLI works without them.
|
|
69
|
+
|
|
70
|
+
## Learn more
|
|
71
|
+
|
|
72
|
+
- [How it works](https://funkadelic.github.io/claude-nomad/how-it-works/) -- path remapping,
|
|
73
|
+
settings merge, what syncs and what doesn't
|
|
74
|
+
- [Setup and migration](https://funkadelic.github.io/claude-nomad/quickstart/) -- full setup
|
|
75
|
+
walkthrough, migrating an existing `~/.claude/`
|
|
76
|
+
- [Commands reference](https://funkadelic.github.io/claude-nomad/commands/) -- all CLI flags
|
|
77
|
+
- [Recovery flows](https://funkadelic.github.io/claude-nomad/recovery/) -- backups, drop-session,
|
|
78
|
+
redact, gitleaks allowlist
|
|
79
|
+
- [Contributing](https://funkadelic.github.io/claude-nomad/contributing/)
|
|
80
|
+
- [Security policy](https://funkadelic.github.io/claude-nomad/security/)
|