claude-code-arcane 1.1.1 → 1.3.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 +16 -0
- package/README.md +2 -0
- package/agents/engineering/dotnet-engineer.md +78 -0
- package/dist/cli.js +284 -72
- package/docs/RELEASE-SETUP.md +99 -0
- package/docs/SKILLS-CATALOG.md +26 -3
- package/docs/presentations/arcane-overview-12.pptx +0 -0
- package/docs/presentations/arcane-overview.pptx +0 -0
- package/docs/presentations/build_arcane_deck.py +310 -0
- package/docs/presentations/build_arcane_deck_12.py +399 -0
- package/package.json +1 -1
- package/profiles/backend-dotnet.yaml +54 -0
- package/profiles/job-hunt.yaml +35 -0
- package/profiles/unity-design.yaml +1 -0
- package/profiles/unity-dev.yaml +1 -0
- package/rules/dotnet-code.md +64 -0
- package/skills/cold-outreach/SKILL.md +65 -0
- package/skills/cold-outreach/references/recruiter-playbook.md +65 -0
- package/skills/cover-letter/SKILL.md +66 -0
- package/skills/cv-ats-export/SKILL.md +64 -0
- package/skills/cv-ats-export/scripts/cv_export.py +306 -0
- package/skills/cv-tailor/SKILL.md +70 -0
- package/skills/cv-tailor/references/ats-keywords.md +46 -0
- package/skills/dotnet-architecture/SKILL.md +66 -0
- package/skills/dotnet-architecture/references/anti-patterns.md +12 -0
- package/skills/dotnet-architecture/references/checklist.md +19 -0
- package/skills/dotnet-architecture/references/patterns.md +118 -0
- package/skills/dotnet-architecture/references/project-structure.md +78 -0
- package/skills/dotnet-best-practices/SKILL.md +76 -0
- package/skills/dotnet-best-practices/references/api-design.md +75 -0
- package/skills/dotnet-best-practices/references/architecture.md +62 -0
- package/skills/dotnet-best-practices/references/async.md +62 -0
- package/skills/dotnet-best-practices/references/database.md +69 -0
- package/skills/dotnet-best-practices/references/dependency-injection.md +73 -0
- package/skills/dotnet-best-practices/references/devops.md +76 -0
- package/skills/dotnet-best-practices/references/error-handling.md +72 -0
- package/skills/dotnet-best-practices/references/performance.md +63 -0
- package/skills/dotnet-best-practices/references/security.md +73 -0
- package/skills/dotnet-best-practices/references/testing.md +76 -0
- package/skills/dotnet-scaffold/SKILL.md +99 -0
- package/skills/install-mcp/SKILL.md +107 -0
- package/skills/install-mcp/references/manual-setup.md +92 -0
- package/skills/interview-prep/SKILL.md +69 -0
- package/skills/interview-prep/references/star-framework.md +42 -0
- package/skills/job-hunt/SKILL.md +92 -0
- package/skills/job-hunt/references/templates/Aplicacion.md +48 -0
- package/skills/job-hunt/references/templates/CV Custom.md +53 -0
- package/skills/job-hunt/references/templates/Contacto.md +30 -0
- package/skills/job-hunt/references/templates/Dashboard.md +45 -0
- package/skills/job-hunt/references/templates/Empresa.md +36 -0
- package/skills/job-hunt/references/templates/Entrevista.md +44 -0
- package/skills/job-hunt/references/templates/Perfil.md +38 -0
- package/skills/job-search/SKILL.md +83 -0
- package/skills/job-search/references/scoring-rubric.md +43 -0
- package/skills/linkedin-optimize/SKILL.md +79 -0
- package/skills/master-profile/SKILL.md +69 -0
- package/skills/network-map/SKILL.md +61 -0
- package/skills/network-map/scripts/network_map.py +109 -0
- package/skills/personal-brand/SKILL.md +54 -0
- package/skills/personal-brand/references/post-pillars.md +66 -0
- package/skills/portfolio-site/SKILL.md +59 -0
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/install.ts
|
|
7
|
-
import
|
|
7
|
+
import path10 from "path";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
|
|
10
10
|
// src/profiles.ts
|
|
@@ -1207,6 +1207,59 @@ function findLatestCache() {
|
|
|
1207
1207
|
return entries.length > 0 ? entries[0].name : null;
|
|
1208
1208
|
}
|
|
1209
1209
|
|
|
1210
|
+
// src/registry.ts
|
|
1211
|
+
import path9 from "path";
|
|
1212
|
+
import os3 from "os";
|
|
1213
|
+
var REGISTRY_VERSION = 1;
|
|
1214
|
+
function arcaneHome() {
|
|
1215
|
+
return process.env.ARCANE_HOME ?? path9.join(os3.homedir(), ".arcane");
|
|
1216
|
+
}
|
|
1217
|
+
function registryPath() {
|
|
1218
|
+
return path9.join(arcaneHome(), "installations.json");
|
|
1219
|
+
}
|
|
1220
|
+
function readRegistryFile() {
|
|
1221
|
+
const p = registryPath();
|
|
1222
|
+
if (!fileExists(p)) {
|
|
1223
|
+
return { version: REGISTRY_VERSION, installations: [] };
|
|
1224
|
+
}
|
|
1225
|
+
try {
|
|
1226
|
+
const data = readJsonSync(p);
|
|
1227
|
+
return {
|
|
1228
|
+
version: REGISTRY_VERSION,
|
|
1229
|
+
installations: Array.isArray(data.installations) ? data.installations : []
|
|
1230
|
+
};
|
|
1231
|
+
} catch {
|
|
1232
|
+
return { version: REGISTRY_VERSION, installations: [] };
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function writeRegistryFile(registry) {
|
|
1236
|
+
try {
|
|
1237
|
+
ensureDir(arcaneHome());
|
|
1238
|
+
writeJsonSync(registryPath(), registry);
|
|
1239
|
+
} catch {
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
function registerInstallation(target) {
|
|
1243
|
+
const abs = path9.resolve(target);
|
|
1244
|
+
const registry = readRegistryFile();
|
|
1245
|
+
if (registry.installations.some((e) => e.path === abs)) {
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
registry.installations.push({
|
|
1249
|
+
path: abs,
|
|
1250
|
+
registered_at: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
1251
|
+
});
|
|
1252
|
+
writeRegistryFile(registry);
|
|
1253
|
+
}
|
|
1254
|
+
function pruneRegistry() {
|
|
1255
|
+
const registry = readRegistryFile();
|
|
1256
|
+
const alive = registry.installations.filter((e) => fileExists(manifestPath(e.path)));
|
|
1257
|
+
if (alive.length !== registry.installations.length) {
|
|
1258
|
+
writeRegistryFile({ version: REGISTRY_VERSION, installations: alive });
|
|
1259
|
+
}
|
|
1260
|
+
return alive;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1210
1263
|
// src/commands/install.ts
|
|
1211
1264
|
async function installCommand(profileExpr, opts) {
|
|
1212
1265
|
const source = await resolveContentSource({
|
|
@@ -1214,8 +1267,8 @@ async function installCommand(profileExpr, opts) {
|
|
|
1214
1267
|
quiet: !profileExpr
|
|
1215
1268
|
});
|
|
1216
1269
|
const root = await source.getContentRoot();
|
|
1217
|
-
const profilesDir =
|
|
1218
|
-
const target =
|
|
1270
|
+
const profilesDir = path10.join(root, "profiles");
|
|
1271
|
+
const target = path10.resolve(opts.target ?? process.cwd());
|
|
1219
1272
|
if (!profileExpr) {
|
|
1220
1273
|
const profiles = listProfiles(profilesDir);
|
|
1221
1274
|
const bases = profiles.filter((p) => p.type === "base");
|
|
@@ -1277,13 +1330,14 @@ Installing profile: ${chalk.cyan(profileExpr)}`)
|
|
|
1277
1330
|
});
|
|
1278
1331
|
installer.run(profileExpr, worktreeMeta);
|
|
1279
1332
|
if (!opts.dryRun) {
|
|
1333
|
+
registerInstallation(target);
|
|
1280
1334
|
console.log(chalk.green("\n Installation complete."));
|
|
1281
1335
|
}
|
|
1282
1336
|
}
|
|
1283
1337
|
|
|
1284
1338
|
// src/commands/add.ts
|
|
1285
1339
|
import fs8 from "fs";
|
|
1286
|
-
import
|
|
1340
|
+
import path11 from "path";
|
|
1287
1341
|
import chalk2 from "chalk";
|
|
1288
1342
|
async function addCommand(items) {
|
|
1289
1343
|
const target = process.cwd();
|
|
@@ -1298,7 +1352,7 @@ async function addCommand(items) {
|
|
|
1298
1352
|
);
|
|
1299
1353
|
process.exit(1);
|
|
1300
1354
|
}
|
|
1301
|
-
const claudeDir =
|
|
1355
|
+
const claudeDir = path11.join(target, ".claude");
|
|
1302
1356
|
const added = [];
|
|
1303
1357
|
const skipped = [];
|
|
1304
1358
|
const notFound = [];
|
|
@@ -1308,7 +1362,7 @@ async function addCommand(items) {
|
|
|
1308
1362
|
for (const item of items) {
|
|
1309
1363
|
if (item.startsWith("+")) {
|
|
1310
1364
|
const profileName = item.slice(1);
|
|
1311
|
-
const profilePath =
|
|
1365
|
+
const profilePath = path11.join(root, "profiles", `${profileName}.yaml`);
|
|
1312
1366
|
if (!fs8.existsSync(profilePath)) {
|
|
1313
1367
|
console.error(chalk2.red(` Profile '${profileName}' not found`));
|
|
1314
1368
|
continue;
|
|
@@ -1322,10 +1376,10 @@ async function addCommand(items) {
|
|
|
1322
1376
|
}
|
|
1323
1377
|
for (const rule of profile.rules.universal) {
|
|
1324
1378
|
if (!manifest.installed_rules.includes(rule)) {
|
|
1325
|
-
const src =
|
|
1379
|
+
const src = path11.join(root, "rules", `${rule}.md`);
|
|
1326
1380
|
if (fs8.existsSync(src)) {
|
|
1327
|
-
ensureDir(
|
|
1328
|
-
fs8.copyFileSync(src,
|
|
1381
|
+
ensureDir(path11.join(claudeDir, "rules"));
|
|
1382
|
+
fs8.copyFileSync(src, path11.join(claudeDir, "rules", `${rule}.md`));
|
|
1329
1383
|
manifest.installed_rules.push(rule);
|
|
1330
1384
|
addedRules.push(rule);
|
|
1331
1385
|
}
|
|
@@ -1333,10 +1387,10 @@ async function addCommand(items) {
|
|
|
1333
1387
|
}
|
|
1334
1388
|
for (const rule of profile.rules.gamedev) {
|
|
1335
1389
|
if (!manifest.installed_rules.includes(rule)) {
|
|
1336
|
-
const src =
|
|
1390
|
+
const src = path11.join(root, "rules", "gamedev", `${rule}.md`);
|
|
1337
1391
|
if (fs8.existsSync(src)) {
|
|
1338
|
-
ensureDir(
|
|
1339
|
-
fs8.copyFileSync(src,
|
|
1392
|
+
ensureDir(path11.join(claudeDir, "rules"));
|
|
1393
|
+
fs8.copyFileSync(src, path11.join(claudeDir, "rules", `${rule}.md`));
|
|
1340
1394
|
manifest.installed_rules.push(rule);
|
|
1341
1395
|
addedRules.push(rule);
|
|
1342
1396
|
}
|
|
@@ -1344,9 +1398,9 @@ async function addCommand(items) {
|
|
|
1344
1398
|
}
|
|
1345
1399
|
for (const agentDir of profile.agents) {
|
|
1346
1400
|
if (!manifest.installed_agents.includes(agentDir)) {
|
|
1347
|
-
const src =
|
|
1401
|
+
const src = path11.join(root, "agents", agentDir);
|
|
1348
1402
|
if (fs8.existsSync(src)) {
|
|
1349
|
-
const dst =
|
|
1403
|
+
const dst = path11.join(claudeDir, "agents", agentDir);
|
|
1350
1404
|
copyDirSync(src, dst);
|
|
1351
1405
|
manifest.installed_agents.push(agentDir);
|
|
1352
1406
|
addedAgents.push(agentDir);
|
|
@@ -1354,9 +1408,9 @@ async function addCommand(items) {
|
|
|
1354
1408
|
}
|
|
1355
1409
|
}
|
|
1356
1410
|
if (profileName === "statusline") {
|
|
1357
|
-
const statuslineSrc =
|
|
1411
|
+
const statuslineSrc = path11.join(root, "hooks", "statusline.sh");
|
|
1358
1412
|
if (fs8.existsSync(statuslineSrc)) {
|
|
1359
|
-
fs8.copyFileSync(statuslineSrc,
|
|
1413
|
+
fs8.copyFileSync(statuslineSrc, path11.join(claudeDir, "statusline.sh"));
|
|
1360
1414
|
statuslineAdded = true;
|
|
1361
1415
|
}
|
|
1362
1416
|
}
|
|
@@ -1403,14 +1457,14 @@ Added ${totalAdded} items:`));
|
|
|
1403
1457
|
}
|
|
1404
1458
|
function addSkill(root, target, skill, installed) {
|
|
1405
1459
|
if (installed.includes(skill)) return "skipped";
|
|
1406
|
-
const src =
|
|
1460
|
+
const src = path11.join(root, "skills", skill);
|
|
1407
1461
|
if (!fs8.existsSync(src)) return "not-found";
|
|
1408
|
-
const dst =
|
|
1462
|
+
const dst = path11.join(target, ".claude", "skills", skill);
|
|
1409
1463
|
copyDirSync(src, dst);
|
|
1410
1464
|
return "added";
|
|
1411
1465
|
}
|
|
1412
1466
|
function mergePermissions(claudeDir, newPerms) {
|
|
1413
|
-
const settingsPath =
|
|
1467
|
+
const settingsPath = path11.join(claudeDir, "settings.json");
|
|
1414
1468
|
if (!fs8.existsSync(settingsPath)) return;
|
|
1415
1469
|
const settings = readJsonSync(settingsPath);
|
|
1416
1470
|
const perms = settings.permissions ?? { allow: [], deny: [] };
|
|
@@ -1424,7 +1478,7 @@ function mergePermissions(claudeDir, newPerms) {
|
|
|
1424
1478
|
writeJsonSync(settingsPath, settings);
|
|
1425
1479
|
}
|
|
1426
1480
|
function addStatuslineToSettings(claudeDir) {
|
|
1427
|
-
const settingsPath =
|
|
1481
|
+
const settingsPath = path11.join(claudeDir, "settings.json");
|
|
1428
1482
|
if (!fs8.existsSync(settingsPath)) return;
|
|
1429
1483
|
const settings = readJsonSync(settingsPath);
|
|
1430
1484
|
if (settings.statusLine) return;
|
|
@@ -1437,7 +1491,7 @@ function addStatuslineToSettings(claudeDir) {
|
|
|
1437
1491
|
|
|
1438
1492
|
// src/commands/remove.ts
|
|
1439
1493
|
import fs9 from "fs";
|
|
1440
|
-
import
|
|
1494
|
+
import path12 from "path";
|
|
1441
1495
|
import chalk3 from "chalk";
|
|
1442
1496
|
var CORE_SKILLS = [
|
|
1443
1497
|
"commit",
|
|
@@ -1500,7 +1554,7 @@ async function removeCommand(items) {
|
|
|
1500
1554
|
}
|
|
1501
1555
|
manifest.total_skills = manifest.installed_skills.length;
|
|
1502
1556
|
manifest.total_rules = manifest.installed_rules.length;
|
|
1503
|
-
const mp =
|
|
1557
|
+
const mp = path12.join(target, ".claude", "arcane-manifest.json");
|
|
1504
1558
|
writeJsonSync(mp, manifest);
|
|
1505
1559
|
const totalRemoved = removedSkills.length + removedAgents.length + removedProfiles.length;
|
|
1506
1560
|
console.log(chalk3.bold(`
|
|
@@ -1521,7 +1575,7 @@ function removeSkill(target, skill, manifest) {
|
|
|
1521
1575
|
);
|
|
1522
1576
|
return "skipped";
|
|
1523
1577
|
}
|
|
1524
|
-
const skillDir =
|
|
1578
|
+
const skillDir = path12.join(target, ".claude", "skills", skill);
|
|
1525
1579
|
if (!fs9.existsSync(skillDir)) return "skipped";
|
|
1526
1580
|
fs9.rmSync(skillDir, { recursive: true, force: true });
|
|
1527
1581
|
manifest.installed_skills = manifest.installed_skills.filter(
|
|
@@ -1542,7 +1596,7 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
1542
1596
|
);
|
|
1543
1597
|
return { removed: false, skills: [], agents: [] };
|
|
1544
1598
|
}
|
|
1545
|
-
const profilePath =
|
|
1599
|
+
const profilePath = path12.join(root, "profiles", `${profileName}.yaml`);
|
|
1546
1600
|
if (!fs9.existsSync(profilePath)) {
|
|
1547
1601
|
console.warn(
|
|
1548
1602
|
chalk3.yellow(` WARNING: Profile '${profileName}.yaml' not found.`)
|
|
@@ -1557,7 +1611,7 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
1557
1611
|
const sharedAgents = /* @__PURE__ */ new Set();
|
|
1558
1612
|
const sharedRules = /* @__PURE__ */ new Set();
|
|
1559
1613
|
for (const rp of remainingProfiles) {
|
|
1560
|
-
const rpPath =
|
|
1614
|
+
const rpPath = path12.join(root, "profiles", `${rp}.yaml`);
|
|
1561
1615
|
if (!fs9.existsSync(rpPath)) continue;
|
|
1562
1616
|
const rpDef = parseProfile(rpPath);
|
|
1563
1617
|
rpDef.skills.forEach((s) => sharedSkills.add(s));
|
|
@@ -1569,7 +1623,7 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
1569
1623
|
for (const skill of profile.skills) {
|
|
1570
1624
|
if (sharedSkills.has(skill)) continue;
|
|
1571
1625
|
if (CORE_SKILLS.includes(skill)) continue;
|
|
1572
|
-
const skillDir =
|
|
1626
|
+
const skillDir = path12.join(target, ".claude", "skills", skill);
|
|
1573
1627
|
if (fs9.existsSync(skillDir)) {
|
|
1574
1628
|
fs9.rmSync(skillDir, { recursive: true, force: true });
|
|
1575
1629
|
removedSkills.push(skill);
|
|
@@ -1581,7 +1635,7 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
1581
1635
|
const removedAgents = [];
|
|
1582
1636
|
for (const agent of profile.agents) {
|
|
1583
1637
|
if (sharedAgents.has(agent)) continue;
|
|
1584
|
-
const agentDir =
|
|
1638
|
+
const agentDir = path12.join(target, ".claude", "agents", agent);
|
|
1585
1639
|
if (fs9.existsSync(agentDir)) {
|
|
1586
1640
|
fs9.rmSync(agentDir, { recursive: true, force: true });
|
|
1587
1641
|
removedAgents.push(agent);
|
|
@@ -1595,7 +1649,7 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
1595
1649
|
...profile.rules.gamedev
|
|
1596
1650
|
]) {
|
|
1597
1651
|
if (sharedRules.has(rule)) continue;
|
|
1598
|
-
const ruleFile =
|
|
1652
|
+
const ruleFile = path12.join(target, ".claude", "rules", `${rule}.md`);
|
|
1599
1653
|
if (fs9.existsSync(ruleFile)) {
|
|
1600
1654
|
fs9.rmSync(ruleFile);
|
|
1601
1655
|
}
|
|
@@ -1604,18 +1658,18 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
1604
1658
|
);
|
|
1605
1659
|
}
|
|
1606
1660
|
if (profileName === "statusline") {
|
|
1607
|
-
const statuslineFile =
|
|
1661
|
+
const statuslineFile = path12.join(target, ".claude", "statusline.sh");
|
|
1608
1662
|
if (fs9.existsSync(statuslineFile)) {
|
|
1609
1663
|
fs9.rmSync(statuslineFile);
|
|
1610
1664
|
}
|
|
1611
|
-
removeStatuslineFromSettings(
|
|
1665
|
+
removeStatuslineFromSettings(path12.join(target, ".claude"));
|
|
1612
1666
|
}
|
|
1613
1667
|
manifest.profiles = remainingProfiles;
|
|
1614
1668
|
manifest.profile_command = remainingProfiles.filter((p) => p !== "core").join("+");
|
|
1615
1669
|
return { removed: true, skills: removedSkills, agents: removedAgents };
|
|
1616
1670
|
}
|
|
1617
1671
|
function removeStatuslineFromSettings(claudeDir) {
|
|
1618
|
-
const settingsPath =
|
|
1672
|
+
const settingsPath = path12.join(claudeDir, "settings.json");
|
|
1619
1673
|
if (!fs9.existsSync(settingsPath)) return;
|
|
1620
1674
|
const settings = JSON.parse(fs9.readFileSync(settingsPath, "utf-8"));
|
|
1621
1675
|
if (settings.statusLine) {
|
|
@@ -1626,13 +1680,13 @@ function removeStatuslineFromSettings(claudeDir) {
|
|
|
1626
1680
|
|
|
1627
1681
|
// src/commands/list.ts
|
|
1628
1682
|
import fs10 from "fs";
|
|
1629
|
-
import
|
|
1683
|
+
import path13 from "path";
|
|
1630
1684
|
import chalk4 from "chalk";
|
|
1631
1685
|
async function listCommand() {
|
|
1632
1686
|
const source = await resolveContentSource({ quiet: true });
|
|
1633
1687
|
const root = await source.getContentRoot();
|
|
1634
|
-
const profilesDir =
|
|
1635
|
-
const skillsDir =
|
|
1688
|
+
const profilesDir = path13.join(root, "profiles");
|
|
1689
|
+
const skillsDir = path13.join(root, "skills");
|
|
1636
1690
|
const target = process.cwd();
|
|
1637
1691
|
const manifest = readManifest(target);
|
|
1638
1692
|
const profiles = listProfiles(profilesDir);
|
|
@@ -1656,7 +1710,7 @@ async function listCommand() {
|
|
|
1656
1710
|
}
|
|
1657
1711
|
if (fs10.existsSync(skillsDir)) {
|
|
1658
1712
|
const allSkills = fs10.readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1659
|
-
(d) => d.isDirectory() && !d.name.startsWith("_") && fs10.existsSync(
|
|
1713
|
+
(d) => d.isDirectory() && !d.name.startsWith("_") && fs10.existsSync(path13.join(skillsDir, d.name, "SKILL.md"))
|
|
1660
1714
|
).map((d) => d.name).sort();
|
|
1661
1715
|
console.log(chalk4.bold(`
|
|
1662
1716
|
=== Skills (${allSkills.length}) ===
|
|
@@ -1724,16 +1778,130 @@ async function statusCommand() {
|
|
|
1724
1778
|
|
|
1725
1779
|
// src/commands/update.ts
|
|
1726
1780
|
import fs11 from "fs";
|
|
1727
|
-
import
|
|
1781
|
+
import path14 from "path";
|
|
1782
|
+
import os4 from "os";
|
|
1728
1783
|
import chalk6 from "chalk";
|
|
1784
|
+
|
|
1785
|
+
// src/self-update.ts
|
|
1786
|
+
import { spawnSync } from "child_process";
|
|
1787
|
+
var PACKAGE_NAME = "claude-code-arcane";
|
|
1788
|
+
function isGloballyInstalled() {
|
|
1789
|
+
const root = getPackageRoot().replace(/\\/g, "/");
|
|
1790
|
+
if (root.includes("/_npx/")) return false;
|
|
1791
|
+
if (!root.includes("/node_modules/")) return false;
|
|
1792
|
+
return true;
|
|
1793
|
+
}
|
|
1794
|
+
async function selfUpdateNpm(opts = {}) {
|
|
1795
|
+
const fromVersion = safeVersion();
|
|
1796
|
+
if (opts.selfUpdate === false) {
|
|
1797
|
+
return { updated: false, skipped: true, reason: "disabled (--no-self-update)", fromVersion };
|
|
1798
|
+
}
|
|
1799
|
+
if (process.env.VITEST || process.env.ARCANE_SOURCE) {
|
|
1800
|
+
return { updated: false, skipped: true, reason: "dev/test environment", fromVersion };
|
|
1801
|
+
}
|
|
1802
|
+
if (!isGloballyInstalled()) {
|
|
1803
|
+
return { updated: false, skipped: true, reason: "not a global npm install", fromVersion };
|
|
1804
|
+
}
|
|
1805
|
+
if (opts.dryRun) {
|
|
1806
|
+
return { updated: false, skipped: true, reason: "dry-run", fromVersion };
|
|
1807
|
+
}
|
|
1808
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
1809
|
+
const result = spawnSync(
|
|
1810
|
+
npm,
|
|
1811
|
+
["install", "-g", `${PACKAGE_NAME}@latest`],
|
|
1812
|
+
{
|
|
1813
|
+
stdio: opts.quiet ? "ignore" : "inherit",
|
|
1814
|
+
encoding: "utf-8",
|
|
1815
|
+
timeout: 12e4
|
|
1816
|
+
}
|
|
1817
|
+
);
|
|
1818
|
+
if (result.status === 0) {
|
|
1819
|
+
return { updated: true, skipped: false, fromVersion };
|
|
1820
|
+
}
|
|
1821
|
+
const reason = result.error ? result.error.message : `npm exited with code ${result.status ?? "unknown"}`;
|
|
1822
|
+
return { updated: false, skipped: false, reason, fromVersion };
|
|
1823
|
+
}
|
|
1824
|
+
function safeVersion() {
|
|
1825
|
+
try {
|
|
1826
|
+
return getPackageVersion();
|
|
1827
|
+
} catch {
|
|
1828
|
+
return void 0;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// src/commands/update.ts
|
|
1729
1833
|
async function updateCommand(opts) {
|
|
1730
|
-
|
|
1834
|
+
if (opts.here) {
|
|
1835
|
+
await updateTarget(process.cwd(), opts);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
const self = await selfUpdateNpm({
|
|
1839
|
+
quiet: opts.quiet,
|
|
1840
|
+
dryRun: opts.dryRun,
|
|
1841
|
+
selfUpdate: opts.selfUpdate
|
|
1842
|
+
});
|
|
1843
|
+
if (!opts.quiet) {
|
|
1844
|
+
if (self.updated) {
|
|
1845
|
+
console.log(chalk6.green("npm package updated to latest."));
|
|
1846
|
+
} else if (self.skipped && self.reason !== "dev/test environment") {
|
|
1847
|
+
console.log(chalk6.dim(`npm self-update skipped (${self.reason}).`));
|
|
1848
|
+
} else if (!self.skipped) {
|
|
1849
|
+
console.log(chalk6.yellow(`npm self-update failed: ${self.reason}`));
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
const cwd = process.cwd();
|
|
1853
|
+
if (fs11.existsSync(manifestPath(cwd))) {
|
|
1854
|
+
registerInstallation(cwd);
|
|
1855
|
+
}
|
|
1856
|
+
const targets = pruneRegistry().map((e) => e.path);
|
|
1857
|
+
const globalTarget = os4.homedir();
|
|
1858
|
+
if (!process.env.VITEST && fs11.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
|
|
1859
|
+
targets.push(globalTarget);
|
|
1860
|
+
}
|
|
1861
|
+
if (targets.length === 0) {
|
|
1862
|
+
if (!opts.quiet) {
|
|
1863
|
+
console.log(chalk6.red("\nNo Arcane installations found. Run 'arcane install' first."));
|
|
1864
|
+
}
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
if (!opts.quiet) {
|
|
1868
|
+
console.log(
|
|
1869
|
+
chalk6.bold(`
|
|
1870
|
+
Updating ${targets.length} installation${targets.length === 1 ? "" : "s"}...`)
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
const results = [];
|
|
1874
|
+
for (const target of targets) {
|
|
1875
|
+
if (!opts.quiet) {
|
|
1876
|
+
console.log(chalk6.bold.cyan(`
|
|
1877
|
+
\u2022 ${target}`));
|
|
1878
|
+
}
|
|
1879
|
+
try {
|
|
1880
|
+
results.push(await updateTarget(target, opts));
|
|
1881
|
+
} catch (err) {
|
|
1882
|
+
if (!opts.quiet) {
|
|
1883
|
+
console.log(chalk6.red(` Update failed: ${err.message}`));
|
|
1884
|
+
}
|
|
1885
|
+
results.push({
|
|
1886
|
+
target,
|
|
1887
|
+
status: "no-changes",
|
|
1888
|
+
updated: 0,
|
|
1889
|
+
skipped: 0,
|
|
1890
|
+
removed: 0
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
if (!opts.quiet) {
|
|
1895
|
+
printGeneralSummary(results, opts.dryRun ?? false);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
async function updateTarget(target, opts) {
|
|
1731
1899
|
const manifest = readManifest(target);
|
|
1732
1900
|
if (!manifest) {
|
|
1733
1901
|
if (!opts.quiet) {
|
|
1734
1902
|
console.log(chalk6.red("No Arcane installation found. Run 'arcane install' first."));
|
|
1735
1903
|
}
|
|
1736
|
-
return;
|
|
1904
|
+
return { target, status: "no-manifest", updated: 0, skipped: 0, removed: 0 };
|
|
1737
1905
|
}
|
|
1738
1906
|
const contentSource = await resolveContentSource({
|
|
1739
1907
|
source: opts.source ?? "auto",
|
|
@@ -1746,7 +1914,15 @@ async function updateCommand(opts) {
|
|
|
1746
1914
|
if (!opts.quiet) {
|
|
1747
1915
|
console.log(chalk6.green(`Already up to date (v${currentVersion}).`));
|
|
1748
1916
|
}
|
|
1749
|
-
return
|
|
1917
|
+
return {
|
|
1918
|
+
target,
|
|
1919
|
+
status: "up-to-date",
|
|
1920
|
+
fromVersion: installedVersion,
|
|
1921
|
+
toVersion: currentVersion,
|
|
1922
|
+
updated: 0,
|
|
1923
|
+
skipped: 0,
|
|
1924
|
+
removed: 0
|
|
1925
|
+
};
|
|
1750
1926
|
}
|
|
1751
1927
|
if (!opts.quiet) {
|
|
1752
1928
|
console.log(
|
|
@@ -1755,11 +1931,11 @@ Update available: ${chalk6.red(installedVersion)} -> ${chalk6.green(currentVersi
|
|
|
1755
1931
|
);
|
|
1756
1932
|
}
|
|
1757
1933
|
const profileNames = manifest.profile_command.split("+").filter(Boolean);
|
|
1758
|
-
const profilesDir =
|
|
1934
|
+
const profilesDir = path14.join(root, "profiles");
|
|
1759
1935
|
const merged = mergeProfiles(profilesDir, profileNames);
|
|
1760
1936
|
const allRules = [...merged.rules.universal, ...merged.rules.gamedev];
|
|
1761
1937
|
const sourceHashes = computeSourceHashes(root, merged.skills, allRules, merged.agents);
|
|
1762
|
-
const claudeDir =
|
|
1938
|
+
const claudeDir = path14.join(target, ".claude");
|
|
1763
1939
|
const installedHashes = computeContentHashes(claudeDir);
|
|
1764
1940
|
const items = computeUpdatePlan(
|
|
1765
1941
|
manifest.content_hashes ?? null,
|
|
@@ -1776,10 +1952,26 @@ Update available: ${chalk6.red(installedVersion)} -> ${chalk6.green(currentVersi
|
|
|
1776
1952
|
if (!opts.quiet) {
|
|
1777
1953
|
console.log(chalk6.green("\nNo changes to apply."));
|
|
1778
1954
|
}
|
|
1779
|
-
return
|
|
1955
|
+
return {
|
|
1956
|
+
target,
|
|
1957
|
+
status: "no-changes",
|
|
1958
|
+
fromVersion: installedVersion,
|
|
1959
|
+
toVersion: currentVersion,
|
|
1960
|
+
updated: 0,
|
|
1961
|
+
skipped: skipped.length,
|
|
1962
|
+
removed: 0
|
|
1963
|
+
};
|
|
1780
1964
|
}
|
|
1781
1965
|
if (opts.dryRun) {
|
|
1782
|
-
return
|
|
1966
|
+
return {
|
|
1967
|
+
target,
|
|
1968
|
+
status: "dry-run",
|
|
1969
|
+
fromVersion: installedVersion,
|
|
1970
|
+
toVersion: currentVersion,
|
|
1971
|
+
updated: updates.length,
|
|
1972
|
+
skipped: skipped.length,
|
|
1973
|
+
removed: removed.length
|
|
1974
|
+
};
|
|
1783
1975
|
}
|
|
1784
1976
|
applyUpdates(items, root, claudeDir, merged, opts.force ?? false);
|
|
1785
1977
|
const newHashes = computeContentHashes(claudeDir);
|
|
@@ -1799,6 +1991,23 @@ Update available: ${chalk6.red(installedVersion)} -> ${chalk6.green(currentVersi
|
|
|
1799
1991
|
Updated to v${currentVersion}.`));
|
|
1800
1992
|
console.log(` ${updates.length} updated, ${skipped.length} skipped, ${removed.length} removed`);
|
|
1801
1993
|
}
|
|
1994
|
+
return {
|
|
1995
|
+
target,
|
|
1996
|
+
status: "updated",
|
|
1997
|
+
fromVersion: installedVersion,
|
|
1998
|
+
toVersion: currentVersion,
|
|
1999
|
+
updated: updates.length,
|
|
2000
|
+
skipped: skipped.length,
|
|
2001
|
+
removed: removed.length
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
function printGeneralSummary(results, isDryRun) {
|
|
2005
|
+
const prefix = isDryRun ? chalk6.yellow("[dry-run] ") : "";
|
|
2006
|
+
const updated = results.filter((r) => r.status === "updated" || r.status === "dry-run").length;
|
|
2007
|
+
const unchanged = results.filter((r) => r.status === "up-to-date" || r.status === "no-changes").length;
|
|
2008
|
+
console.log(chalk6.bold(`
|
|
2009
|
+
${prefix}Done. ${results.length} installation${results.length === 1 ? "" : "s"} processed:`));
|
|
2010
|
+
console.log(` ${chalk6.green(updated)} ${isDryRun ? "would change" : "updated"}, ${chalk6.dim(unchanged + " unchanged")}`);
|
|
1802
2011
|
}
|
|
1803
2012
|
function computeUpdatePlan(manifestHashes, installedHashes, sourceHashes) {
|
|
1804
2013
|
const items = [];
|
|
@@ -1913,7 +2122,7 @@ function applyItem(item, root, claudeDir) {
|
|
|
1913
2122
|
}
|
|
1914
2123
|
copyDirSync(src, dst);
|
|
1915
2124
|
} else {
|
|
1916
|
-
const dir =
|
|
2125
|
+
const dir = path14.dirname(dst);
|
|
1917
2126
|
if (!fs11.existsSync(dir)) {
|
|
1918
2127
|
fs11.mkdirSync(dir, { recursive: true });
|
|
1919
2128
|
}
|
|
@@ -1938,16 +2147,16 @@ function removeItem(item, claudeDir) {
|
|
|
1938
2147
|
function getSourcePath(item, root) {
|
|
1939
2148
|
switch (item.type) {
|
|
1940
2149
|
case "skill":
|
|
1941
|
-
return
|
|
2150
|
+
return path14.join(root, "skills", item.name);
|
|
1942
2151
|
case "rule": {
|
|
1943
|
-
const direct =
|
|
2152
|
+
const direct = path14.join(root, "rules", item.name);
|
|
1944
2153
|
if (fs11.existsSync(direct)) return direct;
|
|
1945
|
-
return
|
|
2154
|
+
return path14.join(root, "rules", "gamedev", item.name);
|
|
1946
2155
|
}
|
|
1947
2156
|
case "agent":
|
|
1948
|
-
return
|
|
2157
|
+
return path14.join(root, "agents", item.name);
|
|
1949
2158
|
case "hook":
|
|
1950
|
-
return
|
|
2159
|
+
return path14.join(root, "hooks", item.name);
|
|
1951
2160
|
default:
|
|
1952
2161
|
return null;
|
|
1953
2162
|
}
|
|
@@ -1955,13 +2164,13 @@ function getSourcePath(item, root) {
|
|
|
1955
2164
|
function getInstalledPath(item, claudeDir) {
|
|
1956
2165
|
switch (item.type) {
|
|
1957
2166
|
case "skill":
|
|
1958
|
-
return
|
|
2167
|
+
return path14.join(claudeDir, "skills", item.name);
|
|
1959
2168
|
case "rule":
|
|
1960
|
-
return
|
|
2169
|
+
return path14.join(claudeDir, "rules", item.name);
|
|
1961
2170
|
case "agent":
|
|
1962
|
-
return
|
|
2171
|
+
return path14.join(claudeDir, "agents", item.name);
|
|
1963
2172
|
case "hook":
|
|
1964
|
-
return
|
|
2173
|
+
return path14.join(claudeDir, "hooks", item.name);
|
|
1965
2174
|
}
|
|
1966
2175
|
}
|
|
1967
2176
|
|
|
@@ -1997,7 +2206,7 @@ async function cleanCommand(opts) {
|
|
|
1997
2206
|
|
|
1998
2207
|
// src/commands/worktree.ts
|
|
1999
2208
|
import fs12 from "fs";
|
|
2000
|
-
import
|
|
2209
|
+
import path15 from "path";
|
|
2001
2210
|
import { execSync as execSync2 } from "child_process";
|
|
2002
2211
|
import chalk8 from "chalk";
|
|
2003
2212
|
async function worktreeCommand(branch, opts) {
|
|
@@ -2008,7 +2217,7 @@ async function worktreeCommand(branch, opts) {
|
|
|
2008
2217
|
process.exit(1);
|
|
2009
2218
|
}
|
|
2010
2219
|
const repoRoot = wtInfo.isWorktree ? wtInfo.mainWorktreePath : cwd;
|
|
2011
|
-
const worktreePath = opts.path ?
|
|
2220
|
+
const worktreePath = opts.path ? path15.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
|
|
2012
2221
|
if (fs12.existsSync(worktreePath)) {
|
|
2013
2222
|
console.error(
|
|
2014
2223
|
chalk8.red(`Path already exists: ${worktreePath}`)
|
|
@@ -2056,7 +2265,7 @@ async function worktreeCommand(branch, opts) {
|
|
|
2056
2265
|
}
|
|
2057
2266
|
console.log(" Installing Arcane...");
|
|
2058
2267
|
const root = getPackageRoot();
|
|
2059
|
-
const profilesDir =
|
|
2268
|
+
const profilesDir = path15.join(root, "profiles");
|
|
2060
2269
|
const profileNames = profileExpr.split("+").filter(Boolean);
|
|
2061
2270
|
const merged = mergeProfiles(profilesDir, profileNames);
|
|
2062
2271
|
const shareFrom = opts.share !== false ? cwd : void 0;
|
|
@@ -2070,6 +2279,7 @@ async function worktreeCommand(branch, opts) {
|
|
|
2070
2279
|
is_worktree: true,
|
|
2071
2280
|
main_worktree: repoRoot
|
|
2072
2281
|
});
|
|
2282
|
+
registerInstallation(worktreePath);
|
|
2073
2283
|
console.log(chalk8.green(" [ok] Arcane installed"));
|
|
2074
2284
|
if (opts.installDeps) {
|
|
2075
2285
|
const pm = detectPackageManager(worktreePath);
|
|
@@ -2091,7 +2301,7 @@ async function worktreeCommand(branch, opts) {
|
|
|
2091
2301
|
}
|
|
2092
2302
|
}
|
|
2093
2303
|
if (opts.isolate) {
|
|
2094
|
-
const isolateScript =
|
|
2304
|
+
const isolateScript = path15.join(
|
|
2095
2305
|
worktreePath,
|
|
2096
2306
|
".claude",
|
|
2097
2307
|
"skills",
|
|
@@ -2133,12 +2343,12 @@ async function worktreeCommand(branch, opts) {
|
|
|
2133
2343
|
|
|
2134
2344
|
// src/commands/global.ts
|
|
2135
2345
|
import fs13 from "fs";
|
|
2136
|
-
import
|
|
2137
|
-
import
|
|
2346
|
+
import path16 from "path";
|
|
2347
|
+
import os5 from "os";
|
|
2138
2348
|
import chalk9 from "chalk";
|
|
2139
|
-
var CLAUDE_HOME =
|
|
2140
|
-
var SCRIPTS_DIR =
|
|
2141
|
-
var SETTINGS_PATH =
|
|
2349
|
+
var CLAUDE_HOME = path16.join(os5.homedir(), ".claude");
|
|
2350
|
+
var SCRIPTS_DIR = path16.join(CLAUDE_HOME, "scripts", "worktree-isolation");
|
|
2351
|
+
var SETTINGS_PATH = path16.join(CLAUDE_HOME, "settings.json");
|
|
2142
2352
|
async function globalCommand(opts) {
|
|
2143
2353
|
if (opts.status) {
|
|
2144
2354
|
showStatus();
|
|
@@ -2153,7 +2363,7 @@ async function globalCommand(opts) {
|
|
|
2153
2363
|
function showStatus() {
|
|
2154
2364
|
console.log(chalk9.bold("\n=== Arcane Global Status ===\n"));
|
|
2155
2365
|
const hasScripts = fs13.existsSync(
|
|
2156
|
-
|
|
2366
|
+
path16.join(SCRIPTS_DIR, "apply.py")
|
|
2157
2367
|
);
|
|
2158
2368
|
console.log(
|
|
2159
2369
|
` Worktree scripts: ${hasScripts ? chalk9.green("installed") : chalk9.dim("not installed")}`
|
|
@@ -2171,7 +2381,7 @@ function showStatus() {
|
|
|
2171
2381
|
function installGlobal() {
|
|
2172
2382
|
console.log(chalk9.bold("\nInstalling Arcane global hooks...\n"));
|
|
2173
2383
|
const root = getPackageRoot();
|
|
2174
|
-
const scriptsSrc =
|
|
2384
|
+
const scriptsSrc = path16.join(
|
|
2175
2385
|
root,
|
|
2176
2386
|
"skills",
|
|
2177
2387
|
"worktree-isolation",
|
|
@@ -2185,8 +2395,8 @@ function installGlobal() {
|
|
|
2185
2395
|
}
|
|
2186
2396
|
fs13.mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
2187
2397
|
for (const file of ["apply.py", "lib.py"]) {
|
|
2188
|
-
const src =
|
|
2189
|
-
const dst =
|
|
2398
|
+
const src = path16.join(scriptsSrc, file);
|
|
2399
|
+
const dst = path16.join(SCRIPTS_DIR, file);
|
|
2190
2400
|
if (!fs13.existsSync(src)) {
|
|
2191
2401
|
console.warn(chalk9.yellow(` WARN: ${file} not found, skipping`));
|
|
2192
2402
|
continue;
|
|
@@ -2243,7 +2453,7 @@ function readSettings() {
|
|
|
2243
2453
|
}
|
|
2244
2454
|
}
|
|
2245
2455
|
function writeSettings(settings) {
|
|
2246
|
-
fs13.mkdirSync(
|
|
2456
|
+
fs13.mkdirSync(path16.dirname(SETTINGS_PATH), { recursive: true });
|
|
2247
2457
|
fs13.writeFileSync(
|
|
2248
2458
|
SETTINGS_PATH,
|
|
2249
2459
|
JSON.stringify(settings, null, 2) + "\n",
|
|
@@ -2302,10 +2512,10 @@ function removeHookFromSettings() {
|
|
|
2302
2512
|
|
|
2303
2513
|
// src/update-check.ts
|
|
2304
2514
|
import fs14 from "fs";
|
|
2305
|
-
import
|
|
2306
|
-
import
|
|
2515
|
+
import path17 from "path";
|
|
2516
|
+
import os6 from "os";
|
|
2307
2517
|
import chalk10 from "chalk";
|
|
2308
|
-
var CHECK_FILE =
|
|
2518
|
+
var CHECK_FILE = path17.join(os6.homedir(), ".arcane", "last-check.json");
|
|
2309
2519
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
2310
2520
|
var GITHUB_OWNER2 = "SebastianLuser";
|
|
2311
2521
|
var GITHUB_REPO2 = "Claude-Code-Arcane";
|
|
@@ -2381,7 +2591,7 @@ function readCachedCheck() {
|
|
|
2381
2591
|
}
|
|
2382
2592
|
function writeCachedCheck(result) {
|
|
2383
2593
|
try {
|
|
2384
|
-
const dir =
|
|
2594
|
+
const dir = path17.dirname(CHECK_FILE);
|
|
2385
2595
|
if (!fs14.existsSync(dir)) {
|
|
2386
2596
|
fs14.mkdirSync(dir, { recursive: true });
|
|
2387
2597
|
}
|
|
@@ -2419,7 +2629,9 @@ program.command("list").description("List all available profiles and skills.").a
|
|
|
2419
2629
|
program.command("status").description("Show current Arcane installation status.").action(async () => {
|
|
2420
2630
|
await statusCommand();
|
|
2421
2631
|
});
|
|
2422
|
-
program.command("update").description(
|
|
2632
|
+
program.command("update").description(
|
|
2633
|
+
"Update everything: the global npm package + all registered Arcane installations."
|
|
2634
|
+
).option("-q, --quiet", "Quiet mode for hook usage").option("-n, --dry-run", "Show what would change without applying").option("-f, --force", "Overwrite even locally modified files").option("--here", "Update only the current repo (legacy per-repo behavior)").option("--no-self-update", "Skip updating the global npm package").option("--source <source>", "Content source: auto, github, or bundled", "auto").action(async (opts) => {
|
|
2423
2635
|
await updateCommand(opts);
|
|
2424
2636
|
});
|
|
2425
2637
|
program.command("clean").description("Remove Arcane installation from current project.").option("-f, --force", "Skip confirmation").action(async (opts) => {
|