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 +7 -0
- package/dist/cli.js +105 -6
- package/docs/trusted-publishing-setup.md +163 -0
- package/package.json +1 -1
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 ${
|
|
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)
|