Synchronize CLI documentation with management command parity

This commit is contained in:
Joseph Doherty
2026-09-10 01:20:12 -04:00
parent 0cc9db9e94
commit 2b9a7c3ca0
4 changed files with 376 additions and 169 deletions
+50 -55
View File
@@ -1,12 +1,12 @@
# CLI # CLI
The CLI is a standalone command-line tool that provides scripting and automation access to all ScadaBridge administrative operations. It connects to the central cluster's HTTP Management API and dispatches commands to the `ManagementActor` via `POST /management`. Authentication and role resolution happen server-side; the CLI sends LDAP credentials as HTTP Basic Auth on every request. The CLI is a standalone command-line tool that provides scripting and automation access to all HTTP-eligible ManagementActor operations. It connects to the central cluster's HTTP Management API and dispatches commands to the `ManagementActor` via `POST /management`. Authentication and role resolution happen server-side; the CLI sends LDAP credentials as HTTP Basic Auth on every request.
## Overview ## Overview
The CLI component lives in `src/ZB.MOM.WW.ScadaBridge.CLI/` and builds to a self-contained `scadabridge` executable (or `scadabridge.exe` on Windows). It is not part of the Host binary — it deploys on any machine with HTTP access to a central node. The CLI component lives in `src/ZB.MOM.WW.ScadaBridge.CLI/` and builds to a .NET 10 `scadabridge` executable (or `scadabridge.exe` on Windows); self-contained distribution requires an explicit publish configuration. It is not part of the Host binary — it deploys on any machine with HTTP access to a central node.
The tool is built on `System.CommandLine` and exposes a hierarchical command tree organized by management domain (`template`, `instance`, `site`, `data-connection`, and so on). Every command follows the same pattern: resolve the management URL and credentials, construct a command object whose type maps to a `ManagementCommandRegistry` entry, serialize it into the `{ command, payload }` JSON envelope, and `POST` it to `<managementUrl>/management`. The server response — JSON success body or `{ error, code }` error envelope — is printed to stdout or stderr respectively. The tool is built on `System.CommandLine` and exposes a hierarchical command tree organized by management domain (`template`, `instance`, `site`, `data-connection`, and so on). Management commands follow the same pattern: resolve the management URL and credentials, construct a command object whose type maps to a `ManagementCommandRegistry` entry, serialize it into the `{ command, payload }` JSON envelope, and `POST` it to `<managementUrl>/management`. The server response — JSON success body or `{ error, code }` error envelope — is printed to stdout or stderr respectively.
The CLI is the preferred automation interface. When setting up system state (sites, templates, data connections, deployments, security) without the Central UI, use `scadabridge` commands rather than direct database manipulation. The CLI is the preferred automation interface. When setting up system state (sites, templates, data connections, deployments, security) without the Central UI, use `scadabridge` commands rather than direct database manipulation.
@@ -20,7 +20,11 @@ Direct-to-node access (`http://localhost:9001` for `central-a`, `http://localhos
### Command dispatch ### Command dispatch
Most commands serialize to a named management command and reach the `ManagementActor` through the `POST /management` endpoint. The `audit query` and `audit export` commands are the exception — they call plain REST endpoints (`GET /api/audit/query`, `GET /api/audit/export`) introduced by Audit Log (#23) and therefore use `SendGetAsync`/`SendGetStreamAsync` on `ManagementHttpClient` rather than `SendCommandAsync`. The streaming `audit export` path uses `HttpCompletionOption.ResponseHeadersRead` to avoid buffering large payloads in memory. Management commands use `POST /management`. Audit query, export, and tree use
`GET /api/audit/query`, `/api/audit/export`, and `/api/audit/tree`; source-node
backfill uses `POST /api/audit/backfill-source-node`. `audit verify-chain` is a
local placeholder that performs no verification. `debug stream` uses a persistent
SignalR connection. Audit export streams the response without buffering it all.
### Authentication and roles ### Authentication and roles
@@ -30,46 +34,37 @@ Role enforcement is applied by the `ManagementActor`. Operations require the app
| Role | Covers | | Role | Covers |
|------|--------| |------|--------|
| `Admin` | Security settings, site management, SMTP config, audit-config queries | | `Administrator` | Security settings, site mutations, SMTP/SMS provider changes, bundle preview/import, certificate management, audit-config queries |
| `Design` | Templates, shared scripts, external systems, DB connections, API methods, notification lists | | `Designer` | Template/schema/script authoring, external systems, DB connections, API methods, notification lists, connection design, bundle export |
| `Deployment` | Instance lifecycle (deploy, enable, disable, delete), data connection bindings, bundle import/export | | `Deployer` | Instance lifecycle and overrides, bindings, deployment, runtime debugging, parked-call decisions |
| `OperationalAudit` | Reading the Audit Log (`audit query`) | | `Operator` | Submit secured writes |
| `AuditExport` | Exporting the Audit Log (`audit export`) | | `Verifier` | Approve/reject secured writes from a different principal |
| Operational audit roles | Audit query/tree and Flow Debugger replay |
| Audit export roles | Export the audit log |
These are operation-level gates, not blanket restrictions on every read in a
group. Many management reads require authentication only; site scope and
operation-specific checks still apply. Secured-write listing accepts Operator,
Verifier, or Administrator. See the [server authorization matrix](../../src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs).
A request lacking the required role exits with code `2` (authorization failure). A bad-credential response (HTTP 401) exits with code `1`. A request lacking the required role exits with code `2` (authorization failure). A bad-credential response (HTTP 401) exits with code `1`.
### Output formats ### Output formats
Every command accepts `--format json` (default) or `--format table`. For JSON output, successful server responses are written to stdout verbatim — the server controls the JSON shape and no re-serialization is applied. Locally-constructed output (errors, `debug stream` events) is serialized by `OutputFormatter` with indentation and camelCase. Table output renders a padded plain-text table derived from the response JSON — arrays become rows, single objects become a two-column `Property / Value` table. Errors are always written as `{ "error": "...", "code": "..." }` to stderr, regardless of format. Management commands accept `--format json` (default) or `--format table`. Audit export accepts its own export formats. For JSON output, successful server responses are written to stdout verbatim — the server controls the JSON shape and no re-serialization is applied. Locally-constructed output (errors, `debug stream` events) is serialized by `OutputFormatter` with indentation and camelCase. Table output renders a padded plain-text table derived from the response JSON — arrays become rows, single objects become a two-column `Property / Value` table. Errors are always written as `{ "error": "...", "code": "..." }` to stderr, regardless of format.
## Architecture ## Architecture
`Program.cs` invokes `CliApplication.BuildRoot()`, which builds the `RootCommand` tree, attaches the four global options (`--url`, `--username`, `--password`, `--format`), and registers each command group: [`Program.cs`](../../src/ZB.MOM.WW.ScadaBridge.CLI/Program.cs) invokes
[`CliApplication.BuildRoot()`](../../src/ZB.MOM.WW.ScadaBridge.CLI/CliApplication.cs),
which registers the global options and all 20 command groups, including the
connection browse/certificate extensions. The executable and parity tests use
this same tree.
```csharp Management actions use `CommandHelpers.ExecuteCommandAsync` for URL/credential
rootCommand.Add(TemplateCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); resolution, HTTP dispatch, formatting, and exit codes. Deployment aliases defer
rootCommand.Add(InstanceCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); the overall deadline to the server; bundles and Flow Debugger have explicit
rootCommand.Add(SiteCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); policies. Audit REST and SignalR actions use their respective transports.
rootCommand.Add(DeployCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(DataConnectionCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(ExternalSystemCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(NotificationCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(SecurityCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(AuditLogCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(AuditCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(HealthCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(DebugCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(SharedSchemaCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(SharedScriptCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(DbConnectionCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(ApiMethodCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(BundleCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(CachedCallCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(SecuredWriteCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
rootCommand.Add(FlowDebugCommands.Build(urlOption, formatOption, usernameOption, passwordOption));
```
Every command's action delegates to `CommandHelpers.ExecuteCommandAsync`, which owns the URL/credential resolution, `ManagementHttpClient` lifetime, and exit-code mapping. This consolidation means the authorization-failure exit code (`2`) is enforced uniformly — including for the `bundle` group, which uses `ExecuteCommandAsync` with a longer 5-minute timeout and a per-command `onSuccess` handler rather than a separate HTTP path.
`CliConfig.Load()` is called at the start of every invocation. It merges `~/.scadabridge/config.json`, environment variables, and any defaults. A malformed or unreadable config file emits a warning to stderr and falls through to the environment variable / command-line precedence chain without crashing. `CliConfig.Load()` is called at the start of every invocation. It merges `~/.scadabridge/config.json`, environment variables, and any defaults. A malformed or unreadable config file emits a warning to stderr and falls through to the environment variable / command-line precedence chain without crashing.
@@ -100,7 +95,7 @@ export SCADABRIDGE_USERNAME=multi-role
export SCADABRIDGE_PASSWORD=password export SCADABRIDGE_PASSWORD=password
scadabridge template create --name "PumpStation" --description "Standard pump station" scadabridge template create --name "PumpStation" --description "Standard pump station"
scadabridge template attribute add --template-id 3 --name Speed --data-type Float --default-value 0 scadabridge template attribute add --template-id 3 --name Speed --data-type Float --value 0
scadabridge instance create --name "PS-01" --template-id 3 --site-id 1 scadabridge instance create --name "PS-01" --template-id 3 --site-id 1
scadabridge instance deploy --id 7 scadabridge instance deploy --id 7
``` ```
@@ -109,26 +104,26 @@ scadabridge instance deploy --id 7
| Group | Subcommands | Role required | | Group | Subcommands | Role required |
|-------|-------------|---------------| |-------|-------------|---------------|
| `template` | `list`, `get`, `create`, `update`, `delete`, `validate`, `resolved-members`, `move`; `attribute add/update/delete`; `alarm add/update/delete`; `script add/update/delete`; `composition add/delete`; `native-alarm-source add/list/update/remove` | `Design` | | `template` | `list`, `get`, `create`, `update`, `delete`, `validate`, `resolved-members`, `move`; `attribute list/add/update/delete`; `alarm add/update/delete`; `script add/update/delete`; `composition add/delete`; `native-alarm-source add/list/update/remove`; `folder list/create/rename/move/reorder/delete`; `resync-members` | Designer mutations; reads generally authenticated |
| `instance` | `list`, `get`, `create`, `set-bindings`, `set-overrides`, `alarm-override set/delete/list`, `native-alarm-source set/clear/list/import`, `attribute-override delete`, `set-area`, `diff`, `deploy`, `enable`, `disable`, `delete` | `Deployment` | | `instance` | `list`, `get`, `create`, `set-bindings`, `bind-module`, `set-overrides`, `import-overrides`, `alarm-override set/delete/list`, `native-alarm-source set/clear/list/import`, `attribute-override delete`, `set-area`, `diff`, `deploy`, `enable`, `disable`, `delete` | `Deployer` |
| `site` | `list`, `get`, `create`, `delete`, `deploy-artifacts`; `area list/create/update/delete` | `Admin` | | `site` | `list`, `get`, `create`, `update`, `delete`, `deploy-artifacts`; `area list/create/update/move/delete` | Administrator site mutations; Designer/Deployer area mutations; Deployer artifact deployment |
| `deploy` | `instance`, `artifacts`, `status` | `Deployment` | | `deploy` | `instance`, `site`, `artifacts`, `status` | `Deployer` |
| `data-connection` | `list`, `get`, `create`, `update`, `delete`, `move`; `browse`, `search`, `verify-endpoint`, `certs` | `Design` / `Deployment` | | `data-connection` | `list`, `get`, `create`, `update`, `delete`, `move`; `browse`, `search`, `verify-endpoint`, `certs` | `Designer` / `Deployer` |
| `external-system` | `list`, `get`, `create`, `update`, `delete` | `Design` | | `external-system` | `list`, `get`, `create`, `update`, `delete`; `method list/get/create/update/delete` | `Designer` |
| `notification` | `list`, `get`, `create`, `update`, `delete`, `rename`; `recipient add/delete`; `smtp list/create/update`; `sms list/create/update` | `Design` / `Admin` | | `notification` | `list`, `get`, `create`, `update`, `delete`, `rename`; `recipient add/delete`; `smtp list/create/update`; `sms list/create/update` | `Designer` / `Administrator` |
| `security` | `api-key list/create/update/delete/set-methods`; `role-mapping list/create/update/delete`; `scope-rule list/add/delete` | `Admin` | | `security` | `api-key list/create/update/delete/set-methods`; `role-mapping list/create/update/delete`; `scope-rule list/add/delete` | `Admin` |
| `shared-schema` | `list`, `get`, `create`, `update`, `delete` | Read: authenticated; mutations: Designer | | `shared-schema` | `list`, `get`, `create`, `update`, `delete` | Read: authenticated; mutations: Designer |
| `secured-write` | `list`, `submit`, `approve`, `reject` | Operator / Verifier / Administrator, enforced per operation | | `secured-write` | `list`, `submit`, `approve`, `reject` | Operator / Verifier / Administrator, enforced per operation |
| `cached-call` | `list`, `retry`, `discard` | Server-enforced operational roles and site scope | | `cached-call` | `retry`, `discard` (list parked messages with `health parked-messages`) | Server-enforced operational roles and site scope |
| `flow-debug` | `run-method`, `run-script`, `run-value-change`, `replay` | Deployer; replay also accepts OperationalAudit roles | | `flow-debug` | `run-method`, `run-script`, `run-value-change`, `replay` | Deployer; replay also accepts OperationalAudit roles |
| `shared-script` | `list`, `get`, `create`, `update`, `delete` | `Design` | | `shared-script` | `list`, `get`, `create`, `update`, `delete` | `Designer` |
| `db-connection` | `list`, `get`, `create`, `update`, `delete` | `Design` | | `db-connection` | `list`, `get`, `create`, `update`, `delete` | `Designer` |
| `api-method` | `list`, `get`, `create`, `update`, `delete` | `Design` | | `api-method` | `list`, `get`, `create`, `update`, `delete` | `Designer` |
| `bundle` | `export`, `preview`, `import` | `Deployment` | | `bundle` | `export`, `preview`, `import` | Designer export; Administrator preview/import |
| `audit` | `query`, `export`, `verify-chain` | `OperationalAudit` / `AuditExport` | | `audit` | `query`, `export`, `tree`, `backfill-source-node`, `verify-chain` | Audit roles; Administrator backfill; verify-chain is a local placeholder |
| `audit-config` | `query` (config-change audit trail; was `audit-log` pre-M8) | `Admin` | | `audit-config` | `query` (config-change audit trail; was `audit-log` pre-M8) | `Admin` |
| `health` | `summary`, `site`, `event-log`, `parked-messages` | `Deployment` | | `health` | `summary`, `site`, `event-log`, `parked-messages` | Authenticated reads with server-side scope checks |
| `debug` | `snapshot`, `stream` | `Deployment` | | `debug` | `snapshot`, `stream` | `Deployer` |
### Selected examples ### Selected examples
@@ -198,10 +193,10 @@ The management URL is resolved in this order: `--url` flag → `SCADABRIDGE_MANA
## Dependencies & Interactions ## Dependencies & Interactions
- [Management Service (#18)](./ManagementService.md) — the server-side counterpart. Every CLI command (except `audit query`/`audit export`) translates to a named management command dispatched through `POST /management` to the `ManagementActor`. Role enforcement and LDAP authentication are applied there. The `ManagementCommandRegistry` in Commons maps command types to their names; both sides must stay in sync. - [Management Service (#18)](./ManagementService.md) — the server-side counterpart. Every CLI command (except the audit REST/placeholder and SignalR stream operations) translates to a named management command dispatched through `POST /management` to the `ManagementActor`. Role enforcement and LDAP authentication are applied there. The `ManagementCommandRegistry` in Commons maps command types to their names; both sides must stay in sync.
- [Traefik Proxy (#20)](./TraefikProxy.md) — the recommended connection target. Pointing `--url` at the Traefik address ensures requests reach the active central node without per-command failover logic in the CLI. The `debug stream` command's WebSocket connection (SignalR `/hubs/debug-stream`) also traverses Traefik, which proxies the WebSocket upgrade natively. - [Traefik Proxy (#20)](./TraefikProxy.md) — the recommended connection target. Pointing `--url` at the Traefik address ensures requests reach the active central node without per-command failover logic in the CLI. The `debug stream` command's WebSocket connection (SignalR `/hubs/debug-stream`) also traverses Traefik, which proxies the WebSocket upgrade natively.
- [Audit Log (#23)](./AuditLog.md) — the `audit` command group targets the `GET /api/audit/query` and `GET /api/audit/export` REST endpoints exposed by the Audit Log component, bypassing the management command envelope. The `audit-config` group (formerly `audit-log`) targets the configuration-change audit trail (`IAuditService`) via the standard management envelope. - [Audit Log (#23)](./AuditLog.md) — the `audit` command group targets the query, export, tree, and backfill REST endpoints exposed by the Audit Log component, bypassing the management command envelope; `verify-chain` is a local placeholder. The `audit-config` group (formerly `audit-log`) targets the configuration-change audit trail (`IAuditService`) via the standard management envelope.
- [Security & Auth (#10)](./Security.md) — the server resolves LDAP credentials and maps group memberships to ScadaBridge roles (`Admin`, `Design`, `Deployment`, `OperationalAudit`, `AuditExport`). The CLI does not interact with LDAP directly. - [Security & Auth (#10)](./Security.md) — the server resolves LDAP credentials and maps group memberships to ScadaBridge roles (including `Administrator`, `Designer`, `Deployer`, `Operator`, `Verifier`, and audit roles). The CLI does not interact with LDAP directly.
- [Commons (#16)](./Commons.md) — owns the management command record types and the `ManagementCommandRegistry` that maps each type to its wire name. The CLI project references Commons for these contracts. - [Commons (#16)](./Commons.md) — owns the management command record types and the `ManagementCommandRegistry` that maps each type to its wire name. The CLI project references Commons for these contracts.
- [Transport (#24)](./Transport.md) — the `bundle` command group drives the Transport feature: `bundle export` requests a base64-encoded bundle from the server and streams it to a local `.scadabundle` file; `bundle preview` uploads a file and returns the diff manifest; `bundle import` uploads a file and applies it with a configurable conflict policy. - [Transport (#24)](./Transport.md) — the `bundle` command group drives the Transport feature: `bundle export` requests a base64-encoded bundle from the server and streams it to a local `.scadabundle` file; `bundle preview` uploads a file and returns the diff manifest; `bundle import` uploads a file and applies it with a configurable conflict policy.
- Design spec: [Component-CLI.md](../requirements/Component-CLI.md). - Design spec: [Component-CLI.md](../requirements/Component-CLI.md).
@@ -226,7 +221,7 @@ The central node or Traefik is not reachable at the configured URL. Verify the c
### Authorization failure (exit 2) ### Authorization failure (exit 2)
The server returned HTTP 403 or an error code of `FORBIDDEN`/`UNAUTHORIZED`. The authenticated user's LDAP groups do not map to a role with permission for the requested operation. Use `security role-mapping list` (requires `Admin`) to inspect role mappings. The `multi-role` test user (`password`) holds `Admin`, `Design`, and `Deployment` roles. The server returned HTTP 403 or an error code of `FORBIDDEN`/`UNAUTHORIZED`. The authenticated user's LDAP groups do not map to a role with permission for the requested operation. Use `security role-mapping list` (requires `Administrator`) to inspect role mappings. The `multi-role` test user (`password`) holds `Administrator`, `Designer`, and `Deployer` roles.
### Malformed config file warning ### Malformed config file warning
@@ -278,3 +278,10 @@ No device writes or live configuration mutations were used for smoke verificatio
Final local logs: `/tmp/parity-final-build.log`, `/tmp/parity-main-cli.log`, Final local logs: `/tmp/parity-final-build.log`, `/tmp/parity-main-cli.log`,
`/tmp/parity-deploy.log`, `/tmp/parity-rig-before.json`, `/tmp/parity-deploy.log`, `/tmp/parity-rig-before.json`,
`/tmp/parity-rig-after.json`, and `/tmp/parity-live-smoke.json`. `/tmp/parity-rig-after.json`, and `/tmp/parity-live-smoke.json`.
### CLI documentation follow-up — complete (2026-09-10)
- [x] Integrated all new operations into the main CLI command reference and synchronized the component overview and requirements.
- [x] Documented the five existing external-system method commands that were missing from the README.
- [x] Corrected stale examples, SMS options, secured-write paging, authorization descriptions, and deployment deadline guidance.
- [x] Checked the executable help tree: all **159 leaf commands** appear in the reference. Documented example option names and local Markdown links/anchors passed validation. Documentation-only follow-up; runtime verification above remains applicable.
+60 -9
View File
@@ -48,9 +48,10 @@ The CLI uses a hierarchical subcommand structure mirroring the Management Servic
scadabridge <group> <action> [options] scadabridge <group> <action> [options]
``` ```
All entities are identified by their integer **ID** (via `--id`, `--template-id`, Commands identify targets by integer ID, site identifier, connection name, or
`--site-id`, etc.), not by name. Create/update commands take individual flags — there canonical member name as shown in their help. Most edits use individual flags;
is no `--file` option. The authoritative, always-current reference is the in-repo CSV imports use `--file`, shared schemas use `--schema-file`, and bundles use
`--input`/`--output`. The authoritative, always-current reference is the in-repo
`src/ZB.MOM.WW.ScadaBridge.CLI/README.md`; the command lists below mirror the implemented command `src/ZB.MOM.WW.ScadaBridge.CLI/README.md`; the command lists below mirror the implemented command
tree at the time of writing. tree at the time of writing.
@@ -58,6 +59,8 @@ tree at the time of writing.
``` ```
scadabridge template list [--detail] [--skip <n>] [--take <n>] scadabridge template list [--detail] [--skip <n>] [--take <n>]
scadabridge template get --id <id> scadabridge template get --id <id>
scadabridge template resolved-members --id <id>
scadabridge template move --id <id> [--folder-id <id>]
scadabridge template create --name <name> [--description <desc>] [--parent-id <id>] scadabridge template create --name <name> [--description <desc>] [--parent-id <id>]
scadabridge template update --id <id> [--name <name>] [--description <desc>] [--parent-id <id>] scadabridge template update --id <id> [--name <name>] [--description <desc>] [--parent-id <id>]
scadabridge template validate --id <id> scadabridge template validate --id <id>
@@ -75,6 +78,7 @@ scadabridge template composition add --template-id <id> --instance-name <name> -
scadabridge template composition delete --id <id> scadabridge template composition delete --id <id>
scadabridge template native-alarm-source add --template-id <id> --name <name> --connection <name> --source-ref <ref> [--filter <expr>] [--description <desc>] [--locked] scadabridge template native-alarm-source add --template-id <id> --name <name> --connection <name> --source-ref <ref> [--filter <expr>] [--description <desc>] [--locked]
scadabridge template native-alarm-source list --template-id <id> scadabridge template native-alarm-source list --template-id <id>
scadabridge template native-alarm-source update --id <id> --name <name> --connection <name> --source-ref <ref> [--filter <expr>] [--description <desc>] [--locked]
scadabridge template native-alarm-source remove --id <id> scadabridge template native-alarm-source remove --id <id>
``` ```
@@ -86,6 +90,8 @@ scadabridge instance create --name <name> --template-id <id> --site-id <id> [--a
scadabridge instance set-bindings --id <id> --bindings <json> scadabridge instance set-bindings --id <id> --bindings <json>
scadabridge instance bind-module --id <id> --module <name> --connection <id> --object <path> [--dry-run] scadabridge instance bind-module --id <id> --module <name> --connection <id> --object <path> [--dry-run]
scadabridge instance set-overrides --id <id> --overrides <json> scadabridge instance set-overrides --id <id> --overrides <json>
scadabridge instance attribute-override delete --instance-id <id> --attribute <canonical-name>
scadabridge instance native-alarm-source list --instance-id <id>
scadabridge instance import-overrides --id <id> --file <path.csv> scadabridge instance import-overrides --id <id> --file <path.csv>
scadabridge instance alarm-override set --instance-id <id> --alarm <name> [--trigger-config <json>] [--priority <n>] scadabridge instance alarm-override set --instance-id <id> --alarm <name> [--trigger-config <json>] [--priority <n>]
scadabridge instance alarm-override delete --instance-id <id> --alarm <name> scadabridge instance alarm-override delete --instance-id <id> --alarm <name>
@@ -172,6 +178,7 @@ scadabridge data-connection get --id <id>
scadabridge data-connection create --site-id <id> --name <name> --protocol <protocol> [--primary-config <json>] [--backup-config <json>] [--failover-retry-count <n>] scadabridge data-connection create --site-id <id> --name <name> --protocol <protocol> [--primary-config <json>] [--backup-config <json>] [--failover-retry-count <n>]
scadabridge data-connection update --id <id> --name <name> --protocol <protocol> [--primary-config <json>] [--backup-config <json>] [--failover-retry-count <n>] scadabridge data-connection update --id <id> --name <name> --protocol <protocol> [--primary-config <json>] [--backup-config <json>] [--failover-retry-count <n>]
scadabridge data-connection delete --id <id> scadabridge data-connection delete --id <id>
scadabridge data-connection move --id <id> --site-id <destination-site-id>
``` ```
#### Site-routed OPC UA operations (browse / search / verify-endpoint / certs) #### Site-routed OPC UA operations (browse / search / verify-endpoint / certs)
@@ -210,12 +217,33 @@ scadabridge external-system method delete --id <id>
scadabridge notification list scadabridge notification list
scadabridge notification get --id <id> scadabridge notification get --id <id>
scadabridge notification create --name <name> --emails <comma-separated> scadabridge notification create --name <name> --emails <comma-separated>
scadabridge notification update --id <id> [--name <name>] [--emails <comma-separated>] scadabridge notification update --id <id> --name <name> --emails <comma-separated>
scadabridge notification create --name <name> --type sms --phones <comma-separated>
scadabridge notification update --id <id> --name <name> --type sms --phones <comma-separated>
scadabridge notification rename --id <id> --name <name>
scadabridge notification recipient add --list-id <id> --name <name> [--email <email>] [--phone <E.164-number>]
scadabridge notification recipient delete --id <recipient-id>
scadabridge notification delete --id <id> scadabridge notification delete --id <id>
scadabridge notification smtp list scadabridge notification smtp list
scadabridge notification smtp update --id <id> --server <host> --port <n> --auth-mode <mode> --from-address <email> [--tls-mode <mode>] [--credentials <value>] scadabridge notification smtp update --id <id> --server <host> --port <n> --auth-mode <mode> --from-address <email> [--tls-mode <mode>] [--credentials <value>]
``` ```
Notification rename and individual recipient edits preserve unrelated IDs and
names. Whole-list update replaces recipients. Recipient add requires at least
one contact; the server validates it against the owning list channel.
```sh
scadabridge notification smtp create --server <host> --port <n> --auth-mode <mode> --from-address <email> [--tls-mode <mode>] [--credentials <value>] [--oauth2-authority <url>] [--oauth2-scope <scope>] [--transport <Smtp|Ews>]
scadabridge notification sms list
scadabridge notification sms create --account-sid <sid> --auth-token <token> [--from-number <number>] [--messaging-service-sid <sid>] [--api-base-url <url>] [--connection-timeout-seconds <n>] [--max-retries <n>] [--retry-delay-seconds <n>]
scadabridge notification sms update --id <id> --account-sid <sid> [--from-number <number>] [--messaging-service-sid <sid>] [--api-base-url <url>] [--auth-token <token>] [--connection-timeout-seconds <n>] [--max-retries <n>] [--retry-delay-seconds <n>]
```
SMS requires at least one sender: a From number or a Messaging Service SID.
Omitted timeout/retry settings preserve update values; creation uses defaults.
Omitted Auth Token preserves the stored token on update; omitted Messaging
Service SID clears it. Provider create/update require Administrator access.
### Security Commands ### Security Commands
``` ```
scadabridge security api-key list scadabridge security api-key list
@@ -332,6 +360,20 @@ Key behaviors:
Unlike `debug snapshot` (which uses the HTTP Management API), `debug stream` uses `Microsoft.AspNetCore.SignalR.Client` as a dependency for its WebSocket transport. Unlike `debug snapshot` (which uses the HTTP Management API), `debug stream` uses `Microsoft.AspNetCore.SignalR.Client` as a dependency for its WebSocket transport.
### Shared Schema Commands
```sh
scadabridge shared-schema list
scadabridge shared-schema get --id <id>
scadabridge shared-schema create --name <name> --schema-file <path.json> [--scope <scope>]
scadabridge shared-schema update --id <id> --name <name> --schema-file <path.json> [--scope <scope>]
scadabridge shared-schema delete --id <id>
```
Create/update validate JSON syntax locally and schema structure on the server.
Update replaces all three fields; omitted scope clears it. Mutations require
Designer access; reads require authentication.
### Shared Script Commands ### Shared Script Commands
``` ```
scadabridge shared-script list scadabridge shared-script list
@@ -393,22 +435,31 @@ table).
### Secured-Write Commands (two-person MxGateway writes) ### Secured-Write Commands (two-person MxGateway writes)
Query two-person (MxGateway) secured writes — the pending queue plus terminal history. Manage two-person MxGateway writes. Listing is gated any-of **Operator**,
Read-only; gated any-of **Operator** / **Verifier** / **Administrator** (the history **Verifier**, or **Administrator** because the history exposes process-sensitive
exposes process-sensitive tag values, so it is not open to every authenticated user). tag values. Submission requires Operator; approval/rejection require Verifier,
with a different authenticated principal from the submitter.
``` ```
scadabridge secured-write list [--status <status>] [--site <identifier>] [--skip <n>] [--take <n>] scadabridge secured-write list [--status <status>] [--site <identifier>] [--skip <n>] [--take <n>]
scadabridge secured-write submit --site <identifier> --connection <name> --tag-path <path> --value-json <json> --value-type <type> [--comment <text>]
scadabridge secured-write approve --id <long> [--comment <text>]
scadabridge secured-write reject --id <long> [--comment <text>]
``` ```
`--status` / `--site` filter the results (omit to match all). `--skip` / `--take` provide `--status` / `--site` filter the results (omit to match all). `--skip` / `--take` provide
offset paging (arch-review P3); omitting them uses the historical unpaged default offset paging (arch-review P3); omitting them uses the default page
(`--skip 0 --take 200`). `--take` is clamped to `1..500` server-side. The result carries a (`--skip 0 --take 200`). `--take` is clamped to `1..500` server-side. The result carries a
`totalCount` (rows matching the filters, ignoring paging) alongside the returned page. `totalCount` (rows matching the filters, ignoring paging) alongside the returned page.
Overdue `Pending` rows are swept to `Expired` opportunistically each time the list is Overdue `Pending` rows are swept to `Expired` opportunistically each time the list is
queried (server-side TTL, arch-review S2). queried (server-side TTL, arch-review S2).
The `--format json|table` option is recursive and accepted on every command above. Approval can execute a real device write. Only a server response with status
`Executed` exits zero; `Failed` or an invalid approval outcome exits one, with
JSON/table details retained. Authorization denial exits two. The server enforces
site scope, expiry, separation of duties, and concurrent decisions.
The `--format json|table` option is recursive and accepted on every management command above; audit export has its own output formats.
## Configuration ## Configuration
+259 -105
View File
@@ -22,13 +22,13 @@ Every command requires a connection to a running Central node. The management UR
scadabridge --url http://central-host:5000 <command> scadabridge --url http://central-host:5000 <command>
``` ```
**Docker:** With the Docker setup, the Central UI (and management API) is available at `http://localhost:9001`: **Docker:** Use Traefik at `http://localhost:9000` to reach the active central node:
```sh ```sh
scadabridge --url http://localhost:9001 <command> scadabridge --url http://localhost:9000 <command>
``` ```
For HA failover, use a load balancer URL. The management API is stateless (Basic Auth per request), so any central node can handle any request without sticky sessions. The management API is stateless (Basic Auth per request), so sticky sessions are unnecessary. Direct node URLs (`9001`/`9002`) are useful for diagnostics; use Traefik for active-node routing and failover.
## Global Options ## Global Options
@@ -107,6 +107,24 @@ scadabridge --url <url> --format json template list # unmodified JS
| `--skip` | no | Offset paging: number of items to skip (default 0) | | `--skip` | no | Offset paging: number of items to skip (default 0) |
| `--take` | no | Offset paging: max items to return (clamped 1..1000; omit for unlimited) | | `--take` | no | Offset paging: max items to return (clamped 1..1000; omit for unlimited) |
#### `template resolved-members`
Inspect effective template members, including inheritance. This does not apply composition or instance overrides.
```sh
scadabridge template resolved-members --id <template-id>
```
#### `template move`
Move a template into a folder, or to the root when `--folder-id` is omitted.
```sh
scadabridge template move --id <template-id> [--folder-id <folder-id>]
```
Requires Designer access; server-side folder and dependency checks apply.
#### `template get` #### `template get`
Get a single template by ID. Unlike `template list`, this returns the **full** template Get a single template by ID. Unlike `template list`, this returns the **full** template
@@ -333,6 +351,16 @@ List the native alarm source bindings on a template.
scadabridge --url <url> template native-alarm-source list --template-id <int> scadabridge --url <url> template native-alarm-source list --template-id <int>
``` ```
#### `template native-alarm-source update`
Replace the fields of an existing native alarm source binding.
```sh
scadabridge template native-alarm-source update --id <source-id> --name <name> --connection <name> --source-ref <reference> [--filter <expression>] [--description <text>] [--locked]
```
The ID, name, connection, and source reference are required. Omitted filter and description clear those values; omitted `--locked` means unlocked. Requires Designer access; the server enforces locking rules.
#### `template native-alarm-source remove` #### `template native-alarm-source remove`
Remove a native alarm source binding from a template (by its ID). Remove a native alarm source binding from a template (by its ID).
@@ -566,6 +594,24 @@ Create Instance page can bind the module groups at creation time (design
2026-08-21); the CLI equivalent is a follow-up `instance set-bindings` / 2026-08-21); the CLI equivalent is a follow-up `instance set-bindings` /
`instance bind-module` against the new instance id. `instance bind-module` against the new instance id.
#### `instance native-alarm-source list`
List the native alarm source overrides for an instance.
```sh
scadabridge instance native-alarm-source list --instance-id <id>
```
#### `instance attribute-override delete`
Delete one attribute override while preserving other overrides.
```sh
scadabridge instance attribute-override delete --instance-id <id> --attribute <canonical-name>
```
Use a composed name such as `Module.Speed` where applicable. Requires Deployer access and passes through server-side site-scope checks.
#### `instance native-alarm-source set` #### `instance native-alarm-source set`
Override an inherited native alarm source binding for a single instance (upsert). Blank Override an inherited native alarm source binding for a single instance (upsert). Blank
@@ -1143,6 +1189,16 @@ scadabridge --url <url> data-connection update --id <int> --name <string> --prot
| `--backup-config` | no | Backup protocol-specific configuration as a JSON string | | `--backup-config` | no | Backup protocol-specific configuration as a JSON string |
| `--failover-retry-count` | no | Retries before failover to the backup configuration (default: `3`) | | `--failover-retry-count` | no | Retries before failover to the backup configuration (default: `3`) |
#### `data-connection move`
Move a data connection to a destination site.
```sh
scadabridge data-connection move --id <connection-id> --site-id <destination-site-id>
```
Both IDs are required. Requires Designer access; the server validates the destination and any dependency restrictions.
#### `data-connection delete` #### `data-connection delete`
Delete a data connection. Delete a data connection.
@@ -1326,6 +1382,48 @@ scadabridge --url <url> external-system delete --id <int>
--- ---
#### `external-system method list`
List the methods belonging to an external system.
```sh
scadabridge external-system method list --external-system-id <id>
```
#### `external-system method get`
Read one method definition.
```sh
scadabridge external-system method get --id <id>
```
#### `external-system method create`
Create an external HTTP method. Requires Designer access.
```sh
scadabridge external-system method create --external-system-id <id> --name <name> --http-method <verb> --path <path> [--params <json>] [--return <json>]
```
#### `external-system method update`
Update an external HTTP method. Requires Designer access.
```sh
scadabridge external-system method update --id <id> [--name <name>] [--http-method <verb>] [--path <path>] [--params <json>] [--return <json>]
```
#### `external-system method delete`
Delete a method by ID. Requires Designer access.
```sh
scadabridge external-system method delete --id <id>
```
---
### `notification` — Manage notification lists ### `notification` — Manage notification lists
#### `notification get` #### `notification get`
@@ -1392,6 +1490,36 @@ scadabridge --url <url> notification update --id <int> --name <string> --type sm
| `--emails` | conditional | Comma-separated recipient email addresses. **Required for `--type email`; rejected for `--type sms`.** | | `--emails` | conditional | Comma-separated recipient email addresses. **Required for `--type email`; rejected for `--type sms`.** |
| `--phones` | conditional | Comma-separated recipient phone numbers in E.164. **Required for `--type sms`; rejected for `--type email`.** | | `--phones` | conditional | Comma-separated recipient phone numbers in E.164. **Required for `--type sms`; rejected for `--type email`.** |
#### `notification rename`
Rename a list without replacing its recipients.
```sh
scadabridge notification rename --id <list-id> --name <name>
```
Requires Designer access. Unrelated recipient IDs, names, and contacts are preserved.
#### `notification recipient add`
Add an individual named recipient to an existing list.
```sh
scadabridge notification recipient add --list-id <id> --name <name> [--email <email>] [--phone <E.164-number>]
```
Provide at least one of `--email` or `--phone`. The server checks the contact against the owning list channel. Requires Designer access; other recipients remain unchanged.
#### `notification recipient delete`
Delete one recipient by recipient ID.
```sh
scadabridge notification recipient delete --id <recipient-id>
```
Requires Designer access. Only the specified recipient is removed.
#### `notification delete` #### `notification delete`
Delete a notification list. Delete a notification list.
@@ -1412,6 +1540,16 @@ Show the current SMTP configuration.
scadabridge --url <url> notification smtp list scadabridge --url <url> notification smtp list
``` ```
#### `notification smtp create`
Create a new SMTP configuration. Requires Administrator access.
```sh
scadabridge notification smtp create --server <host> --port <int> --auth-mode <mode> --from-address <email> [--tls-mode <mode>] [--credentials <value>] [--oauth2-authority <url>] [--oauth2-scope <scope>] [--transport <Smtp|Ews>]
```
Uses the same settings as `smtp update`, without `--id`. This sends the distinct create contract; update with ID zero is not a creation operation. The server validates transport/authentication compatibility.
#### `notification smtp update` #### `notification smtp update`
Update the SMTP configuration. Update the SMTP configuration.
@@ -1445,22 +1583,35 @@ Show the current SMS (Twilio) configuration. The Twilio **Auth Token is never re
scadabridge --url <url> notification sms list scadabridge --url <url> notification sms list
``` ```
#### `notification sms create`
Create a new Twilio configuration. Requires Administrator access.
```sh
scadabridge notification sms create --account-sid <sid> --auth-token <token> [--from-number <E.164-number>] [--messaging-service-sid <sid>] [--api-base-url <url>] [--connection-timeout-seconds <n>] [--max-retries <n>] [--retry-delay-seconds <n>]
```
Account SID and auth token are required. Supply at least one sender: `--from-number` or `--messaging-service-sid`. Timeout must be positive; retries/delay must be nonnegative. Omitted policy settings use creation defaults. Explicit zero retry settings retain the outbox fallback semantics; they do not disable retries.
#### `notification sms update` #### `notification sms update`
Update the SMS (Twilio) configuration. Update the SMS (Twilio) configuration.
```sh ```sh
scadabridge --url <url> notification sms update --id <int> --account-sid <string> --from-number <string> [--messaging-service-sid <string>] [--api-base-url <string>] [--auth-token <string>] scadabridge --url <url> notification sms update --id <int> --account-sid <string> [--from-number <string>] [--messaging-service-sid <string>] [--api-base-url <string>] [--auth-token <string>] [--connection-timeout-seconds <n>] [--max-retries <n>] [--retry-delay-seconds <n>]
``` ```
| Option | Required | Description | | Option | Required | Description |
|--------|----------|-------------| |--------|----------|-------------|
| `--id` | yes | SMS config ID | | `--id` | yes | SMS config ID |
| `--account-sid` | yes | Twilio Account SID | | `--account-sid` | yes | Twilio Account SID |
| `--from-number` | yes | Sender phone number (E.164) | | `--from-number` | conditional | Sender phone number (E.164); provide this and/or `--messaging-service-sid` |
| `--messaging-service-sid` | no | Twilio Messaging Service SID. **Omitting it CLEARS the stored value** (the update overwrites it). | | `--messaging-service-sid` | no | Twilio Messaging Service SID. **Omitting it CLEARS the stored value** (the update overwrites it). |
| `--api-base-url` | no | API base URL override (preserves existing if omitted) | | `--api-base-url` | no | API base URL override (preserves existing if omitted) |
| `--auth-token` | no | Twilio Auth Token. **Omitting it PRESERVES the stored token.** Never printed back. | | `--auth-token` | no | Twilio Auth Token. **Omitting it PRESERVES the stored token.** Never printed back. |
| `--connection-timeout-seconds` | no | Positive timeout; omission preserves the existing value |
| `--max-retries` | no | Nonnegative retry setting; omission preserves it; zero retains outbox fallback semantics |
| `--retry-delay-seconds` | no | Nonnegative delay; omission preserves it; zero retains outbox fallback semantics |
--- ---
@@ -1952,6 +2103,56 @@ single `Property`/`Value` cell (deferred-work register row 57).
--- ---
### `shared-schema` — Manage the shared JSON schema library
Reads require authentication; create/update/delete require Designer access.
#### `shared-schema list`
List all shared schemas.
```sh
scadabridge shared-schema list
```
#### `shared-schema get`
Get one shared schema by ID.
```sh
scadabridge shared-schema get --id <id>
```
#### `shared-schema create`
Create a schema from a UTF-8 JSON file.
```sh
scadabridge shared-schema create --name <name> --schema-file <path.json> [--scope <scope>]
```
The CLI validates file readability and JSON syntax; the server validates schema structure and uniqueness.
#### `shared-schema update`
Replace a schema's name, scope, and document.
```sh
scadabridge shared-schema update --id <id> --name <name> --schema-file <path.json> [--scope <scope>]
```
The CLI validates file readability and JSON syntax; the server validates schema structure and uniqueness. Omit `--scope` to clear it on update.
#### `shared-schema delete`
Delete a shared schema by ID.
```sh
scadabridge shared-schema delete --id <id>
```
---
### `shared-script` — Manage shared scripts ### `shared-script` — Manage shared scripts
#### `shared-script list` #### `shared-script list`
@@ -2270,9 +2471,9 @@ scadabridge --url <url> cached-call discard --site-id <string> --tracked-operati
--- ---
### `secured-write` — Query two-person write requests ### `secured-write` — Manage two-person write requests
`secured-write list` reads the MxGateway secured-write queue and terminal history. It requires any of Operator, Verifier or Administrator and applies the caller's site scope. This group also exposes `submit`, `approve`, and `reject` through the same server-side authorization and separation-of-duties checks as the UI; see the parity additions below. `secured-write list` reads the MxGateway secured-write queue and terminal history. It requires any of Operator, Verifier or Administrator and applies the caller's site scope. This group also exposes `submit`, `approve`, and `reject` through the same server-side authorization and separation-of-duties checks as the UI.
```sh ```sh
scadabridge secured-write list --status Pending --site site-a --skip 0 --take 50 scadabridge secured-write list --status Pending --site site-a --skip 0 --take 50
@@ -2280,6 +2481,38 @@ scadabridge secured-write list --status Pending --site site-a --skip 0 --take 50
`--status` and `--site` are optional filters. `--skip` defaults to 0; `--take` defaults to 200 and is clamped server-side to 1–500. Global connection, authentication and output-format options apply. `--status` and `--site` are optional filters. `--skip` defaults to 0; `--take` defaults to 200 and is clamped server-side to 1–500. Global connection, authentication and output-format options apply.
#### `secured-write submit`
Submit a pending MxGateway write as an Operator. A distinct Verifier must approve or reject it.
```sh
scadabridge secured-write submit --site <identifier> --connection <name> --tag-path <path> --value-json <json> --value-type <type> [--comment <text>]
```
The first five options are required. JSON strings must include JSON quotes, for example `--value-json '"Auto"' --value-type String`. The server validates the type/value pairing and site scope and derives identity from authentication.
#### `secured-write approve`
Approve and execute a pending write as a Verifier. This can write to the real device.
```sh
scadabridge secured-write approve --id <long> [--comment <text>]
```
The 64-bit request ID is required. The server enforces a different principal from the submitter, expiry, site scope, and concurrent decision handling. Exit 0 requires `status: Executed`; a failed execution or invalid response exits 1, preserving response details. Authorization denial exits 2.
#### `secured-write reject`
Reject a pending write as a Verifier.
```sh
scadabridge secured-write reject --id <long> [--comment <text>]
```
The 64-bit request ID is required. The server enforces separation of duties and site scope. A rejection does not execute the device write.
---
### `flow-debug` — Trace an inbound API, script, or value-change flow end to end ### `flow-debug` — Trace an inbound API, script, or value-change flow end to end
The CLI face of the **Flow Debugger** (design `docs/plans/2026-09-05-flow-debugger-design.md`, The CLI face of the **Flow Debugger** (design `docs/plans/2026-09-05-flow-debugger-design.md`,
@@ -2411,6 +2644,15 @@ run's outbound API / DB / notification rows, which are always audited.
--- ---
## Deployment deadlines
`deploy instance`, `instance deploy`, `deploy artifacts`, `site deploy-artifacts`,
and `deploy site` defer the overall deadline to the Management API. They keep the
bounded socket-connect timeout but do not impose the ordinary 30-second client
request deadline on a deployment that may still be applying. The API's configured
long-running timeout remains authoritative. Ordinary commands retain a 30-second
client deadline; explicit bundle/Flow Debugger timeouts retain their own policies.
## Architecture Notes ## Architecture Notes
The CLI connects to the Central cluster over **HTTP** (`ManagementHttpClient`), posting to the `/management` endpoints at the configured `managementUrl` — normally the Traefik load balancer, which routes to the active central node. It carries HTTP **Basic** credentials from `--username`/`--password`. Central's endpoint handler asks the `ManagementActor` in-process via `ManagementActorHolder`. The CLI connects to the Central cluster over **HTTP** (`ManagementHttpClient`), posting to the `/management` endpoints at the configured `managementUrl` — normally the Traefik load balancer, which routes to the active central node. It carries HTTP **Basic** credentials from `--username`/`--password`. Central's endpoint handler asks the `ManagementActor` in-process via `ManagementActorHolder`.
@@ -2419,109 +2661,21 @@ There is no Akka dependency in the CLI at all: it does not join the cluster, doe
An `HttpClient` is created per command invocation and disposed when the command completes. An `HttpClient` is created per command invocation and disposed when the command completes.
Role enforcement is applied by the ManagementActor on the server side. The CLI authenticates against LDAP using `--username` / `--password`, resolves LDAP group memberships, then maps groups to ScadaBridge roles (Admin, Design, Deployment) via role mappings configured in the security settings. Operations require the appropriate role — for example, creating templates requires `Design`, deploying requires `Deployment`. In the test environment, use the `multi-role` user (password: `password`) which has all three roles. Role enforcement is applied by the ManagementActor on the server side. The CLI sends `--username` / `--password` as HTTP Basic credentials. The server performs LDAP authentication, resolves group memberships, and maps them to roles such as Administrator, Designer, and Deployer. Operations require the appropriate role — for example, creating templates requires `Designer`, deploying requires `Deployer`. In the test environment, use the `multi-role` user (password: `password`) which has all three roles.
## Issues & Missing Features ## Issues & Missing Features
If you encounter bugs, unexpected behavior, or missing features in the CLI, log them in [`cli_issues.md`](../../cli_issues.md) in the project root. Include a brief description, the command involved, and any relevant error output. If you encounter bugs, unexpected behavior, or missing features in the CLI, log them in [`cli_issues.md`](../../cli_issues.md) in the project root. Include a brief description, the command involved, and any relevant error output.
## Management parity additions (2026-09-10) ## Management parity additions (2026-09-10)
The production CLI exposes all 149 HTTP-eligible management contracts across 20 All 149 HTTP-eligible management contracts are exposed across 20 command groups.
command groups. `CliApplication.BuildRoot()` is used both by the executable and The new operations are documented in their main command sections above, including
by the HTTP-dispatch parity test. Site-only `ReadTagValues` and the retired [shared schemas](#shared-schema--manage-the-shared-json-schema-library),
`ResolveRoles` flow are not accepted by `POST /management`; binding tests continue [notifications](#notification--manage-notification-lists), and
using the site's existing transport. [secured writes](#secured-write--manage-two-person-write-requests).
[Deployment deadlines](#deployment-deadlines) apply to all deployment aliases.
### Shared schema library `CliApplication.BuildRoot()` constructs the executable command tree and the tree
used by the HTTP parity test. Site-only `ReadTagValues` and retired `ResolveRoles`
```sh are excluded from `POST /management`; site binding tests retain their transport.
scadabridge shared-schema list
scadabridge shared-schema get --id 7
scadabridge shared-schema create --name PumpPayload --schema-file ./pump.schema.json --scope Pump
scadabridge shared-schema update --id 7 --name PumpPayload --schema-file ./pump.schema.json
scadabridge shared-schema delete --id 7
```
Create/update read UTF-8 JSON from `--schema-file`; the server validates schema
structure and uniqueness. Update replaces the name, schema, and scope; omitting
`--scope` clears it. Mutations require Designer access.
### Individual notification edits and provider creation
```sh
scadabridge notification rename --id 4 --name "Shift engineers"
scadabridge notification recipient add --list-id 4 --name "On-call engineer" --email engineer@example.com
scadabridge notification recipient delete --id 18
scadabridge notification smtp create --server smtp.example.com --port 587 --auth-mode None --from-address alerts@example.com
scadabridge notification sms create --account-sid AC0123456789abcdef0123456789abcdef --from-number +15551234567 --auth-token "<token>"
```
Rename and individual recipient edits preserve unrelated recipient IDs and names.
Recipient add accepts `--email` and/or `--phone`; the server checks them against
its owning list's channel. Existing `notification update` retains its whole-list
recipient replacement semantics.
SMTP/SMS create accept their update options except `--id` and send distinct create
contracts. SMS create requires an auth token and a sender (`--from-number` and/or
`--messaging-service-sid`). Provider creation/update requires Administrator access.
SMS create and update also accept:
| Option | Meaning |
|---|---|
| `--connection-timeout-seconds` | Positive connection timeout |
| `--max-retries` | Nonnegative retry setting |
| `--retry-delay-seconds` | Nonnegative retry delay |
Omission preserves existing update values or uses creation defaults. Explicit
zero retry settings retain the existing outbox fallback semantics; they do not
promise that delivery will never retry. Existing secret-preservation and
messaging-service-ID clearing behavior on update is unchanged.
### Template, instance, and connection edits
```sh
scadabridge template resolved-members --id 3
scadabridge template move --id 3 --folder-id 8
scadabridge template native-alarm-source update --id 5 --name Pressure --connection Plc --source-ref Pressure --locked
scadabridge instance attribute-override delete --instance-id 7 --attribute Module.Speed
scadabridge instance native-alarm-source list --instance-id 7
scadabridge data-connection move --id 9 --site-id 2
```
`resolved-members` inspects effective inherited template members, without applying
composition or instance overrides. Omit `--folder-id` to move a template to the
root. Native-alarm-source update replaces all contract fields: optional `--filter`
and `--description` clear when omitted, and omitted `--locked` means false.
Individual attribute-override deletion preserves other overrides. Server-side
role, site-scope, locking, and dependency checks remain authoritative.
### Secured-write lifecycle
```sh
scadabridge secured-write submit --site site-a --connection Plc --tag-path Pump.Setpoint --value-json 42 --value-type Int32 --comment "Approved operating procedure"
scadabridge secured-write approve --id 123 --comment "Verified setpoint"
scadabridge secured-write reject --id 124 --comment "Incorrect target"
```
Submit requires Operator access; approve/reject require Verifier access. The
server derives identity from authentication and enforces a different principal
for the verification decision. Approval can execute a real device write.
`--value-json` must be valid JSON; the server validates the value/type pairing.
IDs for approve/reject are 64-bit integers; `--comment` is optional.
Approval exits zero only for an `Executed` response. A server-reported execution
failure exits one while retaining the response details; malformed or unexpected
approval responses also fail. Authorization denial exits two. JSON/table output
continues to use the standard formatter.
### Deployment deadline policy
`deploy instance`, `instance deploy`, `deploy artifacts`, `site deploy-artifacts`,
and `deploy site` defer the overall deadline to the Management API. They keep the
bounded socket-connect timeout but do not impose the ordinary 30-second client
request deadline on a deployment that may still be applying. The API's configured
long-running timeout remains authoritative. Ordinary commands retain a 30-second
client deadline; explicit bundle/Flow Debugger timeouts retain their own policies.