@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,108 @@
1
+ let atual = null;
2
+ let raiz = null;
3
+ /** Registra o comando que o Commander vai executar. */
4
+ export function setCurrentCommand(cmd) {
5
+ atual = cmd;
6
+ }
7
+ /** O comando em execução, ou `null` fora do Commander. */
8
+ export function getCurrentCommand() {
9
+ return atual;
10
+ }
11
+ /** Registra a raiz da árvore de comandos (o `program`). */
12
+ export function setRootCommand(cmd) {
13
+ raiz = cmd;
14
+ }
15
+ /**
16
+ * A raiz da árvore de comandos: a do comando em execução, se houver; senão a
17
+ * registrada por `installCommandContext`; senão `null`.
18
+ */
19
+ export function getRootCommand() {
20
+ if (atual) {
21
+ let c = atual;
22
+ while (c.parent)
23
+ c = c.parent;
24
+ return c;
25
+ }
26
+ return raiz;
27
+ }
28
+ /**
29
+ * Liga o contexto no programa raiz: guarda a raiz e, antes de cada `.action()`,
30
+ * o comando que vai rodar. Chamar uma vez, no entry-point.
31
+ */
32
+ export function installCommandContext(program) {
33
+ setRootCommand(program);
34
+ program.hook('preAction', (_raiz, comandoDaAcao) => {
35
+ setCurrentCommand(comandoDaAcao);
36
+ });
37
+ }
38
+ /**
39
+ * O comando declara a flag (forma longa ou curta, inclusive alias oculto)?
40
+ * `false` sem comando — ver o cabeçalho.
41
+ */
42
+ export function commandDeclaresFlag(cmd, flag) {
43
+ if (!cmd)
44
+ return false;
45
+ return cmd.options.some((o) => o.long === flag || o.short === flag);
46
+ }
47
+ /**
48
+ * O comando aceita esta entrada? Flag (`--server`, `-y`) ou argumento
49
+ * posicional escrito como no `.command()` (`<id>`, `[serverId]`, `<args...>`).
50
+ * `false` sem comando.
51
+ */
52
+ export function commandDeclaresInput(cmd, input) {
53
+ if (!cmd)
54
+ return false;
55
+ if (input.startsWith('-'))
56
+ return commandDeclaresFlag(cmd, input);
57
+ const m = /^[<[]([^>\]]+?)(?:\.\.\.)?[>\]]$/.exec(input.trim());
58
+ if (!m)
59
+ return false;
60
+ return cmd.registeredArguments.some((a) => a.name() === m[1]);
61
+ }
62
+ /**
63
+ * Das flags pedidas, as que o comando declara — na ordem pedida. Base dos
64
+ * conselhos que oferecem mais de uma saída (`--yes (ou --force)`).
65
+ */
66
+ export function declaredFlags(flags, cmd = getCurrentCommand()) {
67
+ return flags.filter((f) => commandDeclaresFlag(cmd, f));
68
+ }
69
+ /**
70
+ * Das entradas pedidas (flags ou `<posicional>`), as que o comando aceita —
71
+ * na ordem pedida.
72
+ */
73
+ export function declaredInputs(inputs, cmd = getCurrentCommand()) {
74
+ return inputs.filter((i) => commandDeclaresInput(cmd, i));
75
+ }
76
+ /**
77
+ * O comando existe na árvore? `path` sem o `neetru` (ex.: `'auth mfa-session'`).
78
+ * Cada palavra casa com o nome ou um apelido de um subcomando. `false` sem
79
+ * árvore conhecida ou com caminho vazio.
80
+ */
81
+ export function commandExists(path, root = getRootCommand()) {
82
+ if (!root)
83
+ return false;
84
+ const palavras = path.trim().split(/\s+/).filter(Boolean);
85
+ if (palavras.length === 0)
86
+ return false;
87
+ let no = root;
88
+ for (const p of palavras) {
89
+ const filho = no.commands.find((c) => c.name() === p || c.aliases().includes(p));
90
+ if (!filho)
91
+ return false;
92
+ no = filho;
93
+ }
94
+ return true;
95
+ }
96
+ /**
97
+ * Escolhe o conselho conforme o comando em execução declare ou não a flag.
98
+ * `withoutFlag` citar a flag seria bug de quem chamou — o teste de cada call
99
+ * site trava os dois textos, e a guarda estática confere todos os call sites.
100
+ */
101
+ export function adviseFlag(advice, cmd = getCurrentCommand()) {
102
+ return commandDeclaresFlag(cmd, advice.flag) ? advice.withFlag : advice.withoutFlag;
103
+ }
104
+ /** Escolhe a dica conforme o comando citado exista ou não na árvore. */
105
+ export function adviseCommand(advice, root = getRootCommand()) {
106
+ return commandExists(advice.command, root) ? advice.withCommand : advice.withoutCommand;
107
+ }
108
+ //# sourceMappingURL=command-context.js.map
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Erro tipado da CLI (bug_89d2497e, etapa 3).
3
+ *
4
+ * Antes cada comando decidia sozinho como morrer: montava a mensagem à mão,
5
+ * escolhia um `safeExit(n)` e, em `--json`, um formato de payload. O modelo é o
6
+ * `FirebaseError` do firebase-tools (`lib/error.js`): o erro carrega o código
7
+ * de saída, a causa original e os filhos agregados — e quem imprime/encerra é
8
+ * um ponto só (`exitWithCliError`, em `src/lib/cli-error.ts`).
9
+ *
10
+ * Função pura: nada aqui imprime nem encerra o processo. O cli-kit continua sem
11
+ * dependência de logger/process — quem decide a saída é o call site.
12
+ */
13
+ /**
14
+ * Convenção ÚNICA de códigos de saída (a mesma do `handleApiError` desde a
15
+ * auditoria de 2026-06-20 — ver `src/lib/api-error.ts`):
16
+ * 1 — erro do cliente (entrada/recurso: 400, 404, 409, 422, validação)
17
+ * 2 — não autenticado (401)
18
+ * 3 — sem permissão (403)
19
+ * 4 — erro do servidor (5xx), rede ou desconhecido
20
+ */
21
+ export declare const EXIT_CODES: {
22
+ readonly client: 1;
23
+ readonly unauthenticated: 2;
24
+ readonly forbidden: 3;
25
+ readonly server: 4;
26
+ };
27
+ export interface NeetruCliErrorOptions {
28
+ /** Código de máquina — vira o campo `error` do JSON. Padrão: `'cli_error'`. */
29
+ code?: string;
30
+ /**
31
+ * Código de saída do processo. Padrão: o MAIS ALTO entre os filhos (a escala
32
+ * de `EXIT_CODES` cresce com a gravidade), senão `EXIT_CODES.client`.
33
+ */
34
+ exit?: number;
35
+ /** Erro original — preservado em `cause` (nunca engolido). */
36
+ cause?: unknown;
37
+ /** Falhas agregadas (ex.: itens de um lote que não passaram). */
38
+ children?: readonly NeetruCliError[];
39
+ /**
40
+ * Quantos itens do mesmo lote deram certo. Com filhos E sucessos, a falha é
41
+ * PARCIAL — e se reporta como parcial, não como "deu tudo errado" nem como
42
+ * "já existia" (bug_6e9fe576).
43
+ */
44
+ succeeded?: number;
45
+ /**
46
+ * Campos extras do JSON (`status`, `flag`, `valid`, …). Entram entre `error`
47
+ * e `message`, na ordem dada — preserva o formato que os comandos já emitiam.
48
+ */
49
+ details?: Readonly<Record<string, unknown>>;
50
+ /**
51
+ * Texto para humanos. Padrão: `message`. O JSON sempre leva `message` — o
52
+ * texto humano pode trazer prefixo e dica que não são contrato de máquina.
53
+ */
54
+ human?: string;
55
+ }
56
+ export declare class NeetruCliError extends Error {
57
+ readonly code: string;
58
+ readonly exit: number;
59
+ readonly children: readonly NeetruCliError[];
60
+ readonly succeeded: number;
61
+ readonly details: Readonly<Record<string, unknown>>;
62
+ readonly human: string;
63
+ constructor(message: string, opts?: NeetruCliErrorOptions);
64
+ /** Falha parcial: parte do lote falhou e parte deu certo. */
65
+ get partial(): boolean;
66
+ /**
67
+ * Payload `--json`. Sem filhos, é exatamente `{ ok:false, error, ...details,
68
+ * message }` — o formato que os comandos já emitiam. Com filhos, acrescenta
69
+ * `partial`, `succeeded`, `failed` e `children`.
70
+ */
71
+ toJSON(): Record<string, unknown>;
72
+ private corpoJson;
73
+ }
74
+ export declare function isNeetruCliError(x: unknown): x is NeetruCliError;
75
+ /**
76
+ * Texto humano completo do erro, com os filhos indentados. Uma falha parcial
77
+ * diz que é parcial e quanto deu certo.
78
+ */
79
+ export declare function renderCliError(error: NeetruCliError): string;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Erro tipado da CLI (bug_89d2497e, etapa 3).
3
+ *
4
+ * Antes cada comando decidia sozinho como morrer: montava a mensagem à mão,
5
+ * escolhia um `safeExit(n)` e, em `--json`, um formato de payload. O modelo é o
6
+ * `FirebaseError` do firebase-tools (`lib/error.js`): o erro carrega o código
7
+ * de saída, a causa original e os filhos agregados — e quem imprime/encerra é
8
+ * um ponto só (`exitWithCliError`, em `src/lib/cli-error.ts`).
9
+ *
10
+ * Função pura: nada aqui imprime nem encerra o processo. O cli-kit continua sem
11
+ * dependência de logger/process — quem decide a saída é o call site.
12
+ */
13
+ /**
14
+ * Convenção ÚNICA de códigos de saída (a mesma do `handleApiError` desde a
15
+ * auditoria de 2026-06-20 — ver `src/lib/api-error.ts`):
16
+ * 1 — erro do cliente (entrada/recurso: 400, 404, 409, 422, validação)
17
+ * 2 — não autenticado (401)
18
+ * 3 — sem permissão (403)
19
+ * 4 — erro do servidor (5xx), rede ou desconhecido
20
+ */
21
+ export const EXIT_CODES = {
22
+ client: 1,
23
+ unauthenticated: 2,
24
+ forbidden: 3,
25
+ server: 4,
26
+ };
27
+ export class NeetruCliError extends Error {
28
+ code;
29
+ exit;
30
+ children;
31
+ succeeded;
32
+ details;
33
+ human;
34
+ constructor(message, opts = {}) {
35
+ super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
36
+ this.name = 'NeetruCliError';
37
+ this.code = opts.code ?? 'cli_error';
38
+ this.children = [...(opts.children ?? [])];
39
+ this.succeeded = Math.max(0, opts.succeeded ?? 0);
40
+ this.details = { ...(opts.details ?? {}) };
41
+ this.human = opts.human ?? message;
42
+ this.exit =
43
+ opts.exit ??
44
+ (this.children.length > 0
45
+ ? Math.max(...this.children.map((c) => c.exit))
46
+ : EXIT_CODES.client);
47
+ }
48
+ /** Falha parcial: parte do lote falhou e parte deu certo. */
49
+ get partial() {
50
+ return this.children.length > 0 && this.succeeded > 0;
51
+ }
52
+ /**
53
+ * Payload `--json`. Sem filhos, é exatamente `{ ok:false, error, ...details,
54
+ * message }` — o formato que os comandos já emitiam. Com filhos, acrescenta
55
+ * `partial`, `succeeded`, `failed` e `children`.
56
+ */
57
+ toJSON() {
58
+ return { ok: false, ...this.corpoJson() };
59
+ }
60
+ corpoJson() {
61
+ const base = {
62
+ error: this.code,
63
+ ...this.details,
64
+ message: this.message,
65
+ };
66
+ if (this.children.length === 0)
67
+ return base;
68
+ return {
69
+ ...base,
70
+ partial: this.partial,
71
+ succeeded: this.succeeded,
72
+ failed: this.children.length,
73
+ children: this.children.map((c) => c.corpoJson()),
74
+ };
75
+ }
76
+ }
77
+ export function isNeetruCliError(x) {
78
+ return x instanceof NeetruCliError;
79
+ }
80
+ /**
81
+ * Texto humano completo do erro, com os filhos indentados. Uma falha parcial
82
+ * diz que é parcial e quanto deu certo.
83
+ */
84
+ export function renderCliError(error) {
85
+ if (error.children.length === 0)
86
+ return error.human;
87
+ const falhas = error.children.length;
88
+ const resumo = error.partial
89
+ ? `Falha parcial: ${falhas} de ${falhas + error.succeeded} itens falharam (${error.succeeded} deram certo).`
90
+ : `${falhas} ${falhas === 1 ? 'item falhou' : 'itens falharam'}.`;
91
+ const filhos = error.children.map((c) => ` - ${renderCliError(c).split('\n').join('\n ')}`);
92
+ return [error.human, ` ${resumo}`, ...filhos].join('\n');
93
+ }
94
+ //# sourceMappingURL=errors.js.map
@@ -11,3 +11,11 @@ export type { Glyphs } from './glyphs.js';
11
11
  export { ok, err, warn, dim, heading } from './palette.js';
