docs/COMO-FAZER-UM-MOD.md do inema-mods (05/10/2026). O texto exato de um log pode mudar entre versões: guie-se pelo sentido.
Ligue o --debug
A referência oficial dá o conselho em uma linha: rode com claude --debug enquanto desenvolve. O log de depuração tem uma linha para cada vez que algo deu errado e para cada resultado que o engine recusou.
Sem o debug você ainda vê parte disso, mas só em certas sessões. A tabela mostra onde cada sinal aparece.
🆕 Novo aqui? Log de depuração e linha apagada
O log de depuração é o registro detalhado que o --debug liga. A linha apagada (dim) é um aviso discreto que aparece na própria conversa, com o nome do plugin, o evento e o motivo, uma vez só, enquanto a sessão recarrega a pasta do mod a quente.
| Tipo de sessão | Onde aparece o aviso |
|---|---|
Interativa com --plugin-dir (pasta vigiada) | linha apagada na conversa, uma vez; e o log de depuração |
| Plugin instalado por marketplace | só no log de depuração, como <plugin>: <linha> |
claude -p com saída em texto | módulo que não carregou: uma vez no stderr, com o motivo |
claude -p com saída json | só no log de depuração |
O que olhar na tabela: o lugar mais completo é sempre o log. Desenvolva com --plugin-dir e --debug juntos e você vê os dois sinais.
⚠️ Instalado por marketplace não avisa na conversa
A linha apagada só aparece enquanto a sessão recarrega a pasta a quente. Um mod instalado pelo marketplace que falha fica mudo na tela: o único sinal está no log. Se um mod instalado "parou de funcionar", reabra com claude --debug antes de qualquer outra coisa.
cada ocorrência
aviso na conversa
stderr no carregamento
antes de carregar
Leia a linha de hook pulado
Um hook que lança erro, estoura o orçamento de tempo ou devolve uma forma errada é pulado. A cadeia segue sem ele: os hooks de baixo e o núcleo rodam no lugar, ou vale o último resultado do next que ele chegou a chamar.
Por isso o sintoma é silencioso: o Claude Code continua normal, só que sem o seu mod. A exceção é o .catch do registro, que responde no lugar do hook que falhou (módulo 2.2).
Como ler o desenho: o X vermelho não derruba a sessão; a seta passa por cima. O que sobra é a linha pontilhada no log. Pela referência, ela traz o nome do erro e o tamanho da mensagem no lugar do texto; o texto do primeiro erro, cortado, fica na linha apagada da conversa.
✓ Causas comuns de hook pulado
- ✓ Mock ou noun que não responde e lança
- ✓
$.command.registerrecusado por nome repetido - ✓ Laço longo que passa do orçamento do dispatch
- ✓ Resposta com forma errada para o evento
✗ Não conclua
- ✗ "O evento não dispara" antes de ler o log
- ✗ "Foi o outro mod" sem rodar só o seu
- ✗ Que o erro some sozinho no próximo reload
💡 Um .catch por comando registrado
Visto ao vivo no inema-mods: se a pessoa já tem uma skill ou comando com o mesmo nome, o $.command.register é recusado e o erro derruba o hook session.start inteiro, levando junto tudo o que vinha depois. Registre cada comando com o seu próprio .catch(...) e escolha nomes pouco comuns.
Corrija um módulo que não carrega
Pior que um hook pulado é um módulo que nem carrega: nenhum hook entra, nenhum comando aparece. As causas conhecidas são poucas e quase sempre estruturais, não de lógica.
O claude plugin validate lê o manifesto e o código como o engine vai ler e relata o que o engine recusaria, antes de você abrir uma sessão. Rode-o primeiro, sempre.
| Causa | Fonte | Correção |
|---|---|---|
import() dinâmico | reference.md | import estático de arquivo do mod |
| Extensão fora da lista | reference.md | .ts .tsx .jsx .js .mjs .cjs .mts .cts |
require, Node, DOM | pegadinha 2 | tudo pelo $ |
$ passado a função interna | pegadinha 1 | função no topo do arquivo |
Contrato só com export {} | pegadinha 13 | exportar ao menos um tipo |
export const register: Register = on => {
// função que recebe $, DENTRO do register
const avisar = async ($: EngineInterface, t: string) =>
$.ui.toast(t)
on('session.start', async ($, e, next) => {
await avisar($, 'mod pronto')
return next(e)
})
}
// no TOPO do arquivo
async function avisar($: EngineInterface, t: string) {
$.ui.toast(t)
}
export const register: Register = on => {
on('session.start', async ($, e, next) => {
await avisar($, 'mod pronto')
return next(e)
})
}
⚠️ Fora isso, $ é sempre $.<noun>.<método>
A pegadinha 1 completa diz: $ só pode ser passado para função declarada no topo do arquivo; fora isso, use sempre a forma $.<noun>.<método>(…). É por isso que todos os mods do inema-mods (e este curso desde a trilha 2) declaram medir, registrar, aplicarModo acima do register.
Corrija uma árvore recusada
Quando um ui.render devolve uma árvore que não valida (um elemento que a superfície não tem, uma prop que ele não aceita, um filho onde não cabe filho), o engine não desenha a árvore. Ele desenha a dele no lugar e registra o motivo.
No log de depuração a linha começa assim, seguida do motivo:
ui.render (<Component>): a hook returned a tree that does not validate
Numa sessão que recarrega a pasta do mod a quente, a conversa mostra também, uma vez por carga, componente e motivo:
<plugin>: ui.render (<Component>) refused: <reason>; the engine drew its own
Como ler o desenho: o caminho vermelho não quebra a sessão, mas o seu desenho some. Segundo a referência, quando o engine não tem o que desenhar, a faixa fica vazia e a linha termina em nothing was drawn; um painel é fechado e a linha termina em the pane was closed.
✓ Como corrigir
- ✓ Leia o motivo e o tipo das props do elemento
- ✓ Estreite por
e.surfaceantes de usarRaster - ✓ No teste,
ui.drawn()rejeita com a recusa
✗ Recusas clássicas
- ✗
RasterouImagefora do terminal - ✗
Svgno terminal - ✗
InputouSelectno mobile
Percorra as pegadinhas do kit INEMA
O docs/COMO-FAZER-UM-MOD.md do inema-mods lista 23 pegadinhas que custaram tempo na construção dos 18 mods. Abaixo, o sentido de cada uma, agrupado. O número é o do arquivo (lá a 17 aparece por último).
| Grupo | Pegadinhas (resumo) |
|---|---|
| Carregar | 1 $ só em função do topo · 2 sem import(), require, Node, DOM · 13 contrato exporta ao menos um tipo · 21 item do PluginState é objeto literal ali mesmo |
| Testes | 3 nada responde por baixo: mock cada noun · 4 prompt.submit pede { text, origin, wait: false } · 5 FsStat exige isLink · 14 mock.store(on, {…}) · 15 turn.start precisa de mock; turn.step se lê com for await · 18 mocks antes do $ · 19 fs.list com mtimeMs; outros nouns de sessão precisam de mock · 23 datas com meio-dia UTC |
| Estado e tempo | 7 $.clock.now(), nunca Date.now() · 8 estado do desenho em $.state; render nunca escreve · 16 $.command.run num hook que o turno espera fica na fila: use $.clock.after(0, …) · 17 timers do every morrem no reload (recriação no session.start não confirmada) |
| Tela | 9 painel sem pedido só vira barra lateral com tela larga · 10 terminal sem Svg; grade é um Raster · 11 a faixa é uma só: some a sua linha ao next(e) · 12 key em Text some · 20 barra que funcione no desktop: █░ com cor |
| Ferramentas | 6 sucesso = deny === undefined && isError !== true · 22 recusa de permissão chega como isError |
🆕 Novo aqui? "Visto ao vivo"
- •
$.command.registerrecusado por nome repetido derruba osession.start(tópico 2). - • Comandos de mod respondem sem chamar o modelo:
claude -pvira teste de fumaça barato (tópico 6). - • O que um comando grava em
$.storepersiste de verdade: depois de testar ao vivo, volte ao padrão. - •
$.ui.askpode se resolver sozinho por ausência: opção segura primeiro (módulo 4.1).
💡 Leia o original
Esta tabela resume. O texto completo, com os exemplos e as regras do kit (mod nunca chama modelo nem rede, mensagens em português, tudo configurável em userConfig), está em github.com/inematds/inema-mods, arquivo docs/COMO-FAZER-UM-MOD.md.
Faça um teste de fumaça com claude -p
Os testes provam a lógica. O teste de fumaça prova que o mod carrega de verdade numa sessão real. Um comando de mod responde sem chamar o modelo, então custa zero de cota.
O inema-mods junta os três controles anteriores num script só, scripts/checar-mod.sh: validate, tsc contra os tipos do engine e test. O Rafa roda os dois antes de dizer "pronto".
validate
Manifesto e código lidos como o engine lê.
tsc
Nome de evento, campo e método conferidos contra os tipos da sua versão.
test
O comportamento, com o mundo em memória (módulo 4.2).
fumaça com claude -p
Uma sessão de verdade carrega a pasta e responde o comando.
No terminal, dentro da pasta inema-mods. Troque <caminho do tsc> pelo seu (ex.: o de um node_modules/.bin):
TSC=<caminho do tsc> scripts/checar-mod.sh mods/recibo-sessao claude -p "/recibo" --plugin-dir mods/recibo-sessao
Saída real (05/10/2026, Claude Code 2.1.289):
== recibo-sessao validate: ok tsc: ok test: ok (6 linhas de aprovação) recibo-sessao: Nesta sessão: nenhum arquivo criado ou alterado.
checar-mod.sh dizem ok, e o claude -p devolve a linha do recibo prefixada com o nome do plugin. Se em vez disso aparecer no stderr um aviso de que o módulo não carregou, volte ao tópico 3.✓ Fumaça boa
- ✓ Um comando que só lê estado e responde texto
- ✓ Resultado conhecido de antemão (sessão nova, nada feito)
- ✓ Roda em segundos e não grava nada
✗ Fumaça ruim
- ✗ Um prompt que faz o modelo trabalhar (gasta cota)
- ✗ Comando que grava em
$.storesem voltar ao padrão - ✗ Comando que depende de
$.ui.ask(no-pele rejeita)
Teste rápido (opcional): o mod do Rafa abre com --plugin-dir, mas nenhum hook roda e o comando não aparece. A função contar($, e) está declarada dentro do register. Qual a causa provável?
🎓 Resumo do módulo
Próximo módulo:
4.4 — Distribuir e projeto final