@kadj-amoah/showrunner 1.1.3 → 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/CHANGELOG.md +23 -0
- package/dist/cli.js +418 -76
- package/dist/cli.js.map +1 -1
- package/package.json +2 -1
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
|
|
1416
|
+
const text2 = response.content.filter((b) => b.type === "text").map((b) => b.text).join("\n").trim();
|
|
1417
1417
|
throw new LLMProviderError(
|
|
1418
|
-
|
|
1419
|
-
${
|
|
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
|
|
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 (!
|
|
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,
|
|
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
|
|
2723
|
-
if (
|
|
2724
|
-
logger.debug(`browser: ${
|
|
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(
|
|
4606
|
-
return
|
|
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
|
|
4684
|
-
if (
|
|
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
|
|
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
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
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,28 +5967,54 @@ 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(
|
|
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
|
|
5663
|
-
lines.push(
|
|
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.`);
|
|
6006
|
+
} else {
|
|
6007
|
+
if (keysProvidedByWizard.length === 0) {
|
|
6008
|
+
lines.push(` ${step++}. cp .env.example .env`);
|
|
6009
|
+
}
|
|
5664
6010
|
lines.push(` ${step++}. Edit .env to fill in:`);
|
|
5665
|
-
for (const v of
|
|
6011
|
+
for (const v of remainingEnvVars) {
|
|
5666
6012
|
const dash = PROVIDER_DASHBOARDS[v];
|
|
5667
6013
|
lines.push(` ${v}${dash ? ` (get it at ${dash})` : ""}`);
|
|
5668
6014
|
}
|
|
5669
6015
|
lines.push(
|
|
5670
6016
|
` (No keys? Switch llm.default.provider to \`agent_bridge\` in demo.yaml \u2014 uses your local Claude CLI instead.)`
|
|
5671
6017
|
);
|
|
5672
|
-
} else {
|
|
5673
|
-
lines.push(
|
|
5674
|
-
` ${step++}. (.env not needed \u2014 agent_bridge LLM + ${resolved.tts} TTS don't use API keys.)`
|
|
5675
|
-
);
|
|
5676
6018
|
}
|
|
5677
6019
|
lines.push(` ${step++}. Edit docs/PRD.md (the stub explains what each section is for).`);
|
|
5678
6020
|
lines.push(` ${step++}. showrunner doctor -c demo.yaml`);
|
|
@@ -5708,7 +6050,7 @@ function validateChoice(value, allowed, defaultValue, flagName) {
|
|
|
5708
6050
|
}
|
|
5709
6051
|
async function pathExists(p) {
|
|
5710
6052
|
try {
|
|
5711
|
-
await
|
|
6053
|
+
await access3(p);
|
|
5712
6054
|
return true;
|
|
5713
6055
|
} catch {
|
|
5714
6056
|
return false;
|
|
@@ -6127,8 +6469,8 @@ and \`at_word\` actions degrade to \`at\`).
|
|
|
6127
6469
|
}
|
|
6128
6470
|
|
|
6129
6471
|
// src/commands/installBrowser.ts
|
|
6130
|
-
import { spawn as
|
|
6131
|
-
import { access as
|
|
6472
|
+
import { spawn as spawn5 } from "child_process";
|
|
6473
|
+
import { access as access4 } from "fs/promises";
|
|
6132
6474
|
import { fileURLToPath } from "url";
|
|
6133
6475
|
import { dirname as dirname11, join as join11 } from "path";
|
|
6134
6476
|
var DEFAULT_BROWSER = "chromium";
|
|
@@ -6136,7 +6478,7 @@ async function installBrowserCommand(opts) {
|
|
|
6136
6478
|
const browser = opts.browser ?? DEFAULT_BROWSER;
|
|
6137
6479
|
const cli = await resolvePlaywrightCoreCli();
|
|
6138
6480
|
logger.info(`installing Playwright ${browser} (via bundled playwright-core, no project required)`);
|
|
6139
|
-
const child =
|
|
6481
|
+
const child = spawn5(process.execPath, [cli, "install", browser], {
|
|
6140
6482
|
stdio: "inherit",
|
|
6141
6483
|
env: process.env
|
|
6142
6484
|
});
|
|
@@ -6157,7 +6499,7 @@ async function resolvePlaywrightCoreCli() {
|
|
|
6157
6499
|
while (dir && dir !== root) {
|
|
6158
6500
|
const candidate = join11(dir, "node_modules", "playwright-core", "cli.js");
|
|
6159
6501
|
try {
|
|
6160
|
-
await
|
|
6502
|
+
await access4(candidate);
|
|
6161
6503
|
return candidate;
|
|
6162
6504
|
} catch {
|
|
6163
6505
|
}
|
|
@@ -6171,7 +6513,7 @@ async function resolvePlaywrightCoreCli() {
|
|
|
6171
6513
|
}
|
|
6172
6514
|
|
|
6173
6515
|
// src/commands/validate.ts
|
|
6174
|
-
import { stat as
|
|
6516
|
+
import { stat as stat11 } from "fs/promises";
|
|
6175
6517
|
import { isAbsolute as isAbsolute9, resolve as resolve16 } from "path";
|
|
6176
6518
|
async function validateCommand(opts) {
|
|
6177
6519
|
try {
|
|
@@ -6209,7 +6551,7 @@ async function checkReferencedPaths(config, configDir) {
|
|
|
6209
6551
|
if (!relPath) return;
|
|
6210
6552
|
const abs = isAbsolute9(relPath) ? relPath : resolve16(configDir, relPath);
|
|
6211
6553
|
try {
|
|
6212
|
-
await
|
|
6554
|
+
await stat11(abs);
|
|
6213
6555
|
} catch {
|
|
6214
6556
|
warnings.push(`${label} not found: ${abs}`);
|
|
6215
6557
|
}
|
|
@@ -6260,12 +6602,12 @@ async function printVoCommand(opts) {
|
|
|
6260
6602
|
}
|
|
6261
6603
|
throw err;
|
|
6262
6604
|
}
|
|
6263
|
-
const
|
|
6264
|
-
process.stdout.write(
|
|
6605
|
+
const text2 = renderVoScript(manifest, { projectName: loaded.config.project.name });
|
|
6606
|
+
process.stdout.write(text2);
|
|
6265
6607
|
}
|
|
6266
6608
|
|
|
6267
6609
|
// src/commands/approveVo.ts
|
|
6268
|
-
import { rm as rm2, stat as
|
|
6610
|
+
import { rm as rm2, stat as stat12 } from "fs/promises";
|
|
6269
6611
|
import { resolve as resolve18 } from "path";
|
|
6270
6612
|
async function approveVoCommand(opts) {
|
|
6271
6613
|
let loaded;
|
|
@@ -6313,7 +6655,7 @@ async function approveVoCommand(opts) {
|
|
|
6313
6655
|
}
|
|
6314
6656
|
async function pathExists2(p) {
|
|
6315
6657
|
try {
|
|
6316
|
-
await
|
|
6658
|
+
await stat12(p);
|
|
6317
6659
|
return true;
|
|
6318
6660
|
} catch {
|
|
6319
6661
|
return false;
|
|
@@ -6531,9 +6873,9 @@ async function launchHeadedSession(opts) {
|
|
|
6531
6873
|
const page = await context.newPage();
|
|
6532
6874
|
if (opts.withCursor) {
|
|
6533
6875
|
page.on("console", (msg) => {
|
|
6534
|
-
const
|
|
6535
|
-
if (
|
|
6536
|
-
logger.debug(`browser: ${
|
|
6876
|
+
const text2 = msg.text();
|
|
6877
|
+
if (text2.startsWith("[showrunner-cursor]")) {
|
|
6878
|
+
logger.debug(`browser: ${text2}`);
|
|
6537
6879
|
}
|
|
6538
6880
|
});
|
|
6539
6881
|
}
|
|
@@ -6607,7 +6949,7 @@ Then re-run \`showrunner run --config <demo.yaml>\`.
|
|
|
6607
6949
|
}
|
|
6608
6950
|
|
|
6609
6951
|
// src/commands/trace.ts
|
|
6610
|
-
import { readdir as readdir2, stat as
|
|
6952
|
+
import { readdir as readdir2, stat as stat13 } from "fs/promises";
|
|
6611
6953
|
import { isAbsolute as isAbsolute11, join as join13, resolve as resolve22 } from "path";
|
|
6612
6954
|
async function traceCommand(opts) {
|
|
6613
6955
|
let loaded;
|
|
@@ -6689,7 +7031,7 @@ async function listTraces(dir) {
|
|
|
6689
7031
|
}
|
|
6690
7032
|
async function fileExists8(path) {
|
|
6691
7033
|
try {
|
|
6692
|
-
await
|
|
7034
|
+
await stat13(path);
|
|
6693
7035
|
return true;
|
|
6694
7036
|
} catch {
|
|
6695
7037
|
return false;
|
|
@@ -6697,7 +7039,7 @@ async function fileExists8(path) {
|
|
|
6697
7039
|
}
|
|
6698
7040
|
|
|
6699
7041
|
// src/commands/preview.ts
|
|
6700
|
-
import { stat as
|
|
7042
|
+
import { stat as stat14 } from "fs/promises";
|
|
6701
7043
|
import { resolve as resolve23 } from "path";
|
|
6702
7044
|
async function previewCommand(opts) {
|
|
6703
7045
|
let loaded;
|
|
@@ -6736,7 +7078,7 @@ async function previewCommand(opts) {
|
|
|
6736
7078
|
}
|
|
6737
7079
|
async function fileExists9(path) {
|
|
6738
7080
|
try {
|
|
6739
|
-
await
|
|
7081
|
+
await stat14(path);
|
|
6740
7082
|
return true;
|
|
6741
7083
|
} catch {
|
|
6742
7084
|
return false;
|
|
@@ -6875,7 +7217,7 @@ var LineReader = class {
|
|
|
6875
7217
|
});
|
|
6876
7218
|
}
|
|
6877
7219
|
};
|
|
6878
|
-
async function
|
|
7220
|
+
async function ask2(reader, prompt) {
|
|
6879
7221
|
process.stdout.write(prompt);
|
|
6880
7222
|
return reader.read();
|
|
6881
7223
|
}
|
|
@@ -6886,23 +7228,23 @@ async function runInteractiveQA() {
|
|
|
6886
7228
|
process.stdout.write(
|
|
6887
7229
|
"\nLet's build a product_model interactively. Answer each prompt with a short line.\n\n"
|
|
6888
7230
|
);
|
|
6889
|
-
const productName = (await
|
|
7231
|
+
const productName = (await ask2(reader, "1. Product name: ")).trim();
|
|
6890
7232
|
if (!productName) throw new Error("product name is required");
|
|
6891
|
-
const tagline = (await
|
|
6892
|
-
const primaryUser = (await
|
|
6893
|
-
const featuresLine = (await
|
|
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();
|
|
6894
7236
|
const topFeatures = featuresLine.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6895
|
-
const flowCountStr = (await
|
|
7237
|
+
const flowCountStr = (await ask2(reader, "5. How many flows would you like to demo (2-4)? ")).trim();
|
|
6896
7238
|
const flowCount = clamp(parseInt(flowCountStr, 10) || 2, 2, 4);
|
|
6897
7239
|
const topFlows = [];
|
|
6898
7240
|
for (let i = 0; i < flowCount; i++) {
|
|
6899
|
-
const name = (await
|
|
6900
|
-
const stepsLine = (await
|
|
7241
|
+
const name = (await ask2(reader, ` Flow ${i + 1} name: `)).trim();
|
|
7242
|
+
const stepsLine = (await ask2(reader, ` Flow ${i + 1} steps, '|' separated:
|
|
6901
7243
|
`)).trim();
|
|
6902
7244
|
const steps = stepsLine.split("|").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
6903
7245
|
topFlows.push({ name, steps });
|
|
6904
7246
|
}
|
|
6905
|
-
const durationStr = (await
|
|
7247
|
+
const durationStr = (await ask2(reader, "6. Target demo duration in seconds (60-120, default 75): ")).trim();
|
|
6906
7248
|
const suggestedDurationSeconds = clamp(parseInt(durationStr, 10) || 75, 30, 180);
|
|
6907
7249
|
return {
|
|
6908
7250
|
productName,
|
|
@@ -7292,7 +7634,7 @@ Apply with: cd ${loaded.configDir} && git apply ${opts.output}
|
|
|
7292
7634
|
// src/commands/recordActions.ts
|
|
7293
7635
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
7294
7636
|
import { resolve as resolve26 } from "path";
|
|
7295
|
-
import { stat as
|
|
7637
|
+
import { stat as stat15 } from "fs/promises";
|
|
7296
7638
|
|
|
7297
7639
|
// src/recording/captureEvents.ts
|
|
7298
7640
|
async function installCaptureBinding(context, onEvent) {
|
|
@@ -7606,7 +7948,7 @@ async function mergeIntoManifest(opts) {
|
|
|
7606
7948
|
}
|
|
7607
7949
|
async function fileExists10(path) {
|
|
7608
7950
|
try {
|
|
7609
|
-
await
|
|
7951
|
+
await stat15(path);
|
|
7610
7952
|
return true;
|
|
7611
7953
|
} catch {
|
|
7612
7954
|
return false;
|
|
@@ -7615,7 +7957,7 @@ async function fileExists10(path) {
|
|
|
7615
7957
|
|
|
7616
7958
|
// src/cli.ts
|
|
7617
7959
|
var program = new Command();
|
|
7618
|
-
program.name("showrunner").description("Automated product demo recording & production tool").version("1.1.
|
|
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) => {
|
|
7619
7961
|
const opts = thisCmd.opts();
|
|
7620
7962
|
if (opts.json) logger.setJson(true);
|
|
7621
7963
|
if (opts.logLevel) logger.setLevel(opts.logLevel);
|
|
@@ -7635,7 +7977,7 @@ program.command("run").description("Run the full Showrunner pipeline").requiredO
|
|
|
7635
7977
|
"--resolution <preset>",
|
|
7636
7978
|
"override resolution for this run: low (854x480), standard (720p), high (1080p), extreme (4K)"
|
|
7637
7979
|
).action(runCommand2);
|
|
7638
|
-
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(
|
|
7639
7981
|
"--llm-provider <name>",
|
|
7640
7982
|
"LLM provider for comprehension + script + instrument (anthropic | openai | agent_bridge)",
|
|
7641
7983
|
"anthropic"
|
|
@@ -7666,17 +8008,17 @@ program.parseAsync(process.argv).catch((err) => {
|
|
|
7666
8008
|
process.exit(1);
|
|
7667
8009
|
});
|
|
7668
8010
|
async function printWelcome() {
|
|
7669
|
-
const { access:
|
|
8011
|
+
const { access: access5 } = await import("fs/promises");
|
|
7670
8012
|
const { resolve: resolve27 } = await import("path");
|
|
7671
8013
|
const demoYaml = resolve27(process.cwd(), "demo.yaml");
|
|
7672
8014
|
let inProject = false;
|
|
7673
8015
|
try {
|
|
7674
|
-
await
|
|
8016
|
+
await access5(demoYaml);
|
|
7675
8017
|
inProject = true;
|
|
7676
8018
|
} catch {
|
|
7677
8019
|
}
|
|
7678
8020
|
const browserMissing = await isChromiumMissing();
|
|
7679
|
-
const lines = ["", `Showrunner v1.1.
|
|
8021
|
+
const lines = ["", `Showrunner v1.1.4`, ""];
|
|
7680
8022
|
if (browserMissing) {
|
|
7681
8023
|
lines.push(`Showrunner records using Chromium. You haven't installed it yet.`);
|
|
7682
8024
|
lines.push(``);
|
|
@@ -7704,8 +8046,8 @@ async function isChromiumMissing() {
|
|
|
7704
8046
|
try {
|
|
7705
8047
|
const { chromium: chromium6 } = await import("playwright-core");
|
|
7706
8048
|
const exec = chromium6.executablePath();
|
|
7707
|
-
const { stat:
|
|
7708
|
-
await
|
|
8049
|
+
const { stat: stat16 } = await import("fs/promises");
|
|
8050
|
+
await stat16(exec);
|
|
7709
8051
|
return false;
|
|
7710
8052
|
} catch {
|
|
7711
8053
|
return true;
|