@amg555/frameforge-mcp 2.4.9 → 2.7.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.
Files changed (47) hide show
  1. package/.claude-plugin/marketplace.json +22 -4
  2. package/CHANGELOG.md +174 -0
  3. package/CLAUDE.md +192 -9
  4. package/README.md +47 -6
  5. package/output/harness_test_1794418727139/frames/f000000.jpg +1 -0
  6. package/package.json +4 -3
  7. package/plugins/video-pipeline/.claude-plugin/plugin.json +8 -2
  8. package/plugins/video-pipeline/skills/.upstream-skills.json +143 -0
  9. package/plugins/video-pipeline/skills/html-renderer/SKILL.md +59 -15
  10. package/plugins/video-pipeline/skills/html-renderer/references/animation-patterns.md +609 -0
  11. package/plugins/video-pipeline/skills/html-renderer/references/character-animation.md +388 -0
  12. package/plugins/video-pipeline/skills/html-renderer/references/collage-broll.md +41 -0
  13. package/plugins/video-pipeline/skills/html-renderer/references/design.md +217 -0
  14. package/plugins/video-pipeline/skills/html-renderer/references/directing.md +165 -0
  15. package/plugins/video-pipeline/skills/html-renderer/references/earshot-audio.md +201 -0
  16. package/plugins/video-pipeline/skills/html-renderer/references/engine-api.md +480 -0
  17. package/plugins/video-pipeline/skills/html-renderer/references/film-styles.md +95 -0
  18. package/plugins/video-pipeline/skills/html-renderer/references/hyperframes-motion.md +208 -0
  19. package/plugins/video-pipeline/skills/html-renderer/references/motion-craft.md +1 -0
  20. package/plugins/video-pipeline/skills/html-renderer/references/onetake-camera.md +59 -0
  21. package/plugins/video-pipeline/skills/html-renderer/references/procedural-sfx.md +135 -0
  22. package/plugins/video-pipeline/skills/html-renderer/references/remotion-patterns.md +159 -0
  23. package/plugins/video-pipeline/skills/html-renderer/references/render-contract.md +1 -0
  24. package/plugins/video-pipeline/skills/video-pipeline/SKILL.md +19 -2
  25. package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +15 -0
  26. package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +21 -0
  27. package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.js +1293 -20
  28. package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/virtual-clock.js +36 -6
  29. package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +126 -2
  30. package/scripts/deep-validate.js +52 -2
  31. package/src/audio-mixer/mixer.js +594 -63
  32. package/src/cli/doctor.js +36 -2
  33. package/src/cli/make.js +113 -9
  34. package/src/harness/bin/harness.mjs +127 -5
  35. package/src/harness/lib/capture.mjs +215 -192
  36. package/src/harness/lib/verify-quality.mjs +48 -7
  37. package/src/mcp-server/server.js +1020 -61
  38. package/src/skills/sync-upstream.js +290 -0
  39. package/src/utils/media-downloader.js +162 -0
  40. package/src/utils/transcribe.js +341 -0
  41. package/src/video-editor/editor.js +96 -11
  42. package/src/video-renderer/renderer.js +7 -1
  43. package/src/video-renderer/scene-animator.js +143 -1
  44. package/src/workflows/assets.js +4 -0
  45. package/src/workflows/ingest.js +10 -0
  46. package/src/workflows/render.js +2 -0
  47. package/src/workflows/script.js +18 -10
@@ -16,7 +16,9 @@
16
16
  let rafIdCounter = 0;
17
17
  const timeoutQueue = [];
18
18
  let timeoutIdCounter = 0;
19
- const startDate = Date.now();
19
+ const _realDateNow = Date.now;
20
+ const _realSetTimeout = window.setTimeout;
21
+ const startDate = _realDateNow();
20
22
 
21
23
  window.__playwright = true;
22
24
 
@@ -71,17 +73,37 @@
71
73
  };
72
74
 
73
75
  // 4. Seeded PRNG replacement for Math.random()
