Skip to main content
Documentation

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]
bash

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#

GroupCommands
(top level)status, activity, logs, push, pull, clone, releases, rollback, deploy, add, feedback
authlogin, logout, whoami
appcreate, init, list, files, undeploy, transfer, collaborators, domain, usage, source, update
devstart, kill
testrun, screenshot, accounts
secretslist, set, get, delete, upload, download, pull, configs
workspacenew, attach, sync, list, status, land, drop
integrationslist, 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
bash
FlagDescription
--email <addr>Email address, password accounts only (or $DEEPSPACE_EMAIL)
--password <pw>Password. Discouraged - visible in ps and shell history
--password-stdinRead the password from stdin instead of --password
--jsonSingle-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
bash

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
bash

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
bash
Arg / FlagDescription
[name]App name
--interactivePrompt 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
bash
FlagDescription
--new-idReplace the existing id - forks this repo as a separate app with new data, new secrets, new registration. The original keeps running.
--env <name>, -ewrangler.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:

Outcomestatus / codeNotes
Fresh registrationstatus: "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 idstatus: "forked"previousAppId names the id it replaced; the original app is untouched
Id already registered to youstatus: "already_initialized"exit 0, nothing written
Id was minted locally by an older SDK and never registeredrefuses app_not_registeredships 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_ownerno action: forking is a data decision
DEEPSPACE_APP_ID is present but malformedrefuses invalid_app_idids 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
bash

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
bash
SubcommandArgs / 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
bash
SubcommandArgs / 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
invitesnone - 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
bash
Arg / FlagDescription
[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.
--yesSkip 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
bash

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>
bash
SubcommandPurpose
searchSearch for available domains
buyBuy a domain and attach it to an app
listList your domains
statusShow details for one domain
attachRe-point a domain at a different app
detachStop routing the domain (keeps the registration; auto-renew unchanged)
renewToggle 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
bash

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
bash
Arg / FlagDescription
[provider]github or deepspace. Omit to show.
--app <id|name>, -aTarget 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
bash

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:

FieldMeaning
status / readyaligned is the only ready state. Other successful states are guidance_available, dependency_unverified, cli_version_behind, and version_gap_too_wide.
currentSpec / targetVersionThe app's declared deepspace spec and the running CLI's package version. The CLI you invoked is the sole target authority.
dependenciesManual 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.
migrationsOutstanding 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.
manualInstructionsPolicy or dependency checks that cannot be inferred safely, such as an app-owned users-schema visibility choice.
stepsThe ordered checklist assembled from those fields: edit dependencies, install when needed, apply migration guidance, type-check, test, review, and commit.
guidanceUrlThe 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
bash
Arg / FlagDefaultDescription
[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
bash
FlagDefaultDescription
--port <n>5173 (or $DEEPSPACE_PORT)Port the dev server is bound to
--alloffAlso 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
bash
Arg / FlagDefaultDescription
[suite]smoke + apismoke, 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)
--headedoffRun 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
bash
Arg / FlagDescription
<url>URL to capture (required)
<output>Output image path (required)
--full-pageCapture 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
bash
SubcommandPurpose
createCreate a test account. --name sets the users() selector; omit it and the selector defaults to the email's local part
listList 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
deleteDelete a test account by --email or --id. Immediate - it never prompts, and has no --yes
clearDelete 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
bash

Every subcommand accepts these:

FlagDescription
--app <id>, -aApp id (default: DEEPSPACE_APP_ID from the nearest wrangler.toml)
--config <name>, -cConfig name (default: the --env name, or prd)
--env <name>, -e[env.<name>] slot - selects that env's app id and config
SubcommandExtra 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-
configslist; 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
bash
SubcommandArgs / 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
bash
FlagDescription
--branch <name>, -bBranch to push (default: the current branch)
--app <id|name>, -aTarget app
--forceAllow 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.toml still holds the __APP_ID__ placeholder) this is actionRequired: true and exit 2, carrying an executable app init action - the same tier as deploy's app_not_initialized, since it is the same remedy. app init registers 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
bash

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
bash
ArgDescription
<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
bash
FlagDescription
--app <id|name>, -aTarget 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...
bash
Arg / FlagDescription
[release]Release id from deepspace releases (default: the previous release)
--app, -a / --env, -eTarget app / environment
--allow-do-deletionProceed 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
bash
Arg / FlagDescription
[dir]App directory (default: current directory)
--env <name>, -e[env.<name>] block to deploy. Omit for the top-level config.
--renameConfirm 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-releasedPlatform 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-pushSync DeepSpace source before deploying (default on). GitHub source always deploys the local working tree without Git operations.
--ignore-staleDeploy even if someone released a newer version since you last synced (skips the stale-base guard)
--jsonSingle-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
bash
FlagDescription
--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
bash
FlagDescription
--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)
--followKeep polling for new events
--app <id|name>, -aTarget 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
bash
FlagDescription
--app <id|name>, -aTarget app (default: DEEPSPACE_APP_ID from the nearest wrangler.toml)
--env <name>, -e[env.<name>] slot - reads that env's own app id
--follow, -fKeep 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)
--jsonEmit 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:

  • eventType is log for a console.* line, request for the invocation summary of one request, and exception only for an exception that escaped the Worker (uncaught). An error your code catches and logs - including what the template's app.onError does before answering 500 - is a log line at level error.
  • outcome rides request (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 is outcome: "ok". Triage on request.status, level, and eventType === "exception", never on outcome !== "ok" alone.
  • exception ({ name, message, stack? }) is present on eventType: "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's app.onError does 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
bash
Arg / FlagDescription
[feature]Feature to install (positional)
[dir]App directory (positional, default .)
--list, -lList available features and exit
--info <name>Show details about a feature and exit
--installRun 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 -
bash
SubcommandArgs / 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
bash
Arg / FlagDescription
[title]Short summary (prompted if omitted in a terminal)
--type <t>, -tbug (default), feature, or other
--message <text>, -mDetails / description
--yesSkip 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#

FlagAvailable onPurpose
--help, -hevery commandPrint help
--versiontop-level deepspacePrint CLI version
--jsonnearly every commandSingle-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)
--yesapp undeploy, feedback, integrations invoke, test accounts clearSkip the confirmation prompt

Environment#

VariableUsed byPurpose
DEEPSPACE_EMAILauth loginEmail for a password account (test or internally provisioned)
DEEPSPACE_PASSWORDauth loginPassword for that account. Regular DeepSpace accounts have none
DEEPSPACE_PORTdev, testDefault port when --port is not passed
DEEPSPACE_ENVevery commandSelects 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_URLauth, test accountsOverride auth-worker URL (advanced/testing). Like DEEPSPACE_ENV, selects its own stored credential
DEEPSPACE_API_URLapp, integrations, secretsOverride api-worker URL (advanced/testing)
DEEPSPACE_DEPLOY_URLdeploy, push, pull, releasesOverride deploy-worker URL (advanced/testing)
DEEPSPACE_PLATFORM_URLapp filesOverride 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#