@neetru/cli 2.32.1 → 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 (63) hide show
  1. package/CHANGELOG.md +78 -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.d.ts +116 -0
  16. package/dist/commands/db-frontdoor.js +572 -0
  17. package/dist/commands/db-move.d.ts +5 -0
  18. package/dist/commands/db-move.js +27 -0
  19. package/dist/commands/db.d.ts +8 -2
  20. package/dist/commands/db.js +13 -6
  21. package/dist/commands/deploy.d.ts +14 -5
  22. package/dist/commands/deploy.js +376 -55
  23. package/dist/commands/dev-context.js +1 -1
  24. package/dist/commands/environments-repoint.d.ts +49 -0
  25. package/dist/commands/environments-repoint.js +219 -0
  26. package/dist/commands/init.js +7 -1
  27. package/dist/commands/new.js +14 -0
  28. package/dist/commands/secrets.d.ts +6 -4
  29. package/dist/commands/secrets.js +14 -7
  30. package/dist/commands/servers.d.ts +29 -0
  31. package/dist/commands/servers.js +108 -1
  32. package/dist/commands/ui.js +1 -1
  33. package/dist/commands/workspaces.js +16 -1
  34. package/dist/index.js +103 -7
  35. package/dist/lib/ai/auth-discovery.d.ts +9 -12
  36. package/dist/lib/ai/auth-discovery.js +43 -145
  37. package/dist/lib/ai/orchestrator.d.ts +13 -6
  38. package/dist/lib/ai/orchestrator.js +46 -246
  39. package/dist/lib/ai/provedores.d.ts +57 -0
  40. package/dist/lib/ai/provedores.js +66 -0
  41. package/dist/lib/api-client.d.ts +8 -1
  42. package/dist/lib/api-client.js +18 -2
  43. package/dist/lib/build-targets.d.ts +48 -10
  44. package/dist/lib/build-targets.js +64 -10
  45. package/dist/lib/cli-write.js +6 -3
  46. package/dist/lib/code-agent-gateway.d.ts +2 -2
  47. package/dist/lib/code-agent-gateway.js +2 -2
  48. package/dist/lib/code-agent-settings.d.ts +2 -2
  49. package/dist/lib/code-agent-settings.js +2 -4
  50. package/dist/lib/code-agent-types.d.ts +3 -1
  51. package/dist/lib/config.d.ts +4 -7
  52. package/dist/lib/config.js +9 -6
  53. package/dist/lib/deploy-recusa-de-destino.d.ts +9 -0
  54. package/dist/lib/deploy-recusa-de-destino.js +98 -0
  55. package/dist/lib/deploy-render-probe.d.ts +117 -0
  56. package/dist/lib/deploy-render-probe.js +475 -0
  57. package/dist/lib/deploy-vm-um-app.d.ts +62 -0
  58. package/dist/lib/deploy-vm-um-app.js +83 -0
  59. package/dist/lib/segredo-produto.d.ts +25 -5
  60. package/dist/lib/segredo-produto.js +45 -10
  61. package/dist/lib/sse.d.ts +4 -5
  62. package/dist/lib/sse.js +4 -5
  63. package/package.json +1 -2
package/dist/index.js CHANGED
@@ -8,6 +8,8 @@ import { CliApiError } from './lib/api-client.js';
8
8
  import { NeetruCliError } from './cli-kit/errors.js';
9
9
  import { installCommandContext, getCurrentCommand } from './cli-kit/command-context.js';
10
10
  import { emitCliError } from './lib/cli-error.js';
11
+ import { AJUDA_DEPLOY_NA_VM } from './lib/deploy-vm-um-app.js';
12
+ import { MODELOS_DO_AI_NA_TELA, NOMES_DOS_PROVEDORES, PROVEDORES_IA, } from './lib/ai/provedores.js';
11
13
  // UV_HANDLE_CLOSING no Windows (bug_818cf6d): o double-destroy de stdin (handler
12
14
  // 'exit' redundante) foi removido em 2026-07-15 (ddd479f), MAS isso não bastou
13
15
  // — a causa dominante é o `uv_async_t` do undici (fetch) ainda vivo quando o
@@ -114,7 +116,9 @@ installCommandContext(program);
114
116
  const aiCommand = program
115
117
  .command('ai')
116
118
  .description('Abrir REPL de IA Neetru-aware (BYOK ou AI Gateway)')
