@kadj-amoah/showrunner 1.1.1 → 1.1.3

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 CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to Showrunner are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project tracks loose semver — minor bumps for new capability, patch for fixes.
4
4
 
5
+ ## [1.1.3] — 2026-05-24
6
+
7
+ UX polish on the post-install discovery path, prompted by author feedback during the CachyOS install smoke-test of v1.1.2 ("the entire welcome wagon needs to be worked on"). No functional change — pure copy + structure.
8
+
9
+ ### Changed
10
+
11
+ - **Bare-command welcome rewritten to one-thing-at-a-time.** Each state (chromium missing, no project here, project here) now shows a single next command and a one-line description, instead of a 15-line wall that duplicated `init`'s footer and crammed multiple ideas per line. The outside-project branch now just points at `showrunner init` and defers all sequencing to `init`'s own output.
12
+ - **`init` next-steps footer tightened.** The previous `cp .env.example .env # then paste in: KEY_A, KEY_B` line conflated three things (file op, key list, no-keys alternative). Now split: step 2 is the `cp`, step 3 lists each required env var on its own line alongside the dashboard URL it's issued from, with the `agent_bridge` alternative as a single parenthetical at the end. Each step is exactly one observable action.
13
+
14
+ ## [1.1.2] — 2026-05-24
15
+
16
+ Hot-fix for a regression discovered while smoke-testing the v1.1.1 install on CachyOS.
17
+
18
+ ### Fixed
19
+
20
+ - **`showrunner --version` crashed on fresh global install with `ERR_MODULE_NOT_FOUND` for `@babel/parser`.** The babel runtime deps (`@babel/parser`, `@babel/traverse`, `@babel/types`) used by the `instrument` stage were in `devDependencies`, so they weren't installed alongside the published package. The CLI bundle loads all subcommand modules eagerly, so the failure surfaced before any subcommand could run. Moved the three packages to `dependencies`. (`@types/babel__traverse` stays in `devDependencies` — type-only.) This bug was almost certainly latent in v1.1.0 as well; nobody hit it because the v1.1.0 install hung on the silent playwright postinstall (the issue v1.1.1 fixed).
21
+
22
+ ### Added
23
+
24
+ - **`showrunner install-browser`** subcommand — wraps the Playwright browser install by shelling out to the bundled `playwright-core`'s CLI directly, which sidesteps the misleading "install your project's dependencies first" warning that plain `npx playwright install` emits when invoked outside a Node project.
25
+ - **Bare-command welcome now detects missing chromium** and prepends a clear "First-time setup: `showrunner install-browser`" hint when the browser cache doesn't have it. Removes the implicit assumption that users will know they need to bootstrap a browser after install.
26
+
5
27
  ## [1.1.1] — 2026-05-24
6
28
 
7
29
  Cross-OS reliability + first-run UX patch. Addresses install-time UX findings from a Linux (CachyOS/Arch) audit of v1.1.0 *and* the post-install "what do I do now?" gaps the author hit when installing on a fresh OS. No behavioural changes to the pipeline itself.
package/dist/cli.js CHANGED
@@ -5660,23 +5660,35 @@ function printNextSteps(projectName, resolved) {
5660
5660
  let step = 1;
5661
5661
  lines.push(` ${step++}. cd ${projectName}`);
5662
5662
  if (envVars.length > 0) {
5663
- const keysList = envVars.join(", ");
5664
- lines.push(` ${step++}. cp .env.example .env # then paste in: ${keysList}`);
5663
+ lines.push(` ${step++}. cp .env.example .env`);
5664
+ lines.push(` ${step++}. Edit .env to fill in:`);
5665
+ for (const v of envVars) {
5666
+ const dash = PROVIDER_DASHBOARDS[v];
5667
+ lines.push(` ${v}${dash ? ` (get it at ${dash})` : ""}`);
5668
+ }
5669
+ lines.push(
5670
+ ` (No keys? Switch llm.default.provider to \`agent_bridge\` in demo.yaml \u2014 uses your local Claude CLI instead.)`
5671
+ );
5665
5672
  } else {
5666
5673
  lines.push(
5667
- ` ${step++}. (no .env needed \u2014 agent_bridge LLM + ${resolved.tts} TTS don't require API keys)`
5674
+ ` ${step++}. (.env not needed \u2014 agent_bridge LLM + ${resolved.tts} TTS don't use API keys.)`
5668
5675
  );
5669
5676
  }
5670
- lines.push(` ${step++}. $EDITOR docs/PRD.md # replace the stub with your product brief`);
5677
+ lines.push(` ${step++}. Edit docs/PRD.md (the stub explains what each section is for).`);
5671
5678
  lines.push(` ${step++}. showrunner doctor -c demo.yaml`);
5672
- lines.push(` ${step++}. showrunner run -c demo.yaml # \u2192 output/demo_final.mp4`);
5679
+ lines.push(` ${step++}. showrunner run -c demo.yaml # \u2192 output/demo_final.mp4`);
5673
5680
  lines.push("");
5674
5681
  lines.push(
5675
- `Optional: \`showrunner understand -c demo.yaml --interactive\` if you'd rather answer five questions than write the PRD upfront.`
5682
+ `Don't want to write a PRD? Run \`showrunner understand -c demo.yaml --interactive\` instead \u2014 it asks 5 questions and builds the product model from your answers.`
5676
5683
  );
5677
5684
  lines.push("");
5678
5685
  process.stdout.write(lines.join("\n"));
5679
5686
  }
