Skip to content

CLI Reference

The dcs command-line tool communicates with the DCS Gateway REST API.

Installation

Download the dcs binary for your platform from the Releases page, or use the web terminal in the gateway UI which has the CLI pre-installed.

Configuration

The CLI resolves the gateway URL and default site in this order: flag > environment variable > config file.

Setting Flag Env var Config command
Gateway URL --gateway <url> DCS_GATEWAY_URL dcs config set-gateway <url>
Default site --site / -s <name> DCS_SITE dcs config set-site <name>
Auth token DCS_AUTH_TOKEN dcs login
Extra request headers dcs config set-header <name> <value>

Config file location: ~/.dcs/config.yaml (override with --config).

DCS_AUTH_TOKEN overrides the stored token outright (no refresh, no expiry warnings): it hands the CLI a short-lived gateway session token without touching ~/.dcs/config.yaml, for non-interactive use (CI jobs, capture rigs, scripts). Interactive use should prefer dcs login.

Initial setup

dcs config set-gateway https://<gateway-url>
dcs config set-site houston-plant
dcs login                       # required when the gateway runs with OIDC or LDAP auth
dcs config show

Global Flags

Flag Short Description
--gateway <url> Gateway base URL
--site <name> -s Site name (required for site-scoped resources)
--output <format> -o Output format: table (default), json, or yaml
--config <path> Config file path

-o yaml on a dcs get <resource> <name> detail view exports a clean Kubernetes manifest via the gateway export endpoint (not supported for formulas).

Confirmation Prompts

The commands that destroy a resource or move a field output ask before they act: every dcs delete subcommand, dcs formula delete, dcs io write, and dcs io release. Each takes --yes / -y to answer in advance.

An unconfirmed command exits non-zero and does nothing. That covers the declined answer and the case with no one there to answer at all: a script, a CI step, or an agent session whose stdin is a redirect reads EOF, sees the prompt abort, and gets a failure it can act on:

$ dcs io write wago-750-352 coil.7 true </dev/null; echo "exit=$?"
Write value true to channel coil.7 on IO module wago-750-352 in site "bench-site"? [y/N]: Error: confirmation required but stdin is not a terminal; pass --yes to confirm non-interactively
exit=1

Pass --yes for any non-interactive caller. A piped answer (echo y | dcs io write …) is honoured as well.

Command Index

Every visible top-level command. Commands without a dedicated section on this page are summarized here. Run dcs <command> --help for their subcommands and flags.

Command Purpose
get Display one or many resources
create Create a resource
apply Apply a resource from a YAML or JSON file
command Send a state-machine command to a resource
execute Launch ad-hoc procedural elements (Phase, Operation, UnitProcedure) on a Unit
mode Set the ISA-88 mode on a resource
delete Delete a resource
alarm Manage ISA-18.2 alarms (ack, shelve, unshelve)
audit Audit trail commands (trace, export, verify, archive-integrity)
change-control (alias cc) Change-control (21 CFR Part 11 §11.10(k)) commands (propose, history, verify)
batch Batch production record commands (record, finalize, events, messages)
recipe Recipe lifecycle, approval, and version history commands (approve, reject, release, activate, withdraw, history, diff, revert)
formula Manage a master recipe's formulas (list, get, create, delete). Every subcommand is site-scoped and needs --site
config Manage dcs CLI configuration
tag Read and write control module tags (set)
watch Stream live events from the DCS gateway
login Authenticate with the DCS gateway (OIDC device code or LDAP credentials)
logout Clear stored authentication tokens
auth Inspect authorization policy and entitlements (policy, entitlements)
simulate Run a simulation batch against a recipe
mes MES integration commands, ISA-95 Level 3/4 boundary (apikey, work-orders, status)
backup Back up DCS resources (crds, audit)
restore Restore DCS resources from backup (crds)
health Show system health and diagnostics
historian Query historical process data (tags, alarms, states, list-tags)
io Commissioning I/O module operations (list, read, write, release, discover)
opcua Commissioning client for third-party OPC UA servers (endpoints, browse, read, write, subscribe)
status Show operational status overview
runtime Manage unit runtime pods (restart <unit> --reason <text>)
unit Unit-level equipment infrastructure operations (failover, ADR 0006)
validate Validate Structured Text or SFC charts (st, sfc)
qualify Run Installation / Operational / Performance Qualification protocols (iq, oq, pq, dr)
admin Administrative commands (demo provisioning, site repair)
migrate Migrate procedural template manifests across schema breaks
ui Run the DCS gateway UI on this laptop against the active kubeconfig context
edit Edit CRD YAML files in a local directory with the visual editors — no cluster required
diff Show the human-readable semantic diff between two CRD YAML files
version Print the dcs CLI version

Commands

dcs login

Authenticate with the DCS gateway. The CLI queries the gateway's auth configuration and picks the matching flow:

  • oidc — OIDC device-code flow: the CLI prints a verification URL and user code, opens the browser, and polls until you complete sign-in.
  • ldap — prompts for username and password and authenticates against the gateway's LDAP login endpoint.
  • none — login is not required, and the command is a no-op.
dcs login
dcs login --no-browser      # print the verification URL instead of opening a browser
Flag Description
--no-browser Do not attempt to open the verification URL in a browser

In ldap mode both prompts read from standard input. The credentials can therefore be piped for non-interactive use (CI seeding, capture rigs, air-gapped automation):

printf '%s\n%s\n' "$DCS_USERNAME" "$DCS_PASSWORD" | dcs login

Echo suppression applies only when standard input is a terminal. A piped password is taken as the whole line, spaces included. Where a pre-issued gateway token is already available, prefer DCS_AUTH_TOKEN over piping a password.

Tokens are stored in the CLI config file and attached to every subsequent request. Expired OIDC tokens are refreshed automatically when a refresh token is available. Otherwise the CLI prompts you to run dcs login again. Use dcs logout to clear stored tokens.

If the deployment fronts the gateway with an authenticating proxy, the CLI must present the proxy's credential headers before dcs login can reach the gateway. Configure them with dcs config set-header. See Security Operations.