117
- .option('-m, --model <model>', 'modelo: claude | openai | gemini | groq | nvidia | gateway | auto', 'auto')
119
+ // Sem valor padrão de propósito: sem `-m`, o `runAiRepl` usa o `defaultModel`
120
+ // da config (que começa em `auto`). Um padrão aqui o esconderia.
121
+ .option('-m, --model <model>', `modelo: ${MODELOS_DO_AI_NA_TELA} (sem -m: o defaultModel da config, que começa em auto)`)
118
122
  .action(async (opts) => {
119
123
  const { runAiRepl } = await import('./commands/ai.js');
120
124
  await runAiRepl({ model: opts.model });
@@ -125,7 +129,7 @@ const aiCommand = program
125
129
  // por `neetru code`.
126
130
  aiCommand
127
131
  .command('models [id]')
128
- .description('Lista o catalogo de modelos curados (Groq/NVIDIA/OpenAI); com [id] fixa como default')
132
+ .description(`Lista o catalogo de modelos curados (${NOMES_DOS_PROVEDORES}); com [id] fixa como default`)
129
133
  .action(async (id) => {
130
134
  const { runAiModelsList, runAiModelsUse } = await import('./commands/ai-models.js');
131
135
  if (id)
@@ -138,7 +142,7 @@ program
138
142
  .command('code [prompt...]')
139
143
  .description('Abrir assistente local de codigo via Neetru AI Gateway (Groq/NVIDIA)')
140
144
  .option('-m, --model <model>', 'modelo do AI Gateway (default: llama-3.3-70b-versatile) — veja "neetru ai models"')
141
- .option('--provider <provider>', 'forca o provider (groq|nvidia|openai) — necessario p/ modelo nativo de um catalogo especifico')
145
+ .option('--provider <provider>', `forca o provider (${PROVEDORES_IA.join('|')}) — necessario p/ modelo nativo de um catalogo especifico`)
142
146
  .option('--cwd <dir>', 'repo/diretorio alvo (default: cwd atual)')
143
147
  .option('-f, --file <path>', 'inclui arquivo no contexto inicial (repetivel)', (value, previous) => previous.concat(value), [])
144
148
  .option('--max-turns <n>', 'maximo de rodadas tool/read antes de parar', '8')
@@ -222,10 +226,12 @@ program
222
226
  });
223
227
  });
224
228
  // ── neetru auth ───────────────────────────────────────────────────────
225
- // Wave D — detecta Claude Code/Codex/Gemini instalados e reaproveita creds.
229
+ // Wave D — reaproveita chave já existente na máquina. bug_19d312f3: os
230
+ // provedores são os da lista única (`lib/ai/provedores.ts`); Anthropic e Gemini
231
+ // saíram (vetados).
226
232
  const authCmd = program
227
233
  .command('auth')
228
- .description('Gerenciar credenciais de IA (Claude, Codex, Gemini)');
234
+ .description(`Gerenciar credenciais de IA (${NOMES_DOS_PROVEDORES})`);
229
235
  authCmd
230
236
  .command('status')
231
237
  .description('Listar provedores detectados e estado das credenciais')
@@ -244,7 +250,7 @@ authCmd
244
250
  });
245
251
  authCmd
246
252
  .command('pull')
247
- .description('Copiar creds dos CLIs detectados pra config do Neetru')
253
+ .description('Copiar pra config do Neetru a chave achada no arquivo do Codex (as do ambiente já são lidas direto)')
248
254
  .option('--json', 'saída JSON')
