@neetru/cli 2.7.4 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +220 -208
  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/db.d.ts +87 -7
  18. package/dist/commands/db.js +697 -126
  19. package/dist/commands/db.js.map +1 -1
  20. package/dist/commands/deploy.d.ts +5 -0
  21. package/dist/commands/deploy.js +68 -0
  22. package/dist/commands/deploy.js.map +1 -1
  23. package/dist/commands/dev.d.ts +68 -0
  24. package/dist/commands/dev.js +345 -0
  25. package/dist/commands/dev.js.map +1 -0
  26. package/dist/commands/init.js +121 -121
  27. package/dist/commands/new.d.ts +6 -0
  28. package/dist/commands/new.js +31 -10
  29. package/dist/commands/new.js.map +1 -1
  30. package/dist/commands/products-db.d.ts +1 -1
  31. package/dist/commands/products-db.js +17 -4
  32. package/dist/commands/products-db.js.map +1 -1
  33. package/dist/commands/upgrade.js +5 -2
  34. package/dist/commands/upgrade.js.map +1 -1
  35. package/dist/index.js +258 -42
  36. package/dist/index.js.map +1 -1
  37. package/dist/lib/ai/context.js +90 -90
  38. package/dist/lib/db-local/db-json.d.ts +63 -0
  39. package/dist/lib/db-local/db-json.js +189 -0
  40. package/dist/lib/db-local/db-json.js.map +1 -0
  41. package/dist/lib/db-local/env.d.ts +26 -0
  42. package/dist/lib/db-local/env.js +64 -0
  43. package/dist/lib/db-local/env.js.map +1 -0
  44. package/dist/lib/db-local/fingerprint.d.ts +8 -0
  45. package/dist/lib/db-local/fingerprint.js +28 -0
  46. package/dist/lib/db-local/fingerprint.js.map +1 -0
  47. package/dist/lib/db-local/index.d.ts +15 -0
  48. package/dist/lib/db-local/index.js +14 -0
  49. package/dist/lib/db-local/index.js.map +1 -0
  50. package/dist/lib/db-pipeline/build-deps.d.ts +14 -0
  51. package/dist/lib/db-pipeline/build-deps.js +158 -0
  52. package/dist/lib/db-pipeline/build-deps.js.map +1 -0
  53. package/dist/lib/db-pipeline/errors.d.ts +29 -0
  54. package/dist/lib/db-pipeline/errors.js +29 -0
  55. package/dist/lib/db-pipeline/errors.js.map +1 -0
  56. package/dist/lib/db-pipeline/index.d.ts +26 -0
  57. package/dist/lib/db-pipeline/index.js +25 -0
  58. package/dist/lib/db-pipeline/index.js.map +1 -0
  59. package/dist/lib/db-pipeline/pipeline.d.ts +13 -0
  60. package/dist/lib/db-pipeline/pipeline.js +119 -0
  61. package/dist/lib/db-pipeline/pipeline.js.map +1 -0
  62. package/dist/lib/db-pipeline/rehearse.d.ts +99 -0
  63. package/dist/lib/db-pipeline/rehearse.js +219 -0
  64. package/dist/lib/db-pipeline/rehearse.js.map +1 -0
  65. package/dist/lib/db-pipeline/types.d.ts +112 -0
  66. package/dist/lib/db-pipeline/types.js +20 -0
  67. package/dist/lib/db-pipeline/types.js.map +1 -0
  68. package/package.json +63 -62
  69. package/templates/auth/callback.ts +22 -22
  70. package/templates/auth/sign-in.tsx +41 -41
  71. package/templates/billing/checkout.ts +22 -22
  72. package/templates/billing/page.tsx +43 -43
  73. package/templates/support/ticket-form.tsx +68 -68
  74. package/templates/usage/track.ts +30 -30
  75. package/templates/users/profile.tsx +43 -43
package/dist/index.js CHANGED
@@ -56,6 +56,7 @@ program
56
56
  .option('--customer <customerId>', 'customerId dono do workspace (default: self)')
57
57
  .option('--skip-init', 'pula o scaffold local')
58
58
  .option('--skip-open', 'pula a abertura do browser')
