Claude Code mods: como criar seu primeiro mod

Claude Code mods: como criar seu primeiro mod

Os Claude Code mods são a forma mais completa de adaptar o Claude Code ao seu fluxo de trabalho. Com eles, você intercepta eventos, reescreve comportamentos e desenha interface própria dentro da sessão.

Neste artigo, você vai entender como um mod funciona, construir o primeiro deles a partir de uma pasta vazia e ver como publicar o resultado para outras pessoas.

O que são Claude Code mods

O Claude Code já oferecia várias formas de personalização, como configurações, regras de permissão, comandos de barra, skills e linha de status. Os mods vão além, porque permitem alterar ou substituir o que a ferramenta faz.

Na prática, cada mod é um hook que viaja dentro de um plugin. Em outras palavras, trata-se de um módulo pequeno, escrito em JavaScript ou TypeScript, que roda dentro da sua sessão e enxerga cada evento no momento em que ele acontece.

Por isso, os Claude Code mods servem para encaixar a ferramenta no seu jeito de trabalhar. Você pode criar um indicador que consulta o tempo todo, uma trava antes de comandos perigosos ou uma visão de revisão no formato que prefere.

O recurso está disponível a partir do Claude Code 2.1.287 e já vem ativado por padrão. Em versões anteriores, o suporte existia apenas em acesso antecipado, atrás de uma variável de ambiente.

Além disso, a própria Anthropic usa mods para construir partes do produto. Por exemplo, o suporte ao AGENTS.md e o painel do comando /diff foram escritos como mods, e o código com testes está no diretório mods/ do repositório oficial do Claude Code no GitHub.

Como Claude Code mods funcionam por dentro

Tecnicamente, Claude Code mods são, antes de tudo, plugins comuns. Assim, a pasta segue a estrutura que você já conhece, com um manifesto em .claude-plugin/plugin.json.

A diferença está no arquivo hooks/hooks.json. Nele, a chave modules aponta para um único módulo JavaScript ou TypeScript.

Esse módulo exporta uma função register(on, options). Dentro dela, cada chamada a on(evento, filtro?, hook) adiciona um hook à cadeia.

O formato de um hook

Todos os hooks recebem os mesmos três parâmetros, o que torna a API previsível. Observe o formato básico:

on("tool.call", { tool: "Bash" }, async ($, e, next) => {
  // $: API dos mods (ui, session, state, fs, process, command, model...)
  // e: dados do evento atual, como objeto simples
  // next: repassa o evento para o próximo elo da cadeia
  return next(e);
});

O objeto $ concentra todo o acesso ao mundo externo. Isso acontece porque o módulo roda em uma sandbox própria, sem DOM e sem Node.

Já o parâmetro e traz os dados do evento. Por fim, next entrega o evento ao próximo plugin e, no fim da cadeia, ao comportamento original do Claude Code.

Observar, reescrever ou responder

Os hooks formam uma cadeia, de forma parecida com middlewares. Portanto, cada hook escolhe uma entre três atitudes possíveis.

Atitude Como fazer Uso típico
Observar Chamar await next(e) e inspecionar o resultado Registrar edições ou medir algo após cada turno
Reescrever Chamar next com uma cópia alterada do evento Trocar um comando por uma versão mais segura
Responder Retornar { deny: "motivo" } sem chamar next Recusar uma ferramenta ou atender um comando

Os eventos disponíveis cobrem chamadas de ferramentas, envio do prompt, início e fim de turnos, início e fim de sessão e comandos de barra. Além disso, o evento ui.render dispara para cada pedaço da interface que a ferramenta desenha.

Diferença para os hooks de configuração

Os hooks tradicionais, definidos nas configurações, executam um comando de shell para cada evento. Nesse modelo, a comunicação acontece por JSON trafegando pela entrada e pela saída padrão.

Um mod, por outro lado, carrega uma única vez e permanece vivo durante toda a sessão. Com isso, ele mantém estado, desenha interface que se atualiza sozinha e chama recursos do Claude Code, como abrir painéis, executar processos e registrar comandos.

