claude-nomad 0.46.0 → 0.47.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 +33 -0
- package/dist/nomad.mjs +80 -19
- package/package.json +1 -2
- package/shared/.gitignore +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.47.1](https://github.com/funkadelic/claude-nomad/compare/v0.47.0...v0.47.1) (2026-06-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
* **extras:** preserve host-local deny-set files on .claude pull ([#276](https://github.com/funkadelic/claude-nomad/issues/276)) ([3742c3e](https://github.com/funkadelic/claude-nomad/commit/3742c3e4067c1c986e7f182011581d4c867847b0))
|
|
9
|
+
|
|
10
|
+
## [0.47.0](https://github.com/funkadelic/claude-nomad/compare/v0.46.0...v0.47.0) (2026-06-09)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
* **extras:** add .claude as a supported per-project extra ([#274](https://github.com/funkadelic/claude-nomad/issues/274)) ([41a79a3](https://github.com/funkadelic/claude-nomad/commit/41a79a3fa9963f2ade76ec4d89d880d9634bb0d2))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
* remove orphaned shared/ folder from package ([#267](https://github.com/funkadelic/claude-nomad/issues/267)) ([93e4119](https://github.com/funkadelic/claude-nomad/commit/93e411951df4aa600e94da8d9db4298129ad1fa2))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Documentation
|
|
24
|
+
|
|
25
|
+
* **extras:** document .claude extra in security, features, and recovery pages ([#275](https://github.com/funkadelic/claude-nomad/issues/275)) ([232016c](https://github.com/funkadelic/claude-nomad/commit/232016c5cb14988fb3fe8dd3f1f558d49789fffb))
|
|
26
|
+
* **faq:** explain unmapped local projects in nomad doctor ([#273](https://github.com/funkadelic/claude-nomad/issues/273)) ([6c2e9d4](https://github.com/funkadelic/claude-nomad/commit/6c2e9d475decb8cea3e159a3047f5f9daff24785))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Dependencies
|
|
30
|
+
|
|
31
|
+
* bump @types/node in the dev-dependencies group ([#272](https://github.com/funkadelic/claude-nomad/issues/272)) ([951471e](https://github.com/funkadelic/claude-nomad/commit/951471e0879eee0e10756f67a9e58554dce96c52))
|
|
32
|
+
* bump actions/checkout from 6.0.2 to 6.0.3 ([#269](https://github.com/funkadelic/claude-nomad/issues/269)) ([21b7490](https://github.com/funkadelic/claude-nomad/commit/21b7490b273bfe22aa76216edc5de44a49b57c59))
|
|
33
|
+
* bump codecov/codecov-action from 6.0.1 to 7.0.0 ([#270](https://github.com/funkadelic/claude-nomad/issues/270)) ([905d468](https://github.com/funkadelic/claude-nomad/commit/905d46832e6d52c825419be39fac08d5408de39f))
|
|
34
|
+
* bump github/codeql-action from 4.36.0 to 4.36.2 ([#271](https://github.com/funkadelic/claude-nomad/issues/271)) ([6ff50c7](https://github.com/funkadelic/claude-nomad/commit/6ff50c76d3ddc9e0cd8247f572288b722cd588b5))
|
|
35
|
+
|
|
3
36
|
## [0.46.0](https://github.com/funkadelic/claude-nomad/compare/v0.45.0...v0.46.0) (2026-06-08)
|
|
4
37
|
|
|
5
38
|
|
package/dist/nomad.mjs
CHANGED
|
@@ -33,7 +33,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
));
|
|
34
34
|
|
|
35
35
|
// src/config.never-sync.ts
|
|
36
|
-
var NEVER_SYNC;
|
|
36
|
+
var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC;
|
|
37
37
|
var init_config_never_sync = __esm({
|
|
38
38
|
"src/config.never-sync.ts"() {
|
|
39
39
|
"use strict";
|
|
@@ -62,6 +62,7 @@ var init_config_never_sync = __esm({
|
|
|
62
62
|
"security",
|
|
63
63
|
"sessions"
|
|
64
64
|
]);
|
|
65
|
+
CLAUDE_EXTRA_NEVER_SYNC = /* @__PURE__ */ new Set([...NEVER_SYNC, "projects"]);
|
|
65
66
|
}
|
|
66
67
|
});
|
|
67
68
|
|
|
@@ -407,7 +408,7 @@ var init_config = __esm({
|
|
|
407
408
|
"my-statusline.cjs",
|
|
408
409
|
"hooks"
|
|
409
410
|
];
|
|
410
|
-
SUPPORTED_EXTRAS = [".planning", "CLAUDE.md"];
|
|
411
|
+
SUPPORTED_EXTRAS = [".planning", "CLAUDE.md", ".claude"];
|
|
411
412
|
ALWAYS_NEVER_SYNC = /* @__PURE__ */ new Set([
|
|
412
413
|
".claude.json",
|
|
413
414
|
".credentials.json",
|
|
@@ -4239,8 +4240,8 @@ function listDivergingFiles(a, b) {
|
|
|
4239
4240
|
|
|
4240
4241
|
// src/extras-sync.core.ts
|
|
4241
4242
|
init_config();
|
|
4242
|
-
import { cpSync as cpSync6, existsSync as existsSync30, rmSync as rmSync10 } from "node:fs";
|
|
4243
|
-
import { join as join36 } from "node:path";
|
|
4243
|
+
import { cpSync as cpSync6, existsSync as existsSync30, lstatSync as lstatSync8, readdirSync as readdirSync11, rmSync as rmSync10 } from "node:fs";
|
|
4244
|
+
import { basename, join as join36 } from "node:path";
|
|
4244
4245
|
|
|
4245
4246
|
// src/extras-sync.guards.ts
|
|
4246
4247
|
init_utils();
|
|
@@ -4301,6 +4302,48 @@ function copyExtras(src, dst) {
|
|
|
4301
4302
|
rmSync10(dst, { recursive: true, force: true });
|
|
4302
4303
|
cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
4303
4304
|
}
|
|
4305
|
+
function extrasDenySet(dirname7) {
|
|
4306
|
+
return dirname7 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
4307
|
+
}
|
|
4308
|
+
function copyExtrasFiltered(src, dst, blockSet) {
|
|
4309
|
+
rmSync10(dst, { recursive: true, force: true });
|
|
4310
|
+
cpSync6(src, dst, {
|
|
4311
|
+
recursive: true,
|
|
4312
|
+
force: true,
|
|
4313
|
+
verbatimSymlinks: true,
|
|
4314
|
+
filter: (srcEntry) => srcEntry === src || !blockSet.has(basename(srcEntry))
|
|
4315
|
+
});
|
|
4316
|
+
}
|
|
4317
|
+
function prunePreservingDenied(src, dst, blockSet) {
|
|
4318
|
+
for (const name of readdirSync11(dst)) {
|
|
4319
|
+
if (blockSet.has(name)) continue;
|
|
4320
|
+
const dstPath = join36(dst, name);
|
|
4321
|
+
const srcStat = lstatSync8(join36(src, name), { throwIfNoEntry: false });
|
|
4322
|
+
if (srcStat === void 0) {
|
|
4323
|
+
rmSync10(dstPath, { recursive: true, force: true });
|
|
4324
|
+
continue;
|
|
4325
|
+
}
|
|
4326
|
+
const dstStat = lstatSync8(dstPath);
|
|
4327
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
4328
|
+
prunePreservingDenied(join36(src, name), dstPath, blockSet);
|
|
4329
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
4330
|
+
rmSync10(dstPath, { recursive: true, force: true });
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
4335
|
+
const dstStat = lstatSync8(dst, { throwIfNoEntry: false });
|
|
4336
|
+
if (dstStat !== void 0) {
|
|
4337
|
+
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
4338
|
+
else rmSync10(dst, { recursive: true, force: true });
|
|
4339
|
+
}
|
|
4340
|
+
cpSync6(src, dst, {
|
|
4341
|
+
recursive: true,
|
|
4342
|
+
force: true,
|
|
4343
|
+
verbatimSymlinks: true,
|
|
4344
|
+
filter: (srcEntry) => srcEntry === src || !blockSet.has(basename(srcEntry))
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4304
4347
|
|
|
4305
4348
|
// src/extras-sync.ts
|
|
4306
4349
|
init_utils();
|
|
@@ -4311,7 +4354,7 @@ init_config();
|
|
|
4311
4354
|
import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "node:fs";
|
|
4312
4355
|
import { join as join37 } from "node:path";
|
|
4313
4356
|
init_utils_fs();
|
|
4314
|
-
function runExtrasOp(v, dryRun, paths, backup) {
|
|
4357
|
+
function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
4315
4358
|
const counts = { unmapped: 0, skipped: 0 };
|
|
4316
4359
|
const done = [];
|
|
4317
4360
|
const would = [];
|
|
@@ -4324,7 +4367,7 @@ function runExtrasOp(v, dryRun, paths, backup) {
|
|
|
4324
4367
|
continue;
|
|
4325
4368
|
}
|
|
4326
4369
|
backup(dst, t.localRoot);
|
|
4327
|
-
|
|
4370
|
+
copy(src, dst, t.dirname);
|
|
4328
4371
|
done.push(item2);
|
|
4329
4372
|
}
|
|
4330
4373
|
return { ...counts, done, would };
|
|
@@ -4343,7 +4386,10 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
4343
4386
|
src: join37(localRoot, dirname7),
|
|
4344
4387
|
dst: join37(repoExtras, logical, dirname7)
|
|
4345
4388
|
}),
|
|
4346
|
-
(dst) => backupRepoWrite(dst, ts, repo)
|
|
4389
|
+
(dst) => backupRepoWrite(dst, ts, repo),
|
|
4390
|
+
// Push filters every extra by its per-name denylist: `.claude` gets the
|
|
4391
|
+
// full NEVER_SYNC boundary, `.planning` keeps the narrow ALWAYS_NEVER_SYNC.
|
|
4392
|
+
(src, dst, dirname7) => copyExtrasFiltered(src, dst, extrasDenySet(dirname7))
|
|
4347
4393
|
);
|
|
4348
4394
|
return { unmapped, skipped, pushed: done, wouldPush: would };
|
|
4349
4395
|
}
|
|
@@ -4364,7 +4410,17 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
4364
4410
|
}),
|
|
4365
4411
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
4366
4412
|
// localRoot so the backup tree mirrors the project layout.
|
|
4367
|
-
(dst, localRoot) => backupExtrasWrite(dst, ts, localRoot)
|
|
4413
|
+
(dst, localRoot) => backupExtrasWrite(dst, ts, localRoot),
|
|
4414
|
+
// Pull routes `.claude` through copyExtrasFilteredPreserving so host-local
|
|
4415
|
+
// deny-set files already on disk (e.g. settings.local.json) are preserved
|
|
4416
|
+
// instead of being wiped by a blanket rmSync. The same deny-set filter still
|
|
4417
|
+
// strips blocked basenames from the src copy (defense-in-depth: a repo
|
|
4418
|
+
// poisoned out-of-band cannot restore a blocked per-host file). Synced
|
|
4419
|
+
// non-deny files that are absent from src are still mirror-pruned. This
|
|
4420
|
+
// preservation is `.claude`-only; `.planning` and `CLAUDE.md` use the
|
|
4421
|
+
// exact-mirror copyExtras (documented restore semantics; they rarely carry
|
|
4422
|
+
// host-local files, so the exact mirror is the correct default).
|
|
4423
|
+
(src, dst, dirname7) => dirname7 === ".claude" ? copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname7)) : copyExtras(src, dst)
|
|
4368
4424
|
);
|
|
4369
4425
|
return { unmapped, skipped, pulled: done, wouldPull: would };
|
|
4370
4426
|
}
|
|
@@ -4395,7 +4451,7 @@ init_config();
|
|
|
4395
4451
|
init_utils();
|
|
4396
4452
|
init_utils_fs();
|
|
4397
4453
|
init_utils_json();
|
|
4398
|
-
import { existsSync as existsSync33, lstatSync as
|
|
4454
|
+
import { existsSync as existsSync33, lstatSync as lstatSync9, rmSync as rmSync11 } from "node:fs";
|
|
4399
4455
|
import { join as join39 } from "node:path";
|
|
4400
4456
|
function emitAutoMove(onPreview, linkPath, ts, name) {
|
|
4401
4457
|
if (onPreview) {
|
|
@@ -4412,14 +4468,14 @@ function emitCreate(onPreview, from, to) {
|
|
|
4412
4468
|
}
|
|
4413
4469
|
}
|
|
4414
4470
|
function isAlreadySymlink(linkPath) {
|
|
4415
|
-
return existsSync33(linkPath) &&
|
|
4471
|
+
return existsSync33(linkPath) && lstatSync9(linkPath).isSymbolicLink();
|
|
4416
4472
|
}
|
|
4417
4473
|
function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
4418
4474
|
for (const name of linkNames) {
|
|
4419
4475
|
const linkPath = join39(claude, name);
|
|
4420
4476
|
const target = join39(repo, "shared", name);
|
|
4421
4477
|
if (!existsSync33(linkPath)) continue;
|
|
4422
|
-
if (
|
|
4478
|
+
if (lstatSync9(linkPath).isSymbolicLink()) continue;
|
|
4423
4479
|
if (!existsSync33(target)) continue;
|
|
4424
4480
|
if (dryRun) {
|
|
4425
4481
|
emitAutoMove(onPreview, linkPath, ts, name);
|
|
@@ -5055,9 +5111,15 @@ function isAllowed(path, allowed) {
|
|
|
5055
5111
|
}
|
|
5056
5112
|
return false;
|
|
5057
5113
|
}
|
|
5114
|
+
function blockSetFor(segments) {
|
|
5115
|
+
if (segments[0] !== "shared" || segments[1] !== "extras") return NEVER_SYNC;
|
|
5116
|
+
return segments[3] === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
5117
|
+
}
|
|
5058
5118
|
function isNeverSync(path) {
|
|
5059
|
-
const
|
|
5060
|
-
|
|
5119
|
+
const segments = path.split("/");
|
|
5120
|
+
const blockSet = blockSetFor(segments);
|
|
5121
|
+
const scan = segments[0] === "shared" && segments[1] === "extras" ? segments.slice(4) : segments;
|
|
5122
|
+
for (const segment of scan) {
|
|
5061
5123
|
if (blockSet.has(segment)) return true;
|
|
5062
5124
|
}
|
|
5063
5125
|
return false;
|
|
@@ -5118,7 +5180,7 @@ init_color();
|
|
|
5118
5180
|
init_config();
|
|
5119
5181
|
init_config_sharedDirs_guard();
|
|
5120
5182
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
5121
|
-
import { copyFileSync, existsSync as existsSync36, mkdirSync as mkdirSync9, readdirSync as
|
|
5183
|
+
import { copyFileSync, existsSync as existsSync36, mkdirSync as mkdirSync9, readdirSync as readdirSync12, rmSync as rmSync12 } from "node:fs";
|
|
5122
5184
|
import { homedir as homedir5 } from "node:os";
|
|
5123
5185
|
import { join as join42 } from "node:path";
|
|
5124
5186
|
init_push_leak_verdict();
|
|
@@ -5138,7 +5200,7 @@ function stageSessions(tmpRoot, map) {
|
|
|
5138
5200
|
const localProjects = join42(claudeHome(), "projects");
|
|
5139
5201
|
if (!existsSync36(localProjects)) return 0;
|
|
5140
5202
|
let staged = 0;
|
|
5141
|
-
for (const dir of
|
|
5203
|
+
for (const dir of readdirSync12(localProjects)) {
|
|
5142
5204
|
const logical = reverse.get(dir);
|
|
5143
5205
|
if (!logical) continue;
|
|
5144
5206
|
copyDirJsonlOnly(join42(localProjects, dir), join42(tmpRoot, "shared", "projects", logical));
|
|
@@ -5802,7 +5864,7 @@ function parsePushArgs(argv) {
|
|
|
5802
5864
|
// package.json
|
|
5803
5865
|
var package_default = {
|
|
5804
5866
|
name: "claude-nomad",
|
|
5805
|
-
version: "0.
|
|
5867
|
+
version: "0.47.1",
|
|
5806
5868
|
type: "module",
|
|
5807
5869
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
5808
5870
|
keywords: [
|
|
@@ -5825,7 +5887,6 @@ var package_default = {
|
|
|
5825
5887
|
},
|
|
5826
5888
|
files: [
|
|
5827
5889
|
"dist/",
|
|
5828
|
-
"shared/.gitignore",
|
|
5829
5890
|
".gitleaks.toml",
|
|
5830
5891
|
"README.md",
|
|
5831
5892
|
"CHANGELOG.md",
|
|
@@ -6004,7 +6065,7 @@ var DEFAULT_HELP = [
|
|
|
6004
6065
|
init_config();
|
|
6005
6066
|
init_utils();
|
|
6006
6067
|
init_utils_json();
|
|
6007
|
-
import { existsSync as existsSync41, readFileSync as readFileSync14, readdirSync as
|
|
6068
|
+
import { existsSync as existsSync41, readFileSync as readFileSync14, readdirSync as readdirSync13 } from "node:fs";
|
|
6008
6069
|
import { join as join47 } from "node:path";
|
|
6009
6070
|
function resumeCmd(sessionId) {
|
|
6010
6071
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
@@ -6049,7 +6110,7 @@ function resumeCmd(sessionId) {
|
|
|
6049
6110
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
6050
6111
|
}
|
|
6051
6112
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
6052
|
-
for (const dir of
|
|
6113
|
+
for (const dir of readdirSync13(projectsRoot)) {
|
|
6053
6114
|
const candidate = join47(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
6054
6115
|
if (existsSync41(candidate)) return candidate;
|
|
6055
6116
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-nomad",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.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": [
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist/",
|
|
26
|
-
"shared/.gitignore",
|
|
27
26
|
".gitleaks.toml",
|
|
28
27
|
"README.md",
|
|
29
28
|
"CHANGELOG.md",
|