@nomad-e/bluma-cli 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,7 @@ Always read `factorai.sh.json` in the project root before editing a deployed app
37
37
  2. edit_tool / file_write under ./my-app/ using shadcn components + semantic Tailwind (bg-background, etc.)
38
38
  3. Avoid npm run build before deploy (.next inflates ZIP); optional npx tsc --noEmit
39
39
  4. factorai.sh.deploy_app({ projectDir: "./my-app", name: "my-app" })
40
- 5. Poll get_app_status until ready (see Polling)
40
+ 5. factorai.sh.get_app_status({ appId, wait: true }) until ready or failed (see Polling)
41
41
  6. message(result) with factor-sh-url-app from manifest after ready
42
42
  ```
43
43
 
@@ -53,7 +53,7 @@ Always read `factorai.sh.json` in the project root before editing a deployed app
53
53
  ],
54
54
  deploy: true,
55
55
  })
56
- 4. Poll until building ready
56
+ 4. get_app_status({ appId, wait: true }) until ready or failed
57
57
  5. message(result) with factor-sh-url-app from manifest after ready
58
58
  ```
59
59
 
@@ -67,9 +67,9 @@ Always read `factorai.sh.json` in the project root before editing a deployed app
67
67
 
68
68
  ## Polling checklist
69
69
 
70
- 1. Call `factorai.sh.get_app_status({ appId })` every 5–10s
71
- 2. Require seeing `building` after apply/deploy before trusting `ready`
72
- 3. Verify the live route from `appContext.appUrl` (absolute URL); reject `Service Unavailable` JSON as "not live yet"
70
+ 1. Prefer `factorai.sh.get_app_status({ appId, wait: true })` blocks until `ready` or `failed`
71
+ 2. On `failed`: read `data.typescriptErrors`, `data.buildLogTail`, `data.summary` — fix code, retry apply/deploy
72
+ 3. Do not report live URL until `data.success === true` and `data.status === "ready"`
73
73
  4. Typical rebuild: 30–90 seconds
74
74
 
75
75
  ## Tool reference
package/dist/main.js CHANGED
@@ -20269,7 +20269,7 @@ async function uploadToSeverino(zipPath, severinoUrl, name, apiKey, appId) {
20269
20269
  name: data.name,
20270
20270
  status: data.status || "building",
20271
20271
  url: appContext.appUrl || severinoUrl.replace(/\/$/, "") + `/app/${resolvedAppId}`,
20272
- message: data.message || "Deploy iniciado",
20272
+ message: data.message || "Deploy iniciado. Chama factorai.sh.get_app_status({ appId, wait: true }) at\xE9 status ready ou failed; se failed, l\xEA typescriptErrors/buildLogTail e corrige o c\xF3digo.",
20273
20273
  isRedeploy: !!appId,
20274
20274
  appContext
20275
20275
  };
@@ -20413,6 +20413,102 @@ async function deployApp(args) {
20413
20413
  }
20414
20414
  }
20415
20415
 