dcs auth

Inspect authorization policy and entitlements (ADR 0024).

dcs auth entitlements       # the calling identity's resolved effective action set
dcs auth policy             # active role→action resolution (requires supervise)
dcs auth policy --actions   # …with every effective action listed per role

entitlements prints the action-catalog names the current identity may perform, one per line. policy renders, per configured role, the permission tiers, any allow/deny refinements from the deployment roles file, and the size of the effective action set they resolve to. This is the audit-facing view of what the running gateway enforces.

Flag Description
--actions policy only: list every effective action per role

dcs get

List or get detail for any resource type. List output uses table format by default, and detail output is JSON (-o yaml exports a Kubernetes manifest).

dcs get <resource> [name] [flags]

Resources and aliases:

Resource Aliases Site-scoped Columns
enterprises ent no NAME, SITES, DESCRIPTION
sites site no NAME, ENTERPRISE, LOCATION, AREAS
areas area yes NAME, SITE, PROCESS CELLS, DESCRIPTION
processcells pc yes NAME, AREA, UNITS, DESCRIPTION
units unit yes NAME, STATE, PROCESS CELL, CONTROLLER, RUNTIME
controlmodules cm yes NAME, TYPE, PARENT, STATE, TEMPLATE, FB NETWORK
iomodules io yes NAME, CONTROLLER, PROTOCOL, ADDRESS, STATE, CHANNELS
controlprograms cp, fbnetworks, fb yes NAME, UNIT, MODE, STATE, BLOCKS, SCANS
templates tpl no NAME, TYPE, SCAN INTERVAL, INPUTS, OUTPUTS, BLOCKS
functionblocktypes fbt, fbblocktypes, functionblocktype no NAME, INPUTS, OUTPUTS, BLOCKS
blocktypes bt no NAME, CATEGORY, DESCRIPTION
controllers ctrl yes NAME, ADDRESS, PHASE, NODE
batches batch yes BATCH ID, NAME, PHASE, STATE, RECIPE, MESSAGE
batchrecords batchrecord, br yes NAME, BATCH ID, PHASE, OUTCOME, RECIPE, COMPLETED
masterrecipes mr yes NAME, SITE RECIPE, TARGET CELL, PHASE, STEPS
controlrecipes cr yes NAME, MASTER RECIPE, BATCH, PHASE, STEPS
formulas formula no SITE, TYPE, RECIPE, FORMULA, BATCH SIZE, PARAMS, MATERIALS
procedures proc yes NAME, STATE, PROGRESS
unitprocedures up yes NAME, UNIT, STATE, PROGRESS
operations op yes NAME, UNIT PROCEDURE, STATE, PROGRESS
phases phase yes NAME, STATE, STEP, OPERATION, UNIT
phasetemplates pt yes NAME, CAPABILITY, DESCRIPTION
operationtemplates ot yes NAME, DESCRIPTION, STEPS
unitproceduretemplates upt yes NAME, DESCRIPTION, STEPS
proceduretemplates proct yes NAME, DESCRIPTION, STEPS
alarmdefinitions ad, alarmdef yes NAME, SOURCE, CONDITION, SEVERITY, ENABLED
alarms alarm yes NAME, SOURCE, TYPE, SEVERITY, STATE, MESSAGE, AGE
auditrecords audit, ar yes NAME, ACTION, TARGET KIND, TARGET NAME, RESULT, COMPONENT, TIMESTAMP
simulationpresets simpresets, simpreset yes NAME, DESCRIPTION, BEHAVIORS, ADDRESS NAMES

fbnetworks / fb are aliases of controlprograms. Both query the same control programs.

phases lists only live batch-instance phases. The hand-authored library Phase definitions in the recipe library are omitted, because they are authoring artifacts and the command lists runtime state. The STEP column is the phase's currently executing SFC step, the same active step the HMI chart highlights.

dcs get phases <name> drills into one phase: it prints the active SFC step and, for that step's outgoing transition, the condition ST plus the live value of every variable the condition reads, the same state the HMI's transition code panel shows (e.g. open_media.T >= chargeTime, with open_media.T ticking toward chargeTime). That answers "why is this phase holding, and how close is it?" from the shell. --active prints the same detail for whichever phase is running now, so you don't have to type its generated instance name first. -o json/-o yaml are unaffected and return the full phase object.

Live-data resources (special handling, outside the table above):

Resource Description
tags (tag) Live tag/IO values; scope with --cm, --io, or --unit/-u, or omit to browse all site tags
runtime Unit runtime diagnostics (requires --unit/-u)
health System health overview

Tag shorthand (dot notation):

dcs get <cm>.<tag>    # read a single control module tag (e.g. agitator-1.PV)
dcs get <cm>.         # list all tags for a control module
Flag Short Description
--unit -u Unit name (for tags and runtime resources)
--io IOModule name (for tags; resolves the runtime automatically)
--cm ControlModule name (for tags; resolves unit/runtime automatically)
--active For phases: show each running phase's active SFC transition and its live variables

Examples:

# List all enterprises
dcs get enterprises

# List units in table format
dcs get units -s houston-plant

# Get unit detail as JSON
dcs get units reactor-1 -s houston-plant

# Export a unit as a Kubernetes manifest
dcs get units reactor-1 -s houston-plant -o yaml

# List batches in JSON format
dcs get batches -s houston-plant -o json

# Use an alias
dcs get cp -s houston-plant

# Live tags for a unit
dcs get tags -u reactor-1 -s houston-plant

# Why is the running phase holding? Show its transition condition + live variables
dcs get phases --active -s houston-plant

dcs create

Create a resource using flags.

dcs create <resource> [flags]

Subcommands: enterprise, site, area, processcell, unit, controlmodule, controller, iomodule, batch, controlprogram (cp), functionblocktype (fbt), procedure (proc), unitprocedure (up), operation (op), phase, alarmdefinition (ad), simulationpreset (simpreset).

dcs create enterprise

dcs create enterprise --name acme-pharma --description "ACME Pharmaceuticals"
Flag Required Description
--name yes Enterprise name
--description no Description

