Browse the docs

Reference

Reference

Every fuast CLI command and flag, deploys, tokens, inspection routes, the HTTP call API, the realtime protocol, and environment variables.

The quick contract for the fuast CLI and HTTP surface. For the code-level API see capsule API and database.

CLI

Run bunx @revnu/fuast <command>, or fuast <command> when it is installed. Every command that prints data accepts --json. fuast <command> --help (or fuast help <command>) prints one command's usage and options.

Targeting an app

Inspection commands (inspect, run, db, logs, storage) pick their target like this:

  1. --port <n>: the fuast dev server on that port, even if a slug is also given.
  2. A slug or app URL argument: the deployed app.
  3. Neither: fuast dev on port 3000 (or FUAST_PORT_DEV).

With a slug, the CLI talks only to the platform (FUAST_API), which forwards to the app, so your token never goes to the app's own origin. With an app URL, pass --inspect-token. They do not read fuast.json. To inspect a deployed app, pass its slug (the app value in fuast.json) or its URL.

An unclaimed preview is inspected the same way, by the slug deploy printed, on the machine that deployed it. The CLI finds the preview's edit token in ~/.fuast/previews.json and sends it instead of an account token. inspect, logs, db list|dump|export, jobs list, storage list, and run work. Commands that change an app outside its handlers (db import, storage put|delete, jobs cancel, run schedule, share, domains) need an owner: claim the app first.

Platform commands (share, users, domains, deploy archive|restore|terminate) take a slug argument or fall back to fuast.json in the current directory. deploy reads fuast.json from the capsule directory.

Create

fuast new <dir> [--template todo|guestbook|signature|approvals|live-demo] [--no-git]
fuast create <dir> ...                 alias of new
fuast init [--template ...] [--no-git] scaffold into the current directory

new writes the template, fuast-env.d.ts, AGENTS.md, CLAUDE.md, .gitignore, and tsconfig.json, then creates a git repository with a first commit unless you are inside one or pass --no-git. init refuses when the directory has files other than hidden files, README*, LICENSE*, AGENTS.md, and CLAUDE.md, and appends fuast's lines to existing AGENTS.md, CLAUDE.md, and .gitignore.

Develop

fuast dev [dir] [--port 3000] [--host 127.0.0.1] [--reset] [--no-typecheck] [--allow-host <name>]...
fuast run-many [dir] [--count 20] [--base-port 4000]
fuast check [dir] [--no-typecheck] [--json]
fuast build [dir] [--out artifact.json] [--no-typecheck] [--json]
fuast types [dir]
fuast templates
  • dev runs the app locally with hot reload for server and client, the same WebSocket protocol, database engine, limits, and inspection routes as hosted apps. Data and files persist in .fuast/dev. --reset wipes them. --host 0.0.0.0 exposes the server, including its open inspection routes, to your network. --allow-host <name> accepts another Host header (for a tunnel or a LAN name); repeat it per name. --no-typecheck turns off the type check after each save.
  • run-many starts --count independent dev servers of the capsule on consecutive ports from --base-port, each with its own data, and prints their URLs. Ctrl-C stops them all.
  • check builds the capsule, runs the TypeScript checker, and verifies the contract (imports, capsule() keys, schema, cron expressions, favicon). Exit code 1 on any error. Run it before every deploy.
  • check --no-typecheck skips only the type check. check --json prints { ok: true, manifest, ... } or { ok: false, error, code, diagnostics }. Each diagnostic has file, line, column, code, category, message, and a source frame when the file is known.
  • build builds without deploying and prints bundle sizes. --out writes one JSON artifact with the manifest, bundles, CSS, and favicon.
  • types rewrites fuast-env.d.ts (and tsconfig.json if missing) from the installed CLI. Run it after upgrading.
  • templates lists the starter templates.

Local identities: ?fuast_user=<name> and ?fuast_guest=1 in the URL, or fuast auth as <name>. See auth.

Deploy

