onveloz 0.0.0-beta.39 → 0.0.0-beta.40

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 (2) hide show
  1. package/dist/index.mjs +609 -82
  2. package/package.json +5 -6
package/dist/index.mjs CHANGED
@@ -34,7 +34,7 @@ const SERVICE_TYPES = [
34
34
  "WORKER",
35
35
  "DATABASE"
36
36
  ];
37
- const BUILD_TIMEOUT_MS = 600 * 1e3;
37
+ const BUILD_TIMEOUT_MS = 2700 * 1e3;
38
38
  const DEPLOY_TIMEOUT_MS = 300 * 1e3;
39
39
  const DATABASE_PROVISION_TIMEOUT_MS = 300 * 1e3;
40
40
  const DATABASE_WAITING_ON_PROVIDER_AFTER_MS = 60 * 1e3;
@@ -307,6 +307,18 @@ const PROJECT_FLAG_DEFINITIONS = {
307
307
  }
308
308
  };
309
309
  const PROJECT_FLAG_KEYS = Object.keys(PROJECT_FLAG_DEFINITIONS);
310
+ /** All known boolean org flags. Used for validation and UI rendering. */
311
+ const ORGANIZATION_FLAG_DEFINITIONS = {
312
+ browserApi: {
313
+ label: "Browser API",
314
+ description: "Concede a esta organização acesso à API de navegador (sessões de navegador gerenciadas). Sem a flag, o recurso fica restrito a administradores da plataforma."
315
+ },
316
+ managedAuth: {
317
+ label: "Autenticação Gerenciada",
318
+ description: "Concede a esta organização acesso à Autenticação Gerenciada (login hospedado pela Veloz para os apps do cliente). Sem a flag, o recurso fica restrito a administradores da plataforma."
319
+ }
320
+ };
321
+ const ORGANIZATION_FLAG_KEYS = Object.keys(ORGANIZATION_FLAG_DEFINITIONS);
310
322
  /**
311
323
  * Allowlist of user-configurable PostgreSQL parameters with validation bounds.
312
324
  * PGTune-relevant params + commonly tuned settings.
@@ -642,7 +654,7 @@ const ServiceConfigSchema = z$1.object({
642
654
  const ProjectConfigSchema = z$1.object({
643
655
  id: z$1.string().optional(),
644
656
  name: z$1.string().optional(),
645
- slug: z$1.string().regex(/^[a-z0-9-]+$/).optional()
657
+ slug: z$1.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).optional()
646
658
  });
647
659
  const ServiceDefaultsSchema = z$1.object({
648
660
  type: ServiceTypeSchema.optional(),
@@ -886,6 +898,44 @@ function getGitBranch() {
886
898
  }
887
899
  }
888
900
 
901
+ //#endregion
902
+ //#region src/lib/mcp.ts
903
+ const MCP_SERVER_INSTRUCTIONS = [
904
+ "Use Veloz tools for platform operations and start with read-only discovery tools.",
905
+ "Before calling a tool annotated as destructive, obtain explicit confirmation for the exact action.",
906
+ "When a destructive tool accepts userConfirmation, pass the user's confirmation text in that field.",
907
+ "Deploy is non-blocking: after deploy, poll builds_show until the status is terminal, then use builds_logs when the build fails.",
908
+ "Treat credentials, API keys, and environment variables as secrets and never expose them unnecessarily."
909
+ ].join(" ");
910
+ const MCP_READ_ONLY = { annotations: {
911
+ readOnlyHint: true,
912
+ destructiveHint: false,
913
+ openWorldHint: true
914
+ } };
915
+ const MCP_DESTRUCTIVE = {
916
+ annotations: {
917
+ readOnlyHint: false,
918
+ destructiveHint: true,
919
+ idempotentHint: false,
920
+ openWorldHint: true
921
+ },
922
+ instructions: "This tool can cause an irreversible or externally visible change. Call it only after the user explicitly confirms the exact action."
923
+ };
924
+ const MCP_CONFIRMATION_REQUIRED = {
925
+ ...MCP_DESTRUCTIVE,
926
+ instructions: "This tool requires explicit confirmation for the exact action. Pass the user's confirmation text in userConfirmation."
927
+ };
928
+ const MCP_DEPLOY = {
929
+ description: "Start a Veloz deployment. In MCP mode this returns as soon as the build is queued and does not wait for completion.",
930
+ annotations: {
931
+ readOnlyHint: false,
932
+ destructiveHint: false,
933
+ idempotentHint: false,
934
+ openWorldHint: true
935
+ },
936
+ instructions: "Read deploymentId from the result, poll builds_show until status is LIVE, BUILD_FAILED, DEPLOY_FAILED, FAILED, or CANCELLED, and call builds_logs when the build fails."
937
+ };
938
+
889
939
  //#endregion
890
940
  //#region ../../packages/api/src/client.ts
891
941
  function createClient(baseUrl, headers) {
@@ -907,6 +957,16 @@ function createClient(baseUrl, headers) {
907
957
  function isMcpMode() {
908
958
  return process.env.VELOZ_MCP === "true";
909
959
  }
960
+ /**
961
+ * True when the CLI is being driven by an AI coding agent rather than a human.
962
+ * Covers Claude Code (`CLAUDECODE` / `CLAUDE_CODE_ENTRYPOINT`), generic agent
963
+ * runners (`AI_AGENT`), and MCP mode. Used to skip interactive / nested-AI
964
+ * flows like the `veloz init` wizard — an agent should drive `veloz deploy`
965
+ * directly instead of spawning a second agent inside the CLI.
966
+ */
967
+ function isAiAgent() {
968
+ return isMcpMode() || Boolean(process.env.CLAUDECODE) || Boolean(process.env.CLAUDE_CODE_ENTRYPOINT) || Boolean(process.env.AI_AGENT);
969
+ }
910
970
  /** Write a line to the safe output stream (stderr in MCP mode, stdout otherwise). */
