onveloz 0.0.0-beta.30 → 0.0.0-beta.31
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 +2259 -641
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
import { Cli, middleware, z } from "incur";
|
|
3
3
|
import { exec, execSync } from "node:child_process";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
8
8
|
import { z as z$1 } from "zod";
|
|
9
9
|
import { createORPCClient } from "@orpc/client";
|
|
10
10
|
import { RPCLink } from "@orpc/client/fetch";
|
|
11
|
-
import { createHash } from "node:crypto";
|
|
11
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
12
12
|
import { homedir, platform, tmpdir } from "node:os";
|
|
13
13
|
import chalk from "chalk";
|
|
14
14
|
import { createAuthClient } from "better-auth/client";
|
|
15
|
-
import { deviceAuthorizationClient
|
|
15
|
+
import { deviceAuthorizationClient } from "better-auth/client/plugins";
|
|
16
16
|
import * as readline from "node:readline";
|
|
17
17
|
import { createInterface } from "node:readline";
|
|
18
18
|
import ora from "ora";
|
|
@@ -21,7 +21,8 @@ import WebSocket from "ws";
|
|
|
21
21
|
import { link, mkdir, mkdtemp, readdir, rm, stat } from "node:fs/promises";
|
|
22
22
|
import ignore from "ignore";
|
|
23
23
|
import tar from "tar";
|
|
24
|
-
import
|
|
24
|
+
import { promisify } from "node:util";
|
|
25
|
+
import React, { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
25
26
|
import { Box, Text, render, useInput, useStdout } from "ink";
|
|
26
27
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
27
28
|
import { map } from "nanostores";
|
|
@@ -1353,7 +1354,7 @@ function getGlobalProjectFlag() {
|
|
|
1353
1354
|
const idx = process.argv.indexOf("--project");
|
|
1354
1355
|
if (idx !== -1 && idx + 1 < process.argv.length) return process.argv[idx + 1];
|
|
1355
1356
|
}
|
|
1356
|
-
function isMultipleOrgsError$
|
|
1357
|
+
function isMultipleOrgsError$1(error) {
|
|
1357
1358
|
if (!(error instanceof Error)) return false;
|
|
1358
1359
|
const e = error;
|
|
1359
1360
|
return e.data?.code === "MULTIPLE_ORGS" && Array.isArray(e.data?.organizations);
|
|
@@ -1365,7 +1366,7 @@ async function fetchProjectsWithOrgResolution(client) {
|
|
|
1365
1366
|
try {
|
|
1366
1367
|
return await client.projects.list();
|
|
1367
1368
|
} catch (error) {
|
|
1368
|
-
if (!isMultipleOrgsError$
|
|
1369
|
+
if (!isMultipleOrgsError$1(error)) throw error;
|
|
1369
1370
|
const orgs = error.data.organizations;
|
|
1370
1371
|
if (orgs.length === 1) {
|
|
1371
1372
|
const org = orgs[0];
|
|
@@ -12386,16 +12387,16 @@ const isSuccess$1 = /* @__PURE__ */ matchEager$1({
|
|
|
12386
12387
|
onSuccess: () => true
|
|
12387
12388
|
});
|
|
12388
12389
|
/** @internal */
|
|
12389
|
-
const delay$1 = /* @__PURE__ */ dual(2, (self, duration) => andThen$1(sleep$
|
|
12390
|
+
const delay$1 = /* @__PURE__ */ dual(2, (self, duration) => andThen$1(sleep$2(duration), self));
|
|
12390
12391
|
/** @internal */
|
|
12391
|
-
const timeoutOrElse$1 = /* @__PURE__ */ dual(2, (self, options) => raceFirst$1(self, flatMap$1(sleep$
|
|
12392
|
+
const timeoutOrElse$1 = /* @__PURE__ */ dual(2, (self, options) => raceFirst$1(self, flatMap$1(sleep$2(options.duration), options.orElse)));
|
|
12392
12393
|
/** @internal */
|
|
12393
12394
|
const timeout$1 = /* @__PURE__ */ dual(2, (self, duration) => timeoutOrElse$1(self, {
|
|
12394
12395
|
duration,
|
|
12395
12396
|
orElse: () => fail$2(new TimeoutError$1())
|
|
12396
12397
|
}));
|
|
12397
12398
|
/** @internal */
|
|
12398
|
-
const timeoutOption$1 = /* @__PURE__ */ dual(2, (self, duration) => raceFirst$1(asSome$1(self), as$1(sleep$
|
|
12399
|
+
const timeoutOption$1 = /* @__PURE__ */ dual(2, (self, duration) => raceFirst$1(asSome$1(self), as$1(sleep$2(duration), none())));
|
|
12399
12400
|
/** @internal */
|
|
12400
12401
|
const timed$1 = (self) => clockWith$1((clock) => {
|
|
12401
12402
|
const start = clock.currentTimeNanosUnsafe();
|
|
@@ -13212,7 +13213,7 @@ const processOrPerformanceNow = /* @__PURE__ */ function() {
|
|
|
13212
13213
|
/** @internal */
|
|
13213
13214
|
const clockWith$1 = (f) => withFiber$1((fiber$2) => f(fiber$2.getRef(ClockRef)));
|
|
13214
13215
|
/** @internal */
|
|
13215
|
-
const sleep$
|
|
13216
|
+
const sleep$2 = (duration) => clockWith$1((clock) => clock.sleep(fromInputUnsafe(duration)));
|
|
13216
13217
|
/** @internal */
|
|
13217
13218
|
const TimeoutErrorTypeId$1 = "~effect/Cause/TimeoutError";
|
|
13218
13219
|
/** @internal */
|
|
@@ -15536,7 +15537,7 @@ const toStepWithMetadata = (schedule) => clockWith$1((clock) => map$3(toStep(sch
|
|
|
15536
15537
|
const meta = metaFn(now, input);
|
|
15537
15538
|
meta.output = output;
|
|
15538
15539
|
meta.duration = duration;
|
|
15539
|
-
return as$1(sleep$
|
|
15540
|
+
return as$1(sleep$2(duration), meta);
|
|
15540
15541
|
});
|
|
15541
15542
|
});
|
|
15542
15543
|
}));
|
|
@@ -18892,7 +18893,7 @@ const delay = delay$1;
|
|
|
18892
18893
|
* @since 2.0.0
|
|
18893
18894
|
* @category Delays & Timeouts
|
|
18894
18895
|
*/
|
|
18895
|
-
const sleep$
|
|
18896
|
+
const sleep$1 = sleep$2;
|
|
18896
18897
|
/**
|
|
18897
18898
|
* Measures the runtime of an effect and returns the duration with its result.
|
|
18898
18899
|
*
|
|
@@ -24079,7 +24080,7 @@ const LOGO_LINES$1 = [
|
|
|
24079
24080
|
];
|
|
24080
24081
|
const BRAND_COLOR$1 = "#FF4D00";
|
|
24081
24082
|
function getVersion() {
|
|
24082
|
-
return "0.0.0-beta.
|
|
24083
|
+
return "0.0.0-beta.31";
|
|
24083
24084
|
}
|
|
24084
24085
|
function printBanner(subtitle) {
|
|
24085
24086
|
const version$2 = getVersion();
|
|
@@ -25145,6 +25146,7 @@ function printDeployWarnings(warnings) {
|
|
|
25145
25146
|
|
|
25146
25147
|
//#endregion
|
|
25147
25148
|
//#region src/lib/auto-update.ts
|
|
25149
|
+
const execAsync = promisify(exec);
|
|
25148
25150
|
const PACKAGE_NAME = "onveloz";
|
|
25149
25151
|
function detectPackageManager() {
|
|
25150
25152
|
try {
|
|
@@ -25174,22 +25176,24 @@ async function fetchLatestVersion() {
|
|
|
25174
25176
|
}
|
|
25175
25177
|
}
|
|
25176
25178
|
function getCurrentVersion() {
|
|
25177
|
-
return "0.0.0-beta.
|
|
25179
|
+
return "0.0.0-beta.31";
|
|
25178
25180
|
}
|
|
25179
25181
|
/**
|
|
25180
25182
|
* Install a specific CLI version. Returns true on success.
|
|
25181
25183
|
*/
|
|
25182
|
-
function installVersion(pm, version$2) {
|
|
25184
|
+
async function installVersion(pm, version$2) {
|
|
25183
25185
|
const installCmd = getInstallCommand(pm, version$2);
|
|
25184
25186
|
const spin = spinner(`Atualizando CLI ${chalk.dim(getCurrentVersion())} → ${chalk.bold(version$2)}...`);
|
|
25185
25187
|
try {
|
|
25186
|
-
|
|
25188
|
+
await execAsync(installCmd, { timeout: 12e4 });
|
|
25187
25189
|
spin.stop();
|
|
25188
25190
|
console.log(chalk.green(`\n✓ CLI atualizada: ${chalk.bold(version$2)}\n`));
|
|
25189
25191
|
return true;
|
|
25190
|
-
} catch {
|
|
25192
|
+
} catch (error) {
|
|
25191
25193
|
spin.stop();
|
|
25194
|
+
const stderr = error.stderr?.trim();
|
|
25192
25195
|
console.log(chalk.yellow(`\n⚠ Não foi possível atualizar automaticamente. Execute manualmente:\n ${installCmd}\n`));
|
|
25196
|
+
if (stderr) console.log(chalk.dim(stderr.split("\n").slice(-5).join("\n")));
|
|
25193
25197
|
return false;
|
|
25194
25198
|
}
|
|
25195
25199
|
}
|
|
@@ -25215,7 +25219,7 @@ async function autoUpdate() {
|
|
|
25215
25219
|
const currentVersion = getCurrentVersion();
|
|
25216
25220
|
const latestVersion = await fetchLatestVersion();
|
|
25217
25221
|
if (!latestVersion || latestVersion === currentVersion) return;
|
|
25218
|
-
if (installVersion(pm, latestVersion)) reExec();
|
|
25222
|
+
if (await installVersion(pm, latestVersion)) reExec();
|
|
25219
25223
|
}
|
|
25220
25224
|
|
|
25221
25225
|
//#endregion
|
|
@@ -26288,7 +26292,7 @@ async function addServiceFlow(existingConfig, opts) {
|
|
|
26288
26292
|
await provisionDatabases(updatedConfig, { yes: opts.yes ?? false });
|
|
26289
26293
|
await triggerDeploy(service$2.id, service$2.name);
|
|
26290
26294
|
}
|
|
26291
|
-
function isMultipleOrgsError
|
|
26295
|
+
function isMultipleOrgsError(error) {
|
|
26292
26296
|
if (!(error instanceof Error)) return false;
|
|
26293
26297
|
const e = error;
|
|
26294
26298
|
return e.data?.code === "MULTIPLE_ORGS" && Array.isArray(e.data?.organizations);
|
|
@@ -26300,7 +26304,7 @@ async function resolveOrgIfNeeded(nonInteractive) {
|
|
|
26300
26304
|
try {
|
|
26301
26305
|
await client.projects.list();
|
|
26302
26306
|
} catch (error) {
|
|
26303
|
-
if (!isMultipleOrgsError
|
|
26307
|
+
if (!isMultipleOrgsError(error)) throw error;
|
|
26304
26308
|
const orgs = error.data.organizations;
|
|
26305
26309
|
if (orgs.length === 1) {
|
|
26306
26310
|
const org = orgs[0];
|
|
@@ -26438,9 +26442,18 @@ async function* mcpDeployFlow(opts) {
|
|
|
26438
26442
|
});
|
|
26439
26443
|
return;
|
|
26440
26444
|
}
|
|
26441
|
-
|
|
26442
|
-
|
|
26443
|
-
if (
|
|
26445
|
+
let configuredServices = await findServicesFromConfig();
|
|
26446
|
+
let justBootstrapped = false;
|
|
26447
|
+
if (configuredServices.length === 0) {
|
|
26448
|
+
await mcpBootstrapProjectAndService({
|
|
26449
|
+
app: opts.app,
|
|
26450
|
+
name: opts.name
|
|
26451
|
+
});
|
|
26452
|
+
configuredServices = await findServicesFromConfig();
|
|
26453
|
+
if (configuredServices.length === 0) throw new Error("Falha ao configurar o projeto automaticamente. Verifique o output acima para detalhes.");
|
|
26454
|
+
justBootstrapped = true;
|
|
26455
|
+
}
|
|
26456
|
+
if (!opts.service && !opts.all && !justBootstrapped) {
|
|
26444
26457
|
const available = configuredServices.map((s) => `${s.key} (${s.serviceName})`).join(", ");
|
|
26445
26458
|
throw new Error(`Especifique o(s) serviço(s) para deploy com --service ou use --all.\n\nServiços disponíveis: ${available}`);
|
|
26446
26459
|
}
|
|
@@ -26475,6 +26488,76 @@ async function* mcpDeployFlow(opts) {
|
|
|
26475
26488
|
data: result$2
|
|
26476
26489
|
};
|
|
26477
26490
|
}
|
|
26491
|
+
/**
|
|
26492
|
+
* Bootstrap a project + service for the current cwd in MCP mode.
|
|
26493
|
+
*
|
|
26494
|
+
* Mirrors the first-deploy setup in `cliDeployFlow` but runs fully
|
|
26495
|
+
* non-interactively so agents (including the wizard) can call
|
|
26496
|
+
* `veloz:deploy` on a fresh directory without shelling out.
|
|
26497
|
+
*/
|
|
26498
|
+
async function mcpBootstrapProjectAndService(opts) {
|
|
26499
|
+
await resolveOrgIfNeeded(true);
|
|
26500
|
+
const client = await getClient();
|
|
26501
|
+
const remote = isGitRepo() ? getGitRemote() : null;
|
|
26502
|
+
const project = remote ? await withRetry(() => client.projects.findByRepo({
|
|
26503
|
+
githubRepoOwner: remote.owner,
|
|
26504
|
+
githubRepoName: remote.repo
|
|
26505
|
+
})) : null;
|
|
26506
|
+
if (project && project.services.length > 0) {
|
|
26507
|
+
info(`Projeto encontrado: ${project.name}`);
|
|
26508
|
+
const svc = project.services[0];
|
|
26509
|
+
const detectedType = svc.type?.toLowerCase() ?? "web";
|
|
26510
|
+
saveConfig$1({
|
|
26511
|
+
version: "1.0",
|
|
26512
|
+
project: {
|
|
26513
|
+
id: project.id,
|
|
26514
|
+
name: project.name
|
|
26515
|
+
},
|
|
26516
|
+
services: { main: {
|
|
26517
|
+
id: svc.id,
|
|
26518
|
+
name: svc.name,
|
|
26519
|
+
type: detectedType,
|
|
26520
|
+
root: ".",
|
|
26521
|
+
branch: svc.branch
|
|
26522
|
+
} },
|
|
26523
|
+
created: (/* @__PURE__ */ new Date()).toISOString()
|
|
26524
|
+
});
|
|
26525
|
+
info(`Arquivo ${getConfigFileName()} criado.`);
|
|
26526
|
+
return;
|
|
26527
|
+
}
|
|
26528
|
+
if (project && project.services.length === 0) {
|
|
26529
|
+
info(`Projeto encontrado: ${project.name}. Criando serviço...`);
|
|
26530
|
+
await createServiceFlow(project.id, project.name, remote?.repo ?? project.name, {
|
|
26531
|
+
yes: true,
|
|
26532
|
+
app: opts.app
|
|
26533
|
+
});
|
|
26534
|
+
return;
|
|
26535
|
+
}
|
|
26536
|
+
const dirName = basename(process.cwd());
|
|
26537
|
+
const defaultName = opts.name ?? remote?.repo ?? dirName;
|
|
26538
|
+
const toSlug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
26539
|
+
let projectSlug = toSlug(defaultName);
|
|
26540
|
+
let slugCheck = await withRetry(() => client.projects.checkSlug({ slug: projectSlug }));
|
|
26541
|
+
if (!slugCheck.available) {
|
|
26542
|
+
projectSlug = toSlug(`${defaultName}-${Math.random().toString(36).slice(2, 6)}`);
|
|
26543
|
+
slugCheck = await withRetry(() => client.projects.checkSlug({ slug: projectSlug }));
|
|
26544
|
+
if (!slugCheck.available) throw new Error(`Não foi possível reservar um subdomínio para "${defaultName}". Tente novamente ou passe --name.`);
|
|
26545
|
+
}
|
|
26546
|
+
info(`Criando projeto "${defaultName}" (${projectSlug}.runveloz.com)...`);
|
|
26547
|
+
const newProject = await withRetry(() => client.projects.create({
|
|
26548
|
+
name: defaultName,
|
|
26549
|
+
slug: projectSlug,
|
|
26550
|
+
...remote && {
|
|
26551
|
+
githubRepoOwner: remote.owner,
|
|
26552
|
+
githubRepoName: remote.repo
|
|
26553
|
+
}
|
|
26554
|
+
}));
|
|
26555
|
+
success(`Projeto criado: ${newProject.name}`);
|
|
26556
|
+
await createServiceFlow(newProject.id, newProject.name, remote?.repo ?? dirName, {
|
|
26557
|
+
yes: true,
|
|
26558
|
+
app: opts.app
|
|
26559
|
+
});
|
|
26560
|
+
}
|
|
26478
26561
|
async function cliDeployFlow(opts) {
|
|
26479
26562
|
if (opts.verbose) process.env.VELOZ_VERBOSE = "true";
|
|
26480
26563
|
printBanner("Deploy");
|
|
@@ -27048,7 +27131,7 @@ function registerUpdate(cli$1) {
|
|
|
27048
27131
|
version: currentVersion
|
|
27049
27132
|
};
|
|
27050
27133
|
}
|
|
27051
|
-
if (!installVersion(pm, latestVersion)) {
|
|
27134
|
+
if (!await installVersion(pm, latestVersion)) {
|
|
27052
27135
|
console.log(chalk.dim(` Execute manualmente: ${getInstallCommand(pm, latestVersion)}`));
|
|
27053
27136
|
return {
|
|
27054
27137
|
updated: false,
|
|
@@ -27189,6 +27272,25 @@ const FRAMEWORK_LABELS = Object.fromEntries(FRAMEWORK_LIST.map((f) => [f.value,
|
|
|
27189
27272
|
|
|
27190
27273
|
//#endregion
|
|
27191
27274
|
//#region src/wizard/agent/agent-prompt.ts
|
|
27275
|
+
function renderUserNotes(notes) {
|
|
27276
|
+
if (!notes || !notes.trim()) return "";
|
|
27277
|
+
return `
|
|
27278
|
+
|
|
27279
|
+
## Notas do Usuário (instruções explícitas — prioridade máxima)
|
|
27280
|
+
O usuário forneceu as seguintes instruções ANTES de iniciar o deploy. Trate-as como requisitos obrigatórios e respeite-as em todas as decisões (escolha de apps, Dockerfile, flags, variáveis, etc.). Se as notas conflitarem com os defaults do wizard, as notas vencem.
|
|
27281
|
+
|
|
27282
|
+
${notes.trim()}`;
|
|
27283
|
+
}
|
|
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
|
+
}
|
|
27192
27294
|
function assemblePrompt(ctx) {
|
|
27193
27295
|
return `Você é o wizard de deploy da Veloz. Sua missão é configurar e fazer deploy do projeto do usuário de forma autônoma.
|
|
27194
27296
|
|
|
@@ -27196,7 +27298,7 @@ function assemblePrompt(ctx) {
|
|
|
27196
27298
|
- Diretório: ${ctx.cwd}
|
|
27197
27299
|
- Framework detectado: ${ctx.frameworkLabel}
|
|
27198
27300
|
- Package manager: ${ctx.packageManager}
|
|
27199
|
-
- Já autenticado como: ${ctx.userName} (org: ${ctx.orgName})
|
|
27301
|
+
- Já autenticado como: ${ctx.userName} (org: ${ctx.orgName})${renderUserNotes(ctx.userNotes)}
|
|
27200
27302
|
|
|
27201
27303
|
## O que é a Veloz
|
|
27202
27304
|
A Veloz é uma PaaS brasileira que faz deploy de aplicações a partir do código-fonte. A plataforma:
|
|
@@ -27224,54 +27326,334 @@ Leia o arquivo \`.claude/skills/veloz-llms.txt\` para entender a plataforma, fra
|
|
|
27224
27326
|
### 1. Analisar o projeto
|
|
27225
27327
|
- Leia package.json, configs do framework, .env.example
|
|
27226
27328
|
- Identifique: build command, start command, porta, variáveis de ambiente necessárias
|
|
27329
|
+
- Procure Dockerfiles/containers já existentes (veja seção 1b abaixo)
|
|
27227
27330
|
- CUIDADO: NÃO rode \`npm run dev\`, \`next dev\`, \`vite\`, \`nodemon\` ou qualquer servidor de desenvolvimento. Isso trava o processo.
|
|
27228
27331
|
|
|
27332
|
+
### 1b. Escape hatch: Dockerfile
|
|
27333
|
+
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
|
+
|
|
27335
|
+
**Cenário A — já existe um Dockerfile no projeto (USE, não recrie)**
|
|
27336
|
+
ANTES de assumir nixpacks, procure por arquivos Docker já presentes:
|
|
27337
|
+
|
|
27338
|
+
\`\`\`
|
|
27339
|
+
Glob: Dockerfile
|
|
27340
|
+
Glob: **/Dockerfile
|
|
27341
|
+
Glob: **/Dockerfile.*
|
|
27342
|
+
Glob: docker-compose.yml
|
|
27343
|
+
\`\`\`
|
|
27344
|
+
|
|
27345
|
+
Se houver um Dockerfile no caminho do app (raiz do projeto, ou em \`apps/<app>/Dockerfile\` para monorepos), você DEVE reutilizá-lo:
|
|
27346
|
+
- NÃO sobrescreva, NÃO edite, NÃO gere um novo.
|
|
27347
|
+
- Configure o serviço para usar \`method: "dockerfile"\` via \`veloz:config_set\`:
|
|
27348
|
+
\`\`\`
|
|
27349
|
+
veloz:config_set { "build": { "method": "dockerfile", "dockerfile": "apps/api/Dockerfile", "context": "." } }
|
|
27350
|
+
\`\`\`
|
|
27351
|
+
- \`dockerfile\` é o caminho do Dockerfile relativo à raiz do projeto. \`context\` é o diretório de build (geralmente \`.\` para monorepos que precisam do workspace inteiro, ou o caminho do app para projetos simples).
|
|
27352
|
+
- Mencione no diagrama e na confirmação: "Usando Dockerfile existente em \`<caminho>\`".
|
|
27353
|
+
|
|
27354
|
+
Se houver múltiplos Dockerfiles (ex: \`Dockerfile\` + \`Dockerfile.dev\`), prefira o que NÃO tem sufixo \`dev\`/\`test\`. Em dúvida, pergunte ao usuário via \`wizard-tools:prompt_user\` com \`type: "choice"\`.
|
|
27355
|
+
|
|
27356
|
+
**Cenário B — nixpacks não dá conta do stack (fallback)**
|
|
27357
|
+
Criar Dockerfile **novo** (só se não houver nenhum) SOMENTE se:
|
|
27358
|
+
- O framework não é suportado pelo Nixpacks (ex: binários compilados customizados, runtime exótico, setup nativo complexo com bibliotecas de sistema específicas)
|
|
27359
|
+
- Já tentou corrigir a misconfiguração do nixpacks 2 vezes sem sucesso (via \`veloz:config_set\`)
|
|
27360
|
+
- O usuário pediu explicitamente nas notas
|
|
27361
|
+
|
|
27362
|
+
Nesse caso:
|
|
27363
|
+
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 4) que esse arquivo novo será criado
|
|
27365
|
+
3. Configure o serviço com \`method: "dockerfile"\` via \`veloz:config_set\`
|
|
27366
|
+
4. Re-deploye
|
|
27367
|
+
|
|
27368
|
+
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
|
+
|
|
27370
|
+
NUNCA crie um Dockerfile só porque o primeiro build falhou — diagnostique primeiro (seção 7).
|
|
27371
|
+
|
|
27229
27372
|
### 2. Configurar variáveis de ambiente
|
|
27230
27373
|
- Use wizard-tools:check_env_keys para ver quais .env existem
|
|
27231
27374
|
- Use wizard-tools:prompt_user para pedir valores sensíveis ao usuário (com hint de onde obter)
|
|
27232
27375
|
- Use veloz:env_set para configurar no servidor ANTES do deploy (variáveis de build como NEXT_PUBLIC_* precisam existir antes)
|
|
27233
27376
|
- O usuário pode escolher "configurar depois" — nesse caso, continue sem a env var
|
|
27234
27377
|
|
|
27235
|
-
### 3.
|
|
27236
|
-
|
|
27378
|
+
### 3. Desenhar o diagrama (OBRIGATÓRIO — antes do deploy)
|
|
27379
|
+
ANTES de chamar \`veloz:deploy\`, você DEVE chamar \`wizard-tools:set_helper_content\` com um diagrama ASCII da arquitetura que será deployada. Este passo NÃO é opcional — o diagrama aparece na TUI abaixo da lista de tarefas e é essencial para o usuário visualizar o que está acontecendo.
|
|
27380
|
+
|
|
27381
|
+
Requisitos do diagrama:
|
|
27382
|
+
- Use caracteres de box-drawing (┌─┐│└┘├┤┬┴┼) e setas (→ ↓ ↑)
|
|
27383
|
+
- Largura alvo: **~${getDiagramWidth()} colunas** (use a maior parte desse espaço — não deixe o painel vazio). Nunca ultrapasse esse limite ou o diagrama será truncado
|
|
27384
|
+
- Mostre: framework do projeto, pipeline (código → build → serviço), porta, URL esperada
|
|
27385
|
+
- Inclua serviços auxiliares (banco, cache, workers) se houver
|
|
27386
|
+
- Use \`title\` para o cabeçalho e \`lines\` para o corpo
|
|
27387
|
+
|
|
27388
|
+
Exemplo (amplie a largura para preencher ~${getDiagramWidth()} colunas):
|
|
27389
|
+
\`\`\`
|
|
27390
|
+
wizard-tools:set_helper_content {
|
|
27391
|
+
"title": "Deploy — Next.js",
|
|
27392
|
+
"lines": [
|
|
27393
|
+
"┌─────────────────────────────────────────────────────┐",
|
|
27394
|
+
"│ Next.js 15 · porta 3000 · runtime Node 20 │",
|
|
27395
|
+
"└──────────────────────────┬──────────────────────────┘",
|
|
27396
|
+
" │ veloz deploy",
|
|
27397
|
+
" ▼",
|
|
27398
|
+
" build (Nixpacks) ───▶ imagem ───▶ serviço (turbo)",
|
|
27399
|
+
" │",
|
|
27400
|
+
" ▼",
|
|
27401
|
+
" https://app.runveloz.com"
|
|
27402
|
+
]
|
|
27403
|
+
}
|
|
27404
|
+
\`\`\`
|
|
27405
|
+
|
|
27406
|
+
Só prossiga para a confirmação DEPOIS que o diagrama for enviado.
|
|
27407
|
+
|
|
27408
|
+
### 4. Confirmar com o usuário (OBRIGATÓRIO — antes do deploy)
|
|
27409
|
+
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
|
+
|
|
27411
|
+
\`\`\`
|
|
27412
|
+
wizard-tools:prompt_user {
|
|
27413
|
+
"question": "Pronto para fazer deploy de <framework> com build <build-cmd> e start <start-cmd>? Consulte o diagrama acima.",
|
|
27414
|
+
"type": "confirm"
|
|
27415
|
+
}
|
|
27416
|
+
\`\`\`
|
|
27417
|
+
|
|
27418
|
+
Inclua na pergunta um resumo claro do que vai ser deployado:
|
|
27419
|
+
- Framework e caminho do app (para monorepos)
|
|
27420
|
+
- Comando de build e start que serão usados
|
|
27421
|
+
- Se vai criar banco/volume/serviços auxiliares
|
|
27422
|
+
- Variáveis de ambiente que serão configuradas
|
|
27423
|
+
|
|
27424
|
+
Regras:
|
|
27425
|
+
- Se o usuário responder "sim" → prossiga para o deploy
|
|
27426
|
+
- 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
|
+
- Se o usuário não responder (timeout) → NÃO faça deploy. Emita \`[SUMMARY] Deploy cancelado por falta de resposta.\` e encerre.
|
|
27428
|
+
|
|
27429
|
+
### 5. Fazer deploy
|
|
27430
|
+
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
|
+
|
|
27432
|
+
**5a. Dry-run primeiro (OBRIGATÓRIO)**
|
|
27433
|
+
ANTES de fazer o deploy real, você DEVE rodar um dry-run para validar a configuração sem enviar nada:
|
|
27434
|
+
|
|
27435
|
+
\`\`\`
|
|
27436
|
+
veloz:deploy { "dryRun": true }
|
|
27437
|
+
\`\`\`
|
|
27438
|
+
|
|
27439
|
+
O dry-run é **local-only** — ele NÃO autentica, NÃO chama API, NÃO faz upload, NÃO cria serviço, NÃO gera veloz.json, NÃO consome quota. Ele apenas:
|
|
27440
|
+
- Detecta o framework e lista os serviços que seriam criados
|
|
27441
|
+
- Calcula o tamanho do tarball a ser enviado (MB)
|
|
27442
|
+
- Mostra build/start/porta/output-dir que seriam usados
|
|
27443
|
+
- Reporta warnings de pre-deploy (arquivos suspeitos, configs faltando, etc.)
|
|
27444
|
+
|
|
27445
|
+
Use o resultado do dry-run para:
|
|
27446
|
+
- Confirmar que o framework/app/serviço detectado está correto
|
|
27447
|
+
- Verificar se o tamanho do upload é razoável (>100MB é flag vermelha — provavelmente falta .dockerignore/.gitignore excluindo node_modules/dist/.next)
|
|
27448
|
+
- Pegar ajustes óbvios (build/start errado, porta errada, monorepo path errado) ANTES de gastar tempo num build real
|
|
27449
|
+
|
|
27450
|
+
**Como corrigir problemas encontrados no dry-run:**
|
|
27451
|
+
Porque o dry-run NÃO gera veloz.json e NÃO cria serviço no servidor, \`veloz:config_set\` NÃO funciona antes do primeiro deploy real (ele precisa de um serviço existente). As correções pré-primeiro-deploy só podem ser feitas via **arquivos locais do projeto**:
|
|
27452
|
+
|
|
27453
|
+
- **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
|
+
- **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 1b cenário B)
|
|
27456
|
+
- **Porta errada** → geralmente o framework define; se precisa customizar, edite config do framework (next.config, vite.config) OU use Dockerfile com \`EXPOSE\`
|
|
27457
|
+
- **Dockerfile não está sendo usado quando deveria** → garanta que o path do Dockerfile está onde o detector procura (raiz do app)
|
|
27458
|
+
|
|
27459
|
+
Repita \`veloz:deploy { "dryRun": true }\` depois de cada correção para confirmar. Só prossiga para 5b quando o dry-run estiver limpo.
|
|
27460
|
+
|
|
27461
|
+
**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
|
+
|
|
27463
|
+
Para o dry-run, use as mesmas flags de escopo do deploy real (\`app\`, \`service\`, \`all\`):
|
|
27464
|
+
\`\`\`
|
|
27465
|
+
veloz:deploy { "dryRun": true, "app": "apps/web" }
|
|
27466
|
+
veloz:deploy { "dryRun": true, "service": "nome-servico" }
|
|
27467
|
+
\`\`\`
|
|
27468
|
+
|
|
27469
|
+
**5b. Deploy real**
|
|
27470
|
+
Depois que o dry-run passou limpo, faça o deploy real:
|
|
27237
27471
|
|
|
27238
27472
|
\`\`\`
|
|
27239
27473
|
veloz:deploy { "yes": true }
|
|
27240
27474
|
\`\`\`
|
|
27241
27475
|
|
|
27242
|
-
|
|
27476
|
+
Para monorepos ou deploy de serviço específico:
|
|
27477
|
+
- \`veloz:deploy { "yes": true, "app": "apps/web" }\` — deploy de um app específico do monorepo
|
|
27478
|
+
- \`veloz:deploy { "yes": true, "service": "nome-servico" }\` — re-deploy de serviço existente
|
|
27479
|
+
|
|
27480
|
+
O deploy via MCP, mesmo sem veloz.json, faz tudo de ponta-a-ponta:
|
|
27243
27481
|
1. Detecta o framework e o tipo de serviço (web, static, worker)
|
|
27244
|
-
2. Cria o projeto e o serviço na plataforma
|
|
27482
|
+
2. Cria o projeto e o serviço na plataforma (se ainda não existirem)
|
|
27245
27483
|
3. Gera o arquivo veloz.json com a configuração correta
|
|
27246
27484
|
4. Faz upload do código e inicia o build
|
|
27247
27485
|
5. Retorna eventos de progresso e o resultado final
|
|
27248
27486
|
|
|
27249
|
-
Para monorepos ou deploy de serviço específico:
|
|
27250
|
-
- \`veloz:deploy { "yes": true, "app": "apps/web" }\` — deploy de um app específico do monorepo
|
|
27251
|
-
- \`veloz:deploy { "yes": true, "service": "nome-servico" }\` — re-deploy de serviço existente
|
|
27252
|
-
|
|
27253
27487
|
IMPORTANTE:
|
|
27254
|
-
- SEMPRE
|
|
27255
|
-
-
|
|
27488
|
+
- SEMPRE rode \`dryRun: true\` ANTES do deploy real. NÃO pule o dry-run.
|
|
27489
|
+
- NUNCA passe \`dryRun: true\` no deploy de 5b — isso simula e NÃO faz deploy de verdade. O dry-run é só para o passo 5a.
|
|
27490
|
+
- SEMPRE use veloz:deploy (MCP), inclusive no primeiro deploy. NUNCA \`veloz deploy\` via Bash — o Bash pode travar esperando input interativo.
|
|
27491
|
+
- 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.
|
|
27256
27492
|
- NUNCA crie ou edite o arquivo veloz.json manualmente. Ele é gerado automaticamente pelo CLI no primeiro deploy.
|
|
27257
27493
|
- Após o primeiro deploy, se precisar ajustar configurações, use \`veloz:config_set\`.
|
|
27258
27494
|
|
|
27259
|
-
###
|
|
27495
|
+
### 6. Monitorar e diagnosticar
|
|
27260
27496
|
Após o deploy, use as ferramentas MCP para verificar o resultado:
|
|
27261
27497
|
- veloz:builds_logs — logs completos do build (stdout + stderr)
|
|
27262
27498
|
- veloz:builds_show — status detalhado do build
|
|
27263
27499
|
- veloz:logs_search — logs de runtime do serviço (após o serviço iniciar)
|
|
27264
27500
|
- veloz:domains_list — ver URLs/domínios disponíveis
|
|
27265
27501
|
|
|
27266
|
-
###
|
|
27267
|
-
|
|
27502
|
+
### 6b. Health check pós-deploy (OBRIGATÓRIO quando o deploy retorna LIVE)
|
|
27503
|
+
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
|
+
|
|
27505
|
+
**Passo 0 — Desenhar um plano de testes (OBRIGATÓRIO antes de testar)**
|
|
27506
|
+
ANTES de rodar qualquer curl/logs/metrics, você DEVE escrever um plano de testes explícito e emitir para o usuário. O plano deve ser específico ao projeto que acabou de ser deployado (framework, rotas conhecidas, dependências), não um template genérico. Formato:
|
|
27507
|
+
|
|
27508
|
+
\`\`\`
|
|
27509
|
+
[TESTING_PLAN]
|
|
27510
|
+
Plano de health check para <nome-do-serviço> (<framework>):
|
|
27511
|
+
|
|
27512
|
+
HTTP checks:
|
|
27513
|
+
1. GET <url-base>/ — esperar 2xx/3xx
|
|
27514
|
+
2. GET <url-base>/<rota-de-health-conhecida-se-houver> — esperar 200
|
|
27515
|
+
3. GET <url-base>/<rota-principal-da-app> — esperar 2xx
|
|
27516
|
+
(adicione rotas específicas que você identificou na análise do projeto, ex: /api/health, /_health, /ping)
|
|
27517
|
+
|
|
27518
|
+
Logs (janela: últimos 2min):
|
|
27519
|
+
- queries de erro: level:error, "Error:", "TypeError", "ECONNREFUSED", "panic", framework-specific
|
|
27520
|
+
- amostra geral para confirmar tráfego/startup
|
|
27521
|
+
|
|
27522
|
+
Métricas (janela: últimos 5min):
|
|
27523
|
+
- CPU: < 90% sustentado
|
|
27524
|
+
- Memória: < 80% do size preset (<preset atual>)
|
|
27525
|
+
- Instâncias reportando samples
|
|
27526
|
+
|
|
27527
|
+
Critérios de sucesso:
|
|
27528
|
+
- Todos HTTP checks em 2xx/3xx
|
|
27529
|
+
- Zero erros repetidos nos logs (warnings OK)
|
|
27530
|
+
- Métricas dentro dos limites
|
|
27531
|
+
|
|
27532
|
+
Critérios de falha → ação:
|
|
27533
|
+
- 5xx/timeout no HTTP → ler logs, classificar categoria A/B (seção 7)
|
|
27534
|
+
- Erros repetidos nos logs → classificar e corrigir (máx 3 iterações)
|
|
27535
|
+
- CPU sustentado alto sem tráfego → suspeitar crash loop, ler logs
|
|
27536
|
+
\`\`\`
|
|
27537
|
+
|
|
27538
|
+
Emita esse bloco COMPLETO, com as rotas/thresholds específicos, ANTES de rodar os checks. Depois, execute o plano na ordem listada abaixo.
|
|
27539
|
+
|
|
27540
|
+
**1) HTTP check via curl (Bash)**
|
|
27541
|
+
Use a URL retornada em \`urls\` (ou \`dashboardUrl\`) pelo \`veloz:deploy\`. Teste com curl — silencioso, só pegando status code, corpo e tempo:
|
|
27542
|
+
|
|
27543
|
+
\`\`\`bash
|
|
27544
|
+
curl -sS -o /tmp/veloz-health.txt -w "HTTP %{http_code} · %{time_total}s · %{size_download}B\\n" -m 15 "<url>"
|
|
27545
|
+
\`\`\`
|
|
27546
|
+
|
|
27547
|
+
Aguarde ~5 segundos antes do primeiro curl (tempo para o serviço terminar de subir). Se o status for:
|
|
27548
|
+
- **2xx** → OK, prossiga para o passo 2
|
|
27549
|
+
- **3xx** → siga o redirect com \`-L\` e re-teste; se continuar 3xx, OK
|
|
27550
|
+
- **4xx** (exceto 401/403 que podem ser esperados para rotas autenticadas) → tente \`/\` e \`/health\` / \`/api/health\` se disponível; reporte como aviso mas não falhe
|
|
27551
|
+
- **5xx ou timeout** → **falha**. Leia \`/tmp/veloz-health.txt\` para ver o corpo da resposta, vá direto para o passo 2 (logs) e diagnóstico
|
|
27552
|
+
|
|
27553
|
+
**2) Checar logs de runtime (veloz:logs_search)**
|
|
27554
|
+
Procure erros nos últimos 2 minutos:
|
|
27555
|
+
|
|
27556
|
+
\`\`\`
|
|
27557
|
+
veloz:logs_search { "query": "level:error OR level:fatal OR \\"Error:\\" OR \\"TypeError\\" OR \\"ECONNREFUSED\\" OR \\"panic\\"", "since": "2m", "limit": 50 }
|
|
27558
|
+
\`\`\`
|
|
27559
|
+
|
|
27560
|
+
Depois, pegue uma amostra geral para confirmar que o app está servindo requests:
|
|
27561
|
+
|
|
27562
|
+
\`\`\`
|
|
27563
|
+
veloz:logs_search { "query": "*", "since": "2m", "limit": 30 }
|
|
27564
|
+
\`\`\`
|
|
27565
|
+
|
|
27566
|
+
Se aparecerem erros repetidos (crash loop, exceções não tratadas, \`ECONNREFUSED\` em deps, migration falhando) → **falha**. Se só aparecem warnings ou os logs mostram o app servindo requests normalmente → OK.
|
|
27567
|
+
|
|
27568
|
+
**3) Checar métricas (veloz:metrics_show ou veloz:metrics_query)**
|
|
27569
|
+
Verifique CPU e memória nos últimos 5 minutos:
|
|
27570
|
+
|
|
27571
|
+
\`\`\`
|
|
27572
|
+
veloz:metrics_show { "since": "5m" }
|
|
27573
|
+
\`\`\`
|
|
27574
|
+
|
|
27575
|
+
Flags de alerta:
|
|
27576
|
+
- CPU sustentado > 90% sem tráfego real → provavelmente crash loop ou hot loop
|
|
27577
|
+
- Memória subindo rápido ou próxima do limite do size preset → memory leak ou size subdimensionado
|
|
27578
|
+
- Zero samples retornadas → serviço pode não estar reportando métricas (serviço não subiu)
|
|
27579
|
+
|
|
27580
|
+
**Classificando o resultado do health check:**
|
|
27268
27581
|
|
|
27269
|
-
|
|
27270
|
-
|
|
27271
|
-
|
|
27272
|
-
4. **Re-deploy**: Use veloz:deploy para tentar novamente (até 3 tentativas)
|
|
27582
|
+
Se TODOS os 3 checks passaram (HTTP 2xx/3xx, sem erros nos logs, métricas saudáveis):
|
|
27583
|
+
- Emita \`[HEALTH_OK]\` com um resumo curto em pt-BR: URL testada, status HTTP, amostra de logs, CPU/memória atuais
|
|
27584
|
+
- Encerre com \`[SUMMARY] Deploy concluído e saudável.\`
|
|
27273
27585
|
|
|
27274
|
-
Se
|
|
27586
|
+
Se qualquer check falhou:
|
|
27587
|
+
- Classifique como categoria A (misconfig) ou B (código do usuário) seguindo a seção 7
|
|
27588
|
+
- 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
|
+
- 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
|
+
- Máximo 3 iterações de health check + fix. Depois disso, emita \`[DEPLOY_UNHEALTHY]\` com o que foi tentado e o estado atual.
|
|
27591
|
+
|
|
27592
|
+
**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
|
+
|
|
27594
|
+
### 7. Classificar falhas e decidir o próximo passo
|
|
27595
|
+
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
|
+
|
|
27597
|
+
**A) Misconfiguração (responsabilidade do wizard — corrija você mesmo)**
|
|
27598
|
+
Erros onde o código do usuário está correto, mas a Veloz não sabe como construir/iniciar o app. Exemplos:
|
|
27599
|
+
- Comando de build errado ou faltando (ex: \`pnpm run build\` em um projeto sem esse script)
|
|
27600
|
+
- Comando de start errado (ex: porta errada, \`npm start\` sem \`start\` script)
|
|
27601
|
+
- Porta incorreta configurada no serviço
|
|
27602
|
+
- Package manager errado detectado (usando npm num repo pnpm)
|
|
27603
|
+
- Workspace/monorepo path incorreto
|
|
27604
|
+
- Falta de \`preStart\` para migrações de banco
|
|
27605
|
+
- Nixpacks não detectou o framework corretamente
|
|
27606
|
+
|
|
27607
|
+
→ 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
|
+
|
|
27609
|
+
Ú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 1b). Se JÁ existe um Dockerfile no repo e você estava ignorando — pare, use o existente.
|
|
27610
|
+
|
|
27611
|
+
**B) Falha de testes do usuário (responsabilidade do usuário — ÚNICO caso de handoff)**
|
|
27612
|
+
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).
|
|
27613
|
+
|
|
27614
|
+
Para QUALQUER outro tipo de falha — incluindo TypeScript compile errors, erros de sintaxe, módulos faltando, runtime crashes, falhas em scripts \`postinstall\`/\`prebuild\`, variáveis de ambiente ausentes — você DEVE tratar como categoria A (tentar corrigir via \`veloz:config_set\`, ajustar build/start, adicionar env vars ausentes via \`wizard-tools:prompt_user\`, ou cair no Dockerfile como último recurso). NÃO emita \`[BUILD_FAILED_USER_CODE]\` para esses casos — o wizard existe pra resolvê-los.
|
|
27615
|
+
|
|
27616
|
+
→ Ação (somente falha de testes): NÃO tente corrigir o código nem desabilitar os testes. Emita o signal abaixo e encerre:
|
|
27617
|
+
|
|
27618
|
+
\`\`\`
|
|
27619
|
+
[BUILD_FAILED_USER_CODE]
|
|
27620
|
+
Motivo: <explicação curta e clara do erro em pt-BR>
|
|
27621
|
+
|
|
27622
|
+
Detalhes:
|
|
27623
|
+
<trecho dos logs com o erro, arquivo:linha quando disponível>
|
|
27624
|
+
|
|
27625
|
+
Como corrigir:
|
|
27626
|
+
- <passo 1 acionável>
|
|
27627
|
+
- <passo 2 acionável>
|
|
27628
|
+
- <comando sugerido para reproduzir local, ex: pnpm build>
|
|
27629
|
+
|
|
27630
|
+
Após corrigir, execute \`veloz deploy\` novamente.
|
|
27631
|
+
\`\`\`
|
|
27632
|
+
|
|
27633
|
+
Depois de emitir \`[BUILD_FAILED_USER_CODE]\`, PARE. Não re-deploye, não tente editar o código-fonte do usuário, não chame mais ferramentas. Apenas encerre a sessão.
|
|
27634
|
+
|
|
27635
|
+
**Regra de ouro**: Se você não tem certeza em qual categoria o erro cai, teste localmente com \`pnpm build\` (ou equivalente) via Bash. Se o build local falha com testes → B (falha de testes). Caso contrário → A (misconfiguração, fixável pelo wizard).
|
|
27636
|
+
|
|
27637
|
+
## Edits em arquivos existentes do usuário — SEMPRE peça permissão primeiro
|
|
27638
|
+
|
|
27639
|
+
Se em algum momento você precisar **criar, editar ou sobrescrever um arquivo que já existe no projeto do usuário** (package.json, next.config.*, vite.config.*, Dockerfile existente, .env, tsconfig.json, código-fonte em src/, scripts customizados, qualquer arquivo versionado), você DEVE pedir permissão explícita via \`wizard-tools:prompt_user\` com \`type: "confirm"\` ANTES de chamar Write/Edit.
|
|
27640
|
+
|
|
27641
|
+
Formato da pergunta:
|
|
27642
|
+
|
|
27643
|
+
\`\`\`
|
|
27644
|
+
wizard-tools:prompt_user {
|
|
27645
|
+
"question": "Preciso editar <caminho/do/arquivo> para <motivo em 1 linha>. Posso aplicar a mudança?\\n\\nO que vai mudar:\\n- <resumo curto da alteração>\\n- <antes → depois se útil>",
|
|
27646
|
+
"type": "confirm"
|
|
27647
|
+
}
|
|
27648
|
+
\`\`\`
|
|
27649
|
+
|
|
27650
|
+
Regras:
|
|
27651
|
+
- Se o usuário responder "sim" → aplique a edição
|
|
27652
|
+
- 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 4) que o arquivo será criado
|
|
27654
|
+
- 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
|
+
|
|
27656
|
+
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.
|
|
27275
27657
|
|
|
27276
27658
|
## Ferramentas Helper (wizard-tools MCP)
|
|
27277
27659
|
- wizard-tools:check_env_keys — verificar quais env vars existem sem revelar valores
|
|
@@ -27279,12 +27661,19 @@ Se o build falhar, teste localmente com Bash para ver o erro completo antes de a
|
|
|
27279
27661
|
- wizard-tools:set_helper_content — exibir diagramas e conteúdo visual no painel esquerdo
|
|
27280
27662
|
- wizard-tools:prompt_user — perguntar algo ao usuário interativamente
|
|
27281
27663
|
|
|
27282
|
-
## Diagrama Visual
|
|
27283
|
-
|
|
27664
|
+
## Diagrama Visual (OBRIGATÓRIO antes do deploy)
|
|
27665
|
+
SEMPRE chame \`wizard-tools:set_helper_content\` ANTES de \`veloz:deploy\`. O diagrama é renderizado abaixo da lista de tarefas na TUI e é parte essencial da experiência do wizard — não pule esse passo.
|
|
27666
|
+
|
|
27667
|
+
O diagrama deve mostrar:
|
|
27284
27668
|
- O framework/tecnologia do projeto
|
|
27285
|
-
- O pipeline (
|
|
27669
|
+
- O pipeline (código → build → serviço)
|
|
27286
27670
|
- A porta e URL onde o serviço ficará disponível
|
|
27287
|
-
- Serviços auxiliares (banco de dados, etc.) se houver
|
|
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 (→ ↓ ▼)
|
|
27676
|
+
- Uma tarefa do TodoWrite DEVE representar esse passo (ver checklist abaixo)
|
|
27288
27677
|
|
|
27289
27678
|
## Reportar Progresso
|
|
27290
27679
|
Use a ferramenta TodoWrite para manter uma lista de tarefas atualizada:
|
|
@@ -27292,12 +27681,17 @@ Use a ferramenta TodoWrite para manter uma lista de tarefas atualizada:
|
|
|
27292
27681
|
- Mude para "in_progress" quando começar cada etapa
|
|
27293
27682
|
- Mude para "completed" quando terminar
|
|
27294
27683
|
|
|
27295
|
-
Tarefas típicas:
|
|
27684
|
+
Tarefas típicas (inclua TODAS, na ordem):
|
|
27296
27685
|
1. "Aprender CLI" — ler veloz --llms-full
|
|
27297
|
-
2. "Analisar projeto" — ler package.json, configs, estrutura
|
|
27686
|
+
2. "Analisar projeto" — ler package.json, configs, estrutura, procurar Dockerfile existente
|
|
27298
27687
|
3. "Configurar variáveis de ambiente" — env vars no servidor
|
|
27299
|
-
4. "
|
|
27300
|
-
5. "
|
|
27688
|
+
4. "Desenhar diagrama" — chamar wizard-tools:set_helper_content (OBRIGATÓRIO antes do deploy)
|
|
27689
|
+
5. "Confirmar com o usuário" — wizard-tools:prompt_user com type="confirm" (OBRIGATÓRIO antes do deploy)
|
|
27690
|
+
6. "Validar com dry-run" — veloz:deploy { dryRun: true } para checar detecção/tamanho/config (OBRIGATÓRIO antes do deploy real)
|
|
27691
|
+
7. "Fazer deploy" — enviar código e build (veloz:deploy { yes: true }, SEM dryRun)
|
|
27692
|
+
8. "Desenhar plano de testes" — emitir [TESTING_PLAN] com rotas e thresholds específicos (OBRIGATÓRIO antes do health check)
|
|
27693
|
+
9. "Executar health check" — curl + veloz:logs_search + veloz:metrics_show seguindo o plano
|
|
27694
|
+
10. "Finalizar" — emitir [HEALTH_OK] + [SUMMARY] se tudo saudável
|
|
27301
27695
|
|
|
27302
27696
|
## Regras CRÍTICAS — Uso de Ferramentas
|
|
27303
27697
|
- SEMPRE use ferramentas MCP (veloz:*) para TODAS as operações da plataforma. NUNCA use Bash para rodar comandos \`veloz\` exceto:
|
|
@@ -27309,10 +27703,11 @@ Tarefas típicas:
|
|
|
27309
27703
|
|
|
27310
27704
|
## Regras Gerais
|
|
27311
27705
|
- Comunique-se em português (pt-BR)
|
|
27312
|
-
-
|
|
27706
|
+
- 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.
|
|
27313
27707
|
- NUNCA crie veloz.json manualmente — o CLI gera automaticamente
|
|
27708
|
+
- SEMPRE peça confirmação explícita via wizard-tools:prompt_user (type="confirm") antes de chamar veloz:deploy
|
|
27314
27709
|
- Para env vars sensíveis, use wizard-tools:prompt_user — nunca invente valores
|
|
27315
|
-
-
|
|
27710
|
+
- Classifique falhas de build: misconfiguração → corrija e retente; erro de código do usuário → emita [BUILD_FAILED_USER_CODE] e PARE
|
|
27316
27711
|
- NUNCA rode comandos de dev server (next dev, vite, nodemon, npm run dev) — isso trava o processo
|
|
27317
27712
|
- NUNCA exponha secrets nos seus logs/mensagens
|
|
27318
27713
|
- Não use terminologia de infraestrutura interna (Kubernetes, pods, containers) — use "serviço", "instância", "build"
|
|
@@ -27345,9 +27740,9 @@ function getCommandments() {
|
|
|
27345
27740
|
|
|
27346
27741
|
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.
|
|
27347
27742
|
|
|
27348
|
-
8. **
|
|
27743
|
+
8. **Peça permissão antes de tocar arquivos do usuário**: 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, etc.), você DEVE chamar \`wizard-tools:prompt_user\` com \`type: "confirm"\` explicando arquivo + motivo + o que vai mudar, e só aplicar se o usuário responder "sim". Se responder "não", encontre alternativa ou emita \`[BUILD_FAILED_USER_CODE]\`. 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. NUNCA edite veloz.json diretamente.
|
|
27349
27744
|
|
|
27350
|
-
9. **
|
|
27745
|
+
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.
|
|
27351
27746
|
|
|
27352
27747
|
10. **Build local primeiro**: Quando possível, teste o build localmente (via Bash: pnpm build / npm run build) ANTES de fazer deploy. Isso economiza tempo e créditos.
|
|
27353
27748
|
|
|
@@ -27357,7 +27752,9 @@ function getCommandments() {
|
|
|
27357
27752
|
|
|
27358
27753
|
13. **Diagrama ANTES do deploy**: Use wizard-tools:set_helper_content para mostrar um diagrama ASCII da arquitetura ANTES de chamar veloz:deploy. O diagrama deve mostrar: framework detectado, pipeline de build, porta, URL esperada, e serviços auxiliares. Isso é OBRIGATÓRIO — o usuário precisa visualizar o que vai acontecer enquanto o build executa. Use caracteres Unicode para o diagrama (┌┐└┘│─→↓). Atualize o helper conforme avança (ex: diagnóstico de erro, resultado do build).
|
|
27359
27754
|
|
|
27360
|
-
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.
|
|
27755
|
+
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
|
+
|
|
27757
|
+
18. **Confirmação obrigatória antes do deploy**: ANTES de chamar \`veloz:deploy\` (primeiro deploy ou re-deploy), você DEVE pedir confirmação explícita ao usuário com \`wizard-tools:prompt_user\` \`type="confirm"\`. A pergunta precisa resumir o que será deployado (framework, app path, build/start, banco/volumes, env vars). Se o usuário responder "não" ou não responder, NÃO faça deploy — emita \`[SUMMARY] Deploy cancelado pelo usuário.\` e encerre. Exceção única: re-deploy de auto-healing de **misconfiguração** (categoria A da regra 9) pode ser feito sem nova confirmação até o limite de 3 tentativas.
|
|
27361
27758
|
|
|
27362
27759
|
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.
|
|
27363
27760
|
|
|
@@ -27365,16 +27762,227 @@ function getCommandments() {
|
|
|
27365
27762
|
`.trim();
|
|
27366
27763
|
}
|
|
27367
27764
|
|
|
27765
|
+
//#endregion
|
|
27766
|
+
//#region src/wizard/debug-logger.ts
|
|
27767
|
+
/**
|
|
27768
|
+
* Per-session debug logger for `veloz init`.
|
|
27769
|
+
*
|
|
27770
|
+
* Writes a folder under `~/.veloz/logs/init-<timestamp>-<id>/` with
|
|
27771
|
+
* structured files capturing every interesting event in the wizard run,
|
|
27772
|
+
* so failures can be diagnosed after the fact.
|
|
27773
|
+
*
|
|
27774
|
+
* Files:
|
|
27775
|
+
* session.log — human-readable timeline (append-only)
|
|
27776
|
+
* events.jsonl — structured events (category, timestamp, data)
|
|
27777
|
+
* sdk-messages.jsonl — raw SDK messages from the Agent SDK
|
|
27778
|
+
* tool-calls.jsonl — tool_use / tool_result pairs
|
|
27779
|
+
* store-events.jsonl — WizardStore state transitions
|
|
27780
|
+
* build-stream.jsonl — build log streaming events
|
|
27781
|
+
* mcp-tools.jsonl — wizard-tools MCP server invocations
|
|
27782
|
+
* prompts.md — initial + continuation prompts
|
|
27783
|
+
* meta.json — metadata (cwd, framework, user, outcome, timings)
|
|
27784
|
+
*
|
|
27785
|
+
* The logger never throws — disk failures are swallowed. MCP mode is
|
|
27786
|
+
* unaffected because writes only go to the session folder, never stdout.
|
|
27787
|
+
*/
|
|
27788
|
+
var SessionLogger = class {
|
|
27789
|
+
dir;
|
|
27790
|
+
sessionId;
|
|
27791
|
+
startedAt;
|
|
27792
|
+
meta;
|
|
27793
|
+
constructor(config) {
|
|
27794
|
+
this.startedAt = Date.now();
|
|
27795
|
+
const now = new Date(this.startedAt);
|
|
27796
|
+
this.sessionId = `init-${now.toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`;
|
|
27797
|
+
this.dir = join(config.root ?? join(homedir(), ".veloz", "logs"), this.sessionId);
|
|
27798
|
+
try {
|
|
27799
|
+
mkdirSync(this.dir, { recursive: true });
|
|
27800
|
+
} catch {}
|
|
27801
|
+
this.meta = {
|
|
27802
|
+
sessionId: this.sessionId,
|
|
27803
|
+
startedAt: now.toISOString(),
|
|
27804
|
+
cwd: config.cwd,
|
|
27805
|
+
node: process.version,
|
|
27806
|
+
platform: process.platform,
|
|
27807
|
+
pid: process.pid
|
|
27808
|
+
};
|
|
27809
|
+
this.writeMeta();
|
|
27810
|
+
this.log("session", "wizard session started", {
|
|
27811
|
+
cwd: config.cwd,
|
|
27812
|
+
pid: process.pid,
|
|
27813
|
+
node: process.version
|
|
27814
|
+
});
|
|
27815
|
+
}
|
|
27816
|
+
/** Append a line to session.log and a structured event to events.jsonl. */
|
|
27817
|
+
log(category, message, data) {
|
|
27818
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
27819
|
+
const text = data === void 0 ? message : `${message} ${safeStringify(data)}`;
|
|
27820
|
+
this.append("session.log", `[${ts}] [${category}] ${text}\n`);
|
|
27821
|
+
this.appendJsonl("events.jsonl", {
|
|
27822
|
+
ts,
|
|
27823
|
+
category,
|
|
27824
|
+
message,
|
|
27825
|
+
data
|
|
27826
|
+
});
|
|
27827
|
+
}
|
|
27828
|
+
logSdkMessage(message) {
|
|
27829
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
27830
|
+
this.appendJsonl("sdk-messages.jsonl", {
|
|
27831
|
+
ts,
|
|
27832
|
+
message
|
|
27833
|
+
});
|
|
27834
|
+
}
|
|
27835
|
+
logToolUse(name, input, toolUseId, extra) {
|
|
27836
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
27837
|
+
this.appendJsonl("tool-calls.jsonl", {
|
|
27838
|
+
ts,
|
|
27839
|
+
kind: "tool_use",
|
|
27840
|
+
name,
|
|
27841
|
+
toolUseId,
|
|
27842
|
+
input,
|
|
27843
|
+
...extra ?? {}
|
|
27844
|
+
});
|
|
27845
|
+
this.append("session.log", `[${ts}] [tool_use] ${name}${toolUseId ? ` (${toolUseId})` : ""}\n`);
|
|
27846
|
+
}
|
|
27847
|
+
logToolResult(toolUseId, content, isError, extra) {
|
|
27848
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
27849
|
+
this.appendJsonl("tool-calls.jsonl", {
|
|
27850
|
+
ts,
|
|
27851
|
+
kind: "tool_result",
|
|
27852
|
+
toolUseId,
|
|
27853
|
+
isError,
|
|
27854
|
+
content,
|
|
27855
|
+
...extra ?? {}
|
|
27856
|
+
});
|
|
27857
|
+
this.append("session.log", `[${ts}] [tool_result] ${toolUseId}${isError ? " ERROR" : ""}\n`);
|
|
27858
|
+
}
|
|
27859
|
+
logStoreEvent(event, data) {
|
|
27860
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
27861
|
+
this.appendJsonl("store-events.jsonl", {
|
|
27862
|
+
ts,
|
|
27863
|
+
event,
|
|
27864
|
+
data
|
|
27865
|
+
});
|
|
27866
|
+
}
|
|
27867
|
+
logBuildStream(event, data) {
|
|
27868
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
27869
|
+
this.appendJsonl("build-stream.jsonl", {
|
|
27870
|
+
ts,
|
|
27871
|
+
event,
|
|
27872
|
+
data
|
|
27873
|
+
});
|
|
27874
|
+
this.append("session.log", `[${ts}] [build-stream] ${event}\n`);
|
|
27875
|
+
}
|
|
27876
|
+
logPrompt(kind, body, context$2) {
|
|
27877
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
27878
|
+
const header = `\n\n---\n## [${ts}] ${kind} prompt\n`;
|
|
27879
|
+
const ctx = context$2 ? `\n<context>\n${safeStringify(context$2)}\n</context>\n\n` : "\n";
|
|
27880
|
+
this.append("prompts.md", `${header}${ctx}${body}\n`);
|
|
27881
|
+
this.appendJsonl("events.jsonl", {
|
|
27882
|
+
ts,
|
|
27883
|
+
category: "prompt",
|
|
27884
|
+
kind,
|
|
27885
|
+
bytes: body.length
|
|
27886
|
+
});
|
|
27887
|
+
}
|
|
27888
|
+
updateMeta(patch) {
|
|
27889
|
+
this.meta = {
|
|
27890
|
+
...this.meta,
|
|
27891
|
+
...patch
|
|
27892
|
+
};
|
|
27893
|
+
this.writeMeta();
|
|
27894
|
+
}
|
|
27895
|
+
finalize(outcome, info$1) {
|
|
27896
|
+
const endedAt = Date.now();
|
|
27897
|
+
this.updateMeta({
|
|
27898
|
+
outcome,
|
|
27899
|
+
endedAt: new Date(endedAt).toISOString(),
|
|
27900
|
+
durationMs: endedAt - this.startedAt,
|
|
27901
|
+
...info$1 ?? {}
|
|
27902
|
+
});
|
|
27903
|
+
this.log("session", `wizard session finished: ${outcome}`, info$1);
|
|
27904
|
+
}
|
|
27905
|
+
writeMeta() {
|
|
27906
|
+
try {
|
|
27907
|
+
writeFileSync(join(this.dir, "meta.json"), JSON.stringify(this.meta, null, 2));
|
|
27908
|
+
} catch {}
|
|
27909
|
+
}
|
|
27910
|
+
append(file, text) {
|
|
27911
|
+
try {
|
|
27912
|
+
appendFileSync(join(this.dir, file), text);
|
|
27913
|
+
} catch {}
|
|
27914
|
+
}
|
|
27915
|
+
appendJsonl(file, obj) {
|
|
27916
|
+
try {
|
|
27917
|
+
appendFileSync(join(this.dir, file), safeStringify(obj) + "\n");
|
|
27918
|
+
} catch {}
|
|
27919
|
+
}
|
|
27920
|
+
};
|
|
27921
|
+
/**
|
|
27922
|
+
* Safely stringify any value, falling back to a diagnostic shape when the
|
|
27923
|
+
* value contains circular references or non-serializable content.
|
|
27924
|
+
*/
|
|
27925
|
+
function safeStringify(value) {
|
|
27926
|
+
try {
|
|
27927
|
+
return JSON.stringify(value, replacer);
|
|
27928
|
+
} catch (err) {
|
|
27929
|
+
return JSON.stringify({
|
|
27930
|
+
__unserializable: true,
|
|
27931
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
27932
|
+
preview: String(value).slice(0, 500)
|
|
27933
|
+
});
|
|
27934
|
+
}
|
|
27935
|
+
}
|
|
27936
|
+
function replacer(_key, value) {
|
|
27937
|
+
if (value instanceof Error) return {
|
|
27938
|
+
name: value.name,
|
|
27939
|
+
message: value.message,
|
|
27940
|
+
stack: value.stack
|
|
27941
|
+
};
|
|
27942
|
+
if (typeof value === "bigint") return value.toString();
|
|
27943
|
+
if (typeof value === "function") return `[Function ${value.name || "anonymous"}]`;
|
|
27944
|
+
return value;
|
|
27945
|
+
}
|
|
27946
|
+
let active = null;
|
|
27947
|
+
function initSessionLogger(config) {
|
|
27948
|
+
active = new SessionLogger(config);
|
|
27949
|
+
return active;
|
|
27950
|
+
}
|
|
27951
|
+
/** Returns the active logger, or a no-op logger if none was initialized. */
|
|
27952
|
+
function getSessionLogger() {
|
|
27953
|
+
return active ?? noopLogger;
|
|
27954
|
+
}
|
|
27955
|
+
function getSessionLogDir() {
|
|
27956
|
+
return active?.dir ?? null;
|
|
27957
|
+
}
|
|
27958
|
+
const noopLogger = {
|
|
27959
|
+
dir: "",
|
|
27960
|
+
sessionId: "",
|
|
27961
|
+
log: () => {},
|
|
27962
|
+
logSdkMessage: () => {},
|
|
27963
|
+
logToolUse: () => {},
|
|
27964
|
+
logToolResult: () => {},
|
|
27965
|
+
logStoreEvent: () => {},
|
|
27966
|
+
logBuildStream: () => {},
|
|
27967
|
+
logPrompt: () => {},
|
|
27968
|
+
updateMeta: () => {},
|
|
27969
|
+
finalize: () => {}
|
|
27970
|
+
};
|
|
27971
|
+
|
|
27368
27972
|
//#endregion
|
|
27369
27973
|
//#region src/wizard/agent/agent-tools.ts
|
|
27370
27974
|
/**
|
|
27975
|
+
* Small in-process MCP server for sensitive wizard operations.
|
|
27976
|
+
* Only env var safety tools — everything else the agent does directly.
|
|
27977
|
+
*/
|
|
27978
|
+
/**
|
|
27371
27979
|
* Create the wizard-tools MCP server definition.
|
|
27372
27980
|
* Returns a tools map that the agent SDK can consume as an in-process MCP server.
|
|
27373
27981
|
*/
|
|
27374
27982
|
function createWizardToolsServer(config) {
|
|
27375
27983
|
return {
|
|
27376
27984
|
command: "node",
|
|
27377
|
-
args: ["-e", getWizardToolsScript(config.workingDirectory)],
|
|
27985
|
+
args: ["-e", getWizardToolsScript(config.workingDirectory, getSessionLogDir())],
|
|
27378
27986
|
env: {}
|
|
27379
27987
|
};
|
|
27380
27988
|
}
|
|
@@ -27382,13 +27990,26 @@ function createWizardToolsServer(config) {
|
|
|
27382
27990
|
* Generate a self-contained Node.js script that implements the wizard-tools MCP server.
|
|
27383
27991
|
* This runs as a child process communicating via stdio JSON-RPC.
|
|
27384
27992
|
*/
|
|
27385
|
-
function getWizardToolsScript(workingDirectory) {
|
|
27993
|
+
function getWizardToolsScript(workingDirectory, logDir) {
|
|
27386
27994
|
return `
|
|
27387
27995
|
'use strict';
|
|
27388
27996
|
const fs = require('fs');
|
|
27389
27997
|
const path = require('path');
|
|
27390
27998
|
|
|
27391
27999
|
const CWD = '${workingDirectory.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}';
|
|
28000
|
+
const LOG_DIR = '${logDir ? logDir.replace(/\\/g, "\\\\").replace(/'/g, "\\'") : ""}';
|
|
28001
|
+
|
|
28002
|
+
function logEvent(event, data) {
|
|
28003
|
+
if (!LOG_DIR) return;
|
|
28004
|
+
try {
|
|
28005
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), event, data }) + '\\n';
|
|
28006
|
+
fs.appendFileSync(path.join(LOG_DIR, 'mcp-tools.jsonl'), line);
|
|
28007
|
+
} catch {}
|
|
28008
|
+
}
|
|
28009
|
+
|
|
28010
|
+
logEvent('server_start', { cwd: CWD, pid: process.pid });
|
|
28011
|
+
process.on('exit', (code) => logEvent('server_exit', { code }));
|
|
28012
|
+
process.on('uncaughtException', (err) => logEvent('uncaught_exception', { message: String(err && err.message), stack: String(err && err.stack) }));
|
|
27392
28013
|
|
|
27393
28014
|
const TOOLS = {
|
|
27394
28015
|
check_env_keys: {
|
|
@@ -27434,7 +28055,7 @@ const TOOLS = {
|
|
|
27434
28055
|
},
|
|
27435
28056
|
},
|
|
27436
28057
|
set_helper_content: {
|
|
27437
|
-
description: '
|
|
28058
|
+
description: 'Renderizar um diagrama ASCII abaixo da lista de tarefas na TUI do wizard. OBRIGATÓRIO: chame esta ferramenta antes de veloz:deploy com um diagrama da arquitetura (framework, pipeline, porta, URL, serviços auxiliares). Aproveite a largura disponível do painel — veja o prompt do sistema para a largura alvo em colunas; evite diagramas estreitos que deixam metade do painel vazio. Cada chamada SUBSTITUI o conteúdo anterior — atualize ao final com o estado deployado.',
|
|
27438
28059
|
inputSchema: {
|
|
27439
28060
|
type: 'object',
|
|
27440
28061
|
properties: {
|
|
@@ -27602,13 +28223,12 @@ async function handlePromptUser(input) {
|
|
|
27602
28223
|
};
|
|
27603
28224
|
}
|
|
27604
28225
|
|
|
27605
|
-
// JSON-RPC
|
|
27606
|
-
//
|
|
27607
|
-
//
|
|
28226
|
+
// MCP stdio transport: newline-delimited JSON (one JSON-RPC message per line).
|
|
28227
|
+
// Not LSP Content-Length framing — the Claude Agent SDK rejects that and marks
|
|
28228
|
+
// the server as "failed", which is exactly what broke the wizard diagram/prompt.
|
|
27608
28229
|
|
|
27609
28230
|
function send(obj) {
|
|
27610
|
-
|
|
27611
|
-
process.stdout.write('Content-Length: ' + Buffer.byteLength(json) + '\\r\\n\\r\\n' + json);
|
|
28231
|
+
process.stdout.write(JSON.stringify(obj) + '\\n');
|
|
27612
28232
|
}
|
|
27613
28233
|
|
|
27614
28234
|
async function handleJsonRpc(msg) {
|
|
@@ -27639,68 +28259,45 @@ async function handleJsonRpc(msg) {
|
|
|
27639
28259
|
} else if (msg.method === 'tools/call') {
|
|
27640
28260
|
const name = msg.params.name;
|
|
27641
28261
|
const args = msg.params.arguments || {};
|
|
28262
|
+
logEvent('tool_call_start', { name, args });
|
|
27642
28263
|
let result;
|
|
27643
|
-
|
|
27644
|
-
|
|
27645
|
-
|
|
27646
|
-
|
|
27647
|
-
|
|
28264
|
+
try {
|
|
28265
|
+
if (name === 'check_env_keys') result = handleCheckEnvKeys(args);
|
|
28266
|
+
else if (name === 'set_env_values') result = handleSetEnvValues(args);
|
|
28267
|
+
else if (name === 'set_helper_content') result = { content: [{ type: 'text', text: 'Conteúdo do helper atualizado com ' + (args.lines || []).length + ' linhas.' }] };
|
|
28268
|
+
else if (name === 'prompt_user') result = await handlePromptUser(args);
|
|
28269
|
+
else result = { content: [{ type: 'text', text: 'Ferramenta desconhecida: ' + name }], isError: true };
|
|
28270
|
+
logEvent('tool_call_done', { name, isError: Boolean(result && result.isError) });
|
|
28271
|
+
} catch (err) {
|
|
28272
|
+
logEvent('tool_call_error', { name, message: String(err && err.message), stack: String(err && err.stack) });
|
|
28273
|
+
result = { content: [{ type: 'text', text: 'Erro: ' + String(err && err.message) }], isError: true };
|
|
28274
|
+
}
|
|
27648
28275
|
send({ jsonrpc: '2.0', id: msg.id, result });
|
|
27649
28276
|
} else if (msg.id) {
|
|
27650
28277
|
send({ jsonrpc: '2.0', id: msg.id, result: {} });
|
|
27651
28278
|
}
|
|
27652
28279
|
}
|
|
27653
28280
|
|
|
27654
|
-
//
|
|
27655
|
-
let
|
|
27656
|
-
|
|
27657
|
-
|
|
28281
|
+
// MCP stdio transport: read newline-delimited JSON-RPC messages from stdin.
|
|
28282
|
+
let stdinBuffer = '';
|
|
28283
|
+
process.stdin.setEncoding('utf-8');
|
|
27658
28284
|
process.stdin.on('data', (chunk) => {
|
|
27659
|
-
|
|
27660
|
-
|
|
27661
|
-
|
|
27662
|
-
|
|
27663
|
-
|
|
27664
|
-
|
|
27665
|
-
|
|
27666
|
-
|
|
27667
|
-
|
|
27668
|
-
|
|
27669
|
-
|
|
27670
|
-
|
|
27671
|
-
|
|
27672
|
-
if (match) {
|
|
27673
|
-
expectedLength = parseInt(match[1], 10);
|
|
27674
|
-
rawBuffer = rawBuffer.slice(headerEnd + 4); // Skip \\r\\n\\r\\n
|
|
27675
|
-
} else {
|
|
27676
|
-
// No Content-Length header — try line-based JSON (fallback)
|
|
27677
|
-
const lineEnd = rawBuffer.indexOf('\\n');
|
|
27678
|
-
if (lineEnd === -1) return;
|
|
27679
|
-
const line = rawBuffer.slice(0, lineEnd).toString('utf-8').trim();
|
|
27680
|
-
rawBuffer = rawBuffer.slice(lineEnd + 1);
|
|
27681
|
-
if (line) {
|
|
27682
|
-
try {
|
|
27683
|
-
const msg = JSON.parse(line);
|
|
27684
|
-
handleJsonRpc(msg).catch(() => {});
|
|
27685
|
-
} catch {}
|
|
27686
|
-
}
|
|
27687
|
-
continue;
|
|
27688
|
-
}
|
|
27689
|
-
}
|
|
27690
|
-
|
|
27691
|
-
if (expectedLength >= 0 && rawBuffer.length >= expectedLength) {
|
|
27692
|
-
const body = rawBuffer.slice(0, expectedLength).toString('utf-8');
|
|
27693
|
-
rawBuffer = rawBuffer.slice(expectedLength);
|
|
27694
|
-
expectedLength = -1;
|
|
27695
|
-
try {
|
|
27696
|
-
const msg = JSON.parse(body);
|
|
27697
|
-
handleJsonRpc(msg).catch(() => {});
|
|
27698
|
-
} catch {}
|
|
27699
|
-
} else {
|
|
27700
|
-
return; // Need more data
|
|
28285
|
+
stdinBuffer += chunk;
|
|
28286
|
+
let newlineIdx;
|
|
28287
|
+
while ((newlineIdx = stdinBuffer.indexOf('\\n')) !== -1) {
|
|
28288
|
+
const line = stdinBuffer.slice(0, newlineIdx).trim();
|
|
28289
|
+
stdinBuffer = stdinBuffer.slice(newlineIdx + 1);
|
|
28290
|
+
if (!line) continue;
|
|
28291
|
+
try {
|
|
28292
|
+
const msg = JSON.parse(line);
|
|
28293
|
+
handleJsonRpc(msg).catch((err) => {
|
|
28294
|
+
logEvent('handler_error', { message: String(err && err.message) });
|
|
28295
|
+
});
|
|
28296
|
+
} catch (err) {
|
|
28297
|
+
logEvent('parse_error', { message: String(err && err.message), line: line.slice(0, 200) });
|
|
27701
28298
|
}
|
|
27702
28299
|
}
|
|
27703
|
-
}
|
|
28300
|
+
});
|
|
27704
28301
|
`.trim();
|
|
27705
28302
|
}
|
|
27706
28303
|
|
|
@@ -27709,147 +28306,335 @@ function processBuffer() {
|
|
|
27709
28306
|
/**
|
|
27710
28307
|
* Build log streamer for the wizard.
|
|
27711
28308
|
*
|
|
27712
|
-
*
|
|
27713
|
-
*
|
|
27714
|
-
*
|
|
27715
|
-
|
|
27716
|
-
|
|
27717
|
-
*
|
|
27718
|
-
*
|
|
27719
|
-
|
|
27720
|
-
|
|
27721
|
-
|
|
27722
|
-
|
|
27723
|
-
|
|
27724
|
-
const
|
|
28309
|
+
* Triggered by `setBuildDeploymentId` — the deploy tool_result already emits
|
|
28310
|
+
* the deployment ID, so we skip the veloz.json round-trip and open the ORPC
|
|
28311
|
+
* log stream directly.
|
|
28312
|
+
*
|
|
28313
|
+
* Runs alongside the tool_result capture in agent-interface.ts (which pushes
|
|
28314
|
+
* raw progress messages). This module provides the live stderr-style build
|
|
28315
|
+
* output from the platform until the deployment reaches a terminal state.
|
|
28316
|
+
*/
|
|
28317
|
+
async function streamBuildLogs(config) {
|
|
28318
|
+
const { store, deploymentId } = config;
|
|
28319
|
+
const logger = getSessionLogger();
|
|
28320
|
+
logger.logBuildStream("start", { deploymentId });
|
|
28321
|
+
const client = createWizardClient(store);
|
|
28322
|
+
if (!client) {
|
|
28323
|
+
logger.logBuildStream("no_client", { reason: "missing apiKey" });
|
|
28324
|
+
store.pushBuildLog("[stream] aguardando autenticação para conectar ao build...");
|
|
28325
|
+
return;
|
|
28326
|
+
}
|
|
28327
|
+
store.setBuildStreamActive(true);
|
|
27725
28328
|
try {
|
|
27726
|
-
store
|
|
27727
|
-
|
|
27728
|
-
|
|
27729
|
-
|
|
27730
|
-
|
|
27731
|
-
}
|
|
27732
|
-
|
|
27733
|
-
if (serviceIds.length === 0) {
|
|
27734
|
-
store.setBuildStreamActive(false);
|
|
27735
|
-
return;
|
|
27736
|
-
}
|
|
27737
|
-
const deployment = await pollForNewDeployment(client, serviceIds);
|
|
27738
|
-
if (!deployment) {
|
|
27739
|
-
store.setBuildStreamActive(false);
|
|
27740
|
-
return;
|
|
27741
|
-
}
|
|
27742
|
-
store.setBuildDeploymentId(deployment.id);
|
|
27743
|
-
store.pushStatus("Build detectado — transmitindo logs...");
|
|
27744
|
-
await streamLogs(client, deployment.id, store);
|
|
27745
|
-
} catch {} finally {
|
|
28329
|
+
await openStream(client, deploymentId, store);
|
|
28330
|
+
logger.logBuildStream("stream_done", { deploymentId });
|
|
28331
|
+
} catch (err) {
|
|
28332
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28333
|
+
logger.logBuildStream("error", { message: msg });
|
|
28334
|
+
store.pushBuildLog(`[stream] erro: ${msg}`);
|
|
28335
|
+
} finally {
|
|
27746
28336
|
store.setBuildStreamActive(false);
|
|
27747
28337
|
}
|
|
27748
28338
|
}
|
|
27749
|
-
function createWizardClient(store
|
|
28339
|
+
function createWizardClient(store) {
|
|
27750
28340
|
const apiKey = store.session.apiKey;
|
|
27751
28341
|
if (!apiKey) return null;
|
|
27752
|
-
|
|
27753
|
-
let projectId = null;
|
|
27754
|
-
const configPath = join(cwd, "veloz.json");
|
|
27755
|
-
if (existsSync(configPath)) try {
|
|
27756
|
-
projectId = JSON.parse(readFileSync(configPath, "utf-8"))?.project?.id ?? null;
|
|
27757
|
-
} catch {}
|
|
27758
|
-
return createClient(apiUrl, () => {
|
|
28342
|
+
return createClient(process.env.VELOZ_API_URL || "https://api.onveloz.com", () => {
|
|
27759
28343
|
const headers = {
|
|
27760
28344
|
Authorization: `Bearer ${apiKey}`,
|
|
27761
28345
|
"User-Agent": "veloz-cli/wizard"
|
|
27762
28346
|
};
|
|
27763
28347
|
if (store.session.orgId) headers["X-Organization-Id"] = store.session.orgId;
|
|
27764
|
-
if (projectId) headers["X-Project-Id"] = projectId;
|
|
27765
28348
|
return headers;
|
|
27766
28349
|
});
|
|
27767
28350
|
}
|
|
27768
|
-
function
|
|
27769
|
-
const
|
|
27770
|
-
|
|
28351
|
+
async function openStream(client, deploymentId, store) {
|
|
28352
|
+
const logger = getSessionLogger();
|
|
28353
|
+
logger.logBuildStream("open_stream", { deploymentId });
|
|
27771
28354
|
try {
|
|
27772
|
-
const
|
|
27773
|
-
const
|
|
27774
|
-
|
|
27775
|
-
|
|
27776
|
-
|
|
27777
|
-
|
|
27778
|
-
|
|
27779
|
-
|
|
27780
|
-
|
|
27781
|
-
|
|
27782
|
-
|
|
27783
|
-
|
|
27784
|
-
async function pollForNewDeployment(client, serviceIds, maxWaitMs = 3e4) {
|
|
27785
|
-
const startTime = Date.now();
|
|
27786
|
-
const pollInterval = 2e3;
|
|
27787
|
-
while (Date.now() - startTime < maxWaitMs) {
|
|
27788
|
-
for (const serviceId of serviceIds) try {
|
|
27789
|
-
const deployments = await client.deployments.list({
|
|
27790
|
-
serviceId,
|
|
27791
|
-
limit: 1,
|
|
27792
|
-
offset: 0
|
|
27793
|
-
});
|
|
27794
|
-
if (deployments.length > 0) {
|
|
27795
|
-
const d = deployments[0];
|
|
27796
|
-
const status = d.status;
|
|
27797
|
-
if (status === "QUEUED" || status === "BUILDING" || status === "DEPLOYING") return {
|
|
27798
|
-
id: d.id,
|
|
27799
|
-
serviceId,
|
|
27800
|
-
status
|
|
27801
|
-
};
|
|
28355
|
+
const stream = await client.logs.streamBuildLogs({ deploymentId });
|
|
28356
|
+
for await (const event of stream) {
|
|
28357
|
+
logger.logBuildStream("event", event);
|
|
28358
|
+
if (event.type === "status") {
|
|
28359
|
+
const label = statusLabels[event.content] ?? event.content;
|
|
28360
|
+
store.pushBuildLog(`[status] ${label}`);
|
|
28361
|
+
if (event.content === "LIVE") store.pushStatus("Deploy concluído — serviço ativo!");
|
|
28362
|
+
else if (event.content === "BUILD_FAILED" || event.content === "DEPLOY_FAILED") store.pushStatus(`Deploy falhou: ${label}`);
|
|
28363
|
+
if (TERMINAL_STATUSES.has(event.content)) return;
|
|
28364
|
+
} else if (event.type === "log") for (const line of event.content.split("\n")) {
|
|
28365
|
+
const display = formatLogLine(line);
|
|
28366
|
+
if (display) store.pushBuildLog(display);
|
|
27802
28367
|
}
|
|
27803
|
-
}
|
|
27804
|
-
|
|
28368
|
+
}
|
|
28369
|
+
} catch (err) {
|
|
28370
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28371
|
+
store.pushBuildLog(`[stream] desconectou: ${msg} — buscando logs salvos`);
|
|
28372
|
+
await fetchSavedLogs(client, deploymentId, store);
|
|
27805
28373
|
}
|
|
27806
|
-
return null;
|
|
27807
28374
|
}
|
|
27808
|
-
|
|
28375
|
+
/**
|
|
28376
|
+
* One-shot fetch of persisted build logs — used when the live stream breaks
|
|
28377
|
+
* (e.g. deployment finished before we attached).
|
|
28378
|
+
*/
|
|
28379
|
+
async function fetchSavedLogs(client, deploymentId, store) {
|
|
27809
28380
|
try {
|
|
27810
|
-
const
|
|
27811
|
-
|
|
27812
|
-
|
|
27813
|
-
|
|
27814
|
-
if (
|
|
27815
|
-
else if (event.content === "BUILD_FAILED" || event.content === "DEPLOY_FAILED") store.pushStatus(`Deploy falhou: ${label}`);
|
|
27816
|
-
} else if (event.type === "log") {
|
|
27817
|
-
const lines = event.content.split("\n");
|
|
27818
|
-
for (const line of lines) {
|
|
27819
|
-
const trimmed = line.trim();
|
|
27820
|
-
if (!trimmed) continue;
|
|
27821
|
-
if (isHiddenMessage(trimmed)) continue;
|
|
27822
|
-
const parsed = parseBuildLine(trimmed);
|
|
27823
|
-
let displayLine = null;
|
|
27824
|
-
switch (parsed.kind) {
|
|
27825
|
-
case "step":
|
|
27826
|
-
displayLine = `[${parsed.stage} ${parsed.step}/${parsed.total}] ${parsed.command}`;
|
|
27827
|
-
break;
|
|
27828
|
-
case "cached":
|
|
27829
|
-
displayLine = `#${parsed.stepNum} CACHED`;
|
|
27830
|
-
break;
|
|
27831
|
-
case "done":
|
|
27832
|
-
displayLine = `#${parsed.stepNum} done ${parsed.duration}`;
|
|
27833
|
-
break;
|
|
27834
|
-
case "output":
|
|
27835
|
-
displayLine = cleanDisplayLine(parsed.text);
|
|
27836
|
-
break;
|
|
27837
|
-
case "platform":
|
|
27838
|
-
displayLine = cleanDisplayLine(parsed.message);
|
|
27839
|
-
break;
|
|
27840
|
-
case "other":
|
|
27841
|
-
displayLine = cleanDisplayLine(parsed.text);
|
|
27842
|
-
break;
|
|
27843
|
-
}
|
|
27844
|
-
if (displayLine) store.pushBuildLog(displayLine);
|
|
27845
|
-
}
|
|
28381
|
+
const logs = await client.logs.getBuildLogs({ deploymentId });
|
|
28382
|
+
if (!logs.buildLogs) return;
|
|
28383
|
+
for (const line of logs.buildLogs.split("\n")) {
|
|
28384
|
+
const display = formatLogLine(line);
|
|
28385
|
+
if (display) store.pushBuildLog(display);
|
|
27846
28386
|
}
|
|
27847
|
-
} catch {
|
|
28387
|
+
} catch (err) {
|
|
28388
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28389
|
+
store.pushBuildLog(`[stream] falha ao buscar logs salvos: ${msg}`);
|
|
28390
|
+
}
|
|
27848
28391
|
}
|
|
27849
|
-
function
|
|
27850
|
-
|
|
27851
|
-
|
|
27852
|
-
|
|
28392
|
+
function formatLogLine(line) {
|
|
28393
|
+
const trimmed = line.trim();
|
|
28394
|
+
if (!trimmed) return null;
|
|
28395
|
+
if (isHiddenMessage(trimmed)) return null;
|
|
28396
|
+
const parsed = parseBuildLine(trimmed);
|
|
28397
|
+
switch (parsed.kind) {
|
|
28398
|
+
case "step": return `[${parsed.stage} ${parsed.step}/${parsed.total}] ${parsed.command}`;
|
|
28399
|
+
case "cached": return `#${parsed.stepNum} CACHED`;
|
|
28400
|
+
case "done": return `#${parsed.stepNum} done ${parsed.duration}`;
|
|
28401
|
+
case "output": return cleanDisplayLine(parsed.text);
|
|
28402
|
+
case "platform": return cleanDisplayLine(parsed.message);
|
|
28403
|
+
case "other": return cleanDisplayLine(parsed.text);
|
|
28404
|
+
}
|
|
28405
|
+
}
|
|
28406
|
+
|
|
28407
|
+
//#endregion
|
|
28408
|
+
//#region src/wizard/agent/permissions.ts
|
|
28409
|
+
const PACKAGE_MANAGERS = [
|
|
28410
|
+
"npm",
|
|
28411
|
+
"pnpm",
|
|
28412
|
+
"yarn",
|
|
28413
|
+
"bun",
|
|
28414
|
+
"npx",
|
|
28415
|
+
"pnpx",
|
|
28416
|
+
"yarnpkg"
|
|
28417
|
+
];
|
|
28418
|
+
const SAFE_SCRIPTS = [
|
|
28419
|
+
"install",
|
|
28420
|
+
"add",
|
|
28421
|
+
"ci",
|
|
28422
|
+
"build",
|
|
28423
|
+
"tsc",
|
|
28424
|
+
"typecheck",
|
|
28425
|
+
"type-check",
|
|
28426
|
+
"check-types",
|
|
28427
|
+
"types",
|
|
28428
|
+
"lint",
|
|
28429
|
+
"format",
|
|
28430
|
+
"test"
|
|
28431
|
+
];
|
|
28432
|
+
const READ_ONLY_COMMANDS = [
|
|
28433
|
+
"ls",
|
|
28434
|
+
"pwd",
|
|
28435
|
+
"cat",
|
|
28436
|
+
"head",
|
|
28437
|
+
"tail",
|
|
28438
|
+
"echo",
|
|
28439
|
+
"which",
|
|
28440
|
+
"node",
|
|
28441
|
+
"env",
|
|
28442
|
+
"printenv",
|
|
28443
|
+
"find",
|
|
28444
|
+
"test",
|
|
28445
|
+
"stat"
|
|
28446
|
+
];
|
|
28447
|
+
const GIT_READ_SUBCOMMANDS = [
|
|
28448
|
+
"status",
|
|
28449
|
+
"log",
|
|
28450
|
+
"diff",
|
|
28451
|
+
"show",
|
|
28452
|
+
"branch",
|
|
28453
|
+
"remote",
|
|
28454
|
+
"config",
|
|
28455
|
+
"rev-parse",
|
|
28456
|
+
"ls-files"
|
|
28457
|
+
];
|
|
28458
|
+
/**
|
|
28459
|
+
* Read-only subcommand allowlists for third-party platform CLIs.
|
|
28460
|
+
* The wizard inspects existing deploys (Vercel, Railway, Render, Fly, Cloudflare, AWS)
|
|
28461
|
+
* to migrate config to Veloz — it must never mutate those platforms.
|
|
28462
|
+
*/
|
|
28463
|
+
const PLATFORM_READ_SUBCOMMANDS = {
|
|
28464
|
+
vercel: [
|
|
28465
|
+
"env",
|
|
28466
|
+
"project",
|
|
28467
|
+
"projects",
|
|
28468
|
+
"domains",
|
|
28469
|
+
"teams",
|
|
28470
|
+
"whoami",
|
|
28471
|
+
"inspect",
|
|
28472
|
+
"list",
|
|
28473
|
+
"ls",
|
|
28474
|
+
"logs"
|
|
28475
|
+
],
|
|
28476
|
+
vc: [
|
|
28477
|
+
"env",
|
|
28478
|
+
"project",
|
|
28479
|
+
"projects",
|
|
28480
|
+
"domains",
|
|
28481
|
+
"teams",
|
|
28482
|
+
"whoami",
|
|
28483
|
+
"inspect",
|
|
28484
|
+
"list",
|
|
28485
|
+
"ls",
|
|
28486
|
+
"logs"
|
|
28487
|
+
],
|
|
28488
|
+
railway: [
|
|
28489
|
+
"status",
|
|
28490
|
+
"variables",
|
|
28491
|
+
"list",
|
|
28492
|
+
"whoami",
|
|
28493
|
+
"environment",
|
|
28494
|
+
"service",
|
|
28495
|
+
"logs"
|
|
28496
|
+
],
|
|
28497
|
+
render: [
|
|
28498
|
+
"services",
|
|
28499
|
+
"env-groups",
|
|
28500
|
+
"list",
|
|
28501
|
+
"whoami",
|
|
28502
|
+
"logs"
|
|
28503
|
+
],
|
|
28504
|
+
fly: [
|
|
28505
|
+
"status",
|
|
28506
|
+
"apps",
|
|
28507
|
+
"secrets",
|
|
28508
|
+
"config",
|
|
28509
|
+
"regions",
|
|
28510
|
+
"info",
|
|
28511
|
+
"logs",
|
|
28512
|
+
"list",
|
|
28513
|
+
"ips",
|
|
28514
|
+
"volumes"
|
|
28515
|
+
],
|
|
28516
|
+
flyctl: [
|
|
28517
|
+
"status",
|
|
28518
|
+
"apps",
|
|
28519
|
+
"secrets",
|
|
28520
|
+
"config",
|
|
28521
|
+
"regions",
|
|
28522
|
+
"info",
|
|
28523
|
+
"logs",
|
|
28524
|
+
"list",
|
|
28525
|
+
"ips",
|
|
28526
|
+
"volumes"
|
|
28527
|
+
],
|
|
28528
|
+
wrangler: [
|
|
28529
|
+
"whoami",
|
|
28530
|
+
"secret",
|
|
28531
|
+
"kv:namespace",
|
|
28532
|
+
"kv",
|
|
28533
|
+
"r2",
|
|
28534
|
+
"d1",
|
|
28535
|
+
"pages",
|
|
28536
|
+
"tail",
|
|
28537
|
+
"deployments"
|
|
28538
|
+
],
|
|
28539
|
+
aws: [
|
|
28540
|
+
"s3",
|
|
28541
|
+
"ec2",
|
|
28542
|
+
"iam",
|
|
28543
|
+
"sts",
|
|
28544
|
+
"ecr",
|
|
28545
|
+
"ecs",
|
|
28546
|
+
"eks",
|
|
28547
|
+
"lambda",
|
|
28548
|
+
"rds",
|
|
28549
|
+
"route53",
|
|
28550
|
+
"cloudformation",
|
|
28551
|
+
"ssm",
|
|
28552
|
+
"secretsmanager"
|
|
28553
|
+
]
|
|
28554
|
+
};
|
|
28555
|
+
const AWS_READ_VERBS_REGEX = /^(describe-|list-|get-|show-|head-|read-|search-|lookup-|scan$|query$|ls$|whoami$|info$|status$)/;
|
|
28556
|
+
const DANGEROUS_OPERATORS = /[;`$()]/;
|
|
28557
|
+
function matchesPackageManager(command) {
|
|
28558
|
+
const parts = command.split(/\s+/);
|
|
28559
|
+
if (parts.length === 0 || !PACKAGE_MANAGERS.includes(parts[0])) return false;
|
|
28560
|
+
let scriptIndex = 1;
|
|
28561
|
+
if (parts[scriptIndex] === "run" || parts[scriptIndex] === "exec") scriptIndex++;
|
|
28562
|
+
const scriptPart = parts.slice(scriptIndex).join(" ");
|
|
28563
|
+
return SAFE_SCRIPTS.some((s) => scriptPart.startsWith(s));
|
|
28564
|
+
}
|
|
28565
|
+
function matchesVeloz(command) {
|
|
28566
|
+
return /^veloz(\s|$)/.test(command);
|
|
28567
|
+
}
|
|
28568
|
+
function matchesGitRead(command) {
|
|
28569
|
+
const parts = command.split(/\s+/);
|
|
28570
|
+
if (parts[0] !== "git" || !parts[1]) return false;
|
|
28571
|
+
return GIT_READ_SUBCOMMANDS.includes(parts[1]);
|
|
28572
|
+
}
|
|
28573
|
+
function matchesReadOnly(command) {
|
|
28574
|
+
const first = command.split(/\s+/)[0] ?? "";
|
|
28575
|
+
return READ_ONLY_COMMANDS.includes(first);
|
|
28576
|
+
}
|
|
28577
|
+
/**
|
|
28578
|
+
* Match read-only subcommands on third-party platform CLIs.
|
|
28579
|
+
* e.g. `vercel env ls`, `fly secrets list`, `aws s3 ls`.
|
|
28580
|
+
*/
|
|
28581
|
+
function matchesPlatformRead(command) {
|
|
28582
|
+
const parts = command.split(/\s+/);
|
|
28583
|
+
const cli$1 = parts[0] ?? "";
|
|
28584
|
+
const sub = parts[1] ?? "";
|
|
28585
|
+
const allowed = PLATFORM_READ_SUBCOMMANDS[cli$1];
|
|
28586
|
+
if (!allowed) return false;
|
|
28587
|
+
if (!sub || sub.startsWith("-")) return true;
|
|
28588
|
+
if (!allowed.includes(sub)) return false;
|
|
28589
|
+
if (cli$1 === "aws") {
|
|
28590
|
+
const verb = parts[2] ?? "";
|
|
28591
|
+
if (!verb) return true;
|
|
28592
|
+
return AWS_READ_VERBS_REGEX.test(verb);
|
|
28593
|
+
}
|
|
28594
|
+
return true;
|
|
28595
|
+
}
|
|
28596
|
+
function isAllowedBashCommand(command) {
|
|
28597
|
+
return matchesVeloz(command) || matchesGitRead(command) || matchesPackageManager(command) || matchesReadOnly(command) || matchesPlatformRead(command);
|
|
28598
|
+
}
|
|
28599
|
+
function wizardCanUseTool(toolName, input) {
|
|
28600
|
+
if (toolName.startsWith("veloz:") || toolName.startsWith("wizard-tools:") || toolName.startsWith("mcp__")) return {
|
|
28601
|
+
behavior: "allow",
|
|
28602
|
+
updatedInput: input
|
|
28603
|
+
};
|
|
28604
|
+
if (toolName !== "Bash") return {
|
|
28605
|
+
behavior: "allow",
|
|
28606
|
+
updatedInput: input
|
|
28607
|
+
};
|
|
28608
|
+
const command = (typeof input.command === "string" ? input.command : "").trim();
|
|
28609
|
+
if (DANGEROUS_OPERATORS.test(command)) return {
|
|
28610
|
+
behavior: "deny",
|
|
28611
|
+
message: "Operadores shell como ; ` $ ( ) não são permitidos."
|
|
28612
|
+
};
|
|
28613
|
+
const normalized = command.replace(/\s*\d*>&\d+\s*/g, " ").trim();
|
|
28614
|
+
const pipeMatch = normalized.match(/^(.+?)\s*\|\s*(tail|head)(\s+\S+)*\s*$/);
|
|
28615
|
+
if (pipeMatch) {
|
|
28616
|
+
const base = pipeMatch[1].trim();
|
|
28617
|
+
if (/[|&]/.test(base)) return {
|
|
28618
|
+
behavior: "deny",
|
|
28619
|
+
message: "Múltiplos pipes não são permitidos. Apenas | tail/head."
|
|
28620
|
+
};
|
|
28621
|
+
if (isAllowedBashCommand(base)) return {
|
|
28622
|
+
behavior: "allow",
|
|
28623
|
+
updatedInput: input
|
|
28624
|
+
};
|
|
28625
|
+
}
|
|
28626
|
+
if (/[|&]/.test(normalized)) return {
|
|
28627
|
+
behavior: "deny",
|
|
28628
|
+
message: "Pipes só são permitidos com tail/head para limitar saída."
|
|
28629
|
+
};
|
|
28630
|
+
if (isAllowedBashCommand(normalized)) return {
|
|
28631
|
+
behavior: "allow",
|
|
28632
|
+
updatedInput: input
|
|
28633
|
+
};
|
|
28634
|
+
return {
|
|
28635
|
+
behavior: "deny",
|
|
28636
|
+
message: "Comando não permitido. Use veloz, git (leitura), gerenciadores de pacote (install/build/typecheck/lint/test), ou comandos somente-leitura."
|
|
28637
|
+
};
|
|
27853
28638
|
}
|
|
27854
28639
|
|
|
27855
28640
|
//#endregion
|
|
@@ -27881,6 +28666,15 @@ async function getSDKModule() {
|
|
|
27881
28666
|
*/
|
|
27882
28667
|
async function runAgent(config) {
|
|
27883
28668
|
const { store, cwd } = config;
|
|
28669
|
+
const logger = getSessionLogger();
|
|
28670
|
+
logger.log("agent", "runAgent start", {
|
|
28671
|
+
framework: config.framework,
|
|
28672
|
+
frameworkLabel: config.frameworkLabel,
|
|
28673
|
+
packageManager: config.packageManager,
|
|
28674
|
+
userName: config.userName,
|
|
28675
|
+
orgName: config.orgName,
|
|
28676
|
+
hasNotes: Boolean(config.userNotes)
|
|
28677
|
+
});
|
|
27884
28678
|
let attempt = 0;
|
|
27885
28679
|
let lastError = null;
|
|
27886
28680
|
const cleanupPromptFiles = () => {
|
|
@@ -27895,21 +28689,29 @@ async function runAgent(config) {
|
|
|
27895
28689
|
while (attempt <= MAX_RETRIES) {
|
|
27896
28690
|
const isRetry = attempt > 0;
|
|
27897
28691
|
if (isRetry) {
|
|
28692
|
+
logger.log("agent", "retrying agent run", {
|
|
28693
|
+
attempt: attempt + 1,
|
|
28694
|
+
lastError
|
|
28695
|
+
});
|
|
27898
28696
|
store.incrementRetry();
|
|
27899
28697
|
store.setAgentPhase("healing");
|
|
27900
28698
|
store.pushStatus(`Agente reiniciando — tentativa ${attempt + 1} de ${MAX_RETRIES + 1}...`);
|
|
27901
28699
|
store.pushAgentOutput(`--- Reiniciando agente (tentativa ${attempt + 1}) ---`);
|
|
27902
28700
|
await sleep(2e3);
|
|
27903
28701
|
}
|
|
28702
|
+
logger.log("agent", `invoking runAgentOnce (attempt ${attempt + 1})`);
|
|
27904
28703
|
const result$2 = await runAgentOnce(config, isRetry, lastError);
|
|
28704
|
+
logger.log("agent", "runAgentOnce returned", result$2);
|
|
27905
28705
|
if (result$2.success) return;
|
|
27906
28706
|
if (result$2.fatal) {
|
|
28707
|
+
logger.log("agent", "fatal error — not retrying", { error: result$2.error });
|
|
27907
28708
|
store.failAgent(result$2.error ?? "Erro fatal");
|
|
27908
28709
|
return;
|
|
27909
28710
|
}
|
|
27910
28711
|
lastError = result$2.error ?? "Processo do agente encerrou inesperadamente";
|
|
27911
28712
|
attempt++;
|
|
27912
28713
|
if (attempt > MAX_RETRIES) {
|
|
28714
|
+
logger.log("agent", "retries exhausted", { lastError });
|
|
27913
28715
|
store.failAgent(`O agente não conseguiu completar após ${MAX_RETRIES + 1} tentativas. Último erro: ${lastError}. Tente novamente com \`veloz init\` ou faça o deploy manualmente com \`veloz deploy\`.`);
|
|
27914
28716
|
return;
|
|
27915
28717
|
}
|
|
@@ -27921,19 +28723,27 @@ async function runAgent(config) {
|
|
|
27921
28723
|
*/
|
|
27922
28724
|
async function runAgentOnce(config, isRetry, previousError) {
|
|
27923
28725
|
const { store, cwd } = config;
|
|
28726
|
+
const logger = getSessionLogger();
|
|
27924
28727
|
resetBuildStreamState();
|
|
27925
28728
|
const query = (await getSDKModule()).query;
|
|
28729
|
+
logger.log("agent", "SDK module loaded");
|
|
27926
28730
|
const promptCtx = {
|
|
27927
28731
|
cwd,
|
|
27928
28732
|
framework: config.framework,
|
|
27929
28733
|
frameworkLabel: config.frameworkLabel,
|
|
27930
28734
|
userName: config.userName,
|
|
27931
28735
|
orgName: config.orgName,
|
|
27932
|
-
packageManager: config.packageManager
|
|
28736
|
+
packageManager: config.packageManager,
|
|
28737
|
+
userNotes: config.userNotes ?? null
|
|
27933
28738
|
};
|
|
27934
28739
|
const prompt$1 = isRetry ? assembleContinuationPrompt(promptCtx, previousError) : assemblePrompt(promptCtx);
|
|
28740
|
+
logger.logPrompt(isRetry ? "continuation" : "initial", prompt$1, {
|
|
28741
|
+
...promptCtx,
|
|
28742
|
+
previousError: isRetry ? previousError : void 0
|
|
28743
|
+
});
|
|
27935
28744
|
const commandments = getCommandments();
|
|
27936
28745
|
const wizardTools = createWizardToolsServer({ workingDirectory: cwd });
|
|
28746
|
+
logger.log("agent", "wizard-tools MCP server config created");
|
|
27937
28747
|
const mcpServers = {
|
|
27938
28748
|
veloz: {
|
|
27939
28749
|
command: "npx",
|
|
@@ -28016,6 +28826,8 @@ async function runAgentOnce(config, isRetry, previousError) {
|
|
|
28016
28826
|
"TodoWrite",
|
|
28017
28827
|
"TodoRead"
|
|
28018
28828
|
],
|
|
28829
|
+
disallowedTools: ["AskUserQuestion"],
|
|
28830
|
+
canUseTool: (toolName, input) => Promise.resolve(wizardCanUseTool(toolName, input)),
|
|
28019
28831
|
systemPrompt: {
|
|
28020
28832
|
type: "preset",
|
|
28021
28833
|
preset: "claude_code",
|
|
@@ -28031,18 +28843,11 @@ async function runAgentOnce(config, isRetry, previousError) {
|
|
|
28031
28843
|
timeout: 30
|
|
28032
28844
|
}],
|
|
28033
28845
|
PostToolUse: [{
|
|
28034
|
-
matcher: /^veloz:deploy$/,
|
|
28846
|
+
matcher: /^(mcp__veloz__deploy|veloz:deploy)$/,
|
|
28035
28847
|
hooks: [() => {
|
|
28036
28848
|
if (store.session.agentPhase !== "deploying") {
|
|
28037
28849
|
store.setAgentPhase("deploying");
|
|
28038
28850
|
store.pushStatus("Deploy iniciado...");
|
|
28039
|
-
if (!buildStreamStarted) {
|
|
28040
|
-
buildStreamStarted = true;
|
|
28041
|
-
startBuildLogStream({
|
|
28042
|
-
store,
|
|
28043
|
-
cwd
|
|
28044
|
-
}).catch(() => {});
|
|
28045
|
-
}
|
|
28046
28851
|
}
|
|
28047
28852
|
return {};
|
|
28048
28853
|
}]
|
|
@@ -28051,7 +28856,8 @@ async function runAgentOnce(config, isRetry, previousError) {
|
|
|
28051
28856
|
}
|
|
28052
28857
|
});
|
|
28053
28858
|
for await (const message of agentResponse) {
|
|
28054
|
-
|
|
28859
|
+
logger.logSdkMessage(message);
|
|
28860
|
+
handleMessage(message, store, collectedText, (toolUseId, question) => {
|
|
28055
28861
|
handleAskUserQuestion(store, cwd, toolUseId, question).then((response) => {
|
|
28056
28862
|
enqueueUserResponse({
|
|
28057
28863
|
type: "user",
|
|
@@ -28076,33 +28882,73 @@ async function runAgentOnce(config, isRetry, previousError) {
|
|
|
28076
28882
|
});
|
|
28077
28883
|
if (message.type === "result") {
|
|
28078
28884
|
if (message.subtype === "success" && !message.is_error) receivedSuccessResult = true;
|
|
28885
|
+
logger.log("agent", "SDK result received", {
|
|
28886
|
+
subtype: message.subtype,
|
|
28887
|
+
is_error: message.is_error,
|
|
28888
|
+
errors: message.errors
|
|
28889
|
+
});
|
|
28079
28890
|
signalDone();
|
|
28080
28891
|
}
|
|
28081
28892
|
}
|
|
28893
|
+
const outputText = collectedText.join("\n");
|
|
28894
|
+
logger.log("agent", "agent stream ended", {
|
|
28895
|
+
outputBytes: outputText.length,
|
|
28896
|
+
receivedSuccessResult
|
|
28897
|
+
});
|
|
28898
|
+
const userCodeError = extractUserCodeError(outputText);
|
|
28899
|
+
if (userCodeError) {
|
|
28900
|
+
logger.log("agent", "user-code build failure detected", { error: userCodeError });
|
|
28901
|
+
return {
|
|
28902
|
+
success: false,
|
|
28903
|
+
fatal: true,
|
|
28904
|
+
error: userCodeError
|
|
28905
|
+
};
|
|
28906
|
+
}
|
|
28907
|
+
const cancellation = extractCancellation(outputText);
|
|
28908
|
+
if (cancellation) {
|
|
28909
|
+
logger.log("agent", "user cancelled deploy", { summary: cancellation });
|
|
28910
|
+
return {
|
|
28911
|
+
success: false,
|
|
28912
|
+
fatal: true,
|
|
28913
|
+
error: cancellation
|
|
28914
|
+
};
|
|
28915
|
+
}
|
|
28082
28916
|
if (receivedSuccessResult) {
|
|
28917
|
+
logger.log("agent", "deploy reported success");
|
|
28083
28918
|
store.pushStatus("Deploy concluído com sucesso!");
|
|
28084
28919
|
store.completeAgent();
|
|
28085
28920
|
return { success: true };
|
|
28086
28921
|
}
|
|
28087
|
-
|
|
28088
|
-
|
|
28089
|
-
|
|
28090
|
-
|
|
28091
|
-
|
|
28092
|
-
|
|
28093
|
-
|
|
28094
|
-
|
|
28095
|
-
|
|
28096
|
-
|
|
28922
|
+
if (outputText.includes("API Error: 401") || outputText.includes("API Error: 403")) {
|
|
28923
|
+
logger.log("agent", "auth error detected in output");
|
|
28924
|
+
return {
|
|
28925
|
+
success: false,
|
|
28926
|
+
fatal: true,
|
|
28927
|
+
error: "Erro de autenticação. Faça login novamente com `veloz login`."
|
|
28928
|
+
};
|
|
28929
|
+
}
|
|
28930
|
+
if (outputText.includes("API Error:")) {
|
|
28931
|
+
logger.log("agent", "API error detected in output");
|
|
28932
|
+
return {
|
|
28933
|
+
success: false,
|
|
28934
|
+
error: "Erro de API durante o deploy."
|
|
28935
|
+
};
|
|
28936
|
+
}
|
|
28937
|
+
logger.log("agent", "no explicit success signal — treating as success");
|
|
28097
28938
|
store.completeAgent();
|
|
28098
28939
|
return { success: true };
|
|
28099
28940
|
} catch (error) {
|
|
28100
28941
|
signalDone();
|
|
28101
28942
|
if (receivedSuccessResult) {
|
|
28943
|
+
logger.log("agent", "post-success error ignored", { error: error instanceof Error ? error.message : String(error) });
|
|
28102
28944
|
store.completeAgent();
|
|
28103
28945
|
return { success: true };
|
|
28104
28946
|
}
|
|
28105
28947
|
const errorMessage = error instanceof Error ? error.message : "Erro desconhecido";
|
|
28948
|
+
logger.log("agent", "agent threw", {
|
|
28949
|
+
message: errorMessage,
|
|
28950
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
28951
|
+
});
|
|
28106
28952
|
if (errorMessage.includes("ANTHROPIC_API_KEY") || errorMessage.includes("authentication")) return {
|
|
28107
28953
|
success: false,
|
|
28108
28954
|
fatal: true,
|
|
@@ -28117,6 +28963,25 @@ async function runAgentOnce(config, isRetry, previousError) {
|
|
|
28117
28963
|
}
|
|
28118
28964
|
}
|
|
28119
28965
|
/**
|
|
28966
|
+
* Extract a user-code build failure report emitted by the agent.
|
|
28967
|
+
* The agent is instructed to emit `[BUILD_FAILED_USER_CODE]` followed by
|
|
28968
|
+
* a detailed explanation (motivo, trecho dos logs, passos para corrigir).
|
|
28969
|
+
* Returns the detailed body, or null if the signal wasn't emitted.
|
|
28970
|
+
*/
|
|
28971
|
+
function extractUserCodeError(text) {
|
|
28972
|
+
const match$3 = text.match(/\[BUILD_FAILED_USER_CODE\]\s*([\s\S]+?)(?:\n\[(?:STATUS|SUMMARY)\]|$)/);
|
|
28973
|
+
if (!match$3?.[1]) return null;
|
|
28974
|
+
const body = match$3[1].trim();
|
|
28975
|
+
return body.length > 0 ? body : null;
|
|
28976
|
+
}
|
|
28977
|
+
/**
|
|
28978
|
+
* Extract a deploy cancellation summary emitted by the agent when the user
|
|
28979
|
+
* declined the pre-deploy confirmation prompt.
|
|
28980
|
+
*/
|
|
28981
|
+
function extractCancellation(text) {
|
|
28982
|
+
return text.match(/\[SUMMARY\]\s*(Deploy cancelado[^\n]*)/i)?.[1]?.trim() ?? null;
|
|
28983
|
+
}
|
|
28984
|
+
/**
|
|
28120
28985
|
* Build a continuation prompt for retries. Tells the agent to inspect
|
|
28121
28986
|
* the current project state and continue from where the previous run left off.
|
|
28122
28987
|
*/
|
|
@@ -28148,13 +29013,31 @@ Antes de qualquer coisa, verifique o que já foi feito:
|
|
|
28148
29013
|
- Framework: ${ctx.frameworkLabel}
|
|
28149
29014
|
- Package manager: ${ctx.packageManager}
|
|
28150
29015
|
- Autenticado como: ${ctx.userName} (org: ${ctx.orgName})
|
|
28151
|
-
|
|
29016
|
+
${ctx.userNotes && ctx.userNotes.trim() ? `
|
|
29017
|
+
## Notas do Usuário (prioridade máxima — respeitar sempre)
|
|
29018
|
+
${ctx.userNotes.trim()}
|
|
29019
|
+
` : ""}
|
|
28152
29020
|
## Regras
|
|
28153
29021
|
- Use TodoWrite para reportar progresso
|
|
28154
29022
|
- NUNCA desista — diagnostique e corrija qualquer problema
|
|
28155
29023
|
- Comunique-se em português (pt-BR)
|
|
28156
29024
|
- Se algo falhar, leia os logs e tente uma abordagem diferente`;
|
|
28157
29025
|
}
|
|
29026
|
+
/**
|
|
29027
|
+
* Normalize MCP tool names to the `server:tool` shorthand used in prompts.
|
|
29028
|
+
*
|
|
29029
|
+
* The Claude Agent SDK emits MCP tool_use blocks with names like
|
|
29030
|
+
* `mcp__wizard-tools__set_helper_content`. Our interception logic (and the
|
|
29031
|
+
* PostToolUse hook matcher) is written against the `wizard-tools:set_helper_content`
|
|
29032
|
+
* shorthand used throughout the prompt, so we translate here.
|
|
29033
|
+
*/
|
|
29034
|
+
function normalizeMcpToolName(name) {
|
|
29035
|
+
if (!name.startsWith("mcp__")) return name;
|
|
29036
|
+
const rest = name.slice(5);
|
|
29037
|
+
const sep = rest.indexOf("__");
|
|
29038
|
+
if (sep < 0) return name;
|
|
29039
|
+
return `${rest.slice(0, sep)}:${rest.slice(sep + 2)}`;
|
|
29040
|
+
}
|
|
28158
29041
|
/** Human-readable labels for common tools. */
|
|
28159
29042
|
const TOOL_LABELS = {
|
|
28160
29043
|
Read: "Lendo arquivo",
|
|
@@ -28166,11 +29049,23 @@ const TOOL_LABELS = {
|
|
|
28166
29049
|
TodoWrite: "Atualizando tarefas",
|
|
28167
29050
|
TodoRead: "Lendo tarefas"
|
|
28168
29051
|
};
|
|
28169
|
-
/**
|
|
28170
|
-
|
|
29052
|
+
/** Tool-use IDs for in-flight deploy invocations — used to forward tool_result output. */
|
|
29053
|
+
const deployToolUseIds = /* @__PURE__ */ new Set();
|
|
29054
|
+
/** Deployment IDs we've already opened a log stream for in this run. */
|
|
29055
|
+
const streamedDeploymentIds = /* @__PURE__ */ new Set();
|
|
28171
29056
|
/** Reset build stream state for a new agent run. */
|
|
28172
29057
|
function resetBuildStreamState() {
|
|
28173
|
-
|
|
29058
|
+
deployToolUseIds.clear();
|
|
29059
|
+
streamedDeploymentIds.clear();
|
|
29060
|
+
}
|
|
29061
|
+
/**
|
|
29062
|
+
* Flatten a tool_result `content` field into plain text.
|
|
29063
|
+
* The SDK may return a string or an array of text blocks.
|
|
29064
|
+
*/
|
|
29065
|
+
function extractToolResultText(content) {
|
|
29066
|
+
if (!content) return "";
|
|
29067
|
+
if (typeof content === "string") return content;
|
|
29068
|
+
return content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n");
|
|
28174
29069
|
}
|
|
28175
29070
|
/**
|
|
28176
29071
|
* Handle AskUserQuestion by showing the TUI overlay and waiting for response.
|
|
@@ -28200,7 +29095,8 @@ async function handleAskUserQuestion(store, cwd, toolUseId, question) {
|
|
|
28200
29095
|
store.clearAgentPrompt();
|
|
28201
29096
|
return "O usuário não respondeu (timeout).";
|
|
28202
29097
|
}
|
|
28203
|
-
function handleMessage(message, store, collectedText,
|
|
29098
|
+
function handleMessage(message, store, collectedText, onAskUser) {
|
|
29099
|
+
const logger = getSessionLogger();
|
|
28204
29100
|
if (message.type === "assistant") {
|
|
28205
29101
|
const content = message.message?.content;
|
|
28206
29102
|
if (!Array.isArray(content)) return;
|
|
@@ -28231,31 +29127,34 @@ function handleMessage(message, store, collectedText, cwd, onAskUser) {
|
|
|
28231
29127
|
if (lastLine) store.pushAgentLog(lastLine.trim());
|
|
28232
29128
|
}
|
|
28233
29129
|
if (block.type === "tool_use" && block.name) {
|
|
28234
|
-
|
|
28235
|
-
|
|
28236
|
-
|
|
28237
|
-
|
|
28238
|
-
|
|
29130
|
+
const rawName = block.name;
|
|
29131
|
+
const normalizedName = normalizeMcpToolName(rawName);
|
|
29132
|
+
logger.logToolUse(normalizedName, block.input, block.id ?? null, { rawName });
|
|
29133
|
+
let toolDescription = `[tool] ${TOOL_LABELS[rawName] ?? rawName}`;
|
|
29134
|
+
if (rawName === "Read" && block.input?.file_path) toolDescription += `: ${block.input.file_path}`;
|
|
29135
|
+
else if (rawName === "Write" && block.input?.file_path) toolDescription += `: ${block.input.file_path}`;
|
|
29136
|
+
else if (rawName === "Edit" && block.input?.file_path) toolDescription += `: ${block.input.file_path}`;
|
|
29137
|
+
else if (rawName === "Bash" && block.input?.command) {
|
|
28239
29138
|
const cmd = String(block.input.command);
|
|
28240
29139
|
toolDescription += `: ${cmd.length > 60 ? cmd.slice(0, 60) + "..." : cmd}`;
|
|
28241
|
-
} else if (
|
|
28242
|
-
else if (
|
|
28243
|
-
if (
|
|
28244
|
-
const
|
|
28245
|
-
const
|
|
28246
|
-
|
|
28247
|
-
|
|
28248
|
-
|
|
28249
|
-
|
|
28250
|
-
|
|
28251
|
-
store
|
|
28252
|
-
|
|
28253
|
-
}
|
|
29140
|
+
} else if (rawName === "Glob" && block.input?.pattern) toolDescription += `: ${block.input.pattern}`;
|
|
29141
|
+
else if (rawName === "Grep" && block.input?.pattern) toolDescription += `: ${block.input.pattern}`;
|
|
29142
|
+
if (normalizedName.startsWith("veloz:") || normalizedName.startsWith("wizard-tools:")) toolDescription = `[tool] Veloz: ${normalizedName.split(":")[1]}`;
|
|
29143
|
+
const bashCommand = rawName === "Bash" && typeof block.input?.command === "string" ? block.input.command : null;
|
|
29144
|
+
const isDeployViaBash = bashCommand ? /\bveloz\s+deploy\b/.test(bashCommand) : false;
|
|
29145
|
+
const isDeployViaMcp = normalizedName === "veloz:deploy";
|
|
29146
|
+
const isDryRun = isDeployViaBash ? /--dry-run\b/.test(bashCommand ?? "") : isDeployViaMcp && block.input?.dryRun === true;
|
|
29147
|
+
if ((isDeployViaBash || isDeployViaMcp) && !isDryRun) {
|
|
29148
|
+
if (typeof block.id === "string") deployToolUseIds.add(block.id);
|
|
29149
|
+
if (store.session.agentPhase !== "deploying") {
|
|
29150
|
+
store.setAgentPhase("deploying");
|
|
29151
|
+
store.pushStatus("Deploy iniciado — aguardando build...");
|
|
29152
|
+
}
|
|
28254
29153
|
}
|
|
28255
29154
|
store.pushAgentOutput(toolDescription);
|
|
28256
29155
|
store.pushToolCall(toolDescription);
|
|
28257
29156
|
store.pushAgentLog(toolDescription.replace("[tool] ", ""));
|
|
28258
|
-
if (
|
|
29157
|
+
if (normalizedName === "wizard-tools:prompt_user" && block.input?.question) store.setAgentPrompt({
|
|
28259
29158
|
id: `prompt-${Date.now()}`,
|
|
28260
29159
|
question: String(block.input.question),
|
|
28261
29160
|
hint: typeof block.input.hint === "string" ? block.input.hint : void 0,
|
|
@@ -28263,8 +29162,12 @@ function handleMessage(message, store, collectedText, cwd, onAskUser) {
|
|
|
28263
29162
|
options: Array.isArray(block.input.options) ? block.input.options : void 0,
|
|
28264
29163
|
envKey: typeof block.input.env_key === "string" ? block.input.env_key : void 0
|
|
28265
29164
|
});
|
|
28266
|
-
if (
|
|
28267
|
-
|
|
29165
|
+
if (rawName === "AskUserQuestion" && onAskUser) {
|
|
29166
|
+
const toolUseId = typeof block.id === "string" ? block.id : `ask-${Date.now()}`;
|
|
29167
|
+
const questionText = extractAskUserQuestionText(block.input);
|
|
29168
|
+
if (questionText) onAskUser(toolUseId, questionText);
|
|
29169
|
+
}
|
|
29170
|
+
if (normalizedName === "wizard-tools:set_helper_content" && block.input?.lines && Array.isArray(block.input.lines)) {
|
|
28268
29171
|
const title = typeof block.input.title === "string" ? block.input.title : null;
|
|
28269
29172
|
const lines = block.input.lines;
|
|
28270
29173
|
const helperLines = title ? [
|
|
@@ -28274,7 +29177,7 @@ function handleMessage(message, store, collectedText, cwd, onAskUser) {
|
|
|
28274
29177
|
] : lines;
|
|
28275
29178
|
store.setAgentHelper(helperLines);
|
|
28276
29179
|
}
|
|
28277
|
-
if (
|
|
29180
|
+
if (rawName === "TodoWrite" && block.input?.todos && Array.isArray(block.input.todos)) {
|
|
28278
29181
|
const tasks = block.input.todos.map((todo, i) => ({
|
|
28279
29182
|
id: todo.id ?? `task-${i}`,
|
|
28280
29183
|
label: todo.content ?? `Tarefa ${i + 1}`,
|
|
@@ -28291,6 +29194,50 @@ function handleMessage(message, store, collectedText, cwd, onAskUser) {
|
|
|
28291
29194
|
}
|
|
28292
29195
|
}
|
|
28293
29196
|
}
|
|
29197
|
+
if (message.type === "user") {
|
|
29198
|
+
const content = message.message?.content;
|
|
29199
|
+
if (!Array.isArray(content)) return;
|
|
29200
|
+
for (const block of content) {
|
|
29201
|
+
if (block.type !== "tool_result" || !block.tool_use_id) continue;
|
|
29202
|
+
logger.logToolResult(block.tool_use_id, extractToolResultText(block.content), Boolean(block.is_error));
|
|
29203
|
+
if (!deployToolUseIds.has(block.tool_use_id)) continue;
|
|
29204
|
+
const text = extractToolResultText(block.content);
|
|
29205
|
+
if (!text) continue;
|
|
29206
|
+
let lines = [];
|
|
29207
|
+
try {
|
|
29208
|
+
const parsed = JSON.parse(text);
|
|
29209
|
+
if (Array.isArray(parsed)) for (const entry of parsed) {
|
|
29210
|
+
if (typeof entry?.message === "string") lines.push(entry.message);
|
|
29211
|
+
const entryDeploymentId = entry?.data?.deploymentId;
|
|
29212
|
+
if (entryDeploymentId && !streamedDeploymentIds.has(entryDeploymentId)) {
|
|
29213
|
+
streamedDeploymentIds.add(entryDeploymentId);
|
|
29214
|
+
store.setBuildDeploymentId(entryDeploymentId);
|
|
29215
|
+
streamBuildLogs({
|
|
29216
|
+
store,
|
|
29217
|
+
deploymentId: entryDeploymentId
|
|
29218
|
+
}).catch((err) => {
|
|
29219
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
29220
|
+
store.pushBuildLog(`[stream] ${msg}`);
|
|
29221
|
+
});
|
|
29222
|
+
}
|
|
29223
|
+
const url = entry?.data?.urls?.[0];
|
|
29224
|
+
if (url && !store.session.deployUrl) store.setDeployUrl(url);
|
|
29225
|
+
}
|
|
29226
|
+
else if (parsed && typeof parsed === "object" && "message" in parsed) {
|
|
29227
|
+
const msg = parsed.message;
|
|
29228
|
+
if (typeof msg === "string") lines = [msg];
|
|
29229
|
+
} else lines = text.split("\n");
|
|
29230
|
+
} catch {
|
|
29231
|
+
lines = text.split("\n");
|
|
29232
|
+
}
|
|
29233
|
+
for (const raw of lines) {
|
|
29234
|
+
const line = raw.trim();
|
|
29235
|
+
if (!line) continue;
|
|
29236
|
+
store.pushBuildLog(line);
|
|
29237
|
+
}
|
|
29238
|
+
deployToolUseIds.delete(block.tool_use_id);
|
|
29239
|
+
}
|
|
29240
|
+
}
|
|
28294
29241
|
if (message.type === "result") {
|
|
28295
29242
|
if (message.is_error && message.errors) for (const err of message.errors) {
|
|
28296
29243
|
store.pushAgentLog(`Erro: ${err}`);
|
|
@@ -28299,6 +29246,17 @@ function handleMessage(message, store, collectedText, cwd, onAskUser) {
|
|
|
28299
29246
|
}
|
|
28300
29247
|
}
|
|
28301
29248
|
}
|
|
29249
|
+
function extractAskUserQuestionText(input) {
|
|
29250
|
+
if (!input) return null;
|
|
29251
|
+
const questions = input.questions;
|
|
29252
|
+
if (Array.isArray(questions) && questions.length > 0) {
|
|
29253
|
+
const first = questions[0];
|
|
29254
|
+
if (first && typeof first.question === "string" && first.question.trim()) return first.question.trim();
|
|
29255
|
+
if (first && typeof first.header === "string" && first.header.trim()) return first.header.trim();
|
|
29256
|
+
}
|
|
29257
|
+
if (typeof input.question === "string" && input.question.trim()) return input.question.trim();
|
|
29258
|
+
return null;
|
|
29259
|
+
}
|
|
28302
29260
|
function mapTodoStatus(status) {
|
|
28303
29261
|
switch (status) {
|
|
28304
29262
|
case "in_progress": return "in_progress";
|
|
@@ -28317,6 +29275,7 @@ function sleep(ms) {
|
|
|
28317
29275
|
let Screen = /* @__PURE__ */ function(Screen$1) {
|
|
28318
29276
|
Screen$1["Intro"] = "intro";
|
|
28319
29277
|
Screen$1["Auth"] = "auth";
|
|
29278
|
+
Screen$1["Notes"] = "notes";
|
|
28320
29279
|
Screen$1["Run"] = "run";
|
|
28321
29280
|
Screen$1["Outro"] = "outro";
|
|
28322
29281
|
return Screen$1;
|
|
@@ -28337,6 +29296,11 @@ const FLOW = [
|
|
|
28337
29296
|
show: (s) => s.authPhase !== "done",
|
|
28338
29297
|
isComplete: (s) => s.authPhase === "done"
|
|
28339
29298
|
},
|
|
29299
|
+
{
|
|
29300
|
+
screen: Screen.Notes,
|
|
29301
|
+
show: () => true,
|
|
29302
|
+
isComplete: (s) => s.notesConfirmed
|
|
29303
|
+
},
|
|
28340
29304
|
{
|
|
28341
29305
|
screen: Screen.Run,
|
|
28342
29306
|
show: () => true,
|
|
@@ -28419,9 +29383,27 @@ const COPY = {
|
|
|
28419
29383
|
authTimeout: "Tempo esgotado. Tente novamente com `veloz login`.",
|
|
28420
29384
|
authError: "Erro na autenticação.",
|
|
28421
29385
|
authOrgSelect: "Selecione o workspace:",
|
|
29386
|
+
authOrgCreateOption: "Criar novo workspace",
|
|
29387
|
+
authOrgCreateTitle: "Criar novo workspace",
|
|
29388
|
+
authOrgCreateQuestion: "Qual o nome do novo workspace?",
|
|
29389
|
+
authOrgCreatePlaceholder: "Digite o nome e pressione Enter...",
|
|
29390
|
+
authOrgCreateHint: "Enter para criar",
|
|
29391
|
+
authOrgCreating: "Criando workspace...",
|
|
28422
29392
|
authNoOrg: "Você ainda não tem um workspace.",
|
|
28423
29393
|
authNoOrgHint: "Crie um workspace em app.onveloz.com para continuar.",
|
|
28424
29394
|
authOrgResolved: "Workspace:",
|
|
29395
|
+
notesTitle: "Notas para o deploy",
|
|
29396
|
+
notesQuestion: "Algo específico que o agente deve saber antes de começar? Deploy de todos os apps, apenas um serviço, flags customizadas, etc.",
|
|
29397
|
+
notesExamplesLabel: "Exemplos:",
|
|
29398
|
+
notesExamples: [
|
|
29399
|
+
"Deploya só o apps/web",
|
|
29400
|
+
"Usa o Dockerfile que está em apps/api",
|
|
29401
|
+
"Ignora o build cache",
|
|
29402
|
+
"Não cria banco — já existe"
|
|
29403
|
+
],
|
|
29404
|
+
notesPlaceholder: "Digite suas notas ou pressione Enter para pular...",
|
|
29405
|
+
notesHint: "Enter para continuar · Esc para pular",
|
|
29406
|
+
notesFooter: "Suas notas serão repassadas ao agente de deploy",
|
|
28425
29407
|
runTitle: "Configurando seu projeto...",
|
|
28426
29408
|
phaseLabels: {
|
|
28427
29409
|
idle: "Aguardando...",
|
|
@@ -28490,11 +29472,31 @@ const Icons = {
|
|
|
28490
29472
|
]
|
|
28491
29473
|
};
|
|
28492
29474
|
|
|
29475
|
+
//#endregion
|
|
29476
|
+
//#region src/wizard/ui/hooks/useGatedInput.ts
|
|
29477
|
+
/**
|
|
29478
|
+
* useGatedInput — Wrapper around Ink's useInput that respects an
|
|
29479
|
+
* InputFocusContext. When an overlay (like the agent prompt) owns
|
|
29480
|
+
* input, screens rendered behind it should not react to keystrokes.
|
|
29481
|
+
*/
|
|
29482
|
+
const InputFocusContext = createContext({ active: true });
|
|
29483
|
+
function InputFocusProvider({ active: active$1, children }) {
|
|
29484
|
+
return React.createElement(InputFocusContext.Provider, { value: { active: active$1 } }, children);
|
|
29485
|
+
}
|
|
29486
|
+
function useGatedInput(handler, options) {
|
|
29487
|
+
const { active: active$1 } = useContext(InputFocusContext);
|
|
29488
|
+
const callerActive = options?.isActive ?? true;
|
|
29489
|
+
useInput(handler, {
|
|
29490
|
+
...options,
|
|
29491
|
+
isActive: callerActive && active$1
|
|
29492
|
+
});
|
|
29493
|
+
}
|
|
29494
|
+
|
|
28493
29495
|
//#endregion
|
|
28494
29496
|
//#region src/wizard/ui/primitives/PickerMenu.tsx
|
|
28495
29497
|
function PickerMenu({ items, onSelect, onCancel }) {
|
|
28496
29498
|
const [cursor, setCursor] = useState(0);
|
|
28497
|
-
|
|
29499
|
+
useGatedInput((_input, key) => {
|
|
28498
29500
|
if (key.upArrow) setCursor((c) => c > 0 ? c - 1 : items.length - 1);
|
|
28499
29501
|
if (key.downArrow) setCursor((c) => c < items.length - 1 ? c + 1 : 0);
|
|
28500
29502
|
if (key.return) {
|
|
@@ -28533,34 +29535,9 @@ function PickerMenu({ items, onSelect, onCancel }) {
|
|
|
28533
29535
|
});
|
|
28534
29536
|
}
|
|
28535
29537
|
|
|
28536
|
-
//#endregion
|
|
28537
|
-
//#region src/wizard/ui/hooks/useStdoutDimensions.ts
|
|
28538
|
-
/**
|
|
28539
|
-
* useStdoutDimensions — Returns [columns, rows] and re-renders on terminal resize.
|
|
28540
|
-
*/
|
|
28541
|
-
function useStdoutDimensions() {
|
|
28542
|
-
const { stdout } = useStdout();
|
|
28543
|
-
const [size, setSize] = useState(() => [stdout.columns || 80, stdout.rows || 24]);
|
|
28544
|
-
useEffect(() => {
|
|
28545
|
-
setSize([stdout.columns || 80, stdout.rows || 24]);
|
|
28546
|
-
const stream = stdout;
|
|
28547
|
-
if (typeof stream.on !== "function") return;
|
|
28548
|
-
const onResize = () => {
|
|
28549
|
-
const c = stdout.columns || 80;
|
|
28550
|
-
const r = stdout.rows || 24;
|
|
28551
|
-
if (c > 0 && r > 0) setSize([c, r]);
|
|
28552
|
-
};
|
|
28553
|
-
stream.on("resize", onResize);
|
|
28554
|
-
return () => {
|
|
28555
|
-
stream.off?.("resize", onResize);
|
|
28556
|
-
};
|
|
28557
|
-
}, [stdout]);
|
|
28558
|
-
return size;
|
|
28559
|
-
}
|
|
28560
|
-
|
|
28561
29538
|
//#endregion
|
|
28562
29539
|
//#region src/wizard/ui/primitives/ScreenLayout.tsx
|
|
28563
|
-
const version = "0.0.0-beta.
|
|
29540
|
+
const version = "0.0.0-beta.31";
|
|
28564
29541
|
const LOGO_LINES = [
|
|
28565
29542
|
"██╗ ██╗███████╗██╗ ██████╗ ███████╗",
|
|
28566
29543
|
"██║ ██║██╔════╝██║ ██╔═══██╗╚══███╔╝",
|
|
@@ -28570,10 +29547,11 @@ const LOGO_LINES = [
|
|
|
28570
29547
|
" ╚═══╝ ╚══════╝╚══════╝ ╚═════╝ ╚══════╝"
|
|
28571
29548
|
];
|
|
28572
29549
|
function ScreenLayout({ children, footer }) {
|
|
28573
|
-
const [, rows] = useStdoutDimensions();
|
|
28574
29550
|
return /* @__PURE__ */ jsxs(Box, {
|
|
28575
29551
|
flexDirection: "column",
|
|
28576
|
-
|
|
29552
|
+
flexGrow: 1,
|
|
29553
|
+
flexShrink: 1,
|
|
29554
|
+
minHeight: 0,
|
|
28577
29555
|
paddingX: 2,
|
|
28578
29556
|
paddingY: 1,
|
|
28579
29557
|
children: [
|
|
@@ -28594,13 +29572,6 @@ function ScreenLayout({ children, footer }) {
|
|
|
28594
29572
|
})] }),
|
|
28595
29573
|
/* @__PURE__ */ jsx(Box, {
|
|
28596
29574
|
marginTop: 1,
|
|
28597
|
-
marginBottom: 1,
|
|
28598
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
28599
|
-
color: DIM_COLOR,
|
|
28600
|
-
children: "─".repeat(50)
|
|
28601
|
-
})
|
|
28602
|
-
}),
|
|
28603
|
-
/* @__PURE__ */ jsx(Box, {
|
|
28604
29575
|
flexDirection: "column",
|
|
28605
29576
|
flexGrow: 1,
|
|
28606
29577
|
flexShrink: 1,
|
|
@@ -28737,6 +29708,57 @@ function Spinner({ label }) {
|
|
|
28737
29708
|
}), label ? /* @__PURE__ */ jsxs(Text, { children: [" ", label] }) : null] });
|
|
28738
29709
|
}
|
|
28739
29710
|
|
|
29711
|
+
//#endregion
|
|
29712
|
+
//#region src/wizard/session.ts
|
|
29713
|
+
/** Sentinel value used as a PickerMenu item to signal "create new workspace". */
|
|
29714
|
+
const CREATE_ORG_ACTION = "__create__";
|
|
29715
|
+
function createInitialSession() {
|
|
29716
|
+
return {
|
|
29717
|
+
isAuthenticated: false,
|
|
29718
|
+
userName: "",
|
|
29719
|
+
orgId: "",
|
|
29720
|
+
orgName: "",
|
|
29721
|
+
apiKey: "",
|
|
29722
|
+
authPhase: "checking",
|
|
29723
|
+
authDeviceCode: null,
|
|
29724
|
+
authVerificationUrl: null,
|
|
29725
|
+
availableOrgs: [],
|
|
29726
|
+
selectedOrgId: null,
|
|
29727
|
+
newOrgName: null,
|
|
29728
|
+
orgCreateError: null,
|
|
29729
|
+
detectedFramework: null,
|
|
29730
|
+
detectedFrameworkLabel: null,
|
|
29731
|
+
selectedFramework: null,
|
|
29732
|
+
selectedFrameworkLabel: null,
|
|
29733
|
+
packageManager: "npm",
|
|
29734
|
+
gitRemote: null,
|
|
29735
|
+
isMonorepo: false,
|
|
29736
|
+
agentPhase: "idle",
|
|
29737
|
+
agentTasks: [],
|
|
29738
|
+
agentLogLines: [],
|
|
29739
|
+
agentComplete: false,
|
|
29740
|
+
agentError: null,
|
|
29741
|
+
deployUrl: null,
|
|
29742
|
+
retryCount: 0,
|
|
29743
|
+
buildLogLines: [],
|
|
29744
|
+
buildStreamActive: false,
|
|
29745
|
+
buildDeploymentId: null,
|
|
29746
|
+
statusMessages: [],
|
|
29747
|
+
agentOutputLines: [],
|
|
29748
|
+
toolCallLog: [],
|
|
29749
|
+
learnCardBlockIdx: 0,
|
|
29750
|
+
learnCardComplete: false,
|
|
29751
|
+
agentHelperLines: [],
|
|
29752
|
+
agentPrompt: null,
|
|
29753
|
+
agentSummary: null,
|
|
29754
|
+
userNotes: null,
|
|
29755
|
+
notesConfirmed: false,
|
|
29756
|
+
introConfirmed: false,
|
|
29757
|
+
outroDismissed: false,
|
|
29758
|
+
agentStartedAt: null
|
|
29759
|
+
};
|
|
29760
|
+
}
|
|
29761
|
+
|
|
28740
29762
|
//#endregion
|
|
28741
29763
|
//#region src/wizard/ui/screens/AuthScreen.tsx
|
|
28742
29764
|
function AuthScreen({ store }) {
|
|
@@ -28773,6 +29795,8 @@ function AuthScreen({ store }) {
|
|
|
28773
29795
|
})
|
|
28774
29796
|
]
|
|
28775
29797
|
}) : null] });
|
|
29798
|
+
if (session.authPhase === "org-creating") return /* @__PURE__ */ jsx(ScreenLayout, { children: /* @__PURE__ */ jsx(Spinner, { label: COPY.authOrgCreating }) });
|
|
29799
|
+
if (session.authPhase === "org-create") return /* @__PURE__ */ jsx(OrgCreateForm, { store });
|
|
28776
29800
|
if (session.authPhase === "no-org") return /* @__PURE__ */ jsxs(ScreenLayout, { children: [/* @__PURE__ */ jsxs(Box, {
|
|
28777
29801
|
gap: 1,
|
|
28778
29802
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
@@ -28792,10 +29816,13 @@ function AuthScreen({ store }) {
|
|
|
28792
29816
|
})]
|
|
28793
29817
|
})] });
|
|
28794
29818
|
if (session.authPhase === "org-select") {
|
|
28795
|
-
const items = session.availableOrgs.map((org) => ({
|
|
29819
|
+
const items = [...session.availableOrgs.map((org) => ({
|
|
28796
29820
|
label: `${org.name} (${org.slug})`,
|
|
28797
29821
|
value: org.id
|
|
28798
|
-
}))
|
|
29822
|
+
})), {
|
|
29823
|
+
label: COPY.authOrgCreateOption,
|
|
29824
|
+
value: CREATE_ORG_ACTION
|
|
29825
|
+
}];
|
|
28799
29826
|
return /* @__PURE__ */ jsxs(ScreenLayout, { children: [/* @__PURE__ */ jsxs(Box, {
|
|
28800
29827
|
gap: 1,
|
|
28801
29828
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
@@ -28812,7 +29839,10 @@ function AuthScreen({ store }) {
|
|
|
28812
29839
|
marginTop: 1,
|
|
28813
29840
|
children: /* @__PURE__ */ jsx(PickerMenu, {
|
|
28814
29841
|
items,
|
|
28815
|
-
onSelect: (id) =>
|
|
29842
|
+
onSelect: (id) => {
|
|
29843
|
+
if (id === CREATE_ORG_ACTION) store.requestOrgCreation();
|
|
29844
|
+
else store.selectOrg(id);
|
|
29845
|
+
}
|
|
28816
29846
|
})
|
|
28817
29847
|
})]
|
|
28818
29848
|
})] });
|
|
@@ -28841,6 +29871,140 @@ function AuthScreen({ store }) {
|
|
|
28841
29871
|
}) : null] });
|
|
28842
29872
|
return /* @__PURE__ */ jsx(ScreenLayout, { children: /* @__PURE__ */ jsx(Spinner, { label: COPY.authChecking }) });
|
|
28843
29873
|
}
|
|
29874
|
+
function OrgCreateForm({ store }) {
|
|
29875
|
+
const [value, setValue] = useState("");
|
|
29876
|
+
const session = store.session;
|
|
29877
|
+
useGatedInput((input, key) => {
|
|
29878
|
+
if (key.return) {
|
|
29879
|
+
if (value.trim().length > 0) store.submitNewOrgName(value.trim());
|
|
29880
|
+
return;
|
|
29881
|
+
}
|
|
29882
|
+
if (key.backspace || key.delete) {
|
|
29883
|
+
setValue((v) => v.slice(0, -1));
|
|
29884
|
+
return;
|
|
29885
|
+
}
|
|
29886
|
+
if (input && !key.ctrl && !key.meta) setValue((v) => v + input);
|
|
29887
|
+
});
|
|
29888
|
+
return /* @__PURE__ */ jsxs(ScreenLayout, { children: [
|
|
29889
|
+
/* @__PURE__ */ jsx(Text, {
|
|
29890
|
+
color: BRAND_COLOR,
|
|
29891
|
+
bold: true,
|
|
29892
|
+
children: COPY.authOrgCreateTitle
|
|
29893
|
+
}),
|
|
29894
|
+
/* @__PURE__ */ jsx(Box, {
|
|
29895
|
+
marginTop: 1,
|
|
29896
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
29897
|
+
wrap: "wrap",
|
|
29898
|
+
children: COPY.authOrgCreateQuestion
|
|
29899
|
+
})
|
|
29900
|
+
}),
|
|
29901
|
+
/* @__PURE__ */ jsx(Box, {
|
|
29902
|
+
marginTop: 1,
|
|
29903
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
29904
|
+
gap: 1,
|
|
29905
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
29906
|
+
color: BRAND_COLOR,
|
|
29907
|
+
children: Icons.triangleRight
|
|
29908
|
+
}), /* @__PURE__ */ jsxs(Text, { children: [value || /* @__PURE__ */ jsx(Text, {
|
|
29909
|
+
color: DIM_COLOR,
|
|
29910
|
+
children: COPY.authOrgCreatePlaceholder
|
|
29911
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
29912
|
+
color: DIM_COLOR,
|
|
29913
|
+
children: "▌"
|
|
29914
|
+
})] })]
|
|
29915
|
+
})
|
|
29916
|
+
}),
|
|
29917
|
+
session.orgCreateError ? /* @__PURE__ */ jsx(Box, {
|
|
29918
|
+
marginTop: 1,
|
|
29919
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
29920
|
+
color: ERROR_COLOR,
|
|
29921
|
+
children: session.orgCreateError
|
|
29922
|
+
})
|
|
29923
|
+
}) : null
|
|
29924
|
+
] });
|
|
29925
|
+
}
|
|
29926
|
+
|
|
29927
|
+
//#endregion
|
|
29928
|
+
//#region src/wizard/ui/screens/NotesScreen.tsx
|
|
29929
|
+
function NotesScreen({ store }) {
|
|
29930
|
+
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
29931
|
+
const [value, setValue] = useState("");
|
|
29932
|
+
useGatedInput((input, key) => {
|
|
29933
|
+
if (key.return) {
|
|
29934
|
+
store.confirmNotes(value);
|
|
29935
|
+
return;
|
|
29936
|
+
}
|
|
29937
|
+
if (key.escape) {
|
|
29938
|
+
store.confirmNotes(null);
|
|
29939
|
+
return;
|
|
29940
|
+
}
|
|
29941
|
+
if (key.backspace || key.delete) {
|
|
29942
|
+
setValue((v) => v.slice(0, -1));
|
|
29943
|
+
return;
|
|
29944
|
+
}
|
|
29945
|
+
if (input && !key.ctrl && !key.meta) setValue((v) => v + input);
|
|
29946
|
+
});
|
|
29947
|
+
return /* @__PURE__ */ jsxs(ScreenLayout, {
|
|
29948
|
+
footer: COPY.notesFooter,
|
|
29949
|
+
children: [
|
|
29950
|
+
/* @__PURE__ */ jsx(Text, {
|
|
29951
|
+
color: BRAND_COLOR,
|
|
29952
|
+
bold: true,
|
|
29953
|
+
children: COPY.notesTitle
|
|
29954
|
+
}),
|
|
29955
|
+
/* @__PURE__ */ jsx(Box, {
|
|
29956
|
+
marginTop: 1,
|
|
29957
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
29958
|
+
wrap: "wrap",
|
|
29959
|
+
children: COPY.notesQuestion
|
|
29960
|
+
})
|
|
29961
|
+
}),
|
|
29962
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
29963
|
+
marginTop: 1,
|
|
29964
|
+
flexDirection: "column",
|
|
29965
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
29966
|
+
color: DIM_COLOR,
|
|
29967
|
+
children: COPY.notesExamplesLabel
|
|
29968
|
+
}), COPY.notesExamples.map((ex, i) => /* @__PURE__ */ jsxs(Box, {
|
|
29969
|
+
gap: 1,
|
|
29970
|
+
marginLeft: 2,
|
|
29971
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
29972
|
+
color: DIM_COLOR,
|
|
29973
|
+
children: Icons.bullet
|
|
29974
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
29975
|
+
color: DIM_COLOR,
|
|
29976
|
+
children: ex
|
|
29977
|
+
})]
|
|
29978
|
+
}, i))]
|
|
29979
|
+
}),
|
|
29980
|
+
/* @__PURE__ */ jsx(Box, {
|
|
29981
|
+
marginTop: 1,
|
|
29982
|
+
flexDirection: "column",
|
|
29983
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
29984
|
+
gap: 1,
|
|
29985
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
29986
|
+
color: BRAND_COLOR,
|
|
29987
|
+
children: Icons.triangleRight
|
|
29988
|
+
}), /* @__PURE__ */ jsxs(Text, { children: [value || /* @__PURE__ */ jsx(Text, {
|
|
29989
|
+
color: DIM_COLOR,
|
|
29990
|
+
children: COPY.notesPlaceholder
|
|
29991
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
29992
|
+
color: DIM_COLOR,
|
|
29993
|
+
children: "▌"
|
|
29994
|
+
})] })]
|
|
29995
|
+
})
|
|
29996
|
+
}),
|
|
29997
|
+
/* @__PURE__ */ jsx(Box, {
|
|
29998
|
+
marginTop: 1,
|
|
29999
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
30000
|
+
color: SUCCESS_COLOR,
|
|
30001
|
+
dimColor: true,
|
|
30002
|
+
children: COPY.notesHint
|
|
30003
|
+
})
|
|
30004
|
+
})
|
|
30005
|
+
]
|
|
30006
|
+
});
|
|
30007
|
+
}
|
|
28844
30008
|
|
|
28845
30009
|
//#endregion
|
|
28846
30010
|
//#region src/wizard/ui/primitives/text-helpers.ts
|
|
@@ -28976,7 +30140,7 @@ const TEXT_REVEAL_MODE_DEFAULTS = {
|
|
|
28976
30140
|
[TextRevealMode.WordByWord]: 240,
|
|
28977
30141
|
[TextRevealMode.SentenceFade]: 2400
|
|
28978
30142
|
};
|
|
28979
|
-
function TextBlock({ text, active, completed, onComplete, mode, bullet, animationInterval, sentenceInterval = 1600, maxHeight, availableWidth }) {
|
|
30143
|
+
function TextBlock({ text, active: active$1, completed, onComplete, mode, bullet, animationInterval, sentenceInterval = 1600, maxHeight, availableWidth }) {
|
|
28980
30144
|
const speed = animationInterval ?? TEXT_REVEAL_MODE_DEFAULTS[mode];
|
|
28981
30145
|
const [animIdx, setAnimIdx] = useState(mode === TextRevealMode.SentenceFade ? 1 : 0);
|
|
28982
30146
|
const resetRef = useRef(0);
|
|
@@ -28992,14 +30156,14 @@ function TextBlock({ text, active, completed, onComplete, mode, bullet, animatio
|
|
|
28992
30156
|
const sentWordEnds = useMemo(() => sentenceEndWords(text), [text]);
|
|
28993
30157
|
const isDone$2 = mode === TextRevealMode.Typewriter ? animIdx >= text.length : mode === TextRevealMode.WordByWord ? animIdx >= words.length : animIdx >= sentences.length;
|
|
28994
30158
|
useEffect(() => {
|
|
28995
|
-
if (isDone$2 && active) onComplete();
|
|
30159
|
+
if (isDone$2 && active$1) onComplete();
|
|
28996
30160
|
}, [
|
|
28997
30161
|
isDone$2,
|
|
28998
|
-
active,
|
|
30162
|
+
active$1,
|
|
28999
30163
|
onComplete
|
|
29000
30164
|
]);
|
|
29001
30165
|
useEffect(() => {
|
|
29002
|
-
if (!active || isDone$2) return;
|
|
30166
|
+
if (!active$1 || isDone$2) return;
|
|
29003
30167
|
const token = resetRef.current;
|
|
29004
30168
|
const isFirstTick = animIdx === 0;
|
|
29005
30169
|
let delay$2 = isFirstTick ? 0 : speed;
|
|
@@ -29011,7 +30175,7 @@ function TextBlock({ text, active, completed, onComplete, mode, bullet, animatio
|
|
|
29011
30175
|
}, delay$2);
|
|
29012
30176
|
return () => clearTimeout(timer);
|
|
29013
30177
|
}, [
|
|
29014
|
-
active,
|
|
30178
|
+
active$1,
|
|
29015
30179
|
mode,
|
|
29016
30180
|
animIdx,
|
|
29017
30181
|
isDone$2,
|
|
@@ -29053,22 +30217,22 @@ function TextBlock({ text, active, completed, onComplete, mode, bullet, animatio
|
|
|
29053
30217
|
* LinesBlock — Reveals ReactNode lines one at a time.
|
|
29054
30218
|
* Each line can contain colors, bold, ASCII art — any JSX.
|
|
29055
30219
|
*/
|
|
29056
|
-
function LinesBlock({ lines, interval, active, completed, onComplete, maxHeight }) {
|
|
30220
|
+
function LinesBlock({ lines, interval, active: active$1, completed, onComplete, maxHeight }) {
|
|
29057
30221
|
const [revealedCount, setRevealedCount] = useState(0);
|
|
29058
30222
|
useEffect(() => {
|
|
29059
|
-
if (!active || revealedCount >= lines.length) return;
|
|
30223
|
+
if (!active$1 || revealedCount >= lines.length) return;
|
|
29060
30224
|
const timer = setTimeout(() => setRevealedCount((c) => c + 1), revealedCount === 0 ? 0 : interval);
|
|
29061
30225
|
return () => clearTimeout(timer);
|
|
29062
30226
|
}, [
|
|
29063
|
-
active,
|
|
30227
|
+
active$1,
|
|
29064
30228
|
revealedCount,
|
|
29065
30229
|
lines.length,
|
|
29066
30230
|
interval
|
|
29067
30231
|
]);
|
|
29068
30232
|
useEffect(() => {
|
|
29069
|
-
if (active && revealedCount >= lines.length) onComplete();
|
|
30233
|
+
if (active$1 && revealedCount >= lines.length) onComplete();
|
|
29070
30234
|
}, [
|
|
29071
|
-
active,
|
|
30235
|
+
active$1,
|
|
29072
30236
|
revealedCount,
|
|
29073
30237
|
lines.length,
|
|
29074
30238
|
onComplete
|
|
@@ -29092,10 +30256,10 @@ function LinesBlock({ lines, interval, active, completed, onComplete, maxHeight
|
|
|
29092
30256
|
/**
|
|
29093
30257
|
* NodeBlock — Renders static JSX, fires onComplete immediately.
|
|
29094
30258
|
*/
|
|
29095
|
-
function NodeBlock({ content, active, completed, onComplete }) {
|
|
30259
|
+
function NodeBlock({ content, active: active$1, completed, onComplete }) {
|
|
29096
30260
|
useEffect(() => {
|
|
29097
|
-
if (active) onComplete();
|
|
29098
|
-
}, [active, onComplete]);
|
|
30261
|
+
if (active$1) onComplete();
|
|
30262
|
+
}, [active$1, onComplete]);
|
|
29099
30263
|
if (completed) return /* @__PURE__ */ jsx(Text, {
|
|
29100
30264
|
dimColor: true,
|
|
29101
30265
|
children: content
|
|
@@ -29187,7 +30351,7 @@ function ContentSequencer({ blocks, mode, maxHeight, availableWidth, bullet, ani
|
|
|
29187
30351
|
if (i < clearFloor) return null;
|
|
29188
30352
|
if (isClearBlock(block) && i < activeIdx) return null;
|
|
29189
30353
|
if (i < visibleStart || i > visibleEnd) return null;
|
|
29190
|
-
const active = i === activeIdx;
|
|
30354
|
+
const active$1 = i === activeIdx;
|
|
29191
30355
|
const completed = i < activeIdx || skipCurrent && i === activeIdx;
|
|
29192
30356
|
if (completed && isObjectBlock(block)) {
|
|
29193
30357
|
const isText = typeof block.content === "string";
|
|
@@ -29198,7 +30362,7 @@ function ContentSequencer({ blocks, mode, maxHeight, availableWidth, bullet, ani
|
|
|
29198
30362
|
marginBottom: 1,
|
|
29199
30363
|
children: /* @__PURE__ */ jsx(BlockRenderer, {
|
|
29200
30364
|
block,
|
|
29201
|
-
active,
|
|
30365
|
+
active: active$1,
|
|
29202
30366
|
completed,
|
|
29203
30367
|
onComplete: () => handleComplete(i),
|
|
29204
30368
|
mode,
|
|
@@ -29213,19 +30377,19 @@ function ContentSequencer({ blocks, mode, maxHeight, availableWidth, bullet, ani
|
|
|
29213
30377
|
})
|
|
29214
30378
|
});
|
|
29215
30379
|
}
|
|
29216
|
-
function BlockRenderer({ block, active, completed, onComplete, mode, bullet, animationInterval, sentenceInterval, lineInterval, maxHeight, availableWidth }) {
|
|
30380
|
+
function BlockRenderer({ block, active: active$1, completed, onComplete, mode, bullet, animationInterval, sentenceInterval, lineInterval, maxHeight, availableWidth }) {
|
|
29217
30381
|
const isClear = isClearBlock(block);
|
|
29218
30382
|
useEffect(() => {
|
|
29219
|
-
if (isClear && active) onComplete();
|
|
30383
|
+
if (isClear && active$1) onComplete();
|
|
29220
30384
|
}, [
|
|
29221
30385
|
isClear,
|
|
29222
|
-
active,
|
|
30386
|
+
active$1,
|
|
29223
30387
|
onComplete
|
|
29224
30388
|
]);
|
|
29225
30389
|
if (isClear) return null;
|
|
29226
30390
|
if (typeof block === "string") return /* @__PURE__ */ jsx(TextBlock, {
|
|
29227
30391
|
text: block,
|
|
29228
|
-
active,
|
|
30392
|
+
active: active$1,
|
|
29229
30393
|
completed,
|
|
29230
30394
|
onComplete,
|
|
29231
30395
|
mode,
|
|
@@ -29238,14 +30402,14 @@ function BlockRenderer({ block, active, completed, onComplete, mode, bullet, ani
|
|
|
29238
30402
|
if (isLinesBlock(block)) return /* @__PURE__ */ jsx(LinesBlock, {
|
|
29239
30403
|
lines: block.lines,
|
|
29240
30404
|
interval: block.interval ?? lineInterval,
|
|
29241
|
-
active,
|
|
30405
|
+
active: active$1,
|
|
29242
30406
|
completed,
|
|
29243
30407
|
onComplete,
|
|
29244
30408
|
maxHeight
|
|
29245
30409
|
});
|
|
29246
30410
|
if (typeof block.content === "string") return /* @__PURE__ */ jsx(TextBlock, {
|
|
29247
30411
|
text: block.content,
|
|
29248
|
-
active,
|
|
30412
|
+
active: active$1,
|
|
29249
30413
|
completed,
|
|
29250
30414
|
onComplete,
|
|
29251
30415
|
mode: block.mode ?? mode,
|
|
@@ -29257,12 +30421,37 @@ function BlockRenderer({ block, active, completed, onComplete, mode, bullet, ani
|
|
|
29257
30421
|
});
|
|
29258
30422
|
return /* @__PURE__ */ jsx(NodeBlock, {
|
|
29259
30423
|
content: block.content,
|
|
29260
|
-
active,
|
|
30424
|
+
active: active$1,
|
|
29261
30425
|
completed,
|
|
29262
30426
|
onComplete
|
|
29263
30427
|
});
|
|
29264
30428
|
}
|
|
29265
30429
|
|
|
30430
|
+
//#endregion
|
|
30431
|
+
//#region src/wizard/ui/hooks/useStdoutDimensions.ts
|
|
30432
|
+
/**
|
|
30433
|
+
* useStdoutDimensions — Returns [columns, rows] and re-renders on terminal resize.
|
|
30434
|
+
*/
|
|
30435
|
+
function useStdoutDimensions() {
|
|
30436
|
+
const { stdout } = useStdout();
|
|
30437
|
+
const [size, setSize] = useState(() => [stdout.columns || 80, stdout.rows || 24]);
|
|
30438
|
+
useEffect(() => {
|
|
30439
|
+
setSize([stdout.columns || 80, stdout.rows || 24]);
|
|
30440
|
+
const stream = stdout;
|
|
30441
|
+
if (typeof stream.on !== "function") return;
|
|
30442
|
+
const onResize = () => {
|
|
30443
|
+
const c = stdout.columns || 80;
|
|
30444
|
+
const r = stdout.rows || 24;
|
|
30445
|
+
if (c > 0 && r > 0) setSize([c, r]);
|
|
30446
|
+
};
|
|
30447
|
+
stream.on("resize", onResize);
|
|
30448
|
+
return () => {
|
|
30449
|
+
stream.off?.("resize", onResize);
|
|
30450
|
+
};
|
|
30451
|
+
}, [stdout]);
|
|
30452
|
+
return size;
|
|
30453
|
+
}
|
|
30454
|
+
|
|
29266
30455
|
//#endregion
|
|
29267
30456
|
//#region src/wizard/ui/components/LearnCard.tsx
|
|
29268
30457
|
/**
|
|
@@ -29301,7 +30490,7 @@ const DEPLOY_PIPELINE = {
|
|
|
29301
30490
|
/* @__PURE__ */ jsx(Text, { children: "veloz deploy" }),
|
|
29302
30491
|
/* @__PURE__ */ jsx(Text, {
|
|
29303
30492
|
color: DIM_COLOR,
|
|
29304
|
-
children: "
|
|
30493
|
+
children: " │"
|
|
29305
30494
|
})
|
|
29306
30495
|
] }, "deploy-cmd"),
|
|
29307
30496
|
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
@@ -29309,7 +30498,7 @@ const DEPLOY_PIPELINE = {
|
|
|
29309
30498
|
children: " │ ↓"
|
|
29310
30499
|
}), /* @__PURE__ */ jsx(Text, {
|
|
29311
30500
|
color: DIM_COLOR,
|
|
29312
|
-
children: "
|
|
30501
|
+
children: " │"
|
|
29313
30502
|
})] }, "arrow-1"),
|
|
29314
30503
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
29315
30504
|
/* @__PURE__ */ jsx(Text, {
|
|
@@ -29337,7 +30526,7 @@ const DEPLOY_PIPELINE = {
|
|
|
29337
30526
|
}),
|
|
29338
30527
|
/* @__PURE__ */ jsx(Text, {
|
|
29339
30528
|
color: DIM_COLOR,
|
|
29340
|
-
children: "
|
|
30529
|
+
children: " │"
|
|
29341
30530
|
})
|
|
29342
30531
|
] }, "upload-desc"),
|
|
29343
30532
|
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
@@ -29345,7 +30534,7 @@ const DEPLOY_PIPELINE = {
|
|
|
29345
30534
|
children: " │ ↓"
|
|
29346
30535
|
}), /* @__PURE__ */ jsx(Text, {
|
|
29347
30536
|
color: DIM_COLOR,
|
|
29348
|
-
children: "
|
|
30537
|
+
children: " │"
|
|
29349
30538
|
})] }, "arrow-2"),
|
|
29350
30539
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
29351
30540
|
/* @__PURE__ */ jsx(Text, {
|
|
@@ -29373,7 +30562,7 @@ const DEPLOY_PIPELINE = {
|
|
|
29373
30562
|
}),
|
|
29374
30563
|
/* @__PURE__ */ jsx(Text, {
|
|
29375
30564
|
color: DIM_COLOR,
|
|
29376
|
-
children: "
|
|
30565
|
+
children: " │"
|
|
29377
30566
|
})
|
|
29378
30567
|
] }, "build-deps"),
|
|
29379
30568
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
@@ -29387,7 +30576,7 @@ const DEPLOY_PIPELINE = {
|
|
|
29387
30576
|
}),
|
|
29388
30577
|
/* @__PURE__ */ jsx(Text, {
|
|
29389
30578
|
color: DIM_COLOR,
|
|
29390
|
-
children: "
|
|
30579
|
+
children: " │"
|
|
29391
30580
|
})
|
|
29392
30581
|
] }, "build-exec"),
|
|
29393
30582
|
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
@@ -29395,7 +30584,7 @@ const DEPLOY_PIPELINE = {
|
|
|
29395
30584
|
children: " │ ↓"
|
|
29396
30585
|
}), /* @__PURE__ */ jsx(Text, {
|
|
29397
30586
|
color: DIM_COLOR,
|
|
29398
|
-
children: "
|
|
30587
|
+
children: " │"
|
|
29399
30588
|
})] }, "arrow-3"),
|
|
29400
30589
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
29401
30590
|
/* @__PURE__ */ jsx(Text, {
|
|
@@ -29423,7 +30612,7 @@ const DEPLOY_PIPELINE = {
|
|
|
29423
30612
|
}),
|
|
29424
30613
|
/* @__PURE__ */ jsx(Text, {
|
|
29425
30614
|
color: DIM_COLOR,
|
|
29426
|
-
children: "
|
|
30615
|
+
children: " │"
|
|
29427
30616
|
})
|
|
29428
30617
|
] }, "url-label"),
|
|
29429
30618
|
/* @__PURE__ */ jsx(Text, {
|
|
@@ -29806,73 +30995,159 @@ function RunScreen({ store }) {
|
|
|
29806
30995
|
const topRows = Math.max(6, Math.floor(contentRows * .6));
|
|
29807
30996
|
const bottomRows = Math.max(4, contentRows - topRows);
|
|
29808
30997
|
const isNarrow = columns < 80;
|
|
29809
|
-
const helperContent = session.
|
|
30998
|
+
const helperContent = session.learnCardComplete ? /* @__PURE__ */ jsx(TipsCard, {}) : /* @__PURE__ */ jsx(LearnCard, {
|
|
29810
30999
|
store,
|
|
29811
31000
|
onComplete: () => store.setLearnCardComplete()
|
|
29812
31001
|
});
|
|
29813
|
-
const
|
|
29814
|
-
const
|
|
29815
|
-
|
|
29816
|
-
|
|
29817
|
-
|
|
31002
|
+
const hasAgentHelper = session.agentHelperLines.length > 0;
|
|
31003
|
+
const rightPane = /* @__PURE__ */ jsxs(Box, {
|
|
31004
|
+
flexDirection: "column",
|
|
31005
|
+
flexGrow: 1,
|
|
31006
|
+
overflow: "hidden",
|
|
31007
|
+
children: [/* @__PURE__ */ jsx(TasksPane, { store }), hasAgentHelper ? /* @__PURE__ */ jsx(Box, {
|
|
31008
|
+
marginTop: 1,
|
|
29818
31009
|
flexGrow: 1,
|
|
29819
|
-
|
|
29820
|
-
|
|
29821
|
-
|
|
29822
|
-
|
|
29823
|
-
|
|
29824
|
-
|
|
29825
|
-
|
|
29826
|
-
|
|
29827
|
-
|
|
29828
|
-
|
|
29829
|
-
|
|
29830
|
-
|
|
29831
|
-
|
|
29832
|
-
|
|
29833
|
-
|
|
29834
|
-
|
|
29835
|
-
|
|
29836
|
-
|
|
29837
|
-
|
|
29838
|
-
|
|
29839
|
-
|
|
29840
|
-
|
|
29841
|
-
|
|
29842
|
-
|
|
29843
|
-
|
|
29844
|
-
|
|
29845
|
-
|
|
29846
|
-
|
|
29847
|
-
|
|
31010
|
+
overflow: "hidden",
|
|
31011
|
+
children: /* @__PURE__ */ jsx(AgentHelperPane, { lines: session.agentHelperLines })
|
|
31012
|
+
}) : null]
|
|
31013
|
+
});
|
|
31014
|
+
const [activeTab, setActiveTab] = useState("agent");
|
|
31015
|
+
const [seenBuildCount, setSeenBuildCount] = useState(0);
|
|
31016
|
+
const unreadBuildLogs = activeTab !== "build" && session.buildLogLines.length > seenBuildCount;
|
|
31017
|
+
useEffect(() => {
|
|
31018
|
+
if (activeTab === "build") setSeenBuildCount(session.buildLogLines.length);
|
|
31019
|
+
}, [activeTab, session.buildLogLines.length]);
|
|
31020
|
+
useGatedInput((_input, key) => {
|
|
31021
|
+
if (key.tab) setActiveTab((prev) => prev === "agent" ? "build" : "agent");
|
|
31022
|
+
});
|
|
31023
|
+
const statusMessages = session.statusMessages;
|
|
31024
|
+
return /* @__PURE__ */ jsxs(ScreenLayout, { children: [/* @__PURE__ */ jsx(Box, {
|
|
31025
|
+
flexGrow: 1,
|
|
31026
|
+
flexShrink: 1,
|
|
31027
|
+
height: topRows,
|
|
31028
|
+
children: isNarrow ? /* @__PURE__ */ jsx(PaneBox, {
|
|
31029
|
+
title: "Tarefas",
|
|
31030
|
+
flexGrow: 1,
|
|
31031
|
+
children: rightPane
|
|
31032
|
+
}) : /* @__PURE__ */ jsxs(Box, {
|
|
31033
|
+
flexDirection: "row",
|
|
31034
|
+
flexGrow: 1,
|
|
31035
|
+
gap: 1,
|
|
31036
|
+
children: [/* @__PURE__ */ jsxs(PaneBox, {
|
|
31037
|
+
width: "50%",
|
|
31038
|
+
children: [/* @__PURE__ */ jsx(Box, {
|
|
29848
31039
|
flexDirection: "column",
|
|
31040
|
+
flexGrow: 1,
|
|
31041
|
+
flexShrink: 1,
|
|
29849
31042
|
overflow: "hidden",
|
|
29850
|
-
children:
|
|
29851
|
-
})
|
|
29852
|
-
|
|
29853
|
-
|
|
29854
|
-
|
|
29855
|
-
|
|
29856
|
-
|
|
29857
|
-
|
|
29858
|
-
|
|
31043
|
+
children: helperContent
|
|
31044
|
+
}), statusMessages.length > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
31045
|
+
marginTop: 1,
|
|
31046
|
+
flexShrink: 0,
|
|
31047
|
+
children: /* @__PURE__ */ jsx(ScrollableStatus, {
|
|
31048
|
+
messages: statusMessages,
|
|
31049
|
+
maxLines: 5
|
|
31050
|
+
})
|
|
31051
|
+
}) : null]
|
|
31052
|
+
}), /* @__PURE__ */ jsx(PaneBox, {
|
|
31053
|
+
title: "Tarefas",
|
|
31054
|
+
width: "50%",
|
|
31055
|
+
children: rightPane
|
|
31056
|
+
})]
|
|
31057
|
+
})
|
|
31058
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
31059
|
+
marginTop: 1,
|
|
31060
|
+
flexGrow: 0,
|
|
31061
|
+
flexShrink: 0,
|
|
31062
|
+
height: bottomRows,
|
|
31063
|
+
children: /* @__PURE__ */ jsx(PaneBox, {
|
|
31064
|
+
flexGrow: 1,
|
|
31065
|
+
header: /* @__PURE__ */ jsx(TabBar, {
|
|
31066
|
+
active: activeTab,
|
|
31067
|
+
tabs: [{
|
|
31068
|
+
id: "agent",
|
|
31069
|
+
label: "Agente"
|
|
31070
|
+
}, {
|
|
31071
|
+
id: "build",
|
|
31072
|
+
label: "Build",
|
|
31073
|
+
badge: unreadBuildLogs
|
|
31074
|
+
}]
|
|
31075
|
+
}),
|
|
31076
|
+
children: activeTab === "build" ? /* @__PURE__ */ jsx(BuildLogPane, {
|
|
31077
|
+
buildLogLines: session.buildLogLines,
|
|
31078
|
+
maxLines: bottomRows - 3
|
|
31079
|
+
}) : /* @__PURE__ */ jsx(ScrollableLogPane, {
|
|
31080
|
+
lines: session.agentOutputLines,
|
|
31081
|
+
maxLines: bottomRows - 2,
|
|
31082
|
+
isComplete: session.agentComplete
|
|
29859
31083
|
})
|
|
29860
|
-
})
|
|
29861
|
-
|
|
31084
|
+
})
|
|
31085
|
+
})] });
|
|
31086
|
+
}
|
|
31087
|
+
function PaneBox({ title, header, children, width, flexGrow }) {
|
|
31088
|
+
const headerNode = header ?? (title ? /* @__PURE__ */ jsx(Text, {
|
|
31089
|
+
bold: true,
|
|
31090
|
+
color: ACCENT_COLOR,
|
|
31091
|
+
children: title
|
|
31092
|
+
}) : null);
|
|
31093
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
31094
|
+
flexDirection: "column",
|
|
31095
|
+
width,
|
|
31096
|
+
height: "100%",
|
|
31097
|
+
flexGrow,
|
|
31098
|
+
borderStyle: "single",
|
|
31099
|
+
borderColor: DIM_COLOR,
|
|
31100
|
+
paddingX: 1,
|
|
31101
|
+
overflow: "hidden",
|
|
31102
|
+
children: [headerNode ? /* @__PURE__ */ jsx(Box, {
|
|
31103
|
+
marginBottom: 1,
|
|
31104
|
+
children: headerNode
|
|
31105
|
+
}) : null, /* @__PURE__ */ jsx(Box, {
|
|
29862
31106
|
flexDirection: "column",
|
|
29863
|
-
|
|
29864
|
-
paddingX: 1,
|
|
31107
|
+
flexGrow: 1,
|
|
29865
31108
|
overflow: "hidden",
|
|
29866
|
-
children
|
|
29867
|
-
|
|
29868
|
-
|
|
29869
|
-
|
|
29870
|
-
|
|
29871
|
-
|
|
29872
|
-
|
|
29873
|
-
|
|
31109
|
+
children
|
|
31110
|
+
})]
|
|
31111
|
+
});
|
|
31112
|
+
}
|
|
31113
|
+
function TabBar({ tabs, active: active$1 }) {
|
|
31114
|
+
const [pulse, setPulse] = useState(true);
|
|
31115
|
+
useEffect(() => {
|
|
31116
|
+
if (!tabs.some((t) => t.badge && t.id !== active$1)) {
|
|
31117
|
+
setPulse(true);
|
|
31118
|
+
return;
|
|
31119
|
+
}
|
|
31120
|
+
const id = setInterval(() => setPulse((p) => !p), 500);
|
|
31121
|
+
return () => clearInterval(id);
|
|
31122
|
+
}, [tabs, active$1]);
|
|
31123
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
31124
|
+
flexDirection: "row",
|
|
31125
|
+
gap: 2,
|
|
31126
|
+
children: tabs.map((tab, i) => {
|
|
31127
|
+
const isActive = tab.id === active$1;
|
|
31128
|
+
const flashing = Boolean(tab.badge) && !isActive;
|
|
31129
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
31130
|
+
flexDirection: "row",
|
|
31131
|
+
children: [
|
|
31132
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
31133
|
+
bold: true,
|
|
31134
|
+
color: isActive ? ACCENT_COLOR : flashing ? pulse ? BRAND_COLOR : ACCENT_COLOR : DIM_COLOR,
|
|
31135
|
+
dimColor: !isActive && !flashing,
|
|
31136
|
+
children: [isActive ? "● " : flashing ? pulse ? "◆ " : "◇ " : "○ ", tab.label]
|
|
31137
|
+
}),
|
|
31138
|
+
flashing ? /* @__PURE__ */ jsx(Text, {
|
|
31139
|
+
bold: true,
|
|
31140
|
+
color: pulse ? BRAND_COLOR : ACCENT_COLOR,
|
|
31141
|
+
children: pulse ? " NOVO" : " ●"
|
|
31142
|
+
}) : null,
|
|
31143
|
+
i === tabs.length - 1 ? /* @__PURE__ */ jsxs(Text, {
|
|
31144
|
+
dimColor: true,
|
|
31145
|
+
children: [" ", "Tab para alternar"]
|
|
31146
|
+
}) : null
|
|
31147
|
+
]
|
|
31148
|
+
}, tab.id);
|
|
29874
31149
|
})
|
|
29875
|
-
|
|
31150
|
+
});
|
|
29876
31151
|
}
|
|
29877
31152
|
function TaskIcon({ status }) {
|
|
29878
31153
|
switch (status) {
|
|
@@ -29912,57 +31187,94 @@ function TasksPane({ store }) {
|
|
|
29912
31187
|
const tasks = session.agentTasks;
|
|
29913
31188
|
const completedCount = tasks.filter((t) => t.status === "completed").length;
|
|
29914
31189
|
const totalCount = tasks.length;
|
|
31190
|
+
const progressText = totalCount > 0 && !session.agentComplete ? completedCount < totalCount ? `${completedCount}/${totalCount}` : "…" : null;
|
|
29915
31191
|
return /* @__PURE__ */ jsxs(Box, {
|
|
29916
31192
|
flexDirection: "column",
|
|
29917
|
-
|
|
29918
|
-
|
|
29919
|
-
|
|
29920
|
-
|
|
29921
|
-
|
|
29922
|
-
children: [!session.agentComplete ? /* @__PURE__ */ jsx(Spinner, { label: phaseLabel }) : /* @__PURE__ */ jsx(Text, {
|
|
31193
|
+
flexShrink: 0,
|
|
31194
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
31195
|
+
gap: 1,
|
|
31196
|
+
children: [
|
|
31197
|
+
!session.agentComplete ? /* @__PURE__ */ jsx(Spinner, { label: phaseLabel }) : /* @__PURE__ */ jsx(Text, {
|
|
29923
31198
|
bold: true,
|
|
29924
31199
|
color: session.agentError ? ERROR_COLOR : SUCCESS_COLOR,
|
|
29925
31200
|
children: phaseLabel
|
|
29926
|
-
}),
|
|
31201
|
+
}),
|
|
31202
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
29927
31203
|
dimColor: true,
|
|
29928
31204
|
children: [
|
|
29929
31205
|
"(",
|
|
29930
31206
|
elapsed,
|
|
29931
31207
|
")"
|
|
29932
31208
|
]
|
|
29933
|
-
})
|
|
29934
|
-
|
|
29935
|
-
|
|
29936
|
-
|
|
29937
|
-
|
|
29938
|
-
|
|
29939
|
-
const label = task.status === "in_progress" && task.activeForm ? task.activeForm : task.label;
|
|
29940
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
29941
|
-
gap: 1,
|
|
29942
|
-
children: [/* @__PURE__ */ jsx(TaskIcon, { status: task.status }), /* @__PURE__ */ jsx(Text, {
|
|
29943
|
-
dimColor: task.status === "pending",
|
|
29944
|
-
children: label
|
|
29945
|
-
})]
|
|
29946
|
-
}, task.id || i);
|
|
29947
|
-
}), totalCount > 0 && !session.agentComplete ? /* @__PURE__ */ jsx(Box, {
|
|
29948
|
-
marginTop: 1,
|
|
29949
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
29950
|
-
dimColor: true,
|
|
29951
|
-
children: completedCount < totalCount ? `Progresso: ${completedCount}/${totalCount}` : "Finalizando..."
|
|
29952
|
-
})
|
|
29953
|
-
}) : null]
|
|
29954
|
-
}) : !session.agentComplete ? /* @__PURE__ */ jsx(Text, {
|
|
29955
|
-
dimColor: true,
|
|
29956
|
-
children: "Analisando projeto..."
|
|
29957
|
-
}) : null,
|
|
29958
|
-
session.retryCount > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
29959
|
-
marginTop: 1,
|
|
29960
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
31209
|
+
}),
|
|
31210
|
+
progressText ? /* @__PURE__ */ jsxs(Text, {
|
|
31211
|
+
dimColor: true,
|
|
31212
|
+
children: ["· ", progressText]
|
|
31213
|
+
}) : null,
|
|
31214
|
+
session.retryCount > 0 ? /* @__PURE__ */ jsxs(Text, {
|
|
29961
31215
|
color: BRAND_COLOR,
|
|
29962
|
-
children: ["
|
|
29963
|
-
})
|
|
29964
|
-
|
|
29965
|
-
|
|
31216
|
+
children: ["· tentativa ", session.retryCount + 1]
|
|
31217
|
+
}) : null
|
|
31218
|
+
]
|
|
31219
|
+
}), tasks.length > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
31220
|
+
flexDirection: "column",
|
|
31221
|
+
marginTop: 1,
|
|
31222
|
+
children: tasks.map((task, i) => {
|
|
31223
|
+
const label = task.status === "in_progress" && task.activeForm ? task.activeForm : task.label;
|
|
31224
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
31225
|
+
gap: 1,
|
|
31226
|
+
children: [/* @__PURE__ */ jsx(TaskIcon, { status: task.status }), /* @__PURE__ */ jsx(Text, {
|
|
31227
|
+
dimColor: task.status === "pending",
|
|
31228
|
+
wrap: "truncate",
|
|
31229
|
+
children: label
|
|
31230
|
+
})]
|
|
31231
|
+
}, task.id || i);
|
|
31232
|
+
})
|
|
31233
|
+
}) : null]
|
|
31234
|
+
});
|
|
31235
|
+
}
|
|
31236
|
+
function ScrollableStatus({ messages, maxLines }) {
|
|
31237
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
31238
|
+
const [userScrolled, setUserScrolled] = useState(false);
|
|
31239
|
+
useEffect(() => {
|
|
31240
|
+
if (!userScrolled) setScrollOffset(0);
|
|
31241
|
+
}, [messages.length, userScrolled]);
|
|
31242
|
+
useGatedInput((_input, key) => {
|
|
31243
|
+
if (key.pageUp) {
|
|
31244
|
+
setUserScrolled(true);
|
|
31245
|
+
setScrollOffset((prev) => Math.min(prev + 3, Math.max(0, messages.length - maxLines)));
|
|
31246
|
+
}
|
|
31247
|
+
if (key.pageDown) setScrollOffset((prev) => {
|
|
31248
|
+
const next = Math.max(0, prev - 3);
|
|
31249
|
+
if (next === 0) setUserScrolled(false);
|
|
31250
|
+
return next;
|
|
31251
|
+
});
|
|
31252
|
+
});
|
|
31253
|
+
const endIdx = messages.length - scrollOffset;
|
|
31254
|
+
const startIdx = Math.max(0, endIdx - maxLines);
|
|
31255
|
+
const visible = messages.slice(startIdx, endIdx);
|
|
31256
|
+
const isAtBottom = scrollOffset === 0;
|
|
31257
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
31258
|
+
flexDirection: "column",
|
|
31259
|
+
children: [!isAtBottom ? /* @__PURE__ */ jsxs(Text, {
|
|
31260
|
+
dimColor: true,
|
|
31261
|
+
children: [
|
|
31262
|
+
"PgUp/PgDn · ",
|
|
31263
|
+
scrollOffset,
|
|
31264
|
+
" abaixo"
|
|
31265
|
+
]
|
|
31266
|
+
}) : null, visible.map((msg, i) => {
|
|
31267
|
+
const isCurrent = i === visible.length - 1 && isAtBottom;
|
|
31268
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
31269
|
+
color: DIM_COLOR,
|
|
31270
|
+
dimColor: !isCurrent,
|
|
31271
|
+
children: [
|
|
31272
|
+
isCurrent ? Icons.diamond : Icons.bullet,
|
|
31273
|
+
" ",
|
|
31274
|
+
msg
|
|
31275
|
+
]
|
|
31276
|
+
}, `${startIdx + i}`);
|
|
31277
|
+
})]
|
|
29966
31278
|
});
|
|
29967
31279
|
}
|
|
29968
31280
|
function ScrollableLogPane({ lines, maxLines, isComplete }) {
|
|
@@ -29975,7 +31287,7 @@ function ScrollableLogPane({ lines, maxLines, isComplete }) {
|
|
|
29975
31287
|
useEffect(() => {
|
|
29976
31288
|
if (userScrolled && scrollOffset === 0) setUserScrolled(false);
|
|
29977
31289
|
}, [scrollOffset, userScrolled]);
|
|
29978
|
-
|
|
31290
|
+
useGatedInput((_input, key) => {
|
|
29979
31291
|
if (key.upArrow) {
|
|
29980
31292
|
setUserScrolled(true);
|
|
29981
31293
|
setScrollOffset((prev) => Math.min(prev + 3, Math.max(0, lines.length - contentLines)));
|
|
@@ -29992,23 +31304,18 @@ function ScrollableLogPane({ lines, maxLines, isComplete }) {
|
|
|
29992
31304
|
return /* @__PURE__ */ jsxs(Box, {
|
|
29993
31305
|
flexDirection: "column",
|
|
29994
31306
|
height: maxLines,
|
|
29995
|
-
paddingX: 1,
|
|
29996
31307
|
overflow: "hidden",
|
|
29997
|
-
children: [/* @__PURE__ */
|
|
29998
|
-
|
|
29999
|
-
children:
|
|
30000
|
-
bold: true,
|
|
30001
|
-
color: ACCENT_COLOR,
|
|
30002
|
-
children: "Agente"
|
|
30003
|
-
}), !(scrollOffset === 0) ? /* @__PURE__ */ jsxs(Text, {
|
|
31308
|
+
children: [!(scrollOffset === 0) ? /* @__PURE__ */ jsx(Box, {
|
|
31309
|
+
marginBottom: 1,
|
|
31310
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
30004
31311
|
dimColor: true,
|
|
30005
31312
|
children: [
|
|
30006
31313
|
"↑↓ rolar · ",
|
|
30007
31314
|
scrollOffset,
|
|
30008
31315
|
" linhas abaixo"
|
|
30009
31316
|
]
|
|
30010
|
-
})
|
|
30011
|
-
}), visibleLines.length > 0 ? visibleLines.map((line, i) => {
|
|
31317
|
+
})
|
|
31318
|
+
}) : null, visibleLines.length > 0 ? visibleLines.map((line, i) => {
|
|
30012
31319
|
if (line.startsWith("[tool] ")) return /* @__PURE__ */ jsx(Text, {
|
|
30013
31320
|
color: CYAN_COLOR,
|
|
30014
31321
|
wrap: "wrap",
|
|
@@ -30026,12 +31333,37 @@ function ScrollableLogPane({ lines, maxLines, isComplete }) {
|
|
|
30026
31333
|
});
|
|
30027
31334
|
}
|
|
30028
31335
|
function BuildLogPane({ buildLogLines, maxLines }) {
|
|
30029
|
-
const
|
|
30030
|
-
|
|
30031
|
-
|
|
30032
|
-
|
|
30033
|
-
|
|
30034
|
-
}
|
|
31336
|
+
const contentLines = Math.max(1, maxLines - 1);
|
|
31337
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
31338
|
+
const [userScrolled, setUserScrolled] = useState(false);
|
|
31339
|
+
useEffect(() => {
|
|
31340
|
+
if (!userScrolled) setScrollOffset(0);
|
|
31341
|
+
}, [buildLogLines.length, userScrolled]);
|
|
31342
|
+
useGatedInput((_input, key) => {
|
|
31343
|
+
if (key.upArrow) {
|
|
31344
|
+
setUserScrolled(true);
|
|
31345
|
+
setScrollOffset((prev) => Math.min(prev + 3, Math.max(0, buildLogLines.length - contentLines)));
|
|
31346
|
+
}
|
|
31347
|
+
if (key.downArrow) setScrollOffset((prev) => {
|
|
31348
|
+
const next = Math.max(0, prev - 3);
|
|
31349
|
+
if (next === 0) setUserScrolled(false);
|
|
31350
|
+
return next;
|
|
31351
|
+
});
|
|
31352
|
+
});
|
|
31353
|
+
const endIdx = buildLogLines.length - scrollOffset;
|
|
31354
|
+
const startIdx = Math.max(0, endIdx - contentLines);
|
|
31355
|
+
const recentLines = buildLogLines.slice(startIdx, endIdx);
|
|
31356
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [!(scrollOffset === 0) ? /* @__PURE__ */ jsx(Box, {
|
|
31357
|
+
marginBottom: 1,
|
|
31358
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
31359
|
+
dimColor: true,
|
|
31360
|
+
children: [
|
|
31361
|
+
"↑↓ rolar · ",
|
|
31362
|
+
scrollOffset,
|
|
31363
|
+
" linhas abaixo"
|
|
31364
|
+
]
|
|
31365
|
+
})
|
|
31366
|
+
}) : null, recentLines.map((line, i) => {
|
|
30035
31367
|
if (line.startsWith("[status] ")) {
|
|
30036
31368
|
const statusText = line.slice(9);
|
|
30037
31369
|
return /* @__PURE__ */ jsxs(Text, {
|
|
@@ -30108,10 +31440,12 @@ function AgentHelperPane({ lines }) {
|
|
|
30108
31440
|
if (i === 0 && line.trim()) return /* @__PURE__ */ jsx(Text, {
|
|
30109
31441
|
bold: true,
|
|
30110
31442
|
color: ACCENT_COLOR,
|
|
31443
|
+
wrap: "truncate",
|
|
30111
31444
|
children: line
|
|
30112
31445
|
}, i);
|
|
30113
31446
|
return /* @__PURE__ */ jsx(Text, {
|
|
30114
31447
|
color: DIM_COLOR,
|
|
31448
|
+
wrap: "truncate",
|
|
30115
31449
|
children: line
|
|
30116
31450
|
}, i);
|
|
30117
31451
|
})
|
|
@@ -30147,6 +31481,17 @@ function MarkdownText({ children }) {
|
|
|
30147
31481
|
children: children.split("\n").map((line, i) => {
|
|
30148
31482
|
const trimmed = line.trimStart();
|
|
30149
31483
|
if (trimmed === "") return /* @__PURE__ */ jsx(Box, { height: 1 }, i);
|
|
31484
|
+
const headingMatch = trimmed.match(/^(#{1,3})\s+(.*)$/);
|
|
31485
|
+
if (headingMatch) {
|
|
31486
|
+
const level = headingMatch[1]?.length ?? 1;
|
|
31487
|
+
const content = headingMatch[2] ?? "";
|
|
31488
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
31489
|
+
bold: true,
|
|
31490
|
+
color: level === 1 ? BRAND_COLOR : level === 2 ? ACCENT_COLOR : void 0,
|
|
31491
|
+
wrap: "wrap",
|
|
31492
|
+
children: renderInline(content, `h${level}-${i}`)
|
|
31493
|
+
}, i);
|
|
31494
|
+
}
|
|
30150
31495
|
if (trimmed.startsWith("- ") || trimmed.startsWith("* ")) {
|
|
30151
31496
|
const content = trimmed.slice(2);
|
|
30152
31497
|
return /* @__PURE__ */ jsxs(Box, {
|
|
@@ -30183,7 +31528,7 @@ function OutroScreen({ store }) {
|
|
|
30183
31528
|
const [columns] = useStdoutDimensions();
|
|
30184
31529
|
const sepWidth = Math.min(columns - 8, 80);
|
|
30185
31530
|
const aiSetup = useMemo(() => checkAiSetup(), []);
|
|
30186
|
-
|
|
31531
|
+
useGatedInput((_input, key) => {
|
|
30187
31532
|
if (key.return || key.escape) {
|
|
30188
31533
|
store.dismissOutro();
|
|
30189
31534
|
process.exit(isSuccess$4 ? 0 : 1);
|
|
@@ -30234,10 +31579,12 @@ function OutroScreen({ store }) {
|
|
|
30234
31579
|
if (i === 0 && line.trim()) return /* @__PURE__ */ jsx(Text, {
|
|
30235
31580
|
bold: true,
|
|
30236
31581
|
color: ACCENT_COLOR,
|
|
31582
|
+
wrap: "truncate",
|
|
30237
31583
|
children: line
|
|
30238
31584
|
}, i);
|
|
30239
31585
|
return /* @__PURE__ */ jsx(Text, {
|
|
30240
31586
|
color: DIM_COLOR,
|
|
31587
|
+
wrap: "truncate",
|
|
30241
31588
|
children: line
|
|
30242
31589
|
}, i);
|
|
30243
31590
|
})
|
|
@@ -30381,7 +31728,7 @@ function Separator({ width }) {
|
|
|
30381
31728
|
//#endregion
|
|
30382
31729
|
//#region src/wizard/ui/overlays/ErrorOverlay.tsx
|
|
30383
31730
|
function ErrorOverlay({ message, onDismiss }) {
|
|
30384
|
-
|
|
31731
|
+
useGatedInput((_input, key) => {
|
|
30385
31732
|
if (key.return || key.escape) onDismiss();
|
|
30386
31733
|
});
|
|
30387
31734
|
return /* @__PURE__ */ jsxs(Box, {
|
|
@@ -30415,7 +31762,7 @@ function ErrorOverlay({ message, onDismiss }) {
|
|
|
30415
31762
|
//#region src/wizard/ui/overlays/ConfirmExitOverlay.tsx
|
|
30416
31763
|
function ConfirmExitOverlay({ onConfirm, onCancel }) {
|
|
30417
31764
|
const [selected, setSelected] = useState(1);
|
|
30418
|
-
|
|
31765
|
+
useGatedInput((_input, key) => {
|
|
30419
31766
|
if (key.upArrow || key.downArrow) setSelected((s) => s === 0 ? 1 : 0);
|
|
30420
31767
|
if (key.return) if (selected === 0) onConfirm();
|
|
30421
31768
|
else onCancel();
|
|
@@ -30462,6 +31809,11 @@ function ConfirmExitOverlay({ onConfirm, onCancel }) {
|
|
|
30462
31809
|
* Shows question + hint with pulse animation. Supports text input,
|
|
30463
31810
|
* confirm (yes/no), and choice selection. User can skip env prompts.
|
|
30464
31811
|
*/
|
|
31812
|
+
/** Preferred prompt box width, clamped to terminal columns minus margin. */
|
|
31813
|
+
function useOverlayWidth() {
|
|
31814
|
+
const [columns] = useStdoutDimensions();
|
|
31815
|
+
return Math.max(30, Math.min(60, columns - 4));
|
|
31816
|
+
}
|
|
30465
31817
|
function PromptOverlay({ prompt: prompt$1, onSubmit, onSkip }) {
|
|
30466
31818
|
const [pulse, setPulse] = useState(true);
|
|
30467
31819
|
useEffect(() => {
|
|
@@ -30489,6 +31841,7 @@ function PromptOverlay({ prompt: prompt$1, onSubmit, onSkip }) {
|
|
|
30489
31841
|
function TextPrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
30490
31842
|
const [value, setValue] = useState("");
|
|
30491
31843
|
const [mode, setMode] = useState("ask");
|
|
31844
|
+
const width = useOverlayWidth();
|
|
30492
31845
|
useInput((input, key) => {
|
|
30493
31846
|
if (mode === "ask") {
|
|
30494
31847
|
if (input === "1" || key.return) setMode("input");
|
|
@@ -30515,7 +31868,7 @@ function TextPrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
30515
31868
|
borderColor: pulse ? BRAND_COLOR : ACCENT_COLOR,
|
|
30516
31869
|
paddingX: 2,
|
|
30517
31870
|
paddingY: 1,
|
|
30518
|
-
width
|
|
31871
|
+
width,
|
|
30519
31872
|
children: [
|
|
30520
31873
|
/* @__PURE__ */ jsxs(Box, {
|
|
30521
31874
|
gap: 1,
|
|
@@ -30530,10 +31883,7 @@ function TextPrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
30530
31883
|
}),
|
|
30531
31884
|
/* @__PURE__ */ jsx(Box, {
|
|
30532
31885
|
marginTop: 1,
|
|
30533
|
-
children: /* @__PURE__ */ jsx(
|
|
30534
|
-
wrap: "wrap",
|
|
30535
|
-
children: prompt$1.question
|
|
30536
|
-
})
|
|
31886
|
+
children: /* @__PURE__ */ jsx(MarkdownText, { children: prompt$1.question })
|
|
30537
31887
|
}),
|
|
30538
31888
|
prompt$1.hint ? /* @__PURE__ */ jsxs(Box, {
|
|
30539
31889
|
marginTop: 1,
|
|
@@ -30542,10 +31892,9 @@ function TextPrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
30542
31892
|
color: CYAN_COLOR,
|
|
30543
31893
|
bold: true,
|
|
30544
31894
|
children: "Dica:"
|
|
30545
|
-
}), /* @__PURE__ */
|
|
30546
|
-
|
|
30547
|
-
|
|
30548
|
-
children: [" ", prompt$1.hint]
|
|
31895
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
31896
|
+
marginLeft: 1,
|
|
31897
|
+
children: /* @__PURE__ */ jsx(MarkdownText, { children: prompt$1.hint })
|
|
30549
31898
|
})]
|
|
30550
31899
|
}) : null,
|
|
30551
31900
|
prompt$1.envKey ? /* @__PURE__ */ jsx(Box, {
|
|
@@ -30598,6 +31947,7 @@ function TextPrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
30598
31947
|
}
|
|
30599
31948
|
function ConfirmPrompt({ prompt: prompt$1, pulse, onSubmit }) {
|
|
30600
31949
|
const [selected, setSelected] = useState(0);
|
|
31950
|
+
const width = useOverlayWidth();
|
|
30601
31951
|
useInput((_input, key) => {
|
|
30602
31952
|
if (key.upArrow || key.downArrow) setSelected((s) => s === 0 ? 1 : 0);
|
|
30603
31953
|
if (key.return) onSubmit(selected === 0 ? "sim" : "não");
|
|
@@ -30608,7 +31958,7 @@ function ConfirmPrompt({ prompt: prompt$1, pulse, onSubmit }) {
|
|
|
30608
31958
|
borderColor: pulse ? BRAND_COLOR : ACCENT_COLOR,
|
|
30609
31959
|
paddingX: 2,
|
|
30610
31960
|
paddingY: 1,
|
|
30611
|
-
width
|
|
31961
|
+
width,
|
|
30612
31962
|
children: [
|
|
30613
31963
|
/* @__PURE__ */ jsxs(Box, {
|
|
30614
31964
|
gap: 1,
|
|
@@ -30623,10 +31973,7 @@ function ConfirmPrompt({ prompt: prompt$1, pulse, onSubmit }) {
|
|
|
30623
31973
|
}),
|
|
30624
31974
|
/* @__PURE__ */ jsx(Box, {
|
|
30625
31975
|
marginTop: 1,
|
|
30626
|
-
children: /* @__PURE__ */ jsx(
|
|
30627
|
-
wrap: "wrap",
|
|
30628
|
-
children: prompt$1.question
|
|
30629
|
-
})
|
|
31976
|
+
children: /* @__PURE__ */ jsx(MarkdownText, { children: prompt$1.question })
|
|
30630
31977
|
}),
|
|
30631
31978
|
/* @__PURE__ */ jsx(Box, {
|
|
30632
31979
|
marginTop: 1,
|
|
@@ -30648,6 +31995,7 @@ function ConfirmPrompt({ prompt: prompt$1, pulse, onSubmit }) {
|
|
|
30648
31995
|
function ChoicePrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
30649
31996
|
const options = prompt$1.options ?? [];
|
|
30650
31997
|
const [selected, setSelected] = useState(0);
|
|
31998
|
+
const width = useOverlayWidth();
|
|
30651
31999
|
useInput((_input, key) => {
|
|
30652
32000
|
if (key.upArrow) setSelected((s) => Math.max(0, s - 1));
|
|
30653
32001
|
if (key.downArrow) setSelected((s) => Math.min(options.length - 1, s + 1));
|
|
@@ -30660,7 +32008,7 @@ function ChoicePrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
30660
32008
|
borderColor: pulse ? BRAND_COLOR : ACCENT_COLOR,
|
|
30661
32009
|
paddingX: 2,
|
|
30662
32010
|
paddingY: 1,
|
|
30663
|
-
width
|
|
32011
|
+
width,
|
|
30664
32012
|
children: [
|
|
30665
32013
|
/* @__PURE__ */ jsxs(Box, {
|
|
30666
32014
|
gap: 1,
|
|
@@ -30675,18 +32023,11 @@ function ChoicePrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
30675
32023
|
}),
|
|
30676
32024
|
/* @__PURE__ */ jsx(Box, {
|
|
30677
32025
|
marginTop: 1,
|
|
30678
|
-
children: /* @__PURE__ */ jsx(
|
|
30679
|
-
wrap: "wrap",
|
|
30680
|
-
children: prompt$1.question
|
|
30681
|
-
})
|
|
32026
|
+
children: /* @__PURE__ */ jsx(MarkdownText, { children: prompt$1.question })
|
|
30682
32027
|
}),
|
|
30683
32028
|
prompt$1.hint ? /* @__PURE__ */ jsx(Box, {
|
|
30684
32029
|
marginTop: 1,
|
|
30685
|
-
children: /* @__PURE__ */ jsx(
|
|
30686
|
-
color: DIM_COLOR,
|
|
30687
|
-
wrap: "wrap",
|
|
30688
|
-
children: prompt$1.hint
|
|
30689
|
-
})
|
|
32030
|
+
children: /* @__PURE__ */ jsx(MarkdownText, { children: prompt$1.hint })
|
|
30690
32031
|
}) : null,
|
|
30691
32032
|
/* @__PURE__ */ jsx(Box, {
|
|
30692
32033
|
marginTop: 1,
|
|
@@ -30717,83 +32058,82 @@ function ChoicePrompt({ prompt: prompt$1, pulse, onSubmit, onSkip }) {
|
|
|
30717
32058
|
//#region src/wizard/ui/App.tsx
|
|
30718
32059
|
function App({ store, router, cwd }) {
|
|
30719
32060
|
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
32061
|
+
const [, rows] = useStdoutDimensions();
|
|
30720
32062
|
const { session } = store;
|
|
30721
32063
|
const writePromptResponse = useCallback((value, skipped) => {
|
|
30722
|
-
|
|
32064
|
+
const responsePath = join(cwd, ".veloz-wizard-response.json");
|
|
32065
|
+
getSessionLogger().log("prompt-response", "user responded to agent prompt", {
|
|
32066
|
+
skipped,
|
|
32067
|
+
bytes: value.length,
|
|
32068
|
+
promptId: session.agentPrompt?.id ?? null,
|
|
32069
|
+
envKey: session.agentPrompt?.envKey ?? null
|
|
32070
|
+
});
|
|
32071
|
+
writeFileSync(responsePath, JSON.stringify({
|
|
30723
32072
|
value,
|
|
30724
32073
|
skipped
|
|
30725
32074
|
}));
|
|
30726
32075
|
store.clearAgentPrompt();
|
|
30727
|
-
}, [
|
|
30728
|
-
|
|
30729
|
-
|
|
30730
|
-
|
|
30731
|
-
|
|
30732
|
-
});
|
|
32076
|
+
}, [
|
|
32077
|
+
cwd,
|
|
32078
|
+
store,
|
|
32079
|
+
session.agentPrompt
|
|
32080
|
+
]);
|
|
30733
32081
|
const overlay = router.activeOverlay;
|
|
30734
|
-
|
|
32082
|
+
let baseLayer;
|
|
32083
|
+
if (overlay === Overlay.ConfirmExit) baseLayer = /* @__PURE__ */ jsx(ConfirmExitOverlay, {
|
|
30735
32084
|
onConfirm: () => {
|
|
30736
32085
|
router.popOverlay();
|
|
30737
32086
|
process.exit(130);
|
|
30738
32087
|
},
|
|
30739
32088
|
onCancel: () => router.popOverlay()
|
|
30740
32089
|
});
|
|
30741
|
-
if (overlay === Overlay.Error)
|
|
32090
|
+
else if (overlay === Overlay.Error) baseLayer = /* @__PURE__ */ jsx(ErrorOverlay, {
|
|
30742
32091
|
message: session.agentError ?? "Erro desconhecido",
|
|
30743
32092
|
onDismiss: () => router.popOverlay()
|
|
30744
32093
|
});
|
|
30745
|
-
switch (router.resolve(session)) {
|
|
30746
|
-
case Screen.Intro:
|
|
30747
|
-
|
|
30748
|
-
|
|
30749
|
-
case Screen.
|
|
30750
|
-
|
|
32094
|
+
else switch (router.resolve(session)) {
|
|
32095
|
+
case Screen.Intro:
|
|
32096
|
+
baseLayer = /* @__PURE__ */ jsx(IntroScreen, { store });
|
|
32097
|
+
break;
|
|
32098
|
+
case Screen.Auth:
|
|
32099
|
+
baseLayer = /* @__PURE__ */ jsx(AuthScreen, { store });
|
|
32100
|
+
break;
|
|
32101
|
+
case Screen.Notes:
|
|
32102
|
+
baseLayer = /* @__PURE__ */ jsx(NotesScreen, { store });
|
|
32103
|
+
break;
|
|
32104
|
+
case Screen.Run:
|
|
32105
|
+
baseLayer = /* @__PURE__ */ jsx(RunScreen, { store });
|
|
32106
|
+
break;
|
|
32107
|
+
case Screen.Outro:
|
|
32108
|
+
baseLayer = /* @__PURE__ */ jsx(OutroScreen, { store });
|
|
32109
|
+
break;
|
|
32110
|
+
default: baseLayer = /* @__PURE__ */ jsx(IntroScreen, { store });
|
|
30751
32111
|
}
|
|
30752
|
-
|
|
30753
|
-
|
|
30754
|
-
|
|
30755
|
-
|
|
30756
|
-
|
|
30757
|
-
|
|
30758
|
-
|
|
30759
|
-
|
|
30760
|
-
|
|
30761
|
-
|
|
30762
|
-
|
|
30763
|
-
|
|
30764
|
-
|
|
30765
|
-
|
|
30766
|
-
|
|
30767
|
-
|
|
30768
|
-
|
|
30769
|
-
|
|
30770
|
-
|
|
30771
|
-
|
|
30772
|
-
|
|
30773
|
-
|
|
30774
|
-
|
|
30775
|
-
|
|
30776
|
-
|
|
30777
|
-
agentLogLines: [],
|
|
30778
|
-
agentComplete: false,
|
|
30779
|
-
agentError: null,
|
|
30780
|
-
deployUrl: null,
|
|
30781
|
-
retryCount: 0,
|
|
30782
|
-
buildLogLines: [],
|
|
30783
|
-
buildStreamActive: false,
|
|
30784
|
-
buildDeploymentId: null,
|
|
30785
|
-
statusMessages: [],
|
|
30786
|
-
agentOutputLines: [],
|
|
30787
|
-
toolCallLog: [],
|
|
30788
|
-
learnCardBlockIdx: 0,
|
|
30789
|
-
learnCardComplete: false,
|
|
30790
|
-
agentHelperLines: [],
|
|
30791
|
-
agentPrompt: null,
|
|
30792
|
-
agentSummary: null,
|
|
30793
|
-
introConfirmed: false,
|
|
30794
|
-
outroDismissed: false,
|
|
30795
|
-
agentStartedAt: null
|
|
30796
|
-
};
|
|
32112
|
+
const prompt$1 = session.agentPrompt;
|
|
32113
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
32114
|
+
flexDirection: "column",
|
|
32115
|
+
height: rows,
|
|
32116
|
+
children: [/* @__PURE__ */ jsx(InputFocusProvider, {
|
|
32117
|
+
active: !prompt$1,
|
|
32118
|
+
children: /* @__PURE__ */ jsx(Box, {
|
|
32119
|
+
flexDirection: "column",
|
|
32120
|
+
flexGrow: 1,
|
|
32121
|
+
flexShrink: 1,
|
|
32122
|
+
minHeight: 0,
|
|
32123
|
+
overflow: "hidden",
|
|
32124
|
+
children: baseLayer
|
|
32125
|
+
})
|
|
32126
|
+
}), prompt$1 ? /* @__PURE__ */ jsx(Box, {
|
|
32127
|
+
flexShrink: 0,
|
|
32128
|
+
paddingX: 2,
|
|
32129
|
+
paddingY: 1,
|
|
32130
|
+
children: /* @__PURE__ */ jsx(PromptOverlay, {
|
|
32131
|
+
prompt: prompt$1,
|
|
32132
|
+
onSubmit: (value) => writePromptResponse(value, false),
|
|
32133
|
+
onSkip: () => writePromptResponse("", true)
|
|
32134
|
+
})
|
|
32135
|
+
}) : null]
|
|
32136
|
+
});
|
|
30797
32137
|
}
|
|
30798
32138
|
|
|
30799
32139
|
//#endregion
|
|
@@ -30874,6 +32214,12 @@ var WizardStore = class {
|
|
|
30874
32214
|
this.$session.setKey("introConfirmed", true);
|
|
30875
32215
|
this.emitChange();
|
|
30876
32216
|
}
|
|
32217
|
+
confirmNotes(notes) {
|
|
32218
|
+
const trimmed = notes?.trim();
|
|
32219
|
+
this.$session.setKey("userNotes", trimmed && trimmed.length > 0 ? trimmed : null);
|
|
32220
|
+
this.$session.setKey("notesConfirmed", true);
|
|
32221
|
+
this.emitChange();
|
|
32222
|
+
}
|
|
30877
32223
|
setAuth(data) {
|
|
30878
32224
|
this.$session.setKey("isAuthenticated", true);
|
|
30879
32225
|
this.$session.setKey("userName", data.userName);
|
|
@@ -30902,7 +32248,32 @@ var WizardStore = class {
|
|
|
30902
32248
|
this.$session.setKey("selectedOrgId", orgId);
|
|
30903
32249
|
this.emitChange();
|
|
30904
32250
|
}
|
|
32251
|
+
/** User chose "create new" from the org picker — switch to the create screen. */
|
|
32252
|
+
requestOrgCreation() {
|
|
32253
|
+
this.$session.setKey("authPhase", "org-create");
|
|
32254
|
+
this.$session.setKey("newOrgName", null);
|
|
32255
|
+
this.$session.setKey("orgCreateError", null);
|
|
32256
|
+
this.emitChange();
|
|
32257
|
+
}
|
|
32258
|
+
/** User submitted a name on the org-create screen. The runner picks this up via a gate. */
|
|
32259
|
+
submitNewOrgName(name) {
|
|
32260
|
+
this.$session.setKey("newOrgName", name);
|
|
32261
|
+
this.$session.setKey("authPhase", "org-creating");
|
|
32262
|
+
this.$session.setKey("orgCreateError", null);
|
|
32263
|
+
this.emitChange();
|
|
32264
|
+
}
|
|
32265
|
+
/** Creation failed — surface the error and send the user back to the input. */
|
|
32266
|
+
failOrgCreation(error) {
|
|
32267
|
+
this.$session.setKey("orgCreateError", error);
|
|
32268
|
+
this.$session.setKey("newOrgName", null);
|
|
32269
|
+
this.$session.setKey("authPhase", "org-create");
|
|
32270
|
+
this.emitChange();
|
|
32271
|
+
}
|
|
30905
32272
|
setAgentPhase(phase) {
|
|
32273
|
+
getSessionLogger().logStoreEvent("agentPhase", {
|
|
32274
|
+
from: this.session.agentPhase,
|
|
32275
|
+
to: phase
|
|
32276
|
+
});
|
|
30906
32277
|
this.$session.setKey("agentPhase", phase);
|
|
30907
32278
|
if (phase !== "idle" && !this.session.agentStartedAt) this.$session.setKey("agentStartedAt", Date.now());
|
|
30908
32279
|
this.emitChange();
|
|
@@ -30913,6 +32284,14 @@ var WizardStore = class {
|
|
|
30913
32284
|
this.emitChange();
|
|
30914
32285
|
}
|
|
30915
32286
|
syncTasks(tasks) {
|
|
32287
|
+
getSessionLogger().logStoreEvent("syncTasks", {
|
|
32288
|
+
count: tasks.length,
|
|
32289
|
+
tasks: tasks.map((t) => ({
|
|
32290
|
+
id: t.id,
|
|
32291
|
+
label: t.label,
|
|
32292
|
+
status: t.status
|
|
32293
|
+
}))
|
|
32294
|
+
});
|
|
30916
32295
|
this.$session.setKey("agentTasks", tasks);
|
|
30917
32296
|
this.emitChange();
|
|
30918
32297
|
}
|
|
@@ -30925,15 +32304,18 @@ var WizardStore = class {
|
|
|
30925
32304
|
this.emitChange();
|
|
30926
32305
|
}
|
|
30927
32306
|
setDeployUrl(url) {
|
|
32307
|
+
getSessionLogger().logStoreEvent("setDeployUrl", { url });
|
|
30928
32308
|
this.$session.setKey("deployUrl", url);
|
|
30929
32309
|
this.emitChange();
|
|
30930
32310
|
}
|
|
30931
32311
|
completeAgent() {
|
|
32312
|
+
getSessionLogger().logStoreEvent("completeAgent");
|
|
30932
32313
|
this.$session.setKey("agentComplete", true);
|
|
30933
32314
|
this.$session.setKey("agentPhase", "done");
|
|
30934
32315
|
this.emitChange();
|
|
30935
32316
|
}
|
|
30936
32317
|
failAgent(error) {
|
|
32318
|
+
getSessionLogger().logStoreEvent("failAgent", { error });
|
|
30937
32319
|
this.$session.setKey("agentError", error);
|
|
30938
32320
|
this.$session.setKey("agentPhase", "error");
|
|
30939
32321
|
this.$session.setKey("agentComplete", true);
|
|
@@ -30951,6 +32333,7 @@ var WizardStore = class {
|
|
|
30951
32333
|
pushStatus(message) {
|
|
30952
32334
|
const msgs = this.session.statusMessages;
|
|
30953
32335
|
if (msgs.length > 0 && msgs[msgs.length - 1] === message) return;
|
|
32336
|
+
getSessionLogger().logStoreEvent("status", { message });
|
|
30954
32337
|
this.$session.setKey("statusMessages", [...msgs, message].slice(-50));
|
|
30955
32338
|
this.emitChange();
|
|
30956
32339
|
}
|
|
@@ -30989,32 +32372,43 @@ var WizardStore = class {
|
|
|
30989
32372
|
}
|
|
30990
32373
|
/** Show a prompt overlay to the user. */
|
|
30991
32374
|
setAgentPrompt(prompt$1) {
|
|
32375
|
+
getSessionLogger().logStoreEvent("setAgentPrompt", {
|
|
32376
|
+
id: prompt$1.id,
|
|
32377
|
+
type: prompt$1.type,
|
|
32378
|
+
envKey: prompt$1.envKey,
|
|
32379
|
+
question: prompt$1.question
|
|
32380
|
+
});
|
|
30992
32381
|
this.$session.setKey("agentPrompt", prompt$1);
|
|
30993
32382
|
this.emitChange();
|
|
30994
32383
|
}
|
|
30995
32384
|
/** Clear the prompt overlay after user responds. */
|
|
30996
32385
|
clearAgentPrompt() {
|
|
32386
|
+
getSessionLogger().logStoreEvent("clearAgentPrompt");
|
|
30997
32387
|
this.$session.setKey("agentPrompt", null);
|
|
30998
32388
|
this.emitChange();
|
|
30999
32389
|
}
|
|
31000
32390
|
/** Set the agent's final summary for the outro screen. */
|
|
31001
32391
|
setAgentSummary(summary) {
|
|
32392
|
+
getSessionLogger().logStoreEvent("setAgentSummary", { bytes: summary.length });
|
|
31002
32393
|
this.$session.setKey("agentSummary", summary);
|
|
31003
32394
|
this.emitChange();
|
|
31004
32395
|
}
|
|
31005
32396
|
/** Push a build log line from the deployment stream. */
|
|
31006
32397
|
pushBuildLog(line) {
|
|
32398
|
+
getSessionLogger().logBuildStream("line", { line });
|
|
31007
32399
|
const lines = [...this.session.buildLogLines, line].slice(-500);
|
|
31008
32400
|
this.$session.setKey("buildLogLines", lines);
|
|
31009
32401
|
this.emitChange();
|
|
31010
32402
|
}
|
|
31011
32403
|
/** Mark the build log stream as active/inactive. */
|
|
31012
|
-
setBuildStreamActive(active) {
|
|
31013
|
-
|
|
32404
|
+
setBuildStreamActive(active$1) {
|
|
32405
|
+
getSessionLogger().logBuildStream("active", { active: active$1 });
|
|
32406
|
+
this.$session.setKey("buildStreamActive", active$1);
|
|
31014
32407
|
this.emitChange();
|
|
31015
32408
|
}
|
|
31016
32409
|
/** Set the deployment ID being streamed. */
|
|
31017
32410
|
setBuildDeploymentId(id) {
|
|
32411
|
+
getSessionLogger().logBuildStream("deploymentId", { id });
|
|
31018
32412
|
this.$session.setKey("buildDeploymentId", id);
|
|
31019
32413
|
this.emitChange();
|
|
31020
32414
|
}
|
|
@@ -31042,7 +32436,12 @@ function startTUI(cwd) {
|
|
|
31042
32436
|
let sigintCount = 0;
|
|
31043
32437
|
process.on("SIGINT", () => {
|
|
31044
32438
|
sigintCount++;
|
|
31045
|
-
|
|
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
|
+
}
|
|
31046
32445
|
router.pushOverlay(Overlay.ConfirmExit);
|
|
31047
32446
|
});
|
|
31048
32447
|
return {
|
|
@@ -31053,6 +32452,122 @@ function startTUI(cwd) {
|
|
|
31053
32452
|
};
|
|
31054
32453
|
}
|
|
31055
32454
|
|
|
32455
|
+
//#endregion
|
|
32456
|
+
//#region src/wizard/upload-telemetry.ts
|
|
32457
|
+
/**
|
|
32458
|
+
* Upload per-session debug logs to MGC via presigned URLs.
|
|
32459
|
+
*
|
|
32460
|
+
* Flow:
|
|
32461
|
+
* 1. Read meta.json from the session folder
|
|
32462
|
+
* 2. Call `initTelemetry.start` → server creates InitSession row + returns
|
|
32463
|
+
* a presigned PUT URL for each file
|
|
32464
|
+
* 3. PUT each file to MGC directly (no server round-trip)
|
|
32465
|
+
* 4. Call `initTelemetry.finalize` → server patches the row with outcome
|
|
32466
|
+
*
|
|
32467
|
+
* Runs best-effort. Errors are logged to stderr only so they never block
|
|
32468
|
+
* `veloz init`. Disabled via `VELOZ_NO_TELEMETRY=1`.
|
|
32469
|
+
*/
|
|
32470
|
+
const CONTENT_TYPES = {
|
|
32471
|
+
"meta.json": "application/json",
|
|
32472
|
+
"events.jsonl": "application/x-ndjson",
|
|
32473
|
+
"sdk-messages.jsonl": "application/x-ndjson",
|
|
32474
|
+
"tool-calls.jsonl": "application/x-ndjson",
|
|
32475
|
+
"store-events.jsonl": "application/x-ndjson",
|
|
32476
|
+
"build-stream.jsonl": "application/x-ndjson",
|
|
32477
|
+
"mcp-tools.jsonl": "application/x-ndjson",
|
|
32478
|
+
"session.log": "text/plain; charset=utf-8",
|
|
32479
|
+
"prompts.md": "text/markdown; charset=utf-8"
|
|
32480
|
+
};
|
|
32481
|
+
function contentTypeFor(filename) {
|
|
32482
|
+
return CONTENT_TYPES[filename] ?? "application/octet-stream";
|
|
32483
|
+
}
|
|
32484
|
+
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
|
+
}
|
|
32489
|
+
const metaPath = join(sessionDir, "meta.json");
|
|
32490
|
+
let meta;
|
|
32491
|
+
try {
|
|
32492
|
+
meta = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
32493
|
+
} catch (err) {
|
|
32494
|
+
process.stderr.write(`[veloz init] falha ao ler meta.json: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
32495
|
+
return;
|
|
32496
|
+
}
|
|
32497
|
+
const authConfig = loadConfig();
|
|
32498
|
+
if (!authConfig.apiKey) {
|
|
32499
|
+
process.stderr.write("[veloz init] sem apiKey — telemetria não enviada\n");
|
|
32500
|
+
return;
|
|
32501
|
+
}
|
|
32502
|
+
const files = readdirSync(sessionDir).filter((name) => {
|
|
32503
|
+
const full = join(sessionDir, name);
|
|
32504
|
+
try {
|
|
32505
|
+
const s = statSync(full);
|
|
32506
|
+
return s.isFile() && s.size > 0;
|
|
32507
|
+
} catch {
|
|
32508
|
+
return false;
|
|
32509
|
+
}
|
|
32510
|
+
});
|
|
32511
|
+
if (files.length === 0) return;
|
|
32512
|
+
const client = createClient(authConfig.apiUrl, () => {
|
|
32513
|
+
const headers = {
|
|
32514
|
+
Authorization: `Bearer ${authConfig.apiKey}`,
|
|
32515
|
+
"User-Agent": "veloz-cli/telemetry"
|
|
32516
|
+
};
|
|
32517
|
+
if (authConfig.organizationId) headers["X-Organization-Id"] = authConfig.organizationId;
|
|
32518
|
+
return headers;
|
|
32519
|
+
});
|
|
32520
|
+
let started;
|
|
32521
|
+
try {
|
|
32522
|
+
started = await client.initTelemetry.start({
|
|
32523
|
+
clientSessionId: meta.sessionId,
|
|
32524
|
+
cwd: meta.cwd,
|
|
32525
|
+
framework: meta.framework,
|
|
32526
|
+
frameworkLabel: meta.frameworkLabel,
|
|
32527
|
+
packageManager: meta.packageManager,
|
|
32528
|
+
isMonorepo: meta.isMonorepo,
|
|
32529
|
+
gitRemote: meta.gitRemote ?? void 0,
|
|
32530
|
+
userName: meta.userName,
|
|
32531
|
+
orgName: meta.orgName,
|
|
32532
|
+
node: meta.node,
|
|
32533
|
+
platform: meta.platform,
|
|
32534
|
+
pid: meta.pid,
|
|
32535
|
+
startedAt: meta.startedAt,
|
|
32536
|
+
files
|
|
32537
|
+
});
|
|
32538
|
+
} catch (err) {
|
|
32539
|
+
process.stderr.write(`[veloz init] telemetria (start) falhou: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
32540
|
+
return;
|
|
32541
|
+
}
|
|
32542
|
+
const failures = (await Promise.allSettled(files.map(async (filename) => {
|
|
32543
|
+
const url = started.uploads[filename];
|
|
32544
|
+
if (!url) return;
|
|
32545
|
+
const body = readFileSync(join(sessionDir, filename));
|
|
32546
|
+
const res = await fetch(url, {
|
|
32547
|
+
method: "PUT",
|
|
32548
|
+
headers: { "Content-Type": contentTypeFor(filename) },
|
|
32549
|
+
body
|
|
32550
|
+
});
|
|
32551
|
+
if (!res.ok) throw new Error(`${filename}: HTTP ${res.status}`);
|
|
32552
|
+
}))).filter((r) => r.status === "rejected");
|
|
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`);
|
|
32554
|
+
try {
|
|
32555
|
+
await client.initTelemetry.finalize({
|
|
32556
|
+
id: started.id,
|
|
32557
|
+
outcome: meta.outcome ?? "unknown",
|
|
32558
|
+
error: meta.error ?? null,
|
|
32559
|
+
deployUrl: meta.deployUrl ?? null,
|
|
32560
|
+
retries: meta.retries,
|
|
32561
|
+
durationMs: meta.durationMs,
|
|
32562
|
+
endedAt: meta.endedAt
|
|
32563
|
+
});
|
|
32564
|
+
} catch (err) {
|
|
32565
|
+
process.stderr.write(`[veloz init] telemetria (finalize) falhou: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
32566
|
+
return;
|
|
32567
|
+
}
|
|
32568
|
+
process.stderr.write(`[veloz init] telemetria enviada (sessão ${started.id})\n`);
|
|
32569
|
+
}
|
|
32570
|
+
|
|
31056
32571
|
//#endregion
|
|
31057
32572
|
//#region src/wizard/index.ts
|
|
31058
32573
|
/**
|
|
@@ -31102,18 +32617,37 @@ function registerInit(cli$1) {
|
|
|
31102
32617
|
outputPolicy: "agent-only",
|
|
31103
32618
|
async run(c) {
|
|
31104
32619
|
const cwd = process.cwd();
|
|
32620
|
+
const logger = initSessionLogger({ cwd });
|
|
32621
|
+
logger.log("init", "veloz init invoked", {
|
|
32622
|
+
cwd,
|
|
32623
|
+
framework: c.options.framework ?? null,
|
|
32624
|
+
ci: c.options.ci,
|
|
32625
|
+
isTTY: Boolean(process.stdout.isTTY),
|
|
32626
|
+
argv: process.argv.slice(2)
|
|
32627
|
+
});
|
|
32628
|
+
process.stderr.write(`[veloz init] debug logs: ${logger.dir}\n`);
|
|
31105
32629
|
const analysis = detectLocalRepo(cwd);
|
|
32630
|
+
logger.log("detection", "repo analysis complete", {
|
|
32631
|
+
framework: analysis.framework?.name ?? null,
|
|
32632
|
+
frameworkLabel: analysis.framework?.label ?? null,
|
|
32633
|
+
packageManager: analysis.packageManager,
|
|
32634
|
+
isMonorepo: analysis.isMonorepo
|
|
32635
|
+
});
|
|
31106
32636
|
const detectedFrameworkId = analysis.framework ? DETECTOR_TO_FRAMEWORK[analysis.framework.name] ?? "nodejs" : null;
|
|
31107
32637
|
const detectedLabel = analysis.framework?.label ?? null;
|
|
31108
32638
|
const forcedFramework = c.options.framework;
|
|
31109
|
-
if (!process.stdout.isTTY || c.options.ci)
|
|
31110
|
-
|
|
31111
|
-
|
|
31112
|
-
|
|
31113
|
-
|
|
31114
|
-
|
|
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"
|
|
32646
|
+
});
|
|
32647
|
+
}
|
|
31115
32648
|
const tui = startTUI();
|
|
31116
32649
|
const { store } = tui;
|
|
32650
|
+
logger.log("tui", "TUI started");
|
|
31117
32651
|
store.setDetection({
|
|
31118
32652
|
framework: detectedFrameworkId,
|
|
31119
32653
|
frameworkLabel: detectedLabel,
|
|
@@ -31125,10 +32659,37 @@ function registerInit(cli$1) {
|
|
|
31125
32659
|
store.selectFramework(forcedFramework, FRAMEWORK_LABELS[forcedFramework] ?? forcedFramework);
|
|
31126
32660
|
store.confirmIntro();
|
|
31127
32661
|
}
|
|
32662
|
+
logger.log("flow", "awaiting intro confirmation");
|
|
31128
32663
|
await tui.waitForIntro();
|
|
32664
|
+
logger.log("flow", "intro confirmed");
|
|
31129
32665
|
const selectedFramework = store.session.selectedFramework ?? detectedFrameworkId ?? "nodejs";
|
|
31130
32666
|
const selectedLabel = store.session.selectedFrameworkLabel ?? detectedLabel ?? "Node.js";
|
|
32667
|
+
logger.updateMeta({
|
|
32668
|
+
framework: selectedFramework,
|
|
32669
|
+
frameworkLabel: selectedLabel,
|
|
32670
|
+
packageManager: store.session.packageManager ?? "npm",
|
|
32671
|
+
isMonorepo: store.session.isMonorepo,
|
|
32672
|
+
gitRemote: store.session.gitRemote
|
|
32673
|
+
});
|
|
32674
|
+
logger.log("flow", "resolving auth + org");
|
|
31131
32675
|
await resolveAuthAndOrg(store);
|
|
32676
|
+
logger.log("auth", "auth + org resolved", {
|
|
32677
|
+
userName: store.session.userName,
|
|
32678
|
+
orgId: store.session.orgId,
|
|
32679
|
+
orgName: store.session.orgName
|
|
32680
|
+
});
|
|
32681
|
+
logger.updateMeta({
|
|
32682
|
+
userName: store.session.userName,
|
|
32683
|
+
orgName: store.session.orgName,
|
|
32684
|
+
orgId: store.session.orgId
|
|
32685
|
+
});
|
|
32686
|
+
logger.log("flow", "awaiting user notes");
|
|
32687
|
+
await store.getGate("notes", (s) => s.notesConfirmed);
|
|
32688
|
+
logger.log("flow", "notes confirmed", {
|
|
32689
|
+
hasNotes: Boolean(store.session.userNotes),
|
|
32690
|
+
bytes: store.session.userNotes?.length ?? 0
|
|
32691
|
+
});
|
|
32692
|
+
logger.log("agent", "invoking runAgent");
|
|
31132
32693
|
await runAgent({
|
|
31133
32694
|
store,
|
|
31134
32695
|
cwd,
|
|
@@ -31136,12 +32697,29 @@ function registerInit(cli$1) {
|
|
|
31136
32697
|
frameworkLabel: selectedLabel,
|
|
31137
32698
|
userName: store.session.userName,
|
|
31138
32699
|
orgName: store.session.orgName,
|
|
31139
|
-
packageManager: store.session.packageManager ?? "npm"
|
|
32700
|
+
packageManager: store.session.packageManager ?? "npm",
|
|
32701
|
+
userNotes: store.session.userNotes
|
|
31140
32702
|
});
|
|
32703
|
+
logger.log("agent", "runAgent returned", {
|
|
32704
|
+
phase: store.session.agentPhase,
|
|
32705
|
+
error: store.session.agentError,
|
|
32706
|
+
deployUrl: store.session.deployUrl,
|
|
32707
|
+
retryCount: store.session.retryCount
|
|
32708
|
+
});
|
|
32709
|
+
logger.log("flow", "awaiting outro dismissal");
|
|
31141
32710
|
await store.getGate("outro", (s) => s.outroDismissed);
|
|
32711
|
+
logger.log("flow", "outro dismissed");
|
|
31142
32712
|
tui.unmount();
|
|
32713
|
+
logger.log("tui", "TUI unmounted");
|
|
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
|
|
32719
|
+
});
|
|
32720
|
+
await uploadInitTelemetry(logger.dir);
|
|
31143
32721
|
return {
|
|
31144
|
-
success:
|
|
32722
|
+
success: success$1,
|
|
31145
32723
|
url: store.session.deployUrl ?? void 0
|
|
31146
32724
|
};
|
|
31147
32725
|
}
|
|
@@ -31150,6 +32728,8 @@ function registerInit(cli$1) {
|
|
|
31150
32728
|
async function runHeadless(opts) {
|
|
31151
32729
|
const { WizardStore: WizardStore$1 } = await import("./store-CFF2J0lW.mjs");
|
|
31152
32730
|
const { runAgent: run } = await import("./agent-interface-DL5S8SEn.mjs");
|
|
32731
|
+
const logger = getSessionLogger();
|
|
32732
|
+
logger.log("headless", "resolving auth (non-interactive)");
|
|
31153
32733
|
const config = await requireAuth({ nonInteractive: true });
|
|
31154
32734
|
const store = new WizardStore$1();
|
|
31155
32735
|
store.setAuth({
|
|
@@ -31163,6 +32743,14 @@ async function runHeadless(opts) {
|
|
|
31163
32743
|
const lastLog = s.agentLogLines[s.agentLogLines.length - 1];
|
|
31164
32744
|
if (lastLog) process.stderr.write(`[veloz init] ${lastLog}\n`);
|
|
31165
32745
|
});
|
|
32746
|
+
logger.updateMeta({
|
|
32747
|
+
framework: opts.frameworkId,
|
|
32748
|
+
frameworkLabel: opts.frameworkLabel,
|
|
32749
|
+
packageManager: opts.packageManager,
|
|
32750
|
+
userName: "CI",
|
|
32751
|
+
orgId: config.organizationId ?? ""
|
|
32752
|
+
});
|
|
32753
|
+
logger.log("headless", "invoking runAgent");
|
|
31166
32754
|
await run({
|
|
31167
32755
|
store,
|
|
31168
32756
|
cwd: opts.cwd,
|
|
@@ -31172,29 +32760,18 @@ async function runHeadless(opts) {
|
|
|
31172
32760
|
orgName: "",
|
|
31173
32761
|
packageManager: opts.packageManager
|
|
31174
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);
|
|
31175
32770
|
return {
|
|
31176
|
-
success:
|
|
32771
|
+
success: success$1,
|
|
31177
32772
|
url: store.session.deployUrl ?? void 0
|
|
31178
32773
|
};
|
|
31179
32774
|
}
|
|
31180
|
-
function isMultipleOrgsError(error) {
|
|
31181
|
-
if (!(error instanceof Error)) return false;
|
|
31182
|
-
const e = error;
|
|
31183
|
-
return e.data?.code === "MULTIPLE_ORGS" && Array.isArray(e.data?.organizations);
|
|
31184
|
-
}
|
|
31185
|
-
function isNoOrgError(error) {
|
|
31186
|
-
if (!(error instanceof Error)) return false;
|
|
31187
|
-
return error.data?.code === "NO_ORGANIZATION";
|
|
31188
|
-
}
|
|
31189
|
-
/**
|
|
31190
|
-
* Build a Better Auth client with org plugin (extends the device auth client).
|
|
31191
|
-
*/
|
|
31192
|
-
function buildOrgAuthClient(apiUrl) {
|
|
31193
|
-
return createAuthClient({
|
|
31194
|
-
baseURL: apiUrl,
|
|
31195
|
-
plugins: [organizationClient()]
|
|
31196
|
-
});
|
|
31197
|
-
}
|
|
31198
32775
|
/**
|
|
31199
32776
|
* Perform device auth with TUI feedback — reuses shared helpers from login.ts.
|
|
31200
32777
|
* No console.log or ora spinners; all progress goes through the store.
|
|
@@ -31209,100 +32786,141 @@ async function performDeviceAuth(store, apiUrl) {
|
|
|
31209
32786
|
store.setAuthPhase("polling");
|
|
31210
32787
|
return pollForToken(authClient, data.device_code, data.interval || 5);
|
|
31211
32788
|
}
|
|
32789
|
+
/** Fetch the list of orgs the user belongs to via Better Auth. */
|
|
32790
|
+
async function listOrganizations(apiUrl, apiKey) {
|
|
32791
|
+
const res = await fetch(`${apiUrl}/api/auth/organization/list`, { headers: {
|
|
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) => ({
|
|
32797
|
+
id: o.id,
|
|
32798
|
+
name: o.name,
|
|
32799
|
+
slug: o.slug
|
|
32800
|
+
}));
|
|
32801
|
+
}
|
|
31212
32802
|
/**
|
|
31213
|
-
*
|
|
32803
|
+
* Create a new organization via Better Auth. The slug is derived from the name
|
|
32804
|
+
* and suffixed with a random token when a collision occurs.
|
|
31214
32805
|
*/
|
|
31215
|
-
function
|
|
31216
|
-
|
|
31217
|
-
|
|
31218
|
-
|
|
31219
|
-
|
|
31220
|
-
|
|
31221
|
-
|
|
31222
|
-
|
|
31223
|
-
|
|
32806
|
+
async function createOrganization(apiUrl, apiKey, name) {
|
|
32807
|
+
const baseSlug = name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "workspace";
|
|
32808
|
+
let slug = baseSlug;
|
|
32809
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
32810
|
+
const res = await fetch(`${apiUrl}/api/auth/organization/create`, {
|
|
32811
|
+
method: "POST",
|
|
32812
|
+
headers: {
|
|
32813
|
+
Authorization: `Bearer ${apiKey}`,
|
|
32814
|
+
"Content-Type": "application/json",
|
|
32815
|
+
"User-Agent": "veloz-cli"
|
|
32816
|
+
},
|
|
32817
|
+
body: JSON.stringify({
|
|
32818
|
+
name,
|
|
32819
|
+
slug
|
|
32820
|
+
})
|
|
32821
|
+
});
|
|
32822
|
+
if (res.ok) {
|
|
32823
|
+
const data = await res.json();
|
|
32824
|
+
return {
|
|
32825
|
+
id: data.id,
|
|
32826
|
+
name: data.name,
|
|
32827
|
+
slug: data.slug
|
|
32828
|
+
};
|
|
32829
|
+
}
|
|
32830
|
+
const errorText = await res.text().catch(() => "");
|
|
32831
|
+
if (res.status === 400 && errorText.toLowerCase().includes("already")) {
|
|
32832
|
+
slug = `${baseSlug}-${Math.random().toString(36).slice(2, 6)}`;
|
|
32833
|
+
continue;
|
|
32834
|
+
}
|
|
32835
|
+
throw new Error(errorText || `Falha ao criar workspace (${res.status}).`);
|
|
32836
|
+
}
|
|
32837
|
+
throw new Error("Não foi possível gerar um slug único para o workspace.");
|
|
31224
32838
|
}
|
|
31225
32839
|
/**
|
|
31226
32840
|
* Full TUI-aware auth + org resolution.
|
|
31227
32841
|
*
|
|
31228
32842
|
* 1. If already authenticated, skip login
|
|
31229
32843
|
* 2. If not, perform device auth with TUI feedback
|
|
31230
|
-
* 3.
|
|
31231
|
-
* 4.
|
|
32844
|
+
* 3. Always show the org picker (or the create screen if the user has no orgs)
|
|
32845
|
+
* 4. Persist the selected/created org id
|
|
31232
32846
|
* 5. Update store.setAuth() when done
|
|
31233
32847
|
*/
|
|
31234
32848
|
async function resolveAuthAndOrg(store) {
|
|
32849
|
+
const logger = getSessionLogger();
|
|
31235
32850
|
let config = loadConfig();
|
|
31236
32851
|
if (!isAuthenticated()) {
|
|
31237
32852
|
const envApiKey = process.env.VELOZ_API_KEY;
|
|
31238
32853
|
if (envApiKey) {
|
|
32854
|
+
logger.log("auth", "using VELOZ_API_KEY from env");
|
|
31239
32855
|
saveConfig({ apiKey: envApiKey });
|
|
31240
32856
|
config = loadConfig();
|
|
31241
32857
|
} else {
|
|
32858
|
+
logger.log("auth", "starting device auth flow", { apiUrl: config.apiUrl });
|
|
31242
32859
|
store.setAuthPhase("browser");
|
|
31243
32860
|
const token = await performDeviceAuth(store, config.apiUrl);
|
|
31244
|
-
if (!token)
|
|
32861
|
+
if (!token) {
|
|
32862
|
+
logger.log("auth", "device auth failed or expired");
|
|
32863
|
+
throw new Error("Autenticação cancelada ou expirada.");
|
|
32864
|
+
}
|
|
32865
|
+
logger.log("auth", "device auth succeeded");
|
|
31245
32866
|
saveConfig({ apiKey: token });
|
|
31246
32867
|
config = loadConfig();
|
|
31247
32868
|
}
|
|
31248
|
-
}
|
|
31249
|
-
|
|
31250
|
-
const
|
|
31251
|
-
|
|
31252
|
-
|
|
31253
|
-
|
|
31254
|
-
|
|
31255
|
-
|
|
31256
|
-
|
|
31257
|
-
|
|
31258
|
-
|
|
31259
|
-
|
|
31260
|
-
|
|
31261
|
-
|
|
31262
|
-
|
|
31263
|
-
|
|
31264
|
-
|
|
31265
|
-
store.setAvailableOrgs(orgs);
|
|
31266
|
-
await store.getGate("org-select", (s) => s.selectedOrgId !== null);
|
|
31267
|
-
const selectedOrgId = store.session.selectedOrgId;
|
|
31268
|
-
const selectedOrg = orgs.find((o) => o.id === selectedOrgId);
|
|
31269
|
-
saveConfig({ organizationId: selectedOrgId });
|
|
31270
|
-
config = loadConfig();
|
|
31271
|
-
orgName = selectedOrg?.name ?? selectedOrgId;
|
|
31272
|
-
}
|
|
31273
|
-
} else if (isNoOrgError(error)) {
|
|
31274
|
-
store.setAuthPhase("no-org");
|
|
31275
|
-
const authClient = buildOrgAuthClient(config.apiUrl);
|
|
31276
|
-
const userName = process.env.USER ?? process.env.USERNAME ?? "meu-workspace";
|
|
31277
|
-
const slug = userName.toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 30);
|
|
31278
|
-
const createResult = await authClient.organization.create({
|
|
31279
|
-
name: `${userName}'s workspace`,
|
|
31280
|
-
slug
|
|
31281
|
-
});
|
|
31282
|
-
if (createResult.error || !createResult.data) {
|
|
31283
|
-
await openBrowser(`${config.apiUrl.includes("localhost") ? "http://localhost:3001" : "https://app.onveloz.com"}/onboarding`);
|
|
31284
|
-
throw new Error("Não foi possível criar o workspace automaticamente. Crie um em app.onveloz.com e tente novamente.", { cause: createResult.error });
|
|
31285
|
-
}
|
|
31286
|
-
const newOrg = createResult.data;
|
|
31287
|
-
saveConfig({ organizationId: newOrg.id });
|
|
31288
|
-
config = loadConfig();
|
|
31289
|
-
orgName = newOrg.name;
|
|
31290
|
-
} else throw error;
|
|
32869
|
+
} else logger.log("auth", "already authenticated from config");
|
|
32870
|
+
logger.log("auth", "fetching organizations");
|
|
32871
|
+
const orgs = await listOrganizations(config.apiUrl, config.apiKey);
|
|
32872
|
+
logger.log("auth", "organizations loaded", { count: orgs.length });
|
|
32873
|
+
let chosen;
|
|
32874
|
+
if (orgs.length === 0) {
|
|
32875
|
+
store.setAuthPhase("org-create");
|
|
32876
|
+
chosen = await runOrgCreationFlow(store, config.apiUrl, config.apiKey);
|
|
32877
|
+
} else {
|
|
32878
|
+
store.setAvailableOrgs(orgs);
|
|
32879
|
+
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, config.apiKey);
|
|
32881
|
+
else {
|
|
32882
|
+
const selectedOrgId = store.session.selectedOrgId;
|
|
32883
|
+
const match$3 = orgs.find((o) => o.id === selectedOrgId);
|
|
32884
|
+
if (!match$3) throw new Error("Workspace selecionado não encontrado.");
|
|
32885
|
+
chosen = match$3;
|
|
31291
32886
|
}
|
|
31292
32887
|
}
|
|
32888
|
+
saveConfig({ organizationId: chosen.id });
|
|
32889
|
+
config = loadConfig();
|
|
31293
32890
|
store.setAuth({
|
|
31294
32891
|
userName: config.apiKey ? "Usuário" : "",
|
|
31295
|
-
orgId:
|
|
31296
|
-
orgName,
|
|
32892
|
+
orgId: chosen.id,
|
|
32893
|
+
orgName: chosen.name,
|
|
31297
32894
|
apiKey: config.apiKey
|
|
31298
32895
|
});
|
|
31299
32896
|
}
|
|
32897
|
+
/**
|
|
32898
|
+
* Drive the org-create screen: wait for the user to submit a name, try to
|
|
32899
|
+
* create it, and retry on failure without leaving the wizard.
|
|
32900
|
+
*/
|
|
32901
|
+
async function runOrgCreationFlow(store, apiUrl, apiKey) {
|
|
32902
|
+
store.requestOrgCreation();
|
|
32903
|
+
for (;;) {
|
|
32904
|
+
await store.getGate(`org-create-${Date.now()}-${Math.random()}`, (s) => s.newOrgName !== null && s.authPhase === "org-creating");
|
|
32905
|
+
const name = store.session.newOrgName?.trim() ?? "";
|
|
32906
|
+
if (!name) {
|
|
32907
|
+
store.failOrgCreation("Digite um nome para o workspace.");
|
|
32908
|
+
continue;
|
|
32909
|
+
}
|
|
32910
|
+
try {
|
|
32911
|
+
return await createOrganization(apiUrl, apiKey, name);
|
|
32912
|
+
} catch (error) {
|
|
32913
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32914
|
+
store.failOrgCreation(message);
|
|
32915
|
+
}
|
|
32916
|
+
}
|
|
32917
|
+
}
|
|
31300
32918
|
|
|
31301
32919
|
//#endregion
|
|
31302
32920
|
//#region src/index.ts
|
|
31303
32921
|
if (process.argv.includes("--mcp")) process.env.VELOZ_MCP = "true";
|
|
31304
32922
|
const cli = Cli.create("veloz", {
|
|
31305
|
-
version: "0.0.0-beta.
|
|
32923
|
+
version: "0.0.0-beta.31",
|
|
31306
32924
|
description: "CLI da plataforma Veloz — deploy rápido para o Brasil",
|
|
31307
32925
|
env: z.object({ VELOZ_ENV: z.string().optional().describe("Ambiente alvo (ex: preview, staging)") }),
|
|
31308
32926
|
mcp: { command: "npx -y onveloz --mcp" }
|