claude-code-arcane 2.4.0 → 2.5.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +78 -36
  3. package/dist/cli.js +637 -478
  4. package/docs/USER-GUIDE.md +27 -53
  5. package/package.json +2 -1
  6. package/profiles/agile.yaml +1 -1
  7. package/profiles/ai.yaml +1 -1
  8. package/profiles/android-native.yaml +1 -1
  9. package/profiles/audio.yaml +1 -1
  10. package/profiles/backend-dotnet.yaml +1 -1
  11. package/profiles/backend-go.yaml +1 -1
  12. package/profiles/backend-nestjs.yaml +1 -1
  13. package/profiles/backend-nextjs.yaml +1 -1
  14. package/profiles/backend-ts.yaml +1 -1
  15. package/profiles/business.yaml +1 -1
  16. package/profiles/clevel.yaml +1 -1
  17. package/profiles/clickup.yaml +1 -1
  18. package/profiles/core.yaml +1 -1
  19. package/profiles/database.yaml +1 -1
  20. package/profiles/design.yaml +1 -1
  21. package/profiles/docs.yaml +1 -1
  22. package/profiles/ecommerce.yaml +1 -1
  23. package/profiles/finance.yaml +1 -1
  24. package/profiles/flutter.yaml +1 -1
  25. package/profiles/freelance.yaml +1 -1
  26. package/profiles/frontend.yaml +1 -1
  27. package/profiles/infra.yaml +1 -1
  28. package/profiles/integrations.yaml +1 -1
  29. package/profiles/ios-native.yaml +1 -1
  30. package/profiles/jira.yaml +1 -1
  31. package/profiles/job-hunt.yaml +1 -1
  32. package/profiles/marketing.yaml +1 -1
  33. package/profiles/mobile.yaml +1 -1
  34. package/profiles/regulatory.yaml +1 -1
  35. package/profiles/second-brain.yaml +1 -1
  36. package/profiles/security.yaml +1 -1
  37. package/profiles/self-improving.yaml +1 -1
  38. package/profiles/statusline.yaml +1 -1
  39. package/profiles/testing.yaml +1 -1
  40. package/profiles/unity-design.yaml +1 -1
  41. package/profiles/unity-dev.yaml +1 -1
  42. package/profiles/unreal-dev.yaml +1 -1
  43. package/profiles/visual-novel.yaml +1 -1
  44. package/skills/arcane-list/SKILL.md +12 -7
package/dist/cli.js CHANGED
@@ -4,8 +4,8 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/install.ts
7
- import path10 from "path";
8
- import chalk from "chalk";
7
+ import path11 from "path";
8
+ import chalk2 from "chalk";
9
9
 
10
10
  // src/profiles.ts
11
11
  import fs from "fs";