911
971
  function log(message) {
912
972
  if (isMcpMode()) process.stderr.write(`${message}\n`);
@@ -1154,6 +1214,7 @@ async function performLogin(apiUrl) {
1154
1214
  function registerLogin(cli$1) {
1155
1215
  cli$1.command("login", {
1156
1216
  description: "Autenticar na plataforma Veloz",
1217
+ mcp: false,
1157
1218
  options: z.object({
1158
1219
  apiUrl: z.string().optional().describe("URL da API Veloz"),
1159
1220
  apiKey: z.string().optional().describe("Chave de API (para CI/automação)")
@@ -1185,6 +1246,7 @@ function registerLogin(cli$1) {
1185
1246
  });
1186
1247
  cli$1.command("logout", {
1187
1248
  description: "Encerrar sessão na plataforma Veloz",
1249
+ mcp: false,
1188
1250
  output: z.object({ loggedOut: z.boolean() }),
1189
1251
  run() {
1190
1252
  if (!isAuthenticated()) {
@@ -1273,7 +1335,7 @@ async function requireAuth(options) {
1273
1335
 
1274
1336
  //#endregion
1275
1337
  //#region src/lib/client.ts
1276
- const CLI_VERSION = "0.0.0-beta.39";
1338
+ const CLI_VERSION = "0.0.0-beta.40";
1277
1339
  const USER_AGENT = `veloz-cli/${CLI_VERSION}`;
1278
1340
  /**
1279
1341
  * Client source — "cli" by default; the init wizard sets this to "cli-wizard"
@@ -1353,6 +1415,7 @@ const requireAuth$1 = middleware(async (c, next) => {
1353
1415
  const projectsGroup = Cli.create("projects", { description: "Gerenciar projetos" });
1354
1416
  projectsGroup.command("list", {
1355
1417
  description: "Listar todos os projetos",
1418
+ mcp: MCP_READ_ONLY,
1356
1419
  middleware: [requireAuth$1],
1357
1420
  output: z.object({ items: z.array(z.object({
1358
1421
  id: z.string(),
@@ -1386,6 +1449,7 @@ projectsGroup.command("list", {
1386
1449
  const orgsGroup = Cli.create("orgs", { description: "Gerenciar workspaces (organizações) do usuário" });
1387
1450
  orgsGroup.command("list", {
1388
1451
  description: "Listar workspaces que você pertence",
1452
+ mcp: MCP_READ_ONLY,
1389
1453
  middleware: [requireAuth$1],
1390
1454
  output: z.object({ items: z.array(z.object({
1391
1455
  id: z.string(),
@@ -1933,6 +1997,7 @@ async function resolveProjectId(projectFlag) {
1933
1997
  const envGroup = Cli.create("env", { description: "Gerenciar variáveis de ambiente. Valores suportam interpolação no deploy: ${OUTRA_VAR} referencia outra variável do mesmo serviço (inclui vars auto-injetadas como DATABASE_URL/REDIS_URL/PORT/NODE_ENV); $${LITERAL} preserva um ${...} literal; ${{servico.propriedade}} referencia outro serviço (ex: ${{postgres.url}})." });
1934
1998
  envGroup.command("list", {
1935
1999
  description: "Listar variáveis de ambiente",
2000
+ mcp: MCP_READ_ONLY,
1936
2001
  middleware: [requireAuth$1],
1937
2002
  options: z.object({
1938
2003
  service: z.string().optional().describe("Filtrar por serviço (chave ou nome)"),
@@ -2040,6 +2105,7 @@ envGroup.command("set", {
2040
2105
  });
2041
2106
  envGroup.command("delete", {
2042
2107
  description: "Remover variável de ambiente",
2108
+ mcp: MCP_CONFIRMATION_REQUIRED,
2043
2109
  middleware: [requireAuth$1],
2044
2110
  args: z.object({ chave: z.string().describe("Chave da variável de ambiente") }),
2045
2111
  options: z.object({
@@ -2073,6 +2139,7 @@ envGroup.command("delete", {
2073
2139
  });
2074
2140
  envGroup.command("import", {
2075
2141
  description: "Importar variáveis de ambiente de um arquivo .env ou colar diretamente. Valores com ${OUTRA_VAR} são salvos como referência e resolvidos no deploy (contra as outras vars do serviço, incluindo auto-injetadas). Use $${LITERAL} para preservar.",
2142
+ mcp: false,
2076
2143
  middleware: [requireAuth$1],
2077
2144
  args: z.object({ arquivo: z.string().optional().describe("Caminho do arquivo .env") }),
2078
2145
  options: z.object({
@@ -2165,6 +2232,7 @@ envGroup.command("import", {
2165
2232
  });
2166
2233
  envGroup.command("export", {
2167
2234
  description: "Exportar variáveis de ambiente para um arquivo .env",
2235
+ mcp: false,
2168
2236
  middleware: [requireAuth$1],
2169
2237
  args: z.object({ arquivo: z.string().optional().describe("Caminho do arquivo de saída") }),
2170
2238
  options: z.object({
@@ -2235,6 +2303,7 @@ function printDnsRecords(records) {
2235
2303
  const domainsGroup = Cli.create("domains", { description: "Gerenciar domínios personalizados" });
2236
2304
  domainsGroup.command("list", {
2237
2305
  description: "Listar domínios dos serviços",
2306
+ mcp: MCP_READ_ONLY,
2238
2307
  middleware: [requireAuth$1],
2239
2308
  options: z.object({
2240
2309
  service: z.string().optional().describe("Filtrar por serviço (chave ou nome)"),
@@ -2362,12 +2431,13 @@ domainsGroup.command("verify", {
2362
2431
  });
2363
2432
  domainsGroup.command("search", {
2364
2433
  description: "Buscar domínios disponíveis para registro",
2434
+ mcp: MCP_READ_ONLY,
2365
2435
  middleware: [requireAuth$1],
2366
2436
  args: z.object({ query: z.string().describe("Nome do domínio a buscar (ex: meusite)") }),
2367
- output: z.array(z.object({
2437
+ output: z.object({ items: z.array(z.object({
2368
2438
  domain: z.string(),
2369
2439
  available: z.boolean()
2370
- })),
2440
+ })) }),
2371
2441
  async run(c) {
2372
2442
  const client = await getClient();
2373
2443
  const results = await withSpinner({
@@ -2376,17 +2446,18 @@ domainsGroup.command("search", {
2376
2446
  });
2377
2447
  if (results.length === 0) {
2378
2448
  info("Nenhum resultado encontrado.");
2379
- return [];
2449
+ return { items: [] };
2380
2450
  }
2381
2451
  for (const r of results) {
2382
2452
  const status = r.available ? chalk.green("Disponível") : chalk.red("Indisponível");
2383
2453
  console.log(` ${chalk.bold(r.domain.padEnd(30))} ${status}`);
2384
2454
  }
2385
- return results;
2455
+ return { items: results };
2386
2456
  }
2387
2457
  });
2388
2458
  domainsGroup.command("buy", {
2389
2459
  description: "Registrar um domínio",
2460
+ mcp: MCP_DESTRUCTIVE,
2390
2461
  middleware: [requireAuth$1],
2391
2462
  args: z.object({ dominio: z.string().describe("Domínio a registrar (ex: meusite.com)") }),
2392
2463
  options: z.object({ service: z.string().optional().describe("Conectar ao serviço automaticamente") }),
@@ -2417,13 +2488,14 @@ domainsGroup.command("buy", {
2417
2488
  });
2418
2489
  domainsGroup.command("purchased", {
2419
2490
  description: "Listar domínios registrados",
2491
+ mcp: MCP_READ_ONLY,
2420
2492
  middleware: [requireAuth$1],
2421
- output: z.array(z.object({
2493
+ output: z.object({ items: z.array(z.object({
2422
2494
  id: z.string(),
2423
2495
  domain: z.string(),
2424
2496
  status: z.string(),
2425
2497
  expiresAt: z.string().nullable()
2426
- })),
2498
+ })) }),
2427
2499
  async run() {
2428
2500
  const client = await getClient();
2429
2501
  const purchased = await withSpinner({
@@ -2432,9 +2504,9 @@ domainsGroup.command("purchased", {
2432
2504
  });
2433
2505
  if (purchased.length === 0) {
2434
2506
  info("Nenhum domínio registrado.");
2435
- return [];
2507
+ return { items: [] };
2436
2508
  }
2437
- const statusLabels$1 = {
2509
+ const statusLabels$2 = {
2438
2510
  REGISTERING: chalk.yellow("Registrando"),
2439
2511
  PROPAGATING: chalk.yellow("Propagando DNS"),
2440
2512
  ACTIVE: chalk.green("Ativo"),
@@ -2442,20 +2514,21 @@ domainsGroup.command("purchased", {
2442
2514
  EXPIRED: chalk.red("Expirado")
2443
2515
  };
2444
2516
  for (const d of purchased) {
2445
- const status = statusLabels$1[d.status] ?? d.status;
2517
+ const status = statusLabels$2[d.status] ?? d.status;
2446
2518
  const expires = d.expiresAt ? new Date(d.expiresAt).toLocaleDateString("pt-BR") : "—";
2447
2519
  console.log(` ${d.id} ${chalk.bold(d.domain.padEnd(30))} ${status} Expira: ${expires}`);
2448
2520
  }
2449
- return purchased.map((d) => ({
2521
+ return { items: purchased.map((d) => ({
2450
2522
  id: d.id,
2451
2523
  domain: d.domain,
2452
2524
  status: d.status,
2453
2525
  expiresAt: d.expiresAt ? new Date(d.expiresAt).toISOString() : null
2454
- }));
2526
+ })) };
2455
2527
  }
2456
2528
  });
2457
2529
  domainsGroup.command("delete", {
2458
2530
  description: "Remover domínio",
2531
+ mcp: MCP_CONFIRMATION_REQUIRED,
2459
2532
  middleware: [requireAuth$1],
2460
2533
  args: z.object({ domainId: z.string().describe("ID do domínio") }),
2461
2534
  options: z.object({ userConfirmation: z.string().optional() }),
@@ -2474,6 +2547,237 @@ domainsGroup.command("delete", {
2474
2547
  }
2475
2548
  });
2476
2549
 
2550
+ //#endregion
2551
+ //#region src/commands/email.ts
2552
+ const statusLabels$1 = {
2553
+ PENDING: chalk.yellow("Pendente"),
2554
+ VERIFYING: chalk.yellow("Verificando"),
2555
+ ACTIVE: chalk.green("Ativo"),
2556
+ FAILED: chalk.red("Falhou")
2557
+ };
2558
+ async function getEmailClient() {
2559
+ return (await getClient()).email;
2560
+ }
2561
+ function printDkimRecords(records) {
2562
+ if (records.length === 0) return;
2563
+ console.log();
2564
+ console.log(chalk.yellow.bold(" Registros DNS a publicar (DKIM):"));
2565
+ for (const r of records) console.log(` ${chalk.cyan(r.type.padEnd(5))} ${chalk.white(r.name)} → ${chalk.white(r.value)}`);
2566
+ console.log();
2567
+ console.log(chalk.dim(" Após publicar, rode `veloz email verify <id>` para confirmar a verificação."));
2568
+ console.log();
2569
+ }
2570
+ const emailGroup = Cli.create("email", {
2571
+ description: "Gerenciar envio de e-mail transacional",
2572
+ aliases: ["emails"]
2573
+ });
2574
+ emailGroup.command("list", {
2575
+ description: "Listar domínios remetentes",
2576
+ mcp: MCP_READ_ONLY,
2577
+ middleware: [requireAuth$1],
2578
+ output: z.object({ items: z.array(z.object({
2579
+ id: z.string(),
2580
+ domain: z.string(),
2581
+ status: z.string(),
2582
+ emailsSentThisPeriod: z.number()
2583
+ })) }),
2584
+ async run() {
2585
+ const spin = spinner("Carregando domínios de e-mail...");
2586
+ const domains = await (await getEmailClient()).listDomains();
2587
+ spin.stop();
2588
+ if (domains.length === 0) {
2589
+ info("Nenhum domínio remetente configurado. Use `veloz email add <domínio>`.");
2590
+ return { items: [] };
2591
+ }
2592
+ for (const d of domains) console.log(` ${d.id} ${chalk.bold(d.domain.padEnd(30))} ${statusLabels$1[d.status] ?? d.status} ${chalk.dim(`${d.emailsSentThisPeriod} enviados`)}`);
2593
+ return { items: domains.map((d) => ({
2594
+ id: d.id,
2595
+ domain: d.domain,
2596
+ status: d.status,
2597
+ emailsSentThisPeriod: d.emailsSentThisPeriod
2598
+ })) };
2599
+ }
2600
+ });
2601
+ emailGroup.command("add", {
2602
+ description: "Registrar um domínio remetente",
2603
+ middleware: [requireAuth$1],
2604
+ args: z.object({ dominio: z.string().describe("Domínio remetente (ex: mail.meusite.com)") }),
2605
+ async run(c) {
2606
+ const email = await getEmailClient();
2607
+ const result$2 = await withSpinner({
2608
+ text: `Registrando ${chalk.bold(c.args.dominio)}...`,
2609
+ fn: () => email.provisionDomain({ domain: c.args.dominio })
2610
+ });
2611
+ success(`Domínio ${chalk.bold(result$2.domain)} registrado!`);
2612
+ printDkimRecords(result$2.dkimRecords);
2613
+ return c.ok({
2614
+ id: result$2.id,
2615
+ domain: result$2.domain,
2616
+ status: result$2.status,
2617
+ dkimRecords: result$2.dkimRecords
2618
+ }, { cta: { commands: [{
2619
+ command: `email verify ${result$2.id}`,
2620
+ description: "Verificar configuração DKIM"
2621
+ }, {
2622
+ command: "email list",
2623
+ description: "Listar domínios remetentes"
2624
+ }] } });
2625
+ }
2626
+ });
2627
+ emailGroup.command("verify", {
2628
+ description: "Verificar a configuração DKIM de um domínio remetente",
2629
+ middleware: [requireAuth$1],
2630
+ args: z.object({ emailDomainId: z.string().describe("ID do domínio remetente") }),
2631
+ async run(c) {
2632
+ const email = await getEmailClient();
2633
+ const result$2 = await withSpinner({
2634
+ text: "Verificando DKIM...",
2635
+ fn: () => email.verifyDomain({ emailDomainId: c.args.emailDomainId })
2636
+ });
2637
+ if (result$2.ready) success(`Domínio ${chalk.bold(result$2.domain.domain)} verificado e pronto para enviar!`);
2638
+ else {
2639
+ console.log(chalk.yellow(`\n⚠ ${chalk.bold(result$2.domain.domain)} ainda não verificado (${result$2.domain.status}).`));
2640
+ if (result$2.domain.dkimRecords.length > 0) {
2641
+ info("Confirme que os registros DKIM abaixo estão publicados:");
2642
+ printDkimRecords(result$2.domain.dkimRecords);
2643
+ }
2644
+ }
2645
+ return {
2646
+ ready: result$2.ready,
2647
+ domain: result$2.domain.domain,
2648
+ status: result$2.domain.status
2649
+ };
2650
+ }
2651
+ });
2652
+ emailGroup.command("remove", {
2653
+ description: "Remover um domínio remetente",
2654
+ mcp: MCP_CONFIRMATION_REQUIRED,
2655
+ middleware: [requireAuth$1],
2656
+ args: z.object({ emailDomainId: z.string().describe("ID do domínio remetente") }),
2657
+ options: z.object({ userConfirmation: z.string().optional() }),
2658
+ async run(c) {
2659
+ requireMcpConfirmation(c.options.userConfirmation, `remover domínio remetente "${c.args.emailDomainId}"`);
2660
+ const email = await getEmailClient();
2661
+ await withSpinner({
2662
+ text: "Removendo domínio...",
2663
+ fn: () => email.deleteDomain({ emailDomainId: c.args.emailDomainId })
2664
+ });
2665
+ success("Domínio remetente removido.");
2666
+ return {
2667
+ deleted: true,
2668
+ emailDomainId: c.args.emailDomainId
2669
+ };
2670
+ }
2671
+ });
2672
+ emailGroup.command("send", {
2673
+ description: "Enviar um e-mail transacional",
2674
+ mcp: MCP_DESTRUCTIVE,
2675
+ middleware: [requireAuth$1],
2676
+ options: z.object({
2677
+ from: z.string().describe("Endereço remetente (em um domínio verificado)"),
2678
+ to: z.string().describe("Destinatário"),
2679
+ subject: z.string().describe("Assunto"),
2680
+ text: z.string().optional().describe("Corpo em texto puro"),
2681
+ html: z.string().optional().describe("Corpo em HTML")
2682
+ }),
2683
+ output: z.object({ messageId: z.string() }),
2684
+ async run(c) {
2685
+ if (!c.options.text && !c.options.html) {
2686
+ info("Informe --text e/ou --html com o corpo do e-mail.");
2687
+ return { messageId: "" };
2688
+ }
2689
+ const email = await getEmailClient();
2690
+ const result$2 = await withSpinner({
2691
+ text: `Enviando e-mail para ${chalk.bold(c.options.to)}...`,
2692
+ fn: () => email.send({
2693
+ from: c.options.from,
2694
+ to: [c.options.to],
2695
+ subject: c.options.subject,
2696
+ text: c.options.text,
2697
+ html: c.options.html
2698
+ })
2699
+ });
2700
+ success(`E-mail enviado (id: ${chalk.dim(result$2.messageId)}).`);
2701
+ return result$2;
2702
+ }
2703
+ });
2704
+ emailGroup.command("keys", {
2705
+ description: "Listar chaves de API de e-mail (vmail_*)",
2706
+ mcp: MCP_READ_ONLY,
2707
+ middleware: [requireAuth$1],
2708
+ output: z.object({ items: z.array(z.object({
2709
+ id: z.string(),
2710
+ name: z.string().nullable(),
2711
+ prefix: z.string().nullable(),
2712
+ start: z.string().nullable()
2713
+ })) }),
2714
+ async run() {
2715
+ const email = await getEmailClient();
2716
+ const keys$1 = await withSpinner({
2717
+ text: "Carregando chaves...",
2718
+ fn: () => email.listApiKeys()
2719
+ });
2720
+ if (keys$1.length === 0) {
2721
+ info("Nenhuma chave de e-mail. Use `veloz email key-create <nome>`.");
2722
+ return { items: [] };
2723
+ }
2724
+ for (const k of keys$1) {
2725
+ const masked = k.start ? `${k.prefix ?? "vmail"}_${k.start}…` : k.prefix ?? "vmail";
2726
+ console.log(` ${k.id} ${chalk.bold(k.name ?? "—")} ${chalk.dim(masked)}`);
2727
+ }
2728
+ return { items: keys$1.map((k) => ({
2729
+ id: k.id,
2730
+ name: k.name,
2731
+ prefix: k.prefix,
2732
+ start: k.start
2733
+ })) };
2734
+ }
2735
+ });
2736
+ emailGroup.command("key-create", {
2737
+ description: "Criar uma chave de API de e-mail (vmail_*)",
2738
+ middleware: [requireAuth$1],
2739
+ args: z.object({ nome: z.string().describe("Nome da chave") }),
2740
+ output: z.object({
2741
+ id: z.string(),
2742
+ name: z.string(),
2743
+ key: z.string()
2744
+ }),
2745
+ async run(c) {
2746
+ const email = await getEmailClient();
2747
+ const created = await withSpinner({
2748
+ text: "Criando chave...",
2749
+ fn: () => email.createApiKey({ name: c.args.nome })
2750
+ });
2751
+ success("Chave criada. Copie agora — ela não será exibida novamente:");
2752
+ console.log(`\n ${chalk.bold(created.key)}\n`);
2753
+ return {
2754
+ id: created.id,
2755
+ name: created.name,
2756
+ key: created.key
2757
+ };
2758
+ }
2759
+ });
2760
+ emailGroup.command("key-revoke", {
2761
+ description: "Revogar uma chave de API de e-mail",
2762
+ mcp: MCP_CONFIRMATION_REQUIRED,
2763
+ middleware: [requireAuth$1],
2764
+ args: z.object({ keyId: z.string().describe("ID da chave") }),
2765
+ options: z.object({ userConfirmation: z.string().optional() }),
2766
+ async run(c) {
2767
+ requireMcpConfirmation(c.options.userConfirmation, `revogar chave "${c.args.keyId}"`);
2768
+ const email = await getEmailClient();
2769
+ await withSpinner({
2770
+ text: "Revogando chave...",
2771
+ fn: () => email.revokeApiKey({ keyId: c.args.keyId })
2772
+ });
2773
+ success("Chave revogada.");
2774
+ return {
2775
+ revoked: true,
2776
+ keyId: c.args.keyId
2777
+ };
2778
+ }
2779
+ });
2780
+
2477
2781
  //#endregion
2478
2782
  //#region src/lib/volume-config.ts
2479
2783
  function updateServiceVolumesInConfig(serviceKey, updater) {
@@ -2528,6 +2832,7 @@ async function promptAndSyncDeployment(serviceId) {
2528
2832
  const volumesGroup = Cli.create("volumes", { description: "Gerenciar volumes persistentes" });
2529
2833
  volumesGroup.command("list", {
2530
2834
  description: "Listar volumes dos serviços",
2835
+ mcp: MCP_READ_ONLY,
2531
2836
  middleware: [requireAuth$1],
2532
2837
  options: z.object({
2533
2838
  service: z.string().optional().describe("Filtrar por serviço (chave ou nome)"),
@@ -2669,6 +2974,7 @@ volumesGroup.command("expand", {
2669
2974
  });
2670
2975
  volumesGroup.command("delete", {
2671
2976
  description: "Remover um volume do serviço (dados mantidos por 30 dias)",
2977
+ mcp: MCP_CONFIRMATION_REQUIRED,
2672
2978
  middleware: [requireAuth$1],
2673
2979
  args: z.object({ volume: z.string().describe("Nome ou ID do volume") }),
2674
2980
  options: z.object({
@@ -2801,6 +3107,7 @@ function printServiceConfig(service$2) {
2801
3107
  const configGroup = Cli.create("config", { description: "Gerenciar configuração de serviços" });
2802
3108
  configGroup.command("show", {
2803
3109
  description: "Mostrar configurações atuais dos serviços",
3110
+ mcp: MCP_READ_ONLY,
2804
3111
  middleware: [requireAuth$1],
2805
3112
  options: z.object({
2806
3113
  service: z.string().optional().describe("Filtrar por serviço (chave ou nome)"),
@@ -2917,6 +3224,7 @@ configGroup.command("set", {
2917
3224
  });
2918
3225
  configGroup.command("edit", {
2919
3226
  description: "Editar configurações interativamente",
3227
+ mcp: false,
2920
3228
  middleware: [requireAuth$1],
2921
3229
  options: z.object({
2922
3230
  service: z.string().optional().describe("Serviço alvo (chave ou nome)"),
@@ -2978,6 +3286,7 @@ configGroup.command("edit", {
2978
3286
  });
2979
3287
  configGroup.command("reset", {
2980
3288
  description: "Resetar configurações para os padrões",
3289
+ mcp: MCP_CONFIRMATION_REQUIRED,
2981
3290
  middleware: [requireAuth$1],
2982
3291
  options: z.object({
2983
3292
  build: z.boolean().default(false).describe("Resetar comando de build"),
@@ -3097,6 +3406,7 @@ apikeyGroup.command("create", {
3097
3406
  });
3098
3407
  apikeyGroup.command("list", {
3099
3408
  description: "Listar chaves de API",
3409
+ mcp: MCP_READ_ONLY,
3100
3410
  middleware: [requireAuth$1],
3101
3411
  output: z.object({ items: z.array(z.object({
3102
3412
  name: z.string(),
@@ -3124,6 +3434,7 @@ apikeyGroup.command("list", {
3124
3434
  });
3125
3435
  apikeyGroup.command("delete", {
3126
3436
  description: "Deletar uma chave de API",
3437
+ mcp: MCP_DESTRUCTIVE,
3127
3438
  middleware: [requireAuth$1],
3128
3439
  args: z.object({ keyId: z.string().describe("ID da chave de API") }),
3129
3440
  output: z.object({ deleted: z.boolean() }),
@@ -3201,6 +3512,7 @@ function parseDatabaseEngine(value) {
3201
3512
  const dbGroup = Cli.create("db", { description: "Gerenciar bancos de dados" });
3202
3513
  dbGroup.command("list", {
3203
3514
  description: "Listar bancos de dados do projeto",
3515
+ mcp: MCP_READ_ONLY,
3204
3516
  middleware: [requireAuth$1],
3205
3517
  options: z.object({ project: z.string().optional().describe("Projeto (nome, slug ou ID)") }),
3206
3518
  output: z.object({ items: z.array(z.object({
@@ -3342,6 +3654,7 @@ dbGroup.command("create", {
3342
3654
  });
3343
3655
  dbGroup.command("credentials", {
3344
3656
  description: "Exibir credenciais de um banco de dados",
3657
+ mcp: MCP_READ_ONLY,
3345
3658
  middleware: [requireAuth$1],
3346
3659
  args: z.object({ name: z.string().describe("Nome ou ID do banco de dados") }),
3347
3660
  options: z.object({ project: z.string().optional().describe("Projeto (nome, slug ou ID)") }),
@@ -3351,7 +3664,9 @@ dbGroup.command("credentials", {
3351
3664
  username: z.string(),
3352
3665
  password: z.string(),
3353
3666
  database: z.string(),
3354
- connectionUrl: z.string()
3667
+ connectionUrl: z.string(),
3668
+ poolerUrl: z.string().nullable(),
3669
+ poolerPort: z.number().nullable()
3355
3670
  }),
3356
3671
  async run(c) {
3357
3672
  const db = await resolveDatabaseByName(await getProjectId(c.options.project), c.args.name);
@@ -3367,12 +3682,14 @@ dbGroup.command("credentials", {
3367
3682
  console.log(` ${chalk.dim("Senha:")} ${creds.password}`);
3368
3683
  console.log(` ${chalk.dim("Banco:")} ${creds.database}`);
3369
3684
  console.log(` ${chalk.dim("URL:")} ${creds.connectionUrl}`);
3685
+ if (creds.poolerUrl) console.log(` ${chalk.dim("URL (pooler):")} ${creds.poolerUrl}`);
3370
3686
  console.log();
3371
3687
  return creds;
3372
3688
  }
3373
3689
  });
3374
3690
  dbGroup.command("delete", {
3375
3691
  description: "Excluir um banco de dados",
3692
+ mcp: MCP_CONFIRMATION_REQUIRED,
3376
3693
  middleware: [requireAuth$1],
3377
3694
  args: z.object({ name: z.string().describe("Nome ou ID do banco de dados") }),
3378
3695
  options: z.object({
@@ -3426,6 +3743,7 @@ dbGroup.command("delete", {
3426
3743
  });
3427
3744
  dbGroup.command("restart", {
3428
3745
  description: "Reiniciar um banco de dados",
3746
+ mcp: MCP_CONFIRMATION_REQUIRED,
3429
3747
  middleware: [requireAuth$1],
3430
3748
  args: z.object({ name: z.string().describe("Nome ou ID do banco de dados") }),
3431
3749
  options: z.object({
@@ -3609,6 +3927,7 @@ dbGroup.command("update", {
3609
3927
  });
3610
3928
  dbGroup.command("query", {
3611
3929
  description: "Executar uma consulta no banco de dados",
3930
+ mcp: MCP_DESTRUCTIVE,
3612
3931
  middleware: [requireAuth$1],
3613
3932
  args: z.object({ name: z.string().describe("Nome ou ID do banco de dados") }),
3614
3933
  options: z.object({
@@ -3666,11 +3985,13 @@ const ENGINE_CONNECT_HINTS = {
3666
3985
  };
3667
3986
  dbGroup.command("tunnel", {
3668
3987
  description: "Criar túnel local para um banco de dados",
3988
+ mcp: false,
3669
3989
  middleware: [requireAuth$1],
3670
3990
  args: z.object({ name: z.string().describe("Nome ou ID do banco de dados") }),
3671
3991
  options: z.object({
3672
3992
  project: z.string().optional().describe("Projeto (nome, slug ou ID)"),
3673
- port: z.number().optional().describe("Porta local (padrão: porta do engine)")
3993
+ port: z.number().optional().describe("Porta local (padrão: porta do engine)"),
3994
+ pooler: z.boolean().optional().describe("Conectar via PgBouncer (pooling de transações) em vez de conexão direta")
3674
3995
  }),
3675
3996
  alias: { port: "p" },
3676
3997
  async run(c) {
@@ -3688,7 +4009,10 @@ dbGroup.command("tunnel", {
3688
4009
  const server = net.createServer(async (socket) => {
3689
4010
  let session;
3690
4011
  try {
3691
- session = await client.databases.createTunnelSession({ serviceId: db.id });
4012
+ session = await client.databases.createTunnelSession({
4013
+ serviceId: db.id,
4014
+ pooler: c.options.pooler
4015
+ });
3692
4016
  } catch (err) {
3693
4017
  const message = err instanceof Error ? err.message : "Erro desconhecido";
3694
4018
  console.log(chalk.red(` Falha ao criar sessão de túnel: ${message}`));
@@ -3732,7 +4056,7 @@ dbGroup.command("tunnel", {
3732
4056
  });
3733
4057
  server.listen(localPort, "127.0.0.1", () => {
3734
4058
  console.log();
3735
- success(`Túnel ${engineLabel} ativo para ${chalk.bold(db.name)}`);
4059
+ success(`Túnel ${engineLabel} ativo para ${chalk.bold(db.name)}${c.options.pooler ? chalk.dim(" (pooler)") : ""}`);
3736
4060
  console.log();
3737
4061
  console.log(` ${chalk.dim("Endereço local:")} ${chalk.bold(`127.0.0.1:${localPort}`)}`);
3738
4062
  console.log(` ${chalk.dim("URL de conexão:")} ${chalk.cyan(localUrl)}`);
@@ -3759,6 +4083,7 @@ dbGroup.command("tunnel", {
3759
4083
  });
3760
4084
  dbGroup.command("config", {
3761
4085
  description: "Ver ou alterar configuração do PostgreSQL",
4086
+ mcp: MCP_CONFIRMATION_REQUIRED,
3762
4087
  middleware: [requireAuth$1],
3763
4088
  args: z.object({ name: z.string().describe("Nome ou ID do banco de dados") }),
3764
4089
  options: z.object({
@@ -3874,6 +4199,7 @@ dbGroup.command("config", {
3874
4199
  const templateGroup = Cli.create("template", { description: "Gerenciar templates de deploy" });
3875
4200
  templateGroup.command("list", {
3876
4201
  description: "Listar templates disponíveis",
4202
+ mcp: MCP_READ_ONLY,
3877
4203
  middleware: [requireAuth$1],
3878
4204
  output: z.object({ items: z.array(z.object({
3879
4205
  slug: z.string(),
@@ -3910,6 +4236,7 @@ templateGroup.command("list", {
3910
4236
  });
3911
4237
  templateGroup.command("deploy", {
3912
4238
  description: "Fazer deploy de um template (cria um novo projeto automaticamente)",
4239
+ mcp: MCP_CONFIRMATION_REQUIRED,
3913
4240
  middleware: [requireAuth$1],
3914
4241
  args: z.object({ slug: z.string().describe("Slug do template (ex: n8n, metabase)") }),
3915
4242
  options: z.object({
@@ -3971,6 +4298,36 @@ templateGroup.command("deploy", {
3971
4298
  }
3972
4299
  });
3973
4300
 
4301
+ //#endregion
4302
+ //#region src/lib/retry.ts
4303
+ async function withRetry(fn$2, maxRetries = 3) {
4304
+ let orgSwitched = false;
4305
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
4306
+ return await fn$2();
4307
+ } catch (error) {
4308
+ if (!orgSwitched && isOrgMismatchError(error)) {
4309
+ if (applyOrgMismatchSwitch(error)) {
4310
+ orgSwitched = true;
4311
+ continue;
4312
+ }
4313
+ }
4314
+ if (attempt >= maxRetries) throw error;
4315
+ const rateLimit = isRateLimitError(error);
4316
+ if (rateLimit) {
4317
+ const waitMs = Math.min(rateLimit.retryAfterMs, 3e4);
4318
+ await new Promise((r) => {
4319
+ setTimeout(r, waitMs);
4320
+ });
4321
+ } else {
4322
+ const delay$2 = Math.min(1e3 * Math.pow(2, attempt), 1e4);
4323
+ await new Promise((r) => {
4324
+ setTimeout(r, delay$2);
4325
+ });
4326
+ }
4327
+ }
4328
+ throw new Error("Max retries exceeded");
4329
+ }
4330
+
3974
4331
  //#endregion
3975
4332
  //#region src/commands/services.ts
3976
4333
  const servicesGroup = Cli.create("services", {
@@ -3979,6 +4336,7 @@ const servicesGroup = Cli.create("services", {
3979
4336
  });
3980
4337
  servicesGroup.command("list", {
3981
4338
  description: "Listar serviços do projeto",
4339
+ mcp: MCP_READ_ONLY,
3982
4340
  middleware: [requireAuth$1],
3983
4341
  options: z.object({ project: z.string().optional().describe("Projeto (nome, slug ou ID)") }),
3984
4342
  output: z.object({ items: z.array(z.object({
@@ -3998,7 +4356,7 @@ servicesGroup.command("list", {
3998
4356
  const services = await withSpinner({
3999
4357
  text: "Carregando serviços...",
4000
4358
  fn: async () => {
4001
- return (await Promise.all([...projectIds].map((pid) => client.services.list({ projectId: pid })))).flat();
4359
+ return (await Promise.all([...projectIds].map((pid) => withRetry(() => client.services.list({ projectId: pid }))))).flat();
4002
4360
  }
4003
4361
  });
4004
4362
  if (services.length === 0) {
@@ -4015,6 +4373,7 @@ servicesGroup.command("list", {
4015
4373
  });
4016
4374
  servicesGroup.command("delete", {
4017
4375
  description: "Deletar um serviço",
4376
+ mcp: MCP_CONFIRMATION_REQUIRED,
4018
4377
  middleware: [requireAuth$1],
4019
4378
  args: z.object({ service: z.string().optional().describe("Nome ou ID do serviço") }),
4020
4379
  options: z.object({
@@ -4033,7 +4392,7 @@ servicesGroup.command("delete", {
4033
4392
  const services = await withSpinner({
4034
4393
  text: "Carregando serviços...",
4035
4394
  fn: async () => {
4036
- return (await Promise.all([...projectIds].map((pid) => client.services.list({ projectId: pid })))).flat();
4395
+ return (await Promise.all([...projectIds].map((pid) => withRetry(() => client.services.list({ projectId: pid }))))).flat();
4037
4396
  }
4038
4397
  });
4039
4398
  if (services.length === 0) {
@@ -4067,7 +4426,7 @@ servicesGroup.command("delete", {
4067
4426
  }
4068
4427
  await withSpinner({
4069
4428
  text: `Deletando serviço "${serviceToDelete.name}"...`,
4070
- fn: () => client.services.delete({ serviceId: serviceToDelete.id })
4429
+ fn: () => withRetry(() => client.services.delete({ serviceId: serviceToDelete.id }))
4071
4430
  });
4072
4431
  success(`Serviço "${serviceToDelete.name}" deletado com sucesso!`);
4073
4432
  return c.ok({
@@ -4197,6 +4556,7 @@ function printInsightsHint() {
4197
4556
  const metricsGroup = Cli.create("metrics", { description: "Visualizar métricas dos serviços" });
4198
4557
  metricsGroup.command("show", {
4199
4558
  description: "Exibir métricas atuais do serviço",
4559
+ mcp: MCP_READ_ONLY,
4200
4560
  middleware: [requireAuth$1],
4201
4561
  options: z.object({
4202
4562
  service: z.string().optional().describe("Filtrar por serviço ou banco de dados"),
@@ -4311,6 +4671,7 @@ metricsGroup.command("show", {
4311
4671
  });
4312
4672
  metricsGroup.command("range", {
4313
4673
  description: "Exibir métricas em intervalo de tempo com sparklines",
4674
+ mcp: MCP_READ_ONLY,
4314
4675
  middleware: [requireAuth$1],
4315
4676
  options: z.object({
4316
4677
  service: z.string().optional().describe("Filtrar por serviço ou banco de dados"),
@@ -4388,6 +4749,7 @@ metricsGroup.command("range", {
4388
4749
  });
4389
4750
  metricsGroup.command("query", {
4390
4751
  description: "Executar consulta MetricsQL personalizada",
4752
+ mcp: MCP_READ_ONLY,
4391
4753
  middleware: [requireAuth$1],
4392
4754
  args: z.object({ query: z.string().describe("Consulta MetricsQL (ex: up, rate(http_requests[5m]))") }),
4393
4755
  options: z.object({
@@ -4468,6 +4830,7 @@ metricsGroup.command("query", {
4468
4830
  });
4469
4831
  metricsGroup.command("list", {
4470
4832
  description: "Listar nomes de métricas disponíveis",
4833
+ mcp: MCP_READ_ONLY,
4471
4834
  middleware: [requireAuth$1],
4472
4835
  options: z.object({
4473
4836
  match: z.string().optional().describe("Filtrar por seletor (ex: \"{job=\\\"node\\\"}\", \"traefik_.*\")"),
@@ -4501,6 +4864,7 @@ metricsGroup.command("list", {
4501
4864
  });
4502
4865
  metricsGroup.command("labels", {
4503
4866
  description: "Listar labels disponíveis ou valores de um label específico",
4867
+ mcp: MCP_READ_ONLY,
4504
4868
  middleware: [requireAuth$1],
4505
4869
  options: z.object({
4506
4870
  label: z.string().optional().describe("Nome do label para listar valores (ex: pod, namespace)"),
@@ -4540,6 +4904,7 @@ metricsGroup.command("labels", {
4540
4904
  });
4541
4905
  metricsGroup.command("series", {
4542
4906
  description: "Encontrar séries de métricas por seletor",
4907
+ mcp: MCP_READ_ONLY,
4543
4908
  middleware: [requireAuth$1],
4544
4909
  args: z.object({ match: z.string().describe("Seletor de séries (ex: \"traefik_service_requests_total\", '{__name__=~\"pg_.*\"}')") }),
4545
4910
  options: z.object({ limit: z.number().default(100).describe("Número máximo de séries") }),
@@ -4806,6 +5171,7 @@ const QUERY_HELP_SECTIONS$1 = [
4806
5171
  ];
4807
5172
  metricsGroup.command("query-help", {
4808
5173
  description: "Referência de MetricsQL para consultas de métricas",
5174
+ mcp: MCP_READ_ONLY,
4809
5175
  outputPolicy: "agent-only",
4810
5176
  output: z.object({ sections: z.array(z.object({
4811
5177
  title: z.string(),
@@ -4832,6 +5198,7 @@ metricsGroup.command("query-help", {
4832
5198
  });
4833
5199
  metricsGroup.command("help", {
4834
5200
  description: "Referência de métricas disponíveis e seus campos",
5201
+ mcp: MCP_READ_ONLY,
4835
5202
  outputPolicy: "agent-only",
4836
5203
  output: z.object({ sections: z.array(z.object({
4837
5204
  title: z.string(),
@@ -4920,6 +5287,10 @@ async function fetchRecent(services, maxNameLen, tailLines) {
4920
5287
  const logsGroup = Cli.create("logs", { description: "Visualizar logs dos serviços" });
4921
5288
  logsGroup.command("show", {
4922
5289
  description: "Visualizar logs recentes ou acompanhar em tempo real",
5290
+ mcp: {
5291
+ ...MCP_READ_ONLY,
5292
+ instructions: "Never call this tool with follow=true in MCP mode because it opens a long-lived log stream. Use follow=false and poll again when fresh logs are needed."
5293
+ },
4923
5294
  middleware: [requireAuth$1],
4924
5295
  options: z.object({
4925
5296
  follow: z.boolean().optional().describe("Acompanhar logs em tempo real"),
@@ -4932,6 +5303,10 @@ logsGroup.command("show", {
4932
5303
  tail: "n"
4933
5304
  },
4934
5305
  async run(c) {
5306
+ if (c.options.follow && isMcpMode()) return c.error({
5307
+ code: "MCP_FOLLOW_UNSUPPORTED",
5308
+ message: "Long-lived log streaming is not supported over MCP. Use follow=false and poll again for fresh logs."
5309
+ });
4935
5310
  const spin = spinner("Carregando logs...");
4936
5311
  const { services, maxNameLen } = await resolveAllServices(c.options.service, c.options.project);
4937
5312
  const tailLines = c.options.tail;
@@ -4951,6 +5326,7 @@ logsGroup.command("show", {
4951
5326
  });
4952
5327
  logsGroup.command("search", {
4953
5328
  description: "Pesquisar logs com consultas LogsQL",
5329
+ mcp: MCP_READ_ONLY,
4954
5330
  middleware: [requireAuth$1],
4955
5331
  args: z.object({ query: z.string().default("").describe("Consulta LogsQL (ex: error, ~\"regex\", field:value)") }),
4956
5332
  options: z.object({
@@ -5141,6 +5517,7 @@ const QUERY_HELP_SECTIONS = [
5141
5517
  ];
5142
5518
  logsGroup.command("query-help", {
5143
5519
  description: "Referência de sintaxe LogsQL para pesquisa de logs",
5520
+ mcp: MCP_READ_ONLY,
5144
5521
  outputPolicy: "agent-only",
5145
5522
  output: z.object({ sections: z.array(z.object({
5146
5523
  title: z.string(),
@@ -5177,6 +5554,7 @@ logsGroup.command("query-help", {
5177
5554
  const githubGroup = Cli.create("github", { description: "Gerenciar integração com GitHub" });
5178
5555
  githubGroup.command("setup", {
5179
5556
  description: "Configurar GitHub App para deploy automático via webhook",
5557
+ mcp: false,
5180
5558
  middleware: [requireAuth$1],
5181
5559
  options: z.object({ project: z.string().optional().describe("ID do projeto a conectar") }),
5182
5560
  alias: { project: "p" },
@@ -5321,6 +5699,7 @@ githubGroup.command("setup", {
5321
5699
  });
5322
5700
  githubGroup.command("status", {
5323
5701
  description: "Verificar status da integração GitHub do projeto",
5702
+ mcp: MCP_READ_ONLY,
5324
5703
  middleware: [requireAuth$1],
5325
5704
  output: z.object({
5326
5705
  connected: z.boolean(),
@@ -5436,7 +5815,7 @@ const TERMINAL_STATUSES = new Set([
5436
5815
 
5437
5816
  //#endregion
5438
5817
  //#region src/lib/deploy-stream.ts
5439
- const DASHBOARD_BASE$1 = process.env.VELOZ_WEB_URL || "https://app.onveloz.com";
5818
+ const DASHBOARD_BASE$2 = process.env.VELOZ_WEB_URL || "https://app.onveloz.com";
5440
5819
  /**
5441
5820
  * Fetch deployment details from the API and enrich a DeployStreamResult
5442
5821
  * with metadata useful for agents (failReason, commit info, duration, dashboard link).
@@ -5452,7 +5831,7 @@ async function enrichResult(client, result$2) {
5452
5831
  result$2.branch = d.branch ?? null;
5453
5832
  if (d.startedAt && d.finishedAt) result$2.durationSeconds = Math.round((new Date(d.finishedAt).getTime() - new Date(d.startedAt).getTime()) / 1e3);
5454
5833
  if (projectId) {
5455
- result$2.dashboardUrl = `${DASHBOARD_BASE$1}/projetos/${projectId}/servicos/${d.serviceId}/deploys/${d.id}`;
5834
+ result$2.dashboardUrl = `${DASHBOARD_BASE$2}/projetos/${projectId}/servicos/${d.serviceId}/deploys/${d.id}`;
5456
5835
  const project = await client.projects.get({ projectId }).catch(() => null);
5457
5836
  if (project) result$2.organizationId = project.organizationId ?? null;
5458
5837
  }
@@ -5613,6 +5992,8 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
5613
5992
  process.stdout.write(`${header}\n`);
5614
5993
  }
5615
5994
  let finalStatus = "";
5995
+ let finalErrorSummary = null;
5996
+ let finalFailReason = null;
5616
5997
  try {
5617
5998
  const stream = await client.logs.streamBuildLogs({ deploymentId });
5618
5999
  for await (const event of stream) if (event.type === "status") {
@@ -5632,7 +6013,10 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
5632
6013
  }
5633
6014
  } catch {
5634
6015
  try {
5635
- finalStatus = (await client.deployments.get({ deploymentId })).status;
6016
+ const d = await client.deployments.get({ deploymentId });
6017
+ finalStatus = d.status;
6018
+ finalErrorSummary = d.errorSummary ?? finalErrorSummary;
6019
+ finalFailReason = d.failReason ?? finalFailReason;
5636
6020
  try {
5637
6021
  const logs = await client.logs.getBuildLogs({ deploymentId });
5638
6022
  if (logs.buildLogs) allLogLines.push(...logs.buildLogs.split("\n"));
@@ -5640,8 +6024,12 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
5640
6024
  } catch {}
5641
6025
  }
5642
6026
  if (!TERMINAL_STATUSES.has(finalStatus)) try {
5643
- finalStatus = (await client.deployments.get({ deploymentId })).status;
6027
+ const d = await client.deployments.get({ deploymentId });
6028
+ finalStatus = d.status;
6029
+ finalErrorSummary = d.errorSummary ?? finalErrorSummary;
6030
+ finalFailReason = d.failReason ?? finalFailReason;
5644
6031
  } catch {}
6032
+ const finalReason = finalErrorSummary ?? finalFailReason;
5645
6033
  if (isGHA) endGroup();
5646
6034
  const urls = finalStatus === "LIVE" ? await fetchDeployUrls$1(client, serviceId) : [];
5647
6035
  if (finalStatus === "LIVE") {
@@ -5652,6 +6040,7 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
5652
6040
  const hints = getFailureHints$1(finalStatus);
5653
6041
  if (mcp) {
5654
6042
  log(`✗ Deploy finalizou: ${label}`);
6043
+ if (finalReason) log(`Motivo: ${finalReason}`);
5655
6044
  for (const line of allLogLines) if (line.trim()) log(`[build] ${line.trim()}`);
5656
6045
  for (const hint of hints) log(` → ${hint}`);
5657
6046
  } else if (isTTY && allLogLines.length > 0) {
@@ -5664,11 +6053,12 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
5664
6053
  }
5665
6054
  if (isGHA) {
5666
6055
  const msg = serviceName ? `Deploy de ${serviceName} finalizou com status: ${label}` : `Deploy finalizou com status: ${label}`;
5667
- process.stdout.write(`::error::${msg}\n`);
6056
+ process.stdout.write(`::error::${finalReason ? `${msg} — ${finalReason}` : msg}\n`);
5668
6057
  for (const hint of hints) process.stdout.write(` ${hint}\n`);
5669
6058
  } else if (!mcp) {
5670
6059
  const errorMsg = serviceName ? `Deploy de ${chalk.bold(serviceName)} finalizou: ${label}` : `Deploy finalizou: ${label}`;
5671
6060
  console.error(chalk.red(`\n✗ ${errorMsg}`));
6061
+ if (finalReason) console.error(chalk.red(` ${chalk.bold("Motivo:")} ${finalReason}`));
5672
6062
  for (const hint of hints) console.error(chalk.yellow(` → ${hint}`));
5673
6063
  console.error(chalk.yellow(` → Use 'veloz builds logs ${deploymentId}' para ver os logs completos`));
5674
6064
  }
@@ -5729,6 +6119,7 @@ const buildsGroup = Cli.create("builds", {
5729
6119
  });
5730
6120
  buildsGroup.command("list", {
5731
6121
  description: "Listar builds recentes de um serviço",
6122
+ mcp: MCP_READ_ONLY,
5732
6123
  middleware: [requireAuth$1],
5733
6124
  options: z.object({
5734
6125
  service: z.string().optional().describe("Nome ou ID do serviço"),
@@ -5750,11 +6141,11 @@ buildsGroup.command("list", {
5750
6141
  const client = await getClient();
5751
6142
  const deployments = await withSpinner({
5752
6143
  text: "Carregando builds...",
5753
- fn: () => client.deployments.list({
6144
+ fn: () => withRetry(() => client.deployments.list({
5754
6145
  serviceId,
5755
6146
  limit: c.options.limit,
5756
6147
  offset: 0
5757
- })
6148
+ }))
5758
6149
  });
5759
6150
  if (deployments.length === 0) {
5760
6151
  info("Nenhum build encontrado para este serviço.");
@@ -5790,6 +6181,7 @@ buildsGroup.command("list", {
5790
6181
  });
5791
6182
  buildsGroup.command("show", {
5792
6183
  description: "Exibir detalhes de um build específico",
6184
+ mcp: MCP_READ_ONLY,
5793
6185
  middleware: [requireAuth$1],
5794
6186
  args: z.object({ id: z.string().describe("ID do build (completo ou parcial)") }),
5795
6187
  options: z.object({
@@ -5817,17 +6209,17 @@ buildsGroup.command("show", {
5817
6209
  try {
5818
6210
  deployment = await withSpinner({
5819
6211
  text: "Carregando build...",
5820
- fn: () => client.deployments.get({ deploymentId: searchId })
6212
+ fn: () => withRetry(() => client.deployments.get({ deploymentId: searchId }))
5821
6213
  });
5822
6214
  } catch {
5823
6215
  const serviceId = await resolveServiceId(c.options.service, c.options.project);
5824
6216
  deployment = (await withSpinner({
5825
6217
  text: "Buscando build...",
5826
- fn: () => client.deployments.list({
6218
+ fn: () => withRetry(() => client.deployments.list({
5827
6219
  serviceId,
5828
6220
  limit: 50,
5829
6221
  offset: 0
5830
- })
6222
+ }))
5831
6223
  })).find((d) => d.id.startsWith(searchId));
5832
6224
  if (!deployment) throw new Error(`Build "${searchId}" não encontrado.`);
5833
6225
  }
@@ -5875,6 +6267,7 @@ buildsGroup.command("show", {
5875
6267
  });
5876
6268
  buildsGroup.command("logs", {
5877
6269
  description: "Exibir logs de build de um deploy",
6270
+ mcp: MCP_READ_ONLY,
5878
6271
  middleware: [requireAuth$1],
5879
6272
  args: z.object({ id: z.string().describe("ID do build (completo ou parcial)") }),
5880
6273
  options: z.object({
@@ -5892,16 +6285,16 @@ buildsGroup.command("logs", {
5892
6285
  const searchId = c.args.id;
5893
6286
  let deploymentId = searchId;
5894
6287
  try {
5895
- deploymentId = (await client.deployments.get({ deploymentId: searchId })).id;
6288
+ deploymentId = (await withRetry(() => client.deployments.get({ deploymentId: searchId }))).id;
5896
6289
  } catch {
5897
6290
  const serviceId = await resolveServiceId(c.options.service, c.options.project);
5898
6291
  const found = (await withSpinner({
5899
6292
  text: "Buscando build...",
5900
- fn: () => client.deployments.list({
6293
+ fn: () => withRetry(() => client.deployments.list({
5901
6294
  serviceId,
5902
6295
  limit: 50,
5903
6296
  offset: 0
5904
- })
6297
+ }))
5905
6298
  })).find((d) => d.id.startsWith(searchId));
5906
6299
  if (!found) {
5907
6300
  warn(`Build "${searchId}" não encontrado.`);
@@ -5915,7 +6308,7 @@ buildsGroup.command("logs", {
5915
6308
  }
5916
6309
  const result$2 = await withSpinner({
5917
6310
  text: "Carregando logs de build...",
5918
- fn: () => client.logs.getBuildLogs({ deploymentId })
6311
+ fn: () => withRetry(() => client.logs.getBuildLogs({ deploymentId }))
5919
6312
  });
5920
6313
  if (!result$2.buildLogs) {
5921
6314
  info("Nenhum log de build disponível para este deploy.");
@@ -5925,7 +6318,7 @@ buildsGroup.command("logs", {
5925
6318
  logs: ""
5926
6319
  };
5927
6320
  }
5928
- const deployment = await client.deployments.get({ deploymentId });
6321
+ const deployment = await withRetry(() => client.deployments.get({ deploymentId }));
5929
6322
  if (process.stdout.isTTY) {
5930
6323
  const icon = getStatusIcon(deployment.status);
5931
6324
  const label = statusLabels[deployment.status] ?? deployment.status;
@@ -5986,6 +6379,7 @@ buildsGroup.command("logs", {
5986
6379
  function registerLink(cli$1) {
5987
6380
  cli$1.command("link", {
5988
6381
  description: "Verificar vínculo do projeto com Veloz",
6382
+ mcp: MCP_READ_ONLY,
5989
6383
  output: z.object({
5990
6384
  project: z.object({
5991
6385
  id: z.string(),
@@ -22743,6 +23137,25 @@ function resolveProductionStartCmd(scripts, pm) {
22743
23137
  if (isDevScript(scripts.start)) return null;
22744
23138
  return pmRun(pm, "start");
22745
23139
  }
23140
+ /**
23141
+ * Resolve a production start command for a deployable app.
23142
+ *
23143
+ * Precedence: a valid (non-dev) `start` script → `<pm> run start`; otherwise the
23144
+ * framework's canonical default (`next start`, `node dist/main.js`, …). For a
23145
+ * monorepo app in a subdirectory the command is prefixed with `cd <appPath> &&`
23146
+ * so it runs from the app even though the builder's WORKDIR is the repo root —
23147
+ * what plain pnpm/yarn workspaces (no `NIXPACKS_<TOOL>_APP_NAME` targeting) need
23148
+ * to avoid "No start command could be found". Returns null for STATIC apps.
23149
+ */
23150
+ function resolveAppStartCommand(pkg, pm, framework, appPath) {
23151
+ if (framework && framework.type === "STATIC") return null;
23152
+ const path$1 = appPath && appPath !== "." && appPath !== "/" ? appPath : null;
23153
+ const inApp = (cmd) => path$1 ? `cd ${path$1} && ${cmd}` : cmd;
23154
+ const scripts = pkg.scripts ?? {};
23155
+ if (scripts.start && !isDevScript(scripts.start)) return inApp(pmRun(pm, "start"));
23156
+ if (framework?.startCommand) return inApp(framework.startCommand);
23157
+ return null;
23158
+ }
22746
23159
  /** Safely parse a JSON string as PkgJson. */
22747
23160
  function safeParsePkg(content) {
22748
23161
  try {
@@ -23744,13 +24157,9 @@ const detectMonorepo = (pm) => gen(function* () {
23744
24157
  ...appFramework,
23745
24158
  buildCommand: pmRun(pm, `build:${appName}`)
23746
24159
  };
23747
- if (nested.scripts?.start && !isDevScript(nested.scripts.start)) appFramework = {
24160
+ appFramework = {
23748
24161
  ...appFramework,
23749
- startCommand: null
23750
- };
23751
- else if (appFramework.startCommand) appFramework = {
23752
- ...appFramework,
23753
- startCommand: `cd ${appPath} && ${appFramework.startCommand}`
24162
+ startCommand: resolveAppStartCommand(nested, pm, appFramework, appPath)
23754
24163
  };
23755
24164
  }
23756
24165
  let nodeVersion;
@@ -24387,7 +24796,7 @@ const LOGO_LINES$1 = [
24387
24796
  ];
24388
24797
  const BRAND_COLOR$1 = "#FF4D00";
24389
24798
  function getVersion() {
24390
- return "0.0.0-beta.39";
24799
+ return "0.0.0-beta.40";
24391
24800
  }
24392
24801
  function printBanner(subtitle) {
24393
24802
  const version$2 = getVersion();
@@ -24417,36 +24826,6 @@ function stripAnsi(str) {
24417
24826
  return str.replace(/\u001B\[[0-9;]*m/g, "");
24418
24827
  }
24419
24828
 
24420
- //#endregion
24421
- //#region src/lib/retry.ts
24422
- async function withRetry(fn$2, maxRetries = 3) {
24423
- let orgSwitched = false;
24424
- for (let attempt = 0; attempt <= maxRetries; attempt++) try {
24425
- return await fn$2();
24426
- } catch (error) {
24427
- if (!orgSwitched && isOrgMismatchError(error)) {
24428
- if (applyOrgMismatchSwitch(error)) {
24429
- orgSwitched = true;
24430
- continue;
24431
- }
24432
- }
24433
- if (attempt >= maxRetries) throw error;
24434
- const rateLimit = isRateLimitError(error);
24435
- if (rateLimit) {
24436
- const waitMs = Math.min(rateLimit.retryAfterMs, 3e4);
24437
- await new Promise((r) => {
24438
- setTimeout(r, waitMs);
24439
- });
24440
- } else {
24441
- const delay$2 = Math.min(1e3 * Math.pow(2, attempt), 1e4);
24442
- await new Promise((r) => {
24443
- setTimeout(r, delay$2);
24444
- });
24445
- }
24446
- }
24447
- throw new Error("Max retries exceeded");
24448
- }
24449
-
24450
24829
  //#endregion
24451
24830
  //#region src/lib/deploy-core.ts
24452
24831
  async function fetchDeployUrls(client, serviceId) {
@@ -24659,6 +25038,60 @@ async function deployMultipleServices(services, options) {
24659
25038
  if (failed.length > 0 && !process.env.VELOZ_MCP) process.exit(1);
24660
25039
  return results;
24661
25040
  }
25041
+ const DASHBOARD_BASE$1 = process.env.VELOZ_WEB_URL || "https://app.onveloz.com";
25042
+ /**
25043
+ * Trigger deploys for 1-to-N services and return immediately, without waiting
25044
+ * for the build to finish. Uploads the source once per project, creates each
25045
+ * deployment, and starts its build, then reports the initial status.
25046
+ *
25047
+ * Used in MCP mode so an AI agent driving `veloz deploy` is not blocked for the
25048
+ * full build duration (builds can run for many minutes). The caller follows the
25049
+ * build to completion with `veloz builds logs <id>` / `veloz builds show <id>`.
25050
+ */
25051
+ async function startDeploys(services, options) {
25052
+ const { projectRoot } = options;
25053
+ const client = await getClient();
25054
+ const sizeInBytes = await calculateDirectorySize(projectRoot);
25055
+ const sizeMB = Math.round(sizeInBytes / (1024 * 1024) * 10) / 10;
25056
+ const baseTarball = await createBaseTarball(projectRoot);
25057
+ const uniqueProjectIds = [...new Set(services.map((s) => s.projectId))];
25058
+ const objectKeyByProject = /* @__PURE__ */ new Map();
25059
+ try {
25060
+ await Promise.all(uniqueProjectIds.map(async (pid) => {
25061
+ const key = await withRetry(() => uploadSharedSource(client, pid, baseTarball.tarPath));
25062
+ objectKeyByProject.set(pid, key);
25063
+ }));
25064
+ } finally {
25065
+ await cleanupTarball(baseTarball);
25066
+ }
25067
+ info(`Upload concluído${sizeMB > 5 ? ` (${sizeMB} MB)` : ""}`);
25068
+ return await Promise.all(services.map(async (service$2) => {
25069
+ const deployment = await withRetry(() => client.deployments.create({
25070
+ serviceId: service$2.serviceId,
25071
+ serviceConfig: service$2.serviceConfig,
25072
+ config: service$2.config
25073
+ }));
25074
+ await withRetry(() => client.deployments.startBuild({
25075
+ deploymentId: deployment.id,
25076
+ objectKey: objectKeyByProject.get(service$2.projectId) ?? ""
25077
+ }));
25078
+ let status = "QUEUED";
25079
+ try {
25080
+ status = (await client.deployments.get({ deploymentId: deployment.id })).status;
25081
+ } catch {}
25082
+ const followCommand = `veloz builds show ${deployment.id}`;
25083
+ success(`Deploy iniciado para ${service$2.serviceName}. Acompanhe com: ${followCommand}`);
25084
+ return {
25085
+ deploymentId: deployment.id,
25086
+ status,
25087
+ logs: [],
25088
+ urls: [],
25089
+ serviceName: service$2.serviceName,
25090
+ dashboardUrl: `${DASHBOARD_BASE$1}/projetos/${service$2.projectId}/servicos/${service$2.serviceId}/deploys/${deployment.id}`,
25091
+ followCommand
25092
+ };
25093
+ }));
25094
+ }
24662
25095
  /**
24663
25096
  * Unified deploy function for 1-to-N services.
24664
25097
  *
@@ -25511,7 +25944,7 @@ async function fetchLatestVersion() {
25511
25944
  }
25512
25945
  }
25513
25946
  function getCurrentVersion() {
25514
- return "0.0.0-beta.39";
25947
+ return "0.0.0-beta.40";
25515
25948
  }
25516
25949
  /**
25517
25950
  * Install a specific CLI version. Returns true on success.
@@ -25785,6 +26218,40 @@ const SERVICE_TYPE_LABELS = {
25785
26218
  WORKER: "Worker",
25786
26219
  DATABASE: "Banco de Dados"
25787
26220
  };
26221
+ /**
26222
+ * Detect-then-require a start command for long-lived process services (WEB/WORKER).
26223
+ *
26224
+ * A WEB/WORKER service with no resolvable start command dead-ends in a cryptic
26225
+ * "No start command could be found" deep in the build (or, worse, a service that
26226
+ * starts a dev server and crash-loops). We catch it before the upload: try local
26227
+ * framework detection and fill the command when found, otherwise fail fast with
26228
+ * an actionable message. STATIC/DATABASE services and Dockerfile builds (own CMD)
26229
+ * are exempt. Mutates `serviceConf.startCommand` in place when a command is found.
26230
+ */
26231
+ function ensureStartCommand(serviceConf, configSnapshot, serviceName) {
26232
+ if (!serviceConf) return;
26233
+ const type = serviceConf.type;
26234
+ if (type !== "WEB" && type !== "WORKER") return;
26235
+ if (serviceConf.startCommand) return;
26236
+ if (configSnapshot?.build?.method === "dockerfile") return;
26237
+ const root = serviceConf.rootDirectory && serviceConf.rootDirectory !== "/" ? serviceConf.rootDirectory : ".";
26238
+ let analysis = null;
26239
+ try {
26240
+ analysis = detectLocalRepo$1(root);
26241
+ } catch {
26242
+ analysis = null;
26243
+ }
26244
+ const detected = analysis?.framework?.startCommand ?? null;
26245
+ if (detected) {
26246
+ serviceConf.startCommand = detected;
26247
+ info(`Comando de start detectado para ${chalk.bold(serviceName)}: ${chalk.dim(detected)}`);
26248
+ return;
26249
+ }
26250
+ const typeLabel = type === "WEB" ? "web" : "worker";
26251
+ const fw = analysis?.framework;
26252
+ if (fw && fw.type === "STATIC") throw new Error(`O serviço "${serviceName}" parece ser um site estático (${fw.name}), mas está configurado como "${typeLabel}". Defina "type": "static" no veloz.json para servi-lo como arquivos estáticos, ou defina "runtime.command" se ele roda um processo.`);
26253
+ throw new Error(`O serviço "${serviceName}" (${typeLabel}) não tem um comando de start e não foi possível detectar um automaticamente. Defina "runtime.command" no veloz.json (ex.: "node dist/index.js") e refaça o deploy.`);
26254
+ }
25788
26255
  function prepareServicesForDeploy(services) {
25789
26256
  const velozConfig = loadConfig$1();
25790
26257
  const allWarnings = [];
@@ -25802,6 +26269,7 @@ function prepareServicesForDeploy(services) {
25802
26269
  return services.map((svc) => {
25803
26270
  const serviceConf = resolveServiceConf(velozConfig, svc.serviceId);
25804
26271
  const configSnapshot = resolveServiceConfigSnapshot(velozConfig, svc.serviceId);
26272
+ ensureStartCommand(serviceConf, configSnapshot, svc.serviceName);
25805
26273
  return {
25806
26274
  serviceId: svc.serviceId,
25807
26275
  serviceName: svc.serviceName,
@@ -25818,6 +26286,7 @@ async function triggerDeploy(serviceId, serviceName) {
25818
26286
  const velozConfig = loadConfig$1();
25819
26287
  const serviceConf = resolveServiceConf(velozConfig, serviceId);
25820
26288
  const configSnapshot = resolveServiceConfigSnapshot(velozConfig, serviceId);
26289
+ ensureStartCommand(serviceConf, configSnapshot, serviceName ?? serviceId);
25821
26290
  const warnings = runPreDeployChecks(serviceConf?.rootDirectory || ".");
25822
26291
  if (warnings.length > 0) printDeployWarnings(warnings);
25823
26292
  const spinUpload = spinner(serviceName ? `Fazendo upload ${chalk.bold(serviceName)}...` : "Fazendo upload do código...");
@@ -26635,7 +27104,8 @@ async function addServiceFlow(existingConfig, opts) {
26635
27104
  }
26636
27105
  function registerDeploy(cli$1) {
26637
27106
  cli$1.command("deploy", {
26638
- description: "Fazer deploy do serviço. Returns streamed progress events. The last event (type='result') contains: deploymentId, status (LIVE/BUILD_FAILED/DEPLOY_FAILED/FAILED/CANCELLED), urls, failReason, errorSummary, commitSha, branch, durationSeconds, dashboardUrl, and logs.",
27107
+ description: "Fazer deploy do serviço",
27108
+ mcp: MCP_DEPLOY,
26639
27109
  middleware: [requireAuth$1],
26640
27110
  options: z.object({
26641
27111
  all: z.boolean().optional().describe("Deploy todos os serviços do monorepo"),
@@ -26846,7 +27316,20 @@ async function* headlessDeployFlow(opts) {
26846
27316
  }
26847
27317
  servicesToProcess = matched;
26848
27318
  }
26849
- const results = await deployServices(await prepareServicesForDeploy(servicesToProcess), {
27319
+ const servicesToDeploy = await prepareServicesForDeploy(servicesToProcess);
27320
+ if (!opts.wait) {
27321
+ const triggered = await startDeploys(servicesToDeploy, {
27322
+ projectRoot: process.cwd(),
27323
+ output: createDeployOutput()
27324
+ });
27325
+ for (const result$2 of triggered) yield {
27326
+ type: "result",
27327
+ message: `Deploy iniciado (${result$2.status}). O build está em andamento e pode levar alguns minutos. Acompanhe o status com "veloz builds show ${result$2.deploymentId}" (repita até o status ser terminal: LIVE, BUILD_FAILED, DEPLOY_FAILED, FAILED ou CANCELLED) e veja os logs com "veloz builds logs ${result$2.deploymentId}".`,
27328
+ data: result$2
27329
+ };
27330
+ return;
27331
+ }
27332
+ const results = await deployServices(servicesToDeploy, {
26850
27333
  projectRoot: process.cwd(),
26851
27334
  output: createDeployOutput()
26852
27335
  });
@@ -27258,6 +27741,7 @@ function registerUse(cli$1) {
27258
27741
  function registerWhoami(cli$1) {
27259
27742
  cli$1.command("whoami", {
27260
27743
  description: "Mostrar usuário e workspace ativo",
27744
+ mcp: MCP_READ_ONLY,
27261
27745
  middleware: [requireAuth$1],
27262
27746
  output: z.object({
27263
27747
  name: z.string(),
@@ -27510,6 +27994,7 @@ async function pruneRemovedEntries(config, existingConfig, services, databases)
27510
27994
  function registerUpdate(cli$1) {
27511
27995
  cli$1.command("update", {
27512
27996
  description: "Atualizar a CLI para a versão mais recente",
27997
+ mcp: false,
27513
27998
  output: z.object({
27514
27999
  updated: z.boolean(),
27515
28000
  version: z.string().nullable()
@@ -30599,7 +31084,7 @@ function PickerMenu({ items, onSelect, onCancel }) {
30599
31084
 
30600
31085
  //#endregion
30601
31086
  //#region src/wizard/ui/primitives/ScreenLayout.tsx
30602
- const version = "0.0.0-beta.39";
31087
+ const version = "0.0.0-beta.40";
30603
31088
  const LOGO_LINES = [
30604
31089
  "██╗ ██╗███████╗██╗ ██████╗ ███████╗",
30605
31090
  "██║ ██║██╔════╝██║ ██╔═══██╗╚══███╔╝",
@@ -34602,6 +35087,7 @@ function detectGitRemote(cwd) {
34602
35087
  function registerInit(cli$1) {
34603
35088
  cli$1.command("init", {
34604
35089
  description: "Wizard de deploy — configura e faz deploy do projeto automaticamente",
35090
+ mcp: false,
34605
35091
  options: z.object({
34606
35092
  framework: z.string().optional().describe("Forçar framework específico (ex: nextjs-app-router, express)"),
34607
35093
  ci: z.boolean().default(false).describe("Modo CI — pula o wizard de IA e faz deploy direto via veloz deploy")
@@ -34612,7 +35098,8 @@ function registerInit(cli$1) {
34612
35098
  },
34613
35099
  output: z.object({
34614
35100
  success: z.boolean(),
34615
- url: z.string().optional()
35101
+ url: z.string().optional(),
35102
+ hint: z.string().optional()
34616
35103
  }),
34617
35104
  outputPolicy: "agent-only",
34618
35105
  async run(c) {
@@ -34646,6 +35133,16 @@ function registerInit(cli$1) {
34646
35133
  }
34647
35134
  async function runInitFlow(c, logger, cwd) {
34648
35135
  const forcedFramework = c.options.framework;
35136
+ if (isAiAgent() && !c.options.ci) {
35137
+ logger.log("mode", "AI agent detected — skipping wizard, guiding to veloz deploy");
35138
+ await ensureSkillForAgent(logger);
35139
+ info("Detectado agente de IA. O wizard de IA foi ignorado.");
35140
+ info("Para fazer deploy, execute: veloz deploy");
35141
+ return {
35142
+ success: true,
35143
+ hint: "Ambiente de agente de IA detectado. Execute `veloz deploy` para fazer deploy. A skill da Veloz foi instalada em .claude/skills/ com a referência completa de comandos."
35144
+ };
35145
+ }
34649
35146
  const analysis = detectLocalRepo(cwd);
34650
35147
  logger.log("detection", "repo analysis complete", {
34651
35148
  framework: analysis.framework?.name ?? null,
@@ -35004,6 +35501,29 @@ async function pollGithubInstallation(_store, owner) {
35004
35501
  return null;
35005
35502
  }
35006
35503
  /**
35504
+ * Install the Veloz agent skill into `.claude/skills/` for an AI agent that ran
35505
+ * `veloz init`, unless it is already present. Best-effort: a failure is surfaced
35506
+ * as a hint, never fatal — the agent can still run `veloz deploy`.
35507
+ */
35508
+ async function ensureSkillForAgent(logger) {
35509
+ if (checkAiSetup().skillsInstalled) {
35510
+ logger.log("ai-setup", "skill already installed — skipping");
35511
+ return;
35512
+ }
35513
+ const result$2 = await runVelozSubcommand([
35514
+ "skills",
35515
+ "add",
35516
+ "--no-global"
35517
+ ]);
35518
+ if (result$2.ok) {
35519
+ logger.log("ai-setup", "skills add succeeded");
35520
+ success("Skill da Veloz instalada em .claude/skills/");
35521
+ } else {
35522
+ logger.log("ai-setup", "skills add failed", { stderr: result$2.stderr });
35523
+ warn("Não foi possível instalar a skill. Execute manualmente: veloz skills add");
35524
+ }
35525
+ }
35526
+ /**
35007
35527
  * In CI / non-TTY contexts (real CI runners, MCP tool calls, Claude Code's
35008
35528
  * bash tool), `veloz init` delegates to the deterministic deploy flow rather
35009
35529
  * than spawning a Claude Code agent. The deploy flow auto-bootstraps the
@@ -35032,7 +35552,8 @@ async function runHeadless(opts) {
35032
35552
  let url;
35033
35553
  for await (const event of headlessDeployFlow({
35034
35554
  all: true,
35035
- yes: true
35555
+ yes: true,
35556
+ wait: true
35036
35557
  })) if (event.type === "result") {
35037
35558
  const data = event.data;
35038
35559
  if (data?.status === "LIVE") {
@@ -35229,13 +35750,18 @@ async function runOrgCreationFlow(store, apiUrl) {
35229
35750
  //#region src/index.ts
35230
35751
  if (process.argv.includes("--mcp")) process.env.VELOZ_MCP = "true";
35231
35752
  const cli = Cli.create("veloz", {
35232
- version: "0.0.0-beta.39",
35753
+ version: "0.0.0-beta.40",
35233
35754
  description: "CLI da plataforma Veloz — deploy rápido para o Brasil",
35234
35755
  env: z.object({ VELOZ_ENV: z.string().optional().describe("Ambiente alvo (ex: preview, staging)") }),
35235
- mcp: { command: "npx -y onveloz --mcp" }
35756
+ globals: z.object({ env: z.string().optional().describe("Ambiente alvo (ex: preview, staging)") }),
35757
+ globalAlias: { env: "e" },
35758
+ mcp: {
35759
+ command: "npx -y onveloz --mcp",
35760
+ instructions: MCP_SERVER_INSTRUCTIONS
35761
+ }
35236
35762
  });
35237
35763
  cli.use(async (c, next) => {
35238
- const env = c.env.VELOZ_ENV;
35764
+ const env = c.globals.env ?? c.env.VELOZ_ENV;
35239
35765
  if (env) setActiveEnv(env);
35240
35766
  await next();
35241
35767
  });
@@ -35329,6 +35855,7 @@ cli.command(projectsGroup);
35329
35855
  cli.command(orgsGroup);
35330
35856
  cli.command(envGroup);
35331
35857
  cli.command(domainsGroup);
35858
+ cli.command(emailGroup);
35332
35859
  cli.command(volumesGroup);
35333
35860
  cli.command(configGroup);
35334
35861
  cli.command(apikeyGroup);