agentreel 0.3.3 → 0.3.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/bin/agentreel.mjs CHANGED
@@ -30,6 +30,7 @@ function parseArgs() {
30
30
  else if (arg === "--output" || arg === "-o") flags.output = args[++i];
31
31
  else if (arg === "--music") flags.music = args[++i];
32
32
  else if (arg === "--auth" || arg === "-a") flags.auth = args[++i];
33
+ else if (arg === "--guidelines" || arg === "-g") flags.guidelines = args[++i];
33
34
  else if (arg === "--no-share") flags.noShare = true;
34
35
  }
35
36
  return flags;
@@ -49,6 +50,7 @@ Flags:
49
50
  -t, --title <text> video title
50
51
  -o, --output <file> output file (default: agentreel.mp4)
51
52
  -a, --auth <file> Playwright storage state (cookies/auth) for browser demos
53
+ -g, --guidelines <text> guidelines for highlight generation (e.g. "focus on speed")
52
54
  --music <file> path to background music mp3
53
55
  --no-share skip the share prompt
54
56
  -h, --help show help
@@ -98,26 +100,28 @@ function ensureBrowserDeps() {
98
100
  });
99
101
  }
100
102
 
101
- function recordCLI(command, workDir, context) {
103
+ function recordCLI(command, workDir, context, guidelines) {
102
104
  const python = findPython();
103
105
  const script = join(ROOT, "scripts", "cli_demo.py");
104
106
  const outFile = join(tmpdir(), "agentreel-cli-demo.cast");
105
107
 
106
108
  const args = [script, command, workDir, outFile];
107
109
  if (context) args.push(context);
110
+ if (guidelines) args.push(guidelines);
108
111
 
109
112
  console.error(`Agent planning CLI demo for: ${command}`);
110
113
  execFileSync(python, args, { stdio: ["ignore", "inherit", "inherit"], env: process.env });
111
114
  return outFile;
112
115
  }
113
116
 
114
- function extractHighlightsFromCast(castPath, context) {
117
+ function extractHighlightsFromCast(castPath, context, guidelines) {
115
118
  const python = findPython();
116
119
  const script = join(ROOT, "scripts", "cli_demo.py");
117
120
  const outFile = castPath + "-highlights.json";
118
121
 
119
122
  const args = [script, "--highlights", castPath, outFile];
120
123
  if (context) args.push(context);
124
+ if (guidelines) args.push(guidelines);
121
125
 
122
126
  execFileSync(python, args, { stdio: ["ignore", "inherit", "inherit"], env: process.env });
123
127
  return outFile;
@@ -130,7 +134,7 @@ function browserEnv() {
130
134
  return { ...process.env, PLAYWRIGHT_BROWSERS_PATH: browsersDir };
131
135
  }
132
136
 
133
- function recordBrowser(url, task, authState) {
137
+ function recordBrowser(url, task, authState, guidelines) {
134
138
  const python = findPython();
135
139
  const script = join(ROOT, "scripts", "browser_demo.py");
136
140
  const outFile = join(tmpdir(), "agentreel-browser-demo.mp4");
@@ -138,6 +142,7 @@ function recordBrowser(url, task, authState) {
138
142
  console.error(`Agent demoing browser app: ${url}`);
139
143
  const args = [script, url, outFile, task];
140
144
  if (authState) args.push("--auth", authState);
145
+ if (guidelines) args.push("--guidelines", guidelines);
141
146
  execFileSync(python, args, {
142
147
  stdio: ["ignore", "inherit", "inherit"],
143
148
  env: browserEnv(),
@@ -160,12 +165,28 @@ function extractBrowserHighlights(videoPath, task) {
160
165
 
161
166
  // ── Browser Highlight Builder ───────────────────────────────
162
167
 
163
- function buildBrowserHighlights(clicks, videoPath, task) {
168
+ function buildBrowserHighlights(clicks, videoPath, task, guidelines) {
164
169
  const CLIP_DUR = 7;
165
170
  const MIN_HIGHLIGHTS = 3;
166
171
  const MAX_HIGHLIGHTS = 4;
167
- const labels = ["Overview", "Interact", "Navigate", "Result"];
168
- const overlays = ["**First look**", "**Key action**", "**Exploring**", "**The result**"];
172
+ // Ask Claude to generate labels/overlays based on the task
173
+ let labels, overlays;
174
+ try {
175
+ const guidelinesLine = guidelines ? `\nGuidelines: ${guidelines}` : "";
176
+ const genPrompt = `Generate exactly 4 highlight labels and overlay captions for a short browser demo video.
177
+ Task: ${task}${guidelinesLine}
178
+
179
+ Return a JSON object: {"labels": ["word1", "word2", "word3", "word4"], "overlays": ["**caption1**", "**caption2**", "**caption3**", "**caption4**"]}
180
+ Labels: 1-2 words each, specific to this app (not generic). Overlays: short punchy captions with **markdown bold** for emphasis. Return ONLY JSON.`;
181
+ const result = execFileSync("claude", ["-p", genPrompt, "--output-format", "text"], {
182
+ encoding: "utf-8", timeout: 30000, stdio: ["ignore", "pipe", "ignore"],
183
+ }).trim();
184
+ const parsed = JSON.parse(result.replace(/```json?\n?/g, "").replace(/```/g, "").trim());
185
+ labels = parsed.labels?.length >= 4 ? parsed.labels : null;
186
+ overlays = parsed.overlays?.length >= 4 ? parsed.overlays : null;
187
+ } catch { /* fall through */ }
188
+ if (!labels) labels = ["Overview", "Interact", "Navigate", "Result"];
189
+ if (!overlays) overlays = ["**First look**", "**Key action**", "**Exploring**", "**The result**"];
169
190
 
170
191
  // Estimate video duration from last click or default to 25s
171
192
  const lastClickTime = clicks.length > 0 ? clicks[clicks.length - 1].timeSec : 0;
@@ -406,10 +427,10 @@ async function main() {
406
427
 
407
428
  if (demoCmd) {
408
429
  console.error("Step 1/3: Recording CLI demo...");
409
- const castPath = recordCLI(demoCmd, process.cwd(), prompt);
430
+ const castPath = recordCLI(demoCmd, process.cwd(), prompt, flags.guidelines);
410
431
 
411
432
  console.error("Step 2/3: Extracting highlights...");
412
- const highlightsPath = extractHighlightsFromCast(castPath, prompt);
433
+ const highlightsPath = extractHighlightsFromCast(castPath, prompt, flags.guidelines);
413
434
  const highlights = JSON.parse(readFileSync(highlightsPath, "utf-8"));
414
435
  console.error(` ${highlights.length} highlights extracted`);
415
436
 
@@ -432,7 +453,7 @@ async function main() {
432
453
 
433
454
  ensureBrowserDeps();
434
455
  console.error("Step 1/3: Recording browser demo...");
435
- const videoPath = recordBrowser(demoURL, task, flags.auth);
456
+ const videoPath = recordBrowser(demoURL, task, flags.auth, flags.guidelines);
436
457
 
437
458
  // Copy video to Remotion public dir so it can be served
438
459
  const publicDir = join(ROOT, "public");
@@ -449,7 +470,7 @@ async function main() {
449
470
  console.error(` ${allClicks.length} clicks captured`);
450
471
  }
451
472
 
452
- const highlights = buildBrowserHighlights(allClicks, videoPath, task);
473
+ const highlights = buildBrowserHighlights(allClicks, videoPath, task, flags.guidelines);
453
474
 
454
475
  console.error("Step 3/3: Rendering video...");
455
476
  await renderVideo({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentreel",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Turn your web apps and CLIs into viral clips",
5
5
  "bin": {
6
6
  "agentreel": "./bin/agentreel.mjs"
@@ -30,11 +30,13 @@ def find_claude():
30
30
  return "claude"
31
31
 
32
32
 
33
- def generate_playwright_script(url, task):
33
+ def generate_playwright_script(url, task, guidelines=""):
34
34
  """Use claude CLI to generate a Playwright demo script."""
35
+ guidelines_part = f"IMPORTANT guidelines: {guidelines}. " if guidelines else ""
35
36
  prompt = (
36
37
  f"Generate a Playwright Python async function that demos a web app at {url}. "
37
38
  f"Task: {task}. "
39
+ f"{guidelines_part}"
38
40
  f"The function signature is: async def demo(page). "
39
41
  f"Navigate to the URL, wait for load, interact with key features — "
40
42
  f"click buttons, fill forms, scroll. Take about 20 seconds total. "
@@ -124,12 +126,12 @@ def extract_highlights(video_path, task):
124
126
  return highlights
125
127
 
126
128
 
127
- async def record_browser_demo(url, task, output_path, auth_state=None):
129
+ async def record_browser_demo(url, task, output_path, auth_state=None, guidelines=""):
128
130
  """Generate and run a Playwright demo with video recording."""
129
131
  from playwright.async_api import async_playwright
130
132
 
131
133
  print(f"Generating demo script for {url}...", file=sys.stderr)
132
- script_code = generate_playwright_script(url, task)
134
+ script_code = generate_playwright_script(url, task, guidelines)
133
135
  print(f"Script ready ({len(script_code)} chars)", file=sys.stderr)
134
136
 
135
137
  video_dir = tempfile.mkdtemp()
@@ -269,12 +271,16 @@ if __name__ == "__main__":
269
271
  # Parse remaining args: [task] [--auth <state_file>]
270
272
  task = "Explore the main features"
271
273
  auth_state = None
274
+ guidelines = ""
272
275
  i = 3
273
276
  while i < len(sys.argv):
274
277
  if sys.argv[i] == "--auth" and i + 1 < len(sys.argv):
275
278
  auth_state = sys.argv[i + 1]
276
279
  i += 2
280
+ elif sys.argv[i] == "--guidelines" and i + 1 < len(sys.argv):
281
+ guidelines = sys.argv[i + 1]
282
+ i += 2
277
283
  else:
278
284
  task = sys.argv[i]
279
285
  i += 1
280
- asyncio.run(record_browser_demo(url, task, output, auth_state=auth_state))
286
+ asyncio.run(record_browser_demo(url, task, output, auth_state=auth_state, guidelines=guidelines))
@@ -33,12 +33,14 @@ def find_claude():
33
33
  return "claude"
34
34
 
35
35
 
36
- def generate_demo_plan(command: str, context: str) -> list[dict]:
36
+ def generate_demo_plan(command: str, context: str, guidelines: str = "") -> list[dict]:
37
37
  """Use claude CLI to plan a demo sequence."""
38
+ guidelines_block = f"\n\nIMPORTANT guidelines you MUST follow:\n{guidelines}" if guidelines else ""
39
+
38
40
  prompt = f"""You are planning a terminal demo for a CLI tool. The tool is invoked with: {command}
39
41
 
40
42
  Context about what this tool does:
41
- {context}
43
+ {context}{guidelines_block}
42
44
 
43
45
  Generate a JSON array of demo steps. Each step is an object with:
44
46
  - "type": "command" (run a shell command)
@@ -195,7 +197,7 @@ def record_demo(steps: list[dict], workdir: str, output_path: str):
195
197
  print(f"Saved: {output_path}", file=sys.stderr)
196
198
 
197
199
 
198
- def extract_highlights(cast_path: str, context: str) -> list[dict]:
200
+ def extract_highlights(cast_path: str, context: str, guidelines: str = "") -> list[dict]:
199
201
  """Ask Claude to pick 3-4 highlight moments from the recorded session."""
200
202
  # Read the asciicast and strip to just the text content
201
203
  lines_output = []
@@ -214,13 +216,15 @@ def extract_highlights(cast_path: str, context: str) -> list[dict]:
214
216
  # Clean ANSI for Claude to read, but keep the raw for display
215
217
  clean = re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', raw_output)
216
218
 
219
+ guidelines_block = f"\n\nAdditional guidelines: {guidelines}" if guidelines else ""
220
+
217
221
  prompt = f"""You are creating a highlights reel for a CLI tool demo video. Here is the full terminal output:
218
222
 
219
223
  ---
220
224
  {clean[:3000]}
221
225
  ---
222
226
 
223
- Context: {context}
227
+ Context: {context}{guidelines_block}
224
228
 
225
229
  Pick 3-4 highlight moments that would look impressive in a short video. For each highlight, return:
226
230
  - "label": short label (1-2 words) like "Initialize", "Configure", "Run", "Results"
@@ -274,8 +278,9 @@ if __name__ == "__main__":
274
278
  cast_file = sys.argv[2]
275
279
  output = sys.argv[3]
276
280
  context = sys.argv[4] if len(sys.argv) > 4 else ""
281
+ guidelines = sys.argv[5] if len(sys.argv) > 5 else ""
277
282
  print(f"Extracting highlights from: {cast_file}", file=sys.stderr)
278
- highlights = extract_highlights(cast_file, context)
283
+ highlights = extract_highlights(cast_file, context, guidelines)
279
284
  with open(output, "w") as f:
280
285
  json.dump(highlights, f, indent=2)
281
286
  print(f"Saved {len(highlights)} highlights to: {output}", file=sys.stderr)
@@ -286,9 +291,10 @@ if __name__ == "__main__":
286
291
  workdir = sys.argv[2]
287
292
  output = sys.argv[3]
288
293
  context = sys.argv[4] if len(sys.argv) > 4 else ""
294
+ guidelines = sys.argv[5] if len(sys.argv) > 5 else ""
289
295
 
290
296
  print(f"Planning demo for: {command}", file=sys.stderr)
291
- steps = generate_demo_plan(command, context)
297
+ steps = generate_demo_plan(command, context, guidelines)
292
298
  print(f"Generated {len(steps)} steps:", file=sys.stderr)
293
299
  for s in steps:
294
300
  print(f" $ {s['value']} — {s.get('description', '')}", file=sys.stderr)
@@ -299,7 +305,7 @@ if __name__ == "__main__":
299
305
  # Extract highlights from the recording
300
306
  highlights_path = output.replace(".cast", "-highlights.json")
301
307
  print("Extracting highlights...", file=sys.stderr)
302
- highlights = extract_highlights(output, context)
308
+ highlights = extract_highlights(output, context, guidelines)
303
309
  with open(highlights_path, "w") as f:
304
310
  json.dump(highlights, f, indent=2)
305
311
  print(f"Saved {len(highlights)} highlights to: {highlights_path}", file=sys.stderr)