claude-nomad 0.47.1 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.48.0](https://github.com/funkadelic/claude-nomad/compare/v0.47.1...v0.48.0) (2026-06-09)
4
+
5
+
6
+ ### Added
7
+
8
+ * **push:** surface changed shared config in push output ([#278](https://github.com/funkadelic/claude-nomad/issues/278)) ([4774661](https://github.com/funkadelic/claude-nomad/commit/477466138e203f861e60c72cb6522f25393b3987))
9
+ * **update:** frame update output and report new version ([#279](https://github.com/funkadelic/claude-nomad/issues/279)) ([9342de1](https://github.com/funkadelic/claude-nomad/commit/9342de1187db1fd115cc73cb2b516a9477b67a54))
10
+
3
11
  ## [0.47.1](https://github.com/funkadelic/claude-nomad/compare/v0.47.0...v0.47.1) (2026-06-09)
4
12
 
5
13
 
package/dist/nomad.mjs CHANGED
@@ -4166,10 +4166,18 @@ function buildExtrasSection(items, extrasSkipped) {
4166
4166
  if (skip !== null) addItem(s, skip);
4167
4167
  return s;
4168
4168
  }
4169
+ function buildGlobalConfigSection(rows) {
4170
+ const s = section("Global config");
4171
+ for (const row2 of rows) {
4172
+ addItem(s, `${green(okGlyph)} ${row2.label} ${row2.path}`);
4173
+ }
4174
+ return s;
4175
+ }
4169
4176
  function syncedSections(st) {
4170
4177
  const sessions = st.dryRun ? st.remap.wouldPush : st.remap.pushed;
4171
4178
  const extras = st.dryRun ? st.extras.wouldPush : st.extras.pushed;
4172
4179
  return [
4180
+ buildGlobalConfigSection(st.globalConfig),
4173
4181
  buildSessionsSection(sessions, st.remap.unmapped),
4174
4182
  buildExtrasSection(extras, st.extras.skipped)
4175
4183
  ];
@@ -5171,6 +5179,77 @@ function enforceAllowList(statusPorcelain, map) {
5171
5179
  throw new NomadFatal("push allow-list violations");
5172
5180
  }
5173
5181
 
5182
+ // src/push-global-config.ts
5183
+ init_config();
5184
+ import { execFileSync as execFileSync15 } from "node:child_process";
5185
+ var STATUS_LABELS = {
5186
+ A: "add",
5187
+ M: "modify",
5188
+ D: "delete",
5189
+ R: "rename",
5190
+ C: "copy",
5191
+ T: "type-change",
5192
+ U: "unmerged",
5193
+ X: "unknown"
5194
+ };
5195
+ function labelForStatus(statusToken) {
5196
+ const letter = statusToken[0] ?? "";
5197
+ return STATUS_LABELS[letter] ?? "change";
5198
+ }
5199
+ function buildPrefixSets(hostname2) {
5200
+ const exactPrefixes = /* @__PURE__ */ new Set();
5201
+ const dirPrefixes = [];
5202
+ for (const name of SHARED_LINKS) {
5203
+ const p = `shared/${name}`;
5204
+ if (name.includes(".")) {
5205
+ exactPrefixes.add(p);
5206
+ } else {
5207
+ dirPrefixes.push(p);
5208
+ }
5209
+ }
5210
+ exactPrefixes.add("shared/settings.base.json");
5211
+ exactPrefixes.add(`hosts/${hostname2}.json`);
5212
+ return { exactPrefixes, dirPrefixes };
5213
+ }
5214
+ function isInScope(filePath, exactPrefixes, dirPrefixes) {
5215
+ if (filePath.startsWith("shared/projects/") || filePath.startsWith("shared/extras/")) {
5216
+ return false;
5217
+ }
5218
+ if (exactPrefixes.has(filePath)) return true;
5219
+ return dirPrefixes.some((prefix) => filePath.startsWith(`${prefix}/`));
5220
+ }
5221
+ function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
5222
+ const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
5223
+ const raw = execFileSync15("git", args, {
5224
+ cwd: repoHome2,
5225
+ stdio: ["ignore", "pipe", "pipe"]
5226
+ }).toString();
5227
+ const { exactPrefixes, dirPrefixes } = buildPrefixSets(hostname2);
5228
+ const changes = [];
5229
+ const tokens = raw.split("\0");
5230
+ if (tokens.at(-1) === "") tokens.pop();
5231
+ let i = 0;
5232
+ while (i < tokens.length) {
5233
+ const statusToken = tokens[i++] ?? "";
5234
+ if (statusToken === "") continue;
5235
+ const firstLetter = statusToken[0] ?? "";
5236
+ const isRenameOrCopy = firstLetter === "R" || firstLetter === "C";
5237
+ if (isRenameOrCopy) {
5238
+ i++;
5239
+ const newPath = tokens[i++] ?? "";
5240
+ if (isInScope(newPath, exactPrefixes, dirPrefixes)) {
5241
+ changes.push({ status: firstLetter, label: labelForStatus(statusToken), path: newPath });
5242
+ }
5243
+ } else {
5244
+ const filePath = tokens[i++] ?? "";
5245
+ if (isInScope(filePath, exactPrefixes, dirPrefixes)) {
5246
+ changes.push({ status: firstLetter, label: labelForStatus(statusToken), path: filePath });
5247
+ }
5248
+ }
5249
+ }
5250
+ return changes;
5251
+ }
5252
+
5174
5253
  // src/commands.push.ts
5175
5254
  init_push_leak_verdict();
5176
5255
  init_push_checks();
@@ -5276,6 +5355,7 @@ function guardGitlinks(repo) {
5276
5355
  }
5277
5356
  async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo) {
5278
5357
  gitOrFatal(["add", "-A"], "git add", repo);
5358
+ st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: true });
5279
5359
  let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
5280
5360
  if (verdict.leak) {
5281
5361
  renderPushTree(st, verdict);
@@ -5285,7 +5365,8 @@ async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo)
5285
5365
  withSpinner("Pushing", () => gitOrFatal(["push"], "git push", repo));
5286
5366
  renderPushTree(st, verdict);
5287
5367
  }
5288
- function runDryRunPreview(st, map) {
5368
+ function runDryRunPreview(st, map, repo) {
5369
+ st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: false });
5289
5370
  if (map === null) {
5290
5371
  renderNoScanTree(st, { noMapHint: true });
5291
5372
  return;
@@ -5327,7 +5408,7 @@ async function cmdPush(opts = {}) {
5327
5408
  const ts = freshBackupTs(backup);
5328
5409
  const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun }));
5329
5410
  const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
5330
- const st = { dryRun, remap, extras };
5411
+ const st = { dryRun, remap, extras, globalConfig: [] };
5331
5412
  guardGitlinks(repo);
5332
5413
  const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
5333
5414
  if (!dryRun && !status) {
@@ -5337,12 +5418,12 @@ async function cmdPush(opts = {}) {
5337
5418
  }
5338
5419
  const mapPath = join43(repo, "path-map.json");
5339
5420
  if (!existsSync37(mapPath)) {
5340
- if (dryRun) return runDryRunPreview(st, null);
5421
+ if (dryRun) return runDryRunPreview(st, null, repo);
5341
5422
  die("path-map.json missing, cannot enforce push allow-list");
5342
5423
  }
5343
5424
  const map = readPathMap(mapPath);
5344
5425
  if (status) enforceAllowList(status, map);
5345
- if (dryRun) return runDryRunPreview(st, map);
5426
+ if (dryRun) return runDryRunPreview(st, map, repo);
5346
5427
  await commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo);
5347
5428
  } catch (err) {
5348
5429
  if (err instanceof NomadFatal) {
@@ -5357,9 +5438,17 @@ async function cmdPush(opts = {}) {
5357
5438
  }
5358
5439
 
5359
5440
  // src/commands.update.ts
5360
- import { execFileSync as execFileSync15 } from "node:child_process";
5441
+ import { execFileSync as execFileSync16 } from "node:child_process";
5361
5442
  init_utils();
5362
- function cmdUpdate(run = execFileSync15) {
5443
+ function readInstalledVersion(run = execFileSync16) {
5444
+ try {
5445
+ return run("nomad", ["--version"], { encoding: "utf8" }).toString().trim() || null;
5446
+ } catch {
5447
+ return null;
5448
+ }
5449
+ }
5450
+ function cmdUpdate(run = execFileSync16) {
5451
+ console.log("Updating claude-nomad CLI via npm...");
5363
5452
  try {
5364
5453
  run("npm", ["update", "-g", "claude-nomad"], { stdio: "inherit" });
5365
5454
  } catch (err) {
@@ -5369,6 +5458,12 @@ function cmdUpdate(run = execFileSync15) {
5369
5458
  }
5370
5459
  throw new NomadFatal(`npm update -g claude-nomad failed: ${e.message}`);
5371
5460
  }
5461
+ const version = readInstalledVersion(run);
5462
+ if (version) {
5463
+ console.log(`claude-nomad is now at v${version}`);
5464
+ } else {
5465
+ console.log('Update complete. Run "nomad --version" to confirm the new version.');
5466
+ }
5372
5467
  }
5373
5468
 
5374
5469
  // src/nomad.ts
@@ -5406,14 +5501,14 @@ import { join as join46 } from "node:path";
5406
5501
 
5407
5502
  // src/init.gh-onboard.ts
5408
5503
  init_config();
5409
- import { execFileSync as execFileSync16 } from "node:child_process";
5504
+ import { execFileSync as execFileSync17 } from "node:child_process";
5410
5505
  init_utils();
5411
5506
  var DEFAULT_REPO_NAME = "claude-nomad-config";
5412
5507
  function isValidRepoName(name) {
5413
5508
  return /^[A-Za-z0-9._-]{1,100}$/.test(name);
5414
5509
  }
5415
5510
  var GH_NETWORK_TIMEOUT_MS = 3e4;
5416
- function ensureOriginRepo(repoName, run = execFileSync16) {
5511
+ function ensureOriginRepo(repoName, run = execFileSync17) {
5417
5512
  if (!isValidRepoName(repoName)) {
5418
5513
  die(
5419
5514
  `invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
@@ -5864,7 +5959,7 @@ function parsePushArgs(argv) {
5864
5959
  // package.json
5865
5960
  var package_default = {
5866
5961
  name: "claude-nomad",
5867
- version: "0.47.1",
5962
+ version: "0.48.0",
5868
5963
  type: "module",
5869
5964
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
5870
5965
  keywords: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.47.1",
3
+ "version": "0.48.0",
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": [