Standard CLI Flags for Koder Apps
binaries-and-cli specs/binaries-and-cli/standard-flags.kmd
Toda app Koder com binário CLI/desktop/TUI (Flutter desktop, Bubble Tea TUI, cobra CLI) deve aceitar um conjunto comum de flags com contrato preciso: `--version` e `--help` que terminam **antes** de qualquer init de GUI/runtime; `--user-data-dir` que isola profile/cache da sessão real do usuário; `--no-startup-window` que desacopla boot do runtime de criação de janela top-level. A motivação é tornar **probes de automação seguros por construção** — inspecionar versão / capability / capacity de um app Koder não pode abrir GUI, herdar sessão, ou consumir display do usuário.
Specification body
Spec — Standard CLI Flags for Koder Apps
Motivação
Em 2026-05-28 uma sessão de IA tentou detectar a versão de Kruze
instalada via /opt/koder/kruze/koder_kruze --version. O binário,
sem implementar o flag, ignorou-o e bootou a app inteira no display
do owner — três janelas Kruze restauraram a sessão interativa do
usuário (tabs do dia: claude.com, kds.koder.dev, etc.) e ficaram
travadas porque concorriam por GPU + display com o smoke-bench em
progresso. Episódio documentado em meta/context/handoff/recaps/ 2026-05.md (2026-05-28 turn 21+).
O incidente é uma instância de um defeito de superfície: apps Koder desktop não têm contrato comum sobre como respondem a flags canônicas que toda automação tenta primeiro. Esta spec fecha esse gap.
Escopo
Todo binário/CLI/desktop/TUI Koder em qualquer um dos paths:
products/<area>/<sector>/app/desktop/products/<area>/<sector>/app/cli/products/<area>/<sector>/app/tui/products/<area>/<sector>/cli/products/<area>/<sector>/tui/dev/<tool>/(koder-tools binaries)
Mais binários de serviço (daemons/servidores de qualquer domínio —
backend/, cmd/* que sobem servidor), aos quais se aplica apenas
o R8, não o R1-R7 completo. Ver R8 para o porquê do contrato
reduzido.
Componentes que não se aplicam: apps mobile (app/mobile/),
app TV (app/tv/), web (app/web/), SDK libraries (engines/sdk/).
Correção de escopo 2026-07-19. Até esta data a linha acima excluía serviços backend por inteiro. A exclusão foi feita pensando em ergonomia (daemon não tem
--helpde usuário), mas levou junto a parte de segurança do R1: "não inicializar runtime antes de imprimir a versão". O resultado medido está no R8.
R1 — --version / -v
Imprime a string canônica de versão em stdout e termina com exit status 0. Nenhuma inicialização de runtime de GUI/CEF/Flutter
engine/GTK/X11/Wayland/audio é permitida nesse caminho. O argv
parsing deve detectar este flag antes de chamar initCEFProcesses
/ gtk_init / Wayland connect / análogos.
Formato canônico de saída (uma linha):
<slug> <semver>
ou, quando o app distribui múltiplos binários sob a mesma versão de
release (cf. naming.kmd §"single-binary vs multi-binary family"):
<binary> v<semver> (<slug>)
Exemplos:
$ kruze --version
kruze 1.0.53
$ kterm -v
kterm 1.32.4
$ kicon --version
kicon v0.18.2 (koder-tools)
Anti-padrões proibidos:
- Booting a app antes de imprimir versão
- Imprimir versão E continuar (precisa SAIR)
- Exit code ≠ 0
- Adicionar prefixos longos (
Welcome to Koder…,Copyright …) — uma linha curta, parseável
R2 — --help / -h
Imprime usage em stdout e termina com exit status 0. Mesma regra
"zero inicialização de runtime de GUI" do R1.
Usage deve incluir, em ordem:
- Linha "Usage:" com forma canônica
- Lista de flags com descrição curta (uma linha cada)
- Link ou path pra documentação completa (README.kmd do componente,
ou URL
https://<slug>.koder.dev/docs/cliquando publicada)
Exemplos:
$ kruze --help
Usage: kruze [options] [url]
Options:
--version, -v Print version and exit.
--help, -h Print this help and exit.
--user-data-dir=DIR Isolate profile/cache to DIR.
--no-startup-window Boot without creating a top-level window
(requires --cdp-port for control).
--headless Hide the main window after boot.
--incognito Start in incognito mode.
--new-window Open a new top-level (when already running).
--cdp-port=PORT Bind CEF remote-debugging to PORT.
--batch SUBCMD [...] Run a batch subcommand and exit.
--debug-mode Enable in-app inspection capture.
--debug-jsonl=PATH Append CDP events as JSON Lines to PATH.
Documentation: https://kruze.koder.dev/docs/cli
R3 — --user-data-dir=<PATH>
Isola todo state per-user (profile, cache, cookies, session, logs)
em <PATH> em vez do default. Default é o que naming.kmd define
por target (Linux: $XDG_DATA_HOME/<slug>/, macOS: ~/Library/ Application Support/<slug>/, Windows: %APPDATA%\<slug>\).
Quando --user-data-dir=<PATH> está presente:
- Toda escrita de profile/cache/cookies/session ROTEIA para
<PATH> - Default location não é tocada (nem lida, nem escrita)
- Path inexistente é criado (com
mkdir -psemantics) — falhar apenas se o path não pode ser criado por motivo de permissão - Path relativo é resolvido a partir de
$PWD
CEF/Chromium-based apps já têm suporte nativo a --user-data-dir —
o flag deve ser passado adiante ao runtime sem reinterpretação.
GTK/Bubble Tea apps precisam implementar o roteamento manualmente.
Caso de uso primário: probes de automação seguros. Toda
ferramenta que invoca um app Koder pra inspecionar capability /
versão / state DEVE usar --user-data-dir apontando pra path
descartável (ex.: /tmp/probe-<random>/). Sem isso, o probe herda
a sessão real do usuário.
R4 — --no-startup-window
Boota o runtime sem criar janela top-level visível. Compõe com
--cdp-port=<N> pra modo "puramente controlled via CDP" e com
--batch <subcmd> pra one-shot.
Diferente de --headless: --headless ainda CRIA a janela (oculta
via windowManager.hide()); --no-startup-window não cria janela
nenhuma. Em apps Flutter/GTK, isso significa skip de
GtkApplicationWindow + FlView (cf. kruze#175.1 para
arquitetura proposta).
Nota de implementação: este flag é o mais difícil dos 4 porque
requer alterações estruturais no runner GTK/Wayland (cf. KRUZE-175.1
análise técnica). É mandatory na spec mas não-bloqueante pra
release de app que já implemente R1+R2+R3 — falha-soft com mensagem
"--no-startup-window not yet implemented; use --headless for
similar behavior" + exit 1 quando o flag é passado. R5 da spec
elabora.
R5 — Implementação gradual permitida
Apps podem shippear R1+R2+R3 antes de R4. O contrato fraco:
| Flag | Mandatory release-gate | Falha-soft permitida |
|---|---|---|
--version / -v | sim | não |
--help / -h | sim | não |
--user-data-dir=<PATH> | sim | não |
--no-startup-window | sim | sim (mensagem + exit 1) até KRUZE-175.1 / equivalente cobrir |
Release engineering bloqueia release de app que não implementa
R1+R2+R3. R4 pode falhar-soft enquanto o ticket de implementação
estrutural está aberto + linkado no koder.toml [compat].
R6 — Probes de automação devem usar os flags
Toda ferramenta Koder que invoca apps Koder (incl. agentes de IA) DEVE usar:
--versionpara detecção de versão (NUNCA parse binário/strings)--user-data-dir=/tmp/<slug>-probe-<random>/para qualquer invocação que não é o uso normal do usuário
Violar isso é violação direta de policies/heavy-work-isolation.kmd §R9 (proteção da sessão interativa do owner — implícita até esta
spec; explícita após).
R7 — --user-agent=<UA> (passthrough, CEF/Chromium apps)
Apps baseados em CEF/Chromium (Kruze e qualquer futuro browser-shell Koder)
DEVEM aceitar --user-agent=<UA> e aplicá-lo a todas as requisições. Em CEF/
Chromium o switch é nativo — o runner não reinterpreta, apenas deixa o
flag no argv (mesma mecânica do --user-data-dir, R3). Nenhum parsing
Dart-side é necessário nem desejável (reuse-first: não reimplementar o que o
runtime já entrega).
Motivação: em modo --headless/--batch, o CEF historicamente emitia o token
HeadlessChrome no User-Agent default. Bot-walls que filtram esse token (caso
confirmado: mercadolivre.com.br — serve interstitial de erro em vez do conteúdo)
bloqueavam todos os modos batch (html, eval, screenshot, pdf),
inutilizando o app para automação/scraping legítimos.
Default corrigido (KRUZE-253): quando --headless/--batch está ativo e
nenhum --user-agent foi passado, o runner injeta um UA default que reporta
Chrome/<major>.0.0.0 (não HeadlessChrome), então o default já passa o filtro
de token. --user-agent=<UA> continua sendo o override explícito (vence o
default via o guard has_user_agent) — a válvula de escape documentada para UAs
arbitrários.
Contrato:
--user-agent=<UA>aplica-se globalmente (todos os tabs/requests).- Posicionar antes de
--batch— o parser de--batchconsome todo o argv subsequente como argv do subcomando; o CEF varre o argv inteiro, mas a posição antes de--batché a forma canônica e à prova de regressão. - Combina com
--wait-for <css>nos batchhtml/eval/screenshotquando o site tem desafio JS/render assíncrono (o UA passa o filtro de token; o--wait-forcobre o tempo de render).
Apps não-CEF (GTK/Bubble Tea/cobra) só implementam R7 se fizerem requisições HTTP com UA configurável; caso contrário o flag é N/A (não é release-gate).
R8 — Binários de serviço: --version não pode subir o serviço
Aplica-se a todo binário que sobe um servidor (bind de porta,
ListenAndServe, grpc.Serve, loop de daemon), em qualquer domínio.
Contrato mínimo — três obrigações:
--versionimprime uma linha em stdout e termina com exit 0.- Nenhum bind de porta, conexão a banco, leitura de secret ou início de loop de trabalho nesse caminho. O parsing do flag vem antes de qualquer efeito colateral.
- A string identifica o artefato, não o programa: semver e
commit.
"dev"e"0.0.1"constantes não cumprem o R8 — elas parseiam com sucesso e não informam nada, que é pior que falhar.
Formato canônico (o mesmo do R1, com o commit obrigatório aqui):
<slug> v<semver> (commit <sha-curto>)
Referência viva na Stack — o Koder Jet já cumpre:
$ koder-jet --version
Koder Jet v1.44.0 (commit 9d305c3d56)
R2-R7 NÃO se aplicam a binários de serviço: --help de daemon é
opcional, --user-data-dir e --no-startup-window são conceitos de
app com sessão de usuário, e --user-agent é de shell CEF. Exigir o
contrato completo aqui seria cerimônia sem consumidor — e é
justamente o tipo de exigência inaplicável que motivou a exclusão
original do escopo. O R8 pega só o que tem consequência.
Por que isto é segurança, não ergonomia
Medido em 2026-07-19 na frota de s.khost1:
$ koder-id-auth --version
2026/07/19 05:26:01 INFO starting auth service version=0.1.0-dev port=4002
O binário começou a subir o serviço de autenticação e tentou
bindar a porta. Não houve dano porque o serviço real já a segurava —
verificado no mesmo turno: unit active, PID único, porta intacta.
Se a porta estivesse livre, uma sonda de leitura teria subido uma segunda instância de um serviço de auth em produção.
É a mesma classe do incidente que criou esta spec (o Kruze abrindo três janelas no display do owner, 2026-05-28) — com a diferença de que a superfície onde ela se repete estava fora do escopo. A regra existia; a exclusão a desligou exatamente onde importava.
Estado medido (2026-07-19)
| medida | valor |
|---|---|
| binários Go que sobem servidor | 157 |
| destes, sem tratamento de flag de versão | 140 |
| cobertura do audit ANTES desta revisão | 4 binários Go (1,3%) |
O audit reportava 100% de conformidade no que media, e media 4 de 313 binários Go. Um número saudável sobre a fatia errada.
R8.1 — Descoberta não se faz por varredura em produção
Sondar a frota com --version para descobrir quais binários
violam o R8 é proibido: a sonda é o próprio risco — seria iniciar
serviços para aprender que iniciá-los é ruim.
A conformidade se verifica por leitura estática do argv parsing
(o que o audit faz) ou em sandbox. Nunca por probe em produção.
Esta é a razão de o registries/deploy-targets.toml carregar
version_probe = "unknown" em vez de "none" nas linhas não
medidas: ignorância declarada em vez de palpite.
Tests (validação da spec)
| ID | Verificação |
|---|---|
| T1 | <binary> --version imprime uma linha + exit 0 em < 1s wall-clock (zero GUI init) |
| T2 | <binary> -v é equivalente a <binary> --version |
| T3 | <binary> --help imprime usage + exit 0 em < 1s wall-clock |
| T4 | <binary> -h é equivalente a <binary> --help |
| T5 | <binary> --user-data-dir=/tmp/probe-X --headless --cdp-port 9999 about:blank cria /tmp/probe-X/ e NÃO escreve em $XDG_DATA_HOME/<slug>/ |
| T6 | <binary> --no-startup-window --cdp-port 9999 ou exibe banner "not implemented" + exit 1, OU boota sem janela top-level (R4 cumprido) |
| T7 | Audit-walk de cada app/desktop/, app/cli/, app/tui/, cli/, tui/ no monorepo lista quais componentes implementam R1-R3 vs estão pendentes |
| T9 | (R8) Audit-walk estático lista todo binário que sobe servidor e classifica se trata flag de versão antes de qualquer efeito colateral — sem executar nenhum deles |
| T10 | (R8) Em sandbox (nunca em produção): <service-binary> --version termina com exit 0, não binda porta, e a saída casa v<semver> (commit <sha>) |
| T8 | kruze --headless --user-agent="Mozilla/5.0 … Chrome/149 …" --batch eval https://example.com --js "navigator.userAgent" imprime o UA passado (não HeadlessChrome) — confirma o passthrough nativo (R7) |
Cross-references
specs/binaries-and-cli/naming.kmd— define o nome do binário; esta spec define seu contrato CLIpolicies/headless-first.kmd— R8 SDK reuse aplica quando consumidor automatiza esses flagspolicies/heavy-work-isolation.kmd §R9— carve-out de laptop pressupõe esses flags pra probes seguros
Histórico
2026-05-28 — spec criada após incidente "rogue Kruze windows" durante KRUZE-181 fase 2 carve-out run. Kruze ficou como reference implementation (R1+R2+R3 implementadas; R4 pendente em KRUZE-175.1).
2026-07-19 — R8 adicionado; binários de serviço entram no escopo (
infra/observe#002). Descoberto construindo o registrydeploy-targets:koder-id-auth --versioncomeça a subir o serviço de auth e tenta bindar a porta. A exclusão debackend/do escopo original desligou a parte de segurança do R1 exatamente na superfície de maior consequência. Medido: 157 binários sobem servidor, 140 sem tratar flag de versão, enquanto o audit cobria 4 binários Go e reportava 100%. Koder Jet é a implementação de referência do R8 (v1.44.0 (commit 9d305c3d56)).Registrado também o defeito de enforcement encontrado junto: o campo
audit:do frontmatter aponta parakoder-spec-audit cli-flags, subcomando que não existe; quem faz o trabalho é o shellaudit-cli-flags.sh, que nenhum workflow invoca. Uma spec que declara um audit inexistente promete uma verificação que ninguém faz.
References
specs/binaries-and-cli/naming.kmdpolicies/headless-first.kmdpolicies/heavy-work-isolation.kmd