claude-nomad 0.54.0 → 0.55.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 +18 -0
- package/README.md +66 -10
- package/dist/nomad.mjs +165 -51
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.55.0](https://github.com/funkadelic/claude-nomad/compare/v0.54.0...v0.55.0) (2026-06-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
* **doctor:** warn on misaligned host key when NOMAD_HOST is unset ([#356](https://github.com/funkadelic/claude-nomad/issues/356)) ([a13e833](https://github.com/funkadelic/claude-nomad/commit/a13e833a09636f98f1e9c51e9c41f01a4323dea2))
|
|
9
|
+
* **init:** offer to snapshot an existing ~/.claude on init ([#357](https://github.com/funkadelic/claude-nomad/issues/357)) ([92e90b1](https://github.com/funkadelic/claude-nomad/commit/92e90b1977fff51d565fa5d11b3c640790ee1b57))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
* make sync warning messages user-friendly ([#345](https://github.com/funkadelic/claude-nomad/issues/345)) ([002f57d](https://github.com/funkadelic/claude-nomad/commit/002f57de240dfb1349742fa4c2b4ac5e51b5f7ba))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Documentation
|
|
18
|
+
|
|
19
|
+
* clarify onboarding (two-dir model, session sync, push order) ([#355](https://github.com/funkadelic/claude-nomad/issues/355)) ([396fbb8](https://github.com/funkadelic/claude-nomad/commit/396fbb875b4331800ee74834944da17decbcad92))
|
|
20
|
+
|
|
3
21
|
## [0.54.0](https://github.com/funkadelic/claude-nomad/compare/v0.53.3...v0.54.0) (2026-06-27)
|
|
4
22
|
|
|
5
23
|
|
package/README.md
CHANGED
|
@@ -39,7 +39,9 @@ survives different file paths and your secrets never ride along.
|
|
|
39
39
|
live settings syncs normally via `nomad capture-settings`.
|
|
40
40
|
- **Every push is secret-scanned.** Only an explicit allow-list of paths ever leaves the machine,
|
|
41
41
|
credentials never sync, and gitleaks scans the exact files about to be published. The push aborts
|
|
42
|
-
on any hit, with an interactive menu to redact, allow, or drop the finding.
|
|
42
|
+
on any hit, with an interactive menu to redact, allow, or drop the finding. Always publish through
|
|
43
|
+
`nomad push`: the sync repo is an ordinary Git repo, so a manual `git push` from it skips the scan
|
|
44
|
+
entirely and can leak a secret that `nomad push` would have caught.
|
|
43
45
|
- **Preview before you trust it.** `nomad diff` shows offline what a pull would change (gsd-owned
|
|
44
46
|
hook churn is filtered the same as on pull, so the preview matches what a real pull writes), and
|
|
45
47
|
`--dry-run` on pull and push prints the plan without writing anything.
|
|
@@ -47,13 +49,16 @@ survives different file paths and your secrets never ride along.
|
|
|
47
49
|
repo, broken hook references, hooks that would crash on session start because of a missing
|
|
48
50
|
`--preserve-symlinks-main` flag, version drift, oversized backup cache, missing git committer
|
|
49
51
|
identity in the sync repo (a push fails at commit time without one), path-map entries whose local
|
|
50
|
-
project folder no longer exists on this machine,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
project folder no longer exists on this machine, a multi-host repo where this machine's
|
|
53
|
+
hostname-derived key matches no `hosts/<HOST>.json` or path-map entry (a sign `NOMAD_HOST` is
|
|
54
|
+
unset here, so per-host settings and session sync will not line up with the other hosts), synced
|
|
55
|
+
skills with local edits that differ from the shared copy, and settings drift in both directions:
|
|
56
|
+
keys present in the repo merge but absent from your live `settings.json` (behind; the next
|
|
57
|
+
`nomad pull` will restore them, fix: `nomad pull`) and keys present locally but not yet in the
|
|
58
|
+
repo (ahead; local-only additions, fix: `nomad capture-settings`). Each issue includes a fix hint.
|
|
59
|
+
By default the report is compact: it shows only checks that need action plus a one-line verdict.
|
|
60
|
+
Add `--verbose` (or `--all` / `-v`) to see the full per-check tree, including everything that
|
|
61
|
+
passed.
|
|
57
62
|
- **Self-healing sync.** Every overwrite is backed up first, and `nomad pull --force-remote`
|
|
58
63
|
recovers two kinds of stuck sync repo: a repo stuck mid-rebase or mid-merge (aborts the operation,
|
|
59
64
|
parks stranded work on a branch, refuses if shared config is at risk), and a repo where the rebase
|
|
@@ -68,15 +73,27 @@ opt-in per-project sync, transcript redaction, backup pruning, and more.
|
|
|
68
73
|
|
|
69
74
|
## Quickstart
|
|
70
75
|
|
|
76
|
+
nomad works with two directories, and the difference is the one thing worth learning up front:
|
|
77
|
+
|
|
78
|
+
- **`~/claude-nomad/`** is your private sync repo. This is the one you edit.
|
|
79
|
+
- **`~/.claude/`** is Claude Code's live config. nomad regenerates it on every `pull`.
|
|
80
|
+
|
|
81
|
+
Edit the repo, never the live config. In particular, never hand-edit `~/.claude/settings.json`: it
|
|
82
|
+
is rebuilt from the repo on every pull and your changes are lost. Change `shared/settings.base.json`
|
|
83
|
+
(or `hosts/<HOST>.json`) in the repo instead, or run `nomad capture-settings` to pull local changes
|
|
84
|
+
back into the repo (see [Changing settings](#changing-settings)).
|
|
85
|
+
|
|
71
86
|
**First host** (once, ever):
|
|
72
87
|
|
|
73
88
|
```bash
|
|
74
89
|
# 1. Install the CLI.
|
|
75
90
|
$ npm i -g claude-nomad
|
|
76
91
|
|
|
77
|
-
# 2. Create your private sync repo and scaffold it.
|
|
92
|
+
# 2. Create your private sync repo and scaffold it. If you already have a
|
|
93
|
+
# ~/.claude/ worth keeping, init offers to seed the repo from it.
|
|
78
94
|
$ nomad init # prompts for a repo name (default: claude-nomad-config)
|
|
79
|
-
$ nomad init --repo my-config #
|
|
95
|
+
$ nomad init --repo my-config # set the repo name without the prompt
|
|
96
|
+
$ nomad init --snapshot # seed from existing ~/.claude/ without being asked
|
|
80
97
|
|
|
81
98
|
# 3. Add a stable host label to ~/.zshrc or ~/.bashrc, then reload.
|
|
82
99
|
export NOMAD_HOST=<your-host-label>
|
|
@@ -102,6 +119,45 @@ $ nomad pull # apply config to ~/.claude/
|
|
|
102
119
|
$ nomad push # publish local changes (sessions, settings)
|
|
103
120
|
```
|
|
104
121
|
|
|
122
|
+
Pull before you push whenever both machines may have changed. Sync is last-write-wins, so pushing
|
|
123
|
+
stale local state over newer remote state silently overwrites it. The
|
|
124
|
+
[FAQ](https://funkadelic.github.io/claude-nomad/faq/) covers the full push/pull order when both
|
|
125
|
+
sides have changed.
|
|
126
|
+
|
|
127
|
+
### Make your sessions follow you
|
|
128
|
+
|
|
129
|
+
Session history only syncs for projects you list in `path-map.json`, and a fresh `init` starts with
|
|
130
|
+
none, so no sessions sync until you add a mapping. Each entry maps a logical project name to the
|
|
131
|
+
absolute path it lives at on each host:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"projects": {
|
|
136
|
+
"my-app": {
|
|
137
|
+
"laptop": "/Users/you/code/my-app",
|
|
138
|
+
"desktop": "/home/you/projects/my-app"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The host keys (`laptop`, `desktop`) are the same labels you set in `NOMAD_HOST` on each machine.
|
|
145
|
+
After editing `path-map.json`, `nomad push` publishes the matching sessions and `nomad pull` on
|
|
146
|
+
another host copies them into place, rewriting the embedded file paths so `claude --resume` finds
|
|
147
|
+
them at that host's path.
|
|
148
|
+
|
|
149
|
+
### Changing settings
|
|
150
|
+
|
|
151
|
+
There are two ways a settings change reaches the repo, and the right one depends on where you made
|
|
152
|
+
it:
|
|
153
|
+
|
|
154
|
+
- **You are deciding the change:** edit `shared/settings.base.json` (shared by every host) or
|
|
155
|
+
`hosts/<HOST>.json` (one machine only) in the repo, then `nomad push`.
|
|
156
|
+
- **Something else already wrote it** (Claude Code or a tool added keys to your live
|
|
157
|
+
`~/.claude/settings.json`): run `nomad capture-settings` to promote those keys into the repo
|
|
158
|
+
before the next `nomad pull` overwrites them. Add `--host` to land machine-specific values (such
|
|
159
|
+
as absolute paths) in `hosts/<HOST>.json` instead of the shared base.
|
|
160
|
+
|
|
105
161
|
During `nomad push` and `nomad pull`, long-running steps (rebase, secret scan, git push, session
|
|
106
162
|
sync) show an animated progress indicator on an interactive terminal so the CLI does not look hung.
|
|
107
163
|
In CI and when output is piped, only plain text lines are printed, with no ANSI control codes, so
|
package/dist/nomad.mjs
CHANGED
|
@@ -780,7 +780,7 @@ var init_push_gitleaks_config = __esm({
|
|
|
780
780
|
|
|
781
781
|
// src/push-checks.ts
|
|
782
782
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
783
|
-
import { readdirSync as
|
|
783
|
+
import { readdirSync as readdirSync5, rmSync as rmSync5 } from "node:fs";
|
|
784
784
|
import { homedir as homedir2, platform } from "node:os";
|
|
785
785
|
import { join as join17 } from "node:path";
|
|
786
786
|
function gitleaksInstallHint() {
|
|
@@ -820,7 +820,7 @@ function findGitlinks(dir) {
|
|
|
820
820
|
function walk(current) {
|
|
821
821
|
let entries;
|
|
822
822
|
try {
|
|
823
|
-
entries =
|
|
823
|
+
entries = readdirSync5(current, { withFileTypes: true });
|
|
824
824
|
} catch {
|
|
825
825
|
return;
|
|
826
826
|
}
|
|
@@ -1505,6 +1505,14 @@ function partitionByCaptureExclusion(keys) {
|
|
|
1505
1505
|
}
|
|
1506
1506
|
return { promotable, excluded };
|
|
1507
1507
|
}
|
|
1508
|
+
function describeSettings(keys) {
|
|
1509
|
+
const one = keys.length === 1;
|
|
1510
|
+
return {
|
|
1511
|
+
phrase: `${keys.length} ${one ? "setting" : "settings"} (${keys.join(", ")})`,
|
|
1512
|
+
pronoun: one ? "it" : "them",
|
|
1513
|
+
verb: one ? "is" : "are"
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1508
1516
|
var BIN_NODE_RE = /^"?(?:[A-Za-z]:)?[\\/](?:.*[\\/])?bin[\\/]node"?$/;
|
|
1509
1517
|
var LEADING_BIN_NODE_RE = /^"?(?:[A-Za-z]:)?[\\/](?:[^"\s]*[\\/])?bin[\\/]node"?(?=\s)/;
|
|
1510
1518
|
function normalizeNodePathsDeep(value) {
|
|
@@ -1614,14 +1622,16 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
1614
1622
|
const existing = readJson(settingsPath);
|
|
1615
1623
|
const drift = classifySettingsDrift(merged, existing);
|
|
1616
1624
|
if (drift.behind.length > 0) {
|
|
1625
|
+
const { phrase, pronoun } = describeSettings(drift.behind);
|
|
1617
1626
|
warn(
|
|
1618
|
-
`
|
|
1627
|
+
`your settings.json is missing ${phrase} that the synced copy has; run 'nomad pull' to restore ${pronoun}.`
|
|
1619
1628
|
);
|
|
1620
1629
|
}
|
|
1621
1630
|
const { promotable } = partitionByCaptureExclusion(drift.ahead);
|
|
1622
1631
|
if (promotable.length > 0) {
|
|
1632
|
+
const { phrase, pronoun, verb } = describeSettings(promotable);
|
|
1623
1633
|
warn(
|
|
1624
|
-
`
|
|
1634
|
+
`your settings.json has ${phrase} that ${verb} not yet synced; run 'nomad capture-settings' to save ${pronoun} to the repo before the next pull overwrites ${pronoun}.`
|
|
1625
1635
|
);
|
|
1626
1636
|
}
|
|
1627
1637
|
} catch {
|
|
@@ -2069,7 +2079,7 @@ import { join as join36 } from "node:path";
|
|
|
2069
2079
|
// src/commands.doctor.checks.repo.ts
|
|
2070
2080
|
init_color();
|
|
2071
2081
|
init_config();
|
|
2072
|
-
import { existsSync as existsSync9, lstatSync as lstatSync6, statSync as statSync3 } from "node:fs";
|
|
2082
|
+
import { existsSync as existsSync9, lstatSync as lstatSync6, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
|
|
2073
2083
|
import { join as join11 } from "node:path";
|
|
2074
2084
|
|
|
2075
2085
|
// src/commands.doctor.format.ts
|
|
@@ -2189,6 +2199,7 @@ function reasonForPartial(repoHome2, host) {
|
|
|
2189
2199
|
}
|
|
2190
2200
|
|
|
2191
2201
|
// src/commands.doctor.checks.repo.ts
|
|
2202
|
+
init_utils_json();
|
|
2192
2203
|
function isOverrideActive() {
|
|
2193
2204
|
return Boolean(process.env.NOMAD_REPO);
|
|
2194
2205
|
}
|
|
@@ -2206,6 +2217,46 @@ function reportHostAndPaths(section2) {
|
|
|
2206
2217
|
`${existsSync9(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
|
|
2207
2218
|
);
|
|
2208
2219
|
}
|
|
2220
|
+
function pathMapHostKeys() {
|
|
2221
|
+
const mapPath = join11(repoHome(), "path-map.json");
|
|
2222
|
+
if (!existsSync9(mapPath)) return /* @__PURE__ */ new Set();
|
|
2223
|
+
let raw;
|
|
2224
|
+
try {
|
|
2225
|
+
raw = readJson(mapPath);
|
|
2226
|
+
} catch {
|
|
2227
|
+
return /* @__PURE__ */ new Set();
|
|
2228
|
+
}
|
|
2229
|
+
if (validatePathMapShape(raw) !== null) return /* @__PURE__ */ new Set();
|
|
2230
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2231
|
+
for (const hosts of Object.values(raw.projects)) {
|
|
2232
|
+
for (const key of Object.keys(hosts)) keys.add(key);
|
|
2233
|
+
}
|
|
2234
|
+
return keys;
|
|
2235
|
+
}
|
|
2236
|
+
function hostOverrideLabels() {
|
|
2237
|
+
let entries;
|
|
2238
|
+
try {
|
|
2239
|
+
entries = readdirSync2(join11(repoHome(), "hosts"));
|
|
2240
|
+
} catch {
|
|
2241
|
+
return /* @__PURE__ */ new Set();
|
|
2242
|
+
}
|
|
2243
|
+
const labels = /* @__PURE__ */ new Set();
|
|
2244
|
+
for (const entry of entries) {
|
|
2245
|
+
if (entry.endsWith(".json")) labels.add(entry.slice(0, -".json".length));
|
|
2246
|
+
}
|
|
2247
|
+
return labels;
|
|
2248
|
+
}
|
|
2249
|
+
function reportHostKeyAlignment(section2) {
|
|
2250
|
+
if (process.env.NOMAD_HOST) return;
|
|
2251
|
+
const overrideLabels = hostOverrideLabels();
|
|
2252
|
+
const mapKeys = pathMapHostKeys();
|
|
2253
|
+
if (overrideLabels.has(HOST) || mapKeys.has(HOST)) return;
|
|
2254
|
+
if (overrideLabels.size === 0 && mapKeys.size === 0) return;
|
|
2255
|
+
addItem(
|
|
2256
|
+
section2,
|
|
2257
|
+
`${yellow(warnGlyph)} NOMAD_HOST unset: this repo configures other hosts, but the hostname key ${cyan(HOST)} matches no hosts/${HOST}.json or path-map entry; set NOMAD_HOST to the label this host should use so per-host settings and session sync line up`
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2209
2260
|
function reportRepoState(section2) {
|
|
2210
2261
|
const repo = repoHome();
|
|
2211
2262
|
const state = classifyRepoState(repo, HOST);
|
|
@@ -2301,7 +2352,7 @@ function reportDroppedNamesMigration(section2) {
|
|
|
2301
2352
|
// src/commands.doctor.checks.settings.ts
|
|
2302
2353
|
init_color();
|
|
2303
2354
|
init_config();
|
|
2304
|
-
import { existsSync as existsSync10, readdirSync as
|
|
2355
|
+
import { existsSync as existsSync10, readdirSync as readdirSync3 } from "node:fs";
|
|
2305
2356
|
import { join as join12 } from "node:path";
|
|
2306
2357
|
function loadBaseSettings(section2) {
|
|
2307
2358
|
const basePath = join12(repoHome(), "shared", "settings.base.json");
|
|
@@ -2347,7 +2398,7 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
2347
2398
|
);
|
|
2348
2399
|
const hostsDir = join12(repo, "hosts");
|
|
2349
2400
|
if (existsSync10(hostsDir)) {
|
|
2350
|
-
const cands =
|
|
2401
|
+
const cands = readdirSync3(hostsDir).filter((f) => f.endsWith(".json"));
|
|
2351
2402
|
if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
|
|
2352
2403
|
}
|
|
2353
2404
|
process.exitCode = 1;
|
|
@@ -2362,7 +2413,7 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
2362
2413
|
// src/commands.doctor.checks.pathmap.ts
|
|
2363
2414
|
init_color();
|
|
2364
2415
|
init_config();
|
|
2365
|
-
import { existsSync as existsSync11, readdirSync as
|
|
2416
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4 } from "node:fs";
|
|
2366
2417
|
import { join as join13 } from "node:path";
|
|
2367
2418
|
init_utils_json();
|
|
2368
2419
|
function reportMappedProjects(section2, map) {
|
|
@@ -2377,7 +2428,7 @@ function reportUnmappedProjects(section2, map) {
|
|
|
2377
2428
|
if (!existsSync11(localProjects)) return;
|
|
2378
2429
|
let localDirs;
|
|
2379
2430
|
try {
|
|
2380
|
-
localDirs =
|
|
2431
|
+
localDirs = readdirSync4(localProjects);
|
|
2381
2432
|
} catch {
|
|
2382
2433
|
return;
|
|
2383
2434
|
}
|
|
@@ -2666,13 +2717,13 @@ function reportGitIdentity(section2) {
|
|
|
2666
2717
|
|
|
2667
2718
|
// src/commands.doctor.checks.backups.ts
|
|
2668
2719
|
init_color();
|
|
2669
|
-
import { existsSync as existsSync16, lstatSync as lstatSync7, readdirSync as
|
|
2720
|
+
import { existsSync as existsSync16, lstatSync as lstatSync7, readdirSync as readdirSync6 } from "node:fs";
|
|
2670
2721
|
import { join as join19 } from "node:path";
|
|
2671
2722
|
init_config();
|
|
2672
2723
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
2673
2724
|
function safeReaddir(dir) {
|
|
2674
2725
|
try {
|
|
2675
|
-
return
|
|
2726
|
+
return readdirSync6(dir);
|
|
2676
2727
|
} catch {
|
|
2677
2728
|
return [];
|
|
2678
2729
|
}
|
|
@@ -2848,7 +2899,7 @@ function reportCheckSchema(section2) {
|
|
|
2848
2899
|
init_color();
|
|
2849
2900
|
import { randomBytes } from "node:crypto";
|
|
2850
2901
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
2851
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync5, readdirSync as
|
|
2902
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync5, readdirSync as readdirSync8, rmSync as rmSync8 } from "node:fs";
|
|
2852
2903
|
import { homedir as homedir4 } from "node:os";
|
|
2853
2904
|
import { join as join24 } from "node:path";
|
|
2854
2905
|
|
|
@@ -2947,7 +2998,7 @@ init_config();
|
|
|
2947
2998
|
|
|
2948
2999
|
// src/remap.ts
|
|
2949
3000
|
init_config_sharedDirs_guard();
|
|
2950
|
-
import { cpSync as cpSync4, existsSync as existsSync18, mkdirSync as mkdirSync4, readdirSync as
|
|
3001
|
+
import { cpSync as cpSync4, existsSync as existsSync18, mkdirSync as mkdirSync4, readdirSync as readdirSync7, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
|
|
2951
3002
|
import { join as join23, relative as relative3, sep as sep3 } from "node:path";
|
|
2952
3003
|
|
|
2953
3004
|
// src/extras-sync.guards.ts
|
|
@@ -3087,7 +3138,7 @@ function remapPush(ts, opts = {}) {
|
|
|
3087
3138
|
const reverse = buildReverseMap(map);
|
|
3088
3139
|
if (!existsSync18(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
3089
3140
|
if (!dryRun) mkdirSync4(repoProjects, { recursive: true });
|
|
3090
|
-
for (const dir of
|
|
3141
|
+
for (const dir of readdirSync7(localProjects)) {
|
|
3091
3142
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
3092
3143
|
const logical = reverse.get(dir);
|
|
3093
3144
|
if (!logical) {
|
|
@@ -3133,7 +3184,7 @@ function buildScanTree(tmpRoot) {
|
|
|
3133
3184
|
}
|
|
3134
3185
|
const localProjects = join24(claudeHome(), "projects");
|
|
3135
3186
|
if (!existsSync19(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
3136
|
-
for (const dir of
|
|
3187
|
+
for (const dir of readdirSync8(localProjects)) {
|
|
3137
3188
|
const logical = reverse.get(dir);
|
|
3138
3189
|
if (!logical) continue;
|
|
3139
3190
|
copyDirJsonlOnly(join24(localProjects, dir), join24(tmpRoot, "shared", "projects", logical));
|
|
@@ -3192,7 +3243,7 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
3192
3243
|
|
|
3193
3244
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
3194
3245
|
init_color();
|
|
3195
|
-
import { existsSync as existsSync20, readFileSync as readFileSync6, readdirSync as
|
|
3246
|
+
import { existsSync as existsSync20, readFileSync as readFileSync6, readdirSync as readdirSync9, realpathSync as realpathSync2 } from "node:fs";
|
|
3196
3247
|
import { dirname as dirname3, extname, join as join25 } from "node:path";
|
|
3197
3248
|
init_config();
|
|
3198
3249
|
function typeFromPackageJson(pkgPath) {
|
|
@@ -3245,7 +3296,7 @@ function remedy(family) {
|
|
|
3245
3296
|
}
|
|
3246
3297
|
function safeReaddir2(dir) {
|
|
3247
3298
|
try {
|
|
3248
|
-
return
|
|
3299
|
+
return readdirSync9(dir);
|
|
3249
3300
|
} catch {
|
|
3250
3301
|
return [];
|
|
3251
3302
|
}
|
|
@@ -3811,7 +3862,7 @@ import { existsSync as existsSync26, statSync as statSync6 } from "node:fs";
|
|
|
3811
3862
|
import { dirname as dirname5, join as join31 } from "node:path";
|
|
3812
3863
|
|
|
3813
3864
|
// src/commands.redact.subtree.ts
|
|
3814
|
-
import { existsSync as existsSync25, lstatSync as lstatSync8, readFileSync as readFileSync11, readdirSync as
|
|
3865
|
+
import { existsSync as existsSync25, lstatSync as lstatSync8, readFileSync as readFileSync11, readdirSync as readdirSync10, statSync as statSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3815
3866
|
import { join as join30 } from "node:path";
|
|
3816
3867
|
init_utils_fs();
|
|
3817
3868
|
init_utils();
|
|
@@ -3819,7 +3870,7 @@ function collectFiles(dir, out) {
|
|
|
3819
3870
|
if (!existsSync25(dir)) return;
|
|
3820
3871
|
const st = lstatSync8(dir);
|
|
3821
3872
|
if (!st.isDirectory()) return;
|
|
3822
|
-
for (const entry of
|
|
3873
|
+
for (const entry of readdirSync10(dir)) {
|
|
3823
3874
|
const abs = join30(dir, entry);
|
|
3824
3875
|
const lst = lstatSync8(abs);
|
|
3825
3876
|
if (lst.isSymbolicLink()) continue;
|
|
@@ -4580,7 +4631,7 @@ function reportOptionalDeps(section2, run = execFileSync12) {
|
|
|
4580
4631
|
} else {
|
|
4581
4632
|
addItem(
|
|
4582
4633
|
section2,
|
|
4583
|
-
`${yellow(warnGlyph)} gh: not installed (optional;
|
|
4634
|
+
`${yellow(warnGlyph)} gh: not installed (optional; nomad init needs it to create the sync repo and auto-disable Actions, and the Actions-drift check uses it)`
|
|
4584
4635
|
);
|
|
4585
4636
|
}
|
|
4586
4637
|
reportFetcherRow(section2, run);
|
|
@@ -4736,6 +4787,7 @@ function compactSections(sections) {
|
|
|
4736
4787
|
function gatherDoctorSections(opts) {
|
|
4737
4788
|
const host = section("Environment");
|
|
4738
4789
|
reportHostAndPaths(host);
|
|
4790
|
+
reportHostKeyAlignment(host);
|
|
4739
4791
|
reportRepoState(host);
|
|
4740
4792
|
const links = section("Shared links");
|
|
4741
4793
|
const mapPath = join36(repoHome(), "path-map.json");
|
|
@@ -4836,7 +4888,7 @@ function parseDoctorArgs(args) {
|
|
|
4836
4888
|
// src/commands.drop-session.ts
|
|
4837
4889
|
init_config();
|
|
4838
4890
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
4839
|
-
import { existsSync as existsSync32, readdirSync as
|
|
4891
|
+
import { existsSync as existsSync32, readdirSync as readdirSync11, statSync as statSync8 } from "node:fs";
|
|
4840
4892
|
import { join as join38, relative as relative4 } from "node:path";
|
|
4841
4893
|
|
|
4842
4894
|
// src/commands.drop-session.git.ts
|
|
@@ -4949,7 +5001,7 @@ function cmdDropSession(id) {
|
|
|
4949
5001
|
}
|
|
4950
5002
|
function collectMatches(repoProjects, id, repo) {
|
|
4951
5003
|
const matches = [];
|
|
4952
|
-
for (const logical of
|
|
5004
|
+
for (const logical of readdirSync11(repoProjects)) {
|
|
4953
5005
|
const candidate = join38(repoProjects, logical, `${id}.jsonl`);
|
|
4954
5006
|
if (existsSync32(candidate)) {
|
|
4955
5007
|
matches.push(relative4(repo, candidate));
|
|
@@ -5086,12 +5138,12 @@ init_config();
|
|
|
5086
5138
|
|
|
5087
5139
|
// src/extras-sync.ts
|
|
5088
5140
|
init_config();
|
|
5089
|
-
import { existsSync as existsSync35 } from "node:fs";
|
|
5141
|
+
import { existsSync as existsSync35, statSync as statSync9 } from "node:fs";
|
|
5090
5142
|
import { join as join42 } from "node:path";
|
|
5091
5143
|
|
|
5092
5144
|
// src/extras-sync.core.ts
|
|
5093
5145
|
init_config();
|
|
5094
|
-
import { cpSync as cpSync6, existsSync as existsSync33, lstatSync as lstatSync9, readdirSync as
|
|
5146
|
+
import { cpSync as cpSync6, existsSync as existsSync33, lstatSync as lstatSync9, readdirSync as readdirSync12, rmSync as rmSync11 } from "node:fs";
|
|
5095
5147
|
import { basename, join as join39 } from "node:path";
|
|
5096
5148
|
init_utils();
|
|
5097
5149
|
init_utils_json();
|
|
@@ -5132,7 +5184,7 @@ function* eachExtrasTarget(v, counts) {
|
|
|
5132
5184
|
}
|
|
5133
5185
|
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
5134
5186
|
if (!existsSync33(dst)) return;
|
|
5135
|
-
for (const name of
|
|
5187
|
+
for (const name of readdirSync12(src)) {
|
|
5136
5188
|
if (isExcluded(name)) continue;
|
|
5137
5189
|
const dstPath = join39(dst, name);
|
|
5138
5190
|
const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
|
|
@@ -5180,7 +5232,7 @@ function copyExtrasFiltered(src, dst, blockSet) {
|
|
|
5180
5232
|
});
|
|
5181
5233
|
}
|
|
5182
5234
|
function prunePreservingDenied(src, dst, blockSet) {
|
|
5183
|
-
for (const name of
|
|
5235
|
+
for (const name of readdirSync12(dst)) {
|
|
5184
5236
|
if (isDeniedName(blockSet, name)) continue;
|
|
5185
5237
|
const dstPath = join39(dst, name);
|
|
5186
5238
|
const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
|
|
@@ -5211,7 +5263,7 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
5211
5263
|
});
|
|
5212
5264
|
}
|
|
5213
5265
|
function prunePreservingBy(src, dst, isPreserved) {
|
|
5214
|
-
for (const name of
|
|
5266
|
+
for (const name of readdirSync12(dst)) {
|
|
5215
5267
|
if (isPreserved(name)) continue;
|
|
5216
5268
|
const dstPath = join39(dst, name);
|
|
5217
5269
|
const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
|
|
@@ -5248,7 +5300,7 @@ init_utils_json();
|
|
|
5248
5300
|
|
|
5249
5301
|
// src/extras-sync.remap.ts
|
|
5250
5302
|
init_config();
|
|
5251
|
-
import { existsSync as existsSync34, mkdirSync as mkdirSync7, readdirSync as
|
|
5303
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync7, readdirSync as readdirSync13, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
|
|
5252
5304
|
import { dirname as dirname7, join as join41, sep as sep7 } from "node:path";
|
|
5253
5305
|
|
|
5254
5306
|
// src/extras-sync.planning-diff.ts
|
|
@@ -5349,7 +5401,7 @@ function pruneEmptyAncestors(target, planningRoot) {
|
|
|
5349
5401
|
let dir = dirname7(target);
|
|
5350
5402
|
while (dir !== planningRoot && dir.startsWith(planningRoot + sep7)) {
|
|
5351
5403
|
try {
|
|
5352
|
-
if (
|
|
5404
|
+
if (readdirSync13(dir).length > 0) break;
|
|
5353
5405
|
rmSync12(dir, { recursive: true, force: true });
|
|
5354
5406
|
} catch {
|
|
5355
5407
|
break;
|
|
@@ -5482,6 +5534,15 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5482
5534
|
}
|
|
5483
5535
|
|
|
5484
5536
|
// src/extras-sync.ts
|
|
5537
|
+
function divergenceWarnLine(o) {
|
|
5538
|
+
const kind = o.isDir ? "folder" : "file";
|
|
5539
|
+
const name = o.isDir ? `${o.dirname}/` : o.dirname;
|
|
5540
|
+
const one = o.count === 1;
|
|
5541
|
+
const fileCount = one ? "1 file" : `${o.count} files`;
|
|
5542
|
+
const them = one ? "it" : "them";
|
|
5543
|
+
const yours = one ? "your current file is" : "your current files are";
|
|
5544
|
+
return `local ${kind} ${name} in repo ${o.logical} differs from the synced copy in ${fileCount}; the next pull step will overwrite ${them} with the synced version (${yours} backed up to ${o.projectBackupRoot}/)`;
|
|
5545
|
+
}
|
|
5485
5546
|
function divergenceCheckExtras(ts) {
|
|
5486
5547
|
const v = loadValidatedExtras({});
|
|
5487
5548
|
if (v === null) return;
|
|
@@ -5496,7 +5557,13 @@ function divergenceCheckExtras(ts) {
|
|
|
5496
5557
|
if (diff.length === 0) continue;
|
|
5497
5558
|
const projectBackupRoot = join42(backupRoot, encodePath(localRoot));
|
|
5498
5559
|
warn(
|
|
5499
|
-
|
|
5560
|
+
divergenceWarnLine({
|
|
5561
|
+
dirname: dirname8,
|
|
5562
|
+
logical,
|
|
5563
|
+
isDir: statSync9(local).isDirectory(),
|
|
5564
|
+
count: diff.length,
|
|
5565
|
+
projectBackupRoot
|
|
5566
|
+
})
|
|
5500
5567
|
);
|
|
5501
5568
|
for (const f of diff) warn(` ${f}`);
|
|
5502
5569
|
}
|
|
@@ -5504,7 +5571,7 @@ function divergenceCheckExtras(ts) {
|
|
|
5504
5571
|
|
|
5505
5572
|
// src/skills-sync.ts
|
|
5506
5573
|
init_config();
|
|
5507
|
-
import { existsSync as existsSync36, lstatSync as lstatSync10, mkdirSync as mkdirSync8, readdirSync as
|
|
5574
|
+
import { existsSync as existsSync36, lstatSync as lstatSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync14, rmSync as rmSync13 } from "node:fs";
|
|
5508
5575
|
import { join as join43 } from "node:path";
|
|
5509
5576
|
init_utils_fs();
|
|
5510
5577
|
function isGsdOwned(name) {
|
|
@@ -5514,7 +5581,7 @@ function isSkillExcluded(name) {
|
|
|
5514
5581
|
return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
|
|
5515
5582
|
}
|
|
5516
5583
|
function copySkillsPush(src, dst) {
|
|
5517
|
-
const srcNames =
|
|
5584
|
+
const srcNames = readdirSync14(src, { encoding: "utf8" });
|
|
5518
5585
|
const blockSet = /* @__PURE__ */ new Set([
|
|
5519
5586
|
...srcNames.filter((n) => isGsdOwned(n)),
|
|
5520
5587
|
...ALWAYS_NEVER_SYNC
|
|
@@ -6316,7 +6383,7 @@ init_color();
|
|
|
6316
6383
|
init_config();
|
|
6317
6384
|
init_config_sharedDirs_guard();
|
|
6318
6385
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6319
|
-
import { copyFileSync, existsSync as existsSync39, mkdirSync as mkdirSync10, readdirSync as
|
|
6386
|
+
import { copyFileSync, existsSync as existsSync39, mkdirSync as mkdirSync10, readdirSync as readdirSync15, rmSync as rmSync14 } from "node:fs";
|
|
6320
6387
|
import { homedir as homedir5 } from "node:os";
|
|
6321
6388
|
import { join as join46 } from "node:path";
|
|
6322
6389
|
init_push_leak_verdict();
|
|
@@ -6336,7 +6403,7 @@ function stageSessions(tmpRoot, map) {
|
|
|
6336
6403
|
const localProjects = join46(claudeHome(), "projects");
|
|
6337
6404
|
if (!existsSync39(localProjects)) return 0;
|
|
6338
6405
|
let staged = 0;
|
|
6339
|
-
for (const dir of
|
|
6406
|
+
for (const dir of readdirSync15(localProjects)) {
|
|
6340
6407
|
const logical = reverse.get(dir);
|
|
6341
6408
|
if (!logical) continue;
|
|
6342
6409
|
copyDirJsonlOnly(join46(localProjects, dir), join46(tmpRoot, "shared", "projects", logical));
|
|
@@ -6427,9 +6494,9 @@ function reportSettingsAheadDrift(repo) {
|
|
|
6427
6494
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
6428
6495
|
const { promotable } = partitionByCaptureExclusion(ahead);
|
|
6429
6496
|
if (promotable.length === 0) return;
|
|
6430
|
-
const
|
|
6497
|
+
const { phrase, pronoun, verb } = describeSettings(promotable);
|
|
6431
6498
|
warn(
|
|
6432
|
-
`settings.json has
|
|
6499
|
+
`your settings.json has ${phrase} that ${verb} not yet in the repo; run 'nomad capture-settings' to save ${pronoun} (or 'nomad capture-settings --host' for host-specific values).`
|
|
6433
6500
|
);
|
|
6434
6501
|
} catch {
|
|
6435
6502
|
}
|
|
@@ -6697,7 +6764,7 @@ init_config();
|
|
|
6697
6764
|
init_utils();
|
|
6698
6765
|
init_utils_fs();
|
|
6699
6766
|
init_utils_json();
|
|
6700
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync42, rmSync as rmSync15, statSync as
|
|
6767
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync42, rmSync as rmSync15, statSync as statSync10 } from "node:fs";
|
|
6701
6768
|
import { join as join49 } from "node:path";
|
|
6702
6769
|
function snapshotIntoShared(map) {
|
|
6703
6770
|
const repo = repoHome();
|
|
@@ -6706,7 +6773,7 @@ function snapshotIntoShared(map) {
|
|
|
6706
6773
|
const src = join49(claude, name);
|
|
6707
6774
|
if (!existsSync42(src)) continue;
|
|
6708
6775
|
const dst = join49(repo, "shared", name);
|
|
6709
|
-
if (
|
|
6776
|
+
if (statSync10(src).isDirectory()) {
|
|
6710
6777
|
const gk = join49(dst, ".gitkeep");
|
|
6711
6778
|
if (existsSync42(gk)) rmSync15(gk);
|
|
6712
6779
|
cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
@@ -6747,6 +6814,9 @@ function preflightConflict(repoHome2) {
|
|
|
6747
6814
|
}
|
|
6748
6815
|
return null;
|
|
6749
6816
|
}
|
|
6817
|
+
function isAlreadyInitialized(repoHome2) {
|
|
6818
|
+
return preflightConflict(repoHome2) !== null;
|
|
6819
|
+
}
|
|
6750
6820
|
function cmdInit(opts = {}) {
|
|
6751
6821
|
const snapshot = opts.snapshot === true;
|
|
6752
6822
|
const keepActions = opts.keepActions === true;
|
|
@@ -6839,6 +6909,47 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
6839
6909
|
}
|
|
6840
6910
|
}
|
|
6841
6911
|
|
|
6912
|
+
// src/init.prompt.ts
|
|
6913
|
+
init_config();
|
|
6914
|
+
init_utils();
|
|
6915
|
+
import { existsSync as existsSync44, readdirSync as readdirSync16, statSync as statSync11 } from "node:fs";
|
|
6916
|
+
import { join as join51 } from "node:path";
|
|
6917
|
+
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
6918
|
+
function nonEmptyExists(path) {
|
|
6919
|
+
if (!existsSync44(path)) return false;
|
|
6920
|
+
try {
|
|
6921
|
+
if (statSync11(path).isDirectory()) return readdirSync16(path).length > 0;
|
|
6922
|
+
return true;
|
|
6923
|
+
} catch {
|
|
6924
|
+
return false;
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
function hasExistingClaudeConfig(claudeHome2) {
|
|
6928
|
+
if (existsSync44(join51(claudeHome2, "settings.json"))) return true;
|
|
6929
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join51(claudeHome2, name)));
|
|
6930
|
+
}
|
|
6931
|
+
async function confirmSnapshotDefault(claudeHome2) {
|
|
6932
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
6933
|
+
log(
|
|
6934
|
+
`tip: ${claudeHome2} already has config; re-run 'nomad init --snapshot' to seed the repo from it.`
|
|
6935
|
+
);
|
|
6936
|
+
return false;
|
|
6937
|
+
}
|
|
6938
|
+
log(`Found existing config in ${claudeHome2}.`);
|
|
6939
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
6940
|
+
try {
|
|
6941
|
+
const answer = await rl.question("Seed the new repo from it? [Y/n] ");
|
|
6942
|
+
return !/^n(o)?$/i.test(answer.trim());
|
|
6943
|
+
} finally {
|
|
6944
|
+
rl.close();
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
async function resolveSnapshotChoice(flagSnapshot, claudeHome2, confirm = confirmSnapshotDefault) {
|
|
6948
|
+
if (flagSnapshot) return true;
|
|
6949
|
+
if (!hasExistingClaudeConfig(claudeHome2)) return false;
|
|
6950
|
+
return confirm(claudeHome2);
|
|
6951
|
+
}
|
|
6952
|
+
|
|
6842
6953
|
// src/nomad.dispatch.helpers.ts
|
|
6843
6954
|
var REJECT = { ok: false, advance: 0 };
|
|
6844
6955
|
function applyBool(seen, set) {
|
|
@@ -7098,7 +7209,7 @@ function parsePushArgs(argv) {
|
|
|
7098
7209
|
// package.json
|
|
7099
7210
|
var package_default = {
|
|
7100
7211
|
name: "claude-nomad",
|
|
7101
|
-
version: "0.
|
|
7212
|
+
version: "0.55.0",
|
|
7102
7213
|
type: "module",
|
|
7103
7214
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
7104
7215
|
keywords: [
|
|
@@ -7230,7 +7341,8 @@ var DEFAULT_HELP = [
|
|
|
7230
7341
|
" init",
|
|
7231
7342
|
"Create a private GitHub repo via gh (if none exists), scaffold shared/, hosts/, path-map."
|
|
7232
7343
|
),
|
|
7233
|
-
row(" --snapshot", "
|
|
7344
|
+
row(" --snapshot", "Seed shared/ from the current ~/.claude/. Without this flag, init"),
|
|
7345
|
+
cont("offers the same seed interactively when it detects existing config."),
|
|
7234
7346
|
row(" --keep-actions", "Skip auto-disabling GitHub Actions on the private repo."),
|
|
7235
7347
|
row(
|
|
7236
7348
|
" --repo <name>",
|
|
@@ -7308,7 +7420,8 @@ var DEFAULT_HELP = [
|
|
|
7308
7420
|
"",
|
|
7309
7421
|
row(" --version", "Print the installed CLI version as bare semver to stdout; exits 0."),
|
|
7310
7422
|
"",
|
|
7311
|
-
"
|
|
7423
|
+
"You edit the sync repo (~/claude-nomad/); nomad regenerates ~/.claude/ on every",
|
|
7424
|
+
"pull. Run `nomad doctor` to validate your setup. Edit shared/ or hosts/<HOST>.json",
|
|
7312
7425
|
"in the repo, never ~/.claude/settings.json directly (it is regenerated on",
|
|
7313
7426
|
"every pull)."
|
|
7314
7427
|
].join("\n");
|
|
@@ -7317,15 +7430,15 @@ var DEFAULT_HELP = [
|
|
|
7317
7430
|
init_config();
|
|
7318
7431
|
init_utils();
|
|
7319
7432
|
init_utils_json();
|
|
7320
|
-
import { existsSync as
|
|
7321
|
-
import { join as
|
|
7433
|
+
import { existsSync as existsSync45, readFileSync as readFileSync14, readdirSync as readdirSync17 } from "node:fs";
|
|
7434
|
+
import { join as join52 } from "node:path";
|
|
7322
7435
|
function resumeCmd(sessionId) {
|
|
7323
7436
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
7324
7437
|
fail(`invalid session id: ${sessionId}`);
|
|
7325
7438
|
process.exit(1);
|
|
7326
7439
|
}
|
|
7327
|
-
const projectsRoot =
|
|
7328
|
-
if (!
|
|
7440
|
+
const projectsRoot = join52(claudeHome(), "projects");
|
|
7441
|
+
if (!existsSync45(projectsRoot)) {
|
|
7329
7442
|
fail(`${projectsRoot} does not exist`);
|
|
7330
7443
|
process.exit(1);
|
|
7331
7444
|
}
|
|
@@ -7339,8 +7452,8 @@ function resumeCmd(sessionId) {
|
|
|
7339
7452
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
7340
7453
|
process.exit(1);
|
|
7341
7454
|
}
|
|
7342
|
-
const mapPath =
|
|
7343
|
-
if (!
|
|
7455
|
+
const mapPath = join52(repoHome(), "path-map.json");
|
|
7456
|
+
if (!existsSync45(mapPath)) {
|
|
7344
7457
|
fail("path-map.json missing");
|
|
7345
7458
|
process.exit(1);
|
|
7346
7459
|
}
|
|
@@ -7362,9 +7475,9 @@ function resumeCmd(sessionId) {
|
|
|
7362
7475
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
7363
7476
|
}
|
|
7364
7477
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
7365
|
-
for (const dir of
|
|
7366
|
-
const candidate =
|
|
7367
|
-
if (
|
|
7478
|
+
for (const dir of readdirSync17(projectsRoot)) {
|
|
7479
|
+
const candidate = join52(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
7480
|
+
if (existsSync45(candidate)) return candidate;
|
|
7368
7481
|
}
|
|
7369
7482
|
return null;
|
|
7370
7483
|
}
|
|
@@ -7447,8 +7560,9 @@ try {
|
|
|
7447
7560
|
console.error("usage: nomad init [--snapshot] [--keep-actions] [--repo <name>]");
|
|
7448
7561
|
process.exit(1);
|
|
7449
7562
|
}
|
|
7563
|
+
const snapshot = isAlreadyInitialized(repoHome()) ? initArgs.snapshot : await resolveSnapshotChoice(initArgs.snapshot, claudeHome());
|
|
7450
7564
|
cmdInit({
|
|
7451
|
-
snapshot
|
|
7565
|
+
snapshot,
|
|
7452
7566
|
keepActions: initArgs.keepActions,
|
|
7453
7567
|
repoName: initArgs.repoName
|
|
7454
7568
|
});
|
package/package.json
CHANGED