5687
+ var PROVIDER_DASHBOARDS = {
5688
+ ANTHROPIC_API_KEY: "https://console.anthropic.com/settings/keys",
5689
+ OPENAI_API_KEY: "https://platform.openai.com/api-keys",
5690
+ ELEVENLABS_API_KEY: "https://elevenlabs.io/app/settings/api-keys"
5691
+ };
5680
5692
  function requiredEnvVars(resolved) {
5681
5693
  const vars = /* @__PURE__ */ new Set();
5682
5694
  if (resolved.llm === "anthropic") vars.add("ANTHROPIC_API_KEY");
@@ -6114,6 +6126,50 @@ and \`at_word\` actions degrade to \`at\`).
6114
6126
  `;
6115
6127
  }
6116
6128
 
6129
+ // src/commands/installBrowser.ts
6130
+ import { spawn as spawn4 } from "child_process";
6131
+ import { access as access3 } from "fs/promises";
6132
+ import { fileURLToPath } from "url";
6133
+ import { dirname as dirname11, join as join11 } from "path";
6134
+ var DEFAULT_BROWSER = "chromium";
6135
+ async function installBrowserCommand(opts) {
6136
+ const browser = opts.browser ?? DEFAULT_BROWSER;
6137
+ const cli = await resolvePlaywrightCoreCli();
6138
+ logger.info(`installing Playwright ${browser} (via bundled playwright-core, no project required)`);
6139
+ const child = spawn4(process.execPath, [cli, "install", browser], {
6140
+ stdio: "inherit",
6141
+ env: process.env
6142
+ });
6143
+ const code = await new Promise((resolve27, reject) => {
6144
+ child.on("error", reject);
6145
+ child.on("close", (c) => resolve27(c ?? 0));
6146
+ });
6147
+ if (code !== 0) {
6148
+ logger.error(`playwright install exited with code ${code}`);
6149
+ process.exit(code);
6150
+ }
6151
+ logger.info(`${browser} installed. Try \`showrunner doctor -c demo.yaml\` next.`);
6152
+ }
6153
+ async function resolvePlaywrightCoreCli() {
6154
+ const here = fileURLToPath(import.meta.url);
6155
+ let dir = dirname11(here);
6156
+ const root = dir.split(/[\\/]/)[0] + "/";
6157
+ while (dir && dir !== root) {
6158
+ const candidate = join11(dir, "node_modules", "playwright-core", "cli.js");
6159
+ try {
6160
+ await access3(candidate);
6161
+ return candidate;
6162
+ } catch {
6163
+ }
6164
+ const parent = dirname11(dir);
6165
+ if (parent === dir) break;
6166
+ dir = parent;
6167
+ }
6168
+ throw new Error(
6169
+ "Could not locate playwright-core CLI inside Showrunner's node_modules. This is a packaging bug \u2014 please file an issue."
6170
+ );
6171
+ }
6172
+
6117
6173
  // src/commands/validate.ts
6118
6174
  import { stat as stat10 } from "fs/promises";
6119
6175
  import { isAbsolute as isAbsolute9, resolve as resolve16 } from "path";
