agentreel 0.1.1 → 0.1.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/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  Turn your Claude Code sessions into viral demo videos.
4
4
 
5
- https://github.com/user-attachments/assets/070ee610-298c-4989-8d7e-369ca495469e
5
+ https://github.com/user-attachments/assets/474fd85d-3b35-48f4-82b8-1b337840fb51
6
+
7
+ > 🔊 Turn on sound
6
8
 
7
9
  ## Install
8
10
 
package/bin/agentreel.mjs CHANGED
@@ -258,7 +258,7 @@ function extractBrowserHighlights(videoPath, task) {
258
258
 
259
259
  // ── Render ──────────────────────────────────────────────────
260
260
 
261
- function renderVideo(props, output, musicPath) {
261
+ async function renderVideo(props, output, musicPath) {
262
262
  const publicDir = join(ROOT, "public");
263
263
  if (!existsSync(publicDir)) mkdirSync(publicDir, { recursive: true });
264
264
  if (musicPath && existsSync(musicPath)) {
@@ -267,11 +267,37 @@ function renderVideo(props, output, musicPath) {
267
267
 
268
268
  const absOutput = resolve(output);
269
269
  const propsJSON = JSON.stringify(props);
270
- const remotion = join(ROOT, "node_modules", ".bin", "remotion");
271
270
 
272
- execFileSync(remotion, ["render", "CastVideo", absOutput, "--props", propsJSON], {
273
- cwd: ROOT,
274
- stdio: ["ignore", "inherit", "inherit"],
271
+ // Render using Remotion's Node.js API — no CLI binary needed
272
+ const { bundle } = await import("@remotion/bundler");
273
+ const { renderMedia, selectComposition } = await import("@remotion/renderer");
274
+
275
+ const entryPoint = join(ROOT, "src", "index.ts");
276
+
277
+ console.error(" Bundling...");
278
+ const serveUrl = await bundle({
279
+ entryPoint,
280
+ webpackOverride: (config) => config,
281
+ });
282
+
283
+ console.error(" Preparing renderer...");
284
+ const composition = await selectComposition({
285
+ serveUrl,
286
+ id: "CastVideo",
287
+ inputProps: props,
288
+ onBrowserDownload: () => {
289
+ console.error(" Downloading renderer (one-time, ~90MB)...");
290
+ return () => {}; // suppress progress logs
291
+ },
292
+ });
293
+
294
+ console.error(" Rendering...");
295
+ await renderMedia({
296
+ composition,
297
+ serveUrl,
298
+ codec: "h264",
299
+ outputLocation: absOutput,
300
+ inputProps: props,
275
301
  });
276
302
 
277
303
  const size = statSync(absOutput).size;
@@ -280,46 +306,40 @@ function renderVideo(props, output, musicPath) {
280
306
 
281
307
  // ── Upload + Share ──────────────────────────────────────────
282
308
 
283
- async function uploadToStreamable(filePath) {
284
- const { FormData, File } = await import("node:buffer")
285
- .then(() => globalThis)
286
- .catch(() => globalThis);
287
-
288
- const fileBuffer = readFileSync(filePath);
289
- const fileName = basename(filePath);
290
-
291
- // Use multipart form upload via fetch
292
- const boundary = "----agentreel" + Date.now();
293
- const CRLF = "\r\n";
294
-
295
- const header = [
296
- `--${boundary}`,
297
- `Content-Disposition: form-data; name="file"; filename="${fileName}"`,
298
- "Content-Type: video/mp4",
299
- "",
300
- ].join(CRLF);
301
-
302
- const footer = `${CRLF}--${boundary}--${CRLF}`;
303
-
304
- const headerBuf = Buffer.from(header + CRLF);
305
- const footerBuf = Buffer.from(footer);
306
- const body = Buffer.concat([headerBuf, fileBuffer, footerBuf]);
307
-
308
- const resp = await fetch("https://api.streamable.com/upload", {
309
- method: "POST",
310
- headers: {
311
- "Content-Type": `multipart/form-data; boundary=${boundary}`,
312
- },
313
- body,
314
- });
315
-
316
- if (!resp.ok) {
317
- const text = await resp.text();
318
- throw new Error(`Streamable upload failed (${resp.status}): ${text}`);
309
+ async function uploadVideo(filePath) {
310
+ // Try uploading via curl to Streamable (works if user has STREAMABLE_EMAIL/PASSWORD set)
311
+ const email = process.env.STREAMABLE_EMAIL;
312
+ const password = process.env.STREAMABLE_PASSWORD;
313
+
314
+ if (email && password) {
315
+ try {
316
+ const result = execFileSync("curl", [
317
+ "-s", "-u", `${email}:${password}`,
318
+ "-F", `file=@${filePath}`,
319
+ "https://api.streamable.com/upload",
320
+ ], { timeout: 60000 });
321
+ const data = JSON.parse(result.toString());
322
+ if (data.shortcode) {
323
+ return `https://streamable.com/${data.shortcode}`;
324
+ }
325
+ } catch { /* fall through */ }
319
326
  }
320
327
 
321
- const data = await resp.json();
322
- return `https://streamable.com/${data.shortcode}`;
328
+ // Try Imgur as fallback (supports anonymous video upload)
329
+ try {
330
+ const result = execFileSync("curl", [
331
+ "-s",
332
+ "-H", "Authorization: Client-ID 546c25a59c58ad7",
333
+ "-F", `video=@${filePath}`,
334
+ "https://api.imgur.com/3/upload",
335
+ ], { timeout: 120000 });
336
+ const data = JSON.parse(result.toString());
337
+ if (data.data?.link) {
338
+ return data.data.link;
339
+ }
340
+ } catch { /* fall through */ }
341
+
342
+ return null;
323
343
  }
324
344
 
325
345
  function openShareURL(videoURL, text) {
@@ -353,14 +373,25 @@ async function shareFlow(outputPath, title) {
353
373
  const shouldShare = await askYesNo("Share to Twitter? [Y/n] ");
354
374
  if (!shouldShare) return;
355
375
 
356
- console.error("Uploading to Streamable...");
357
- try {
358
- const url = await uploadToStreamable(outputPath);
359
- const text = `${title}\n\nMade with @agentreel`;
376
+ console.error("Uploading video...");
377
+ const url = await uploadVideo(outputPath);
378
+
379
+ if (url) {
380
+ const text = `${title}\n\nMade with agentreel`;
360
381
  openShareURL(url, text);
361
- } catch (err) {
362
- console.error(`Upload failed: ${err.message}`);
363
- console.error("You can manually upload the video and share it.");
382
+ } else {
383
+ // No upload worked — open Twitter with just the text, user attaches video manually
384
+ console.error("Could not auto-upload. Opening Twitter — drag your video into the tweet.");
385
+ const text = `${title}\n\nMade with agentreel`;
386
+ const tweetText = encodeURIComponent(text);
387
+ const intentURL = `https://twitter.com/intent/tweet?text=${tweetText}`;
388
+ const cmd = process.platform === "darwin" ? "open" : "xdg-open";
389
+ try {
390
+ execFileSync(cmd, [intentURL], { stdio: "ignore" });
391
+ } catch {
392
+ console.error(` Tweet link: ${intentURL}`);
393
+ }
394
+ console.error(` Video: ${resolve(outputPath)}`);
364
395
  }
365
396
  }
366
397
 
@@ -413,7 +444,7 @@ async function main() {
413
444
  console.error(` ${highlights.length} highlights extracted`);
414
445
 
415
446
  console.error("Step 3/3: Rendering video...");
416
- renderVideo({
447
+ await renderVideo({
417
448
  title: videoTitle,
418
449
  subtitle: prompt,
419
450
  highlights,
@@ -443,7 +474,7 @@ async function main() {
443
474
  console.error(` ${highlights.length} highlights extracted`);
444
475
 
445
476
  console.error("Step 3/3: Rendering video...");
446
- renderVideo({
477
+ await renderVideo({
447
478
  title: videoTitle,
448
479
  subtitle: prompt,
449
480
  highlights,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentreel",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Turn Claude Code sessions into viral demo videos",
5
5
  "bin": {
6
6
  "agentreel": "./bin/agentreel.mjs"
@@ -10,6 +10,8 @@
10
10
  "render": "remotion render CastVideo out/cast.mp4"
11
11
  },
12
12
  "dependencies": {
13
+ "@remotion/bundler": "^4",
14
+ "@remotion/renderer": "^4",
13
15
  "@remotion/cli": "^4",
14
16
  "remotion": "^4",
15
17
  "react": "^18",
package/src/CastVideo.tsx CHANGED
@@ -115,7 +115,7 @@ export const CastVideo: React.FC<CastProps> = ({
115
115
  letterSpacing: 2,
116
116
  }}
117
117
  >
118
- made with ♥ by agentreel
118
+ made with agentreel
119
119
  </div>
120
120
 
121
121
  <MusicTrack />
@@ -1010,7 +1010,7 @@ const EndCard: React.FC<{ text: string; url?: string }> = ({ text, url }) => {
1010
1010
  letterSpacing: 3,
1011
1011
  }}
1012
1012
  >
1013
- made with ♥ by agentreel
1013
+ made with agentreel
1014
1014
  </div>
1015
1015
  </AbsoluteFill>
1016
1016
  );