Se você ainda não domina os conceitos de contexto e consumo de tokens, vale ler antes o artigo sobre custo de contexto e cache do Claude Opus 5.5. Esse conhecimento ajuda a entender o exemplo que vamos construir.

Criando seu primeiro mod no Claude Code

O exemplo deste artigo se chama Token Weather. Ele lê o uso da janela de contexto após cada turno e desenha uma linha acima do prompt, no formato de uma previsão do tempo.

A previsão muda conforme a ocupação do contexto cresce. Veja a tabela de faixas usada pelo mod:

Uso do contexto Previsão exibida Significado
Abaixo de 25% ☀ Clear Céu limpo
De 25% a 49% ☁ Cloudy Nublado
De 50% a 74% ☂ Rain Chuva
De 75% a 89% ☇ Storm Tempestade
A partir de 90% ↯ Compaction soon Compactação em breve

Atalho: peça ao Claude Code para escrever o mod

Antes de seguir o passo a passo, vale conhecer um atalho. O Claude Code sabe escrever Claude Code mods, então basta descrever o que você quer ver.

Inicie uma sessão com o comando claude e explique o resultado esperado em linguagem natural. Por exemplo, peça uma linha acima do prompt com o ícone do tempo, o percentual usado, os tokens consumidos e um pequeno gráfico dos últimos turnos.

Em seguida, a ferramenta pergunta se pode ativar o recarregamento automático na sessão. Ao permitir, a faixa aparece acima do prompt quando o turno termina, e cada ajuste pedido recarrega o mod no lugar.

Contudo, esse mod vive apenas na sessão atual e a pasta dele é limpa depois. Para mantê-lo, copie a pasta e instale como qualquer plugin, conforme a seção sobre compartilhamento.

Passo 1: estrutura de pastas

Primeiro, confirme que sua versão é compatível executando claude --version. O resultado precisa ser 2.1.287 ou superior.

Depois, crie a seguinte estrutura de pastas:

token-weather/
├── .claude-plugin/
│   ├── plugin.json
│   └── types/            (gerado pelo Claude Code ao carregar o mod)
├── hooks/
│   ├── hooks.json
│   └── token-weather.mjs
├── types/
│   └── index.d.ts
└── tests/
    └── token-weather.test.ts

O arquivo plugin.json é um manifesto de plugin padrão:

{
  "name": "token-weather",
  "version": "0.1.0",
  "description": "Context window usage forecast, above the prompt.",
  "author": { "name": "Your name" },
  "types": "./types/index.d.ts"
}

Já o hooks.json aponta para o módulo. Lembre que um mod tem exatamente um módulo:

{
  "modules": ["./token-weather.mjs"]
}

Passo 2: desenhando acima do prompt

A faixa logo acima do prompt corresponde a um componente chamado AbovePrompt. Como o Claude Code não desenha nada ali por conta própria, esse é um ótimo primeiro alvo.

Para desenhar, você intercepta o evento ui.render desse componente e retorna uma árvore de elementos:

// hooks/token-weather.mjs
export function register(on) {
  on("ui.render", { component: "AbovePrompt" }, ($, e, next) => {
    const { Box, Text } = $.ui.resolve(e);
    return Box({
      paddingX: 1,
      children: [Text({ color: "yellow", bold: true, children: "☀ Clear sky" })],
    });
  });
}

Note que os elementos Box e Text não são globais. O método $.ui.resolve(e) devolve os construtores da superfície atual, porque cada superfície suporta um conjunto um pouco diferente.

Agora, inicie uma sessão carregando o plugin com claude --plugin-dir ./token-weather. A mensagem aparece acima do prompt e, a partir daí, cada vez que você salva o arquivo o módulo recarrega sem reiniciar a sessão.

Esse ciclo rápido de feedback é, de fato, um dos motivos que tornam os Claude Code mods tão produtivos de escrever.

Passo 3: lendo o uso de contexto com $.state

O método $.session.usage() retorna os mesmos números da linha de status. Nesse caso, interessam os campos context.tokens, context.window e context.percent.

Uma variável no nível do módulo parece a escolha óbvia para guardar o histórico. No entanto, cada recarregamento executa register de novo e zera essas variáveis.

