demobites 1.0.9 → 1.4.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 +160 -9
- package/launcher/index.mjs +210 -0
- package/package.json +38 -10
- package/recorder/SKILL.md +30 -0
- package/recorder/STANDALONE-ENDING.md +34 -0
- package/recorder/scripts/frame.mjs +58 -0
- package/recorder/scripts/mux.mjs +74 -0
- package/recorder/scripts/post.sh +29 -0
- package/recorder/scripts/tts.mjs +72 -0
- package/scripts/calibrate.mjs +307 -0
- package/scripts/check-release.mjs +8 -0
- package/scripts/media-tools.mjs +93 -0
- package/scripts/record.mjs +1089 -0
- package/scripts/release.mjs +48 -0
- package/scripts/trim.mjs +170 -0
- package/skill/SKILL.md +450 -0
- package/skill/scripts/briefs.mjs +168 -0
- package/skill/scripts/cleanup.mjs +104 -0
- package/skill/scripts/login.mjs +157 -0
- package/skill/scripts/manifest.mjs +371 -0
- package/skill/scripts/retake.mjs +76 -0
- package/skill/scripts/rules.mjs +60 -0
- package/skill/scripts/stage-wait.mjs +113 -0
- package/skill/scripts/status.mjs +89 -0
- package/skill/scripts/upload.mjs +330 -0
- package/skill/scripts/vocab.mjs +88 -0
- package/index.mjs +0 -5
package/README.md
CHANGED
|
@@ -1,15 +1,166 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DemoBites Agentic Recorder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Ask your coding agent to record your product. Edit the result. Download the MP4.**
|
|
4
|
+
|
|
5
|
+
Your agent drives a real browser on your machine and records the flow you describe. Connect a free DemoBites account to turn the take into an editable video, with text-to-speech, zooms, captions, and MP4 export included.
|
|
6
|
+
|
|
7
|
+
**Sign in to your app locally. You do not hand DemoBites your app password or upload your recording browser profile.**
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx demobite@latest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Free Studio · Text-to-speech included · Unlimited exports · No watermark · No credit card**
|
|
14
|
+
|
|
15
|
+
[Website](https://agentic-recorder.com/) · [npm](https://www.npmjs.com/package/demobite) · [Report an issue](https://github.com/demobites/agentic-recorder/issues)
|
|
16
|
+
|
|
17
|
+
## See the workflow
|
|
18
|
+
|
|
19
|
+
https://github.com/user-attachments/assets/18293b85-82e2-475f-8d94-543f1f9bbc1d
|
|
20
|
+
|
|
21
|
+
An illustrated walkthrough of the flow, with waiting time compressed.
|
|
22
|
+
|
|
23
|
+
## Start with your own agent
|
|
24
|
+
|
|
25
|
+
You need a coding agent that can read skill instructions, run terminal commands, and control a local browser. A browser-only chat session cannot run this local workflow.
|
|
26
|
+
|
|
27
|
+
Before installing:
|
|
28
|
+
|
|
29
|
+
- **Node.js 18 or newer** and npm.
|
|
30
|
+
- **Google Chrome**, recommended. The recorder also supports Playwright's Chromium.
|
|
31
|
+
- A **free DemoBites account** for the Studio workflow below.
|
|
32
|
+
|
|
33
|
+
Run these commands from your project directory:
|
|
4
34
|
|
|
5
35
|
```bash
|
|
6
|
-
|
|
7
|
-
npx
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
npx
|
|
36
|
+
# Install the skill and check your setup.
|
|
37
|
+
npx demobite@latest
|
|
38
|
+
|
|
39
|
+
# Connect this project to your DemoBites workspace.
|
|
40
|
+
npx demobite login
|
|
11
41
|
```
|
|
12
42
|
|
|
13
|
-
|
|
43
|
+
The installer installs Playwright, ffmpeg and ffprobe beside the skill if they are not already there. It uses your own ffmpeg when a compatible build is on your PATH. It checks for Chrome and downloads Chromium on the first take if Chrome is missing.
|
|
44
|
+
|
|
45
|
+
To connect, open the link printed in your terminal and approve the connection in your browser. If you are signed out of DemoBites, sign in there first. You do not paste a password into the terminal.
|
|
46
|
+
|
|
47
|
+
Then ask your agent:
|
|
48
|
+
|
|
49
|
+
> Record a demo of inviting a teammate in my app at http://localhost:3000. Open Team settings, invite alex@example.com as an Admin, and show the confirmation. Show me the storyboard before recording.
|
|
50
|
+
|
|
51
|
+
Use your own app URL and a demo account. The agent performs real actions in your app, including sending an invitation if that is part of the approved flow.
|
|
52
|
+
|
|
53
|
+
### Which agent?
|
|
54
|
+
|
|
55
|
+
The installer currently places the skill at `~/.claude/skills/agentic-recorder/`, the Claude Code skill directory.
|
|
56
|
+
|
|
57
|
+
For Cursor or Codex, make the [recorder instructions](skill/SKILL.md) available to your agent and ensure it can run the installed scripts. The launcher currently does not install into separate Cursor or Codex skill directories. Connecting an MCP client alone does not install the recorder skill.
|
|
58
|
+
|
|
59
|
+
## From prompt to finished video
|
|
60
|
+
|
|
61
|
+
1. **Describe the flow.** Give your agent the app URL and what you want to demonstrate.
|
|
62
|
+
2. **Approve the storyboard.** Review the steps and estimated length before filming. If your app needs a login, sign in yourself in the recorder's local browser window.
|
|
63
|
+
3. **Let it record.** The agent checks the flow and films it. Recording normally runs in the background.
|
|
64
|
+
4. **Review the raw take.** Open the review link printed in the terminal. Check the footage, then approve it to create a Bite, an editable video project.
|
|
65
|
+
5. **Tweak and download.** In the Studio, change a sentence, generate its voice, adjust the zooms or timing, and export your MP4.
|
|
66
|
+
|
|
67
|
+
The storyboard's narration is a starting point. DemoBites rewrites and fits it to the footage; you can edit the final wording in the Studio.
|
|
68
|
+
|
|
69
|
+
## What connecting to DemoBites adds
|
|
70
|
+
|
|
71
|
+
The recorder supplies footage, action timings, cursor movements, and camera targets. The Studio uses that information to create a video you can keep editing.
|
|
72
|
+
|
|
73
|
+
| Included in the free Studio | What you can do |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| Script and text-to-speech | Edit the words and generate narration without recording your own voice. |
|
|
76
|
+
| Camera and cursor editing | Adjust automatic zooms, cursor appearance, and timing. |
|
|
77
|
+
| Video finishing | Trim the take, edit captions, and add your background, branding, intro, and outro. |
|
|
78
|
+
| MP4 export | Download Full HD video with narration and no DemoBites watermark. |
|
|
79
|
+
|
|
80
|
+
**The free Studio holds up to 8 Bites at a time, with unlimited exports. Invite three people who sign up and the Studio becomes unlimited, still free.** It has no trial clock and requires no credit card. You bring your own coding agent; its subscription or model usage is separate. An ElevenLabs key is optional, not required for the Studio's included text-to-speech.
|
|
81
|
+
|
|
82
|
+
You can use the exported video in your docs, website, release notes, or wherever you need it. Paid publishing and management features are optional. See [current plan details](https://www.demobites.com/pricing).
|
|
83
|
+
|
|
84
|
+
## One prompt, one result
|
|
85
|
+
|
|
86
|
+
> Record a demo of Funnels: where customers drop off, which channels work. Show the sidebar and graphs.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
https://github.com/user-attachments/assets/02eb7d3e-fe74-4c0d-b532-e16bfc3fa614
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
Recorded by the agent, then edited and narrated in the free Studio. This prompt assumes the agent already knows the app URL and can access the Funnels page.
|
|
95
|
+
|
|
96
|
+
## Your app login stays local
|
|
97
|
+
|
|
98
|
+
**You do not need to hand DemoBites your app password to record a signed-in flow.** Sign in yourself in the recorder's browser on your machine.
|
|
99
|
+
|
|
100
|
+
The recorder uses a separate, persistent Chrome profile at `.recorder/profile/`. Your app session is saved there for later takes. It does not automatically inherit the sessions in your everyday Chrome profile.
|
|
101
|
+
|
|
102
|
+
| Data | Where it goes |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| Your app's browser session | Stays in the local recording profile; the standard local workflow does not upload that profile to DemoBites. |
|
|
105
|
+
| DemoBites recorder key | Saved in `.recorder/config.json` and used to authenticate recorder requests to DemoBites. This is separate from your app login. |
|
|
106
|
+
| Footage and recording metadata | Uploaded to DemoBites for review and processing, including the storyboard, narration intent, cursor events, and camera targets. |
|
|
107
|
+
|
|
108
|
+
Recording happens locally. Studio processing and editing use DemoBites online. Anything visible or typed into the recorded flow can appear in the footage or recording metadata, so use suitable demo data. Your coding agent's own data handling still follows its provider and your settings.
|
|
109
|
+
|
|
110
|
+
Keep `.recorder/` and take directories out of version control. To disconnect the recorder, run `npx demobite logout`.
|
|
111
|
+
|
|
112
|
+
## Local output without a DemoBites account
|
|
113
|
+
|
|
114
|
+
The repository also contains a [standalone recorder skill](recorder/SKILL.md) and local finishing scripts. These produce a local MP4; optional voiceover uses your own ElevenLabs key.
|
|
115
|
+
|
|
116
|
+
This is an advanced, manual workflow. The default `npx demobite@latest` command installs the Studio-connected skill, not the standalone skill. The standalone finishing reference contains legacy instructions; use the standalone skill as the entry point rather than treating it as a second ending of the installed Studio skill.
|
|
117
|
+
|
|
118
|
+
## A few boundaries
|
|
119
|
+
|
|
120
|
+
- The agentic recorder films **browser workflows**. It does not record native desktop apps or phone screens.
|
|
121
|
+
- The Studio-connected skill is designed for short demos, usually **30–45 seconds**, with a **90-second maximum per Bite**. Split longer stories into separate videos.
|
|
122
|
+
- Some sites block automated browsers. The workflow may need a human sign-in or verification step, and some sites may refuse recording.
|
|
123
|
+
- Filming does not publish anything. You review the take before creating the Bite; public publishing is a separate action.
|
|
124
|
+
|
|
125
|
+
## Optional: Retake and management
|
|
126
|
+
|
|
127
|
+
When your UI changes, **Retake** can refilm an existing Bite from its saved recipe. It is a paid capability; see [plan details](https://www.demobites.com/pricing).
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npx demobite retake <biteId> --note "Export moved to the header"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**In your app's repository.** Run the skill from the project that is your app and the agent reads the routes, the navigation, the control labels and the data handlers before it plans, then verifies everything on the live page. Without the code it works from the live app alone and asks when it cannot find a screen.
|
|
134
|
+
|
|
135
|
+
**Workspace rules.** A workspace admin can write standing rules for the recorder in plain words in the DemoBites settings tab Agentic Recorder Rules, one per line: what to mask, which pages never to open, which words to use. The agent reads them at the start of every take and applies them under its own filming laws; `npx demobite rules` prints them. Every take records the rules version it was filmed under.
|
|
136
|
+
|
|
137
|
+
**Batches of briefs.** When a pull request in DemoBites produces approved briefs, your agent claims them, films one take per brief after you approve each storyboard, and delivers each take. A delivered take becomes a Bite in DemoBites by itself; there is no second review click for these takes. Nothing is published or shared.
|
|
138
|
+
|
|
139
|
+
The package also includes a DemoBites management MCP for releases and centers:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx demobite mcp
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The default installer also attempts to register that MCP with Claude Code when the current project is already connected. Management access depends on your account and key permissions. You do not need paid management to record, edit, or export a free Bite.
|
|
146
|
+
|
|
147
|
+
[Management documentation](https://www.demobites.com/docs/bites/manage-from-your-agent)
|
|
148
|
+
|
|
149
|
+
## Repository
|
|
150
|
+
|
|
151
|
+
| Directory | Purpose |
|
|
152
|
+
| --- | --- |
|
|
153
|
+
| `launcher/` | CLI setup, connection commands, and MCP registration. |
|
|
154
|
+
| `skill/` | Studio-connected recorder instructions, login, upload, and Retake. |
|
|
155
|
+
| `scripts/` | Shared browser recording, trimming, and timing calibration. |
|
|
156
|
+
| `recorder/` | Standalone skill and local video finishing tools. |
|
|
157
|
+
|
|
158
|
+
The same package is published under three names, `demobite`, `agentic-recorder` and `demobites`, always at the same version; `npx agentic-recorder@latest` and `npx demobites@latest` do exactly what `npx demobite@latest` does. Run it again to update the installed skill. For publishing instructions, see [RELEASING.md](RELEASING.md).
|
|
159
|
+
|
|
160
|
+
Found something confusing or have an example to share? [Open an issue](https://github.com/demobites/agentic-recorder/issues). Include your operating system, agent, and recorder version. Remove keys, cookies, and private app data from logs or recordings before attaching them.
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
The code in this repository is [MIT licensed](LICENSE). The hosted DemoBites Studio is a separate service; its source is not included here.
|
|
14
165
|
|
|
15
|
-
|
|
166
|
+
Built and maintained by [DemoBites](https://www.demobites.com/).
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// demobite — connect DemoBites to your agent, one command away.
|
|
3
|
+
//
|
|
4
|
+
// npx demobite@latest full setup: checks, recorder skill, MCP
|
|
5
|
+
// npx demobite@latest login connect this machine to DemoBites
|
|
6
|
+
// npx demobite@latest mcp register the DemoBites management MCP
|
|
7
|
+
// npx demobite@latest logout disconnect (revokes the key server-side)
|
|
8
|
+
//
|
|
9
|
+
// One front door (founder 2026-08-31): the user never chooses between the
|
|
10
|
+
// recorder skill and the management MCP — bare `npx demobite` sets up both.
|
|
11
|
+
// The MCP is universal (every agent gets it); the skill is the bonus layer
|
|
12
|
+
// for code agents, and stays the advocated recording lane because the agent
|
|
13
|
+
// knows the customer's code. `agentic-recorder` remains a docs alias of bare.
|
|
14
|
+
//
|
|
15
|
+
// This launcher is deliberately boring: it verifies the environment, installs
|
|
16
|
+
// the recorder skill into your agent's skills directory, wires the MCP, and
|
|
17
|
+
// hands off. The recorder itself is driven by your coding agent (Claude
|
|
18
|
+
// Code): once set up, you just ask it — "record a demo of our search flow".
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import os from "node:os";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
24
|
+
|
|
25
|
+
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
26
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf8"));
|
|
27
|
+
const arg = process.argv[2] ?? "";
|
|
28
|
+
|
|
29
|
+
const ok = (m) => console.log(` ✓ ${m}`);
|
|
30
|
+
const warn = (m) => console.log(` ! ${m}`);
|
|
31
|
+
|
|
32
|
+
console.log(`\ndemobite v${pkg.version} — the DemoBites agentic recorder\n`);
|
|
33
|
+
|
|
34
|
+
// ── 1. Environment checks ──────────────────────────────────────────────────
|
|
35
|
+
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
36
|
+
if (nodeMajor >= 18) ok(`Node ${process.versions.node}`);
|
|
37
|
+
else { warn(`Node ${process.versions.node} — 18+ required`); process.exit(1); }
|
|
38
|
+
|
|
39
|
+
const hasBin = (bin) => {
|
|
40
|
+
try { execFileSync(process.platform === "win32" ? "where" : "which", [bin], { stdio: "ignore" }); return true; }
|
|
41
|
+
catch { return false; }
|
|
42
|
+
};
|
|
43
|
+
const chromePaths = [
|
|
44
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
45
|
+
"C:/Program Files/Google/Chrome/Application/chrome.exe",
|
|
46
|
+
"/usr/bin/google-chrome",
|
|
47
|
+
];
|
|
48
|
+
const haveChrome = chromePaths.some((p) => fs.existsSync(p)) || hasBin("google-chrome") || hasBin("google-chrome-stable");
|
|
49
|
+
if (haveChrome) ok("Google Chrome (films with the real browser)");
|
|
50
|
+
else console.log(" · Google Chrome not found — installing Playwright's Chromium for the recorder");
|
|
51
|
+
if (hasBin("claude")) ok("Claude Code (drives the recorder; MCP registers automatically)");
|
|
52
|
+
else warn("Claude Code not found — using Cursor or Codex? They drive the recorder too; MCP setup prints below");
|
|
53
|
+
|
|
54
|
+
// ── 2. Install / update the skill ──────────────────────────────────────────
|
|
55
|
+
const skillsDir = path.join(os.homedir(), ".claude", "skills");
|
|
56
|
+
const dest = path.join(skillsDir, "agentic-recorder");
|
|
57
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
58
|
+
fs.mkdirSync(path.join(dest, "scripts"), { recursive: true });
|
|
59
|
+
const copy = (from, to) => fs.copyFileSync(path.join(pkgRoot, from), path.join(dest, to));
|
|
60
|
+
copy("skill/SKILL.md", "SKILL.md");
|
|
61
|
+
for (const f of fs.readdirSync(path.join(pkgRoot, "skill/scripts"))) copy(`skill/scripts/${f}`, `scripts/${f}`);
|
|
62
|
+
for (const f of fs.readdirSync(path.join(pkgRoot, "scripts"))) copy(`scripts/${f}`, `scripts/${f}`);
|
|
63
|
+
ok(`Skill installed → ${dest}`);
|
|
64
|
+
|
|
65
|
+
// Playwright and the media tools live with the skill so takes can film and
|
|
66
|
+
// finish on any machine. ffmpeg and ffprobe are packaged per platform
|
|
67
|
+
// (@ffmpeg-installer, @ffprobe-installer); a compatible system build is preferred when present
|
|
68
|
+
// (scripts/media-tools.mjs decides). One line installs everything.
|
|
69
|
+
const needed = ["playwright", "@ffmpeg-installer/ffmpeg", "@ffprobe-installer/ffprobe"].filter(
|
|
70
|
+
(m) => !fs.existsSync(path.join(dest, "node_modules", m)),
|
|
71
|
+
);
|
|
72
|
+
if (needed.length) {
|
|
73
|
+
console.log(`\n Installing ${needed.join(", ")} (one-time)…`);
|
|
74
|
+
const r = spawnSync("npm", ["install", "--prefix", dest, "--silent", "--no-audit", "--no-fund", ...needed], { stdio: "inherit" });
|
|
75
|
+
if (r.status === 0) ok("Playwright ready");
|
|
76
|
+
else warn(`Install failed — run: npm install --prefix ~/.claude/skills/agentic-recorder ${needed.join(" ")}`);
|
|
77
|
+
}
|
|
78
|
+
// No Chrome on this machine: fetch Playwright's Chromium once so the first
|
|
79
|
+
// take has a browser. Linux needs the shared libraries too (--with-deps).
|
|
80
|
+
if (!haveChrome) {
|
|
81
|
+
const pw = path.join(dest, "node_modules", "playwright", "cli.js");
|
|
82
|
+
const marker = path.join(dest, ".chromium-ready");
|
|
83
|
+
if (!fs.existsSync(marker) && fs.existsSync(pw)) {
|
|
84
|
+
console.log(" Installing Chromium (one-time)…");
|
|
85
|
+
const args = [pw, "install", "chromium", ...(process.platform === "linux" ? ["--with-deps"] : [])];
|
|
86
|
+
const r = spawnSync("node", args, { stdio: "inherit", cwd: dest });
|
|
87
|
+
if (r.status === 0) { fs.writeFileSync(marker, new Date().toISOString()); ok("Chromium ready"); }
|
|
88
|
+
else warn(`Chromium install failed — run: node ${pw} install chromium${process.platform === "linux" ? " --with-deps" : ""}`);
|
|
89
|
+
} else if (fs.existsSync(marker)) ok("Chromium ready");
|
|
90
|
+
}
|
|
91
|
+
{
|
|
92
|
+
// Verify both tools actually run (compatibility, not presence).
|
|
93
|
+
const r = spawnSync("node", [path.join(dest, "scripts", "media-tools.mjs")], { encoding: "utf8" });
|
|
94
|
+
const rows = (r.stdout || "").trim().split("\n").filter(Boolean).map((l) => l.split("\t"));
|
|
95
|
+
for (const [tool, source, info] of rows) {
|
|
96
|
+
if (source === "missing") warn(`${tool}: no working build (${info})`);
|
|
97
|
+
else ok(`${tool} ready (${source === "packaged" ? "packaged with the recorder" : source === "system" ? "your system build" : "from .recorder/config.json"})`);
|
|
98
|
+
}
|
|
99
|
+
if (r.status !== 0) warn("Recording will fail until ffmpeg and ffprobe both work. Re-run this command, or install them on your system.");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── 3. Subcommands ─────────────────────────────────────────────────────────
|
|
103
|
+
if (arg === "login" || arg === "logout") {
|
|
104
|
+
const r = spawnSync("node", [path.join(dest, "scripts", "login.mjs"), ...(arg === "logout" ? ["--logout"] : [])], {
|
|
105
|
+
stdio: "inherit",
|
|
106
|
+
cwd: process.cwd(),
|
|
107
|
+
});
|
|
108
|
+
process.exit(r.status ?? 0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── 3b. Management MCP ─────────────────────────────────────────────────────
|
|
112
|
+
// The key lives in <cwd>/.recorder/config.json (login.mjs writes it there),
|
|
113
|
+
// so MCP registration is per-project too — `claude mcp add` default (local)
|
|
114
|
+
// scope matches that exactly and keeps the key out of committable files.
|
|
115
|
+
const readCfg = () => {
|
|
116
|
+
try { return JSON.parse(fs.readFileSync(path.join(process.cwd(), ".recorder", "config.json"), "utf8")); }
|
|
117
|
+
catch { return null; }
|
|
118
|
+
};
|
|
119
|
+
const registerMcp = (cfg, { quiet = false } = {}) => {
|
|
120
|
+
const url = `${cfg.base ?? "https://app.demobites.com"}/api/mcp`;
|
|
121
|
+
const header = `Authorization: Bearer ${cfg.api_key}`;
|
|
122
|
+
if (hasBin("claude")) {
|
|
123
|
+
const r = spawnSync("claude", ["mcp", "add", "--transport", "http", "demobites", url, "--header", header], {
|
|
124
|
+
stdio: quiet ? "ignore" : "inherit",
|
|
125
|
+
cwd: process.cwd(),
|
|
126
|
+
});
|
|
127
|
+
if (r.status === 0) { ok(`DemoBites MCP registered with Claude Code (${url})`); return true; }
|
|
128
|
+
}
|
|
129
|
+
if (!quiet) {
|
|
130
|
+
console.log(`
|
|
131
|
+
Add the DemoBites MCP to your agent manually — Streamable HTTP:
|
|
132
|
+
|
|
133
|
+
URL: ${url}
|
|
134
|
+
Header: ${header}
|
|
135
|
+
|
|
136
|
+
Claude Code: claude mcp add --transport http demobites ${url} --header "${header}"
|
|
137
|
+
Cursor: add the URL + header under Settings → MCP
|
|
138
|
+
Other MCP clients: any Streamable HTTP client works with the same URL + header.
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (arg === "retake") {
|
|
145
|
+
const biteId = process.argv[3];
|
|
146
|
+
if (!biteId || !/^\d+$/.test(biteId)) { warn('Usage: npx demobite retake <biteId> [--note "what changed"]'); process.exit(2); }
|
|
147
|
+
let cfg = readCfg();
|
|
148
|
+
if (!cfg?.api_key) {
|
|
149
|
+
console.log("\n Not connected yet — linking this machine to DemoBites first…\n");
|
|
150
|
+
const r = spawnSync("node", [path.join(dest, "scripts", "login.mjs")], { stdio: "inherit", cwd: process.cwd() });
|
|
151
|
+
if (r.status !== 0) process.exit(r.status ?? 1);
|
|
152
|
+
cfg = readCfg();
|
|
153
|
+
}
|
|
154
|
+
if (!cfg?.api_key) { warn("Login did not complete — run: npx demobite login"); process.exit(1); }
|
|
155
|
+
const r = spawnSync("node", [path.join(dest, "scripts", "retake.mjs"), ...process.argv.slice(3)], { stdio: "inherit", cwd: process.cwd() });
|
|
156
|
+
process.exit(r.status ?? 1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Batch of briefs (2026-09-13): `npx demobite briefs list <batchId>` etc. and
|
|
160
|
+
// `npx demobite status <takeDir|stagingId>` hand straight to the skill scripts.
|
|
161
|
+
// WORKSPACE RULES (1.4): `npx demobite rules` prints the workspace's standing rules.
|
|
162
|
+
if (arg === "briefs" || arg === "status" || arg === "rules") {
|
|
163
|
+
let cfg = readCfg();
|
|
164
|
+
if (!cfg?.api_key) {
|
|
165
|
+
console.log("\n Not connected yet — linking this machine to DemoBites first…\n");
|
|
166
|
+
const r = spawnSync("node", [path.join(dest, "scripts", "login.mjs")], { stdio: "inherit", cwd: process.cwd() });
|
|
167
|
+
if (r.status !== 0) process.exit(r.status ?? 1);
|
|
168
|
+
cfg = readCfg();
|
|
169
|
+
}
|
|
170
|
+
if (!cfg?.api_key) { warn("Login did not complete — run: npx demobite login"); process.exit(1); }
|
|
171
|
+
const r = spawnSync("node", [path.join(dest, "scripts", `${arg}.mjs`), ...process.argv.slice(3)], { stdio: "inherit", cwd: process.cwd() });
|
|
172
|
+
process.exit(r.status ?? 1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (arg === "mcp") {
|
|
176
|
+
let cfg = readCfg();
|
|
177
|
+
if (!cfg?.api_key) {
|
|
178
|
+
console.log("\n Not connected yet — linking this machine to DemoBites first…\n");
|
|
179
|
+
const r = spawnSync("node", [path.join(dest, "scripts", "login.mjs")], { stdio: "inherit", cwd: process.cwd() });
|
|
180
|
+
if (r.status !== 0) process.exit(r.status ?? 1);
|
|
181
|
+
cfg = readCfg();
|
|
182
|
+
}
|
|
183
|
+
if (!cfg?.api_key) { warn("Login did not complete — run: npx demobite login"); process.exit(1); }
|
|
184
|
+
registerMcp(cfg);
|
|
185
|
+
console.log(`
|
|
186
|
+
Your agent can now manage DemoBites — try asking it:
|
|
187
|
+
|
|
188
|
+
"Create a release with my latest bites and add Spanish"
|
|
189
|
+
|
|
190
|
+
Publishing always shows you a preview to approve first.
|
|
191
|
+
`);
|
|
192
|
+
process.exit(0);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── 4. Handoff ─────────────────────────────────────────────────────────────
|
|
196
|
+
// Bare invocation (and the `agentic-recorder` docs alias): if this project is
|
|
197
|
+
// already linked, quietly wire the MCP too — one command, both magics.
|
|
198
|
+
const cfg = readCfg();
|
|
199
|
+
if (cfg?.api_key) registerMcp(cfg, { quiet: true });
|
|
200
|
+
console.log(`
|
|
201
|
+
Ready. Everything is agent-driven — open your coding agent (Claude Code,
|
|
202
|
+
Cursor, Codex) in your project and ask:
|
|
203
|
+
|
|
204
|
+
"Record a demo of <your flow> and upload it to DemoBites"
|
|
205
|
+
"Create a release with my latest bites and add Spanish"
|
|
206
|
+
|
|
207
|
+
It signs in via your browser on first use (or run: npx demobite login).
|
|
208
|
+
Manage-by-agent needs the MCP: npx demobite mcp (once, after login).
|
|
209
|
+
Only the recorder, no DemoBites? See the open recorder in this package's repo.
|
|
210
|
+
`);
|
package/package.json
CHANGED
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "demobites",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "DemoBites agentic recorder
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "The DemoBites agentic recorder — you prompt, it films a real browser, and DemoBites turns the take into an editable demo bite.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"demobites": "launcher/index.mjs"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"launcher",
|
|
10
|
+
"skill",
|
|
11
|
+
"recorder",
|
|
12
|
+
"scripts",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"demo",
|
|
21
|
+
"screen-recording",
|
|
22
|
+
"playwright",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"skill",
|
|
25
|
+
"demobites",
|
|
26
|
+
"agentic"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/demobites/agentic-recorder.git"
|
|
31
|
+
},
|
|
12
32
|
"homepage": "https://github.com/demobites/agentic-recorder#readme",
|
|
13
|
-
"
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/demobites/agentic-recorder/issues"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"type": "module",
|
|
38
|
+
"scripts": {
|
|
39
|
+
"release": "node scripts/release.mjs",
|
|
40
|
+
"prepublishOnly": "node scripts/check-release.mjs"
|
|
41
|
+
}
|
|
14
42
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: open-recorder
|
|
3
|
+
description: Film a polished product demo by driving a real browser from a storyboard, delivering a finished styled demo.mp4 — no account required. The open sibling of the DemoBites agentic recorder.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The Open Recorder
|
|
7
|
+
|
|
8
|
+
The same filming engine as the DemoBites agentic recorder — storyboard-driven
|
|
9
|
+
Playwright capture in a real Chrome, hover-anchor clock calibration, human-pace
|
|
10
|
+
cursor motion — ending at a finished, styled `demo.mp4` on your disk.
|
|
11
|
+
|
|
12
|
+
Phases (shared engine lives in `../scripts/`):
|
|
13
|
+
|
|
14
|
+
1. **Storyboard** — write the shot list as JSON (see the schema in
|
|
15
|
+
`../skill/SKILL.md`, identical here), show it to the human, get approval.
|
|
16
|
+
2. **Dry run** — resolve every selector headless before filming.
|
|
17
|
+
3. **The take** — `node ../scripts/record.mjs <takeDir> <storyboard.json>`
|
|
18
|
+
4. **Trim + calibrate** — `node ../scripts/trim.mjs <takeDir>` then
|
|
19
|
+
`node ../scripts/calibrate.mjs <takeDir>`
|
|
20
|
+
5. **Deliver** — the standalone finishing tools in `scripts/`:
|
|
21
|
+
`frame.mjs` (rounded corners + shadow), `post.sh` (backdrop + trim to
|
|
22
|
+
`demo.mp4`), optional `tts.mjs`/`mux.mjs` voiceover with your own
|
|
23
|
+
ElevenLabs key, optional SRT captions.
|
|
24
|
+
|
|
25
|
+
The full standalone-ending reference, preserved verbatim from the original
|
|
26
|
+
skill, is in `STANDALONE-ENDING.md`.
|
|
27
|
+
|
|
28
|
+
Want narration written for you, cinematic zooms, a rendered cursor, an
|
|
29
|
+
editable timeline, hosting and analytics? That is the DemoBites ending — the
|
|
30
|
+
sibling skill in `../skill/`, one login away: `npx demobite login`.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# PARKED: the standalone ending (future community skill)
|
|
2
|
+
|
|
3
|
+
This skill is the COMMERCIAL DemoBites recorder — it does not offer a
|
|
4
|
+
standalone MP4. The content below is preserved verbatim for the planned
|
|
5
|
+
open-source community skill and is NOT part of this skill's flow.
|
|
6
|
+
Do not ask the human which ending they want; there is one ending.
|
|
7
|
+
|
|
8
|
+
### Ending A: STANDALONE
|
|
9
|
+
|
|
10
|
+
Deliver a finished, styled demo.mp4.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
node scripts/frame.mjs <takeDir> # look overlays: shadow + alpha mask (radius 28)
|
|
14
|
+
scripts/post.sh <takeDir> <backdropHexNo#> # raw.webm -> demo.mp4, backdrop + corners + shadow + trim
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Look laws inside: TRUE alpha mask rounded corners (alphamerge, radius 28, never painted on), shadow strength derived from backdrop luminance, `shortest=1` so the still overlays never extend the cut.
|
|
18
|
+
|
|
19
|
+
Optional voiceover, only with the customer's OWN ElevenLabs key (env `ELEVENLABS_API_KEY` or `.recorder/elevenlabs.key`, never ask the human to paste it into chat):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
node scripts/tts.mjs <takeDir> # narration lines -> measured mp3 segments
|
|
23
|
+
node scripts/mux.mjs <takeDir> # demo.mp4 -> demo-voiced.mp4, anchored to real step timings
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Optional captions:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node scripts/manifest.mjs <takeDir> --srt # writes captions.srt from the narration timeline
|
|
30
|
+
ffmpeg -i <takeDir>/demo-voiced.mp4 -vf "subtitles=<takeDir>/captions.srt" -c:a copy <takeDir>/demo-captioned.mp4
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Deliverables: `demo.mp4` (or `demo-voiced.mp4` / `demo-captioned.mp4`) plus `manifest.json`.
|
|
34
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Standalone ending only — generate the look overlays for post.sh:
|
|
3
|
+
// a shadow ring PNG and a TRUE rounded-alpha mask PNG.
|
|
4
|
+
//
|
|
5
|
+
// LAW (true alpha corners): rounded corners must be a real alpha mask fed to
|
|
6
|
+
// ffmpeg alphamerge — never painted-on corner squares. Radius 28.
|
|
7
|
+
// LAW (shadow from luminance): shadow strength derives from the backdrop's
|
|
8
|
+
// luminance — light backdrops get a soft ink shadow, dark backdrops a deep one.
|
|
9
|
+
//
|
|
10
|
+
// Usage: node frame.mjs <takeDir> [backdropHex]
|
|
11
|
+
// Backdrop resolution order: CLI arg, .recorder/config.json look.backdrop, #0f1420.
|
|
12
|
+
import { chromium } from "playwright";
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
const [, , outDir, argBackdrop] = process.argv;
|
|
17
|
+
if (!outDir) {
|
|
18
|
+
console.error("Usage: node frame.mjs <takeDir> [backdropHex]");
|
|
19
|
+
process.exit(2);
|
|
20
|
+
}
|
|
21
|
+
let backdrop = argBackdrop;
|
|
22
|
+
if (!backdrop) {
|
|
23
|
+
try {
|
|
24
|
+
backdrop = JSON.parse(fs.readFileSync(path.resolve(".recorder", "config.json"), "utf8")).look?.backdrop;
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
backdrop = backdrop || "#0f1420";
|
|
28
|
+
if (!/^#?[0-9a-fA-F]{6}$/.test(backdrop)) {
|
|
29
|
+
console.error(`Backdrop must be a 6 digit hex color, got "${backdrop}"`);
|
|
30
|
+
process.exit(2);
|
|
31
|
+
}
|
|
32
|
+
if (!backdrop.startsWith("#")) backdrop = "#" + backdrop;
|
|
33
|
+
fs.mkdirSync(path.resolve(outDir), { recursive: true });
|
|
34
|
+
|
|
35
|
+
const hx = backdrop.slice(1);
|
|
36
|
+
const light =
|
|
37
|
+
parseInt(hx.slice(0, 2), 16) * 0.299 +
|
|
38
|
+
parseInt(hx.slice(2, 4), 16) * 0.587 +
|
|
39
|
+
parseInt(hx.slice(4, 6), 16) * 0.114 > 128;
|
|
40
|
+
const W = 1920, H = 1080, VW = 1728, VH = 972, R = 28;
|
|
41
|
+
const X = (W - VW) / 2, Y = (H - VH) / 2;
|
|
42
|
+
|
|
43
|
+
const b = await chromium.launch();
|
|
44
|
+
const p = await b.newPage();
|
|
45
|
+
await p.setViewportSize({ width: W, height: H });
|
|
46
|
+
// Shadow ring: transparent page, rounded rect with only its box-shadow visible.
|
|
47
|
+
const shadow = light
|
|
48
|
+
? "0 14px 60px rgba(20,22,28,.16), 0 3px 14px rgba(20,22,28,.08)"
|
|
49
|
+
: "0 18px 80px rgba(0,0,0,.5), 0 4px 20px rgba(0,0,0,.3)";
|
|
50
|
+
await p.setContent(`<body style="margin:0;background:transparent"><div style="position:absolute;left:${X}px;top:${Y}px;width:${VW}px;height:${VH}px;border-radius:${R}px;background:transparent;box-shadow:${shadow}"></div></body>`);
|
|
51
|
+
await p.screenshot({ path: path.join(outDir, "shadow.png"), omitBackground: true });
|
|
52
|
+
// Alpha mask: black canvas, white rounded rect — post.sh alphamerges this
|
|
53
|
+
// grayscale source onto the scaled video.
|
|
54
|
+
await p.setViewportSize({ width: VW, height: VH });
|
|
55
|
+
await p.setContent(`<body style="margin:0;background:#000"><div style="position:absolute;inset:0;border-radius:${R}px;background:#fff"></div></body>`);
|
|
56
|
+
await p.screenshot({ path: path.join(outDir, "mask.png") });
|
|
57
|
+
await b.close();
|
|
58
|
+
console.log("overlays ready (radius", R + "px,", light ? "light" : "dark", "shadow,", "backdrop", backdrop + ")");
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Standalone ending only — mux the measured TTS segments onto the composited
|
|
3
|
+
// cut (demo.mp4), anchored to the manifest's REAL step timings. The script is
|
|
4
|
+
// the metronome: each line starts when its step starts, minus the trim.
|
|
5
|
+
//
|
|
6
|
+
// Founder-proven anchor tweaks (kept, commented):
|
|
7
|
+
// * the OPENING line gets a +0.3s beat after the hold starts, so the voice
|
|
8
|
+
// never fires on the very first frame
|
|
9
|
+
// * a line attached to a SCROLL step starts 1.5s early — it describes the
|
|
10
|
+
// page that just revealed, so it lands as the page settles, before the
|
|
11
|
+
// scroll moves
|
|
12
|
+
//
|
|
13
|
+
// Usage: node mux.mjs <takeDir>
|
|
14
|
+
// Reads <takeDir>/demo.mp4 + <takeDir>/manifest.json + <takeDir>/tts/tts.json.
|
|
15
|
+
// Writes <takeDir>/demo-voiced.mp4.
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { execFileSync } from "node:child_process";
|
|
19
|
+
|
|
20
|
+
const dir = process.argv[2];
|
|
21
|
+
if (!dir) {
|
|
22
|
+
console.error("Usage: node mux.mjs <takeDir>");
|
|
23
|
+
process.exit(2);
|
|
24
|
+
}
|
|
25
|
+
import { ffmpeg as FFMPEG, ffprobe as FFPROBE, requireMediaTools } from "../../scripts/media-tools.mjs";
|
|
26
|
+
requireMediaTools(["ffmpeg","ffprobe"]);
|
|
27
|
+
|
|
28
|
+
const demoPath = path.join(dir, "demo.mp4");
|
|
29
|
+
const manPath = path.join(dir, "manifest.json");
|
|
30
|
+
const ttsPath = path.join(dir, "tts", "tts.json");
|
|
31
|
+
if (!fs.existsSync(demoPath)) { console.error(`${demoPath} not found. Run frame.mjs + post.sh first.`); process.exit(1); }
|
|
32
|
+
if (!fs.existsSync(manPath)) { console.error(`${manPath} not found. Run record.mjs first.`); process.exit(1); }
|
|
33
|
+
if (!fs.existsSync(ttsPath)) { console.error(`${ttsPath} not found. Run tts.mjs first.`); process.exit(1); }
|
|
34
|
+
|
|
35
|
+
const man = JSON.parse(fs.readFileSync(manPath, "utf8"));
|
|
36
|
+
const tts = JSON.parse(fs.readFileSync(ttsPath, "utf8"));
|
|
37
|
+
const r0 = man.record_from ?? 0;
|
|
38
|
+
|
|
39
|
+
// Anchor each narrated step to its real start time, normalized to the cut.
|
|
40
|
+
const narrated = (man.steps ?? []).filter((s) => s.narration?.text);
|
|
41
|
+
const anchors = new Map();
|
|
42
|
+
let first = true;
|
|
43
|
+
for (const s of narrated) {
|
|
44
|
+
let at = (s.t_start ?? 0) - r0;
|
|
45
|
+
if (first) { at += 0.3; first = false; } // opening line, small beat after the hold starts
|
|
46
|
+
if (s.action === "scroll") at -= 1.5; // page reveal line starts as the page settles, before the scroll
|
|
47
|
+
anchors.set(s.n, Math.max(0.05, Math.round(at * 100) / 100));
|
|
48
|
+
}
|
|
49
|
+
console.log("narration anchors (s):", JSON.stringify(Object.fromEntries(anchors)));
|
|
50
|
+
|
|
51
|
+
const inputArgs = [];
|
|
52
|
+
for (const seg of tts) inputArgs.push("-i", path.join(dir, "tts", seg.file));
|
|
53
|
+
const delays = tts
|
|
54
|
+
.map((seg, i) => {
|
|
55
|
+
const ms = Math.round((anchors.get(seg.n) ?? 0) * 1000);
|
|
56
|
+
return `[${i + 1}:a]adelay=${ms}|${ms}[a${i}]`;
|
|
57
|
+
})
|
|
58
|
+
.join(";");
|
|
59
|
+
const mix = tts.map((_, i) => `[a${i}]`).join("") + `amix=inputs=${tts.length}:normalize=0[aout]`;
|
|
60
|
+
|
|
61
|
+
const outPath = path.join(dir, "demo-voiced.mp4");
|
|
62
|
+
execFileSync(FFMPEG(), [
|
|
63
|
+
"-y", "-loglevel", "error",
|
|
64
|
+
"-i", demoPath,
|
|
65
|
+
...inputArgs,
|
|
66
|
+
"-filter_complex", `${delays};${mix}`,
|
|
67
|
+
"-map", "0:v", "-map", "[aout]",
|
|
68
|
+
"-c:v", "copy", "-c:a", "aac", "-b:a", "160k",
|
|
69
|
+
outPath,
|
|
70
|
+
], { stdio: "inherit" });
|
|
71
|
+
const dur = execFileSync(FFPROBE(), [
|
|
72
|
+
"-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", outPath,
|
|
73
|
+
]).toString().trim();
|
|
74
|
+
console.log(`demo-voiced.mp4 ready, ${parseFloat(dur).toFixed(2)}s`);
|