跳到正文
GitHub·· 6 天前AI 评分48

Kuble-Lab 开源 open-creative-director:本地优先的 AI 创意总监聊天应用

Kuble-Lab/open-creative-director

AI 导读

Kuble-Lab 开源 open-creative-director,一个本地优先的聊天应用,通过 OpenRouter 上的 LLM 规划并调用真实生成工具产出图像、视频、语音和动态图形。

正文

Your personal AI creative director — a chat app that plans and produces images, videos, voice and motion graphics on your machine.

Deutsche Version: README.de.md

You describe what you want in plain language ("a 15-second vertical ad for my coffee brand, warm morning light"). The Creative Director — an LLM running through OpenRouter — plans the production and executes it with real generation tools: images with GPT Image 2, video with Seedance 2.5, speech with ElevenLabs, 30+ extra models (Kling, Veo, Sora, …) through Higgsfield, and HTML/GSAP motion graphics rendered on your own machines. Results appear inline in the chat and are stored locally.

Inspired by the Higgsfield "Supercomputer" concept — rebuilt as an open, local-first app.

Open Creative Director — chat with a generated hero image, cost badges and the Director's quality check

Features

  • Chat-driven production — the Director asks the right questions, then generates. Tool calling, live streaming, cost transparency per generation.
  • Images — GPT Image 2 generation and editing, reference images, SVG rasterization.
  • Image safety recovery (WP52b) — image nodes retry a safety-blocked image with the same prompt, a softened scene and a configurable reference-capable fallback model. Other list images finish and stay cached; the next run retries only missing images. Attempts and actual models are logged and billed through the usual budget checks.
  • Video — Seedance 2.5 text-to-video and image-to-video (4–30 s). Voice/character consistency via image, video and audio references. Final cuts via concat_videos: ffmpeg joins finished clips losslessly, no size limit (needs ffmpeg on the PATH).
  • Video model choice — before a video starts, the Director shows a card in the chat with the OpenRouter video models that fit the job (length, resolution, format, first frame, references): estimated price for exactly this job, strengths and weaknesses, a recommendation. Only the click starts the paid generation; cancelling charges nothing. People with a budget see what is left, options over it are disabled, and the server checks it again and reserves the estimate. “Do not ask again in this chat” remembers the model for you in this chat (in a shared chat everyone else still gets the card; reset above the input field). Admins can switch the question off in Settings → Services (Ask for the model before every video); the configured default model then starts right away. Runway Gen-4.5 (WP41) is on the card with the others (text or first frame to video, 720p, 16:9 or 9:16 only, 2–10 s, no sound, 0.12 USD per second, so 0.60 USD for 5 s). The card shows up to seven models (the curated list has seven; the cards sit in a wrapping grid). Gen-4.5 only takes a start image in 16:9 or 9:16: a square or 4:3 image is never cropped silently. The card leaves the model out and a direct call is refused before it is paid, with the format named (Crop the image or pad the canvas first). A start image that fits goes out with its own format as aspect_ratio (720 x 1280 as 9:16, 1280 x 720 as 16:9): without it OpenRouter takes 16:9 and cropped a 9:16 start image in the live test of 2026-10-04. FLUX.3 Video (2026-10-08) is the seventh model on the card (a preview model of Black Forest Labs, black-forest-labs/flux-3-video: text or first frame to video with sound, 5–20 s in whole seconds, 720p for 0.17 USD per second, so 0.85 USD for 5 s; the node view also offers 1080p at 0.29 USD per second and an end frame; no reference files). Like Gen-4.5 it only takes a start image in one of its formats (21:9, 16:9, 4:3, 1:1, 3:4 or 9:16): a 3:2 photo is left off the card and refused before payment, never cropped silently. A start image in 9:16 is confirmed by a paid run (live test 2026-10-08: 5 s at 720p for 0.85 USD, the video came back upright, 704 x 1280, with sound); the other formats come from the OpenRouter list and are not tried with a paid run yet. When which model: Gen-4.5 for cinematic motion and image quality that follow the prompt closely, short clips (up to 10 s) without sound and in a standard format; Seedance when you need sound, references (images, video, audio), other formats, or 11 to 30 s; Kling for facial expression and natural motion in short clips; Veo for high image quality with sound in a few formats and fixed lengths; FLUX.3 Video for clips with sound of 5 to 20 s; Wan for reference images and social formats at a balanced price. A model whose price list names a minimum per generation (minimum_cents_per_generation) is never estimated below it.
  • Live production status — running jobs show up in a live status bar with elapsed time, the Director posts results and failures into the chat by itself, and the thinking indicator shows what is currently executing.
  • Speech — ElevenLabs text-to-speech for voice-overs and voice masters; Eleven v4 by default, the model can be chosen in the node (optional).
  • Higgsfield integration — connect your higgsfield.ai account with one click (no API key needed) and the Director gains 30+ image/video models, billed as Higgsfield credits on your plan (optional).
  • Motion graphics — HTML/GSAP compositions rendered to MP4 on one or more render nodes (16:9, 9:16, 1:1) (optional).
  • Node view — build reusable production flows on a canvas (batch runs, ready-made templates), publish them as a clean Design App and send results back to a chat.
  • Casting — reusable characters per project: reference images plus a voice master keep faces and voices consistent across episodes.
  • Branding studio — colors, typography, logos, imagery, tone, a speaker voice. Import a design system from a ZIP (e.g. exported from claude.ai/design), export as ZIP.
  • Projects with production profiles — briefing guidelines, context files, brandings, cast and a per-project memory the Director maintains itself.
  • Roles — custom system prompts per chat, with an AI role generator.
  • Prompt presets — a curated, collapsible menu of production-ready prompts (casting, series, branding, motion, social media, marketing) plus your own team-shared presets.
  • Three languages — UI in English, German and Spanish.
  • Cost journal — every generation and LLM call is logged with its cost.
  • Agent access (MCP) — external agents order runs with a personal key, within limits per run and per month (details).

Quick start

Requirements: Node.js ≥ 22, an OpenRouter API key. Optional: ffmpeg on your PATH (enables automatic visual consistency checks on finished videos); Poppler (pdftotext, pdftoppm) for reading PDF files in the node view.

git clone https://github.com/Kuble-Lab/open-creative-director.git
cd open-creative-director
npm install
cp .env.example .env    # then put your OpenRouter key into .env
npm start               # → http://localhost:3111

By default the app listens only on 127.0.0.1. For network or container access, set HOST explicitly, for example HOST=0.0.0.0 npm start, and configure authentication before exposing it. With an explicit PORT or NODE_ENV=production, an occupied port aborts startup; only development without PORT tries the next port.

That's it. Everything else is optional and degrades gracefully.

Prefer to let an AI agent do the setup? Copy the prompt from INSTALL-PROMPT.md into Claude Code, Codex or any coding agent.

Which keys do I need?

