Crear un mod
Pídele a Claude que escriba un mod de Claude Code a partir de una descripción, o escribe tú mismo uno que cuente las llamadas a herramientas y agregue un comando. Aprende el ciclo de recarga y validación.
Un mod es un plugin de Claude Code con un archivo de entrada, llamado módulo de hooks: un archivo JavaScript o TypeScript cuyas funciones Claude Code llama cuando ocurren eventos. Para crear uno:
- Pídele a Claude que lo escriba: describe lo que quieres en una sesión de Claude Code
- Escríbelo tú mismo: sigue el tutorial para aprender cómo funciona el código de un mod. No necesitas Node.js, un bundler ni un paso de compilación, porque Claude Code carga los archivos
.jsy.tsdirectamente.
Si aún no has decidido si un mod es la herramienta adecuada, lee primero la comparación en la descripción general.
Los mods requieren Claude Code v2.1.287 o posterior. En tu shell, ejecuta claude --version para comprobarlo. Para ver si los mods pueden cargarse en tu caso, consulta Comprobar si los mods pueden cargarse.
Pídele un mod a Claude
Describe el mod que quieres en una sesión interactiva de Claude Code y Claude lo escribe. Claude trabaja a partir de un skill integrado llamado plugin-authoring, que le indica dónde escribir el mod, qué eventos y métodos tiene tu versión y cómo se carga el mod. Claude puede cargar el skill cuando pides un mod, o puedes cargarlo tú mismo ejecutando /plugin-authoring en el prompt de Claude Code.
El mod se ejecuta una vez que lo apruebas, excepto en sesiones donde un mod que escribe Claude no puede cargarse.
Describe el mod
Pide el mod con tus propias palabras, por ejemplo make a mod that shows the current git branch above the prompt. Claude escribe el mod en un directorio propio dentro de la carpeta de mods de la sesión, que es ~/.claude/dev-mods/ seguido del ID de la sesión. La ruta completa de un mod se ve así: ~/.claude/dev-mods/3f2a9c1e-5b7d-4e8a-9c21-6d0f4b8a7e13/git-branch/.
En los modos de permisos default y acceptEdits, Claude Code pregunta antes de que Claude cree cada uno de los archivos del mod, porque ~/.claude es una ruta protegida. Aprueba cada archivo a medida que aparezca.
Aprueba el mod
Cuando Claude guarda el primer archivo, Claude Code pregunta si quieres habilitar la recarga en caliente para la sesión. La recarga en caliente ejecuta los mods que Claude escribe en esta sesión y aplica cada cambio posterior.
Elige una de estas respuestas:
- Enable for this session: los mods de la carpeta de mods de la sesión se cargan cuando termina el turno y se recargan al final de cada turno que los modifique. Tu respuesta dura toda la sesión, incluso después de reanudarla.
- Not now: por ahora no se carga nada. Los archivos permanecen donde Claude los escribió y los mods se cargan la próxima vez que se inicie esa sesión. Para evitar que un mod se cargue alguna vez, elimina su directorio.
Comprueba que el mod se cargó
Ejecuta /plugin en el prompt de Claude Code y presiona Tab hasta que quede seleccionada la pestaña Installed. Ahí aparece el mod, y puedes desactivarlo desde allí.
Prueba el mod
Usa lo que pediste. Para el prompt de ejemplo, el nombre de la rama actual aparece encima del cuadro del prompt. Si el mod no hace lo que querías, dile a Claude qué cambiar. El mod se recarga al final de cada turno que modifique sus archivos, así que puedes probar el cambio en cuanto Claude termine.
Usa el mod en otras sesiones
Un mod que escribió Claude solo se carga en la sesión que lo creó, y Claude Code elimina la carpeta de mods de esa sesión una vez que supera la antigüedad de cleanupPeriodDays. Para conservar el mod, copia su directorio fuera de la carpeta de mods a un lugar propio, como ~/mods/git-branch. Luego elige cómo cargarlo:
- En una sesión que inicies: en tu shell, ejecuta
claude --plugin-dir ~/mods/git-branch - Para otras personas: agrégalo a un marketplace para que puedan instalarlo
Sesiones donde un mod que escribe Claude no puede cargarse
Un mod que escribe Claude solo se carga después de que lo apruebas, en un espacio de trabajo de confianza donde se permite ejecutar mods. En estas sesiones no se carga:
- No hay nadie para aprobarlo: la sesión no puede mostrarte una solicitud, como en una ejecución de
claude -po en el mododontAsk - El espacio de trabajo no es de confianza: no has aceptado la solicitud de confianza para el directorio
- Los mods están deshabilitados: iniciaste con
--safe-modeo--bare, configurastedisableAllHooks, o la configuración administrada de tu organización lo bloquea
Escribe un mod tú mismo
En este tutorial creas un mod llamado first-mod que cuenta las llamadas a herramientas que hace Claude, muestra el conteo junto al spinner mientras Claude trabaja y agrega un comando /tally que lo imprime. Luego lees las declaraciones de tipos que Claude Code escribe junto a tu mod y ejecutas claude plugin validate. Juntos te muestran los eventos y métodos que ofrece tu versión y lo que Claude Code lee de tu código.
Esta grabación muestra el mod terminado. El spinner cuenta las llamadas a herramientas, /tally imprime el conteo y una edición al código surte efecto mientras la sesión se ejecuta:
Escribes tres archivos:
first-mod/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.js
plugin.json: el manifiesto del pluginhooks.json: apunta a tu archivo de códigoregister.js: tu código, llamado módulo de hooks
Crea el directorio del plugin
Crea los dos directorios que contienen los archivos:
mkdir -p first-mod/.claude-plugin first-mod/hooks
New-Item -ItemType Directory -Force first-mod\.claude-plugin, first-mod\hooks
Escribe el manifiesto
Un mod es un plugin, y un mod necesita un manifiesto. El manifiesto de este mod no tiene campos especiales. Guarda esto como first-mod/.claude-plugin/plugin.json:
{
"name": "first-mod",
"version": "0.1.0",
"description": "Counts Claude's tool calls, shows the count beside the spinner, and adds a /tally command",
"author": { "name": "Your Name" }
}
Indícale a Claude Code dónde está tu código
Cuando Claude Code carga un plugin, lee el archivo hooks/hooks.json del plugin. La clave modules de ese archivo indica la ruta a tu código, y tenerla es lo que convierte al plugin en un mod. Enumera una ruta, relativa a hooks.json. Aquí apunta a register.js, que escribes en el siguiente paso.
Guarda esto como first-mod/hooks/hooks.json:
{
"description": "The first-mod hooks module",
"modules": ["./register.js"]
}
Escribe el código
Este archivo es el código del mod, llamado módulo de hooks. Cuando el mod se carga, Claude Code llama a la función register que exporta el archivo y le pasa una función llamada on. Cada llamada a on registra un manejador de eventos, llamado hook, para el evento que nombra.
Guarda esto como first-mod/hooks/register.js:
// The count, shared by the hooks below
let calls = 0
// Claude Code calls this once when the mod loads
export function register(on) {
// Runs when the session starts, before your first prompt
on('session.start', async ($, e, next) => {
// Add the /tally command
await $.command.register({
name: 'tally',
description: 'Show how many tool calls Claude has made',
})
// Let the session start as usual
return next(e)
})
// Runs each time Claude is about to use a tool
on('tool.call', async ($, e, next) => {
calls += 1
// Ask Claude Code to draw the interface again, so the new count shows
$.ui.invalidate('ui.render')
// Let the tool run as usual
return next(e)
})
// Runs when you type /tally, and only then, because of the matcher
on('command.run', { command: 'tally' }, async () => {
// The text to print in the transcript
return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' }
})
// Runs each time Claude Code draws the spinner
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
// Keep Claude Code's spinner, with the count added after its word
return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
})
}
El archivo mantiene un conteo en calls y registra cuatro hooks:
session.startse ejecuta cuando empieza la sesión, antes de tu primer prompt, y de nuevo cada vez que el mod se recarga. Agrega el comando/tallya Claude Code.tool.callse ejecuta cada vez que Claude está a punto de usar una herramienta. Suma uno acallsy le pide a Claude Code que vuelva a dibujar la interfaz.command.runse ejecuta cuando escribes/tally. Devuelve el texto que se imprimirá.ui.renderse ejecuta cada vez que Claude Code dibuja el spinner. Agrega el conteo después de la palabra del spinner.
Cómo funciona el mod de ejemplo explica los tres argumentos que recibe cada hook y lo que devuelve cada uno.
Carga el mod
Inicia Claude Code con el flag --plugin-dir, que carga un directorio de plugin durante una sesión sin instalarlo:
claude --plugin-dir ./first-mod
Prueba el mod
Pídele a Claude que haga algo que requiera algunas llamadas a herramientas, como list the files here and read the README. Mientras Claude trabaja, la palabra del spinner va seguida de un conteo que aumenta, como en Thinking · tool calls: 2…. Cuando Claude termine, escribe /tally y presiona Enter. La transcripción muestra first-mod: Claude has made 2 tool calls since this mod loaded, con tu propio conteo. Claude Code antepone el nombre del plugin al texto del comando.
Para comprobar el comando sin una sesión interactiva, ejecútalo en modo no interactivo:
claude -p "/tally" --plugin-dir ./first-mod
first-mod: Claude has made 0 tool calls since this mod loaded
Si /tally no está en la lista de comandos, el módulo no se cargó. Consulta Averigua por qué un mod no hace nada.
Cambia el código mientras la sesión se ejecuta
Deja la sesión abierta. En register.js, cambia ' · tool calls: ' por ' · tools used: ' en el hook ui.render y guarda. La línea resaltada es la que cambia:
// Runs each time Claude Code draws the spinner
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
// Keep Claude Code's spinner, with the count added after its word
return next({ ...e, props: { ...e.props, suffix: ' · tools used: ' + calls + '…' } })
})
Una línea en la transcripción indica que first-mod se recargó y enumera sus hooks, y el siguiente spinner usa el nuevo texto, como en Thinking · tools used: 1….
Cómo funciona el mod de ejemplo
Cada función que pasas a on es un hook, que es un manejador de eventos. Claude Code pasa a cada hook los mismos tres argumentos:
- La API de mods, llamada
$: todos los métodos que un mod puede llamar para llegar fuera de sí mismo, organizados en espacios de nombres como$.uiy$.command - El evento, llamado
e: la entrada del evento como datos simples, como el nombre y los argumentos de una llamada a herramienta - El siguiente manejador, llamado
next: una función que pasa el evento a los demás mods y luego al comportamiento propio de Claude Code, y devuelve el resultado
Los hooks de first-mod manejan sus eventos de estas formas:
- Observar: el hook
session.startregistra el comando, y el hooktool.callcuenta la llamada y solicita que se vuelva a dibujar. Ambos devuelvennext(e), así que la sesión empieza y la herramienta se ejecuta como de costumbre. - Responder: el hook
command.rundevuelve su propio resultado y nunca llama anext. El segundo argumento deon,{ command: 'tally' }, es un filtro, llamado matcher, por lo que el hook se ejecuta solo para/tally. - Reescribir: el hook
ui.renderllama anextcon una copia deecuyosuffixcontiene el conteo, así que Claude Code dibuja su spinner habitual con tu texto después de la palabra
Claude Code vigila un directorio cargado con --plugin-dir y recarga en caliente el módulo de hooks cuando cambia un archivo en él. Cada recarga ejecuta register de nuevo, así que calls se restablece a 0 y /tally vuelve a empezar a contar. Para conservar un valor entre recargas, consulta Conservar el estado.
Seguir trabajando en un mod
Una vez que un mod se carga, puedes pedirle a Claude que lo cambie, comprobar tu código con las definiciones de tipos de tu versión, listar los eventos y las llamadas que Claude Code encuentra en él, y probarlo.
Cambiar un mod con Claude
Para cambiar un mod que ya tienes, inicia la sesión con --plugin-dir apuntando al directorio del mod, de modo que lo que Claude escriba se cargue en la misma sesión:
claude --plugin-dir ./first-mod
Luego pide el cambio, por ejemplo add a /tally-reset command to this mod that sets the tally back to zero. Claude edita el módulo de hooks, ejecuta claude plugin validate y corrige lo que este reporta. Un directorio que cargas con --plugin-dir es una ruta protegida, así que en los modos default y acceptEdits se te pide que apruebes cada una de las ediciones de Claude al mod. La tabla de rutas protegidas indica el resultado para los demás modos de permisos.
Los archivos que Claude guarda durante su turno se recargan cuando el turno termina, así que puedes probar /tally-reset en cuanto Claude termine.
Obtener las definiciones de tipos para tu versión
Cada vez que Claude Code carga o recarga un mod desde un directorio que pasas a --plugin-dir, o un mod que Claude escribió por ti, escribe archivos de declaración de TypeScript, que terminan en .d.ts, en .claude-plugin/types/ dentro del directorio del mod. Describen exactamente los eventos, los métodos de la API de mods y los elementos de la versión de Claude Code que estás ejecutando, para que tu editor pueda autocompletar y verificar los tipos de tus hooks. Para consultar las declaraciones en línea, lee mods/types/claude-code.d.ts en el repositorio de Claude Code, cuya primera línea indica la versión que lo escribió. El directorio contiene estos archivos:
| Ruta | Lo que declara |
|---|---|
claude-code/index.d.ts |
Cada evento con su entrada y su resultado, cada espacio de nombres y método de la API de mods, y los elementos que cada superficie puede dibujar |
claude-code-tools/index.d.ts |
Las entradas y los resultados de las herramientas integradas, de modo que comprobar e.tool === 'Bash' acota el tipo de e |
claude-code-mcp/index.d.ts |
Las entradas de las herramientas MCP que estaban conectadas la última vez que guardaste un archivo en el mod |
index.d.ts en un directorio con el nombre de un plugin |
Lo que ese plugin agrega a la API de mods. Hay un directorio por cada plugin que tu plugin.json lista en dependencies. |
tsconfig.json |
Opciones del compilador adecuadas para un módulo de hooks |
Si tu mod no tiene un tsconfig.json propio, Claude Code agrega uno en la raíz del mod que extiende el generado, para que tu editor y tsc -p ./first-mod verifiquen los tipos del mod sin más configuración.
Los eventos y los métodos pueden cambiar entre versiones, así que confía en estos archivos antes que en cualquier página, incluida esta, cuando no coincidan.
claude-code/index.d.ts es la referencia más completa para tu compilación, con un comentario y un ejemplo para cada método de la API de mods. Para buscar algo, busca su nombre en el archivo, como 'tool.call'.
Comprobar lo que Claude Code lee de tu mod
Para ver tu mod como lo ve Claude Code, sin ejecutar tu código ni iniciar una sesión, usa claude plugin validate. Comprueba el manifiesto y ejecuta sobre el código fuente del módulo de hooks el mismo análisis estático que Claude Code ejecuta al cargar un mod. En tu shell, ejecútalo sobre el directorio del mod:
claude plugin validate ./first-mod
Para first-mod, la salida incluye estas líneas.
❯ ./register.js hooks: session.start, tool.call, command.run{command=tally}, ui.render{component=Spinner}
❯ ./register.js calls: $.command.register, $.ui.invalidate
✔ Validation passed
La línea hooks: lista los eventos a los que se engancha tu módulo, cada uno con su filtro entre llaves. La línea calls: lista cada método de la API de mods que llama. Un módulo que lee o establece variables de entorno también obtiene las líneas env reads: y env writes:, y uno que usa $.state obtiene state reads: y state writes:.
Si en la primera línea falta un evento que querías manejar, Claude Code tampoco llamará a ese hook. La causa habitual es un nombre de evento mal escrito, que el comando reporta como un error como "tool.calls" is not an event.
Sigue estas reglas para que el análisis estático pueda encontrar cada hook y cada llamada:
- Escribe cada llamada a la API de mods completa:
$, el espacio de nombres y luego el método, como en$.store.get('notes'). Puedes pasar$a una función declarada en el nivel superior del mismo archivo, y para una función tuya llamadaloadNotes, la líneacalls:muestra entonces$.store.get (via loadNotes). Pasar$a un método, a una función definida dentro del hook o a una función que importas de otro de tus archivos hace que la validación falle. Las funcionesreadyupdateque usa$.stateson las importaciones que pueden recibirlo. No asignes$ni uno de sus espacios de nombres a una variable, no lo desestructures ni lo indexes con un nombre calculado.const ui = $.uifalla con$.ui is used as a value. - Escribe el nombre del evento en cada llamada a
oncomo un literal de cadena, como'tool.call'. Una variable, o un bucle sobre una lista de nombres, falla conthe event name passed to on() is not a string literal. - Dentro de
register, no declares una segunda variable ni un parámetro llamadoon. La validación falla con"on" is declared again (shadowed). - Importa solo desde archivos dentro del directorio del plugin, mediante ruta relativa. La única importación sin ruta permitida es
claude-code, para tipos y algunas utilidades. - Usa declaraciones
importal principio del archivo, como enimport { name } from './file.js'. Unimport()dinámico falla cona dynamic import(); a hooks module imports its own files with an import declaration. - Escribe cada archivo como un módulo ES, con
importy norequire. La referencia lista las extensiones de archivo que carga Claude Code.
Probar el mod
Puedes escribir pruebas automatizadas para un mod y ejecutarlas desde tu shell con claude plugin test, sin sesión, sin inicio de sesión y sin red. Una prueba dispara los eventos que manejan tus hooks y comprueba lo que hicieron los hooks.
Esta prueba dispara dos llamadas a herramientas, ejecuta /tally y comprueba que la respuesta cuenta ambas. Guárdala como first-mod/tests/first-mod.test.ts:
import { expect, test } from 'claude-code/testing'
test('/tally reports the tool calls the mod has seen', async ($, on) => {
// Answer each tool call in Claude Code's place, so no tool runs
on('tool.call', () => ({ result: 'ok' }))
// Fire two tool calls, which the mod's tool.call hook counts
await $.tool.call({ tool: 'Bash', command: 'ls' })
await $.tool.call({ tool: 'Read', file_path: 'README.md' })
// Run /tally and check the text its hook returns
const answer = await $.command.run({ command: 'tally', args: '' })
expect(answer.text).toBe('Claude has made 2 tool calls since this mod loaded')
})
En tu shell, ejecuta las pruebas desde el directorio first-mod:
claude plugin test
La salida nombra cada prueba e indica si pasó, con tiempos que varían de una ejecución a otra:
tests/first-mod.test.ts:
(pass) /tally reports the tool calls the mod has seen [22.87ms]
1 pass
0 fail
Ran 1 test across 1 file. [0.19s]
Probar un mod explica cómo simular una llamada al modelo o el almacén, y cómo probar temporizadores y dibujos.
Comparte tu mod
Un mod es un plugin, así que le asignas la versión en el manifiesto y las personas lo instalan y actualizan con los comandos de /plugin. La forma de compartirlo depende de a quién va dirigido:
- Unas pocas personas: envíales el directorio del plugin o un
.zipde él. Consulta Compartir un plugin sin un marketplace - Tu equipo: inclúyelo en tu propio marketplace, como un repositorio privado con un directorio para cada plugin. Para agregar ese marketplace para todas las personas que trabajan en un repositorio, regístralo en la configuración del repositorio
- Toda tu organización: un administrador puede instalar los mods de tu organización mediante la configuración administrada
- Cualquier persona: haz público el repositorio de tu marketplace o envía el plugin al directorio de Anthropic
Antes de hacerlo, revisa el name del plugin: claude plugin validate rechaza un nombre que parece uno de los propios de Anthropic, como uno que empieza con claude-. Los eventos y métodos pueden cambiar entre versiones, así que tu README es el lugar para indicar con qué versión de Claude Code hiciste las pruebas.
Sigue desarrollando contra el directorio con --plugin-dir, no contra una copia instalada. Claude Code almacena en caché un plugin instalado por versión, así que tus ediciones no llegan a la copia instalada hasta que incrementes la versión y lo instales de nuevo.
Próximos pasos
- Dibuja en la interfaz: abre un panel, dibuja encima del prompt y agrega botones y campos de texto
- Reacciona a eventos: intercepta llamadas a herramientas, prompts y turnos
- Usa la API de mods: agrega comandos y herramientas, llama a un modelo y ejecuta trabajo con un temporizador
- Prueba un mod: simula lo que respondería Claude Code y prueba temporizadores y dibujos
- Soluciona problemas de un mod: los motivos por los que un mod no hace nada y el registro de depuración
- Lee el código fuente de los mods integrados: plugins completos, cada uno con su módulo de hooks y sus pruebas