Skip to content

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.

Verify the install:

Terminal window
csy --help # branded help; lists every command group
csy auth status # shows the current credential, if any

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.

Terminal window
# Define a context for your org's cell, then make it current
csy context add prod \
--server https://acme.consystence.com \
--type cell \
--tenant acme
csy context use prod
csy context list # all contexts (current marked with *)
csy context current # endpoint, type, and auth-mode of the current one
CommandPurpose
context listList defined contexts
context currentShow 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.

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.

Terminal window
# 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 browser
csy 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:

Terminal window
csy auth login --email you@example.com --pin <site-pin>

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).

Terminal window
# Environment variable (recommended for CI and scripts)
export CONSYSTENCE_TOKEN=csk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
csy site list
# Global flag (highest precedence) — a one-off override for any command, against the current context's server and tenant
csy site get chpp --token csk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Credential 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.

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"
}
}
}

csy <group> <command>. Every group offers --help. The groups:

GroupWhat it manages
accountThe 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
aiThe 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-keyTenant 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
authSign-in: login, logout, status, whoami, plus pin for the site PIN (forgot, reset, set, issue)
configStored configuration overrides, for the server or (with --org) for a tenant: list, get, set, unset, audit, reload — see below
contextCLI connection contexts (endpoints + credentials)
controllerControllers (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)
displayA site’s authored displays: list, get, diff, apply, remove
electricalElectrical power-distribution topology: node create|list|get|update|remove, edge create|list|remove, topology
instanceDevice 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)
orgOrg membership and email-domain bindings: get, members, domains
sequenceOffline checks of sequence group models: check <paths…> and schema [--out <file>]; no sign-in needed
serverOn-prem site/edge server: status
siteSites 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)
statusThe public platform-status feed (GET /status.json) from the account control plane; --url overrides the origin
tenantTenant 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-importBulk 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
uiThe 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.

Most org-scoped commands accept an explicit --org slug, and otherwise default to the current context’s selected slug:

Terminal window
csy tenant select acme # set the active tenant/org slug locally
csy tenant current # show it
csy org get # org detail (identity + its licences)
csy org members --list # active members
csy org members --pending # pending join requests
csy org members approve <accountId> --role admin

Org 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.

Terminal window
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.

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.

Terminal window
# 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 real
csy tenant import acme-chpp.yaml --org acme --dry-run
csy 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).

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.

Terminal window
csy config list --prefix Historian # stored overrides, with their effective values
csy config get Historian:Retention:DefaultRetentionDays
csy 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 30
csy config reload # re-read the stored overrides now

Without --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.

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:

Terminal window
csy ui admin/api-keys # open a starting route, then drive it
# REPL verbs: goto · inspect · action · wait

Headless script — run a declarative YAML scenario for CI. The scenario opens routes, performs actions and asserts state, then exits with a structured result:

Terminal window
csy ui --script scenario.yaml --no-color
# exit 0 = all steps passed · 1 = a step failed · 2 = scenario/connect error

The --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.