demobite 1.0.4 → 1.0.6
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/package.json +1 -1
- package/scripts/calibrate.mjs +12 -1
- package/scripts/record.mjs +14 -0
- package/skill/SKILL.md +2 -1
- package/skill/scripts/upload.mjs +14 -1
package/package.json
CHANGED
package/scripts/calibrate.mjs
CHANGED
|
@@ -38,6 +38,14 @@ if (!fs.existsSync(manPath)) { console.error(`${manPath} not found. Run record.m
|
|
|
38
38
|
if (!fs.existsSync(clean)) { console.error(`${clean} not found. Run trim.mjs first.`); process.exit(1); }
|
|
39
39
|
const man = JSON.parse(fs.readFileSync(manPath, "utf8"));
|
|
40
40
|
const tb = man.timebase;
|
|
41
|
+
// IDEMPOTENCE (2026-09-02): always measure from the identity base. A previous
|
|
42
|
+
// run may have already applied a hover-anchor shift to `b`; re-running on top
|
|
43
|
+
// of it compounded two shifts (-0.71 then -1.67 on one take) and produced a
|
|
44
|
+
// clock that matched nothing. Re-base first, then anchor once.
|
|
45
|
+
if (tb && typeof man.record_from === "number") {
|
|
46
|
+
tb.a = 1; tb.k = 1; tb.b = -man.record_from; tb.videoRecordFrom = man.record_from;
|
|
47
|
+
tb.method = "identity (raw is wall-rate; see 2026-08-09 four-lane verification)";
|
|
48
|
+
}
|
|
41
49
|
if (!tb || typeof tb.a !== "number" || typeof tb.b !== "number") {
|
|
42
50
|
console.error("manifest.json has no timebase stamp. Run trim.mjs first.");
|
|
43
51
|
process.exit(1);
|
|
@@ -45,7 +53,10 @@ if (!tb || typeof tb.a !== "number" || typeof tb.b !== "number") {
|
|
|
45
53
|
|
|
46
54
|
const FRAME = { w: man.frame?.width ?? 1920, h: man.frame?.height ?? 1080 };
|
|
47
55
|
const clicks = (man.steps ?? [])
|
|
48
|
-
|
|
56
|
+
// A `type` step has no single consequence instant — its pixels keep changing
|
|
57
|
+
// for the whole typing run, so the detector lands at the END of it (+5..7 s
|
|
58
|
+
// every take, 2026-09-02) and poisons the median. Clicks only.
|
|
59
|
+
.filter((s) => typeof s.click_at === "number" && s.action !== "type")
|
|
49
60
|
.map((s) => ({ wall: s.click_at, label: s.label ?? "click" }))
|
|
50
61
|
.sort((x, y) => x.wall - y.wall);
|
|
51
62
|
|
package/scripts/record.mjs
CHANGED
|
@@ -36,6 +36,20 @@ for (const [i, s] of STORYBOARD.steps.entries()) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const DIR = path.resolve(outArg);
|
|
39
|
+
|
|
40
|
+
// RE-TAKE (founder 2026-09-02): the storyboard IS the bite's DNA. Keep a verbatim
|
|
41
|
+
// copy in the take dir so upload.mjs can stage it as the recording recipe —
|
|
42
|
+
// selectors, urls, typed text, hideCss — the wire manifest alone cannot re-film.
|
|
43
|
+
const ENGINE_VERSION = "1.0.6";
|
|
44
|
+
fs.mkdirSync(DIR, { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(DIR, "storyboard.json"), JSON.stringify(STORYBOARD, null, 2));
|
|
46
|
+
try {
|
|
47
|
+
const cfgPath = path.resolve(".recorder/config.json");
|
|
48
|
+
const cfg = fs.existsSync(cfgPath) ? JSON.parse(fs.readFileSync(cfgPath, "utf8")) : {};
|
|
49
|
+
// Only the public shape — NEVER the api_key or workspace.
|
|
50
|
+
const config = { app: STORYBOARD.app ?? cfg.app ?? null, url: STORYBOARD.url ?? cfg.url ?? null, frame: cfg.frame ?? { width: 1920, height: 1080 }, base: cfg.base ?? "https://app.demobites.com" };
|
|
51
|
+
fs.writeFileSync(path.join(DIR, "recipe.json"), JSON.stringify({ version: 1, lane: "skill", engine: ENGINE_VERSION, config }, null, 2));
|
|
52
|
+
} catch (e) { console.error("recipe.json not written:", e.message); }
|
|
39
53
|
fs.mkdirSync(DIR, { recursive: true });
|
|
40
54
|
|
|
41
55
|
// LAW (supersampled capture, measured 2026-08-09): deviceScaleFactor is a
|
package/skill/SKILL.md
CHANGED
|
@@ -281,7 +281,8 @@ DELETE <base>/api/recorder/key (Authorization: Bearer <api_key>)
|
|
|
281
281
|
-> { revoked: true } (logout)
|
|
282
282
|
|
|
283
283
|
PUT <base>/api/recorder/stage (Authorization: Bearer <api_key>)
|
|
284
|
-
{ filename, sizeBytes, previewSizeBytes, manifest }
|
|
284
|
+
{ filename, sizeBytes, previewSizeBytes, manifest, recipe? }
|
|
285
|
+
// recipe = { version:1, lane:'skill', engine, storyboard, config:{app,url,frame,base} } — the RE-TAKE DNA (never the api_key)
|
|
285
286
|
-> { stagingId, uploadUrl, previewUploadUrl, videoKey, previewUrl }
|
|
286
287
|
|
|
287
288
|
GET <base>/api/recorder/stage?id=<stagingId> (Authorization: Bearer <api_key>)
|
package/skill/scripts/upload.mjs
CHANGED
|
@@ -40,6 +40,19 @@ const base = cfg.base.replace(/\/+$/, "");
|
|
|
40
40
|
|
|
41
41
|
const cleanPath = path.join(dir, "clean.mp4");
|
|
42
42
|
const wirePath = path.join(dir, "manifest.demobites.json");
|
|
43
|
+
// Recording recipe (RE-TAKE, 2026-09-02): storyboard + public config, written by
|
|
44
|
+
// record.mjs. Staged next to the manifest so the bite can be re-filmed later.
|
|
45
|
+
// Absent on takes filmed by older engines — staging still works without it.
|
|
46
|
+
let recipe = null;
|
|
47
|
+
try {
|
|
48
|
+
const sbPath = path.join(dir, "storyboard.json");
|
|
49
|
+
const rcPath = path.join(dir, "recipe.json");
|
|
50
|
+
if (fs.existsSync(sbPath)) {
|
|
51
|
+
const rc = fs.existsSync(rcPath) ? JSON.parse(fs.readFileSync(rcPath, "utf8")) : {};
|
|
52
|
+
recipe = { version: 1, lane: rc.lane ?? "skill", engine: rc.engine ?? null, storyboard: JSON.parse(fs.readFileSync(sbPath, "utf8")), config: rc.config ?? {} };
|
|
53
|
+
if (recipe.config && "api_key" in recipe.config) delete recipe.config.api_key;
|
|
54
|
+
}
|
|
55
|
+
} catch (e) { console.error("recipe skipped:", e.message); }
|
|
43
56
|
if (!fs.existsSync(cleanPath)) { console.error(`${cleanPath} not found. Run: node scripts/trim.mjs ${dir}`); process.exit(1); }
|
|
44
57
|
if (!fs.existsSync(wirePath)) { console.error(`${wirePath} not found. Run: node scripts/manifest.mjs ${dir}`); process.exit(1); }
|
|
45
58
|
const manifest = JSON.parse(fs.readFileSync(wirePath, "utf8"));
|
|
@@ -124,7 +137,7 @@ try {
|
|
|
124
137
|
stageRes = await fetch(`${base}/api/recorder/stage`, {
|
|
125
138
|
method: "PUT",
|
|
126
139
|
headers: authHeaders,
|
|
127
|
-
body: JSON.stringify({ filename: "take.zip", sizeBytes, previewSizeBytes, manifest }),
|
|
140
|
+
body: JSON.stringify({ filename: "take.zip", sizeBytes, previewSizeBytes, manifest, ...(recipe ? { recipe } : {}) }),
|
|
128
141
|
});
|
|
129
142
|
} catch (e) {
|
|
130
143
|
console.error(`Could not reach ${base}: ${e.message}`);
|