@neetru/cli 2.33.0 → 2.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +38 -3
  3. package/dist/commands/admin/sdk-credential.js +3 -1
  4. package/dist/commands/ai-models.d.ts +2 -1
  5. package/dist/commands/ai-models.js +4 -5
  6. package/dist/commands/ai.d.ts +12 -2
  7. package/dist/commands/ai.js +47 -38
  8. package/dist/commands/auth.d.ts +3 -2
  9. package/dist/commands/auth.js +96 -57
  10. package/dist/commands/build.js +35 -4
  11. package/dist/commands/chat.d.ts +24 -6
  12. package/dist/commands/chat.js +43 -7
  13. package/dist/commands/code.js +11 -2
  14. package/dist/commands/config.js +15 -13
  15. package/dist/commands/db-frontdoor.js +10 -5
  16. package/dist/commands/db.d.ts +8 -2
  17. package/dist/commands/db.js +13 -6
  18. package/dist/commands/deploy.d.ts +14 -5
  19. package/dist/commands/deploy.js +376 -55
  20. package/dist/commands/dev-context.js +1 -1
  21. package/dist/commands/environments-repoint.d.ts +49 -0
  22. package/dist/commands/environments-repoint.js +219 -0
  23. package/dist/commands/init.js +7 -1
  24. package/dist/commands/new.js +14 -0
  25. package/dist/commands/secrets.d.ts +6 -4
  26. package/dist/commands/secrets.js +14 -7
  27. package/dist/commands/ui.js +1 -1
  28. package/dist/commands/workspaces.js +16 -1
  29. package/dist/index.js +47 -7
  30. package/dist/lib/ai/auth-discovery.d.ts +9 -12
  31. package/dist/lib/ai/auth-discovery.js +43 -145
  32. package/dist/lib/ai/orchestrator.d.ts +13 -6
  33. package/dist/lib/ai/orchestrator.js +46 -246
  34. package/dist/lib/ai/provedores.d.ts +57 -0
  35. package/dist/lib/ai/provedores.js +66 -0
  36. package/dist/lib/build-targets.d.ts +48 -10
  37. package/dist/lib/build-targets.js +64 -10
  38. package/dist/lib/cli-write.js +6 -3
  39. package/dist/lib/code-agent-gateway.d.ts +2 -2
  40. package/dist/lib/code-agent-gateway.js +2 -2
  41. package/dist/lib/code-agent-settings.d.ts +2 -2
  42. package/dist/lib/code-agent-settings.js +2 -4
  43. package/dist/lib/code-agent-types.d.ts +3 -1
  44. package/dist/lib/config.d.ts +4 -7
  45. package/dist/lib/config.js +9 -6
  46. package/dist/lib/deploy-recusa-de-destino.d.ts +9 -0
  47. package/dist/lib/deploy-recusa-de-destino.js +98 -0
  48. package/dist/lib/deploy-render-probe.d.ts +117 -0
  49. package/dist/lib/deploy-render-probe.js +475 -0
  50. package/dist/lib/deploy-vm-um-app.d.ts +62 -0
  51. package/dist/lib/deploy-vm-um-app.js +83 -0
  52. package/dist/lib/segredo-produto.d.ts +25 -5
  53. package/dist/lib/segredo-produto.js +45 -10
  54. package/dist/lib/sse.d.ts +4 -5
  55. package/dist/lib/sse.js +4 -5
  56. package/package.json +1 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,66 @@ Convenção: [Keep a Changelog](https://keepachangelog.com/) + [SemVer](https://
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.34.0] — 2026-10-26 (`neetru environments repoint` e Dockerfile gerado certo para monorepo e pnpm/yarn/bun)
8
+
9
+ > **Por que MINOR.** Entra um comando novo (`neetru environments repoint`), sem quebrar comportamento existente; o resto são correções. O `repoint` só funciona contra um Core com a rota `POST /api/cli/v1/products/{slug}/environments/{env}/server` no ar (neetru-core ca18fea); antes disso o Core responde 404.
10
+
11
+ ### Adicionado
12
+
13
+ - **`neetru environments repoint <slug> <staging|prod> --to <serverId> --expect-from <serverId> --reason "<motivo>"`: re-aponta o ambiente de um produto para outra VM sem PATCH manual no Firestore** (bug_7446f7f2; precisa do Core com a rota `POST /api/cli/v1/products/{slug}/environments/{env}/server` no ar). Antes nenhum comando mudava a VM que o ambiente aponta: só o Dev Core, à mão, o que travava o go-live quando ele estava fora. Só o ponteiro muda — o app continua onde está até o próximo `neetru deploy --target vm --env <env> --server <serverId>`, e a promoção de staging para prod passa a usar a VM apontada. `--dry-run` mostra os requisitos sem gravar e sem MFA (VM nova pronta, da frota, aceitando app, com espaço e IP público; nenhum outro ambiente do produto nela; banco do ambiente noutra VM só com a porta de entrada ligada para a VM nova). Fora dele, admin + MFA (`--mfa-token` ou a sessão de `neetru auth mfa-session`). `--expect-from` protege contra mudar um estado que você não viu: se o ambiente já aponta outra VM, nada é gravado. Cada recusa do Core (`REPOINT_*`) sai com a mensagem do Core e o que fazer. O grupo novo se chama `environments` porque `neetru env` já cuida do NEETRU_ENV local e das variáveis do Cloud Run.
14
+
15
+ ### Corrigido
16
+
17
+ - **`neetru deploy --target cloud-run` sem Dockerfile: o template gerado para `--stack=node|nextjs` passa a usar o gerenciador de pacote certo e o layout aninhado do monorepo** (bug_1dcf8e03, achado na revisão do cli#106). O template supunha sempre app único com npm: `COPY package*.json ./` + `RUN npm ci --omit=dev` e `CMD node server.js`/`.next/standalone` na raiz de `/app`. Num projeto pnpm/yarn/bun sem `package-lock.json` o `npm ci` falhava de cara; em monorepo o contexto era sempre o `cwd` (as dependências `workspace:*` não resolvem sem o workspace inteiro) e o entrypoint do standalone ficava aninhado em `apps/web/server.js`, não na raiz.
18
+ - `generateDockerfileTemplate` chama `detectPackageManager` (`build-targets.ts`, a mesma detecção do cli#106) a partir do `cwd` de onde `neetru deploy` roda. Com `pm.rootDir !== cwd` (monorepo), o `appPrefix` relativo é o mesmo conceito de `relativeAppDir` do cli#106: o build context vira a raiz do workspace (`pm.rootDir`, onde mora o lockfile — `workspace:*` só resolve com o monorepo inteiro), o Dockerfile instala e builda lá, e o runner copia o standalone aninhado (`<prefixo>/.next/standalone/<prefixo>/server.js`) e aponta `CMD ["node", "<prefixo>/server.js"]`. O caminho de hoje (app na raiz, `cwd === pm.rootDir`) preserva o shape de 3 estágios (deps/builder/runner) de antes — só o gerenciador muda.
19
+ - O install usa pnpm (`corepack enable` + `pnpm install`), yarn (`corepack enable` + `yarn install`) ou bun (instalado via script oficial, já que a imagem `node:22-slim` só traz npm) conforme o lockfile/`packageManager` detectado; com lockfile presente prefere `npm ci`/`--frozen-lockfile` (reprodutível), sem lockfile cai pro install não travado — nos dois casos sem exigir `package-lock.json` de quem não usa npm.
20
+ - O Dockerfile gerado (e sua limpeza, no `finally`) agora é escrito no build context resolvido, não sempre em `cwd` — em monorepo isso é a raiz do workspace.
21
+ - **Fica de fora:** yarn berry (2+) com PnP (o template assume `node-modules` linker); workspaces `npm`/`yarn` nativos (sem lockfile de 3º nível) não foram testados — só pnpm (fixture de monorepo) e npm de app único (fixture de hoje). O README (seção Build de monorepo) ainda recomenda o Dockerfile próprio pra casos fora dessas duas fixtures.
22
+
23
+ - **`neetru ai`, `neetru auth` e `neetru config` param de oferecer a API da Anthropic e o Gemini, e a CLI passa a ter uma lista só de provedores de IA** (bug_19d312f3, achado no pacote publicado). A 2.33.0 tinha duas listas que discordavam: `neetru ai` e `neetru auth` com anthropic, openai, gemini, groq e nvidia; `neetru code`, `neetru ai models` e a config do AI Gateway com groq, nvidia e openai. A primeira oferecia dois provedores que a casa não usa (IA só de provedor gratuito; a API da Anthropic e o Gemini custam e estão vetados). E o `neetru ai` ainda saía usando ANTHROPIC_API_KEY ou GEMINI_API_KEY achadas na máquina, sem o dev escolher. Agora:
24
+ - A lista mora num lugar só (`src/lib/ai/provedores.ts`): Groq, NVIDIA NIM e OpenAI. Todo lugar que enumera provedor lê dela. O teste `provedores-ia-lista-unica.test.ts` trava a lista e falha se outro arquivo de `src/` declarar lista própria ou se um texto voltar a citar Anthropic ou Gemini, a chave, a variável de ambiente, o SDK ou o endereço da API deles.
25
+ - `neetru auth status|setup|pull` mostra Groq, NVIDIA NIM e OpenAI (antes: Claude, Codex e Gemini; Groq e NVIDIA nem apareciam). Para Groq e NVIDIA o assistente pede a chave direto, sem programa para instalar.
26
+ - O `auth pull` e o `auth setup` não copiam mais para a config a chave que vem do ambiente (GROQ_API_KEY, NVIDIA_API_KEY, OPENAI_API_KEY): a CLI já a lê direto da variável, e a cópia passava a valer mais que ela (a chave da config vem primeiro), então trocar a chave na variável deixava a CLI usando a antiga. A chave do arquivo do Codex continua sendo copiada. O assistente também não pede de novo um provedor que já tem chave na config, e o `auth status` mostra esse provedor pronto e diz que a CLI usa a chave da config.
27
+ - A descoberta não lê mais ANTHROPIC_API_KEY, GEMINI_API_KEY, GOOGLE_API_KEY nem os arquivos do Claude Code e do Gemini. Chave da Anthropic ou do Gemini, na config ou na máquina, não libera mais o `neetru ai`.
28
+ - A dependência `@anthropic-ai/sdk` sai do package.json e do package-lock.json, e com ela os pacotes que só ela usava (`json-schema-to-ts`, `ts-algebra`, `@babel/runtime`, `standardwebhooks`, `@stablelib/base64`, `fast-sha256`): o `npm i -g @neetru/cli` não baixa mais o SDK da Anthropic. O teste também confere o package.json e o lock.
29
+ - O `auto` do `neetru ai` prefere os provedores gratuitos: geração de código vai primeiro para a NVIDIA NIM, pergunta curta para o Groq e, no resto, a ordem é Groq, NVIDIA NIM e só então a OpenAI (antes a OpenAI, que é paga, vinha primeiro na geração de código e no resto, e uma OPENAI_API_KEY na máquina bastava para ela atender sem o dev escolher). A OpenAI só entra no `auto` quando nenhum gratuito tem chave; com `-m openai` ela é usada direto.
30
+ - `neetru ai -m <nome>` e o `/debug:model` aceitam `groq | nvidia | openai | gateway | auto`. `-m claude`, `-m gemini` ou um nome qualquer saem com exit 1 dizendo o que aceitam (antes caíam calados no `auto`).
31
+ - Sem `-m`, o `neetru ai` passa a usar o `defaultModel` da config, que começa em `auto`. Antes o `-m` tinha `auto` de padrão e o `defaultModel` nunca era lido pelo `neetru ai`: `neetru config set defaultModel groq` não tinha efeito.
32
+ - `neetru config set` recusa `anthropicApiKey`, `geminiApiKey`, `claudeModel`, `geminiModel` e `defaultModel` fora da lista. Um config.json antigo com essas chaves, ou com `defaultModel: "claude"`, continua carregando: as chaves só deixam de ser lidas. Com o `defaultModel` antigo, o `neetru ai` sem `-m` sai com exit 1 dizendo para rodar `neetru config set defaultModel auto` (ou abrir com `-m <modelo>`).
33
+ - O `/agentes` (vários modelos em paralelo) consulta na ordem da lista: Groq, NVIDIA NIM, OpenAI.
34
+ - `neetru code --provider <nome fora da lista>` (ex.: `gemini`) avisa que o nome não existe e qual a lista. O pedido segue sem provedor forçado, como antes (antes não avisava).
35
+ - **Quebra:** quem usava o `neetru ai` só com chave da Anthropic ou do Gemini fica sem provedor. Sem sessão do `neetru login`, o `neetru ai` abre o assistente do `neetru auth setup`. Com sessão, o REPL abre, mas o `auto` não usa o AI Gateway por conta própria (isso gasta a cota do gateway): cada pergunta responde que não há provedor com chave e diz como pedir o gateway (`neetru ai -m gateway` ou `neetru config set defaultModel gateway`). O `neetru auth status --json` perde `anthropic` e `gemini` em `configured` e em `providers`. Quem dependia do `auth pull` para gravar a OPENAI_API_KEY do ambiente na config passa a gravá-la com `neetru config set openaiApiKey <chave>`.
36
+ - **Fica para depois:** a OpenAI segue na lista. É paga, mas não estava no veto do bug e as duas listas já concordavam nela; tirar ou não é decisão do dono. Até lá, o `auto` só a usa quando nenhum gratuito tem chave.
37
+
38
+ - **`neetru deploy --target vm`: o aviso do `--port` e a ajuda passam a dizer que cada produto tem UM app por ambiente na VM, e como ter dois** (bug_6d92ef71, 2ª metade). O Core reconhece o app pelo par produto + ambiente (`appKeyOf` no `port-registry` do Core): um 2º deploy do mesmo produto no mesmo ambiente reusa a mesma porta e o mesmo endereço e substitui o 1º. O aviso antigo ("a porta é gerenciada pelo Core… --port é ignorado") confirmava a limitação e não dava saída a quem passou `--port` querendo um segundo app (ex.: site e API em endereços diferentes). Agora o aviso — e `neetru deploy --help`, para quem nem passou `--port` — explica o modelo e o caminho: dois apps são dois produtos (`neetru products create`, papel admin ou operator, e o deploy de cada um com o seu `--product`); o deploy do produto novo exige uma chave da CLI que o alcance (a de admin alcança todos; uma chave com escopo de produto recebe `403 forbidden_product` do Core, e o deploy fica com um admin); cada produto tem endereço, banco e segredos próprios, e se os dois precisam do mesmo banco a API fica no próprio app. O texto mora num lugar só (`src/lib/deploy-vm-um-app.ts`). O aviso também passa a sair **antes** da escolha do servidor (num `--json --non-interactive` sem `--server` a CLI morria em `server_required` sem avisar) e com `--port` inválido (`--port abc` virava NaN e calava o aviso). Seção nova no README.
39
+ - **O caminho ensinado esbarra hoje num defeito do Core, e a CLI passa a explicar a recusa** (bug_fe30f035, no Core). O produto criado pelo `neetru products create` nasce com staging e prod no destino workspace, e o 1º deploy dele na VM volta `400 vm_deploy_failed` com "Ambiente prod tem destino 'workspace', não 'vm'. Migre o destino antes (changeEnvironmentTarget) ou use o destino atual." — um conselho que a CLI não tem como seguir: ela não tem comando que troque o destino de um ambiente. Agora, quando o Core recusa assim (na VM ou no Cloud Run), a CLI mantém o recado do Core e acrescenta o que aconteceu, que a troca de destino não está ao alcance da CLI e o que fazer: no produto novo, é defeito do Core e o caso fica com um admin do Core; com o ambiente em outro destino de app, refazer com `--target <destino atual>`. Código, exit e `message` do erro não mudam; em `--json` entram `ambiente`, `destinoAtual`, `destinoPedido` e `hint`. Outros 400 passam intactos. O texto mora em `src/lib/deploy-recusa-de-destino.ts` e só aparece quando a recusa acontece, então não envelhece quando o Core for consertado: o conserto proposto do Core (core#791, aberto em 24/10) troca esse recado por outro, sem a cauda `changeEnvironmentTarget`, e a CLI então deixa o erro passar como veio. O caminho de dois apps só funciona de ponta a ponta com o conserto do Core no ar.
40
+ - **`neetru secrets set` e `neetru deploy --target vm` param de dizer que o agente da VM "ainda não entrega" segredos do produto ao app** (bug_8b85353b, defeito nomeado na vistoria de 22/10). A frase foi escrita em 2026-10-16, quando o lado do agente (Parte 2) ainda não tinha saído; o agente 1.6.80 a entrega desde 2026-10-20 (`agent_releases/1.6.80`, canal stable; a 1.6.79, onde o CHANGELOG do agente põe o recurso, foi declarada e nunca publicada) e as duas VMs operacionais da frota reportam 1.6.80 desde 2026-10-22. A 2.33.0 a repetia em todo `secrets set` bem-sucedido (na tela e no campo `note` do `--json`, que automação lê) e no `deploy --target vm` que descarta do `--env-file` um segredo como o `NEETRU_WEBHOOK_SECRET` — exatamente o passo do relato. Ela mandava o dev não usar um caminho que funciona. Agora:
41
+ - `secrets set` diz quando o segredo chega: no próximo deploy do ambiente, pelo agente 1.6.80 ou mais novo, que grava o valor num arquivo que só o root da VM lê; o app no ar só vê a gravação depois desse deploy. O `note` do `--json` traz a mesma frase.
42
+ - `deploy --target vm`, depois de descartar a chave, diz que o segredo chega pelo registro: a chave já registrada no ambiente entra neste deploy, a que não está só chega no primeiro deploy depois do registro, e `neetru secrets list --env <ambiente>` mostra o que já está registrado. Sai como informação, não mais como aviso.
43
+ - **Limites declarados (lado Core, fora desta CLI):** a mensagem do próprio Core ao recusar segredo em `envVars` (`assertNoSecretEnvVars`) ainda diz que o agente não foi liberado; e, no deploy, o Core lê as chaves registradas com `resolveProductEnvKeys`, que vira lista vazia num erro de leitura — o agente então apaga o arquivo de segredos e o deploy sai com sucesso. As notas desta CLI descrevem o caminho normal; os dois consertos são do Core e só chegam com deploy dele.
44
+ - **`neetru chat identity <palavra>` não grava mais a palavra como apelido: o default foi invertido** (bug_09574d4b, encerramento derrubado na vistoria de 22/10). Os dois consertos anteriores (#76 e P105) eram lista de permissão — recusavam 18 palavras (`show`/`set`/`clear`, os equivalentes em PT-BR e os sinônimos do `SYNONYMS`) e gravavam todo o resto. Na 2.33.0 publicada, `identity mostrar`, `identity status`, `identity whoami`, `identity atual`, `identity apelido` (o alias do próprio comando) e `identity "Dev Core"` gravavam o argumento como apelido da credencial — que é compartilhada por todas as sessões do mesmo token —, com o mesmo "✓ Apelido definido" do caminho feliz. Agora qualquer primeira palavra fora de `show|ver|get`, `set|definir` e `clear|limpar` é recusada sem nenhuma chamada ao Core (exit 1, `uso_invalido`; no JSON, `value`, `suggestions` e `valid`): a recusa lista os subcomandos e ensina `neetru chat identity set "<o mesmo texto>"`. As únicas formas de gravar são `identity set "Nome"`, `identity clear` e `chat send --as` (que já avisa no stderr).
45
+ - **Quebra a forma posicional da CH3:** `neetru chat identity "Nome"` deixou de gravar — use `neetru chat identity set "Nome"`. A skill `neetru-chat` ainda ensina a forma posicional como "define"; precisa mudar junto com a publicação.
46
+
47
+ - **Mensagens que mandavam usar uma flag que o comando em execução não tem** (bug_89d2497e, etapa 1: cli#98 e o PR seguinte). A guarda de testes passou a conferir cada flag citada contra o subcomando que imprime a mensagem, e não só contra a CLI inteira. Os conselhos errados que ela achou foram consertados:
48
+ - `neetru new` com o diretório `./<nome>` ocupado mandava "Use --here ou --path ou --force", flags que o `new` não tem. Agora cita o comando inteiro: `neetru init <nome> --here`, `--path <outro-diretório>` ou, para sobrescrever, `--path <nome> --force`.
49
+ - `neetru deploy` (standalone sem `server.js`) mandava "use `--app <dir>`". Agora manda rodar `neetru build --app <dir>` no diretório de onde vai rodar o deploy.
50
+ - `dev sync`/`dev status --sync` citavam `--prototype <slug>` solto. Agora citam `neetru dev use --prototype <slug>`.
51
+ - `neetru ui` sem terminal interativo mandava usar "--json", que o `ui` não tem.
52
+ - O step-up de MFA de `vm provision`/`agent release` falava em "modo --json", flag que esses comandos não declaram. Agora diz "modo não-interativo".
53
+ - `neetru new "!!!"` (nome que não gera slug) caía no "informe --slug" do `products create`. Agora o `new` recusa antes de autenticar, citando `--product-slug`.
54
+ - `neetru new` num produto que já tem ambiente ativo no mesmo env imprimia o JSON de `workspaces create` com "Use --force-duplicate para criar mesmo assim", e o `new` não tem essa flag. O `message` desse JSON agora depende do comando em execução: `workspaces create --json` continua citando `--force-duplicate`; o `new` manda rodar `neetru workspaces create --product <id> --env <env>` num terminal interativo, que pergunta se reusa o ambiente existente ou cria outro. O código do erro (`tenant_already_exists`, 409) não mudou.
55
+ - A guarda passou a ler mais formas de texto (o logger `lg` do `scopedLog`, `console.*`, `process.stderr.write`, `chalk`, mapa de mensagens, `${…}`, retorno de função, `export const` importada, `let msg` atribuída por ramo, as chaves `guidance`/`warning`/`note` do JSON). Na guarda do cli#98, 102 textos com flag que o usuário lê não eram conferidos; agora são 36, e nenhum deles é conselho errado. Os que ela achou:
56
+ - `neetru deploy` no Windows, sem artefato pronto (o deploy roda o build): "use --no-install pra pular esta etapa", e o `deploy` não tem essa flag. No `deploy` o aviso agora só pede para pausar o dev server; o `neetru build` continua citando `--no-install`.
57
+ - `neetru secrets archive CHAVE=VALOR`: "mande o valor por --stdin, --from-file ou pelo pedido na tela". O `archive` não recebe valor nem tem essas flags; agora ele só pede o nome. O `secrets set` continua ensinando as três origens.
58
+ - `neetru admin sdk-credential scopes`: "em --scopes, separe por vírgula". A flag é do `create`, e a dica agora cita `neetru admin sdk-credential create --scopes`.
59
+ - O aviso de segredo descartado do `neetru deploy --target vm` dizia "use --generate" solto. Agora cita `neetru secrets set <NOME> --env <ambiente> --generate`.
60
+ - O "o que fazer" de `neetru db frontdoor`, comum ao `enable`, ao `status` (que mostra o último erro da ligação) e ao `disable`: "Confira o id passado em --app-server" (só o `enable` tem) e "repita com --force"/"nem com --force" (só o `disable` tem). Agora cada um cita o comando inteiro.
61
+ - Dois textos mudaram sem ser conselho errado, para a guarda não cobrar à toa: o rótulo do `neetru chat history --since` passou de "desde o cursor --since" para "desde o cursor informado" (o runner é o mesmo do `neetru chat`, que nunca chega a esse rótulo), e o `--no-cache` do build com yarn/bun diz que eles não têm equivalente ao `npm cache clean --force`.
62
+
63
+ - **`neetru build` em monorepo: o lugar do app dentro do standalone vem do próprio Next.js** (bug_678ddc98, o que faltava do comportamento esperado do relato). Desde a 2.31.1 o build detecta o gerenciador de pacotes, aceita `--app`/`--cwd` e acha o `server.js` aninhado, mas achava por varredura da árvore: entre dois `server.js` com `.next` ao lado escolhia pela ordem alfabética, e não descia mais de 6 pastas. Agora lê `relativeAppDir` de `.next/required-server-files.json`, que o Next.js grava no mesmo build com a mesma conta que usa para escrever `.next/standalone/<relativeAppDir>/server.js` (um valor gravado no Windows, com `\`, vira `/`). A varredura fica só para Next.js antigo, sem o campo. Se o Next.js registrou um lugar e o `server.js` não está lá, ou registrou um lugar fora do standalone, o build para com exit 1 e diz qual dos dois, em vez de empacotar outro app da árvore. App único segue igual (`relativeAppDir` vazio, `server.js` na raiz).
64
+ - **README:** seção nova "Build de monorepo". Abre com o aviso de que o deploy de monorepo na VM ainda não funciona (limite abaixo) e traz `--app`, onde sai o pacote, a ordem de escolha do gerenciador (o lockfile da pasta do app vem antes de tudo da raiz, inclusive do `packageManager` da raiz) e o Dockerfile do Cloud Run, que ainda supõe app único com npm.
65
+ - **Limite (lado do agente, fora desta CLI):** o agente da VM (1.6.80) decide se instala dependências olhando só a raiz do pacote. O pacote de monorepo não tem `server.js` nem `.next/` na raiz, então o agente não o reconhece como standalone e roda `npm rebuild` (e, se falhar, `npm install`) na raiz do pacote em vez de pular a instalação. O conserto é do `neetru-agent-service-vm` (bug_bc72fe7e). O bug fica aberto por isso.
66
+
7
67
  ## [2.33.0] — 2026-10-19 (`neetru db frontdoor` e confirmação da VM na limpeza forçada)
8
68
 
9
69
  > **Por que MINOR.** Entram um comando novo (`neetru db frontdoor enable|status|disable`) e uma flag nova (`neetru servers force-cleanup --confirm-instance-id`), sem quebrar comportamento existente. Os dois só funcionam contra um Core com o core#727 e o core#732 no ar; antes disso a rota da porta de entrada responde 404, e a limpeza forçada ignora a confirmação.
package/README.md CHANGED
@@ -56,7 +56,7 @@ Cria produto + workspace + scaffold local Next.js 15 + instala deps + abre cockp
56
56
  | `neetru docs new <tipo> "<título>"` | Doc novo a partir do modelo, na pasta e com o nome do protocolo |
57
57
  | `neetru docs check [--changed] [--strict]` | Valida os docs do produto offline (códigos FM/TI/NM/LK/PB/LC + PR001–PR004) |
58
58
  | `neetru docs sync [--dry-run]` | Publica os docs do produto no Core (só os sem erro; nada é apagado) |
59
- | `neetru build [--stack ...] [--target-env dev\|workspace\|prod] [--env-file ...] [--no-cache] [--no-install]` | Empacotar produto em tarball pra deploy |
59
+ | `neetru build [--app <pasta>] [--stack ...] [--target-env dev\|workspace\|prod] [--env-file ...] [--no-cache] [--no-install]` | Empacotar produto em tarball pra deploy (monorepo: `--app apps/web`, ou `--cwd`) |
60
60
  | `neetru smoke [--base-url ...] [--prod ...] [--health-path ...] [--routes csv] [--json]` | Percorrer as páginas do produto **local** antes do deploy (alcance + login + DB) + smoke de prod opcional |
61
61
  | `neetru promote` | Promover dev → staging → prod |
62
62
  | `neetru env switch <target>` | Trocar `NEETRU_ENV` no `.env.local` |
@@ -87,7 +87,7 @@ Cria produto + workspace + scaffold local Next.js 15 + instala deps + abre cockp
87
87
  | `neetru logs` | Ver logs de produtos |
88
88
  | `neetru status` | Visão geral do ecossistema |
89
89
  | `neetru open <produto>` | Abrir cockpit do produto no browser |
90
- | `neetru ai` | REPL IA Neetru-aware (Claude/OpenAI/Gemini) |
90
+ | `neetru ai` | REPL IA Neetru-aware (Groq, NVIDIA NIM, OpenAI ou o AI Gateway da Neetru via `neetru login`) |
91
91
  | `neetru ui` | Menu interativo do CLI |
92
92
  | `neetru publish` | Publicar produto no catálogo público |
93
93
  | `neetru upgrade` | Auto-update do CLI |
@@ -171,6 +171,24 @@ Isso existe porque o Next.js carrega `.env.local` (arquivo do disco do dev, nunc
171
171
 
172
172
  Se o produto envolver `next.config.mjs` com `withNeetruBuildGuard` (`@neetru/sdk/build-guard`, `@neetru/sdk >= 3.1.15`), `neetru build` confere pós-build que o env realmente compilado bate com o esperado — e **recusa empacotar o tarball** se divergir, em vez de deployar um bundle quebrado silenciosamente.
173
173
 
174
+ ## Build de monorepo (pnpm, yarn, bun ou npm workspaces)
175
+
176
+ > **Deploy de monorepo na VM ainda não funciona.** O agente da VM (1.6.80) não reconhece o app que fica dentro de uma pasta do pacote (`apps/web/server.js`): em vez de pular a instalação, ele roda `npm rebuild` na raiz do pacote e, se falhar, `npm install` — o mesmo caminho que já derrubou deploy em VM pequena (exit 137). Para a VM, mantenha o seu empacotador próprio até sair a versão do agente que reconhece o app aninhado (bug_bc72fe7e no agente; origem bug_678ddc98). O `neetru build --app` abaixo já monta o pacote; o que falta é o agente saber instalá-lo.
177
+
178
+ Rode da raiz do monorepo e aponte o app com `--app` (`--cwd` é o mesmo):
179
+
180
+ ```bash
181
+ neetru build --app apps/web --product meu-produto
182
+ neetru deploy --product meu-produto # da mesma pasta: usa o pacote que o build gerou (na VM, veja o aviso acima)
183
+ ```
184
+
185
+ - **Onde sai o pacote:** em `.neetru-build/` da pasta onde você rodou o comando — é lá que o `neetru deploy` procura. O `neetru.config.json` pode ficar no app ou na raiz (o do app vale mais). A versão vem do `package.json` do app.
186
+ - **Gerenciador de pacotes, nesta ordem:** (1) o campo `packageManager` do `package.json` do app; (2) o lockfile da pasta do app (`pnpm-lock.yaml`, `yarn.lock`, `bun.lock`/`bun.lockb`, `package-lock.json`), que vem antes de tudo o que está na raiz, inclusive do `packageManager` da raiz; (3) sem lockfile no app, a CLI sobe até a raiz do workspace, e lá o `packageManager` do `package.json` da raiz vale mais que o lockfile da raiz. A raiz só conta se se declara workspace (`pnpm-workspace.yaml` ou `workspaces` no `package.json`), e a busca para na pasta que tem `.git`. Achada a raiz, a instalação roda nela e o script `build` roda na pasta do app. Sem lockfile nenhum, fica o npm, como sempre.
187
+ - **Cuidado com lockfile solto no app:** um `package-lock.json` esquecido em `apps/web` de um monorepo pnpm faz a CLI escolher o npm e instalar só dentro de `apps/web`, onde as dependências `workspace:*` falham. Apague o lockfile solto antes do build.
188
+ - **Next.js com `output: 'standalone'`:** no monorepo o Next.js põe o app dentro do standalone, no mesmo caminho que ele tem no repositório (`.next/standalone/apps/web/server.js`). A CLI lê esse caminho de `.next/required-server-files.json` (campo `relativeAppDir`, gravado pelo próprio Next.js no build). Se o arquivo não traz o campo (Next.js antigo), ela procura o `server.js` pela árvore. O pacote leva o standalone inteiro, `.next/static` e `public` vão para dentro de `apps/web/`, e o `neetru deploy` manda o entrypoint `apps/web/server.js` para o Core.
189
+ - **Quando a CLI recusa:** se o Next.js registrou o app num lugar e o `server.js` não está lá, ou registrou um lugar fora do standalone (o `outputFileTracingRoot` precisa ser uma pasta acima do app), o build para com exit 1 em vez de empacotar um pacote que não sobe.
190
+ - **Cloud Run sem Dockerfile:** o Dockerfile que o `neetru deploy --target cloud-run` gera quando o projeto não tem um reconhece o monorepo (bug_1dcf8e03): detecta o gerenciador do app (mesma regra do `neetru build --app` acima), builda com o contexto na raiz do workspace (onde mora o lockfile — `workspace:*` só resolve assim) e aponta o `CMD`/`COPY` do standalone para o caminho aninhado do app (`apps/web/server.js`). Rode `neetru deploy` de dentro do app (`cd apps/web && neetru deploy …`), como no `neetru build --app`. Testado com pnpm (fixture de monorepo) e npm de app único (caso de sempre); yarn berry com PnP e workspace nativo npm/yarn sem teste próprio — fora desses dois casos, escreva o seu Dockerfile.
191
+
174
192
  ## Segredo no pacote público bloqueia o deploy (2.30.0)
175
193
 
176
194
  A credencial `nrt_` do produto é **só de servidor**. Antes de enviar o pacote, `neetru deploy` varre o que é servido ao navegador — `.next/static/**`, `public/**`, `out/**`, `dist/**`, `build/**` (na stack `static`, o pacote inteiro; `node_modules` nunca) — procurando:
@@ -191,6 +209,23 @@ neetru deploy --allow-public-secret "chave antiga já revogada, ticket #123"
191
209
 
192
210
  Limites: `--artifact-url` (pacote remoto) e `--image` (imagem pronta, cloud-run) não passam pelo CLI e **não são varridos** — o deploy avisa. Em `target=cloud-run` sem `--image`, a varredura é nas pastas públicas do disco antes do build da imagem.
193
211
 
212
+ ## Deploy na VM: um app por produto e ambiente
213
+
214
+ Na VM (`--target vm`) quem escolhe a porta do app é o Core; `--port` é ignorado (a CLI avisa). O Core reconhece o app pelo **produto + ambiente**: um novo `neetru deploy --product X --env prod` **substitui** o app de `X` em `prod` que está no ar — o endereço de `prod` passa a servir o deploy novo, e não sobe um segundo app ao lado.
215
+
216
+ Para ter **dois apps** (ex.: site e API em endereços diferentes), cadastre o segundo como produto próprio e faça o deploy de cada um com o seu `--product`:
217
+
218
+ ```bash
219
+ neetru products create --name "Meu SaaS API" --slug meu-saas-api # exige papel admin ou operator
220
+ neetru deploy --product meu-saas-api --env prod --target vm --server <id>
221
+ ```
222
+
223
+ O deploy do produto novo exige que a **chave da CLI** o alcance: a de admin alcança todos os produtos; uma chave com escopo de produto só alcança os dela, e o deploy do novo volta `403 forbidden_product` — nesse caso o deploy fica com um admin.
224
+
225
+ **Enquanto o conserto do Core para o bug_fe30f035 não estiver no ar, o 1º deploy do produto novo na VM é recusado pelo Core**: o produto nasce com staging e prod no destino workspace, e o deploy na VM volta `400 vm_deploy_failed` ("Ambiente prod tem destino 'workspace', não 'vm'. Migre o destino antes (changeEnvironmentTarget)…"). A CLI não tem comando para trocar o destino de um ambiente. Quando essa recusa acontece, a CLI a explica, e o caso fica com um admin do Core.
226
+
227
+ Cada produto tem endereço, banco e segredos próprios. Se os dois precisam do **mesmo banco**, separar não serve (o banco é isolado por produto): sirva a API pelo próprio app (ex.: rotas `/api`), no mesmo endereço. O mesmo texto aparece em `neetru deploy --help`.
228
+
194
229
  ## Smoke local antes do deploy (`neetru smoke`)
195
230
 
196
231
  Roda contra o servidor **dev local** (`next dev` / `neetru dev up`) e percorre as páginas do produto **antes** de `neetru build` + `neetru deploy` — pega página quebrada, login que não renderiza ou banco fora do ar sem subir nada em produção.
@@ -220,7 +255,7 @@ Token Bearer fica em `~/.config/neetru-cli/auth.json` (chmod 0600 em Unix, NTFS
220
255
  | Chave | Valor |
221
256
  |---|---|
222
257
  | `neetruApiUrl` | URL base do Core (default `https://api.neetru.com`). **Allowlist** desde 2.11.0: `*.neetru.com` + `localhost`; demais hosts exigem `--allow-untrusted-url` |
223
- | `defaultModel` | `claude` / `openai` / `gemini` / `auto` (REPL `neetru ai`) |
258
+ | `defaultModel` | `groq` / `nvidia` / `openai` / `gateway` / `auto` (default `auto`): o modelo do `neetru ai` aberto sem `-m`. No `auto`, a CLI escolhe entre os provedores com chave, gratuitos primeiro (Groq, NVIDIA NIM; a OpenAI só sem eles) |
224
259
  | `telemetry.enabled` | Telemetry opt-in (default `false`) — desde 2.11.0 emite eventos anônimos pra `/api/sdk/v1/telemetry/log` |
225
260
  | `neetruApiKey` | **(legado)** Token em Conf — substituído por `auth.json`. Será removido em 2.12.0 |
226
261
 
@@ -207,7 +207,9 @@ export async function runSdkCredentialScopes(opts) {
207
207
  }
208
208
  console.log();
209
209
  }
210
- console.log(chalk.dim('Dica: em --scopes, separe por vírgula. Aliases legados (ex.: "data" → ' +
210
+ // bug_89d2497e (etapa 1): este é o `admin sdk-credential scopes`, que não
211
+ // declara --scopes; a flag é do `create`, e o texto diz qual.
212
+ console.log(chalk.dim('Dica: em `neetru admin sdk-credential create --scopes`, separe por vírgula. Aliases legados (ex.: "data" → ' +
211
213
  'datastore.read, "auth" → profile.read) são aceitos e normalizados pelo servidor.'));
212
214
  }
213
215
  //# sourceMappingURL=sdk-credential.js.map
@@ -1,6 +1,7 @@
1
+ import { type ProvedorIa } from '../lib/ai/provedores.js';
1
2
  export interface AiModelCatalogEntry {
2
3
  id: string;
3
- provider: 'groq' | 'nvidia' | 'openai';
4
+ provider: ProvedorIa;
4
5
  modelId: string;
5
6
  label: string;
6
7
  notes: string;
@@ -13,14 +13,13 @@
13
13
  import chalk from 'chalk';
14
14
  import { apiRequest } from '../lib/api-client.js';
15
15
  import { handleApiError } from '../lib/api-error.js';
16
+ import { NOME_DO_PROVEDOR } from '../lib/ai/provedores.js';
16
17
  import { config } from '../lib/config.js';
17
18
  import { log } from '../utils/logger.js';
18
19
  import { setSafeExitCode } from '../utils/safe-exit.js';
19
- const PROVIDER_LABELS = {
20
- groq: 'Groq',
21
- nvidia: 'NVIDIA NIM',
22
- openai: 'OpenAI',
23
- };
20
+ // Nomes da lista única (`provedores.ts`, bug_19d312f3). Indexado por string
21
+ // porque o provider vem do Core; fora da lista, a tela mostra o id cru.
22
+ const PROVIDER_LABELS = NOME_DO_PROVEDOR;
24
23
  async function fetchCatalog() {
25
24
  const { entries } = await apiRequest('/api/cli/v1/ai/models');
26
25
  return entries;
@@ -1,4 +1,14 @@
1
- import { type ModelId } from '../lib/ai/orchestrator.js';
1
+ /**
2
+ * Checa se o usuário tem pelo menos uma cred (local OU descoberta) de um
3
+ * provedor da lista única OU uma sessão `neetru login` válida (gateway). Se
4
+ * zero, roda o setup wizard inline antes de abrir o REPL.
5
+ *
6
+ * bug_19d312f3: antes, `anthropicApiKey`/`geminiApiKey` na config ou
7
+ * ANTHROPIC_API_KEY/GEMINI_API_KEY no ambiente liberavam o REPL, que saía
8
+ * usando a API da Anthropic ou o Gemini sem o dev escolher. Os dois estão
9
+ * vetados e não contam mais.
10
+ */
11
+ export declare function ensureAtLeastOneProvider(): Promise<boolean>;
2
12
  export declare function runAiRepl(opts?: {
3
- model?: ModelId;
13
+ model?: string;
4
14
  }): Promise<void>;
@@ -2,6 +2,7 @@ import * as readline from 'readline/promises';
2
2
  import chalk from 'chalk';
3
3
  import { chat, chatParallel } from '../lib/ai/orchestrator.js';
4
4
  import { discoveredKeys } from '../lib/ai/auth-discovery.js';
5
+ import { CHAVE_DO_PROVEDOR, MODELOS_DO_AI_NA_TELA, NOMES_DOS_PROVEDORES, PROVEDORES_IA, ehModeloDoAi, } from '../lib/ai/provedores.js';
5
6
  import { config } from '../lib/config.js';
6
7
  import { getActiveTokenSync } from '../lib/token-resolver.js';
7
8
  import { log } from '../utils/logger.js';
@@ -10,64 +11,72 @@ import { safeExit } from '../utils/safe-exit.js';
10
11
  const PROMPT_PREFIX = chalk.hex('#1E90FF').bold(' › ');
11
12
  const ASSISTANT_PREFIX = chalk.dim(' ◆ ');
12
13
  // Help público — sem /modelo (orchestrator escolhe). /debug:model é o
13
- // override avançado pra quem quer forçar.
14
+ // override avançado pra quem quer forçar. Provedores e modelos vêm da lista
15
+ // única (`provedores.ts`, bug_19d312f3).
14
16
  const HELP_TEXT = `
15
17
  Comandos:
16
18
  /sair /exit encerrar sessão
17
19
  /limpar /clear limpar histórico de contexto
18
20
  /agentes <pergunta> consultar 2+ modelos em paralelo e comparar
19
- /auth rodar wizard de configuração (claude/codex/gemini)
21
+ /auth rodar wizard de configuração (${NOMES_DOS_PROVEDORES})
20
22
  /help mostrar este texto
21
- /debug:model <nome> forçar modelo (claude|openai|gemini|groq|nvidia|gateway|auto)
23
+ /debug:model <nome> forçar modelo (${MODELOS_DO_AI_NA_TELA})
22
24
  `.trim();
23
- const DEBUG_MODELS = ['claude', 'openai', 'gemini', 'groq', 'nvidia', 'gateway', 'auto'];
25
+ /** Algum provedor da lista única tem chave na config local? */
26
+ function temChaveLocal() {
27
+ return PROVEDORES_IA.some((p) => !!config.get(CHAVE_DO_PROVEDOR[p]));
28
+ }
29
+ /** Algum provedor da lista única tem chave achada na máquina (ambiente, Codex)? */
30
+ function temChaveDescoberta() {
31
+ const descobertas = discoveredKeys();
32
+ return PROVEDORES_IA.some((p) => !!descobertas[p]);
33
+ }
24
34
  /**
25
- * Checa se o usuário tem pelo menos uma cred (local OU descoberta) OU uma
26
- * sessão `neetru login` válida (gateway). Se zero, roda o setup wizard
27
- * inline antes de abrir o REPL.
35
+ * Checa se o usuário tem pelo menos uma cred (local OU descoberta) de um
36
+ * provedor da lista única OU uma sessão `neetru login` válida (gateway). Se
37
+ * zero, roda o setup wizard inline antes de abrir o REPL.
38
+ *
39
+ * bug_19d312f3: antes, `anthropicApiKey`/`geminiApiKey` na config ou
40
+ * ANTHROPIC_API_KEY/GEMINI_API_KEY no ambiente liberavam o REPL, que saía
41
+ * usando a API da Anthropic ou o Gemini sem o dev escolher. Os dois estão
42
+ * vetados e não contam mais.
28
43
  */
29
- async function ensureAtLeastOneProvider() {
30
- const local = {
31
- anthropic: !!config.get('anthropicApiKey'),
32
- openai: !!config.get('openaiApiKey'),
33
- gemini: !!config.get('geminiApiKey'),
34
- groq: !!config.get('groqApiKey'),
35
- nvidia: !!config.get('nvidiaApiKey'),
36
- };
37
- if (local.anthropic || local.openai || local.gemini || local.groq || local.nvidia)
44
+ export async function ensureAtLeastOneProvider() {
45
+ if (temChaveLocal())
38
46
  return true;
39
47
  // Gateway (não-BYOK): sessão de `neetru login` já basta.
40
48
  if (getActiveTokenSync())
41
49
  return true;
42
- const discovered = discoveredKeys();
43
- if (discovered.anthropic ||
44
- discovered.openai ||
45
- discovered.gemini ||
46
- discovered.groq ||
47
- discovered.nvidia) {
50
+ if (temChaveDescoberta())
48
51
  return true;
49
- }
50
52
  // Zero — força setup interativo.
51
53
  log.warn('Nenhum provedor de IA configurado.');
52
54
  log.info('Abrindo wizard de configuração...');
53
55
  await runAuthSetup();
54
56
  // Re-checa.
55
- const disc = discoveredKeys();
56
- const after = !!config.get('anthropicApiKey') ||
57
- !!config.get('openaiApiKey') ||
58
- !!config.get('geminiApiKey') ||
59
- !!config.get('groqApiKey') ||
60
- !!config.get('nvidiaApiKey') ||
61
- !!disc.anthropic ||
62
- !!disc.openai ||
63
- !!disc.gemini ||
64
- !!disc.groq ||
65
- !!disc.nvidia;
66
- return after;
57
+ return temChaveLocal() || temChaveDescoberta();
67
58
  }
68
59
  export async function runAiRepl(opts = {}) {
69
60
  log.banner();
70
61
  log.info('Assistente Neetru — contexto da plataforma carregado.');
62
+ // bug_19d312f3: `-m claude` / `-m gemini` (ou qualquer nome fora da lista)
63
+ // caía calado no `auto`. Agora é recusado antes de abrir a sessão.
64
+ //
65
+ // Revisão do #104: sem `-m`, vale o `defaultModel` da config (`neetru config
66
+ // set defaultModel <modelo>`), que começa em `auto`. Antes o `-m` tinha
67
+ // `auto` de padrão no index.ts e o `defaultModel` nunca chegava aqui — nem o
68
+ // válido (`groq`) tinha efeito, nem o antigo (`claude`) era avisado.
69
+ const doConfig = opts.model === undefined;
70
+ const pedido = opts.model ?? config.get('defaultModel') ?? 'auto';
71
+ if (!ehModeloDoAi(pedido)) {
72
+ log.error(doConfig
73
+ ? `O defaultModel gravado na config ("${String(pedido)}") não existe no neetru ai. ` +
74
+ `Use: ${MODELOS_DO_AI_NA_TELA}. Rode \`neetru config set defaultModel auto\` ` +
75
+ '(ou abra com `neetru ai -m <modelo>`).'
76
+ : `O modelo "${String(pedido)}" não existe no neetru ai. Use: ${MODELOS_DO_AI_NA_TELA}.`);
77
+ safeExit(1);
78
+ return;
79
+ }
71
80
  // Bloqueia antes do REPL se não tem cred nenhuma.
72
81
  const ready = await ensureAtLeastOneProvider();
73
82
  if (!ready) {
@@ -86,7 +95,7 @@ export async function runAiRepl(opts = {}) {
86
95
  // bug: histórico crescia ilimitado → context_window_exceeded em sessões longas.
87
96
  // Janela deslizante: mantém as últimas HISTORY_WINDOW mensagens (par user+assistant).
88
97
  const HISTORY_WINDOW = 40;
89
- let currentModel = opts.model ?? 'auto';
98
+ let currentModel = pedido;
90
99
  let running = true;
91
100
  rl.on('close', () => {
92
101
  running = false;
@@ -121,12 +130,12 @@ export async function runAiRepl(opts = {}) {
121
130
  }
122
131
  if (input.startsWith('/debug:model ')) {
123
132
  const m = input.split(' ')[1];
124
- if (DEBUG_MODELS.includes(m)) {
133
+ if (ehModeloDoAi(m)) {
125
134
  currentModel = m;
126
135
  log.success(`Modelo forçado: ${m}`);
127
136
  }
128
137
  else {
129
- log.warn('Use: claude | openai | gemini | groq | nvidia | gateway | auto');
138
+ log.warn(`Use: ${MODELOS_DO_AI_NA_TELA}`);
130
139
  }
131
140
  continue;
132
141
  }
@@ -6,8 +6,9 @@ interface CommonOpts {
6
6
  */
7
7
  export declare function runAuthStatus(opts?: CommonOpts): Promise<void>;
8
8
  /**
9
- * `neetru auth pull` — sem perguntar, copia toda apiKey extraída dos CLIs
10
- * concorrentes pra config do Neetru. Útil pra CI/script.
9
+ * `neetru auth pull` — sem perguntar, copia pra config do Neetru a chave achada
10
+ * no arquivo do Codex. A do ambiente não é copiada: a CLI já a lê direto da
11
+ * variável (ver `chaveCopiavel`). Não sobrescreve chave já configurada.
11
12
  */
12
13
  export declare function runAuthPull(opts?: CommonOpts): Promise<void>;
13
14
  /**