@neetru/cli 2.7.5 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +316 -220
  2. package/README.md +137 -137
  3. package/dist/cli-kit/format.d.ts +49 -0
  4. package/dist/cli-kit/format.js +88 -0
  5. package/dist/cli-kit/format.js.map +1 -0
  6. package/dist/cli-kit/glyphs.d.ts +22 -0
  7. package/dist/cli-kit/glyphs.js +22 -0
  8. package/dist/cli-kit/glyphs.js.map +1 -0
  9. package/dist/cli-kit/index.d.ts +13 -0
  10. package/dist/cli-kit/index.js +12 -0
  11. package/dist/cli-kit/index.js.map +1 -0
  12. package/dist/cli-kit/palette.d.ts +10 -0
  13. package/dist/cli-kit/palette.js +36 -0
  14. package/dist/cli-kit/palette.js.map +1 -0
  15. package/dist/commands/ai.js +8 -8
  16. package/dist/commands/autocomplete.js +34 -34
  17. package/dist/commands/bug.d.ts +87 -0
  18. package/dist/commands/bug.js +419 -0
  19. package/dist/commands/bug.js.map +1 -0
  20. package/dist/commands/customers.d.ts +17 -0
  21. package/dist/commands/customers.js +160 -0
  22. package/dist/commands/customers.js.map +1 -0
  23. package/dist/commands/db.d.ts +91 -7
  24. package/dist/commands/db.js +898 -123
  25. package/dist/commands/db.js.map +1 -1
  26. package/dist/commands/deploy.d.ts +5 -0
  27. package/dist/commands/deploy.js +68 -0
  28. package/dist/commands/deploy.js.map +1 -1
  29. package/dist/commands/dev.d.ts +68 -0
  30. package/dist/commands/dev.js +345 -0
  31. package/dist/commands/dev.js.map +1 -0
  32. package/dist/commands/docs.d.ts +4 -0
  33. package/dist/commands/docs.js +99 -7
  34. package/dist/commands/docs.js.map +1 -1
  35. package/dist/commands/doctor.js +4 -1
  36. package/dist/commands/doctor.js.map +1 -1
  37. package/dist/commands/init.js +121 -121
  38. package/dist/commands/marketplace.d.ts +36 -0
  39. package/dist/commands/marketplace.js +584 -0
  40. package/dist/commands/marketplace.js.map +1 -0
  41. package/dist/commands/new.d.ts +6 -0
  42. package/dist/commands/new.js +220 -40
  43. package/dist/commands/new.js.map +1 -1
  44. package/dist/commands/open.d.ts +8 -0
  45. package/dist/commands/open.js +61 -13
  46. package/dist/commands/open.js.map +1 -1
  47. package/dist/commands/products-db.d.ts +1 -1
  48. package/dist/commands/products-db.js +17 -4
  49. package/dist/commands/products-db.js.map +1 -1
  50. package/dist/commands/products.d.ts +23 -0
  51. package/dist/commands/products.js +39 -1
  52. package/dist/commands/products.js.map +1 -1
  53. package/dist/commands/tenants.js +15 -0
  54. package/dist/commands/tenants.js.map +1 -1
  55. package/dist/commands/ui.d.ts +1 -1
  56. package/dist/commands/ui.js +172 -2
  57. package/dist/commands/ui.js.map +1 -1
  58. package/dist/commands/workspaces.d.ts +10 -1
  59. package/dist/commands/workspaces.js +136 -22
  60. package/dist/commands/workspaces.js.map +1 -1
  61. package/dist/index.js +532 -44
  62. package/dist/index.js.map +1 -1
  63. package/dist/lib/ai/context.js +90 -90
  64. package/dist/lib/config-schema.d.ts +8 -8
  65. package/dist/lib/db-local/db-json.d.ts +63 -0
  66. package/dist/lib/db-local/db-json.js +189 -0
  67. package/dist/lib/db-local/db-json.js.map +1 -0
  68. package/dist/lib/db-local/env.d.ts +26 -0
  69. package/dist/lib/db-local/env.js +64 -0
  70. package/dist/lib/db-local/env.js.map +1 -0
  71. package/dist/lib/db-local/fingerprint.d.ts +8 -0
  72. package/dist/lib/db-local/fingerprint.js +28 -0
  73. package/dist/lib/db-local/fingerprint.js.map +1 -0
  74. package/dist/lib/db-local/index.d.ts +15 -0
  75. package/dist/lib/db-local/index.js +14 -0
  76. package/dist/lib/db-local/index.js.map +1 -0
  77. package/dist/lib/db-pipeline/build-deps.d.ts +14 -0
  78. package/dist/lib/db-pipeline/build-deps.js +158 -0
  79. package/dist/lib/db-pipeline/build-deps.js.map +1 -0
  80. package/dist/lib/db-pipeline/errors.d.ts +29 -0
  81. package/dist/lib/db-pipeline/errors.js +29 -0
  82. package/dist/lib/db-pipeline/errors.js.map +1 -0
  83. package/dist/lib/db-pipeline/index.d.ts +26 -0
  84. package/dist/lib/db-pipeline/index.js +25 -0
  85. package/dist/lib/db-pipeline/index.js.map +1 -0
  86. package/dist/lib/db-pipeline/pipeline.d.ts +13 -0
  87. package/dist/lib/db-pipeline/pipeline.js +119 -0
  88. package/dist/lib/db-pipeline/pipeline.js.map +1 -0
  89. package/dist/lib/db-pipeline/rehearse.d.ts +99 -0
  90. package/dist/lib/db-pipeline/rehearse.js +219 -0
  91. package/dist/lib/db-pipeline/rehearse.js.map +1 -0
  92. package/dist/lib/db-pipeline/types.d.ts +112 -0
  93. package/dist/lib/db-pipeline/types.js +20 -0
  94. package/dist/lib/db-pipeline/types.js.map +1 -0
  95. package/dist/lib/pickers.d.ts +12 -0
  96. package/dist/lib/pickers.js +34 -0
  97. package/dist/lib/pickers.js.map +1 -1
  98. package/package.json +66 -62
  99. package/templates/auth/callback.ts +22 -22
  100. package/templates/auth/sign-in.tsx +41 -41
  101. package/templates/billing/checkout.ts +22 -22
  102. package/templates/billing/page.tsx +43 -43
  103. package/templates/support/ticket-form.tsx +68 -68
  104. package/templates/usage/track.ts +30 -30
  105. package/templates/users/profile.tsx +43 -43