@@ -6266,7 +6322,7 @@ async function pathExists2(p) {
6266
6322
 
6267
6323
  // src/commands/rerunSegment.ts
6268
6324
  import { mkdir as mkdir12, rename as rename3, writeFile as writeFile13 } from "fs/promises";
6269
- import { join as join11, resolve as resolve19 } from "path";
6325
+ import { join as join12, resolve as resolve19 } from "path";
6270
6326
  import { chromium as chromium4, firefox as firefox4, webkit as webkit4 } from "playwright-core";
6271
6327
  var RERUN_BUFFER_MS = 500;
6272
6328
  var browserMap4 = { chromium: chromium4, firefox: firefox4, webkit: webkit4 };
@@ -6385,7 +6441,7 @@ async function rerunSegmentCommand(opts) {
6385
6441
  await page.waitForTimeout(config.recording.segment_buffer_ms);
6386
6442
  const traceDir = resolve19(configDir, config.recording.trace_dir);
6387
6443
  await mkdir12(traceDir, { recursive: true });
6388
- await ctx.tracing.stopChunk({ path: join11(traceDir, `${segment.id}.zip`) });
6444
+ await ctx.tracing.stopChunk({ path: join12(traceDir, `${segment.id}.zip`) });
6389
6445
  const videoHandle = page.video();
6390
6446
  await ctx.close();
6391
6447
  if (!videoHandle) {
@@ -6393,9 +6449,9 @@ async function rerunSegmentCommand(opts) {
6393
6449
  process.exit(1);
6394
6450
  }
6395
6451
  const original = await videoHandle.path();
6396
- const dest = join11(videoDir, `${segment.id}.webm`);
6452
+ const dest = join12(videoDir, `${segment.id}.webm`);
6397
6453
  await rename3(original, dest);
6398
- const metadataPath = join11(videoDir, `${segment.id}.rerun.json`);
6454
+ const metadataPath = join12(videoDir, `${segment.id}.rerun.json`);
6399
6455
  await writeFile13(
6400
6456
  metadataPath,
6401
6457
  JSON.stringify(
@@ -6424,7 +6480,7 @@ async function rerunSegmentCommand(opts) {
6424
6480
 
6425
6481
  // src/commands/captureAuth.ts
6426
6482
  import { mkdir as mkdir14 } from "fs/promises";
6427
- import { dirname as dirname11, isAbsolute as isAbsolute10, resolve as resolve21 } from "path";
6483
+ import { dirname as dirname12, isAbsolute as isAbsolute10, resolve as resolve21 } from "path";
6428
6484
  import { createInterface } from "readline/promises";
6429
6485
 
6430
6486
  // src/recording/headed.ts
@@ -6511,7 +6567,7 @@ async function captureAuthCommand(opts) {
6511
6567
  }
6512
6568
  const cookiesRel = opts.outputCookies ?? "./auth/session.json";
6513
6569
  const cookiesPath = isAbsolute10(cookiesRel) ? cookiesRel : resolve21(loaded.configDir, cookiesRel);
6514
- await mkdir14(dirname11(cookiesPath), { recursive: true });
6570
+ await mkdir14(dirname12(cookiesPath), { recursive: true });
6515
6571
  logger.info("Launching headed browser for auth capture", {
6516
6572
  target: loaded.config.recording.target_url,
6517
6573
  output: cookiesPath
@@ -6552,7 +6608,7 @@ Then re-run \`showrunner run --config <demo.yaml>\`.
6552
6608
 
6553
6609
  // src/commands/trace.ts
6554
6610
  import { readdir as readdir2, stat as stat12 } from "fs/promises";
6555
- import { isAbsolute as isAbsolute11, join as join12, resolve as resolve22 } from "path";
6611
+ import { isAbsolute as isAbsolute11, join as join13, resolve as resolve22 } from "path";
6556
6612
  async function traceCommand(opts) {
6557
6613
  let loaded;
6558
6614
  try {
@@ -6566,7 +6622,7 @@ async function traceCommand(opts) {
6566
6622
  }
6567
6623
  const traceDir = resolve22(loaded.configDir, loaded.config.recording.trace_dir);
6568
6624
  const videoDir = resolve22(loaded.configDir, loaded.config.recording.output_dir);
6569
- const slicePlanPath = join12(videoDir, "slice_plan.json");
6625
+ const slicePlanPath = join13(videoDir, "slice_plan.json");
6570
6626
  if (opts.all) {
6571
6627
  let plan;
6572
6628
  try {
@@ -6577,14 +6633,14 @@ async function traceCommand(opts) {
6577
6633
  process.exit(1);
6578
6634
  }
6579
6635
  for (const seg of plan.segments) {
6580
- const tracePath = seg.trace_path && isAbsolute11(seg.trace_path) ? seg.trace_path : seg.trace_path ? resolve22(loaded.configDir, seg.trace_path) : join12(traceDir, `${seg.id}.zip`);
6636
+ 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`);
6581
6637
  logger.info(`Opening trace for ${seg.id}`, { path: tracePath });
6582
6638
  await openTrace(tracePath);
6583
6639
  }
6584
6640
  return;
6585
6641
  }
6586
6642
  if (opts.segment) {
6587
- const tracePath = join12(traceDir, `${opts.segment}.zip`);
6643
+ const tracePath = join13(traceDir, `${opts.segment}.zip`);
6588
6644
  if (!await fileExists8(tracePath)) {
6589
6645
  logger.error(`No trace found at ${tracePath}`);
6590
6646
  process.exit(1);
@@ -6689,7 +6745,7 @@ async function fileExists9(path) {
6689
6745
 
6690
6746
  // src/commands/understand.ts
6691
6747
  import { mkdir as mkdir15, readFile as readFile10, writeFile as writeFile14 } from "fs/promises";
6692
- import { dirname as dirname12, isAbsolute as isAbsolute12, resolve as resolve24 } from "path";
6748
+ import { dirname as dirname13, isAbsolute as isAbsolute12, resolve as resolve24 } from "path";
6693
6749
 
6694
6750
  // src/productModel/prompts.ts
6695
6751
  var PRODUCT_MODEL_SYSTEM_PROMPT = `You build product_model.json for Showrunner, an automated product demo recorder.
@@ -6894,7 +6950,7 @@ async function understandCommand(opts) {
6894
6950
  }
6895
6951
  }
6896
6952
  const outputPath = isAbsolute12(outputRel) ? outputRel : resolve24(configDir, outputRel);
6897
- await mkdir15(dirname12(outputPath), { recursive: true });
6953
+ await mkdir15(dirname13(outputPath), { recursive: true });
6898
6954
  let productModel;
6899
6955
  const provider = resolveDefaultLLMProvider({ configDir, llm: llmConfig });
6900
6956
  try {
@@ -6942,7 +6998,7 @@ async function understandCommand(opts) {
6942
6998
 
6943
6999
  // src/commands/instrument.ts
6944
7000
  import { mkdir as mkdir16, readdir as readdir3, writeFile as writeFile15 } from "fs/promises";
6945
- import { dirname as dirname13, isAbsolute as isAbsolute13, join as join13, resolve as resolve25, relative as relative2 } from "path";
7001
+ import { dirname as dirname14, isAbsolute as isAbsolute13, join as join14, resolve as resolve25, relative as relative2 } from "path";
6946
7002
 
6947
7003
  // src/instrument/scan.ts
6948
7004
  import { readFile as readFile11 } from "fs/promises";
@@ -7167,7 +7223,7 @@ async function instrumentCommand(opts) {
7167
7223
  async function walk(root, accept) {
7168
7224
  const entries = await readdir3(root, { withFileTypes: true });
7169
7225
  for (const entry of entries) {
7170
- const abs = join13(root, entry.name);
7226
+ const abs = join14(root, entry.name);
7171
7227
  if (entry.isDirectory()) {
7172
7228
  if (IGNORED_DIRS.has(entry.name)) continue;
7173
7229
  await walk(abs, accept);
@@ -7224,7 +7280,7 @@ async function instrumentCommand(opts) {
7224
7280
  logger.warn(`Skipped suggestion at ${s.file}:${s.line} \u2014 ${s.reason}`);
7225
7281
  }
7226
7282
  const outputPath = isAbsolute13(opts.output) ? opts.output : resolve25(loaded.configDir, opts.output);
7227
- await mkdir16(dirname13(outputPath), { recursive: true });
7283
+ await mkdir16(dirname14(outputPath), { recursive: true });
7228
7284
  await writeFile15(outputPath, patch, "utf8");
7229
7285
  logger.info("Wrote instrumentation patch", { path: outputPath, suggestions: suggestions.length });
7230
7286
  process.stdout.write(`
@@ -7559,7 +7615,7 @@ async function fileExists10(path) {
7559
7615
 
7560
7616
  // src/cli.ts
7561
7617
  var program = new Command();
7562
- program.name("showrunner").description("Automated product demo recording & production tool").version("1.1.1").option("--json", "emit structured JSON logs to stdout").option("--log-level <level>", "log level (debug|info|warn|error)").hook("preAction", (thisCmd) => {
7618
+ program.name("showrunner").description("Automated product demo recording & production tool").version("1.1.3").option("--json", "emit structured JSON logs to stdout").option("--log-level <level>", "log level (debug|info|warn|error)").hook("preAction", (thisCmd) => {
7563
7619
  const opts = thisCmd.opts();
7564
7620
  if (opts.json) logger.setJson(true);
7565
7621
  if (opts.logLevel) logger.setLevel(opts.logLevel);
@@ -7592,6 +7648,7 @@ program.command("init").description("Scaffold a new Showrunner project").option(
7592
7648
  "scaffold resolution: low (854x480) | standard (720p) | high (1080p) | extreme (4K)",
7593
7649
  "standard"
7594
7650
  ).action(initCommand);
7651
+ 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);
7595
7652
  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);
7596
7653
  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);
7597
7654
  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);
@@ -7609,41 +7666,51 @@ program.parseAsync(process.argv).catch((err) => {
7609
7666
  process.exit(1);
7610
7667
  });
7611
7668
  async function printWelcome() {
7612
- const { access: access3 } = await import("fs/promises");
7669
+ const { access: access4 } = await import("fs/promises");
7613
7670
  const { resolve: resolve27 } = await import("path");
7614
7671
  const demoYaml = resolve27(process.cwd(), "demo.yaml");
7615
7672
  let inProject = false;
7616
7673
  try {
7617
- await access3(demoYaml);
7674
+ await access4(demoYaml);
7618
7675
  inProject = true;
7619
7676
  } catch {
7620
7677
  }
7621
- const lines = ["", `Showrunner v1.1.1 \u2014 automated product-demo recording & production`, ""];
7622
- if (inProject) {
7623
- lines.push(`Detected demo.yaml in this directory. Likely next:`);
7678
+ const browserMissing = await isChromiumMissing();
7679
+ const lines = ["", `Showrunner v1.1.3`, ""];
7680
+ if (browserMissing) {
7681
+ lines.push(`Showrunner records using Chromium. You haven't installed it yet.`);
7624
7682
  lines.push(``);
7625
- lines.push(` showrunner doctor -c demo.yaml # preflight checks`);
7626
- lines.push(` showrunner run -c demo.yaml # run the full pipeline`);
7683
+ lines.push(` showrunner install-browser`);
7627
7684
  lines.push(``);
7628
- lines.push(`Other commands: \`showrunner --help\``);
7629
- } else {
7630
- lines.push(`No demo.yaml here. To scaffold a new project:`);
7685
+ lines.push(`(~150 MB, one-off. Re-run \`showrunner\` after it finishes for the next step.)`);
7686
+ } else if (inProject) {
7687
+ lines.push(`This is a Showrunner project (found demo.yaml).`);
7631
7688
  lines.push(``);
7632
- lines.push(` showrunner init --name my-demo --url http://localhost:3000`);
7689
+ lines.push(` showrunner doctor -c demo.yaml # check everything is wired correctly`);
7690
+ lines.push(` showrunner run -c demo.yaml # then run the full pipeline`);
7633
7691
  lines.push(``);
7634
- lines.push(`Then inside the new directory:`);
7692
+ lines.push(`Full command list: \`showrunner --help\``);
7693
+ } else {
7694
+ lines.push(`No Showrunner project in this directory. To create one:`);
7635
7695
  lines.push(``);
7636
- lines.push(` cd my-demo`);
7637
- lines.push(` cp .env.example .env # paste provider keys, or use agent_bridge (no keys)`);
7638
- lines.push(` $EDITOR docs/PRD.md # write your product brief`);
7639
- lines.push(` showrunner doctor -c demo.yaml # preflight`);
7640
- lines.push(` showrunner run -c demo.yaml # full pipeline \u2192 output/demo_final.mp4`);
7696
+ lines.push(` showrunner init`);
7641
7697
  lines.push(``);
7642
- lines.push(`See all commands: \`showrunner --help\``);
7698
+ lines.push(`\`init\` scaffolds the project and prints the next 4 commands tailored to your provider choice.`);
7643
7699
  }
7644
7700
  lines.push("");
7645
7701
  process.stdout.write(lines.join("\n"));
7646
7702
  }
7703
+ async function isChromiumMissing() {
7704
+ try {
7705
+ const { chromium: chromium6 } = await import("playwright-core");
7706
+ const exec = chromium6.executablePath();
7707
+ const { stat: stat15 } = await import("fs/promises");
7708
+ await stat15(exec);
7709
+ return false;
7710
+ } catch {
7711
+ return true;
7712
+ }
7713
+ }
7647
7714
  function parseStages(value) {
7648
7715
  const requested = value.split(",").map((s) => s.trim());
7649
7716
  const invalid = requested.filter((s) => !stageChoices.includes(s));