@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.
Files changed (259) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/README.md +63 -17
  3. package/dist/cli-kit/choices.d.ts +33 -0
  4. package/dist/cli-kit/choices.js +32 -0
  5. package/dist/cli-kit/command-context.d.ts +98 -0
  6. package/dist/cli-kit/command-context.js +108 -0
  7. package/dist/cli-kit/errors.d.ts +79 -0
  8. package/dist/cli-kit/errors.js +94 -0
  9. package/dist/cli-kit/index.d.ts +8 -0
  10. package/dist/cli-kit/index.js +8 -0
  11. package/dist/cli-kit/suggest.d.ts +74 -0
  12. package/dist/cli-kit/suggest.js +207 -0
  13. package/dist/commands/admin/oidc-login.js +1 -1
  14. package/dist/commands/admin/sdk-credential.js +1 -1
  15. package/dist/commands/autocomplete.d.ts +1 -1
  16. package/dist/commands/autocomplete.js +1 -0
  17. package/dist/commands/bug.d.ts +59 -0
  18. package/dist/commands/bug.js +114 -2
  19. package/dist/commands/build.d.ts +24 -1
  20. package/dist/commands/build.js +195 -73
  21. package/dist/commands/chat.d.ts +94 -1
  22. package/dist/commands/chat.js +258 -26
  23. package/dist/commands/customers.js +1 -1
  24. package/dist/commands/db-move.js +3 -3
  25. package/dist/commands/db.js +17 -24
  26. package/dist/commands/deploy.d.ts +136 -2
  27. package/dist/commands/deploy.js +547 -69
  28. package/dist/commands/deployments.js +11 -6
  29. package/dist/commands/dev-context.js +15 -5
  30. package/dist/commands/dev-up.d.ts +7 -5
  31. package/dist/commands/dev-up.js +24 -23
  32. package/dist/commands/docs-produto.d.ts +90 -0
  33. package/dist/commands/docs-produto.js +384 -0
  34. package/dist/commands/doctor.js +36 -1
  35. package/dist/commands/domains.js +85 -4
  36. package/dist/commands/env.d.ts +9 -3
  37. package/dist/commands/env.js +23 -3
  38. package/dist/commands/infra-read.js +2 -2
  39. package/dist/commands/init.d.ts +2 -0
  40. package/dist/commands/init.js +14 -1
  41. package/dist/commands/login.js +35 -8
  42. package/dist/commands/logs.js +12 -4
  43. package/dist/commands/mobile.js +33 -9
  44. package/dist/commands/new.d.ts +31 -1
  45. package/dist/commands/new.js +94 -4
  46. package/dist/commands/open.js +16 -6
  47. package/dist/commands/ops.js +1 -1
  48. package/dist/commands/products-db.d.ts +1 -0
  49. package/dist/commands/products-db.js +41 -8
  50. package/dist/commands/products.d.ts +17 -3
  51. package/dist/commands/products.js +84 -16
  52. package/dist/commands/promote.js +30 -5
  53. package/dist/commands/secrets.d.ts +91 -0
  54. package/dist/commands/secrets.js +471 -0
  55. package/dist/commands/servers.js +6 -4
  56. package/dist/commands/status.js +2 -2
  57. package/dist/commands/support.js +5 -3
  58. package/dist/commands/tenants.js +28 -16
  59. package/dist/commands/upgrade.d.ts +9 -0
  60. package/dist/commands/upgrade.js +50 -1
  61. package/dist/commands/vm-memory.js +1 -0
  62. package/dist/commands/webhooks.d.ts +2 -0
  63. package/dist/commands/webhooks.js +9 -3
  64. package/dist/commands/workspaces.js +42 -11
  65. package/dist/index.js +142 -20
  66. package/dist/lib/api-client.d.ts +22 -1
  67. package/dist/lib/api-client.js +79 -8
  68. package/dist/lib/api-error.d.ts +14 -0
  69. package/dist/lib/api-error.js +107 -46
  70. package/dist/lib/build-targets.d.ts +127 -0
  71. package/dist/lib/build-targets.js +348 -0
  72. package/dist/lib/cli-error.d.ts +13 -0
  73. package/dist/lib/cli-error.js +24 -0
  74. package/dist/lib/cli-write.d.ts +43 -9
  75. package/dist/lib/cli-write.js +154 -45
  76. package/dist/lib/compose-generator.d.ts +13 -9
  77. package/dist/lib/compose-generator.js +28 -60
  78. package/dist/lib/config-schema.d.ts +36 -2
  79. package/dist/lib/config-schema.js +11 -0
  80. package/dist/lib/db-local/db-json.d.ts +1 -1
  81. package/dist/lib/db-local/db-json.js +13 -5
  82. package/dist/lib/db-local/env.d.ts +15 -3
  83. package/dist/lib/db-local/env.js +37 -10
  84. package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
  85. package/dist/lib/db-pipeline/build-deps.js +23 -3
  86. package/dist/lib/db-pipeline/types.d.ts +6 -2
  87. package/dist/lib/db-pipeline/types.js +0 -18
  88. package/dist/lib/deploy-target-contract.d.ts +152 -0
  89. package/dist/lib/deploy-target-contract.js +411 -0
  90. package/dist/lib/env-file-do-build.d.ts +19 -0
  91. package/dist/lib/env-file-do-build.js +44 -0
  92. package/dist/lib/envs.d.ts +108 -0
  93. package/dist/lib/envs.js +127 -0
  94. package/dist/lib/fs-seguro.d.ts +63 -0
  95. package/dist/lib/fs-seguro.js +108 -0
  96. package/dist/lib/mobile-local/mobile-json.js +10 -4
  97. package/dist/lib/pickers.d.ts +44 -7
  98. package/dist/lib/pickers.js +60 -4
  99. package/dist/lib/product-docs/check.d.ts +62 -0
  100. package/dist/lib/product-docs/check.js +489 -0
  101. package/dist/lib/product-docs/config.d.ts +66 -0
  102. package/dist/lib/product-docs/config.js +234 -0
  103. package/dist/lib/product-docs/dates.d.ts +17 -0
  104. package/dist/lib/product-docs/dates.js +41 -0
  105. package/dist/lib/product-docs/frontmatter.d.ts +43 -0
  106. package/dist/lib/product-docs/frontmatter.js +302 -0
  107. package/dist/lib/product-docs/git.d.ts +16 -0
  108. package/dist/lib/product-docs/git.js +92 -0
  109. package/dist/lib/product-docs/markdown.d.ts +27 -0
  110. package/dist/lib/product-docs/markdown.js +98 -0
  111. package/dist/lib/product-docs/ops.d.ts +133 -0
  112. package/dist/lib/product-docs/ops.js +975 -0
  113. package/dist/lib/product-docs/structure.d.ts +61 -0
  114. package/dist/lib/product-docs/structure.js +239 -0
  115. package/dist/lib/product-docs/sync.d.ts +107 -0
  116. package/dist/lib/product-docs/sync.js +322 -0
  117. package/dist/lib/product-docs/taxonomy.d.ts +46 -0
  118. package/dist/lib/product-docs/taxonomy.js +163 -0
  119. package/dist/lib/product-docs/templates.d.ts +53 -0
  120. package/dist/lib/product-docs/templates.js +245 -0
  121. package/dist/lib/public-secret-scan.d.ts +117 -0
  122. package/dist/lib/public-secret-scan.js +771 -0
  123. package/dist/lib/render.d.ts +16 -0
  124. package/dist/lib/render.js +34 -4
  125. package/dist/lib/segredo-produto.d.ts +124 -0
  126. package/dist/lib/segredo-produto.js +336 -0
  127. package/dist/lib/texto-utf8.d.ts +96 -0
  128. package/dist/lib/texto-utf8.js +196 -0
  129. package/package.json +2 -1
  130. package/dist/cli-kit/format.js.map +0 -1
  131. package/dist/cli-kit/glyphs.js.map +0 -1
  132. package/dist/cli-kit/index.js.map +0 -1
  133. package/dist/cli-kit/palette.js.map +0 -1
  134. package/dist/commands/add.js.map +0 -1
  135. package/dist/commands/admin/iam-check.js.map +0 -1
  136. package/dist/commands/admin/oidc-login.js.map +0 -1
  137. package/dist/commands/admin/sdk-credential.js.map +0 -1
  138. package/dist/commands/agent-release.js.map +0 -1
  139. package/dist/commands/agent-write.js.map +0 -1
  140. package/dist/commands/ai-models.js.map +0 -1
  141. package/dist/commands/ai.js.map +0 -1
  142. package/dist/commands/api-catalog.js.map +0 -1
  143. package/dist/commands/archive.js.map +0 -1
  144. package/dist/commands/artifact-registry.js.map +0 -1
  145. package/dist/commands/audit.js.map +0 -1
  146. package/dist/commands/auth.js.map +0 -1
  147. package/dist/commands/autocomplete.js.map +0 -1
  148. package/dist/commands/billing.js.map +0 -1
  149. package/dist/commands/bootstrap.js.map +0 -1
  150. package/dist/commands/bug.js.map +0 -1
  151. package/dist/commands/build.js.map +0 -1
  152. package/dist/commands/chat-menu.js.map +0 -1
  153. package/dist/commands/chat.js.map +0 -1
  154. package/dist/commands/cloud-run.js.map +0 -1
  155. package/dist/commands/code.js.map +0 -1
  156. package/dist/commands/config.js.map +0 -1
  157. package/dist/commands/customers.js.map +0 -1
  158. package/dist/commands/db-move.js.map +0 -1
  159. package/dist/commands/db.js.map +0 -1
  160. package/dist/commands/deploy.js.map +0 -1
  161. package/dist/commands/deployments.js.map +0 -1
  162. package/dist/commands/dev-context.js.map +0 -1
  163. package/dist/commands/dev-up.js.map +0 -1
  164. package/dist/commands/dev.js.map +0 -1
  165. package/dist/commands/docs.js.map +0 -1
  166. package/dist/commands/doctor.js.map +0 -1
  167. package/dist/commands/domains.js.map +0 -1
  168. package/dist/commands/dr.js.map +0 -1
  169. package/dist/commands/env.js.map +0 -1
  170. package/dist/commands/hosting-write.js.map +0 -1
  171. package/dist/commands/infra-read.js.map +0 -1
  172. package/dist/commands/init.js.map +0 -1
  173. package/dist/commands/login.js.map +0 -1
  174. package/dist/commands/logout.js.map +0 -1
  175. package/dist/commands/logs.js.map +0 -1
  176. package/dist/commands/mobile.js.map +0 -1
  177. package/dist/commands/mocks.js.map +0 -1
  178. package/dist/commands/new.js.map +0 -1
  179. package/dist/commands/open.js.map +0 -1
  180. package/dist/commands/ops.js.map +0 -1
  181. package/dist/commands/products-db.js.map +0 -1
  182. package/dist/commands/products.js.map +0 -1
  183. package/dist/commands/promote.js.map +0 -1
  184. package/dist/commands/publish.js.map +0 -1
  185. package/dist/commands/schema.js.map +0 -1
  186. package/dist/commands/servers.js.map +0 -1
  187. package/dist/commands/smoke.js.map +0 -1
  188. package/dist/commands/status.js.map +0 -1
  189. package/dist/commands/support.js.map +0 -1
  190. package/dist/commands/surface-status.js.map +0 -1
  191. package/dist/commands/tenants.js.map +0 -1
  192. package/dist/commands/ui.js.map +0 -1
  193. package/dist/commands/upgrade.js.map +0 -1
  194. package/dist/commands/validate.js.map +0 -1
  195. package/dist/commands/vm-memory.js.map +0 -1
  196. package/dist/commands/vm.js.map +0 -1
  197. package/dist/commands/webhooks.js.map +0 -1
  198. package/dist/commands/whoami.js.map +0 -1
  199. package/dist/commands/workspaces.js.map +0 -1
  200. package/dist/index.js.map +0 -1
  201. package/dist/lib/agent-artifact-url.js.map +0 -1
  202. package/dist/lib/agent-commands.js.map +0 -1
  203. package/dist/lib/ai/auth-discovery.js.map +0 -1
  204. package/dist/lib/ai/context.js.map +0 -1
  205. package/dist/lib/ai/orchestrator.js.map +0 -1
  206. package/dist/lib/api-client.js.map +0 -1
  207. package/dist/lib/api-error.js.map +0 -1
  208. package/dist/lib/auth.js.map +0 -1
  209. package/dist/lib/cli-read.js.map +0 -1
  210. package/dist/lib/cli-write.js.map +0 -1
  211. package/dist/lib/code-agent-budget.js.map +0 -1
  212. package/dist/lib/code-agent-files.js.map +0 -1
  213. package/dist/lib/code-agent-gateway.js.map +0 -1
  214. package/dist/lib/code-agent-git-tools.js.map +0 -1
  215. package/dist/lib/code-agent-protocol.js.map +0 -1
  216. package/dist/lib/code-agent-session.js.map +0 -1
  217. package/dist/lib/code-agent-settings.js.map +0 -1
  218. package/dist/lib/code-agent-skills.js.map +0 -1
  219. package/dist/lib/code-agent-subagents.js.map +0 -1
  220. package/dist/lib/code-agent-tools.js.map +0 -1
  221. package/dist/lib/code-agent-tui.js.map +0 -1
  222. package/dist/lib/code-agent-types.js.map +0 -1
  223. package/dist/lib/code-agent-ui.js.map +0 -1
  224. package/dist/lib/code-agent-web-tools.js.map +0 -1
  225. package/dist/lib/code-agent-write-tools.js.map +0 -1
  226. package/dist/lib/compose-generator.js.map +0 -1
  227. package/dist/lib/config-schema.js.map +0 -1
  228. package/dist/lib/config.js.map +0 -1
  229. package/dist/lib/db-local/db-json.js.map +0 -1
  230. package/dist/lib/db-local/env.js.map +0 -1
  231. package/dist/lib/db-local/fingerprint.js.map +0 -1
  232. package/dist/lib/db-local/index.js.map +0 -1
  233. package/dist/lib/db-pipeline/build-deps.js.map +0 -1
  234. package/dist/lib/db-pipeline/errors.js.map +0 -1
  235. package/dist/lib/db-pipeline/index.js.map +0 -1
  236. package/dist/lib/db-pipeline/pipeline.js.map +0 -1
  237. package/dist/lib/db-pipeline/rehearse.js.map +0 -1
  238. package/dist/lib/db-pipeline/types.js.map +0 -1
  239. package/dist/lib/dev-local-context.js.map +0 -1
  240. package/dist/lib/iam-preflight.js.map +0 -1
  241. package/dist/lib/mobile-local/index.js.map +0 -1
  242. package/dist/lib/mobile-local/mobile-json.js.map +0 -1
  243. package/dist/lib/pickers.js.map +0 -1
  244. package/dist/lib/product-config.js.map +0 -1
  245. package/dist/lib/render.js.map +0 -1
  246. package/dist/lib/schema-cache.js.map +0 -1
  247. package/dist/lib/sse.js.map +0 -1
  248. package/dist/lib/telemetry.js.map +0 -1
  249. package/dist/lib/token-resolver.js.map +0 -1
  250. package/dist/lib/url-allowlist.js.map +0 -1
  251. package/dist/lib/version-check.js.map +0 -1
  252. package/dist/utils/logger.js.map +0 -1
  253. package/dist/utils/logo.js.map +0 -1
  254. package/dist/utils/safe-exit.js.map +0 -1
  255. package/dist/utils/spinner-registry.js.map +0 -1
  256. package/dist/version.js.map +0 -1
  257. package/templates/dev/Dockerfile.agent +0 -39
  258. package/templates/dev/agent-entrypoint.sh +0 -41
  259. 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 (default) | staging | prod', '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 (default: self)')
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` = ambiente dev-local (banco + agente) via Docker Compose.
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 ambiente completo (`dev up`)')
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 ambiente dev-local (banco + agente) via Docker Compose')
326
- .option('--core-port <port>', 'porta do app SaaS local (default 3000)')
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 `npm install` (assume node_modules pronto — útil quando `next dev` ativo no Windows)')
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 (default: self/Neetru)')
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
- .option('--env <csv>', 'ambientes separados por vírgula (default: dev,prod)')
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 emulador (dev up) em vez do Core; default .neetru/dev/logs.ndjson')
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 (sessão → --mfa-token
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('Documentação + registry de docs publicados em gs://neetru-docs');
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 —
@@ -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
- constructor(status: number, code: string, message: string, correlationId?: string);
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>;
@@ -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
- constructor(status, code, message, correlationId) {
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
- assertTrustedApiHost(baseUrl);
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
- assertTrustedApiHost(baseUrl);
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 = {
@@ -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;