onveloz 0.0.0-beta.24 → 0.0.0-beta.26

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 +658 -106
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -85,17 +85,17 @@ const DISK_ENGINE_SIZES = {
85
85
  },
86
86
  nitro: {
87
87
  label: "Nitro",
88
- cpu: "2",
88
+ cpu: "1",
89
89
  memory: "4Gi",
90
- cpuLabel: "2 vCPU",
90
+ cpuLabel: "1 vCPU",
91
91
  memoryLabel: "4 GB"
92
92
  },
93
93
  "nitro-plus": {
94
94
  label: "Nitro Plus",
95
- cpu: "4",
96
- memory: "8Gi",
97
- cpuLabel: "4 vCPU",
98
- memoryLabel: "8 GB"
95
+ cpu: "2",
96
+ memory: "4Gi",
97
+ cpuLabel: "2 vCPU",
98
+ memoryLabel: "4 GB"
99
99
  }
100
100
  };
101
101
  /** Redis size tiers — memory-heavy since Redis is purely in-memory, CPU stays minimal. */
@@ -138,9 +138,9 @@ const REDIS_SIZES = {
138
138
  "nitro-plus": {
139
139
  label: "Nitro Plus",
140
140
  cpu: "1",
141
- memory: "4Gi",
141
+ memory: "3Gi",
142
142
  cpuLabel: "1 vCPU",
143
- memoryLabel: "4 GB"
143
+ memoryLabel: "3 GB"
144
144
  }
145
145
  };
146
146
  /** Per-engine size tier maps. */
@@ -179,6 +179,14 @@ function resolveDatabaseSize(cpu, memory, engine) {
179
179
  for (const [key, tier] of Object.entries(sizes)) if (tier.cpu === cpu && tier.memory === memory) return key;
180
180
  return null;
181
181
  }
