command-line
The Crossplay CLI
Everything the desktop app does, scriptable. Generate a project, add pieces, pull updates, inspect the catalog — from a terminal or from CI.
Install
Two ways, depending on where you are working.
Windows
Put it anywhere on your PATH. It ships with the desktop app's release, so its version
tracks the service it talks to.
Anywhere else — CI, Linux, macOS
dotnet tool install --global Crossplay.Cli \
--add-source https://download.crossplay.dev/app/dl/
Needs the .NET 10 SDK. In CI, skip crossplay login and set CROSSPLAY_TOKEN
from a secret.
Sign in
crossplay login prints a short code, opens this site, and waits while you approve it in
the browser. Your password never goes near the CLI, your normal 2-step verification
still applies, and what the tool receives is a token you can revoke.
$ crossplay login
Your code: BKM2-K6MK
Approve at: https://crossplay.dev/cli/auth
Signed in as you.
On a machine with no browser — a server, a container, SSH — add --no-browser and approve
from your phone. In CI, skip signing in and set CROSSPLAY_TOKEN.
Commands
crossplay auth Sign in and out of a Crossplay Hub.
-
crossplay login [--hub URL] [--no-browser]Sign in through your browser (no password touches this tool).
Starts an OAuth 2.0 device-code sign-in: the CLI shows a short code, opens the approval page, and waits while YOU authenticate in the browser — with your real 2FA. The CLI never sees your password, and the token it receives is revocable. --no-browser print the URL instead of opening it (headless boxes, SSH, containers) In CI, skip login entirely and set CROSSPLAY_TOKEN.
-
crossplay logoutForget the saved credential on this machine.
LOCAL only: it deletes the saved token, it does not revoke it server-side. If you believe a token leaked, change your password — that ends every session for the account.
-
crossplay whoamiShow who this machine is signed in as.
crossplay account Your Crossplay account.
-
crossplay account showAccount details: username, email, admin, 2FA, licence expiry.
-
crossplay account entitlementsThe pieces this account may download ('*' = all).
crossplay piece The piece catalog.
-
crossplay piece list [--category NAME] [--free] [--owned]Every piece, with its latest published version.
-
crossplay piece show <key>One piece: description, dependencies, version, price.
crossplay content Import a game's spreadsheets into its content database.
-
crossplay content scaffold --file X.csv [--type KEY] [--out profile.json] [--delimiter , ] [--comment #] [--decimal-comma] [--no-header]Draft an import profile from a CSV's header.
Reads the header and samples the rows to guess each column's kind, then prints (or writes) a profile you refine by hand. Nothing is imported. It is a DRAFT on purpose: Ref and Enum columns encode relationships the file does not state, so only a person can set 'refTypeKey' / 'enumChoices'. --type KEY the content type key (default: the file name)
-
crossplay content import --profile p.json [--out bundle.json] [--dry-run] [--server URL --admin-token T] [--insecure]Import CSVs through a profile.
Builds the {types,records} bundle and posts it to the server's content-import admin action. File paths in the profile are relative to the PROFILE, so it works from any directory. Re-importing UPSERTS: each row's id comes from the profile's key column, so the same sheet imported twice updates rows instead of duplicating them. --dry-run build + report, send nothing --out FILE write the bundle (works with no --server, for CI or to commit) --insecure accept a self-signed cert (a dev server on localhost) The admin token may come from CROSSPLAY_ADMIN_TOKEN instead of the flag. A bundle over 256 KiB uploads in chunks automatically (the admin surface is GET/query-based, so it cannot ride one URL); an older server without that surface says so instead of failing mid-upload.
crossplay project A generated project on THIS machine.
-
crossplay project new --name NAME --pieces a,b,c [--database sqlite] [--out DIR] [--remote]Generate a new project from the catalog.
Without --remote this generates from a local Crossplay CHECKOUT (source mode) — the owner's path, and it works offline. With --remote the HUB builds the project and sends it back, which is the only path if you do not have the framework source: pieces are entitlement-checked and the server tier arrives as licensed DLLs. --remote needs 'crossplay login' and refuses a non-empty destination. Either way the project is registered on the Hub, so it appears in 'crossplay cloud list', in the desktop app and on the website with remote admin already paired. Signed out or offline, the local generate still works and 'crossplay cloud register' finishes the job later.
-
crossplay project update [--project DIR] [--pieces a,b,c] [--crossplay-root DIR] [--dry-run]Pull the latest published pieces into a project.
Reads the project's crossplay-lock.json, downloads anything newer, replaces the Unity packages and the server pieces, and regenerates the machine-owned composition glue. Your own code and your appsettings are never touched. --crossplay-root (or CROSSPLAY_ROOT) names the Crossplay CHECKOUT that serves a SOURCE-MODE project — one made by 'project new' without --remote. The generator that created a project is the one that has to maintain it: the Hub cannot emit framework source, so its glue would delete declarations it has no replacement for, and the apply refuses it. A licensed project ignores this flag and always uses the Hub.
-
crossplay project add <a,b,c> [--project DIR] [--crossplay-root DIR] [--dry-run]Install pieces the project does not have yet.
-
crossplay project remove <key> [--project DIR] [--crossplay-root DIR] [--cascade] [--dry-run]Uninstall a piece.
-
crossplay project settings [--project DIR] [--set key=value ...] [--crossplay-root DIR] [--dry-run]Show, or change, the recorded generation axes.
With no --set this is read-only. Axes are things chosen at generation time — database, transport, topology, hosting — that used to require regenerating from scratch to change.
-
crossplay project show [--project DIR]What this project is: pieces, versions, axes.
crossplay cloud Projects registered on crossplay.dev.
-
crossplay cloud register [--project DIR] [--name NAME] [--admin-url URL] [--label L] [--no-agent] [--force]Register a project on this disk so it appears here.
A LOCAL 'project new' generates offline and never contacts the Hub, so the project does not show up in this list, in the desktop app or on the website — and therefore has no remote-admin pairing. This registers it (find-or-create by name, so it is safe to run twice and a re-generate keeps the same project id), mints an agent enrollment token, and writes both into the project's git-ignored appsettings.Local.json. 'project new' now does this for you; this verb is for projects generated before it did. The name comes from the project's crossplay-lock.json unless you pass --name. --no-agent register only: no token minted, no file touched --force replace an agent token the project already carries (it is left alone by default, because a deployed instance may be running on it)
-
crossplay cloud listYour registered projects and whether their agent is connected.
-
crossplay cloud show <id>One registered project.
-
crossplay cloud delete <id> [--yes]Unregister a project.
-
crossplay cloud token <id>Mint a short-lived admin token for a project's server.
crossplay feed The published feed.
-
crossplay feed statusLive versions: feed, API and desktop app.
crossplay app The desktop app and this CLI, on the published app track.
-
crossplay app statusWhat is installed here versus what is published.
Reports the installed desktop app, this CLI, and the latest published version of both. It names EVERY copy of the app it can find, because they can disagree: the app's own updater replaces the binary in place wherever it was launched from, so an app first run out of a build folder keeps updating there while the default install folder still holds whatever was installed months ago. Updating the wrong one looks exactly like an update that did nothing.
-
crossplay app update [--app] [--cli] [--exe PATH] [--force] [--dry-run]Update the desktop app and this CLI to the published version.
With no selector it updates BOTH — leaving half the toolchain behind is rarely what 'update to the latest' means. --app or --cli narrows it. --exe PATH update this executable rather than the discovered one --force when the app is running, stage the update and swap it in as it exits --dry-run report what would happen and download nothing THE APP is a separate process: while it is running this refuses and names the pid, rather than closing your app for you. THIS CLI cannot overwrite its own running image, so a self-update downloads and verifies immediately (a bad download fails now, while you are watching) and hands only the final move to a helper that waits for this command to exit. VERIFICATION IS NOT EQUAL, and the output says which applied: the app exe is covered by the publisher signature; the CLI artifacts carry a published SHA-256 served over TLS but are outside that signature, because the signed payload is a fixed field list that already-shipped clients recompute — extending it would make every existing client reject every future manifest.
crossplay config CLI defaults, saved per machine.
-
crossplay config listEvery saved setting.
-
crossplay config get <key>One setting.
-
crossplay config set <key>=<value>Save a setting.
Known keys: hub (default Hub URL), output (table|json|tsv).
-
crossplay config unset <key>Remove a setting.
crossplay rest Call any Hub endpoint directly.
-
crossplay rest <get|post|delete> <path> [--body JSON]Send an authenticated request — the escape hatch for anything without a command yet.
Sends your saved token. The equivalent of `az rest`: when the API grows a capability before this CLI does, you are not blocked waiting for a release.
Every level has help: crossplay, crossplay piece,
crossplay piece list --help.
Commands talk to https://api.crossplay.dev unless --hub says otherwise, or a saved config
does — point it at your own with crossplay config set hub <url>.
Output you can script
Every command takes -o table (default, for reading), -o json or
-o tsv, and --query to pick out part of the result. Data goes to stdout and
nothing else does, so pipes stay clean.
# every piece key, one per line
$ crossplay piece list --query "[].key" -o tsv
# what is live right now
$ crossplay feed status
HUB API FEED APP PIECES
https://api.crossplay.dev 0.5.43 0.7.39 0.5.40 85
Exit codes: 0 success, 1 the command failed, 2 the command line
was wrong.
Where things are kept
~/.crossplay/credential.dat | Your token. Encrypted with DPAPI on Windows; owner-only elsewhere. |
~/.crossplay/config.json | Your defaults. Plain text — nothing secret. |
CROSSPLAY_TOKEN | A token for CI. Wins over the saved one. |
CROSSPLAY_CONFIG_DIR | Move both, so parallel jobs can hold different identities. |
crossplay logout forgets the token on this machine; it does not revoke it. If you think a
token leaked, change your password — that ends every session on the account.