59
+ .option('--skip-db-init', 'pula o 5º passo de inicialização do banco de dados')
59
60
  .option('--json', 'saída em JSON (machine-readable)')
60
61
  .action(async (name, opts) => {
61
62
  const { runNewMacro } = await import('./commands/new.js');
@@ -66,6 +67,7 @@ program
66
67
  customer: opts.customer,
67
68
  skipInit: !!opts.skipInit,
68
69
  skipOpen: !!opts.skipOpen,
70
+ skipDbInit: !!opts.skipDbInit,
69
71
  json: !!opts.json,
70
72
  });
71
73
  });
@@ -150,6 +152,19 @@ program
150
152
  const { runWhoami } = await import('./commands/whoami.js');
151
153
  await runWhoami({ json: !!opts.json });
152
154
  });
155
+ // ── neetru dev ────────────────────────────────────────────────────────
156
+ // Banco local Docker + watch de schema (M1 GAP-M1-4).
157
+ // "salvar = aplicado" — cada save no schema roda o pipeline neetru db apply
158
+ // contra o banco dev-local do produto.
159
+ program
160
+ .command('dev')
161
+ .description('Rodar banco Docker local + watch de schema (dev-local)')
162
+ .option('--db <name>', 'banco alvo do .neetru/db.json (default: único registrado)')
163
+ .option('--schema <path>', 'caminho do arquivo de schema (default: db/schema.ts)')
164
+ .action(async (opts) => {
165
+ const { runDev } = await import('./commands/dev.js');
166
+ await runDev({ db: opts.db, schema: opts.schema });
167
+ });
153
168
  // ── neetru build ──────────────────────────────────────────────────────
154
169
  program
155
170
  .command('build')
@@ -189,6 +204,7 @@ program
189
204
  .option('--env <env>', 'ambiente alvo: dev | staging | prod', 'dev')
190
205
  .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
206
  .option('--non-interactive', 'falha em vez de perguntar (modo CI)')
207
+ .option('--force', 'ignora o gate de schema e prossegue mesmo com divergência de fingerprint')
192
208
  .action(async (opts) => {
193
209
  const { runDeploy } = await import('./commands/deploy.js');
194
210
  await runDeploy({
@@ -208,6 +224,7 @@ program
208
224
  env: opts.env,
209
225
  localArtifact: !!opts.localArtifact,
210
226
  nonInteractive: !!opts.nonInteractive,
227
+ force: !!opts.force,
211
228
  });
212
229
  });
213
230
  // ── neetru status ─────────────────────────────────────────────────────
@@ -728,17 +745,23 @@ productsCmd
728
745
  const { runProductsUnpublish } = await import('./commands/products.js');
729
746
  await runProductsUnpublish(slug, { json: !!opts.json });
730
747
  });
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
748
+ // ── neetru admin database (Phase A — plano de controle staff de bancos) ───────
749
+ // D-1: renomeado de `neetru products db` → `neetru admin database`.
750
+ // Alias `neetru products db` emite aviso de remoção.
751
+ // D-2: `status` (que era WRITE nesta árvore) renomeado para `set-status`.
752
+ const adminCmd = program
753
+ .command('admin')
754
+ .description('Comandos de administração do plano de controle staff');
755
+ const adminDatabaseCmd = adminCmd
756
+ .command('database')
757
+ .description('Plano de controle staff — bancos isolados por produto (fleet-wide)');
758
+ adminDatabaseCmd
736
759
  .command('list')
737
- .description('Listar bancos de produtos')
738
- .option('--product-id <id>', 'ID do produto')
760
+ .description('[Staff] Listar bancos de todos os produtos')
761
+ .option('--product-id <id>', 'filtrar por ID de produto')
739
762
  .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')
763
+ .option('--status <status>', 'filtrar por status (ativo, provisionando, falha, arquivado)')
764
+ .option('--json', 'saída em JSON estruturado')
742
765
  .action(async (opts) => {
743
766
  const { runDbList } = await import('./commands/products-db.js');
744
767
  await runDbList({
@@ -748,25 +771,25 @@ productsDbCmd
748
771
  json: !!opts.json,
749
772
  });
750
773
  });
