diff --git a/.astro/data-store.json b/.astro/data-store.json index 91e8452..180d6ce 100644 --- a/.astro/data-store.json +++ b/.astro/data-store.json @@ -1 +1 @@ -[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.17.1","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://skills.here.run.place\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\",\"entrypoint\":\"astro/assets/endpoint/dev\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[]},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false},\"session\":{\"driver\":\"fs-lite\",\"options\":{\"base\":\"/Users/alex/projects/skillit/node_modules/.astro/sessions\"}}}"] \ No newline at end of file +[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.17.1","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://grimoi.red\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\",\"entrypoint\":\"astro/assets/endpoint/dev\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[]},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false},\"session\":{\"driver\":\"fs-lite\",\"options\":{\"base\":\"/Users/alex/projects/skillit/node_modules/.astro/sessions\"}}}"] \ No newline at end of file diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b28cfc9..8d68edc 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,11 @@ "WebFetch(domain:astro.build)", "Bash(npm init:*)", "Bash(npm install:*)", - "Bash(npx astro check:*)" + "Bash(npx astro check:*)", + "Bash(node -e:*)", + "Bash(ls:*)", + "Bash(npx tsc:*)", + "mcp__ide__getDiagnostics" ] } } diff --git a/.claude/skills/testeo/SKILL.md b/.claude/skills/testeo/SKILL.md new file mode 100644 index 0000000..ba46ca9 --- /dev/null +++ b/.claude/skills/testeo/SKILL.md @@ -0,0 +1,10 @@ +--- +name: testeo +description: Un test de skill +tags: test +allowed-tools: Bash, Read, Write, Edit +model: claude-opus-4-6 +agent: Plan +--- + +# Es solo un prueba diff --git a/.claude/skills/testeo/assets/temp.tpl b/.claude/skills/testeo/assets/temp.tpl new file mode 100644 index 0000000..8f91b47 --- /dev/null +++ b/.claude/skills/testeo/assets/temp.tpl @@ -0,0 +1 @@ +template \ No newline at end of file diff --git a/.claude/skills/testeo/references/readme.md b/.claude/skills/testeo/references/readme.md new file mode 100644 index 0000000..cd4ab18 --- /dev/null +++ b/.claude/skills/testeo/references/readme.md @@ -0,0 +1 @@ +referencia \ No newline at end of file diff --git a/.claude/skills/testeo/scripts/run.sh b/.claude/skills/testeo/scripts/run.sh new file mode 100755 index 0000000..f4d7fa5 --- /dev/null +++ b/.claude/skills/testeo/scripts/run.sh @@ -0,0 +1 @@ +bash \ No newline at end of file diff --git a/PLAN-HOOKS.md b/PLAN-HOOKS.md new file mode 100644 index 0000000..447961e --- /dev/null +++ b/PLAN-HOOKS.md @@ -0,0 +1,368 @@ +# Plan: Hooks como nuevo tipo de recurso + +## Contexto + +Claude Code soporta **hooks**: comandos que se ejecutan automaticamente en respuesta a eventos del ciclo de vida del agente. Se configuran en `.claude/settings.json` bajo la clave `hooks`. + +### Estructura de hooks en Claude Code + +```json +// .claude/settings.json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { "type": "command", "command": "echo 'About to run bash'" } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Write", + "hooks": [ + { "type": "command", "command": "./scripts/lint.sh $CLAUDE_FILE_PATHS" } + ] + } + ] + } +} +``` + +### Eventos disponibles (14) + +| Evento | Cuando se dispara | +|--------|-------------------| +| `PreToolUse` | Antes de ejecutar una herramienta (puede bloquear) | +| `PostToolUse` | Despues de ejecutar una herramienta | +| `Notification` | Cuando Claude envia una notificacion | +| `Stop` | Cuando Claude termina de responder | +| `SubagentStop` | Cuando un subagente termina | +| `PreCompact` | Antes de compactar contexto | +| `PostCompact` | Despues de compactar contexto | + +### Tipos de handler + +| Tipo | Descripcion | +|------|-------------| +| `command` | Ejecuta un comando shell. Variables de entorno disponibles: `$CLAUDE_TOOL_NAME`, `$CLAUDE_FILE_PATHS`, `$CLAUDE_TOOL_INPUT`, etc. | +| `prompt` | Inyecta un prompt adicional a Claude | +| `agent` | Invoca un subagente para evaluar | + +### Variables de entorno en hooks + +- `CLAUDE_TOOL_NAME` — nombre de la herramienta +- `CLAUDE_TOOL_INPUT` — JSON con los parametros de la herramienta +- `CLAUDE_FILE_PATHS` — rutas de archivos separadas por newline +- `CLAUDE_TOOL_OUTPUT` — salida de la herramienta (solo en PostToolUse) +- `CLAUDE_NOTIFICATION` — texto de notificacion (solo en Notification) +- `CLAUDE_TRANSCRIPT` — transcripcion de la conversacion (solo en Stop/SubagentStop) + +## Modelo de datos + +Un hook en Grimoired se almacena como **carpeta** en `data/hooks//`: + +``` +my-hook/ +├── HOOK.md # Documentacion del hook (frontmatter + descripcion) +├── hooks.json # Configuracion JSON del hook (lo que se inyecta en settings) +└── scripts/ # Scripts opcionales referenciados por el hook + ├── lint.sh + └── validate.py +``` + +### HOOK.md (frontmatter) + +```yaml +--- +name: Auto Lint +description: Ejecuta linting automatico despues de cada escritura de archivo +tags: + - linting + - quality +author: alex +author-email: alex@example.com +events: + - PostToolUse +--- + +# Auto Lint + +Este hook ejecuta automaticamente el linter despues de que Claude escribe o edita un archivo... +``` + +### hooks.json + +```json +{ + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": ".claude/hooks/auto-lint/scripts/lint.sh $CLAUDE_FILE_PATHS" + } + ] + } + ] +} +``` + +> **Nota**: Los paths en `command` se reescriben durante la instalacion para apuntar a la ubicacion correcta en `.claude/hooks//`. + +## Archivos a modificar + +| Archivo | Cambio | +|---------|--------| +| `src/lib/registry.ts` | Agregar tipo `hooks` al registro con sus campos | +| `src/lib/resources.ts` | Sin cambios — ya soporta formato carpeta generico | +| `src/pages/[type]/[slug].astro` | Renderizar preview de hooks.json y info de eventos | +| `src/pages/[type]/[slug]/i.ts` | Script de instalacion especial para hooks | +| `src/pages/[type]/[slug]/gi.ts` | Script de instalacion global para hooks | +| `src/lib/sync.ts` | Sin cambios mayores — sync/push generico ya funciona | + +## Archivos nuevos + +| Archivo | Proposito | +|---------|-----------| +| `src/components/HookConfigPreview.astro` | Preview visual del hooks.json en pagina de detalle | +| `src/components/HookConfigEditor.vue` | Editor visual de hooks.json (formulario, no JSON crudo) | + +## Fases de implementacion + +### Fase 1: Registry — agregar tipo `hooks` + +Agregar `'hooks'` a `RESOURCE_TYPES` y su configuracion al `REGISTRY`: + +```typescript +export const RESOURCE_TYPES = ['skills', 'agents', 'output-styles', 'rules', 'hooks'] as const; + +// En REGISTRY: +hooks: { + slug: 'hooks', + label: 'Hooks', + labelSingular: 'Hook', + claudeDir: 'hooks', + dataDir: path.resolve(process.env.HOOKS_DIR || `${DATA_ROOT}/hooks`), + color: '#a78bfa', // violet + mainFileName: 'HOOK.md', + fields: [ + { + key: 'description', + label: 'Description', + type: 'text', + placeholder: 'Brief description of what this hook does', + }, + { + key: 'events', + label: 'Events', + type: 'tags', + hint: 'Hook events this resource handles', + placeholder: 'Add event...', + }, + ], +}, +``` + +**Nota**: Los hooks siempre son formato `folder` porque necesitan `hooks.json` + scripts opcionales. El campo `events` en frontmatter es informativo (para filtrado/busqueda); la configuracion real esta en `hooks.json`. + +### Fase 2: Formato obligatorio carpeta + +En `src/pages/[type]/new.astro` y `src/components/ResourceEditor.vue`: + +- Para tipo `hooks`: forzar `format: 'folder'` sin mostrar toggle. +- Al crear un hook, generar automaticamente un `hooks.json` vacio (`{}`) como archivo inicial en el directorio. + +En `src/lib/resources.ts` — `createResource`: +- Despues de crear la carpeta y el mainFile, si el tipo es `hooks`, escribir `hooks.json` con `{}`. + +### Fase 3: API — lectura de hooks.json + +El endpoint existente `/api/resources/[type]/[slug]/files/[...filePath].ts` ya permite GET de `hooks.json`. + +Agregar un endpoint auxiliar o logica al GET de `/api/resources/[type]/[slug].ts` para incluir el contenido de `hooks.json` parseado en la respuesta JSON cuando el tipo es `hooks`: + +```typescript +// En GET de [slug].ts, si type === 'hooks': +const hooksJsonBuf = await getResourceFile(type, slug, 'hooks.json'); +if (hooksJsonBuf) { + resource.hooksConfig = JSON.parse(hooksJsonBuf.toString('utf8')); +} +``` + +### Fase 4: Pagina de detalle — preview de hooks.json + +**`HookConfigPreview.astro`**: componente que recibe el JSON parseado de hooks.json y lo renderiza visualmente: + +``` +┌─────────────────────────────────────────┐ +│ PostToolUse │ +│ ┌─────────────────────────────────────┐ │ +│ │ Matcher: Write | Edit │ │ +│ │ ⚡ command │ │ +│ │ └ ./scripts/lint.sh $CLAUDE_FILE... │ │ +│ └─────────────────────────────────────┘ │ +└─────────────────────────────────────────┘ +``` + +- Cada evento como seccion con su nombre como titulo +- Cada matcher como sub-bloque +- Cada handler con icono segun tipo (command/prompt/agent) +- Truncar comandos largos con tooltip + +En `[type]/[slug].astro`: +- Leer `hooks.json` del recurso +- Si existe, renderizar `HookConfigPreview` entre la descripcion y el arbol de archivos +- Si no existe, mostrar un aviso sutil + +### Fase 5: Editor — HookConfigEditor.vue + +Componente Vue interactivo para editar hooks.json visualmente: + +**Estructura del formulario:** + +1. **Selector de evento** — dropdown con los 7 eventos +2. **Lista de matchers** por evento — cada uno con: + - Campo `matcher` (texto, patron regex) + - Lista de handlers: + - Tipo: `command` | `prompt` | `agent` + - Valor: texto del comando, prompt, o nombre del agente +3. **Botones** — Agregar evento, agregar matcher, agregar handler, eliminar + +**Integracion en ResourceEditor.vue:** +- Detectar tipo `hooks` +- Cargar `hooks.json` existente via API +- Renderizar `HookConfigEditor` debajo del editor de markdown +- Al guardar, enviar PUT al endpoint de files para actualizar `hooks.json` + +**Alternativa simplificada (fase inicial):** +- Usar el campo `json` existente del registry para editar hooks.json como JSON crudo +- Agregar al registry.ts un campo: +```typescript +{ + key: 'hooks-config', + label: 'Hook Configuration', + type: 'json', + placeholder: '{ "PostToolUse": [{ "matcher": "Write", "hooks": [{ "type": "command", "command": "echo done" }] }] }', + hint: 'JSON config that gets installed as hooks.json', +}, +``` +- **Pros**: Implementacion rapida, reutiliza componente existente +- **Contras**: UX menos amigable, propenso a errores de sintaxis +- **Recomendacion**: Empezar con JSON crudo, iterar al editor visual despues + +### Fase 6: Scripts de instalacion — logica especial para hooks + +Los hooks se instalan de forma diferente a skills/agents. No van a `.claude/hooks/` (ese directorio no existe en Claude Code). Se instalan asi: + +1. **Scripts** se copian a `.claude/hooks//scripts/` +2. **hooks.json** se lee y su contenido se **mergea** en `.claude/settings.local.json` bajo la clave `hooks` +3. **Los paths en commands** se reescriben para apuntar a la ubicacion de scripts + +**Script de instalacion (bash):** + +```bash +#!/usr/bin/env bash +set -euo pipefail + +SLUG="my-hook" +HOOKS_DIR=".claude/hooks/$SLUG" +SETTINGS=".claude/settings.local.json" + +# 1. Crear directorio y descargar archivos +mkdir -p "$HOOKS_DIR/scripts" +curl -fsSL "$ORIGIN/api/resources/hooks/$SLUG/files/hooks.json" -o "$HOOKS_DIR/hooks.json" +# ... descargar scripts ... +chmod +x "$HOOKS_DIR/scripts/"* + +# 2. Mergear hooks.json en settings +if [ ! -f "$SETTINGS" ]; then + echo '{}' > "$SETTINGS" +fi + +# Usar jq para mergear +jq --slurpfile new "$HOOKS_DIR/hooks.json" ' + .hooks //= {} | + .hooks = (.hooks * $new[0]) +' "$SETTINGS" > "$SETTINGS.tmp" && mv "$SETTINGS.tmp" "$SETTINGS" + +echo "✓ Installed hook $SLUG" +echo " Config merged into $SETTINGS" +echo " Scripts at $HOOKS_DIR/scripts/" +``` + +**Reescritura de paths**: Antes del merge, los paths en `command` que empiezan con `./scripts/` o `scripts/` se reescriben a `.claude/hooks//scripts/`. + +**Modificaciones en `i.ts` y `gi.ts`:** +- Detectar `type === 'hooks'` +- En vez del flujo generico de copiar a `.claude//`, usar el flujo especial: + 1. Descargar hooks.json y scripts a `.claude/hooks//` + 2. Reescribir paths en hooks.json + 3. Mergear en settings.local.json con jq +- Para `gi.ts`: lo mismo pero con `~/.claude/settings.json` (global) + +**PowerShell**: Equivalente usando `ConvertFrom-Json`/`ConvertTo-Json` en vez de jq. + +### Fase 7: Desinstalacion + +Agregar endpoint o logica para generar script de desinstalacion: + +**`src/pages/[type]/[slug]/uninstall.ts`** (nuevo, solo para hooks): +- Genera script que: + 1. Lee settings.local.json + 2. Elimina las entradas de hooks que coincidan con el slug + 3. Elimina `.claude/hooks//` + 4. Escribe settings.local.json actualizado + +Esto es mas importante para hooks que para otros tipos porque la instalacion modifica un archivo compartido (settings). + +## Consideraciones especiales + +### Diferencias con otros tipos de recursos + +| Aspecto | Skills/Agents/Rules | Hooks | +|---------|-------------------|-------| +| Formato | file o folder | siempre folder | +| Destino instalacion | `.claude//.md` o carpeta | `.claude/hooks//` + merge en settings | +| Desinstalacion | Borrar archivo/carpeta | Borrar carpeta + limpiar settings | +| Archivo config | No tiene | `hooks.json` | +| Claude Dir | `skills`, `agents`, etc. | `hooks` (para scripts, no para config) | + +### Validacion de hooks.json + +Validar estructura al guardar: +- Claves raiz deben ser eventos validos +- Cada entrada debe tener `matcher` (string) y `hooks` (array) +- Cada handler debe tener `type` (command/prompt/agent) y el valor correspondiente + +### Seguridad + +- Los scripts de hooks ejecutan comandos arbitrarios — la pagina de detalle debe mostrar un aviso claro +- En la instalacion, mostrar que comandos se van a registrar antes de ejecutar +- Nunca ejecutar hooks desde el servidor — solo generar scripts de instalacion + +## Orden de implementacion + +1. **Fase 1** — Registry: agregar tipo hooks (5 min) +2. **Fase 2** — Formato carpeta obligatorio + hooks.json inicial (15 min) +3. **Fase 3** — API lectura hooks.json (10 min) +4. **Fase 4** — Preview en pagina de detalle (30 min) +5. **Fase 5** — Editor (JSON crudo primero, visual despues) (20 min) +6. **Fase 6** — Scripts de instalacion especiales (45 min) — **mas complejo** +7. **Fase 7** — Script de desinstalacion (20 min) + +## Verificacion + +1. Crear un hook via web — verificar que se crea como carpeta con HOOK.md + hooks.json vacio +2. Editar hooks.json con configuracion real (PostToolUse + command) +3. Subir script a scripts/ via FileManager +4. Ver pagina de detalle — verificar preview de configuracion +5. Ejecutar `curl .../hooks/my-hook/i | bash` — verificar: + - Scripts descargados a `.claude/hooks/my-hook/scripts/` + - hooks.json mergeado en `.claude/settings.local.json` + - Paths reescritos correctamente +6. Verificar que el hook funciona ejecutando Claude Code en un proyecto con el hook instalado +7. Ejecutar script de desinstalacion — verificar limpieza de settings y archivos +8. Crear hook sin scripts (solo command inline) — verificar instalacion simplificada diff --git a/PLAN.md b/PLAN.md index b9625a2..2c1d921 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,179 +1,152 @@ -# Skillit - Plan de Implementacion +# Plan: Soporte de Skills en formato carpeta (Folder-based resources) ## 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 - +El PDF oficial de Anthropic define que una skill puede ser una **carpeta** con subdirectorios: ``` -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 +my-skill/ +├── SKILL.md # Requerido +├── scripts/ # Opcional - codigo ejecutable +├── references/ # Opcional - documentacion +└── assets/ # Opcional - plantillas, fuentes, iconos ``` ---- +Actualmente Skillit almacena cada recurso como un unico `.md` en `data//.md`. Queremos soportar **ambos formatos**: simple (archivo .md) y carpeta (directorio con SKILL.md + subdirectorios). + +## Archivos a modificar + +| Archivo | Cambio | +|---------|--------| +| `src/lib/registry.ts` | Agregar `mainFileName` por tipo y constante `FOLDER_SUBDIRS` | +| `src/lib/resources.ts` | Reescribir CRUD para detectar/manejar ambos formatos; nuevas funciones para sub-archivos | +| `src/lib/skills.ts` | Actualizar interfaz `Skill` con `format` y `files` | +| `src/lib/sync.ts` | Scripts de sync/push con soporte carpeta | +| `src/pages/api/resources/[type]/index.ts` | Aceptar `format` en POST | +| `src/pages/api/resources/[type]/[slug].ts` | Manejar carpeta en GET | +| `src/pages/[type]/[slug].astro` | Mostrar arbol de archivos | +| `src/pages/[type]/[slug]/edit.astro` | Pasar format/files al editor | +| `src/pages/[type]/new.astro` | Soporte selector de formato | +| `src/pages/[type]/[slug]/i.ts` | Install scripts multi-archivo | +| `src/pages/[type]/[slug]/gi.ts` | Install global multi-archivo | +| `src/components/ResourceEditor.vue` | Toggle formato + integrar FileManager | + +## Archivos nuevos + +| Archivo | Proposito | +|---------|-----------| +| `src/pages/api/resources/[type]/[slug]/files/index.ts` | Listar y subir sub-archivos | +| `src/pages/api/resources/[type]/[slug]/files/[...filePath].ts` | CRUD individual de sub-archivos | +| `src/components/FileManager.vue` | Componente Vue para gestionar sub-archivos | +| `src/components/FolderTree.astro` | Componente Astro para mostrar arbol de archivos en detalle | ## Fases de implementacion -### Fase 0: Scaffolding +### Fase 1: Registry (`registry.ts`) -1. **Crear proyecto Astro** en el directorio actual - ```bash - npm create astro@latest . -- --template minimal --typescript strict --install --git - ``` +Agregar campo `mainFileName` a `ResourceTypeConfig`: +- skills: `SKILL.md` +- agents: `AGENT.md` +- output-styles: `OUTPUT-STYLE.md` +- rules: `RULE.md` -2. **Instalar dependencias** - ```bash - npx astro add node vue tailwind - npm install gray-matter marked - ``` +Agregar constante `FOLDER_SUBDIRS = ['scripts', 'references', 'assets']`. -3. **Configurar `astro.config.mjs`** - - `output: 'server'` (SSR) - - `adapter: node({ mode: 'standalone' })` - - Integraciones: Vue, TailwindCSS vite plugin +### Fase 2: Core CRUD (`resources.ts`) -4. **`src/styles/global.css`**: solo `@import "tailwindcss"` +Cambios principales: -5. **Crear `data/skills/`** y el seed `example-skill.md` +1. **Nuevos tipos**: + - `ResourceFormat = 'file' | 'folder'` + - `ResourceFileEntry = { relativePath: string; size: number }` + - Agregar `format` y `files` a interfaz `Resource` -### Fase 1: Core library +2. **`resolveResource(type, slug)`** (nueva): detecta si el recurso es archivo o carpeta. Carpeta tiene prioridad si ambos existen. -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/` +3. **`listResources(type)`**: escanear tanto `*.md` como directorios con el mainFileName. -### Fase 2: API endpoints +4. **`getResource(type, slug)`**: usar `resolveResource`, leer mainFile, listar sub-archivos si es carpeta. -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 +5. **`createResource(type, slug, content, format?)`**: parametro `format` opcional (default `'file'`). Si `'folder'`, crear directorio y escribir mainFileName. -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 +6. **`updateResource(type, slug, content)`**: auto-detectar formato, escribir en la ruta correcta. -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` +7. **`deleteResource(type, slug)`**: auto-detectar. Para carpeta usar `fs.rm(dirPath, { recursive: true })`. -### Fase 3: UI read-only +8. **Funciones nuevas para sub-archivos**: + - `listResourceFiles(type, slug)` - listar archivos auxiliares + - `getResourceFile(type, slug, relativePath)` - leer sub-archivo (Buffer) + - `addResourceFile(type, slug, relativePath, data: Buffer)` - escribir sub-archivo + - `deleteResourceFile(type, slug, relativePath)` - eliminar sub-archivo + - `convertToFolder(type, slug)` - convertir simple a carpeta -10. **`src/layouts/Base.astro`** - HTML shell con nav (logo + link "New Skill") +9. **Seguridad**: validar que `relativePath` no contenga `..`, solo permita rutas dentro de `FOLDER_SUBDIRS`. -11. **`src/components/SkillCard.astro`** - Card con nombre, descripcion truncada, badges de tools +### Fase 3: API de sub-archivos -12. **`src/pages/index.astro`** - Catalogo: llama `listSkills()`, renderiza grid de SkillCards. Empty state si no hay skills. +**`/api/resources/[type]/[slug]/files/index.ts`**: +- GET: lista sub-archivos (JSON) +- POST: subir archivo via multipart/form-data -13. **`src/pages/skills/[slug].astro`** - Vista detalle: renderiza markdown con `marked`, muestra metadata, botones Edit/Delete +**`/api/resources/[type]/[slug]/files/[...filePath].ts`**: +- GET: descargar sub-archivo +- PUT: subir/reemplazar sub-archivo +- DELETE: eliminar sub-archivo -### Fase 4: UI write +**Modificar `/api/resources/[type]/index.ts`**: +- POST acepta `format: 'file' | 'folder'` opcional -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 +### Fase 4: Pagina de detalle (`[type]/[slug].astro`) -15. **`src/pages/skills/new.astro`** - Monta SkillEditor en modo create +- Comprobar `resource.format` +- Para carpeta: renderizar seccion "Files" con `FolderTree.astro` +- El raw markdown (para curl) sigue devolviendo solo el mainFile -16. **`src/pages/skills/[slug]/edit.astro`** - Carga skill, monta SkillEditor en modo edit con datos +**`FolderTree.astro`**: componente que muestra arbol de archivos con links de descarga. -17. **`src/components/DeleteButton.vue`** (island `client:load`) - - Prop: `slug` - - Click -> confirm -> `fetch DELETE` -> redirect a `/` +### Fase 5: Editor UI -### Fase 5: Deployment +**`ResourceEditor.vue`**: +- Nuevos props: `initialFormat`, `files` +- Toggle formato al crear (Simple / Carpeta) +- Seccion FileManager al editar recurso tipo carpeta -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 +**`FileManager.vue`** (nuevo componente Vue): +- Lista archivos con boton eliminar +- Boton subir: selector de subdirectorio (scripts/references/assets) + file input +- Llamadas fetch a la API de files -19. **.dockerignore**: `node_modules`, `dist`, `.git`, `.env` +### Fase 6: Scripts de instalacion ---- +**`[type]/[slug]/i.ts` y `gi.ts`**: +- Para formato carpeta: generar script que crea estructura de directorios y descarga cada archivo +- URLs de sub-archivos: `${origin}/api/resources/${type}/${slug}/files/${relativePath}` +- `chmod +x` para archivos en `scripts/` + +**`sync.ts`**: +- Sync: detectar formato por recurso, generar descarga multi-archivo +- Push: iterar tanto `*.md` como directorios; subir main content + sub-archivos + +### Fase 7: Backward compat (`skills.ts`) + +Actualizar interfaz `Skill` con `format` y `files`. El wrapper sigue delegando a `resources.ts`. + +## Orden de implementacion + +1. Fase 1 (registry) - base +2. Fase 2 (resources.ts CRUD) - critico +3. Fase 3 (API sub-archivos) +4. Fase 4 (pagina detalle) +5. Fase 5 (editor UI) +6. Fase 6 (scripts instalacion) +7. Fase 7 (backward compat) ## 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) +1. Crear un skill simple (`test-simple`) via web - verificar que funciona como antes +2. Crear un skill carpeta (`test-folder`) via web - verificar que se crea directorio con SKILL.md +3. Subir archivos a scripts/ y references/ del skill carpeta via FileManager +4. Ver pagina de detalle - verificar arbol de archivos +5. Ejecutar script de instalacion (`curl .../skills/test-folder/i | bash`) - verificar descarga completa +6. Verificar que listado muestra ambos formatos correctamente +7. Editar ambos formatos - verificar que se actualizan +8. Eliminar ambos formatos - verificar limpieza diff --git a/data/agents/example-agent.md b/data/agents/example-agent.md new file mode 100644 index 0000000..e9f9a37 --- /dev/null +++ b/data/agents/example-agent.md @@ -0,0 +1,37 @@ +--- +name: Code Reviewer +description: An agent specialized in reviewing code for best practices, security issues, and performance. +author: Alejandro Martinez +author-email: amartinez2@certinia.com +tags: code-review, quality +tools: Read, Glob, Grep, WebSearch +model: claude-sonnet-4-5-20250929 +permissionMode: plan +maxTurns: 10 +skills: example-skill +--- + +# Code Reviewer Agent + +You are a code review specialist. When asked to review code, follow these steps: + +## Process + +1. **Read** the files or changes to be reviewed +2. **Analyze** for: + - Security vulnerabilities (OWASP top 10) + - Performance issues + - Code style and consistency + - Error handling gaps + - Test coverage +3. **Report** findings organized by severity (critical, warning, suggestion) + +## Output Format + +For each finding: +- **File**: path and line number +- **Severity**: Critical / Warning / Suggestion +- **Issue**: Clear description +- **Fix**: Recommended solution + +Always start with a summary of overall code quality before listing individual findings. diff --git a/data/claude-md/.gitkeep b/data/claude-md/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/data/output-styles/example-style.md b/data/output-styles/example-style.md new file mode 100644 index 0000000..01074af --- /dev/null +++ b/data/output-styles/example-style.md @@ -0,0 +1,19 @@ +--- +name: Concise Technical +description: Short, direct responses focused on code and technical accuracy. No fluff. +keep-coding-instructions: true +tags: concise, technical +author: Alejandro Martinez +author-email: amartinez2@certinia.com +--- + +# Concise Technical Style + +Follow these formatting rules for all responses: + +- **Be brief**: Get to the point immediately. No preambles like "Sure, I can help with that." +- **Code first**: When the answer is code, show the code before explaining it +- **No redundancy**: Don't repeat the question back. Don't summarize what you're about to do. +- **Bullet points**: Use bullets over paragraphs when listing multiple items +- **Technical precision**: Use exact terminology. Reference specific APIs, functions, and patterns by name. +- **Skip obvious context**: Don't explain basic concepts unless asked diff --git a/data/rules/example-rule.md b/data/rules/example-rule.md new file mode 100644 index 0000000..7070adc --- /dev/null +++ b/data/rules/example-rule.md @@ -0,0 +1,38 @@ +--- +name: TypeScript Strict +description: Enforce strict TypeScript patterns and conventions for all TS/TSX files. +paths: src/**/*.ts, src/**/*.tsx +tags: typescript, conventions +author: Alejandro Martinez +author-email: amartinez2@certinia.com +--- + +# TypeScript Strict Rules + +When working with TypeScript files, always follow these conventions: + +## Type Safety + +- Never use `any` — prefer `unknown` with type narrowing +- Always define return types for exported functions +- Use `readonly` for arrays and objects that shouldn't be mutated +- Prefer `interface` over `type` for object shapes (except unions/intersections) + +## Imports + +- Use named imports, not default imports +- Group imports: external libs, then internal modules, then relative paths +- No circular dependencies + +## Naming + +- `camelCase` for variables and functions +- `PascalCase` for types, interfaces, and classes +- `SCREAMING_SNAKE_CASE` for constants +- Prefix boolean variables with `is`, `has`, `should`, `can` + +## Error Handling + +- Never swallow errors silently (empty catch blocks) +- Use custom error classes for domain errors +- Always type error parameters in catch blocks diff --git a/data/skills/testeo/SKILL.md b/data/skills/testeo/SKILL.md new file mode 100644 index 0000000..ba46ca9 --- /dev/null +++ b/data/skills/testeo/SKILL.md @@ -0,0 +1,10 @@ +--- +name: testeo +description: Un test de skill +tags: test +allowed-tools: Bash, Read, Write, Edit +model: claude-opus-4-6 +agent: Plan +--- + +# Es solo un prueba diff --git a/data/skills/testeo/assets/temp.tpl b/data/skills/testeo/assets/temp.tpl new file mode 100644 index 0000000..8f91b47 --- /dev/null +++ b/data/skills/testeo/assets/temp.tpl @@ -0,0 +1 @@ +template \ No newline at end of file diff --git a/data/skills/testeo/references/readme.md b/data/skills/testeo/references/readme.md new file mode 100644 index 0000000..cd4ab18 --- /dev/null +++ b/data/skills/testeo/references/readme.md @@ -0,0 +1 @@ +referencia \ No newline at end of file diff --git a/data/skills/testeo/scripts/run.sh b/data/skills/testeo/scripts/run.sh new file mode 100644 index 0000000..f4d7fa5 --- /dev/null +++ b/data/skills/testeo/scripts/run.sh @@ -0,0 +1 @@ +bash \ No newline at end of file diff --git a/data/stats.json b/data/stats.json index 5755e1e..13c0d5f 100644 --- a/data/stats.json +++ b/data/stats.json @@ -1,7 +1,22 @@ { - "example-skill": { - "downloads": 0, + "skills:example-skill": { + "downloads": 1, "pushes": 1, "lastPushedAt": "2026-02-12T13:27:13.727Z" + }, + "agents:example-agent": { + "downloads": 1, + "pushes": 1, + "lastPushedAt": "2026-02-13T02:09:58.494Z" + }, + "skills:example-skill-2": { + "downloads": 1, + "pushes": 1, + "lastPushedAt": "2026-02-13T09:59:01.660Z" + }, + "skills:testeo": { + "downloads": 1, + "pushes": 1, + "lastPushedAt": "2026-02-13T12:18:38.376Z" } } \ No newline at end of file diff --git a/skills-here.svg b/skills-here.svg new file mode 100644 index 0000000..1726c13 --- /dev/null +++ b/skills-here.svg @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/src/components/HookConfigEditor.vue b/src/components/HookConfigEditor.vue new file mode 100644 index 0000000..feb1591 --- /dev/null +++ b/src/components/HookConfigEditor.vue @@ -0,0 +1,479 @@ +