| name | cli-creator |
| description | Cria e refatora CLIs no ecossistema Ravi, aterrado na arquitetura REAL do runtime. Cobre as duas famílias (CLI nativo do Ravi = classe com decorators auto-descoberta; CLI standalone/domínio = binário próprio), o contrato de decorators (@Group/@Command/@Arg/@Option/@CommandAccess/@Returns/@Scope), output agent-first (mandato --json, stdout=dado/stderr=log, exit 0/1, helper fail(), IDs semânticos), paginação real (páginas limitadas 50/500 + nextCommand), gate de --help padrão-ouro, e os meta-testes de CI que são a lei do repo. Use quando criar nova ferramenta CLI, refatorar CLI existente, padronizar comandos, ou integrar CLI ao runtime com RAVI_CONTEXT_KEY. Skill standalone — o essencial de --help e de skill→cli está inline; cli-help-engineering e dissolving-skills-into-cli-help são aprofundamento opcional. NÃO cobre: criar a skill que ensina o CLI (use skill-creator). |
CLI Creator — Ciclo Completo de CLIs no Ravi
Função: transformar um problema operacional num CLI correto, agent-first e descobrível — usando a arquitetura REAL do runtime do Ravi, não um modelo genérico. Todo padrão aqui foi spike-testado no código vivo (ravi-src-dev); nada é aspiracional.
Esta skill é STANDALONE: o essencial das 3 capacidades (criar CLI, --help padrão-ouro, skill→cli) está todo inline. As companion citadas (cli-help-engineering, dissolving-skills-into-cli-help) são aprofundamento opcional, não dependência — um agente que só tenha esta skill consegue os 3.
⚠️ Antes de tudo — a armadilha nº1: NÃO existe no CLI ravi um comando cli-manifest por-CLI nem um tools cli-register. Discovery é por reflexão automática de decorators. Se você escrever program.command(...) + cli-manifest + registro manual, está construindo a coisa errada.
1. DECIDIR primeiro: qual das 2 famílias
| Família | Quando | Como se constrói |
|---|
CLI nativo do Ravi (ravi <grupo>) | O comando faz parte do runtime Ravi (agents, tasks, contexto, etc.) | Classe com decorators em src/cli/commands/<grupo>.ts → barrel auto-descobre. Padrão default. |
CLI standalone / de domínio (ex: sde, binance-cli) | Ferramenta externa/de negócio com binário próprio, fora do processo ravi | Pacote próprio em bun + commander clássico, seu próprio --help/manifest |
O resto desta skill foca no nativo do Ravi (o caso comum). Para standalone, valem as mesmas regras de output/erro/help, mas SEM decorators/barrel.
2. Invariantes (não-negociáveis)
- Problema antes do parser — modele os dados/decisão que o CLI destrava ANTES de desenhar comandos.
- Ferramenta antes do agente — o CLI nasce antes da skill que o ensina.
- Contrato de decorators (nativo Ravi) — um comando só é um "tool" de primeira classe com
@Command + @CommandAccess + @Returns.
- Agent-first — o consumidor é um agente autônomo com contexto escasso: alto sinal, IDs semânticos,
--json, erros que se autocorrigem.
- RAVI_CONTEXT_KEY — CLIs que rodam dentro do Ravi usam o contexto como credencial canônica.
3. Fluxo canônico (7 passos)
1. Brainstorm
Qual problema resolve? Qual decisão melhora? Quais entidades/artefatos/lineage? O que persistir e por quê?
2. Mapa de implementação (GATE — apresentar ao aprovador HITL)
Apresentar o mapa ao aprovador humano (HITL). NUNCA implementar sem aprovação. (O HITL é quem o runtime/route define como owner — pode ser o RM, o dono do agente, ou o revisor. A skill é genérica: fala HITL, nunca uma pessoa específica.)
3. Escrever o comando (contrato real de decorators)
Nativo Ravi = classe @Group com métodos @Command em src/cli/commands/<grupo>.ts. Template REAL (de tasks.ts):
import "reflect-metadata";
@Group({ name: "tasks", description: "Task runtime for dispatching work to Ravi agents", scope: "open" })
export class TaskCommands {
@Command({ name: "create", description: "Create a tracked task; ..." })
@CommandAccess({ kind: "mutate", resource: "tasks", action: "create", risk: "medium" })
@Returns(taskCreateReturnSchema)
async create(
@Arg("title", { description: "Short task title" }) title: string,
@Option({ flags: "--priority <level>", description: "low|normal|high|urgent", defaultValue: "normal" }) priority?: string,
@Option({ flags: "--json", description: "Print raw JSON result" }) asJson?: boolean,
) {
if (asJson) console.log(JSON.stringify(payload, null, 2));
else console.log();
return payload;
}
}
Os 8 decorators reais (src/cli/decorators.ts): @Group @Command @Arg @Option @CommandAccess @Returns @Scope @CliOnly.
@CommandAccess({ kind, resource, action, risk }) — obrigatório em quase todo comando; alimenta o permission-provider. kind: read|mutate. risk: low|medium|high|destructive.
@Scope / scope no @Group — fail-secure: default é admin. Enum real (decorators.ts): superadmin|admin|writeContacts|resource|open. Use open só para leitura genérica.
@Returns(zodSchema) — SEM isso o comando não vira tool tipado do SDK. Schema inline (como acima) ou em massa via declareCommandReturns(Classe, { metodo: schema }). Primitivos reutilizáveis em src/cli/return-schemas.ts: cliOffsetPaginationSchema, cliCursorPageSchema, mutationAckSchema, jsonValueSchema.
@Returns.binary() — para resposta binária (blob) no lugar do schema Zod.
@CliOnly() — exclui o comando da superfície SDK (comando só-humano).
4. Descoberta = automática (NÃO manual)
- Criar
src/cli/commands/<grupo>.ts com a classe @Group.
- Rodar
bun run gen:commands → regenera o barrel src/cli/commands/index.ts (auto-gerado, NÃO editar à mão).
- Pronto.
registerCommands lê os decorators e monta tudo (inclusive enforcement de scope/access). Colisão de (grupo, comando) estoura no boot — bug pego cedo.
Sem passo de "registrar manifest". A reflexão faz. Verifica com: ravi tools show <grupo>_<comando> e ravi tools manifest.
5. Integração runtime (RAVI_CONTEXT_KEY) — se aplicável
- Pai emite
ravi context issue <cli> --allow <cap> --ttl <dur> → filho recebe RAVI_CONTEXT_KEY (única credencial, formato rctx_*).
- CLI valida com
ravi context whoami / check <perm> <objType> <objId>.
- Capability mínima, TTL curto, audit completo. (Esta parte a skill antiga já acertava.)
6. Spike + testes (GATE — antes de entregar)
Rodar CADA comando com input válido / inválido / faltando. Todos os checks abaixo têm que PASSAR (senão corrigir + re-testar; NUNCA entregar com bug):
A lei do repo = meta-testes de CI (não o spike manual). O repo tem json-coverage.test.ts (todo comando finito DEVE expor --json) e pagination-coverage.test.ts (todo list/ls DEVE ser paginado). Teste co-locado em src/cli/commands/<grupo>.test.ts, roda em bun:test com mock + await import(...). Se seu CLI não conformar, a CI fica vermelha.
7. Entrega (HITL + doc por domínio)
- Perguntar ao HITL: "CLI implementado, testado e descoberto. Disponibilizo pra qual agente?"
- Documentar o CLI na skill do DOMÍNIO DE NEGÓCIO, NUNCA por ferramenta/API. Extrato bancário → skill financeira, nunca "banco-inter".
4. Output agent-first (regras duras)
stdout = só dado. stderr = log/progresso/erro. Assim o pipe/JSON não corrompe. (console.log p/ payload, console.error p/ erro.)
- Mandato
--json — todo comando finito expõe --json, imprime JSON.stringify(payload, null, 2) E returna o payload. É CI-enforced.
- Exit codes = 0/1. Sucesso 0, erro 1. (Não invente "2 = uso" — não é a convenção real.) NUNCA
process.exit() no caminho de sucesso.
- Helper
fail(msg) (src/cli/context.ts): dentro de contexto Ravi ele lança (o gateway captura); fora, console.error + exit(1). Use fail(), não process.exit cru — senão quebra rodando dentro do daemon.
- Alto sinal — retorne os campos que informam a próxima ação (
name, status, url); corte ruído (uuid cru, mime_type, 256px_url) salvo se pedido.
- IDs semânticos — resolva UUID pra nome legível no output; melhora a precisão do agente.
- Erros acionáveis — RUIM:
Error: EACCES. BOM: Invalid priority: X. Use low|normal|high|urgent — sempre com o COMO corrigir / o próximo comando. Retorne o contexto que o agente gastaria uma chamada pra buscar.
5. --help padrão-ouro (GATE — o contrato completo)
--help NÃO é lista de flags — é o contrato comportamental completo da CLI, a SSoT. Um agente sem contexto tem que dirigir a CLI só lendo o help ("teste do agente cego"). Bloqueia entrega se falhar. O essencial pra escrever um --help padrão-ouro está inline abaixo — esta skill é standalone. Aprofundamento opcional (building blocks, tipologias): cli-help-engineering, se disponível.
Padrão-ouro de referência (rodar e replicar a estrutura): sde tiny pedido-montar-json --help.
Mínimo por comando (todo CLI):
As 14 seções canônicas (no addHelpText('after', ...) do orquestrador; leitura-pura pode omitir 5-9):
3. USE (quando é a escolha certa) · 4. NÃO USE (+ pointer alternativo) · 5. REGRAS HARD (constraints que a CLI bloqueia) · 6. HITL OBRIGATÓRIO · 7. VALIDAÇÕES AUTOMÁTICAS (ref arquivo:linha) · 8. LIFECYCLE (estados+transições) · 9. HITL TEMPLATE (mensagem literal pro humano) · 10. EXAMPLES · 11. ON ERROR (código→causa→fix) · 12. PIPELINE (upstream→esta→downstream) · 13. SEE ALSO · 14. FORMATO (datas/IDs/valores) · 15. DEBUG · 16. FONTES (datas + path do source).
Exemplo real de --help anotado (excerto do padrão-ouro sde tiny pedido-montar-json). Repara: cada flag carrega tipo | significado | origem | constraint | default, e o rodapé traz as seções — não é "lista de flags", é o contrato inteiro:
--lista-preco <id> enum | 629174891="SITE (+39%)" | 601762293="Oficial (+32.25%)" (default: 629174891)
--parcelas <dias> string | intervalo em DIAS. LIMITE: <R$1k=max 2x, R$1k-3k=3x, >R$3k=4x (default: 4)
CUSTO / SEGURANÇA
• READ-ONLY: monta o JSON, NÃO submete à API. Idempotente, seguro p/ retry.
• Destrutivo? NÃO — o passo destrutivo do pipeline é `pedido-incluir`.
USE ✓ Cliente existe + SKU cadastrado + balcão padrão
NÃO USE ✗ Item sem cadastro → `pedido-incluir` direto ✗ Marketplace → CLI dedicada
REGRAS HARD • Atacado requer subtotal > R$2.000 • Frete=0 + subtotal<R$500 → alerta
Seções obrigatórias sempre: USE, NÃO USE, EXAMPLES, ON ERROR, FONTES. As de risco (REGRAS HARD, HITL, LIFECYCLE) entram quando o CLI muta/é irreversível.
Cristalizar a precedência NO --help (senão agentes divergem em conflito):
REGRAS HARD > INPUT HUMANO > CONVENÇÕES > REGRAS AGENTS.MD
Validação empírica (obrigatória): antes de escrever o --help, rodar --help de TODAS as CLIs adjacentes citadas + 1 chamada real à API. Sem isso = nomes inventados + doc-IA-genérica.
6. Paginação real (contrato do repo)
NÃO é "busca todas as páginas". É paginação limitada com próximo passo explícito. Helpers em src/cli/pagination.ts:
parseCliListLimit (default 50, máx 500), parseCliListOffset (default 0)
paginateCliItems(items, {limit, offset}) → { items, total, limit, offset }
buildCliOffsetPagination({ baseCommand, limit, offset, returned, total }) → monta o nextCommand
Shape de retorno real (spike verificado — ravi agents list --json --limit 2):
{ "total": 81,
"pagination": { "limit": 2, "offset": 0, "returned": 2, "total": 81,
"hasMore": true, "nextOffset": 2,
"nextCommand": "ravi agents list --json --limit 2 --offset 2" },
"items": [ ... ] }
Para sets grandes/temporais use cursor (src/cli/listing.ts + cliCursorPageSchema). Todo list/ls precisa de --limit+--offset OU --cursor (CI-enforced).
7. Anti-patterns (parar e corrigir)
- Arquitetura errada —
program.command() + cli-manifest + tools cli-register para um CLI nativo. Não existe. Use decorators + barrel.
- Comando sem
@Returns — não vira tool tipado do SDK.
- Comando finito sem
--json ou list sem paginação — CI vermelha.
- Wrap 1:1 de endpoint de API como comando — baixo sinal, queima contexto. Consolide fluxos.
- Dump de dataset inteiro — pagine/filtre; não force o agente a ler tudo token-a-token.
- ID críptico como output primário (UUID cru) — degrada retrieval do agente.
- Output instável entre versões (renomear campo JSON, reordenar coluna) — quebra todo consumidor.
--help fino/sem exemplo — falha o teste do agente cego.
- Segredo em flag ou env var — vaza em
ps/histórico/processos-filho. Use arquivo de credencial / stdin.
process.exit cru dentro de handler — quebra execução in-daemon. Use fail().
- Editar o barrel
commands/index.ts à mão — é auto-gerado. Rode gen:commands.
8. Skill → CLI: injetar conhecimento no --help (ferramenta de anotação)
Quando o CLI existe e tem regra de negócio no source que o --help não mostra, migra-se o conhecimento tribal da skill PRA o --help — o CLI vira a SSoT e a skill vira STUB (triggers + pointer, nunca deletar). Os passos essenciais estão inline abaixo (standalone); aprofundamento opcional: dissolving-skills-into-cli-help (validado em 4 skills do jarvis).
Mecanismo real (anotar o CLI), POR FAMÍLIA: CLI nativo Ravi → setar a string helpAfter no @Command (o registry.ts chama addHelpText('after', helpAfter) por baixo). CLI standalone/commander → encadear .addHelpText('after', ...) direto. Nos dois, escrever as 14 seções canônicas (§5) ali — o --help passa a carregar REGRAS HARD, HITL template literal, lifecycle e error mapping, não só flags.
Classificar cada pedaço da skill em 5 buckets: A → AGENTS.md (roteamento/triggers) · B → --help do orquestrador (regras hard, HITL, lifecycle) · C → --help dos subcomandos (regras locais, formato) · D → profile de task (dispatch denso) · E → vault (knowledge denso, multi-fase).
Sequência operacional (o processo completo, condensado):
- ANTES: rodar o
--help atual (baseline) + rodar o CLI com args reais (ver o JSON de output) + ler a skill inteira + classificar cada item nos 5 buckets + rodar --help de TODAS as CLIs que vai citar (cross-CLI empírica — zero invenção de nome).
- APPLY: backup do source (nativo:
git stash/branch; standalone: cp index.ts index.ts.bak-<ts>) → localizar o comando (nativo: método @Command; standalone: program.command('<nome>')) → migrar buckets B+C pro help (nativo: string helpAfter; standalone: addHelpText('after', ...)) escrevendo as 14 seções → validar que --help renderiza E o CLI ainda executa.
- PÓS: skill vira STUB (mantém triggers + pointer, nunca deletar) → AGENTS.md aponta
→ TODAS regras em <cli> --help → simulação real: operador monta 1 output completo só com o --help, sem a skill → archive a skill (só após simulação verde) → delete definitivo SÓ com "sim" explícito do HITL (ação destrutiva).
Fecha o triângulo: cli-creator (constrói) → cli-help-engineering (método do --help) → dissolving-skills-into-cli-help (injeta o conhecimento como anotação). AGENTS.md do operador aponta: → TODAS regras em <cli> --help.
LEMBRETE FINAL
- Mapa antes de código, apresentado ao HITL — genérico, nunca uma pessoa fixa. NUNCA implementar sem aprovação.
- Contrato real = decorators +
@Returns + @CommandAccess + bun run gen:commands. Esqueça manifest/register manual — é ficção pro CLI nativo.
--json + paginação + --help padrão-ouro são gates de CI/entrega, não opcionais. Cada um foi spike-testado no código vivo.