onveloz 0.0.0-beta.31 → 0.0.0-beta.33
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.
- package/dist/index.mjs +2804 -652
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Cli, middleware, z } from "incur";
|
|
3
|
-
import { exec, execSync } from "node:child_process";
|
|
3
|
+
import { exec, execSync, spawn } from "node:child_process";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
@@ -682,6 +682,14 @@ function mergeServiceWithDefaults(service$2, defaults) {
|
|
|
682
682
|
function parseVelozConfig(data) {
|
|
683
683
|
return VelozConfigSchema.parse(data);
|
|
684
684
|
}
|
|
685
|
+
function validateVelozConfig(data) {
|
|
686
|
+
const result$2 = VelozConfigSchema.safeParse(data);
|
|
687
|
+
if (!result$2.success) return {
|
|
688
|
+
success: false,
|
|
689
|
+
error: result$2.error.issues.map((e) => `${String(e.path.join("."))}: ${e.message}`).join("; ")
|
|
690
|
+
};
|
|
691
|
+
return { success: true };
|
|
692
|
+
}
|
|
685
693
|
function resolveConfigForEnv(config, env) {
|
|
686
694
|
const envOverride = config.environments?.[env];
|
|
687
695
|
if (!envOverride) return null;
|
|
@@ -1191,9 +1199,11 @@ function ensureConfigDir() {
|
|
|
1191
1199
|
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1192
1200
|
}
|
|
1193
1201
|
function loadConfig() {
|
|
1202
|
+
const envOrgId = process.env.VELOZ_ORG_ID || void 0;
|
|
1194
1203
|
if (!existsSync(CONFIG_FILE)) return {
|
|
1195
1204
|
apiKey: "",
|
|
1196
|
-
apiUrl: DEFAULT_API_URL
|
|
1205
|
+
apiUrl: DEFAULT_API_URL,
|
|
1206
|
+
organizationId: envOrgId
|
|
1197
1207
|
};
|
|
1198
1208
|
try {
|
|
1199
1209
|
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
@@ -1201,12 +1211,13 @@ function loadConfig() {
|
|
|
1201
1211
|
return {
|
|
1202
1212
|
apiKey: parsed.apiKey ?? "",
|
|
1203
1213
|
apiUrl: ENV_API_URL ?? parsed.apiUrl ?? DEFAULT_API_URL,
|
|
1204
|
-
organizationId: parsed.organizationId
|
|
1214
|
+
organizationId: envOrgId ?? parsed.organizationId
|
|
1205
1215
|
};
|
|
1206
1216
|
} catch {
|
|
1207
1217
|
return {
|
|
1208
1218
|
apiKey: "",
|
|
1209
|
-
apiUrl: DEFAULT_API_URL
|
|
1219
|
+
apiUrl: DEFAULT_API_URL,
|
|
1220
|
+
organizationId: envOrgId
|
|
1210
1221
|
};
|
|
1211
1222
|
}
|
|
1212
1223
|
}
|
|
@@ -1249,23 +1260,58 @@ async function requireAuth(options) {
|
|
|
1249
1260
|
|
|
1250
1261
|
//#endregion
|
|
1251
1262
|
//#region src/lib/client.ts
|
|
1263
|
+
const CLI_VERSION = "0.0.0-beta.33";
|
|
1264
|
+
const USER_AGENT = `veloz-cli/${CLI_VERSION}`;
|
|
1265
|
+
/**
|
|
1266
|
+
* Client source — "cli" by default; the init wizard sets this to "cli-wizard"
|
|
1267
|
+
* so server can flag deployments/audit records as coming from `veloz init`.
|
|
1268
|
+
*/
|
|
1269
|
+
let clientSource = "cli";
|
|
1270
|
+
function setClientSource(source) {
|
|
1271
|
+
clientSource = source;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Single source of truth for headers the CLI sends to the Veloz server.
|
|
1275
|
+
*
|
|
1276
|
+
* Every ORPC request (via getClient) and every raw fetch against the Veloz
|
|
1277
|
+
* backend (Better Auth endpoints, presigned URL negotiations) must route
|
|
1278
|
+
* through this builder. Any new header — workspace scope, project scope,
|
|
1279
|
+
* tenant hints, user-agent — is added here and nowhere else.
|
|
1280
|
+
*/
|
|
1281
|
+
function buildVelozHeaders(authConfig) {
|
|
1282
|
+
const headers = {
|
|
1283
|
+
Authorization: `Bearer ${authConfig.apiKey}`,
|
|
1284
|
+
"User-Agent": USER_AGENT,
|
|
1285
|
+
"X-Veloz-Cli-Version": CLI_VERSION,
|
|
1286
|
+
"X-Veloz-Client-Source": clientSource
|
|
1287
|
+
};
|
|
1288
|
+
const projectConfig = loadConfig$1();
|
|
1289
|
+
if (authConfig.organizationId) headers["X-Organization-Id"] = authConfig.organizationId;
|
|
1290
|
+
if (projectConfig?.project?.id) headers["X-Project-Id"] = projectConfig.project.id;
|
|
1291
|
+
return headers;
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Build headers from the current on-disk config. Use when you have a raw fetch
|
|
1295
|
+
* and you know auth has already been enforced by an upstream middleware.
|
|
1296
|
+
*/
|
|
1297
|
+
async function getAuthHeaders() {
|
|
1298
|
+
return buildVelozHeaders(await requireAuth());
|
|
1299
|
+
}
|
|
1252
1300
|
async function getClient() {
|
|
1253
1301
|
const authConfig = await requireAuth();
|
|
1254
|
-
|
|
1255
|
-
return createClient(authConfig.apiUrl, () => {
|
|
1256
|
-
const headers = {
|
|
1257
|
-
Authorization: `Bearer ${authConfig.apiKey}`,
|
|
1258
|
-
"User-Agent": "veloz-cli"
|
|
1259
|
-
};
|
|
1260
|
-
if (authConfig.organizationId) headers["X-Organization-Id"] = authConfig.organizationId;
|
|
1261
|
-
if (projectConfig?.project?.id) headers["X-Project-Id"] = projectConfig.project.id;
|
|
1262
|
-
return headers;
|
|
1263
|
-
});
|
|
1302
|
+
return createClient(authConfig.apiUrl, () => buildVelozHeaders(authConfig));
|
|
1264
1303
|
}
|
|
1265
1304
|
|
|
1266
1305
|
//#endregion
|
|
1267
1306
|
//#region src/lib/middleware.ts
|
|
1307
|
+
function isDryRun() {
|
|
1308
|
+
return process.argv.includes("--dry-run");
|
|
1309
|
+
}
|
|
1268
1310
|
const requireAuth$1 = middleware(async (c, next) => {
|
|
1311
|
+
if (isDryRun()) {
|
|
1312
|
+
await next();
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1269
1315
|
const config = loadConfig();
|
|
1270
1316
|
if (!(process.env.VELOZ_API_KEY || config?.apiKey)) {
|
|
1271
1317
|
if (!process.stdout.isTTY) return c.error({
|
|
@@ -1315,6 +1361,123 @@ projectsGroup.command("list", {
|
|
|
1315
1361
|
}
|
|
1316
1362
|
});
|
|
1317
1363
|
|
|
1364
|
+
//#endregion
|
|
1365
|
+
//#region src/commands/orgs.ts
|
|
1366
|
+
const orgsGroup = Cli.create("orgs", { description: "Gerenciar workspaces (organizações) do usuário" });
|
|
1367
|
+
orgsGroup.command("list", {
|
|
1368
|
+
description: "Listar workspaces que você pertence",
|
|
1369
|
+
middleware: [requireAuth$1],
|
|
1370
|
+
output: z.object({ items: z.array(z.object({
|
|
1371
|
+
id: z.string(),
|
|
1372
|
+
name: z.string(),
|
|
1373
|
+
slug: z.string(),
|
|
1374
|
+
role: z.string(),
|
|
1375
|
+
isActive: z.boolean()
|
|
1376
|
+
})) }),
|
|
1377
|
+
async run() {
|
|
1378
|
+
const client = await getClient();
|
|
1379
|
+
const orgs = await withSpinner({
|
|
1380
|
+
text: "Carregando workspaces...",
|
|
1381
|
+
fn: () => client.organizations.list()
|
|
1382
|
+
});
|
|
1383
|
+
if (orgs.length === 0) {
|
|
1384
|
+
info("Nenhum workspace encontrado. Crie um em app.onveloz.com/onboarding.");
|
|
1385
|
+
return { items: [] };
|
|
1386
|
+
}
|
|
1387
|
+
return { items: orgs };
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
orgsGroup.command("use", {
|
|
1391
|
+
description: "Selecionar qual workspace usar como padrão",
|
|
1392
|
+
args: z.object({ organizacao: z.string().optional().describe("Nome, slug ou ID do workspace") }),
|
|
1393
|
+
middleware: [requireAuth$1],
|
|
1394
|
+
output: z.object({
|
|
1395
|
+
id: z.string(),
|
|
1396
|
+
name: z.string(),
|
|
1397
|
+
slug: z.string(),
|
|
1398
|
+
role: z.string()
|
|
1399
|
+
}),
|
|
1400
|
+
async run(c) {
|
|
1401
|
+
const client = await getClient();
|
|
1402
|
+
const orgs = await withSpinner({
|
|
1403
|
+
text: "Carregando workspaces...",
|
|
1404
|
+
fn: () => client.organizations.list()
|
|
1405
|
+
});
|
|
1406
|
+
if (orgs.length === 0) throw new Error("Nenhum workspace encontrado. Crie um em app.onveloz.com/onboarding.");
|
|
1407
|
+
const current = loadConfig().organizationId;
|
|
1408
|
+
let selectedId;
|
|
1409
|
+
if (c.args.organizacao) {
|
|
1410
|
+
const query = c.args.organizacao.toLowerCase();
|
|
1411
|
+
const found = orgs.find((o) => o.id === c.args.organizacao || o.slug.toLowerCase() === query || o.name.toLowerCase() === query);
|
|
1412
|
+
if (!found) {
|
|
1413
|
+
const available = orgs.map((o) => ` • ${o.name} (${o.slug})`).join("\n");
|
|
1414
|
+
throw new Error(`Workspace '${c.args.organizacao}' não encontrado.\n\nWorkspaces disponíveis:\n${available}`);
|
|
1415
|
+
}
|
|
1416
|
+
selectedId = found.id;
|
|
1417
|
+
} else selectedId = await promptSelect("Qual workspace usar como padrão?", orgs.map((o) => ({
|
|
1418
|
+
label: `${o.name} ${chalk.dim(`(${o.slug})`)}${o.id === current ? chalk.cyan(" ← atual") : ""}`,
|
|
1419
|
+
value: o.id
|
|
1420
|
+
})));
|
|
1421
|
+
const selected = orgs.find((o) => o.id === selectedId);
|
|
1422
|
+
await client.organizations.setActive({ organizationId: selected.id });
|
|
1423
|
+
saveConfig({ organizationId: selected.id });
|
|
1424
|
+
success(`Workspace padrão: ${chalk.bold(selected.name)} (${selected.slug})`);
|
|
1425
|
+
return c.ok({
|
|
1426
|
+
id: selected.id,
|
|
1427
|
+
name: selected.name,
|
|
1428
|
+
slug: selected.slug,
|
|
1429
|
+
role: selected.role
|
|
1430
|
+
}, { cta: { commands: [{
|
|
1431
|
+
command: "projects list",
|
|
1432
|
+
description: "Listar projetos do workspace"
|
|
1433
|
+
}, {
|
|
1434
|
+
command: "whoami",
|
|
1435
|
+
description: "Ver usuário e workspace ativo"
|
|
1436
|
+
}] } });
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
//#endregion
|
|
1441
|
+
//#region src/lib/org-resolver.ts
|
|
1442
|
+
/**
|
|
1443
|
+
* Ensure an active organization is selected for the current CLI session.
|
|
1444
|
+
*
|
|
1445
|
+
* Short-circuits when:
|
|
1446
|
+
* - config already has organizationId
|
|
1447
|
+
* - a project is linked in the current directory (org inferred server-side from X-Project-Id)
|
|
1448
|
+
*
|
|
1449
|
+
* Otherwise, fetches the user's orgs and prompts when interactive. Non-interactive
|
|
1450
|
+
* multi-org calls throw with a CTA pointing at `veloz orgs use`.
|
|
1451
|
+
*/
|
|
1452
|
+
async function resolveOrgIfNeeded(options) {
|
|
1453
|
+
if (loadConfig().organizationId) return;
|
|
1454
|
+
if (loadConfig$1()?.project?.id) return;
|
|
1455
|
+
const client = await getClient();
|
|
1456
|
+
const orgs = await withSpinner({
|
|
1457
|
+
text: "Carregando workspaces...",
|
|
1458
|
+
fn: () => client.organizations.list()
|
|
1459
|
+
});
|
|
1460
|
+
if (orgs.length === 0) return;
|
|
1461
|
+
if (orgs.length === 1) {
|
|
1462
|
+
const org = orgs[0];
|
|
1463
|
+
saveConfig({ organizationId: org.id });
|
|
1464
|
+
info(`Workspace: ${chalk.bold(org.name)}`);
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
if (options?.nonInteractive || !isInteractive()) {
|
|
1468
|
+
const available = orgs.map((o) => ` • ${o.name} (${o.slug})`).join("\n");
|
|
1469
|
+
throw new Error(`Múltiplos workspaces encontrados. Selecione um com \`veloz orgs use <slug>\`.\n\nWorkspaces disponíveis:\n${available}`);
|
|
1470
|
+
}
|
|
1471
|
+
info(`Você tem acesso a ${orgs.length} workspaces.`);
|
|
1472
|
+
const selectedId = await promptSelect("Selecione o workspace padrão:", orgs.map((o) => ({
|
|
1473
|
+
label: `${o.name} ${chalk.dim(`(${o.slug})`)}`,
|
|
1474
|
+
value: o.id
|
|
1475
|
+
})));
|
|
1476
|
+
saveConfig({ organizationId: selectedId });
|
|
1477
|
+
const selected = orgs.find((o) => o.id === selectedId);
|
|
1478
|
+
info(`Workspace: ${chalk.bold(selected?.name ?? selectedId)}`);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1318
1481
|
//#endregion
|
|
1319
1482
|
//#region src/lib/project-resolver.ts
|
|
1320
1483
|
const CACHE_FILE = join(homedir(), ".veloz", "directory-cache.json");
|
|
@@ -1354,39 +1517,6 @@ function getGlobalProjectFlag() {
|
|
|
1354
1517
|
const idx = process.argv.indexOf("--project");
|
|
1355
1518
|
if (idx !== -1 && idx + 1 < process.argv.length) return process.argv[idx + 1];
|
|
1356
1519
|
}
|
|
1357
|
-
function isMultipleOrgsError$1(error) {
|
|
1358
|
-
if (!(error instanceof Error)) return false;
|
|
1359
|
-
const e = error;
|
|
1360
|
-
return e.data?.code === "MULTIPLE_ORGS" && Array.isArray(e.data?.organizations);
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Fetch projects, handling MULTIPLE_ORGS by prompting for org selection first.
|
|
1364
|
-
*/
|
|
1365
|
-
async function fetchProjectsWithOrgResolution(client) {
|
|
1366
|
-
try {
|
|
1367
|
-
return await client.projects.list();
|
|
1368
|
-
} catch (error) {
|
|
1369
|
-
if (!isMultipleOrgsError$1(error)) throw error;
|
|
1370
|
-
const orgs = error.data.organizations;
|
|
1371
|
-
if (orgs.length === 1) {
|
|
1372
|
-
const org = orgs[0];
|
|
1373
|
-
saveConfig({ organizationId: org.id });
|
|
1374
|
-
info(`Workspace: ${org.name}`);
|
|
1375
|
-
return client.projects.list();
|
|
1376
|
-
}
|
|
1377
|
-
if (!isInteractive()) {
|
|
1378
|
-
const available = orgs.map((o) => ` • ${o.name} (${o.slug})`).join("\n");
|
|
1379
|
-
throw new Error(`Múltiplos workspaces encontrados. Execute 'veloz login' para selecionar um.\n\nWorkspaces:\n${available}`);
|
|
1380
|
-
}
|
|
1381
|
-
const selectedOrgId = await promptSelect("Qual workspace?", orgs.map((o) => ({
|
|
1382
|
-
label: `${o.name} (${o.slug})`,
|
|
1383
|
-
value: o.id
|
|
1384
|
-
})));
|
|
1385
|
-
saveConfig({ organizationId: selectedOrgId });
|
|
1386
|
-
info(`Workspace: ${orgs.find((o) => o.id === selectedOrgId)?.name ?? selectedOrgId}`);
|
|
1387
|
-
return client.projects.list();
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
1520
|
/**
|
|
1391
1521
|
* Resolve a project ID when no veloz.json is present.
|
|
1392
1522
|
*
|
|
@@ -1400,10 +1530,11 @@ async function fetchProjectsWithOrgResolution(client) {
|
|
|
1400
1530
|
async function resolveProjectFromAPI(explicitProject) {
|
|
1401
1531
|
const projectFlag = explicitProject ?? getGlobalProjectFlag();
|
|
1402
1532
|
if (projectFlag) {
|
|
1533
|
+
await resolveOrgIfNeeded();
|
|
1403
1534
|
const client$1 = await getClient();
|
|
1404
1535
|
const projects$1 = await withSpinner({
|
|
1405
1536
|
text: "Carregando projetos...",
|
|
1406
|
-
fn: () =>
|
|
1537
|
+
fn: () => client$1.projects.list()
|
|
1407
1538
|
});
|
|
1408
1539
|
const match$3 = projects$1.find((p) => p.id === projectFlag || p.slug === projectFlag || p.name === projectFlag);
|
|
1409
1540
|
if (!match$3) {
|
|
@@ -1418,10 +1549,11 @@ async function resolveProjectFromAPI(explicitProject) {
|
|
|
1418
1549
|
}
|
|
1419
1550
|
const cached$2 = getCachedProjectId();
|
|
1420
1551
|
if (cached$2) return cached$2;
|
|
1552
|
+
await resolveOrgIfNeeded();
|
|
1421
1553
|
const client = await getClient();
|
|
1422
1554
|
const projects = await withSpinner({
|
|
1423
1555
|
text: "Carregando projetos...",
|
|
1424
|
-
fn: () =>
|
|
1556
|
+
fn: () => client.projects.list()
|
|
1425
1557
|
});
|
|
1426
1558
|
if (projects.length === 0) throw new Error("Nenhum projeto encontrado. Execute 'veloz deploy' para criar seu primeiro projeto.");
|
|
1427
1559
|
if (projects.length === 1) {
|
|
@@ -1752,7 +1884,7 @@ async function resolveProjectId(projectFlag) {
|
|
|
1752
1884
|
|
|
1753
1885
|
//#endregion
|
|
1754
1886
|
//#region src/commands/env.ts
|
|
1755
|
-
const envGroup = Cli.create("env", { description: "Gerenciar variáveis de ambiente" });
|
|
1887
|
+
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}})." });
|
|
1756
1888
|
envGroup.command("list", {
|
|
1757
1889
|
description: "Listar variáveis de ambiente",
|
|
1758
1890
|
middleware: [requireAuth$1],
|
|
@@ -1787,10 +1919,15 @@ envGroup.command("list", {
|
|
|
1787
1919
|
});
|
|
1788
1920
|
continue;
|
|
1789
1921
|
}
|
|
1922
|
+
let anyInterpolated = false;
|
|
1790
1923
|
for (const v of envVars) {
|
|
1791
1924
|
const displayValue = v.value ?? v.maskedValue;
|
|
1792
|
-
|
|
1925
|
+
const hasRef = typeof v.value === "string" && /\$\{[A-Za-z_][A-Za-z0-9_]*\}/.test(v.value);
|
|
1926
|
+
if (hasRef) anyInterpolated = true;
|
|
1927
|
+
const suffix = hasRef ? chalk.cyan(" ·interpolada") : "";
|
|
1928
|
+
console.log(` ${chalk.bold(v.key)} ${chalk.dim(displayValue)}${suffix} ${new Date(v.updatedAt).toLocaleDateString("pt-BR")}`);
|
|
1793
1929
|
}
|
|
1930
|
+
if (anyInterpolated) console.log(chalk.dim(" Valores com ${VAR} são resolvidos no deploy contra as outras vars do serviço."));
|
|
1794
1931
|
result$2.push({
|
|
1795
1932
|
serviceName: service$2.name,
|
|
1796
1933
|
vars: envVars.map((v) => ({
|
|
@@ -1806,7 +1943,7 @@ envGroup.command("list", {
|
|
|
1806
1943
|
}
|
|
1807
1944
|
});
|
|
1808
1945
|
envGroup.command("set", {
|
|
1809
|
-
description: "Definir variável de ambiente (formato: CHAVE=VALOR)",
|
|
1946
|
+
description: "Definir variável de ambiente (formato: CHAVE=VALOR). O valor pode referenciar outras variáveis do mesmo serviço com ${OUTRA_VAR} — a substituição acontece no deploy. Use $${LITERAL} para preservar um ${...} literal. Ex.: DATABASE_URL='${POSTGRES_POOLER_URL}?sslmode=require'.",
|
|
1810
1947
|
middleware: [requireAuth$1],
|
|
1811
1948
|
args: z.object({ pares: z.string().describe("Pares CHAVE=VALOR separados por espaço") }),
|
|
1812
1949
|
options: z.object({
|
|
@@ -1889,7 +2026,7 @@ envGroup.command("delete", {
|
|
|
1889
2026
|
}
|
|
1890
2027
|
});
|
|
1891
2028
|
envGroup.command("import", {
|
|
1892
|
-
description: "Importar variáveis de ambiente de um arquivo .env ou colar diretamente",
|
|
2029
|
+
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.",
|
|
1893
2030
|
middleware: [requireAuth$1],
|
|
1894
2031
|
args: z.object({ arquivo: z.string().optional().describe("Caminho do arquivo .env") }),
|
|
1895
2032
|
options: z.object({
|
|
@@ -2722,7 +2859,7 @@ async function authFetch(path$1, options = {}) {
|
|
|
2722
2859
|
...options,
|
|
2723
2860
|
headers: {
|
|
2724
2861
|
"Content-Type": "application/json",
|
|
2725
|
-
|
|
2862
|
+
...buildVelozHeaders(config),
|
|
2726
2863
|
...options.headers
|
|
2727
2864
|
}
|
|
2728
2865
|
});
|
|
@@ -5136,7 +5273,7 @@ const TERMINAL_STATUSES = new Set([
|
|
|
5136
5273
|
|
|
5137
5274
|
//#endregion
|
|
5138
5275
|
//#region src/lib/deploy-stream.ts
|
|
5139
|
-
const DASHBOARD_BASE = process.env.VELOZ_WEB_URL || "https://app.onveloz.com";
|
|
5276
|
+
const DASHBOARD_BASE$1 = process.env.VELOZ_WEB_URL || "https://app.onveloz.com";
|
|
5140
5277
|
/**
|
|
5141
5278
|
* Fetch deployment details from the API and enrich a DeployStreamResult
|
|
5142
5279
|
* with metadata useful for agents (failReason, commit info, duration, dashboard link).
|
|
@@ -5151,7 +5288,11 @@ async function enrichResult(client, result$2) {
|
|
|
5151
5288
|
result$2.commitSha = d.commitSha ?? null;
|
|
5152
5289
|
result$2.branch = d.branch ?? null;
|
|
5153
5290
|
if (d.startedAt && d.finishedAt) result$2.durationSeconds = Math.round((new Date(d.finishedAt).getTime() - new Date(d.startedAt).getTime()) / 1e3);
|
|
5154
|
-
if (projectId)
|
|
5291
|
+
if (projectId) {
|
|
5292
|
+
result$2.dashboardUrl = `${DASHBOARD_BASE$1}/projetos/${projectId}/servicos/${d.serviceId}/deploys/${d.id}`;
|
|
5293
|
+
const project = await client.projects.get({ projectId }).catch(() => null);
|
|
5294
|
+
if (project) result$2.organizationId = project.organizationId ?? null;
|
|
5295
|
+
}
|
|
5155
5296
|
} catch {}
|
|
5156
5297
|
return result$2;
|
|
5157
5298
|
}
|
|
@@ -24080,7 +24221,7 @@ const LOGO_LINES$1 = [
|
|
|
24080
24221
|
];
|
|
24081
24222
|
const BRAND_COLOR$1 = "#FF4D00";
|
|
24082
24223
|
function getVersion() {
|
|
24083
|
-
return "0.0.0-beta.
|
|
24224
|
+
return "0.0.0-beta.33";
|
|
24084
24225
|
}
|
|
24085
24226
|
function printBanner(subtitle) {
|
|
24086
24227
|
const version$2 = getVersion();
|
|
@@ -24704,6 +24845,16 @@ function createDeployOutput() {
|
|
|
24704
24845
|
|
|
24705
24846
|
//#endregion
|
|
24706
24847
|
//#region src/lib/deploy-config.ts
|
|
24848
|
+
/**
|
|
24849
|
+
* Resolve the full ServiceConfig snapshot for a service id — the SSOT that
|
|
24850
|
+
* gets stored on the Deployment row. Preserves all veloz.json fields
|
|
24851
|
+
* (build.method, dockerfile, context, runtime.healthCheck, autoscale, env,
|
|
24852
|
+
* watch, ignore, …) that the flat `DeploymentServiceConfigInput` drops.
|
|
24853
|
+
*/
|
|
24854
|
+
function resolveServiceConfigSnapshot(velozConfig, serviceId) {
|
|
24855
|
+
if (!velozConfig) return void 0;
|
|
24856
|
+
for (const [, conf] of Object.entries(velozConfig.services)) if (conf.id === serviceId) return mergeServiceWithDefaults(conf, velozConfig.defaults);
|
|
24857
|
+
}
|
|
24707
24858
|
function resolveServiceConf(velozConfig, serviceId) {
|
|
24708
24859
|
if (!velozConfig) return void 0;
|
|
24709
24860
|
for (const [, conf] of Object.entries(velozConfig.services)) if (conf.id === serviceId) {
|
|
@@ -25176,7 +25327,7 @@ async function fetchLatestVersion() {
|
|
|
25176
25327
|
}
|
|
25177
25328
|
}
|
|
25178
25329
|
function getCurrentVersion() {
|
|
25179
|
-
return "0.0.0-beta.
|
|
25330
|
+
return "0.0.0-beta.33";
|
|
25180
25331
|
}
|
|
25181
25332
|
/**
|
|
25182
25333
|
* Install a specific CLI version. Returns true on success.
|
|
@@ -25234,6 +25385,7 @@ async function provisionDatabases(config, opts) {
|
|
|
25234
25385
|
const entries = Object.entries(databases);
|
|
25235
25386
|
if (entries.length === 0) return config;
|
|
25236
25387
|
const projectId = config.project.id;
|
|
25388
|
+
if (!projectId) throw new Error("Projeto ainda não foi vinculado. Execute 'veloz deploy' primeiro.");
|
|
25237
25389
|
const client = await getClient();
|
|
25238
25390
|
const serverDatabases = await withSpinner({
|
|
25239
25391
|
text: "Verificando bancos de dados...",
|
|
@@ -25465,11 +25617,13 @@ function prepareServicesForDeploy(services) {
|
|
|
25465
25617
|
}
|
|
25466
25618
|
return services.map((svc) => {
|
|
25467
25619
|
const serviceConf = resolveServiceConf(velozConfig, svc.serviceId);
|
|
25620
|
+
const configSnapshot = resolveServiceConfigSnapshot(velozConfig, svc.serviceId);
|
|
25468
25621
|
return {
|
|
25469
25622
|
serviceId: svc.serviceId,
|
|
25470
25623
|
serviceName: svc.serviceName,
|
|
25471
25624
|
projectId: svc.projectId,
|
|
25472
|
-
serviceConfig: serviceConf
|
|
25625
|
+
serviceConfig: serviceConf,
|
|
25626
|
+
config: configSnapshot
|
|
25473
25627
|
};
|
|
25474
25628
|
});
|
|
25475
25629
|
}
|
|
@@ -25479,6 +25633,7 @@ async function triggerDeploy(serviceId, serviceName) {
|
|
|
25479
25633
|
const client = await getClient();
|
|
25480
25634
|
const velozConfig = loadConfig$1();
|
|
25481
25635
|
const serviceConf = resolveServiceConf(velozConfig, serviceId);
|
|
25636
|
+
const configSnapshot = resolveServiceConfigSnapshot(velozConfig, serviceId);
|
|
25482
25637
|
const warnings = runPreDeployChecks(serviceConf?.rootDirectory || ".");
|
|
25483
25638
|
if (warnings.length > 0) printDeployWarnings(warnings);
|
|
25484
25639
|
const spinUpload = spinner(serviceName ? `Fazendo upload ${chalk.bold(serviceName)}...` : "Fazendo upload do código...");
|
|
@@ -25486,7 +25641,8 @@ async function triggerDeploy(serviceId, serviceName) {
|
|
|
25486
25641
|
spinUpload.text = "Iniciando deploy...";
|
|
25487
25642
|
const deployment = await withRetry(() => client.deployments.create({
|
|
25488
25643
|
serviceId,
|
|
25489
|
-
serviceConfig: serviceConf
|
|
25644
|
+
serviceConfig: serviceConf,
|
|
25645
|
+
config: configSnapshot
|
|
25490
25646
|
}));
|
|
25491
25647
|
spinUpload.text = "Fazendo upload do código...";
|
|
25492
25648
|
await withRetry(() => uploadSource(deployment.id, process.cwd()));
|
|
@@ -26292,36 +26448,6 @@ async function addServiceFlow(existingConfig, opts) {
|
|
|
26292
26448
|
await provisionDatabases(updatedConfig, { yes: opts.yes ?? false });
|
|
26293
26449
|
await triggerDeploy(service$2.id, service$2.name);
|
|
26294
26450
|
}
|
|
26295
|
-
function isMultipleOrgsError(error) {
|
|
26296
|
-
if (!(error instanceof Error)) return false;
|
|
26297
|
-
const e = error;
|
|
26298
|
-
return e.data?.code === "MULTIPLE_ORGS" && Array.isArray(e.data?.organizations);
|
|
26299
|
-
}
|
|
26300
|
-
async function resolveOrgIfNeeded(nonInteractive) {
|
|
26301
|
-
if ((await requireAuth({ nonInteractive })).organizationId) return;
|
|
26302
|
-
if (loadConfig$1()?.project?.id) return;
|
|
26303
|
-
const client = await getClient();
|
|
26304
|
-
try {
|
|
26305
|
-
await client.projects.list();
|
|
26306
|
-
} catch (error) {
|
|
26307
|
-
if (!isMultipleOrgsError(error)) throw error;
|
|
26308
|
-
const orgs = error.data.organizations;
|
|
26309
|
-
if (orgs.length === 1) {
|
|
26310
|
-
const org = orgs[0];
|
|
26311
|
-
saveConfig({ organizationId: org.id });
|
|
26312
|
-
info(`Workspace: ${chalk.bold(org.name)}`);
|
|
26313
|
-
return;
|
|
26314
|
-
}
|
|
26315
|
-
info(`Você tem acesso a ${orgs.length} workspaces.`);
|
|
26316
|
-
const selectedOrgId = await promptSelect("Selecione o workspace para este deploy:", orgs.map((org) => ({
|
|
26317
|
-
label: `${org.name} ${chalk.dim(`(${org.slug})`)}`,
|
|
26318
|
-
value: org.id
|
|
26319
|
-
})));
|
|
26320
|
-
const selectedOrg = orgs.find((o) => o.id === selectedOrgId);
|
|
26321
|
-
saveConfig({ organizationId: selectedOrgId });
|
|
26322
|
-
info(`Workspace: ${chalk.bold(selectedOrg?.name ?? selectedOrgId)}`);
|
|
26323
|
-
}
|
|
26324
|
-
}
|
|
26325
26451
|
function registerDeploy(cli$1) {
|
|
26326
26452
|
cli$1.command("deploy", {
|
|
26327
26453
|
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.",
|
|
@@ -26392,6 +26518,12 @@ async function dryRunFlow() {
|
|
|
26392
26518
|
warnings
|
|
26393
26519
|
});
|
|
26394
26520
|
}
|
|
26521
|
+
const draftConfig = buildDraftVelozConfig({
|
|
26522
|
+
detection,
|
|
26523
|
+
services,
|
|
26524
|
+
branch
|
|
26525
|
+
});
|
|
26526
|
+
const validation = validateVelozConfig(draftConfig);
|
|
26395
26527
|
if (!isMcpMode()) {
|
|
26396
26528
|
printBanner("Deploy — Dry Run");
|
|
26397
26529
|
if (detection.isMonorepo) info(`Monorepo detectado (${detection.packageManager})${detection.monorepoTool ? ` — ${detection.monorepoTool}` : ""}`);
|
|
@@ -26411,6 +26543,7 @@ async function dryRunFlow() {
|
|
|
26411
26543
|
if (svc.warnings.length > 0) printDeployWarnings(svc.warnings);
|
|
26412
26544
|
}
|
|
26413
26545
|
console.log(chalk.dim(` Upload estimado: ${sizeMB} MB`));
|
|
26546
|
+
if (!validation.success) console.log(chalk.yellow(` Config rascunho inválido: ${validation.error}`));
|
|
26414
26547
|
console.log();
|
|
26415
26548
|
console.log(chalk.cyan(" Dry run — nenhum deploy foi realizado."));
|
|
26416
26549
|
console.log();
|
|
@@ -26419,7 +26552,49 @@ async function dryRunFlow() {
|
|
|
26419
26552
|
services,
|
|
26420
26553
|
isMonorepo: detection.isMonorepo,
|
|
26421
26554
|
monorepoTool: detection.monorepoTool,
|
|
26422
|
-
uploadSizeMB: sizeMB
|
|
26555
|
+
uploadSizeMB: sizeMB,
|
|
26556
|
+
config: draftConfig,
|
|
26557
|
+
configValid: validation.success,
|
|
26558
|
+
configError: validation.error,
|
|
26559
|
+
analysis: detection
|
|
26560
|
+
};
|
|
26561
|
+
}
|
|
26562
|
+
function toConfigServiceType(type) {
|
|
26563
|
+
const lower = type.toLowerCase();
|
|
26564
|
+
if (lower === "static" || lower === "worker") return lower;
|
|
26565
|
+
return "web";
|
|
26566
|
+
}
|
|
26567
|
+
function buildDraftVelozConfig(input) {
|
|
26568
|
+
const { detection, services, branch } = input;
|
|
26569
|
+
const monorepoTool = detection.monorepoTool;
|
|
26570
|
+
const safeBranch = branch && branch !== "unknown" ? branch : void 0;
|
|
26571
|
+
const configServices = {};
|
|
26572
|
+
for (const svc of services) {
|
|
26573
|
+
const buildMethod = existsSync(join(resolve(process.cwd(), svc.rootDir), "Dockerfile")) ? "dockerfile" : monorepoTool ?? "nixpacks";
|
|
26574
|
+
const matchingApp = detection.isMonorepo && detection.monorepoApps.length > 0 ? detection.monorepoApps.find((a) => a.path === svc.rootDir) : void 0;
|
|
26575
|
+
const appName = monorepoTool ? matchingApp?.packageName ?? matchingApp?.name : void 0;
|
|
26576
|
+
const build = buildMethod === "dockerfile" ? { method: "dockerfile" } : monorepoTool || svc.buildCommand ? {
|
|
26577
|
+
method: buildMethod,
|
|
26578
|
+
command: svc.buildCommand ?? void 0,
|
|
26579
|
+
...appName ? { appName } : {}
|
|
26580
|
+
} : void 0;
|
|
26581
|
+
const runtime = {
|
|
26582
|
+
...svc.startCommand ? { command: svc.startCommand } : {},
|
|
26583
|
+
port: svc.port
|
|
26584
|
+
};
|
|
26585
|
+
configServices[svc.rootDir] = {
|
|
26586
|
+
name: svc.name,
|
|
26587
|
+
type: toConfigServiceType(svc.type),
|
|
26588
|
+
root: svc.rootDir,
|
|
26589
|
+
...safeBranch ? { branch: safeBranch } : {},
|
|
26590
|
+
...build ? { build } : {},
|
|
26591
|
+
runtime
|
|
26592
|
+
};
|
|
26593
|
+
}
|
|
26594
|
+
return {
|
|
26595
|
+
version: "1.0",
|
|
26596
|
+
project: { name: basename(process.cwd()) },
|
|
26597
|
+
services: configServices
|
|
26423
26598
|
};
|
|
26424
26599
|
}
|
|
26425
26600
|
async function* mcpDeployFlow(opts) {
|
|
@@ -26434,13 +26609,14 @@ async function* mcpDeployFlow(opts) {
|
|
|
26434
26609
|
}
|
|
26435
26610
|
if (opts.new) {
|
|
26436
26611
|
const existingConfig = loadConfig$1();
|
|
26437
|
-
if (
|
|
26438
|
-
|
|
26439
|
-
|
|
26440
|
-
|
|
26441
|
-
|
|
26442
|
-
|
|
26443
|
-
|
|
26612
|
+
if (existingConfig) {
|
|
26613
|
+
await addServiceFlow(existingConfig, {
|
|
26614
|
+
yes: true,
|
|
26615
|
+
app: opts.app,
|
|
26616
|
+
name: opts.name
|
|
26617
|
+
});
|
|
26618
|
+
return;
|
|
26619
|
+
}
|
|
26444
26620
|
}
|
|
26445
26621
|
let configuredServices = await findServicesFromConfig();
|
|
26446
26622
|
let justBootstrapped = false;
|
|
@@ -26496,7 +26672,7 @@ async function* mcpDeployFlow(opts) {
|
|
|
26496
26672
|
* `veloz:deploy` on a fresh directory without shelling out.
|
|
26497
26673
|
*/
|
|
26498
26674
|
async function mcpBootstrapProjectAndService(opts) {
|
|
26499
|
-
await resolveOrgIfNeeded(true);
|
|
26675
|
+
await resolveOrgIfNeeded({ nonInteractive: true });
|
|
26500
26676
|
const client = await getClient();
|
|
26501
26677
|
const remote = isGitRepo() ? getGitRemote() : null;
|
|
26502
26678
|
const project = remote ? await withRetry(() => client.projects.findByRepo({
|
|
@@ -26507,6 +26683,30 @@ async function mcpBootstrapProjectAndService(opts) {
|
|
|
26507
26683
|
info(`Projeto encontrado: ${project.name}`);
|
|
26508
26684
|
const svc = project.services[0];
|
|
26509
26685
|
const detectedType = svc.type?.toLowerCase() ?? "web";
|
|
26686
|
+
if (loadRawConfig()) {
|
|
26687
|
+
patchConfig((raw) => {
|
|
26688
|
+
raw.project = {
|
|
26689
|
+
...raw.project,
|
|
26690
|
+
id: project.id,
|
|
26691
|
+
name: project.name
|
|
26692
|
+
};
|
|
26693
|
+
raw.services ??= {};
|
|
26694
|
+
const key = Object.keys(raw.services)[0] ?? "main";
|
|
26695
|
+
const existing = raw.services[key];
|
|
26696
|
+
raw.services[key] = {
|
|
26697
|
+
...existing ?? {
|
|
26698
|
+
type: detectedType,
|
|
26699
|
+
root: ".",
|
|
26700
|
+
branch: svc.branch
|
|
26701
|
+
},
|
|
26702
|
+
id: svc.id,
|
|
26703
|
+
name: svc.name
|
|
26704
|
+
};
|
|
26705
|
+
raw.updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
26706
|
+
});
|
|
26707
|
+
info(`Arquivo ${getConfigFileName()} atualizado.`);
|
|
26708
|
+
return;
|
|
26709
|
+
}
|
|
26510
26710
|
saveConfig$1({
|
|
26511
26711
|
version: "1.0",
|
|
26512
26712
|
project: {
|
|
@@ -26563,7 +26763,7 @@ async function cliDeployFlow(opts) {
|
|
|
26563
26763
|
printBanner("Deploy");
|
|
26564
26764
|
await autoUpdate();
|
|
26565
26765
|
await requireAuth({ nonInteractive: opts.yes });
|
|
26566
|
-
await resolveOrgIfNeeded(opts.yes);
|
|
26766
|
+
await resolveOrgIfNeeded({ nonInteractive: opts.yes });
|
|
26567
26767
|
migrateResourcesConfig();
|
|
26568
26768
|
const activeEnv = getActiveEnv();
|
|
26569
26769
|
if (activeEnv) {
|
|
@@ -26865,18 +27065,25 @@ function registerUse(cli$1) {
|
|
|
26865
27065
|
//#region src/commands/whoami.ts
|
|
26866
27066
|
function registerWhoami(cli$1) {
|
|
26867
27067
|
cli$1.command("whoami", {
|
|
26868
|
-
description: "Mostrar usuário
|
|
27068
|
+
description: "Mostrar usuário e workspace ativo",
|
|
26869
27069
|
middleware: [requireAuth$1],
|
|
26870
27070
|
output: z.object({
|
|
26871
27071
|
name: z.string(),
|
|
26872
|
-
email: z.string()
|
|
27072
|
+
email: z.string(),
|
|
27073
|
+
organization: z.object({
|
|
27074
|
+
id: z.string(),
|
|
27075
|
+
name: z.string(),
|
|
27076
|
+
slug: z.string(),
|
|
27077
|
+
role: z.string()
|
|
27078
|
+
}).nullable()
|
|
26873
27079
|
}),
|
|
26874
27080
|
async run(c) {
|
|
26875
27081
|
const user = await (await getClient()).me();
|
|
26876
27082
|
return c.ok({
|
|
26877
27083
|
name: user.name,
|
|
26878
|
-
email: user.email
|
|
26879
|
-
|
|
27084
|
+
email: user.email,
|
|
27085
|
+
organization: user.organization
|
|
27086
|
+
}, { cta: { commands: user.organization ? [
|
|
26880
27087
|
{
|
|
26881
27088
|
command: "deploy",
|
|
26882
27089
|
description: "Fazer deploy do serviço"
|
|
@@ -26888,8 +27095,18 @@ function registerWhoami(cli$1) {
|
|
|
26888
27095
|
{
|
|
26889
27096
|
command: "projects list",
|
|
26890
27097
|
description: "Listar projetos"
|
|
27098
|
+
},
|
|
27099
|
+
{
|
|
27100
|
+
command: "orgs list",
|
|
27101
|
+
description: "Ver todos os workspaces"
|
|
26891
27102
|
}
|
|
26892
|
-
]
|
|
27103
|
+
] : [{
|
|
27104
|
+
command: "orgs list",
|
|
27105
|
+
description: "Listar workspaces disponíveis"
|
|
27106
|
+
}, {
|
|
27107
|
+
command: "orgs use",
|
|
27108
|
+
description: "Selecionar workspace padrão"
|
|
27109
|
+
}] } });
|
|
26893
27110
|
}
|
|
26894
27111
|
});
|
|
26895
27112
|
}
|
|
@@ -27281,16 +27498,6 @@ O usuário forneceu as seguintes instruções ANTES de iniciar o deploy. Trate-a
|
|
|
27281
27498
|
|
|
27282
27499
|
${notes.trim()}`;
|
|
27283
27500
|
}
|
|
27284
|
-
/**
|
|
27285
|
-
* Max width (in columns) for the diagram drawn via wizard-tools:set_helper_content.
|
|
27286
|
-
* The diagram renders inside the right pane of RunScreen (~50% of terminal) and
|
|
27287
|
-
* full-width in OutroScreen. Scale with the current terminal width so we don't
|
|
27288
|
-
* leave half the pane empty on wide terminals.
|
|
27289
|
-
*/
|
|
27290
|
-
function getDiagramWidth() {
|
|
27291
|
-
const cols = process.stdout.columns ?? 120;
|
|
27292
|
-
return Math.max(36, Math.floor(cols * .45));
|
|
27293
|
-
}
|
|
27294
27501
|
function assemblePrompt(ctx) {
|
|
27295
27502
|
return `Você é o wizard de deploy da Veloz. Sua missão é configurar e fazer deploy do projeto do usuário de forma autônoma.
|
|
27296
27503
|
|
|
@@ -27308,7 +27515,12 @@ A Veloz é uma PaaS brasileira que faz deploy de aplicações a partir do códig
|
|
|
27308
27515
|
- Suporta variáveis de ambiente, volumes persistentes e bancos de dados gerenciados
|
|
27309
27516
|
- Escala serviços com presets de tamanho (\`basico\`, \`essencial\`, \`turbo\`, \`turbo-plus\`, \`nitro\`, \`nitro-plus\`)
|
|
27310
27517
|
|
|
27311
|
-
## Passo
|
|
27518
|
+
## Passo 0a — Criar \`veloz-deploy-plano.md\`
|
|
27519
|
+
Antes de tudo, crie o arquivo \`veloz-deploy-plano.md\` na raiz do projeto (\`${ctx.cwd}\`) com o plano inicial. Use Write com um esqueleto em pt-BR contendo: \`# Plano de Deploy — Veloz\`, data/hora inicial, contexto (framework, package manager, org), e seções vazias para \`Detecção\`, \`Configuração\`, \`Deploy\`, \`Health Check\`, \`Próximos Passos\`.
|
|
27520
|
+
|
|
27521
|
+
Depois, ATUALIZE este arquivo ao longo de toda a sessão — a cada passo relevante (detecção de plataforma, análise do projeto, banco detectado, env vars configuradas, diagrama, confirmação, dry-run, deploy real, health check, correções, migração de dados) use Edit para acrescentar/atualizar seções com o que foi feito. Este é o "diário de bordo" que o usuário vai ler depois. NUNCA escreva valores de secrets — apenas nomes de env vars.
|
|
27522
|
+
|
|
27523
|
+
## Passo 0b — Aprender sobre a Veloz
|
|
27312
27524
|
ANTES de qualquer coisa, execute via Bash:
|
|
27313
27525
|
|
|
27314
27526
|
\`\`\`bash
|
|
@@ -27323,13 +27535,97 @@ Leia o arquivo \`.claude/skills/veloz-llms.txt\` para entender a plataforma, fra
|
|
|
27323
27535
|
|
|
27324
27536
|
## Fluxo de Deploy
|
|
27325
27537
|
|
|
27326
|
-
### 1.
|
|
27538
|
+
### 1. Detectar deploys existentes em outras plataformas (PRIMEIRO PASSO — antes de qualquer outra coisa)
|
|
27539
|
+
A primeira coisa que você DEVE fazer — antes de ler package.json, antes de procurar Dockerfile, antes de qualquer Glob — é descobrir se este projeto já está rodando em outro lugar (Vercel, Railway, Fly, Cloudflare, Netlify, Render, Heroku, DigitalOcean, AWS Amplify, Coolify). Se estiver, a Veloz consegue importar a maior parte do trabalho que o usuário já fez lá: build/start commands, env vars, regions. Pular essa detecção significa fazer o usuário re-configurar do zero algo que já existe — inaceitável.
|
|
27540
|
+
|
|
27541
|
+
**Não seja preguiçoso aqui.** Não assuma "provavelmente não tem nada deployado" só porque o repo parece simples. Frontends quase sempre têm \`vercel.json\` ou estão acoplados a um projeto Vercel. APIs Node frequentemente vivem em Railway/Fly/Render. Muitos projetos deployam só via GitHub Actions (sem nenhum config local). Sempre cheque.
|
|
27542
|
+
|
|
27543
|
+
**1a. Chame a ferramenta de detecção (uma vez, no início):**
|
|
27544
|
+
|
|
27545
|
+
\`\`\`
|
|
27546
|
+
wizard-tools:detect_existing_deployments {}
|
|
27547
|
+
\`\`\`
|
|
27548
|
+
|
|
27549
|
+
Esta ferramenta faz TUDO sob o capô: (1) escaneia o disco pelos arquivos de config das plataformas, (2) lê \`.github/workflows/*.yml\` procurando deploy actions de cada plataforma, (3) verifica se o CLI de cada uma está instalado e autenticado. **NÃO use Glob nem Bash (\`which\`, \`vercel whoami\`, leitura manual de workflows etc.) para esse trabalho** — a ferramenta já fez isso de forma estruturada. Use o resultado dela.
|
|
27550
|
+
|
|
27551
|
+
A resposta diz, por plataforma detectada:
|
|
27552
|
+
- \`files_found\`: arquivos de config encontrados na raiz
|
|
27553
|
+
- \`github_workflows\`: workflows do GitHub Actions que deployam para essa plataforma
|
|
27554
|
+
- \`detection_source\`: \`config_files\`, \`github_workflows\`, ou ambos
|
|
27555
|
+
- \`cli\`: se a CLI está instalada e autenticada localmente
|
|
27556
|
+
- \`extract_commands\`: comandos exatos para extrair env vars / config
|
|
27557
|
+
- \`can_bring\`: o que exatamente é portável para a Veloz (frontend code, build/start, env vars, regions, etc.)
|
|
27558
|
+
- \`next_step\`: como proceder dado o estado do CLI / fonte da detecção
|
|
27559
|
+
|
|
27560
|
+
**1b. Se nada foi detectado** (\`any_detected: false\`): faça uma varredura silenciosa de sinais secundários adicionais antes de desistir (a ferramenta já cobriu config files + workflows; aqui você cobre o resto):
|
|
27561
|
+
|
|
27562
|
+
Sinais para checar (Read tool, sem prompts, sem anúncios):
|
|
27563
|
+
- \`package.json\` — \`scripts\` mencionando \`vercel\`, \`netlify\`, \`fly\`, \`railway\`, \`wrangler\`, \`heroku\`, \`amplify\`, \`render-cli\`, \`coolify\`; \`dependencies\`/\`devDependencies\` com \`@vercel/*\`, \`netlify-cli\`, \`wrangler\`, etc.
|
|
27564
|
+
- \`README.md\` / \`README\` — badges ou links \`vercel.app\`, \`netlify.app\`, \`fly.dev\`, \`herokuapp.com\`, \`onrender.com\`, \`pages.dev\`, \`workers.dev\`, instâncias Coolify (subdomínios próprios)
|
|
27565
|
+
- \`.git/config\` — remotes \`heroku.com\`, \`dokku@\`, ou hooks de deploy
|
|
27566
|
+
- Variáveis no shell de quem chamou o wizard (\`process.env\`): \`VERCEL\`, \`VERCEL_*\`, \`RAILWAY_*\`, \`NETLIFY_*\`, \`FLY_*\`, \`CF_API_TOKEN\`, \`COOLIFY_*\` — sinais de uma sessão CI/local já configurada
|
|
27567
|
+
|
|
27568
|
+
Se UM sinal credível aparecer (não inferências fracas), trate como detecção e siga 1c-1f normalmente — mas use \`can_bring\` adaptado ao sinal. Se NENHUM sinal aparecer, **não diga nada ao usuário sobre essa busca** — não pergunte "achei algo?", não anuncie "verifiquei e não tem deploy". Pule direto para a seção 2 (analisar projeto). Silêncio é o resultado correto quando não há nada.
|
|
27569
|
+
|
|
27570
|
+
Não invente uma migração que não existe.
|
|
27571
|
+
|
|
27572
|
+
**1c. Se algo foi detectado:** apresente ao usuário CLARAMENTE o que foi encontrado e o que pode ser trazido. Use \`wizard-tools:prompt_user\`:
|
|
27573
|
+
|
|
27574
|
+
- **Uma plataforma detectada** → \`type: "confirm"\`:
|
|
27575
|
+
\`\`\`
|
|
27576
|
+
wizard-tools:prompt_user {
|
|
27577
|
+
"question": "Detectei que esse projeto já está deployado na <Plataforma> (<arquivos>). Posso importar para a Veloz: <can_bring resumido>. Não vou tocar em nada na <Plataforma> — só leitura. Quer que eu importe?",
|
|
27578
|
+
"type": "confirm"
|
|
27579
|
+
}
|
|
27580
|
+
\`\`\`
|
|
27581
|
+
|
|
27582
|
+
- **Múltiplas plataformas detectadas** → \`type: "choice"\` com a lista + opção "nenhuma":
|
|
27583
|
+
\`\`\`
|
|
27584
|
+
wizard-tools:prompt_user {
|
|
27585
|
+
"question": "Encontrei deploys existentes em mais de uma plataforma. Qual deve ser a fonte de verdade para importar para a Veloz?",
|
|
27586
|
+
"type": "choice",
|
|
27587
|
+
"options": ["Vercel (frontend Next.js + 12 envs)", "Railway (backend Node + 8 envs)", "Nenhuma — começar do zero"]
|
|
27588
|
+
}
|
|
27589
|
+
\`\`\`
|
|
27590
|
+
|
|
27591
|
+
Regra: a pergunta deve mencionar o conteúdo real (não "Vercel detectado" mas "Vercel — Next.js frontend, 12 env vars, build \`next build\`"). Use o campo \`can_bring\` da resposta da ferramenta, NÃO um texto genérico.
|
|
27592
|
+
|
|
27593
|
+
**1d. Se o usuário aprovar a migração:** prossiga para 1e. Se recusar OU não responder (timeout): mencione no diagrama "Migração declinada" e pule para a seção 2.
|
|
27594
|
+
|
|
27595
|
+
**1e. Extrair (read-only — NUNCA mute a outra plataforma):**
|
|
27596
|
+
|
|
27597
|
+
Use o campo \`extract_commands\` da resposta da ferramenta. Para cada plataforma aprovada:
|
|
27598
|
+
|
|
27599
|
+
a) **Config (build/start/regions/framework):**
|
|
27600
|
+
- Se o CLI está autenticado: rode o comando do CLI via Bash (já está no allowlist read-only — ex: \`vercel project ls\`, \`fly config show\`)
|
|
27601
|
+
- Se o CLI NÃO está instalado/autenticado: use Read tool no arquivo de config diretamente (vercel.json, fly.toml, render.yaml, etc.)
|
|
27602
|
+
- Extraia: build command, install command, start command, output directory, regions (informativo), env vars referenciadas (sem valores), cron jobs / workers (avise se existir — ainda não suportado)
|
|
27603
|
+
|
|
27604
|
+
b) **Env vars:**
|
|
27605
|
+
- Se o CLI está autenticado: rode o comando de extração de envs (ex: \`vercel env pull .env.vercel\`, \`railway variables\`, \`fly secrets list\`)
|
|
27606
|
+
- Se o CLI NÃO está autenticado: peça ao usuário via \`wizard-tools:prompt_user\` cada env sensível (use o \`hint\` para indicar onde achar no dashboard da plataforma)
|
|
27607
|
+
- **NUNCA** logue valores de env vars na saída do agente
|
|
27608
|
+
- Use \`wizard-tools:check_env_keys\` para confirmar quais keys já existem em \`.env\` local (não duplique)
|
|
27609
|
+
- Antes de configurar na Veloz, confirme com o usuário a lista (só os **nomes** das keys + contagem):
|
|
27610
|
+
\`\`\`
|
|
27611
|
+
"Importar 12 variáveis da Vercel para a Veloz? Keys: NEXT_PUBLIC_API_URL, DATABASE_URL, STRIPE_SECRET_KEY, ... (sem alterar valores na Vercel)"
|
|
27612
|
+
\`\`\`
|
|
27613
|
+
- Configure via \`veloz:env_set\` (uma chamada por env var). Variáveis de build (\`NEXT_PUBLIC_*\`) precisam estar no servidor ANTES do deploy.
|
|
27614
|
+
- Se gerou \`.env.vercel\` ou similar, **delete o arquivo após importar** (não deixar secrets no disco)
|
|
27615
|
+
|
|
27616
|
+
c) Aplique a config detectada (build/start/port/method) APÓS o primeiro deploy via \`veloz:config_set\`. Antes do primeiro deploy, guarde o que detectou e use no diagrama + na confirmação do passo 5.
|
|
27617
|
+
|
|
27618
|
+
**1f. Mencionar a migração no diagrama e na confirmação:**
|
|
27619
|
+
|
|
27620
|
+
No diagrama (seção 4), inclua uma linha como \`Migrado de: Vercel (12 envs, build "next build")\`. Na confirmação (seção 5), liste explicitamente o que foi importado para o usuário aprovar antes do deploy.
|
|
27621
|
+
|
|
27622
|
+
### 2. Analisar o projeto
|
|
27327
27623
|
- Leia package.json, configs do framework, .env.example
|
|
27328
|
-
- Identifique: build command, start command, porta, variáveis de ambiente necessárias
|
|
27329
|
-
- Procure Dockerfiles/containers já existentes (veja seção
|
|
27624
|
+
- Identifique: build command, start command, porta, variáveis de ambiente necessárias (use a config importada da seção 1 como ponto de partida quando houver)
|
|
27625
|
+
- Procure Dockerfiles/containers já existentes (veja seção 2b abaixo)
|
|
27330
27626
|
- CUIDADO: NÃO rode \`npm run dev\`, \`next dev\`, \`vite\`, \`nodemon\` ou qualquer servidor de desenvolvimento. Isso trava o processo.
|
|
27331
27627
|
|
|
27332
|
-
###
|
|
27628
|
+
### 2b. Escape hatch: Dockerfile
|
|
27333
27629
|
A Veloz tem dois métodos de build: **nixpacks** (auto-detect a partir do código) e **dockerfile** (usa um Dockerfile explícito). Prefira nixpacks sempre que possível — é mais simples e mais rápido. Mas existem dois cenários em que você DEVE usar Dockerfile:
|
|
27334
27630
|
|
|
27335
27631
|
**Cenário A — já existe um Dockerfile no projeto (USE, não recrie)**
|
|
@@ -27361,51 +27657,162 @@ Criar Dockerfile **novo** (só se não houver nenhum) SOMENTE se:
|
|
|
27361
27657
|
|
|
27362
27658
|
Nesse caso:
|
|
27363
27659
|
1. Escreva um Dockerfile minimalista e idiomático na raiz do app (arquivo novo — OK sem confirmação prévia)
|
|
27364
|
-
2. Mencione no resumo da confirmação do deploy (seção
|
|
27660
|
+
2. Mencione no resumo da confirmação do deploy (seção 5) que esse arquivo novo será criado
|
|
27365
27661
|
3. Configure o serviço com \`method: "dockerfile"\` via \`veloz:config_set\`
|
|
27366
27662
|
4. Re-deploye
|
|
27367
27663
|
|
|
27368
27664
|
Se precisar **editar** um Dockerfile existente (raro), siga a regra de permissão da seção "Edits em arquivos existentes do usuário" — peça confirmação antes.
|
|
27369
27665
|
|
|
27370
|
-
NUNCA crie um Dockerfile só porque o primeiro build falhou — diagnostique primeiro (seção
|
|
27371
|
-
|
|
27372
|
-
###
|
|
27373
|
-
|
|
27374
|
-
|
|
27375
|
-
|
|
27376
|
-
-
|
|
27377
|
-
|
|
27378
|
-
|
|
27379
|
-
|
|
27380
|
-
|
|
27381
|
-
|
|
27382
|
-
-
|
|
27383
|
-
-
|
|
27384
|
-
-
|
|
27385
|
-
|
|
27386
|
-
|
|
27387
|
-
|
|
27388
|
-
|
|
27666
|
+
NUNCA crie um Dockerfile só porque o primeiro build falhou — diagnostique primeiro (seção 8).
|
|
27667
|
+
|
|
27668
|
+
### 2c. Detectar banco de dados e serviços auxiliares (SEM prompt — auto-incluir no plano)
|
|
27669
|
+
Não pergunte "quer criar um banco?" — se o projeto claramente usa um banco, INCLUA na plan e mostre no diagrama/confirmação. O usuário aprova (ou rejeita) tudo junto na confirmação única da seção 5.
|
|
27670
|
+
|
|
27671
|
+
**Sinais de banco de dados** (basta UM para considerar que o projeto usa):
|
|
27672
|
+
- \`dependencies\`/\`devDependencies\` em \`package.json\`:
|
|
27673
|
+
- PostgreSQL: \`pg\`, \`postgres\`, \`@prisma/client\`, \`prisma\`, \`drizzle-orm\`, \`kysely\` com \`pg\`, \`@neondatabase/*\`, \`postgres.js\`
|
|
27674
|
+
- MySQL: \`mysql\`, \`mysql2\`, \`mariadb\`, \`@planetscale/database\`
|
|
27675
|
+
- Redis: \`redis\`, \`ioredis\`, \`@upstash/redis\`, \`bullmq\`
|
|
27676
|
+
- Mongo: \`mongoose\`, \`mongodb\` (Veloz ainda NÃO tem Mongo gerenciado — ver abaixo)
|
|
27677
|
+
- ORMs ambíguas: \`typeorm\`, \`sequelize\` — cheque o dialeto pelo config
|
|
27678
|
+
- \`schema.prisma\` / \`drizzle.config.*\` / \`knexfile.*\` / \`ormconfig.*\` — leia para saber o provider
|
|
27679
|
+
- \`.env\` / \`.env.example\` / \`.env.local\` com \`DATABASE_URL\`, \`POSTGRES_URL\`, \`MYSQL_URL\`, \`REDIS_URL\`, \`MONGO_URL\`
|
|
27680
|
+
- \`docker-compose.yml\` com serviço \`postgres\`/\`mysql\`/\`redis\`
|
|
27681
|
+
|
|
27682
|
+
**Regra de decisão:**
|
|
27683
|
+
- Sinal claro (Postgres/MySQL/Redis) → crie o banco via \`veloz:db_create\` ANTES do deploy, ou declare em \`veloz.json\` na seção \`databases\` ANTES do primeiro \`veloz:deploy\` (o deploy provisiona tudo junto). **Não prompte.** Coloque "Postgres 16" (ou equivalente) em \`services\` no diagrama e mencione "Criando Postgres gerenciado" na confirmação da seção 5.
|
|
27684
|
+
- Mongo detectado → a Veloz ainda não tem Mongo gerenciado. Avise 1 linha na confirmação: "Mongo detectado — sem Mongo gerenciado ainda; o app vai subir, você precisará apontar \`MONGO_URL\` para um Mongo externo". **Não prompte escolha.**
|
|
27685
|
+
- Nenhum sinal → não invente banco.
|
|
27686
|
+
|
|
27687
|
+
**Variáveis de conexão são auto-injetadas.** Quando o banco é gerenciado pela Veloz, \`DATABASE_URL\` (postgres/mysql) e \`REDIS_URL\` (redis) são preenchidas automaticamente pelo serviço de deploy — NÃO peça esses valores ao usuário em seção 3, NÃO rode \`veloz:env_set\` para eles. Se o nome da key no \`.env\` do usuário for diferente (ex: \`POSTGRES_URL\`, \`DB_URL\`), configure um alias via \`veloz:config_set\` nas env vars do serviço apontando para a var injetada.
|
|
27688
|
+
|
|
27689
|
+
**Dados locais — NUNCA migre automaticamente.** Se o \`.env\` local aponta para \`localhost\`/\`127.0.0.1\`/\`db:5432\` (docker-compose), ou existe \`docker-compose.yml\` com o serviço de banco, o projeto PROVAVELMENTE tem dados locais. O banco gerenciado da Veloz vai ser provisionado **VAZIO** — isso NÃO é bug, é o comportamento esperado. NÃO tente ler/exportar dados do banco local no meio do wizard. Apenas:
|
|
27690
|
+
1. Na confirmação da seção 5, inclua a linha: "⚠ Dados locais NÃO serão migrados — o banco da Veloz vai começar vazio. Instruções de migração serão mostradas no final."
|
|
27691
|
+
2. Depois do health check, emita o bloco \`[DATA_MIGRATION]\` (ver seção 7c).
|
|
27692
|
+
|
|
27693
|
+
### 2d. PgBouncer (connection pooler) — quando habilitar (SEM prompt)
|
|
27694
|
+
PgBouncer é o pooler gerenciado da Veloz para Postgres. A Veloz expõe \`<KEY>_POOLER_URL\` (porta 6432) além do \`<KEY>_DATABASE_URL\` direto (5432). Habilitar o pooler previne exaustão de conexões em cargas com muitos processos curtos — obrigatório para serverless-style apps, serviços auto-escaláveis e workers paralelos. **Decida pelo uso, não pergunte.**
|
|
27695
|
+
|
|
27696
|
+
**Habilite \`pooler.enabled: true\` (em \`databases.<key>\` no \`veloz.json\` ANTES do primeiro deploy, ou via \`veloz:config_set\` depois) quando detectar QUALQUER um destes sinais:**
|
|
27697
|
+
- Framework com rotas serverless / route handlers por request curto: Next.js App Router com \`route.ts\`/\`api/\`, Remix, SvelteKit, Astro com endpoints server, Nuxt server routes, Hono/Elysia em serverless-style
|
|
27698
|
+
- ORM que abre conexão por request: Prisma com \`new PrismaClient()\` sem pool custom, Drizzle em edge, \`pg\` sem \`Pool\`
|
|
27699
|
+
- Worker / job runner que escala horizontal: BullMQ (\`bullmq\` em deps), Inngest, Trigger.dev, cron com concorrência
|
|
27700
|
+
- \`size\` do serviço ≥ \`turbo\` E tipo web (provavelmente vai receber tráfego real)
|
|
27701
|
+
- Env com \`DATABASE_POOL_URL\`/\`POSTGRES_POOLING_URL\`/\`PRISMA_DATABASE_URL\` (sinal claro que o app espera usar pooler)
|
|
27702
|
+
|
|
27703
|
+
**Escolha do \`poolMode\`:**
|
|
27704
|
+
- \`transaction\` (default) — funciona para 95% dos apps web (Prisma, Drizzle, request-scoped queries). Use este.
|
|
27705
|
+
- \`session\` — só se o app precisa de \`SET LOCAL\`, \`LISTEN/NOTIFY\`, prepared statements nomeados persistentes, ou transações que cruzam múltiplas conexões. Detecte por \`pg.Client\` com \`client.query('LISTEN ...')\` ou \`SET search_path\` no código.
|
|
27706
|
+
- \`statement\` — raríssimo; só se o usuário pedir explicitamente.
|
|
27707
|
+
|
|
27708
|
+
**Quando um ORM exige pooler, aponte a env correta:** Prisma em serverless precisa do \`POOLER_URL\` no \`DATABASE_URL\` E do URL direto no \`DIRECT_URL\` para migrações (veja 2e). Configure via \`veloz:config_set\` env vars do serviço:
|
|
27709
|
+
\`\`\`
|
|
27710
|
+
DATABASE_URL = \${<KEY>_POOLER_URL}
|
|
27711
|
+
DIRECT_URL = \${<KEY>_DATABASE_URL}
|
|
27712
|
+
\`\`\`
|
|
27713
|
+
Para Drizzle/Kysely/pg puro, basta \`DATABASE_URL = \${<KEY>_POOLER_URL}\`.
|
|
27714
|
+
|
|
27715
|
+
**Não habilite pooler quando:** engine ≠ postgresql (MySQL/Redis não têm), app é batch job único sem concorrência, ou o usuário pediu explicitamente conexão direta.
|
|
27716
|
+
|
|
27717
|
+
Mencione "Pooler (PgBouncer)" em \`services\` ou \`notes\` do diagrama quando habilitado.
|
|
27718
|
+
|
|
27719
|
+
### 2e. Pre-start command (migrações de banco e setup antes do start)
|
|
27720
|
+
\`runtime.preStartCommand\` roda UMA vez por deploy, ANTES do comando \`start\`, no mesmo container com todas as env vars e conexões de banco disponíveis. É o lugar certo para **migrações**, seeds idempotentes, e qualquer setup que precise ser aplicado antes do app começar a servir. **Detecte pelo código do usuário e configure sem perguntar.**
|
|
27721
|
+
|
|
27722
|
+
**Sinais → comando:**
|
|
27723
|
+
- \`schema.prisma\` presente + \`prisma\`/\`@prisma/client\` em deps → \`preStartCommand: "npx prisma migrate deploy"\` (NUNCA \`migrate dev\` em produção; NUNCA \`db push\` — corrompe histórico de migrações)
|
|
27724
|
+
- \`drizzle.config.*\` presente + \`drizzle-kit\` em deps → \`preStartCommand: "npx drizzle-kit migrate"\` (ou o script que o usuário já tem em \`package.json\`, ex: \`pnpm run db:migrate\`)
|
|
27725
|
+
- \`knexfile.*\` presente → \`preStartCommand: "npx knex migrate:latest"\`
|
|
27726
|
+
- \`ormconfig.*\` / TypeORM com \`migrations/\` → \`preStartCommand: "npx typeorm migration:run -d dist/data-source.js"\` (ajuste o path do data-source)
|
|
27727
|
+
- \`alembic.ini\` (Python) → \`preStartCommand: "alembic upgrade head"\`
|
|
27728
|
+
- Rails \`db/migrate/\` → \`preStartCommand: "bundle exec rails db:migrate"\`
|
|
27729
|
+
- Django \`manage.py\` + \`migrations/\` → \`preStartCommand: "python manage.py migrate --noinput"\`
|
|
27730
|
+
- Script próprio em \`package.json\` (\`migrate\`, \`db:migrate\`, \`db:deploy\`) → prefira o script do usuário: \`preStartCommand: "pnpm run db:migrate"\`
|
|
27731
|
+
|
|
27732
|
+
**Regras:**
|
|
27733
|
+
- Configure via \`veloz:config_set\` (ex: \`{ "runtime": { "preStartCommand": "npx prisma migrate deploy" } }\`) ou declare em \`veloz.json\`.
|
|
27734
|
+
- Use o package manager detectado no contexto (\`${ctx.packageManager}\`) — não troque pnpm por npm.
|
|
27735
|
+
- Use \`npx\`/\`pnpm exec\` apenas se o binário não estiver no PATH direto; prefira o script do \`package.json\` quando existir.
|
|
27736
|
+
- **Idempotência obrigatória.** Migrate deploy e alembic upgrade são idempotentes. Evite seeds não-idempotentes no preStart — rode seeds uma única vez via \`veloz db tunnel\` do lado do usuário e mencione isso em \`[DATA_MIGRATION]\`.
|
|
27737
|
+
- **Prisma + pooler:** o preStart precisa do URL DIRETO (não pooler). Se habilitou PgBouncer (2d), garanta que \`DIRECT_URL\` está setado para \`\${<KEY>_DATABASE_URL}\` e que o Prisma schema tem \`directUrl = env("DIRECT_URL")\`. Se o schema ainda não tem, peça permissão (seção "Edits em arquivos existentes") antes de editar \`schema.prisma\`.
|
|
27738
|
+
- **Falhas no preStart abortam o deploy** — o serviço NÃO sobe se o preStart retornar exit code ≠ 0. Trate falha de migração como categoria A (misconfig) se for env/DATABASE_URL errada, ou categoria B (código do usuário) se for erro de SQL/schema do usuário.
|
|
27739
|
+
- Mencione "preStart: \`<cmd>\`" em \`notes\` do diagrama e na confirmação da seção 5.
|
|
27740
|
+
|
|
27741
|
+
Se não detectar nenhum sinal de migração, NÃO invente \`preStartCommand\` — deixe vazio.
|
|
27742
|
+
|
|
27743
|
+
### 3. Configurar variáveis de ambiente
|
|
27744
|
+
|
|
27745
|
+
**Regra de ouro: só prompte o que você NÃO pode obter de outra fonte.** Para cada env var que o app precisa, siga esta ordem de resolução ANTES de considerar perguntar ao usuário:
|
|
27746
|
+
|
|
27747
|
+
1. **Já migrada na seção 1** (importada de outra plataforma via \`vercel env pull\`/\`railway variables\`/\`fly secrets list\`) → já está no servidor, NÃO prompte.
|
|
27748
|
+
2. **Presente em \`.env\` / \`.env.local\` local** → leia o arquivo diretamente (Read tool), rode \`veloz:env_set\` com o valor, NÃO prompte. O \`.env\` é fonte de verdade local do usuário — se o valor está lá, é para usar. Use \`wizard-tools:check_env_keys\` só para saber QUAIS keys existem; depois leia o arquivo via Read para pegar os valores e rodar \`veloz:env_set\`. (Nunca imprima os valores nos seus logs — só rode o env_set.)
|
|
27749
|
+
3. **Auto-injetada por serviço gerenciado da Veloz** (banco gerenciado detectado na seção 2c → \`DATABASE_URL\`, \`REDIS_URL\`, pooler URL) → NÃO rode \`veloz:env_set\` nem prompte. A injeção é automática no runtime do serviço.
|
|
27750
|
+
4. **Nenhuma das anteriores** (ex: \`STRIPE_SECRET_KEY\` que só existe no dashboard do Stripe, chave de API de terceiro que o usuário ainda não salvou localmente) → AÍ SIM chame \`wizard-tools:prompt_user\` com \`hint\` explicando onde obter. O usuário pode escolher "configurar depois" — continue sem, e inclua \`veloz env set <KEY>\` nas instruções finais.
|
|
27751
|
+
|
|
27752
|
+
Regras adicionais:
|
|
27753
|
+
- Variáveis de build (\`NEXT_PUBLIC_*\`, \`VITE_*\`, \`PUBLIC_*\`) precisam estar no servidor ANTES do deploy — resolva-as primeiro.
|
|
27754
|
+
- Para chaves com nome diferente entre \`.env\` local e o que o app espera (ex: \`.env\` tem \`POSTGRES_URL\` mas o banco Veloz injeta \`DATABASE_URL\`), use interpolação \`\${VAR}\` (ver 3a) em vez de duplicar o segredo — é o mecanismo correto para aliases.
|
|
27755
|
+
- Se a key existe em \`.env.example\` mas não em \`.env\` local → trate como caso 4 (prompte, porque o usuário não tem o valor salvo).
|
|
27756
|
+
|
|
27757
|
+
### 3a. Interpolação de variáveis no valor (\`\${OUTRA_VAR}\`)
|
|
27758
|
+
Valores passados para \`veloz:env_set\` podem referenciar OUTRAS env vars do MESMO serviço usando \`\${NOME_DA_VAR}\` (single-brace). A resolução acontece no deploy — a Veloz substitui a referência pelo valor real da variável referenciada antes do container subir.
|
|
27759
|
+
|
|
27760
|
+
**Escopo da interpolação:**
|
|
27761
|
+
- Resolve contra as outras vars definidas no mesmo serviço (via \`veloz:env_set\`)
|
|
27762
|
+
- Resolve contra as vars **auto-injetadas** pela plataforma:
|
|
27763
|
+
- Sistema: \`PORT\` (exceto workers), \`NODE_ENV\`.
|
|
27764
|
+
- Banco: \`<PREFIXO>_DATABASE_URL\`, \`<PREFIXO>_HOST\`, \`<PREFIXO>_PORT\`, \`<PREFIXO>_USERNAME\`, \`<PREFIXO>_PASSWORD\`, \`<PREFIXO>_DATABASE\`, \`<PREFIXO>_POOLER_URL\`/\`<PREFIXO>_POOLER_PORT\` (só com pooler ativo em Postgres), e os apelidos canônicos \`DATABASE_URL\`/\`REDIS_URL\` quando o projeto tem exatamente um banco daquele tipo. \`<PREFIXO>\` = nome do serviço de banco em maiúsculas (ex: banco \`postgres\` → \`POSTGRES_*\`; banco \`main-db\` → \`MAIN_DB_*\`).
|
|
27765
|
+
- **Metadados da plataforma (\`VELOZ_*\`)** — sempre disponíveis para referenciar em outras vars:
|
|
27766
|
+
- Sempre: \`VELOZ_SERVICE_ID\`, \`VELOZ_SERVICE_NAME\`, \`VELOZ_SERVICE_TYPE\`, \`VELOZ_PROJECT_ID\`, \`VELOZ_PROJECT_NAME\`, \`VELOZ_DEPLOYMENT_ID\`, \`VELOZ_DEPLOYED_AT\`.
|
|
27767
|
+
- Quando o build gerou imagem: \`VELOZ_IMAGE_TAG\`.
|
|
27768
|
+
- Quando o deploy veio do Git: \`VELOZ_GIT_COMMIT_SHA\`, \`VELOZ_GIT_COMMIT_SHORT\` (7 chars), \`VELOZ_GIT_COMMIT_MESSAGE\`, \`VELOZ_GIT_BRANCH\`.
|
|
27769
|
+
- Quando o serviço tem domínios: \`VELOZ_PRIMARY_DOMAIN\`, \`VELOZ_PUBLIC_URL\` (\`https://<primary>\`), \`VELOZ_DOMAINS\` (vírgula-separado).
|
|
27770
|
+
- Use para versão do app (\`APP_VERSION=\${VELOZ_GIT_COMMIT_SHORT}\`), release do Sentry (\`SENTRY_RELEASE=\${VELOZ_PROJECT_NAME}@\${VELOZ_GIT_COMMIT_SHORT}\`), URL canônica (\`NEXT_PUBLIC_SITE_URL=\${VELOZ_PUBLIC_URL}\`), allowlist de CORS (\`ALLOWED_ORIGINS=\${VELOZ_DOMAINS}\`) — evita hardcoding.
|
|
27771
|
+
- NÃO resolve entre serviços — pra isso use \`\${{nome-do-servico.propriedade}}\` (duplo-brace), que só funciona quando apontado para serviços gerenciados (banco).
|
|
27772
|
+
|
|
27773
|
+
**Use interpolação nos seguintes casos (não duplique valores):**
|
|
27774
|
+
- **Alias de variável:** o app lê \`POSTGRES_URL\` mas a plataforma injeta \`DATABASE_URL\` → \`veloz:env_set POSTGRES_URL='\${DATABASE_URL}'\`
|
|
27775
|
+
- **Compor URL com query params:** \`DATABASE_URL='\${POSTGRES_POOLER_URL}?sslmode=require&pgbouncer=true'\`
|
|
27776
|
+
- **Separar connection string em partes:** quando o app espera \`DB_HOST\`/\`DB_PORT\`/\`DB_USER\`/\`DB_PASS\`/\`DB_NAME\` individuais em vez de uma URL → \`DB_HOST='\${POSTGRES_HOST}'\`, \`DB_PORT='\${POSTGRES_PORT}'\`, etc.
|
|
27777
|
+
- **Reusar base URL entre vars:** \`API_BASE='https://\${APP_HOST}'\`, \`NEXT_PUBLIC_API='\${API_BASE}/v1'\`
|
|
27778
|
+
|
|
27779
|
+
**Regras:**
|
|
27780
|
+
- Ref não encontrada passa como literal (\`\${VAR}\`) e vira aviso no log do deploy — NÃO quebra o deploy, mas é sinal de que faltou definir algo.
|
|
27781
|
+
- Para preservar um \`\${...}\` literal sem interpolação, escape com \`$\${LITERAL}\` (dois dólares).
|
|
27782
|
+
- Cadeias são resolvidas (\`A=\${B}\`, \`B=\${C}\`, \`C=x\` → \`A=x\`), até profundidade 10. Ciclos são interrompidos e viram aviso.
|
|
27783
|
+
- \`\${VAR}\` NÃO conflita com \`\${{svc.prop}}\` — use duplo-brace só para referenciar outro serviço.
|
|
27784
|
+
|
|
27785
|
+
**Quando EVITAR interpolação:** valores estáticos (API keys, feature flags, URLs fixas de terceiros) — passe o valor direto. Interpolação é para derivar um valor a partir de outro que já existe.
|
|
27786
|
+
|
|
27787
|
+
### 4. Desenhar o diagrama (OBRIGATÓRIO — antes do deploy)
|
|
27788
|
+
ANTES de chamar \`veloz:deploy\`, você DEVE chamar \`wizard-tools:set_helper_content\` com a estrutura do deploy. Este passo NÃO é opcional — o diagrama aparece na TUI abaixo da lista de tarefas.
|
|
27789
|
+
|
|
27790
|
+
**Envie apenas a estrutura — NUNCA gere ASCII art.** A TUI desenha as caixas, setas e escolhe o layout (horizontal/vertical) com base na largura do terminal. Se você mandar box-drawing manual, a largura fica errada.
|
|
27791
|
+
|
|
27792
|
+
Campos:
|
|
27793
|
+
- \`title\` — cabeçalho curto (ex: "Deploy — Next.js")
|
|
27794
|
+
- \`pipeline\` — array de etiquetas em ordem, cada uma ≤ 28 caracteres. A TUI renderiza cada item como uma caixa ligada por setas. Use 3–5 nós — começa na fonte (ex: "GitHub" ou "Código local"), passa por build/deploy, termina na URL pública
|
|
27795
|
+
- \`services\` — array de serviços auxiliares (bancos, caches, workers). Etiquetas ≤ 28 caracteres (ex: "Postgres 16", "Redis")
|
|
27796
|
+
- \`notes\` — array de anotações curtas (ex: runtime/porta, migração de outra plataforma, tamanho do serviço, Dockerfile detectado)
|
|
27797
|
+
|
|
27798
|
+
Exemplo:
|
|
27389
27799
|
\`\`\`
|
|
27390
27800
|
wizard-tools:set_helper_content {
|
|
27391
27801
|
"title": "Deploy — Next.js",
|
|
27392
|
-
"
|
|
27393
|
-
|
|
27394
|
-
|
|
27395
|
-
"└──────────────────────────┬──────────────────────────┘",
|
|
27396
|
-
" │ veloz deploy",
|
|
27397
|
-
" ▼",
|
|
27398
|
-
" build (Nixpacks) ───▶ imagem ───▶ serviço (turbo)",
|
|
27399
|
-
" │",
|
|
27400
|
-
" ▼",
|
|
27401
|
-
" https://app.runveloz.com"
|
|
27402
|
-
]
|
|
27802
|
+
"pipeline": ["GitHub", "Build (Nixpacks)", "Serviço turbo", "app.runveloz.com"],
|
|
27803
|
+
"services": ["Postgres 16", "Redis"],
|
|
27804
|
+
"notes": ["Porta 3000 · Node 20", "Migrado de: Vercel (12 envs)"]
|
|
27403
27805
|
}
|
|
27404
27806
|
\`\`\`
|
|
27405
27807
|
|
|
27808
|
+
Regras:
|
|
27809
|
+
- Etiquetas curtas — qualquer texto > 28 caracteres é truncado
|
|
27810
|
+
- Não envie \`lines\` junto com \`pipeline\` — use apenas o formato estruturado
|
|
27811
|
+
- \`lines\` só serve como fallback se o deploy for exótico e não couber em pipeline/services/notes
|
|
27812
|
+
|
|
27406
27813
|
Só prossiga para a confirmação DEPOIS que o diagrama for enviado.
|
|
27407
27814
|
|
|
27408
|
-
###
|
|
27815
|
+
### 5. Confirmar com o usuário (OBRIGATÓRIO — antes do deploy)
|
|
27409
27816
|
ANTES de chamar \`veloz:deploy\`, você DEVE pedir confirmação explícita ao usuário usando \`wizard-tools:prompt_user\` com \`type: "confirm"\`. Este passo NÃO é opcional.
|
|
27410
27817
|
|
|
27411
27818
|
\`\`\`
|
|
@@ -27426,10 +27833,10 @@ Regras:
|
|
|
27426
27833
|
- Se o usuário responder "não" → NÃO faça deploy. Emita \`[SUMMARY] Deploy cancelado pelo usuário antes de começar.\` e encerre.
|
|
27427
27834
|
- Se o usuário não responder (timeout) → NÃO faça deploy. Emita \`[SUMMARY] Deploy cancelado por falta de resposta.\` e encerre.
|
|
27428
27835
|
|
|
27429
|
-
###
|
|
27836
|
+
### 6. Fazer deploy
|
|
27430
27837
|
Use SEMPRE a ferramenta MCP \`veloz:deploy\` para fazer deploy — inclusive no PRIMEIRO deploy (sem veloz.json ainda). NUNCA use Bash para rodar \`veloz deploy\`.
|
|
27431
27838
|
|
|
27432
|
-
**
|
|
27839
|
+
**6a. Dry-run primeiro (OBRIGATÓRIO)**
|
|
27433
27840
|
ANTES de fazer o deploy real, você DEVE rodar um dry-run para validar a configuração sem enviar nada:
|
|
27434
27841
|
|
|
27435
27842
|
\`\`\`
|
|
@@ -27452,11 +27859,11 @@ Porque o dry-run NÃO gera veloz.json e NÃO cria serviço no servidor, \`veloz:
|
|
|
27452
27859
|
|
|
27453
27860
|
- **Upload grande demais** → edite/crie \`.dockerignore\` e/ou \`.gitignore\` local para excluir \`node_modules\`, \`dist\`, \`.next\`, \`.turbo\`, \`coverage\`, etc. (pedir permissão via \`wizard-tools:prompt_user\` antes de editar se o arquivo já existir)
|
|
27454
27861
|
- **Framework detectado errado** → verifique se a raiz do app está correta (use \`--app\` para monorepos) e se \`package.json\` tem os sinais esperados (dependências, \`scripts.build\`/\`start\`). Ajuste scripts do package.json (com permissão)
|
|
27455
|
-
- **Build/start command errado ou ausente** → adicione/corrija os scripts em \`package.json\` (com permissão), OU gere um Dockerfile novo (seção
|
|
27862
|
+
- **Build/start command errado ou ausente** → adicione/corrija os scripts em \`package.json\` (com permissão), OU gere um Dockerfile novo (seção 2b cenário B)
|
|
27456
27863
|
- **Porta errada** → geralmente o framework define; se precisa customizar, edite config do framework (next.config, vite.config) OU use Dockerfile com \`EXPOSE\`
|
|
27457
27864
|
- **Dockerfile não está sendo usado quando deveria** → garanta que o path do Dockerfile está onde o detector procura (raiz do app)
|
|
27458
27865
|
|
|
27459
|
-
Repita \`veloz:deploy { "dryRun": true }\` depois de cada correção para confirmar. Só prossiga para
|
|
27866
|
+
Repita \`veloz:deploy { "dryRun": true }\` depois de cada correção para confirmar. Só prossiga para 6b quando o dry-run estiver limpo.
|
|
27460
27867
|
|
|
27461
27868
|
**Depois do primeiro deploy real** (\`veloz.json\` criado, serviço existe no servidor), \`veloz:config_set\` passa a funcionar e é a forma preferida de ajustar build/start/port/size/preStart sem re-editar arquivos locais.
|
|
27462
27869
|
|
|
@@ -27466,7 +27873,7 @@ veloz:deploy { "dryRun": true, "app": "apps/web" }
|
|
|
27466
27873
|
veloz:deploy { "dryRun": true, "service": "nome-servico" }
|
|
27467
27874
|
\`\`\`
|
|
27468
27875
|
|
|
27469
|
-
**
|
|
27876
|
+
**6b. Deploy real**
|
|
27470
27877
|
Depois que o dry-run passou limpo, faça o deploy real:
|
|
27471
27878
|
|
|
27472
27879
|
\`\`\`
|
|
@@ -27477,6 +27884,17 @@ Para monorepos ou deploy de serviço específico:
|
|
|
27477
27884
|
- \`veloz:deploy { "yes": true, "app": "apps/web" }\` — deploy de um app específico do monorepo
|
|
27478
27885
|
- \`veloz:deploy { "yes": true, "service": "nome-servico" }\` — re-deploy de serviço existente
|
|
27479
27886
|
|
|
27887
|
+
**Retry após falha — use \`service\` OU \`all\`**
|
|
27888
|
+
Na PRIMEIRA chamada, \`veloz:deploy\` pode rodar sem escopo (o wizard faz bootstrap do projeto e deploy do serviço recém-criado). Depois que o serviço já existe — mesmo que o deploy tenha falhado no build, no health check, ou o CLI só tenha conseguido fazer o bootstrap antes de errar — chamar \`veloz:deploy\` sem escopo retorna:
|
|
27889
|
+
|
|
27890
|
+
> Especifique o(s) serviço(s) para deploy com --service ou use --all.
|
|
27891
|
+
|
|
27892
|
+
Esse erro NÃO significa que o wizard quebrou — significa que o CLI precisa que você seja explícito porque \`veloz.json\` já existe. Resolução:
|
|
27893
|
+
- Se só existe um serviço → \`veloz:deploy { "yes": true, "all": true }\`
|
|
27894
|
+
- Se existem múltiplos → \`veloz:deploy { "yes": true, "service": "<key>" }\` (use a chave do serviço em \`veloz.json\` — ex: \`main\`, \`web\`, \`api\`; o nome aparece na mensagem de erro entre parênteses: \`main (corgea)\`)
|
|
27895
|
+
|
|
27896
|
+
Se encontrar esse erro após um deploy falhado, NÃO tente investigar o erro como se fosse novo — só repita a chamada com \`all: true\` ou \`service\`.
|
|
27897
|
+
|
|
27480
27898
|
O deploy via MCP, mesmo sem veloz.json, faz tudo de ponta-a-ponta:
|
|
27481
27899
|
1. Detecta o framework e o tipo de serviço (web, static, worker)
|
|
27482
27900
|
2. Cria o projeto e o serviço na plataforma (se ainda não existirem)
|
|
@@ -27486,20 +27904,20 @@ O deploy via MCP, mesmo sem veloz.json, faz tudo de ponta-a-ponta:
|
|
|
27486
27904
|
|
|
27487
27905
|
IMPORTANTE:
|
|
27488
27906
|
- SEMPRE rode \`dryRun: true\` ANTES do deploy real. NÃO pule o dry-run.
|
|
27489
|
-
- NUNCA passe \`dryRun: true\` no deploy de
|
|
27907
|
+
- NUNCA passe \`dryRun: true\` no deploy de 6b — isso simula e NÃO faz deploy de verdade. O dry-run é só para o passo 6a.
|
|
27490
27908
|
- SEMPRE use veloz:deploy (MCP), inclusive no primeiro deploy. NUNCA \`veloz deploy\` via Bash — o Bash pode travar esperando input interativo.
|
|
27491
27909
|
- Só caia para Bash (\`veloz deploy --yes\`) se o MCP retornar erro explícito de conexão/timeout — nunca por ausência de veloz.json.
|
|
27492
27910
|
- NUNCA crie ou edite o arquivo veloz.json manualmente. Ele é gerado automaticamente pelo CLI no primeiro deploy.
|
|
27493
27911
|
- Após o primeiro deploy, se precisar ajustar configurações, use \`veloz:config_set\`.
|
|
27494
27912
|
|
|
27495
|
-
###
|
|
27913
|
+
### 7. Monitorar e diagnosticar
|
|
27496
27914
|
Após o deploy, use as ferramentas MCP para verificar o resultado:
|
|
27497
27915
|
- veloz:builds_logs — logs completos do build (stdout + stderr)
|
|
27498
27916
|
- veloz:builds_show — status detalhado do build
|
|
27499
27917
|
- veloz:logs_search — logs de runtime do serviço (após o serviço iniciar)
|
|
27500
27918
|
- veloz:domains_list — ver URLs/domínios disponíveis
|
|
27501
27919
|
|
|
27502
|
-
###
|
|
27920
|
+
### 7b. Health check pós-deploy (OBRIGATÓRIO quando o deploy retorna LIVE)
|
|
27503
27921
|
Após um deploy bem-sucedido (\`status: "LIVE"\` no resultado de \`veloz:deploy\`), você DEVE executar um health check completo antes de declarar sucesso. Este passo NÃO é opcional — um deploy "LIVE" não garante que o app esteja respondendo corretamente (pode estar em crash loop, retornando 5xx, consumindo CPU/memória fora do normal, etc.).
|
|
27504
27922
|
|
|
27505
27923
|
**Passo 0 — Desenhar um plano de testes (OBRIGATÓRIO antes de testar)**
|
|
@@ -27530,7 +27948,7 @@ Critérios de sucesso:
|
|
|
27530
27948
|
- Métricas dentro dos limites
|
|
27531
27949
|
|
|
27532
27950
|
Critérios de falha → ação:
|
|
27533
|
-
- 5xx/timeout no HTTP → ler logs, classificar categoria A/B (seção
|
|
27951
|
+
- 5xx/timeout no HTTP → ler logs, classificar categoria A/B (seção 8)
|
|
27534
27952
|
- Erros repetidos nos logs → classificar e corrigir (máx 3 iterações)
|
|
27535
27953
|
- CPU sustentado alto sem tráfego → suspeitar crash loop, ler logs
|
|
27536
27954
|
\`\`\`
|
|
@@ -27584,14 +28002,39 @@ Se TODOS os 3 checks passaram (HTTP 2xx/3xx, sem erros nos logs, métricas saud
|
|
|
27584
28002
|
- Encerre com \`[SUMMARY] Deploy concluído e saudável.\`
|
|
27585
28003
|
|
|
27586
28004
|
Se qualquer check falhou:
|
|
27587
|
-
- Classifique como categoria A (misconfig) ou B (código do usuário) seguindo a seção
|
|
28005
|
+
- Classifique como categoria A (misconfig) ou B (código do usuário) seguindo a seção 8
|
|
27588
28006
|
- Categoria A típica: porta errada (curl falha com connection refused, mas serviço está LIVE), healthcheck path errado, env var de runtime faltando, migration pendente, connection string de banco errada → corrija via \`veloz:config_set\` / \`veloz:env_set\` e faça o health check de novo
|
|
27589
28007
|
- Categoria B: se logs mostram \`Error: <erro do código do usuário>\` repetidamente e nenhuma correção de config resolve, emita \`[DEPLOY_UNHEALTHY_USER_CODE]\` com o trecho de erro e instruções de correção
|
|
27590
28008
|
- Máximo 3 iterações de health check + fix. Depois disso, emita \`[DEPLOY_UNHEALTHY]\` com o que foi tentado e o estado atual.
|
|
27591
28009
|
|
|
27592
28010
|
**IMPORTANTE:** NÃO emita \`[SUMMARY] Deploy concluído\` sem antes rodar o health check. Um deploy LIVE mas com 5xx ou crash loop NÃO é um deploy concluído.
|
|
27593
28011
|
|
|
27594
|
-
###
|
|
28012
|
+
### 7c. Instruções de migração de dados (quando há dados locais)
|
|
28013
|
+
Se na seção 2c você detectou um banco local com dados potenciais (DATABASE_URL apontando para localhost/docker-compose), APÓS \`[HEALTH_OK]\` e ANTES do \`[SUMMARY]\` final, emita um bloco com comandos prontos para o usuário migrar os dados manualmente. O wizard NÃO executa esses comandos — é o usuário que decide quando rodar.
|
|
28014
|
+
|
|
28015
|
+
Formato (Postgres como exemplo; adapte a engine e o nome do banco):
|
|
28016
|
+
|
|
28017
|
+
\`\`\`
|
|
28018
|
+
[DATA_MIGRATION]
|
|
28019
|
+
O banco \`<nome>\` foi provisionado vazio. Para migrar dados do seu banco local, rode:
|
|
28020
|
+
|
|
28021
|
+
# 1. Pegue as credenciais do banco Veloz (imprime DATABASE_URL do servidor)
|
|
28022
|
+
veloz db credentials <nome>
|
|
28023
|
+
|
|
28024
|
+
# 2. Migre em uma linha (ajuste <LOCAL_URL> e cole a URL do passo 1 em <VELOZ_URL>)
|
|
28025
|
+
pg_dump --no-owner --no-acl "<LOCAL_URL>" | psql "<VELOZ_URL>"
|
|
28026
|
+
|
|
28027
|
+
# Alternativa: use um túnel se a rede bloquear conexão direta
|
|
28028
|
+
veloz db tunnel <nome> # abre conexão local na porta 5432
|
|
28029
|
+
# em outro terminal:
|
|
28030
|
+
pg_dump --no-owner --no-acl "<LOCAL_URL>" | psql "postgres://<user>:<pass>@localhost:5432/<db>"
|
|
28031
|
+
\`\`\`
|
|
28032
|
+
|
|
28033
|
+
Para MySQL, troque \`pg_dump\`/\`psql\` por \`mysqldump\`/\`mysql\`. Para Redis, use \`redis-cli --rdb\` + \`--pipe\`. Para múltiplos bancos detectados, emita um bloco por banco.
|
|
28034
|
+
|
|
28035
|
+
Mantenha este bloco **informativo** — não chame tool nenhuma aqui, não rode nada via Bash, só imprima o bloco.
|
|
28036
|
+
|
|
28037
|
+
### 8. Classificar falhas e decidir o próximo passo
|
|
27595
28038
|
Quando um deploy falhar, você DEVE ler os logs com \`veloz:builds_logs\` e classificar o erro em UMA das duas categorias abaixo antes de decidir o que fazer.
|
|
27596
28039
|
|
|
27597
28040
|
**A) Misconfiguração (responsabilidade do wizard — corrija você mesmo)**
|
|
@@ -27606,7 +28049,13 @@ Erros onde o código do usuário está correto, mas a Veloz não sabe como const
|
|
|
27606
28049
|
|
|
27607
28050
|
→ Ação: corrija via \`veloz:config_set\` (build, start, port, preStart, packageManager, etc.) e re-deploye. Máximo 3 tentativas. NÃO envolva o usuário — essa é sua responsabilidade como wizard.
|
|
27608
28051
|
|
|
27609
|
-
|
|
28052
|
+
**Exceção crítica — quando a misconfig só pode ser corrigida tocando código do usuário**: se a correção exigir Write/Edit em arquivo existente do projeto (ex: adicionar \`scripts.build\` em \`package.json\` ausente, corrigir \`next.config.js\` inválido, ajustar \`tsconfig.json\`, editar código-fonte em \`src/\`), VOCÊ NÃO PODE fazer a edição silenciosamente. Você tem EXATAMENTE duas opções:
|
|
28053
|
+
1. **Prompt** — chame \`wizard-tools:prompt_user\` com \`type: "confirm"\` explicando arquivo + motivo + o que vai mudar. Se o usuário aprovar, aplique o edit. Se recusar/não responder, vá para opção 2.
|
|
28054
|
+
2. **Fail over** — emita \`[BUILD_FAILED_USER_CODE]\` com motivo, trecho dos logs e instruções claras para o usuário aplicar a mudança manualmente e rodar \`veloz deploy\` de novo. PARE. Não tente mais nada.
|
|
28055
|
+
|
|
28056
|
+
Nunca edite arquivos do usuário sem autorização, mesmo que pareça uma correção "trivial" de misconfig.
|
|
28057
|
+
|
|
28058
|
+
Último recurso: se após 2 tentativas o nixpacks ainda não estiver funcionando (detecção falhando, runtime/libs específicas, framework exótico) e NÃO houver Dockerfile existente, gere um Dockerfile minimalista e mude \`build.method\` para \`"dockerfile"\` via \`veloz:config_set\` (veja seção 2b). Se JÁ existe um Dockerfile no repo e você estava ignorando — pare, use o existente.
|
|
27610
28059
|
|
|
27611
28060
|
**B) Falha de testes do usuário (responsabilidade do usuário — ÚNICO caso de handoff)**
|
|
27612
28061
|
Emita \`[BUILD_FAILED_USER_CODE]\` e pare **APENAS** quando o build falhar porque **testes do usuário estão falhando** (jest, vitest, pytest, go test, cargo test, rspec, phpunit, etc. executados como parte do build/CI do projeto).
|
|
@@ -27650,29 +28099,25 @@ wizard-tools:prompt_user {
|
|
|
27650
28099
|
Regras:
|
|
27651
28100
|
- Se o usuário responder "sim" → aplique a edição
|
|
27652
28101
|
- Se o usuário responder "não" → NÃO edite. Encontre uma alternativa (\`veloz:config_set\`, Dockerfile à parte, env var, etc.) ou emita \`[BUILD_FAILED_USER_CODE]\` com instruções para o usuário fazer a edição manualmente
|
|
27653
|
-
- Exceção: criar um arquivo **novo** que não existia antes (ex: novo \`Dockerfile\` na raiz quando não há nenhum) NÃO precisa de confirmação prévia — mas mencione no resumo da confirmação do deploy (seção
|
|
28102
|
+
- Exceção: criar um arquivo **novo** que não existia antes (ex: novo \`Dockerfile\` na raiz quando não há nenhum) NÃO precisa de confirmação prévia — mas mencione no resumo da confirmação do deploy (seção 5) que o arquivo será criado
|
|
27654
28103
|
- Exceção: arquivos gerenciados pelo wizard (\`.env\` via \`wizard-tools:set_env_values\`, configuração do serviço via \`veloz:config_set\`) NÃO precisam de confirmação por edit — já são escopo do wizard
|
|
27655
28104
|
|
|
27656
28105
|
Em caso de dúvida: peça permissão. É melhor uma pergunta extra do que editar algo que o usuário não queria tocar.
|
|
27657
28106
|
|
|
27658
28107
|
## Ferramentas Helper (wizard-tools MCP)
|
|
28108
|
+
- wizard-tools:detect_existing_deployments — escanear o projeto por configs de Vercel/Railway/Fly/Cloudflare/Netlify/Render/Heroku/DO/Amplify e checar CLI + auth, em uma chamada (chame PRIMEIRO)
|
|
27659
28109
|
- wizard-tools:check_env_keys — verificar quais env vars existem sem revelar valores
|
|
27660
28110
|
- wizard-tools:set_env_values — escrever env vars com segurança (auto-adiciona ao .gitignore)
|
|
27661
28111
|
- wizard-tools:set_helper_content — exibir diagramas e conteúdo visual no painel esquerdo
|
|
27662
28112
|
- wizard-tools:prompt_user — perguntar algo ao usuário interativamente
|
|
27663
28113
|
|
|
27664
28114
|
## Diagrama Visual (OBRIGATÓRIO antes do deploy)
|
|
27665
|
-
SEMPRE chame \`wizard-tools:set_helper_content\` ANTES de \`veloz:deploy\`.
|
|
28115
|
+
SEMPRE chame \`wizard-tools:set_helper_content\` ANTES de \`veloz:deploy\`. Veja a seção "4. Desenhar o diagrama" para o schema completo (pipeline/services/notes).
|
|
27666
28116
|
|
|
27667
|
-
|
|
27668
|
-
-
|
|
27669
|
-
-
|
|
27670
|
-
-
|
|
27671
|
-
- Serviços auxiliares (banco de dados, cache, etc.) se houver
|
|
27672
|
-
|
|
27673
|
-
Restrições visuais:
|
|
27674
|
-
- Largura alvo: ~${getDiagramWidth()} colunas (aproveite o espaço — o painel direito é largo quando o terminal é largo)
|
|
27675
|
-
- Use box-drawing (┌─┐│└┘├┤┬┴┼) e setas (→ ↓ ▼)
|
|
28117
|
+
Lembretes rápidos:
|
|
28118
|
+
- Envie apenas o schema — a TUI desenha as caixas e escolhe horizontal/vertical pela largura
|
|
28119
|
+
- NUNCA gere ASCII art manualmente
|
|
28120
|
+
- Etiquetas ≤ 28 caracteres; pipeline com 3–5 nós
|
|
27676
28121
|
- Uma tarefa do TodoWrite DEVE representar esse passo (ver checklist abaixo)
|
|
27677
28122
|
|
|
27678
28123
|
## Reportar Progresso
|
|
@@ -27682,16 +28127,23 @@ Use a ferramenta TodoWrite para manter uma lista de tarefas atualizada:
|
|
|
27682
28127
|
- Mude para "completed" quando terminar
|
|
27683
28128
|
|
|
27684
28129
|
Tarefas típicas (inclua TODAS, na ordem):
|
|
27685
|
-
1. "
|
|
27686
|
-
2. "
|
|
27687
|
-
3. "
|
|
27688
|
-
4. "
|
|
27689
|
-
5. "
|
|
27690
|
-
6. "
|
|
27691
|
-
7. "
|
|
27692
|
-
8. "
|
|
27693
|
-
9. "
|
|
27694
|
-
10. "
|
|
28130
|
+
1. "Criar veloz-deploy-plano.md" — escrever esqueleto inicial na raiz (passo 0a). Mantenha esse arquivo atualizado ao longo de TODAS as etapas seguintes.
|
|
28131
|
+
2. "Aprender CLI" — ler veloz --llms-full
|
|
28132
|
+
3. "Detectar deploy existente" — chamar wizard-tools:detect_existing_deployments PRIMEIRO; se algo for encontrado, perguntar ao usuário e importar config + envs (seção 1)
|
|
28133
|
+
4. "Analisar projeto" — ler package.json, configs, estrutura, procurar Dockerfile existente
|
|
28134
|
+
5. "Detectar banco de dados" — auto-incluir no plano se o projeto usar Postgres/MySQL/Redis (seção 2c, SEM prompt)
|
|
28135
|
+
6. "Decidir pooler (PgBouncer)" — habilitar \`pooler.enabled\` quando o uso pede (seção 2d, SEM prompt); configurar \`DATABASE_URL\`/\`DIRECT_URL\` se necessário
|
|
28136
|
+
7. "Configurar pre-start" — detectar migrações (Prisma/Drizzle/Alembic/etc.) e setar \`runtime.preStartCommand\` (seção 2e, SEM prompt)
|
|
28137
|
+
8. "Configurar variáveis de ambiente" — env vars no servidor (a partir do que foi importado, se houver)
|
|
28138
|
+
9. "Desenhar diagrama" — chamar wizard-tools:set_helper_content incluindo bancos, pooler e preStart detectados (OBRIGATÓRIO antes do deploy)
|
|
28139
|
+
10. "Confirmar com o usuário" — wizard-tools:prompt_user com type="confirm", incluindo bancos a criar + pooler + preStart + aviso de dados locais se aplicável (OBRIGATÓRIO antes do deploy)
|
|
28140
|
+
11. "Validar com dry-run" — veloz:deploy { dryRun: true } para checar detecção/tamanho/config (OBRIGATÓRIO antes do deploy real)
|
|
28141
|
+
12. "Fazer deploy" — enviar código e build (veloz:deploy { yes: true }, SEM dryRun)
|
|
28142
|
+
13. "Desenhar plano de testes" — emitir [TESTING_PLAN] com rotas e thresholds específicos (OBRIGATÓRIO antes do health check)
|
|
28143
|
+
14. "Executar health check" — curl + veloz:logs_search + veloz:metrics_show seguindo o plano
|
|
28144
|
+
15. "Instruções de migração de dados" — emitir [DATA_MIGRATION] APENAS se havia dados locais detectados na seção 2c
|
|
28145
|
+
16. "Atualizar veloz-deploy-plano.md final" — garantir que o arquivo reflete o estado final (URL, arquivos modificados, env vars, próximos passos)
|
|
28146
|
+
17. "Finalizar" — emitir [HEALTH_OK] + [SUMMARY] se tudo saudável
|
|
27695
28147
|
|
|
27696
28148
|
## Regras CRÍTICAS — Uso de Ferramentas
|
|
27697
28149
|
- SEMPRE use ferramentas MCP (veloz:*) para TODAS as operações da plataforma. NUNCA use Bash para rodar comandos \`veloz\` exceto:
|
|
@@ -27703,6 +28155,7 @@ Tarefas típicas (inclua TODAS, na ordem):
|
|
|
27703
28155
|
|
|
27704
28156
|
## Regras Gerais
|
|
27705
28157
|
- Comunique-se em português (pt-BR)
|
|
28158
|
+
- **Minimize prompts.** Decisões óbvias a partir do código (framework, package manager, banco detectado, build/start commands) NÃO viram pergunta — entram direto no diagrama (seção 4) e na confirmação única do deploy (seção 5). Só chame \`wizard-tools:prompt_user\` quando: (a) um secret é necessário E não está no \`.env\` local E não veio da migração (seção 1) E não é auto-injetado por serviço gerenciado (ver seção 3, regra de ouro), (b) houver ambiguidade real (ex: múltiplos apps em monorepo sem pista clara, múltiplas plataformas detectadas na seção 1), (c) for a confirmação única pré-deploy da seção 5, ou (d) edit em arquivo existente (seção "Edits em arquivos existentes"). NÃO prompte "quer criar banco?", "posso usar pnpm?", "posso usar porta 3000?", nem peça um secret que já está no \`.env\` — isso já foi inferido/extraído, coloque no plano e siga.
|
|
27706
28159
|
- Para QUALQUER edit em arquivo existente do projeto (package.json, tsconfig.json, configs de framework, Dockerfile existente, código-fonte, scripts customizados), SEMPRE peça permissão primeiro via \`wizard-tools:prompt_user\` type="confirm" (ver seção "Edits em arquivos existentes do usuário"). Exceções: \`.env\` via \`wizard-tools:set_env_values\` e config de serviço via \`veloz:config_set\` — esses são escopo do wizard.
|
|
27707
28160
|
- NUNCA crie veloz.json manualmente — o CLI gera automaticamente
|
|
27708
28161
|
- SEMPRE peça confirmação explícita via wizard-tools:prompt_user (type="confirm") antes de chamar veloz:deploy
|
|
@@ -27740,7 +28193,7 @@ function getCommandments() {
|
|
|
27740
28193
|
|
|
27741
28194
|
7. **Edits mínimos**: Prefira edits focados e mínimos. Não refatore código que não precisa ser mudado. Não adicione comentários desnecessários.
|
|
27742
28195
|
|
|
27743
|
-
8. **Peça permissão antes de tocar
|
|
28196
|
+
8. **Peça permissão antes de tocar código do usuário — OU faça fail over**: Para QUALQUER Write/Edit em arquivo que já existe no projeto (package.json, tsconfig.json, next.config.*, vite.config.*, Dockerfile existente, código em src/, scripts customizados, migrations, etc.), você DEVE chamar \`wizard-tools:prompt_user\` com \`type: "confirm"\` explicando arquivo + motivo + o que vai mudar ANTES de qualquer Write/Edit. Só aplique se o usuário responder "sim". Se responder "não" ou não responder: **NÃO edite silenciosamente**, **NÃO tente outra abordagem que também toque o código** — faça fail over emitindo \`[BUILD_FAILED_USER_CODE]\` com instruções claras para o usuário aplicar a mudança manualmente e re-rodar \`veloz deploy\`. Isso vale inclusive para correções de categoria A (misconfiguração) quando a causa exigir mexer em arquivos do usuário (ex: script de build ausente em \`package.json\`, import quebrado, config de framework inválida) — nesse caso a correção via \`veloz:config_set\` não resolve e você DEVE prompt+edit ou fail over. Exceções (não precisam de confirmação): \`.env\` via \`wizard-tools:set_env_values\`, configuração de serviço via \`veloz:config_set\`, criar arquivo **novo** que não existia antes (ex: Dockerfile novo, \`.dockerignore\` novo). NUNCA edite veloz.json diretamente. **Regra de ouro: se precisa modificar código do usuário para o deploy funcionar → prompt OU fail over, nunca silencioso.**
|
|
27744
28197
|
|
|
27745
28198
|
9. **Classificar falhas de deploy**: Se um deploy falhar, leia os logs (veloz:builds_logs) e classifique o erro. (A) Se for **misconfiguração** da Veloz (build/start/port/preStart errado, package manager incorreto, monorepo path, nixpacks): corrija via \`veloz:config_set\` e retente (máx. 3 tentativas). (B) Se for **erro no código do usuário** (TypeScript, sintaxe, import quebrado, módulo faltando no package.json, teste quebrando no build, runtime crash do app): NÃO tente corrigir o código-fonte, NÃO retente. Emita o signal \`[BUILD_FAILED_USER_CODE]\` com motivo + trecho dos logs + passos para corrigir, e ENCERRE. Em caso de dúvida, teste \`pnpm build\` (ou equivalente) localmente: se falha localmente → é código do usuário.
|
|
27746
28199
|
|
|
@@ -27750,7 +28203,7 @@ function getCommandments() {
|
|
|
27750
28203
|
|
|
27751
28204
|
12. **Veloz MCP OBRIGATÓRIO**: Use SEMPRE as ferramentas MCP (veloz:*) para TODAS as operações da plataforma. NUNCA rode \`veloz ...\` via Bash — use o MCP equivalente (veloz:deploy, veloz:config_set, veloz:env_set, veloz:builds_logs, etc.). As únicas exceções são: \`veloz --llms-full\` (aprender a CLI) e fallback se o MCP falhar com erro de conexão.
|
|
27752
28205
|
|
|
27753
|
-
13. **Diagrama ANTES do deploy**: Use wizard-tools:set_helper_content
|
|
28206
|
+
13. **Diagrama ANTES do deploy**: Use wizard-tools:set_helper_content ANTES de chamar veloz:deploy. Envie a estrutura (campos \`title\`, \`pipeline\`, \`services\`, \`notes\`) — a TUI desenha as caixas e setas. NUNCA gere ASCII art manualmente. O diagrama deve cobrir: framework detectado, pipeline de build (3–5 nós), serviços auxiliares e anotações (porta, migração, etc.). Atualize o helper conforme avança (ex: diagnóstico de erro, resultado do build).
|
|
27754
28207
|
|
|
27755
28208
|
14. **Perguntar ao usuário**: Use wizard-tools:prompt_user para pedir informações ao usuário. Para variáveis de ambiente, forneça hint de como obtê-las (ex: "Acesse dashboard.stripe.com > API Keys"). O usuário pode optar por configurar depois — nesse caso, continue o deploy sem a env var e informe o comando \`veloz env set\` no final.
|
|
27756
28209
|
|
|
@@ -27759,6 +28212,8 @@ function getCommandments() {
|
|
|
27759
28212
|
15. **NUNCA rode dev server**: Comandos como \`next dev\`, \`vite\`, \`nodemon\`, \`npm run dev\`, \`pnpm dev\` travam o processo do agente. Use apenas comandos de build (\`npm run build\`, \`pnpm build\`) para testes locais.
|
|
27760
28213
|
|
|
27761
28214
|
16. **NUNCA crie veloz.json manualmente**: O arquivo veloz.json é gerado automaticamente pelo CLI no primeiro \`veloz deploy\`. Criar ou editar manualmente produz IDs inválidos. Para ajustar configurações após o deploy, use \`veloz:config_set\`.
|
|
28215
|
+
|
|
28216
|
+
19. **Manter veloz-deploy-plano.md**: Logo no início (antes mesmo do Passo 0), crie o arquivo \`veloz-deploy-plano.md\` na raiz do projeto (\`cwd\`) com o plano inicial do deploy. Mantenha-o atualizado ao longo de TODA a sessão — a cada passo relevante (detecção de plataforma existente, análise do projeto, banco detectado, env vars configuradas, diagrama desenhado, confirmação do usuário, dry-run, deploy real, health check, correções de misconfig, migração de dados) reabra o arquivo e adicione/atualize seções. Este arquivo é o "diário de bordo" do wizard — é o que o usuário vai ler depois para saber exatamente o que foi feito. Ao final, o arquivo deve refletir o estado final: URL do deploy, arquivos criados/modificados, serviços/bancos criados, env vars configuradas (só os **nomes**, NUNCA valores), próximos passos e comandos úteis. Use Write para criar/sobrescrever e Edit para updates pontuais. Nunca exponha secrets neste arquivo. Estrutura sugerida: \`# Plano de Deploy — Veloz\` · \`## Contexto\` · \`## Detecção\` · \`## Configuração\` · \`## Deploy\` · \`## Health Check\` · \`## Próximos Passos\`.
|
|
27762
28217
|
`.trim();
|
|
27763
28218
|
}
|
|
27764
28219
|
|
|
@@ -27995,6 +28450,7 @@ function getWizardToolsScript(workingDirectory, logDir) {
|
|
|
27995
28450
|
'use strict';
|
|
27996
28451
|
const fs = require('fs');
|
|
27997
28452
|
const path = require('path');
|
|
28453
|
+
const { execFileSync } = require('child_process');
|
|
27998
28454
|
|
|
27999
28455
|
const CWD = '${workingDirectory.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}';
|
|
28000
28456
|
const LOG_DIR = '${logDir ? logDir.replace(/\\/g, "\\\\").replace(/'/g, "\\'") : ""}';
|
|
@@ -28054,19 +28510,40 @@ const TOOLS = {
|
|
|
28054
28510
|
required: ['question'],
|
|
28055
28511
|
},
|
|
28056
28512
|
},
|
|
28513
|
+
detect_existing_deployments: {
|
|
28514
|
+
description: 'Detectar deploys existentes em outras plataformas (Vercel, Railway, Cloudflare, Netlify, Fly, Render, Heroku, DigitalOcean, AWS Amplify). Faz duas coisas em uma só chamada: (1) escaneia o projeto por arquivos de config dessas plataformas, (2) checa se o CLI da plataforma está instalado e autenticado localmente. NÃO use Glob nem Bash para isso — toda a detecção acontece dentro da ferramenta. Retorna, por plataforma detectada: arquivos encontrados, presença/auth do CLI, comando de extração de envs e o que pode ser migrado (build/start command, env vars, frontend code, regions). Chame esta ferramenta UMA VEZ no INÍCIO da sessão, antes de analisar package.json ou desenhar diagramas.',
|
|
28515
|
+
inputSchema: {
|
|
28516
|
+
type: 'object',
|
|
28517
|
+
properties: {},
|
|
28518
|
+
},
|
|
28519
|
+
},
|
|
28057
28520
|
set_helper_content: {
|
|
28058
|
-
description: 'Renderizar
|
|
28521
|
+
description: 'Renderizar o diagrama da arquitetura abaixo da lista de tarefas na TUI do wizard. OBRIGATÓRIO: chame esta ferramenta antes de veloz:deploy. Prefira o formato estruturado (pipeline, services, notes) — a TUI desenha caixas e setas no tamanho certo automaticamente. NÃO gere ASCII art manualmente — largura e alinhamento são calculados pela TUI. Use "lines" apenas como fallback para conteúdo livre. Cada chamada SUBSTITUI o conteúdo anterior — atualize ao final com o estado deployado real.',
|
|
28059
28522
|
inputSchema: {
|
|
28060
28523
|
type: 'object',
|
|
28061
28524
|
properties: {
|
|
28062
|
-
title: { type: 'string', description: 'Título do
|
|
28525
|
+
title: { type: 'string', description: 'Título do diagrama (ex: "Deploy — Next.js")' },
|
|
28526
|
+
pipeline: {
|
|
28527
|
+
type: 'array',
|
|
28528
|
+
items: { type: 'string' },
|
|
28529
|
+
description: 'Nós do pipeline em ordem, cada um uma etiqueta curta (ex: ["GitHub", "Build (Next.js)", "Deploy", "app.runveloz.com"]). A TUI desenha caixas com setas entre elas.',
|
|
28530
|
+
},
|
|
28531
|
+
services: {
|
|
28532
|
+
type: 'array',
|
|
28533
|
+
items: { type: 'string' },
|
|
28534
|
+
description: 'Serviços auxiliares anexados ao deploy, cada um uma etiqueta curta (ex: ["Postgres 16", "Redis"]). Renderizados como caixas laterais abaixo do pipeline.',
|
|
28535
|
+
},
|
|
28536
|
+
notes: {
|
|
28537
|
+
type: 'array',
|
|
28538
|
+
items: { type: 'string' },
|
|
28539
|
+
description: 'Anotações extras (ex: ["Migrado de: Vercel (12 envs)", "Tamanho: turbo"]). Renderizadas como texto dim abaixo do diagrama.',
|
|
28540
|
+
},
|
|
28063
28541
|
lines: {
|
|
28064
28542
|
type: 'array',
|
|
28065
28543
|
items: { type: 'string' },
|
|
28066
|
-
description: '
|
|
28544
|
+
description: 'FALLBACK — só use quando pipeline/services/notes não cabem no formato estruturado. Linhas brutas renderizadas em sequência.',
|
|
28067
28545
|
},
|
|
28068
28546
|
},
|
|
28069
|
-
required: ['lines'],
|
|
28070
28547
|
},
|
|
28071
28548
|
},
|
|
28072
28549
|
};
|
|
@@ -28157,6 +28634,259 @@ function handleSetEnvValues(input) {
|
|
|
28157
28634
|
return { content: [{ type: 'text', text: 'Variáveis escritas em ' + file + ': ' + keysSet.join(', ') }] };
|
|
28158
28635
|
}
|
|
28159
28636
|
|
|
28637
|
+
const PLATFORM_PROBES = [
|
|
28638
|
+
{
|
|
28639
|
+
platform: 'vercel',
|
|
28640
|
+
label: 'Vercel',
|
|
28641
|
+
files: ['vercel.json', '.vercel/project.json'],
|
|
28642
|
+
cli: 'vercel',
|
|
28643
|
+
altCli: 'vc',
|
|
28644
|
+
authArgs: ['whoami'],
|
|
28645
|
+
extract: {
|
|
28646
|
+
envs: 'vercel env pull .env.vercel',
|
|
28647
|
+
config: 'vercel project ls',
|
|
28648
|
+
},
|
|
28649
|
+
canBring: 'Frontend (Next.js, SvelteKit, etc.) e env vars (build + runtime). Domínios precisam ser re-verificados na Veloz.',
|
|
28650
|
+
},
|
|
28651
|
+
{
|
|
28652
|
+
platform: 'railway',
|
|
28653
|
+
label: 'Railway',
|
|
28654
|
+
files: ['railway.json', 'railway.toml'],
|
|
28655
|
+
cli: 'railway',
|
|
28656
|
+
authArgs: ['whoami'],
|
|
28657
|
+
extract: {
|
|
28658
|
+
envs: 'railway variables',
|
|
28659
|
+
config: 'railway status',
|
|
28660
|
+
},
|
|
28661
|
+
canBring: 'App full-stack inteiro (build/start/env vars). Volumes precisam ser recriados.',
|
|
28662
|
+
},
|
|
28663
|
+
{
|
|
28664
|
+
platform: 'cloudflare',
|
|
28665
|
+
label: 'Cloudflare Workers/Pages',
|
|
28666
|
+
files: ['wrangler.toml', 'wrangler.jsonc', 'wrangler.json'],
|
|
28667
|
+
cli: 'wrangler',
|
|
28668
|
+
authArgs: ['whoami'],
|
|
28669
|
+
extract: {
|
|
28670
|
+
envs: 'wrangler secret list',
|
|
28671
|
+
config: 'cat wrangler.toml',
|
|
28672
|
+
},
|
|
28673
|
+
canBring: 'Workers/Pages. Atenção: runtime serverless (V8 isolates) ≠ Node — algumas APIs podem não funcionar na Veloz sem ajustes.',
|
|
28674
|
+
},
|
|
28675
|
+
{
|
|
28676
|
+
platform: 'netlify',
|
|
28677
|
+
label: 'Netlify',
|
|
28678
|
+
files: ['netlify.toml', '.netlify/state.json'],
|
|
28679
|
+
cli: 'netlify',
|
|
28680
|
+
altCli: 'ntl',
|
|
28681
|
+
authArgs: ['status'],
|
|
28682
|
+
extract: {
|
|
28683
|
+
envs: 'netlify env:list',
|
|
28684
|
+
config: 'netlify status',
|
|
28685
|
+
},
|
|
28686
|
+
canBring: 'Sites estáticos e SSR (Next.js, Astro). Netlify Functions precisam ser portadas para serviço HTTP normal.',
|
|
28687
|
+
},
|
|
28688
|
+
{
|
|
28689
|
+
platform: 'fly',
|
|
28690
|
+
label: 'Fly.io',
|
|
28691
|
+
files: ['fly.toml'],
|
|
28692
|
+
cli: 'fly',
|
|
28693
|
+
altCli: 'flyctl',
|
|
28694
|
+
authArgs: ['auth', 'whoami'],
|
|
28695
|
+
extract: {
|
|
28696
|
+
envs: 'fly secrets list',
|
|
28697
|
+
config: 'fly config show',
|
|
28698
|
+
},
|
|
28699
|
+
canBring: 'App full-stack (Dockerfile + secrets + regions). Volumes Fly precisam ser recriados como volumes Veloz.',
|
|
28700
|
+
},
|
|
28701
|
+
{
|
|
28702
|
+
platform: 'render',
|
|
28703
|
+
label: 'Render',
|
|
28704
|
+
files: ['render.yaml'],
|
|
28705
|
+
cli: null,
|
|
28706
|
+
authArgs: null,
|
|
28707
|
+
extract: {
|
|
28708
|
+
envs: '(parse render.yaml — Render CLI não confiável)',
|
|
28709
|
+
config: 'cat render.yaml',
|
|
28710
|
+
},
|
|
28711
|
+
canBring: 'Web services, workers e cron jobs (parse YAML). Env vars sensíveis ficam no dashboard — pedir ao usuário.',
|
|
28712
|
+
},
|
|
28713
|
+
{
|
|
28714
|
+
platform: 'heroku',
|
|
28715
|
+
label: 'Heroku',
|
|
28716
|
+
files: ['app.json', 'Procfile'],
|
|
28717
|
+
cli: 'heroku',
|
|
28718
|
+
authArgs: ['auth:whoami'],
|
|
28719
|
+
extract: {
|
|
28720
|
+
envs: 'heroku config',
|
|
28721
|
+
config: 'cat Procfile',
|
|
28722
|
+
},
|
|
28723
|
+
canBring: 'App full-stack (web/worker dynos via Procfile + env vars). Add-ons (Postgres, Redis) precisam ser recriados.',
|
|
28724
|
+
},
|
|
28725
|
+
{
|
|
28726
|
+
platform: 'digitalocean',
|
|
28727
|
+
label: 'DigitalOcean App Platform',
|
|
28728
|
+
files: ['.do/app.yaml'],
|
|
28729
|
+
cli: 'doctl',
|
|
28730
|
+
authArgs: ['account', 'get'],
|
|
28731
|
+
extract: {
|
|
28732
|
+
envs: 'doctl apps spec get <app-id>',
|
|
28733
|
+
config: 'cat .do/app.yaml',
|
|
28734
|
+
},
|
|
28735
|
+
canBring: 'App spec (services + workers + jobs). Env vars sensíveis precisam de prompt ao usuário.',
|
|
28736
|
+
},
|
|
28737
|
+
{
|
|
28738
|
+
platform: 'amplify',
|
|
28739
|
+
label: 'AWS Amplify',
|
|
28740
|
+
files: ['amplify.yml'],
|
|
28741
|
+
cli: 'amplify',
|
|
28742
|
+
authArgs: ['status'],
|
|
28743
|
+
extract: {
|
|
28744
|
+
envs: '(Amplify CLI não expõe envs facilmente — pedir ao usuário)',
|
|
28745
|
+
config: 'cat amplify.yml',
|
|
28746
|
+
},
|
|
28747
|
+
canBring: 'Build spec do frontend. Backend Amplify (Cognito, AppSync) NÃO é portável — substituir por equivalentes na Veloz.',
|
|
28748
|
+
},
|
|
28749
|
+
{
|
|
28750
|
+
platform: 'coolify',
|
|
28751
|
+
label: 'Coolify',
|
|
28752
|
+
files: ['coolify.json', '.coolify/config.json', 'docker-compose.coolify.yml'],
|
|
28753
|
+
cli: 'coolify',
|
|
28754
|
+
authArgs: ['version'],
|
|
28755
|
+
extract: {
|
|
28756
|
+
envs: '(Coolify guarda envs/secrets no servidor — consultar via API ou pedir ao usuário)',
|
|
28757
|
+
config: 'cat coolify.json',
|
|
28758
|
+
},
|
|
28759
|
+
canBring: 'Apps self-hosted (Dockerfile/Nixpacks). Env vars/secrets ficam no servidor Coolify — pedir ao usuário ou usar a API do Coolify para extrair.',
|
|
28760
|
+
},
|
|
28761
|
+
];
|
|
28762
|
+
|
|
28763
|
+
const WORKFLOW_PLATFORM_PATTERNS = [
|
|
28764
|
+
{ platform: 'vercel', regex: /amondnet\\/vercel-action|@vercel\\/cli|\\bvercel\\s+(deploy|build|pull|env)/i },
|
|
28765
|
+
{ platform: 'cloudflare', regex: /cloudflare\\/wrangler-action|\\bwrangler\\s+(deploy|publish|pages)/i },
|
|
28766
|
+
{ platform: 'fly', regex: /superfly\\/flyctl-actions|\\bfly(ctl)?\\s+(deploy|launch|apps)/i },
|
|
28767
|
+
{ platform: 'railway', regex: /bervProject\\/railway-deploy|\\brailway\\s+(up|deploy)/i },
|
|
28768
|
+
{ platform: 'netlify', regex: /nwtgck\\/actions-netlify|netlify\\/actions|\\bnetlify\\s+deploy/i },
|
|
28769
|
+
{ platform: 'heroku', regex: /akhileshns\\/heroku-deploy|git\\s+push\\s+heroku|\\bheroku\\s+(create|deploy|releases)/i },
|
|
28770
|
+
{ platform: 'render', regex: /JorgeLNJunior\\/render-deploy|render-deploy@/i },
|
|
28771
|
+
{ platform: 'digitalocean', regex: /digitalocean\\/app_action|\\bdoctl\\s+apps/i },
|
|
28772
|
+
{ platform: 'amplify', regex: /aws-actions\\/amplify|\\bamplify\\s+(push|publish)/i },
|
|
28773
|
+
{ platform: 'coolify', regex: /coollabsio\\/coolify|coolify(.*)webhook|api\\/v1\\/deploy.*coolify/i },
|
|
28774
|
+
];
|
|
28775
|
+
|
|
28776
|
+
function scanGithubWorkflowsForPlatforms() {
|
|
28777
|
+
const dir = path.join(CWD, '.github', 'workflows');
|
|
28778
|
+
const matches = {};
|
|
28779
|
+
if (!fs.existsSync(dir)) return matches;
|
|
28780
|
+
let entries = [];
|
|
28781
|
+
try {
|
|
28782
|
+
entries = fs.readdirSync(dir).filter((f) => /\\.(ya?ml)$/i.test(f));
|
|
28783
|
+
} catch {
|
|
28784
|
+
return matches;
|
|
28785
|
+
}
|
|
28786
|
+
for (const file of entries) {
|
|
28787
|
+
let content;
|
|
28788
|
+
try {
|
|
28789
|
+
content = fs.readFileSync(path.join(dir, file), 'utf-8');
|
|
28790
|
+
} catch {
|
|
28791
|
+
continue;
|
|
28792
|
+
}
|
|
28793
|
+
for (const { platform, regex } of WORKFLOW_PLATFORM_PATTERNS) {
|
|
28794
|
+
if (regex.test(content)) {
|
|
28795
|
+
if (!matches[platform]) matches[platform] = [];
|
|
28796
|
+
const rel = '.github/workflows/' + file;
|
|
28797
|
+
if (!matches[platform].includes(rel)) matches[platform].push(rel);
|
|
28798
|
+
}
|
|
28799
|
+
}
|
|
28800
|
+
}
|
|
28801
|
+
return matches;
|
|
28802
|
+
}
|
|
28803
|
+
|
|
28804
|
+
function whichSync(bin) {
|
|
28805
|
+
try {
|
|
28806
|
+
const out = execFileSync('which', [bin], { encoding: 'utf-8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
28807
|
+
const trimmed = out.trim();
|
|
28808
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
28809
|
+
} catch {
|
|
28810
|
+
return null;
|
|
28811
|
+
}
|
|
28812
|
+
}
|
|
28813
|
+
|
|
28814
|
+
function probeCliAuth(bin, args) {
|
|
28815
|
+
if (!bin || !args) return { authenticated: false, identity: null, error: null };
|
|
28816
|
+
try {
|
|
28817
|
+
const out = execFileSync(bin, args, { encoding: 'utf-8', timeout: 6000, stdio: ['ignore', 'pipe', 'pipe'], cwd: CWD });
|
|
28818
|
+
const identity = out.trim().split('\\n').find((l) => l.trim().length > 0) || null;
|
|
28819
|
+
return { authenticated: true, identity, error: null };
|
|
28820
|
+
} catch (err) {
|
|
28821
|
+
const msg = String((err && err.stderr) || (err && err.message) || '').slice(0, 200);
|
|
28822
|
+
return { authenticated: false, identity: null, error: msg };
|
|
28823
|
+
}
|
|
28824
|
+
}
|
|
28825
|
+
|
|
28826
|
+
function handleDetectExistingDeployments() {
|
|
28827
|
+
const workflowMatches = scanGithubWorkflowsForPlatforms();
|
|
28828
|
+
const detected = [];
|
|
28829
|
+
const skipped = [];
|
|
28830
|
+
|
|
28831
|
+
for (const probe of PLATFORM_PROBES) {
|
|
28832
|
+
const filesFound = probe.files.filter((f) => fs.existsSync(path.join(CWD, f)));
|
|
28833
|
+
const workflowFiles = workflowMatches[probe.platform] || [];
|
|
28834
|
+
if (filesFound.length === 0 && workflowFiles.length === 0) {
|
|
28835
|
+
skipped.push(probe.platform);
|
|
28836
|
+
continue;
|
|
28837
|
+
}
|
|
28838
|
+
|
|
28839
|
+
const primaryCliPath = probe.cli ? whichSync(probe.cli) : null;
|
|
28840
|
+
const altCliPath = probe.altCli && !primaryCliPath ? whichSync(probe.altCli) : null;
|
|
28841
|
+
const cliBin = primaryCliPath ? probe.cli : altCliPath ? probe.altCli : null;
|
|
28842
|
+
const cliPath = primaryCliPath || altCliPath;
|
|
28843
|
+
const auth = cliBin ? probeCliAuth(cliBin, probe.authArgs) : { authenticated: false, identity: null, error: 'CLI não instalado' };
|
|
28844
|
+
|
|
28845
|
+
const sources = [];
|
|
28846
|
+
if (filesFound.length > 0) sources.push('config_files');
|
|
28847
|
+
if (workflowFiles.length > 0) sources.push('github_workflows');
|
|
28848
|
+
|
|
28849
|
+
detected.push({
|
|
28850
|
+
platform: probe.platform,
|
|
28851
|
+
label: probe.label,
|
|
28852
|
+
files_found: filesFound,
|
|
28853
|
+
github_workflows: workflowFiles,
|
|
28854
|
+
detection_source: sources.join('+'),
|
|
28855
|
+
cli: {
|
|
28856
|
+
bin: cliBin,
|
|
28857
|
+
installed: Boolean(cliPath),
|
|
28858
|
+
path: cliPath,
|
|
28859
|
+
authenticated: auth.authenticated,
|
|
28860
|
+
identity: auth.identity,
|
|
28861
|
+
auth_error: auth.error,
|
|
28862
|
+
},
|
|
28863
|
+
extract_commands: probe.extract,
|
|
28864
|
+
can_bring: probe.canBring,
|
|
28865
|
+
next_step: !cliPath
|
|
28866
|
+
? (filesFound.length > 0
|
|
28867
|
+
? 'CLI não instalado — leia o arquivo de config diretamente (Read tool) para extrair build/start/regions; pergunte ao usuário pelas env vars sensíveis.'
|
|
28868
|
+
: 'CLI não instalado e sem arquivo de config — leia o(s) workflow(s) GitHub Actions para extrair env vars referenciadas via secrets.* (sintaxe dollar-brace dupla) e o build/deploy command; peça os valores ao usuário.')
|
|
28869
|
+
: !auth.authenticated
|
|
28870
|
+
? 'CLI instalado mas não autenticado — leia o arquivo de config e/ou workflow (Read tool) para extrair config; peça env vars sensíveis ao usuário. NÃO tente fazer login na plataforma concorrente.'
|
|
28871
|
+
: 'CLI pronto — após confirmação do usuário, rode "' + probe.extract.envs + '" para extrair env vars; use Read tool no arquivo de config (e workflow, se houver) para build/start/regions.',
|
|
28872
|
+
});
|
|
28873
|
+
}
|
|
28874
|
+
|
|
28875
|
+
return {
|
|
28876
|
+
content: [{
|
|
28877
|
+
type: 'text',
|
|
28878
|
+
text: JSON.stringify({
|
|
28879
|
+
any_detected: detected.length > 0,
|
|
28880
|
+
detected,
|
|
28881
|
+
skipped_no_signal: skipped,
|
|
28882
|
+
instructions: detected.length === 0
|
|
28883
|
+
? 'Nenhum deploy existente detectado (nem em arquivos de config nem em workflows do GitHub Actions). Prossiga para a análise normal do projeto.'
|
|
28884
|
+
: 'Antes de qualquer outra coisa: (1) mostre ao usuário a lista de plataformas detectadas com o que cada uma traz (campo can_bring) e a origem da detecção (config_files vs github_workflows), (2) chame wizard-tools:prompt_user type="confirm" perguntando se quer migrar (se >1 plataforma, use type="choice" com a lista para escolher a fonte de verdade), (3) só depois rode os comandos em extract_commands ou leia os workflows. NUNCA mute a plataforma de origem — só leitura.',
|
|
28885
|
+
}, null, 2),
|
|
28886
|
+
}],
|
|
28887
|
+
};
|
|
28888
|
+
}
|
|
28889
|
+
|
|
28160
28890
|
const PROMPT_REQUEST_FILE = path.join(CWD, '.veloz-wizard-prompt.json');
|
|
28161
28891
|
const PROMPT_RESPONSE_FILE = path.join(CWD, '.veloz-wizard-response.json');
|
|
28162
28892
|
|
|
@@ -28264,7 +28994,18 @@ async function handleJsonRpc(msg) {
|
|
|
28264
28994
|
try {
|
|
28265
28995
|
if (name === 'check_env_keys') result = handleCheckEnvKeys(args);
|
|
28266
28996
|
else if (name === 'set_env_values') result = handleSetEnvValues(args);
|
|
28267
|
-
else if (name === '
|
|
28997
|
+
else if (name === 'detect_existing_deployments') result = handleDetectExistingDeployments();
|
|
28998
|
+
else if (name === 'set_helper_content') {
|
|
28999
|
+
const pipelineLen = Array.isArray(args.pipeline) ? args.pipeline.length : 0;
|
|
29000
|
+
const servicesLen = Array.isArray(args.services) ? args.services.length : 0;
|
|
29001
|
+
const notesLen = Array.isArray(args.notes) ? args.notes.length : 0;
|
|
29002
|
+
const linesLen = Array.isArray(args.lines) ? args.lines.length : 0;
|
|
29003
|
+
const hasStructured = pipelineLen + servicesLen + notesLen > 0;
|
|
29004
|
+
const summary = hasStructured
|
|
29005
|
+
? 'Diagrama atualizado: ' + pipelineLen + ' nós, ' + servicesLen + ' serviços, ' + notesLen + ' notas.'
|
|
29006
|
+
: 'Diagrama atualizado com ' + linesLen + ' linhas (fallback).';
|
|
29007
|
+
result = { content: [{ type: 'text', text: summary }] };
|
|
29008
|
+
}
|
|
28268
29009
|
else if (name === 'prompt_user') result = await handlePromptUser(args);
|
|
28269
29010
|
else result = { content: [{ type: 'text', text: 'Ferramenta desconhecida: ' + name }], isError: true };
|
|
28270
29011
|
logEvent('tool_call_done', { name, isError: Boolean(result && result.isError) });
|
|
@@ -28315,10 +29056,13 @@ process.stdin.on('data', (chunk) => {
|
|
|
28315
29056
|
* output from the platform until the deployment reaches a terminal state.
|
|
28316
29057
|
*/
|
|
28317
29058
|
async function streamBuildLogs(config) {
|
|
28318
|
-
const { store, deploymentId } = config;
|
|
29059
|
+
const { store, deploymentId, organizationId } = config;
|
|
28319
29060
|
const logger = getSessionLogger();
|
|
28320
|
-
logger.logBuildStream("start", {
|
|
28321
|
-
|
|
29061
|
+
logger.logBuildStream("start", {
|
|
29062
|
+
deploymentId,
|
|
29063
|
+
organizationId
|
|
29064
|
+
});
|
|
29065
|
+
const client = createWizardClient(store, organizationId);
|
|
28322
29066
|
if (!client) {
|
|
28323
29067
|
logger.logBuildStream("no_client", { reason: "missing apiKey" });
|
|
28324
29068
|
store.pushBuildLog("[stream] aguardando autenticação para conectar ao build...");
|
|
@@ -28336,15 +29080,18 @@ async function streamBuildLogs(config) {
|
|
|
28336
29080
|
store.setBuildStreamActive(false);
|
|
28337
29081
|
}
|
|
28338
29082
|
}
|
|
28339
|
-
function createWizardClient(store) {
|
|
28340
|
-
const
|
|
29083
|
+
function createWizardClient(store, organizationId) {
|
|
29084
|
+
const authConfig = loadConfig();
|
|
29085
|
+
const apiKey = store.session.apiKey || authConfig.apiKey;
|
|
28341
29086
|
if (!apiKey) return null;
|
|
28342
|
-
|
|
29087
|
+
const apiUrl = authConfig.apiUrl;
|
|
29088
|
+
const orgId = organizationId ?? store.session.orgId;
|
|
29089
|
+
return createClient(apiUrl, () => {
|
|
28343
29090
|
const headers = {
|
|
28344
29091
|
Authorization: `Bearer ${apiKey}`,
|
|
28345
29092
|
"User-Agent": "veloz-cli/wizard"
|
|
28346
29093
|
};
|
|
28347
|
-
if (
|
|
29094
|
+
if (orgId) headers["X-Organization-Id"] = orgId;
|
|
28348
29095
|
return headers;
|
|
28349
29096
|
});
|
|
28350
29097
|
}
|
|
@@ -28536,6 +29283,43 @@ const PLATFORM_READ_SUBCOMMANDS = {
|
|
|
28536
29283
|
"tail",
|
|
28537
29284
|
"deployments"
|
|
28538
29285
|
],
|
|
29286
|
+
netlify: [
|
|
29287
|
+
"env:list",
|
|
29288
|
+
"env:get",
|
|
29289
|
+
"status",
|
|
29290
|
+
"sites:list",
|
|
29291
|
+
"sites:info",
|
|
29292
|
+
"api",
|
|
29293
|
+
"open"
|
|
29294
|
+
],
|
|
29295
|
+
ntl: [
|
|
29296
|
+
"env:list",
|
|
29297
|
+
"env:get",
|
|
29298
|
+
"status",
|
|
29299
|
+
"sites:list",
|
|
29300
|
+
"sites:info",
|
|
29301
|
+
"api",
|
|
29302
|
+
"open"
|
|
29303
|
+
],
|
|
29304
|
+
heroku: [
|
|
29305
|
+
"config",
|
|
29306
|
+
"apps",
|
|
29307
|
+
"apps:info",
|
|
29308
|
+
"ps",
|
|
29309
|
+
"domains",
|
|
29310
|
+
"addons",
|
|
29311
|
+
"auth:whoami",
|
|
29312
|
+
"logs"
|
|
29313
|
+
],
|
|
29314
|
+
doctl: [
|
|
29315
|
+
"apps",
|
|
29316
|
+
"account",
|
|
29317
|
+
"auth",
|
|
29318
|
+
"compute",
|
|
29319
|
+
"databases",
|
|
29320
|
+
"registry",
|
|
29321
|
+
"serverless"
|
|
29322
|
+
],
|
|
28539
29323
|
aws: [
|
|
28540
29324
|
"s3",
|
|
28541
29325
|
"ec2",
|
|
@@ -28550,6 +29334,16 @@ const PLATFORM_READ_SUBCOMMANDS = {
|
|
|
28550
29334
|
"cloudformation",
|
|
28551
29335
|
"ssm",
|
|
28552
29336
|
"secretsmanager"
|
|
29337
|
+
],
|
|
29338
|
+
coolify: [
|
|
29339
|
+
"version",
|
|
29340
|
+
"list",
|
|
29341
|
+
"ls",
|
|
29342
|
+
"status",
|
|
29343
|
+
"whoami",
|
|
29344
|
+
"info",
|
|
29345
|
+
"show",
|
|
29346
|
+
"get"
|
|
28553
29347
|
]
|
|
28554
29348
|
};
|
|
28555
29349
|
const AWS_READ_VERBS_REGEX = /^(describe-|list-|get-|show-|head-|read-|search-|lookup-|scan$|query$|ls$|whoami$|info$|status$)/;
|
|
@@ -28744,6 +29538,10 @@ async function runAgentOnce(config, isRetry, previousError) {
|
|
|
28744
29538
|
const commandments = getCommandments();
|
|
28745
29539
|
const wizardTools = createWizardToolsServer({ workingDirectory: cwd });
|
|
28746
29540
|
logger.log("agent", "wizard-tools MCP server config created");
|
|
29541
|
+
const velozMcpEnv = {};
|
|
29542
|
+
for (const [k, v] of Object.entries(process.env)) if (typeof v === "string") velozMcpEnv[k] = v;
|
|
29543
|
+
if (store.session.apiKey) velozMcpEnv.VELOZ_API_KEY = store.session.apiKey;
|
|
29544
|
+
if (store.session.orgId) velozMcpEnv.VELOZ_ORG_ID = store.session.orgId;
|
|
28747
29545
|
const mcpServers = {
|
|
28748
29546
|
veloz: {
|
|
28749
29547
|
command: "npx",
|
|
@@ -28751,7 +29549,8 @@ async function runAgentOnce(config, isRetry, previousError) {
|
|
|
28751
29549
|
"-y",
|
|
28752
29550
|
"onveloz",
|
|
28753
29551
|
"--mcp"
|
|
28754
|
-
]
|
|
29552
|
+
],
|
|
29553
|
+
env: velozMcpEnv
|
|
28755
29554
|
},
|
|
28756
29555
|
"wizard-tools": wizardTools
|
|
28757
29556
|
};
|
|
@@ -29038,6 +29837,17 @@ function normalizeMcpToolName(name) {
|
|
|
29038
29837
|
if (sep < 0) return name;
|
|
29039
29838
|
return `${rest.slice(0, sep)}:${rest.slice(sep + 2)}`;
|
|
29040
29839
|
}
|
|
29840
|
+
/** Coerce unknown tool input into a clean string[] — non-strings and empties dropped. */
|
|
29841
|
+
function toStringArray(value) {
|
|
29842
|
+
if (!Array.isArray(value)) return [];
|
|
29843
|
+
const out = [];
|
|
29844
|
+
for (const item of value) {
|
|
29845
|
+
if (typeof item !== "string") continue;
|
|
29846
|
+
const trimmed = item.trim();
|
|
29847
|
+
if (trimmed) out.push(trimmed);
|
|
29848
|
+
}
|
|
29849
|
+
return out;
|
|
29850
|
+
}
|
|
29041
29851
|
/** Human-readable labels for common tools. */
|
|
29042
29852
|
const TOOL_LABELS = {
|
|
29043
29853
|
Read: "Lendo arquivo",
|
|
@@ -29143,8 +29953,8 @@ function handleMessage(message, store, collectedText, onAskUser) {
|
|
|
29143
29953
|
const bashCommand = rawName === "Bash" && typeof block.input?.command === "string" ? block.input.command : null;
|
|
29144
29954
|
const isDeployViaBash = bashCommand ? /\bveloz\s+deploy\b/.test(bashCommand) : false;
|
|
29145
29955
|
const isDeployViaMcp = normalizedName === "veloz:deploy";
|
|
29146
|
-
const isDryRun = isDeployViaBash ? /--dry-run\b/.test(bashCommand ?? "") : isDeployViaMcp && block.input?.dryRun === true;
|
|
29147
|
-
if ((isDeployViaBash || isDeployViaMcp) && !isDryRun) {
|
|
29956
|
+
const isDryRun$1 = isDeployViaBash ? /--dry-run\b/.test(bashCommand ?? "") : isDeployViaMcp && block.input?.dryRun === true;
|
|
29957
|
+
if ((isDeployViaBash || isDeployViaMcp) && !isDryRun$1) {
|
|
29148
29958
|
if (typeof block.id === "string") deployToolUseIds.add(block.id);
|
|
29149
29959
|
if (store.session.agentPhase !== "deploying") {
|
|
29150
29960
|
store.setAgentPhase("deploying");
|
|
@@ -29167,15 +29977,27 @@ function handleMessage(message, store, collectedText, onAskUser) {
|
|
|
29167
29977
|
const questionText = extractAskUserQuestionText(block.input);
|
|
29168
29978
|
if (questionText) onAskUser(toolUseId, questionText);
|
|
29169
29979
|
}
|
|
29170
|
-
if (normalizedName === "wizard-tools:set_helper_content" && block.input
|
|
29171
|
-
const
|
|
29172
|
-
const
|
|
29173
|
-
const
|
|
29980
|
+
if (normalizedName === "wizard-tools:set_helper_content" && block.input) {
|
|
29981
|
+
const input = block.input;
|
|
29982
|
+
const title = typeof input.title === "string" ? input.title : null;
|
|
29983
|
+
const pipeline = toStringArray(input.pipeline);
|
|
29984
|
+
const services = toStringArray(input.services);
|
|
29985
|
+
const notes = toStringArray(input.notes);
|
|
29986
|
+
const lines = toStringArray(input.lines);
|
|
29987
|
+
if (pipeline.length > 0 || services.length > 0 || notes.length > 0) store.setAgentHelperDiagram({
|
|
29174
29988
|
title,
|
|
29175
|
-
|
|
29176
|
-
|
|
29177
|
-
|
|
29178
|
-
|
|
29989
|
+
pipeline,
|
|
29990
|
+
services,
|
|
29991
|
+
notes
|
|
29992
|
+
});
|
|
29993
|
+
else if (lines.length > 0) {
|
|
29994
|
+
const helperLines = title ? [
|
|
29995
|
+
title,
|
|
29996
|
+
"",
|
|
29997
|
+
...lines
|
|
29998
|
+
] : lines;
|
|
29999
|
+
store.setAgentHelper(helperLines);
|
|
30000
|
+
}
|
|
29179
30001
|
}
|
|
29180
30002
|
if (rawName === "TodoWrite" && block.input?.todos && Array.isArray(block.input.todos)) {
|
|
29181
30003
|
const tasks = block.input.todos.map((todo, i) => ({
|
|
@@ -29209,12 +30031,14 @@ function handleMessage(message, store, collectedText, onAskUser) {
|
|
|
29209
30031
|
if (Array.isArray(parsed)) for (const entry of parsed) {
|
|
29210
30032
|
if (typeof entry?.message === "string") lines.push(entry.message);
|
|
29211
30033
|
const entryDeploymentId = entry?.data?.deploymentId;
|
|
30034
|
+
const entryOrgId = entry?.data?.organizationId ?? void 0;
|
|
29212
30035
|
if (entryDeploymentId && !streamedDeploymentIds.has(entryDeploymentId)) {
|
|
29213
30036
|
streamedDeploymentIds.add(entryDeploymentId);
|
|
29214
30037
|
store.setBuildDeploymentId(entryDeploymentId);
|
|
29215
30038
|
streamBuildLogs({
|
|
29216
30039
|
store,
|
|
29217
|
-
deploymentId: entryDeploymentId
|
|
30040
|
+
deploymentId: entryDeploymentId,
|
|
30041
|
+
organizationId: entryOrgId ?? void 0
|
|
29218
30042
|
}).catch((err) => {
|
|
29219
30043
|
const msg = err instanceof Error ? err.message : String(err);
|
|
29220
30044
|
store.pushBuildLog(`[stream] ${msg}`);
|
|
@@ -29277,6 +30101,8 @@ let Screen = /* @__PURE__ */ function(Screen$1) {
|
|
|
29277
30101
|
Screen$1["Auth"] = "auth";
|
|
29278
30102
|
Screen$1["Notes"] = "notes";
|
|
29279
30103
|
Screen$1["Run"] = "run";
|
|
30104
|
+
Screen$1["GithubSetup"] = "github-setup";
|
|
30105
|
+
Screen$1["AiSetup"] = "ai-setup";
|
|
29280
30106
|
Screen$1["Outro"] = "outro";
|
|
29281
30107
|
return Screen$1;
|
|
29282
30108
|
}({});
|
|
@@ -29306,6 +30132,16 @@ const FLOW = [
|
|
|
29306
30132
|
show: () => true,
|
|
29307
30133
|
isComplete: (s) => s.agentComplete
|
|
29308
30134
|
},
|
|
30135
|
+
{
|
|
30136
|
+
screen: Screen.GithubSetup,
|
|
30137
|
+
show: (s) => s.agentComplete && !s.agentError && s.githubSetupPhase !== "idle",
|
|
30138
|
+
isComplete: (s) => s.githubSetupPhase === "done" || s.githubSetupPhase === "skipped" || s.githubSetupPhase === "error"
|
|
30139
|
+
},
|
|
30140
|
+
{
|
|
30141
|
+
screen: Screen.AiSetup,
|
|
30142
|
+
show: (s) => s.agentComplete && !s.agentError && s.aiSetupPhase !== "idle",
|
|
30143
|
+
isComplete: (s) => s.aiSetupPhase === "done" || s.aiSetupPhase === "skipped" || s.aiSetupPhase === "error"
|
|
30144
|
+
},
|
|
29309
30145
|
{
|
|
29310
30146
|
screen: Screen.Outro,
|
|
29311
30147
|
show: () => true,
|
|
@@ -29414,6 +30250,40 @@ const COPY = {
|
|
|
29414
30250
|
done: "Concluído!",
|
|
29415
30251
|
error: "Erro durante o processo"
|
|
29416
30252
|
},
|
|
30253
|
+
githubSetupTitle: "Ativar deploy automático",
|
|
30254
|
+
githubSetupQuestion: (repo) => `Deseja conectar ${repo} para fazer deploys automaticamente a cada push?`,
|
|
30255
|
+
githubSetupHint: "Pushes para o branch configurado vão disparar novos deploys automaticamente via GitHub.",
|
|
30256
|
+
githubSetupYes: "Sim, ativar deploy automático",
|
|
30257
|
+
githubSetupNo: "Agora não",
|
|
30258
|
+
githubSetupConnecting: "Conectando repositório...",
|
|
30259
|
+
githubSetupInstallTitle: "Instale o GitHub App para continuar",
|
|
30260
|
+
githubSetupInstallHint: "Abrimos o navegador — instale o app e volte aqui.",
|
|
30261
|
+
githubSetupInstallFallback: "Caso o navegador não abra, acesse:",
|
|
30262
|
+
githubSetupPolling: "Aguardando instalação do GitHub App...",
|
|
30263
|
+
githubSetupDone: "Deploy automático ativado!",
|
|
30264
|
+
githubSetupDoneHint: "Próximos pushes vão disparar deploys automaticamente.",
|
|
30265
|
+
githubSetupSkipped: "Tudo bem — você pode ativar depois com `veloz github setup`.",
|
|
30266
|
+
githubSetupErrorTitle: "Não conseguimos ativar o deploy automático",
|
|
30267
|
+
githubSetupErrorHint: "Tente novamente depois com `veloz github setup`.",
|
|
30268
|
+
aiSetupTitle: "Integração com IA",
|
|
30269
|
+
aiSetupQuestion: "Deseja integrar a Veloz com seu editor (Claude Code, Cursor, etc.) e com agentes de IA?",
|
|
30270
|
+
aiSetupMcpLabel: "Servidor MCP (`veloz mcp add`)",
|
|
30271
|
+
aiSetupMcpDescription: "Permite que editores como Claude Code controlem a Veloz via tools nativas.",
|
|
30272
|
+
aiSetupSkillsLabel: "Skills de agente (`veloz skills add`)",
|
|
30273
|
+
aiSetupSkillsDescription: "Ensina agentes de IA a usar os comandos da Veloz com documentação no projeto.",
|
|
30274
|
+
aiSetupYes: "Sim, instalar agora",
|
|
30275
|
+
aiSetupNo: "Agora não",
|
|
30276
|
+
aiSetupInstallingTitle: "Instalando integrações...",
|
|
30277
|
+
aiSetupMcpInstalling: "Registrando servidor MCP...",
|
|
30278
|
+
aiSetupMcpDone: "Servidor MCP registrado",
|
|
30279
|
+
aiSetupMcpError: "Falha ao registrar o servidor MCP",
|
|
30280
|
+
aiSetupSkillsInstalling: "Gerando skills no projeto...",
|
|
30281
|
+
aiSetupSkillsDone: "Skills geradas em .claude/skills/",
|
|
30282
|
+
aiSetupSkillsError: "Falha ao gerar as skills",
|
|
30283
|
+
aiSetupDoneTitle: "Integrações prontas!",
|
|
30284
|
+
aiSetupDoneHint: "Seu editor e agentes agora podem operar a Veloz com comandos nativos.",
|
|
30285
|
+
aiSetupErrorTitle: "Alguma integração falhou",
|
|
30286
|
+
aiSetupErrorHint: "Você pode tentar novamente depois com `veloz mcp add` e `veloz skills add`.",
|
|
29417
30287
|
outroSuccess: "Deploy realizado com sucesso!",
|
|
29418
30288
|
outroUrl: "URL do serviço:",
|
|
29419
30289
|
outroNextSteps: "Próximos passos:",
|
|
@@ -29537,7 +30407,7 @@ function PickerMenu({ items, onSelect, onCancel }) {
|
|
|
29537
30407
|
|
|
29538
30408
|
//#endregion
|
|
29539
30409
|
//#region src/wizard/ui/primitives/ScreenLayout.tsx
|
|
29540
|
-
const version = "0.0.0-beta.
|
|
30410
|
+
const version = "0.0.0-beta.33";
|
|
29541
30411
|
const LOGO_LINES = [
|
|
29542
30412
|
"██╗ ██╗███████╗██╗ ██████╗ ███████╗",
|
|
29543
30413
|
"██║ ██║██╔════╝██║ ██╔═══██╗╚══███╔╝",
|
|
@@ -29726,6 +30596,7 @@ function createInitialSession() {
|
|
|
29726
30596
|
selectedOrgId: null,
|
|
29727
30597
|
newOrgName: null,
|
|
29728
30598
|
orgCreateError: null,
|
|
30599
|
+
reauthNotice: null,
|
|
29729
30600
|
detectedFramework: null,
|
|
29730
30601
|
detectedFrameworkLabel: null,
|
|
29731
30602
|
selectedFramework: null,
|
|
@@ -29749,10 +30620,22 @@ function createInitialSession() {
|
|
|
29749
30620
|
learnCardBlockIdx: 0,
|
|
29750
30621
|
learnCardComplete: false,
|
|
29751
30622
|
agentHelperLines: [],
|
|
30623
|
+
agentHelperDiagram: null,
|
|
29752
30624
|
agentPrompt: null,
|
|
29753
30625
|
agentSummary: null,
|
|
29754
30626
|
userNotes: null,
|
|
29755
30627
|
notesConfirmed: false,
|
|
30628
|
+
githubSetupPhase: "idle",
|
|
30629
|
+
githubSetupError: null,
|
|
30630
|
+
githubInstallUrl: null,
|
|
30631
|
+
githubRepoOwner: null,
|
|
30632
|
+
githubRepoName: null,
|
|
30633
|
+
aiSetupPhase: "idle",
|
|
30634
|
+
aiSetupError: null,
|
|
30635
|
+
aiSetupMcpStatus: "pending",
|
|
30636
|
+
aiSetupSkillsStatus: "pending",
|
|
30637
|
+
aiSetupNeedsMcp: false,
|
|
30638
|
+
aiSetupNeedsSkills: false,
|
|
29756
30639
|
introConfirmed: false,
|
|
29757
30640
|
outroDismissed: false,
|
|
29758
30641
|
agentStartedAt: null
|
|
@@ -29847,29 +30730,45 @@ function AuthScreen({ store }) {
|
|
|
29847
30730
|
})]
|
|
29848
30731
|
})] });
|
|
29849
30732
|
}
|
|
29850
|
-
if (session.authPhase === "browser" || session.authPhase === "polling") return /* @__PURE__ */ jsxs(ScreenLayout, { children: [
|
|
29851
|
-
|
|
29852
|
-
|
|
29853
|
-
|
|
29854
|
-
|
|
29855
|
-
|
|
29856
|
-
|
|
29857
|
-
|
|
29858
|
-
|
|
29859
|
-
|
|
29860
|
-
|
|
29861
|
-
}), session.authVerificationUrl ? /* @__PURE__ */ jsxs(Box, {
|
|
30733
|
+
if (session.authPhase === "browser" || session.authPhase === "polling") return /* @__PURE__ */ jsxs(ScreenLayout, { children: [
|
|
30734
|
+
session.reauthNotice ? /* @__PURE__ */ jsx(Box, {
|
|
30735
|
+
marginBottom: 1,
|
|
30736
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
30737
|
+
color: "yellow",
|
|
30738
|
+
children: ["⚠ ", session.reauthNotice]
|
|
30739
|
+
})
|
|
30740
|
+
}) : null,
|
|
30741
|
+
/* @__PURE__ */ jsx(Spinner, { label: COPY.authPolling }),
|
|
30742
|
+
session.authDeviceCode ? /* @__PURE__ */ jsxs(Box, {
|
|
30743
|
+
marginTop: 1,
|
|
29862
30744
|
flexDirection: "column",
|
|
29863
|
-
|
|
29864
|
-
|
|
29865
|
-
|
|
29866
|
-
|
|
29867
|
-
|
|
29868
|
-
|
|
29869
|
-
|
|
29870
|
-
|
|
29871
|
-
|
|
29872
|
-
|
|
30745
|
+
gap: 1,
|
|
30746
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
30747
|
+
gap: 1,
|
|
30748
|
+
children: [/* @__PURE__ */ jsx(Text, { children: COPY.authDeviceCode }), /* @__PURE__ */ jsx(Text, {
|
|
30749
|
+
bold: true,
|
|
30750
|
+
color: BRAND_COLOR,
|
|
30751
|
+
children: session.authDeviceCode
|
|
30752
|
+
})]
|
|
30753
|
+
}), session.authVerificationUrl ? /* @__PURE__ */ jsxs(Box, {
|
|
30754
|
+
flexDirection: "column",
|
|
30755
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
30756
|
+
dimColor: true,
|
|
30757
|
+
children: COPY.authBrowserFallback
|
|
30758
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
30759
|
+
dimColor: true,
|
|
30760
|
+
children: session.authVerificationUrl
|
|
30761
|
+
})]
|
|
30762
|
+
}) : null]
|
|
30763
|
+
}) : null
|
|
30764
|
+
] });
|
|
30765
|
+
return /* @__PURE__ */ jsxs(ScreenLayout, { children: [session.reauthNotice ? /* @__PURE__ */ jsx(Box, {
|
|
30766
|
+
marginBottom: 1,
|
|
30767
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
30768
|
+
color: "yellow",
|
|
30769
|
+
children: ["⚠ ", session.reauthNotice]
|
|
30770
|
+
})
|
|
30771
|
+
}) : null, /* @__PURE__ */ jsx(Spinner, { label: COPY.authChecking })] });
|
|
29873
30772
|
}
|
|
29874
30773
|
function OrgCreateForm({ store }) {
|
|
29875
30774
|
const [value, setValue] = useState("");
|
|
@@ -30977,6 +31876,165 @@ function TipsCard() {
|
|
|
30977
31876
|
});
|
|
30978
31877
|
}
|
|
30979
31878
|
|
|
31879
|
+
//#endregion
|
|
31880
|
+
//#region src/wizard/ui/primitives/DiagramPane.tsx
|
|
31881
|
+
const HORIZONTAL_ARROW = " ─▶ ";
|
|
31882
|
+
const VERTICAL_ARROW = "▼";
|
|
31883
|
+
const MIN_NODE_INNER = 10;
|
|
31884
|
+
const MAX_NODE_INNER = 28;
|
|
31885
|
+
function DiagramPane({ diagram, width }) {
|
|
31886
|
+
const { title, pipeline, services, notes } = diagram;
|
|
31887
|
+
const hasPipeline = pipeline.length > 0;
|
|
31888
|
+
const hasServices = services.length > 0;
|
|
31889
|
+
const hasNotes = notes.length > 0;
|
|
31890
|
+
const orientation = pickOrientation(pipeline, width);
|
|
31891
|
+
const nodeInner = pickNodeInner(pipeline, width, orientation);
|
|
31892
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
31893
|
+
flexDirection: "column",
|
|
31894
|
+
paddingX: 1,
|
|
31895
|
+
children: [
|
|
31896
|
+
title ? /* @__PURE__ */ jsx(Box, {
|
|
31897
|
+
marginBottom: 1,
|
|
31898
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
31899
|
+
bold: true,
|
|
31900
|
+
color: ACCENT_COLOR,
|
|
31901
|
+
wrap: "truncate",
|
|
31902
|
+
children: title
|
|
31903
|
+
})
|
|
31904
|
+
}) : null,
|
|
31905
|
+
hasPipeline ? orientation === "horizontal" ? /* @__PURE__ */ jsx(HorizontalPipeline, {
|
|
31906
|
+
nodes: pipeline,
|
|
31907
|
+
nodeInner
|
|
31908
|
+
}) : /* @__PURE__ */ jsx(VerticalPipeline, {
|
|
31909
|
+
nodes: pipeline,
|
|
31910
|
+
nodeInner
|
|
31911
|
+
}) : null,
|
|
31912
|
+
hasServices ? /* @__PURE__ */ jsxs(Box, {
|
|
31913
|
+
marginTop: hasPipeline ? 1 : 0,
|
|
31914
|
+
flexDirection: "column",
|
|
31915
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
31916
|
+
dimColor: true,
|
|
31917
|
+
children: "Serviços auxiliares"
|
|
31918
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
31919
|
+
marginTop: 1,
|
|
31920
|
+
flexDirection: "row",
|
|
31921
|
+
flexWrap: "wrap",
|
|
31922
|
+
gap: 1,
|
|
31923
|
+
children: services.map((label, i) => /* @__PURE__ */ jsx(ServiceBox, {
|
|
31924
|
+
label,
|
|
31925
|
+
inner: nodeInner
|
|
31926
|
+
}, `${i}-${label}`))
|
|
31927
|
+
})]
|
|
31928
|
+
}) : null,
|
|
31929
|
+
hasNotes ? /* @__PURE__ */ jsx(Box, {
|
|
31930
|
+
marginTop: hasPipeline || hasServices ? 1 : 0,
|
|
31931
|
+
flexDirection: "column",
|
|
31932
|
+
children: notes.map((note, i) => /* @__PURE__ */ jsxs(Text, {
|
|
31933
|
+
color: DIM_COLOR,
|
|
31934
|
+
wrap: "truncate",
|
|
31935
|
+
children: ["· ", note]
|
|
31936
|
+
}, i))
|
|
31937
|
+
}) : null
|
|
31938
|
+
]
|
|
31939
|
+
});
|
|
31940
|
+
}
|
|
31941
|
+
function HorizontalPipeline({ nodes, nodeInner }) {
|
|
31942
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
31943
|
+
flexDirection: "row",
|
|
31944
|
+
flexWrap: "nowrap",
|
|
31945
|
+
children: nodes.map((label, i) => /* @__PURE__ */ jsxs(Box, {
|
|
31946
|
+
flexDirection: "row",
|
|
31947
|
+
flexShrink: 0,
|
|
31948
|
+
children: [/* @__PURE__ */ jsx(PipelineNode, {
|
|
31949
|
+
label,
|
|
31950
|
+
inner: nodeInner,
|
|
31951
|
+
isLast: i === nodes.length - 1
|
|
31952
|
+
}), i < nodes.length - 1 ? /* @__PURE__ */ jsx(Box, {
|
|
31953
|
+
alignItems: "center",
|
|
31954
|
+
paddingX: 0,
|
|
31955
|
+
flexShrink: 0,
|
|
31956
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
31957
|
+
color: BRAND_COLOR,
|
|
31958
|
+
children: HORIZONTAL_ARROW
|
|
31959
|
+
})
|
|
31960
|
+
}) : null]
|
|
31961
|
+
}, `${i}-${label}`))
|
|
31962
|
+
});
|
|
31963
|
+
}
|
|
31964
|
+
function VerticalPipeline({ nodes, nodeInner }) {
|
|
31965
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
31966
|
+
flexDirection: "column",
|
|
31967
|
+
children: nodes.map((label, i) => /* @__PURE__ */ jsxs(Box, {
|
|
31968
|
+
flexDirection: "column",
|
|
31969
|
+
children: [/* @__PURE__ */ jsx(PipelineNode, {
|
|
31970
|
+
label,
|
|
31971
|
+
inner: nodeInner,
|
|
31972
|
+
isLast: i === nodes.length - 1
|
|
31973
|
+
}), i < nodes.length - 1 ? /* @__PURE__ */ jsx(Box, {
|
|
31974
|
+
paddingLeft: Math.max(1, Math.floor(nodeInner / 2)),
|
|
31975
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
31976
|
+
color: BRAND_COLOR,
|
|
31977
|
+
children: VERTICAL_ARROW
|
|
31978
|
+
})
|
|
31979
|
+
}) : null]
|
|
31980
|
+
}, `${i}-${label}`))
|
|
31981
|
+
});
|
|
31982
|
+
}
|
|
31983
|
+
function PipelineNode({ label, inner, isLast }) {
|
|
31984
|
+
const displayed = truncate(label, inner);
|
|
31985
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
31986
|
+
borderStyle: "round",
|
|
31987
|
+
borderColor: isLast ? BRAND_COLOR : ACCENT_COLOR,
|
|
31988
|
+
paddingX: 1,
|
|
31989
|
+
flexShrink: 0,
|
|
31990
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
31991
|
+
color: isLast ? BRAND_COLOR : void 0,
|
|
31992
|
+
bold: isLast,
|
|
31993
|
+
children: displayed
|
|
31994
|
+
})
|
|
31995
|
+
});
|
|
31996
|
+
}
|
|
31997
|
+
function ServiceBox({ label, inner }) {
|
|
31998
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
31999
|
+
borderStyle: "single",
|
|
32000
|
+
borderColor: DIM_COLOR,
|
|
32001
|
+
paddingX: 1,
|
|
32002
|
+
flexShrink: 0,
|
|
32003
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32004
|
+
dimColor: true,
|
|
32005
|
+
children: truncate(label, inner)
|
|
32006
|
+
})
|
|
32007
|
+
});
|
|
32008
|
+
}
|
|
32009
|
+
/**
|
|
32010
|
+
* Decide horizontal vs vertical pipeline. Horizontal fits when the sum of all
|
|
32011
|
+
* node widths plus arrows is ≤ the width budget. Falls back to vertical if any
|
|
32012
|
+
* single node would be truncated below a useful minimum.
|
|
32013
|
+
*/
|
|
32014
|
+
function pickOrientation(nodes, width) {
|
|
32015
|
+
if (nodes.length <= 1) return "horizontal";
|
|
32016
|
+
const arrows = 4 * (nodes.length - 1);
|
|
32017
|
+
const boxChrome = 4 * nodes.length;
|
|
32018
|
+
return nodes.reduce((sum, n) => sum + Math.min(n.length, MAX_NODE_INNER), 0) + boxChrome + arrows <= Math.max(0, width - 2) ? "horizontal" : "vertical";
|
|
32019
|
+
}
|
|
32020
|
+
function pickNodeInner(nodes, width, orientation) {
|
|
32021
|
+
if (nodes.length === 0) return MIN_NODE_INNER;
|
|
32022
|
+
const longest = nodes.reduce((max, n) => Math.max(max, n.length), 0);
|
|
32023
|
+
if (orientation === "vertical") {
|
|
32024
|
+
const budget$1 = Math.max(MIN_NODE_INNER, width - 6);
|
|
32025
|
+
return Math.min(MAX_NODE_INNER, Math.max(MIN_NODE_INNER, Math.min(longest, budget$1)));
|
|
32026
|
+
}
|
|
32027
|
+
const arrows = 4 * (nodes.length - 1);
|
|
32028
|
+
const perBox = Math.floor((Math.max(0, width - 2) - arrows) / nodes.length) - 4;
|
|
32029
|
+
const budget = Math.max(MIN_NODE_INNER, perBox);
|
|
32030
|
+
return Math.min(MAX_NODE_INNER, Math.max(MIN_NODE_INNER, Math.min(longest, budget)));
|
|
32031
|
+
}
|
|
32032
|
+
function truncate(label, maxInner) {
|
|
32033
|
+
if (label.length <= maxInner) return label;
|
|
32034
|
+
if (maxInner <= 1) return "…";
|
|
32035
|
+
return label.slice(0, maxInner - 1) + "…";
|
|
32036
|
+
}
|
|
32037
|
+
|
|
30980
32038
|
//#endregion
|
|
30981
32039
|
//#region src/wizard/ui/screens/RunScreen.tsx
|
|
30982
32040
|
/**
|
|
@@ -30999,12 +32057,23 @@ function RunScreen({ store }) {
|
|
|
30999
32057
|
store,
|
|
31000
32058
|
onComplete: () => store.setLearnCardComplete()
|
|
31001
32059
|
});
|
|
31002
|
-
const
|
|
32060
|
+
const diagram = session.agentHelperDiagram;
|
|
32061
|
+
const hasDiagram = diagram !== null;
|
|
32062
|
+
const hasAgentHelperLines = session.agentHelperLines.length > 0;
|
|
32063
|
+
const diagramWidth = Math.max(20, Math.floor(columns / 2) - 6);
|
|
31003
32064
|
const rightPane = /* @__PURE__ */ jsxs(Box, {
|
|
31004
32065
|
flexDirection: "column",
|
|
31005
32066
|
flexGrow: 1,
|
|
31006
32067
|
overflow: "hidden",
|
|
31007
|
-
children: [/* @__PURE__ */ jsx(TasksPane, { store }),
|
|
32068
|
+
children: [/* @__PURE__ */ jsx(TasksPane, { store }), hasDiagram ? /* @__PURE__ */ jsx(Box, {
|
|
32069
|
+
marginTop: 1,
|
|
32070
|
+
flexGrow: 1,
|
|
32071
|
+
overflow: "hidden",
|
|
32072
|
+
children: /* @__PURE__ */ jsx(DiagramPane, {
|
|
32073
|
+
diagram,
|
|
32074
|
+
width: diagramWidth
|
|
32075
|
+
})
|
|
32076
|
+
}) : hasAgentHelperLines ? /* @__PURE__ */ jsx(Box, {
|
|
31008
32077
|
marginTop: 1,
|
|
31009
32078
|
flexGrow: 1,
|
|
31010
32079
|
overflow: "hidden",
|
|
@@ -31452,6 +32521,397 @@ function AgentHelperPane({ lines }) {
|
|
|
31452
32521
|
});
|
|
31453
32522
|
}
|
|
31454
32523
|
|
|
32524
|
+
//#endregion
|
|
32525
|
+
//#region src/wizard/ui/screens/GithubSetupScreen.tsx
|
|
32526
|
+
/**
|
|
32527
|
+
* GithubSetupScreen — Post-deploy step asking whether to enable automatic
|
|
32528
|
+
* deploys via the Veloz GitHub App. The runner in `index.ts` drives the
|
|
32529
|
+
* phases; this screen only renders the current state.
|
|
32530
|
+
*/
|
|
32531
|
+
function GithubSetupScreen({ store }) {
|
|
32532
|
+
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
32533
|
+
const { session } = store;
|
|
32534
|
+
const repo = session.githubRepoOwner && session.githubRepoName ? `${session.githubRepoOwner}/${session.githubRepoName}` : "seu repositório";
|
|
32535
|
+
if (session.githubSetupPhase === "prompt") return /* @__PURE__ */ jsx(PromptView$1, {
|
|
32536
|
+
store,
|
|
32537
|
+
repo
|
|
32538
|
+
});
|
|
32539
|
+
if (session.githubSetupPhase === "connecting") return /* @__PURE__ */ jsxs(ScreenLayout, { children: [/* @__PURE__ */ jsx(Text, {
|
|
32540
|
+
color: BRAND_COLOR,
|
|
32541
|
+
bold: true,
|
|
32542
|
+
children: COPY.githubSetupTitle
|
|
32543
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
32544
|
+
marginTop: 1,
|
|
32545
|
+
children: /* @__PURE__ */ jsx(Spinner, { label: COPY.githubSetupConnecting })
|
|
32546
|
+
})] });
|
|
32547
|
+
if (session.githubSetupPhase === "installing") return /* @__PURE__ */ jsxs(ScreenLayout, {
|
|
32548
|
+
footer: COPY.githubSetupInstallHint,
|
|
32549
|
+
children: [
|
|
32550
|
+
/* @__PURE__ */ jsx(Text, {
|
|
32551
|
+
color: BRAND_COLOR,
|
|
32552
|
+
bold: true,
|
|
32553
|
+
children: COPY.githubSetupInstallTitle
|
|
32554
|
+
}),
|
|
32555
|
+
/* @__PURE__ */ jsx(Box, {
|
|
32556
|
+
marginTop: 1,
|
|
32557
|
+
children: /* @__PURE__ */ jsx(Spinner, { label: COPY.githubSetupPolling })
|
|
32558
|
+
}),
|
|
32559
|
+
session.githubInstallUrl ? /* @__PURE__ */ jsxs(Box, {
|
|
32560
|
+
marginTop: 1,
|
|
32561
|
+
flexDirection: "column",
|
|
32562
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32563
|
+
dimColor: true,
|
|
32564
|
+
children: COPY.githubSetupInstallFallback
|
|
32565
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32566
|
+
bold: true,
|
|
32567
|
+
children: session.githubInstallUrl
|
|
32568
|
+
})]
|
|
32569
|
+
}) : null
|
|
32570
|
+
]
|
|
32571
|
+
});
|
|
32572
|
+
if (session.githubSetupPhase === "done") return /* @__PURE__ */ jsxs(ScreenLayout, { children: [/* @__PURE__ */ jsxs(Box, {
|
|
32573
|
+
gap: 1,
|
|
32574
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32575
|
+
color: SUCCESS_COLOR,
|
|
32576
|
+
children: Icons.completed
|
|
32577
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32578
|
+
color: SUCCESS_COLOR,
|
|
32579
|
+
bold: true,
|
|
32580
|
+
children: COPY.githubSetupDone
|
|
32581
|
+
})]
|
|
32582
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
32583
|
+
marginTop: 1,
|
|
32584
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32585
|
+
dimColor: true,
|
|
32586
|
+
children: COPY.githubSetupDoneHint
|
|
32587
|
+
})
|
|
32588
|
+
})] });
|
|
32589
|
+
if (session.githubSetupPhase === "error") return /* @__PURE__ */ jsxs(ScreenLayout, { children: [
|
|
32590
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
32591
|
+
gap: 1,
|
|
32592
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32593
|
+
color: ERROR_COLOR,
|
|
32594
|
+
children: Icons.error
|
|
32595
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32596
|
+
color: ERROR_COLOR,
|
|
32597
|
+
bold: true,
|
|
32598
|
+
children: COPY.githubSetupErrorTitle
|
|
32599
|
+
})]
|
|
32600
|
+
}),
|
|
32601
|
+
session.githubSetupError ? /* @__PURE__ */ jsx(Box, {
|
|
32602
|
+
marginTop: 1,
|
|
32603
|
+
marginLeft: 2,
|
|
32604
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32605
|
+
color: ERROR_COLOR,
|
|
32606
|
+
wrap: "wrap",
|
|
32607
|
+
children: session.githubSetupError
|
|
32608
|
+
})
|
|
32609
|
+
}) : null,
|
|
32610
|
+
/* @__PURE__ */ jsx(Box, {
|
|
32611
|
+
marginTop: 1,
|
|
32612
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32613
|
+
dimColor: true,
|
|
32614
|
+
children: COPY.githubSetupErrorHint
|
|
32615
|
+
})
|
|
32616
|
+
})
|
|
32617
|
+
] });
|
|
32618
|
+
return /* @__PURE__ */ jsx(ScreenLayout, { children: /* @__PURE__ */ jsx(Text, {
|
|
32619
|
+
dimColor: true,
|
|
32620
|
+
children: COPY.githubSetupSkipped
|
|
32621
|
+
}) });
|
|
32622
|
+
}
|
|
32623
|
+
function PromptView$1({ store, repo }) {
|
|
32624
|
+
const [selected, setSelected] = React.useState(0);
|
|
32625
|
+
useGatedInput((_input, key) => {
|
|
32626
|
+
if (key.upArrow || key.downArrow) {
|
|
32627
|
+
setSelected((s) => s === 0 ? 1 : 0);
|
|
32628
|
+
return;
|
|
32629
|
+
}
|
|
32630
|
+
if (key.return) store.setGithubSetupPhase(selected === 0 ? "connecting" : "skipped");
|
|
32631
|
+
});
|
|
32632
|
+
const options = [COPY.githubSetupYes, COPY.githubSetupNo];
|
|
32633
|
+
return /* @__PURE__ */ jsxs(ScreenLayout, {
|
|
32634
|
+
footer: "↑↓ escolher · Enter confirmar",
|
|
32635
|
+
children: [
|
|
32636
|
+
/* @__PURE__ */ jsx(Text, {
|
|
32637
|
+
color: BRAND_COLOR,
|
|
32638
|
+
bold: true,
|
|
32639
|
+
children: COPY.githubSetupTitle
|
|
32640
|
+
}),
|
|
32641
|
+
/* @__PURE__ */ jsx(Box, {
|
|
32642
|
+
marginTop: 1,
|
|
32643
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32644
|
+
wrap: "wrap",
|
|
32645
|
+
children: COPY.githubSetupQuestion(repo)
|
|
32646
|
+
})
|
|
32647
|
+
}),
|
|
32648
|
+
/* @__PURE__ */ jsx(Box, {
|
|
32649
|
+
marginTop: 1,
|
|
32650
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32651
|
+
color: DIM_COLOR,
|
|
32652
|
+
wrap: "wrap",
|
|
32653
|
+
children: COPY.githubSetupHint
|
|
32654
|
+
})
|
|
32655
|
+
}),
|
|
32656
|
+
/* @__PURE__ */ jsx(Box, {
|
|
32657
|
+
marginTop: 1,
|
|
32658
|
+
flexDirection: "column",
|
|
32659
|
+
children: options.map((opt, i) => /* @__PURE__ */ jsxs(Box, {
|
|
32660
|
+
gap: 1,
|
|
32661
|
+
marginLeft: 2,
|
|
32662
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32663
|
+
color: i === selected ? BRAND_COLOR : DIM_COLOR,
|
|
32664
|
+
children: i === selected ? Icons.triangleRight : " "
|
|
32665
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32666
|
+
color: i === selected ? BRAND_COLOR : void 0,
|
|
32667
|
+
bold: i === selected,
|
|
32668
|
+
children: opt
|
|
32669
|
+
})]
|
|
32670
|
+
}, i))
|
|
32671
|
+
})
|
|
32672
|
+
]
|
|
32673
|
+
});
|
|
32674
|
+
}
|
|
32675
|
+
|
|
32676
|
+
//#endregion
|
|
32677
|
+
//#region src/wizard/ui/screens/AiSetupScreen.tsx
|
|
32678
|
+
/**
|
|
32679
|
+
* AiSetupScreen — Post-deploy step that offers to install the Veloz MCP
|
|
32680
|
+
* server and the agent skills. The runner in `index.ts` detects what's
|
|
32681
|
+
* missing, drives the phases, and spawns the installation commands; this
|
|
32682
|
+
* screen renders the current state.
|
|
32683
|
+
*/
|
|
32684
|
+
function AiSetupScreen({ store }) {
|
|
32685
|
+
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
32686
|
+
const { session } = store;
|
|
32687
|
+
if (session.aiSetupPhase === "prompt") return /* @__PURE__ */ jsx(PromptView, { store });
|
|
32688
|
+
if (session.aiSetupPhase === "installing") return /* @__PURE__ */ jsxs(ScreenLayout, { children: [/* @__PURE__ */ jsx(Text, {
|
|
32689
|
+
color: BRAND_COLOR,
|
|
32690
|
+
bold: true,
|
|
32691
|
+
children: COPY.aiSetupInstallingTitle
|
|
32692
|
+
}), /* @__PURE__ */ jsxs(Box, {
|
|
32693
|
+
marginTop: 1,
|
|
32694
|
+
flexDirection: "column",
|
|
32695
|
+
children: [session.aiSetupNeedsMcp ? /* @__PURE__ */ jsx(ToolRow, {
|
|
32696
|
+
status: session.aiSetupMcpStatus,
|
|
32697
|
+
installingLabel: COPY.aiSetupMcpInstalling,
|
|
32698
|
+
doneLabel: COPY.aiSetupMcpDone,
|
|
32699
|
+
errorLabel: COPY.aiSetupMcpError,
|
|
32700
|
+
pendingLabel: COPY.aiSetupMcpLabel
|
|
32701
|
+
}) : null, session.aiSetupNeedsSkills ? /* @__PURE__ */ jsx(ToolRow, {
|
|
32702
|
+
status: session.aiSetupSkillsStatus,
|
|
32703
|
+
installingLabel: COPY.aiSetupSkillsInstalling,
|
|
32704
|
+
doneLabel: COPY.aiSetupSkillsDone,
|
|
32705
|
+
errorLabel: COPY.aiSetupSkillsError,
|
|
32706
|
+
pendingLabel: COPY.aiSetupSkillsLabel
|
|
32707
|
+
}) : null]
|
|
32708
|
+
})] });
|
|
32709
|
+
if (session.aiSetupPhase === "done") return /* @__PURE__ */ jsxs(ScreenLayout, { children: [
|
|
32710
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
32711
|
+
gap: 1,
|
|
32712
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32713
|
+
color: SUCCESS_COLOR,
|
|
32714
|
+
children: Icons.completed
|
|
32715
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32716
|
+
color: SUCCESS_COLOR,
|
|
32717
|
+
bold: true,
|
|
32718
|
+
children: COPY.aiSetupDoneTitle
|
|
32719
|
+
})]
|
|
32720
|
+
}),
|
|
32721
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
32722
|
+
marginTop: 1,
|
|
32723
|
+
flexDirection: "column",
|
|
32724
|
+
children: [session.aiSetupNeedsMcp ? /* @__PURE__ */ jsx(ToolRow, {
|
|
32725
|
+
status: session.aiSetupMcpStatus,
|
|
32726
|
+
installingLabel: COPY.aiSetupMcpInstalling,
|
|
32727
|
+
doneLabel: COPY.aiSetupMcpDone,
|
|
32728
|
+
errorLabel: COPY.aiSetupMcpError,
|
|
32729
|
+
pendingLabel: COPY.aiSetupMcpLabel
|
|
32730
|
+
}) : null, session.aiSetupNeedsSkills ? /* @__PURE__ */ jsx(ToolRow, {
|
|
32731
|
+
status: session.aiSetupSkillsStatus,
|
|
32732
|
+
installingLabel: COPY.aiSetupSkillsInstalling,
|
|
32733
|
+
doneLabel: COPY.aiSetupSkillsDone,
|
|
32734
|
+
errorLabel: COPY.aiSetupSkillsError,
|
|
32735
|
+
pendingLabel: COPY.aiSetupSkillsLabel
|
|
32736
|
+
}) : null]
|
|
32737
|
+
}),
|
|
32738
|
+
/* @__PURE__ */ jsx(Box, {
|
|
32739
|
+
marginTop: 1,
|
|
32740
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32741
|
+
dimColor: true,
|
|
32742
|
+
children: COPY.aiSetupDoneHint
|
|
32743
|
+
})
|
|
32744
|
+
})
|
|
32745
|
+
] });
|
|
32746
|
+
if (session.aiSetupPhase === "error") return /* @__PURE__ */ jsxs(ScreenLayout, { children: [
|
|
32747
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
32748
|
+
gap: 1,
|
|
32749
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32750
|
+
color: ERROR_COLOR,
|
|
32751
|
+
children: Icons.error
|
|
32752
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32753
|
+
color: ERROR_COLOR,
|
|
32754
|
+
bold: true,
|
|
32755
|
+
children: COPY.aiSetupErrorTitle
|
|
32756
|
+
})]
|
|
32757
|
+
}),
|
|
32758
|
+
session.aiSetupError ? /* @__PURE__ */ jsx(Box, {
|
|
32759
|
+
marginTop: 1,
|
|
32760
|
+
marginLeft: 2,
|
|
32761
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32762
|
+
color: ERROR_COLOR,
|
|
32763
|
+
wrap: "wrap",
|
|
32764
|
+
children: session.aiSetupError
|
|
32765
|
+
})
|
|
32766
|
+
}) : null,
|
|
32767
|
+
/* @__PURE__ */ jsx(Box, {
|
|
32768
|
+
marginTop: 1,
|
|
32769
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32770
|
+
dimColor: true,
|
|
32771
|
+
children: COPY.aiSetupErrorHint
|
|
32772
|
+
})
|
|
32773
|
+
})
|
|
32774
|
+
] });
|
|
32775
|
+
return /* @__PURE__ */ jsx(ScreenLayout, { children: null });
|
|
32776
|
+
}
|
|
32777
|
+
function ToolRow({ status, installingLabel, doneLabel, errorLabel, pendingLabel }) {
|
|
32778
|
+
if (status === "installing") return /* @__PURE__ */ jsx(Box, {
|
|
32779
|
+
marginLeft: 2,
|
|
32780
|
+
children: /* @__PURE__ */ jsx(Spinner, { label: installingLabel })
|
|
32781
|
+
});
|
|
32782
|
+
if (status === "done") return /* @__PURE__ */ jsxs(Box, {
|
|
32783
|
+
gap: 1,
|
|
32784
|
+
marginLeft: 2,
|
|
32785
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32786
|
+
color: SUCCESS_COLOR,
|
|
32787
|
+
children: Icons.completed
|
|
32788
|
+
}), /* @__PURE__ */ jsx(Text, { children: doneLabel })]
|
|
32789
|
+
});
|
|
32790
|
+
if (status === "error") return /* @__PURE__ */ jsxs(Box, {
|
|
32791
|
+
gap: 1,
|
|
32792
|
+
marginLeft: 2,
|
|
32793
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32794
|
+
color: ERROR_COLOR,
|
|
32795
|
+
children: Icons.error
|
|
32796
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32797
|
+
color: ERROR_COLOR,
|
|
32798
|
+
children: errorLabel
|
|
32799
|
+
})]
|
|
32800
|
+
});
|
|
32801
|
+
if (status === "skipped") return /* @__PURE__ */ jsxs(Box, {
|
|
32802
|
+
gap: 1,
|
|
32803
|
+
marginLeft: 2,
|
|
32804
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32805
|
+
color: DIM_COLOR,
|
|
32806
|
+
children: "-"
|
|
32807
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
32808
|
+
dimColor: true,
|
|
32809
|
+
children: [pendingLabel, " (já instalado)"]
|
|
32810
|
+
})]
|
|
32811
|
+
});
|
|
32812
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
32813
|
+
gap: 1,
|
|
32814
|
+
marginLeft: 2,
|
|
32815
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32816
|
+
color: DIM_COLOR,
|
|
32817
|
+
children: Icons.pending
|
|
32818
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32819
|
+
dimColor: true,
|
|
32820
|
+
children: pendingLabel
|
|
32821
|
+
})]
|
|
32822
|
+
});
|
|
32823
|
+
}
|
|
32824
|
+
function PromptView({ store }) {
|
|
32825
|
+
const [selected, setSelected] = React.useState(0);
|
|
32826
|
+
const { session } = store;
|
|
32827
|
+
useGatedInput((_input, key) => {
|
|
32828
|
+
if (key.upArrow || key.downArrow) {
|
|
32829
|
+
setSelected((s) => s === 0 ? 1 : 0);
|
|
32830
|
+
return;
|
|
32831
|
+
}
|
|
32832
|
+
if (key.return) store.setAiSetupPhase(selected === 0 ? "installing" : "skipped");
|
|
32833
|
+
});
|
|
32834
|
+
const options = [COPY.aiSetupYes, COPY.aiSetupNo];
|
|
32835
|
+
return /* @__PURE__ */ jsxs(ScreenLayout, {
|
|
32836
|
+
footer: "↑↓ escolher · Enter confirmar",
|
|
32837
|
+
children: [
|
|
32838
|
+
/* @__PURE__ */ jsx(Text, {
|
|
32839
|
+
color: BRAND_COLOR,
|
|
32840
|
+
bold: true,
|
|
32841
|
+
children: COPY.aiSetupTitle
|
|
32842
|
+
}),
|
|
32843
|
+
/* @__PURE__ */ jsx(Box, {
|
|
32844
|
+
marginTop: 1,
|
|
32845
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32846
|
+
wrap: "wrap",
|
|
32847
|
+
children: COPY.aiSetupQuestion
|
|
32848
|
+
})
|
|
32849
|
+
}),
|
|
32850
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
32851
|
+
marginTop: 1,
|
|
32852
|
+
flexDirection: "column",
|
|
32853
|
+
marginLeft: 2,
|
|
32854
|
+
children: [session.aiSetupNeedsMcp ? /* @__PURE__ */ jsxs(Box, {
|
|
32855
|
+
flexDirection: "column",
|
|
32856
|
+
marginBottom: 1,
|
|
32857
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
32858
|
+
gap: 1,
|
|
32859
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32860
|
+
color: BRAND_COLOR,
|
|
32861
|
+
children: Icons.bullet
|
|
32862
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32863
|
+
bold: true,
|
|
32864
|
+
children: COPY.aiSetupMcpLabel
|
|
32865
|
+
})]
|
|
32866
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
32867
|
+
marginLeft: 2,
|
|
32868
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32869
|
+
color: DIM_COLOR,
|
|
32870
|
+
wrap: "wrap",
|
|
32871
|
+
children: COPY.aiSetupMcpDescription
|
|
32872
|
+
})
|
|
32873
|
+
})]
|
|
32874
|
+
}) : null, session.aiSetupNeedsSkills ? /* @__PURE__ */ jsxs(Box, {
|
|
32875
|
+
flexDirection: "column",
|
|
32876
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
32877
|
+
gap: 1,
|
|
32878
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32879
|
+
color: BRAND_COLOR,
|
|
32880
|
+
children: Icons.bullet
|
|
32881
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32882
|
+
bold: true,
|
|
32883
|
+
children: COPY.aiSetupSkillsLabel
|
|
32884
|
+
})]
|
|
32885
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
32886
|
+
marginLeft: 2,
|
|
32887
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
32888
|
+
color: DIM_COLOR,
|
|
32889
|
+
wrap: "wrap",
|
|
32890
|
+
children: COPY.aiSetupSkillsDescription
|
|
32891
|
+
})
|
|
32892
|
+
})]
|
|
32893
|
+
}) : null]
|
|
32894
|
+
}),
|
|
32895
|
+
/* @__PURE__ */ jsx(Box, {
|
|
32896
|
+
marginTop: 1,
|
|
32897
|
+
flexDirection: "column",
|
|
32898
|
+
children: options.map((opt, i) => /* @__PURE__ */ jsxs(Box, {
|
|
32899
|
+
gap: 1,
|
|
32900
|
+
marginLeft: 2,
|
|
32901
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
32902
|
+
color: i === selected ? BRAND_COLOR : DIM_COLOR,
|
|
32903
|
+
children: i === selected ? Icons.triangleRight : " "
|
|
32904
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
32905
|
+
color: i === selected ? BRAND_COLOR : void 0,
|
|
32906
|
+
bold: i === selected,
|
|
32907
|
+
children: opt
|
|
32908
|
+
})]
|
|
32909
|
+
}, i))
|
|
32910
|
+
})
|
|
32911
|
+
]
|
|
32912
|
+
});
|
|
32913
|
+
}
|
|
32914
|
+
|
|
31455
32915
|
//#endregion
|
|
31456
32916
|
//#region src/wizard/ui/primitives/MarkdownText.tsx
|
|
31457
32917
|
/** Parse inline markdown (**bold** and `code`) into Ink <Text> elements. */
|
|
@@ -31517,203 +32977,327 @@ function MarkdownText({ children }) {
|
|
|
31517
32977
|
//#endregion
|
|
31518
32978
|
//#region src/wizard/ui/screens/OutroScreen.tsx
|
|
31519
32979
|
/**
|
|
31520
|
-
* OutroScreen — Final screen after agent completes or fails.
|
|
31521
|
-
*
|
|
31522
|
-
*
|
|
32980
|
+
* OutroScreen — Final screen after the agent completes or fails.
|
|
32981
|
+
*
|
|
32982
|
+
* Scrollable content (↑/↓) with an action bar at the bottom:
|
|
32983
|
+
* [ Abrir projeto ] [ Abrir dashboard ] [ Fechar ]
|
|
32984
|
+
* Left/right to move between actions, Enter to confirm.
|
|
31523
32985
|
*/
|
|
32986
|
+
const DASHBOARD_BASE = process.env.VELOZ_WEB_URL || "https://app.onveloz.com";
|
|
31524
32987
|
function OutroScreen({ store }) {
|
|
31525
32988
|
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
31526
32989
|
const { session } = store;
|
|
31527
32990
|
const isSuccess$4 = !session.agentError;
|
|
31528
|
-
const [columns] = useStdoutDimensions();
|
|
32991
|
+
const [columns, rows] = useStdoutDimensions();
|
|
31529
32992
|
const sepWidth = Math.min(columns - 8, 80);
|
|
31530
32993
|
const aiSetup = useMemo(() => checkAiSetup(), []);
|
|
32994
|
+
const projectConfig = useMemo(() => loadConfig$1(), []);
|
|
32995
|
+
const dashboardUrl = useMemo(() => {
|
|
32996
|
+
const projectId = projectConfig?.project?.id;
|
|
32997
|
+
return projectId ? `${DASHBOARD_BASE}/projetos/${projectId}` : DASHBOARD_BASE;
|
|
32998
|
+
}, [projectConfig]);
|
|
32999
|
+
const actions = useMemo(() => {
|
|
33000
|
+
if (!isSuccess$4) return [{
|
|
33001
|
+
id: "close",
|
|
33002
|
+
label: "Fechar"
|
|
33003
|
+
}];
|
|
33004
|
+
const list = [];
|
|
33005
|
+
if (session.deployUrl) list.push({
|
|
33006
|
+
id: "open-project",
|
|
33007
|
+
label: "Abrir projeto"
|
|
33008
|
+
});
|
|
33009
|
+
list.push({
|
|
33010
|
+
id: "open-dashboard",
|
|
33011
|
+
label: "Abrir dashboard"
|
|
33012
|
+
});
|
|
33013
|
+
list.push({
|
|
33014
|
+
id: "close",
|
|
33015
|
+
label: "Fechar"
|
|
33016
|
+
});
|
|
33017
|
+
return list;
|
|
33018
|
+
}, [isSuccess$4, session.deployUrl]);
|
|
33019
|
+
const [actionIdx, setActionIdx] = useState(0);
|
|
33020
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
33021
|
+
const performExit = React.useCallback(async (action) => {
|
|
33022
|
+
try {
|
|
33023
|
+
if (action.id === "open-project" && session.deployUrl) await openBrowser(session.deployUrl);
|
|
33024
|
+
else if (action.id === "open-dashboard") await openBrowser(dashboardUrl);
|
|
33025
|
+
} catch {}
|
|
33026
|
+
store.dismissOutro();
|
|
33027
|
+
process.exit(isSuccess$4 ? 0 : 1);
|
|
33028
|
+
}, [
|
|
33029
|
+
dashboardUrl,
|
|
33030
|
+
isSuccess$4,
|
|
33031
|
+
session.deployUrl,
|
|
33032
|
+
store
|
|
33033
|
+
]);
|
|
31531
33034
|
useGatedInput((_input, key) => {
|
|
31532
|
-
if (key.
|
|
33035
|
+
if (key.upArrow) {
|
|
33036
|
+
setScrollOffset((s) => Math.max(0, s - 1));
|
|
33037
|
+
return;
|
|
33038
|
+
}
|
|
33039
|
+
if (key.downArrow) {
|
|
33040
|
+
setScrollOffset((s) => s + 1);
|
|
33041
|
+
return;
|
|
33042
|
+
}
|
|
33043
|
+
if (key.leftArrow) {
|
|
33044
|
+
setActionIdx((i) => i > 0 ? i - 1 : actions.length - 1);
|
|
33045
|
+
return;
|
|
33046
|
+
}
|
|
33047
|
+
if (key.rightArrow) {
|
|
33048
|
+
setActionIdx((i) => i < actions.length - 1 ? i + 1 : 0);
|
|
33049
|
+
return;
|
|
33050
|
+
}
|
|
33051
|
+
if (key.return) {
|
|
33052
|
+
const action = actions[actionIdx] ?? actions[0];
|
|
33053
|
+
if (action) performExit(action);
|
|
33054
|
+
return;
|
|
33055
|
+
}
|
|
33056
|
+
if (key.escape) {
|
|
31533
33057
|
store.dismissOutro();
|
|
31534
33058
|
process.exit(isSuccess$4 ? 0 : 1);
|
|
31535
33059
|
}
|
|
31536
33060
|
});
|
|
31537
|
-
|
|
31538
|
-
|
|
31539
|
-
|
|
31540
|
-
|
|
31541
|
-
|
|
31542
|
-
|
|
31543
|
-
|
|
31544
|
-
|
|
31545
|
-
|
|
31546
|
-
|
|
31547
|
-
|
|
31548
|
-
|
|
31549
|
-
|
|
31550
|
-
|
|
31551
|
-
|
|
31552
|
-
|
|
31553
|
-
|
|
31554
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
31555
|
-
dimColor: true,
|
|
31556
|
-
children: COPY.outroUrl
|
|
31557
|
-
}), /* @__PURE__ */ jsxs(Text, {
|
|
31558
|
-
color: BRAND_COLOR,
|
|
31559
|
-
bold: true,
|
|
31560
|
-
children: [" ", session.deployUrl]
|
|
31561
|
-
})]
|
|
31562
|
-
})] }) : null,
|
|
31563
|
-
session.agentSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsxs(Box, {
|
|
31564
|
-
marginTop: 1,
|
|
31565
|
-
flexDirection: "column",
|
|
31566
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
31567
|
-
bold: true,
|
|
31568
|
-
color: ACCENT_COLOR,
|
|
31569
|
-
children: "Resumo do deploy:"
|
|
31570
|
-
}), /* @__PURE__ */ jsx(Box, {
|
|
31571
|
-
marginLeft: 2,
|
|
31572
|
-
children: /* @__PURE__ */ jsx(MarkdownText, { children: session.agentSummary })
|
|
31573
|
-
})]
|
|
31574
|
-
})] }) : null,
|
|
31575
|
-
session.agentHelperLines.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsx(Box, {
|
|
31576
|
-
marginTop: 1,
|
|
33061
|
+
const body = isSuccess$4 ? /* @__PURE__ */ jsx(SuccessBody, {
|
|
33062
|
+
store,
|
|
33063
|
+
sepWidth,
|
|
33064
|
+
aiSetup
|
|
33065
|
+
}) : /* @__PURE__ */ jsx(ErrorBody, {
|
|
33066
|
+
store,
|
|
33067
|
+
sepWidth
|
|
33068
|
+
});
|
|
33069
|
+
return /* @__PURE__ */ jsxs(ScreenLayout, {
|
|
33070
|
+
footer: "↑↓ rolar · ←→ escolher ação · Enter confirmar · Esc fechar",
|
|
33071
|
+
children: [/* @__PURE__ */ jsx(Box, {
|
|
33072
|
+
flexDirection: "column",
|
|
33073
|
+
flexGrow: 1,
|
|
33074
|
+
flexShrink: 1,
|
|
33075
|
+
overflow: "hidden",
|
|
33076
|
+
height: Math.max(6, rows - 14),
|
|
33077
|
+
children: /* @__PURE__ */ jsx(Box, {
|
|
31577
33078
|
flexDirection: "column",
|
|
31578
|
-
|
|
31579
|
-
|
|
31580
|
-
|
|
31581
|
-
|
|
31582
|
-
|
|
31583
|
-
|
|
31584
|
-
|
|
31585
|
-
|
|
31586
|
-
|
|
31587
|
-
|
|
31588
|
-
|
|
31589
|
-
|
|
33079
|
+
marginTop: -scrollOffset,
|
|
33080
|
+
children: body
|
|
33081
|
+
})
|
|
33082
|
+
}), /* @__PURE__ */ jsx(ActionBar, {
|
|
33083
|
+
actions,
|
|
33084
|
+
selected: actionIdx
|
|
33085
|
+
})]
|
|
33086
|
+
});
|
|
33087
|
+
}
|
|
33088
|
+
function ActionBar({ actions, selected }) {
|
|
33089
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
33090
|
+
marginTop: 1,
|
|
33091
|
+
gap: 2,
|
|
33092
|
+
children: actions.map((action, i) => {
|
|
33093
|
+
const active$1 = i === selected;
|
|
33094
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
33095
|
+
borderStyle: "single",
|
|
33096
|
+
borderColor: active$1 ? BRAND_COLOR : DIM_COLOR,
|
|
33097
|
+
paddingX: 1,
|
|
33098
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
33099
|
+
color: active$1 ? BRAND_COLOR : void 0,
|
|
33100
|
+
bold: active$1,
|
|
33101
|
+
children: action.label
|
|
31590
33102
|
})
|
|
31591
|
-
}
|
|
31592
|
-
|
|
31593
|
-
|
|
31594
|
-
|
|
31595
|
-
|
|
31596
|
-
|
|
33103
|
+
}, action.id);
|
|
33104
|
+
})
|
|
33105
|
+
});
|
|
33106
|
+
}
|
|
33107
|
+
function SuccessBody({ store, sepWidth, aiSetup }) {
|
|
33108
|
+
const { session } = store;
|
|
33109
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
33110
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
33111
|
+
gap: 1,
|
|
33112
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33113
|
+
color: SUCCESS_COLOR,
|
|
33114
|
+
children: Icons.squareFilled
|
|
33115
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
33116
|
+
color: SUCCESS_COLOR,
|
|
33117
|
+
bold: true,
|
|
33118
|
+
children: COPY.outroSuccess
|
|
33119
|
+
})]
|
|
33120
|
+
}),
|
|
33121
|
+
session.deployUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsxs(Box, {
|
|
33122
|
+
marginTop: 1,
|
|
33123
|
+
flexDirection: "column",
|
|
33124
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33125
|
+
dimColor: true,
|
|
33126
|
+
children: COPY.outroUrl
|
|
33127
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
33128
|
+
color: BRAND_COLOR,
|
|
33129
|
+
bold: true,
|
|
33130
|
+
children: [" ", session.deployUrl]
|
|
33131
|
+
})]
|
|
33132
|
+
})] }) : null,
|
|
33133
|
+
session.githubSetupPhase === "done" ? /* @__PURE__ */ jsxs(Box, {
|
|
33134
|
+
marginTop: 1,
|
|
33135
|
+
gap: 1,
|
|
33136
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33137
|
+
color: SUCCESS_COLOR,
|
|
33138
|
+
children: Icons.completed
|
|
33139
|
+
}), /* @__PURE__ */ jsx(Text, { children: "Deploy automático ativado via GitHub." })]
|
|
33140
|
+
}) : null,
|
|
33141
|
+
session.agentSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsxs(Box, {
|
|
33142
|
+
marginTop: 1,
|
|
33143
|
+
flexDirection: "column",
|
|
33144
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33145
|
+
bold: true,
|
|
33146
|
+
color: ACCENT_COLOR,
|
|
33147
|
+
children: "Resumo do deploy:"
|
|
33148
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
33149
|
+
marginLeft: 2,
|
|
33150
|
+
children: /* @__PURE__ */ jsx(MarkdownText, { children: session.agentSummary })
|
|
33151
|
+
})]
|
|
33152
|
+
})] }) : null,
|
|
33153
|
+
session.agentHelperDiagram ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsx(Box, {
|
|
33154
|
+
marginTop: 1,
|
|
33155
|
+
flexDirection: "column",
|
|
33156
|
+
children: /* @__PURE__ */ jsx(DiagramPane, {
|
|
33157
|
+
diagram: session.agentHelperDiagram,
|
|
33158
|
+
width: sepWidth
|
|
33159
|
+
})
|
|
33160
|
+
})] }) : session.agentHelperLines.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsx(Box, {
|
|
33161
|
+
marginTop: 1,
|
|
33162
|
+
flexDirection: "column",
|
|
33163
|
+
children: session.agentHelperLines.map((line, i) => {
|
|
33164
|
+
if (i === 0 && line.trim()) return /* @__PURE__ */ jsx(Text, {
|
|
31597
33165
|
bold: true,
|
|
31598
|
-
|
|
31599
|
-
|
|
33166
|
+
color: ACCENT_COLOR,
|
|
33167
|
+
wrap: "truncate",
|
|
33168
|
+
children: line
|
|
33169
|
+
}, i);
|
|
33170
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
31600
33171
|
color: DIM_COLOR,
|
|
31601
|
-
|
|
31602
|
-
|
|
31603
|
-
|
|
31604
|
-
|
|
31605
|
-
|
|
31606
|
-
|
|
31607
|
-
|
|
31608
|
-
|
|
31609
|
-
|
|
31610
|
-
|
|
31611
|
-
|
|
33172
|
+
wrap: "truncate",
|
|
33173
|
+
children: line
|
|
33174
|
+
}, i);
|
|
33175
|
+
})
|
|
33176
|
+
})] }) : null,
|
|
33177
|
+
/* @__PURE__ */ jsx(Separator, { width: sepWidth }),
|
|
33178
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
33179
|
+
marginTop: 1,
|
|
33180
|
+
flexDirection: "column",
|
|
33181
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33182
|
+
bold: true,
|
|
33183
|
+
children: COPY.outroNextSteps
|
|
33184
|
+
}), COPY.outroNextStepsList.map((step, i) => /* @__PURE__ */ jsxs(Text, {
|
|
33185
|
+
color: DIM_COLOR,
|
|
31612
33186
|
children: [
|
|
31613
|
-
|
|
31614
|
-
|
|
31615
|
-
|
|
31616
|
-
|
|
31617
|
-
}),
|
|
31618
|
-
!aiSetup.mcpInstalled ? /* @__PURE__ */ jsxs(Text, {
|
|
31619
|
-
color: DIM_COLOR,
|
|
31620
|
-
children: [
|
|
31621
|
-
" ",
|
|
31622
|
-
Icons.bullet,
|
|
31623
|
-
" ",
|
|
31624
|
-
COPY.outroAiSetupMcp
|
|
31625
|
-
]
|
|
31626
|
-
}) : null,
|
|
31627
|
-
!aiSetup.skillsInstalled ? /* @__PURE__ */ jsxs(Text, {
|
|
31628
|
-
color: DIM_COLOR,
|
|
31629
|
-
children: [
|
|
31630
|
-
" ",
|
|
31631
|
-
Icons.bullet,
|
|
31632
|
-
" ",
|
|
31633
|
-
COPY.outroAiSetupSkills
|
|
31634
|
-
]
|
|
31635
|
-
}) : null
|
|
33187
|
+
" ",
|
|
33188
|
+
i + 1,
|
|
33189
|
+
". ",
|
|
33190
|
+
step
|
|
31636
33191
|
]
|
|
31637
|
-
})]
|
|
31638
|
-
|
|
31639
|
-
|
|
31640
|
-
|
|
31641
|
-
|
|
31642
|
-
|
|
31643
|
-
|
|
31644
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
31645
|
-
gap: 1,
|
|
31646
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
31647
|
-
color: ERROR_COLOR,
|
|
31648
|
-
children: Icons.error
|
|
31649
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
31650
|
-
color: ERROR_COLOR,
|
|
31651
|
-
bold: true,
|
|
31652
|
-
children: COPY.outroError
|
|
31653
|
-
})]
|
|
31654
|
-
}),
|
|
31655
|
-
session.agentError ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsxs(Box, {
|
|
31656
|
-
marginTop: 1,
|
|
31657
|
-
flexDirection: "column",
|
|
31658
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
31659
|
-
bold: true,
|
|
31660
|
-
color: ERROR_COLOR,
|
|
31661
|
-
children: "Motivo:"
|
|
31662
|
-
}), /* @__PURE__ */ jsx(Box, {
|
|
31663
|
-
marginLeft: 2,
|
|
31664
|
-
flexDirection: "column",
|
|
31665
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
31666
|
-
color: ERROR_COLOR,
|
|
31667
|
-
wrap: "wrap",
|
|
31668
|
-
children: session.agentError
|
|
31669
|
-
})
|
|
31670
|
-
})]
|
|
31671
|
-
})] }) : null,
|
|
31672
|
-
recentOutput.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsxs(Box, {
|
|
31673
|
-
marginTop: 1,
|
|
31674
|
-
flexDirection: "column",
|
|
31675
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
33192
|
+
}, i))]
|
|
33193
|
+
}),
|
|
33194
|
+
!aiSetup.mcpInstalled || !aiSetup.skillsInstalled ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsxs(Box, {
|
|
33195
|
+
marginTop: 1,
|
|
33196
|
+
flexDirection: "column",
|
|
33197
|
+
children: [
|
|
33198
|
+
/* @__PURE__ */ jsx(Text, {
|
|
31676
33199
|
bold: true,
|
|
31677
33200
|
color: ACCENT_COLOR,
|
|
31678
|
-
children: "
|
|
31679
|
-
}),
|
|
33201
|
+
children: "Integração com IA:"
|
|
33202
|
+
}),
|
|
33203
|
+
!aiSetup.mcpInstalled ? /* @__PURE__ */ jsxs(Text, {
|
|
31680
33204
|
color: DIM_COLOR,
|
|
31681
|
-
wrap: "wrap",
|
|
31682
|
-
children: [" ", line]
|
|
31683
|
-
}, i))]
|
|
31684
|
-
})] }) : null,
|
|
31685
|
-
session.retryCount > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
31686
|
-
marginTop: 1,
|
|
31687
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
31688
|
-
dimColor: true,
|
|
31689
33205
|
children: [
|
|
31690
|
-
"
|
|
31691
|
-
|
|
31692
|
-
"
|
|
31693
|
-
|
|
31694
|
-
" antes de falhar."
|
|
33206
|
+
" ",
|
|
33207
|
+
Icons.bullet,
|
|
33208
|
+
" ",
|
|
33209
|
+
COPY.outroAiSetupMcp
|
|
31695
33210
|
]
|
|
31696
|
-
})
|
|
31697
|
-
|
|
31698
|
-
/* @__PURE__ */ jsx(Separator, { width: sepWidth }),
|
|
31699
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
31700
|
-
marginTop: 1,
|
|
31701
|
-
flexDirection: "column",
|
|
31702
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
31703
|
-
bold: true,
|
|
31704
|
-
children: "Próximos passos:"
|
|
31705
|
-
}), COPY.outroErrorHints.map((hint, i) => /* @__PURE__ */ jsxs(Text, {
|
|
33211
|
+
}) : null,
|
|
33212
|
+
!aiSetup.skillsInstalled ? /* @__PURE__ */ jsxs(Text, {
|
|
31706
33213
|
color: DIM_COLOR,
|
|
31707
33214
|
children: [
|
|
31708
33215
|
" ",
|
|
31709
33216
|
Icons.bullet,
|
|
31710
33217
|
" ",
|
|
31711
|
-
|
|
33218
|
+
COPY.outroAiSetupSkills
|
|
31712
33219
|
]
|
|
31713
|
-
}
|
|
33220
|
+
}) : null
|
|
33221
|
+
]
|
|
33222
|
+
})] }) : null
|
|
33223
|
+
] });
|
|
33224
|
+
}
|
|
33225
|
+
function ErrorBody({ store, sepWidth }) {
|
|
33226
|
+
const { session } = store;
|
|
33227
|
+
const recentOutput = session.agentOutputLines.slice(-8);
|
|
33228
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
33229
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
33230
|
+
gap: 1,
|
|
33231
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33232
|
+
color: ERROR_COLOR,
|
|
33233
|
+
children: Icons.error
|
|
33234
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
33235
|
+
color: ERROR_COLOR,
|
|
33236
|
+
bold: true,
|
|
33237
|
+
children: COPY.outroError
|
|
33238
|
+
})]
|
|
33239
|
+
}),
|
|
33240
|
+
session.agentError ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsxs(Box, {
|
|
33241
|
+
marginTop: 1,
|
|
33242
|
+
flexDirection: "column",
|
|
33243
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33244
|
+
bold: true,
|
|
33245
|
+
color: ERROR_COLOR,
|
|
33246
|
+
children: "Motivo:"
|
|
33247
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
33248
|
+
marginLeft: 2,
|
|
33249
|
+
flexDirection: "column",
|
|
33250
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
33251
|
+
color: ERROR_COLOR,
|
|
33252
|
+
wrap: "wrap",
|
|
33253
|
+
children: session.agentError
|
|
33254
|
+
})
|
|
33255
|
+
})]
|
|
33256
|
+
})] }) : null,
|
|
33257
|
+
recentOutput.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, { width: sepWidth }), /* @__PURE__ */ jsxs(Box, {
|
|
33258
|
+
marginTop: 1,
|
|
33259
|
+
flexDirection: "column",
|
|
33260
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33261
|
+
bold: true,
|
|
33262
|
+
color: ACCENT_COLOR,
|
|
33263
|
+
children: "Últimas ações do agente:"
|
|
33264
|
+
}), recentOutput.map((line, i) => /* @__PURE__ */ jsxs(Text, {
|
|
33265
|
+
color: DIM_COLOR,
|
|
33266
|
+
wrap: "wrap",
|
|
33267
|
+
children: [" ", line]
|
|
33268
|
+
}, i))]
|
|
33269
|
+
})] }) : null,
|
|
33270
|
+
session.retryCount > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
33271
|
+
marginTop: 1,
|
|
33272
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
33273
|
+
dimColor: true,
|
|
33274
|
+
children: [
|
|
33275
|
+
"O agente tentou ",
|
|
33276
|
+
session.retryCount + 1,
|
|
33277
|
+
" vez",
|
|
33278
|
+
session.retryCount > 0 ? "es" : "",
|
|
33279
|
+
" antes de falhar."
|
|
33280
|
+
]
|
|
31714
33281
|
})
|
|
31715
|
-
|
|
31716
|
-
|
|
33282
|
+
}) : null,
|
|
33283
|
+
/* @__PURE__ */ jsx(Separator, { width: sepWidth }),
|
|
33284
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
33285
|
+
marginTop: 1,
|
|
33286
|
+
flexDirection: "column",
|
|
33287
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33288
|
+
bold: true,
|
|
33289
|
+
children: "Próximos passos:"
|
|
33290
|
+
}), COPY.outroErrorHints.map((hint, i) => /* @__PURE__ */ jsxs(Text, {
|
|
33291
|
+
color: DIM_COLOR,
|
|
33292
|
+
children: [
|
|
33293
|
+
" ",
|
|
33294
|
+
Icons.bullet,
|
|
33295
|
+
" ",
|
|
33296
|
+
hint
|
|
33297
|
+
]
|
|
33298
|
+
}, i))]
|
|
33299
|
+
})
|
|
33300
|
+
] });
|
|
31717
33301
|
}
|
|
31718
33302
|
function Separator({ width }) {
|
|
31719
33303
|
return /* @__PURE__ */ jsx(Box, {
|
|
@@ -31760,25 +33344,58 @@ function ErrorOverlay({ message, onDismiss }) {
|
|
|
31760
33344
|
|
|
31761
33345
|
//#endregion
|
|
31762
33346
|
//#region src/wizard/ui/overlays/ConfirmExitOverlay.tsx
|
|
33347
|
+
/**
|
|
33348
|
+
* ConfirmExitOverlay — Alert-style confirmation for Ctrl+C.
|
|
33349
|
+
*
|
|
33350
|
+
* Rendered in the absolute-positioned overlay slot in App.tsx, just like
|
|
33351
|
+
* PromptOverlay: pulsing border, own `useInput` handler, screen remains
|
|
33352
|
+
* visible behind it while `InputFocusProvider` gates the base layer.
|
|
33353
|
+
*/
|
|
33354
|
+
function useOverlayWidth$1() {
|
|
33355
|
+
const [columns] = useStdoutDimensions();
|
|
33356
|
+
return Math.max(30, Math.min(60, columns - 4));
|
|
33357
|
+
}
|
|
31763
33358
|
function ConfirmExitOverlay({ onConfirm, onCancel }) {
|
|
31764
33359
|
const [selected, setSelected] = useState(1);
|
|
31765
|
-
|
|
31766
|
-
|
|
31767
|
-
|
|
31768
|
-
|
|
33360
|
+
const [pulse, setPulse] = useState(true);
|
|
33361
|
+
const width = useOverlayWidth$1();
|
|
33362
|
+
useEffect(() => {
|
|
33363
|
+
const timer = setInterval(() => setPulse((p) => !p), 800);
|
|
33364
|
+
return () => clearInterval(timer);
|
|
33365
|
+
}, []);
|
|
33366
|
+
useInput((_input, key) => {
|
|
33367
|
+
if (key.upArrow || key.downArrow) {
|
|
33368
|
+
setSelected((s) => s === 0 ? 1 : 0);
|
|
33369
|
+
return;
|
|
33370
|
+
}
|
|
33371
|
+
if (key.return) {
|
|
33372
|
+
if (selected === 0) onConfirm();
|
|
33373
|
+
else onCancel();
|
|
33374
|
+
return;
|
|
33375
|
+
}
|
|
31769
33376
|
if (key.escape) onCancel();
|
|
31770
33377
|
});
|
|
33378
|
+
const borderColor = pulse ? ERROR_COLOR : BRAND_COLOR;
|
|
31771
33379
|
const options = [COPY.confirmExitYes, COPY.confirmExitNo];
|
|
31772
33380
|
return /* @__PURE__ */ jsxs(Box, {
|
|
31773
33381
|
flexDirection: "column",
|
|
31774
|
-
borderStyle: "
|
|
31775
|
-
borderColor
|
|
33382
|
+
borderStyle: "bold",
|
|
33383
|
+
borderColor,
|
|
33384
|
+
backgroundColor: "black",
|
|
31776
33385
|
paddingX: 2,
|
|
31777
33386
|
paddingY: 1,
|
|
33387
|
+
width,
|
|
31778
33388
|
children: [
|
|
31779
|
-
/* @__PURE__ */
|
|
31780
|
-
|
|
31781
|
-
children:
|
|
33389
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
33390
|
+
gap: 1,
|
|
33391
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33392
|
+
color: ERROR_COLOR,
|
|
33393
|
+
bold: true,
|
|
33394
|
+
children: Icons.diamond
|
|
33395
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
33396
|
+
bold: true,
|
|
33397
|
+
children: COPY.confirmExitTitle
|
|
33398
|
+
})]
|
|
31782
33399
|
}),
|
|
31783
33400
|
/* @__PURE__ */ jsx(Box, {
|
|
31784
33401
|
marginTop: 1,
|
|
@@ -31791,12 +33408,19 @@ function ConfirmExitOverlay({ onConfirm, onCancel }) {
|
|
|
31791
33408
|
gap: 1,
|
|
31792
33409
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
31793
33410
|
color: i === selected ? BRAND_COLOR : DIM_COLOR,
|
|
31794
|
-
children: i === selected ?
|
|
33411
|
+
children: i === selected ? Icons.triangleRight : " "
|
|
31795
33412
|
}), /* @__PURE__ */ jsx(Text, {
|
|
31796
33413
|
bold: i === selected,
|
|
31797
33414
|
children: opt
|
|
31798
33415
|
})]
|
|
31799
33416
|
}, i))
|
|
33417
|
+
}),
|
|
33418
|
+
/* @__PURE__ */ jsx(Box, {
|
|
33419
|
+
marginTop: 1,
|
|
33420
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
33421
|
+
dimColor: true,
|
|
33422
|
+
children: "↑↓ escolher · Enter confirmar · Esc cancelar"
|
|
33423
|
+
})
|
|
31800
33424
|
})
|
|
31801
33425
|
]
|
|
31802
33426
|
});
|
|
@@ -31840,14 +33464,8 @@ function PromptOverlay({ prompt: prompt$1, onSubmit, onSkip }) {
|
|
|
31840
33464
|
}
|
|
31841
33465
|
function TextPrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
31842
33466
|
const [value, setValue] = useState("");
|
|
31843
|
-
const [mode, setMode] = useState("ask");
|
|
31844
33467
|
const width = useOverlayWidth();
|
|
31845
33468
|
useInput((input, key) => {
|
|
31846
|
-
if (mode === "ask") {
|
|
31847
|
-
if (input === "1" || key.return) setMode("input");
|
|
31848
|
-
if (input === "2" || key.escape) onSkip();
|
|
31849
|
-
return;
|
|
31850
|
-
}
|
|
31851
33469
|
if (key.return && value.trim()) {
|
|
31852
33470
|
onSubmit(value.trim());
|
|
31853
33471
|
return;
|
|
@@ -31866,6 +33484,7 @@ function TextPrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
31866
33484
|
flexDirection: "column",
|
|
31867
33485
|
borderStyle: "bold",
|
|
31868
33486
|
borderColor: pulse ? BRAND_COLOR : ACCENT_COLOR,
|
|
33487
|
+
backgroundColor: "black",
|
|
31869
33488
|
paddingX: 2,
|
|
31870
33489
|
paddingY: 1,
|
|
31871
33490
|
width,
|
|
@@ -31912,17 +33531,7 @@ function TextPrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
31912
33531
|
]
|
|
31913
33532
|
})
|
|
31914
33533
|
}) : null,
|
|
31915
|
-
|
|
31916
|
-
marginTop: 1,
|
|
31917
|
-
flexDirection: "column",
|
|
31918
|
-
children: [/* @__PURE__ */ jsx(Text, {
|
|
31919
|
-
color: SUCCESS_COLOR,
|
|
31920
|
-
children: "[1] Inserir valor agora"
|
|
31921
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
31922
|
-
color: DIM_COLOR,
|
|
31923
|
-
children: "[2] Configurar depois manualmente"
|
|
31924
|
-
})]
|
|
31925
|
-
}) : /* @__PURE__ */ jsxs(Box, {
|
|
33534
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
31926
33535
|
marginTop: 1,
|
|
31927
33536
|
flexDirection: "column",
|
|
31928
33537
|
children: [/* @__PURE__ */ jsxs(Box, {
|
|
@@ -31956,6 +33565,7 @@ function ConfirmPrompt({ prompt: prompt$1, pulse, onSubmit }) {
|
|
|
31956
33565
|
flexDirection: "column",
|
|
31957
33566
|
borderStyle: "bold",
|
|
31958
33567
|
borderColor: pulse ? BRAND_COLOR : ACCENT_COLOR,
|
|
33568
|
+
backgroundColor: "black",
|
|
31959
33569
|
paddingX: 2,
|
|
31960
33570
|
paddingY: 1,
|
|
31961
33571
|
width,
|
|
@@ -31995,17 +33605,40 @@ function ConfirmPrompt({ prompt: prompt$1, pulse, onSubmit }) {
|
|
|
31995
33605
|
function ChoicePrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
31996
33606
|
const options = prompt$1.options ?? [];
|
|
31997
33607
|
const [selected, setSelected] = useState(0);
|
|
33608
|
+
const [typed, setTyped] = useState("");
|
|
31998
33609
|
const width = useOverlayWidth();
|
|
31999
|
-
useInput((
|
|
32000
|
-
if (key.upArrow)
|
|
32001
|
-
|
|
32002
|
-
|
|
32003
|
-
|
|
33610
|
+
useInput((input, key) => {
|
|
33611
|
+
if (key.upArrow) {
|
|
33612
|
+
setSelected((s) => Math.max(0, s - 1));
|
|
33613
|
+
return;
|
|
33614
|
+
}
|
|
33615
|
+
if (key.downArrow) {
|
|
33616
|
+
setSelected((s) => Math.min(options.length - 1, s + 1));
|
|
33617
|
+
return;
|
|
33618
|
+
}
|
|
33619
|
+
if (key.return) {
|
|
33620
|
+
const trimmed = typed.trim();
|
|
33621
|
+
if (trimmed) onSubmit(trimmed);
|
|
33622
|
+
else onSubmit(options[selected] ?? "");
|
|
33623
|
+
return;
|
|
33624
|
+
}
|
|
33625
|
+
if (key.escape) {
|
|
33626
|
+
onSkip();
|
|
33627
|
+
return;
|
|
33628
|
+
}
|
|
33629
|
+
if (key.backspace || key.delete) {
|
|
33630
|
+
setTyped((v) => v.slice(0, -1));
|
|
33631
|
+
return;
|
|
33632
|
+
}
|
|
33633
|
+
if (input && !key.ctrl && !key.meta) setTyped((v) => v + input);
|
|
32004
33634
|
});
|
|
33635
|
+
const borderColor = pulse ? BRAND_COLOR : ACCENT_COLOR;
|
|
33636
|
+
const trimmedTyped = typed.trim();
|
|
32005
33637
|
return /* @__PURE__ */ jsxs(Box, {
|
|
32006
33638
|
flexDirection: "column",
|
|
32007
33639
|
borderStyle: "bold",
|
|
32008
|
-
borderColor
|
|
33640
|
+
borderColor,
|
|
33641
|
+
backgroundColor: "black",
|
|
32009
33642
|
paddingX: 2,
|
|
32010
33643
|
paddingY: 1,
|
|
32011
33644
|
width,
|
|
@@ -32032,22 +33665,43 @@ function ChoicePrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
32032
33665
|
/* @__PURE__ */ jsx(Box, {
|
|
32033
33666
|
marginTop: 1,
|
|
32034
33667
|
flexDirection: "column",
|
|
32035
|
-
children: options.map((opt, i) =>
|
|
33668
|
+
children: options.map((opt, i) => {
|
|
33669
|
+
const active$1 = !trimmedTyped && i === selected;
|
|
33670
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
33671
|
+
gap: 1,
|
|
33672
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
33673
|
+
color: active$1 ? BRAND_COLOR : DIM_COLOR,
|
|
33674
|
+
children: active$1 ? Icons.triangleRight : " "
|
|
33675
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
33676
|
+
bold: active$1,
|
|
33677
|
+
dimColor: Boolean(trimmedTyped),
|
|
33678
|
+
children: opt
|
|
33679
|
+
})]
|
|
33680
|
+
}, i);
|
|
33681
|
+
})
|
|
33682
|
+
}),
|
|
33683
|
+
/* @__PURE__ */ jsx(Box, {
|
|
33684
|
+
marginTop: 1,
|
|
33685
|
+
flexDirection: "column",
|
|
33686
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
32036
33687
|
gap: 1,
|
|
32037
33688
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
32038
|
-
color:
|
|
32039
|
-
children:
|
|
33689
|
+
color: trimmedTyped ? BRAND_COLOR : DIM_COLOR,
|
|
33690
|
+
children: Icons.triangleRight
|
|
33691
|
+
}), /* @__PURE__ */ jsxs(Text, { children: [typed || /* @__PURE__ */ jsx(Text, {
|
|
33692
|
+
color: DIM_COLOR,
|
|
33693
|
+
children: "ou digite uma resposta livre"
|
|
32040
33694
|
}), /* @__PURE__ */ jsx(Text, {
|
|
32041
|
-
|
|
32042
|
-
children:
|
|
32043
|
-
})]
|
|
32044
|
-
}
|
|
33695
|
+
color: DIM_COLOR,
|
|
33696
|
+
children: "▌"
|
|
33697
|
+
})] })]
|
|
33698
|
+
})
|
|
32045
33699
|
}),
|
|
32046
33700
|
/* @__PURE__ */ jsx(Box, {
|
|
32047
33701
|
marginTop: 1,
|
|
32048
33702
|
children: /* @__PURE__ */ jsx(Text, {
|
|
32049
33703
|
dimColor: true,
|
|
32050
|
-
children: "Esc
|
|
33704
|
+
children: "↑↓ escolher · Enter confirmar · Esc pular"
|
|
32051
33705
|
})
|
|
32052
33706
|
})
|
|
32053
33707
|
]
|
|
@@ -32056,9 +33710,9 @@ function ChoicePrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
32056
33710
|
|
|
32057
33711
|
//#endregion
|
|
32058
33712
|
//#region src/wizard/ui/App.tsx
|
|
32059
|
-
function App({ store, router, cwd }) {
|
|
33713
|
+
function App({ store, router, cwd, onExit: onExit$2 }) {
|
|
32060
33714
|
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
32061
|
-
const [, rows] = useStdoutDimensions();
|
|
33715
|
+
const [columns, rows] = useStdoutDimensions();
|
|
32062
33716
|
const { session } = store;
|
|
32063
33717
|
const writePromptResponse = useCallback((value, skipped) => {
|
|
32064
33718
|
const responsePath = join(cwd, ".veloz-wizard-response.json");
|
|
@@ -32080,14 +33734,7 @@ function App({ store, router, cwd }) {
|
|
|
32080
33734
|
]);
|
|
32081
33735
|
const overlay = router.activeOverlay;
|
|
32082
33736
|
let baseLayer;
|
|
32083
|
-
if (overlay === Overlay.
|
|
32084
|
-
onConfirm: () => {
|
|
32085
|
-
router.popOverlay();
|
|
32086
|
-
process.exit(130);
|
|
32087
|
-
},
|
|
32088
|
-
onCancel: () => router.popOverlay()
|
|
32089
|
-
});
|
|
32090
|
-
else if (overlay === Overlay.Error) baseLayer = /* @__PURE__ */ jsx(ErrorOverlay, {
|
|
33737
|
+
if (overlay === Overlay.Error) baseLayer = /* @__PURE__ */ jsx(ErrorOverlay, {
|
|
32091
33738
|
message: session.agentError ?? "Erro desconhecido",
|
|
32092
33739
|
onDismiss: () => router.popOverlay()
|
|
32093
33740
|
});
|
|
@@ -32104,17 +33751,34 @@ function App({ store, router, cwd }) {
|
|
|
32104
33751
|
case Screen.Run:
|
|
32105
33752
|
baseLayer = /* @__PURE__ */ jsx(RunScreen, { store });
|
|
32106
33753
|
break;
|
|
33754
|
+
case Screen.GithubSetup:
|
|
33755
|
+
baseLayer = /* @__PURE__ */ jsx(GithubSetupScreen, { store });
|
|
33756
|
+
break;
|
|
33757
|
+
case Screen.AiSetup:
|
|
33758
|
+
baseLayer = /* @__PURE__ */ jsx(AiSetupScreen, { store });
|
|
33759
|
+
break;
|
|
32107
33760
|
case Screen.Outro:
|
|
32108
33761
|
baseLayer = /* @__PURE__ */ jsx(OutroScreen, { store });
|
|
32109
33762
|
break;
|
|
32110
33763
|
default: baseLayer = /* @__PURE__ */ jsx(IntroScreen, { store });
|
|
32111
33764
|
}
|
|
32112
33765
|
const prompt$1 = session.agentPrompt;
|
|
33766
|
+
const floatingOverlay = overlay === Overlay.ConfirmExit ? /* @__PURE__ */ jsx(ConfirmExitOverlay, {
|
|
33767
|
+
onConfirm: () => {
|
|
33768
|
+
onExit$2("user confirmed exit");
|
|
33769
|
+
},
|
|
33770
|
+
onCancel: () => router.popOverlay()
|
|
33771
|
+
}) : prompt$1 ? /* @__PURE__ */ jsx(PromptOverlay, {
|
|
33772
|
+
prompt: prompt$1,
|
|
33773
|
+
onSubmit: (value) => writePromptResponse(value, false),
|
|
33774
|
+
onSkip: () => writePromptResponse("", true)
|
|
33775
|
+
}) : null;
|
|
32113
33776
|
return /* @__PURE__ */ jsxs(Box, {
|
|
32114
33777
|
flexDirection: "column",
|
|
32115
33778
|
height: rows,
|
|
33779
|
+
width: columns,
|
|
32116
33780
|
children: [/* @__PURE__ */ jsx(InputFocusProvider, {
|
|
32117
|
-
active: !
|
|
33781
|
+
active: !floatingOverlay,
|
|
32118
33782
|
children: /* @__PURE__ */ jsx(Box, {
|
|
32119
33783
|
flexDirection: "column",
|
|
32120
33784
|
flexGrow: 1,
|
|
@@ -32123,15 +33787,13 @@ function App({ store, router, cwd }) {
|
|
|
32123
33787
|
overflow: "hidden",
|
|
32124
33788
|
children: baseLayer
|
|
32125
33789
|
})
|
|
32126
|
-
}),
|
|
32127
|
-
|
|
32128
|
-
|
|
32129
|
-
|
|
32130
|
-
|
|
32131
|
-
|
|
32132
|
-
|
|
32133
|
-
onSkip: () => writePromptResponse("", true)
|
|
32134
|
-
})
|
|
33790
|
+
}), floatingOverlay ? /* @__PURE__ */ jsx(Box, {
|
|
33791
|
+
position: "absolute",
|
|
33792
|
+
width: columns,
|
|
33793
|
+
height: rows,
|
|
33794
|
+
alignItems: "center",
|
|
33795
|
+
justifyContent: "center",
|
|
33796
|
+
children: floatingOverlay
|
|
32135
33797
|
}) : null]
|
|
32136
33798
|
});
|
|
32137
33799
|
}
|
|
@@ -32239,6 +33901,15 @@ var WizardStore = class {
|
|
|
32239
33901
|
this.$session.setKey("authPhase", "browser");
|
|
32240
33902
|
this.emitChange();
|
|
32241
33903
|
}
|
|
33904
|
+
/** Show a banner on AuthScreen explaining why we're re-running device auth. */
|
|
33905
|
+
setReauthNotice(message) {
|
|
33906
|
+
this.$session.setKey("reauthNotice", message);
|
|
33907
|
+
this.emitChange();
|
|
33908
|
+
}
|
|
33909
|
+
clearReauthNotice() {
|
|
33910
|
+
this.$session.setKey("reauthNotice", null);
|
|
33911
|
+
this.emitChange();
|
|
33912
|
+
}
|
|
32242
33913
|
setAvailableOrgs(orgs) {
|
|
32243
33914
|
this.$session.setKey("availableOrgs", orgs);
|
|
32244
33915
|
this.$session.setKey("authPhase", "org-select");
|
|
@@ -32362,6 +34033,7 @@ var WizardStore = class {
|
|
|
32362
34033
|
/** Set agent helper content (replaces previous content). */
|
|
32363
34034
|
setAgentHelper(lines) {
|
|
32364
34035
|
this.$session.setKey("agentHelperLines", lines);
|
|
34036
|
+
this.$session.setKey("agentHelperDiagram", null);
|
|
32365
34037
|
this.emitChange();
|
|
32366
34038
|
}
|
|
32367
34039
|
/** Append a line to agent helper content. */
|
|
@@ -32370,6 +34042,12 @@ var WizardStore = class {
|
|
|
32370
34042
|
this.$session.setKey("agentHelperLines", lines);
|
|
32371
34043
|
this.emitChange();
|
|
32372
34044
|
}
|
|
34045
|
+
/** Set structured deploy diagram (replaces previous diagram and lines). */
|
|
34046
|
+
setAgentHelperDiagram(diagram) {
|
|
34047
|
+
this.$session.setKey("agentHelperDiagram", diagram);
|
|
34048
|
+
this.$session.setKey("agentHelperLines", []);
|
|
34049
|
+
this.emitChange();
|
|
34050
|
+
}
|
|
32373
34051
|
/** Show a prompt overlay to the user. */
|
|
32374
34052
|
setAgentPrompt(prompt$1) {
|
|
32375
34053
|
getSessionLogger().logStoreEvent("setAgentPrompt", {
|
|
@@ -32412,46 +34090,60 @@ var WizardStore = class {
|
|
|
32412
34090
|
this.$session.setKey("buildDeploymentId", id);
|
|
32413
34091
|
this.emitChange();
|
|
32414
34092
|
}
|
|
34093
|
+
setGithubSetupPhase(phase) {
|
|
34094
|
+
getSessionLogger().logStoreEvent("githubSetupPhase", {
|
|
34095
|
+
from: this.session.githubSetupPhase,
|
|
34096
|
+
to: phase
|
|
34097
|
+
});
|
|
34098
|
+
this.$session.setKey("githubSetupPhase", phase);
|
|
34099
|
+
this.emitChange();
|
|
34100
|
+
}
|
|
34101
|
+
setGithubRepoInfo(owner, repo) {
|
|
34102
|
+
this.$session.setKey("githubRepoOwner", owner);
|
|
34103
|
+
this.$session.setKey("githubRepoName", repo);
|
|
34104
|
+
this.emitChange();
|
|
34105
|
+
}
|
|
34106
|
+
setGithubInstallUrl(url) {
|
|
34107
|
+
this.$session.setKey("githubInstallUrl", url);
|
|
34108
|
+
this.emitChange();
|
|
34109
|
+
}
|
|
34110
|
+
failGithubSetup(error) {
|
|
34111
|
+
getSessionLogger().logStoreEvent("failGithubSetup", { error });
|
|
34112
|
+
this.$session.setKey("githubSetupError", error);
|
|
34113
|
+
this.$session.setKey("githubSetupPhase", "error");
|
|
34114
|
+
this.emitChange();
|
|
34115
|
+
}
|
|
34116
|
+
setAiSetupNeeds(needs) {
|
|
34117
|
+
this.$session.setKey("aiSetupNeedsMcp", needs.mcp);
|
|
34118
|
+
this.$session.setKey("aiSetupNeedsSkills", needs.skills);
|
|
34119
|
+
this.$session.setKey("aiSetupMcpStatus", needs.mcp ? "pending" : "skipped");
|
|
34120
|
+
this.$session.setKey("aiSetupSkillsStatus", needs.skills ? "pending" : "skipped");
|
|
34121
|
+
this.emitChange();
|
|
34122
|
+
}
|
|
34123
|
+
setAiSetupPhase(phase) {
|
|
34124
|
+
getSessionLogger().logStoreEvent("aiSetupPhase", {
|
|
34125
|
+
from: this.session.aiSetupPhase,
|
|
34126
|
+
to: phase
|
|
34127
|
+
});
|
|
34128
|
+
this.$session.setKey("aiSetupPhase", phase);
|
|
34129
|
+
this.emitChange();
|
|
34130
|
+
}
|
|
34131
|
+
setAiSetupMcpStatus(status) {
|
|
34132
|
+
this.$session.setKey("aiSetupMcpStatus", status);
|
|
34133
|
+
this.emitChange();
|
|
34134
|
+
}
|
|
34135
|
+
setAiSetupSkillsStatus(status) {
|
|
34136
|
+
this.$session.setKey("aiSetupSkillsStatus", status);
|
|
34137
|
+
this.emitChange();
|
|
34138
|
+
}
|
|
34139
|
+
failAiSetup(error) {
|
|
34140
|
+
getSessionLogger().logStoreEvent("failAiSetup", { error });
|
|
34141
|
+
this.$session.setKey("aiSetupError", error);
|
|
34142
|
+
this.$session.setKey("aiSetupPhase", "error");
|
|
34143
|
+
this.emitChange();
|
|
34144
|
+
}
|
|
32415
34145
|
};
|
|
32416
34146
|
|
|
32417
|
-
//#endregion
|
|
32418
|
-
//#region src/wizard/start-tui.ts
|
|
32419
|
-
/**
|
|
32420
|
-
* Start the Ink TUI renderer for the wizard.
|
|
32421
|
-
* Forces dark background, creates store + router, renders App.
|
|
32422
|
-
*/
|
|
32423
|
-
function startTUI(cwd) {
|
|
32424
|
-
if (process.stdout.isTTY) process.stdout.write("\x1B[48;2;0;0;0m\x1B[2J\x1B[H");
|
|
32425
|
-
const store = new WizardStore();
|
|
32426
|
-
const router = new WizardRouter();
|
|
32427
|
-
const { unmount } = render(createElement(App, {
|
|
32428
|
-
store,
|
|
32429
|
-
router,
|
|
32430
|
-
cwd: cwd ?? process.cwd()
|
|
32431
|
-
}));
|
|
32432
|
-
const cleanup = () => {
|
|
32433
|
-
if (process.stdout.isTTY) process.stdout.write("\x1B[0m\x1B[2J\x1B[H");
|
|
32434
|
-
};
|
|
32435
|
-
process.on("exit", cleanup);
|
|
32436
|
-
let sigintCount = 0;
|
|
32437
|
-
process.on("SIGINT", () => {
|
|
32438
|
-
sigintCount++;
|
|
32439
|
-
const logger = getSessionLogger();
|
|
32440
|
-
logger.log("signal", "SIGINT received", { count: sigintCount });
|
|
32441
|
-
if (sigintCount >= 2) {
|
|
32442
|
-
logger.finalize("abort", { error: "user double-SIGINT" });
|
|
32443
|
-
process.exit(130);
|
|
32444
|
-
}
|
|
32445
|
-
router.pushOverlay(Overlay.ConfirmExit);
|
|
32446
|
-
});
|
|
32447
|
-
return {
|
|
32448
|
-
store,
|
|
32449
|
-
router,
|
|
32450
|
-
unmount,
|
|
32451
|
-
waitForIntro: () => store.getGate("intro", (s) => s.introConfirmed)
|
|
32452
|
-
};
|
|
32453
|
-
}
|
|
32454
|
-
|
|
32455
34147
|
//#endregion
|
|
32456
34148
|
//#region src/wizard/upload-telemetry.ts
|
|
32457
34149
|
/**
|
|
@@ -32482,23 +34174,15 @@ function contentTypeFor(filename) {
|
|
|
32482
34174
|
return CONTENT_TYPES[filename] ?? "application/octet-stream";
|
|
32483
34175
|
}
|
|
32484
34176
|
async function uploadInitTelemetry(sessionDir) {
|
|
32485
|
-
if (process.env.VELOZ_NO_TELEMETRY === "1")
|
|
32486
|
-
process.stderr.write("[veloz init] telemetria desativada (VELOZ_NO_TELEMETRY=1)\n");
|
|
32487
|
-
return;
|
|
32488
|
-
}
|
|
34177
|
+
if (process.env.VELOZ_NO_TELEMETRY === "1") return;
|
|
32489
34178
|
const metaPath = join(sessionDir, "meta.json");
|
|
32490
34179
|
let meta;
|
|
32491
34180
|
try {
|
|
32492
34181
|
meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
32493
|
-
} catch
|
|
32494
|
-
process.stderr.write(`[veloz init] falha ao ler meta.json: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
34182
|
+
} catch {
|
|
32495
34183
|
return;
|
|
32496
34184
|
}
|
|
32497
34185
|
const authConfig = loadConfig();
|
|
32498
|
-
if (!authConfig.apiKey) {
|
|
32499
|
-
process.stderr.write("[veloz init] sem apiKey — telemetria não enviada\n");
|
|
32500
|
-
return;
|
|
32501
|
-
}
|
|
32502
34186
|
const files = readdirSync(sessionDir).filter((name) => {
|
|
32503
34187
|
const full = join(sessionDir, name);
|
|
32504
34188
|
try {
|
|
@@ -32511,9 +34195,10 @@ async function uploadInitTelemetry(sessionDir) {
|
|
|
32511
34195
|
if (files.length === 0) return;
|
|
32512
34196
|
const client = createClient(authConfig.apiUrl, () => {
|
|
32513
34197
|
const headers = {
|
|
32514
|
-
|
|
32515
|
-
"
|
|
34198
|
+
"User-Agent": "veloz-cli/telemetry",
|
|
34199
|
+
"X-Veloz-Client-Source": "cli-wizard"
|
|
32516
34200
|
};
|
|
34201
|
+
if (authConfig.apiKey) headers.Authorization = `Bearer ${authConfig.apiKey}`;
|
|
32517
34202
|
if (authConfig.organizationId) headers["X-Organization-Id"] = authConfig.organizationId;
|
|
32518
34203
|
return headers;
|
|
32519
34204
|
});
|
|
@@ -32535,11 +34220,10 @@ async function uploadInitTelemetry(sessionDir) {
|
|
|
32535
34220
|
startedAt: meta.startedAt,
|
|
32536
34221
|
files
|
|
32537
34222
|
});
|
|
32538
|
-
} catch
|
|
32539
|
-
process.stderr.write(`[veloz init] telemetria (start) falhou: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
34223
|
+
} catch {
|
|
32540
34224
|
return;
|
|
32541
34225
|
}
|
|
32542
|
-
|
|
34226
|
+
await Promise.allSettled(files.map(async (filename) => {
|
|
32543
34227
|
const url = started.uploads[filename];
|
|
32544
34228
|
if (!url) return;
|
|
32545
34229
|
const body = readFileSync(join(sessionDir, filename));
|
|
@@ -32549,8 +34233,7 @@ async function uploadInitTelemetry(sessionDir) {
|
|
|
32549
34233
|
body
|
|
32550
34234
|
});
|
|
32551
34235
|
if (!res.ok) throw new Error(`${filename}: HTTP ${res.status}`);
|
|
32552
|
-
}))
|
|
32553
|
-
if (failures.length > 0) for (const failure of failures) process.stderr.write(`[veloz init] upload de arquivo falhou: ${String(failure.reason?.message ?? failure.reason)}\n`);
|
|
34236
|
+
}));
|
|
32554
34237
|
try {
|
|
32555
34238
|
await client.initTelemetry.finalize({
|
|
32556
34239
|
id: started.id,
|
|
@@ -32561,11 +34244,70 @@ async function uploadInitTelemetry(sessionDir) {
|
|
|
32561
34244
|
durationMs: meta.durationMs,
|
|
32562
34245
|
endedAt: meta.endedAt
|
|
32563
34246
|
});
|
|
32564
|
-
} catch
|
|
32565
|
-
|
|
32566
|
-
|
|
32567
|
-
|
|
32568
|
-
|
|
34247
|
+
} catch {}
|
|
34248
|
+
}
|
|
34249
|
+
let telemetrySent = false;
|
|
34250
|
+
/**
|
|
34251
|
+
* Finalize the session logger and upload telemetry exactly once per process.
|
|
34252
|
+
*
|
|
34253
|
+
* Called on both normal exit and abort paths (double-SIGINT, ConfirmExit, headless
|
|
34254
|
+
* SIGINT). Bounded by `timeoutMs` so a hanging upload cannot block process exit.
|
|
34255
|
+
*/
|
|
34256
|
+
async function finalizeAndUploadOnce(logger, outcome, info$1, timeoutMs = 5e3) {
|
|
34257
|
+
if (telemetrySent) return;
|
|
34258
|
+
telemetrySent = true;
|
|
34259
|
+
logger.finalize(outcome, info$1);
|
|
34260
|
+
await Promise.race([uploadInitTelemetry(logger.dir).catch(() => {}), new Promise((resolve$1) => {
|
|
34261
|
+
setTimeout(resolve$1, timeoutMs);
|
|
34262
|
+
})]);
|
|
34263
|
+
}
|
|
34264
|
+
|
|
34265
|
+
//#endregion
|
|
34266
|
+
//#region src/wizard/start-tui.ts
|
|
34267
|
+
/**
|
|
34268
|
+
* Start the Ink TUI renderer for the wizard.
|
|
34269
|
+
* Forces dark background, creates store + router, renders App.
|
|
34270
|
+
*/
|
|
34271
|
+
function startTUI(cwd) {
|
|
34272
|
+
if (process.stdout.isTTY) process.stdout.write("\x1B[48;2;0;0;0m\x1B[2J\x1B[H");
|
|
34273
|
+
const store = new WizardStore();
|
|
34274
|
+
const router = new WizardRouter();
|
|
34275
|
+
const workingDir = cwd ?? process.cwd();
|
|
34276
|
+
let teardown = () => {};
|
|
34277
|
+
const exitWithTelemetry = async (reason) => {
|
|
34278
|
+
try {
|
|
34279
|
+
teardown();
|
|
34280
|
+
} catch {}
|
|
34281
|
+
await finalizeAndUploadOnce(getSessionLogger(), "abort", { error: reason });
|
|
34282
|
+
process.exit(130);
|
|
34283
|
+
};
|
|
34284
|
+
const { unmount } = render(createElement(App, {
|
|
34285
|
+
store,
|
|
34286
|
+
router,
|
|
34287
|
+
cwd: workingDir,
|
|
34288
|
+
onExit: exitWithTelemetry
|
|
34289
|
+
}));
|
|
34290
|
+
teardown = unmount;
|
|
34291
|
+
const cleanup = () => {
|
|
34292
|
+
if (process.stdout.isTTY) process.stdout.write("\x1B[0m");
|
|
34293
|
+
};
|
|
34294
|
+
process.on("exit", cleanup);
|
|
34295
|
+
let sigintCount = 0;
|
|
34296
|
+
process.on("SIGINT", () => {
|
|
34297
|
+
sigintCount++;
|
|
34298
|
+
getSessionLogger().log("signal", "SIGINT received", { count: sigintCount });
|
|
34299
|
+
if (sigintCount >= 2) {
|
|
34300
|
+
exitWithTelemetry("user double-SIGINT");
|
|
34301
|
+
return;
|
|
34302
|
+
}
|
|
34303
|
+
router.pushOverlay(Overlay.ConfirmExit);
|
|
34304
|
+
});
|
|
34305
|
+
return {
|
|
34306
|
+
store,
|
|
34307
|
+
router,
|
|
34308
|
+
unmount,
|
|
34309
|
+
waitForIntro: () => store.getGate("intro", (s) => s.introConfirmed)
|
|
34310
|
+
};
|
|
32569
34311
|
}
|
|
32570
34312
|
|
|
32571
34313
|
//#endregion
|
|
@@ -32580,6 +34322,57 @@ async function uploadInitTelemetry(sessionDir) {
|
|
|
32580
34322
|
* 5. Spawn Claude Agent → show RunScreen
|
|
32581
34323
|
* 6. Show OutroScreen with URLs + next steps
|
|
32582
34324
|
*/
|
|
34325
|
+
/** Convert an unknown thrown value into a stable string for telemetry. */
|
|
34326
|
+
function describeError(err) {
|
|
34327
|
+
if (err instanceof Error) return {
|
|
34328
|
+
message: err.message,
|
|
34329
|
+
stack: err.stack
|
|
34330
|
+
};
|
|
34331
|
+
return { message: typeof err === "string" ? err : JSON.stringify(err) };
|
|
34332
|
+
}
|
|
34333
|
+
/**
|
|
34334
|
+
* Detect an ORPC / HTTP 401 from a thrown value. Covers the three shapes we see
|
|
34335
|
+
* in practice: ORPCError instances (`.code === "UNAUTHORIZED"`), ORPC fetch
|
|
34336
|
+
* failures that preserve `.status === 401`, and raw Error messages containing
|
|
34337
|
+
* "Unauthorized".
|
|
34338
|
+
*/
|
|
34339
|
+
function isUnauthorizedError(err) {
|
|
34340
|
+
if (!err || typeof err !== "object") return false;
|
|
34341
|
+
const e = err;
|
|
34342
|
+
if (e.code === "UNAUTHORIZED") return true;
|
|
34343
|
+
if (e.status === 401) return true;
|
|
34344
|
+
if (typeof e.message === "string" && /unauthorized/i.test(e.message)) return true;
|
|
34345
|
+
return false;
|
|
34346
|
+
}
|
|
34347
|
+
/** Compose the error string we persist on `meta.error`. */
|
|
34348
|
+
function formatErrorForMeta(prefix, err) {
|
|
34349
|
+
const { message, stack } = describeError(err);
|
|
34350
|
+
return stack ? `${prefix}: ${message}\n${stack}` : `${prefix}: ${message}`;
|
|
34351
|
+
}
|
|
34352
|
+
/**
|
|
34353
|
+
* Install process-level safety nets so a stray throw / rejection still flushes
|
|
34354
|
+
* telemetry before the CLI dies. Returns a disposer that removes the handlers
|
|
34355
|
+
* (so we don't pollute long-running parent processes in tests).
|
|
34356
|
+
*/
|
|
34357
|
+
function installCrashHandlers(logger) {
|
|
34358
|
+
const handleCrash = (kind, err) => {
|
|
34359
|
+
const { message, stack } = describeError(err);
|
|
34360
|
+
logger.log("crash", `process ${kind}`, {
|
|
34361
|
+
message,
|
|
34362
|
+
stack
|
|
34363
|
+
});
|
|
34364
|
+
process.stderr.write(`[veloz init] ${kind}: ${message}\n`);
|
|
34365
|
+
finalizeAndUploadOnce(logger, "error", { error: formatErrorForMeta(kind, err) }).catch(() => {}).finally(() => process.exit(1));
|
|
34366
|
+
};
|
|
34367
|
+
const onUncaught = (err) => handleCrash("uncaughtException", err);
|
|
34368
|
+
const onUnhandled = (reason) => handleCrash("unhandledRejection", reason);
|
|
34369
|
+
process.on("uncaughtException", onUncaught);
|
|
34370
|
+
process.on("unhandledRejection", onUnhandled);
|
|
34371
|
+
return () => {
|
|
34372
|
+
process.off("uncaughtException", onUncaught);
|
|
34373
|
+
process.off("unhandledRejection", onUnhandled);
|
|
34374
|
+
};
|
|
34375
|
+
}
|
|
32583
34376
|
/** Detect repo using the lazy local filesystem layer. */
|
|
32584
34377
|
function detectLocalRepo(cwd) {
|
|
32585
34378
|
const layer = makeLocalFs(cwd);
|
|
@@ -32616,6 +34409,7 @@ function registerInit(cli$1) {
|
|
|
32616
34409
|
}),
|
|
32617
34410
|
outputPolicy: "agent-only",
|
|
32618
34411
|
async run(c) {
|
|
34412
|
+
setClientSource("cli-wizard");
|
|
32619
34413
|
const cwd = process.cwd();
|
|
32620
34414
|
const logger = initSessionLogger({ cwd });
|
|
32621
34415
|
logger.log("init", "veloz init invoked", {
|
|
@@ -32626,105 +34420,398 @@ function registerInit(cli$1) {
|
|
|
32626
34420
|
argv: process.argv.slice(2)
|
|
32627
34421
|
});
|
|
32628
34422
|
process.stderr.write(`[veloz init] debug logs: ${logger.dir}\n`);
|
|
32629
|
-
const
|
|
32630
|
-
|
|
32631
|
-
|
|
32632
|
-
|
|
32633
|
-
|
|
32634
|
-
|
|
32635
|
-
|
|
32636
|
-
|
|
32637
|
-
const detectedLabel = analysis.framework?.label ?? null;
|
|
32638
|
-
const forcedFramework = c.options.framework;
|
|
32639
|
-
if (!process.stdout.isTTY || c.options.ci) {
|
|
32640
|
-
logger.log("mode", "running headless (no TUI)");
|
|
32641
|
-
return await runHeadless({
|
|
32642
|
-
cwd,
|
|
32643
|
-
frameworkId: forcedFramework ?? detectedFrameworkId ?? "nodejs",
|
|
32644
|
-
frameworkLabel: forcedFramework ? FRAMEWORK_LABELS[forcedFramework] ?? forcedFramework : detectedLabel ?? "Node.js",
|
|
32645
|
-
packageManager: analysis.packageManager ?? "npm"
|
|
34423
|
+
const removeCrashHandlers = installCrashHandlers(logger);
|
|
34424
|
+
try {
|
|
34425
|
+
return await runInitFlow(c, logger, cwd);
|
|
34426
|
+
} catch (err) {
|
|
34427
|
+
const { message, stack } = describeError(err);
|
|
34428
|
+
logger.log("error", "init flow failed", {
|
|
34429
|
+
message,
|
|
34430
|
+
stack
|
|
32646
34431
|
});
|
|
34432
|
+
await finalizeAndUploadOnce(logger, "error", { error: formatErrorForMeta("init", err) });
|
|
34433
|
+
throw err;
|
|
34434
|
+
} finally {
|
|
34435
|
+
removeCrashHandlers();
|
|
32647
34436
|
}
|
|
32648
|
-
|
|
32649
|
-
|
|
32650
|
-
|
|
32651
|
-
|
|
32652
|
-
|
|
32653
|
-
|
|
32654
|
-
|
|
32655
|
-
|
|
32656
|
-
|
|
32657
|
-
|
|
32658
|
-
|
|
32659
|
-
|
|
32660
|
-
|
|
32661
|
-
|
|
32662
|
-
|
|
32663
|
-
|
|
32664
|
-
|
|
32665
|
-
|
|
32666
|
-
|
|
32667
|
-
|
|
32668
|
-
|
|
32669
|
-
|
|
32670
|
-
|
|
32671
|
-
|
|
32672
|
-
|
|
32673
|
-
|
|
32674
|
-
|
|
32675
|
-
|
|
32676
|
-
|
|
32677
|
-
|
|
32678
|
-
|
|
32679
|
-
|
|
32680
|
-
|
|
32681
|
-
|
|
32682
|
-
|
|
32683
|
-
|
|
32684
|
-
|
|
34437
|
+
}
|
|
34438
|
+
});
|
|
34439
|
+
}
|
|
34440
|
+
async function runInitFlow(c, logger, cwd) {
|
|
34441
|
+
const forcedFramework = c.options.framework;
|
|
34442
|
+
const analysis = detectLocalRepo(cwd);
|
|
34443
|
+
logger.log("detection", "repo analysis complete", {
|
|
34444
|
+
framework: analysis.framework?.name ?? null,
|
|
34445
|
+
frameworkLabel: analysis.framework?.label ?? null,
|
|
34446
|
+
packageManager: analysis.packageManager,
|
|
34447
|
+
isMonorepo: analysis.isMonorepo
|
|
34448
|
+
});
|
|
34449
|
+
logger.updateMeta({ analysis });
|
|
34450
|
+
const detectedFrameworkId = analysis.framework ? DETECTOR_TO_FRAMEWORK[analysis.framework.name] ?? "nodejs" : null;
|
|
34451
|
+
const detectedLabel = analysis.framework?.label ?? null;
|
|
34452
|
+
if (!process.stdout.isTTY || c.options.ci) {
|
|
34453
|
+
logger.log("mode", "running headless (no TUI)");
|
|
34454
|
+
return await runHeadless({
|
|
34455
|
+
cwd,
|
|
34456
|
+
frameworkId: forcedFramework ?? detectedFrameworkId ?? "nodejs",
|
|
34457
|
+
frameworkLabel: forcedFramework ? FRAMEWORK_LABELS[forcedFramework] ?? forcedFramework : detectedLabel ?? "Node.js",
|
|
34458
|
+
packageManager: analysis.packageManager ?? "npm"
|
|
34459
|
+
});
|
|
34460
|
+
}
|
|
34461
|
+
const tui = startTUI();
|
|
34462
|
+
const { store } = tui;
|
|
34463
|
+
logger.log("tui", "TUI started");
|
|
34464
|
+
try {
|
|
34465
|
+
return await runInteractiveFlow({
|
|
34466
|
+
tui,
|
|
34467
|
+
store,
|
|
34468
|
+
logger,
|
|
34469
|
+
cwd,
|
|
34470
|
+
analysis,
|
|
34471
|
+
detectedFrameworkId,
|
|
34472
|
+
detectedLabel,
|
|
34473
|
+
forcedFramework
|
|
34474
|
+
});
|
|
34475
|
+
} catch (err) {
|
|
34476
|
+
try {
|
|
34477
|
+
tui.unmount();
|
|
34478
|
+
} catch {}
|
|
34479
|
+
const { message } = describeError(err);
|
|
34480
|
+
process.stderr.write(`\n✗ Erro: ${message}\n`);
|
|
34481
|
+
process.stderr.write(` Logs de depuração: ${logger.dir}\n\n`);
|
|
34482
|
+
throw err;
|
|
34483
|
+
}
|
|
34484
|
+
}
|
|
34485
|
+
async function runInteractiveFlow(opts) {
|
|
34486
|
+
const { tui, store, logger, cwd, analysis, detectedFrameworkId, detectedLabel, forcedFramework } = opts;
|
|
34487
|
+
store.setDetection({
|
|
34488
|
+
framework: detectedFrameworkId,
|
|
34489
|
+
frameworkLabel: detectedLabel,
|
|
34490
|
+
packageManager: analysis.packageManager,
|
|
34491
|
+
gitRemote: detectGitRemote(cwd),
|
|
34492
|
+
isMonorepo: analysis.isMonorepo
|
|
34493
|
+
});
|
|
34494
|
+
if (forcedFramework) {
|
|
34495
|
+
store.selectFramework(forcedFramework, FRAMEWORK_LABELS[forcedFramework] ?? forcedFramework);
|
|
34496
|
+
store.confirmIntro();
|
|
34497
|
+
}
|
|
34498
|
+
logger.log("flow", "awaiting intro confirmation");
|
|
34499
|
+
await tui.waitForIntro();
|
|
34500
|
+
logger.log("flow", "intro confirmed");
|
|
34501
|
+
const selectedFramework = store.session.selectedFramework ?? detectedFrameworkId ?? "nodejs";
|
|
34502
|
+
const selectedLabel = store.session.selectedFrameworkLabel ?? detectedLabel ?? "Node.js";
|
|
34503
|
+
logger.updateMeta({
|
|
34504
|
+
framework: selectedFramework,
|
|
34505
|
+
frameworkLabel: selectedLabel,
|
|
34506
|
+
packageManager: store.session.packageManager ?? "npm",
|
|
34507
|
+
isMonorepo: store.session.isMonorepo,
|
|
34508
|
+
gitRemote: store.session.gitRemote
|
|
34509
|
+
});
|
|
34510
|
+
logger.log("flow", "resolving auth + org");
|
|
34511
|
+
await resolveAuthAndOrg(store);
|
|
34512
|
+
logger.log("auth", "auth + org resolved", {
|
|
34513
|
+
userName: store.session.userName,
|
|
34514
|
+
orgId: store.session.orgId,
|
|
34515
|
+
orgName: store.session.orgName
|
|
34516
|
+
});
|
|
34517
|
+
logger.updateMeta({
|
|
34518
|
+
userName: store.session.userName,
|
|
34519
|
+
orgName: store.session.orgName,
|
|
34520
|
+
orgId: store.session.orgId
|
|
34521
|
+
});
|
|
34522
|
+
logger.log("flow", "awaiting user notes");
|
|
34523
|
+
await store.getGate("notes", (s) => s.notesConfirmed);
|
|
34524
|
+
logger.log("flow", "notes confirmed", {
|
|
34525
|
+
hasNotes: Boolean(store.session.userNotes),
|
|
34526
|
+
bytes: store.session.userNotes?.length ?? 0
|
|
34527
|
+
});
|
|
34528
|
+
logger.log("agent", "invoking runAgent");
|
|
34529
|
+
await runAgent({
|
|
34530
|
+
store,
|
|
34531
|
+
cwd,
|
|
34532
|
+
framework: selectedFramework,
|
|
34533
|
+
frameworkLabel: selectedLabel,
|
|
34534
|
+
userName: store.session.userName,
|
|
34535
|
+
orgName: store.session.orgName,
|
|
34536
|
+
packageManager: store.session.packageManager ?? "npm",
|
|
34537
|
+
userNotes: store.session.userNotes
|
|
34538
|
+
});
|
|
34539
|
+
logger.log("agent", "runAgent returned", {
|
|
34540
|
+
phase: store.session.agentPhase,
|
|
34541
|
+
error: store.session.agentError,
|
|
34542
|
+
deployUrl: store.session.deployUrl,
|
|
34543
|
+
retryCount: store.session.retryCount
|
|
34544
|
+
});
|
|
34545
|
+
if (!store.session.agentError) {
|
|
34546
|
+
logger.log("flow", "running github auto-deploy prompt flow");
|
|
34547
|
+
await runGithubSetupFlow(store, cwd);
|
|
34548
|
+
logger.log("flow", "github auto-deploy flow done", {
|
|
34549
|
+
phase: store.session.githubSetupPhase,
|
|
34550
|
+
error: store.session.githubSetupError
|
|
34551
|
+
});
|
|
34552
|
+
}
|
|
34553
|
+
if (!store.session.agentError) {
|
|
34554
|
+
logger.log("flow", "running ai tooling prompt flow");
|
|
34555
|
+
await runAiSetupFlow(store);
|
|
34556
|
+
logger.log("flow", "ai tooling flow done", {
|
|
34557
|
+
phase: store.session.aiSetupPhase,
|
|
34558
|
+
error: store.session.aiSetupError
|
|
34559
|
+
});
|
|
34560
|
+
}
|
|
34561
|
+
logger.log("flow", "awaiting outro dismissal");
|
|
34562
|
+
await store.getGate("outro", (s) => s.outroDismissed);
|
|
34563
|
+
logger.log("flow", "outro dismissed");
|
|
34564
|
+
tui.unmount();
|
|
34565
|
+
logger.log("tui", "TUI unmounted");
|
|
34566
|
+
const success$1 = !store.session.agentError;
|
|
34567
|
+
await finalizeAndUploadOnce(logger, success$1 ? "success" : "error", {
|
|
34568
|
+
error: store.session.agentError ?? void 0,
|
|
34569
|
+
deployUrl: store.session.deployUrl ?? void 0,
|
|
34570
|
+
retries: store.session.retryCount
|
|
34571
|
+
});
|
|
34572
|
+
return {
|
|
34573
|
+
success: success$1,
|
|
34574
|
+
url: store.session.deployUrl ?? void 0
|
|
34575
|
+
};
|
|
34576
|
+
}
|
|
34577
|
+
/**
|
|
34578
|
+
* Parse the first URL in `.git/config` into an owner/repo pair.
|
|
34579
|
+
* We read the file directly (instead of shelling out) so the flow works
|
|
34580
|
+
* regardless of cwd and without depending on `git` being on PATH.
|
|
34581
|
+
*/
|
|
34582
|
+
function detectGithubRepo(cwd) {
|
|
34583
|
+
try {
|
|
34584
|
+
const gitConfigPath = join(cwd, ".git", "config");
|
|
34585
|
+
if (!existsSync(gitConfigPath)) return null;
|
|
34586
|
+
const match$3 = readFileSync(gitConfigPath, "utf-8").match(/url\s*=\s*(.+)/);
|
|
34587
|
+
if (!match$3?.[1]) return null;
|
|
34588
|
+
const url = match$3[1].trim();
|
|
34589
|
+
const httpsMatch = url.match(/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
34590
|
+
if (httpsMatch?.[1] && httpsMatch[2]) return {
|
|
34591
|
+
owner: httpsMatch[1],
|
|
34592
|
+
repo: httpsMatch[2]
|
|
34593
|
+
};
|
|
34594
|
+
const sshMatch = url.match(/github\.com:([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
34595
|
+
if (sshMatch?.[1] && sshMatch[2]) return {
|
|
34596
|
+
owner: sshMatch[1],
|
|
34597
|
+
repo: sshMatch[2]
|
|
34598
|
+
};
|
|
34599
|
+
return null;
|
|
34600
|
+
} catch {
|
|
34601
|
+
return null;
|
|
34602
|
+
}
|
|
34603
|
+
}
|
|
34604
|
+
/**
|
|
34605
|
+
* Drive the post-deploy GitHub auto-deploy setup.
|
|
34606
|
+
*
|
|
34607
|
+
* 1. If there is no GitHub remote or no veloz.json project id, stay idle.
|
|
34608
|
+
* 2. Show the prompt screen (Yes/No). If the user declines → phase=skipped.
|
|
34609
|
+
* 3. On "yes", check if the project is already connected on the server — if so,
|
|
34610
|
+
* short-circuit to `done` without hitting the GitHub App APIs.
|
|
34611
|
+
* 4. Otherwise check the GitHub App installation status. If installed → connect
|
|
34612
|
+
* the repo and finish. If not → open the install URL, poll for install, then
|
|
34613
|
+
* connect the repo.
|
|
34614
|
+
*/
|
|
34615
|
+
async function runGithubSetupFlow(store, cwd) {
|
|
34616
|
+
const logger = getSessionLogger();
|
|
34617
|
+
const remote = detectGithubRepo(cwd);
|
|
34618
|
+
if (!remote) {
|
|
34619
|
+
logger.log("github-setup", "no GitHub remote detected — skipping");
|
|
34620
|
+
return;
|
|
34621
|
+
}
|
|
34622
|
+
const projectId = loadConfig$1()?.project?.id;
|
|
34623
|
+
if (!projectId) {
|
|
34624
|
+
logger.log("github-setup", "no project id in veloz.json — skipping");
|
|
34625
|
+
return;
|
|
34626
|
+
}
|
|
34627
|
+
store.setGithubRepoInfo(remote.owner, remote.repo);
|
|
34628
|
+
store.setGithubSetupPhase("prompt");
|
|
34629
|
+
await store.getGate("github-setup-prompt", (s) => s.githubSetupPhase !== "prompt");
|
|
34630
|
+
if (store.session.githubSetupPhase === "skipped") {
|
|
34631
|
+
logger.log("github-setup", "user declined auto-deploy setup");
|
|
34632
|
+
return;
|
|
34633
|
+
}
|
|
34634
|
+
try {
|
|
34635
|
+
const client = await getClient();
|
|
34636
|
+
const existing = (await client.projects.list()).find((p) => p.id === projectId);
|
|
34637
|
+
if (existing?.githubRepoOwner === remote.owner && existing.githubRepoName === remote.repo && existing.githubInstallationId) {
|
|
34638
|
+
logger.log("github-setup", "project already connected — short-circuit");
|
|
34639
|
+
store.setGithubSetupPhase("done");
|
|
34640
|
+
return;
|
|
34641
|
+
}
|
|
34642
|
+
const installation = await client.github.getInstallation({ owner: remote.owner });
|
|
34643
|
+
if (installation.error === "TOKEN_EXPIRED") {
|
|
34644
|
+
store.failGithubSetup("Token do GitHub expirado. Reconecte sua conta no dashboard e tente novamente.");
|
|
34645
|
+
return;
|
|
34646
|
+
}
|
|
34647
|
+
if (installation.error === "RATE_LIMITED") {
|
|
34648
|
+
store.failGithubSetup("Limite de requisições do GitHub atingido. Tente de novo em alguns minutos.");
|
|
34649
|
+
return;
|
|
34650
|
+
}
|
|
34651
|
+
if (installation.installed && installation.installationId) {
|
|
34652
|
+
store.setGithubSetupPhase("connecting");
|
|
34653
|
+
await client.projects.connectRepo({
|
|
34654
|
+
projectId,
|
|
34655
|
+
githubRepoOwner: remote.owner,
|
|
34656
|
+
githubRepoName: remote.repo,
|
|
34657
|
+
githubInstallationId: installation.installationId
|
|
32685
34658
|
});
|
|
32686
|
-
|
|
32687
|
-
|
|
32688
|
-
|
|
32689
|
-
|
|
32690
|
-
|
|
34659
|
+
store.setGithubSetupPhase("done");
|
|
34660
|
+
return;
|
|
34661
|
+
}
|
|
34662
|
+
if (!installation.installUrl) {
|
|
34663
|
+
store.failGithubSetup("GitHub App não está configurado no servidor. Contate o administrador da plataforma.");
|
|
34664
|
+
return;
|
|
34665
|
+
}
|
|
34666
|
+
store.setGithubInstallUrl(installation.installUrl);
|
|
34667
|
+
store.setGithubSetupPhase("installing");
|
|
34668
|
+
await openBrowser(installation.installUrl);
|
|
34669
|
+
const installationId = await pollGithubInstallation(store, remote.owner);
|
|
34670
|
+
if (!installationId) {
|
|
34671
|
+
store.failGithubSetup("Tempo esgotado aguardando a instalação do GitHub App.");
|
|
34672
|
+
return;
|
|
34673
|
+
}
|
|
34674
|
+
store.setGithubSetupPhase("connecting");
|
|
34675
|
+
await client.projects.connectRepo({
|
|
34676
|
+
projectId,
|
|
34677
|
+
githubRepoOwner: remote.owner,
|
|
34678
|
+
githubRepoName: remote.repo,
|
|
34679
|
+
githubInstallationId: installationId
|
|
34680
|
+
});
|
|
34681
|
+
store.setGithubSetupPhase("done");
|
|
34682
|
+
} catch (error) {
|
|
34683
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34684
|
+
logger.log("github-setup", "unexpected error", { message });
|
|
34685
|
+
store.failGithubSetup(message);
|
|
34686
|
+
}
|
|
34687
|
+
}
|
|
34688
|
+
/**
|
|
34689
|
+
* Offer to install the Veloz MCP server and/or the agent skills when missing.
|
|
34690
|
+
* When the user agrees, we re-invoke the current CLI binary (`process.execPath
|
|
34691
|
+
* <argv[1]>`) for each command so we work regardless of install location, and
|
|
34692
|
+
* capture stdio so the child's output doesn't corrupt the Ink frame.
|
|
34693
|
+
*/
|
|
34694
|
+
async function runAiSetupFlow(store) {
|
|
34695
|
+
const logger = getSessionLogger();
|
|
34696
|
+
const status = checkAiSetup();
|
|
34697
|
+
const needsMcp = !status.mcpInstalled;
|
|
34698
|
+
const needsSkills = !status.skillsInstalled;
|
|
34699
|
+
if (!needsMcp && !needsSkills) {
|
|
34700
|
+
logger.log("ai-setup", "both MCP and skills already installed — skipping");
|
|
34701
|
+
return;
|
|
34702
|
+
}
|
|
34703
|
+
store.setAiSetupNeeds({
|
|
34704
|
+
mcp: needsMcp,
|
|
34705
|
+
skills: needsSkills
|
|
34706
|
+
});
|
|
34707
|
+
store.setAiSetupPhase("prompt");
|
|
34708
|
+
await store.getGate("ai-setup-prompt", (s) => s.aiSetupPhase !== "prompt");
|
|
34709
|
+
if (store.session.aiSetupPhase === "skipped") {
|
|
34710
|
+
logger.log("ai-setup", "user declined ai tooling setup");
|
|
34711
|
+
return;
|
|
34712
|
+
}
|
|
34713
|
+
let anyError = false;
|
|
34714
|
+
if (needsMcp) {
|
|
34715
|
+
store.setAiSetupMcpStatus("installing");
|
|
34716
|
+
const mcpResult = await runVelozSubcommand(["mcp", "add"]);
|
|
34717
|
+
if (mcpResult.ok) store.setAiSetupMcpStatus("done");
|
|
34718
|
+
else {
|
|
34719
|
+
store.setAiSetupMcpStatus("error");
|
|
34720
|
+
anyError = true;
|
|
34721
|
+
logger.log("ai-setup", "mcp add failed", { stderr: mcpResult.stderr });
|
|
34722
|
+
}
|
|
34723
|
+
}
|
|
34724
|
+
if (needsSkills) {
|
|
34725
|
+
store.setAiSetupSkillsStatus("installing");
|
|
34726
|
+
const skillsResult = await runVelozSubcommand(["skills", "add"]);
|
|
34727
|
+
if (skillsResult.ok) store.setAiSetupSkillsStatus("done");
|
|
34728
|
+
else {
|
|
34729
|
+
store.setAiSetupSkillsStatus("error");
|
|
34730
|
+
anyError = true;
|
|
34731
|
+
logger.log("ai-setup", "skills add failed", { stderr: skillsResult.stderr });
|
|
34732
|
+
}
|
|
34733
|
+
}
|
|
34734
|
+
if (anyError) store.failAiSetup("Alguma etapa da integração falhou. Veja os logs de debug para detalhes.");
|
|
34735
|
+
else store.setAiSetupPhase("done");
|
|
34736
|
+
}
|
|
34737
|
+
/**
|
|
34738
|
+
* Re-invoke the current CLI (`process.execPath <argv[1]>`) with the given args
|
|
34739
|
+
* while the TUI is running. Silences child stdout/stderr (captured and returned
|
|
34740
|
+
* on failure) so the child doesn't paint over the Ink frame. Times out after
|
|
34741
|
+
* two minutes to prevent a stuck interactive child from hanging the wizard.
|
|
34742
|
+
*/
|
|
34743
|
+
function runVelozSubcommand(args$1) {
|
|
34744
|
+
return new Promise((resolve$1) => {
|
|
34745
|
+
const entry = process.argv[1];
|
|
34746
|
+
if (!entry) {
|
|
34747
|
+
resolve$1({
|
|
34748
|
+
ok: false,
|
|
34749
|
+
stderr: "Could not resolve CLI entry path."
|
|
32691
34750
|
});
|
|
32692
|
-
|
|
32693
|
-
|
|
32694
|
-
|
|
32695
|
-
|
|
32696
|
-
|
|
32697
|
-
|
|
32698
|
-
|
|
32699
|
-
|
|
32700
|
-
|
|
32701
|
-
|
|
34751
|
+
return;
|
|
34752
|
+
}
|
|
34753
|
+
const child = spawn(process.execPath, [entry, ...args$1], {
|
|
34754
|
+
stdio: [
|
|
34755
|
+
"ignore",
|
|
34756
|
+
"pipe",
|
|
34757
|
+
"pipe"
|
|
34758
|
+
],
|
|
34759
|
+
env: process.env
|
|
34760
|
+
});
|
|
34761
|
+
let stderr = "";
|
|
34762
|
+
let settled = false;
|
|
34763
|
+
const settle = (result$2) => {
|
|
34764
|
+
if (settled) return;
|
|
34765
|
+
settled = true;
|
|
34766
|
+
clearTimeout(timeoutHandle);
|
|
34767
|
+
resolve$1(result$2);
|
|
34768
|
+
};
|
|
34769
|
+
const timeoutHandle = setTimeout(() => {
|
|
34770
|
+
child.kill("SIGTERM");
|
|
34771
|
+
settle({
|
|
34772
|
+
ok: false,
|
|
34773
|
+
stderr: "Tempo esgotado na instalação."
|
|
32702
34774
|
});
|
|
32703
|
-
|
|
32704
|
-
|
|
32705
|
-
|
|
32706
|
-
|
|
32707
|
-
|
|
34775
|
+
}, 12e4);
|
|
34776
|
+
child.stdout?.on("data", () => {});
|
|
34777
|
+
child.stderr?.on("data", (chunk) => {
|
|
34778
|
+
stderr += chunk.toString();
|
|
34779
|
+
});
|
|
34780
|
+
child.on("error", (err) => {
|
|
34781
|
+
settle({
|
|
34782
|
+
ok: false,
|
|
34783
|
+
stderr: err.message
|
|
32708
34784
|
});
|
|
32709
|
-
|
|
32710
|
-
|
|
32711
|
-
|
|
32712
|
-
|
|
32713
|
-
|
|
32714
|
-
const success$1 = !store.session.agentError;
|
|
32715
|
-
logger.finalize(success$1 ? "success" : "error", {
|
|
32716
|
-
error: store.session.agentError ?? void 0,
|
|
32717
|
-
deployUrl: store.session.deployUrl ?? void 0,
|
|
32718
|
-
retries: store.session.retryCount
|
|
34785
|
+
});
|
|
34786
|
+
child.on("close", (code) => {
|
|
34787
|
+
settle({
|
|
34788
|
+
ok: code === 0,
|
|
34789
|
+
stderr
|
|
32719
34790
|
});
|
|
32720
|
-
|
|
32721
|
-
return {
|
|
32722
|
-
success: success$1,
|
|
32723
|
-
url: store.session.deployUrl ?? void 0
|
|
32724
|
-
};
|
|
32725
|
-
}
|
|
34791
|
+
});
|
|
32726
34792
|
});
|
|
32727
34793
|
}
|
|
34794
|
+
/**
|
|
34795
|
+
* Poll the server for a completed GitHub App installation on `owner`.
|
|
34796
|
+
* Returns the installation id when detected, or null on timeout / auth loss.
|
|
34797
|
+
*/
|
|
34798
|
+
async function pollGithubInstallation(store, owner) {
|
|
34799
|
+
const client = await getClient();
|
|
34800
|
+
const maxAttempts = 60;
|
|
34801
|
+
const pollInterval = 5e3;
|
|
34802
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
34803
|
+
await new Promise((resolve$1) => {
|
|
34804
|
+
setTimeout(resolve$1, pollInterval);
|
|
34805
|
+
});
|
|
34806
|
+
const check = await client.github.getInstallation({ owner });
|
|
34807
|
+
if (check.installed && check.installationId) return check.installationId;
|
|
34808
|
+
if (check.error === "TOKEN_EXPIRED") {
|
|
34809
|
+
store.failGithubSetup("Token do GitHub expirou durante a espera.");
|
|
34810
|
+
return null;
|
|
34811
|
+
}
|
|
34812
|
+
}
|
|
34813
|
+
return null;
|
|
34814
|
+
}
|
|
32728
34815
|
async function runHeadless(opts) {
|
|
32729
34816
|
const { WizardStore: WizardStore$1 } = await import("./store-CFF2J0lW.mjs");
|
|
32730
34817
|
const { runAgent: run } = await import("./agent-interface-DL5S8SEn.mjs");
|
|
@@ -32750,27 +34837,50 @@ async function runHeadless(opts) {
|
|
|
32750
34837
|
userName: "CI",
|
|
32751
34838
|
orgId: config.organizationId ?? ""
|
|
32752
34839
|
});
|
|
32753
|
-
|
|
32754
|
-
|
|
32755
|
-
|
|
32756
|
-
|
|
32757
|
-
|
|
32758
|
-
|
|
32759
|
-
userName: "CI",
|
|
32760
|
-
orgName: "",
|
|
32761
|
-
packageManager: opts.packageManager
|
|
32762
|
-
});
|
|
32763
|
-
const success$1 = !store.session.agentError;
|
|
32764
|
-
logger.finalize(success$1 ? "success" : "error", {
|
|
32765
|
-
error: store.session.agentError ?? void 0,
|
|
32766
|
-
deployUrl: store.session.deployUrl ?? void 0,
|
|
32767
|
-
retries: store.session.retryCount
|
|
32768
|
-
});
|
|
32769
|
-
await uploadInitTelemetry(logger.dir);
|
|
32770
|
-
return {
|
|
32771
|
-
success: success$1,
|
|
32772
|
-
url: store.session.deployUrl ?? void 0
|
|
34840
|
+
const onSigint = () => {
|
|
34841
|
+
logger.log("signal", "SIGINT received (headless)");
|
|
34842
|
+
(async () => {
|
|
34843
|
+
await finalizeAndUploadOnce(logger, "abort", { error: "user SIGINT" });
|
|
34844
|
+
process.exit(130);
|
|
34845
|
+
})();
|
|
32773
34846
|
};
|
|
34847
|
+
process.on("SIGINT", onSigint);
|
|
34848
|
+
try {
|
|
34849
|
+
logger.log("headless", "invoking runAgent");
|
|
34850
|
+
await run({
|
|
34851
|
+
store,
|
|
34852
|
+
cwd: opts.cwd,
|
|
34853
|
+
framework: opts.frameworkId,
|
|
34854
|
+
frameworkLabel: opts.frameworkLabel,
|
|
34855
|
+
userName: "CI",
|
|
34856
|
+
orgName: "",
|
|
34857
|
+
packageManager: opts.packageManager
|
|
34858
|
+
});
|
|
34859
|
+
const success$1 = !store.session.agentError;
|
|
34860
|
+
await finalizeAndUploadOnce(logger, success$1 ? "success" : "error", {
|
|
34861
|
+
error: store.session.agentError ?? void 0,
|
|
34862
|
+
deployUrl: store.session.deployUrl ?? void 0,
|
|
34863
|
+
retries: store.session.retryCount
|
|
34864
|
+
});
|
|
34865
|
+
return {
|
|
34866
|
+
success: success$1,
|
|
34867
|
+
url: store.session.deployUrl ?? void 0
|
|
34868
|
+
};
|
|
34869
|
+
} catch (err) {
|
|
34870
|
+
const { message, stack } = describeError(err);
|
|
34871
|
+
logger.log("headless", "runAgent threw", {
|
|
34872
|
+
message,
|
|
34873
|
+
stack
|
|
34874
|
+
});
|
|
34875
|
+
await finalizeAndUploadOnce(logger, "error", {
|
|
34876
|
+
error: formatErrorForMeta("headless", err),
|
|
34877
|
+
deployUrl: store.session.deployUrl ?? void 0,
|
|
34878
|
+
retries: store.session.retryCount
|
|
34879
|
+
});
|
|
34880
|
+
throw err;
|
|
34881
|
+
} finally {
|
|
34882
|
+
process.off("SIGINT", onSigint);
|
|
34883
|
+
}
|
|
32774
34884
|
}
|
|
32775
34885
|
/**
|
|
32776
34886
|
* Perform device auth with TUI feedback — reuses shared helpers from login.ts.
|
|
@@ -32786,33 +34896,53 @@ async function performDeviceAuth(store, apiUrl) {
|
|
|
32786
34896
|
store.setAuthPhase("polling");
|
|
32787
34897
|
return pollForToken(authClient, data.device_code, data.interval || 5);
|
|
32788
34898
|
}
|
|
32789
|
-
/** Fetch the list of orgs the user belongs to via
|
|
32790
|
-
async function listOrganizations(
|
|
32791
|
-
|
|
32792
|
-
Authorization: `Bearer ${apiKey}`,
|
|
32793
|
-
"User-Agent": "veloz-cli"
|
|
32794
|
-
} });
|
|
32795
|
-
if (!res.ok) throw new Error(`Falha ao listar workspaces (${res.status}).`);
|
|
32796
|
-
return (await res.json()).map((o) => ({
|
|
34899
|
+
/** Fetch the list of orgs the user belongs to via ORPC. */
|
|
34900
|
+
async function listOrganizations() {
|
|
34901
|
+
return (await (await getClient()).organizations.list()).map((o) => ({
|
|
32797
34902
|
id: o.id,
|
|
32798
34903
|
name: o.name,
|
|
32799
34904
|
slug: o.slug
|
|
32800
34905
|
}));
|
|
32801
34906
|
}
|
|
32802
34907
|
/**
|
|
34908
|
+
* Fetch orgs, handling an expired local API key by transparently re-running
|
|
34909
|
+
* the device auth flow inside the TUI. The user sees: "Verificando autenticação…"
|
|
34910
|
+
* → "Sessão expirou, autenticando novamente…" → device-code screen →
|
|
34911
|
+
* org picker, without ever bailing back to the shell.
|
|
34912
|
+
*/
|
|
34913
|
+
async function listOrgsOrReauth(store) {
|
|
34914
|
+
const logger = getSessionLogger();
|
|
34915
|
+
try {
|
|
34916
|
+
return await listOrganizations();
|
|
34917
|
+
} catch (err) {
|
|
34918
|
+
if (!isUnauthorizedError(err)) throw err;
|
|
34919
|
+
logger.log("auth", "stale api key detected (401) — forcing re-login", { message: describeError(err).message });
|
|
34920
|
+
store.setReauthNotice("Sua sessão expirou. Vamos autenticar novamente para continuar.");
|
|
34921
|
+
saveConfig({ apiKey: "" });
|
|
34922
|
+
const config = loadConfig();
|
|
34923
|
+
store.setAuthPhase("browser");
|
|
34924
|
+
const token = await performDeviceAuth(store, config.apiUrl);
|
|
34925
|
+
if (!token) throw new Error("Autenticação cancelada ou expirada.");
|
|
34926
|
+
logger.log("auth", "re-login succeeded — retrying org fetch");
|
|
34927
|
+
saveConfig({ apiKey: token });
|
|
34928
|
+
store.clearReauthNotice();
|
|
34929
|
+
return await listOrganizations();
|
|
34930
|
+
}
|
|
34931
|
+
}
|
|
34932
|
+
/**
|
|
32803
34933
|
* Create a new organization via Better Auth. The slug is derived from the name
|
|
32804
34934
|
* and suffixed with a random token when a collision occurs.
|
|
32805
34935
|
*/
|
|
32806
|
-
async function createOrganization(apiUrl,
|
|
34936
|
+
async function createOrganization(apiUrl, name) {
|
|
32807
34937
|
const baseSlug = name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "workspace";
|
|
34938
|
+
const headers = await getAuthHeaders();
|
|
32808
34939
|
let slug = baseSlug;
|
|
32809
34940
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
32810
34941
|
const res = await fetch(`${apiUrl}/api/auth/organization/create`, {
|
|
32811
34942
|
method: "POST",
|
|
32812
34943
|
headers: {
|
|
32813
|
-
|
|
32814
|
-
"Content-Type": "application/json"
|
|
32815
|
-
"User-Agent": "veloz-cli"
|
|
34944
|
+
...headers,
|
|
34945
|
+
"Content-Type": "application/json"
|
|
32816
34946
|
},
|
|
32817
34947
|
body: JSON.stringify({
|
|
32818
34948
|
name,
|
|
@@ -32868,16 +34998,17 @@ async function resolveAuthAndOrg(store) {
|
|
|
32868
34998
|
}
|
|
32869
34999
|
} else logger.log("auth", "already authenticated from config");
|
|
32870
35000
|
logger.log("auth", "fetching organizations");
|
|
32871
|
-
const orgs = await
|
|
35001
|
+
const orgs = await listOrgsOrReauth(store);
|
|
32872
35002
|
logger.log("auth", "organizations loaded", { count: orgs.length });
|
|
35003
|
+
config = loadConfig();
|
|
32873
35004
|
let chosen;
|
|
32874
35005
|
if (orgs.length === 0) {
|
|
32875
35006
|
store.setAuthPhase("org-create");
|
|
32876
|
-
chosen = await runOrgCreationFlow(store, config.apiUrl
|
|
35007
|
+
chosen = await runOrgCreationFlow(store, config.apiUrl);
|
|
32877
35008
|
} else {
|
|
32878
35009
|
store.setAvailableOrgs(orgs);
|
|
32879
35010
|
await store.getGate("org-select", (s) => s.selectedOrgId !== null || s.authPhase === "org-create");
|
|
32880
|
-
if (store.session.authPhase === "org-create") chosen = await runOrgCreationFlow(store, config.apiUrl
|
|
35011
|
+
if (store.session.authPhase === "org-create") chosen = await runOrgCreationFlow(store, config.apiUrl);
|
|
32881
35012
|
else {
|
|
32882
35013
|
const selectedOrgId = store.session.selectedOrgId;
|
|
32883
35014
|
const match$3 = orgs.find((o) => o.id === selectedOrgId);
|
|
@@ -32898,7 +35029,7 @@ async function resolveAuthAndOrg(store) {
|
|
|
32898
35029
|
* Drive the org-create screen: wait for the user to submit a name, try to
|
|
32899
35030
|
* create it, and retry on failure without leaving the wizard.
|
|
32900
35031
|
*/
|
|
32901
|
-
async function runOrgCreationFlow(store, apiUrl
|
|
35032
|
+
async function runOrgCreationFlow(store, apiUrl) {
|
|
32902
35033
|
store.requestOrgCreation();
|
|
32903
35034
|
for (;;) {
|
|
32904
35035
|
await store.getGate(`org-create-${Date.now()}-${Math.random()}`, (s) => s.newOrgName !== null && s.authPhase === "org-creating");
|
|
@@ -32908,7 +35039,7 @@ async function runOrgCreationFlow(store, apiUrl, apiKey) {
|
|
|
32908
35039
|
continue;
|
|
32909
35040
|
}
|
|
32910
35041
|
try {
|
|
32911
|
-
return await createOrganization(apiUrl,
|
|
35042
|
+
return await createOrganization(apiUrl, name);
|
|
32912
35043
|
} catch (error) {
|
|
32913
35044
|
const message = error instanceof Error ? error.message : String(error);
|
|
32914
35045
|
store.failOrgCreation(message);
|
|
@@ -32920,7 +35051,7 @@ async function runOrgCreationFlow(store, apiUrl, apiKey) {
|
|
|
32920
35051
|
//#region src/index.ts
|
|
32921
35052
|
if (process.argv.includes("--mcp")) process.env.VELOZ_MCP = "true";
|
|
32922
35053
|
const cli = Cli.create("veloz", {
|
|
32923
|
-
version: "0.0.0-beta.
|
|
35054
|
+
version: "0.0.0-beta.33",
|
|
32924
35055
|
description: "CLI da plataforma Veloz — deploy rápido para o Brasil",
|
|
32925
35056
|
env: z.object({ VELOZ_ENV: z.string().optional().describe("Ambiente alvo (ex: preview, staging)") }),
|
|
32926
35057
|
mcp: { command: "npx -y onveloz --mcp" }
|
|
@@ -32937,6 +35068,26 @@ cli.use(async (c, next) => {
|
|
|
32937
35068
|
if (error instanceof Error) {
|
|
32938
35069
|
const orpcError = error;
|
|
32939
35070
|
const orpcData = orpcError.data;
|
|
35071
|
+
if (orpcData?.code === "MULTIPLE_ORGS") {
|
|
35072
|
+
const msg = "Você pertence a múltiplos workspaces. Selecione um para continuar.";
|
|
35073
|
+
if (process.env.GITHUB_ACTIONS === "true") process.stdout.write(`::error::${msg}\n`);
|
|
35074
|
+
else if (process.stdout.isTTY) {
|
|
35075
|
+
console.error(`\n✗ ${msg}`);
|
|
35076
|
+
console.error(" Execute `veloz orgs list` para ver e `veloz orgs use <slug>` para escolher.");
|
|
35077
|
+
}
|
|
35078
|
+
return c.error({
|
|
35079
|
+
code: "MULTIPLE_ORGS",
|
|
35080
|
+
message: msg,
|
|
35081
|
+
exitCode: 1,
|
|
35082
|
+
cta: { commands: [{
|
|
35083
|
+
command: "orgs list",
|
|
35084
|
+
description: "Listar workspaces disponíveis"
|
|
35085
|
+
}, {
|
|
35086
|
+
command: "orgs use",
|
|
35087
|
+
description: "Selecionar workspace padrão"
|
|
35088
|
+
}] }
|
|
35089
|
+
});
|
|
35090
|
+
}
|
|
32940
35091
|
if (orpcData?.code === "NO_ORGANIZATION" || orpcData?.code === "ACCESS_NOT_APPROVED") {
|
|
32941
35092
|
const msg = "Você precisa criar um workspace antes de continuar.";
|
|
32942
35093
|
const hint = "Acesse app.onveloz.com/onboarding para criar seu workspace.";
|
|
@@ -32997,6 +35148,7 @@ cli.use(async (c, next) => {
|
|
|
32997
35148
|
}
|
|
32998
35149
|
});
|
|
32999
35150
|
cli.command(projectsGroup);
|
|
35151
|
+
cli.command(orgsGroup);
|
|
33000
35152
|
cli.command(envGroup);
|
|
33001
35153
|
cli.command(domainsGroup);
|
|
33002
35154
|
cli.command(volumesGroup);
|