@neetru/cli 2.29.0 → 2.32.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 +141 -0
- package/README.md +63 -17
- package/dist/cli-kit/choices.d.ts +33 -0
- package/dist/cli-kit/choices.js +32 -0
- package/dist/cli-kit/command-context.d.ts +98 -0
- package/dist/cli-kit/command-context.js +108 -0
- package/dist/cli-kit/errors.d.ts +79 -0
- package/dist/cli-kit/errors.js +94 -0
- package/dist/cli-kit/index.d.ts +8 -0
- package/dist/cli-kit/index.js +8 -0
- package/dist/cli-kit/suggest.d.ts +74 -0
- package/dist/cli-kit/suggest.js +207 -0
- package/dist/commands/admin/oidc-login.js +1 -1
- package/dist/commands/admin/sdk-credential.js +1 -1
- package/dist/commands/autocomplete.d.ts +1 -1
- package/dist/commands/autocomplete.js +1 -0
- package/dist/commands/bug.d.ts +59 -0
- package/dist/commands/bug.js +114 -2
- package/dist/commands/build.d.ts +24 -1
- package/dist/commands/build.js +195 -73
- package/dist/commands/chat.d.ts +94 -1
- package/dist/commands/chat.js +258 -26
- package/dist/commands/customers.js +1 -1
- package/dist/commands/db-move.js +3 -3
- package/dist/commands/db.js +17 -24
- package/dist/commands/deploy.d.ts +136 -2
- package/dist/commands/deploy.js +547 -69
- package/dist/commands/deployments.js +11 -6
- package/dist/commands/dev-context.js +15 -5
- package/dist/commands/dev-up.d.ts +7 -5
- package/dist/commands/dev-up.js +24 -23
- package/dist/commands/docs-produto.d.ts +90 -0
- package/dist/commands/docs-produto.js +384 -0
- package/dist/commands/doctor.js +36 -1
- package/dist/commands/domains.js +85 -4
- package/dist/commands/env.d.ts +9 -3
- package/dist/commands/env.js +23 -3
- package/dist/commands/infra-read.js +2 -2
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +14 -1
- package/dist/commands/login.js +35 -8
- package/dist/commands/logs.js +12 -4
- package/dist/commands/mobile.js +33 -9
- package/dist/commands/new.d.ts +31 -1
- package/dist/commands/new.js +94 -4
- package/dist/commands/open.js +16 -6
- package/dist/commands/ops.js +1 -1
- package/dist/commands/products-db.d.ts +1 -0
- package/dist/commands/products-db.js +41 -8
- package/dist/commands/products.d.ts +17 -3
- package/dist/commands/products.js +84 -16
- package/dist/commands/promote.js +30 -5
- package/dist/commands/secrets.d.ts +91 -0
- package/dist/commands/secrets.js +471 -0
- package/dist/commands/servers.js +6 -4
- package/dist/commands/status.js +2 -2
- package/dist/commands/support.js +5 -3
- package/dist/commands/tenants.js +28 -16
- package/dist/commands/upgrade.d.ts +9 -0
- package/dist/commands/upgrade.js +50 -1
- package/dist/commands/vm-memory.js +1 -0
- package/dist/commands/webhooks.d.ts +2 -0
- package/dist/commands/webhooks.js +9 -3
- package/dist/commands/workspaces.js +42 -11
- package/dist/index.js +142 -20
- package/dist/lib/api-client.d.ts +22 -1
- package/dist/lib/api-client.js +79 -8
- package/dist/lib/api-error.d.ts +14 -0
- package/dist/lib/api-error.js +107 -46
- package/dist/lib/build-targets.d.ts +127 -0
- package/dist/lib/build-targets.js +348 -0
- package/dist/lib/cli-error.d.ts +13 -0
- package/dist/lib/cli-error.js +24 -0
- package/dist/lib/cli-write.d.ts +43 -9
- package/dist/lib/cli-write.js +154 -45
- package/dist/lib/compose-generator.d.ts +13 -9
- package/dist/lib/compose-generator.js +28 -60
- package/dist/lib/config-schema.d.ts +36 -2
- package/dist/lib/config-schema.js +11 -0
- package/dist/lib/db-local/db-json.d.ts +1 -1
- package/dist/lib/db-local/db-json.js +13 -5
- package/dist/lib/db-local/env.d.ts +15 -3
- package/dist/lib/db-local/env.js +37 -10
- package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
- package/dist/lib/db-pipeline/build-deps.js +23 -3
- package/dist/lib/db-pipeline/types.d.ts +6 -2
- package/dist/lib/db-pipeline/types.js +0 -18
- package/dist/lib/deploy-target-contract.d.ts +152 -0
- package/dist/lib/deploy-target-contract.js +411 -0
- package/dist/lib/env-file-do-build.d.ts +19 -0
- package/dist/lib/env-file-do-build.js +44 -0
- package/dist/lib/envs.d.ts +108 -0
- package/dist/lib/envs.js +127 -0
- package/dist/lib/fs-seguro.d.ts +63 -0
- package/dist/lib/fs-seguro.js +108 -0
- package/dist/lib/mobile-local/mobile-json.js +10 -4
- package/dist/lib/pickers.d.ts +44 -7
- package/dist/lib/pickers.js +60 -4
- package/dist/lib/product-docs/check.d.ts +62 -0
- package/dist/lib/product-docs/check.js +489 -0
- package/dist/lib/product-docs/config.d.ts +66 -0
- package/dist/lib/product-docs/config.js +234 -0
- package/dist/lib/product-docs/dates.d.ts +17 -0
- package/dist/lib/product-docs/dates.js +41 -0
- package/dist/lib/product-docs/frontmatter.d.ts +43 -0
- package/dist/lib/product-docs/frontmatter.js +302 -0
- package/dist/lib/product-docs/git.d.ts +16 -0
- package/dist/lib/product-docs/git.js +92 -0
- package/dist/lib/product-docs/markdown.d.ts +27 -0
- package/dist/lib/product-docs/markdown.js +98 -0
- package/dist/lib/product-docs/ops.d.ts +133 -0
- package/dist/lib/product-docs/ops.js +975 -0
- package/dist/lib/product-docs/structure.d.ts +61 -0
- package/dist/lib/product-docs/structure.js +239 -0
- package/dist/lib/product-docs/sync.d.ts +107 -0
- package/dist/lib/product-docs/sync.js +322 -0
- package/dist/lib/product-docs/taxonomy.d.ts +46 -0
- package/dist/lib/product-docs/taxonomy.js +163 -0
- package/dist/lib/product-docs/templates.d.ts +53 -0
- package/dist/lib/product-docs/templates.js +245 -0
- package/dist/lib/public-secret-scan.d.ts +117 -0
- package/dist/lib/public-secret-scan.js +771 -0
- package/dist/lib/render.d.ts +16 -0
- package/dist/lib/render.js +34 -4
- package/dist/lib/segredo-produto.d.ts +124 -0
- package/dist/lib/segredo-produto.js +336 -0
- package/dist/lib/texto-utf8.d.ts +96 -0
- package/dist/lib/texto-utf8.js +196 -0
- package/package.json +2 -1
- package/dist/cli-kit/format.js.map +0 -1
- package/dist/cli-kit/glyphs.js.map +0 -1
- package/dist/cli-kit/index.js.map +0 -1
- package/dist/cli-kit/palette.js.map +0 -1
- package/dist/commands/add.js.map +0 -1
- package/dist/commands/admin/iam-check.js.map +0 -1
- package/dist/commands/admin/oidc-login.js.map +0 -1
- package/dist/commands/admin/sdk-credential.js.map +0 -1
- package/dist/commands/agent-release.js.map +0 -1
- package/dist/commands/agent-write.js.map +0 -1
- package/dist/commands/ai-models.js.map +0 -1
- package/dist/commands/ai.js.map +0 -1
- package/dist/commands/api-catalog.js.map +0 -1
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/artifact-registry.js.map +0 -1
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/autocomplete.js.map +0 -1
- package/dist/commands/billing.js.map +0 -1
- package/dist/commands/bootstrap.js.map +0 -1
- package/dist/commands/bug.js.map +0 -1
- package/dist/commands/build.js.map +0 -1
- package/dist/commands/chat-menu.js.map +0 -1
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/cloud-run.js.map +0 -1
- package/dist/commands/code.js.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/customers.js.map +0 -1
- package/dist/commands/db-move.js.map +0 -1
- package/dist/commands/db.js.map +0 -1
- package/dist/commands/deploy.js.map +0 -1
- package/dist/commands/deployments.js.map +0 -1
- package/dist/commands/dev-context.js.map +0 -1
- package/dist/commands/dev-up.js.map +0 -1
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/docs.js.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/domains.js.map +0 -1
- package/dist/commands/dr.js.map +0 -1
- package/dist/commands/env.js.map +0 -1
- package/dist/commands/hosting-write.js.map +0 -1
- package/dist/commands/infra-read.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/mobile.js.map +0 -1
- package/dist/commands/mocks.js.map +0 -1
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/open.js.map +0 -1
- package/dist/commands/ops.js.map +0 -1
- package/dist/commands/products-db.js.map +0 -1
- package/dist/commands/products.js.map +0 -1
- package/dist/commands/promote.js.map +0 -1
- package/dist/commands/publish.js.map +0 -1
- package/dist/commands/schema.js.map +0 -1
- package/dist/commands/servers.js.map +0 -1
- package/dist/commands/smoke.js.map +0 -1
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/support.js.map +0 -1
- package/dist/commands/surface-status.js.map +0 -1
- package/dist/commands/tenants.js.map +0 -1
- package/dist/commands/ui.js.map +0 -1
- package/dist/commands/upgrade.js.map +0 -1
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/vm-memory.js.map +0 -1
- package/dist/commands/vm.js.map +0 -1
- package/dist/commands/webhooks.js.map +0 -1
- package/dist/commands/whoami.js.map +0 -1
- package/dist/commands/workspaces.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/agent-artifact-url.js.map +0 -1
- package/dist/lib/agent-commands.js.map +0 -1
- package/dist/lib/ai/auth-discovery.js.map +0 -1
- package/dist/lib/ai/context.js.map +0 -1
- package/dist/lib/ai/orchestrator.js.map +0 -1
- package/dist/lib/api-client.js.map +0 -1
- package/dist/lib/api-error.js.map +0 -1
- package/dist/lib/auth.js.map +0 -1
- package/dist/lib/cli-read.js.map +0 -1
- package/dist/lib/cli-write.js.map +0 -1
- package/dist/lib/code-agent-budget.js.map +0 -1
- package/dist/lib/code-agent-files.js.map +0 -1
- package/dist/lib/code-agent-gateway.js.map +0 -1
- package/dist/lib/code-agent-git-tools.js.map +0 -1
- package/dist/lib/code-agent-protocol.js.map +0 -1
- package/dist/lib/code-agent-session.js.map +0 -1
- package/dist/lib/code-agent-settings.js.map +0 -1
- package/dist/lib/code-agent-skills.js.map +0 -1
- package/dist/lib/code-agent-subagents.js.map +0 -1
- package/dist/lib/code-agent-tools.js.map +0 -1
- package/dist/lib/code-agent-tui.js.map +0 -1
- package/dist/lib/code-agent-types.js.map +0 -1
- package/dist/lib/code-agent-ui.js.map +0 -1
- package/dist/lib/code-agent-web-tools.js.map +0 -1
- package/dist/lib/code-agent-write-tools.js.map +0 -1
- package/dist/lib/compose-generator.js.map +0 -1
- package/dist/lib/config-schema.js.map +0 -1
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/db-local/db-json.js.map +0 -1
- package/dist/lib/db-local/env.js.map +0 -1
- package/dist/lib/db-local/fingerprint.js.map +0 -1
- package/dist/lib/db-local/index.js.map +0 -1
- package/dist/lib/db-pipeline/build-deps.js.map +0 -1
- package/dist/lib/db-pipeline/errors.js.map +0 -1
- package/dist/lib/db-pipeline/index.js.map +0 -1
- package/dist/lib/db-pipeline/pipeline.js.map +0 -1
- package/dist/lib/db-pipeline/rehearse.js.map +0 -1
- package/dist/lib/db-pipeline/types.js.map +0 -1
- package/dist/lib/dev-local-context.js.map +0 -1
- package/dist/lib/iam-preflight.js.map +0 -1
- package/dist/lib/mobile-local/index.js.map +0 -1
- package/dist/lib/mobile-local/mobile-json.js.map +0 -1
- package/dist/lib/pickers.js.map +0 -1
- package/dist/lib/product-config.js.map +0 -1
- package/dist/lib/render.js.map +0 -1
- package/dist/lib/schema-cache.js.map +0 -1
- package/dist/lib/sse.js.map +0 -1
- package/dist/lib/telemetry.js.map +0 -1
- package/dist/lib/token-resolver.js.map +0 -1
- package/dist/lib/url-allowlist.js.map +0 -1
- package/dist/lib/version-check.js.map +0 -1
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/logo.js.map +0 -1
- package/dist/utils/safe-exit.js.map +0 -1
- package/dist/utils/spinner-registry.js.map +0 -1
- package/dist/version.js.map +0 -1
- package/templates/dev/Dockerfile.agent +0 -39
- package/templates/dev/agent-entrypoint.sh +0 -41
- package/templates/dev/docker-compose.dev.yml +0 -63
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ import { CLI_VERSION } from './version.js';
|
|
|
5
5
|
import { installTelemetryFlushHook, markCommandStart, markCommandEnd, } from './lib/telemetry.js';
|
|
6
6
|
import { safeExit, isSafeExitHalt } from './utils/safe-exit.js';
|
|
7
7
|
import { CliApiError } from './lib/api-client.js';
|
|
8
|
+
import { NeetruCliError } from './cli-kit/errors.js';
|
|
9
|
+
import { installCommandContext, getCurrentCommand } from './cli-kit/command-context.js';
|
|
10
|
+
import { emitCliError } from './lib/cli-error.js';
|
|
8
11
|
// UV_HANDLE_CLOSING no Windows (bug_818cf6d): o double-destroy de stdin (handler
|
|
9
12
|
// 'exit' redundante) foi removido em 2026-07-15 (ddd479f), MAS isso não bastou
|
|
10
13
|
// — a causa dominante é o `uv_async_t` do undici (fetch) ainda vivo quando o
|
|
@@ -29,6 +32,20 @@ installTelemetryFlushHook();
|
|
|
29
32
|
function handleFatalSignal(err) {
|
|
30
33
|
if (isSafeExitHalt(err))
|
|
31
34
|
return; // saída já agendada dentro do safeExit
|
|
35
|
+
// bug_89d2497e: erro tipado lançado e não tratado sai com o código DELE (e,
|
|
36
|
+
// se o comando em execução recebeu --json, como JSON) — não com o 1 genérico.
|
|
37
|
+
if (err instanceof NeetruCliError) {
|
|
38
|
+
let code = err.exit;
|
|
39
|
+
try {
|
|
40
|
+
code = emitCliError(err, Boolean(getCurrentCommand()?.opts().json));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
/* logger indisponível — segue direto pro exit deferido */
|
|
44
|
+
}
|
|
45
|
+
process.exitCode = code;
|
|
46
|
+
setTimeout(() => process.exit(code), 300);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
32
49
|
try {
|
|
33
50
|
log.error(err instanceof Error ? (err.stack ?? err.message) : String(err));
|
|
34
51
|
// LABS #6 (2026-09-28): se o erro carrega o x-correlation-id do Core, mostra
|
|
@@ -89,6 +106,10 @@ program.hook('preAction', (_thisCmd, actionCmd) => {
|
|
|
89
106
|
process.once('exit', (code) => {
|
|
90
107
|
markCommandEnd(typeof code === 'number' ? code : 0);
|
|
91
108
|
});
|
|
109
|
+
// bug_89d2497e: guarda o comando que o Commander vai executar, para que um
|
|
110
|
+
// conselho de erro (ex.: "passe --mfa-token") só cite flag que ESSE comando
|
|
111
|
+
// declara. Ver `src/cli-kit/command-context.ts`.
|
|
112
|
+
installCommandContext(program);
|
|
92
113
|
// ── neetru ai ─────────────────────────────────────────────────────────
|
|
93
114
|
const aiCommand = program
|
|
94
115
|
.command('ai')
|
|
@@ -157,6 +178,7 @@ program
|
|
|
157
178
|
.option('--force', 'sobrescreve arquivos existentes em conflito (cuidado!)')
|
|
158
179
|
// -y padronizado com bootstrap (`-y, --yes`) — muscle-memory (auditoria UX 2026-09).
|
|
159
180
|
.option('-y, --yes', 'modo non-interactive (assume defaults, sai com erro se conflito sem flag)')
|
|
181
|
+
.option('--sem-docs', 'não cria o docs/ do produto (o padrão é rodar `neetru docs init`)')
|
|
160
182
|
.action(async (name, opts) => {
|
|
161
183
|
const { runInit } = await import('./commands/init.js');
|
|
162
184
|
await runInit({
|
|
@@ -166,6 +188,7 @@ program
|
|
|
166
188
|
path: opts.path,
|
|
167
189
|
force: opts.force,
|
|
168
190
|
yes: opts.yes,
|
|
191
|
+
semDocs: !!opts.semDocs,
|
|
169
192
|
});
|
|
170
193
|
});
|
|
171
194
|
// ── neetru new (macro end-to-end) ─────────────────────────────────────
|
|
@@ -174,13 +197,14 @@ program
|
|
|
174
197
|
.command('new <name>')
|
|
175
198
|
.description('Criar produto, workspace, scaffold e abrir painel')
|
|
176
199
|
.option('--product-slug <slug>', 'slug do produto (default: derivado de <name>)')
|
|
177
|
-
.option('--env <env>', 'dev (
|
|
200
|
+
.option('--env <env>', 'dev-local (padrão) | staging | prod')
|
|
178
201
|
.option('--tier <tier>', 'standard (default) | dev | enterprise', 'standard')
|
|
179
|
-
.option('--customer <customerId>', 'customerId dono do workspace (
|
|
202
|
+
.option('--customer <customerId>', 'customerId dono do workspace (OBRIGATÓRIO se o produto não for Neetru-owned)')
|
|
180
203
|
.option('--skip-init', 'pula o scaffold local')
|
|
181
204
|
.option('--open', 'abre o browser após criar o workspace (opt-in)')
|
|
182
205
|
.option('--skip-open', '(obsoleto — browser não abre por default; use --open para abrir)')
|
|
183
206
|
.option('--skip-db-init', 'pula o 5º passo de inicialização do banco de dados')
|
|
207
|
+
.option('--sem-docs', 'não cria o docs/ do produto no scaffold')
|
|
184
208
|
.option('--json', 'saída em JSON (machine-readable)')
|
|
185
209
|
.action(async (name, opts) => {
|
|
186
210
|
const { runNewMacro } = await import('./commands/new.js');
|
|
@@ -193,6 +217,7 @@ program
|
|
|
193
217
|
open: !!opts.open,
|
|
194
218
|
skipOpen: !!opts.skipOpen,
|
|
195
219
|
skipDbInit: !!opts.skipDbInit,
|
|
220
|
+
semDocs: !!opts.semDocs,
|
|
196
221
|
json: !!opts.json,
|
|
197
222
|
});
|
|
198
223
|
});
|
|
@@ -309,11 +334,11 @@ program
|
|
|
309
334
|
// ── neetru dev ────────────────────────────────────────────────────────
|
|
310
335
|
// `neetru dev` (sem subcomando) = banco local Docker + watch de schema (M1
|
|
311
336
|
// GAP-M1-4) — comportamento legado preservado (action + options intactos).
|
|
312
|
-
// `neetru dev up` =
|
|
337
|
+
// `neetru dev up` = banco do produto em container via Docker Compose.
|
|
313
338
|
// Subcomando aditivo: a action de `dev` só dispara quando nenhum subcomando casa.
|
|
314
339
|
const devCmd = program
|
|
315
340
|
.command('dev')
|
|
316
|
-
.description('Ambiente de dev local — banco Docker (default) ou
|
|
341
|
+
.description('Ambiente de dev local — banco Docker + watch de schema (default) ou só o banco (`dev up`)')
|
|
317
342
|
.option('--db <name>', 'banco alvo do .neetru/db.json (default: único registrado)')
|
|
318
343
|
.option('--schema <path>', 'caminho do arquivo de schema (default: db/schema.ts)')
|
|
319
344
|
.action(async (opts) => {
|
|
@@ -322,8 +347,8 @@ const devCmd = program
|
|
|
322
347
|
});
|
|
323
348
|
devCmd
|
|
324
349
|
.command('up')
|
|
325
|
-
.description('Sobe o
|
|
326
|
-
.option('--core-port <port>', 'porta do app
|
|
350
|
+
.description('Sobe o banco do produto em container (Docker Compose) e mostra a conexão')
|
|
351
|
+
.option('--core-port <port>', 'porta do app local mostrada nas instruções (default 3000)')
|
|
327
352
|
.action(async (opts) => {
|
|
328
353
|
const { runDevUp } = await import('./commands/dev-up.js');
|
|
329
354
|
await runDevUp({
|
|
@@ -399,11 +424,15 @@ program
|
|
|
399
424
|
.command('build')
|
|
400
425
|
.description('Construir tarball do produto pronto para deploy')
|
|
401
426
|
.option('--product <slug>', 'override do slug (default: lê neetru.config.json)')
|
|
427
|
+
// bug_678ddc98: monorepo — aponta pro diretório do app sem precisar de `cd`.
|
|
428
|
+
// `--cwd` é alias porque é o nome que a maioria das ferramentas usa.
|
|
429
|
+
.option('--app <dir>', 'diretório do app a construir (monorepo: ex. apps/web). O artefato continua saindo no diretório atual')
|
|
430
|
+
.option('--cwd <dir>', 'alias de --app')
|
|
402
431
|
.option('--stack <stack>', 'node | docker | php-apache | static (default: detectado)')
|
|
403
432
|
.option('--output <dir>', 'diretório de saída', '.neetru-build')
|
|
404
433
|
.option('--version <version>', 'versão (default: detectada de package.json ou timestamp)')
|
|
405
434
|
.option('--no-cache', 'invalida caches do build (recria lockfile, --no-cache no docker)')
|
|
406
|
-
.option('--no-install', 'pula
|
|
435
|
+
.option('--no-install', 'pula o install (assume node_modules pronto — útil quando `next dev` ativo no Windows)')
|
|
407
436
|
.option('--env-file <path>', 'arquivo .env com vars de ambiente a injetar no build (ex: NEXT_PUBLIC_API_URL)')
|
|
408
437
|
.option('--target-env <env>', 'dev | workspace | prod — força NEXT_PUBLIC_NEETRU_ENV no bundle (default: prod; ' +
|
|
409
438
|
'neutraliza .env.local vencendo .env.production — incidente 2026-07-22)', 'prod')
|
|
@@ -412,6 +441,9 @@ program
|
|
|
412
441
|
const { runBuild } = await import('./commands/build.js');
|
|
413
442
|
await runBuild({
|
|
414
443
|
product: opts.product,
|
|
444
|
+
// bug_678ddc98: `--app` vence `--cwd` quando os dois vêm juntos (o
|
|
445
|
+
// canônico ganha do alias, em vez de escolher em silêncio).
|
|
446
|
+
app: opts.app ?? opts.cwd,
|
|
415
447
|
stack: opts.stack,
|
|
416
448
|
output: opts.output,
|
|
417
449
|
version: opts.version,
|
|
@@ -449,6 +481,9 @@ program
|
|
|
449
481
|
.option('--non-interactive', 'falha em vez de perguntar (modo CI)')
|
|
450
482
|
.option('--force', 'ignora o gate de schema e prossegue mesmo com divergência de fingerprint')
|
|
451
483
|
.option('--skip-health-probe', 'NÃO faz o GET de saúde a https://<domain>/api/health?deep=1 pós-deploy (default: sempre faz; use só em serviço sem HTTP)')
|
|
484
|
+
.option('--allow-public-secret <motivo>', 'EMERGÊNCIA: deploya mesmo com segredo (nrt_/npk_/chave privada/env pública sensível) no pacote público. ' +
|
|
485
|
+
'Motivo obrigatório; fica registrado no deploy. Rotacione a chave depois.')
|
|
486
|
+
.option('--sem-docs', 'pula a checagem dos docs (só aviso) e a publicação automática (docs.publishOnDeploy)')
|
|
452
487
|
.option('--json', 'saída em JSON estruturado (implica --non-interactive)')
|
|
453
488
|
.action(async (opts) => {
|
|
454
489
|
const { runDeploy } = await import('./commands/deploy.js');
|
|
@@ -474,6 +509,8 @@ program
|
|
|
474
509
|
force: !!opts.force,
|
|
475
510
|
skipHealthProbe: !!opts.skipHealthProbe,
|
|
476
511
|
mfaToken: opts.mfaToken,
|
|
512
|
+
allowPublicSecret: opts.allowPublicSecret,
|
|
513
|
+
semDocs: !!opts.semDocs,
|
|
477
514
|
json: !!opts.json,
|
|
478
515
|
});
|
|
479
516
|
});
|
|
@@ -593,7 +630,7 @@ tenantsCmd
|
|
|
593
630
|
.option('--slug <slug>', 'slug do tenant')
|
|
594
631
|
.option('--customer <customerId>', 'id do customer')
|
|
595
632
|
.option('--product <productId>', 'id do produto (interativo se ausente)')
|
|
596
|
-
.option('--env <env>', 'dev | staging | prod (default: dev)')
|
|
633
|
+
.option('--env <env>', 'dev-local | staging | prod (default: dev-local)')
|
|
597
634
|
.option('--domain <domain>', 'domínio primário')
|
|
598
635
|
.option('--json', 'saída em JSON')
|
|
599
636
|
.action(async (opts) => {
|
|
@@ -615,7 +652,7 @@ tenantsCmd
|
|
|
615
652
|
.option('--slug <slug>', 'slug do tenant')
|
|
616
653
|
.option('--customer <customerId>', 'id do customer')
|
|
617
654
|
.option('--product <productId>', 'id do produto (interativo se ausente)')
|
|
618
|
-
.option('--env <env>', 'dev | staging | prod (default: dev)')
|
|
655
|
+
.option('--env <env>', 'dev-local | staging | prod (default: dev-local)')
|
|
619
656
|
.option('--domain <domain>', 'domínio primário')
|
|
620
657
|
.option('--json', 'saída em JSON')
|
|
621
658
|
.action(async (id, opts) => {
|
|
@@ -892,8 +929,8 @@ workspacesCmd
|
|
|
892
929
|
.command('create')
|
|
893
930
|
.description('Criar workspace (retorna OAuth secret one-time)')
|
|
894
931
|
.option('--product <productId>', 'id do produto (interativo se ausente)')
|
|
895
|
-
.option('--customer <customerId>', 'id do customer (
|
|
896
|
-
.option('--env <env>', 'dev | staging | prod (default: dev)')
|
|
932
|
+
.option('--customer <customerId>', 'id do customer (omitir só em produto Neetru-owned)')
|
|
933
|
+
.option('--env <env>', 'dev-local | staging | prod (default: dev-local)')
|
|
897
934
|
.option('--tier <tier>', 'dev | standard | enterprise (default: standard)')
|
|
898
935
|
.option('--name <name>', 'label do workspace')
|
|
899
936
|
.option('--bind-config', 'grava tenantId retornado no neetru.config.json do cwd')
|
|
@@ -930,7 +967,7 @@ workspacesCmd
|
|
|
930
967
|
.command('list')
|
|
931
968
|
.description('Listar workspaces')
|
|
932
969
|
.option('--product <productId>', 'filtra por productId')
|
|
933
|
-
.option('--env <env>', 'dev | staging | prod')
|
|
970
|
+
.option('--env <env>', 'dev-local | staging | prod')
|
|
934
971
|
.option('--status <status>', 'active | paused | provisioning | …')
|
|
935
972
|
.option('--json', 'saída em JSON')
|
|
936
973
|
.action(async (opts) => {
|
|
@@ -967,7 +1004,7 @@ deploymentsCmd
|
|
|
967
1004
|
.option('--product <productId>', 'id do produto (interativo se ausente)')
|
|
968
1005
|
.option('--tenant <tenantId>', 'id do tenant alvo (interativo se ausente)')
|
|
969
1006
|
.option('--version <version>', 'versao a deployar')
|
|
970
|
-
.option('--env <env>', 'dev | staging | prod (default: dev)')
|
|
1007
|
+
.option('--env <env>', 'dev-local | staging | prod (default: dev-local)')
|
|
971
1008
|
.option('--server <serverId>', 'id do server alvo (interativo se ausente)')
|
|
972
1009
|
.option('--json', 'saída em JSON')
|
|
973
1010
|
.action(async (opts) => {
|
|
@@ -1200,7 +1237,10 @@ productsCmd
|
|
|
1200
1237
|
.option('--description <text>', 'descrição (opcional)')
|
|
1201
1238
|
.option('--owner-team <team>', 'time dono (default: neetru-eng)')
|
|
1202
1239
|
.option('--plan-template <tier>', 'free | starter | pro (default: free)')
|
|
1203
|
-
|
|
1240
|
+
// bug bug_db50b21f84e8400f9c3f6286b69c353b: o Core descarta `environments` no
|
|
1241
|
+
// POST. A flag segue aceita pra não quebrar script existente, mas o texto
|
|
1242
|
+
// precisa dizer a verdade — antes prometia uma escolha que não acontecia.
|
|
1243
|
+
.option('--env <csv>', 'IGNORADO hoje: o Core provisiona staging e prod sozinho')
|
|
1204
1244
|
.option('--yes', 'pula prompts (exige --name; resto usa defaults)')
|
|
1205
1245
|
.option('--json', 'saída em JSON (machine-readable)')
|
|
1206
1246
|
.action(async (opts) => {
|
|
@@ -1380,10 +1420,15 @@ adminDatabaseCmd
|
|
|
1380
1420
|
.command('delete [id]')
|
|
1381
1421
|
.description('[Staff] Arquivar banco por soft-delete')
|
|
1382
1422
|
.option('--force', 'Requerido para DBs em status=active (passa force=true ao Core)')
|
|
1423
|
+
// bug_757b9746: a rota exige step-up MFA e o hint de erro já mandava usar
|
|
1424
|
+
// `--mfa-token`, mas o subcomando não declarava a flag. Sem sessão de 1h
|
|
1425
|
+
// ativa (`neetru auth mfa-session`), em --json/não-interativo este era o
|
|
1426
|
+
// único caminho possível — e ele não existia.
|
|
1427
|
+
.option('--mfa-token <codigo>', 'Código TOTP para o step-up MFA (dispensado com `neetru auth mfa-session`)')
|
|
1383
1428
|
.option('--json')
|
|
1384
1429
|
.action(async (id, opts) => {
|
|
1385
1430
|
const { runDbDelete } = await import('./commands/products-db.js');
|
|
1386
|
-
await runDbDelete(id, { json: !!opts.json, force: !!opts.force });
|
|
1431
|
+
await runDbDelete(id, { json: !!opts.json, force: !!opts.force, mfaToken: opts.mfaToken });
|
|
1387
1432
|
});
|
|
1388
1433
|
// ── neetru admin sdk-credential ────────────────────────────────────────────
|
|
1389
1434
|
// Gerencia credenciais OIDC de MÁQUINA (M2M / client_credentials) por produto —
|
|
@@ -1604,10 +1649,14 @@ productsDbCmd
|
|
|
1604
1649
|
.command('set-status [id] [status]')
|
|
1605
1650
|
.description('[RENOMEADO — use: neetru admin database set-status]')
|
|
1606
1651
|
.option('--reason <text>')
|
|
1652
|
+
// A7 (2026-10-13): a rota exige step-up MFA com o banco active/degraded; o
|
|
1653
|
+
// alias depreciado não declarava a flag e, em --json/CI sem sessão de 1h,
|
|
1654
|
+
// não havia como mandar o código (o `admin database set-status` já tinha).
|
|
1655
|
+
.option('--mfa-token <code>', 'código TOTP para step-up MFA (exigido se o banco está active/degraded)')
|
|
1607
1656
|
.option('--json')
|
|
1608
1657
|
.action(async (id, status, opts) => {
|
|
1609
1658
|
const { runDbSetStatus } = await import('./commands/products-db.js');
|
|
1610
|
-
await runDbSetStatus(id, status, { reason: opts.reason, json: !!opts.json });
|
|
1659
|
+
await runDbSetStatus(id, status, { reason: opts.reason, mfaToken: opts.mfaToken, json: !!opts.json });
|
|
1611
1660
|
});
|
|
1612
1661
|
productsDbCmd
|
|
1613
1662
|
.command('retry [id]')
|
|
@@ -1629,10 +1678,14 @@ productsDbCmd
|
|
|
1629
1678
|
productsDbCmd
|
|
1630
1679
|
.command('delete [id]')
|
|
1631
1680
|
.description('[RENOMEADO — use: neetru admin database delete]')
|
|
1681
|
+
// bug_757b9746: o alias roteia pro MESMO runDbDelete, que agora faz step-up
|
|
1682
|
+
// MFA. Sem a flag aqui o alias reproduziria o bug inteiro (401 com instrução
|
|
1683
|
+
// de usar uma flag inexistente).
|
|
1684
|
+
.option('--mfa-token <codigo>', 'Código TOTP para o step-up MFA (dispensado com `neetru auth mfa-session`)')
|
|
1632
1685
|
.option('--json')
|
|
1633
1686
|
.action(async (id, opts) => {
|
|
1634
1687
|
const { runDbDelete } = await import('./commands/products-db.js');
|
|
1635
|
-
await runDbDelete(id, { json: !!opts.json });
|
|
1688
|
+
await runDbDelete(id, { json: !!opts.json, mfaToken: opts.mfaToken });
|
|
1636
1689
|
});
|
|
1637
1690
|
// ── neetru logs ───────────────────────────────────────────────────────
|
|
1638
1691
|
program
|
|
@@ -1648,7 +1701,7 @@ program
|
|
|
1648
1701
|
.option('--channel <channel>', 'filtrar por channel (stdout | stderr | app)')
|
|
1649
1702
|
.option('--correlation-id <id>', 'filtrar por correlationId (UUID)')
|
|
1650
1703
|
.option('--json', 'saída em JSON (NDJSON linha a linha, útil para pipes)')
|
|
1651
|
-
.option('--local [path]', 'ler NDJSON local do
|
|
1704
|
+
.option('--local [path]', 'ler NDJSON local (coletor do Core local) em vez do Core; default .neetru/dev/logs.ndjson')
|
|
1652
1705
|
.action(async (opts) => {
|
|
1653
1706
|
const { runLogs } = await import('./commands/logs.js');
|
|
1654
1707
|
await runLogs({
|
|
@@ -2043,7 +2096,7 @@ dbMigrationsCmd
|
|
|
2043
2096
|
.description('Confirmar migração destrutiva (POST /api/cli/v1/db/migrations/[id]/confirm — exige MFA)')
|
|
2044
2097
|
// Era `requiredOption('--mfa')` — o commander rejeitava ANTES de rodar, matando
|
|
2045
2098
|
// o caminho de sessão MFA de 1h (`neetru auth mfa-session`) que o resolveStepUp
|
|
2046
|
-
// promete honrar. Agora `option`: resolveStepUp faz o fail-closed (
|
|
2099
|
+
// promete honrar. Agora `option`: resolveStepUp faz o fail-closed (--mfa-token → sessão
|
|
2047
2100
|
// → erro acionável). Flag canônica `--mfa-token`; `--mfa` fica como alias oculto. (auditoria UX 2026-09)
|
|
2048
2101
|
.option('--mfa-token <token>', 'código TOTP step-up MFA (ou use `neetru auth mfa-session`)')
|
|
2049
2102
|
.addOption(new Option('--mfa <token>').hideHelp())
|
|
@@ -2769,7 +2822,7 @@ drCmd
|
|
|
2769
2822
|
// entrada pra quem quer LER docs, não publicar.
|
|
2770
2823
|
const docsCmd = program
|
|
2771
2824
|
.command('docs')
|
|
2772
|
-
.description('
|
|
2825
|
+
.description('Docs do produto (init/new/check/sync) + registry de docs do Core em gs://neetru-docs');
|
|
2773
2826
|
docsCmd
|
|
2774
2827
|
.command('open [topic]')
|
|
2775
2828
|
.description('Abrir documentação no browser (ex: sdk, cli, db, webhooks, auth)')
|
|
@@ -2837,6 +2890,63 @@ docsCmd
|
|
|
2837
2890
|
json: !!opts.json,
|
|
2838
2891
|
});
|
|
2839
2892
|
});
|
|
2893
|
+
// ── neetru docs init|new|check|sync — docs do PRODUTO ─────────────────
|
|
2894
|
+
// PROTOCOLO_DOCS_PRODUTOS_SAAS v1 (2026-10-03). Os subcomandos acima são o
|
|
2895
|
+
// control plane dos docs do Core; estes cuidam do `docs/` do repo do produto.
|
|
2896
|
+
docsCmd
|
|
2897
|
+
.command('init')
|
|
2898
|
+
.description('Criar o mínimo do primeiro dia do docs/ do produto (idempotente; nunca sobrescreve)')
|
|
2899
|
+
.option('--com-api', 'inclui docs/api/API_V1.md (produto expõe /api/v1) e grava docs.api=true')
|
|
2900
|
+
.option('--migrar', 'repo que já tem docs: só acrescenta frontmatter inferido + relatório (não move nem renomeia nada)')
|
|
2901
|
+
.option('--dry-run', 'mostra o que faria, sem escrever')
|
|
2902
|
+
.option('--json', 'saída em JSON')
|
|
2903
|
+
.action(async (opts) => {
|
|
2904
|
+
const { runDocsInit } = await import('./commands/docs-produto.js');
|
|
2905
|
+
await runDocsInit({ comApi: !!opts.comApi, migrar: !!opts.migrar, dryRun: !!opts.dryRun, json: !!opts.json });
|
|
2906
|
+
});
|
|
2907
|
+
docsCmd
|
|
2908
|
+
.command('new <tipo> <titulo>')
|
|
2909
|
+
.description('Criar doc a partir do modelo (guia|referencia|arquitetura|adr|runbook|norma|plano|relatorio|incidente|ata|rfc|spec|politica|ameacas)')
|
|
2910
|
+
.option('--categoria <categoria>', 'categoria da taxonomia (ex.: "Integrações")')
|
|
2911
|
+
.option('--subcategoria <subcategoria>', 'subcategoria da categoria (ex.: marketplaces)')
|
|
2912
|
+
.option('--audience <audience>', 'dev-produto | staff | publico (padrão: o da pasta)')
|
|
2913
|
+
.option('--descricao <frase>', 'description (uma frase de 40 a 200 caracteres)')
|
|
2914
|
+
.option('--data <AAAA-MM-DD>', 'data do registro (relatório, incidente, ata); padrão hoje')
|
|
2915
|
+
.option('--dry-run', 'mostra o que faria, sem escrever')
|
|
2916
|
+
.option('--json', 'saída em JSON')
|
|
2917
|
+
.action(async (tipo, titulo, opts) => {
|
|
2918
|
+
const { runDocsNew } = await import('./commands/docs-produto.js');
|
|
2919
|
+
await runDocsNew(tipo, titulo, {
|
|
2920
|
+
categoria: opts.categoria,
|
|
2921
|
+
subcategoria: opts.subcategoria,
|
|
2922
|
+
audience: opts.audience,
|
|
2923
|
+
descricao: opts.descricao,
|
|
2924
|
+
data: opts.data,
|
|
2925
|
+
dryRun: !!opts.dryRun,
|
|
2926
|
+
json: !!opts.json,
|
|
2927
|
+
});
|
|
2928
|
+
});
|
|
2929
|
+
docsCmd
|
|
2930
|
+
.command('check')
|
|
2931
|
+
.description('Validar os docs do produto (offline; mesmos códigos do Core + PR001–PR004)')
|
|
2932
|
+
.option('--changed', 'só os arquivos alterados contra a branch principal (catraca)')
|
|
2933
|
+
.option('--strict', 'aviso também falha')
|
|
2934
|
+
.option('--offline', 'não consulta a taxonomia do Core (usa a embutida)')
|
|
2935
|
+
.option('--json', 'saída em JSON')
|
|
2936
|
+
.action(async (opts) => {
|
|
2937
|
+
const { runDocsCheck } = await import('./commands/docs-produto.js');
|
|
2938
|
+
await runDocsCheck({ changed: !!opts.changed, strict: !!opts.strict, offline: !!opts.offline, json: !!opts.json });
|
|
2939
|
+
});
|
|
2940
|
+
docsCmd
|
|
2941
|
+
.command('sync')
|
|
2942
|
+
.description('Publicar os docs do produto no Core (só os sem erro; nada é apagado)')
|
|
2943
|
+
.option('--dry-run', 'mostra o que enviaria, sem enviar')
|
|
2944
|
+
.option('--offline', 'checa com a taxonomia embutida (o envio continua precisando do Core)')
|
|
2945
|
+
.option('--json', 'saída em JSON')
|
|
2946
|
+
.action(async (opts) => {
|
|
2947
|
+
const { runDocsSync } = await import('./commands/docs-produto.js');
|
|
2948
|
+
await runDocsSync({ dryRun: !!opts.dryRun, offline: !!opts.offline, json: !!opts.json });
|
|
2949
|
+
});
|
|
2840
2950
|
// ── neetru webhooks ──────────────────────────────────────────────────
|
|
2841
2951
|
// Produtos SaaS registram endpoints de webhook pra receber eventos do Core.
|
|
2842
2952
|
const webhooksCmd = program
|
|
@@ -2875,6 +2985,7 @@ webhooksCmd
|
|
|
2875
2985
|
.requiredOption('--product-id <id>', 'ID do produto')
|
|
2876
2986
|
.option('--yes', 'pula a confirmação interativa (necessário em --json/CI)')
|
|
2877
2987
|
.option('--force', 'alias de --yes')
|
|
2988
|
+
.option('--mfa-token <code>', 'código TOTP para step-up MFA (ou use `neetru auth mfa-session`)')
|
|
2878
2989
|
.option('--json', 'saída em JSON')
|
|
2879
2990
|
.action(async (id, opts) => {
|
|
2880
2991
|
const { runWebhooksUnregister } = await import('./commands/webhooks.js');
|
|
@@ -2883,6 +2994,7 @@ webhooksCmd
|
|
|
2883
2994
|
productId: opts.productId,
|
|
2884
2995
|
yes: !!opts.yes,
|
|
2885
2996
|
force: !!opts.force,
|
|
2997
|
+
mfaToken: opts.mfaToken,
|
|
2886
2998
|
json: !!opts.json,
|
|
2887
2999
|
});
|
|
2888
3000
|
});
|
|
@@ -2951,6 +3063,16 @@ domainsCmd
|
|
|
2951
3063
|
const { runDomainsRetry } = await import('./commands/domains.js');
|
|
2952
3064
|
await runDomainsRetry({ product: opts.product, sub: opts.sub, json: !!opts.json });
|
|
2953
3065
|
});
|
|
3066
|
+
// ── neetru secrets ──────────────────────────────────────────────────
|
|
3067
|
+
// Segredos PRÓPRIOS do produto (bug_8b85353b, Parte 3): registrar/trocar
|
|
3068
|
+
// (`set`, inclusive `--generate`), listar só nomes (`list`) e arquivar
|
|
3069
|
+
// (`archive`). O valor vai direto pro Secret Manager e nunca entra pelo argv.
|
|
3070
|
+
// Contrato: core#632 (GET/POST/DELETE /api/cli/v1/products/{slug}/secrets).
|
|
3071
|
+
// @top-level: secrets
|
|
3072
|
+
{
|
|
3073
|
+
const { registerSecretsCommand } = await import('./commands/secrets.js');
|
|
3074
|
+
registerSecretsCommand(program);
|
|
3075
|
+
}
|
|
2954
3076
|
// ── neetru mobile ───────────────────────────────────────────────────
|
|
2955
3077
|
// Devs/IAs registram/listam/revogam Apps Mobile (iOS/Android) do produto
|
|
2956
3078
|
// atual (lido de neetru.config.json no cwd). Dev-facing, self-service —
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -11,8 +11,15 @@ export declare class CliApiError extends Error {
|
|
|
11
11
|
* de debug de campo (usuário informa o `ref:` ao suporte).
|
|
12
12
|
*/
|
|
13
13
|
readonly correlationId?: string;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Segundos pedidos pelo `Retry-After` (429/503), quando o Core manda. Usado
|
|
16
|
+
* por quem faz vários envios em sequência (ex.: `neetru docs sync`).
|
|
17
|
+
*/
|
|
18
|
+
readonly retryAfterSeconds?: number;
|
|
19
|
+
constructor(status: number, code: string, message: string, correlationId?: string, retryAfterSeconds?: number);
|
|
15
20
|
}
|
|
21
|
+
/** `Retry-After` em segundos (aceita número ou data HTTP). undefined se ausente/ilegível. */
|
|
22
|
+
export declare function parseRetryAfter(value: string | null | undefined, now?: number): number | undefined;
|
|
16
23
|
export declare class CliNetworkError extends Error {
|
|
17
24
|
readonly cause?: unknown;
|
|
18
25
|
constructor(message: string, cause?: unknown);
|
|
@@ -43,6 +50,20 @@ interface StreamOptions {
|
|
|
43
50
|
export declare function assertTrustedApiHost(rawUrl: string): void;
|
|
44
51
|
/** @deprecated Nome legado — agora é fail-closed (throw), não só warning. Use `assertTrustedApiHost`. */
|
|
45
52
|
export declare const maybeWarnUntrustedHost: typeof assertTrustedApiHost;
|
|
53
|
+
/**
|
|
54
|
+
* URL absoluta de um path da API do Core: MESMA base (`neetruApiUrl`) e MESMA
|
|
55
|
+
* guarda fail-closed de `apiRequest`/`apiStream`. Lança `CliNetworkError` se a
|
|
56
|
+
* base estiver fora da allowlist — antes de qualquer Bearer sair da máquina.
|
|
57
|
+
*
|
|
58
|
+
* Todo `fetch` que leve o token da CLI e não passe por `apiRequest`/`apiStream`
|
|
59
|
+
* (ex.: o SSE de `neetru logs -f`, que precisa ler o corpo aos poucos) monta a
|
|
60
|
+
* URL por aqui. C7 (2026-10-13): o stream do `logs -f` lia uma chave
|
|
61
|
+
* `apiBase` (que nem existe no schema do config) e fazia `fetch` direto, sem a
|
|
62
|
+
* guarda — com `"apiBase":"https://coletor.malicioso"` no `config.json`, o
|
|
63
|
+
* segundo request ia pra lá com `Bearer nrt_…`, reabrindo o ataque que o
|
|
64
|
+
* LABS #4 fechou aqui; e o `neetruApiUrl` de staging/local era ignorado.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveTrustedApiUrl(path: string): string;
|
|
46
67
|
/** @deprecated Sem estado agora (fail-closed não tem flag one-shot). No-op mantido p/ compat de teste. */
|
|
47
68
|
export declare function _resetUntrustedHostWarningForTests(): void;
|
|
48
69
|
export declare function apiRequest<T>(path: string, opts?: RequestOptions): Promise<T>;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -12,14 +12,32 @@ export class CliApiError extends Error {
|
|
|
12
12
|
* de debug de campo (usuário informa o `ref:` ao suporte).
|
|
13
13
|
*/
|
|
14
14
|
correlationId;
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Segundos pedidos pelo `Retry-After` (429/503), quando o Core manda. Usado
|
|
17
|
+
* por quem faz vários envios em sequência (ex.: `neetru docs sync`).
|
|
18
|
+
*/
|
|
19
|
+
retryAfterSeconds;
|
|
20
|
+
constructor(status, code, message, correlationId, retryAfterSeconds) {
|
|
16
21
|
super(message);
|
|
17
22
|
this.name = 'CliApiError';
|
|
18
23
|
this.status = status;
|
|
19
24
|
this.code = code;
|
|
20
25
|
this.correlationId = correlationId;
|
|
26
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
21
27
|
}
|
|
22
28
|
}
|
|
29
|
+
/** `Retry-After` em segundos (aceita número ou data HTTP). undefined se ausente/ilegível. */
|
|
30
|
+
export function parseRetryAfter(value, now = Date.now()) {
|
|
31
|
+
if (!value)
|
|
32
|
+
return undefined;
|
|
33
|
+
const v = value.trim();
|
|
34
|
+
if (/^\d+$/.test(v))
|
|
35
|
+
return Number.parseInt(v, 10);
|
|
36
|
+
const at = Date.parse(v);
|
|
37
|
+
if (Number.isNaN(at))
|
|
38
|
+
return undefined;
|
|
39
|
+
return Math.max(0, Math.ceil((at - now) / 1000));
|
|
40
|
+
}
|
|
23
41
|
export class CliNetworkError extends Error {
|
|
24
42
|
cause;
|
|
25
43
|
constructor(message, cause) {
|
|
@@ -51,16 +69,32 @@ export function assertTrustedApiHost(rawUrl) {
|
|
|
51
69
|
}
|
|
52
70
|
/** @deprecated Nome legado — agora é fail-closed (throw), não só warning. Use `assertTrustedApiHost`. */
|
|
53
71
|
export const maybeWarnUntrustedHost = assertTrustedApiHost;
|
|
72
|
+
/**
|
|
73
|
+
* URL absoluta de um path da API do Core: MESMA base (`neetruApiUrl`) e MESMA
|
|
74
|
+
* guarda fail-closed de `apiRequest`/`apiStream`. Lança `CliNetworkError` se a
|
|
75
|
+
* base estiver fora da allowlist — antes de qualquer Bearer sair da máquina.
|
|
76
|
+
*
|
|
77
|
+
* Todo `fetch` que leve o token da CLI e não passe por `apiRequest`/`apiStream`
|
|
78
|
+
* (ex.: o SSE de `neetru logs -f`, que precisa ler o corpo aos poucos) monta a
|
|
79
|
+
* URL por aqui. C7 (2026-10-13): o stream do `logs -f` lia uma chave
|
|
80
|
+
* `apiBase` (que nem existe no schema do config) e fazia `fetch` direto, sem a
|
|
81
|
+
* guarda — com `"apiBase":"https://coletor.malicioso"` no `config.json`, o
|
|
82
|
+
* segundo request ia pra lá com `Bearer nrt_…`, reabrindo o ataque que o
|
|
83
|
+
* LABS #4 fechou aqui; e o `neetruApiUrl` de staging/local era ignorado.
|
|
84
|
+
*/
|
|
85
|
+
export function resolveTrustedApiUrl(path) {
|
|
86
|
+
const baseUrl = buildBaseUrl();
|
|
87
|
+
assertTrustedApiHost(baseUrl);
|
|
88
|
+
return `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
89
|
+
}
|
|
54
90
|
/** @deprecated Sem estado agora (fail-closed não tem flag one-shot). No-op mantido p/ compat de teste. */
|
|
55
91
|
export function _resetUntrustedHostWarningForTests() {
|
|
56
92
|
/* no-op */
|
|
57
93
|
}
|
|
58
94
|
export async function apiRequest(path, opts = {}) {
|
|
59
|
-
const baseUrl = buildBaseUrl();
|
|
60
|
-
const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
61
95
|
// LABS #4 (2026-09-28): fail-closed — bloqueia host fora da allowlist ANTES
|
|
62
96
|
// de anexar o Bearer (era só warning que mandava o token assim mesmo).
|
|
63
|
-
|
|
97
|
+
const url = resolveTrustedApiUrl(path);
|
|
64
98
|
// MASTER-HIGH-048 (2.11.0): prefere auth.json (canonico) sobre Conf (legacy).
|
|
65
99
|
const token = opts.token ?? getActiveTokenSync();
|
|
66
100
|
const headers = {
|
|
@@ -167,9 +201,48 @@ export async function apiRequest(path, opts = {}) {
|
|
|
167
201
|
else if (typeof parsed === 'string' && parsed.trim() !== '') {
|
|
168
202
|
message = parsed;
|
|
169
203
|
}
|
|
204
|
+
else if (errBody && typeof errBody === 'object') {
|
|
205
|
+
// bug_a3c4d7a8 / achado 2026-10-12: o Core tem TRÊS formatos de erro e
|
|
206
|
+
// este ramo cobre o terceiro, que antes caía no chão inteiro.
|
|
207
|
+
//
|
|
208
|
+
// 1. `{ error: { code, message } }` — canônico (`cliJsonError`)
|
|
209
|
+
// 2. `{ error: "x", error_description }` — RFC 6749 (OAuth)
|
|
210
|
+
// 3. `{ ok: false, code, reason, ... }` — rotas que respondem direto
|
|
211
|
+
//
|
|
212
|
+
// O formato 3 NÃO tem a chave `error`, então o `if (errBody?.error)` acima
|
|
213
|
+
// era pulado e tudo virava `Erro HTTP <status>`. Medido no campo: o
|
|
214
|
+
// `GET /servers/{id}/logs?source=startup` devolve 410 com `code`, um
|
|
215
|
+
// `reason` de dez linhas e quatro `alternatives` acionáveis — e o operador
|
|
216
|
+
// lia apenas "Requisição rejeitada (410): Erro HTTP 410". Pior: o
|
|
217
|
+
// `servers get` RECOMENDA esse comando justamente quando o agente morreu,
|
|
218
|
+
// então a mensagem inútil chega no pior momento possível.
|
|
219
|
+
const cru = errBody;
|
|
220
|
+
if (typeof cru.code === 'string' && cru.code)
|
|
221
|
+
code = cru.code;
|
|
222
|
+
// `reason` primeiro: é o campo que as rotas do formato 3 usam pro texto
|
|
223
|
+
// longo. `message`/`detail`/`hint` cobrem as variações que já existem.
|
|
224
|
+
for (const chave of ['reason', 'message', 'detail', 'hint']) {
|
|
225
|
+
const v = cru[chave];
|
|
226
|
+
if (typeof v === 'string' && v.trim() !== '') {
|
|
227
|
+
message = v;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// O que fazer EM VEZ disso é a parte que salva o operador; sem anexar
|
|
232
|
+
// aqui, ela morre no servidor. Só strings — nada de despejar objeto na
|
|
233
|
+
// cara de quem lê.
|
|
234
|
+
const alt = cru.alternatives;
|
|
235
|
+
if (alt && typeof alt === 'object' && !Array.isArray(alt)) {
|
|
236
|
+
const linhas = Object.entries(alt)
|
|
237
|
+
.filter(([, v]) => typeof v === 'string' && v.trim() !== '')
|
|
238
|
+
.map(([k, v]) => ` ${k}: ${v}`);
|
|
239
|
+
if (linhas.length > 0)
|
|
240
|
+
message = `${message}\n\nAlternativas:\n${linhas.join('\n')}`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
170
243
|
// LABS #6 (2026-09-28): captura o x-correlation-id do Core p/ o rodapé do erro.
|
|
171
244
|
const correlationId = response.headers.get('x-correlation-id') ?? undefined;
|
|
172
|
-
throw new CliApiError(response.status, code, message, correlationId);
|
|
245
|
+
throw new CliApiError(response.status, code, message, correlationId, parseRetryAfter(response.headers.get('retry-after')));
|
|
173
246
|
}
|
|
174
247
|
return parsed;
|
|
175
248
|
}
|
|
@@ -182,10 +255,8 @@ export async function apiRequest(path, opts = {}) {
|
|
|
182
255
|
* `opts.signal` (Ctrl-C). Erros HTTP antes do stream abrir viram `CliApiError`.
|
|
183
256
|
*/
|
|
184
257
|
export async function* apiStream(path, opts = {}) {
|
|
185
|
-
const baseUrl = buildBaseUrl();
|
|
186
|
-
const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
187
258
|
// LABS #4 (2026-09-28): fail-closed também no stream.
|
|
188
|
-
|
|
259
|
+
const url = resolveTrustedApiUrl(path);
|
|
189
260
|
// MASTER-HIGH-048 (2.11.0): mesmo helper aplica ao stream NDJSON.
|
|
190
261
|
const token = opts.token ?? getActiveTokenSync();
|
|
191
262
|
const headers = {
|
package/dist/lib/api-error.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NeetruCliError } from '../cli-kit/errors.js';
|
|
1
2
|
/** Exit codes canônicos, exportados pra teste/documentação. */
|
|
2
3
|
export declare const API_ERROR_EXIT: {
|
|
3
4
|
readonly client: 1;
|
|
@@ -5,4 +6,17 @@ export declare const API_ERROR_EXIT: {
|
|
|
5
6
|
readonly forbidden: 3;
|
|
6
7
|
readonly server: 4;
|
|
7
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* O que fazer diante de `401 mfa_required`. bug_757b9746 nasceu aqui: o texto
|
|
11
|
+
* mandava passar `--mfa-token` a qualquer comando, inclusive aos que não
|
|
12
|
+
* declaravam a flag. Agora a flag só é citada se o comando em execução a
|
|
13
|
+
* declara; senão, a alternativa que existe sempre — a sessão de 1h.
|
|
14
|
+
*/
|
|
15
|
+
export declare function mfaRequiredAdvice(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Traduz qualquer erro para o `NeetruCliError` canônico — sem imprimir nem
|
|
18
|
+
* encerrar. Exportado para teste e para quem precise agregar erros de API
|
|
19
|
+
* como filhos de uma falha parcial.
|
|
20
|
+
*/
|
|
21
|
+
export declare function cliErrorFromApiError(error: unknown): NeetruCliError;
|
|
8
22
|
export declare function handleApiError(error: unknown, json?: boolean): never;
|