Envelope

Run it locally

To bring existing data into a seed, sign in as the owner and use its import screen. First step: open this path on the destination app:

/app/import

Upload a supported source export, review the plan and its differences, then approve that exact plan to apply it. Reconcile the result against native data; use Undo from the same history when needed. Base's reference accepts people JSON here, not installation .rer archives. Editions adopt this same door with their own supported readers and native adapters.

Start a published seed with one line: npx -y @runeditrun/rer dev just-every/<repo>, for example npx -y @runeditrun/rer dev just-every/run-inkwell. It clones the seed into ./<repo>, installs its dependencies, sets up a local owner and starts the app. The clone has every commit and release tag (file contents arrive as a checkout needs them), so git checkout <tag> adopts an older release. To set up without starting, use npx -y @runeditrun/rer setup just-every/<repo>. Without Node 22 or later, the RER installer runs the same line: curl -fsSL https://runeditrun.com/install.sh | sh -s -- dev just-every/<repo>. It installs rer in ~/.local/bin with its own Node and edits no shell profile: when ~/.local/bin is not on PATH for new terminals, its install step ends with the one command that adds it for your shell (for zsh: echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && export PATH="$HOME/.local/bin:$PATH"); run it once. To start the seed again later, run cd <repo> && rer dev (the folder is the repository name). npm install -g @runeditrun/rer is another way to install rer. The installer's Node stays private to rer: it puts no node, npm or npx on PATH. First⁠-⁠party commands fetch and verify the exact pnpm version declared in package.json when it is missing, using $HOME/.rer/pnpm; no global pnpm installation or sudo is needed. That pnpm stays off PATH, so where a project's docs say pnpm run X -- ARGS, run rer run X -- ARGS: the same script, with the pnpm and Node the project pins. rer run alone lists the scripts. Package⁠-⁠mode seeds pin an exact @runeditrun/rer devDependency. The checkout's OPERATIONS.json argv and package.json aliases select the version that rer VERB runs.

Launch with one command

From a runnable edition's root, rer dev is the whole launch. It installs locked dependencies when they are missing or older than the lockfile. On a checkout that is not set up it sets up a local owner (owner@<checkout-name>.test) and fills the setup answers the seed declares a localDefault for in rer-setup.json. Then it starts the app and prints the RER_READY lines. It never prompts and never re⁠-⁠runs setup on a checkout that is set up. A required setup answer with no value and no localDefault stops it with exit 3 and a message naming the field.

Install for a real owner

To install with a real owner mailbox instead, run the verbs yourself from the runnable edition's root: install locked dependencies, prepare its local owner, then start development:

rer install
rer setup --owner-email you@example.com
rer dev

rer install installs the locked dependencies only. The Playwright browser a seed's tests use downloads the first time a command needs it (rer verify, or rer run of a script that runs Playwright), so an owner who only deploys never fetches it. rer setup ends by listing what a hosted deploy will still need, read from the deploy declaration: the Cloudflare account ID and API token (with the permissions, or the project's deploy guide), each required secret not yet set, and production sign⁠-⁠in's mail inputs.

Commands that run project work select the exact Node version declared by the project. They use an exact matching installed runtime or a checksum⁠-⁠verified copy cached at ~/.rer/node/node-vVERSION-PLATFORM-ARCH/; when needed on supported platforms, the runner retrieves the official Node release and checks its SHA⁠-⁠256 against SHASUMS256.txt. It does not change global Node installations. Set RER_NODE_AUTO=0 to disable automatic selection. If selection is disabled or acquisition fails, the command reports the required runtime and repair action. Resolve that runtime requirement, then repeat the same rer verb. Help, check, describe, doctor and stop do not select or acquire Node. A runtime that has just verified also drops its stale .quarantine- copies and Node's unused include/ headers, which saves about 250 MB per Node release; rer doctor lists what it could not remove. rer doctor also warns, without failing, about Node releases, workerd versions and pnpm pins that checkouts running on this computer set differently (each distinct pin keeps its own copy), and about a checkout on another volume than your home folder, where pnpm cannot clone packages from its store.

