Github|...

CLI reference

Every spky command, grouped by what you're trying to do. Run spky <command> --help for full flags.

spky --help

Commands read sp00ky.yml from the current directory. Pass --path <dir> to point at a project somewhere else, and spky <command> --help for the flags of any single command.

Project & dev loop

CommandWhat it does
spky initScaffold a new project: schema, app, config, first migration.
spky devStart the local stack: SurrealDB, SSP, scheduler, your apps. A Worker backend runs from its dev command at its baseUrl, not under workerd. With sync.queryAllowlist on, passes the mode to the SSP and publishes the allowlist JSON as version dev on every start. Docs
spky generate (gen)Generate client types from sp00ky.yml, or a single file via --input/--output. --format json|typescript|dart|surql, --all, --append, --no-header, --mode, --endpoint, --secret, --modules-dir. For a typescript entry with queries, also records the query allowlist by running the query module through @spooky-sync/query-allowlist (the app’s node_modules/.bin/spooky-query-allowlist, else npx) and writes <queries>.allowlist.json; commit it. Docs
spky migrateDatabase migrations: create, apply, prod, status, fix. prod migrates the cloud deployment without deploying. apply --endpoint <url> --secret <secret> also installs the sync params a self-hosted database needs; --mode defaults to mode in sp00ky.yml. Docs
spky lintValidate sp00ky.yml, including each Worker backend’s entry and expose patterns.
spky doctor [--json]Project health: config, codegen freshness, migrations, Docker. Docs
spky verify [--fix]Check the SSP/scheduler snapshot against upstream SurrealDB and print the scheduler’s own drift verdict; --fix re-clones the replica when its counts are off, else forces every SSP to re-bootstrap.
spky query [sql]Run SurrealQL once, or open a REPL. --cloud targets the deployment, --json prints raw JSON instead of a table.
spky versionPrint version information.

Repository performance benchmark

The repository also contains spooky-bench, a separate developer tool in apps/benchmark. Its whitepawn suite measures scheduler and SSP bootstrap, 50-row live windows with related player names, scrolling with twenty retained windows, and propagation during steady writes and import bursts. It uses deterministic synthetic chess-game libraries and real database changefeeds. Measured startup, latency, throughput, memory and backlog results are published in the WhitePawn performance baseline, including completed cases and resource-limited tiers.

# Run from the repository root with Docker running.
cargo build --release -p sp00ky-cli -p scheduler -p ssp-server
pnpm -F @spooky-sync/benchmark build
pnpm -F @spooky-sync/benchmark test
node apps/benchmark/dist/index.js whitepawn --smoke
node apps/benchmark/dist/index.js whitepawn --profile standard

--smoke runs compact and PGN-heavy 10K-game cases with short load phases. --profile standard runs 100K compact, 1M compact and 1M PGN-heavy cases. The default large profile adds 10M compact games. Each case starts a disposable SurrealDB container and fresh scheduler/SSP processes; Docker must be running.

The benchmark records registration and publication latency separately and checks published root/child references and record versions. Scroll evictions remove both published references and durable registration metadata. Bounded checks between phases verify the retained registration ids and graph/follower counts. Raw reports include CPU, memory, write and readiness samples. Capacity checks include games added by load phases and reserve disk and memory headroom. Without a smaller measured PGN-heavy case, PGN projections use measured compact costs plus payload allowances as a floor, keeping the larger value for each resource against the initial PGN estimate. A preflight resource-limited result records a conservative estimate, not an observed out-of-memory failure, and produces a nonzero exit rather than a successful benchmark result.

Use whitepawn --help for binary paths, image, readiness timeout and output options. The default image is surrealdb/surrealdb:v3.1.5. --startup-timeout-secs bounds startup and publication-readiness waits, including propagation probes and load catch-up, at 3,600 seconds per wait by default. It is separate from the overall run duration. --schema accepts a custom source schema compatible with the chess fixture. The repository’s apps/benchmark/README.md describes the dataset matrix, load phases, measurements and existing focused benchmark suites. This command is invoked with spooky-bench, not spky.

Adding things to a project

