claude-code-arcane 2.7.1 → 2.9.1
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 +46 -0
- package/README.md +2 -2
- 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 -47
- 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 +487 -322
- 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/hooks/statusline.sh +17 -4
- package/output-styles/arcane.md +53 -0
- package/package.json +2 -1
- package/profiles/backend-go.yaml +1 -0
- package/profiles/core.yaml +4 -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/rules/code-comments.md +63 -0
- package/rules/gamedev/audio-code.md +0 -1
- package/rules/gamedev/gameplay-code.md +0 -1
- package/rules/gamedev/shader-code.md +0 -1
- 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
|
|
@@ -26,6 +26,7 @@ function parseProfile(filePath) {
|
|
|
26
26
|
},
|
|
27
27
|
agents: raw.agents ?? [],
|
|
28
28
|
hooks: raw.hooks ?? [],
|
|
29
|
+
output_styles: raw.output_styles ?? [],
|
|
29
30
|
permissions: {
|
|
30
31
|
allow: perms.allow ?? [],
|
|
31
32
|
deny: perms.deny ?? []
|
|
@@ -67,6 +68,7 @@ function mergeProfiles(profilesDir, profileNames) {
|
|
|
67
68
|
const seenRulesGamedev = /* @__PURE__ */ new Set();
|
|
68
69
|
const seenAgents = /* @__PURE__ */ new Set();
|
|
69
70
|
const seenHooks = /* @__PURE__ */ new Set();
|
|
71
|
+
const seenOutputStyles = /* @__PURE__ */ new Set();
|
|
70
72
|
const allPermAllow = [];
|
|
71
73
|
const allPermDeny = [];
|
|
72
74
|
const skills = [];
|
|
@@ -74,6 +76,7 @@ function mergeProfiles(profilesDir, profileNames) {
|
|
|
74
76
|
const rulesGamedev = [];
|
|
75
77
|
const agents = [];
|
|
76
78
|
const hooks = [];
|
|
79
|
+
const outputStyles = [];
|
|
77
80
|
for (const name of namesToLoad) {
|
|
78
81
|
const filePath = path.join(profilesDir, `${name}.yaml`);
|
|
79
82
|
if (!fs.existsSync(filePath)) {
|
|
@@ -112,6 +115,12 @@ function mergeProfiles(profilesDir, profileNames) {
|
|
|
112
115
|
hooks.push(h);
|
|
113
116
|
}
|
|
114
117
|
}
|
|
118
|
+
for (const o of profile.output_styles) {
|
|
119
|
+
if (!seenOutputStyles.has(o)) {
|
|
120
|
+
seenOutputStyles.add(o);
|
|
121
|
+
outputStyles.push(o);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
115
124
|
allPermAllow.push(...profile.permissions.allow);
|
|
116
125
|
allPermDeny.push(...profile.permissions.deny);
|
|
117
126
|
}
|
|
@@ -121,6 +130,7 @@ function mergeProfiles(profilesDir, profileNames) {
|
|
|
121
130
|
rules: { universal: rulesUniv, gamedev: rulesGamedev },
|
|
122
131
|
agents,
|
|
123
132
|
hooks,
|
|
133
|
+
output_styles: outputStyles,
|
|
124
134
|
permissions: {
|
|
125
135
|
allow: [...new Set(allPermAllow)],
|
|
126
136
|
deny: [...new Set(allPermDeny)]
|
|
@@ -374,8 +384,8 @@ function hint(description, max = 90) {
|
|
|
374
384
|
}
|
|
375
385
|
|
|
376
386
|
// src/installer.ts
|
|
377
|
-
import
|
|
378
|
-
import
|
|
387
|
+
import fs7 from "fs";
|
|
388
|
+
import path8 from "path";
|
|
379
389
|
|
|
380
390
|
// src/utils.ts
|
|
381
391
|
import { fileURLToPath } from "url";
|
|
@@ -434,11 +444,66 @@ function safeRemove(p2) {
|
|
|
434
444
|
}
|
|
435
445
|
}
|
|
436
446
|
|
|
437
|
-
// src/
|
|
447
|
+
// src/agent-entries.ts
|
|
448
|
+
import fs4 from "fs";
|
|
438
449
|
import path4 from "path";
|
|
450
|
+
function parseAgentEntry(raw) {
|
|
451
|
+
const trimmed = raw.trim().replace(/\/+$/, "");
|
|
452
|
+
const slash = trimmed.indexOf("/");
|
|
453
|
+
if (slash < 0) return { raw: trimmed, division: trimmed };
|
|
454
|
+
return {
|
|
455
|
+
raw: trimmed,
|
|
456
|
+
division: trimmed.slice(0, slash),
|
|
457
|
+
agent: trimmed.slice(slash + 1)
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function isGranular(raw) {
|
|
461
|
+
return parseAgentEntry(raw).agent !== void 0;
|
|
462
|
+
}
|
|
463
|
+
function agentSourcePath(root, raw) {
|
|
464
|
+
const { division, agent } = parseAgentEntry(raw);
|
|
465
|
+
const p2 = agent ? path4.join(root, "agents", division, `${agent}.md`) : path4.join(root, "agents", division);
|
|
466
|
+
return fs4.existsSync(p2) ? p2 : null;
|
|
467
|
+
}
|
|
468
|
+
function copyAgentEntry(root, claudeDir, raw) {
|
|
469
|
+
const src = agentSourcePath(root, raw);
|
|
470
|
+
if (!src) return null;
|
|
471
|
+
const { division, agent } = parseAgentEntry(raw);
|
|
472
|
+
const dstDir = path4.join(claudeDir, "agents", division);
|
|
473
|
+
if (agent) {
|
|
474
|
+
ensureDir(dstDir);
|
|
475
|
+
fs4.copyFileSync(src, path4.join(dstDir, `${agent}.md`));
|
|
476
|
+
return 1;
|
|
477
|
+
}
|
|
478
|
+
copyDirSync(src, dstDir);
|
|
479
|
+
return fs4.readdirSync(dstDir, { recursive: true }).filter((f) => String(f).endsWith(".md")).length;
|
|
480
|
+
}
|
|
481
|
+
function removeAgentEntry(claudeDir, raw) {
|
|
482
|
+
const { division, agent } = parseAgentEntry(raw);
|
|
483
|
+
const dstDir = path4.join(claudeDir, "agents", division);
|
|
484
|
+
if (!agent) {
|
|
485
|
+
if (!fs4.existsSync(dstDir)) return false;
|
|
486
|
+
fs4.rmSync(dstDir, { recursive: true, force: true });
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
const file = path4.join(dstDir, `${agent}.md`);
|
|
490
|
+
if (!fs4.existsSync(file)) return false;
|
|
491
|
+
safeRemove(file);
|
|
492
|
+
if (fs4.existsSync(dstDir) && fs4.readdirSync(dstDir).length === 0) {
|
|
493
|
+
fs4.rmSync(dstDir, { recursive: true, force: true });
|
|
494
|
+
}
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
function agentEntryLabel(raw) {
|
|
498
|
+
const { division, agent } = parseAgentEntry(raw);
|
|
499
|
+
return agent ? `agents/${division}/${agent}.md` : `agents/${division}/`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/manifest.ts
|
|
503
|
+
import path5 from "path";
|
|
439
504
|
var MANIFEST_FILE = "arcane-manifest.json";
|
|
440
505
|
function manifestPath(target) {
|
|
441
|
-
return
|
|
506
|
+
return path5.join(target, ".claude", MANIFEST_FILE);
|
|
442
507
|
}
|
|
443
508
|
function readManifest(target) {
|
|
444
509
|
const p2 = manifestPath(target);
|
|
@@ -492,8 +557,8 @@ function updateManifestFields(target, updates) {
|
|
|
492
557
|
|
|
493
558
|
// src/worktree.ts
|
|
494
559
|
import { execSync } from "child_process";
|
|
495
|
-
import
|
|
496
|
-
import
|
|
560
|
+
import fs5 from "fs";
|
|
561
|
+
import path6 from "path";
|
|
497
562
|
function getWorktreeInfo(target) {
|
|
498
563
|
try {
|
|
499
564
|
execSync("git rev-parse --is-inside-work-tree", {
|
|
@@ -514,10 +579,10 @@ function getWorktreeInfo(target) {
|
|
|
514
579
|
encoding: "utf-8",
|
|
515
580
|
stdio: "pipe"
|
|
516
581
|
}).trim();
|
|
517
|
-
const resolvedCommon =
|
|
518
|
-
const resolvedGit =
|
|
582
|
+
const resolvedCommon = path6.resolve(target, gitCommonDir);
|
|
583
|
+
const resolvedGit = path6.resolve(target, gitDir);
|
|
519
584
|
const isWorktree = resolvedCommon !== resolvedGit;
|
|
520
|
-
const mainWorktreePath = isWorktree ?
|
|
585
|
+
const mainWorktreePath = isWorktree ? path6.resolve(resolvedCommon, "..") : target;
|
|
521
586
|
let currentBranch = "HEAD";
|
|
522
587
|
try {
|
|
523
588
|
currentBranch = execSync("git branch --show-current", {
|
|
@@ -532,7 +597,7 @@ function getWorktreeInfo(target) {
|
|
|
532
597
|
isWorktree,
|
|
533
598
|
mainWorktreePath,
|
|
534
599
|
currentBranch,
|
|
535
|
-
worktreeId:
|
|
600
|
+
worktreeId: path6.basename(target),
|
|
536
601
|
allWorktrees
|
|
537
602
|
};
|
|
538
603
|
}
|
|
@@ -568,17 +633,17 @@ function parseWorktreeList(cwd) {
|
|
|
568
633
|
}
|
|
569
634
|
}
|
|
570
635
|
function findMainArcaneInstall(mainPath) {
|
|
571
|
-
return
|
|
572
|
-
|
|
636
|
+
return fs5.existsSync(
|
|
637
|
+
path6.join(mainPath, ".claude", "arcane-manifest.json")
|
|
573
638
|
);
|
|
574
639
|
}
|
|
575
640
|
function linkOrCopyDir(src, dest, mode) {
|
|
576
641
|
if (mode === "symlink") {
|
|
577
642
|
try {
|
|
578
|
-
const parentDir =
|
|
579
|
-
|
|
643
|
+
const parentDir = path6.dirname(dest);
|
|
644
|
+
fs5.mkdirSync(parentDir, { recursive: true });
|
|
580
645
|
const type = process.platform === "win32" ? "junction" : "dir";
|
|
581
|
-
|
|
646
|
+
fs5.symlinkSync(src, dest, type);
|
|
582
647
|
return "linked";
|
|
583
648
|
} catch {
|
|
584
649
|
}
|
|
@@ -590,9 +655,9 @@ function sanitizeBranchForPath(branch) {
|
|
|
590
655
|
return branch.replace(/^(feat|fix|feature|hotfix)\//, "").replace(/\//g, "-").replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
591
656
|
}
|
|
592
657
|
function defaultWorktreePath(repoRoot, branch) {
|
|
593
|
-
const repoName =
|
|
658
|
+
const repoName = path6.basename(repoRoot);
|
|
594
659
|
const sanitized = sanitizeBranchForPath(branch);
|
|
595
|
-
return
|
|
660
|
+
return path6.resolve(repoRoot, "..", `${repoName}-${sanitized}`);
|
|
596
661
|
}
|
|
597
662
|
function createGitWorktree(repoRoot, worktreePath, branch, base) {
|
|
598
663
|
const branchExists = checkBranchExists(repoRoot, branch);
|
|
@@ -634,20 +699,20 @@ function detectPackageManager(dir) {
|
|
|
634
699
|
{ lockfile: "package-lock.json", command: "npm install" }
|
|
635
700
|
];
|
|
636
701
|
for (const m of managers) {
|
|
637
|
-
if (
|
|
702
|
+
if (fs5.existsSync(path6.join(dir, m.lockfile))) return m;
|
|
638
703
|
}
|
|
639
|
-
if (
|
|
704
|
+
if (fs5.existsSync(path6.join(dir, "package.json"))) {
|
|
640
705
|
return { lockfile: "package.json", command: "npm install" };
|
|
641
706
|
}
|
|
642
707
|
return null;
|
|
643
708
|
}
|
|
644
709
|
|
|
645
710
|
// src/content-hash.ts
|
|
646
|
-
import
|
|
647
|
-
import
|
|
711
|
+
import fs6 from "fs";
|
|
712
|
+
import path7 from "path";
|
|
648
713
|
import { createHash } from "crypto";
|
|
649
714
|
function hashFile(filePath) {
|
|
650
|
-
const content =
|
|
715
|
+
const content = fs6.readFileSync(filePath);
|
|
651
716
|
return createHash("sha256").update(content).digest("hex");
|
|
652
717
|
}
|
|
653
718
|
function hashDirectory(dirPath) {
|
|
@@ -656,10 +721,10 @@ function hashDirectory(dirPath) {
|
|
|
656
721
|
return hashes;
|
|
657
722
|
}
|
|
658
723
|
function walkDir(base, current, hashes) {
|
|
659
|
-
for (const entry of
|
|
724
|
+
for (const entry of fs6.readdirSync(current, { withFileTypes: true })) {
|
|
660
725
|
if (isBuildArtifact(entry.name)) continue;
|
|
661
|
-
const fullPath =
|
|
662
|
-
const relPath =
|
|
726
|
+
const fullPath = path7.join(current, entry.name);
|
|
727
|
+
const relPath = path7.relative(base, fullPath).split(path7.sep).join("/");
|
|
663
728
|
if (entry.isDirectory()) {
|
|
664
729
|
walkDir(base, fullPath, hashes);
|
|
665
730
|
} else {
|
|
@@ -674,39 +739,39 @@ function computeContentHashes(claudeDir) {
|
|
|
674
739
|
agents: {},
|
|
675
740
|
hooks: {}
|
|
676
741
|
};
|
|
677
|
-
const skillsDir =
|
|
678
|
-
if (
|
|
679
|
-
for (const entry of
|
|
742
|
+
const skillsDir = path7.join(claudeDir, "skills");
|
|
743
|
+
if (fs6.existsSync(skillsDir)) {
|
|
744
|
+
for (const entry of fs6.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
680
745
|
if (entry.isDirectory()) {
|
|
681
|
-
const dirHash = hashDirectory(
|
|
746
|
+
const dirHash = hashDirectory(path7.join(skillsDir, entry.name));
|
|
682
747
|
const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
|
|
683
748
|
hashes.skills[entry.name] = combined;
|
|
684
749
|
}
|
|
685
750
|
}
|
|
686
751
|
}
|
|
687
|
-
const rulesDir =
|
|
688
|
-
if (
|
|
689
|
-
for (const entry of
|
|
752
|
+
const rulesDir = path7.join(claudeDir, "rules");
|
|
753
|
+
if (fs6.existsSync(rulesDir)) {
|
|
754
|
+
for (const entry of fs6.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
690
755
|
if (!entry.isDirectory()) {
|
|
691
|
-
hashes.rules[entry.name] = hashFile(
|
|
756
|
+
hashes.rules[entry.name] = hashFile(path7.join(rulesDir, entry.name));
|
|
692
757
|
}
|
|
693
758
|
}
|
|
694
759
|
}
|
|
695
|
-
const agentsDir =
|
|
696
|
-
if (
|
|
697
|
-
for (const entry of
|
|
760
|
+
const agentsDir = path7.join(claudeDir, "agents");
|
|
761
|
+
if (fs6.existsSync(agentsDir)) {
|
|
762
|
+
for (const entry of fs6.readdirSync(agentsDir, { withFileTypes: true })) {
|
|
698
763
|
if (entry.isDirectory()) {
|
|
699
|
-
const dirHash = hashDirectory(
|
|
764
|
+
const dirHash = hashDirectory(path7.join(agentsDir, entry.name));
|
|
700
765
|
const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
|
|
701
766
|
hashes.agents[entry.name] = combined;
|
|
702
767
|
}
|
|
703
768
|
}
|
|
704
769
|
}
|
|
705
|
-
const hooksDir =
|
|
706
|
-
if (
|
|
707
|
-
for (const entry of
|
|
770
|
+
const hooksDir = path7.join(claudeDir, "hooks");
|
|
771
|
+
if (fs6.existsSync(hooksDir)) {
|
|
772
|
+
for (const entry of fs6.readdirSync(hooksDir, { withFileTypes: true })) {
|
|
708
773
|
if (!entry.isDirectory()) {
|
|
709
|
-
hashes.hooks[entry.name] = hashFile(
|
|
774
|
+
hashes.hooks[entry.name] = hashFile(path7.join(hooksDir, entry.name));
|
|
710
775
|
}
|
|
711
776
|
}
|
|
712
777
|
}
|
|
@@ -720,8 +785,8 @@ function computeSourceHashes(packageRoot, skills, rules, agents) {
|
|
|
720
785
|
hooks: {}
|
|
721
786
|
};
|
|
722
787
|
for (const skill of skills) {
|
|
723
|
-
const skillDir =
|
|
724
|
-
if (
|
|
788
|
+
const skillDir = path7.join(packageRoot, "skills", skill);
|
|
789
|
+
if (fs6.existsSync(skillDir)) {
|
|
725
790
|
const dirHash = hashDirectory(skillDir);
|
|
726
791
|
const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
|
|
727
792
|
hashes.skills[skill] = combined;
|
|
@@ -729,29 +794,30 @@ function computeSourceHashes(packageRoot, skills, rules, agents) {
|
|
|
729
794
|
}
|
|
730
795
|
for (const rule of rules) {
|
|
731
796
|
const ruleName = rule.endsWith(".md") ? rule : `${rule}.md`;
|
|
732
|
-
const rulePath =
|
|
733
|
-
if (
|
|
797
|
+
const rulePath = path7.join(packageRoot, "rules", ruleName);
|
|
798
|
+
if (fs6.existsSync(rulePath)) {
|
|
734
799
|
hashes.rules[ruleName] = hashFile(rulePath);
|
|
735
800
|
} else {
|
|
736
|
-
const gamedevPath =
|
|
737
|
-
if (
|
|
801
|
+
const gamedevPath = path7.join(packageRoot, "rules", "gamedev", ruleName);
|
|
802
|
+
if (fs6.existsSync(gamedevPath)) {
|
|
738
803
|
hashes.rules[ruleName] = hashFile(gamedevPath);
|
|
739
804
|
}
|
|
740
805
|
}
|
|
741
806
|
}
|
|
742
807
|
for (const agent of agents) {
|
|
743
|
-
const agentDir =
|
|
744
|
-
if (
|
|
808
|
+
const agentDir = path7.join(packageRoot, "agents", agent);
|
|
809
|
+
if (fs6.existsSync(agentDir)) {
|
|
745
810
|
const dirHash = hashDirectory(agentDir);
|
|
746
811
|
const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
|
|
747
812
|
hashes.agents[agent] = combined;
|
|
748
813
|
}
|
|
749
814
|
}
|
|
750
|
-
const hooksDir =
|
|
751
|
-
if (
|
|
752
|
-
for (const entry of
|
|
815
|
+
const hooksDir = path7.join(packageRoot, "hooks");
|
|
816
|
+
if (fs6.existsSync(hooksDir)) {
|
|
817
|
+
for (const entry of fs6.readdirSync(hooksDir, { withFileTypes: true })) {
|
|
818
|
+
if (entry.name === "statusline.sh") continue;
|
|
753
819
|
if (!entry.isDirectory()) {
|
|
754
|
-
hashes.hooks[entry.name] = hashFile(
|
|
820
|
+
hashes.hooks[entry.name] = hashFile(path7.join(hooksDir, entry.name));
|
|
755
821
|
}
|
|
756
822
|
}
|
|
757
823
|
}
|
|
@@ -920,7 +986,7 @@ var Installer = class {
|
|
|
920
986
|
constructor(merged, opts) {
|
|
921
987
|
this.root = opts.contentRoot ?? getPackageRoot();
|
|
922
988
|
this.target = opts.target;
|
|
923
|
-
this.claudeDir =
|
|
989
|
+
this.claudeDir = path8.join(opts.target, ".claude");
|
|
924
990
|
this.merged = merged;
|
|
925
991
|
this.dryRun = opts.dryRun;
|
|
926
992
|
this.shareFrom = opts.shareFrom;
|
|
@@ -935,6 +1001,7 @@ var Installer = class {
|
|
|
935
1001
|
this.copyHooks();
|
|
936
1002
|
this.copySkills();
|
|
937
1003
|
this.copyRules();
|
|
1004
|
+
this.copyOutputStyles();
|
|
938
1005
|
this.copyAgents();
|
|
939
1006
|
this.copyDocs();
|
|
940
1007
|
this.generateSettings();
|
|
@@ -952,22 +1019,25 @@ var Installer = class {
|
|
|
952
1019
|
return this.logs;
|
|
953
1020
|
}
|
|
954
1021
|
backup() {
|
|
955
|
-
if (!
|
|
956
|
-
const backup =
|
|
1022
|
+
if (!fs7.existsSync(this.claudeDir)) return;
|
|
1023
|
+
const backup = path8.join(this.target, ".claude.bak");
|
|
957
1024
|
if (this.dryRun) {
|
|
958
1025
|
this.log(" [dry-run] Would backup .claude/ -> .claude.bak/");
|
|
959
1026
|
return;
|
|
960
1027
|
}
|
|
961
|
-
if (
|
|
962
|
-
|
|
1028
|
+
if (fs7.existsSync(backup)) {
|
|
1029
|
+
fs7.rmSync(backup, { recursive: true, force: true });
|
|
963
1030
|
}
|
|
964
|
-
|
|
1031
|
+
fs7.renameSync(this.claudeDir, backup);
|
|
965
1032
|
this.log(" Backed up .claude/ -> .claude.bak/");
|
|
966
1033
|
}
|
|
967
1034
|
createDirs() {
|
|
968
1035
|
if (this.dryRun) return;
|
|
969
1036
|
for (const d of ["skills", "rules", "agents", "docs"]) {
|
|
970
|
-
ensureDir(
|
|
1037
|
+
ensureDir(path8.join(this.claudeDir, d));
|
|
1038
|
+
}
|
|
1039
|
+
if (this.merged.output_styles.length > 0) {
|
|
1040
|
+
ensureDir(path8.join(this.claudeDir, "output-styles"));
|
|
971
1041
|
}
|
|
972
1042
|
}
|
|
973
1043
|
get hasStatusline() {
|
|
@@ -975,7 +1045,7 @@ var Installer = class {
|
|
|
975
1045
|
}
|
|
976
1046
|
copyHooks() {
|
|
977
1047
|
this.log("\nHooks:");
|
|
978
|
-
const hooksDir =
|
|
1048
|
+
const hooksDir = path8.join(this.claudeDir, "hooks");
|
|
979
1049
|
if (this.dryRun) {
|
|
980
1050
|
if (this.shareFrom) {
|
|
981
1051
|
this.log(" [dry-run] hooks/ -> symlink from main worktree");
|
|
@@ -986,8 +1056,8 @@ var Installer = class {
|
|
|
986
1056
|
return;
|
|
987
1057
|
}
|
|
988
1058
|
if (this.shareFrom) {
|
|
989
|
-
const sharedHooks =
|
|
990
|
-
if (
|
|
1059
|
+
const sharedHooks = path8.join(this.shareFrom, ".claude", "hooks");
|
|
1060
|
+
if (fs7.existsSync(sharedHooks)) {
|
|
991
1061
|
const result = linkOrCopyDir(sharedHooks, hooksDir, "symlink");
|
|
992
1062
|
if (result === "linked") {
|
|
993
1063
|
this.sharedDirs.push("hooks");
|
|
@@ -999,17 +1069,17 @@ var Installer = class {
|
|
|
999
1069
|
return;
|
|
1000
1070
|
}
|
|
1001
1071
|
}
|
|
1002
|
-
const hooksSrc =
|
|
1003
|
-
if (
|
|
1072
|
+
const hooksSrc = path8.join(this.root, "hooks");
|
|
1073
|
+
if (fs7.existsSync(hooksSrc)) {
|
|
1004
1074
|
ensureDir(hooksDir);
|
|
1005
|
-
for (const entry of
|
|
1075
|
+
for (const entry of fs7.readdirSync(hooksSrc, { withFileTypes: true })) {
|
|
1006
1076
|
if (entry.name === "statusline.sh") continue;
|
|
1007
|
-
const src =
|
|
1008
|
-
const dst =
|
|
1077
|
+
const src = path8.join(hooksSrc, entry.name);
|
|
1078
|
+
const dst = path8.join(hooksDir, entry.name);
|
|
1009
1079
|
if (entry.isDirectory()) {
|
|
1010
1080
|
copyDirSync(src, dst);
|
|
1011
1081
|
} else {
|
|
1012
|
-
|
|
1082
|
+
fs7.copyFileSync(src, dst);
|
|
1013
1083
|
}
|
|
1014
1084
|
}
|
|
1015
1085
|
this.log(" [ok] hooks/");
|
|
@@ -1018,11 +1088,11 @@ var Installer = class {
|
|
|
1018
1088
|
}
|
|
1019
1089
|
copyStatusline() {
|
|
1020
1090
|
if (!this.hasStatusline) return;
|
|
1021
|
-
const statusline =
|
|
1022
|
-
if (
|
|
1023
|
-
|
|
1091
|
+
const statusline = path8.join(this.root, "hooks", "statusline.sh");
|
|
1092
|
+
if (fs7.existsSync(statusline)) {
|
|
1093
|
+
fs7.copyFileSync(
|
|
1024
1094
|
statusline,
|
|
1025
|
-
|
|
1095
|
+
path8.join(this.claudeDir, "statusline.sh")
|
|
1026
1096
|
);
|
|
1027
1097
|
this.log(" [ok] statusline.sh (statusline profile)");
|
|
1028
1098
|
}
|
|
@@ -1030,12 +1100,12 @@ var Installer = class {
|
|
|
1030
1100
|
copySkills() {
|
|
1031
1101
|
this.log("\nSkills:");
|
|
1032
1102
|
for (const skill of this.merged.skills) {
|
|
1033
|
-
const src =
|
|
1034
|
-
if (!
|
|
1103
|
+
const src = path8.join(this.root, "skills", skill);
|
|
1104
|
+
if (!fs7.existsSync(src)) {
|
|
1035
1105
|
this.log(` WARN: Skill '${skill}' not found in skills/`);
|
|
1036
1106
|
continue;
|
|
1037
1107
|
}
|
|
1038
|
-
const dst =
|
|
1108
|
+
const dst = path8.join(this.claudeDir, "skills", skill);
|
|
1039
1109
|
if (this.dryRun) {
|
|
1040
1110
|
this.log(` [dry-run] ${skill}`);
|
|
1041
1111
|
} else {
|
|
@@ -1045,9 +1115,9 @@ var Installer = class {
|
|
|
1045
1115
|
}
|
|
1046
1116
|
const hasGamedev = this.merged.rules.gamedev.length > 0;
|
|
1047
1117
|
if (hasGamedev) {
|
|
1048
|
-
const tplSrc =
|
|
1049
|
-
if (
|
|
1050
|
-
const tplDst =
|
|
1118
|
+
const tplSrc = path8.join(this.root, "templates", "gamedev");
|
|
1119
|
+
if (fs7.existsSync(tplSrc)) {
|
|
1120
|
+
const tplDst = path8.join(this.claudeDir, "skills", "_templates");
|
|
1051
1121
|
if (this.dryRun) {
|
|
1052
1122
|
this.log(" [dry-run] _templates/");
|
|
1053
1123
|
} else {
|
|
@@ -1060,61 +1130,79 @@ var Installer = class {
|
|
|
1060
1130
|
copyRules() {
|
|
1061
1131
|
this.log("\nRules:");
|
|
1062
1132
|
for (const rule of this.merged.rules.universal) {
|
|
1063
|
-
const src =
|
|
1064
|
-
if (!
|
|
1133
|
+
const src = path8.join(this.root, "rules", `${rule}.md`);
|
|
1134
|
+
if (!fs7.existsSync(src)) {
|
|
1065
1135
|
this.log(` WARN: Rule '${rule}.md' not found`);
|
|
1066
1136
|
continue;
|
|
1067
1137
|
}
|
|
1068
1138
|
if (this.dryRun) {
|
|
1069
1139
|
this.log(` [dry-run] ${rule}.md`);
|
|
1070
1140
|
} else {
|
|
1071
|
-
|
|
1141
|
+
fs7.copyFileSync(
|
|
1072
1142
|
src,
|
|
1073
|
-
|
|
1143
|
+
path8.join(this.claudeDir, "rules", `${rule}.md`)
|
|
1074
1144
|
);
|
|
1075
1145
|
this.log(` [ok] ${rule}.md`);
|
|
1076
1146
|
}
|
|
1077
1147
|
}
|
|
1078
1148
|
for (const rule of this.merged.rules.gamedev) {
|
|
1079
|
-
const src =
|
|
1080
|
-
if (!
|
|
1149
|
+
const src = path8.join(this.root, "rules", "gamedev", `${rule}.md`);
|
|
1150
|
+
if (!fs7.existsSync(src)) {
|
|
1081
1151
|
this.log(` WARN: Rule '${rule}.md' not found`);
|
|
1082
1152
|
continue;
|
|
1083
1153
|
}
|
|
1084
1154
|
if (this.dryRun) {
|
|
1085
1155
|
this.log(` [dry-run] ${rule}.md`);
|
|
1086
1156
|
} else {
|
|
1087
|
-
|
|
1157
|
+
fs7.copyFileSync(
|
|
1088
1158
|
src,
|
|
1089
|
-
|
|
1159
|
+
path8.join(this.claudeDir, "rules", `${rule}.md`)
|
|
1090
1160
|
);
|
|
1091
1161
|
this.log(` [ok] ${rule}.md`);
|
|
1092
1162
|
}
|
|
1093
1163
|
}
|
|
1094
1164
|
}
|
|
1165
|
+
copyOutputStyles() {
|
|
1166
|
+
if (this.merged.output_styles.length === 0) return;
|
|
1167
|
+
this.log("\nOutput styles:");
|
|
1168
|
+
for (const style of this.merged.output_styles) {
|
|
1169
|
+
const src = path8.join(this.root, "output-styles", `${style}.md`);
|
|
1170
|
+
if (!fs7.existsSync(src)) {
|
|
1171
|
+
this.log(` WARN: Output style '${style}.md' not found`);
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
if (this.dryRun) {
|
|
1175
|
+
this.log(` [dry-run] ${style}.md`);
|
|
1176
|
+
} else {
|
|
1177
|
+
fs7.copyFileSync(
|
|
1178
|
+
src,
|
|
1179
|
+
path8.join(this.claudeDir, "output-styles", `${style}.md`)
|
|
1180
|
+
);
|
|
1181
|
+
this.log(` [ok] ${style}.md`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1095
1185
|
copyAgents() {
|
|
1096
1186
|
if (this.merged.agents.length === 0) return;
|
|
1097
1187
|
this.log("\nAgents:");
|
|
1098
|
-
for (const
|
|
1099
|
-
const
|
|
1100
|
-
if (!
|
|
1101
|
-
this.log(` WARN: Agent
|
|
1188
|
+
for (const entry of this.merged.agents) {
|
|
1189
|
+
const label = agentEntryLabel(entry);
|
|
1190
|
+
if (!agentSourcePath(this.root, entry)) {
|
|
1191
|
+
this.log(` WARN: Agent entry '${entry}' not found`);
|
|
1102
1192
|
continue;
|
|
1103
1193
|
}
|
|
1104
|
-
const dst = path7.join(this.claudeDir, "agents", agentDir);
|
|
1105
1194
|
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)`);
|
|
1195
|
+
this.log(` [dry-run] ${label}`);
|
|
1196
|
+
continue;
|
|
1111
1197
|
}
|
|
1198
|
+
const count = copyAgentEntry(this.root, this.claudeDir, entry);
|
|
1199
|
+
this.log(` [ok] ${label}${count === 1 ? "" : ` (${count} agents)`}`);
|
|
1112
1200
|
}
|
|
1113
1201
|
}
|
|
1114
1202
|
copyDocs() {
|
|
1115
|
-
const docsDir =
|
|
1116
|
-
const docsSrc =
|
|
1117
|
-
if (!
|
|
1203
|
+
const docsDir = path8.join(this.claudeDir, "docs");
|
|
1204
|
+
const docsSrc = path8.join(this.root, "docs");
|
|
1205
|
+
if (!fs7.existsSync(docsSrc)) return;
|
|
1118
1206
|
this.log("\nDocs:");
|
|
1119
1207
|
if (this.dryRun) {
|
|
1120
1208
|
if (this.shareFrom) {
|
|
@@ -1125,8 +1213,8 @@ var Installer = class {
|
|
|
1125
1213
|
return;
|
|
1126
1214
|
}
|
|
1127
1215
|
if (this.shareFrom) {
|
|
1128
|
-
const sharedDocs =
|
|
1129
|
-
if (
|
|
1216
|
+
const sharedDocs = path8.join(this.shareFrom, ".claude", "docs");
|
|
1217
|
+
if (fs7.existsSync(sharedDocs)) {
|
|
1130
1218
|
const result = linkOrCopyDir(sharedDocs, docsDir, "symlink");
|
|
1131
1219
|
if (result === "linked") {
|
|
1132
1220
|
this.sharedDirs.push("docs");
|
|
@@ -1151,14 +1239,18 @@ var Installer = class {
|
|
|
1151
1239
|
command: "bash .claude/statusline.sh"
|
|
1152
1240
|
};
|
|
1153
1241
|
}
|
|
1242
|
+
const activeStyle = this.merged.output_styles[0];
|
|
1243
|
+
if (activeStyle) {
|
|
1244
|
+
settings.outputStyle = activeStyle;
|
|
1245
|
+
}
|
|
1154
1246
|
if (this.dryRun) {
|
|
1155
1247
|
this.log(
|
|
1156
1248
|
` [dry-run] settings.json (${this.merged.permissions.allow.length} allow, ${this.merged.permissions.deny.length} deny)`
|
|
1157
1249
|
);
|
|
1158
1250
|
return;
|
|
1159
1251
|
}
|
|
1160
|
-
const settingsPath =
|
|
1161
|
-
|
|
1252
|
+
const settingsPath = path8.join(this.claudeDir, "settings.json");
|
|
1253
|
+
fs7.writeFileSync(
|
|
1162
1254
|
settingsPath,
|
|
1163
1255
|
JSON.stringify(settings, null, 2) + "\n",
|
|
1164
1256
|
"utf-8"
|
|
@@ -1167,15 +1259,15 @@ var Installer = class {
|
|
|
1167
1259
|
}
|
|
1168
1260
|
};
|
|
1169
1261
|
function clean(target) {
|
|
1170
|
-
const claudeDir =
|
|
1171
|
-
const backup =
|
|
1172
|
-
if (!
|
|
1262
|
+
const claudeDir = path8.join(target, ".claude");
|
|
1263
|
+
const backup = path8.join(target, ".claude.bak");
|
|
1264
|
+
if (!fs7.existsSync(claudeDir)) {
|
|
1173
1265
|
console.log(`No .claude/ found in ${target} \u2014 nothing to clean.`);
|
|
1174
1266
|
return;
|
|
1175
1267
|
}
|
|
1176
|
-
const manifestFile =
|
|
1177
|
-
if (
|
|
1178
|
-
const data = JSON.parse(
|
|
1268
|
+
const manifestFile = path8.join(claudeDir, "arcane-manifest.json");
|
|
1269
|
+
if (fs7.existsSync(manifestFile)) {
|
|
1270
|
+
const data = JSON.parse(fs7.readFileSync(manifestFile, "utf-8"));
|
|
1179
1271
|
const profiles = (data.profiles ?? []).filter(
|
|
1180
1272
|
(p2) => p2 !== "core"
|
|
1181
1273
|
);
|
|
@@ -1183,49 +1275,49 @@ function clean(target) {
|
|
|
1183
1275
|
console.log(` Installed: ${data.installed_at ?? "unknown"}`);
|
|
1184
1276
|
const shared = data.worktree?.shared_dirs ?? [];
|
|
1185
1277
|
for (const dir of shared) {
|
|
1186
|
-
const p2 =
|
|
1278
|
+
const p2 = path8.join(claudeDir, dir);
|
|
1187
1279
|
if (isSymlinkOrJunction(p2)) {
|
|
1188
|
-
|
|
1280
|
+
fs7.unlinkSync(p2);
|
|
1189
1281
|
console.log(` [ok] Unlinked shared ${dir}/`);
|
|
1190
1282
|
}
|
|
1191
1283
|
}
|
|
1192
1284
|
}
|
|
1193
1285
|
for (const sub of ["hooks", "docs", "skills", "agents", "rules"]) {
|
|
1194
|
-
const p2 =
|
|
1286
|
+
const p2 = path8.join(claudeDir, sub);
|
|
1195
1287
|
safeRemove(p2);
|
|
1196
1288
|
}
|
|
1197
|
-
const remaining =
|
|
1289
|
+
const remaining = fs7.readdirSync(claudeDir);
|
|
1198
1290
|
for (const f of remaining) {
|
|
1199
|
-
const p2 =
|
|
1291
|
+
const p2 = path8.join(claudeDir, f);
|
|
1200
1292
|
safeRemove(p2);
|
|
1201
1293
|
}
|
|
1202
|
-
if (
|
|
1203
|
-
|
|
1294
|
+
if (fs7.existsSync(claudeDir)) {
|
|
1295
|
+
fs7.rmSync(claudeDir, { recursive: true, force: true });
|
|
1204
1296
|
}
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1297
|
+
if (fs7.existsSync(backup)) {
|
|
1298
|
+
fs7.rmSync(backup, { recursive: true, force: true });
|
|
1207
1299
|
}
|
|
1208
1300
|
console.log(`
|
|
1209
1301
|
[ok] Removed .claude/ from ${target}`);
|
|
1210
1302
|
}
|
|
1211
1303
|
|
|
1212
1304
|
// src/content-source.ts
|
|
1213
|
-
import
|
|
1214
|
-
import
|
|
1305
|
+
import fs9 from "fs";
|
|
1306
|
+
import path10 from "path";
|
|
1215
1307
|
import os2 from "os";
|
|
1216
1308
|
|
|
1217
1309
|
// src/cache.ts
|
|
1218
|
-
import
|
|
1219
|
-
import
|
|
1310
|
+
import fs8 from "fs";
|
|
1311
|
+
import path9 from "path";
|
|
1220
1312
|
import os from "os";
|
|
1221
|
-
var ARCANE_DIR =
|
|
1222
|
-
var CACHE_DIR =
|
|
1313
|
+
var ARCANE_DIR = path9.join(os.homedir(), ".arcane");
|
|
1314
|
+
var CACHE_DIR = path9.join(ARCANE_DIR, "cache");
|
|
1223
1315
|
function getCachePath(version) {
|
|
1224
|
-
return
|
|
1316
|
+
return path9.join(CACHE_DIR, version);
|
|
1225
1317
|
}
|
|
1226
1318
|
function isCached(version) {
|
|
1227
1319
|
const versionDir = getCachePath(version);
|
|
1228
|
-
return
|
|
1320
|
+
return fs8.existsSync(versionDir) && fs8.existsSync(path9.join(versionDir, ".cache-meta.json"));
|
|
1229
1321
|
}
|
|
1230
1322
|
function getCachedContentRoot(version) {
|
|
1231
1323
|
if (!isCached(version)) return null;
|
|
@@ -1233,34 +1325,34 @@ function getCachedContentRoot(version) {
|
|
|
1233
1325
|
}
|
|
1234
1326
|
function storeInCache(version, contentDir) {
|
|
1235
1327
|
const versionDir = getCachePath(version);
|
|
1236
|
-
if (
|
|
1237
|
-
|
|
1328
|
+
if (fs8.existsSync(versionDir)) {
|
|
1329
|
+
fs8.rmSync(versionDir, { recursive: true, force: true });
|
|
1238
1330
|
}
|
|
1239
|
-
|
|
1240
|
-
for (const dir of ["skills", "rules", "agents", "hooks", "profiles", "templates", "docs"]) {
|
|
1241
|
-
const src =
|
|
1242
|
-
if (
|
|
1243
|
-
copyDirRecursive(src,
|
|
1331
|
+
fs8.mkdirSync(versionDir, { recursive: true });
|
|
1332
|
+
for (const dir of ["skills", "rules", "agents", "hooks", "profiles", "templates", "docs", "output-styles"]) {
|
|
1333
|
+
const src = path9.join(contentDir, dir);
|
|
1334
|
+
if (fs8.existsSync(src)) {
|
|
1335
|
+
copyDirRecursive(src, path9.join(versionDir, dir));
|
|
1244
1336
|
}
|
|
1245
1337
|
}
|
|
1246
1338
|
const meta = {
|
|
1247
1339
|
version,
|
|
1248
1340
|
cached_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1249
1341
|
};
|
|
1250
|
-
|
|
1251
|
-
|
|
1342
|
+
fs8.writeFileSync(
|
|
1343
|
+
path9.join(versionDir, ".cache-meta.json"),
|
|
1252
1344
|
JSON.stringify(meta, null, 2) + "\n"
|
|
1253
1345
|
);
|
|
1254
1346
|
return versionDir;
|
|
1255
1347
|
}
|
|
1256
1348
|
function pruneCache(keepCount = 3) {
|
|
1257
|
-
if (!
|
|
1258
|
-
const entries =
|
|
1259
|
-
const metaPath =
|
|
1349
|
+
if (!fs8.existsSync(CACHE_DIR)) return;
|
|
1350
|
+
const entries = fs8.readdirSync(CACHE_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
|
|
1351
|
+
const metaPath = path9.join(CACHE_DIR, e.name, ".cache-meta.json");
|
|
1260
1352
|
let cachedAt = 0;
|
|
1261
|
-
if (
|
|
1353
|
+
if (fs8.existsSync(metaPath)) {
|
|
1262
1354
|
try {
|
|
1263
|
-
const meta = JSON.parse(
|
|
1355
|
+
const meta = JSON.parse(fs8.readFileSync(metaPath, "utf-8"));
|
|
1264
1356
|
cachedAt = new Date(meta.cached_at).getTime();
|
|
1265
1357
|
} catch {
|
|
1266
1358
|
cachedAt = 0;
|
|
@@ -1269,18 +1361,18 @@ function pruneCache(keepCount = 3) {
|
|
|
1269
1361
|
return { name: e.name, cachedAt };
|
|
1270
1362
|
}).sort((a, b) => b.cachedAt - a.cachedAt);
|
|
1271
1363
|
for (const entry of entries.slice(keepCount)) {
|
|
1272
|
-
|
|
1364
|
+
fs8.rmSync(path9.join(CACHE_DIR, entry.name), { recursive: true, force: true });
|
|
1273
1365
|
}
|
|
1274
1366
|
}
|
|
1275
1367
|
function copyDirRecursive(src, dest) {
|
|
1276
|
-
|
|
1277
|
-
for (const entry of
|
|
1278
|
-
const srcPath =
|
|
1279
|
-
const destPath =
|
|
1368
|
+
fs8.mkdirSync(dest, { recursive: true });
|
|
1369
|
+
for (const entry of fs8.readdirSync(src, { withFileTypes: true })) {
|
|
1370
|
+
const srcPath = path9.join(src, entry.name);
|
|
1371
|
+
const destPath = path9.join(dest, entry.name);
|
|
1280
1372
|
if (entry.isDirectory()) {
|
|
1281
1373
|
copyDirRecursive(srcPath, destPath);
|
|
1282
1374
|
} else {
|
|
1283
|
-
|
|
1375
|
+
fs8.copyFileSync(srcPath, destPath);
|
|
1284
1376
|
}
|
|
1285
1377
|
}
|
|
1286
1378
|
}
|
|
@@ -1296,7 +1388,7 @@ var BundledContentSource = class {
|
|
|
1296
1388
|
}
|
|
1297
1389
|
async isAvailable() {
|
|
1298
1390
|
const root = getPackageRoot();
|
|
1299
|
-
return
|
|
1391
|
+
return fs9.existsSync(path10.join(root, "skills"));
|
|
1300
1392
|
}
|
|
1301
1393
|
async getVersion() {
|
|
1302
1394
|
return getPackageVersion();
|
|
@@ -1336,14 +1428,14 @@ var GitHubContentSource = class {
|
|
|
1336
1428
|
if (isCached(version)) {
|
|
1337
1429
|
return getCachedContentRoot(version);
|
|
1338
1430
|
}
|
|
1339
|
-
const tmpDir =
|
|
1431
|
+
const tmpDir = fs9.mkdtempSync(path10.join(os2.tmpdir(), "arcane-github-"));
|
|
1340
1432
|
try {
|
|
1341
1433
|
await this.downloadAndExtract(tmpDir);
|
|
1342
1434
|
const contentRoot = storeInCache(version, this.findExtractedRoot(tmpDir));
|
|
1343
1435
|
pruneCache(3);
|
|
1344
1436
|
return contentRoot;
|
|
1345
1437
|
} finally {
|
|
1346
|
-
|
|
1438
|
+
fs9.rmSync(tmpDir, { recursive: true, force: true });
|
|
1347
1439
|
}
|
|
1348
1440
|
}
|
|
1349
1441
|
async isAvailable() {
|
|
@@ -1399,8 +1491,8 @@ var GitHubContentSource = class {
|
|
|
1399
1491
|
throw new Error(`Failed to download tarball: ${response.status}`);
|
|
1400
1492
|
}
|
|
1401
1493
|
const tar = await import("tar");
|
|
1402
|
-
const tarballPath =
|
|
1403
|
-
const fileStream =
|
|
1494
|
+
const tarballPath = path10.join(destDir, "repo.tar.gz");
|
|
1495
|
+
const fileStream = fs9.createWriteStream(tarballPath);
|
|
1404
1496
|
const reader = response.body.getReader();
|
|
1405
1497
|
const writable = new WritableStream({
|
|
1406
1498
|
write(chunk) {
|
|
@@ -1425,12 +1517,12 @@ var GitHubContentSource = class {
|
|
|
1425
1517
|
});
|
|
1426
1518
|
}
|
|
1427
1519
|
findExtractedRoot(tmpDir) {
|
|
1428
|
-
const entries =
|
|
1520
|
+
const entries = fs9.readdirSync(tmpDir, { withFileTypes: true });
|
|
1429
1521
|
const dirs = entries.filter(
|
|
1430
1522
|
(e) => e.isDirectory() && e.name !== "." && e.name !== ".."
|
|
1431
1523
|
);
|
|
1432
1524
|
if (dirs.length === 1) {
|
|
1433
|
-
return
|
|
1525
|
+
return path10.join(tmpDir, dirs[0].name);
|
|
1434
1526
|
}
|
|
1435
1527
|
return tmpDir;
|
|
1436
1528
|
}
|
|
@@ -1485,14 +1577,14 @@ async function resolveContentSourceForVersion(version, opts = {}) {
|
|
|
1485
1577
|
return source;
|
|
1486
1578
|
}
|
|
1487
1579
|
function findLatestCache() {
|
|
1488
|
-
const cacheDir =
|
|
1489
|
-
if (!
|
|
1490
|
-
const entries =
|
|
1491
|
-
const metaPath =
|
|
1580
|
+
const cacheDir = path10.join(os2.homedir(), ".arcane", "cache");
|
|
1581
|
+
if (!fs9.existsSync(cacheDir)) return null;
|
|
1582
|
+
const entries = fs9.readdirSync(cacheDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
|
|
1583
|
+
const metaPath = path10.join(cacheDir, e.name, ".cache-meta.json");
|
|
1492
1584
|
let cachedAt = 0;
|
|
1493
|
-
if (
|
|
1585
|
+
if (fs9.existsSync(metaPath)) {
|
|
1494
1586
|
try {
|
|
1495
|
-
const meta = JSON.parse(
|
|
1587
|
+
const meta = JSON.parse(fs9.readFileSync(metaPath, "utf-8"));
|
|
1496
1588
|
cachedAt = new Date(meta.cached_at).getTime();
|
|
1497
1589
|
} catch {
|
|
1498
1590
|
cachedAt = 0;
|
|
@@ -1504,14 +1596,14 @@ function findLatestCache() {
|
|
|
1504
1596
|
}
|
|
1505
1597
|
|
|
1506
1598
|
// src/registry.ts
|
|
1507
|
-
import
|
|
1599
|
+
import path11 from "path";
|
|
1508
1600
|
import os3 from "os";
|
|
1509
1601
|
var REGISTRY_VERSION = 1;
|
|
1510
1602
|
function arcaneHome() {
|
|
1511
|
-
return process.env.ARCANE_HOME ??
|
|
1603
|
+
return process.env.ARCANE_HOME ?? path11.join(os3.homedir(), ".arcane");
|
|
1512
1604
|
}
|
|
1513
1605
|
function registryPath() {
|
|
1514
|
-
return
|
|
1606
|
+
return path11.join(arcaneHome(), "installations.json");
|
|
1515
1607
|
}
|
|
1516
1608
|
function readRegistryFile() {
|
|
1517
1609
|
const p2 = registryPath();
|
|
@@ -1536,7 +1628,7 @@ function writeRegistryFile(registry) {
|
|
|
1536
1628
|
}
|
|
1537
1629
|
}
|
|
1538
1630
|
function registerInstallation(target) {
|
|
1539
|
-
const abs =
|
|
1631
|
+
const abs = path11.resolve(target);
|
|
1540
1632
|
const registry = readRegistryFile();
|
|
1541
1633
|
if (registry.installations.some((e) => e.path === abs)) {
|
|
1542
1634
|
return;
|
|
@@ -1563,8 +1655,8 @@ async function installCommand(profileExpr, opts) {
|
|
|
1563
1655
|
quiet: !profileExpr
|
|
1564
1656
|
});
|
|
1565
1657
|
const root = await source.getContentRoot();
|
|
1566
|
-
const profilesDir =
|
|
1567
|
-
const target =
|
|
1658
|
+
const profilesDir = path12.join(root, "profiles");
|
|
1659
|
+
const target = path12.resolve(opts.target ?? process.cwd());
|
|
1568
1660
|
if (!profileExpr) {
|
|
1569
1661
|
const profiles = listProfiles(profilesDir);
|
|
1570
1662
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
@@ -1635,21 +1727,21 @@ Installing profile: ${chalk2.cyan(profileExpr)}`)
|
|
|
1635
1727
|
}
|
|
1636
1728
|
|
|
1637
1729
|
// src/commands/add.ts
|
|
1638
|
-
import
|
|
1639
|
-
import
|
|
1730
|
+
import fs11 from "fs";
|
|
1731
|
+
import path14 from "path";
|
|
1640
1732
|
import chalk3 from "chalk";
|
|
1641
1733
|
|
|
1642
1734
|
// src/skills-catalog.ts
|
|
1643
|
-
import
|
|
1644
|
-
import
|
|
1735
|
+
import fs10 from "fs";
|
|
1736
|
+
import path13 from "path";
|
|
1645
1737
|
var FRONTMATTER_BYTES = 4096;
|
|
1646
1738
|
function listSkills(skillsDir) {
|
|
1647
|
-
if (!
|
|
1648
|
-
return
|
|
1649
|
-
(d) => d.isDirectory() && !d.name.startsWith("_") &&
|
|
1739
|
+
if (!fs10.existsSync(skillsDir)) return [];
|
|
1740
|
+
return fs10.readdirSync(skillsDir, { withFileTypes: true }).filter(
|
|
1741
|
+
(d) => d.isDirectory() && !d.name.startsWith("_") && fs10.existsSync(path13.join(skillsDir, d.name, "SKILL.md"))
|
|
1650
1742
|
).map((d) => ({
|
|
1651
1743
|
name: d.name,
|
|
1652
|
-
description: readDescription(
|
|
1744
|
+
description: readDescription(path13.join(skillsDir, d.name, "SKILL.md"))
|
|
1653
1745
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
1654
1746
|
}
|
|
1655
1747
|
function readDescription(skillFile) {
|
|
@@ -1670,13 +1762,13 @@ function readDescription(skillFile) {
|
|
|
1670
1762
|
return inline.replace(/^["']|["']$/g, "").trim();
|
|
1671
1763
|
}
|
|
1672
1764
|
function readHead(file) {
|
|
1673
|
-
const fd =
|
|
1765
|
+
const fd = fs10.openSync(file, "r");
|
|
1674
1766
|
try {
|
|
1675
1767
|
const buf = Buffer.alloc(FRONTMATTER_BYTES);
|
|
1676
|
-
const read =
|
|
1768
|
+
const read = fs10.readSync(fd, buf, 0, FRONTMATTER_BYTES, 0);
|
|
1677
1769
|
return buf.subarray(0, read).toString("utf-8");
|
|
1678
1770
|
} finally {
|
|
1679
|
-
|
|
1771
|
+
fs10.closeSync(fd);
|
|
1680
1772
|
}
|
|
1681
1773
|
}
|
|
1682
1774
|
|
|
@@ -1700,15 +1792,15 @@ async function addCommand(items = []) {
|
|
|
1700
1792
|
return;
|
|
1701
1793
|
}
|
|
1702
1794
|
const selected = await runAddWizard({
|
|
1703
|
-
profiles: listProfiles(
|
|
1704
|
-
skills: listSkills(
|
|
1795
|
+
profiles: listProfiles(path14.join(root, "profiles")),
|
|
1796
|
+
skills: listSkills(path14.join(root, "skills")),
|
|
1705
1797
|
installedProfiles: manifest.profiles,
|
|
1706
1798
|
installedSkills: manifest.installed_skills
|
|
1707
1799
|
});
|
|
1708
1800
|
if (!selected) return;
|
|
1709
1801
|
items = selected;
|
|
1710
1802
|
}
|
|
1711
|
-
const claudeDir =
|
|
1803
|
+
const claudeDir = path14.join(target, ".claude");
|
|
1712
1804
|
const added = [];
|
|
1713
1805
|
const skipped = [];
|
|
1714
1806
|
const notFound = [];
|
|
@@ -1718,8 +1810,8 @@ async function addCommand(items = []) {
|
|
|
1718
1810
|
for (const item of items) {
|
|
1719
1811
|
if (item.startsWith("+")) {
|
|
1720
1812
|
const profileName = item.slice(1);
|
|
1721
|
-
const profilePath =
|
|
1722
|
-
if (!
|
|
1813
|
+
const profilePath = path14.join(root, "profiles", `${profileName}.yaml`);
|
|
1814
|
+
if (!fs11.existsSync(profilePath)) {
|
|
1723
1815
|
console.error(chalk3.red(` Profile '${profileName}' not found`));
|
|
1724
1816
|
continue;
|
|
1725
1817
|
}
|
|
@@ -1732,10 +1824,10 @@ async function addCommand(items = []) {
|
|
|
1732
1824
|
}
|
|
1733
1825
|
for (const rule of profile.rules.universal) {
|
|
1734
1826
|
if (!manifest.installed_rules.includes(rule)) {
|
|
1735
|
-
const src =
|
|
1736
|
-
if (
|
|
1737
|
-
ensureDir(
|
|
1738
|
-
|
|
1827
|
+
const src = path14.join(root, "rules", `${rule}.md`);
|
|
1828
|
+
if (fs11.existsSync(src)) {
|
|
1829
|
+
ensureDir(path14.join(claudeDir, "rules"));
|
|
1830
|
+
fs11.copyFileSync(src, path14.join(claudeDir, "rules", `${rule}.md`));
|
|
1739
1831
|
manifest.installed_rules.push(rule);
|
|
1740
1832
|
addedRules.push(rule);
|
|
1741
1833
|
}
|
|
@@ -1743,30 +1835,28 @@ async function addCommand(items = []) {
|
|
|
1743
1835
|
}
|
|
1744
1836
|
for (const rule of profile.rules.gamedev) {
|
|
1745
1837
|
if (!manifest.installed_rules.includes(rule)) {
|
|
1746
|
-
const src =
|
|
1747
|
-
if (
|
|
1748
|
-
ensureDir(
|
|
1749
|
-
|
|
1838
|
+
const src = path14.join(root, "rules", "gamedev", `${rule}.md`);
|
|
1839
|
+
if (fs11.existsSync(src)) {
|
|
1840
|
+
ensureDir(path14.join(claudeDir, "rules"));
|
|
1841
|
+
fs11.copyFileSync(src, path14.join(claudeDir, "rules", `${rule}.md`));
|
|
1750
1842
|
manifest.installed_rules.push(rule);
|
|
1751
1843
|
addedRules.push(rule);
|
|
1752
1844
|
}
|
|
1753
1845
|
}
|
|
1754
1846
|
}
|
|
1755
|
-
for (const
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
addedAgents.push(agentDir);
|
|
1763
|
-
}
|
|
1847
|
+
for (const entry of profile.agents) {
|
|
1848
|
+
const { division } = parseAgentEntry(entry);
|
|
1849
|
+
if (manifest.installed_agents.includes(entry)) continue;
|
|
1850
|
+
if (isGranular(entry) && manifest.installed_agents.includes(division)) continue;
|
|
1851
|
+
if (copyAgentEntry(root, claudeDir, entry) !== null) {
|
|
1852
|
+
manifest.installed_agents.push(entry);
|
|
1853
|
+
addedAgents.push(entry);
|
|
1764
1854
|
}
|
|
1765
1855
|
}
|
|
1766
1856
|
if (profileName === "statusline") {
|
|
1767
|
-
const statuslineSrc =
|
|
1768
|
-
if (
|
|
1769
|
-
|
|
1857
|
+
const statuslineSrc = path14.join(root, "hooks", "statusline.sh");
|
|
1858
|
+
if (fs11.existsSync(statuslineSrc)) {
|
|
1859
|
+
fs11.copyFileSync(statuslineSrc, path14.join(claudeDir, "statusline.sh"));
|
|
1770
1860
|
statuslineAdded = true;
|
|
1771
1861
|
}
|
|
1772
1862
|
}
|
|
@@ -1796,6 +1886,7 @@ async function addCommand(items = []) {
|
|
|
1796
1886
|
rules: { universal: manifest.installed_rules, gamedev: [] },
|
|
1797
1887
|
agents: manifest.installed_agents,
|
|
1798
1888
|
hooks: [],
|
|
1889
|
+
output_styles: [],
|
|
1799
1890
|
permissions: { allow: [], deny: [] }
|
|
1800
1891
|
};
|
|
1801
1892
|
writeManifest(target, merged, manifest.profile_command, root);
|
|
@@ -1808,7 +1899,7 @@ Added ${totalAdded} items:`
|
|
|
1808
1899
|
);
|
|
1809
1900
|
for (const s of added) console.log(chalk3.green(` [ok] skill: ${s}`));
|
|
1810
1901
|
for (const r of addedRules) console.log(chalk3.green(` [ok] rule: ${r}`));
|
|
1811
|
-
for (const a of addedAgents) console.log(chalk3.green(` [ok]
|
|
1902
|
+
for (const a of addedAgents) console.log(chalk3.green(` [ok] ${agentEntryLabel(a)}`));
|
|
1812
1903
|
if (statuslineAdded) console.log(chalk3.green(" [ok] statusline.sh"));
|
|
1813
1904
|
for (const s of skipped)
|
|
1814
1905
|
console.log(chalk3.dim(` [skip] ${s} (already installed)`));
|
|
@@ -1824,10 +1915,10 @@ Added ${totalAdded} items:`
|
|
|
1824
1915
|
}
|
|
1825
1916
|
}
|
|
1826
1917
|
function printCatalog(root, installedProfiles, installedSkills) {
|
|
1827
|
-
const profiles = listProfiles(
|
|
1918
|
+
const profiles = listProfiles(path14.join(root, "profiles")).filter(
|
|
1828
1919
|
(pr) => !installedProfiles.includes(pr.name)
|
|
1829
1920
|
);
|
|
1830
|
-
const skills = listSkills(
|
|
1921
|
+
const skills = listSkills(path14.join(root, "skills")).filter(
|
|
1831
1922
|
(sk) => !installedSkills.includes(sk.name)
|
|
1832
1923
|
);
|
|
1833
1924
|
if (profiles.length === 0 && skills.length === 0) {
|
|
@@ -1853,15 +1944,15 @@ function printCatalog(root, installedProfiles, installedSkills) {
|
|
|
1853
1944
|
}
|
|
1854
1945
|
function addSkill(root, target, skill, installed) {
|
|
1855
1946
|
if (installed.includes(skill)) return "skipped";
|
|
1856
|
-
const src =
|
|
1857
|
-
if (!
|
|
1858
|
-
const dst =
|
|
1947
|
+
const src = path14.join(root, "skills", skill);
|
|
1948
|
+
if (!fs11.existsSync(src)) return "not-found";
|
|
1949
|
+
const dst = path14.join(target, ".claude", "skills", skill);
|
|
1859
1950
|
copyDirSync(src, dst);
|
|
1860
1951
|
return "added";
|
|
1861
1952
|
}
|
|
1862
1953
|
function mergePermissions(claudeDir, newPerms) {
|
|
1863
|
-
const settingsPath =
|
|
1864
|
-
if (!
|
|
1954
|
+
const settingsPath = path14.join(claudeDir, "settings.json");
|
|
1955
|
+
if (!fs11.existsSync(settingsPath)) return;
|
|
1865
1956
|
const settings = readJsonSync(settingsPath);
|
|
1866
1957
|
const perms = settings.permissions ?? { allow: [], deny: [] };
|
|
1867
1958
|
const allowSet = new Set(perms.allow);
|
|
@@ -1874,8 +1965,8 @@ function mergePermissions(claudeDir, newPerms) {
|
|
|
1874
1965
|
writeJsonSync(settingsPath, settings);
|
|
1875
1966
|
}
|
|
1876
1967
|
function addStatuslineToSettings(claudeDir) {
|
|
1877
|
-
const settingsPath =
|
|
1878
|
-
if (!
|
|
1968
|
+
const settingsPath = path14.join(claudeDir, "settings.json");
|
|
1969
|
+
if (!fs11.existsSync(settingsPath)) return;
|
|
1879
1970
|
const settings = readJsonSync(settingsPath);
|
|
1880
1971
|
if (settings.statusLine) return;
|
|
1881
1972
|
settings.statusLine = {
|
|
@@ -1886,8 +1977,8 @@ function addStatuslineToSettings(claudeDir) {
|
|
|
1886
1977
|
}
|
|
1887
1978
|
|
|
1888
1979
|
// src/commands/remove.ts
|
|
1889
|
-
import
|
|
1890
|
-
import
|
|
1980
|
+
import fs12 from "fs";
|
|
1981
|
+
import path15 from "path";
|
|
1891
1982
|
import chalk4 from "chalk";
|
|
1892
1983
|
var CORE_SKILLS = [
|
|
1893
1984
|
"commit",
|
|
@@ -1952,7 +2043,7 @@ async function removeCommand(items, opts = {}) {
|
|
|
1952
2043
|
}
|
|
1953
2044
|
manifest.total_skills = manifest.installed_skills.length;
|
|
1954
2045
|
manifest.total_rules = manifest.installed_rules.length;
|
|
1955
|
-
const mp =
|
|
2046
|
+
const mp = path15.join(target, ".claude", "arcane-manifest.json");
|
|
1956
2047
|
writeJsonSync(mp, manifest);
|
|
1957
2048
|
const totalRemoved = removedSkills.length + removedAgents.length + removedProfiles.length;
|
|
1958
2049
|
console.log(chalk4.bold(`
|
|
@@ -1973,9 +2064,9 @@ function removeSkill(target, skill, manifest) {
|
|
|
1973
2064
|
);
|
|
1974
2065
|
return "skipped";
|
|
1975
2066
|
}
|
|
1976
|
-
const skillDir =
|
|
1977
|
-
if (!
|
|
1978
|
-
|
|
2067
|
+
const skillDir = path15.join(target, ".claude", "skills", skill);
|
|
2068
|
+
if (!fs12.existsSync(skillDir)) return "skipped";
|
|
2069
|
+
fs12.rmSync(skillDir, { recursive: true, force: true });
|
|
1979
2070
|
manifest.installed_skills = manifest.installed_skills.filter(
|
|
1980
2071
|
(s) => s !== skill
|
|
1981
2072
|
);
|
|
@@ -1994,8 +2085,8 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
1994
2085
|
);
|
|
1995
2086
|
return { removed: false, skills: [], agents: [] };
|
|
1996
2087
|
}
|
|
1997
|
-
const profilePath =
|
|
1998
|
-
if (!
|
|
2088
|
+
const profilePath = path15.join(root, "profiles", `${profileName}.yaml`);
|
|
2089
|
+
if (!fs12.existsSync(profilePath)) {
|
|
1999
2090
|
console.warn(
|
|
2000
2091
|
chalk4.yellow(` WARNING: Profile '${profileName}.yaml' not found.`)
|
|
2001
2092
|
);
|
|
@@ -2009,8 +2100,8 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
2009
2100
|
const sharedAgents = /* @__PURE__ */ new Set();
|
|
2010
2101
|
const sharedRules = /* @__PURE__ */ new Set();
|
|
2011
2102
|
for (const rp of remainingProfiles) {
|
|
2012
|
-
const rpPath =
|
|
2013
|
-
if (!
|
|
2103
|
+
const rpPath = path15.join(root, "profiles", `${rp}.yaml`);
|
|
2104
|
+
if (!fs12.existsSync(rpPath)) continue;
|
|
2014
2105
|
const rpDef = parseProfile(rpPath);
|
|
2015
2106
|
rpDef.skills.forEach((s) => sharedSkills.add(s));
|
|
2016
2107
|
rpDef.agents.forEach((a) => sharedAgents.add(a));
|
|
@@ -2021,9 +2112,9 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
2021
2112
|
for (const skill of profile.skills) {
|
|
2022
2113
|
if (sharedSkills.has(skill)) continue;
|
|
2023
2114
|
if (CORE_SKILLS.includes(skill)) continue;
|
|
2024
|
-
const skillDir =
|
|
2025
|
-
if (
|
|
2026
|
-
|
|
2115
|
+
const skillDir = path15.join(target, ".claude", "skills", skill);
|
|
2116
|
+
if (fs12.existsSync(skillDir)) {
|
|
2117
|
+
fs12.rmSync(skillDir, { recursive: true, force: true });
|
|
2027
2118
|
removedSkills.push(skill);
|
|
2028
2119
|
}
|
|
2029
2120
|
manifest.installed_skills = manifest.installed_skills.filter(
|
|
@@ -2031,15 +2122,14 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
2031
2122
|
);
|
|
2032
2123
|
}
|
|
2033
2124
|
const removedAgents = [];
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
}
|
|
2125
|
+
const claudeDir = path15.join(target, ".claude");
|
|
2126
|
+
for (const entry of profile.agents) {
|
|
2127
|
+
if (sharedAgents.has(entry)) continue;
|
|
2128
|
+
const { division } = parseAgentEntry(entry);
|
|
2129
|
+
if (isGranular(entry) && sharedAgents.has(division)) continue;
|
|
2130
|
+
if (removeAgentEntry(claudeDir, entry)) removedAgents.push(entry);
|
|
2041
2131
|
manifest.installed_agents = manifest.installed_agents.filter(
|
|
2042
|
-
(a) => a !==
|
|
2132
|
+
(a) => a !== entry
|
|
2043
2133
|
);
|
|
2044
2134
|
}
|
|
2045
2135
|
for (const rule of [
|
|
@@ -2047,44 +2137,44 @@ function removeProfile(root, target, profileName, manifest) {
|
|
|
2047
2137
|
...profile.rules.gamedev
|
|
2048
2138
|
]) {
|
|
2049
2139
|
if (sharedRules.has(rule)) continue;
|
|
2050
|
-
const ruleFile =
|
|
2051
|
-
if (
|
|
2052
|
-
|
|
2140
|
+
const ruleFile = path15.join(target, ".claude", "rules", `${rule}.md`);
|
|
2141
|
+
if (fs12.existsSync(ruleFile)) {
|
|
2142
|
+
fs12.rmSync(ruleFile);
|
|
2053
2143
|
}
|
|
2054
2144
|
manifest.installed_rules = manifest.installed_rules.filter(
|
|
2055
2145
|
(r) => r !== rule
|
|
2056
2146
|
);
|
|
2057
2147
|
}
|
|
2058
2148
|
if (profileName === "statusline") {
|
|
2059
|
-
const statuslineFile =
|
|
2060
|
-
if (
|
|
2061
|
-
|
|
2149
|
+
const statuslineFile = path15.join(target, ".claude", "statusline.sh");
|
|
2150
|
+
if (fs12.existsSync(statuslineFile)) {
|
|
2151
|
+
fs12.rmSync(statuslineFile);
|
|
2062
2152
|
}
|
|
2063
|
-
removeStatuslineFromSettings(
|
|
2153
|
+
removeStatuslineFromSettings(path15.join(target, ".claude"));
|
|
2064
2154
|
}
|
|
2065
2155
|
manifest.profiles = remainingProfiles;
|
|
2066
2156
|
manifest.profile_command = remainingProfiles.filter((p2) => p2 !== "core").join("+");
|
|
2067
2157
|
return { removed: true, skills: removedSkills, agents: removedAgents };
|
|
2068
2158
|
}
|
|
2069
2159
|
function removeStatuslineFromSettings(claudeDir) {
|
|
2070
|
-
const settingsPath =
|
|
2071
|
-
if (!
|
|
2072
|
-
const settings = JSON.parse(
|
|
2160
|
+
const settingsPath = path15.join(claudeDir, "settings.json");
|
|
2161
|
+
if (!fs12.existsSync(settingsPath)) return;
|
|
2162
|
+
const settings = JSON.parse(fs12.readFileSync(settingsPath, "utf-8"));
|
|
2073
2163
|
if (settings.statusLine) {
|
|
2074
2164
|
delete settings.statusLine;
|
|
2075
|
-
|
|
2165
|
+
fs12.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
2076
2166
|
}
|
|
2077
2167
|
}
|
|
2078
2168
|
|
|
2079
2169
|
// src/commands/list.ts
|
|
2080
|
-
import
|
|
2081
|
-
import
|
|
2170
|
+
import fs13 from "fs";
|
|
2171
|
+
import path16 from "path";
|
|
2082
2172
|
import chalk5 from "chalk";
|
|
2083
2173
|
async function listCommand() {
|
|
2084
2174
|
const source = await resolveContentSource({ quiet: true });
|
|
2085
2175
|
const root = await source.getContentRoot();
|
|
2086
|
-
const profilesDir =
|
|
2087
|
-
const skillsDir =
|
|
2176
|
+
const profilesDir = path16.join(root, "profiles");
|
|
2177
|
+
const skillsDir = path16.join(root, "skills");
|
|
2088
2178
|
const target = process.cwd();
|
|
2089
2179
|
const manifest = readManifest(target);
|
|
2090
2180
|
const profiles = listProfiles(profilesDir);
|
|
@@ -2101,7 +2191,7 @@ async function listCommand() {
|
|
|
2101
2191
|
}
|
|
2102
2192
|
console.log();
|
|
2103
2193
|
}
|
|
2104
|
-
if (
|
|
2194
|
+
if (fs13.existsSync(skillsDir)) {
|
|
2105
2195
|
const allSkills = listSkills(skillsDir).map((s) => s.name);
|
|
2106
2196
|
console.log(chalk5.bold(`
|
|
2107
2197
|
=== Skills (${allSkills.length}) ===
|
|
@@ -2168,8 +2258,8 @@ async function statusCommand() {
|
|
|
2168
2258
|
}
|
|
2169
2259
|
|
|
2170
2260
|
// src/commands/update.ts
|
|
2171
|
-
import
|
|
2172
|
-
import
|
|
2261
|
+
import fs14 from "fs";
|
|
2262
|
+
import path17 from "path";
|
|
2173
2263
|
import os4 from "os";
|
|
2174
2264
|
import chalk7 from "chalk";
|
|
2175
2265
|
|
|
@@ -2245,12 +2335,12 @@ async function updateCommand(opts) {
|
|
|
2245
2335
|
}
|
|
2246
2336
|
}
|
|
2247
2337
|
const cwd = process.cwd();
|
|
2248
|
-
if (
|
|
2338
|
+
if (fs14.existsSync(manifestPath(cwd))) {
|
|
2249
2339
|
registerInstallation(cwd);
|
|
2250
2340
|
}
|
|
2251
2341
|
const targets = pruneRegistry().map((e) => e.path);
|
|
2252
2342
|
const globalTarget = os4.homedir();
|
|
2253
|
-
if (!process.env.VITEST &&
|
|
2343
|
+
if (!process.env.VITEST && fs14.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
|
|
2254
2344
|
targets.push(globalTarget);
|
|
2255
2345
|
}
|
|
2256
2346
|
if (targets.length === 0) {
|
|
@@ -2305,16 +2395,34 @@ async function updateTarget(target, opts) {
|
|
|
2305
2395
|
const root = await contentSource.getContentRoot();
|
|
2306
2396
|
const currentVersion = await contentSource.getVersion();
|
|
2307
2397
|
const installedVersion = manifest.source_version ?? manifest.arcane_version;
|
|
2398
|
+
const profileNamesForSync = manifest.profile_command.split("+").filter(Boolean);
|
|
2399
|
+
const claudeDirForSync = path17.join(target, ".claude");
|
|
2400
|
+
const earlySync = syncUnhashedFiles(
|
|
2401
|
+
root,
|
|
2402
|
+
claudeDirForSync,
|
|
2403
|
+
mergeProfiles(path17.join(root, "profiles"), profileNamesForSync),
|
|
2404
|
+
opts.dryRun ?? false
|
|
2405
|
+
);
|
|
2406
|
+
if (earlySync.length > 0 && !opts.quiet) {
|
|
2407
|
+
const prefix = opts.dryRun ? "[dry-run] " : "";
|
|
2408
|
+
console.log(`
|
|
2409
|
+
${prefix}${chalk7.cyan("Refreshed:")}`);
|
|
2410
|
+
for (const label of earlySync) {
|
|
2411
|
+
console.log(` ~ ${label}`);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2308
2414
|
if (installedVersion === currentVersion && !opts.force) {
|
|
2309
2415
|
if (!opts.quiet) {
|
|
2310
|
-
console.log(
|
|
2416
|
+
console.log(
|
|
2417
|
+
earlySync.length > 0 ? chalk7.green(`Refreshed ${earlySync.length} file(s); otherwise up to date (v${currentVersion}).`) : chalk7.green(`Already up to date (v${currentVersion}).`)
|
|
2418
|
+
);
|
|
2311
2419
|
}
|
|
2312
2420
|
return {
|
|
2313
2421
|
target,
|
|
2314
|
-
status: "up-to-date",
|
|
2422
|
+
status: earlySync.length > 0 ? "updated" : "up-to-date",
|
|
2315
2423
|
fromVersion: installedVersion,
|
|
2316
2424
|
toVersion: currentVersion,
|
|
2317
|
-
updated:
|
|
2425
|
+
updated: earlySync.length,
|
|
2318
2426
|
skipped: 0,
|
|
2319
2427
|
removed: 0
|
|
2320
2428
|
};
|
|
@@ -2326,12 +2434,13 @@ Update available: ${chalk7.red(installedVersion)} -> ${chalk7.green(currentVersi
|
|
|
2326
2434
|
);
|
|
2327
2435
|
}
|
|
2328
2436
|
const profileNames = manifest.profile_command.split("+").filter(Boolean);
|
|
2329
|
-
const profilesDir =
|
|
2437
|
+
const profilesDir = path17.join(root, "profiles");
|
|
2330
2438
|
const merged = mergeProfiles(profilesDir, profileNames);
|
|
2331
2439
|
const allRules = [...merged.rules.universal, ...merged.rules.gamedev];
|
|
2332
2440
|
const sourceHashes = computeSourceHashes(root, merged.skills, allRules, merged.agents);
|
|
2333
|
-
const claudeDir =
|
|
2441
|
+
const claudeDir = path17.join(target, ".claude");
|
|
2334
2442
|
const installedHashes = computeContentHashes(claudeDir);
|
|
2443
|
+
const unhashed = earlySync;
|
|
2335
2444
|
const items = computeUpdatePlan(
|
|
2336
2445
|
manifest.content_hashes ?? null,
|
|
2337
2446
|
installedHashes,
|
|
@@ -2340,10 +2449,11 @@ Update available: ${chalk7.red(installedVersion)} -> ${chalk7.green(currentVersi
|
|
|
2340
2449
|
const updates = items.filter((i) => i.action === "update" || i.action === "add" || i.action === "conflict");
|
|
2341
2450
|
const skipped = items.filter((i) => i.action === "skip-customized" || i.action === "skip-unchanged");
|
|
2342
2451
|
const removed = items.filter((i) => i.action === "remove");
|
|
2452
|
+
const forcible = opts.force ? items.filter((i) => i.action === "skip-customized") : [];
|
|
2343
2453
|
if (!opts.quiet) {
|
|
2344
2454
|
printUpdateSummary(items, opts.dryRun ?? false);
|
|
2345
2455
|
}
|
|
2346
|
-
if (updates.length === 0 && removed.length === 0) {
|
|
2456
|
+
if (updates.length === 0 && removed.length === 0 && forcible.length === 0) {
|
|
2347
2457
|
if (!opts.dryRun) {
|
|
2348
2458
|
updateManifestFields(target, {
|
|
2349
2459
|
arcane_version: currentVersion,
|
|
@@ -2358,7 +2468,10 @@ Update available: ${chalk7.red(installedVersion)} -> ${chalk7.green(currentVersi
|
|
|
2358
2468
|
});
|
|
2359
2469
|
}
|
|
2360
2470
|
if (!opts.quiet) {
|
|
2361
|
-
console.log(
|
|
2471
|
+
console.log(
|
|
2472
|
+
unhashed.length > 0 ? chalk7.green(`
|
|
2473
|
+
Refreshed ${unhashed.length} file(s); no other changes to apply.`) : chalk7.green("\nNo changes to apply.")
|
|
2474
|
+
);
|
|
2362
2475
|
}
|
|
2363
2476
|
return {
|
|
2364
2477
|
target,
|
|
@@ -2520,51 +2633,103 @@ function applyUpdates(items, root, claudeDir, merged, force) {
|
|
|
2520
2633
|
}
|
|
2521
2634
|
}
|
|
2522
2635
|
}
|
|
2636
|
+
function syncUnhashedFiles(root, claudeDir, merged, dryRun) {
|
|
2637
|
+
const targets = [];
|
|
2638
|
+
const statuslineDst = path17.join(claudeDir, "statusline.sh");
|
|
2639
|
+
if (merged.loaded.includes("statusline") || fs14.existsSync(statuslineDst)) {
|
|
2640
|
+
targets.push({
|
|
2641
|
+
src: path17.join(root, "hooks", "statusline.sh"),
|
|
2642
|
+
dst: statuslineDst,
|
|
2643
|
+
label: "statusline.sh"
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
for (const style of merged.output_styles) {
|
|
2647
|
+
targets.push({
|
|
2648
|
+
src: path17.join(root, "output-styles", `${style}.md`),
|
|
2649
|
+
dst: path17.join(claudeDir, "output-styles", `${style}.md`),
|
|
2650
|
+
label: `output-styles/${style}.md`
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
const changed = [];
|
|
2654
|
+
for (const t of targets) {
|
|
2655
|
+
if (!fs14.existsSync(t.src)) continue;
|
|
2656
|
+
const src = fs14.readFileSync(t.src);
|
|
2657
|
+
if (fs14.existsSync(t.dst) && src.equals(fs14.readFileSync(t.dst))) continue;
|
|
2658
|
+
changed.push(t.label);
|
|
2659
|
+
if (dryRun) continue;
|
|
2660
|
+
if (fs14.existsSync(t.dst)) {
|
|
2661
|
+
const bak = `${t.dst}.bak`;
|
|
2662
|
+
if (fs14.existsSync(bak)) fs14.rmSync(bak, { force: true });
|
|
2663
|
+
fs14.renameSync(t.dst, bak);
|
|
2664
|
+
}
|
|
2665
|
+
fs14.mkdirSync(path17.dirname(t.dst), { recursive: true });
|
|
2666
|
+
fs14.copyFileSync(t.src, t.dst);
|
|
2667
|
+
}
|
|
2668
|
+
const activeStyle = merged.output_styles[0];
|
|
2669
|
+
if (activeStyle) {
|
|
2670
|
+
const settingsPath = path17.join(claudeDir, "settings.json");
|
|
2671
|
+
if (fs14.existsSync(settingsPath)) {
|
|
2672
|
+
try {
|
|
2673
|
+
const settings = JSON.parse(fs14.readFileSync(settingsPath, "utf-8"));
|
|
2674
|
+
if (settings.outputStyle !== activeStyle) {
|
|
2675
|
+
changed.push(`settings.json (outputStyle: ${activeStyle})`);
|
|
2676
|
+
if (!dryRun) {
|
|
2677
|
+
settings.outputStyle = activeStyle;
|
|
2678
|
+
fs14.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
2679
|
+
`, "utf-8");
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
} catch {
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
return changed;
|
|
2687
|
+
}
|
|
2523
2688
|
function applyItem(item, root, claudeDir) {
|
|
2524
2689
|
const src = getSourcePath(item, root);
|
|
2525
2690
|
const dst = getInstalledPath(item, claudeDir);
|
|
2526
|
-
if (!src || !
|
|
2691
|
+
if (!src || !fs14.existsSync(src)) return;
|
|
2527
2692
|
if (item.type === "skill" || item.type === "agent") {
|
|
2528
|
-
if (
|
|
2529
|
-
|
|
2693
|
+
if (fs14.existsSync(dst)) {
|
|
2694
|
+
fs14.rmSync(dst, { recursive: true, force: true });
|
|
2530
2695
|
}
|
|
2531
2696
|
copyDirSync(src, dst);
|
|
2532
2697
|
} else {
|
|
2533
|
-
const dir =
|
|
2534
|
-
if (!
|
|
2535
|
-
|
|
2698
|
+
const dir = path17.dirname(dst);
|
|
2699
|
+
if (!fs14.existsSync(dir)) {
|
|
2700
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
2536
2701
|
}
|
|
2537
|
-
|
|
2702
|
+
fs14.copyFileSync(src, dst);
|
|
2538
2703
|
}
|
|
2539
2704
|
}
|
|
2540
2705
|
function backupItem(item, claudeDir) {
|
|
2541
2706
|
const installed = getInstalledPath(item, claudeDir);
|
|
2542
|
-
if (!
|
|
2707
|
+
if (!fs14.existsSync(installed)) return;
|
|
2543
2708
|
const backupPath = installed + ".bak";
|
|
2544
|
-
if (
|
|
2545
|
-
|
|
2709
|
+
if (fs14.existsSync(backupPath)) {
|
|
2710
|
+
fs14.rmSync(backupPath, { recursive: true, force: true });
|
|
2546
2711
|
}
|
|
2547
|
-
|
|
2712
|
+
fs14.renameSync(installed, backupPath);
|
|
2548
2713
|
}
|
|
2549
2714
|
function removeItem(item, claudeDir) {
|
|
2550
2715
|
const installed = getInstalledPath(item, claudeDir);
|
|
2551
|
-
if (
|
|
2552
|
-
|
|
2716
|
+
if (fs14.existsSync(installed)) {
|
|
2717
|
+
fs14.rmSync(installed, { recursive: true, force: true });
|
|
2553
2718
|
}
|
|
2554
2719
|
}
|
|
2555
2720
|
function getSourcePath(item, root) {
|
|
2556
2721
|
switch (item.type) {
|
|
2557
2722
|
case "skill":
|
|
2558
|
-
return
|
|
2723
|
+
return path17.join(root, "skills", item.name);
|
|
2559
2724
|
case "rule": {
|
|
2560
|
-
const direct =
|
|
2561
|
-
if (
|
|
2562
|
-
return
|
|
2725
|
+
const direct = path17.join(root, "rules", item.name);
|
|
2726
|
+
if (fs14.existsSync(direct)) return direct;
|
|
2727
|
+
return path17.join(root, "rules", "gamedev", item.name);
|
|
2563
2728
|
}
|
|
2564
2729
|
case "agent":
|
|
2565
|
-
return
|
|
2730
|
+
return path17.join(root, "agents", item.name);
|
|
2566
2731
|
case "hook":
|
|
2567
|
-
return
|
|
2732
|
+
return path17.join(root, "hooks", item.name);
|
|
2568
2733
|
default:
|
|
2569
2734
|
return null;
|
|
2570
2735
|
}
|
|
@@ -2572,13 +2737,13 @@ function getSourcePath(item, root) {
|
|
|
2572
2737
|
function getInstalledPath(item, claudeDir) {
|
|
2573
2738
|
switch (item.type) {
|
|
2574
2739
|
case "skill":
|
|
2575
|
-
return
|
|
2740
|
+
return path17.join(claudeDir, "skills", item.name);
|
|
2576
2741
|
case "rule":
|
|
2577
|
-
return
|
|
2742
|
+
return path17.join(claudeDir, "rules", item.name);
|
|
2578
2743
|
case "agent":
|
|
2579
|
-
return
|
|
2744
|
+
return path17.join(claudeDir, "agents", item.name);
|
|
2580
2745
|
case "hook":
|
|
2581
|
-
return
|
|
2746
|
+
return path17.join(claudeDir, "hooks", item.name);
|
|
2582
2747
|
}
|
|
2583
2748
|
}
|
|
2584
2749
|
|
|
@@ -2613,8 +2778,8 @@ async function cleanCommand(opts) {
|
|
|
2613
2778
|
}
|
|
2614
2779
|
|
|
2615
2780
|
// src/commands/worktree.ts
|
|
2616
|
-
import
|
|
2617
|
-
import
|
|
2781
|
+
import fs15 from "fs";
|
|
2782
|
+
import path18 from "path";
|
|
2618
2783
|
import { execSync as execSync2 } from "child_process";
|
|
2619
2784
|
import chalk9 from "chalk";
|
|
2620
2785
|
async function worktreeCommand(branch, opts) {
|
|
@@ -2625,8 +2790,8 @@ async function worktreeCommand(branch, opts) {
|
|
|
2625
2790
|
process.exit(1);
|
|
2626
2791
|
}
|
|
2627
2792
|
const repoRoot = wtInfo.isWorktree ? wtInfo.mainWorktreePath : cwd;
|
|
2628
|
-
const worktreePath = opts.path ?
|
|
2629
|
-
if (
|
|
2793
|
+
const worktreePath = opts.path ? path18.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
|
|
2794
|
+
if (fs15.existsSync(worktreePath)) {
|
|
2630
2795
|
console.error(
|
|
2631
2796
|
chalk9.red(`Path already exists: ${worktreePath}`)
|
|
2632
2797
|
);
|
|
@@ -2673,7 +2838,7 @@ async function worktreeCommand(branch, opts) {
|
|
|
2673
2838
|
}
|
|
2674
2839
|
console.log(" Installing Arcane...");
|
|
2675
2840
|
const root = getPackageRoot();
|
|
2676
|
-
const profilesDir =
|
|
2841
|
+
const profilesDir = path18.join(root, "profiles");
|
|
2677
2842
|
const profileNames = profileExpr.split("+").filter(Boolean);
|
|
2678
2843
|
const merged = mergeProfiles(profilesDir, profileNames);
|
|
2679
2844
|
const shareFrom = opts.share !== false ? cwd : void 0;
|
|
@@ -2709,13 +2874,13 @@ async function worktreeCommand(branch, opts) {
|
|
|
2709
2874
|
}
|
|
2710
2875
|
}
|
|
2711
2876
|
if (opts.isolate) {
|
|
2712
|
-
const isolateScript =
|
|
2877
|
+
const isolateScript = path18.join(
|
|
2713
2878
|
worktreePath,
|
|
2714
2879
|
".claude",
|
|
2715
2880
|
"skills",
|
|
2716
2881
|
"worktree-isolation"
|
|
2717
2882
|
);
|
|
2718
|
-
if (
|
|
2883
|
+
if (fs15.existsSync(isolateScript)) {
|
|
2719
2884
|
console.log(" Applying worktree isolation...");
|
|
2720
2885
|
try {
|
|
2721
2886
|
execSync2(
|
|
@@ -2750,13 +2915,13 @@ async function worktreeCommand(branch, opts) {
|
|
|
2750
2915
|
}
|
|
2751
2916
|
|
|
2752
2917
|
// src/commands/global.ts
|
|
2753
|
-
import
|
|
2754
|
-
import
|
|
2918
|
+
import fs16 from "fs";
|
|
2919
|
+
import path19 from "path";
|
|
2755
2920
|
import os5 from "os";
|
|
2756
2921
|
import chalk10 from "chalk";
|
|
2757
|
-
var CLAUDE_HOME =
|
|
2758
|
-
var SCRIPTS_DIR =
|
|
2759
|
-
var SETTINGS_PATH =
|
|
2922
|
+
var CLAUDE_HOME = path19.join(os5.homedir(), ".claude");
|
|
2923
|
+
var SCRIPTS_DIR = path19.join(CLAUDE_HOME, "scripts", "worktree-isolation");
|
|
2924
|
+
var SETTINGS_PATH = path19.join(CLAUDE_HOME, "settings.json");
|
|
2760
2925
|
async function globalCommand(opts) {
|
|
2761
2926
|
if (opts.status) {
|
|
2762
2927
|
showStatus();
|
|
@@ -2770,8 +2935,8 @@ async function globalCommand(opts) {
|
|
|
2770
2935
|
}
|
|
2771
2936
|
function showStatus() {
|
|
2772
2937
|
console.log(chalk10.bold("\n=== Arcane Global Status ===\n"));
|
|
2773
|
-
const hasScripts =
|
|
2774
|
-
|
|
2938
|
+
const hasScripts = fs16.existsSync(
|
|
2939
|
+
path19.join(SCRIPTS_DIR, "apply.py")
|
|
2775
2940
|
);
|
|
2776
2941
|
console.log(
|
|
2777
2942
|
` Worktree scripts: ${hasScripts ? chalk10.green("installed") : chalk10.dim("not installed")}`
|
|
@@ -2789,32 +2954,32 @@ function showStatus() {
|
|
|
2789
2954
|
function installGlobal() {
|
|
2790
2955
|
console.log(chalk10.bold("\nInstalling Arcane global hooks...\n"));
|
|
2791
2956
|
const root = getPackageRoot();
|
|
2792
|
-
const scriptsSrc =
|
|
2957
|
+
const scriptsSrc = path19.join(
|
|
2793
2958
|
root,
|
|
2794
2959
|
"skills",
|
|
2795
2960
|
"worktree-isolation",
|
|
2796
2961
|
"scripts"
|
|
2797
2962
|
);
|
|
2798
|
-
if (!
|
|
2963
|
+
if (!fs16.existsSync(scriptsSrc)) {
|
|
2799
2964
|
console.error(
|
|
2800
2965
|
chalk10.red(" worktree-isolation scripts not found in package.")
|
|
2801
2966
|
);
|
|
2802
2967
|
process.exit(1);
|
|
2803
2968
|
}
|
|
2804
|
-
|
|
2969
|
+
fs16.mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
2805
2970
|
for (const file of ["apply.py", "lib.py"]) {
|
|
2806
|
-
const src =
|
|
2807
|
-
const dst =
|
|
2808
|
-
if (!
|
|
2971
|
+
const src = path19.join(scriptsSrc, file);
|
|
2972
|
+
const dst = path19.join(SCRIPTS_DIR, file);
|
|
2973
|
+
if (!fs16.existsSync(src)) {
|
|
2809
2974
|
console.warn(chalk10.yellow(` WARN: ${file} not found, skipping`));
|
|
2810
2975
|
continue;
|
|
2811
2976
|
}
|
|
2812
|
-
if (
|
|
2977
|
+
if (fs16.existsSync(dst) && fs16.readFileSync(src).equals(fs16.readFileSync(dst))) {
|
|
2813
2978
|
console.log(chalk10.dim(` [skip] ${file} (already up-to-date)`));
|
|
2814
2979
|
} else {
|
|
2815
|
-
|
|
2980
|
+
fs16.copyFileSync(src, dst);
|
|
2816
2981
|
if (process.platform !== "win32") {
|
|
2817
|
-
|
|
2982
|
+
fs16.chmodSync(dst, 493);
|
|
2818
2983
|
}
|
|
2819
2984
|
console.log(chalk10.green(` [ok] ${file} -> ${SCRIPTS_DIR}`));
|
|
2820
2985
|
}
|
|
@@ -2838,8 +3003,8 @@ function installGlobal() {
|
|
|
2838
3003
|
}
|
|
2839
3004
|
function removeGlobal() {
|
|
2840
3005
|
console.log(chalk10.bold("\nRemoving Arcane global hooks...\n"));
|
|
2841
|
-
if (
|
|
2842
|
-
|
|
3006
|
+
if (fs16.existsSync(SCRIPTS_DIR)) {
|
|
3007
|
+
fs16.rmSync(SCRIPTS_DIR, { recursive: true, force: true });
|
|
2843
3008
|
console.log(chalk10.green(" [ok] Removed worktree-isolation scripts"));
|
|
2844
3009
|
} else {
|
|
2845
3010
|
console.log(chalk10.dim(" [skip] Scripts not found"));
|
|
@@ -2853,16 +3018,16 @@ function removeGlobal() {
|
|
|
2853
3018
|
console.log(chalk10.bold.green("\n Global hooks removed.\n"));
|
|
2854
3019
|
}
|
|
2855
3020
|
function readSettings() {
|
|
2856
|
-
if (!
|
|
3021
|
+
if (!fs16.existsSync(SETTINGS_PATH)) return {};
|
|
2857
3022
|
try {
|
|
2858
|
-
return JSON.parse(
|
|
3023
|
+
return JSON.parse(fs16.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
2859
3024
|
} catch {
|
|
2860
3025
|
return {};
|
|
2861
3026
|
}
|
|
2862
3027
|
}
|
|
2863
3028
|
function writeSettings(settings) {
|
|
2864
|
-
|
|
2865
|
-
|
|
3029
|
+
fs16.mkdirSync(path19.dirname(SETTINGS_PATH), { recursive: true });
|
|
3030
|
+
fs16.writeFileSync(
|
|
2866
3031
|
SETTINGS_PATH,
|
|
2867
3032
|
JSON.stringify(settings, null, 2) + "\n",
|
|
2868
3033
|
"utf-8"
|
|
@@ -2919,11 +3084,11 @@ function removeHookFromSettings() {
|
|
|
2919
3084
|
}
|
|
2920
3085
|
|
|
2921
3086
|
// src/update-check.ts
|
|
2922
|
-
import
|
|
2923
|
-
import
|
|
3087
|
+
import fs17 from "fs";
|
|
3088
|
+
import path20 from "path";
|
|
2924
3089
|
import os6 from "os";
|
|
2925
3090
|
import chalk11 from "chalk";
|
|
2926
|
-
var CHECK_FILE =
|
|
3091
|
+
var CHECK_FILE = path20.join(os6.homedir(), ".arcane", "last-check.json");
|
|
2927
3092
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
2928
3093
|
var GITHUB_OWNER2 = "SebastianLuser";
|
|
2929
3094
|
var GITHUB_REPO2 = "Claude-Code-Arcane";
|
|
@@ -3048,19 +3213,19 @@ async function getLatestNpmVersion() {
|
|
|
3048
3213
|
}
|
|
3049
3214
|
function readCachedCheck() {
|
|
3050
3215
|
try {
|
|
3051
|
-
if (!
|
|
3052
|
-
return JSON.parse(
|
|
3216
|
+
if (!fs17.existsSync(CHECK_FILE)) return null;
|
|
3217
|
+
return JSON.parse(fs17.readFileSync(CHECK_FILE, "utf-8"));
|
|
3053
3218
|
} catch {
|
|
3054
3219
|
return null;
|
|
3055
3220
|
}
|
|
3056
3221
|
}
|
|
3057
3222
|
function writeCachedCheck(result) {
|
|
3058
3223
|
try {
|
|
3059
|
-
const dir =
|
|
3060
|
-
if (!
|
|
3061
|
-
|
|
3224
|
+
const dir = path20.dirname(CHECK_FILE);
|
|
3225
|
+
if (!fs17.existsSync(dir)) {
|
|
3226
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
3062
3227
|
}
|
|
3063
|
-
|
|
3228
|
+
fs17.writeFileSync(CHECK_FILE, JSON.stringify(result, null, 2) + "\n");
|
|
3064
3229
|
} catch {
|
|
3065
3230
|
}
|
|
3066
3231
|
}
|