249
255
  .action(async (opts) => {
250
256
  const { runAuthPull } = await import('./commands/auth.js');
@@ -459,6 +465,8 @@ program
459
465
  program
460
466
  .command('deploy')
461
467
  .description('Deployar produto via pipeline do Neetru Core')
468
+ // bug_6d92ef71: um app por produto e ambiente na VM, e como ter dois.
469
+ .addHelpText('after', AJUDA_DEPLOY_NA_VM)
462
470
  .option('--product <slug>', 'slug do produto')
463
471
  .option('--version <version>', 'versão a deployar')
464
472
  .option('--stack <stack>', 'node | nextjs | docker | php-apache | static (gera Dockerfile automático para node/nextjs)')
@@ -854,6 +862,7 @@ serversCmd
854
862
  .option('--yes', 'pula a confirmação interativa (modo script)')
855
863
  .option('--mfa-token <code>', 'código TOTP para step-up MFA (ou use `neetru auth mfa-session`)')
856
864
  .option('--dry-run', 'valida e retorna o efeito sem aplicar (sem MFA)')
865
+ .option('--confirm-instance-id <id>', 'id numérico da VM no Google Cloud que você conferiu — só quando o Core pedir (servidor sem o id da VM registrado)')
857
866
  .option('--json', 'saída em JSON')
858
867
  .action(async (id, opts) => {
859
868
  const { runServersForceCleanup } = await import('./commands/servers.js');
@@ -862,6 +871,7 @@ serversCmd
862
871
  yes: !!opts.yes,
863
872
  mfaToken: opts.mfaToken,
864
873
  dryRun: !!opts.dryRun,
874
+ confirmInstanceId: opts.confirmInstanceId,
865
875
  json: !!opts.json,
866
876
  });
867
877
  });
@@ -1962,6 +1972,36 @@ envCmd
1962
1972
  json: !!opts.json,
1963
1973
  });
1964
1974
  });
1975
+ // ── neetru environments ───────────────────────────────────────────────
1976
+ // Ambientes (staging/prod) de um produto no Core. Separado de `neetru env`, que
1977
+ // cuida do NEETRU_ENV local e das variáveis do Cloud Run.
1978
+ const environmentsCmd = program
1979
+ .command('environments')
1980
+ .description('Ambientes (staging/prod) de um produto no Core');
1981
+ // bug_7446f7f2: re-apontar a VM do ambiente sem PATCH manual no Firestore.
1982
+ environmentsCmd
1983
+ .command('repoint <slug> <env>')
1984
+ .description('Re-apontar o ambiente (staging|prod) para outra VM — só o ponteiro; o app muda no próximo deploy ' +
1985
+ '(POST /api/cli/v1/products/[slug]/environments/[env]/server — exige MFA)')
1986
+ .requiredOption('--to <serverId>', 'VM nova (id do servidor)')
1987
+ .requiredOption('--expect-from <serverId>', 'VM que o ambiente aponta hoje (se mudou, nada é gravado)')
1988
+ .requiredOption('--reason <text>', 'motivo (vai para o registro de auditoria)')
1989
+ .option('--dry-run', 'só confere os requisitos (não altera nada, sem MFA)')
1990
+ .option('-y, --yes', 'pula a confirmação interativa (modo script)')
1991
+ .option('--mfa-token <code>', 'código TOTP step-up MFA (ou use `neetru auth mfa-session`)')
1992
+ .option('--json', 'saída em JSON estruturado')
1993
+ .action(async (slug, env, opts) => {
1994
+ const { runEnvironmentsRepoint } = await import('./commands/environments-repoint.js');
1995
+ await runEnvironmentsRepoint(slug, env, {
1996
+ to: opts.to,
1997
+ expectFrom: opts.expectFrom,
1998
+ reason: opts.reason,
1999
+ dryRun: !!opts.dryRun,
2000
+ yes: !!opts.yes,
2001
+ mfaToken: opts.mfaToken,
2002
+ json: !!opts.json,
2003
+ });
2004
+ });
1965
2005
  // ── neetru db ─────────────────────────────────────────────────────────
1966
2006
  // M1 — árvore completa de banco por produto.
1967
2007
  // Subcomandos novos: list, status, apply, migrations list/confirm/cancel.
@@ -2161,7 +2201,9 @@ dbCmd
2161
2201
  .option('--backup-id <id>', 'ID do backup a restaurar (pula o prompt; obrigatório em --json)')
2162
2202
  .option('-y, --yes', 'pula a confirmação textual "RESTAURAR" (obrigatório em --json)')
2163
2203
  // Flag canônica `--mfa-token`; `--mfa` como alias oculto (retrocompat).
2164
- .option('--mfa-token <token>', 'código TOTP step-up MFA (obrigatório — operação destrutiva)')
2204
+ // bug_757b9746: deixou de ser obrigatória — com a sessão de 1h ativa o
2205
+ // restore vai sem código, como os outros comandos destrutivos.
2206
+ .option('--mfa-token <token>', 'código TOTP step-up MFA (ou use `neetru auth mfa-session`)')
2165
2207
  .addOption(new Option('--mfa <token>').hideHelp())
