Files
skills-here-run-place/PLAN-HOOKS.md
Alejandro Martinez b86c9f3e3a Add hooks and CLAUDE.md resource types with install/uninstall scripts
Introduces two new resource types (hooks, claude-md) with full CRUD,
   visual hook config editor, section-delimited CLAUDE.md installs,
   uninstall endpoints, and shell injection hardening in sync scripts.
2026-02-16 11:51:33 +01:00

369 lines
13 KiB
Markdown

# 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/<slug>/`:
```
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/<slug>/`.
## 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/<slug>/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/<slug>/scripts/`.
**Modificaciones en `i.ts` y `gi.ts`:**
- Detectar `type === 'hooks'`
- En vez del flujo generico de copiar a `.claude/<claudeDir>/<slug>`, usar el flujo especial:
1. Descargar hooks.json y scripts a `.claude/hooks/<slug>/`
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/<slug>/`
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/<type>/<slug>.md` o carpeta | `.claude/hooks/<slug>/` + 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