package/dist/index.js CHANGED
@@ -2,6 +2,16 @@
2
2
  import { Command } from 'commander';
3
3
  import { log } from './utils/logger.js';
4
4
  import { CLI_VERSION } from './version.js';
5
+ // pdv #6 fix: fecha stdin antes de qualquer process.exit() para evitar a
6
+ // asserção libuv "!(handle->flags & UV_HANDLE_CLOSING)" no Windows.
7
+ // stdin fica aberto como handle libuv quando inquirer/ora o pegam; destruir
8
+ // antes do exit garante que o loop de eventos drene limpo.
9
+ process.on('exit', () => {
10
+ try {
11
+ process.stdin.destroy();
12
+ }
13
+ catch { /* silencioso — handle pode já estar fechado */ }
14
+ });
5
15
  const program = new Command();
6
16
  program
7
17
  .name('neetru')
@@ -56,6 +66,7 @@ program
56
66
  .option('--customer <customerId>', 'customerId dono do workspace (default: self)')
57
67
  .option('--skip-init', 'pula o scaffold local')
58
68
  .option('--skip-open', 'pula a abertura do browser')
69
+ .option('--skip-db-init', 'pula o 5º passo de inicialização do banco de dados')
59
70
  .option('--json', 'saída em JSON (machine-readable)')
60
71
  .action(async (name, opts) => {
61
72
  const { runNewMacro } = await import('./commands/new.js');
@@ -66,6 +77,7 @@ program
66
77
  customer: opts.customer,
67
78
  skipInit: !!opts.skipInit,
68
79
  skipOpen: !!opts.skipOpen,
80
+ skipDbInit: !!opts.skipDbInit,
69
81
  json: !!opts.json,
70
82
  });
71
83
  });
@@ -150,6 +162,19 @@ program
150
162
  const { runWhoami } = await import('./commands/whoami.js');
151
163
  await runWhoami({ json: !!opts.json });
152
164
  });
165
+ // ── neetru dev ────────────────────────────────────────────────────────
166
+ // Banco local Docker + watch de schema (M1 GAP-M1-4).
167
+ // "salvar = aplicado" — cada save no schema roda o pipeline neetru db apply
168
+ // contra o banco dev-local do produto.
169
+ program
170
+ .command('dev')
171
+ .description('Rodar banco Docker local + watch de schema (dev-local)')
172
+ .option('--db <name>', 'banco alvo do .neetru/db.json (default: único registrado)')
173
+ .option('--schema <path>', 'caminho do arquivo de schema (default: db/schema.ts)')
174
+ .action(async (opts) => {
175
+ const { runDev } = await import('./commands/dev.js');
176
+ await runDev({ db: opts.db, schema: opts.schema });
177
+ });
153
178
  // ── neetru build ──────────────────────────────────────────────────────
154
179
  program
155
180
  .command('build')
@@ -189,6 +214,7 @@ program
189
214
  .option('--env <env>', 'ambiente alvo: dev | staging | prod', 'dev')
190
215
  .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)')
191
216
  .option('--non-interactive', 'falha em vez de perguntar (modo CI)')
217
+ .option('--force', 'ignora o gate de schema e prossegue mesmo com divergência de fingerprint')
192
218
  .action(async (opts) => {
193
219
  const { runDeploy } = await import('./commands/deploy.js');
194
220
  await runDeploy({
@@ -208,6 +234,7 @@ program
208
234
  env: opts.env,
209
235
  localArtifact: !!opts.localArtifact,
210
236
  nonInteractive: !!opts.nonInteractive,
237
+ force: !!opts.force,
211
238
  });
212
239
  });
213
240
  // ── neetru status ─────────────────────────────────────────────────────
@@ -222,6 +249,47 @@ program
222
249
  const { runSurfaceStatus } = await import('./commands/surface-status.js');
223
250
  await runSurfaceStatus({ clientId: opts.clientId, json: !!opts.json });
224
251
  });
252
+ // ── neetru customers ──────────────────────────────────────────────────
253
+ // pdv #2 fix: operadores podem criar customers pelo CLI sem abrir o painel.
254
+ // Customer = empresa/PJ que paga (CRM comercial).
255
+ // Tenant = instância do produto para esse customer.
256
+ const customersCmd = program
257
+ .command('customers')
258
+ .description('Gerenciar customers (empresa/PJ que paga o serviço Neetru)');
259
+ customersCmd
260
+ .command('list')
261
+ .description('Listar customers')
262
+ .option('--status <status>', 'ativo | em prospecção | suspenso | arquivado')
263
+ .option('--search <texto>', 'busca por nome, email ou CNPJ')
264
+ .option('--json', 'saída em JSON')
265
+ .action(async (opts) => {
266
+ const { runCustomersList } = await import('./commands/customers.js');
267
+ await runCustomersList({ status: opts.status, search: opts.search, json: !!opts.json });
268
+ });
269
+ customersCmd
270
+ .command('get <id>')
271
+ .description('Mostrar detalhes de um customer')
272
+ .option('--json', 'saída em JSON')
273
+ .action(async (id, opts) => {
274
+ const { runCustomersGet } = await import('./commands/customers.js');
275
+ await runCustomersGet(id, { json: !!opts.json });
276
+ });
277
+ customersCmd
278
+ .command('create')
279
+ .description('Criar customer (empresa/PJ) — necessário antes de criar workspace')
280
+ .option('--name <nome>', 'nome do customer (razão social ou nome comercial)')
281
+ .option('--email <email>', 'email de contato')
282
+ .option('--tax-id <cnpj>', 'CNPJ ou CPF (opcional)')
283
+ .option('--json', 'saída em JSON')
284
+ .action(async (opts) => {
285
+ const { runCustomersCreate } = await import('./commands/customers.js');
286
+ await runCustomersCreate({
287
+ name: opts.name,
288
+ email: opts.email,
289
+ taxId: opts.taxId,
290
+ json: !!opts.json,
291
+ });
292
+ });
225
293
  // ── neetru tenants ────────────────────────────────────────────────────