2166
2208
  .option('--json', 'saída em JSON')
2167
2209
  .action(async (dbId, opts) => {
@@ -2281,6 +2323,60 @@ dbMoveCmd
2281
2323
  json: !!opts.json,
2282
2324
  });
2283
2325
  });
2326
+ // ── neetru db frontdoor ───────────────────────────────────────────────
2327
+ // Porta de entrada PRIVADA de um banco Postgres em VM para um app em OUTRA VM,
2328
+ // sem mover o banco (Core #727, bug_5c0e7738). O Core prepara o PgBouncer na
2329
+ // VM do banco, testa a partir da VM do app e na própria VM do banco, e só então
2330
+ // grava o novo endereço. `enable` repetido RETOMA a mesma operação.
2331
+ const dbFrontdoorCmd = dbCmd
2332
+ .command('frontdoor')
2333
+ .description('Porta de entrada privada do banco para um app em outra VM da rede interna (sem mover o banco)');
2334
+ dbFrontdoorCmd
2335
+ .command('enable <dbId>')
2336
+ .description('Ligar a porta de entrada para a VM do app (POST /api/cli/v1/db/[id]/frontdoor — exige MFA; repetir retoma)')
2337
+ .requiredOption('--app-server <serverId>', 'VM onde o app roda ou vai rodar (recebe a liberação na rede interna)')
2338
+ .requiredOption('--reason <text>', 'motivo (vai para o registro de auditoria)')
2339
+ .option('--dry-run', 'só confere os requisitos (não altera nada, sem MFA)')
2340
+ .option('-y, --yes', 'pula a confirmação interativa (modo script)')
2341
+ .option('--mfa-token <code>', 'código TOTP step-up MFA (ou use `neetru auth mfa-session`)')
2342
+ .option('--json', 'saída em JSON estruturado')
2343
+ .action(async (dbId, opts) => {
2344
+ const { runDbFrontdoorEnable } = await import('./commands/db-frontdoor.js');
2345
+ await runDbFrontdoorEnable(dbId, {
2346
+ appServer: opts.appServer,
2347
+ reason: opts.reason,
2348
+ dryRun: !!opts.dryRun,
2349
+ yes: !!opts.yes,
2350
+ mfaToken: opts.mfaToken,
2351
+ json: !!opts.json,
2352
+ });
2353
+ });
2354
+ dbFrontdoorCmd
2355
+ .command('status <dbId>')
2356
+ .description('Estado da porta de entrada e da última operação (GET /api/cli/v1/db/[id]/frontdoor)')
2357
+ .option('--json', 'saída em JSON')
2358
+ .action(async (dbId, opts) => {
2359
+ const { runDbFrontdoorStatus } = await import('./commands/db-frontdoor.js');
2360
+ await runDbFrontdoorStatus(dbId, { json: !!opts.json });
2361
+ });
2362
+ dbFrontdoorCmd
2363
+ .command('disable <dbId>')
2364
+ .description('Desligar a porta de entrada — o app volta ao endereço local da VM do banco (POST /api/cli/v1/db/[id]/frontdoor/disable — exige MFA)')
2365
+ .requiredOption('--reason <text>', 'motivo (vai para o registro de auditoria)')
2366
+ .option('--force', 'obrigatório para desligar uma porta ligada (o desfazer normal) ou quando não se sabe onde o app roda: o app volta ao endereço local da VM do banco e, se ele roda em outra VM, fica sem banco')
2367
+ .option('-y, --yes', 'pula a confirmação interativa (modo script)')
2368
+ .option('--mfa-token <code>', 'código TOTP step-up MFA (ou use `neetru auth mfa-session`)')
2369
+ .option('--json', 'saída em JSON')
2370
+ .action(async (dbId, opts) => {
2371
+ const { runDbFrontdoorDisable } = await import('./commands/db-frontdoor.js');
2372
+ await runDbFrontdoorDisable(dbId, {
2373
+ reason: opts.reason,
2374
+ force: !!opts.force,
2375
+ yes: !!opts.yes,
2376
+ mfaToken: opts.mfaToken,
2377
+ json: !!opts.json,
2378
+ });
2379
+ });
2284
2380
  // ── neetru db where ───────────────────────────────────────────────────
2285
2381
  // READ-ONLY: onde o banco de um produto VIVE hoje (VM/serverId + endpoint).
