repovid 0.1.1 → 0.2.0

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
@@ -9,8 +9,8 @@ npx repovid login
9
9
 
10
10
  Then open Claude Code or Codex in your project and ask for a RepoVid walkthrough or demo.
11
11
 
12
- Your first run is free. After that, `npx repovid upgrade` unlocks unlimited videos for $5, once.
12
+ Your first run is free. `npx repovid upgrade` unlocks unlimited local videos for $5, once. `npx repovid plans` adds a hosted link you can share.
13
13
 
14
14
  RepoVid records and renders on your machine. It needs Node 20 or newer and a web app running locally with demo data.
15
15
 
16
- More at https://repovid.vercel.app. Support: repovid.support@gmail.com.
16
+ More at https://repovid.carles-mallafre.workers.dev. Support: repovid.support@gmail.com.
package/bin/repovid.mjs CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
- // repovid login | upgrade | doctor | init | schema | inspect <path> [--role r] | validate <plan.json> | render <plan.json> [--only a,b] [--out dir]
2
+ // repovid login | upgrade | plans | publish | doctor | init | schema | inspect <path> [--role r] | validate <plan.json> | render <plan.json> [--only a,b] [--out dir]
3
3
  import { execFileSync, spawnSync } from "node:child_process";
4
4
  import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
5
  import { homedir } from "node:os";
6
6
  import { createRequire } from "node:module";
7
7
  import { dirname, join, resolve } from "node:path";
8
8
  import ffmpegPath from "ffmpeg-static";
9
- import { completeExport, login, requestExport, upgrade } from "../src/account.mjs";
9
+ import { completeExport, login, plans, requestExport, upgrade } from "../src/account.mjs";
10
+ import { publish } from "../src/publish.mjs";
10
11
  import { writeGuide } from "../src/guide.mjs";
11
12
  import { validatePlan, validateProject } from "../src/plan.mjs";
12
13
  import { inspectScreen, launch, recordVideo } from "../src/record.mjs";