dcs create site

dcs create site --name houston-plant --enterprise acme-pharma --location "Houston, TX"
Flag Required Description
--name yes Site name
--enterprise yes Parent enterprise
--description no Description
--location no Location

dcs create area

dcs create area --name granulation-area -s houston-plant
Flag Required Description
--name yes Area name
--description no Description

dcs create processcell

dcs create processcell --name granulation-cell-1 --area granulation-area -s houston-plant
Flag Required Description
--name yes Process cell name
--area yes Parent area
--description no Description

dcs create unit

dcs create unit --name reactor-1 --process-cell granulation-cell-1 \
  --controller plc-controller-1 --protocol modbus --endpoint 10.0.1.100:502 \
  -s houston-plant
Flag Required Description
--name yes Unit name
--process-cell yes Parent process cell
--description no Description
--controller no Controller to run the runtime on
--protocol no Device protocol (simulation, modbus, ethernetip, opcua)
--endpoint no Protocol-specific endpoint

dcs create controlmodule

dcs create controlmodule --name temp-sensor-1 \
  --parent-name reactor-1 --parent-kind Unit \
  --template-ref basic-temp-input \
  -s houston-plant
Flag Required Description
--name yes Control module name
--parent-name yes Parent resource name
--parent-kind yes Parent kind: Unit
--template-ref yes Control module template reference
--description no Description

dcs create controller

dcs create controller --name plc-controller-1 --labels dcs.io/device=plc-io -s houston-plant
Flag Required Description
--name yes Controller name
--labels no Comma-separated key=value labels

dcs create iomodule

dcs create iomodule --name turck-io-1 \
  --controller-ref plc-controller-1 --protocol modbus --address 10.0.1.100:502 \
  -s houston-plant
Flag Required Description
--name yes I/O module name
--controller-ref yes Controller reference
--protocol yes Protocol (modbus, ethernetip, opcua, simulation)
--address yes Network address

dcs create batch

dcs create batch --batch-id BATCH-2026-001 --recipe api-granulation-cell1 -s houston-plant
Flag Required Description
--batch-id yes Unique batch identifier
--recipe yes MasterRecipe name
--formula no Formula name from the master recipe

dcs create controlprogram

Create an IEC 61131-3 control program (FBD or Structured Text) deployed to a unit runtime.

dcs create controlprogram --name pid-loop --unit reactor-1 --scan-interval 100ms -s houston-plant
dcs create controlprogram --name temp-ctrl --unit reactor-1 --st-source "OUT := SP - PV;" -s houston-plant
dcs create controlprogram --name temp-ctrl --unit reactor-1 --st-file program.st -s houston-plant

Aliases: cp

Flag Required Description
--name yes Control program name
--unit yes Target unit name
--scan-interval no Scan cycle interval (default: 200ms)
--cm no Control module reference
--st-source no Inline Structured Text source code (mutually exclusive with --st-file)
--st-file no Path to Structured Text source file

dcs create functionblocktype

Create a composite function block type definition (IEC 61131-3).

dcs create functionblocktype --name cascade-pid --description "Cascade PID" --inputs SP,PV --outputs CV

Aliases: fbt

Flag Required Description
--name yes Function block type name
--description no Block type description
--inputs no Data input port names (comma-separated)
--outputs no Data output port names (comma-separated)

dcs create procedure

Create a procedure (ISA-88 procedural model).

dcs create procedure --name api-granulation-proc --recipe-ref api-granulation-cell1 \
  --unit-procedures granulate-up,dry-up -s houston-plant

Aliases: proc

Flag Required Description
--name yes Procedure name
--description no Description
--recipe-ref no Recipe reference
--execution-mode no Sequential or Parallel (default: Sequential)
--mode no Automatic, Semi-Automatic, or Manual (default: Automatic)
--unit-procedures no Child UnitProcedure names (comma-separated)

dcs create unitprocedure

Create a unit procedure (ISA-88 procedural model).

dcs create unitprocedure --name granulate-up --unit reactor-1 \
  --procedure-ref api-granulation-proc --operations charge-op,mix-op -s houston-plant

Aliases: up

Flag Required Description
--name yes Unit procedure name
--unit no Target unit name
--description no Description
--procedure-ref no Parent procedure reference
--operations no Child Operation names (comma-separated)

dcs create operation

Create an operation (ISA-88 procedural model).

dcs create operation --name mix-op --unit-procedure-ref granulate-up \
  --phases dose-api,agitate -s houston-plant

Aliases: op

Flag Required Description
--name yes Operation name
--description no Description
--unit-procedure-ref no Parent unit procedure reference
--phases no Child Phase names (comma-separated)

dcs create phase

Create a phase (ISA-88 procedural model).

dcs create phase --name dose-api --operation-ref mix-op --unit-ref reactor-1 \
  --timeout 600 -s houston-plant
Flag Required Description
--name yes Phase name
--description no Description
--operation-ref no Parent operation reference
--unit-ref no Unit to run on
--timeout no Timeout in seconds

dcs create alarmdefinition

Create an alarm definition.

dcs create alarmdefinition --name reactor-temp-high \
  --source-kind ControlModule --source-name temp-sensor-1 \
  --condition TagHigh --tag-name PV --threshold 85 \
  --type Equipment --severity High --message "Reactor temperature high" \
  -s houston-plant

Aliases: ad

Flag Required Description
--name yes Alarm definition name
--source-kind yes Source kind: Unit, ControlModule, etc.
--source-name yes Source resource name
--condition yes Condition type: StateEquals, TagHigh, TagLow, TagHighHigh, TagLowLow, TagDeviation, TagTrue, TagFalse
--type yes Alarm type: Equipment or System
--severity yes Alarm severity
--message yes Human-readable alarm message
--state-value no State value (for StateEquals condition)
--tag-name no Tag name (for tag-based conditions)
--threshold no Threshold value
--setpoint no Setpoint (for TagDeviation)
--deadband no Deadband/hysteresis
--debounce-seconds no Activation debounce in seconds
--priority no ISA-18.2 priority (1-4)

