Pular para o conteúdo
MÓDULO 2.1

🗺️ O mapa de eventos

Escolha o evento antes do código. Um mod bom começa com uma pergunta simples: em que momento da sessão isso precisa acontecer? Este módulo é o mapa para responder.

6
Tópicos
~35
Minutos
Médio
Nível
Fundamento
Tipo
0 de 60%
Versão conferida: os nomes de eventos deste módulo foram conferidos no arquivo de tipos do Claude Code 2.1.289, em 05/10/2026. A API muda entre versões; o tópico 6 mostra como conferir na sua instalação.
1

Agrupe os eventos por noun

O arquivo de tipos lista mais de cem eventos. Parece muito até você notar o padrão: todo nome é noun.ação. O noun diz de que parte da sessão se trata. A ação diz o que aconteceu.

Então a primeira pergunta nunca é "qual evento?". É "de que parte?". Ferramenta é tool.*. Tela é ui.*. O que você digitou é prompt.*. Achou o noun, a lista encolhe para meia dúzia de nomes.

🆕 Novo aqui? Evento e chamada têm o mesmo nome

  • Evento — algo que o Claude Code faz e anuncia, como tool.call. Você pendura um hook nele com on('tool.call', hook).
  • Chamada — algo que o seu mod pede pelo $, como $.fs.read. Nos tipos, essa chamada também é um evento (fs.read), que outro mod pode observar.
  • noun.* — jeito curto de dizer "todos os eventos daquele noun", como session.*.
NounParte da sessãoEventos que você mais vai usar
session.*a sessão inteirasession.start, session.end, session.append
prompt.*o que a pessoa mandaprompt.submit, prompt.context
turn.*uma resposta do modeloturn.start, turn.step, turn.complete
tool.*as ferramentastool.call, tool.describe
ui.*a telaui.render, ui.press, ui.input, ui.select
command.*os comandos de barracommand.run
state.*valores guardados pelo hoststate.set
process.*programas da máquinaprocess.run, process.spawn
classic.*os hooks do settings.jsonclassic.Stop, classic.PreToolUse e mais 31

O que olhar na tabela: nove nouns cobrem quase tudo o que um mod faz. Os outros (agent, skill, telemetry, config...) aparecem quando você precisar deles, e seguem a mesma regra de nome.

🔧
tool

o que o Claude roda

🔁
turn

cada resposta

🎨
ui

o que aparece

🪝
classic

hooks de settings

2

Siga uma volta completa do turno

O Rafa digita "corrija o teste que quebrou" e aperta Enter. Até o Claude responder, cinco eventos passam pela cadeia, sempre na mesma ordem. Saber essa ordem é saber onde pendurar cada coisa.

O miolo se repete: o modelo responde um pedaço (turn.step), pede uma ferramenta (tool.call), lê o resultado e responde de novo. Um turno de verdade dá várias voltas nesse laço antes de terminar.

prompt.submitEnter turn.startantes do modelo turn.stepresposta em pedaços tool.callRead, Edit, Bash... repete turn.completetexto da resposta o laço azul é onde o turno passa a maior parte do tempo

Como ler o desenho: leia da esquerda para a direita. Os dois blocos azuis formam o laço: cada pedido de ferramenta gera um tool.call, e o resultado volta para o modelo num novo turn.step. Quando o modelo para de pedir ferramentas, o turno termina.

1

prompt.submit

O texto foi enviado e o turno ainda não começou. É aqui que se reescreve ou se barra um prompt.

2

turn.start

Antes da primeira chamada ao modelo. Só observa: next(e) devolve o turnId, e devolver outra coisa não muda nada.

3

turn.step

Uma chamada ao modelo, chegando em pedaços. É um stream (tópico 4).

4

tool.call

O engine vai rodar uma ferramenta. next(e) passa pelos hooks de baixo e depois pelo pedido de permissão e pela ferramenta em si.

5

turn.complete

O turno acabou. next(e) devolve { text }, a resposta; e.reason diz por que parou.

💡 O exemplo oficial usa três desses cinco

A faixa de exemplo da referência (plugin-authoring/examples/band.tsx) marca a hora em prompt.submit, conta ferramentas em tool.call e calcula a duração em turn.complete. Três eventos, um dado: quanto tempo o turno levou. Você vai abrir esse arquivo no módulo 2.3.

3

Use matchers para filtrar

Você já viu o matcher no módulo 1.1: o objeto entre o nome do evento e o hook. Agora a regra completa. O matcher é um pedaço do próprio e: o hook só roda quando os campos que você escreveu batem com os do evento.

