claude-code-arcane 1.1.1 → 1.2.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.
@@ -0,0 +1,99 @@
1
+ # Release Setup Guide
2
+
3
+ Arcane usa **semantic-release** para publicar automaticamente a npm en cada push a `main`. Esta guia explica como configurar los secrets necesarios para que el pipeline funcione.
4
+
5
+ ## Arquitectura del release
6
+
7
+ ```
8
+ Push a main → GitHub Actions → build + test → semantic-release → npm publish + GitHub Release
9
+ ```
10
+
11
+ semantic-release analiza los commits (conventional commits) para determinar la version:
12
+ - `fix:` → patch (1.0.0 → 1.0.1)
13
+ - `feat:` → minor (1.0.0 → 1.1.0)
14
+ - `BREAKING CHANGE:` → major (1.0.0 → 2.0.0)
15
+ - `chore:`, `docs:`, `test:`, `refactor:` → no release
16
+
17
+ ## Prerequisitos
18
+
19
+ - Cuenta en [npmjs.com](https://www.npmjs.com) con acceso al paquete `claude-code-arcane`
20
+ - Acceso de admin al repositorio en GitHub
21
+
22
+ ## Paso 1: Crear token en npm
23
+
24
+ 1. Ir a **https://www.npmjs.com** y loguearse
25
+ 2. Click en el avatar (arriba a la derecha) → **Access Tokens**
26
+ 3. Click **Generate New Token** → elegir **Granular Access Token**
27
+ 4. Configurar:
28
+ - **Token name:** `arcane-release`
29
+ - **Expiration:** sin expiracion (o la que se prefiera)
30
+ - **Packages and scopes:** seleccionar **Read and write**
31
+ - **Select packages:** elegir `claude-code-arcane`
32
+ 5. Click **Generate Token**
33
+ 6. **Copiar el token** — solo se muestra una vez
34
+
35
+ ## Paso 2: Agregar secret en GitHub
36
+
37
+ 1. Ir a [Settings > Secrets > Actions](https://github.com/SebastianLuser/Claude-Code-Arcane/settings/secrets/actions)
38
+ 2. Click **New repository secret**
39
+ 3. Completar:
40
+ - **Name:** `NPM_TOKEN`
41
+ - **Secret:** pegar el token copiado en el paso anterior
42
+ 4. Click **Add secret**
43
+
44
+ > **Nota:** `GITHUB_TOKEN` se provee automaticamente por GitHub Actions, no hace falta configurarlo.
45
+
46
+ ## Paso 3: Verificar
47
+
48
+ 1. Hacer un push a `main` con un commit tipo `feat:` o `fix:`
49
+ 2. Ir a [Actions](https://github.com/SebastianLuser/Claude-Code-Arcane/actions) y verificar que el workflow **Release** corre exitosamente
50
+ 3. Verificar en npm que la nueva version aparece: `npm view claude-code-arcane version`
51
+ 4. Verificar que se creo un [GitHub Release](https://github.com/SebastianLuser/Claude-Code-Arcane/releases) con changelog automatico
52
+
53
+ ## Archivos de configuracion
54
+
55
+ | Archivo | Proposito |
56
+ |---------|-----------|
57
+ | `.releaserc.json` | Configuracion de semantic-release (plugins, branches) |
58
+ | `.github/workflows/release.yml` | Workflow de GitHub Actions que ejecuta semantic-release |
59
+ | `.github/workflows/ci.yml` | CI separado (build + test + typecheck) |
60
+
61
+ ## Troubleshooting
62
+
63
+ ### El workflow falla con "NPM_TOKEN is not set"
64
+ El secret no esta configurado. Seguir el Paso 2.
65
+
66
+ ### El workflow corre pero no publica
67
+ semantic-release solo publica si hay commits que justifiquen un bump de version. Commits tipo `chore:`, `docs:`, `test:` no generan release. Usar `feat:` o `fix:`.
68
+
69
+ ### Error "npm ERR! 403 Forbidden"
70
+ El token no tiene permisos de escritura sobre el paquete, o expiro. Regenerar siguiendo el Paso 1.
71
+
72
+ ### El CHANGELOG.md no se actualiza
73
+ semantic-release commitea el changelog automaticamente con `[skip ci]` para no generar un loop. Si no aparece, verificar que el plugin `@semantic-release/git` este en `.releaserc.json`.
74
+
75
+ ## Hybrid Distribution
76
+
77
+ Ademas del release automatico, Arcane soporta distribucion hibrida:
78
+
79
+ - **GitHub source:** `npx arcane install backend-ts --source github` baja contenido directamente del repo
80
+ - **Cache local:** contenido descargado se cachea en `~/.arcane/cache/`
81
+ - **Bundled fallback:** si GitHub no esta disponible, usa el contenido empaquetado en npm
82
+ - **Auto:** `--source auto` (default) intenta GitHub → cache → bundled
83
+
84
+ Variable de entorno: `ARCANE_SOURCE=bundled|github|auto`
85
+
86
+ ## Smart Update
87
+
88
+ `arcane update` compara el contenido instalado con la ultima version disponible:
89
+
90
+ ```bash
91
+ arcane update # actualiza contenido (three-way merge)
92
+ arcane update --dry-run # muestra que cambiaria sin aplicar
93
+ arcane update --force # sobreescribe incluso archivos modificados localmente
94
+ ```
95
+
96
+ El three-way merge detecta:
97
+ - Cambios upstream que no fueron tocados localmente → **actualiza**
98
+ - Archivos customizados localmente sin cambios upstream → **preserva**
99
+ - Conflictos (ambos cambiaron) → **backup + actualiza + avisa**
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env python3
2
+ """Genera la presentacion ejecutiva de Claude Code Arcane (8 slides, 16:9)."""
3
+ import os
4
+ from pptx import Presentation
5
+ from pptx.util import Inches, Pt, Emu
6
+ from pptx.dml.color import RGBColor
7
+ from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
8
+ from pptx.enum.shapes import MSO_SHAPE
9
+ from pptx.oxml.ns import qn
10
+
11
+ # ---------- Tema (paleta Arcane: indigo profundo + cian) ----------
12
+ BG = RGBColor(0x1E, 0x1B, 0x4B) # indigo-950 fondo
13
+ PANEL = RGBColor(0x2A, 0x25, 0x60) # panel
14
+ PANEL2 = RGBColor(0x31, 0x2E, 0x81) # panel mas claro
15
+ VIOLET = RGBColor(0x5B, 0x4F, 0xE8) # acento violeta
16
+ CYAN = RGBColor(0x22, 0xD3, 0xEE) # acento cian
17
+ WHITE = RGBColor(0xF8, 0xFA, 0xFC)
18
+ LIGHT = RGBColor(0xE5, 0xE7, 0xEB)
19
+ MUTED = RGBColor(0x9C, 0xA3, 0xC4)
20
+ GREEN = RGBColor(0x34, 0xD3, 0x99)
21
+ AMBER = RGBColor(0xFB, 0xBF, 0x24)
22
+ FONT = "Calibri"
23
+ FONT_H = "Calibri"
24
+
25
+ prs = Presentation()
26
+ prs.slide_width = Inches(13.333)
27
+ prs.slide_height = Inches(7.5)
28
+ BLANK = prs.slide_layouts[6]
29
+ SW, SH = prs.slide_width, prs.slide_height
30
+
31
+
32
+ def slide():
33
+ s = prs.slides.add_slide(BLANK)
34
+ r = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SW, SH)
35
+ r.fill.solid(); r.fill.fore_color.rgb = BG
36
+ r.line.fill.background()
37
+ r.shadow.inherit = False
38
+ return s
39
+
40
+
41
+ def rect(s, x, y, w, h, color, line=None, radius=False, line_w=1.0):
42
+ shp = MSO_SHAPE.ROUNDED_RECTANGLE if radius else MSO_SHAPE.RECTANGLE
43
+ sp = s.shapes.add_shape(shp, Inches(x), Inches(y), Inches(w), Inches(h))
44
+ if color is None:
45
+ sp.fill.background()
46
+ else:
47
+ sp.fill.solid(); sp.fill.fore_color.rgb = color
48
+ if line is None:
49
+ sp.line.fill.background()
50
+ else:
51
+ sp.line.color.rgb = line; sp.line.width = Pt(line_w)
52
+ sp.shadow.inherit = False
53
+ return sp
54
+
55
+
56
+ def txt(s, x, y, w, h, runs, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP,
57
+ space_after=4, line_spacing=1.0):
58
+ """runs: lista de parrafos; cada parrafo es lista de (texto, size, color, bold)."""
59
+ tb = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
60
+ tf = tb.text_frame; tf.word_wrap = True
61
+ tf.vertical_anchor = anchor
62
+ for i, para in enumerate(runs):
63
+ p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
64
+ p.alignment = align
65
+ p.space_after = Pt(space_after); p.space_before = Pt(0)
66
+ p.line_spacing = line_spacing
67
+ for (t, sz, col, bold) in para:
68
+ r = p.add_run(); r.text = t
69
+ r.font.size = Pt(sz); r.font.color.rgb = col
70
+ r.font.bold = bold; r.font.name = FONT
71
+ return tb
72
+
73
+
74
+ def badge(s, n):
75
+ c = s.shapes.add_shape(MSO_SHAPE.OVAL, Inches(12.55), Inches(6.85), Inches(0.42), Inches(0.42))
76
+ c.fill.solid(); c.fill.fore_color.rgb = VIOLET; c.line.fill.background(); c.shadow.inherit = False
77
+ tf = c.text_frame; tf.word_wrap = False
78
+ p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER
79
+ r = p.add_run(); r.text = str(n)
80
+ r.font.size = Pt(12); r.font.bold = True; r.font.color.rgb = WHITE; r.font.name = FONT
81
+ tf.vertical_anchor = MSO_ANCHOR.MIDDLE
82
+
83
+
84
+ def footer(s):
85
+ txt(s, 0.55, 7.0, 5, 0.35, [[("Claude Code Arcane", 9, MUTED, False)]])
86
+
87
+
88
+ def kicker(s, text, color=CYAN):
89
+ rect(s, 0.55, 0.55, 0.12, 0.55, color)
90
+ txt(s, 0.8, 0.5, 11, 0.5, [[(text, 13, color, True)]])
91
+
92
+
93
+ def title(s, text, y=0.95, size=34):
94
+ txt(s, 0.78, y, 11.8, 1.0, [[(text, size, WHITE, True)]])
95
+
96
+
97
+ # ================= SLIDE 1 — Portada =================
98
+ s = slide()
99
+ rect(s, 0, 0, 13.333, 7.5, None)
100
+ # barra de acento lateral
101
+ rect(s, 0, 0, 0.25, 7.5, VIOLET)
102
+ rect(s, 0.25, 0, 0.08, 7.5, CYAN)
103
+ txt(s, 1.0, 1.7, 11.5, 0.6, [[("CLAUDE CODE", 20, CYAN, True)]])
104
+ txt(s, 1.0, 2.15, 11.5, 1.4, [[("Arcane", 88, WHITE, True)]])
105
+ txt(s, 1.05, 3.65, 11, 0.6,
106
+ [[("Un gestor de paquetes de configuracion para Claude Code", 22, LIGHT, False)]])
107
+ txt(s, 1.05, 4.25, 11, 0.5,
108
+ [[("Instala solo las capacidades que tu proyecto necesita — con un comando.", 15, MUTED, False)]])
109
+
110
+ # dashboard de metricas
111
+ metrics = [("322", "Skills"), ("86", "Agentes"), ("29", "Profiles"), ("19", "Rules")]
112
+ mx, mw, gap = 1.0, 2.7, 0.25
113
+ for i, (num, lbl) in enumerate(metrics):
114
+ x = mx + i * (mw + gap)
115
+ rect(s, x, 5.3, mw, 1.35, PANEL, radius=True)
116
+ rect(s, x, 5.3, 0.1, 1.35, CYAN if i % 2 else VIOLET, radius=False)
117
+ txt(s, x, 5.45, mw, 0.8, [[(num, 44, WHITE, True)]], align=PP_ALIGN.CENTER)
118
+ txt(s, x, 6.2, mw, 0.4, [[(lbl.upper(), 13, CYAN, True)]], align=PP_ALIGN.CENTER)
119
+
120
+ # ================= SLIDE 2 — El problema =================
121
+ s = slide()
122
+ kicker(s, "POR QUE EXISTE")
123
+ title(s, "El problema que resuelve")
124
+ problems = [
125
+ ("Configuracion manual", "Copiar .claude/ a mano en cada proyecto: repetitivo y diverge con el tiempo."),
126
+ ("Todo-o-nada", "Un mega-repo cargaria las 322 skills en CADA proyecto: mas tokens, mas ruido, mas latencia."),
127
+ ("Sin estandarizacion", "Cada dev configura su Claude distinto. No hay fuente de verdad compartida."),
128
+ ("Sin actualizacion", "Una mejora en una skill nunca llega a los proyectos que ya la copiaron."),
129
+ ]
130
+ y = 2.05
131
+ for i, (h, d) in enumerate(problems):
132
+ rect(s, 0.78, y, 11.7, 1.0, PANEL, radius=True)
133
+ rect(s, 0.78, y, 0.1, 1.0, VIOLET)
134
+ txt(s, 1.05, y + 0.13, 3.4, 0.8, [[(h, 17, CYAN, True)]], anchor=MSO_ANCHOR.MIDDLE)
135
+ txt(s, 4.5, y + 0.13, 7.8, 0.8, [[(d, 13.5, LIGHT, False)]], anchor=MSO_ANCHOR.MIDDLE)
136
+ y += 1.15
137
+ txt(s, 0.78, 6.75, 11.8, 0.5,
138
+ [[("Falta una forma modular, versionada y compartible de configurar Claude Code.", 15, WHITE, True)]])
139
+ badge(s, 2); footer(s)
140
+
141
+ # ================= SLIDE 3 — Idea central =================
142
+ s = slide()
143
+ kicker(s, "LA IDEA CENTRAL")
144
+ title(s, "npm, pero para las capacidades de Claude Code")
145
+ txt(s, 0.78, 2.0, 11.7, 1.0,
146
+ [[("En vez de instalar librerias de codigo, instalas ", 17, LIGHT, False),
147
+ ("capacidades", 17, CYAN, True),
148
+ (": hacer commits, disenar APIs, correr un sprint, auditar seguridad — adaptadas a tu stack.", 17, LIGHT, False)]],
149
+ line_spacing=1.15)
150
+
151
+ # comando hero estilo terminal
152
+ rect(s, 0.78, 3.25, 11.7, 1.5, RGBColor(0x14, 0x11, 0x33), radius=True, line=VIOLET, line_w=1.5)
153
+ txt(s, 1.1, 3.45, 11, 0.4, [[("terminal", 11, MUTED, False)]])
154
+ txt(s, 1.1, 3.85, 11, 0.7,
155
+ [[("$ ", 26, CYAN, True), ("npx arcane install ", 26, WHITE, True),
156
+ ("backend-ts", 26, GREEN, True), ("+agile", 26, AMBER, True), ("+testing", 26, AMBER, True)]])
157
+
158
+ cols = [("backend-ts", "base", GREEN), ("+agile", "addon", AMBER), ("+testing", "addon", AMBER)]
159
+ cx = 0.78
160
+ for name, kind, col in cols:
161
+ rect(s, cx, 5.05, 3.0, 1.1, PANEL, radius=True)
162
+ txt(s, cx, 5.2, 3.0, 0.5, [[(name, 18, col, True)]], align=PP_ALIGN.CENTER)
163
+ txt(s, cx, 5.7, 3.0, 0.4, [[(kind.upper(), 12, MUTED, True)]], align=PP_ALIGN.CENTER)
164
+ cx += 3.25
165
+ txt(s, 0.78, 6.45, 11.7, 0.5,
166
+ [[("base + addon + addon -> solo lo que ESE proyecto necesita.", 15, WHITE, True)]])
167
+ badge(s, 3); footer(s)
168
+
169
+ # ================= SLIDE 4 — Como funciona =================
170
+ s = slide()
171
+ kicker(s, "ARQUITECTURA")
172
+ title(s, "Como funciona")
173
+ steps = [
174
+ ("1", "Resuelve el contenido", "GitHub -> cache local -> bundled (funciona offline)"),
175
+ ("2", "Carga core.yaml siempre", "21 skills base + 15 hooks + permisos de seguridad"),
176
+ ("3", "Mergea los profiles", "Combina los profiles pedidos con +"),
177
+ ("4", "Deduplica", "Un skill repetido se copia una sola vez"),
178
+ ("5", "Genera .claude/", "settings.json + manifest + skills, agents, rules, hooks"),
179
+ ]
180
+ y = 2.05
181
+ for num, h, d in steps:
182
+ rect(s, 0.78, y, 11.7, 0.84, PANEL, radius=True)
183
+ c = s.shapes.add_shape(MSO_SHAPE.OVAL, Inches(0.98), Inches(y + 0.17), Inches(0.5), Inches(0.5))
184
+ c.fill.solid(); c.fill.fore_color.rgb = VIOLET; c.line.fill.background(); c.shadow.inherit = False
185
+ ctf = c.text_frame; ctf.vertical_anchor = MSO_ANCHOR.MIDDLE
186
+ cp = ctf.paragraphs[0]; cp.alignment = PP_ALIGN.CENTER
187
+ cr = cp.add_run(); cr.text = num; cr.font.size = Pt(20); cr.font.bold = True
188
+ cr.font.color.rgb = WHITE; cr.font.name = FONT
189
+ txt(s, 1.75, y + 0.05, 4.0, 0.74, [[(h, 16, CYAN, True)]], anchor=MSO_ANCHOR.MIDDLE)
190
+ txt(s, 5.9, y + 0.05, 6.4, 0.74, [[(d, 13, LIGHT, False)]], anchor=MSO_ANCHOR.MIDDLE)
191
+ y += 0.95
192
+ badge(s, 4); footer(s)
193
+
194
+ # ================= SLIDE 5 — 4 piezas =================
195
+ s = slide()
196
+ kicker(s, "SEPARACION DE RESPONSABILIDADES")
197
+ title(s, "Cuatro piezas, cuatro responsabilidades")
198
+ cards = [
199
+ ("Skills", "Acciones que Claude ejecuta", "/commit · /code-review", CYAN),
200
+ ("Agents", "Personas que Claude adopta", "backend-architect · game-designer", VIOLET),
201
+ ("Hooks", "Automatizacion invisible (eventos)", "valida commits · escanea secrets", GREEN),
202
+ ("Rules", "Estandares que Claude obedece", "\"Clean architecture en backend\"", AMBER),
203
+ ]
204
+ positions = [(0.78, 2.05), (6.93, 2.05), (0.78, 4.25), (6.93, 4.25)]
205
+ cw, ch = 5.6, 2.0
206
+ for (name, what, ex, col), (x, y) in zip(cards, positions):
207
+ rect(s, x, y, cw, ch, PANEL, radius=True)
208
+ rect(s, x, y, cw, 0.12, col)
209
+ txt(s, x + 0.3, y + 0.25, cw - 0.6, 0.5, [[(name, 24, col, True)]])
210
+ txt(s, x + 0.3, y + 0.85, cw - 0.6, 0.5, [[(what, 14.5, LIGHT, False)]])
211
+ txt(s, x + 0.3, y + 1.4, cw - 0.6, 0.5, [[(ex, 13, MUTED, True)]])
212
+ txt(s, 0.78, 6.55, 11.7, 0.5,
213
+ [[("Todo en Markdown + YAML: legible, diffeable en git, editable por cualquiera.", 14, WHITE, True)]])
214
+ badge(s, 5); footer(s)
215
+
216
+ # ================= SLIDE 6 — Por que gana =================
217
+ s = slide()
218
+ kicker(s, "DIFERENCIADORES")
219
+ title(s, "Por que Arcane gana")
220
+ # encabezados
221
+ rect(s, 0.78, 2.0, 5.7, 0.55, PANEL2, radius=True)
222
+ rect(s, 6.63, 2.0, 5.85, 0.55, PANEL2, radius=True)
223
+ txt(s, 1.0, 2.05, 5.3, 0.45, [[("ALTERNATIVA", 13, MUTED, True)]], anchor=MSO_ANCHOR.MIDDLE)
224
+ txt(s, 6.85, 2.05, 5.4, 0.45, [[("COMO GANA ARCANE", 13, CYAN, True)]], anchor=MSO_ANCHOR.MIDDLE)
225
+ rows = [
226
+ ("Config a mano", "Un comando, versionado, reproducible"),
227
+ ("Mega-repo monolitico", "Selectivo por profile + dedup (menos tokens)"),
228
+ ("Copiar config entre proyectos", "Smart update con three-way merge (no pisa tus cambios)"),
229
+ ("Claude \"vanilla\"", "Hooks + rules + 86 agentes especializados"),
230
+ ]
231
+ y = 2.65
232
+ for a, b in rows:
233
+ rect(s, 0.78, y, 5.7, 0.7, PANEL, radius=True)
234
+ rect(s, 6.63, y, 5.85, 0.7, PANEL, radius=True)
235
+ txt(s, 1.0, y + 0.05, 5.3, 0.6, [[(a, 14, LIGHT, False)]], anchor=MSO_ANCHOR.MIDDLE)
236
+ txt(s, 6.85, y + 0.05, 5.4, 0.6, [[(b, 13.5, WHITE, True)]], anchor=MSO_ANCHOR.MIDDLE)
237
+ y += 0.8
238
+ rect(s, 0.78, y + 0.05, 11.7, 0.7, VIOLET, radius=True)
239
+ txt(s, 0.78, y + 0.1, 11.7, 0.6,
240
+ [[("Trata la configuracion de IA como software de primera clase: versionado, modular, testeable y distribuido.",
241
+ 14, WHITE, True)]], align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
242
+ badge(s, 6); footer(s)
243
+
244
+ # ================= SLIDE 7 — Pros y contras =================
245
+ s = slide()
246
+ kicker(s, "EVALUACION HONESTA")
247
+ title(s, "Trade-offs")
248
+ # Fortalezas
249
+ rect(s, 0.78, 2.0, 5.7, 4.6, PANEL, radius=True)
250
+ rect(s, 0.78, 2.0, 5.7, 0.6, RGBColor(0x14, 0x3D, 0x2E), radius=True)
251
+ txt(s, 1.05, 2.05, 5.3, 0.5, [[("FORTALEZAS", 16, GREEN, True)]], anchor=MSO_ANCHOR.MIDDLE)
252
+ pros = [
253
+ "Eficiencia de tokens (solo lo relevante)",
254
+ "Un comando, cero friccion (npx)",
255
+ "Reproducible (manifest = snapshot exacto)",
256
+ "Estandarizacion de equipo",
257
+ "Auto-actualizable sin pisar tus cambios",
258
+ "Testeable (framework skills-selftest)",
259
+ ]
260
+ y = 2.85
261
+ for p in pros:
262
+ txt(s, 1.05, y, 5.2, 0.5, [[("+ ", 14, GREEN, True), (p, 13.5, LIGHT, False)]])
263
+ y += 0.6
264
+ # Costos
265
+ rect(s, 6.93, 2.0, 5.55, 4.6, PANEL, radius=True)
266
+ rect(s, 6.93, 2.0, 5.55, 0.6, RGBColor(0x4A, 0x39, 0x12), radius=True)
267
+ txt(s, 7.2, 2.05, 5.1, 0.5, [[("COSTOS / TRADE-OFFS", 16, AMBER, True)]], anchor=MSO_ANCHOR.MIDDLE)
268
+ cons = [
269
+ "Mantener 322 skills con calidad consistente",
270
+ "Skills son prompts -> no deterministas",
271
+ "Dependencia de la API de Claude Code",
272
+ "Curva de aprendizaje para combinar profiles",
273
+ ]
274
+ y = 2.95
275
+ for c in cons:
276
+ txt(s, 7.2, y, 5.0, 0.6, [[("! ", 14, AMBER, True), (c, 13.5, LIGHT, False)]])
277
+ y += 0.75
278
+ badge(s, 7); footer(s)
279
+
280
+ # ================= SLIDE 8 — Cierre =================
281
+ s = slide()
282
+ rect(s, 0, 0, 0.25, 7.5, VIOLET)
283
+ rect(s, 0.25, 0, 0.08, 7.5, CYAN)
284
+ kicker(s, "PRODUCTION-GRADE")
285
+ title(s, "No es un script: es infraestructura", size=32)
286
+ feats = [
287
+ ("Distribucion hibrida", "GitHub -> cache -> bundled. Funciona incluso offline."),
288
+ ("Smart update", "Three-way merge: actualiza sin pisar tus customizaciones."),
289
+ ("Worktree-aware", "Comparte hooks/docs, copia skills por rama."),
290
+ ("Auto-release", "semantic-release: conventional commits -> publish a npm."),
291
+ ]
292
+ positions = [(0.78, 2.15), (6.93, 2.15), (0.78, 3.75), (6.93, 3.75)]
293
+ for (h, d), (x, y) in zip(feats, positions):
294
+ rect(s, x, y, 5.6, 1.4, PANEL, radius=True)
295
+ rect(s, x, y, 0.1, 1.4, CYAN)
296
+ txt(s, x + 0.3, y + 0.18, 5.1, 0.5, [[(h, 17, CYAN, True)]])
297
+ txt(s, x + 0.3, y + 0.68, 5.1, 0.6, [[(d, 13, LIGHT, False)]])
298
+
299
+ rect(s, 0.78, 5.55, 11.7, 1.3, VIOLET, radius=True)
300
+ txt(s, 0.78, 5.7, 11.7, 0.55,
301
+ [[("Configuracion de IA, modular y versionada — un comando para empezar.", 19, WHITE, True)]],
302
+ align=PP_ALIGN.CENTER)
303
+ txt(s, 0.78, 6.25, 11.7, 0.5,
304
+ [[("npx arcane install <profile>", 22, WHITE, True)]], align=PP_ALIGN.CENTER)
305
+ badge(s, 8); footer(s)
306
+
307
+ out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "arcane-overview.pptx")
308
+ prs.save(out)
309
+ print("OK ->", out)
310
+ print("Slides:", len(prs.slides._sldIdLst))