CommandWhat it does
spky api addRegister a backend and generate its outbox table. Docs
spky bucketadd a file bucket, or configure its storage backend. Docs
spky flagFeature flags: list, create, get, delete, enable, disable, set, unset, eval. set adds a targeting rule, eval dry-runs one user. Docs
spky adminOperator roster (_00_admin): list (ls), add <user>, remove <user>. Grants access to the feature-flag editor and the admin dashboard. Root-only, so nobody promotes themselves.
spky recipe <name>Render a cookbook snippet (live-list, optimistic-mutation, crdt-text-field). spky recipe list for the index.
spky agents initGenerate AGENTS.md, parameterized by your schema. Docs
spky mcpMCP server for editors and agents: serve, token, tokens, revoke, install. Docs

Running unattended: -y / --yes

Every subcommand accepts -y / --yes, and SPKY_YES=1 does the same from the environment. It answers confirmation prompts without asking, so a command never sits waiting for a keypress.

This is the flag a CI job or a coding agent should pass. A pipe is easy for the CLI to detect, but an agent usually runs commands on a pseudo-terminal, where the prompt renders normally and then waits forever for input nothing can send.

The two kinds of question get different answers, because “yes to everything” is not what anyone means:

QuestionExampleWith --yesNo terminal, no --yes
Consent to something consequentialApply to PRODUCTION? Restart SurrealDB? Overwrite this directory?Yes, and the log says soRefuses, naming the flag
Preference with a defaultInitialize git? Cache the vault key? Create a backup first?The defaultThe default
IrreversibleDestroy the project. Reset its database.Not coveredRefuses

The last row is deliberate. --yes is the flag that gets added to every command by reflex, which makes it the wrong thing to arm “delete everything” with. Those two take the project’s name instead:

spky project destroy --confirm my-project
spky backup reset --confirm my-project          # still takes its backup first
spky backup reset --confirm my-project --no-backup

--yes also switches the guided flows (spky login, picking or creating a project, billing setup) to their unattended path. They lead into pickers and browser logins that no flag can answer, so instead of opening one they stop with the exact command or environment variable to use.

Note

Consent without a terminal used to be inconsistent: some commands hung, some failed with a bare “Failed to read confirmation”, and spky migrate prod and spky link disconnect skipped the question and went ahead. They now refuse like everything else. If a script relied on that, add --yes to it.

Operating a running system

CommandWhat it does
spky jobsBackground jobs: list, get, kill, retry, clear. clear -A/--all also drops queued jobs; running ones are never deleted. No subcommand opens the interactive dashboard. Docs
spky schedulesServer-side schedules: list, get, pause, resume, trigger, release, runs, sync. release <name> <key> lets a quarantined forEach key fire again. Docs
spky workflowsWorkflow DAGs: list, show, watch, runs, kill. show renders the graph, watch follows a run live. Docs
spky pushWeb Push: status, send, devices, sync. send pushes to users directly and waits for the delivery result. Flags
spky logsTail or browse logs. --filter, --split, --since, --until, --grep, -i/--interactive (TUI), -F/--follow. Docs
spky statsLive CPU, memory, disk and network graphs. --window 1h, --filter backend,surrealdb.
spky statusDeployment version, status and running VMs.
spky restart [TARGET...]Restart deployment containers. With no target: the scheduler and SSPs. Flags
spky scale ssp <n>Scale SSP instances.

Cloud