74
- let seed = 0x2F6E2B1;
76
+ // CRITICAL: re-derive seed from virtualTime on every call so that seek(t) gives
77
+ // the SAME random sequence regardless of how many frames were rendered before it.
78
+ // This matches the harness-injected shim in capture.mjs and ensures dev-preview
79
+ // mode is deterministic even without Playwright in the loop.
75
80
  window.Math.random = () => {
76
- seed = (seed + 0x6D2B79F5) | 0;
77
- let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
78
- t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
79
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
81
+ let s = (0x2F6E2B1 ^ Math.imul(Math.floor(virtualTime), 0x9E3779B9)) | 0;
82
+ s = (s + 0x6D2B79F5) | 0;
83
+ let x = Math.imul(s ^ (s >>> 15), 1 | s);
84
+ x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;
85
+ return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
86
+ };
87
+
88
+ // 4b. Remotion-pattern delayRender / continueRender async barrier
89
+ const delayRenderHandles = new Set();
90
+ let delayRenderCounter = 0;
91
+ window.delayRender = (label = 'asset') => {
92
+ const id = ++delayRenderCounter;
93
+ delayRenderHandles.add(id);
94
+ return id;
95
+ };
96
+ window.continueRender = (id) => {
97
+ delayRenderHandles.delete(id);
98
+ };
99
+ window.cancelRender = (err) => {
100
+ console.error("Render cancelled:", err);
80
101
  };
81
102
 
82
103
  // 5. Global seek contract
