claude-code-arcane 2.9.3 → 2.9.5
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 +15 -0
- package/dist/cli.js +110 -60
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [2.9.5](https://github.com/SebastianLuser/claude-code-arcane/compare/v2.9.4...v2.9.5) (2026-09-07)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **update:** record the hash Arcane shipped, not the customized one on disk ([921c89d](https://github.com/SebastianLuser/claude-code-arcane/commit/921c89d25eb8167e1efbcd27ae233faf3beb85c8))
|
|
7
|
+
|
|
8
|
+
## [2.9.4](https://github.com/SebastianLuser/claude-code-arcane/compare/v2.9.3...v2.9.4) (2026-09-07)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **add:** keep content_hashes instead of rebuilding the manifest ([1ecce98](https://github.com/SebastianLuser/claude-code-arcane/commit/1ecce98ebcb8a480dd4b748f41606badec9fb0fd))
|
|
14
|
+
* **cache:** resolve the arcane home in one place, honouring ARCANE_HOME ([4a7a24d](https://github.com/SebastianLuser/claude-code-arcane/commit/4a7a24d6a28df1abfe94be75f590c79d8428dca5))
|
|
15
|
+
|
|
1
16
|
## [2.9.3](https://github.com/SebastianLuser/claude-code-arcane/compare/v2.9.2...v2.9.3) (2026-09-07)
|
|
2
17
|
|
|
3
18
|
|
package/dist/cli.js
CHANGED
|
@@ -391,6 +391,10 @@ import path8 from "path";
|
|
|
391
391
|
import { fileURLToPath } from "url";
|
|
392
392
|
import path3 from "path";
|
|
393
393
|
import fs3 from "fs";
|
|
394
|
+
import os from "os";
|
|
395
|
+
function arcaneHome() {
|
|
396
|
+
return process.env.ARCANE_HOME ?? path3.join(os.homedir(), ".arcane");
|
|
397
|
+
}
|
|
394
398
|
function getPackageRoot() {
|
|
395
399
|
const thisFile = fileURLToPath(import.meta.url);
|
|
396
400
|
return path3.resolve(path3.dirname(thisFile), "..");
|
|
@@ -1310,15 +1314,18 @@ import os2 from "os";
|
|
|
1310
1314
|
// src/cache.ts
|
|
1311
1315
|
import fs8 from "fs";
|
|
1312
1316
|
import path9 from "path";
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1317
|
+
function cacheDir() {
|
|
1318
|
+
return path9.join(arcaneHome(), "cache");
|
|
1319
|
+
}
|
|
1316
1320
|
function getCachePath(version) {
|
|
1317
|
-
return path9.join(
|
|
1321
|
+
return path9.join(cacheDir(), version);
|
|
1322
|
+
}
|
|
1323
|
+
function isUsableContentRoot(dir) {
|
|
1324
|
+
return fs8.existsSync(path9.join(dir, "profiles")) && fs8.existsSync(path9.join(dir, "skills"));
|
|
1318
1325
|
}
|
|
1319
1326
|
function isCached(version) {
|
|
1320
1327
|
const versionDir = getCachePath(version);
|
|
1321
|
-
return fs8.existsSync(versionDir) && fs8.existsSync(path9.join(versionDir, ".cache-meta.json"));
|
|
1328
|
+
return fs8.existsSync(versionDir) && fs8.existsSync(path9.join(versionDir, ".cache-meta.json")) && isUsableContentRoot(versionDir);
|
|
1322
1329
|
}
|
|
1323
1330
|
function getCachedContentRoot(version) {
|
|
1324
1331
|
if (!isCached(version)) return null;
|
|
@@ -1347,9 +1354,9 @@ function storeInCache(version, contentDir) {
|
|
|
1347
1354
|
return versionDir;
|
|
1348
1355
|
}
|
|
1349
1356
|
function pruneCache(keepCount = 3) {
|
|
1350
|
-
if (!fs8.existsSync(
|
|
1351
|
-
const entries = fs8.readdirSync(
|
|
1352
|
-
const metaPath = path9.join(
|
|
1357
|
+
if (!fs8.existsSync(cacheDir())) return;
|
|
1358
|
+
const entries = fs8.readdirSync(cacheDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
|
|
1359
|
+
const metaPath = path9.join(cacheDir(), e.name, ".cache-meta.json");
|
|
1353
1360
|
let cachedAt = 0;
|
|
1354
1361
|
if (fs8.existsSync(metaPath)) {
|
|
1355
1362
|
try {
|
|
@@ -1362,9 +1369,24 @@ function pruneCache(keepCount = 3) {
|
|
|
1362
1369
|
return { name: e.name, cachedAt };
|
|
1363
1370
|
}).sort((a, b) => b.cachedAt - a.cachedAt);
|
|
1364
1371
|
for (const entry of entries.slice(keepCount)) {
|
|
1365
|
-
fs8.rmSync(path9.join(
|
|
1372
|
+
fs8.rmSync(path9.join(cacheDir(), entry.name), { recursive: true, force: true });
|
|
1366
1373
|
}
|
|
1367
1374
|
}
|
|
1375
|
+
function listCachedVersions() {
|
|
1376
|
+
if (!fs8.existsSync(cacheDir())) return [];
|
|
1377
|
+
return fs8.readdirSync(cacheDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
|
|
1378
|
+
const metaPath = path9.join(cacheDir(), e.name, ".cache-meta.json");
|
|
1379
|
+
let cachedAt = "unknown";
|
|
1380
|
+
if (fs8.existsSync(metaPath)) {
|
|
1381
|
+
try {
|
|
1382
|
+
const meta = JSON.parse(fs8.readFileSync(metaPath, "utf-8"));
|
|
1383
|
+
cachedAt = meta.cached_at;
|
|
1384
|
+
} catch {
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return { version: e.name, cachedAt };
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1368
1390
|
function copyDirRecursive(src, dest) {
|
|
1369
1391
|
fs8.mkdirSync(dest, { recursive: true });
|
|
1370
1392
|
for (const entry of fs8.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -1595,31 +1617,16 @@ async function resolveContentSourceForVersion(version, opts = {}) {
|
|
|
1595
1617
|
return source;
|
|
1596
1618
|
}
|
|
1597
1619
|
function findLatestCache() {
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
if (fs9.existsSync(metaPath)) {
|
|
1604
|
-
try {
|
|
1605
|
-
const meta = JSON.parse(fs9.readFileSync(metaPath, "utf-8"));
|
|
1606
|
-
cachedAt = new Date(meta.cached_at).getTime();
|
|
1607
|
-
} catch {
|
|
1608
|
-
cachedAt = 0;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
return { name: e.name, cachedAt };
|
|
1612
|
-
}).sort((a, b) => b.cachedAt - a.cachedAt);
|
|
1613
|
-
return entries.length > 0 ? entries[0].name : null;
|
|
1620
|
+
const usable = listCachedVersions().filter((entry) => isCached(entry.version)).map((entry) => ({
|
|
1621
|
+
version: entry.version,
|
|
1622
|
+
cachedAt: new Date(entry.cachedAt).getTime() || 0
|
|
1623
|
+
})).sort((a, b) => b.cachedAt - a.cachedAt);
|
|
1624
|
+
return usable.length > 0 ? usable[0].version : null;
|
|
1614
1625
|
}
|
|
1615
1626
|
|
|
1616
1627
|
// src/registry.ts
|
|
1617
1628
|
import path11 from "path";
|
|
1618
|
-
import os3 from "os";
|
|
1619
1629
|
var REGISTRY_VERSION = 1;
|
|
1620
|
-
function arcaneHome() {
|
|
1621
|
-
return process.env.ARCANE_HOME ?? path11.join(os3.homedir(), ".arcane");
|
|
1622
|
-
}
|
|
1623
1630
|
function registryPath() {
|
|
1624
1631
|
return path11.join(arcaneHome(), "installations.json");
|
|
1625
1632
|
}
|
|
@@ -1898,16 +1905,11 @@ async function addCommand(items = []) {
|
|
|
1898
1905
|
manifest.installed_skills.push(...added);
|
|
1899
1906
|
manifest.total_skills = manifest.installed_skills.length;
|
|
1900
1907
|
manifest.total_rules = manifest.installed_rules.length;
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
hooks: [],
|
|
1907
|
-
output_styles: [],
|
|
1908
|
-
permissions: { allow: [], deny: [] }
|
|
1909
|
-
};
|
|
1910
|
-
writeManifest(target, merged, manifest.profile_command, root);
|
|
1908
|
+
persistAdditions(target, claudeDir, manifest, {
|
|
1909
|
+
skills: added,
|
|
1910
|
+
rules: addedRules,
|
|
1911
|
+
agents: addedAgents
|
|
1912
|
+
});
|
|
1911
1913
|
const totalAdded = added.length + addedRules.length + addedAgents.length + (statuslineAdded ? 1 : 0);
|
|
1912
1914
|
console.log(
|
|
1913
1915
|
chalk3.bold(
|
|
@@ -1932,6 +1934,35 @@ Added ${totalAdded} items:`
|
|
|
1932
1934
|
);
|
|
1933
1935
|
}
|
|
1934
1936
|
}
|
|
1937
|
+
function persistAdditions(target, claudeDir, manifest, addedItems) {
|
|
1938
|
+
const updates = {
|
|
1939
|
+
profiles: manifest.profiles,
|
|
1940
|
+
profile_command: manifest.profile_command,
|
|
1941
|
+
installed_skills: manifest.installed_skills,
|
|
1942
|
+
installed_rules: manifest.installed_rules,
|
|
1943
|
+
installed_agents: manifest.installed_agents,
|
|
1944
|
+
total_skills: manifest.total_skills,
|
|
1945
|
+
total_rules: manifest.total_rules,
|
|
1946
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
1947
|
+
};
|
|
1948
|
+
const hashes = manifest.content_hashes;
|
|
1949
|
+
if (hashes) {
|
|
1950
|
+
const fresh = computeContentHashes(claudeDir);
|
|
1951
|
+
for (const skill of addedItems.skills) {
|
|
1952
|
+
if (fresh.skills[skill]) hashes.skills[skill] = fresh.skills[skill];
|
|
1953
|
+
}
|
|
1954
|
+
for (const rule of addedItems.rules) {
|
|
1955
|
+
const key = rule.endsWith(".md") ? rule : `${rule}.md`;
|
|
1956
|
+
if (fresh.rules[key]) hashes.rules[key] = fresh.rules[key];
|
|
1957
|
+
}
|
|
1958
|
+
for (const entry of addedItems.agents) {
|
|
1959
|
+
const { division } = parseAgentEntry(entry);
|
|
1960
|
+
if (fresh.agents[division]) hashes.agents[division] = fresh.agents[division];
|
|
1961
|
+
}
|
|
1962
|
+
updates.content_hashes = hashes;
|
|
1963
|
+
}
|
|
1964
|
+
updateManifestFields(target, updates);
|
|
1965
|
+
}
|
|
1935
1966
|
function printCatalog(root, installedProfiles, installedSkills) {
|
|
1936
1967
|
const profiles = listProfiles(path14.join(root, "profiles")).filter(
|
|
1937
1968
|
(pr) => !installedProfiles.includes(pr.name)
|
|
@@ -2278,7 +2309,7 @@ async function statusCommand() {
|
|
|
2278
2309
|
// src/commands/update.ts
|
|
2279
2310
|
import fs14 from "fs";
|
|
2280
2311
|
import path17 from "path";
|
|
2281
|
-
import
|
|
2312
|
+
import os3 from "os";
|
|
2282
2313
|
import chalk7 from "chalk";
|
|
2283
2314
|
|
|
2284
2315
|
// src/self-update.ts
|
|
@@ -2358,7 +2389,7 @@ async function updateCommand(opts) {
|
|
|
2358
2389
|
registerInstallation(cwd);
|
|
2359
2390
|
}
|
|
2360
2391
|
const targets = pruneRegistry().map((e) => e.path);
|
|
2361
|
-
const globalTarget =
|
|
2392
|
+
const globalTarget = os3.homedir();
|
|
2362
2393
|
if (!process.env.VITEST && fs14.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
|
|
2363
2394
|
targets.push(globalTarget);
|
|
2364
2395
|
}
|
|
@@ -2484,7 +2515,12 @@ Update available: ${chalk7.red(installedVersion)} -> ${chalk7.green(currentVersi
|
|
|
2484
2515
|
arcane_version: currentVersion,
|
|
2485
2516
|
source_version: currentVersion,
|
|
2486
2517
|
updated_at: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
2487
|
-
content_hashes:
|
|
2518
|
+
content_hashes: hashesToRecord(
|
|
2519
|
+
installedHashes,
|
|
2520
|
+
manifest.content_hashes ?? null,
|
|
2521
|
+
items,
|
|
2522
|
+
opts.force ?? false
|
|
2523
|
+
),
|
|
2488
2524
|
installed_skills: merged.skills,
|
|
2489
2525
|
installed_rules: allRules,
|
|
2490
2526
|
installed_agents: merged.agents,
|
|
@@ -2525,7 +2561,12 @@ Refreshed ${unhashed.length} file(s); no other changes to apply.`) : chalk7.gree
|
|
|
2525
2561
|
arcane_version: currentVersion,
|
|
2526
2562
|
source_version: currentVersion,
|
|
2527
2563
|
updated_at: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
2528
|
-
content_hashes:
|
|
2564
|
+
content_hashes: hashesToRecord(
|
|
2565
|
+
newHashes,
|
|
2566
|
+
manifest.content_hashes ?? null,
|
|
2567
|
+
items,
|
|
2568
|
+
opts.force ?? false
|
|
2569
|
+
),
|
|
2529
2570
|
installed_skills: merged.skills,
|
|
2530
2571
|
installed_rules: allRules,
|
|
2531
2572
|
installed_agents: merged.agents,
|
|
@@ -2590,19 +2631,25 @@ function granularAgentDivisions(entries) {
|
|
|
2590
2631
|
for (const division of whole) partial.delete(division);
|
|
2591
2632
|
return partial;
|
|
2592
2633
|
}
|
|
2593
|
-
function
|
|
2594
|
-
const
|
|
2595
|
-
skills: { ...
|
|
2596
|
-
rules: { ...
|
|
2597
|
-
agents: { ...
|
|
2598
|
-
hooks: { ...
|
|
2634
|
+
function hashesToRecord(fresh, previous, items, force) {
|
|
2635
|
+
const recorded = {
|
|
2636
|
+
skills: { ...fresh.skills },
|
|
2637
|
+
rules: { ...fresh.rules },
|
|
2638
|
+
agents: { ...fresh.agents },
|
|
2639
|
+
hooks: { ...fresh.hooks }
|
|
2599
2640
|
};
|
|
2600
2641
|
for (const item of items) {
|
|
2601
|
-
if (item.action !== "keep-foreign") continue;
|
|
2602
2642
|
const bucket = `${item.type}s`;
|
|
2603
|
-
|
|
2643
|
+
if (item.action === "keep-foreign") {
|
|
2644
|
+
delete recorded[bucket][item.name];
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2647
|
+
if (item.action === "skip-customized" && !force) {
|
|
2648
|
+
const asInstalled = previous?.[bucket]?.[item.name];
|
|
2649
|
+
if (asInstalled) recorded[bucket][item.name] = asInstalled;
|
|
2650
|
+
}
|
|
2604
2651
|
}
|
|
2605
|
-
return
|
|
2652
|
+
return recorded;
|
|
2606
2653
|
}
|
|
2607
2654
|
function computeUpdatePlan(manifestHashes, installedHashes, sourceHashes, owned, exemptAgentDivisions = /* @__PURE__ */ new Set()) {
|
|
2608
2655
|
const items = [];
|
|
@@ -3022,9 +3069,9 @@ async function worktreeCommand(branch, opts) {
|
|
|
3022
3069
|
// src/commands/global.ts
|
|
3023
3070
|
import fs16 from "fs";
|
|
3024
3071
|
import path19 from "path";
|
|
3025
|
-
import
|
|
3072
|
+
import os4 from "os";
|
|
3026
3073
|
import chalk10 from "chalk";
|
|
3027
|
-
var CLAUDE_HOME = path19.join(
|
|
3074
|
+
var CLAUDE_HOME = path19.join(os4.homedir(), ".claude");
|
|
3028
3075
|
var SCRIPTS_DIR = path19.join(CLAUDE_HOME, "scripts", "worktree-isolation");
|
|
3029
3076
|
var SETTINGS_PATH = path19.join(CLAUDE_HOME, "settings.json");
|
|
3030
3077
|
async function globalCommand(opts) {
|
|
@@ -3191,9 +3238,10 @@ function removeHookFromSettings() {
|
|
|
3191
3238
|
// src/update-check.ts
|
|
3192
3239
|
import fs17 from "fs";
|
|
3193
3240
|
import path20 from "path";
|
|
3194
|
-
import os6 from "os";
|
|
3195
3241
|
import chalk11 from "chalk";
|
|
3196
|
-
|
|
3242
|
+
function checkFile() {
|
|
3243
|
+
return path20.join(arcaneHome(), "last-check.json");
|
|
3244
|
+
}
|
|
3197
3245
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
3198
3246
|
var GITHUB_OWNER2 = "SebastianLuser";
|
|
3199
3247
|
var GITHUB_REPO2 = "Claude-Code-Arcane";
|
|
@@ -3318,19 +3366,21 @@ async function getLatestNpmVersion() {
|
|
|
3318
3366
|
}
|
|
3319
3367
|
function readCachedCheck() {
|
|
3320
3368
|
try {
|
|
3321
|
-
|
|
3322
|
-
|
|
3369
|
+
const file = checkFile();
|
|
3370
|
+
if (!fs17.existsSync(file)) return null;
|
|
3371
|
+
return JSON.parse(fs17.readFileSync(file, "utf-8"));
|
|
3323
3372
|
} catch {
|
|
3324
3373
|
return null;
|
|
3325
3374
|
}
|
|
3326
3375
|
}
|
|
3327
3376
|
function writeCachedCheck(result) {
|
|
3328
3377
|
try {
|
|
3329
|
-
const
|
|
3378
|
+
const file = checkFile();
|
|
3379
|
+
const dir = path20.dirname(file);
|
|
3330
3380
|
if (!fs17.existsSync(dir)) {
|
|
3331
3381
|
fs17.mkdirSync(dir, { recursive: true });
|
|
3332
3382
|
}
|
|
3333
|
-
fs17.writeFileSync(
|
|
3383
|
+
fs17.writeFileSync(file, JSON.stringify(result, null, 2) + "\n");
|
|
3334
3384
|
} catch {
|
|
3335
3385
|
}
|
|
3336
3386
|
}
|