CommandWhat it does
spky login, spky logoutAuthenticate with Sp00ky Cloud.
spky projectManage cloud projects: create, list, credentials, move, destroy. create --plan takes payg (Cloudflare, pay as you go; the default), starter or pro (VMs). credentials --raw prints just the password, for piping. destroy deletes the project with its data and its backups, and frees the slug; a new project with that slug starts empty. move puts a Pro project that runs on the shared host onto VMs of its own: it copies the data with the project running, then waits for move cutover unless --no-hold is given (--no-wait returns at once). Subcommands: status, watch, cutover, abort, rollback. cutover and rollback ask first, because the project goes down. A pay-as-you-go project’s own database gets a root login like a VM project’s; on the retired free plan’s shared database, credentials prints a namespace-level login (user owner in the namespace named after the slug): sign in at the namespace level, not as root. Docs
spky deployBuild, upload, provision, migrate, deploy. On a Cloudflare project it bundles each Worker backend with esbuild and uploads it with the SSP Worker instead of building a Docker image (Node.js with npx required). With sync.queryAllowlist on, also publishes the committed allowlist JSON as _00_query_allowlist:<app>__<version> (the frontend’s package.json version) and keeps the previous version’s row; under enforce, a missing or stale JSON (sourceHash no longer matches the query module) fails the deploy, under warn it warns. Docs
spky push (no subcommand)Push schema to a Cloudflare project’s SSP node. VM plans apply schema during deploy. With a subcommand it is the Web Push tool.
spky release <app>Announce a new version to running clients without deploying. --mandatory, --cache-bust. Also publishes the app’s allowlist JSON under the announced version, so a regenerated query allowlist goes live without a restart.
spky envEncrypted variables and the vault: set, list, rm, pull, import, unlock, passphrase, share-ci, reset. set --file <path> reads a multi-line value (a PEM key) from disk, scoped with --dev or --prod. reset takes request, approve <email>, complete, list. Docs
spky domainCustom domains: add, list, remove. add --app <name> picks which app serves it (a frontend, an exposed backend, or a backend on a dedicated machine), add --status attaches it to the public uptime page.
spky backupDatabase backups: list, create, restore, delete, configure, reset. Docs
spky db upgradeUpgrade SurrealDB one step: to the newest 3.x, then from there to 4.x. --no-cutover stops before the cutover, --soak-minutes sets the watch window after a 4.x cutover (default 60), --confirm <project> goes ahead on a 4.x step without being asked, --no-wait returns at once. Subcommands: status, watch, cutover, cancel, finish, rollback. Docs
spky hostA Pro project’s own VMs. status prints each VM’s kind, type, location and status, and any upgrade waiting on the project host. upgrade upgrades the project host now instead of in its quiet window; it asks first, because web, API and live sync are down for about two minutes (the database is not touched), and prints the reason when it is refused. Docs
spky linkGitHub push-to-deploy: connect, status, settings, disconnect, trigger, runs. Docs
spky tokenAPI tokens for CI: create, list, revoke.
spky teamMembers and invitations: list, invite, invites, revoke, remove, rename. Docs
spky billingBilling: usage, plan, cap <dollars>. No subcommand opens the portal. usage in a pay-as-you-go project shows the month so far, priced, and its spend cap; cap sets that cap (base fee included, at least $10). plan switches between Starter and Pro on a VM project; a pay-as-you-go project has one plan, and moving between Cloudflare and VM hosting needs a new project. See Plans & billing.
spky notice [message]Post a notice to the public uptime page. list and remove manage existing ones.

Restarting containers

spky restart recreates containers in place. With no target it restarts the scheduler and its SSPs, leaving backends, frontends and SurrealDB running.

InputWhat it does
TARGET...Roles (db or surrealdb, scheduler, ssp, frontend) or the name of a single app from sp00ky.yml. Several may be given at once.
--all-backendsRestart every backend app.
--upgradeMove the scheduler and SSP to the newest published build before restarting. A role that sp00ky.yml pins to an exact version restarts on its pin instead, with a warning.
--cleanAlso wipe the scheduler’s persistent volume, its replica and write-ahead log. SurrealDB data is untouched.
--surreal (--db)Also restart SurrealDB. A process restart rather than a wipe, so data on the volume survives, but the whole deployment is briefly unavailable. Equivalent to adding db to the targets.
-y, --yesThe global flag: confirms the prompt a SurrealDB target triggers. Without it, and without a terminal, the restart is refused.
spky restart                 # scheduler + SSPs
spky restart db              # SurrealDB only (prompts first)
spky restart ssp scheduler   # several roles at once
spky restart gameanalysis    # one backend, by app name
spky restart --all-backends  # every backend app
spky restart --upgrade       # newest scheduler/SSP build first (unless version: pins them)
spky restart --clean         # also wipe the scheduler's replica + WAL
spky restart db -y           # CI: skip the SurrealDB prompt

What --upgrade, --clean and --surreal do at the container level is spelled out in Admin dashboard, which offers the same three actions from a browser.

Web Push

spky push inspects and drives Web Push as root, against the local dev database by default or the cloud deployment with --cloud (or any database with --url, --namespace, --database, --username, --password), like spky schedules.

