agentreel 0.1.2 → 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
@@ -4,6 +4,8 @@ Turn your Claude Code sessions into viral demo videos.
4
4
 
5
5
  https://github.com/user-attachments/assets/474fd85d-3b35-48f4-82b8-1b337840fb51
6
6
 
7
+ > 🔊 Turn on sound
8
+
7
9
  ## Install
8
10
 
9
11
  ```bash
package/bin/agentreel.mjs CHANGED
@@ -280,11 +280,15 @@ async function renderVideo(props, output, musicPath) {
280
280
  webpackOverride: (config) => config,
281
281
  });
282
282
 
283
- console.error(" Selecting composition...");
283
+ console.error(" Preparing renderer...");
284
284
  const composition = await selectComposition({
285
285
  serveUrl,
286
286
  id: "CastVideo",
287
287
  inputProps: props,
288
+ onBrowserDownload: () => {
289
+ console.error(" Downloading renderer (one-time, ~90MB)...");
290
+ return () => {}; // suppress progress logs
291
+ },
288
292
  });
289
293
 
290
294
  console.error(" Rendering...");
@@ -302,46 +306,40 @@ async function renderVideo(props, output, musicPath) {
302
306
 
303
307
  // ── Upload + Share ──────────────────────────────────────────
304
308
 
305
- async function uploadToStreamable(filePath) {
306
- const { FormData, File } = await import("node:buffer")
307
- .then(() => globalThis)
308
- .catch(() => globalThis);
309
-
310
- const fileBuffer = readFileSync(filePath);
311
- const fileName = basename(filePath);
312
-
313
- // Use multipart form upload via fetch
314
- const boundary = "----agentreel" + Date.now();
315
- const CRLF = "\r\n";
316
-
317
- const header = [
318
- `--${boundary}`,
319
- `Content-Disposition: form-data; name="file"; filename="${fileName}"`,
320
- "Content-Type: video/mp4",
321
- "",
322
- ].join(CRLF);
323
-
324
- const footer = `${CRLF}--${boundary}--${CRLF}`;
325
-
326
- const headerBuf = Buffer.from(header + CRLF);
327
- const footerBuf = Buffer.from(footer);
328
- const body = Buffer.concat([headerBuf, fileBuffer, footerBuf]);
329
-
330
- const resp = await fetch("https://api.streamable.com/upload", {
331
- method: "POST",
332
- headers: {
333
- "Content-Type": `multipart/form-data; boundary=${boundary}`,
334
- },
335
- body,
336
- });
337
-
338
- if (!resp.ok) {
339
- const text = await resp.text();
340
- 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 */ }
341
326
  }
342
327
 
343
- const data = await resp.json();
344
- 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;
345
343
  }
346
344
 
347
345
  function openShareURL(videoURL, text) {
@@ -375,14 +373,25 @@ async function shareFlow(outputPath, title) {
375
373
  const shouldShare = await askYesNo("Share to Twitter? [Y/n] ");
376
374
  if (!shouldShare) return;
377
375
 
378
- console.error("Uploading to Streamable...");
379
- try {
380
- const url = await uploadToStreamable(outputPath);
381
- 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`;
382
381
  openShareURL(url, text);
383
- } catch (err) {
384
- console.error(`Upload failed: ${err.message}`);
385
- 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)}`);
386
395
  }
387
396
  }
388
397
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentreel",
3
- "version": "0.1.2",
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"
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
  );