751
- productsDbCmd
774
+ adminDatabaseCmd
752
775
  .command('engines')
753
- .description('Listar engines de banco suportados')
776
+ .description('[Staff] Listar engines de banco suportados')
754
777
  .option('--json')
755
778
  .action(async (opts) => {
756
779
  const { runDbEngines } = await import('./commands/products-db.js');
757
780
  await runDbEngines({ json: !!opts.json });
758
781
  });
759
- productsDbCmd
782
+ adminDatabaseCmd
760
783
  .command('create')
761
- .description('Criar banco de produto (Phase A: registra + audit)')
784
+ .description('[Staff] Registrar banco de produto no plano de controle (Phase A)')
762
785
  .option('--product-id <id>', 'ID do produto')
763
- .option('--label <label>', 'Nome descritivo do banco')
786
+ .option('--label <label>', 'nome descritivo do banco')
764
787
  .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')
788
+ .option('--env <env>', 'ambiente: dev-local | staging | production')
789
+ .option('--region <region>', 'região GCP', 'us-central1')
767
790
  .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')
791
+ .option('--replica-count <n>', 'número de réplicas para engines *-cluster (padrão 2)')
792
+ .option('--json', 'saída em JSON estruturado')
770
793
  .action(async (opts) => {
771
794
  const { runDbCreate } = await import('./commands/products-db.js');
772
795
  await runDbCreate({
@@ -780,26 +803,112 @@ productsDbCmd
780
803
  json: !!opts.json,
781
804
  });
782
805
  });
806
+ adminDatabaseCmd
807
+ .command('get [id]')
808
+ .description('[Staff] Mostrar detalhes de um banco')
809
+ .option('--json')
810
+ .action(async (id, opts) => {
811
+ const { runDbGet } = await import('./commands/products-db.js');
812
+ await runDbGet(id, { json: !!opts.json });
813
+ });
814
+ // D-2: `set-status` é ESCRITA explícita. `status` era ambíguo com o READ em `neetru db status`.
815
+ adminDatabaseCmd
816
+ .command('set-status [id] [status]')
817
+ .description('[Staff] ESCRITA — forçar status do ciclo de vida de um banco (active/failed/archived/…)')
818
+ .option('--reason <text>', 'motivo da mudança de status')
819
+ .option('--json')
820
+ .action(async (id, status, opts) => {
821
+ const { runDbSetStatus } = await import('./commands/products-db.js');
822
+ await runDbSetStatus(id, status, { reason: opts.reason, json: !!opts.json });
823
+ });
824
+ adminDatabaseCmd
825
+ .command('retry [id]')
826
+ .description('[Staff] Reenfileirar provisionamento de banco com falha')
827
+ .option('--json')
828
+ .action(async (id, opts) => {
829
+ const { runDbRetry } = await import('./commands/products-db.js');
830
+ await runDbRetry(id, { json: !!opts.json });
831
+ });
832
+ adminDatabaseCmd
833
+ .command('rotate [id]')
834
+ .description('[Staff] Rotacionar credenciais do banco')
835
+ .option('--json')
836
+ .action(async (id, opts) => {
837
+ const { runDbRotate } = await import('./commands/products-db.js');
838
+ await runDbRotate(id, { json: !!opts.json });
839
+ });
840
+ adminDatabaseCmd
841
+ .command('delete [id]')
842
+ .description('[Staff] Arquivar banco por soft-delete')
843
+ .option('--json')
844
+ .action(async (id, opts) => {
845
+ const { runDbDelete } = await import('./commands/products-db.js');
846
+ await runDbDelete(id, { json: !!opts.json });
847
+ });
848
+ // ── neetru products db (alias de remoção — D-1) ────────────────────────────
849
+ // Emite aviso informando o novo caminho. Mantém subcomandos funcionais por
850
+ // compatibilidade temporária mas deixa claro que foi renomeado.
851
+ const productsDbCmd = productsCmd
852
+ .command('db')
853
+ .description('[RENOMEADO] Use `neetru admin database`. Este alias emite aviso.')
854
+ .hook('preAction', () => {
855
+ process.stderr.write('\nAviso: `neetru products db` foi renomeado para `neetru admin database`.\n' +
856
+ 'Por favor, atualize seus scripts. Este alias será removido em breve.\n\n');
857
+ });
858
+ productsDbCmd
859
+ .command('list')
860
+ .description('[RENOMEADO — use: neetru admin database list]')
861
+ .option('--product-id <id>')
862
+ .option('--engine <engine>')
863
+ .option('--status <status>')
864
+ .option('--json')
865
+ .action(async (opts) => {
866
+ const { runDbList } = await import('./commands/products-db.js');
867
+ await runDbList({ productId: opts.productId, engine: opts.engine, status: opts.status, json: !!opts.json });
868
+ });
869
+ productsDbCmd
870
+ .command('engines')
871
+ .description('[RENOMEADO — use: neetru admin database engines]')
872
+ .option('--json')
873
+ .action(async (opts) => {
874
+ const { runDbEngines } = await import('./commands/products-db.js');
875
+ await runDbEngines({ json: !!opts.json });
876
+ });
877
+ productsDbCmd
878
+ .command('create')
879
+ .description('[RENOMEADO — use: neetru admin database create]')
880
+ .option('--product-id <id>')
881
+ .option('--label <label>')
882
+ .option('--engine <engine>')
883
+ .option('--env <env>')
884
+ .option('--region <region>', '', 'us-central1')
885
+ .option('--server-id <serverId>')
886
+ .option('--replica-count <n>')
887
+ .option('--json')
888
+ .action(async (opts) => {
889
+ const { runDbCreate } = await import('./commands/products-db.js');
890
+ 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 });
891
+ });
783
892
  productsDbCmd
