demobite 1.7.1 → 1.7.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
@@ -22,6 +22,37 @@ An illustrated walkthrough of the flow, with waiting time compressed.
22
22
 
23
23
  ## Start with your own agent
24
24
 
25
+ ### Install as a plugin (Claude Code, Codex, Cursor)
26
+
27
+ The recorder ships as a plugin of this repository. Install it once, then run its setup skill: it installs everything (Playwright, ffmpeg, Chromium when needed) and connects your account with one approval in your browser.
28
+
29
+ **Claude Code**
30
+
31
+ ```
32
+ /plugin marketplace add demobites/agentic-recorder
33
+ /plugin install demobites@demobites
34
+ /demobites:setup
35
+ ```
36
+
37
+ Then `/demobites:record`, `/demobites:login`, `/demobites:logout` — or just ask: "record a demo of the new billing page".
38
+
39
+ You can also skip the setup line: ask for a demo right after the install and the record skill sets the recorder up by itself (once per machine, no browser), asks for one "Go" before opening the approval link, and then films what you asked for. Until the project is connected, every new session opens with one line that says so.
40
+
41
+ **Codex**
42
+
43
+ ```
44
+ codex plugin marketplace add demobites/agentic-recorder
45
+ codex plugin add demobites@demobites
46
+ $demobites-setup
47
+ ```
48
+
49
+ **Cursor**
50
+
51
+ Install **DemoBites** from the Cursor marketplace (Customize → Plugins), then `/demobites-setup`. Teams can also add this repository as a team marketplace.
52
+
53
+ ### Or one command, any agent
54
+
55
+
25
56
  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
57
 
27
58
  Before installing:
@@ -75,7 +75,7 @@ if (arg === "record") {
75
75
  }
76
76
  const cfg = readCfg();
77
77
  if (!cfg?.api_key) {
78
- console.log("This project is not connected to DemoBites yet.\nRun `npx demobite login` (it prints a link to approve in your browser), then run this command again.");
78
+ console.log("Before we record, this project needs a DemoBites account.\nRun `npx demobite login` (it prints a link to approve in your browser), then run this command again.");
79
79
  process.exit(1);
80
80
  }
