claude-code-arcane 2.7.0 → 2.8.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 +26 -0
- package/README.md +1 -1
- package/agents/ai/ai-architect.md +1 -1
- package/agents/ai/data-engineer.md +1 -1
- package/agents/ai/data-scientist.md +1 -1
- package/agents/ai/ml-engineer.md +1 -1
- package/agents/audio/audio-director.md +32 -40
- package/agents/audio/audio-programmer.md +32 -45
- package/agents/audio/audio-qa.md +36 -47
- package/agents/audio/composer.md +32 -42
- package/agents/audio/technical-sound-designer.md +32 -43
- package/agents/audio/voice-director.md +33 -42
- package/agents/business/business-ops-lead.md +1 -0
- package/agents/business/financial-analyst.md +1 -0
- package/agents/business/sales-engineer.md +1 -0
- package/agents/career/career-strategist.md +1 -1
- package/agents/career/cv-reviewer.md +1 -1
- package/agents/career/hiring-manager.md +1 -1
- package/agents/career/mock-interviewer.md +1 -1
- package/agents/clevel/ceo-advisor.md +2 -2
- package/agents/clevel/cfo-advisor.md +2 -2
- package/agents/clevel/chief-of-staff.md +2 -2
- package/agents/clevel/cmo-advisor.md +2 -2
- package/agents/clevel/coo-advisor.md +2 -2
- package/agents/clevel/cto-advisor.md +2 -2
- package/agents/devops/cloud-architect.md +2 -3
- package/agents/devops/devops-engineer.md +48 -0
- package/agents/devops/platform-lead.md +2 -3
- package/agents/devops/sre-lead.md +2 -3
- package/agents/ecommerce/ecommerce-architect.md +1 -0
- package/agents/engineering/android-engineer.md +1 -0
- package/agents/engineering/backend-architect.md +3 -4
- package/agents/engineering/chief-technology-officer.md +53 -0
- package/agents/engineering/database-architect.md +2 -3
- package/agents/engineering/dotnet-engineer.md +2 -1
- package/agents/engineering/e2e-tester.md +2 -1
- package/agents/engineering/flutter-engineer.md +1 -0
- package/agents/engineering/frontend-architect.md +3 -4
- package/agents/engineering/go-engineer.md +2 -1
- package/agents/engineering/ios-engineer.md +1 -0
- package/agents/engineering/mobile-lead.md +59 -0
- package/agents/engineering/nestjs-engineer.md +2 -1
- package/agents/engineering/nextjs-engineer.md +2 -1
- package/agents/engineering/nextjs-reviewer.md +4 -3
- package/agents/engineering/node-engineer.md +2 -1
- package/agents/engineering/react-engineer.md +2 -1
- package/agents/engineering/react-native-engineer.md +2 -1
- package/agents/engineering/sql-specialist.md +3 -2
- package/agents/engineering/vp-engineering.md +54 -0
- package/agents/freelance/client-screener.md +1 -1
- package/agents/freelance/discovery-call.md +1 -1
- package/agents/freelance/pipeline-strategist.md +1 -1
- package/agents/freelance/proposal-reviewer.md +1 -1
- package/agents/game/accessibility-specialist.md +39 -51
- package/agents/game/ai-programmer.md +32 -44
- package/agents/game/analytics-engineer.md +32 -44
- package/agents/game/art-director.md +1 -0
- package/agents/game/creative-director.md +48 -0
- package/agents/game/economy-designer.md +35 -60
- package/agents/game/game-designer.md +1 -0
- package/agents/game/gameplay-programmer.md +32 -46
- package/agents/game/lead-programmer.md +2 -1
- package/agents/game/level-designer.md +35 -60
- package/agents/game/live-ops-designer.md +36 -56
- package/agents/game/narrative-director.md +1 -0
- package/agents/game/network-programmer.md +32 -44
- package/agents/game/performance-analyst.md +32 -44
- package/agents/game/producer.md +50 -0
- package/agents/game/qa-lead.md +3 -2
- package/agents/game/qa-tester-gamedev.md +33 -45
- package/agents/game/release-manager.md +2 -1
- package/agents/game/sound-designer.md +33 -45
- package/agents/game/systems-designer.md +35 -60
- package/agents/game/technical-artist.md +32 -44
- package/agents/game/technical-director.md +2 -1
- package/agents/game/ue-blueprint-specialist.md +36 -48
- package/agents/game/ue-gas-specialist.md +36 -48
- package/agents/game/ue-replication-specialist.md +36 -48
- package/agents/game/ue-umg-specialist.md +36 -48
- package/agents/game/unity-shader-specialist.md +36 -48
- package/agents/game/unity-specialist.md +36 -51
- package/agents/game/unreal-specialist.md +36 -48
- package/agents/game/ux-designer.md +35 -55
- package/agents/game/world-builder.md +35 -60
- package/agents/game/writer.md +32 -50
- package/agents/integrations/api-tools-specialist.md +2 -2
- package/agents/integrations/comms-tools-specialist.md +2 -2
- package/agents/integrations/design-tools-specialist.md +53 -7
- package/agents/integrations/docs-tools-specialist.md +18 -2
- package/agents/integrations/integrations-architect.md +2 -2
- package/agents/integrations/project-tools-specialist.md +18 -2
- package/agents/management/delivery-manager.md +2 -1
- package/agents/management/program-director.md +2 -1
- package/agents/management/project-manager.md +2 -2
- package/agents/management/scrum-master.md +2 -2
- package/agents/marketing/content-lead.md +1 -0
- package/agents/marketing/growth-lead.md +1 -0
- package/agents/marketing/marketing-analyst.md +1 -0
- package/agents/marketing/marketing-director.md +1 -0
- package/agents/marketing/seo-lead.md +1 -0
- package/agents/product/chief-product-officer.md +1 -0
- package/agents/product/design-system-lead.md +3 -3
- package/agents/product/product-manager.md +2 -2
- package/agents/product/ui-lead.md +2 -4
- package/agents/product/ux-lead.md +2 -5
- package/agents/quality/qa-director.md +2 -5
- package/agents/quality/qa-engineer.md +1 -0
- package/agents/quality/security-architect.md +2 -5
- package/agents/regulatory/compliance-officer.md +1 -0
- package/agents/regulatory/quality-manager.md +1 -0
- package/agents/regulatory/regulatory-director.md +1 -0
- package/agents/visualnovel/vn-comfyui-artist.md +35 -14
- package/agents/visualnovel/vn-narrative-director.md +36 -30
- package/agents/visualnovel/vn-producer.md +36 -12
- package/agents/visualnovel/vn-renpy-developer.md +34 -12
- package/agents/visualnovel/vn-scene-director.md +34 -9
- package/agents/visualnovel/vn-ui-designer.md +36 -20
- package/dist/cli.js +380 -326
- package/docs/USER-GUIDE.md +26 -1
- package/docs/agent-hierarchy.md +42 -2
- package/docs/collaborative-design.md +2 -1
- package/docs/directory-structure.md +18 -13
- package/package.json +1 -1
- package/profiles/backend-go.yaml +1 -0
- package/profiles/docs.yaml +0 -2
- package/profiles/security.yaml +1 -1
- package/profiles/unity-design.yaml +2 -0
- package/profiles/unity-dev.yaml +2 -0
- package/profiles/unreal-dev.yaml +1 -0
- package/profiles/visual-novel.yaml +1 -0
- package/skills/architecture-review/SKILL.md +1 -0
- package/skills/balance-check/SKILL.md +1 -0
- package/skills/code-review/SKILL.md +49 -16
- package/skills/create-epics/SKILL.md +2 -1
- package/skills/create-stories/SKILL.md +5 -4
- package/skills/database-indexing/SKILL.md +2 -1
- package/skills/help/SKILL.md +6 -2
- package/skills/hotfix/SKILL.md +11 -3
- package/skills/patch-notes/SKILL.md +2 -1
- package/skills/perf-profile/SKILL.md +0 -1
- package/skills/prototype/SKILL.md +0 -2
- package/skills/qa-plan/SKILL.md +2 -1
- package/skills/retrospective/SKILL.md +4 -2
- package/skills/security-audit/SKILL.md +2 -1
- package/skills/skill-auditor/SKILL.md +16 -4
- package/skills/skill-auditor/references/frontmatter-spec.md +93 -86
- package/skills/slo-sli/SKILL.md +2 -2
- package/skills/sprint-plan/SKILL.md +4 -2
- package/skills/story-readiness/SKILL.md +1 -1
- package/skills/ux-design/SKILL.md +2 -1
- package/skills/ux-review/SKILL.md +2 -1
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 path12 from "path";
|
|
8
8
|
import chalk2 from "chalk";
|
|
9
9
|
|
|
10
10
|
// src/profiles.ts
|
|
@@ -374,8 +374,8 @@ function hint(description, max = 90) {
|
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
// src/installer.ts
|
|
377
|
-
import
|
|
378
|
-
import
|
|
377
|
+
import fs7 from "fs";
|
|
378
|
+
import path8 from "path";
|
|
379
379
|
|
|
380
380
|
// src/utils.ts
|
|
381
381
|
import { fileURLToPath } from "url";
|
|
@@ -434,11 +434,66 @@ function safeRemove(p2) {
|
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
// src/
|
|
437
|
+
// src/agent-entries.ts
|
|
438
|
+
import fs4 from "fs";
|
|
438
439
|
import path4 from "path";
|
|
440
|
+
function parseAgentEntry(raw) {
|
|
441
|
+
const trimmed = raw.trim().replace(/\/+$/, "");
|
|
442
|
+
const slash = trimmed.indexOf("/");
|
|
443
|
+
if (slash < 0) return { raw: trimmed, division: trimmed };
|
|
444
|
+
return {
|
|
445
|
+
raw: trimmed,
|
|
446
|
+
division: trimmed.slice(0, slash),
|
|
447
|
+
agent: trimmed.slice(slash + 1)
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function isGranular(raw) {
|
|
451
|
+
return parseAgentEntry(raw).agent !== void 0;
|
|
452
|
+
}
|
|
453
|
+
function agentSourcePath(root, raw) {
|
|
454
|
+
const { division, agent } = parseAgentEntry(raw);
|
|
455
|
+
const p2 = agent ? path4.join(root, "agents", division, `${agent}.md`) : path4.join(root, "agents", division);
|
|
456
|
+
return fs4.existsSync(p2) ? p2 : null;
|
|
457
|
+
}
|
|
458
|
+
function copyAgentEntry(root, claudeDir, raw) {
|
|
459
|
+
const src = agentSourcePath(root, raw);
|
|
460
|
+
if (!src) return null;
|
|
461
|
+
const { division, agent } = parseAgentEntry(raw);
|
|
462
|
+
const dstDir = path4.join(claudeDir, "agents", division);
|
|
463
|
+
if (agent) {
|
|
464
|
+
ensureDir(dstDir);
|
|
465
|
+
fs4.copyFileSync(src, path4.join(dstDir, `${agent}.md`));
|
|
466
|
+
return 1;
|
|
467
|
+
}
|
|
468
|
+
copyDirSync(src, dstDir);
|
|
469
|
+
return fs4.readdirSync(dstDir, { recursive: true }).filter((f) => String(f).endsWith(".md")).length;
|
|
470
|
+
}
|
|
471
|
+
function removeAgentEntry(claudeDir, raw) {
|
|
472
|
+
const { division, agent } = parseAgentEntry(raw);
|
|
473
|
+
const dstDir = path4.join(claudeDir, "agents", division);
|
|
474
|
+
if (!agent) {
|
|
475
|
+
if (!fs4.existsSync(dstDir)) return false;
|
|
476
|
+
fs4.rmSync(dstDir, { recursive: true, force: true });
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
const file = path4.join(dstDir, `${agent}.md`);
|
|
480
|
+
if (!fs4.existsSync(file)) return false;
|
|
481
|
+
safeRemove(file);
|
|
482
|
+
if (fs4.existsSync(dstDir) && fs4.readdirSync(dstDir).length === 0) {
|
|
483
|
+
fs4.rmSync(dstDir, { recursive: true, force: true });
|
|
484
|
+
}
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
function agentEntryLabel(raw) {
|
|
488
|
+
const { division, agent } = parseAgentEntry(raw);
|
|
489
|
+
return agent ? `agents/${division}/${agent}.md` : `agents/${division}/`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/manifest.ts
|
|
493
|
+
import path5 from "path";
|
|
439
494
|
var MANIFEST_FILE = "arcane-manifest.json";
|
|
440
495
|
function manifestPath(target) {
|
|
441
|
-
return
|
|
496
|
+
return path5.join(target, ".claude", MANIFEST_FILE);
|
|
442
497
|
}
|
|
443
498
|
function readManifest(target) {
|
|
444
499
|
const p2 = manifestPath(target);
|
|
@@ -492,8 +547,8 @@ function updateManifestFields(target, updates) {
|
|
|
492
547
|
|
|
493
548
|
// src/worktree.ts
|
|
494
549
|
import { execSync } from "child_process";
|
|
495
|
-
import
|
|
496
|
-
import
|
|
550
|
+
import fs5 from "fs";
|
|
551
|
+
import path6 from "path";
|
|
497
552
|
function getWorktreeInfo(target) {
|
|
498
553
|
try {
|
|
499
554
|
execSync("git rev-parse --is-inside-work-tree", {
|
|
@@ -514,10 +569,10 @@ function getWorktreeInfo(target) {
|
|
|
514
569
|
encoding: "utf-8",
|
|
515
570
|
stdio: "pipe"
|
|
516
571
|
}).trim();
|
|
517
|
-
const resolvedCommon =
|
|
518
|
-
const resolvedGit =
|
|
572
|
+
const resolvedCommon = path6.resolve(target, gitCommonDir);
|
|
573
|
+
const resolvedGit = path6.resolve(target, gitDir);
|
|
519
574
|
const isWorktree = resolvedCommon !== resolvedGit;
|
|
520
|
-
const mainWorktreePath = isWorktree ?
|
|
575
|
+
const mainWorktreePath = isWorktree ? path6.resolve(resolvedCommon, "..") : target;
|
|
521
576
|
let currentBranch = "HEAD";
|
|
522
577
|
try {
|
|
523
578
|
currentBranch = execSync("git branch --show-current", {
|
|
@@ -532,7 +587,7 @@ function getWorktreeInfo(target) {
|
|
|
532
587
|
isWorktree,
|
|
533
588
|
mainWorktreePath,
|
|
534
589
|
currentBranch,
|
|
535
|
-
worktreeId:
|
|
590
|
+
worktreeId: path6.basename(target),
|
|
536
591
|
allWorktrees
|
|
537
592
|
};
|
|
538
593
|
}
|
|
@@ -568,17 +623,17 @@ function parseWorktreeList(cwd) {
|
|
|
568
623
|
}
|
|
569
624
|
}
|
|
570
625
|
function findMainArcaneInstall(mainPath) {
|
|
571
|
-
return
|
|
572
|
-
|
|
626
|
+
return fs5.existsSync(
|
|
627
|
+
path6.join(mainPath, ".claude", "arcane-manifest.json")
|
|
573
628
|
);
|
|
574
629
|
}
|
|
575
630
|
function linkOrCopyDir(src, dest, mode) {
|
|
576
631
|
if (mode === "symlink") {
|
|
577
632
|
try {
|
|
578
|
-
const parentDir =
|
|
579
|
-
|
|
633
|
+
const parentDir = path6.dirname(dest);
|
|
634
|
+
fs5.mkdirSync(parentDir, { recursive: true });
|
|
580
635
|
const type = process.platform === "win32" ? "junction" : "dir";
|
|
581
|
-
|
|
636
|
+
fs5.symlinkSync(src, dest, type);
|
|
582
637
|
return "linked";
|
|
583
638
|
} catch {
|
|
584
639
|
}
|
|
@@ -590,9 +645,9 @@ function sanitizeBranchForPath(branch) {
|
|
|
590
645
|
return branch.replace(/^(feat|fix|feature|hotfix)\//, "").replace(/\//g, "-").replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
591
646
|
}
|
|
592
647
|
function defaultWorktreePath(repoRoot, branch) {
|
|
593
|
-
const repoName =
|
|
648
|
+
const repoName = path6.basename(repoRoot);
|
|
594
649
|
const sanitized = sanitizeBranchForPath(branch);
|
|
595
|
-
return
|
|
650
|
+
return path6.resolve(repoRoot, "..", `${repoName}-${sanitized}`);
|
|
596
651
|
}
|
|
597
652
|
function createGitWorktree(repoRoot, worktreePath, branch, base) {
|
|
598
653
|
const branchExists = checkBranchExists(repoRoot, branch);
|
|
@@ -634,20 +689,20 @@ function detectPackageManager(dir) {
|
|
|
634
689
|
{ lockfile: "package-lock.json", command: "npm install" }
|
|
635
690
|
];
|
|
636
691
|
for (const m of managers) {
|
|
637
|
-
if (
|
|
692
|
+
if (fs5.existsSync(path6.join(dir, m.lockfile))) return m;
|
|
638
693
|
}
|
|
639
|
-
if (
|
|
694
|
+
if (fs5.existsSync(path6.join(dir, "package.json"))) {
|
|
640
695
|
return { lockfile: "package.json", command: "npm install" };
|
|
641
696
|
}
|
|
642
697
|
return null;
|
|
643
698
|
}
|
|
644
699
|
|
|
645
700
|
// src/content-hash.ts
|
|
646
|
-
import
|
|
647
|
-
import
|
|
701
|
+
import fs6 from "fs";
|
|
702
|
+
import path7 from "path";
|
|
648
703
|
import { createHash } from "crypto";
|
|
649
704
|
function hashFile(filePath) {
|
|
650
|
-
const content =
|
|
705
|
+
const content = fs6.readFileSync(filePath);
|
|
651
706
|
return createHash("sha256").update(content).digest("hex");
|
|
652
707
|
}
|
|
653
708
|
function hashDirectory(dirPath) {
|
|
@@ -656,10 +711,10 @@ function hashDirectory(dirPath) {
|
|
|
656
711
|
return hashes;
|
|
657
712
|
}
|
|
658
713
|
function walkDir(base, current, hashes) {
|
|
659
|
-
for (const entry of
|
|
714
|
+
for (const entry of fs6.readdirSync(current, { withFileTypes: true })) {
|
|
660
715
|
if (isBuildArtifact(entry.name)) continue;
|
|
661
|
-
const fullPath =
|
|
662
|
-
const relPath =
|
|
716
|
+
const fullPath = path7.join(current, entry.name);
|
|
717
|
+
const relPath = path7.relative(base, fullPath).split(path7.sep).join("/");
|
|
663
718
|
if (entry.isDirectory()) {
|
|
664
719
|
walkDir(base, fullPath, hashes);
|
|
665
720
|
} else {
|
|
@@ -674,39 +729,39 @@ function computeContentHashes(claudeDir) {
|
|
|
674
729
|
agents: {},
|
|
675
730
|
hooks: {}
|
|
676
731
|
};
|
|
677
|
-
const skillsDir =
|
|
678
|
-
if (
|
|
679
|
-
for (const entry of
|
|
732
|
+
const skillsDir = path7.join(claudeDir, "skills");
|
|
733
|
+
if (fs6.existsSync(skillsDir)) {
|
|
734
|
+
for (const entry of fs6.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
680
735
|
if (entry.isDirectory()) {
|
|
681
|
-
const dirHash = hashDirectory(
|
|
736
|
+
const dirHash = hashDirectory(path7.join(skillsDir, entry.name));
|
|
682
737
|
const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
|
|
683
738
|
hashes.skills[entry.name] = combined;
|
|
684
739
|
}
|
|
685
740
|
}
|
|
686
741
|
}
|
|
687
|
-
const rulesDir =
|
|
688
|
-
if (
|
|
689
|
-
for (const entry of
|
|
742
|
+
const rulesDir = path7.join(claudeDir, "rules");
|
|
743
|
+
if (fs6.existsSync(rulesDir)) {
|
|
744
|
+
for (const entry of fs6.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
690
745
|
if (!entry.isDirectory()) {
|
|
691
|
-
hashes.rules[entry.name] = hashFile(
|
|
746
|
+
hashes.rules[entry.name] = hashFile(path7.join(rulesDir, entry.name));
|
|
692
747
|
}
|
|
693
748
|
}
|
|
694
749
|
}
|
|
695
|
-
const agentsDir =
|
|
696
|
-
if (
|
|
697
|
-
for (const entry of
|
|
750
|
+
const agentsDir = path7.join(claudeDir, "agents");
|
|
751
|
+
if (fs6.existsSync(agentsDir)) {
|
|
752
|
+
for (const entry of fs6.readdirSync(agentsDir, { withFileTypes: true })) {
|
|
698
753
|
if (entry.isDirectory()) {
|
|
699
|
-
const dirHash = hashDirectory(
|
|
754
|
+
const dirHash = hashDirectory(path7.join(agentsDir, entry.name));
|
|
700
755
|
const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
|
|
701
756
|
hashes.agents[entry.name] = combined;
|
|
702
757
|
}
|
|
703
758
|
}
|
|
704
759
|
}
|
|
705
|
-
const hooksDir =
|
|
706
|
-
if (
|
|
707
|
-
for (const entry of
|
|
760
|
+
const hooksDir = path7.join(claudeDir, "hooks");
|
|
761
|
+
if (fs6.existsSync(hooksDir)) {
|
|
762
|
+
for (const entry of fs6.readdirSync(hooksDir, { withFileTypes: true })) {
|
|
708
763
|
if (!entry.isDirectory()) {
|
|
709
|
-
hashes.hooks[entry.name] = hashFile(
|
|
764
|
+
hashes.hooks[entry.name] = hashFile(path7.join(hooksDir, entry.name));
|
|
710
765
|
}
|
|
711
766
|
}
|
|
712
767
|
}
|
|
@@ -720,8 +775,8 @@ function computeSourceHashes(packageRoot, skills, rules, agents) {
|
|
|
720
775
|
hooks: {}
|
|
721
776
|
};
|
|
722
777
|
for (const skill of skills) {
|
|
723
|
-
const skillDir =
|
|
724
|
-
if (
|
|
778
|
+
const skillDir = path7.join(packageRoot, "skills", skill);
|
|
779
|
+
if (fs6.existsSync(skillDir)) {
|
|
725
780
|
const dirHash = hashDirectory(skillDir);
|
|
726
781
|
const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
|
|
727
782
|
hashes.skills[skill] = combined;
|
|
@@ -729,29 +784,29 @@ function computeSourceHashes(packageRoot, skills, rules, agents) {
|
|
|
729
784
|
}
|
|
730
785
|
for (const rule of rules) {
|
|
731
786
|
const ruleName = rule.endsWith(".md") ? rule : `${rule}.md`;
|
|
732
|
-
const rulePath =
|
|
733
|
-
if (
|
|
787
|
+
const rulePath = path7.join(packageRoot, "rules", ruleName);
|
|
788
|
+
if (fs6.existsSync(rulePath)) {
|
|
734
789
|
hashes.rules[ruleName] = hashFile(rulePath);
|
|
735
790
|
} else {
|
|
736
|
-
const gamedevPath =
|
|
737
|
-
if (
|
|
791
|
+
const gamedevPath = path7.join(packageRoot, "rules", "gamedev", ruleName);
|
|
792
|
+
if (fs6.existsSync(gamedevPath)) {
|
|
738
793
|
hashes.rules[ruleName] = hashFile(gamedevPath);
|
|
739
794
|
}
|
|
740
795
|
}
|
|
741
796
|
}
|
|
742
797
|
for (const agent of agents) {
|
|
743
|
-
const agentDir =
|
|
744
|
-
if (
|
|
798
|
+
const agentDir = path7.join(packageRoot, "agents", agent);
|
|
799
|
+
if (fs6.existsSync(agentDir)) {
|
|
745
800
|
const dirHash = hashDirectory(agentDir);
|
|
746
801
|
const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
|
|
747
802
|
hashes.agents[agent] = combined;
|
|
748
803
|
}
|
|
749
804
|
}
|
|
750
|
-
const hooksDir =
|
|
751
|
-
if (
|
|
752
|
-
for (const entry of
|
|
805
|
+
const hooksDir = path7.join(packageRoot, "hooks");
|
|
806
|
+
if (fs6.existsSync(hooksDir)) {
|
|
807
|
+
for (const entry of fs6.readdirSync(hooksDir, { withFileTypes: true })) {
|
|
753
808
|
if (!entry.isDirectory()) {
|
|
754
|
-
hashes.hooks[entry.name] = hashFile(
|
|
809
|
+
hashes.hooks[entry.name] = hashFile(path7.join(hooksDir, entry.name));
|
|
755
810
|
}
|
|
756
811
|
}
|
|
757
812
|
}
|
|
@@ -920,7 +975,7 @@ var Installer = class {
|
|
|
920
975
|
constructor(merged, opts) {
|
|
921
976
|
this.root = opts.contentRoot ?? getPackageRoot();
|
|
922
977
|
this.target = opts.target;
|
|
923
|
-
this.claudeDir =
|
|
978
|
+
this.claudeDir = path8.join(opts.target, ".claude");
|
|
924
979
|
this.merged = merged;
|
|
925
980
|
this.dryRun = opts.dryRun;
|
|
926
981
|
this.shareFrom = opts.shareFrom;
|
|
@@ -952,22 +1007,22 @@ var Installer = class {
|
|
|
952
1007
|
return this.logs;
|
|
953
1008
|
}
|
|
954
1009
|
backup() {
|
|
955
|
-
if (!
|
|
956
|
-
const backup =
|
|
1010
|
+
if (!fs7.existsSync(this.claudeDir)) return;
|
|
1011
|
+
const backup = path8.join(this.target, ".claude.bak");
|
|
957
1012
|
if (this.dryRun) {
|
|
958
1013
|
this.log(" [dry-run] Would backup .claude/ -> .claude.bak/");
|
|
959
1014
|
return;
|
|
960
1015
|
}
|
|
961
|
-
if (
|
|
962
|
-
|
|
1016
|
+
if (fs7.existsSync(backup)) {
|
|
1017
|
+
fs7.rmSync(backup, { recursive: true, force: true });
|
|
963
1018
|
}
|
|
964
|
-
|
|
1019
|
+
fs7.renameSync(this.claudeDir, backup);
|
|
965
1020
|
this.log(" Backed up .claude/ -> .claude.bak/");
|
|
966
1021
|
}
|
|
967
1022
|
createDirs() {
|
|
968
1023
|
if (this.dryRun) return;
|
|
969
1024
|
for (const d of ["skills", "rules", "agents", "docs"]) {
|
|
970
|
-
ensureDir(
|
|
1025
|
+
ensureDir(path8.join(this.claudeDir, d));
|
|
971
1026
|
}
|
|
972
1027
|
}
|
|
973
1028
|
get hasStatusline() {
|
|
@@ -975,7 +1030,7 @@ var Installer = class {
|
|
|
975
1030
|
}
|
|
976
1031
|
copyHooks() {
|
|
977
1032
|
this.log("\nHooks:");
|
|
978
|
-
const hooksDir =
|
|
1033
|
+
const hooksDir = path8.join(this.claudeDir, "hooks");
|
|
979
1034
|
if (this.dryRun) {
|
|
980
1035
|
if (this.shareFrom) {
|
|
981
1036
|
this.log(" [dry-run] hooks/ -> symlink from main worktree");
|
|
@@ -986,8 +1041,8 @@ var Installer = class {
|
|
|
986
1041
|
return;
|
|
987
1042
|
}
|
|
988
1043
|
if (this.shareFrom) {
|
|
989
|
-
const sharedHooks =
|
|
990
|
-
if (
|
|
1044
|
+
const sharedHooks = path8.join(this.shareFrom, ".claude", "hooks");
|
|
1045
|
+
if (fs7.existsSync(sharedHooks)) {
|
|
991
1046
|
const result = linkOrCopyDir(sharedHooks, hooksDir, "symlink");
|
|
992
1047
|
if (result === "linked") {
|
|
993
1048
|
this.sharedDirs.push("hooks");
|
|
@@ -999,17 +1054,17 @@ var Installer = class {
|
|
|
999
1054
|
return;
|
|
1000
1055
|
}
|
|
1001
1056
|
}
|
|
1002
|
-
const hooksSrc =
|
|
1003
|
-
if (
|
|
1057
|
+
const hooksSrc = path8.join(this.root, "hooks");
|
|
1058
|
+
if (fs7.existsSync(hooksSrc)) {
|
|
1004
1059
|
ensureDir(hooksDir);
|
|
1005
|
-
for (const entry of
|
|
1060
|
+
for (const entry of fs7.readdirSync(hooksSrc, { withFileTypes: true })) {
|
|
1006
1061
|
if (entry.name === "statusline.sh") continue;
|
|
1007
|
-
const src =
|
|
1008
|
-
const dst =
|
|
1062
|
+
const src = path8.join(hooksSrc, entry.name);
|
|
1063
|
+
const dst = path8.join(hooksDir, entry.name);
|
|
1009
1064
|
if (entry.isDirectory()) {
|
|
1010
1065
|
copyDirSync(src, dst);
|
|
1011
1066
|
} else {
|
|
1012
|
-
|
|
1067
|
+
fs7.copyFileSync(src, dst);
|
|
1013
1068
|
}
|
|
1014
1069
|
}
|
|
1015
1070
|
this.log(" [ok] hooks/");
|
|
@@ -1018,11 +1073,11 @@ var Installer = class {
|
|
|
1018
1073
|
}
|
|
1019
1074
|
copyStatusline() {
|
|
1020
1075
|
if (!this.hasStatusline) return;
|
|
1021
|
-
const statusline =
|
|
1022
|
-
if (
|
|
1023
|
-
|
|
1076
|
+
const statusline = path8.join(this.root, "hooks", "statusline.sh");
|
|
1077
|
+
if (fs7.existsSync(statusline)) {
|
|
1078
|
+
fs7.copyFileSync(
|
|
1024
1079
|
statusline,
|
|
1025
|
-
|
|
1080
|
+
path8.join(this.claudeDir, "statusline.sh")
|
|
1026
1081
|
);
|
|
1027
1082
|
this.log(" [ok] statusline.sh (statusline profile)");
|
|
1028
1083
|
}
|
|
@@ -1030,12 +1085,12 @@ var Installer = class {
|
|
|
1030
1085
|
copySkills() {
|
|
1031
1086
|
this.log("\nSkills:");
|
|
1032
1087
|
for (const skill of this.merged.skills) {
|
|
1033
|
-
const src =
|
|
1034
|
-
if (!
|
|
1088
|
+
const src = path8.join(this.root, "skills", skill);
|
|
1089
|
+
if (!fs7.existsSync(src)) {
|
|
1035
1090
|
this.log(` WARN: Skill '${skill}' not found in skills/`);
|
|
1036
1091
|
continue;
|
|
1037
1092
|
}
|
|
1038
|
-
const dst =
|
|
1093
|
+
const dst = path8.join(this.claudeDir, "skills", skill);
|
|
1039
1094
|
if (this.dryRun) {
|
|
1040
1095
|
this.log(` [dry-run] ${skill}`);
|
|
1041
1096
|
} else {
|
|
@@ -1045,9 +1100,9 @@ var Installer = class {
|
|
|
1045
1100
|
}
|
|
1046
1101
|
const hasGamedev = this.merged.rules.gamedev.length > 0;
|
|
1047
1102
|
if (hasGamedev) {
|
|
1048
|
-
const tplSrc =
|
|
1049
|
-
if (
|
|
1050
|
-
const tplDst =
|
|
1103
|
+
const tplSrc = path8.join(this.root, "templates", "gamedev");
|
|
1104
|
+
if (fs7.existsSync(tplSrc)) {
|
|
1105
|
+
const tplDst = path8.join(this.claudeDir, "skills", "_templates");
|
|
1051
1106
|
if (this.dryRun) {
|
|
1052
1107
|
this.log(" [dry-run] _templates/");
|
|
1053
1108
|
} else {
|
|
@@ -1060,33 +1115,33 @@ var Installer = class {
|
|
|
1060
1115
|
copyRules() {
|
|
1061
1116
|
this.log("\nRules:");
|
|
1062
1117
|
for (const rule of this.merged.rules.universal) {
|
|
1063
|
-
const src =
|
|
1064
|
-
if (!
|
|
1118
|
+
const src = path8.join(this.root, "rules", `${rule}.md`);
|
|
1119
|
+
if (!fs7.existsSync(src)) {
|
|
1065
1120
|
this.log(` WARN: Rule '${rule}.md' not found`);
|
|
1066
1121
|
continue;
|
|
1067
1122
|
}
|
|
1068
1123
|
if (this.dryRun) {
|
|
1069
1124
|
this.log(` [dry-run] ${rule}.md`);
|
|
1070
1125
|
} else {
|
|
1071
|
-
|
|
1126
|
+
fs7.copyFileSync(
|
|
1072
1127
|
src,
|
|
1073
|
-
|
|
1128
|
+
path8.join(this.claudeDir, "rules", `${rule}.md`)
|
|
1074
1129
|
);
|
|
1075
1130
|
this.log(` [ok] ${rule}.md`);
|
|
1076
1131
|
}
|
|
1077
1132
|
}
|
|
1078
1133
|
for (const rule of this.merged.rules.gamedev) {
|
|
1079
|
-
const src =
|
|
1080
|
-
if (!
|
|
1134
|
+
const src = path8.join(this.root, "rules", "gamedev", `${rule}.md`);
|
|
1135
|
+
if (!fs7.existsSync(src)) {
|
|
1081
1136
|
this.log(` WARN: Rule '${rule}.md' not found`);
|
|
1082
1137
|
continue;
|
|
1083
1138
|
}
|
|
1084
1139
|
if (this.dryRun) {
|
|
1085
1140
|
this.log(` [dry-run] ${rule}.md`);
|
|
1086
1141
|
} else {
|
|
1087
|
-
|
|
1142
|
+
fs7.copyFileSync(
|
|
1088
1143
|
src,
|
|
1089
|
-
|
|
1144
|
+
path8.join(this.claudeDir, "rules", `${rule}.md`)
|
|
1090
1145
|
);
|
|
1091
1146
|
this.log(` [ok] ${rule}.md`);
|
|
1092
1147
|
}
|
|
@@ -1095,26 +1150,24 @@ var Installer = class {
|
|
|
1095
1150
|
copyAgents() {
|
|
1096
1151
|
if (this.merged.agents.length === 0) return;
|
|
1097
1152
|
this.log("\nAgents:");
|
|
1098
|
-
for (const
|
|
1099
|
-
const
|
|
1100
|
-
if (!
|
|
1101
|
-
this.log(` WARN: Agent
|
|
1153
|
+
for (const entry of this.merged.agents) {
|
|
1154
|
+
const label = agentEntryLabel(entry);
|
|
1155
|
+
if (!agentSourcePath(this.root, entry)) {
|
|
1156
|
+
this.log(` WARN: Agent entry '${entry}' not found`);
|
|
1102
1157
|
continue;
|
|
1103
1158
|
}
|
|
1104
|
-
const dst = path7.join(this.claudeDir, "agents", agentDir);
|
|
1105
1159
|
if (this.dryRun) {
|
|
1106
|
-
this.log(` [dry-run]
|
|
1107
|
-
|
|
1108
|
-
copyDirSync(src, dst);
|
|
1109
|
-
const count = fs6.readdirSync(dst, { recursive: true }).filter((f) => String(f).endsWith(".md")).length;
|
|
1110
|
-
this.log(` [ok] agents/${agentDir}/ (${count} agents)`);
|
|
1160
|
+
this.log(` [dry-run] ${label}`);
|
|
1161
|
+
continue;
|
|
1111
1162
|
}
|
|
1163
|
+
const count = copyAgentEntry(this.root, this.claudeDir, entry);
|
|
1164
|
+
this.log(` [ok] ${label}${count === 1 ? "" : ` (${count} agents)`}`);
|
|
1112
1165
|
}
|
|
1113
1166
|
}
|
|
1114
1167
|
copyDocs() {
|
|
1115
|
-
const docsDir =
|
|
1116
|
-
const docsSrc =
|
|
1117
|
-
if (!
|
|
1168
|
+
const docsDir = path8.join(this.claudeDir, "docs");
|
|
1169
|
+
const docsSrc = path8.join(this.root, "docs");
|
|
1170
|
+
if (!fs7.existsSync(docsSrc)) return;
|
|
1118
1171
|
this.log("\nDocs:");
|
|
1119
1172
|
if (this.dryRun) {
|
|
1120
1173
|
if (this.shareFrom) {
|
|
@@ -1125,8 +1178,8 @@ var Installer = class {
|
|
|
1125
1178
|
return;
|
|
1126
1179
|
}
|
|
1127
1180
|
if (this.shareFrom) {
|
|
1128
|
-
const sharedDocs =
|
|
1129
|
-
if (
|
|
1181
|
+
const sharedDocs = path8.join(this.shareFrom, ".claude", "docs");
|
|
1182
|
+
if (fs7.existsSync(sharedDocs)) {
|
|
1130
1183
|
const result = linkOrCopyDir(sharedDocs, docsDir, "symlink");
|
|
1131
1184
|
if (result === "linked") {
|
|
1132
1185
|
this.sharedDirs.push("docs");
|
|
@@ -1157,8 +1210,8 @@ var Installer = class {
|
|
|
1157
1210
|
);
|
|
1158
1211
|
return;
|
|
1159
1212
|
}
|
|
1160
|
-
const settingsPath =
|
|
1161
|
-
|
|
1213
|
+
const settingsPath = path8.join(this.claudeDir, "settings.json");
|
|
1214
|
+
fs7.writeFileSync(
|
|
1162
1215
|
settingsPath,
|
|
1163
1216
|
JSON.stringify(settings, null, 2) + "\n",
|
|
1164
1217
|
"utf-8"
|
|
@@ -1167,15 +1220,15 @@ var Installer = class {
|
|
|
1167
1220
|
}
|
|
1168
1221
|
};
|
|
1169
1222
|
function clean(target) {
|
|
1170
|
-
const claudeDir =
|
|
1171
|
-
const backup =
|
|
1172
|
-
if (!
|
|
1223
|
+
const claudeDir = path8.join(target, ".claude");
|
|
1224
|
+
const backup = path8.join(target, ".claude.bak");
|
|
1225
|
+
if (!fs7.existsSync(claudeDir)) {
|
|
1173
1226
|
console.log(`No .claude/ found in ${target} \u2014 nothing to clean.`);
|
|
1174
1227
|
return;
|
|
1175
1228
|
}
|
|
1176
|
-
const manifestFile =
|
|
1177
|
-
if (
|
|
1178
|
-
const data = JSON.parse(
|
|
1229
|
+
const manifestFile = path8.join(claudeDir, "arcane-manifest.json");
|
|
1230
|
+
if (fs7.existsSync(manifestFile)) {
|
|
1231
|
+
const data = JSON.parse(fs7.readFileSync(manifestFile, "utf-8"));
|
|
1179
1232
|
const profiles = (data.profiles ?? []).filter(
|
|
1180
1233
|
(p2) => p2 !== "core"
|
|
1181
1234
|
);
|
|
@@ -1183,49 +1236,49 @@ function clean(target) {
|
|
|
1183
1236
|
console.log(` Installed: ${data.installed_at ?? "unknown"}`);
|
|
1184
1237
|
const shared = data.worktree?.shared_dirs ?? [];
|
|
1185
1238
|
for (const dir of shared) {
|
|
1186
|
-
const p2 =
|
|
1239
|
+
const p2 = path8.join(claudeDir, dir);
|
|
1187
1240
|
if (isSymlinkOrJunction(p2)) {
|
|
1188
|
-
|
|
1241
|
+
fs7.unlinkSync(p2);
|
|
1189
1242
|
console.log(` [ok] Unlinked shared ${dir}/`);
|
|
1190
1243
|
}
|
|
1191
1244
|
}
|
|
1192
1245
|
}
|
|
1193
1246
|
for (const sub of ["hooks", "docs", "skills", "agents", "rules"]) {
|
|
1194
|
-
const p2 =
|
|
1247
|
+
const p2 = path8.join(claudeDir, sub);
|
|
1195
1248
|
safeRemove(p2);
|
|
1196
1249
|
}
|
|
1197
|
-
const remaining =
|
|
1250
|
+
const remaining = fs7.readdirSync(claudeDir);
|
|
1198
1251
|
for (const f of remaining) {
|
|
1199
|
-
const p2 =
|
|
1252
|
+
const p2 = path8.join(claudeDir, f);
|
|
1200
1253
|
safeRemove(p2);
|
|
1201
1254
|
}
|
|
1202
|
-
if (
|
|
1203
|
-
|
|
1255
|
+
if (fs7.existsSync(claudeDir)) {
|
|
1256
|
+
fs7.rmSync(claudeDir, { recursive: true, force: true });
|
|
1204
1257
|
}
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1258
|
+
if (fs7.existsSync(backup)) {
|
|
1259
|
+
fs7.rmSync(backup, { recursive: true, force: true });
|
|
1207
1260
|
}
|
|
1208
1261
|
console.log(`
|
|
1209
1262
|
[ok] Removed .claude/ from ${target}`);
|
|
1210
1263
|
}
|
|
1211
1264
|
|
|
1212
1265
|
// src/content-source.ts
|
|
1213
|
-
import
|
|
1214
|
-
import
|
|
1266
|
+
import fs9 from "fs";
|
|
1267
|
+
import path10 from "path";
|
|
1215
1268
|
import os2 from "os";
|
|
1216
1269
|
|
|
1217
1270
|
// src/cache.ts
|
|
1218
|
-
import
|
|
1219
|
-
import
|
|
1271
|
+
import fs8 from "fs";
|
|
1272
|
+
import path9 from "path";
|
|
1220
1273
|
import os from "os";
|
|
1221
|
-
var ARCANE_DIR =
|
|
1222
|
-
var CACHE_DIR =
|
|
1274
|
+
var ARCANE_DIR = path9.join(os.homedir(), ".arcane");
|
|
1275
|
+
var CACHE_DIR = path9.join(ARCANE_DIR, "cache");
|
|
1223
1276
|
function getCachePath(version) {
|
|
1224
|
-
return
|
|
1277
|
+
return path9.join(CACHE_DIR, version);
|
|
1225
1278
|
}
|
|
1226
1279
|
function isCached(version) {
|
|
1227
1280
|
const versionDir = getCachePath(version);
|
|
1228
|
-
return
|
|
1281
|
+
return fs8.existsSync(versionDir) && fs8.existsSync(path9.join(versionDir, ".cache-meta.json"));
|
|
1229
1282
|
}
|
|
1230
1283
|
function getCachedContentRoot(version) {
|
|
1231
1284
|
if (!isCached(version)) return null;
|
|
@@ -1233,34 +1286,34 @@ function getCachedContentRoot(version) {
|
|
|
1233
1286
|
}
|
|
1234
1287
|
function storeInCache(version, contentDir) {
|
|
1235
1288
|
const versionDir = getCachePath(version);
|
|
1236
|
-
if (
|
|
1237
|
-
|
|
1289
|
+
if (fs8.existsSync(versionDir)) {
|
|
1290
|
+
fs8.rmSync(versionDir, { recursive: true, force: true });
|
|
1238
1291
|
}
|
|
1239
|
-
|
|
1292
|
+
fs8.mkdirSync(versionDir, { recursive: true });
|
|
1240
1293
|
for (const dir of ["skills", "rules", "agents", "hooks", "profiles", "templates", "docs"]) {
|
|
1241
|
-
const src =
|
|
1242
|
-
if (
|
|
1243
|
-
copyDirRecursive(src,
|
|
1294
|
+
const src = path9.join(contentDir, dir);
|
|
1295
|
+
if (fs8.existsSync(src)) {
|
|
1296
|
+
copyDirRecursive(src, path9.join(versionDir, dir));
|
|
1244
1297
|
}
|
|
1245
1298
|
}
|
|
1246
1299
|
const meta = {
|
|
1247
1300
|
version,
|
|
1248
1301
|
cached_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1249
1302
|
};
|
|
1250
|
-
|
|
1251
|
-
|
|
1303
|
+
fs8.writeFileSync(
|
|
1304
|
+
path9.join(versionDir, ".cache-meta.json"),
|
|
1252
1305
|
JSON.stringify(meta, null, 2) + "\n"
|
|
1253
1306
|
);
|
|
1254
1307
|
return versionDir;
|
|
1255
1308
|
}
|
|
1256
1309
|
function pruneCache(keepCount = 3) {
|
|
1257
|
-
if (!
|
|
1258
|
-
const entries =
|
|
1259
|
-
const metaPath =
|
|
1310
|
+
if (!fs8.existsSync(CACHE_DIR)) return;
|
|
1311
|
+
const entries = fs8.readdirSync(CACHE_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
|
|
1312
|
+
const metaPath = path9.join(CACHE_DIR, e.name, ".cache-meta.json");
|
|
1260
1313
|
let cachedAt = 0;
|
|
1261
|
-
if (
|
|
1314
|
+
if (fs8.existsSync(metaPath)) {
|
|
1262
1315
|
try {
|
|
1263
|
-
const meta = JSON.parse(
|
|
1316
|
+
const meta = JSON.parse(fs8.readFileSync(metaPath, "utf-8"));
|
|
1264
1317
|
cachedAt = new Date(meta.cached_at).getTime();
|
|
1265
1318
|
} catch {
|
|
1266
1319
|
cachedAt = 0;
|
|
@@ -1269,18 +1322,18 @@ function pruneCache(keepCount = 3) {
|
|
|
1269
1322
|
return { name: e.name, cachedAt };
|
|
1270
1323
|
}).sort((a, b) => b.cachedAt - a.cachedAt);
|
|
1271
1324
|
for (const entry of entries.slice(keepCount)) {
|
|
1272
|
-
|
|
1325
|
+
fs8.rmSync(path9.join(CACHE_DIR, entry.name), { recursive: true, force: true });
|
|
1273
1326
|
}
|
|
1274
1327
|
}
|
|
1275
1328
|
function copyDirRecursive(src, dest) {
|
|
1276
|
-
|
|
1277
|
-
for (const entry of
|
|
1278
|
-
const srcPath =
|
|
1279
|
-
const destPath =
|
|
1329
|
+
fs8.mkdirSync(dest, { recursive: true });
|
|
1330
|
+
for (const entry of fs8.readdirSync(src, { withFileTypes: true })) {
|
|
1331
|
+
const srcPath = path9.join(src, entry.name);
|
|
1332
|
+
const destPath = path9.join(dest, entry.name);
|
|
1280
1333
|
if (entry.isDirectory()) {
|
|
1281
1334
|
copyDirRecursive(srcPath, destPath);
|
|
1282
1335
|
} else {
|
|
1283
|
-
|
|
1336
|
+
fs8.copyFileSync(srcPath, destPath);
|
|
1284
1337
|
}
|
|
1285
1338
|
}
|
|
1286
1339
|
}
|
|
@@ -1296,7 +1349,7 @@ var BundledContentSource = class {
|
|
|
1296
1349
|
}
|
|
1297
1350
|
async isAvailable() {
|
|
1298
1351
|
const root = getPackageRoot();
|
|
1299
|
-
return
|
|
1352
|
+
return fs9.existsSync(path10.join(root, "skills"));
|
|
1300
1353
|
}
|
|
1301
1354
|
async getVersion() {
|
|
1302
1355
|
return getPackageVersion();
|
|
@@ -1336,14 +1389,14 @@ var GitHubContentSource = class {
|
|
|
1336
1389
|
if (isCached(version)) {
|
|
1337
1390
|
return getCachedContentRoot(version);
|
|
1338
1391
|
}
|
|
1339
|
-
const tmpDir =
|
|
1392
|
+
const tmpDir = fs9.mkdtempSync(path10.join(os2.tmpdir(), "arcane-github-"));
|
|
1340
1393
|
try {
|
|
1341
1394
|
await this.downloadAndExtract(tmpDir);
|
|
1342
1395
|
const contentRoot = storeInCache(version, this.findExtractedRoot(tmpDir));
|
|
1343
1396
|
pruneCache(3);
|
|
1344
1397
|
return contentRoot;
|
|
1345
1398
|
} finally {
|
|
1346
|
-
|
|
1399
|
+
fs9.rmSync(tmpDir, { recursive: true, force: true });
|
|
1347
1400
|
}
|
|
1348
1401
|
}
|
|
1349
1402
|
async isAvailable() {
|
|
@@ -1399,8 +1452,8 @@ var GitHubContentSource = class {
|
|
|
1399
1452
|
throw new Error(`Failed to download tarball: ${response.status}`);
|
|
1400
1453
|
}
|
|
1401
1454
|
const tar = await import("tar");
|
|
1402
|
-
const tarballPath =
|
|
1403
|
-
const fileStream =
|
|
1455
|
+
const tarballPath = path10.join(destDir, "repo.tar.gz");
|
|
1456
|
+
const fileStream = fs9.createWriteStream(tarballPath);
|
|
1404
1457
|
const reader = response.body.getReader();
|
|
1405
1458
|
const writable = new WritableStream({
|
|
1406
1459
|
write(chunk) {
|
|
@@ -1425,12 +1478,12 @@ var GitHubContentSource = class {
|
|
|
1425
1478
|
});
|
|
1426
1479
|
}
|
|
1427
1480
|
findExtractedRoot(tmpDir) {
|
|
1428
|
-
const entries =
|
|
1481
|
+
const entries = fs9.readdirSync(tmpDir, { withFileTypes: true });
|
|
1429
1482
|
const dirs = entries.filter(
|
|
1430
1483
|
(e) => e.isDirectory() && e.name !== "." && e.name !== ".."
|
|
1431
1484
|
);
|
|
1432
1485
|
if (dirs.length === 1) {
|
|
1433
|
-
return
|
|
1486
|
+
return path10.join(tmpDir, dirs[0].name);
|
|
1434
1487
|
}
|
|
1435
1488
|
return tmpDir;
|
|
1436
1489
|
}
|
|
@@ -1485,14 +1538,14 @@ async function resolveContentSourceForVersion(version, opts = {}) {
|
|
|
1485
1538
|
return source;
|
|
1486
1539
|
}
|
|
1487
1540
|
function findLatestCache() {
|
|
1488
|
-
const cacheDir =
|
|
1489
|
-
if (!
|
|
1490
|
-
const entries =
|
|
1491
|
-
const metaPath =
|
|
1541
|
+
const cacheDir = path10.join(os2.homedir(), ".arcane", "cache");
|
|
1542
|
+
if (!fs9.existsSync(cacheDir)) return null;
|
|
1543
|
+
const entries = fs9.readdirSync(cacheDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
|
|
1544
|
+
const metaPath = path10.join(cacheDir, e.name, ".cache-meta.json");
|
|
1492
1545
|
let cachedAt = 0;
|
|
1493
|
-
if (
|
|
1546
|
+
if (fs9.existsSync(metaPath)) {
|
|
1494
1547
|
try {
|
|
1495
|
-
const meta = JSON.parse(
|
|
1548
|
+
const meta = JSON.parse(fs9.readFileSync(metaPath, "utf-8"));
|
|
1496
1549
|
cachedAt = new Date(meta.cached_at).getTime();
|
|
1497
1550
|
} catch {
|
|
1498
1551
|
cachedAt = 0;
|
|
@@ -1504,14 +1557,14 @@ function findLatestCache() {
|
|
|
1504
1557
|
}
|
|
1505
1558
|
|
|
1506
1559
|
// src/registry.ts
|
|
1507
|
-
import
|
|
1560
|
+
import path11 from "path";
|
|
1508
1561
|
import os3 from "os";
|
|
1509
1562
|
var REGISTRY_VERSION = 1;
|
|
1510
1563
|
function arcaneHome() {
|
|
1511
|
-
return process.env.ARCANE_HOME ??
|
|
1564
|
+
return process.env.ARCANE_HOME ?? path11.join(os3.homedir(), ".arcane");
|
|
1512
1565
|
}
|
|
1513
1566
|
function registryPath() {
|
|
1514
|
-
return
|
|
1567
|
+
return path11.join(arcaneHome(), "installations.json");
|
|
1515
1568
|
}
|
|
1516
1569
|
function readRegistryFile() {
|
|
1517
1570
|
const p2 = registryPath();
|
|
@@ -1536,7 +1589,7 @@ function writeRegistryFile(registry) {
|
|
|
1536
1589
|
}
|
|
1537
1590
|
}
|
|
1538
1591
|
function registerInstallation(target) {
|
|
1539
|
-
const abs =
|
|
1592
|
+
const abs = path11.resolve(target);
|
|
1540
1593
|
const registry = readRegistryFile();
|
|
1541
1594
|
if (registry.installations.some((e) => e.path === abs)) {
|
|
1542
1595
|
return;
|
|
@@ -1563,8 +1616,8 @@ async function installCommand(profileExpr, opts) {
|
|
|
1563
1616
|
quiet: !profileExpr
|
|
1564
1617
|
});
|
|
1565
1618
|
const root = await source.getContentRoot();
|
|
1566
|
-
const profilesDir =
|
|
1567
|
-
const target =
|
|
1619
|
+
const profilesDir = path12.join(root, "profiles");
|
|
1620
|
+
const target = path12.resolve(opts.target ?? process.cwd());
|
|
1568
1621
|
if (!profileExpr) {
|
|
1569
1622
|
const profiles = listProfiles(profilesDir);
|
|
1570
1623
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
@@ -1635,21 +1688,21 @@ Installing profile: ${chalk2.cyan(profileExpr)}`)
|
|
|
1635
1688
|
}
|
|
1636
1689
|
|
|
1637
1690
|
// src/commands/add.ts
|
|
1638
|
-
import
|
|
1639
|
-
import
|
|
1691
|
+
import fs11 from "fs";
|
|
1692
|
+
import path14 from "path";
|
|
1640
1693
|
import chalk3 from "chalk";
|
|
1641
1694
|
|
|
1642
1695
|
// src/skills-catalog.ts
|
|
1643
|
-
import
|
|
1644
|
-
import
|
|
1696
|
+
import fs10 from "fs";
|
|
1697
|
+
import path13 from "path";
|
|
1645
1698
|
var FRONTMATTER_BYTES = 4096;
|
|
1646
1699
|
function listSkills(skillsDir) {
|
|
1647
|
-
if (!
|
|
1648
|
-
return
|
|
1649
|
-
(d) => d.isDirectory() && !d.name.startsWith("_") &&
|
|
1700
|
+
if (!fs10.existsSync(skillsDir)) return [];
|
|
1701
|
+
return fs10.readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1702
|
+
(d) => d.isDirectory() && !d.name.startsWith("_") && fs10.existsSync(path13.join(skillsDir, d.name, "SKILL.md"))
|
|
1650
1703
|
).map((d) => ({
|
|
1651
1704
|
name: d.name,
|
|
1652
|
-
description: readDescription(
|
|
1705
|
+
description: readDescription(path13.join(skillsDir, d.name, "SKILL.md"))
|
|
1653
1706
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
1654
1707
|
}
|
|
1655
1708
|
function readDescription(skillFile) {
|
|
@@ -1670,13 +1723,13 @@ function readDescription(skillFile) {
|
|
|
1670
1723
|
return inline.replace(/^["']|["']$/g, "").trim();
|
|
1671
1724
|
}
|
|
1672
1725
|
function readHead(file) {
|
|
1673
|
-
const fd =
|
|
1726
|
+
const fd = fs10.openSync(file, "r");
|
|
1674
1727
|
try {
|
|
1675
1728
|
const buf = Buffer.alloc(FRONTMATTER_BYTES);
|
|
1676
|
-
const read =
|
|
1729
|
+
const read = fs10.readSync(fd, buf, 0, FRONTMATTER_BYTES, 0);
|
|
1677
1730
|
return buf.subarray(0, read).toString("utf-8");
|
|
1678
1731
|
} finally {
|
|
1679
|
-
|
|
1732
|
+
fs10.closeSync(fd);
|
|
1680
1733
|
}
|
|
1681
1734
|
}
|
|
1682
1735
|
|
|
@@ -1700,15 +1753,15 @@ async function addCommand(items = []) {
|
|
|
1700
1753
|
return;
|
|
1701
1754
|
}
|
|
1702
1755
|
const selected = await runAddWizard({
|
|
1703
|
-
profiles: listProfiles(
|
|
1704
|
-
skills: listSkills(
|
|
1756
|
+
profiles: listProfiles(path14.join(root, "profiles")),
|
|
1757
|
+
skills: listSkills(path14.join(root, "skills")),
|
|
1705
1758
|
installedProfiles: manifest.profiles,
|
|
1706
1759
|
installedSkills: manifest.installed_skills
|
|
1707
1760
|
});
|
|
1708
1761
|
if (!selected) return;
|
|
1709
1762
|
items = selected;
|
|
1710
1763
|
}
|
|
1711
|
-
const claudeDir =
|
|
1764
|
+
const claudeDir = path14.join(target, ".claude");
|
|
1712
1765
|
const added = [];
|
|
1713
1766
|
const skipped = [];
|
|
1714
1767
|
const notFound = [];
|
|
@@ -1718,8 +1771,8 @@ async function addCommand(items = []) {
|
|
|
1718
1771
|
for (const item of items) {
|
|
1719
1772
|
if (item.startsWith("+")) {
|
|
1720
1773
|
const profileName = item.slice(1);
|
|
1721
|
-
const profilePath =
|
|
1722
|
-
if (!
|
|
1774
|
+
const profilePath = path14.join(root, "profiles", `${profileName}.yaml`);
|
|
1775
|
+
if (!fs11.existsSync(profilePath)) {
|
|
1723
1776
|
console.error(chalk3.red(` Profile '${profileName}' not found`));
|
|
1724
1777
|
continue;
|
|
1725
1778
|
}
|
|
@@ -1732,10 +1785,10 @@ async function addCommand(items = []) {
|
|
|
1732
1785
|
}
|
|
1733
1786
|
for (const rule of profile.rules.universal) {
|
|
1734
1787
|
if (!manifest.installed_rules.includes(rule)) {
|
|
1735
|
-
const src =
|
|
1736
|
-
if (
|
|
1737
|
-
ensureDir(
|
|
1738
|
-
|
|
1788
|
+
const src = path14.join(root, "rules", `${rule}.md`);
|
|
1789
|
+
if (fs11.existsSync(src)) {
|
|
1790
|
+
ensureDir(path14.join(claudeDir, "rules"));
|
|
1791
|
+
fs11.copyFileSync(src, path14.join(claudeDir, "rules", `${rule}.md`));
|
|
1739
1792
|
manifest.installed_rules.push(rule);
|
|
1740
1793
|
addedRules.push(rule);
|
|
1741
1794
|
}
|
|
@@ -1743,30 +1796,28 @@ async function addCommand(items = []) {
|
|
|
1743
1796
|
}
|
|
1744
1797
|
for (const rule of profile.rules.gamedev) {
|
|
1745
1798
|
if (!manifest.installed_rules.includes(rule)) {
|
|
1746
|
-
const src =
|
|
1747
|
-
if (
|
|
1748
|
-
ensureDir(
|
|
1749
|
-
|
|
1799
|
+
const src = path14.join(root, "rules", "gamedev", `${rule}.md`);
|
|
1800
|
+
if (fs11.existsSync(src)) {
|
|
1801
|
+
ensureDir(path14.join(claudeDir, "rules"));
|
|
1802
|
+
fs11.copyFileSync(src, path14.join(claudeDir, "rules", `${rule}.md`));
|
|
1750
1803
|
manifest.installed_rules.push(rule);
|
|
1751
1804
|
addedRules.push(rule);
|
|
1752
1805
|
}
|
|
1753
1806
|
}
|
|
1754
1807
|
}
|
|
1755
|
-
for (const
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
addedAgents.push(agentDir);
|
|
1763
|
-
}
|
|
1808
|
+
for (const entry of profile.agents) {
|
|
1809
|
+
const { division } = parseAgentEntry(entry);
|
|
1810
|
+
if (manifest.installed_agents.includes(entry)) continue;
|
|
1811
|
+
if (isGranular(entry) && manifest.installed_agents.includes(division)) continue;
|
|
1812
|
+
if (copyAgentEntry(root, claudeDir, entry) !== null) {
|
|
1813
|
+
manifest.installed_agents.push(entry);
|
|
1814
|
+
addedAgents.push(entry);
|
|
1764
1815
|
}
|
|
1765
1816
|
}
|
|
1766
1817
|
if (profileName === "statusline") {
|
|
1767
|
-
const statuslineSrc =
|
|
1768
|
-
if (
|
|
1769
|
-
|
|
1818
|
+
const statuslineSrc = path14.join(root, "hooks", "statusline.sh");
|
|
1819
|
+
if (fs11.existsSync(statuslineSrc)) {
|
|
1820
|
+
fs11.copyFileSync(statuslineSrc, path14.join(claudeDir, "statusline.sh"));
|
|
1770
1821
|
statuslineAdded = true;
|
|
1771
1822
|
}
|
|
1772
1823
|
}
|
|
@@ -1808,7 +1859,7 @@ Added ${totalAdded} items:`
|
|
|
1808
1859
|
);
|
|
1809
1860
|
for (const s of added) console.log(chalk3.green(` [ok] skill: ${s}`));
|
|
1810
1861
|
for (const r of addedRules) console.log(chalk3.green(` [ok] rule: ${r}`));
|
|
1811
|
-
for (const a of addedAgents) console.log(chalk3.green(` [ok]
|
|
1862
|
+
for (const a of addedAgents) console.log(chalk3.green(` [ok] ${agentEntryLabel(a)}`));
|
|
1812
1863
|
if (statuslineAdded) console.log(chalk3.green(" [ok] statusline.sh"));
|
|
1813
1864
|
for (const s of skipped)
|
|
1814
1865
|
console.log(chalk3.dim(` [skip] ${s} (already installed)`));
|
|
@@ -1824,10 +1875,10 @@ Added ${totalAdded} items:`
|
|
|
1824
1875
|
}
|
|
1825
1876
|
}
|
|
1826
1877
|
function printCatalog(root, installedProfiles, installedSkills) {
|
|
1827
|
-
const profiles = listProfiles(
|
|
1878
|
+
const profiles = listProfiles(path14.join(root, "profiles")).filter(
|
|
1828
1879
|
(pr) => !installedProfiles.includes(pr.name)
|
|
1829
1880
|
);
|
|
1830
|
-
const skills = listSkills(
|
|
1881
|
+
const skills = listSkills(path14.join(root, "skills")).filter(
|
|
1831
1882
|
(sk) => !installedSkills.includes(sk.name)
|
|
1832
1883
|
);
|
|
1833
1884
|
if (profiles.length === 0 && skills.length === 0) {
|
|
@@ -1853,15 +1904,15 @@ function printCatalog(root, installedProfiles, installedSkills) {
|
|
|
1853
1904
|
}
|
|
1854
1905
|
function addSkill(root, target, skill, installed) {
|
|
1855
1906
|
if (installed.includes(skill)) return "skipped";
|
|
1856
|
-
const src =
|
|
1857
|
-
if (!
|
|
1858
|
-
const dst =
|
|
1907
|
+
const src = path14.join(root, "skills", skill);
|
|
1908
|
+
if (!fs11.existsSync(src)) return "not-found";
|
|
1909
|
+
const dst = path14.join(target, ".claude", "skills", skill);
|
|
1859
1910
|
copyDirSync(src, dst);
|
|
1860
1911
|
return "added";
|
|
1861
1912
|
}
|
|
1862
1913
|
function mergePermissions(claudeDir, newPerms) {
|
|
1863
|
-
const settingsPath =
|
|
1864
|
-
if (!
|
|
1914
|
+
const settingsPath = path14.join(claudeDir, "settings.json");
|
|
1915
|
+
if (!fs11.existsSync(settingsPath)) return;
|
|
1865
1916
|
const settings = readJsonSync(settingsPath);
|
|
1866
1917
|
const perms = settings.permissions ?? { allow: [], deny: [] };
|
|
1867
1918
|
const allowSet = new Set(perms.allow);
|
|
@@ -1874,8 +1925,8 @@ function mergePermissions(claudeDir, newPerms) {
|
|
|
1874
1925
|
writeJsonSync(settingsPath, settings);
|
|
1875
1926
|
}
|
|
1876
1927
|
function addStatuslineToSettings(claudeDir) {
|
|
1877
|
-
const settingsPath =
|
|
1878
|
-
if (!
|
|
1928
|
+
const settingsPath = path14.join(claudeDir, "settings.json");
|
|
1929
|
+
if (!fs11.existsSync(settingsPath)) return;
|
|
1879
1930
|
const settings = readJsonSync(settingsPath);
|
|
1880
1931
|
if (settings.statusLine) return;
|
|
1881
1932
|
settings.statusLine = {
|
|
@@ -1886,8 +1937,8 @@ function addStatuslineToSettings(claudeDir) {
|
|
|
1886
1937
|
}
|
|
1887
1938
|
|
|
1888
1939
|
// src/commands/remove.ts
|
|
1889
|
-
import
|
|
1890
|
-
import
|
|
1940
|
+
import fs12 from "fs";
|
|
1941
|
+
import path15 from "path";
|
|
1891
1942
|
import chalk4 from "chalk";
|
|
1892
1943
|
var CORE_SKILLS = [
|
|
1893
1944
|
"commit",
|
|
@@ -1952,7 +2003,7 @@ async function removeCommand(items, opts = {}) {
|
|
|
1952
2003
|
}
|
|
1953
2004
|
manifest.total_skills = manifest.installed_skills.length;
|
|
1954
2005
|
manifest.total_rules = manifest.installed_rules.length;
|
|
1955
|
-
const mp =
|
|
2006
|
+
const mp = path15.join(target, ".claude", "arcane-manifest.json");
|
|
1956
2007
|
writeJsonSync(mp, manifest);
|
|
1957
2008
|
const totalRemoved = removedSkills.length + removedAgents.length + removedProfiles.length;
|
|
1958
2009
|
console.log(chalk4.bold(`
|
|
@@ -1973,9 +2024,9 @@ function removeSkill(target, skill, manifest) {
|
|
|
1973
2024
|
);
|
|
1974
2025
|
return "skipped";
|
|
1975
2026
|
}
|
|
1976
|
-
const skillDir =
|
|
1977
|
-
if (!
|
|
1978
|
-
|
|
2027
|
+
const skillDir = path15.join(target, ".claude", "skills", skill);
|
|
2028
|
+
if (!fs12.existsSync(skillDir)) return "skipped";
|
|
2029
|
+
fs12.rmSync(skillDir, { recursive: true, force: true });
|
|
1979
2030
|
manifest.installed_skills = manifest.installed_skills.filter(
|
|
1980
2031
|
(s) => s !== skill
|
|
1981
2032
|
);
|
|
@@ -1994,8 +2045,8 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
1994
2045
|
);
|
|
1995
2046
|
return { removed: false, skills: [], agents: [] };
|
|
1996
2047
|
}
|
|
1997
|
-
const profilePath =
|
|
1998
|
-
if (!
|
|
2048
|
+
const profilePath = path15.join(root, "profiles", `${profileName}.yaml`);
|
|
2049
|
+
if (!fs12.existsSync(profilePath)) {
|
|
1999
2050
|
console.warn(
|
|
2000
2051
|
chalk4.yellow(` WARNING: Profile '${profileName}.yaml' not found.`)
|
|
2001
2052
|
);
|
|
@@ -2009,8 +2060,8 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
2009
2060
|
const sharedAgents = /* @__PURE__ */ new Set();
|
|
2010
2061
|
const sharedRules = /* @__PURE__ */ new Set();
|
|
2011
2062
|
for (const rp of remainingProfiles) {
|
|
2012
|
-
const rpPath =
|
|
2013
|
-
if (!
|
|
2063
|
+
const rpPath = path15.join(root, "profiles", `${rp}.yaml`);
|
|
2064
|
+
if (!fs12.existsSync(rpPath)) continue;
|
|
2014
2065
|
const rpDef = parseProfile(rpPath);
|
|
2015
2066
|
rpDef.skills.forEach((s) => sharedSkills.add(s));
|
|
2016
2067
|
rpDef.agents.forEach((a) => sharedAgents.add(a));
|
|
@@ -2021,9 +2072,9 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
2021
2072
|
for (const skill of profile.skills) {
|
|
2022
2073
|
if (sharedSkills.has(skill)) continue;
|
|
2023
2074
|
if (CORE_SKILLS.includes(skill)) continue;
|
|
2024
|
-
const skillDir =
|
|
2025
|
-
if (
|
|
2026
|
-
|
|
2075
|
+
const skillDir = path15.join(target, ".claude", "skills", skill);
|
|
2076
|
+
if (fs12.existsSync(skillDir)) {
|
|
2077
|
+
fs12.rmSync(skillDir, { recursive: true, force: true });
|
|
2027
2078
|
removedSkills.push(skill);
|
|
2028
2079
|
}
|
|
2029
2080
|
manifest.installed_skills = manifest.installed_skills.filter(
|
|
@@ -2031,15 +2082,14 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
2031
2082
|
);
|
|
2032
2083
|
}
|
|
2033
2084
|
const removedAgents = [];
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
}
|
|
2085
|
+
const claudeDir = path15.join(target, ".claude");
|
|
2086
|
+
for (const entry of profile.agents) {
|
|
2087
|
+
if (sharedAgents.has(entry)) continue;
|
|
2088
|
+
const { division } = parseAgentEntry(entry);
|
|
2089
|
+
if (isGranular(entry) && sharedAgents.has(division)) continue;
|
|
2090
|
+
if (removeAgentEntry(claudeDir, entry)) removedAgents.push(entry);
|
|
2041
2091
|
manifest.installed_agents = manifest.installed_agents.filter(
|
|
2042
|
-
(a) => a !==
|
|
2092
|
+
(a) => a !== entry
|
|
2043
2093
|
);
|
|
2044
2094
|
}
|
|
2045
2095
|
for (const rule of [
|
|
@@ -2047,44 +2097,44 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
2047
2097
|
...profile.rules.gamedev
|
|
2048
2098
|
]) {
|
|
2049
2099
|
if (sharedRules.has(rule)) continue;
|
|
2050
|
-
const ruleFile =
|
|
2051
|
-
if (
|
|
2052
|
-
|
|
2100
|
+
const ruleFile = path15.join(target, ".claude", "rules", `${rule}.md`);
|
|
2101
|
+
if (fs12.existsSync(ruleFile)) {
|
|
2102
|
+
fs12.rmSync(ruleFile);
|
|
2053
2103
|
}
|
|
2054
2104
|
manifest.installed_rules = manifest.installed_rules.filter(
|
|
2055
2105
|
(r) => r !== rule
|
|
2056
2106
|
);
|
|
2057
2107
|
}
|
|
2058
2108
|
if (profileName === "statusline") {
|
|
2059
|
-
const statuslineFile =
|
|
2060
|
-
if (
|
|
2061
|
-
|
|
2109
|
+
const statuslineFile = path15.join(target, ".claude", "statusline.sh");
|
|
2110
|
+
if (fs12.existsSync(statuslineFile)) {
|
|
2111
|
+
fs12.rmSync(statuslineFile);
|
|
2062
2112
|
}
|
|
2063
|
-
removeStatuslineFromSettings(
|
|
2113
|
+
removeStatuslineFromSettings(path15.join(target, ".claude"));
|
|
2064
2114
|
}
|
|
2065
2115
|
manifest.profiles = remainingProfiles;
|
|
2066
2116
|
manifest.profile_command = remainingProfiles.filter((p2) => p2 !== "core").join("+");
|
|
2067
2117
|
return { removed: true, skills: removedSkills, agents: removedAgents };
|
|
2068
2118
|
}
|
|
2069
2119
|
function removeStatuslineFromSettings(claudeDir) {
|
|
2070
|
-
const settingsPath =
|
|
2071
|
-
if (!
|
|
2072
|
-
const settings = JSON.parse(
|
|
2120
|
+
const settingsPath = path15.join(claudeDir, "settings.json");
|
|
2121
|
+
if (!fs12.existsSync(settingsPath)) return;
|
|
2122
|
+
const settings = JSON.parse(fs12.readFileSync(settingsPath, "utf-8"));
|
|
2073
2123
|
if (settings.statusLine) {
|
|
2074
2124
|
delete settings.statusLine;
|
|
2075
|
-
|
|
2125
|
+
fs12.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
2076
2126
|
}
|
|
2077
2127
|
}
|
|
2078
2128
|
|
|
2079
2129
|
// src/commands/list.ts
|
|
2080
|
-
import
|
|
2081
|
-
import
|
|
2130
|
+
import fs13 from "fs";
|
|
2131
|
+
import path16 from "path";
|
|
2082
2132
|
import chalk5 from "chalk";
|
|
2083
2133
|
async function listCommand() {
|
|
2084
2134
|
const source = await resolveContentSource({ quiet: true });
|
|
2085
2135
|
const root = await source.getContentRoot();
|
|
2086
|
-
const profilesDir =
|
|
2087
|
-
const skillsDir =
|
|
2136
|
+
const profilesDir = path16.join(root, "profiles");
|
|
2137
|
+
const skillsDir = path16.join(root, "skills");
|
|
2088
2138
|
const target = process.cwd();
|
|
2089
2139
|
const manifest = readManifest(target);
|
|
2090
2140
|
const profiles = listProfiles(profilesDir);
|
|
@@ -2101,7 +2151,7 @@ async function listCommand() {
|
|
|
2101
2151
|
}
|
|
2102
2152
|
console.log();
|
|
2103
2153
|
}
|
|
2104
|
-
if (
|
|
2154
|
+
if (fs13.existsSync(skillsDir)) {
|
|
2105
2155
|
const allSkills = listSkills(skillsDir).map((s) => s.name);
|
|
2106
2156
|
console.log(chalk5.bold(`
|
|
2107
2157
|
=== Skills (${allSkills.length}) ===
|
|
@@ -2168,8 +2218,8 @@ async function statusCommand() {
|
|
|
2168
2218
|
}
|
|
2169
2219
|
|
|
2170
2220
|
// src/commands/update.ts
|
|
2171
|
-
import
|
|
2172
|
-
import
|
|
2221
|
+
import fs14 from "fs";
|
|
2222
|
+
import path17 from "path";
|
|
2173
2223
|
import os4 from "os";
|
|
2174
2224
|
import chalk7 from "chalk";
|
|
2175
2225
|
|
|
@@ -2196,22 +2246,26 @@ async function selfUpdateNpm(opts = {}) {
|
|
|
2196
2246
|
if (opts.dryRun) {
|
|
2197
2247
|
return { updated: false, skipped: true, reason: "dry-run", fromVersion };
|
|
2198
2248
|
}
|
|
2199
|
-
const npm =
|
|
2200
|
-
const result = spawnSync(
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
timeout: 12e4
|
|
2207
|
-
}
|
|
2208
|
-
);
|
|
2249
|
+
const npm = npmInstallCommand();
|
|
2250
|
+
const result = spawnSync(npm.command, npm.args, {
|
|
2251
|
+
stdio: opts.quiet ? "ignore" : "inherit",
|
|
2252
|
+
encoding: "utf-8",
|
|
2253
|
+
timeout: 12e4,
|
|
2254
|
+
shell: npm.shell
|
|
2255
|
+
});
|
|
2209
2256
|
if (result.status === 0) {
|
|
2210
2257
|
return { updated: true, skipped: false, fromVersion };
|
|
2211
2258
|
}
|
|
2212
2259
|
const reason = result.error ? result.error.message : `npm exited with code ${result.status ?? "unknown"}`;
|
|
2213
2260
|
return { updated: false, skipped: false, reason, fromVersion };
|
|
2214
2261
|
}
|
|
2262
|
+
function npmInstallCommand(platform = process.platform) {
|
|
2263
|
+
const args = ["install", "-g", `${PACKAGE_NAME}@latest`];
|
|
2264
|
+
if (platform === "win32") {
|
|
2265
|
+
return { command: ["npm", ...args].join(" "), args: [], shell: true };
|
|
2266
|
+
}
|
|
2267
|
+
return { command: "npm", args, shell: false };
|
|
2268
|
+
}
|
|
2215
2269
|
function safeVersion() {
|
|
2216
2270
|
try {
|
|
2217
2271
|
return getPackageVersion();
|
|
@@ -2241,12 +2295,12 @@ async function updateCommand(opts) {
|
|
|
2241
2295
|
}
|
|
2242
2296
|
}
|
|
2243
2297
|
const cwd = process.cwd();
|
|
2244
|
-
if (
|
|
2298
|
+
if (fs14.existsSync(manifestPath(cwd))) {
|
|
2245
2299
|
registerInstallation(cwd);
|
|
2246
2300
|
}
|
|
2247
2301
|
const targets = pruneRegistry().map((e) => e.path);
|
|
2248
2302
|
const globalTarget = os4.homedir();
|
|
2249
|
-
if (!process.env.VITEST &&
|
|
2303
|
+
if (!process.env.VITEST && fs14.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
|
|
2250
2304
|
targets.push(globalTarget);
|
|
2251
2305
|
}
|
|
2252
2306
|
if (targets.length === 0) {
|
|
@@ -2322,11 +2376,11 @@ Update available: ${chalk7.red(installedVersion)} -> ${chalk7.green(currentVersi
|
|
|
2322
2376
|
);
|
|
2323
2377
|
}
|
|
2324
2378
|
const profileNames = manifest.profile_command.split("+").filter(Boolean);
|
|
2325
|
-
const profilesDir =
|
|
2379
|
+
const profilesDir = path17.join(root, "profiles");
|
|
2326
2380
|
const merged = mergeProfiles(profilesDir, profileNames);
|
|
2327
2381
|
const allRules = [...merged.rules.universal, ...merged.rules.gamedev];
|
|
2328
2382
|
const sourceHashes = computeSourceHashes(root, merged.skills, allRules, merged.agents);
|
|
2329
|
-
const claudeDir =
|
|
2383
|
+
const claudeDir = path17.join(target, ".claude");
|
|
2330
2384
|
const installedHashes = computeContentHashes(claudeDir);
|
|
2331
2385
|
const items = computeUpdatePlan(
|
|
2332
2386
|
manifest.content_hashes ?? null,
|
|
@@ -2519,48 +2573,48 @@ function applyUpdates(items, root, claudeDir, merged, force) {
|
|
|
2519
2573
|
function applyItem(item, root, claudeDir) {
|
|
2520
2574
|
const src = getSourcePath(item, root);
|
|
2521
2575
|
const dst = getInstalledPath(item, claudeDir);
|
|
2522
|
-
if (!src || !
|
|
2576
|
+
if (!src || !fs14.existsSync(src)) return;
|
|
2523
2577
|
if (item.type === "skill" || item.type === "agent") {
|
|
2524
|
-
if (
|
|
2525
|
-
|
|
2578
|
+
if (fs14.existsSync(dst)) {
|
|
2579
|
+
fs14.rmSync(dst, { recursive: true, force: true });
|
|
2526
2580
|
}
|
|
2527
2581
|
copyDirSync(src, dst);
|
|
2528
2582
|
} else {
|
|
2529
|
-
const dir =
|
|
2530
|
-
if (!
|
|
2531
|
-
|
|
2583
|
+
const dir = path17.dirname(dst);
|
|
2584
|
+
if (!fs14.existsSync(dir)) {
|
|
2585
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
2532
2586
|
}
|
|
2533
|
-
|
|
2587
|
+
fs14.copyFileSync(src, dst);
|
|
2534
2588
|
}
|
|
2535
2589
|
}
|
|
2536
2590
|
function backupItem(item, claudeDir) {
|
|
2537
2591
|
const installed = getInstalledPath(item, claudeDir);
|
|
2538
|
-
if (!
|
|
2592
|
+
if (!fs14.existsSync(installed)) return;
|
|
2539
2593
|
const backupPath = installed + ".bak";
|
|
2540
|
-
if (
|
|
2541
|
-
|
|
2594
|
+
if (fs14.existsSync(backupPath)) {
|
|
2595
|
+
fs14.rmSync(backupPath, { recursive: true, force: true });
|
|
2542
2596
|
}
|
|
2543
|
-
|
|
2597
|
+
fs14.renameSync(installed, backupPath);
|
|
2544
2598
|
}
|
|
2545
2599
|
function removeItem(item, claudeDir) {
|
|
2546
2600
|
const installed = getInstalledPath(item, claudeDir);
|
|
2547
|
-
if (
|
|
2548
|
-
|
|
2601
|
+
if (fs14.existsSync(installed)) {
|
|
2602
|
+
fs14.rmSync(installed, { recursive: true, force: true });
|
|
2549
2603
|
}
|
|
2550
2604
|
}
|
|
2551
2605
|
function getSourcePath(item, root) {
|
|
2552
2606
|
switch (item.type) {
|
|
2553
2607
|
case "skill":
|
|
2554
|
-
return
|
|
2608
|
+
return path17.join(root, "skills", item.name);
|
|
2555
2609
|
case "rule": {
|
|
2556
|
-
const direct =
|
|
2557
|
-
if (
|
|
2558
|
-
return
|
|
2610
|
+
const direct = path17.join(root, "rules", item.name);
|
|
2611
|
+
if (fs14.existsSync(direct)) return direct;
|
|
2612
|
+
return path17.join(root, "rules", "gamedev", item.name);
|
|
2559
2613
|
}
|
|
2560
2614
|
case "agent":
|
|
2561
|
-
return
|
|
2615
|
+
return path17.join(root, "agents", item.name);
|
|
2562
2616
|
case "hook":
|
|
2563
|
-
return
|
|
2617
|
+
return path17.join(root, "hooks", item.name);
|
|
2564
2618
|
default:
|
|
2565
2619
|
return null;
|
|
2566
2620
|
}
|
|
@@ -2568,13 +2622,13 @@ function getSourcePath(item, root) {
|
|
|
2568
2622
|
function getInstalledPath(item, claudeDir) {
|
|
2569
2623
|
switch (item.type) {
|
|
2570
2624
|
case "skill":
|
|
2571
|
-
return
|
|
2625
|
+
return path17.join(claudeDir, "skills", item.name);
|
|
2572
2626
|
case "rule":
|
|
2573
|
-
return
|
|
2627
|
+
return path17.join(claudeDir, "rules", item.name);
|
|
2574
2628
|
case "agent":
|
|
2575
|
-
return
|
|
2629
|
+
return path17.join(claudeDir, "agents", item.name);
|
|
2576
2630
|
case "hook":
|
|
2577
|
-
return
|
|
2631
|
+
return path17.join(claudeDir, "hooks", item.name);
|
|
2578
2632
|
}
|
|
2579
2633
|
}
|
|
2580
2634
|
|
|
@@ -2609,8 +2663,8 @@ async function cleanCommand(opts) {
|
|
|
2609
2663
|
}
|
|
2610
2664
|
|
|
2611
2665
|
// src/commands/worktree.ts
|
|
2612
|
-
import
|
|
2613
|
-
import
|
|
2666
|
+
import fs15 from "fs";
|
|
2667
|
+
import path18 from "path";
|
|
2614
2668
|
import { execSync as execSync2 } from "child_process";
|
|
2615
2669
|
import chalk9 from "chalk";
|
|
2616
2670
|
async function worktreeCommand(branch, opts) {
|
|
@@ -2621,8 +2675,8 @@ async function worktreeCommand(branch, opts) {
|
|
|
2621
2675
|
process.exit(1);
|
|
2622
2676
|
}
|
|
2623
2677
|
const repoRoot = wtInfo.isWorktree ? wtInfo.mainWorktreePath : cwd;
|
|
2624
|
-
const worktreePath = opts.path ?
|
|
2625
|
-
if (
|
|
2678
|
+
const worktreePath = opts.path ? path18.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
|
|
2679
|
+
if (fs15.existsSync(worktreePath)) {
|
|
2626
2680
|
console.error(
|
|
2627
2681
|
chalk9.red(`Path already exists: ${worktreePath}`)
|
|
2628
2682
|
);
|
|
@@ -2669,7 +2723,7 @@ async function worktreeCommand(branch, opts) {
|
|
|
2669
2723
|
}
|
|
2670
2724
|
console.log(" Installing Arcane...");
|
|
2671
2725
|
const root = getPackageRoot();
|
|
2672
|
-
const profilesDir =
|
|
2726
|
+
const profilesDir = path18.join(root, "profiles");
|
|
2673
2727
|
const profileNames = profileExpr.split("+").filter(Boolean);
|
|
2674
2728
|
const merged = mergeProfiles(profilesDir, profileNames);
|
|
2675
2729
|
const shareFrom = opts.share !== false ? cwd : void 0;
|
|
@@ -2705,13 +2759,13 @@ async function worktreeCommand(branch, opts) {
|
|
|
2705
2759
|
}
|
|
2706
2760
|
}
|
|
2707
2761
|
if (opts.isolate) {
|
|
2708
|
-
const isolateScript =
|
|
2762
|
+
const isolateScript = path18.join(
|
|
2709
2763
|
worktreePath,
|
|
2710
2764
|
".claude",
|
|
2711
2765
|
"skills",
|
|
2712
2766
|
"worktree-isolation"
|
|
2713
2767
|
);
|
|
2714
|
-
if (
|
|
2768
|
+
if (fs15.existsSync(isolateScript)) {
|
|
2715
2769
|
console.log(" Applying worktree isolation...");
|
|
2716
2770
|
try {
|
|
2717
2771
|
execSync2(
|
|
@@ -2746,13 +2800,13 @@ async function worktreeCommand(branch, opts) {
|
|
|
2746
2800
|
}
|
|
2747
2801
|
|
|
2748
2802
|
// src/commands/global.ts
|
|
2749
|
-
import
|
|
2750
|
-
import
|
|
2803
|
+
import fs16 from "fs";
|
|
2804
|
+
import path19 from "path";
|
|
2751
2805
|
import os5 from "os";
|
|
2752
2806
|
import chalk10 from "chalk";
|
|
2753
|
-
var CLAUDE_HOME =
|
|
2754
|
-
var SCRIPTS_DIR =
|
|
2755
|
-
var SETTINGS_PATH =
|
|
2807
|
+
var CLAUDE_HOME = path19.join(os5.homedir(), ".claude");
|
|
2808
|
+
var SCRIPTS_DIR = path19.join(CLAUDE_HOME, "scripts", "worktree-isolation");
|
|
2809
|
+
var SETTINGS_PATH = path19.join(CLAUDE_HOME, "settings.json");
|
|
2756
2810
|
async function globalCommand(opts) {
|
|
2757
2811
|
if (opts.status) {
|
|
2758
2812
|
showStatus();
|
|
@@ -2766,8 +2820,8 @@ async function globalCommand(opts) {
|
|
|
2766
2820
|
}
|
|
2767
2821
|
function showStatus() {
|
|
2768
2822
|
console.log(chalk10.bold("\n=== Arcane Global Status ===\n"));
|
|
2769
|
-
const hasScripts =
|
|
2770
|
-
|
|
2823
|
+
const hasScripts = fs16.existsSync(
|
|
2824
|
+
path19.join(SCRIPTS_DIR, "apply.py")
|
|
2771
2825
|
);
|
|
2772
2826
|
console.log(
|
|
2773
2827
|
` Worktree scripts: ${hasScripts ? chalk10.green("installed") : chalk10.dim("not installed")}`
|
|
@@ -2785,32 +2839,32 @@ function showStatus() {
|
|
|
2785
2839
|
function installGlobal() {
|
|
2786
2840
|
console.log(chalk10.bold("\nInstalling Arcane global hooks...\n"));
|
|
2787
2841
|
const root = getPackageRoot();
|
|
2788
|
-
const scriptsSrc =
|
|
2842
|
+
const scriptsSrc = path19.join(
|
|
2789
2843
|
root,
|
|
2790
2844
|
"skills",
|
|
2791
2845
|
"worktree-isolation",
|
|
2792
2846
|
"scripts"
|
|
2793
2847
|
);
|
|
2794
|
-
if (!
|
|
2848
|
+
if (!fs16.existsSync(scriptsSrc)) {
|
|
2795
2849
|
console.error(
|
|
2796
2850
|
chalk10.red(" worktree-isolation scripts not found in package.")
|
|
2797
2851
|
);
|
|
2798
2852
|
process.exit(1);
|
|
2799
2853
|
}
|
|
2800
|
-
|
|
2854
|
+
fs16.mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
2801
2855
|
for (const file of ["apply.py", "lib.py"]) {
|
|
2802
|
-
const src =
|
|
2803
|
-
const dst =
|
|
2804
|
-
if (!
|
|
2856
|
+
const src = path19.join(scriptsSrc, file);
|
|
2857
|
+
const dst = path19.join(SCRIPTS_DIR, file);
|
|
2858
|
+
if (!fs16.existsSync(src)) {
|
|
2805
2859
|
console.warn(chalk10.yellow(` WARN: ${file} not found, skipping`));
|
|
2806
2860
|
continue;
|
|
2807
2861
|
}
|
|
2808
|
-
if (
|
|
2862
|
+
if (fs16.existsSync(dst) && fs16.readFileSync(src).equals(fs16.readFileSync(dst))) {
|
|
2809
2863
|
console.log(chalk10.dim(` [skip] ${file} (already up-to-date)`));
|
|
2810
2864
|
} else {
|
|
2811
|
-
|
|
2865
|
+
fs16.copyFileSync(src, dst);
|
|
2812
2866
|
if (process.platform !== "win32") {
|
|
2813
|
-
|
|
2867
|
+
fs16.chmodSync(dst, 493);
|
|
2814
2868
|
}
|
|
2815
2869
|
console.log(chalk10.green(` [ok] ${file} -> ${SCRIPTS_DIR}`));
|
|
2816
2870
|
}
|
|
@@ -2834,8 +2888,8 @@ function installGlobal() {
|
|
|
2834
2888
|
}
|
|
2835
2889
|
function removeGlobal() {
|
|
2836
2890
|
console.log(chalk10.bold("\nRemoving Arcane global hooks...\n"));
|
|
2837
|
-
if (
|
|
2838
|
-
|
|
2891
|
+
if (fs16.existsSync(SCRIPTS_DIR)) {
|
|
2892
|
+
fs16.rmSync(SCRIPTS_DIR, { recursive: true, force: true });
|
|
2839
2893
|
console.log(chalk10.green(" [ok] Removed worktree-isolation scripts"));
|
|
2840
2894
|
} else {
|
|
2841
2895
|
console.log(chalk10.dim(" [skip] Scripts not found"));
|
|
@@ -2849,16 +2903,16 @@ function removeGlobal() {
|
|
|
2849
2903
|
console.log(chalk10.bold.green("\n Global hooks removed.\n"));
|
|
2850
2904
|
}
|
|
2851
2905
|
function readSettings() {
|
|
2852
|
-
if (!
|
|
2906
|
+
if (!fs16.existsSync(SETTINGS_PATH)) return {};
|
|
2853
2907
|
try {
|
|
2854
|
-
return JSON.parse(
|
|
2908
|
+
return JSON.parse(fs16.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
2855
2909
|
} catch {
|
|
2856
2910
|
return {};
|
|
2857
2911
|
}
|
|
2858
2912
|
}
|
|
2859
2913
|
function writeSettings(settings) {
|
|
2860
|
-
|
|
2861
|
-
|
|
2914
|
+
fs16.mkdirSync(path19.dirname(SETTINGS_PATH), { recursive: true });
|
|
2915
|
+
fs16.writeFileSync(
|
|
2862
2916
|
SETTINGS_PATH,
|
|
2863
2917
|
JSON.stringify(settings, null, 2) + "\n",
|
|
2864
2918
|
"utf-8"
|
|
@@ -2915,11 +2969,11 @@ function removeHookFromSettings() {
|
|
|
2915
2969
|
}
|
|
2916
2970
|
|
|
2917
2971
|
// src/update-check.ts
|
|
2918
|
-
import
|
|
2919
|
-
import
|
|
2972
|
+
import fs17 from "fs";
|
|
2973
|
+
import path20 from "path";
|
|
2920
2974
|
import os6 from "os";
|
|
2921
2975
|
import chalk11 from "chalk";
|
|
2922
|
-
var CHECK_FILE =
|
|
2976
|
+
var CHECK_FILE = path20.join(os6.homedir(), ".arcane", "last-check.json");
|
|
2923
2977
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
2924
2978
|
var GITHUB_OWNER2 = "SebastianLuser";
|
|
2925
2979
|
var GITHUB_REPO2 = "Claude-Code-Arcane";
|
|
@@ -3044,19 +3098,19 @@ async function getLatestNpmVersion() {
|
|
|
3044
3098
|
}
|
|
3045
3099
|
function readCachedCheck() {
|
|
3046
3100
|
try {
|
|
3047
|
-
if (!
|
|
3048
|
-
return JSON.parse(
|
|
3101
|
+
if (!fs17.existsSync(CHECK_FILE)) return null;
|
|
3102
|
+
return JSON.parse(fs17.readFileSync(CHECK_FILE, "utf-8"));
|
|
3049
3103
|
} catch {
|
|
3050
3104
|
return null;
|
|
3051
3105
|
}
|
|
3052
3106
|
}
|
|
3053
3107
|
function writeCachedCheck(result) {
|
|
3054
3108
|
try {
|
|
3055
|
-
const dir =
|
|
3056
|
-
if (!
|
|
3057
|
-
|
|
3109
|
+
const dir = path20.dirname(CHECK_FILE);
|
|
3110
|
+
if (!fs17.existsSync(dir)) {
|
|
3111
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
3058
3112
|
}
|
|
3059
|
-
|
|
3113
|
+
fs17.writeFileSync(CHECK_FILE, JSON.stringify(result, null, 2) + "\n");
|
|
3060
3114
|
} catch {
|
|
3061
3115
|
}
|
|
3062
3116
|
}
|