12
12
  export { step, errorBlock, sacredPause, } from './format.js';
13
13
  export type { ErrorBlockOpts, SacredPauseOpts } from './format.js';
14
+ export { EXIT_CODES, NeetruCliError, isNeetruCliError, renderCliError } from './errors.js';
15
+ export type { NeetruCliErrorOptions } from './errors.js';
16
+ export { invalidChoiceError, formatChoices } from './choices.js';
17
+ export type { InvalidChoiceInput } from './choices.js';
18
+ export { installCommandContext, setCurrentCommand, getCurrentCommand, setRootCommand, getRootCommand, commandDeclaresFlag, commandDeclaresInput, declaredFlags, declaredInputs, commandExists, adviseFlag, adviseCommand, } from './command-context.js';
19
+ export type { FlagAdvice, CommandAdvice } from './command-context.js';
20
+ export { SYNONYMS, MIN_RATIO, MAX_SUGGESTIONS, isSubcommandVerb, wordScore, scoreCandidates, suggestCommands, suggestSubcommands, reservedVerbError, } from './suggest.js';
21
+ export type { ReservedVerbInput } from './suggest.js';
@@ -9,4 +9,12 @@
9
9
  export { GLYPHS } from './glyphs.js';
10
10
  export { ok, err, warn, dim, heading } from './palette.js';