Por isso, o histórico fica em $.state, um armazenamento mantido pelo host durante toda a sessão. Dessa forma, os valores sobrevivem aos recarregamentos, e essa regra vale para todos os Claude Code mods que guardam histórico:

// chave de estado onde o histórico de leituras fica guardado
const readingsKey = { plugin: "token-weather", key: "readings" };

async function recordReading($) {
  const { context } = await $.session.usage();
  if (!context?.window) return;
  const { value: history = [] } = await $.state.get(readingsKey);
  const reading = {
    tokens: context.tokens ?? 0,
    window: context.window,
    percent: context.percent ?? Math.round(((context.tokens ?? 0) / context.window) * 100),
  };
  // mantém apenas as 12 leituras mais recentes
  await $.state.set(readingsKey, [...history, reading].slice(-12));
}

Em seguida, registre a leitura no início da sessão e ao fim de cada turno principal. Ambos os hooks chamam next(e) primeiro e só depois observam:

on("session.start", async ($, e, next) => {
  const result = await next(e);
  await recordReading($);
  return result;
});

on("turn.complete", async ($, e, next) => {
  const result = await next(e);
  if (!e.agentId) await recordReading($); // ignora turnos de subagentes
  return result;
});

Todo valor de estado precisa constar no contrato de tipos do plugin. Assim, crie o arquivo types/index.d.ts:

export type ContextReading = { tokens: number; window: number; percent: number };

declare module "claude-code" {
  interface PluginState {
    "token-weather": { readings: ContextReading[] };
  }
}

Sem essa declaração, o validador interrompe o processo e indica exatamente qual chave falta. Em troca, você ganha redesenho automático, porque cada leitura de $.state feita durante a renderização inscreve a faixa nas mudanças seguintes.

Passo 4: desenhando a previsão

Com o histórico em mãos, falta transformar os números em previsão. Essa é a parte visual que mais diferencia os mods do Claude Code dos hooks tradicionais. O trecho abaixo mostra a parte essencial da renderização:

// faixas de previsão, da menor para a maior ocupação do contexto
const FORECAST = [
  { upTo: 25, icon: "☀", label: "Clear", color: "yellow" },
  { upTo: 50, icon: "☁", label: "Cloudy", color: "cyan" },
  { upTo: 75, icon: "☂", label: "Rain", color: "blue" },
  { upTo: 90, icon: "☇", label: "Storm", color: "magenta" },
  { upTo: Infinity, icon: "↯", label: "Compaction soon", color: "red" },
];

on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
  const { value: history = [] } = await $.state.get(readingsKey);
  if (e.props.hasSurvey || history.length === 0) return next(e);

  const { Box, Text } = $.ui.resolve(e);
  const current = history.at(-1);
  const band = FORECAST.find((f) => current.percent < f.upTo);

  return Box({
    flexDirection: "row",
    paddingX: 1,
    children: [
      Text({ color: band.color, bold: true, children: `${band.icon} ${band.label}` }),
      Text({ children: `  ${current.percent}% of context` }),
      Text({ dimColor: true, children: `  ${current.tokens} / ${current.window}` }),
    ],
  });
});

Três detalhes desse código valem para qualquer projeto. Primeiro, as propriedades do componente ficam em e.props, e não diretamente em e.

Segundo, quando não há nada a desenhar, o hook retorna next(e) e devolve a faixa para a ferramenta e para outros mods. Além disso, a propriedade bodyColumns informa a largura real disponível, o que permite adaptar o conteúdo a telas estreitas.

Terceiro, prefira símbolos de largura simples em vez de emojis. Os caracteres ☀ ☁ ☂ ☇ ↯ ficam alinhados em qualquer fonte de terminal.

Passo 5: validação e testes de Claude Code mods

O comando claude plugin validate ./token-weather lê o manifesto e o código do módulo da mesma forma que o Claude Code fará. Em seguida, ele lista os eventos interceptados, os métodos de $ utilizados e as chaves de estado lidas e escritas.

