Lição 05 · Alembic Completo v3 · A superfície CLI ← índice
Alembic Completo v3 · Módulo 3 · A execução

A superfície CLI

apps/cli é a cara do motor: 33 comandos top-level, 47 formas documentadas — todos nascidos do MESMO padrão de três arquivos (args.tscommands.tsindex.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.

1

O padrão de três arquivos

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.

argv alembic import … args.ts parseArgs + PARSE_OPTIONS toRecord → Zod schema Zod NA BORDA, tipos no meio inválido → exit 2 + USAGE commands.ts runImport, runDistill, … retorna Result<T, Error> nunca lança pela borda index.ts dispatch + USAGE exportado ok → 0 · err → 1 · uso → 2 USAGE = fonte dos 47 novo comando = tocar os 3 arquivos na ordem: schema → run<Comando> → dispatch + USAGE (+ testes + docs)
args.ts:1248 (parseCliArgs) → commands.ts (run<Comando> → Result) → index.ts:68 (USAGE) — a mesma esteira para os 33 comandos.
harness · 0004-saida-estruturada-ferramentas · s0

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.

2

Doutrina: dry-run por default

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:

ComandoDefault ($0, sem efeito)Opt-in explícito
importPreview do que seria importado--write grava (idempotente)
import --distillFalha fechado sem rede--online chama; --write grava
employee runImprime o prompt e PARA--online chama o modelo
marketing video/ad/campaignPreview de custo (estimate FIRST)--approve E --yes juntos
courseByte-idêntico, só SVG--images (+ --yes se pago)
distill--offline = hermético $0--budget/--learnings-budget com cap
A dupla trava
Nos comandos que gastam dinheiro de verdade (Higgsfield), UMA flag nunca basta: 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.
produto · 0006-marketing-factory · s1
marketing video estimateCost PRIMEIRO 🔒 --approve decisão do operador 🔒 --yes confirmação de gasto geração PAGA as DUAS chaves dry-run $0 0 ou 1 chave só uma flag sozinha NUNCA gasta (args.ts:415–418) — o preview de custo é o caminho de queda
A dupla trava dos comandos pagos: --approve E --yes em série; qualquer combinação menor cai no preview $0.
3

derive-counts: números derivados, nunca digitados

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.

33
comandos top-level (commandSchema, args.ts:18–52)
47
formas documentadas no USAGE exportado
32→33
o drift que a 1ª execução do derive-counts pegou
Checklist para um comando novo (a receita canônica do repo): ① schema em 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.

Exit codes: a interface para scripts

CódigoSignificadoQuem o produz
0Sucesso — o run<Comando> retornou okcaminho feliz do dispatch
1Falha de execução — retornou err (mensagem no stderr)o próprio comando, fail-closed
2Uso inválido — parse/validação recusou o argvEXIT_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).

4

Cheque seu modelo mental

Você roda alembic marketing video shots.json --approve — sem --yes. O que acontece?
Correto: a dupla trava é permissiva no parse e rígida no efeito — o comando RODA, mas fica no preview de custo; a geração paga exige --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.
Qual é a fonte de verdade dos números "33 comandos / 47 formas" da superfície CLI?
Correto: o 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.
Camada técnica — comandos copy-paste

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.

O que levar desta lição (e do módulo do motor)
Pergunta de acompanhamento sugerida: "e quando o comando PRECISA gastar — como o funil decide quanto?" — revisite a lição 02 (BudgetGuard e as duas cadeias do ADR-0002); e do índice sigam os módulos seguintes do curso, que sobem do motor para o domínio (employees, marketing factory, RAG).