demobites 1.37.0 → 1.38.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.
- package/package.json +1 -1
- package/skill/scripts/stage-wait.mjs +11 -0
- package/skill/scripts/stills.mjs +39 -0
package/package.json
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// this take; a no-op everywhere else (the terminal flow, the cloud sandbox
|
|
23
23
|
// copy that may not ship listen-event.mjs).
|
|
24
24
|
const postEvent = await import("./listen-event.mjs").then((m) => m.postEvent).catch(() => async () => null);
|
|
25
|
+
const listening = await import("./listen-event.mjs").then((m) => m.listening).catch(() => false);
|
|
25
26
|
/** Where the person watches their takes: the Demos grid. `dashboardUrl` from
|
|
26
27
|
* the server wins (relative or absolute), else <base>/demos. */
|
|
27
28
|
export function gridUrl(base, dashboardUrl) {
|
|
@@ -151,6 +152,16 @@ export async function waitForDecision({ base, apiKey, stagingId, pageUrl, delive
|
|
|
151
152
|
if (last.zooms === 0) console.error("WARNING: no camera shots landed.");
|
|
152
153
|
}
|
|
153
154
|
if (finalStudioUrl) console.log(`Studio: ${finalStudioUrl}`);
|
|
155
|
+
// THE STILLS, DRAWN HERE BEFORE "READY" (2026-10-18): the demo's intro and backdrop, by the studio's own code in
|
|
156
|
+
// this recorder's Chrome, remembered by the app so the export draws nothing. Bounded; a miss costs nothing.
|
|
157
|
+
if (listening) {
|
|
158
|
+
try {
|
|
159
|
+
const { spawnSync } = await import("node:child_process");
|
|
160
|
+
const here = (await import("node:path")).dirname((await import("node:url")).fileURLToPath(import.meta.url));
|
|
161
|
+
const r = spawnSync(process.execPath, [(await import("node:path")).join(here, "stills.mjs"), String(approvedBiteId)], { cwd: process.cwd(), encoding: "utf8", env: process.env, timeout: 60000 });
|
|
162
|
+
for (const l of (r.stdout || "").trim().split("\n").filter(Boolean)) console.log(` ${l}`);
|
|
163
|
+
} catch (e) { console.log(` stills: skipped (${String(e?.message ?? e).split("\n")[0]})`); }
|
|
164
|
+
}
|
|
154
165
|
await postEvent("ready", { biteId: approvedBiteId, studioUrl: finalStudioUrl ?? null });
|
|
155
166
|
return { exitCode: 0, status: "completed", biteId: approvedBiteId, studioUrl: finalStudioUrl };
|
|
156
167
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// THE STILLS ARE DRAWN HERE (founder 2026-10-18: "at that point you have a browser… load fabric… create the two
|
|
3
|
+
// images"). After the demo is built and before this recorder says "ready", its own Chrome opens the app's internal
|
|
4
|
+
// still page — the studio's own render code, the design's fonts from the web — for the intro and the backdrop,
|
|
5
|
+
// and hands the PNGs to the app, which remembers them for the export. Seconds here; a 30-second sandbox saved there.
|
|
6
|
+
// Never fatal: on any miss the export draws them itself as before.
|
|
7
|
+
// usage: node stills.mjs <biteId> (base and key from <cwd>/.recorder/config.json)
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
const biteId = Number(process.argv[2] || 0);
|
|
11
|
+
if (!biteId) { console.error("usage: stills.mjs <biteId>"); process.exit(2); }
|
|
12
|
+
const cfg = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), ".recorder", "config.json"), "utf8"));
|
|
13
|
+
const base = (process.env.RECORDER_BASE || cfg.base || "https://app.demobites.com").replace(/\/+$/, "");
|
|
14
|
+
const headers = { Authorization: `Bearer ${cfg.api_key}`, "Content-Type": "application/json" };
|
|
15
|
+
const t0 = Date.now();
|
|
16
|
+
const plan = await fetch(`${base}/api/recorder/stills?bite_id=${biteId}`, { headers, signal: AbortSignal.timeout(15000) }).then((r) => (r.ok ? r.json() : null)).catch(() => null);
|
|
17
|
+
if (!plan?.stills?.length) { console.log("stills: nothing to draw here"); process.exit(0); }
|
|
18
|
+
const { chromium } = await import("playwright");
|
|
19
|
+
const browser = await chromium.launch({ channel: "chrome", headless: true }).catch(async () => chromium.launch({ headless: true }));
|
|
20
|
+
const out = {};
|
|
21
|
+
try {
|
|
22
|
+
const ctx = await browser.newContext({ viewport: { width: 1920, height: 1080 } });
|
|
23
|
+
for (const still of plan.stills) {
|
|
24
|
+
const page = await ctx.newPage();
|
|
25
|
+
try {
|
|
26
|
+
const res = await page.goto(still.url, { waitUntil: "domcontentloaded", timeout: 20000 });
|
|
27
|
+
if (!res || res.status() !== 200) throw new Error(`the still page answered ${res ? res.status() : "nothing"}`);
|
|
28
|
+
await page.waitForFunction("window.__FABRIC_STILL__ !== undefined", undefined, { timeout: 25000 });
|
|
29
|
+
const r = await page.evaluate("window.__FABRIC_STILL__");
|
|
30
|
+
if (!r?.ok || typeof r.dataUrl !== "string" || !r.dataUrl.startsWith("data:image/png;base64,")) throw new Error(r?.error || "no PNG from the still page");
|
|
31
|
+
out[still.kind] = r.dataUrl.slice("data:image/png;base64,".length);
|
|
32
|
+
console.log(`stills: ${still.kind} drawn (${Math.round(out[still.kind].length * 0.75 / 1024)} KB)`);
|
|
33
|
+
} catch (e) { console.log(`stills: ${still.kind} skipped (${String(e?.message ?? e).split("\n")[0]})`); }
|
|
34
|
+
finally { await page.close().catch(() => {}); }
|
|
35
|
+
}
|
|
36
|
+
} finally { await browser.close().catch(() => {}); }
|
|
37
|
+
if (!Object.keys(out).length) process.exit(0);
|
|
38
|
+
const put = await fetch(`${base}/api/recorder/stills`, { method: "PUT", headers, body: JSON.stringify({ bite_id: biteId, hash: plan.hash, stills: out }), signal: AbortSignal.timeout(30000) }).then((r) => r.json().catch(() => ({ ok: r.ok }))).catch((e) => ({ error: String(e?.message ?? e) }));
|
|
39
|
+
console.log(`stills: ${put?.ok ? `remembered for the export (${Object.keys(out).join(", ")})` : `not kept (${put?.error ?? "?"})`} in ${((Date.now() - t0) / 1000).toFixed(1)} s`);
|