claude-code-arcane 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [1.0.1](https://github.com/SebastianLuser/claude-code-arcane/compare/v1.0.0...v1.0.1) (2026-06-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add/remove commands now handle full profile assets (rules, agents, statusline, permissions) ([2425ea9](https://github.com/SebastianLuser/claude-code-arcane/commit/2425ea93e3b41cc28d4b7622e6cf1a58dfaa814e))
7
+
1
8
  # 1.0.0 (2026-06-03)
2
9
 
3
10
 
package/dist/cli.js CHANGED
@@ -1298,8 +1298,13 @@ async function addCommand(items) {
1298
1298
  );
1299
1299
  process.exit(1);
1300
1300
  }
1301
+ const claudeDir = path10.join(target, ".claude");
1301
1302
  const added = [];
1302
1303
  const skipped = [];
1304
+ const notFound = [];
1305
+ const addedRules = [];
1306
+ const addedAgents = [];
1307
+ let statuslineAdded = false;
1303
1308
  for (const item of items) {
1304
1309
  if (item.startsWith("+")) {
1305
1310
  const profileName = item.slice(1);
@@ -1312,19 +1317,69 @@ async function addCommand(items) {
1312
1317
  for (const skill of profile.skills) {
1313
1318
  const result = addSkill(root, target, skill, manifest.installed_skills);
1314
1319
  if (result === "added") added.push(skill);
1320
+ else if (result === "not-found") notFound.push(skill);
1315
1321
  else skipped.push(skill);
1316
1322
  }
1323
+ for (const rule of profile.rules.universal) {
1324
+ if (!manifest.installed_rules.includes(rule)) {
1325
+ const src = path10.join(root, "rules", `${rule}.md`);
1326
+ if (fs8.existsSync(src)) {
1327
+ ensureDir(path10.join(claudeDir, "rules"));
1328
+ fs8.copyFileSync(src, path10.join(claudeDir, "rules", `${rule}.md`));
1329
+ manifest.installed_rules.push(rule);
1330
+ addedRules.push(rule);
1331
+ }
1332
+ }
1333
+ }
1334
+ for (const rule of profile.rules.gamedev) {
1335
+ if (!manifest.installed_rules.includes(rule)) {
1336
+ const src = path10.join(root, "rules", "gamedev", `${rule}.md`);
1337
+ if (fs8.existsSync(src)) {
1338
+ ensureDir(path10.join(claudeDir, "rules"));
1339
+ fs8.copyFileSync(src, path10.join(claudeDir, "rules", `${rule}.md`));
1340
+ manifest.installed_rules.push(rule);
1341
+ addedRules.push(rule);
1342
+ }
1343
+ }
1344
+ }
1345
+ for (const agentDir of profile.agents) {
1346
+ if (!manifest.installed_agents.includes(agentDir)) {
1347
+ const src = path10.join(root, "agents", agentDir);
1348
+ if (fs8.existsSync(src)) {
1349
+ const dst = path10.join(claudeDir, "agents", agentDir);
1350
+ copyDirSync(src, dst);
1351
+ manifest.installed_agents.push(agentDir);
1352
+ addedAgents.push(agentDir);
1353
+ }
1354
+ }
1355
+ }
1356
+ if (profileName === "statusline") {
1357
+ const statuslineSrc = path10.join(root, "hooks", "statusline.sh");
1358
+ if (fs8.existsSync(statuslineSrc)) {
1359
+ fs8.copyFileSync(statuslineSrc, path10.join(claudeDir, "statusline.sh"));
1360
+ statuslineAdded = true;
1361
+ }
1362
+ }
1363
+ if (profile.permissions.allow.length > 0 || profile.permissions.deny.length > 0) {
1364
+ mergePermissions(claudeDir, profile.permissions);
1365
+ }
1317
1366
  if (!manifest.profiles.includes(profileName)) {
1318
1367
  manifest.profiles.push(profileName);
1368
+ manifest.profile_command = manifest.profiles.filter((p) => p !== "core").join("+");
1369
+ }
1370
+ if (statuslineAdded) {
1371
+ addStatuslineToSettings(claudeDir);
1319
1372
  }
1320
1373
  } else {
1321
1374
  const result = addSkill(root, target, item, manifest.installed_skills);
1322
1375
  if (result === "added") added.push(item);
1376
+ else if (result === "not-found") notFound.push(item);
1323
1377
  else skipped.push(item);
1324
1378
  }
1325
1379
  }
1326
1380
  manifest.installed_skills.push(...added);
1327
1381
  manifest.total_skills = manifest.installed_skills.length;
1382
+ manifest.total_rules = manifest.installed_rules.length;
1328
1383
  const merged = {
1329
1384
  loaded: manifest.profiles,
1330
1385
  skills: manifest.installed_skills,
@@ -1334,23 +1389,51 @@ async function addCommand(items) {
1334
1389
  permissions: { allow: [], deny: [] }
1335
1390
  };
1336
1391
  writeManifest(target, merged, manifest.profile_command, root);
1392
+ const totalAdded = added.length + addedRules.length + addedAgents.length + (statuslineAdded ? 1 : 0);
1337
1393
  console.log(chalk2.bold(`
1338
- Added ${added.length} skills:`));
1339
- for (const s of added) console.log(chalk2.green(` [ok] ${s}`));
1394
+ Added ${totalAdded} items:`));
1395
+ for (const s of added) console.log(chalk2.green(` [ok] skill: ${s}`));
1396
+ for (const r of addedRules) console.log(chalk2.green(` [ok] rule: ${r}`));
1397
+ for (const a of addedAgents) console.log(chalk2.green(` [ok] agents: ${a}/`));
1398
+ if (statuslineAdded) console.log(chalk2.green(" [ok] statusline.sh"));
1340
1399
  for (const s of skipped)
1341
1400
  console.log(chalk2.dim(` [skip] ${s} (already installed)`));
1401
+ for (const s of notFound)
1402
+ console.log(chalk2.red(` [miss] ${s} (not found in source)`));
1342
1403
  }
1343
1404
  function addSkill(root, target, skill, installed) {
1344
1405
  if (installed.includes(skill)) return "skipped";
1345
1406
  const src = path10.join(root, "skills", skill);
1346
- if (!fs8.existsSync(src)) {
1347
- console.error(chalk2.red(` Skill '${skill}' not found in skills/`));
1348
- return "skipped";
1349
- }
1407
+ if (!fs8.existsSync(src)) return "not-found";
1350
1408
  const dst = path10.join(target, ".claude", "skills", skill);
1351
1409
  copyDirSync(src, dst);
1352
1410
  return "added";
1353
1411
  }
1412
+ function mergePermissions(claudeDir, newPerms) {
1413
+ const settingsPath = path10.join(claudeDir, "settings.json");
1414
+ if (!fs8.existsSync(settingsPath)) return;
1415
+ const settings = readJsonSync(settingsPath);
1416
+ const perms = settings.permissions ?? { allow: [], deny: [] };
1417
+ const allowSet = new Set(perms.allow);
1418
+ for (const a of newPerms.allow) allowSet.add(a);
1419
+ perms.allow = [...allowSet];
1420
+ const denySet = new Set(perms.deny);
1421
+ for (const d of newPerms.deny) denySet.add(d);
1422
+ perms.deny = [...denySet];
1423
+ settings.permissions = perms;
1424
+ writeJsonSync(settingsPath, settings);
1425
+ }
1426
+ function addStatuslineToSettings(claudeDir) {
1427
+ const settingsPath = path10.join(claudeDir, "settings.json");
1428
+ if (!fs8.existsSync(settingsPath)) return;
1429
+ const settings = readJsonSync(settingsPath);
1430
+ if (settings.statusLine) return;
1431
+ settings.statusLine = {
1432
+ type: "command",
1433
+ command: "bash .claude/statusline.sh"
1434
+ };
1435
+ writeJsonSync(settingsPath, settings);
1436
+ }
1354
1437
 
1355
1438
  // src/commands/remove.ts
1356
1439
  import fs9 from "fs";
@@ -1520,10 +1603,26 @@ function removeProfile(root, target, profileName, manifest) {
1520
1603
  (r) => r !== rule
1521
1604
  );
1522
1605
  }
1606
+ if (profileName === "statusline") {
1607
+ const statuslineFile = path11.join(target, ".claude", "statusline.sh");
1608
+ if (fs9.existsSync(statuslineFile)) {
1609
+ fs9.rmSync(statuslineFile);
1610
+ }
1611
+ removeStatuslineFromSettings(path11.join(target, ".claude"));
1612
+ }
1523
1613
  manifest.profiles = remainingProfiles;
1524
1614
  manifest.profile_command = remainingProfiles.filter((p) => p !== "core").join("+");
1525
1615
  return { removed: true, skills: removedSkills, agents: removedAgents };
1526
1616
  }
1617
+ function removeStatuslineFromSettings(claudeDir) {
1618
+ const settingsPath = path11.join(claudeDir, "settings.json");
1619
+ if (!fs9.existsSync(settingsPath)) return;
1620
+ const settings = JSON.parse(fs9.readFileSync(settingsPath, "utf-8"));
1621
+ if (settings.statusLine) {
1622
+ delete settings.statusLine;
1623
+ fs9.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
1624
+ }
1625
+ }
1527
1626
 
1528
1627
  // src/commands/list.ts
1529
1628
  import fs10 from "fs";
@@ -0,0 +1,163 @@
1
+ # Migración a Trusted Publishing (OIDC) — guía futura
2
+
3
+ > **Estado actual (jun 2026):** el paquete se publica con un **token `NPM_TOKEN`** (Classic Automation, saltea 2FA) cargado como secret en GitHub Actions. Funciona y es estable. Este documento describe cómo migrar a **Trusted Publishing (OIDC)** cuando convenga.
4
+
5
+ ---
6
+
7
+ ## 1. Qué es y por qué migrar
8
+
9
+ **Trusted Publishing** usa **OpenID Connect (OIDC)**: GitHub Actions se autentica contra npm con una **identidad efímera por run** (un id-token de corta vida), en vez de un token de larga duración guardado como secret.
10
+
11
+ | | Token (actual) | Trusted Publishing (OIDC) |
12
+ |---|---|---|
13
+ | Secret de larga vida | Sí (`NPM_TOKEN`) | **No** |
14
+ | Expira / hay que rotar | Sí (vence **2026-09-01**) | **No** |
15
+ | Riesgo si se filtra | Alto (válido hasta expirar) | Bajo (token efímero por run) |
16
+ | Provenance / attestations | Manual | **Automático** |
17
+ | Mantenimiento | Rotar token periódicamente | Cero |
18
+
19
+ **Motivo principal para migrar:** eliminar la rotación del token y el riesgo de que un release falle sin aviso cuando el token expire.
20
+
21
+ ---
22
+
23
+ ## 2. ⚠️ Por qué HOY (jun 2026) no se hizo
24
+
25
+ El setup actual está varias piezas por debajo de lo que OIDC requiere. **Verificar y resolver estos puntos ANTES de migrar:**
26
+
27
+ | Requisito OIDC | Estado al jun-2026 | Acción |
28
+ |---|---|---|
29
+ | `@semantic-release/npm` **≥ 13.1.0** (soporte OIDC, oct-2025) | **12.0.2** (bundleado por `semantic-release@24.2.9`) — **no soporta OIDC** | Forzar upgrade del plugin |
30
+ | **npm ≥ 11.5.1** en el CI | Node 22 trae **npm 10.x** | Agregar step que actualice npm |
31
+ | `permissions: id-token: write` en el job | No está | Editar `release.yml` |
32
+ | Trusted publisher configurado en npm | No está | Configurar en la web de npm |
33
+ | Paquete ya existe en npm | ✅ (`claude-code-arcane@1.0.0`) | OK — OIDC **no** permite el *primer* publish, pero ya está hecho |
34
+
35
+ **Bugs conocidos a revisar antes de migrar** (pueden estar resueltos en versiones nuevas):
36
+ - [semantic-release/npm#1069](https://github.com/semantic-release/npm/issues/1069) — `ENONPMTOKEN` aún pide token en `verifyConditions` (reportado ene-2026).
37
+ - [semantic-release/npm#1023](https://github.com/semantic-release/npm/issues/1023) — falla el primer publish desde maintenance branch.
38
+
39
+ > Antes de empezar, chequear que estos issues estén cerrados o tengan workaround claro.
40
+
41
+ ---
42
+
43
+ ## 3. Setup paso a paso
44
+
45
+ ### Paso 1 — Actualizar dependencias
46
+
47
+ Asegurar `@semantic-release/npm` ≥ 13.1.0. Como `semantic-release@24.x` puede seguir bundleando una versión vieja, declararlo como dependencia directa:
48
+
49
+ ```bash
50
+ npm install -D @semantic-release/npm@latest semantic-release@latest
51
+ ```
52
+
53
+ Verificar que quedó ≥ 13.1.0:
54
+
55
+ ```bash
56
+ npm ls @semantic-release/npm
57
+ ```
58
+
59
+ ### Paso 2 — Editar `.github/workflows/release.yml`
60
+
61
+ Dos cambios: **(a)** agregar el permiso `id-token: write`, **(b)** actualizar npm a ≥ 11.5.1 antes del release.
62
+
63
+ ```yaml
64
+ name: Release
65
+
66
+ on:
67
+ push:
68
+ branches: [main]
69
+
70
+ permissions:
71
+ contents: write
72
+ issues: write
73
+ pull-requests: write
74
+ id-token: write # ← NUEVO: habilita OIDC
75
+
76
+ jobs:
77
+ release:
78
+ runs-on: ubuntu-latest
79
+ steps:
80
+ - uses: actions/checkout@v4
81
+ with:
82
+ fetch-depth: 0
83
+ persist-credentials: false
84
+ - uses: actions/setup-node@v4
85
+ with:
86
+ node-version: 22
87
+ cache: 'npm'
88
+ - run: npm install -g npm@latest # ← NUEVO: npm >= 11.5.1 para OIDC
89
+ - run: npm ci
90
+ - run: npm run build
91
+ - run: npm test
92
+ - name: Release
93
+ env:
94
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
95
+ # NPM_TOKEN ya NO es necesario una vez que OIDC funcione.
96
+ # Dejarlo como fallback durante la transición; quitarlo al confirmar.
97
+ run: npx semantic-release
98
+ ```
99
+
100
+ > **Importante:** durante la transición, **no borrar** el secret `NPM_TOKEN` todavía. Token y OIDC pueden coexistir; el token queda como red de seguridad.
101
+
102
+ ### Paso 3 — Configurar el Trusted Publisher en npm
103
+
104
+ 1. Ir a [npmjs.com](https://www.npmjs.com) → paquete **claude-code-arcane** → **Settings**.
105
+ 2. Sección **Trusted Publishers** (o *Publishing access*).
106
+ 3. **Add trusted publisher** → GitHub Actions:
107
+ - **Organization / user:** `SebastianLuser`
108
+ - **Repository:** `claude-code-arcane`
109
+ - **Workflow filename:** `release.yml`
110
+ - **Environment:** (dejar vacío salvo que el job use un `environment:`)
111
+ 4. Guardar.
112
+
113
+ ### Paso 4 — Release de prueba (additive, sin riesgo)
114
+
115
+ Con el token todavía presente como fallback:
116
+
117
+ ```bash
118
+ git commit --allow-empty -m "fix: test trusted publishing via OIDC"
119
+ git push origin main
120
+ ```
121
+
122
+ Revisar el log del step **Release** en Actions:
123
+ - ✅ Si publica vía OIDC (suele loguear `provenance` / autenticación OIDC) → migración OK.
124
+ - ❌ Si falla pidiendo token (`ENONPMTOKEN`) → el soporte aún tiene asperezas; **revertir** y seguir con token.
125
+
126
+ ### Paso 5 — Quitar el token (solo si el Paso 4 fue verde)
127
+
128
+ 1. En GitHub → Settings → Secrets → borrar `NPM_TOKEN`.
129
+ 2. En npm → Access Tokens → borrar el token de automatización.
130
+ 3. Commit confirmando que ya no se usa el token.
131
+
132
+ ---
133
+
134
+ ## 4. Rollback
135
+
136
+ Si algo falla después de migrar:
137
+
138
+ 1. Recargar el secret `NPM_TOKEN` (regenerar token Classic Automation en npm).
139
+ 2. Revertir los cambios del `release.yml` (`git revert` del commit de migración).
140
+ 3. El release vuelve a funcionar con token como antes.
141
+
142
+ ---
143
+
144
+ ## 5. Checklist rápido
145
+
146
+ - [ ] `@semantic-release/npm` ≥ 13.1.0 instalado y verificado
147
+ - [ ] Issues #1069 / #1023 cerrados o con workaround
148
+ - [ ] `id-token: write` agregado al workflow
149
+ - [ ] Step `npm install -g npm@latest` agregado
150
+ - [ ] Trusted publisher configurado en npm (repo + `release.yml`)
151
+ - [ ] Release de prueba publicó vía OIDC (con token aún presente)
152
+ - [ ] Token `NPM_TOKEN` eliminado de GitHub y npm
153
+ - [ ] Documentado el cambio en CHANGELOG / README
154
+
155
+ ---
156
+
157
+ ## Referencias
158
+
159
+ - [npm trusted publishing GA — GitHub Changelog](https://github.blog/changelog/2025-07-31-npm-trusted-publishing-with-oidc-is-generally-available/)
160
+ - [Trusted publishing for npm packages — npm Docs](https://docs.npmjs.com/trusted-publishers/)
161
+ - [@semantic-release/npm releases (v13.1.0 = soporte OIDC)](https://github.com/semantic-release/npm/releases)
162
+ - [Issue #1069 — ENONPMTOKEN con OIDC](https://github.com/semantic-release/npm/issues/1069)
163
+ - [Issue #1023 — primer publish desde maintenance branch](https://github.com/semantic-release/npm/issues/1023)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-arcane",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Skills, agents, hooks and rules for Claude Code — installable via npx arcane",
5
5
  "type": "module",
6
6
  "bin": {