CLI guide
The cloud from the terminal
The BusinessStack CLI (os) talks to BusinessOS Cloud through the same routes the console uses. Anything a button does, a command or a curl does too.
One set of routes
The cloud has no separate CLI: the cabinet serves the same routes (/api/v1/cloud/*, /api/v1/marketplace/*) the BusinessStack CLI works with, so its commands work unchanged once OS_CLOUD_URL points here. Console actions post to the same routes (ADR-0006), which is why support can reproduce any UI action from a shell.
Install and point at the cloud
npm i -g @objectstack/cli # installs the `os` command
os --version # 18.x
export OS_CLOUD_URL=https://bos.virtuman.com # without it the CLI points at a different cloud — always set this
| Variable | Meaning |
|---|---|
OS_CLOUD_URL | Cabinet base URL. Also read by a runtime's cloud-connection (tenants get it from the provisioner). |
OS_CLOUD_API_KEY / OS_TOKEN | Bearer for cloud routes; overrides the stored credential. Use in CI. |
OS_CLOUD_TIMEOUT_MS | HTTP timeout for publish/install (0 disables). |
Sign in
os cloud login # device flow: prints a verification URL, waits for approval in the browser
os cloud login --email you@example.ru --password '…' # non-interactive; social/SSO accounts must use the browser flow
os cloud whoami
os cloud logout
Credentials are stored in ~/.objectstack/cloud.json (used by os package publish) and ~/.objectstack/credentials.json (active environment for os environments …). --force re-authenticates, --no-browser prints the URL only, --json gives NDJSON for scripts.
Everything you do is scoped to the account (organization) you signed in with. Operators see fleet routes on top; customers see only their own rows — same wall as the console.
Environments
os environments list
os environments show <id>
# Create — same entitlement rules as the console button
os environments create --name "Production" --org <accountId> --plan free
os environments create --name "Dev 1" --org <accountId> # the second and later ones are development environments; needs plan feature dev_environments
# Make one the default target for later commands (--no-remote skips /activate on the server;
# for a running environment the cabinet answers /activate as a no-op)
os environments switch <id>
# Bind a locally compiled app into an environment, optionally compiling first and reseeding
os environments bind <id> --artifact ./dist/objectstack.json --build --reseed
Create answers 201 {environment, warnings, durationMs}; the environment then provisions for 2–3 minutes. Refusals come back as 403 with a code: DEV_ENV_PLAN_LOCKED, DEV_ENV_LIMIT, PRODUCTION_ENV_LIMIT.
Lifecycle verbs with curl
The CLI wraps create/list/show/switch/bind. The other verbs are plain routes — the bearer is the one in ~/.objectstack/cloud.json.
T=$(jq -r .token ~/.objectstack/cloud.json)
B=https://bos.virtuman.com/api/v1/cloud
curl -s -X POST -H "Authorization: Bearer $T" $B/environments/<id>/suspend
curl -s -X POST -H "Authorization: Bearer $T" $B/environments/<id>/resume
curl -s -X POST -H "Authorization: Bearer $T" $B/environments/<id>/archive
curl -s -X POST -H "Authorization: Bearer $T" $B/environments/<id>/retry
curl -s -X POST -H "Authorization: Bearer $T" $B/environments/<id>/rollback # to the version it last ran on
curl -s -X POST -H "Authorization: Bearer $T" $B/environments/<id>/open # → { redirectUrl } signed sign-in into the environment
curl -s -X PATCH -H "Authorization: Bearer $T" $B/environments/<id> -d '{"display_name":"Production"}' -H 'content-type: application/json'
curl -s -X DELETE -H "Authorization: Bearer $T" "$B/environments/<id>" # step 1: archive, data kept
curl -s -X DELETE -H "Authorization: Bearer $T" "$B/environments/<id>?purge=1" # step 2, on an archived env: destroy — irreversible
# Domains and company sign-in, keyed by row id
curl -s -X POST -H "Authorization: Bearer $T" $B/environments/<id>/domains -d '{"hostname":"crm.example.ru"}' -H 'content-type: application/json'
curl -s -X POST -H "Authorization: Bearer $T" $B/domains/<domainId>/verify
curl -s -X POST -H "Authorization: Bearer $T" $B/sso/<connId>/verify
# Backups: take one now; restore into a NEW development environment
curl -s -X POST -H "Authorization: Bearer $T" $B/environments/<id>/backups
curl -s -X POST -H "Authorization: Bearer $T" $B/backups/<backupId>/restore -d '{"display_name":"Copy"}' -H 'content-type: application/json'
Every response is the envelope { success, data | error: { code, message, details } }.
Deleting takes two steps: the first DELETE only archives (restorable); the second, with ?purge=1 on an already archived environment, destroys it for good. A production environment answers 409 PRODUCTION_FORCE_REQUIRED without force=1 on both steps; an environment that never deployed (failed) is destroyed at once.
Starting, stopping, archiving, deleting an environment and changing its settings or domains takes the account owner or an admin, or the member who created it; other members get 403. Any member can list environments and open them.
Publish an app
cd my-app
os validate && os compile # → dist/objectstack.json
os package publish # creates/updates the package in your account; new version
os package publish --submit # …and submits it for marketplace review
os package publish --icon-url https://… --visibility org # icon by link; org = every environment of your account
Routes behind it: POST /api/v1/cloud/packages, …/packages/:id/versions, …/packages/:id/icon. Plan feature publish_apps and the max_private_apps limit apply; a refusal names them. Versions are immutable — publishing the same version again is refused. Publishing does not install: an environment pulls its apps (next section). Icons go by link (--icon-url); file upload is not supported. Publishing from inside an environment (console action «Опубликовать в реестр») produces the same version rows.
Install into an environment
Install talks to the runtime, not the cabinet: it signs in to the environment and calls its /api/v1/marketplace/install-local, which fetches the manifest from the cabinet and runs installPackage inside (ADR-0003).
os package install ru.example.crm \
--runtime https://<sub>.bos.virtuman.com --email owner@example.ru --password '…' --version latest
os package install ./dist/objectstack.json --runtime https://<sub>.bos.virtuman.com --email … --password …
# --confirm-global-uniques acknowledges installation-wide unique constraints the app declares
The cabinet's install ledger (sys_package_installation) is written by the runtime's cloud-connection callback, so the environment record's «Установленные приложения» tab reflects CLI installs too. Removing an app in the environment reaches the cabinet as well: the entry becomes removed and leaves the installed list.
Work inside an environment
An environment is a standard BusinessStack runtime. Point any runtime-targeting command at https://<sub>.bos.virtuman.com with an account of that environment:
os data query ru_example_crm.deal --where '{"status":"open"}' --runtime https://<sub>.bos.virtuman.com
os data import ./deals.csv --object ru_example_crm.deal --runtime …
# MCP for external AI agents (if «Доступ AI-агентов» is enabled on the environment)
POST https://<sub>.bos.virtuman.com/api/v1/mcp
AI routes (/api/v1/ai/*) are served by @businessos/service-ai inside the environment and metered through the platform gateway — no provider key needed on your side.
In CI
# GitLab CI job in an app repository: compile and upload a version on every tag
# OS_CLOUD_API_KEY — a personal osk_ key (POST /api/v1/keys), a masked CI variable
publish:
image: node:22
rules: [{ if: $CI_COMMIT_TAG }]
script:
- npm ci && npx os validate && npx os compile
- |
curl -sf -X POST "https://bos.virtuman.com/api/v1/artifacts/ru.example.crm/$CI_COMMIT_TAG" \
-H "Authorization: Bearer $OS_CLOUD_API_KEY" -H "content-type: application/json" \
--data-binary @dist/objectstack.json
The route takes a personal osk_ key (an operator — any package; anyone else — only a package of their own organization) or the environment's own token («Опубликовать в реестр»); the shared UPLOAD_TOKEN is gone. An uploaded artifact lands as a published pre-release; an operator clears is_pre_release in the console to make it the default. Alternatively use OS_CLOUD_API_KEY with os package publish --submit — the same result through the customer path.
Operator scripts (repository)
| Script | Purpose |
|---|---|
node scripts/seed-catalog.mjs | Plans and packaged apps from artifacts/; idempotent |
node scripts/sso-connect.mjs | Register/verify a company IdP for an environment from a shell |
node scripts/pull-app.mjs | Pull an app out of any environment into diffable JSON |
scripts/sync-skills.sh | Refresh .claude/skills from the framework checkout |
# Fleet-wide reads (operator token)
curl -s -H "Authorization: Bearer $T" https://bos.virtuman.com/api/v1/cloud/fleet/overview
curl -s -H "Authorization: Bearer $T" https://bos.virtuman.com/api/v1/cloud/fleet/environments
curl -s -H "Authorization: Bearer $T" https://bos.virtuman.com/api/v1/cloud/fleet/sso
Error codes you will meet
| Code | Meaning |
|---|---|
DEV_ENV_PLAN_LOCKED / DEV_ENV_LIMIT / PRODUCTION_ENV_LIMIT | Plan gate on environment creation; details carries used/limit |
ENVIRONMENT_NOT_ACTIVE | The verb needs a running environment |
DOMAIN_PLAN_LOCKED | Custom domains need «Команда» or higher |
SSO_NOT_PROVISIONED | Environment predates SSO secrets — «Развернуть заново» |
PERMISSION_DENIED | Not your environment, or a plan feature (publish_apps, private-app limit) refused |
UNKNOWN_INSTANCE | Gateway token does not match any environment |
MODEL_UNSET (503) | Platform AI model not configured in Setup |
ENVIRONMENT_FROZEN (423) | The operator froze the environment — start, redeploy and address change are closed until it is unfrozen |
SEAT_LIMIT | Every seat of the plan is taken (an invitation or an AI seat) |
BACKUP_IN_PROGRESS / BACKUP_NOT_CONFIGURED / BACKUP_UNSUPPORTED | A backup is already running; backup storage is not configured; backups only for cluster environments (not docker) |
The full ledger lives in apps/control/src/fleet.ts.
Not covered by the CLI
| Gap | Workaround |
|---|---|
| No commands for suspend/archive/destroy/open, domains, SSO, team | curl the routes above; a thin bos wrapper script would be a small addition |
No os cloud login for social/SSO cabinet accounts without a browser | use the device flow once; token is reusable |
| No operator commands (plan change, review, replay job) | console actions or direct routes (/api/v1/cloud/fleet/*, action endpoints) |
| No backup/restore command | curl the …/environments/<id>/backups and …/backups/<id>/restore routes (above); restoring over an existing environment — operator guide (kubectl + psql) |
| Environment-level data export | os data per object; scripts/pull-app.mjs for metadata |