@neetru/cli 2.0.0 → 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.
- package/dist/commands/agent-release.d.ts +13 -0
- package/dist/commands/agent-release.js +204 -0
- package/dist/commands/agent-release.js.map +1 -0
- package/dist/commands/agent-write.d.ts +12 -0
- package/dist/commands/agent-write.js +94 -0
- package/dist/commands/agent-write.js.map +1 -0
- package/dist/commands/api-catalog.d.ts +20 -0
- package/dist/commands/api-catalog.js +126 -0
- package/dist/commands/api-catalog.js.map +1 -0
- package/dist/commands/audit.d.ts +8 -0
- package/dist/commands/audit.js +69 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/billing.d.ts +6 -0
- package/dist/commands/billing.js +69 -0
- package/dist/commands/billing.js.map +1 -0
- package/dist/commands/build.js +10 -3
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/cloud-run.d.ts +11 -0
- package/dist/commands/cloud-run.js +87 -0
- package/dist/commands/cloud-run.js.map +1 -0
- package/dist/commands/deploy.d.ts +7 -0
- package/dist/commands/deploy.js +150 -17
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/deployments.d.ts +11 -0
- package/dist/commands/deployments.js +69 -0
- package/dist/commands/deployments.js.map +1 -0
- package/dist/commands/dr.d.ts +11 -0
- package/dist/commands/dr.js +79 -0
- package/dist/commands/dr.js.map +1 -0
- package/dist/commands/infra-read.d.ts +9 -0
- package/dist/commands/infra-read.js +113 -0
- package/dist/commands/infra-read.js.map +1 -0
- package/dist/commands/init.js +91 -27
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/products-db.d.ts +37 -0
- package/dist/commands/products-db.js +230 -0
- package/dist/commands/products-db.js.map +1 -0
- package/dist/commands/products.d.ts +12 -0
- package/dist/commands/products.js +97 -0
- package/dist/commands/products.js.map +1 -0
- package/dist/commands/servers.d.ts +23 -0
- package/dist/commands/servers.js +166 -0
- package/dist/commands/servers.js.map +1 -0
- package/dist/commands/support.d.ts +25 -0
- package/dist/commands/support.js +184 -0
- package/dist/commands/support.js.map +1 -0
- package/dist/commands/surface-status.d.ts +5 -0
- package/dist/commands/surface-status.js +63 -0
- package/dist/commands/surface-status.js.map +1 -0
- package/dist/commands/tenants.d.ts +34 -0
- package/dist/commands/tenants.js +179 -0
- package/dist/commands/tenants.js.map +1 -0
- package/dist/commands/workspaces.d.ts +15 -0
- package/dist/commands/workspaces.js +72 -0
- package/dist/commands/workspaces.js.map +1 -0
- package/dist/index.js +761 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/api-client.d.ts +18 -0
- package/dist/lib/api-client.js +124 -4
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/cli-read.d.ts +13 -0
- package/dist/lib/cli-read.js +103 -0
- package/dist/lib/cli-read.js.map +1 -0
- package/dist/lib/cli-write.d.ts +47 -0
- package/dist/lib/cli-write.js +137 -0
- package/dist/lib/cli-write.js.map +1 -0
- package/dist/lib/config-schema.d.ts +10 -10
- package/dist/lib/render.d.ts +16 -0
- package/dist/lib/render.js +74 -0
- package/dist/lib/render.js.map +1 -0
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -110,6 +110,8 @@ program
|
|
|
110
110
|
.option('--artifact-url <url>', 'URL pública/signed do artifact (GCS, S3)')
|
|
111
111
|
.option('--artifact-sha256 <sha>', 'sha256 do artifact (par com --artifact-url)')
|
|
112
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)')
|
|
113
115
|
.option('--non-interactive', 'falha em vez de perguntar (modo CI)')
|
|
114
116
|
.action(async (opts) => {
|
|
115
117
|
const { runDeploy } = await import('./commands/deploy.js');
|
|
@@ -125,18 +127,560 @@ program
|
|
|
125
127
|
artifactUrl: opts.artifactUrl,
|
|
126
128
|
artifactSha256: opts.artifactSha256,
|
|
127
129
|
envFile: opts.envFile,
|
|
130
|
+
env: opts.env,
|
|
131
|
+
localArtifact: !!opts.localArtifact,
|
|
128
132
|
nonInteractive: !!opts.nonInteractive,
|
|
129
133
|
});
|
|
130
134
|
});
|
|
131
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).
|
|
132
138
|
program
|
|
133
139
|
.command('status')
|
|
134
|
-
.description('
|
|
135
|
-
.option('--client-id <id>', 'oauthClientId do workspace (
|
|
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)')
|
|
136
142
|
.option('--json', 'saída em JSON')
|
|
137
143
|
.action(async (opts) => {
|
|
138
|
-
const {
|
|
139
|
-
await
|
|
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 });
|
|
140
684
|
});
|
|
141
685
|
// ── neetru logs ───────────────────────────────────────────────────────
|
|
142
686
|
program
|
|
@@ -332,6 +876,219 @@ program
|
|
|
332
876
|
const { runAutocomplete } = await import('./commands/autocomplete.js');
|
|
333
877
|
await runAutocomplete(shell);
|
|
334
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
|
+
});
|
|
335
1092
|
// ── parse ─────────────────────────────────────────────────────────────
|
|
336
1093
|
program.parse(process.argv);
|
|
337
1094
|
if (!process.argv.slice(2).length) {
|