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 +31 -10
- package/package.json +1 -1
- package/scripts/browser_demo.py +10 -4
- package/scripts/cli_demo.py +13 -7
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
|
-
|
|
168
|
-
|
|
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
package/scripts/browser_demo.py
CHANGED
|
@@ -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))
|
package/scripts/cli_demo.py
CHANGED
|
@@ -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)
|