Command line
Every mistri command and flag — the App framework’s — laid out from the help text the command
prints: what each does, and where its section is. npx mistri --help all
prints the same text, npx mistri --help the commands and common options
on one screen, and npx mistri <command> --help one command’s.
Browsing a database with no App is the database browser’s: its command line.
The first line of the help says it: mistri — the App framework: a few lines of TypeScript, a complete App
Commands
Section titled “Commands”| Command | What it does |
|---|---|
mistri --app <dir> |
serve an App (see –app below) |
mistri check --app <dir> |
types, typecheck and validation of an App (see below) |
mistri build --app <dir> |
make an App ready for production (see below) |
mistri new [<name>] [<dir>] |
a starter App with its own SQLite database: a task list in one app.ts, a schema and rows, for mistri dev |
mistri new --api <name> [<dir>] |
an App that is only an API: a resource, a command, its event, tokens and MCP; no pages |
mistri suite <suite.json> |
serve a local suite with per-App configuration; its paths are relative to the suite file’s folder |
mistri virtual <service.ts> |
run a typed virtual HTTP service for tests |
mistri flags --help |
explicit flag control-store provisioning |
mistri experiments --help |
explicit experiment evidence-store provisioning |
mistri analytics --help |
behavior-analytics store provisioning and funnels |
mistri shot --app <dir> [<route>…] |
an App’s pages as PNG files, to look at (see below) |
mistri grade [<app-dir>] |
an App’s pages measured against the rubric (see below) |
| `mistri theme show <look | .>` |
mistri schema --brief [<db>] |
the schema as one line per table, for an agent’s context |
mistri explain <check-code> |
what a check code means, with a fix example |
mistri messages extract [--app <dir>] [--locale <tag>] |
an App’s words for a translator: a catalog’s missing keys appended as comments, or its literal captions |
mistri migrate <command> |
change the schema with versioned SQL files (see below) |
mistri seed [--app <dir>] |
believable rows in an App’s writable database (see below) |
mistri dev [<app-dir>] |
serve an App with a dev secret and a sign-in per role (see below) |
mistri test init [<app-dir>] |
a starter suite: a journey per role, a permissions matrix (see below) |
mistri flows start <resource> |
start a resource’s flows on records already there (see below) |
mistri jobs run <name> |
run one of an App’s jobs at times now, once (see below) |
mistri decide eval <name> |
measure a decision on settled records, and pin its model (see below) |
| `mistri tenant init | grant` |
| `mistri tenant clone | import |
mistri docs [topic] |
the App reference, offline: access, relationships, dates, dashboards, reload and restart, … |
mistri agent init |
put the agent skill where coding agents read it (see below) |
mistri compare-openapi <previous.json> <current.json> |
compare two exported App API documents; no server |
mistri api-token --help |
local token-store maintenance; no server |
mistri mail-dns [<config.json>] |
the SPF, DKIM and DMARC records a tenant App’s mail is sent under; makes the DKIM key if it is missing |
Options
Section titled “Options”The server’s flags: where the data is, how it is served, and what an App adds.
| Flag | What it does |
|---|---|
-d, --db <target> |
SQLite file path or postgres:// URL (or set DATABASE_URL, used when no config names connections) |
-p, --port <n> |
Port to listen on (default 4111) |
--host <addr> |
Interface to bind (default 127.0.0.1, loopback only) |
--allowed-host <name> |
Also answer for this Host header; repeatable. Needed behind a proxy or in a container reached by name. Or set TABLEWALK_ALLOWED_HOSTS=a.example,b.example |
| `–trust-proxy <address | CIDR>` |
--trust-proxy-hops <n> |
Behind a managed front end with no address to name (Cloud Run, a cloud load balancer): how many proxies append to X-Forwarded-For. The client is the address n places from the right; anything the visitor wrote left of it is ignored. Rate limiting only. Or set TABLEWALK_TRUST_PROXY_HOPS=1 |
--trust-proxy-cloudflare |
Read CF-Connecting-IP as the client. Only when the server is reachable through Cloudflare alone (a tunnel or its address ranges): anyone else can write the header. Not with –trust-proxy-hops. Or set TABLEWALK_TRUST_PROXY_CLOUDFLARE=1 |
--auth-proxy-header <name> |
Behind a proxy that signs people in: the header it sets with the user (X-Forwarded-User). Requests from beyond loopback must carry it, and every write is journaled with it. Or set TABLEWALK_AUTH_PROXY_HEADER. |
--schema <name> |
Postgres only; repeatable. Defaults to all user schemas. |
--read-pool <n> |
PostgreSQL connections that may read at once, per database (default 4; an App’s follows its writers, at most 64). A connection’s “pool”: { “read”: n } in tablewalk.json wins. Or set TABLEWALK_READ_POOL. |
--write-pool <n> |
Connections an App may write through at once, per PostgreSQL or MySQL database (default twice the cores, 4 to 16; at most 64). A connection’s “pool”: { “write”: n } wins. Or set TABLEWALK_WRITE_POOL. |
--lock-timeout <s> |
Seconds a write waits for a row or table another transaction holds before it is refused (default 5, 0.1 to 30). Or set TABLEWALK_LOCK_TIMEOUT. |
--pooler <mode> |
session (the default): PostgreSQL is reached directly, or through a pooler that keeps a client on one server connection. transaction: through PgBouncer in transaction mode or RDS Proxy; the read-only guard and the statement cap are then set per transaction. Or set TABLEWALK_POOLER. |
--drain-delay <s> |
Seconds /readyz answers 503 before the listener closes on SIGTERM, so a balancer that probes takes this instance out first (default 0, up to 120). Or set TABLEWALK_DRAIN_DELAY. |
--shutdown-grace <s> |
Seconds a response still unfinished after the drain may keep the process before it is cut (default 10, 1 to 300). Hold the drain and this inside the platform’s stop timeout. Or set TABLEWALK_SHUTDOWN_GRACE. |
--config <path> |
Read connections from this file, and the cache, search, webhook, email (smtps://, smtp:// or log:) and OAuth services beside them; the paths in it are relative to its folder |
--no-config |
Ignore any config file |
--no-live |
Turn live sync off, even for an App that asks for it (live: true): its open pages then refresh on focus and after their own saves. Nothing is written to the change store and /api/live answers 204. |
--app <dir> |
Serve an app: a directory whose app.ts (or app.mjs / app.json) declares its resources, views, navigation and pages. Repeatable; with more than one, every app names its connection. A save reloads it; a change to its grants, sources or sign-in needs a restart. mistri docs getting-started is where to begin. One App is served at the root (/leases/12); several each under its id (/lintel/leases/12). |
--mount |
|
--base-path <path> |
The path a reverse proxy serves this server under, like /tools: every address, asset, API call, cookie and link carries it. Or “basePath” in the config, or set TABLEWALK_BASE_PATH. |
--app-entry <file> |
With one –app: the manifest to read, when it is not the directory’s own app.ts. The directory stays the App’s root: its components, pages/, features/ and authority.lock. |
--commands <file> |
The server-only default defineCommands registry of one –app, when it is not the commands.ts beside the App’s manifest, which is read without the flag. The App names its commands and is served their input from it. Requires authenticated access and an explicitly migrated writable SQLite or PostgreSQL command source; restart to reload. |
--jobs <file> |
Load a server-only default defineJobs module with one –app and its commands: jobs that run a command, as a declared principal, when a committed event says so or at the times they say (every). Its stores are provisioned once from tablewalk/maintenance; restart to reload. |
--policy <file> |
Load a server-only row or tenant policy for one authenticated, named-source –app whose auth.rows is “policy” or “tenant”. Tenant Apps require one existing provisioned SQLite or PostgreSQL source. Row policies support reads and explicit commands and scoped creation/updates with Undo. Delete/restore requires an explicit deleted-record policy. App and policy changes require a restart. |
| `–from-openapi <spec.json | url>` |
| `–openapi <spec.json | url>` |
--api-url <url> |
With –openapi: the API’s base URL, when the spec’s servers do not give a usable one. |
--api-token-env <name> |
With –openapi: send $name as a bearer token. |
--scaffold-app <dir> |
Draft an App project for the active connection, in TypeScript built from the tablewalk/app helpers: list views, display rules, an Overview dashboard, record pages, tablewalk-schema.d.ts, tsconfig.json, an .oxfmtrc.json and (outside a tablewalk checkout) package.json with format scripts. A SQLite file is named by a tablewalk.json beside the App, by a path from there. It passes mistri check as written; a start to curate. With –config naming several connections, one App with a source per connection; a PostgreSQL or MySQL one holding several schemas is a source per schema on that one connection. |
--name <name> |
With –scaffold-app: the App’s name, instead of one read off the database file’s. |
--write-authority |
With mistri check: write each app’s authority.lock — what it grants, one fact per line, and each command’s contract — to commit beside it. The server refuses to start an app whose authority differs from its lock, and a reload that changes it. |
--bump <command> |
With mistri check: move the command’s contract to its next version in authority.lock, when it changed and the App API, a job or kept receipts depend on it. |
--reindex [resource] |
With –app and a search provider plugged in (search providers are packs, after 1.0): read every searched resource (or the one named) from the database into its search index, then exit. Run it after declaring search, and after writes made outside the App. |
--demo |
Build a throwaway sample database and open it. Nothing of yours is touched; it lives in the temp directory. |
--open |
Open a browser once the server is up |
--dev |
Serve the dev observer at /_tablewalk/observe: every request’s trace with its SQL and parameters, commands with their input and refusal cause, logs, page errors. Loopback only, kept in memory, credentials masked. mistri observe prints its address. With an App, the theme bench too, at /_theme. |
--export <fmt> |
Print brief or types, then exit: brief is the schema as one line per table, for an agent’s context; types emits a .d.ts of this connection’s own table and column names for the app DSL — save it next to the app and “satisfies App” squiggles a typo the editor can catch before the server has to. openapi exports one App’s API document; it enables no API routes and requires –api-integers. asyncapi exports its AsyncAPI 3 document: feed events, webhooks and, with –jobs, inbound endpoints. The schema as md, mermaid or json is the database browser’s: npx tablewalk |
--api-integers <kind> |
With –export openapi or asyncapi, –api-reads or –app-mcp: decimal-string or number. No default; number refuses unsafe integer responses. |
--api-reads |
Serve one authenticated App’s API and its MCP (…/$mcp): its reads, and with –commands the targeted commands it exposes. Requires integers and rate budget; bearer tokens only; SQLite or PostgreSQL. Serving must pass native readiness. On loopback, or beyond it (–host, –allowed-host, –trust-proxy) only for an App whose api declares network: true. A tenant App is served under its –policy, each token bound to one membership; a row-policy App under its –policy, each token reading its own account’s rows. A “kind”: “oauth” entry lets a signed-in person give an agent a token. |
--api-requests-per-minute <count> |
Explicit –api-reads rate budget per token, 1–1000000; no default. Counted in this process. |
--app-mcp |
Speak one authenticated App’s API as MCP tools on stdio instead of serving HTTP: its reads, and with –commands the targeted commands it exposes. The token is read from TABLEWALK_API_TOKEN, never the command line. Requires –api-integers. SQLite or PostgreSQL; a tenant or row-policy App under its –policy. |
-h, --help |
Print the short help and stop; –help all prints this |
-v, --version |
Print the version and stop |
Checking an App while authoring it
Section titled “Checking an App while authoring it”The one command of the loop.
mistri check --app <dir> [--app-entry <file>] [<db> | --db <target> | --config <path>] [--commands <file>] [--jobs <file>] [--policy <file>] [--bump <command>] [--routes [--mount <app>=<path>] [--base-path <path>]] [--design] [--fix] [--defaults] [--fail-on-design-warning] [--warnings] [--pending [--pending-on <url>] [--migrations <dir>] [--history-table <name>]] [--deploy [--replicas <n>]] [--json | --brief]
Three or four steps, each with its own verdict line. Schema types: regeneratesthe App's tablewalk-schema.d.ts from the database when it has drifted (afile tablewalk did not generate is left alone; an App validation refuses isstill typed from what it declares, a refused segment, computed column ortags included, so its typecheck does not bury the one problem), itstablewalk-commands.d.tsfrom its commands module, which types the command names the App uses, and itstablewalk-components.d.ts, a props type for each custom component from itsdeclaration (SalesSummaryProps). Typecheck: the App'stsconfig.json, or the flags the agent guide gives, errors said asfile:line, the mismatch and a hint. Validation: the App loaded against itsdatabase, with its problems and its warnings; an App with an authority.lockbeside its manifest fails when what it grants differs from the lock. Components, for an App that declares customcomponents: each compiled, its sources failed on markup or code from textand the server's environment, warned on what a look cannot dress. Everystep runs; the errors print first, and warnings are advisory (unless--fail-on-design-warning is set) and print as one line a kind unless --warningslists them in full.--routes also prints every address the App answers to (/{app}/leads/{id},/{app}/leads/new, ...) and what each one opens. --design reads its pages,forms and shell for compositions that come out basic (a list cut beside anaside, a page that opens on a block of fields), each a warning with its fix:mistri docs design; with --fix it first deletes from the App's sourceevery rule a restated warning names. --fix also writes into an App theme'ssource each color or chart order its contrast and color-blind failuressay passes, and checks again; the themes step comes first, a line pertheme and mode, and fonts says the faces against their 150 KB budget. --defaults prints what each column isdrawn by when the App says nothing, and the layout each page is drawn in.--pending first applies the pending migrations (mistri migrate) to ascratch copy of the database — SQLite: a copy of the file; PostgreSQL:CREATE DATABASE … TEMPLATE, which needs CREATEDB and no other session onthe database — and every step reads the copy. --pending-on <url> (whichimplies --pending) runs them on the scratch database that URL names instead,on any engine: a branch or restore of the real one, or an empty one the wholehistory can build; for a hosted role without CREATEDB. It is never droppedand never the App's own database; a \${NAME} in the URL is expanded. --deploy adds a step thatjudges whether the App survives a container being replaced: it refuses asource or auth.store on a SQLite file, the default auth.store, a missingBETTER_AUTH_SECRET and a \${NAME} the environment lacks (run it with thedeployment's environment), and with --replicas <n> above one it warns thatrate limits are per process without a cache service. --json prints one JSONdocument instead of the lines (schema/check.schema.json): every findingof every step with its code, file, line, column, definition path and fix,for an agent; the exit code is the same. --brief prints one line perproblem, "code file:line message — fix: …", and one "preview-feature(info)" line naming the Preview features the App uses; repeated warningsare a line a code and resource with a count (--warnings lists each).Exit codes: 1 could not check, 2 schema types, 3 typecheck, 4 validation,5 components, 6 a pending migration failed on the copy, 7 --deploy refused. What each stepchecks and the common fixes: mistri docs check-codes.Looking at an App
Section titled “Looking at an App”Its pages as PNG files, for the agent that built it.
mistri shot --app <dir> [<route>…] [<db> | --db <target> | --config <path>] [--commands <file>] [--jobs <file>] [--policy <file>] [--theme <id|all>] [--palette <id>] [--compare <look>] [--recipes <part>] [--dark | --light] [--phone | --desk | --both] [--click <name>] [--type <field> <text>] [--press <key>] [--wait-for <text>] [--matrix] [--width <px>] [--full] [--scale <1-3>] [--as <role|email> [--tenant <id>]] [--out <dir>] [--text] [--browser <path>]
Serves the App on a free port with a temporary config directory (none ofyours is read or written), opens each route in Chromium once it has settledand prints the path of each PNG. A route is /{app}/leads/12 or leads/12;with none, every view and page the App declares, a page on its first record. --theme <id|all> with shot or grade: a built-in look (lumen, mono, sage) or one of the App's own themes instead of the one it wears; with shot, all shoots every built-in look, then the App's. The menu stands where that theme suggests (shell.placement) unless the App's ui.shell says, which a note says --palette <id> with shot or grade: one of that theme's palettes --compare <look> the App's theme (or --theme) beside that look, one picture of the two, <page>-<mode>.png, the look on the left --recipes <part> one page (the home, or the route) in each value of that recipe part, one contact sheet at <part>-<page>-<mode>.png; a note names values that draw the page nearly alike --dark, --light the scheme a reader picked: the page wears it as if chosen in its menu, whatever the machine is set to (without either, the machine's, light) --click <name> before the picture, press the button, link, tab, menu item or option with that accessible name, else the visible text; css:<selector> names an element by selector --type <field> <text> type into the box with that label or placeholder (css:<selector> likewise) --press <key> press a key (Enter, Escape, ArrowDown) --wait-for <text> wait until that text is visible. These four run in the order given, repeatable, on every route; the page is settled (requests done, nothing busy) before the first and after each, and a name that matches nothing in 15 seconds is refused with the step and the address --matrix every built-in look and the App's own, light and dark, at a desk and on a phone, for the routes: <page>-<look>- <light|dark>-<desk|phone>.png and an index.html laying them out. Takes no --theme, --palette, --dark, --light or size flag --phone, --desk, --both 390 wide, 1280 wide (the default), or both --width <px> the window's width instead (320 to 2560), with --desk or --phone: a narrow desk, a tablet --full the whole page, not the window --scale <1-3> device pixels per CSS pixel (default 1; 2 for a sharp picture): the page lays out the same, the PNG is larger --as <role> sign in as an account of that role the App's local store holds (one with a person row first, then the earliest made), in a copy of it (its password reset there only), else as a fresh one, which owns no rows; never a service role, which no person signs in as; without it, signed out --as <email> sign in as that account, already made (by mistri dev or a local start), with the password in TABLEWALK_AS_PASSWORD; its store is read, never written --tenant <id> with --as on a tenant App: the tenant the role is held in (a platform role needs none). The account is granted a temporary membership in the App's tenant store, journaled as maintenance:tablewalk-shot and revoked when the shot ends; each page opens in that context --out <dir> with shot: where every file goes, from the current directory (default tablewalk-shots/<app> in the system's temporary directory, where --compare, --recipes and --matrix each write to compare/, recipes/ or matrix/) --text with shot: print what each page shows (title, headings, table columns and row count, actions, empty states, page errors) as compact text instead of writing PNG files --browser <path> a Chromium or Chrome to shoot with (default: the CHROME variable, else Playwright's own, the machine's Chrome, then any Chromium another Playwright version installed)Needs Playwright's Chromium: install the playwright package where you run it,then npx playwright install chromium; the machine's Chrome serves whenPlaywright's browser is missing. Exit codes: 1 could not shoot, 2 a page threw or asection could not be drawn.Rows to look at
Section titled “Rows to look at”A draft judged on believable data, with no seed script.
mistri seed [--app <dir>] [<db> | --db <target> | --config <path>] [--connection <name>] [--rows <n>] [--seed <n>] [--append]
Fills the App's database (or every table, without --app) through itsadapter, parents before children: CHECK vocabularies, keys, NOT NULL andunique columns hold. A status the App moves with a machine (or, withoutan App, one read off the vocabulary) is spread over its states — most workopen, about a third done, a little called off — each record with thestamps of the states it passed through. Dates fall across the last 90days, and some open work is due today or overdue. Names, emails(example.com) and phone numbers (555-01xx) come from a small word list.Writes only to a connection whose config says "writable": true. --rows <n> rows per main table (default 40); staff and lookups get fewer, lines more --seed <n> the same rows again, on the same day (default: one chosen and printed) --append add to tables that already hold rows; without it, a table with rows is refused before anything is writtenExit codes: 1 could not run, 2 refused (read-only, or rows already there).Grading an App
Section titled “Grading an App”The rubric measured in a browser, not looked at.
mistri grade [<app-dir>] [<db> | --db <target> | --config <path>] [--commands <file>] [--jobs <file>] [--policy <file>] [<route>…] [--as <role|email|all> [--tenant <id>]] [--theme <id|all> [--palette <id|all>]] [--json] [--browser <path>]
Serves the App as shot does and opens every view and a record of each pageat 1280, 390 and 320 wide, and at 390 in the dark scheme. Each is measured:sideways scroll, figures and words cut off, controls drawn over each other,targets under 40px on a phone, text and field edges below the contrastrule, layout shift after the first paint, page errors and failed requests,empty sections, two number formats in one surface, chart axes that are notround, a record's name past two lines on a phone. One line per finding,"code file:line page@widths message — fix: …", then a score out of 100.The App is <app-dir>, else the current directory. An App with sign-in isgraded as the role that reads the most, unless --as names one (or anaccount, as shot's --as <email> does). --json with grade: one JSON document of the findings and score --theme <id> with grade: grade in a built-in look or one of the App's own themes instead of the one it wears; a finding the theme causes is placed at the theme, and any error fails it; all grades every look as one table, a row each and a column each window--palette all and --as all add a row for each palette (at 390, light anddark, its colors only) and for signed out and each role. A finding of thetheme contract says its promise, C1 to C13: mistri docs theme-contract.Exit codes: 1 could not grade, 2 an error-level finding. The codes and theirfixes: mistri docs design.A theme as it is worn
Section titled “A theme as it is worn”Every color, recipe, type and shape after extends.
mistri theme show <look|theme-id|.> [--app <dir>] [--diff <look|theme-id>] [--json]
A built-in look (lumen, mono, sage), or a theme of the App in --app (elsethe current directory); . is the one the App wears. Every color in lightand dark and each palette's changes, every recipe, the type and the shape,each value it inherits marked with where from: the look it extends, itsDefault palette, the plain structure's default, or derived from its othercolors. --diff <look> only what differs from that look or theme --json with theme show: one JSON documentChanging the schema
Section titled “Changing the schema”Versioned SQL files in Flyway’s format, forward only.
mistri migrate new <name> [--repeatable]mistri migrate status | up | validate | baseline | repairmistri migrate diff <name> | check | squash [--app <dir>] [<db> | --db <target> | --config <path>] [--connection <name>] [--schema <name>] [--migrations <dir>] [--history-table <name>] [--dry-run] [--tool flyway] [--schema-file <path>] [--dev-url <url>]
V3__add_payoff_quote.sql runs once, in version order, in its owntransaction (MySQL commits DDL as it goes: a failure there stops the runand is recorded as failed); R__views.sql runs again whenever it changes.up applies what is pending; with --app it then regenerates the App's schematypes and runs check, which names the view, page or command a dropped orrenamed column breaks. status lists what is applied and pending, the changesmade outside migrations (drift), and whether the App's types are older thanthe database. validate fails on a changed, missing or failed file, a pendingone, or drift. baseline records a database that already has tables asversion 0, touching nothing, and writes its schema beside the migrations(baseline.schema.json, never run). repair removes failed entries, acceptschanged files and marks missing ones deleted.
diff, check and squash read the schema you want from schema.sql beside themigrations (db/migrations -> db/schema.sql). diff writes the next V fileholding what the migrations still lack to build it, for review: a renamecomes out as a drop and an add. The first is schema.sql itself, writtenwith no download. check fails (exit 2) when the migrations,applied to a scratch database, do not build schema.sql. squash folds the Vfiles into one V1__baseline.sql, before a database worth keeping exists.tablewalk's own __tablewalk_* tables are left out. The comparison is Atlas'scommunity build, pinned and checked by sha256, fetched on first use into.tablewalk/bin with its update check off (TABLEWALK_ATLAS names your own);serving never fetches it. PostgreSQL and SQLite. --migrations <dir> where the files are (default: migrations/ in the App, or the connection's "migrations": { "dir": … } in tablewalk.json, relative to that file) --history-table <name> the history table: Flyway's layout, named __tablewalk_schema_history unless this or the connection's "migrations": { "table" } says otherwise (flyway_schema_history, to take over from Flyway) --connection <name> with several connections and no --app: which one --dry-run with up, baseline or repair: print what would run (the SQL, for up) and change nothing --tool flyway run the command with an installed Flyway instead (on the PATH, or TABLEWALK_FLYWAY), over the same files and history --repeatable with new: an R__ file --schema-file <path> with diff, check or squash: the schema you want (default schema.sql beside the migrations directory) --dev-url <url> with diff, check or squash on PostgreSQL: the server where two scratch databases are made and dropped (default: the connection's; needs CREATEDB)A run holds a lock — PostgreSQL: the advisory lock Flyway takes for the sametable; MySQL: GET_LOCK; SQLite: its write lock — so two deploys never migrateat once. Scoped to the App's schemas (or the connection's, or --schema).Exit codes: 1 could not run, 2 files and history disagree, 3 a migrationfailed, 4 applied but the App no longer checks.
mistri migrate stores status | up [--suite <suite.json>] [--app <dir> [--jobs <file>] [--policy <file>]] [<db> | --db <target> | --config <path>] [--connection <name>]The layouts tablewalk keeps in a database of its own (auth, tokens, history,inbox, command journal and sequences, tenant stores, flags, experiments, analytics)move forward only here: serving never migrates, and refuses a store behindits layout with this command in the message. status prints one line perstore; up applies each pending step in its own transaction (PostgreSQL: underan advisory lock) and is safe to run twice. --suite adds the suite's identity,grant, preference, flag and experiment stores and each App's connections.With --app, up also creates every store the App uses: the inbox its notifyrules write, the record history it shows, the command journal and eventstore its commands need, their effects' store; --jobs <file> adds the jobsmodule's stores and delivery destinations. The one step before it serves.A tenant App's stores are bound to its policy: with --policy <file>, upalso makes the tenant store and the journals the policy declares.--app may name a build (mistri build): its own bundled commands and jobsare read, and a --jobs or --commands must name the build's own.Exit codes: 1 could not run, 2 a store refused, 3 a step failed.
mistri migrate files status | up | sweep --app <dir> [<db> | --db <target> | --config <path>] [--connection <name>] [--policy <file>] [--dry-run]An App's files move to where each resource's storage says: up copies eachdatabase-kept file to the bucket and then empties its content, or back; afile is read from wherever its row says throughout, so up can stop and runagain anywhere. sweep deletes the bucket's objects no row names (a deletedrecord's, an upload whose row was refused) that are older than an hour. statuscounts both sides; --dry-run says what up or sweep would do. A tenant Appnames its --policy. Credentials come from TABLEWALK_S3_ACCESS_KEY_ID andTABLEWALK_S3_SECRET_ACCESS_KEY, else from an ECS task role or (withTABLEWALK_S3_INSTANCE_ROLE=1) an EC2 instance role; a server whose App names abucket does not start without them. Served with --jobs, an App is swept daily by theserver itself (a tenant App tenant by tenant); otherwise run sweep dailyfrom cron.Exit codes: 1 could not run, 3 some files did not move.Starting flows on records already there
Section titled “Starting flows on records already there”A backfill, run once beside the server.
mistri flows start <resource> --app <dir> --jobs <file> [--where <query>] [--flow <id>] [--tenant <id> --policy <file>] [--dry-run] [<db> | --db <target> | --config <path>]Start a resource's created flows on the records already there, as thougheach had just been created: one keyed dispatch per record, in the App's jobstore, which the server started with --jobs runs. Each flow's conditionstill decides. --where narrows the records (the query language over theirown columns), --flow to one flow; a tenant App runs one --tenant at a time,whose owner column its --policy names. Running it again starts nothing new.Exit codes: 1 could not run.Running a job now
Section titled “Running a job now”One of an App’s jobs at times, once, by hand.
mistri jobs run <name> --app <dir> --jobs <file> [--wait <seconds>] [<db> | --db <target> | --config <path>]Enqueues one run of the job in the App's job store, marked manual besidethe slots the schedule enqueues, for the server started with --jobs to runas the job's principal, journaled as a slot's run is; waits --wait seconds(default 60; 0 returns at once) and says how it ended. A job at times runsone at a time: a manual run waits while the schedule's run of the job holdsits lease. A name that is not one of the App's jobs at times is refused withthe names that are. A job store with no run of a job yet starts at its nextslot: a first start never runs a missed one.Exit codes: 1 could not run, 2 still waiting when --wait ran out, 3 the rundid not succeed.Measuring a decision before it applies
Section titled “Measuring a decision before it applies”And pinning the model that earned it.
mistri decide eval <decision> --app <dir> --jobs <file> [--provider jev|anthropic] [--limit <n>] [--truth "<query>"] [--max-cost <dollars>] [--max-error <share>] [--dry] [--yes] [--pin] [--json <file>] [--policy <file>] [<db> | --db <target> | --config <path>]Asks a provider about records whose answer people settled (--truth narrowsthem; default every record with the field set, at most --limit, 500) andreports accuracy, Brier, calibration error, a reliability table and what thedeclared thresholds would have applied, suggested and left. The records areread as the decision reads them, put back as they were before the answer wassettled where the App keeps their history. It prints the count and a pricethat errs high first, and asks nothing without --yes or above --max-cost(default $5); --dry prints what one record would send. It writes nothing tothe database and never applies; --pin writes the model version intodecisions.lock, which is what lets a decision apply, only when the share itwould apply was wrong at most --max-error of the time (default 0.02).Exit codes: 1 could not run, 2 not spent (no --yes, or over --max-cost),3 --pin asked and not earned. A row-policy App names its policy with--policy, and the records are read as its service reads them when served.A tenant App’s first boot
Section titled “A tenant App’s first boot”After mistri migrate stores up –policy.
mistri tenant init [<tenant>...] --app <dir> --policy <file>mistri tenant grant <email> --roles <role,...> [--tenant <id>] [--name <name>] [--days <n>] --app <dir> --policy <file> [--operator <name>] [<db> | --db <target> | --config <path>]init opens the platform realm (when the App declares one) and puts each namedtenant, active; one already there is left as it is, so it runs again after astop part-way. grant grants <email> a membership of the platform realm, orwith --tenant of that tenant, with that realm's --roles, for --days (default180, at most 366). An address with no account gets one in the App's authstore, its password read from TABLEWALK_ACCOUNT_PASSWORD, never the commandline. Exit codes: 1 could not run.A tenant App’s sandboxes
Section titled “A tenant App’s sandboxes”A copy of a tenant to try changes in.
mistri tenant clone <source> <sandbox> --app <dir> --policy <file> [--samples]mistri tenant import <sandbox> --app <dir> --policy <file>mistri tenant clock <sandbox> --app <dir> --policy <file> --ahead <duration> [--rate <n>] [--operator <name>] [--commands <file>] [<db> | --db <target> | --config <path>]clone makes a new sandbox tenant from a copy of the source's rows of everyresource the App marks clone: 'config' (and 'sample' with --samples), itsids rewritten so the copy names only its own rows. import prints whatimporting the sandbox into its source would change and what conflicts; thesource's owner approves it in Manage tenants. clock sets a sandbox's devclock, which its machine timers fall due on; a live tenant has none.Exit codes: 1 could not run.Building an App for production
Section titled “Building an App for production”Optional; an App also runs from its source.
mistri build --app <dir> [--app-entry <file>] [--out <dir>] [--config <path>] [--commands <file>] [--jobs <file>] [--policy <file>] [--dockerfile <file>]
Typechecks the App (its tsconfig.json, with the App's or tablewalk'sTypeScript), validates it as a start does, bundles its definition andserver entries (with rolldown when it is installed; otherwise each moduleis type-stripped on its own), and writes app.json, tablewalk-build.jsonand server/<kind>.mjs, and an App's custom components as components/. --out <dir> Where the build goes, from the current directory as every path on the command line is (default build/<dir name> in the current directory, the project the build must stay inside to resolve tablewalk; or <dir name>-build beside an App built from its own directory). Replaced whole on success; refused unless it is new, empty or an earlier build. --dockerfile <file> Build nothing: write the Dockerfile that builds this App with these options in a build stage and serves it (tini, uid 1000, PORT, a /healthz health check). Run docker build from the project root; --config is copied in, not built with.A --config may list this App (and no other): its apps[].env is what thedefinition is built with. With --config reaching the App's database, italso writes the generated types and runs mistri check's validation on the build. Serveit with --app <out>, and the build's own server/<kind>.mjs for --commands,--jobs or --policy. A build is pinned to the tablewalk version that madeit and never watched.Exit codes: 1 could not build, 2 typecheck, 3 validation, 4 bundle.Comparing exported App API documents
Section titled “Comparing exported App API documents”mistri compare-openapi <previous.json> <current.json>
Reads two bounded local JSON files, prints a conservative change report,and exits without loading a config, database or command handlers.Requires no-follow/nonblocking file-open support; Windows is not supported.Exit 0: version requirement met; 1: a required version increase is missingor the version decreased; 2: invalid input or unsupported document.Findings remain visible after a version increase. This is not a generalOpenAPI compatibility proof, and it does not enable API routes.Reading the reference offline
Section titled “Reading the reference offline”mistri docs list the topics and every shipped pagemistri docs <topic> print one (access-model, secrets, relationships, …)mistri docs <topic> --brief its headings and the first sentence under each, one screenmistri docs --search <words> every line of the shipped pages that says themGiving a coding agent the App skill
Section titled “Giving a coding agent the App skill”mistri agent init [--dir <project>] [--tool claude|codex|cursor|copilot|all] [--force]
Copies the skill tablewalk ships (a decision guide for building an App:which block fits, the design language, the choices that are hard to undo,and a rubric for screenshots) into the project, and points at it from amarked block in AGENTS.md. Claude Code reads .claude/skills/tablewalk;Codex, Cursor and Copilot read .agents/skills/tablewalk; all (the default)writes both. --dir <project> where to install (default: the current directory) --tool <name> claude, codex, cursor, copilot or all; comma-separated --force replace a copy the project has changedA re-run updates a copy it installed and says so; one the project changedis left alone, and the exit code is 1.Running an App on this machine, and testing it
Section titled “Running an App on this machine, and testing it”mistri dev [<app-dir>] [<db> | --db <target> | --config <path>] [options]
Serves the App (default: the current directory) as --app <dir> --dev does,with a throwaway session secret and an account per declared role, eachsign-in printed. They are kept in <app-dir>/.tablewalk/ (the auth store too),so a restart signs the same people in; delete it to start over. Local andfor development only: it refuses to run with NODE_ENV=production. Thedatabase is the App's tablewalk.json unless one is named. Where auth.menames the table people are rows of, each role's account takes the email ofone of its rows, so a view of "= me" shows that person's records. Atenant App, started with its --policy, gets an account per role that is amember of the first two tenants of its profile table, or of the platformrealm for a platform role.
mistri test init [<app-dir>] [--config <path>] [--force]
Writes tests/start.ts, tests/journeys.test.ts and tests/permissions.test.tsinto the App, on tablewalk/testing alone: a journey per role (its views, arecord, a command it is granted, each machine verb it holds), each job attimes run now, and every role × resource and command, allowed or refused asauthority.lock says, for Node's test runner (tests/*.test.ts). A row-policyApp is served with the policy.ts and jobs.ts beside its app.ts. A tenantApp gets tests/tenants.test.ts instead: for two of its tenants, eachresource a tenant owns, each command a tenant role runs and each platformrole's reads, served with its tenant policy and commands.ts. A filealready there is refused unless --force. --config <path> with test init: the tablewalk.json the suite copies its database from (default: the App's own); the suite reads TABLEWALK_TEST_CONFIG first, when it is setConnections can also come from a tablewalk.json
Section titled “Connections can also come from a tablewalk.json”{ "connections": [ { "name": "app", "url": "postgres://localhost/app" } ] }An App’s definition reads no environment unless the file lists it
Section titled “An App’s definition reads no environment unless the file lists it” { "apps": [ { "dir": "apps/ops", "env": ["REGION"] } ] }
Looked for in ./tablewalk.json, ./.tablewalk.json, then$XDG_CONFIG_HOME/tablewalk/connections.json.Watching what a --dev server did
Section titled “Watching what a --dev server did” mistri observe [--url <base>] print the running dev server's observer address mistri observe --mcp [--url <base>] speak its telemetry to a coding agent on stdio: check (mistri check --json; no server needed), recent_errors, list_traces, get_trace, search_logs, slow_queries, n_plus_one, explain_access, page_vitals, tail
OpenTelemetry is on when the standard variables say so: setOTEL_EXPORTER_OTLP_ENDPOINT (and OTEL_EXPORTER_OTLP_HEADERS, OTEL_SERVICE_NAME,OTEL_RESOURCE_ATTRIBUTES, OTEL_TRACES_SAMPLER as your collector, Datadog orDynatrace asks) and traces, metrics and logs are sent over OTLP/HTTP. Nothingprivate is in them: no bodies, row values, SQL parameters, emails or tokens.GET /healthz and /readyz answer an orchestrator (under --base-path, as everyroute is); /robots.txt and /sitemap.xml a crawler, with an App's publicaddresses where it has auth.public.
Set TABLEWALK_DIAGNOSTICS=1 for best-effort HTTP JSONL diagnostics on stderrand generated X-Request-ID response headers. Disabled by default. These recordsomit URLs, bodies, identities, SQL and error messages; existing human CLI logsare separate.
Browsing a database with no App is the database browser's: npx tablewalk <db>.