# @rindle/cli

The npm-installed Rindle toolchain: the rindle CLI, the supervised local rindled daemon, SQL migrations, generated TypeScript schema, and remote daemon commands.

`@rindle/cli` is the JS/TS developer toolchain for Rindle. It installs the
`rindle` CLI and the matching `rindled` daemon binary as prebuilt, per-platform
npm artifacts - no Rust toolchain required for app development.

Use it for four jobs:

- scaffold a local topology with `rindle init`;
- render it and supervise the local write-master + follower pair with `rindle up`;
- launch app processes with stable bindings derived from the topology using `rindle exec`;
- apply SQL migrations with `rindle migrate`;
- generate `@rindle/client` TypeScript schema with `rindle schema gen`.

## Install

```bash
pnpm add -D @rindle/cli
# or npm i -D @rindle/cli
```

Run it through the package manager's bin resolution:

```bash
npx rindle --help
pnpm exec rindle status
```

The package installs exactly the platform binary it needs through optional
dependencies. The `rindle` CLI and `rindled` daemon binary are versioned together
so `rindle up` can always find and supervise the daemon it was released with.

### Supported platforms

Prebuilt binaries ship for **macOS** (Apple Silicon + Intel) and **Linux** (x86_64 +
arm64, glibc + musl).

**Windows: use WSL2.** There is no native Windows build — Rindle's storage engine is
Linux-only — and installing on Windows fails fast with a message pointing here rather
than silently half-working. WSL2 runs a real Linux kernel, so the normal Linux binaries
and the whole `rindle up` loop work unmodified inside your distro. Your app and browser
stay on Windows: WSL2 forwards `localhost`, so a dev server started in the distro is
reachable from a Windows browser, and the browser tier is wasm and doesn't care what OS
it runs on.

One rule that matters:

> **Keep the database on the WSL filesystem (`~/`), never under `/mnt/c`.** Windows
> drives are exposed to WSL through a translation layer that can't reliably back a
> memory-mapped database — the same reason SQLite shouldn't live on a network share.
> Your source tree can sit wherever you like; the data directory can't. It'll also be
> dramatically faster on the Linux side.

WSL1 is not supported: it emulates the memory-mapping and file-locking calls the engine
depends on, rather than implementing them.

## Local dev loop

Most apps use one long-running command for the data-tier side of development:

```bash
npx rindle init
npx rindle up --migrate --gen shared/schema.gen.ts --watch
npx rindle exec -- vite dev
```

`rindle up` renders your `rindle.ncl`, starts the write-master + follower + local fleet edge,
waits for it to be ready, applies pending `migrations/*.sql` to the master, generates
`shared/schema.gen.ts` from the follower's live schema, and repeats apply/generate
whenever the migrations directory changes.

Keep your app server and frontend dev server in a separate process. `rindle up` owns the local
fleet, migrations, and schema generation; `rindle exec -- <command>` evaluates the same topology
and injects its stable read/write/ws bindings into your web framework process.

## Migrations and schema generation

Under `rindle up`, migrations apply to the write-master and the schema regenerates from
a follower automatically. To run the steps directly, point `migrate` at the write-master
(`:7611` locally) and `schema gen` at a follower (`:7600`):

```bash
npx rindle migrate create init
npx rindle migrate apply  --url http://127.0.0.1:7611          # the write-master
npx rindle migrate status --url http://127.0.0.1:7611
npx rindle schema gen     --url http://127.0.0.1:7600 --out shared/schema.gen.ts   # a follower
```

Or point them at a remote deployment — migrations at the write-master, schema at a
follower's control plane:

```bash
npx rindle migrate apply --url https://YOUR-WRITE-MASTER --token "$RINDLE_DAEMON_TOKEN"

npx rindle schema gen \
  --url https://YOUR-APP:8443 \
  --token "$RINDLE_DAEMON_TOKEN" \
  --out shared/schema.gen.ts
```

The daemon token is server/operator-only. It should never be shipped to the
browser; browser clients subscribe through the public WebSocket using leases
minted by your API server.

## `rindle up` vs. `rindled`

`rindled` is the read-follower daemon; the `rindle-replicator` write-master feeds it.
`rindle up` is the local-development supervisor that renders your `rindle.ncl`, starts
the pair, watches it, applies migrations to the master, and generates schema from the
follower.

For application development, prefer `rindle up`. For production, run the pair under your
real process supervisor or use Rindle Cloud:

- local dev: `npx rindle up --migrate --gen shared/schema.gen.ts --watch`;
- self-hosted production: run the write-master + follower from release artifacts,
  container images, or your own supervisor, then use
  `rindle migrate apply --url <write-master> --token ...`;
- managed production: provision Rindle Cloud and run the same `migrate` /
  `schema gen` commands against its endpoints.

## Command reference