After RER_READY, a seed with dev identity prints RER_SIGNED_IN EMAIL and RER_OPEN URL: open that URL on this computer and the app opens signed in as the owner, at /setup until first⁠-⁠run is finished. Legacy rer dev --login uses the same automatic sign⁠-⁠in. Treat RER_OPEN as a credential; do not share it. Until first⁠-⁠run completion is stored durably, app routes send the signed⁠-⁠in owner to /setup; signed⁠-⁠out visitors sign in and return there. Once complete, /setup opens the app or settings.

Invite another person from the app's operator access screen, then choose Sign in as <name> beside that person. The same action appears at /signin; it accepts their current invitation and opens /app with their assigned role. After opening RER_OPEN, this browser can switch people without email. A fresh browser profile first opens RER_OPEN to receive this checkout's key cookie. rer dev --as person@example.com also selects an invited or active person, including one invited at a built⁠-⁠in persona's address (the persona name selects them too); for an email that is no one in the local database, it lists the people it takes. Local dev and demos silently suppress mail; there is no mail⁠-⁠reading step. Cancelled or expired invitations and inactive people cannot use these actions.

The saved local port is used; a first setup saves the first free port from 8787. Explicit ports are decimal integers from 1 through 65535 and must be free; a busy requested port fails instead of silently moving. Only 127.0.0.1, localhost and ::1 are allowed. Dev applies the selected host and port to both the listener and runtime bindings; readiness is bounded and failure stops the owned processes. Sign⁠-⁠in and dev⁠-⁠key cookies are named for the port, so checkouts open side by side on different ports in one browser each keep their own sign⁠-⁠in.

A first rer setup without --port saves the first free port from 8787 and prints it in one line; it never saves a port another process listens on, on any address. To move a checkout whose saved port is held by another checkout (or by nothing), run rer setup --port N in it: only the saved port changes, its data is kept, no reset is needed, and setup prints the old and new port. A refusal for a busy port names the process or checkout that holds it and gives that exact command with a free port.

Heavy work (install, setup, build, verify) waits in a machine⁠-⁠wide gate in ~/.rer/jobs when 3 jobs are running or available memory is at or below 2 GiB. Available memory is what the OS can reclaim (macOS: free, inactive, speculative and purgeable pages from vm_stat; Linux: MemAvailable), not the small "free" counter. A waiting message gives the queue position, the jobs ahead with their checkout paths and the available memory against the floor. Change the floor with RER_JOBS_MIN_FREE_MEMORY_BYTES (bytes; 0 turns it off). rer dev and rer preview never wait behind other checkouts' jobs; they start unless available memory is below their own floor, described next.

Missing non⁠-⁠interactive setup answers exit 3 and name the manifest fields. Read rer-setup.json and pass a private, mode⁠-⁠0600 JSON object keyed by those names using rer setup --answers PATH --non-interactive, for example {"OWNER_EMAIL":"owner@example.com"}. Secret values stay in private inputs and never go in command arguments. A setup rerun with the saved owner and origin preserves existing owner/data and exact private settings. Conflicting saved identity or origin reports an actionable recovery path. Review reset's exact removal inventory before confirmation. If data must be kept, back up this installation first; restore that archive only into an empty destination. Rows the app's own migrations insert, such as default settings, do not count: the archive's rows replace them. Any other row refuses the import, naming its table.

Queue waits report their reason and live position while the command waits. Package mode has no queue status command.

For the real owner mailbox, rer setup can prompt instead of receiving --owner-email on the command line. Setup builds the app, applies local migrations, prepares the first owner, requests an ordinary sign⁠-⁠in message and stops its temporary app before it exits. Existing owner data and private settings are preserved on a repeated setup.