226
294
  const tenantsCmd = program
227
295
  .command('tenants')
@@ -252,7 +320,10 @@ tenantsCmd
252
320
  });
253
321
  tenantsCmd
254
322
  .command('create')
255
- .description('Criar tenant (status inicial: em provisionamento)')
323
+ .description('[STAFF-ONLY] Criar tenant sem OAuth client/secret.\n' +
324
+ 'DEPRECATED: para uso normal (ambiente completo com OAuth), use:\n' +
325
+ ' neetru workspaces create\n' +
326
+ 'Este comando permanece para casos staff-only (registro de tenant pré-existente).')
256
327
  .option('--name <name>', 'nome do tenant')
257
328
  .option('--slug <slug>', 'slug do tenant')
258
329
  .option('--customer <customerId>', 'id do customer')
@@ -433,6 +504,8 @@ workspacesCmd
433
504
  .option('--env <env>', 'dev | staging | prod (default: dev)')
434
505
  .option('--tier <tier>', 'dev | standard | enterprise (default: standard)')
435
506
  .option('--name <name>', 'label do workspace')
507
+ .option('--bind-config', 'grava tenantId retornado no neetru.config.json do cwd')
508
+ .option('--force-duplicate', 'cria mesmo que já exista ambiente ativo para produto+env')
436
509
  .option('--json', 'saída em JSON')
437
510
  .action(async (opts) => {
438
511
  const { runWorkspacesCreate } = await import('./commands/workspaces.js');
@@ -442,6 +515,8 @@ workspacesCmd
442
515
  env: opts.env,
443
516
  tier: opts.tier,
444
517
  name: opts.name,
518
+ bindConfig: !!opts.bindConfig,
519
+ forceDuplicate: !!opts.forceDuplicate,
445
520
  json: !!opts.json,
446
521
  });
447
522
  });
@@ -728,17 +803,23 @@ productsCmd
728
803
  const { runProductsUnpublish } = await import('./commands/products.js');
729
804
  await runProductsUnpublish(slug, { json: !!opts.json });
730
805
  });
731
- // ── neetru products db (Phase A — per-product DB isolation) ────────────
732
- const productsDbCmd = productsCmd
733
- .command('db')
734
- .description('Gerenciar bancos isolados por produto');
735
- productsDbCmd
806
+ // ── neetru admin database (Phase A — plano de controle staff de bancos) ───────
807
+ // D-1: renomeado de `neetru products db` → `neetru admin database`.
808
+ // Alias `neetru products db` emite aviso de remoção.
809
+ // D-2: `status` (que era WRITE nesta árvore) renomeado para `set-status`.
810
+ const adminCmd = program
811
+ .command('admin')
812
+ .description('Comandos de administração do plano de controle staff');
813
+ const adminDatabaseCmd = adminCmd
814
+ .command('database')
815
+ .description('Plano de controle staff — bancos isolados por produto (fleet-wide)');
816
+ adminDatabaseCmd
736
817
  .command('list')
737
- .description('Listar bancos de produtos')
738
- .option('--product-id <id>', 'ID do produto')
818
+ .description('[Staff] Listar bancos de todos os produtos')
819
+ .option('--product-id <id>', 'filtrar por ID de produto')
739
820
  .option('--engine <engine>', 'firestore-instance | cloud-sql-postgres | cloud-sql-mysql | vm-postgres-single | vm-postgres-cluster | vm-mysql-single | vm-mysql-cluster')
740
- .option('--status <status>', 'Filtrar por status (ativo, provisionando, falha, arquivado)')
741
- .option('--json', 'Saída em JSON estruturado')
821
+ .option('--status <status>', 'filtrar por status (ativo, provisionando, falha, arquivado)')
822
+ .option('--json', 'saída em JSON estruturado')
742
823
  .action(async (opts) => {
743
824
  const { runDbList } = await import('./commands/products-db.js');
744
825
  await runDbList({
@@ -748,25 +829,25 @@ productsDbCmd
748
829
  json: !!opts.json,
749
830
  });
750
831
  });
751
- productsDbCmd
832
+ adminDatabaseCmd
752
833
  .command('engines')
753
- .description('Listar engines de banco suportados')
834
+ .description('[Staff] Listar engines de banco suportados')
754
835
  .option('--json')
755
836
  .action(async (opts) => {
756
837
  const { runDbEngines } = await import('./commands/products-db.js');
757
838
  await runDbEngines({ json: !!opts.json });
758
839
  });
759
- productsDbCmd
840
+ adminDatabaseCmd
760
841
  .command('create')
761
- .description('Criar banco de produto (Phase A: registra + audit)')
842
+ .description('[Staff] Registrar banco de produto no plano de controle (Phase A)')
762
843
  .option('--product-id <id>', 'ID do produto')
763
- .option('--label <label>', 'Nome descritivo do banco')
844
+ .option('--label <label>', 'nome descritivo do banco')
764
845
  .option('--engine <engine>', 'firestore-instance | cloud-sql-postgres | cloud-sql-mysql | vm-postgres-single | vm-postgres-cluster | vm-mysql-single | vm-mysql-cluster')
765
- .option('--env <env>', 'Ambiente: dev | staging | production')
766
- .option('--region <region>', 'Região GCP', 'us-central1')
846
+ .option('--env <env>', 'ambiente: dev-local | staging | production')
847
+ .option('--region <region>', 'região GCP', 'us-central1')
767
848
  .option('--server-id <serverId>', 'ID da VM alvo (obrigatório para engines vm-*)')
