@neetru/cli 1.0.1 → 2.1.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 (192) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +109 -152
  3. package/dist/commands/add.d.ts +8 -3
  4. package/dist/commands/add.js +70 -143
  5. package/dist/commands/add.js.map +1 -1
  6. package/dist/commands/agent-release.d.ts +13 -0
  7. package/dist/commands/agent-release.js +204 -0
  8. package/dist/commands/agent-release.js.map +1 -0
  9. package/dist/commands/agent-write.d.ts +12 -0
  10. package/dist/commands/agent-write.js +94 -0
  11. package/dist/commands/agent-write.js.map +1 -0
  12. package/dist/commands/ai.d.ts +4 -0
  13. package/dist/commands/ai.js +88 -0
  14. package/dist/commands/ai.js.map +1 -0
  15. package/dist/commands/api-catalog.d.ts +20 -0
  16. package/dist/commands/api-catalog.js +126 -0
  17. package/dist/commands/api-catalog.js.map +1 -0
  18. package/dist/commands/audit.d.ts +8 -0
  19. package/dist/commands/audit.js +69 -0
  20. package/dist/commands/audit.js.map +1 -0
  21. package/dist/commands/autocomplete.d.ts +7 -0
  22. package/dist/commands/autocomplete.js +107 -0
  23. package/dist/commands/autocomplete.js.map +1 -0
  24. package/dist/commands/billing.d.ts +6 -0
  25. package/dist/commands/billing.js +69 -0
  26. package/dist/commands/billing.js.map +1 -0
  27. package/dist/commands/build.d.ts +18 -0
  28. package/dist/commands/build.js +295 -0
  29. package/dist/commands/build.js.map +1 -0
  30. package/dist/commands/cloud-run.d.ts +11 -0
  31. package/dist/commands/cloud-run.js +87 -0
  32. package/dist/commands/cloud-run.js.map +1 -0
  33. package/dist/commands/config.d.ts +3 -0
  34. package/dist/commands/config.js +70 -0
  35. package/dist/commands/config.js.map +1 -0
  36. package/dist/commands/db.d.ts +14 -0
  37. package/dist/commands/db.js +187 -0
  38. package/dist/commands/db.js.map +1 -0
  39. package/dist/commands/deploy.d.ts +23 -3
  40. package/dist/commands/deploy.js +530 -177
  41. package/dist/commands/deploy.js.map +1 -1
  42. package/dist/commands/deployments.d.ts +11 -0
  43. package/dist/commands/deployments.js +69 -0
  44. package/dist/commands/deployments.js.map +1 -0
  45. package/dist/commands/doctor.d.ts +27 -0
  46. package/dist/commands/doctor.js +211 -0
  47. package/dist/commands/doctor.js.map +1 -0
  48. package/dist/commands/dr.d.ts +11 -0
  49. package/dist/commands/dr.js +79 -0
  50. package/dist/commands/dr.js.map +1 -0
  51. package/dist/commands/env.d.ts +15 -0
  52. package/dist/commands/env.js +56 -0
  53. package/dist/commands/env.js.map +1 -0
  54. package/dist/commands/fn.d.ts +6 -0
  55. package/dist/commands/fn.js +87 -0
  56. package/dist/commands/fn.js.map +1 -0
  57. package/dist/commands/infra-read.d.ts +9 -0
  58. package/dist/commands/infra-read.js +113 -0
  59. package/dist/commands/infra-read.js.map +1 -0
  60. package/dist/commands/init.d.ts +10 -3
  61. package/dist/commands/init.js +275 -142
  62. package/dist/commands/init.js.map +1 -1
  63. package/dist/commands/login.d.ts +6 -3
  64. package/dist/commands/login.js +222 -92
  65. package/dist/commands/login.js.map +1 -1
  66. package/dist/commands/logout.d.ts +1 -0
  67. package/dist/commands/logout.js +28 -0
  68. package/dist/commands/logout.js.map +1 -0
  69. package/dist/commands/logs.d.ts +14 -3
  70. package/dist/commands/logs.js +132 -106
  71. package/dist/commands/logs.js.map +1 -1
  72. package/dist/commands/mocks.d.ts +5 -0
  73. package/dist/commands/mocks.js +23 -0
  74. package/dist/commands/mocks.js.map +1 -0
  75. package/dist/commands/open.d.ts +4 -3
  76. package/dist/commands/open.js +53 -85
  77. package/dist/commands/open.js.map +1 -1
  78. package/dist/commands/products-db.d.ts +37 -0
  79. package/dist/commands/products-db.js +230 -0
  80. package/dist/commands/products-db.js.map +1 -0
  81. package/dist/commands/products.d.ts +12 -0
  82. package/dist/commands/products.js +97 -0
  83. package/dist/commands/products.js.map +1 -0
  84. package/dist/commands/promote.d.ts +9 -0
  85. package/dist/commands/promote.js +114 -0
  86. package/dist/commands/promote.js.map +1 -0
  87. package/dist/commands/publish.d.ts +14 -0
  88. package/dist/commands/publish.js +180 -0
  89. package/dist/commands/publish.js.map +1 -0
  90. package/dist/commands/servers.d.ts +23 -0
  91. package/dist/commands/servers.js +166 -0
  92. package/dist/commands/servers.js.map +1 -0
  93. package/dist/commands/status.d.ts +5 -3
  94. package/dist/commands/status.js +91 -93
  95. package/dist/commands/status.js.map +1 -1
  96. package/dist/commands/support.d.ts +25 -0
  97. package/dist/commands/support.js +184 -0
  98. package/dist/commands/support.js.map +1 -0
  99. package/dist/commands/surface-status.d.ts +5 -0
  100. package/dist/commands/surface-status.js +63 -0
  101. package/dist/commands/surface-status.js.map +1 -0
  102. package/dist/commands/tenants.d.ts +34 -0
  103. package/dist/commands/tenants.js +179 -0
  104. package/dist/commands/tenants.js.map +1 -0
  105. package/dist/commands/upgrade.d.ts +12 -0
  106. package/dist/commands/upgrade.js +77 -0
  107. package/dist/commands/upgrade.js.map +1 -0
  108. package/dist/commands/validate.d.ts +1 -3
  109. package/dist/commands/validate.js +83 -91
  110. package/dist/commands/validate.js.map +1 -1
  111. package/dist/commands/whoami.d.ts +5 -3
  112. package/dist/commands/whoami.js +76 -28
  113. package/dist/commands/whoami.js.map +1 -1
  114. package/dist/commands/workspaces.d.ts +15 -0
  115. package/dist/commands/workspaces.js +72 -0
  116. package/dist/commands/workspaces.js.map +1 -0
  117. package/dist/index.d.ts +0 -1
  118. package/dist/index.js +1094 -36
  119. package/dist/index.js.map +1 -1
  120. package/dist/lib/ai/context.d.ts +11 -0
  121. package/dist/lib/ai/context.js +112 -0
  122. package/dist/lib/ai/context.js.map +1 -0
  123. package/dist/lib/ai/orchestrator.d.ts +10 -0
  124. package/dist/lib/ai/orchestrator.js +92 -0
  125. package/dist/lib/ai/orchestrator.js.map +1 -0
  126. package/dist/lib/api-client.d.ts +39 -0
  127. package/dist/lib/api-client.js +185 -0
  128. package/dist/lib/api-client.js.map +1 -0
  129. package/dist/lib/auth.d.ts +15 -0
  130. package/dist/lib/auth.js +98 -0
  131. package/dist/lib/auth.js.map +1 -0
  132. package/dist/lib/cli-read.d.ts +13 -0
  133. package/dist/lib/cli-read.js +103 -0
  134. package/dist/lib/cli-read.js.map +1 -0
  135. package/dist/lib/cli-write.d.ts +47 -0
  136. package/dist/lib/cli-write.js +137 -0
  137. package/dist/lib/cli-write.js.map +1 -0
  138. package/dist/lib/config-schema.d.ts +165 -0
  139. package/dist/lib/config-schema.js +57 -0
  140. package/dist/lib/config-schema.js.map +1 -0
  141. package/dist/lib/config.d.ts +15 -0
  142. package/dist/lib/config.js +33 -0
  143. package/dist/lib/config.js.map +1 -0
  144. package/dist/lib/render.d.ts +16 -0
  145. package/dist/lib/render.js +74 -0
  146. package/dist/lib/render.js.map +1 -0
  147. package/dist/utils/logger.d.ts +13 -0
  148. package/dist/utils/logger.js +27 -0
  149. package/dist/utils/logger.js.map +1 -0
  150. package/package.json +35 -33
  151. package/templates/auth/callback.ts +22 -0
  152. package/templates/auth/sign-in.tsx +41 -0
  153. package/templates/billing/checkout.ts +22 -0
  154. package/templates/billing/page.tsx +43 -0
  155. package/templates/support/ticket-form.tsx +68 -0
  156. package/templates/usage/track.ts +30 -0
  157. package/templates/users/profile.tsx +43 -0
  158. package/LICENSE +0 -21
  159. package/dist/commands/add.d.ts.map +0 -1
  160. package/dist/commands/deploy.d.ts.map +0 -1
  161. package/dist/commands/generate-types.d.ts +0 -3
  162. package/dist/commands/generate-types.d.ts.map +0 -1
  163. package/dist/commands/generate-types.js +0 -150
  164. package/dist/commands/generate-types.js.map +0 -1
  165. package/dist/commands/init.d.ts.map +0 -1
  166. package/dist/commands/login.d.ts.map +0 -1
  167. package/dist/commands/logs.d.ts.map +0 -1
  168. package/dist/commands/open.d.ts.map +0 -1
  169. package/dist/commands/status.d.ts.map +0 -1
  170. package/dist/commands/validate.d.ts.map +0 -1
  171. package/dist/commands/whoami.d.ts.map +0 -1
  172. package/dist/config.d.ts +0 -14
  173. package/dist/config.d.ts.map +0 -1
  174. package/dist/config.js +0 -83
  175. package/dist/config.js.map +0 -1
  176. package/dist/index.d.ts.map +0 -1
  177. package/dist/scaffold/auth.d.ts +0 -3
  178. package/dist/scaffold/auth.d.ts.map +0 -1
  179. package/dist/scaffold/auth.js +0 -228
  180. package/dist/scaffold/auth.js.map +0 -1
  181. package/dist/scaffold/billing.d.ts +0 -3
  182. package/dist/scaffold/billing.d.ts.map +0 -1
  183. package/dist/scaffold/billing.js +0 -184
  184. package/dist/scaffold/billing.js.map +0 -1
  185. package/dist/scaffold/usage.d.ts +0 -3
  186. package/dist/scaffold/usage.d.ts.map +0 -1
  187. package/dist/scaffold/usage.js +0 -173
  188. package/dist/scaffold/usage.js.map +0 -1
  189. package/dist/scaffold/users.d.ts +0 -3
  190. package/dist/scaffold/users.d.ts.map +0 -1
  191. package/dist/scaffold/users.js +0 -135
  192. package/dist/scaffold/users.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,40 +1,1098 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const commander_1 = require("commander");
