@air-bizapps/crucible-cli
v1.5.0
Published
Vibe coding CLI conectado ao seu LiteLLM proxy
Keywords
Readme
Crucible CLI
CLI de vibe coding conectado ao seu proxy LiteLLM. Um harness de coding completo no terminal: o agente lê e edita arquivos, roda comandos, baixa URLs, fala com servidores MCP, planeja antes de agir e delega tarefas a sub-agentes paralelos — tudo atrás de um modelo de permissões y/n/always com confiança ligada ao conteúdo do projeto.
Recursos, em uma olhada:
- Permissões e confiança — leituras livres; escritas, bash, fetch e tools MCP pedem confirmação; regras persistidas em
.crucible/settings.json; a confiança liga ao conteúdo que você viu, não ao diretório (umgit pullque muda o que o arquivo concede repergunta). - Escrever fora do projeto —
--add-dir//add-dirautorizam outros diretórios, com veto para home/raiz/ancestrais. - Ler a web —
fetch_url(HTML → texto) e busca via servidor MCP, com a chave no servidor, não no CLI. - MCP — servidores com tools, resources e prompts, globais ou por projeto.
- Plan mode —
/planexplora só-leitura;/goexecuta o plano aprovado. - Skills e hooks — instruções reusáveis em markdown; comandos de shell em eventos (
pos-escrita,pos-turno). - Sub-agentes —
spawn_subagentsroda até 4 filhos em paralelo, com tipoexplore/plan(só leitura) ougeneral(herda permissões). Ctrl+C aborta o pai e a fila. - Deny —
denyno settings e--denyna CLI vencem allow e vencemallow-all. - Bash em background —
bashcombackground: truenão sequestra o turno;await_taskespera o resultado. - Sessões e rewind — conversa gravada por turno,
/resume, e/rewindque desfaz arquivos e/ou conversa.
Instalação
Pré-requisitos, antes de clonar:
2FA na conta GitHub. A org
air-bizappsexige autenticação de dois fatores para acessar o repositório — sem isso, ogit clonefalha com "You must set up two-factor authentication" mesmo com uma chave SSH válida cadastrada. Configure em github.com/settings/security.Chave SSH cadastrada. Clonar por SSH sem chave pública associada à conta falha com
Permission denied (publickey). Adicione a sua em github.com/settings/keys (ou clone por HTTPS com um token).git clone [email protected]:air-bizapps/crucible-cli.git cd crucible-cli npm install && npm run build && npm link
Em instalações de Node "de sistema" (installer oficial, sem nvm/volta), o diretório global do npm pertence ao root e o npm link acima falha com EACCES; rode sudo npm link. Não contorne com npm link --prefix <caminho> — a flag muda o prefixo global e cria um bin//lib/ falsos dentro do próprio clone (não afeta o /usr/local de verdade): o comando roda sem erro, ainda imprime "added 1 package", e parece ter funcionado sem ter funcionado.
Uso
crucibleNo primeiro uso, o login já vem com a URL do proxy padrão (https://llm.twinforge.webjump.ai) preenchida — basta dar Enter e informar sua API key (sk-...). Para apontar a outro proxy, edite o campo antes de confirmar; o default fica em src/defaults.ts. A config fica em ~/.config/crucible/config.json.
Atualização
npm i -g não atualiza sozinho. Na abertura, o CLI consulta o registro npm (uma vez a cada 24h, com cache em ~/.config/crucible/atualizacao.json) e avisa quando há versão nova:
⚠ 1.5.0 disponível (você está na 1.4.0) · npm i -g @air-bizapps/crucible-cli@latestA consulta não atrasa a abertura e é silenciosa em qualquer falha (offline, timeout, registro fora do ar). CRUCIBLE_NO_UPDATE_CHECK=1 desliga a checagem.
Modo headless
Para orquestradores e CI (ex.: o adapter crucible_local da plataforma TwinForge), o CLI roda sem TTY:
crucible -p "corrija os testes" # texto final em stdout
crucible -p "..." --output-format stream-json # NDJSON: 1 evento por linha
crucible -p "..." --model gpt-5.2 --resume-session <id>
crucible -p "..." --model auto # harness escolhe o modelo pelo tier do pedido
crucible -p "..." --permission-mode allow-all # default: settings
crucible --prompt-file pedido.md # prompt longo sem lutar com a shell
crucible -p "..." --cwd /outro/repo
crucible -p "..." --tools read_file,grep,ls # allowlist de tools builtin
crucible -p "..." --disallowed-tools bash
crucible -p "..." --allow 'bash(npm test:*)' --deny 'bash(rm:*)'
crucible -p "..." --max-turns 20 # teto de passos deste run (vence config.maxSteps)
crucible models --json # ids de modelos do proxyEventos do stream-json (primeiro system/init, último sempre result — exceção: falha antes do run começar, como credencial ausente ou sessão desconhecida, emite só o result de erro; stdout contém só JSON — avisos vão para stderr):
{"type":"system","subtype":"init","sessionId":"...","model":"...","cwd":"..."}
{"type":"text","text":"..."}
{"type":"tool_use","name":"write_file","input":{"path":"..."}}
{"type":"tool_result","name":"write_file","isError":false,"output":"<truncado>"}
{"type":"result","subtype":"success","sessionId":"...","model":"...","usage":{"inputTokens":0,"outputTokens":0},"costUsd":0.0123,"turns":1}- Credenciais:
CRUCIBLE_BASE_URL/CRUCIBLE_API_KEYno ambiente vencem oconfig.json, campo a campo. Sem credencial:resultcomerrorCode:"auth"e exit 1 — nunca abre login. - Permissões:
settingsaplica as regras do.crucible/settings.jsonconfiado e nega o que pediria confirmação (viratool_resultcomisError:true);allow-allaprova tudo. Headless nunca pergunta. - Sessões: mesma store da TUI — um run headless pode ser retomado na TUI e vice-versa.
--resume-sessioncom id inexistente devolveerrorCode:"unknown_session"e exit 1. - Progresso: durante o turno, um evento
{"type":"progress","outputTokens":N}sai a cada 30 s — sinal de vida para o orquestrador (parsers que não o conhecem podem ignorar o tipo com segurança). - Custo:
costUsdvem do pricing do/model/infodo LiteLLM;nullquando o proxy não informa preço.turnsconta os turnos desta invocação (um-p= 1), não o acumulado da sessão. - Modelo AUTO:
--model auto(ver "Modo AUTO") vale também no headless; osystem/inite oresultsempre carregam o modelo resolvido (o real que atendeu o pedido, nuncacrucible:auto) — um adapter que consome o NDJSON não precisa saber que o AUTO existe. - Exit codes:
0sucesso,1erro.crucible --versionimprime a versão real do pacote (o adapter valida versão mínima por ela). - Limitação: prompt começando com
-é interpretado como flag (proteção contra flag engolir flag) — comece com espaço ou reformule.
Comandos
| Comando | Descrição |
|---|---|
| /model | Selecionar o modelo (lista do LiteLLM) ou AUTO, em que o harness escolhe por turno |
| /login | Reconfigurar proxy/key |
| /clear | Limpar a conversa |
| /compact | Resumir a conversa para liberar contexto |
| /init | Gerar o CRUCIBLE.md do projeto |
| /resume | Retomar uma sessão anterior deste diretório |
| /rewind | Voltar arquivos e/ou conversa a um turno anterior |
| /add-dir | Autorizar escrita em outro diretório (sem argumento, lista as raízes ativas) |
| /permissions | Ver e gerenciar as regras de permissão do projeto (add/remove, auto on\|off para a sessão) |
| /mcp | Listar servidores MCP, tools e prompts |
| /cost | Tokens e custo estimado da sessão (preço vem do proxy, quando informado); em AUTO, detalha por modelo usado |
| /precos | Ver a tabela de preços em uso, com a origem de cada um e a data da última sondagem (/precos sondar força uma nova sondagem e regrava o cache) |
| /context | Uso da janela de contexto por categoria (system, histórico, tools, skills) |
| /plan | Entrar no modo de planejamento (só leitura) |
| /go | Aprovar o plano e executar (aceita ajuste: /go só a parte 1) |
| /skills | Listar as skills disponíveis |
| /hooks | Listar os hooks configurados |
| /novidades | Ver as novidades desta versão (a abertura mostra as três primeiras) |
| /help | Ajuda |
| /exit | Sair |
Modo AUTO
/model tem uma entrada extra, AUTO: em vez de fixar um modelo, o harness escolhe um a cada turno.
Como o tier é escolhido. Uma heurística local (sem chamada extra ao modelo) classifica o pedido em rapido, equilibrado ou profundo, nessa ordem de prioridade: /plan ligado sempre vira profundo (é declaração explícita do usuário, não palpite sobre texto); palavras-chave em PT/EN denunciam a tarefa (refatora, investiga, arquitetura, debug... → profundo; lista, mostra, roda... → rápido, só quando o pedido também é curto e de uma linha); sem keyword, o tamanho decide (acima de ~600 caracteres ou 8 linhas vira profundo). Sem nenhuma regra, o pedido cai em equilibrado.
De onde vêm os modelos de cada tier. Os três tiers saem do preço de cada modelo (de onde esse preço vem, ver "Se o proxy não expõe preço" abaixo): o mais barato vira rapido, o mais caro vira profundo, e equilibrado é o modelo mais próximo da média geométrica dos dois extremos (a aritmética colaria o meio perto do topo, porque preço de LLM varia em ordens de magnitude). Dá para sobrepor qualquer tier no config.json:
{
"auto": {
"rapido": "gpt-5-mini",
"equilibrado": "gpt-5",
"profundo": "gpt-5.2"
}
}Um tier ausente no override continua derivado do preço; um id que não bate com nenhum modelo já visto em /model/info vira aviso (auto.<tier>: modelo "..." não está no catálogo do proxy) e cai no derivado — mas essa validação só roda quando /model/info devolveu algum modelo. Se o catálogo veio vazio, não há nada contra o que validar, e o override é aceito como digitado, sem aviso: um id que você escreveu é evidência melhor que um catálogo que o CLI não conseguiu buscar, e um typo aparece no proxy no primeiro uso — sinal imediato.
Preço zero não conta. No LiteLLM, custo zero por token quase sempre significa "não configurado", não "grátis" — por isso a derivação de tiers ignora qualquer modelo com inputCostPerToken igual a zero (ou ausente): um modelo de 550B nessas condições não deveria virar o tier rapido por acidente. Isso só tira o modelo da derivação automática do AUTO; ele continua listado e selecionável à mão no /model, e o /cost continua contando esse mesmo zero como custo real — a mesma constante lida de duas formas opostas por dois consumidores diferentes, de propósito.
O tier não é a última palavra. O modelo do tier ainda passa por um filtro de contexto antes de o turno sair: se a janela dele não comporta o histórico atual mais uma folga de 8k tokens, o AUTO sobe para o modelo mais barato que couber (e, se nenhum couber, para o de maior janela). O tier anunciado não muda junto — isso é restrição, não classificação —, então numa conversa já longa o transcript pode dizer AUTO: rapido → <modelo caro> e o /cost pode aparecer dominado por um modelo caro numa sessão de pedidos triviais. Subagente não sofre esse efeito: o contexto dele é a própria task, não o histórico do turno que o disparou.
Se o proxy não expõe preço, o CLI sonda sozinho. O preço de cada modelo vem de uma cadeia em camadas, cada uma preenchendo só o que a anterior deixou vazio:
- Override completo — os três tiers declarados em
autonoconfig.json(o exemplo acima). Palavra final do usuário, sem nenhuma chamada de rede. /model/info— sempre tentado, mesmo com override completo (é grátis quando a chave tem acesso).- Cache em disco — preços de uma sondagem anterior, lidos de
~/.config/crucible/precos.json. - Sondagem (probe) — só para o que sobrou sem preço depois das três camadas acima.
A sondagem faz duas chamadas não-streaming e curtas por modelo, com a saída limitada a 1 token (o header de custo, x-litellm-response-cost, não existe em respostas em stream): os dois pontos, com contagens de tokens diferentes entre si, bastam para resolver um sistema de duas equações e chegar ao preço de entrada e ao de saída. Ela acontece uma vez por modelo por proxy — o resultado fica em ~/.config/crucible/precos.json, indexado pela URL do proxy (o mesmo id de modelo em outro proxy pode custar outra coisa), e nas próximas vezes o AUTO lê o cache em vez de sondar de novo. Apagar esse arquivo é seguro: ele só reconstrói os preços na próxima sondagem, ao custo de pagar de novo as chamadas que já tinha economizado. /precos mostra a tabela em uso e de onde veio cada preço; /precos sondar força uma nova sondagem e regrava o cache, ignorando o que já estava lá.
Neste projeto, contra o proxy próprio, a chave configurada recebe HTTP 403 (Virtual key is not allowed to call this route) em /model/info — mas isso deixou de ser um beco sem saída: a cadeia cai para o cache e, na primeira vez, para a sondagem, e o AUTO deriva os três tiers sozinho, sem exigir override nenhum. A sondagem custa uma fração de centavo (duas chamadas curtas por modelo sem preço) e avisa antes de rodar — entrada de sistema na TUI, stderr no headless.
Só numa situação mais extrema o AUTO continua de fato indisponível: se nem a sondagem conseguir preço para nenhum modelo (proxy fora do ar, ou a chave também sem acesso a /v1/chat/completions). Nesse caso o picker mostra a entrada AUTO desabilitada com o motivo, e o transcript avisa que o AUTO não está rodando e qual modelo está sendo usado no lugar: gpt-5, uma constante do próprio CLI (DEFAULT_MODEL, em src/defaults.ts) e não o default do seu proxy. Se esse modelo não existir no proxy, quem não trava é o CLI: o turno em si morre na chamada de chat, com o erro do proxy no transcript. As duas saídas abaixo continuam válidas para esse caso extremo:
- Dar à chave acesso a
/model/infoou a/v1/chat/completionssem streaming (o suficiente para a sondagem funcionar). - Declarar os três tiers explicitamente em
autonoconfig.json(o exemplo acima) — sempre aceito, sem I/O nenhum, mesmo com o catálogo vazio.
Só um detalhe: o override precisa cobrir os três tiers para dispensar preço por completo. Cobertura parcial não fecha o mapa — um tier ausente do override é completado pela cadeia acima (cache ou sondagem) e, só se ela também falhar, some, deixando o AUTO indisponível.
Modelo que não responde. Se um modelo não responde — watchdog de inatividade (idleTimeoutSec) ou falha de chamada ao proxy — ele é marcado como suspeito e pulado no turno seguinte, sem retry automático dentro do turno que falhou. Se todos os modelos dos tiers estiverem suspeitos, a lista é ignorada — tentar um modelo que talvez tenha voltado é melhor que travar a sessão.
Onde mais o AUTO entra. A compactação da conversa (/compact e a automática a 90%) sempre roda no tier rapido — resumir é mecânico, não precisa do modelo mais caro. Cada subagente de spawn_subagents é classificado pela própria task, independente do tier do turno que o disparou. E no headless, --model auto ativa o mesmo mecanismo (veja "Modo headless").
O [auto] da StatusBar é outra coisa. A StatusBar pode mostrar duas marcas parecidas ao mesmo tempo, e não têm relação uma com a outra: [auto] em amarelo é auto-aprovação (tecla s, /permissions auto on|off) — nada a ver com escolha de modelo. Já AUTO→gpt-5 é o modo AUTO deste modelo escolhendo por turno. As duas podem aparecer juntas na mesma linha.
Contexto
O CLI mostra o uso da janela de contexto na barra de status. Aos 80% ele avisa; acima de 90% compacta a conversa automaticamente antes do próximo turno (preservando os últimos 2 turnos literais). /compact força a compactação a qualquer momento. /context quebra a janela por categoria (system prompt, histórico, tools, skills) — estimado a ~4 chars/token, mais o usage medido do último turno. O raciocínio (thinking) do modelo não entra no histórico: não é re-enviado nem gravado na sessão.
Shift+Tab cicla o modo da sessão: Normal → Plan → Auto-aprovação → Normal. Não age no meio de um turno nem de um prompt de permissão.
Instruções de projeto: o system prompt carrega CLAUDE.md, AGENTS.md e CRUCIBLE.md da raiz (nessa ordem — o último vence em conflito). /init continua gerando só o CRUCIBLE.md.
Convergência
O agente é pressionado a concluir cedo: releitura idêntica dentro do turno é negada (após uma mutação, reler volta a valer), e quando o orçamento de passos esgota (maxSteps no config.json, default 50) o turno não morre calado — o CLI força uma última chamada sem tools em que o modelo entrega sua melhor conclusão, rotulando o que está verificado e o que é hipótese.
Anti-stall
Proteções contra geração monolítica e streams mudos, todas impostas pelo harness:
- Escrita em partes:
write_file/edit_filecom argumento acima de 48 KB retornam erro imediato instruindo a dividir (write_fileaceitaappend: truepara as continuações). Feedback de segundos em vez de minutos de stall. - Teto de saída por chamada (
maxOutputTokensnoconfig.json, default 32768): geração gigante numa chamada só fica impossível; quando o corte acontece, o modelo recebe a instrução de continuar em partes (no máximo 2 realimentações por turno). - Watchdog de inatividade (
idleTimeoutSec, default 120): stream sem nenhum sinal é abortado com erro claro — pausado durante a execução de tools (um build longo não é travamento). - Retries visíveis: quando o SDK repete uma chamada ao proxy (429/5xx), o CLI avisa — entrada de sistema na TUI, stderr no headless.
Nota de operação: sessões longas re-enviam o histórico a cada turno — habilite prefix caching no proxy (LiteLLM/vLLM) para não pagar o prefixo repetido; a auto-compactação a 90% complementa.
Configurações do config.json
| Campo | Default | Efeito |
|---|---|---|
| maxSteps | 50 | orçamento de passos por turno (esgotar dispara a última palavra) |
| maxOutputTokens | 32768 | teto de tokens de saída por chamada |
| idleTimeoutSec | 120 | watchdog de inatividade do stream |
| auto | — | override por tier do modo AUTO (rapido/equilibrado/profundo); tier ausente é derivado do preço do /model/info (ver "Modo AUTO") |
Permissões
Leituras são livres. Escritas e bash pedem confirmação com 5 teclas: y (uma vez), a (sempre — salva regra em .crucible/settings.json), c (sempre para o comando base, só em bash — ex. bash(npm:*) em vez de bash(npm test:*)), s (auto-aprova o resto da sessão, sem gravar nada em disco), n (nega). a e c somem quando não há o que persistir (ver "Escrever fora do projeto" para o caso do diretório vetado). Regras: bash(npm test:*), write(src/**), fetch(example.com), mcp(busca:web_search).
deny é hard limit: vence allow e vence --permission-mode allow-all. Vale mesmo sem o projeto confiado (não é concessão). --deny/--allow na CLI somam ao settings, sem gravar em disco.
{ "allow": ["bash(npm test:*)"], "deny": ["bash(rm *)", "write(**/.env)"] }Regras write(...) são sempre confinadas ao diretório do projeto: caminhos absolutos ou com ../ que saem do projeto nunca são aprovados automaticamente, nem por write(**). Deny write alcança também as raízes adicionais.
⚠️ Aviso de segurança: .crucible/settings.json é local ao projeto
O arquivo .crucible/settings.json fica dentro do repositório. Um repositório que você clonou pode trazer o próprio arquivo com regras como {"allow": ["bash(sh:*)", "write(**)"]} e pode declarar servidores MCP (que podem executar comandos stdio) — por isso, na primeira execução num diretório cujo settings concede alguma coisa, o CLI mostra o conteúdo do arquivo e pergunta se você confia nele.
A confiança não é "uma vez por diretório": ela liga ao conteúdo específico que você viu — as regras, os diretórios autorizados e os servidores MCP declarados. Se um git pull (ou qualquer edição do arquivo) muda o que o settings concede, o prompt reaparece antes de qualquer concessão valer, pedindo que você confirme o novo conteúdo. Primeira abertura após esta versão: em projetos já confiados, o CLI repergunta uma vez (migração interna do formato do registro).
O prompt também exibe qualquer servidor MCP declarado no arquivo. Enquanto você não confiar, o arquivo não concede nada; recusar não fecha o CLI, só ignora o arquivo. A resposta fica gravada fora do repositório, então um clone não consegue se pré-aprovar.
Antes de confiar num repositório de terceiros, inspecione o arquivo:
cat .crucible/settings.jsonSe não reconhecer alguma regra ou servidor MCP, apague o arquivo ou responda n no prompt.
Escrever fora do projeto
O projeto é o diretório em que você chamou o crucible. Escrever fora dele pede confirmação; apertar a autoriza aquele diretório para sempre, gravando em additionalDirectories no .crucible/settings.json.
Também dá para autorizar na abertura, com crucible --add-dir /outro/repo (a flag pode repetir), ou de dentro da sessão com /add-dir /outro/repo. /add-dir sem argumento lista as raízes ativas.
Três coisas que o CLI recusa autorizar, porque a autorização valeria para tudo abaixo delas: a sua home, a raiz do sistema, e qualquer diretório acima do projeto. Nesses casos só o a some do prompt (não há o que persistir); y, s e n continuam disponíveis.
Regras write(...) continuam valendo só dentro do projeto: um write(**) no seu settings não alcança os diretórios adicionados.
Arquivos escritos fora do projeto entram no snapshot do turno como os de dentro, então o /rewind também os desfaz.
Ler URLs
A tool fetch_url baixa uma URL http(s) e entrega o conteúdo como texto (HTML é convertido). Cada fetch pede confirmação mostrando a URL completa; a grava a regra fetch(host) — só aquele host, com a porta quando houver (fetch(localhost:3000) não cobre localhost:8080), sem subdomínios.
Redirecionamento para outro host não é seguido automaticamente: a resposta indica o destino e um novo fetch_url passa pela permissão de novo. Limites: 15s de timeout, 2 MB de download, só conteúdo de texto.
Plan mode
/plan coloca o agente em modo de planejamento: bash e spawn somem; a única escrita permitida é .crucible/plan.md. Quando o plano estiver bom, /go abre o review: j/k navega, c comenta a linha, a aprova e executa, s manda revisar (com os comentários), Esc cancela. /go sem a parte de docs leva o ajuste junto na aprovação. Sem o arquivo, /go executa o que ficou na conversa. O status mostra [plan] enquanto o modo está ativo.
Tools MCP marcadas pelo servidor como só-leitura (readOnlyHint) continuam disponíveis no modo — e continuam pedindo confirmação, como sempre.
Skills
Skills são instruções reusáveis em markdown: um .md solto ou um diretório com SKILL.md (o diretório vence o arquivo de mesmo nome). Crie em ~/.config/crucible/skills/ (globais) ou .crucible/skills/ (do projeto, que vence colisões de nome). Para puxar de um repo de terceiros (formato SKILL.md):
crucible skill install anthropics/skills --path frontend-design
crucible skill install ./minha-skill # caminho local
crucible skill install owner/[email protected] --path skills/review --project
crucible skill list
crucible skill update # reinstala todas com origem gravada
crucible skill update review
crucible skill remove reviewA instalação só copia o diretório da skill (SKILL.md, references/, scripts/). Não roda scripts, não ativa hooks nem MCP. Se a fonte tiver várias skills, --path escolhe uma. --force substitui. --project grava em .crucible/skills/ do cwd. O SHA do clone (quando houver git) fica em .install.json; skill update reinstala a partir da origem gravada.
Workflows
Roteiros nomeados em .crucible/workflows/<nome>.yaml (ou .json; o projeto vence o global em ~/.config/crucible/workflows/):
name: review
steps:
- type: explore
prompt: Diff contra main; liste riscos.
- type: plan
prompt: Plano mínimo para os riscos reais.
crucible workflow list
crucible workflow run reviewexplore/plan rodam só com tools de leitura; general é o turno normal. Os passos compartilham a mesma sessão.
ACP
crucible agent stdioJSON-RPC 2.0 no stdio (initialize, session/new, session/load, session/prompt, session/cancel). Para editores que falam ACP. Sem servidor WebSocket.
O frontmatter leva uma description:
---
description: Revisa o diff atual com o checklist do time
---
Instruções da skill...O modelo vê as descrições e carrega a skill sozinho quando a tarefa combina (load_skill); você também pode disparar direto: /revisar (o nome do arquivo vira comando) — com argumentos, /revisar só o gate. /skills lista tudo.
Skills do projeto carregam sem o prompt de confiança — são texto de prompt, como o CRUCIBLE.md; não executam nada.
Hooks
Hooks são comandos de shell que rodam em eventos, definidos em hooks no config global (~/.config/crucible/config.json) ou no settings do projeto (.crucible/settings.json, atrás do prompt de confiança):
{
"hooks": {
"pos-escrita": [{ "command": "npx prettier --write \"$CRUCIBLE_FILE\"" }],
"pos-turno": [{ "command": "afplay /System/Library/Sounds/Glass.aiff" }],
"pre-bash": [{ "command": "echo \"$CRUCIBLE_COMMAND\" | grep -qv rm || exit 1" }]
}
}pos-escrita roda após cada arquivo escrito (com $CRUCIBLE_FILE no ambiente); pos-turno, ao fim de cada turno. São observadores: rodam em segundo plano, e só aparecem se falharem. pre-bash é diferente: roda antes do comando, com $CRUCIBLE_COMMAND; exit ≠ 0 veta a execução e devolve o stderr ao modelo. /hooks lista os ativos.
Como hooks executam comandos, os do projeto entram no prompt de confiança — e mudá-los (por um git pull, por exemplo) faz o prompt reaparecer antes de qualquer hook rodar.
O fingerprint de confiança passou a incluir hooks: todo projeto já confiado antes desta versão reabre o prompt uma única vez na primeira abertura seguinte, mesmo sem nenhum hook configurado — é esperado, e confirmar de novo resolve.
Subagentes
O agente pode delegar tarefas independentes a sub-agentes que rodam em paralelo (até 4 por vez) via spawn_subagents — úteis para explorar partes diferentes do projeto ao mesmo tempo. Cada tarefa é uma string (tipo general) ou {prompt, type}:
explore/plan— só leitura (não escrevem mesmo comwrite(**)no settings).general— herda tools e permissões, mas nunca pede confirmação: o que exigiria aprovação ele pula.
O agente principal consolida os resultados. Ctrl+C no turno aborta os filhos em voo e não inicia a fila restante.
Isso significa que, sob uma regra já concedida (por exemplo write(src/**)), filhos general podem escrever nesses caminhos em paralelo sem perguntar por ação — é a mesma autorização que você já deu, aplicada em paralelo. explore/plan não escrevem, regra ou não. Fora do que o settings libera, general nega a ação em vez de perguntar — mas também roda bash, fetch_url e tools MCP já autorizados pelo allow. As escritas via write_file/edit_file entram no /rewind como as suas e disparam os hooks pos-escrita; escritas feitas via bash não entram no /rewind, exatamente como no agente principal.
Ctrl+C aborta o turno principal e os sub-agentes (os que ainda não começaram saem como interrompido). Um bash em background (background: true) segue até você chamar await_task — não é morto pelo abort do turno. Ctrl+B no meio de um bash em primeiro plano faz o mesmo: o comando continua, o turno recebe um task_id e você volta a falar.
Servidores MCP
Declare servidores no config global (~/.config/crucible/config.json) ou no settings do projeto (.crucible/settings.json, atrás do prompt de confiança):
{
"mcpServers": {
"busca": { "url": "https://seu-servidor-mcp.dev", "headers": { "authorization": "Bearer ..." } },
"fs": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/dados"] }
}
}As tools dos servidores aparecem para o agente como mcp__servidor__tool e pedem confirmação; a grava mcp(servidor:tool) — só aquela tool daquele servidor. Resources são lidos via list_mcp_resources/read_mcp_resource (o a da leitura grava mcp(servidor:resources)). Prompts viram comandos: /mcp__servidor__prompt argumentos. /mcp mostra o estado de tudo.
Nome de servidor do projeto que colide com um global é ignorado — o global vence. Servidor que falha na conexão vira aviso; o CLI abre normalmente. A busca na web entra por aqui: aponte um servidor MCP de busca e a chave fica nele, nunca no CLI.
Sessões
A conversa é gravada a cada turno em ~/.config/crucible/projects/<projeto>/, fora do repositório. Retome com crucible --continue (a última deste diretório), crucible --resume (escolhe da lista) ou /resume de dentro da sessão.
Ao retomar, a tela mostra um resumo compacto (sessão retomada — N turnos anteriores) em vez de reconstruir a conversa mensagem a mensagem — mas o histórico inteiro volta para o modelo, só a exibição é resumida. crucible --continue num diretório sem sessão anterior não é erro nem no-op: começa uma sessão nova, avisando (Nenhuma sessão anterior neste diretório; começando uma nova.).
/rewind volta ao estado de um turno anterior e deixa você escolher: só os arquivos, só a conversa, ou os dois. Rewind não desfaz o que comandos bash executaram — migration, install e push permanecem.
Desenvolvimento
npm run dev # roda via tsx
npm test # vitest
npm run typecheck
npm run test:tty # teste ponta a ponta num pty real (exige build antes)Smoke test manual
O build automatizado confirma que dist/cli.js é gerado com o shebang correto e que --version funciona, mas o fluxo interativo completo depende do seu proxy LiteLLM real e não pode ser validado sem credenciais.
A maior parte do checklist abaixo tem versão automatizada: npm run smoke:real (exige build antes e config real em ~/.config/crucible) roda 13 cenários num pty contra o proxy de verdade — gasta tokens e tem latência real, exceto permissions (só mexe em /permissions e no .crucible/settings.json do disco, sem chamar o modelo). SMOKE_ONLY=write,cost roda um subconjunto (ids: write, persist, cost, ctrlc, trust, skill, fetch, subagentes, permissions, auto, automodel, plan, hooks). O que segue manual, e por quê:
- login — precisa de credenciais interativas (URL/key reais); não dá para automatizar sem expor segredo.
/model— selecionar um modelo qualquer da lista mexe na config real do usuário (~/.config/crucible/config.json); a seleção livre fica manual, para não trocar o modelo configurado por engano. O fluxo do AUTO tem cobertura automatizada à parte no cenárioautomodel(id diferente deauto, que já é o de auto-aprovação) porque ele restaura o modelo original da config no final, sucesso ou falha.- MCP — exige um servidor MCP externo já configurado (
command/urlde verdade); não há um genérico para subir no CI/smoke.
Depois de npm link, rode manualmente:
cd /tmp && mkdir -p crucible-smoke && cd crucible-smoke
crucibleE confira cada item:
Básico
- Splash aparece com logo colorido, versão e dicas.
- Sem config → fluxo de login pede URL e key; key errada mostra erro; key certa entra no chat.
/modellista os modelos reais do proxy; seleção persiste (sair e reabrir mantém)./model→AUTO; um pedido curto (lista os testes) e um profundo (refatora o loop) usam modelos diferentes, visíveis na StatusBar;/costmostra os dois.- Pedir "crie um arquivo hello.txt com 'oi'" → PermissionPrompt aparece;
ycria o arquivo. - Pedir de novo com
a→ regra aparece em.crucible/settings.json; próxima escrita no mesmo dir não pergunta. /help,/clear,/compact,/cost,/exitfuncionam;/costmostra tokens (e custo, se o proxy informar preço).- Ctrl+C durante um turno interrompe; Ctrl+C 2x sai.
Confiança por conteúdo
- Adicione uma regra ao
.crucible/settings.jsonà mão e reabra → o prompt de confiança reaparece mostrando o conteúdo;nignora o arquivo,sconfia. Edite de novo → repergunta.
Web e MCP
- Pedir "leia https://example.com" →
fetch_urlpede confirmação com a URL completa; o conteúdo volta como texto. - Declare um servidor MCP em
config.json(ou no settings) →/mcplista tools/resources/prompts; uma tool MCP pede confirmação; um prompt vira/mcp__servidor__nome.
Plan mode, skills, hooks
/plan→ o status mostra[plan]e o agente não escreve;/goexecuta o plano.- Crie
.crucible/skills/revisar.mdcom um frontmatterdescription→/skillslista,/revisardispara, e o modelo pode carregá-la sozinho. - Configure um hook
pos-escrita(ex.:touch /tmp/hook-rodou) → após uma escrita, o hook roda; um hook que falha vira aviso no transcript.
Sub-agentes e rewind
- Pedir "use subagentes para explorar src/ e docs/ em paralelo" →
spawn_subagentspedey, mostra o andamento e consolida os resultados. - Após algumas escritas,
/rewindvolta arquivos e/ou conversa a um turno anterior;--continue/--resumeretomam a sessão.
