Hooks
Hooks são um mecanismo de disparo automático: você diz ao Jarvis Code CLI, com antecedência, "sempre que X acontecer, execute este script". O script roda na sua máquina local e você pode colocar qualquer lógica dentro dele. Casos de uso típicos:
- Interceptação de segurança: antes de o agente executar um comando de shell, verificar se ele contém operações perigosas (como
rm -rf) e bloquear a execução em caso positivo - Notificações no desktop: quando uma tarefa em segundo plano termina, exibir uma notificação do sistema para trazer você de volta a revisar os resultados
- Verificações automáticas: cada vez que o usuário envia uma mensagem, acrescentar automaticamente alguma informação de contexto (como o branch Git atual)
Como os hooks funcionam
Configurar uma regra de hook exige especificar três coisas: em qual evento disparar, quais alvos corresponder e qual script executar.
Quando disparado, o CLI empacota os detalhes do evento (motivo do disparo, nome da ferramenta, conteúdo do comando etc.) em JSON e os passa ao seu script pela entrada padrão (stdin). O script lê essa informação e decide como responder.
A resposta do script é determinada por duas coisas:
- Código de saída:
0significa permitir,2significa bloquear, outros valores diferentes de zero permitem por padrão - Saída padrão (stdout): pode incluir texto explicativo
Mesmo que o script dê erro ou estoure o tempo limite, o CLI não interrompe seu trabalho por isso — esse desenho de "permitir em caso de falha" se chama fail-open, e evita que erros de hook virem bloqueios.
Nota
Justamente por causa do fail-open, hooks são adequados para alertas e interceptação leve, mas não devem ser usados como única barreira de segurança. Para operações realmente de alto risco, conte com as aprovações de permissão e a confirmação manual.
Início rápido: um hook mínimo
O hook a seguir exibe uma notificação cada vez que uma tarefa em segundo plano termina (no macOS é preciso ter o terminal-notifier instalado):
# Escrito em ~/.jarvis-code/config.toml
[[hooks]]
event = "Notification" # Disparo: quando o status de uma tarefa em segundo plano muda
matcher = "task\\.completed" # Só interessam as notificações de conclusão
command = "terminal-notifier -title 'Jarvis Code' -message 'Task done'"Salve a configuração, inicie uma nova sessão e uma notificação aparecerá na próxima vez que uma tarefa em segundo plano terminar.
Configuração
Todas as regras de hook são escritas no array [[hooks]] em ~/.jarvis-code/config.toml, onde cada entrada é uma regra:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
event | string | Sim | Nome do evento de disparo; precisa ser uma das entradas da tabela "Referência de eventos" abaixo |
matcher | string | Não | Uma expressão regular para filtrar os alvos do evento; se omitida, corresponde a todos |
command | string | Sim | O comando de shell a executar no disparo |
timeout | integer | Não | Tempo limite em segundos, de 1 a 600; o padrão é 30 segundos |
O [[hooks]] só aceita esses quatro campos; campos extras fazem o arquivo de configuração falhar ao carregar.
Quando várias regras correspondem ao mesmo evento, todos os hooks correspondentes rodam em paralelo; regras com o mesmo valor de command rodam apenas uma vez.
O diretório de trabalho dos comandos de hook é o diretório de projeto da sessão atual. Em plataformas não Windows, processos de hook ficam em um grupo de processos separado; no tempo limite, um sinal é enviado primeiro para dar chance de limpeza e então o processo é encerrado à força.
Formato dos dados do evento
Cada vez que um hook dispara, o CLI passa ao script, via stdin, a seguinte informação base:
{
"hook_event_name": "PreToolUse",
"session_id": "session_abc",
"session_title": "Fix the login page",
"client_type": "kimi_code_cli",
"cwd": "/path/to/project"
}Eventos específicos também incluem campos adicionais (como nome da ferramenta e conteúdo do comando); veja a referência de eventos abaixo. Todos os nomes de campo usam snake_case.
Nota
O client_type carrega o identificador de plataforma do host que o CLI envia para o serviço upstream, que continua sendo kimi_code_cli. Ele é um identificador de protocolo, não um nome de produto; faça a correspondência literal.
Valores de retorno
Depois que o script sai, o CLI determina a intenção do hook pelo código de saída:
| Código de saída | Significado | Comportamento do CLI |
|---|---|---|
0 | Saída normal, permitir | Continua a execução; o conteúdo de stdout (se houver) pode ser acrescentado ao contexto |
2 | Bloqueio intencional | Interrompe a operação atual; o conteúdo de stderr (impresso via console.error) é usado como motivo do bloqueio |
| Outro diferente de zero | Erro no script | Permite por padrão (fail-open) |
| Tempo limite ou falha | Exceção no script | Permite por padrão (fail-open) |
Você também pode retornar um objeto JSON por stdout para bloquear:
{
"hookSpecificOutput": {
"permissionDecision": "deny",
"permissionDecisionReason": "Use rg em vez de grep"
}
}Quais eventos suportam bloqueio?
Apenas os eventos bloqueáveis (PreToolUse, Stop, UserPromptSubmit) têm valores de retorno que afetam o fluxo principal. Todos os demais são eventos de observação — disparam e seguem em frente; o fluxo principal não é afetado, independentemente do que o script retorne.
Referência de eventos
| Evento | O matcher corresponde a | Suporta bloqueio? | Descrição |
|---|---|---|---|
UserPromptSubmit | O texto enviado pelo usuário | ✓ | Disparado quando o usuário envia uma mensagem; o texto retornado é acrescentado ao contexto; se bloqueado, o modelo não é chamado neste turno |
UserPromptQueued | O texto do prompt enfileirado | — | Disparado quando uma mensagem é enfileirada enquanto um turno ainda roda; o payload inclui prompt_id, prompt e queue_length (apenas observação) |
PreToolUse | Nome da ferramenta | ✓ | Disparado antes de uma chamada de ferramenta (antes das verificações de permissão); a ferramenta não executa se bloqueada |
Stop | String vazia | ✓ | Disparado quando o modelo está prestes a encerrar o turno atual; se bloqueado, uma mensagem pode ser acrescentada para o modelo continuar |
TurnStarted | Tipo de origem do turno (por exemplo user, task, system_trigger) | — | Disparado quando um novo turno começa; o payload inclui turn_id, origin_kind, origin_name e prompt (apenas observação) |
PostToolUse | Nome da ferramenta | — | Disparado depois de uma ferramenta executar com sucesso (apenas observação) |
PostToolUseFailure | Nome da ferramenta | — | Disparado depois de uma ferramenta falhar ou ser bloqueada (apenas observação) |
PermissionRequest | Nome da ferramenta | — | Disparado imediatamente antes de aguardar a aprovação do usuário (apenas observação) |
PermissionResult | Nome da ferramenta | — | Disparado depois de a aprovação ser concluída (apenas observação) |
SessionStart | startup ou resume | — | Disparado depois de uma nova sessão iniciar ou de uma sessão anterior ser retomada; o payload inclui source, model e profile |
SessionEnd | exit ou archive | — | Disparado depois de uma sessão fechar; archive significa que a sessão foi arquivada em vez de encerrada |
SessionHeartbeat | String vazia | — | Disparado a cada 60 segundos enquanto a sessão está viva; o temporizador só roda quando este evento está configurado. O payload inclui uptime_ms (apenas observação) |
SubagentStart | Nome do subagente | — | Disparado antes de um subagente começar a rodar |
SubagentStop | Nome do subagente | — | Disparado depois de um subagente concluir com sucesso (apenas observação) |
TaskStarted | Tipo de tarefa (agent, process ou question) | — | Disparado quando uma tarefa em segundo plano começa; o payload inclui task_id, description e detached (apenas observação) |
StopFailure | Tipo de erro | — | Disparado depois de o turno atual falhar por um erro (apenas observação) |
Interrupt | String vazia | — | Disparado quando o usuário interrompe o turno atual (por exemplo, pressionando Esc); não dispara em tempos limite ou outros abortos programáticos. O Stop não dispara em interrupções, então este evento dispara no lugar. O payload inclui um campo reason (apenas observação) |
PreCompact | manual ou auto | — | Disparado antes de a compactação de contexto começar; valores de retorno são completamente ignorados |
PostCompact | manual ou auto | — | Disparado depois de a compactação de contexto terminar (apenas observação) |
Notification | Tipo de notificação (por exemplo task.completed) | — | Disparado quando o status de uma tarefa em segundo plano muda (apenas observação) |
Exemplo: bloquear comandos de shell perigosos
O hook a seguir verifica o conteúdo do comando antes de o agente chamar a ferramenta Bash e o bloqueia se detectar rm -rf:
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.jarvis-code/hooks/block-dangerous-bash.mjs"
timeout = 5// block-dangerous-bash.mjs
// Lê do stdin os dados de evento passados pelo CLI
let input = '';
process.stdin.on('data', (chunk) => { input += chunk; });
process.stdin.on('end', () => {
const payload = JSON.parse(input); // Analisa os dados do evento
const command = payload.tool_input?.command ?? '';
if (command.includes('rm -rf')) {
// Explica o motivo do bloqueio via stderr; código de saída 2 significa bloquear
console.error('Comando perigoso detectado, bloqueado');
process.exit(2);
}
// Saída normal (código 0) significa permitir
});Depois de bloquear, o Jarvis Code CLI escreve o motivo do bloqueio de volta no contexto, e o modelo pode usar isso para escolher uma alternativa mais segura.
Nota
Este exemplo apenas demonstra o mecanismo de bloqueio — não é um analisador de segurança de nível de produção. Cenários reais são melhor atendidos por listas de permissão, ou por um analisador de shell dedicado que trate aspas, expansão de variáveis e sequências com vários comandos.
Próximos passos
- Configuração — referência completa de campos de
[[hooks]]noconfig.toml - Agentes e subagentes — use o evento
SubagentStoppara disparar notificações após um subagente concluir