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

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 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)

---
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 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:

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.tscreateResource:

  • 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:

// 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:
{
  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):

#!/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