20416
+ // src/app/agent/runtime/factorai_deploy_feedback.ts
20417
+ var FAILED_STATUSES = /* @__PURE__ */ new Set(["failed", "error", "build_failed"]);
20418
+ var READY_STATUSES = /* @__PURE__ */ new Set(["ready", "running", "live"]);
20419
+ var BUILDING_STATUSES = /* @__PURE__ */ new Set(["building", "deploying", "installing", "pending"]);
20420
+ function extractTypeScriptErrors(logText) {
20421
+ if (!logText.trim()) return [];
20422
+ const lines = logText.split("\n");
20423
+ const blocks = [];
20424
+ for (let i = 0; i < lines.length; i++) {
20425
+ const line = lines[i];
20426
+ if (line.includes("Type error:") || line.includes("Failed to compile") || /^\s*>\s*\d+\s*\|/.test(line)) {
20427
+ const start = Math.max(0, i - 3);
20428
+ const end = Math.min(lines.length, i + 8);
20429
+ blocks.push(lines.slice(start, end).join("\n").trim());
20430
+ }
20431
+ }
20432
+ return [...new Set(blocks)].slice(0, 6);
20433
+ }
20434
+ function normalizeFactorAiAppStatus(raw, appId) {
20435
+ const envelope = raw && typeof raw === "object" ? raw : {};
20436
+ const data = envelope.data && typeof envelope.data === "object" && !Array.isArray(envelope.data) ? envelope.data : envelope;
20437
+ const status = String(data.status ?? "unknown").toLowerCase();
20438
+ const logs = typeof data.logs === "string" ? data.logs : "";
20439
+ const logTail = logs.length > 12e3 ? logs.slice(-12e3) : logs;
20440
+ const lastError = typeof data.lastError === "string" && data.lastError || typeof data.error === "string" && data.error || void 0;
20441
+ const slug = typeof data.slug === "string" ? data.slug : void 0;
20442
+ const url = typeof data.url === "string" ? data.url : void 0;
20443
+ const tsErrors = extractTypeScriptErrors(logTail);
20444
+ if (FAILED_STATUSES.has(status) || lastError) {
20445
+ const error = lastError || tsErrors[0] || (logTail.includes("next build falhou") ? logTail.split("\n").slice(-5).join("\n") : "") || "Deploy/build failed on FactorAI.sh";
20446
+ return {
20447
+ success: false,
20448
+ terminal: true,
20449
+ status: "failed",
20450
+ appId,
20451
+ slug,
20452
+ url,
20453
+ error,
20454
+ buildLogTail: logTail || void 0,
20455
+ typescriptErrors: tsErrors.length > 0 ? tsErrors : void 0,
20456
+ agentGuidance: "O build falhou no servidor. Corrige os erros TypeScript/compila\xE7\xE3o no projeto local (ex.: components/ui/card.tsx), confirma com npx tsc --noEmit se poss\xEDvel, depois factorai.sh.apply_app_changes ou deploy_app. N\xE3o digas ao utilizador que a app est\xE1 online."
20457
+ };
20458
+ }
20459
+ if (READY_STATUSES.has(status)) {
20460
+ return {
20461
+ success: true,
20462
+ terminal: true,
20463
+ status: "ready",
20464
+ appId,
20465
+ slug,
20466
+ url,
20467
+ buildLogTail: logTail || void 0
20468
+ };
20469
+ }
20470
+ if (BUILDING_STATUSES.has(status)) {
20471
+ return {
20472
+ success: true,
20473
+ terminal: false,
20474
+ status,
20475
+ appId,
20476
+ slug,
20477
+ url,
20478
+ buildLogTail: logTail || void 0
20479
+ };
20480
+ }
20481
+ return {
20482
+ success: true,
20483
+ terminal: false,
20484
+ status,
20485
+ appId,
20486
+ slug,
20487
+ url,
20488
+ buildLogTail: logTail || void 0
20489
+ };
20490
+ }
20491
+ function formatFactorAiDeployStatusForAgent(result) {
20492
+ const lines = [`status=${result.status}`, `appId=${result.appId}`];
20493
+ if (result.slug) lines.push(`slug=${result.slug}`);
20494
+ if (result.url) lines.push(`url=${result.url}`);
20495
+ if (result.error) lines.push(`error=${result.error}`);
20496
+ if (result.typescriptErrors?.length) {
20497
+ lines.push("typescriptErrors:");
20498
+ for (const block of result.typescriptErrors) {
20499
+ lines.push(block);
20500
+ }
20501
+ } else if (result.buildLogTail) {
20502
+ lines.push("buildLogTail:");
20503
+ lines.push(result.buildLogTail);
20504
+ }
20505
+ if (result.agentGuidance) lines.push(`guidance=${result.agentGuidance}`);
20506
+ return lines.join("\n");
20507
+ }
20508
+ function sleep(ms) {
20509
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
20510
+ }
20511
+
20416
20512
  // src/app/agent/runtime/native_tool_catalog.ts
20417
20513
  init_sandbox_policy();
20418
20514
  function getFactorAiBaseUrl() {
@@ -20494,9 +20590,81 @@ async function resolveFactorAiAppId(explicitAppId) {
20494
20590
  "appId em falta: passa o UUID de factorai.sh.json (appContext.appId) ou faz deploy_app primeiro nesta sess\xE3o."
20495
20591
  );
20496
20592
  }