2286
2382
  // Complementa `neetru db status <dbId>` (que exige o dbId) aceitando --product.
@@ -1,8 +1,11 @@
1
- export type Provider = 'anthropic' | 'openai' | 'gemini' | 'groq' | 'nvidia';
1
+ import { type ProvedorIa } from './provedores.js';
2
+ export type Provider = ProvedorIa;
2
3
  export interface ProviderStatus {
3
4
  provider: Provider;
4
- binary: string;
5
- npmPackage: string;
5
+ /** Programa de terceiro que guarda a chave (hoje só o `codex`). Ausente = provedor só de chave. */
6
+ binary?: string;
7
+ /** Pacote npm desse programa (hoje só `@openai/codex`). */
8
+ npmPackage?: string;
6
9
  installed: boolean;
7
10
  credentialSource: 'env' | 'config-file' | 'binary-config' | null;
8
11
  credentialPath?: string;
@@ -10,18 +13,12 @@ export interface ProviderStatus {
10
13
  hint: string;
11
14
  }
12
15
  /**
13
- * Detecta todos os providers conhecidos. Read-only — não escreve nada,
14
- * não chama API. Seguro pra rodar em startup do `neetru ai`.
15
- *
16
- * Cobre só os providers que têm CLI concorrente (claude/codex/gemini) — o
17
- * wizard de setup usa esta lista pra ofertar install/login per-binário.
18
- * Groq e NVIDIA NIM são API-key-only (sem CLI pra instalar); a descoberta
19
- * deles vive em `discoveredKeys` (env direto).
16
+ * Estado de cada provedor da lista única, na ordem dela. Read-only — não
17
+ * escreve nada, não chama API. Seguro pra rodar em startup do `neetru ai`.
20
18
  */
21
19
  export declare function detectAllProviders(): ProviderStatus[];
22
20
  /**
23
- * Conveniência: retorna apenas providers com apiKey extraída (pronta pra usar).
24
- * Inclui os CLI-discoverable (claude/codex/gemini) + os API-key-only (groq/nvidia).
21
+ * Conveniência: retorna apenas os provedores com chave achada (pronta pra usar).
25
22
  */
26
23
  export declare function discoveredKeys(): Partial<Record<Provider, string>>;
27
24
  /**
@@ -1,33 +1,33 @@
1
1
  /**
2
- * Auth auto-discovery — detecta Claude Code, Codex (OpenAI) e Gemini CLIs
3
- * já instalados na máquina e tenta extrair credenciais existentes.
2
+ * Auth auto-discovery — procura chave já existente na máquina para cada
3
+ * provedor da lista única (`provedores.ts`), sem mexer em nada (read-only).
4
4
  *
5
- * Motivação: usuário não deveria precisar setar `anthropicApiKey` na mão se
6
- * ele já tem `claude` (Claude Code) ou `codex` (OpenAI Codex CLI) ou `gemini`
7
- * (Google Gemini CLI) instalados e logados. CLI Neetru reaproveita as creds
8
- * pra ligar o `neetru ai` direto.
5
+ * - Groq e NVIDIA NIM: só chave de API, lida do ambiente (GROQ_API_KEY,
6
+ * NVIDIA_API_KEY). Não há programa para instalar.
7
+ * - OpenAI: a variável OPENAI_API_KEY ou o arquivo do Codex (`codex`, o
8
+ * programa de linha de comando da OpenAI) em ~/.codex/auth.json.
9
9
  *
10
- * Estratégia (cross-platform Windows/macOS/Linux):
11
- * 1. Detectar binário no PATH (`where` no Windows, `command -v` no POSIX).
12
- * 2. Olhar locais conhecidos de config (~/.claude, ~/.codex, ~/.config/gemini).
13
- * 3. Olhar env vars (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY).
14
- * 4. Reporta o que está disponível sem mexer em nada (read-only).
10
+ * bug_19d312f3: a descoberta lia também ANTHROPIC_API_KEY, GEMINI_API_KEY,
11
+ * GOOGLE_API_KEY e os arquivos do Claude Code e do Gemini, e o `neetru ai` saía
12
+ * usando essas chaves sem o dev escolher. Anthropic e Gemini estão vetados pela
13
+ * casa e saíram da lista; a descoberta não procura mais as chaves deles.
15
14
  *
16
- * O `setup wizard` (em commands/auth.ts) usa essa detecção pra ofertar
17
- * install + login per-provider.
15
+ * O assistente do `neetru auth setup` (commands/auth.ts) usa esta detecção
16
+ * para mostrar o estado de cada provedor e pedir a chave que falta.
18
17
  */
19
18
  import { execSync, execFileSync } from 'node:child_process';
20
19
  import { existsSync, readFileSync } from 'node:fs';
21
20
  import { homedir, platform } from 'node:os';
22
21
  import { join } from 'node:path';
22
+ import { CHAVE_DO_PROVEDOR, PROVEDORES_IA, VARIAVEL_DO_PROVEDOR, } from './provedores.js';
23
23
  const IS_WINDOWS = platform() === 'win32';
24
24
  /**
25
25
  * Checa se um binário existe no PATH. Cross-platform via `where` (Windows)
26
26
  * e `command -v` (POSIX). Silencioso — captura erro e retorna false.
27
27
  */
28
28
  function binaryExists(name) {
29
- // Defesa em profundidade: hoje `name` vem de uma lista interna fixa
30
- // (claude/codex/gemini), mas validamos o formato e evitamos o shell para que
29
+ // Defesa em profundidade: hoje `name` vem de uma constante interna
30
+ // (`codex`), mas validamos o formato e evitamos o shell para que
31
31
  // isto NUNCA permita injeção de comando, mesmo se a fonte do nome mudar.
32
32
  if (!/^[A-Za-z0-9._-]+$/.test(name))
33
33
  return false;
@@ -64,60 +64,6 @@ function readJsonSafe(path) {
64
64
  return null;
65
65
  }
66
66
  }
67
- /**
68
- * Anthropic / Claude Code.
69
- * - Binário: `claude` (instalado via npm i -g @anthropic-ai/claude-code)
70
- * - Config: ~/.claude/.credentials.json (oauth token OU api key)
71
- * - Env: ANTHROPIC_API_KEY
72
- */
73
- function detectAnthropic() {
74
- const home = homedir();
75
- const installed = binaryExists('claude');
76
- // Prioridade 1: env var (mais simples — Anthropic SDK aceita direto)
77
- const envKey = process.env.ANTHROPIC_API_KEY;
78
- if (envKey && envKey.startsWith('sk-ant-')) {
79
- return {
80
- provider: 'anthropic',
81
- binary: 'claude',
82
- npmPackage: '@anthropic-ai/claude-code',
83
- installed,
84
- credentialSource: 'env',
85
- apiKey: envKey,
86
- hint: 'usando ANTHROPIC_API_KEY do ambiente',
87
- };
88
- }
89
- // Prioridade 2: arquivo de credentials do Claude Code
90
- const credsPath = join(home, '.claude', '.credentials.json');
91
- const creds = readJsonSafe(credsPath);
92
- if (creds) {
93
- // Claude Code armazena `apiKey` ou tokens OAuth. Só populamos apiKey se for sk-ant.
94
- const maybeKey = typeof creds.apiKey === 'string' && creds.apiKey.startsWith('sk-ant-')
95
- ? creds.apiKey
96
- : undefined;
97
- return {
98
- provider: 'anthropic',
99
- binary: 'claude',
100
- npmPackage: '@anthropic-ai/claude-code',
101
- installed,
102
- credentialSource: 'binary-config',
103
- credentialPath: credsPath,
104
- apiKey: maybeKey,
105
- hint: maybeKey
106
- ? `extraindo apiKey de ${credsPath}`
107
- : `Claude Code logado via OAuth (sem apiKey plaintext) — rode "claude /login" e setting ANTHROPIC_API_KEY ou use "neetru config set anthropicApiKey"`,
108
- };
109
- }
110
- return {
111
- provider: 'anthropic',
112
- binary: 'claude',
113
- npmPackage: '@anthropic-ai/claude-code',
114
- installed,
115
- credentialSource: null,
116
- hint: installed
117
- ? 'Claude Code instalado mas sem credenciais — rode "claude" pra logar'
118
- : 'instalar: npm i -g @anthropic-ai/claude-code',
119
- };
120
- }
121
67
  /**
122
68
  * OpenAI / Codex CLI.
123
69
  * - Binário: `codex` (npm i -g @openai/codex)
@@ -172,96 +118,48 @@ function detectOpenAI() {
172
118
  };
173
119
  }
174
120
  /**
175
- * Google / Gemini CLI.
176
- * - Binário: `gemini` (npm i -g @google/gemini-cli)
177
- * - Config: ~/.config/gemini/auth.json (Linux/macOS) ou %APPDATA%\gemini\auth.json (Windows)
178
- * - Env: GEMINI_API_KEY ou GOOGLE_API_KEY
121
+ * Groq e NVIDIA NIM — provedores OpenAI-compatible, só de chave de API. A
122
+ * descoberta lê só a variável de ambiente (GROQ_API_KEY / NVIDIA_API_KEY). O
123
+ * valor PODE ser um pool separado por vírgula; aqui vai cru e o orchestrator
124
+ * separa.
179
125
  */
180
- function detectGemini() {
181
- const home = homedir();
182
- const installed = binaryExists('gemini');
183
- const envKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
184
- if (envKey) {
126
+ function detectApenasChave(provider) {
127
+ const variavel = VARIAVEL_DO_PROVEDOR[provider];
128
+ const valor = process.env[variavel];
129
+ if (valor) {
185
130
  return {
186
- provider: 'gemini',
187
- binary: 'gemini',
188
- npmPackage: '@google/gemini-cli',
189
- installed,
131
+ provider,
132
+ installed: false,
190
133
  credentialSource: 'env',
191
- apiKey: envKey,
192
- hint: 'usando GEMINI_API_KEY (ou GOOGLE_API_KEY) do ambiente',
193
- };
194
- }
195
- const candidates = IS_WINDOWS
196
- ? [
197
- join(process.env.APPDATA ?? join(home, 'AppData', 'Roaming'), 'gemini', 'auth.json'),
198
- join(home, '.config', 'gemini', 'auth.json'),
199
- ]
200
- : [join(home, '.config', 'gemini', 'auth.json')];
201
- for (const path of candidates) {
202
- const auth = readJsonSafe(path);
203
- if (!auth)
204
- continue;
205
- const maybeKey = typeof auth.apiKey === 'string'
206
- ? auth.apiKey
207
- : typeof auth.GEMINI_API_KEY === 'string'
208
- ? auth.GEMINI_API_KEY
209
- : undefined;
210
- return {
211
- provider: 'gemini',
212
- binary: 'gemini',
213
- npmPackage: '@google/gemini-cli',
214
- installed,
215
- credentialSource: 'binary-config',
216
- credentialPath: path,
217
- apiKey: maybeKey,
218
- hint: maybeKey
219
- ? `extraindo apiKey de ${path}`
220
- : `Gemini logado mas sem apiKey plaintext — rode "gemini auth" com --api-key ou exporte GEMINI_API_KEY`,
134
+ apiKey: valor,
135
+ hint: `usando ${variavel} do ambiente`,
221
136
  };
222
137
  }
223
138
  return {
224
- provider: 'gemini',
225
- binary: 'gemini',
226
- npmPackage: '@google/gemini-cli',
227
- installed,
139
+ provider,
140
+ installed: false,
228
141
  credentialSource: null,
229
- hint: installed
230
- ? 'Gemini instalado mas sem credenciais — rode "gemini auth"'
231
- : 'instalar: npm i -g @google/gemini-cli',
142
+ hint: `sem chave — exporte ${variavel} ou rode "neetru config set ${CHAVE_DO_PROVEDOR[provider]} <chave>"`,
232
143
  };
233
144
  }
234
145
  /**
235
- * Detecta todos os providers conhecidos. Read-only — não escreve nada,
236
- * não chama API. Seguro pra rodar em startup do `neetru ai`.
237
- *
238
- * Cobre só os providers que têm CLI concorrente (claude/codex/gemini) — o
239
- * wizard de setup usa esta lista pra ofertar install/login per-binário.
240
- * Groq e NVIDIA NIM são API-key-only (sem CLI pra instalar); a descoberta
241
- * deles vive em `discoveredKeys` (env direto).
146
+ * Um detector por provedor da lista única. O `Record<ProvedorIa, …>` faz o tsc
147
+ * recusar provedor novo em `PROVEDORES_IA` sem detector aqui.
242
148
  */
243
- export function detectAllProviders() {
244
- return [detectAnthropic(), detectOpenAI(), detectGemini()];
245
- }
149
+ const DETECTORES = {
150
+ groq: () => detectApenasChave('groq'),
151
+ nvidia: () => detectApenasChave('nvidia'),
152
+ openai: detectOpenAI,
153
+ };
246
154
  /**
247
- * Groq + NVIDIA NIM — providers OpenAI-compatible, API-key-only. Descoberta
248
- * só por env var (GROQ_API_KEY / NVIDIA_API_KEY). O env PODE conter um pool
249
- * comma-separated; aqui devolvemos o valor cru e o orchestrator parseia.
250
- * Não disrupta o wizard de binários (detectAllProviders).
155
+ * Estado de cada provedor da lista única, na ordem dela. Read-only — não
156
+ * escreve nada, não chama API. Seguro pra rodar em startup do `neetru ai`.
251
157
  */
252
- function discoverApiOnlyKeys() {
253
- const out = {};
254
- const groq = process.env.GROQ_API_KEY;
255
- const nvidia = process.env.NVIDIA_API_KEY;
256
- if (groq)
257
- out.groq = groq;
258
- if (nvidia)
259
- out.nvidia = nvidia;
260
- return out;
158
+ export function detectAllProviders() {
159
+ return PROVEDORES_IA.map((p) => DETECTORES[p]());
261
160
  }
262
161
  /**
263
- * Conveniência: retorna apenas providers com apiKey extraída (pronta pra usar).
264
- * Inclui os CLI-discoverable (claude/codex/gemini) + os API-key-only (groq/nvidia).
162
+ * Conveniência: retorna apenas os provedores com chave achada (pronta pra usar).
265
163
  */
266
164
  export function discoveredKeys() {
267
165
  const out = {};
@@ -269,7 +167,7 @@ export function discoveredKeys() {
269
167
  if (p.apiKey)
270
168
  out[p.provider] = p.apiKey;
271
169
  }
272
- return { ...out, ...discoverApiOnlyKeys() };
170
+ return out;
273
171
  }
274
172
  /**
275
173
  * Tenta instalar globalmente um pacote via npm. Bloqueante, com output
@@ -1,6 +1,7 @@
1
- export type ModelId = 'claude' | 'openai' | 'gemini' | 'groq' | 'nvidia' | 'gateway' | 'auto';
1
+ import { type ModeloDoAi, type ProvedorIa } from './provedores.js';
2
+ export type ModelId = ModeloDoAi;
2
3
  /** Modelo concreto já resolvido (sem 'auto'). */
3
- export type ResolvedModel = 'claude' | 'openai' | 'gemini' | 'groq' | 'nvidia' | 'gateway';
4
+ export type ResolvedModel = ProvedorIa | 'gateway';
4
5
  export interface Message {
5
6
  role: 'user' | 'assistant';
6
7
  content: string;
@@ -32,10 +33,16 @@ export declare function parseKeyPool(raw: string | undefined | null): string[];
32
33
  *
33
34
  * Heurísticas (em ordem):
34
35
  * 1. Se user forçou um modelo (!= auto), respeita — desde que tenha cred.
35
- * 2. Code generation pesado → OpenAI (gpt-4.1); senão NVIDIA NIM (llama-70b).
36
- * 3. Sumarização rápida / Q&A factual → Groq (mais rápido); senão Gemini.
37
- * 4. Default: Claude (arquitetura, análise, raciocínio longo).
38
- * 5. Fallback: primeiro provider com cred (claude > openai > gemini > groq > nvidia).
36
+ * 2. Code generation pesado → NVIDIA NIM (llama-70b, gratuito).
37
+ * 3. Sumarização rápida / Q&A factual → Groq (mais rápido).
38
+ * 4. Fallback: primeiro provider com cred, gratuitos primeiro
39
+ * (groq > nvidia > openai).
40
+ *
41
+ * bug_19d312f3: o default era Claude (API da Anthropic) e o Q&A rápido caía no
42
+ * Gemini quando faltava o Groq. Os dois saíram da lista única (vetados).
43
+ * Revisão do #104: o `auto` preferia a OpenAI (paga) na geração de código e no
44
+ * fallback, à frente do Groq e da NVIDIA. Agora ela só entra no `auto` quando
45
+ * nenhum provedor gratuito tem chave; quem a quer antes pede com `-m openai`.
39
46
  */
40
47
  export declare function routeModel(input: string, preferred: ModelId): ResolvedModel;
41
48
  /**