Por isso o campo muda de evento para evento. No tool.call, o e tem tool. No command.run, tem command. No ui.render, tem component. O matcher usa os mesmos nomes.

📄 Matchers reais dos kits (cada linha com a origem)
// templates/starter-mod/hooks/starter.mjs
on("tool.call", { tool: "Read" }, hook)

// inema-mods/mods/recibo-sessao/hooks/register.tsx
on('command.run', { command: 'recibo' }, hook)
on('ui.render', { component: 'Pane', requestId: PAINEL }, hook)

// plugin-authoring/examples/band.tsx
on('ui.render', { component: 'AbovePrompt' }, hook)
O que olhar: o matcher do recibo tem dois campos. Os dois precisam bater: é um painel (Pane) e é o painel deste mod.
Valor no matcherComo comparaExemplo
texto, número, booleanoigual, exatamente{ tool: 'Edit' }
expressão regulartesta o valor como texto{ command: /^p4 / }
listabasta um item bater{ tool: ['Edit', 'Write'] }
objetoparte de um objeto de dentro{ props: { origin: { kind: 'task-notification' } } }

O que olhar na tabela: as quatro formas vêm do comentário do tipo Matcher nos tipos. Não existe startsWith nem outra função: para prefixo, use a expressão regular.

✓ Bom uso

  • ✓ Um on por ferramenta, cada um com seu matcher
  • ✓ classic.* para ouvir todos os hooks clássicos
  • ✓ Matcher no ui.render sempre: sem ele, o hook roda para cada linha da conversa

✗ Evite

  • ✗ Hook sem matcher em tool.call com um if enorme por dentro
  • ✗ Inventar campo: o tipo recusa matcher com nome que o e não tem
  • ✗ Contar com { startsWith: 'p4' }: nunca casa
4

Trate os eventos que são stream

Dois eventos não chegam inteiros: turn.step (a resposta do modelo, pedaço por pedaço) e process.spawn (a saída de um programa, pedaço por pedaço). Neles o hook tem outra forma.

Em vez de async ($, e, next) =>, é um gerador assíncrono: async function* ($, e, next). É a única forma que carrega nesses dois eventos. Uma função comum dá erro de tipo, mesmo que só devolva next(e).

🆕 Novo aqui? Stream e gerador assíncrono

  • Stream — um evento que entrega várias partes ao longo do tempo e só no fim tem um resultado.
  • async function* — função que entrega valores um a um com yield e pode esperar com await.
  • yield* next(e) — repassa todas as partes de baixo, sem mexer, e no fim vale o resultado do evento.
📄 inema-mods/mods/linha-do-tempo/hooks/register.tsx (trecho)
// turn.step é um fluxo: o hook é um gerador assíncrono que repassa tudo e,
// no fim, lê o resultado (modelo, esforço e ferramentas pedidas nesse passo).
on('turn.step', async function* ($, e, next) {
  const r = yield* next(e)
  if (e.agentId !== undefined) return r
  const modelo = r.usage?.model ?? e.model
  const esforco = esforcoEmTexto(e.effort)
  const usadas = r.toolUses.map(u => tipoDaFerramenta(u.name))
  // ... grava modelo, esforço e ferramentas no estado ...
  return r
})
O que olhar: o mod não toca nos pedaços. Ele repassa tudo com yield* e só trabalha com o resultado final r. É o caso mais comum.
repassar yield* next(e) transformar for await ... yield responder não roda yield sem next

Como ler o desenho: cada barrinha é um pedaço do stream. Na primeira linha os pedaços saem iguais. Na segunda saem de outra cor: o hook mudou cada um. Na terceira, nada vem de baixo: o hook inventa os próprios pedaços e responde sozinho.

⚠️ Pedaço entregue fica entregue

Se o hook falha no meio do stream, o que ele já entregou com yield fica, e o resto vem direto de baixo. Os tipos mostram o jeito de transformar a saída de um process.spawn: for await (const chunk of next(e)) yield { ...chunk, text: ... }. Faça a transformação ser segura pedaço a pedaço.

5

Pendure-se nos hooks clássicos

Os hooks clássicos do settings.json têm 33 eventos: Stop, PreToolUse, FileChanged, PreCompact... Um mod enxerga todos com o prefixo classic., mesmo que nenhum hook clássico esteja configurado.

O e é exatamente o JSON que um hook clássico receberia na entrada padrão, com transcript_path, cwd e os outros campos de base. A exceção é classic.PreToolUse: ali o e é o mesmo envelope do tool.call.

