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 +42 -0
- package/README.md +18 -12
- package/dist/nomad.mjs +343 -126
- package/package.json +7 -2
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
|
|
17
|
-
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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),
|
|
220
|
-
`/nomad:clean`, plus a session-start drift check. The plugin versions
|
|
221
|
-
but requires nomad `>= 0.35.0` because it calls recent subcommands
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
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
|
|
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
|
|
6517
|
-
|
|
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
|
|
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
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
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
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
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
|
|
7103
|
-
import { join as
|
|
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 (!
|
|
7290
|
+
if (!existsSync45(repo)) die(`repo not cloned at ${repo}`);
|
|
7111
7291
|
const ts = freshBackupTs(backupBase());
|
|
7112
|
-
const mapPath =
|
|
7113
|
-
const map =
|
|
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
|
|
7129
|
-
import { join as
|
|
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
|
|
7212
|
-
import { join as
|
|
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 =
|
|
7218
|
-
if (!
|
|
7219
|
-
const dst =
|
|
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 =
|
|
7222
|
-
if (
|
|
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 =
|
|
7230
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
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 (
|
|
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(
|
|
7276
|
-
mkdirSync12(
|
|
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(
|
|
7458
|
+
mkdirSync12(join55(repo, "shared", name), { recursive: true });
|
|
7279
7459
|
}
|
|
7280
|
-
const userClaudeMd =
|
|
7281
|
-
if (!snapshot || !
|
|
7282
|
-
writeFileSync6(
|
|
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(
|
|
7466
|
+
writeFileSync6(join55(repo, "shared", name, ".gitkeep"), "");
|
|
7287
7467
|
item(`created shared/${name}/.gitkeep`);
|
|
7288
7468
|
}
|
|
7289
|
-
writeFileSync6(
|
|
7469
|
+
writeFileSync6(join55(repo, "hosts", ".gitkeep"), "");
|
|
7290
7470
|
item("created hosts/.gitkeep");
|
|
7291
|
-
writeJsonAtomic(
|
|
7471
|
+
writeJsonAtomic(join55(repo, "shared", "settings.base.json"), {});
|
|
7292
7472
|
item("created shared/settings.base.json");
|
|
7293
|
-
writeJsonAtomic(
|
|
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
|
|
7360
|
-
import { join as
|
|
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 (!
|
|
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 (
|
|
7373
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
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.
|
|
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.
|
|
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
|
|
7886
|
-
import { join as
|
|
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 =
|
|
7893
|
-
if (!
|
|
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 =
|
|
7908
|
-
if (!
|
|
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 =
|
|
7932
|
-
if (
|
|
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.
|
|
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.
|
|
86
|
+
"markdownlint-cli2": "^0.23.0",
|
|
82
87
|
"picocolors": "^1.1.1",
|
|
83
88
|
"prettier": "^3.8.3",
|
|
84
89
|
"typescript": "^6.0.3",
|