Dev starts the real local app with assets and HMR and prints RER_READY URL after the application health check. With dev identity it then prints RER_SIGNED_IN EMAIL and RER_OPEN URL. The RER_OPEN URL carries this checkout's dev key in its fragment: opening it signs the browser in through a real dev session, and the key is removed from the address bar and kept only as a host⁠-⁠only cookie. Treat that line as a credential and open it only on this computer. The app name and browser title identify Dev mode. rer dev --as PERSONA starts as another persona or signed-out. Legacy rer dev --login uses the same automatic sign⁠-⁠in. Captured email and its browser UI have been retired.

Dev and preview are managed servers. In a terminal the first invocation stays active while serving; without one, rer dev returns once the app is ready and leaves it running (see the launch section above). Use rer dev --stop or rer preview --stop to stop them; --idle-timeout SECONDS sets inactivity cleanup. Compatible repeated starts reuse the registered server, print readiness and exit. Preview serves fixed production output; dev serves live source edits. Both retain the same owner and .wrangler/state.

Both also run the project's Cron Triggers on their schedule, read from the same files rer check reads for the deployed Worker (wrangler.jsonc, wrangler.toml, a deploy template such as deploy/wrangler.template.toml). Each run prints one RER_SCHEDULED TIME "CRON" ok MSms line. If two of those files declare different schedules the server refuses to start and names both files; make them the same.

A server stops after 12 hours (43200 seconds) without activity, so one left running overnight is still there in the morning. Start it with --idle-timeout SECONDS (or set RER_SERVER_IDLE_SECONDS) to change that.

Up to 10 dev or preview servers run on one computer, so several seeds can run side by side. At the limit a start is refused and the message lists the running servers with the command that stops each; start with RER_SERVER_LIMIT=N to allow more. Machine load alone never refuses a start or holds heavy work; a fleet that wants a load guard sets RER_SERVERS_LOAD_THRESHOLD=N and RER_JOBS_LOAD_THRESHOLD=N (the 1⁠-⁠minute load) in its own environment. When rer dev or rer setup holds a step 20 minutes for that guard, it stops with Not started: this computer is busy, the measured load, the threshold and the next step (run the same command again, or raise RER_JOBS_LOAD_THRESHOLD); it is never reported as a failed migration. A start needs 15 GiB free on the checkout and temporary volumes; RER_SERVERS_DISK_FLOOR_GIB=N changes that floor. A CI runner on a small disk may set it lower (for example 4); people should not.

A new server is refused only when available memory is below 1.5 GiB. An idle dev server holds about 0.5⁠-⁠1.3 GiB and a preview about 0.4⁠-⁠0.8 GiB. The message gives the available memory, the floor and the running servers with their stop commands; start with RER_SERVERS_MIN_MEMORY_BYTES=N (bytes; 0 turns it off) to start anyway. Reusing a checkout's running server is never refused for memory.

Back up and restore this installation

Use /app/import above for owner move⁠-⁠in. The low⁠-⁠level rer export and rer import commands below back up and restore this installation. They do not use the shared door's reviewed plan, reconciliation or Undo, and their .rer archive cannot be uploaded to that screen.

Reset is destructive local cleanup; preserve a backup of any data you need to keep before resetting. Archive export saves under exports/: normalized relative paths such as exports/x.rer and ./exports/x.rer, and absolute paths inside this checkout's exports/, are accepted. An absolute path outside the checkout is also accepted. Export refuses other paths in the checkout, .. segments, unsafe symlinks and any path served by the app. Archive restore requires an empty destination and --yes confirms the restore.

To back up and restore this installation locally, run these in order from the same checkout:

rer dev --stop
rer export --output exports/x.rer
rer reset --yes
rer import --input exports/x.rer --yes
rer setup --owner-email you@example.com
rer dev

Stop the running app so no operation can write during the backup. Export saves its domain data before reset removes the local installation. Reset makes the destination empty, as import requires. Import restores the saved domain data; setup then establishes the owner and private local configuration excluded from the archive. Start development to use the restored installation.

Other supported operations are rer verify, rer deploy and rer describe.

