@amg555/frameforge-mcp 0.0.0-stage → 2.3.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 (51) hide show
  1. package/.bob/artifacts/enterprise-ai-video-agent-platform-agnostic-implementation-guide.html +464 -0
  2. package/.claude-plugin/marketplace.json +17 -0
  3. package/.gitattributes +19 -0
  4. package/.mcp.json +45 -0
  5. package/CHANGELOG.md +431 -0
  6. package/CLAUDE.md +278 -0
  7. package/CONTRIBUTING.md +112 -0
  8. package/LICENSE +21 -0
  9. package/Procfile +1 -0
  10. package/README.md +359 -2
  11. package/SECURITY.md +71 -0
  12. package/START_HERE.md +144 -0
  13. package/assets/metadata/assets.json +5 -0
  14. package/assets/metadata/embeddings.json +5 -0
  15. package/install.sh +64 -0
  16. package/package.json +58 -4
  17. package/src/animation/animation-system.js +229 -0
  18. package/src/asset-library/README.md +188 -0
  19. package/src/asset-library/asset-library.js +639 -0
  20. package/src/audio-mixer/mixer.js +187 -0
  21. package/src/cli/doctor.js +173 -0
  22. package/src/cli/make.js +655 -0
  23. package/src/cli/setup-keys.js +378 -0
  24. package/src/config/load-config.js +72 -0
  25. package/src/fact-checker/checker.js +384 -0
  26. package/src/fact-checker/review-gate.js +306 -0
  27. package/src/fact-checker/validation-set.js +1078 -0
  28. package/src/mcp-server/server.js +1299 -0
  29. package/src/mcp-server/stdio.js +87 -0
  30. package/src/pipeline/artifact-manifest.js +323 -0
  31. package/src/pipeline/artifact-retention.js +189 -0
  32. package/src/pipeline/external-adapter.js +366 -0
  33. package/src/pipeline/providers.js +853 -0
  34. package/src/pipeline/retry-policy.js +134 -0
  35. package/src/pipeline/state-machine.js +297 -0
  36. package/src/pipeline/telemetry.js +162 -0
  37. package/src/quality-gate/gate.js +229 -0
  38. package/src/schema/scene.schema.json +101 -0
  39. package/src/shorts-extractor/extractor.js +343 -0
  40. package/src/utils/log-redaction.js +274 -0
  41. package/src/video-renderer/renderer.js +364 -0
  42. package/src/worker/server-render-free.js +420 -0
  43. package/src/worker/server.js +328 -0
  44. package/src/workflows/assets.js +131 -0
  45. package/src/workflows/ingest.js +299 -0
  46. package/src/workflows/packaging.js +156 -0
  47. package/src/workflows/publishing.js +206 -0
  48. package/src/workflows/render.js +102 -0
  49. package/src/workflows/research.js +151 -0
  50. package/src/workflows/script.js +210 -0
  51. package/src/workflows/shorts.js +113 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,431 @@