fuast deploy [dir] [--name "My App"] [--delete-table TABLE=COUNT]... [--no-typecheck] [--no-unclaimed] [--claim | --new] [--json]
fuast claim [dir]
fuast status [dir]
fuast deploy list
fuast deploy rollback <deployId>
fuast deploy archive [slug|deployId]
fuast deploy restore [slug|deployId]
fuast deploy terminate [slug|deployId] --yes
  • deploy uploads the capsule source and .env.fuast.server. The platform type checks it (errors block the deploy unless --no-typecheck), builds it, describes it in a sandbox, applies schema changes, and makes it live.
  • Logged in (fuast login, or FUAST_TOKEN in CI), it deploys to your account and prints the app URL. The first deploy creates the app and writes fuast.json ({"app": "<slug>"}). Commit it.
  • Not logged in, it deploys an unclaimed preview and prints a preview link, a claim link, and when the preview is deleted. See deploy without an account.
  • Before uploading, deploy runs the same build, contract check, and type check as fuast check locally, and stops on the first error.
  • --name sets the app's display name on first deploy. Without it, the CLI uses capsule({ name }), then the directory name.
  • deploy list prints each app you own with its status, live deploy id, and URL.
  • --delete-table TABLE=COUNT confirms dropping a populated table removed from the schema. Repeat it per table. See schema changes.
  • A rejected destructive change returns code DESTRUCTIVE_SCHEMA_CHANGE (HTTP 409) with tables: [{ table, rowCount }].
  • Every deploy is an immutable record with its source snapshot. rollback makes an earlier deploy live again with the current data. It fails if that deploy's schema cannot accept the current rows.
  • archive stops serving the app until restore. terminate stops it permanently. Both keep its data, logs, and records.

Deploy without an account

