Crear un mod
Haga que Claude escriba un mod de Claude Code a partir de una descripción, o escriba uno usted mismo que cuente llamadas de herramientas y agregue un comando. Aprenda el ciclo de recarga y validación.
Un mod es un plugin de Claude Code con un archivo de entrada, llamado el módulo de hooks: un archivo JavaScript o TypeScript cuyas funciones Claude Code llama cuando ocurren eventos. Hay dos formas de hacer uno:
- Pida a Claude que lo escriba: describa lo que desea en una sesión de Claude Code
- Escríbalo usted mismo: siga el tutorial para aprender cómo funciona el código de un mod. No necesita Node.js, un empaquetador o un paso de compilación, porque Claude Code carga archivos
.jsy.tsdirectamente.
Si aún no ha decidido si un mod es la herramienta adecuada, lea primero la comparación en la descripción general.
Los mods requieren Claude Code v2.1.287 o posterior. En su shell, ejecute claude --version para verificar. Para ver si los mods pueden cargarse para usted, consulte Verificar si los mods pueden cargarse.
Pida a Claude un mod
Describa el mod que desea en una sesión interactiva de Claude Code, y Claude lo escribe. Claude trabaja a partir de una skill integrada llamada plugin-authoring, que le dice dónde escribir el mod, qué eventos y métodos tiene su versión, y cómo se carga el mod. Claude puede cargar la skill cuando le pide un mod, o puede cargarla usted mismo ejecutando /plugin-authoring en el símbolo del sistema de Claude Code.
El mod se ejecuta una vez que lo aprueba, excepto en sesiones donde un mod que Claude escribe no puede cargarse.
Describa el mod
Pida el mod con sus propias palabras, por ejemplo make a mod that shows the current git branch above the prompt. Claude escribe el mod en un directorio propio en 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 como ~/.claude/dev-mods/3f2a9c1e-5b7d-4e8a-9c21-6d0f4b8a7e13/git-branch/.
En los modos de permiso default y acceptEdits, Claude Code pregunta antes de que Claude cree cada uno de los archivos del mod, porque ~/.claude es una ruta protegida. Apruebe cada archivo cuando aparezca.
Apruebe el mod
Cuando Claude guarda el primer archivo, Claude Code pregunta si desea habilitar la recarga en caliente para la sesión. La recarga en caliente ejecuta los mods que Claude escribe en esta sesión y recoge cada cambio posterior.
Elija una de estas respuestas:
- Habilitar para esta sesión: los mods en la carpeta de mods de la sesión se cargan cuando termina el turno, y se recargan al final de cada turno que los cambia. Su respuesta dura para la sesión, incluso después de reanudarla.
- Ahora no: nada se carga por ahora. Los archivos permanecen donde Claude los escribió, y los mods se cargan la próxima vez que esa sesión comienza. Para evitar que un mod se cargue nunca, elimine su directorio.
Verifique que el mod se cargó
Ejecute /plugin en el símbolo del sistema de Claude Code y presione Tab hasta que se seleccione la pestaña Installed. Enumera el mod, y puede desactivarlo allí.
Pruebe el mod
Use lo que pidió. Para el ejemplo de símbolo del sistema, el nombre de la rama actual aparece encima del cuadro de símbolo del sistema. Si el mod no hace lo que deseaba, dígale a Claude qué cambiar. El mod se recarga al final de cada turno que cambia sus archivos, por lo que puede probar el cambio tan pronto como Claude termine.
Use el mod en otras sesiones
Un mod que Claude escribió se carga solo en la sesión que lo creó, y Claude Code elimina la carpeta de mods de esa sesión una vez que es más antigua que cleanupPeriodDays. Para mantener el mod, copie su directorio fuera de la carpeta de mods a un lugar de su elección, como ~/mods/git-branch. Luego elija cómo cargarlo:
- En una sesión que inicia: en su shell, ejecute
claude --plugin-dir ~/mods/git-branch - Para otras personas: agréguelo a un marketplace para que puedan instalarlo
Sesiones donde un mod que Claude escribe no puede cargarse
Un mod que Claude escribe se carga solo después de que lo aprueba, en un espacio de trabajo de confianza donde se permite que los mods se ejecuten. En estas sesiones no se carga:
- Nadie está allí para aprobar: la sesión no puede mostrarle un símbolo del sistema, como en una ejecución
claude -po mododontAsk - El espacio de trabajo no es de confianza: no ha aceptado el símbolo del sistema de confianza para el directorio
- Los mods están detenidos: comenzó con
--safe-modeo--bare, estableciódisableAllHooks, o la configuración administrada de su organización lo bloquea
Escriba un mod usted mismo
En este tutorial construye un mod llamado first-mod que cuenta las llamadas de herramientas que Claude hace, muestra el recuento junto al spinner mientras Claude trabaja, y agrega un comando /tally que lo imprime. Luego lee las declaraciones de tipo que Claude Code escribe junto a su mod y ejecuta claude plugin validate. Juntos muestran los eventos y métodos que su versión ofrece y qué Claude Code lee de su código.
Esta grabación muestra el mod terminado. El spinner cuenta llamadas de herramientas, /tally imprime el recuento, y una edición del código toma efecto mientras la sesión se ejecuta:
Escribe tres archivos:
first-mod/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.js
plugin.json: el manifiesto del pluginhooks.json: apunta a su archivo de códigoregister.js: su código, llamado el módulo de hooks
Cree el directorio del plugin
Cree 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
Escriba el manifiesto
Un mod es un plugin, y un mod necesita un manifiesto. El manifiesto de este mod no tiene campos especiales. Guarde 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" }
}
Dígale a Claude Code dónde está su código
Cuando Claude Code carga un plugin, lee el hooks/hooks.json del plugin. La clave modules en ese archivo da la ruta a su código, y tenerla es lo que hace que el plugin sea un mod. Enumere una ruta, relativa a hooks.json. Aquí apunta a register.js, que escribe en el siguiente paso.
Guarde esto como first-mod/hooks/hooks.json:
{
"description": "The first-mod hooks module",
"modules": ["./register.js"]
}
Escriba el código
Este archivo es el código del mod, llamado el módulo de hooks. Cuando el mod se carga, Claude Code llama a la función register que el archivo exporta y le pasa una función llamada on. Cada llamada a on registra un controlador de eventos, llamado un hook, para el evento que nombra.
Guarde 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 recuento en calls y registra cuatro hooks:
session.startse ejecuta cuando la sesión comienza, antes de su primer símbolo del sistema, y nuevamente 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 pide a Claude Code que dibuje la interfaz nuevamente.command.runse ejecuta cuando escribe/tally. Devuelve el texto a imprimir.ui.renderse ejecuta cada vez que Claude Code dibuja el spinner. Agrega el recuento después de la palabra del spinner.
Cómo funciona el mod de ejemplo explica los tres argumentos que cada hook toma y qué devuelve cada uno.
Cargue el mod
Inicie Claude Code con la bandera --plugin-dir, que carga un directorio de plugin para una sesión sin instalarlo:
claude --plugin-dir ./first-mod
Pruebe el mod
Pida a Claude que haga algo que requiera algunas llamadas de herramientas, como list the files here and read the README. Mientras Claude trabaja, la palabra del spinner va seguida de un recuento que sube, como en Thinking · tool calls: 2…. Cuando Claude termina, escriba /tally y presione Enter. La transcripción muestra first-mod: Claude has made 2 tool calls since this mod loaded, con su propio recuento. Claude Code pone el nombre del plugin delante del texto del comando.
Para verificar el comando sin una sesión interactiva, ejecútelo 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ó. Consulte Descubra por qué un mod no hace nada.
Cambie el código mientras la sesión se ejecuta
Deje la sesión abierta. En register.js, cambie ' · tool calls: ' a ' · tools used: ' en el hook ui.render y guarde. 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 dice 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 pasa a on es un hook, que es un controlador de eventos. Claude Code pasa a cada hook los mismos tres argumentos:
- La API de mods, llamada
$: cada método que un mod puede llamar para llegar fuera de sí mismo, 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 de herramienta - El siguiente controlador, llamado
next: una función que pasa el evento a los otros mods y luego al comportamiento propio de Claude Code, y devuelve el resultado
Los hooks en first-mod manejan sus eventos de las tres formas en que un hook puede:
- Observar: el hook
session.startregistra el comando, y el hooktool.callcuenta la llamada y pide un redibujado. Ambos devuelvennext(e), por lo que la sesión comienza y la herramienta se ejecuta como de costumbre. - Responder: el hook
command.rundevuelve su propio resultado y nunca llama anext. El segundo argumento aon,{ command: 'tally' }, es un filtro, llamado un matcher, por lo que el hook se ejecuta solo para/tally. - Reescribir: el hook
ui.renderllama anextcon una copia deecuyosuffixcontiene el recuento, por lo que Claude Code dibuja su spinner habitual con su texto después de la palabra
Claude Code observa un directorio cargado con --plugin-dir y recarga en caliente el módulo de hooks cuando un archivo en él cambia. Cada recarga ejecuta register nuevamente, por lo que calls vuelve a 0 y /tally comienza a contar nuevamente. Para mantener un valor entre recargas, consulte Mantener estado.
Continúe trabajando en un mod
Una vez que un mod se carga, puede hacer que Claude lo cambie, verificar su código contra las definiciones de tipo para su versión, enumerar los eventos y llamadas que Claude Code encuentra en él, y probarlo.
Cambie un mod con Claude
Para cambiar un mod que ya tiene, inicie la sesión con --plugin-dir apuntando al directorio del mod, para que lo que Claude escribe se cargue en la misma sesión:
claude --plugin-dir ./first-mod
Luego pida 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 reporta. Un directorio que carga con --plugin-dir es una ruta protegida, por lo que en los modos default y acceptEdits se le pide que apruebe cada edición de Claude al mod. La tabla de rutas protegidas da el resultado para los otros modos de permiso.
Los archivos que Claude guarda durante su turno se recargan cuando termina el turno, por lo que puede probar /tally-reset tan pronto como Claude termine.
Obtenga definiciones de tipo para su versión
Cada vez que Claude Code carga o recarga un mod desde un directorio que pasa a --plugin-dir, o un mod que Claude escribió para usted, escribe archivos de declaración de TypeScript, terminando en .d.ts, en .claude-plugin/types/ dentro del directorio del mod. Describen los eventos exactos, métodos de la API de mods, y elementos en la versión de Claude Code que está ejecutando, por lo que su editor puede autocompletar y verificar el tipo de sus hooks. Para examinar las declaraciones en línea, lea mods/types/claude-code.d.ts en el repositorio de Claude Code, cuya primera línea nombra la versión que la escribió. El directorio contiene estos archivos:
| Ruta | Lo que declara |
|---|---|
claude-code/index.d.ts |
Cada evento y su entrada y 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 resultados de las herramientas integradas, para que verificar e.tool === 'Bash' reduzca e |
claude-code-mcp/index.d.ts |
Las entradas de las herramientas MCP que se conectaron la última vez que guardó un archivo en el mod |
index.d.ts en un directorio nombrado para un plugin |
Lo que ese plugin agrega a la API de mods. Hay un directorio para cada plugin que su plugin.json enumera bajo dependencies. |
tsconfig.json |
Opciones del compilador que se ajustan a un módulo de hooks |
Si su mod no tiene su propio tsconfig.json, Claude Code agrega uno en la raíz del mod que extiende el generado, por lo que su editor y tsc -p ./first-mod verifican el tipo del mod sin más configuración.
Los eventos y métodos pueden cambiar entre versiones, por lo que confíe en estos archivos sobre cualquier página, incluso esta, cuando no estén de acuerdo.
claude-code/index.d.ts es la referencia más completa para su compilación, con un comentario y un ejemplo para cada método de la API de mods. Para buscar algo, busque en el archivo su nombre, como 'tool.call'.
Verifique qué Claude Code lee de su mod
Para ver su mod de la forma en que Claude Code lo ve, sin ejecutar su código o iniciar una sesión, use claude plugin validate. Verifica el manifiesto y ejecuta el mismo análisis estático en la fuente del módulo de hooks que Claude Code ejecuta cuando carga un mod. En su shell, ejecútelo en 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: enumera los eventos que su módulo engancha, cada uno con su filtro entre llaves. La línea calls: enumera cada método de la API de mods que llama. Un módulo que lee o establece variables de entorno también obtiene líneas env reads: y env writes:, y uno que usa $.state obtiene state reads: y state writes:.
Si un evento que pretendía enganchar falta en la primera línea, 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.
Siga estas reglas para que el análisis estático pueda encontrar cada hook y llamada:
- Deletree cada llamada de la API de mods en su totalidad:
$, el espacio de nombres, luego el método, como en$.store.get('notes'). Puede pasar$a una función declarada en el nivel superior del mismo archivo, y para una función suya llamadaloadNotes, la líneacalls:entonces lee$.store.get (via loadNotes). Pasar$a un método, una función definida dentro del hook, o una función que importa de otro de sus archivos falla la validación. Las funcionesreadyupdateque$.stateusa son las importaciones que pueden tomarlo. No asigne$o uno de sus espacios de nombres a una variable, desestructúrelo, o indexarlo con un nombre calculado.const ui = $.uifalla con$.ui is used as a value. - Escriba 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 declare una segunda variable o parámetro llamadoon. La validación falla con"on" is declared again (shadowed). - Importe solo desde archivos dentro del directorio del plugin, por ruta relativa. La única importación desnuda permitida es
claude-code, para tipos y algunos ayudantes. - Use declaraciones
importen la parte superior 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. - Escriba cada archivo como un módulo ES, con
importy norequire. La referencia enumera las extensiones de archivo que Claude Code carga.
Pruebe el mod
Puede escribir pruebas automatizadas para un mod y ejecutarlas desde su shell con claude plugin test, sin sesión, inicio de sesión o red. Una prueba genera los eventos que sus hooks manejan y verifica qué hicieron los hooks.
Esta prueba genera dos llamadas de herramientas, ejecuta /tally, y verifica que la respuesta cuente ambas. Guárdela 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' }))
// Raise 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 su shell, ejecute las pruebas desde el directorio first-mod:
claude plugin test
La salida nombra cada prueba y 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]
Pruebe un mod cubre el stubbing de una llamada de modelo o la tienda, y pruebas de temporizadores y dibujos.
Comparta su mod
Un mod es un plugin, por lo que lo versiona en el manifiesto y las personas lo instalan y actualizan con los comandos /plugin. Para dárselo a otras personas, agréguelo a un marketplace.
Antes de hacerlo, verifique el name del plugin: claude plugin validate falla un nombre que parece uno de los propios de Anthropic, como uno que comienza con claude-. Los eventos y métodos pueden cambiar entre versiones, por lo que su README es el lugar para decir qué versión de Claude Code probó.
Continúe desarrollando contra el directorio con --plugin-dir, no contra una copia instalada. Claude Code almacena en caché un plugin instalado por versión, por lo que sus ediciones no llegan a la copia instalada hasta que sube la versión e instala nuevamente.
Próximos pasos
- Dibuje en la interfaz: abra un panel, dibuje encima del símbolo del sistema, y agregue botones y campos de texto
- Reaccione a eventos: enganche llamadas de herramientas, símboles del sistema, y turnos
- Use la API de mods: agregue comandos y herramientas, llame a un modelo, y ejecute trabajo en un temporizador
- Pruebe un mod: stub lo que Claude Code respondería, y pruebe temporizadores y dibujos
- Solucione problemas de un mod: las razones por las que un mod no hace nada, y el registro de depuración
- Lea la fuente de mods integrados: plugins completos, cada uno con su módulo de hooks y pruebas