statelyai
statelyai connects local machine source files to Stately Studio. Use it to open a file in the visual editor, compare local and remote machines, or sync a project in both directions.
statelyai connects local machine source files to Stately Studio. Use it to
open a file in the visual editor, compare local and remote machines, or sync a
project in both directions.
Run the CLI
Run without installing:
npx statelyai --helpOr install it globally:
npm install --global statelyaiChoose a workflow
| Goal | Command | Writes |
|---|---|---|
| Edit one local file visually | statelyai open <file> | Local file, when you save in the editor |
| Set up project sync | statelyai init [--scan] | Remote project and local statelyai.json |
| Inspect project machines | statelyai status | Nothing |
| Preview project uploads | statelyai push --dry-run | Nothing |
| Upload local machines | statelyai push [file] | Remote machines and local @statelyai IDs |
| Download project machines | statelyai pull | Linked local files and new files under newMachinesDir |
| Compare two machines | statelyai diff <source> <target> | Nothing |
| Fail on differences in CI | statelyai diff <source> <target> --fail-on-changes | Nothing |
Quick start
For hosted Stately, authenticate once:
statelyai loginInitialize the current repository and scan for XState files:
statelyai init --scan--scan suggests include globs and asks before saving them. Without
--scan, init creates statelyai.json with an empty include list.
Preview the files and remote operations:
statelyai push --dry-runThen push and pull:
statelyai push
statelyai pullpush creates remote machines for unlinked source machines, updates already
linked machines, and writes returned IDs into source comments. pull updates
linked files. If newMachinesDir is configured, it also creates local files
for machines that exist only in the Studio project.
Authentication
Browser OAuth is the default:
statelyai loginUse an API key instead:
statelyai login --api-keyCreate or manage keys in Stately API key settings.
For noninteractive input:
printf '%s\n' "$STATELY_API_KEY" | statelyai login --stdinFor automation, prefer an environment variable instead of storing a credential. Resolution order is:
STATELY_ACCESS_TOKENSTATELY_API_KEYNEXT_PUBLIC_STATELY_API_KEY- the credential stored by
statelyai login
The CLI loads .env.local from the current directory before resolving these
variables.
Use statelyai status --auth to report the selected source without printing
the credential. statelyai logout removes stored credentials but does not modify
environment variables.
Stored credentials use macOS Keychain or Linux Secret Service when available,
with a private config file as fallback. Set STATELYAI_CREDENTIALS_BACKEND=file
to force file storage, or STATELYAI_CONFIG_DIR to choose its directory.
statelyai.json
push and pull use statelyai.json from the current directory unless
--config supplies another path.
{
"$schema": "https://stately.ai/schemas/statelyai.json",
"version": "1.0.0",
"projectId": "project_123",
"studioUrl": "https://stately.ai",
"defaultXStateVersion": 5,
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.*", "**/*.spec.*"],
"newMachinesDir": "src/machines"
}| Field | Purpose |
|---|---|
$schema | Published JSON Schema URL. |
version | Config format version. Currently 1.0.0. |
projectId | Remote Studio project ID. |
studioUrl | Studio API origin. |
defaultXStateVersion | XState version used when creating remote machines. Minimum 5. |
include | Source globs used by project-wide push and pull. |
exclude | Globs removed from discovery. Defaults to tests and specs. |
newMachinesDir | Destination for remote-only machines created by pull. |
Project-wide push currently discovers machine-bearing JavaScript and
TypeScript files configured as xstate or auto. The config schema accepts
other format names, but project discovery does not push them.
Mutating project commands rewrite legacy configs with one sources entry to
the current top-level shape. status and push --dry-run normalize legacy
config in memory without writing it. Multiple legacy entries cannot be merged
safely and require manual migration.
Commands
login
Store an OAuth credential or API key.
statelyai login
statelyai login --api-keyFlags: --api-key, --stdin, --base-url.
logout
statelyai logoutlogout deletes stored credentials. Environment variables are unchanged.
status
Inspect the configured project and classify local and remote machines:
statelyai statusStatuses are linked, local-only, remote-only, and missing-remote.
Use --json for automation or --auth to show only credential resolution.
status is read-only and never migrates statelyai.json.
Flags: --config <path>, --base-url <url>, --json, --auth.
init
Create or reuse a Studio project and write statelyai.json.
statelyai init --name "Checkout" --visibility Private --scanFlags:
--name <name>sets the remote project name.--visibility Private|Public|Unlisteddefaults toPrivate.--scanproposes source globs interactively.--forcereplaces an existing config.--base-url <url>overrides the Studio API origin.
open
Start a local bridge and open one source file in the browser editor:
statelyai open src/checkout.machine.tsSaved file changes refresh the editor. Saving visual edits writes them back to the source file.
Flags:
--editor-url <url>selects the editor origin. Default:https://editor.stately.ai.--host <host>sets the local bridge host. Default:127.0.0.1.--port <port>selects a port. Default: a random available port.--no-openstarts the bridge without launching a browser.--debuglogs editor protocol messages.
diff
Compare two locators after normalizing them to graph form:
statelyai diff src/checkout.machine.ts machine_123 --fail-on-changesLocators may be:
- a local JavaScript or TypeScript machine file
- a local XState JSON, Stately graph JSON, or Studio digraph JSON file
- a Studio machine ID
- a Studio machine URL
--fail-on-changes exits with status 1 when the normalized graphs differ.
Use --base-url for remote IDs at another Studio origin. plan remains a
hidden compatibility alias for diff.
push
Push every discovered machine in statelyai.json:
statelyai pushPush one file while still using the project and defaults from the config:
statelyai push src/checkout.machine.tsPreview discovery and link/update decisions without a credential:
statelyai push --dry-run--dry-run does not create or update machines, write @statelyai IDs, or
migrate legacy configuration.
Flags: --config <path>, --base-url <url>, --dry-run.
pull
Pull all linked files from the configured project:
statelyai pullPull a linked file using its @statelyai ID:
statelyai pull src/checkout.machine.tsPull a machine ID or URL to an explicit target:
statelyai pull machine_123 src/checkout.machine.tsNew targets support JavaScript/TypeScript, .digraph.json, and .graph.json.
For existing JSON targets, the current file shape determines the output format.
Project-wide pull skips linked files with uncommitted Git changes. Pass
--force to overwrite them. Remote-only machines are skipped until
newMachinesDir is set.
Flags: --config <path>, --base-url <url>, --force.
CI examples
Fail when a local machine differs from Studio:
npx statelyai diff src/checkout.machine.ts machine_123 --fail-on-changesProvide STATELY_API_KEY or STATELY_ACCESS_TOKEN through the CI runner's
secret environment.
Check project discovery without network credentials or writes:
npx statelyai push --dry-runSet NO_COLOR=1 or CI=true for plain output.
Common problems
- The server returns
401: runstatelyai login, or setSTATELY_ACCESS_TOKENorSTATELY_API_KEY. pushfinds no files: checkincludeandexclude;initwithout--scanintentionally leavesincludeempty.pushmatches files but finds no machines: project discovery currently requires XState imports pluscreateMachine(...)orsetup(...).createMachine(...).- Remote-only machines are skipped: set
newMachinesDir, then runstatelyai pullagain. pullrefuses to overwrite a file: commit or stash its changes, or pass--forceif overwriting is intentional.- A linked remote machine was deleted or is inaccessible: interactive
pushoffers to relink it as a new remote machine and replaces the local ID.
Run statelyai <command> --help for generated command syntax and flags.
Self-hosting
Skip login when the server has authentication disabled. The CLI sends no
authorization header when no credential exists; the server decides whether
authentication is required. This also applies to open editor-sync requests
and CI commands.
For OAuth, point login at the deployment's protected resource:
statelyai login --base-url https://editor.example.com/api/mcpPassing only an origin uses its /api/mcp resource. The resource must
advertise an authorization server. If that server does not support dynamic
client registration, set STATELY_OAUTH_CLIENT_ID.
URL settings are intentionally scoped:
| Setting | Used by | Meaning |
|---|---|---|
--base-url | init, status, diff, push, pull | Studio API origin. |
studioUrl | statelyai.json project sync | Default Studio API origin for that project. |
login --base-url | login only | OAuth protected-resource URL used for discovery. |
--editor-url | open only | Visual editor origin. Default https://editor.stately.ai. |
A complete setup may look like:
statelyai login --base-url https://editor.example.com/api/mcp
statelyai init --base-url https://studio.example.com --scan
statelyai open src/checkout.machine.ts \
--editor-url https://editor.example.com