dcs create simulationpreset

Create a reusable equipment simulation template. Presets define behavior models using generic address names that are mapped to real IOModule addresses when used by a simulation profile.

dcs create simulationpreset --name simple-tank \
  --description "Basic tank with level and temperature" \
  --behavior "level:TankLevel:inFlowAddr=inlet_flow,outFlowAddr=outlet_flow,capacity=500" \
  --behavior "temperature_pv:TemperatureRamp:spAddr=temperature_sp,ratePerSec=2.0" \
  -s houston-plant

# Seed the built-in presets (reactor-vessel, mixing-tank, control-valve, cip-skid, heat-exchanger)
dcs create simulationpreset --seed-builtins -s houston-plant

Aliases: simpreset

Flag Required Description
--name yes, unless --seed-builtins Preset name
--description no Human-readable description
--behavior yes, unless --seed-builtins Behavior template address:Type:param=val,param=val (repeatable)
--seed-builtins no Create the built-in equipment presets

dcs apply

Create a resource from a YAML or JSON manifest file, similar to kubectl apply.

dcs apply -f manifest.yaml
dcs apply -f -              # read from stdin
cat manifest.yaml | dcs apply -f -
Flag Short Required Description
--file -f yes Path to YAML/JSON file (use - for stdin)

dcs command

Send an ISA-88 state machine command to a resource.

dcs command <Kind> <name> <Command> [--reason <text>] -s <site>

Kind: Batch, or Phase / Operation / UnitProcedure when that resource is the root of an ad-hoc execution. ISA-88 state commands flow through the Batch resource, so a procedural element owned by a batch (or a child of a larger ad-hoc run) is rejected. Command its owner instead. Physical equipment is never commanded directly, and Unit is refused by the CLI before the request is sent.

Name: a Batch answers to either of its two handles: the resource name (batch-6jrkm, the NAME column of dcs get batches) or the batch ID you gave it at creation (--batch-id, the BATCH ID column). The CLI resolves the ID onto the resource before sending the command, and the resource name wins when a name and some other batch's ID collide. A batch ID two live batches share is ambiguous and refused by name, so address one of them by its resource name. Every other kind is addressed by resource name only.

Resolution reads the site's batch list. Where the CLI cannot read it (no grant, an outage), a handle that is already a legal resource name is sent through unchanged, because the server can still answer for it. A handle that is not a legal resource name (every batch ID carrying an uppercase letter is one) is refused here instead. The command endpoint takes a resource name, so sending the handle unresolved could only return a Kubernetes naming complaint about an identifier the operator never thought of as a Kubernetes name. The refusal carries the list error, which is the half that says whether to fix a grant or wait out an outage.

Valid commands: Abort, Hold, Pause, Reset, Restart, Resume, Start, Stop

Flag Required Description
--reason for Abort, Hold, Reset, Stop Justification recorded in the 21 CFR Part 11 audit trail; must meet the deployment reason policy minimum length

Examples:

dcs command Batch BATCH-2026-001 Start -s houston-plant
dcs command Batch BATCH-2026-001 Hold --reason "Foam excursion on reactor-1, per SOP-OP-021" -s houston-plant
dcs command Batch BATCH-2026-001 Abort --reason "Deviation DEV-0142: charge overshoot" -s houston-plant

dcs execute

Launch an ad-hoc procedural element on an idle Unit without creating a Batch. See Ad-Hoc Execution for the full guide and rules.

dcs execute phase         --unit <unit> --template <PhaseTemplate> [--param k=v ...] -s <site>
dcs execute operation     --unit <unit> --template <OperationTemplate> [--param k=v ...] -s <site>
dcs execute unitprocedure --unit <unit> --template <UnitProcedureTemplate> --reason <text> -s <site>

All three subcommands share these flags:

Flag Required Description
--unit yes Target Unit name
--template yes Template name
--param no Parameter override key=value (repeat for multiple)
--timeout no Override the template's TimeoutSeconds
--reason unitprocedure only (in production mode) Audit-trail justification

operation requires the OperationTemplate to have spec.category = equipment and the user to have the dcs-lead-operator group (or higher). unitprocedure additionally requires a fresh OIDC token in production mode for the 21 CFR Part 11 electronic signature.

Examples:

# Operator: take an ad-hoc sample
dcs execute phase --unit reactor-1 --template take-sample --param volume_ml=10 -s houston-plant

# Lead operator: weekly CIP cycle
dcs execute operation --unit reactor-1 --template cip-cycle --param caustic_temp_c=80 -s houston-plant

# Lead operator: full SIP+CIP+leak-test sequence in production
dcs execute unitprocedure --unit reactor-1 --template full-cleaning-cycle \
  --reason "weekly CIP per SOP-CL-014" -s houston-plant

dcs mode

Set the ISA-88 mode on a procedural or equipment resource.

dcs mode <Kind> <name> <Mode> -s <site>
Kind family Kinds Valid modes
Procedural Procedure, UnitProcedure, Operation, Phase Automatic, Semi-Automatic, Manual
Equipment Unit, ControlModule Automatic, Manual

Example:

dcs mode Phase dose-api Manual -s houston-plant

dcs unit failover

Re-bind a unit's runtime to a designated standby node (ADR 0006). ISA-88 state commands do not live under dcs unit. They flow through the Batch resource (dcs command Batch …), and this group covers equipment infrastructure operations.

dcs unit failover <unit> -s <site>                  # list eligible targets + availability state
dcs unit failover <unit> -s <site> --to-node <node> --reason "<justification>"

Without --to-node, prints the unit's availability mode, current node and lease state, and the eligible failover targets (resolved from spec.availability.failoverTargets, where nodes must be enrolled via the dcs.io/site label and Ready). With --to-node, requests the failover: the physical operator fences the old binding, re-binds the runtime pod to the target, and redeploys the control program from the control-plane source of truth. Recovery is an ISA-88 Restart. The outcome lands on the unit's FailoverRequest status condition.

