@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.
- package/CHANGELOG.md +136 -0
- package/README.md +109 -152
- package/dist/commands/add.d.ts +8 -3
- package/dist/commands/add.js +70 -143
- package/dist/commands/add.js.map +1 -1
- 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/ai.d.ts +4 -0
- package/dist/commands/ai.js +88 -0
- package/dist/commands/ai.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/autocomplete.d.ts +7 -0
- package/dist/commands/autocomplete.js +107 -0
- package/dist/commands/autocomplete.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.d.ts +18 -0
- package/dist/commands/build.js +295 -0
- package/dist/commands/build.js.map +1 -0
- 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/config.d.ts +3 -0
- package/dist/commands/config.js +70 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/db.d.ts +14 -0
- package/dist/commands/db.js +187 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/deploy.d.ts +23 -3
- package/dist/commands/deploy.js +530 -177
- 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/doctor.d.ts +27 -0
- package/dist/commands/doctor.js +211 -0
- package/dist/commands/doctor.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/env.d.ts +15 -0
- package/dist/commands/env.js +56 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/fn.d.ts +6 -0
- package/dist/commands/fn.js +87 -0
- package/dist/commands/fn.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.d.ts +10 -3
- package/dist/commands/init.js +275 -142
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts +6 -3
- package/dist/commands/login.js +222 -92
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +28 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/logs.d.ts +14 -3
- package/dist/commands/logs.js +132 -106
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/mocks.d.ts +5 -0
- package/dist/commands/mocks.js +23 -0
- package/dist/commands/mocks.js.map +1 -0
- package/dist/commands/open.d.ts +4 -3
- package/dist/commands/open.js +53 -85
- package/dist/commands/open.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/promote.d.ts +9 -0
- package/dist/commands/promote.js +114 -0
- package/dist/commands/promote.js.map +1 -0
- package/dist/commands/publish.d.ts +14 -0
- package/dist/commands/publish.js +180 -0
- package/dist/commands/publish.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/status.d.ts +5 -3
- package/dist/commands/status.js +91 -93
- package/dist/commands/status.js.map +1 -1
- 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/upgrade.d.ts +12 -0
- package/dist/commands/upgrade.js +77 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/validate.d.ts +1 -3
- package/dist/commands/validate.js +83 -91
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/whoami.d.ts +5 -3
- package/dist/commands/whoami.js +76 -28
- package/dist/commands/whoami.js.map +1 -1
- 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.d.ts +0 -1
- package/dist/index.js +1094 -36
- package/dist/index.js.map +1 -1
- package/dist/lib/ai/context.d.ts +11 -0
- package/dist/lib/ai/context.js +112 -0
- package/dist/lib/ai/context.js.map +1 -0
- package/dist/lib/ai/orchestrator.d.ts +10 -0
- package/dist/lib/ai/orchestrator.js +92 -0
- package/dist/lib/ai/orchestrator.js.map +1 -0
- package/dist/lib/api-client.d.ts +39 -0
- package/dist/lib/api-client.js +185 -0
- package/dist/lib/api-client.js.map +1 -0
- package/dist/lib/auth.d.ts +15 -0
- package/dist/lib/auth.js +98 -0
- package/dist/lib/auth.js.map +1 -0
- 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 +165 -0
- package/dist/lib/config-schema.js +57 -0
- package/dist/lib/config-schema.js.map +1 -0
- package/dist/lib/config.d.ts +15 -0
- package/dist/lib/config.js +33 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/render.d.ts +16 -0
- package/dist/lib/render.js +74 -0
- package/dist/lib/render.js.map +1 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +35 -33
- package/templates/auth/callback.ts +22 -0
- package/templates/auth/sign-in.tsx +41 -0
- package/templates/billing/checkout.ts +22 -0
- package/templates/billing/page.tsx +43 -0
- package/templates/support/ticket-form.tsx +68 -0
- package/templates/usage/track.ts +30 -0
- package/templates/users/profile.tsx +43 -0
- package/LICENSE +0 -21
- package/dist/commands/add.d.ts.map +0 -1
- package/dist/commands/deploy.d.ts.map +0 -1
- package/dist/commands/generate-types.d.ts +0 -3
- package/dist/commands/generate-types.d.ts.map +0 -1
- package/dist/commands/generate-types.js +0 -150
- package/dist/commands/generate-types.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/logs.d.ts.map +0 -1
- package/dist/commands/open.d.ts.map +0 -1
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/whoami.d.ts.map +0 -1
- package/dist/config.d.ts +0 -14
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -83
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/scaffold/auth.d.ts +0 -3
- package/dist/scaffold/auth.d.ts.map +0 -1
- package/dist/scaffold/auth.js +0 -228
- package/dist/scaffold/auth.js.map +0 -1
- package/dist/scaffold/billing.d.ts +0 -3
- package/dist/scaffold/billing.d.ts.map +0 -1
- package/dist/scaffold/billing.js +0 -184
- package/dist/scaffold/billing.js.map +0 -1
- package/dist/scaffold/usage.d.ts +0 -3
- package/dist/scaffold/usage.d.ts.map +0 -1
- package/dist/scaffold/usage.js +0 -173
- package/dist/scaffold/usage.js.map +0 -1
- package/dist/scaffold/users.d.ts +0 -3
- package/dist/scaffold/users.d.ts.map +0 -1
- package/dist/scaffold/users.js +0 -135
- package/dist/scaffold/users.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,40 +1,1098 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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(
|
|
23
|
-
'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|