@mevdragon/vidfarm-devcli 0.3.2 → 0.3.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/SKILL.director.md CHANGED
@@ -142,6 +142,8 @@ Two modes:
142
142
  - **`smart`** — uses the caller's Gemini/OpenAI/OpenRouter key. Samples frames, extracts scenes with labels and viral notes, extracts on-screen captions with positions, and detects viral DNA (hook, retention, payoff). Takes 30-60 seconds. Auto-fires GhostCut for subtitle removal in the background.
143
143
  - **`time-slice`** — deterministic equal-duration split. Instant. No AI provider needed.
144
144
 
145
+ **Source length cap.** Vidfarm caps auto-decompose (and all inspiration ingest) at **120 seconds** of source video. If the source is longer, the API responds with `400 { ok: false, code: "source_too_long", duration_seconds, max_duration_seconds }` and no scenes are written. Trim the source before forking / ingesting.
146
+
145
147
  Response:
146
148
 
147
149
  ```
@@ -150,6 +152,8 @@ Response:
150
152
 
151
153
  If `ghostcut_pending: true`, poll `POST /api/v1/compositions/:forkId/ghostcut-poll` until status transitions to `done` or `failed`.
152
154
 
155
+ Read-only state (does **not** advance the job or bill): `GET /api/v1/compositions/:forkId/ghostcut`. Returns `{ status, task_id, original_source_url, mirrored_url, submitted_at_ms, completed_at_ms, failure_reason }`. Use this when you want to know both video URLs without triggering another poll — the AI copilot uses it to pick between the raw upload and the caption-free mirror before feeding a video into a primitive route (image extract, dedupe, trim, ai video edit, etc.). Prefer `mirrored_url` when `status === "done"` and the downstream call should be caption-free; prefer `original_source_url` when the user explicitly wants the raw upload or when `status !== "done"`.
156
+
153
157
  ## Publish
154
158
 
155
159
  Publishing renders the fork's current working state to an MP4 using shared Remotion Lambda.
@@ -271,6 +275,11 @@ Flags: `--port`, `--dir`, `--host`, `--fork`, `--api-key` (or set `VIDFARM_API_K
271
275
 
272
276
  The dev source is persisted in `localStorage.VIDFARM_DEV_SOURCE`, so you don't need to keep the `?dev=` param in the URL after the first load.
273
277
 
278
+ `dev-serve` (`vidfarm-devcli` 0.3.2+) is realtime-safe:
279
+ - Writes coming through `PUT /composition.html` / `PATCH /composition.json` **do not loop back** as reload events — the server suppresses filesystem notifications for the just-written file for 500ms so the editor's own save never triggers a self-reload.
280
+ - The SSE `hello` event carries an `instance` id + a monotonic `cursor`. If the client reconnects after a server restart the browser detects the new instance and fires a `vidfarm:dev-reload` so the editor resyncs from disk. A 20s heartbeat keeps the SSE connection alive through intermediate proxies.
281
+ - Port collisions surface as a clear `[dev-serve] port <n> is already in use` message instead of a raw stack trace. Request bodies are capped at 8MB and time out after 15s so a stuck client can't hang the server. File paths are canonicalised so `../etc/passwd`-style paths cannot escape `--dir`.
282
+
274
283
  ## What NOT to do
275
284
 
276
285
  - Do **not** manipulate composition HTML by string concatenation. Always parse, edit, and re-serialize the DOM. The editor and runtime rely on `data-start`, `data-duration`, `data-track-index`, `data-hf-id`, `data-composition-id` attributes being well-formed.
package/SKILL.platform.md CHANGED
@@ -161,6 +161,7 @@ All three files (`composition.html`, `composition.json`, `manifest.json`) are tr
161
161
  - **Player adapter** — exposes `window.__player = { play(), pause(), seek(t), setTime(t), duration, playing, playbackRate, previewMuted, refreshClips() }` for the editor to control the iframe
162
162
  - **Media coordination** — respects `data-playback-start`, `data-volume`, and clip-specific mute
163
163
  - **Selection sync** — listens for `postMessage({ source: "hf-parent", ... })` from the parent editor to highlight/deselect clips
164
+ - **Origin-scoped messaging** — the runtime rejects inbound `message` events unless `event.source === window.parent`, so sibling iframes / popups cannot spoof play/pause/seek by forging `data.source`. Outbound `window.parent.postMessage(...)` calls are pinned to the parent's origin (seeded from `document.referrer` and refreshed from the first accepted control message) instead of `"*"`, so preview frame coordinates and layer ids no longer leak to any embedding third-party page.
164
165
 
165
166
  The same runtime is used inside the Trackpad Editor iframe AND when a published fork is embedded elsewhere. It is agnostic to whether it's being edited or viewed.
166
167
 
@@ -201,6 +202,8 @@ The Remotion Lambda is **shared across all templates and forks** — it's an inf
201
202
 
202
203
  Both modes update the composition.html and composition.json in place, and create a fork version snapshot with `reason: "manual"`.
203
204
 
205
+ **Source-duration guardrail.** `probeMediaAsset()` in `src/services/media-processing.ts` enforces a hard cap of `MAX_PROBE_DURATION_SECONDS` (120s). If the probed source exceeds it, the probe throws `MediaDurationExceededError` and the route responds with `400 { code: "source_too_long", duration_seconds, max_duration_seconds }`. The same cap applies to inspiration ingest (`ensureInspirationReadyThenFork`) — over-long submissions land as `InspirationRecord { status: "failed", errorMessage }` without ever creating a Template or fork. This is baked into the probe (not the route) so every consumer — auto-decompose, ingest, `video_probe` / `audio_probe` primitives — is protected from repeated probes of long videos being used to burn Lambda / ffmpeg / vision-API budget.
206
+
204
207
  ## GhostCut integration