Flag Purpose
--to-node <node> Target standby node; omit to list eligible targets
--reason <text> Required with --to-node; recorded in the audit trail and must meet the deployment reason policy
--confirm-fenced One-writer certification: you certify the current node is powered off or disconnected from the field network. Required in Autonomy mode; overrides a still-held lease in Failover mode (planned maintenance)
--allow-colocation Permit a target that already hosts another unit's runtime (co-location guard refuses otherwise)

See the Failover Runbook for the full operational procedure and HA and Failure Modes for when to use it.

dcs delete

Delete a resource with confirmation prompt.

dcs delete <resource> <name> [flags]

All delete subcommands support --yes / -y to skip the confirmation prompt. A prompt that is declined or cannot be answered exits non-zero and deletes nothing (see Confirmation Prompts).

Resources: enterprise, site, area, processcell, unit, controlmodule, iomodule, batch, template, masterrecipe, controlrecipe, fbnetwork, controlprogram, functionblocktype, procedure, unitprocedure, operation, phase, alarmdefinition, alarm, simulationpreset, controller

fbnetwork and controlprogram delete the same resource. alarm deletes an alarm instance (admin cleanup of orphans, since alarms are normally managed by the alarm-operator).

Examples:

dcs delete unit reactor-1 -s houston-plant
dcs delete enterprise acme-pharma -y
dcs delete batch batch-001 -s houston-plant

dcs watch

Stream live events from the DCS gateway via Server-Sent Events (SSE), or poll REST endpoints with --interval when SSE/MQTT is unavailable.

dcs watch [channel] [flags]

Channels: tags (use --unit to filter), alarms, states, all (default when no channel is given).

Flag Short Description
--unit -u Filter tag events to a specific unit (required when polling tags)
--interval -i Poll interval (e.g. 2s). Switches the transport from SSE to REST polling

Examples:

dcs watch -s houston-plant                          # SSE: all events
dcs watch tags --unit reactor-1 -s houston-plant    # SSE: tag changes for a unit
dcs watch alarms -s houston-plant                   # SSE: alarm events
dcs watch tags -u reactor-1 -i 2s -s houston-plant  # poll tag values every 2s
dcs watch states -o json -s houston-plant           # machine-readable output

A GAP line means the gateway shed events this connection was too slow to take, and the stream is no longer a complete record from that point on. Tag values recover on their own within one 200 ms republish. Alarms and states publish on transition only, so re-read those with dcs get alarms and dcs get units. What scrolled past is stale the moment a frame was shed. See scada-integration.md.

dcs simulate

Execute a recipe against simulated equipment for development and IQ/OQ validation. Creates a simulation Batch (all units forced to the simulation driver), watches it to completion, prints a simulation summary (outcome, wall-clock vs. simulated duration), and deletes the Batch unless --keep is set.

dcs simulate <master-recipe-name> [flags] -s <site>
Flag Description
--time-scale Simulation time multiplier (e.g. 10.0 for 10x; default 1.0)
--step-through Hold after each SFC step transition
--seed RNG seed for deterministic simulation (0 = random)
--keep Keep the simulation Batch after completion

Example:

dcs simulate api-granulation-cell1 --time-scale 10 -s houston-plant

dcs audit

Query the 21 CFR Part 11 audit trail.

dcs audit trace

Display the complete audit trail for a batch execution chain, in chronological order. The correlation ID is typically the batch ID.

dcs audit trace BATCH-2026-001 -s houston-plant

dcs audit export

Export audit records to JSON or CSV for regulatory review. Records are fetched from both active (Kubernetes) and archived (historian) storage.

dcs audit export --from 2026-01-01 --to 2026-03-01 -f audit-q1.json
dcs audit export --format csv --action Approve -f approvals.csv
dcs audit export --format json --target-kind Batch --from 2026-01-01
Flag Short Description
--action Filter by action (Create, Approve, etc.)
--target-kind Filter by target resource kind
--component Filter by actor component
--from Start date (RFC3339 or YYYY-MM-DD)
--to End date (RFC3339 or YYYY-MM-DD)
--format Output format: json (default) or csv
--file -f Output file path (default: stdout)
--allow-partial Accept an export the gateway could not read the archived records for

An archive the gateway cannot reach shortens the export without changing its shape: a range old enough to have been archived comes back empty, which in the file is indistinguishable from a range in which nothing happened. The gateway declares which halves it read, and this command acts on that declaration. The export is still written, and then the command exits non-zero with the reason on stderr. Pass --allow-partial to accept the gap deliberately.

The warning goes to stderr and never into the document itself. That keeps dcs audit export --format json | jq parseable. A gateway too old to make the declaration is reported as unverified. That is not the same as incomplete, so it does not fail the command.

dcs audit verify

Check electronic signatures on audit records against the current HMAC signing key store (21 CFR Part 11 §11.70). The default mode confirms that every signed record's keyID still resolves in the store. That is the check a key rotation puts at risk. It does not recompute digests, because an AuditRecord does not retain the payload that was signed. With --archived, recomputes digests of archive batches signed by the audit-archiver and verifies manifest signatures. Exit code 1 if any verification fails.

dcs audit verify
dcs audit verify --archived --since 2026-01-01T00:00:00Z
Flag Description
--site Limit to a specific site (default: all sites; ignored with --archived)
--archived Verify archived audit batches in place of active records
--since Lower bound for archive batch start time, RFC3339 (requires --archived)
--until Upper bound for archive batch start time, RFC3339 (requires --archived)

dcs audit archive-integrity

Read and trigger the gateway's archive-integrity verifier, which fetches every archive manifest from the historian and checks each against its recorded digest.

status reports the most recent verification. It reads in-memory scheduler state, so it is empty until the first run completes and is cleared by any gateway restart.

run performs one verification immediately and prints the verdict. The scheduler's first run fires one interval after boot and the interval has a 15-minute floor, so a gateway that has just started has no answer for at least that long. That is the right cadence for a background sweep, and the wrong one when you have just restored an archive and need to know whether it verifies. The run is synchronous and exits 1 if any manifest fails. It writes the same AuditRecord a scheduled run writes, so an on-demand check is as auditable as a scheduled one. A run requested while another is in flight is refused, with no duplicate started.