@@ -18,7 +18,7 @@ function parseProfile(filePath) {
18
18
  return {
19
19
  name: String(raw.name ?? path.basename(filePath, ".yaml")),
20
20
  description: String(raw.description ?? ""),
21
- type: raw.type ?? "addon",
21
+ category: String(raw.category ?? "other"),
22
22
  skills: raw.skills ?? [],
23
23
  rules: {
24
24
  universal: rules.universal ?? [],
@@ -32,6 +32,29 @@ function parseProfile(filePath) {
32
32
  }
33
33
  };
34
34
  }
35
+ var CATEGORY_ORDER = [
36
+ { id: "backend", label: "Backend" },
37
+ { id: "frontend", label: "Frontend & Design" },
38
+ { id: "mobile", label: "Mobile" },
39
+ { id: "gamedev", label: "Gamedev" },
40
+ { id: "platform", label: "Platform & Quality" },
41
+ { id: "management", label: "Project Management" },
42
+ { id: "business", label: "Business" },
43
+ { id: "personal", label: "Personal" },
44
+ { id: "utilities", label: "Utilities" }
45
+ ];
46
+ function groupByCategory(profiles) {
47
+ const known = new Set(CATEGORY_ORDER.map((c) => c.id));
48
+ const groups = CATEGORY_ORDER.map((c) => ({
49
+ ...c,
50
+ profiles: profiles.filter((p2) => p2.category === c.id)
51
+ }));
52
+ const rest = profiles.filter((p2) => !known.has(p2.category));
53
+ if (rest.length > 0) {
54
+ groups.push({ id: "other", label: "Other", profiles: rest });
55
+ }
56
+ return groups.filter((g) => g.profiles.length > 0);
57
+ }
35
58
  function listProfiles(profilesDir) {
36
59
  if (!fs.existsSync(profilesDir)) return [];
37
60
  return fs.readdirSync(profilesDir).filter((f) => f.endsWith(".yaml") && f !== "core.yaml").map((f) => parseProfile(path.join(profilesDir, f))).sort((a, b) => a.name.localeCompare(b.name));
@@ -105,77 +128,215 @@ function mergeProfiles(profilesDir, profileNames) {
105
128
  };
106
129
  }
107
130
 
131
+ // src/detect.ts
132
+ import fs2 from "fs";
133
+ import path2 from "path";
134
+ function hasFile(dir, rel) {
135
+ return fs2.existsSync(path2.join(dir, rel));
136
+ }
137
+ function topLevelMatch(dir, suffix) {
138
+ try {
139
+ return fs2.readdirSync(dir).find((f) => f.endsWith(suffix));
140
+ } catch {
141
+ return void 0;
142
+ }
143
+ }
144
+ function readPackageDeps(dir) {
145
+ try {
146
+ const raw = JSON.parse(
147
+ fs2.readFileSync(path2.join(dir, "package.json"), "utf-8")
148
+ );
149
+ return {
150
+ ...raw.dependencies ?? {},
151
+ ...raw.devDependencies ?? {}
152
+ };
153
+ } catch {
154
+ return {};
155
+ }
156
+ }
157
+ function detectStack(dir) {
158
+ const out = [];
159
+ const isUnity = hasFile(dir, "ProjectSettings/ProjectVersion.txt");
160
+ if (isUnity) {
161
+ out.push({ profile: "unity-dev", reason: "ProjectSettings/ProjectVersion.txt" });
162
+ }
163
+ const uproject = topLevelMatch(dir, ".uproject");
164
+ if (uproject) {
165
+ out.push({ profile: "unreal-dev", reason: uproject });
166
+ }
167
+ const isFlutter = hasFile(dir, "pubspec.yaml");
168
+ if (isFlutter) {
169
+ out.push({ profile: "flutter", reason: "pubspec.yaml" });
170
+ }
171
+ if (hasFile(dir, "go.mod")) {
172
+ out.push({ profile: "backend-go", reason: "go.mod" });
173
+ }
174
+ let isReactNative = false;
175
+ if (hasFile(dir, "package.json")) {
176
+ const deps = readPackageDeps(dir);
177
+ if (deps["@nestjs/core"]) {
178
+ out.push({ profile: "backend-nestjs", reason: "package.json: @nestjs/core" });
179
+ } else if (deps["next"]) {
180
+ out.push({ profile: "backend-nextjs", reason: "package.json: next" });
181
+ } else if (deps["react-native"] || deps["expo"]) {
182
+ isReactNative = true;
183
+ out.push({ profile: "mobile", reason: "package.json: react-native/expo" });
184
+ } else if (deps["react"] || deps["vue"] || deps["svelte"]) {
185
+ out.push({ profile: "frontend", reason: "package.json: react/vue/svelte" });
186
+ } else if (deps["fastify"] || deps["express"] || deps["koa"] || deps["hono"]) {
187
+ out.push({ profile: "backend-ts", reason: "package.json: server framework" });
188
+ } else if (deps["typescript"]) {
189
+ out.push({ profile: "backend-ts", reason: "package.json: typescript" });
190
+ }
191
+ }
192
+ if (!isUnity) {
193
+ const dotnet = topLevelMatch(dir, ".sln") ?? topLevelMatch(dir, ".csproj");
194
+ if (dotnet) {
195
+ out.push({ profile: "backend-dotnet", reason: dotnet });
196
+ }
197
+ }
198
+ if (!isFlutter && !isReactNative) {
199
+ if ((hasFile(dir, "settings.gradle") || hasFile(dir, "settings.gradle.kts")) && hasFile(dir, "app")) {
200
+ out.push({ profile: "android-native", reason: "settings.gradle + app/" });
201
+ }
202
+ const ios = topLevelMatch(dir, ".xcodeproj") ?? topLevelMatch(dir, ".xcworkspace") ?? (hasFile(dir, "Podfile") ? "Podfile" : void 0);
203
+ if (ios) {
204
+ out.push({ profile: "ios-native", reason: ios });
205
+ }
206
+ }
207
+ return out;
208
+ }
209
+
210
+ // src/wizard.ts
211
+ import * as p from "@clack/prompts";
212
+ import chalk from "chalk";
213
+ async function runInstallWizard(profiles, detections) {
214
+ p.intro(chalk.cyan("claude-code-arcane install"));
215
+ const available = new Set(profiles.map((pr) => pr.name));
216
+ const suggested = detections.filter((d) => available.has(d.profile));
217
+ if (suggested.length > 0) {
218
+ p.note(
219
+ suggested.map((d) => `${chalk.green(d.profile)} ${chalk.dim(`(${d.reason})`)}`).join("\n"),
220
+ "Detected stack \u2014 pre-selected below"
221
+ );
222
+ } else {
223
+ p.note(
224
+ "No stack detected in this directory.\nPick whatever fits your work \u2014 profiles combine freely.",
225
+ "No suggestions"
226
+ );
227
+ }
228
+ const options = {};
229
+ for (const group of groupByCategory(profiles)) {
230
+ options[group.label] = group.profiles.map((pr) => ({
231
+ value: pr.name,
232
+ label: pr.name,
233
+ hint: pr.description
234
+ }));
235
+ }
236
+ const selected = await p.groupMultiselect({
237
+ message: "Select profiles (space to toggle, enter to confirm)",
238
+ options,
239
+ initialValues: suggested.map((d) => d.profile),
240
+ required: false
241
+ });
242
+ if (p.isCancel(selected)) {
243
+ p.cancel("Install cancelled.");
244
+ return null;
245
+ }
246
+ const names = selected;
247
+ if (names.length === 0) {
248
+ p.outro(
249
+ chalk.dim(
250
+ "Nothing selected. Run again, or install directly: npx claude-code-arcane install backend-ts+testing"
251
+ )
252
+ );
253
+ return null;
254
+ }
255
+ const expr = names.join("+");
256
+ const ok = await p.confirm({
257
+ message: `Install ${chalk.cyan(expr)} (plus core, always included)?`
258
+ });
259
+ if (p.isCancel(ok) || !ok) {
260
+ p.cancel("Install cancelled.");
261
+ return null;
262
+ }
263
+ p.outro(
264
+ chalk.dim(`Next time, skip the menu: npx claude-code-arcane install ${expr}`)
265
+ );
266
+ return names;
267
+ }
268
+
108
269
  // src/installer.ts
109
- import fs5 from "fs";
110
- import path6 from "path";
270
+ import fs6 from "fs";
271
+ import path7 from "path";
111
272
 
112
273
  // src/utils.ts
113
274
  import { fileURLToPath } from "url";
114
- import path2 from "path";
115
- import fs2 from "fs";
275
+ import path3 from "path";
276
+ import fs3 from "fs";
116
277
  function getPackageRoot() {
117
278
  const thisFile = fileURLToPath(import.meta.url);
118
- return path2.resolve(path2.dirname(thisFile), "..");
279
+ return path3.resolve(path3.dirname(thisFile), "..");
119
280
  }
120
281
  var COPY_SKIP = /* @__PURE__ */ new Set(["__pycache__", ".pytest_cache"]);
121
282
  function isBuildArtifact(name) {
122
283
  return COPY_SKIP.has(name) || name.endsWith(".pyc");
123
284
  }
124
285
  function copyDirSync(src, dest) {
125
- fs2.mkdirSync(dest, { recursive: true });
126
- for (const entry of fs2.readdirSync(src, { withFileTypes: true })) {
286
+ fs3.mkdirSync(dest, { recursive: true });
287
+ for (const entry of fs3.readdirSync(src, { withFileTypes: true })) {
127
288
  if (isBuildArtifact(entry.name)) continue;
128
- const srcPath = path2.join(src, entry.name);
129
- const destPath = path2.join(dest, entry.name);
289
+ const srcPath = path3.join(src, entry.name);
290
+ const destPath = path3.join(dest, entry.name);
130
291
  if (entry.isDirectory()) {
131
292
  copyDirSync(srcPath, destPath);
132
293
  } else {
133
- fs2.copyFileSync(srcPath, destPath);
294
+ fs3.copyFileSync(srcPath, destPath);
134
295
  }
135
296
  }
136
297
  }
137
298
  function ensureDir(dir) {
138
- fs2.mkdirSync(dir, { recursive: true });
299
+ fs3.mkdirSync(dir, { recursive: true });
139
300
  }
140
- function fileExists(p) {
141
- return fs2.existsSync(p);
301
+ function fileExists(p2) {
302
+ return fs3.existsSync(p2);
142
303
  }
143
- function readJsonSync(p) {
144
- return JSON.parse(fs2.readFileSync(p, "utf-8"));
304
+ function readJsonSync(p2) {
305
+ return JSON.parse(fs3.readFileSync(p2, "utf-8"));
145
306
  }
146
- function writeJsonSync(p, data) {
147
- fs2.writeFileSync(p, JSON.stringify(data, null, 2) + "\n", "utf-8");
307
+ function writeJsonSync(p2, data) {
308
+ fs3.writeFileSync(p2, JSON.stringify(data, null, 2) + "\n", "utf-8");
148
309
  }
149
- function isSymlinkOrJunction(p) {
310
+ function isSymlinkOrJunction(p2) {
150
311
  try {
151
- return fs2.lstatSync(p).isSymbolicLink();
312
+ return fs3.lstatSync(p2).isSymbolicLink();
152
313
  } catch {
153
314
  return false;
154
315
  }
155
316
  }
156
317
  function getPackageVersion() {
157
- const pkgPath = path2.join(getPackageRoot(), "package.json");
318
+ const pkgPath = path3.join(getPackageRoot(), "package.json");
158
319
  const pkg = readJsonSync(pkgPath);
159
320
  return pkg.version;
160
321
  }
161
- function safeRemove(p) {
162
- if (isSymlinkOrJunction(p)) {
163
- fs2.unlinkSync(p);
164
- } else if (fs2.existsSync(p)) {
165
- fs2.rmSync(p, { recursive: true, force: true });
322
+ function safeRemove(p2) {
323
+ if (isSymlinkOrJunction(p2)) {
324
+ fs3.unlinkSync(p2);
325
+ } else if (fs3.existsSync(p2)) {
326
+ fs3.rmSync(p2, { recursive: true, force: true });
166
327
  }
167
328
  }
168
329
 
169
330
  // src/manifest.ts
170
- import path3 from "path";
331
+ import path4 from "path";
171
332
  var MANIFEST_FILE = "arcane-manifest.json";
172
333
  function manifestPath(target) {
173
- return path3.join(target, ".claude", MANIFEST_FILE);
334
+ return path4.join(target, ".claude", MANIFEST_FILE);
174
335
  }
175
336
  function readManifest(target) {
176
- const p = manifestPath(target);
177
- if (!fileExists(p)) return null;
178
- return readJsonSync(p);
337
+ const p2 = manifestPath(target);
338
+ if (!fileExists(p2)) return null;
339
+ return readJsonSync(p2);
179
340
  }
180
341
  function writeManifest(target, merged, profileCommand, packageRoot, worktreeOrOpts) {
181
342
  let opts = {};
@@ -224,8 +385,8 @@ function updateManifestFields(target, updates) {
224
385
 
225
386
  // src/worktree.ts
226
387
  import { execSync } from "child_process";
227
- import fs3 from "fs";
228
- import path4 from "path";
388
+ import fs4 from "fs";
389
+ import path5 from "path";
229
390
  function getWorktreeInfo(target) {
230
391
  try {
231
392
  execSync("git rev-parse --is-inside-work-tree", {
@@ -246,10 +407,10 @@ function getWorktreeInfo(target) {
246
407
  encoding: "utf-8",
247
408
  stdio: "pipe"
248
409
  }).trim();
249
- const resolvedCommon = path4.resolve(target, gitCommonDir);
250
- const resolvedGit = path4.resolve(target, gitDir);
410
+ const resolvedCommon = path5.resolve(target, gitCommonDir);
411
+ const resolvedGit = path5.resolve(target, gitDir);
251
412
  const isWorktree = resolvedCommon !== resolvedGit;
252
- const mainWorktreePath = isWorktree ? path4.resolve(resolvedCommon, "..") : target;
413
+ const mainWorktreePath = isWorktree ? path5.resolve(resolvedCommon, "..") : target;
253
414
  let currentBranch = "HEAD";
254
415
  try {
255
416
  currentBranch = execSync("git branch --show-current", {
@@ -264,7 +425,7 @@ function getWorktreeInfo(target) {
264
425
  isWorktree,
265
426
  mainWorktreePath,
266
427
  currentBranch,
267
- worktreeId: path4.basename(target),
428
+ worktreeId: path5.basename(target),
268
429
  allWorktrees
269
430
  };
270
431
  }
@@ -300,17 +461,17 @@ function parseWorktreeList(cwd) {
300
461
  }
301
462
  }
302
463
  function findMainArcaneInstall(mainPath) {
303
- return fs3.existsSync(
304
- path4.join(mainPath, ".claude", "arcane-manifest.json")
464
+ return fs4.existsSync(
465
+ path5.join(mainPath, ".claude", "arcane-manifest.json")
305
466
  );
306
467
  }
307
468
  function linkOrCopyDir(src, dest, mode) {
308
469
  if (mode === "symlink") {
309
470
  try {
310
- const parentDir = path4.dirname(dest);
311
- fs3.mkdirSync(parentDir, { recursive: true });
471
+ const parentDir = path5.dirname(dest);
472
+ fs4.mkdirSync(parentDir, { recursive: true });
312
473
  const type = process.platform === "win32" ? "junction" : "dir";
313
- fs3.symlinkSync(src, dest, type);
474
+ fs4.symlinkSync(src, dest, type);
314
475
  return "linked";
315
476
  } catch {
316
477
  }
@@ -322,9 +483,9 @@ function sanitizeBranchForPath(branch) {
322
483
  return branch.replace(/^(feat|fix|feature|hotfix)\//, "").replace(/\//g, "-").replace(/[^a-zA-Z0-9._-]/g, "-");
323
484
  }
324
485
  function defaultWorktreePath(repoRoot, branch) {
325
- const repoName = path4.basename(repoRoot);
486
+ const repoName = path5.basename(repoRoot);
326
487
  const sanitized = sanitizeBranchForPath(branch);
327
- return path4.resolve(repoRoot, "..", `${repoName}-${sanitized}`);
488
+ return path5.resolve(repoRoot, "..", `${repoName}-${sanitized}`);
328
489
  }
329
490
  function createGitWorktree(repoRoot, worktreePath, branch, base) {
330
491
  const branchExists = checkBranchExists(repoRoot, branch);
@@ -366,20 +527,20 @@ function detectPackageManager(dir) {
366
527
  { lockfile: "package-lock.json", command: "npm install" }
367
528
  ];
368
529
  for (const m of managers) {
369
- if (fs3.existsSync(path4.join(dir, m.lockfile))) return m;
530
+ if (fs4.existsSync(path5.join(dir, m.lockfile))) return m;
370
531
  }
371
- if (fs3.existsSync(path4.join(dir, "package.json"))) {
532
+ if (fs4.existsSync(path5.join(dir, "package.json"))) {
372
533
  return { lockfile: "package.json", command: "npm install" };
373
534
  }
374
535
  return null;
375
536
  }
376
537
 
377
538
  // src/content-hash.ts
378
- import fs4 from "fs";
379
- import path5 from "path";
539
+ import fs5 from "fs";
540
+ import path6 from "path";
380
541
  import { createHash } from "crypto";
381
542
  function hashFile(filePath) {
382
- const content = fs4.readFileSync(filePath);
543
+ const content = fs5.readFileSync(filePath);
383
544
  return createHash("sha256").update(content).digest("hex");
384
545
  }
385
546
  function hashDirectory(dirPath) {
@@ -388,10 +549,10 @@ function hashDirectory(dirPath) {
388
549
  return hashes;
389
550
  }
390
551
  function walkDir(base, current, hashes) {
391
- for (const entry of fs4.readdirSync(current, { withFileTypes: true })) {
552
+ for (const entry of fs5.readdirSync(current, { withFileTypes: true })) {
392
553
  if (isBuildArtifact(entry.name)) continue;
393
- const fullPath = path5.join(current, entry.name);
394
- const relPath = path5.relative(base, fullPath).split(path5.sep).join("/");
554
+ const fullPath = path6.join(current, entry.name);
555
+ const relPath = path6.relative(base, fullPath).split(path6.sep).join("/");
395
556
  if (entry.isDirectory()) {
396
557
  walkDir(base, fullPath, hashes);
397
558
  } else {
@@ -406,39 +567,39 @@ function computeContentHashes(claudeDir) {
406
567
  agents: {},
407
568
  hooks: {}
408
569
  };
409
- const skillsDir = path5.join(claudeDir, "skills");
410
- if (fs4.existsSync(skillsDir)) {
411
- for (const entry of fs4.readdirSync(skillsDir, { withFileTypes: true })) {
570
+ const skillsDir = path6.join(claudeDir, "skills");
571
+ if (fs5.existsSync(skillsDir)) {
572
+ for (const entry of fs5.readdirSync(skillsDir, { withFileTypes: true })) {
412
573
  if (entry.isDirectory()) {
413
- const dirHash = hashDirectory(path5.join(skillsDir, entry.name));
574
+ const dirHash = hashDirectory(path6.join(skillsDir, entry.name));
414
575
  const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
415
576
  hashes.skills[entry.name] = combined;
416
577
  }
417
578
  }
418
579
  }
419
- const rulesDir = path5.join(claudeDir, "rules");
420
- if (fs4.existsSync(rulesDir)) {
421
- for (const entry of fs4.readdirSync(rulesDir, { withFileTypes: true })) {
580
+ const rulesDir = path6.join(claudeDir, "rules");
581
+ if (fs5.existsSync(rulesDir)) {
582
+ for (const entry of fs5.readdirSync(rulesDir, { withFileTypes: true })) {
422
583
  if (!entry.isDirectory()) {
423
- hashes.rules[entry.name] = hashFile(path5.join(rulesDir, entry.name));
584
+ hashes.rules[entry.name] = hashFile(path6.join(rulesDir, entry.name));
424
585
  }
425
586
  }
426
587
  }
427
- const agentsDir = path5.join(claudeDir, "agents");
428
- if (fs4.existsSync(agentsDir)) {
429
- for (const entry of fs4.readdirSync(agentsDir, { withFileTypes: true })) {
588
+ const agentsDir = path6.join(claudeDir, "agents");
589
+ if (fs5.existsSync(agentsDir)) {
590
+ for (const entry of fs5.readdirSync(agentsDir, { withFileTypes: true })) {
430
591
  if (entry.isDirectory()) {
431
- const dirHash = hashDirectory(path5.join(agentsDir, entry.name));
592
+ const dirHash = hashDirectory(path6.join(agentsDir, entry.name));
432
593
  const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
433
594
  hashes.agents[entry.name] = combined;
434
595
  }
435
596
  }
436
597
  }
437
- const hooksDir = path5.join(claudeDir, "hooks");
438
- if (fs4.existsSync(hooksDir)) {
439
- for (const entry of fs4.readdirSync(hooksDir, { withFileTypes: true })) {
598
+ const hooksDir = path6.join(claudeDir, "hooks");
599
+ if (fs5.existsSync(hooksDir)) {
600
+ for (const entry of fs5.readdirSync(hooksDir, { withFileTypes: true })) {
440
601
  if (!entry.isDirectory()) {
441
- hashes.hooks[entry.name] = hashFile(path5.join(hooksDir, entry.name));
602
+ hashes.hooks[entry.name] = hashFile(path6.join(hooksDir, entry.name));
442
603
  }
443
604
  }
444
605
  }
@@ -452,8 +613,8 @@ function computeSourceHashes(packageRoot, skills, rules, agents) {
452
613
  hooks: {}
453
614
  };
454
615
  for (const skill of skills) {
455
- const skillDir = path5.join(packageRoot, "skills", skill);
456
- if (fs4.existsSync(skillDir)) {
616
+ const skillDir = path6.join(packageRoot, "skills", skill);
617
+ if (fs5.existsSync(skillDir)) {
457
618
  const dirHash = hashDirectory(skillDir);
458
619
  const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
459
620
  hashes.skills[skill] = combined;
@@ -461,29 +622,29 @@ function computeSourceHashes(packageRoot, skills, rules, agents) {
461
622
  }
462
623
  for (const rule of rules) {
463
624
  const ruleName = rule.endsWith(".md") ? rule : `${rule}.md`;
464
- const rulePath = path5.join(packageRoot, "rules", ruleName);
465
- if (fs4.existsSync(rulePath)) {
625
+ const rulePath = path6.join(packageRoot, "rules", ruleName);
626
+ if (fs5.existsSync(rulePath)) {
466
627
  hashes.rules[ruleName] = hashFile(rulePath);
467
628
  } else {
468
- const gamedevPath = path5.join(packageRoot, "rules", "gamedev", ruleName);
469
- if (fs4.existsSync(gamedevPath)) {
629
+ const gamedevPath = path6.join(packageRoot, "rules", "gamedev", ruleName);
630
+ if (fs5.existsSync(gamedevPath)) {
470
631
  hashes.rules[ruleName] = hashFile(gamedevPath);
471
632
  }
472
633
  }
473
634
  }
474
635
  for (const agent of agents) {
475
- const agentDir = path5.join(packageRoot, "agents", agent);
476
- if (fs4.existsSync(agentDir)) {
636
+ const agentDir = path6.join(packageRoot, "agents", agent);
637
+ if (fs5.existsSync(agentDir)) {
477
638
  const dirHash = hashDirectory(agentDir);
478
639
  const combined = createHash("sha256").update(JSON.stringify(dirHash)).digest("hex");
479
640
  hashes.agents[agent] = combined;
480
641
  }
481
642
  }
482
- const hooksDir = path5.join(packageRoot, "hooks");
483
- if (fs4.existsSync(hooksDir)) {
484
- for (const entry of fs4.readdirSync(hooksDir, { withFileTypes: true })) {
643
+ const hooksDir = path6.join(packageRoot, "hooks");
644
+ if (fs5.existsSync(hooksDir)) {
645
+ for (const entry of fs5.readdirSync(hooksDir, { withFileTypes: true })) {
485
646
  if (!entry.isDirectory()) {
486
- hashes.hooks[entry.name] = hashFile(path5.join(hooksDir, entry.name));
647
+ hashes.hooks[entry.name] = hashFile(path6.join(hooksDir, entry.name));
487
648
  }
488
649
  }
489
650
  }
@@ -652,7 +813,7 @@ var Installer = class {
652
813
  constructor(merged, opts) {
653
814
  this.root = opts.contentRoot ?? getPackageRoot();
654
815
  this.target = opts.target;
655
- this.claudeDir = path6.join(opts.target, ".claude");
816
+ this.claudeDir = path7.join(opts.target, ".claude");
656
817
  this.merged = merged;
657
818
  this.dryRun = opts.dryRun;
658
819
  this.shareFrom = opts.shareFrom;
@@ -684,22 +845,22 @@ var Installer = class {
684
845
  return this.logs;
685
846
  }
686
847
  backup() {
687
- if (!fs5.existsSync(this.claudeDir)) return;
688
- const backup = path6.join(this.target, ".claude.bak");
848
+ if (!fs6.existsSync(this.claudeDir)) return;
849
+ const backup = path7.join(this.target, ".claude.bak");
689
850
  if (this.dryRun) {
690
851
  this.log(" [dry-run] Would backup .claude/ -> .claude.bak/");
691
852
  return;
692
853
  }
693
- if (fs5.existsSync(backup)) {
694
- fs5.rmSync(backup, { recursive: true, force: true });
854
+ if (fs6.existsSync(backup)) {
855
+ fs6.rmSync(backup, { recursive: true, force: true });
695
856
  }
696
- fs5.renameSync(this.claudeDir, backup);
857
+ fs6.renameSync(this.claudeDir, backup);
697
858
  this.log(" Backed up .claude/ -> .claude.bak/");
698
859
  }
699
860
  createDirs() {
700
861
  if (this.dryRun) return;
701
862
  for (const d of ["skills", "rules", "agents", "docs"]) {
702
- ensureDir(path6.join(this.claudeDir, d));
863
+ ensureDir(path7.join(this.claudeDir, d));
703
864
  }
704
865
  }
705
866
  get hasStatusline() {
@@ -707,7 +868,7 @@ var Installer = class {
707
868
  }
708
869
  copyHooks() {
709
870
  this.log("\nHooks:");
710
- const hooksDir = path6.join(this.claudeDir, "hooks");
871
+ const hooksDir = path7.join(this.claudeDir, "hooks");
711
872
  if (this.dryRun) {
712
873
  if (this.shareFrom) {
713
874
  this.log(" [dry-run] hooks/ -> symlink from main worktree");
@@ -718,8 +879,8 @@ var Installer = class {
718
879
  return;
719
880
  }
720
881
  if (this.shareFrom) {
721
- const sharedHooks = path6.join(this.shareFrom, ".claude", "hooks");
722
- if (fs5.existsSync(sharedHooks)) {
882
+ const sharedHooks = path7.join(this.shareFrom, ".claude", "hooks");
883
+ if (fs6.existsSync(sharedHooks)) {
723
884
  const result = linkOrCopyDir(sharedHooks, hooksDir, "symlink");
724
885
  if (result === "linked") {
725
886
  this.sharedDirs.push("hooks");
@@ -731,17 +892,17 @@ var Installer = class {
731
892
  return;
732
893
  }
733
894
  }
734
- const hooksSrc = path6.join(this.root, "hooks");
735
- if (fs5.existsSync(hooksSrc)) {
895
+ const hooksSrc = path7.join(this.root, "hooks");
896
+ if (fs6.existsSync(hooksSrc)) {
736
897
  ensureDir(hooksDir);
737
- for (const entry of fs5.readdirSync(hooksSrc, { withFileTypes: true })) {
898
+ for (const entry of fs6.readdirSync(hooksSrc, { withFileTypes: true })) {
738
899
  if (entry.name === "statusline.sh") continue;
739
- const src = path6.join(hooksSrc, entry.name);
740
- const dst = path6.join(hooksDir, entry.name);
900
+ const src = path7.join(hooksSrc, entry.name);
901
+ const dst = path7.join(hooksDir, entry.name);
741
902
  if (entry.isDirectory()) {
742
903
  copyDirSync(src, dst);
743
904
  } else {
744
- fs5.copyFileSync(src, dst);
905
+ fs6.copyFileSync(src, dst);
745
906
  }
746
907
  }
747
908
  this.log(" [ok] hooks/");
@@ -750,24 +911,24 @@ var Installer = class {
750
911
  }
751
912
  copyStatusline() {
752
913
  if (!this.hasStatusline) return;
753
- const statusline = path6.join(this.root, "hooks", "statusline.sh");
754
- if (fs5.existsSync(statusline)) {
755
- fs5.copyFileSync(
914
+ const statusline = path7.join(this.root, "hooks", "statusline.sh");
915
+ if (fs6.existsSync(statusline)) {
916
+ fs6.copyFileSync(
756
917
  statusline,
757
- path6.join(this.claudeDir, "statusline.sh")
918
+ path7.join(this.claudeDir, "statusline.sh")
758
919
  );
759
- this.log(" [ok] statusline.sh (+statusline addon)");
920
+ this.log(" [ok] statusline.sh (statusline profile)");
760
921
  }
761
922
  }
762
923
  copySkills() {
763
924
  this.log("\nSkills:");
764
925
  for (const skill of this.merged.skills) {
765
- const src = path6.join(this.root, "skills", skill);
766
- if (!fs5.existsSync(src)) {
926
+ const src = path7.join(this.root, "skills", skill);
927
+ if (!fs6.existsSync(src)) {
767
928
  this.log(` WARN: Skill '${skill}' not found in skills/`);
768
929
  continue;
769
930
  }
770
- const dst = path6.join(this.claudeDir, "skills", skill);
931
+ const dst = path7.join(this.claudeDir, "skills", skill);
771
932
  if (this.dryRun) {
772
933
  this.log(` [dry-run] ${skill}`);
773
934
  } else {
@@ -777,9 +938,9 @@ var Installer = class {
777
938
  }
778
939
  const hasGamedev = this.merged.rules.gamedev.length > 0;
779
940
  if (hasGamedev) {
780
- const tplSrc = path6.join(this.root, "templates", "gamedev");
781
- if (fs5.existsSync(tplSrc)) {
782
- const tplDst = path6.join(this.claudeDir, "skills", "_templates");
941
+ const tplSrc = path7.join(this.root, "templates", "gamedev");
942
+ if (fs6.existsSync(tplSrc)) {
943
+ const tplDst = path7.join(this.claudeDir, "skills", "_templates");
783
944
  if (this.dryRun) {
784
945
  this.log(" [dry-run] _templates/");
785
946
  } else {
@@ -792,33 +953,33 @@ var Installer = class {
792
953
  copyRules() {
793
954
  this.log("\nRules:");
794
955
  for (const rule of this.merged.rules.universal) {
795
- const src = path6.join(this.root, "rules", `${rule}.md`);
796
- if (!fs5.existsSync(src)) {
956
+ const src = path7.join(this.root, "rules", `${rule}.md`);
957
+ if (!fs6.existsSync(src)) {
797
958
  this.log(` WARN: Rule '${rule}.md' not found`);
798
959
  continue;
799
960
  }
800
961
  if (this.dryRun) {
801
962
  this.log(` [dry-run] ${rule}.md`);
802
963
  } else {
803
- fs5.copyFileSync(
964
+ fs6.copyFileSync(
804
965
  src,
805
- path6.join(this.claudeDir, "rules", `${rule}.md`)
966
+ path7.join(this.claudeDir, "rules", `${rule}.md`)
806
967
  );
807
968
  this.log(` [ok] ${rule}.md`);
808
969
  }
809
970
  }
810
971
  for (const rule of this.merged.rules.gamedev) {
811
- const src = path6.join(this.root, "rules", "gamedev", `${rule}.md`);
812
- if (!fs5.existsSync(src)) {
972
+ const src = path7.join(this.root, "rules", "gamedev", `${rule}.md`);
973
+ if (!fs6.existsSync(src)) {
813
974
  this.log(` WARN: Rule '${rule}.md' not found`);
814
975
  continue;
815
976
  }
816
977
  if (this.dryRun) {
817
978
  this.log(` [dry-run] ${rule}.md`);
818
979
  } else {
819
- fs5.copyFileSync(
980
+ fs6.copyFileSync(
820
981
  src,
821
- path6.join(this.claudeDir, "rules", `${rule}.md`)
982
+ path7.join(this.claudeDir, "rules", `${rule}.md`)
822
983
  );
823
984
  this.log(` [ok] ${rule}.md`);
824
985
  }
@@ -828,25 +989,25 @@ var Installer = class {
828
989
  if (this.merged.agents.length === 0) return;
829
990
  this.log("\nAgents:");
830
991
  for (const agentDir of this.merged.agents) {
831
- const src = path6.join(this.root, "agents", agentDir);
832
- if (!fs5.existsSync(src)) {
992
+ const src = path7.join(this.root, "agents", agentDir);
993
+ if (!fs6.existsSync(src)) {
833
994
  this.log(` WARN: Agent dir '${agentDir}' not found`);
834
995
  continue;
835
996
  }
836
- const dst = path6.join(this.claudeDir, "agents", agentDir);
997
+ const dst = path7.join(this.claudeDir, "agents", agentDir);
837
998
  if (this.dryRun) {
838
999
  this.log(` [dry-run] agents/${agentDir}/`);
839
1000
  } else {
840
1001
  copyDirSync(src, dst);
841
- const count = fs5.readdirSync(dst, { recursive: true }).filter((f) => String(f).endsWith(".md")).length;
1002
+ const count = fs6.readdirSync(dst, { recursive: true }).filter((f) => String(f).endsWith(".md")).length;
842
1003
  this.log(` [ok] agents/${agentDir}/ (${count} agents)`);
843
1004
  }
844
1005
  }
845
1006
  }
846
1007
  copyDocs() {
847
- const docsDir = path6.join(this.claudeDir, "docs");
848
- const docsSrc = path6.join(this.root, "docs");
849
- if (!fs5.existsSync(docsSrc)) return;
1008
+ const docsDir = path7.join(this.claudeDir, "docs");
1009
+ const docsSrc = path7.join(this.root, "docs");
1010
+ if (!fs6.existsSync(docsSrc)) return;
850
1011
  this.log("\nDocs:");
851
1012
  if (this.dryRun) {
852
1013
  if (this.shareFrom) {
@@ -857,8 +1018,8 @@ var Installer = class {
857
1018
  return;
858
1019
  }
859
1020
  if (this.shareFrom) {
860
- const sharedDocs = path6.join(this.shareFrom, ".claude", "docs");
861
- if (fs5.existsSync(sharedDocs)) {
1021
+ const sharedDocs = path7.join(this.shareFrom, ".claude", "docs");
1022
+ if (fs6.existsSync(sharedDocs)) {
862
1023
  const result = linkOrCopyDir(sharedDocs, docsDir, "symlink");
863
1024
  if (result === "linked") {
864
1025
  this.sharedDirs.push("docs");
@@ -889,8 +1050,8 @@ var Installer = class {
889
1050
  );
890
1051
  return;
891
1052
  }
892
- const settingsPath = path6.join(this.claudeDir, "settings.json");
893
- fs5.writeFileSync(
1053
+ const settingsPath = path7.join(this.claudeDir, "settings.json");
1054
+ fs6.writeFileSync(
894
1055
  settingsPath,
895
1056
  JSON.stringify(settings, null, 2) + "\n",
896
1057
  "utf-8"
@@ -899,65 +1060,65 @@ var Installer = class {
899
1060
  }
900
1061
  };
901
1062
  function clean(target) {
902
- const claudeDir = path6.join(target, ".claude");
903
- const backup = path6.join(target, ".claude.bak");
904
- if (!fs5.existsSync(claudeDir)) {
1063
+ const claudeDir = path7.join(target, ".claude");
1064
+ const backup = path7.join(target, ".claude.bak");
1065
+ if (!fs6.existsSync(claudeDir)) {
905
1066
  console.log(`No .claude/ found in ${target} \u2014 nothing to clean.`);
906
1067
  return;
907
1068
  }
908
- const manifestFile = path6.join(claudeDir, "arcane-manifest.json");
909
- if (fs5.existsSync(manifestFile)) {
910
- const data = JSON.parse(fs5.readFileSync(manifestFile, "utf-8"));
1069
+ const manifestFile = path7.join(claudeDir, "arcane-manifest.json");
1070
+ if (fs6.existsSync(manifestFile)) {
1071
+ const data = JSON.parse(fs6.readFileSync(manifestFile, "utf-8"));
911
1072
  const profiles = (data.profiles ?? []).filter(
912
- (p) => p !== "core"
1073
+ (p2) => p2 !== "core"
913
1074
  );
914
1075
  console.log(` Current profile: ${profiles.join(" + ")}`);
915
1076
  console.log(` Installed: ${data.installed_at ?? "unknown"}`);
916
1077
  const shared = data.worktree?.shared_dirs ?? [];
917
1078
  for (const dir of shared) {
918
- const p = path6.join(claudeDir, dir);
919
- if (isSymlinkOrJunction(p)) {
920
- fs5.unlinkSync(p);
1079
+ const p2 = path7.join(claudeDir, dir);
1080
+ if (isSymlinkOrJunction(p2)) {
1081
+ fs6.unlinkSync(p2);
921
1082
  console.log(` [ok] Unlinked shared ${dir}/`);
922
1083
  }
923
1084
  }
924
1085
  }
925
1086
  for (const sub of ["hooks", "docs", "skills", "agents", "rules"]) {
926
- const p = path6.join(claudeDir, sub);
927
- safeRemove(p);
1087
+ const p2 = path7.join(claudeDir, sub);
1088
+ safeRemove(p2);
928
1089
  }
929
- const remaining = fs5.readdirSync(claudeDir);
1090
+ const remaining = fs6.readdirSync(claudeDir);
930
1091
  for (const f of remaining) {
931
- const p = path6.join(claudeDir, f);
932
- safeRemove(p);
1092
+ const p2 = path7.join(claudeDir, f);
1093
+ safeRemove(p2);
933
1094
  }
934
- if (fs5.existsSync(claudeDir)) {
935
- fs5.rmSync(claudeDir, { recursive: true, force: true });
1095
+ if (fs6.existsSync(claudeDir)) {
1096
+ fs6.rmSync(claudeDir, { recursive: true, force: true });
936
1097
  }
937
- if (fs5.existsSync(backup)) {
938
- fs5.rmSync(backup, { recursive: true, force: true });
1098
+ if (fs6.existsSync(backup)) {
1099
+ fs6.rmSync(backup, { recursive: true, force: true });
939
1100
  }
940
1101
  console.log(`
941
1102
  [ok] Removed .claude/ from ${target}`);
942
1103
  }
943
1104
 
944
1105
  // src/content-source.ts
945
- import fs7 from "fs";
946
- import path8 from "path";
1106
+ import fs8 from "fs";
1107
+ import path9 from "path";
947
1108
  import os2 from "os";
948
1109
 
949
1110
  // src/cache.ts
950
- import fs6 from "fs";
951
- import path7 from "path";
1111
+ import fs7 from "fs";
1112
+ import path8 from "path";
952
1113
  import os from "os";
953
- var ARCANE_DIR = path7.join(os.homedir(), ".arcane");
954
- var CACHE_DIR = path7.join(ARCANE_DIR, "cache");
1114
+ var ARCANE_DIR = path8.join(os.homedir(), ".arcane");
1115
+ var CACHE_DIR = path8.join(ARCANE_DIR, "cache");
955
1116
  function getCachePath(version) {
956
- return path7.join(CACHE_DIR, version);
1117
+ return path8.join(CACHE_DIR, version);
957
1118
  }
958
1119
  function isCached(version) {
959
1120
  const versionDir = getCachePath(version);
960
- return fs6.existsSync(versionDir) && fs6.existsSync(path7.join(versionDir, ".cache-meta.json"));
1121
+ return fs7.existsSync(versionDir) && fs7.existsSync(path8.join(versionDir, ".cache-meta.json"));
961
1122
  }
962
1123
  function getCachedContentRoot(version) {
963
1124
  if (!isCached(version)) return null;
@@ -965,34 +1126,34 @@ function getCachedContentRoot(version) {
965
1126
  }
966
1127
  function storeInCache(version, contentDir) {
967
1128
  const versionDir = getCachePath(version);
968
- if (fs6.existsSync(versionDir)) {
969
- fs6.rmSync(versionDir, { recursive: true, force: true });
1129
+ if (fs7.existsSync(versionDir)) {
1130
+ fs7.rmSync(versionDir, { recursive: true, force: true });
970
1131
  }
971
- fs6.mkdirSync(versionDir, { recursive: true });
1132
+ fs7.mkdirSync(versionDir, { recursive: true });
972
1133
  for (const dir of ["skills", "rules", "agents", "hooks", "profiles", "templates", "docs"]) {
973
- const src = path7.join(contentDir, dir);
974
- if (fs6.existsSync(src)) {
975
- copyDirRecursive(src, path7.join(versionDir, dir));
1134
+ const src = path8.join(contentDir, dir);
1135
+ if (fs7.existsSync(src)) {
1136
+ copyDirRecursive(src, path8.join(versionDir, dir));
976
1137
  }
977
1138
  }
978
1139
  const meta = {
979
1140
  version,
980
1141
  cached_at: (/* @__PURE__ */ new Date()).toISOString()
981
1142
  };
982
- fs6.writeFileSync(
983
- path7.join(versionDir, ".cache-meta.json"),
1143
+ fs7.writeFileSync(
1144
+ path8.join(versionDir, ".cache-meta.json"),
984
1145
  JSON.stringify(meta, null, 2) + "\n"
985
1146
  );
986
1147
  return versionDir;
987
1148
  }
988
1149
  function pruneCache(keepCount = 3) {
989
- if (!fs6.existsSync(CACHE_DIR)) return;
990
- const entries = fs6.readdirSync(CACHE_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
991
- const metaPath = path7.join(CACHE_DIR, e.name, ".cache-meta.json");
1150
+ if (!fs7.existsSync(CACHE_DIR)) return;
1151
+ const entries = fs7.readdirSync(CACHE_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1152
+ const metaPath = path8.join(CACHE_DIR, e.name, ".cache-meta.json");
992
1153
  let cachedAt = 0;
993
- if (fs6.existsSync(metaPath)) {
1154
+ if (fs7.existsSync(metaPath)) {
994
1155
  try {
995
- const meta = JSON.parse(fs6.readFileSync(metaPath, "utf-8"));
1156
+ const meta = JSON.parse(fs7.readFileSync(metaPath, "utf-8"));
996
1157
  cachedAt = new Date(meta.cached_at).getTime();
997
1158
  } catch {
998
1159
  cachedAt = 0;
@@ -1001,18 +1162,18 @@ function pruneCache(keepCount = 3) {
1001
1162
  return { name: e.name, cachedAt };
1002
1163
  }).sort((a, b) => b.cachedAt - a.cachedAt);
1003
1164
  for (const entry of entries.slice(keepCount)) {
1004
- fs6.rmSync(path7.join(CACHE_DIR, entry.name), { recursive: true, force: true });
1165
+ fs7.rmSync(path8.join(CACHE_DIR, entry.name), { recursive: true, force: true });
1005
1166
  }
1006
1167
  }
1007
1168
  function copyDirRecursive(src, dest) {
1008
- fs6.mkdirSync(dest, { recursive: true });
1009
- for (const entry of fs6.readdirSync(src, { withFileTypes: true })) {
1010
- const srcPath = path7.join(src, entry.name);
1011
- const destPath = path7.join(dest, entry.name);
1169
+ fs7.mkdirSync(dest, { recursive: true });
1170
+ for (const entry of fs7.readdirSync(src, { withFileTypes: true })) {
1171
+ const srcPath = path8.join(src, entry.name);
1172
+ const destPath = path8.join(dest, entry.name);
1012
1173
  if (entry.isDirectory()) {
1013
1174
  copyDirRecursive(srcPath, destPath);
1014
1175
  } else {
1015
- fs6.copyFileSync(srcPath, destPath);
1176
+ fs7.copyFileSync(srcPath, destPath);
1016
1177
  }
1017
1178
  }
1018
1179
  }
@@ -1028,7 +1189,7 @@ var BundledContentSource = class {
1028
1189
  }
1029
1190
  async isAvailable() {
1030
1191
  const root = getPackageRoot();
1031
- return fs7.existsSync(path8.join(root, "skills"));
1192
+ return fs8.existsSync(path9.join(root, "skills"));
1032
1193
  }
1033
1194
  async getVersion() {
1034
1195
  return getPackageVersion();
@@ -1068,14 +1229,14 @@ var GitHubContentSource = class {
1068
1229
  if (isCached(version)) {
1069
1230
  return getCachedContentRoot(version);
1070
1231
  }
1071
- const tmpDir = fs7.mkdtempSync(path8.join(os2.tmpdir(), "arcane-github-"));
1232
+ const tmpDir = fs8.mkdtempSync(path9.join(os2.tmpdir(), "arcane-github-"));
1072
1233
  try {
1073
1234
  await this.downloadAndExtract(tmpDir);
1074
1235
  const contentRoot = storeInCache(version, this.findExtractedRoot(tmpDir));
1075
1236
  pruneCache(3);
1076
1237
  return contentRoot;
1077
1238
  } finally {
1078
- fs7.rmSync(tmpDir, { recursive: true, force: true });
1239
+ fs8.rmSync(tmpDir, { recursive: true, force: true });
1079
1240
  }
1080
1241
  }
1081
1242
  async isAvailable() {
@@ -1131,8 +1292,8 @@ var GitHubContentSource = class {
1131
1292
  throw new Error(`Failed to download tarball: ${response.status}`);
1132
1293
  }
1133
1294
  const tar = await import("tar");
1134
- const tarballPath = path8.join(destDir, "repo.tar.gz");
1135
- const fileStream = fs7.createWriteStream(tarballPath);
1295
+ const tarballPath = path9.join(destDir, "repo.tar.gz");
1296
+ const fileStream = fs8.createWriteStream(tarballPath);
1136
1297
  const reader = response.body.getReader();
1137
1298
  const writable = new WritableStream({
1138
1299
  write(chunk) {
@@ -1157,12 +1318,12 @@ var GitHubContentSource = class {
1157
1318
  });
1158
1319
  }
1159
1320
  findExtractedRoot(tmpDir) {
1160
- const entries = fs7.readdirSync(tmpDir, { withFileTypes: true });
1321
+ const entries = fs8.readdirSync(tmpDir, { withFileTypes: true });
1161
1322
  const dirs = entries.filter(
1162
1323
  (e) => e.isDirectory() && e.name !== "." && e.name !== ".."
1163
1324
  );
1164
1325
  if (dirs.length === 1) {
1165
- return path8.join(tmpDir, dirs[0].name);
1326
+ return path9.join(tmpDir, dirs[0].name);
1166
1327
  }
1167
1328
  return tmpDir;
1168
1329
  }
@@ -1217,14 +1378,14 @@ async function resolveContentSourceForVersion(version, opts = {}) {
1217
1378
  return source;
1218
1379
  }
1219
1380
  function findLatestCache() {
1220
- const cacheDir = path8.join(os2.homedir(), ".arcane", "cache");
1221
- if (!fs7.existsSync(cacheDir)) return null;
1222
- const entries = fs7.readdirSync(cacheDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1223
- const metaPath = path8.join(cacheDir, e.name, ".cache-meta.json");
1381
+ const cacheDir = path9.join(os2.homedir(), ".arcane", "cache");
1382
+ if (!fs8.existsSync(cacheDir)) return null;
1383
+ const entries = fs8.readdirSync(cacheDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1384
+ const metaPath = path9.join(cacheDir, e.name, ".cache-meta.json");
1224
1385
  let cachedAt = 0;
1225
- if (fs7.existsSync(metaPath)) {
1386
+ if (fs8.existsSync(metaPath)) {
1226
1387
  try {
1227
- const meta = JSON.parse(fs7.readFileSync(metaPath, "utf-8"));
1388
+ const meta = JSON.parse(fs8.readFileSync(metaPath, "utf-8"));
1228
1389
  cachedAt = new Date(meta.cached_at).getTime();
1229
1390
  } catch {
1230
1391
  cachedAt = 0;
@@ -1236,22 +1397,22 @@ function findLatestCache() {
1236
1397
  }
1237
1398
 
1238
1399
  // src/registry.ts
1239
- import path9 from "path";
1400
+ import path10 from "path";
1240
1401
  import os3 from "os";
1241
1402
  var REGISTRY_VERSION = 1;
1242
1403
  function arcaneHome() {
1243
- return process.env.ARCANE_HOME ?? path9.join(os3.homedir(), ".arcane");
1404
+ return process.env.ARCANE_HOME ?? path10.join(os3.homedir(), ".arcane");
1244
1405
  }
1245
1406
  function registryPath() {
1246
- return path9.join(arcaneHome(), "installations.json");
1407
+ return path10.join(arcaneHome(), "installations.json");
1247
1408
  }
1248
1409
  function readRegistryFile() {
1249
- const p = registryPath();
1250
- if (!fileExists(p)) {
1410
+ const p2 = registryPath();
1411
+ if (!fileExists(p2)) {
1251
1412
  return { version: REGISTRY_VERSION, installations: [] };
1252
1413
  }
1253
1414
  try {
1254
- const data = readJsonSync(p);
1415
+ const data = readJsonSync(p2);
1255
1416
  return {
1256
1417
  version: REGISTRY_VERSION,
1257
1418
  installations: Array.isArray(data.installations) ? data.installations : []
@@ -1268,7 +1429,7 @@ function writeRegistryFile(registry) {
1268
1429
  }
1269
1430
  }
1270
1431
  function registerInstallation(target) {
1271
- const abs = path9.resolve(target);
1432
+ const abs = path10.resolve(target);
1272
1433
  const registry = readRegistryFile();
1273
1434
  if (registry.installations.some((e) => e.path === abs)) {
1274
1435
  return;
@@ -1295,39 +1456,42 @@ async function installCommand(profileExpr, opts) {
1295
1456
  quiet: !profileExpr
1296
1457
  });
1297
1458
  const root = await source.getContentRoot();
1298
- const profilesDir = path10.join(root, "profiles");
1299
- const target = path10.resolve(opts.target ?? process.cwd());
1459
+ const profilesDir = path11.join(root, "profiles");
1460
+ const target = path11.resolve(opts.target ?? process.cwd());
1300
1461
  if (!profileExpr) {
1301
1462
  const profiles = listProfiles(profilesDir);
1302
- const bases = profiles.filter((p) => p.type === "base");
1303
- const addons = profiles.filter((p) => p.type === "addon");
1304
- console.log(chalk.bold("\nAvailable profiles:\n"));
1305
- console.log(chalk.cyan(" Base (pick one):"));
1306
- for (const p of bases) {
1307
- console.log(` ${chalk.green(p.name.padEnd(20))} ${p.description}`);
1308
- }
1309
- console.log(chalk.cyan("\n Addons (combine with +):"));
1310
- for (const p of addons) {
1463
+ if (process.stdin.isTTY && process.stdout.isTTY) {
1464
+ const selected = await runInstallWizard(profiles, detectStack(target));
1465
+ if (!selected) return;
1466
+ profileExpr = selected.join("+");
1467
+ } else {
1468
+ console.log(chalk2.bold("\nAvailable profiles (combine freely with +):\n"));
1469
+ for (const group of groupByCategory(profiles)) {
1470
+ console.log(chalk2.cyan(` ${group.label}:`));
1471
+ for (const p2 of group.profiles) {
1472
+ console.log(
1473
+ ` ${chalk2.green(p2.name.padEnd(20))} ${p2.description}`
1474
+ );
1475
+ }
1476
+ console.log();
1477
+ }
1311
1478
  console.log(
1312
- ` ${chalk.green(("+" + p.name).padEnd(20))} ${p.description}`
1479
+ chalk2.dim(" Usage: npx claude-code-arcane install backend-ts+agile+testing\n")
1313
1480
  );
1481
+ return;
1314
1482
  }
1315
- console.log(
1316
- chalk.dim("\n Usage: npx claude-code-arcane install backend-ts+agile+testing\n")
1317
- );
1318
- return;
1319
1483
  }
1320
1484
  const profileNames = profileExpr.split("+").filter(Boolean);
1321
1485
  console.log(
1322
- chalk.bold(`
1323
- Installing profile: ${chalk.cyan(profileExpr)}`)
1486
+ chalk2.bold(`
1487
+ Installing profile: ${chalk2.cyan(profileExpr)}`)
1324
1488
  );
1325
1489
  console.log(` Target: ${target}`);
1326
1490
  const wtInfo = getWorktreeInfo(target);
1327
1491
  let shareFrom = opts.shareFrom;
1328
1492
  let worktreeMeta;
1329
1493
  if (wtInfo?.isWorktree) {
1330
- console.log(chalk.blue(` Worktree: yes (main: ${wtInfo.mainWorktreePath})`));
1494
+ console.log(chalk2.blue(` Worktree: yes (main: ${wtInfo.mainWorktreePath})`));
1331
1495
  worktreeMeta = {
1332
1496
  is_worktree: true,
1333
1497
  main_worktree: wtInfo.mainWorktreePath
@@ -1335,13 +1499,13 @@ Installing profile: ${chalk.cyan(profileExpr)}`)
1335
1499
  if (!shareFrom && findMainArcaneInstall(wtInfo.mainWorktreePath)) {
1336
1500
  shareFrom = wtInfo.mainWorktreePath;
1337
1501
  console.log(
1338
- chalk.blue(" Sharing: hooks + docs from main worktree")
1502
+ chalk2.blue(" Sharing: hooks + docs from main worktree")
1339
1503
  );
1340
1504
  }
1341
1505
  } else if (wtInfo) {
1342
1506
  worktreeMeta = { is_worktree: false, main_worktree: target };
1343
1507
  }
1344
- if (opts.dryRun) console.log(chalk.yellow(" Mode: dry-run\n"));
1508
+ if (opts.dryRun) console.log(chalk2.yellow(" Mode: dry-run\n"));
1345
1509
  const merged = mergeProfiles(profilesDir, profileNames);
1346
1510
  console.log(` Profiles loaded: ${merged.loaded.join(", ")}`);
1347
1511
  console.log(` Skills: ${merged.skills.length}`);
@@ -1359,14 +1523,14 @@ Installing profile: ${chalk.cyan(profileExpr)}`)
1359
1523
  installer.run(profileExpr, worktreeMeta);
1360
1524
  if (!opts.dryRun) {
1361
1525
  registerInstallation(target);
1362
- console.log(chalk.green("\n Installation complete."));
1526
+ console.log(chalk2.green("\n Installation complete."));
1363
1527
  }
1364
1528
  }
1365
1529
 
1366
1530
  // src/commands/add.ts
1367
- import fs8 from "fs";
1368
- import path11 from "path";
1369
- import chalk2 from "chalk";
1531
+ import fs9 from "fs";
1532
+ import path12 from "path";
1533
+ import chalk3 from "chalk";
1370
1534
  async function addCommand(items) {
1371
1535
  const target = process.cwd();
1372
1536
  const source = await resolveContentSource({ quiet: true });
@@ -1374,13 +1538,13 @@ async function addCommand(items) {
1374
1538
  const manifest = readManifest(target);
1375
1539
  if (!manifest) {
1376
1540
  console.error(
1377
- chalk2.red(
1541
+ chalk3.red(
1378
1542
  "No arcane-manifest.json found. Run `npx claude-code-arcane install` first."
1379
1543
  )
1380
1544
  );
1381
1545
  process.exit(1);
1382
1546
  }
1383
- const claudeDir = path11.join(target, ".claude");
1547
+ const claudeDir = path12.join(target, ".claude");
1384
1548
  const added = [];
1385
1549
  const skipped = [];
1386
1550
  const notFound = [];
@@ -1390,9 +1554,9 @@ async function addCommand(items) {
1390
1554
  for (const item of items) {
1391
1555
  if (item.startsWith("+")) {
1392
1556
  const profileName = item.slice(1);
1393
- const profilePath = path11.join(root, "profiles", `${profileName}.yaml`);
1394
- if (!fs8.existsSync(profilePath)) {
1395
- console.error(chalk2.red(` Profile '${profileName}' not found`));
1557
+ const profilePath = path12.join(root, "profiles", `${profileName}.yaml`);
1558
+ if (!fs9.existsSync(profilePath)) {
1559
+ console.error(chalk3.red(` Profile '${profileName}' not found`));
1396
1560
  continue;
1397
1561
  }
1398
1562
  const profile = parseProfile(profilePath);
@@ -1404,10 +1568,10 @@ async function addCommand(items) {
1404
1568
  }
1405
1569
  for (const rule of profile.rules.universal) {
1406
1570
  if (!manifest.installed_rules.includes(rule)) {
1407
- const src = path11.join(root, "rules", `${rule}.md`);
1408
- if (fs8.existsSync(src)) {
1409
- ensureDir(path11.join(claudeDir, "rules"));
1410
- fs8.copyFileSync(src, path11.join(claudeDir, "rules", `${rule}.md`));
1571
+ const src = path12.join(root, "rules", `${rule}.md`);
1572
+ if (fs9.existsSync(src)) {
1573
+ ensureDir(path12.join(claudeDir, "rules"));
1574
+ fs9.copyFileSync(src, path12.join(claudeDir, "rules", `${rule}.md`));
1411
1575
  manifest.installed_rules.push(rule);
1412
1576
  addedRules.push(rule);
1413
1577
  }
@@ -1415,10 +1579,10 @@ async function addCommand(items) {
1415
1579
  }
1416
1580
  for (const rule of profile.rules.gamedev) {
1417
1581
  if (!manifest.installed_rules.includes(rule)) {
1418
- const src = path11.join(root, "rules", "gamedev", `${rule}.md`);
1419
- if (fs8.existsSync(src)) {
1420
- ensureDir(path11.join(claudeDir, "rules"));
1421
- fs8.copyFileSync(src, path11.join(claudeDir, "rules", `${rule}.md`));
1582
+ const src = path12.join(root, "rules", "gamedev", `${rule}.md`);
1583
+ if (fs9.existsSync(src)) {
1584
+ ensureDir(path12.join(claudeDir, "rules"));
1585
+ fs9.copyFileSync(src, path12.join(claudeDir, "rules", `${rule}.md`));
1422
1586
  manifest.installed_rules.push(rule);
1423
1587
  addedRules.push(rule);
1424
1588
  }
@@ -1426,9 +1590,9 @@ async function addCommand(items) {
1426
1590
  }
1427
1591
  for (const agentDir of profile.agents) {
1428
1592
  if (!manifest.installed_agents.includes(agentDir)) {
1429
- const src = path11.join(root, "agents", agentDir);
1430
- if (fs8.existsSync(src)) {
1431
- const dst = path11.join(claudeDir, "agents", agentDir);
1593
+ const src = path12.join(root, "agents", agentDir);
1594
+ if (fs9.existsSync(src)) {
1595
+ const dst = path12.join(claudeDir, "agents", agentDir);
1432
1596
  copyDirSync(src, dst);
1433
1597
  manifest.installed_agents.push(agentDir);
1434
1598
  addedAgents.push(agentDir);
@@ -1436,9 +1600,9 @@ async function addCommand(items) {
1436
1600
  }
1437
1601
  }
1438
1602
  if (profileName === "statusline") {
1439
- const statuslineSrc = path11.join(root, "hooks", "statusline.sh");
1440
- if (fs8.existsSync(statuslineSrc)) {
1441
- fs8.copyFileSync(statuslineSrc, path11.join(claudeDir, "statusline.sh"));
1603
+ const statuslineSrc = path12.join(root, "hooks", "statusline.sh");
1604
+ if (fs9.existsSync(statuslineSrc)) {
1605
+ fs9.copyFileSync(statuslineSrc, path12.join(claudeDir, "statusline.sh"));
1442
1606
  statuslineAdded = true;
1443
1607
  }
1444
1608
  }
@@ -1447,7 +1611,7 @@ async function addCommand(items) {
1447
1611
  }
1448
1612
  if (!manifest.profiles.includes(profileName)) {
1449
1613
  manifest.profiles.push(profileName);
1450
- manifest.profile_command = manifest.profiles.filter((p) => p !== "core").join("+");
1614
+ manifest.profile_command = manifest.profiles.filter((p2) => p2 !== "core").join("+");
1451
1615
  }
1452
1616
  if (statuslineAdded) {
1453
1617
  addStatuslineToSettings(claudeDir);
@@ -1472,28 +1636,28 @@ async function addCommand(items) {
1472
1636
  };
1473
1637
  writeManifest(target, merged, manifest.profile_command, root);
1474
1638
  const totalAdded = added.length + addedRules.length + addedAgents.length + (statuslineAdded ? 1 : 0);
1475
- console.log(chalk2.bold(`
1639
+ console.log(chalk3.bold(`
1476
1640
  Added ${totalAdded} items:`));
1477
- for (const s of added) console.log(chalk2.green(` [ok] skill: ${s}`));
1478
- for (const r of addedRules) console.log(chalk2.green(` [ok] rule: ${r}`));
1479
- for (const a of addedAgents) console.log(chalk2.green(` [ok] agents: ${a}/`));
1480
- if (statuslineAdded) console.log(chalk2.green(" [ok] statusline.sh"));
1641
+ for (const s of added) console.log(chalk3.green(` [ok] skill: ${s}`));
1642
+ for (const r of addedRules) console.log(chalk3.green(` [ok] rule: ${r}`));
1643
+ for (const a of addedAgents) console.log(chalk3.green(` [ok] agents: ${a}/`));
1644
+ if (statuslineAdded) console.log(chalk3.green(" [ok] statusline.sh"));
1481
1645
  for (const s of skipped)
1482
- console.log(chalk2.dim(` [skip] ${s} (already installed)`));
1646
+ console.log(chalk3.dim(` [skip] ${s} (already installed)`));
1483
1647
  for (const s of notFound)
1484
- console.log(chalk2.red(` [miss] ${s} (not found in source)`));
1648
+ console.log(chalk3.red(` [miss] ${s} (not found in source)`));
1485
1649
  }
1486
1650
  function addSkill(root, target, skill, installed) {
1487
1651
  if (installed.includes(skill)) return "skipped";
1488
- const src = path11.join(root, "skills", skill);
1489
- if (!fs8.existsSync(src)) return "not-found";
1490
- const dst = path11.join(target, ".claude", "skills", skill);
1652
+ const src = path12.join(root, "skills", skill);
1653
+ if (!fs9.existsSync(src)) return "not-found";
1654
+ const dst = path12.join(target, ".claude", "skills", skill);
1491
1655
  copyDirSync(src, dst);
1492
1656
  return "added";
1493
1657
  }
1494
1658
  function mergePermissions(claudeDir, newPerms) {
1495
- const settingsPath = path11.join(claudeDir, "settings.json");
1496
- if (!fs8.existsSync(settingsPath)) return;
1659
+ const settingsPath = path12.join(claudeDir, "settings.json");
1660
+ if (!fs9.existsSync(settingsPath)) return;
1497
1661
  const settings = readJsonSync(settingsPath);
1498
1662
  const perms = settings.permissions ?? { allow: [], deny: [] };
1499
1663
  const allowSet = new Set(perms.allow);
@@ -1506,8 +1670,8 @@ function mergePermissions(claudeDir, newPerms) {
1506
1670
  writeJsonSync(settingsPath, settings);
1507
1671
  }
1508
1672
  function addStatuslineToSettings(claudeDir) {
1509
- const settingsPath = path11.join(claudeDir, "settings.json");
1510
- if (!fs8.existsSync(settingsPath)) return;
1673
+ const settingsPath = path12.join(claudeDir, "settings.json");
1674
+ if (!fs9.existsSync(settingsPath)) return;
1511
1675
  const settings = readJsonSync(settingsPath);
1512
1676
  if (settings.statusLine) return;
1513
1677
  settings.statusLine = {
@@ -1518,9 +1682,9 @@ function addStatuslineToSettings(claudeDir) {
1518
1682
  }
1519
1683
 
1520
1684
  // src/commands/remove.ts
1521
- import fs9 from "fs";
1522
- import path12 from "path";
1523
- import chalk3 from "chalk";
1685
+ import fs10 from "fs";
1686
+ import path13 from "path";
1687
+ import chalk4 from "chalk";
1524
1688
  var CORE_SKILLS = [
1525
1689
  "commit",
1526
1690
  "create-pr",
@@ -1549,7 +1713,7 @@ async function removeCommand(items, opts = {}) {
1549
1713
  const manifest = readManifest(target);
1550
1714
  if (!manifest) {
1551
1715
  console.error(
1552
- chalk3.red(
1716
+ chalk4.red(
1553
1717
  "No arcane-manifest.json found. Run `npx claude-code-arcane install` first."
1554
1718
  )
1555
1719
  );
@@ -1584,30 +1748,30 @@ async function removeCommand(items, opts = {}) {
1584
1748
  }
1585
1749
  manifest.total_skills = manifest.installed_skills.length;
1586
1750
  manifest.total_rules = manifest.installed_rules.length;
1587
- const mp = path12.join(target, ".claude", "arcane-manifest.json");
1751
+ const mp = path13.join(target, ".claude", "arcane-manifest.json");
1588
1752
  writeJsonSync(mp, manifest);
1589
1753
  const totalRemoved = removedSkills.length + removedAgents.length + removedProfiles.length;
1590
- console.log(chalk3.bold(`
1754
+ console.log(chalk4.bold(`
1591
1755
  Removed ${totalRemoved} items:`));
1592
- for (const p of removedProfiles)
1593
- console.log(chalk3.magenta(` [ok] +${p} (profile)`));
1756
+ for (const p2 of removedProfiles)
1757
+ console.log(chalk4.magenta(` [ok] +${p2} (profile)`));
1594
1758
  for (const s of removedSkills)
1595
- console.log(chalk3.green(` [ok] ${s} (skill)`));
1759
+ console.log(chalk4.green(` [ok] ${s} (skill)`));
1596
1760
  for (const a of removedAgents)
1597
- console.log(chalk3.cyan(` [ok] ${a} (agent dir)`));
1761
+ console.log(chalk4.cyan(` [ok] ${a} (agent dir)`));
1598
1762
  for (const s of skipped)
1599
- console.log(chalk3.dim(` [skip] ${s}`));
1763
+ console.log(chalk4.dim(` [skip] ${s}`));
1600
1764
  }
1601
1765
  function removeSkill(target, skill, manifest) {
1602
1766
  if (CORE_SKILLS.includes(skill)) {
1603
1767
  console.warn(
1604
- chalk3.yellow(` WARNING: '${skill}' is a core skill. Skipping.`)
1768
+ chalk4.yellow(` WARNING: '${skill}' is a core skill. Skipping.`)
1605
1769
  );
1606
1770
  return "skipped";
1607
1771
  }
1608
- const skillDir = path12.join(target, ".claude", "skills", skill);
1609
- if (!fs9.existsSync(skillDir)) return "skipped";
1610
- fs9.rmSync(skillDir, { recursive: true, force: true });
1772
+ const skillDir = path13.join(target, ".claude", "skills", skill);
1773
+ if (!fs10.existsSync(skillDir)) return "skipped";
1774
+ fs10.rmSync(skillDir, { recursive: true, force: true });
1611
1775
  manifest.installed_skills = manifest.installed_skills.filter(
1612
1776
  (s) => s !== skill
1613
1777
  );
@@ -1616,33 +1780,33 @@ function removeSkill(target, skill, manifest) {
1616
1780
  function removeProfile(root, target, profileName, manifest) {
1617
1781
  if (profileName === "core") {
1618
1782
  console.warn(
1619
- chalk3.yellow(" WARNING: Cannot remove core profile. Skipping.")
1783
+ chalk4.yellow(" WARNING: Cannot remove core profile. Skipping.")
1620
1784
  );
1621
1785
  return { removed: false, skills: [], agents: [] };
1622
1786
  }
1623
1787
  if (!manifest.profiles.includes(profileName)) {
1624
1788
  console.warn(
1625
- chalk3.yellow(` WARNING: Profile '${profileName}' is not installed.`)
1789
+ chalk4.yellow(` WARNING: Profile '${profileName}' is not installed.`)
1626
1790
  );
1627
1791
  return { removed: false, skills: [], agents: [] };
1628
1792
  }
1629
- const profilePath = path12.join(root, "profiles", `${profileName}.yaml`);
1630
- if (!fs9.existsSync(profilePath)) {
1793
+ const profilePath = path13.join(root, "profiles", `${profileName}.yaml`);
1794
+ if (!fs10.existsSync(profilePath)) {
1631
1795
  console.warn(
1632
- chalk3.yellow(` WARNING: Profile '${profileName}.yaml' not found.`)
1796
+ chalk4.yellow(` WARNING: Profile '${profileName}.yaml' not found.`)
1633
1797
  );
1634
1798
  return { removed: false, skills: [], agents: [] };
1635
1799
  }
1636
1800
  const profile = parseProfile(profilePath);
1637
1801
  const remainingProfiles = manifest.profiles.filter(
1638
- (p) => p !== profileName
1802
+ (p2) => p2 !== profileName
1639
1803
  );
1640
1804
  const sharedSkills = /* @__PURE__ */ new Set();
1641
1805
  const sharedAgents = /* @__PURE__ */ new Set();
1642
1806
  const sharedRules = /* @__PURE__ */ new Set();
1643
1807
  for (const rp of remainingProfiles) {
1644
- const rpPath = path12.join(root, "profiles", `${rp}.yaml`);
1645
- if (!fs9.existsSync(rpPath)) continue;
1808
+ const rpPath = path13.join(root, "profiles", `${rp}.yaml`);
1809
+ if (!fs10.existsSync(rpPath)) continue;
1646
1810
  const rpDef = parseProfile(rpPath);
1647
1811
  rpDef.skills.forEach((s) => sharedSkills.add(s));
1648
1812
  rpDef.agents.forEach((a) => sharedAgents.add(a));
@@ -1653,9 +1817,9 @@ function removeProfile(root, target, profileName, manifest) {
1653
1817
  for (const skill of profile.skills) {
1654
1818
  if (sharedSkills.has(skill)) continue;
1655
1819
  if (CORE_SKILLS.includes(skill)) continue;
1656
- const skillDir = path12.join(target, ".claude", "skills", skill);
1657
- if (fs9.existsSync(skillDir)) {
1658
- fs9.rmSync(skillDir, { recursive: true, force: true });
1820
+ const skillDir = path13.join(target, ".claude", "skills", skill);
1821
+ if (fs10.existsSync(skillDir)) {
1822
+ fs10.rmSync(skillDir, { recursive: true, force: true });
1659
1823
  removedSkills.push(skill);
1660
1824
  }
1661
1825
  manifest.installed_skills = manifest.installed_skills.filter(
@@ -1665,9 +1829,9 @@ function removeProfile(root, target, profileName, manifest) {
1665
1829
  const removedAgents = [];
1666
1830
  for (const agent of profile.agents) {
1667
1831
  if (sharedAgents.has(agent)) continue;
1668
- const agentDir = path12.join(target, ".claude", "agents", agent);
1669
- if (fs9.existsSync(agentDir)) {
1670
- fs9.rmSync(agentDir, { recursive: true, force: true });
1832
+ const agentDir = path13.join(target, ".claude", "agents", agent);
1833
+ if (fs10.existsSync(agentDir)) {
1834
+ fs10.rmSync(agentDir, { recursive: true, force: true });
1671
1835
  removedAgents.push(agent);
1672
1836
  }
1673
1837
  manifest.installed_agents = manifest.installed_agents.filter(
@@ -1679,138 +1843,133 @@ function removeProfile(root, target, profileName, manifest) {
1679
1843
  ...profile.rules.gamedev
1680
1844
  ]) {
1681
1845
  if (sharedRules.has(rule)) continue;
1682
- const ruleFile = path12.join(target, ".claude", "rules", `${rule}.md`);
1683
- if (fs9.existsSync(ruleFile)) {
1684
- fs9.rmSync(ruleFile);
1846
+ const ruleFile = path13.join(target, ".claude", "rules", `${rule}.md`);
1847
+ if (fs10.existsSync(ruleFile)) {
1848
+ fs10.rmSync(ruleFile);
1685
1849
  }
1686
1850
  manifest.installed_rules = manifest.installed_rules.filter(
1687
1851
  (r) => r !== rule
1688
1852
  );
1689
1853
  }
1690
1854
  if (profileName === "statusline") {
1691
- const statuslineFile = path12.join(target, ".claude", "statusline.sh");
1692
- if (fs9.existsSync(statuslineFile)) {
1693
- fs9.rmSync(statuslineFile);
1855
+ const statuslineFile = path13.join(target, ".claude", "statusline.sh");
1856
+ if (fs10.existsSync(statuslineFile)) {
1857
+ fs10.rmSync(statuslineFile);
1694
1858
  }
1695
- removeStatuslineFromSettings(path12.join(target, ".claude"));
1859
+ removeStatuslineFromSettings(path13.join(target, ".claude"));
1696
1860
  }
1697
1861
  manifest.profiles = remainingProfiles;
1698
- manifest.profile_command = remainingProfiles.filter((p) => p !== "core").join("+");
1862
+ manifest.profile_command = remainingProfiles.filter((p2) => p2 !== "core").join("+");
1699
1863
  return { removed: true, skills: removedSkills, agents: removedAgents };
1700
1864
  }
1701
1865
  function removeStatuslineFromSettings(claudeDir) {
1702
- const settingsPath = path12.join(claudeDir, "settings.json");
1703
- if (!fs9.existsSync(settingsPath)) return;
1704
- const settings = JSON.parse(fs9.readFileSync(settingsPath, "utf-8"));
1866
+ const settingsPath = path13.join(claudeDir, "settings.json");
1867
+ if (!fs10.existsSync(settingsPath)) return;
1868
+ const settings = JSON.parse(fs10.readFileSync(settingsPath, "utf-8"));
1705
1869
  if (settings.statusLine) {
1706
1870
  delete settings.statusLine;
1707
- fs9.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
1871
+ fs10.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
1708
1872
  }
1709
1873
  }
1710
1874
 
1711
1875
  // src/commands/list.ts
1712
- import fs10 from "fs";
1713
- import path13 from "path";
1714
- import chalk4 from "chalk";
1876
+ import fs11 from "fs";
1877
+ import path14 from "path";
1878
+ import chalk5 from "chalk";
1715
1879
  async function listCommand() {
1716
1880
  const source = await resolveContentSource({ quiet: true });
1717
1881
  const root = await source.getContentRoot();
1718
- const profilesDir = path13.join(root, "profiles");
1719
- const skillsDir = path13.join(root, "skills");
1882
+ const profilesDir = path14.join(root, "profiles");
1883
+ const skillsDir = path14.join(root, "skills");
1720
1884
  const target = process.cwd();
1721
1885
  const manifest = readManifest(target);
1722
1886
  const profiles = listProfiles(profilesDir);
1723
- const bases = profiles.filter((p) => p.type === "base");
1724
- const addons = profiles.filter((p) => p.type === "addon");
1725
1887
  const installedSkills = new Set(manifest?.installed_skills ?? []);
1726
- console.log(chalk4.bold("\n=== Available Profiles ===\n"));
1727
- console.log(chalk4.cyan("Base:"));
1728
- for (const p of bases) {
1729
- const tag = manifest?.profiles.includes(p.name) ? chalk4.green(" [installed]") : "";
1730
- console.log(
1731
- ` ${chalk4.green(p.name.padEnd(20))} ${p.description}${tag}`
1732
- );
1733
- }
1734
- console.log(chalk4.cyan("\nAddons:"));
1735
- for (const p of addons) {
1736
- const tag = manifest?.profiles.includes(p.name) ? chalk4.green(" [installed]") : "";
1737
- console.log(
1738
- ` ${chalk4.green(("+" + p.name).padEnd(20))} ${p.description}${tag}`
1739
- );
1888
+ console.log(chalk5.bold("\n=== Available Profiles ===\n"));
1889
+ console.log(chalk5.dim("Profiles combine freely: install a+b+c\n"));
1890
+ for (const group of groupByCategory(profiles)) {
1891
+ console.log(chalk5.cyan(`${group.label}:`));
1892
+ for (const p2 of group.profiles) {
1893
+ const tag = manifest?.profiles.includes(p2.name) ? chalk5.green(" [installed]") : "";
1894
+ console.log(
1895
+ ` ${chalk5.green(p2.name.padEnd(20))} ${p2.description}${tag}`
1896
+ );
1897
+ }
1898
+ console.log();
1740
1899
  }
1741
- if (fs10.existsSync(skillsDir)) {
1742
- const allSkills = fs10.readdirSync(skillsDir, { withFileTypes: true }).filter(
1743
- (d) => d.isDirectory() && !d.name.startsWith("_") && fs10.existsSync(path13.join(skillsDir, d.name, "SKILL.md"))
1900
+ if (fs11.existsSync(skillsDir)) {
1901
+ const allSkills = fs11.readdirSync(skillsDir, { withFileTypes: true }).filter(
1902
+ (d) => d.isDirectory() && !d.name.startsWith("_") && fs11.existsSync(path14.join(skillsDir, d.name, "SKILL.md"))
1744
1903
  ).map((d) => d.name).sort();
1745
- console.log(chalk4.bold(`
1904
+ console.log(chalk5.bold(`
1746
1905
  === Skills (${allSkills.length}) ===
1747
1906
  `));
1748
1907
  const cols = 3;
1749
1908
  for (let i = 0; i < allSkills.length; i += cols) {
1750
1909
  const row = allSkills.slice(i, i + cols).map((s) => {
1751
- const tag = installedSkills.has(s) ? chalk4.green("*") : " ";
1910
+ const tag = installedSkills.has(s) ? chalk5.green("*") : " ";
1752
1911
  return `${tag} ${s.padEnd(30)}`;
1753
1912
  });
1754
1913
  console.log(" " + row.join(""));
1755
1914
  }
1756
1915
  if (manifest) {
1757
- console.log(chalk4.dim("\n * = installed in current project"));
1916
+ console.log(chalk5.dim("\n * = installed in current project"));
1758
1917
  }
1759
1918
  }
1760
1919
  }
1761
1920
 
1762
1921
  // src/commands/status.ts
1763
- import chalk5 from "chalk";
1922
+ import chalk6 from "chalk";
1764
1923
  async function statusCommand() {
1765
1924
  const target = process.cwd();
1766
1925
  const manifest = readManifest(target);
1767
1926
  if (!manifest) {
1768
1927
  console.log(
1769
- chalk5.yellow("No Arcane installation found in this project.")
1928
+ chalk6.yellow("No Arcane installation found in this project.")
1770
1929
  );
1771
- console.log(chalk5.dim("Run `npx claude-code-arcane install <profile>` to install."));
1930
+ console.log(chalk6.dim("Run `npx claude-code-arcane install <profile>` to install."));
1772
1931
  return;
1773
1932
  }
1774
- const profiles = manifest.profiles.filter((p) => p !== "core");
1775
- console.log(chalk5.bold("\n=== Arcane Status ===\n"));
1776
- console.log(` Profiles: ${chalk5.cyan(profiles.join(" + "))}`);
1933
+ const profiles = manifest.profiles.filter((p2) => p2 !== "core");
1934
+ console.log(chalk6.bold("\n=== Arcane Status ===\n"));
1935
+ console.log(` Profiles: ${chalk6.cyan(profiles.join(" + "))}`);
1777
1936
  console.log(` Installed: ${manifest.installed_at}`);
1778
1937
  console.log(` Version: ${manifest.arcane_version}`);
1779
1938
  console.log(` CLI: ${manifest.cli}`);
1780
- console.log(` Source: ${chalk5.dim(manifest.source)}`);
1939
+ console.log(` Source: ${chalk6.dim(manifest.source)}`);
1781
1940
  if (manifest.worktree) {
1782
1941
  const wt = manifest.worktree;
1783
1942
  console.log(
1784
- ` Worktree: ${wt.is_worktree ? chalk5.blue("yes") : "no"} (main: ${chalk5.dim(wt.main_worktree)})`
1943
+ ` Worktree: ${wt.is_worktree ? chalk6.blue("yes") : "no"} (main: ${chalk6.dim(wt.main_worktree)})`
1785
1944
  );
1786
1945
  if (wt.shared_dirs.length > 0) {
1787
1946
  console.log(
1788
- ` Shared: ${chalk5.blue(wt.shared_dirs.join(", "))} (symlinked)`
1947
+ ` Shared: ${chalk6.blue(wt.shared_dirs.join(", "))} (symlinked)`
1789
1948
  );
1790
1949
  }
1791
1950
  }
1792
- console.log(chalk5.bold(`
1951
+ console.log(chalk6.bold(`
1793
1952
  Skills (${manifest.total_skills}):`));
1794
1953
  const skills = manifest.installed_skills ?? [];
1795
1954
  for (let i = 0; i < skills.length; i += 5) {
1796
1955
  const row = skills.slice(i, i + 5).map((s) => s.padEnd(25)).join("");
1797
1956
  console.log(` ${row}`);
1798
1957
  }
1799
- console.log(chalk5.bold(`
1958
+ console.log(chalk6.bold(`
1800
1959
  Rules (${manifest.total_rules}):`));
1801
1960
  const rules = manifest.installed_rules ?? [];
1802
1961
  console.log(` ${rules.join(", ")}`);
1803
- console.log(chalk5.bold("\n Agents:"));
1962
+ console.log(chalk6.bold("\n Agents:"));
1804
1963
  const agents = manifest.installed_agents ?? [];
1805
1964
  console.log(` ${agents.join(", ")}`);
1806
1965
  console.log();
1807
1966
  }
1808
1967
 
1809
1968
  // src/commands/update.ts
1810
- import fs11 from "fs";
1811
- import path14 from "path";
1969
+ import fs12 from "fs";
1970
+ import path15 from "path";
1812
1971
  import os4 from "os";
1813
- import chalk6 from "chalk";
1972
+ import chalk7 from "chalk";
1814
1973
 
1815
1974
  // src/self-update.ts
1816
1975
  import { spawnSync } from "child_process";
@@ -1872,45 +2031,45 @@ async function updateCommand(opts) {
1872
2031
  });
1873
2032
  if (!opts.quiet) {
1874
2033
  if (self.updated) {
1875
- console.log(chalk6.green("npm package updated to latest."));
2034
+ console.log(chalk7.green("npm package updated to latest."));
1876
2035
  } else if (self.skipped && self.reason !== "dev/test environment") {
1877
- console.log(chalk6.dim(`npm self-update skipped (${self.reason}).`));
2036
+ console.log(chalk7.dim(`npm self-update skipped (${self.reason}).`));
1878
2037
  } else if (!self.skipped) {
1879
- console.log(chalk6.yellow(`npm self-update failed: ${self.reason}`));
2038
+ console.log(chalk7.yellow(`npm self-update failed: ${self.reason}`));
1880
2039
  }
1881
2040
  }
1882
2041
  const cwd = process.cwd();
1883
- if (fs11.existsSync(manifestPath(cwd))) {
2042
+ if (fs12.existsSync(manifestPath(cwd))) {
1884
2043
  registerInstallation(cwd);
1885
2044
  }
1886
2045
  const targets = pruneRegistry().map((e) => e.path);
1887
2046
  const globalTarget = os4.homedir();
1888
- if (!process.env.VITEST && fs11.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
2047
+ if (!process.env.VITEST && fs12.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
1889
2048
  targets.push(globalTarget);
1890
2049
  }
1891
2050
  if (targets.length === 0) {
1892
2051
  if (!opts.quiet) {
1893
- console.log(chalk6.red("\nNo Arcane installations found. Run 'arcane install' first."));
2052
+ console.log(chalk7.red("\nNo Arcane installations found. Run 'arcane install' first."));
1894
2053
  }
1895
2054
  return;
1896
2055
  }
1897
2056
  if (!opts.quiet) {
1898
2057
  console.log(
1899
- chalk6.bold(`
2058
+ chalk7.bold(`
1900
2059
  Updating ${targets.length} installation${targets.length === 1 ? "" : "s"}...`)
1901
2060
  );
1902
2061
  }
1903
2062
  const results = [];
1904
2063
  for (const target of targets) {
1905
2064
  if (!opts.quiet) {
1906
- console.log(chalk6.bold.cyan(`
2065
+ console.log(chalk7.bold.cyan(`
1907
2066
  \u2022 ${target}`));
1908
2067
  }
1909
2068
  try {
1910
2069
  results.push(await updateTarget(target, opts));
1911
2070
  } catch (err) {
1912
2071
  if (!opts.quiet) {
1913
- console.log(chalk6.red(` Update failed: ${err.message}`));
2072
+ console.log(chalk7.red(` Update failed: ${err.message}`));
1914
2073
  }
1915
2074
  results.push({
1916
2075
  target,
@@ -1929,7 +2088,7 @@ async function updateTarget(target, opts) {
1929
2088
  const manifest = readManifest(target);
1930
2089
  if (!manifest) {
1931
2090
  if (!opts.quiet) {
1932
- console.log(chalk6.red("No Arcane installation found. Run 'arcane install' first."));
2091
+ console.log(chalk7.red("No Arcane installation found. Run 'arcane install' first."));
1933
2092
  }
1934
2093
  return { target, status: "no-manifest", updated: 0, skipped: 0, removed: 0 };
1935
2094
  }
@@ -1942,7 +2101,7 @@ async function updateTarget(target, opts) {
1942
2101
  const installedVersion = manifest.source_version ?? manifest.arcane_version;
1943
2102
  if (installedVersion === currentVersion && !opts.force) {
1944
2103
  if (!opts.quiet) {
1945
- console.log(chalk6.green(`Already up to date (v${currentVersion}).`));
2104
+ console.log(chalk7.green(`Already up to date (v${currentVersion}).`));
1946
2105
  }
1947
2106
  return {
1948
2107
  target,
@@ -1956,16 +2115,16 @@ async function updateTarget(target, opts) {
1956
2115
  }
1957
2116
  if (!opts.quiet) {
1958
2117
  console.log(
1959
- chalk6.bold(`
1960
- Update available: ${chalk6.red(installedVersion)} -> ${chalk6.green(currentVersion)}`)
2118
+ chalk7.bold(`
2119
+ Update available: ${chalk7.red(installedVersion)} -> ${chalk7.green(currentVersion)}`)
1961
2120
  );
1962
2121
  }
1963
2122
  const profileNames = manifest.profile_command.split("+").filter(Boolean);
1964
- const profilesDir = path14.join(root, "profiles");
2123
+ const profilesDir = path15.join(root, "profiles");
1965
2124
  const merged = mergeProfiles(profilesDir, profileNames);
1966
2125
  const allRules = [...merged.rules.universal, ...merged.rules.gamedev];
1967
2126
  const sourceHashes = computeSourceHashes(root, merged.skills, allRules, merged.agents);
1968
- const claudeDir = path14.join(target, ".claude");
2127
+ const claudeDir = path15.join(target, ".claude");
1969
2128
  const installedHashes = computeContentHashes(claudeDir);
1970
2129
  const items = computeUpdatePlan(
1971
2130
  manifest.content_hashes ?? null,
@@ -1993,7 +2152,7 @@ Update available: ${chalk6.red(installedVersion)} -> ${chalk6.green(currentVersi
1993
2152
  });
1994
2153
  }
1995
2154
  if (!opts.quiet) {
1996
- console.log(chalk6.green("\nNo changes to apply."));
2155
+ console.log(chalk7.green("\nNo changes to apply."));
1997
2156
  }
1998
2157
  return {
1999
2158
  target,
@@ -2030,7 +2189,7 @@ Update available: ${chalk6.red(installedVersion)} -> ${chalk6.green(currentVersi
2030
2189
  total_rules: allRules.length
2031
2190
  });
2032
2191
  if (!opts.quiet) {
2033
- console.log(chalk6.green(`
2192
+ console.log(chalk7.green(`
2034
2193
  Updated to v${currentVersion}.`));
2035
2194
  console.log(` ${updates.length} updated, ${skipped.length} skipped, ${removed.length} removed`);
2036
2195
  }
@@ -2045,12 +2204,12 @@ Updated to v${currentVersion}.`));
2045
2204
  };
2046
2205
  }
2047
2206
  function printGeneralSummary(results, isDryRun) {
2048
- const prefix = isDryRun ? chalk6.yellow("[dry-run] ") : "";
2207
+ const prefix = isDryRun ? chalk7.yellow("[dry-run] ") : "";
2049
2208
  const updated = results.filter((r) => r.status === "updated" || r.status === "dry-run").length;
2050
2209
  const unchanged = results.filter((r) => r.status === "up-to-date" || r.status === "no-changes").length;
2051
- console.log(chalk6.bold(`
2210
+ console.log(chalk7.bold(`
2052
2211
  ${prefix}Done. ${results.length} installation${results.length === 1 ? "" : "s"} processed:`));
2053
- console.log(` ${chalk6.green(updated)} ${isDryRun ? "would change" : "updated"}, ${chalk6.dim(unchanged + " unchanged")}`);
2212
+ console.log(` ${chalk7.green(updated)} ${isDryRun ? "would change" : "updated"}, ${chalk7.dim(unchanged + " unchanged")}`);
2054
2213
  }
2055
2214
  function computeUpdatePlan(manifestHashes, installedHashes, sourceHashes) {
2056
2215
  const items = [];
@@ -2091,7 +2250,7 @@ function resolveAction(manifestHash, installedHash, sourceHash) {
2091
2250
  return "conflict";
2092
2251
  }
2093
2252
  function printUpdateSummary(items, isDryRun) {
2094
- const prefix = isDryRun ? chalk6.yellow("[dry-run] ") : "";
2253
+ const prefix = isDryRun ? chalk7.yellow("[dry-run] ") : "";
2095
2254
  const updates = items.filter((i) => i.action === "update");
2096
2255
  const adds = items.filter((i) => i.action === "add");
2097
2256
  const conflicts = items.filter((i) => i.action === "conflict");
@@ -2099,37 +2258,37 @@ function printUpdateSummary(items, isDryRun) {
2099
2258
  const removed = items.filter((i) => i.action === "remove");
2100
2259
  if (updates.length > 0) {
2101
2260
  console.log(`
2102
- ${prefix}${chalk6.cyan("Will update:")}`);
2261
+ ${prefix}${chalk7.cyan("Will update:")}`);
2103
2262
  for (const item of updates) {
2104
- console.log(` ${chalk6.green("~")} ${item.type}: ${item.name}`);
2263
+ console.log(` ${chalk7.green("~")} ${item.type}: ${item.name}`);
2105
2264
  }
2106
2265
  }
2107
2266
  if (adds.length > 0) {
2108
2267
  console.log(`
2109
- ${prefix}${chalk6.cyan("Will add:")}`);
2268
+ ${prefix}${chalk7.cyan("Will add:")}`);
2110
2269
  for (const item of adds) {
2111
- console.log(` ${chalk6.green("+")} ${item.type}: ${item.name}`);
2270
+ console.log(` ${chalk7.green("+")} ${item.type}: ${item.name}`);
2112
2271
  }
2113
2272
  }
2114
2273
  if (removed.length > 0) {
2115
2274
  console.log(`
2116
- ${prefix}${chalk6.cyan("Will remove:")}`);
2275
+ ${prefix}${chalk7.cyan("Will remove:")}`);
2117
2276
  for (const item of removed) {
2118
- console.log(` ${chalk6.red("-")} ${item.type}: ${item.name}`);
2277
+ console.log(` ${chalk7.red("-")} ${item.type}: ${item.name}`);
2119
2278
  }
2120
2279
  }
2121
2280
  if (conflicts.length > 0) {
2122
2281
  console.log(`
2123
- ${prefix}${chalk6.yellow("Conflicts (will backup + update):")}`);
2282
+ ${prefix}${chalk7.yellow("Conflicts (will backup + update):")}`);
2124
2283
  for (const item of conflicts) {
2125
- console.log(` ${chalk6.yellow("!")} ${item.type}: ${item.name}`);
2284
+ console.log(` ${chalk7.yellow("!")} ${item.type}: ${item.name}`);
2126
2285
  }
2127
2286
  }
2128
2287
  if (customized.length > 0) {
2129
2288
  console.log(`
2130
- ${prefix}${chalk6.dim("Skipped (locally customized):")}`);
2289
+ ${prefix}${chalk7.dim("Skipped (locally customized):")}`);
2131
2290
  for (const item of customized) {
2132
- console.log(` ${chalk6.dim(" " + item.type + ": " + item.name)}`);
2291
+ console.log(` ${chalk7.dim(" " + item.type + ": " + item.name)}`);
2133
2292
  }
2134
2293
  }
2135
2294
  }
@@ -2158,48 +2317,48 @@ function applyUpdates(items, root, claudeDir, merged, force) {
2158
2317
  function applyItem(item, root, claudeDir) {
2159
2318
  const src = getSourcePath(item, root);
2160
2319
  const dst = getInstalledPath(item, claudeDir);
2161
- if (!src || !fs11.existsSync(src)) return;
2320
+ if (!src || !fs12.existsSync(src)) return;
2162
2321
  if (item.type === "skill" || item.type === "agent") {
2163
- if (fs11.existsSync(dst)) {
2164
- fs11.rmSync(dst, { recursive: true, force: true });
2322
+ if (fs12.existsSync(dst)) {
2323
+ fs12.rmSync(dst, { recursive: true, force: true });
2165
2324
  }
2166
2325
  copyDirSync(src, dst);
2167
2326
  } else {
2168
- const dir = path14.dirname(dst);
2169
- if (!fs11.existsSync(dir)) {
2170
- fs11.mkdirSync(dir, { recursive: true });
2327
+ const dir = path15.dirname(dst);
2328
+ if (!fs12.existsSync(dir)) {
2329
+ fs12.mkdirSync(dir, { recursive: true });
2171
2330
  }
2172
- fs11.copyFileSync(src, dst);
2331
+ fs12.copyFileSync(src, dst);
2173
2332
  }
2174
2333
  }
2175
2334
  function backupItem(item, claudeDir) {
2176
2335
  const installed = getInstalledPath(item, claudeDir);
2177
- if (!fs11.existsSync(installed)) return;
2336
+ if (!fs12.existsSync(installed)) return;
2178
2337
  const backupPath = installed + ".bak";
2179
- if (fs11.existsSync(backupPath)) {
2180
- fs11.rmSync(backupPath, { recursive: true, force: true });
2338
+ if (fs12.existsSync(backupPath)) {
2339
+ fs12.rmSync(backupPath, { recursive: true, force: true });
2181
2340
  }
2182
- fs11.renameSync(installed, backupPath);
2341
+ fs12.renameSync(installed, backupPath);
2183
2342
  }
2184
2343
  function removeItem(item, claudeDir) {
2185
2344
  const installed = getInstalledPath(item, claudeDir);
2186
- if (fs11.existsSync(installed)) {
2187
- fs11.rmSync(installed, { recursive: true, force: true });
2345
+ if (fs12.existsSync(installed)) {
2346
+ fs12.rmSync(installed, { recursive: true, force: true });
2188
2347
  }
2189
2348
  }
2190
2349
  function getSourcePath(item, root) {
2191
2350
  switch (item.type) {
2192
2351
  case "skill":
2193
- return path14.join(root, "skills", item.name);
2352
+ return path15.join(root, "skills", item.name);
2194
2353
  case "rule": {
2195
- const direct = path14.join(root, "rules", item.name);
2196
- if (fs11.existsSync(direct)) return direct;
2197
- return path14.join(root, "rules", "gamedev", item.name);
2354
+ const direct = path15.join(root, "rules", item.name);
2355
+ if (fs12.existsSync(direct)) return direct;
2356
+ return path15.join(root, "rules", "gamedev", item.name);
2198
2357
  }
2199
2358
  case "agent":
2200
- return path14.join(root, "agents", item.name);
2359
+ return path15.join(root, "agents", item.name);
2201
2360
  case "hook":
2202
- return path14.join(root, "hooks", item.name);
2361
+ return path15.join(root, "hooks", item.name);
2203
2362
  default:
2204
2363
  return null;
2205
2364
  }
@@ -2207,26 +2366,26 @@ function getSourcePath(item, root) {
2207
2366
  function getInstalledPath(item, claudeDir) {
2208
2367
  switch (item.type) {
2209
2368
  case "skill":
2210
- return path14.join(claudeDir, "skills", item.name);
2369
+ return path15.join(claudeDir, "skills", item.name);
2211
2370
  case "rule":
2212
- return path14.join(claudeDir, "rules", item.name);
2371
+ return path15.join(claudeDir, "rules", item.name);
2213
2372
  case "agent":
2214
- return path14.join(claudeDir, "agents", item.name);
2373
+ return path15.join(claudeDir, "agents", item.name);
2215
2374
  case "hook":
2216
- return path14.join(claudeDir, "hooks", item.name);
2375
+ return path15.join(claudeDir, "hooks", item.name);
2217
2376
  }
2218
2377
  }
2219
2378
 
2220
2379
  // src/commands/clean.ts
2221
- import chalk7 from "chalk";
2380
+ import chalk8 from "chalk";
2222
2381
  async function cleanCommand(opts) {
2223
2382
  const target = process.cwd();
2224
2383
  const manifest = readManifest(target);
2225
2384
  if (!manifest) {
2226
- console.log(chalk7.yellow("No Arcane installation found."));
2385
+ console.log(chalk8.yellow("No Arcane installation found."));
2227
2386
  return;
2228
2387
  }
2229
- console.log(chalk7.bold("\nThis will remove:"));
2388
+ console.log(chalk8.bold("\nThis will remove:"));
2230
2389
  console.log(` - ${manifest.total_skills} skills in .claude/skills/`);
2231
2390
  console.log(` - ${manifest.total_rules} rules in .claude/rules/`);
2232
2391
  console.log(
@@ -2237,33 +2396,33 @@ async function cleanCommand(opts) {
2237
2396
  console.log(" - arcane-manifest.json");
2238
2397
  if (!opts.force) {
2239
2398
  console.log(
2240
- chalk7.yellow("\n Pass --force to confirm removal.\n")
2399
+ chalk8.yellow("\n Pass --force to confirm removal.\n")
2241
2400
  );
2242
2401
  return;
2243
2402
  }
2244
2403
  clean(target);
2245
2404
  console.log(
2246
- chalk7.green("\n Run `npx claude-code-arcane install` to reinstall.\n")
2405
+ chalk8.green("\n Run `npx claude-code-arcane install` to reinstall.\n")
2247
2406
  );
2248
2407
  }
2249
2408
 
2250
2409
  // src/commands/worktree.ts
2251
- import fs12 from "fs";
2252
- import path15 from "path";
2410
+ import fs13 from "fs";
2411
+ import path16 from "path";
2253
2412
  import { execSync as execSync2 } from "child_process";
2254
- import chalk8 from "chalk";
2413
+ import chalk9 from "chalk";
2255
2414
  async function worktreeCommand(branch, opts) {
2256
2415
  const cwd = process.cwd();
2257
2416
  const wtInfo = getWorktreeInfo(cwd);
2258
2417
  if (!wtInfo) {
2259
- console.error(chalk8.red("Not a git repository."));
2418
+ console.error(chalk9.red("Not a git repository."));
2260
2419
  process.exit(1);
2261
2420
  }
2262
2421
  const repoRoot = wtInfo.isWorktree ? wtInfo.mainWorktreePath : cwd;
2263
- const worktreePath = opts.path ? path15.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
2264
- if (fs12.existsSync(worktreePath)) {
2422
+ const worktreePath = opts.path ? path16.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
2423
+ if (fs13.existsSync(worktreePath)) {
2265
2424
  console.error(
2266
- chalk8.red(`Path already exists: ${worktreePath}`)
2425
+ chalk9.red(`Path already exists: ${worktreePath}`)
2267
2426
  );
2268
2427
  process.exit(1);
2269
2428
  }
@@ -2273,7 +2432,7 @@ async function worktreeCommand(branch, opts) {
2273
2432
  if (manifest) {
2274
2433
  profileExpr = manifest.profile_command;
2275
2434
  console.log(
2276
- chalk8.dim(
2435
+ chalk9.dim(
2277
2436
  ` Inheriting profile from current installation: ${profileExpr}`
2278
2437
  )
2279
2438
  );
@@ -2281,34 +2440,34 @@ async function worktreeCommand(branch, opts) {
2281
2440
  }
2282
2441
  if (!profileExpr) {
2283
2442
  console.error(
2284
- chalk8.red(
2443
+ chalk9.red(
2285
2444
  "No profile specified and no existing installation to inherit from.\nUse --profile to specify one: npx claude-code-arcane worktree feat/api --profile backend-ts+agile"
2286
2445
  )
2287
2446
  );
2288
2447
  process.exit(1);
2289
2448
  }
2290
- console.log(chalk8.bold("\nCreating worktree:"));
2291
- console.log(` Branch: ${chalk8.cyan(branch)}`);
2449
+ console.log(chalk9.bold("\nCreating worktree:"));
2450
+ console.log(` Branch: ${chalk9.cyan(branch)}`);
2292
2451
  console.log(` Path: ${worktreePath}`);
2293
- console.log(` Profile: ${chalk8.green(profileExpr)}`);
2452
+ console.log(` Profile: ${chalk9.green(profileExpr)}`);
2294
2453
  if (opts.base) console.log(` Base: ${opts.base}`);
2295
2454
  if (opts.dryRun) {
2296
- console.log(chalk8.yellow("\n [dry-run] Would create worktree and install Arcane."));
2455
+ console.log(chalk9.yellow("\n [dry-run] Would create worktree and install Arcane."));
2297
2456
  return;
2298
2457
  }
2299
2458
  console.log("\n Creating git worktree...");
2300
2459
  try {
2301
2460
  createGitWorktree(repoRoot, worktreePath, branch, opts.base);
2302
- console.log(chalk8.green(" [ok] Git worktree created"));
2461
+ console.log(chalk9.green(" [ok] Git worktree created"));
2303
2462
  } catch (err) {
2304
2463
  console.error(
2305
- chalk8.red(` Failed to create worktree: ${err.message}`)
2464
+ chalk9.red(` Failed to create worktree: ${err.message}`)
2306
2465
  );
2307
2466
  process.exit(1);
2308
2467
  }
2309
2468
  console.log(" Installing Arcane...");
2310
2469
  const root = getPackageRoot();
2311
- const profilesDir = path15.join(root, "profiles");
2470
+ const profilesDir = path16.join(root, "profiles");
2312
2471
  const profileNames = profileExpr.split("+").filter(Boolean);
2313
2472
  const merged = mergeProfiles(profilesDir, profileNames);
2314
2473
  const shareFrom = opts.share !== false ? cwd : void 0;
@@ -2323,7 +2482,7 @@ async function worktreeCommand(branch, opts) {
2323
2482
  main_worktree: repoRoot
2324
2483
  });
2325
2484
  registerInstallation(worktreePath);
2326
- console.log(chalk8.green(" [ok] Arcane installed"));
2485
+ console.log(chalk9.green(" [ok] Arcane installed"));
2327
2486
  if (opts.installDeps) {
2328
2487
  const pm = detectPackageManager(worktreePath);
2329
2488
  if (pm) {
@@ -2335,22 +2494,22 @@ async function worktreeCommand(branch, opts) {
2335
2494
  stdio: "pipe",
2336
2495
  timeout: 12e4
2337
2496
  });
2338
- console.log(chalk8.green(` [ok] Dependencies installed`));
2497
+ console.log(chalk9.green(` [ok] Dependencies installed`));
2339
2498
  } catch {
2340
2499
  console.warn(
2341
- chalk8.yellow(" WARN: Dependency install failed. Run manually.")
2500
+ chalk9.yellow(" WARN: Dependency install failed. Run manually.")
2342
2501
  );
2343
2502
  }
2344
2503
  }
2345
2504
  }
2346
2505
  if (opts.isolate) {
2347
- const isolateScript = path15.join(
2506
+ const isolateScript = path16.join(
2348
2507
  worktreePath,
2349
2508
  ".claude",
2350
2509
  "skills",
2351
2510
  "worktree-isolation"
2352
2511
  );
2353
- if (fs12.existsSync(isolateScript)) {
2512
+ if (fs13.existsSync(isolateScript)) {
2354
2513
  console.log(" Applying worktree isolation...");
2355
2514
  try {
2356
2515
  execSync2(
@@ -2362,36 +2521,36 @@ async function worktreeCommand(branch, opts) {
2362
2521
  timeout: 3e4
2363
2522
  }
2364
2523
  );
2365
- console.log(chalk8.green(" [ok] Worktree isolation applied"));
2524
+ console.log(chalk9.green(" [ok] Worktree isolation applied"));
2366
2525
  } catch {
2367
2526
  console.warn(
2368
- chalk8.yellow(
2527
+ chalk9.yellow(
2369
2528
  " WARN: Worktree isolation failed. Run /worktree-isolation manually."
2370
2529
  )
2371
2530
  );
2372
2531
  }
2373
2532
  } else {
2374
2533
  console.warn(
2375
- chalk8.yellow(
2534
+ chalk9.yellow(
2376
2535
  " WARN: worktree-isolation skill not installed. Add it with: npx claude-code-arcane add worktree-isolation"
2377
2536
  )
2378
2537
  );
2379
2538
  }
2380
2539
  }
2381
- console.log(chalk8.bold.green("\nWorktree ready:"));
2382
- console.log(` ${chalk8.cyan(`cd ${worktreePath}`)}`);
2383
- console.log(` ${chalk8.dim("claude")}
2540
+ console.log(chalk9.bold.green("\nWorktree ready:"));
2541
+ console.log(` ${chalk9.cyan(`cd ${worktreePath}`)}`);
2542
+ console.log(` ${chalk9.dim("claude")}
2384
2543
  `);
2385
2544
  }
2386
2545
 
2387
2546
  // src/commands/global.ts
2388
- import fs13 from "fs";
2389
- import path16 from "path";
2547
+ import fs14 from "fs";
2548
+ import path17 from "path";
2390
2549
  import os5 from "os";
2391
- import chalk9 from "chalk";
2392
- var CLAUDE_HOME = path16.join(os5.homedir(), ".claude");
2393
- var SCRIPTS_DIR = path16.join(CLAUDE_HOME, "scripts", "worktree-isolation");
2394
- var SETTINGS_PATH = path16.join(CLAUDE_HOME, "settings.json");
2550
+ import chalk10 from "chalk";
2551
+ var CLAUDE_HOME = path17.join(os5.homedir(), ".claude");
2552
+ var SCRIPTS_DIR = path17.join(CLAUDE_HOME, "scripts", "worktree-isolation");
2553
+ var SETTINGS_PATH = path17.join(CLAUDE_HOME, "settings.json");
2395
2554
  async function globalCommand(opts) {
2396
2555
  if (opts.status) {
2397
2556
  showStatus();
@@ -2404,100 +2563,100 @@ async function globalCommand(opts) {
2404
2563
  installGlobal();
2405
2564
  }
2406
2565
  function showStatus() {
2407
- console.log(chalk9.bold("\n=== Arcane Global Status ===\n"));
2408
- const hasScripts = fs13.existsSync(
2409
- path16.join(SCRIPTS_DIR, "apply.py")
2566
+ console.log(chalk10.bold("\n=== Arcane Global Status ===\n"));
2567
+ const hasScripts = fs14.existsSync(
2568
+ path17.join(SCRIPTS_DIR, "apply.py")
2410
2569
  );
2411
2570
  console.log(
2412
- ` Worktree scripts: ${hasScripts ? chalk9.green("installed") : chalk9.dim("not installed")}`
2571
+ ` Worktree scripts: ${hasScripts ? chalk10.green("installed") : chalk10.dim("not installed")}`
2413
2572
  );
2414
2573
  if (hasScripts) {
2415
- console.log(` ${chalk9.dim(SCRIPTS_DIR)}`);
2574
+ console.log(` ${chalk10.dim(SCRIPTS_DIR)}`);
2416
2575
  }
2417
2576
  const hookInstalled = isHookInstalled();
2418
2577
  console.log(
2419
- ` SessionStart hook: ${hookInstalled ? chalk9.green("active") : chalk9.dim("not configured")}`
2578
+ ` SessionStart hook: ${hookInstalled ? chalk10.green("active") : chalk10.dim("not configured")}`
2420
2579
  );
2421
- console.log(` ${chalk9.dim(SETTINGS_PATH)}`);
2580
+ console.log(` ${chalk10.dim(SETTINGS_PATH)}`);
2422
2581
  console.log();
2423
2582
  }
2424
2583
  function installGlobal() {
2425
- console.log(chalk9.bold("\nInstalling Arcane global hooks...\n"));
2584
+ console.log(chalk10.bold("\nInstalling Arcane global hooks...\n"));
2426
2585
  const root = getPackageRoot();
2427
- const scriptsSrc = path16.join(
2586
+ const scriptsSrc = path17.join(
2428
2587
  root,
2429
2588
  "skills",
2430
2589
  "worktree-isolation",
2431
2590
  "scripts"
2432
2591
  );
2433
- if (!fs13.existsSync(scriptsSrc)) {
2592
+ if (!fs14.existsSync(scriptsSrc)) {
2434
2593
  console.error(
2435
- chalk9.red(" worktree-isolation scripts not found in package.")
2594
+ chalk10.red(" worktree-isolation scripts not found in package.")
2436
2595
  );
2437
2596
  process.exit(1);
2438
2597
  }
2439
- fs13.mkdirSync(SCRIPTS_DIR, { recursive: true });
2598
+ fs14.mkdirSync(SCRIPTS_DIR, { recursive: true });
2440
2599
  for (const file of ["apply.py", "lib.py"]) {
2441
- const src = path16.join(scriptsSrc, file);
2442
- const dst = path16.join(SCRIPTS_DIR, file);
2443
- if (!fs13.existsSync(src)) {
2444
- console.warn(chalk9.yellow(` WARN: ${file} not found, skipping`));
2600
+ const src = path17.join(scriptsSrc, file);
2601
+ const dst = path17.join(SCRIPTS_DIR, file);
2602
+ if (!fs14.existsSync(src)) {
2603
+ console.warn(chalk10.yellow(` WARN: ${file} not found, skipping`));
2445
2604
  continue;
2446
2605
  }
2447
- if (fs13.existsSync(dst) && fs13.readFileSync(src).equals(fs13.readFileSync(dst))) {
2448
- console.log(chalk9.dim(` [skip] ${file} (already up-to-date)`));
2606
+ if (fs14.existsSync(dst) && fs14.readFileSync(src).equals(fs14.readFileSync(dst))) {
2607
+ console.log(chalk10.dim(` [skip] ${file} (already up-to-date)`));
2449
2608
  } else {
2450
- fs13.copyFileSync(src, dst);
2609
+ fs14.copyFileSync(src, dst);
2451
2610
  if (process.platform !== "win32") {
2452
- fs13.chmodSync(dst, 493);
2611
+ fs14.chmodSync(dst, 493);
2453
2612
  }
2454
- console.log(chalk9.green(` [ok] ${file} -> ${SCRIPTS_DIR}`));
2613
+ console.log(chalk10.green(` [ok] ${file} -> ${SCRIPTS_DIR}`));
2455
2614
  }
2456
2615
  }
2457
2616
  if (isHookInstalled()) {
2458
2617
  console.log(
2459
- chalk9.dim(" [skip] SessionStart hook (already configured)")
2618
+ chalk10.dim(" [skip] SessionStart hook (already configured)")
2460
2619
  );
2461
2620
  } else {
2462
2621
  addHookToSettings();
2463
2622
  console.log(
2464
- chalk9.green(" [ok] SessionStart hook added to ~/.claude/settings.json")
2623
+ chalk10.green(" [ok] SessionStart hook added to ~/.claude/settings.json")
2465
2624
  );
2466
2625
  }
2467
- console.log(chalk9.bold.green("\n Global hooks installed."));
2626
+ console.log(chalk10.bold.green("\n Global hooks installed."));
2468
2627
  console.log(
2469
- chalk9.dim(
2628
+ chalk10.dim(
2470
2629
  " Worktree isolation will auto-apply on every new Claude Code session.\n"
2471
2630
  )
2472
2631
  );
2473
2632
  }
2474
2633
  function removeGlobal() {
2475
- console.log(chalk9.bold("\nRemoving Arcane global hooks...\n"));
2476
- if (fs13.existsSync(SCRIPTS_DIR)) {
2477
- fs13.rmSync(SCRIPTS_DIR, { recursive: true, force: true });
2478
- console.log(chalk9.green(" [ok] Removed worktree-isolation scripts"));
2634
+ console.log(chalk10.bold("\nRemoving Arcane global hooks...\n"));
2635
+ if (fs14.existsSync(SCRIPTS_DIR)) {
2636
+ fs14.rmSync(SCRIPTS_DIR, { recursive: true, force: true });
2637
+ console.log(chalk10.green(" [ok] Removed worktree-isolation scripts"));
2479
2638
  } else {
2480
- console.log(chalk9.dim(" [skip] Scripts not found"));
2639
+ console.log(chalk10.dim(" [skip] Scripts not found"));
2481
2640
  }
2482
2641
  if (isHookInstalled()) {
2483
2642
  removeHookFromSettings();
2484
- console.log(chalk9.green(" [ok] Removed SessionStart hook"));
2643
+ console.log(chalk10.green(" [ok] Removed SessionStart hook"));
2485
2644
  } else {
2486
- console.log(chalk9.dim(" [skip] Hook not configured"));
2645
+ console.log(chalk10.dim(" [skip] Hook not configured"));
2487
2646
  }
2488
- console.log(chalk9.bold.green("\n Global hooks removed.\n"));
2647
+ console.log(chalk10.bold.green("\n Global hooks removed.\n"));
2489
2648
  }
2490
2649
  function readSettings() {
2491
- if (!fs13.existsSync(SETTINGS_PATH)) return {};
2650
+ if (!fs14.existsSync(SETTINGS_PATH)) return {};
2492
2651
  try {
2493
- return JSON.parse(fs13.readFileSync(SETTINGS_PATH, "utf-8"));
2652
+ return JSON.parse(fs14.readFileSync(SETTINGS_PATH, "utf-8"));
2494
2653
  } catch {
2495
2654
  return {};
2496
2655
  }
2497
2656
  }
2498
2657
  function writeSettings(settings) {
2499
- fs13.mkdirSync(path16.dirname(SETTINGS_PATH), { recursive: true });
2500
- fs13.writeFileSync(
2658
+ fs14.mkdirSync(path17.dirname(SETTINGS_PATH), { recursive: true });
2659
+ fs14.writeFileSync(
2501
2660
  SETTINGS_PATH,
2502
2661
  JSON.stringify(settings, null, 2) + "\n",
2503
2662
  "utf-8"
@@ -2554,11 +2713,11 @@ function removeHookFromSettings() {
2554
2713
  }
2555
2714
 
2556
2715
  // src/update-check.ts
2557
- import fs14 from "fs";
2558
- import path17 from "path";
2716
+ import fs15 from "fs";
2717
+ import path18 from "path";
2559
2718
  import os6 from "os";
2560
- import chalk10 from "chalk";
2561
- var CHECK_FILE = path17.join(os6.homedir(), ".arcane", "last-check.json");
2719
+ import chalk11 from "chalk";
2720
+ var CHECK_FILE = path18.join(os6.homedir(), ".arcane", "last-check.json");
2562
2721
  var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
2563
2722
  var GITHUB_OWNER2 = "SebastianLuser";
2564
2723
  var GITHUB_REPO2 = "Claude-Code-Arcane";
@@ -2598,11 +2757,11 @@ async function checkForUpdates(opts = {}) {
2598
2757
  }
2599
2758
  function printUpdateNotice(localVersion, remoteSha) {
2600
2759
  console.log(
2601
- chalk10.yellow(`
2760
+ chalk11.yellow(`
2602
2761
  Arcane update available: ${localVersion} \u2192 ${remoteSha}`)
2603
2762
  );
2604
2763
  console.log(
2605
- chalk10.dim(" Run: arcane update (--dry-run to preview)\n")
2764
+ chalk11.dim(" Run: arcane update (--dry-run to preview)\n")
2606
2765
  );
2607
2766
  }
2608
2767
  async function getLatestCommitSha() {
@@ -2626,19 +2785,19 @@ async function getLatestCommitSha() {
2626
2785
  }
2627
2786
  function readCachedCheck() {
2628
2787
  try {
2629
- if (!fs14.existsSync(CHECK_FILE)) return null;
2630
- return JSON.parse(fs14.readFileSync(CHECK_FILE, "utf-8"));
2788
+ if (!fs15.existsSync(CHECK_FILE)) return null;
2789
+ return JSON.parse(fs15.readFileSync(CHECK_FILE, "utf-8"));
2631
2790
  } catch {
2632
2791
  return null;
2633
2792
  }
2634
2793
  }
2635
2794
  function writeCachedCheck(result) {
2636
2795
  try {
2637
- const dir = path17.dirname(CHECK_FILE);
2638
- if (!fs14.existsSync(dir)) {
2639
- fs14.mkdirSync(dir, { recursive: true });
2796
+ const dir = path18.dirname(CHECK_FILE);
2797
+ if (!fs15.existsSync(dir)) {
2798
+ fs15.mkdirSync(dir, { recursive: true });
2640
2799
  }
2641
- fs14.writeFileSync(CHECK_FILE, JSON.stringify(result, null, 2) + "\n");
2800
+ fs15.writeFileSync(CHECK_FILE, JSON.stringify(result, null, 2) + "\n");
2642
2801
  } catch {
2643
2802
  }
2644
2803
  }
@@ -2649,7 +2808,7 @@ program.name("arcane").description(
2649
2808
  "Claude Code Arcane \u2014 Skills, agents, hooks and rules for Claude Code"
2650
2809
  ).version(getPackageVersion());
2651
2810
  program.command("install [profile]").description(
2652
- "Install a profile (e.g., backend-ts+agile). Without args, shows available profiles."
2811
+ "Install profiles (e.g., backend-ts+agile). Without args, opens an interactive picker."
2653
2812
  ).option("-t, --target <dir>", "Target project directory", process.cwd()).option("-n, --dry-run", "Show what would be installed without changing files").option("-f, --force", "Overwrite without backup prompt").option(
2654
2813
  "-s, --share-from <dir>",
2655
2814
  "Share hooks/docs from an existing Arcane installation (e.g., main worktree)"