Esse relatório também funciona como inventário de capacidades. Portanto, quem for instalar o seu mod consegue ver exatamente o que ele acessa.

Já o comando claude plugin test ./token-weather executa os arquivos *.test.ts contra o runtime real. Nos testes, os hooks registrados rodam depois do mod e simulam as respostas da ferramenta:

import { describe, expect, test } from "claude-code/testing";

describe("token-weather", () => {
  test("banner follows context usage", async ($, on) => {
    let tokens = 30_000;
    on("session.start", ($, e) => ({ cwd: e.cwd }));
    on("session.usage", () => ({
      value: { startedAt: 0, rateLimits: [], context: { tokens, window: 200_000, percent: Math.round(tokens / 2_000) } },
    }));
    on("turn.complete", () => ({ text: "" }));

    await $.session.start({ surface: "terminal", isInteractive: true, cwd: "/work" } as any);
    const ui = await $.ui.mount({
      plugin: "token-weather",
      surface: "terminal",
      component: "AbovePrompt",
      props: { hasSurvey: false, isWorking: false, maxRows: 10, bodyColumns: 120 },
    } as any);
    expect(await ui.find({ type: "Text", text: /Clear/ })).toBeDefined();

    tokens = 130_000;
    await $.turn.complete({ reason: "answer", answer: "ok", durationMs: 1 } as any);
    expect(await ui.find({ type: "Text", text: /Rain/ })).toBeDefined();
    await ui.unmount();
  });
});

Repare que o teste confirma o redesenho automático. A faixa muda depois de turn.complete sem que o mod peça uma nova renderização.

Como compartilhar Claude Code mods

Como todo mod é um plugin, a distribuição segue o mesmo caminho. Ou seja, você não precisa aprender nada novo para publicar.

Um marketplace pode ser simplesmente uma pasta com o arquivo .claude-plugin/marketplace.json:

{
  "name": "my-mods",
  "owner": { "name": "Your name" },
  "plugins": [{ "name": "token-weather", "source": "./token-weather" }]
}

Localmente, basta registrar o marketplace e instalar o plugin:

claude plugin marketplace add ./my-mods
claude plugin install token-weather@my-mods --scope user

Ao colocar essa pasta em um repositório do GitHub, ele passa a funcionar como marketplace público. Assim, qualquer pessoa instala com /plugin marketplace add, /plugin install e /reload-plugins, e você atualiza tudo com um simples push.

No entanto, existe um cuidado importante de segurança. Um mod roda na sua máquina com o mesmo acesso do Claude Code e é escrito pelo publicador, não pela Anthropic.

Portanto, trate Claude Code mods como pacotes de terceiros: leia o repositório antes e instale apenas de fontes confiáveis. Análises independentes já demonstraram que um mod malicioso consegue, por exemplo, desenhar um falso pedido de credenciais acima do prompt.

A análise de segurança da Pluto Security também mostrou um mod lendo arquivos fora do projeto com $.fs.read, sem nenhum aviso ao usuário. Além disso, um mod que baixa scripts com $.http.fetch e os executa com $.process.run pode mudar de comportamento depois da instalação.

Por isso, revise com atenção chamadas a next que reescrevem eventos e qualquer padrão de baixar e executar código. Em ambientes corporativos, os administradores podem usar as configurações disableAllHooks ou allowManagedHooksOnly para restringir o que roda nas máquinas do time.

Exemplos avançados de Claude Code mods

O Token Weather apenas observa e desenha. Por outro lado, os dois exemplos a seguir interferem em eventos, abrem painéis e recebem entrada do usuário.

Blast Radius

O Blast Radius segura chamadas arriscadas ao Bash, como rm -rf, git reset --hard, git clean, force push e migrações de banco. Em seguida, ele calcula o que o comando alteraria e abre um painel com as opções Proceed e Cancel.

A essência dele é a atitude de responder, vista na tabela anterior:

on("tool.call", { tool: "Bash" }, async ($, e, next) => {
  if (!isRisky(e.command)) return next(e);       // o resto segue normalmente
  const decision = await askUser($, e);          // painel com Proceed e Cancel
  if (decision === "proceed") return next(e);
  return { deny: "Command cancelled by the user in Blast Radius." };
});

