Skip to content
evolution-foundationPublic

About

CLI etus: os apps da Etus no terminal, gerados do MCP do hub

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

29 Commits

Folders and files

Repository files navigation

etus

CLI global da plataforma ETUS. Um binário só, etus, gera os comandos de todos os apps a partir das ferramentas MCP que o hub libera para você: um app novo com ferramentas MCP vira etus <app> <comando> sem nenhuma mudança aqui.

Para agentes de código (Claude Code, Codex) é a interface mais econômica: o agente só lê --help quando precisa e usa --json para receber a resposta compacta.

Contrato completo: docs/specs/cli.md no repositório do hub (etusdigital/nexus-platforms).

Instalar

curl -fsSL https://apps.etus.io/cli/install.sh | sh
etus --version

O instalador escolhe o binário da sua máquina (macOS Apple Silicon ou Intel, Linux x86_64 ou aarch64), confere o SHA256SUMS e coloca o etus no PATH.

Outras formas:

# Download direto da release (troque pelo nome da sua máquina)
base=https://github.com/evolution-foundation/etus-cli/releases/latest/download
curl -fsSLO "$base/etus-aarch64-macos" && curl -fsSLO "$base/SHA256SUMS"
shasum -a 256 -c SHA256SUMS --ignore-missing
chmod +x etus-aarch64-macos && sudo mv etus-aarch64-macos /usr/local/bin/etus
# etus-aarch64-macos | etus-x86_64-macos | etus-x86_64-linux | etus-aarch64-linux

# Com o GitHub CLI
gh release download --repo evolution-foundation/etus-cli --pattern etus-aarch64-macos --output etus
chmod +x etus && sudo mv etus /usr/local/bin/etus

Ou compile (Rust 1.89 ou mais novo):

cargo install --git https://github.com/evolution-foundation/etus-cli --locked

Depois, etus atualizar-cli baixa a versão mais nova sozinho, sem token: lê VERSION, o binário e o SHA256SUMS de https://github.com/evolution-foundation/etus-cli/releases/latest/download/ e só troca o binário se o sha256 conferir. ETUS_CLI_DOWNLOAD_BASE aponta para outro endereço (um espelho, por exemplo). Se os arquivos públicos não existirem, usa GH_TOKEN, GITHUB_TOKEN ou gh auth token na API do GitHub, como reserva para um repositório privado.

Entrar