81
81
  const r = spawnSync("node", [briefsScript, "list", "--slug", slug], {
@@ -269,7 +269,8 @@ Cursor, Codex) in your project and ask:
269
269
  "Record a demo of <your flow> and upload it to DemoBites"
270
270
  "Create a release with my latest bites and add Spanish"
271
271
 
272
- It signs in via your browser on first use (or run: npx demobite login).
272
+ Before the first demo it connects this project to your DemoBites account —
273
+ one link to approve in your browser (or run now: npx demobite login).
273
274
  Manage-by-agent needs the MCP: npx demobite mcp (once, after login).
274
275
  Only the recorder, no DemoBites? See the open recorder in this package's repo.
275
276
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "demobite",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "The DemoBites agentic recorder — you prompt, it films a real browser, and DemoBites turns the take into an editable demo bite.",
5
5
  "bin": {
6
6
  "demobite": "launcher/index.mjs"
package/scripts/trim.mjs CHANGED
@@ -152,12 +152,16 @@ const dur = execFileSync(FFPROBE(), [
152
152
  "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", clean,
153
153
  ]).toString().trim();
154
154
  const duration = parseFloat(dur);
155
- // LAW (founder): a Bite is capped at 90 seconds by product concept. A take
156
- // that cuts longer than that must not proceed to staging — the story needs a
157
- // SPLIT, not a trim. Fail loudly here, before any upload machinery runs.
158
- if (duration > 90) {
155
+ // LAW (founder 2026-10-10: 3 minutes, was 90 s since 2026-08-09): a Bite is
156
+ // capped at 180 seconds by product concept — a LIMIT, not a target; the
157
+ // sweet spot stays 30 to 45 s. A take that cuts longer than that must not
158
+ // proceed to staging — the story needs a SPLIT, not a trim. Fail loudly here,
159
+ // before any upload machinery runs. The server refuses the same number
160
+ // (MAX_BITE_SECONDS; GET /api/recorder/limits answers it).
161
+ const MAX_BITE_SECONDS = 180;
162
+ if (duration > MAX_BITE_SECONDS) {
159
163
  console.error(
160
- `HARD CAP: this take cuts to ${duration.toFixed(1)}s — a Bite is limited to 90 seconds. ` +
164
+ `HARD CAP: this take cuts to ${duration.toFixed(1)}s — a Bite is limited to 3 minutes. ` +
161
165
  "Do not upload. Split the scenario into multiple Bites and refilm."
162
166
  );
163
167
  process.exit(1);
package/skill/SKILL.md CHANGED
@@ -20,9 +20,10 @@ storyboard talk: check `.recorder/config.json` for `api_key`.
20
20
  **The choreography when the key is missing (founder law, 2026-08-09 — never
21
21
  surprise the human with a browser page):**
22
22
 
23
- 1. TELL, don't act: "You're not connected to DemoBites yet. Signing in means
24
- approving a link in your browser — say Go when you're ready." Then WAIT.
25
- Nothing opens until the human gives the word.
23
+ 1. TELL, don't act: "Before we record, connect this project to your
24
+ DemoBites account: say Go and I'll open the approval link." Then WAIT.
25
+ Nothing opens until the human gives the word. Keep the brief the human
26
+ gave: once connected, go straight on with it, never ask for it again.
26
27
  2. On their word, run `node scripts/login.mjs`. It prints the link and code,
27
28
  opens the approval page, and polls. The human approves in their own
28
29
  signed-in browser session — this script never sees credentials. The key
@@ -144,9 +145,10 @@ Holding shots to cover estimated lines is what produced a 60 second take with th
144
145
  **Budgets, hold yourself to them:**
145
146
 
146
147
  - **30 to 45 seconds total (35 to 45 when the story crosses pages).** Over 45 is a rewrite, not a trim; under 30 with two pages is rushing, see the linger law below.
147
- - **90 seconds is a HARD CAP, by product concept, and it is a limit, not a
148
- target.** A Bite over 90 seconds does not exist. Budget the beats BEFORE
149
- filming: if the human's scenario cannot honestly fit inside 90 seconds, do
148
+ - **3 minutes (180 s) is a HARD CAP, by product concept, and it is a limit,
149
+ not a target** (founder 2026-10-10; it was 90 s before). A Bite over 3
150
+ minutes does not exist. Budget the beats BEFORE filming: if the human's
151
+ scenario cannot honestly fit inside 3 minutes, do
150
152
  not film-and-trim and do not compress it into uselessness — tell them up
151
153
  front and ask them to SPLIT it into multiple Bites, one story per Bite.
152
154
  State the estimated duration in every storyboard presentation.
@@ -363,7 +365,7 @@ Laws for a re-take:
363
365
 
364
366
  ## Batch of briefs (GitHub PR → demos)
365
367
 
366
- The human pastes a bundle of approved briefs into the chat: a header (batchId, workspaceId, the target URL, the 90 second rule) and one block per brief (briefId, revision, contentHash, title, audience, outcome, flowIntent). Up to five briefs. The pasted text is a copy; the server holds the truth.
368
+ The human pastes a bundle of approved briefs into the chat: a header (batchId, workspaceId, the target URL, the 3-minute rule) and one block per brief (briefId, revision, contentHash, title, audience, outcome, flowIntent). Up to five briefs. The pasted text is a copy; the server holds the truth.
367
369
 
368
370
  ```bash
369
371
  node scripts/briefs.mjs list <batchId> [--paste bundle.txt] # the approved briefs; warns when the paste drifted
@@ -96,7 +96,7 @@ function printBatch(data) {
96
96
  const { batch, briefs } = data;
97
97
  const src = batch?.source ? `${batch.source.repo}#${batch.source.prNumber}` : "";
98
98
  console.log(`Batch ${batch?.id ?? "?"} ${src} target ${batch?.target?.url ?? "?"}${batch?.target?.environment ? ` (${batch.target.environment})` : ""}`);
99
- console.log(`${briefs.length} brief${briefs.length === 1 ? "" : "s"}, each at most 90 seconds. One storyboard approval per brief.`);
99
+ console.log(`${briefs.length} brief${briefs.length === 1 ? "" : "s"}, each at most 180 seconds (3 minutes). One storyboard approval per brief.`);
100
100
  for (const b of briefs) {
101
101
  const att = b.attempt ? ` · attempt ${b.attempt.ref} (${b.attempt.state})` : "";
102
102
  console.log(`\n ${b.briefId} r${b.revision} ${b.status}${att}\n ${b.title}\n audience: ${b.audience}\n outcome: ${b.outcome}${b.estimatedDurationSec ? `\n about ${b.estimatedDurationSec}s` : ""}`);
@@ -138,7 +138,7 @@ function printRadar(data, slug) {
138
138
  // A radar batch has no target address: the agent films where the code
139
139
  // already runs (the Record stage says so); print the target only when set.
140
140
  console.log(`Batch ${batch?.id ?? "?"}${batch?.target?.url ? ` target ${batch.target.url}${batch.target.environment ? ` (${batch.target.environment})` : ""}` : ""}`);
141
- console.log(`${briefs.length} brief${briefs.length === 1 ? "" : "s"}, each at most ${batch?.rules?.maxSeconds ?? 90} seconds, in this order:\n`);
141
+ console.log(`${briefs.length} brief${briefs.length === 1 ? "" : "s"}, each at most ${batch?.rules?.maxSeconds ?? 180} seconds, in this order:\n`);
142
142
  const questions = [];
143
143
  order.forEach((b, i) => {
144
144
  const pos = String(i + 1).padStart(2, " ");
@@ -178,7 +178,7 @@ function writeRadarBundle(slug, data) {
178
178
  * The limits are the server's (a refine that breaks one answers 400
179
179
  * invalid_refine): title ≤ 80, audience ≤ 60, outcome ≤ 200, flowIntent 2..8
180
180
  * lines ≤ 120 each, prerequisites/exclusions ≤ 6 lines ≤ 160 each,
181
- * estimatedDurationSec 15..90. They are checked here first, with the field
181
+ * estimatedDurationSec 15..180 (3 minutes, founder 2026-10-10). They are checked here first, with the field
182
182
  * named, so the agent trims before posting instead of reading a 400. */
183
183
  const CONTENT_LIMITS = { title: 80, audience: 60, outcome: 200 };
184
184
  const LIST_LIMITS = { flowIntent: { min: 2, max: 8, line: 120 }, prerequisites: { min: 0, max: 6, line: 160 }, exclusions: { min: 0, max: 6, line: 160 } };
@@ -206,7 +206,7 @@ function refineProblems(c) {
206
206
  v.forEach((line, i) => { if (typeof line !== "string" || !line.trim()) out.push(`${k}[${i}]: an empty line`); else if (line.trim().length > lim.line) out.push(`${k}[${i}]: ${line.trim().length} characters, the limit is ${lim.line}`); });
207
207
  }
208
208
  const d = c.estimatedDurationSec;
209
- if (!Number.isInteger(d) || d < 15 || d > 90) out.push(`estimatedDurationSec: ${JSON.stringify(d)}, must be a whole number of seconds 15..90`);
209
+ if (!Number.isInteger(d) || d < 15 || d > 180) out.push(`estimatedDurationSec: ${JSON.stringify(d)}, must be a whole number of seconds 15..90`);
210
210
  return out;
211
211
  }
212
212