@kadj-amoah/showrunner 1.1.4 → 1.1.5

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
@@ -1824,7 +1824,7 @@ async function fileExists2(p) {
1824
1824
  }
1825
1825
  }
1826
1826
  function sleep(ms) {
1827
- return new Promise((resolve27) => setTimeout(resolve27, ms));
1827
+ return new Promise((resolve28) => setTimeout(resolve28, ms));
1828
1828
  }
1829
1829
 
1830
1830
  // src/providers/llm/custom.ts
@@ -5317,14 +5317,14 @@ function printRow(r) {
5317
5317
  else logger.info(line);
5318
5318
  }
5319
5319
  async function checkBinary(name, args) {
5320
- return new Promise((resolve27) => {
5320
+ return new Promise((resolve28) => {
5321
5321
  const child = spawn3(name, args, { stdio: ["ignore", "pipe", "pipe"] });
5322
5322
  let stdout = "";
5323
5323
  child.stdout?.on("data", (chunk) => {
5324
5324
  stdout += chunk.toString("utf8");
5325
5325
  });
5326
5326
  child.on("error", () => {
5327
- resolve27({
5327
+ resolve28({
5328
5328
  status: "FAIL",
5329
5329
  label: `${name} not on PATH`,
5330
5330
  detail: installHintFor(name)
@@ -5333,9 +5333,9 @@ async function checkBinary(name, args) {
5333
5333
  child.on("exit", (code) => {
5334
5334
  if (code === 0) {
5335
5335
  const firstLine = stdout.split("\n")[0]?.trim() ?? "";
5336
- resolve27({ status: "PASS", label: `${name} present`, detail: firstLine });
5336
+ resolve28({ status: "PASS", label: `${name} present`, detail: firstLine });
5337
5337
  } else {
5338
- resolve27({
5338
+ resolve28({
5339
5339
  status: "FAIL",
5340
5340
  label: `${name} exited with code ${code}`,
5341
5341
  detail: installHintFor(name)
@@ -5596,7 +5596,7 @@ async function runCommand2(opts) {
5596
5596
 
5597
5597
  // src/commands/init.ts
5598
5598
  import { mkdir as mkdir11, writeFile as writeFile12, access as access3 } from "fs/promises";
5599
- import { dirname as dirname10, isAbsolute as isAbsolute8, join as join10, resolve as resolve15 } from "path";
5599
+ import { dirname as dirname10, isAbsolute as isAbsolute8, join as join11, resolve as resolve15 } from "path";
5600
5600
 
5601
5601
  // src/setup/detect.ts
5602
5602
  import { spawn as spawn4 } from "child_process";
@@ -5621,7 +5621,7 @@ async function detectEnvironment() {
5621
5621
  };
5622
5622
  }
5623
5623
  async function isOnPath(binary) {
5624
- return new Promise((resolve27) => {
5624
+ return new Promise((resolve28) => {
5625
5625
  const useShell = process.platform === "win32";
5626
5626
  const child = spawn4(binary, ["--version"], {
5627
5627
  stdio: ["ignore", "ignore", "ignore"],
@@ -5631,7 +5631,7 @@ async function isOnPath(binary) {
5631
5631
  const finish = (ok) => {
5632
5632
  if (settled) return;
5633
5633
  settled = true;
5634
- resolve27(ok);
5634
+ resolve28(ok);
5635
5635
  };
5636
5636
  child.on("error", () => finish(false));
5637
5637
  child.on("exit", (code) => finish(code === 0));
@@ -5696,6 +5696,234 @@ async function probeUrl(url, timeoutMs = DEFAULT_TIMEOUT_MS) {
5696
5696
  }
5697
5697
  }
5698
5698
 
5699
+ // src/setup/portScan.ts
5700
+ var COMMON_DEV_PORTS = [3e3, 3001, 4321, 5173, 5174, 8e3, 8080];
5701
+ async function scanLocalPorts(host = "localhost", ports = COMMON_DEV_PORTS, timeoutMs = 800) {
5702
+ const probes = ports.map(async (port) => {
5703
+ const url = `http://${host}:${port}`;
5704
+ const result = await probeUrl(url, timeoutMs);
5705
+ if (!result.reachable) return null;
5706
+ return {
5707
+ url,
5708
+ port,
5709
+ statusCode: result.statusCode ?? 0,
5710
+ elapsedMs: result.elapsedMs ?? 0
5711
+ };
5712
+ });
5713
+ const all = await Promise.all(probes);
5714
+ return all.filter((x) => x !== null).sort((a, b) => a.port - b.port);
5715
+ }
5716
+
5717
+ // src/setup/agentDiscover.ts
5718
+ import { z as z9 } from "zod";
5719
+
5720
+ // src/setup/projectSnapshot.ts
5721
+ import { readFile as readFile10, readdir as readdir2, stat as stat11 } from "fs/promises";
5722
+ import { join as join10, relative } from "path";
5723
+ var FILE_CAP_BYTES = 2048;
5724
+ var README_LINE_CAP = 100;
5725
+ var CONFIG_FILE_PATTERNS = [
5726
+ /^vite\.config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5727
+ /^next\.config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5728
+ /^astro\.config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5729
+ /^nuxt\.config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5730
+ /^svelte\.config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5731
+ /^vue\.config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5732
+ /^webpack\.config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5733
+ /^remix\.config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5734
+ /^gatsby-config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5735
+ /^rsbuild\.config\.(?:js|mjs|cjs|ts|mts|cts)$/,
5736
+ /^manage\.py$/,
5737
+ /^pyproject\.toml$/,
5738
+ /^Cargo\.toml$/
5739
+ ];
5740
+ async function collectProjectSnapshot(projectDir) {
5741
+ const sections = [];
5742
+ const pkg = await readFileSafe(join10(projectDir, "package.json"));
5743
+ if (pkg) sections.push({ label: "package.json", path: "package.json", body: pkg });
5744
+ for (const candidate of ["README.md", "readme.md", "README.MD"]) {
5745
+ const r = await readFileSafe(join10(projectDir, candidate), { lineCap: README_LINE_CAP });
5746
+ if (r) {
5747
+ sections.push({ label: "README (head)", path: candidate, body: r });
5748
+ break;
5749
+ }
5750
+ }
5751
+ const envEx = await readFileSafe(join10(projectDir, ".env.example"));
5752
+ if (envEx) sections.push({ label: ".env.example", path: ".env.example", body: envEx });
5753
+ else {
5754
+ const envSample = await readFileSafe(join10(projectDir, ".env.sample"));
5755
+ if (envSample) sections.push({ label: ".env.sample", path: ".env.sample", body: envSample });
5756
+ }
5757
+ let topEntries;
5758
+ try {
5759
+ topEntries = await readdir2(projectDir);
5760
+ } catch {
5761
+ topEntries = [];
5762
+ }
5763
+ for (const entry of topEntries) {
5764
+ if (CONFIG_FILE_PATTERNS.some((re) => re.test(entry))) {
5765
+ const body = await readFileSafe(join10(projectDir, entry));
5766
+ if (body) sections.push({ label: entry, path: entry, body });
5767
+ }
5768
+ }
5769
+ return { projectDir, sections };
5770
+ }
5771
+ function renderSnapshot(snap) {
5772
+ if (snap.sections.length === 0) {
5773
+ return `(empty or non-readable project at ${snap.projectDir})`;
5774
+ }
5775
+ const out = [];
5776
+ for (const s of snap.sections) {
5777
+ out.push(`### ${s.label} \u2014 ${s.path}`);
5778
+ out.push("```");
5779
+ out.push(s.body.trim());
5780
+ out.push("```");
5781
+ out.push("");
5782
+ }
5783
+ return out.join("\n");
5784
+ }
5785
+ async function readFileSafe(path, options = {}) {
5786
+ try {
5787
+ const s = await stat11(path);
5788
+ if (!s.isFile()) return null;
5789
+ } catch {
5790
+ return null;
5791
+ }
5792
+ let text2;
5793
+ try {
5794
+ text2 = await readFile10(path, "utf8");
5795
+ } catch {
5796
+ return null;
5797
+ }
5798
+ if (options.lineCap !== void 0) {
5799
+ const lines = text2.split("\n");
5800
+ if (lines.length > options.lineCap) {
5801
+ text2 = lines.slice(0, options.lineCap).join("\n") + `
5802
+ \u2026[truncated at ${options.lineCap} lines]`;
5803
+ }
5804
+ }
5805
+ const byteCap = options.byteCap ?? FILE_CAP_BYTES;
5806
+ if (Buffer.byteLength(text2, "utf8") > byteCap) {
5807
+ text2 = text2.slice(0, byteCap) + `
5808
+ \u2026[truncated at ${byteCap} bytes]`;
5809
+ }
5810
+ return text2;
5811
+ }
5812
+
5813
+ // src/setup/agentDiscover.ts
5814
+ var DevServerProposalSchema = z9.object({
5815
+ command: z9.string().min(1).describe('Executable name, e.g. "npm" or "pnpm".'),
5816
+ args: z9.array(z9.string()).describe('CLI arguments, e.g. ["run", "dev"].'),
5817
+ url: z9.string().url().describe("URL the dev server will listen on once started."),
5818
+ confidence: z9.enum(["high", "medium", "low"]),
5819
+ rationale: z9.string().min(1).describe("One short sentence on why this is the right command.")
5820
+ });
5821
+ var SYSTEM_PROMPT = [
5822
+ "You are helping the Showrunner CLI identify how to start a project's development server.",
5823
+ "You will be given a project snapshot (package.json, README head, common framework config files, .env.example).",
5824
+ "",
5825
+ "Your job: identify the command + arguments to run the dev server, and the URL it will listen on.",
5826
+ "",
5827
+ "Rules:",
5828
+ '- Use the scripts in package.json as your primary signal. Prefer "dev" > "start" > "serve" when present.',
5829
+ "- For Vite default port = 5173; Next/CRA = 3000; Astro = 4321; Vue CLI = 8080; Django = 8000.",
5830
+ "- If the project explicitly sets a port (via env, config, or CLI flags), use that, NOT the framework default.",
5831
+ '- If the snapshot is too sparse to tell, set confidence to "low" and use your best guess for both command and URL.',
5832
+ '- "confidence" reflects how sure you are. "high" = explicit script + config-confirmed port; "medium" = explicit script, default port; "low" = guessing.',
5833
+ '- "rationale" is one short sentence, plain English. No marketing speak.',
5834
+ "",
5835
+ "Return only the structured output. No prose outside the JSON."
5836
+ ].join("\n");
5837
+ async function discoverDevServer(projectDir, opts = {}) {
5838
+ const snapshot = await collectProjectSnapshot(projectDir);
5839
+ const rendered = renderSnapshot(snapshot);
5840
+ const provider = new AgentBridgeLLMProvider({
5841
+ mode: "spawn",
5842
+ command: opts.command ?? "claude",
5843
+ args: opts.args ?? ["-p", "--output-format", "json"],
5844
+ timeoutMs: opts.timeoutMs ?? 9e4
5845
+ });
5846
+ const userPrompt = [
5847
+ `Project directory: ${projectDir}`,
5848
+ "",
5849
+ "Project snapshot follows:",
5850
+ "",
5851
+ rendered,
5852
+ "",
5853
+ "Identify the dev-server command + args + URL."
5854
+ ].join("\n");
5855
+ return await provider.generateStructured({
5856
+ systemPrompt: SYSTEM_PROMPT,
5857
+ userPrompt,
5858
+ schema: DevServerProposalSchema,
5859
+ schemaName: "DevServerProposal"
5860
+ });
5861
+ }
5862
+
5863
+ // src/setup/spawnDevServer.ts
5864
+ import { spawn as spawn5 } from "child_process";
5865
+ async function spawnAndWait(opts) {
5866
+ const waitTimeoutMs = opts.waitTimeoutMs ?? 6e4;
5867
+ const pollIntervalMs = opts.pollIntervalMs ?? 750;
5868
+ const useShell = process.platform === "win32";
5869
+ let child;
5870
+ try {
5871
+ child = spawn5(opts.command, opts.args, {
5872
+ cwd: opts.cwd,
5873
+ detached: true,
5874
+ stdio: "ignore",
5875
+ shell: useShell
5876
+ });
5877
+ child.unref();
5878
+ } catch (err) {
5879
+ return {
5880
+ ok: false,
5881
+ command: opts.command,
5882
+ args: opts.args,
5883
+ url: opts.url,
5884
+ reason: `failed to spawn: ${err instanceof Error ? err.message : String(err)}`
5885
+ };
5886
+ }
5887
+ const pid = child.pid;
5888
+ const start = Date.now();
5889
+ let lastReason = "";
5890
+ while (Date.now() - start < waitTimeoutMs) {
5891
+ if (child.exitCode !== null) {
5892
+ return {
5893
+ ok: false,
5894
+ pid,
5895
+ command: opts.command,
5896
+ args: opts.args,
5897
+ url: opts.url,
5898
+ reason: `child process exited with code ${child.exitCode} before the URL came up`
5899
+ };
5900
+ }
5901
+ const probe = await probeUrl(opts.url, pollIntervalMs);
5902
+ if (probe.reachable) {
5903
+ return {
5904
+ ok: true,
5905
+ pid,
5906
+ command: opts.command,
5907
+ args: opts.args,
5908
+ url: opts.url
5909
+ };
5910
+ }
5911
+ lastReason = probe.reason ?? `HTTP ${probe.statusCode ?? "?"}`;
5912
+ await sleep3(pollIntervalMs);
5913
+ }
5914
+ return {
5915
+ ok: false,
5916
+ pid,
5917
+ command: opts.command,
5918
+ args: opts.args,
5919
+ url: opts.url,
5920
+ reason: `timed out after ${waitTimeoutMs}ms waiting for ${opts.url} (last: ${lastReason}). Child still running as pid ${pid}.`
5921
+ };
5922
+ }
5923
+ function sleep3(ms) {
5924
+ return new Promise((r) => setTimeout(r, ms));
5925
+ }
5926
+
5699
5927
  // src/setup/wizard.ts
5700
5928
  var SLUG_RE = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/i;
5701
5929
  async function runWizard(env) {
@@ -5798,39 +6026,13 @@ async function runWizard(env) {
5798
6026
  if (key === null) return null;
5799
6027
  if (key.length > 0) collectedKeys["OPENAI_API_KEY"] = key;
5800
6028
  }
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
- }
6029
+ const resolved = await resolveTargetUrl({
6030
+ env,
6031
+ llm,
6032
+ projectDir: process.cwd()
6033
+ });
6034
+ if (resolved === null) return null;
6035
+ const url = resolved.url;
5834
6036
  const proceed = await ask(
5835
6037
  confirm({
5836
6038
  message: `Scaffold ${projectName}/ with these choices?`,
@@ -5881,6 +6083,164 @@ function formatDetection(env) {
5881
6083
  lines.push(` ELEVENLABS_API_KEY: ${env.envVars.elevenlabs ? "set" : "unset"}`);
5882
6084
  return lines.join("\n");
5883
6085
  }
6086
+ async function resolveTargetUrl(input) {
6087
+ const { env, llm, projectDir } = input;
6088
+ const initialUrl = await ask(
6089
+ text({
6090
+ message: "What URL is your product dev server on?",
6091
+ placeholder: "http://localhost:3000",
6092
+ defaultValue: "http://localhost:3000",
6093
+ validate: (v) => {
6094
+ if (!v) return void 0;
6095
+ try {
6096
+ new URL(v);
6097
+ return void 0;
6098
+ } catch {
6099
+ return "Must be a valid URL (e.g. http://localhost:3000).";
6100
+ }
6101
+ }
6102
+ })
6103
+ );
6104
+ if (initialUrl === null) return null;
6105
+ const probeSpin = spinner();
6106
+ probeSpin.start(`Probing ${initialUrl} ...`);
6107
+ const directProbe = await probeUrl(initialUrl);
6108
+ if (directProbe.reachable) {
6109
+ probeSpin.stop(`${initialUrl} reachable (HTTP ${directProbe.statusCode}, ${directProbe.elapsedMs}ms).`);
6110
+ return { url: initialUrl };
6111
+ }
6112
+ probeSpin.stop(`${initialUrl} not reachable yet.`);
6113
+ const scanSpin = spinner();
6114
+ scanSpin.start("Scanning common dev-server ports on localhost...");
6115
+ let scanHits = [];
6116
+ try {
6117
+ scanHits = await scanLocalPorts("localhost");
6118
+ } catch {
6119
+ }
6120
+ if (scanHits.length > 0) {
6121
+ scanSpin.stop(`Found ${scanHits.length} responding port${scanHits.length === 1 ? "" : "s"} on localhost.`);
6122
+ const pick = await ask(
6123
+ select({
6124
+ message: "Use one of the running servers?",
6125
+ options: [
6126
+ ...scanHits.map((h) => ({
6127
+ value: h.url,
6128
+ label: `${h.url} (HTTP ${h.statusCode})`
6129
+ })),
6130
+ { value: "__keep__", label: `No \u2014 keep what I typed (${initialUrl})` },
6131
+ { value: "__agent__", label: "No \u2014 let the agent figure it out (only useful with agent_bridge + claude)" }
6132
+ ],
6133
+ initialValue: scanHits[0]?.url ?? "__keep__"
6134
+ })
6135
+ );
6136
+ if (pick === null) return null;
6137
+ if (pick !== "__keep__" && pick !== "__agent__") {
6138
+ return { url: pick };
6139
+ }
6140
+ if (pick === "__keep__") {
6141
+ warnFallback(initialUrl);
6142
+ return { url: initialUrl, warnedUnreachable: true };
6143
+ }
6144
+ } else {
6145
+ scanSpin.stop("No common dev ports responding on localhost.");
6146
+ }
6147
+ const agentAvailable = llm === "agent_bridge" && env.claudeCli;
6148
+ if (!agentAvailable) {
6149
+ if (scanHits.length === 0) {
6150
+ warnFallback(initialUrl);
6151
+ return { url: initialUrl, warnedUnreachable: true };
6152
+ }
6153
+ return { url: initialUrl, warnedUnreachable: true };
6154
+ }
6155
+ const useAgent = await ask(
6156
+ confirm({
6157
+ message: "Want me to inspect this directory with the `claude` agent and propose a dev-server command + URL?",
6158
+ initialValue: true
6159
+ })
6160
+ );
6161
+ if (useAgent === null) return null;
6162
+ if (useAgent === false) {
6163
+ warnFallback(initialUrl);
6164
+ return { url: initialUrl, warnedUnreachable: true };
6165
+ }
6166
+ const discoverSpin = spinner();
6167
+ discoverSpin.start("Asking the agent to read your project...");
6168
+ let proposal;
6169
+ try {
6170
+ proposal = await discoverDevServer(projectDir);
6171
+ discoverSpin.stop("Agent returned a proposal.");
6172
+ } catch (err) {
6173
+ discoverSpin.stop("Agent inspection failed.");
6174
+ note(
6175
+ `${err instanceof Error ? err.message : String(err)}
6176
+
6177
+ Keeping your typed URL for now. Start your server, then run:
6178
+ showrunner set-target -c demo.yaml --url ${initialUrl}`,
6179
+ "Agent error"
6180
+ );
6181
+ return { url: initialUrl, warnedUnreachable: true };
6182
+ }
6183
+ note(formatProposal(proposal, projectDir), "Agent proposal");
6184
+ const spawnIt = await ask(
6185
+ confirm({
6186
+ message: `Spawn \`${proposal.command} ${proposal.args.join(" ")}\` in ${projectDir} and wait for ${proposal.url}?`,
6187
+ initialValue: proposal.confidence !== "low"
6188
+ })
6189
+ );
6190
+ if (spawnIt === null) return null;
6191
+ if (spawnIt === false) {
6192
+ note(
6193
+ `OK \u2014 start the server yourself, then run:
6194
+ showrunner set-target -c demo.yaml --url ${proposal.url}`,
6195
+ "Skipping spawn"
6196
+ );
6197
+ return { url: proposal.url, warnedUnreachable: true };
6198
+ }
6199
+ const spawnSpin = spinner();
6200
+ spawnSpin.start(`Spawning and waiting for ${proposal.url} (up to 60s)...`);
6201
+ const spawnResult = await spawnAndWait({
6202
+ command: proposal.command,
6203
+ args: proposal.args,
6204
+ url: proposal.url,
6205
+ cwd: projectDir
6206
+ });
6207
+ if (spawnResult.ok) {
6208
+ spawnSpin.stop(`Dev server up at ${proposal.url} (pid ${spawnResult.pid}).`);
6209
+ note(
6210
+ `When you're done, stop it with: kill ${spawnResult.pid}`,
6211
+ "Server running"
6212
+ );
6213
+ return { url: proposal.url, spawnedPid: spawnResult.pid };
6214
+ }
6215
+ spawnSpin.stop(`Spawn or wait failed.`);
6216
+ note(
6217
+ `${spawnResult.reason ?? "unknown error"}
6218
+
6219
+ Keeping ${proposal.url} as the configured target. If the server eventually comes up, no action needed; otherwise run:
6220
+ showrunner set-target -c demo.yaml --url <actual-url>`,
6221
+ "Spawn issue"
6222
+ );
6223
+ return { url: proposal.url, warnedUnreachable: true };
6224
+ }
6225
+ function warnFallback(url) {
6226
+ note(
6227
+ `That's fine \u2014 you can start your dev server later. When it's up, run:
6228
+
6229
+ showrunner set-target -c demo.yaml --url ${url}
6230
+
6231
+ to re-probe and update the config.`,
6232
+ "Heads up"
6233
+ );
6234
+ }
6235
+ function formatProposal(p, cwd) {
6236
+ return [
6237
+ `command: ${p.command} ${p.args.join(" ")}`,
6238
+ `cwd: ${cwd}`,
6239
+ `url: ${p.url}`,
6240
+ `confidence: ${p.confidence}`,
6241
+ `rationale: ${p.rationale}`
6242
+ ].join("\n");
6243
+ }
5884
6244
 
5885
6245
  // src/commands/init.ts
5886
6246
  var LLM_CHOICES = ["anthropic", "openai", "agent_bridge"];
@@ -5938,7 +6298,7 @@ async function initCommand(opts) {
5938
6298
  };
5939
6299
  }
5940
6300
  const parent = isAbsolute8(resolved.dir) ? resolved.dir : resolve15(process.cwd(), resolved.dir);
5941
- const projectRoot = join10(parent, resolved.name);
6301
+ const projectRoot = join11(parent, resolved.name);
5942
6302
  if (!resolved.force && await pathExists(projectRoot)) {
5943
6303
  logger.error(
5944
6304
  `Directory already exists: ${projectRoot}. Pass --force to overwrite, or choose a different --name/--dir.`
@@ -5947,28 +6307,28 @@ async function initCommand(opts) {
5947
6307
  }
5948
6308
  await mkdir11(projectRoot, { recursive: true });
5949
6309
  for (const rel of PLACEHOLDER_FILES) {
5950
- const dest = join10(projectRoot, rel);
6310
+ const dest = join11(projectRoot, rel);
5951
6311
  await mkdir11(dirname10(dest), { recursive: true });
5952
6312
  await writeFile12(dest, "", "utf8");
5953
6313
  }
5954
- await writeFile12(join10(projectRoot, "demo.yaml"), demoYamlTemplate(resolved), "utf8");
5955
- await writeFile12(join10(projectRoot, ".env.example"), envExampleTemplate(resolved), "utf8");
5956
- await writeFile12(join10(projectRoot, ".gitignore"), gitignoreTemplate(), "utf8");
5957
- await mkdir11(join10(projectRoot, "docs"), { recursive: true });
5958
- await writeFile12(join10(projectRoot, "docs/PRD.md"), prdStubTemplate(resolved), "utf8");
5959
- await writeFile12(join10(projectRoot, "scripts/manifest.json"), starterManifest(resolved), "utf8");
5960
- await writeFile12(join10(projectRoot, "scripts/seed_demo_data.sh"), seedScript(), {
6314
+ await writeFile12(join11(projectRoot, "demo.yaml"), demoYamlTemplate(resolved), "utf8");
6315
+ await writeFile12(join11(projectRoot, ".env.example"), envExampleTemplate(resolved), "utf8");
6316
+ await writeFile12(join11(projectRoot, ".gitignore"), gitignoreTemplate(), "utf8");
6317
+ await mkdir11(join11(projectRoot, "docs"), { recursive: true });
6318
+ await writeFile12(join11(projectRoot, "docs/PRD.md"), prdStubTemplate(resolved), "utf8");
6319
+ await writeFile12(join11(projectRoot, "scripts/manifest.json"), starterManifest(resolved), "utf8");
6320
+ await writeFile12(join11(projectRoot, "scripts/seed_demo_data.sh"), seedScript(), {
5961
6321
  mode: 493
5962
6322
  });
5963
- await writeFile12(join10(projectRoot, "scripts/reset_demo_data.sh"), resetScript(), {
6323
+ await writeFile12(join11(projectRoot, "scripts/reset_demo_data.sh"), resetScript(), {
5964
6324
  mode: 493
5965
6325
  });
5966
- await writeFile12(join10(projectRoot, "scripts/teardown.sh"), teardownScript(), {
6326
+ await writeFile12(join11(projectRoot, "scripts/teardown.sh"), teardownScript(), {
5967
6327
  mode: 493
5968
6328
  });
5969
- await writeFile12(join10(projectRoot, "README.md"), readmeTemplate(resolved), "utf8");
6329
+ await writeFile12(join11(projectRoot, "README.md"), readmeTemplate(resolved), "utf8");
5970
6330
  if (Object.keys(collectedKeys).length > 0) {
5971
- await writeFile12(join10(projectRoot, ".env"), buildEnvFile(collectedKeys, resolved), "utf8");
6331
+ await writeFile12(join11(projectRoot, ".env"), buildEnvFile(collectedKeys, resolved), "utf8");
5972
6332
  }
5973
6333
  logger.info(`Showrunner project scaffolded at ${projectRoot} (llm=${resolved.llm}, tts=${resolved.tts})`);
5974
6334
  printNextSteps(resolved.name, resolved, collectedKeys);
@@ -6469,22 +6829,22 @@ and \`at_word\` actions degrade to \`at\`).
6469
6829
  }
6470
6830
 
6471
6831
  // src/commands/installBrowser.ts
6472
- import { spawn as spawn5 } from "child_process";
6832
+ import { spawn as spawn6 } from "child_process";
6473
6833
  import { access as access4 } from "fs/promises";
6474
6834
  import { fileURLToPath } from "url";
6475
- import { dirname as dirname11, join as join11 } from "path";
6835
+ import { dirname as dirname11, join as join12 } from "path";
6476
6836
  var DEFAULT_BROWSER = "chromium";
6477
6837
  async function installBrowserCommand(opts) {
6478
6838
  const browser = opts.browser ?? DEFAULT_BROWSER;
6479
6839
  const cli = await resolvePlaywrightCoreCli();
6480
6840
  logger.info(`installing Playwright ${browser} (via bundled playwright-core, no project required)`);
6481
- const child = spawn5(process.execPath, [cli, "install", browser], {
6841
+ const child = spawn6(process.execPath, [cli, "install", browser], {
6482
6842
  stdio: "inherit",
6483
6843
  env: process.env
6484
6844
  });
6485
- const code = await new Promise((resolve27, reject) => {
6845
+ const code = await new Promise((resolve28, reject) => {
6486
6846
  child.on("error", reject);
6487
- child.on("close", (c) => resolve27(c ?? 0));
6847
+ child.on("close", (c) => resolve28(c ?? 0));
6488
6848
  });
6489
6849
  if (code !== 0) {
6490
6850
  logger.error(`playwright install exited with code ${code}`);
@@ -6497,7 +6857,7 @@ async function resolvePlaywrightCoreCli() {
6497
6857
  let dir = dirname11(here);
6498
6858
  const root = dir.split(/[\\/]/)[0] + "/";
6499
6859
  while (dir && dir !== root) {
6500
- const candidate = join11(dir, "node_modules", "playwright-core", "cli.js");
6860
+ const candidate = join12(dir, "node_modules", "playwright-core", "cli.js");
6501
6861
  try {
6502
6862
  await access4(candidate);
6503
6863
  return candidate;
@@ -6512,9 +6872,60 @@ async function resolvePlaywrightCoreCli() {
6512
6872
  );
6513
6873
  }
6514
6874
 
6515
- // src/commands/validate.ts
6516
- import { stat as stat11 } from "fs/promises";
6875
+ // src/commands/setTarget.ts
6876
+ import { readFile as readFile11, writeFile as writeFile13 } from "fs/promises";
6517
6877
  import { isAbsolute as isAbsolute9, resolve as resolve16 } from "path";
6878
+ import yaml2 from "js-yaml";
6879
+ async function setTargetCommand(opts) {
6880
+ try {
6881
+ new URL(opts.url);
6882
+ } catch {
6883
+ logger.error(`Invalid URL: ${opts.url}`);
6884
+ process.exit(2);
6885
+ }
6886
+ try {
6887
+ await loadConfig(opts.config);
6888
+ } catch (err) {
6889
+ if (err instanceof ConfigError) {
6890
+ logger.error(err.message);
6891
+ process.exit(2);
6892
+ }
6893
+ throw err;
6894
+ }
6895
+ if (!opts.force) {
6896
+ logger.info(`probing ${opts.url} ...`);
6897
+ const probe = await probeUrl(opts.url);
6898
+ if (!probe.reachable) {
6899
+ logger.error(
6900
+ `target ${opts.url} not reachable${probe.reason ? ` (${probe.reason})` : ""}. Start your dev server first, or pass --force to set the URL anyway.`
6901
+ );
6902
+ process.exit(1);
6903
+ }
6904
+ logger.info(`reachable (HTTP ${probe.statusCode}, ${probe.elapsedMs}ms).`);
6905
+ }
6906
+ const absPath = isAbsolute9(opts.config) ? opts.config : resolve16(process.cwd(), opts.config);
6907
+ const rawText = await readFile11(absPath, "utf8");
6908
+ const doc = yaml2.load(rawText);
6909
+ if (!doc || typeof doc !== "object" || !doc["recording"] || typeof doc["recording"] !== "object") {
6910
+ logger.error(
6911
+ `config has no \`recording\` block \u2014 has it been edited by hand into an invalid shape?`
6912
+ );
6913
+ process.exit(2);
6914
+ }
6915
+ doc["recording"]["target_url"] = opts.url;
6916
+ const newText = yaml2.dump(doc, {
6917
+ lineWidth: 100,
6918
+ noRefs: true,
6919
+ sortKeys: false
6920
+ });
6921
+ await writeFile13(absPath, newText, "utf8");
6922
+ logger.info(`updated recording.target_url \u2192 ${opts.url}`);
6923
+ logger.info(`(comments in demo.yaml may have been stripped \u2014 re-add them if needed.)`);
6924
+ }
6925
+
6926
+ // src/commands/validate.ts
6927
+ import { stat as stat12 } from "fs/promises";
6928
+ import { isAbsolute as isAbsolute10, resolve as resolve17 } from "path";
6518
6929
  async function validateCommand(opts) {
6519
6930
  try {
6520
6931
  process.loadEnvFile?.();
@@ -6549,9 +6960,9 @@ async function checkReferencedPaths(config, configDir) {
6549
6960
  const warnings = [];
6550
6961
  const check = async (relPath, label) => {
6551
6962
  if (!relPath) return;
6552
- const abs = isAbsolute9(relPath) ? relPath : resolve16(configDir, relPath);
6963
+ const abs = isAbsolute10(relPath) ? relPath : resolve17(configDir, relPath);
6553
6964
  try {
6554
- await stat11(abs);
6965
+ await stat12(abs);
6555
6966
  } catch {
6556
6967
  warnings.push(`${label} not found: ${abs}`);
6557
6968
  }
@@ -6579,7 +6990,7 @@ async function checkReferencedPaths(config, configDir) {
6579
6990
  }
6580
6991
 
6581
6992
  // src/commands/printVo.ts
6582
- import { resolve as resolve17 } from "path";
6993
+ import { resolve as resolve18 } from "path";
6583
6994
  async function printVoCommand(opts) {
6584
6995
  let loaded;
6585
6996
  try {
@@ -6591,7 +7002,7 @@ async function printVoCommand(opts) {
6591
7002
  }
6592
7003
  throw err;
6593
7004
  }
6594
- const manifestPath = resolve17(loaded.configDir, "./scripts/manifest.json");
7005
+ const manifestPath = resolve18(loaded.configDir, "./scripts/manifest.json");
6595
7006
  let manifest;
6596
7007
  try {
6597
7008
  manifest = await readManifest(manifestPath);
@@ -6607,8 +7018,8 @@ async function printVoCommand(opts) {
6607
7018
  }
6608
7019
 
6609
7020
  // src/commands/approveVo.ts
6610
- import { rm as rm2, stat as stat12 } from "fs/promises";
6611
- import { resolve as resolve18 } from "path";
7021
+ import { rm as rm2, stat as stat13 } from "fs/promises";
7022
+ import { resolve as resolve19 } from "path";
6612
7023
  async function approveVoCommand(opts) {
6613
7024
  let loaded;
6614
7025
  try {
@@ -6620,9 +7031,9 @@ async function approveVoCommand(opts) {
6620
7031
  }
6621
7032
  throw err;
6622
7033
  }
6623
- const manifestPath = resolve18(loaded.configDir, "./scripts/manifest.json");
6624
- const voScriptPath = resolve18(loaded.configDir, "./scripts/vo_script.txt");
6625
- const lockPath = resolve18(loaded.configDir, ".showrunner-lock");
7034
+ const manifestPath = resolve19(loaded.configDir, "./scripts/manifest.json");
7035
+ const voScriptPath = resolve19(loaded.configDir, "./scripts/vo_script.txt");
7036
+ const lockPath = resolve19(loaded.configDir, ".showrunner-lock");
6626
7037
  let manifest;
6627
7038
  try {
6628
7039
  manifest = await readManifest(manifestPath);
@@ -6655,7 +7066,7 @@ async function approveVoCommand(opts) {
6655
7066
  }
6656
7067
  async function pathExists2(p) {
6657
7068
  try {
6658
- await stat12(p);
7069
+ await stat13(p);
6659
7070
  return true;
6660
7071
  } catch {
6661
7072
  return false;
@@ -6663,8 +7074,8 @@ async function pathExists2(p) {
6663
7074
  }
6664
7075
 
6665
7076
  // src/commands/rerunSegment.ts
6666
- import { mkdir as mkdir12, rename as rename3, writeFile as writeFile13 } from "fs/promises";
6667
- import { join as join12, resolve as resolve19 } from "path";
7077
+ import { mkdir as mkdir12, rename as rename3, writeFile as writeFile14 } from "fs/promises";
7078
+ import { join as join13, resolve as resolve20 } from "path";
6668
7079
  import { chromium as chromium4, firefox as firefox4, webkit as webkit4 } from "playwright-core";
6669
7080
  var RERUN_BUFFER_MS = 500;
6670
7081
  var browserMap4 = { chromium: chromium4, firefox: firefox4, webkit: webkit4 };
@@ -6680,8 +7091,8 @@ async function rerunSegmentCommand(opts) {
6680
7091
  throw err;
6681
7092
  }
6682
7093
  const { config, configDir } = loaded;
6683
- const manifestPath = resolve19(configDir, "./scripts/manifest.json");
6684
- const videoDir = resolve19(configDir, config.recording.output_dir);
7094
+ const manifestPath = resolve20(configDir, "./scripts/manifest.json");
7095
+ const videoDir = resolve20(configDir, config.recording.output_dir);
6685
7096
  let manifest;
6686
7097
  try {
6687
7098
  manifest = await readManifest(manifestPath);
@@ -6781,9 +7192,9 @@ async function rerunSegmentCommand(opts) {
6781
7192
  }
6782
7193
  }
6783
7194
  await page.waitForTimeout(config.recording.segment_buffer_ms);
6784
- const traceDir = resolve19(configDir, config.recording.trace_dir);
7195
+ const traceDir = resolve20(configDir, config.recording.trace_dir);
6785
7196
  await mkdir12(traceDir, { recursive: true });
6786
- await ctx.tracing.stopChunk({ path: join12(traceDir, `${segment.id}.zip`) });
7197
+ await ctx.tracing.stopChunk({ path: join13(traceDir, `${segment.id}.zip`) });
6787
7198
  const videoHandle = page.video();
6788
7199
  await ctx.close();
6789
7200
  if (!videoHandle) {
@@ -6791,10 +7202,10 @@ async function rerunSegmentCommand(opts) {
6791
7202
  process.exit(1);
6792
7203
  }
6793
7204
  const original = await videoHandle.path();
6794
- const dest = join12(videoDir, `${segment.id}.webm`);
7205
+ const dest = join13(videoDir, `${segment.id}.webm`);
6795
7206
  await rename3(original, dest);
6796
- const metadataPath = join12(videoDir, `${segment.id}.rerun.json`);
6797
- await writeFile13(
7207
+ const metadataPath = join13(videoDir, `${segment.id}.rerun.json`);
7208
+ await writeFile14(
6798
7209
  metadataPath,
6799
7210
  JSON.stringify(
6800
7211
  {
@@ -6822,12 +7233,12 @@ async function rerunSegmentCommand(opts) {
6822
7233
 
6823
7234
  // src/commands/captureAuth.ts
6824
7235
  import { mkdir as mkdir14 } from "fs/promises";
6825
- import { dirname as dirname12, isAbsolute as isAbsolute10, resolve as resolve21 } from "path";
7236
+ import { dirname as dirname12, isAbsolute as isAbsolute11, resolve as resolve22 } from "path";
6826
7237
  import { createInterface } from "readline/promises";
6827
7238
 
6828
7239
  // src/recording/headed.ts
6829
7240
  import { mkdir as mkdir13 } from "fs/promises";
6830
- import { resolve as resolve20 } from "path";
7241
+ import { resolve as resolve21 } from "path";
6831
7242
  import {
6832
7243
  chromium as chromium5,
6833
7244
  firefox as firefox5,
@@ -6841,7 +7252,7 @@ async function launchHeadedSession(opts) {
6841
7252
  viewport: { width: recording.viewport.width, height: recording.viewport.height }
6842
7253
  };
6843
7254
  if (opts.recordVideo) {
6844
- const videoDir = resolve20(configDir, recording.output_dir);
7255
+ const videoDir = resolve21(configDir, recording.output_dir);
6845
7256
  await mkdir13(videoDir, { recursive: true });
6846
7257
  contextOptions.recordVideo = {
6847
7258
  dir: videoDir,
@@ -6902,13 +7313,13 @@ async function captureAuthCommand(opts) {
6902
7313
  }
6903
7314
  throw err;
6904
7315
  }
6905
- const envFile = resolve21(loaded.configDir, ".env");
7316
+ const envFile = resolve22(loaded.configDir, ".env");
6906
7317
  try {
6907
7318
  process.loadEnvFile(envFile);
6908
7319
  } catch {
6909
7320
  }
6910
7321
  const cookiesRel = opts.outputCookies ?? "./auth/session.json";
6911
- const cookiesPath = isAbsolute10(cookiesRel) ? cookiesRel : resolve21(loaded.configDir, cookiesRel);
7322
+ const cookiesPath = isAbsolute11(cookiesRel) ? cookiesRel : resolve22(loaded.configDir, cookiesRel);
6912
7323
  await mkdir14(dirname12(cookiesPath), { recursive: true });
6913
7324
  logger.info("Launching headed browser for auth capture", {
6914
7325
  target: loaded.config.recording.target_url,
@@ -6949,8 +7360,8 @@ Then re-run \`showrunner run --config <demo.yaml>\`.
6949
7360
  }
6950
7361
 
6951
7362
  // src/commands/trace.ts
6952
- import { readdir as readdir2, stat as stat13 } from "fs/promises";
6953
- import { isAbsolute as isAbsolute11, join as join13, resolve as resolve22 } from "path";
7363
+ import { readdir as readdir3, stat as stat14 } from "fs/promises";
7364
+ import { isAbsolute as isAbsolute12, join as join14, resolve as resolve23 } from "path";
6954
7365
  async function traceCommand(opts) {
6955
7366
  let loaded;
6956
7367
  try {
@@ -6962,9 +7373,9 @@ async function traceCommand(opts) {
6962
7373
  }
6963
7374
  throw err;
6964
7375
  }
6965
- const traceDir = resolve22(loaded.configDir, loaded.config.recording.trace_dir);
6966
- const videoDir = resolve22(loaded.configDir, loaded.config.recording.output_dir);
6967
- const slicePlanPath = join13(videoDir, "slice_plan.json");
7376
+ const traceDir = resolve23(loaded.configDir, loaded.config.recording.trace_dir);
7377
+ const videoDir = resolve23(loaded.configDir, loaded.config.recording.output_dir);
7378
+ const slicePlanPath = join14(videoDir, "slice_plan.json");
6968
7379
  if (opts.all) {
6969
7380
  let plan;
6970
7381
  try {
@@ -6975,14 +7386,14 @@ async function traceCommand(opts) {
6975
7386
  process.exit(1);
6976
7387
  }
6977
7388
  for (const seg of plan.segments) {
6978
- 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`);
7389
+ const tracePath = seg.trace_path && isAbsolute12(seg.trace_path) ? seg.trace_path : seg.trace_path ? resolve23(loaded.configDir, seg.trace_path) : join14(traceDir, `${seg.id}.zip`);
6979
7390
  logger.info(`Opening trace for ${seg.id}`, { path: tracePath });
6980
7391
  await openTrace(tracePath);
6981
7392
  }
6982
7393
  return;
6983
7394
  }
6984
7395
  if (opts.segment) {
6985
- const tracePath = join13(traceDir, `${opts.segment}.zip`);
7396
+ const tracePath = join14(traceDir, `${opts.segment}.zip`);
6986
7397
  if (!await fileExists8(tracePath)) {
6987
7398
  logger.error(`No trace found at ${tracePath}`);
6988
7399
  process.exit(1);
@@ -7023,7 +7434,7 @@ async function openTrace(tracePath) {
7023
7434
  }
7024
7435
  async function listTraces(dir) {
7025
7436
  try {
7026
- const entries = await readdir2(dir);
7437
+ const entries = await readdir3(dir);
7027
7438
  return entries.filter((e) => e.endsWith(".zip")).sort();
7028
7439
  } catch {
7029
7440
  return [];
@@ -7031,7 +7442,7 @@ async function listTraces(dir) {
7031
7442
  }
7032
7443
  async function fileExists8(path) {
7033
7444
  try {
7034
- await stat13(path);
7445
+ await stat14(path);
7035
7446
  return true;
7036
7447
  } catch {
7037
7448
  return false;
@@ -7039,8 +7450,8 @@ async function fileExists8(path) {
7039
7450
  }
7040
7451
 
7041
7452
  // src/commands/preview.ts
7042
- import { stat as stat14 } from "fs/promises";
7043
- import { resolve as resolve23 } from "path";
7453
+ import { stat as stat15 } from "fs/promises";
7454
+ import { resolve as resolve24 } from "path";
7044
7455
  async function previewCommand(opts) {
7045
7456
  let loaded;
7046
7457
  try {
@@ -7052,7 +7463,7 @@ async function previewCommand(opts) {
7052
7463
  }
7053
7464
  throw err;
7054
7465
  }
7055
- const previewSpec = resolve23(loaded.configDir, "./scripts/playwright_demo.spec.ts");
7466
+ const previewSpec = resolve24(loaded.configDir, "./scripts/playwright_demo.spec.ts");
7056
7467
  if (!await fileExists9(previewSpec)) {
7057
7468
  logger.error(
7058
7469
  `No preview spec at ${previewSpec}. Run \`showrunner run --stages script\` first to generate it.`
@@ -7078,7 +7489,7 @@ async function previewCommand(opts) {
7078
7489
  }
7079
7490
  async function fileExists9(path) {
7080
7491
  try {
7081
- await stat14(path);
7492
+ await stat15(path);
7082
7493
  return true;
7083
7494
  } catch {
7084
7495
  return false;
@@ -7086,8 +7497,8 @@ async function fileExists9(path) {
7086
7497
  }
7087
7498
 
7088
7499
  // src/commands/understand.ts
7089
- import { mkdir as mkdir15, readFile as readFile10, writeFile as writeFile14 } from "fs/promises";
7090
- import { dirname as dirname13, isAbsolute as isAbsolute12, resolve as resolve24 } from "path";
7500
+ import { mkdir as mkdir15, readFile as readFile12, writeFile as writeFile15 } from "fs/promises";
7501
+ import { dirname as dirname13, isAbsolute as isAbsolute13, resolve as resolve25 } from "path";
7091
7502
 
7092
7503
  // src/productModel/prompts.ts
7093
7504
  var PRODUCT_MODEL_SYSTEM_PROMPT = `You build product_model.json for Showrunner, an automated product demo recorder.
@@ -7211,8 +7622,8 @@ var LineReader = class {
7211
7622
  if (this.closed) {
7212
7623
  return Promise.reject(new Error("stdin closed before answer was provided"));
7213
7624
  }
7214
- return new Promise((resolve27, reject) => {
7215
- this.waiter = resolve27;
7625
+ return new Promise((resolve28, reject) => {
7626
+ this.waiter = resolve28;
7216
7627
  this.rejecter = reject;
7217
7628
  });
7218
7629
  }
@@ -7285,13 +7696,13 @@ async function understandCommand(opts) {
7285
7696
  }
7286
7697
  sources = loaded.config.comprehension.sources.map((s) => ({ path: s.path, type: s.type }));
7287
7698
  llmConfig = loaded.config.llm;
7288
- const envFile = resolve24(loaded.configDir, ".env");
7699
+ const envFile = resolve25(loaded.configDir, ".env");
7289
7700
  try {
7290
7701
  process.loadEnvFile(envFile);
7291
7702
  } catch {
7292
7703
  }
7293
7704
  }
7294
- const outputPath = isAbsolute12(outputRel) ? outputRel : resolve24(configDir, outputRel);
7705
+ const outputPath = isAbsolute13(outputRel) ? outputRel : resolve25(configDir, outputRel);
7295
7706
  await mkdir15(dirname13(outputPath), { recursive: true });
7296
7707
  let productModel;
7297
7708
  const provider = resolveDefaultLLMProvider({ configDir, llm: llmConfig });
@@ -7309,9 +7720,9 @@ async function understandCommand(opts) {
7309
7720
  }
7310
7721
  const docs = [];
7311
7722
  for (const src of sources) {
7312
- const abs = isAbsolute12(src.path) ? src.path : resolve24(configDir, src.path);
7723
+ const abs = isAbsolute13(src.path) ? src.path : resolve25(configDir, src.path);
7313
7724
  try {
7314
- const content = await readFile10(abs, "utf8");
7725
+ const content = await readFile12(abs, "utf8");
7315
7726
  docs.push({ path: src.path, type: src.type, content });
7316
7727
  } catch (err) {
7317
7728
  const cause = err instanceof Error ? err.message : String(err);
@@ -7334,16 +7745,16 @@ async function understandCommand(opts) {
7334
7745
  }
7335
7746
  throw err;
7336
7747
  }
7337
- await writeFile14(outputPath, JSON.stringify(productModel, null, 2) + "\n", "utf8");
7748
+ await writeFile15(outputPath, JSON.stringify(productModel, null, 2) + "\n", "utf8");
7338
7749
  logger.info("Wrote product_model.json", { path: outputPath });
7339
7750
  }
7340
7751
 
7341
7752
  // src/commands/instrument.ts
7342
- import { mkdir as mkdir16, readdir as readdir3, writeFile as writeFile15 } from "fs/promises";
7343
- import { dirname as dirname14, isAbsolute as isAbsolute13, join as join14, resolve as resolve25, relative as relative2 } from "path";
7753
+ import { mkdir as mkdir16, readdir as readdir4, writeFile as writeFile16 } from "fs/promises";
7754
+ import { dirname as dirname14, isAbsolute as isAbsolute14, join as join15, resolve as resolve26, relative as relative3 } from "path";
7344
7755
 
7345
7756
  // src/instrument/scan.ts
7346
- import { readFile as readFile11 } from "fs/promises";
7757
+ import { readFile as readFile13 } from "fs/promises";
7347
7758
  import { parse } from "@babel/parser";
7348
7759
  import _traverse from "@babel/traverse";
7349
7760
  var traverse = _traverse.default ?? _traverse;
@@ -7361,7 +7772,7 @@ var TARGET_TAGS = /* @__PURE__ */ new Set([
7361
7772
  "Form"
7362
7773
  ]);
7363
7774
  async function scanFile(filePath) {
7364
- const source = await readFile11(filePath, "utf8");
7775
+ const source = await readFile13(filePath, "utf8");
7365
7776
  let ast;
7366
7777
  try {
7367
7778
  ast = parse(source, {
@@ -7420,23 +7831,23 @@ function pickAttr(node, attr) {
7420
7831
  }
7421
7832
 
7422
7833
  // src/instrument/suggest.ts
7423
- import { z as z9 } from "zod";
7834
+ import { z as z10 } from "zod";
7424
7835
  var DEFAULT_MAX_TOKENS5 = 8e3;
7425
- var suggestionSchema = z9.object({
7426
- suggestions: z9.array(
7427
- z9.object({
7428
- file: z9.string(),
7429
- line: z9.number().int().positive(),
7430
- original: z9.string(),
7431
- replacement: z9.string(),
7432
- reasoning: z9.string().default("")
7836
+ var suggestionSchema = z10.object({
7837
+ suggestions: z10.array(
7838
+ z10.object({
7839
+ file: z10.string(),
7840
+ line: z10.number().int().positive(),
7841
+ original: z10.string(),
7842
+ replacement: z10.string(),
7843
+ reasoning: z10.string().default("")
7433
7844
  })
7434
7845
  )
7435
7846
  });
7436
7847
  var InstrumentSuggestionError = class extends Error {
7437
7848
  name = "InstrumentSuggestionError";
7438
7849
  };
7439
- var SYSTEM_PROMPT = `You add data-testid attributes to JSX elements so they can be reliably targeted by automated demo tools.
7850
+ var SYSTEM_PROMPT2 = `You add data-testid attributes to JSX elements so they can be reliably targeted by automated demo tools.
7440
7851
 
7441
7852
  Rules:
7442
7853
  - For each candidate, propose a stable kebab-case data-testid based on visible text, surrounding context, or the element's role. Examples: "login-submit", "new-article-button", "todo-item-toggle".
@@ -7463,7 +7874,7 @@ For each, return {file, line, original (exact source line), replacement (source
7463
7874
  });
7464
7875
  try {
7465
7876
  const result = await opts.provider.generateStructured({
7466
- systemPrompt: SYSTEM_PROMPT,
7877
+ systemPrompt: SYSTEM_PROMPT2,
7467
7878
  userPrompt,
7468
7879
  schema: suggestionSchema,
7469
7880
  schemaName: "instrument_suggestions",
@@ -7478,8 +7889,8 @@ For each, return {file, line, original (exact source line), replacement (source
7478
7889
  }
7479
7890
 
7480
7891
  // src/instrument/diff.ts
7481
- import { readFile as readFile12 } from "fs/promises";
7482
- import { relative } from "path";
7892
+ import { readFile as readFile14 } from "fs/promises";
7893
+ import { relative as relative2 } from "path";
7483
7894
  async function buildDiff(suggestions, opts = {}) {
7484
7895
  const byFile = /* @__PURE__ */ new Map();
7485
7896
  for (const s of suggestions) {
@@ -7492,7 +7903,7 @@ async function buildDiff(suggestions, opts = {}) {
7492
7903
  for (const [file, perFile] of byFile) {
7493
7904
  let source;
7494
7905
  try {
7495
- source = await readFile12(file, "utf8");
7906
+ source = await readFile14(file, "utf8");
7496
7907
  } catch (err) {
7497
7908
  for (const s of perFile) {
7498
7909
  skipped.push({
@@ -7528,7 +7939,7 @@ async function buildDiff(suggestions, opts = {}) {
7528
7939
  );
7529
7940
  }
7530
7941
  if (hunks.length === 0) continue;
7531
- const displayPath = opts.basePath ? relative(opts.basePath, file).replace(/\\/g, "/") : file;
7942
+ const displayPath = opts.basePath ? relative2(opts.basePath, file).replace(/\\/g, "/") : file;
7532
7943
  fileDiffs.push(`--- a/${displayPath}
7533
7944
  +++ b/${displayPath}
7534
7945
  ${hunks.join("\n")}`);
@@ -7548,7 +7959,7 @@ async function instrumentCommand(opts) {
7548
7959
  }
7549
7960
  throw err;
7550
7961
  }
7551
- const envFile = resolve25(loaded.configDir, ".env");
7962
+ const envFile = resolve26(loaded.configDir, ".env");
7552
7963
  try {
7553
7964
  process.loadEnvFile(envFile);
7554
7965
  } catch {
@@ -7563,13 +7974,13 @@ async function instrumentCommand(opts) {
7563
7974
  const filesToScan = /* @__PURE__ */ new Set();
7564
7975
  const IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out"]);
7565
7976
  async function walk(root, accept) {
7566
- const entries = await readdir3(root, { withFileTypes: true });
7977
+ const entries = await readdir4(root, { withFileTypes: true });
7567
7978
  for (const entry of entries) {
7568
- const abs = join14(root, entry.name);
7979
+ const abs = join15(root, entry.name);
7569
7980
  if (entry.isDirectory()) {
7570
7981
  if (IGNORED_DIRS.has(entry.name)) continue;
7571
7982
  await walk(abs, accept);
7572
- } else if (entry.isFile() && accept(relative2(root, abs))) {
7983
+ } else if (entry.isFile() && accept(relative3(root, abs))) {
7573
7984
  filesToScan.add(abs);
7574
7985
  }
7575
7986
  }
@@ -7580,7 +7991,7 @@ async function instrumentCommand(opts) {
7580
7991
  await walk(root, isJsxFile);
7581
7992
  } else {
7582
7993
  for (const src of codebaseSources) {
7583
- const root = isAbsolute13(src.path) ? src.path : resolve25(loaded.configDir, src.path);
7994
+ const root = isAbsolute14(src.path) ? src.path : resolve26(loaded.configDir, src.path);
7584
7995
  await walk(root, isJsxFile);
7585
7996
  }
7586
7997
  }
@@ -7621,9 +8032,9 @@ async function instrumentCommand(opts) {
7621
8032
  for (const s of skipped) {
7622
8033
  logger.warn(`Skipped suggestion at ${s.file}:${s.line} \u2014 ${s.reason}`);
7623
8034
  }
7624
- const outputPath = isAbsolute13(opts.output) ? opts.output : resolve25(loaded.configDir, opts.output);
8035
+ const outputPath = isAbsolute14(opts.output) ? opts.output : resolve26(loaded.configDir, opts.output);
7625
8036
  await mkdir16(dirname14(outputPath), { recursive: true });
7626
- await writeFile15(outputPath, patch, "utf8");
8037
+ await writeFile16(outputPath, patch, "utf8");
7627
8038
  logger.info("Wrote instrumentation patch", { path: outputPath, suggestions: suggestions.length });
7628
8039
  process.stdout.write(`
7629
8040
  Apply with: cd ${loaded.configDir} && git apply ${opts.output}
@@ -7633,8 +8044,8 @@ Apply with: cd ${loaded.configDir} && git apply ${opts.output}
7633
8044
 
7634
8045
  // src/commands/recordActions.ts
7635
8046
  import { createInterface as createInterface3 } from "readline/promises";
7636
- import { resolve as resolve26 } from "path";
7637
- import { stat as stat15 } from "fs/promises";
8047
+ import { resolve as resolve27 } from "path";
8048
+ import { stat as stat16 } from "fs/promises";
7638
8049
 
7639
8050
  // src/recording/captureEvents.ts
7640
8051
  async function installCaptureBinding(context, onEvent) {
@@ -7832,12 +8243,12 @@ async function recordActionsCommand(opts) {
7832
8243
  }
7833
8244
  throw err;
7834
8245
  }
7835
- const envFile = resolve26(loaded.configDir, ".env");
8246
+ const envFile = resolve27(loaded.configDir, ".env");
7836
8247
  try {
7837
8248
  process.loadEnvFile(envFile);
7838
8249
  } catch {
7839
8250
  }
7840
- const manifestPath = opts.output ? resolve26(loaded.configDir, opts.output) : resolve26(loaded.configDir, "./scripts/manifest.json");
8251
+ const manifestPath = opts.output ? resolve27(loaded.configDir, opts.output) : resolve27(loaded.configDir, "./scripts/manifest.json");
7841
8252
  logger.info("Launching headed browser for action capture", {
7842
8253
  target: loaded.config.recording.target_url
7843
8254
  });
@@ -7948,7 +8359,7 @@ async function mergeIntoManifest(opts) {
7948
8359
  }
7949
8360
  async function fileExists10(path) {
7950
8361
  try {
7951
- await stat15(path);
8362
+ await stat16(path);
7952
8363
  return true;
7953
8364
  } catch {
7954
8365
  return false;
@@ -7957,7 +8368,7 @@ async function fileExists10(path) {
7957
8368
 
7958
8369
  // src/cli.ts
7959
8370
  var program = new Command();
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) => {
8371
+ program.name("showrunner").description("Automated product demo recording & production tool").version("1.1.5").option("--json", "emit structured JSON logs to stdout").option("--log-level <level>", "log level (debug|info|warn|error)").hook("preAction", (thisCmd) => {
7961
8372
  const opts = thisCmd.opts();
7962
8373
  if (opts.json) logger.setJson(true);
7963
8374
  if (opts.logLevel) logger.setLevel(opts.logLevel);
@@ -7990,6 +8401,7 @@ program.command("init").description("Scaffold a new Showrunner project (interact
7990
8401
  "scaffold resolution: low (854x480) | standard (720p) | high (1080p) | extreme (4K)",
7991
8402
  "standard"
7992
8403
  ).action(initCommand);
8404
+ program.command("set-target").description("Update demo.yaml's recording.target_url and re-probe it").requiredOption("-c, --config <path>", "path to demo.yaml").requiredOption("--url <url>", "new target URL (e.g. http://localhost:5173)").option("--force", "skip the reachability probe and update the URL anyway", false).action(setTargetCommand);
7993
8405
  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);
7994
8406
  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);
7995
8407
  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);
@@ -8009,8 +8421,8 @@ program.parseAsync(process.argv).catch((err) => {
8009
8421
  });
8010
8422
  async function printWelcome() {
8011
8423
  const { access: access5 } = await import("fs/promises");
8012
- const { resolve: resolve27 } = await import("path");
8013
- const demoYaml = resolve27(process.cwd(), "demo.yaml");
8424
+ const { resolve: resolve28 } = await import("path");
8425
+ const demoYaml = resolve28(process.cwd(), "demo.yaml");
8014
8426
  let inProject = false;
8015
8427
  try {
8016
8428
  await access5(demoYaml);
@@ -8018,7 +8430,7 @@ async function printWelcome() {
8018
8430
  } catch {
8019
8431
  }
8020
8432
  const browserMissing = await isChromiumMissing();
8021
- const lines = ["", `Showrunner v1.1.4`, ""];
8433
+ const lines = ["", `Showrunner v1.1.5`, ""];
8022
8434
  if (browserMissing) {
8023
8435
  lines.push(`Showrunner records using Chromium. You haven't installed it yet.`);
8024
8436
  lines.push(``);
@@ -8046,8 +8458,8 @@ async function isChromiumMissing() {
8046
8458
  try {
8047
8459
  const { chromium: chromium6 } = await import("playwright-core");
8048
8460
  const exec = chromium6.executablePath();
8049
- const { stat: stat16 } = await import("fs/promises");
8050
- await stat16(exec);
8461
+ const { stat: stat17 } = await import("fs/promises");
8462
+ await stat17(exec);
8051
8463
  return false;
8052
8464
  } catch {
8053
8465
  return true;