dcs audit archive-integrity status
dcs audit archive-integrity run

The archive-integrity:run action is admin by default. Grant it to another role through that role's allow list. See Security Operations.

dcs change-control

Propose changes through change control, and inspect and verify the change-control audit trail. Alias: dcs cc.

dcs change-control propose

Wrap a manifest document into a ChangeRequest and submit it for approval. A direct write of the resource is exactly what this path replaces. This is the CLI counterpart of the gateway's Promote flow: the plant model is declarative YAML, so the file you edit (with any editor, tracked in git) is the change you propose. The change is realised only after an authorised approver signs the ChangeRequest in the approval queue.

The proposed document replaces the target's spec when the ChangeRequest applies. Propose from a file that matches the cluster's current state apart from the change you intend: a git-managed site tree, where a stale copy would silently revert someone else's landed change.

# Single-document file
dcs change-control propose -f recipe.yaml --reason "CCR-1042: raise hold temperature"

# Multi-document site file — pick one document
dcs change-control propose -f sites/plant-01/08a-phase-templates.yaml \
  --target phasetemplate/ferment-hold \
  --reason "CCR-1042: reduce recirculation pump speed during the hold"
Flag Short Description
--file -f Path to a YAML/JSON manifest (use - for stdin)
--reason Justification recorded on the ChangeRequest (e.g. a change-control ticket)
--target kind/name of the document to propose from a multi-document file
--backend direct-apply | git-pr | webhook | in-cluster-queue (default: the deployment default)
--operation Create | Update | Delete (default: Update)

dcs change-control history

Show the change-control admission audit chain for a resource.

dcs change-control history ControlProgram cp-fill --site pharma-1

dcs change-control verify

Walk the AuditRecords produced by the change-control admission webhook and confirm every record carries the required §11.10(k) fields. Exit code 1 if any record is incomplete, which suits a scheduled job or CI pipeline.

dcs change-control verify
dcs change-control verify --site-filter pharma-1

dcs historian

Query historical tag values, alarm events, and state transitions from the historian. Time values support RFC3339, date-only (2026-04-10), and relative durations (1h, 2d).

Common flags (tags, alarms, states):

Flag Description
--from Start time (default: 1h)
--to End time (default: now)
--batch Batch ID (switches to the batch-specific endpoint)
--limit Maximum number of results (default: 100)
--offset Pagination offset

dcs historian tags

dcs historian tags --tag TT-101 -s houston-plant
dcs historian tags --unit reactor-1 --agg avg --interval 5m -s houston-plant
dcs historian tags --batch BATCH-2026-001 -s houston-plant
Flag Description
--tag Filter by tag address
--unit Filter by unit name
--agg Aggregation function (avg, min, max, last, first)
--interval Aggregation interval (1m, 5m, 15m, 1h, 1d)

dcs historian alarms

dcs historian alarms --severity Critical -s houston-plant
dcs historian alarms --source-name reactor-1 --from 24h -s houston-plant
Flag Description
--source-kind Filter by source resource kind
--source-name Filter by source resource name
--alarm-name Filter by alarm name
--severity Filter by severity (Critical, High, Medium, Low)

dcs historian states

dcs historian states --kind Unit --name reactor-1 -s houston-plant
dcs historian states --from 7d -s houston-plant
Flag Description
--kind Filter by resource kind (Unit, Phase, etc.)
--name Filter by resource name

dcs historian list-tags

List distinct tag names (unit/address pairs) known to the historian for the given site.

dcs historian list-tags -s houston-plant

dcs io

Commission I/O wiring: list the modules a site carries, read and write single channels, and discover what a device actually presents. Reads and writes go through the gateway to the io-probe pod on the module's Controller, which holds the driver connection. A simulation module is served by its unit runtime instead, because that is where the simulated process state lives.

dcs --site newark-plant io list
dcs --site newark-plant io list wago-750-352
dcs --site newark-plant io read wago-750-352 DI0
dcs --site newark-plant io write wago-750-352 DO2 true

Channels are named or raw. The <channel> argument accepts either a channel name the IOModule declares in spec.channels (DI0) or the raw driver address behind it (discrete.0). The gateway resolves a declared name to its address before the driver sees it, and read prints both so the mapping it used is on the screen:

CHANNEL  ADDRESS                   VALUE  QUALITY
DI0      wago-750-352:discrete.0   true   Good

An identifier the module declares no channel for is passed to the driver as a raw address, with no refusal. That fall-through is what makes commissioning possible: you read addresses off a coupler before anyone has written them into spec.channels, and dcs io discover exists to turn what you find into that channel list. When the driver then rejects the address, the error names the channels the module does declare, so a mistyped name does not read as a protocol fault.

A write is refused while a unit is executing through the module. io write addresses a channel below the tag model, so there is no control module to carry an ISA-88 equipment mode and no mode to consult. The gateway asks the question it can answer instead, which is whether a Unit that reads and writes through this IOModule is running a batch or an ad-hoc phase. If one is, the write is refused and the refusal names the unit and the control module that binds it. The mid-batch way to move an output is the modelled one: put the control module in Manual and write the tag, which is what ISA-88 Table 1 describes. io read is unaffected, and so is a write to a module no executing unit touches.

Subcommands:

Subcommand Purpose
list [name] List the site's IOModules, or show one module's protocol, state and declared channels
read <module> <channel> Read one channel, by declared name or raw driver address
write <module> <channel> <value> Write one channel. Prompts for confirmation unless --yes is given; an unconfirmed write exits non-zero, see Confirmation Prompts
release <module> <channel> Clear the force a previous write placed on a simulation channel, so its configured behaviors drive it again. Simulation modules only — a write to real hardware simply becomes the device state
discover <module> Ask the device what channels it presents, which is the input to the module's spec.channels

dcs opcua