768
- .option('--replica-count <n>', 'Número de réplicas para engines *-cluster (padrão 2)')
769
- .option('--json', 'Saída em JSON estruturado')
849
+ .option('--replica-count <n>', 'número de réplicas para engines *-cluster (padrão 2)')
850
+ .option('--json', 'saída em JSON estruturado')
770
851
  .action(async (opts) => {
771
852
  const { runDbCreate } = await import('./commands/products-db.js');
772
853
  await runDbCreate({
@@ -780,26 +861,132 @@ productsDbCmd
780
861
  json: !!opts.json,
781
862
  });
782
863
  });
864
+ adminDatabaseCmd
865
+ .command('get [id]')
866
+ .description('[Staff] Mostrar detalhes de um banco')
867
+ .option('--json')
868
+ .action(async (id, opts) => {
869
+ const { runDbGet } = await import('./commands/products-db.js');
870
+ await runDbGet(id, { json: !!opts.json });
871
+ });
872
+ // gestovendas #12 fix: alias `status` → `get` (skill /neetru documenta como `status`).
873
+ adminDatabaseCmd
874
+ .command('status [id]')
875
+ .description('[Staff] Alias de `get` — mostrar detalhes e status de um banco')
876
+ .option('--json')
877
+ .action(async (id, opts) => {
878
+ const { runDbGet } = await import('./commands/products-db.js');
879
+ await runDbGet(id, { json: !!opts.json });
880
+ });
881
+ // D-2: `set-status` é ESCRITA explícita. `status` era ambíguo com o READ em `neetru db status`.
882
+ adminDatabaseCmd
883
+ .command('set-status [id] [status]')
884
+ .description('[Staff] ESCRITA — forçar status do ciclo de vida de um banco (active/failed/archived/…)')
885
+ .option('--reason <text>', 'motivo da mudança de status')
886
+ .option('--json')
887
+ .action(async (id, status, opts) => {
888
+ const { runDbSetStatus } = await import('./commands/products-db.js');
889
+ await runDbSetStatus(id, status, { reason: opts.reason, json: !!opts.json });
890
+ });
891
+ adminDatabaseCmd
892
+ .command('retry [id]')
893
+ .description('[Staff] Reenfileirar provisionamento de banco com falha')
894
+ .option('--json')
895
+ .action(async (id, opts) => {
896
+ const { runDbRetry } = await import('./commands/products-db.js');
897
+ await runDbRetry(id, { json: !!opts.json });
898
+ });
899
+ adminDatabaseCmd
900
+ .command('rotate [id]')
901
+ .description('[Staff] Rotacionar credenciais do banco')
902
+ .option('--json')
903
+ .action(async (id, opts) => {
904
+ const { runDbRotate } = await import('./commands/products-db.js');
905
+ await runDbRotate(id, { json: !!opts.json });
906
+ });
907
+ adminDatabaseCmd
908
+ .command('delete [id]')
909
+ .description('[Staff] Arquivar banco por soft-delete')
910
+ .option('--json')
911
+ .action(async (id, opts) => {
912
+ const { runDbDelete } = await import('./commands/products-db.js');
913
+ await runDbDelete(id, { json: !!opts.json });
914
+ });
915
+ // ── neetru products db (alias de remoção — D-1) ────────────────────────────
916
+ // Emite aviso informando o novo caminho. Mantém subcomandos funcionais por
917
+ // compatibilidade temporária mas deixa claro que foi renomeado.
918
+ const productsDbCmd = productsCmd
919
+ .command('db')
920
+ .description('[RENOMEADO] Use `neetru admin database`. Este alias emite aviso.')
921
+ .hook('preAction', () => {
922
+ process.stderr.write('\nAviso: `neetru products db` foi renomeado para `neetru admin database`.\n' +
923
+ 'Por favor, atualize seus scripts. Este alias será removido em breve.\n\n');
924
+ });
925
+ // gestovendas #2 / pdv #5 fix: alias `list` aceita --product (slug legado) OU
926
+ // --product-id (novo). Se slug recebido, passa como productId para o Core.
927
+ // O Core tenta resolver slug → id server-side (fallback); se o Core não suportar,
928
+ // o operador vê mensagem específica do servidor e não um erro de sintaxe.
929
+ productsDbCmd
930
+ .command('list')
931
+ .description('[RENOMEADO — use: neetru admin database list]')
932
+ .option('--product-id <id>', 'ID do produto (novo)')
933
+ .option('--product <slugOrId>', 'slug ou ID do produto (legado — use --product-id)')
934
+ .option('--engine <engine>')
935
+ .option('--status <status>')
936
+ .option('--json')
937
+ .action(async (opts) => {
938
+ // Tradução de flag: --product (legado) → productId (novo).
939
+ const productId = opts.productId ?? opts.product;
940
+ if (opts.product && !opts.productId) {
941
+ process.stderr.write(`[DEPRECATED] Use --product-id em vez de --product.\n` +
942
+ ` Próxima major remove o suporte a --product neste alias.\n`);
943
+ }
944
+ const { runDbList } = await import('./commands/products-db.js');
945
+ await runDbList({ productId, engine: opts.engine, status: opts.status, json: !!opts.json });
946
+ });
947
+ productsDbCmd
948
+ .command('engines')
949
+ .description('[RENOMEADO — use: neetru admin database engines]')
950
+ .option('--json')
951
+ .action(async (opts) => {
952
+ const { runDbEngines } = await import('./commands/products-db.js');
953
+ await runDbEngines({ json: !!opts.json });
954
+ });
955
+ productsDbCmd
956
+ .command('create')
957
+ .description('[RENOMEADO — use: neetru admin database create]')
958
+ .option('--product-id <id>')
959
+ .option('--label <label>')
960
+ .option('--engine <engine>')
961
+ .option('--env <env>')
962
+ .option('--region <region>', '', 'us-central1')
963
+ .option('--server-id <serverId>')
964
+ .option('--replica-count <n>')
965
+ .option('--json')
966
+ .action(async (opts) => {
967
+ const { runDbCreate } = await import('./commands/products-db.js');
968
+ await runDbCreate({ productId: opts.productId, label: opts.label, engine: opts.engine, env: opts.env, region: opts.region, serverId: opts.serverId, replicaCount: opts.replicaCount, json: !!opts.json });
969
+ });
783
970
  productsDbCmd