@@ -134,10 +135,12 @@ process.on("uncaughtException", e => { console.error(e.message); process.exit(1)
134
135
  if (command === "doctor") await doctor();
135
136
  else if (command === "login") await login();
136
137
  else if (command === "upgrade") await upgrade();
138
+ else if (command === "plans") await plans(file);
139
+ else if (command === "publish" && file) await publish(file, flag("out") ?? "repovid-output", rest.includes("--public-link"));
137
140
  else if (command === "init") init();
138
141
  else if (command === "schema") process.stdout.write(readFileSync(asset("schema.md"), "utf8"));
139
142
  else if (command === "inspect") await inspect();
140
143
  else if (command === "validate" && file) { loadPlan(); console.log("plan is valid"); }
141
144
  else if (command === "render" && file) await render();
142
- else { console.error("usage: repovid login | upgrade | doctor | init | schema | inspect <path> [--role role] | validate <plan.json> | render <plan.json> [--only a,b] [--out dir]"); process.exit(2); }
145
+ else { console.error("usage: repovid login | upgrade | plans [name] | publish <plan.json> --public-link | doctor | init | schema | inspect <path> [--role role] | validate <plan.json> | render <plan.json> [--only a,b] [--out dir]"); process.exit(2); }
143
146
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "repovid",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Turn your web app into narrated demo videos and a guide page, using the coding agent you already have.",
5
- "homepage": "https://repovid.vercel.app",
5
+ "homepage": "https://repovid.carles-mallafre.workers.dev",
6
6
  "license": "UNLICENSED",
7
7
  "keywords": [
8
8
  "demo",
package/src/account.mjs CHANGED
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import { spawn } from "node:child_process";
6
6
 
7
- const API = process.env.REPOVID_API ?? "https://repovid.vercel.app";
7
+ const API = process.env.REPOVID_API ?? "https://repovid.carles-mallafre.workers.dev";
8
8
  const AUTH_FILE = join(homedir(), ".config", "repovid", "auth.json");
9
9
  const POLL_MS = 2000, LOGIN_MS = 10 * 60 * 1000;
10
10
 
@@ -47,8 +47,22 @@ export async function login() {
47
47
  throw new Error("Sign-in expired. Run repovid login again.");
48
48
  }
49
49
 
50
+ export const authed = () => { if (!readToken()) throw new Error("Sign in first with repovid login."); };
51
+ export const api = call;
52
+
53
+ export async function plans(plan) {
54
+ authed();
55
+ if (!plan) {
56
+ console.log("Hosting plans, billed yearly:\n solo 3 projects, 30 videos each, 5 GB $12\n pro 10 projects, 50 videos each, 25 GB $29\n team 50 projects, 100 videos each, 100 GB $79\nChoose one: repovid plans <name>");
57
+ return;
58
+ }
59
+ const { url } = await call("/api/hosting/checkout", { plan });
60
+ console.log(`Opening ${url}`);
61
+ openBrowser(url);
62
+ }
63
+
50
64
  export async function upgrade() {
51
- if (!readToken()) throw new Error("Sign in first with repovid login.");
65
+ authed();
52
66
  const { url } = await call("/api/checkout");
53
67
  console.log(`Opening ${url}`);
54
68
  openBrowser(url);
@@ -0,0 +1,48 @@
1
+ // Sends structured guide text and rendered files to RepoVid. The plan's actions never leave the machine.
2
+ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { api, authed } from "./account.mjs";
5
+
6
+ const stateName = ".published.json";
7
+ const put = async (url, file, type) => {
8
+ const res = await fetch(url, { method: "PUT", body: readFileSync(file), headers: { "content-type": type } });
9
+ if (!res.ok) throw new Error(`upload failed (${res.status})`);
10
+ };
11
+
12
+ export async function publish(planFile, out, confirmed) {
13
+ authed();
14
+ if (!confirmed) throw new Error("Anyone with the link will be able to watch these videos. Review them first, then add --public-link.");
15
+ const outDir = resolve(out);
16
+ const plan = JSON.parse(readFileSync(planFile, "utf8"));
17
+ const stateFile = join(outDir, ".render.json");
18
+ if (!existsSync(stateFile)) throw new Error("Render the videos first.");
19
+ const rendered = new Map(JSON.parse(readFileSync(stateFile, "utf8")).map(r => [r.id, r]));
20
+ const videos = plan.videos.filter(v => rendered.has(v.id) && existsSync(join(outDir, "videos", `${v.id}.mp4`)));
21
+ if (!videos.length) throw new Error("Nothing to publish yet.");
22
+
23
+ const guide = {
24
+ mode: plan.mode, name: plan.project.name, summary: plan.project.summary, sections: plan.project.sections ?? [],
25
+ videos: videos.map(v => { const r = rendered.get(v.id); return { id: v.id, title: v.title, summary: v.summary, role: v.role, steps: v.steps, length: r.length, cues: r.cues }; }),
26
+ };
27
+ const publishedFile = join(outDir, stateName);
28
+ const slug = existsSync(publishedFile) ? JSON.parse(readFileSync(publishedFile, "utf8")).slug : undefined;
29
+ const bytes = videos.reduce((n, v) => n + statSync(join(outDir, "videos", `${v.id}.mp4`)).size + statSync(join(outDir, "guide", "media", `${v.id}.jpg`)).size, 0);
30
+ const draft = await api("/api/guides", { guide, slug, bytes });
31
+ // Saved before commit: if the reply is lost, the guide can still be updated or removed from here.
32
+ writeFileSync(publishedFile, JSON.stringify({ slug: draft.slug, url: draft.url }));
33
+ for (const u of draft.uploads) {
34
+ const mp4 = join(outDir, "videos", `${u.id}.mp4`);
35
+ if (statSync(mp4).size > draft.maxVideoBytes) throw new Error(`${u.id} is too large to publish.`);
36
+ await put(u.video, mp4, "video/mp4");
37
+ await put(u.poster, join(outDir, "guide", "media", `${u.id}.jpg`), "image/jpeg");
38
+ console.log(`uploaded ${u.id}`);
39
+ }
40
+ // Large projects finish over several calls.
41
+ for (;;) {
42
+ const r = await api("/api/guides/commit", { guideId: draft.guideId, version: draft.version });
43
+ if (!r.remaining) break;
44
+ console.log(`processing, ${r.remaining} left`);
45
+ }
46
+ console.log(`Published: ${draft.url}\nAnyone with this link can watch. Manage it at ${new URL(draft.url).origin}/app.`);
47
+ }
48
+
package/src/skill.md CHANGED
@@ -49,6 +49,7 @@ Run `npx repovid schema` for the format, then write `.repovid/plan.json`.
49
49
  - Each scene's `say` is what the narrator says while that scene's actions run. Plain, friendly English for the people who use this app. Explain why, not only what.
50
50
  - Fill `project.summary` and `project.sections` (title and text pairs). walkthrough: what it is, how it works, roles, high-level architecture. demo: what it is, who it's for, what problem it solves, how to get started.
51
51
  - Give each walkthrough video a `summary` and short `steps`.
52
+ - Project text may be published, so keep local URLs, file names, demo users and internal details out of it. "How to get started" should describe the product, not this machine.
52
53
  - Spell product names the way they sound if a voice would read them wrongly.
53
54
  - Use only fake people and contact details. Don't submit forms that send real emails or messages, and never touch billing.
54
55
 
@@ -62,3 +63,6 @@ Run `npx repovid render .repovid/plan.json`. If a video fails, inspect that scre
62
63
  Rendering really uses the app, so records a flow creates stay there. Before rendering a video again, tell the user which demo data to reset, because a second run can find duplicates or taken slots.
63
64
 
64
65
  Then tell the user to open `repovid-output/guide/index.html`, and to review every video before sharing, in case something private is on screen.
66
+
67
+ ## 7. Share (optional)
68
+ `npx repovid publish .repovid/plan.json --public-link` uploads the videos and guide text to RepoVid and returns a link. Anyone with the link can watch. Only run it after the user has watched every video and told you to publish. Publishing again from the same folder updates the same link and keeps earlier versions. The owner manages projects in the web dashboard. Publishing needs a hosting plan: `npx repovid plans`.