Commission against a third-party OPC UA server: discover endpoints, browse the address space, and verify reads, writes and subscriptions. Unlike every other command on this page, dcs opcua talks directly to the OPC UA server, and no gateway sits in the path. It needs no --gateway, no site, and no login. It connects through the same client code the serviceBinding reconciler uses, so a security tuple proven here is proven for the CR that will carry it.

Start with endpoints, which is a metadata-only call needing no credentials:

dcs opcua endpoints opc.tcp://192.168.10.50:4840

Every other subcommand opens a session and requires either an explicit security tuple or --insecure (SecurityPolicy=None, SecurityMode=None, for anonymous exploration only and never as a default):

dcs opcua browse opc.tcp://192.168.10.50:4840 --insecure --depth 3
dcs opcua read  opc.tcp://192.168.10.50:4840 "ns=4;s=Tank1.Level" --insecure

A secured session exercises certificate negotiation the way an IOModule or serviceBinding will. The server must trust-list the client certificate first, and a rejection tells you so explicitly.

dcs opcua read opc.tcp://192.168.10.50:4840 "ns=4;s=Tank1.Level" \
  --security-policy Basic256Sha256 --cert client.pem --key client.key \
  --server-cert-pin 6f3a…9c04
dcs opcua write opc.tcp://192.168.10.50:4840 "ns=4;s=Tank1.Setpoint" 72.5 \
  --security-policy Basic256Sha256 --cert client.pem --key client.key
dcs opcua subscribe opc.tcp://192.168.10.50:4840 "ns=4;s=Tank1.Level" \
  --interval 500ms --security-policy Basic256Sha256 --cert client.pem --key client.key

Shared flags (all subcommands):

Flag Description
--security-policy Security policy short name (None, Basic256Sha256, …)
--security-mode Message security mode (None, Sign, SignAndEncrypt; defaults to SignAndEncrypt when a policy other than None is set)
--auth User token type: anonymous (default), username, certificate
--username, --password Credentials for --auth username (password also via DCS_OPCUA_PASSWORD)
--cert, --key PEM client certificate and private key (required for Sign/SignAndEncrypt and --auth certificate)
--server-cert-pin Expected SHA-256 fingerprint of the server certificate; the connection is refused on a mismatch
--timeout Connect and per-request timeout (default 10s)
--insecure Connect with None/None in place of an explicit tuple

Subcommands:

Subcommand Purpose
endpoints <url> List every endpoint with its security policy, mode, level, user token types, and server certificate fingerprint (the --server-cert-pin value)
browse <url> Walk the address space from --node (default i=85, ObjectsFolder) down --depth levels, printing node IDs, browse names, data types and current values — the fields an IOModule or serviceBinding CR needs. --max-nodes bounds the walk and says so when it truncates
read <url> <node-id>… Read the given nodes in a single Read service call
write <url> <node-id> <value> Write a value and read it back. Without --type (Bool, Int, UInt, Double, String), the type is inferred from the node's current value
subscribe <url> <node-id>… Subscribe at --interval (default 1s) and print data changes until interrupted

Each failure mode is distinguished: a security tuple the server does not offer points you at endpoints, an untrusted client certificate names the server's trust list, a rejected identity names the credential flags, and an unknown node ID points you at browse.

dcs backup

Export DCS custom resources to YAML files, and the historian time-series database to a plain-SQL dump, for backup and disaster recovery.

dcs backup crds [-o <file>] [-s <site>]    # all DCS custom resources
dcs backup audit [-o <file>] [-s <site>]   # all AuditRecord CRs (21 CFR Part 11)
dcs backup historian [-o <file>]           # the historian database, via pg_dump
dcs backup verify <file>                   # integrity + completeness, before every restore
Flag Short Description
--output -o Output file path (default: dcs-crds-<timestamp>.yaml / dcs-audit-<timestamp>.yaml / dcs-historian-<timestamp>.sql)
--allow-partial Accept an export that could not read every kind, or a dump that was cut short (exit 0 on a known gap)

--site/-s is optional for backup: without it, all sites are exported.

Every export ends with a completeness footer, and the command exits non-zero when the gateway could not read a kind (usually a missing rule in its ClusterRole). The file is still written, so --allow-partial accepts a known gap during an incident. Verification with dcs backup verify re-checks the same footer alongside the SHA-256, because an intact file can still be missing a kind. See Backup and Recovery.

dcs backup historian

dcs backup historian is the one backup subcommand that does not go through the gateway. It runs pg_dump inside the historian database's primary Pod and streams the result to the local file. What it needs is therefore a kubeconfig with exec rights in the namespace the chart is installed in, and no gateway token at all. No gateway route could serve it: the gateway proxies historian queries to the collector, and only the database Pod holds both the credentials and a pg_dump built against its own server version.

