apps/cli é a cara do motor: 33 comandos top-level, 47 formas documentadas — todos nascidos do MESMO padrão de três arquivos (args.ts → commands.ts → index.ts) e da MESMA doutrina: dry-run por default, gasto real só atrás de dupla trava. E, desde o PR #158, até os números desta frase são derivados da fonte — nunca digitados à mão.
Todo comando percorre o mesmo caminho. Em apps/cli/src/args.ts: o enum canônico commandSchema (args.ts:18–52 — os 33 nomes), o PARSE_OPTIONS do parseArgs nativo do Node (args.ts:839 — zero dependências novas), o toRecord que casa posicionais + flags (args.ts:993) e um <comando>ArgsSchema Zod por comando, tudo unificado por parseCliArgs (args.ts:1248). Em commands.ts: um run<Comando> por comando, devolvendo Result<T, Error> — nunca lançando. Em index.ts: o dispatch, o USAGE exportado (index.ts:68) e os exit codes — uso inválido é EXIT_USAGE = 2 (index.ts:225), com o USAGE impresso no stderr.
Pense como… a recepção de um hospital: ninguém entra direto na sala de cirurgia; todo mundo passa pela triagem (Zod), ganha um prontuário tipado (Args) e só então é encaminhado ao especialista (run<Comando>). Onde quebra: recepção humana improvisa; parseCliArgs recusa QUALQUER coisa fora do schema — e é isso que você quer.
A regra da casa: um comando digitado errado, incompleto ou por curiosidade custa $0 e não muda nada. O caminho barato/seguro é o DEFAULT; rede, escrita e gasto são opt-in explícito, cada um com sua flag:
| Comando | Default ($0, sem efeito) | Opt-in explícito |
|---|---|---|
import | Preview do que seria importado | --write grava (idempotente) |
import --distill | Falha fechado sem rede | --online chama; --write grava |
employee run | Imprime o prompt e PARA | --online chama o modelo |
marketing video/ad/campaign | Preview de custo (estimate FIRST) | --approve E --yes juntos |
course | Byte-idêntico, só SVG | --images (+ --yes se pago) |
distill | --offline = hermético $0 | --budget/--learnings-budget com cap |
approve e yes são booleans separados com default(false) e docstrings espelhadas — "Operator approval for a PAID video run; needs --yes too" (args.ts:415–418). Só a CONJUNÇÃO libera a geração paga; qualquer uma sozinha continua no preview de custo. É a mesma lógica de uma chave dupla de cofre: aprovação (decisão) e confirmação (execução) são atos distintos, e um script que passe uma flag por engano não queima orçamento.De onde vêm "33 comandos, 47 formas"? Não de alguém contando: o USAGE exportado de index.ts:68 é a fonte única, e um script os deriva dela. A história é o motor se auto-corrigindo: o digest #1 do failure-historian elegeu o drift de contagem como pior ofensor recorrente → propôs "derivar contagens da fonte" → nasceu scripts/derive-counts.mjs (PR #158) → e a PRIMEIRA execução já pegou drift vivo: os docs diziam 32 comandos; a fonte dizia 33/47. Contagem manual não sobrevive a um repo com 200 arquivos de teste e 1.834 testes — regra da casa: se é número de superfície, derive.
args.ts (+ PARSE_OPTIONS/toRecord); ② run<Comando> em commands.ts devolvendo Result; ③ dispatch em index.ts; ④ atualizar o USAGE; ⑤ testes em commands.test.ts/e2e.test.ts; ⑥ documentar em CLAUDE.md e README. Pular o ④ é criar o próximo drift que o derive-counts vai denunciar.| Código | Significado | Quem o produz |
|---|---|---|
| 0 | Sucesso — o run<Comando> retornou ok | caminho feliz do dispatch |
| 1 | Falha de execução — retornou err (mensagem no stderr) | o próprio comando, fail-closed |
| 2 | Uso inválido — parse/validação recusou o argv | EXIT_USAGE (index.ts:225) + USAGE no stderr |
Para automação, isso significa: if alembic import x --write; then … é confiável — um argv malformado nunca "meio-roda"; ele morre em 2 antes de tocar qualquer arquivo, e um err de execução sai em 1 sem deixar escrita parcial (as escritas são idempotentes e deduplicadas por id, como visto na lição 03).
alembic marketing video shots.json --approve — sem --yes. O que acontece?--approve E --yes (args.ts:415–418: "needs --yes too"). Não é erro de uso de propósito: o dry-run é o resultado útil e seguro do caminho incompleto.USAGE (index.ts:68) é a superfície declarada, e derive-counts (PR #158) a conta por script. Foi exatamente assim que o drift 32→33/47 foi pego na primeira execução — os docs mentiam por preguiça honesta, a fonte não. Números de superfície se derivam, não se digitam.Explore a superfície inteira sem gastar um centavo (tudo abaixo é $0 por doutrina):
# a superfície declarada (os 33/47 saem daqui) alembic help # o enum canônico dos comandos sed -n '18,52p' apps/cli/src/args.ts # a dupla trava, na fonte sed -n '415,418p' apps/cli/src/args.ts # dry-runs de doutrina: rodar "de verdade" sem efeito nenhum alembic import ~/algum/rollout-2026.jsonl # preview; nada gravado alembic employee run <id> --goal "teste" # imprime o prompt e para alembic marketing campaign request.json --out plan.json # pipeline inteiro em $0 # observabilidade de runs, também $0 alembic runs list alembic tail <run-id> -n 20 # o status do funil e o baseline de saúde alembic status pnpm -r typecheck && pnpm -r build && node scripts/safe-test.mjs pnpm -w test
Fecho do curso-módulo: o CLI é fino DE PROPÓSITO — parse na borda, Result no meio, USAGE como contrato. Toda a inteligência mora nos packages que você viu nas lições 01–04.
args.ts) → run<Comando> (commands.ts) → dispatch + USAGE (index.ts).Result no meio, exit code na saída — o mesmo idioma da lição 01, agora no argv.--approve E --yes; uma chave só = preview $0 (args.ts:415–418).