# 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