API reference
The CLI, the Vite plugin's options, the HTTP API every Retake server answers under /__retake/, and the runtime API inside your app's frame.
CLI
Installed as retake and retake-dev. With npx, use npx retake-dev.
| Command | Does |
|---|---|
retake <project> | Runs the project's dev server with the timeline: Vite apps through the plugin, frameworks behind the front server |
retake dev <project> | The same |
retake http://localhost:3000 | Puts the timeline in front of a dev server that's already running |
retake -- <dev command> | Runs that command with the timeline in front (retake -- next dev, retake -- pnpm dev) |
retake init | Prints the lines for vite.config or Next's proxy.ts |
retake mcp | The MCP server (stdio) |
retake code … | Each timeline's code, from a terminal (below) |
| Option | Does |
|---|---|
--port <n> | Port to serve on. Default 3014. For mcp: the server at http://localhost:<n> |
--root <dir> | Where .retake/ goes. Default: the project, or the current folder |
--code-timelines | Each timeline keeps its own code: switching to one puts its code on disk (the newest goes back when Retake stops). --code-branches is the old name |
--no-code-timelines | Every timeline shares the files. Without either flag the dock asks the first time it matters |
--verbose | Logs every request the front server handles to .retake/front.log |
--url <url> | For mcp: the Retake server to talk to |
--branch <name> | For retake code export: the branch name (default retake/timeline-<id>) |
-- … | After a project: arguments for its dev server (retake . -- --host). Without one: the dev command |
-v | --version: prints the version |
-h | --help: prints the usage |
Environment: RETAKE_URL is --url for mcp. RETAKE_CACHE_DIR moves the wrapper config and Vite cache Retake keeps for a Vite app (default $TMPDIR/retake/). PORT is given to the dev command Retake starts. RETAKE_CODE_TIMELINES is for Next's proxy.ts / middleware.ts, where there are no flags: 1 separate code, 0 shared, unset the dock asks.
Code timelines
Each timeline can keep its own code (how it works). Turn it on or off with the dock's prompt, a lane's right-click menu, or:
| Where | On | Off |
|---|---|---|
| CLI | --code-timelines | --no-code-timelines |
| Vite plugin | retake({ codeTimelines: true }) | retake({ codeTimelines: false }) |
Next's proxy.ts | RETAKE_CODE_TIMELINES=1 next dev | RETAKE_CODE_TIMELINES=0 |
Left unset, the dock asks once, the first time the code changes with two timelines; the answer is kept in .retake/settings.json.
retake code works with or without a dev server running (through its HTTP API when one is, else on .retake/ directly). Run it in the project, or pass --root.
| Command | Does |
|---|---|
retake code status | Each timeline's code version and the files it changed since it started, and whose code is on disk. list is the same |
retake code checkout <timeline> | Puts that timeline's code on disk (by id or name) |
retake code restore [version] | Puts the newest code back on disk, or a given version |
retake code export <timeline> | Makes a git branch of that timeline's code, on top of HEAD (--branch <name>). Your working tree, index and HEAD stay as they are |
Vite plugin
retake(options) returns the plugins for vite dev; it does nothing in vite build.
import { defineConfig } from "vite"
import { retake } from "retake-dev"
export default defineConfig({
plugins: [
retake({
codeTimelines: "ask", // each timeline keeps its own code (true rewrites files when you switch)
banner: true, // the "Retake timeline docked at …" line
}),
],
})| Option | Type | Default | Does |
|---|---|---|---|
enabled | boolean | true | false turns the plugin off; pages are served as they are |
codeTimelines | boolean | "ask" | "ask" | Each timeline keeps its own code; switching to one puts its code on disk. "ask": the dock asks the first time it matters (codeBranches: true is the old name) |
token | string | random per start | The token mutating /__retake/ requests must carry |
root | string | Vite's root | Where .retake/ goes |
banner | boolean | true | false hides the startup line |
Types ship with the package (RetakeOptions from retake-dev). The plugin turns on Vite's CSS source maps in dev unless you set them, so notes can point at CSS lines.
Server building blocks
For serving the dock from your own server (Retake's site does this on Vercel). All four come from retake-dev:
| Export | Returns |
|---|---|
shellHtml(options?) | The dock page: a full HTML document with the app in a frame. Options: marker ("url" or "header"), appPage, docs, server (false: no Retake server, keep the session in memory), token, codeBranches |
runtimeTag(options?) | The <script data-retake> tag to put first in the framed page. It removes itself and runs only in a frame the dock made. Options: rt, nonce, script |
runtimeSource(rt?) | The time runtime as one script. rt: marker, bootAt ("dcl" or "load"), exemptUrls, holdScripts, next, docId, docStored |
injectHtml(tag, { encoding? }) | A stream transform that inserts tag after <head> as the HTML streams, decoding gzip, br, deflate or zstd first |
Session HTTP API
Every Retake server (the plugin and the front server) answers these under /__retake/. Timelines, recordings and notes are kept in .retake/.
| Route | Does |
|---|---|
GET /__retake/session | { branches, activeId, markers, notes }. Each branch: { id, parentId, forkAt, name, codeVersion } |
PUT /__retake/session | Replaces the session; recordings of timelines it no longer has are deleted |
GET /__retake/recording/:id | A timeline's recording (JSON; gzipped on disk) |
PUT /__retake/recording/:id | Saves a timeline's recording |
DELETE /__retake/recording/:id | Deletes it |
GET /__retake/notes | Every note |
GET /__retake/notes/:id | One note (404 if there's none) |
PATCH /__retake/notes/:id | { status?, reply? }: status is pending, acknowledged, resolved or dismissed; reply a string (from the agent, or the user with header x-retake-from: user). Returns the note |
GET /__retake/events | Server-sent events: session, note-updated, active-changed, code-version |
GET /__retake/health | Front server only: 200 once the dev server behind it has answered, 503 before |
GET /__retake/code | Each timeline's code: { enabled, checkedOut, disk, newest, timelines: { [id]: { fork, head, changed, files } } } |
POST /__retake/code/checkout | { branchId, force? }: puts a timeline's code on disk (with code timelines on). Returns the files that changed |
GET /__retake/code/diff?branch=&against= | The files a timeline changed since it started (or against another timeline's id), and a unified diff |
POST /__retake/code/enabled | { on }: separate code per timeline, or shared |
POST /__retake/code/restore | { version? }: puts that version (default: the newest) back on disk |
POST /__retake/code/resume | Resumes swapping after a git branch switch paused it |
The token
Every request that changes something needs the header x-retake-token. Reads don't. The token is random for each server start (or the plugin's token option). It's in .retake/server.json ({ url, base, token, pid }, readable only by you) and in the dock page as window.__RETAKE_TOKEN.
TOKEN=$(node -p 'require("./.retake/server.json").token')
curl -X PATCH http://localhost:3014/__retake/notes/nmg3k2x1a9f \
-H "x-retake-token: $TOKEN" \
-H "content-type: application/json" \
-d '{"status":"resolved","reply":"Eased it out, 60ms delay."}'Recordings hold what you typed and what your API answered. The front server only answers on localhost. With the plugin and vite --host, anyone on your network can read the session.
window.__retake
Inside the dock's frame your app can read Retake's clock and timeline. In any other page or frame it's { inert: true }, and without Retake it's undefined. Types: add retake-dev/client to compilerOptions.types, or reference it.
/// <reference types="retake-dev/client" />
const rt = window.__retake
if (rt && !rt.inert) {
// Running in Retake's frame.
const off = rt.onPlayState(({ playing, now }) => {
console.log(playing ? "playing" : "paused", "at", now, "ms")
})
const { markers, clips } = rt.timeline()
}| Member | Returns |
|---|---|
version | The runtime's version |
now() | The virtual clock, in ms |
timeline() | { now, end, viewport: { w, h }, markers, clips, activity } |
clipsFor(elementOrSelector) | Animation clips on that element and its descendants, looping ones included |
clipAt(t, selector?) | The latest clip running at t, as { clip, offset } (ms into it), or null |
isInteractive() | True only while playing at the live edge |
isPaused() | Paused anywhere |
onPlayState(fn) | fn({ playing, now }) on every play or pause; returns an unsubscribe |
play() | Plays from here: replays the recorded future, then carries on live |
pause() | Pauses |
record() | Starts the timeline, or resumes recording from its end |
seek(t, play?) | Goes to moment t; false before anything was recorded |
setToolActive(on) | The dock tells the runtime a comment or select tool is on |
timeline()
markers: your actions only,{ t, end?, kind, label, selector? }withkindone of click, submit, route, focus or type. Atypemarker is one burst of typing (gaps under 800ms).clips: animations,{ id, start, end, kind, label, selector, component?, property?, path, iterations?, pseudoElement? }withkindtransition, css-animation or waapi;endisnullwhile running.activity: samples at 10 Hz,{ t, v }, wherev(0 to 1) is how much of the viewport changed.
Room for the dock
Your app fills the window and the dock sits over its bottom edge. In the dock's frame, window.__retakeDockHeight is how many px it covers (0 when it's folded away), and a retake:dock event on window (detail: { height }) says when that changes.
const room = (h: number) => document.body.style.setProperty("padding-bottom", `${h}px`)
room(window.__retakeDockHeight ?? 0)
window.addEventListener("retake:dock", (e) => room(e.detail.height))