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.
13 KiB
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
// .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 herramientaCLAUDE_TOOL_INPUT— JSON con los parametros de la herramientaCLAUDE_FILE_PATHS— rutas de archivos separadas por newlineCLAUDE_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)
---
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
{
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/auto-lint/scripts/lint.sh $CLAUDE_FILE_PATHS"
}
]
}
]
}
Nota: Los paths en
commandse 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:
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: forzarformat: 'folder'sin mostrar toggle. - Al crear un hook, generar automaticamente un
hooks.jsonvacio ({}) 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, escribirhooks.jsoncon{}.
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:
// 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.jsondel recurso - Si existe, renderizar
HookConfigPreviewentre 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:
- Selector de evento — dropdown con los 7 eventos
- 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
- Tipo:
- Campo
- Botones — Agregar evento, agregar matcher, agregar handler, eliminar
Integracion en ResourceEditor.vue:
- Detectar tipo
hooks - Cargar
hooks.jsonexistente via API - Renderizar
HookConfigEditordebajo del editor de markdown - Al guardar, enviar PUT al endpoint de files para actualizar
hooks.json
Alternativa simplificada (fase inicial):
- Usar el campo
jsonexistente del registry para editar hooks.json como JSON crudo - Agregar al registry.ts un campo:
{
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:
- Scripts se copian a
.claude/hooks/<slug>/scripts/ - hooks.json se lee y su contenido se mergea en
.claude/settings.local.jsonbajo la clavehooks - Los paths en commands se reescriben para apuntar a la ubicacion de scripts
Script de instalacion (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:- Descargar hooks.json y scripts a
.claude/hooks/<slug>/ - Reescribir paths en hooks.json
- Mergear en settings.local.json con jq
- Descargar hooks.json y scripts a
- 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:
- Lee settings.local.json
- Elimina las entradas de hooks que coincidan con el slug
- Elimina
.claude/hooks/<slug>/ - 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) yhooks(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
- Fase 1 — Registry: agregar tipo hooks (5 min)
- Fase 2 — Formato carpeta obligatorio + hooks.json inicial (15 min)
- Fase 3 — API lectura hooks.json (10 min)
- Fase 4 — Preview en pagina de detalle (30 min)
- Fase 5 — Editor (JSON crudo primero, visual despues) (20 min)
- Fase 6 — Scripts de instalacion especiales (45 min) — mas complejo
- Fase 7 — Script de desinstalacion (20 min)
Verificacion
- Crear un hook via web — verificar que se crea como carpeta con HOOK.md + hooks.json vacio
- Editar hooks.json con configuracion real (PostToolUse + command)
- Subir script a scripts/ via FileManager
- Ver pagina de detalle — verificar preview de configuracion
- 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
- Scripts descargados a
- Verificar que el hook funciona ejecutando Claude Code en un proyecto con el hook instalado
- Ejecutar script de desinstalacion — verificar limpieza de settings y archivos
- Crear hook sin scripts (solo command inline) — verificar instalacion simplificada