claude-code-arcane 2.9.2 → 2.9.3

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,3 +1,10 @@
1
+ ## [2.9.3](https://github.com/SebastianLuser/claude-code-arcane/compare/v2.9.2...v2.9.3) (2026-09-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **update:** resolve the content version once per run, never fabricate it ([ef78ecf](https://github.com/SebastianLuser/claude-code-arcane/commit/ef78ecf6c5ab17f1a51ea6ae27ef9fa161594798))
7
+
1
8
  ## [2.9.2](https://github.com/SebastianLuser/claude-code-arcane/compare/v2.9.1...v2.9.2) (2026-09-07)
2
9
 
3
10
 
package/dist/cli.js CHANGED
@@ -1439,22 +1439,18 @@ var GitHubContentSource = class {
1439
1439
  fs9.rmSync(tmpDir, { recursive: true, force: true });
1440
1440
  }
1441
1441
  }
1442
- async isAvailable() {
1443
- try {
1444
- const response = await fetch(
1445
- `https://api.github.com/repos/${this.owner}/${this.repo}/commits/${this.branch}`,
1446
- {
1447
- method: "HEAD",
1448
- headers: { "User-Agent": "arcane-cli" },
1449
- signal: AbortSignal.timeout(5e3)
1450
- }
1451
- );
1452
- return response.ok;
1453
- } catch {
1454
- return false;
1455
- }
1456
- }
1457
- async getVersion() {
1442
+ /**
1443
+ * The head commit of the branch, or null when GitHub cannot answer.
1444
+ *
1445
+ * One request, memoized, shared by isAvailable() and getVersion(). It used to be
1446
+ * two - a HEAD to answer "is it reachable?" and a GET to read the sha - against an
1447
+ * endpoint that allows 60 requests an hour unauthenticated. That doubled the cost of
1448
+ * every resolution and, worse, opened a window between the two calls: the HEAD spent
1449
+ * the last request of the hour, the GET came back 403, and the caller had already
1450
+ * committed to the github source. That window is where the fabricated versions came
1451
+ * from.
1452
+ */
1453
+ async resolveVersion() {
1458
1454
  if (this.resolvedVersion) return this.resolvedVersion;
1459
1455
  try {
1460
1456
  const response = await fetch(
@@ -1467,17 +1463,36 @@ var GitHubContentSource = class {
1467
1463
  signal: AbortSignal.timeout(1e4)
1468
1464
  }
1469
1465
  );
1470
- if (!response.ok) {
1471
- throw new Error(`GitHub API returned ${response.status}`);
1472
- }
1466
+ if (!response.ok) return null;
1473
1467
  const data = await response.json();
1474
1468
  this.resolvedVersion = data.sha.substring(0, 12);
1475
1469
  return this.resolvedVersion;
1476
1470
  } catch {
1477
- this.resolvedVersion = `github-${Date.now()}`;
1478
- return this.resolvedVersion;
1471
+ return null;
1479
1472
  }
1480
1473
  }
1474
+ async isAvailable() {
1475
+ return await this.resolveVersion() !== null;
1476
+ }
1477
+ /**
1478
+ * Throws when the version cannot be read, rather than inventing one.
1479
+ *
1480
+ * It used to answer `github-${Date.now()}` on any failure, and that string went on to
1481
+ * be an identity: it stamped `source_version` in the manifest, named a cache directory,
1482
+ * and made the install permanently un-diffable against any real version - it never
1483
+ * matched again, so every later run reported an update and no run ever settled. A
1484
+ * transient 403 became durable state on disk. Callers that cannot proceed without a
1485
+ * version now fall back to the cache, which knows a real one.
1486
+ */
1487
+ async getVersion() {
1488
+ const version = await this.resolveVersion();
1489
+ if (!version) {
1490
+ throw new Error(
1491
+ "Could not read the content version from GitHub (offline, or the unauthenticated API rate limit is exhausted - it resets hourly)."
1492
+ );
1493
+ }
1494
+ return version;
1495
+ }
1481
1496
  async downloadAndExtract(destDir) {
1482
1497
  const tarballUrl = `https://api.github.com/repos/${this.owner}/${this.repo}/tarball/${this.branch}`;
1483
1498
  const response = await fetch(tarballUrl, {
@@ -1548,7 +1563,9 @@ async function resolveContentSource(opts = {}) {
1548
1563
  return github;
1549
1564
  }
1550
1565
  if (preference === "github") {
1551
- throw new Error("GitHub source requested but not available. Check your internet connection.");
1566
+ throw new Error(
1567
+ "GitHub source requested but not available. Check your internet connection, or wait for the unauthenticated API rate limit to reset (hourly)."
1568
+ );
1552
1569
  }
1553
1570
  const cached = findLatestCache();
1554
1571
  if (cached) {
@@ -2357,6 +2374,10 @@ async function updateCommand(opts) {
2357
2374
  Updating ${targets.length} installation${targets.length === 1 ? "" : "s"}...`)
2358
2375
  );
2359
2376
  }
2377
+ const sharedSource = await resolveContentSource({
2378
+ source: opts.source ?? "auto",
2379
+ quiet: opts.quiet
2380
+ });
2360
2381
  const results = [];
2361
2382
  for (const target of targets) {
2362
2383
  if (!opts.quiet) {
@@ -2364,7 +2385,7 @@ Updating ${targets.length} installation${targets.length === 1 ? "" : "s"}...`)
2364
2385
  \u2022 ${target}`));
2365
2386
  }
2366
2387
  try {
2367
- results.push(await updateTarget(target, opts));
2388
+ results.push(await updateTarget(target, opts, sharedSource));
2368
2389
  } catch (err) {
2369
2390
  if (!opts.quiet) {
2370
2391
  console.log(chalk7.red(` Update failed: ${err.message}`));
@@ -2382,7 +2403,7 @@ Updating ${targets.length} installation${targets.length === 1 ? "" : "s"}...`)
2382
2403
  printGeneralSummary(results, opts.dryRun ?? false);
2383
2404
  }
2384
2405
  }
2385
- async function updateTarget(target, opts) {
2406
+ async function updateTarget(target, opts, sharedSource) {
2386
2407
  const manifest = readManifest(target);
2387
2408
  if (!manifest) {
2388
2409
  if (!opts.quiet) {
@@ -2390,7 +2411,7 @@ async function updateTarget(target, opts) {
2390
2411
  }
2391
2412
  return { target, status: "no-manifest", updated: 0, skipped: 0, removed: 0 };
2392
2413
  }
2393
- const contentSource = await resolveContentSource({
2414
+ const contentSource = sharedSource ?? await resolveContentSource({
2394
2415
  source: opts.source ?? "auto",
2395
2416
  quiet: opts.quiet
2396
2417
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-arcane",
3
- "version": "2.9.2",
3
+ "version": "2.9.3",
4
4
  "description": "Skills, agents, hooks and rules for Claude Code — installable via npx claude-code-arcane",
5
5
  "type": "module",
6
6
  "bin": {