@kadj-amoah/showrunner 1.1.0 → 1.1.2
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/CHANGELOG.md +45 -0
- package/README.md +48 -2
- package/dist/cli.js +321 -77
- package/dist/cli.js.map +1 -1
- package/dist/index.js +35 -18
- package/dist/index.js.map +1 -1
- package/package.json +5 -7
package/dist/cli.js
CHANGED
|
@@ -687,7 +687,7 @@ var HEADER = `// scripts/playwright_demo.ts
|
|
|
687
687
|
function renderPlaywrightSpec(inputs) {
|
|
688
688
|
const { manifest, recording, targetUrl, videoDir, traceDir } = inputs;
|
|
689
689
|
const launchLines = [
|
|
690
|
-
`import { chromium, firefox, webkit,
|
|
690
|
+
`import { chromium, firefox, webkit, type BrowserContext } from 'playwright-core';`,
|
|
691
691
|
`import { mkdirSync } from 'node:fs';`,
|
|
692
692
|
``,
|
|
693
693
|
`const VIDEO_DIR = ${JSON.stringify(videoDir)};`,
|
|
@@ -774,7 +774,7 @@ function renderAction(action) {
|
|
|
774
774
|
case "wait":
|
|
775
775
|
return `await page.waitForTimeout(${action.ms});`;
|
|
776
776
|
case "assert_visible":
|
|
777
|
-
return `await
|
|
777
|
+
return `await page.locator(${JSON.stringify(flattenSelector(action.selector))}).first().waitFor({ state: 'visible' });`;
|
|
778
778
|
case "press":
|
|
779
779
|
return action.selector ? `await page.locator(${JSON.stringify(flattenSelector(action.selector))}).first().press(${JSON.stringify(action.key)});` : `await page.keyboard.press(${JSON.stringify(action.key)});`;
|
|
780
780
|
case "custom":
|
|
@@ -1229,7 +1229,7 @@ async function generateManifest(opts) {
|
|
|
1229
1229
|
}
|
|
1230
1230
|
|
|
1231
1231
|
// src/scriptGen/domPreflight.ts
|
|
1232
|
-
import { chromium, firefox, webkit } from "playwright";
|
|
1232
|
+
import { chromium, firefox, webkit } from "playwright-core";
|
|
1233
1233
|
|
|
1234
1234
|
// src/recording/selectorHeuristic.ts
|
|
1235
1235
|
var SELECTOR_FOR_FN_SOURCE = `
|
|
@@ -1318,7 +1318,7 @@ async function scrapeSelectorInventory(opts) {
|
|
|
1318
1318
|
try {
|
|
1319
1319
|
await page.waitForLoadState("networkidle", { timeout: networkIdleMs });
|
|
1320
1320
|
} catch {
|
|
1321
|
-
logger.debug("domPreflight: networkidle did not fire within budget \
|
|
1321
|
+
logger.debug("domPreflight: networkidle did not fire within budget \xE2\u20AC\u201D proceeding");
|
|
1322
1322
|
}
|
|
1323
1323
|
const items = await page.evaluate(
|
|
1324
1324
|
buildScrapeScript(SELECTOR_FOR_FN_SOURCE, maxItems)
|
|
@@ -2219,7 +2219,7 @@ import {
|
|
|
2219
2219
|
chromium as chromium2,
|
|
2220
2220
|
firefox as firefox2,
|
|
2221
2221
|
webkit as webkit2
|
|
2222
|
-
} from "playwright";
|
|
2222
|
+
} from "playwright-core";
|
|
2223
2223
|
|
|
2224
2224
|
// src/manifest/alignment.ts
|
|
2225
2225
|
import { readFile as readFile6 } from "fs/promises";
|
|
@@ -2330,7 +2330,7 @@ async function buildStorageState(cookiesFile, localStorageFile, configDir) {
|
|
|
2330
2330
|
cookiesJson.origins = [...cookiesJson.origins ?? [], ...lsJson];
|
|
2331
2331
|
} else {
|
|
2332
2332
|
logger.warn(
|
|
2333
|
-
`local_storage_file at ${lsAbs} is not a Playwright origins list \
|
|
2333
|
+
`local_storage_file at ${lsAbs} is not a Playwright origins list \xE2\u20AC\u201D skipping. Use storageState() output format.`
|
|
2334
2334
|
);
|
|
2335
2335
|
}
|
|
2336
2336
|
} catch (err) {
|
|
@@ -2371,7 +2371,7 @@ function buildFormFill(auth) {
|
|
|
2371
2371
|
} catch (err) {
|
|
2372
2372
|
const cause = err instanceof Error ? err.message : String(err);
|
|
2373
2373
|
throw new AuthError(
|
|
2374
|
-
`Form auth: did not reach ${auth.success_url_pattern} within ${auth.timeout_ms}ms \
|
|
2374
|
+
`Form auth: did not reach ${auth.success_url_pattern} within ${auth.timeout_ms}ms \xE2\u20AC\u201D ${cause}`
|
|
2375
2375
|
);
|
|
2376
2376
|
}
|
|
2377
2377
|
};
|
|
@@ -2380,7 +2380,7 @@ function buildFormFill(auth) {
|
|
|
2380
2380
|
// src/recording/actions.ts
|
|
2381
2381
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
2382
2382
|
import { dirname as dirname5, join as join2 } from "path";
|
|
2383
|
-
import { errors as playwrightErrors } from "playwright";
|
|
2383
|
+
import { errors as playwrightErrors } from "playwright-core";
|
|
2384
2384
|
|
|
2385
2385
|
// src/recording/cursorOverlay.ts
|
|
2386
2386
|
var CURSOR_INIT_SCRIPT = `
|
|
@@ -2454,7 +2454,7 @@ var CURSOR_INIT_SCRIPT = `
|
|
|
2454
2454
|
|
|
2455
2455
|
if (tryInstall()) return;
|
|
2456
2456
|
|
|
2457
|
-
console.log('[showrunner-cursor] deferring \
|
|
2457
|
+
console.log('[showrunner-cursor] deferring \xE2\u20AC\u201D body not ready');
|
|
2458
2458
|
|
|
2459
2459
|
const tick = () => { if (tryInstall()) cleanup(); };
|
|
2460
2460
|
const observer = new MutationObserver(tick);
|
|
@@ -2738,7 +2738,7 @@ async function recordDemo(opts) {
|
|
|
2738
2738
|
const tFirstSegmentStart = performance.now();
|
|
2739
2739
|
const preSegmentOffsetSec = (tFirstSegmentStart - tRecordingStart) / 1e3;
|
|
2740
2740
|
logger.info(
|
|
2741
|
-
`pre-segment recording offset: ${preSegmentOffsetSec.toFixed(2)}s (page.goto + setup) \
|
|
2741
|
+
`pre-segment recording offset: ${preSegmentOffsetSec.toFixed(2)}s (page.goto + setup) \xE2\u20AC\u201D slice_plan will be offset by this amount`
|
|
2742
2742
|
);
|
|
2743
2743
|
const t0 = tFirstSegmentStart;
|
|
2744
2744
|
let cursorPos = {
|
|
@@ -2817,7 +2817,7 @@ async function recordDemo(opts) {
|
|
|
2817
2817
|
);
|
|
2818
2818
|
if (adjusted < motionMs) {
|
|
2819
2819
|
warnings.push(
|
|
2820
|
-
`cursor motion for action at ${arrivalAt}s clamped from ${Math.round(motionMs)}ms to ${adjusted}ms \
|
|
2820
|
+
`cursor motion for action at ${arrivalAt}s clamped from ${Math.round(motionMs)}ms to ${adjusted}ms \xE2\u20AC\u201D manifest timing tight`
|
|
2821
2821
|
);
|
|
2822
2822
|
}
|
|
2823
2823
|
motionMs = adjusted;
|
|
@@ -2865,14 +2865,14 @@ async function recordDemo(opts) {
|
|
|
2865
2865
|
if (outcome.status === "skipped") {
|
|
2866
2866
|
warnings.push(`${action.type}: ${outcome.reason}`);
|
|
2867
2867
|
if (outcome.screenshot) failureScreenshots.push(outcome.screenshot);
|
|
2868
|
-
logger.warn(`segment ${seg.id} \
|
|
2868
|
+
logger.warn(`segment ${seg.id} \xE2\u20AC\u201D ${action.type} skipped`, {
|
|
2869
2869
|
reason: outcome.reason,
|
|
2870
2870
|
screenshot: outcome.screenshot
|
|
2871
2871
|
});
|
|
2872
2872
|
} else if (outcome.status === "segment_failed") {
|
|
2873
2873
|
failure = outcome.reason;
|
|
2874
2874
|
if (outcome.screenshot) failureScreenshots.push(outcome.screenshot);
|
|
2875
|
-
logger.error(`segment ${seg.id} \
|
|
2875
|
+
logger.error(`segment ${seg.id} \xE2\u20AC\u201D failed`, {
|
|
2876
2876
|
reason: outcome.reason,
|
|
2877
2877
|
screenshot: outcome.screenshot
|
|
2878
2878
|
});
|
|
@@ -2889,7 +2889,7 @@ async function recordDemo(opts) {
|
|
|
2889
2889
|
await page.waitForTimeout(Math.round(remaining * 1e3));
|
|
2890
2890
|
} else if (remaining < -0.5) {
|
|
2891
2891
|
warnings.push(
|
|
2892
|
-
`segment took ${elapsedInSegment.toFixed(2)}s but only ${allocated.toFixed(2)}s allocated \
|
|
2892
|
+
`segment took ${elapsedInSegment.toFixed(2)}s but only ${allocated.toFixed(2)}s allocated \xE2\u20AC\u201D manifest timing tight for this segment`
|
|
2893
2893
|
);
|
|
2894
2894
|
}
|
|
2895
2895
|
const segEnd = (performance.now() - t0) / 1e3;
|
|
@@ -2924,7 +2924,7 @@ async function recordDemo(opts) {
|
|
|
2924
2924
|
}
|
|
2925
2925
|
recordingPath = dest;
|
|
2926
2926
|
} else {
|
|
2927
|
-
logger.warn("Recording context did not produce a video file \
|
|
2927
|
+
logger.warn("Recording context did not produce a video file \xE2\u20AC\u201D recordVideo may have been ignored");
|
|
2928
2928
|
}
|
|
2929
2929
|
return {
|
|
2930
2930
|
recording_path: recordingPath,
|
|
@@ -2958,10 +2958,10 @@ async function resolveStorageState(browser, recording, authPlan) {
|
|
|
2958
2958
|
var PreflightError = class extends Error {
|
|
2959
2959
|
constructor(failures) {
|
|
2960
2960
|
const lines = failures.map(
|
|
2961
|
-
(f) => ` - ${f.segment}#${f.actionIndex} \
|
|
2961
|
+
(f) => ` - ${f.segment}#${f.actionIndex} \xE2\u2020\u2019 [${f.selectors.join(" | ")}]`
|
|
2962
2962
|
);
|
|
2963
2963
|
super(
|
|
2964
|
-
`Pre-flight check failed \
|
|
2964
|
+
`Pre-flight check failed \xE2\u20AC\u201D ${failures.length} selector(s) did not resolve on ${failures[0]?.segment ? `the live target page` : "the page"}:
|
|
2965
2965
|
${lines.join("\n")}
|
|
2966
2966
|
|
|
2967
2967
|
Fix the manifest selectors, or set recording.preflight: false to skip this check.`
|
|
@@ -5013,6 +5013,16 @@ function resolveMaybe(p, configDir) {
|
|
|
5013
5013
|
var STAGE_NAMES = ["comprehension", "script", "voiceover", "record", "mux"];
|
|
5014
5014
|
|
|
5015
5015
|
// src/pipeline/run.ts
|
|
5016
|
+
var PipelineStageError = class extends Error {
|
|
5017
|
+
constructor(stage, message, cause) {
|
|
5018
|
+
super(message);
|
|
5019
|
+
this.stage = stage;
|
|
5020
|
+
this.cause = cause;
|
|
5021
|
+
}
|
|
5022
|
+
stage;
|
|
5023
|
+
cause;
|
|
5024
|
+
name = "PipelineStageError";
|
|
5025
|
+
};
|
|
5016
5026
|
var STAGES_BY_NAME = {
|
|
5017
5027
|
comprehension: comprehensionStage,
|
|
5018
5028
|
script: scriptStage,
|
|
@@ -5052,7 +5062,14 @@ async function run(loaded, options = {}) {
|
|
|
5052
5062
|
continue;
|
|
5053
5063
|
}
|
|
5054
5064
|
const stage = STAGES_BY_NAME[name];
|
|
5055
|
-
|
|
5065
|
+
let result;
|
|
5066
|
+
try {
|
|
5067
|
+
result = await stage.run(ctx);
|
|
5068
|
+
} catch (err) {
|
|
5069
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5070
|
+
logger.event({ stage: name, status: "failed", error: message });
|
|
5071
|
+
throw new PipelineStageError(name, message, err);
|
|
5072
|
+
}
|
|
5056
5073
|
results.push(result);
|
|
5057
5074
|
logger.event({
|
|
5058
5075
|
stage: name,
|
|
@@ -5117,10 +5134,11 @@ async function writeBuildManifest(path, payload) {
|
|
|
5117
5134
|
|
|
5118
5135
|
// src/commands/doctor.ts
|
|
5119
5136
|
import { spawn as spawn3 } from "child_process";
|
|
5120
|
-
import { stat as stat9, access, constants } from "fs/promises";
|
|
5121
|
-
import { isAbsolute as isAbsolute7, resolve as resolve13, dirname as dirname9 } from "path";
|
|
5137
|
+
import { stat as stat9, access, constants, readdir } from "fs/promises";
|
|
5138
|
+
import { isAbsolute as isAbsolute7, resolve as resolve13, dirname as dirname9, join as join9 } from "path";
|
|
5139
|
+
import { homedir, platform as osPlatform } from "os";
|
|
5122
5140
|
import { request as undiciRequest } from "undici";
|
|
5123
|
-
import { chromium as chromium3, firefox as firefox3, webkit as webkit3 } from "playwright";
|
|
5141
|
+
import { chromium as chromium3, firefox as firefox3, webkit as webkit3 } from "playwright-core";
|
|
5124
5142
|
|
|
5125
5143
|
// src/config/providerEnv.ts
|
|
5126
5144
|
function inspectProviderEnv(config) {
|
|
@@ -5188,6 +5206,7 @@ async function doctorCommand(opts) {
|
|
|
5188
5206
|
for (const r of results) printRow(r);
|
|
5189
5207
|
const summary = summarize(results);
|
|
5190
5208
|
if (summary.fail > 0) {
|
|
5209
|
+
printFixOrder(results);
|
|
5191
5210
|
logger.error(`doctor: ${summary.fail} FAIL, ${summary.warn} WARN, ${summary.pass} PASS`);
|
|
5192
5211
|
} else if (summary.warn > 0) {
|
|
5193
5212
|
logger.warn(`doctor: ${summary.warn} WARN, ${summary.pass} PASS`);
|
|
@@ -5213,10 +5232,18 @@ async function runDoctorChecks(configPath) {
|
|
|
5213
5232
|
const { config, configDir } = loaded;
|
|
5214
5233
|
for (const row of inspectProviderEnv(config)) {
|
|
5215
5234
|
const suffix = row.stage ? ` (${row.stage} override)` : "";
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5235
|
+
if (row.set) {
|
|
5236
|
+
results.push({
|
|
5237
|
+
status: "PASS",
|
|
5238
|
+
label: `${row.slot} provider=${row.provider}${suffix}, ${row.envVar} set`
|
|
5239
|
+
});
|
|
5240
|
+
} else {
|
|
5241
|
+
results.push({
|
|
5242
|
+
status: "FAIL",
|
|
5243
|
+
label: `${row.slot} provider=${row.provider}${suffix}, ${row.envVar} NOT set`,
|
|
5244
|
+
detail: providerEnvHint(row.slot, row.provider, row.envVar)
|
|
5245
|
+
});
|
|
5246
|
+
}
|
|
5220
5247
|
}
|
|
5221
5248
|
results.push(await checkBinary("ffmpeg", ["-version"]));
|
|
5222
5249
|
results.push(await checkBinary("ffprobe", ["-version"]));
|
|
@@ -5271,9 +5298,20 @@ function summarize(results) {
|
|
|
5271
5298
|
fail: results.filter((r) => r.status === "FAIL").length
|
|
5272
5299
|
};
|
|
5273
5300
|
}
|
|
5301
|
+
function printFixOrder(results) {
|
|
5302
|
+
const fails = results.filter((r) => r.status === "FAIL");
|
|
5303
|
+
if (fails.length === 0) return;
|
|
5304
|
+
process.stdout.write("\n");
|
|
5305
|
+
logger.info("To fix:");
|
|
5306
|
+
fails.forEach((r, i) => {
|
|
5307
|
+
const hint = r.detail ? ` \u2014 ${r.detail}` : "";
|
|
5308
|
+
logger.info(` ${i + 1}. ${r.label}${hint}`);
|
|
5309
|
+
});
|
|
5310
|
+
process.stdout.write("\n");
|
|
5311
|
+
}
|
|
5274
5312
|
function printRow(r) {
|
|
5275
5313
|
const tag = `[${r.status}]`;
|
|
5276
|
-
const line = `${tag} ${r.label}${r.detail ? ` \
|
|
5314
|
+
const line = `${tag} ${r.label}${r.detail ? ` \xE2\u20AC\u201D ${r.detail}` : ""}`;
|
|
5277
5315
|
if (r.status === "FAIL") logger.error(line);
|
|
5278
5316
|
else if (r.status === "WARN") logger.warn(line);
|
|
5279
5317
|
else logger.info(line);
|
|
@@ -5286,18 +5324,44 @@ async function checkBinary(name, args) {
|
|
|
5286
5324
|
stdout += chunk.toString("utf8");
|
|
5287
5325
|
});
|
|
5288
5326
|
child.on("error", () => {
|
|
5289
|
-
resolve27({
|
|
5327
|
+
resolve27({
|
|
5328
|
+
status: "FAIL",
|
|
5329
|
+
label: `${name} not on PATH`,
|
|
5330
|
+
detail: installHintFor(name)
|
|
5331
|
+
});
|
|
5290
5332
|
});
|
|
5291
5333
|
child.on("exit", (code) => {
|
|
5292
5334
|
if (code === 0) {
|
|
5293
5335
|
const firstLine = stdout.split("\n")[0]?.trim() ?? "";
|
|
5294
5336
|
resolve27({ status: "PASS", label: `${name} present`, detail: firstLine });
|
|
5295
5337
|
} else {
|
|
5296
|
-
resolve27({
|
|
5338
|
+
resolve27({
|
|
5339
|
+
status: "FAIL",
|
|
5340
|
+
label: `${name} exited with code ${code}`,
|
|
5341
|
+
detail: installHintFor(name)
|
|
5342
|
+
});
|
|
5297
5343
|
}
|
|
5298
5344
|
});
|
|
5299
5345
|
});
|
|
5300
5346
|
}
|
|
5347
|
+
function providerEnvHint(slot, provider, envVar) {
|
|
5348
|
+
const dashboards = {
|
|
5349
|
+
anthropic: "https://console.anthropic.com/settings/keys",
|
|
5350
|
+
openai: "https://platform.openai.com/api-keys",
|
|
5351
|
+
elevenlabs: "https://elevenlabs.io/app/settings/api-keys"
|
|
5352
|
+
};
|
|
5353
|
+
const dash = dashboards[provider];
|
|
5354
|
+
const dashHint = dash ? ` (get a key from ${dash})` : "";
|
|
5355
|
+
const altHint = slot === "llm" ? ` \u2014 or switch llm.default.provider to "agent_bridge" in demo.yaml to use a local CLI agent (no API key needed)` : "";
|
|
5356
|
+
return `add ${envVar}=... to your project's .env file${dashHint}${altHint}`;
|
|
5357
|
+
}
|
|
5358
|
+
function installHintFor(binary) {
|
|
5359
|
+
const p = osPlatform();
|
|
5360
|
+
if (binary !== "ffmpeg" && binary !== "ffprobe") return "";
|
|
5361
|
+
if (p === "darwin") return "install via `brew install ffmpeg`";
|
|
5362
|
+
if (p === "win32") return "install via `winget install Gyan.FFmpeg` or `choco install ffmpeg`";
|
|
5363
|
+
return "install via `apt install ffmpeg` (Debian/Ubuntu), `pacman -S ffmpeg` (Arch), or `dnf install ffmpeg` (Fedora)";
|
|
5364
|
+
}
|
|
5301
5365
|
async function checkPlaywrightBrowser(config) {
|
|
5302
5366
|
const browserName = config.recording.browser;
|
|
5303
5367
|
try {
|
|
@@ -5309,13 +5373,48 @@ async function checkPlaywrightBrowser(config) {
|
|
|
5309
5373
|
detail: exec
|
|
5310
5374
|
};
|
|
5311
5375
|
} catch (err) {
|
|
5376
|
+
const sudoHit = await probeRootCache(browserName);
|
|
5377
|
+
const fallback = `run \`npx playwright install ${browserName}\``;
|
|
5378
|
+
if (sudoHit) {
|
|
5379
|
+
return {
|
|
5380
|
+
status: "FAIL",
|
|
5381
|
+
label: `playwright ${browserName} binary missing for current user, but found in root/admin cache`,
|
|
5382
|
+
detail: `${sudoHit} \u2014 re-run install WITHOUT sudo so the browser lands in your user cache: ${fallback}`
|
|
5383
|
+
};
|
|
5384
|
+
}
|
|
5312
5385
|
return {
|
|
5313
5386
|
status: "FAIL",
|
|
5314
5387
|
label: `playwright ${browserName} binary missing`,
|
|
5315
|
-
detail:
|
|
5388
|
+
detail: `${fallback} (${err instanceof Error ? err.message : String(err)})`
|
|
5316
5389
|
};
|
|
5317
5390
|
}
|
|
5318
5391
|
}
|
|
5392
|
+
async function probeRootCache(browserName) {
|
|
5393
|
+
const candidates = [];
|
|
5394
|
+
const home = homedir();
|
|
5395
|
+
const p = osPlatform();
|
|
5396
|
+
if (p === "linux") {
|
|
5397
|
+
candidates.push("/root/.cache/ms-playwright");
|
|
5398
|
+
} else if (p === "darwin") {
|
|
5399
|
+
candidates.push("/var/root/Library/Caches/ms-playwright");
|
|
5400
|
+
} else if (p === "win32") {
|
|
5401
|
+
const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
|
|
5402
|
+
candidates.push(join9(systemRoot, "System32", "config", "systemprofile", "AppData", "Local", "ms-playwright"));
|
|
5403
|
+
}
|
|
5404
|
+
if (candidates.length === 0) return null;
|
|
5405
|
+
if (home === "/root" || home === "/var/root") return null;
|
|
5406
|
+
for (const dir of candidates) {
|
|
5407
|
+
try {
|
|
5408
|
+
const entries = await readdir(dir);
|
|
5409
|
+
const match = entries.find((e) => e.toLowerCase().startsWith(browserName.toLowerCase()));
|
|
5410
|
+
if (match) {
|
|
5411
|
+
return `found at ${join9(dir, match)}`;
|
|
5412
|
+
}
|
|
5413
|
+
} catch {
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
return null;
|
|
5417
|
+
}
|
|
5319
5418
|
async function checkTargetReachable(url) {
|
|
5320
5419
|
try {
|
|
5321
5420
|
const start = Date.now();
|
|
@@ -5336,10 +5435,12 @@ async function checkTargetReachable(url) {
|
|
|
5336
5435
|
label: `target ${url} returned HTTP ${res.statusCode}`
|
|
5337
5436
|
};
|
|
5338
5437
|
} catch (err) {
|
|
5438
|
+
const reason = err instanceof Error ? err.message.trim() : String(err).trim();
|
|
5439
|
+
const prefix = reason ? `${reason} \u2014 ` : "";
|
|
5339
5440
|
return {
|
|
5340
5441
|
status: "FAIL",
|
|
5341
5442
|
label: `target ${url} not reachable`,
|
|
5342
|
-
detail:
|
|
5443
|
+
detail: `${prefix}start your dev server on this URL, or change \`recording.target_url\` in demo.yaml`
|
|
5343
5444
|
};
|
|
5344
5445
|
}
|
|
5345
5446
|
}
|
|
@@ -5347,7 +5448,11 @@ async function checkScript(label, abs) {
|
|
|
5347
5448
|
try {
|
|
5348
5449
|
await stat9(abs);
|
|
5349
5450
|
} catch {
|
|
5350
|
-
return {
|
|
5451
|
+
return {
|
|
5452
|
+
status: "FAIL",
|
|
5453
|
+
label: `${label} not found`,
|
|
5454
|
+
detail: `expected at ${abs} \u2014 re-scaffold (\`showrunner init\` writes these) or remove the entry from demo.yaml's recording.state block`
|
|
5455
|
+
};
|
|
5351
5456
|
}
|
|
5352
5457
|
if (process.platform !== "win32") {
|
|
5353
5458
|
try {
|
|
@@ -5402,6 +5507,13 @@ function formatResolution(r) {
|
|
|
5402
5507
|
}
|
|
5403
5508
|
|
|
5404
5509
|
// src/commands/run.ts
|
|
5510
|
+
var STAGE_REMEDIATION = {
|
|
5511
|
+
comprehension: "Check the LLM provider env (or switch llm.default.provider to agent_bridge in demo.yaml). If you already have a product_model.json, skip this stage: --stages script,record,voiceover,mux",
|
|
5512
|
+
script: "Manifest generation failed. Inspect scripts/manifest.json if it was partially written. If the LLM is misbehaving, hand-author scripts/manifest.json and resume: --stages record,voiceover,mux",
|
|
5513
|
+
record: "Playwright recording failed. Inspect with `showrunner trace -c <config> --all` or `--segment <id>`. Demo a problem segment manually with `showrunner record-actions -c <config> --segment <id>`.",
|
|
5514
|
+
voiceover: "TTS synthesis or alignment failed. Check your TTS provider env. If alignment is the issue, set voiceover.alignment_strategy: best_effort in demo.yaml.",
|
|
5515
|
+
mux: "ffmpeg mux failed. Check the `doctor` row about free disk + RAM. Cap libx264 threads with `SHOWRUNNER_FFMPEG_THREADS=1` if RAM is tight."
|
|
5516
|
+
};
|
|
5405
5517
|
async function runCommand2(opts) {
|
|
5406
5518
|
let loaded;
|
|
5407
5519
|
try {
|
|
@@ -5470,6 +5582,12 @@ async function runCommand2(opts) {
|
|
|
5470
5582
|
buildManifest: result.buildManifestPath
|
|
5471
5583
|
});
|
|
5472
5584
|
} catch (err) {
|
|
5585
|
+
if (err instanceof PipelineStageError) {
|
|
5586
|
+
logger.error(`Pipeline failed in stage \`${err.stage}\`: ${err.message}`);
|
|
5587
|
+
const remediation = STAGE_REMEDIATION[err.stage];
|
|
5588
|
+
if (remediation) logger.error(`To fix: ${remediation}`);
|
|
5589
|
+
process.exit(1);
|
|
5590
|
+
}
|
|
5473
5591
|
const message = err instanceof Error ? err.message : String(err);
|
|
5474
5592
|
logger.error(`Pipeline failed: ${message}`);
|
|
5475
5593
|
process.exit(1);
|
|
@@ -5478,7 +5596,7 @@ async function runCommand2(opts) {
|
|
|
5478
5596
|
|
|
5479
5597
|
// src/commands/init.ts
|
|
5480
5598
|
import { mkdir as mkdir11, writeFile as writeFile12, access as access2 } from "fs/promises";
|
|
5481
|
-
import { dirname as dirname10, isAbsolute as isAbsolute8, join as
|
|
5599
|
+
import { dirname as dirname10, isAbsolute as isAbsolute8, join as join10, resolve as resolve15 } from "path";
|
|
5482
5600
|
var LLM_CHOICES = ["anthropic", "openai", "agent_bridge"];
|
|
5483
5601
|
var TTS_CHOICES = ["elevenlabs", "openai", "custom"];
|
|
5484
5602
|
var PLACEHOLDER_FILES = [
|
|
@@ -5504,7 +5622,7 @@ async function initCommand(opts) {
|
|
|
5504
5622
|
resolution: resolvePreset(resolutionPreset)
|
|
5505
5623
|
};
|
|
5506
5624
|
const parent = isAbsolute8(opts.dir) ? opts.dir : resolve15(process.cwd(), opts.dir);
|
|
5507
|
-
const projectRoot =
|
|
5625
|
+
const projectRoot = join10(parent, opts.name);
|
|
5508
5626
|
if (!opts.force && await pathExists(projectRoot)) {
|
|
5509
5627
|
logger.error(
|
|
5510
5628
|
`Directory already exists: ${projectRoot}. Pass --force to overwrite, or choose a different --name/--dir.`
|
|
@@ -5513,34 +5631,59 @@ async function initCommand(opts) {
|
|
|
5513
5631
|
}
|
|
5514
5632
|
await mkdir11(projectRoot, { recursive: true });
|
|
5515
5633
|
for (const rel of PLACEHOLDER_FILES) {
|
|
5516
|
-
const dest =
|
|
5634
|
+
const dest = join10(projectRoot, rel);
|
|
5517
5635
|
await mkdir11(dirname10(dest), { recursive: true });
|
|
5518
5636
|
await writeFile12(dest, "", "utf8");
|
|
5519
5637
|
}
|
|
5520
|
-
await writeFile12(
|
|
5521
|
-
await writeFile12(
|
|
5522
|
-
await writeFile12(
|
|
5523
|
-
await mkdir11(
|
|
5524
|
-
await writeFile12(
|
|
5525
|
-
await writeFile12(
|
|
5526
|
-
await writeFile12(
|
|
5638
|
+
await writeFile12(join10(projectRoot, "demo.yaml"), demoYamlTemplate(resolved), "utf8");
|
|
5639
|
+
await writeFile12(join10(projectRoot, ".env.example"), envExampleTemplate(resolved), "utf8");
|
|
5640
|
+
await writeFile12(join10(projectRoot, ".gitignore"), gitignoreTemplate(), "utf8");
|
|
5641
|
+
await mkdir11(join10(projectRoot, "docs"), { recursive: true });
|
|
5642
|
+
await writeFile12(join10(projectRoot, "docs/PRD.md"), prdStubTemplate(resolved), "utf8");
|
|
5643
|
+
await writeFile12(join10(projectRoot, "scripts/manifest.json"), starterManifest(resolved), "utf8");
|
|
5644
|
+
await writeFile12(join10(projectRoot, "scripts/seed_demo_data.sh"), seedScript(), {
|
|
5527
5645
|
mode: 493
|
|
5528
5646
|
});
|
|
5529
|
-
await writeFile12(
|
|
5647
|
+
await writeFile12(join10(projectRoot, "scripts/reset_demo_data.sh"), resetScript(), {
|
|
5530
5648
|
mode: 493
|
|
5531
5649
|
});
|
|
5532
|
-
await writeFile12(
|
|
5650
|
+
await writeFile12(join10(projectRoot, "scripts/teardown.sh"), teardownScript(), {
|
|
5533
5651
|
mode: 493
|
|
5534
5652
|
});
|
|
5535
|
-
await writeFile12(
|
|
5653
|
+
await writeFile12(join10(projectRoot, "README.md"), readmeTemplate(resolved), "utf8");
|
|
5536
5654
|
logger.info(`Showrunner project scaffolded at ${projectRoot} (llm=${resolved.llm}, tts=${resolved.tts})`);
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5655
|
+
printNextSteps(opts.name, resolved);
|
|
5656
|
+
}
|
|
5657
|
+
function printNextSteps(projectName, resolved) {
|
|
5658
|
+
const envVars = requiredEnvVars(resolved);
|
|
5659
|
+
const lines = ["", `Next:`, ""];
|
|
5660
|
+
let step = 1;
|
|
5661
|
+
lines.push(` ${step++}. cd ${projectName}`);
|
|
5662
|
+
if (envVars.length > 0) {
|
|
5663
|
+
const keysList = envVars.join(", ");
|
|
5664
|
+
lines.push(` ${step++}. cp .env.example .env # then paste in: ${keysList}`);
|
|
5665
|
+
} else {
|
|
5666
|
+
lines.push(
|
|
5667
|
+
` ${step++}. (no .env needed \u2014 agent_bridge LLM + ${resolved.tts} TTS don't require API keys)`
|
|
5668
|
+
);
|
|
5669
|
+
}
|
|
5670
|
+
lines.push(` ${step++}. $EDITOR docs/PRD.md # replace the stub with your product brief`);
|
|
5671
|
+
lines.push(` ${step++}. showrunner doctor -c demo.yaml`);
|
|
5672
|
+
lines.push(` ${step++}. showrunner run -c demo.yaml # \u2192 output/demo_final.mp4`);
|
|
5673
|
+
lines.push("");
|
|
5674
|
+
lines.push(
|
|
5675
|
+
`Optional: \`showrunner understand -c demo.yaml --interactive\` if you'd rather answer five questions than write the PRD upfront.`
|
|
5676
|
+
);
|
|
5677
|
+
lines.push("");
|
|
5678
|
+
process.stdout.write(lines.join("\n"));
|
|
5679
|
+
}
|
|
5680
|
+
function requiredEnvVars(resolved) {
|
|
5681
|
+
const vars = /* @__PURE__ */ new Set();
|
|
5682
|
+
if (resolved.llm === "anthropic") vars.add("ANTHROPIC_API_KEY");
|
|
5683
|
+
if (resolved.llm === "openai") vars.add("OPENAI_API_KEY");
|
|
5684
|
+
if (resolved.tts === "elevenlabs") vars.add("ELEVENLABS_API_KEY");
|
|
5685
|
+
if (resolved.tts === "openai") vars.add("OPENAI_API_KEY");
|
|
5686
|
+
return [...vars];
|
|
5544
5687
|
}
|
|
5545
5688
|
function validateChoice(value, allowed, defaultValue, flagName) {
|
|
5546
5689
|
if (!value) return defaultValue;
|
|
@@ -5971,6 +6114,50 @@ and \`at_word\` actions degrade to \`at\`).
|
|
|
5971
6114
|
`;
|
|
5972
6115
|
}
|
|
5973
6116
|
|
|
6117
|
+
// src/commands/installBrowser.ts
|
|
6118
|
+
import { spawn as spawn4 } from "child_process";
|
|
6119
|
+
import { access as access3 } from "fs/promises";
|
|
6120
|
+
import { fileURLToPath } from "url";
|
|
6121
|
+
import { dirname as dirname11, join as join11 } from "path";
|
|
6122
|
+
var DEFAULT_BROWSER = "chromium";
|
|
6123
|
+
async function installBrowserCommand(opts) {
|
|
6124
|
+
const browser = opts.browser ?? DEFAULT_BROWSER;
|
|
6125
|
+
const cli = await resolvePlaywrightCoreCli();
|
|
6126
|
+
logger.info(`installing Playwright ${browser} (via bundled playwright-core, no project required)`);
|
|
6127
|
+
const child = spawn4(process.execPath, [cli, "install", browser], {
|
|
6128
|
+
stdio: "inherit",
|
|
6129
|
+
env: process.env
|
|
6130
|
+
});
|
|
6131
|
+
const code = await new Promise((resolve27, reject) => {
|
|
6132
|
+
child.on("error", reject);
|
|
6133
|
+
child.on("close", (c) => resolve27(c ?? 0));
|
|
6134
|
+
});
|
|
6135
|
+
if (code !== 0) {
|
|
6136
|
+
logger.error(`playwright install exited with code ${code}`);
|
|
6137
|
+
process.exit(code);
|
|
6138
|
+
}
|
|
6139
|
+
logger.info(`${browser} installed. Try \`showrunner doctor -c demo.yaml\` next.`);
|
|
6140
|
+
}
|
|
6141
|
+
async function resolvePlaywrightCoreCli() {
|
|
6142
|
+
const here = fileURLToPath(import.meta.url);
|
|
6143
|
+
let dir = dirname11(here);
|
|
6144
|
+
const root = dir.split(/[\\/]/)[0] + "/";
|
|
6145
|
+
while (dir && dir !== root) {
|
|
6146
|
+
const candidate = join11(dir, "node_modules", "playwright-core", "cli.js");
|
|
6147
|
+
try {
|
|
6148
|
+
await access3(candidate);
|
|
6149
|
+
return candidate;
|
|
6150
|
+
} catch {
|
|
6151
|
+
}
|
|
6152
|
+
const parent = dirname11(dir);
|
|
6153
|
+
if (parent === dir) break;
|
|
6154
|
+
dir = parent;
|
|
6155
|
+
}
|
|
6156
|
+
throw new Error(
|
|
6157
|
+
"Could not locate playwright-core CLI inside Showrunner's node_modules. This is a packaging bug \u2014 please file an issue."
|
|
6158
|
+
);
|
|
6159
|
+
}
|
|
6160
|
+
|
|
5974
6161
|
// src/commands/validate.ts
|
|
5975
6162
|
import { stat as stat10 } from "fs/promises";
|
|
5976
6163
|
import { isAbsolute as isAbsolute9, resolve as resolve16 } from "path";
|
|
@@ -6123,8 +6310,8 @@ async function pathExists2(p) {
|
|
|
6123
6310
|
|
|
6124
6311
|
// src/commands/rerunSegment.ts
|
|
6125
6312
|
import { mkdir as mkdir12, rename as rename3, writeFile as writeFile13 } from "fs/promises";
|
|
6126
|
-
import { join as
|
|
6127
|
-
import { chromium as chromium4, firefox as firefox4, webkit as webkit4 } from "playwright";
|
|
6313
|
+
import { join as join12, resolve as resolve19 } from "path";
|
|
6314
|
+
import { chromium as chromium4, firefox as firefox4, webkit as webkit4 } from "playwright-core";
|
|
6128
6315
|
var RERUN_BUFFER_MS = 500;
|
|
6129
6316
|
var browserMap4 = { chromium: chromium4, firefox: firefox4, webkit: webkit4 };
|
|
6130
6317
|
async function rerunSegmentCommand(opts) {
|
|
@@ -6242,17 +6429,17 @@ async function rerunSegmentCommand(opts) {
|
|
|
6242
6429
|
await page.waitForTimeout(config.recording.segment_buffer_ms);
|
|
6243
6430
|
const traceDir = resolve19(configDir, config.recording.trace_dir);
|
|
6244
6431
|
await mkdir12(traceDir, { recursive: true });
|
|
6245
|
-
await ctx.tracing.stopChunk({ path:
|
|
6432
|
+
await ctx.tracing.stopChunk({ path: join12(traceDir, `${segment.id}.zip`) });
|
|
6246
6433
|
const videoHandle = page.video();
|
|
6247
6434
|
await ctx.close();
|
|
6248
6435
|
if (!videoHandle) {
|
|
6249
|
-
logger.error("No video captured \
|
|
6436
|
+
logger.error("No video captured \xE2\u20AC\u201D recordVideo may have been ignored");
|
|
6250
6437
|
process.exit(1);
|
|
6251
6438
|
}
|
|
6252
6439
|
const original = await videoHandle.path();
|
|
6253
|
-
const dest =
|
|
6440
|
+
const dest = join12(videoDir, `${segment.id}.webm`);
|
|
6254
6441
|
await rename3(original, dest);
|
|
6255
|
-
const metadataPath =
|
|
6442
|
+
const metadataPath = join12(videoDir, `${segment.id}.rerun.json`);
|
|
6256
6443
|
await writeFile13(
|
|
6257
6444
|
metadataPath,
|
|
6258
6445
|
JSON.stringify(
|
|
@@ -6270,7 +6457,7 @@ async function rerunSegmentCommand(opts) {
|
|
|
6270
6457
|
) + "\n",
|
|
6271
6458
|
"utf8"
|
|
6272
6459
|
);
|
|
6273
|
-
logger.info(`Segment ${segment.id} re-recorded \
|
|
6460
|
+
logger.info(`Segment ${segment.id} re-recorded \xE2\u2020\u2019 ${dest}`, { metadata: metadataPath });
|
|
6274
6461
|
if (failure) {
|
|
6275
6462
|
process.exit(1);
|
|
6276
6463
|
}
|
|
@@ -6281,7 +6468,7 @@ async function rerunSegmentCommand(opts) {
|
|
|
6281
6468
|
|
|
6282
6469
|
// src/commands/captureAuth.ts
|
|
6283
6470
|
import { mkdir as mkdir14 } from "fs/promises";
|
|
6284
|
-
import { dirname as
|
|
6471
|
+
import { dirname as dirname12, isAbsolute as isAbsolute10, resolve as resolve21 } from "path";
|
|
6285
6472
|
import { createInterface } from "readline/promises";
|
|
6286
6473
|
|
|
6287
6474
|
// src/recording/headed.ts
|
|
@@ -6291,7 +6478,7 @@ import {
|
|
|
6291
6478
|
chromium as chromium5,
|
|
6292
6479
|
firefox as firefox5,
|
|
6293
6480
|
webkit as webkit5
|
|
6294
|
-
} from "playwright";
|
|
6481
|
+
} from "playwright-core";
|
|
6295
6482
|
var browserMap5 = { chromium: chromium5, firefox: firefox5, webkit: webkit5 };
|
|
6296
6483
|
async function launchHeadedSession(opts) {
|
|
6297
6484
|
const { recording, configDir } = opts;
|
|
@@ -6368,7 +6555,7 @@ async function captureAuthCommand(opts) {
|
|
|
6368
6555
|
}
|
|
6369
6556
|
const cookiesRel = opts.outputCookies ?? "./auth/session.json";
|
|
6370
6557
|
const cookiesPath = isAbsolute10(cookiesRel) ? cookiesRel : resolve21(loaded.configDir, cookiesRel);
|
|
6371
|
-
await mkdir14(
|
|
6558
|
+
await mkdir14(dirname12(cookiesPath), { recursive: true });
|
|
6372
6559
|
logger.info("Launching headed browser for auth capture", {
|
|
6373
6560
|
target: loaded.config.recording.target_url,
|
|
6374
6561
|
output: cookiesPath
|
|
@@ -6408,8 +6595,8 @@ Then re-run \`showrunner run --config <demo.yaml>\`.
|
|
|
6408
6595
|
}
|
|
6409
6596
|
|
|
6410
6597
|
// src/commands/trace.ts
|
|
6411
|
-
import { readdir, stat as stat12 } from "fs/promises";
|
|
6412
|
-
import { isAbsolute as isAbsolute11, join as
|
|
6598
|
+
import { readdir as readdir2, stat as stat12 } from "fs/promises";
|
|
6599
|
+
import { isAbsolute as isAbsolute11, join as join13, resolve as resolve22 } from "path";
|
|
6413
6600
|
async function traceCommand(opts) {
|
|
6414
6601
|
let loaded;
|
|
6415
6602
|
try {
|
|
@@ -6423,7 +6610,7 @@ async function traceCommand(opts) {
|
|
|
6423
6610
|
}
|
|
6424
6611
|
const traceDir = resolve22(loaded.configDir, loaded.config.recording.trace_dir);
|
|
6425
6612
|
const videoDir = resolve22(loaded.configDir, loaded.config.recording.output_dir);
|
|
6426
|
-
const slicePlanPath =
|
|
6613
|
+
const slicePlanPath = join13(videoDir, "slice_plan.json");
|
|
6427
6614
|
if (opts.all) {
|
|
6428
6615
|
let plan;
|
|
6429
6616
|
try {
|
|
@@ -6434,14 +6621,14 @@ async function traceCommand(opts) {
|
|
|
6434
6621
|
process.exit(1);
|
|
6435
6622
|
}
|
|
6436
6623
|
for (const seg of plan.segments) {
|
|
6437
|
-
const tracePath = seg.trace_path && isAbsolute11(seg.trace_path) ? seg.trace_path : seg.trace_path ? resolve22(loaded.configDir, seg.trace_path) :
|
|
6624
|
+
const tracePath = seg.trace_path && isAbsolute11(seg.trace_path) ? seg.trace_path : seg.trace_path ? resolve22(loaded.configDir, seg.trace_path) : join13(traceDir, `${seg.id}.zip`);
|
|
6438
6625
|
logger.info(`Opening trace for ${seg.id}`, { path: tracePath });
|
|
6439
6626
|
await openTrace(tracePath);
|
|
6440
6627
|
}
|
|
6441
6628
|
return;
|
|
6442
6629
|
}
|
|
6443
6630
|
if (opts.segment) {
|
|
6444
|
-
const tracePath =
|
|
6631
|
+
const tracePath = join13(traceDir, `${opts.segment}.zip`);
|
|
6445
6632
|
if (!await fileExists8(tracePath)) {
|
|
6446
6633
|
logger.error(`No trace found at ${tracePath}`);
|
|
6447
6634
|
process.exit(1);
|
|
@@ -6482,7 +6669,7 @@ async function openTrace(tracePath) {
|
|
|
6482
6669
|
}
|
|
6483
6670
|
async function listTraces(dir) {
|
|
6484
6671
|
try {
|
|
6485
|
-
const entries = await
|
|
6672
|
+
const entries = await readdir2(dir);
|
|
6486
6673
|
return entries.filter((e) => e.endsWith(".zip")).sort();
|
|
6487
6674
|
} catch {
|
|
6488
6675
|
return [];
|
|
@@ -6546,7 +6733,7 @@ async function fileExists9(path) {
|
|
|
6546
6733
|
|
|
6547
6734
|
// src/commands/understand.ts
|
|
6548
6735
|
import { mkdir as mkdir15, readFile as readFile10, writeFile as writeFile14 } from "fs/promises";
|
|
6549
|
-
import { dirname as
|
|
6736
|
+
import { dirname as dirname13, isAbsolute as isAbsolute12, resolve as resolve24 } from "path";
|
|
6550
6737
|
|
|
6551
6738
|
// src/productModel/prompts.ts
|
|
6552
6739
|
var PRODUCT_MODEL_SYSTEM_PROMPT = `You build product_model.json for Showrunner, an automated product demo recorder.
|
|
@@ -6751,7 +6938,7 @@ async function understandCommand(opts) {
|
|
|
6751
6938
|
}
|
|
6752
6939
|
}
|
|
6753
6940
|
const outputPath = isAbsolute12(outputRel) ? outputRel : resolve24(configDir, outputRel);
|
|
6754
|
-
await mkdir15(
|
|
6941
|
+
await mkdir15(dirname13(outputPath), { recursive: true });
|
|
6755
6942
|
let productModel;
|
|
6756
6943
|
const provider = resolveDefaultLLMProvider({ configDir, llm: llmConfig });
|
|
6757
6944
|
try {
|
|
@@ -6798,8 +6985,8 @@ async function understandCommand(opts) {
|
|
|
6798
6985
|
}
|
|
6799
6986
|
|
|
6800
6987
|
// src/commands/instrument.ts
|
|
6801
|
-
import { mkdir as mkdir16, readdir as
|
|
6802
|
-
import { dirname as
|
|
6988
|
+
import { mkdir as mkdir16, readdir as readdir3, writeFile as writeFile15 } from "fs/promises";
|
|
6989
|
+
import { dirname as dirname14, isAbsolute as isAbsolute13, join as join14, resolve as resolve25, relative as relative2 } from "path";
|
|
6803
6990
|
|
|
6804
6991
|
// src/instrument/scan.ts
|
|
6805
6992
|
import { readFile as readFile11 } from "fs/promises";
|
|
@@ -7022,9 +7209,9 @@ async function instrumentCommand(opts) {
|
|
|
7022
7209
|
const filesToScan = /* @__PURE__ */ new Set();
|
|
7023
7210
|
const IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out"]);
|
|
7024
7211
|
async function walk(root, accept) {
|
|
7025
|
-
const entries = await
|
|
7212
|
+
const entries = await readdir3(root, { withFileTypes: true });
|
|
7026
7213
|
for (const entry of entries) {
|
|
7027
|
-
const abs =
|
|
7214
|
+
const abs = join14(root, entry.name);
|
|
7028
7215
|
if (entry.isDirectory()) {
|
|
7029
7216
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
7030
7217
|
await walk(abs, accept);
|
|
@@ -7081,7 +7268,7 @@ async function instrumentCommand(opts) {
|
|
|
7081
7268
|
logger.warn(`Skipped suggestion at ${s.file}:${s.line} \u2014 ${s.reason}`);
|
|
7082
7269
|
}
|
|
7083
7270
|
const outputPath = isAbsolute13(opts.output) ? opts.output : resolve25(loaded.configDir, opts.output);
|
|
7084
|
-
await mkdir16(
|
|
7271
|
+
await mkdir16(dirname14(outputPath), { recursive: true });
|
|
7085
7272
|
await writeFile15(outputPath, patch, "utf8");
|
|
7086
7273
|
logger.info("Wrote instrumentation patch", { path: outputPath, suggestions: suggestions.length });
|
|
7087
7274
|
process.stdout.write(`
|
|
@@ -7163,7 +7350,7 @@ var IN_PAGE_SCRIPT = `
|
|
|
7163
7350
|
send('submit', el, null);
|
|
7164
7351
|
}, true);
|
|
7165
7352
|
|
|
7166
|
-
// Scroll capture \
|
|
7353
|
+
// Scroll capture \xE2\u20AC\u201D debounce 250ms, coalesce within window, emit direction + accumulated deltaY.
|
|
7167
7354
|
let scrollLastY = window.scrollY;
|
|
7168
7355
|
let scrollAccum = 0;
|
|
7169
7356
|
let scrollTimer = null;
|
|
@@ -7186,7 +7373,7 @@ var IN_PAGE_SCRIPT = `
|
|
|
7186
7373
|
const delta = currentY - scrollLastY;
|
|
7187
7374
|
scrollLastY = currentY;
|
|
7188
7375
|
if (delta === 0) return;
|
|
7189
|
-
// Direction change \
|
|
7376
|
+
// Direction change \xE2\u2020\u2019 flush the previous burst immediately.
|
|
7190
7377
|
if (scrollAccum !== 0 && Math.sign(delta) !== Math.sign(scrollAccum)) {
|
|
7191
7378
|
if (scrollTimer !== null) { clearTimeout(scrollTimer); scrollTimer = null; }
|
|
7192
7379
|
flushScroll();
|
|
@@ -7416,10 +7603,12 @@ async function fileExists10(path) {
|
|
|
7416
7603
|
|
|
7417
7604
|
// src/cli.ts
|
|
7418
7605
|
var program = new Command();
|
|
7419
|
-
program.name("showrunner").description("Automated product demo recording & production tool").version("1.1.
|
|
7606
|
+
program.name("showrunner").description("Automated product demo recording & production tool").version("1.1.2").option("--json", "emit structured JSON logs to stdout").option("--log-level <level>", "log level (debug|info|warn|error)").hook("preAction", (thisCmd) => {
|
|
7420
7607
|
const opts = thisCmd.opts();
|
|
7421
7608
|
if (opts.json) logger.setJson(true);
|
|
7422
7609
|
if (opts.logLevel) logger.setLevel(opts.logLevel);
|
|
7610
|
+
}).action(async () => {
|
|
7611
|
+
await printWelcome();
|
|
7423
7612
|
});
|
|
7424
7613
|
var stageChoices = [...STAGE_NAMES];
|
|
7425
7614
|
program.command("run").description("Run the full Showrunner pipeline").requiredOption("-c, --config <path>", "path to demo.yaml").addOption(
|
|
@@ -7447,6 +7636,7 @@ program.command("init").description("Scaffold a new Showrunner project").option(
|
|
|
7447
7636
|
"scaffold resolution: low (854x480) | standard (720p) | high (1080p) | extreme (4K)",
|
|
7448
7637
|
"standard"
|
|
7449
7638
|
).action(initCommand);
|
|
7639
|
+
program.command("install-browser").description("Install the Playwright browser binary (chromium by default) \u2014 wraps playwright-core install").option("--browser <name>", "browser to install: chromium | firefox | webkit", "chromium").action(installBrowserCommand);
|
|
7450
7640
|
program.command("doctor").description("Run preflight checks on the current config + environment").requiredOption("-c, --config <path>", "path to demo.yaml").option("--json", "emit results as JSON instead of human-readable rows").action(doctorCommand);
|
|
7451
7641
|
program.command("validate").description("Validate a demo.yaml config file").requiredOption("-c, --config <path>", "path to demo.yaml").option("--strict", "exit nonzero on any warning (e.g. missing provider env var)").action(validateCommand);
|
|
7452
7642
|
program.command("understand").description("Build product_model.json from documents or interactive Q&A").option("-c, --config <path>", "path to demo.yaml").option("--interactive", "use interactive Q&A mode").option("--output <path>", "output path for product_model.json").action(understandCommand);
|
|
@@ -7463,6 +7653,60 @@ program.parseAsync(process.argv).catch((err) => {
|
|
|
7463
7653
|
logger.error(message);
|
|
7464
7654
|
process.exit(1);
|
|
7465
7655
|
});
|
|
7656
|
+
async function printWelcome() {
|
|
7657
|
+
const { access: access4 } = await import("fs/promises");
|
|
7658
|
+
const { resolve: resolve27 } = await import("path");
|
|
7659
|
+
const demoYaml = resolve27(process.cwd(), "demo.yaml");
|
|
7660
|
+
let inProject = false;
|
|
7661
|
+
try {
|
|
7662
|
+
await access4(demoYaml);
|
|
7663
|
+
inProject = true;
|
|
7664
|
+
} catch {
|
|
7665
|
+
}
|
|
7666
|
+
const browserMissing = await isChromiumMissing();
|
|
7667
|
+
const lines = ["", `Showrunner v1.1.2 \u2014 automated product-demo recording & production`, ""];
|
|
7668
|
+
if (browserMissing) {
|
|
7669
|
+
lines.push(`First-time setup: install the recording browser (one-off, ~150 MB):`);
|
|
7670
|
+
lines.push(``);
|
|
7671
|
+
lines.push(` showrunner install-browser # wraps Playwright; no "install dependencies first" warning`);
|
|
7672
|
+
lines.push(``);
|
|
7673
|
+
}
|
|
7674
|
+
if (inProject) {
|
|
7675
|
+
lines.push(`Detected demo.yaml in this directory. Likely next:`);
|
|
7676
|
+
lines.push(``);
|
|
7677
|
+
lines.push(` showrunner doctor -c demo.yaml # preflight checks`);
|
|
7678
|
+
lines.push(` showrunner run -c demo.yaml # run the full pipeline`);
|
|
7679
|
+
lines.push(``);
|
|
7680
|
+
lines.push(`Other commands: \`showrunner --help\``);
|
|
7681
|
+
} else {
|
|
7682
|
+
lines.push(`No demo.yaml here. To scaffold a new project:`);
|
|
7683
|
+
lines.push(``);
|
|
7684
|
+
lines.push(` showrunner init --name my-demo --url http://localhost:3000`);
|
|
7685
|
+
lines.push(``);
|
|
7686
|
+
lines.push(`Then inside the new directory:`);
|
|
7687
|
+
lines.push(``);
|
|
7688
|
+
lines.push(` cd my-demo`);
|
|
7689
|
+
lines.push(` cp .env.example .env # paste provider keys, or use agent_bridge (no keys)`);
|
|
7690
|
+
lines.push(` $EDITOR docs/PRD.md # write your product brief`);
|
|
7691
|
+
lines.push(` showrunner doctor -c demo.yaml # preflight`);
|
|
7692
|
+
lines.push(` showrunner run -c demo.yaml # full pipeline \u2192 output/demo_final.mp4`);
|
|
7693
|
+
lines.push(``);
|
|
7694
|
+
lines.push(`See all commands: \`showrunner --help\``);
|
|
7695
|
+
}
|
|
7696
|
+
lines.push("");
|
|
7697
|
+
process.stdout.write(lines.join("\n"));
|
|
7698
|
+
}
|
|
7699
|
+
async function isChromiumMissing() {
|
|
7700
|
+
try {
|
|
7701
|
+
const { chromium: chromium6 } = await import("playwright-core");
|
|
7702
|
+
const exec = chromium6.executablePath();
|
|
7703
|
+
const { stat: stat15 } = await import("fs/promises");
|
|
7704
|
+
await stat15(exec);
|
|
7705
|
+
return false;
|
|
7706
|
+
} catch {
|
|
7707
|
+
return true;
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7466
7710
|
function parseStages(value) {
|
|
7467
7711
|
const requested = value.split(",").map((s) => s.trim());
|
|
7468
7712
|
const invalid = requested.filter((s) => !stageChoices.includes(s));
|