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:
bash-logger/
.claude-plugin/plugin.json
hooks/hooks.json
hooks/register.tsxPaso 1: el manifiesto
Todo plugin de Claude Code tiene un manifiesto. Crea la carpeta y .claude-plugin/plugin.json:
mkdir -p bash-logger/.claude-plugin bash-logger/hooks{
"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:
{ "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:
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 deon,$ye. En ejecución no se importa nada.on('tool.call', { tool: 'Bash' }, manejador)engancha un manejador al eventotool.call. El segundo argumento es el filtro: solo le llegan las llamadas a la herramientaBash. 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,$.httpo$.state.ees la entrada del evento: entool.call, la herramienta y suinput.nextdeja 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 anextsalvo 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:
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:
claude plugin validate ./bash-logger
claude plugin test ./bash-loggervalidate 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:
claude --plugin-dir ./bash-loggerPí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:
zip -r bash-logger.zip bash-loggerLí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:
curl -X POST https://mods.gonzaloverdugo.com/api/v1/mods \
-H "Authorization: Bearer mdy_..." \
-F archive=@bash-logger.zip- Un agente: conecta Claude Code al MCP de Modyard y pídele que lo publique; lo tienes en usa Modyard desde un agente.
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:
/plugin marketplace add https://mods.gonzaloverdugo.com/publishers/ana/marketplace.jsonclaude plugin install bash-logger@ana --scope userPara 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.