@cross-deck/cli
The Crossdeck command line. Sign in, spin up a project, mint keys, and watch your
revenue, errors, and analytics — without leaving the terminal.
npm i -g @cross-deck/cli
crossdeck
crossdeck on an interactive terminal opens a session (like claude). Inside
it you type bare commands with no prefix, and your active project persists across
the whole session and shows in the prompt:
prolend ❯ login # sign in through your browser
prolend ❯ init --name "My App" --platform web
prolend ❯ revenue
prolend ❯ exit
Two ways to drive it — same engine. Prefer one-shot commands? Everything is
also a plain command, no session: crossdeck login, crossdeck init …,
crossdeck revenue. Use the session by hand; use one-shot in scripts and CI.
Crossdeck opens a session only on an interactive terminal — a pipe or CI
runner runs the command and exits, never trapped in a REPL.
New to Crossdeck? The docs have a word-for-word walkthrough from a blank
machine to a live project with identity wired and a payment rail connected:
cross-deck.com/docs/cli → Zero to a running account.
Two families, one binary
crossdeck does two distinct jobs. They share nothing but the name, on purpose —
so you never trip from one into the other.
| Account — provision & monitor | login, init, projects, apps, use, revenue, analytics, errors | crossdeck login (browser OAuth) | your machine |
| Build — source maps ("the errors CLI") | sourcemaps upload (alias: upload-sourcemaps), doctor | a cd_sk_ secret key | CI |
If you came here to get readable production stack traces, you only need the
Build family — jump to Source maps. It never
asks you to log in.
Account family
Sign in
crossdeck login
crossdeck whoami
crossdeck logout
login uses the same flow as gh auth login and stripe login: it opens a
consent page, you approve, and a short-lived token lands back in the CLI. By
default it requests read + provisioning scopes (projects:write apps:write keys:write) — the consent screen shows you exactly what you're granting. Want a
read-only session? crossdeck login --read-only. Headless box with no browser?
crossdeck login --no-browser prints the URL to open elsewhere.
Your credentials are stored owner-only (chmod 600) at
~/.crossdeck/credentials.json. The long-lived refresh token never leaves that
file; the 1-hour access token lives in memory only and is never written to disk,
argv, or logs. crossdeck logout revokes this machine's session server-side
(RFC 7009) and then wipes the local file — a stolen credentials.json is dead the
moment you log out. Manage and revoke every connected machine from the dashboard's
Settings → Developer → Connected apps.
Zero to installed
crossdeck init --name "My App" --platform web
init creates the project, creates the app, mints its publishable keys, sets the
project active, and prints the install snippet:
Setting up your Crossdeck project
✓ Project proj_a1b2c3d4 — My App
✓ App app_web_9f8e7d (web)
Your publishable keys
live cd_pub_live_…
test cd_pub_test_…
Install — web
import { Crossdeck } from "@cross-deck/web";
Crossdeck.init({ publishableKey: "cd_pub_live_…" });
// after your user signs in:
await Crossdeck.identify(currentUser.id);
Add --env-file to drop the keys into a .env. For native apps, pass
--platform ios --bundle-id com.you.app or --platform android --package-name com.you.app.
Web origins configure themselves. A new web app starts with an empty
allow-list; the first heartbeat from your SDK learns your origin (and its
*.your-domain.com wildcard) automatically. Know your domain up front? Pass
--domain app.example.com and it's seeded so the key works on the first load.
Projects & apps
crossdeck projects create --name "Checkout" --business-model subscription --activate
crossdeck projects list
crossdeck use proj_a1b2c3d4
crossdeck apps create --platform web --domain app.example.com
crossdeck apps create --platform ios --bundle-id com.you.app
crossdeck apps list
use sets the active project so you can drop --project from later commands.
Every command that returns data also takes --json for the raw envelope.
Monitor
crossdeck revenue
crossdeck analytics
crossdeck errors --issue <fingerprint>
errors is the moat in one line: it stitches an error to the people it hit and
how many of them are paying customers — the cross-layer answer no single-tool
error tracker can give.
Source maps (the errors CLI)
This is the CI/build family — key-based, no login. It uploads your .map files
so a production stack trace renders as src/checkout/Pay.tsx:114 — handleSubmit
instead of main.a1b2c3.js:1:48202.
npx @cross-deck/cli sourcemaps upload \
--release v1.2.3 \
--url-prefix https://app.example.com/static/js/ \
./dist
upload-sourcemaps ./dist … is a permanent alias of sourcemaps upload ./dist …
— both work identically.
Auth: a Crossdeck secret key (cd_sk_test_… / cd_sk_live_…), from
/dashboard/developers/api/.
Publishable keys are rejected — source maps reveal source, so they need
server-only credentials.
export CROSSDECK_SECRET_KEY=cd_sk_live_…
Bundler must emit external maps with sourcesContent:
| Vite / Rollup | build: { sourcemap: true } |
| Webpack | devtool: 'source-map' |
| ESBuild | sourcemap: true, sourcesContent: true |
| Next.js | productionBrowserSourceMaps: true |
Avoid eval-source-map / inline-source-map for production — those can't be
uploaded separately.
In CI (GitHub Actions):
- run: npm run build
- env: { CROSSDECK_SECRET_KEY: ${{ secrets.CROSSDECK_SECRET_KEY }} }
run: npx @cross-deck/cli sourcemaps upload --release ${{ github.sha }} --url-prefix https://app.example.com/static/js/ ./dist
crossdeck doctor validates your key, environment, and API reachability without
uploading anything.
Source maps stay private — stored in private Cloud Storage, never served to any
client. Only resolved file:line frames ever leave the backend.
Configuration
CROSSDECK_SECRET_KEY | source maps (CI) | — (required for that family) |
CROSSDECK_PROJECT_ID | account monitor commands | active project from crossdeck use |
CROSSDECK_BASE_URL / --base-url | all | https://api.cross-deck.com |
CROSSDECK_CONFIG_DIR | credential + config location | ~/.crossdeck |
Flags always override environment variables.
Exit codes
0 | Success |
1 | Runtime error (auth, network, API rejection — carries a request_id) |
2 | Argument / flag validation error |
Security
- PKCE-S256 + loopback —
login binds a one-shot listener to 127.0.0.1
only, with a random state checked byte-for-byte on return.
- Least-privilege, consented — the token is the narrowest that serves the
session; write access is a visible step on the consent screen, never assumed.
- Custody — refresh token
chmod 600, rotated on every use; access token
memory-only; loose file permissions are refused with the exact fix.
- One boundary — read and write authenticate through the same gate, one scope
model, one audit chain. Every write you make is audit-logged exactly like the
dashboard's.
License
MIT