Key / setting Required? What it enables Where to get it
OPENROUTER_API_KEY Yes The Director LLM, GPT Image 2 images, Seedance 2.5 video — all billed through one OpenRouter account openrouter.ai/keys
ELEVENLABS_API_KEY Optional Text-to-speech (generate_speech, voice list, model list; the models come from your account, a built-in list stands in where the key may not read them) and, in the node view, music and song text (audio.music, audio.music_plan) and the word times of a song (audio.lyrics_timing, for the music video). The ElevenLabs Music API needs a paid ElevenLabs plan; without one the music node stops with a clear message and nothing is charged elevenlabs.io
FAL_KEY Optional fal.ai models in the node view: the MiniMax H3 Max nodes (text/image/reference video, lip sync, camera moves, extend, insert scene, 3D to video, styles), image to 3D (Tripo, Hunyuan, Meshy, SAM 3D), editing a video with reference images (Kling O3, Wan Replace, Gemini Omni; Runway Aleph 2.0 and FLUX Video Edit run through OpenRouter and need no fal key) and a free-form fal.ai model node. Billed per second or per generation by fal.ai fal.ai/dashboard/keys
GEMINI_API_KEY Optional Nano Banana 2.1 and Nano Banana Pro straight at Google (Gemini API) on your own quota instead of through OpenRouter, in the chat and in the node view. If Google refuses for a reason of the key, the account or the service (limit, outage) and OPENROUTER_API_KEY is set, the app tries once through OpenRouter; a block of the safety filter is final. With only this key the two Google models run, everything else still needs OpenRouter. The same key lets Lyrics timing hear who sings a line (Detect choir, WP48; GEMINI_VOICES_MODEL names another model than the Flash default) aistudio.google.com/apikey
Higgsfield account Optional 30+ extra image/video models for the Director No key — click Connect in Menu → Settings and sign in to Higgsfield in the browser (OAuth login, tokens stay on the server). Behind a reverse proxy set PUBLIC_BASE_URL so the browser can reach the callback
ChatGPT subscription Optional Run the Director on GPT-5.6 models billed to your ChatGPT Plus/Pro plan instead of per-token No key — log in to the Codex CLI with "Sign in with ChatGPT", then click Import from Codex CLI login in Menu → Settings. Unofficial route via the Codex backend (gray area in OpenAI's terms) — use at your own discretion. If the subscription cannot be reached (login expired, usage limit, outage) and an OpenRouter key is set, that one model call is repeated through OpenRouter as openai/<model>: a notice in the chat says so, it is billed to your OpenRouter account, and the subscription is skipped for 10 minutes
RENDER_NODE_URL + RENDER_NODE_TOKEN Optional HTML/GSAP motion-graphics rendering Run your own render node (see below); multiple nodes manageable in Menu → Settings
PUBLIC_BASE_URL Optional Seedance audio/video references (they require publicly reachable HTTPS URLs); also the base of the Higgsfield login callback (<PUBLIC_BASE_URL>/api/higgsfield/oauth/callback) Only needed when hosting publicly or behind a reverse proxy
AUTH_WHOAMI_URL + ADMIN_EMAILS Optional User management behind your own login: private chats/workflows, sharing, admins, team list (SUPERADMIN_EMAILS, AUTH_LOGOUT_URL optional) Only for team hosting — locally the app runs open. See User management
GTS_API_TOKEN + GTS_BASE_URL Optional Attach knowledge-base documents as chat context Any compatible GTS endpoint; the example default points to gts.kuble.com

Keys can also be entered at runtime in Menu → Settings (stored server-side in data/settings.json, chmod 600, never in the repo) — they override .env without a restart.

How it works

Browser (vanilla JS, SSE)
   │
Node/Express server (server.js)
   │
   ├─ Director LLM ── OpenRouter chat completions with tool calling
   ├─ generate_image / edit_image ── GPT Image 2 via OpenRouter; Nano Banana 2.1 / Pro straight at Google with GEMINI_API_KEY (optional)
   ├─ generate_video ── Seedance 2.5 via OpenRouter (async jobs + poller)
   ├─ generate_speech / list_voices ── ElevenLabs (optional)
   ├─ higgsfield_* ── Higgsfield MCP client, OAuth login in the browser (optional)
   ├─ render_motion_graphics ── your HyperFrames render node(s) (optional)
   └─ Storage: sessions, assets, brandings, cast, projects — plain files under data/ and assets/

No database, no cloud storage: sessions, generated assets, brandings and settings are plain files on your disk. Delete the folder, everything is gone.

Node view (workflows and Design Apps)

Open Creative Director — node view with the template Still to vertical ad with voice-over: inputs, an LLM description step, Seedance animation, voice-over and a result node on the canvas

Besides the chat, the Chat | Nodes switch in the header opens a canvas for building reusable production flows. Nodes for inputs (text, text list, images, videos, audio), LLM steps, image/video/audio generation, ffmpeg editing and results are wired together; every workflow keeps its media in its own hidden session, so nothing shows up in your chat list. The image and video nodes let you choose the model in the node (see imageModels below); “Videos side by side” puts 2 to 4 videos on one canvas to compare them (local, free), “Remove background (fal)” cuts out an image, “Image to 3D (fal)” turns an image into a 3D model (GLB), “Segment video (fal)” cuts a named object out of a video, “Overlay video on video” lays a second video (for example that cutout) over the first, “Burn in captions” writes the sung words into a video as karaoke subtitles, “Sound wave” lays a moving wave over a video and the template “Music video from a song” turns a song into a video cut to the beat.

  • Canvas — left-drag on the empty canvas draws a selection marquee (Shift adds), the toolbar switches between Select (V) and Hand (H); you can also pan with Space + drag, the middle mouse button or two fingers on a trackpad, and zoom with Ctrl/Cmd + wheel or a pinch toward the cursor. Undo/redo, copy/paste, groups and notes work as expected.
  • Runs — a node, the selection or the whole flow runs on the server. Results are cached by their inputs, so unchanged nodes are skipped (“Run all” takes them from the cache; to calculate again run one node, a selection, or the whole flow with Run all again, see below). Paid nodes are marked with $, the run dialog shows the last known cost, and a paid run always asks for confirmation.
  • Batch — a Text list (one item per line, or blocks separated by ---) or a Media list makes the flow run once per item (up to 50). The counter under the list shows how many items are used.
  • fal.ai (MiniMax H3 Max) — with a FAL_KEY the fal.ai category offers nodes for every queue endpoint of MiniMax H3 Max (H3 Max video and Turbo, reference video, lip sync, camera move, extend video, insert scene, 3D to video, five styles) plus a free-form fal.ai model node for any other endpoint. Media are uploaded to the fal storage, the job runs in the fal queue and the result is fetched in the background, so you can leave the page. Costs are list-price estimates (fal.ai reports no price); the nodes are experimental until they have been tested live. The realtime director endpoint is not offered.
  • Image to 3D (fal.ai) — the node Image to 3D turns an image into a 3D model (GLB) with one of four models of your choice: Tripo H3.1, Hunyuan 3D Pro 3.1, Meshy 7.1 or SAM 3D Objects (experimental; you name the object). The front image is required; up to three more views (left, back, right) improve the back of the model where the model takes them (Tripo needs them in that order without a gap, SAM 3D takes none). The model list shows what each model is good at, how many views it takes and the price from; texture, PBR materials and detail level cost extra with some models and the estimate follows your choices. The result is the GLB file plus a preview image of it (PNG with a transparent background): Tripo, Hunyuan and Meshy deliver their own render; for SAM 3D, or when a render cannot be fetched, the app draws the image itself from the stored model (lib/glb-preview.js: in a child process, one model at a time, seen from the front like the card shows it; JPEG and WebP textures need ffmpeg, without it the model is drawn in its colour). The image goes on into other nodes, for example Edit image. It is shown in the card, the large view and the Design App with the bundled <model-viewer> (drag to rotate, wheel or pinch to zoom, no augmented reality); you download the GLB from the download icon or the result menu. Only GLB is stored, nothing uploads a 3D file, and a 3D model cannot be sent to the chat (its preview image can). A model the app cannot draw (a compressed one, for example) has no preview: a node connected to the Preview output then stops with a message and the model stays. The node is open to participants and counts against their budget, like Remove background (fal). When fal.ai finishes a job and bills it but no usable GLB comes out (for example SAM 3D delivers only a splat), the list price goes into the cost journal under the name betreiber with the note “Kein Ergebnis (Listenpreis)”: the monitoring shows what the operator paid, a participant's budget is not charged. Prices are list prices of 2026-10-02; check the terms of each provider (Tripo, Tencent Hunyuan, Meshy, Meta SAM) before you use a generated model commercially.
  • Segment video (fal.ai) — the node Segment video cuts an object you name out of a video and follows it through the clip (SAM 3, experimental). You write what to cut out in the Object field (person; several objects with commas, best in English) and choose the Output: Cutout is a WebM (VP9) with a transparent background, Mask a black and white MP4 (the object white; made for editing software), Object on black an MP4 without transparency. Max seconds (1 to 60, default 10) limits how much of the video is used, Threshold (0.1 to 0.9, default 0.5) decides how sure the model has to be: lower finds more, but less precisely. Before anything is sent, the app prepares the video with ffmpeg (lib/nodes/video-prep.js): the first Max seconds, at most 30 frames per second (a lower rate stays as it is, nothing is invented), at most 1920 pixels on the long side, H.264 in yuv420p, without sound; a video that fits already goes up unchanged. The frames of the file that is really sent are counted exactly (ffprobe), so the price is known before the upload: 0.005 USD per 16 frames, a started block of 16 counts as a whole (about 0.075 USD for 10 s at 24 fps; PRICES.videoSegment in lib/nodes/nodes-fal.js, list price of 2026-10-03). That count is the amount the run books. The plan shows an upper limit instead: Max seconds at 30 frames per second, or the length of the connected video where the plan knows it (a video from a node before that is up to date and has a stored length; an upload has none). Without ffmpeg and ffprobe the node is shown as unavailable, and if it is run anyway it stops with a message before anything is uploaded or charged. The prepared file would show up as an extra asset of the workflow if it went through fal_generate like other media, so the node uploads it itself from a scratch folder (lib/fal.js, the checks of the tool repeated: a scratch folder of this workflow, file type, size limit, key, cancellation) and deletes it right after; the address goes into input.video_url. A video that needed no preparation goes through fal_generate like any other media. The result is named by its first bytes (videoExtensionOf in lib/poller.js: an EBML header is .webm, an ftyp box is .mp4; the endpoint sends application/octet-stream) and is never re-encoded, so the transparency of the WebM stays. It has the geometry of the prepared video (at most 1920 pixels, at most 30 frames per second, no sound). Chrome and Firefox show the WebM transparent, Safari does not (the preview then says so); how the ffmpeg nodes treat the transparency is in the next two entries. The node is open to participants and counts against their budget, like Remove background (fal).
  • Transparency through the ffmpeg nodes (WP33b) — a video has an alpha channel when ffprobe says so: VP8 or VP9 with the stream tag alpha_mode = 1, a pixel format with alpha (yuva*, rgba, bgra, argb, abgr, ya8, ya16*, gbrap*) or ProRes 4444 with alpha (video.alpha of probeMedia in lib/nodes/ffmpeg-ops.js). ffmpeg reads the alpha of VP8 and VP9 only with the decoder libvpx-vp9 / libvpx, so the app names it before -i of every input with alpha (hasDecoder in lib/ffmpeg.js); an ffmpeg without it writes the log line “transparency is lost” and the run goes on. The nodes that reshape exactly one video keep the transparency and write a WebM (VP9 with alpha, -crf 32, the sound as Opus): Trim video (always re-encoded with alpha), Change speed, Resize video (the bars of contain are transparent), Adjust video colours and Concatenate videos (when every clip has alpha; clips of another size sit on transparent bars). Everything else writes MP4 as before and the transparency is gone: Overlay image on video, Overlay video on video, Merge audio into video, Burn in captions, Sound wave, Videos side by side and the finished films of the explainer video and the music video; Extract frame writes a PNG, which keeps the alpha. An ffmpeg without the encoder libvpx-vp9 or libopus writes MP4 for the nodes of the first list too, with a log line. A video without alpha gets exactly the ffmpeg arguments it got before. A result with alpha is marked in the ledger (alpha), and the node view shows a chequerboard behind it (also in the thumbnail and the app view); Safari and every browser on an iPhone or iPad play the WebM without its transparency, so the view adds a short note there. Images and uploads are marked too since WP33c (next entry).
  • Overlay video on video (WP33b) — the node Overlay video on video (video.overlay_video, local, free) lays a second video, the layer, over a background video; the result is an MP4 in the size of the background. A layer with an alpha channel (the Cutout of Segment video) is laid with its transparency; for a layer on a green background without alpha, set Remove a colour to Chroma key and choose the colour, the similarity and the soft edge. X and Y are pixels or percent (default: the centre), Scale is a share of the width of the background (default 100), Opacity runs from 0 to 1, Start is the second of the background at which the layer appears, End the second from which it is hidden (0 = until its own end), Loop the layer starts it over until the result ends. Length is the length of the background (default; a longer layer is cut), of the layer (a shorter background holds its last frame) or the shorter of the two; Sound is the sound of the background (default), of the layer (delayed by Start), a mix of both or none. The template Cut out an object and place it over another video (video-cutout-overlay) chains Segment video (output Cutout) and this node; its description names the price of the segmentation (0.005 USD per 16 frames) and that Safari does not show the transparency of the intermediate result.
  • Edit video with references (fal.ai, WP40) — the node Edit video with references (fal.video_edit, experimental) changes an existing video and keeps its motion and camera: people, objects, weather or light are replaced or changed. It is not H3 Max reference video, which makes a new clip from references and edits none. Inputs: video (required), images (up to 4, in the order of the connections) and prompt; output: video, as long as the input video. The Model decides what the node can do: Kling O3 (default; fal-ai/kling-video/o3/{standard,pro,4k}/video-to-video/edit) takes a prompt and up to 4 reference images, as people/objects (elements, default) or as style images (image_urls), and keeps the original sound if you want (Keep original sound, default on); Wan Replace (one person) (fal-ai/wan/v2.2-14b/animate/replace) replaces the main person by the person of exactly one image and takes no prompt (the field is hidden, a connected text gets a warning); Gemini Omni (text only) (google/gemini-omni-flash/v1.1/edit) changes the video by the prompt alone (weather, clothes, material, light, an object more), takes no images and sends exactly { video_url, prompt, resolution }, with no switch for the sound because it is not documented. Options: Quality for Kling (Standard, Pro, 4K), Role of the images (People/objects or Style), Resolution for Gemini Omni (360p, 720p default, 1080p, 4K), Resolution for Wan (480p default, 580p, 720p), and Cut to the allowed length (default off). The prompt is written in your language: you write “Image 1”, “Image 2” and “Video” (German “Bild 1”, “Video”; Spanish “Imagen 1”, “Vídeo”; any case for the images, a capital V for the video anywhere and lower case after an article (“the video”), so “a video game” and “Video-Datei” stay what they are), and the node sends @Element1 / @Element2 (role People/objects) or @Image1 (role Style) and @Video1; the same convention as the H3 nodes (“Image 1” by connection order). Images the prompt does not mention are sent anyway; a number beyond the connected images is refused before the run. Written in English a prompt often works better. Checks before the run, free of charge (each with a stable code, translated into German, English and Spanish, that says what to do): Kling O3 takes 3 to 15 s, MP4 or MOV, 720 to 3840 px on both sides and at most 200 MB (the upload limit of the app is 90 MB), up to 4 images and, for people/objects, at least 300 x 300 px; Gemini Omni takes at most 10 s (1 s at least), no images (“Gemini Omni takes no images. Disconnect the images or choose Kling O3.”) and needs a prompt; Wan Replace needs exactly 1 image. A message names the node that helps (Trim video, Resize video, Resize image). The switch Cut to the allowed length cuts a video that is too long to the limit of the model itself, before it is sent (the first 15 s for Kling O3, the first 10 s for Gemini Omni, with its sound; an MP4 from ffmpeg, uploaded like the prepared video of Segment video); without it the run is refused with the length and the remedy. Price (list prices of 2026-10-04, PRICES.videoEdit): per second of the video that is sent, Kling O3 0.14 USD (Standard and Pro) or 0.42 USD (4K), Wan Replace 0.08 USD, Gemini Omni 0.03 (360p), 0.10 (720p), 0.15 (1080p) or 0.30 USD (4K); the price API of fal names only 0.03 USD for Gemini Omni, the other three come from the model page. The plan shows the price per second times the length of the connected video (after the cut); an upload has no length the plan knows, so it shows the upper bound of the model (Kling O3 at most 15 s, Gemini Omni 10 s; for Wan Replace the price stays unknown). The run reads the length itself (ffprobe) and stops before the upload when there is none, because nothing would be booked; so the node needs ffmpeg and ffprobe. The node is open to participants and counts against their budget, like the other fal nodes. The template Replace people in a video (replace-people-in-video: a video, two photos, a prompt with “Image 1” / “Image 2”, Kling O3) comes with the note to use only people who have agreed to appear.
  • Runway Aleph 2.0 and long videos in parts (WP41) — the node Edit video with references has a fourth model, Runway Aleph 2.0 (text only) (runway/aleph-2 through OpenRouter, so it needs OPENROUTER_API_KEY and PUBLIC_BASE_URL, no fal key): it changes the video by the prompt alone, like Gemini Omni, and takes no images (refused before the run; the way to hand them over is not confirmed). It sends exactly { model, prompt, input_references: [{ type: 'video_url', video_url: { url } }] }: no duration, resolution or aspect ratio (the result is as long as the video). Price 0.28 USD per second, but OpenRouter bills at least 5 s, so at least 1.40 USD per run (live tests 2026-10-04: a video of 3 s and a video of 8 s were each billed 1.40 USD, not the 0.84 and 2.24 of the list; 2 s costs the same 1.40 by that rule). The plan shows an upper bound: a video over 5 s is priced by its length (8 s = 2.24 USD) although 8 s were billed 1.40 USD, because nobody knows whether OpenRouter keeps billing a flat 5 s for videos up to 30 s; the reservation must not lie below the bill if it starts to bill by length, and what is booked is always the usage.cost of the job. The price is read from the live OpenRouter list when it is loaded, the numbers in the code are the fallback, and both count the 5 billed seconds, so the plan, the reservation and the job show the same price. Live tests 2026-10-04 (two paid runs, MP4, 720 x 1280, 30 fps, no sound): with a video of 3.0 s the edit did what the prompt asked; the result is as long as the input (3.0 s), has no sound and is smaller than the input (612 x 1088). A control run with a video of 8.0 s came back 8.0 s long (240 frames at 30 fps, 612 x 1088), and the whole video was edited (frames at 1, 4, 5.5 and 7.8 s all show the change), again billed 1.40 USD. A video well over 5 s is therefore edited as a whole, and one run takes 30 s at the most, the figure the providers of the model name (2 to 30 s). Proven are 3 s and 8 s; what lies between 8 s and 30 s stays an assumption for a later paid test (is all of it edited, how is it billed). A video longer than 30 s is edited in parts (below). The other limits (at least 2 s, MP4 or WebM, at most 16 MB, 1920 px), other aspect ratios than 9:16 (only 9:16 was tried) and the way to hand over images are still assumptions, all in one table (lib/nodes/aleph-edit.js, marked “per live test”). The node is open to participants and counts against the budget like every OpenRouter video job. When which edit model: Kling O3 Edit for people or objects from reference images (the best choice to replace a person), Wan Replace for exactly one person from one image without a prompt, Gemini Omni for cheap text edits up to 10 s (weather, clothes, light), Runway Aleph 2.0 as the other model for text edits (up to 30 s per run and at least 1.40 USD a run, no sound, the result can be smaller than the input; a longer clip goes in parts), FLUX Video Edit (below) for cheap text edits up to 15 s that keep the sound. Edit longer videos in parts (switch, default off; shown for Kling O3 15 s, Gemini Omni 10 s, Aleph 30 s and FLUX Video Edit 15 s): a video longer than the limit of the model is cut into parts of nearly equal length on frame boundaries (53.5 s with Kling O3: 4 parts of about 13.4 s; with Aleph: 2 parts of about 26.7 s), every part is edited with the same images and prompt, the parts are joined in order and the original sound of the whole video is laid over the film, so nothing clicks at the joins. A model returns every part in its own length and frame rate (Kling O3: 13.375 s at 24 fps for each part), so each edited part is brought to exactly the length of its input part before the join (under 1 % stretching), and the film is as long as the original within one frame. A part that comes back more than 10 % off its length is not stretched (it would play too fast or too slow against the original sound): the node stops with a clear message (Part n came back with x s for the y s that were sent ...), the paid parts stay on their jobs (a new run costs nothing for them), and Make part again (number) makes the bad one. Price = the sum of the parts (Aleph: every part is priced for at least 5 s and by its length above that, so 53.5 s are 2 parts of 803 and 802 frames at about 7.49 USD each, 14.98 USD in all, an upper bound as above), shown in the plan and paid part by part. A part that fails never makes the finished parts cost again: the finished work lives on the jobs (each job carries the key of its part: video, frame range, model, prompt, images, options), the next run takes finished parts from them and waits for parts that are still running; the field Make part again (number) (redo_parts, e.g. 2 or 2, 4) makes single parts again once for those numbers (a later run of the node with the same numbers takes the new part and pays nothing; other numbers, or Run again on the node, make it once more), and Run again on a node with a finished result makes all of them again (after a run that failed it only goes on with what is missing, so the button under the error message never pays a finished part twice). The parts run at the same time (a part can take 40 minutes; the node waits up to 100 minutes), and a participant's budget is checked for all parts before the first one starts: all parts are cut and checked first (a part that is too heavy stops the run before anything is paid), the sum of the parts is then added to the reservation of the run where the plan did not know it, and a job that was started always keeps its share of it if the run ends early. The length of a video uploaded in the node view is read when it is uploaded (ffprobe, free), so the plan shows the sum of the parts; an older upload or a video from the chat has none, then the plan says unknown and the run reserves the sum when it starts. When both in parts and Cut to the allowed length are on, in parts wins. At the joins the people can look slightly different (each part is edited on its own): motion and camera fit because they come from the original; check the result part by part and make a part again that went wrong (a model sometimes leaves a person out of one part). The template Replace people in a video has the switch as an app input (Longer than 15 s: edit in parts).
  • FLUX Video Edit and FLUX.3 Video (2026-10-08) — two models of Black Forest Labs through OpenRouter, built on the pattern of Aleph and Gen-4.5. Live test 2026-10-08 (one paid run each, 0.94 USD in all): FLUX Video Edit edited a 3 s MP4 (1920 x 1080, 30 fps, with sound) as the prompt asked and returned 3.04 s at 1248 x 704 and 24 fps with the sound unchanged, billed by length: 0.09 USD = 3 s x 0.03, no minimum; FLUX.3 Video made 5 s at 720p from a 9:16 start image and returned it upright (704 x 1280, 24 fps, with a quiet sound track) for 0.85 USD = 5 s x 0.17. Still open: FLUX Video Edit above 3 s, at its limits and with formats other than MP4; FLUX.3 Video at 1080p, with an end frame, with a start image without aspect_ratio and from text alone. FLUX Video Edit (text only) (black-forest-labs/flux-video-edit) is the fifth model of the node Edit video with references: the video and a prompt of 1 to 4096 characters, no images (refused before the run), one run takes 1 to 15 s, an MP4 of at most 50 MiB and at least 160 px on the smaller side; a longer clip goes in parts of at most 15 s or is cut to its first 15 s, like for the other models. It sends exactly { model, prompt, input_references: [{ type: 'video_url', video_url: { url } }] }, the request of Aleph, built by the same tool (edit_video_openrouter; a test pins Aleph's request byte for byte). Price 0.03 USD per second of the result (the OpenRouter list and the price list of the maker, 2026-10-08); the plan counts the length (3 s = 0.09 USD, 15 s = 0.45 USD), with the live list and without it. The maker says the result is as long as the source, keeps its aspect ratio, has 24 fps, is scaled down to about 720p and keeps the sound of the source; the live test confirmed this for a clip of 3 s. The way the video travels and the request without extra fields are confirmed by the live test; the limits (1 to 15 s, 50 MiB, 160 px, 4096 characters, MP4 only) are still assumptions “per live test”, collected with the facts and their sources in one file (lib/nodes/flux-video-edit.js); a further paid test with a clip near the limits confirms or corrects each line. FLUX.3 Video is the seventh model on the card of the chat and in the node Generate video: 720p 0.17 and 1080p 0.29 USD per second from the list, 5–20 s, first and end frame, six formats. At 1080p the plan shows a range (10 s: 1.70 to 2.90 USD) and reserves the upper end, because the general price of the list counts as the lower end of the range. A start image in 9:16 sent with aspect_ratio 9:16 is confirmed (it came back upright); not tried are a start image without aspect_ratio (the app always sends the format of a start image and takes only the six formats of the list), a start image of another format, an end frame (of any format) and 1080p.
  • Higgsfield models that need a video (WP42) — the node Higgsfield video (video.higgsfield) has no video input, so it no longer offers models that cannot run without one, such as Kling 3.0 Motion Control; for movement from a video there is Motion transfer (Higgsfield) (hf.motion_control, the standalone Kling 3.0 Motion Control tool: image and motion video), for changing a video Edit video with references. The catalogue (lib/nodes/higgsfield-catalog.js) now tells video slots from image slots by their roles (isVideoSlot: type video, or roles that all say video, like motion_video; a slot that mixes image_references and video_references is a reference slot and only its image roles count), reports video: { max, required } in capabilitiesOf, and videoNeed / requiresVideo say whether a model needs a video (a required video slot, or video slots as the only inputs). Both Higgsfield model menus leave such models out. A saved workflow that still has one chosen is refused before the run with needs_video_motion (hint: Motion transfer (Higgsfield)) or needs_video (hint: Edit video with references), translated in de/en/es; nothing is charged, and execute refuses as well. The same holds for image.higgsfield, and since 2026-10-05 for the Director's tools higgsfield_generate_video / higgsfield_generate_image: they refuse such a model before any reference is uploaded or a job submitted, and the error names the node that takes the job. The live catalogue (read on 2026-10-04) shows no video slot for these models at all: Kling 3.0 Motion Control, Genjutsu (motion transfer and object replacement) and Kling 3.0 Omni Edit each have one optional image slot. They are therefore known by id (MODELS_NEEDING_VIDEO), and a model whose id or name says "Motion Control" counts as needing a motion video; the slot rule (video in the roles) stays for models that do describe a video slot.
  • Chequerboard for images and uploads, edge cases of the overlay, notes (WP33c) — every file the app stores is looked at once, in one place (saveAsset, completeAsset and completeAssetFile of lib/store.js, with lib/alpha.js): a PNG or WebP image whose header says it has an alpha channel (PNG colour type 4 or 6 or a tRNS chunk; WebP VP8X alpha flag or the alpha bit of VP8L) is then checked pixel by pixel with ffmpeg (alphaextract and signalstats, one call: the lowest alpha must be below 255), because many generators write RGBA PNGs in which every pixel is opaque. Without ffmpeg (or without those two filters) the header alone counts; an ffmpeg that fails on the file gives no mark. A WebM, MKV or MOV video goes through ffprobe and streamHasAlpha; MP4, JPEG, GIF and audio are not looked at at all, so they cost no time. The ledger entry gets alpha: true and the node view shows the chequerboard behind the image or video in the card, the thumbnail, the large view, the app view and the asset lists (upload field and picker); the Safari note stays with videos, because Safari shows images with alpha correctly. What WP33b has already decided (the ffmpeg ops, the WebM of fal, the concatenation) is not looked at again; a failure or a missing ffmpeg gives no mark and storing always goes on; existing assets are not marked (no migration). The chequerboard sits behind the picture itself and not in the empty margins of its frame (an image or video with alpha takes the shape of its picture instead of filling a bigger box with object-fit: contain). Overlay image on video and Overlay video on video: a background with an odd width or height stopped in the libx264 encoder; now one pixel is added at the end of the graph (pad=ceil(iw/2)*2:ceil(ih/2)*2), only for such a background; with even sizes the ffmpeg arguments are exactly the old ones. Overlay video on video writes a log line when the layer starts at or after the end of the background and the length is Background or Shortest, because the layer is then never seen. The two templates that take a PDF (Explainer video from a PDF and Explainer video: script from a PDF) say in their description and note: use only PDFs you have the rights to; figures from the PDF appear in the video.
  • Music and song text (ElevenLabs) — Generate music makes music from a description or from a song text with structure; with a video on its Length like video input the music is as long as the video (rounded up, 3 to 600 seconds), otherwise Length decides, and a song text sets the length through its sections. Song text and structure writes such a song text from an idea. It is plain text you can edit: + lines are wanted styles, - lines unwanted ones, [Verse 1 | 20 s] starts a section (20 s, 20s or 0:20, 3 to 120 seconds, the song 10 minutes at most), every other line is a song line (30 per section, 200 characters each; with the newer models music_v2 and music_v2_5 the section name counts as one of the 30 lines, so 29 song lines); \+, \-, \[ at the start of a line stand for the character itself. The inspector checks the text while you type and names the line of a problem. To change the text, right-click Song text and structure and choose Use as text: a Prompt node with the text appears and takes over the connections of that output (one undo step, nothing runs). Use as text works for any node with exactly one text result, language-model nodes included. Vocals work in English, Spanish, German and Japanese; ElevenLabs rejects artist names and protected lyrics and the node shows its suggestion. The music is a paid node (the estimate is by the minute, see ELEVENLABS_MUSIC_USD_PER_MIN below, and unknown while the length comes through a connection); the song text costs no credits but counts against the limits of your plan and is marked as free. Text to speech is a paid node as well, with an estimate by character and model; its Speech model is chosen from a list (Eleven v4 for new nodes; a workflow saved with another model keeps it). Participants may use both and pay from the budget of their team. Guests have no team and a budget of 0: they can use the song text, but the music stops with a budget error.
  • Music video from a song — the template Music video from a song turns a song into a video that is cut to the beat. You bring the song, a photo of the main person and an idea for the video (what happens in it; the run does not start without it), and the text of the song if you have it. Four nodes do the song-specific work and can be used on their own. Analyze song (audio.beats) finds the tempo, the beats, the sections and the loudness on this machine, free of charge. Lyrics timing (ElevenLabs) (audio.lyrics_timing) finds when each word and line is sung: with the text it is aligned to the audio (forced alignment, a song plan with [Section | 20 s] lines works as the text), without it the words are recognised (Scribe). It is a paid node, priced by the hour of audio (ELEVENLABS_TIMING_USD_PER_HOUR below, a few cents for a song); the length is measured and decides what is booked, and the plan shows a price only once the length is known (a song from the music node carries its length, an uploaded file does not). A song may be 20 minutes and 150 MB at most. Plan music video (music_video.plan, a language model of your choice) cuts the song into scenes and writes an image prompt and a motion for each from your idea. The cuts are not left to the model: the app computes them from the beats and the lyric lines (Cut on: lines, beats or sections; Scenes per minute 6 to 30; Clip length 2 to 10 seconds, the longest story scene). A plan has at most 50 scenes, so a song of more than about four minutes needs longer clips. Share of singer scenes (0 to 1, default 0.3) decides how many scenes show the singer: windows of 5 to 10 seconds of sung lines, taken preferably from the chorus and the loud parts and spread over the song, each with its exact slice of the song as audio (WAV, to the sample) for the lip sync, which takes 5 seconds at least. With a share of 0, for a song without words and without lyric times there is no such scene: the lists are then empty and everything else runs as usual (without lyric times the cuts follow the beats and the plan node warns). Cut to the beat (music_video.edit, local and free) puts the clips on the beat and lays the song underneath: every scene gets exactly its frames, a story clip that is too short is slowed down to 0.8 at most and then holds its last frame, the clip of the singer is never slowed down (the lips stay in sync). Transition is cut, crossfade or flash; the video is 720p or 1080p at 24, 25 or 30 frames per second, cropped or padded to the format, with a fade-out at the end. A wrong number of clips is named before anything is cut. The cut can burn the lyrics in as captions (Captions: off, karaoke, single words or lines; Caption position): connect the lyric times of Lyrics timing to its optional input Caption times. The captions are drawn in the one encoder pass that makes the film, so a film cut in batches gets them once; the times of the song are converted to the times of the film, and a song without words stays without text. They need libass (see Captions and sound wave below). The template wires it all: an image for every scene (the photo is the reference), a video from every story image (H3 Max video on fal.ai with the model turbo, 768P, 5 seconds, 0.04 US dollars a second, so 0.20 per clip), a lip-synced clip (H3 Max lip sync, fal.ai) for every scene of the singer, and the cut. Both clip nodes need FAL_KEY. The story clips are not made with the video model of the settings because the template puts the person of your photo into every story scene and Seedance refuses images that may show a real person; in the node you can switch to the model max or to 1080P for more quality, and for scenes without people Generate video works as well. The format is 16:9; for another one change it in the plan and in both image nodes (the clips follow their first image), and keep the clip length of the plan equal to the duration of the story clips (H3 Max makes 5 seconds at least). A song of two minutes costs about 11 US dollars with the preset settings (24 story clips at 0.20 = 4.80, 28 images at about 0.13 = 3.75, four singer scenes of about 7 seconds in 768P about 2.40, the plan and the times a few cents) and roughly 5 to 20 depending on the number of scenes, the image model and the video settings; the prices of the images, clips and lip sync are known once the plan has run, so the first confirmation shows them as unknown, and nothing is charged before it. The template ships with the captions off; the switch Captions in its form turns them on (karaoke at the bottom, from the lyric times of the same song), and a server whose ffmpeg has no libass refuses a run with captions on. The cheaper variant is the template Music video from a song (moving images): the story scenes are not made into video clips by fal.ai but moved by the local Still image zoom (a slow movement that changes from scene to scene, 5 seconds, made on this machine, free), so only the images and the lip sync of the singer scenes are charged. A song of two minutes then costs about 6 US dollars with the preset settings (28 images at about 0.13 = 3.75, four singer scenes of about 7 seconds about 2.40, the plan and the times a few cents) and roughly 3 to 10, against about 11 with video clips; the rest is the same, and so are the inputs. The story scenes move in turn (zoom in, pan right, zoom out, pan left, by their position), and a switch in the form adds a depth map per image for a parallax effect (off by default, a few cents); for real movement inside the picture use the template with video clips. In the chat the Director offers the template for requests like “music video” or “video for my song” (the variant with the words “moving images” or “cheap”), asks for the song, the photo and the idea, and starts only after your click.
  • Music video in the HUD style (WP44) — a pop music video in which a photoreal AI singer sings to the camera, the cuts fall on the beat and a layer of graphics on top explains every line of the lyrics (a counter that escalates, a chart, a list, a terminal, a stopwatch, big words on the key word of the line). Two styles: HUD Blue (electric blue on the footage, heavy condensed type, an interface frame with a ticker and a recording badge, an RGB glitch on the hits) and Kuble (night ink, Kuble Blue with one amber accent, geometric type, the graphics on cards, a prism glitch and a light bloom); the field Style of the form sets the plan and the drawing together. Two nodes do the work and the templates wire them: Plan music video HUD (music_video.hud_plan) lets the code cut the film on the beats and the lyric lines (Analyze song now also finds the bars and the hardest hits; the sung stretches of 5 to 8 seconds for the lip sync begin and end in a gap between two words), and one request to a language model (GPT-6 Astra unless you choose another) writes the picture of every unit and one to three graphics for every line, each entering on its own word; the code checks the answer, asks once more with the list of problems, makes plain values for what is still missing and keeps the graphics off the face. It also writes the board, a readable plan with the estimate for the whole film. Draw music video HUD (music_video.hud_render, free) draws the graphics on the render nodes in pieces of at most 24 seconds (24 frames per second, so a render node is needed) and finishes the film with ffmpeg on this machine (glitch on the hits, film grain, BT.709, the song as AAC, H.264). Cut to the beat got an optional input Still clips and a third kind of scene, the still picture that is moved by the local parallax. The picture model is Nano Banana 2.1: a character sheet of the figure, then one picture per unit with the sheet as the reference; the sung units get a lip sync (H3 Max lip sync), the others become clips (H3 Max video, turbo, 768P): all of them by default, the portrait at the start too (Share of motion 1); with a lower share the rest are still pictures that move by parallax (a depth map from fal.ai for a cent each, then the local Still image zoom). Subtitles are off by default: the karaoke line with the sung words is drawn only when you switch Subtitles (karaoke line) on in the form (the big words are design, not subtitles); the film ends with a card of 3 seconds that says that it is made with AI and credits the figure (End card, on by default). The template Music video in the HUD style (your song) asks for the song, the idea (the run does not start without it) and, if you like, the lyrics and another figure; Music video in the HUD style (song by ElevenLabs) makes the song from the idea, 90 seconds by default (long enough for verses and choruses, short enough to keep the film near 9 US dollars); Suno song pack writes title, style, exclusions and lyrics for Suno from an idea (one answer of GPT-6 Astra, about 7 US cents), you create the song at Suno and upload it in the first template; Music video in the HUD style (song by Suno) does both in one workflow with one idea: step 1 makes the pack, you upload the song from Suno, step 2 makes the board, the sheet and the pictures, step 3 the rest. In the app view the board, the character sheet and the pictures come first for your approval, for about 1 US dollar per minute of song (about 1 when ElevenLabs makes the song too); Approve and finish then makes the lip sync, the clips, the cut and the HUD (the Director has no such step and runs everything after the cost card). Orders of magnitude from the price tables of the planner, computed again by a test: the film about 6.5 US dollars per minute of song (about 10 for 90 seconds, about 12 for 2 minutes; the clips and the lip sync are the biggest parts; about 3.5 with a share of motion of 0.3), the music of ElevenLabs about 0.20 per minute; the first confirmation shows the paid steps with their estimate, and nothing is charged before it. A song of up to about 3 minutes fits the preset 50 units; for a longer one raise the clip length in the planner. The figure Claudia is the default of the field Figure: an AI singer by anabology (claudia.gallery), fan work about Claude with no connection to Anthropic. She may be used for images, videos, songs, stories and games, commercial or not, on these terms (as read on her site on 2026-10-08; whoever builds on this reads them again): the credit «Claudia by anabology» is wanted (the end card carries it), what is generated has to be said (the end card does it, so keep it on), she is always an adult and nothing explicit is made. Another figure is any text; with the lines NAME:, FULL: (the full description for close shots), SHORT: (for wide shots) and CREDIT: (the line for the end card) it is used as written. The fonts are listed under Third-party software. The tests use doubles for every provider and the render node; a live run with the real providers has still to be made, so treat the first film as a trial and the prices as estimates.
  • Explainer video: documents, research, branding, planner (WP37a) — the foundation for explainer videos; it ends at the script (the voice, the scenes and the cut follow in WP37b below). Document input (input.document) takes PDF, TXT and MD files (50 MB each at most; a PDF must start with %PDF-, a text file must be UTF-8; the server checks the content, not only the name). Read documents (doc.read) reads them on this machine, free of charge, with Poppler (pdftotext, pdftoppm; on macOS brew install poppler, on Debian apt install poppler-utils; POPPLER_BIN_DIR names another folder): the text has a mark before every page ([p. 3] without a language, [Seite 3], [Page 3] or [Página 3] with one), the info text gives pages and characters per page and says when a document looks scanned, optionally the pages come as images (150 dpi), and the PDFs are handed on for a model that reads files. Text and Markdown files have no pages: sections of about 6000 characters act as pages. Max pages (default 150, at most 300) is a budget for all documents together. Research the topic (llm.research, GPT-6 Astra by default) searches the web through OpenRouter (plugin web, 1 to 20 results, optional lists of allowed and blocked domains) and writes notes in which every fact carries a number [n] and the list of sources has title, address and date. It needs OpenRouter (not a ChatGPT model) and costs the tokens plus about 0.7 cent a search. Branding (input.branding) hands over a brand from the branding library as a short profile (colours, fonts, tone, rules) and its logo; it is not open to participants and guests. Plan explainer video (explainer.plan) writes the script from a document, from research notes or from a topic: scenes with narration, text on screen (title of 6 words at most, 3 bullets of 7 words at most, numbers, quotes), elements with the word they appear on, a figure where one helps, and the sources of every statement (page of the document or number of the research). The length of the narration decides the length of the scene (135 words a minute in German, 150 in English and Spanish), a scene is 15 seconds at most, and what the model gets wrong in the numbers is repaired by the app, not left to the model; one repair request goes back to the model when the script is too short, too long or has empty scenes. Visual mode: motion (everything is drawn by code), mix (default: numbers, quotes, figures and flows are always motion, stills for at most Share of stills = 0.35 of the scenes, at most 2 clips and only with fal.ai) and ai_video (every scene a clip); a mode the account cannot do (no image model, no fal.ai) is lowered, and the script says so. Check statements (default on) runs a second pass that compares each statement with the document or the research; it can only shorten or remove. The script counts as verified only when the check answered for every scene (and for as long as nobody edits its text); when it fails, skips scenes or would empty the script, the script stays as planned, is marked not verified and the log says why. The model is GPT-6 Astra (openai/gpt-6-astra, 10 and 50 US dollars per million tokens) unless you choose another; if the account may not use it, the person's default model is used and the log says so. A PDF goes to the model both as text with page marks and, where the model reads files, as the file itself (at most 22 MB of PDF in all, because files travel as base64, and 300 pages each; a ChatGPT model gets the text only; without an OpenRouter key the default model of the app is used instead of GPT-6 Astra). The result is the script as JSON (script) and lists by scene: narration and briefs (what is on screen) have one entry for every scene (the closing sources card has an empty narration), image_prompts and clip_prompts only the scenes of that kind, shots says where each scene sits in them, plus presenter and the sources as text. Two phases: put an edited script into the field Edited script and run the node again; it is only checked and passed on, with no model call and no cost. The templates Explainer video: script from a PDF (needs OpenRouter and Poppler; about 1.25 to 2.25 US dollars for a 20-page PDF and 2 minutes, reading is free) and Explainer video: script for a topic (needs OpenRouter; about 0.75 to 1.25 US dollars for 2 minutes with research) wire it all. These are orders of magnitude from the research on explainer videos, not prices: the plan shows no price until the document has been read, and the run asks first.
  • Beat effects for the music video in the HUD style (WP45) — Draw music video HUD has the field Effects (Strong by default, Subtle or Off; also in the form of the three HUD templates). It puts motion, distortion, light and colour on the music: a zoom punch on the downbeats, a shake on strong hits, a lens bulge on the kick, a wave, a zoom or sideways blur over the cuts, chromatic edges, a pulse of the exposure on the beat, a brighter and more colourful chorus, a flash at the start of a section and on the strongest hits, a strobe and a negative flash in the drop, and more glitch over the whole picture in five kinds (colour channels, displaced blocks, a torn line, a VHS track, digital noise; Kuble has its prism, its shards and its colours). Code plans it from the song, without a language model: dense in the chorus and the drop, calm in the verse, quiet in a break (only its colour is calmer); the strength of a hit counts against the other hits of the same song, so a quiet song gets its effects as well. The singing face stays clear (no bulge or wave in a lip-sync cut, no blur while a word is sung), the HUD stays sharp (the effects work on the footage under it; a glitch over everything lasts at most 4 frames), the end card with the AI label stays free, and there are never more than 3 flashes in a second and no red flash. Glitch still sets how hard the glitch hits; Off draws the film as before. Changing the field draws only the film again, no paid step. Free like the node; measured locally, a piece of the film renders about as fast as before and the finish takes up to about a fifth longer.
  • Brief from the song and lines of a choir (WP48) — in Music video in the HUD style (your song) the field Idea for the video may stay empty: Plan music video HUD then asks the same language model for a brief first (200 to 300 words from the lyrics with their parts and times, the energy of the parts, the tempo, the length and the figure: theme, arc per part, the machine in the HUD, look, graphics for the lines, end card, what must not appear; no brands or real people, the figure an adult, metaphors such as needle or blood never literal) and plans the film with it. The brief is the new output Brief and the result Brief of step 1, before the board: copy it, change it and put it into the field to plan with your own. A second run with the same song takes it from the cache; with an idea in the field the run is the one of before, byte for byte. Its language is a setting of the node (Language of the brief: English, German or Spanish; the German and the Spanish form of the template set theirs). Lines the singer does not sing are marked in the lyrics: a section [Choir], [Chor], [Coros], [Backing Vocals] or [Background Vocals], "Choir:" at the start of a line, or a whole line in parentheses with words such as (Claudia, Claudia); [Chorus], [Refrain] and [Hook] stay hers. These lines keep their times and graphics but get no lip sync, and no sung stretch lies before her first line. Without marks Lyrics timing can let Gemini hear the song (Detect choir, on in the three HUD templates; needs GEMINI_API_KEY, about 2 US cents for three minutes); the board lists the lines it heard, and marks always win. The templates with the song by ElevenLabs or Suno still need the idea.
  • Effects 2: variety and visible glitches (WP49) — the beat effects of Draw music video HUD give every part of the song a palette of its own (3 to 5 kinds of effect from a seed of the song: the breaks, the verses, the choruses, the bridge and the drop look different, and a second chorus varies the first instead of copying it), and HyperFrames draws every effect in the page of the chunk; the finish with ffmpeg only adds grain, vignette and colour. New kinds: an RGB split (a short hit, or wandering over whole bars of the drop), fine and coarse noise and snow that change with every frame, scanlines with displaced lines, a VHS band, the picture rolling, slices, a block glitch, pixels, a smear in the manner of a datamosh, freeze and stutter, an echo, and a mirror or a kaleidoscope as a rare accent; zoom punch, shake, turn, bulge, wave, blur, light and colour stay. No kind comes twice in a row, every kind has a cap per minute, and zoom punch and exposure pulse come only on accents (the downbeat of a loud bar, a strong hit, the start of a part) instead of on almost every beat. A glitch on the strongest hits lasts 6 to 12 frames (before: 2 to 4 frames, about one every 5 s), a short hit 2 to 4. A disturbance of the signal (RGB split, slices, blocks, VHS, roll, snow) takes the whole picture with the HUD for at most 12 frames, never in the first half second of a new line; everything else works on the footage under the HUD. In the style Kuble the glitch keeps its colours: the RGB split is a prism of amber and blue, the seams and the negative blocks are Kuble Blue. While a word is sung in a lip-sync cut, only a colour split of at most 3 px, light noise, zoom and light touch the face. New level Wild (more accents, stronger, one more kind per part); Subtle, Strong (default) and Off (the film of before) stay, and Glitch sets the strength of the glitch kinds (0: none). The shaders of HyperFrames were tried first: frame exact, but about 50 times slower on a render node without a graphics card, so the effects are SVG filters and CSS. Measured locally: with the effects a chunk renders up to 11 % longer than without, without a graphics card about 1.6 times as long (WP45 about 1.5 times); the finish with ffmpeg is 2 to 3 % shorter than with WP45, and a page stays under 700 KB.
  • Claudia as on claudia.gallery (WP50) — the character sheet of the three HUD templates is made with three official images of Claudia as references (two photographs and face sheet 3 from claudia.gallery, Claudia by anabology, in lib/music-video-hud/figures/claudia/ with their source, the date and the terms of the site). Before, Nano Banana 2.1 made the sheet from the canon text alone and drew an older, harder woman; the text was written for Midjourney, which made her official images. The images go only with the figure Claudia (the default); another text in the field Figure gets none. Generate image takes optional reference images for this (input Images), Plan music video HUD the switch Official images of the figure (on in the templates), the input Picture of the figure for an own figure and the output Figure sheet references. The sheet costs about 0.0365 instead of 0.034 US dollars. Saved workflows keep their results: nothing paid runs again after the update. Claudia is fan work about Claude, not affiliated with Anthropic.
  • Tags that point at the right place (WP51) — a tag of the HUD (a short label with a leader line) now points at the place on the figure it names and follows it while the face moves. Plan music video HUD gives every tag a target (face, eyes, mouth, hair, hands, outfit, object or none); without one, as in plans made before, the words of the tag decide ("LIP GLOSS ON" is the mouth, "AUGEN" the eyes, "FLEQUILLO" the hair; the face where no word names a place). Draw music video HUD finds the face in the base cut under the tags before the render (a face finder in plain JavaScript on every 4th frame: free, no new dependency, the same on every machine; about 6 s for 100 frames on a laptop) and draws the brackets round the eyes, the mouth, the face, the hair or the clothes where they are, through the crop of the cut and the beat effects, in HUD Blue and Kuble. Where no face is found (turned away, too small, not there) and for hands, objects and none, the tag stands without its line; a line to the hair or the clothes keeps off the eyes and the mouth. Measured on a film of 3:28 against MediaPipe: the eyes 6 px off (median; 95 % within 40 px at 1080p), the mouth 25 px (73 % within 40 px), a face found in 70 % of the frames in which MediaPipe sees one; the fixed point of before was 160 px off the eyes and 300 px off the mouth. A film with tags is drawn once more when its workflow runs (free); a film without tags keeps its result, and pages without a tag stay byte for byte as they were.
  • B-roll that fits the line, more singing, a planner that really plans (WP52a) — Plan music video HUD shows the image of the sung line without the figure in about a third of the units that are not sung (B-roll: "let the flood begin" becomes a flood of light through a street, a hard beat an explosion of glass), big and cinematic with hard motion, mostly on drops, hits and refrains, never more than three in a row; the hook and every sung unit stay with the figure, the figure is on screen for at least 55 % of the film. Safety rules for these pictures: no second figure, no real people, brands or logos, crowds only from far away, nobody hurt, metaphors such as needle or blood never literal. Share of B-roll (0 to 0.6, 0.35) sets the share. In a song sung without pauses the lip-sync windows may now start and end in a gap of 0.12 s between two words and last up to 10 s, so the figure sings what Lip sync (s per minute) asks for (a dense song of 3:28: 70 s instead of 40 s). The planner request has room for its thinking (64,000 tokens, effort medium) and keeps the complete parts of an answer that is cut off (the second request asks only for the rest); when the model still delivers too little, the node stops with a message naming the attempts, the tokens and the cost instead of quietly making a plain plan (Allow a plain plan brings that back), and the board names model, tries and plain parts. A film costs about 6.5 US dollars per minute of song (5.5 before), the planner about 0.45 per minute. Existing workflows keep their cache keys.
  • Event video (aftermovie) (WP53) — the template Event video (aftermovie) turns the clips and photos of an event into a film of 30, 60 or 90 seconds cut to music, in 16:9, 9:16 or 1:1: a title, lower thirds over the soundbites with subtitles, a few intertitles and an end card with the logo of the branding. Videos and photos are both optional (at least 5 together; an event of photos only works, HEIC/HEIF is converted once to an upright JPEG with capture time retained); the description of the event is the only source of names, numbers and places on the screen, besides a transcript and a text in a picture. Six nodes do the work: Analyze event media (scenes, quality, faces, what can be seen with Claude Sonnet 5.5, what is said with ElevenLabs only when someone speaks; photos made small to 1600 px first), Event video style (kind of event, mood, length, format, language; the format goes to the cut and the render alone, so another format costs nothing), Event video soundtrack (instrumental music by ElevenLabs, or your own), Plan event video (the code lays six acts on the downbeats, a language model chooses the moments and writes the texts, the code checks them; board and contact sheet), Cut event video (crop around the people, or the whole picture over a blurred copy for an upright photo in 16:9; zoom, pan, parallax or AI clips for the photos) and Draw event video (the graphics on the render nodes, ducking, -16 LUFS, SRT). The app shows the music, the board and the chosen shots for approval first (about 0.9 US dollars for 60 seconds); the film costs about 0.8, 1.0 or 1.2 US dollars for 30, 60 or 90 seconds with the photos moved by code, AI clips add about 0.4, 0.8 or 1.2, the voice-over about 0.06. The fonts DM Sans, Space Grotesk and Playfair Display (SIL OFL) are in lib/fonts. Show only people who agreed to it, above all with the AI animation. Existing workflows keep their analysis cache keys; a missing text classification keeps the earlier behaviour. Readable text on slides, screens and signs stays out of automatic AI animation, with explicit choices still available. Intertitles avoid text shots or use a solid title surface; portrait end cards use larger text and logos within the safe area.
  • Explainer video: the language follows the input (WP38d) — Language of Research a topic and Plan explainer video has the new choice Same as input (auto); every new node and the five explainer templates start with it, so a German topic gives a German video without touching two nodes. The node decides without a model call (lib/language-detect.js): it counts stop words (words that belong to only one of German, English and Spanish) and special characters (umlauts, the sharp s, ñ, ¿, ¡, accented vowels). A source is clear when the best language has at least 2 points and twice as many as the next. The first clear source decides, in this order: what you write yourself (topic, brief; for the research the topic and the focus), then the text input, the research notes, the list of sources and the titles of the documents; so a German brief beats an English PDF. A short topic such as “Git vs GitHub” has no stop words and decides nothing, the next source is asked; only when none is clear, a source with a single kind of hint decides, and with no hint at all the language is English. The same input always gives the same language, so the cache stays valid. The language is used for the prompt, the words per minute (German 135, English and Spanish 150) and the target of words, the word for “page” and the sources line, and the script carries the real code in language (de, en or es, never auto). The log names it: “Language: German (from the topic)”. An edited script (field Script) keeps its own language. Saved workflows do not change: the default of the parameter stays en, a node without a value keeps it and keeps its cache key; only new nodes (palette, assistant) start with auto (initial of the parameter). The chat tools and the MCP tools take auto like any other choice. For another language choose German, English or Spanish in the two nodes or in the app view. Not changed: the language of Describe image, Describe video and Read documents.
  • Explainer video: voice, scenes, cut, three workflows (WP37b) — the film behind the script of the explainer video. Explainer voice (explainer.voice, ElevenLabs with-timestamps, paid by the characters, 0.08 US dollars per 1000 for Eleven v4) speaks the narration of every scene and returns the audio, the word times (built from the times of the characters, in the format of Word times) and the length. The narration of the scenes before and after goes along as previous_text and next_text (switch Use text around the scene, on by default); if the API refuses the fields for a model, the call is made once more without them and the log says so. A scene without narration (the sources card, one line for each document with all its pages, e.g. Report (p. 1, 2, 3), and one line for each source of the research) makes no call, costs nothing and gives 4 seconds of silence. Draw explainer scene (explainer.scene, GPT-6 Astra by default, about 0.25 US dollars a scene) turns the brief of a scene, the word times and the brand into a motion-graphics film on the render node: the length is the voice plus 0.4 seconds in whole frames, every element appears at the start of its anchor word minus 0.15 seconds (but not before 0.2 seconds; a title that opens the scene always comes at 0.2 seconds, whatever its word, so that no scene starts empty), the model writes the HTML, the app checks it (structure, forbidden calls, no web address except the files of the GSAP package, no navigation: no location, link, form or markup built in code) and puts a content security policy first in its <head> (scripts only from the GSAP package, no form, no <base>, no requests), renders it and looks at two frames (at half of the scene or at the last cue, and at the end). Only blockers lead to a new try (Retries, default 2: the same conversation goes on, the model sees its answer and the problem); a scene that still fails is drawn by the fixed fallback (title, at most three bullets or numbers, in the colours of the brand, with the still as background) unless Fallback is off. Stills, figures and logo are matched by the id of the scene (Scene s3 ...), never by position: a figure is cut out of the page image with a margin of 2 %, the source line is not written by the model: the app builds it from the source references and the titles of the documents (the pages of one document together: Report, p. 1, 2) and puts it small and right-aligned at the very bottom of the frame, in the code of the model's scene and of the fallback alike, while the model keeps the lower 20 % of the frame (16 % in portrait) free of text for the captions that the cut burns in, so the two never cover each other; brand fonts are embedded (two files of 400 KB at most each, the HTML stays under 2 MB). A scene that is planned as a clip gets a plain stand-in that the cut replaces by the clip. The output is silent and as long as the scene to the frame. Cut explainer video (explainer.edit, local ffmpeg, free) puts the scenes in a row, each voice on the exact frame where its scene starts (cut or crossfade of a quarter second, which makes the film shorter by the overlap), mixes music under it (Music level quiet, medium or loud; Ducking lowers it under the voice), brings the whole sound to -16 LUFS (one gain from a measurement of the loudness, then a limiter at about -1.5 dBFS so that nothing clips; if the measurement fails, the sound stays as it is and the log says so), optionally puts intro and outro (with their own sound) before and after, uses the clips of the shot list for scenes that are clips (trimmed, slowed down or held) and burns in captions (needs libass in ffmpeg). It also gives the subtitles as an SRT file and the captions as word times of the film. The log names the real length and warns when it differs from the target by more than 15 %. The templates Explainer video from a PDF (page images of the PDF are read so that figures can be cut out; about 5.00 to 6.50 US dollars for 20 pages and 2 minutes, music included), Explainer video for a topic (research with sources; about 4.5 to 5.5 US dollars for 2 minutes) and Explainer video with a presenter (portrait) (a photo speaks the intro and the outro with H3 Max lip sync on fal.ai, so it needs fal.ai and the consent of the person; about 6 to 7 US dollars) wire it all (they need OpenRouter, ElevenLabs, a render node and ffmpeg). Each of them has a Background music node (ElevenLabs, instrumental, 120 seconds, about 0.40 US dollars, a paid ElevenLabs plan is needed; delete the node if you have none) that feeds the cut, where it repeats if the film is longer. The render node renders one scene at a time: every scene waits for the ones sent before it (a minute more per job on top of 8 minutes), and a voice that is longer than its scene is named in the log of the cut. These are estimates; the plan shows the real price of the voices and the scenes once the script exists, and the run asks first. Always run only the planner first (Plan explainer video), read the script, edit it in the field Edited script and then run everything: the edited script is only checked, with no model call. The planner switch Allow AI video clips is off in the three workflows (they have no clip chain). The logo of a branding is not wired in the templates (without a branding there is none and the plan would refuse the run): choose a branding and connect its Logo output to Draw explainer scene to show it.
  • Explainer video: speaker voice and trial listening (WP38f) — a branding can have a speaker voice (Menu → Brandings, admins: a list of the ElevenLabs voices with a play button, or No voice). Branding (input.branding) passes its ID on at the new output Voice; Explainer voice and Text to speech take it at their new input Voice instead of the voice chosen in the node, and fall back to that choice when the branding has none. The three explainer templates are wired that way. Wherever you choose a voice (node card, inspector, app view, branding window) a ▶ button plays a sample first: only one plays at a time; the list shows what each voice is like. The sample is the free one of ElevenLabs where there is one (kept by the app, no address of yours is ever fetched); a voice without one (often a cloned voice) gets a short sentence spoken once in the language of the page with the chosen model, about one cent through the usual budget, stored and paid only once. Participants and guests still hear and use the library voices only. Your own brand voice is created at ElevenLabs (a clone only with the consent of the person whose voice it is); after that it appears in the list. Details: docs/node-view/SPEC.md §11.1, IMPLEMENTATION-NOTES.md.
  • Captions and sound wave — Burn in captions (video.captions, local and free) writes the sung words into a video. It takes the video and the lyric times of Lyrics timing (ElevenLabs) (the JSON text with lines and words). Style: Karaoke (the line stands and each word changes colour while it is sung), Single words or Lyric lines; Position: bottom, middle or top. Text size is a share of the picture height (0 = automatic: 6 %, a little less in portrait); the colour, the highlight colour, the outline, the font (DejaVu, Liberation or Noto) and upper case can be set, and Offset shifts all times in seconds. Long lines are wrapped to the picture. A line shows from 0.4 s before its first word to 0.4 s after its last one; times without words or lines leave the video as it is, and a karaoke line whose times have no words gets them spread evenly. The sound is copied. The text is drawn by libass (the filter ass of ffmpeg) from an ASS script the app writes itself (lib/captions-ass.js, a pure module); an ffmpeg without libass (ffmpeg -hide_banner -filters has no ass) shows the node as unavailable and the run is refused before anything starts, and the picture needs a font on the system (for example the package fonts-dejavu-core). Sound wave (video.soundwave, local and free) lays a wave over a video that follows the sound: the sound of the video, or of an audio on the optional second input (if the video has no sound of its own, that audio also becomes the sound of the result). Mode: waveform (line, outline, filled band, dots) or bars (the spectrum); Position, Height (share of the picture height, as wide as the picture), Colour and Opacity. The picture is encoded again, the sound is copied where it is AAC or MP3. Both nodes keep the size and the length of the video. Chinese and Japanese are wrapped between characters under the rules of kinsoku (no row starts with a closing mark or ends with an opening bracket) and a wide character counts two columns; Hebrew and Arabic stay in logical order and libass lays them out right to left, but its karaoke fill would run from the wrong side of each word, so the node uses Single words there and says so in its log. Where fc-list :lang=ja|zh|ko|ar|he finds no font, the log warns and names fonts-noto-cjk or fonts-noto-core (the captions are made anyway).
  • Still image zoom: movement and parallax — Still image zoom (local) (image.to_video) has the motions Zoom in and Zoom out, Pan left / right / up / down (constant zoom 1.15), Alternate (in and out by the position of the item in a list) and Varied (in, pan right, out, pan left; the template Music video from a song (moving images) uses it). The parallax motions (Parallax left / right / in) take the optional input Depth map (grayscale, bright = near) from Depth map (fal) (fal.depth_map, Depth Anything V2, the estimate of 0.01 US dollars per image is booked; the list price in the price API of fal is 0.00125 US dollars per compute second, and fal does not tell the app the compute time) and Parallax strength (0–100 %, 30): geq turns the depth into a displacement map and displace moves the picture, all locally. Without a depth map the matching pan or zoom is made and the log says so. The template has a switch for the depth maps (off by default).
  • Delete results (WP38e) — in the history of a node (inspector) every result has a Delete button, Delete all results of this node sits below the list and in the node's right-click menu. The app asks first: the result disappears from the node and this cannot be undone, the file is deleted when nothing else needs it, and the next run calculates the node again and costs again if it is paid (the nodes behind it turn out of date). Over the API: DELETE /api/workflows/:id/results/:nodeId/entries/:entryId and DELETE /api/workflows/:id/results/:nodeId (same permission as choosing a variant; 409 RUN_ACTIVE while a run is active; the other tabs are told by the event results_changed). If the deleted result was selected, the newest one left is. A deleted result never serves as a cache hit again, not for the whole node and not per item; the items a failed run kept that are copies of it go too. A file is deleted only if it lies in the workflow's own session and nothing else needs it: no other entry of any node (a Result node passes the same file on), no parameter of an input node, no note or app that names it, no open provider job with published references, no stored chat that points at it; otherwise it stays and only the entry goes (the answer says how many files went and how many stay). Single variants and single scenes are not deleted; a single scene can be made again in the scene table. The limit of 30 results per node still drops the oldest entries from the list without deleting their files. Details: docs/node-view/IMPLEMENTATION-NOTES.md, «Deleting results (WP38e)».
  • A changed branding is noticed, and Run all again (WP38g) — after a tone, a colour, a font, the guidelines, the logo or the speaker voice of a branding was changed, Run all brought nothing new: every node was “Cached”. The node Branding now has a cache stamp, a hash of everything it gives out (profile text, the bytes of the logo, the voice), so it shows as out of date with the nodes after it and runs again; after that only what depends on the changed part is calculated again: the profile text the planner and everything after it, the logo the scenes, the voice the speaker and the cut. The logo of the same bytes keeps its asset (same session and asset ID) however often the node runs, so a scene is not paid for twice for the same logo; an entry from before the change is taken when it says the same as the branding now, so nothing paid runs for a branding nobody changed. The hook is general (def.cacheStamp(params, ctx), async, cheap, never a paid call; its result is part of the cache key and of the key of every item; plan, price, the out of date state and the budget reservation use it; without a hook every key is byte for byte as before, and cacheStampAdopts lets an old entry be taken once so that a deploy does not make paid work run again). It is also used by explainer.scene (the font files of the brand), by explainer.plan (the kinds of pictures the app can make) and by the nodes that use the default model of the settings (defaultBrain, imageModel, videoModel) while their own model is empty. After a deploy the first change of one item of a list calculates only that item, and the plan shows only what really changes after a changed branding (the logo of the same bytes, the voice and the profile text are known beforehand). The page asks the plan again when the window comes back to the foreground, so a branding changed in the chat shows up. Run all again (menu “···” of the workflow, desktop and phone, and a button in the inspector) runs every node without the cache, like Run again in the app view: it asks first and shows the estimated total cost of the run (nodes without a known price are counted), cancelling changes nothing, and the budget checks are the usual ones. Details: docs/node-view/SPEC.md §9.3 and docs/node-view/IMPLEMENTATION-NOTES.md, “A changed branding is noticed” (WP38g).
  • Explainer video: style Typography (WP40) — kinetic typography in the Swiss manner: the spoken words are the picture. Plan explainer video (explainer.plan) has a fourth Visual mode, typography («Typografie» / «Tipografía»), in the same field as motion, mix and ai_video (in the app view through the templates Typography video for a topic and Typography video from your own text, where the mode is fixed; the older explainer templates do not show the field there). The planner then plans only motion scenes: image_prompts and clip_prompts stay empty, so Create image and the clip nodes have nothing to do and cost nothing; what a model still writes as a still or a clip is made a motion scene (the script says so in downgrades, reason visual_mode_typography), and bullets are dropped. The narration is written in short, rhythmic sentences. For every scene (not the sources card) the script has typography: 1 to 4 keywords of the narration (the app checks that they are spoken and picks a word or a number itself if not), a layout (stack, column, blocks, compare, curve, number; the app corrects a layout that is unknown, repeated in a row or number without a number in the scene, so the layouts alternate) and a camera hint that continues the movement of the scene before (empty in the first scene). The brief of the scene carries them (Style: typography, Layout:, Stress (huge, accent colour):, Camera:) and shots lists them per scene. Draw explainer scene reads the style from the brief and writes the scene differently: the request lists every word of the voice with the second at which the voice starts it (from Word times), and the model must show each word at that moment (a data-at attribute and one loop in the script). The limits are those of the style: every spoken word may appear (at most 40 words in view at the same time, at most 12 words that are not spoken), spoken words are at least 54 px high, small captions at least 28 px in a legible colour (instead of 25 words and no whole sentence); no band is kept free for captions, because this style has none: the type uses the whole frame down to the small line of sources that the app sets itself. Cropping at the frame edge, overlapping layers, a tilted or turned column, 3D blocks, a camera that zooms and turns, vignette and grain are wanted. The look at the frames (after the middle word and at the end, with what the voice has said by then) has its own rubric: a defect is a key word or spoken word that cannot be read, type that collides by accident, an empty or broken frame, the wrong order and a wrong word; the line of sources is still never a blocker. The other three modes are unchanged to the byte (a golden test compares every prompt, brief, list and fixed scene with the code before WP40). A typography scene costs about 0.28 US dollars with the look at the frames (0.25 without), measured in a live test on 2026-10-04 with 3 scenes (the first guess of 0.13 was too low: the model thinks along and writes more code), shown in the plan once the script exists; a film of 90 seconds costs about 3.80 to 4 US dollars with research and about 3.70 with your own text, since nothing is paid for pictures or clips (an estimate from one test run: about 11 scenes at 0.28, the voice about 0.10, the music 0.30, the script about 0.20 for your own text and 0.30 to 0.50 with research). Default font: without a branding, or for a font of the branding without a file, the scenes use Inter Tight (SIL Open Font License 1.1, a variable font with weights 100 to 900, a latin subset of 44 916 bytes in lib/fonts/inter-tight/, the licence next to it; see Third-party software). It is embedded in the page like the fonts of a branding (within the limits of two files of 400 KB), declared with the range 100 900 so that the browser uses the real bold cuts and does not smear a regular one; no system font is ever named, because the render nodes run on different systems. The colours without a branding are a warm paper ground with a vignette, ink, one accent and one second colour for comparisons; with a branding its colours and fonts apply. The cache of a scene notices a change of the font file. Own text: Plan explainer video has the new input Own text (own_text, text): the text is the narration, word for word. The planner (a model call, measured 0.06 US dollars for 75 words, so about 0.20 for 90 seconds of text) only cuts it into scenes and plans titles, keywords, layouts and cameras; the app puts the narration back from the original text (every word, the German sharp s and quotation marks as written) and, where the model changed, dropped, added or reordered a word, asks once more and then cuts the text itself at sentence ends (about 10 seconds a scene) and says so in the log. Such a script is marked verbatim, is not checked against sources (there are none) and may have fewer than three scenes; its length is that of the text (up to 8 minutes of speech: a longer text stops the run with EXPLAINER_TEXT_TOO_LONG), and the language follows the text. It works in every mode, not only in typography. The templates Typography video for a topic (research with sources, 90 seconds; about 3.80 to 4 US dollars) and Typography video from your own text (a text field instead of the topic, no research; about 3.70 US dollars) wire it all with Branding (they need OpenRouter, ElevenLabs, a render node and ffmpeg, not fal.ai). Always run only the planner first, read the script and its layouts, edit it in the field Edited script and then run everything. After the live test (part C): an empty answer of the model is still billed by the provider, so it is now booked like any other call (journal, budget, the flat amount for participants without a reported cost; for every node that asks a model, not only the scenes), and the error names finish_reason and the tokens ("The model returned an empty answer (finish_reason length, 9000 output tokens, 8950 of them reasoning)"). The scene node counts the cost of a failed try in its own cost, logs the output and thinking tokens of every good try, and when a try ended empty because the model only thought until the token limit, the next try thinks less (OpenRouter reasoning.effort "low"; more tokens did not help in the test, Claude Opus 5.5 thought for 16000 tokens as well); if that try ends empty at the limit too, no further try is made and the fixed scene stands in. If no try gives a scene, the fixed scene of this style is no longer a static title but a plain kinetic typography: the spoken words appear at the second the voice says them, line by line, key words large and in the accent colour, over the whole frame down to the line of sources. No subtitles: in this style the words are the picture, so a film in it never gets burnt-in subtitles (they would repeat the text and disturb the layout). The two typography templates have Cut explainer video on captions "off" and show no field Captions and no result Subtitles; the node itself burns nothing in for a typography film even if Captions says "lines" or "words" (for instance in another explainer template), and says so in the log. The SRT file (output subtitles) and the word times (output captions) are still made at Cut explainer video and can be connected; the other modes (motion, mix, ai_video) are as before. Details: docs/node-view/SPEC.md §5.12 and §15, IMPLEMENTATION-NOTES.md, «Style Typography (WP40)».

Current planner default: GPT-6 Astra. Typography scenes are estimated at 0.70 USD with the frame check and 0.625 USD without it; the measurements above used Opus 5.5.

  • Cache per item and scene table — a node that runs once per item of a list keeps a key for every item, so a second run only makes the items whose inputs or settings changed (the log says Reused 4 of 6 unchanged items) and the plan prices only those. A node can narrow what an item reads (itemKeyInputs): a scene of the explainer video counts its own entry of the shot list (plus the fields that hold for all scenes), its still, the page images of its figure, the brand and the logo, so one changed shot makes one scene run again and not all; the voice leaves out the text around the scene where it does not read it. The run mode items ({ mode: 'items', nodeId, items: [0, 2] }) makes chosen items of a list result again and keeps the others. The inspector shows this as a scene table for a node with a list result: a row per item (number, preview, main text, length), Make again per row with the price of one item and a confirmation for paid nodes, and Make selected again for ticked rows; it works on desktop and phone. It needs a result that was made item by item (an older one works after its next run); scenes are not swapped or moved.
  • Prompt node — Prompt (first entry of the Inputs category) is a dedicated node with a large text field for writing a prompt on the canvas; wire it into one or more generation nodes so the graph shows which prompt goes where. The embedded prompt fields still work: the small button in their corner (and the one next to the field in the inspector) moves the text into a new Prompt node left of the node and connects it (one undo step). Dragging a connection from a node and dropping it on the empty canvas opens a quick pick at that spot with only the nodes that fit (Prompt first for text inputs); Esc or a click beside it cancels.
  • Port help — hover a port (dot or label, inputs and outputs) for about a third of a second to see what it expects or delivers: name, type, required or optional, a short description, how many connections it takes and in which order they count. Esc, a click, a drag, scrolling or zooming hides it.
  • Several references — inputs with a double ring (for example Images of the H3 Max reference node, References of Seedance) take several connections, shown as n/max next to the label. Either wire several image nodes to the same input, or wire one Media list to it: a list on such an input delivers all its files at once, while a list on a single input runs the node once per item. Draw further connections from the output of each source to the same input (dragging from the input itself detaches its last connection). In prompts, the references count in the order of the connections (H3 Max reference video: "Image 1", "Image 2", "Video 1" …); a media list counts with all of its files and each file counts toward the maximum, so the numbers after a list shift and the hover help then no longer shows positions. Every connection shows its number on its own curve, and under an input with two or more connections the card shows a numbered row of the sources (previews for images) in exactly this order: drag an entry, use the arrow keys or right-click it to change the order, so that "Image 1" in the prompt means the picture you expect; the node then runs again. Dragging from a multi-input onto the empty canvas offers the Media list and the matching single input node first.
  • Motion graphics from HTML — the Motion graphics from HTML (render node) node renders finished HTML/GSAP code, not an instruction. Code comes from a Motion HTML writer node in front of it (describe the animation there; attach the same files to both nodes and refer to them as {{asset:1}}, {{asset:2}} …) or is pasted into the field. To join videos use Concatenate videos instead. If the field holds plain text ("make it one video") or HTML without the data-width / data-height of the chosen format, the node is marked incomplete before the run and says what to change; for plain text the button Convert to HTML with AI adds a Prompt node and a Motion HTML writer to the left (your text becomes the prompt, the format and the files are copied, the field is cleared, one undo step). Nothing runs until you start it; the writer is a paid node and asks for confirmation as usual.
  • Assets — every image/video/audio input takes an upload, a file from one of your chats, or an asset already produced in this workflow.
  • Export and import — the workflow menu offers Export (JSON) (graph and app only) and Export with files (ZIP), which adds the input images, videos and audio of the nodes (generated results stay out; the results ZIP covers those). Before the ZIP download the size is shown; above 500 MB or 200 files a hint says that an import with the default limits fails (the export itself still works). Import takes .json and .zip, recognised by extension and content. A ZIP shows an upload card with progress and is checked completely before anything is created (500 MB, 200 files, only images, video and audio, no unsafe paths); on any error nothing is left behind. After every import a note says how many files came along, for example “3 of 4 files imported, 1 missing”. A JSON import on the same server takes back the files you are allowed to see (your own or shared workflows); everything else stays marked missing and is uploaded again in the node.
  • Templates — New from template offers ready flows, for example: Product hero (4 variants), Still to vertical ad with voice-over, Consistent character series (batch), Continuous shots via last frame, Motion title over footage, Masked edit, Clip in three national languages (Higgsfield dubbing with lip sync), Talking portrait (ElevenLabs voice plus H3 Max lip sync on fal.ai), Photo to 3D model (background removal, then Tripo on fal.ai), Video with music (music as long as the video, mixed quietly under the original sound), Song from an idea (song text, then music) and Music video from a song (beats and word times, a plan of scenes, an image and a clip per scene, lip sync for the singer, cut to the beat), Music video in the HUD style with your song, with a song by ElevenLabs or with a song by Suno (a photoreal AI singer, lip sync, hard cuts on the beat and a graphics layer that explains every line, in the style HUD Blue or Kuble, subtitles off; about 6.5 US dollars per minute of song) and Suno song pack (title, style, exclusions and lyrics to copy into Suno; about 3 US cents), Explainer video from a PDF, Explainer video for a topic and Explainer video with a presenter (voice, scenes drawn as motion graphics, cut with captions; about 2.50 to 3.50, 2 to 3 and 4 to 5 US dollars for 2 minutes, music included), Motion video with storyboard (one cheap storyboard picture per scene with Nano Banana 2.1 to approve first, then the scenes drawn after the pictures and checked on a contact sheet; about 1.50 to 2.50 US dollars for 45 seconds), Explainer video: script from a PDF (about 1.25 to 2.25 US dollars for 20 pages) and Explainer video: script for a topic (about 0.75 to 1.25 US dollars), the two script templates give the script with sources only. Templates whose provider is not configured are shown as unavailable; their texts come in English, German and Spanish.
  • Design App — switch on Design App in the top bar, expose parameters of your nodes (the small toggle next to a field) and pick the result nodes. #app=<workflow id> then shows a clean form with a Run button, live status, a result gallery, downloads and Send to chat. The form values only apply to that run; the saved workflow is not changed. A result node can be marked Show first for approval (step 1): the app then runs in two steps, the first button makes only the marked results (with everything they need) and shows them, Approve and finish makes the rest, and what step 1 made is not made or paid again. Approval in step (2 to 5) gives a marked result a later step: results with the same number come together, every further step starts with Approve and go on with step n, and a short hint of the template can say what to do in between (the music video with the song by Suno: make the song at Suno and upload it). Without a mark the app runs in one step.
  • Chat bridge — Send to chat first shows what will be sent (media with a small preview, texts with their first characters; extra texts of a media result start unchecked), then copies the result into a chat of your choice. There it appears as a card with the workflow, the node, a player or thumbnail, collapsible texts and a link back to the workflow. The Director sees the files with their asset IDs and can continue from there; nothing starts automatically.
  • Projects — assign a workflow to a project (the same folders your chats use); the project name shows in the workflow list. Who can see a workflow depends on the user management (open for everybody when it is off); deleting one also deletes its media (the app asks for confirmation first).
  • Compare and approve variants — when a node has two or more variants, the inspector shows them side by side with their preview, the recorded prompt, model, parameters, time and cost of the run (what was not recorded says so instead of guessing). Approve marks one variant per node, separately from selecting it. Edits, runs, undo/redo or another selection that would replace an approved variant ask first, and a run that starts on outdated results is refused with 409 REVIEW_CONFLICT (the editor reloads and asks again).
  • Spending limit and change preview — before a paid run the dialog lists every step with the reason it runs again (changed setting, changed input upstream, not in the cache, forced) or that it comes from the cache, and the cost of the fresh steps only. The run gets a binding limit (suggested: the estimate plus 25 %, at least USD 0.10 more, rounded up to 0.05) and by default requires known prices; a step that would go above it pauses the run (Waiting for approval, with the amount) until you continue with a new limit or cancel. The run summary separates booked, reserved and pending accounting (provider jobs that are billed after a cancel). The API takes maxUsd and requireKnownPrices (MCP max_usd); runs without a limit work as before.
  • Cancel stops the renders — cancelling a run also cancels its waiting and running motion-graphics renders on every computer: render agents lose the job, updated push render nodes stop the renderer (POST /jobs/:id/cancel); older render nodes finish, but their result is discarded.
  • Event video: replan, correct texts, resume — in the app's approval of an event video, Replan only repeats just the planner (analysis and music come from the cache, the cost dialog lists only the planner). Title, intertitles, lower thirds and the end card can be corrected before the approval without another model call (render parameter text_overrides; a new plan discards the corrections); a preview shows the title and the end card in 16:9, 1:1 and 9:16 with the unsaved corrections, drawn with the same fonts, line breaks and layout as the render, on a neutral background. After a cancelled, failed or interrupted run the app shows which steps are kept and which are still missing, with their cost and Resume.

Motion-graphics render node (optional)

The render_motion_graphics tool sends HTML/GSAP compositions to a small render service (Node + hyperframes + Chrome + ffmpeg) that can run on the same machine or any other computer — reachable directly or through an SSH tunnel. The service ships in this repo under render-node/. Multiple nodes are load-balanced automatically (idle first, then shortest queue), and imported media stream to the node in chunks (up to 500 MB per render). Configure nodes in Menu → Settings → Render nodes. The render node needs hyperframes 0.8: after an update run npm install in render-node/ and restart it; render-node/README.md lists the settings it starts hyperframes with.

Own computers as render nodes (WP46)

Everybody can connect their own computer (macOS, Windows or Linux) as a render node under Menu → My computers. The computer connects to the app itself over HTTPS and fetches its jobs (long poll): no SSH, no tunnel, no open port on the computer. The shared render nodes above stay the reserve.

  • Pairing. Create code shows a code (XXXX-XXXX, valid 10 minutes, once, one open code per person) and two commands, one for bash (macOS, Linux) and one for PowerShell (Windows). The command fetches the package with the code (setup.js, served by the app with the files of render-node/ embedded, each with its SHA-256), writes the files into ~/ocd-render-agent, pairs the computer (since WP54 before the slow steps), installs with npm ci from a lockfile (HyperFrames 0.8.139 exactly, ffmpeg and ffprobe from ffmpeg-static and @derhuerst/ffprobe-static; without them the agent takes ffmpeg from the PATH), loads the browser of HyperFrames (at most 4 minutes) and starts node agent.js in the foreground. On Linux the browser needs unzip; without it install.sh stops with a hint before it uses the code. Needs Node.js 22 or newer with npm. The computer gets a token of its own (ocra_ and 43 characters, 256 bits), kept in agent.json (mode 600) on the computer only; data/render-agents.json (mode 600) holds its SHA-256 hash, owner, name, platform, versions and the sharing options. node agent.js update fetches a new package with the token (no new code), status and forget show or delete the pairing.
  • Who renders what. A computer renders the jobs of its owner. Also render for my teams adds the jobs of people who share an active team with the owner; for everybody (only an admin, only on their own computer) makes it a shared node. A job of a person goes into the queue (data/render-queue/) as soon as such a computer is online, else the app sends it to a render node exactly as before. An idle computer takes the next job it may render: the owner's own computer first, then a computer of a team, then a shared one, the longest waiting first; when no computer takes it, an idle render node gets it. When nothing else waits, a job that a computer has rendered for 60 s and that, after its progress, needs at least 30 s more is also given to a second idle computer or render node (work stealing); the first result wins, the other copy is cancelled. The job id stays the same throughout; the job bar names the computer.
  • Leases. A computer reports every 10 s. Without a sign of life for 60 s, or after 20 minutes (plus 15 minutes while it uploads), the job goes back into the queue and to the next computer or render node. Ctrl+C hands the job back at once. After two failed renders or five tries the job fails with the last error; a job for which no computer and no render node is online for 10 minutes fails. Finished jobs are deleted after a day. After a restart of the app a running job keeps its lease for 60 s.
  • Limits. Page of a job up to 2 MB, at most 10 files with 500 MB together, result up to 1 GiB (an MP4, checked by its first bytes). At most 10 computers per person, 10 codes per person in 10 minutes, 10 wrong codes per IP address in 10 minutes, 30 wrong tokens per IP address in 10 minutes.
  • Versions. The app demands protocol 1 and the HyperFrames version of render-node/agent/package.json; an agent with another version gets 426 and tells its owner to run node agent.js update. The list shows the versions of every computer (also of ffmpeg).
  • The page of a job runs on somebody's computer. The agent puts a Content-Security-Policy into it (connect-src 'self' data: blob:, form-action 'none', object-src 'none'; scripts only from the page and from jsDelivr, unpkg and cdnjs, images, media, styles and fonts also from https:), so a page cannot send data anywhere with fetch, XHR, WebSocket or a form while it renders. HyperFrames renders in Chrome without a sandbox; whoever shares a computer with a team runs the pages of that team.
  • API. Computers: GET /api/render-agent/install.sh, GET /api/render-agent/install.ps1, GET /api/render-agent/package (header X-Render-Agent-Code or Authorization: Bearer), POST /api/render-agent/pair, POST /api/render-agent/poll, GET /api/render-agent/jobs/<job>/assets/<file>, POST /api/render-agent/jobs/<job>/progress, PUT /api/render-agent/jobs/<job>/result, POST /api/render-agent/jobs/<job>/fail (all but the first four with the token, the job paths also with the header X-Render-Lease). People: GET /api/render-agents, POST /api/render-agents/pairing-code, PATCH /api/render-agents/:id (the owner only) and DELETE /api/render-agents/:id (the owner or an admin; errors TOO_MANY_AGENTS, TOO_MANY_CODES, PACKAGE_MISSING, AGENT_NOT_FOUND, INVALID_NAME, FORBIDDEN). Admins see all computers in Settings → Render nodes and remove them there; removal stops the token at once.
  • Reverse proxy. The paths of the computers check the token themselves, so /api/render-agent/ (singular) must be released without a login in front, and only it: in nginx location ^~ /api/render-agent/, never a plain location /api/render-agent (that would also release /api/render-agents, the routes of the people). Everything under the prefix that is not a path above answers 404. Allow large bodies for the result and do not buffer them (client_max_body_size 1024m; and proxy_request_buffering off;), keep proxy_read_timeout at 60 s or more for the long poll (25 s), and pass X-Real-IP for the limits per address. Set PUBLIC_BASE_URL: the commands carry the public address. The server needs the folder render-node/ (or RENDER_AGENT_PACKAGE_DIR); without it the dialog says so and creates no code.
  • Limits of the agent. It keeps a Mac awake while it renders (caffeinate -i), but not a closed MacBook lid; on Windows and Linux it does not prevent sleep. One agent per folder renders one job at a time.

Tests: scripts/test-render-agents-store.js, test-render-queue.js, test-render-agent-dispatch.js (without a computer the requests to the render nodes are byte for byte those before WP46), test-render-agent-api.js, test-render-agent.js, test-render-agent-package.js (runs the bash installer against a fake app) and test-render-agents-ui.js; no paid call, no download.

Render servers at Hetzner (WP54, optional)

  • Render failures. Jobs remember failed agent and push-node IDs across restarts and try other eligible online workers first. The failure limit applies once no untried eligible worker is online; the attempt cap always applies. A lone worker still gets the usual two tries. A cloud server that has never rendered and fails twice in a row (say Chrome is missing in the snapshot) is removed (its agent first); a server that rendered before stays, then the jobs are the likely cause. Three broken servers or failed boots pause new starts until settings are saved. The status names the server and asks to check the snapshot, with a shortened, redacted job error. Start/boot errors clear when a newer server first asks for work; cap, authorization and configuration errors remain.

When a render job waits and no free computer takes it, the app rents a server at Hetzner Cloud. It starts from a snapshot with the render agent, pairs itself, renders and is deleted before its paid hour is over. Admins set it up in Settings → Rendering → Render servers at Hetzner; switched off (the default) the app behaves as before.

  • Project and token. Create a project of its own for the render servers in the Hetzner Console and a read and write API token there (Security → API Tokens). The field in the settings only takes the token and then shows «set» or «not set», never the value. It is kept in data/cloud-render/settings.json (folder 700, file 600; keep the file out of backups, like data/settings.json) and appears in no log, no answer to the browser and no error. While servers exist the token can be neither removed nor changed, because only it can delete them (Delete servers now first); only a token that Hetzner refuses (401) can be replaced, and a server the new one does not see is reported. PUBLIC_BASE_URL must be set: the servers report to this address.
  • Snapshot. The script render-node/cloud-snapshot.py builds it in one go (server, script, clean shutdown, snapshot, server deleted, the new id printed; --dry-run shows the steps). By hand: create a CPX42 with Ubuntu 24.04 in nbg1, create a code in Menu → My computers (as an admin) and run on the server curl -fsSL https://<app>/api/render-agent/cloud-image.sh | sudo bash -s -- --server https://<app> --code XXXX-XXXX. render-node/cloud-image.sh fetches the package first (the code is only checked, not used up), installs Node.js 22, unzip, the libraries of Chrome and fonts, the user ocdrender, the agent without a pairing in /opt/ocd-render-agent and two systemd units, and cleans up. Then type poweroff on the server (not «Power off» in the Console: it cuts the power, and files written last, such as the units, would be empty in the snapshot), take a snapshot, delete the server and enter the snapshot id in the settings. The snapshot holds no token and no pairing. It records the versions it was built with (/usr/local/lib/ocd-render/image.json); the agent of every server reports them, and the settings say «Snapshot outdated: HyperFrames … on the render server, expected …» when HyperFrames (as the agent package pins it) or the Node.js major of cloud-image.sh changed, or that the version of an older snapshot is unknown. Then build a new snapshot.
  • Firewall. The servers need no incoming connection (the agent fetches its jobs itself). Before a start the app makes sure the Hetzner firewall of this installation exists (labels ocd-render=firewall, ocd-instance=<id>, name ocd-render-<id>; made once, reused, never deleted, it costs nothing) and gives it to the server: nothing comes in but ICMP. Without it no server starts (error code firewall, the usual wait, then the next try). To log in to a server for debugging, add an inbound rule TCP 22 from your own address to that firewall in the Hetzner Console (Firewalls → ocd-render- → Rules) and remove it afterwards; the app leaves the rules of an existing firewall alone. The API token needs write access to firewalls as well.- First start. Every server gets a code of its own through user_data (15 minutes, once); on its first start it fetches the current package with it, pairs (npm ci only when the lockfile changed since the snapshot) and starts the agent; the agent starts only when this first start succeeded. A server counts as online from the first request of its agent, not from the pairing. Its computer belongs to the system, renders for everybody after the people's computers, and is listed for admins only, marked «cloud server».
  • Start. Switched on and complete, every job goes through the central queue. Mode Reserve (default): a server starts when a job has waited 20 s, or at once when no computer or render node is online for it. Mode First: at once. At most one start every 5 s and at most max servers at a time (default 1); a server that boots or is idle counts for a waiting job. Before a start the app asks Hetzner whether the type is available at the location (server_types[].locations[].available) and what it costs.
  • Delete. Hetzner bills every hour begun, also for a server that is off, so servers are always deleted, never stopped: when nothing waits, its computer holds no job and the paid hour is nearly over (from minute 50); at the hard limit of 3 hours even while it renders (the job goes back into the queue); when it is not online after 15 minutes, not paired or paired with an agent that never asks (three in a row pause the starts until the settings are saved again); 10 minutes after its agent stopped asking. Its computer is removed with it, its token stops working at once. Delete servers now deletes all servers of this installation at once, also rendering ones, and switches the cloud off (else a returned job would start a new server right away).
  • Costs and cap. Every server is booked in data/cloud-render/ledger.json (mode 600): hours begun × the hourly price of the API, in the month of its creation. When the next hour would go over the monthly cap (default 30 €), no server starts; the settings and the log say so, and a job with no computer online fails after 10 minutes with that reason. A running server can go over the cap by its hours up to the hard limit.
  • Reconcile and restart. Every 5 minutes the app lists the servers with the label ocd-render=cloud. Those with the id of this installation that it does not know and those over the hard limit are deleted and booked, gone ones booked out, computers without a server removed. Servers with another id (another installation in the project, or a lost id) are left alone and shown in the settings. The id is kept in state.json, in instance.json and in every entry of the ledger, and is never made anew while one of them knows it. The state survives a restart of the app, and so does the code of a server that boots meanwhile: system codes are kept in data/render-agent-codes.json (mode 600) as a salted SHA-256 only, with the key of the server, the expiry and the uses, and leave the file when used, expired or when their server is deleted. A server whose code is gone anyway (an older version of the app) is deleted at once.
  • Errors. A refusal of Hetzner (project limit, resource_unavailable, a wrong snapshot id, 401) is shown with its reason; the app waits 1, 2, 5, 10, then 30 minutes (401: 30 minutes at once) before it tries again, and saving the settings resets the wait.
  • Place in the queue. A node that waits for its render shows «In the queue: place 2 of 3, about 4 min», «Render server is starting, about 1 min» or «Rendering: part 2 of 3» in the node view and in app mode. Counted per film (all parts of a node run), estimated with 2 s of rendering per film second (then measured) and the start time of a server. Everybody sees only their own place and the total, with or without the cloud.
  • API. GET /api/cloud-render, PUT /api/cloud-render, POST /api/cloud-render/delete-servers (admins); GET /api/render-agent/cloud-image.sh (public, static).
  • Cleanup without the app. Every render or snapshot server carries its expiry time and a fingerprint of the app's public address as labels. The app deletes its expired servers even after it lost its local state. python3 render-node/cloud-cleanup.py lists them on its own (--delete deletes them, --server <public app address> limits it to one app); schedule it, for example with cron, if cleanup must also run while the app is off. See render-node/README.md.

Tests: scripts/test-cloud-render.js (against a fake Hetzner API on 127.0.0.1, no call to Hetzner), test-cloud-snapshot.js (the build script against a fake API and a fake server), test-render-agents-store.js (system codes on disk), test-render-queue-position.js and test-render-agent-package.js (the installers pair first and check unzip; the browser has a time limit).

User management (optional)

Out of the box the app is single-machine and open: everybody may do everything, nothing has an owner. User management is active if and only if AUTH_WHOAMI_URL is set. The login itself stays outside the app: put it behind your own login (any auth proxy that can answer a whoami endpoint { "logged_in": true, "email": "…" } for the caller's cookie). The app never asks for a password.

Setting Meaning
AUTH_WHOAMI_URL Whoami endpoint of your login. Setting it turns user management on. Unset = the local mode, exactly as before
ADMIN_EMAILS Comma-separated admins (more can be added in Menu → Settings). No built-in default
SUPERADMIN_EMAILS Comma-separated superadmins, any domain. Environment only, never editable in the app. They are admins as well and may open the monitoring page
AUTH_LOGOUT_URL Optional sign-out link for the account menu (https://… or an absolute path). Without it there is no sign-out entry
INTERNAL_EMAIL_DOMAINS Comma-separated e-mail domains of your own organisation (e.g. example.com). Their addresses are internal people. Only relevant with teams: a logged-in address that is neither internal nor in a team is a guest. Unset = everybody logged in counts as internal (the behaviour before teams), except addresses that are or were in a team (also archived or removed): those count as guests. The app warns at startup and in the teams settings while it is unset
ACCESS_ALLOWLIST_FILE + ACCESS_ALLOWLIST_ROUTE Optional allowlist sync: path of the JSON file of your login and the route to maintain (e.g. /training). Both or none

Rules in the active mode:

  • Who is who. The caller is the e-mail address the whoami endpoint returns. A request without a valid address (whoami failed, direct access to the app's port) is anonymous: it sees and uses only what has no owner, and what it creates gets no owner either.
  • Unconfirmed login (fail closed). As soon as a restriction is active, an anonymous request may be a participant whose login could not be confirmed (whoami timeout, network error, 5xx). A restriction is active when the teams file knows any address (also of removed people), the teams file is unreadable, or INTERNAL_EMAIL_DOMAINS is set. Then every /api route answers 401 with the code LOGIN_UNCONFIRMED (German, English and Spanish text), except the public ones (GET /api/me, /api/nodes/registry, /api/workflow-templates, /api/roles/default) and GET /api/config (filtered like for guests). The static files and the app shell stay reachable; both views show a banner with a reload button, and the page reloads by itself once the login is confirmed again. A failed whoami call is remembered for 2 s only (a clear answer for 10 s), and no paid action starts for such a request. Without a restriction (no teams ever, no domain list) and in the local mode nothing changes.
  • New chats and node workflows start private (owner and admins). The owner can share them with the whole team or with selected people from the team list. People a chat is shared with can open it, keep chatting, edit and run a workflow, use its Design App and download its files; only the owner and admins can rename, move, delete or re-share it.
  • Existing data stays open. Chats and workflows without an owner (created before user management was switched on) remain visible and usable for everybody, including renaming and deleting, exactly as before. Only admins change their sharing; the admin who does becomes the owner.
  • Foreign private entries do not exist for you. Every route answers 404 (not 403) for a chat or workflow you may not see, and the same holds for its files (/assets/…), downloads (ZIP), exports, event streams and runs. Taking a share back ends open event streams.
  • Projects (folders) show only entries you may see; a project with only other people's private entries is hidden. Renaming or deleting a project that holds other people's entries, or one with a production profile, context files, memory or cast, needs an admin. Project names are unique across the app: a name taken by a project you cannot see is answered with a neutral "not available".
  • The Director's memory follows the chat. Notes saved with save_memory belong to the person who was chatting and only reach that person's chats (notes from before user management stay global). Notes saved with save_project_memory are only shown, and only reach the prompt, for people who may use the chat they came from (sharing the chat shares them; admins see all). Chat tools that change brandings (create_branding, update_branding, add_branding_asset) are admin-only like branding import/delete. Known limit: the cast tools (create_cast_member, update_cast_member, import_cast_asset) stay available to everybody who may use the project, because they are the only way to fill the cast (participants and guests only in projects they created themselves); only deleting a member is admin-only.
  • Admin-only (the rest is open to everybody who is logged in): Menu → Settings and API keys, admin and user management, render nodes, Higgsfield and ChatGPT connections, roles, custom prompt presets, branding import/delete, project profiles (guidelines, context files, memory, cast).
  • Team list. The people you can share with: admins, people an admin added in Menu → Settings (data/users.json) and everybody who has used the app once (recorded automatically with first/last seen in data/team-seen.json). It is not an access list — access stays with your login. Admins can remove entries; a removed person is not recorded again until an admin adds them back.
  • Costs are recorded per person; admins see all costs, everybody else only their own.
  • Monitoring (superadmins only, everybody else gets a 404): /monitoring.html and GET /api/admin/monitoring (?days=7|30|90|all&user=…&provider=…, CSV: /api/admin/monitoring/export) show usage and costs per person, provider and day, runs, jobs and failed requests.

What people see in the interface (nothing of this appears in the local mode):

  • Menu (top right, in the chat and in the node view): initials chip, e-mail address, role (person, admin, superadmin), the person's own costs of the month, settings (admins), Help, a link to the monitoring page for superadmins, the language and a Sign out entry if AUTH_LOGOUT_URL is set. In the local mode the same menu sits behind a three-dots button and has costs, settings, help and language. A person who is not identified sees a note that only entries without an owner are available.
  • Sidebar and header. Somebody else's chat shows its owner next to the date (full address as tooltip) and an Internal, team-name or n people badge when it is shared. In the header the breadcrumb Project / chat title shows the owner (by name) only in somebody else's chat; owners who are admins carry an ADMIN badge. Shared is a status with a green dot, Shared with you in somebody else's chat; whoever may not manage the sharing sees the status without a button. Chats and workflows that existed before user management show neither a badge nor an owner.
  • Share button (chats in the header and sidebar menu, workflows in the node view header and list menu; only for the owner and admins): private, everyone internal, teams or selected people (the modes depend on the account, see Interface of the teams below). For an entry without an owner the dialog says so; saving makes the admin the owner. If the owner takes the sharing back while somebody has the entry open, it closes with a short message (also for open event streams).
  • Design App links. Copying the app link of a private workflow reminds you that others can only open it after it has been shared.
  • Admin-only parts (custom prompt presets, new roles, branding import/delete, project profile editing) are hidden for everybody else; the project profile opens read-only. Menu → Settings show a Team list with first/last seen (add or remove people) and, for superadmins, a link to the monitoring page.

Teams with a USD budget (optional)

Teams are for trainings and courses: people from outside your organisation get access to the app, a fixed USD budget and a deliberately narrow view. They need the user management (AUTH_WHOAMI_URL); in the local mode the team routes answer 400 USER_MANAGEMENT_INACTIVE and nothing changes. Without teams and without INTERNAL_EMAIL_DOMAINS the app behaves exactly as described above.

Roles. Admins create teams in the interface (Menu → Manage teams, or Menu → Settings → People; see Interface of the teams below) or through the API (/api/teams, admin only) and add people by e-mail address.

Role Who Sees and may do
Admin / superadmin ADMIN_EMAILS, SUPERADMIN_EMAILS, admins added in Menu → Settings Everything, no budget
Internal person Address of INTERNAL_EMAIL_DOMAINS (or, unset, any logged-in address that is not and never was in a team) that is in no team As before: existing data, all tools, no budget
Participant Member of at least one active team (also with an internal address) Own chats, workflows and projects and what is shared with their team; the budget of their team; no internal resources
Guest Logged in, not internal, in no team Like a participant with a budget of 0 (free things only)
Anonymous No valid login As before

What a participant does not get. Existing data without an owner and every private entry of others (404), the internal knowledge base (GTS), brandings, team roles and custom prompt templates, the project profile (guidelines, context files, brandings) of projects they did not create, the names of render nodes, the ChatGPT subscription models, all Higgsfield tools and nodes (operator credits) and the cloned ElevenLabs voices (library voices only). They can share only with teams they are in or with people of their own teams, never with "everybody internal". Guests can only keep things private. Answers say why: 403 FORBIDDEN_FOR_ROLE with the feature (gts, higgsfield, chatgpt …).

Expensive Director models (optional). config.json accepts restrictedBrainModels, a list of model ids (for example ["openai/gpt-5.6-luna", "google/gemini-3.1-pro"]). When it is set, participants and guests see and use only these Director models: in the chat menu, as default model (the configured defaultBrain if it is on the list, else the first entry) and in the LLM nodes. The server enforces it: a chat request for another model is answered by the default model of the list (with a notice), and an LLM node that names another model is refused with 403 FORBIDDEN_FOR_ROLE (feature: models). Empty or missing: nothing changes. The ChatGPT subscription models stay unavailable for them in any case; internal people and admins are not affected. There is no settings screen for it, the file is enough.

Image models in the node view (optional). config.json accepts imageModels, a list of OpenRouter image model ids (default: ["openai/gpt-image-2", "google/gemini-3-pro-image", "google/gemini-nano-banana-2.1"], GPT Image 2, Nano Banana Pro and Nano Banana 2.1). The nodes Generate image and Edit image offer exactly these models plus the entry "Default" (the model in imageModel, which always counts as listed and keeps old workflows working). The choice is made per node. The list shows what each model takes (reference images) and, where a price is known, what an image costs; the estimate of the chosen model feeds the plan and the budget of participants. A workflow that names a model outside the list is refused when it runs (IMAGE_MODEL_NOT_ALLOWED), so a shared or imported workflow cannot spend on an arbitrary model. The same list applies to everybody, as in the chat. Missing or invalid: the default list.

Sharing with teams. A new sharing mode teams (sharedTeams: up to 20 team ids) makes a chat or workflow available to all members of these teams at once — and only them. Internal people may pick any active team, participants only their own. Removing a person from a team or archiving the team ends the access at once.

Interface of the teams. Admins: Menu → Manage teams (or Menu → Settings → People, the teams come first) lists every team as a card: people, USD per person, amount spent. An opened team has its name, amount and description with Archive / Reactivate / Delete, a paste box and the table of members (last seen, spent, left, budget). Change budget sets an own amount for one person (marked custom, Team amount takes it back), Reset restarts the counting from now, Remove ends access and budget at once; reset, remove and delete need a second click. The paste box takes anything with addresses — an Excel column, one per line, separated by commas or semicolons, Name <mail@example.com> from a mail program — and previews N new · M already in the team · K invalid (the invalid pieces can be expanded) before one button adds the new ones in a single request (up to 500). The team list in the settings has the same paste box. When the allowlist sync failed, a warning appears above the list; the team is saved anyway. Superadmins get a Team filter and a Teams and budget table (budget, spent, left, reserved, spending in the period, jobs without price per person) in the monitoring. Participants: the menu shows the budget left with a bar, the amount and the spending, and the teams; a line above the chat says when the budget runs low or is used up, and paid actions that are refused (402) or not available (403) are explained in the interface language while chats and results stay readable. A paid node run shows the remaining budget and its estimate in the confirmation dialog and is not offered at all when the estimate exceeds what is left. The share dialog offers Private, Teams (only the person's own) and Specific people (only teammates); internal people and admins also get Everyone internal, guests can only keep things private. The help page (public/help.html) explains all of this in Teams and trainings and Budget and participation.

Budget. Every team has an amount in USD per person (0 to 10 000, whole cents). Per person it can be overridden (budgetOverrideUsd) or restarted (resetBudget: counting starts again from now). Someone in several teams has the highest amount and the latest start of their memberships; joining a new team starts a fresh count. The spent amount comes from the cost journal (data/costs.jsonl, every row carries the person) and is read incrementally, never as a whole. Costs the provider does not report count 0; admins see such jobs in the monitoring (unknownCostJobs).

Every place that can spend money asks the budget first, and answers 402 before anything starts:

  • chat turns (before the stream, and before every tool round of a turn),
  • the Director's tools generate_image, edit_image, generate_video, generate_speech and (node view) fal_generate,
  • node runs: the plan estimate must fit and is reserved for the run; the plan (POST /api/workflows/:id/runs/plan) reports budget and blocked nodes,
  • the assistant of the node view (POST /api/workflows/:id/assistant, one llm.completeText call, a second one only to repair an invalid proposal; the real cost is booked on the person; the panel has a list of the language models the person may also choose in the chat (the same names, the default of a new chat preselected, the choice kept in the browser), model in the body must be one of them (else 403), and a small line under every answer names the model that answered; while a question runs the budget reserves the estimated prompt times the input price plus the longest answer (3500 tokens) times the output price of that model, at least 5 cents (prices: the table of the explainer plan, GPT-6 Astra, Opus 5.5 and Sonnet 5.5; any other model reserves with the highest known price of the models the person may choose, at least 5 cents (a conservative approximation; none of them priced: the highest price the app knows); subscription models, for internal people only, reserve nothing because the budget only concerns restricted people; this also holds without model for the preselected model of a new chat, which with GPT-6 Astra usually reserves about 28 instead of 5 cents), and when the provider reports no cost the person is charged the estimate of the model itself (5 cents for a model without a known price, not the reservation); at most 30 questions per person in 10 minutes);
  • LLM nodes (llm.completeText); asynchronous jobs (video, fal) keep their reservation until the poller has booked the cost. A video started from the model card reserves the estimated price of the chosen model (the upper end of the estimate). Where a video job has no price in advance, the Director reserves a flat BUDGET_ASYNC_HOLD_USD (default 2, at most what is left) per job, and a participant may have at most BUDGET_MAX_OPEN_JOBS (default 2) provider jobs open at once (429 BUDGET_JOBS_OPEN). When a node run is cancelled or times out while a provider job is still running, the run's reservation moves to that job instead of being released.
  • Speech (generate_speech) is priced per character and model (ELEVENLABS_USD_PER_1K_CHARS, default 0.08, is the price of a full-price model such as Eleven v4, v3 or Multilingual v2; the Turbo and Flash models count half, as the ElevenLabs price page lists them, read 2026-10-03): the estimate is reserved, booked as a cost of type speech (for every person, internal ones too) and shown in the monitoring.
  • Music (generate_music, node view only) is priced by the minute (ELEVENLABS_MUSIC_USD_PER_MIN, default 0.20, an estimate; the ElevenLabs Music API needs a paid ElevenLabs plan): the estimate is reserved, booked as a cost of type music and shown in the monitoring. A guest has no budget, so the music stops with BUDGET_EXHAUSTED. The song text (plan_music) is not booked.
  • Word times (lyrics_timing, node view only) are priced by the hour of audio (ELEVENLABS_TIMING_USD_PER_HOUR, default 0.22: ElevenLabs lists speech to text with Scribe v2 at 0.22 USD per hour, and the alignment costs the same): the length of the file is measured, the estimate for it is reserved and booked as a cost of type speech, shown in the monitoring. Where the length is not known before, a call reserves the price of ten minutes, never more than the longest song the node takes (20 minutes); what is booked is always the measured length. A guest has no budget, so the node stops with BUDGET_EXHAUSTED.

Codes: BUDGET_EXHAUSTED (nothing left) and BUDGET_INSUFFICIENT (the price estimate exceeds what is left) — body with budget, estimateUsd, remainingUsd and a German error text. Chats, results and downloads stay available when the budget is used up. Reservations of parallel actions are kept in memory (restored for open jobs after a restart).

Known limit: where no price is known in advance (images; videos use the flat reservation above) only "something is left" is checked, so a few parallel jobs of one person can still overshoot the budget by their own price; the real cost is booked afterwards and the next action is refused.

Chats and workflows by team (admins). Admins see every chat and workflow. A switch “Projects | Teams” above the chat list and the workflow list of the node view groups them by team (default: “Projects”, the choice is remembered per view in the browser). Each team is a collapsible group with its number of chats or workflows and people; archived teams follow collapsed, everything without a team (owners without a team, guests, old data without an owner) comes last as “Internal”; every entry names its owner. In the workflow list each team is further split into one collapsible subgroup per person who created workflows there (latest activity first, workflows without an owner last); the open state of teams and subgroups is remembered in the browser. Everybody else gets no switch and no team information (the server refuses the group routes with 403). Which team an entry belongs to: new chats and workflows store teamId, the active team of the creator (the one joined last if there are several); only the server sets it, no request body can (also not on import, where a teamId in the file is dropped, and not on duplicating: that stores the team of whoever duplicates). Entries without teamId belong to the owner's team at the time of creation: the membership with the latest addedAt that is not later than createdAt (archived teams count), else the earliest later one, else “Internal”. A stored team that was deleted sends the entry to “Internal”; removing a person from a team drops the derived assignment of their older entries but keeps the stored one. The groups are complete however the chat list pages (they come from the in-memory index of the session files, no file is read again). The keyboard focus stays on the group head or the “Load more” button when a group is opened, closed or extended, and an answer for a group that was read before a chat was deleted is dropped and read again. In the team view the sidebar heading reads “Chats”.

API (admin only unless noted; sync is the state of the allowlist sync):

Route Purpose
GET /api/teams, POST /api/teams List / create (name, budgetUsd, description)
GET/PATCH/DELETE /api/teams/:id Detail with the budget of every person / rename, change amount, archived / delete
POST /api/teams/:id/members { emails: [...] } or { text }: up to 500 at once, answers added, already, invalid, duplicates
PATCH /api/teams/:id/members/:email { budgetOverrideUsd: number or null } or { resetBudget: true }
DELETE /api/teams/:id/members/:email Remove a person
GET /api/teams/mine Any logged-in person: the teams they may share with
GET /api/sessions/team-groups The groups of the chat list: { groups: [{ id, name, archived, internal, count, people, lastActivity }], total }, active teams by latest activity, archived ones, then id: "none" (internal)
GET /api/sessions?team=<id|none>&limit=&offset= The chats of one group, paged; every chat carries team: { id, name, archived } or null. With q=…&teams=1 the flat search result carries the team as well
GET /api/workflows Admins in addition get team per workflow and teamGroups (same head data); the list is not paged, the client groups it
POST /api/users Also { emails: [...] } / { text } for the team list (same parser)
GET /api/admin/monitoring?team=<id> Superadmins: usage and budget picture per team and person (teams, options.teams)

Pasted lists (Excel columns, Outlook / Gmail recipient lists, CSV, one address per line) are read by one shared parser (public/email-list.js, in the browser and on the server): lower case, duplicates once, pieces that are not addresses reported as invalid.

Data: data/teams.json (chmod 600, written atomically), data/folder-owners.json (who created a project) and data/access-sync.json. Errors: 400 with INVALID_TEAM, INVALID_BUDGET, TEAM_NAME_TAKEN, NO_EMAILS, INVALID_EMAILS, TOO_MANY_EMAILS, TEAM_FULL, SHARE_MODE_FORBIDDEN, UNKNOWN_TEAMS; 404 with TEAM_NOT_FOUND, MEMBER_NOT_FOUND.

Allowlist sync (optional)

Participants have addresses your login does not know. If your login keeps an allowlist file ({ "default": {...}, "routes": [{ "path": "/training", "public": false, "extra_emails": [...] }] }), set ACCESS_ALLOWLIST_FILE (path) and ACCESS_ALLOWLIST_ROUTE (route). After every change of a team the app then makes extra_emails of that route hold the manually maintained addresses plus everybody in an active team (addresses of INTERNAL_EMAIL_DOMAINS are not entered). Manual entries and other routes are never touched: only what the app added itself is removed again (remembered in data/access-sync.json, written before the list changes, with a copy next to the list; an unreadable record stops the sync with a warning). The file is written atomically (a symlink is followed; the owner is kept where the app may set it; a single file mounted into a container is written in place), keeps its permissions, and a copy <file>.bak-<timestamp> is made once before the first change; a missing route is created. A failure never blocks the team change — it shows up as a warning in sync. Both variables unset = nothing happens.

Agent access (MCP) (optional)

External agents (Claude Code, Codex, your own bots) can order runs from the app through an MCP endpoint. An agent works with a personal key, in the name of the person who created it: the same templates, workflows and cost rules as in the interface, limited per run and per month. The app executes the workflows; the agent only orders runs and fetches the results. The endpoint is POST /mcp (MCP over Streamable HTTP, JSON answers only; GET answers 405). It is a small own implementation without an extra dependency. It speaks the protocol revision 2026-07-28 and, with initialize and version negotiation, the earlier revisions 2025-11-25, 2025-06-18 and 2025-03-26. JSON-RPC batches (an array of up to 10 messages) are accepted as revision 2025-03-26 requires, that is without the version header or with 2025-03-26.

Set up

  1. Open Menu → Agent access (MCP) (admins and internal people; in the local mode everybody). The dialog shows the address of the endpoint, your keys with right, limits, usage this month, last use and expiry, and a form for a new key.

  2. Create a key: name, right (Read only or Read and start), limit per run, limit per month and validity. The key is shown once; copy it right away. Only a hash is stored.

  3. Enter the address and the key in the agent. For Claude Code:

    claude mcp add --transport http open-creator https://your-app.example.com/mcp --header "Authorization: Bearer <key>"

    Any other client needs three things: transport Streamable HTTP, the address, and the header Authorization: Bearer <key>. The key is only read from that header, never from the address.

Keys

  • Format ocd_k1_ plus 43 characters (256 bits of randomness). data/mcp-keys.json (mode 600) holds the SHA-256 hash, a short prefix for the list, name, owner, right, limits, creation, last use, expiry and revocation. The key never appears in a log, an error message or the cost journal.
  • Defaults: 2 USD per run, 20 USD per month, 90 days. Upper bounds: 1000 USD per run, 10000 USD per month, 365 days; at most 25 valid keys per person.
  • A key acts as its person and sees the same workflows, templates and teams, never more. It stops working as soon as the person is no longer an admin or internal person or was removed from the team list. Participants and guests get no keys. Without user management (AUTH_WHOAMI_URL unset) the key acts as the local person, and everybody who can reach the app can create keys.
  • Admins see and revoke all keys; everybody else only their own. Revoking works at once. API: GET and POST /api/mcp/keys, PATCH and DELETE /api/mcp/keys/:id (errors INVALID_NAME, INVALID_RIGHT, INVALID_LIMIT, INVALID_EXPIRY, TOO_MANY_KEYS, KEY_NOT_FOUND, KEY_REVOKED, FORBIDDEN_FOR_ROLE).

Tools

Tool Right What it does
list_templates read Templates with inputs and cost range
list_workflows read Own and shared workflows
get_workflow read Nodes, inputs, latest results as links
estimate_run read Cost estimate of the engine with the paid steps
get_run read Status, progress, results as links (can wait up to 30 s)
upload_asset read and start Image, video or audio as an input: base64 up to 15 MB, larger files through a one-time upload link
create_from_template read and start Creates a workflow from a template and fills its inputs
run_workflow read and start Starts a run; requires max_usd, the most the agent accepts to pay
cancel_run read and start Cancels a run of this key

create_from_template and run_workflow accept optional file_marks for app file inputs with per-file AI marks: an object from asset id, session_id/asset_id or wf/asset_id to never or always. Omit auto. Unknown files and invalid marks are refused before changing the workflow; marks follow copied files into its storage.

A Read only key sees and uses the five read tools. run_workflow refuses with a readable reason, and charges nothing, when the estimate is above max_usd or above the limit per run, when the calendar month would exceed the monthly limit (booked costs of the key plus reservations of running runs), when a paid step has no known price, when the workflow uses Higgsfield (blocked for agents), when the key already has two runs in progress, or when the budget of the person does not cover it.

Links. Results are delivered as GET /mcp/files/<token> (signed, valid 24 hours, one file, no login; only media, no SVG or JSON). Large uploads go to PUT /mcp/upload/<token> (valid 15 minutes, once, up to 500 MB, SVG up to 10 MB, same type rules as the normal upload). Links begin with PUBLIC_BASE_URL, also with a sub-path; without it the address of the request is used. The signing secret lives in data/mcp-link-secret (mode 600). An upload link stops working as soon as the key is revoked or the person is no longer eligible; a result link is signed and keeps no state on the server, so it stays valid for its 24 hours.

Costs. A run through a key books on the person, as always. Every line of the cost journal carries the id and the name of the key (never the key itself), so the cost summary can tell what an agent spent.

Limits. 60 requests per minute and key (a JSON-RPC batch counts as many requests as it has messages); at most 4 requests at the same moment per key, and at most 2 request bodies above 1 MB in progress in the whole app (more are answered 429 with Retry-After); a request body of at most 22 MiB; two runs at a time per key; 30 workflows from templates per key and hour; 20 open upload links per key. Uploads stay in a hidden session per key, at most 2 GB, 1 GB per hour and 1000 files per key (over a limit the tool answers with an error, a link with 413 or 429, and a refused link is not used up). They are removed after 7 days; a workflow keeps its own copy of a file it was given. The first bytes of a file have to match its type (png, jpg, webp, gif, mp4, m4a, webm, mp3, wav, aac); an SVG is rasterised. A restart of the app ends open upload links and the reservations of running runs.

Reverse proxy. The app checks the key itself, so three paths must be released without a login in front: the endpoint /mcp, /mcp/files/ and /mcp/upload/. In nginx that is location = /mcp and location ^~ /mcp/; a plain location /mcp would also release /mcp-ui.js and every other path that begins with /mcp. Everything else keeps the login. For /mcp/upload/ allow large bodies and do not buffer them (nginx: client_max_body_size 500m; and proxy_request_buffering off;). Set PUBLIC_BASE_URL to the public address of the app, otherwise the dialog and the links show the internal one. Agents send no Origin header; requests from a browser page are checked against the whole origin (scheme, host and port): the address in PUBLIC_BASE_URL is allowed, further origins go into MCP_ALLOWED_ORIGINS (comma-separated; a bare host means https://host). Loopback addresses are accepted only on a local installation, without PUBLIC_BASE_URL (or with a loopback one), and only on the port of the app itself. The app does not limit wrong keys per IP address (the keys have 256 bits); do that at the proxy if you need it. The paths /.well-known/oauth-* are not used (bearer key only); the proxy may answer them with 404.

Tests: scripts/test-mcp-protocol.js, test-mcp-keys.js, test-mcp-api.js, test-mcp-tools.js, test-mcp-limits.js (JSON-RPC client over real HTTP against isolated app copies, no paid call) and test-mcp-ui.js.

Tests

npm test                          # every scripts/test-*.js, one after another
npm test -- nodes-generate        # only tests whose file name contains the text
npm test -- --timeout=600         # seconds per test (default 240)
npm test -- --jobs=4              # several at once (faster; tests share the data folders of the copy, so they can collide)
npm test -- --keep                # keep the temporary copy the tests ran in (its path is printed)

scripts/run-tests.js starts each scripts/test-*.js as its own process and prints PASS or FAIL with the duration, then n/m passed (exit code 1 if anything failed). A test passes only if it exits with 0 and its last output line is <file name>: ok; a test that just stops early (for example a promise that never settles) is reported as FAIL ... silent end. Every new test therefore ends with console.log('test-<name>.js: ok'), after its last check. Some tests skip their browser part when Chrome or render-node/ is missing. npm test runs the tests in a temporary copy of the repository (the files git tracks or would track, node_modules linked, empty data/, projects/ and assets/; no .env), so they never write into your data/, projects/ or assets/ and never read your settings or keys; the copy is removed at the end. A test started directly with node scripts/test-….js runs in place. Test processes inherit only a small allowlist of runtime environment variables, without service credentials. Each test runs in its own process group; a timeout kills the entire group before the temporary copy is removed.

Browser coverage: node scripts/run-tests.js browser- --timeout=900 --jobs=3 checks the real page, editor, app uploads/reload, SSE reconnection, approval and cancellation. It uses puppeteer-core from render-node/node_modules and the newest Chrome or chrome-headless-shell in $HOME/.cache/puppeteer; OCD_TEST_BROWSER=/absolute/path/to/chrome overrides the executable. Missing optional browser dependencies print SKIP and the runner's : ok marker (no browser coverage); an invalid explicit path or launch failure fails the test. Each browser file has an 85-second deadline. The fixture uses temporary stores, the real HTTP routes and engine, and local controlled nodes with synthetic costs; no provider is called and no production test hook is installed. test-browser-fixture.js and test-browser-runtime.js also run without Chrome or listening ports. A sandbox listen EPERM is a failure to run the browser checks, not a successful browser test.

Help and contributing

The help page (public/help.html, linked in the menu) documents every function in German, English and Spanish. Whoever changes a function updates all three language blocks; see docs/agent-rules.md.

Third-party software

  • <model-viewer> 4.3.1 (Google, Apache-2.0, includes three.js) shows 3D results. It is shipped in public/vendor/model-viewer/ (version, source and integrity value of the npm registry are noted there), loaded only when a 3D model is shown, and is no entry of package.json. The decoders for compressed models (Draco, KTX2, Meshopt) are not shipped: an ordinary GLB, as the image-to-3D nodes deliver it, makes no request to another address, but a compressed model makes <model-viewer> load its decoder from Google (www.gstatic.com). The bundle names one more address as the default for Lottie textures (cdn.jsdelivr.net); no GLB uses it, and the app points that setting at itself before the viewer starts. A GLB that points at images or data outside its own file is not stored (the result fails with a message), so the statement holds for every model the nodes deliver.

  • Inter Tight (The Inter Project Authors, SIL Open Font License 1.1, as of 2026-10-04) is the default font of the typography style of the explainer video. The variable font (weights 100 to 900, latin subset, one WOFF2 file of 44 916 bytes from Google Fonts) lies in lib/fonts/inter-tight/ with its licence text and is not under the MIT license. It is embedded into the page of a scene (as a data URL) when a scene has no font of its own branding; it is never fetched from the internet. The licence allows use, embedding and redistribution of the font together with this software; the font may not be sold on its own, and the licence text stays with it. The style HUD Blue of the music video in the HUD style also sets its karaoke line, speech bubbles and notifications in Inter Tight.

  • Anton, Instrument Serif, JetBrains Mono and Montserrat (SIL Open Font License 1.1, as of 2026-10-08) are the fonts of the music video in the HUD style (WP44): Anton 2.116 (The Anton Project Authors), Instrument Serif Italic 1.000 (The Instrument Serif Project Authors), JetBrains Mono Medium and Bold 2.305 (The JetBrains Mono Project Authors) and Montserrat 9.000 as one variable font (The Montserrat.Git Project Authors). They lie in lib/fonts/anton/, lib/fonts/instrument-serif/, lib/fonts/jetbrains-mono/ and lib/fonts/montserrat/, each with its licence text and a README that names the source, the version and the change, and are not under the MIT license. The files are subsets (Basic Latin, Latin-1, Latin Extended-A, punctuation and the signs the HUD draws) made with scripts/subset-font.js, because a page for the render node may be 2 MB at most; the licence allows that, and no copyright line names a Reserved Font Name. They are embedded into the page of a chunk as data URLs and never fetched from the internet. The licence allows use, embedding and redistribution of the fonts together with this software; they may not be sold on their own, and the licence texts stay with them.

  • The figure Claudia (an AI singer by anabology, claudia.gallery) is not software and not part of the MIT license: it is a character whose looks are written down as a text (the default of the field Figure in the templates of the music video in the HUD style). The terms, as read on her site on 2026-10-08: use for images, videos, songs, stories and games, commercial or not; the credit «Claudia by anabology» is wanted; what is generated has to be said; she is always an adult; nothing explicit; she is fan work about Claude and has no connection to Anthropic. The end card of the film carries the label and the credit. Whoever builds on this app reads the current terms themselves.

  • ffmpeg-static 5.3.0 and @derhuerst/ffprobe-static 5.3.0 (GPL-3.0-or-later, as of 2026-10-09) are optional dependencies of the render agent (render-node/agent/package.json), not of the app, and not part of this repository. npm ci on the computer of a person downloads the packages from the npm registry and their install scripts download the ffmpeg and ffprobe binaries for that system from GitHub (as of 2026-10-09: ffmpeg 6.0 on macOS arm64, 6.1.1 on macOS x64 and Windows x64, 7.0.2 on Linux x64); the build for macOS arm64 was configured with --enable-nonfree. Nothing of it is distributed by this app. Whoever bundles these binaries themselves checks the licences of ffmpeg and of the build.

  • GSAP (Webflow, GSAP Standard License, as of 2026-10-04) is not part of this repository and not under the MIT license. The render node loads it from jsDelivr while it renders motion graphics (the package gsap at a fixed version). The license is free of charge, also for commercial use; GSAP is forbidden only in no-code tools for visual animation that compete with Webflow. According to the FAQ of GSAP, GSAP code written by an AI is not a forbidden use. Whoever builds on this app checks the current license text themselves.

License

MIT © 2026 Kuble AG — built by the Kuble team with Claude Code and Codex.

来源:GitHub · github.com