Flag Description
--kubeconfig Path to a kubeconfig file (default: $KUBECONFIG or ~/.kube/config)
--context Kubeconfig context to use (default: the kubeconfig's current context)
--system-namespace Namespace the chart is installed in (default: discovered from the cluster)
--cluster CloudNativePG cluster to dump, required when the namespace holds more than one

Four things it settles that an out-of-band kubectl exec leaves to the operator:

  • It dumps the primary. A dump taken from a standby succeeds and is as stale as that standby's replication lag, which is the quantity the operator is trying to bound. A cluster carrying only replicas is refused.
  • It refuses an ambiguous namespace. The disaster-recovery procedure stands a restore cluster beside the live one, so during the incident that matters most there are two primaries. Name one with --cluster.
  • It says whether the file is whole. The dump is streamed out of a Pod, and an evicted Pod or a dropped connection leaves a file that is valid SQL as far as it goes and restores without complaint. The command checks for pg_dump's own completion marker and exits non-zero without it. So does dcs backup verify, which re-reads the marker from the file.
  • It writes the integrity manifest. A shell redirection writes none, so an out-of-band dump could not be verified before a restore at all.

The dump covers every site. The historian is one database for the whole install and the site is a column in it, so --site cannot narrow this export and the command says so when one is passed. An installation pointed at an external PostgreSQL through historian.database.externalURL has no Pod to exec into. The command names that case in its refusal, so the answer is never a bare "nothing found".

The output is plain SQL, restored with psql. That is the format the DR runbook drill exercised, and a custom-format dump would need TimescaleDB's timescaledb_pre_restore() and timescaledb_post_restore() around pg_restore without buying the operator anything.

dcs restore

Restore DCS custom resources from a multi-document YAML backup file created by dcs backup, applying each document via the gateway apply endpoint.

dcs restore crds -f dcs-crds-20260315-120000.yaml
Flag Short Required Description
--file -f yes Path to YAML backup file

Each document is reported as applied, skipped, or failed, and the command exits non-zero only when something failed. Skips are export-only kinds that the backup captures as evidence but the gateway does not re-apply. See Backup and Recovery for the list and the reason each carries. Documents throttled by the gateway's write budget are retried with backoff, and the failure count leaves them out.

dcs edit

Open the DCS visual editors (ST / SFC / FBD, recipes, physical model) against a local working tree of CRD YAML files, with no Kubernetes cluster in the loop. This is filesystem authoring mode. Every Save writes the edited resource back to its YAML file on disk: no cluster, no etcd, no network required. The editor is git-agnostic. Review and commit changes with your own git tooling.

Features that need live equipment (runtime tag values, diagnostics, historian trends) are unavailable and grayed out. Connect a cluster with dcs ui to use them.

dcs edit [directory]      # defaults to the current directory
Flag Default Description
--port <n> 8200, falling back to ephemeral Local listen port
--bind-address <ip> 127.0.0.1 Listen address; keep loopback for single-user mode
--no-browser off Print the URL but do not auto-open a browser

Examples:

# edit the YAML tree in ./plant, opens a browser
dcs edit ./plant

# current directory, fixed port, no auto-open (useful for SSH-forwarded UIs)
dcs edit --port 8200 --no-browser

dcs diff

Render the human-readable semantic diff between two versions of a CRD YAML file ("Mash Hold: hold time 30 min -> 45 min"), using the gateway's own diff renderers. Convenient as a git difftool. Informational only: it creates no audit record and no electronic signature, and the 21 CFR Part 11 review-of-record stays in the connected Promote / ChangeRequest flow.

Each file must contain a single resource. Exit status is 0 when the files are semantically identical and 1 when they differ, so dcs diff composes in scripts like diff and git diff --exit-code.

dcs diff <old.yaml> <new.yaml>
dcs diff --format json old.yaml new.yaml    # machine-readable
Flag Description
--format Output format: semantic (default) or json

dcs migrate

Rewrite legacy phase / phase-template manifests to match the current schema. Each subcommand handles one breaking change.

dcs migrate readback

Migrates parameters[].readback and outputs[].source to the current model (issue #250): readbacks are hoisted into separate outputs, WriteActual() calls are rewritten into bare ST assignments, and outputs[].captureOn is dropped. Pass one or more paths (file or directory). Without --write the rewritten content is printed to stdout. With --write files are replaced in place.

dcs migrate readback ./templates/             # preview
dcs migrate readback --write ./templates/     # rewrite in place
Flag Description
--write Rewrite files in place (the default prints to stdout)

dcs ui

Boot the gateway UI on this laptop against the active kubeconfig context and open a browser. The CLI binary embeds the static UI assets. A single dcs install is therefore the whole laptop-mode runtime: no extra container, no kubectl proxy, no separate gateway binary needed.

dcs ui [flags]

This is the entry point for topology D: each engineer runs the UI against their own kubeconfig context (kind, k3d, Docker Desktop, or a remote dev cluster they have credentials for). Auth mode is none and the local user is treated as a cluster admin. Cluster authorization is whatever your kubeconfig grants.

Flag Default Description
--kubeconfig <path> $KUBECONFIG or ~/.kube/config Kubeconfig file to load
--context <name> current-context Kubeconfig context to use
--system-namespace <ns> discovered from the cluster Namespace the DCS operators run in, which the system-health rollup queries. Discovered from the app.kubernetes.io/part-of: cloud-native-dcs label the chart applies; pass it explicitly when a cluster carries more than one install.
--port <n> 8200, falling back to ephemeral Local listen port
--bind-address <ip> 127.0.0.1 Listen address (loopback by default for security)
--no-browser off Print the URL but do not auto-open a browser

Examples:

# default kubeconfig and context
dcs ui

# specific cluster
dcs ui --kubeconfig ~/.kube/dev --context kind-dev

# fixed port, no auto-open (useful when forwarded over SSH)
dcs ui --port 8200 --no-browser

When running inside a Pod (pod-mode gateway), the in-cluster service account is auto-detected via /var/run/secrets/kubernetes.io/serviceaccount/token and dcs ui is unused. The deployed dcs-gateway binary is the production entry point.

dcs config

Manage persistent CLI configuration.

dcs config set-gateway <url>            # Set default gateway URL
dcs config set-site <name>              # Set default site
dcs config set-header <name> <value>    # Set an extra HTTP header sent with every gateway request
dcs config unset-header <name>          # Remove an extra header
dcs config show                         # Show current configuration

set-header is for deployments that front the gateway with an authenticating proxy expecting credential headers. The value may reference an environment variable with ${VAR} syntax. It is expanded when the CLI runs, so secrets stay out of the config file. show redacts literal header values and prints ${VAR} references as-is. See Security Operations for the full flow.

dcs config set-header My-Proxy-Client-Secret '${PROXY_CLIENT_SECRET}'

dcs admin heal-site

Audit a site and recreate any missing tenant resources (RBAC, ResourceQuota, NetworkPolicies, secrets, scenario manifests). The heal path is the same idempotent provisioning pipeline that runs at sandbox provisioning time, exposed here so operators can trigger it without forcing the user through a browser re-login.

dcs admin heal-site <siteName>

Authorization:

  • Admins (dcs-admin group) may heal any site.
  • Non-admins may only heal the site matching their dcs-site-* group (self-heal).

If the Site CR does not yet exist, heal-site falls through to the full bootstrap provisioning path and returns state=provisioning with existed=false.

dcs version

Print the CLI version.

dcs version