@neetru/cli 2.31.0 → 2.32.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 (243) hide show
  1. package/CHANGELOG.md +91 -2
  2. package/README.md +2 -0
  3. package/dist/cli-kit/choices.d.ts +33 -0
  4. package/dist/cli-kit/choices.js +32 -0
  5. package/dist/cli-kit/command-context.d.ts +98 -0
  6. package/dist/cli-kit/command-context.js +108 -0
  7. package/dist/cli-kit/errors.d.ts +79 -0
  8. package/dist/cli-kit/errors.js +94 -0
  9. package/dist/cli-kit/index.d.ts +8 -0
  10. package/dist/cli-kit/index.js +8 -0
  11. package/dist/cli-kit/suggest.d.ts +74 -0
  12. package/dist/cli-kit/suggest.js +207 -0
  13. package/dist/commands/admin/oidc-login.js +1 -1
  14. package/dist/commands/admin/sdk-credential.js +1 -1
  15. package/dist/commands/autocomplete.d.ts +1 -1
  16. package/dist/commands/autocomplete.js +1 -0
  17. package/dist/commands/bug.d.ts +59 -0
  18. package/dist/commands/bug.js +114 -2
  19. package/dist/commands/build.d.ts +24 -1
  20. package/dist/commands/build.js +195 -73
  21. package/dist/commands/chat.d.ts +94 -1
  22. package/dist/commands/chat.js +258 -26
  23. package/dist/commands/customers.js +1 -1
  24. package/dist/commands/db-move.js +3 -3
  25. package/dist/commands/db.js +17 -24
  26. package/dist/commands/deploy.d.ts +91 -2
  27. package/dist/commands/deploy.js +414 -69
  28. package/dist/commands/deployments.js +11 -6
  29. package/dist/commands/dev-context.js +15 -5
  30. package/dist/commands/docs-produto.d.ts +25 -0
  31. package/dist/commands/docs-produto.js +70 -8
  32. package/dist/commands/doctor.js +36 -1
  33. package/dist/commands/domains.js +85 -4
  34. package/dist/commands/env.d.ts +9 -3
  35. package/dist/commands/env.js +23 -3
  36. package/dist/commands/infra-read.js +2 -2
  37. package/dist/commands/init.js +7 -1
  38. package/dist/commands/login.js +35 -8
  39. package/dist/commands/logs.js +12 -4
  40. package/dist/commands/mobile.js +33 -9
  41. package/dist/commands/new.d.ts +29 -1
  42. package/dist/commands/new.js +92 -4
  43. package/dist/commands/open.js +16 -6
  44. package/dist/commands/ops.js +1 -1
  45. package/dist/commands/products-db.d.ts +1 -0
  46. package/dist/commands/products-db.js +41 -8
  47. package/dist/commands/products.d.ts +17 -3
  48. package/dist/commands/products.js +84 -16
  49. package/dist/commands/promote.js +30 -5
  50. package/dist/commands/secrets.d.ts +91 -0
  51. package/dist/commands/secrets.js +471 -0
  52. package/dist/commands/servers.js +6 -4
  53. package/dist/commands/status.js +2 -2
  54. package/dist/commands/support.js +5 -3
  55. package/dist/commands/tenants.js +28 -16
  56. package/dist/commands/upgrade.d.ts +9 -0
  57. package/dist/commands/upgrade.js +50 -1
  58. package/dist/commands/vm-memory.js +1 -0
  59. package/dist/commands/webhooks.d.ts +2 -0
  60. package/dist/commands/webhooks.js +9 -3
  61. package/dist/commands/workspaces.js +42 -11
  62. package/dist/index.js +70 -14
  63. package/dist/lib/api-client.d.ts +14 -0
  64. package/dist/lib/api-client.js +59 -6
  65. package/dist/lib/api-error.d.ts +14 -0
  66. package/dist/lib/api-error.js +107 -46
  67. package/dist/lib/build-targets.d.ts +127 -0
  68. package/dist/lib/build-targets.js +348 -0
  69. package/dist/lib/cli-error.d.ts +13 -0
  70. package/dist/lib/cli-error.js +24 -0
  71. package/dist/lib/cli-write.d.ts +43 -9
  72. package/dist/lib/cli-write.js +154 -45
  73. package/dist/lib/config-schema.d.ts +8 -8
  74. package/dist/lib/db-local/db-json.d.ts +1 -1
  75. package/dist/lib/db-local/db-json.js +13 -5
  76. package/dist/lib/db-local/env.d.ts +15 -3
  77. package/dist/lib/db-local/env.js +37 -10
  78. package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
  79. package/dist/lib/db-pipeline/build-deps.js +23 -3
  80. package/dist/lib/db-pipeline/types.d.ts +6 -2
  81. package/dist/lib/db-pipeline/types.js +0 -18
  82. package/dist/lib/deploy-target-contract.d.ts +152 -0
  83. package/dist/lib/deploy-target-contract.js +411 -0
  84. package/dist/lib/env-file-do-build.d.ts +19 -0
  85. package/dist/lib/env-file-do-build.js +44 -0
  86. package/dist/lib/envs.d.ts +108 -0
  87. package/dist/lib/envs.js +127 -0
  88. package/dist/lib/fs-seguro.d.ts +63 -0
  89. package/dist/lib/fs-seguro.js +108 -0
  90. package/dist/lib/mobile-local/mobile-json.js +10 -4
  91. package/dist/lib/pickers.d.ts +44 -7
  92. package/dist/lib/pickers.js +60 -4
  93. package/dist/lib/product-docs/sync.d.ts +6 -0
  94. package/dist/lib/product-docs/sync.js +10 -1
  95. package/dist/lib/public-secret-scan.d.ts +44 -3
  96. package/dist/lib/public-secret-scan.js +361 -36
  97. package/dist/lib/render.d.ts +16 -0
  98. package/dist/lib/render.js +34 -4
  99. package/dist/lib/segredo-produto.d.ts +124 -0
  100. package/dist/lib/segredo-produto.js +336 -0
  101. package/dist/lib/texto-utf8.d.ts +96 -0
  102. package/dist/lib/texto-utf8.js +196 -0
  103. package/package.json +2 -1
  104. package/dist/cli-kit/format.js.map +0 -1
  105. package/dist/cli-kit/glyphs.js.map +0 -1
  106. package/dist/cli-kit/index.js.map +0 -1
  107. package/dist/cli-kit/palette.js.map +0 -1
  108. package/dist/commands/add.js.map +0 -1
  109. package/dist/commands/admin/iam-check.js.map +0 -1
  110. package/dist/commands/admin/oidc-login.js.map +0 -1
  111. package/dist/commands/admin/sdk-credential.js.map +0 -1
  112. package/dist/commands/agent-release.js.map +0 -1
  113. package/dist/commands/agent-write.js.map +0 -1
  114. package/dist/commands/ai-models.js.map +0 -1
  115. package/dist/commands/ai.js.map +0 -1
  116. package/dist/commands/api-catalog.js.map +0 -1
  117. package/dist/commands/archive.js.map +0 -1
  118. package/dist/commands/artifact-registry.js.map +0 -1
  119. package/dist/commands/audit.js.map +0 -1
  120. package/dist/commands/auth.js.map +0 -1
  121. package/dist/commands/autocomplete.js.map +0 -1
  122. package/dist/commands/billing.js.map +0 -1
  123. package/dist/commands/bootstrap.js.map +0 -1
  124. package/dist/commands/bug.js.map +0 -1
  125. package/dist/commands/build.js.map +0 -1
  126. package/dist/commands/chat-menu.js.map +0 -1
  127. package/dist/commands/chat.js.map +0 -1
  128. package/dist/commands/cloud-run.js.map +0 -1
  129. package/dist/commands/code.js.map +0 -1
  130. package/dist/commands/config.js.map +0 -1
  131. package/dist/commands/customers.js.map +0 -1
  132. package/dist/commands/db-move.js.map +0 -1
  133. package/dist/commands/db.js.map +0 -1
  134. package/dist/commands/deploy.js.map +0 -1
  135. package/dist/commands/deployments.js.map +0 -1
  136. package/dist/commands/dev-context.js.map +0 -1
  137. package/dist/commands/dev-up.js.map +0 -1
  138. package/dist/commands/dev.js.map +0 -1
  139. package/dist/commands/docs-produto.js.map +0 -1
  140. package/dist/commands/docs.js.map +0 -1
  141. package/dist/commands/doctor.js.map +0 -1
  142. package/dist/commands/domains.js.map +0 -1
  143. package/dist/commands/dr.js.map +0 -1
  144. package/dist/commands/env.js.map +0 -1
  145. package/dist/commands/hosting-write.js.map +0 -1
  146. package/dist/commands/infra-read.js.map +0 -1
  147. package/dist/commands/init.js.map +0 -1
  148. package/dist/commands/login.js.map +0 -1
  149. package/dist/commands/logout.js.map +0 -1
  150. package/dist/commands/logs.js.map +0 -1
  151. package/dist/commands/mobile.js.map +0 -1
  152. package/dist/commands/mocks.js.map +0 -1
  153. package/dist/commands/new.js.map +0 -1
  154. package/dist/commands/open.js.map +0 -1
  155. package/dist/commands/ops.js.map +0 -1
  156. package/dist/commands/products-db.js.map +0 -1
  157. package/dist/commands/products.js.map +0 -1
  158. package/dist/commands/promote.js.map +0 -1
  159. package/dist/commands/publish.js.map +0 -1
  160. package/dist/commands/schema.js.map +0 -1
  161. package/dist/commands/servers.js.map +0 -1
  162. package/dist/commands/smoke.js.map +0 -1
  163. package/dist/commands/status.js.map +0 -1
  164. package/dist/commands/support.js.map +0 -1
  165. package/dist/commands/surface-status.js.map +0 -1
  166. package/dist/commands/tenants.js.map +0 -1
  167. package/dist/commands/ui.js.map +0 -1
  168. package/dist/commands/upgrade.js.map +0 -1
  169. package/dist/commands/validate.js.map +0 -1
  170. package/dist/commands/vm-memory.js.map +0 -1
  171. package/dist/commands/vm.js.map +0 -1
  172. package/dist/commands/webhooks.js.map +0 -1
  173. package/dist/commands/whoami.js.map +0 -1
  174. package/dist/commands/workspaces.js.map +0 -1
  175. package/dist/index.js.map +0 -1
  176. package/dist/lib/agent-artifact-url.js.map +0 -1
  177. package/dist/lib/agent-commands.js.map +0 -1
  178. package/dist/lib/ai/auth-discovery.js.map +0 -1
  179. package/dist/lib/ai/context.js.map +0 -1
  180. package/dist/lib/ai/orchestrator.js.map +0 -1
  181. package/dist/lib/api-client.js.map +0 -1
  182. package/dist/lib/api-error.js.map +0 -1
  183. package/dist/lib/auth.js.map +0 -1
  184. package/dist/lib/cli-read.js.map +0 -1
  185. package/dist/lib/cli-write.js.map +0 -1
  186. package/dist/lib/code-agent-budget.js.map +0 -1
  187. package/dist/lib/code-agent-files.js.map +0 -1
  188. package/dist/lib/code-agent-gateway.js.map +0 -1
  189. package/dist/lib/code-agent-git-tools.js.map +0 -1
  190. package/dist/lib/code-agent-protocol.js.map +0 -1
  191. package/dist/lib/code-agent-session.js.map +0 -1
  192. package/dist/lib/code-agent-settings.js.map +0 -1
  193. package/dist/lib/code-agent-skills.js.map +0 -1
  194. package/dist/lib/code-agent-subagents.js.map +0 -1
  195. package/dist/lib/code-agent-tools.js.map +0 -1
  196. package/dist/lib/code-agent-tui.js.map +0 -1
  197. package/dist/lib/code-agent-types.js.map +0 -1
  198. package/dist/lib/code-agent-ui.js.map +0 -1
  199. package/dist/lib/code-agent-web-tools.js.map +0 -1
  200. package/dist/lib/code-agent-write-tools.js.map +0 -1
  201. package/dist/lib/compose-generator.js.map +0 -1
  202. package/dist/lib/config-schema.js.map +0 -1
  203. package/dist/lib/config.js.map +0 -1
  204. package/dist/lib/db-local/db-json.js.map +0 -1
  205. package/dist/lib/db-local/env.js.map +0 -1
  206. package/dist/lib/db-local/fingerprint.js.map +0 -1
  207. package/dist/lib/db-local/index.js.map +0 -1
  208. package/dist/lib/db-pipeline/build-deps.js.map +0 -1
  209. package/dist/lib/db-pipeline/errors.js.map +0 -1
  210. package/dist/lib/db-pipeline/index.js.map +0 -1
  211. package/dist/lib/db-pipeline/pipeline.js.map +0 -1
  212. package/dist/lib/db-pipeline/rehearse.js.map +0 -1
  213. package/dist/lib/db-pipeline/types.js.map +0 -1
  214. package/dist/lib/dev-local-context.js.map +0 -1
  215. package/dist/lib/iam-preflight.js.map +0 -1
  216. package/dist/lib/mobile-local/index.js.map +0 -1
  217. package/dist/lib/mobile-local/mobile-json.js.map +0 -1
  218. package/dist/lib/pickers.js.map +0 -1
  219. package/dist/lib/product-config.js.map +0 -1
  220. package/dist/lib/product-docs/check.js.map +0 -1
  221. package/dist/lib/product-docs/config.js.map +0 -1
  222. package/dist/lib/product-docs/dates.js.map +0 -1
  223. package/dist/lib/product-docs/frontmatter.js.map +0 -1
  224. package/dist/lib/product-docs/git.js.map +0 -1
  225. package/dist/lib/product-docs/markdown.js.map +0 -1
  226. package/dist/lib/product-docs/ops.js.map +0 -1
  227. package/dist/lib/product-docs/structure.js.map +0 -1
  228. package/dist/lib/product-docs/sync.js.map +0 -1
  229. package/dist/lib/product-docs/taxonomy.js.map +0 -1
  230. package/dist/lib/product-docs/templates.js.map +0 -1
  231. package/dist/lib/public-secret-scan.js.map +0 -1
  232. package/dist/lib/render.js.map +0 -1
  233. package/dist/lib/schema-cache.js.map +0 -1
  234. package/dist/lib/sse.js.map +0 -1
  235. package/dist/lib/telemetry.js.map +0 -1
  236. package/dist/lib/token-resolver.js.map +0 -1
  237. package/dist/lib/url-allowlist.js.map +0 -1
  238. package/dist/lib/version-check.js.map +0 -1
  239. package/dist/utils/logger.js.map +0 -1
  240. package/dist/utils/logo.js.map +0 -1
  241. package/dist/utils/safe-exit.js.map +0 -1
  242. package/dist/utils/spinner-registry.js.map +0 -1
  243. package/dist/version.js.map +0 -1