784
893
  .command('get [id]')
785
- .description('Mostrar detalhes de um banco')
894
+ .description('[RENOMEADO use: neetru admin database get]')
786
895
  .option('--json')
787
896
  .action(async (id, opts) => {
788
897
  const { runDbGet } = await import('./commands/products-db.js');
789
898
  await runDbGet(id, { json: !!opts.json });
790
899
  });
791
900
  productsDbCmd
792
- .command('status [id] [status]')
793
- .description('Atualizar status de um banco de produto')
901
+ .command('set-status [id] [status]')
902
+ .description('[RENOMEADO use: neetru admin database set-status]')
794
903
  .option('--reason <text>')
795
904
  .option('--json')
796
905
  .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 });
906
+ const { runDbSetStatus } = await import('./commands/products-db.js');
907
+ await runDbSetStatus(id, status, { reason: opts.reason, json: !!opts.json });
799
908
  });
800
909
  productsDbCmd
801
910
  .command('retry [id]')
802
- .description('Reenfileirar provisionamento de banco')
911
+ .description('[RENOMEADO use: neetru admin database retry]')
803
912
  .option('--json')
804
913
  .action(async (id, opts) => {
805
914
  const { runDbRetry } = await import('./commands/products-db.js');
@@ -807,7 +916,7 @@ productsDbCmd
807
916
  });
808
917
  productsDbCmd
809
918
  .command('rotate [id]')
810
- .description('Rotacionar credenciais do banco (admin)')
919
+ .description('[RENOMEADO use: neetru admin database rotate]')
811
920
  .option('--json')
812
921
  .action(async (id, opts) => {
813
922
  const { runDbRotate } = await import('./commands/products-db.js');
@@ -815,7 +924,7 @@ productsDbCmd
815
924
  });
816
925
  productsDbCmd
817
926
  .command('delete [id]')
818
- .description('Arquivar banco por soft-delete (admin)')
927
+ .description('[RENOMEADO use: neetru admin database delete]')
819
928
  .option('--json')
820
929
  .action(async (id, opts) => {
821
930
  const { runDbDelete } = await import('./commands/products-db.js');
@@ -972,33 +1081,140 @@ envCmd
972
1081
  });
973
1082
  });
974
1083
  // ── neetru db ─────────────────────────────────────────────────────────
1084
+ // M1 — árvore completa de banco por produto.
1085
+ // Subcomandos novos: list, status, apply, migrations list/confirm.
1086
+ // Subcomandos legados (Sprint 10): init --out, migrate, seed.
975
1087
  const dbCmd = program