fuast deploy works before anyone has a fuast account. It puts the app online as an unclaimed preview and prints two links:

  • The preview link (https://preview-<random>.<apps domain>/?fuast_preview=…) shows the app, after a fuast page that says what a preview is. Anyone who has the link can look. It cannot claim the app.
  • The claim link (https://fuast.com/claim/…) makes the app someone's. They open it, sign in with a verified account, and choose Claim. It works once.

What the CLI does:

  • It saves { api, slug, editToken, expiresAt, url, claimUrl } in ~/.fuast/previews.json (mode 600), under the real path of the project folder. The edit token lets this machine redeploy and inspect the preview. The CLI never prints it and sends it only to the platform that issued it. Nothing is written to the project folder: fuast.json is for apps that have an owner.
  • It never reads a credential from the project folder. A repository can ship any file, so a .fuast/unclaimed.json in the working tree is ignored, with the warning ignoring .fuast/unclaimed.json in this folder. A folder this machine has no record for gets a new preview. Copying or moving the folder does the same.
  • fuast deploy again redeploys the same preview. The links and the expiry stay the same.
  • fuast claim prints the claim link, the preview link, and the expiry again. fuast status prints the platform, the account, and what the folder is: not deployed, an unclaimed preview, a preview someone claimed, or an app linked in fuast.json. fuast whoami and fuast auth status mention the preview too.
  • After the claim, a deploy without a login stops with This app was claimed by <email> and now deploys with that account. Run \fuast login`, then `fuast deploy` again. Once you are logged in as an account that may deploy the claimed app, deployupdates it, writesfuast.json`, and forgets the preview record.
  • A logged-in deploy in a folder whose preview is still unclaimed does not claim it. It stops with code preview_unclaimed (--json: { ok: false, code, slug, claimUrl, choices }) and asks you to choose: fuast deploy --claim claims this machine's preview for the logged-in account, keeps its data, and deploys to it; fuast deploy --new leaves the preview alone and deploys the folder as a new app.
  • When the preview has expired, deploy says so and creates a new one.
  • --no-unclaimed, or FUAST_UNCLAIMED=0 in the environment, turns all of this off: without a login, deploy stops with Not logged in to <platform>. Run \fuast login`, or set FUAST_TOKEN for CI.`
  • A folder with fuast.json is linked to an owned app and always needs a login.

What a preview is:

Unclaimed preview Claimed, restricted Trusted
Address Random preview-… slug The same, or one the claimer picks The same
Who can open it Anyone with the preview link, after the fuast page Private to the owner until shared By sharing mode
Identity Guests only, no sign-in Accounts and guests, by sharing mode The same
ctx.email, ctx.team, ctx.workspace Fail with code unclaimed Fail with code restricted Work
Schedules and jobs Registered, not run Registered, not run Run
Limits Preview limits Normal limits Normal limits
Lifetime 72 hours from the first deploy Until deleted Until deleted

Claiming keeps the app's rows and files and cancels every job that was queued while it was a preview. The preview link, the claim link, and the edit token stop working.

A claimed app is restricted until its owner or a workspace admin trusts it with the workspace: the box under Options on the claim page, "Trust this app with my workspace" in the dashboard, or fuast trust. A deploy never does that. deploy to a restricted app works, prints that workspace features are off and the fuast trust <slug> command that turns them on, and --json carries restricted: true, restrictedNotice, and trust: { command, dashboard }. See share for the commands and security for the reasons behind each rule.

preview-… addresses are reserved for unclaimed previews. An account cannot create an app or an alias with such a name.

Share

fuast share [slug] --visibility private|workspace|public [--emails a@x.com,b@y.com]
fuast users list [slug]
fuast users approve <email> [slug]
fuast users deny <email> [slug]
fuast users remove <email> [slug]
fuast trust [slug]
fuast untrust [slug]
fuast list

See teams. list prints every app you can see with its slug, URL, owner, and sharing mode.

  • trust grants an app your workspace: it may send email, list the workspace's members, read and write team tables (every app in the workspace shares them), and run its schedules and delayed jobs. An app claimed from an unclaimed preview starts without that, and deploying to it never changes it: trust, the dashboard button, or the box on the claim page does. It needs the login of the app's owner or a workspace admin; deploy tokens (--app) and preview edit tokens cannot do it. Trust an app when you know who wrote its code.
  • untrust takes it away again, from any app. The app keeps working with its own data; email, team tables, and the member list are refused with code restricted, and its schedules and jobs stop until it is trusted.
  • fuast inspect and fuast status say when an app is restricted.

Account and tokens

fuast login [--token <token>] [--email <email> --password <password>]
fuast logout
fuast whoami
fuast auth status
fuast auth as <name>          local dev identity
fuast auth reset
fuast token create --name <name> [--app <slug>]
fuast token list
fuast token revoke <id>
  • login prints a short code and opens a browser approval page. A person signs in, checks the code, and approves. The CLI polls for up to 10 minutes, then saves the session in ~/.fuast/config.json. Set BROWSER=none to print the URL without opening a browser. --token checks an existing token with the platform and saves it; a rejected token is not saved. --email with --password (or FUAST_PASSWORD) logs in without a browser, for test platforms.
  • auth status prints the platform, the logged-in account, and the local dev identity. whoami prints the account and exits 1 when there is none. Both say so when the current folder has an unclaimed preview.
  • token create prints a token once. Without --app it is a personal token that can do anything you can. With --app it can only deploy and inspect that app. Use it as FUAST_TOKEN in CI.

Inspect and operate

fuast inspect [slug|url] [--port 3000] [--usage] [--query <name>] [--inspect-token <token>] [--json]
fuast run <name> [arg...] [slug|url] [--port 3000] [--as <name> | --guest | --anon] [--json]
fuast run schedule <name> [slug|url] [--port 3000] [--json]
fuast jobs list [slug] [--port 3000] [--status <status>] [--limit 100]
fuast jobs cancel <jobId> [slug] [--port 3000]
fuast db list [slug] [--port 3000]
fuast db dump [slug] [--port 3000] [--table <name>]
fuast db export [slug] [--port 3000] [--out backup.json]
fuast db import <file> [slug] [--port 3000]
fuast logs [slug] [--port 3000] [--follow] [--limit 100]
fuast storage list [slug] [--port 3000] [--prefix <p>] [--limit <n>]
fuast storage get <key> [slug] [--port 3000] [--out <file>]
fuast storage put <file> [slug] [--port 3000] [--public] [--content-type <type>]
fuast storage delete <key> [slug] [--port 3000]
fuast domains add <host> [slug]
fuast domains list [slug]
fuast domains check <host> [slug]
fuast domains remove <host> [slug]
  • run calls one query, mutation, or action and prints the result as JSON. Each argument is one JSON value ('"milk"', 3, '{"text":"milk"}'); a last argument that is not JSON is the slug or URL. The kind comes from the app's manifest. Locally it runs as your dev identity (fuast auth as), or as --as <name>, --guest, or --anon for one call. Against a slug it runs as your logged-in account with the same sharing rules and quotas as the browser. --json prints { ok, kind, name, result, ms, logs, metrics } with the call's log entries and read metrics. A handler error exits with code 1. See test and debug.
  • run schedule <name> runs a cron schedule now as the system identity; its next run time does not change. Hosted, it needs the app owner or a workspace admin.
  • jobs list prints durable jobs (ctx.scheduler): pending and running ones by due time, then recent finished ones, with attempts and errors. --status pending|running|done|failed|canceled filters. jobs cancel <jobId> cancels a pending job and exits 1 when it is no longer pending. See jobs.
  • inspect --query <name> prints the database read metrics recorded for that query, grouped by run: totals per run (rows read, rows returned, bytes, index scans) and each run's database calls with their index and full-scan flag.
  • inspect prints the app name, URL, deploy id, sharing, handlers by kind, schema with indexes and row counts, endpoints, schedules with next run times, tools, and database read metrics. --usage adds daily counters and stored totals.
  • db list prints table names and row counts. db dump prints rows, bounded by the 1000-row return limit. db export walks every table in pages and writes a complete JSON backup atomically. db import loads a backup into empty tables (owner only).
  • logs prints the newest 100 entries (--limit changes it). --follow keeps polling and prints new entries as they arrive.
  • Inspection commands send your fuast token only to the platform API origin. For an app served from its own subdomain, pass --inspect-token <token> (or set FUAST_INSPECT_TOKEN) with a token from fuast token create --name inspect --app <slug>.
  • domains add app.example.com connects a custom domain. It prints the two DNS records to create (a TXT record at _fuast.app.example.com and a CNAME to the platform), and fuast checks them in the background until the domain is live. domains check <host> checks now and exits 1 until the domain is active. ls and rm are aliases of list and remove. See custom domains.
  • domains add my-tool (a name without a dot) adds my-tool.<apps domain> as an extra hostname for the app.

Inspection routes

fuast dev and hosted apps serve the same GET routes with the same JSON. The CLI reads them, and you can curl them directly.

Route Returns
/__fuast/manifest Name, deploy id, URL, runtimeVersion, artifactHash, inspectPolicy, domains, handlers by kind, schema, index state (database.indexes, failedIndexes, fullScanWarning), endpoints, schedules, tools, auth flags, favicon, limits, read metrics. Add ?query=<name> for queryExecution (what inspect --query prints).
/__fuast/usage limits, state (stateRows, stateBytes), usage (today's requests, mutations, email).
/__fuast/url { basePath, url }.
/__fuast/db/tables Table names, row counts, index state.
/__fuast/db Bounded row dump per table, with truncated.
/__fuast/db/export Backup metadata. Add ?table=<name>&schemaHash=<hash>&cursor=<c> for pages.
/__fuast/logs Recent log entries { at, level, message, data, source, handler }.
/__fuast/storage Stored objects with size, type, visibility, and uploader.
/__fuast/tools WebMCP tools with kind, description, and argument schema.
/__fuast/jobs Durable jobs { jobs, counts, nextRunAt }. ?status= and ?limit= filter. POST /__fuast/jobs/<id>/cancel cancels a pending job and POST /__fuast/schedules/<name>/run runs a schedule now (owner or workspace admin).

Locally they are open. fuast dev binds to 127.0.0.1 by default. Hosted, they require the app owner or a workspace admin, by app session cookie or Authorization: Bearer <token>.

/__fuast/manifest has restricted: true for a restricted app.

An unclaimed preview has no owner. Its GET inspection routes answer the preview's edit token in the x-fuast-edit-token header, on the app's own origin and at GET <platform>/api/deploy/unclaimed/inspect/<route> (what the CLI uses). The token is never accepted in a URL, and the POST routes are not available until the app is claimed.

One more route is for the app's own client, not for inspection: hosted GET /__fuast/auth/token returns { token, expiresAt }, a fresh identity token for the caller's same-origin app session (401 without a session, 403 without access). fuast/client calls it to refresh getIdentity().token. See endpoint credentials.

HTTP call API

POST <app url>/api/call
Content-Type: application/json
Authorization: Bearer <fuast token>        (or the app session cookie)

{"kind": "query" | "mutation" | "action", "name": "todos", "args": []}
  • args is the array of positional arguments after ctx. For a handler with tool args, send one object: "args": [{"text": "milk"}].
  • Success: HTTP 200 with the handler's JSON result.
  • Handler error: HTTP 500 with { "error": "<message>", "code"?: "<code>" }.
  • Not signed in: 401 { "error": "...", "code": "sign_in_required" }. No access: 403 { "code": "forbidden" }. Quota: 429, see limits.

GET <app url>/api/me returns the identity the app sees for the caller.

The same sharing rules and quotas apply as in the browser. A token with --app scope only works for its app.

Realtime protocol

Clients talk to <app url>/__fuast/ws with JSON messages that have an op. fuast/client handles this. It is documented for debugging, not as an app API.

Direction op Fields
client query.subscribe id, name, args, patches? (true to receive query.patch)
client query.unsubscribe id
client mutation.run, action.run id, name, args. At most 4 run at once per connection; later ones queue in order.
client client.timings Client-side timing samples. No reply.
client ping
server hello session, deployId, features
server query.result id, data, version?
server query.patch id, base, version, operations: JSON patch against version base.
server query.error id, error, code?
server mutation.result, action.result id, ok, data?, error?, code?
server refresh deployId. A new deploy is live.
server error error, code, quota?. Malformed messages get code invalid_request.

The server closes the socket with code 4401 when the session ends (sign-out) and 4403 when the caller loses access to the app. fuast dev and hosted apps speak the same protocol.

Operate a platform

For the person who runs a fuast platform. These commands use the platform's operator API, which exists only when the platform sets FUAST_OPERATOR_TOKEN.

fuast operator overview
fuast operator apps [--workspace <id|domain>] [--search <text>] [--unclaimed] [--limit 50] [--cursor <cursor>]
fuast operator reports [--limit 100]
fuast operator release-preview <slug>
fuast operator users [--search <text>] [--limit 100] [--cursor <cursor>]
fuast operator workspaces [--search <text>] [--limit 100] [--cursor <cursor>]
fuast operator delete-app <slug> [--yes]
fuast operator verify-user <email>
fuast operator audit [--limit 100] [--cursor <cursor>]
fuast operator agents [--limit 100]
fuast operator disconnect-agent <id>
fuast operator agent-audit [--limit 100] [--cursor <cursor>]
  • The CLI reads the operator token from the FUAST_OPERATOR_TOKEN environment variable. There is no flag for it and it is never saved to ~/.fuast/config.json. It is sent only to the platform origin (FUAST_API or the saved login), over https unless the platform is on this machine. fuast help lists these commands only when the variable is set.
  • overview prints counts of users, workspaces, apps, and deploys, plus stored file bytes and the size of all app databases on disk.
  • apps lists every app on the platform, newest first, with its owner, workspace, sharing, status, row and byte usage, last deploy, and custom domains. --workspace takes a workspace id or an email domain. --search matches the slug, name, or owner email. When there is more, the output ends with the --cursor value for the next page.
  • apps --unclaimed lists only previews deployed without an account, with their status (unclaimed, suspended, expired), expiry, and report count. delete-app takes a preview down like any app.
  • reports lists the previews people reported, with each report's address and reason. A preview that enough different addresses report is suspended: it stops serving until you run release-preview <slug> (reports are kept) or delete-app <slug>, or until it expires.
  • users and workspaces list accounts (email, verified, workspace, role, app count) and workspaces (owner, members, apps, storage).
  • delete-app deletes an app with its database, stored files, deploys, schedules, and custom domains. It is the same deletion the app's owner can do, and there is no undo. It shows what will be deleted and asks you to type the slug. --yes skips the question.
  • verify-user marks an account's email as verified, for someone whose verification email never arrived. It does what the emailed link does, so the account joins its company workspace under the usual rules. Only do this for an address you know the person controls.
  • audit prints what was done with the operator token: action, target, time, address, and result.
  • agents lists the agents people connected through the MCP server across every account. disconnect-agent ends one connection, and agent-audit prints what agents did through MCP.
  • The operator API cannot sign in as a user, create tokens for a user, or read app data, env values, or secrets. See security.

Environment variables

App env

.env.fuast.server holds KEY=value lines for ctx.env. Lines starting with # are comments. fuast dev reads it on every reload, and fuast deploy uploads it, replacing the previous deploy's file values. Values set in the dashboard Env tab override file values and survive deploys. Use the dashboard for production secrets you do not want in a file. Never commit the file. Env values never reach client code.

CLI env

Name Effect
FUAST_TOKEN Token from fuast token create. Used instead of the saved login, for CI.
FUAST_API Platform origin. Defaults to https://fuast.com.
FUAST_UNCLAIMED 0 makes fuast deploy need a login, like --no-unclaimed. By default a deploy without a login creates an unclaimed preview.
FUAST_OPERATOR_TOKEN Operator token for fuast operator commands, for people running their own platform. Read from the environment only.
FUAST_TOKEN=<token> bunx @revnu/fuast deploy

Platform environment

For people running their own fuast platform:

Name Effect
FUAST_BASE_URL Public origin of the platform.
FUAST_APPS_DOMAIN Serve each app at <slug>.<domain>. Required in production. Set to off for /a/<slug>/ path mode.
FUAST_ALLOW_SAME_ORIGIN_APPS 1 lets a production platform run in path mode, where apps share the platform origin. Only for platforms where every app is trusted. See security.
FUAST_TRUST_PROXY 1 to read client IPs from proxy headers for rate limits.
FUAST_TRUSTED_PROXY_SECRET Secret the web front end sends when it forwards app-subdomain requests. Unset: forwarded app headers are honoured only from loopback.
FUAST_RUNTIME_SECRET Shared secret between platform and runtime (formerly FUAST_EXECUTOR_SECRET). Required in production: the platform and runtime refuse to start with the default.
FUAST_PERSONAL_DOMAINS Extra comma-separated email domains treated as personal (a workspace of one).
FUAST_OPERATOR_TOKEN Turns on the operator API (/api/operator/*, used by fuast operator). At least 32 random characters, for example openssl rand -hex 32. Unset: the routes do not exist. A production platform refuses to start with a shorter value. See operate a platform.
FUAST_UNCLAIMED_DEPLOYS off refuses deploys without an account (403, with a message that says to run fuast login). Previews that already exist keep working until they are claimed or expire. On by default, and only on platforms with FUAST_APPS_DOMAIN. The caps and quotas of previews are FUAST_UNCLAIMED_* variables, listed in limits.
FUAST_CUSTOM_DOMAIN_TARGET Hostname (or comma-separated hostnames/IPs) users point custom domains at, e.g. your Railway public domain. See custom domains.
FUAST_CUSTOM_DOMAIN_TLS manual (default) or railway (default when RAILWAY_API_TOKEN is set): who issues certificates for custom domains.
FUAST_MCP off turns the MCP server off: /mcp, /oauth/*, and the OAuth metadata answer 404, and tokens issued to connected agents stop working. On by default.
FUAST_MCP_ALLOWED_ORIGINS Comma-separated web origins that may call /mcp from a browser page, with a bearer token. Empty by default: other websites are refused. See security.
FUAST_MCP_ANON_PER_MINUTE, FUAST_MCP_USER_PER_MINUTE MCP requests per minute without an account, per client address (60), and with one, per account (600).
FUAST_MCP_ACCESS_TTL_SECONDS, FUAST_MCP_REFRESH_TTL_DAYS Lifetime of the access tokens (3600 seconds) and refresh tokens (30 days) of connected agents.
FUAST_CUSTOM_DOMAINS off turns custom domains off. Also FUAST_CUSTOM_DOMAIN_LIMIT (per app, 5), FUAST_CUSTOM_DOMAIN_PENDING_DAYS (7), FUAST_WEB_HOSTS (extra dashboard hostnames).

Limit defaults can be overridden by environment variables on the platform and runtime. Each variable, and the process that reads it, is listed in limits.

Deploys and data

  • Deploys are immutable. Data is not tied to a deploy: every deploy of an app shares its database, storage, logs, env, and schedules.
  • Archive and terminate keep data. Restore resumes an archived app. Terminated apps cannot be restored.
  • fuast db export is the backup. There is no automatic point-in-time restore.