Tutoriales

Escribe tu primer mod

En este tutorial construyes bash-logger, un mod que enseña en la línea de estado cada comando Bash que lanza Claude. Tres ficheros pequeños, unos cuantos comandos para comprobarlo y listo para publicar.

4 min de lectura

Qué necesitas

  • Claude Code 2.1.287 o posterior, la primera versión con hooks de función. Lo compruebas con claude --version.
  • Una terminal y un editor. No hay paso de build ni package.json: Claude Code carga el módulo TypeScript por sí mismo.

Al terminar tendrás esta carpeta:

text
bash-logger/
  .claude-plugin/plugin.json
  hooks/hooks.json
  hooks/register.tsx

Paso 1: el manifiesto

Todo plugin de Claude Code tiene un manifiesto. Crea la carpeta y .claude-plugin/plugin.json:

bash
mkdir -p bash-logger/.claude-plugin bash-logger/hooks
json
{
  "name": "bash-logger",
  "version": "0.1.0",
  "description": "Shows each Bash command Claude runs in the status line"
}

El name va en minúsculas, con números y guiones si hacen falta, y la version con la forma x.y.z. Las dos reglas cuentan después: Modyard rechaza cualquier otra cosa al publicar.

Paso 2: declara los módulos

hooks/hooks.json le dice a Claude Code qué módulos cargar. Para hooks de función se enumeran en modules, con rutas relativas a la carpeta hooks:

json
{ "modules": ["./register.tsx"] }

Un mod puede tener varios módulos. De momento, con uno basta.

Paso 3: la función register

hooks/register.tsx exporta una función llamada register. Claude Code la llama una vez, al cargar el plugin, y le pasa on, con el que enganchas manejadores a los eventos, y options, las opciones del plugin:

tsx
import type { Register } from 'claude-code'

export const register: Register = (on) => {
  on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
    $.ui.status(`Bash: ${e.command.slice(0, 40)}`)
    return next(e)
  })
}

Vamos por partes:

  • import type { Register } from 'claude-code' trae solo el tipo, para que tu editor conozca la forma de on, $ y e. En ejecución no se importa nada.
  • on('tool.call', { tool: 'Bash' }, manejador) engancha un manejador al evento tool.call. El segundo argumento es el filtro: solo le llegan las llamadas a la herramienta Bash. Es opcional; sin él, el manejador ve todas las llamadas a herramientas.
  • El manejador recibe tres cosas. $ es la interfaz del motor, un conjunto de sustantivos como $.ui, $.fs, $.http o $.state. e es la entrada del evento: en tool.call, la herramienta y su input. next deja pasar el evento.
  • $.ui.status(...) escribe en la línea de estado.
  • return next(e) ejecuta los plugins que hay por debajo y después el comportamiento propio de Claude Code, que aquí es lanzar el comando. Llama siempre a next salvo que quieras responder tú.

Paso 4: responder por tu cuenta

Si vuelves sin llamar a next, la cadena se corta. En tool.call, devolver { deny: 'motivo' } bloquea la llamada y le explica a Claude por qué. Llamar a next con el evento cambiado lo reescribe por el camino. Prueba las dos cosas en el mismo manejador:

tsx
import type { Register } from 'claude-code'

export const register: Register = (on) => {
  on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
    const command = e.command
    $.ui.status(`Bash: ${command.slice(0, 40)}`)
    if (command.includes('rm -rf /')) return { deny: 'bash-logger refuses rm -rf /' }
    if (command.startsWith('ls')) return next({ ...e, command: `${command} -la` })
    return next(e)
  })
}

Los demás eventos funcionan igual: prompt.submit cuando envías un prompt, session.start cuando se abre una sesión, ui.render cuando se pinta la interfaz, command.run para los comandos con barra. Por ahora deja bash-logger con su única tarea; la versión sin estas ramas extra es la que publicamos más abajo.

Paso 5: valida y prueba

Dos comandos revisan el plugin antes de que nadie más lo vea:

bash
claude plugin validate ./bash-logger
claude plugin test ./bash-logger

validate comprueba el manifiesto, el fichero de hooks y que existan todos los módulos. test carga los módulos y los pasa por las comprobaciones de Claude Code. Arregla lo que te digan antes de seguir.

Paso 6: pruébalo en una sesión de verdad

Carga la carpeta en una sola sesión, sin instalarla:

bash
claude --plugin-dir ./bash-logger

Pídele a Claude que liste los ficheros de la carpeta. Cuando lance ls, en la línea de estado verás Bash: ls. Cambia register.tsx, abre otra sesión y repite hasta que te guste.

Paso 7: mira qué toca

Antes de publicar, piensa en lo que verá quien lea tu mod. Este módulo usa un hook de tool.call y $.ui, así que Modyard enumerará tool calls y display. Si hubieras añadido una petición con $.http, saldría también network. En qué puede tocar un mod tienes cada categoría. Una lista corta y coherente con la descripción es lo que da confianza.

Paso 8: comprímelo

Haz un zip de la carpeta. Valen los ficheros en la raíz del zip o dentro de una carpeta que los envuelva:

bash
zip -r bash-logger.zip bash-logger

Límites que conviene recordar: 2 MB comprimido, 10 MB descomprimido y 500 ficheros.

Paso 9: publícalo

Entra con Google; la primera vez se crea tu publicador, por ejemplo ana. Después elige una forma:

  • Web: abre publicar y suelta bash-logger.zip. Revisa la lista de lo que toca y confirma.
  • curl: crea una clave de lectura y escritura en ajustes y ejecuta:
bash
curl -X POST https://mods.gonzaloverdugo.com/api/v1/mods \
  -H "Authorization: Bearer mdy_..." \
  -F archive=@bash-logger.zip

Los nombres de mod son únicos en todo Modyard; si bash-logger ya está cogido, cambia el name de plugin.json y vuelve a comprimir. En publica un mod tienes todas las reglas y lo que pasa después.

Paso 10: instálalo como cualquiera

Tu publicador ya es un marketplace. Instala tu propio mod igual que lo harán tus usuarios:

text
/plugin marketplace add https://mods.gonzaloverdugo.com/publishers/ana/marketplace.json
bash
claude plugin install bash-logger@ana --scope user

Para sacar un cambio, sube la version a 0.1.1, comprime y publica otra vez. Las versiones no se pueden modificar, así que cada arreglo es una versión nueva, y a tus usuarios les llega con /plugin marketplace update ana.

Tu primer mod, instalado con dos comandos

Entra con Google y tienes un publicador con su propio marketplace. Sube un zip, lee lo que Modyard encontró dentro y comparte el comando de instalación.