20593
+ function isFactorAiNotFoundError(err) {
20594
+ const msg = err instanceof Error ? err.message : String(err);
20595
+ return /not found/i.test(msg) || /404/.test(msg);
20596
+ }
20597
+ async function fetchAndNormalizeAppStatus(appId) {
20598
+ const payload = await requestFactorAi(`/api/v1/apps/${encodeURIComponent(appId)}`);
20599
+ return normalizeFactorAiAppStatus(payload, appId);
20600
+ }
20497
20601
  async function factorAiGetAppStatus(args) {
20498
20602
  const appId = await resolveFactorAiAppId(args?.appId);
20499
- return requestFactorAi(`/api/v1/apps/${encodeURIComponent(appId)}`);
20603
+ const wait = args?.wait === true;
20604
+ const timeoutMs = Math.max(5, args?.timeoutSeconds ?? 480) * 1e3;
20605
+ const pollMs = Math.max(2, args?.pollIntervalSeconds ?? 5) * 1e3;
20606
+ const deadline = Date.now() + timeoutMs;
20607
+ const finalize = (result) => ({
20608
+ success: true,
20609
+ data: {
20610
+ ...result,
20611
+ summary: formatFactorAiDeployStatusForAgent(result)
20612
+ }
20613
+ });
20614
+ if (!wait) {
20615
+ try {
20616
+ return finalize(await fetchAndNormalizeAppStatus(appId));
20617
+ } catch (err) {
20618
+ if (isFactorAiNotFoundError(err)) {
20619
+ return finalize({
20620
+ success: false,
20621
+ terminal: true,
20622
+ status: "failed",
20623
+ appId,
20624
+ error: String(err instanceof Error ? err.message : err),
20625
+ agentGuidance: "App n\xE3o encontrada no FactorAI.sh \u2014 o deploy provavelmente falhou e o registo foi limpo, ou o appId est\xE1 errado. L\xEA factorai.sh.json, confirma FACTORAI_BASE_URL=SEVERINO_URL, e faz deploy de novo ap\xF3s corrigir o c\xF3digo."
20626
+ });
20627
+ }
20628
+ throw err;
20629
+ }
20630
+ }
20631
+ let sawBuilding = false;
20632
+ while (Date.now() < deadline) {
20633
+ try {
20634
+ const result = await fetchAndNormalizeAppStatus(appId);
20635
+ if (!result.terminal && result.status !== "failed") {
20636
+ sawBuilding = true;
20637
+ }
20638
+ if (result.terminal) {
20639
+ if (result.status === "ready" && !sawBuilding) {
20640
+ await sleep(pollMs);
20641
+ continue;
20642
+ }
20643
+ return finalize(result);
20644
+ }
20645
+ } catch (err) {
20646
+ if (isFactorAiNotFoundError(err)) {
20647
+ return finalize({
20648
+ success: false,
20649
+ terminal: true,
20650
+ status: "failed",
20651
+ appId,
20652
+ error: String(err instanceof Error ? err.message : err),
20653
+ agentGuidance: "Deploy falhou: a app desapareceu do registry (build error no servidor). Corrige o c\xF3digo e volta a deploy_app."
20654
+ });
20655
+ }
20656
+ throw err;
20657
+ }
20658
+ await sleep(pollMs);
20659
+ }
20660
+ return finalize({
20661
+ success: true,
20662
+ terminal: false,
20663
+ status: "building",
20664
+ appId,
20665
+ error: `Timeout ap\xF3s ${Math.round(timeoutMs / 1e3)}s \xE0 espera de ready/failed`,
20666
+ agentGuidance: "Continua a pollar get_app_status ou verifica logs no FactorAI.sh."
20667
+ });
20500
20668
  }
20501
20669
  async function factorAiApplyAppChanges(args) {
20502
20670
  const appId = await resolveFactorAiAppId(args?.appId);
@@ -20512,7 +20680,13 @@ async function factorAiApplyAppChanges(args) {
20512
20680
  })
20513
20681
  });
20514
20682
  await persistFactorAiWorkspaceManifestFromResponse(response);
20515
- return response;
20683
+ const normalized = normalizeFactorAiAppStatus(response, appId);
20684
+ const summary = formatFactorAiDeployStatusForAgent(normalized);
20685
+ const message2 = args?.deploy !== false && !normalized.terminal ? "Changes accepted; build em curso. Chama factorai.sh.get_app_status({ wait: true }) at\xE9 ready ou failed." : normalized.success ? "Changes applied" : normalized.error;
20686
+ return {
20687
+ success: true,
20688
+ data: { ...normalized, summary, message: message2 }
20689
+ };
20516
20690
  }
