@kadj-amoah/showrunner 1.1.6 → 1.1.8

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
@@ -76,7 +76,14 @@ import "zod";
76
76
  import { z } from "zod";
77
77
  var projectSchema = z.object({
78
78
  name: z.string().min(1),
79
- product_model: z.string().optional()
79
+ product_model: z.string().optional(),
80
+ /**
81
+ * Path to the product's codebase, resolved relative to demo.yaml's directory.
82
+ * Used by `understand --agent` to anchor the agent's exploration.
83
+ * Defaults to `..` because the canonical scaffold layout places the
84
+ * Showrunner project inside or beside the product directory.
85
+ */
86
+ codebase_root: z.string().optional()
80
87
  });
81
88
  var comprehensionSourceSchema = z.object({
82
89
  type: z.enum(["prd", "readme", "codebase", "openapi", "changelog", "custom"]),
@@ -5928,6 +5935,29 @@ function sleep3(ms) {
5928
5935
  var SLUG_RE = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/i;
5929
5936
  async function runWizard(env) {
5930
5937
  intro("Showrunner setup");
5938
+ note(
5939
+ [
5940
+ `Showrunner will scaffold a new project inside:`,
5941
+ ``,
5942
+ ` ${process.cwd()}`,
5943
+ ``,
5944
+ `That directory should be the root of the product you want to demo (the dir`,
5945
+ `where your package.json / pyproject.toml / etc. lives). If you're somewhere`,
5946
+ `else, cancel with Ctrl+C, \`cd\` to your product's root, and re-run`,
5947
+ `\`showrunner init\` from there.`
5948
+ ].join("\n"),
5949
+ "Heads up \u2014 confirm your starting directory"
5950
+ );
5951
+ const proceedFromCwd = await ask(
5952
+ confirm({
5953
+ message: "Continue scaffolding from this directory?",
5954
+ initialValue: true
5955
+ })
5956
+ );
5957
+ if (proceedFromCwd === null || proceedFromCwd === false) {
5958
+ cancel("Setup cancelled. Re-run `showrunner init` from your product root.");
5959
+ return null;
5960
+ }
5931
5961
  note(formatDetection(env), "Detected on this machine");
5932
5962
  const projectName = await ask(
5933
5963
  text({
@@ -6423,6 +6453,11 @@ function demoYamlTemplate(opts) {
6423
6453
  project:
6424
6454
  name: ${opts.name}
6425
6455
  # product_model: ./product_model.json # uncomment to skip comprehension
6456
+ # Where the product codebase lives, relative to this demo.yaml. Used by
6457
+ # \`showrunner understand --agent\` to anchor the agent's exploration.
6458
+ # Default \`..\` matches the canonical layout where the Showrunner scaffold
6459
+ # sits inside (or beside) the product directory.
6460
+ codebase_root: ..
6426
6461
 
6427
6462
  comprehension:
6428
6463
  mode: documents
@@ -7623,8 +7658,17 @@ async function generateProductModelViaAgent(opts) {
7623
7658
  const provider = new AgentBridgeLLMProvider({
7624
7659
  mode: "spawn",
7625
7660
  command: opts.command ?? "claude",
7626
- args: opts.args ?? ["-p", "--output-format", "json"],
7627
- timeoutMs: opts.timeoutMs ?? 18e4,
7661
+ args: opts.args ?? [
7662
+ "-p",
7663
+ "--output-format",
7664
+ "json",
7665
+ // Explicitly allow read-only filesystem tools so claude doesn't stall on
7666
+ // permission ambiguity in headless mode. Comma-separated per claude CLI syntax.
7667
+ "--allowedTools",
7668
+ "Read,Glob,Grep"
7669
+ ],
7670
+ // 90s cap — was 180s in v1.1.6, dropped here because failures should fail fast.
7671
+ timeoutMs: opts.timeoutMs ?? 9e4,
7628
7672
  cwd: opts.projectDir
7629
7673
  });
7630
7674
  const userPrompt = [
@@ -7748,6 +7792,7 @@ async function understandCommand(opts) {
7748
7792
  let outputRel = opts.output ?? "./product_model.json";
7749
7793
  let sources = [];
7750
7794
  let llmConfig;
7795
+ let loadedCodebaseRoot;
7751
7796
  if (opts.config) {
7752
7797
  let loaded;
7753
7798
  try {
@@ -7765,6 +7810,7 @@ async function understandCommand(opts) {
7765
7810
  }
7766
7811
  sources = loaded.config.comprehension.sources.map((s) => ({ path: s.path, type: s.type }));
7767
7812
  llmConfig = loaded.config.llm;
7813
+ loadedCodebaseRoot = loaded.config.project.codebase_root;
7768
7814
  const envFile = resolve25(loaded.configDir, ".env");
7769
7815
  try {
7770
7816
  process.loadEnvFile(envFile);
@@ -7776,8 +7822,19 @@ async function understandCommand(opts) {
7776
7822
  let productModel;
7777
7823
  try {
7778
7824
  if (opts.agent) {
7779
- const projectDir = process.cwd();
7780
- logger.info("Generating product_model via local `claude` agent", { projectDir });
7825
+ const projectDir = resolveAgentProjectDir({
7826
+ flagOverride: opts.projectDir,
7827
+ codebaseRoot: opts.config ? loadedCodebaseRoot : void 0,
7828
+ configDir: opts.config ? configDir : void 0
7829
+ });
7830
+ logger.info("Generating product_model via local `claude` agent", {
7831
+ projectDir,
7832
+ source: resolveAgentProjectDirSource({
7833
+ flagOverride: opts.projectDir,
7834
+ codebaseRoot: opts.config ? loadedCodebaseRoot : void 0,
7835
+ configDir: opts.config ? configDir : void 0
7836
+ })
7837
+ });
7781
7838
  productModel = await generateProductModelViaAgent({ projectDir });
7782
7839
  } else if (opts.interactive) {
7783
7840
  const answers = await runInteractiveQA();
@@ -7822,6 +7879,24 @@ async function understandCommand(opts) {
7822
7879
  await writeFile15(outputPath, JSON.stringify(productModel, null, 2) + "\n", "utf8");
7823
7880
  logger.info("Wrote product_model.json", { path: outputPath });
7824
7881
  }
7882
+ function resolveAgentProjectDir(input) {
7883
+ if (input.flagOverride) {
7884
+ return isAbsolute13(input.flagOverride) ? input.flagOverride : resolve25(process.cwd(), input.flagOverride);
7885
+ }
7886
+ if (input.codebaseRoot && input.configDir) {
7887
+ return isAbsolute13(input.codebaseRoot) ? input.codebaseRoot : resolve25(input.configDir, input.codebaseRoot);
7888
+ }
7889
+ if (input.configDir) {
7890
+ return resolve25(input.configDir, "..");
7891
+ }
7892
+ return process.cwd();
7893
+ }
7894
+ function resolveAgentProjectDirSource(input) {
7895
+ if (input.flagOverride) return "--project-dir flag";
7896
+ if (input.codebaseRoot && input.configDir) return "project.codebase_root in demo.yaml";
7897
+ if (input.configDir) return "configDir/.. (default; set project.codebase_root to override)";
7898
+ return "cwd (no -c flag)";
7899
+ }
7825
7900
 
7826
7901
  // src/commands/instrument.ts
7827
7902
  import { mkdir as mkdir16, readdir as readdir4, writeFile as writeFile16 } from "fs/promises";
@@ -8442,7 +8517,7 @@ async function fileExists10(path) {
8442
8517
 
8443
8518
  // src/cli.ts
8444
8519
  var program = new Command();
8445
- program.name("showrunner").description("Automated product demo recording & production tool").version("1.1.6").option("--json", "emit structured JSON logs to stdout").option("--log-level <level>", "log level (debug|info|warn|error)").hook("preAction", (thisCmd) => {
8520
+ program.name("showrunner").description("Automated product demo recording & production tool").version("1.1.8").option("--json", "emit structured JSON logs to stdout").option("--log-level <level>", "log level (debug|info|warn|error)").hook("preAction", (thisCmd) => {
8446
8521
  const opts = thisCmd.opts();
8447
8522
  if (opts.json) logger.setJson(true);
8448
8523
  if (opts.logLevel) logger.setLevel(opts.logLevel);
@@ -8481,7 +8556,10 @@ program.command("doctor").description("Run preflight checks on the current confi
8481
8556
  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);
8482
8557
  program.command("understand").description("Build product_model.json from documents, interactive Q&A, or agent-driven repo exploration").option("-c, --config <path>", "path to demo.yaml").option("--interactive", "use interactive Q&A mode (5 prompts, no LLM)").option(
8483
8558
  "--agent",
8484
- "delegate to the local `claude` CLI: it explores the current directory with its read tools and synthesizes the product model. Closes the type=codebase gap in demo.yaml sources."
8559
+ "delegate to the local `claude` CLI: it explores the project with its read tools and synthesizes the product model. Closes the type=codebase gap in demo.yaml sources."
8560
+ ).option(
8561
+ "--project-dir <path>",
8562
+ "directory the --agent run should explore. Overrides project.codebase_root from demo.yaml. Defaults to configDir/.. when -c is given, or cwd otherwise."
8485
8563
  ).option("--output <path>", "output path for product_model.json").action(understandCommand);
8486
8564
  program.command("instrument").description("Suggest data-testid attributes for a codebase").requiredOption("-c, --config <path>", "path to demo.yaml").requiredOption("--output <path>", "unified diff output path").option("--glob <pattern>", "override comprehension.sources with an ad-hoc glob (relative to configDir)").action(instrumentCommand);
8487
8565
  program.command("record-actions").description("Author manifest actions by demonstrating them in a live browser").requiredOption("-c, --config <path>", "path to demo.yaml").option("--segment <id>", "replace actions for an existing segment id").option("--output <path>", "manifest output path (default ./scripts/manifest.json)").action(recordActionsCommand);
@@ -8506,14 +8584,28 @@ async function printWelcome() {
8506
8584
  inProject = true;
8507
8585
  } catch {
8508
8586
  }
8509
- const browserMissing = await isChromiumMissing();
8510
- const lines = ["", `Showrunner v1.1.6`, ""];
8511
- if (browserMissing) {
8512
- lines.push(`Showrunner records using Chromium. You haven't installed it yet.`);
8587
+ const missing = await detectMissingPrereqs();
8588
+ const anyMissing = missing.ffmpeg || missing.ffprobe || missing.chromium;
8589
+ const lines = ["", `Showrunner v1.1.8`, ""];
8590
+ if (anyMissing) {
8591
+ lines.push(`First-time setup. Showrunner needs these system tools:`);
8513
8592
  lines.push(``);
8514
- lines.push(` showrunner install-browser`);
8515
- lines.push(``);
8516
- lines.push(`(~150 MB, one-off. Re-run \`showrunner\` after it finishes for the next step.)`);
8593
+ if (missing.ffmpeg || missing.ffprobe) {
8594
+ const which = missing.ffmpeg && missing.ffprobe ? "ffmpeg / ffprobe" : missing.ffmpeg ? "ffmpeg" : "ffprobe";
8595
+ lines.push(` ${which} \u2014 install via your OS package manager:`);
8596
+ lines.push(` Linux (apt): sudo apt install ffmpeg`);
8597
+ lines.push(` Linux (pacman): sudo pacman -S ffmpeg`);
8598
+ lines.push(` Linux (dnf): sudo dnf install ffmpeg`);
8599
+ lines.push(` macOS: brew install ffmpeg`);
8600
+ lines.push(` Windows: winget install Gyan.FFmpeg`);
8601
+ lines.push(``);
8602
+ }
8603
+ if (missing.chromium) {
8604
+ lines.push(` chromium recording browser \u2014 install via:`);
8605
+ lines.push(` showrunner install-browser`);
8606
+ lines.push(``);
8607
+ }
8608
+ lines.push(`Re-run \`showrunner\` once those are in place.`);
8517
8609
  } else if (inProject) {
8518
8610
  lines.push(`This is a Showrunner project (found demo.yaml).`);
8519
8611
  lines.push(``);
@@ -8524,22 +8616,60 @@ async function printWelcome() {
8524
8616
  } else {
8525
8617
  lines.push(`No Showrunner project in this directory. To create one:`);
8526
8618
  lines.push(``);
8527
- lines.push(` showrunner init`);
8619
+ lines.push(` cd <your-product's-root-directory> # the dir with package.json / etc.`);
8620
+ lines.push(` showrunner init # then run init from THERE`);
8528
8621
  lines.push(``);
8529
- lines.push(`\`init\` scaffolds the project and prints the next 4 commands tailored to your provider choice.`);
8622
+ lines.push(`\`init\` scaffolds inside cwd, so cwd must be your product. The wizard will`);
8623
+ lines.push(`confirm the path before doing anything destructive.`);
8530
8624
  }
8531
8625
  lines.push("");
8532
8626
  process.stdout.write(lines.join("\n"));
8533
8627
  }
8534
- async function isChromiumMissing() {
8628
+ async function detectMissingPrereqs() {
8629
+ const [ffmpegOk, ffprobeOk, chromiumOk] = await Promise.all([
8630
+ binaryOnPath("ffmpeg"),
8631
+ binaryOnPath("ffprobe"),
8632
+ chromiumInstalled2()
8633
+ ]);
8634
+ return {
8635
+ ffmpeg: !ffmpegOk,
8636
+ ffprobe: !ffprobeOk,
8637
+ chromium: !chromiumOk
8638
+ };
8639
+ }
8640
+ async function binaryOnPath(name) {
8641
+ const { spawn: spawn7 } = await import("child_process");
8642
+ return new Promise((resolve28) => {
8643
+ const useShell = process.platform === "win32";
8644
+ const child = spawn7(name, ["-version"], {
8645
+ stdio: ["ignore", "ignore", "ignore"],
8646
+ shell: useShell
8647
+ });
8648
+ let settled = false;
8649
+ const finish = (ok) => {
8650
+ if (settled) return;
8651
+ settled = true;
8652
+ resolve28(ok);
8653
+ };
8654
+ child.on("error", () => finish(false));
8655
+ child.on("exit", (code) => finish(code === 0));
8656
+ setTimeout(() => {
8657
+ if (!settled) {
8658
+ child.kill("SIGKILL");
8659
+ finish(false);
8660
+ }
8661
+ }, 3e3);
8662
+ });
8663
+ }
8664
+ async function chromiumInstalled2() {
8535
8665
  try {
8536
8666
  const { chromium: chromium6 } = await import("playwright-core");
8537
8667
  const exec = chromium6.executablePath();
8538
8668
  const { stat: stat17 } = await import("fs/promises");
8539
8669
  await stat17(exec);
8540
- return false;
8541
- } catch {
8542
8670
  return true;
8671
+ } catch {
8672
+ return false;
8543
8673
  }
8544
8674
  }
8545
8675
  function parseStages(value) {