БизнесОС · from the terminal
2026-10-23 · BusinessStack CLI (os) 18.x against bos.virtuman.com · Русский

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
VariableMeaning
OS_CLOUD_URLCabinet base URL. Also read by a runtime's cloud-connection (tenants get it from the provisioner).
OS_CLOUD_API_KEY / OS_TOKENBearer for cloud routes; overrides the stored credential. Use in CI.
OS_CLOUD_TIMEOUT_MSHTTP 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.

Which account

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)

ScriptPurpose
node scripts/seed-catalog.mjsPlans and packaged apps from artifacts/; idempotent
node scripts/sso-connect.mjsRegister/verify a company IdP for an environment from a shell
node scripts/pull-app.mjsPull an app out of any environment into diffable JSON
scripts/sync-skills.shRefresh .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

CodeMeaning
DEV_ENV_PLAN_LOCKED / DEV_ENV_LIMIT / PRODUCTION_ENV_LIMITPlan gate on environment creation; details carries used/limit
ENVIRONMENT_NOT_ACTIVEThe verb needs a running environment
DOMAIN_PLAN_LOCKEDCustom domains need «Команда» or higher
SSO_NOT_PROVISIONEDEnvironment predates SSO secrets — «Развернуть заново»
PERMISSION_DENIEDNot your environment, or a plan feature (publish_apps, private-app limit) refused
UNKNOWN_INSTANCEGateway 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_LIMITEvery seat of the plan is taken (an invitation or an AI seat)
BACKUP_IN_PROGRESS / BACKUP_NOT_CONFIGURED / BACKUP_UNSUPPORTEDA 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

GapWorkaround
No commands for suspend/archive/destroy/open, domains, SSO, teamcurl the routes above; a thin bos wrapper script would be a small addition
No os cloud login for social/SSO cabinet accounts without a browseruse 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 commandcurl the …/environments/<id>/backups and …/backups/<id>/restore routes (above); restoring over an existing environment — operator guide (kubectl + psql)
Environment-level data exportos data per object; scripts/pull-app.mjs for metadata