@@ -53,7 +53,7 @@ export async function runServersList(opts) {
53
53
  }
54
54
  log.heading(`Servers (${res.count})`);
55
55
  const columns = [
56
- { header: 'ID', value: (r) => r.id, maxWidth: 22 },
56
+ { header: 'ID', value: (r) => r.id, identificador: true },
57
57
  { header: 'NOME', value: (r) => r.name ?? '—', maxWidth: 24 },
58
58
  { header: 'PROVIDER', value: (r) => r.provider ?? '—' },
59
59
  { header: 'REGIÃO', value: (r) => r.region ?? '—' },
@@ -100,7 +100,7 @@ export async function runServersFleetStatus(opts) {
100
100
  return chalk.green(`${pct}%`);
101
101
  };
102
102
  renderTable([
103
- { header: 'ID', value: (r) => r.id, maxWidth: 22 },
103
+ { header: 'ID', value: (r) => r.id, identificador: true },
104
104
  { header: 'NOME', value: (r) => r.name ?? '—', maxWidth: 20 },
105
105
  { header: 'ROLE', value: (r) => r.role ?? 'multi' },
106
106
  { header: 'STATUS', value: (r) => r.status ?? '—' },
@@ -321,6 +321,7 @@ export async function runServersDeactivate(id, opts) {
321
321
  const token = await requireToken(opts.json);
322
322
  const serverId = await pickServer(token, {
323
323
  fromFlag: id,
324
+ inputs: ['<serverId>'],
324
325
  json: opts.json,
325
326
  message: 'Server a desativar:',
326
327
  });
@@ -428,6 +429,7 @@ export async function runServersDispatch(id, commandType, opts) {
428
429
  const token = await requireToken(opts.json);
429
430
  const serverId = await pickServer(token, {
430
431
  fromFlag: id,
432
+ inputs: ['<serverId>'],
431
433
  json: opts.json,
432
434
  message: 'Server alvo:',
433
435
  });
@@ -603,13 +605,13 @@ export async function runServersPgbouncerSyncUserlist(serverId, opts = {}) {
603
605
  log.success(`Userlist do PgBouncer sincronizado na VM ${resp.serverId} (mode ${resp.mode}).`);
604
606
  }
605
607
  renderTable([
606
- { header: 'DB', value: (r) => r.dbId, maxWidth: 24 },
608
+ { header: 'DB', value: (r) => r.dbId, identificador: true },
607
609
  { header: 'PAPÉIS', value: (r) => r.roles.join(', '), maxWidth: 50 },
608
610
  ], plan.databases);
609
611
  if (plan.skipped.length > 0) {
610
612
  log.heading('Bancos deixados de fora (papel fora do formato)');
611
613
  renderTable([
612
- { header: 'DB', value: (r) => r.dbId, maxWidth: 24 },
614
+ { header: 'DB', value: (r) => r.dbId, identificador: true },
613
615
  { header: 'MOTIVO', value: (r) => r.reason, maxWidth: 50 },
614
616
  ], plan.skipped);
615
617
  }
@@ -82,11 +82,11 @@ export async function runStatus(opts) {
82
82
  }
83
83
  console.log();
84
84
  console.log(chalk.bold(' Last Build'));
85
- console.log(` ${chalk.dim(' ID:')} ${status.lastBuildId ? chalk.cyan(status.lastBuildId.slice(0, 12)) : chalk.dim('—')}`);
85
+ console.log(` ${chalk.dim(' ID:')} ${status.lastBuildId ? chalk.cyan(status.lastBuildId) : chalk.dim('—')}`);
86
86
  console.log(` ${chalk.dim(' Status:')} ${colorStatus(status.lastBuildStatus)}`);
87
87
  console.log();
88
88
  console.log(chalk.bold(' Last Deployment'));
89
- console.log(` ${chalk.dim(' ID:')} ${status.lastDeploymentId ? chalk.cyan(status.lastDeploymentId.slice(0, 12)) : chalk.dim('—')}`);
89
+ console.log(` ${chalk.dim(' ID:')} ${status.lastDeploymentId ? chalk.cyan(status.lastDeploymentId) : chalk.dim('—')}`);
90
90
  console.log(` ${chalk.dim(' Status:')} ${colorStatus(status.lastDeploymentStatus)}`);
91
91
  if (status.expiresAt) {
92
92
  const expiresIn = Math.ceil((new Date(status.expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24));
@@ -66,13 +66,15 @@ export async function runSupportTicketsList(opts) {
66
66
  return;
67
67
  }
68
68
  renderTable([
69
- { header: 'ID', value: (t) => t.id, maxWidth: 18 },
69
+ { header: 'ID', value: (t) => t.id, identificador: true },
70
70
  { header: 'TÍTULO', value: (t) => t.title ?? '—', maxWidth: 36 },
71
71
  { header: 'STATUS', value: (t) => colorStatus(t.status) },
72
72
  { header: 'SEVERITY', value: (t) => t.severity ?? '—' },
73
- { header: 'PRODUTO', value: (t) => t.productId ?? '—', maxWidth: 18 },
73
+ { header: 'PRODUTO', value: (t) => t.productId ?? '—', identificador: true },
74
74
  { header: 'SLA', value: (t) => (t.slaBreached ? chalk.red('estourado') : 'ok') },
75
- { header: 'ASSIGNED', value: (t) => t.assignedTo ?? chalk.dim('—'), maxWidth: 22 },
75
+ // uid de 28 caracteres (o mesmo que `bug list/claim --assigned-to` aceita):
76
+ // cortado em 22 virava um uid que não existe (revisão do #75).
77
+ { header: 'ASSIGNED', value: (t) => t.assignedTo ?? chalk.dim('—'), identificador: true },
76
78
  ], res.tickets);
77
79
  console.log();
78
80
  }
@@ -14,6 +14,7 @@ import { requireToken, handleApiError } from '../lib/cli-read.js';
14
14
  import { confirmDestructive, printWriteResult, } from '../lib/cli-write.js';
15
15
  import { renderTable, fmtTimestamp } from '../lib/render.js';
16
16
  import { pickProductId, pickTenant, promptChoice, promptText, resolveCustomer } from '../lib/pickers.js';
17
+ import { DEFAULT_TENANT_ENVIRONMENT_ID, ESCOLHAS_AMBIENTE_TENANT, resolverAmbienteTenant, } from '../lib/envs.js';
17
18
  import { log } from '../utils/logger.js';
18
19
  function colorStatus(s) {
19
20
  if (!s)
@@ -52,11 +53,11 @@ export async function runTenantsList(opts) {
52
53
  }
53
54
  log.heading(`Tenants (${res.count})`);
54
55
  renderTable([
55
- { header: 'ID', value: (r) => r.id, maxWidth: 22 },
56
+ { header: 'ID', value: (r) => r.id, identificador: true },
56
57
  { header: 'NOME', value: (r) => r.name ?? '—', maxWidth: 28 },
57
58
  { header: 'STATUS', value: (r) => colorStatus(r.status) },
58
- { header: 'PRODUTO', value: (r) => r.productId ?? '—', maxWidth: 22 },
59
- { header: 'ENV', value: (r) => r.environmentId ?? '—' },
59
+ { header: 'PRODUTO', value: (r) => r.productId ?? '—', identificador: true },
60
+ { header: 'ENV', value: (r) => r.environmentId ?? '—', identificador: true },
60
61
  { header: 'DOMÍNIO', value: (r) => r.primaryDomain ?? '—', maxWidth: 30 },
61
62
  ], res.tenants);
62
63
  console.log();
@@ -65,6 +66,7 @@ export async function runTenantsGet(id, opts) {
65
66
  const token = await requireToken(opts.json);
66
67
  const tenantId = await pickTenant(token, {
67
68
  fromFlag: id,
69
+ inputs: ['<id>'],
68
70
  json: opts.json,
69
71
  message: 'Tenant:',
70
72
  });
@@ -107,7 +109,7 @@ function warnTenantsCreateDeprecated(json) {
107
109
  return; // em modo machine não printa texto fora do JSON
108
110
  console.error(chalk.yellow('\n⚠ AVISO: `neetru tenants create` é para uso staff-only (cria tenant SEM OAuth client/secret).\n' +
109
111
  ' Para criar um ambiente completo (tenant + OAuth + secret), use:\n\n' +
110
- ' neetru workspaces create --product <id> --env <dev|staging|prod>\n\n' +
112
+ ' neetru workspaces create --product <id> --env <dev-local|staging|prod>\n\n' +
111
113
  ' Se você é staff e sabe o que está fazendo, ignore este aviso.\n'));
112
114
  }
113
115
  export async function runTenantsCreate(opts) {
@@ -128,12 +130,16 @@ export async function runTenantsCreate(opts) {
128
130
  productId: product,
129
131
  token,
130
132
  });
131
- const env = opts.env ??
132
- (await promptChoice('Ambiente:', [
133
- { name: 'dev', value: 'dev' },
134
- { name: 'staging', value: 'staging' },
135
- { name: 'prod', value: 'prod' },
136
- ], { default: 'dev', json: opts.json }));
133
+ // bug_7dcb68e2666a43f69ef362d2cbff94ed: 'dev' nao existe no enum do Core
134
+ // (['dev-local','staging','prod']) e as rotas de tenant comparam por
135
+ // igualdade ESTRITA -> 400 validation_failed. O alfabeto agora vem de
136
+ // ../lib/envs.js, que tambem traduz o apelido historico 'dev' -> 'dev-local'.
137
+ const env = opts.env
138
+ ? resolverAmbienteTenant(opts.env, opts.json)
139
+ : (await promptChoice('Ambiente:', [...ESCOLHAS_AMBIENTE_TENANT], {
140
+ default: DEFAULT_TENANT_ENVIRONMENT_ID,
141
+ json: opts.json,
142
+ }));
137
143
  const spinner = opts.json ? null : ora({ text: 'Criando tenant…', color: 'blue' }).start();
138
144
  let res;
139
145
  try {
@@ -161,6 +167,7 @@ export async function runTenantsUpdate(id, opts) {
161
167
  const token = await requireToken(opts.json);
162
168
  const tenantId = await pickTenant(token, {
163
169
  fromFlag: id,
170
+ inputs: ['<id>'],
164
171
  json: opts.json,
165
172
  message: 'Tenant a atualizar:',
166
173
  });
@@ -172,12 +179,16 @@ export async function runTenantsUpdate(id, opts) {
172
179
  json: opts.json,
173
180
  message: 'Produto do tenant:',
174
181
  });
175
- const env = opts.env ??
176
- (await promptChoice('Ambiente:', [
177
- { name: 'dev', value: 'dev' },
178
- { name: 'staging', value: 'staging' },
179
- { name: 'prod', value: 'prod' },
180
- ], { default: 'dev', json: opts.json }));
182
+ // bug_7dcb68e2666a43f69ef362d2cbff94ed: 'dev' nao existe no enum do Core
183
+ // (['dev-local','staging','prod']) e as rotas de tenant comparam por
184
+ // igualdade ESTRITA -> 400 validation_failed. O alfabeto agora vem de
185
+ // ../lib/envs.js, que tambem traduz o apelido historico 'dev' -> 'dev-local'.
186
+ const env = opts.env
187
+ ? resolverAmbienteTenant(opts.env, opts.json)
188
+ : (await promptChoice('Ambiente:', [...ESCOLHAS_AMBIENTE_TENANT], {
189
+ default: DEFAULT_TENANT_ENVIRONMENT_ID,
190
+ json: opts.json,
191
+ }));
181
192
  const spinner = opts.json ? null : ora({ text: `Atualizando tenant ${tenantId}...`, color: 'blue' }).start();
182
193
  try {
183
194
  await apiRequest(`/api/cli/v1/tenants/${encodeURIComponent(tenantId)}`, {
@@ -204,6 +215,7 @@ async function runTenantsStatusChange(id, action, opts) {
204
215
  const token = await requireToken(opts.json);
205
216
  const tenantId = await pickTenant(token, {
206
217
  fromFlag: id,
218
+ inputs: ['<id>'],
207
219
  json: opts.json,
208
220
  message: action === 'suspend' ? 'Tenant a suspender:' : 'Tenant a reativar:',
209
221
  });
@@ -8,5 +8,14 @@ export interface UpgradeReport {
8
8
  upgradeAvailable: boolean;
9
9
  message: string;
10
10
  }
11
+ /**
12
+ * Compara duas versões semver: >0 se `a` for maior, <0 se menor, 0 se iguais.
13
+ *
14
+ * Sem dependência de propósito — a CLI não carrega `semver` e este caso não
15
+ * justifica puxar um pacote. Pré-lançamento (`1.2.3-rc.1`) é tratado como
16
+ * MENOR que o lançamento (`1.2.3`), que é a regra do semver e é a que importa
17
+ * aqui: um rc nunca deve pedir upgrade sobre o estável de mesmo número.
18
+ */
19
+ export declare function compareSemver(a: string, b: string): number;
11
20
  export declare function fetchLatest(fetchImpl?: typeof fetch): Promise<string | null>;
12
21
  export declare function runUpgrade(opts?: UpgradeOptions): Promise<void>;
@@ -14,6 +14,34 @@
14
14
  import chalk from 'chalk';
15
15
  import { CLI_VERSION } from '../version.js';
16
16
  const NPM_REGISTRY = 'https://registry.npmjs.org/@neetru/cli';
17
+ /**
18
+ * Compara duas versões semver: >0 se `a` for maior, <0 se menor, 0 se iguais.
19
+ *
20
+ * Sem dependência de propósito — a CLI não carrega `semver` e este caso não
21
+ * justifica puxar um pacote. Pré-lançamento (`1.2.3-rc.1`) é tratado como
22
+ * MENOR que o lançamento (`1.2.3`), que é a regra do semver e é a que importa
23
+ * aqui: um rc nunca deve pedir upgrade sobre o estável de mesmo número.
24
+ */
25
+ export function compareSemver(a, b) {
26
+ const parse = (v) => {
27
+ const [nucleo = '', pre = ''] = v.replace(/^v/, '').split('-', 2);
28
+ const nums = nucleo.split('.').map((n) => Number.parseInt(n, 10) || 0);
29
+ return { nums: [nums[0] ?? 0, nums[1] ?? 0, nums[2] ?? 0], pre };
30
+ };
31
+ const pa = parse(a);
32
+ const pb = parse(b);
33
+ for (let i = 0; i < 3; i++) {
34
+ if (pa.nums[i] !== pb.nums[i])
35
+ return pa.nums[i] - pb.nums[i];
36
+ }
37
+ if (pa.pre === pb.pre)
38
+ return 0;
39
+ if (pa.pre === '')
40
+ return 1; // lançamento > pré-lançamento
41
+ if (pb.pre === '')
42
+ return -1;
43
+ return pa.pre < pb.pre ? -1 : 1;
44
+ }
17
45
  export async function fetchLatest(fetchImpl = fetch) {
18
46
  const ctrl = new AbortController();
19
47
  const timer = setTimeout(() => ctrl.abort(), 5000);
@@ -37,7 +65,22 @@ export async function runUpgrade(opts = {}) {
37
65
  catch {
38
66
  networkError = true;
39
67
  }
40
- const upgradeAvailable = latest !== null && latest !== CLI_VERSION;
68
+ // 2026-10-12: era `latest !== CLI_VERSION`, e isso mandava quem estava
69
+ // ADIANTE voltar atrás.
70
+ //
71
+ // Caso real e vivo: a 2.31.0 está publicada no npm desde 2026-10-09, mas o
72
+ // dist-tag `latest` aponta para a 2.29.0 (trava deliberada do dono — a
73
+ // varredura de segredo da 2.30.0 é fail-closed e pararia o deploy de quem
74
+ // ainda carrega chave no bundle do navegador). Quem instalou a 2.31.0 lia
75
+ // "Versão 2.29.0 disponível. Atualize com: npm install -g @neetru/cli@latest"
76
+ // e, obedecendo, fazia DOWNGRADE — perdendo os consertos que tinha.
77
+ //
78
+ // A comparação agora é por precedência semver: só sugere quando `latest` é
79
+ // de fato MAIOR. Quando é menor, a mensagem diz a verdade (você está à
80
+ // frente) em vez de mandar voltar.
81
+ const comparado = latest !== null ? compareSemver(latest, CLI_VERSION) : 0;
82
+ const upgradeAvailable = latest !== null && comparado > 0;
83
+ const estaAdiante = latest !== null && comparado < 0;
41
84
  let message;
42
85
  if (networkError || latest === null) {
43
86
  message = 'Não foi possível verificar latest (offline?). Tente mais tarde.';
@@ -45,6 +88,12 @@ export async function runUpgrade(opts = {}) {
45
88
  else if (upgradeAvailable) {
46
89
  message = `Versão ${latest} disponível. Atualize com: npm install -g @neetru/cli@latest`;
47
90
  }
91
+ else if (estaAdiante) {
92
+ // Não mandar voltar: quem está à frente perderia conserto ao "atualizar".
93
+ message =
94
+ `Você está na ${CLI_VERSION}, à frente da tag latest (${latest}). ` +
95
+ 'Nada a fazer — instalar a latest seria um downgrade.';
96
+ }
48
97
  else {
49
98
  message = `Você já está na latest (${CLI_VERSION}).`;
50
99
  }
@@ -123,6 +123,7 @@ export async function runVmMemoryStatus(serverIdArg, opts) {
123
123
  const token = await requireToken(opts.json);
124
124
  const serverId = await pickServer(token, {
125
125
  fromFlag: serverIdArg ?? opts.server,
126
+ inputs: ['<serverId>', '--server'],
126
127
  json: opts.json,
127
128
  message: 'VM alvo:',
128
129
  capacity: true,
@@ -17,6 +17,8 @@ export interface WebhooksUnregisterOptions {
17
17
  yes?: boolean;
18
18
  force?: boolean;
19
19
  json?: boolean;
20
+ /** `--mfa-token <código>` — TOTP do step-up MFA (dispensado com sessão de 1h). */
21
+ mfaToken?: string;
20
22
  }
21
23
  export declare function runWebhooksUnregister(opts: WebhooksUnregisterOptions): Promise<void>;
22
24
  export interface WebhooksTestOptions {
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { apiRequest } from '../lib/api-client.js';
4
4
  import { requireToken, handleApiError } from '../lib/cli-read.js';
5
- import { confirmDestructive } from '../lib/cli-write.js';
5
+ import { confirmDestructive, resolveStepUp } from '../lib/cli-write.js';
6
6
  import { renderTable } from '../lib/render.js';
7
7
  import { log } from '../utils/logger.js';
8
8
  const ALL_EVENTS = [
@@ -69,7 +69,7 @@ export async function runWebhooksList(opts) {
69
69
  }
70
70
  log.heading(`Webhooks — ${res.productId} (${res.count})`);
71
71
  renderTable([
72
- { header: 'ID', value: (e) => e.id, maxWidth: 24 },
72
+ { header: 'ID', value: (e) => e.id, identificador: true },
73
73
  { header: 'URL', value: (e) => e.url, maxWidth: 48 },
74
74
  { header: 'EVENTOS', value: (e) => e.events.join(', '), maxWidth: 36 },
75
75
  { header: 'SECRET', value: (e) => (e.hasSecret ? chalk.green('sim') : chalk.yellow('não')) },
@@ -85,10 +85,16 @@ export async function runWebhooksUnregister(opts) {
85
85
  // Gate igual ao resto das destrutivas: confirmDestructive (y/N, bypass
86
86
  // --yes/--force, fail-closed em --json/não-TTY exigindo --yes).
87
87
  await confirmDestructive(`Remover o webhook ${opts.id} do produto ${opts.productId}? Apaga a config e o histórico de entregas (irreversível).`, { yes: opts.yes, force: opts.force, json: opts.json });
88
+ // A7 (2026-10-13): o Core exige step-up MFA neste DELETE desde 2026-08-20
89
+ // (`requireCliStepUpMfa` em `webhooks/[id]/route.ts`, commit 7d37208d). O
90
+ // comando nunca mandou o cabeçalho — 401 `mfa_required` sempre, com ou sem
91
+ // `neetru auth mfa-session`. Mesmo caminho dos outros destrutivos: `--mfa-token`
92
+ // → sessão de 1h → prompt TOTP (fail-closed em --json/não-TTY).
93
+ const { headers } = await resolveStepUp({ mfaToken: opts.mfaToken, json: opts.json });
88
94
  const spinner = opts.json ? null : ora({ text: 'Removendo webhook…', color: 'yellow' }).start();
89
95
  let res;
90
96
  try {
91
- res = await apiRequest(`/api/cli/v1/webhooks/${encodeURIComponent(opts.id)}?productId=${encodeURIComponent(opts.productId)}`, { method: 'DELETE', token });
97
+ res = await apiRequest(`/api/cli/v1/webhooks/${encodeURIComponent(opts.id)}?productId=${encodeURIComponent(opts.productId)}`, { method: 'DELETE', token, headers });
92
98
  spinner?.stop();
93
99
  }
94
100
  catch (error) {
@@ -2,12 +2,22 @@
2
2
  * `neetru workspaces create|advance|list|get|open` — control plane de
3
3
  * workspaces (runtime shared `neetru-workspaces`).
4
4
  *
5
- * Endpoints:
5
+ * Endpoints (conferidos contra o Core em 2026-10-12):
6
6
  * POST /api/cli/v1/workspaces (create)
7
7
  * POST /api/cli/v1/workspaces/{id}/advance (advance — promove bundle)
8
- * GET /api/cli/v1/workspaces (list — filtros productId/env/status)
9
- * GET /api/cli/v1/workspaces/{id} (get)
8
+ * GET /api/cli/v1/tenants (list — filtros product/status;
9
+ * env filtrado aqui, normalizado)
10
+ * GET /api/cli/v1/tenants/{id} (get)
10
11
  * (open) NÃO chama API — só monta a URL do painel e abre o browser.
12
+ *
13
+ * Este cabeçalho documentava `GET /workspaces` e `GET /workspaces/{id}`, e o
14
+ * código chamava `/tenants`. Conferido: `GET /workspaces/{id}` NÃO EXISTE no
15
+ * Core (só `advance` e `retry`). E `GET /workspaces` exige papel
16
+ * admin/operator/viewer, esconde os arquivados e devolve a lista em `items` —
17
+ * trocar a rota aqui deixaria a lista vazia e daria 403 a credencial
18
+ * `billing`, que hoje lista. Por isso a leitura FICA em `/tenants`; as colunas
19
+ * TIER e PRODUTO (slug), que nunca preenchiam, passam a vir do próprio
20
+ * `/tenants` no Core (mudança aditiva lá, sem quebrar ninguém aqui).
11
21
  */
12
22
  import * as fs from 'node:fs/promises';
13
23
  import * as path from 'node:path';
@@ -19,6 +29,8 @@ import { config } from '../lib/config.js';
19
29
  import { requireToken, handleApiError, printWriteResult } from '../lib/cli-write.js';
20
30
  import { renderTable, fmtTimestamp } from '../lib/render.js';
21
31
  import { pickProductId, pickProductSlug, pickWorkspace, resolveProductIdentifier, isInteractive, promptChoice, promptText, resolveCustomer, } from '../lib/pickers.js';
32
+ import { DEFAULT_TENANT_ENVIRONMENT_ID, ESCOLHAS_AMBIENTE_TENANT, resolverAmbienteTenant, erroAmbienteTenantInvalido, normalizarAmbienteTenant, } from '../lib/envs.js';
33
+ import { exitWithCliError } from '../lib/cli-error.js';
22
34
  import { log } from '../utils/logger.js';
23
35
  import { safeExit } from '../utils/safe-exit.js';
24
36
  /** Formata o erro 400 "Nenhum customer ativo" de forma acionável. */
@@ -53,12 +65,18 @@ export async function runWorkspacesCreate(opts) {
53
65
  productId: product,
54
66
  token,
55
67
  });
56
- const env = opts.env ??
57
- (await promptChoice('Ambiente:', [
58
- { name: 'dev', value: 'dev' },
59
- { name: 'staging', value: 'staging' },
60
- { name: 'prod', value: 'prod' },
61
- ], { default: 'dev', json: opts.json }));
68
+ // bug_7dcb68e2666a43f69ef362d2cbff94ed: o prompt oferecia 'dev' e usava 'dev'
69
+ // como padrão, mas POST /api/cli/v1/workspaces compara por igualdade ESTRITA
70
+ // contra ['dev-local','staging','prod'] e devolve 400 validation_failed —
71
+ // ou seja, aceitar o default sugerido pela própria ferramenta não criava
72
+ // workspace nenhum. O alfabeto agora vem de ../lib/envs.js e um `--env dev`
73
+ // digitado à mão é traduzido para o token canônico em vez de ir cru.
74
+ const env = opts.env
75
+ ? resolverAmbienteTenant(opts.env, opts.json)
76
+ : (await promptChoice('Ambiente:', [...ESCOLHAS_AMBIENTE_TENANT], {
77
+ default: DEFAULT_TENANT_ENVIRONMENT_ID,
78
+ json: opts.json,
79
+ }));
62
80
  // Bug #40: detecta tenant ativo existente para produto+env antes de criar.
63
81
  if (!opts.forceDuplicate) {
64
82
  const dupSpinner = opts.json
@@ -270,7 +288,20 @@ export async function runWorkspacesList(opts) {
270
288
  res = await apiRequest(`/api/cli/v1/tenants${qs ? `?${qs}` : ''}`, { token });
271
289
  rows = res.workspaces ?? res.tenants ?? [];
272
290
  if (opts.env) {
273
- rows = rows.filter((r) => (r.env ?? r.environmentId) === opts.env);
291
+ // 5a copia do defeito do `--env` (2026-10-12). O conserto anterior
292
+ // centralizou o alfabeto e corrigiu os quatro comandos de ESCRITA — este
293
+ // filtro de LEITURA ficou comparando cru. Resultado: `--env dev` devolvia
294
+ // ZERO linhas em silencio, porque o Core grava `dev-local` e `dev` nunca
295
+ // casa. Zero resultado e indistinguivel de "nao existe nenhum".
296
+ const alvo = normalizarAmbienteTenant(opts.env);
297
+ if (alvo === null) {
298
+ spinner?.stop();
299
+ // bug_89d2497e: mesmo erro tipado dos comandos de escrita — com
300
+ // `--json`, sai o JSON com os valores válidos em vez de só stderr.
301
+ exitWithCliError(erroAmbienteTenantInvalido(opts.env), opts.json);
302
+ return;
303
+ }
304
+ rows = rows.filter((r) => (r.env ?? r.environmentId) === alvo);
274
305
  }
275
306
  spinner?.stop();
276
307
  }
@@ -284,7 +315,7 @@ export async function runWorkspacesList(opts) {
284
315
  }
285
316
  log.heading(`Workspaces (${rows.length})`);
286
317
  renderTable([
287
- { header: 'ID', value: (r) => r.id ?? r.workspaceId ?? '—', maxWidth: 24 },
318
+ { header: 'ID', value: (r) => r.id ?? r.workspaceId ?? '—', identificador: true },
288
319
  { header: 'PRODUTO', value: (r) => r.productSlug ?? r.productId ?? '—', maxWidth: 22 },
289
320
  { header: 'ENV', value: (r) => r.env ?? r.environmentId ?? '—', maxWidth: 10 },
290
321
  { header: 'TIER', value: (r) => r.tier ?? r.resourceTier ?? '—', maxWidth: 12 },