npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@air-bizapps/crucible-cli

v1.5.0

Published

Vibe coding CLI conectado ao seu LiteLLM proxy

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 (um git pull que muda o que o arquivo concede repergunta).
  • Escrever fora do projeto--add-dir//add-dir autorizam outros diretórios, com veto para home/raiz/ancestrais.
  • Ler a webfetch_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/plan explora só-leitura; /go executa o plano aprovado.
  • Skills e hooks — instruções reusáveis em markdown; comandos de shell em eventos (pos-escrita, pos-turno).
  • Sub-agentesspawn_subagents roda até 4 filhos em paralelo, com tipo explore/plan (só leitura) ou general (herda permissões). Ctrl+C aborta o pai e a fila.
  • Denydeny no settings e --deny na CLI vencem allow e vencem allow-all.
  • Bash em backgroundbash com background: true não sequestra o turno; await_task espera o resultado.
  • Sessões e rewind — conversa gravada por turno, /resume, e /rewind que desfaz arquivos e/ou conversa.

Instalação

Pré-requisitos, antes de clonar:

  • 2FA na conta GitHub. A org air-bizapps exige autenticação de dois fatores para acessar o repositório — sem isso, o git clone falha 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

crucible

No 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@latest

A 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 proxy

Eventos 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 o result de erro; stdout contém 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_KEY no ambiente vencem o config.json, campo a campo. Sem credencial: result com errorCode:"auth" e exit 1 — nunca abre login.
  • Permissões: settings aplica as regras do .crucible/settings.json confiado e nega o que pediria confirmação (vira tool_result com isError:true); allow-all aprova tudo. Headless nunca pergunta.
  • Sessões: mesma store da TUI — um run headless pode ser retomado na TUI e vice-versa. --resume-session com id inexistente devolve errorCode:"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: costUsd vem do pricing do /model/info do LiteLLM; null quando o proxy não informa preço. turns conta 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; o system/init e o result sempre carregam o modelo resolvido (o real que atendeu o pedido, nunca crucible:auto) — um adapter que consome o NDJSON não precisa saber que o AUTO existe.
  • Exit codes: 0 sucesso, 1 erro. crucible --version imprime 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:

  1. Override completo — os três tiers declarados em auto no config.json (o exemplo acima). Palavra final do usuário, sem nenhuma chamada de rede.
  2. /model/info — sempre tentado, mesmo com override completo (é grátis quando a chave tem acesso).
  3. Cache em disco — preços de uma sondagem anterior, lidos de ~/.config/crucible/precos.json.
  4. 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:

  1. Dar à chave acesso a /model/info ou a /v1/chat/completions sem streaming (o suficiente para a sondagem funcionar).
  2. Declarar os três tiers explicitamente em auto no config.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_file com argumento acima de 48 KB retornam erro imediato instruindo a dividir (write_file aceita append: true para as continuações). Feedback de segundos em vez de minutos de stall.
  • Teto de saída por chamada (maxOutputTokens no config.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.json

Se 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 review

A 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 review

explore/plan rodam só com tools de leitura; general é o turno normal. Os passos compartilham a mesma sessão.

ACP

crucible agent stdio

JSON-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 com write(**) 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ário automodel (id diferente de auto, 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/url de 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
crucible

E confira cada item:

Básico

  1. Splash aparece com logo colorido, versão e dicas.
  2. Sem config → fluxo de login pede URL e key; key errada mostra erro; key certa entra no chat.
  3. /model lista os modelos reais do proxy; seleção persiste (sair e reabrir mantém).
  4. /modelAUTO; um pedido curto (lista os testes) e um profundo (refatora o loop) usam modelos diferentes, visíveis na StatusBar; /cost mostra os dois.
  5. Pedir "crie um arquivo hello.txt com 'oi'" → PermissionPrompt aparece; y cria o arquivo.
  6. Pedir de novo com a → regra aparece em .crucible/settings.json; próxima escrita no mesmo dir não pergunta.
  7. /help, /clear, /compact, /cost, /exit funcionam; /cost mostra tokens (e custo, se o proxy informar preço).
  8. Ctrl+C durante um turno interrompe; Ctrl+C 2x sai.

Confiança por conteúdo

  1. Adicione uma regra ao .crucible/settings.json à mão e reabra → o prompt de confiança reaparece mostrando o conteúdo; n ignora o arquivo, s confia. Edite de novo → repergunta.

Web e MCP

  1. Pedir "leia https://example.com" → fetch_url pede confirmação com a URL completa; o conteúdo volta como texto.
  2. Declare um servidor MCP em config.json (ou no settings) → /mcp lista tools/resources/prompts; uma tool MCP pede confirmação; um prompt vira /mcp__servidor__nome.

Plan mode, skills, hooks

  1. /plan → o status mostra [plan] e o agente não escreve; /go executa o plano.
  2. Crie .crucible/skills/revisar.md com um frontmatter description/skills lista, /revisar dispara, e o modelo pode carregá-la sozinho.
  3. 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

  1. Pedir "use subagentes para explorar src/ e docs/ em paralelo" → spawn_subagents pede y, mostra o andamento e consolida os resultados.
  2. Após algumas escritas, /rewind volta arquivos e/ou conversa a um turno anterior; --continue/--resume retomam a sessão.