@kadj-amoah/showrunner 1.1.2 → 1.1.4

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/cli.js CHANGED
@@ -1413,10 +1413,10 @@ function extractAndValidate(response, schema) {
1413
1413
  if (response.parsed_output !== void 0 && response.parsed_output !== null) {
1414
1414
  return schema.parse(response.parsed_output);
1415
1415
  }
1416
- const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n").trim();
1416
+ const text2 = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n").trim();
1417
1417
  throw new LLMProviderError(
1418
- text.length > 0 ? `Anthropic returned no parseable structured output. Raw text:
1419
- ${text.slice(0, 2e3)}` : "Anthropic returned an empty response with no parseable output.",
1418
+ text2.length > 0 ? `Anthropic returned no parseable structured output. Raw text:
1419
+ ${text2.slice(0, 2e3)}` : "Anthropic returned an empty response with no parseable output.",
1420
1420
  "anthropic"
1421
1421
  );
1422
1422
  }
@@ -2351,20 +2351,20 @@ function isOriginsList(value) {
2351
2351
  function buildFormFill(auth) {
2352
2352
  return async (page) => {
2353
2353
  const email = process.env[auth.fields.email.env];
2354
- const password = process.env[auth.fields.password.env];
2354
+ const password2 = process.env[auth.fields.password.env];
2355
2355
  if (!email) {
2356
2356
  throw new AuthError(
2357
2357
  `Form auth: env var ${auth.fields.email.env} is not set`
2358
2358
  );
2359
2359
  }
2360
- if (!password) {
2360
+ if (!password2) {
2361
2361
  throw new AuthError(
2362
2362
  `Form auth: env var ${auth.fields.password.env} is not set`
2363
2363
  );
2364
2364
  }
2365
2365
  await page.goto(auth.login_url);
2366
2366
  await page.fill(auth.fields.email.selector, email);
2367
- await page.fill(auth.fields.password.selector, password);
2367
+ await page.fill(auth.fields.password.selector, password2);
2368
2368
  await page.click(auth.submit_selector);
2369
2369
  try {
2370
2370
  await page.waitForURL(auth.success_url_pattern, { timeout: auth.timeout_ms });
@@ -2719,9 +2719,9 @@ async function recordDemo(opts) {
2719
2719
  const page = await context.newPage();
2720
2720
  if (recording.cursor_highlight) {
2721
2721
  page.on("console", (msg) => {
2722
- const text = msg.text();
2723
- if (text.startsWith("[showrunner-cursor]")) {
2724
- logger.debug(`browser: ${text}`);
2722
+ const text2 = msg.text();
2723
+ if (text2.startsWith("[showrunner-cursor]")) {
2724
+ logger.debug(`browser: ${text2}`);
2725
2725
  }
2726
2726
  });
2727
2727
  }
@@ -4602,8 +4602,8 @@ async function fileExists7(path) {
4602
4602
  return false;
4603
4603
  }
4604
4604
  }
4605
- function escapeFilterText(text) {
4606
- return text.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/:/g, "\\:").replace(/,/g, "\\,");
4605
+ function escapeFilterText(text2) {
4606
+ return text2.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/:/g, "\\:").replace(/,/g, "\\,");
4607
4607
  }
4608
4608
  function escapeFilterPath(path) {
4609
4609
  return path.replace(/\\/g, "/").replace(/:/g, "\\\\:");
@@ -4680,14 +4680,14 @@ function chunkAlignmentToCues(alignment, segmentStartSec) {
4680
4680
  let lastBoundaryIdx = -1;
4681
4681
  let i = 0;
4682
4682
  const pushCue = (endIdx) => {
4683
- const text = cueChars.join("").replace(/\s+/g, " ").trim();
4684
- if (text.length === 0) return;
4683
+ const text2 = cueChars.join("").replace(/\s+/g, " ").trim();
4684
+ if (text2.length === 0) return;
4685
4685
  cues.push({
4686
4686
  index: 0,
4687
4687
  // assigned by caller
4688
4688
  startSec: segmentStartSec + cueStart,
4689
4689
  endSec: segmentStartSec + ends[endIdx],
4690
- text
4690
+ text: text2
4691
4691
  });
4692
4692
  };
4693
4693
  while (i < chars.length) {
@@ -5595,8 +5595,294 @@ async function runCommand2(opts) {
5595
5595
  }
5596
5596
 
5597
5597
  // src/commands/init.ts
5598
- import { mkdir as mkdir11, writeFile as writeFile12, access as access2 } from "fs/promises";
5598
+ import { mkdir as mkdir11, writeFile as writeFile12, access as access3 } from "fs/promises";
5599
5599
  import { dirname as dirname10, isAbsolute as isAbsolute8, join as join10, resolve as resolve15 } from "path";
5600
+
5601
+ // src/setup/detect.ts
5602
+ import { spawn as spawn4 } from "child_process";
5603
+ import { access as access2, stat as stat10 } from "fs/promises";
5604
+ async function detectEnvironment() {
5605
+ const [claudeCli, ffmpeg, ffprobe, chromium6] = await Promise.all([
5606
+ isOnPath("claude"),
5607
+ isOnPath("ffmpeg"),
5608
+ isOnPath("ffprobe"),
5609
+ chromiumInstalled()
5610
+ ]);
5611
+ return {
5612
+ claudeCli,
5613
+ ffmpeg,
5614
+ ffprobe,
5615
+ chromium: chromium6,
5616
+ envVars: {
5617
+ anthropic: Boolean(process.env["ANTHROPIC_API_KEY"]),
5618
+ openai: Boolean(process.env["OPENAI_API_KEY"]),
5619
+ elevenlabs: Boolean(process.env["ELEVENLABS_API_KEY"])
5620
+ }
5621
+ };
5622
+ }
5623
+ async function isOnPath(binary) {
5624
+ return new Promise((resolve27) => {
5625
+ const useShell = process.platform === "win32";
5626
+ const child = spawn4(binary, ["--version"], {
5627
+ stdio: ["ignore", "ignore", "ignore"],
5628
+ shell: useShell
5629
+ });
5630
+ let settled = false;
5631
+ const finish = (ok) => {
5632
+ if (settled) return;
5633
+ settled = true;
5634
+ resolve27(ok);
5635
+ };
5636
+ child.on("error", () => finish(false));
5637
+ child.on("exit", (code) => finish(code === 0));
5638
+ setTimeout(() => {
5639
+ if (!settled) {
5640
+ child.kill("SIGKILL");
5641
+ finish(false);
5642
+ }
5643
+ }, 4e3);
5644
+ });
5645
+ }
5646
+ async function chromiumInstalled() {
5647
+ try {
5648
+ const { chromium: chromium6 } = await import("playwright-core");
5649
+ const exec = chromium6.executablePath();
5650
+ await stat10(exec);
5651
+ return true;
5652
+ } catch {
5653
+ return false;
5654
+ }
5655
+ }
5656
+
5657
+ // src/setup/wizard.ts
5658
+ import {
5659
+ intro,
5660
+ outro,
5661
+ text,
5662
+ password,
5663
+ select,
5664
+ confirm,
5665
+ note,
5666
+ spinner,
5667
+ isCancel,
5668
+ cancel
5669
+ } from "@clack/prompts";
5670
+
5671
+ // src/setup/targetProbe.ts
5672
+ import { request as undiciRequest2 } from "undici";
5673
+ var DEFAULT_TIMEOUT_MS = 4e3;
5674
+ async function probeUrl(url, timeoutMs = DEFAULT_TIMEOUT_MS) {
5675
+ const start = Date.now();
5676
+ try {
5677
+ const res = await undiciRequest2(url, {
5678
+ method: "HEAD",
5679
+ bodyTimeout: timeoutMs,
5680
+ headersTimeout: timeoutMs
5681
+ });
5682
+ const elapsedMs = Date.now() - start;
5683
+ return {
5684
+ url,
5685
+ reachable: res.statusCode >= 200 && res.statusCode < 500,
5686
+ statusCode: res.statusCode,
5687
+ elapsedMs
5688
+ };
5689
+ } catch (err) {
5690
+ return {
5691
+ url,
5692
+ reachable: false,
5693
+ reason: err instanceof Error ? err.message : String(err),
5694
+ elapsedMs: Date.now() - start
5695
+ };
5696
+ }
5697
+ }
5698
+
5699
+ // src/setup/wizard.ts
5700
+ var SLUG_RE = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/i;
5701
+ async function runWizard(env) {
5702
+ intro("Showrunner setup");
5703
+ note(formatDetection(env), "Detected on this machine");
5704
+ const projectName = await ask(
5705
+ text({
5706
+ message: "What's the project directory name?",
5707
+ placeholder: "showrunner-demo",
5708
+ defaultValue: "showrunner-demo",
5709
+ validate: (v) => {
5710
+ if (!v) return void 0;
5711
+ if (!SLUG_RE.test(v)) {
5712
+ return "Use letters/digits/dash/underscore/dot, starting & ending with alphanumeric.";
5713
+ }
5714
+ return void 0;
5715
+ }
5716
+ })
5717
+ );
5718
+ if (projectName === null) return null;
5719
+ const resolutionPreset = await ask(
5720
+ select({
5721
+ message: "Recording resolution preset?",
5722
+ options: [
5723
+ { value: "low", label: "low (854\xD7480) \u2014 draft / fast iteration" },
5724
+ { value: "standard", label: "standard (1280\xD7720) \u2014 recommended default" },
5725
+ { value: "high", label: "high (1920\xD71080)" },
5726
+ { value: "extreme", label: "extreme (3840\xD72160) \u2014 needs serious RAM" }
5727
+ ],
5728
+ initialValue: "standard"
5729
+ })
5730
+ );
5731
+ if (resolutionPreset === null) return null;
5732
+ if (!RESOLUTION_PRESETS.includes(resolutionPreset)) {
5733
+ cancel("Invalid resolution preset.");
5734
+ return null;
5735
+ }
5736
+ const llmDefault = env.claudeCli ? "agent_bridge" : "anthropic";
5737
+ const llm = await ask(
5738
+ select({
5739
+ message: "Which LLM provider should drive comprehension + script?",
5740
+ options: [
5741
+ {
5742
+ value: "agent_bridge",
5743
+ label: env.claudeCli ? "agent_bridge \u2014 uses your local `claude` CLI (detected). No API key required." : "agent_bridge \u2014 uses a local headless agent CLI. No API key required."
5744
+ },
5745
+ {
5746
+ value: "anthropic",
5747
+ label: env.envVars.anthropic ? "anthropic \u2014 Claude via API (ANTHROPIC_API_KEY already in env)" : "anthropic \u2014 Claude via API (you'll paste an API key in a moment)"
5748
+ },
5749
+ {
5750
+ value: "openai",
5751
+ label: env.envVars.openai ? "openai \u2014 GPT via API (OPENAI_API_KEY already in env)" : "openai \u2014 GPT via API (you'll paste an API key in a moment)"
5752
+ }
5753
+ ],
5754
+ initialValue: llmDefault
5755
+ })
5756
+ );
5757
+ if (llm === null) return null;
5758
+ const collectedKeys = {};
5759
+ if (llm === "anthropic" && !env.envVars.anthropic) {
5760
+ const key = await askKey("ANTHROPIC_API_KEY", "https://console.anthropic.com/settings/keys");
5761
+ if (key === null) return null;
5762
+ if (key.length > 0) collectedKeys["ANTHROPIC_API_KEY"] = key;
5763
+ }
5764
+ if (llm === "openai" && !env.envVars.openai) {
5765
+ const key = await askKey("OPENAI_API_KEY", "https://platform.openai.com/api-keys");
5766
+ if (key === null) return null;
5767
+ if (key.length > 0) collectedKeys["OPENAI_API_KEY"] = key;
5768
+ }
5769
+ const ttsDefault = llm === "agent_bridge" && !env.envVars.openai && !env.envVars.elevenlabs ? "custom" : "elevenlabs";
5770
+ const tts = await ask(
5771
+ select({
5772
+ message: "Which TTS provider for voiceover?",
5773
+ options: [
5774
+ {
5775
+ value: "elevenlabs",
5776
+ label: env.envVars.elevenlabs ? "elevenlabs \u2014 best alignment (ELEVENLABS_API_KEY already in env)" : "elevenlabs \u2014 best alignment (you'll paste an API key)"
5777
+ },
5778
+ {
5779
+ value: "openai",
5780
+ label: env.envVars.openai || llm === "openai" ? "openai \u2014 tts-1-hd (reuses your OpenAI key)" : "openai \u2014 tts-1-hd (you'll paste an API key)"
5781
+ },
5782
+ {
5783
+ value: "custom",
5784
+ label: "custom \u2014 plug in your own TTSProvider module. No API key required here."
5785
+ }
5786
+ ],
5787
+ initialValue: ttsDefault
5788
+ })
5789
+ );
5790
+ if (tts === null) return null;
5791
+ if (tts === "elevenlabs" && !env.envVars.elevenlabs) {
5792
+ const key = await askKey("ELEVENLABS_API_KEY", "https://elevenlabs.io/app/settings/api-keys");
5793
+ if (key === null) return null;
5794
+ if (key.length > 0) collectedKeys["ELEVENLABS_API_KEY"] = key;
5795
+ }
5796
+ if (tts === "openai" && !env.envVars.openai && !collectedKeys["OPENAI_API_KEY"]) {
5797
+ const key = await askKey("OPENAI_API_KEY", "https://platform.openai.com/api-keys");
5798
+ if (key === null) return null;
5799
+ if (key.length > 0) collectedKeys["OPENAI_API_KEY"] = key;
5800
+ }
5801
+ const url = await ask(
5802
+ text({
5803
+ message: "What URL is your product dev server on?",
5804
+ placeholder: "http://localhost:3000",
5805
+ defaultValue: "http://localhost:3000",
5806
+ validate: (v) => {
5807
+ if (!v) return void 0;
5808
+ try {
5809
+ new URL(v);
5810
+ return void 0;
5811
+ } catch {
5812
+ return "Must be a valid URL (e.g. http://localhost:3000).";
5813
+ }
5814
+ }
5815
+ })
5816
+ );
5817
+ if (url === null) return null;
5818
+ const probeSpinner = spinner();
5819
+ probeSpinner.start(`Probing ${url} ...`);
5820
+ const probe = await probeUrl(url);
5821
+ if (probe.reachable) {
5822
+ probeSpinner.stop(`${url} reachable (HTTP ${probe.statusCode}, ${probe.elapsedMs}ms).`);
5823
+ } else {
5824
+ probeSpinner.stop(`${url} not reachable yet.`);
5825
+ note(
5826
+ `That's fine \u2014 you can start your dev server later. When it's up, run:
5827
+
5828
+ showrunner set-target -c demo.yaml --url ${url}
5829
+
5830
+ to re-probe and update the config.`,
5831
+ "Heads up"
5832
+ );
5833
+ }
5834
+ const proceed = await ask(
5835
+ confirm({
5836
+ message: `Scaffold ${projectName}/ with these choices?`,
5837
+ initialValue: true
5838
+ })
5839
+ );
5840
+ if (proceed === null || proceed === false) {
5841
+ cancel("Setup cancelled. Nothing written.");
5842
+ return null;
5843
+ }
5844
+ outro("Scaffolding now...");
5845
+ return {
5846
+ projectName,
5847
+ url,
5848
+ llm,
5849
+ tts,
5850
+ resolutionPreset,
5851
+ collectedKeys
5852
+ };
5853
+ }
5854
+ async function askKey(envVar, dashUrl) {
5855
+ const value = await ask(
5856
+ password({
5857
+ message: `Paste your ${envVar} (or press enter to skip and fill .env later)`
5858
+ })
5859
+ );
5860
+ if (value === null) return null;
5861
+ return typeof value === "string" ? value.trim() : "";
5862
+ }
5863
+ async function ask(promptResult) {
5864
+ const result = await promptResult;
5865
+ if (isCancel(result)) {
5866
+ cancel("Setup cancelled.");
5867
+ return null;
5868
+ }
5869
+ return result;
5870
+ }
5871
+ function formatDetection(env) {
5872
+ const lines = [];
5873
+ lines.push(`claude CLI: ${env.claudeCli ? "found" : "not found"}`);
5874
+ lines.push(`ffmpeg: ${env.ffmpeg ? "found" : "NOT found \u2190 required for muxing"}`);
5875
+ lines.push(`ffprobe: ${env.ffprobe ? "found" : "NOT found \u2190 required for muxing"}`);
5876
+ lines.push(`chromium: ${env.chromium ? "installed" : "NOT installed \u2190 run `showrunner install-browser`"}`);
5877
+ lines.push("");
5878
+ lines.push("env vars:");
5879
+ lines.push(` ANTHROPIC_API_KEY: ${env.envVars.anthropic ? "set" : "unset"}`);
5880
+ lines.push(` OPENAI_API_KEY: ${env.envVars.openai ? "set" : "unset"}`);
5881
+ lines.push(` ELEVENLABS_API_KEY: ${env.envVars.elevenlabs ? "set" : "unset"}`);
5882
+ return lines.join("\n");
5883
+ }
5884
+
5885
+ // src/commands/init.ts
5600
5886
  var LLM_CHOICES = ["anthropic", "openai", "agent_bridge"];
5601
5887
  var TTS_CHOICES = ["elevenlabs", "openai", "custom"];
5602
5888
  var PLACEHOLDER_FILES = [
@@ -5608,22 +5894,52 @@ var PLACEHOLDER_FILES = [
5608
5894
  "output/.gitkeep"
5609
5895
  ];
5610
5896
  async function initCommand(opts) {
5611
- const resolutionPreset = validateChoice(
5612
- opts.resolution,
5613
- [...RESOLUTION_PRESETS],
5614
- "standard",
5615
- "--resolution"
5616
- );
5617
- const resolved = {
5618
- ...opts,
5619
- llm: validateChoice(opts.llmProvider, LLM_CHOICES, "anthropic", "--llm-provider"),
5620
- tts: validateChoice(opts.ttsProvider, TTS_CHOICES, "elevenlabs", "--tts-provider"),
5621
- resolutionPreset,
5622
- resolution: resolvePreset(resolutionPreset)
5623
- };
5624
- const parent = isAbsolute8(opts.dir) ? opts.dir : resolve15(process.cwd(), opts.dir);
5625
- const projectRoot = join10(parent, opts.name);
5626
- if (!opts.force && await pathExists(projectRoot)) {
5897
+ const wantsInteractive = Boolean(process.stdout.isTTY) && !opts.yes;
5898
+ let resolved;
5899
+ let collectedKeys = {};
5900
+ if (wantsInteractive) {
5901
+ const env = await detectEnvironment();
5902
+ const wiz = await runWizard(env);
5903
+ if (!wiz) return;
5904
+ resolved = {
5905
+ name: wiz.projectName,
5906
+ url: wiz.url,
5907
+ dir: opts.dir,
5908
+ force: opts.force,
5909
+ llm: wiz.llm,
5910
+ tts: wiz.tts,
5911
+ resolutionPreset: wiz.resolutionPreset,
5912
+ resolution: resolvePreset(wiz.resolutionPreset)
5913
+ };
5914
+ collectedKeys = wiz.collectedKeys;
5915
+ } else {
5916
+ const resolutionPreset = validateChoice(
5917
+ opts.resolution,
5918
+ [...RESOLUTION_PRESETS],
5919
+ "standard",
5920
+ "--resolution"
5921
+ );
5922
+ resolved = {
5923
+ ...opts,
5924
+ llm: validateChoice(
5925
+ opts.llmProvider,
5926
+ LLM_CHOICES,
5927
+ "anthropic",
5928
+ "--llm-provider"
5929
+ ),
5930
+ tts: validateChoice(
5931
+ opts.ttsProvider,
5932
+ TTS_CHOICES,
5933
+ "elevenlabs",
5934
+ "--tts-provider"
5935
+ ),
5936
+ resolutionPreset,
5937
+ resolution: resolvePreset(resolutionPreset)
5938
+ };
5939
+ }
5940
+ const parent = isAbsolute8(resolved.dir) ? resolved.dir : resolve15(process.cwd(), resolved.dir);
5941
+ const projectRoot = join10(parent, resolved.name);
5942
+ if (!resolved.force && await pathExists(projectRoot)) {
5627
5943
  logger.error(
5628
5944
  `Directory already exists: ${projectRoot}. Pass --force to overwrite, or choose a different --name/--dir.`
5629
5945
  );
@@ -5651,32 +5967,70 @@ async function initCommand(opts) {
5651
5967
  mode: 493
5652
5968
  });
5653
5969
  await writeFile12(join10(projectRoot, "README.md"), readmeTemplate(resolved), "utf8");
5970
+ if (Object.keys(collectedKeys).length > 0) {
5971
+ await writeFile12(join10(projectRoot, ".env"), buildEnvFile(collectedKeys, resolved), "utf8");
5972
+ }
5654
5973
  logger.info(`Showrunner project scaffolded at ${projectRoot} (llm=${resolved.llm}, tts=${resolved.tts})`);
5655
- printNextSteps(opts.name, resolved);
5974
+ printNextSteps(resolved.name, resolved, collectedKeys);
5975
+ }
5976
+ function buildEnvFile(keys, resolved) {
5977
+ const lines = [`# Showrunner secrets \u2014 generated by \`showrunner init\`.`];
5978
+ for (const [k, v] of Object.entries(keys)) {
5979
+ lines.push(`${k}=${v}`);
5980
+ }
5981
+ lines.push("");
5982
+ lines.push(`# Optional: form / setup-script auth.`);
5983
+ lines.push(`DEMO_EMAIL=`);
5984
+ lines.push(`DEMO_PASSWORD=`);
5985
+ lines.push("");
5986
+ lines.push(`# Optional log level: debug | info | warn | error`);
5987
+ lines.push(`SHOWRUNNER_LOG_LEVEL=info`);
5988
+ lines.push("");
5989
+ if (resolved.llm === "openai" || resolved.tts === "openai") {
5990
+ }
5991
+ return lines.join("\n");
5656
5992
  }
5657
- function printNextSteps(projectName, resolved) {
5993
+ function printNextSteps(projectName, resolved, collectedKeys) {
5658
5994
  const envVars = requiredEnvVars(resolved);
5995
+ const keysProvidedByWizard = Object.keys(collectedKeys);
5996
+ const remainingEnvVars = envVars.filter((v) => !keysProvidedByWizard.includes(v));
5659
5997
  const lines = ["", `Next:`, ""];
5660
5998
  let step = 1;
5661
5999
  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}`);
6000
+ if (envVars.length === 0) {
6001
+ lines.push(
6002
+ ` ${step++}. (.env not needed \u2014 agent_bridge LLM + ${resolved.tts} TTS don't use API keys.)`
6003
+ );
6004
+ } else if (remainingEnvVars.length === 0) {
6005
+ lines.push(` ${step++}. .env is already populated with the keys you pasted.`);
5665
6006
  } else {
6007
+ if (keysProvidedByWizard.length === 0) {
6008
+ lines.push(` ${step++}. cp .env.example .env`);
6009
+ }
6010
+ lines.push(` ${step++}. Edit .env to fill in:`);
6011
+ for (const v of remainingEnvVars) {
6012
+ const dash = PROVIDER_DASHBOARDS[v];
6013
+ lines.push(` ${v}${dash ? ` (get it at ${dash})` : ""}`);
6014
+ }
5666
6015
  lines.push(
5667
- ` ${step++}. (no .env needed \u2014 agent_bridge LLM + ${resolved.tts} TTS don't require API keys)`
6016
+ ` (No keys? Switch llm.default.provider to \`agent_bridge\` in demo.yaml \u2014 uses your local Claude CLI instead.)`
5668
6017
  );
5669
6018
  }
5670
- lines.push(` ${step++}. $EDITOR docs/PRD.md # replace the stub with your product brief`);
6019
+ lines.push(` ${step++}. Edit docs/PRD.md (the stub explains what each section is for).`);
5671
6020
  lines.push(` ${step++}. showrunner doctor -c demo.yaml`);
5672
- lines.push(` ${step++}. showrunner run -c demo.yaml # \u2192 output/demo_final.mp4`);
6021
+ lines.push(` ${step++}. showrunner run -c demo.yaml # \u2192 output/demo_final.mp4`);
5673
6022
  lines.push("");
5674
6023
  lines.push(
5675
- `Optional: \`showrunner understand -c demo.yaml --interactive\` if you'd rather answer five questions than write the PRD upfront.`
6024
+ `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
6025
  );
5677
6026
  lines.push("");
5678
6027
  process.stdout.write(lines.join("\n"));
5679
6028
  }
6029
+ var PROVIDER_DASHBOARDS = {
6030
+ ANTHROPIC_API_KEY: "https://console.anthropic.com/settings/keys",
6031
+ OPENAI_API_KEY: "https://platform.openai.com/api-keys",
6032
+ ELEVENLABS_API_KEY: "https://elevenlabs.io/app/settings/api-keys"
6033
+ };
5680
6034
  function requiredEnvVars(resolved) {
5681
6035
  const vars = /* @__PURE__ */ new Set();
5682
6036
  if (resolved.llm === "anthropic") vars.add("ANTHROPIC_API_KEY");
@@ -5696,7 +6050,7 @@ function validateChoice(value, allowed, defaultValue, flagName) {
5696
6050
  }
5697
6051
  async function pathExists(p) {
5698
6052
  try {
5699
- await access2(p);
6053
+ await access3(p);
5700
6054
  return true;
5701
6055
  } catch {
5702
6056
  return false;
@@ -6115,8 +6469,8 @@ and \`at_word\` actions degrade to \`at\`).
6115
6469
  }
6116
6470
 
6117
6471
  // src/commands/installBrowser.ts
6118
- import { spawn as spawn4 } from "child_process";
6119
- import { access as access3 } from "fs/promises";
6472
+ import { spawn as spawn5 } from "child_process";
6473
+ import { access as access4 } from "fs/promises";
6120
6474
  import { fileURLToPath } from "url";
6121
6475
  import { dirname as dirname11, join as join11 } from "path";
6122
6476
  var DEFAULT_BROWSER = "chromium";
@@ -6124,7 +6478,7 @@ async function installBrowserCommand(opts) {
6124
6478
  const browser = opts.browser ?? DEFAULT_BROWSER;
6125
6479
  const cli = await resolvePlaywrightCoreCli();
6126
6480
  logger.info(`installing Playwright ${browser} (via bundled playwright-core, no project required)`);
6127
- const child = spawn4(process.execPath, [cli, "install", browser], {
6481
+ const child = spawn5(process.execPath, [cli, "install", browser], {
6128
6482
  stdio: "inherit",
6129
6483
  env: process.env
6130
6484
  });
@@ -6145,7 +6499,7 @@ async function resolvePlaywrightCoreCli() {
6145
6499
  while (dir && dir !== root) {
6146
6500
  const candidate = join11(dir, "node_modules", "playwright-core", "cli.js");
6147
6501
  try {
6148
- await access3(candidate);
6502
+ await access4(candidate);
6149
6503
  return candidate;
6150
6504
  } catch {
6151
6505
  }
@@ -6159,7 +6513,7 @@ async function resolvePlaywrightCoreCli() {
6159
6513
  }
6160
6514
 
6161
6515
  // src/commands/validate.ts
6162
- import { stat as stat10 } from "fs/promises";
6516
+ import { stat as stat11 } from "fs/promises";
6163
6517
  import { isAbsolute as isAbsolute9, resolve as resolve16 } from "path";
6164
6518
  async function validateCommand(opts) {
6165
6519
  try {
@@ -6197,7 +6551,7 @@ async function checkReferencedPaths(config, configDir) {
6197
6551
  if (!relPath) return;
6198
6552
  const abs = isAbsolute9(relPath) ? relPath : resolve16(configDir, relPath);
6199
6553
  try {
6200
- await stat10(abs);
6554
+ await stat11(abs);
6201
6555
  } catch {
6202
6556
  warnings.push(`${label} not found: ${abs}`);
6203
6557
  }
@@ -6248,12 +6602,12 @@ async function printVoCommand(opts) {
6248
6602
  }
6249
6603
  throw err;
6250
6604
  }
6251
- const text = renderVoScript(manifest, { projectName: loaded.config.project.name });
6252
- process.stdout.write(text);
6605
+ const text2 = renderVoScript(manifest, { projectName: loaded.config.project.name });
6606
+ process.stdout.write(text2);
6253
6607
  }
6254
6608
 
6255
6609
  // src/commands/approveVo.ts
6256
- import { rm as rm2, stat as stat11 } from "fs/promises";
6610
+ import { rm as rm2, stat as stat12 } from "fs/promises";
6257
6611
  import { resolve as resolve18 } from "path";
6258
6612
  async function approveVoCommand(opts) {
6259
6613
  let loaded;
@@ -6301,7 +6655,7 @@ async function approveVoCommand(opts) {
6301
6655
  }
6302
6656
  async function pathExists2(p) {
6303
6657
  try {
6304
- await stat11(p);
6658
+ await stat12(p);
6305
6659
  return true;
6306
6660
  } catch {
6307
6661
  return false;
@@ -6519,9 +6873,9 @@ async function launchHeadedSession(opts) {
6519
6873
  const page = await context.newPage();
6520
6874
  if (opts.withCursor) {
6521
6875
  page.on("console", (msg) => {
6522
- const text = msg.text();
6523
- if (text.startsWith("[showrunner-cursor]")) {
6524
- logger.debug(`browser: ${text}`);
6876
+ const text2 = msg.text();
6877
+ if (text2.startsWith("[showrunner-cursor]")) {
6878
+ logger.debug(`browser: ${text2}`);
6525
6879
  }
6526
6880
  });
6527
6881
  }
@@ -6595,7 +6949,7 @@ Then re-run \`showrunner run --config <demo.yaml>\`.
6595
6949
  }
6596
6950
 
6597
6951
  // src/commands/trace.ts
6598
- import { readdir as readdir2, stat as stat12 } from "fs/promises";
6952
+ import { readdir as readdir2, stat as stat13 } from "fs/promises";
6599
6953
  import { isAbsolute as isAbsolute11, join as join13, resolve as resolve22 } from "path";
6600
6954
  async function traceCommand(opts) {
6601
6955
  let loaded;
@@ -6677,7 +7031,7 @@ async function listTraces(dir) {
6677
7031
  }
6678
7032
  async function fileExists8(path) {
6679
7033
  try {
6680
- await stat12(path);
7034
+ await stat13(path);
6681
7035
  return true;
6682
7036
  } catch {
6683
7037
  return false;
@@ -6685,7 +7039,7 @@ async function fileExists8(path) {
6685
7039
  }
6686
7040
 
6687
7041
  // src/commands/preview.ts
6688
- import { stat as stat13 } from "fs/promises";
7042
+ import { stat as stat14 } from "fs/promises";
6689
7043
  import { resolve as resolve23 } from "path";
6690
7044
  async function previewCommand(opts) {
6691
7045
  let loaded;
@@ -6724,7 +7078,7 @@ async function previewCommand(opts) {
6724
7078
  }
6725
7079
  async function fileExists9(path) {
6726
7080
  try {
6727
- await stat13(path);
7081
+ await stat14(path);
6728
7082
  return true;
6729
7083
  } catch {
6730
7084
  return false;
@@ -6863,7 +7217,7 @@ var LineReader = class {
6863
7217
  });
6864
7218
  }
6865
7219
  };
6866
- async function ask(reader, prompt) {
7220
+ async function ask2(reader, prompt) {
6867
7221
  process.stdout.write(prompt);
6868
7222
  return reader.read();
6869
7223
  }
@@ -6874,23 +7228,23 @@ async function runInteractiveQA() {
6874
7228
  process.stdout.write(
6875
7229
  "\nLet's build a product_model interactively. Answer each prompt with a short line.\n\n"
6876
7230
  );
6877
- const productName = (await ask(reader, "1. Product name: ")).trim();
7231
+ const productName = (await ask2(reader, "1. Product name: ")).trim();
6878
7232
  if (!productName) throw new Error("product name is required");
6879
- const tagline = (await ask(reader, "2. One-sentence tagline: ")).trim();
6880
- const primaryUser = (await ask(reader, "3. Who is the primary user? ")).trim();
6881
- const featuresLine = (await ask(reader, "4. Top 3-6 features, comma-separated:\n ")).trim();
7233
+ const tagline = (await ask2(reader, "2. One-sentence tagline: ")).trim();
7234
+ const primaryUser = (await ask2(reader, "3. Who is the primary user? ")).trim();
7235
+ const featuresLine = (await ask2(reader, "4. Top 3-6 features, comma-separated:\n ")).trim();
6882
7236
  const topFeatures = featuresLine.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
6883
- const flowCountStr = (await ask(reader, "5. How many flows would you like to demo (2-4)? ")).trim();
7237
+ const flowCountStr = (await ask2(reader, "5. How many flows would you like to demo (2-4)? ")).trim();
6884
7238
  const flowCount = clamp(parseInt(flowCountStr, 10) || 2, 2, 4);
6885
7239
  const topFlows = [];
6886
7240
  for (let i = 0; i < flowCount; i++) {
6887
- const name = (await ask(reader, ` Flow ${i + 1} name: `)).trim();
6888
- const stepsLine = (await ask(reader, ` Flow ${i + 1} steps, '|' separated:
7241
+ const name = (await ask2(reader, ` Flow ${i + 1} name: `)).trim();
7242
+ const stepsLine = (await ask2(reader, ` Flow ${i + 1} steps, '|' separated:
6889
7243
  `)).trim();
6890
7244
  const steps = stepsLine.split("|").map((s) => s.trim()).filter((s) => s.length > 0);
6891
7245
  topFlows.push({ name, steps });
6892
7246
  }
6893
- const durationStr = (await ask(reader, "6. Target demo duration in seconds (60-120, default 75): ")).trim();
7247
+ const durationStr = (await ask2(reader, "6. Target demo duration in seconds (60-120, default 75): ")).trim();
6894
7248
  const suggestedDurationSeconds = clamp(parseInt(durationStr, 10) || 75, 30, 180);
6895
7249
  return {
6896
7250
  productName,
@@ -7280,7 +7634,7 @@ Apply with: cd ${loaded.configDir} && git apply ${opts.output}
7280
7634
  // src/commands/recordActions.ts
7281
7635
  import { createInterface as createInterface3 } from "readline/promises";
7282
7636
  import { resolve as resolve26 } from "path";
7283
- import { stat as stat14 } from "fs/promises";
7637
+ import { stat as stat15 } from "fs/promises";
7284
7638
 
7285
7639
  // src/recording/captureEvents.ts
7286
7640
  async function installCaptureBinding(context, onEvent) {
@@ -7594,7 +7948,7 @@ async function mergeIntoManifest(opts) {
7594
7948
  }
7595
7949
  async function fileExists10(path) {
7596
7950
  try {
7597
- await stat14(path);
7951
+ await stat15(path);
7598
7952
  return true;
7599
7953
  } catch {
7600
7954
  return false;
@@ -7603,7 +7957,7 @@ async function fileExists10(path) {
7603
7957
 
7604
7958
  // src/cli.ts
7605
7959
  var program = new Command();
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) => {
7960
+ program.name("showrunner").description("Automated product demo recording & production tool").version("1.1.4").option("--json", "emit structured JSON logs to stdout").option("--log-level <level>", "log level (debug|info|warn|error)").hook("preAction", (thisCmd) => {
7607
7961
  const opts = thisCmd.opts();
7608
7962
  if (opts.json) logger.setJson(true);
7609
7963
  if (opts.logLevel) logger.setLevel(opts.logLevel);
@@ -7623,7 +7977,7 @@ program.command("run").description("Run the full Showrunner pipeline").requiredO
7623
7977
  "--resolution <preset>",
7624
7978
  "override resolution for this run: low (854x480), standard (720p), high (1080p), extreme (4K)"
7625
7979
  ).action(runCommand2);
7626
- program.command("init").description("Scaffold a new Showrunner project").option("--name <name>", "project name", "showrunner-demo").option("--url <url>", "target URL of the product to demo", "http://localhost:3000").option("--dir <dir>", "parent directory in which to create the project", process.cwd()).option("--force", "overwrite an existing directory", false).option(
7980
+ program.command("init").description("Scaffold a new Showrunner project (interactive by default; use --yes to skip prompts)").option("--name <name>", "project name", "showrunner-demo").option("--url <url>", "target URL of the product to demo", "http://localhost:3000").option("--dir <dir>", "parent directory in which to create the project", process.cwd()).option("--force", "overwrite an existing directory", false).option("--yes", "skip interactive prompts and use defaults / passed flags", false).option(
7627
7981
  "--llm-provider <name>",
7628
7982
  "LLM provider for comprehension + script + instrument (anthropic | openai | agent_bridge)",
7629
7983
  "anthropic"
@@ -7654,44 +8008,36 @@ program.parseAsync(process.argv).catch((err) => {
7654
8008
  process.exit(1);
7655
8009
  });
7656
8010
  async function printWelcome() {
7657
- const { access: access4 } = await import("fs/promises");
8011
+ const { access: access5 } = await import("fs/promises");
7658
8012
  const { resolve: resolve27 } = await import("path");
7659
8013
  const demoYaml = resolve27(process.cwd(), "demo.yaml");
7660
8014
  let inProject = false;
7661
8015
  try {
7662
- await access4(demoYaml);
8016
+ await access5(demoYaml);
7663
8017
  inProject = true;
7664
8018
  } catch {
7665
8019
  }
7666
8020
  const browserMissing = await isChromiumMissing();
7667
- const lines = ["", `Showrunner v1.1.2 \u2014 automated product-demo recording & production`, ""];
8021
+ const lines = ["", `Showrunner v1.1.4`, ""];
7668
8022
  if (browserMissing) {
7669
- lines.push(`First-time setup: install the recording browser (one-off, ~150 MB):`);
8023
+ lines.push(`Showrunner records using Chromium. You haven't installed it yet.`);
7670
8024
  lines.push(``);
7671
- lines.push(` showrunner install-browser # wraps Playwright; no "install dependencies first" warning`);
8025
+ lines.push(` showrunner install-browser`);
7672
8026
  lines.push(``);
7673
- }
7674
- if (inProject) {
7675
- lines.push(`Detected demo.yaml in this directory. Likely next:`);
8027
+ lines.push(`(~150 MB, one-off. Re-run \`showrunner\` after it finishes for the next step.)`);
8028
+ } else if (inProject) {
8029
+ lines.push(`This is a Showrunner project (found demo.yaml).`);
7676
8030
  lines.push(``);
7677
- lines.push(` showrunner doctor -c demo.yaml # preflight checks`);
7678
- lines.push(` showrunner run -c demo.yaml # run the full pipeline`);
8031
+ lines.push(` showrunner doctor -c demo.yaml # check everything is wired correctly`);
8032
+ lines.push(` showrunner run -c demo.yaml # then run the full pipeline`);
7679
8033
  lines.push(``);
7680
- lines.push(`Other commands: \`showrunner --help\``);
8034
+ lines.push(`Full command list: \`showrunner --help\``);
7681
8035
  } 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:`);
8036
+ lines.push(`No Showrunner project in this directory. To create one:`);
7687
8037
  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`);
8038
+ lines.push(` showrunner init`);
7693
8039
  lines.push(``);
7694
- lines.push(`See all commands: \`showrunner --help\``);
8040
+ lines.push(`\`init\` scaffolds the project and prints the next 4 commands tailored to your provider choice.`);
7695
8041
  }
7696
8042
  lines.push("");
7697
8043
  process.stdout.write(lines.join("\n"));
@@ -7700,8 +8046,8 @@ async function isChromiumMissing() {
7700
8046
  try {
7701
8047
  const { chromium: chromium6 } = await import("playwright-core");
7702
8048
  const exec = chromium6.executablePath();
7703
- const { stat: stat15 } = await import("fs/promises");
7704
- await stat15(exec);
8049
+ const { stat: stat16 } = await import("fs/promises");
8050
+ await stat16(exec);
7705
8051
  return false;
7706
8052
  } catch {
7707
8053
  return true;