Para montar o relatório, o mod usa $.process.run com comandos de simulação, como git status --porcelain e git clean -n. Como os argumentos seguem em formato de array, nenhum caminho é interpretado como código de shell.

Vale lembrar que cada hook tem 10 segundos de tempo próprio por disparo. Contudo, o tempo de espera dentro de chamadas a $ não conta, o que permite aguardar a decisão do usuário.

Esse mod funciona como rede de proteção, não como sistema de permissões. Como ele lê apenas o texto do comando, aliases e scripts conseguem escapar, então use regras de permissão quando precisar de um bloqueio rígido.

Replay Theater

Enquanto um turno roda, o Replay Theater registra cada chamada de Edit e Write, com o texto antes e depois. Quando o turno termina, uma dica aparece acima do prompt e o comando /replay abre um painel que percorre as edições uma a uma.

Esse exemplo ensina três técnicas úteis. Primeiro, ele combina turn.start e turn.complete para agrupar as edições de cada turno.

Depois, registra um comando de barra com $.command.register no início da sessão e o atende no evento command.run. Por fim, usa $.fs.read para capturar o conteúdo original do arquivo antes da escrita.

Além disso, ele mostra que o posicionamento é responsabilidade da superfície. Em tela cheia o painel fica à direita, enquanto em terminais estreitos ele aparece acima do prompt, sem mudança no código do mod.

Boas práticas para escrever mods no Claude Code

Algumas práticas evitam a maior parte dos problemas ao escrever Claude Code mods. Confira as principais:

  • Use os tipos gerados. A cada carregamento, o Claude Code grava as declarações da sua versão em .claude-plugin/types/, e elas são a referência oficial da API.
  • Leia propriedades em e.props. Valores como hasSurvey e bodyColumns ficam ali, e não no próprio evento.
  • Planeje para o recarregamento. Como cada salvamento executa register de novo, mantenha os dados em $.state.
  • Consulte o log. Quando um desenho não aparece, execute claude --debug e procure árvores que não passaram na validação.
  • Regere os tipos após atualizar. Depois de um upgrade do Claude Code, o comando /plugin-types recria as declarações para a versão instalada.

Por fim, lembre que a API ainda pode mudar entre versões. Por isso, confie sempre nas declarações de tipos geradas para o seu build, e não em exemplos antigos.

Se você pretende usar Claude Code mods em agentes mais críticos, vale também estruturar uma rotina de avaliação. O artigo sobre dez evals para agentes de IA mostra como medir o comportamento de forma sistemática.

Conclusão

Os Claude Code mods transformam a ferramenta em uma plataforma extensível, na qual você decide o que observar, o que bloquear e o que mostrar. Como cada mod é um plugin comum, criar, testar e distribuir segue um fluxo já conhecido.

Na prática, os melhores mods nascem de perguntas simples do dia a dia. Quanto do contexto já usei, o que este comando vai apagar e o que o agente acabou de mudar são exemplos de dúvidas que viram ferramentas.

Agora é a sua vez: comece pelo Token Weather, valide com claude plugin validate e evolua a partir das suas próprias necessidades. Para aprofundar, consulte a documentação oficial do Claude Code.

FAQ

Preciso ativar alguma opção para usar mods?

Não. A partir do Claude Code 2.1.287, os mods já vêm ativados por padrão.

Qual a diferença entre um mod e um hook de configuração?

O hook de configuração executa um comando de shell por evento. Já o mod carrega uma vez, mantém estado e chama recursos internos da ferramenta.

Posso escrever mods em TypeScript?

Sim. O módulo pode ser JavaScript ou TypeScript, e JSX também funciona com h como factory.

Mods funcionam no modo não interativo?

Os hooks de eventos funcionam, mas a interface depende da superfície. Exemplos da comunidade indicam que nada é desenhado no modo claude -p.

É seguro instalar mods de terceiros?

Um mod roda com o mesmo acesso do Claude Code. Por isso, leia o código e use claude plugin validate para conferir as capacidades antes de instalar.