The Consystence CLI
The csy command-line client is a first-class, supported way to drive the platform — alongside the browser. It is not a thin wrapper: every surface the web UI renders can be driven from the terminal, because the CLI is one of the platform’s peer rendering branches over the same semantic UI tree. For CI, scripts and partner automation it is the primary interface.
The binary is a self-contained, single-file executable named csy (assembly name csy, built on .NET 10) — no separate .NET runtime install is needed. It speaks to org cells, to an on-prem site/edge server, or to the account control plane — selected by the active context (see below). The compiled-in default server URL is https://api.consystence.com, and every command group targets the platform’s cell-, site-, or account-direct /api surfaces.
Install
Section titled “Install”Each tagged release will ship the CLI two ways:
- Release binary — a self-contained, single-file build per platform (
csy-<version>-linux-x64,csy-<version>-win-x64.exe). There is no runtime to install: download the binary for your platform and put it on yourPATHascsy. - .NET global tool —
dotnet tool install --global Consystence.Cliinstalls the same CLI as a framework-dependent tool, invoked as the samecsycommand. Unlike the release binary, this channel requires the .NET runtime.
Verify the install:
csy --help # branded help; lists every command groupcsy auth status # shows the current credential, if anyContexts: one CLI, many endpoints
Section titled “Contexts: one CLI, many endpoints”A context bundles one endpoint and its credential: a server URL, a server type (cell, site, edge, or account), an auth-mode derived from that type, and an optional tenant/org slug. This is gh-style host targeting — switch contexts to point the same commands at a different cell, a site server, or the account control plane.
# Define a context for your org's cell, then make it currentcsy context add prod \ --server https://acme.consystence.com \ --type cell \ --tenant acmecsy context use prod
csy context list # all contexts (current marked with *)csy context current # endpoint, type, and auth-mode of the current one| Command | Purpose |
|---|---|
context list | List defined contexts |
context current | Show the current context’s endpoint, type, auth-mode |
context use <name> | Switch the current context |
context add <name> --server <url> --type <cell|site|edge|account> [--tenant <slug>] [--api-scope <scope>] | Define a new context |
context remove <name> [--yes] | Delete a context and its stored credential |
context rename <old> <new> | Rename a context (migrates its stored credential) |
The context’s --type is what decides how you authenticate — you never pick an auth flow by hand.
Authentication
Section titled “Authentication”The CLI consumes the platform’s existing MultiAuth schemes; it does not define new ones. There are two ways to authenticate, and the mode is derived from the current context’s type rather than chosen with a flag.
Interactive login (OIDC against Entra External ID)
Section titled “Interactive login (OIDC against Entra External ID)”For cell and account contexts, csy auth login performs an OIDC sign-in against the id.consystence.com CIAM identity provider and stores the resulting Bearer JWT. The default interactive flow opens your browser for an authorization-code + PKCE sign-in on a loopback redirect; on headless hosts, pass --device for the OIDC device-authorization grant (RFC 8628) and enter a code in any browser.
# Interactive sign-in — opens the browser (authorization-code + PKCE, loopback redirect)csy auth login
# Headless hosts (SSH, CI) — device-authorization grant: enter a code in any browsercsy auth login --device
csy auth status # mechanism + credential source (identity is `auth whoami`)csy auth whoami # decode the stored token locally (oid, tid, preferred_username)csy auth logout # clear the current context's credential (keeps the context defined)For a site context, the same command runs the site-PIN path (the LocallyIssuedJwt scheme) against the site server:
Non-interactive (API key / X-Api-Key)
Section titled “Non-interactive (API key / X-Api-Key)”For CI, scripts and partner automation, supply a tenant API key (the csk_-prefixed value) and the CLI sends it as the X-Api-Key header; the server selects the ApiKey scheme. Mint keys at POST /api/admin/api-keys — the plaintext value is shown once (see API keys).
# Global flag (highest precedence) — applies to any commandcsy org get --org acme --token csk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Or via environment variable (ideal for CI)export CONSYSTENCE_TOKEN=csk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxcsy site listCredential precedence is: --token flag, then CONSYSTENCE_TOKEN, then the ApiKey field in config, then a stored interactive Bearer token.
Configuration file
Section titled “Configuration file”The CLI reads ~/.consystence/config.json. The on-disk shape mirrors the context model: a singleton ciam block (authority, clientId, apiScope — the API scope keeps the issued token’s aud on the Consystence API, not Microsoft Graph), a current-context name, and a contexts map keyed by context name. A null ciam.authority falls back to a compiled default for the default browser flow (the --device flow needs it set explicitly); ciam.clientId has no compiled default and must be set before the first interactive login. Leave apiScope unset and the login requests only the base OpenID scopes — the token then lacks the Consystence API audience, so set it before using bearer auth against /api. Context fields are kebab-case: server-url, server-type, auth-mode, api-scope, tenant-slug, api-key, and the CLI-managed token fields (access-token, token-expires-at, refresh-token). The long-lived refresh token is stored in the OS keyring (Windows Credential Manager, macOS Keychain, libsecret), keyed by context; the per-context refresh-token field is a plaintext fallback written only where no keyring exists, and the CLI warns once when it falls back.
{ "ciam": { "clientId": "<public-client-app-id>", "apiScope": "api://<api-app-id>/access_as_user" }, "current-context": "prod", "contexts": { "prod": { "server-url": "https://acme.consystence.com", "server-type": "cell", "auth-mode": "ciam-bearer", "tenant-slug": "acme" } }}Command groups
Section titled “Command groups”csy <group> <command>. Every group offers --help. The full set of groups:
| Group | What it manages |
|---|---|
account | The account control plane (account.consystence.com): whoami, set-type, the tenant directory (tenant list / tenant open), plus the self-service org subgroup (create, list, get, licences, request-tier, provision), the member subgroup (invite, revoke-invite, remove), and an internal operator subgroup for platform staff (cell fleet management), not intended for integrators — see the not-yet-GA caveat above |
ai | The AI plane: health (provider availability + circuit state) and complete (a test completion; --system, --model, --tier, --max-tokens) over the org-admin-gated /api/ai surface |
auth | Sign-in: login, logout, status, whoami |
config | Platform config store: list, get, set, unset, audit, reload — see below |
context | CLI connection contexts (endpoints + credentials) |
controller | Controllers (the platform’s term for a PLC) at a site: create, list, get, update, assign, decommission |
electrical | Electrical power-distribution topology: node create|list|get|update|remove, edge create|list|remove, topology |
instance | Device instances at a site: create, list, get, update, delete, plus the live read verbs tags (current values with quality), trend (a historian window), and commands (the instance’s command-audit ledger) |
org | Org membership and email-domain bindings: get, members, domains |
provision | Provisioning-plane operator commands (platform-admin, cloud cell only): re-bootstrap --org [--yes] re-runs an org’s tenant bootstrap from the server’s bootstrap source |
server | On-prem site/edge server: status |
site | Sites under an org: create, list, get, delete, plus alarms (active alarms, read-only) and commands (the site-wide command-audit ledger) |
status | Anonymous public platform-status feed (GET /status.json) — no auth; --url overrides the origin |
tenant | Tenant/org selection, control-plane CRUD (create, list, get — gated, not yet GA), and YAML export / import |
trend-import | Bulk historian import: upload a trend-data CSV plus its tag-mapping CSV as an import batch and poll its status — csy trend-import <file> --mapping <csv> --site <id> [--force] [--poll-interval <s>] [--no-wait], over /api/historian/import-batches — cloud cell only: a site server does not serve the import surface |
ui | The interactive renderer/REPL over the semantic UI tree (see below) |
Licences are no longer a standalone command group — read them via org get (an org’s licence pools appear inline on its detail) or account org licences <org>.
A few groups are context-sensitive and refuse to run against the wrong context with a clear message and a non-zero exit code rather than misfiring: the server commands target a site/edge server and exit cleanly against a cell context, the control-plane tenant create / tenant list / tenant get verbs are gated off on a cell (see the not-yet-GA caveat above), and the provision surface itself is cloud-only and platform-admin-gated, so it fails server-side against anything else.
Selecting an org/tenant
Section titled “Selecting an org/tenant”Most org-scoped commands accept an explicit --org slug, and otherwise default to the current context’s selected slug:
csy tenant select acme # set the active tenant/org slug locallycsy tenant current # show itcsy org get # org detail (identity + its licences)csy org members --list # active memberscsy org members --pending # pending join requestscsy org members approve <accountId> --role adminOrg membership is exactly owner / admin / member — see roles. The accountId is the member’s stable Entra oid. An org’s licences come back inline on org get; the account control plane also exposes account org licences <org> for the pool/allocation view.
Modelling a site
Section titled “Modelling a site”csy site create --code chpp --name "Coal Handling" --region australiaeastcsy controller create --site chpp \ --name "Pump house PLC" --platform PLCnext \ --asset-number PLC-001 --protocol ethernet-ip --ip 192.0.2.20csy instance create --site chpp \ --name "Process pump 2" --type consystence.pump.centrifugal --asset-number PP-02 \ --controller ctl_7Q4W9K2M \ --tag Running=Program:Pump1.Running--controller is required: an instance is bound to the controller its tags are acquired from — pass the ctl_… id minted by the controller create step above. Controllers and instances are minted with canonical ids (ctl_…, dev_…) — you supply the operator-facing asset number, not the id. A device type (e.g. consystence.pump.centrifugal) is reusable; an instance is a per-site placement keyed by its asset number. See instances & commissioning.
Tenant import and export
Section titled “Tenant import and export”The tenant group round-trips a site’s configuration as tenant YAML — the same declarative format used for provisioning. Export validates against the route org (you must be owner/admin of it); import validates locally first, then applies.
# Export one site to YAML (stdout, or --output <file>)csy tenant export --site chpp --org acme --output acme-chpp.yaml
# Validate without applying, then import for realcsy tenant import acme-chpp.yaml --org acme --dry-runcsy tenant import acme-chpp.yaml --org acme --force--dry-run parses and validates only (no server call); --force overwrites an existing tenant. A file whose org.slug does not match the --org route is rejected. This is the scriptable companion to tenant provisioning.
Platform config (config)
Section titled “Platform config (config)”csy config is the operator surface over the cell’s audited platform-config store. It reads and writes runtime overrides on top of the appsettings/env defaults, with every write validated and audited server-side.
csy config list --prefix Historian # stored overrides + served/effective provenancecsy config get Historian:Retention:DefaultRetentionDayscsy config set Historian:Retention:DefaultRetentionDays 90 --reason "extend retention"csy config unset Historian:Retention:DefaultRetentionDays --reason "revert to default"csy config audit --key Historian:Retention:DefaultRetentionDays --days 30csy config reload # force the cell to re-read the storeKeys are IConfiguration paths. Add --org <slug> to any subcommand to target a tenant’s per-tenant config instead of the global store (that variant is authorised against the org’s own owner/admin, not platform staff). Writes to high-blast-radius key families (Jwt, Oidc, Tenancy, Server, Ai) prompt for confirmation — a wrong authority would trust the wrong IdP for the whole cell — skippable with --yes for scripted use. set/unset surface the server’s warnings verbatim, including the “stored but not served yet” signal for keys the cell does not yet read.
The ui REPL: a first-class renderer
Section titled “The ui REPL: a first-class renderer”csy ui is not a help menu — it is a full renderer of the server-driven semantic UI tree, a peer rendering branch alongside the browser’s wire renderer. A native WinUI3 client is a further planned branch, but is not yet built — the shipped renderers today are the browser wire renderer and this CLI renderer. It connects to the server’s /hubs/ui-wire SignalR hub with the credential the CLI already resolved and drives the same server-side UI session actor (keyed by sessionId) the browser drives — so what you navigate in the terminal is the identical session model, just rendered as text.
flowchart LR CLI["csy ui<br/>CLI renderer + REPL"] -->|"text patches over<br/>/hubs/ui-wire (SignalR)"| Session["UI session actor<br/>keyed by sessionId"] Session --> Scene["Semantic UI tree<br/>authoritative scene graph"] Scene -.->|"same tree,<br/>other branches"| WEB["Browser wire renderer<br/>same wire format → DOM"]
It has two modes:
Interactive REPL — open a route, inspect the rendered view, fire actions, and wait for state to settle:
csy ui admin/api-keys # open a starting route, then drive it# REPL verbs: goto · inspect · action · waitHeadless script — run a declarative YAML scenario for CI. The scenario opens routes, performs actions and asserts state, then exits with a structured result:
csy ui --script scenario.yaml --no-color# exit 0 = all steps passed · 1 = a step failed · 2 = scenario/connect errorThe --sentinel flag emits a flushed __csy_ready__ marker before each prompt so a shell driver can read until the session settles — combine it with --no-color and wait until <predicate> for deterministic machine driving. Script mode is what replaces browser click-throughs in integration testing. For the wider rendering model, see multi-renderer architecture.
Related
Section titled “Related”- CLI quickstart — a guided first session
- Authentication and the API auth model
- API keys — minting
csk_keys for non-interactive use - Multi-renderer architecture — how the
uiREPL fits - SDK — the typed client the CLI is built on