Retake1,311 downloads
GitHub
npx retake-dev .

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.

CommandDoes
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:3000Puts 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 initPrints the lines for vite.config or Next's proxy.ts
retake mcpThe MCP server (stdio)
retake code …Each timeline's code, from a terminal (below)
OptionDoes
--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-timelinesEach 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-timelinesEvery timeline shares the files. Without either flag the dock asks the first time it matters
--verboseLogs 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:

WhereOnOff
CLI--code-timelines--no-code-timelines
Vite pluginretake({ codeTimelines: true })retake({ codeTimelines: false })
Next's proxy.tsRETAKE_CODE_TIMELINES=1 next devRETAKE_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.

CommandDoes
retake code statusEach 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.

vite.config.ts
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
    }),
  ],
})
OptionTypeDefaultDoes
enabledbooleantruefalse turns the plugin off; pages are served as they are
codeTimelinesboolean | "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)
tokenstringrandom per startThe token mutating /__retake/ requests must carry
rootstringVite's rootWhere .retake/ goes
bannerbooleantruefalse 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:

ExportReturns
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/.

RouteDoes
GET /__retake/session{ branches, activeId, markers, notes }. Each branch: { id, parentId, forkAt, name, codeVersion }
PUT /__retake/sessionReplaces the session; recordings of timelines it no longer has are deleted
GET /__retake/recording/:idA timeline's recording (JSON; gzipped on disk)
PUT /__retake/recording/:idSaves a timeline's recording
DELETE /__retake/recording/:idDeletes it
GET /__retake/notesEvery note
GET /__retake/notes/:idOne 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/eventsServer-sent events: session, note-updated, active-changed, code-version
GET /__retake/healthFront server only: 200 once the dev server behind it has answered, 503 before
GET /__retake/codeEach 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/resumeResumes 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.

Resolve a note from a script
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.

app.ts
/// <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()
}
MemberReturns
versionThe 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? } with kind one of click, submit, route, focus or type. A type marker is one burst of typing (gaps under 800ms).
  • clips: animations, { id, start, end, kind, label, selector, component?, property?, path, iterations?, pseudoElement? } with kind transition, css-animation or waapi; end is null while running.
  • activity: samples at 10 Hz, { t, v }, where v (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.

app.ts
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))