784
971
  .command('get [id]')
785
- .description('Mostrar detalhes de um banco')
972
+ .description('[RENOMEADO use: neetru admin database get]')
786
973
  .option('--json')
787
974
  .action(async (id, opts) => {
788
975
  const { runDbGet } = await import('./commands/products-db.js');
789
976
  await runDbGet(id, { json: !!opts.json });
790
977
  });
791
978
  productsDbCmd
792
- .command('status [id] [status]')
793
- .description('Atualizar status de um banco de produto')
979
+ .command('set-status [id] [status]')
980
+ .description('[RENOMEADO use: neetru admin database set-status]')
794
981
  .option('--reason <text>')
795
982
  .option('--json')
796
983
  .action(async (id, status, opts) => {
797
- const { runDbStatus } = await import('./commands/products-db.js');
798
- await runDbStatus(id, status, { reason: opts.reason, json: !!opts.json });
984
+ const { runDbSetStatus } = await import('./commands/products-db.js');
985
+ await runDbSetStatus(id, status, { reason: opts.reason, json: !!opts.json });
799
986
  });
800
987
  productsDbCmd
801
988
  .command('retry [id]')
802
- .description('Reenfileirar provisionamento de banco')
989
+ .description('[RENOMEADO use: neetru admin database retry]')
803
990
  .option('--json')
804
991
  .action(async (id, opts) => {
805
992
  const { runDbRetry } = await import('./commands/products-db.js');
@@ -807,7 +994,7 @@ productsDbCmd
807
994
  });
808
995
  productsDbCmd
809
996
  .command('rotate [id]')
810
- .description('Rotacionar credenciais do banco (admin)')
997
+ .description('[RENOMEADO use: neetru admin database rotate]')
811
998
  .option('--json')
812
999
  .action(async (id, opts) => {
813
1000
  const { runDbRotate } = await import('./commands/products-db.js');
@@ -815,7 +1002,7 @@ productsDbCmd
815
1002
  });
816
1003
  productsDbCmd
817
1004
  .command('delete [id]')
818
- .description('Arquivar banco por soft-delete (admin)')
1005
+ .description('[RENOMEADO use: neetru admin database delete]')
819
1006
  .option('--json')
820
1007
  .action(async (id, opts) => {
821
1008
  const { runDbDelete } = await import('./commands/products-db.js');
@@ -916,6 +1103,74 @@ program
916
1103
  ctaLabel: opts.ctaLabel,
917
1104
  });
918
1105
  });
1106
+ // ── neetru marketplace ────────────────────────────────────────────────
1107
+ const marketplaceCmd = program
1108
+ .command('marketplace')
1109
+ .description('Central de artefatos do ecossistema Neetru (skills, SDK, templates)');
1110
+ const marketplaceSkillsCmd = marketplaceCmd
1111
+ .command('skills')
1112
+ .description('Gerenciar Skills do Claude Code');
1113
+ marketplaceSkillsCmd
1114
+ .command('install')
1115
+ .description('Clonar neetru-libs e instalar skills em ~/.claude/skills/')
1116
+ .action(async () => {
1117
+ const { runSkillsInstall } = await import('./commands/marketplace.js');
1118
+ await runSkillsInstall();
1119
+ });
1120
+ marketplaceSkillsCmd
1121
+ .command('update')
1122
+ .description('Atualizar cache neetru-libs (git pull) e re-copiar skills instaladas')
1123
+ .action(async () => {
1124
+ const { runSkillsUpdate } = await import('./commands/marketplace.js');
1125
+ await runSkillsUpdate();
1126
+ });
1127
+ marketplaceSkillsCmd
1128
+ .command('list')
1129
+ .description('Listar skills disponíveis no cache local')
1130
+ .action(async () => {
1131
+ const { runSkillsList } = await import('./commands/marketplace.js');
1132
+ await runSkillsList();
1133
+ });
1134
+ marketplaceSkillsCmd
1135
+ .command('uninstall')
1136
+ .description('Remover skills de ~/.claude/skills/ (pede confirmação)')
1137
+ .option('--yes', 'pular confirmação interativa')
1138
+ .action(async (opts) => {
1139
+ const { runSkillsUninstall } = await import('./commands/marketplace.js');
1140
+ await runSkillsUninstall({ yes: !!opts.yes });
1141
+ });
1142
+ const marketplaceSdkCmd = marketplaceCmd
1143
+ .command('sdk')
1144
+ .description('Gerenciar @neetru/sdk no projeto atual');
1145
+ marketplaceSdkCmd
1146
+ .command('init')
1147
+ .description('Adicionar @neetru/sdk ao package.json e criar src/neetru.ts')
1148
+ .action(async () => {
1149
+ const { runSdkInit } = await import('./commands/marketplace.js');
1150
+ await runSdkInit();
1151
+ });
1152
+ marketplaceSdkCmd
1153
+ .command('templates')
1154
+ .description('Listar templates de feature disponíveis (auth, billing, usage…)')
1155
+ .action(async () => {
1156
+ const { runSdkTemplates } = await import('./commands/marketplace.js');
1157
+ await runSdkTemplates();
1158
+ });
1159
+ marketplaceSdkCmd
1160
+ .command('add <template>')
1161
+ .description('Copiar template de feature pra src/ do projeto atual')
1162
+ .option('--force', 'sobrescrever arquivos existentes')
1163
+ .action(async (template, opts) => {
1164
+ const { runSdkAddTemplate } = await import('./commands/marketplace.js');
1165
+ await runSdkAddTemplate(template, { force: !!opts.force });
1166
+ });
1167
+ marketplaceCmd
1168
+ .command('browse')
1169
+ .description('Abrir https://github.com/Neetru/neetru-libs no browser')
1170
+ .action(async () => {
1171
+ const { runMarketplaceBrowse } = await import('./commands/marketplace.js');
1172
+ await runMarketplaceBrowse();
1173
+ });
919
1174
  // ── neetru add ────────────────────────────────────────────────────────