976
1088
  .command('db')
977
- .description('Gerenciar schema, migrations e seed do produto');
1089
+ .description('Gerenciar schema, migrations e banco isolado do produto');
1090
+ // ── neetru db list ────────────────────────────────────────────────────
1091
+ dbCmd
1092
+ .command('list')
1093
+ .description('Listar bancos do produto (GET /api/cli/v1/db)')
1094
+ .option('--product-id <id>', 'filtrar por ID de produto')
1095
+ .option('--engine <engine>', 'filtrar por engine (cloud-sql-postgres, vm-postgres-single, …)')
1096
+ .option('--status <status>', 'filtrar por status (ativo, provisionando, falha, arquivado)')
1097
+ .option('--json', 'saída em JSON estruturado')
1098
+ .action(async (opts) => {
1099
+ const { runDbList } = await import('./commands/db.js');
1100
+ await runDbList({
1101
+ productId: opts.productId,
1102
+ engine: opts.engine,
1103
+ status: opts.status,
1104
+ json: !!opts.json,
1105
+ });
1106
+ });
1107
+ // ── neetru db status <dbId> ───────────────────────────────────────────
1108
+ dbCmd
1109
+ .command('status <dbId>')
1110
+ .description('Detalhar banco + status de provisionamento/saúde')
1111
+ .option('--json', 'saída em JSON estruturado')
1112
+ .action(async (dbId, opts) => {
1113
+ const { runDbStatus } = await import('./commands/db.js');
1114
+ await runDbStatus(dbId, { json: !!opts.json });
1115
+ });
1116
+ // ── neetru db init ────────────────────────────────────────────────────
1117
+ // D-3: opções legadas --out e --force removidas. db init faz UMA coisa.
1118
+ // D-4: --env aceita dev-local (canônico) ou dev (alias explícito com aviso).
978
1119
  dbCmd
979
1120
  .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')
1121
+ .description('Registrar banco no Core + gravar .neetru/db.json + scaffoldar db/schema.ts')
1122
+ .option('--name <name>', 'nome do banco (interativo se ausente)')
1123
+ .option('--engine <engine>', 'engine: cloud-sql-postgres | vm-postgres-single | firestore-instance | …')
1124
+ .option('--env <env>', 'ambiente: dev-local (padrão) | staging | production')
1125
+ .option('--product-id <id>', 'override do productId (default: slug do neetru.config.json)')
1126
+ .option('-y, --yes', 'modo não-interativo (usa defaults)')
983
1127
  .action(async (opts) => {
984
1128
  const { runDbInit } = await import('./commands/db.js');
985
- await runDbInit({ out: opts.out, force: !!opts.force });
1129
+ await runDbInit({
1130
+ name: opts.name,
1131
+ engine: opts.engine,
1132
+ env: opts.env,
1133
+ productId: opts.productId,
1134
+ yes: !!opts.yes,
1135
+ });
986
1136
  });
1137
+ // ── neetru db apply ───────────────────────────────────────────────────
1138
+ // D-4: --env usa dev-local como default canônico.
987
1139
  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 });
1140
+ .command('apply')
1141
+ .description('Executar pipeline de migração + enviar ao Core (POST /api/cli/v1/db/migrations/push)')
1142
+ .option('--db <name>', 'banco alvo (interativo se mais de um registrado)')
1143
+ .option('--env <env>', 'ambiente alvo: dev-local (padrão) | staging | production')
1144
+ .option('--schema <path>', 'caminho do schema (default: db/schema.ts)')
1145
+ .option('--dry-run', 'calcula o diff mas NÃO envia ao Core')
1146
+ .option('-y, --yes', 'pula a confirmação interativa')
1147
+ .action(async (opts) => {
1148
+ const { runDbApply } = await import('./commands/db.js');
1149
+ await runDbApply({
1150
+ dbName: opts.db,
1151
+ env: opts.env,
1152
+ schemaPath: opts.schema,
1153
+ dryRun: !!opts.dryRun,
1154
+ yes: !!opts.yes,
1155
+ });
994
1156
  });