11
11
  export { step, errorBlock, sacredPause, } from './format.js';
12
+ // Camada de CORREÇÃO (bug_89d2497e): erro tipado, conselho validado contra o
13
+ // comando em execução e valores válidos de enum vindos da fonte real.
14
+ export { EXIT_CODES, NeetruCliError, isNeetruCliError, renderCliError } from './errors.js';
15
+ export { invalidChoiceError, formatChoices } from './choices.js';
16
+ export { installCommandContext, setCurrentCommand, getCurrentCommand, setRootCommand, getRootCommand, commandDeclaresFlag, commandDeclaresInput, declaredFlags, declaredInputs, commandExists, adviseFlag, adviseCommand, } from './command-context.js';
17
+ // P105: verbo de subcomando nunca é valor livre; sugestão com piso de qualidade
18
+ // (SYNONYMS + MIN_RATIO, o `suggest_commands` do gcloud).
19
+ export { SYNONYMS, MIN_RATIO, MAX_SUGGESTIONS, isSubcommandVerb, wordScore, scoreCandidates, suggestCommands, suggestSubcommands, reservedVerbError, } from './suggest.js';
12
20
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,74 @@
1
+ import { NeetruCliError } from './errors.js';
2
+ /** Verbo digitado → verbo(s) que a CLI usa. */
3
+ export declare const SYNONYMS: Readonly<Record<string, readonly string[]>>;
4
+ /** Mínimo da razão pontuação/pontuação-do-primeiro para entrar na sugestão. */
5
+ export declare const MIN_RATIO = 0.7;
6
+ /** No máximo isto de sugestões. */
7
+ export declare const MAX_SUGGESTIONS = 10;
8
+ /**
9
+ * A palavra é, de fato, verbo de subcomando DESTE comando? Sim quando é um dos
10
+ * subcomandos dados (nome ou apelido) ou um sinônimo (`SYNONYMS`) de um deles.
11
+ * Sem diferenciar caixa, sem espaços nas pontas.
12
+ *
13
+ * Revisão do #87 (BAIXO 6): a 1ª versão recusava uma lista GLOBAL de verbos em
14
+ * qualquer texto livre, e `neetru chat send show` (resposta comum em PT-BR)
15
+ * passou a ser recusado sem que `show` fosse subcomando de `chat`. A recusa
16
+ * agora depende dos subcomandos de verdade de quem pergunta: para `chat send`,
17
+ * só `channels`, `identity`/`apelido`, `send` e `history`; para
18
+ * `chat identity`, os verbos dele (`show`, `set`, `clear`…) e seus sinônimos.
19
+ */
20
+ export declare function isSubcommandVerb(word: string, subcommands: readonly string[]): boolean;
21
+ /**
22
+ * Pontuação de uma palavra digitada contra uma palavra de um comando — o
23
+ * `_WordScore` do gcloud, linha a linha. Sempre ≥ 0; zero = sem relação.
24
+ */
25
+ export declare function wordScore(index: number, word: string, canonical: string, canonicalLength: number): number;
26
+ /**
27
+ * Candidatos pontuados, do maior para o menor (empate: ordem lexicográfica).
28
+ * Só entra quem pontua > 0. Cada candidato é a lista de palavras do comando.
29
+ */
30
+ export declare function scoreCandidates(words: readonly string[], candidates: ReadonlyArray<readonly string[]>): Array<{
31
+ command: readonly string[];
32
+ score: number;
33
+ }>;
34
+ /**
35
+ * Comandos sugeridos para as palavras digitadas — o `GetCommandSuggestions`
36
+ * do gcloud: só quem pontua > 0 e faz pelo menos `MIN_RATIO` da pontuação do
37
+ * primeiro, no máximo `MAX_SUGGESTIONS`. Vazio quando nada pontua.
38
+ */
39
+ export declare function suggestCommands(words: readonly string[], candidates: ReadonlyArray<readonly string[]>): string[];
40
+ /**
41
+ * Sugestões para a palavra digitada, entre os subcomandos dados. Vazio quando
42
+ * nada passa o piso — aí quem chama lista as escolhas, nunca chuta.
43
+ */
44
+ export declare function suggestSubcommands(word: string, subcommands: readonly string[]): string[];
45
+ export interface ReservedVerbInput {
46
+ /** O verbo recebido como valor, como a pessoa digitou. */
47
+ verb: string;
48
+ /** O comando onde ele chegou, sem o `neetru` (ex.: `'chat identity'`). */
49
+ command: string;
50
+ /**
51
+ * O comando cujos subcomandos são sugeridos, sem o `neetru`. Padrão:
52
+ * `command`. Ex.: a palavra chega em `chat send`, e os subcomandos são de `chat`.
53
+ */
54
+ parent?: string;
55
+ /** O que o comando esperava naquela posição (ex.: `'um apelido'`). */
56
+ what: string;
57
+ /**
58
+ * Os subcomandos/verbos que o comando aceita — a MESMA lista que ele usa
59
+ * para decidir. Vazio = sem sugestão possível (só a recusa).
60
+ */
61
+ subcommands: readonly string[];
62
+ /** Apelido → nome do subcomando (ex.: `apelido → identity`): a sugestão vai direto ao nome. */
63
+ aliases?: Readonly<Record<string, string>>;
64
+ /** Como passar a mesma palavra como texto, quando existe jeito. */
65
+ escapeHint?: string;
66
+ /** Código do JSON. Padrão: `'uso_invalido'`. */
67
+ code?: string;
68
+ }
69
+ /**
70
+ * Recusa um verbo usado como valor e sugere o subcomando certo — ou, sem
71
+ * sugestão que passe o piso, lista as escolhas. Exit 1 (erro do cliente). No
72
+ * JSON: `value`, `suggestions` (comandos completos) e `valid`.
73
+ */
74
+ export declare function reservedVerbError(input: ReservedVerbInput): NeetruCliError;
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Verbo de subcomando nunca é valor livre, e sugestão só com piso de qualidade
3
+ * (P105 / bug_89d2497e; o caso que nasceu daqui foi o bug_09574d4b).
4
+ *
5
+ * O bug: `neetru chat identity show` GRAVOU o apelido "show", e
6
+ * `identity set "X"` gravou "set". O comando recebia texto livre posicional e
7
+ * aceitava qualquer palavra — inclusive o verbo que a pessoa usou achando que
8
+ * era subcomando. O gcloud não teria aceitado essa string: ele sabe quais
9
+ * palavras são verbos e, quando a palavra não é escolha válida, sugere o
10
+ * comando certo ou lista as escolhas — nunca adivinha lixo.
11
+ *
12
+ * O MODELO é o `calliope/suggest_commands.py` do gcloud (lido no SDK instalado
13
+ * nesta máquina, `google-cloud-sdk/lib/googlecloudsdk/calliope`):
14
+ * - `SYNONYMS`: verbo digitado → verbo que a CLI de fato usa. O gcloud mapeia
15
+ * para os verbos DELE (`show → describe`); aqui o mapa aponta para os nossos
16
+ * (`describe → show`), e um verbo pode ter mais de um correspondente.
17
+ * - `_WordScore`: só pontua quando uma palavra CONTÉM a outra, e só em começo
18
+ * de palavra (ou depois de `-`). Sem relação de texto, a pontuação é zero e
19
+ * o candidato some. É esse o piso absoluto: nada de distância de edição, que
20
+ * acha "parecido" em qualquer par de palavras curtas.
21
+ * - `MIN_RATIO = 0.7`: entre os que pontuam, só entra quem faz pelo menos 70%
22
+ * da pontuação do primeiro. `MAX_SUGGESTIONS = 10`.
23
+ * - O histórico de uso do usuário (`_GetSurfaceHistoryFrequencies`) NÃO foi
24
+ * portado: a CLI não guarda histórico de comandos, e inventar um seria pior
25
+ * que não ranquear por ele.
26
+ *
27
+ * Quando nada passa o piso, quem chama LISTA as escolhas válidas (o gcloud faz
28
+ * o mesmo em erro de argumento, `parser_extensions.py`) em vez de sugerir.
29
+ */
30
+ import { formatChoices } from './choices.js';
31
+ import { EXIT_CODES, NeetruCliError } from './errors.js';
32
+ /** Verbo digitado → verbo(s) que a CLI usa. */
33
+ export const SYNONYMS = {
34
+ describe: ['show', 'get'],
35
+ get: ['show', 'describe'],
36
+ show: ['describe', 'get'],
37
+ view: ['show'],
38
+ ver: ['show'],
39
+ list: ['show'],
40
+ update: ['set'],
41
+ modify: ['set', 'update'],
42
+ patch: ['set', 'update'],
43
+ change: ['set', 'update'],
44
+ definir: ['set'],
45
+ unset: ['clear'],
46
+ remove: ['clear', 'delete'],
47
+ delete: ['clear', 'remove'],
48
+ reset: ['clear'],
49
+ limpar: ['clear'],
50
+ };
51
+ /** Mínimo da razão pontuação/pontuação-do-primeiro para entrar na sugestão. */
52
+ export const MIN_RATIO = 0.7;
53
+ /** No máximo isto de sugestões. */
54
+ export const MAX_SUGGESTIONS = 10;
55
+ /**
56
+ * A palavra é, de fato, verbo de subcomando DESTE comando? Sim quando é um dos
57
+ * subcomandos dados (nome ou apelido) ou um sinônimo (`SYNONYMS`) de um deles.
58
+ * Sem diferenciar caixa, sem espaços nas pontas.
59
+ *
60
+ * Revisão do #87 (BAIXO 6): a 1ª versão recusava uma lista GLOBAL de verbos em
61
+ * qualquer texto livre, e `neetru chat send show` (resposta comum em PT-BR)
62
+ * passou a ser recusado sem que `show` fosse subcomando de `chat`. A recusa
63
+ * agora depende dos subcomandos de verdade de quem pergunta: para `chat send`,
64
+ * só `channels`, `identity`/`apelido`, `send` e `history`; para
65
+ * `chat identity`, os verbos dele (`show`, `set`, `clear`…) e seus sinônimos.
66
+ */
67
+ export function isSubcommandVerb(word, subcommands) {
68
+ const w = word.trim().toLowerCase();
69
+ if (!w)
70
+ return false;
71
+ if (subcommands.includes(w))
72
+ return true;
73
+ return (SYNONYMS[w] ?? []).some((s) => subcommands.includes(s));
74
+ }
75
+ /**
76
+ * Pontuação de uma palavra digitada contra uma palavra de um comando — o
77
+ * `_WordScore` do gcloud, linha a linha. Sempre ≥ 0; zero = sem relação.
78
+ */
79
+ export function wordScore(index, word, canonical, canonicalLength) {
80
+ if (!word || !canonical)
81
+ return 0;
82
+ let shorter;
83
+ let longer;
84
+ if (canonical.includes(word)) {
85
+ shorter = word;
86
+ longer = canonical;
87
+ }
88
+ else if (word.includes(canonical)) {
89
+ shorter = canonical;
90
+ longer = word;
91
+ }
92
+ else {
93
+ return 0;
94
+ }
95
+ // O acerto no meio precisa cair em fronteira de palavra.
96
+ const hit = longer.indexOf(shorter);
97
+ if (hit > 0 && longer[hit - 1] !== '-')
98
+ return 0;
99
+ let score = 10;
100
+ if (canonicalLength === 1)
101
+ score += 30;
102
+ else if (canonicalLength === 2)
103
+ score += 20;
104
+ else if (canonicalLength === 3)
105
+ score += 10;
106
+ if (index === 0)
107
+ score += 25;
108
+ else if (index === 1)
109
+ score += 15;
110
+ else
111
+ score += 5;
112
+ let extra = longer.length - shorter.length;
113
+ if (extra <= 2) {
114
+ extra = 3 - extra;
115
+ if (longer.startsWith(shorter))
116
+ extra *= 2;
117
+ score += extra;
118
+ }
119
+ if (index === 0 && canonicalLength > 1)
120
+ score += 30;
121
+ else if (index > 0 && canonicalLength > index + 1)
122
+ score += 15;
123
+ return score;
124
+ }
125
+ /**
126
+ * Candidatos pontuados, do maior para o menor (empate: ordem lexicográfica).
127
+ * Só entra quem pontua > 0. Cada candidato é a lista de palavras do comando.
128
+ */
129
+ export function scoreCandidates(words, candidates) {
130
+ const normalizadas = words.map((w) => w.trim().toLowerCase().replace(/_/g, '-')).filter(Boolean);
131
+ const out = [];
132
+ for (const cand of candidates) {
133
+ const casadas = new Set();
134
+ let score = 0;
135
+ cand.forEach((canonica, index) => {
136
+ for (const w of normalizadas) {
137
+ let inc = wordScore(index, w, canonica, cand.length);
138
+ for (const alt of SYNONYMS[w] ?? []) {
139
+ inc = Math.max(inc, wordScore(index, alt, canonica, cand.length));
140
+ }
141
+ if (inc) {
142
+ casadas.add(w);
143
+ score += inc;
144
+ }
145
+ }
146
+ });
147
+ if (normalizadas.length > 0 && casadas.size === normalizadas.length)
148
+ score += 10;
149
+ if (score > 0)
150
+ out.push({ command: cand, score });
151
+ }
152
+ out.sort((a, b) => b.score - a.score || a.command.join(' ').localeCompare(b.command.join(' ')));
153
+ return out;
154
+ }
155
+ /**
156
+ * Comandos sugeridos para as palavras digitadas — o `GetCommandSuggestions`
157
+ * do gcloud: só quem pontua > 0 e faz pelo menos `MIN_RATIO` da pontuação do
158
+ * primeiro, no máximo `MAX_SUGGESTIONS`. Vazio quando nada pontua.
159
+ */
160
+ export function suggestCommands(words, candidates) {
161
+ const pontuados = scoreCandidates(words, candidates);
162
+ if (pontuados.length === 0)
163
+ return [];
164
+ const topo = pontuados[0].score;
165
+ return pontuados
166
+ .filter((p) => p.score / topo >= MIN_RATIO)
167
+ .slice(0, MAX_SUGGESTIONS)
168
+ .map((p) => p.command.join(' '));
169
+ }
170
+ /**
171
+ * Sugestões para a palavra digitada, entre os subcomandos dados. Vazio quando
172
+ * nada passa o piso — aí quem chama lista as escolhas, nunca chuta.
173
+ */
174
+ export function suggestSubcommands(word, subcommands) {
175
+ return suggestCommands([word], subcommands.map((s) => [s]));
176
+ }
177
+ /**
178
+ * Recusa um verbo usado como valor e sugere o subcomando certo — ou, sem
179
+ * sugestão que passe o piso, lista as escolhas. Exit 1 (erro do cliente). No
180
+ * JSON: `value`, `suggestions` (comandos completos) e `valid`.
181
+ */
182
+ export function reservedVerbError(input) {
183
+ const pai = input.parent ?? input.command;
184
+ const apelidoDe = input.aliases?.[input.verb.trim().toLowerCase()];
185
+ const sugestoes = (apelidoDe ? [apelidoDe] : suggestSubcommands(input.verb, input.subcommands)).map((s) => `neetru ${pai} ${s}`);
186
+ const linhas = [
187
+ `"${input.verb}" é palavra de comando da CLI e não vale como ${input.what} em \`neetru ${input.command}\`.`,
188
+ ];
189
+ if (sugestoes.length > 0) {
190
+ linhas.push(`Você quis dizer: ${sugestoes.map((s) => `\`${s}\``).join(' ou ')}?`);
191
+ }
192
+ else if (input.subcommands.length > 0) {
193
+ linhas.push(`Subcomandos de \`neetru ${pai}\`: ${formatChoices(input.subcommands)}.`);
194
+ }
195
+ if (input.escapeHint)
196
+ linhas.push(input.escapeHint);
197
+ return new NeetruCliError(linhas.join('\n '), {
198
+ code: input.code ?? 'uso_invalido',
199
+ exit: EXIT_CODES.client,
200
+ details: {
201
+ value: input.verb,
202
+ suggestions: sugestoes,
203
+ valid: [...input.subcommands],
204
+ },
205
+ });
206
+ }
207
+ //# sourceMappingURL=suggest.js.map
@@ -202,7 +202,7 @@ export async function runOidcLoginList(opts) {
202
202
  }