920
1175
  program
921
1176
  .command('add <feature>')
@@ -972,33 +1227,144 @@ envCmd
972
1227
  });
973
1228
  });
974
1229
  // ── neetru db ─────────────────────────────────────────────────────────
1230
+ // M1 — árvore completa de banco por produto.
1231
+ // Subcomandos novos: list, status, apply, migrations list/confirm.
1232
+ // Subcomandos legados (Sprint 10): init --out, migrate, seed.
975
1233
  const dbCmd = program
976
1234
  .command('db')
977
- .description('Gerenciar schema, migrations e seed do produto');
1235
+ .description('Gerenciar schema, migrations e banco isolado do produto');
1236
+ // ── neetru db list ────────────────────────────────────────────────────
1237
+ dbCmd
1238
+ .command('list')
1239
+ .description('Listar bancos do produto (GET /api/cli/v1/db)')
1240
+ .option('--product-id <id>', 'filtrar por ID de produto')
1241
+ .option('--engine <engine>', 'filtrar por engine (cloud-sql-postgres, vm-postgres-single, …)')
1242
+ .option('--status <status>', 'filtrar por status (ativo, provisionando, falha, arquivado)')
1243
+ .option('--json', 'saída em JSON estruturado')
1244
+ .action(async (opts) => {
1245
+ const { runDbList } = await import('./commands/db.js');
1246
+ await runDbList({
1247
+ productId: opts.productId,
1248
+ engine: opts.engine,
1249
+ status: opts.status,
1250
+ json: !!opts.json,
1251
+ });
1252
+ });
1253
+ // ── neetru db status <dbId> ───────────────────────────────────────────
1254
+ dbCmd
1255
+ .command('status <dbId>')
1256
+ .description('Detalhar banco + status de provisionamento/saúde')
1257
+ .option('--json', 'saída em JSON estruturado')
1258
+ .action(async (dbId, opts) => {
1259
+ const { runDbStatus } = await import('./commands/db.js');
1260
+ await runDbStatus(dbId, { json: !!opts.json });
1261
+ });
1262
+ // ── neetru db init ────────────────────────────────────────────────────
1263
+ // D-3: opções legadas --out e --force removidas. db init faz UMA coisa.
1264
+ // D-4: --env aceita dev-local (canônico) ou dev (alias explícito com aviso).
978
1265
  dbCmd
979
1266
  .command('init')
980
- .description('Criar manifest de schema a partir do config')
981
- .option('--out <path>', 'caminho do manifest (default db/schema.manifest.json)')
982
- .option('--force', 'sobrescreve se existir')
1267
+ .description('Registrar banco no Core + gravar .neetru/db.json + scaffoldar db/schema.ts')
1268
+ .option('--name <name>', 'nome do banco (interativo se ausente)')
1269
+ .option('--engine <engine>', 'engine: cloud-sql-postgres | vm-postgres-single | firestore-instance | …')
1270
+ .option('--env <env>', 'ambiente: dev-local (padrão) | staging | production')
1271
+ .option('--product-id <id>', 'override do productId (default: slug do neetru.config.json)')
1272
+ .option('-y, --yes', 'modo não-interativo (usa defaults)')
1273
+ .option('--json', 'saída em JSON estruturado')
983
1274
  .action(async (opts) => {
984
1275
  const { runDbInit } = await import('./commands/db.js');
985
- await runDbInit({ out: opts.out, force: !!opts.force });
1276
+ await runDbInit({
1277
+ name: opts.name,
1278
+ engine: opts.engine,
1279
+ env: opts.env,
1280
+ productId: opts.productId,
1281
+ yes: !!opts.yes,
1282
+ json: !!opts.json,
1283
+ });
986
1284
  });
1285
+ // ── neetru db apply ───────────────────────────────────────────────────
1286
+ // D-4: --env usa dev-local como default canônico.
987
1287
  dbCmd
