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.
- package/CHANGELOG.md +8 -0
- package/README.md +78 -36
- package/dist/cli.js +637 -478
- package/docs/USER-GUIDE.md +27 -53
- package/package.json +2 -1
- package/profiles/agile.yaml +1 -1
- package/profiles/ai.yaml +1 -1
- package/profiles/android-native.yaml +1 -1
- package/profiles/audio.yaml +1 -1
- package/profiles/backend-dotnet.yaml +1 -1
- package/profiles/backend-go.yaml +1 -1
- package/profiles/backend-nestjs.yaml +1 -1
- package/profiles/backend-nextjs.yaml +1 -1
- package/profiles/backend-ts.yaml +1 -1
- package/profiles/business.yaml +1 -1
- package/profiles/clevel.yaml +1 -1
- package/profiles/clickup.yaml +1 -1
- package/profiles/core.yaml +1 -1
- package/profiles/database.yaml +1 -1
- package/profiles/design.yaml +1 -1
- package/profiles/docs.yaml +1 -1
- package/profiles/ecommerce.yaml +1 -1
- package/profiles/finance.yaml +1 -1
- package/profiles/flutter.yaml +1 -1
- package/profiles/freelance.yaml +1 -1
- package/profiles/frontend.yaml +1 -1
- package/profiles/infra.yaml +1 -1
- package/profiles/integrations.yaml +1 -1
- package/profiles/ios-native.yaml +1 -1
- package/profiles/jira.yaml +1 -1
- package/profiles/job-hunt.yaml +1 -1
- package/profiles/marketing.yaml +1 -1
- package/profiles/mobile.yaml +1 -1
- package/profiles/regulatory.yaml +1 -1
- package/profiles/second-brain.yaml +1 -1
- package/profiles/security.yaml +1 -1
- package/profiles/self-improving.yaml +1 -1
- package/profiles/statusline.yaml +1 -1
- package/profiles/testing.yaml +1 -1
- package/profiles/unity-design.yaml +1 -1
- package/profiles/unity-dev.yaml +1 -1
- package/profiles/unreal-dev.yaml +1 -1
- package/profiles/visual-novel.yaml +1 -1
- 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
|
|
8
|
-
import
|
|
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
|
-
|
|
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
|
|
110
|
-
import
|
|
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
|
|
115
|
-
import
|
|
275
|
+
import path3 from "path";
|
|
276
|
+
import fs3 from "fs";
|
|
116
277
|
function getPackageRoot() {
|
|
117
278
|
const thisFile = fileURLToPath(import.meta.url);
|
|
118
|
-
return
|
|
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
|
-
|
|
126
|
-
for (const entry of
|
|
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 =
|
|
129
|
-
const destPath =
|
|
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
|
-
|
|
294
|
+
fs3.copyFileSync(srcPath, destPath);
|
|
134
295
|
}
|
|
135
296
|
}
|
|
136
297
|
}
|
|
137
298
|
function ensureDir(dir) {
|
|
138
|
-
|
|
299
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
139
300
|
}
|
|
140
|
-
function fileExists(
|
|
141
|
-
return
|
|
301
|
+
function fileExists(p2) {
|
|
302
|
+
return fs3.existsSync(p2);
|
|
142
303
|
}
|
|
143
|
-
function readJsonSync(
|
|
144
|
-
return JSON.parse(
|
|
304
|
+
function readJsonSync(p2) {
|
|
305
|
+
return JSON.parse(fs3.readFileSync(p2, "utf-8"));
|
|
145
306
|
}
|
|
146
|
-
function writeJsonSync(
|
|
147
|
-
|
|
307
|
+
function writeJsonSync(p2, data) {
|
|
308
|
+
fs3.writeFileSync(p2, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
148
309
|
}
|
|
149
|
-
function isSymlinkOrJunction(
|
|
310
|
+
function isSymlinkOrJunction(p2) {
|
|
150
311
|
try {
|
|
151
|
-
return
|
|
312
|
+
return fs3.lstatSync(p2).isSymbolicLink();
|
|
152
313
|
} catch {
|
|
153
314
|
return false;
|
|
154
315
|
}
|
|
155
316
|
}
|
|
156
317
|
function getPackageVersion() {
|
|
157
|
-
const pkgPath =
|
|
318
|
+
const pkgPath = path3.join(getPackageRoot(), "package.json");
|
|
158
319
|
const pkg = readJsonSync(pkgPath);
|
|
159
320
|
return pkg.version;
|
|
160
321
|
}
|
|
161
|
-
function safeRemove(
|
|
162
|
-
if (isSymlinkOrJunction(
|
|
163
|
-
|
|
164
|
-
} else if (
|
|
165
|
-
|
|
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
|
|
331
|
+
import path4 from "path";
|
|
171
332
|
var MANIFEST_FILE = "arcane-manifest.json";
|
|
172
333
|
function manifestPath(target) {
|
|
173
|
-
return
|
|
334
|
+
return path4.join(target, ".claude", MANIFEST_FILE);
|
|
174
335
|
}
|
|
175
336
|
function readManifest(target) {
|
|
176
|
-
const
|
|
177
|
-
if (!fileExists(
|
|
178
|
-
return readJsonSync(
|
|
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
|
|
228
|
-
import
|
|
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 =
|
|
250
|
-
const resolvedGit =
|
|
410
|
+
const resolvedCommon = path5.resolve(target, gitCommonDir);
|
|
411
|
+
const resolvedGit = path5.resolve(target, gitDir);
|
|
251
412
|
const isWorktree = resolvedCommon !== resolvedGit;
|
|
252
|
-
const mainWorktreePath = isWorktree ?
|
|
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:
|
|
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
|
|
304
|
-
|
|
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 =
|
|
311
|
-
|
|
471
|
+
const parentDir = path5.dirname(dest);
|
|
472
|
+
fs4.mkdirSync(parentDir, { recursive: true });
|
|
312
473
|
const type = process.platform === "win32" ? "junction" : "dir";
|
|
313
|
-
|
|
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 =
|
|
486
|
+
const repoName = path5.basename(repoRoot);
|
|
326
487
|
const sanitized = sanitizeBranchForPath(branch);
|
|
327
|
-
return
|
|
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 (
|
|
530
|
+
if (fs4.existsSync(path5.join(dir, m.lockfile))) return m;
|
|
370
531
|
}
|
|
371
|
-
if (
|
|
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
|
|
379
|
-
import
|
|
539
|
+
import fs5 from "fs";
|
|
540
|
+
import path6 from "path";
|
|
380
541
|
import { createHash } from "crypto";
|
|
381
542
|
function hashFile(filePath) {
|
|
382
|
-
const content =
|
|
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
|
|
552
|
+
for (const entry of fs5.readdirSync(current, { withFileTypes: true })) {
|
|
392
553
|
if (isBuildArtifact(entry.name)) continue;
|
|
393
|
-
const fullPath =
|
|
394
|
-
const relPath =
|
|
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 =
|
|
410
|
-
if (
|
|
411
|
-
for (const entry of
|
|
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(
|
|
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 =
|
|
420
|
-
if (
|
|
421
|
-
for (const entry of
|
|
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(
|
|
584
|
+
hashes.rules[entry.name] = hashFile(path6.join(rulesDir, entry.name));
|
|
424
585
|
}
|
|
425
586
|
}
|
|
426
587
|
}
|
|
427
|
-
const agentsDir =
|
|
428
|
-
if (
|
|
429
|
-
for (const entry of
|
|
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(
|
|
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 =
|
|
438
|
-
if (
|
|
439
|
-
for (const entry of
|
|
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(
|
|
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 =
|
|
456
|
-
if (
|
|
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 =
|
|
465
|
-
if (
|
|
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 =
|
|
469
|
-
if (
|
|
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 =
|
|
476
|
-
if (
|
|
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 =
|
|
483
|
-
if (
|
|
484
|
-
for (const entry of
|
|
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(
|
|
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 =
|
|
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 (!
|
|
688
|
-
const backup =
|
|
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 (
|
|
694
|
-
|
|
854
|
+
if (fs6.existsSync(backup)) {
|
|
855
|
+
fs6.rmSync(backup, { recursive: true, force: true });
|
|
695
856
|
}
|
|
696
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
722
|
-
if (
|
|
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 =
|
|
735
|
-
if (
|
|
895
|
+
const hooksSrc = path7.join(this.root, "hooks");
|
|
896
|
+
if (fs6.existsSync(hooksSrc)) {
|
|
736
897
|
ensureDir(hooksDir);
|
|
737
|
-
for (const entry of
|
|
898
|
+
for (const entry of fs6.readdirSync(hooksSrc, { withFileTypes: true })) {
|
|
738
899
|
if (entry.name === "statusline.sh") continue;
|
|
739
|
-
const src =
|
|
740
|
-
const dst =
|
|
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
|
-
|
|
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 =
|
|
754
|
-
if (
|
|
755
|
-
|
|
914
|
+
const statusline = path7.join(this.root, "hooks", "statusline.sh");
|
|
915
|
+
if (fs6.existsSync(statusline)) {
|
|
916
|
+
fs6.copyFileSync(
|
|
756
917
|
statusline,
|
|
757
|
-
|
|
918
|
+
path7.join(this.claudeDir, "statusline.sh")
|
|
758
919
|
);
|
|
759
|
-
this.log(" [ok] statusline.sh (
|
|
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 =
|
|
766
|
-
if (!
|
|
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 =
|
|
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 =
|
|
781
|
-
if (
|
|
782
|
-
const tplDst =
|
|
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 =
|
|
796
|
-
if (!
|
|
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
|
-
|
|
964
|
+
fs6.copyFileSync(
|
|
804
965
|
src,
|
|
805
|
-
|
|
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 =
|
|
812
|
-
if (!
|
|
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
|
-
|
|
980
|
+
fs6.copyFileSync(
|
|
820
981
|
src,
|
|
821
|
-
|
|
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 =
|
|
832
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
848
|
-
const docsSrc =
|
|
849
|
-
if (!
|
|
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 =
|
|
861
|
-
if (
|
|
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 =
|
|
893
|
-
|
|
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 =
|
|
903
|
-
const backup =
|
|
904
|
-
if (!
|
|
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 =
|
|
909
|
-
if (
|
|
910
|
-
const data = JSON.parse(
|
|
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
|
-
(
|
|
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
|
|
919
|
-
if (isSymlinkOrJunction(
|
|
920
|
-
|
|
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
|
|
927
|
-
safeRemove(
|
|
1087
|
+
const p2 = path7.join(claudeDir, sub);
|
|
1088
|
+
safeRemove(p2);
|
|
928
1089
|
}
|
|
929
|
-
const remaining =
|
|
1090
|
+
const remaining = fs6.readdirSync(claudeDir);
|
|
930
1091
|
for (const f of remaining) {
|
|
931
|
-
const
|
|
932
|
-
safeRemove(
|
|
1092
|
+
const p2 = path7.join(claudeDir, f);
|
|
1093
|
+
safeRemove(p2);
|
|
933
1094
|
}
|
|
934
|
-
if (
|
|
935
|
-
|
|
1095
|
+
if (fs6.existsSync(claudeDir)) {
|
|
1096
|
+
fs6.rmSync(claudeDir, { recursive: true, force: true });
|
|
936
1097
|
}
|
|
937
|
-
if (
|
|
938
|
-
|
|
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
|
|
946
|
-
import
|
|
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
|
|
951
|
-
import
|
|
1111
|
+
import fs7 from "fs";
|
|
1112
|
+
import path8 from "path";
|
|
952
1113
|
import os from "os";
|
|
953
|
-
var ARCANE_DIR =
|
|
954
|
-
var CACHE_DIR =
|
|
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
|
|
1117
|
+
return path8.join(CACHE_DIR, version);
|
|
957
1118
|
}
|
|
958
1119
|
function isCached(version) {
|
|
959
1120
|
const versionDir = getCachePath(version);
|
|
960
|
-
return
|
|
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 (
|
|
969
|
-
|
|
1129
|
+
if (fs7.existsSync(versionDir)) {
|
|
1130
|
+
fs7.rmSync(versionDir, { recursive: true, force: true });
|
|
970
1131
|
}
|
|
971
|
-
|
|
1132
|
+
fs7.mkdirSync(versionDir, { recursive: true });
|
|
972
1133
|
for (const dir of ["skills", "rules", "agents", "hooks", "profiles", "templates", "docs"]) {
|
|
973
|
-
const src =
|
|
974
|
-
if (
|
|
975
|
-
copyDirRecursive(src,
|
|
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
|
-
|
|
983
|
-
|
|
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 (!
|
|
990
|
-
const entries =
|
|
991
|
-
const metaPath =
|
|
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 (
|
|
1154
|
+
if (fs7.existsSync(metaPath)) {
|
|
994
1155
|
try {
|
|
995
|
-
const meta = JSON.parse(
|
|
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
|
-
|
|
1165
|
+
fs7.rmSync(path8.join(CACHE_DIR, entry.name), { recursive: true, force: true });
|
|
1005
1166
|
}
|
|
1006
1167
|
}
|
|
1007
1168
|
function copyDirRecursive(src, dest) {
|
|
1008
|
-
|
|
1009
|
-
for (const entry of
|
|
1010
|
-
const srcPath =
|
|
1011
|
-
const destPath =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1135
|
-
const fileStream =
|
|
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 =
|
|
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
|
|
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 =
|
|
1221
|
-
if (!
|
|
1222
|
-
const entries =
|
|
1223
|
-
const metaPath =
|
|
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 (
|
|
1386
|
+
if (fs8.existsSync(metaPath)) {
|
|
1226
1387
|
try {
|
|
1227
|
-
const meta = JSON.parse(
|
|
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
|
|
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 ??
|
|
1404
|
+
return process.env.ARCANE_HOME ?? path10.join(os3.homedir(), ".arcane");
|
|
1244
1405
|
}
|
|
1245
1406
|
function registryPath() {
|
|
1246
|
-
return
|
|
1407
|
+
return path10.join(arcaneHome(), "installations.json");
|
|
1247
1408
|
}
|
|
1248
1409
|
function readRegistryFile() {
|
|
1249
|
-
const
|
|
1250
|
-
if (!fileExists(
|
|
1410
|
+
const p2 = registryPath();
|
|
1411
|
+
if (!fileExists(p2)) {
|
|
1251
1412
|
return { version: REGISTRY_VERSION, installations: [] };
|
|
1252
1413
|
}
|
|
1253
1414
|
try {
|
|
1254
|
-
const data = readJsonSync(
|
|
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 =
|
|
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 =
|
|
1299
|
-
const target =
|
|
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
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
console.log(
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1323
|
-
Installing profile: ${
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
1526
|
+
console.log(chalk2.green("\n Installation complete."));
|
|
1363
1527
|
}
|
|
1364
1528
|
}
|
|
1365
1529
|
|
|
1366
1530
|
// src/commands/add.ts
|
|
1367
|
-
import
|
|
1368
|
-
import
|
|
1369
|
-
import
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
1394
|
-
if (!
|
|
1395
|
-
console.error(
|
|
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 =
|
|
1408
|
-
if (
|
|
1409
|
-
ensureDir(
|
|
1410
|
-
|
|
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 =
|
|
1419
|
-
if (
|
|
1420
|
-
ensureDir(
|
|
1421
|
-
|
|
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 =
|
|
1430
|
-
if (
|
|
1431
|
-
const dst =
|
|
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 =
|
|
1440
|
-
if (
|
|
1441
|
-
|
|
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((
|
|
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(
|
|
1639
|
+
console.log(chalk3.bold(`
|
|
1476
1640
|
Added ${totalAdded} items:`));
|
|
1477
|
-
for (const s of added) console.log(
|
|
1478
|
-
for (const r of addedRules) console.log(
|
|
1479
|
-
for (const a of addedAgents) console.log(
|
|
1480
|
-
if (statuslineAdded) console.log(
|
|
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(
|
|
1646
|
+
console.log(chalk3.dim(` [skip] ${s} (already installed)`));
|
|
1483
1647
|
for (const s of notFound)
|
|
1484
|
-
console.log(
|
|
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 =
|
|
1489
|
-
if (!
|
|
1490
|
-
const dst =
|
|
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 =
|
|
1496
|
-
if (!
|
|
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 =
|
|
1510
|
-
if (!
|
|
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
|
|
1522
|
-
import
|
|
1523
|
-
import
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
1754
|
+
console.log(chalk4.bold(`
|
|
1591
1755
|
Removed ${totalRemoved} items:`));
|
|
1592
|
-
for (const
|
|
1593
|
-
console.log(
|
|
1756
|
+
for (const p2 of removedProfiles)
|
|
1757
|
+
console.log(chalk4.magenta(` [ok] +${p2} (profile)`));
|
|
1594
1758
|
for (const s of removedSkills)
|
|
1595
|
-
console.log(
|
|
1759
|
+
console.log(chalk4.green(` [ok] ${s} (skill)`));
|
|
1596
1760
|
for (const a of removedAgents)
|
|
1597
|
-
console.log(
|
|
1761
|
+
console.log(chalk4.cyan(` [ok] ${a} (agent dir)`));
|
|
1598
1762
|
for (const s of skipped)
|
|
1599
|
-
console.log(
|
|
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
|
-
|
|
1768
|
+
chalk4.yellow(` WARNING: '${skill}' is a core skill. Skipping.`)
|
|
1605
1769
|
);
|
|
1606
1770
|
return "skipped";
|
|
1607
1771
|
}
|
|
1608
|
-
const skillDir =
|
|
1609
|
-
if (!
|
|
1610
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1789
|
+
chalk4.yellow(` WARNING: Profile '${profileName}' is not installed.`)
|
|
1626
1790
|
);
|
|
1627
1791
|
return { removed: false, skills: [], agents: [] };
|
|
1628
1792
|
}
|
|
1629
|
-
const profilePath =
|
|
1630
|
-
if (!
|
|
1793
|
+
const profilePath = path13.join(root, "profiles", `${profileName}.yaml`);
|
|
1794
|
+
if (!fs10.existsSync(profilePath)) {
|
|
1631
1795
|
console.warn(
|
|
1632
|
-
|
|
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
|
-
(
|
|
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 =
|
|
1645
|
-
if (!
|
|
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 =
|
|
1657
|
-
if (
|
|
1658
|
-
|
|
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 =
|
|
1669
|
-
if (
|
|
1670
|
-
|
|
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 =
|
|
1683
|
-
if (
|
|
1684
|
-
|
|
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 =
|
|
1692
|
-
if (
|
|
1693
|
-
|
|
1855
|
+
const statuslineFile = path13.join(target, ".claude", "statusline.sh");
|
|
1856
|
+
if (fs10.existsSync(statuslineFile)) {
|
|
1857
|
+
fs10.rmSync(statuslineFile);
|
|
1694
1858
|
}
|
|
1695
|
-
removeStatuslineFromSettings(
|
|
1859
|
+
removeStatuslineFromSettings(path13.join(target, ".claude"));
|
|
1696
1860
|
}
|
|
1697
1861
|
manifest.profiles = remainingProfiles;
|
|
1698
|
-
manifest.profile_command = remainingProfiles.filter((
|
|
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 =
|
|
1703
|
-
if (!
|
|
1704
|
-
const settings = JSON.parse(
|
|
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
|
-
|
|
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
|
|
1713
|
-
import
|
|
1714
|
-
import
|
|
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 =
|
|
1719
|
-
const skillsDir =
|
|
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(
|
|
1727
|
-
console.log(
|
|
1728
|
-
for (const
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
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 (
|
|
1742
|
-
const allSkills =
|
|
1743
|
-
(d) => d.isDirectory() && !d.name.startsWith("_") &&
|
|
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(
|
|
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) ?
|
|
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(
|
|
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
|
|
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
|
-
|
|
1928
|
+
chalk6.yellow("No Arcane installation found in this project.")
|
|
1770
1929
|
);
|
|
1771
|
-
console.log(
|
|
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((
|
|
1775
|
-
console.log(
|
|
1776
|
-
console.log(` Profiles: ${
|
|
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: ${
|
|
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 ?
|
|
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: ${
|
|
1947
|
+
` Shared: ${chalk6.blue(wt.shared_dirs.join(", "))} (symlinked)`
|
|
1789
1948
|
);
|
|
1790
1949
|
}
|
|
1791
1950
|
}
|
|
1792
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
|
1811
|
-
import
|
|
1969
|
+
import fs12 from "fs";
|
|
1970
|
+
import path15 from "path";
|
|
1812
1971
|
import os4 from "os";
|
|
1813
|
-
import
|
|
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(
|
|
2034
|
+
console.log(chalk7.green("npm package updated to latest."));
|
|
1876
2035
|
} else if (self.skipped && self.reason !== "dev/test environment") {
|
|
1877
|
-
console.log(
|
|
2036
|
+
console.log(chalk7.dim(`npm self-update skipped (${self.reason}).`));
|
|
1878
2037
|
} else if (!self.skipped) {
|
|
1879
|
-
console.log(
|
|
2038
|
+
console.log(chalk7.yellow(`npm self-update failed: ${self.reason}`));
|
|
1880
2039
|
}
|
|
1881
2040
|
}
|
|
1882
2041
|
const cwd = process.cwd();
|
|
1883
|
-
if (
|
|
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 &&
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1960
|
-
Update available: ${
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 ?
|
|
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(
|
|
2210
|
+
console.log(chalk7.bold(`
|
|
2052
2211
|
${prefix}Done. ${results.length} installation${results.length === 1 ? "" : "s"} processed:`));
|
|
2053
|
-
console.log(` ${
|
|
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 ?
|
|
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}${
|
|
2261
|
+
${prefix}${chalk7.cyan("Will update:")}`);
|
|
2103
2262
|
for (const item of updates) {
|
|
2104
|
-
console.log(` ${
|
|
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}${
|
|
2268
|
+
${prefix}${chalk7.cyan("Will add:")}`);
|
|
2110
2269
|
for (const item of adds) {
|
|
2111
|
-
console.log(` ${
|
|
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}${
|
|
2275
|
+
${prefix}${chalk7.cyan("Will remove:")}`);
|
|
2117
2276
|
for (const item of removed) {
|
|
2118
|
-
console.log(` ${
|
|
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}${
|
|
2282
|
+
${prefix}${chalk7.yellow("Conflicts (will backup + update):")}`);
|
|
2124
2283
|
for (const item of conflicts) {
|
|
2125
|
-
console.log(` ${
|
|
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}${
|
|
2289
|
+
${prefix}${chalk7.dim("Skipped (locally customized):")}`);
|
|
2131
2290
|
for (const item of customized) {
|
|
2132
|
-
console.log(` ${
|
|
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 || !
|
|
2320
|
+
if (!src || !fs12.existsSync(src)) return;
|
|
2162
2321
|
if (item.type === "skill" || item.type === "agent") {
|
|
2163
|
-
if (
|
|
2164
|
-
|
|
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 =
|
|
2169
|
-
if (!
|
|
2170
|
-
|
|
2327
|
+
const dir = path15.dirname(dst);
|
|
2328
|
+
if (!fs12.existsSync(dir)) {
|
|
2329
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
2171
2330
|
}
|
|
2172
|
-
|
|
2331
|
+
fs12.copyFileSync(src, dst);
|
|
2173
2332
|
}
|
|
2174
2333
|
}
|
|
2175
2334
|
function backupItem(item, claudeDir) {
|
|
2176
2335
|
const installed = getInstalledPath(item, claudeDir);
|
|
2177
|
-
if (!
|
|
2336
|
+
if (!fs12.existsSync(installed)) return;
|
|
2178
2337
|
const backupPath = installed + ".bak";
|
|
2179
|
-
if (
|
|
2180
|
-
|
|
2338
|
+
if (fs12.existsSync(backupPath)) {
|
|
2339
|
+
fs12.rmSync(backupPath, { recursive: true, force: true });
|
|
2181
2340
|
}
|
|
2182
|
-
|
|
2341
|
+
fs12.renameSync(installed, backupPath);
|
|
2183
2342
|
}
|
|
2184
2343
|
function removeItem(item, claudeDir) {
|
|
2185
2344
|
const installed = getInstalledPath(item, claudeDir);
|
|
2186
|
-
if (
|
|
2187
|
-
|
|
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
|
|
2352
|
+
return path15.join(root, "skills", item.name);
|
|
2194
2353
|
case "rule": {
|
|
2195
|
-
const direct =
|
|
2196
|
-
if (
|
|
2197
|
-
return
|
|
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
|
|
2359
|
+
return path15.join(root, "agents", item.name);
|
|
2201
2360
|
case "hook":
|
|
2202
|
-
return
|
|
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
|
|
2369
|
+
return path15.join(claudeDir, "skills", item.name);
|
|
2211
2370
|
case "rule":
|
|
2212
|
-
return
|
|
2371
|
+
return path15.join(claudeDir, "rules", item.name);
|
|
2213
2372
|
case "agent":
|
|
2214
|
-
return
|
|
2373
|
+
return path15.join(claudeDir, "agents", item.name);
|
|
2215
2374
|
case "hook":
|
|
2216
|
-
return
|
|
2375
|
+
return path15.join(claudeDir, "hooks", item.name);
|
|
2217
2376
|
}
|
|
2218
2377
|
}
|
|
2219
2378
|
|
|
2220
2379
|
// src/commands/clean.ts
|
|
2221
|
-
import
|
|
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(
|
|
2385
|
+
console.log(chalk8.yellow("No Arcane installation found."));
|
|
2227
2386
|
return;
|
|
2228
2387
|
}
|
|
2229
|
-
console.log(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2252
|
-
import
|
|
2410
|
+
import fs13 from "fs";
|
|
2411
|
+
import path16 from "path";
|
|
2253
2412
|
import { execSync as execSync2 } from "child_process";
|
|
2254
|
-
import
|
|
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(
|
|
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 ?
|
|
2264
|
-
if (
|
|
2422
|
+
const worktreePath = opts.path ? path16.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
|
|
2423
|
+
if (fs13.existsSync(worktreePath)) {
|
|
2265
2424
|
console.error(
|
|
2266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
2291
|
-
console.log(` 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: ${
|
|
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(
|
|
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(
|
|
2461
|
+
console.log(chalk9.green(" [ok] Git worktree created"));
|
|
2303
2462
|
} catch (err) {
|
|
2304
2463
|
console.error(
|
|
2305
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
2497
|
+
console.log(chalk9.green(` [ok] Dependencies installed`));
|
|
2339
2498
|
} catch {
|
|
2340
2499
|
console.warn(
|
|
2341
|
-
|
|
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 =
|
|
2506
|
+
const isolateScript = path16.join(
|
|
2348
2507
|
worktreePath,
|
|
2349
2508
|
".claude",
|
|
2350
2509
|
"skills",
|
|
2351
2510
|
"worktree-isolation"
|
|
2352
2511
|
);
|
|
2353
|
-
if (
|
|
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(
|
|
2524
|
+
console.log(chalk9.green(" [ok] Worktree isolation applied"));
|
|
2366
2525
|
} catch {
|
|
2367
2526
|
console.warn(
|
|
2368
|
-
|
|
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
|
-
|
|
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(
|
|
2382
|
-
console.log(` ${
|
|
2383
|
-
console.log(` ${
|
|
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
|
|
2389
|
-
import
|
|
2547
|
+
import fs14 from "fs";
|
|
2548
|
+
import path17 from "path";
|
|
2390
2549
|
import os5 from "os";
|
|
2391
|
-
import
|
|
2392
|
-
var CLAUDE_HOME =
|
|
2393
|
-
var SCRIPTS_DIR =
|
|
2394
|
-
var SETTINGS_PATH =
|
|
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(
|
|
2408
|
-
const hasScripts =
|
|
2409
|
-
|
|
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 ?
|
|
2571
|
+
` Worktree scripts: ${hasScripts ? chalk10.green("installed") : chalk10.dim("not installed")}`
|
|
2413
2572
|
);
|
|
2414
2573
|
if (hasScripts) {
|
|
2415
|
-
console.log(` ${
|
|
2574
|
+
console.log(` ${chalk10.dim(SCRIPTS_DIR)}`);
|
|
2416
2575
|
}
|
|
2417
2576
|
const hookInstalled = isHookInstalled();
|
|
2418
2577
|
console.log(
|
|
2419
|
-
` SessionStart hook: ${hookInstalled ?
|
|
2578
|
+
` SessionStart hook: ${hookInstalled ? chalk10.green("active") : chalk10.dim("not configured")}`
|
|
2420
2579
|
);
|
|
2421
|
-
console.log(` ${
|
|
2580
|
+
console.log(` ${chalk10.dim(SETTINGS_PATH)}`);
|
|
2422
2581
|
console.log();
|
|
2423
2582
|
}
|
|
2424
2583
|
function installGlobal() {
|
|
2425
|
-
console.log(
|
|
2584
|
+
console.log(chalk10.bold("\nInstalling Arcane global hooks...\n"));
|
|
2426
2585
|
const root = getPackageRoot();
|
|
2427
|
-
const scriptsSrc =
|
|
2586
|
+
const scriptsSrc = path17.join(
|
|
2428
2587
|
root,
|
|
2429
2588
|
"skills",
|
|
2430
2589
|
"worktree-isolation",
|
|
2431
2590
|
"scripts"
|
|
2432
2591
|
);
|
|
2433
|
-
if (!
|
|
2592
|
+
if (!fs14.existsSync(scriptsSrc)) {
|
|
2434
2593
|
console.error(
|
|
2435
|
-
|
|
2594
|
+
chalk10.red(" worktree-isolation scripts not found in package.")
|
|
2436
2595
|
);
|
|
2437
2596
|
process.exit(1);
|
|
2438
2597
|
}
|
|
2439
|
-
|
|
2598
|
+
fs14.mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
2440
2599
|
for (const file of ["apply.py", "lib.py"]) {
|
|
2441
|
-
const src =
|
|
2442
|
-
const dst =
|
|
2443
|
-
if (!
|
|
2444
|
-
console.warn(
|
|
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 (
|
|
2448
|
-
console.log(
|
|
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
|
-
|
|
2609
|
+
fs14.copyFileSync(src, dst);
|
|
2451
2610
|
if (process.platform !== "win32") {
|
|
2452
|
-
|
|
2611
|
+
fs14.chmodSync(dst, 493);
|
|
2453
2612
|
}
|
|
2454
|
-
console.log(
|
|
2613
|
+
console.log(chalk10.green(` [ok] ${file} -> ${SCRIPTS_DIR}`));
|
|
2455
2614
|
}
|
|
2456
2615
|
}
|
|
2457
2616
|
if (isHookInstalled()) {
|
|
2458
2617
|
console.log(
|
|
2459
|
-
|
|
2618
|
+
chalk10.dim(" [skip] SessionStart hook (already configured)")
|
|
2460
2619
|
);
|
|
2461
2620
|
} else {
|
|
2462
2621
|
addHookToSettings();
|
|
2463
2622
|
console.log(
|
|
2464
|
-
|
|
2623
|
+
chalk10.green(" [ok] SessionStart hook added to ~/.claude/settings.json")
|
|
2465
2624
|
);
|
|
2466
2625
|
}
|
|
2467
|
-
console.log(
|
|
2626
|
+
console.log(chalk10.bold.green("\n Global hooks installed."));
|
|
2468
2627
|
console.log(
|
|
2469
|
-
|
|
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(
|
|
2476
|
-
if (
|
|
2477
|
-
|
|
2478
|
-
console.log(
|
|
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(
|
|
2639
|
+
console.log(chalk10.dim(" [skip] Scripts not found"));
|
|
2481
2640
|
}
|
|
2482
2641
|
if (isHookInstalled()) {
|
|
2483
2642
|
removeHookFromSettings();
|
|
2484
|
-
console.log(
|
|
2643
|
+
console.log(chalk10.green(" [ok] Removed SessionStart hook"));
|
|
2485
2644
|
} else {
|
|
2486
|
-
console.log(
|
|
2645
|
+
console.log(chalk10.dim(" [skip] Hook not configured"));
|
|
2487
2646
|
}
|
|
2488
|
-
console.log(
|
|
2647
|
+
console.log(chalk10.bold.green("\n Global hooks removed.\n"));
|
|
2489
2648
|
}
|
|
2490
2649
|
function readSettings() {
|
|
2491
|
-
if (!
|
|
2650
|
+
if (!fs14.existsSync(SETTINGS_PATH)) return {};
|
|
2492
2651
|
try {
|
|
2493
|
-
return JSON.parse(
|
|
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
|
-
|
|
2500
|
-
|
|
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
|
|
2558
|
-
import
|
|
2716
|
+
import fs15 from "fs";
|
|
2717
|
+
import path18 from "path";
|
|
2559
2718
|
import os6 from "os";
|
|
2560
|
-
import
|
|
2561
|
-
var CHECK_FILE =
|
|
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
|
-
|
|
2760
|
+
chalk11.yellow(`
|
|
2602
2761
|
Arcane update available: ${localVersion} \u2192 ${remoteSha}`)
|
|
2603
2762
|
);
|
|
2604
2763
|
console.log(
|
|
2605
|
-
|
|
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 (!
|
|
2630
|
-
return JSON.parse(
|
|
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 =
|
|
2638
|
-
if (!
|
|
2639
|
-
|
|
2796
|
+
const dir = path18.dirname(CHECK_FILE);
|
|
2797
|
+
if (!fs15.existsSync(dir)) {
|
|
2798
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
2640
2799
|
}
|
|
2641
|
-
|
|
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
|
|
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)"
|