agentreel 0.3.1 → 0.3.3

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
@@ -29,6 +29,7 @@ function parseArgs() {
29
29
  else if (arg === "--title" || arg === "-t") flags.title = args[++i];
30
30
  else if (arg === "--output" || arg === "-o") flags.output = args[++i];
31
31
  else if (arg === "--music") flags.music = args[++i];
32
+ else if (arg === "--auth" || arg === "-a") flags.auth = args[++i];
32
33
  else if (arg === "--no-share") flags.noShare = true;
33
34
  }
34
35
  return flags;
@@ -47,6 +48,7 @@ Flags:
47
48
  -p, --prompt <text> description of what the tool does
48
49
  -t, --title <text> video title
49
50
  -o, --output <file> output file (default: agentreel.mp4)
51
+ -a, --auth <file> Playwright storage state (cookies/auth) for browser demos
50
52
  --music <file> path to background music mp3
51
53
  --no-share skip the share prompt
52
54
  -h, --help show help
@@ -128,13 +130,15 @@ function browserEnv() {
128
130
  return { ...process.env, PLAYWRIGHT_BROWSERS_PATH: browsersDir };
129
131
  }
130
132
 
131
- function recordBrowser(url, task) {
133
+ function recordBrowser(url, task, authState) {
132
134
  const python = findPython();
133
135
  const script = join(ROOT, "scripts", "browser_demo.py");
134
136
  const outFile = join(tmpdir(), "agentreel-browser-demo.mp4");
135
137
 
136
138
  console.error(`Agent demoing browser app: ${url}`);
137
- execFileSync(python, [script, url, outFile, task], {
139
+ const args = [script, url, outFile, task];
140
+ if (authState) args.push("--auth", authState);
141
+ execFileSync(python, args, {
138
142
  stdio: ["ignore", "inherit", "inherit"],
139
143
  env: browserEnv(),
140
144
  timeout: 300000,
@@ -428,7 +432,7 @@ async function main() {
428
432
 
429
433
  ensureBrowserDeps();
430
434
  console.error("Step 1/3: Recording browser demo...");
431
- const videoPath = recordBrowser(demoURL, task);
435
+ const videoPath = recordBrowser(demoURL, task, flags.auth);
432
436
 
433
437
  // Copy video to Remotion public dir so it can be served
434
438
  const publicDir = join(ROOT, "public");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentreel",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Turn your web apps and CLIs into viral clips",
5
5
  "bin": {
6
6
  "agentreel": "./bin/agentreel.mjs"
Binary file
@@ -54,7 +54,7 @@ def generate_playwright_script(url, task):
54
54
  [claude, "-p", prompt, "--output-format", "text"],
55
55
  capture_output=True,
56
56
  text=True,
57
- timeout=120,
57
+ timeout=300,
58
58
  )
59
59
 
60
60
  text = result.stdout.strip()
@@ -88,7 +88,7 @@ def extract_highlights(video_path, task):
88
88
  [claude, "-p", prompt, "--output-format", "text"],
89
89
  capture_output=True,
90
90
  text=True,
91
- timeout=120,
91
+ timeout=300,
92
92
  )
93
93
 
94
94
  text = result.stdout.strip()
@@ -124,7 +124,7 @@ def extract_highlights(video_path, task):
124
124
  return highlights
125
125
 
126
126
 
127
- async def record_browser_demo(url, task, output_path):
127
+ async def record_browser_demo(url, task, output_path, auth_state=None):
128
128
  """Generate and run a Playwright demo with video recording."""
129
129
  from playwright.async_api import async_playwright
130
130
 
@@ -137,11 +137,15 @@ async def record_browser_demo(url, task, output_path):
137
137
 
138
138
  async with async_playwright() as p:
139
139
  browser = await p.chromium.launch(headless=True)
140
- context = await browser.new_context(
140
+ ctx_opts = dict(
141
141
  viewport={"width": 1280, "height": 800},
142
142
  record_video_dir=video_dir,
143
143
  record_video_size={"width": 1280, "height": 800},
144
144
  )
145
+ if auth_state and os.path.isfile(auth_state):
146
+ ctx_opts["storage_state"] = auth_state
147
+ print(f"Using auth state: {auth_state}", file=sys.stderr)
148
+ context = await browser.new_context(**ctx_opts)
145
149
 
146
150
  # Inject click tracker — persists across navigations
147
151
  click_tracker_js = (
@@ -262,5 +266,15 @@ if __name__ == "__main__":
262
266
  else:
263
267
  url = sys.argv[1]
264
268
  output = sys.argv[2]
265
- task = sys.argv[3] if len(sys.argv) > 3 else "Explore the main features"
266
- asyncio.run(record_browser_demo(url, task, output))
269
+ # Parse remaining args: [task] [--auth <state_file>]
270
+ task = "Explore the main features"
271
+ auth_state = None
272
+ i = 3
273
+ while i < len(sys.argv):
274
+ if sys.argv[i] == "--auth" and i + 1 < len(sys.argv):
275
+ auth_state = sys.argv[i + 1]
276
+ i += 2
277
+ else:
278
+ task = sys.argv[i]
279
+ i += 1
280
+ asyncio.run(record_browser_demo(url, task, output, auth_state=auth_state))
@@ -4,10 +4,13 @@ CLI demo recorder. Uses `claude` CLI to plan the demo, then records it.
4
4
  Usage:
5
5
  python cli_demo.py <command> <workdir> <output_cast> [context]
6
6
  """
7
+ import getpass
7
8
  import json
8
9
  import os
9
10
  import pty
11
+ import re
10
12
  import select
13
+ import socket
11
14
  import subprocess
12
15
  import sys
13
16
  import time
@@ -97,7 +100,24 @@ def record_demo(steps: list[dict], workdir: str, output_path: str):
97
100
  }
98
101
  f.write(json.dumps(header) + "\n")
99
102
 
100
- def write_event(event_type: str, data: str):
103
+ # Build patterns to strip user identity from output
104
+ _user = getpass.getuser()
105
+ _host = socket.gethostname().split(".")[0]
106
+ _home = os.path.expanduser("~")
107
+ _title_seq = re.compile(r'\x1b\][\d;]*[^\x07\x1b]*(?:\x07|\x1b\\)')
108
+ _identity = re.compile(
109
+ r'|'.join(re.escape(s) for s in {_user, _host, _home} if s),
110
+ re.IGNORECASE,
111
+ )
112
+
113
+ def _sanitize(text: str) -> str:
114
+ text = _title_seq.sub('', text)
115
+ text = _identity.sub('user', text)
116
+ return text
117
+
118
+ def write_event(event_type: str, data: str, sanitize: bool = False):
119
+ if sanitize:
120
+ data = _sanitize(data)
101
121
  elapsed = time.time() - start_time
102
122
  f.write(json.dumps([round(elapsed, 6), event_type, data]) + "\n")
103
123
  f.flush()
@@ -114,18 +134,23 @@ def record_demo(steps: list[dict], workdir: str, output_path: str):
114
134
  write_event("o", f"\x1b[38;5;245m# {desc}\x1b[0m\r\n")
115
135
  time.sleep(0.3)
116
136
 
117
- # Type the command character by character
118
- write_event("o", "\x1b[38;5;76m$\x1b[0m ")
137
+ # Type the command character by character (no $ prompt — the renderer adds one)
138
+ write_event("o", "")
119
139
  for char in cmd:
120
140
  write_event("o", char)
121
141
  time.sleep(0.04)
122
142
  write_event("o", "\r\n")
123
143
  time.sleep(0.2)
124
144
 
125
- # Execute in PTY
145
+ # Execute in PTY with sanitized env (hide username/hostname)
126
146
  pid, fd = pty.fork()
127
147
  if pid == 0:
128
148
  os.chdir(workdir)
149
+ os.environ["PS1"] = "$ "
150
+ os.environ["PROMPT_COMMAND"] = ""
151
+ os.environ.pop("BASH_COMMAND", None)
152
+ # Suppress terminal title sequences (user@host)
153
+ os.environ["TERM"] = "dumb"
129
154
  os.execvp("/bin/sh", ["/bin/sh", "-c", cmd])
130
155
  else:
131
156
  deadline = time.time() + 15
@@ -135,7 +160,7 @@ def record_demo(steps: list[dict], workdir: str, output_path: str):
135
160
  try:
136
161
  data = os.read(fd, 4096)
137
162
  if data:
138
- write_event("o", data.decode("utf-8", errors="replace"))
163
+ write_event("o", data.decode("utf-8", errors="replace"), sanitize=True)
139
164
  else:
140
165
  break
141
166
  except OSError:
@@ -149,7 +174,7 @@ def record_demo(steps: list[dict], workdir: str, output_path: str):
149
174
  if r:
150
175
  data = os.read(fd, 4096)
151
176
  if data:
152
- write_event("o", data.decode("utf-8", errors="replace"))
177
+ write_event("o", data.decode("utf-8", errors="replace"), sanitize=True)
153
178
  else:
154
179
  break
155
180
  else:
@@ -187,7 +212,6 @@ def extract_highlights(cast_path: str, context: str) -> list[dict]:
187
212
 
188
213
  raw_output = "".join(lines_output)
189
214
  # Clean ANSI for Claude to read, but keep the raw for display
190
- import re
191
215
  clean = re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', raw_output)
192
216
 
193
217
  prompt = f"""You are creating a highlights reel for a CLI tool demo video. Here is the full terminal output:
package/src/CastVideo.tsx CHANGED
@@ -121,22 +121,6 @@ export const CastVideo: React.FC<CastProps> = ({
121
121
  {/* Subtle animated glow blobs in background */}
122
122
  <AnimatedBackground frame={frame} duration={durationInFrames} />
123
123
 
124
- {/* Global watermark — always visible */}
125
- <div
126
- style={{
127
- position: "absolute",
128
- top: 16,
129
- right: 20,
130
- zIndex: 5,
131
- fontFamily: MONO,
132
- fontSize: 11,
133
- color: "rgba(255,255,255,0.2)",
134
- letterSpacing: 2,
135
- }}
136
- >
137
- made with agentreel
138
- </div>
139
-
140
124
  <MusicTrack />
141
125
 
142
126
  <Sequence durationInFrames={titleFrames}>
@@ -673,17 +657,7 @@ const HighlightClip: React.FC<{
673
657
  backgroundColor: "#50fa7b",
674
658
  }}
675
659
  />
676
- <div
677
- style={{
678
- flex: 1,
679
- textAlign: "center",
680
- fontFamily: MONO,
681
- fontSize: 12,
682
- color: "rgba(255,255,255,0.25)",
683
- }}
684
- >
685
- Terminal
686
- </div>
660
+ <div style={{ flex: 1 }} />
687
661
  </div>
688
662
 
689
663
  {/* Terminal body */}
@@ -704,7 +678,9 @@ const HighlightClip: React.FC<{
704
678
  const lineOpacity = interpolate(lineSpring, [0, 1], [0, 1]);
705
679
  const lineX = interpolate(lineSpring, [0, 1], [12, 0]);
706
680
 
707
- let displayText = line.text;
681
+ // Strip leading "$ " from text — renderer adds its own $ prefix
682
+ const cleanText = line.isPrompt ? line.text.replace(/^\$\s*/, "") : line.text;
683
+ let displayText = cleanText;
708
684
  let isTyping = false;
709
685
  if (line.isPrompt) {
710
686
  const typingEnd = lineFrame + fps * 0.6;
@@ -715,9 +691,9 @@ const HighlightClip: React.FC<{
715
691
  [0, 1],
716
692
  { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
717
693
  );
718
- const chars = Math.floor(progress * line.text.length);
719
- displayText = line.text.slice(0, chars);
720
- isTyping = chars < line.text.length;
694
+ const chars = Math.floor(progress * cleanText.length);
695
+ displayText = cleanText.slice(0, chars);
696
+ isTyping = chars < cleanText.length;
721
697
  }
722
698
  }
723
699
 
@@ -1176,11 +1152,12 @@ const EndCard: React.FC<{ text: string; url?: string }> = ({ text, url }) => {
1176
1152
  <div
1177
1153
  style={{
1178
1154
  position: "absolute",
1179
- bottom: 40,
1155
+ top: 24,
1156
+ right: 28,
1180
1157
  opacity: brandSpring * 0.4,
1181
- transform: `translateY(${interpolate(brandSpring, [0, 1], [10, 0])}px)`,
1158
+ transform: `translateY(${interpolate(brandSpring, [0, 1], [-10, 0])}px)`,
1182
1159
  fontFamily: MONO,
1183
- fontSize: 13,
1160
+ fontSize: 16,
1184
1161
  color: DIM,
1185
1162
  letterSpacing: 3,
1186
1163
  }}
package/src/types.ts CHANGED
@@ -35,57 +35,52 @@ export interface CastProps {
35
35
  title: string; // big opening title
36
36
  subtitle?: string; // smaller text under title
37
37
  highlights: Highlight[];
38
- endText?: string; // closing CTA command, e.g. "npm install itsovertime"
39
- endUrl?: string; // URL shown under CTA, e.g. "github.com/islo-labs/overtime"
38
+ endText?: string; // closing CTA command, e.g. "npx agentreel"
39
+ endUrl?: string; // URL shown under CTA, e.g. "github.com/islo-labs/agentreel"
40
40
  gradient?: [string, string]; // background gradient colors
41
41
  }
42
42
 
43
43
  export const defaultProps: CastProps = {
44
- title: "itsovertime",
45
- subtitle: "Cron for AI agents",
44
+ title: "agentreel",
45
+ subtitle: "Turn your apps into viral clips",
46
46
  highlights: [
47
47
  {
48
- label: "Initialize",
48
+ label: "Record",
49
49
  overlay: "One command.",
50
50
  lines: [
51
- { text: "npx @islo-labs/overtime init", isPrompt: true },
51
+ { text: "npx agentreel --cmd 'my-cli-tool'", isPrompt: true },
52
52
  { text: "" },
53
- { text: " itsovertime Cron for AI agents", bold: true, color: "#bd93f9" },
53
+ { text: " agentreel Turn your apps into viral clips", bold: true, color: "#bd93f9" },
54
54
  { text: "" },
55
- { text: " ✓ Created overtime.yml", color: "#50fa7b" },
55
+ { text: " ✓ Recording CLI demo...", color: "#50fa7b" },
56
56
  ],
57
57
  },
58
58
  {
59
- label: "Configure",
60
- overlay: "Plain English schedules.",
59
+ label: "Highlight",
60
+ overlay: "AI picks the best moments.",
61
61
  lines: [
62
- { text: "cat overtime.yml", isPrompt: true },
63
- { text: "shifts:", dim: true },
64
- { text: " - name: pr-review", color: "#f8f8f2" },
65
- { text: ' schedule: "every hour"', color: "#50fa7b" },
66
- { text: ' task: "Review open PRs..."', color: "#50fa7b" },
67
- { text: " notify: slack", color: "#f8f8f2" },
62
+ { text: "Extracting highlights...", dim: true },
63
+ { text: "" },
64
+ { text: " 4 highlights extracted", color: "#50fa7b" },
65
+ { text: ' "Initialize" — first run', color: "#f8f8f2" },
66
+ { text: ' "Configure" setup step', color: "#f8f8f2" },
67
+ { text: ' "Run" — the wow moment', color: "#f1fa8c" },
68
68
  ],
69
- zoomLine: 3,
69
+ zoomLine: 2,
70
70
  },
71
71
  {
72
- label: "Run",
73
- overlay: "Fully autonomous.",
72
+ label: "Share",
73
+ overlay: "Ready to post.",
74
74
  lines: [
75
- { text: "npx @islo-labs/overtime", isPrompt: true },
75
+ { text: "Rendering video...", dim: true },
76
76
  { text: "" },
77
- { text: "┌─ itsovertime ───────────────────────────┐", color: "#bd93f9" },
78
- { text: "│ pr-review every hour ⟳ running │", color: "#f1fa8c" },
79
- { text: "│ dep-updates Mon at 2am idle │", dim: true },
80
- { text: "└──────────────────────────────────────────┘", color: "#bd93f9" },
77
+ { text: " Done: agentreel.mp4 (2.4 MB)", color: "#50fa7b" },
81
78
  { text: "" },
82
- { text: " PR #42 reviewed — approved", color: "#50fa7b" },
83
- { text: " ✓ PR #43 reviewed — changes requested", color: "#f1fa8c" },
79
+ { text: " Share to Twitter? [Y/n]", color: "#f8f8f2" },
84
80
  ],
85
- zoomLine: 3,
81
+ zoomLine: 2,
86
82
  },
87
83
  ],
88
- endText: "npx @islo-labs/overtime",
89
- endUrl: "github.com/islo-labs/overtime",
84
+ endText: "npx agentreel",
90
85
  gradient: ["#0f0f1a", "#1a0f2e"],
91
86
  };