988
- .command('migrate <toVersion>')
989
- .description('Aplicar migration de schema via Core')
990
- .option('--from <fromVersion>', 'versão atual (default: schemaVersion do config)')
991
- .action(async (toVersion, opts) => {
992
- const { runDbMigrate } = await import('./commands/db.js');
993
- await runDbMigrate({ toVersion, fromVersion: opts.from });
1288
+ .command('apply')
1289
+ .description('Executar pipeline de migração + enviar ao Core (POST /api/cli/v1/db/migrations/push)')
1290
+ .option('--db <name>', 'banco alvo (interativo se mais de um registrado)')
1291
+ .option('--env <env>', 'ambiente alvo: dev-local (padrão) | staging | production')
1292
+ .option('--schema <path>', 'caminho do schema (default: db/schema.ts)')
1293
+ .option('--dry-run', 'calcula o diff mas NÃO envia ao Core')
1294
+ .option('-y, --yes', 'pula a confirmação interativa')
1295
+ .option('--json', 'saída em JSON estruturado')
1296
+ .action(async (opts) => {
1297
+ const { runDbApply } = await import('./commands/db.js');
1298
+ await runDbApply({
1299
+ dbName: opts.db,
1300
+ env: opts.env,
1301
+ schemaPath: opts.schema,
1302
+ dryRun: !!opts.dryRun,
1303
+ yes: !!opts.yes,
1304
+ json: !!opts.json,
1305
+ });
1306
+ });
1307
+ // ── neetru db migrations ──────────────────────────────────────────────
1308
+ const dbMigrationsCmd = dbCmd
1309
+ .command('migrations')
1310
+ .description('Operar migrações de banco');
1311
+ dbMigrationsCmd
1312
+ .command('list')
1313
+ .description('Listar migrações registradas (GET /api/cli/v1/db/migrations)')
1314
+ .option('--db <dbId>', 'filtrar por ID do banco')
1315
+ .option('--json', 'saída em JSON estruturado')
1316
+ .action(async (opts) => {
1317
+ const { runDbMigrationsList } = await import('./commands/db.js');
1318
+ await runDbMigrationsList({ dbId: opts.db, json: !!opts.json });
994
1319
  });
1320
+ dbMigrationsCmd
1321
+ .command('confirm <migrationId>')
1322
+ .description('Confirmar migração destrutiva (POST /api/cli/v1/db/migrations/[id]/confirm — exige MFA)')
1323
+ .requiredOption('--mfa <token>', 'código TOTP step-up MFA')
1324
+ .option('--json', 'saída em JSON')
1325
+ .action(async (migrationId, opts) => {
1326
+ const { runDbMigrationsConfirm } = await import('./commands/db.js');
1327
+ await runDbMigrationsConfirm(migrationId, { mfa: opts.mfa, json: !!opts.json });
1328
+ });
1329
+ // D-3: neetru db migrate removido (legado Sprint 10 cortado).
1330
+ // ── neetru db backups <dbId> ──────────────────────────────────────────
1331
+ dbCmd
1332
+ .command('backups <dbId>')
1333
+ .description('Listar backups de um banco (GET /api/cli/v1/db/[id]/backups)')
1334
+ .option('--json', 'saída em JSON estruturado')
1335
+ .action(async (dbId, opts) => {
1336
+ const { runDbBackups } = await import('./commands/db.js');
1337
+ await runDbBackups(dbId, { json: !!opts.json });
1338
+ });
1339
+ // ── neetru db restore <dbId> ──────────────────────────────────────────
1340
+ dbCmd
1341
+ .command('restore <dbId>')
1342
+ .description('Restaurar banco a partir de backup (POST /api/cli/v1/db/[id]/restore — exige MFA)')
1343
+ .requiredOption('--mfa <token>', 'código TOTP step-up MFA (obrigatório — operação destrutiva)')
1344
+ .option('--json', 'saída em JSON')
1345
+ .action(async (dbId, opts) => {
1346
+ const { runDbRestore } = await import('./commands/db.js');
1347
+ await runDbRestore(dbId, { mfa: opts.mfa, json: !!opts.json });
1348
+ });
1349
+ // ── neetru db hosts ───────────────────────────────────────────────────
1350
+ // Alias: também acessível via `neetru db-hosts` (top-level).
995
1351
  dbCmd
996
- .command('seed')
997
- .description('Executar script de seed do produto')
998
- .option('--script <path>', 'caminho custom do script de seed')
1352
+ .command('hosts')
1353
+ .description('Listar VM hosts de banco e densidade (GET /api/cli/v1/servers?capacity=true&db=true)')
1354
+ .option('--json', 'saída em JSON estruturado')
999
1355
  .action(async (opts) => {
1000
- const { runDbSeed } = await import('./commands/db.js');
1001
- await runDbSeed({ script: opts.script });
1356
+ const { runDbHosts } = await import('./commands/db.js');
1357
+ await runDbHosts({ json: !!opts.json });
1358
+ });
1359
+ // D-3: neetru db seed removido (legado Sprint 10 cortado).
1360
+ // ── neetru db-hosts (top-level alias de `neetru db hosts`) ───────────
1361
+ program
1362
+ .command('db-hosts')
1363
+ .description('Listar VM hosts de banco e densidade (alias de `neetru db hosts`)')
1364
+ .option('--json', 'saída em JSON estruturado')
1365
+ .action(async (opts) => {
1366
+ const { runDbHosts } = await import('./commands/db.js');
1367
+ await runDbHosts({ json: !!opts.json });
1002
1368
  });
1003
1369
  // ── neetru fn ─────────────────────────────────────────────────────────
1004
1370
  const fnCmd = program
@@ -1220,6 +1586,117 @@ supportTicketsCmd
1220
1586
  const { runSupportTicketsStatus } = await import('./commands/support.js');
1221
1587
  await runSupportTicketsStatus(id, { to: opts.to, json: !!opts.json });
1222
1588
  });