83
104
  window.seek = async (tSeconds) => {
84
105
  virtualTime = tSeconds * 1000;
106
+ seed = (0x2F6E2B1 ^ Math.imul(Math.floor(tSeconds * 1000), 0x9E3779B9)) | 0;
85
107
 
86
108
  // Flush due timeouts
87
109
  while (timeoutQueue.length > 0 && timeoutQueue[0].runAt <= virtualTime) {
@@ -128,6 +150,14 @@
128
150
  if (document.fonts && document.fonts.ready) {
129
151
  await document.fonts.ready;
130
152
  }
153
+
154
+ // Await all async delayRender handles before resolving seek
155
+ if (delayRenderHandles.size > 0) {
156
+ const startW = _realDateNow();
157
+ while (delayRenderHandles.size > 0 && _realDateNow() - startW < 10000) {
158
+ await new Promise(r => _realSetTimeout(r, 10));
159
+ }
160
+ }
131
161
  };
132
162
 
133
163
  // Bridge for renderer
@@ -1,8 +1,8 @@
1
1
  # MCP Tool Reference
2
2
 
3
- FrameForge exposes 13 tools on the HTTP MCP server (`http://localhost:3210`)
3
+ FrameForge exposes **28 tools** on the HTTP MCP server (`http://localhost:3210`)
4
4
  and via the Cloudflare Remote MCP proxy. Any MCP-compatible client works:
5
- IBM Bob, Claude Code, Codex, opencode, Cursor, Windsurf.
5
+ IBM Bob, Claude Code, Codex, opencode, Cursor, Windsurf, Kiro, Copilot.
6
6
 
7
7
  ---
8
8
 
@@ -150,6 +150,130 @@ Configure with `EXTERNAL_PLATFORM_URL` + `EXTERNAL_PLATFORM_KEY` in `.env`.
150
150
 
151
151
  ---
152
152
 
153
+ ## Media ingestion (yt-dlp & Whisper)
154
+
155
+ ### ingest_url — Download from any web URL
156
+
157
+ ```json
158
+ {
159
+ "url": "https://youtube.com/watch?v=...",
160
+ "format": "video",
161
+ "outputDir": ".frameforge-files/downloads/"
162
+ }
163
+ ```
164
+
165
+ Requires `yt-dlp` in PATH. Supports 1000+ sites. Returns `{ localPath, duration, width, height }`.
166
+
167
+ ### transcribe_media — Whisper word-level timestamps
168
+
169
+ ```json
170
+ {
171
+ "mediaPath": ".frameforge-files/voice_0.m4a",
172
+ "model": "base",
173
+ "format": "all",
174
+ "language": "en"
175
+ }
176
+ ```
177
+
178
+ Returns `{ outputs: { ndjson, srt, json }, pauses, wordCount }`. NDJSON feeds `captions()` in engine.js.
179
+
180
+ ---
181
+
182
+ ## Professional audio post-production
183
+
184
+ ### mix_audio_pro — 15+ year sound engineer chain
185
+
186
+ ```json
187
+ {
188
+ "voicePath": ".frameforge-files/voice_0.m4a",
189
+ "outputPath": ".frameforge-files/audio_pro.m4a",
190
+ "musicPath": "/path/to/bg-music.mp3",
191
+ "platform": "youtube",
192
+ "roomProfile": "cinematic",
193
+ "noiseGate": true,
194
+ "stereoWiden": true,
195
+ "sfxCues": [
196
+ { "name": "whoosh", "time": 2.5, "volume": 0.8 },
197
+ { "name": "impact", "time": 8.0, "volume": 1.0 },
198
+ { "name": "riser", "time": 18.0, "volume": 0.7 },
199
+ { "name": "deep_boom","time": 30.0, "volume": 0.9 },
200
+ { "name": "sparkle", "time": 35.2, "volume": 0.4 }
201
+ ]
202
+ }
203
+ ```
204
+
205
+ Chain: noise gate → 80Hz HPF → presence EQ → de-esser → compressor → room acoustics → sidechain duck → stereo widen → SFX → EBU R128 loudnorm.
206
+ SFX types: whoosh | impact | pop | click | riser | chime | buzz | tick | swish | punch | sparkle | deep_boom | transition
207
+ Room profiles: studio_dry | broadcast | cinematic | concert_hall | intimate
208
+ Platform presets: youtube (-14 LUFS) | podcast (-16) | broadcast (-23) | netflix (-27) | instagram (-14)
209
+
210
+ ---
211
+
212
+ ## Video editing & planning
213
+
214
+ ### plan_video_edit — Plan/Approve/Render
215
+
216
+ ```json
217
+ {
218
+ "footage": ["/path/clip1.mp4", "/path/clip2.mp4"],
219
+ "goal": "Create a cinematic product showcase",
220
+ "style": "cinematic_anamorphic",
221
+ "target_duration": 60,
222
+ "audio_path": ".frameforge-files/voice_0.m4a",
223
+ "music_path": "/path/to/bg.mp3"
224
+ }
225
+ ```
226
+
227
+ Style options include all lemo-opuscar film styles:
228
+ `cinematic` | `fast_paced` | `documentary` | `vintage` | `cyberpunk` | `vibrant`
229
+ `editorial_collage` | `retro_vhs` | `anime_action` | `sci_fi_hud` | `lofi_chill` | `minimal_monochrome`
230
+ `cinematic_anamorphic` | `broadcast_news` | `blueprint_3d` | `film_16mm` | `product_obsidian` | `retro_vhs_80s`
231
+
232
+ Returns: `{ planPath, editJsonPath, planMarkdown, editConfig }`.
233
+
234
+ ### sync_upstream_skills — Fetch new commits from 9 reference repos
235
+
236
+ ```json
237
+ { "silent": false }
238
+ ```
239
+
240
+ Makes GitHub API requests. Returns `{ updatedRepos, perCategoryUpdates, lastSync }`.
241
+ Each category update includes: `repo`, `sha`, `desc`, `userImpact`, `capabilities[]`.
242
+
243
+ Tracked repos: HyperFrames, lemo-opuscar, OneTake, gbro-collage-broll, remotion-dev/skills,
244
+ earshot, ClaudeAnimationBase, claude-remotion-skill, ffmpeg-skill.
245
+
246
+ ### check_upstream_updates — Fast offline manifest read
247
+
248
+ ```json
249
+ {}
250
+ ```
251
+
252
+ No network requests — reads the local manifest only. Returns:
253
+ ```json
254
+ {
255
+ "lastSync": "2026-11-11T20:00:00Z",
256
+ "manifestVersion": "2.7.0",
257
+ "totalRepos": 9,
258
+ "syncedCount": 9,
259
+ "repos": [
260
+ {
261
+ "id": "hyperframes",
262
+ "repo": "heygen-com/hyperframes",
263
+ "category": "compositor",
264
+ "sha": "a1b2c3d",
265
+ "lastChecked": "...",
266
+ "userImpact": "New compositor patterns or GSAP animation primitives available"
267
+ }
268
+ ],
269
+ "hint": "Run sync_upstream_skills to fetch latest commits from GitHub."
270
+ }
271
+ ```
272
+
273
+ Call this to show users their current sync status without making any API calls.
274
+
275
+ ---
276
+
153
277
  ## Calling from any IDE
154
278
 
155
279
  Any IDE that supports MCP calls these tools via JSON-RPC:
@@ -20,7 +20,7 @@ check('batch handling', code.includes('Array.isArray(body)'));
20
20
  check('ping handler', code.includes("method === 'ping'"));
21
21
  check('notifications/init', code.includes("notifications/initialized"));
22
22
  check('body size limit', code.includes('MAX_BODY_BYTES'));
23
- check('SERVER_INFO version 2.4.9', code.includes("version: '2.4.9'"));
23
+ check('SERVER_INFO version 2.7.0', code.includes("version: '2.7.0'"));
24
24
  check('18 tools defined (incl get_job_status)', code.includes('get_job_status'));
25
25
  check('export_to_editor tool defined', code.includes('export_to_editor'));
26
26
  check('edit_video tool defined', code.includes('edit_video'));
@@ -28,6 +28,10 @@ check('generate_scene_code tool defined', code.includes('generate_scene_code'));
28
28
  check('preview_scene tool defined', code.includes('preview_scene'));
29
29
  check('verify_scene_quality tool defined', code.includes('verify_scene_quality'));
30
30
  check('plan_video_edit tool defined', code.includes('plan_video_edit'));
31
+ check('sync_upstream_skills tool defined', code.includes('sync_upstream_skills'));
32
+ check('media-downloader exists', fs.existsSync('./src/utils/media-downloader.js'));
33
+ check('transcribe engine exists', fs.existsSync('./src/utils/transcribe.js'));
34
+ check('sync-upstream script exists', fs.existsSync('./src/skills/sync-upstream.js'));
31
35
  check('video-editor exists', fs.existsSync('./src/video-editor/editor.js'));
32
36
  check('voxforge TTS provider', code.includes('voxforge'));
33
37
  const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
@@ -280,6 +284,51 @@ check('engine.js exports onTwos()', engineJs.includes('export function onTwo
280
284
  check('engine.js exports seg()', engineJs.includes('export function seg'));
281
285
  check('engine.js exports pulse()', engineJs.includes('export function pulse'));
282
286
  check('engine.js exports shots()', engineJs.includes('export function shots'));
287
+ check('engine.js exports interpolate()', engineJs.includes('export function interpolate'));
288
+ check('engine.js exports interpolateColors()', engineJs.includes('export function interpolateColors'));
289
+ check('engine.js exports measureSpring()', engineJs.includes('export function measureSpring'));
290
+ check('engine.js exports sequence()', engineJs.includes('export function sequence'));
291
+ check('engine.js exports noise2D()', engineJs.includes('export function noise2D'));
292
+ check('engine.js exports noise3D()', engineJs.includes('export function noise3D'));
293
+ check('engine.js exports evolvePath()', engineJs.includes('export function evolvePath'));
294
+ check('engine.js exports transition', engineJs.includes('export const transition'));
295
+ check('engine.js exports absoluteFill()', engineJs.includes('export function absoluteFill'));
296
+ check('engine.js exports loopTime()', engineJs.includes('export function loopTime'));
297
+ check('engine.js exports cameraPath()', engineJs.includes('export function cameraPath'));
298
+ check('engine.js exports cameraShake()', engineJs.includes('export function cameraShake'));
299
+ check('engine.js exports collageCard()', engineJs.includes('export function collageCard'));
300
+ check('engine.js exports mascot()', engineJs.includes('export function mascot'));
301
+ check('engine.js handles declarative .clip', engineJs.includes('.clip[data-start]'));
302
+ check('editor.js supports splitCut (j-cut/l-cut)', fs.readFileSync('./src/video-editor/editor.js', 'utf8').includes('splitCut'));
303
+ // v2.7.0 — HyperFrames beat-grid, OneTake OM library, ROOM_SFX, event/window.__events
304
+ check('engine.js exports beatAt()', engineJs.includes('export function beatAt'));
305
+ check('engine.js exports beatTime()', engineJs.includes('export function beatTime'));
306
+ check('engine.js exports snapToBeat()', engineJs.includes('export function snapToBeat'));
307
+ check('engine.js exports OM object', engineJs.includes('export const OM'));
308
+ check('engine.js OM.spring defined', engineJs.includes('spring: (t,') || engineJs.includes('spring:(t,'));
309
+ check('engine.js OM.iris defined', engineJs.includes('iris: (t,') || engineJs.includes('iris:(t,'));
310
+ check('engine.js OM.camera defined', engineJs.includes('camera: (t,') || engineJs.includes('camera:(t,'));
311
+ check('engine.js OM.carryScore defined', engineJs.includes('carryScore'));
312
+ check('engine.js exports ROOM_SFX', engineJs.includes('export const ROOM_SFX'));
313
+ check('engine.js exports event()', engineJs.includes('export function event'));
314
+ check('engine.js sets window.__events', engineJs.includes('window.__events'));
315
+ // v2.7.0 — Earshot forge DSP in mixer.js
316
+ const mixerJs = fs.readFileSync('./src/audio-mixer/mixer.js', 'utf8');
317
+ check('mixer.js has forgeModalResonator()', mixerJs.includes('forgeModalResonator'));
318
+ check('mixer.js has forgeSVFilter()', mixerJs.includes('forgeSVFilter'));
319
+ check('mixer.js has forgeComputedRoom()', mixerJs.includes('forgeComputedRoom'));
320
+ check('mixer.js has forgeKitSoundBank()', mixerJs.includes('forgeKitSoundBank'));
321
+ check('mixer.js supports computed: room', mixerJs.includes("startsWith('computed:')"));
322
+ // v2.7.0 — plan_video_edit J/L-cut + TREATMENT.md
323
+ const serverJsV27 = fs.readFileSync('./src/mcp-server/server.js', 'utf8');
324
+ check('server.js plan_video_edit has bpm param', serverJsV27.includes('bpm:') && serverJsV27.includes('plan_video_edit'));
325
+ check('server.js plan_video_edit has generate_treatment', serverJsV27.includes('generate_treatment'));
326
+ check('server.js plan_video_edit writes TREATMENT.md', serverJsV27.includes('TREATMENT.md'));
327
+ check('server.js plan_video_edit assigns cut_type', serverJsV27.includes('cut_type'));
328
+ check('server.js plan_video_edit assigns audio_offset', serverJsV27.includes('audio_offset'));
329
+ check('server.js check_upstream_updates returns hasUpdates', serverJsV27.includes('hasUpdates'));
330
+ check('server.js check_upstream_updates returns pendingUpdates', serverJsV27.includes('pendingUpdates'));
331
+ check('server.js render_animation returns sfxEvents', serverJsV27.includes('sfxEvents'));
283
332
 
284
333
  // ── Code-Driven Video Harness ─────────────────────────────────────────────
285
334
  console.log('\n=== Code-Driven Video Harness ===');
@@ -303,7 +352,7 @@ check('cf-worker has checkRateLimit', cfWorker.includes('checkRateLimit'));
303
352
  check('cf-worker hashes token (SHA-256)', cfWorker.includes('SHA-256'));
304
353
  check('cf-worker uses RATE_LIMITS KV', cfWorker.includes('env.RATE_LIMITS'));
305
354
  check('cf-worker returns 429 on limit', cfWorker.includes('429'));
306
- check('cf-worker version 2.4.9', cfWorker.includes("'2.4.9'"));
355
+ check('cf-worker version 2.7.0', cfWorker.includes("'2.7.0'"));
307
356
  check('wrangler.toml has kv_namespaces',fs.readFileSync('./cloudflare-mcp/wrangler.toml','utf8').includes('kv_namespaces'));
308
357
 
309
358
  // ── plugins/skills present ────────────────────────────────────────────────
@@ -311,6 +360,7 @@ console.log('\n=== Plugin skills ===');
311
360
  check('video-pipeline SKILL.md exists', fs.existsSync('./plugins/video-pipeline/skills/video-pipeline/SKILL.md'));
312
361
  check('html-renderer SKILL.md exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/SKILL.md'));
313
362
  check('motion-craft reference exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/references/motion-craft.md'));
363
+ check('remotion-patterns reference exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/references/remotion-patterns.md'));
314
364
  check('render-contract reference exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/references/render-contract.md'));
315
365
 
316
366
  // ── Security: no .kiro references in committed source ────────────────────