@devmotion/cli
DevMotion Local Studio. The DevMotion editor, renderer and MCP server, running on a project folder on your machine. Generation (voiceover, images, video, music, captions, dubbing…) and the DevMotion chat run on devmotion.app with your account; the chat can also drive the Claude Code or Codex you already have installed.

npx @devmotion/cli init my-video
cd my-video
npx @devmotion/cli # opens the Studio in your browser
Install
npm i -g @devmotion/cli # then: devmotion …
# or without installing
npx @devmotion/cli <command>
Requires Node.js ≥ 20. ffmpeg ships with the package (@ffmpeg-installer/ffmpeg), and
a postinstall step downloads Playwright Chromium (~150 MB, once) for rendering. Skip it
with DEVMOTION_SKIP_POSTINSTALL=1; devmotion render fetches it on first use.
Commands
| Command | Purpose |
|---|---|
devmotion | Open the Studio on the project in this folder (or show help) |
devmotion init [dir] [--name] [--size 1920x1080] [--fps 30] | Create a project, .gitignore and agent setup (--no-agent to skip) |
devmotion studio [dir] [--port 4747] [--no-open] | Run the Studio: editor, renderer and MCP server (alias preview) |
devmotion render [dir] [-o file.mp4] [--port] | Render to renders/ (uses the running Studio or starts one) |
devmotion record <tour.mjs> [-p dir] / --check | Record an app while a tour drives it; -p edits it into a project |
devmotion record init [url] | Write a starter tour that works on its first run |
devmotion beats <script.txt> -w <words.json> [-o take.json] | Place a narration's {beat} marks on a voice's timed words (offline) |
devmotion skill install [--global] / skill print | Give Claude Code, Codex or Cursor the DevMotion skill |
devmotion login / logout / whoami | Your devmotion.app account: generation credits and the cloud chat |
devmotion doctor | Check ffmpeg, Chromium, login, server compatibility and local agents |
| devmotion render --changed [-c config] | Render only stale/missing/modified targets, update the lock |
| devmotion render --all / --target a,b / --watch | Render every / chosen targets; re-render a target when it changes |
| devmotion check [-c config] | No render: exit 1 listing stale, missing or modified targets |
| devmotion lock [-c config] | Record the current outputs in the lock without rendering |
| devmotion studio -c config [--target id] | Open a target's project, or pick one of the config's projects |
The Studio listens on 127.0.0.1:4747 by default — one port, one project. Run a second
project with --port. In a repo with several projects (one per language, say), point the
Studio at the targets file: devmotion studio -c videos/devmotion.config.json lists its
projects and asks which one to open, and --target intro.it opens that target's project
directly (without a terminal to ask in, --target is required).
Renders and your plan
A render on your machine (devmotion render in every mode, the Studio's Export, the MCP
render_project tool) follows the plan of the account the CLI is logged in with, by the same
rule as an export on devmotion.app: without a plan that exports clean, the video carries the
DevMotion badge, and every plan caps the size at its export resolution. A larger project is
scaled down as it is, its layout untouched. Local renders are not counted and use no credits.
When a render carries the badge or was scaled down, devmotion render says so once and names
the plan that lifts it (devmotion.app/pricing); the Studio's
stage shows the badge the render will carry.
The plan is asked from devmotion.app when you render and remembered for a day
(~/.config/devmotion/plan.json), so an offline render keeps the last known plan. Not logged
in, or no answer and nothing remembered: the Free plan. In CI, set DEVMOTION_API_KEY to a key
of an account whose plan exports clean; without one, CI renders carry the badge. The account
plays no part in the render lock below: devmotion check gives the same answer with or without
a key.
Render targets and the lock
Declare outputs once in devmotion.config.json (paths relative to the file). Several targets
can share a project; a variant is a target with other values or range.
{
"version": 1,
"lock": "devmotion.lock.json",
"targets": {
"intro": {
"source": "videos/intro",
"values": { "headline": "Hello" },
"range": { "start": 0, "end": 8 },
"audio": false,
"encode": { "crf": 23, "maxCrf": 35, "maxBytes": 1572864 },
"output": "public/intro.mp4",
"poster": { "path": "public/intro.jpg", "time": 5 }
}
}
}
devmotion render --changed hashes every target and renders only those that need it. A target's
sourceHash is the sha256 over its project files (sorted; renders/, .devmotion/,
node_modules/ excluded), values, range, audio, encode, poster time and the renderer
version (CLI major.minor). devmotion.lock.json is committed:
{
"lockVersion": 1,
"renderer": "1.1",
"targets": {
"intro": {
"sourceHash": "<sha256>",
"outputs": { "public/intro.mp4": { "sha256": "<sha256>", "bytes": 123456 } }
}
}
}
A target is stale when its hash differs or it is not locked, missing when an output is gone, and
modified when an output's checksum differs from the lock. devmotion check runs in CI with no
renderer installed. After a minor CLI upgrade, run devmotion render --changed (or
devmotion lock if the outputs are unchanged).
Programmatic use: import { loadConfig, readLock, checkTargets, recordTarget, writeLock } from '@devmotion/cli'
(same as @devmotion/cli/lock).
Project folder
my-video/
├── project.json # the project (the same JSON devmotion.app stores)
├── scenes/<layerId>/ # HTML / CSS / script sources of scripted scenes
├── assets/ # your media, referenced as /local-assets/<path>
├── renders/ # rendered MP4s
└── .devmotion/ # local state (chat history, cloud cache) — not versioned
Edit in the Studio, with an agent, or by hand: the Studio reloads when files change. Scene
sources are plain files (index.html, style.css, script-<n>.js), referenced from
project.json as { "$file": "scenes/<layerId>/index.html" }. A source keeps its file when the
Studio saves, whatever it is called.
Shared folders
Files several projects use (a kit, a scene's logic, sound effects) live once, outside any project. A project names the folders it uses, relative to its own folder:
{
"shared": { "kit": "../../kit", "scene": "../shared" },
"layers": [
{
"type": "html",
"props": {
"scripts": [
{ "src": "/local-shared/kit/world.js" },
{ "src": "/local-shared/scene/scene.js" }
]
}
},
{ "type": "audio", "props": { "src": "/local-shared/kit/sfx/tick.wav" } }
]
}
The Studio serves them at /local-shared/<name>/<path> and reloads when they change, a render
reaches them from its staged copy, and the render lock hashes their files with the project's own:
change the kit and every project using it is stale. One project per language, sharing the scene,
renders every language with its own voice, timing and length.
Editor support
init points project.json at the JSON Schema of the CLI version that created it:
{ "$schema": "https://unpkg.com/@devmotion/cli@<version>/schema/project.schema.json" }
It is generated from the Studio's own schemas, layer props per type included, so an editor
validates and completes the file. devmotion.config.json has one too:
https://unpkg.com/@devmotion/cli@<version>/schema/config.schema.json.
Record an app

devmotion record records a real app while a script drives it, the way Screen Studio records
a person: a website, an Electron app (VS Code, Cursor, Slack…) or a desktop app, captured by the
operating system at 60 fps with a soft physical pointer, plus a track of every move, click and
key.
// my-app.tour.mjs
export default {
app: { kind: 'browser', url: 'https://example.com' },
async run(D) {
await D.click('text=Get started');
await D.click({ describe: 'The email field of the sign-up form' });
await D.type('[email protected]');
await D.key('Meta+Enter');
}
};
devmotion record --check # permissions, helpers and login on this machine
devmotion record init https://example.com # a starter tour: example.tour.mjs
devmotion record my-app.tour.mjs # recordings/my-app.mp4 + my-app.track.json
devmotion record my-app.tour.mjs -p demo # … edited into the project ./demo
record init [url] writes a tour that works on its first take: it waits, scrolls down and
back, and clicks the first link of the page's header, with selectors only. --name <name>
names the file (<name>.tour.mjs, from the site by default), --app electron (VS Code by
default) or --app native (Calculator by default) starts an app's tour instead. It never
overwrites a file, and prints the command that records the tour.
The director: D.move, D.click, D.type, D.key, D.scroll, D.drag, D.show (a moment
worth a closer look), D.skip(work) (cuts a slow step out of the video), D.at / D.wait
(beats). A target is a selector, a point, or a description of what to click.
--project <dir> lands the take in a project already edited (the folder is created like
devmotion init when it has no project): an automatic camera that closes in on each click,
typed-in field and shown target, then pulls back; a ring on each click; a chip for each
keyboard shortcut (⌘⇧P); a Mouse layer when the video has no pointer. All of it is plain
layers and keyframes you can change in the Studio. Record the same tour again and its take is
replaced in place, the rest of the project untouched: a new release needs only a new take.
Tune it in the tour:
edit: { camera: { maxZoom: 1.6, hold: 1.2 }, ripple: true, keystrokes: 'shortcuts' }
// camera: false for the whole screen throughout; keystrokes: 'all' | 'none'
Narrated takes: devmotion beats. To time a tour on a voice-over, mark its beats in the
script with {name} right before a word, and give beats the voice's timed words: the Studio's
Transcribe Audio action (action_transcribe_audio, { text, language, words: [{ word, start, end }] }) or [word, start, end] triples. It runs offline and writes the take:
devmotion beats script.txt --words transcription.json --out take.json
# script.txt: # lines are notes, [tags] and *accents* are ignored, {end} is reserved
[warm] {open}Open the *editor*. {render}Then render it.
{ "words": [["Open", 0.1, 0.4], …], "beats": { "open": 0.1, "render": 1.4, "end": 2.3 } }
Each beat is the start of its word in seconds of the voice, end the end of the last word (an
accented word carries a fourth element, 1). Words match across case, punctuation and small
numbers (2 / two); a beat whose word the voice does not say fails the command, named. In
the tour, await D.at(take.beats.render) waits for that beat.
macOS 14+ (Screen Recording permission; Accessibility to drive desktop apps); Linux X11 is
experimental and untested. Targets described in words (and every desktop app's) are found on
screenshots by DevMotion and use your AI credits: log in first (devmotion login). Before a
take that uses them, record prints the price and your balance, and stops when you are not
logged in or out of credits. Selectors and points are free and need no account.
Agents (MCP and the skill)
The Studio serves the DevMotion MCP server at http://127.0.0.1:4747/mcp: the same tools as
devmotion.app (get_design_guide, get_project, upsert_layer, write_layer_source,
instantiate_block, inspect_frames, action_*…), writing to the project folder. Keep the
Studio running while the agent works. devmotion init writes the MCP config for Claude Code
(.mcp.json) and Codex (.codex/config.toml), plus the DevMotion skill.
The skill tells an agent how to use the CLI: the project folder, the Studio and its MCP tools,
rendering and render targets, record, the account, and where the full docs are. Install it in
any repository, or for every project:
devmotion skill install # this folder, for Claude Code, Codex and Cursor
devmotion skill install --global # every project of this user (your home folder)
devmotion skill install --agent cursor # one agent: claude, codex or cursor
devmotion skill print # print it, for any other agent
By default it writes .claude/skills/devmotion/SKILL.md (Claude Code) and
.agents/skills/devmotion/SKILL.md (Codex; Cursor reads both); --agent cursor alone writes
.cursor/skills/devmotion/SKILL.md. Run it again after an update. The same file is served at
https://devmotion.app/skills/devmotion/SKILL.md for an agent told to read it.
Cloud features
devmotion login opens devmotion.app to create an API key for this machine (stored in
~/.config/devmotion/credentials.json). Then everything generative in the Studio and in the
MCP action_* tools runs on devmotion.app with your plan's credits; inputs are uploaded once
and results are downloaded into assets/generated/. Your project stays local.
Environment overrides: DEVMOTION_API_KEY (use this key) and DEVMOTION_API_URL (another
DevMotion server).
Chat
The chat panel offers the DevMotion cloud models (needs login) and, when installed and logged in on your machine, Claude Code and Codex: the Studio runs them against its own MCP server, and the editor follows their edits live.
License
Proprietary and closed source: use is subject to the DevMotion Terms of Service. See LICENSE; third-party licenses in THIRD_PARTY_LICENSES.txt.