Every command talks to a daemon's control plane at `--url` (except `init`, `up`,
and the local `migrate list`/`create`). Aliases in parentheses.

| Command | What it does |
| --- | --- |
| `status` · `stats` | up/down + commit + boot id + the live counters; repaint with `--watch` |
| `version` | liveness + deployed commit (no token needed) |
| `health` / `ready` | liveness (+ auth) probe with a meaningful exit code — for scripts and orchestrators |
| `schema [show]` | the deployed base-table shape |
| `schema gen` (`generate`) | emit the `@rindle/client` schema TS — `--out <file>` (default stdout), `--import-from <module>`, `--schema-const <name>` |
| `migrate apply` (`up`) | apply pending `*.sql` from the migrations directory |
| `migrate list` (`ls`) | list local migrations |
| `migrate create <name>` (`new`) | scaffold a new migration file |
| `migrate status` | diff local migrations vs. the daemon's applied journal |
| `init` | scaffold `rindle.ncl` (the colocated pair) + `migrations/` (`--dir .`) |
| `up` | render `rindle.ncl` + supervise the local write-master + follower(s) + fleet edge — `--migrate` (to the master), `--gen <out>` (from a follower), `--watch` |
| `exec -- <command>` | launch an app command with `RINDLE_DAEMON_URL`, `RINDLE_REPLICATOR_URL`, and the public fleet ws derived from `rindle.ncl`; inherited secrets are untouched |
| `ps` · `stop` (`kill`) | list / stop running rindle processes (`stop --all`, or by pid) |
| `restart` | bounce a supervised daemon and wait for it back (`--no-wait` to skip the wait) |
| `login` · `logout` · `whoami` | Rindle Cloud auth (browser device flow; `$RINDLE_CLOUD_TOKEN` overrides for CI) |

`rindle --help` prints the full list, including the fleet/topology and recovery
commands (`render`, `dematerialize`, `re-bootstrap`).

## Flags & environment

Precedence is always **flag → environment variable → default**.

| Flag | Env var | Default | Meaning |
| --- | --- | --- | --- |
| `--url <url>` | `RINDLE_DAEMON_URL` (`RINDLE_REPLICATOR_URL` is preferred by `migrate`) | `http://127.0.0.1:7600` | the control plane commands talk to; `rindle exec` derives both local roles |
| `--token <bearer>` | `RINDLE_DAEMON_TOKEN` | *(none)* | bearer token for the control plane |
| `--dir <path>` | `RINDLE_MIGRATIONS_DIR` | `migrations` | the migrations directory (`migrate *`) |
| `--out <file>` | — | stdout | where `schema gen` writes |
| `--gen <out>` | — | — | (`up`) regenerate the schema TS to `<out>` after `--migrate` — **always takes the path** |
| `--json` | — | off | machine-readable output |
| `--watch`, `-w` | — | off | repaint on an interval (`status`/`stats`); under `up`, re-apply/regen on `migrations/` change |
| `--interval <s>` | — | `2` | the `--watch` repaint interval |
| `--timeout <s>` | — | `5` | per-request timeout |
| `--daemon-bin <p>` | `RINDLE_DAEMON_BIN` | sibling of `rindle`, then `$PATH` | (`up`) explicit path to the `rindled` binary |
| — | `RINDLE_REPLICATOR_BIN` | sibling of `rindle`, then `$PATH` | (`up`) explicit path to the write-master binary |
| — | `RINDLE_DEV_EDGE_BIN` | sibling of `rindle`, then `$PATH` | (`up`) explicit path to the native local fleet-edge binary |
| — | `RINDLE_BIN_DIR` | the platform npm package | (npm wrapper) a directory of locally built binaries to use instead of the prebuilt ones |

## Supervise from Node

If you need to supervise the npm-installed daemon from Node, import the package
helpers:

```ts
import { rindledBinaryPath, spawnRindled } from "@rindle/cli";

const child = spawnRindled(["--config", "./follower.json"]);
console.log(rindledBinaryPath());
```

## Package scripts

If you want a package script, make it forward to the CLI:

```json
{
  "scripts": {
    "rindle": "rindle",
    "dev:daemon": "rindle up --migrate --gen shared/schema.gen.ts --watch",
    "dev:web": "rindle exec -- vite dev"
  }
}
```

Then run:

```bash
npm run dev:daemon
# or
npm run rindle -- status
```

## Next steps

- [Scaffold with create-rindle](/docs/create-rindle) - a TanStack Start app that
  uses this toolchain for its dev loop.
- [Schema & migrations](/docs/schema) - the SQL-first migration and schema-gen
  workflow.
- [Run the daemon](/docs/daemon) - the daemon's config, two network planes, and
  restart behavior.
- [Synced-app quickstart](/docs/synced-app-quickstart) - the manual app setup
  that uses `@rindle/cli` directly.

---

[View this page on Rindle](https://rindle.sh/docs/rindle-cli)