Use rer dev --stop or rer preview --stop (or Ctrl⁠-⁠C) before rer build, rer verify, rer reset and data transfer. Reset removes only the declared local inventory: local database/storage state, owner and sessions, CLI and installer bookkeeping, and generated private settings. It keeps source, dependencies, exports and deployment receipts. It refuses active operations and ambiguous or linked state.

Local export creates a private archive with an integrity receipt and explicit exclusions. Export, reset and import print only their own steps: Node's "SQLite is an experimental feature" notice and wrangler's local⁠-⁠only hints (such as "Use -⁠-⁠remote if you want to access the remote instance") are left out. They are local operations; nothing remote is read or changed. Archive restore validates the archive and restores into an empty destination. A large install takes many minutes (about 30 for 1.2 GB on a busy machine), so export and import print a line every few seconds: the first says roughly how long an archive of that size takes, then parts done and bytes, and the time left once two samples exist. An export builds every part before it writes any, because the archive opens with a manifest that holds the final part count. If an import stops part⁠-⁠way (the machine slept, or the process was killed), run the same command again: it removes the unfinished import's staged data, says so, and starts over. An unexpected failure names its error class and code, never row data, and says to run the same command again. After import, run setup to establish the owner and local configuration. Auth, private settings and transient runtime state are excluded. Remote transfer is unsupported by the maintained references and fails visibly.

Import defaults to the local copy. If you supply --origin URL, a local import accepts only HTTP on exactly localhost, 127.0.0.1 or [::1] at this checkout's configured local dev port. That is the saved port, or 8787 when no origin has been saved. Every HTTPS URL, other host (including a front⁠-⁠door NAME.localhost) or different port requires --target remote; without it, import refuses before reading the archive or preparing data. Leaving out --origin, or supplying a matching local origin, keeps the usual local restore behavior. --target remote still refuses when the adapter does not support complete remote import, as in the maintained references.

Deployment and command inspection

Real deployment uses the owner⁠-⁠account procedure and its private credentials. rer deploy --dry-run validates and builds locally, then reads the target secret bindings using CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN. It uses the same required⁠-⁠secret resolver as deploy: every missing binding and its input name is reported with the same nonzero exit and error, including in --json mode. Secrets held by the target are inherited; optional absent secrets stay unbound. Missing credentials or a refused secret⁠-⁠list read fail the dry run. It performs no provider writes, uploads, secret changes or resource creation. The default environment is production; select another with --environment NAME. The dry run does not establish upload acceptance, hosted health or mail delivery.

rer describe reads the current checkout's actual verbs, flags, aliases, pins, effects and local state without installing or running project commands. New verbs accept --help, --cwd PATH, --non-interactive and --json. Setup also accepts --owner-name, --port, --host, a private JSON --answers FILE and --door. Dev and preview accept --port, --host, --idle-timeout, and --stop; dev also --door and --direct. Deploy accepts --environment and --dry-run; export/import accept their target, file and restore options. rer check accepts --profile first-party. Heavy commands accept --no-queue only when the original caller has CI=true. Release retains its own compatibility flags.

Local durable state is .wrangler/state; CLI bookkeeping is .rer/local, installer state is .seed/installer, and the default private settings file is .dev.vars with mode 0600. Setup writes this checkout's dev key to .rer/local/dev-key.env (mode 0600); a repeat setup keeps it and reset removes it, which also ends every browser's earlier dev cookie. Dev sign⁠-⁠in, personas answer only a request holding that key's cookie, so a tunnel or local proxy to the app cannot reach them; anything else returns 404. Any local user or process that can read the checkout can read the key and sign in as the owner. rer deploy refuses the key, and rer build refuses output that contains the dev layer.

The first⁠-⁠party command profile is optional for independently authored projects. They may keep their own runtime, tools, installer, commands and provider choices. RER executes only their declared operations and does not impose Base pins, aliases or state layout.

The front door (opt⁠-⁠in)

With the installed rer command, one process on one port can serve every checkout on this computer at its own name, starting each on its first visit and stopping it when idle:

