@officexapp/vidfarm-devcli 0.21.29 → 0.21.30

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.
@@ -365,6 +365,8 @@ vidfarm doctor # ✓/⚠/✗ for node, ffmpeg, hy
365
365
  - **`ffmpeg`** — the media engine the devcli drives for every local media op: `remove-greenscreen --local` (chroma-key → transparent PNG/WebP/WebM, ProRes fallback where the local libvpx can't do WebM alpha), STT/caption demux, `raws scan` clip cutting, `stills`, audio trims. **Hard requirement** — `vidfarm doctor` exits non-zero without it.
366
366
  - **`hyperframes`** — **Vidfarm's own render/animation engine, open-source under a whitelabel.** *Vidfarm renders ARE hyperframes renders.* The devcli bundles it and re-exposes everything it does locally as **`vidfarm hf <subcommand>`** — `vidfarm hf render <dir> -o out.mp4`, `capture`, `validate`, `preview`, `doctor`, plus the ONNX `remove-background` matting, Kokoro TTS, and Whisper STT engines the `tts`/`stt`/`remove-background` commands ride. Installing it standalone makes these run **natively** instead of a slower `npx -y hyperframes` fallback (which `vidfarm doctor` will warn about).
367
367
 
368
+ **The local studio UI is whitelabeled automatically.** `hyperframes preview` (and `vidfarm hf preview`) serves a studio SPA whose stock shell paints an upstream "HeyGen · HyperFrames" header logo and a "HyperFrames Studio" tab title. Every devcli hyperframes spawn — and `vidfarm doctor` — idempotently patches that shell in place (`src/devcli/studio-brand.ts`) so the header reads **VidFarm**, the tab title is **VidFarm Studio**, and the product name in the studio's own copy (lint panel, feedback prompt, storyboard/agent prompts, clipboard writes) is rewritten. `.hyperframes/` paths and `data-hyperframes-*` DOM hooks are preserved verbatim — they name real files and hooks — and CLI verbs map to their twins (`hyperframes render` → `vidfarm render`). A fresh `npm install` restores the stock shell; the next devcli command (or `vidfarm doctor`) re-applies it. If a director ever screenshots upstream branding, run `vidfarm doctor`.
369
+
368
370
  **Treat `hyperframes` as Vidfarm** (it is — just whitelabeled): never point it at an external HeyGen/vendor account, publish service, or telemetry endpoint. Keep `HYPERFRAMES_SKIP_SKILLS=1` + `HYPERFRAMES_NO_TELEMETRY=1` set (the devcli sets these for its children); do not run `hyperframes auth`/`cloud`/`publish`/`play`/`feedback` — Vidfarm's own render/share/telemetry surfaces replace all of them. Everything else `hyperframes` can do locally is fair game and encouraged.
369
371
 
370
372
  ## Local dev loop (`vidfarm serve`)
package/SKILL.director.md CHANGED
@@ -2059,6 +2059,8 @@ vidfarm doctor # ✓/⚠/✗ for node, ffmpeg, hy
2059
2059
  - **`ffmpeg`** — the media engine the devcli drives for every local media op: `remove-greenscreen --local` (chroma-key → transparent PNG/WebP/WebM, ProRes fallback where the local libvpx can't do WebM alpha), STT/caption demux, `raws scan` clip cutting, `stills`, audio trims. **Hard requirement** — `vidfarm doctor` exits non-zero without it.
2060
2060
  - **`hyperframes`** — **Vidfarm's own render/animation engine, open-source under a whitelabel.** *Vidfarm renders ARE hyperframes renders.* The devcli bundles it and re-exposes everything it does locally as **`vidfarm hf <subcommand>`** — `vidfarm hf render <dir> -o out.mp4`, `capture`, `validate`, `preview`, `doctor`, plus the ONNX `remove-background` matting, Kokoro TTS, and Whisper STT engines the `tts`/`stt`/`remove-background` commands ride. Installing it standalone makes these run **natively** instead of a slower `npx -y hyperframes` fallback (which `vidfarm doctor` will warn about).
2061
2061
 
2062
+ **The local studio UI is whitelabeled automatically.** `hyperframes preview` (and `vidfarm hf preview`) serves a studio SPA whose stock shell paints an upstream "HeyGen · HyperFrames" header logo and a "HyperFrames Studio" tab title. Every devcli hyperframes spawn — and `vidfarm doctor` — idempotently patches that shell in place (`src/devcli/studio-brand.ts`) so the header reads **VidFarm**, the tab title is **VidFarm Studio**, and the product name in the studio's own copy (lint panel, feedback prompt, storyboard/agent prompts, clipboard writes) is rewritten. `.hyperframes/` paths and `data-hyperframes-*` DOM hooks are preserved verbatim — they name real files and hooks — and CLI verbs map to their twins (`hyperframes render` → `vidfarm render`). A fresh `npm install` restores the stock shell; the next devcli command (or `vidfarm doctor`) re-applies it. If a director ever screenshots upstream branding, run `vidfarm doctor`.
2063
+
2062
2064
  **Treat `hyperframes` as Vidfarm** (it is — just whitelabeled): never point it at an external HeyGen/vendor account, publish service, or telemetry endpoint. Keep `HYPERFRAMES_SKIP_SKILLS=1` + `HYPERFRAMES_NO_TELEMETRY=1` set (the devcli sets these for its children); do not run `hyperframes auth`/`cloud`/`publish`/`play`/`feedback` — Vidfarm's own render/share/telemetry surfaces replace all of them. Everything else `hyperframes` can do locally is fair game and encouraged.
2063
2065
 
2064
2066
  ## Local dev loop (`vidfarm serve`)
@@ -15,6 +15,7 @@ import { parseArgs } from "node:util";
15
15
  import { detectLocalAgent } from "../services/clip-curation/index.js";
16
16
  import { hasFfmpeg, resolveFfmpeg, resolveFfprobe } from "../services/clip-curation/ffmpeg.js";
17
17
  import { resolveHyperframesCli } from "./hyperframes-cli.js";
18
+ import { applyVidfarmStudioBrand } from "./studio-brand.js";
18
19
  import { resolveSkillsRoot } from "./skills.js";
19
20
  import { readStoredAuth } from "./auth-store.js";
20
21
  import { scanLocalServers, reapProcesses } from "./process-scan.js";
@@ -138,6 +139,18 @@ export async function runDoctorCommand(argv) {
138
139
  add("hyperframes", hyperframesCli ? "ok" : "warn", hyperframesCli
139
140
  ? `${hyperframesCli} (Vidfarm's whitelabel render/animation engine — reach it via \`vidfarm hf …\`)`
140
141
  : "not installed — Vidfarm's render/animation engine (open-source whitelabel). Install for native local render/tts/stt/matting: `npm i -g hyperframes` (else falls back to slow `npx -y hyperframes`)");
142
+ // 3b. Whitelabel the LOCAL studio shell (the `hyperframes preview` editor UI),
143
+ // which otherwise renders the upstream "HeyGen · HyperFrames" header logo and
144
+ // tab title. `doctor` REPAIRS it rather than just reporting: the patch is
145
+ // idempotent and a fresh `npm install` restores the pristine shell.
146
+ if (hyperframesCli) {
147
+ const brand = applyVidfarmStudioBrand();
148
+ add("studio branding", brand === "unavailable" ? "warn" : "ok", brand === "patched"
149
+ ? "local studio shell re-branded as VidFarm (upstream logo/title replaced)"
150
+ : brand === "already-branded"
151
+ ? "local studio shell already whitelabeled as VidFarm"
152
+ : "could not patch the local studio shell — `hyperframes preview` will show upstream branding (cosmetic only)");
153
+ }
141
154
  // 4. Chrome for the in-process render (stills / serve local render).
142
155
  const chrome = detectChromeForRender();
143
156
  add("chrome", chrome.found ? "ok" : "warn", chrome.found ? chrome.detail : `${chrome.detail} — local render/stills will try to download one on first run`);
@@ -49,6 +49,18 @@ export async function runHyperframesCommand(subcommand, args, opts = {}) {
49
49
  "Local engines and vidfarm BYOK keys are the only auth.");
50
50
  }
51
51
  const cli = resolveHyperframesCli();
52
+ // The local studio SPA shell ships upstream branding (a "HeyGen · HyperFrames"
53
+ // header logo and a "HyperFrames Studio" tab title) that `hyperframes preview`
54
+ // serves straight off disk — the one Vidfarm surface the web editor's own
55
+ // rebrand never reaches. Whitelabel the shell on EVERY hyperframes spawn, not
56
+ // just preview: the patch is a cheap idempotent file check, and doing it here
57
+ // means a director who later runs `hyperframes preview` by hand still gets the
58
+ // VidFarm studio. Best-effort — branding never fails a render.
59
+ try {
60
+ const { applyVidfarmStudioBrand } = await import("./studio-brand.js");
61
+ applyVidfarmStudioBrand();
62
+ }
63
+ catch { /* cosmetic only */ }
52
64
  // Fresh empty config dir per invocation — guarantees no ~/.heygen credential
53
65
  // (or any other persisted account state) leaks into the child process.
54
66
  const emptyConfigDir = mkdtempSync(path.join(os.tmpdir(), "vidfarm-hf-noauth-"));
@@ -0,0 +1,196 @@
1
+ // Whitelabel the LOCAL hyperframes Studio UI as VidFarm.
2
+ //
3
+ // The web editor (`/editor`, `vidfarm serve`) mounts the sealed <StudioApp/>
4
+ // through demo/src/VidfarmStudioEditor.tsx, which already swaps the upstream
5
+ // "HeyGen · HyperFrames" header logo and rewrites the product name in copy.
6
+ // But `hyperframes preview` (and `vidfarm hf preview`) boots the studio SPA
7
+ // straight out of `node_modules/hyperframes/dist/studio/index.html` — that
8
+ // bundle never passes through our editor entry, so it renders upstream branding
9
+ // on a Vidfarm director's screen.
10
+ //
11
+ // The studio server reads that index.html **per request** (studioServer.ts's
12
+ // SPA fallback), so a one-time on-disk patch of the shell is enough: no fork, no
13
+ // dist rewriting of the JS, no proxy in the middle. We inject a marker-delimited
14
+ // <style> + <script> block that does exactly what the web editor does in JS —
15
+ // hide `svg[aria-label="Hyperframes"]`, insert a VidFarm wordmark, and rename
16
+ // the product in text nodes / tooltips / clipboard writes.
17
+ //
18
+ // The patch is idempotent and versioned: a stale block is replaced, an up-to-date
19
+ // one is left alone, and a fresh `npm install` (which restores the pristine file)
20
+ // is re-patched on the next hyperframes spawn. `.hyperframes/` paths, the
21
+ // `data-hyperframes-*` DOM hooks, and CLI verbs are deliberately preserved /
22
+ // mapped, same as the web editor — renaming those would send an agent hunting
23
+ // for files that do not exist.
24
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
25
+ import path from "node:path";
26
+ import { resolveHyperframesCli } from "./hyperframes-cli.js";
27
+ /** Bump when the injected block changes — older blocks are replaced in place. */
28
+ const BRAND_VERSION = 1;
29
+ const MARK_OPEN = `<!-- vidfarm-studio-brand v${BRAND_VERSION} -->`;
30
+ const MARK_OPEN_ANY = /<!-- vidfarm-studio-brand v\d+ -->/;
31
+ const MARK_CLOSE = "<!-- /vidfarm-studio-brand -->";
32
+ // Tab icon: a self-contained gold "V" plate (data URI, so we never have to ship
33
+ // or overwrite a file inside the hyperframes package). Matches the header
34
+ // wordmark's badge below.
35
+ const VIDFARM_FAVICON = "data:image/svg+xml," +
36
+ encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">' +
37
+ '<rect width="100" height="100" rx="26" fill="#f5c518"/>' +
38
+ '<path d="M26 28h14l10 30 10-30h14L58 76H42z" fill="#1c1608"/>' +
39
+ "</svg>");
40
+ /**
41
+ * Locate `hyperframes/dist/studio/index.html` next to the resolved CLI entry.
42
+ * Returns null when hyperframes is not installed locally (the `npx -y` fallback
43
+ * path — nothing on disk of ours to patch).
44
+ */
45
+ export function resolveStudioIndexPath() {
46
+ const cli = resolveHyperframesCli();
47
+ if (!cli)
48
+ return null;
49
+ const indexPath = path.join(path.dirname(cli), "studio", "index.html");
50
+ return existsSync(indexPath) ? indexPath : null;
51
+ }
52
+ /** The injected head block: hide the upstream mark, paint ours, rename the product. */
53
+ function brandBlock() {
54
+ return `${MARK_OPEN}
55
+ <style>
56
+ /* Belt-and-suspenders: kill the upstream mark before the script runs so it
57
+ never flashes. The wordmark below is inserted in its place. */
58
+ svg[aria-label="Hyperframes"] { display: none !important; }
59
+ </style>
60
+ <script>
61
+ (function () {
62
+ var MARK = "data-vidfarm-logo";
63
+
64
+ function wordmark() {
65
+ var el = document.createElement("div");
66
+ el.setAttribute(MARK, "");
67
+ el.style.cssText = "display:inline-flex;align-items:center;gap:8px;user-select:none;line-height:1";
68
+ var badge = document.createElement("span");
69
+ badge.style.cssText =
70
+ "display:inline-grid;place-items:center;width:24px;height:24px;border-radius:7px;" +
71
+ "background:#f5c518;color:#1c1608;font-weight:800;font-size:14px;font-family:system-ui,sans-serif";
72
+ badge.textContent = "V";
73
+ var word = document.createElement("span");
74
+ word.style.cssText =
75
+ "font-weight:800;font-size:15px;letter-spacing:-.01em;color:#fffbe6;font-family:system-ui,sans-serif";
76
+ word.textContent = "VidFarm";
77
+ el.appendChild(badge);
78
+ el.appendChild(word);
79
+ return el;
80
+ }
81
+
82
+ function swapLogo() {
83
+ var logos = document.querySelectorAll('svg[aria-label="Hyperframes"]');
84
+ for (var i = 0; i < logos.length; i += 1) {
85
+ var svg = logos[i];
86
+ var prev = svg.previousElementSibling;
87
+ if ((prev && prev.hasAttribute && prev.hasAttribute(MARK)) || svg.getAttribute("data-vidfarm-hidden") === "1") continue;
88
+ svg.setAttribute("data-vidfarm-hidden", "1");
89
+ svg.style.display = "none";
90
+ if (svg.parentElement) svg.parentElement.insertBefore(wordmark(), svg);
91
+ }
92
+ }
93
+
94
+ // Same rewrite table as the web editor: CLI verbs and .hyperframes/ paths are
95
+ // matched BEFORE the bare product name, so real files/commands survive.
96
+ function rewrite(text) {
97
+ return text
98
+ .replace(/\\bhyperframes (render|preview|lint|check|publish|init|add)\\b/g, "vidfarm $1")
99
+ .replace(/(^|[^./\\w-])[Hh]yper[Ff]rames(?![./\\w-])/g, "$1Vidfarm")
100
+ .replace(/(^|[^./\\w-])[Hh]yper[Ff]rame(?![s./\\w-])/g, "$1Vidfarm")
101
+ .replace(/\\bHeyGen\\b/g, "Vidfarm");
102
+ }
103
+
104
+ function needsRewrite(text) {
105
+ return /[Hh]yper[Ff]rame|HeyGen/.test(text) && rewrite(text) !== text;
106
+ }
107
+
108
+ function swapText() {
109
+ var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
110
+ var pending = [];
111
+ for (var node = walker.nextNode(); node; node = walker.nextNode()) {
112
+ var tag = node.parentElement && node.parentElement.tagName;
113
+ if (tag === "SCRIPT" || tag === "STYLE") continue;
114
+ if (needsRewrite(node.nodeValue || "")) pending.push(node);
115
+ }
116
+ for (var i = 0; i < pending.length; i += 1) pending[i].nodeValue = rewrite(pending[i].nodeValue || "");
117
+ var attrs = ["title", "aria-label", "placeholder"];
118
+ var els = document.querySelectorAll("[title],[aria-label],[placeholder]");
119
+ for (var j = 0; j < els.length; j += 1) {
120
+ for (var k = 0; k < attrs.length; k += 1) {
121
+ var value = els[j].getAttribute(attrs[k]);
122
+ if (value && needsRewrite(value)) els[j].setAttribute(attrs[k], rewrite(value));
123
+ }
124
+ }
125
+ }
126
+
127
+ function apply() {
128
+ swapLogo();
129
+ swapText();
130
+ }
131
+
132
+ function start() {
133
+ apply();
134
+ new MutationObserver(apply).observe(document.body, { childList: true, subtree: true, characterData: true });
135
+ // Lint / storyboard panels hand agent prompts to the clipboard — rebrand on the way out.
136
+ try {
137
+ if (navigator.clipboard && navigator.clipboard.writeText) {
138
+ var original = navigator.clipboard.writeText.bind(navigator.clipboard);
139
+ navigator.clipboard.writeText = function (data) {
140
+ return original(typeof data === "string" ? rewrite(data) : data);
141
+ };
142
+ }
143
+ } catch (err) { /* clipboard locked down — branding is best-effort */ }
144
+ }
145
+
146
+ if (document.body) start();
147
+ else document.addEventListener("DOMContentLoaded", start);
148
+ })();
149
+ </script>
150
+ ${MARK_CLOSE}`;
151
+ }
152
+ /**
153
+ * Patch the installed studio shell in place. Never throws — a read-only or
154
+ * missing node_modules just means the director sees upstream branding, which is
155
+ * cosmetic and must not fail a render.
156
+ */
157
+ export function applyVidfarmStudioBrand(indexPathOverride) {
158
+ const indexPath = indexPathOverride ?? resolveStudioIndexPath();
159
+ if (!indexPath)
160
+ return "unavailable";
161
+ let html;
162
+ try {
163
+ html = readFileSync(indexPath, "utf-8");
164
+ }
165
+ catch {
166
+ return "unavailable";
167
+ }
168
+ const current = brandBlock();
169
+ if (html.includes(MARK_OPEN) && html.includes(MARK_CLOSE)) {
170
+ // Up-to-date block already present (same version) — but the block body can
171
+ // still drift if BRAND_VERSION was not bumped during development, so compare.
172
+ if (html.includes(current))
173
+ return "already-branded";
174
+ }
175
+ // Strip any previous (older-version or drifted) block before re-injecting.
176
+ let out = html.replace(new RegExp(`${MARK_OPEN_ANY.source}[\\s\\S]*?${MARK_CLOSE.replace(/[/]/g, "\\/")}`), "");
177
+ out = out.replace(/<title>[\s\S]*?<\/title>/, "<title>VidFarm Studio</title>");
178
+ // Tab icon (no-op once already swapped — the upstream href is gone by then).
179
+ out = out.replace(/href="\/favicon\.svg"/, `href="${VIDFARM_FAVICON}"`);
180
+ if (out.includes("</head>")) {
181
+ out = out.replace("</head>", `${current}\n </head>`);
182
+ }
183
+ else {
184
+ out = `${current}\n${out}`;
185
+ }
186
+ if (out === html)
187
+ return "already-branded";
188
+ try {
189
+ writeFileSync(indexPath, out, "utf-8");
190
+ }
191
+ catch {
192
+ return "unavailable";
193
+ }
194
+ return "patched";
195
+ }
196
+ //# sourceMappingURL=studio-brand.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@officexapp/vidfarm-devcli",
3
- "version": "0.21.29",
3
+ "version": "0.21.30",
4
4
  "description": "Local bridge for the Vidfarm Trackpad Editor. `vidfarm serve <template_id>` boots the FULL editor on localhost (disk-backed records/storage, free in-process render); edit composition.html on disk (Claude Code, Codex, etc.) and the browser live-morphs it.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,7 @@
32
32
  "dist/src/devcli/skills.js",
33
33
  "dist/src/devcli/speech.js",
34
34
  "dist/src/devcli/sticker-pack.js",
35
+ "dist/src/devcli/studio-brand.js",
35
36
  "dist/src/devcli/stills.js",
36
37
  "dist/src/devcli/storyboard.js",
37
38
  "dist/src/devcli/telemetry.js",
@@ -93,6 +94,7 @@
93
94
  "check": "tsc -p tsconfig.json --noEmit && npm run check:skills",
94
95
  "test:clips": "node --import tsx --test test/clip-curation.test.ts",
95
96
  "test:qa": "node --import tsx --test test/qa-check.test.ts",
97
+ "test:studio-brand": "node --import tsx --test test/studio-brand.test.ts",
96
98
  "check:skills": "node scripts/build-director-skill-rollup.mjs --check && node scripts/check-skill-routes.mjs",
97
99
  "benchmark:editor-chat": "node --import tsx scripts/benchmark-editor-chat-harness.mjs",
98
100
  "cdk:deploy:prod-serverless": "npm run build && dotenv -e .env.production -- npx aws-cdk deploy --app 'node dist/infra/cdk/bin/vidfarm-prod.js'",