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. In CI and scripts it runs unattended with an API key (below).
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). A fresh install has no usable endpoint: the built-in placeholder address is not a Consystence host. Define a context for the server you use (for an org’s cell, https://{slug}.consystence.com) with csy context add before running any command. Every command group targets the platform’s cell-, site-, or account-direct /api surfaces.
Install
Section titled “Install”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 uses the platform’s existing sign-in methods; it adds none of its own. 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 signs in with a site PIN 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); the CLI sends it as the X-Api-Key header. A key is read-only unless it was created as an operator key. A read-only key reads tenant and site data and never changes anything, so a change needs a person’s sign-in (csy auth login, or a site PIN). Its role decides which reads it may make: the site reads (site list, site get) and the live operational reads (tags, trends, alarms, the command log) need an admin-role key, and organisation, membership, controller-record and configuration reads are never open to keys. An operator key covers one person and one native site for at most 30 days, and can issue commands and acknowledge alarms on that site. An org owner or admin creates keys with csy api-key create <name> --role admin (the default role is member) or at POST /api/admin/api-keys; the plaintext value is shown once (see API keys).
# Environment variable (recommended for CI and scripts)export CONSYSTENCE_TOKEN=csk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxcsy site list
# Global flag (highest precedence) — a one-off override for any command, against the current context's server and tenantcsy site get chpp --token csk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCredential precedence is: --token flag, then CONSYSTENCE_TOKEN, then the current context’s api-key field in config, then a stored interactive Bearer token. For routine use, prefer the environment variable or the context’s api-key field, which keep the key out of your shell history and the process list; keep --token for a one-off override.
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: before the first interactive login, set it to the client id provided with your CLI access. 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": "<client-id-provided-with-your-cli-access>", "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 groups:
| Group | What it manages |
|---|---|
account | The account control plane (account.consystence.com): whoami, set-type, the tenant directory (tenant list / tenant open), the org subgroup (create, list, get, licences, request-tier, provision), and the member subgroup (invite, revoke-invite, remove) — see the public-beta note 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 |
api-key | Tenant API keys: create (read-only by default, or --class operator --site <code> --on-behalf-of <email> for one person on one native site, at most 30 days; the value is shown once), list, revoke — see API keys |
auth | Sign-in: login, logout, status, whoami, plus pin for the site PIN (forgot, reset, set, issue) |
config | Stored configuration overrides, for the server or (with --org) for a tenant: 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, and device-set <ctl> --site <code> --out <file> (writes a ControlLogix controller’s device set to a new file for the project generator) |
display | A site’s authored displays: list, get, diff, apply, remove |
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 |
sequence | Offline checks of sequence group models: check <paths…> and schema [--out <file>]; no sign-in needed |
server | On-prem site/edge server: status |
site | Sites under an org: create, list, get, update, status (cloud sync state), delete (type the site code, or --confirm <code> in scripts), purge, alarms (active alarms, read-only; alarms history, read-only; alarms ack, which acknowledges one occurrence in the alarm record and never writes to a controller), commands (the site-wide command-audit ledger), logbook (read, and append signed entries), kpi and maintenance (read-only), simulation (status, start, stop, reset, ledger, scenario), and operations (the site’s operations declaration: get, diff, apply, clear) |
status | The public platform-status feed (GET /status.json) from the account control plane; --url overrides the origin |
tenant | Tenant selection (select, current); YAML export / import, with import-apply / import-discard for a dry-run plan; from-bindings (writes tenant YAML device entries offline from a ControlLogix generator’s bindings file); and kpi-settings. create, list and get are account-plane operations: they refuse on every context and name csy account org provision, csy account tenant list or csy org get --org <slug> instead |
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 — and delete <batchId> to remove a batch’s data — cloud cell only: a site server does not serve the import surface |
ui | The interactive renderer/REPL over the semantic UI tree, and render <route> to render one route once and exit (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 tenant create / tenant list / tenant get verbs refuse on every context and point to the account plane.
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 "North pit"csy controller create --site chpp \ --name "Pump house PLC" --platform ControlLogix \ --asset-number PLC-001 --protocol ethernet-ip --ip 192.0.2.20 --cip-path "1,0"csy 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. In the web console, a site’s Add device form creates an instance the same way. 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, a declarative file format. The file carries the site’s equipment hierarchy and each device’s placement in it. 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 validates the file locally, then asks the server for a plan: the import run with nothing written, diffed against the current model with plant-affecting changes first. The plan replaces any unapplied plan. It prints the exact csy tenant import-apply <plan-id> --org <slug> --confirm "<phrase>" and csy tenant import-discard <plan-id> --org <slug> lines. An apply is refused if the model has changed since the plan. A direct import that would make a plant-affecting change on a site that can take commands is refused with nothing imported; go through the plan instead. --force overwrites existing entities and brings back decommissioned controllers the file declares, but never lifts that rule. A file whose org.slug does not match the --org route is rejected. Import targets an existing tenant; tenants themselves are created on the account plane (see tenant provisioning).
Configuration overrides (config)
Section titled “Configuration overrides (config)”csy config reads and writes stored configuration overrides, which sit on top of the server’s configuration-file and environment defaults. Every write is validated and recorded, with the --reason you give; csy config audit shows the trail.
csy config list --prefix Historian # stored overrides, with their effective valuescsy config get Historian:Retention:DefaultRetentionDayscsy config set Historian:Retention:DefaultRetentionDays 90 --reason "site retention policy"csy config unset Historian:Retention:DefaultRetentionDays --reason "revert to default"csy config audit --key Historian:Retention:DefaultRetentionDays --days 30csy config reload # re-read the stored overrides nowWithout --org, the commands act on the server’s own settings; on a site server, that is a site administrator’s surface. Add --org <slug> to any subcommand except reload to read or change a tenant’s own settings instead, as an owner or admin of that organisation; a tenant can set only a few keys, such as the historian’s default retention, and csy config list --org <slug> lists them. Writes to the sign-in, tenancy, server and AI key families ask for confirmation, which --yes skips in scripts. set and unset print the server’s warnings as given.
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 the browser drives — so what you navigate in the terminal is the same session model, just rendered as text.
flowchart LR CLI["csy ui<br/>CLI renderer + REPL"] -->|"text patches over<br/>/hubs/ui-wire (SignalR)"| Session["Server-side<br/>UI session"] 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