📄 Exemplo curto: avisar quando o Claude para
import type { Register } from 'claude-code'

export const register: Register = (on, options) => {
  on('classic.Stop', async ($, e, next) => {
    // e é o JSON do hook Stop: stop_hook_active, last_assistant_message...
    if (!e.stop_hook_active) $.ui.toast('O Claude terminou. Confira o resultado.')
    return next(e)
  })
}
O que olhar: os campos stop_hook_active e last_assistant_message vêm do tipo StopHookInput nos tipos. São os mesmos que um script de shell leria.
Você querEvento nativoClássico equivalente
saber que o turno acabouturn.completeclassic.Stop
ver uma ferramenta antes de rodartool.callclassic.PreToolUse
saber que um arquivo mudou foranão háclassic.FileChanged
agir antes de compactarnão háclassic.PreCompact

O que olhar na tabela: quando existe evento nativo, prefira ele: o e é mais rico e o resultado é mais fácil de usar. O clássico serve para o que só os hooks de settings anunciam.

✓ Use classic.*

  • ✓ para o que só os hooks de settings anunciam (FileChanged, PreCompact)
  • ✓ quando quer ler os mesmos campos que um script de shell leria
  • ✓ com matcher ou nome exato, para não ouvir os 33 sem querer

✗ Evite

  • ✗ classic.Stop quando turn.complete já resolve
  • ✗ classic.PreToolUse no lugar de tool.call sem motivo
  • ✗ supor que o e do clássico tem o formato de um evento nativo

💡 Onde o mod fica na fila dos clássicos

Os tipos descrevem a ordem: primeiro os hooks clássicos gerenciados pela empresa, depois os mods, depois os outros hooks de settings. Um bloqueio gerenciado termina a cadeia antes de chegar ao seu mod. A cadeia é o assunto do módulo 2.2.

6

Ache qualquer evento nos tipos

A lista deste módulo vale para a 2.1.289. A da sua máquina pode ser outra. A autoridade é um arquivo que o próprio Claude Code escreve dentro da pasta do mod, a cada carga e recarga: .claude-plugin/types/claude-code/index.d.ts.

Ele tem cerca de 20 mil linhas. Ninguém lê de cima a baixo. Você busca o nome do evento entre aspas seguido de dois-pontos, por exemplo 'turn.step':, no editor ou com grep. O comentário logo acima diz quando o evento dispara, o que next(e) devolve e o que você pode devolver.

🎯 Objetivo: achar os tipos da sua instalação

Na pasta de um mod que já carregou ao menos uma vez com claude --plugin-dir:

ls .claude-plugin/types/

Depois, cole no Claude Code (ele só lê):

Leia .claude-plugin/types/claude-code/index.d.ts e me mostre o comentário
do evento 'turn.complete': quando dispara, o que next(e) devolve e o que
um hook pode devolver no lugar. Não edite nada.
Resultado esperado: o ls lista claude-code/, claude-code-tools/, claude-code-mcp/ e tsconfig.json. O Claude responde citando o comentário: dispara no fim do turno, next(e) devolve { text }, e um { text } diferente aparece abaixo da resposta sem mudar o registro.

Outro atalho: o claude plugin validate lista os eventos que o seu mod pendura, com o matcher entre chaves. É a prova de que o engine leu o que você quis dizer.

📄 Saída real de claude plugin validate templates/starter-mod (Claude Mods Starter Kit, da Prompt Advisers, MIT; 05/10/2026)
  ❯ ./starter.mjs hooks: session.start, tool.call{tool=Read}, command.run{command=readcount}
  ❯ ./starter.mjs calls: $.command.register

✔ Validation passed

Teste rápido (opcional): o Rafa quer contar os caracteres da resposta do modelo enquanto ela chega. Qual hook carrega?

📁
.claude-plugin/types

escrito a cada carga

🔎
'evento':

o que buscar

💬
Comentário

quando, next, retorno

✔️
validate

lista seus hooks

🎓 Resumo do módulo

✓
Todo evento é noun.ação — ache o noun e a lista encolhe.
✓
O turno tem ordem fixa — prompt.submit, turn.start, laço turn.step/tool.call, turn.complete.
✓
O matcher é um pedaço do e — igual, regex, lista ou objeto parcial.
✓
turn.step e process.spawn são stream — async function* e yield* next(e).
✓
A verdade está no index.d.ts da sua pasta — classic.* incluídos.

Próximo módulo:

2.2 — A cadeia: next, reescrever e responder