CommandWhat it does
spky push status [--json]Whether a host has published its VAPID key (and its kid), whether push.apns / push.fcm have a stored credential and how many devices each reaches, the rules stored in _00_push_config, devices per user (disabled ones, and web ones subscribed under an older key), and direct messages by status with the most recent failures.
spky push send --to <user> ...Creates a _00_push_message for the given users and waits up to 40 s for the delivery result: how many devices accepted it, or why it failed. With no notification flags it sends a nudge. On --cloud it asks first (-y answers).
spky push devices <user> [--json]A user’s devices, newest first: label or user agent, where it is reached (a browser push service, APNs sandbox · <bundle id>, FCM · android · <package>), last success, failures, disabled reason, rule filter. Never the encryption keys; --json includes the full endpoint.
spky push syncStore push: from sp00ky.yml without a deploy (spky deploy and spky migrate do it anyway), and the native credentials it references: { vault: .. } resolves against the production vault with --cloud, the dev vault otherwise.

send takes:

FlagWhat it does
--to <user>Recipient, as $auth.id reads (user:abc). Repeat it or comma-separate. Required.
--title, --bodyThe visible notification. A notification needs a --title.
--link <url>Opened when the notification is clicked (notification.url). Not --url: that one picks the database.
--icon, --tagNotification icon, and a tag that replaces an earlier notification with the same tag on screen.
--topic <key>Collapse key: an undelivered push with the same topic is replaced at the push service.
--at <when>Send later: an RFC 3339 time (2026-10-01T09:00:00Z) or a delay (10m, 2h, 1d).
--ttl <duration>How long the push service keeps it for a device that is offline (30s, 1h, 1d).
--urgency <u>very-low, low, normal or high.
--data <json>A JSON object handed to the service worker as payload.data.
--no-waitReturn once the message is queued.
--jsonPrint the outcome as JSON (id, status, delivered, error).
spky push status                                    # local dev stack
spky push status --cloud                            # the deployment
spky push send --to user:abc --title "Hello" --body "Push works"
spky push send --to user:abc,user:def --title "Maintenance at 22:00" --at 2h
spky push send --to user:abc --data '{"conversation":"c1"}'   # a nudge
spky push devices user:abc
spky push sync --cloud                              # store push: without a deploy

Migrating a deployment

spky migrate prod applies pending migrations plus the internal Sp00ky schema to your cloud deployment, without deploying anything else. It resolves the SurrealDB URL and root password from your login and the slug in sp00ky.yml, and reads the mode, namespace and database from sp00ky.yml too, so there are no connection flags to pass.

A pay-as-you-go project’s own database is reached as root, like a VM project’s. A project on the retired free plan has no root login: its data lives in a namespace of the shared free database. There migrate prod, spky release and every --cloud command (migrate, query, jobs, flag, schedules, workflows, push) sign in as the project’s namespace-level owner user, the one spky project credentials prints, and work in the namespace and database the project was given there. --namespace cannot reach outside it.

FlagWhat it does
-y, --yesThe global flag: confirms applying to production. Without it, and without a terminal, the migration is refused rather than run unasked.
--force-schemaRe-apply the internal schema and remote functions even when unchanged, bypassing the schema-hash skip. Also accepted by spky migrate apply. Use it for drift recovery.
--migrations-dir <dir>Override the migrations directory from sp00ky.yml.
--endpoint <url>, --secret <s>Override the SSP or scheduler endpoint and auth secret used for the internal schema.
--config <path>Point at a sp00ky.yml somewhere else.

Raise SPKY_DB_HTTP_TIMEOUT_SECS when a large migration exceeds the 120 second per-request deadline. Migrations

Flags worth memorising

spky dev --clean            # rebuild SSP/scheduler state, keep your data
spky dev --clean-db         # also wipe the database
spky dev --verbose          # stream all SurrealDB/SSP/scheduler logs
spky deploy --only api,web  # deploy a subset of apps
spky deploy --upgrade       # also move SSP/scheduler to the newest build (unless version: pins them); pay as you go: SurrealDB 3 -> 4 after a backup
spky doctor --json          # machine-readable health check
spky query --cloud '…'      # run SurrealQL against production
spky logs --since 1h --grep 'error'
Note

spky doctor --json is the designed integration point for scripts and AI agents: a stable list of checks, each with a severity and a fix command. See AI coding agents.