1157
+ // ── neetru db migrations ──────────────────────────────────────────────
1158
+ const dbMigrationsCmd = dbCmd
1159
+ .command('migrations')
1160
+ .description('Operar migrações de banco');
1161
+ dbMigrationsCmd
1162
+ .command('list')
1163
+ .description('Listar migrações registradas (GET /api/cli/v1/db/migrations)')
1164
+ .option('--db <dbId>', 'filtrar por ID do banco')
1165
+ .option('--json', 'saída em JSON estruturado')
1166
+ .action(async (opts) => {
1167
+ const { runDbMigrationsList } = await import('./commands/db.js');
1168
+ await runDbMigrationsList({ dbId: opts.db, json: !!opts.json });
1169
+ });
1170
+ dbMigrationsCmd
1171
+ .command('confirm <migrationId>')
1172
+ .description('Confirmar migração destrutiva (POST /api/cli/v1/db/migrations/[id]/confirm — exige MFA)')
1173
+ .requiredOption('--mfa <token>', 'código TOTP step-up MFA')
1174
+ .option('--json', 'saída em JSON')
1175
+ .action(async (migrationId, opts) => {
1176
+ const { runDbMigrationsConfirm } = await import('./commands/db.js');
1177
+ await runDbMigrationsConfirm(migrationId, { mfa: opts.mfa, json: !!opts.json });
1178
+ });
1179
+ // D-3: neetru db migrate removido (legado Sprint 10 cortado).
1180
+ // ── neetru db backups <dbId> ──────────────────────────────────────────
1181
+ dbCmd
1182
+ .command('backups <dbId>')
1183
+ .description('Listar backups de um banco (GET /api/cli/v1/db/[id]/backups)')
1184
+ .option('--json', 'saída em JSON estruturado')
1185
+ .action(async (dbId, opts) => {
1186
+ const { runDbBackups } = await import('./commands/db.js');
1187
+ await runDbBackups(dbId, { json: !!opts.json });
1188
+ });
1189
+ // ── neetru db restore <dbId> ──────────────────────────────────────────
995
1190
  dbCmd
996
- .command('seed')
997
- .description('Executar script de seed do produto')
998
- .option('--script <path>', 'caminho custom do script de seed')
1191
+ .command('restore <dbId>')
1192
+ .description('Restaurar banco a partir de backup (POST /api/cli/v1/db/[id]/restore — exige MFA)')
1193
+ .requiredOption('--mfa <token>', 'código TOTP step-up MFA (obrigatório — operação destrutiva)')
1194
+ .option('--json', 'saída em JSON')
1195
+ .action(async (dbId, opts) => {
1196
+ const { runDbRestore } = await import('./commands/db.js');
1197
+ await runDbRestore(dbId, { mfa: opts.mfa, json: !!opts.json });
1198
+ });
1199
+ // ── neetru db hosts ───────────────────────────────────────────────────
1200
+ // Alias: também acessível via `neetru db-hosts` (top-level).
1201
+ dbCmd
1202
+ .command('hosts')
1203
+ .description('Listar VM hosts de banco e densidade (GET /api/cli/v1/servers?capacity=true&db=true)')
1204
+ .option('--json', 'saída em JSON estruturado')
1205
+ .action(async (opts) => {
1206
+ const { runDbHosts } = await import('./commands/db.js');
1207
+ await runDbHosts({ json: !!opts.json });
1208
+ });
1209
+ // D-3: neetru db seed removido (legado Sprint 10 cortado).
1210
+ // ── neetru db-hosts (top-level alias de `neetru db hosts`) ───────────
1211
+ program
1212
+ .command('db-hosts')
1213
+ .description('Listar VM hosts de banco e densidade (alias de `neetru db hosts`)')
1214
+ .option('--json', 'saída em JSON estruturado')
999
1215
  .action(async (opts) => {
1000
- const { runDbSeed } = await import('./commands/db.js');
1001
- await runDbSeed({ script: opts.script });
1216
+ const { runDbHosts } = await import('./commands/db.js');
1217
+ await runDbHosts({ json: !!opts.json });
1002
1218
  });
1003
1219
  // ── neetru fn ─────────────────────────────────────────────────────────
1004
1220
  const fnCmd = program