1589
+ // ── neetru bug ───────────────────────────────────────────────────────
1590
+ /**
1591
+ * neetru bug — bugs e ocorrências reportados pela CLI, agentes Claude e staff.
1592
+ *
1593
+ * Subcomandos:
1594
+ * report — reportar novo bug (interativo ou one-shot via flags)
1595
+ * list — listar bugs abertos (ou com filtros)
1596
+ * show <id> — detalhe completo
1597
+ * claim <id> — assumir investigação
1598
+ * resolve <id> — encerrar (resolved/wont_fix/duplicate)
1599
+ * comment <id> — adicionar comentário na thread
1600
+ *
1601
+ * Exemplo rápido (agentes Claude):
1602
+ * neetru bug report --title "X trava" --body "Stack trace…" --category cli --severity high --actor-type agent_claude --json
1603
+ * neetru bug list --status open --json
1604
+ */
1605
+ const bugCmd = program
1606
+ .command('bug')
1607
+ .description('Reportar, listar e resolver bugs e ocorrências da plataforma');
1608
+ bugCmd
1609
+ .command('report')
1610
+ .description('Reportar novo bug (interativo ou flags --title/--body para agentes)')
1611
+ .option('--title <text>', 'título curto do bug')
1612
+ .option('--body <text>', 'descrição detalhada')
1613
+ .option('--category <cat>', 'cli | sdk | core | agent | libs | docs | infra | other', 'other')
1614
+ .option('--severity <sev>', 'critical | high | medium | low', 'medium')
1615
+ .option('--product <id>', 'productId afetado (opcional)')
1616
+ .option('--steps <text>', 'passos para reproduzir (opcional)')
1617
+ .option('--expected <text>', 'comportamento esperado (opcional)')
1618
+ .option('--actual <text>', 'comportamento atual (opcional)')
1619
+ .option('--actor-type <type>', 'cli_principal | agent_claude | staff_session', 'cli_principal')
1620
+ .option('--json', 'saída em JSON (modo não-interativo)')
1621
+ .action(async (opts) => {
1622
+ const { runBugReport } = await import('./commands/bug.js');
1623
+ await runBugReport({
1624
+ title: opts.title,
1625
+ body: opts.body,
1626
+ category: opts.category,
1627
+ severity: opts.severity,
1628
+ product: opts.product,
1629
+ steps: opts.steps,
1630
+ expected: opts.expected,
1631
+ actual: opts.actual,
1632
+ actorType: opts.actorType,
1633
+ json: !!opts.json,
1634
+ });
1635
+ });
1636
+ bugCmd
1637
+ .command('list')
1638
+ .description('Listar bugs e ocorrências')
1639
+ .option('--status <s>', 'open | triaged | in_progress | resolved | wont_fix | duplicate')
1640
+ .option('--category <cat>', 'cli | sdk | core | agent | libs | docs | infra | other')
1641
+ .option('--assigned-to <uid>', 'filtrar por responsável (uid ou session id)')
1642
+ .option('--product <id>', 'filtrar por productId')
1643
+ .option('--limit <n>', 'máximo de resultados (default 20, max 200)')
1644
+ .option('--json', 'saída em JSON')
1645
+ .action(async (opts) => {
1646
+ const { runBugList } = await import('./commands/bug.js');
1647
+ await runBugList({
1648
+ status: opts.status,
1649
+ category: opts.category,
1650
+ assignedTo: opts.assignedTo,
1651
+ product: opts.product,
1652
+ limit: opts.limit,
1653
+ json: !!opts.json,
1654
+ });
1655
+ });
1656
+ bugCmd
1657
+ .command('show <id>')
1658
+ .description('Mostrar detalhe completo de um bug')
1659
+ .option('--json', 'saída em JSON')
1660
+ .action(async (id, opts) => {
1661
+ const { runBugShow } = await import('./commands/bug.js');
1662
+ await runBugShow(id, { json: !!opts.json });
1663
+ });
1664
+ bugCmd
1665
+ .command('claim <id>')
1666
+ .description('Assumir investigação de um bug')
1667
+ .option('--assigned-to <uid>', 'uid ou session id (default: caller)')
1668
+ .option('--json', 'saída em JSON')
1669
+ .action(async (id, opts) => {
1670
+ const { runBugClaim } = await import('./commands/bug.js');
1671
+ await runBugClaim(id, { assignedTo: opts.assignedTo, json: !!opts.json });
1672
+ });
1673
+ bugCmd
1674
+ .command('resolve <id>')
1675
+ .description('Encerrar um bug como resolvido, wont_fix ou duplicate')
1676
+ .option('--summary <text>', 'resumo do que foi feito / por que não corrigir')
1677
+ .option('--commit <sha>', 'commit SHA que corrige o bug (opcional)')
1678
+ .option('--status <s>', 'resolved | wont_fix | duplicate (default: resolved)', 'resolved')
1679
+ .option('--duplicate-of <id>', 'ID do bug original (obrigatório quando --status duplicate)')
1680
+ .option('--json', 'saída em JSON (modo não-interativo)')
1681
+ .action(async (id, opts) => {
1682
+ const { runBugResolve } = await import('./commands/bug.js');
1683
+ await runBugResolve(id, {
1684
+ summary: opts.summary,
1685
+ commit: opts.commit,
1686
+ status: opts.status,
1687
+ duplicateOf: opts.duplicateOf,
1688
+ json: !!opts.json,
1689
+ });
1690
+ });
1691
+ bugCmd
1692
+ .command('comment <id>')
1693
+ .description('Adicionar comentário na thread de um bug')
1694
+ .option('--body <text>', 'corpo do comentário')
1695
+ .option('--json', 'saída em JSON (modo não-interativo)')
1696
+ .action(async (id, opts) => {
1697
+ const { runBugComment } = await import('./commands/bug.js');
1698
+ await runBugComment(id, { body: opts.body, json: !!opts.json });
1699
+ });
1223
1700
  // ── neetru dns ───────────────────────────────────────────────────────
1224
1701
  const dnsCmd = program
1225
1702
  .command('dns')
@@ -1356,9 +1833,20 @@ drCmd
1356
1833
  // Frente B — docs em GCS + Firestore registry. Desacopla conteúdo de docs
1357
1834
  // do deploy do Core (owner edita markdown direto pelo bucket OU via CLI,
1358
1835
  // sem rebuild).
1836
+ //
1837
+ // `neetru docs open [topic]` abre documentação no browser. É o ponto de
1838
+ // entrada pra quem quer LER docs, não publicar.
1359
1839
  const docsCmd = program
1360
1840
  .command('docs')
1361
- .description('Gerenciar registry de docs publicados em gs://neetru-docs');
1841
+ .description('Documentação + registry de docs publicados em gs://neetru-docs');
1842
+ docsCmd
1843
+ .command('open [topic]')
1844
+ .description('Abrir documentação no browser (ex: sdk, cli, db, webhooks, auth)')
1845
+ .option('--json', 'saída em JSON (imprime URL sem abrir browser)')
1846
+ .action(async (topic, opts) => {
1847
+ const { runDocsOpen } = await import('./commands/docs.js');
1848
+ await runDocsOpen(topic, { json: !!opts.json });
1849
+ });
1362
1850
  docsCmd
1363
1851
  .command('publish <file>')
1364
1852
  .description('Publicar arquivo markdown (sobe pro GCS + registry Firestore)')