@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
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Saída do processo a partir de um `NeetruCliError` (bug_89d2497e, etapa 3).
3
+ *
4
+ * O cli-kit monta o erro (puro); aqui ele é impresso — JSON no stdout em
5
+ * `--json`, texto humano no stderr fora dele — e o processo sai com o código
6
+ * de saída DO ERRO. Ponto único: `handleApiError`, `resolveStepUp`, a validação
7
+ * de `--env` e o trap global de `index.ts` passam por aqui.
8
+ */
9
+ import { NeetruCliError } from '../cli-kit/errors.js';
10
+ /** Imprime o erro e devolve o código de saída — sem encerrar. */
11
+ export declare function emitCliError(error: NeetruCliError, json?: boolean): number;
12
+ /** Imprime o erro e encerra com o código de saída dele. Nunca retorna. */
13
+ export declare function exitWithCliError(error: NeetruCliError, json?: boolean): never;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Saída do processo a partir de um `NeetruCliError` (bug_89d2497e, etapa 3).
3
+ *
4
+ * O cli-kit monta o erro (puro); aqui ele é impresso — JSON no stdout em
5
+ * `--json`, texto humano no stderr fora dele — e o processo sai com o código
6
+ * de saída DO ERRO. Ponto único: `handleApiError`, `resolveStepUp`, a validação
7
+ * de `--env` e o trap global de `index.ts` passam por aqui.
8
+ */
9
+ import { renderCliError } from '../cli-kit/errors.js';
10
+ import { log } from '../utils/logger.js';
11
+ import { safeExit } from '../utils/safe-exit.js';
12
+ /** Imprime o erro e devolve o código de saída — sem encerrar. */
13
+ export function emitCliError(error, json) {
14
+ if (json)
15
+ console.log(JSON.stringify(error.toJSON()));
16
+ else
17
+ log.error(renderCliError(error));
18
+ return error.exit;
19
+ }
20
+ /** Imprime o erro e encerra com o código de saída dele. Nunca retorna. */
21
+ export function exitWithCliError(error, json) {
22
+ return safeExit(emitCliError(error, json));
23
+ }
24
+ //# sourceMappingURL=cli-error.js.map
@@ -18,6 +18,27 @@ export declare function saveMfaSession(mfaSessionToken: string, expiresAt: strin
18
18
  * apagado junto com o Bearer token.
19
19
  */
20
20
  export declare function clearMfaSession(): Promise<void>;
21
+ /**
22
+ * uid dono de um token de sessão MFA. O Core emite
23
+ * `<uid_base64url>.<issuedAt_hex>.<hmac_sha256_hex>` (`createCliMfaSessionToken`
24
+ * em `src/lib/auth-mfa-session.ts`): o uid é a parte ASSINADA, não segredo — o
25
+ * servidor só aceita a sessão se ele bater com o uid do Bearer. `null` se o
26
+ * formato não for reconhecido.
27
+ */
28
+ export declare function mfaSessionOwnerUid(token: string): string | null;
29
+ /**
30
+ * Descarta a sessão MFA salva quando ela não é do `uid` que acabou de logar.
31
+ *
32
+ * A8 (2026-10-13): `neetru login` com outra conta trocava o Bearer mas deixava
33
+ * o `mfa-session.json` da conta anterior. O Core recusa sessão de outro uid,
34
+ * então TODO comando destrutivo tomava 401 `mfa_required` por até 1h (e, antes
35
+ * do conserto da precedência em `resolveStepUp`, nem `--mfa-token` salvava).
36
+ * Sessão de dono desconhecido (arquivo corrompido, formato não reconhecido)
37
+ * também sai — o servidor recusaria do mesmo jeito. Sessão do MESMO uid fica
38
+ * (re-login na mesma conta não deve exigir TOTP de novo). Retorna `true` se
39
+ * apagou.
40
+ */
41
+ export declare function discardMfaSessionOfOtherUid(uid: string): Promise<boolean>;
21
42
  export interface ConfirmOptions {
22
43
  /** `--yes` / `--force` — pula a confirmação (modo script/CI). */
23
44
  yes?: boolean;
@@ -25,6 +46,17 @@ export interface ConfirmOptions {
25
46
  /** Saída em JSON — não imprime prompt humano. */
26
47
  json?: boolean;
27
48
  }
49
+ /**
50
+ * O que dizer quando a confirmação não pode ser perguntada (P105 /
51
+ * bug_89d2497e). Cita só as flags de confirmação que o comando EM EXECUÇÃO
52
+ * declara: o texto fixo "use --yes (ou --force)" chegava a 8 comandos que
53
+ * declaram `--yes` mas não `--force` (medido na árvore do Commander em
54
+ * 2026-10-18: `servers pgbouncer sync-userlist`, `products memory set`,
55
+ * `vm memory set`, `db move start|abort|decommission`, `promote execute`,
56
+ * `mobile revoke`) — quem seguisse com `--force` tomava "unknown option".
57
+ * Sem nenhuma das duas (ou fora do Commander, como no menu), não cita flag.
58
+ */
59
+ export declare function confirmationAdvice(modo: 'json' | 'nao-interativo'): string;
28
60
  /**
29
61
  * Gate de confirmação pra operações DESTRUTIVAS (suspend, deactivate, delete,
30
62
  * rollback, archive, yank). Se `--yes`/`--force` foi passado, retorna sem
@@ -58,19 +90,21 @@ export interface StepUpOptions extends ConfirmOptions {
58
90
  }
59
91
  /**
60
92
  * Resolve o que uma operação destrutiva TOP-TIER precisa mandar pro servidor:
61
- * - `headers`: `X-Neetru-MFA-Session` (sessão de 1h) OU `X-Neetru-MFA-Token`
62
- * (TOTP fresco) e/ou `X-Neetru-Dry-Run`.
93
+ * - `headers`: `X-Neetru-MFA-Token` (TOTP fresco) OU `X-Neetru-MFA-Session`
94
+ * (sessão de 1h) e/ou `X-Neetru-Dry-Run`.
63
95
  *
64
96
  * Ordem de preferência:
65
97
  * 1. dry-run: sem MFA, só `X-Neetru-Dry-Run: true`.
66
- * 2. Sessão de 1h salva localmente (`~/.config/neetru-cli/mfa-session.json`):
67
- * usa `X-Neetru-MFA-Session` sem pedir TOTP. Silencioso — o operador
68
- * não precisa saber que a sessão está ativa.
69
- * 3. TOTP fresco: do flag `--mfa-token` ou prompt interativo.
70
- * Em terminal não-interativo sem `--mfa-token`, encerra com instrução
71
- * clara — o servidor recusaria de qualquer forma (fail-closed).
98
+ * 2. `--mfa-token` explícito: manda `X-Neetru-MFA-Token` — e também a sessão
99
+ * salva, se houver (o Core tenta a sessão antes e não gasta o TOTP à toa).
100
+ * O código NUNCA é descartado em favor da sessão (A8).
101
+ * 3. Sessão de 1h salva localmente: usa `X-Neetru-MFA-Session` sem pedir
102
+ * TOTP. Silencioso — o operador não precisa saber que a sessão está ativa.
103
+ * 4. Prompt interativo do TOTP. Em `--json`/não-interativo sem código nem
104
+ * sessão, encerra com instrução clara — o servidor recusaria de qualquer
105
+ * forma (fail-closed).
72
106
  *
73
- * feature mfa-session (2026-06-12): o item 2 elimina o re-prompt TOTP a cada
107
+ * feature mfa-session (2026-06-12): o item 3 elimina o re-prompt TOTP a cada
74
108
  * comando destrutivo quando o operador já rodou `neetru auth mfa-session`.
75
109
  */
76
110
  export declare function resolveStepUp(opts: StepUpOptions): Promise<{
@@ -20,6 +20,9 @@ import { requireToken, handleApiError } from './cli-read.js';
20
20
  import { CliApiError } from './api-client.js';
21
21
  import { log } from '../utils/logger.js';
22
22
  import { safeExit } from '../utils/safe-exit.js';
23
+ import { EXIT_CODES, NeetruCliError } from '../cli-kit/errors.js';
24
+ import { adviseCommand, adviseFlag, declaredFlags } from '../cli-kit/command-context.js';
25
+ import { exitWithCliError } from './cli-error.js';
23
26
  export { requireToken, handleApiError };
24
27
  function getMfaSessionFilePath() {
25
28
  return join(homedir(), '.config', 'neetru-cli', 'mfa-session.json');
@@ -89,6 +92,55 @@ export async function clearMfaSession() {
89
92
  // Ignora — arquivo já inexistente ou sem permissão.
90
93
  }
91
94
  }
95
+ /**
96
+ * uid dono de um token de sessão MFA. O Core emite
97
+ * `<uid_base64url>.<issuedAt_hex>.<hmac_sha256_hex>` (`createCliMfaSessionToken`
98
+ * em `src/lib/auth-mfa-session.ts`): o uid é a parte ASSINADA, não segredo — o
99
+ * servidor só aceita a sessão se ele bater com o uid do Bearer. `null` se o
100
+ * formato não for reconhecido.
101
+ */
102
+ export function mfaSessionOwnerUid(token) {
103
+ const parts = token.split('.');
104
+ if (parts.length !== 3 || !parts[0])
105
+ return null;
106
+ try {
107
+ const uid = Buffer.from(parts[0], 'base64url').toString('utf8');
108
+ return /^[\x21-\x7e]+$/.test(uid) ? uid : null;
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ }
114
+ /**
115
+ * Descarta a sessão MFA salva quando ela não é do `uid` que acabou de logar.
116
+ *
117
+ * A8 (2026-10-13): `neetru login` com outra conta trocava o Bearer mas deixava
118
+ * o `mfa-session.json` da conta anterior. O Core recusa sessão de outro uid,
119
+ * então TODO comando destrutivo tomava 401 `mfa_required` por até 1h (e, antes
120
+ * do conserto da precedência em `resolveStepUp`, nem `--mfa-token` salvava).
121
+ * Sessão de dono desconhecido (arquivo corrompido, formato não reconhecido)
122
+ * também sai — o servidor recusaria do mesmo jeito. Sessão do MESMO uid fica
123
+ * (re-login na mesma conta não deve exigir TOTP de novo). Retorna `true` se
124
+ * apagou.
125
+ */
126
+ export async function discardMfaSessionOfOtherUid(uid) {
127
+ const path = getMfaSessionFilePath();
128
+ if (!existsSync(path))
129
+ return false;
130
+ let owner = null;
131
+ try {
132
+ const data = JSON.parse(readFileSync(path, 'utf8'));
133
+ if (typeof data.mfaSessionToken === 'string')
134
+ owner = mfaSessionOwnerUid(data.mfaSessionToken);
135
+ }
136
+ catch {
137
+ // corrompido → dono desconhecido → descarta
138
+ }
139
+ if (owner !== null && owner === uid)
140
+ return false;
141
+ await clearMfaSession();
142
+ return true;
143
+ }
92
144
  /**
93
145
  * Pede uma confirmação interativa `y/N` no terminal. Resolve `true` se o
94
146
  * operador digitou `y`/`yes` (case-insensitive); `false` caso contrário.
@@ -108,6 +160,31 @@ function promptYesNo(question) {
108
160
  });
109
161
  });
110
162
  }
163
+ /** As flags que pulam a confirmação, na ordem em que o conselho as cita. */
164
+ const FLAGS_DE_CONFIRMACAO = ['--yes', '--force'];
165
+ /**
166
+ * O que dizer quando a confirmação não pode ser perguntada (P105 /
167
+ * bug_89d2497e). Cita só as flags de confirmação que o comando EM EXECUÇÃO
168
+ * declara: o texto fixo "use --yes (ou --force)" chegava a 8 comandos que
169
+ * declaram `--yes` mas não `--force` (medido na árvore do Commander em
170
+ * 2026-10-18: `servers pgbouncer sync-userlist`, `products memory set`,
171
+ * `vm memory set`, `db move start|abort|decommission`, `promote execute`,
172
+ * `mobile revoke`) — quem seguisse com `--force` tomava "unknown option".
173
+ * Sem nenhuma das duas (ou fora do Commander, como no menu), não cita flag.
174
+ */
175
+ export function confirmationAdvice(modo) {
176
+ const flags = declaredFlags(FLAGS_DE_CONFIRMACAO);
177
+ if (modo === 'json') {
178
+ return flags.length > 0
179
+ ? `Operação destrutiva em modo --json exige ${flags[0]}.`
180
+ : 'Operação destrutiva em modo --json exige confirmação, e este comando não tem como confirmar sem perguntar.';
181
+ }
182
+ if (flags.length === 0) {
183
+ return 'Terminal não-interativo: esta operação destrutiva só pode ser confirmada num terminal interativo.';
184
+ }
185
+ const lista = flags.length > 1 ? `${flags[0]} (ou ${flags[1]})` : flags[0];
186
+ return `Terminal não-interativo: use ${lista} para confirmar operações destrutivas.`;
187
+ }
111
188
  /**
112
189
  * Gate de confirmação pra operações DESTRUTIVAS (suspend, deactivate, delete,
113
190
  * rollback, archive, yank). Se `--yes`/`--force` foi passado, retorna sem
@@ -129,7 +206,7 @@ export async function confirmDestructive(message, opts) {
129
206
  console.log(JSON.stringify({
130
207
  ok: false,
131
208
  error: 'confirmation_required',
132
- message: 'Operação destrutiva em modo --json exige --yes.',
209
+ message: confirmationAdvice('json'),
133
210
  }));
134
211
  safeExit(1);
135
212
  }
@@ -137,7 +214,7 @@ export async function confirmDestructive(message, opts) {
137
214
  const confirmed = await promptYesNo('Confirmar?');
138
215
  if (!confirmed) {
139
216
  if (!process.stdin.isTTY) {
140
- log.error('Terminal não-interativo: use --yes (ou --force) para confirmar operações destrutivas.');
217
+ log.error(confirmationAdvice('nao-interativo'));
141
218
  }
142
219
  else {
143
220
  log.dim('Operação cancelada.');
@@ -157,21 +234,56 @@ function promptLine(question) {
157
234
  });
158
235
  });
159
236
  }
237
+ /**
238
+ * O step-up que dá pra mandar SEM interação, na ordem de precedência:
239
+ * 1. `--mfa-token` explícito → `X-Neetru-MFA-Token`;
240
+ * 2. sessão de 1h salva (`~/.config/neetru-cli/mfa-session.json`) →
241
+ * `X-Neetru-MFA-Session` (salvo `skipSession`).
242
+ * `{}` se não houver nenhum dos dois (aí o caller prompta ou falha fechado).
243
+ *
244
+ * A8 (2026-10-13): o código explícito VENCE a sessão. Antes `resolveStepUp`
245
+ * olhava a sessão primeiro e devolvia só `X-Neetru-MFA-Session`, descartando o
246
+ * `--mfa-token` em silêncio — enquanto `withStepUpRetry` fazia o contrário.
247
+ * Depois de `neetru login` com outra conta (sessão do uid anterior no disco,
248
+ * que o Core recusa), ~40 comandos destrutivos davam 401 por até 1h MESMO com
249
+ * `--mfa-token`. Quem digita o código está pedindo que ele seja usado. As duas
250
+ * funções agora decidem por aqui — uma regra só.
251
+ */
252
+ function nonInteractiveStepUp(opts) {
253
+ const code = (opts.mfaToken ?? '').trim();
254
+ const savedSession = opts.skipSession ? null : loadMfaSession();
255
+ // Código explícito + sessão salva: manda OS DOIS (revisão do #78). O Core
256
+ // (`requireCliStepUpMfa`) tenta a sessão primeiro e só usa o TOTP se ela não
257
+ // servir — e o TOTP é de USO ÚNICO (`mfa-lockout`). Mandar só o código gastava
258
+ // o código à toa quando a sessão era válida, e o 2º comando com o mesmo código
259
+ // (agente com o código ditado, script) falhava com mfa_invalid. Com os dois, a
260
+ // sessão de OUTRA conta (o caso do A8) cai e o código ainda salva o pedido.
261
+ if (code) {
262
+ return savedSession
263
+ ? { 'X-Neetru-MFA-Token': code, 'X-Neetru-MFA-Session': savedSession }
264
+ : { 'X-Neetru-MFA-Token': code };
265
+ }
266
+ if (savedSession)
267
+ return { 'X-Neetru-MFA-Session': savedSession };
268
+ return {};
269
+ }
160
270
  /**
161
271
  * Resolve o que uma operação destrutiva TOP-TIER precisa mandar pro servidor:
162
- * - `headers`: `X-Neetru-MFA-Session` (sessão de 1h) OU `X-Neetru-MFA-Token`
163
- * (TOTP fresco) e/ou `X-Neetru-Dry-Run`.
272
+ * - `headers`: `X-Neetru-MFA-Token` (TOTP fresco) OU `X-Neetru-MFA-Session`
273
+ * (sessão de 1h) e/ou `X-Neetru-Dry-Run`.
164
274
  *
165
275
  * Ordem de preferência:
166
276
  * 1. dry-run: sem MFA, só `X-Neetru-Dry-Run: true`.
167
- * 2. Sessão de 1h salva localmente (`~/.config/neetru-cli/mfa-session.json`):
168
- * usa `X-Neetru-MFA-Session` sem pedir TOTP. Silencioso — o operador
169
- * não precisa saber que a sessão está ativa.
170
- * 3. TOTP fresco: do flag `--mfa-token` ou prompt interativo.
171
- * Em terminal não-interativo sem `--mfa-token`, encerra com instrução
172
- * clara — o servidor recusaria de qualquer forma (fail-closed).
277
+ * 2. `--mfa-token` explícito: manda `X-Neetru-MFA-Token` — e também a sessão
278
+ * salva, se houver (o Core tenta a sessão antes e não gasta o TOTP à toa).
279
+ * O código NUNCA é descartado em favor da sessão (A8).
280
+ * 3. Sessão de 1h salva localmente: usa `X-Neetru-MFA-Session` sem pedir
281
+ * TOTP. Silencioso — o operador não precisa saber que a sessão está ativa.
282
+ * 4. Prompt interativo do TOTP. Em `--json`/não-interativo sem código nem
283
+ * sessão, encerra com instrução clara — o servidor recusaria de qualquer
284
+ * forma (fail-closed).
173
285
  *
174
- * feature mfa-session (2026-06-12): o item 2 elimina o re-prompt TOTP a cada
286
+ * feature mfa-session (2026-06-12): o item 3 elimina o re-prompt TOTP a cada
175
287
  * comando destrutivo quando o operador já rodou `neetru auth mfa-session`.
176
288
  */
177
289
  export async function resolveStepUp(opts) {
@@ -180,34 +292,35 @@ export async function resolveStepUp(opts) {
180
292
  headers['X-Neetru-Dry-Run'] = 'true';
181
293
  return { headers };
182
294
  }
183
- // ── Prioridade 2: sessão de 1h salva localmente ─────────────────────────
184
- // Verifica ANTES de pedir TOTP — se há sessão válida, usa sem interação.
185
- const savedSession = opts.skipSession ? null : loadMfaSession();
186
- if (savedSession) {
187
- headers['X-Neetru-MFA-Session'] = savedSession;
188
- return { headers };
189
- }
190
- // ── Prioridade 3: TOTP fresco (flag ou prompt) ─────────────────────────
191
- let code = (opts.mfaToken ?? '').trim();
192
- if (!code) {
193
- // bug auditoria 2026-06-30: em --json o prompt interativo de TOTP NÃO pode
194
- // rodar NEM com TTY — ele escreveria a pergunta no stdout (corrompe o JSON)
195
- // e penduraria um consumidor programático. Fail-CLOSED em --json OU sem TTY;
196
- // só promptar em terminal interativo humano (não-json + TTY).
197
- if (opts.json || opts.nonInteractive || !process.stdin.isTTY) {
198
- const msg = 'Operação destrutiva top-tier exige step-up MFA: passe --mfa-token <código TOTP> ' +
199
- '(modo --json/não-interativo) ou rode `neetru auth mfa-session` para obter uma sessão de 1h.';
200
- if (opts.json) {
201
- console.log(JSON.stringify({ ok: false, error: 'mfa_required', message: msg }));
202
- }
203
- else {
204
- log.error(msg);
205
- }
206
- safeExit(1);
207
- return { headers }; // safeExit não retorna em prod; satisfaz o tipo + testes
208
- }
209
- code = await promptLine('Código MFA (TOTP) para confirmar:');
295
+ // ── Prioridades 2 e 3: código explícito, depois sessão de 1h ────────────
296
+ const ready = nonInteractiveStepUp(opts);
297
+ if (Object.keys(ready).length > 0)
298
+ return { headers: ready };
299
+ // ── Prioridade 4: prompt do TOTP ─────────────────────────────────────────
300
+ // bug auditoria 2026-06-30: em --json o prompt interativo de TOTP NÃO pode
301
+ // rodar NEM com TTY — ele escreveria a pergunta no stdout (corrompe o JSON)
302
+ // e penduraria um consumidor programático. Fail-CLOSED em --json OU sem TTY;
303
+ // só promptar em terminal interativo humano (não-json + TTY).
304
+ if (opts.json || opts.nonInteractive || !process.stdin.isTTY) {
305
+ // bug_89d2497e: o conselho só cita `--mfa-token` se o comando em execução
306
+ // declara a flag (mesmo defeito do bug_757b9746, do lado do step-up). O
307
+ // código de saída segue 1 e o JSON segue `{ ok, error:'mfa_required', message }`.
308
+ // P105: o comando alternativo (`neetru auth mfa-session`) também só é
309
+ // citado se existe na árvore do Commander.
310
+ const sessao = (withCommand, withoutCommand) => adviseCommand({ command: 'auth mfa-session', withCommand, withoutCommand });
311
+ const msg = adviseFlag({
312
+ flag: '--mfa-token',
313
+ withFlag: sessao('Operação destrutiva top-tier exige step-up MFA: passe --mfa-token <código TOTP> ' +
314
+ '(modo --json/não-interativo) ou rode `neetru auth mfa-session` para obter uma sessão de 1h.', 'Operação destrutiva top-tier exige step-up MFA: passe --mfa-token <código TOTP> ' +
315
+ '(modo --json/não-interativo).'),
316
+ withoutFlag: sessao('Operação destrutiva top-tier exige step-up MFA: rode `neetru auth mfa-session` ' +
317
+ 'para obter uma sessão de 1h e repita o comando.', 'Operação destrutiva top-tier exige step-up MFA, e esta CLI não tem, neste comando, ' +
318
+ 'como enviá-lo em modo --json/não-interativo.'),
319
+ });
320
+ exitWithCliError(new NeetruCliError(msg, { code: 'mfa_required', exit: EXIT_CODES.client }), opts.json);
321
+ return { headers }; // safeExit não retorna em prod; satisfaz o tipo + testes
210
322
  }
323
+ const code = await promptLine('Código MFA (TOTP) para confirmar:');
211
324
  if (!code) {
212
325
  log.error('Código MFA não fornecido — operação cancelada.');
213
326
  safeExit(1);
@@ -237,13 +350,9 @@ export function isMfaRequiredError(error) {
237
350
  export async function withStepUpRetry(opts, doRequest, onError,
238
351
  /** Chamado antes do prompt TOTP do retry (ex.: parar o spinner). */
239
352
  beforePrompt) {
240
- const first = {};
241
- const code = (opts.mfaToken ?? '').trim();
242
- const savedSession = code ? null : loadMfaSession();
243
- if (code)
244
- first['X-Neetru-MFA-Token'] = code;
245
- else if (savedSession)
246
- first['X-Neetru-MFA-Session'] = savedSession;
353
+ // Mesma precedência do `resolveStepUp` (A8): código explícito, depois sessão.
354
+ const first = nonInteractiveStepUp(opts);
355
+ const code = first['X-Neetru-MFA-Token'];
247
356
  let firstError;
248
357
  try {
249
358
  return await doRequest(first);
@@ -20,17 +20,21 @@ export declare function resolveDbConfig(engine: string): ComposeDbConfig | null;
20
20
  export interface ComposeGeneratorInput {
21
21
  productSlug: string;
22
22
  dbEngine: string;
23
- appPort?: number;
24
- coreUrl?: string;
25
- firebaseProject?: string;
26
- firebaseApiKey?: string;
27
23
  }
28
24
  /**
29
- * Gera o conteúdo do docker-compose.dev.yml baseado na config do produto.
30
- * Template em templates/dev/docker-compose.dev.yml com substituições {{VAR}}.
25
+ * URL de conexão do host (onde o app roda com `npm run dev`) para o banco do
26
+ * compose. `null` quando a engine não usa URL (Firestore usa o emulador).
31
27
  */
32
- export declare function generateComposeDev(input: ComposeGeneratorInput): string;
28
+ export declare function localDatabaseUrl(dbEngine: string): string | null;
33
29
  /**
34
- * Escreve o compose + Dockerfile.agent no diretório do projeto.
30
+ * Gera o conteúdo do `neetru-compose.dev.yml` baseado na config do produto.
31
+ *
32
+ * Só o BANCO sobe em container. O agente ficou de fora de propósito
33
+ * (2026-10-09): o serviço `agent` antigo dependia de um `Dockerfile.agent` que
34
+ * nenhum caminho do `dev up` gravava (o compose falhava no build e nem o banco
35
+ * subia), rodava o binário do agente com `node` (é executável `pkg`, não JS) e
36
+ * ligava um `NEETRU_DEV_MODE` que o agente não implementa — além de montar o
37
+ * `docker.sock` do host num binário baixado. O agente local volta quando a
38
+ * sprint dev-suite entregar o modo dev no próprio agente.
35
39
  */
36
- export declare function writeComposeDev(cwd: string, input: ComposeGeneratorInput): string;
40
+ export declare function generateComposeDev(input: ComposeGeneratorInput): string;
@@ -1,5 +1,3 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
1
  /**
4
2
  * Mapeia engine de banco → config Docker Compose.
5
3
  * Usa imagens oficiais — mesmas engines que rodam em produção na VM.
@@ -57,12 +55,35 @@ export function resolveDbConfig(engine) {
57
55
  return configs['vm-postgres-single'] ?? null; // default
58
56
  }
59
57
  /**
60
- * Gera o conteúdo do docker-compose.dev.yml baseado na config do produto.
61
- * Template em templates/dev/docker-compose.dev.yml com substituições {{VAR}}.
58
+ * URL de conexão do host (onde o app roda com `npm run dev`) para o banco do
59
+ * compose. `null` quando a engine não usa URL (Firestore usa o emulador).
60
+ */
61
+ export function localDatabaseUrl(dbEngine) {
62
+ const db = resolveDbConfig(dbEngine) ?? resolveDbConfig('vm-postgres-single');
63
+ switch (db.internalPort) {
64
+ case 5432:
65
+ return `postgresql://postgres:neetru_dev@localhost:${db.port}/app`;
66
+ case 3306:
67
+ return `mysql://root:neetru_dev@localhost:${db.port}/app`;
68
+ case 27017:
69
+ return `mongodb://localhost:${db.port}/app`;
70
+ default:
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Gera o conteúdo do `neetru-compose.dev.yml` baseado na config do produto.
76
+ *
77
+ * Só o BANCO sobe em container. O agente ficou de fora de propósito
78
+ * (2026-10-09): o serviço `agent` antigo dependia de um `Dockerfile.agent` que
79
+ * nenhum caminho do `dev up` gravava (o compose falhava no build e nem o banco
80
+ * subia), rodava o binário do agente com `node` (é executável `pkg`, não JS) e
81
+ * ligava um `NEETRU_DEV_MODE` que o agente não implementa — além de montar o
82
+ * `docker.sock` do host num binário baixado. O agente local volta quando a
83
+ * sprint dev-suite entregar o modo dev no próprio agente.
62
84
  */
63
85
  export function generateComposeDev(input) {
64
86
  const db = resolveDbConfig(input.dbEngine) ?? resolveDbConfig('vm-postgres-single');
65
- const appPort = input.appPort ?? 3000;
66
87
  // Bloco `environment:` só entra quando há vars — senão vira `environment:`
67
88
  // vazio, que o docker compose rejeita ("environment must be a mapping").
68
89
  // Firestore (env: {}) caía nisso → `dev up` firestore gerava compose inválido.
@@ -76,8 +97,9 @@ export function generateComposeDev(input) {
76
97
  ` timeout: ${db.healthcheck.timeout}`,
77
98
  ` retries: ${db.healthcheck.retries}`,
78
99
  ].join('\n');
79
- return `# Neetru Dev Local — emulador de VM (${db.engine})
100
+ return `# Neetru Dev Local — banco do produto ${input.productSlug} (${db.engine})
80
101
  # Gerado por \`neetru dev up\`. Modifique o neetru.config.json e rode novamente.
102
+ # O app roda fora do container, com \`npm run dev\`.
81
103
 
82
104
  services:
83
105
  database:
@@ -90,63 +112,9 @@ ${dbEnvBlock} ports:
90
112
  healthcheck:
91
113
  ${dbHealthcheck}
92
114
  restart: unless-stopped
93
- networks:
94
- - neetru-dev-net
95
-
96
- agent:
97
- build:
98
- context: .
99
- dockerfile: Dockerfile.agent
100
- container_name: neetru-dev-agent
101
- environment:
102
- - NEETRU_SERVER_ID=dev-local-${input.productSlug}
103
- - NEETRU_CORE_URL=${input.coreUrl ?? 'https://core.neetru.com'}
104
- - NEETRU_FIREBASE_PROJECT=${input.firebaseProject ?? 'neetru'}
105
- - NEETRU_FIREBASE_API_KEY=${input.firebaseApiKey ?? ''}
106
- - NEETRU_DEV_MODE=true
107
- - NEETRU_APP_PORT=${appPort}
108
- - DATABASE_URL=${db.internalPort === 5432 ? 'postgresql://postgres:neetru_dev@database:5432/app' : db.internalPort === 3306 ? 'mysql://root:neetru_dev@database:3306/app' : db.internalPort === 27017 ? 'mongodb://database:27017/app' : ''}
109
- ports:
110
- - "${appPort}:${appPort}"
111
- volumes:
112
- - /var/run/docker.sock:/var/run/docker.sock
113
- - neetru-agent-data:/var/cache/neetru
114
- - .:/app/product:cached
115
- depends_on:
116
- database:
117
- condition: service_healthy
118
- restart: unless-stopped
119
- networks:
120
- - neetru-dev-net
121
115
 
122
116
  volumes:
123
117
  neetru-db-data:
124
- neetru-agent-data:
125
-
126
- networks:
127
- neetru-dev-net:
128
- driver: bridge
129
118
  `;
130
119
  }
131
- /**
132
- * Escreve o compose + Dockerfile.agent no diretório do projeto.
133
- */
134
- export function writeComposeDev(cwd, input) {
135
- const composePath = path.join(cwd, 'neetru-compose.dev.yml');
136
- const dockerfilePath = path.join(cwd, 'Dockerfile.agent');
137
- const entrypointPath = path.join(cwd, 'agent-entrypoint.sh');
138
- const compose = generateComposeDev(input);
139
- fs.writeFileSync(composePath, compose, 'utf8');
140
- // Copia o Dockerfile.agent do template embutido
141
- const templateDir = path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', 'templates', 'dev');
142
- // Em modo pkg/compilado, busca do __dirname ou do diretório do CLI
143
- const altTemplate = path.join(process.cwd(), 'node_modules', '@neetru', 'cli', 'templates', 'dev');
144
- const dockerfileSrc = fs.existsSync(path.join(templateDir, 'Dockerfile.agent'))
145
- ? path.join(templateDir, 'Dockerfile.agent')
146
- : path.join(altTemplate, 'Dockerfile.agent');
147
- if (fs.existsSync(dockerfileSrc)) {
148
- fs.copyFileSync(dockerfileSrc, dockerfilePath);
149
- }
150
- return composePath;
151
- }
152
120
  //# sourceMappingURL=compose-generator.js.map
@@ -89,16 +89,43 @@ export declare const neetruConfigSchema: z.ZodObject<{
89
89
  engine?: "firestore-instance" | "vm-postgres-single" | "vm-mysql-single" | "nosql-vm" | undefined;
90
90
  dbId?: string | undefined;
91
91
  }>>;
92
+ /** Protocolo de docs de produto v1 (`neetru docs init` grava; ver lib/product-docs/config.ts). */
93
+ docs: z.ZodOptional<z.ZodObject<{
94
+ protocolVersion: z.ZodOptional<z.ZodNumber>;
95
+ root: z.ZodOptional<z.ZodString>;
96
+ publish: z.ZodOptional<z.ZodBoolean>;
97
+ publishOnDeploy: z.ZodOptional<z.ZodBoolean>;
98
+ api: z.ZodOptional<z.ZodBoolean>;
99
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
100
+ protocolVersion: z.ZodOptional<z.ZodNumber>;
101
+ root: z.ZodOptional<z.ZodString>;
102
+ publish: z.ZodOptional<z.ZodBoolean>;
103
+ publishOnDeploy: z.ZodOptional<z.ZodBoolean>;
104
+ api: z.ZodOptional<z.ZodBoolean>;
105
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
106
+ protocolVersion: z.ZodOptional<z.ZodNumber>;
107
+ root: z.ZodOptional<z.ZodString>;
108
+ publish: z.ZodOptional<z.ZodBoolean>;
109
+ publishOnDeploy: z.ZodOptional<z.ZodBoolean>;
110
+ api: z.ZodOptional<z.ZodBoolean>;
111
+ }, z.ZodTypeAny, "passthrough">>>;
92
112
  }, "strip", z.ZodTypeAny, {
93
113
  name: string;
94
114
  slug: string;
95
115
  runtime: "nextjs" | "node-api";
96
116
  $schema?: string | undefined;
97
- tenantId?: string | undefined;
98
117
  deploy?: z.objectOutputType<{
99
118
  region: z.ZodOptional<z.ZodString>;
100
119
  serviceName: z.ZodOptional<z.ZodString>;
101
120
  }, z.ZodTypeAny, "passthrough"> | undefined;
121
+ tenantId?: string | undefined;
122
+ docs?: z.objectOutputType<{
123
+ protocolVersion: z.ZodOptional<z.ZodNumber>;
124
+ root: z.ZodOptional<z.ZodString>;
125
+ publish: z.ZodOptional<z.ZodBoolean>;
126
+ publishOnDeploy: z.ZodOptional<z.ZodBoolean>;
127
+ api: z.ZodOptional<z.ZodBoolean>;
128
+ }, z.ZodTypeAny, "passthrough"> | undefined;
102
129
  publish?: {
103
130
  description: string;
104
131
  status: "beta" | "soon" | "live";
@@ -117,11 +144,18 @@ export declare const neetruConfigSchema: z.ZodObject<{
117
144
  slug: string;
118
145
  runtime: "nextjs" | "node-api";
119
146
  $schema?: string | undefined;
120
- tenantId?: string | undefined;
121
147
  deploy?: z.objectInputType<{
122
148
  region: z.ZodOptional<z.ZodString>;
123
149
  serviceName: z.ZodOptional<z.ZodString>;
124
150
  }, z.ZodTypeAny, "passthrough"> | undefined;
151
+ tenantId?: string | undefined;
152
+ docs?: z.objectInputType<{
153
+ protocolVersion: z.ZodOptional<z.ZodNumber>;
154
+ root: z.ZodOptional<z.ZodString>;
155
+ publish: z.ZodOptional<z.ZodBoolean>;
156
+ publishOnDeploy: z.ZodOptional<z.ZodBoolean>;
157
+ api: z.ZodOptional<z.ZodBoolean>;
158
+ }, z.ZodTypeAny, "passthrough"> | undefined;
125
159
  publish?: {
126
160
  description: string;
127
161
  status: "beta" | "soon" | "live";
@@ -42,6 +42,17 @@ export const neetruConfigSchema = z.object({
42
42
  engine: z.enum(['vm-postgres-single', 'vm-mysql-single', 'nosql-vm', 'firestore-instance']).optional(),
43
43
  dbId: z.string().optional(),
44
44
  }).optional(),
45
+ /** Protocolo de docs de produto v1 (`neetru docs init` grava; ver lib/product-docs/config.ts). */
46
+ docs: z
47
+ .object({
48
+ protocolVersion: z.number().int().positive().optional(),
49
+ root: z.string().optional(),
50
+ publish: z.boolean().optional(),
51
+ publishOnDeploy: z.boolean().optional(),
52
+ api: z.boolean().optional(),
53
+ })
54
+ .passthrough()
55
+ .optional(),
45
56
  });
46
57
  export const catalogUpsertPayloadSchema = z.object({
47
58
  slug: slugSchema,
@@ -1,4 +1,4 @@
1
- import type { NeetruEnv } from './env.js';
1
+ import { type NeetruEnv } from './env.js';
2
2
  /** Mapa de IDs de banco por ambiente canônico. */
3
3
  export type DbJsonIds = Partial<Record<NeetruEnv, string>>;
4
4
  /** Uma entrada de banco lógico no registry. */