# Skillit - Plan de Implementacion ## Contexto App web para gestionar y distribuir Claude Code skills. Los usuarios suben/editan skills via web, y Claude Code las descarga ejecutando un script de sync (`curl ... | bash`). **Stack**: Astro 5 (SSR) + Vue 3 (islands) + TailwindCSS 4 + Node adapter **Deploy**: Coolify (Docker) **Storage**: Filesystem directo (no Content Collections, no DB) **Auth**: Ninguna --- ## Decision arquitectonica clave **No usar Astro Content Collections.** Las Content Collections cachean datos en build time, pero esta app necesita CRUD en tiempo real. Usamos `gray-matter` + `fs/promises` directamente. Los skills se guardan en `data/skills/` (fuera de `src/content/`) para evitar conflictos con Astro. --- ## Estructura de archivos ``` skillit/ ├── astro.config.mjs ├── tsconfig.json ├── package.json ├── Dockerfile ├── .dockerignore ├── data/ │ └── skills/ # Skills .md (target del CRUD) │ └── example-skill.md # Seed data ├── src/ │ ├── styles/global.css # @import "tailwindcss" │ ├── lib/skills.ts # CRUD filesystem helpers │ ├── components/ │ │ ├── SkillCard.astro # Card para el catalogo │ │ ├── SkillEditor.vue # Editor markdown + preview │ │ └── DeleteButton.vue # Boton eliminar con confirmacion │ ├── layouts/Base.astro # HTML shell, nav, CSS │ └── pages/ │ ├── index.astro # Catalogo (grid de cards) │ ├── skills/ │ │ ├── [slug].astro # Ver skill (markdown renderizado) │ │ ├── new.astro # Crear skill (monta SkillEditor) │ │ └── [slug]/edit.astro # Editar skill │ └── api/ │ ├── skills/index.ts # GET lista + POST crear │ ├── skills/[slug].ts # GET raw + PUT + DELETE │ └── sync.ts # GET -> script bash de sync ``` --- ## Fases de implementacion ### Fase 0: Scaffolding 1. **Crear proyecto Astro** en el directorio actual ```bash npm create astro@latest . -- --template minimal --typescript strict --install --git ``` 2. **Instalar dependencias** ```bash npx astro add node vue tailwind npm install gray-matter marked ``` 3. **Configurar `astro.config.mjs`** - `output: 'server'` (SSR) - `adapter: node({ mode: 'standalone' })` - Integraciones: Vue, TailwindCSS vite plugin 4. **`src/styles/global.css`**: solo `@import "tailwindcss"` 5. **Crear `data/skills/`** y el seed `example-skill.md` ### Fase 1: Core library 6. **`src/lib/skills.ts`** - Modulo central de CRUD: - `listSkills()` - lee directorio, parsea con gray-matter - `getSkill(slug)` - lee un .md, devuelve null si no existe - `createSkill(slug, content)` - escribe .md, error si ya existe - `updateSkill(slug, content)` - sobreescribe .md - `deleteSkill(slug)` - elimina .md - `isValidSlug()` - valida `/^[a-z0-9][a-z0-9-]*[a-z0-9]$/`, max 64 chars - `SKILLS_DIR` configurable via env var, default `data/skills/` ### Fase 2: API endpoints 7. **`src/pages/api/skills/index.ts`** - GET: lista skills como JSON `[{slug, name, description, allowedTools}]` - POST: crea skill, body `{slug, content}`, returns 201/400/409 8. **`src/pages/api/skills/[slug].ts`** - GET: devuelve raw .md (`Content-Type: text/markdown`) - PUT: actualiza skill, body `{content}`, returns 200/404 - DELETE: elimina skill, returns 204/404 9. **`src/pages/api/sync.ts`** - GET: genera script bash que: - Crea `~/.claude/skills/` si no existe - Para cada skill: `mkdir -p` + `curl` del raw .md a `SKILL.md` - Uso: `curl -fsSL https://skillit.example.com/api/sync | bash` ### Fase 3: UI read-only 10. **`src/layouts/Base.astro`** - HTML shell con nav (logo + link "New Skill") 11. **`src/components/SkillCard.astro`** - Card con nombre, descripcion truncada, badges de tools 12. **`src/pages/index.astro`** - Catalogo: llama `listSkills()`, renderiza grid de SkillCards. Empty state si no hay skills. 13. **`src/pages/skills/[slug].astro`** - Vista detalle: renderiza markdown con `marked`, muestra metadata, botones Edit/Delete ### Fase 4: UI write 14. **`src/components/SkillEditor.vue`** (island `client:load`) - Props: `initialContent?`, `slug?`, `mode: 'create' | 'edit'` - Layout 2 paneles: textarea izquierda + preview derecha - Campos de formulario arriba: name (auto-genera slug), description, allowed-tools - Preview en tiempo real con `marked` (debounced 300ms) - Save: POST o PUT segun mode, redirect al detalle 15. **`src/pages/skills/new.astro`** - Monta SkillEditor en modo create 16. **`src/pages/skills/[slug]/edit.astro`** - Carga skill, monta SkillEditor en modo edit con datos 17. **`src/components/DeleteButton.vue`** (island `client:load`) - Prop: `slug` - Click -> confirm -> `fetch DELETE` -> redirect a `/` ### Fase 5: Deployment 18. **Dockerfile** (multi-stage): - Build: `node:22-alpine`, `npm install`, `npm run build` - Runtime: copia `dist/` + `node_modules` (prod) + `data/skills/` - `ENV SKILLS_DIR=/app/data/skills` - `CMD ["node", "./dist/server/entry.mjs"]` - Puerto 4321 19. **.dockerignore**: `node_modules`, `dist`, `.git`, `.env` --- ## Verificacion **Tras Fase 2 (API):** ```bash npm run dev curl http://localhost:4321/api/skills # JSON array curl http://localhost:4321/api/skills/example-skill # raw .md ``` **Tras Fase 3 (UI read-only):** - Visitar `/` -> ver card del example-skill - Click card -> ver skill renderizada **Tras Fase 4 (CRUD completo):** - `/skills/new` -> crear skill -> aparece en catalogo - Editar skill -> cambios persistidos - Eliminar skill -> desaparece - `curl http://localhost:4321/api/sync` -> script bash funcional - `curl -fsSL http://localhost:4321/api/sync | bash && ls ~/.claude/skills/` **Tras Fase 5 (Docker):** ```bash docker build -t skillit . docker run -p 4321:4321 -v skillit-data:/app/data/skills skillit ``` --- ## Notas para Coolify - Build pack: Dockerfile - Volumen persistente: montar en `/app/data/skills` para que los skills sobrevivan rebuilds - Puerto: 4321 - Env var opcional: `SKILLS_DIR` (default ya configurado en Dockerfile)