205
208
 
206
209
  GhostCut is an external AI subtitle-removal service. Vidfarm proxies to it via `src/services/ghostcut.ts`:
@@ -211,6 +214,8 @@ GhostCut is an external AI subtitle-removal service. Vidfarm proxies to it via `
211
214
 
212
215
  State is persisted per fork at `compositions/forks/<forkId>/working/ghostcut.json`. The client polls `POST /api/v1/compositions/:forkId/ghostcut-poll` every few seconds during smart decompose.
213
216
 
217
+ Read-only companion: `GET /api/v1/compositions/:forkId/ghostcut` returns `{ status, task_id, original_source_url, mirrored_url, submitted_at_ms, completed_at_ms, failure_reason }` without touching GhostCut. Non-billing. Exposed for two reasons: (1) the editor chat harness / AI copilot uses it to look up both video URLs on demand so downstream primitive calls can pick raw-upload vs caption-free-mirror without hardcoding either in chat context; (2) it's `http_request`-allowlisted under `COMPOSITIONS_PREFIX` in `src/app.ts`, which is why the editor chat system prompt (`src/editor-chat.ts`) directs the model to call it before feeding a video into a primitive route. The AI is instructed **not** to call `POST /ghostcut-poll` unless the user explicitly asks to advance subtitle removal.
218
+
214
219
  Configuration: `GHOSTCUT_KEY` and `GHOSTCUT_SECRET` env vars per stage. Pass-through cost: `GHOSTCUT_USD_PER_30S` (default 0.10).
215
220
 
216
221
  ## Editor Chat
@@ -363,10 +368,13 @@ vidfarm-devcli dev-serve --dir ./my-fork --port 4321
363
368
 
364
369
  Edit flags: `--port`, `--dir`, `--host`, `--fork`, `--api-key` (or `VIDFARM_API_KEY`), `--share`, `--refetch`.
365
370
 
366
- `dev-serve` behavior:
371
+ `dev-serve` behavior (`vidfarm-devcli` 0.3.2+):
367
372
  - Serves `composition.html` / `composition.json` / `manifest.json` (and `/versions/{n}/…`) with CORS `*`.
368
373
  - Accepts `PUT /composition.html` and `PATCH /composition.json`.
369
- - Watches the dir with `fs.watch`, coalesces bursts, fans out reload events via `GET /_events` SSE.
374
+ - Watches the dir with `fs.watch`, coalesces bursts (80ms), fans out reload events via `GET /_events` SSE.
375
+ - Reload loop prevention: any successful `PUT` / `PATCH` marks the target file "self-write suppressed" for 500ms so the fs.watch event fired by the write we just performed does NOT round-trip back to the editor as a reload event.
376
+ - SSE `hello` includes `{ at, instance, cursor }`. `instance` changes on every dev-serve restart so a reconnecting client can detect a restart and force a resync; `cursor` is a monotonic counter that bumps on every broadcast reload. Heartbeat `: heartbeat <ts>` every 20s keeps intermediate proxies from timing out the connection.
377
+ - Safety rails: request bodies capped at 8MB with a 15s body-read timeout; path resolution runs through `path.relative(rootDir, ...)` so `versions/../../etc/passwd`-style paths cannot escape the served directory; `EADDRINUSE` produces a plain-English error instead of a raw stack; broadcast iterates a snapshot of the client set so a mid-broadcast disconnect can't skip healthy clients; `SIGINT` + `SIGTERM` both close cleanly with a 2s hard-exit fallback.
370
378
 
371
- The editor side lives in `demo/src/dev-mode.ts` — a self-installing module imported at the top of `demo/src/main.tsx`. It reads `?dev=<url>` (persisted to `localStorage.VIDFARM_DEV_SOURCE`), monkey-patches `window.fetch` to rewrite `/api/v1/compositions/{forkId}/*` → `${devSource}/*`, injects a persistent orange banner with an Exit button, and dispatches `vidfarm:dev-reload` on SSE reload events. `HyperframesStudioEditor.tsx` listens for that event and re-runs the composition fetch effect with `cache: "reload"`.
379
+ The editor side lives in `demo/src/dev-mode.ts` — a self-installing module imported at the top of `demo/src/main.tsx`. It reads `?dev=<url>` (persisted to `localStorage.VIDFARM_DEV_SOURCE`), monkey-patches `window.fetch` to rewrite `/api/v1/compositions/{forkId}/*` → `${devSource}/*`, injects a persistent orange banner with an Exit button, and dispatches `vidfarm:dev-reload` on SSE reload events. It also caches the `hello.instance` and re-dispatches `vidfarm:dev-reload` with `{ reason: "server-restart" }` when a reconnect sees a different instance. `HyperframesStudioEditor.tsx` listens for that event and re-runs the composition fetch effect with `cache: "reload"`.
372
380
  - Lambdas — `infra/lambda/*.ts`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mevdragon/vidfarm-devcli",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Local bridge for the Vidfarm Trackpad Editor. Point it at a template id, edit composition.html on disk (Claude Code, Codex, etc.), preview live in the browser at https://vidfarm.cc/editor/.",
5
5
  "type": "module",
6
6
  "bin": {