@amg555/frameforge-mcp 2.4.10 → 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.
- package/.claude-plugin/marketplace.json +22 -4
- package/CHANGELOG.md +174 -0
- package/CLAUDE.md +192 -9
- package/README.md +47 -6
- package/output/harness_test_1794418727139/frames/f000000.jpg +1 -0
- package/package.json +4 -3
- package/plugins/video-pipeline/.claude-plugin/plugin.json +8 -2
- package/plugins/video-pipeline/skills/.upstream-skills.json +143 -0
- package/plugins/video-pipeline/skills/html-renderer/SKILL.md +59 -15
- package/plugins/video-pipeline/skills/html-renderer/references/animation-patterns.md +609 -0
- package/plugins/video-pipeline/skills/html-renderer/references/character-animation.md +388 -0
- package/plugins/video-pipeline/skills/html-renderer/references/collage-broll.md +41 -0
- package/plugins/video-pipeline/skills/html-renderer/references/design.md +217 -0
- package/plugins/video-pipeline/skills/html-renderer/references/directing.md +165 -0
- package/plugins/video-pipeline/skills/html-renderer/references/earshot-audio.md +201 -0
- package/plugins/video-pipeline/skills/html-renderer/references/engine-api.md +480 -0
- package/plugins/video-pipeline/skills/html-renderer/references/film-styles.md +95 -0
- package/plugins/video-pipeline/skills/html-renderer/references/hyperframes-motion.md +208 -0
- package/plugins/video-pipeline/skills/html-renderer/references/onetake-camera.md +59 -0
- package/plugins/video-pipeline/skills/html-renderer/references/procedural-sfx.md +135 -0
- package/plugins/video-pipeline/skills/html-renderer/references/remotion-patterns.md +159 -0
- package/plugins/video-pipeline/skills/video-pipeline/SKILL.md +19 -2
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +15 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +21 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.js +1293 -20
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/virtual-clock.js +36 -6
- package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +126 -2
- package/scripts/deep-validate.js +52 -2
- package/src/audio-mixer/mixer.js +594 -63
- package/src/cli/doctor.js +36 -2
- package/src/cli/make.js +113 -9
- package/src/harness/bin/harness.mjs +127 -5
- package/src/harness/lib/capture.mjs +215 -192
- package/src/harness/lib/verify-quality.mjs +47 -7
- package/src/mcp-server/server.js +1015 -59
- package/src/skills/sync-upstream.js +290 -0
- package/src/utils/media-downloader.js +162 -0
- package/src/utils/transcribe.js +341 -0
- package/src/video-editor/editor.js +96 -11
- package/src/workflows/ingest.js +10 -0
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
let rafIdCounter = 0;
|
|
17
17
|
const timeoutQueue = [];
|
|
18
18
|
let timeoutIdCounter = 0;
|
|
19
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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:
|
package/scripts/deep-validate.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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 ────────────────────
|