@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.
- package/CHANGELOG.md +78 -0
- package/README.md +38 -3
- package/dist/commands/admin/sdk-credential.js +3 -1
- package/dist/commands/ai-models.d.ts +2 -1
- package/dist/commands/ai-models.js +4 -5
- package/dist/commands/ai.d.ts +12 -2
- package/dist/commands/ai.js +47 -38
- package/dist/commands/auth.d.ts +3 -2
- package/dist/commands/auth.js +96 -57
- package/dist/commands/build.js +35 -4
- package/dist/commands/chat.d.ts +24 -6
- package/dist/commands/chat.js +43 -7
- package/dist/commands/code.js +11 -2
- package/dist/commands/config.js +15 -13
- package/dist/commands/db-frontdoor.d.ts +116 -0
- package/dist/commands/db-frontdoor.js +572 -0
- package/dist/commands/db-move.d.ts +5 -0
- package/dist/commands/db-move.js +27 -0
- package/dist/commands/db.d.ts +8 -2
- package/dist/commands/db.js +13 -6
- package/dist/commands/deploy.d.ts +14 -5
- package/dist/commands/deploy.js +376 -55
- package/dist/commands/dev-context.js +1 -1
- package/dist/commands/environments-repoint.d.ts +49 -0
- package/dist/commands/environments-repoint.js +219 -0
- package/dist/commands/init.js +7 -1
- package/dist/commands/new.js +14 -0
- package/dist/commands/secrets.d.ts +6 -4
- package/dist/commands/secrets.js +14 -7
- package/dist/commands/servers.d.ts +29 -0
- package/dist/commands/servers.js +108 -1
- package/dist/commands/ui.js +1 -1
- package/dist/commands/workspaces.js +16 -1
- package/dist/index.js +103 -7
- package/dist/lib/ai/auth-discovery.d.ts +9 -12
- package/dist/lib/ai/auth-discovery.js +43 -145
- package/dist/lib/ai/orchestrator.d.ts +13 -6
- package/dist/lib/ai/orchestrator.js +46 -246
- package/dist/lib/ai/provedores.d.ts +57 -0
- package/dist/lib/ai/provedores.js +66 -0
- package/dist/lib/api-client.d.ts +8 -1
- package/dist/lib/api-client.js +18 -2
- package/dist/lib/build-targets.d.ts +48 -10
- package/dist/lib/build-targets.js +64 -10
- package/dist/lib/cli-write.js +6 -3
- package/dist/lib/code-agent-gateway.d.ts +2 -2
- package/dist/lib/code-agent-gateway.js +2 -2
- package/dist/lib/code-agent-settings.d.ts +2 -2
- package/dist/lib/code-agent-settings.js +2 -4
- package/dist/lib/code-agent-types.d.ts +3 -1
- package/dist/lib/config.d.ts +4 -7
- package/dist/lib/config.js +9 -6
- package/dist/lib/deploy-recusa-de-destino.d.ts +9 -0
- package/dist/lib/deploy-recusa-de-destino.js +98 -0
- package/dist/lib/deploy-render-probe.d.ts +117 -0
- package/dist/lib/deploy-render-probe.js +475 -0
- package/dist/lib/deploy-vm-um-app.d.ts +62 -0
- package/dist/lib/deploy-vm-um-app.js +83 -0
- package/dist/lib/segredo-produto.d.ts +25 -5
- package/dist/lib/segredo-produto.js +45 -10
- package/dist/lib/sse.d.ts +4 -5
- package/dist/lib/sse.js +4 -5
- 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
|
-
|
|
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(
|
|
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>',
|
|
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 —
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
import { type ProvedorIa } from './provedores.js';
|
|
2
|
+
export type Provider = ProvedorIa;
|
|
2
3
|
export interface ProviderStatus {
|
|
3
4
|
provider: Provider;
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
*
|
|
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
|
|
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 —
|
|
3
|
-
*
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
|
17
|
-
*
|
|
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
|
|
30
|
-
// (
|
|
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
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
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
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
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
|
|
187
|
-
|
|
188
|
-
npmPackage: '@google/gemini-cli',
|
|
189
|
-
installed,
|
|
131
|
+
provider,
|
|
132
|
+
installed: false,
|
|
190
133
|
credentialSource: 'env',
|
|
191
|
-
apiKey:
|
|
192
|
-
hint:
|
|
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
|
|
225
|
-
|
|
226
|
-
npmPackage: '@google/gemini-cli',
|
|
227
|
-
installed,
|
|
139
|
+
provider,
|
|
140
|
+
installed: false,
|
|
228
141
|
credentialSource: null,
|
|
229
|
-
hint:
|
|
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
|
-
*
|
|
236
|
-
*
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
149
|
+
const DETECTORES = {
|
|
150
|
+
groq: () => detectApenasChave('groq'),
|
|
151
|
+
nvidia: () => detectApenasChave('nvidia'),
|
|
152
|
+
openai: detectOpenAI,
|
|
153
|
+
};
|
|
246
154
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
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
|
|
253
|
-
|
|
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
|
|
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
|
|
170
|
+
return out;
|
|
273
171
|
}
|
|
274
172
|
/**
|
|
275
173
|
* Tenta instalar globalmente um pacote via npm. Bloqueante, com output
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
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 =
|
|
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 →
|
|
36
|
-
* 3. Sumarização rápida / Q&A factual → Groq (mais rápido)
|
|
37
|
-
* 4.
|
|
38
|
-
*
|
|
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
|
/**
|