claude-nomad 0.56.2 → 0.57.1

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 CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.57.1](https://github.com/funkadelic/claude-nomad/compare/v0.57.0...v0.57.1) (2026-07-08)
4
+
5
+
6
+ ### Fixed
7
+
8
+ * catch EPERM on directory fsync for Windows compatibility ([#359](https://github.com/funkadelic/claude-nomad/issues/359)) ([d4266b2](https://github.com/funkadelic/claude-nomad/commit/d4266b2ca60a2fc2747628340726d40c774b5bfe))
9
+
10
+
11
+ ### Changed
12
+
13
+ * accept hyphenated commit types in PR title gate ([#393](https://github.com/funkadelic/claude-nomad/issues/393)) ([3be650c](https://github.com/funkadelic/claude-nomad/commit/3be650cb5fffb2b70d058e78672a5e9d9568f310))
14
+ * **doctor:** sync settings schema keys ([#389](https://github.com/funkadelic/claude-nomad/issues/389)) ([c0b91c0](https://github.com/funkadelic/claude-nomad/commit/c0b91c0d3e958c79dc0f445bd0c48e43c6c38d5f))
15
+ * group codeql-action bumps into a single dependabot PR ([#391](https://github.com/funkadelic/claude-nomad/issues/391)) ([520e8be](https://github.com/funkadelic/claude-nomad/commit/520e8beec96a6e38b1d3bf887f430d7bb5a294e5))
16
+
17
+
18
+ ### Dependencies
19
+
20
+ * bump github/codeql-action from 4.36.2 to 4.36.3 ([#384](https://github.com/funkadelic/claude-nomad/issues/384)) ([f3e630c](https://github.com/funkadelic/claude-nomad/commit/f3e630c11cd6fca5bac5d17f7bc96fa4c56b7dac))
21
+ * bump starlight-links-validator ([#387](https://github.com/funkadelic/claude-nomad/issues/387)) ([aee5c0f](https://github.com/funkadelic/claude-nomad/commit/aee5c0fcdfeae0e44940c862a3201c22bc73943d))
22
+ * bump the dev-dependencies group across 1 directory with 3 updates ([#392](https://github.com/funkadelic/claude-nomad/issues/392)) ([f2c1107](https://github.com/funkadelic/claude-nomad/commit/f2c1107a179fc3b1d7d9f89675bf01281d085739))
23
+ * bump the prod-dependencies group in /docs-site with 3 updates ([#388](https://github.com/funkadelic/claude-nomad/issues/388)) ([5af66a7](https://github.com/funkadelic/claude-nomad/commit/5af66a7e725ef3dd936cb446280a7dab6c4fd3cb))
24
+ * **deps-dev:** bump the dev-dependencies group with 5 updates ([#386](https://github.com/funkadelic/claude-nomad/issues/386)) ([f9b9382](https://github.com/funkadelic/claude-nomad/commit/f9b9382e808a592d235e068a91678e84dd2d518a))
25
+
26
+ ## [0.57.0](https://github.com/funkadelic/claude-nomad/compare/v0.56.2...v0.57.0) (2026-07-03)
27
+
28
+
29
+ ### Added
30
+
31
+ * **docs-site:** add starlight-llms-txt plugin ([#378](https://github.com/funkadelic/claude-nomad/issues/378)) ([0604327](https://github.com/funkadelic/claude-nomad/commit/0604327ffa69abbee0b70183a49416ec14a3306c))
32
+ * **sync:** add unified nomad sync command ([#380](https://github.com/funkadelic/claude-nomad/issues/380)) ([e60a05e](https://github.com/funkadelic/claude-nomad/commit/e60a05e62179751652d70e54ba3deb8b6b7c4859))
33
+
34
+
35
+ ### Fixed
36
+
37
+ * **plugin:** tolerate a user-supplied --dry-run in push and sync wrappers ([#381](https://github.com/funkadelic/claude-nomad/issues/381)) ([7ce0a99](https://github.com/funkadelic/claude-nomad/commit/7ce0a99ae53cd8737b5e03bb0f5f730447b1108d))
38
+
39
+
40
+ ### Changed
41
+
42
+ * remove vestigial private-repo guards from workflows ([#383](https://github.com/funkadelic/claude-nomad/issues/383)) ([31e52b8](https://github.com/funkadelic/claude-nomad/commit/31e52b834ca55204dac29fcec6ceee35605f818b))
43
+ * scrub internal planning IDs from source and test comments ([#382](https://github.com/funkadelic/claude-nomad/issues/382)) ([b024b7b](https://github.com/funkadelic/claude-nomad/commit/b024b7b1b877af347db7ca1754751ede73b5879f))
44
+
3
45
  ## [0.56.2](https://github.com/funkadelic/claude-nomad/compare/v0.56.1...v0.56.2) (2026-07-01)
4
46
 
5
47
 
package/README.md CHANGED
@@ -13,8 +13,9 @@
13
13
 
14
14
  Open Claude Code on a second machine and it is a blank slate: none of your custom skills, slash
15
15
  commands, tuned settings, or past conversations. **claude-nomad** keeps all of it in sync through a
16
- private Git repo you control. `nomad push` on one machine, `nomad pull` on the next, and everything
17
- is there, conversations included.
16
+ private Git repo you control. Run `nomad sync` on any machine and everything is there, conversations
17
+ included; it pulls in your latest config first, then publishes your local changes, so you never have
18
+ to remember which one to run first.
18
19
 
19
20
  Not dotfiles, not rsync. **claude-nomad** understands Claude Code's state, so your session history
20
21
  survives different file paths and your secrets never ride along.
@@ -117,14 +118,19 @@ Everyday loop on any host:
117
118
 
118
119
  ```bash
119
120
  $ nomad doctor # confirm setup
120
- $ nomad pull # apply config to ~/.claude/
121
- $ nomad push # publish local changes (sessions, settings)
121
+ $ nomad sync # pull config, then publish local changes, in one step
122
122
  ```
123
123
 
124
- Pull before you push whenever both machines may have changed. Sync is last-write-wins, so pushing
125
- stale local state over newer remote state silently overwrites it. The
126
- [FAQ](https://funkadelic.github.io/claude-nomad/faq/) covers the full push/pull order when both
127
- sides have changed.
124
+ `nomad sync` is the command to reach for day to day: it always pulls first (so changes from your
125
+ other machines land before anything is pushed, and work that exists only on this machine is kept,
126
+ not deleted) and then pushes, under one lock, so there is no ordering to remember. `nomad pull` and
127
+ `nomad push` are still available as lower-level commands for cases `sync` does not cover: recovering
128
+ a wedged repo with `nomad pull --force-remote`, or resolving a detected secret without the
129
+ interactive menu via `nomad push --redact-all` / `--allow` / `--allow-all` (see
130
+ [Changing settings](#changing-settings) and
131
+ [Recovery flows](https://funkadelic.github.io/claude-nomad/recovery/)). The
132
+ [FAQ](https://funkadelic.github.io/claude-nomad/faq/) covers what `sync` does under the hood and the
133
+ push/pull order it enforces.
128
134
 
129
135
  ### Make your sessions follow you
130
136
 
@@ -216,10 +222,10 @@ first (minimum version `>= 0.35.0`), then add the plugin.
216
222
  /plugin install nomad@claude-nomad
217
223
  ```
218
224
 
219
- It adds `/nomad:pull`, `/nomad:diff`, `/nomad:push` (preview only), `/nomad:doctor`, and
220
- `/nomad:clean`, plus a session-start drift check. The plugin versions independently from the CLI,
221
- but requires nomad `>= 0.35.0` because it calls recent subcommands (`nomad diff`,
222
- `nomad clean --backups`) and reads the doctor command's status output. See the
225
+ It adds `/nomad:sync` (preview only), `/nomad:pull`, `/nomad:diff`, `/nomad:push` (preview only),
226
+ `/nomad:doctor`, and `/nomad:clean`, plus a session-start drift check. The plugin versions
227
+ independently from the CLI, but requires nomad `>= 0.35.0` because it calls recent subcommands
228
+ (`nomad diff`, `nomad clean --backups`) and reads the doctor command's status output. See the
223
229
  [plugin guide](https://funkadelic.github.io/claude-nomad/plugin/) for details.
224
230
 
225
231
  ## Requirements
package/dist/nomad.mjs CHANGED
@@ -214,12 +214,13 @@ function gitOrFatal(args, context, cwd) {
214
214
  throw new NomadFatal(`${context} failed`);
215
215
  }
216
216
  }
217
- var log, warn, fail, item, NomadFatal, die, gitStatusPorcelainZ;
217
+ var log, ok, warn, fail, item, NomadFatal, die, gitStatusPorcelainZ;
218
218
  var init_utils = __esm({
219
219
  "src/utils.ts"() {
220
220
  "use strict";
221
221
  init_color();
222
222
  log = (msg) => console.log(`${dim(infoGlyph)} ${msg}`);
223
+ ok = (msg) => console.log(`${green(okGlyph)} ${msg}`);
223
224
  warn = (msg) => {
224
225
  console.error(`${yellow(warnGlyph)} ${msg}`);
225
226
  };
@@ -294,7 +295,10 @@ var init_settings_keys = __esm({
294
295
  "use strict";
295
296
  SCHEMA_KEYS = [
296
297
  "$schema",
298
+ "advisorModel",
297
299
  "agent",
300
+ "agentPushNotifEnabled",
301
+ "allowAllClaudeAiMcps",
298
302
  "allowedChannelPlugins",
299
303
  "allowedHttpHookUrls",
300
304
  "allowedMcpServers",
@@ -304,42 +308,64 @@ var init_settings_keys = __esm({
304
308
  "alwaysThinkingEnabled",
305
309
  "apiKeyHelper",
306
310
  "attribution",
311
+ "autoCompactEnabled",
307
312
  "autoMemoryDirectory",
308
313
  "autoMemoryEnabled",
309
314
  "autoMode",
315
+ "autoScrollEnabled",
310
316
  "autoUpdatesChannel",
311
317
  "availableModels",
318
+ "awaySummaryEnabled",
312
319
  "awsAuthRefresh",
313
320
  "awsCredentialExport",
321
+ "axScreenReader",
314
322
  "blockedMarketplaces",
315
323
  "channelsEnabled",
324
+ "claudeMd",
316
325
  "claudeMdExcludes",
317
326
  "cleanupPeriodDays",
318
327
  "companyAnnouncements",
319
328
  "defaultShell",
320
329
  "deniedMcpServers",
330
+ "disableAgentView",
321
331
  "disableAllHooks",
332
+ "disableArtifact",
333
+ "disableAutoMode",
334
+ "disableBundledSkills",
335
+ "disableClaudeAiConnectors",
322
336
  "disableDeepLinkRegistration",
323
337
  "disabledMcpjsonServers",
338
+ "disableRemoteControl",
324
339
  "disableSkillShellExecution",
340
+ "disableWorkflows",
341
+ "editorMode",
325
342
  "effortLevel",
326
343
  "enableAllProjectMcpServers",
327
344
  "enabledMcpjsonServers",
328
345
  "enabledPlugins",
346
+ "enforceAvailableModels",
329
347
  "env",
330
348
  "extraKnownMarketplaces",
349
+ "fallbackModel",
331
350
  "fastMode",
332
351
  "fastModePerSessionOptIn",
333
352
  "feedbackSurveyRate",
353
+ "fileCheckpointingEnabled",
334
354
  "fileSuggestion",
355
+ "footerLinksRegexes",
335
356
  "forceLoginMethod",
336
357
  "forceLoginOrgUUID",
337
358
  "forceRemoteSettingsRefresh",
359
+ "gcpAuthRefresh",
338
360
  "hooks",
339
361
  "httpHookAllowedEnvVars",
340
362
  "includeCoAuthoredBy",
341
363
  "includeGitInstructions",
364
+ "inputNeededNotifEnabled",
342
365
  "language",
366
+ "leftArrowOpensAgents",
367
+ "managedMcpServers",
368
+ "maxSkillDescriptionChars",
343
369
  "minimumVersion",
344
370
  "model",
345
371
  "modelOverrides",
@@ -349,14 +375,22 @@ var init_settings_keys = __esm({
349
375
  "permissions",
350
376
  "plansDirectory",
351
377
  "pluginConfigs",
378
+ "pluginSuggestionMarketplaces",
352
379
  "pluginTrustMessage",
380
+ "policyHelper",
381
+ "preferredNotifChannel",
353
382
  "prefersReducedMotion",
354
383
  "prUrlTemplate",
384
+ "remoteControlAtStartup",
385
+ "requiredMaximumVersion",
386
+ "requiredMinimumVersion",
355
387
  "respectGitignore",
388
+ "respondToBashCommands",
356
389
  "sandbox",
357
390
  "showClearContextOnPlanAccept",
358
391
  "showThinkingSummaries",
359
392
  "showTurnDuration",
393
+ "skillListingBudgetFraction",
360
394
  "skillOverrides",
361
395
  "skipDangerousModePermissionPrompt",
362
396
  "skippedMarketplaces",
@@ -365,21 +399,28 @@ var init_settings_keys = __esm({
365
399
  "spinnerTipsEnabled",
366
400
  "spinnerTipsOverride",
367
401
  "spinnerVerbs",
402
+ "sshConfigs",
403
+ "sshHostAllowlist",
368
404
  "statusLine",
369
405
  "strictKnownMarketplaces",
370
406
  "strictPluginOnlyCustomization",
371
407
  "subagentStatusLine",
408
+ "syntaxHighlightingDisabled",
372
409
  "teammateMode",
373
410
  "terminalProgressBarEnabled",
411
+ "theme",
374
412
  "tui",
375
413
  "useAutoModeDuringPlan",
414
+ "verbose",
376
415
  "viewMode",
416
+ "voice",
377
417
  "voiceEnabled",
418
+ "wheelScrollAccelerationEnabled",
419
+ "workflowKeywordTriggerEnabled",
378
420
  "worktree",
379
421
  "wslInheritsWindowsSettings"
380
422
  ];
381
423
  APP_ONLY_KEYS = [
382
- "agentPushNotifEnabled",
383
424
  "agents",
384
425
  "apiKeyHelperTimeoutMs",
385
426
  "awsLoginRefresh",
@@ -387,7 +428,6 @@ var init_settings_keys = __esm({
387
428
  "awsRetryMode",
388
429
  "disableNonEssentialModelCalls",
389
430
  "enabledExperimentalFeatures",
390
- "inputNeededNotifEnabled",
391
431
  "installMethod",
392
432
  "pluginGroups",
393
433
  "pluginRepositoryEnabled",
@@ -395,8 +435,7 @@ var init_settings_keys = __esm({
395
435
  "proxy",
396
436
  "skipAutoPermissionPrompt",
397
437
  "statsig",
398
- "subagents",
399
- "theme"
438
+ "subagents"
400
439
  ];
401
440
  KNOWN_SETTINGS_KEYS = /* @__PURE__ */ new Set([...SCHEMA_KEYS, ...APP_ONLY_KEYS]);
402
441
  }
@@ -468,7 +507,7 @@ var init_config = __esm({
468
507
  "hosts/",
469
508
  "path-map.json",
470
509
  ".gitleaksignore",
471
- // written by nomad push Allow action (D-04)
510
+ // written by nomad push Allow action
472
511
  ".gitleaks.overlay.toml"
473
512
  // user-owned gitleaks allowlist overlay layered on the bundled base
474
513
  ];
@@ -583,6 +622,8 @@ function writeJsonAtomic(path, data) {
583
622
  const dirFd = openSync(dirname(path), "r");
584
623
  try {
585
624
  fsyncSync(dirFd);
625
+ } catch (e) {
626
+ if (process.platform !== "win32" || e.code !== "EPERM") throw e;
586
627
  } finally {
587
628
  closeSync(dirFd);
588
629
  }
@@ -5804,7 +5845,7 @@ function remapExtrasPush(ts, opts = {}) {
5804
5845
  // Repo-only files survive; local edits propagate (overlay overwrites).
5805
5846
  // The filter prevents ALWAYS_NEVER_SYNC files from landing in the repo
5806
5847
  // working tree before the allow-list gate fires, eliminating the
5807
- // "residue wedges repeat push" regression (WR-02). The allow-list gate
5848
+ // "residue wedges repeat push" regression. The allow-list gate
5808
5849
  // (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
5809
5850
  // remains the hard security boundary.
5810
5851
  // All others: copyExtrasFiltered with per-extra denylist.
@@ -5871,16 +5912,18 @@ function divergenceWarnLine(o) {
5871
5912
  }
5872
5913
  function divergenceCheckExtras(ts, prePostHeads) {
5873
5914
  const v = loadValidatedExtras({});
5874
- if (v === null) return;
5915
+ if (v === null) return 0;
5875
5916
  const counts = { unmapped: 0, skipped: 0 };
5876
5917
  const backupRoot = join43(backupBase(), ts, "extras");
5877
5918
  const repo = repoHome();
5919
+ let divergedCount = 0;
5878
5920
  for (const { logical, localRoot, dirname: dirname10 } of eachExtrasTarget(v, counts)) {
5879
5921
  const local = join43(localRoot, dirname10);
5880
5922
  const repoEntry = join43(repo, "shared", "extras", logical, dirname10);
5881
5923
  if (!existsSync36(local) || !existsSync36(repoEntry)) continue;
5882
5924
  const modified = listDivergingModified(local, repoEntry);
5883
5925
  if (modified.length === 0) continue;
5926
+ divergedCount += modified.length;
5884
5927
  const projectBackupRoot = join43(backupRoot, encodePath(localRoot));
5885
5928
  warn(
5886
5929
  divergenceWarnLine({
@@ -5898,6 +5941,7 @@ function divergenceCheckExtras(ts, prePostHeads) {
5898
5941
  warn(keptDeleteWarnLine(logical, relToLocal));
5899
5942
  }
5900
5943
  }
5944
+ return divergedCount;
5901
5945
  }
5902
5946
 
5903
5947
  // src/skills-sync.ts
@@ -6458,7 +6502,7 @@ function capturePrePostHeads(repo, rebase) {
6458
6502
  if (pre === void 0 || post === void 0) return void 0;
6459
6503
  return { pre, post };
6460
6504
  }
6461
- function applyWetPull(ts, map, prePostHeads) {
6505
+ function buildWetPullSections(ts, map, prePostHeads) {
6462
6506
  applySharedLinks(ts, map);
6463
6507
  const { label } = regenerateSettings(ts);
6464
6508
  syncSkillsPull(ts);
@@ -6476,12 +6520,15 @@ function applyWetPull(ts, map, prePostHeads) {
6476
6520
  localOnly
6477
6521
  )
6478
6522
  );
6479
- renderTree([
6480
- buildSettingsSection(label),
6481
- buildSessionsSection(remapResult.pulled, remapResult.unmapped, localOnly),
6482
- buildExtrasSection(extrasResult.pulled, extrasResult.skipped),
6483
- summary
6484
- ]);
6523
+ return {
6524
+ sections: [
6525
+ buildSettingsSection(label),
6526
+ buildSessionsSection(remapResult.pulled, remapResult.unmapped, localOnly),
6527
+ buildExtrasSection(extrasResult.pulled, extrasResult.skipped),
6528
+ summary
6529
+ ],
6530
+ localOnly
6531
+ };
6485
6532
  }
6486
6533
  function handleWedge(repo, forceRemote) {
6487
6534
  const wedge = classifyWedge(repo);
@@ -6501,11 +6548,40 @@ function handleWedge(repo, forceRemote) {
6501
6548
  const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
6502
6549
  die(wedgeMarkerRunbookText(state));
6503
6550
  }
6504
- function cmdPull(opts = {}) {
6551
+ function runPullCore(opts = {}) {
6505
6552
  const dryRun = opts.dryRun === true;
6506
6553
  const forceRemote = opts.forceRemote === true;
6507
6554
  const repo = repoHome();
6508
6555
  const backup = backupBase();
6556
+ const ts = freshBackupTs(backup);
6557
+ handleWedge(repo, forceRemote);
6558
+ if (!dryRun) {
6559
+ const backupRoot = join46(backup, ts);
6560
+ try {
6561
+ mkdirSync10(backupRoot, { recursive: true });
6562
+ } catch (err) {
6563
+ die(`could not create backup dir: ${err.message}`);
6564
+ }
6565
+ }
6566
+ log(
6567
+ dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
6568
+ );
6569
+ const prePostHeads = capturePrePostHeads(repo, () => {
6570
+ gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
6571
+ });
6572
+ const mapPath = join46(repo, "path-map.json");
6573
+ const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
6574
+ const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
6575
+ if (dryRun) {
6576
+ computePreview(ts, map, "pull");
6577
+ log("dry-run complete; no mutation");
6578
+ return { tag: "dry" };
6579
+ }
6580
+ const { sections, localOnly } = buildWetPullSections(ts, map, prePostHeads);
6581
+ return { tag: "wet", sections, localOnly, divergedKeptLocal };
6582
+ }
6583
+ function cmdPull(opts = {}) {
6584
+ const repo = repoHome();
6509
6585
  if (!existsSync39(repo)) die(`repo not cloned at ${repo}`);
6510
6586
  if (!existsSync39(join46(repo, "shared", "settings.base.json"))) {
6511
6587
  die("repo not initialized; run 'nomad init' to scaffold");
@@ -6513,31 +6589,8 @@ function cmdPull(opts = {}) {
6513
6589
  const handle = acquireLock("pull");
6514
6590
  if (handle === null) process.exit(0);
6515
6591
  try {
6516
- const ts = freshBackupTs(backup);
6517
- handleWedge(repo, forceRemote);
6518
- if (!dryRun) {
6519
- const backupRoot = join46(backup, ts);
6520
- try {
6521
- mkdirSync10(backupRoot, { recursive: true });
6522
- } catch (err) {
6523
- die(`could not create backup dir: ${err.message}`);
6524
- }
6525
- }
6526
- log(
6527
- dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
6528
- );
6529
- const prePostHeads = capturePrePostHeads(repo, () => {
6530
- gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
6531
- });
6532
- const mapPath = join46(repo, "path-map.json");
6533
- const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
6534
- divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
6535
- if (dryRun) {
6536
- computePreview(ts, map, "pull");
6537
- log("dry-run complete; no mutation");
6538
- } else {
6539
- applyWetPull(ts, map, prePostHeads);
6540
- }
6592
+ const result = runPullCore(opts);
6593
+ if (result.tag === "wet") renderTree(result.sections);
6541
6594
  } catch (err) {
6542
6595
  if (err instanceof NomadFatal) {
6543
6596
  fail(err.message);
@@ -6962,7 +7015,7 @@ async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
6962
7015
  if (staged.length === toDrop.length) {
6963
7016
  log("nothing to commit");
6964
7017
  renderNoScanTree(st);
6965
- return;
7018
+ return "nothing";
6966
7019
  }
6967
7020
  st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: true });
6968
7021
  let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
@@ -6978,6 +7031,7 @@ async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
6978
7031
  warn(`could not write push manifest (next push will full-rescan): ${String(err)}`);
6979
7032
  }
6980
7033
  renderPushTree(st, verdict);
7034
+ return "pushed";
6981
7035
  }
6982
7036
  function runDryRunPreview(st, map, repo, selection) {
6983
7037
  st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: false });
@@ -6994,55 +7048,181 @@ function runDryRunPreview(st, map, repo, selection) {
6994
7048
  init_push_checks();
6995
7049
  init_utils();
6996
7050
  init_utils_fs();
6997
- async function cmdPush(opts = {}) {
7051
+ async function runPushCore(opts = {}) {
6998
7052
  const dryRun = opts.dryRun === true;
6999
7053
  const redactAll = opts.redactAll === true;
7000
7054
  const allowAll = opts.allowAll === true;
7001
7055
  const allowRule = opts.allowRule;
7002
7056
  const fullScan = opts.fullScan === true;
7003
- guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
7004
7057
  const repo = repoHome();
7005
7058
  const backup = backupBase();
7059
+ console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
7060
+ reportSettingsAheadDrift(repo);
7061
+ const scannerVersion = probeGitleaks();
7062
+ const configHash = computeConfigHash();
7063
+ const old = readManifest(manifestPath());
7064
+ const mapPath = join51(repo, "path-map.json");
7065
+ const { map, selection, newManifest } = loadSelectionForPush(
7066
+ mapPath,
7067
+ old,
7068
+ scannerVersion,
7069
+ configHash,
7070
+ fullScan
7071
+ );
7072
+ withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
7073
+ const ts = freshBackupTs(backup);
7074
+ const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun, selection }));
7075
+ const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
7076
+ if (!dryRun) {
7077
+ syncSkillsPush();
7078
+ stripGsdHooksFromBase(repo, backup);
7079
+ }
7080
+ const st = { dryRun, remap, extras, globalConfig: [] };
7081
+ guardGitlinks(repo);
7082
+ const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
7083
+ if (!dryRun && !status) {
7084
+ log("nothing to commit");
7085
+ renderNoScanTree(st);
7086
+ return { tag: "nothing" };
7087
+ }
7088
+ if (map === null) {
7089
+ if (dryRun) {
7090
+ runDryRunPreview(st, null, repo, selection);
7091
+ return { tag: "dry" };
7092
+ }
7093
+ return die("path-map.json missing, cannot enforce push allow-list");
7094
+ }
7095
+ if (status) enforceAllowList(status, map);
7096
+ if (dryRun) {
7097
+ runDryRunPreview(st, map, repo, selection);
7098
+ return { tag: "dry" };
7099
+ }
7100
+ const outcome = await commitAndPush(
7101
+ st,
7102
+ ts,
7103
+ map,
7104
+ { redactAll, allowAll, allowRule },
7105
+ repo,
7106
+ newManifest
7107
+ );
7108
+ return outcome === "nothing" ? { tag: "nothing" } : { tag: "pushed" };
7109
+ }
7110
+ async function cmdPush(opts = {}) {
7111
+ const dryRun = opts.dryRun === true;
7112
+ const redactAll = opts.redactAll === true;
7113
+ const allowAll = opts.allowAll === true;
7114
+ const allowRule = opts.allowRule;
7115
+ guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
7116
+ const repo = repoHome();
7006
7117
  if (!existsSync43(repo)) die(`repo not cloned at ${repo}`);
7007
7118
  const handle = acquireLock("push");
7008
7119
  if (handle === null) process.exit(0);
7009
7120
  try {
7010
- console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
7011
- reportSettingsAheadDrift(repo);
7012
- const scannerVersion = probeGitleaks();
7013
- const configHash = computeConfigHash();
7014
- const old = readManifest(manifestPath());
7015
- const mapPath = join51(repo, "path-map.json");
7016
- const { map, selection, newManifest } = loadSelectionForPush(
7017
- mapPath,
7018
- old,
7019
- scannerVersion,
7020
- configHash,
7021
- fullScan
7022
- );
7023
- withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
7024
- const ts = freshBackupTs(backup);
7025
- const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun, selection }));
7026
- const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
7027
- if (!dryRun) {
7028
- syncSkillsPush();
7029
- stripGsdHooksFromBase(repo, backup);
7030
- }
7031
- const st = { dryRun, remap, extras, globalConfig: [] };
7032
- guardGitlinks(repo);
7033
- const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
7034
- if (!dryRun && !status) {
7035
- log("nothing to commit");
7036
- renderNoScanTree(st);
7037
- return;
7121
+ await runPushCore(opts);
7122
+ } catch (err) {
7123
+ if (err instanceof NomadFatal) {
7124
+ fail(err.message);
7125
+ process.exitCode = 1;
7126
+ } else {
7127
+ throw err;
7128
+ }
7129
+ } finally {
7130
+ releaseLock(handle);
7131
+ }
7132
+ }
7133
+
7134
+ // src/commands.sync.ts
7135
+ import { existsSync as existsSync44 } from "node:fs";
7136
+ import { join as join52 } from "node:path";
7137
+ init_config();
7138
+ init_utils();
7139
+ init_utils_fs();
7140
+ init_utils_json();
7141
+ function pullHasNoSyncedItems(sections) {
7142
+ return sections.every(
7143
+ (s) => s.header === "Settings" || s.header === "Summary" || s.items.length === 0
7144
+ );
7145
+ }
7146
+ function isNoopSync(pull, pushOutcome) {
7147
+ if (!pushOutcome.ok) return false;
7148
+ if (pull.localOnly !== 0 || pull.divergedKeptLocal !== 0) return false;
7149
+ if (pushOutcome.result.tag !== "nothing") return false;
7150
+ return pullHasNoSyncedItems(pull.sections);
7151
+ }
7152
+ function pullPhrase(pull) {
7153
+ const summary = pull.sections.find((s) => s.header === "Summary");
7154
+ return summary?.items[0] ?? "applied";
7155
+ }
7156
+ function reconciledNotes(pull) {
7157
+ const notes = [];
7158
+ if (pull.divergedKeptLocal > 0) {
7159
+ notes.push(`${pull.divergedKeptLocal} diverged files kept local and pushed`);
7160
+ }
7161
+ if (pull.localOnly > 0) {
7162
+ notes.push(`${pull.localOnly} local-only sessions pushed`);
7163
+ }
7164
+ return notes;
7165
+ }
7166
+ function buildSyncSummarySection(pull, pushOutcome) {
7167
+ const s = section("Summary");
7168
+ if (!pushOutcome.ok) {
7169
+ addItem(s, `pull: applied, push: failed (${pushOutcome.message})`);
7170
+ return s;
7171
+ }
7172
+ addItem(s, `pull: ${pullPhrase(pull)}`);
7173
+ addItem(s, `push: ${pushOutcome.result.tag === "pushed" ? "pushed" : "nothing to push"}`);
7174
+ for (const note of reconciledNotes(pull)) addItem(s, note);
7175
+ return s;
7176
+ }
7177
+ function renderWetSync(pull, pushOutcome) {
7178
+ if (isNoopSync(pull, pushOutcome)) {
7179
+ ok("already in sync");
7180
+ return;
7181
+ }
7182
+ renderTree(pull.sections);
7183
+ renderTree([buildSyncSummarySection(pull, pushOutcome)]);
7184
+ }
7185
+ async function runSyncPushHalf() {
7186
+ try {
7187
+ const result = await runPushCore();
7188
+ return { ok: true, result };
7189
+ } catch (err) {
7190
+ if (err instanceof NomadFatal) {
7191
+ process.exitCode = 1;
7192
+ return { ok: false, message: err.message };
7038
7193
  }
7039
- if (map === null) {
7040
- if (dryRun) return runDryRunPreview(st, null, repo, selection);
7041
- return die("path-map.json missing, cannot enforce push allow-list");
7194
+ throw err;
7195
+ }
7196
+ }
7197
+ async function runSyncWet() {
7198
+ const pull = runPullCore();
7199
+ const pushOutcome = await runSyncPushHalf();
7200
+ renderWetSync(pull, pushOutcome);
7201
+ }
7202
+ async function runSyncDryRun(repo, backup) {
7203
+ const ts = freshBackupTs(backup);
7204
+ const mapPath = join52(repo, "path-map.json");
7205
+ const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
7206
+ computePreview(ts, map, "pull");
7207
+ log("push preview below is computed against pre-pull state (a real sync pushes after pull)");
7208
+ await runPushCore({ dryRun: true });
7209
+ }
7210
+ async function cmdSync(opts = {}) {
7211
+ const dryRun = opts.dryRun === true;
7212
+ const repo = repoHome();
7213
+ const backup = backupBase();
7214
+ if (!existsSync44(repo)) die(`repo not cloned at ${repo}`);
7215
+ if (!existsSync44(join52(repo, "shared", "settings.base.json"))) {
7216
+ die("repo not initialized; run 'nomad init' to scaffold");
7217
+ }
7218
+ const handle = acquireLock("sync");
7219
+ if (handle === null) process.exit(0);
7220
+ try {
7221
+ if (dryRun) {
7222
+ await runSyncDryRun(repo, backup);
7223
+ } else {
7224
+ await runSyncWet();
7042
7225
  }
7043
- if (status) enforceAllowList(status, map);
7044
- if (dryRun) return runDryRunPreview(st, map, repo, selection);
7045
- await commitAndPush(st, ts, map, { redactAll, allowAll, allowRule }, repo, newManifest);
7046
7226
  } catch (err) {
7047
7227
  if (err instanceof NomadFatal) {
7048
7228
  fail(err.message);
@@ -7099,18 +7279,18 @@ init_config();
7099
7279
 
7100
7280
  // src/diff.ts
7101
7281
  init_config();
7102
- import { existsSync as existsSync44 } from "node:fs";
7103
- import { join as join52 } from "node:path";
7282
+ import { existsSync as existsSync45 } from "node:fs";
7283
+ import { join as join53 } from "node:path";
7104
7284
  init_utils();
7105
7285
  init_utils_fs();
7106
7286
  init_utils_json();
7107
7287
  function cmdDiff() {
7108
7288
  try {
7109
7289
  const repo = repoHome();
7110
- if (!existsSync44(repo)) die(`repo not cloned at ${repo}`);
7290
+ if (!existsSync45(repo)) die(`repo not cloned at ${repo}`);
7111
7291
  const ts = freshBackupTs(backupBase());
7112
- const mapPath = join52(repo, "path-map.json");
7113
- const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
7292
+ const mapPath = join53(repo, "path-map.json");
7293
+ const map = existsSync45(mapPath) ? readPathMap(mapPath) : { projects: {} };
7114
7294
  divergenceCheckExtras(ts);
7115
7295
  computePreview(ts, map, "diff");
7116
7296
  } catch (err) {
@@ -7125,8 +7305,8 @@ function cmdDiff() {
7125
7305
 
7126
7306
  // src/init.ts
7127
7307
  init_config();
7128
- import { existsSync as existsSync46, mkdirSync as mkdirSync12, writeFileSync as writeFileSync6 } from "node:fs";
7129
- import { join as join54 } from "node:path";
7308
+ import { existsSync as existsSync47, mkdirSync as mkdirSync12, writeFileSync as writeFileSync6 } from "node:fs";
7309
+ import { join as join55 } from "node:path";
7130
7310
 
7131
7311
  // src/init.gh-onboard.ts
7132
7312
  init_config();
@@ -7208,33 +7388,33 @@ init_config();
7208
7388
  init_utils();
7209
7389
  init_utils_fs();
7210
7390
  init_utils_json();
7211
- import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync45, rmSync as rmSync15, statSync as statSync12 } from "node:fs";
7212
- import { join as join53 } from "node:path";
7391
+ import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync46, rmSync as rmSync15, statSync as statSync12 } from "node:fs";
7392
+ import { join as join54 } from "node:path";
7213
7393
  function snapshotIntoShared(map) {
7214
7394
  const repo = repoHome();
7215
7395
  const claude = claudeHome();
7216
7396
  for (const name of allSharedLinks(map)) {
7217
- const src = join53(claude, name);
7218
- if (!existsSync45(src)) continue;
7219
- const dst = join53(repo, "shared", name);
7397
+ const src = join54(claude, name);
7398
+ if (!existsSync46(src)) continue;
7399
+ const dst = join54(repo, "shared", name);
7220
7400
  if (statSync12(src).isDirectory()) {
7221
- const gk = join53(dst, ".gitkeep");
7222
- if (existsSync45(gk)) rmSync15(gk);
7401
+ const gk = join54(dst, ".gitkeep");
7402
+ if (existsSync46(gk)) rmSync15(gk);
7223
7403
  cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
7224
7404
  } else {
7225
7405
  copyFileSync2(src, dst);
7226
7406
  }
7227
7407
  log(`snapshotted shared/${name} from ${src}`);
7228
7408
  }
7229
- const userSettings = join53(claude, "settings.json");
7230
- if (existsSync45(userSettings)) {
7409
+ const userSettings = join54(claude, "settings.json");
7410
+ if (existsSync46(userSettings)) {
7231
7411
  let parsed;
7232
7412
  try {
7233
7413
  parsed = readJson(userSettings);
7234
7414
  } catch (err) {
7235
7415
  return die(`malformed ${userSettings}: ${err.message}`);
7236
7416
  }
7237
- const hostFile = join53(repo, "hosts", `${HOST}.json`);
7417
+ const hostFile = join54(repo, "hosts", `${HOST}.json`);
7238
7418
  writeJsonAtomic(hostFile, parsed);
7239
7419
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
7240
7420
  }
@@ -7247,14 +7427,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
7247
7427
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
7248
7428
  function preflightConflict(repoHome2) {
7249
7429
  const candidates = [
7250
- join54(repoHome2, "shared", "settings.base.json"),
7251
- join54(repoHome2, "shared", "CLAUDE.md"),
7252
- join54(repoHome2, "path-map.json"),
7253
- join54(repoHome2, "hosts"),
7254
- join54(repoHome2, "shared")
7430
+ join55(repoHome2, "shared", "settings.base.json"),
7431
+ join55(repoHome2, "shared", "CLAUDE.md"),
7432
+ join55(repoHome2, "path-map.json"),
7433
+ join55(repoHome2, "hosts"),
7434
+ join55(repoHome2, "shared")
7255
7435
  ];
7256
7436
  for (const c of candidates) {
7257
- if (existsSync46(c)) return c;
7437
+ if (existsSync47(c)) return c;
7258
7438
  }
7259
7439
  return null;
7260
7440
  }
@@ -7272,25 +7452,25 @@ function cmdInit(opts = {}) {
7272
7452
  die(`already initialized; refusing to clobber ${conflict}`);
7273
7453
  }
7274
7454
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
7275
- mkdirSync12(join54(repo, "shared"), { recursive: true });
7276
- mkdirSync12(join54(repo, "hosts"), { recursive: true });
7455
+ mkdirSync12(join55(repo, "shared"), { recursive: true });
7456
+ mkdirSync12(join55(repo, "hosts"), { recursive: true });
7277
7457
  for (const name of SHARED_KEEP_DIRS) {
7278
- mkdirSync12(join54(repo, "shared", name), { recursive: true });
7458
+ mkdirSync12(join55(repo, "shared", name), { recursive: true });
7279
7459
  }
7280
- const userClaudeMd = join54(claude, "CLAUDE.md");
7281
- if (!snapshot || !existsSync46(userClaudeMd)) {
7282
- writeFileSync6(join54(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
7460
+ const userClaudeMd = join55(claude, "CLAUDE.md");
7461
+ if (!snapshot || !existsSync47(userClaudeMd)) {
7462
+ writeFileSync6(join55(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
7283
7463
  item("created shared/CLAUDE.md");
7284
7464
  }
7285
7465
  for (const name of SHARED_KEEP_DIRS) {
7286
- writeFileSync6(join54(repo, "shared", name, ".gitkeep"), "");
7466
+ writeFileSync6(join55(repo, "shared", name, ".gitkeep"), "");
7287
7467
  item(`created shared/${name}/.gitkeep`);
7288
7468
  }
7289
- writeFileSync6(join54(repo, "hosts", ".gitkeep"), "");
7469
+ writeFileSync6(join55(repo, "hosts", ".gitkeep"), "");
7290
7470
  item("created hosts/.gitkeep");
7291
- writeJsonAtomic(join54(repo, "shared", "settings.base.json"), {});
7471
+ writeJsonAtomic(join55(repo, "shared", "settings.base.json"), {});
7292
7472
  item("created shared/settings.base.json");
7293
- writeJsonAtomic(join54(repo, "path-map.json"), { projects: {} });
7473
+ writeJsonAtomic(join55(repo, "path-map.json"), { projects: {} });
7294
7474
  item("created path-map.json");
7295
7475
  if (snapshot) {
7296
7476
  snapshotIntoShared({ projects: {} });
@@ -7356,11 +7536,11 @@ function maybeDisableRepoActions(repoHome2, run) {
7356
7536
  // src/init.prompt.ts
7357
7537
  init_config();
7358
7538
  init_utils();
7359
- import { existsSync as existsSync47, readdirSync as readdirSync17, statSync as statSync13 } from "node:fs";
7360
- import { join as join55 } from "node:path";
7539
+ import { existsSync as existsSync48, readdirSync as readdirSync17, statSync as statSync13 } from "node:fs";
7540
+ import { join as join56 } from "node:path";
7361
7541
  import { createInterface as createInterface3 } from "node:readline/promises";
7362
7542
  function nonEmptyExists(path) {
7363
- if (!existsSync47(path)) return false;
7543
+ if (!existsSync48(path)) return false;
7364
7544
  try {
7365
7545
  if (statSync13(path).isDirectory()) return readdirSync17(path).length > 0;
7366
7546
  return true;
@@ -7369,8 +7549,8 @@ function nonEmptyExists(path) {
7369
7549
  }
7370
7550
  }
7371
7551
  function hasExistingClaudeConfig(claudeHome2) {
7372
- if (existsSync47(join55(claudeHome2, "settings.json"))) return true;
7373
- return SHARED_LINKS.some((name) => nonEmptyExists(join55(claudeHome2, name)));
7552
+ if (existsSync48(join56(claudeHome2, "settings.json"))) return true;
7553
+ return SHARED_LINKS.some((name) => nonEmptyExists(join56(claudeHome2, name)));
7374
7554
  }
7375
7555
  async function confirmSnapshotDefault(claudeHome2) {
7376
7556
  if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
@@ -7654,10 +7834,27 @@ function parsePushArgs(argv) {
7654
7834
  };
7655
7835
  }
7656
7836
 
7837
+ // src/nomad.dispatch.sync.ts
7838
+ function parseSyncArgs(argv) {
7839
+ let dryRun = false;
7840
+ let i = 3;
7841
+ while (i < argv.length) {
7842
+ const token = argv[i];
7843
+ if (token === "--dry-run") {
7844
+ if (dryRun) return null;
7845
+ dryRun = true;
7846
+ } else {
7847
+ return null;
7848
+ }
7849
+ i++;
7850
+ }
7851
+ return { dryRun };
7852
+ }
7853
+
7657
7854
  // package.json
7658
7855
  var package_default = {
7659
7856
  name: "claude-nomad",
7660
- version: "0.56.2",
7857
+ version: "0.57.1",
7661
7858
  type: "module",
7662
7859
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
7663
7860
  keywords: [
@@ -7719,6 +7916,11 @@ var package_default = {
7719
7916
  "prettier --write"
7720
7917
  ]
7721
7918
  },
7919
+ overrides: {
7920
+ "@conventional-changelog/git-client": {
7921
+ "conventional-commits-parser": "^7.0.0"
7922
+ }
7923
+ },
7722
7924
  devDependencies: {
7723
7925
  "@commitlint/cli": "^21.0.1",
7724
7926
  "@commitlint/config-conventional": "^21.0.1",
@@ -7735,7 +7937,7 @@ var package_default = {
7735
7937
  globals: "^17.6.0",
7736
7938
  husky: "^9.1.7",
7737
7939
  "lint-staged": "^17.0.5",
7738
- "markdownlint-cli2": "^0.22.1",
7940
+ "markdownlint-cli2": "^0.23.0",
7739
7941
  picocolors: "^1.1.1",
7740
7942
  prettier: "^3.8.3",
7741
7943
  typescript: "^6.0.3",
@@ -7786,6 +7988,12 @@ var DEFAULT_HELP = [
7786
7988
  cont("when no finding survives. No TTY required. Never skips the scan."),
7787
7989
  cont("Mutually exclusive with --redact-all/--allow. Cannot combine with --dry-run."),
7788
7990
  "",
7991
+ row(" sync", "Pull (retain-merge), then push, under one lock. One-shot; hides ordering."),
7992
+ row(" --dry-run", "Stack the pull preview then the push preview; mutates nothing."),
7993
+ cont("Fails fast on a pull-half error; a wedged repo hints at nomad pull --force-remote"),
7994
+ cont("(sync itself takes no --force-remote). Mid-push leak recovery behaves exactly"),
7995
+ cont("like nomad push."),
7996
+ "",
7789
7997
  row(" diff", "Offline preview of what `pull` would change against local repo state."),
7790
7998
  cont("No git pull, no lock acquired."),
7791
7999
  "",
@@ -7882,15 +8090,15 @@ var DEFAULT_HELP = [
7882
8090
  init_config();
7883
8091
  init_utils();
7884
8092
  init_utils_json();
7885
- import { existsSync as existsSync48, readFileSync as readFileSync17, readdirSync as readdirSync18 } from "node:fs";
7886
- import { join as join56 } from "node:path";
8093
+ import { existsSync as existsSync49, readFileSync as readFileSync17, readdirSync as readdirSync18 } from "node:fs";
8094
+ import { join as join57 } from "node:path";
7887
8095
  function resumeCmd(sessionId) {
7888
8096
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
7889
8097
  fail(`invalid session id: ${sessionId}`);
7890
8098
  process.exit(1);
7891
8099
  }
7892
- const projectsRoot = join56(claudeHome(), "projects");
7893
- if (!existsSync48(projectsRoot)) {
8100
+ const projectsRoot = join57(claudeHome(), "projects");
8101
+ if (!existsSync49(projectsRoot)) {
7894
8102
  fail(`${projectsRoot} does not exist`);
7895
8103
  process.exit(1);
7896
8104
  }
@@ -7904,8 +8112,8 @@ function resumeCmd(sessionId) {
7904
8112
  fail(`no cwd field found in ${jsonlPath}`);
7905
8113
  process.exit(1);
7906
8114
  }
7907
- const mapPath = join56(repoHome(), "path-map.json");
7908
- if (!existsSync48(mapPath)) {
8115
+ const mapPath = join57(repoHome(), "path-map.json");
8116
+ if (!existsSync49(mapPath)) {
7909
8117
  fail("path-map.json missing");
7910
8118
  process.exit(1);
7911
8119
  }
@@ -7928,8 +8136,8 @@ function resumeCmd(sessionId) {
7928
8136
  }
7929
8137
  function findTranscriptPath(projectsRoot, sessionId) {
7930
8138
  for (const dir of readdirSync18(projectsRoot)) {
7931
- const candidate = join56(projectsRoot, dir, `${sessionId}.jsonl`);
7932
- if (existsSync48(candidate)) return candidate;
8139
+ const candidate = join57(projectsRoot, dir, `${sessionId}.jsonl`);
8140
+ if (existsSync49(candidate)) return candidate;
7933
8141
  }
7934
8142
  return null;
7935
8143
  }
@@ -8007,6 +8215,15 @@ try {
8007
8215
  });
8008
8216
  break;
8009
8217
  }
8218
+ case "sync": {
8219
+ const syncArgs = parseSyncArgs(process.argv);
8220
+ if (syncArgs === null) {
8221
+ console.error("usage: nomad sync [--dry-run]");
8222
+ process.exit(1);
8223
+ }
8224
+ await cmdSync({ dryRun: syncArgs.dryRun });
8225
+ break;
8226
+ }
8010
8227
  case "init": {
8011
8228
  const initArgs = parseInitArgs(process.argv);
8012
8229
  if (initArgs === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.56.2",
3
+ "version": "0.57.1",
4
4
  "type": "module",
5
5
  "description": "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
6
6
  "keywords": [
@@ -62,6 +62,11 @@
62
62
  "prettier --write"
63
63
  ]
64
64
  },
65
+ "overrides": {
66
+ "@conventional-changelog/git-client": {
67
+ "conventional-commits-parser": "^7.0.0"
68
+ }
69
+ },
65
70
  "devDependencies": {
66
71
  "@commitlint/cli": "^21.0.1",
67
72
  "@commitlint/config-conventional": "^21.0.1",
@@ -78,7 +83,7 @@
78
83
  "globals": "^17.6.0",
79
84
  "husky": "^9.1.7",
80
85
  "lint-staged": "^17.0.5",
81
- "markdownlint-cli2": "^0.22.1",
86
+ "markdownlint-cli2": "^0.23.0",
82
87
  "picocolors": "^1.1.1",
83
88
  "prettier": "^3.8.3",
84
89
  "typescript": "^6.0.3",