Command line
Every tablewalk command and flag, laid out from the help text the command
prints: what each does, and where its section is. npx tablewalk --help all
prints the same text, npx tablewalk --help the commands and common options
on one screen, and npx tablewalk <command> --help one command’s.
The first line of the help says it: tablewalk — browse a relational database as a graph you walk.
Commands
Section titled “Commands”| Command | What it does |
|---|---|
tablewalk |
start with nothing; the page asks for a connection |
tablewalk <sqlite-file> |
Open it in the database browser. |
tablewalk postgres://user:pass@host/db |
Open it in the database browser. |
tablewalk --db postgres://… --schema public --port 4111 |
Open it in the database browser. |
tablewalk check --app <dir> |
types, typecheck and validation of an App (see below) |
tablewalk build --app <dir> |
make an App ready for production (see below) |
tablewalk new --api <name> [<dir>] |
an App that is only an API: a resource, a command, its event, tokens and MCP; no pages |
tablewalk suite <suite.json> |
serve a local suite with per-App configuration; its paths are relative to the suite file’s folder |
tablewalk virtual <service.ts> |
run a typed virtual HTTP service for tests |
tablewalk flags --help |
explicit flag control-store provisioning |
tablewalk experiments --help |
explicit experiment evidence-store provisioning |
tablewalk analytics --help |
behavior-analytics store provisioning and funnels |
tablewalk shot --app <dir> [<route>…] |
an App’s pages as PNG files, to look at (see below) |
tablewalk grade [<app-dir>] |
an App’s pages measured against the rubric (see below) |
| `tablewalk theme show <look | .>` |
tablewalk schema --brief [<db>] |
the schema as one line per table, for an agent’s context |
tablewalk explain <check-code> |
what a check code means, with a fix example |
tablewalk 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 |
tablewalk migrate <command> |
change the schema with versioned SQL files (see below) |
tablewalk seed [--app <dir>] |
believable rows in an App’s writable database (see below) |
tablewalk dev [<app-dir>] |
serve an App with a dev secret and a sign-in per role (see below) |
tablewalk test init [<app-dir>] |
a starter suite: a journey per role, a permissions matrix (see below) |
tablewalk flows start <resource> |
start a resource’s flows on records already there (see below) |
tablewalk jobs run <name> |
run one of an App’s jobs at times now, once (see below) |
| `tablewalk tenant clone | import |
tablewalk docs [topic] |
the App reference, offline: access, relationships, dates, dashboards, reload and restart, … |
tablewalk agent init |
put the agent skill where coding agents read it (see below) |
tablewalk compare-openapi <previous.json> <current.json> |
compare two exported App API documents; no server |
tablewalk api-token --help |
local token-store maintenance; no server |
tablewalk 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); the database browser always writes through one. 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 |
--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. tablewalk docs start 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 tablewalk 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 tablewalk 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 tablewalk 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. tablewalk observe prints its address. With an App, the theme bench too, at /_theme. |
--mcp |
Speak MCP on stdio for coding agents, instead of HTTP: the database browser’s tools, which serve no App |
--tools <name> |
Offer one job’s worth of tools instead of all of them: explore, migrate, seed, or full (the default). |
--output-schema |
Advertise each tool’s output shape in tools/list. Off by default: it is half the listing’s size, and most agents never read it. Answers carry the same fields either way. |
--export <fmt> |
Print the schema as md, mermaid, json, brief or types, then exit. 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. |
--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. |
--lint [fmt] |
Report what the shape will cost — missing keys, unindexed references, naive timestamps — as text or json, then exit. |
--fail-on <sev> |
With –lint, exit non-zero when a finding is at least this severe: high, medium or low. For CI. |
--diff <conn> |
Compare the active connection’s shape with this one, print the differences, then exit. Exit code 1 when they differ, the way diff(1) does. |
-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.
tablewalk 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:tablewalk 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 (tablewalk 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 nothing else; repeatedwarnings are 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: tablewalk docs check-codes.Looking at an App
Section titled “Looking at an App”Its pages as PNG files, for the agent that built it.
tablewalk 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 tablewalk 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.
tablewalk 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.
tablewalk 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: tablewalk docs theme-contract.Exit codes: 1 could not grade, 2 an error-level finding. The codes and theirfixes: tablewalk docs design.A theme as it is worn
Section titled “A theme as it is worn”Every color, recipe, type and shape after extends.
tablewalk 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.
tablewalk migrate new <name> [--repeatable]tablewalk migrate status | up | validate | baseline | repairtablewalk 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.
tablewalk 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 (tablewalk 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.
tablewalk 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.
tablewalk 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.
tablewalk 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.A tenant App’s sandboxes
Section titled “A tenant App’s sandboxes”A copy of a tenant to try changes in.
tablewalk tenant clone <source> <sandbox> --app <dir> --policy <file> [--samples]tablewalk tenant import <sandbox> --app <dir> --policy <file>tablewalk 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.
tablewalk 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 tablewalk 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”tablewalk 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”tablewalk docs list the topics and every shipped pagetablewalk docs <topic> print one (access-model, secrets, relationships, …)tablewalk docs <topic> --brief its headings and the first sentence under each, one screentablewalk 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”tablewalk 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”tablewalk 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.
tablewalk 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” tablewalk observe [--url <base>] print the running dev server's observer address tablewalk observe --mcp [--url <base>] speak its telemetry to a coding agent on stdio: check (tablewalk 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 is read-only. Editing is off by default and enabled in the UI.