rer door enable            # port 8700, 10 servers, 12 hours idle
rer door add ~/review/*    # register checkouts; prints each URL

rer door enable --port N --server-limit N --idle-timeout S changes the policy. The door stores it with the RER_* variables in effect when you enable it, and every checkout it starts runs with exactly those, whoever starts the door later.

Each checkout is then at http://NAME.localhost:8700, where NAME is its folder name (a second cradle becomes cradle-2); http://localhost:8700 lists them with their state. Opening a stopped checkout's URL in a browser starts it (a page reloads once it is ready); images, scripts, API calls and WebSockets never start one. A checkout the door started stops after the idle time, and at the server limit the door stops its own least recently used one, never a server you started with rer dev; when it cannot make room, the page names what holds the limit. Each name is its own site, so sign⁠-⁠in cookies stay apart. A name stays with its checkout: rer door remove NAME stops serving it there but keeps the name reserved, and that checkout's next rer dev (or rer door add) registers it again. A checkout you move or rename keeps its name, address and sign⁠-⁠in: run rer dev (or rer door add) in its new folder. A copy of a checkout gets a name of its own.

rer door status shows the door and every checkout; --json gives the same as data and --links adds sign⁠-⁠in links that start a checkout and sign that browser in. While the door is enabled, rer dev sets a fresh checkout up at its door URL. An existing checkout moves with rer dev --door, which keeps its data and changes only its saved address; rer dev --direct moves it back, and rer dev --port N always serves directly at http://127.0.0.1:N. A checkout whose pinned Base predates the door stays direct: the door lists it and a visit to its door URL starts it and sends the browser to its own address.

Stay direct for a checkout that signs in through an external identity provider (Google, Microsoft and others reject *.localhost callback addresses). rer door restart drains and restarts the door: checkouts that were starting finish starting, and open tabs reconnect. After a reboot or logout the door needs rer door start (or any rer dev in a checkout that uses it); rer door stop and rer door disable stop it and opt this computer out.

If hosted deployment stops

Deploy and removal errors name the failing Cloudflare method and path (without credentials or query values), its error code and message when supplied, and the command that continues with the same environment. Keep .rer/deploy/ private: it records ownership so a repeat reuses resources safely, and .rer/deploy/<environment>.secrets.env keeps the secrets rer generated for that environment (session and link secrets), so a redeploy keeps sessions and emailed links working. To deploy the environment from another checkout, copy that file there. Without it, rer deploy refuses to replace secrets the live Worker holds; rer deploy --rotate-secret NAME replaces one on purpose.

provider-incomplete-pagination means Cloudflare advertised another list page but rer could not read a valid continuation, or returned a full page with no pagination information. A short list without result_info, a missing or empty terminal cursor, and a reached total_pages are complete. No resource is assumed absent from an incomplete list. Check the named call in Cloudflare, resolve the provider error or incomplete response, then rerun the printed command with the same account and environment. Do not delete the deployment record.

owner-claim-failed means the hosted owner bootstrap did not confirm a claim. The app may already be deployed while sign⁠-⁠in remains unavailable. The error names the observed bootstrap POST, HTTP status and application error code when available; a seed hook that fails before an exchange can only report its hook cause. Correct that cause (for example, owner authorization or a supplied setup token that differs from the deployed token), then rerun the same deploy. Leave a stale supplied setup token unset so rer can issue a fresh token. A completed claim retires the one⁠-⁠time token; deploying again keeps an existing owner.

rer deploy --environment NAME --remove --yes deletes the hosted install's Worker and its databases and files. Save the hosted data first: rer export saves only the local copy on this computer, and refuses --origin or --target remote (exit 2), saving nothing. Its refusal names the command that exports each hosted database, with the Cloudflare credentials that deployed it: npx wrangler d1 export DATABASE --remote --output exports/DATABASE.sql. Files the hosted install keeps in R2 are not in that export. Removal detaches the environment's queue consumers before deleting its queues, then confirms their absence in Cloudflare. It waits up to a minute for a deleted queue to disappear from the account list. If the provider still lists it, the error names the queue and the call; keep the record and rerun the printed removal command after resolving the cause.