1
+ # Changelog
2
+
3
+ All notable changes to FrameForge are documented here.
4
+
5
+ ---
6
+
7
+ ## [2.3.0] — 2026-11-02
8
+
9
+ ### Added
10
+
11
+ - **HTML renderer** — complete Playwright-based HTML compositor for motion-graphics-quality video output. Activated via the `html-renderer` skill (`plugins/video-pipeline/skills/html-renderer/SKILL.md`).
12
+ - `plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.js` — full `seek(t)` runtime: `layer()`, `text()`, `chip()`, `wallCard()`, `imageCard()`, `stamp()`, `codeCard()`, `terminal()`, `at()`, `stagger()`, `captions()`, `cue()`, `faceAt()`, `spring()`, `lerp()`, `easeIn/Out/InOut()`, browser preview loop
13
+ - `reel/engine.css` — base reset + design tokens
14
+ - `scripts/render.mjs` — Playwright frame renderer (sheet / stills / full / mux)
15
+ - `scripts/mix_audio.py` — voice concat + sidechain ducking + -14 LUFS
16
+ - `scripts/verify.py` — 7-gate quality verification (same gates as `QualityGate`)
17
+ - `scripts/transcribe.py` — Whisper word-level caption outline generator (`openai-whisper`, free local)
18
+ - `scripts/capture.mjs` — headless Playwright screenshot for b-roll (site, tweet, URL)
19
+ - `scripts/stock.py` — Pexels stock footage/image downloader (free API key); Pollinations fallback for images
20
+ - `scripts/html-renderer/new-project.mjs` updated: copies real engine files instead of writing stubs; includes all 8 scripts
21
+ - **Tool 14: `get_job_status`** — MCP tool to poll a job queued by `queue_full_pipeline`. Returns `{ status, stage, result, error }`. Added to `src/mcp-server/server.js`; automatically available on stdio transport. 5 new tests.
22
+ - **Cloudflare KV rate limiter** — sliding-window 100 req/hr per bearer token. SHA-256-hashed key stored in `RATE_LIMITS` KV namespace. Returns HTTP 429 + `Retry-After` when exceeded. KV binding documented in `cloudflare-mcp/wrangler.toml`. Rate-limiting skipped when KV not bound (local dev).
23
+
24
+ ### Changed
25
+
26
+ - `VideoRenderer.assembleMain` — minimum scene guard lowered from 5 → 1 (throws only on 0; warns below 3). The README's 2-scene `create_video_from_script` examples now work without error.
27
+ - Cloudflare Worker version → `2.3.0`; `SERVER_INFO.version` → `2.3.0`; `package.json` → `2.3.0`
28
+ - `scripts/deep-validate.js` — +27 checks (engine asset files, rate-limit, Tool 14, new scripts, auth token forwarding, publish_video invariants)
29
+ - `README.md` — version badge, test count, Tool 14 row, HTML Renderer section, architecture diagram tool counts updated
30
+
31
+ ### Security
32
+
33
+ - `get_job_status` and `queue_full_pipeline` now forward `INTERNAL_API_TOKEN` as `Authorization: Bearer` in all HTTP requests to the worker API. Previously these two tools made unauthenticated calls.
34
+ - `publish_video` manifest no longer emits stale `youtubeTitle`/`youtubeDescription` alias keys.
35
+ - `cloudflare-mcp/src/index.js` architecture comment corrected (was "Telegram / YouTube").
36
+ - `log-redaction.js` — removed stale YouTube OAuth2 key names (`youtubeRefreshToken`, `youtubeClientSecret`).
37
+ - MCP HTTP server now enforces `MCP_AUTH_TOKEN` when set, using `crypto.timingSafeEqual` to prevent timing oracle attacks. Previously the token was validated only in the Cloudflare Worker proxy; the local HTTP server only logged a warning.
38
+ - `external-adapter.js` — fixed `this`-binding bug in `downloadOutput()` where `this._authHeader()` was called inside a nested regular function that lost the instance context. Auth headers are now captured before the closure.
39
+ - `providers.js` — removed unused `execSync` import (imported but never called; reduces unnecessary attack surface).
40
+ - `publishing.js` — removed stale `youtubeTitle`/`youtubeDescription` fallback from title/description extraction.
41
+ - Server startup no longer logs active provider configuration (`LLM=groq TTS=edge-tts ...`) to stdout, preventing accidental info disclosure in log aggregation pipelines.
42
+ - `scripts/deep-validate.js` — +5 new invariant checks covering MCP auth enforcement, provider config log hygiene, and stale YouTube fallback removal.
43
+
44
+ ### Tests
45
+
46
+ - 456 tests, 98 suites (was 449 / 96 before this patch)
47
+
48
+ ---
49
+
50
+ ## [2.2.0] — 2026-11-02
51
+
52
+ ### Changed
53
+
54
+ - **Delivery channels** — Telegram and YouTube removed from `publishing.js`. Replaced with two platform-agnostic channels:
55
+ - `file` — copy master + shorts to `N8N_FILES_DIR/published/` (default)
56
+ - `webhook` — POST JSON delivery summary to `PUBLISH_WEBHOOK_URL`
57
+ - **Review notifications** — `review-gate.js` no longer uses Telegram `sendMessage`. Now POSTs structured JSON events (`HUMAN_REVIEW_REQUIRED`, `PRODUCTION_REJECTED`) to `REVIEW_NOTIFY_URL` — compatible with any HTTP receiver (n8n, Make, Zapier, custom)
58
+ - **Tool 13 renamed** — `n8n_webhook_trigger` → `queue_full_pipeline`. POSTs `{ type: "full_pipeline", payload }` to `WORKER_API_URL/jobs`. All three server variants updated (HTTP, stdio, Cloudflare Worker)
59
+ - **`src/cli/make.js`** — `--channels` default changed from `telegram` to `file`; Telegram batch-summary curl block removed
60
+ - **`src/cli/setup-keys.js`** — Telegram + YouTube setup sections replaced with `PUBLISH_WEBHOOK_URL` and `REVIEW_NOTIFY_URL` sections
61
+ - **`docs/env.template`** — Telegram and YouTube env vars removed; `PUBLISH_WEBHOOK_URL`, `REVIEW_NOTIFY_URL`, `WORKER_API_URL` added
62
+ - **`scripts/deep-validate.js`** — stale YouTube OAuth2 + Telegram + n8n checks replaced with file/webhook delivery checks; `SERVER_INFO version` updated to `2.2.0`
63
+ - **`package.json`** — version bumped to `2.2.0`
64
+ - **`README.md`** — delivery description, Tool 13 name, test counts, badge versions updated
65
+
66
+ ### Removed
67
+
68
+ - **`docs/youtube-upload.md`** — YouTube OAuth2 upload guide. YouTube delivery removed from the core pipeline; external delivery via `external_platform_call` or a webhook receiver handles it.
69
+ - **`TELEGRAM_BOT_TOKEN`**, **`TELEGRAM_CHAT_ID`**, **`YOUTUBE_CLIENT_ID`**, **`YOUTUBE_CLIENT_SECRET`**, **`YOUTUBE_REFRESH_TOKEN`**, **`N8N_WEBHOOK_URL`** — all env vars no longer read by any source file.
70
+
71
+ ### New env vars
72
+
73
+ | Var | Purpose |
74
+ |-----|---------|
75
+ | `PUBLISH_WEBHOOK_URL` | HTTP endpoint that receives delivery summary JSON on publish |
76
+ | `REVIEW_NOTIFY_URL` | HTTP endpoint that receives fact-check review request events |
77
+ | `WORKER_API_URL` | Worker API base URL for `queue_full_pipeline` (default: `http://localhost:3211`) |
78
+
79
+ ---
80
+
81
+
82
+ ## [2.1.0] — 2026-11-02
83
+
84
+ ### Added
85
+
86
+ - **5 new test modules** — 103 new tests across previously untested production modules:
87
+ - `src/quality-gate/gate.test.js` — QualityGate result schema, check entry shape, _ffprobe/_ffmpeg helpers
88
+ - `src/shorts-extractor/extractor.test.js` — _focalToFFmpegX (even-pixel constraint, clamping), _scoreSegment, _selectSegments (non-overlap, ≤5), _silenceToBoundaries, _defaultFocalY, error on too-short video
89
+ - `src/audio-mixer/mixer.test.js` — constructor, detectPauses graceful fallback, mixWithDucking/mixVideo error paths
90
+ - `src/video-renderer/renderer.test.js` — _captionFilter (sanitisation, truncation, upper-case), _discoverScenes (ordering, size filter, sceneCount cap), _findRandomMusicFile, renderScene/assembleMain error paths
91
+ - `src/fact-checker/checker.test.js` — _normalizeClaim, _classifyRisk, _requiredSources, _hash, _gatePass, _domainGroup, checkClaims (cache, structure, empty), checkClaim, formatCitation
92
+
93
+ ### Changed
94
+
95
+ - **`src/schema/scene.schema.json`** — aligned with the v2.0.0 breaking changes shipped in that release:
96
+ - `schema_version` default corrected from `1.0.0` → `2.0.0`
97
+ - `character_id` enum (`ancient_man|modern_man|none`) replaced with a free-form string description
98
+ - `character` block no longer `required` in schema `required` array (character is optional)
99
+ - `narration` and `visual_description` `minLength` relaxed from `10` → `1`
100
+ - `camera_hint` enum expanded to include all 11 AnimationSystem hints: `slow_zoom_in`, `slow_zoom_out`, `tilt_up`, `tilt_down`, `ken_burns` (were missing since the animation system was extended)
101
+ - `character.pose` and `character.emotion` enums removed — free-form strings
102
+ - **`package.json`** — version bumped to `2.1.0`; `test` script extended to include 4 new test files; dead `check-credentials` script reference to deleted `n8n-log-redaction.js` fixed
103
+
104
+ ### Removed
105
+
106
+ - **`src/utils/n8n-log-redaction.js`** — n8n Code node utility. n8n was removed from this project; the file was dead code. Log redaction is handled by `src/utils/log-redaction.js`.
107
+ - **`src/asset-library/test-asset-library.js`** — legacy console-log test script referencing the removed `ancient_man`/`modern_man` characters. Not in node:test format, not run by `npm test`.
108
+
109
+ ---
110
+
111
+ ## [2.0.1] — 2026-11-02
112
+
113
+ ### Security
114
+
115
+ - **Timing-safe token comparison** — `cloudflare-mcp` and `server-render-free.js` both used plain `===` to compare auth tokens. Replaced with `crypto.subtle.timingSafeEqual` (Worker) and `crypto.timingSafeEqual` (Node) to eliminate token-oracle timing attacks.
116
+ - **Request body size limits** — All three HTTP servers (`server.js`, `server-render-free.js`, `src/mcp-server/server.js`) now reject bodies > 4 MB with HTTP 413, preventing OOM from oversized payloads.
117
+ - **Path traversal guards** — `edit_scenes` and `render_from_scenes` now reject any `productionId` containing `/` or `\`. `multi_voice_tts` now validates `outputPath` is inside `FILES_DIR`.
118
+ - **`multi_voice_tts` env-mutation race fixed** — The tool previously mutated `process.env` and restored it in `finally`, which was a race condition under concurrent MCP calls. `TTSProvider` now accepts per-call constructor options `{platform, voice, rate, apiKey, webhookUrl}` that override `process.env` without mutation. All `_edgeTTS`/`_elevenLabs`/`_azureTTS`/`_openaiTTS`/`_customTTS` methods read `this._overrides` first.
119
+ - **Startup warnings** — Both `server.js` and `server-render-free.js` now print a `WARNING` on startup when `MCP_AUTH_TOKEN` / `INTERNAL_API_TOKEN` is not set.
120
+ - **Health endpoint hardened** — `src/mcp-server/server.js` health check no longer leaks the full tool list.
121
+
122
+ ### Fixed
123
+
124
+ - **Stale `youtubeTitle` field names** — `runFullPipeline` in both `server-render-free.js` and `src/worker/server.js` was passing `scr.youtubeTitle` / `scr.youtubeDescription` / `scr.youtubeTags` to `runPackaging`. Script schema v2.0.0 renamed these fields to `title` / `description` / `tags`. The pipeline now uses the correct names.
125
+ - **Cloudflare Worker tool list was stale** — `cloudflare-mcp/src/index.js` only advertised 6 tools. All 13 tools are now exposed (`ingest_media`, `edit_scenes`, `render_from_scenes`, `external_platform_call`, `create_video_from_script`, `multi_voice_tts`, `n8n_webhook_trigger` added).
126
+ - **`write_script` description updated** — Removed "55-scene" and "educational comedy" from the tool description (stale since v2.0.0).
127
+ - **Cloudflare Worker `serverInfo.version` updated** to `2.0.0` (was `1.1.0`).
128
+ - **`SERVER_INFO.version` in `server.js`** updated to `2.0.0` (was `1.0.0`).
129
+
130
+ ---
131
+
132
+ ## [2.0.0] — 2026-11-02
133
+
134
+ ### Breaking Changes
135
+
136
+ **Scene schema updated to `2.0.0`.**
137
+ - `character_id` is now a free-form string (was an enum of `ancient_man|modern_man|none`)
138
+ - `visual_description` no longer requires `[CAMP][MODERN][COMPARE][REACTION][TIMELINE]` tags
139
+ - `youtubeTitle`/`youtubeDescription`/`youtubeTags` renamed to `title`/`description`/`tags` in `runScript()` return value (legacy keys still returned for backwards compat)
140
+
141
+ ### Removed
142
+ - All hardcoded domain content: "YouTube history researcher", "ancient_man", "modern_man", caveman loincloth image style constant, `[CAMP][MODERN][COMPARE][REACTION][TIMELINE]` scene tags
143
+ - `.kiro/` folder removed from git history entirely (IBM Kiro IDE dev artifacts — not needed by contributors)
144
+ - Hardcoded 55-scene / 1000-word minimums relaxed to 5 scenes / 50 words (short-form reels now work)
145
+
146
+ ### Added
147
+
148
+ **Tool 11: `create_video_from_script`** — Agent-first path. The IDE agent writes the complete `scenes[]` array in its own context; FrameForge renders without any internal LLM calls. This is the primary path for , Claude Code, Cursor, and any agentic IDE.
149
+
150
+ ```json
151
+ { "scenes": [
152
+ { "narration": "Spoken text", "caption": "OVERLAY", "visual_description": "Image prompt" }
153
+ ] }
154
+ ```
155
+
156
+ **Tool 12: `multi_voice_tts`** — Per-call TTS platform override. Call ElevenLabs, Azure, OpenAI TTS, edge-tts, or any webhook without changing global `.env`. Pass `apiKey` inline for voice cloning workflows.
157
+
158
+ **Generic `research_topic` and `write_script` tools** — now accept:
159
+ - `persona`: who the narrator is (e.g. "travel blogger", "tech reviewer")
160
+ - `format`: video format (e.g. "Instagram reel", "long-form tutorial", "explainer")
161
+ - `style`: content style (e.g. "comedic", "documentary", "educational", "vlog")
162
+ - `language`: BCP-47 code for the script language
163
+ - `targetDuration`: video duration in seconds (auto-calculates scene count)
164
+ - `imageStyle`: visual aesthetic for AI image prompts ("photorealistic", "anime", "watercolour", etc.)
165
+
166
+ **`generate_scene_image`** — `imageStyle` param replaces hardcoded art style. Any visual aesthetic now possible.
167
+
168
+ ### How FrameForge works with an agentic IDE
169
+
170
+ FrameForge is a **rendering execution engine**. Your IDE ( / Claude Code / Cursor) is the intelligence.
171
+
172
+ **Farhan's workflow** (the PDF): Claude Desktop controls a browser, types scripts into ElevenLabs, runs Remotion renders. The agent drives the GUI.
173
+
174
+ **FrameForge + agentic IDE**: The IDE writes scenes in its context and calls structured MCP tool functions. No browser control needed. The IDE calls `create_video_from_script` → FrameForge handles TTS + image gen + FFmpeg assembly → returns `masterPath`. Like Opus Clip but controlled by your IDE.
175
+
176
+ ---
177
+
178
+ ## [1.4.1] — 2026-11-02
179
+
180
+ ### Changed — LLM Provider Overhaul
181
+
182
+ **Removed**: `watsonx` provider. All WatsonX references removed from providers, doctor, setup wizard, and env template.
183
+
184
+ **Added 6 new LLM providers** (all OpenAI-compatible except Anthropic):
185
+
186
+ | Provider | Default model | Key env var | Free tier |
187
+ |---|---|---|---|
188
+ | `cerebras` | `llama3.3-70b` (2000+ tok/s) | `CEREBRAS_API_KEY` | ✅ |
189
+ | `openrouter` | `anthropic/claude-sonnet-4-5` | `OPENROUTER_API_KEY` | ✅ credits |
190
+ | `anthropic` | `claude-sonnet-4-5` | `ANTHROPIC_API_KEY` | — |
191
+ | `gemini` | `gemini-2.0-flash` | `GEMINI_API_KEY` | ✅ |
192
+ | `mistral` | `mistral-large-latest` | `MISTRAL_API_KEY` | — |
193
+ | `together` | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | `TOGETHER_API_KEY` | ✅ credits |
194
+
195
+ - `openai` default model updated from `gpt-4o-mini` → `gpt-4o`
196
+ - `groq` default updated to `llama-3.3-70b-versatile`
197
+ - **Anthropic native API**: Anthropic uses `/v1/messages` (not `/chat/completions`) — a private `_anthropicChat()` shim handles this transparently.
198
+ - **OpenRouter headers**: `HTTP-Referer` + `X-Title` attribution headers added automatically.
199
+ - **`LLM_MODEL` env var** overrides the default model for any provider.
200
+ - **`webhook` provider** now explicitly listed in `LLM_DEFAULTS` (covers LM Studio, vLLM, LocalAI, any proxy).
201
+ - **Model update guide** embedded as a comment in `providers.js` with docs URL for each provider.
202
+
203
+ **`npm run doctor`** updated — detects all configured providers, shows active model name, recommends free options with a better fix message.
204
+
205
+ **`npm run setup` wizard** updated — all 6 new providers in the menu, WatsonX removed.
206
+
207
+ **`.kiro/config/production-secrets.env.template`** — full rewrite:
208
+ - All LLM providers documented with inline links
209
+ - External platform adapter section added
210
+ - YouTube OAuth2 section added
211
+ - TTS key names corrected (`AZURE_SPEECH_KEY` was `AZURE_TTS_KEY`)
212
+
213
+ ---
214
+
215
+ ## [1.4.0] — 2026-11-02
216
+
217
+ ### Added — Flexible Pipeline Modes
218
+
219
+ **Pipeline mode system (`--mode` flag)**
220
+ FrameForge is no longer all-or-nothing. Every stage is now independently skippable.
221
+
222
+ | Mode | What it does |
223
+ |---|---|
224
+ | `full-auto` (default) | Research → Script → AI Images → TTS → Render → Publish |
225
+ | `edit-only` | Skip all AI generation. Supply your own clips/images/audio. |
226
+ | `script-only` | Research + Script only. Review/edit the JSON, then resume. |
227
+ | `custom` | Mix and match with `--no-*` flags. |
228
+
229
+ **New CLI flags**
230
+ - `--mode full-auto|edit-only|script-only|custom`
231
+ - `--scenes-file <path>` — Load a scenes JSON directly, skip research + script
232
+ - `--script-file <path>` — Load an approved script JSON, skip research only
233
+ - `--media-dir <path>` — Auto-discover video/image/audio clips from a directory
234
+ - `--no-research` — Use `--topic` as raw context instead of running the research stage
235
+ - `--no-script` — Skip script generation (requires `--scenes-file`)
236
+ - `--no-images` — Skip AI image generation (use `video_path`/`image_path` in scenes)
237
+ - `--no-tts` — Skip TTS synthesis (use `audio_path` in scenes)
238
+ - `--no-publish` — Render only, skip all delivery channels
239
+ - `--dry-run` now prints a resume hint: `npm run make -- --script-file <path>`
240
+
241
+ **Interactive key setup wizard (`src/cli/setup-keys.js`)**
242
+ Walk through every provider in ~2 minutes. Writes a ready-to-use `.env` file.
243
+ - `npm run setup` — Launch the wizard
244
+ - `npm run setup:check` — Validate existing `.env`
245
+ - `node src/cli/setup-keys.js --out .env.production` — Write to a named file
246
+ - Covers: Groq / OpenAI / WatsonX / Ollama / webhook LLM, edge-tts / ElevenLabs (voice cloning) / Azure / OpenAI TTS / webhook TTS, Pollinations / fal.ai / Replicate / DALL-E / webhook image, Telegram, YouTube OAuth2, external platform adapter
247
+
248
+ **Media ingest workflow (`src/workflows/ingest.js`)**
249
+ Build a production-ready `scenes.json` from user-supplied media — no LLM needed.
250
+ - File-based: `--scenes-file ./my-scenes.json`
251
+ - Directory auto-discovery: `--media-dir ./clips` (pairs `scene01.mp4` + `scene01.mp3` + `scene01.txt` automatically)
252
+ - Scenes format: `{ narration, caption, video_path, image_path, audio_path, camera_hint, music_mood }`
253
+ - Reports how many scenes need AI images vs pre-supplied, TTS vs pre-recorded audio
254
+
255
+ **External platform adapter (`src/pipeline/external-adapter.js`)**
256
+ Connect FrameForge to any external platform via HTTP — VoxForge, ElevenLabs, Remotion, Runway, Pika, n8n, Make, or any REST API.
257
+ - Configure with 5 env vars: `EXTERNAL_PLATFORM_NAME`, `EXTERNAL_PLATFORM_URL`, `EXTERNAL_PLATFORM_KEY`, `EXTERNAL_PLATFORM_INGEST_PATH`, `EXTERNAL_PLATFORM_RENDER_PATH`
258
+ - Built-in presets for: `elevenlabs`, `remotion`, `voxforge`, `runway`, `pika`
259
+ - Operations: `ingestMedia()`, `renderScenes()`, `checkStatus()`, `downloadOutput()`
260
+ - Polls job status automatically until completion
261
+
262
+ **4 new MCP tools** (accessible from Cursor, Claude Code, Kiro, Windsurf, )
263
+ - `ingest_media` — Upload clips/images/audio, get a scenes manifest back
264
+ - `edit_scenes` — Read and patch a scenes manifest (update captions, swap files, append scenes)
265
+ - `render_from_scenes` — Render directly from a manifest, skipping research/script
266
+ - `external_platform_call` — Bridge to VoxForge/Runway/ElevenLabs/Remotion/any platform
267
+
268
+ **`assets.js` `skipImages`/`skipTts` flags**
269
+ `runAssets({ skipImages: true, skipTts: true })` now skips generation stages cleanly. Scenes with `_skipImageGen` or `_skipTts` fields (set by `--no-images` / `--no-tts`) are respected.
270
+
271
+ **New npm scripts**
272
+ - `npm run setup` — Key setup wizard
273
+ - `npm run setup:check` — Validate `.env`
274
+ - New `frameforge-setup` CLI binary
275
+
276
+ ---
277
+
278
+ ## [1.3.2] — 2026-11-02
279
+
280
+ ### Fixed
281
+
282
+ - **`asset-library.js` `findAsset` — null-dereference on semantic match** (#bug): `_semanticSearch`
283
+ returns the asset directly, not a `{similarity, asset}` wrapper. The caller checked
284
+ `semanticMatch.similarity` which was always `undefined`, so semantic fallback never matched.
285
+ The guard is now `if (semanticMatch)` with a direct return.
286
+ - **`asset-library.js` `_callPollinationsAPI` — double download** (#bug): The method called
287
+ `_downloadAsset(imageUrl, description)` internally, then `generateAsset` also called
288
+ `_downloadAsset` with the returned path. The image was fetched twice. Fixed: `_callPollinationsAPI`
289
+ now returns the URL string only; `generateAsset → _downloadAsset` performs the single download.
290
+ - **`asset-library.js` `_generateAssetId` — timestamp-based ID breaks idempotency** (#bug):
291
+ `Date.now()` was part of every generated ID, so running the same description twice created
292
+ two registry entries for the same asset. ID is now a deterministic content hash of the
293
+ visual description.
294
+ - **`asset-library.js` `_saveRegistry` — non-atomic write corrupts JSON under concurrency** (#bug):
295
+ A crash mid-write left a truncated `assets.json`. Registry is now written to a `.tmp` file
296
+ and atomically renamed, so readers always see a complete file.
297
+ - **`pipeline/providers.js` `_download` — no redirect following** (#bug): Pollinations and most
298
+ image CDNs issue 302 redirects. The old helper rejected non-200 responses, making every
299
+ Pollinations download fail. The method now follows up to 5 redirects and cleans up the
300
+ partial output file on any error.
301
+ - **`pipeline/providers.js` `httpPost`/`httpGet` — no request timeout** (#bug): Both helpers
302
+ had no socket timeout, so a hung connection (e.g., LLM provider under load) blocked the
303
+ process indefinitely. A 30-second `timeout` option is now set on every request.
304
+ - **`fact-checker/review-gate.js` `_sendReviewRequest` — dead `escaped` variable** (#cleanup):
305
+ The method built an `escaped` string with MarkdownV2 escaping but then passed the raw
306
+ `message` (not `escaped`) to Telegram with `parse_mode: 'Markdown'`. Raw message with
307
+ unescaped markdown caused Telegram 400 errors. Fixed: removed the unused `escaped` variable
308
+ and dropped `parse_mode` so the message is delivered as plain text.
309
+ - **`fact-checker/review-gate.js` `_waitForDecisions` — silent TIMEOUT_APPROVE** (#bug):
310
+ Timed-out claims were marked `TIMEOUT_APPROVE` and logged only at `warn` level, silently
311
+ releasing the pipeline. They are now marked `TIMEOUT` (neutral decision, visible in state
312
+ machine logs, no auto-approve semantics).
313
+ - **`fact-checker/checker.js` `_searchBrave` — `r.age` NaN year** (#bug): Brave Search returns
314
+ `r.age` as a human-readable string like `"5 days ago"`, which `new Date("5 days ago")` parses
315
+ as `Invalid Date` (year `NaN`). The field `r.page_age` is the ISO date string — used that
316
+ instead, with `new Date().getFullYear()` as the fallback.
317
+ - **`pipeline/artifact-retention.js` `sweepExpiredIntermediates` — shallow scan** (#bug): The
318
+ sweep only descended one directory level from `filesDir`, missing retention tags in
319
+ `video_tmp` and deep production subdirectories. The directory walk is now fully recursive.
320
+
321
+ ---
322
+
323
+ ## [1.3.1] — 2026-11-02
324
+
325
+ ### Fixed
326
+
327
+ - **`server.js` `publish_video` tool — double Telegram delivery** (#bug): The handler sent
328
+ the video inline via `spawnSync('curl')` and then immediately called `runPublishing()`,
329
+ which also sends to Telegram when `channels` includes `'telegram'`. Removed the redundant
330
+ inline curl block; the tool now delegates entirely to `runPublishing()`.
331
+ - **`server.js` `n8n_webhook_trigger` — dead destructure** (#cleanup): `const { https: _https,
332
+ http: _http } = require('https')` was a no-op (the `https` module has no `http` export) and
333
+ the variables were unused. Removed the dead line.
334
+ - **`fact-checker/checker.js` `_searchBrave` — gzip decode corruption** (#bug): The Brave Search
335
+ request included `Accept-Encoding: gzip` but `_httpsGet` collects raw Buffer chunks and decodes
336
+ them as UTF-8 directly. A gzip-compressed response would produce garbled JSON and a parse error.
337
+ Removed the `Accept-Encoding: gzip` header so the server responds with plain JSON.
338
+ - **`renderer.js` `_captionFilter` — dead re-escape** (#cleanup): The caption was already stripped
339
+ of `:` and `%` characters by the preceding regex, making the subsequent `.replace(/:/g, '\\:')` and
340
+ `.replace(/%/g, '\\%')` calls unreachable no-ops. Removed the dead replacements.
341
+ - **`quality-gate/gate.js` — comment/code mismatch** (#docs): The file header said
342
+ `Integrated loudness: -14 LUFS ±1.0 LU` and `True peak: ≤ -1.0 dBTP`, but the actual
343
+ enforcement thresholds in code are ±1.5 LU and -0.5 dBTP. Corrected the comments.
344
+ - **`animation-system.js` `_zoomIn`/`_zoomOut` — frames param unused** (#bug): Both functions
345
+ accepted a `frames` argument but always used a fixed step constant regardless of duration.
346
+ The zoom step is now proportional to `frames` so a 2-second scene zooms at the same visual
347
+ speed as a 10-second scene. A 2× cap prevents snapping on very short scenes.
348
+ - **`retry-policy.js` Script timeout too short** (#bug): The `Script` stage timeout was 3 min,
349
+ but LLM calls with `maxTokens: 32000` on slower providers routinely exceed that, causing
350
+ spurious retries and dead-letters. Raised to 5 min (matching `Research`).
351
+ - **`workflows/assets.js` — null imagePaths in output hash** (#bug): For scenes with a
352
+ user-supplied `video_path`, `imagePath` is `null`. The hash was computed from
353
+ `results.map(r => r.imagePath).join(',')` which included literal `null` strings, making the
354
+ hash content-unstable across runs. Hash is now computed from `sceneId:voicePath` pairs which
355
+ are always populated.
356
+ - **`cli/doctor.js` — incomplete LLM detection** (#bug): The LLM provider check only tested for
357
+ `LLM_BASE_URL` but missed `WEBHOOK_LLM_URL` and `OLLAMA_BASE_URL` / `PROVIDER_LLM=ollama`.
358
+ Ollama users with only `PROVIDER_LLM=ollama` set would receive a false warning. All three
359
+ env-var shapes are now detected.
360
+
361
+ ---
362
+
363
+ ## [1.2.0] — 2026-11-02
364
+
365
+ ### Added
366
+ - **YouTube Data API v3 uploader** — full OAuth2 resumable chunked upload
367
+ (`YOUTUBE_CLIENT_ID` + `YOUTUBE_CLIENT_SECRET` + `YOUTUBE_REFRESH_TOKEN`).
368
+ Uploads default to `private` privacy; set `YOUTUBE_PRIVACY=public|unlisted` to override.
369
+ - **`docs/youtube-upload.md`** — step-by-step OAuth2 setup guide with troubleshooting table.
370
+ - **Real search API support in fact-checker** — set `SEARCH_API_KEY` +
371
+ `SEARCH_PROVIDER=tavily|brave|searxng` to verify claims against live web results.
372
+ Falls back to LLM-synthesized sources when no key is configured.
373
+ - **Asset library download implementation** — `_downloadAsset()` now performs a real
374
+ HTTPS download with redirect-following (up to 5 hops); partial files are cleaned up on error.
375
+ - **Asset library Pollinations integration** — `_callPollinationsAPI()` now downloads the
376
+ generated image to disk instead of returning a URL string.
377
+ - **Asset library FFmpeg composition** — `composeCharacter()` uses `ffmpeg overlay` filter
378
+ to layer identity + pose + emotion PNGs; falls back gracefully to pose-only if FFmpeg fails.
379
+ - **Asset library keyword search** — `_semanticSearch()` implements term-overlap scoring
380
+ against `visual_description`, `tags`, and metadata fields (no embedding API required).
381
+ - **`log-redaction.js` expanded** — added patterns for ElevenLabs (`sk_`), Tavily (`tvly-`),
382
+ Brave Search (`BSA`), fal.ai (`key_`), Replicate (`r8_`), 32-64 char hex internal tokens,
383
+ and new JSON key names for webhook URLs, MCP auth, YouTube, and search credentials.
384
+ - **`CONTRIBUTING.md`** — contributor guide with PR workflow and coding standards.
385
+ - **`SECURITY.md`** — vulnerability disclosure policy.
386
+ - **`LICENSE`** — MIT license.
387
+ - **`CHANGELOG.md`** — this file.
388
+
389
+ ### Changed
390
+ - `_logForReview()` in `publishing.js` now uses the already-imported `fsSync` instead of
391
+ re-requiring `fs` at call time.
392
+ - Commit message convention locked: no `feat:`/`fix:`/`docs:` prefixes — plain imperative only.
393
+ - Git author set to `AMG555 <amgt503@gmail.com>`.
394
+
395
+ ---
396
+
397
+ ## [1.1.0] — 2026-11-02
398
+
399
+ ### Added
400
+ - **`src/cli/doctor.js`** — cross-platform pre-flight checker (Node.js, ffmpeg, ffprobe,
401
+ edge-tts, fonts, env vars, Telegram credentials).
402
+ - **`src/cli/make.js`** — standalone one-command video generator (`npm run make -- --topic "..."`).
403
+ Supports `--dry-run`, `--no-shorts`, `--channels`, `--out-dir`, `--style`, `--env`.
404
+ - **`src/mcp-server/stdio.js`** — zero-port stdio MCP transport with 8 tools, lazy loading,
405
+ and in-process async job registry. Works with , Cursor, Windsurf, Kiro.
406
+ - **Webhook provider adapters** — `WebhookLLMProvider`, `WebhookTTSProvider`,
407
+ `WebhookImageProvider` in `src/pipeline/providers.js`. Configure any HTTP API via env
408
+ vars without code changes.
409
+ - **User-supplied media bypass** — `scene.video_path`, `scene.image_path`, `scene.audio_path`
410
+ skip AI generation entirely per scene.
411
+ - **`cloudflare-mcp/README.md`** — complete Cloudflare Workers deploy guide.
412
+
413
+ ### Fixed
414
+ - P0 crash in `research.js`: `checker.checkClaim()` replaced with `checker.checkClaims([c]).then(r => r[0])`.
415
+ - Cross-platform font resolver in `renderer.js` (Windows arialbd/calibrib/segoeui, macOS Helvetica, `FFMPEG_FONT` env override).
416
+ - Stale `rate` parameter removed from `tts.synthesize()` call in `assets.js`.
417
+
418
+ ---
419
+
420
+ ## [1.0.0] — 2026-11-01
421
+
422
+ ### Added
423
+ - Full A→F pipeline: Research → Script → Assets → Render → Shorts → Packaging → Publishing.
424
+ - Cloudflare Workers remote MCP proxy (SSE + Streamable HTTP, ~80% token savings).
425
+ - Render-free split worker (`server-render-free.js`) for Render.com + local Pi/Oracle hybrid.
426
+ - State machine, retry policy, telemetry, artifact manifest, artifact retention.
427
+ - Animation system (10 camera presets), AudioMixer (LUFS -14, sidechain ducking), quality gate.
428
+ - Shorts extractor (silence segmentation + focal-point 9:16 crop).
429
+ - Fact checker with risk classification, 3-way review gate, 100-claim gold standard set.
430
+ - Provider abstraction: Groq, OpenAI, watsonx, Ollama, ElevenLabs, Azure TTS, fal.ai,
431
+ Replicate, DALL-E 3, Pollinations.