182
+ /**
183
+ * Returns the rank (0-based index) of a database size key.
184
+ * Higher rank = bigger tier. Returns -1 if unknown.
185
+ */
186
+ function getDatabaseSizeRank(size, engine) {
187
+ const sizes = engine ? DATABASE_SIZES_BY_ENGINE[engine] : DISK_ENGINE_SIZES;
188
+ return Object.keys(sizes).indexOf(size);
189
+ }
182
190
  const SERVICE_SIZES = {
183
191
  basico: {
184
192
  label: "Básico",
@@ -1106,9 +1114,29 @@ async function resolveService(serviceFlag) {
1106
1114
  };
1107
1115
  }
1108
1116
  async function resolveServiceId(serviceFlag) {
1117
+ if (serviceFlag) {
1118
+ const config = loadConfig();
1119
+ if (config) {
1120
+ const dbFound = Object.entries(config.databases ?? {}).find(([key, db]) => key === serviceFlag || db.name !== void 0 && db.name === serviceFlag || db.id === serviceFlag);
1121
+ if (dbFound) {
1122
+ const [key, db] = dbFound;
1123
+ if (!db.id) throw new Error(`Banco "${db.name ?? key}" não possui ID. Execute 'veloz deploy' para vincular.`);
1124
+ return db.id;
1125
+ }
1126
+ }
1127
+ }
1109
1128
  const { service } = await resolveService(serviceFlag);
1110
1129
  return service.id;
1111
1130
  }
1131
+ /** Check if a service flag refers to a database in the config */
1132
+ function isDatabaseService(serviceFlag) {
1133
+ const config = loadConfig();
1134
+ if (!config) return false;
1135
+ if (serviceFlag) return Object.entries(config.databases ?? {}).some(([key, db]) => key === serviceFlag || db.name !== void 0 && db.name === serviceFlag || db.id === serviceFlag);
1136
+ const entries = Object.entries(config.services);
1137
+ if (entries.length === 1) return entries[0][1].type === "database";
1138
+ return false;
1139
+ }
1112
1140
  function resolveAllServices(serviceFlag) {
1113
1141
  const config = requireConfig();
1114
1142
  const entries = Object.entries(config.services);
@@ -2974,52 +3002,142 @@ function printSparkline(label, values, suffix, width) {
2974
3002
  const latest = latestValue(values);
2975
3003
  console.log(` ${label.padEnd(width)} ${sparkline(values)} ${chalk.bold(`${formatCompact(latest)}${suffix}`)}`);
2976
3004
  }
3005
+ function formatUptime(seconds) {
3006
+ if (seconds < 60) return `${Math.round(seconds)}s`;
3007
+ if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
3008
+ if (seconds < 86400) return `${Math.round(seconds / 3600)}h`;
3009
+ return `${Math.round(seconds / 86400)}d`;
3010
+ }
3011
+ function formatBytes(bytes) {
3012
+ if (bytes < 1024) return `${bytes.toFixed(0)} B`;
3013
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3014
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3015
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
3016
+ }
3017
+ function formatPercent(value) {
3018
+ return `${value.toFixed(1)}%`;
3019
+ }
3020
+ function printInsightsHint() {
3021
+ console.log();
3022
+ info(`Ative insights para métricas detalhadas do banco: ${chalk.bold("veloz db update <nome> --insights")}`);
3023
+ }
2977
3024
  const metricsGroup = Cli.create("metrics", { description: "Visualizar métricas dos serviços" });
2978
3025
  metricsGroup.command("show", {
2979
3026
  description: "Exibir métricas atuais do serviço",
2980
3027
  middleware: [requireAuth],
2981
- options: z.object({ service: z.string().optional().describe("Filtrar por serviço") }),
3028
+ options: z.object({ service: z.string().optional().describe("Filtrar por serviço ou banco de dados") }),
2982
3029
  output: z.object({
2983
3030
  requestRate: z.number(),
2984
3031
  errorRate: z.number(),
2985
3032
  latencyP95: z.number(),
2986
3033
  statusCodes: z.record(z.string(), z.number()),
2987
- grafanaDashboardUrl: z.string().nullable()
3034
+ grafanaDashboardUrl: z.string().nullable(),
3035
+ resource: z.object({
3036
+ restarts: z.number(),
3037
+ uptimeSeconds: z.number(),
3038
+ oomEvents: z.number(),
3039
+ cpuThrottlePercent: z.number()
3040
+ }).nullable(),
3041
+ database: z.object({
3042
+ activeConnections: z.number(),
3043
+ queriesPerSec: z.number(),
3044
+ cacheHitRatio: z.number(),
3045
+ totalConnections: z.number(),
3046
+ idleConnections: z.number(),
3047
+ databaseSize: z.number(),
3048
+ insightsEnabled: z.boolean()
3049
+ }).nullable(),
3050
+ hint: z.string()
2988
3051
  }),
2989
3052
  async run(c) {
2990
3053
  const serviceId = await resolveServiceId(c.options.service);
2991
3054
  const client = await getClient();
2992
- const metrics = await withSpinner({
3055
+ const isDb = isDatabaseService(c.options.service);
3056
+ const { metrics, resource, database } = await withSpinner({
2993
3057
  text: "Carregando métricas...",
2994
- fn: () => client.metrics.getServiceMetrics({ serviceId })
3058
+ fn: async () => {
3059
+ const [metrics$1, resource$1, database$1] = await Promise.all([
3060
+ client.metrics.getServiceMetrics({ serviceId }),
3061
+ client.metrics.getResourceMetricsInstant({ serviceId }),
3062
+ isDb ? client.databaseInsights.getMetrics({ serviceId }) : null
3063
+ ]);
3064
+ return {
3065
+ metrics: metrics$1,
3066
+ resource: resource$1,
3067
+ database: database$1
3068
+ };
3069
+ }
2995
3070
  });
2996
3071
  if (process.stdout.isTTY) {
3072
+ const W = 22;
2997
3073
  console.log();
2998
- console.log(chalk.bold(" Métricas do Serviço"));
3074
+ if (!isDb) {
3075
+ console.log(chalk.bold(" Métricas de Tráfego"));
3076
+ console.log();
3077
+ console.log(` ${"Requisições/min".padEnd(W)} ${chalk.bold(formatCompact(metrics.requestRate))}`);
3078
+ console.log(` ${"Erros/min".padEnd(W)} ${rateColor(metrics.errorRate)(formatCompact(metrics.errorRate))}`);
3079
+ console.log(` ${"Latência P95".padEnd(W)} ${chalk.bold(formatCompact(metrics.latencyP95, "s"))}`);
3080
+ const codes = Object.entries(metrics.statusCodes);
3081
+ if (codes.length > 0) {
3082
+ console.log();
3083
+ console.log(chalk.dim(" Status HTTP:"));
3084
+ for (const [code, count] of codes.sort(([a], [b]) => a.localeCompare(b))) console.log(` ${statusColor$1(code)(code)} ${formatCompact(count)}`);
3085
+ }
3086
+ console.log();
3087
+ }
3088
+ console.log(chalk.bold(" Recursos"));
2999
3089
  console.log();
3000
- console.log(` Requisições/min ${chalk.bold(formatCompact(metrics.requestRate))}`);
3001
- console.log(` Erros/min ${rateColor(metrics.errorRate)(formatCompact(metrics.errorRate))}`);
3002
- console.log(` Latência P95 ${chalk.bold(formatCompact(metrics.latencyP95, "s"))}`);
3003
- const codes = Object.entries(metrics.statusCodes);
3004
- if (codes.length > 0) {
3090
+ console.log(` ${"Restarts".padEnd(W)} ${chalk.bold(String(resource.restarts))}`);
3091
+ console.log(` ${"Uptime".padEnd(W)} ${chalk.bold(formatUptime(resource.uptimeSeconds))}`);
3092
+ console.log(` ${"OOM Events".padEnd(W)} ${resource.oomEvents > 0 ? chalk.red(String(resource.oomEvents)) : chalk.green("0")}`);
3093
+ console.log(` ${"CPU Throttle".padEnd(W)} ${resource.cpuThrottlePercent > 10 ? chalk.yellow(formatPercent(resource.cpuThrottlePercent)) : chalk.green(formatPercent(resource.cpuThrottlePercent))}`);
3094
+ if (isDb && database) {
3095
+ console.log();
3096
+ console.log(chalk.bold(" Banco de Dados"));
3005
3097
  console.log();
3006
- console.log(chalk.dim(" Status HTTP:"));
3007
- for (const [code, count] of codes.sort(([a], [b]) => a.localeCompare(b))) console.log(` ${statusColor$1(code)(code)} ${formatCompact(count)}`);
3098
+ if (database.insightsEnabled) {
3099
+ console.log(` ${"Conexões Ativas".padEnd(W)} ${chalk.bold(String(database.activeConnections))}`);
3100
+ console.log(` ${"Conexões Total".padEnd(W)} ${chalk.bold(String(database.totalConnections))}`);
3101
+ console.log(` ${"Conexões Idle".padEnd(W)} ${chalk.bold(String(database.idleConnections))}`);
3102
+ console.log(` ${"Queries/s".padEnd(W)} ${chalk.bold(formatCompact(database.queriesPerSec))}`);
3103
+ console.log(` ${"Cache Hit Ratio".padEnd(W)} ${database.cacheHitRatio > .9 ? chalk.green(formatPercent(database.cacheHitRatio * 100)) : chalk.yellow(formatPercent(database.cacheHitRatio * 100))}`);
3104
+ console.log(` ${"Tamanho".padEnd(W)} ${chalk.bold(formatBytes(database.databaseSize))}`);
3105
+ } else printInsightsHint();
3008
3106
  }
3009
3107
  if (metrics.grafanaDashboardUrl) {
3010
3108
  console.log();
3011
3109
  info(`Dashboard: ${chalk.underline(metrics.grafanaDashboardUrl)}`);
3012
3110
  }
3013
3111
  console.log();
3112
+ console.log(chalk.dim(" Dicas:"));
3113
+ console.log(chalk.dim(` veloz metrics range Sparklines ao longo do tempo`));
3114
+ console.log(chalk.dim(` veloz metrics list Descobrir métricas disponíveis`));
3115
+ console.log(chalk.dim(` veloz metrics query '<consulta>' Consulta MetricsQL personalizada`));
3116
+ console.log(chalk.dim(` veloz metrics query-help Referência completa de MetricsQL`));
3117
+ console.log();
3014
3118
  }
3015
- return metrics;
3119
+ const hints = [
3120
+ "Use 'veloz metrics range' para sparklines ao longo do tempo.",
3121
+ "Use 'veloz metrics list' para descobrir métricas disponíveis.",
3122
+ "Use 'veloz metrics query <metricsql>' para consultas MetricsQL personalizadas.",
3123
+ "Use 'veloz metrics labels' para descobrir labels e seus valores.",
3124
+ "Use 'veloz metrics series <seletor>' para encontrar séries por seletor.",
3125
+ "Use 'veloz metrics query-help' para referência completa de MetricsQL."
3126
+ ];
3127
+ if (isDb && database && !database.insightsEnabled) hints.unshift("Ative insights para métricas detalhadas do banco: 'veloz db update <nome> --insights'.");
3128
+ return {
3129
+ ...metrics,
3130
+ resource,
3131
+ database,
3132
+ hint: hints.join("\n")
3133
+ };
3016
3134
  }
3017
3135
  });
3018
3136
  metricsGroup.command("range", {
3019
3137
  description: "Exibir métricas em intervalo de tempo com sparklines",
3020
3138
  middleware: [requireAuth],
3021
3139
  options: z.object({
3022
- service: z.string().optional().describe("Filtrar por serviço"),
3140
+ service: z.string().optional().describe("Filtrar por serviço ou banco de dados"),
3023
3141
  range: z.enum([
3024
3142
  "1h",
3025
3143
  "6h",
@@ -3031,100 +3149,508 @@ metricsGroup.command("range", {
3031
3149
  async run(c) {
3032
3150
  const serviceId = await resolveServiceId(c.options.service);
3033
3151
  const client = await getClient();
3034
- const data = await withSpinner({
3035
- text: `Carregando métricas (${c.options.range})...`,
3036
- fn: () => client.metrics.getServiceMetricsRange({
3037
- serviceId,
3038
- range: c.options.range
3039
- })
3152
+ const isDb = isDatabaseService(c.options.service);
3153
+ const range = c.options.range;
3154
+ const { data, dbData } = await withSpinner({
3155
+ text: `Carregando métricas (${range})...`,
3156
+ fn: async () => {
3157
+ const [data$1, dbData$1] = await Promise.all([client.metrics.getServiceMetricsRange({
3158
+ serviceId,
3159
+ range
3160
+ }), isDb ? client.databaseInsights.getMetricsRange({
3161
+ serviceId,
3162
+ range
3163
+ }) : null]);
3164
+ return {
3165
+ data: data$1,
3166
+ dbData: dbData$1
3167
+ };
3168
+ }
3040
3169
  });
3041
3170
  if (process.stdout.isTTY) {
3042
3171
  const W = 20;
3043
- const rangeLabel = chalk.dim(`(últimas ${c.options.range})`);
3172
+ const rangeLabel = chalk.dim(`(últimas ${range})`);
3044
3173
  console.log();
3045
3174
  console.log(chalk.bold(` Métricas ${rangeLabel}`));
3046
3175
  console.log();
3047
- if (data.requestRate.length > 0) {
3048
- const v = seriesValues(data.requestRate);
3049
- console.log(` ${"Req/min".padEnd(W)} ${sparkline(v)} ${chalk.bold(formatCompact(latestValue(v)))}`);
3050
- }
3051
- if (data.latencyP95.length > 0) {
3052
- const v = seriesValues(data.latencyP95);
3053
- console.log(` ${"Latência P95".padEnd(W)} ${sparkline(v)} ${chalk.bold(formatCompact(latestValue(v), "s"))}`);
3054
- }
3055
- if (data.latencyP50.length > 0) {
3056
- const v = seriesValues(data.latencyP50);
3057
- console.log(` ${"Latência P50".padEnd(W)} ${sparkline(v)} ${chalk.bold(formatCompact(latestValue(v), "s"))}`);
3176
+ if (!isDb) {
3177
+ if (data.requestRate.length > 0) {
3178
+ const v = seriesValues(data.requestRate);
3179
+ console.log(` ${"Req/min".padEnd(W)} ${sparkline(v)} ${chalk.bold(formatCompact(latestValue(v)))}`);
3180
+ }
3181
+ if (data.latencyP95.length > 0) {
3182
+ const v = seriesValues(data.latencyP95);
3183
+ console.log(` ${"Latência P95".padEnd(W)} ${sparkline(v)} ${chalk.bold(formatCompact(latestValue(v), "s"))}`);
3184
+ }
3185
+ if (data.latencyP50.length > 0) {
3186
+ const v = seriesValues(data.latencyP50);
3187
+ console.log(` ${"Latência P50".padEnd(W)} ${sparkline(v)} ${chalk.bold(formatCompact(latestValue(v), "s"))}`);
3188
+ }
3058
3189
  }
3059
3190
  for (const series of data.cpuUsage) printSparkline(`CPU (${series.label})`, labeledValues(series), "m", W);
3060
3191
  for (const series of data.memoryUsage) printSparkline(`Memória (${series.label})`, labeledValues(series), "Mi", W);
3061
3192
  if (data.networkIn.length > 0) printSparkline("Rede ↓", seriesValues(data.networkIn), "KB/s", W);
3062
3193
  if (data.networkOut.length > 0) printSparkline("Rede ↑", seriesValues(data.networkOut), "KB/s", W);
3194
+ if (isDb && dbData) if (dbData.insightsEnabled) {
3195
+ console.log();
3196
+ console.log(chalk.bold(" Banco de Dados"));
3197
+ console.log();
3198
+ if (dbData.activeConnections.length > 0) printSparkline("Conexões Ativas", seriesValues(dbData.activeConnections), "", W);
3199
+ if (dbData.queriesPerSec.length > 0) printSparkline("Queries/s", seriesValues(dbData.queriesPerSec), "", W);
3200
+ if (dbData.cacheHitRatio.length > 0) printSparkline("Cache Hit", seriesValues(dbData.cacheHitRatio).map((r) => r * 100), "%", W);
3201
+ } else printInsightsHint();
3202
+ console.log();
3203
+ }
3204
+ const hint = isDb && dbData && !dbData.insightsEnabled ? "Ative insights para métricas detalhadas do banco: 'veloz db update <nome> --insights'. Use 'veloz metrics query <metricsql>' para consultas personalizadas." : "Use 'veloz metrics query <metricsql>' para consultas MetricsQL personalizadas. Use 'veloz metrics list' para descobrir métricas disponíveis.";
3205
+ return {
3206
+ ...data,
3207
+ database: dbData,
3208
+ hint
3209
+ };
3210
+ }
3211
+ });
3212
+ metricsGroup.command("query", {
3213
+ description: "Executar consulta MetricsQL personalizada",
3214
+ middleware: [requireAuth],
3215
+ args: z.object({ query: z.string().describe("Consulta MetricsQL (ex: up, rate(http_requests[5m]))") }),
3216
+ options: z.object({
3217
+ service: z.string().optional().describe("Serviço para contexto de autenticação"),
3218
+ start: z.string().optional().describe("Início do intervalo (ISO 8601, unix timestamp, ou relativo: 1h, 6h, 24h, 7d)"),
3219
+ end: z.string().optional().describe("Fim do intervalo (padrão: agora)"),
3220
+ step: z.string().optional().describe("Resolução (ex: 30s, 1m, 5m). Padrão: auto")
3221
+ }),
3222
+ output: z.object({
3223
+ resultType: z.string(),
3224
+ results: z.array(z.object({
3225
+ metric: z.record(z.string(), z.string()),
3226
+ values: z.array(z.object({
3227
+ timestamp: z.number(),
3228
+ value: z.number()
3229
+ }))
3230
+ }))
3231
+ }),
3232
+ async run(c) {
3233
+ const client = await getClient();
3234
+ let start = c.options.start;
3235
+ if (start && /^\d+[smhdw]$/.test(start)) start = `-${start}`;
3236
+ let step = c.options.step;
3237
+ if (start && !step) {
3238
+ const durationMatch = start.replace(/^-/, "").match(/^(\d+)([smhdw])$/);
3239
+ if (durationMatch) {
3240
+ const duration = parseInt(durationMatch[1], 10) * ({
3241
+ s: 1,
3242
+ m: 60,
3243
+ h: 3600,
3244
+ d: 86400,
3245
+ w: 604800
3246
+ }[durationMatch[2]] ?? 1);
3247
+ if (duration <= 3600) step = "30s";
3248
+ else if (duration <= 21600) step = "60s";
3249
+ else if (duration <= 86400) step = "300s";
3250
+ else step = "1800s";
3251
+ } else step = "60s";
3252
+ }
3253
+ const result = await withSpinner({
3254
+ text: "Executando consulta...",
3255
+ fn: () => client.metrics.queryMetrics({
3256
+ query: c.args.query,
3257
+ start,
3258
+ end: c.options.end,
3259
+ step
3260
+ })
3261
+ });
3262
+ if (process.stdout.isTTY) {
3263
+ console.log();
3264
+ if (result.results.length === 0) info("Nenhum resultado para esta consulta.");
3265
+ else {
3266
+ console.log(chalk.dim(` Tipo: ${result.resultType} | ${result.results.length} série(s)`));
3267
+ console.log();
3268
+ for (const series of result.results) {
3269
+ const labels = Object.entries(series.metric).map(([k, v]) => `${chalk.dim(k)}=${chalk.cyan(`"${v}"`)}`).join(" ");
3270
+ if (series.values.length === 1) {
3271
+ const val = series.values[0];
3272
+ console.log(` ${labels} ${chalk.bold(formatCompact(val.value))}`);
3273
+ } else if (series.values.length > 1) {
3274
+ const values = series.values.map((point) => point.value);
3275
+ const latest = latestValue(values);
3276
+ console.log(` ${labels}`);
3277
+ console.log(` ${sparkline(values)} ${chalk.bold(formatCompact(latest))}`);
3278
+ }
3279
+ }
3280
+ }
3281
+ console.log();
3282
+ info(`Execute ${chalk.bold("veloz metrics query-help")} para ver a referência completa de MetricsQL.`);
3283
+ console.log();
3284
+ }
3285
+ return {
3286
+ ...result,
3287
+ hint: "Use 'veloz metrics list' para descobrir métricas. Use 'veloz metrics labels --label <nome>' para ver valores de labels. Use 'veloz metrics series <seletor>' para encontrar séries. Use 'veloz metrics query-help' para referência completa de MetricsQL."
3288
+ };
3289
+ }
3290
+ });
3291
+ metricsGroup.command("list", {
3292
+ description: "Listar nomes de métricas disponíveis",
3293
+ middleware: [requireAuth],
3294
+ options: z.object({
3295
+ match: z.string().optional().describe("Filtrar por seletor (ex: \"{job=\\\"node\\\"}\", \"traefik_.*\")"),
3296
+ limit: z.number().default(500).describe("Número máximo de resultados")
3297
+ }),
3298
+ output: z.object({ names: z.array(z.string()) }),
3299
+ async run(c) {
3300
+ const client = await getClient();
3301
+ const result = await withSpinner({
3302
+ text: "Listando métricas...",
3303
+ fn: () => client.metrics.listMetricNames({
3304
+ match: c.options.match,
3305
+ limit: c.options.limit
3306
+ })
3307
+ });
3308
+ if (process.stdout.isTTY) {
3309
+ console.log();
3310
+ if (result.names.length === 0) info("Nenhuma métrica encontrada.");
3311
+ else {
3312
+ console.log(chalk.dim(` ${result.names.length} métrica(s) encontrada(s)`));
3313
+ console.log();
3314
+ for (const name of result.names) console.log(` ${name}`);
3315
+ }
3316
+ console.log();
3317
+ }
3318
+ return {
3319
+ ...result,
3320
+ hint: "Use 'veloz metrics labels --label <nome>' para ver valores de um label. Use 'veloz metrics series <nome_da_metrica>' para encontrar séries. Use 'veloz metrics query <metricsql>' para consultar dados."
3321
+ };
3322
+ }
3323
+ });
3324
+ metricsGroup.command("labels", {
3325
+ description: "Listar labels disponíveis ou valores de um label específico",
3326
+ middleware: [requireAuth],
3327
+ options: z.object({
3328
+ label: z.string().optional().describe("Nome do label para listar valores (ex: pod, namespace)"),
3329
+ match: z.string().optional().describe("Filtrar por seletor de métrica (ex: \"container_cpu_usage_seconds_total\")"),
3330
+ limit: z.number().default(500).describe("Número máximo de resultados")
3331
+ }),
3332
+ output: z.object({
3333
+ values: z.array(z.string()),
3334
+ type: z.string()
3335
+ }),
3336
+ async run(c) {
3337
+ const client = await getClient();
3338
+ const result = await withSpinner({
3339
+ text: c.options.label ? `Listando valores de "${c.options.label}"...` : "Listando labels...",
3340
+ fn: () => client.metrics.listLabels({
3341
+ label: c.options.label,
3342
+ match: c.options.match,
3343
+ limit: c.options.limit
3344
+ })
3345
+ });
3346
+ if (process.stdout.isTTY) {
3347
+ console.log();
3348
+ if (result.values.length === 0) info("Nenhum resultado encontrado.");
3349
+ else {
3350
+ const heading = result.type === "names" ? "Labels disponíveis" : `Valores de "${result.type}"`;
3351
+ console.log(chalk.dim(` ${heading} (${result.values.length})`));
3352
+ console.log();
3353
+ for (const val of result.values) console.log(` ${val}`);
3354
+ }
3355
+ console.log();
3356
+ }
3357
+ return {
3358
+ ...result,
3359
+ hint: "Use 'veloz metrics series <nome_da_metrica>' para encontrar séries com labels. Use 'veloz metrics query <metricsql>' para consultar dados. Use 'veloz metrics query-help' para referência MetricsQL."
3360
+ };
3361
+ }
3362
+ });
3363
+ metricsGroup.command("series", {
3364
+ description: "Encontrar séries de métricas por seletor",
3365
+ middleware: [requireAuth],
3366
+ args: z.object({ match: z.string().describe("Seletor de séries (ex: \"traefik_service_requests_total\", '{__name__=~\"pg_.*\"}')") }),
3367
+ options: z.object({ limit: z.number().default(100).describe("Número máximo de séries") }),
3368
+ output: z.object({
3369
+ series: z.array(z.record(z.string(), z.string())),
3370
+ count: z.number()
3371
+ }),
3372
+ async run(c) {
3373
+ const client = await getClient();
3374
+ const result = await withSpinner({
3375
+ text: "Buscando séries...",
3376
+ fn: () => client.metrics.findSeries({
3377
+ match: c.args.match,
3378
+ limit: c.options.limit
3379
+ })
3380
+ });
3381
+ if (process.stdout.isTTY) {
3382
+ console.log();
3383
+ if (result.series.length === 0) info("Nenhuma série encontrada para este seletor.");
3384
+ else {
3385
+ console.log(chalk.dim(` ${result.count} série(s) encontrada(s)`));
3386
+ console.log();
3387
+ for (const s of result.series) {
3388
+ const name = s.__name__ ?? "";
3389
+ const labels = Object.entries(s).filter(([k]) => k !== "__name__").map(([k, v]) => `${chalk.dim(k)}=${chalk.cyan(`"${v}"`)}`).join(", ");
3390
+ console.log(` ${chalk.bold(name)}${labels ? ` {${labels}}` : ""}`);
3391
+ }
3392
+ }
3063
3393
  console.log();
3064
3394
  }
3065
- return data;
3395
+ return {
3396
+ ...result,
3397
+ hint: "Use 'veloz metrics query <metricsql>' para consultar os dados dessas séries. Use 'veloz metrics labels --label <nome>' para ver valores de um label específico."
3398
+ };
3066
3399
  }
3067
3400
  });
3068
- const METRICS_HELP_SECTIONS = [
3401
+ const QUERY_HELP_SECTIONS$1 = [
3069
3402
  {
3070
- title: "Métricas disponíveis",
3071
- content: ` metrics show Snapshot instantâneo do serviço
3072
- metrics range Séries temporais com sparklines no terminal`
3403
+ title: "Comandos disponíveis",
3404
+ content: ` metrics show Snapshot instantâneo do serviço
3405
+ metrics range Séries temporais com sparklines
3406
+ metrics query <consulta> Consulta MetricsQL personalizada
3407
+ metrics list Listar nomes de métricas disponíveis
3408
+ metrics labels Listar labels ou seus valores
3409
+ metrics series <seletor> Encontrar séries por seletor
3410
+ metrics query-help Esta referência`
3073
3411
  },
3074
3412
  {
3075
3413
  title: "metrics show — campos retornados",
3076
- content: ` requestRate Requisições por minuto (req/min)
3077
- errorRate Erros 5xx por minuto
3078
- latencyP95 Latência percentil 95 (segundos)
3079
- statusCodes Mapa de código HTTP → contagem (ex: {"200": 1500, "404": 12})
3080
- grafanaDashboardUrl URL do dashboard Grafana (pode ser null)`
3414
+ content: ` Tráfego (serviços web/worker):
3415
+ requestRate Requisições por minuto (req/min)
3416
+ errorRate Erros 5xx por minuto
3417
+ latencyP95 Latência percentil 95 (segundos)
3418
+ statusCodes Mapa de código HTTP contagem
3419
+
3420
+ Recursos (todos os serviços):
3421
+ restarts Número de restarts
3422
+ uptimeSeconds Tempo de atividade em segundos
3423
+ oomEvents Eventos de falta de memória (OOM)
3424
+ cpuThrottlePercent Porcentagem de throttle de CPU
3425
+
3426
+ Banco de dados (requer insights ativado):
3427
+ activeConnections Conexões ativas
3428
+ totalConnections Total de conexões
3429
+ idleConnections Conexões ociosas
3430
+ queriesPerSec Consultas por segundo
3431
+ cacheHitRatio Taxa de acerto do cache (0-1)
3432
+ databaseSize Tamanho do banco em bytes`
3081
3433
  },
3082
3434
  {
3083
3435
  title: "metrics range — séries temporais retornadas",
3084
3436
  content: ` Tráfego (Traefik):
3085
- requestRate [{timestamp, value}] — req/min ao longo do tempo
3086
- latencyP50 [{timestamp, value}] — latência mediana
3087
- latencyP95 [{timestamp, value}] latência P95
3088
- latencyP99 [{timestamp, value}] — latência P99
3089
- statusCodes [{label, data}] — série por código HTTP
3437
+ requestRate [{timestamp, value}] — req/min
3438
+ latencyP50 / P95 / P99 [{timestamp, value}] — latência
3439
+ statusCodes [{label, data}] por código HTTP
3090
3440
 
3091
3441
  Recursos (por instância):
3092
- cpuUsage [{label, data}] — millicores por instância
3093
- memoryUsage [{label, data}] — MiB por instância
3094
- networkIn [{timestamp, value}] — KB/s entrada
3095
- networkOut [{timestamp, value}] — KB/s saída`
3442
+ cpuUsage [{label, data}] — millicores
3443
+ memoryUsage [{label, data}] — MiB
3444
+ networkIn / networkOut [{timestamp, value}] — KB/s
3445
+
3446
+ Banco de dados (requer insights):
3447
+ activeConnections [{timestamp, value}] — conexões ativas
3448
+ queriesPerSec [{timestamp, value}] — queries/s
3449
+ cacheHitRatio [{timestamp, value}] — taxa de cache`
3450
+ },
3451
+ {
3452
+ title: "metrics query — consultas MetricsQL",
3453
+ content: ` Consulta instantânea (sem --start):
3454
+ veloz metrics query "up"
3455
+ veloz metrics query 'sum(rate(traefik_service_requests_total[5m]))'
3456
+
3457
+ Consulta com intervalo (range query):
3458
+ veloz metrics query "rate(traefik_service_requests_total[5m])" --start 1h
3459
+ veloz metrics query "container_memory_working_set_bytes" --start 6h --step 5m
3460
+ veloz metrics query "pg_stat_activity_count" --start 24h`
3461
+ },
3462
+ {
3463
+ title: "Seletores de métricas",
3464
+ content: ` nome_da_metrica Seleciona todas as séries
3465
+ nome{label="valor"} Filtro por label exato
3466
+ nome{label=~"regex"} Filtro por regex
3467
+ nome{label!="valor"} Exclusão por valor
3468
+ nome{label!~"regex"} Exclusão por regex
3469
+ nome{a="x", b="y"} Múltiplos filtros (AND)`
3470
+ },
3471
+ {
3472
+ title: "Vetores de intervalo (range vectors)",
3473
+ content: ` metrica[5m] Últimos 5 minutos de amostras
3474
+ metrica[1h] Última hora
3475
+ metrica[1d] Último dia
3476
+ metrica[5m] offset 1h Amostras de 1h atrás`
3477
+ },
3478
+ {
3479
+ title: "Funções de taxa e incremento",
3480
+ content: ` rate(metrica[5m]) Taxa por segundo (para counters)
3481
+ irate(metrica[5m]) Taxa instantânea (últimas 2 amostras)
3482
+ increase(metrica[5m]) Incremento total no período
3483
+ delta(metrica[5m]) Diferença (para gauges)
3484
+ deriv(metrica[5m]) Derivada (para gauges)`
3485
+ },
3486
+ {
3487
+ title: "Funções de agregação",
3488
+ content: ` sum(metrica) by (label) Soma agrupada por label
3489
+ avg(metrica) by (label) Média agrupada
3490
+ min(metrica) by (label) Mínimo
3491
+ max(metrica) by (label) Máximo
3492
+ count(metrica) by (label) Contagem
3493
+ topk(5, metrica) Top 5 séries por valor
3494
+ bottomk(3, metrica) Bottom 3 séries
3495
+ quantile(0.95, metrica) Percentil 95 entre séries`
3496
+ },
3497
+ {
3498
+ title: "Funções de rollup (over time)",
3499
+ content: ` avg_over_time(m[5m]) Média no período
3500
+ max_over_time(m[5m]) Máximo no período
3501
+ min_over_time(m[5m]) Mínimo no período
3502
+ sum_over_time(m[5m]) Soma no período
3503
+ count_over_time(m[5m]) Contagem no período
3504
+ quantile_over_time(0.95, m[5m]) Percentil no período
3505
+ stddev_over_time(m[5m]) Desvio padrão no período
3506
+ last_over_time(m[5m]) Último valor no período`
3507
+ },
3508
+ {
3509
+ title: "Funções de histograma",
3510
+ content: ` histogram_quantile(0.95, sum(rate(metrica_bucket[5m])) by (le))
3511
+ Calcula percentil 95 de histograma (ex: latência P95)
3512
+
3513
+ histogram_quantile(0.50, ...) Mediana (P50)
3514
+ histogram_quantile(0.99, ...) P99`
3515
+ },
3516
+ {
3517
+ title: "Operadores e funções matemáticas",
3518
+ content: ` Aritméticos: + - * / % ^
3519
+ Comparação: == != > < >= <=
3520
+ Lógicos: and or unless
3521
+ Funções: abs() ceil() floor() round()
3522
+ clamp(m, min, max) clamp_min(m, min) clamp_max(m, max)
3523
+ ln() log2() log10() exp() sqrt()`
3524
+ },
3525
+ {
3526
+ title: "MetricsQL — extensões sobre PromQL",
3527
+ content: ` MetricsQL é compatível com PromQL, com extensões extras:
3528
+
3529
+ range_median(m[5m]) Mediana no intervalo
3530
+ range_avg(m[5m]) Média no intervalo (alias de avg_over_time)
3531
+ share_gt_over_time(m[5m], 100) % de amostras acima de 100
3532
+ count_gt_over_time(m[5m], 100) Contagem de amostras acima de 100
3533
+ duration_over_time(m[5m], max) Duração máxima contínua
3534
+ lag(m[5m]) Tempo desde última amostra
3535
+ lifetime(m[5m]) Duração total da série
3536
+ scrape_interval(m[5m]) Intervalo médio entre amostras`
3096
3537
  },
3097
3538
  {
3098
- title: "Intervalos de tempo (--range)",
3099
- content: ` 1h Última hora (padrão)
3100
- 6h Últimas 6 horas
3101
- 24h Últimas 24 horas
3102
- 7d Últimos 7 dias`
3539
+ title: "Métricas disponíveis na Veloz",
3540
+ content: ` Tráfego (Traefik — serviços web):
3541
+ traefik_service_requests_total Requisições HTTP (counter)
3542
+ traefik_service_request_duration_seconds_bucket Latência (histogram)
3543
+
3544
+ Recursos (cAdvisor — todos os serviços):
3545
+ container_cpu_usage_seconds_total CPU usado (counter)
3546
+ container_memory_working_set_bytes Memória em uso (gauge)
3547
+ container_network_receive_bytes_total Rede entrada (counter)
3548
+ container_network_transmit_bytes_total Rede saída (counter)
3549
+ container_cpu_cfs_throttled_periods_total Períodos com throttle
3550
+ container_cpu_cfs_periods_total Total de períodos CFS
3551
+ container_threads Threads por container
3552
+ container_file_descriptors File descriptors abertos
3553
+ container_oom_events_total Eventos OOM (counter)
3554
+
3555
+ Kubernetes:
3556
+ kube_pod_container_status_restarts_total Restarts (counter)
3557
+ container_start_time_seconds Timestamp de início
3558
+
3559
+ Banco de dados (PostgreSQL — requer insights):
3560
+ pg_stat_activity_count Conexões por estado
3561
+ pg_stat_database_xact_commit Transactions commit
3562
+ pg_stat_database_xact_rollback Transactions rollback
3563
+ pg_stat_database_blks_hit Blocos do cache (buffer)
3564
+ pg_stat_database_blks_read Blocos lidos do disco
3565
+ pg_database_size_bytes Tamanho do banco
3566
+
3567
+ Plataforma (métricas internas):
3568
+ veloz_rpc_requests_total Chamadas RPC (counter)
3569
+ veloz_rpc_request_duration_seconds Latência RPC (histogram)
3570
+ veloz_http_requests_total Requisições HTTP internas
3571
+ veloz_job_total Jobs processados (counter)
3572
+ veloz_job_duration_seconds Duração de jobs (histogram)
3573
+ veloz_job_active Jobs ativos (gauge)
3574
+ veloz_dlq_depth Profundidade da DLQ (gauge)`
3103
3575
  },
3104
3576
  {
3105
- title: "Tipos de dados",
3106
- content: ` TimeSeriesPoint { timestamp: number, value: number }
3107
- LabeledTimeSeries { label: string, data: TimeSeriesPoint[] }
3108
- label identifica a instância (ex: "web-abc12")`
3577
+ title: "Descoberta de métricas (list, labels, series)",
3578
+ content: ` Listar todas as métricas:
3579
+ veloz metrics list
3580
+ veloz metrics list --match '{job="node"}'
3581
+
3582
+ Listar labels disponíveis:
3583
+ veloz metrics labels
3584
+ veloz metrics labels --match "container_cpu_usage_seconds_total"
3585
+
3586
+ Listar valores de um label:
3587
+ veloz metrics labels --label pod
3588
+ veloz metrics labels --label namespace
3589
+ veloz metrics labels --label state --match "pg_stat_activity_count"
3590
+
3591
+ Encontrar séries:
3592
+ veloz metrics series "traefik_service_requests_total"
3593
+ veloz metrics series '{__name__=~"pg_.*"}'
3594
+ veloz metrics series '{namespace="proj-meu-projeto"}'`
3109
3595
  },
3110
3596
  {
3111
- title: "Exemplos de uso",
3112
- content: ` veloz metrics show Métricas atuais (serviço padrão)
3113
- veloz metrics show --service api Métricas da API
3114
- veloz metrics range Sparklines última hora
3115
- veloz metrics range --range 24h Sparklines últimas 24h
3116
- veloz metrics range --range 7d --service web Sparklines 7 dias do frontend`
3597
+ title: "Exemplos práticos",
3598
+ content: ` Tráfego:
3599
+ veloz metrics query 'sum(rate(traefik_service_requests_total[5m])) * 60'
3600
+ veloz metrics query 'histogram_quantile(0.95, sum(rate(traefik_service_request_duration_seconds_bucket[5m])) by (le))'
3601
+ veloz metrics query 'sum(rate(traefik_service_requests_total[5m])) by (code) * 60'
3602
+
3603
+ Recursos:
3604
+ veloz metrics query 'sum by (pod) (rate(container_cpu_usage_seconds_total[5m])) * 1000'
3605
+ veloz metrics query 'sum by (pod) (container_memory_working_set_bytes) / 1024 / 1024'
3606
+ veloz metrics query 'sum(container_oom_events_total) by (pod)' --start 24h
3607
+
3608
+ Banco de dados:
3609
+ veloz metrics query 'pg_stat_activity_count{state="active"}' --start 1h
3610
+ veloz metrics query 'rate(pg_stat_database_xact_commit[5m]) + rate(pg_stat_database_xact_rollback[5m])' --start 6h
3611
+ veloz metrics query 'pg_database_size_bytes' --start 7d
3612
+
3613
+ Range queries com sparklines:
3614
+ veloz metrics query 'rate(traefik_service_requests_total[5m]) * 60' --start 1h
3615
+ veloz metrics query 'container_memory_working_set_bytes / 1024 / 1024' --start 24h --step 5m
3616
+
3617
+ Referência completa:
3618
+ https://docs.victoriametrics.com/metricsql/`
3117
3619
  },
3118
3620
  {
3119
- title: "Workflow para agentes IA",
3120
- content: ` 1. veloz metrics show → verificar saúde geral
3121
- 2. Se errorRate > 0:
3122
- veloz logs search "error" → investigar causa dos erros
3123
- 3. veloz metrics range --range 1h verificar tendência recente
3124
- 4. Se latencyP95 alto:
3125
- veloz metrics range --range 24h → comparar com padrão do dia`
3621
+ title: "Dica: insights para bancos de dados",
3622
+ content: ` As métricas pg_stat_* e pg_database_* requerem insights ativado.
3623
+ Para ativar: veloz db update <nome> --insights
3624
+
3625
+ Após ativar, o exporter coleta métricas do PostgreSQL
3626
+ automaticamente. As métricas ficam disponíveis em ~2 minutos.`
3126
3627
  }
3127
3628
  ];
3629
+ metricsGroup.command("query-help", {
3630
+ description: "Referência de MetricsQL para consultas de métricas",
3631
+ output: z.object({ sections: z.array(z.object({
3632
+ title: z.string(),
3633
+ content: z.string()
3634
+ })) }),
3635
+ async run() {
3636
+ if (process.stdout.isTTY) {
3637
+ console.log();
3638
+ console.log(chalk.bold(" Referência MetricsQL"));
3639
+ console.log(chalk.dim(" Sintaxe de consulta para métricas VictoriaMetrics"));
3640
+ console.log();
3641
+ for (const section of QUERY_HELP_SECTIONS$1) {
3642
+ console.log(chalk.bold.underline(` ${section.title}`));
3643
+ console.log();
3644
+ console.log(section.content);
3645
+ console.log();
3646
+ }
3647
+ console.log(chalk.dim(" Uso: veloz metrics query '<consulta>' [--start 1h|6h|24h|7d] [--step 30s|1m|5m]"));
3648
+ console.log(chalk.dim(" Docs: https://docs.victoriametrics.com/metricsql/"));
3649
+ console.log();
3650
+ }
3651
+ return { sections: QUERY_HELP_SECTIONS$1 };
3652
+ }
3653
+ });
3128
3654
  metricsGroup.command("help", {
3129
3655
  description: "Referência de métricas disponíveis e seus campos",
3130
3656
  output: z.object({ sections: z.array(z.object({
@@ -3135,18 +3661,18 @@ metricsGroup.command("help", {
3135
3661
  if (process.stdout.isTTY) {
3136
3662
  console.log();
3137
3663
  console.log(chalk.bold(" Referência de Métricas"));
3138
- console.log(chalk.dim(" Dados disponíveis via metrics show e metrics range"));
3664
+ console.log(chalk.dim(" Dica: use 'veloz metrics query-help' para a referência completa de MetricsQL"));
3139
3665
  console.log();
3140
- for (const section of METRICS_HELP_SECTIONS) {
3666
+ for (const section of QUERY_HELP_SECTIONS$1.slice(0, 4)) {
3141
3667
  console.log(chalk.bold.underline(` ${section.title}`));
3142
3668
  console.log();
3143
3669
  console.log(section.content);
3144
3670
  console.log();
3145
3671
  }
3146
- console.log(chalk.dim(" Uso: veloz metrics show [--service NOME] | veloz metrics range [--range 1h|6h|24h|7d]"));
3672
+ console.log(chalk.dim(" Para referência completa de MetricsQL: veloz metrics query-help"));
3147
3673
  console.log();
3148
3674
  }
3149
- return { sections: METRICS_HELP_SECTIONS };
3675
+ return { sections: QUERY_HELP_SECTIONS$1.slice(0, 4) };
3150
3676
  }
3151
3677
  });
3152
3678
 
@@ -3233,11 +3759,13 @@ logsGroup.command("show", {
3233
3759
  spin.stop();
3234
3760
  info("Streaming de logs ativo. Pressione Ctrl+C para sair.\n");
3235
3761
  const lines$1 = await streamFollow(services, maxNameLen, tailLines);
3236
- return lines$1.length > 0 ? lines$1.join("\n") : "Nenhum log encontrado.";
3762
+ const hint$1 = "\nDica: para consultas avançadas (filtros, regex, agregações), use `veloz logs search <consulta>`. Execute `veloz logs query-help` para ver a referência completa de sintaxe LogsQL.";
3763
+ return lines$1.length > 0 ? lines$1.join("\n") + hint$1 : "Nenhum log encontrado." + hint$1;
3237
3764
  }
3238
3765
  spin.stop();
3239
3766
  const lines = await fetchRecent(services, maxNameLen, tailLines);
3240
- return lines.length > 0 ? lines.join("\n") : "Nenhum log encontrado.";
3767
+ const hint = "\nDica: para consultas avançadas (filtros, regex, agregações), use `veloz logs search <consulta>`. Execute `veloz logs query-help` para ver a referência completa de sintaxe LogsQL.";
3768
+ return lines.length > 0 ? lines.join("\n") + hint : "Nenhum log encontrado." + hint;
3241
3769
  }
3242
3770
  });
3243
3771
  logsGroup.command("search", {
@@ -3259,14 +3787,21 @@ logsGroup.command("search", {
3259
3787
  })),
3260
3788
  async run(c) {
3261
3789
  const serviceId = await resolveServiceId(c.options.service);
3262
- const entries = await (await getClient()).logs.search({
3263
- serviceId,
3264
- query: c.args.query,
3265
- start: c.options.start,
3266
- end: c.options.end,
3267
- limit: c.options.limit,
3268
- deploymentId: c.options.deployment
3269
- });
3790
+ const client = await getClient();
3791
+ let entries;
3792
+ try {
3793
+ entries = await client.logs.search({
3794
+ serviceId,
3795
+ query: c.args.query,
3796
+ start: c.options.start,
3797
+ end: c.options.end,
3798
+ limit: c.options.limit,
3799
+ deploymentId: c.options.deployment
3800
+ });
3801
+ } catch (err) {
3802
+ warn(err instanceof Error ? err.message : "Erro ao pesquisar logs");
3803
+ return [];
3804
+ }
3270
3805
  if (process.stdout.isTTY) if (entries.length === 0) info("Nenhum log encontrado para essa consulta.");
3271
3806
  else {
3272
3807
  console.log();
@@ -4748,7 +5283,7 @@ const LOGO_LINES = [
4748
5283
  ];
4749
5284
  const BRAND_COLOR = "#FF4D00";
4750
5285
  function getVersion() {
4751
- return "0.0.0-beta.24";
5286
+ return "0.0.0-beta.26";
4752
5287
  }
4753
5288
  function printBanner(subtitle) {
4754
5289
  const version = getVersion();
@@ -4975,7 +5510,14 @@ async function deployMultipleServices(services, options) {
4975
5510
  urls: []
4976
5511
  }));
4977
5512
  output.printSummary(successful, failed);
4978
- if (successful.length > 0) output.printFollowUp();
5513
+ if (successful.length > 0) {
5514
+ let hasGithub = false;
5515
+ try {
5516
+ const projectId = services[0]?.projectId;
5517
+ if (projectId) hasGithub = !!(await client.projects.get({ projectId })).githubInstallationId;
5518
+ } catch {}
5519
+ output.printFollowUp({ hasGithub });
5520
+ }
4979
5521
  const results = [];
4980
5522
  for (const [serviceId, progress] of progressMap) results.push({
4981
5523
  deploymentId: deploymentIdMap.get(serviceId) ?? "",
@@ -5088,7 +5630,7 @@ var TtyOutput = class {
5088
5630
  }
5089
5631
  }
5090
5632
  }
5091
- printFollowUp() {
5633
+ printFollowUp(_context) {
5092
5634
  console.log(chalk.cyan("\nℹ Use 'veloz logs -f' para acompanhar os logs de execução."));
5093
5635
  console.log(chalk.dim(" Use 'veloz builds list' para ver o histórico de builds."));
5094
5636
  }
@@ -5144,7 +5686,7 @@ var PlainOutput = class {
5144
5686
  }
5145
5687
  }
5146
5688
  }
5147
- printFollowUp() {
5689
+ printFollowUp(_context) {
5148
5690
  process.stdout.write("Use 'veloz logs -f' para acompanhar os logs de execução.\n");
5149
5691
  process.stdout.write("Use 'veloz builds list' para ver o histórico de builds.\n");
5150
5692
  }
@@ -5204,7 +5746,7 @@ var GhaOutput = class {
5204
5746
  }
5205
5747
  }
5206
5748
  }
5207
- printFollowUp() {
5749
+ printFollowUp(_context) {
5208
5750
  process.stdout.write("Use 'veloz logs -f' para acompanhar os logs de execução.\n");
5209
5751
  process.stdout.write("Use 'veloz builds list' para ver o histórico de builds.\n");
5210
5752
  }
@@ -5259,9 +5801,10 @@ var McpOutput = class {
5259
5801
  }
5260
5802
  }
5261
5803
  }
5262
- printFollowUp() {
5804
+ printFollowUp(context) {
5263
5805
  log("[deploy] Use 'veloz logs -f' para acompanhar os logs de execução.");
5264
5806
  log("[deploy] Use 'veloz builds list' para ver o histórico de builds.");
5807
+ if (context?.hasGithub) log("[deploy] Dica: este projeto tem deploys automaticos via GitHub configurados. Pushes para o branch configurado fazem deploy automaticamente — não é necessário rodar 'veloz deploy' manualmente.");
5265
5808
  }
5266
5809
  };
5267
5810
 
@@ -5794,7 +6337,7 @@ async function autoUpdate() {
5794
6337
  if (process.env.VELOZ_MCP === "true") return;
5795
6338
  const pm = detectPackageManager();
5796
6339
  if (!pm) return;
5797
- const currentVersion = "0.0.0-beta.24";
6340
+ const currentVersion = "0.0.0-beta.26";
5798
6341
  const latestVersion = await fetchLatestVersion();
5799
6342
  if (!latestVersion || latestVersion === currentVersion) return;
5800
6343
  const installCmd = getInstallCommand(pm, latestVersion);
@@ -5921,7 +6464,16 @@ async function provisionDatabases(config, opts) {
5921
6464
  async function updateDatabaseResources(client, key, dbConfig, existing, serviceId, isLive) {
5922
6465
  const desiredSize = dbConfig.size;
5923
6466
  if (!desiredSize) return;
5924
- if ((existing.size ?? resolveDatabaseSize(existing.cpuLimit, existing.memoryLimit, existing.engine)) === desiredSize) return;
6467
+ const currentSize = existing.size ?? resolveDatabaseSize(existing.cpuLimit, existing.memoryLimit, existing.engine);
6468
+ if (currentSize === desiredSize) return;
6469
+ if (currentSize) {
6470
+ const currentRank = getDatabaseSizeRank(currentSize, existing.engine);
6471
+ const desiredRank = getDatabaseSizeRank(desiredSize, existing.engine);
6472
+ if (desiredRank >= 0 && currentRank >= 0 && desiredRank < currentRank) {
6473
+ info(`Banco de dados "${key}" já está em ${DATABASE_SIZES[currentSize]?.label ?? currentSize} — tamanho não pode ser reduzido via deploy.`);
6474
+ return;
6475
+ }
6476
+ }
5925
6477
  if (!isLive) {
5926
6478
  warn(`Banco de dados "${key}" não está LIVE — não é possível alterar recursos agora.`);
5927
6479
  return;
@@ -6111,13 +6663,13 @@ async function findServicesFromConfig() {
6111
6663
  if (missingIds.length > 0 && config.project.id) {
6112
6664
  const client = await getClient();
6113
6665
  const remoteServicesByProject = /* @__PURE__ */ new Map();
6114
- async function getRemoteServices(pid) {
6666
+ const getRemoteServices = async (pid) => {
6115
6667
  const cached = remoteServicesByProject.get(pid);
6116
6668
  if (cached) return cached;
6117
6669
  const services$1 = await withRetry(() => client.services.list({ projectId: pid }));
6118
6670
  remoteServicesByProject.set(pid, services$1);
6119
6671
  return services$1;
6120
- }
6672
+ };
6121
6673
  let configUpdated = false;
6122
6674
  for (const [key, serviceConfig] of missingIds) {
6123
6675
  const effectiveProjectId = serviceConfig.projectId ?? config.project.id;
@@ -7407,7 +7959,7 @@ async function pruneRemovedEntries(config, existingConfig, services, databases)
7407
7959
  //#region src/index.ts
7408
7960
  if (process.argv.includes("--mcp")) process.env.VELOZ_MCP = "true";
7409
7961
  const cli = Cli.create("veloz", {
7410
- version: "0.0.0-beta.24",
7962
+ version: "0.0.0-beta.26",
7411
7963
  description: "CLI da plataforma Veloz — deploy rápido para o Brasil",
7412
7964
  env: z.object({ VELOZ_ENV: z.string().optional().describe("Ambiente alvo (ex: preview, staging)") })
7413
7965
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onveloz",
3
- "version": "0.0.0-beta.24",
3
+ "version": "0.0.0-beta.26",
4
4
  "description": "CLI da plataforma Veloz — deploy rápido para o Brasil",
5
5
  "keywords": [
6
6
  "brasil",