20517
20691
  async function factorAiRedeployApp(args) {
20518
20692
  const appId = await resolveFactorAiAppId(args?.appId);
@@ -20520,7 +20694,13 @@ async function factorAiRedeployApp(args) {
20520
20694
  method: "POST"
20521
20695
  });
20522
20696
  await persistFactorAiWorkspaceManifestFromResponse(response);
20523
- return response;
20697
+ const normalized = normalizeFactorAiAppStatus(response, appId);
20698
+ const summary = formatFactorAiDeployStatusForAgent(normalized);
20699
+ const message2 = normalized.terminal ? normalized.success ? "Redeploy complete" : normalized.error : "Redeploy started; poll get_app_status({ wait: true }).";
20700
+ return {
20701
+ success: true,
20702
+ data: { ...normalized, summary, message: message2 }
20703
+ };
20524
20704
  }
20525
20705
  function extractFactorAiResponseData(response) {
20526
20706
  if (response && typeof response === "object" && response.data && typeof response.data === "object") {
@@ -20607,13 +20787,25 @@ function getFactorAiSandboxToolDefinitions() {
20607
20787
  type: "function",
20608
20788
  function: {
20609
20789
  name: "factorai.sh.get_app_status",
20610
- description: "Poll deploy/rebuild status (building|ready|failed). Call after deploy_app or apply_app_changes until ready before telling the user the app is live.",
20790
+ description: "Poll deploy/rebuild status. On failed returns buildLogTail + typescriptErrors so you can fix code. Use wait:true after deploy_app/apply_app_changes until ready or failed before message(result).",
20611
20791
  parameters: {
20612
20792
  type: "object",
20613
20793
  properties: {
20614
20794
  appId: {
20615
20795
  type: "string",
20616
20796
  description: "UUID from factorai.sh.json (appContext.appId). Optional if manifest exists in cwd. Do not use URL slug."
20797
+ },
20798
+ wait: {
20799
+ type: "boolean",
20800
+ description: "If true, poll until ready/failed/timeout (recommended after deploy)."
20801
+ },
20802
+ timeoutSeconds: {
20803
+ type: "number",
20804
+ description: "Max wait time when wait=true (default 480)."
20805
+ },
20806
+ pollIntervalSeconds: {
20807
+ type: "number",
20808
+ description: "Seconds between polls when wait=true (default 5)."
20617
20809
  }
20618
20810
  },
20619
20811
  required: [],
@@ -23791,11 +23983,11 @@ Use \`factorai.sh.redeploy_app({ appId })\` only when sources on the server are
23791
23983
 
23792
23984
  ## Polling after deploy / apply_app_changes
23793
23985
 
23794
- 1. \`factorai.sh.get_app_status({ appId })\` or GET \`/api/v1/apps/{appId}\`.
23986
+ 1. **Always** \`factorai.sh.get_app_status({ appId, wait: true })\` after \`deploy_app\` or \`apply_app_changes\` (polls at\xE9 \`ready\` ou \`failed\`).
23795
23987
  2. Expect \`status: "building"\` during \`npm install\` / \`next build\` (~30\u201390s).
23796
- 3. **Do not** report "live" on the first \`ready\` if you have not seen \`building\` since this operation \u2014 stale \`ready\` can appear briefly.
23797
- 4. Wait until \`status\` is \`ready\` (or \`running\` / \`live\`) **after** a \`building\` phase, then verify the public route returns HTML (not \`Service Unavailable\`).
23798
- 5. On \`failed\` / \`error\`: read server/build logs if available; fix TS/scaffold issues; retry \`apply_app_changes\` or \`redeploy_app\`.
23988
+ 3. **Do not** report "live" until \`success: true\` and \`status: "ready"\` no resultado do \`get_app_status\`.
23989
+ 4. On \`success: false\` / \`status: "failed"\`: read \`typescriptErrors\`, \`buildLogTail\`, and \`summary\` from the tool result \u2014 fix the code like a human reading the Next build log, then \`apply_app_changes\` or \`deploy_app\` again.
23990
+ 5. Never ignore deploy failures; never tell the user the app is online when \`get_app_status\` returned \`failed\`.
23799
23991
 
23800
23992
  ---
23801
23993
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomad-e/bluma-cli",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "BluMa independent agent for automation and advanced software engineering.",
5
5
  "author": "Alex Fonseca",
6
6
  "license": "Apache-2.0",