etus login            # abre o navegador; você entra no hub e clica em Permitir
etus whoami           # quem está conectado e a empresa ativa
etus apps             # apps que você pode usar
  • etus login --no-browser só mostra o endereço. Se o navegador estiver em outra máquina, cole no terminal o endereço da página de retorno (http://127.0.0.1:<porta>/callback?...).
  • etus login --somente-leitura pede o escopo mcp:read: nenhum comando que altera dados aparece.
  • Precisa da permissão "Conectar agentes externos" no hub, na empresa ativa.
  • etus logout revoga a sessão no hub e apaga o token desta máquina.

Onde o token fica

No macOS e no Linux o token fica em um arquivo com permissão 0600 na pasta de configuração (tokens-<hash>.json; etus config mostra o caminho), como fazem o gh, o gcloud e o aws. Nada de token aparece em log ou saída, exceto em etus token.

  • Por que não o chaveiro por padrão: o binário é assinado ad hoc, então cada versão nova tem outra identidade e o macOS volta a pedir permissão para ler o item etus-cli. Num agente de código rodando etus sem terminal, essa janela trava o comando.
  • Quem vem da 0.2.0: o primeiro comando rodado num terminal lê o token do chaveiro uma vez (pode aparecer uma última janela), grava no arquivo e apaga o item do chaveiro. Sem terminal, o etus não tenta ler o chaveiro: sai com código 3 pedindo etus login. Se a leitura falhar ou for negada, também pede etus login.
  • Usar o chaveiro mesmo assim: etus config armazenamento chaveiro (move o token para lá) ou ETUS_TOKEN_STORE=keychain na sessão. Para voltar: etus config armazenamento arquivo. A variável vale mais que a configuração.
  • Chaveiro sem terminal: com o chaveiro escolhido no macOS, qualquer comando sem terminal (stdin ou stdout que não é terminal, como em agentes e em $(...)) sai na hora com código 3 em vez de esperar a janela. Para scripts e agentes, use o arquivo.
  • Windows: continua no Gerenciador de Credenciais por padrão, que não abre janela; etus config armazenamento arquivo troca para arquivo.
  • Linux: não há chaveiro suportado; o token fica sempre no arquivo.

Token para scripts

MCP_TOKEN=$(etus token)          # access token válido, renovado se estiver perto de expirar
etus token --json                # {"accessToken": "...", "expiresAt": 1760000000}

O token é um segredo: dá acesso à sua conta enquanto valer. Não cole em chat, log, issue nem arquivo versionado. Para não ficar visível na tela, etus token se recusa a imprimir quando a saída é um terminal (código 2); --force imprime mesmo assim. Útil para suítes de avaliação e scripts que chamam o gateway MCP direto.

Usar

etus tarefas --help                                  # comandos do app
etus tarefas listar-tarefas --help                   # descrição completa e parâmetros
etus tarefas listar-tarefas --view today --today 2026-10-14
etus tarefas criar-tarefa --title "Revisar contrato" --due-date 2026-10-20 --yes
etus reunioes buscar-reunioes --q "preço" --json
etus mural --help
etus empresa listar
etus empresa usar etus                               # id, slug ou nome
etus skills buscar "planilha de custos"
etus skills ler <id>

Nomes, pt-BR primeiro:

App Nome Também aceita
Tarefas tarefas tasks
Reuniões reunioes records, meetings, gravador
Mural de Reconhecimento mural muraldereconheci, mural-de-reconhecimento
Etus Design design etus-design
Etus Work work etus-work
Extensões de IA extensoes extensions, ext
Rotinas rotinas routines
ETUS Platforms hub plataforma, platforms

Um app novo ganha o nome do próprio app no hub, sem acento e até o primeiro "de" (Agenda de Salas vira agenda); o prefixo MCP vira alias. Os comandos seguem o título da ferramenta quando ele é em pt-BR (Criar tarefa vira criar-tarefa); senão, o nome da ferramenta (tasks__create_task vira create-task). A outra forma sempre funciona: etus tasks create-task e etus tarefas criar-tarefa chamam a mesma ferramenta. etus apps mostra os nomes e os aliases, e etus <app> <comando> --help mostra as outras formas aceitas.

Argumentos:

  • flags geradas do JSON Schema: --project-id x, números, --done ou --done false, listas repetindo a flag (--tag a --tag b) ou em JSON (--tag '["a","b"]'), objetos em JSON, null para apagar campos que aceitam nulo;
  • ou o objeto inteiro: --json '{"title":"x"}', --input args.json, --input - (stdin). Flags somam e têm prioridade sobre o objeto;
  • obrigatórios e valores permitidos são conferidos antes de chamar o hub.

Ferramentas que alteram dados pedem confirmação no terminal ou --yes (sem terminal, sem --yes, o comando é recusado com código 2). --dry-run mostra a chamada sem executar.

Para agentes

etus setup                      # regras, plugins e MCP etus em cada editor do projeto (veja abaixo)
etus ativar radar               # só os apps que importam neste projeto, com o guia de cada um (veja abaixo)
etus skills sincronizar         # uma skill por app que você usa, na pasta do agente (veja abaixo)
etus --help                     # apps disponíveis (do cache)
etus <app> --help               # comandos do app, os que alteram marcados com [altera]
etus <app> <comando> --help     # parâmetros, exemplo e se pede --yes
etus <app> <comando> ... --json # resposta em JSON compacto

Com --json, a saída é o structuredContent da ferramenta ou o texto dela já decodificado; erros vão para o stderr como {"error":{"kind","message","exitCode"}}.

Código Significado
0 ok
1 a ferramenta falhou, ou falha de rede ou do hub
2 uso errado: flag inválida, obrigatório faltando, escrita sem --yes, cancelado
3 precisa entrar: rode etus login
4 sem permissão: ferramenta não liberada, conexão só de leitura, sem "Conectar agentes externos"
5 ação necessária: a ferramenta pediu que você faça algo numa tela do hub (autorizar, conectar, entrar, pedir acesso); o link está no stderr e no JSON do stdout

Ação necessária: o link para a tela certa

Alguns passos precisam de um clique seu por definição: autorizar um app a agir em seu nome, conectar uma conta (Google, GitHub, Slack), entrar, pedir uma permissão a um admin. Nesses casos a ferramenta não falha: devolve um resultado normal com este structuredContent (contrato da plataforma, docs/specs/action-required.md no hub):

{
  "status": "action_required",
  "action": {
    "kind": "consent | connect | login | permission",
    "title": "Conectar o Google",
    "message": "Conecte sua conta Google para ler a agenda.",
    "url": "https://apps.etus.io/...",
    "missing": ["google"],
    "expiresAt": "2026-10-14T18:00:00Z"
  }
}

O etus também reconhece o mesmo objeto quando ele vem só como JSON no texto da ferramenta. Ao receber:

  • stdout fica igual a qualquer outra resposta: o resultado da ferramenta (com --json, o objeto acima compacto), para o agente ler o link;
  • stderr mostra o título, a mensagem, o que falta e o link (Abra: https://...); com --json, o erro traz também action: {"error":{"kind":"action_required","message","exitCode":5,"action":{...}}};
  • código de saída 5, para scripts e agentes detectarem sem ler o texto.

O etus nunca faz a ação por você: ele só mostra o link (e abre o navegador, se você pedir). Só abre links https (ou http em localhost).

etus <app> <comando> --open           # abre o link no navegador (também --abrir)
etus <app> <comando> --wait           # mostra o link, espera você concluir e repete a chamada (até 300 s)
etus <app> <comando> --wait=600 --open
etus config abrir-links sim            # abre sozinho quando há terminal (nao para desligar)
  • --open abre sempre, com ou sem terminal. Sem a flag, o link só abre sozinho com etus config abrir-links sim (open_actions no arquivo) ou ETUS_OPEN_ACTIONS=1, e ainda assim só quando stdin e stderr são um terminal; agentes rodando sem terminal nunca abrem o navegador sem --open.
  • --wait[=SEGUNDOS] (também --aguardar) repete a mesma chamada, com os mesmos argumentos e a mesma confirmação, a primeira vez depois de 2 s e depois a cada 5 s, até a resposta não ser mais action_required ou o tempo acabar (padrão 300 s, no máximo 1800). Concluiu: imprime o resultado final e sai com o código dele. O tempo acabou: imprime o último resultado e sai com 5. Num agente, só use --wait quando a pessoa estiver por perto para clicar.
  • As flags valem antes do app (etus --open tarefas ...) ou depois do comando. Por isso open, abrir, wait e aguardar passam a ser nomes reservados: um parâmetro de ferramenta com esse nome vira --arg-open, como já acontecia com json e yes.

Skills dos seus apps para os agentes

etus skills sincronizar (também etus skills sync) gera uma skill por app que você pode usar na empresa ativa, para o agente saber de cara o que cada app faz e quais comandos chamar, sem redescobrir com --help a cada conversa. A regra geral do etus (login, empresa, JSON, códigos de saída, confirmação) fica na skill escrita à mão etus-cli, do pacote etus-core do etus-skills.

etus login
etus skills sincronizar                      # Claude Code: ~/.claude/skills/etus-<app>/SKILL.md
etus skills sincronizar --agente codex       # Codex: ~/.agents/skills/etus-<app>/SKILL.md
etus skills sincronizar --agente todos       # os dois
etus skills sincronizar --projeto            # pastas do repositório: .claude/skills e .agents/skills
etus skills sincronizar --pasta ./skills     # uma pasta qualquer (vale mais que --agente e --projeto)
etus skills sincronizar --dry-run            # mostra o que mudaria, sem gravar
etus skills sincronizar --json               # {"pastas","criadas","atualizadas","removidas","iguais","ignoradas"}
  • Do seu catálogo, na hora. Renova o catálogo como etus atualizar, então respeita a empresa ativa, o --perfil e o login só leitura: só entram os apps e comandos liberados para você.
  • O que cada skill traz. Para que o app serve, como chamar, cada comando com aliases, resumo, se lê, altera ou é destrutivo, a permissão exigida e os parâmetros (obrigatórios com o tipo; opcionais pelo nome), as receitas e os cuidados que o app declara no manifesto (bloco guide) e as regras de confirmação e de ação necessária. App com mais de 60 comandos lista só os obrigatórios.
  • Guia do app. Vem de etus_detalhar com app:<prefixo>, no hub. Num hub que ainda não serve o guia, a skill sai só das ferramentas, sem receitas nem permissões, e o etus avisa no stderr.
  • Nome e dono. A pasta é etus-<app>, com o mesmo nome de etus apps. etus-cli é reservado para a skill geral: nunca é gravada nem apagada aqui. Toda skill gerada tem a linha <!-- gerado por etus skills sincronizar: não edite, rode o comando de novo --> logo depois do frontmatter.
  • Só mexe no que gerou. Atualiza só o que mudou e remove só as pastas etus-* com essa linha cujo app saiu da sua lista (perdeu acesso, app removido, outra empresa). Um SKILL.md com o mesmo nome feito à mão fica como está e aparece como ignorado. Pastas que são links simbólicos não são seguidas.
  • Idempotente. Sem data no conteúdo: rodar de novo sem mudança no hub não grava nada.
  • Rode de novo depois de etus empresa usar, de ganhar ou perder acesso a um app, ou quando um app publicar versão nova. Depois de gravar, abra uma conversa nova no agente para ele carregar as skills.

etus skills limpar-globais (também clean-global) apaga de ~/.claude/skills e ~/.agents/skills só as pastas etus-<app> com a linha de etus skills sincronizar; as feitas à mão e a etus-cli ficam. Aceita --dry-run e --json.

Configurar o projeto

etus setup (também init) deixa o projeto pronto para os agentes da Etus em cada editor: regras da Etus, plugins dos perfis no Claude Code, o MCP etus e o .etus.json. Precisa de etus login. É o mesmo setup de npx github:evolution-foundation/etus-skills setup: quando a CLI está instalada, o etus-skills delega a ela.

etus setup                              # detecta o perfil; plataformas do .etus.json (ou todas, na primeira vez)
etus setup codigo,design --projeto ENG  # soma perfis; o projeto do Plane vai para o .etus.json
etus setup --target cursor,codex        # soma plataformas às do .etus.json (--substituir-alvos troca a lista)
etus setup design --com-templates       # liga também os plugins opcionais do perfil
etus setup --mcp-modo completo          # modo do MCP etus: quente (padrão), compacto ou completo
etus setup -i                           # pergunta perfil e plataformas
etus setup sincronizar                  # aplica de novo o .etus.json, sem login (também `sync`)
etus setup diagnosticar                 # confere tudo; sai com 1 se houver falha (também `doctor` e `etus doctor`)
etus doctor --maquina                   # confere as ferramentas desta máquina pelo perfil (ver abaixo)
etus --dry-run setup                    # mostra o que mudaria, sem gravar

O que cada editor recebe:

Plataforma (--target) Regras MCP etus Skills dos apps (etus ativar)
Claude Code (claude-code) plugins do perfil em .claude/settings.json .mcp.json (type: http) .claude/skills
Cursor (cursor) .cursor/rules/etus.mdc .cursor/mcp.json .agents/skills, ou .claude/skills, que o Cursor também lê
Codex (codex) AGENTS.md .codex/config.toml (só em projeto confiável) .agents/skills
Gemini CLI e Antigravity (antigravity, gemini) GEMINI.md .gemini/settings.json (httpUrl) e .agents/mcp_config.json (serverUrl) .agents/skills
GitHub Copilot (copilot) .github/copilot-instructions.md .mcp.json, o mesmo do Claude Code (VS Code e Copilot CLI) .agents/skills, ou .claude/skills, que o Copilot também lê
Windsurf (windsurf) .windsurfrules ~/.config/devin/mcp_config.json (usuário; o legado ~/.codeium/windsurf/mcp_config.json quando é ele que existe) .agents/skills
pi (pi) AGENTS.md (o mesmo bloco do Codex) e os packs do perfil no pacote etus-pi do settings.json global do pi (nunca no .pi/settings.json) .pi/mcp.json .agents/skills
  • Regras. Ficam entre <!-- etus-skills:begin --> e <!-- etus-skills:end -->; o resto do arquivo fica como está. Arquivo existente sem os marcadores só com --force.
  • AGENTS.md como fonte única. Com o Codex ou o pi entre as plataformas, as regras completas ficam só no AGENTS.md; Cursor, Copilot, Windsurf e Antigravity recebem um bloco curto mandando ler o AGENTS.md (mais as diretrizes próprias de cada um), como no etus-skills. Sem os dois, cada um recebe as regras completas. Codex e pi juntos gravam um bloco só no AGENTS.md.
  • Plugins. Os do perfil (.claude-plugin/profiles.json do etus-skills, embutido na CLI; o etus-skills passa o dele em ETUS_SKILLS_PROFILES). Só acrescenta: marketplace etus com origem customizada e plugin desligado (false) ficam como estão.
  • MCP etus. Endereço <hub>/mcp?modo=quente por padrão. No Claude Code que passa pelo AI Router o carregamento sob demanda de ferramentas fica desligado, e o modo completo poria todas as ferramentas em toda sessão; o quente traz as mais usadas mais as de catálogo (etus_buscar, etus_detalhar, etus_consultar, etus_executar). Um etus que já existe com outro endereço nunca é trocado sem --mcp-modo: o setup avisa e mantém. Os outros servidores do arquivo ficam como estão. O login é OAuth, feito no editor (o setup diz onde), e nenhum token vai para arquivo.
  • Claude Code: .mcp.json, não claude mcp add -s local. O arquivo é declarativo e versionável (o time tem o mesmo modo, sem segredo), funciona sem o claude instalado, entra no --dry-run e no diagnosticar, e serve também ao Copilot. Se houver um etus local para a pasta (~/.claude.json), que vale mais que o .mcp.json, o setup avisa como removê-lo.
  • pi: pacotes só no global. O pi instala pacote de projeto em <projeto>/.pi/git/... (ou .pi/npm/...), o AI Router apaga do system prompt todo parágrafo que contém /.pi/, e o pi lista todas as skills, as do projeto e as globais, num parágrafo só (o bloco <available_skills>): um único pacote no projeto bastava para o modelo não ver nenhuma skill. Por isso o setup soma os packs do perfil ao pacote git:github.com/evolution-foundation/etus-skills@<tag> (o manifesto pi do package.json do etus-skills; a tag é a da versão da CLI, pi::PACKAGE) no settings.json global do pi, na pasta que o pi usa agora (o PI_CODING_AGENT_DIR do ambiente ou do perfil do shell; sem ele, ~/.pi/agent, e aí o setup avisa para rodar etus router setup --pi), com o filtro skills em plugins/<pack>/skills/**; o pack nexus-stack entra como o pacote git:github.com/etusdigital/nexus-stack@v0.27.1. Só soma: packs e pacotes que já estão lá ficam, nada se repete, um pacote do etus-skills com outro ref passa para a tag da CLI, e o arquivo ganha backup quando muda. No pi, os packs do perfil valem em todos os projetos, não só neste. O pi tem um ajuste por projeto que reaproveita o pacote global sem instalar nada em .pi/ (a entrada com "autoload": false que o pi config -l grava), mas ele não está na documentação do pi e, se o pacote some do global, o pi volta a instalar em .pi/git; a CLI não usa. O AGENTS.md e o .pi/mcp.json continuam no projeto (nenhum dos dois põe /.pi/ no prompt), e as skills dos apps do etus ativar ficam em .agents/skills, fora do .pi/. Pelo mesmo motivo, não ponha skills em .pi/skills do projeto. O pi só lê .pi/mcp.json e .agents/skills depois que você confia no projeto (ele pergunta na primeira vez); o AGENTS.md vale sempre. Login do MCP: pi mcp login etus.
  • pi: migração do .pi/settings.json. Os pacotes que a CLI gravava lá até a 0.12.1 (os do etus-skills e do nexus-stack, reconhecidos pela origem) vão para o global com os packs do filtro deles e saem do projeto. O arquivo antigo fica em .pi/settings.json.bak (um só; apague depois de conferir), e o .pi/settings.json é apagado quando fica vazio. Pacotes que você pôs à mão ficam onde estão: o setup, o etus setup diagnosticar e o etus doctor --maquina avisam que eles escondem as skills e dão o comando para mover (pi remove -l <origem> && pi install <origem>). Uma entrada "autoload": false cujo pacote está no global não é acusada.
  • Não suportado. Copilot coding agent na nuvem não aceita MCP com OAuth. O Windsurf não lê MCP do projeto, por isso o etus vai no arquivo do usuário. O Codex só lê .codex/config.toml de projeto confiável.
  • Skills globais. Se houver skills de etus skills sincronizar em ~/.claude/skills ou ~/.agents/skills, o setup sugere etus skills limpar-globais: no global fica só a etus-cli, e o resto vai por projeto com etus ativar.

Diagnóstico da máquina

etus doctor --maquina [PERFIL] (também etus setup diagnosticar --maquina) confere se esta máquina tem o que o perfil precisa. Só lê: nunca instala nem altera nada, e nunca mostra chave nem token. Sai com 1 se um componente obrigatório estiver bloqueado. No fim, envia o relatório da máquina ao Etus Extensões (veja "Relatório da máquina", abaixo).

etus doctor --maquina                   # perfil desta máquina (o que o etus router setup fixou)
etus doctor --maquina produto,design    # confere outros perfis só desta vez (soma os componentes; não troca o da máquina)
etus --json doctor --maquina            # inventário em JSON, sem segredos e com ${HOME} no lugar da pasta do usuário
  • Manifesto. environment.json, ao lado do profiles.json no etus-skills (.claude-plugin/environment.json), com uma cópia embutida na CLI. ETUS_SKILLS_ENVIRONMENT aponta para outro arquivo; com ETUS_SKILLS_PROFILES, vale o environment.json da mesma pasta, se existir. Cada componente declara id, name, required (o perfil pode deixá-lo opcional em optional), o comando de versão com a mínima, as provas present, configured e works (um comando que precisa sair com 0, ou uma prova interna da CLI) e o comando sugerido em fix.
  • Quatro estados. presente (instalado na versão mínima), configurado, funcional e bloqueado (falta, versão velha ou uma prova falhou). Quando uma prova não pôde ser conferida (sem rede, por exemplo), o componente fica no último estado alcançado, com aviso.
  • Perfil codigo. git (autor configurado), gh (logado e a API aceita), Node 24, pnpm, Docker (engine respondendo), Docker Compose, Claude Code (versão mínima), RTK (hook no Claude Code e rtk gain), etus (logado e igual à última release publicada), chave do AI Router (presente, no cofre e lida pelo apiKeyHelper, pela mesma leitura do etus router status), MCP etus (no projeto ou no usuário, em modo quente, e o hub respondendo ao whoami), Orca (opcional) e pi (opcional: pacote etus-pi no settings.json, nenhum pacote que o pi carregue de .pi/ no .pi/settings.json do projeto, provider etus do AI Router com a chave pelo comando no models.json, e o etus em modo quente no mcp.json da pasta do pi).
  • Relatório da máquina. No fim do etus doctor --maquina e do etus router setup, a CLI envia ao Etus Extensões, por REST (o hub troca o seu login por um token de 5 minutos só para isso, e nenhum agente vê uma ferramenta de relatório), o mesmo inventário do --json, para o admin ver quais máquinas estão com a etus ou a receita desatualizadas e você ver as suas em "Meu perfil". Vai: um id aleatório desta máquina (gerado uma vez e guardado em machine-report.json, na pasta de configuração do etus; nunca o nome da máquina, o usuário ou um id de hardware), sistema e arquitetura, a versão do etus, as ferramentas do manifesto com versão e status, o perfil de trabalho com a origem e se está fixado, a tag da receita e os itens da seção "Receita do perfil" com o comando que corrige. Não vão o nome da máquina, o seu usuário nem segredos: a pasta do usuário vira ${HOME}, o que parece chave ou token vira [removido] e cada texto é cortado no limite do Extensões. É melhor esforço, com tempo curto: sem login, fora do ar ou recusado, o comando segue igual e o código de saída não muda. Sai uma linha no stderr dizendo que foi enviado (--quiet esconde); na primeira vez, um aviso do que é enviado; quando o hub ou o Extensões não liberam o envio (no_app_access ou permission_required: app Etus Extensões não liberado para você, falta da permissão "Enviar o relatório da máquina" ou sem empresa ativa), um aviso (uma vez só) com essas causas. Com login só de leitura (etus login --somente-leitura), não tenta enviar. Não envia com --dry-run, com --sem-relatorio (também --no-report), com ETUS_NO_MACHINE_REPORT=1 nem no etus doctor --maquina <PERFIL>, que confere outro perfil só daquela vez. ETUS_DEBUG=1 mostra no stderr por que não foi enviado.
  • Checagens extras. Hook do RTK registrado mais de uma vez no ~/.claude/settings.json; nexus-stack@nexus-stack e nexus-stack@etus ligados juntos; ANTHROPIC_AUTH_TOKEN ou ANTHROPIC_API_KEY exportados no shell passando na frente do apiKeyHelper; skills globais geradas sobrando (sugere etus skills limpar-globais); worktrees de repositórios em ~/Projects cujo branch já está no principal ou teve o remoto apagado (só lista).

Formato do .etus.json

Um arquivo só, na raiz do repositório, versionável, lido e gravado pelo etus setup, pelo etus ativar e pelo etus-skills. Chaves que a CLI não conhece ficam como estão.

{
  "project": "ENG",
  "workspace": "etus",
  "perfil": ["codigo"],
  "targets": ["claude-code", "cursor", "codex"],
  "mcp": { "modo": "quente" },
  "active": {
    "apps": ["etusradar"],
    "packs": { "stalk": ["etusclone", "etuspersonas", "etusradar"] },
    "agents": { "stalk": ["etusradar"] }
  }
}
Chave O que é Quem grava
project Chave do projeto no Plane (maiúsculas); sem ela, as regras usam o nome do repositório setup --projeto
workspace Workspace do Plane (padrão etus) setup --workspace
perfil Lista de perfis do etus-skills; cada setup <perfil> soma setup
targets Plataformas: claude-code, cursor, codex, antigravity, copilot, windsurf, pi. Ausente num arquivo antigo do etus-skills com perfil: só claude-code setup --target
mcp.modo quente, compacto ou completo; ausente vale quente setup --mcp-modo
active Apps, pacotes e agentes ativos neste projeto etus ativar e etus desativar

Ativar apps e agentes por projeto

As skills globais entram em toda sessão, de qualquer projeto. etus ativar (também activate) põe só o que importa dentro do projeto:

etus ativar radar personas          # apps, pelos nomes de `etus apps`
etus ativar arbitragem              # um pacote: a categoria do app no hub
etus ativar stalk                   # pacote fixo radar, personas e clone (quando nenhuma categoria do hub tem esse nome)
etus ativar agente:stalk            # um agente do Etus Agentes e os apps das ferramentas dele
etus ativar                         # gera de novo tudo o que já está ativo
etus ativar --agente todos radar    # .claude/skills e .agents/skills (padrão: as plataformas do .etus.json)
etus ativos [--json]                # o que está ativo e por quê (também `active`)
etus desativar arbitragem           # tira e apaga os arquivos gerados (também `deactivate`)
  • Raiz e estado. A raiz é a do repositório git (ou a pasta atual fora de um), a mesma do etus setup. O que está ativo fica na chave active do .etus.json da raiz (apps, packs, agents); as outras chaves do arquivo ficam como estão. O .etus.json é versionável: o time ativa a mesma lista.
  • App. Grava .claude/skills/etus-<app>/SKILL.md (Claude Code) e .agents/skills/etus-<app>/SKILL.md (a pasta aberta do padrão SKILL.md, lida por Codex, pi, Cursor, Copilot, Windsurf, Gemini CLI e Antigravity) conforme as plataformas do .etus.json. Cursor e Copilot também leem .claude/skills, então sem Codex, pi, Gemini, Antigravity nem Windsurf a cópia em .agents/skills não é feita. Sem targets no .etus.json: Claude Code, mais .agents/skills se o projeto tem AGENTS.md ou .agents. O conteúdo é o da mesma geração de etus skills sincronizar: comandos, permissões, e o guia do app (para que serve, receitas e cuidados) vindo de etus_detalhar com app:<prefixo>. App sem guia publicado sai só com os comandos, e o etus ativar diz quais.
  • Pacote. Uma categoria de apps do hub (arbitragem, produtividade...), ou o pacote fixo stalk (radar, personas e clone) quando nenhuma categoria tem esse nome. A lista de membros é gravada no .etus.json.
  • Agente. agente:<slug> (ou agent:<slug>) lê o agente pelo Extensões (extensoes__catalogo_ler com agente:agentes:<slug>), grava .claude/agents/etus-<slug>.md com as instruções dele e ativa os apps dos prefixos das ferramentas dele. Sem acesso, o etus mostra o link da ação necessária (código 5) ou o motivo (código 4).
  • Arquivos gerados fora do git. Cada arquivo gerado entra no .git/info/exclude, como o etus trabalho faz com o settings.local.json: o conteúdo depende da empresa ativa e do acesso de cada pessoa, então cada um gera o seu a partir do .etus.json. Arquivo que o repositório já versiona fica versionado.
  • Só mexe no que gerou. Os arquivos levam a linha <!-- gerado por etus ativar: não edite, rode o comando de novo -->. Uma skill de etus skills sincronizar --projeto com o mesmo nome é atualizada, mas só as de etus ativar são apagadas; as feitas à mão aparecem como ignoradas.
  • Dica. Se houver skills globais de etus skills sincronizar, o fim do etus ativar sugere etus skills limpar-globais.

Etiqueta de trabalho

etus trabalho marca em qual item do Plane você está trabalhando, para o uso de IA ser medido por empresa, projeto e item no painel do roteador. Contrato completo: docs/specs/work-context.md no repositório do hub.

etus trabalho ENG-123                         # marca o item; empresa = empresa ativa do etus
etus trabalho ENG-123 --fase review           # plan, impl, review ou qa
etus trabalho ENG-123 --projeto hub:<id>      # projeto da Etus: hub:<id>, chat:<id> ou rotina:<id>
etus trabalho ENG-123 --empresa <id>          # outra empresa (id da empresa no hub)
etus trabalho --etiqueta estudo-cache         # trabalho fora do Plane
etus trabalho --fase qa                       # só troca a fase do que já está marcado
etus trabalho                                 # o mesmo que `etus trabalho mostrar`
etus trabalho mostrar                         # estado e a linha do header que vai sair
etus trabalho cabecalho                       # só a linha x-etus-work, para hooks e scripts
etus trabalho limpar                          # apaga a marca (volta ao que o branch disser)
etus trabalho handoff                         # comentário de passagem no item do Plane (ver abaixo)
  • Persiste até mudar. Dentro de um repositório, o estado fica em <git dir>/etus-work (um por worktree); fora, em ~/.claude/etus-work (ou $CLAUDE_CONFIG_DIR/etus-work). O formato é o mesmo do utilitário do instalador do roteador (issue, phase, tag, workspace), com as chaves novas company e project.
  • Empresa automática. Vem da empresa ativa guardada no cache do catálogo (etus empresa usar), sem chamar a rede. Sem login nem --empresa, fica vazia.
  • Item pelo branch. Sem item marcado, vale a chave escrita no branch (feat/eng-123-upload vira ENG-123), como no hook.
  • Versão do header. v=2 só quando há empresa ou projeto (c, pj e via); senão v=1, que todo leitor antigo entende. A linha tem no máximo 256 caracteres: se passar, cortam-se a etiqueta, o repositório e o workspace, nunca empresa, projeto ou item.
  • --json em mostrar traz version, item, planeProject, project, company, tag, repo, workspace, phase, source, header e statePath.

Passagem de trabalho (handoff)

etus trabalho handoff grava no item marcado (ou --item ENG-123) um comentário para a próxima pessoa, pela ferramenta plane__add_comment do app Plane:

etus trabalho handoff                                  # no terminal, pergunta o que faltar e pede confirmação
etus trabalho handoff --objetivo "Doctor da máquina" --aceite "roda nesta máquina" \
  --evidencia "cargo test: 0 falhas" --falta "release" --bloqueio "Docker parado" \
  --recurso "porta 5011" --reconferir "saída do doctor no Linux" --yes
etus trabalho handoff --dry-run                        # mostra a chamada, sem gravar

O comentário traz objetivo e critério de aceite, o commit conferido (git rev-parse HEAD, branch e quantos arquivos estão alterados sem commit), a evidência (comandos rodados e resultado), o que falta, bloqueios, recursos ocupados (a worktree atual entra sozinha) e o que reconferir. O que não foi informado aparece como "não informado". Como toda escrita da CLI, pede confirmação no terminal e, fora dele, só grava com --yes.

Contrato dos hooks

Os comandos que o etus router setup registra no Claude Code (etus hook em SessionStart, CwdChanged, FileChanged e UserPromptSubmit, e o etus statusline) falham abertos: com stdin vazio, null, JSON inválido, campos faltando ou o config.json do etus quebrado, saem com 0 sem escrever nada no stdout além do JSON do hook (ou da linha da statusline). O túnel da saída pela casa (etus router tunel) nunca escreve no stdout e só sai com 0 ou 2. tests/hooks_contract.rs confere tudo isso.

Como o header chega ao Claude Code

  1. O Claude Code manda em toda chamada os headers de ANTHROPIC_CUSTOM_HEADERS (um por linha, Nome: valor), lidos do env dos arquivos de configuração.
  2. Com o comando etus router setup, a CLI configura o Claude Code nativamente, registrando hooks em SessionStart, CwdChanged, FileChanged (no .git/HEAD) e UserPromptSubmit. A cada evento, o hook lê o arquivo de estado e reescreve ANTHROPIC_CUSTOM_HEADERS em .claude/settings.local.json do projeto com a linha x-etus-work, mantendo os outros headers que você já tinha.
  3. Além disso, comandos como etus trabalho <item> atualizam imediatamente o .claude/settings.local.json da pasta atual (garantindo também que o arquivo seja ignorado no .git/info/exclude), de modo que as alterações passem a valer a partir do próximo prompt sem reiniciar o Claude Code. O roteador remove o header antes de chamar o provedor.

Configuração do AI Router

etus login                                   # uma vez por computador
etus router setup                            # pega a chave deste computador pelo login, sem colar nada
etus router setup --model dev-continuo       # define modelo principal e rápido
etus router setup <SUA_CHAVE>                # plano B: informa a chave à mão (também OMNIROUTE_API_KEY)
etus router setup --pi                       # também configura o pi, mesmo sem ele no PATH (--sem-pi pula)
etus router setup --perfil-trabalho design   # troca o perfil desta máquina e aplica a receita dele (regras, políticas, automações e modelo)
etus router setup --sem-regras               # não mexe no CLAUDE.md nem nos AGENTS.md
etus router setup --sem-relatorio            # não envia o relatório da máquina ao Etus Extensões no fim
etus --dry-run router setup                  # mostra o que mudaria, sem gravar nada e sem buscar a chave
etus router status                           # inspeciona a configuração atual e a origem da chave
etus router                                  # o mesmo que etus router status
etus router chave                            # imprime a chave (é um segredo; quem chama é o Claude Code)
etus router eval -- <args do plugin eval>    # roda o claude plugin eval pelo roteador (também avaliar)
  • Uma chave por computador. Sem --chave, o etus router setup troca a sessão do etus login por um token de repasse do hub (60 s, só para o app router-panel) e pede ao AI Router uma chave para este computador, com o nome da máquina (hostname). A chave aparece no painel do roteador, em Minha chave, e pode ser revogada lá sem afetar os outros computadores. O uso, o orçamento e os limites continuam os da sua chave pessoal: o roteador troca a chave do computador pela sua.
  • Rodar de novo troca a chave. Cada etus router setup pede uma chave nova e revoga a anterior deste computador (sessões abertas do Claude Code pegam a nova em até 5 minutos).
  • Sem texto puro. A chave fica no mesmo lugar do token do login (arquivo 0600 por padrão, ou o chaveiro com etus config armazenamento chaveiro). O ~/.claude/settings.json recebe "apiKeyHelper": "etus router chave" e perde o ANTHROPIC_AUTH_TOKEN (e o ANTHROPIC_API_KEY). Uma chave antiga em texto puro no settings.json vai para o cofre quando não há login.
  • Como o Claude Code lê. O Claude Code roda o apiKeyHelper ao abrir e a cada 5 minutos (padrão do CLAUDE_CODE_API_KEY_HELPER_TTL_MS) e manda o valor em Authorization: Bearer e em x-api-key para o ANTHROPIC_BASE_URL. ANTHROPIC_AUTH_TOKEN ou ANTHROPIC_API_KEY exportados no shell têm prioridade sobre o apiKeyHelper; o etus router status avisa quando isso acontece. Com o chaveiro no macOS, o apiKeyHelper não roda num terminal e falha: use o arquivo.
  • etus router chave imprime um segredo. Recusa quando a saída é um terminal (--force imprime mesmo assim). Não cole a chave em chat, log ou arquivo versionado.
  • claude plugin eval pelo roteador. O avaliador de plugins roda cada caso num Claude Code isolado, com HOME e pasta de configuração próprios: ele herda as variáveis ANTHROPIC_* (do shell e do env do settings.json), mas não o apiKeyHelper, e o caso para em "Not logged in". etus router eval -- <argumentos> lê a chave deste computador do cofre e roda claude plugin eval <argumentos> com ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL (o roteador da chave) e os modelos do env do settings.json (padrão dev-continuo e rapido) só no ambiente daquele processo: nada é gravado em arquivo nem impresso, e o Claude Code não repassa o ANTHROPIC_AUTH_TOKEN ao Bash do agente. Ponha -- antes dos argumentos (o --json do eval não vira o --json do etus). Sem chave no cofre e sem o apiKeyHelper do etus, roda o claude plugin eval sem mudar nada; com o apiKeyHelper do etus e sem chave, pede etus router setup. Exemplo: etus router eval -- plugins/etus-produto --case nao-se-aplica --trust-plugin --no-publish.
  • pi. Com o pi no PATH (ou um settings.json na pasta dele), ou com --pi, o etus router setup também configura o pi na pasta ~/.etus/pi (ou no PI_CODING_AGENT_DIR que você já definiu no ambiente ou no perfil do shell), com backup de cada arquivo que muda: no models.json, o provider etus (anthropic-messages, a URL do roteador, os modelos principal com raciocínio e rápido, e "apiKey": "!etus router chave", que o pi roda a cada requisição); no settings.json, defaultProvider etus, defaultModel igual ao --model (sem ele, o ai.model do perfil), defaultThinkingLevel do ai.thinking do perfil (hoje high), enableInstallTelemetry false, o pacote etus-pi com as skills do etus-core (packs que você somou ao filtro ficam), o npm:pi-subagents@0.79.1 (o pi instala na primeira execução) e, em skills, as pastas de ~/.claude/skills uma a uma; no mcp.json, o etus em modo quente (um etus que já existe com outro endereço fica como está). A chave nunca vai para esses arquivos: uma chave em texto puro no provider etus é trocada pelo comando. Os outros providers, servidores e pacotes ficam como estão. O etus router status mostra o pi também, com a pasta que ele usa e de onde ela veio.
  • Por que ~/.etus/pi. O AI Router apaga do system prompt todo parágrafo que contém /.pi/, e o pi lista as skills dos pacotes (instalados em <pasta do pi>/git/...) num parágrafo só: com a pasta padrão ~/.pi/agent, o modelo não vê nenhuma skill de pacote. Por isso o setup usa ~/.etus/pi e grava export PI_CODING_AGENT_DIR="$HOME/.etus/pi" num bloco marcado (# >>> etus pi >>>) no perfil do shell: o do seu $SHELL (~/.zshrc, ~/.bashrc ou ~/.config/fish/config.fish) e os outros desses que já existem (no macOS, também o ~/.bash_profile). Rodar de novo não duplica o bloco. Um PI_CODING_AGENT_DIR que você já definiu (no ambiente ou fora do bloco, no perfil) é respeitado e o perfil fica como está. No Windows, a variável vai para as variáveis do usuário com setx. Abra um terminal novo depois do setup.
  • Migração do ~/.pi/agent. Na primeira vez, o setup copia para a pasta nova o que existe no ~/.pi/agent: settings.json, models.json, mcp.json, auth.json, mcp-auth.json, models-store.json, keybindings.json, AGENTS.md, SYSTEM.md, APPEND_SYSTEM.md e as pastas extensions (inclusive as do Orca), git e npm (os pacotes instalados; sem elas o pi instala de novo), skills, prompts, themes, tools e bin. O que já existe na pasta nova não é sobrescrito, e o ~/.pi/agent fica como estava (serve de backup, com as sessions e a instalação do próprio pi). O extensions/etus.ts do antigo setup-pi.sh não é copiado (a extensão do pacote etus-pi já manda a sessão) e, se estiver na pasta do pi, é apagado com backup.
  • Skills do Claude Code no pi. O settings.json não aponta para ~/.claude/skills inteira: o ~/.claude/skills/synced tem uma pasta por conta do app do Claude com as mesmas skills, e o pi acusa conflito. Entram as pastas próprias (com SKILL.md), uma só do synced (a de lastUpdated mais recente no manifest.json; se você já escolheu uma, ela fica) e nada que já esteja em ~/.agents/skills, que o pi lê sozinho. Uma entrada com a pasta inteira ou com outra pasta do synced sai.
  • etus doctor --maquina e o pi. Acusa falha quando a pasta que o pi usa contém /.pi/, ou quando o .pi/settings.json do projeto onde você roda tem pacotes que o pi carrega de .pi/ (diz quais e como mover), e confere os pacotes etus-pi e pi-subagents. O etus router status mostra o mesmo aviso (--json: projectPackagesHidden).
  • Sem acesso. Sem a permissão "Usar a IA" do AI Router, o comando mostra o link para pedir acesso no hub e sai com código 5; depois de aprovado, rode de novo.
  • etus logout revoga a chave deste computador no roteador e apaga a do cofre (uma chave informada à mão só é apagada). O Claude Code volta a usar o roteador depois de etus login e etus router setup. Também desliga a saída pela casa.

Receita do perfil na máquina

O etus router setup aplica, no nível do usuário, a receita do perfil de trabalho que o etus-skills publica: regras, políticas, automações e modelo (docs/convencoes.md do etus-skills, seções Regras, Políticas, Automações e Perfil v2). É ele que faz isso porque é o comando da máquina: já grava o ~/.claude/settings.json, os hooks do etus e a pasta do pi, e é o que o etus doctor --maquina manda rodar. O etus setup continua cuidando do projeto (e, no pi, dos packs do perfil no settings.json global).

  • Perfil da máquina. A receita grava arquivos que valem para a máquina toda (o bloco do ~/.claude/CLAUDE.md, o permissions.deny do Claude Code, o settings.json do pi), então ela segue um perfil fixo da máquina, guardado em recipe-state.json na pasta de configuração do etus, e não a pasta onde você roda o comando: rodar o setup num repositório de design e depois num de código não troca a receita.

    • Na primeira vez, sem perfil guardado, o etus router setup pergunta qual perfil usar (a lista vem da receita). A sugestão é, primeiro, o perfil de trabalho padrão que o hub guarda para você na empresa ativa (definido para você, para um time seu ou para um tipo de acesso, em Administração > Perfil de trabalho; GET /api/v1/mcp/work-profile, com o mesmo login do MCP), quando a receita desta versão do etus conhece esse perfil; depois, a da pasta atual: o .etus.json do projeto, a detecção pelo projeto ou codigo. Sem login, com o hub fora do ar (espera até 3 s) ou num hub sem esse recurso, a sugestão é a da pasta, sem aviso (ETUS_DEBUG=1 diz por quê). Fora do terminal, com --json ou com --yes, ele fixa essa sugestão e diz isso na saída (Perfil: ... fixado como perfil desta máquina; no --json, receita.fixadoAgora), com a origem: padrão do hub na primeira configuração (time Plataforma, empresa Etus) ou sugestão da pasta na primeira configuração (...).
    • Depois, o perfil da máquina só muda com --perfil-trabalho <perfil> (também --work-profile; vários com vírgula), que aplica a receita nova e guarda a escolha. O .etus.json e a detecção da pasta atual não mudam mais o setup da máquina, o etus doctor --maquina nem o etus router status.
    • O etus doctor --maquina e o etus router status dizem qual perfil da máquina está valendo e de onde veio (origemDoPerfil e perfilFixado no --json). Se o .etus.json do projeto atual pede outro perfil, eles avisam numa linha Projeto: (notaDoProjeto), só como informação: não é diferença. Se o padrão do hub para você é outro perfil, o etus doctor --maquina avisa numa linha Hub: (notaDoHub), também só como informação, com o comando para trocar (etus router setup --perfil-trabalho <perfil>): o padrão do hub nunca troca um perfil já fixado. Com o perfil fixado, só o doctor pergunta ao hub; o etus router status e o etus router setup não. Enquanto nenhum setup fixou o perfil, os dois usam a sugestão (o padrão do hub ou a pasta atual) e dizem que o perfil ainda não foi fixado. O etus doctor --maquina <PERFIL> confere outro perfil só daquela vez, sem trocar o da máquina.
    • Se o perfil guardado saiu da receita (numa versão nova do etus-skills), o etus router setup e o etus doctor --maquina param com código 2 e a lista de perfis válidos, pedindo etus router setup --perfil-trabalho <perfil>; o etus router status mostra a mesma mensagem na linha da receita (receita.erro).
    • O etus setup do projeto continua usando o .etus.json e a detecção do projeto, como antes. Não confunda --perfil-trabalho com o --perfil global, que é o perfil de acesso enviado ao hub.
  • Regras. As regras do perfil, em ordem (order, depois id) e filtradas pelos targets de cada uma, vão para um bloco gerenciado:

    <!-- etus: início (a CLI atualiza este trecho) -->
    ...regras do perfil...
    <!-- etus: fim -->
    

    no ~/.claude/CLAUDE.md (Claude Code; CLAUDE_CONFIG_DIR vale), no ~/.codex/AGENTS.md (Codex, quando a pasta dele existe ou o codex está no PATH; CODEX_HOME vale) e no AGENTS.md da pasta do pi (quando o pi é configurado). Só o bloco é trocado; o texto fora dele nunca muda. Arquivo sem o bloco ganha o bloco no fim; arquivo que não existe é criado. Antes de mudar, fica um backup (CLAUDE.md.bak.<data>, os 5 mais novos). Rodar de novo sem mudança na receita não grava nada. Um link (o AGENTS.md do pi apontando para o CLAUDE.md, por exemplo) é seguido: o arquivo de verdade é gravado uma vez só, com as regras das ferramentas que o leem. Marcadores repetidos ou fora de ordem deixam o arquivo como está, com aviso. --sem-regras pula esse passo.

  • Políticas. As políticas do perfil são somadas (listas unidas; stopGuard ligado em qualquer uma fica ligado). No Claude Code viram Bash(...), Read(...) e Edit(...) no permissions.deny do ~/.claude/settings.json. O etus só acrescenta: o que você ou um repositório puseram fica, e o que o etus pôs fica anotado em recipe-state.json na pasta de configuração do etus, para ele tirar só as dele quando a receita mudar. No pi, a extensão etus do pacote etus-pi lê as políticas da empresa do próprio pacote (por isso a tag do pacote acompanha a receita) e também o permissions.deny do Claude Code; com stopGuard, o setup religa a extensão se o filtro extensions da entrada do pacote a desligou. O Codex não tem uma lista de negação que a CLI possa gravar: as políticas não valem lá, e o setup diz isso.

  • Automações. Cada automação do perfil diz como cada ferramenta instala (install.<ferramenta>.kind). settings cujo setup é do etus vira hook no ~/.claude/settings.json (hoje o etus hook do x-etus-work, com a statusline; --no-work-tags desliga); settings de outra ferramenta (o rtk init -g do RTK) não é instalado pelo etus, só conferido; native não precisa de nada; package vira pacote no settings.json do pi (etus-pi na tag da CLI e pi-subagents); extension depende do pacote que traz a extensão.

  • Modelo. ai.model do perfil vira o modelo do Claude Code (ANTHROPIC_MODEL e afins) e o defaultModel do pi, a menos que você passe --model. ai.thinking vira o defaultThinkingLevel do pi; o etus não grava nível de raciocínio no Claude Code.

  • Diferenças. etus doctor --maquina mostra, numa seção "Receita do perfil", o que difere da receita (bloco de regras faltando ou desatualizado, regras de negação faltando, automação faltando, pacote do pi em outra versão, extensão desligada, modelo ou raciocínio diferente, componente de ambiente exigido pelo perfil bloqueado), cada uma com o comando que corrige; no --json, em receita.itens. O etus router status resume numa linha. Diferença é aviso: não muda o código de saída.

  • Camadas. As regras e políticas se aplicam da mais larga para a mais estreita, e cada camada só acrescenta: empresa (as políticas do etus-core, que todo perfil lista), perfil (as regras e políticas que o perfil escolhe dos packs dele), repositório (o CLAUDE.md ou AGENTS.md do projeto e o .claude/settings.json dele, com seu permissions.deny e seus hooks Stop) e pessoa (o seu texto fora do bloco gerenciado e as suas entradas no permissions.deny do usuário). Nenhuma camada desliga o que outra negou; no Claude Code um allow também não vence um deny.

  • De onde vem a receita. De uma cópia embutida na CLI (src/setup-recipe.json, gerada de uma tag do etus-skills junto com setup-profiles.json e setup-environment.json). ETUS_SKILLS_DIR aponta para um clone local do etus-skills e lê dele as regras, políticas, automações e perfis. Quando o Etus Extensões passar a entregar a receita, ela entra pelo mesmo Recipe::load (src/recipe.rs).

Saída pela casa

Para quem o admin escolheu no painel do AI Router, as chamadas à Anthropic da conta Claude dedicada a essa pessoa saem pelo IP de casa dela. O computador abre uma conexão de saída até o roteador e só repassa bytes TLS entre o roteador e a Anthropic: nunca vê o token da conta.

etus router setup --saida-casa               # setup normal e liga a saída pela casa neste computador
etus router saida-casa ligar                 # liga num computador já configurado
etus router saida-casa status                # agente, estado da conexão e IP de saída visto pela Anthropic
etus router saida-casa desligar              # para o agente e apaga o arquivo dele
  • Só com a chave pelo login. A conexão usa a chave deste computador (sk-etd-...). Com --chave ou OMNIROUTE_API_KEY o comando recusa: rode etus login e etus router setup --saida-casa. Com o chaveiro no macOS também recusa, porque o agente roda sem terminal: use etus config armazenamento arquivo.
  • Um agente do sistema, não um hook. No macOS, um LaunchAgent do usuário (~/Library/LaunchAgents/io.etus.saida-casa.plist); no Linux, um serviço systemd --user (~/.config/systemd/user/etus-saida-casa.service). Ele fica de pé independente de quantas sessões do Claude Code estão abertas (uma conexão por computador), volta sozinho depois de queda, suspensão ou troca de rede e também atende o chat da Etus no navegador. O hook SessionStart só rodaria quando uma sessão abre e precisaria de trava contra duplicatas. Windows ainda não tem suporte.
  • Lista fechada. Só api.anthropic.com, console.anthropic.com, platform.claude.com e claude.ai, na porta 443. Qualquer outro destino é recusado sem consultar o DNS.
  • O admin também liga. Enquanto o admin não liga a saída pela casa para você no painel, o status mostra "aguardando o admin" e o agente tenta de novo a cada 5 minutos.
  • Estado e log. saida-casa.json e saida-casa.log (até 256 KB, depois saida-casa.log.1) na pasta de configuração do etus (~/Library/Application Support/etus no macOS, ~/.config/etus no Linux, ou ETUS_CONFIG_DIR). Nenhum dos dois guarda a chave.
  • Para testar. etus router tunel roda o túnel em primeiro plano; ETUS_HOME_EGRESS_URL troca o endereço do túnel (por exemplo ws://127.0.0.1:20140/etus/home-egress). ETUS_SAIDA_CASA_AGENT_DIR troca a pasta do agente e ETUS_SAIDA_CASA_NO_SERVICE=1 não chama launchctl nem systemctl.

Nesta CLI, etus work ... faz o dispatch para o app Etus Work; por isso o rastreamento de tarefas usa o comando canônico etus trabalho.

Configuração

O quê Como
Hub --hub URL, ETUS_HUB_URL ou hub_url no arquivo (padrão https://apps.etus.io)
Perfil --perfil ID ou etus perfil usar ID / etus perfil limpar; vai ao gateway como ?perfil=ID
Cache do catálogo 5 minutos (cache_ttl_secs no arquivo); etus atualizar renova na hora
Arquivo config.json na pasta de configuração do usuário (etus config mostra o caminho)
Pastas ETUS_CONFIG_DIR troca a pasta inteira
Perfis e ambiente ETUS_SKILLS_PROFILES (lista de perfis) e ETUS_SKILLS_ENVIRONMENT (manifesto de ambiente); sem eles, as cópias embutidas
Receita do perfil ETUS_SKILLS_DIR (clone local do etus-skills: regras, políticas, automações e perfis); sem ele, a cópia embutida
Links de ação etus config abrir-links sim ou nao (open_actions no arquivo), ou ETUS_OPEN_ACTIONS=1 ou 0, que vale mais; padrão não abrir sozinho
Relatório da máquina --sem-relatorio no etus doctor --maquina e no etus router setup, ou ETUS_NO_MACHINE_REPORT=1, não envia
Diagnóstico ETUS_DEBUG=1 mostra no stderr o que foi pulado sem aviso (o padrão do hub, o relatório da máquina)
Token etus config armazenamento arquivo ou chaveiro (token_store no arquivo), ou ETUS_TOKEN_STORE=file ou keychain, que vale mais; padrão arquivo no macOS e no Linux, Gerenciador de Credenciais no Windows

Autocompletar: etus completions zsh > ~/.zfunc/_etus (também bash, fish, powershell, elvish).

Desenvolvimento

cargo test                                  # unitários e integração com um gateway MCP falso
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo test keychain -- --ignored            # testa o chaveiro do sistema de verdade
scripts/update-recipe.sh v2026.10.18.9      # copia receita, perfis e ambiente de uma tag do etus-skills
scripts/update-recipe.sh v2026.10.18.9 --check   # só falha se as cópias embutidas estiverem velhas

A tag do pacote etus-pi (pi::PACKAGE) acompanha a tag da receita: o teste package_tag_follows_the_snapshot confere, e embedded_snapshot_covers_every_embedded_profile falha quando um perfil embutido cita uma regra, política ou automação que a receita não tem. ETUS_SKILLS_GIT troca o repositório clonado pelo script (um clone local, por exemplo).

Próximo passo: assinatura estável

A correção certa para quem quer o chaveiro, e para distribuir o binário, é assinar com um certificado Developer ID (e notarizar). Com uma identidade de código estável entre versões, o macOS reconhece o etus atualizado como o mesmo programa e não pede permissão de novo a cada atualização. Hoje o binário é assinado ad hoc (codesign -dv mostra Signature=adhoc), por isso o arquivo é o padrão. Fica como follow up: assinar e notarizar no fluxo de release e, depois disso, reavaliar o chaveiro como padrão no macOS.

Publicar uma versão

Sem CI: os binários saem de um Mac com Docker (buildx) e rustup.

  1. Suba a versão no Cargo.toml (e no Cargo.lock) e escreva a seção dela no CHANGELOG.md.

  2. scripts/release.sh X.Y.Z gera em dist/vX.Y.Z/:

    • etus-aarch64-macos e etus-x86_64-macos, nativos, assinados ad hoc (codesign -s -);
    • etus-x86_64-linux e etus-aarch64-linux, estáticos (musl), com docker buildx build --platform linux/amd64|linux/arm64 e scripts/release.Dockerfile;
    • SHA256SUMS, VERSION e um <binário>.sha256 por binário.

    No fim, confere o sha256, a assinatura, que os de Linux são estáticos e que cada binário responde etus X.Y.Z a --version (os de Linux com docker run --platform, o Intel com Rosetta).

  3. Depois do merge na main, scripts/release.sh X.Y.Z --publish (com a main atualizada e limpa) gera tudo de novo e cria a release vX.Y.Z com esses arquivos e as notas do CHANGELOG.md. O etus atualizar-cli e o instalador procuram exatamente esses nomes.

About

CLI etus: os apps da Etus no terminal, gerados do MCP do hub

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages