agentreel 0.3.5 → 0.4.1

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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # agentreel
2
2
 
3
- Turn your web apps and CLIs into viral clips.
3
+ Turn your web apps and CLIs into viral clips — or demo what a PR does.
4
4
 
5
5
  https://github.com/user-attachments/assets/474fd85d-3b35-48f4-82b8-1b337840fb51
6
6
 
7
- > 🔊 Turn on sound
7
+ > Turn on sound
8
8
 
9
9
  ## Install
10
10
 
@@ -15,46 +15,72 @@ npx agentreel
15
15
  ## Usage
16
16
 
17
17
  ```bash
18
+ # Demo a PR (reads context from GitHub):
19
+ agentreel --pr 123
20
+ agentreel --pr owner/repo#123
21
+ agentreel --pr https://github.com/owner/repo/pull/123
22
+
23
+ # PR demo with a dev server (for web UI changes):
24
+ agentreel --pr 123 --start "npm run dev"
25
+
18
26
  # CLI demo:
19
27
  agentreel --cmd "npx my-cli-tool"
20
28
 
21
29
  # Browser demo:
22
30
  agentreel --url http://localhost:3000
23
-
24
- # With context for smarter demo planning:
25
- agentreel --cmd "npx my-tool" --prompt "A CLI that manages cron jobs"
26
31
  ```
27
32
 
28
- ## How it works
33
+ ## Modes
34
+
35
+ ### PR demo (`--pr`)
36
+
37
+ Point it at a pull request. It fetches the diff, description, and README from GitHub, then AI plans and records a demo showing what the PR actually does.
29
38
 
30
- 1. You provide a CLI command or URL
31
- 2. AI plans and executes a demo (terminal or browser)
32
- 3. AI picks the 3-4 best highlight moments
33
- 4. Renders a polished video with music, transitions, and overlays
34
- 5. Prompts you to share on Twitter
39
+ - **1920x1080 landscape** chapter-based walkthrough
40
+ - **4-6 chapters**, 12s each full command + output flows
41
+ - No music, no marketing overlays just the real demo
42
+ - Great for attaching to PRs so reviewers can see the change in action
35
43
 
36
- ## What you get
44
+ Requires [`gh` CLI](https://cli.github.com) to be installed and authenticated.
37
45
 
38
- A 15-20 second 1080x1080 video with:
39
- - **Title card** with your project name
40
- - **Highlight clips** — terminal or browser window on animated gradient
41
- - **Text overlays** — bold captions that work on mute
42
- - **Cursor + typing** — looks like someone's actually using it
43
- - **Background music** with fade in/out
44
- - **End CTA** — install command + URL
46
+ ### Marketing reel (`--cmd` / `--url`)
45
47
 
46
- Ready for Twitter/X, LinkedIn, Reels.
48
+ The original mode — creates a short, polished clip for social media.
47
49
 
48
- ## Supports
50
+ - **1080x1080 square** — optimized for Twitter/X, LinkedIn, Reels
51
+ - **3-4 highlight snippets**, 4.5s each — the best moments
52
+ - Music, animated transitions, text overlays, cursor animations
53
+ - Prompts you to share on Twitter
49
54
 
50
- - **CLI demos** — records your tool in a terminal, shows the highlights
51
- - **Browser demos** — records your web app via Playwright, shows the key moments
55
+ ## How it works
56
+
57
+ 1. **PR mode**: Fetches PR diff + README from GitHub, AI decides CLI or browser demo, plans steps that show the actual changes
58
+ 2. **Manual mode**: You provide a CLI command or URL, AI plans an impressive demo
59
+ 3. AI executes the demo (terminal PTY or Playwright browser)
60
+ 4. AI picks the best moments as highlights
61
+ 5. Renders video with Remotion
62
+
63
+ ## Flags
64
+
65
+ ```
66
+ --pr <ref> PR number, owner/repo#N, or full GitHub URL
67
+ --start <cmd> command to start a dev server (for browser PR demos)
68
+ -c, --cmd <command> CLI command to demo
69
+ -u, --url <url> URL to demo (browser mode)
70
+ -t, --title <text> video title
71
+ -o, --output <file> output file (default: agentreel.mp4)
72
+ -a, --auth <file> Playwright storage state (cookies/auth) for browser demos
73
+ -g, --guidelines <text> guidelines for highlight generation
74
+ --music <file> path to background music mp3
75
+ --no-share skip the share prompt
76
+ ```
52
77
 
53
78
  ## Requirements
54
79
 
55
80
  - Node.js 18+
56
81
  - Python 3.10+
57
82
  - Claude CLI (`claude`) — used to plan demo sequences
83
+ - `gh` CLI — required for `--pr` mode
58
84
 
59
85
  ## Credits
60
86
 
package/bin/agentreel.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execFileSync } from "node:child_process";
3
+ import { execFileSync, spawn } from "node:child_process";
4
4
  import { readFileSync, statSync, existsSync, mkdirSync, copyFileSync } from "node:fs";