203
203
  log.heading(`Login-clients OIDC — produto ${opts.productId} (${res.count})`);
204
204
  renderTable([
205
- { header: 'CLIENT ID', value: (r) => r.clientId, maxWidth: 38 },
205
+ { header: 'CLIENT ID', value: (r) => r.clientId, identificador: true },
206
206
  { header: 'NOME', value: (r) => r.name, maxWidth: 24 },
207
207
  { header: 'REDIRECT_URI', value: (r) => (r.redirectUris ?? []).join(', '), maxWidth: 44 },
208
208
  {
@@ -120,7 +120,7 @@ export async function runSdkCredentialList(opts) {
120
120
  }
121
121
  log.heading(`Credenciais OIDC — produto ${opts.productId} (${res.count})`);
122
122
  renderTable([
123
- { header: 'CLIENT ID', value: (r) => r.clientId, maxWidth: 38 },
123
+ { header: 'CLIENT ID', value: (r) => r.clientId, identificador: true },
124
124
  { header: 'NOME', value: (r) => r.name, maxWidth: 28 },
125
125
  { header: 'ESCOPOS', value: (r) => r.allowedScopes.join(', '), maxWidth: 40 },
126
126
  {
@@ -6,7 +6,7 @@ export type Shell = 'bash' | 'zsh' | 'pwsh';
6
6
  * IMPORTANTE: incluir TODOS os `program.command('X')` de `src/index.ts`.
7
7
  * O regression test `top-level-commands-coverage.test.ts` exige >=95%.
8
8
  */
9
- export declare const TOP_LEVEL_COMMANDS: readonly ["add", "admin", "agent", "ai", "api-catalog", "ar", "archive", "artifact-registry", "audit", "auth", "autocomplete", "billing", "bootstrap", "bug", "build", "builds", "cloud-run", "code", "config", "customers", "db", "db-hosts", "deploy", "deployments", "dev", "dns", "docs", "doctor", "dr", "env", "hosting", "init", "login", "logout", "logs", "menu", "mobile", "mocks", "new", "open", "ops", "products", "promote", "publish", "schema", "servers", "smoke", "status", "support", "tenants", "ui", "upgrade", "validate", "vm", "whoami", "workspaces"];
9
+ export declare const TOP_LEVEL_COMMANDS: readonly ["add", "admin", "agent", "ai", "api-catalog", "ar", "archive", "artifact-registry", "audit", "auth", "autocomplete", "billing", "bootstrap", "bug", "build", "builds", "cloud-run", "code", "config", "customers", "db", "db-hosts", "deploy", "deployments", "dev", "dns", "docs", "doctor", "dr", "env", "hosting", "init", "login", "logout", "logs", "menu", "mobile", "mocks", "new", "open", "ops", "products", "promote", "publish", "schema", "secrets", "servers", "smoke", "status", "support", "tenants", "ui", "upgrade", "validate", "vm", "whoami", "workspaces"];
10
10
  export interface AutocompleteOptions {
11
11
  shell?: string;
12
12
  }
@@ -73,6 +73,7 @@ export const TOP_LEVEL_COMMANDS = [
73
73
  'promote',
74
74
  'publish',
75
75
  'schema', // neetru schema — inspecionar spec OpenAPI do Core
76
+ 'secrets', // neetru secrets — segredos do próprio produto (set/list/archive)
76
77
  'servers',
77
78
  'smoke', // neetru smoke — percorre páginas do produto local antes do deploy
78
79
  'status',