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 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
- copyExtras(src, dst);
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 lstatSync8, rmSync as rmSync11 } from "node:fs";
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) && lstatSync8(linkPath).isSymbolicLink();
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 (lstatSync8(linkPath).isSymbolicLink()) continue;
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 blockSet = path.startsWith("shared/extras/") ? ALWAYS_NEVER_SYNC : NEVER_SYNC;
5060
- for (const segment of path.split("/")) {
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 readdirSync11, rmSync as rmSync12 } from "node:fs";
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 readdirSync11(localProjects)) {
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.46.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 readdirSync12 } from "node:fs";
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 readdirSync12(projectsRoot)) {
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.46.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",
package/shared/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- *.token
2
- *.key
3
- .env
4
- .env.*
5
- .claude.json
6
- settings.local.json
7
- *.pem
8
- id_rsa
9
- id_ed25519