5
5
  import { join, dirname, resolve } from "node:path";
6
6
  import { tmpdir } from "node:os";
@@ -25,7 +25,8 @@ function parseArgs() {
25
25
  }
26
26
  if (arg === "--cmd" || arg === "-c") flags.cmd = args[++i];
27
27
  else if (arg === "--url" || arg === "-u") flags.url = args[++i];
28
- else if (arg === "--prompt" || arg === "-p") flags.prompt = args[++i];
28
+ else if (arg === "--pr") flags.pr = args[++i];
29
+ else if (arg === "--start") flags.start = args[++i];
29
30
  else if (arg === "--title" || arg === "-t") flags.title = args[++i];
30
31
  else if (arg === "--output" || arg === "-o") flags.output = args[++i];
31
32
  else if (arg === "--music") flags.music = args[++i];
@@ -40,13 +41,16 @@ function printUsage() {
40
41
  console.log(`agentreel — Turn your web apps and CLIs into viral clips
41
42
 
42
43
  Usage:
44
+ agentreel --pr 123 # demo a PR (reads context from GitHub)
45
+ agentreel --pr owner/repo#123 # demo a PR (explicit repo)
43
46
  agentreel --cmd "npx my-cli-tool" # CLI demo
44
47
  agentreel --url http://localhost:3000 # browser demo
45
48
 
46
49
  Flags:
50
+ --pr <ref> PR number, owner/repo#N, or full GitHub URL
51
+ --start <cmd> command to start a dev server (for browser PR demos)
47
52
  -c, --cmd <command> CLI command to demo
48
53
  -u, --url <url> URL to demo (browser mode)
49
- -p, --prompt <text> description of what the tool does
50
54
  -t, --title <text> video title
51
55
  -o, --output <file> output file (default: agentreel.mp4)
52
56
  -a, --auth <file> Playwright storage state (cookies/auth) for browser demos
@@ -326,29 +330,7 @@ async function renderVideo(props, output, musicPath) {
326
330
  console.error(`\nDone: ${output} (${Math.round(size / 1024)} KB)`);
327
331
  }
328
332
 
329
- // ── Upload + Share ──────────────────────────────────────────
330
-
331
- // Video upload placeholder — will add agentreel.dev hosting later
332
- async function uploadVideo(_filePath) {
333
- return null;
334
- }
335
-
336
- function openShareURL(videoURL, text) {
337
- const tweetText = encodeURIComponent(text);
338
- const encodedURL = encodeURIComponent(videoURL);
339
- const intentURL = `https://twitter.com/intent/tweet?text=${tweetText}&url=${encodedURL}`;
340
-
341
- console.error(`\n Share: ${videoURL}`);
342
- console.error(` Tweet: ${intentURL}\n`);
343
-
344
- // Open in browser
345
- const cmd = process.platform === "darwin" ? "open" : "xdg-open";
346
- try {
347
- execFileSync(cmd, [intentURL], { stdio: "ignore" });
348
- } catch {
349
- console.error(" (Could not open browser — copy the link above)");
350
- }
351
- }
333
+ // ── Share ───────────────────────────────────────────────────
352
334
 
353
335
  function askYesNo(question) {
354
336
  return new Promise((resolve) => {
@@ -383,20 +365,150 @@ async function shareFlow(outputPath, title, prompt) {
383
365
  }
384
366
  }
385
367
 
386
- // ── Auto-describe ──────────────────────────────────────────
368
+ // ── PR Context ─────────────────────────────────────────────
387
369
 
388
- function autoDescribe(cmd, url) {
389
- const target = cmd || url;
370
+ function fetchPRContext(prRef) {
390
371
  try {
391
- const result = execFileSync("claude", [
392
- "-p",
393
- `Describe what this tool/app does in one short sentence (under 10 words). No quotes, no period. Just the description.\n\n${target}`,
394
- "--output-format", "text",
395
- ], { encoding: "utf-8", timeout: 30000, stdio: ["ignore", "pipe", "ignore"] });
396
- const desc = result.trim();
397
- if (desc && desc.length < 100) return desc;
398
- } catch { /* fall through */ }
399
- return cmd ? cmd.split(/\s+/).pop() : "Web app demo";
372
+ execFileSync("gh", ["--version"], { stdio: "ignore" });
373
+ } catch {
374
+ console.error("Error: `gh` CLI is required for --pr mode. Install it from https://cli.github.com");
375
+ process.exit(1);
376
+ }
377
+
378
+ const prJson = execFileSync("gh", [
379
+ "pr", "view", String(prRef),
380
+ "--json", "title,body,headRefName,baseRefName,url,number",
381
+ ], { encoding: "utf-8", timeout: 30000 });
382
+ const pr = JSON.parse(prJson);
383
+
384
+ let diff = "";
385
+ try {
386
+ diff = execFileSync("gh", ["pr", "diff", String(prRef)], {
387
+ encoding: "utf-8", timeout: 30000,
388
+ });
389
+ } catch (e) {
390
+ console.error(` Warning: could not fetch PR diff: ${e.message}`);
391
+ }
392
+
393
+ // Read README from cwd (the agent already has the repo checked out)
394
+ let readme = "";
395
+ for (const name of ["README.md", "readme.md", "README", "README.rst"]) {
396
+ const p = join(process.cwd(), name);
397
+ if (existsSync(p)) {
398
+ readme = readFileSync(p, "utf-8");
399
+ break;
400
+ }
401
+ }
402
+
403
+ return { ...pr, diff, readme };
404
+ }
405
+
406
+ function planDemoFromPR(prContext, guidelines) {
407
+ const guidelinesBlock = guidelines
408
+ ? `\nAdditional guidelines: ${guidelines}`
409
+ : "";
410
+
411
+ const prompt = `You are planning a demo for a Pull Request. Your job is to decide whether this is a CLI or browser demo, and provide the details needed to record it.
412
+
413
+ PR Title: ${prContext.title}
414
+ PR Description: ${prContext.body || "(no description)"}
415
+
416
+ Diff (truncated):
417
+ ${prContext.diff.slice(0, 8000)}
418
+
419
+ README (truncated):
420
+ ${prContext.readme.slice(0, 3000)}${guidelinesBlock}
421
+
422
+ Return a JSON object with these fields:
423
+ {
424
+ "type": "cli" or "browser",
425
+ "command": "the command to run" (for CLI demos, e.g. "npx my-tool --help") or null,
426
+ "url": "http://localhost:3000/relevant-page" (for browser demos) or null,
427
+ "description": "one-sentence summary of what the PR does",
428
+ "title": "short video title (2-4 words)",
429
+ "guidelines": "specific instructions for the demo recorder about what steps to show and what to focus on"
430
+ }
431
+
432
+ Rules:
433
+ - If the PR changes a CLI tool, script, or backend logic that can be demonstrated in a terminal, use "cli".
434
+ - If the PR changes a web UI, frontend, or something best shown in a browser, use "browser".
435
+ - The "guidelines" field should tell the demo recorder exactly what to demonstrate — the specific feature or fix from this PR.
436
+ - The demo should show the actual changes working honestly, not market the product.
437
+ - Return ONLY the JSON object, no markdown fences.`;
438
+
439
+ const result = execFileSync("claude", ["-p", prompt, "--output-format", "text"], {
440
+ encoding: "utf-8",
441
+ timeout: 60000,
442
+ stdio: ["ignore", "pipe", "ignore"],
443
+ }).trim();
444
+
445
+ // Strip markdown fences if present
446
+ let text = result;
447
+ if (text.includes("```")) {
448
+ const parts = text.split("```");
449
+ for (let part of parts) {
450
+ part = part.trim();
451
+ if (part.startsWith("json")) part = part.slice(4).trim();
452
+ if (part.startsWith("{")) { text = part; break; }
453
+ }
454
+ }
455
+
456
+ return JSON.parse(text);
457
+ }
458
+
459
+ // ── Dev Server ─────────────────────────────────────────────
460
+
461
+ function startDevServer(command) {
462
+ console.error(` Starting dev server: ${command}`);
463
+ const proc = spawn("sh", ["-c", command], {
464
+ stdio: ["ignore", "pipe", "pipe"],
465
+ detached: true,
466
+ });
467
+
468
+ // Wait for server to be ready (look for common ready signals in output)
469
+ return new Promise((resolve, reject) => {
470
+ const timeout = setTimeout(() => {
471
+ console.error(" Dev server ready (timeout — assuming started)");
472
+ resolve(proc);
473
+ }, 30000);
474
+
475
+ const onData = (data) => {
476
+ const text = data.toString();
477
+ if (/localhost|ready|started|listening|compiled/i.test(text)) {
478
+ clearTimeout(timeout);
479
+ // Give it a moment to fully start
480
+ setTimeout(() => {
481
+ console.error(" Dev server ready");
482
+ resolve(proc);
483
+ }, 2000);
484
+ }
485
+ };
486
+
487
+ proc.stdout.on("data", onData);
488
+ proc.stderr.on("data", onData);
489
+
490
+ proc.on("error", (err) => {
491
+ clearTimeout(timeout);
492
+ reject(new Error(`Dev server failed to start: ${err.message}`));
493
+ });
494
+
495
+ proc.on("exit", (code) => {
496
+ clearTimeout(timeout);
497
+ if (code !== null && code !== 0) {
498
+ reject(new Error(`Dev server exited with code ${code}`));
499
+ }
500
+ });
501
+ });
502
+ }
503
+
504
+ function stopDevServer(proc) {
505
+ if (!proc || proc.killed) return;
506
+ try {
507
+ // Kill the process group (detached process + children)
508
+ process.kill(-proc.pid, "SIGTERM");
509
+ } catch {
510
+ try { proc.kill("SIGTERM"); } catch { /* already dead */ }
511
+ }
400
512
  }
401
513
 
402
514
  // ── Main ────────────────────────────────────────────────────
@@ -406,83 +518,152 @@ async function main() {
406
518
  const output = flags.output || "agentreel.mp4";
407
519
  const noShare = flags.noShare;
408
520
 
409
- let demoCmd = flags.cmd;
410
- let demoURL = flags.url;
411
- let prompt = flags.prompt;
412
-
413
- // Auto-generate description if not provided
414
- if (!prompt) {
415
- console.error("Generating description...");
416
- prompt = autoDescribe(demoCmd, demoURL);
417
- console.error(` "${prompt}"`);
418
- }
419
-
420
- if (!demoCmd && !demoURL) {
421
- console.error("Please provide --cmd or --url.\n");
521
+ if (!flags.cmd && !flags.url && !flags.pr) {
522
+ console.error("Please provide --pr, --cmd, or --url.\n");
422
523
  printUsage();
423
524
  process.exit(1);
424
525
  }
425
526
 
426
- let videoTitle = flags.title || demoCmd || demoURL;
527
+ // ── PR mode ──────────────────────────────────────────────
528
+ if (flags.pr) {
529
+ console.error("Fetching PR context...");
530
+ const prContext = fetchPRContext(flags.pr);
531
+ console.error(` PR #${prContext.number}: ${prContext.title}`);
532
+
533
+ console.error("Planning demo...");
534
+ const plan = planDemoFromPR(prContext, flags.guidelines);
535
+ console.error(` Type: ${plan.type}, "${plan.description}"`);
536
+
537
+ const videoTitle = flags.title || plan.title || prContext.title;
538
+ const description = plan.description;
539
+ // Prepend "demo" to guidelines so downstream scripts know to use chapter-based extraction
540
+ const demoGuidelines = `[demo] ${plan.guidelines || ""}`.trim();
541
+
542
+ if (plan.type === "browser") {
543
+ const url = plan.url || "http://localhost:3000";
544
+ let serverProc = null;
545
+
546
+ try {
547
+ if (flags.start) {
548
+ serverProc = await startDevServer(flags.start);
549
+ }
550
+
551
+ ensureBrowserDeps();
552
+ console.error("Step 1/3: Recording browser demo...");
553
+ const videoPath = recordBrowser(url, demoGuidelines, flags.auth, demoGuidelines);
554
+
555
+ const publicDir = join(ROOT, "public");
556
+ if (!existsSync(publicDir)) mkdirSync(publicDir, { recursive: true });
557
+ copyFileSync(videoPath, join(publicDir, "browser-demo.mp4"));
558
+
559
+ console.error("Step 2/3: Building highlights...");
560
+ const clicksPath = videoPath.replace(".mp4", "-clicks.json");
561
+ let allClicks = [];
562
+ if (existsSync(clicksPath)) {
563
+ allClicks = JSON.parse(readFileSync(clicksPath, "utf-8"));
564
+ console.error(` ${allClicks.length} clicks captured`);
565
+ }
566
+ const highlights = buildBrowserHighlights(allClicks, videoPath, demoGuidelines, demoGuidelines);
567
+
568
+ console.error("Step 3/3: Rendering video...");
569
+ await renderVideo({
570
+ title: videoTitle,
571
+ subtitle: description,
572
+ highlights,
573
+ endText: prContext.title,
574
+ endUrl: prContext.url,
575
+ mode: "demo",
576
+ }, output, flags.music);
577
+ } finally {
578
+ stopDevServer(serverProc);
579
+ }
580
+ } else {
581
+ // CLI demo
582
+ if (!plan.command) {
583
+ console.error("Error: Claude could not determine a command to demo for this PR.");
584
+ process.exit(1);
585
+ }
586
+
587
+ console.error("Step 1/3: Recording CLI demo...");
588
+ const castPath = recordCLI(plan.command, process.cwd(), description, demoGuidelines);
589
+
590
+ console.error("Step 2/3: Extracting highlights...");
591
+ const highlightsPath = extractHighlightsFromCast(castPath, description, demoGuidelines);
592
+ const highlights = JSON.parse(readFileSync(highlightsPath, "utf-8"));
593
+ console.error(` ${highlights.length} highlights extracted`);
594
+
595
+ console.error("Step 3/3: Rendering video...");
596
+ await renderVideo({
597
+ title: videoTitle,
598
+ subtitle: description,
599
+ highlights,
600
+ endText: plan.command,
601
+ endUrl: prContext.url,
602
+ mode: "demo",
603
+ }, output, flags.music);
604
+ }
427
605
 
428
- if (demoCmd) {
606
+ if (!noShare) {
607
+ await shareFlow(resolve(output), videoTitle, description);
608
+ }
609
+ return;
610
+ }
611
+
612
+ // ── Manual modes (--cmd / --url) ─────────────────────────
613
+ let videoTitle = flags.title || flags.cmd || flags.url;
614
+
615
+ if (flags.cmd) {
429
616
  console.error("Step 1/3: Recording CLI demo...");
430
- const castPath = recordCLI(demoCmd, process.cwd(), prompt, flags.guidelines);
617
+ const castPath = recordCLI(flags.cmd, process.cwd(), flags.cmd, flags.guidelines);
431
618
 
432
619
  console.error("Step 2/3: Extracting highlights...");
433
- const highlightsPath = extractHighlightsFromCast(castPath, prompt, flags.guidelines);
620
+ const highlightsPath = extractHighlightsFromCast(castPath, flags.cmd, flags.guidelines);
434
621
  const highlights = JSON.parse(readFileSync(highlightsPath, "utf-8"));
435
622
  console.error(` ${highlights.length} highlights extracted`);
436
623
 
437
624
  console.error("Step 3/3: Rendering video...");
438
625
  await renderVideo({
439
626
  title: videoTitle,
440
- subtitle: prompt,
441
627
  highlights,
442
- endText: demoCmd,
628
+ endText: flags.cmd,
443
629
  }, output, flags.music);
444
630
 
445
631
  if (!noShare) {
446
- await shareFlow(resolve(output), videoTitle, prompt);
632
+ await shareFlow(resolve(output), videoTitle, flags.cmd);
447
633
  }
448
634
  return;
449
635
  }
450
636
 
451
- if (demoURL) {
452
- const task = prompt || "Explore the main features of this app";
637
+ if (flags.url) {
638
+ const task = "Explore the main features of this app";
453
639
 
454
640
  ensureBrowserDeps();
455
641
  console.error("Step 1/3: Recording browser demo...");
456
- const videoPath = recordBrowser(demoURL, task, flags.auth, flags.guidelines);
642
+ const videoPath = recordBrowser(flags.url, task, flags.auth, flags.guidelines);
457
643
 
458
- // Copy video to Remotion public dir so it can be served
459
644
  const publicDir = join(ROOT, "public");
460
645
  if (!existsSync(publicDir)) mkdirSync(publicDir, { recursive: true });
461
646
  copyFileSync(videoPath, join(publicDir, "browser-demo.mp4"));
462
647
 
463
648
  console.error("Step 2/3: Building highlights...");
464
-
465
- // Read click data — this is the primary signal for highlights
466
649
  const clicksPath = videoPath.replace(".mp4", "-clicks.json");
467
650
  let allClicks = [];
468
651
  if (existsSync(clicksPath)) {
469
652
  allClicks = JSON.parse(readFileSync(clicksPath, "utf-8"));
470
653
  console.error(` ${allClicks.length} clicks captured`);
471
654
  }
472
-
473
655
  const highlights = buildBrowserHighlights(allClicks, videoPath, task, flags.guidelines);
474
656
 
475
657
  console.error("Step 3/3: Rendering video...");
476
658
  await renderVideo({
477
659
  title: videoTitle,
478
- subtitle: prompt,
479
660
  highlights,
480
- endText: demoURL,
481
- endUrl: demoURL,
661
+ endText: flags.url,
662
+ endUrl: flags.url,
482
663
  }, output, flags.music);
483
664
 
484
665
  if (!noShare) {
485
- await shareFlow(resolve(output), videoTitle, prompt);
666
+ await shareFlow(resolve(output), videoTitle, flags.url);
486
667
  }
487
668
  return;
488
669
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentreel",
3
- "version": "0.3.5",
3
+ "version": "0.4.1",
4
4
  "description": "Turn your web apps and CLIs into viral clips",
5
5
  "bin": {
6
6
  "agentreel": "./bin/agentreel.mjs"
@@ -198,7 +198,7 @@ def record_demo(steps: list[dict], workdir: str, output_path: str):
198
198
 
199
199
 
200
200
  def extract_highlights(cast_path: str, context: str, guidelines: str = "") -> list[dict]:
201
- """Ask Claude to pick 3-4 highlight moments from the recorded session."""
201
+ """Ask Claude to pick highlight moments from the recorded session."""
202
202
  # Read the asciicast and strip to just the text content
203
203
  lines_output = []
204
204
  with open(cast_path) as f:
@@ -218,7 +218,29 @@ def extract_highlights(cast_path: str, context: str, guidelines: str = "") -> li
218
218
 
219
219
  guidelines_block = f"\n\nAdditional guidelines: {guidelines}" if guidelines else ""
220
220
 
221
- prompt = f"""You are creating a highlights reel for a CLI tool demo video. Here is the full terminal output:
221
+ # Demo mode: more chapters, more lines, show full flows
222
+ is_demo = "demo" in guidelines.lower() if guidelines else False
223
+
224
+ if is_demo:
225
+ prompt = f"""You are creating chapter-based highlights for a demo walkthrough video. Here is the full terminal output:
226
+
227
+ ---
228
+ {clean[:6000]}
229
+ ---
230
+
231
+ Context: {context}{guidelines_block}
232
+
233
+ Create 4-6 chapters that walk through the full demo. Each chapter shows a complete command and its output. For each chapter, return:
234
+ - "label": chapter name (1-3 words) like "Setup", "Run Command", "View Results", "Verify"
235
+ - "lines": array of objects, each with "text" (string), and optionally "color" (hex), "bold" (bool), "dim" (bool), "isPrompt" (bool if it's a shell command)
236
+
237
+ Each chapter should have 12-20 lines. Show the COMPLETE command and output for each step.
238
+ Include the prompt line (isPrompt: true) followed by the actual output.
239
+ Use these colors: green="#50fa7b", yellow="#f1fa8c", purple="#bd93f9", red="#ff5555", dim="#6272a4", white="#f8f8f2"
240
+
241
+ Return ONLY a JSON array. No markdown fences."""
242
+ else:
243
+ prompt = f"""You are creating a highlights reel for a CLI tool demo video. Here is the full terminal output:
222
244
 
223
245
  ---
224
246
  {clean[:3000]}