@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/commands/auth.js
CHANGED
|
@@ -1,33 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `neetru auth` — gerencia credenciais dos provedores de IA usados pelo
|
|
3
|
-
* `neetru ai
|
|
3
|
+
* `neetru ai`: os da lista única (`lib/ai/provedores.ts` — Groq, NVIDIA NIM e
|
|
4
|
+
* OpenAI). O AI Gateway da Neetru não precisa de chave: basta `neetru login`.
|
|
4
5
|
*
|
|
5
6
|
* Sub-comandos:
|
|
6
|
-
* neetru auth status →
|
|
7
|
-
* neetru auth setup → wizard interativo:
|
|
8
|
-
*
|
|
9
|
-
* neetru auth pull →
|
|
7
|
+
* neetru auth status → estado de cada provedor (chave achada ou não)
|
|
8
|
+
* neetru auth setup → wizard interativo: mostra o estado e pede a chave
|
|
9
|
+
* que falta
|
|
10
|
+
* neetru auth pull → copia pra config do Neetru a chave achada no
|
|
11
|
+
* arquivo do Codex (a do ambiente já é lida direto)
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* bug_19d312f3: o wizard oferecia Claude (Anthropic) e Gemini (Google), vetados
|
|
14
|
+
* pela casa, e não oferecia Groq nem NVIDIA. Agora ele percorre a lista única.
|
|
13
15
|
*/
|
|
14
16
|
import chalk from 'chalk';
|
|
15
17
|
import * as readline from 'readline/promises';
|
|
16
18
|
import inquirer from 'inquirer';
|
|
17
19
|
import { detectAllProviders, installGlobal, } from '../lib/ai/auth-discovery.js';
|
|
20
|
+
import { CHAVE_DO_PROVEDOR, NOME_DO_PROVEDOR, PROVEDORES_IA } from '../lib/ai/provedores.js';
|
|
18
21
|
import { config } from '../lib/config.js';
|
|
19
22
|
import { log } from '../utils/logger.js';
|
|
20
|
-
import { saveMfaSession } from '../lib/cli-write.js';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
import { loadMfaSession, saveMfaSession } from '../lib/cli-write.js';
|
|
24
|
+
/**
|
|
25
|
+
* Chave achada na máquina que pode ser copiada para a config do Neetru.
|
|
26
|
+
*
|
|
27
|
+
* A do ambiente (GROQ_API_KEY, NVIDIA_API_KEY, OPENAI_API_KEY) NÃO é copiada:
|
|
28
|
+
* a CLI já a lê direto da variável a cada uso, e a cópia passaria a valer mais
|
|
29
|
+
* que ela (a config vem primeiro) — trocar a chave na variável deixaria a CLI
|
|
30
|
+
* usando a antiga, com 401 sem pista de onde ela vem (revisão do #104). A do
|
|
31
|
+
* arquivo do Codex continua sendo copiada, como antes.
|
|
32
|
+
*/
|
|
33
|
+
function chaveCopiavel(s) {
|
|
34
|
+
return s.credentialSource === 'env' ? undefined : s.apiKey;
|
|
35
|
+
}
|
|
36
|
+
function badge(s, naConfig = false) {
|
|
37
|
+
// Chave gravada na config do Neetru também conta como pronto (revisão do
|
|
38
|
+
// #104: o assistente mostrava "sem chave" e pedia de novo um provedor já
|
|
39
|
+
// configurado com `neetru config set`).
|
|
40
|
+
if (s.apiKey || naConfig)
|
|
30
41
|
return chalk.green('✓ pronto');
|
|
42
|
+
// Provedor só de chave (Groq, NVIDIA): não há programa para instalar.
|
|
43
|
+
if (!s.binary)
|
|
44
|
+
return chalk.dim('- sem chave');
|
|
31
45
|
if (s.installed && s.credentialSource)
|
|
32
46
|
return chalk.yellow('! login parcial');
|
|
33
47
|
if (s.installed)
|
|
@@ -35,31 +49,14 @@ function badge(s) {
|
|
|
35
49
|
return chalk.dim('- não instalado');
|
|
36
50
|
}
|
|
37
51
|
function rowName(p) {
|
|
38
|
-
|
|
39
|
-
case 'anthropic':
|
|
40
|
-
return 'Claude (Anthropic)';
|
|
41
|
-
case 'openai':
|
|
42
|
-
return 'Codex (OpenAI)';
|
|
43
|
-
case 'gemini':
|
|
44
|
-
return 'Gemini (Google)';
|
|
45
|
-
case 'groq':
|
|
46
|
-
return 'Groq';
|
|
47
|
-
case 'nvidia':
|
|
48
|
-
return 'NVIDIA NIM';
|
|
49
|
-
}
|
|
52
|
+
return NOME_DO_PROVEDOR[p];
|
|
50
53
|
}
|
|
51
54
|
/**
|
|
52
55
|
* `neetru auth status` — pura leitura, mostra tabela.
|
|
53
56
|
*/
|
|
54
57
|
export async function runAuthStatus(opts = {}) {
|
|
55
58
|
const providers = detectAllProviders();
|
|
56
|
-
const configured =
|
|
57
|
-
anthropic: !!config.get('anthropicApiKey'),
|
|
58
|
-
openai: !!config.get('openaiApiKey'),
|
|
59
|
-
gemini: !!config.get('geminiApiKey'),
|
|
60
|
-
groq: !!config.get('groqApiKey'),
|
|
61
|
-
nvidia: !!config.get('nvidiaApiKey'),
|
|
62
|
-
};
|
|
59
|
+
const configured = Object.fromEntries(PROVEDORES_IA.map((p) => [p, !!config.get(CHAVE_DO_PROVEDOR[p])]));
|
|
63
60
|
if (opts.json) {
|
|
64
61
|
console.log(JSON.stringify({
|
|
65
62
|
providers: providers.map((p) => ({
|
|
@@ -72,33 +69,46 @@ export async function runAuthStatus(opts = {}) {
|
|
|
72
69
|
}
|
|
73
70
|
console.log(chalk.bold('\nNeetru Auth — status dos provedores de IA\n'));
|
|
74
71
|
for (const p of providers) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
console.log(`
|
|
72
|
+
const naConfig = configured[p.provider];
|
|
73
|
+
const configuredHere = naConfig ? chalk.green('(neetru config OK)') : '';
|
|
74
|
+
console.log(` ${badge(p, naConfig)} ${chalk.bold(rowName(p.provider))} ${configuredHere}`);
|
|
75
|
+
// A chave da config vem antes da achada na máquina (`resolveKeyPool` no
|
|
76
|
+
// orchestrator): com ela gravada, é ela que a CLI usa.
|
|
77
|
+
const dica = naConfig ? 'usando a chave gravada na config do Neetru' : p.hint;
|
|
78
|
+
console.log(` ${chalk.dim(dica)}`);
|
|
78
79
|
}
|
|
79
80
|
console.log();
|
|
80
81
|
console.log(chalk.dim('Para configurar tudo de uma vez: neetru auth setup'));
|
|
81
82
|
}
|
|
82
83
|
/**
|
|
83
|
-
* `neetru auth pull` — sem perguntar, copia
|
|
84
|
-
*
|
|
84
|
+
* `neetru auth pull` — sem perguntar, copia pra config do Neetru a chave achada
|
|
85
|
+
* no arquivo do Codex. A do ambiente não é copiada: a CLI já a lê direto da
|
|
86
|
+
* variável (ver `chaveCopiavel`). Não sobrescreve chave já configurada.
|
|
85
87
|
*/
|
|
86
88
|
export async function runAuthPull(opts = {}) {
|
|
87
89
|
const providers = detectAllProviders();
|
|
88
90
|
const copied = [];
|
|
91
|
+
const doAmbiente = [];
|
|
89
92
|
for (const p of providers) {
|
|
90
|
-
|
|
93
|
+
const chave = chaveCopiavel(p);
|
|
94
|
+
if (!chave) {
|
|
95
|
+
if (p.apiKey)
|
|
96
|
+
doAmbiente.push(p.provider);
|
|
91
97
|
continue;
|
|
92
|
-
|
|
98
|
+
}
|
|
99
|
+
const cfgKey = CHAVE_DO_PROVEDOR[p.provider];
|
|
93
100
|
if (config.get(cfgKey))
|
|
94
101
|
continue; // não sobrescreve
|
|
95
|
-
config.set(cfgKey,
|
|
102
|
+
config.set(cfgKey, chave);
|
|
96
103
|
copied.push(p.provider);
|
|
97
104
|
}
|
|
98
105
|
if (opts.json) {
|
|
99
106
|
console.log(JSON.stringify({ copied }, null, 2));
|
|
100
107
|
return;
|
|
101
108
|
}
|
|
109
|
+
if (doAmbiente.length) {
|
|
110
|
+
log.dim(`Lidas direto do ambiente, sem cópia: ${doAmbiente.map(rowName).join(', ')}.`);
|
|
111
|
+
}
|
|
102
112
|
if (copied.length === 0) {
|
|
103
113
|
log.info('Nenhuma cred nova para copiar (ou já estão configuradas).');
|
|
104
114
|
return;
|
|
@@ -126,28 +136,32 @@ export async function runAuthSetup(opts = {}) {
|
|
|
126
136
|
// Modo não-interativo: só roda pull silencioso.
|
|
127
137
|
return runAuthPull(opts);
|
|
128
138
|
}
|
|
139
|
+
const naConfig = (p) => !!config.get(CHAVE_DO_PROVEDOR[p.provider]);
|
|
129
140
|
console.log(chalk.bold('\nNeetru Auth — wizard de configuração\n'));
|
|
130
141
|
console.log(chalk.dim('Detectado:\n'));
|
|
131
142
|
for (const p of providers) {
|
|
132
|
-
console.log(` ${badge(p)} ${rowName(p.provider)}`);
|
|
143
|
+
console.log(` ${badge(p, naConfig(p))} ${rowName(p.provider)}`);
|
|
133
144
|
}
|
|
134
145
|
console.log();
|
|
135
|
-
// Pull silencioso
|
|
146
|
+
// Pull silencioso do que dá para copiar (o arquivo do Codex). A chave do
|
|
147
|
+
// ambiente não é copiada: a CLI já a lê direto (ver `chaveCopiavel`).
|
|
136
148
|
const autoConfigured = [];
|
|
137
149
|
for (const p of providers) {
|
|
138
|
-
|
|
150
|
+
const chave = chaveCopiavel(p);
|
|
151
|
+
if (!chave)
|
|
139
152
|
continue;
|
|
140
|
-
const cfgKey =
|
|
153
|
+
const cfgKey = CHAVE_DO_PROVEDOR[p.provider];
|
|
141
154
|
if (!config.get(cfgKey)) {
|
|
142
|
-
config.set(cfgKey,
|
|
155
|
+
config.set(cfgKey, chave);
|
|
143
156
|
autoConfigured.push(p.provider);
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
if (autoConfigured.length) {
|
|
147
160
|
log.success(`Reaproveitadas creds de: ${autoConfigured.join(', ')}`);
|
|
148
161
|
}
|
|
149
|
-
// Identifica provedores que precisam de ação
|
|
150
|
-
|
|
162
|
+
// Identifica provedores que precisam de ação: sem chave achada na máquina E
|
|
163
|
+
// sem chave na config do Neetru.
|
|
164
|
+
const needsAction = providers.filter((p) => !p.apiKey && !naConfig(p));
|
|
151
165
|
if (needsAction.length === 0) {
|
|
152
166
|
log.success('Todos os provedores prontos. Você pode rodar `neetru ai`.');
|
|
153
167
|
return;
|
|
@@ -167,8 +181,9 @@ export async function runAuthSetup(opts = {}) {
|
|
|
167
181
|
}
|
|
168
182
|
for (const p of wanted) {
|
|
169
183
|
console.log(chalk.bold(`\n→ ${rowName(p.provider)}`));
|
|
170
|
-
// Instalar binário se ausente.
|
|
171
|
-
|
|
184
|
+
// Instalar binário se ausente. Provedor só de chave (Groq, NVIDIA) não
|
|
185
|
+
// tem programa: vai direto pedir a chave.
|
|
186
|
+
if (p.npmPackage && !p.installed) {
|
|
172
187
|
const install = await confirm(rl, ` Instalar ${p.npmPackage} globalmente?`, true);
|
|
173
188
|
if (install) {
|
|
174
189
|
log.info(`Rodando: npm install -g ${p.npmPackage}`);
|
|
@@ -184,10 +199,10 @@ export async function runAuthSetup(opts = {}) {
|
|
|
184
199
|
continue;
|
|
185
200
|
}
|
|
186
201
|
}
|
|
187
|
-
else {
|
|
202
|
+
else if (p.binary) {
|
|
188
203
|
log.dim(` ${p.binary} já instalado.`);
|
|
189
204
|
}
|
|
190
|
-
// Pedir api key direto (mais simples que mandar usuário
|
|
205
|
+
// Pedir api key direto (mais simples que mandar o usuário logar no programa).
|
|
191
206
|
const provideKey = await confirm(rl, ` Colar API key agora? (alternativa: ${p.hint.replace(/^.*— /, '')})`, true);
|
|
192
207
|
if (provideKey) {
|
|
193
208
|
// Auditoria UX 2026-09: a API key é SEGREDO — não pode ecoar na tela/
|
|
@@ -206,16 +221,19 @@ export async function runAuthSetup(opts = {}) {
|
|
|
206
221
|
rl.resume();
|
|
207
222
|
}
|
|
208
223
|
if (key) {
|
|
209
|
-
config.set(
|
|
224
|
+
config.set(CHAVE_DO_PROVEDOR[p.provider], key);
|
|
210
225
|
log.success(`${rowName(p.provider)} configurado.`);
|
|
211
226
|
}
|
|
212
227
|
else {
|
|
213
228
|
log.warn('Vazio — não configurado.');
|
|
214
229
|
}
|
|
215
230
|
}
|
|
216
|
-
else {
|
|
231
|
+
else if (p.binary) {
|
|
217
232
|
log.dim(`Use o login do ${p.binary} ou rode "neetru auth pull" depois pra puxar.`);
|
|
218
233
|
}
|
|
234
|
+
else {
|
|
235
|
+
log.dim(`Depois, ${p.hint.replace(/^.*— /, '')}.`);
|
|
236
|
+
}
|
|
219
237
|
}
|
|
220
238
|
console.log();
|
|
221
239
|
log.success('Wizard finalizado. Rode `neetru ai` pra testar.');
|
|
@@ -292,6 +310,27 @@ export async function runAuthMfaSession(opts = {}) {
|
|
|
292
310
|
}
|
|
293
311
|
// Salva o token localmente para uso automático em operações destrutivas.
|
|
294
312
|
await saveMfaSession(res.mfaSessionToken, res.expiresAt);
|
|
313
|
+
// bug_757b9746: `saveMfaSession` engole a falha de gravação, e este comando
|
|
314
|
+
// respondia `saved: true` + "usarão esta sessão automaticamente" sem conferir
|
|
315
|
+
// nada — se a gravação falhasse, o comando destrutivo seguinte pedia o TOTP de
|
|
316
|
+
// novo e a mensagem tinha mentido. Confere lendo de volta pelo MESMO caminho
|
|
317
|
+
// que os comandos destrutivos usam (`loadMfaSession`): pega a gravação que
|
|
318
|
+
// falhou e também o `expiresAt` que o relógio desta máquina já dá por vencido.
|
|
319
|
+
if (loadMfaSession() !== res.mfaSessionToken) {
|
|
320
|
+
const msg = 'A sessão MFA foi aberta no Core, mas não ficou gravada nesta máquina ' +
|
|
321
|
+
'(~/.config/neetru-cli/mfa-session.json) ou o relógio daqui já a dá por vencida. ' +
|
|
322
|
+
'Os comandos destrutivos vão pedir o código TOTP de novo. Confira a permissão ' +
|
|
323
|
+
'dessa pasta e a hora do sistema, e rode `neetru auth mfa-session` outra vez com um código novo.';
|
|
324
|
+
if (opts.json) {
|
|
325
|
+
console.log(JSON.stringify({ ok: false, error: 'mfa_session_not_saved', message: msg, saved: false }));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
log.error(msg);
|
|
329
|
+
}
|
|
330
|
+
const { safeExit } = await import('../utils/safe-exit.js');
|
|
331
|
+
safeExit(1);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
295
334
|
if (opts.json) {
|
|
296
335
|
console.log(JSON.stringify({
|
|
297
336
|
ok: true,
|
package/dist/commands/build.js
CHANGED
|
@@ -30,12 +30,13 @@ import { safeExit } from '../utils/safe-exit.js';
|
|
|
30
30
|
import { normalizeTarget } from './env.js';
|
|
31
31
|
import { RUNTIME_ENVIRONMENT_IDS } from '../lib/envs.js';
|
|
32
32
|
import { formatChoices } from '../cli-kit/choices.js';
|
|
33
|
+
import { adviseFlag } from '../cli-kit/command-context.js';
|
|
33
34
|
import { registerSpinner, unregisterSpinner } from '../utils/spinner-registry.js';
|
|
34
35
|
import { CodificacaoInvalida, decodificarUtf8, semBomInicial } from '../lib/texto-utf8.js';
|
|
35
36
|
import { variaveisDoEnvFile } from '../lib/env-file-do-build.js';
|
|
36
37
|
// bug_678ddc98: detecção de alvo — diretório do app, gerenciador de pacote e
|
|
37
38
|
// layout do standalone (possivelmente aninhado). Ver `src/lib/build-targets.ts`.
|
|
38
|
-
import { detectPackageManager, comandoInstall, comandoRunScript, comandoExecBin, resolveStandaloneLayout, baseDeInjecaoNoTar, } from '../lib/build-targets.js';
|
|
39
|
+
import { detectPackageManager, comandoInstall, comandoRunScript, comandoExecBin, resolveStandaloneLayout, lerRelativeAppDir, baseDeInjecaoNoTar, } from '../lib/build-targets.js';
|
|
39
40
|
const CONFIG_FILENAMES = ['neetru.config.json', '.neetru.json'];
|
|
40
41
|
function fileExists(p) {
|
|
41
42
|
try {
|
|
@@ -542,12 +543,20 @@ pm = { name: 'npm', rootDir: cwd, source: 'default' }) {
|
|
|
542
543
|
}
|
|
543
544
|
}
|
|
544
545
|
else {
|
|
545
|
-
lg.dim(` --no-cache ignorado: ${pm.name} não tem \`cache clean --force\`.`);
|
|
546
|
+
lg.dim(` --no-cache ignorado: ${pm.name} não tem equivalente ao \`npm cache clean --force\`.`);
|
|
546
547
|
}
|
|
547
548
|
}
|
|
548
549
|
if (process.platform === 'win32') {
|
|
550
|
+
// bug_89d2497e (etapa 1): o `neetru deploy` chega aqui pelo `runBuild`
|
|
551
|
+
// (sem artefato pronto) e não declara --no-install — quem seguia o
|
|
552
|
+
// conselho batia em "unknown option". Só o `neetru build` ouve a flag; o
|
|
553
|
+
// deploy recebe só o pedido de pausar o dev server.
|
|
549
554
|
lg.warn(`Se houver \`next dev\` ativo na pasta, ele pode conflitar com o ${install.cmd} install (file locks).\n` +
|
|
550
|
-
|
|
555
|
+
adviseFlag({
|
|
556
|
+
flag: '--no-install',
|
|
557
|
+
withFlag: ' Pause o dev server antes do build, ou use --no-install pra pular esta etapa.',
|
|
558
|
+
withoutFlag: ' Pause o dev server antes de continuar.',
|
|
559
|
+
}));
|
|
551
560
|
}
|
|
552
561
|
// bug_678ddc98: o install roda na RAIZ do workspace (onde mora o lockfile),
|
|
553
562
|
// não no diretório do app — `pnpm install` dentro de `apps/web` sem o
|
|
@@ -620,9 +629,31 @@ pm = { name: 'npm', rootDir: cwd, source: 'default' }) {
|
|
|
620
629
|
// Next.js aninha o app dentro do standalone (`apps/web/server.js`) e esse
|
|
621
630
|
// caminho fixo apontava pro vazio. Agora vem do layout real pós-build.
|
|
622
631
|
if (standalone && isStandaloneBuildDir(cwd) && !layout) {
|
|
632
|
+
// bug_678ddc98: quando o Next.js declarou o lugar do app
|
|
633
|
+
// (`relativeAppDir`), o motivo do aborto é outro e a mensagem diz qual.
|
|
634
|
+
const declarado = lerRelativeAppDir(cwd);
|
|
635
|
+
if (declarado.tipo === 'declarado') {
|
|
636
|
+
const prefixo = declarado.prefixo;
|
|
637
|
+
throw new Error(`O Next.js registrou o app ${prefixo ? `em \`${prefixo}\`` : 'na raiz do standalone'} ` +
|
|
638
|
+
'(campo `relativeAppDir` de `.next/required-server-files.json`), ' +
|
|
639
|
+
`mas \`.next/standalone/${prefixo ? `${prefixo}/` : ''}server.js\` não existe. ` +
|
|
640
|
+
'Isso costuma indicar uma pasta `.next` com restos de builds diferentes: apague a pasta `.next` do app, ' +
|
|
641
|
+
'rode o build de novo e confira se `output: "standalone"` está ativo. ' +
|
|
642
|
+
'Abortando: um tarball sem entrypoint sobe e nunca inicia.');
|
|
643
|
+
}
|
|
644
|
+
if (declarado.tipo === 'invalido') {
|
|
645
|
+
throw new Error(`O campo \`relativeAppDir\` de \`.next/required-server-files.json\` vale "${declarado.valor}" ` +
|
|
646
|
+
'e aponta para fora do standalone: o `outputFileTracingRoot` do next.config precisa ser uma pasta ' +
|
|
647
|
+
'ACIMA do app (no monorepo, a raiz do repositório). ' +
|
|
648
|
+
'Abortando: um tarball sem entrypoint sobe e nunca inicia.');
|
|
649
|
+
}
|
|
623
650
|
throw new Error('Existe `.next/standalone` mas nenhum `server.js` foi encontrado dentro dele. ' +
|
|
624
651
|
'Num monorepo o Next.js aninha o app sob o `outputFileTracingRoot` (ex: `apps/web/server.js`) — ' +
|
|
625
|
-
|
|
652
|
+
// bug_89d2497e (etapa 1): o `neetru deploy` chega aqui pelo `runBuild` e
|
|
653
|
+
// não declara --app; o conselho cita o `neetru build` inteiro. O artefato
|
|
654
|
+
// sai no diretório de onde o build roda, que é onde o deploy o procura.
|
|
655
|
+
'confira se o build rodou no diretório certo (rode `neetru build --app <dir>` no diretório de onde vai rodar o deploy) ' +
|
|
656
|
+
'e se `output: "standalone"` está ativo. ' +
|
|
626
657
|
'Abortando: um tarball sem entrypoint sobe e nunca inicia.');
|
|
627
658
|
}
|
|
628
659
|
if (layout?.appPrefix) {
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import
|
|
2
|
+
import { NeetruCliError } from '../cli-kit/errors.js';
|
|
3
3
|
type ActorType = 'agent_claude' | 'human' | 'cli';
|
|
4
4
|
type ChatChannelType = 'global' | 'product' | 'custom';
|
|
5
5
|
export interface ChatChannelsOptions {
|
|
@@ -55,8 +55,9 @@ export interface ChatIdentityOptions {
|
|
|
55
55
|
* nome. Sem aviso: a saída era o mesmo "✓ Apelido definido" do caminho feliz.
|
|
56
56
|
*
|
|
57
57
|
* Agora `show`/`ver` lê, `set`/`definir` grava o resto, `clear`/`limpar` limpa,
|
|
58
|
-
* e o posicional antigo (`identity "PDV · IA"`) segue funcionando
|
|
59
|
-
* dá pra ter um apelido que
|
|
58
|
+
* e o posicional antigo (`identity "PDV · IA"`) segue funcionando — REVOGADO em
|
|
59
|
+
* 22/10, ver DEFAULT INVERTIDO abaixo. O preço: não dá pra ter um apelido que
|
|
60
|
+
* seja SÓ uma dessas palavras.
|
|
60
61
|
*
|
|
61
62
|
* P105 (2026-10-18): o conserto acima cobria só as palavras que ele conhecia.
|
|
62
63
|
* `identity describe` gravava "describe", `identity list` gravava "list",
|
|
@@ -72,6 +73,20 @@ export interface ChatIdentityOptions {
|
|
|
72
73
|
* Os caminhos citados (`neetru chat identity …`, `neetru chat send --as`) saem
|
|
73
74
|
* da árvore do Commander (`contextoDeIdentidadeDaArvore`), não de literal
|
|
74
75
|
* (revisão do #87, BAIXO 5).
|
|
76
|
+
*
|
|
77
|
+
* DEFAULT INVERTIDO (2026-10-22, encerramento do bug_09574d4b derrubado): os
|
|
78
|
+
* dois consertos acima eram LISTA DE PERMISSÃO — recusavam as 18 palavras que
|
|
79
|
+
* conheciam e gravavam todo o resto. Medido na 2.33.0 publicada: `identity
|
|
80
|
+
* mostrar`, `identity status`, `identity whoami`, `identity atual`, `identity
|
|
81
|
+
* apelido` (o alias do próprio comando) e `identity "Dev Core"` gravavam o
|
|
82
|
+
* argumento como apelido da credencial COMPARTILHADA, com o mesmo "✓ Apelido
|
|
83
|
+
* definido" do caminho feliz. Allowlist de verbo nunca fecha esse padrão: a
|
|
84
|
+
* pessoa sempre acha mais um verbo. Agora o posicional solto NÃO grava nunca —
|
|
85
|
+
* qualquer primeira palavra fora de `show`/`set`/`clear` (e seus equivalentes)
|
|
86
|
+
* é recusada, lista os subcomandos e ensina `identity set "<texto>"`. As únicas
|
|
87
|
+
* portas de escrita são `identity set "Nome"`, `identity clear` e o `--as` do
|
|
88
|
+
* `send` (que avisa no stderr). O preço, aceito: `identity "Nome"` — a forma
|
|
89
|
+
* posicional da CH3 — deixou de gravar.
|
|
75
90
|
*/
|
|
76
91
|
export type PedidoDeIdentidade = {
|
|
77
92
|
acao: 'mostrar';
|
|
@@ -122,10 +137,13 @@ interface ChatIdentityResponse {
|
|
|
122
137
|
email?: string;
|
|
123
138
|
}
|
|
124
139
|
/**
|
|
125
|
-
* `neetru chat identity
|
|
140
|
+
* O que `neetru chat identity` faz depois de `interpretarArgsDeIdentidade`
|
|
141
|
+
* decidir — apelido inerente à credencial (CH3). Quem chega aqui com texto já
|
|
142
|
+
* passou por `identity set "Nome"`, `identity clear` ou `send --as`: o
|
|
143
|
+
* posicional solto não chega (default invertido, bug_09574d4b).
|
|
126
144
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
145
|
+
* `undefined`: mostra o apelido atual (GET /api/cli/v1/chat/identity).
|
|
146
|
+
* Texto: persiste o apelido na credencial (POST). A partir daí, TODA
|
|
129
147
|
* mensagem postada com este token aparece com este nome — a IA se identifica em
|
|
130
148
|
* nome do dev dono do token. Apelido vazio ("") limpa (volta ao email).
|
|
131
149
|
*
|
package/dist/commands/chat.js
CHANGED
|
@@ -33,7 +33,9 @@ import { log } from '../utils/logger.js';
|
|
|
33
33
|
import { safeExit } from '../utils/safe-exit.js';
|
|
34
34
|
import { fmtTimestamp } from '../lib/render.js';
|
|
35
35
|
import { exitWithCliError } from '../lib/cli-error.js';
|
|
36
|
-
import {
|
|
36
|
+
import { EXIT_CODES, NeetruCliError } from '../cli-kit/errors.js';
|
|
37
|
+
import { formatChoices } from '../cli-kit/choices.js';
|
|
38
|
+
import { isSubcommandVerb, reservedVerbError, suggestSubcommands } from '../cli-kit/suggest.js';
|
|
37
39
|
import { commandDeclaresFlag, declaredFlags } from '../cli-kit/command-context.js';
|
|
38
40
|
// ─── Helpers de display ───────────────────────────────────────────────────────
|
|
39
41
|
function colorRole(role) {
|
|
@@ -305,7 +307,35 @@ export function interpretarArgsDeIdentidade(args, ctx = CONTEXTO_DE_IDENTIDADE_P
|
|
|
305
307
|
? `Para gravar "${texto}" como apelido: neetru ${ctx.comando} set "${texto}"`
|
|
306
308
|
: comoGravarVerboComoApelido(primeiro.trim(), ctx));
|
|
307
309
|
}
|
|
308
|
-
|
|
310
|
+
// Default INVERTIDO: o que não é subcomando de `identity` é recusado — nunca
|
|
311
|
+
// gravado como apelido (ver o bloco acima de `PedidoDeIdentidade`).
|
|
312
|
+
return subcomandoDesconhecido(args, ctx);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* `identity <qualquer outra coisa>`: recusa SEM escrever. Não diz que a palavra
|
|
316
|
+
* é "de comando" (`Dev Core` não é) — diz que não é subcomando de `identity`,
|
|
317
|
+
* lista os que existem e ensina a forma explícita de gravar o mesmo texto.
|
|
318
|
+
* Sugestão só quando passa o piso do gcloud (`suggestSubcommands`).
|
|
319
|
+
*/
|
|
320
|
+
function subcomandoDesconhecido(args, ctx) {
|
|
321
|
+
const texto = args.join(' ');
|
|
322
|
+
const sugestoes = suggestSubcommands(args[0].trim(), VERBOS_DE_IDENTIDADE).map((s) => `neetru ${ctx.comando} ${s}`);
|
|
323
|
+
const linhas = [
|
|
324
|
+
`"${texto}" não é subcomando de \`neetru ${ctx.comando}\` — e o argumento solto não grava apelido.`,
|
|
325
|
+
];
|
|
326
|
+
if (sugestoes.length > 0)
|
|
327
|
+
linhas.push(`Você quis dizer: ${sugestoes.map((s) => `\`${s}\``).join(' ou ')}?`);
|
|
328
|
+
linhas.push(`Subcomandos de \`neetru ${ctx.comando}\`: ${formatChoices(VERBOS_DE_IDENTIDADE)} ` +
|
|
329
|
+
`(sem subcomando, \`neetru ${ctx.comando}\` mostra o apelido atual).`);
|
|
330
|
+
linhas.push(texto.trim()
|
|
331
|
+
? `Para gravar "${texto.trim()}" como apelido: neetru ${ctx.comando} set "${texto.trim()}"`
|
|
332
|
+
: `Para limpar o apelido: neetru ${ctx.comando} clear`);
|
|
333
|
+
const erro = new NeetruCliError(linhas.join('\n '), {
|
|
334
|
+
code: 'uso_invalido',
|
|
335
|
+
exit: EXIT_CODES.client,
|
|
336
|
+
details: { value: texto, suggestions: sugestoes, valid: [...VERBOS_DE_IDENTIDADE] },
|
|
337
|
+
});
|
|
338
|
+
return { acao: 'erro', mensagem: erro.message, erro };
|
|
309
339
|
}
|
|
310
340
|
function subcomandosIrmaos(command) {
|
|
311
341
|
const irmaos = command?.parent?.commands ?? [];
|
|
@@ -360,10 +390,13 @@ function respondeuEmModoMaquina(res, opts) {
|
|
|
360
390
|
return false;
|
|
361
391
|
}
|
|
362
392
|
/**
|
|
363
|
-
* `neetru chat identity
|
|
393
|
+
* O que `neetru chat identity` faz depois de `interpretarArgsDeIdentidade`
|
|
394
|
+
* decidir — apelido inerente à credencial (CH3). Quem chega aqui com texto já
|
|
395
|
+
* passou por `identity set "Nome"`, `identity clear` ou `send --as`: o
|
|
396
|
+
* posicional solto não chega (default invertido, bug_09574d4b).
|
|
364
397
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
398
|
+
* `undefined`: mostra o apelido atual (GET /api/cli/v1/chat/identity).
|
|
399
|
+
* Texto: persiste o apelido na credencial (POST). A partir daí, TODA
|
|
367
400
|
* mensagem postada com este token aparece com este nome — a IA se identifica em
|
|
368
401
|
* nome do dev dono do token. Apelido vazio ("") limpa (volta ao email).
|
|
369
402
|
*
|
|
@@ -617,7 +650,9 @@ export async function runChatHistory(opts) {
|
|
|
617
650
|
console.log(JSON.stringify({ ok: true, count: messages.length, messages }));
|
|
618
651
|
return;
|
|
619
652
|
}
|
|
620
|
-
|
|
653
|
+
// bug_89d2497e (etapa 1): o `neetru chat` também chama este runner e não tem
|
|
654
|
+
// --since; o rótulo descreve a janela sem citar a flag.
|
|
655
|
+
const janela = initialSinceMs !== undefined ? 'desde o cursor informado' : `últimas ${sinceHours}h`;
|
|
621
656
|
console.log(chalk.bold(`\nHistórico — ${label} (${janela} · ${messages.length} msg)\n`));
|
|
622
657
|
if (messages.length === 0) {
|
|
623
658
|
log.dim('Nenhuma mensagem no período.');
|
|
@@ -825,7 +860,8 @@ export function registerChatCommand(program) {
|
|
|
825
860
|
.command('identity [args...]')
|
|
826
861
|
.alias('apelido')
|
|
827
862
|
.description('Apelido da credencial no chat (persiste por token). ' +
|
|
828
|
-
'`identity` ou `identity show` lê; `identity set "Nome"` grava; `identity clear` limpa.'
|
|
863
|
+
'`identity` ou `identity show` lê; `identity set "Nome"` grava; `identity clear` limpa. ' +
|
|
864
|
+
'Qualquer outra palavra é recusada — nunca vira apelido.')
|
|
829
865
|
.option('--json', 'saída em JSON')
|
|
830
866
|
.action(async (args, _cmdOpts, command) => {
|
|
831
867
|
// O `--json` vai pro pai `chat` (ver `opcaoHerdada`): ler só `cmdOpts.json`
|
package/dist/commands/code.js
CHANGED
|
@@ -11,6 +11,7 @@ import { parseToolRequests, extractPatch } from '../lib/code-agent-protocol.js';
|
|
|
11
11
|
import { applyUnifiedPatch, checkPatchApplies, getGitDiff, getProjectOverview, listFiles, readFileContext, renderColoredDiff, runCodeTool, truncateForGateway, } from '../lib/code-agent-files.js';
|
|
12
12
|
import { compactCodeHistory, compactHistoryToFit, MAX_HISTORY_TOTAL_CHARS, saveCodeSession, autoSaveCodeSession, listCodeSessions, loadCodeSession, logAppliedPatch, CodeAgentStateMachine, } from '../lib/code-agent-session.js';
|
|
13
13
|
import { DEFAULT_CODE_MAX_TOKENS, DEFAULT_CODE_MAX_TURNS, DEFAULT_CODE_TEMPERATURE, ULTRACODE_EFFORT, ULTRACODE_MAX_TOKENS, ULTRACODE_MAX_TURNS, parseBoundedNumber, resolveCodeEffort, resolveCodeModel, resolveCodeProvider, } from '../lib/code-agent-settings.js';
|
|
14
|
+
import { PROVEDORES_IA, ehProvedorIa } from '../lib/ai/provedores.js';
|
|
14
15
|
import { callGateway, streamGatewayChat, AI_GATEWAY_CHAT_PATH } from '../lib/code-agent-gateway.js';
|
|
15
16
|
import { createBudgetTracker, decideBudgetAction, MAX_CONSECUTIVE_PATCH_REPAIRS, MAX_TOOLS_PER_TURN, SINGLE_ACTION_TOOLS, } from '../lib/code-agent-budget.js';
|
|
16
17
|
import { CliApiError } from '../lib/api-client.js';
|
|
@@ -45,7 +46,15 @@ export function resolveSettings(opts) {
|
|
|
45
46
|
envModel: process.env.NEETRU_CODE_MODEL,
|
|
46
47
|
configuredGatewayModel: config.get('gatewayModel'),
|
|
47
48
|
});
|
|
48
|
-
const
|
|
49
|
+
const pedidoDeProvider = asOptionalString(opts.provider);
|
|
50
|
+
const provider = resolveCodeProvider(pedidoDeProvider, config.get('gatewayProvider'), model.model);
|
|
51
|
+
// Revisão do #104 (bug_19d312f3): `--provider gemini` (ou qualquer nome fora
|
|
52
|
+
// da lista única) era ignorado calado e o pedido seguia sem provedor forçado.
|
|
53
|
+
// Continua seguindo (fail-open, ver `resolveCodeProvider`), mas avisa.
|
|
54
|
+
const avisoDeProvider = pedidoDeProvider && !ehProvedorIa(pedidoDeProvider)
|
|
55
|
+
? `--provider "${pedidoDeProvider}" não existe; use ${PROVEDORES_IA.join(' | ')}. ` +
|
|
56
|
+
'Seguindo sem provedor forçado: o AI Gateway escolhe.'
|
|
57
|
+
: undefined;
|
|
49
58
|
const ultracode = opts.ultracode === true;
|
|
50
59
|
const objectiveMode = opts.goal === true || opts.objective === true || ultracode;
|
|
51
60
|
const effort = ultracode
|
|
@@ -56,7 +65,7 @@ export function resolveSettings(opts) {
|
|
|
56
65
|
files: normalizeFiles(opts.file),
|
|
57
66
|
model: model.model,
|
|
58
67
|
provider,
|
|
59
|
-
modelWarning: model.warning,
|
|
68
|
+
modelWarning: [model.warning, avisoDeProvider].filter(Boolean).join('\n') || undefined,
|
|
60
69
|
// GAP-M1-18: `--ultracode` sobrepoe `--max-turns`/`--max-tokens`
|
|
61
70
|
// explicitos — e um preset "tudo pra cima", nao so um default alternativo.
|
|
62
71
|
maxTurns: ultracode
|
package/dist/commands/config.js
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { config } from '../lib/config.js';
|
|
3
|
+
import { CHAVE_DE_MODELO_DO_PROVEDOR, CHAVE_DO_PROVEDOR, MODELOS_DO_AI_NA_TELA, PROVEDORES_IA, ehModeloDoAi, } from '../lib/ai/provedores.js';
|
|
3
4
|
import { classifyApiUrl, isOverrideEnabled } from '../lib/url-allowlist.js';
|
|
4
5
|
import { log } from '../utils/logger.js';
|
|
5
6
|
import { safeExit } from '../utils/safe-exit.js';
|
|
6
7
|
const SETTABLE_KEYS = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
'groqModel',
|
|
14
|
-
'nvidiaModel',
|
|
15
|
-
// Override de modelo por provider primário (claude/openai/gemini) — eram
|
|
16
|
-
// documentados mas faltavam aqui → `config set` rejeitava (auditoria 2026-06-20).
|
|
17
|
-
'claudeModel',
|
|
18
|
-
'openaiModel',
|
|
19
|
-
'geminiModel',
|
|
8
|
+
// bug_19d312f3: chave de API e override de modelo de cada provedor vêm da
|
|
9
|
+
// lista única (`provedores.ts` — Groq, NVIDIA NIM, OpenAI). O valor da chave
|
|
10
|
+
// pode ser um pool separado por vírgula. `anthropicApiKey`, `geminiApiKey`,
|
|
11
|
+
// `claudeModel` e `geminiModel` saíram (Anthropic e Gemini vetados).
|
|
12
|
+
...PROVEDORES_IA.map((p) => CHAVE_DO_PROVEDOR[p]),
|
|
13
|
+
...PROVEDORES_IA.map((p) => CHAVE_DE_MODELO_DO_PROVEDOR[p]),
|
|
20
14
|
// AI Gateway do Core (`neetru login` + `defaultModel=gateway`) — sem chave
|
|
21
15
|
// própria, só override do modelo enviado no body de `/api/cli/v1/ai/chat`.
|
|
22
16
|
'gatewayModel',
|
|
@@ -36,6 +30,14 @@ export function configSet(key, value, opts = {}) {
|
|
|
36
30
|
log.error(`Chave inválida: "${key}". Chaves disponíveis: ${SETTABLE_KEYS.join(', ')}`);
|
|
37
31
|
safeExit(1);
|
|
38
32
|
}
|
|
33
|
+
// bug_19d312f3: o schema da config não tem mais `enum` em `defaultModel`
|
|
34
|
+
// (para um valor antigo como `claude` não derrubar a leitura) — a conferência
|
|
35
|
+
// do valor gravado fica aqui.
|
|
36
|
+
if (key === 'defaultModel' && !ehModeloDoAi(value)) {
|
|
37
|
+
log.error(`Valor inválido para defaultModel: "${value}". Use: ${MODELOS_DO_AI_NA_TELA}.`);
|
|
38
|
+
safeExit(1);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
39
41
|
// MASTER-HIGH-048 (2.11.0): set manual de `neetruApiKey` está deprecated.
|
|
40
42
|
// O storage canonico agora é `~/.config/neetru-cli/auth.json` (via `neetru login`).
|
|
41
43
|
// O Conf storage será removido em @neetru/cli@2.12.
|