8
- const chalk_1 = __importDefault(require("chalk"));
9
- const init_1 = require("./commands/init");
10
- const add_1 = require("./commands/add");
11
- const validate_1 = require("./commands/validate");
12
- const generate_types_1 = require("./commands/generate-types");
13
- const whoami_1 = require("./commands/whoami");
14
- const login_1 = require("./commands/login");
15
- const status_1 = require("./commands/status");
16
- const logs_1 = require("./commands/logs");
17
- const deploy_1 = require("./commands/deploy");
18
- const open_1 = require("./commands/open");
19
- const program = new commander_1.Command();
2
+ import { Command } from 'commander';
3
+ import { log } from './utils/logger.js';
4
+ const program = new Command();
20
5
  program
21
6
  .name('neetru')
22
- .description(chalk_1.default.bold('Neetru CLI') +
23
- ' — SDK & DevOps para integrar seu SaaS com Neetru Core\n' +
24
- chalk_1.default.dim(' init · add · login · status · deploy · logs · open'))
25
- .version('1.0.0');
26
- // Setup
27
- program.addCommand((0, init_1.initCommand)());
28
- program.addCommand((0, add_1.addCommand)());
29
- program.addCommand((0, validate_1.validateCommand)());
30
- program.addCommand((0, generate_types_1.generateTypesCommand)());
31
- // Auth & info
32
- program.addCommand((0, login_1.loginCommand)());
33
- program.addCommand((0, whoami_1.whoamiCommand)());
34
- // Operations
35
- program.addCommand((0, status_1.statusCommand)());
36
- program.addCommand((0, deploy_1.deployCommand)());
37
- program.addCommand((0, logs_1.logsCommand)());
38
- program.addCommand((0, open_1.openCommand)());
39
- program.parseAsync(process.argv);
7
+ .description('Neetru Developer Kit — scaffold, AI assistant e deploy para produtos SaaS')
8
+ .version('2.0.0');
9
+ // ── neetru ai ─────────────────────────────────────────────────────────
10
+ program
11
+ .command('ai')
12
+ .description('REPL interativo com IA Neetru-aware (Claude / OpenAI / Gemini)')
13
+ .option('-m, --model <model>', 'modelo: claude | openai | gemini | auto', 'auto')
14
+ .action(async (opts) => {
15
+ const { runAiRepl } = await import('./commands/ai.js');
16
+ await runAiRepl({ model: opts.model });
17
+ });
18
+ // ── neetru init ───────────────────────────────────────────────────────
19
+ program
20
+ .command('init <name>')
21
+ .description('Scaffold de novo produto SaaS Neetru')
22
+ .option('--type <type>', 'nextjs | node-api', 'nextjs')
23
+ .action(async (name, opts) => {
24
+ const { runInit } = await import('./commands/init.js');
25
+ await runInit({ name, type: opts.type });
26
+ });
27
+ // ── neetru config ─────────────────────────────────────────────────────
28
+ const configCmd = program
29
+ .command('config')
30
+ .description('Gerenciar configurações do CLI (chaves de API, modelo padrão)');
31
+ configCmd
32
+ .command('set <key> <value>')
33
+ .description('Define uma configuração')
34
+ .action(async (key, value) => {
35
+ const { configSet } = await import('./commands/config.js');
36
+ configSet(key, value);
37
+ });
38
+ configCmd
39
+ .command('get [key]')
40
+ .description('Lê configuração(ões)')
41
+ .action(async (key) => {
42
+ const { configGet } = await import('./commands/config.js');
43
+ configGet(key);
44
+ });
45
+ configCmd
46
+ .command('path')
47
+ .description('Mostra o caminho do arquivo de configuração')
48
+ .action(async () => {
49
+ const { configPath } = await import('./commands/config.js');
50
+ configPath();
51
+ });
52
+ // ── neetru login ──────────────────────────────────────────────────────
53
+ program
54
+ .command('login')
55
+ .description('Autentica o CLI via Device Code OAuth (RFC 8628)')
56
+ .option('--token <token>', 'token nrt_<keyId>_<secret> (modo CI/legado, pula browser)')
57
+ .option('--json', 'saída em JSON (machine-readable)')
58
+ .action(async (opts) => {
59
+ const { runLogin } = await import('./commands/login.js');
60
+ await runLogin({ token: opts.token, json: !!opts.json });
61
+ });
62
+ // ── neetru logout ─────────────────────────────────────────────────────
63
+ program
64
+ .command('logout')
65
+ .description('Apaga as credenciais locais (~/.config/neetru-cli/auth.json + cache legado)')
66
+ .action(async () => {
67
+ const { runLogout } = await import('./commands/logout.js');
68
+ await runLogout();
69
+ });
70
+ // ── neetru whoami ─────────────────────────────────────────────────────
71
+ program
72
+ .command('whoami')
73
+ .description('Mostra a identidade da chave CLI corrente')
74
+ .option('--json', 'saída em JSON')
75
+ .action(async (opts) => {
76
+ const { runWhoami } = await import('./commands/whoami.js');
77
+ await runWhoami({ json: !!opts.json });
78
+ });
79
+ // ── neetru build ──────────────────────────────────────────────────────
80
+ program
81
+ .command('build')
82
+ .description('Empacota o produto no diretório atual num tarball pronto para deploy')
83
+ .option('--product <slug>', 'override do slug (default: lê neetru.config.json)')
84
+ .option('--stack <stack>', 'node | docker | php-apache | static (default: detectado)')
85
+ .option('--output <dir>', 'diretório de saída', '.neetru-build')
86
+ .option('--version <version>', 'versão (default: detectada de package.json ou timestamp)')
87
+ .option('--no-cache', 'invalida caches do build (recria lockfile, --no-cache no docker)')
88
+ .action(async (opts) => {
89
+ const { runBuild } = await import('./commands/build.js');
90
+ await runBuild({
91
+ product: opts.product,
92
+ stack: opts.stack,
93
+ output: opts.output,
94
+ version: opts.version,
95
+ noCache: !opts.cache,
96
+ });
97
+ });
98
+ // ── neetru deploy ─────────────────────────────────────────────────────
99
+ program
100
+ .command('deploy')
101
+ .description('Deploy do produto via pipeline Neetru Core (interativo por default)')
102
+ .option('--product <slug>', 'slug do produto')
103
+ .option('--version <version>', 'versão a deployar')
104
+ .option('--stack <stack>', 'node | docker | php-apache | static')
105
+ .option('--target <target>', 'cloud-run | vm', 'vm')
106
+ .option('--server <id>', 'id do server (target=vm)')
107
+ .option('--domain <domain>', 'domínio público')
108
+ .option('--port <port>', 'porta interna do app', (v) => Number.parseInt(v, 10))
109
+ .option('--artifact <file>', 'caminho do tarball já buildado')
110
+ .option('--artifact-url <url>', 'URL pública/signed do artifact (GCS, S3)')
111
+ .option('--artifact-sha256 <sha>', 'sha256 do artifact (par com --artifact-url)')
112
+ .option('--env-file <file>', 'arquivo .env com vars passadas pro deploy')
113
+ .option('--env <env>', 'ambiente alvo: dev | staging | prod', 'dev')
114
+ .option('--local-artifact', 'F-11: NÃO faz upload pra GCS — manda file:// pro Core (só funciona se o agent compartilha FS com este CLI)')
115
+ .option('--non-interactive', 'falha em vez de perguntar (modo CI)')
116
+ .action(async (opts) => {
117
+ const { runDeploy } = await import('./commands/deploy.js');
118
+ await runDeploy({
119
+ product: opts.product,
120
+ version: opts.version,
121
+ stack: opts.stack,
122
+ target: opts.target,
123
+ server: opts.server,
124
+ domain: opts.domain,
125
+ port: opts.port,
126
+ artifact: opts.artifact,
127
+ artifactUrl: opts.artifactUrl,
128
+ artifactSha256: opts.artifactSha256,
129
+ envFile: opts.envFile,
130
+ env: opts.env,
131
+ localArtifact: !!opts.localArtifact,
132
+ nonInteractive: !!opts.nonInteractive,
133
+ });
134
+ });
135
+ // ── neetru status ─────────────────────────────────────────────────────
136
+ // Default: saúde das superfícies públicas (control plane). Com --client-id,
137
+ // delega ao status de workspace legado (backward-compat).
138
+ program
139
+ .command('status')
140
+ .description('Saúde das superfícies públicas (landing/api/portal/políticas). --client-id = status de workspace')
141
+ .option('--client-id <id>', 'oauthClientId do workspace (status de workspace legado)')
142
+ .option('--json', 'saída em JSON')
143
+ .action(async (opts) => {
144
+ const { runSurfaceStatus } = await import('./commands/surface-status.js');
145
+ await runSurfaceStatus({ clientId: opts.clientId, json: !!opts.json });
146
+ });
147
+ // ── neetru tenants ────────────────────────────────────────────────────
148
+ const tenantsCmd = program
149
+ .command('tenants')
150
+ .description('Control plane — inspeção de tenants (read-only)');
151
+ tenantsCmd
152
+ .command('list')
153
+ .description('Lista tenants (filtros --status / --product)')
154
+ .option('--status <status>', 'ativo | em provisionamento | suspenso | arquivado')
155
+ .option('--product <productId>', 'filtra por productId')
156
+ .option('--limit <n>', 'máximo de resultados (default 100, max 500)')
157
+ .option('--json', 'saída em JSON')
158
+ .action(async (opts) => {
159
+ const { runTenantsList } = await import('./commands/tenants.js');
160
+ await runTenantsList({
161
+ status: opts.status,
162
+ product: opts.product,
163
+ limit: opts.limit,
164
+ json: !!opts.json,
165
+ });
166
+ });
167
+ tenantsCmd
168
+ .command('get <id>')
169
+ .description('Detalha um tenant pelo ID')
170
+ .option('--json', 'saída em JSON')
171
+ .action(async (id, opts) => {
172
+ const { runTenantsGet } = await import('./commands/tenants.js');
173
+ await runTenantsGet(id, { json: !!opts.json });
174
+ });
175
+ tenantsCmd
176
+ .command('create')
177
+ .description('Cria um tenant (status inicial: em provisionamento)')
178
+ .requiredOption('--name <name>', 'nome do tenant')
179
+ .requiredOption('--slug <slug>', 'slug do tenant')
180
+ .requiredOption('--customer <customerId>', 'id do customer')
181
+ .requiredOption('--product <productId>', 'id do produto')
182
+ .requiredOption('--env <env>', 'dev | staging | prod')
183
+ .option('--domain <domain>', 'domínio primário')
184
+ .option('--json', 'saída em JSON')
185
+ .action(async (opts) => {
186
+ const { runTenantsCreate } = await import('./commands/tenants.js');
187
+ await runTenantsCreate({
188
+ name: opts.name,
189
+ slug: opts.slug,
190
+ customer: opts.customer,
191
+ product: opts.product,
192
+ env: opts.env,
193
+ domain: opts.domain,
194
+ json: !!opts.json,
195
+ });
196
+ });
197
+ tenantsCmd
198
+ .command('update <id>')
199
+ .description('Atualiza dados de um tenant existente')
200
+ .requiredOption('--name <name>', 'nome do tenant')
201
+ .requiredOption('--slug <slug>', 'slug do tenant')
202
+ .requiredOption('--customer <customerId>', 'id do customer')
203
+ .requiredOption('--product <productId>', 'id do produto')
204
+ .requiredOption('--env <env>', 'dev | staging | prod')
205
+ .option('--domain <domain>', 'domínio primário')
206
+ .option('--json', 'saída em JSON')
207
+ .action(async (id, opts) => {
208
+ const { runTenantsUpdate } = await import('./commands/tenants.js');
209
+ await runTenantsUpdate(id, {
210
+ name: opts.name,
211
+ slug: opts.slug,
212
+ customer: opts.customer,
213
+ product: opts.product,
214
+ env: opts.env,
215
+ domain: opts.domain,
216
+ json: !!opts.json,
217
+ });
218
+ });
219
+ tenantsCmd
220
+ .command('suspend <id>')
221
+ .description('Suspende um tenant (destrutivo — confirmação interativa)')
222
+ .option('--yes', 'pula a confirmação interativa (modo script)')
223
+ .option('--force', 'alias de --yes')
224
+ .option('--json', 'saída em JSON')
225
+ .action(async (id, opts) => {
226
+ const { runTenantsSuspend } = await import('./commands/tenants.js');
227
+ await runTenantsSuspend(id, { yes: !!opts.yes, force: !!opts.force, json: !!opts.json });
228
+ });
229
+ tenantsCmd
230
+ .command('reactivate <id>')
231
+ .description('Reativa um tenant suspenso')
232
+ .option('--json', 'saída em JSON')
233
+ .action(async (id, opts) => {
234
+ const { runTenantsReactivate } = await import('./commands/tenants.js');
235
+ await runTenantsReactivate(id, { json: !!opts.json });
236
+ });
237
+ // ── neetru audit ──────────────────────────────────────────────────────
238
+ const auditCmd = program
239
+ .command('audit')
240
+ .description('Control plane — trilha de auditoria do Core (read-only)');
241
+ auditCmd
242
+ .command('tail')
243
+ .description('Últimos N eventos de audit_logs (mais recente primeiro)')
244
+ .option('-n, --limit <n>', 'número de eventos (default 50, max 200)')
245
+ .option('--action <substring>', 'filtra por substring da ação (ex: billing, tenant.create)')
246
+ .option('--severity <level>', 'info | warning | critical')
247
+ .option('--actor <substring>', 'filtra por uid ou email do ator')
248
+ .option('--json', 'saída em JSON')
249
+ .action(async (opts) => {
250
+ const { runAuditTail } = await import('./commands/audit.js');
251
+ await runAuditTail({
252
+ limit: opts.limit,
253
+ action: opts.action,
254
+ severity: opts.severity,
255
+ actor: opts.actor,
256
+ json: !!opts.json,
257
+ });
258
+ });
259
+ // ── neetru billing ────────────────────────────────────────────────────
260
+ const billingCmd = program
261
+ .command('billing')
262
+ .description('Control plane — billing/contabilidade (read-only)');
263
+ billingCmd
264
+ .command('summary')
265
+ .description('Sumário contábil do mês corrente (MRR, invoices, subscriptions)')
266
+ .option('--year <YYYY>', 'ano (default: corrente)')
267
+ .option('--month <1-12>', 'mês (default: corrente)')
268
+ .option('--json', 'saída em JSON')
269
+ .action(async (opts) => {
270
+ const { runBillingSummary } = await import('./commands/billing.js');
271
+ await runBillingSummary({ year: opts.year, month: opts.month, json: !!opts.json });
272
+ });
273
+ // ── neetru servers ────────────────────────────────────────────────────
274
+ const serversCmd = program
275
+ .command('servers')
276
+ .description('Control plane — inventário de servers (read-only)');
277
+ serversCmd
278
+ .command('list')
279
+ .description('Lista servers (filtros --status / --provider / --capacity)')
280
+ .option('--status <status>', 'online (operacional + heartbeat fresco)')
281
+ .option('--provider <provider>', 'hetzner | digitalocean | aws | ...')
282
+ .option('--capacity', 'inclui colunas de RAM/CPU/disco')
283
+ .option('--json', 'saída em JSON')
284
+ .action(async (opts) => {
285
+ const { runServersList } = await import('./commands/servers.js');
286
+ await runServersList({
287
+ status: opts.status,
288
+ provider: opts.provider,
289
+ capacity: !!opts.capacity,
290
+ json: !!opts.json,
291
+ });
292
+ });
293
+ serversCmd
294
+ .command('provision')
295
+ .description('Provisiona uma VM GCP (admin only) — cria server + registration token')
296
+ .requiredOption('--name <name>', 'nome do servidor')
297
+ .requiredOption('--zone <zone>', 'zona GCP (ex: us-central1-a)')
298
+ .requiredOption('--machine-type <type>', 'machine type GCP (ex: e2-small)')
299
+ .option('--tenant <tenantId>', 'tenant associado')
300
+ .option('--customer <customerId>', 'customer associado')
301
+ .option('--json', 'saída em JSON')
302
+ .action(async (opts) => {
303
+ const { runServersProvision } = await import('./commands/servers.js');
304
+ await runServersProvision({
305
+ name: opts.name,
306
+ zone: opts.zone,
307
+ machineType: opts.machineType,
308
+ tenant: opts.tenant,
309
+ customer: opts.customer,
310
+ json: !!opts.json,
311
+ });
312
+ });
313
+ serversCmd
314
+ .command('deactivate <serverId>')
315
+ .description('Desativa um server (destrutivo top-tier — confirmação + step-up MFA)')
316
+ .option('--yes', 'pula a confirmação interativa (modo script)')
317
+ .option('--force', 'alias de --yes')
318
+ .option('--dry-run', 'valida e mostra o efeito sem aplicar')
319
+ .option('--mfa-token <code>', 'código TOTP para step-up MFA')
320
+ .option('--json', 'saída em JSON')
321
+ .action(async (id, opts) => {
322
+ const { runServersDeactivate } = await import('./commands/servers.js');
323
+ await runServersDeactivate(id, {
324
+ yes: !!opts.yes,
325
+ force: !!opts.force,
326
+ dryRun: !!opts.dryRun,
327
+ mfaToken: opts.mfaToken,
328
+ json: !!opts.json,
329
+ });
330
+ });
331
+ serversCmd
332
+ .command('dispatch <serverId> <commandType>')
333
+ .description('Enfileira um comando pro agente Linux do server')
334
+ .option('--params <json>', 'objeto JSON de params do comando')
335
+ .option('--json', 'saída em JSON')
336
+ .action(async (id, commandType, opts) => {
337
+ const { runServersDispatch } = await import('./commands/servers.js');
338
+ await runServersDispatch(id, commandType, { params: opts.params, json: !!opts.json });
339
+ });
340
+ // ── neetru workspaces ─────────────────────────────────────────────────
341
+ const workspacesCmd = program
342
+ .command('workspaces')
343
+ .description('Control plane — runtime shared de workspaces');
344
+ workspacesCmd
345
+ .command('create')
346
+ .description('Cria um workspace (devolve OAuth client secret one-time)')
347
+ .requiredOption('--product <productId>', 'id do produto')
348
+ .requiredOption('--customer <customerId>', 'id do customer')
349
+ .requiredOption('--env <env>', 'dev | staging | prod')
350
+ .requiredOption('--tier <tier>', 'dev | standard | enterprise')
351
+ .option('--name <name>', 'label do workspace')
352
+ .option('--json', 'saída em JSON')
353
+ .action(async (opts) => {
354
+ const { runWorkspacesCreate } = await import('./commands/workspaces.js');
355
+ await runWorkspacesCreate({
356
+ product: opts.product,
357
+ customer: opts.customer,
358
+ env: opts.env,
359
+ tier: opts.tier,
360
+ name: opts.name,
361
+ json: !!opts.json,
362
+ });
363
+ });
364
+ workspacesCmd
365
+ .command('advance <workspaceId>')
366
+ .description('Promove uma versão de bundle pra "running" no workspace')
367
+ .requiredOption('--product <slug>', 'productSlug do bundle')
368
+ .requiredOption('--version <version>', 'versão do bundle a ativar')
369
+ .option('--json', 'saída em JSON')
370
+ .action(async (id, opts) => {
371
+ const { runWorkspacesAdvance } = await import('./commands/workspaces.js');
372
+ await runWorkspacesAdvance(id, {
373
+ product: opts.product,
374
+ version: opts.version,
375
+ json: !!opts.json,
376
+ });
377
+ });
378
+ // ── neetru deployments ────────────────────────────────────────────────
379
+ const deploymentsCmd = program
380
+ .command('deployments')
381
+ .description('Control plane — recurso deployments/ (paridade com a UI)');
382
+ deploymentsCmd
383
+ .command('create')
384
+ .description('Cria um deployment (dispara comando deploy pro agente)')
385
+ .requiredOption('--product <productId>', 'id do produto')
386
+ .requiredOption('--tenant <tenantId>', 'id do tenant alvo')
387
+ .requiredOption('--version <version>', 'versão a deployar')
388
+ .requiredOption('--env <env>', 'dev | staging | prod')
389
+ .requiredOption('--server <serverId>', 'id do server alvo')
390
+ .option('--json', 'saída em JSON')
391
+ .action(async (opts) => {
392
+ const { runDeploymentsCreate } = await import('./commands/deployments.js');
393
+ await runDeploymentsCreate({
394
+ product: opts.product,
395
+ tenant: opts.tenant,
396
+ version: opts.version,
397
+ env: opts.env,
398
+ server: opts.server,
399
+ json: !!opts.json,
400
+ });
401
+ });
402
+ deploymentsCmd
403
+ .command('rollback <deploymentId>')
404
+ .description('Rollback de um deployment (destrutivo top-tier — confirmação + step-up MFA)')
405
+ .option('--yes', 'pula a confirmação interativa (modo script)')
406
+ .option('--force', 'alias de --yes')
407
+ .option('--dry-run', 'valida e mostra o efeito sem aplicar')
408
+ .option('--mfa-token <code>', 'código TOTP para step-up MFA')
409
+ .option('--json', 'saída em JSON')
410
+ .action(async (id, opts) => {
411
+ const { runDeploymentsRollback } = await import('./commands/deployments.js');
412
+ await runDeploymentsRollback(id, {
413
+ yes: !!opts.yes,
414
+ force: !!opts.force,
415
+ dryRun: !!opts.dryRun,
416
+ mfaToken: opts.mfaToken,
417
+ json: !!opts.json,
418
+ });
419
+ });
420
+ // ── neetru cloud-run ──────────────────────────────────────────────────
421
+ const cloudRunCmd = program
422
+ .command('cloud-run')
423
+ .description('Control plane — serviços Cloud Run (admin only)');
424
+ cloudRunCmd
425
+ .command('pause <service>')
426
+ .description('Pausa um serviço Cloud Run (scale-to-zero)')
427
+ .option('--json', 'saída em JSON')
428
+ .action(async (service, opts) => {
429
+ const { runCloudRunPause } = await import('./commands/cloud-run.js');
430
+ await runCloudRunPause(service, { json: !!opts.json });
431
+ });
432
+ cloudRunCmd
433
+ .command('resume <service>')
434
+ .description('Retoma um serviço Cloud Run pausado')
435
+ .option('--min-instances <n>', 'minInstanceCount')
436
+ .option('--max-instances <n>', 'maxInstanceCount')
437
+ .option('--json', 'saída em JSON')
438
+ .action(async (service, opts) => {
439
+ const { runCloudRunResume } = await import('./commands/cloud-run.js');
440
+ await runCloudRunResume(service, {
441
+ minInstances: opts.minInstances,
442
+ maxInstances: opts.maxInstances,
443
+ json: !!opts.json,
444
+ });
445
+ });
446
+ cloudRunCmd
447
+ .command('delete <service>')
448
+ .description('Exclui um serviço Cloud Run (IRREVERSÍVEL — confirmação + step-up MFA)')
449
+ .option('--yes', 'pula a confirmação interativa (modo script)')
450
+ .option('--force', 'alias de --yes')
451
+ .option('--dry-run', 'valida e mostra o efeito sem aplicar')
452
+ .option('--mfa-token <code>', 'código TOTP para step-up MFA')
453
+ .option('--json', 'saída em JSON')
454
+ .action(async (service, opts) => {
455
+ const { runCloudRunDelete } = await import('./commands/cloud-run.js');
456
+ await runCloudRunDelete(service, {
457
+ yes: !!opts.yes,
458
+ force: !!opts.force,
459
+ dryRun: !!opts.dryRun,
460
+ mfaToken: opts.mfaToken,
461
+ json: !!opts.json,
462
+ });
463
+ });
464
+ // ── neetru api-catalog ────────────────────────────────────────────────
465
+ const apiCatalogCmd = program
466
+ .command('api-catalog')
467
+ .description('Control plane — registry de APIs versionadas (apis/)');
468
+ apiCatalogCmd
469
+ .command('create <slug>')
470
+ .description('Registra uma nova API no catálogo')
471
+ .requiredOption('--name <name>', 'nome da API')
472
+ .requiredOption('--base-url <url>', 'base URL')
473
+ .requiredOption('--version <version>', 'versão')
474
+ .requiredOption('--auth-method <method>', 'método de auth')
475
+ .requiredOption('--status <status>', 'status (ex: active | deprecated)')
476
+ .option('--description <text>', 'descrição')
477
+ .option('--docs-url <url>', 'URL da documentação')
478
+ .option('--owner-team <team>', 'time responsável')
479
+ .option('--contact-email <email>', 'email de contato')
480
+ .option('--scopes <csv>', 'scopes obrigatórios (separados por vírgula)')
481
+ .option('--json', 'saída em JSON')
482
+ .action(async (slug, opts) => {
483
+ const { runApiCatalogCreate } = await import('./commands/api-catalog.js');
484
+ await runApiCatalogCreate(slug, {
485
+ name: opts.name,
486
+ baseUrl: opts.baseUrl,
487
+ version: opts.version,
488
+ authMethod: opts.authMethod,
489
+ status: opts.status,
490
+ description: opts.description,
491
+ docsUrl: opts.docsUrl,
492
+ ownerTeam: opts.ownerTeam,
493
+ contactEmail: opts.contactEmail,
494
+ scopes: opts.scopes,
495
+ json: !!opts.json,
496
+ });
497
+ });
498
+ apiCatalogCmd
499
+ .command('update <slug>')
500
+ .description('Atualiza uma entrada do catálogo (slug é imutável)')
501
+ .requiredOption('--name <name>', 'nome da API')
502
+ .requiredOption('--base-url <url>', 'base URL')
503
+ .requiredOption('--version <version>', 'versão')
504
+ .requiredOption('--auth-method <method>', 'método de auth')
505
+ .requiredOption('--status <status>', 'status (ex: active | deprecated)')
506
+ .option('--description <text>', 'descrição')
507
+ .option('--docs-url <url>', 'URL da documentação')
508
+ .option('--owner-team <team>', 'time responsável')
509
+ .option('--contact-email <email>', 'email de contato')
510
+ .option('--scopes <csv>', 'scopes obrigatórios (separados por vírgula)')
511
+ .option('--json', 'saída em JSON')
512
+ .action(async (slug, opts) => {
513
+ const { runApiCatalogUpdate } = await import('./commands/api-catalog.js');
514
+ await runApiCatalogUpdate(slug, {
515
+ name: opts.name,
516
+ baseUrl: opts.baseUrl,
517
+ version: opts.version,
518
+ authMethod: opts.authMethod,
519
+ status: opts.status,
520
+ description: opts.description,
521
+ docsUrl: opts.docsUrl,
522
+ ownerTeam: opts.ownerTeam,
523
+ contactEmail: opts.contactEmail,
524
+ scopes: opts.scopes,
525
+ json: !!opts.json,
526
+ });
527
+ });
528
+ apiCatalogCmd
529
+ .command('archive <slug>')
530
+ .description('Soft-delete de uma API (--unarchive reativa)')
531
+ .option('--unarchive', 'reativa em vez de arquivar')
532
+ .option('--yes', 'pula a confirmação interativa (modo script)')
533
+ .option('--force', 'alias de --yes')
534
+ .option('--json', 'saída em JSON')
535
+ .action(async (slug, opts) => {
536
+ const { runApiCatalogArchive } = await import('./commands/api-catalog.js');
537
+ await runApiCatalogArchive(slug, {
538
+ unarchive: !!opts.unarchive,
539
+ yes: !!opts.yes,
540
+ force: !!opts.force,
541
+ json: !!opts.json,
542
+ });
543
+ });
544
+ apiCatalogCmd
545
+ .command('delete <slug>')
546
+ .description('Delete físico de uma API (IRREVERSÍVEL — confirmação + step-up MFA)')
547
+ .option('--yes', 'pula a confirmação interativa (modo script)')
548
+ .option('--force', 'alias de --yes')
549
+ .option('--dry-run', 'valida e mostra o efeito sem aplicar')
550
+ .option('--mfa-token <code>', 'código TOTP para step-up MFA')
551
+ .option('--json', 'saída em JSON')
552
+ .action(async (slug, opts) => {
553
+ const { runApiCatalogDelete } = await import('./commands/api-catalog.js');
554
+ await runApiCatalogDelete(slug, {
555
+ yes: !!opts.yes,
556
+ force: !!opts.force,
557
+ dryRun: !!opts.dryRun,
558
+ mfaToken: opts.mfaToken,
559
+ json: !!opts.json,
560
+ });
561
+ });
562
+ // ── neetru products ───────────────────────────────────────────────────
563
+ const productsCmd = program
564
+ .command('products')
565
+ .description('Control plane — registry interno de produtos SaaS (read-only)');
566
+ productsCmd
567
+ .command('list')
568
+ .description('Lista produtos internos (registry products/, filtro --status)')
569
+ .option('--status <status>', 'ativo | beta | descontinuado')
570
+ .option('--limit <n>', 'máximo de resultados (default 100, max 500)')
571
+ .option('--json', 'saída em JSON')
572
+ .action(async (opts) => {
573
+ const { runProductsList } = await import('./commands/products.js');
574
+ await runProductsList({ status: opts.status, limit: opts.limit, json: !!opts.json });
575
+ });
576
+ productsCmd
577
+ .command('publish <slug>')
578
+ .description('Publica um produto no catálogo público (published=true)')
579
+ .option('--json', 'saída em JSON')
580
+ .action(async (slug, opts) => {
581
+ const { runProductsPublish } = await import('./commands/products.js');
582
+ await runProductsPublish(slug, { json: !!opts.json });
583
+ });
584
+ productsCmd
585
+ .command('unpublish <slug>')
586
+ .description('Remove um produto do catálogo público (published=false)')
587
+ .option('--json', 'saída em JSON')
588
+ .action(async (slug, opts) => {
589
+ const { runProductsUnpublish } = await import('./commands/products.js');
590
+ await runProductsUnpublish(slug, { json: !!opts.json });
591
+ });
592
+ // ── neetru products db (Phase A — per-product DB isolation) ────────────
593
+ const productsDbCmd = productsCmd
594
+ .command('db')
595
+ .description('Bancos de dados isolados por produto (firestore-instance | cloud-sql-* | vm-*)');
596
+ productsDbCmd
597
+ .command('list')
598
+ .description('Lista bancos. Filtros: --product-id, --engine, --status')
599
+ .option('--product-id <id>')
600
+ .option('--engine <engine>')
601
+ .option('--status <status>')
602
+ .option('--json')
603
+ .action(async (opts) => {
604
+ const { runDbList } = await import('./commands/products-db.js');
605
+ await runDbList({
606
+ productId: opts.productId,
607
+ engine: opts.engine,
608
+ status: opts.status,
609
+ json: !!opts.json,
610
+ });
611
+ });
612
+ productsDbCmd
613
+ .command('engines')
614
+ .description('Lista engines suportados')
615
+ .option('--json')
616
+ .action(async (opts) => {
617
+ const { runDbEngines } = await import('./commands/products-db.js');
618
+ await runDbEngines({ json: !!opts.json });
619
+ });
620
+ productsDbCmd
621
+ .command('create')
622
+ .description('Cria um banco (Phase A: registra + audit; provisionamento real é Phase B)')
623
+ .requiredOption('--product-id <id>')
624
+ .requiredOption('--label <label>')
625
+ .requiredOption('--engine <engine>', 'firestore-instance|cloud-sql-postgres|cloud-sql-mysql|vm-postgres-single|vm-postgres-cluster|vm-mysql-single|vm-mysql-cluster')
626
+ .requiredOption('--environment <env>', 'dev|staging|production')
627
+ .option('--region <region>', 'região GCP', 'us-central1')
628
+ .option('--server-id <serverId>', 'target VM (obrigatório pra engines vm-*)')
629
+ .option('--replica-count <n>', 'pra engines *-cluster (default 2)')
630
+ .option('--json')
631
+ .action(async (opts) => {
632
+ const { runDbCreate } = await import('./commands/products-db.js');
633
+ await runDbCreate({
634
+ productId: opts.productId,
635
+ label: opts.label,
636
+ engine: opts.engine,
637
+ environment: opts.environment,
638
+ region: opts.region,
639
+ serverId: opts.serverId,
640
+ replicaCount: opts.replicaCount,
641
+ json: !!opts.json,
642
+ });
643
+ });
644
+ productsDbCmd
645
+ .command('get <id>')
646
+ .description('Detalhes de um banco')
647
+ .option('--json')
648
+ .action(async (id, opts) => {
649
+ const { runDbGet } = await import('./commands/products-db.js');
650
+ await runDbGet(id, { json: !!opts.json });
651
+ });
652
+ productsDbCmd
653
+ .command('status <id> <status>')
654
+ .description('Atualiza status (requested|provisioning|active|degraded|failed|archived)')
655
+ .option('--reason <text>')
656
+ .option('--json')
657
+ .action(async (id, status, opts) => {
658
+ const { runDbStatus } = await import('./commands/products-db.js');
659
+ await runDbStatus(id, status, { reason: opts.reason, json: !!opts.json });
660
+ });
661
+ productsDbCmd
662
+ .command('retry <id>')
663
+ .description('Re-enfileira provisionamento (status failed/degraded → requested)')
664
+ .option('--json')
665
+ .action(async (id, opts) => {
666
+ const { runDbRetry } = await import('./commands/products-db.js');
667
+ await runDbRetry(id, { json: !!opts.json });
668
+ });
669
+ productsDbCmd
670
+ .command('rotate <id>')
671
+ .description('Rotaciona credenciais (admin; Phase A: intent + audit)')
672
+ .option('--json')
673
+ .action(async (id, opts) => {
674
+ const { runDbRotate } = await import('./commands/products-db.js');
675
+ await runDbRotate(id, { json: !!opts.json });
676
+ });
677
+ productsDbCmd
678
+ .command('delete <id>')
679
+ .description('Arquiva o banco (soft-delete; admin)')
680
+ .option('--json')
681
+ .action(async (id, opts) => {
682
+ const { runDbDelete } = await import('./commands/products-db.js');
683
+ await runDbDelete(id, { json: !!opts.json });
684
+ });
685
+ // ── neetru logs ───────────────────────────────────────────────────────
686
+ program
687
+ .command('logs')
688
+ .description('Visualiza logs do workspace; suporta tail contínuo')
689
+ .option('--client-id <id>', 'oauthClientId do workspace')
690
+ .option('-f, --follow', 'tail contínuo (poll a cada 5s)')
691
+ .option('-n, --lines <count>', 'número de linhas a buscar', '50')
692
+ .option('--since <duration>', 'logs desde N atrás (ex: 30m, 2h, 1d)', '15m')
693
+ .option('--level <level>', 'filtrar por nível: info | warn | error | debug')
694
+ .option('--product <product>', 'v1.3 — filtrar por productId')
695
+ .option('--channel <channel>', 'v1.3 — filtrar por channel (stdout | stderr | app)')
696
+ .option('--correlation-id <id>', 'v1.3 — filtrar por correlationId (UUID)')
697
+ .action(async (opts) => {
698
+ const { runLogs } = await import('./commands/logs.js');
699
+ await runLogs({
700
+ clientId: opts.clientId,
701
+ follow: !!opts.follow,
702
+ lines: opts.lines,
703
+ since: opts.since,
704
+ level: opts.level,
705
+ product: opts.product,
706
+ channel: opts.channel,
707
+ correlationId: opts.correlationId,
708
+ });
709
+ });
710
+ // ── neetru validate ───────────────────────────────────────────────────
711
+ program
712
+ .command('validate')
713
+ .description('Health check da config local + conexão com Core')
714
+ .action(async () => {
715
+ const { runValidate } = await import('./commands/validate.js');
716
+ await runValidate();
717
+ });
718
+ // ── neetru open ───────────────────────────────────────────────────────
719
+ program
720
+ .command('open [target]')
721
+ .description('Abre uma página do painel Neetru no browser')
722
+ .option('--client-id <id>', 'oauthClientId do workspace (anexa em targets workspace-scoped)')
723
+ .action(async (target, opts) => {
724
+ const { runOpen } = await import('./commands/open.js');
725
+ await runOpen(target, { clientId: opts.clientId });
726
+ });
727
+ // ── neetru publish ────────────────────────────────────────────────────
728
+ program
729
+ .command('publish')
730
+ .description('Publica o produto no catálogo público da landing (public_products)')
731
+ .option('--draft', 'salva como rascunho (published=false)')
732
+ .option('--unpublish', 'remove o produto da landing (mantém o doc)')
733
+ .option('--slug <slug>', 'override do slug do neetru.config.json')
734
+ .option('--name <name>', 'override do nome')
735
+ .option('--tagline <tagline>', 'override do tagline')
736
+ .option('--description <description>', 'override da description')
737
+ .option('--icon-key <iconKey>', 'override do iconKey')
738
+ .option('--status <status>', 'override do status (live | soon | beta)')
739
+ .option('--order <order>', 'override do order (inteiro)')
740
+ .option('--cta-href <ctaHref>', 'override do ctaHref')
741
+ .option('--cta-label <ctaLabel>', 'override do ctaLabel')
742
+ .action(async (opts) => {
743
+ const { runPublish } = await import('./commands/publish.js');
744
+ await runPublish({
745
+ draft: !!opts.draft,
746
+ unpublish: !!opts.unpublish,
747
+ slug: opts.slug,
748
+ name: opts.name,
749
+ tagline: opts.tagline,
750
+ description: opts.description,
751
+ iconKey: opts.iconKey,
752
+ status: opts.status,
753
+ order: opts.order,
754
+ ctaHref: opts.ctaHref,
755
+ ctaLabel: opts.ctaLabel,
756
+ });
757
+ });
758
+ // ── neetru add ────────────────────────────────────────────────────────
759
+ program
760
+ .command('add <feature>')
761
+ .description('v1.3 — copia template (auth | billing | usage | users | support) pra src/lib/neetru/')
762
+ .option('--force', 'sobrescrever arquivos existentes')
763
+ .action(async (feature, opts) => {
764
+ const { runAdd } = await import('./commands/add.js');
765
+ await runAdd({ feature, force: !!opts.force });
766
+ });
767
+ // ── neetru mocks ──────────────────────────────────────────────────────
768
+ const mocksCmd = program
769
+ .command('mocks')
770
+ .description('v1.3 — gerenciar fixtures dev (NEETRU_ENV=dev)');
771
+ mocksCmd
772
+ .command('reset')
773
+ .description('Reseta .neetru/dev-fixtures.json para {}')
774
+ .action(async () => {
775
+ const { runMocksReset } = await import('./commands/mocks.js');
776
+ await runMocksReset();
777
+ });
778
+ // ── neetru env ────────────────────────────────────────────────────────
779
+ const envCmd = program
780
+ .command('env')
781
+ .description('v1.3 — gerenciar configuração NEETRU_ENV no .env.local');
782
+ envCmd
783
+ .command('switch <target>')
784
+ .description('Alterna NEETRU_ENV: dev | workspace | production')
785
+ .action(async (target) => {
786
+ const { runEnvSwitch } = await import('./commands/env.js');
787
+ await runEnvSwitch({ target });
788
+ });
789
+ // ── neetru db ─────────────────────────────────────────────────────────
790
+ const dbCmd = program
791
+ .command('db')
792
+ .description('v1.4 — gerência de schema/migrations/seed do produto');
793
+ dbCmd
794
+ .command('init')
795
+ .description('Cria manifest stub a partir de neetru.config.json')
796
+ .option('--out <path>', 'caminho do manifest (default db/schema.manifest.json)')
797
+ .option('--force', 'sobrescreve se já existir')
798
+ .action(async (opts) => {
799
+ const { runDbInit } = await import('./commands/db.js');
800
+ await runDbInit({ out: opts.out, force: !!opts.force });
801
+ });
802
+ dbCmd
803
+ .command('migrate <toVersion>')
804
+ .description('Aplica migration via Core (chama runMigrations Sprint 8)')
805
+ .option('--from <fromVersion>', 'versão atual (default: schemaVersion do config)')
806
+ .action(async (toVersion, opts) => {
807
+ const { runDbMigrate } = await import('./commands/db.js');
808
+ await runDbMigrate({ toVersion, fromVersion: opts.from });
809
+ });
810
+ dbCmd
811
+ .command('seed')
812
+ .description('Executa db/seed.ts no projeto (NEETRU_ENV=dev)')
813
+ .option('--script <path>', 'caminho custom do script de seed')
814
+ .action(async (opts) => {
815
+ const { runDbSeed } = await import('./commands/db.js');
816
+ await runDbSeed({ script: opts.script });
817
+ });
818
+ // ── neetru fn ─────────────────────────────────────────────────────────
819
+ const fnCmd = program
820
+ .command('fn')
821
+ .description('v1.4 — gestão de Functions/APIs do produto');
822
+ fnCmd
823
+ .command('deploy')
824
+ .description('Registra nova versão da API no catálogo neetru-apis (Sprint 7 stub)')
825
+ .option('--version <v>', 'versão da API (default: apiVersion do config ou v1)')
826
+ .option('--channel <channel>', 'stable | beta | alpha', 'stable')
827
+ .option('--spec <path>', 'caminho de OpenAPI/JSON schema')
828
+ .action(async (opts) => {
829
+ const { runFnDeploy } = await import('./commands/fn.js');
830
+ await runFnDeploy({ version: opts.version, channel: opts.channel, spec: opts.spec });
831
+ });
832
+ // ── neetru promote ────────────────────────────────────────────────────
833
+ program
834
+ .command('promote')
835
+ .description('v1.4 — solicita promotion entre ambientes (staff-gated). dev→workspace→beta→prod')
836
+ .requiredOption('--from <env>', 'dev | workspace | beta | prod')
837
+ .requiredOption('--to <env>', 'dev | workspace | beta | prod')
838
+ .option('--product <slug>', 'override do slug do neetru.config.json')
839
+ .option('--reason <text>', 'razão livre da promotion')
840
+ .option('--from-revision <rev>', 'revisão Cloud Run atual (rollback target)')
841
+ .option('--to-revision <rev>', 'revisão Cloud Run novo (target do rollout)')
842
+ .action(async (opts) => {
843
+ const { runPromote } = await import('./commands/promote.js');
844
+ await runPromote({
845
+ from: opts.from,
846
+ to: opts.to,
847
+ product: opts.product,
848
+ reason: opts.reason,
849
+ fromRevision: opts.fromRevision,
850
+ toRevision: opts.toRevision,
851
+ });
852
+ });
853
+ // ── neetru doctor ─────────────────────────────────────────────────────
854
+ program
855
+ .command('doctor')
856
+ .description('v2.0 — diagnóstico do CLI: token, core, schema, NEETRU_ENV, version')
857
+ .option('--json', 'saída em JSON (machine-readable)')
858
+ .action(async (opts) => {
859
+ const { runDoctor } = await import('./commands/doctor.js');
860
+ await runDoctor({ json: !!opts.json });
861
+ });
862
+ // ── neetru upgrade ────────────────────────────────────────────────────
863
+ program
864
+ .command('upgrade')
865
+ .description('v2.0 — verifica latest no npm e exibe instrução de upgrade')
866
+ .option('--json', 'saída em JSON')
867
+ .action(async (opts) => {
868
+ const { runUpgrade } = await import('./commands/upgrade.js');
869
+ await runUpgrade({ json: !!opts.json });
870
+ });
871
+ // ── neetru autocomplete ───────────────────────────────────────────────
872
+ program
873
+ .command('autocomplete <shell>')
874
+ .description('v2.0 — gera script de shell completion (bash | zsh | pwsh)')
875
+ .action(async (shell) => {
876
+ const { runAutocomplete } = await import('./commands/autocomplete.js');
877
+ await runAutocomplete(shell);
878
+ });
879
+ // ── neetru agent release ──────────────────────────────────────────────
880
+ const agentCmd = program
881
+ .command('agent')
882
+ .description('Operações sobre o binário do Neetru Agent (Linux daemon)');
883
+ agentCmd
884
+ .command('release')
885
+ .description('Registra nova release do agente em agent_releases (Firestore)')
886
+ .requiredOption('--version <semver>', 'versão (ex: 1.2.0 ou 1.2.0-beta.1)')
887
+ .option('--channel <channel>', 'stable | beta | canary (default: beta)', 'beta')
888
+ .option('--changelog <text|@file>', 'changelog markdown inline ou "@path" pra ler de arquivo')
889
+ .option('--min-core <revision>', 'min revision Cloud Run requerida (ex: 00037-qm8)')
890
+ .option('--binary <arch=path>', 'binário local (sha256+size computados, URL canônica GCS auto-derivada). Repetível.', (val, prev = []) => [...prev, val])
891
+ .option('--artifact <arch=URL@sha256@sizeBytes>', 'artifact com URL/sha256/size explícitos (não toca FS). Repetível.', (val, prev = []) => [...prev, val])
892
+ .option('--dry-run', 'imprime payload sem enviar')
893
+ .action(async (opts) => {
894
+ const { runAgentRelease } = await import('./commands/agent-release.js');
895
+ await runAgentRelease({
896
+ version: opts.version,
897
+ channel: opts.channel,
898
+ changelog: opts.changelog,
899
+ minCore: opts.minCore,
900
+ binary: opts.binary,
901
+ artifact: opts.artifact,
902
+ dryRun: !!opts.dryRun,
903
+ });
904
+ });
905
+ agentCmd
906
+ .command('yank <version>')
907
+ .description('Soft-revoke de uma release do agente (destrutivo top-tier — confirmação + step-up MFA)')
908
+ .requiredOption('--reason <text>', 'motivo do yank (mínimo 5 caracteres)')
909
+ .option('--yes', 'pula a confirmação interativa (modo script)')
910
+ .option('--force', 'alias de --yes')
911
+ .option('--dry-run', 'valida e mostra o efeito sem aplicar')
912
+ .option('--mfa-token <code>', 'código TOTP para step-up MFA')
913
+ .option('--json', 'saída em JSON')
914
+ .action(async (version, opts) => {
915
+ const { runAgentYank } = await import('./commands/agent-write.js');
916
+ await runAgentYank(version, {
917
+ reason: opts.reason,
918
+ yes: !!opts.yes,
919
+ force: !!opts.force,
920
+ dryRun: !!opts.dryRun,
921
+ mfaToken: opts.mfaToken,
922
+ json: !!opts.json,
923
+ });
924
+ });
925
+ const agentCanaryCmd = agentCmd
926
+ .command('canary')
927
+ .description('Controla o canary rollout de releases do agente');
928
+ agentCanaryCmd
929
+ .command('start <version>')
930
+ .description('Inicia o canary rollout de uma release (phase1 = 5%)')
931
+ .option('--json', 'saída em JSON')
932
+ .action(async (version, opts) => {
933
+ const { runAgentCanaryStart } = await import('./commands/agent-write.js');
934
+ await runAgentCanaryStart(version, { json: !!opts.json });
935
+ });
936
+ agentCanaryCmd
937
+ .command('rollback <version>')
938
+ .description('Rollback do canary de uma release (destrutivo top-tier — confirmação + step-up MFA)')
939
+ .requiredOption('--reason <text>', 'motivo do rollback (mínimo 5 caracteres)')
940
+ .option('--yes', 'pula a confirmação interativa (modo script)')
941
+ .option('--force', 'alias de --yes')
942
+ .option('--dry-run', 'valida e mostra o efeito sem aplicar')
943
+ .option('--mfa-token <code>', 'código TOTP para step-up MFA')
944
+ .option('--json', 'saída em JSON')
945
+ .action(async (version, opts) => {
946
+ const { runAgentCanaryRollback } = await import('./commands/agent-write.js');
947
+ await runAgentCanaryRollback(version, {
948
+ reason: opts.reason,
949
+ yes: !!opts.yes,
950
+ force: !!opts.force,
951
+ dryRun: !!opts.dryRun,
952
+ mfaToken: opts.mfaToken,
953
+ json: !!opts.json,
954
+ });
955
+ });
956
+ // ── neetru support ───────────────────────────────────────────────────
957
+ const supportCmd = program
958
+ .command('support')
959
+ .description('Support tickets — inbox staff via CLI (F5 §4.2.6)');
960
+ const supportTicketsCmd = supportCmd
961
+ .command('tickets')
962
+ .description('Operações sobre support_tickets');
963
+ supportTicketsCmd
964
+ .command('list')
965
+ .description('Lista tickets com filtros')
966
+ .option('--status <s>', 'open | in_progress | resolved | closed')
967
+ .option('--severity <s>', 'sev1 | sev2 | sev3 | sev4')
968
+ .option('--product <id>', 'filtra por productId')
969
+ .option('--customer <id>', 'filtra por customerId/tenantId')
970
+ .option('--breached', 'apenas tickets com SLA estourado')
971
+ .option('--limit <n>', 'máximo de resultados (default 100, max 500)')
972
+ .option('--json', 'saída em JSON')
973
+ .action(async (opts) => {
974
+ const { runSupportTicketsList } = await import('./commands/support.js');
975
+ await runSupportTicketsList({
976
+ status: opts.status,
977
+ severity: opts.severity,
978
+ product: opts.product,
979
+ customer: opts.customer,
980
+ breached: !!opts.breached,
981
+ limit: opts.limit,
982
+ json: !!opts.json,
983
+ });
984
+ });
985
+ supportTicketsCmd
986
+ .command('describe <id>')
987
+ .description('Detalha um ticket + thread de mensagens')
988
+ .option('--json', 'saída em JSON')
989
+ .action(async (id, opts) => {
990
+ const { runSupportTicketsDescribe } = await import('./commands/support.js');
991
+ await runSupportTicketsDescribe(id, { json: !!opts.json });
992
+ });
993
+ supportTicketsCmd
994
+ .command('reply <id>')
995
+ .description('Anexa mensagem staff ao ticket (transiciona open → in_progress se aplicável)')
996
+ .requiredOption('--message <text>', 'corpo da mensagem')
997
+ .option('--json', 'saída em JSON')
998
+ .action(async (id, opts) => {
999
+ const { runSupportTicketsReply } = await import('./commands/support.js');
1000
+ await runSupportTicketsReply(id, { message: opts.message, json: !!opts.json });
1001
+ });
1002
+ supportTicketsCmd
1003
+ .command('assign <id>')
1004
+ .description('Atribui ticket a um staff (uid)')
1005
+ .requiredOption('--to <staffUid>', 'uid do staff destino')
1006
+ .option('--json', 'saída em JSON')
1007
+ .action(async (id, opts) => {
1008
+ const { runSupportTicketsAssign } = await import('./commands/support.js');
1009
+ await runSupportTicketsAssign(id, { to: opts.to, json: !!opts.json });
1010
+ });
1011
+ supportTicketsCmd
1012
+ .command('status <id>')
1013
+ .description('Transiciona status do ticket (FSM)')
1014
+ .requiredOption('--to <status>', 'open | in_progress | resolved | closed')
1015
+ .option('--json', 'saída em JSON')
1016
+ .action(async (id, opts) => {
1017
+ const { runSupportTicketsStatus } = await import('./commands/support.js');
1018
+ await runSupportTicketsStatus(id, { to: opts.to, json: !!opts.json });
1019
+ });
1020
+ // ── neetru dns ───────────────────────────────────────────────────────
1021
+ const dnsCmd = program.command('dns').description('Cloud DNS — managed zones');
1022
+ dnsCmd
1023
+ .command('zones')
1024
+ .description('Operações sobre managed zones')
1025
+ .command('list')
1026
+ .description('Lista managed zones do projeto')
1027
+ .option('--json', 'saída em JSON')
1028
+ .action(async (opts) => {
1029
+ const { runDnsZonesList } = await import('./commands/infra-read.js');
1030
+ await runDnsZonesList({ json: !!opts.json });
1031
+ });
1032
+ // ── neetru hosting ───────────────────────────────────────────────────
1033
+ program
1034
+ .command('hosting')
1035
+ .description('Customer domains — hosting setup')
1036
+ .command('list')
1037
+ .description('Lista customer domains com scope de tenant aplicado')
1038
+ .option('--json', 'saída em JSON')
1039
+ .action(async (opts) => {
1040
+ const { runHostingList } = await import('./commands/infra-read.js');
1041
+ await runHostingList({ json: !!opts.json });
1042
+ });
1043
+ // ── neetru builds ────────────────────────────────────────────────────
1044
+ program
1045
+ .command('builds')
1046
+ .description('Cloud Build — builds live (status + duração)')
1047
+ .command('list')
1048
+ .description('Lista Cloud Builds recentes (global + regional)')
1049
+ .option('--json', 'saída em JSON')
1050
+ .action(async (opts) => {
1051
+ const { runBuildsList } = await import('./commands/infra-read.js');
1052
+ await runBuildsList({ json: !!opts.json });
1053
+ });
1054
+ // ── neetru dr ────────────────────────────────────────────────────────
1055
+ // Disaster Recovery — listar exports + restore assistido (admin + step-up MFA).
1056
+ const drCmd = program
1057
+ .command('dr')
1058
+ .description('Disaster Recovery — exports + restore (admin only, RUNBOOK_DR_DRILL)');
1059
+ const drExportsCmd = drCmd
1060
+ .command('exports')
1061
+ .description('Operações sobre exports de Firestore em gs://neetru-backups');
1062
+ drExportsCmd
1063
+ .command('list')
1064
+ .description('Lista exports disponíveis (sort: mais recente primeiro)')
1065
+ .option('--json', 'saída em JSON')
1066
+ .action(async (opts) => {
1067
+ const { runDrExportsList } = await import('./commands/dr.js');
1068
+ await runDrExportsList({ json: !!opts.json });
1069
+ });
1070
+ drCmd
1071
+ .command('restore')
1072
+ .description('Dispara restore Firestore (IRREVERSÍVEL no destino — destrutivo top-tier)')
1073
+ .requiredOption('--gcs-path <path>', 'gs://neetru-backups/firestore-exports/<stamp>/')
1074
+ .requiredOption('--target-project <project>', 'projeto GCP de destino (deve ser STAGING)')
1075
+ .option('--yes', 'pula a confirmação interativa (modo script)')
1076
+ .option('--force', 'alias de --yes')
1077
+ .option('--dry-run', 'valida e mostra o efeito sem aplicar')
1078
+ .option('--mfa-token <code>', 'código TOTP para step-up MFA')
1079
+ .option('--json', 'saída em JSON')
1080
+ .action(async (opts) => {
1081
+ const { runDrRestore } = await import('./commands/dr.js');
1082
+ await runDrRestore({
1083
+ gcsPath: opts.gcsPath,
1084
+ targetProject: opts.targetProject,
1085
+ yes: !!opts.yes,
1086
+ force: !!opts.force,
1087
+ dryRun: !!opts.dryRun,
1088
+ mfaToken: opts.mfaToken,
1089
+ json: !!opts.json,
1090
+ });
1091
+ });
1092
+ // ── parse ─────────────────────────────────────────────────────────────
1093
+ program.parse(process.argv);
1094
+ if (!process.argv.slice(2).length) {
1095
+ log.banner();
1096
+ program.outputHelp();
1097
+ }
40
1098
  //# sourceMappingURL=index.js.map