Command reference
Per-command flags and examples for every `deepspace` command (large page - prefer search for one command).
On this page
This page documents every CLI command. For an overview and the dev loop, see CLI overview.
npx deepspace <command> [options]
The surface is noun-verb: related commands are grouped under a noun (auth, app, dev, test, secrets, workspace, integrations). Running a group on its own prints its subcommands - deepspace dev does not start a server, deepspace dev start does.
All commands support --help (or -h). The top-level deepspace command also accepts --version. Every command listed here supports --json unless noted.
Command map#
| Group | Commands |
|---|---|
| (top level) | status, activity, logs, push, pull, clone, releases, rollback, deploy, add, feedback |
auth | login, logout, whoami |
app | create, init, list, files, undeploy, transfer, collaborators, domain, usage, source, update |
dev | start, kill |
test | run, screenshot, accounts |
secrets | list, set, get, delete, upload, download, pull, configs |
workspace | new, attach, sync, list, status, land, drop |
integrations | list, info, invoke |
auth#
Session management. Login is shared across every app on the machine and stored at ~/.deepspace/session.
auth login#
Opens a browser to sign in with GitHub or Google. DeepSpace accounts are OAuth-only.
npx deepspace auth login
| Flag | Description |
|---|---|
--email <addr> | Email address, password accounts only (or $DEEPSPACE_EMAIL) |
--password <pw> | Password. Discouraged - visible in ps and shell history |
--password-stdin | Read the password from stdin instead of --password |
--json | Single-line JSON result |
Password login refuses through the same envelope as every other command: a 401 from the auth service is invalid_credentials (wrong email or password); any other refusal - or a session issued without a cookie or token - is login_failed. A --json/non-TTY call with no credentials is interactive_required (above). Credentials are stored per auth plane, so a login under DEEPSPACE_ENV=staging or a DEEPSPACE_AUTH_URL override never touches your production session - see login state.
auth logout#
Sign out and remove cached credentials.
npx deepspace auth logout
auth whoami#
Print the current login state. This is the canonical login probe - it refreshes the JWT through the same path dev, test, and deploy use.
npx deepspace auth whoami
npx deepspace auth whoami --json
On not-signed-in it prints an error to stderr and exits non-zero.
app#
Everything about the app as a platform object: its identity, its files, who can deploy it, and where its source lives.
app create#
Scaffold a new app. Runs create-deepspace; all flags are forwarded.
npx deepspace app create my-app
npx deepspace app create my-app --template copilot
| Arg / Flag | Description |
|---|---|
[name] | App name |
--interactive | Prompt for options instead of using defaults |
--template <name> | Starter template: starter (default) or copilot |
--local <path> | Use a local SDK monorepo checkout instead of the published package |
npm create deepspace@latest my-app does the same thing without needing the CLI on PATH.
app init#
Mint this app's immutable DEEPSPACE_APP_ID into wrangler.toml.
npx deepspace app init
npx deepspace app init --new-id # fork this repo as a SEPARATE app
| Flag | Description |
|---|---|
--new-id | Replace the existing id - forks this repo as a separate app with new data, new secrets, new registration. The original keeps running. |
--env <name>, -e | wrangler.toml [env.<name>] block to stamp (each env is its own app) |
The app id is the app's permanent identity. The name field in wrangler.toml is only the subdomain label; data, secrets, and collaborators key to the id, so renames are safe.
"Already initialized" means registered, not merely id-shaped: init verifies the id with the platform before claiming done. The outcomes, and what --json carries:
| Outcome | status / code | Notes |
|---|---|---|
| Fresh registration | status: "registered" | appId, committedScaffold (true when init made the initial commit), and a git commit … wrangler.toml action when the file is left uncommitted |
--new-id on a registered id | status: "forked" | previousAppId names the id it replaced; the original app is untouched |
| Id already registered to you | status: "already_initialized" | exit 0, nothing written |
| Id was minted locally by an older SDK and never registered | refuses app_not_registered | ships the executable app init --new-id action - there is no server-side state to migrate, so the fork is the one next command |
| Id belongs to another account (a cloned repo) | refuses not_app_owner | no action: forking is a data decision |
DEEPSPACE_APP_ID is present but malformed | refuses invalid_app_id | ids are server-minted, so a malformed one was hand-edited - restore it, or pass --new-id to replace it; the result then carries replacedMalformed naming the value it overwrote |
Every result also names the plane and the slot: env is the platform plane the registration lives on (production unless DEEPSPACE_ENV says otherwise - the same field status --json reports), and wranglerEnv is the [env.<name>] block (null for the top level).
app list#
List every app you can access - both apps you own and apps shared with you as a collaborator, deployed and registered alike. The ROLE column shows your access on each.
npx deepspace app list
npx deepspace app list --json
app files#
Upload and manage the app's own files allocation - images, media, and other assets served from the app's origin.
npx deepspace app files put logo.png
npx deepspace app files put hero.jpg --key img/hero.jpg
npx deepspace app files list --prefix img/
npx deepspace app files get img/hero.jpg --out ./hero.jpg
npx deepspace app files rm img/hero.jpg
| Subcommand | Args / Flags |
|---|---|
put | <file> (required), --key <k> (default: the file name), --app <id|name> |
list | --prefix <p>, --limit <n> (1-1000, default 100), --app <id|name> |
get | <key> (required), --out <path> (default: the key's file name), --app <id|name> |
rm | <key> (required), --app <id|name> |
Keys are relative to the app (logo.png, img/hero.jpg). The platform owns the physical prefix and validates every key against it, so a key can never address another app. That prefix is part of the serving URL: files are reachable from your app's origin at /api/files/apps/<resourceId>/<key>?scope=app, which put prints for you (and returns as path under --json). The relative key alone does not resolve.
See large files and media for when to reach for this instead of Git.
app collaborators#
Authorize other people to deploy your app. See the collaborators guide for the full model.
npx deepspace app collaborators list
npx deepspace app collaborators add teammate@example.com
npx deepspace app collaborators remove teammate@example.com
npx deepspace app collaborators cancel invited@example.com
npx deepspace app collaborators invites # invites waiting for YOUR email
npx deepspace app collaborators accept app_01H… # accept one, no email link needed
| Subcommand | Args / Flags |
|---|---|
list | --app <id|name>, -a |
add | <email> (required), --app <id|name> |
remove | <email> (required), --app <id|name> |
cancel | <email> (required), --app <id|name> - cancels a pending, un-accepted invite |
invites | none - lists pending invites addressed to the signed-in email |
accept | <app-id> (required) - accepts an invite as the signed-in invitee |
app undeploy#
Remove a deployed app.
npx deepspace app undeploy # reads DEEPSPACE_APP_ID from wrangler.toml
npx deepspace app undeploy my-app # explicit app id or subdomain name
| Arg / Flag | Description |
|---|---|
[name] | App id or subdomain name (positional). Reads wrangler.toml if omitted. |
--env <name> | [env.<name>] block whose deployed app to remove. Ignored if a positional name is given. |
--yes | Skip the interactive confirmation |
At an interactive terminal undeploy confirms before the URL goes dark, and the default answer is No. The prompt names the app (its wrangler name and id) and states exactly what happens: the URL stops serving immediately and the app's data - records, messages, canvas state, cron history - is destroyed with the worker's Durable Objects, while secrets and the registration stay and the name is reserved for you for 30 days. Declining refuses with the code undeploy_declined and changes nothing. Pass --yes to skip the prompt. Scripts and agents are never prompted: if stdin is not a TTY, or --json was passed, the command runs straight through - the invocation is its consent - so a piped or --json caller cannot hang on it.
Undeploying an app that is not serving is a no-op, and says so: the result carries alreadyUndeployed: true with an empty releasedHosts, and the human line reads "was already offline". A real takedown lists the released hosts and alreadyUndeployed: false. Either way the app keeps its id; a later deploy revives it - see undeploy and revival.
app transfer#
Transfer app ownership to another DeepSpace user. Two-sided: the owner offers, the recipient accepts.
npx deepspace app transfer offer newowner@example.com
npx deepspace app transfer status
npx deepspace app transfer accept
npx deepspace app transfer cancel # either party
app domain#
Buy, attach, and manage custom domains. See the custom domains guide.
npx deepspace app domain search <query>
npx deepspace app domain buy <domain>
npx deepspace app domain list
npx deepspace app domain status <domain>
npx deepspace app domain attach <domain>
npx deepspace app domain detach <domain>
npx deepspace app domain renew <domain>
| Subcommand | Purpose |
|---|---|
search | Search for available domains |
buy | Buy a domain and attach it to an app |
list | List your domains |
status | Show details for one domain |
attach | Re-point a domain at a different app |
detach | Stop routing the domain (keeps the registration; auto-renew unchanged) |
renew | Toggle auto-renewal at the registrar |
app usage#
Show credit balance, quota headroom, and per-integration spend.
npx deepspace app usage
npx deepspace app usage --json
app source#
Show or change the app's one authoritative Git repository.
npx deepspace app source # show current
npx deepspace app source deepspace # DeepSpace-hosted cloud repo
npx deepspace app source github # your own GitHub repo
| Arg / Flag | Description |
|---|---|
[provider] | github or deepspace. Omit to show. |
--app <id|name>, -a | Target app (default: current app) |
--remote <name> | GitHub remote name when it cannot be inferred (default: origin) |
An app has exactly one source of record. Under deepspace source, push/pull/clone/workspace operate on the cloud repo and deploy synchronizes before shipping. Under github source, you manage commits with normal Git and deploy ships the local working tree without touching Git.
Changing the provider also reconciles the local space git remote, reported as spaceRemote (removed | absent | present) in --json and as a line under Source: in human output - see one authority, one remote.
app update#
Inspect an app and print the exact work needed to move it to the running CLI's SDK version. Run the target CLI before installing the target SDK - it owns that release's checklist:
npx deepspace@latest app update --json
app update is deliberately read-only: it does not edit package.json, rewrite source, stamp deepspace.migrations.json, run an installer, or require DeepSpace source control. Every successful result carries writes: [] and no executable action.
The JSON result contains:
| Field | Meaning |
|---|---|
status / ready | aligned is the only ready state. Other successful states are guidance_available, dependency_unverified, cli_version_behind, and version_gap_too_wide. |
currentSpec / targetVersion | The app's declared deepspace spec and the running CLI's package version. The CLI you invoked is the sole target authority. |
dependencies | Manual package.json edits, including the compatible direct ai dependency when the app declares one. Local, workspace, VCS, and URL SDK specs are left under the developer's control and reported as unverified. |
migrations | Outstanding app-owned changes, each with an id, description, candidate files, and guidance. After applying and validating one, add its id to deepspace.migrations.json; mark it not applicable there when the named seam does not exist. |
manualInstructions | Policy or dependency checks that cannot be inferred safely, such as an app-owned users-schema visibility choice. |
steps | The ordered checklist assembled from those fields: edit dependencies, install when needed, apply migration guidance, type-check, test, review, and commit. |
guidanceUrl | The release-specific migration reference. |
A malformed package.json or deepspace.migrations.json refuses with invalid_package_manifest or invalid_migration_manifest; neither file is repaired automatically. A wide version gap is a successful version_gap_too_wide guide rather than a failed partial migration. The full sequence, and what to do with a scaffold that predates server-minted ids, is in Updating an app.
dev#
dev start#
Run the app locally with Vite and the worker in-process, with HMR.
npx deepspace dev start
npx deepspace dev start ./my-app
npx deepspace dev start --port 5180 # parallel apps
npx deepspace dev start --env staging
| Arg / Flag | Default | Description |
|---|---|---|
[dir] | . | App directory (positional) |
--port <n> | 5173 (or $DEEPSPACE_PORT) | Port to bind |
--env <name>, -e | - | [env.<name>] block to run. Applies the env's overrides at build time. |
dev start regenerates .dev.vars whole on every run - SDK-managed keys plus the selected config from the secrets store. Hand edits are overwritten; set values with secrets set, and restart dev to pick up store changes. If the store refresh fails, dev start aborts rather than run against stale values.
Under --json, the server's own output still streams through. dev start emits two envelopes: a readiness line ({"ok":true,"ready":true,"url":...}) the moment the server is up, and a final exit envelope when it stops - the last line of output, not the only one.
dev kill#
Stop the local dev server and any orphaned workerd processes.
npx deepspace dev kill
npx deepspace dev kill --port 5180
npx deepspace dev kill --all
| Flag | Default | Description |
|---|---|---|
--port <n> | 5173 (or $DEEPSPACE_PORT) | Port the dev server is bound to |
--all | off | Also kill stray workerd/wrangler processes across all ports |
test#
test run#
Run tests against the dev workers. Auto-installs Playwright and Chromium on first run.
npx deepspace test run # default = smoke + api
npx deepspace test run smoke
npx deepspace test run e2e
npx deepspace test run unit # vitest
npx deepspace test run all
npx deepspace test run tests/checkout.spec.ts # one file
npx deepspace test run --port 5180
npx deepspace test run e2e --grep "sign in" # only matching Playwright tests
| Arg / Flag | Default | Description |
|---|---|---|
[suite] | smoke + api | smoke, api, e2e, unit, all, or a path ending in .spec.ts |
--port <n> | 5173 (or $DEEPSPACE_PORT) | Port for Vite / Playwright's webServer |
--env <name>, -e | - | [env.<name>] block to test (uses secrets config <name> by default) |
--grep <pattern> | - | Run only Playwright tests whose title matches (forwarded to Playwright --grep) |
--project <name> | - | Run only the named Playwright project (forwarded to --project) |
--headed | off | Run Playwright headed instead of headless |
The default suite (smoke + api) does not include e2e; run test run e2e or test run all to exercise end-to-end specs, including any you add.
An unrecognized [suite] refuses with unknown_suite and names every way to narrow a run - the five suite names, a path to a single spec file (tests/<name>.spec.ts), and --grep <pattern> for part of a suite. Without those, the reader's next move was moving spec files out of tests/ to isolate one.
test run requires a logged-in user - it mints an app-owner JWT into the regenerated .dev.vars.
The Playwright dependency preflight writes to stderr under --json, like the suite itself, so it cannot corrupt the stdout envelope. On Linux as root it also announces itself before starting: installing Chromium's system libraries shells out to apt-get, which is minutes of transcript on a cold container - the line up front is what distinguishes that from a hang.
Under --json the suite streams on stderr and stdout carries exactly one JSON envelope, so npx deepspace test run --json | jq works with no last-line handling and the live Playwright/vitest transcript stays visible. (test screenshot --json still inherits its child's stdout, so its envelope is the last line there.)
The envelope names what the run left out: skippedSpecs lists the spec files the chosen suite did not run - the default smoke + api suite reports every spec outside it - so a green result cannot be mistaken for full coverage. Human output prints the same fact as a skipped N spec file(s) ... line.
test screenshot#
Capture a Playwright screenshot of any URL. Shares the same Chromium install as test run.
npx deepspace test screenshot http://localhost:5173/ out.png
npx deepspace test screenshot http://localhost:5173/ out.png --full-page
npx deepspace test screenshot http://localhost:5173/ mobile.png --viewport 390x844
npx deepspace test screenshot http://localhost:5173/ out.png --wait-for-timeout 500
| Arg / Flag | Description |
|---|---|
<url> | URL to capture (required) |
<output> | Output image path (required) |
--full-page | Capture the full scrollable page |
--viewport <WIDTHxHEIGHT> | Set positive browser viewport dimensions, for example 390x844 |
--wait-for-timeout <ms> | Milliseconds to wait before capture |
test accounts#
Manage @deepspace.test accounts for multi-user Playwright tests. Unlike your own account, these sign in with email and password - that is what makes them usable as fixtures. They are heavily restricted in exchange; see restrictions. Only an OAuth-authenticated developer can create them.
npx deepspace test accounts list
npx deepspace test accounts create
npx deepspace test accounts delete --email alice-1@deepspace.test
npx deepspace test accounts clear
| Subcommand | Purpose |
|---|---|
create | Create a test account. --name sets the users() selector; omit it and the selector defaults to the email's local part |
list | List your test accounts. --usable shows only those with locally saved credentials (the ones users() can drive); passwords are masked unless you pass --reveal. --json adds a usableByFixture flag, and honours --reveal the same way: without it the password field is omitted, with it the locally saved password (or null) is included |
delete | Delete a test account by --email or --id. Immediate - it never prompts, and has no --yes |
clear | Delete all your test accounts (or those matching --label). This one confirms, and --yes skips it |
Every account prints a Selector: - the string to pass users(['Selector']). Test accounts cannot deploy apps or be added as collaborators.
create never echoes the password, on either surface. You supplied it with --password, so repeating it only puts a live credential into terminal scrollback, CI logs, and agent transcripts. It is written 0600 to the local test-accounts file, and both surfaces name that file instead - savedTo in --json. Read it back deliberately with test accounts list --reveal.
delete is unambiguous - one named account - which is why it runs immediately; clear, which empties the whole pool, is the verb that confirms. delete --help says so, so a teardown script does not have to discover it by hanging.
secrets#
Manage the app's secrets store. Secrets live in named configs (default prd); --env <name> selects that environment's app id and its config. The store is the only deploy input - .dev.vars is a generated cache. The secrets guide carries the model, caps, and troubleshooting.
npx deepspace secrets list
npx deepspace secrets set STRIPE_KEY=sk_live_... OPENAI_KEY=sk-...
npx deepspace secrets get STRIPE_KEY --plain
npx deepspace secrets delete STRIPE_KEY
npx deepspace secrets upload .env.production
npx deepspace secrets download --format json
npx deepspace secrets pull # refresh the .dev.vars cache
npx deepspace secrets configs list
Every subcommand accepts these:
| Flag | Description |
|---|---|
--app <id>, -a | App id (default: DEEPSPACE_APP_ID from the nearest wrangler.toml) |
--config <name>, -c | Config name (default: the --env name, or prd) |
--env <name>, -e | [env.<name>] slot - selects that env's app id and config |
| Subcommand | Extra flags |
|---|---|
list | --only-names - print names only, omitting values and metadata |
set | <KEY=value> (repeatable, required) |
get | <key> (required), --plain - print the plaintext value |
delete | <key> (repeatable, required) |
upload | <file> (required; - for stdin), --replace - delete keys missing from the file |
download | --format dotenv|json|shell (default dotenv) |
pull | - |
configs | list; create <name> with --copy-from <existing> for a server-side copy (refuses to copy over an existing config); delete <name> |
The app is resolved before the token is read, through the same resolver deploy uses, so a missing target never surfaces as not_authenticated: outside an app directory it is not_in_app_repo, inside one whose wrangler.toml has no id it is app_not_initialized (with the app init action), and a malformed id is invalid_app_id. upload with a path that does not exist refuses file_not_found (pass a dotenv/JSON path, or - for stdin). A transport failure reaching the deploy service - offline, DNS, refused connection - is network_error, naming the URL and DEEPSPACE_DEPLOY_URL, rather than a bare fetch failed.
workspace#
Durable parallel-agent workspaces: a server-side ref plus a local worktree, so several agents or people can work on one app without colliding.
npx deepspace workspace new -t "add billing page"
npx deepspace workspace status
npx deepspace workspace sync
npx deepspace workspace land
npx deepspace workspace list
npx deepspace workspace attach ws_...
npx deepspace workspace drop
| Subcommand | Args / Flags |
|---|---|
new | --task <text>, -t (required), --base <rev>, --dir <path> (default .deepspace/ws/<id>), --app |
attach | <id> (required), [dir], --app |
sync | --workspace <id>, -w (default: inferred from the ws/<id> branch), --app |
list | --all (include landed/dropped), --limit <n> (default 50), --app |
status | --workspace <id>, -w, --app |
land | --into <branch> (default: cloud repo default branch), --workspace <id>, --keep-worktree, --validate, --app |
drop | [id] (default: inferred from the branch), --keep-worktree, --app |
land --validate runs the project validation on the merged tree before publishing trunk, and exits 2 on failure.
Version control#
These operate on the app's cloud repo when the app uses deepspace source. Under github source all three refuse identically with source_managed_by_github, whose --json carries repository and appId - use normal Git instead. See the one refusal push, pull, and clone share.
push#
Push local git commits to the app's cloud repo.
npx deepspace push
npx deepspace push --branch feature-x
| Flag | Description |
|---|---|
--branch <name>, -b | Branch to push (default: the current branch) |
--app <id|name>, -a | Target app |
--force | Allow a non-fast-forward ref move. Guarded: refuses whenever the remote tip is a commit your branch does not contain, so no work is silently dropped. |
Two different empty-repo refusals, deliberately kept apart:
no_commits- the branch genuinely has no commits. On an uninitialized scaffold (wrangler.tomlstill holds the__APP_ID__placeholder) this isactionRequired: trueand exit 2, carrying an executableapp initaction - the same tier asdeploy'sapp_not_initialized, since it is the same remedy.app initregisters the app and makes the initial commit, so committing first would put an unregistered placeholder into history. On an already-registered app the same code stays an ordinary exit 1 with no action: "commit first" is not one command.unknown_branch- the repo has commits but the named branch does not exist (a typo'd-b). Committing on the current branch would never make the missing ref appear, so this gets its own code and remedy: create or switch to the branch first.
push, pull, and deploy share one more guard: a worktree in the middle of a merge, rebase, cherry-pick, or revert refuses merge_in_progress before touching the network. HEAD is the pre-operation commit then, so a push would publish a commit carrying none of the in-flight work and report success, and a pull would report up_to_date having touched nothing. The refusal names both remedies (--continue or --abort) and ships no single action, because finishing and abandoning are different decisions.
pull#
Fetch the app's cloud repo and fast-forward the local branch.
npx deepspace pull
npx deepspace pull --branch main
Exits 2 only when one executable local continuation remains.
clone#
Clone an app's cloud repo into a new directory.
npx deepspace clone my-app
npx deepspace clone app_01ABC... ./local-dir
| Arg | Description |
|---|---|
<app> | App id (app_...) or subdomain name (required) |
[dir] | Target directory (default: the app name you passed) |
releases#
List the app's deploy history. Releases are immutable.
npx deepspace releases
npx deepspace releases --limit 50
| Flag | Description |
|---|---|
--app <id|name>, -a | Target app (default: the surrounding app directory) |
--env <name>, -e | [env.<name>] slot - selects that environment's app id |
--limit <n> | Max entries (default 20) |
Under --json, each entry carries rollbackAvailable - storage pressure can evict an old bundle while its ledger row remains. What a release records depends on the app's source mode; see Releases and rollback.
releases, status, and activity describe a release's source through one shared formatter, so a GitHub-source release reads GitHub · owner/repo on all three human surfaces instead of the "no source recorded" they used to print while --json knew better. The full mapping is in how a release names its source.
rollback#
Re-deploy a prior release's exact bundle. No rebuild happens.
npx deepspace rollback # the previous release
npx deepspace rollback rel_01ABC...
| Arg / Flag | Description |
|---|---|
[release] | Release id from deepspace releases (default: the previous release) |
--app, -a / --env, -e | Target app / environment |
--allow-do-deletion | Proceed even though the target release declares fewer Durable Object classes than the current one |
A release whose bundle was evicted refuses with no_bundle - pick another release marked rollbackAvailable in releases --json. Every rollback appends a new release fact; see Releases and rollback.
deploy#
Build and deploy the app.
npx deepspace deploy
npx deepspace deploy ./my-app
npx deepspace deploy --env staging
| Arg / Flag | Description |
|---|---|
[dir] | App directory (default: current directory) |
--env <name>, -e | [env.<name>] block to deploy. Omit for the top-level config. |
--rename | Confirm that a changed wrangler name renames this app. Its URL moves; data, secrets, and collaborators travel - the display name does not. Without the flag an interactive prompt asks. |
--claim-released | Platform admins only: claim a name still inside its 30-day release cooldown. The previous owner permanently loses their reserved reclaim. Non-admin accounts are refused. |
--push / --no-push | Sync DeepSpace source before deploying (default on). GitHub source always deploys the local working tree without Git operations. |
--ignore-stale | Deploy even if someone released a newer version since you last synced (skips the stale-base guard) |
--json | Single-line JSON result; human output goes to stderr |
The subdomain comes from the name field in wrangler.toml. Names are globally unique across all of DeepSpace - a name already claimed by another app is refused with The name <host> is taken by another app. A repo without a DEEPSPACE_APP_ID gets one minted and written into wrangler.toml by its first deploy - commit it.
Secrets ship from the app's remote store; deploy never reads .dev.vars (it rewrites it). If the selected secrets config does not exist, deploy refuses with secrets_config_missing and returns the executable secrets configs create action - see missing is not empty.
The success envelope reports what tree shipped as branch and dirty (both null outside a usable Git repo), and, when the deploy renamed the app, renamedFrom plus staleDisplayName. See what a release records and renaming an app.
Once the edge confirms the release (serving: confirmed), deploy sends the new worker one request - the template's own GET /api/auth/ok health route - so the app's Durable Objects exist before any visitor arrives; that is what arms a deployed cron schedule. It is best effort: if the wake fails, the first real request arms it instead.
Who may deploy, and where from. forbidden names both sides: the app id and the account you are signed in as (app_… belongs to another account - you are signed in as you@example.com, who is neither its owner nor a collaborator), and the two remedies - have the owner run app collaborators add <your email>, or app init --new-id to publish the code as your own app. An id no server has registered (an older SDK's locally minted scaffold) is app_not_registered, with app init --new-id as the remedy.
One deploy at a time, per checkout and per app. Two deploys of one directory race on the shared dist/, so deploy takes a local lock at .deepspace/deploy.lock before any work and refuses a second run with deploy_in_progress, naming the other run's pid and start time (lockPath and holder in --json). A lock left by a dead process is reclaimed automatically; only remove it by hand when no deploy is running. Separately, when another deploy of the same app from another checkout is between prepared and live, the platform answers release_in_progress: this run built and uploaded fine but did not release, so it exits 2 with an executable retry action - wait a moment and run the same deploy again, and check deepspace releases if it keeps refusing for more than a couple of minutes.
status#
One screenful of current state: environment, session, app, workspace, and live release. The fastest orientation command in a fresh shell or sandbox.
npx deepspace status
npx deepspace status --json
| Flag | Description |
|---|---|
--env <name>, -e | [env.<name>] slot - reads that environment's app identity |
The environment line is always first, and never elided. status opens with Env naming the plane the next command will mutate (production unless DEEPSPACE_ENV says otherwise), plus any of DEEPSPACE_DEPLOY_URL / DEEPSPACE_AUTH_URL / DEEPSPACE_API_URL / DEEPSPACE_PLATFORM_URL that are set:
Env production
A second Services line spelling out the four resolved service URLs is printed only when there is something to notice - a non-production plane, or at least one URL override:
Env staging · DEEPSPACE_DEPLOY_URL set
Services auth https://... · api https://... · platform https://... · deploy https://...
--json carries both unconditionally: env is the plane string and services is the resolved { auth, api, platform, deploy } object, whether or not the human Services line was printed. urlOverrides lists the override variable names when any are set. Check env before any mutating command rather than inferring the plane from the shell.
activity#
The app's coordination feed: pushes, workspaces, releases.
npx deepspace activity
npx deepspace activity --follow
npx deepspace activity --since now --limit 100
| Flag | Description |
|---|---|
--since <cursor> | Events after this cursor, or now. One-shot defaults to 0; --follow defaults to now. |
--limit <n> | Max events per page (default 50) |
--follow | Keep polling for new events |
--app <id|name>, -a | Target app |
One-shot --json returns a single document - { events, cursor, hasMore }; page with --since while hasMore is true, and persist the last cursor for continuity. --follow --json is an NDJSON stream: one ready frame ({"type":"ready","appId":...,"cursor":...}), then one activity frame per event carrying the event and its cursor. Recoverable polling failures emit transport frames with "state":"retrying" and never advance the cursor. Events are facts, not instructions.
logs#
Show production logs for a deployed app.
npx deepspace logs
npx deepspace logs --follow
npx deepspace logs --since 2h --level error
npx deepspace logs --search "checkout" --json
| Flag | Description |
|---|---|
--app <id|name>, -a | Target app (default: DEEPSPACE_APP_ID from the nearest wrangler.toml) |
--env <name>, -e | [env.<name>] slot - reads that env's own app id |
--follow, -f | Keep polling for new logs until Ctrl+C (~3s cadence) |
--since <window> | 30s, 15m, 2h, 24h, 7d, or an ISO timestamp (default 15m) |
--level <lvl> | debug, log, info, warn, error |
--search <text> | Only events whose message contains this text |
--limit <n> | Max events per fetch (default 100, max 500) |
--json | Emit NDJSON - one event object per line |
--json is NDJSON in both snapshot and follow modes. Log events never carry a type field; frames that do are metadata: an empty snapshot window emits {"type":"meta","count":0,...} so "ran, no events" is distinguishable from a crash, and a truncated snapshot emits {"type":"meta","truncated":true} - narrow with --since/--level, or raise --limit.
An app that has never been deployed has no logs and never will until it is, so logs refuses app_not_deployed instead of printing an empty window - with the deploy action when you are in the app's own checkout.
Reading an event#
Each event is { id, timestamp, level, eventType, message, source?, outcome?, request?, exception? }. Three fields are easy to misread:
eventTypeislogfor aconsole.*line,requestfor the invocation summary of one request, andexceptiononly for an exception that escaped the Worker (uncaught). An error your code catches and logs - including what the template'sapp.onErrordoes before answering 500 - is alogline at levelerror.outcomeridesrequest(invocation) events only and is the runtime's verdict on whether the Worker returned (ok,exception,exceededCpu,canceled, …) - not whether the app succeeded. An action that throws inside the app's own error handling and answers 500 isoutcome: "ok". Triage onrequest.status,level, andeventType === "exception", never onoutcome !== "ok"alone.exception({ name, message, stack? }) is present oneventType: "exception"and on browser-reported errors (source: "client"). The Workers runtime renders a logged Error object as its stack frames only - the message never reaches the store - so when you catch and log, put the message in the line itself:console.error(`[error] … ${err.message}`, err.stack). The scaffold'sapp.onErrordoes exactly that, and so do the SDK's own rooms.
Scheduled work logs one line per run - [cron] <task> ok <ms>ms or [cron] <task> failed <ms>ms: <message> (the stack rides along as plain text) - so logs --search cron finds every run by task name; a failed background job logs [jobs] <type> (<id>) failed: <message> the same way. See scheduled jobs.
add#
Install a scaffold feature into the current app.
npx deepspace add --list
npx deepspace add --info messaging
npx deepspace add messaging
npx deepspace add messaging ./my-app --install
| Arg / Flag | Description |
|---|---|
[feature] | Feature to install (positional) |
[dir] | App directory (positional, default .) |
--list, -l | List available features and exit |
--info <name> | Show details about a feature and exit |
--install | Run your package manager after adding dependencies |
Run --list for the current feature set - it changes between releases.
integrations#
Discover and call third-party integration endpoints, billed to the logged-in user.
npx deepspace integrations list
npx deepspace integrations info openai/chat-completion
npx deepspace integrations invoke openai/chat-completion --body '{"messages":[]}'
npx deepspace integrations invoke openai/chat-completion --body-file req.json
cat req.json | npx deepspace integrations invoke openai/chat-completion --body-file -
| Subcommand | Args / Flags |
|---|---|
list | --json |
info | <target> (required) - <integration>/<endpoint> |
invoke | <target> (required), --body <json> / -d, --body-file <path> / -f (- for stdin), --timeout <ms> (default 120000), --yes / -y to skip the paid-call cost confirmation |
A paid call needs consent. Without --yes, invoke looks the endpoint's price up first. At an interactive terminal (stdin and stdout are TTYs, no --json) it asks <endpoint> costs <price>, billed to your account. Continue? with the default No - declining is a success (cancelled: true), nothing was billed. Anywhere else - --json, a piped stdin such as --body-file -, CI - it refuses cost_confirmation_required before any call is made; pass --yes to confirm the spend. Free endpoints proceed without a prompt.
info prints an example body you can copy: the catalog's own example when it has one, otherwise one synthesized from the input schema's required keys with placeholders from the schema (example, default, first enum) or the type ("<string>", 0, false), so the body an agent copies is never {} for an endpoint that rejects {}. --json carries the same body as example; it is null only when neither the catalog nor the schema says anything.
feedback#
Submit a bug report or feature request to DeepSpace.
npx deepspace feedback "Deploy hangs on large assets"
npx deepspace feedback "Add dark mode" --type feature -m "Details here" --yes
| Arg / Flag | Description |
|---|---|
[title] | Short summary (prompted if omitted in a terminal) |
--type <t>, -t | bug (default), feature, or other |
--message <text>, -m | Details / description |
--yes | Skip the confirmation prompt |
git-credential#
Git credential helper. Installed automatically by the CLI so git can authenticate against the cloud repo. Not for direct use.
Global flags#
| Flag | Available on | Purpose |
|---|---|---|
--help, -h | every command | Print help |
--version | top-level deepspace | Print CLI version |
--json | nearly every command | Single-line JSON envelope for scripts and agents (logs and activity --follow emit NDJSON; dev start / test screenshot envelopes follow the child stream on stdout; test run streams the suite on stderr and keeps stdout to the one envelope) |
--yes | app undeploy, feedback, integrations invoke, test accounts clear | Skip the confirmation prompt |
Environment#
| Variable | Used by | Purpose |
|---|---|---|
DEEPSPACE_EMAIL | auth login | Email for a password account (test or internally provisioned) |
DEEPSPACE_PASSWORD | auth login | Password for that account. Regular DeepSpace accounts have none |
DEEPSPACE_PORT | dev, test | Default port when --port is not passed |
DEEPSPACE_ENV | every command | Selects the platform plane (production when unset). Also selects which stored credential the CLI reads - each plane keeps its own ~/.deepspace/session pair, see login state |
DEEPSPACE_AUTH_URL | auth, test accounts | Override auth-worker URL (advanced/testing). Like DEEPSPACE_ENV, selects its own stored credential |
DEEPSPACE_API_URL | app, integrations, secrets | Override api-worker URL (advanced/testing) |
DEEPSPACE_DEPLOY_URL | deploy, push, pull, releases | Override deploy-worker URL (advanced/testing) |
DEEPSPACE_PLATFORM_URL | app files | Override platform-worker URL (advanced/testing) |
DEEPSPACE_EMAIL and DEEPSPACE_PASSWORD are consulted only when stdin is non-interactive or --json was passed, so ambient values in a dev shell cannot hijack an interactive deepspace auth login.
See also#
- CLI overview - dev loop, and running agents and CI as yourself
- Collaborators - sharing deploy access
- Quickstart - first-time walkthrough
- Custom domains guide - purchase and attach flow
- Testing guide - the test runner in context