# Component: CLI

## Purpose

The CLI is a standalone command-line tool for scripting and automating administrative operations against the ScadaBridge central cluster. It connects to the Central Host's HTTP Management API (`POST /management`), which dispatches commands to the ManagementActor. Authentication and role resolution are handled server-side — the CLI sends credentials via HTTP Basic Auth. The CLI provides the same administrative capabilities as the Central UI, enabling automation, batch operations, and integration with CI/CD pipelines.

## Location

Standalone executable, not part of the Host binary. Deployed on any machine with HTTP access to a central node.

`src/ZB.MOM.WW.ScadaBridge.CLI/`

## Responsibilities

- Parse command-line arguments and dispatch to the appropriate management operation.
- Send HTTP requests to the Central Host's Management API endpoint with Basic Auth credentials.
- Display structured responses from the Management API.
- Support both JSON and human-readable table output formats.

## Technology

- **Argument parsing**: `System.CommandLine` library for command/subcommand/option parsing with built-in help generation.
- **Transport**: HTTP client connecting to the Central Host's `POST /management` endpoint. Authentication is via HTTP Basic Auth — the server performs LDAP bind and role resolution.
- **Serialization**: Commands serialized as JSON with a type discriminator (`command` field). Message contracts from Commons define the command types.

## Authentication

The CLI sends user credentials to the Management API via HTTP Basic Auth:

1. The user provides credentials via `--username` / `--password` options.
2. On each request, the CLI encodes credentials as a Basic Auth header and sends them with the command.
3. The server performs LDAP authentication, group lookup, and role resolution — the CLI does not communicate with LDAP directly.
4. Credentials are not stored or cached between invocations. Each CLI invocation requires fresh credentials.

## Connection

The CLI connects to the Central Host via HTTP:

- **Management URL**: The URL of a central node's web server (e.g., `http://localhost:9001`). The management API is served at `POST /management` on the same host as the Central UI.
- **Failover**: For HA, use a load balancer URL in front of both central nodes. The management API is stateless (Basic Auth per request), so any central node can handle any request without sticky sessions.
- **No Akka.NET dependency**: The CLI is a pure HTTP client with no Akka.NET runtime.

## Command Structure

The CLI uses a hierarchical subcommand structure mirroring the Management Service message groups:

```
scadabridge <group> <action> [options]
```

All entities are identified by their integer **ID** (via `--id`, `--template-id`,
`--site-id`, etc.), not by name. Create/update commands take individual flags — there
is no `--file` option. 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
tree at the time of writing.

### Template Commands
```
scadabridge template list
scadabridge template get --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 validate --id <id>
scadabridge template delete --id <id>
scadabridge template attribute add --template-id <id> --name <name> --data-type <type> [--value <value>] [--description <desc>] [--data-source <ref>] [--locked]
scadabridge template attribute update --id <id> [--name <name>] [--data-type <type>] [--value <value>] [--description <desc>] [--data-source <ref>] [--locked]
scadabridge template attribute delete --id <id>
scadabridge template alarm add --template-id <id> --name <name> --trigger-type <type> --priority <n> [--description <desc>] [--trigger-config <json>] [--locked] [--trigger-kind advisory|strict]
scadabridge template alarm update --id <id> --name <name> --trigger-type <type> --priority <n> [--description <desc>] [--trigger-config <json>] [--locked] [--trigger-kind advisory|strict]
scadabridge template alarm delete --id <id>
scadabridge template script add --template-id <id> --name <name> --code <code> --trigger-type <type> [--trigger-config <json>] [--locked] [--parameters <json>] [--return-def <json>] [--trigger-kind advisory|strict]
scadabridge template script update --id <id> --name <name> --code <code> --trigger-type <type> [--trigger-config <json>] [--locked] [--parameters <json>] [--return-def <json>] [--trigger-kind advisory|strict]
scadabridge template script delete --id <id>
scadabridge template composition add --template-id <id> --instance-name <name> --composed-template-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 list --template-id <id>
scadabridge template native-alarm-source remove --id <id>
```

### Instance Commands
```
scadabridge instance list [--site-id <id>] [--template-id <id>] [--search <term>]
scadabridge instance get --id <id>
scadabridge instance create --name <name> --template-id <id> --site-id <id> [--area-id <id>]
scadabridge instance set-bindings --id <id> --bindings <json>
scadabridge instance set-overrides --id <id> --overrides <json>
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 delete --instance-id <id> --alarm <name>
scadabridge instance alarm-override list --instance-id <id>
scadabridge instance native-alarm-source set --instance-id <id> --source <name> [--connection <name>] [--source-ref <ref>] [--filter <expr>]
scadabridge instance native-alarm-source clear --instance-id <id> --source <name>
scadabridge instance set-area --id <id> [--area-id <id>]
scadabridge instance diff --id <id>
scadabridge instance deploy --id <id>
scadabridge instance enable --id <id>
scadabridge instance disable --id <id>
scadabridge instance delete --id <id>
```

`--bindings` is a JSON array of `[attributeName, dataConnectionId]` pairs, e.g.
`[["Speed", 5], ["Mode", 7]]`. `--overrides` is a JSON object of attribute name to
value, e.g. `{"Speed": "100", "Mode": null}`.

### Native Alarm Source Commands

The `native-alarm-source` subcommands manage the **read-only native alarm mirror** —
alarms surfaced from an alarm-capable data connection rather than evaluated by the
ScadaBridge alarm engine. Native alarm sources are declared on a template and may be
overridden per instance. The subcommands map to management commands that resolve via
`ManagementCommandRegistry`:

- `--connection` names an alarm-capable data connection (**OPC UA** or **MxGateway**).
- `--source-ref` is the connection-specific reference: an **OPC UA `SourceNode` nodeId**
  or an **MxAccess object/area**.
- `--filter` is an optional connection-specific filter expression that narrows the
  mirrored alarm set.

**Template-level** (defines the inherited native alarm sources):

| CLI command | Management command | Required role |
|-------------|--------------------|---------------|
| `template native-alarm-source add` | `AddTemplateNativeAlarmSourceCommand` | Design |
| `template native-alarm-source list` | `ListTemplateNativeAlarmSourcesCommand` | — |
| `template native-alarm-source remove` | `DeleteTemplateNativeAlarmSourceCommand` | Design |

`add` takes `--name`, `--connection`, and `--source-ref` (required), plus optional
`--filter`, `--description`, and `--locked` (a flag that prevents instance-level
override). `remove` targets a single native alarm source by its own `--id`.

**Instance-level** (per-instance overrides of an inherited source; upsert semantics):

| CLI command | Management command | Required role |
|-------------|--------------------|---------------|
| `instance native-alarm-source set` | `SetInstanceNativeAlarmSourceOverrideCommand` | Deployment |
| `instance native-alarm-source clear` | `DeleteInstanceNativeAlarmSourceOverrideCommand` | Deployment |

`set` is an **upsert** keyed by `--instance-id` and `--source` (the inherited source
name): a blank/omitted `--connection`, `--source-ref`, or `--filter` keeps the
inherited value, so only the supplied options are overridden. `clear` removes the
override and **reverts the instance to the inherited template value**.

### Site Commands
```
scadabridge site list
scadabridge site get --id <id>
scadabridge site create --identifier <id> --name <name> [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
scadabridge site update --id <id> [--name <name>] [--description <desc>] [--node-a-address <addr>] [--node-b-address <addr>] [--grpc-node-a-address <addr>] [--grpc-node-b-address <addr>]
scadabridge site delete --id <id>
scadabridge site area list --site-id <id>
scadabridge site area create --site-id <id> --name <name> [--parent-id <id>]
scadabridge site area update --id <id> --name <name>
scadabridge site area delete --id <id>
scadabridge site deploy-artifacts [--site-id <id>]
```

### Deployment Commands
```
scadabridge deploy instance --id <id>
scadabridge deploy artifacts [--site-id <id>]
scadabridge deploy status [--instance-id <id>] [--status <status>] [--page <n>] [--page-size <n>]
```

### Data Connection Commands
```
scadabridge data-connection list [--site-id <id>]
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 update --id <id> --name <name> --protocol <protocol> [--primary-config <json>] [--backup-config <json>] [--failover-retry-count <n>]
scadabridge data-connection delete --id <id>
```

### External System Commands
```
scadabridge external-system list
scadabridge external-system get --id <id>
scadabridge external-system create --name <name> --endpoint-url <url> --auth-type <type> [--auth-config <json>]
scadabridge external-system update --id <id> [--name <name>] [--endpoint-url <url>] [--auth-type <type>] [--auth-config <json>]
scadabridge external-system delete --id <id>
scadabridge external-system method list --external-system-id <id>
scadabridge external-system method get --id <id>
scadabridge external-system method create --external-system-id <id> --name <name> --http-method <verb> --path <path> [--params <json>] [--return <json>]
scadabridge external-system method update --id <id> [--name <name>] [--http-method <verb>] [--path <path>] [--params <json>] [--return <json>]
scadabridge external-system method delete --id <id>
```

### Notification Commands
```
scadabridge notification list
scadabridge notification get --id <id>
scadabridge notification create --name <name> --emails <comma-separated>
scadabridge notification update --id <id> [--name <name>] [--emails <comma-separated>]
scadabridge notification delete --id <id>
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>]
```

### Security Commands
```
scadabridge security api-key list
scadabridge security api-key create --name <name> --methods <comma-separated-method-names>
scadabridge security api-key update --key-id <id> --enabled <bool>
scadabridge security api-key set-methods --key-id <id> --methods <comma-separated-method-names>
scadabridge security api-key delete --key-id <id>
scadabridge security role-mapping list
scadabridge security role-mapping create --ldap-group <group> --role <role>
scadabridge security role-mapping update --id <id> [--ldap-group <group>] [--role <role>]
scadabridge security role-mapping delete --id <id>
scadabridge security scope-rule list --mapping-id <id>
scadabridge security scope-rule add --mapping-id <id> --site-id <id>
scadabridge security scope-rule delete --id <id>
```

### Configuration-Change Audit Commands

The `audit-config` group queries the configuration-change audit trail (the `IAuditService`
record of admin edits). `audit-log` is a deprecated alias that still works but emits a
warning; migrate scripts to `audit-config`.

```
scadabridge audit-config query [--user <username>] [--entity-type <type>] [--action <action>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>]
```

The new centralized Audit Log component (#23) is exposed via the `scadabridge audit` group below.

### Centralized Audit Commands

The `scadabridge audit` group targets the centralized Audit Log component (#23) and
exposes the UI-equivalent operational audit surface. Permissions follow the same
read-vs-export split the Central UI uses (see Component-AuditLog.md, Security &
Tamper-Evidence, and Security & Auth #10): `audit query`, `audit tree`, and
`audit verify-chain` require the `OperationalAudit` permission; `audit export`
additionally requires `AuditExport`; `audit backfill-source-node` requires the
`Admin` role (maintenance path only). The server enforces permission checks and
returns HTTP 403 (CLI exit code 2) on denial.

```
scadabridge audit query [--since <t>] [--until <t>] [--channel <c>] [--kind <k>] [--status <s>] [--site <s>] [--target <t>] [--actor <a>] [--correlation-id <id>] [--execution-id <id>] [--parent-execution-id <id>] [--errors-only] [--page-size <n>] [--all]
scadabridge audit export --since <t> --until <t> --format csv|jsonl|parquet --output <path> [--channel <c>] [--kind <k>] [--status <s>] [--site <s>] [--target <t>] [--actor <a>]
scadabridge audit tree --execution-id <guid> [--format table|json]
scadabridge audit backfill-source-node --before <ISO-8601-UTC> [--sentinel <value>] [--batch <n>]
scadabridge audit verify-chain --month <YYYY-MM>
```

- `audit query` — filtered query against the central `AuditLog` table, matching the
  Central UI Audit Log page filter set (time range, channel, kind, status, site,
  instance/script, target, actor, correlation ID, errors-only). Results stream as
  JSON (default) or table.
- `audit export` — server-side streaming export of the central `AuditLog` to the
  requested format (`csv`, `jsonl`, `parquet`) written to `--output`. The server
  streams rows rather than materializing them in memory; the CLI writes bytes
  through to disk. Supports the same scoping filters as `audit query`.
- `audit tree --execution-id <guid>` (M5.3 T8) — renders the full execution-chain
  tree for the given `ExecutionId`. The server resolves the root from any node in
  the chain (walks `ParentExecutionId` to find the root, then traverses downward)
  and returns all reachable executions with their summary row counts and first/last
  occurred timestamps. Output format: `json` (default — structured tree suitable
  for scripting) or `table` (human-readable indented tree). Requires
  `OperationalAudit` permission. Backed by `GET /api/audit/tree?executionId=<guid>`.
- `audit backfill-source-node --before <ISO-8601-UTC>` (M5.6 T5) — sets
  `SourceNode` to a sentinel value (`--sentinel`, default `"unknown"`) on pre-feature
  rows where `SourceNode IS NULL` and `OccurredAtUtc < --before`, in batches
  (`--batch`, default 5000). Admin-only maintenance command. Idempotent.
  Backed by `POST /api/audit/backfill-source-node`.
- `audit verify-chain` — hash-chain verification for the named month.
  **No-op in v1**: the command is defined so the command tree is stable, but
  verification only becomes meaningful once the hash-chain ships (see
  Component-AuditLog.md, Security & Tamper-Evidence). Until then, the server
  responds with a "verification not yet available" status and the CLI exits 0.

### Health Commands
```
scadabridge health summary
scadabridge health site --identifier <site-identifier>
scadabridge health event-log --site <site-identifier> [--event-type <type>] [--severity <level>] [--keyword <term>] [--from <date>] [--to <date>] [--page <n>] [--page-size <n>] [--instance-name <name>]
scadabridge health parked-messages --site <site-identifier> [--page <n>] [--page-size <n>]
```

### Debug Commands
```
scadabridge debug snapshot --id <id>
scadabridge debug stream --id <id>
```

The `debug snapshot` command retrieves a point-in-time snapshot via the HTTP Management API.

The `debug stream` command streams live attribute values and alarm state changes in real-time using a SignalR WebSocket connection. The CLI connects to the `/hubs/debug-stream` SignalR hub on the central server, authenticates with Basic Auth, and subscribes to the specified instance. Events are printed as they arrive — JSON format (default) outputs one NDJSON object per event; table format shows streaming rows. Press Ctrl+C to disconnect.

Key behaviors:
- **Automatic reconnection**: Uses SignalR's `.WithAutomaticReconnect()` to re-establish the connection on loss.
- **Re-subscription**: Automatically re-subscribes to the instance after reconnection.
- **Traefik compatible**: Works through the Traefik reverse proxy — WebSocket upgrade is proxied natively.
- **Required role**: `Deployment`.

Unlike `debug snapshot` (which uses the HTTP Management API), `debug stream` uses `Microsoft.AspNetCore.SignalR.Client` as a dependency for its WebSocket transport.

### Shared Script Commands
```
scadabridge shared-script list
scadabridge shared-script get --id <id>
scadabridge shared-script create --name <name> --code <code> [--parameters <json>] [--return-def <json>]
scadabridge shared-script update --id <id> [--name <name>] [--code <code>] [--parameters <json>] [--return-def <json>]
scadabridge shared-script delete --id <id>
```

### Database Connection Commands
```
scadabridge db-connection list
scadabridge db-connection get --id <id>
scadabridge db-connection create --name <name> --connection-string <string>
scadabridge db-connection update --id <id> [--name <name>] [--connection-string <string>]
scadabridge db-connection delete --id <id>
```

### Inbound API Method Commands
```
scadabridge api-method list
scadabridge api-method get --id <id>
scadabridge api-method create --name <name> --script <code> [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
scadabridge api-method update --id <id> --script <code> [--timeout <seconds>] [--parameters <json>] [--return-def <json>]
scadabridge api-method delete --id <id>
```

### Bundle Commands (Transport #24)

```
scadabridge bundle export --output <path> [--passphrase <phrase>] [--all] [--include-dependencies] [--templates <names>] [--shared-scripts <names>] [--external-systems <names>] [--db-connections <names>] [--notification-lists <names>] [--smtp-configs <names>] [--api-methods <names>] [--sites <ids|names>] [--instances <unique-names>] [--source-environment <env>]
scadabridge bundle preview --input <path> [--passphrase <phrase>]
scadabridge bundle import --input <path> [--passphrase <phrase>] [--on-conflict skip|overwrite|rename] [--map-site <src=dst> ...] [--map-connection <srcSite/srcName=dstName> ...] [--create-missing-sites] [--create-missing-connections]
```

`--sites` / `--instances` (M8/T18) pull site-scoped artifacts: a `--sites` token is a `SiteIdentifier` (preferred) or friendly name; a `--instances` token is a `UniqueName`. Selecting an instance also pulls its site and bound `DataConnection`s.

`bundle preview` prints the per-row diff plus a **required-mapping summary**: for each source site/connection it states either the auto-match (`auto-matches 'X'`) or the directive to supply `--map-site` / `--map-connection` or `--create-missing-*`.

On import, the mapping flags reconcile environment-specific identifiers. `--map-site` / `--map-connection` are repeatable; a token with no `=dst` (or `=` with an empty right-hand side) means **create-new** from the bundle payload, otherwise it binds the source to the named existing target. `--create-missing-sites` / `--create-missing-connections` create any still-unmapped source site/connection instead of aborting. Imported instances always land `NotDeployed`.

Inbound API keys are not transported between environments — re-create them on the destination via CLI or UI.
Bundle commands use a 5-minute timeout.

### Cached-Call Commands (Site Call Audit #22)

Retry or discard parked `ExternalSystem.CachedCall` / `Database.CachedWrite` operations
at a site. Both commands relay via the central `SiteCallAuditActor` → site relay and
require the **Deployer** role (same gate as the Central UI Site Calls page).

```
scadabridge cached-call retry   --site-id <identifier> --tracked-operation-id <id>
scadabridge cached-call discard --site-id <identifier> --tracked-operation-id <id>
```

The `--tracked-operation-id` value corresponds to the `MessageId` field of the parked
`SiteCall` row (visible on the Central UI Site Calls page and in the `SiteCalls` audit
table).

The `--format json|table` option is recursive and accepted on every command above.

## Configuration

Configuration is resolved in the following priority order (highest wins):

1. **Command-line options**: `--url`, `--username`, `--password`, `--format`.
2. **Environment variables**:
   - `SCADABRIDGE_MANAGEMENT_URL` — Management API URL (e.g., `http://central-host:5000`).
   - `SCADABRIDGE_FORMAT` — Default output format (`json` or `table`).
   - `SCADABRIDGE_USERNAME` / `SCADABRIDGE_PASSWORD` — LDAP credentials. Preferred over
     `--password` on the command line, which is visible in process listings and shell
     history. Credentials are never read from the config file.
3. **Configuration file**: `~/.scadabridge/config.json` — Persistent defaults for management URL and output format only (never credentials).

### Configuration File Format

```json
{
  "managementUrl": "http://central-host:5000"
}
```

## Output Formats

- **JSON** (default): Machine-readable JSON output to stdout. Suitable for piping to `jq` or processing in scripts. Errors are written to stderr as JSON objects with `error` and `code` fields.
- **Table** (`--format table` or `--table`): Human-readable tabular output with aligned columns. Suitable for interactive use.

## Exit Codes

| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | General error (command failed, connection failure, or authentication failure) |
| 2 | Authorization failure (insufficient role) |

## Error Handling

- **Connection failure**: If the CLI cannot connect to the management URL (e.g., DNS failure, connection refused), it exits with code 1 and a descriptive error message.
- **Command timeout**: If the server does not respond within 30 seconds, the command fails with a timeout error (HTTP 504).
- **Authentication failure**: If the server returns HTTP 401 (LDAP bind failed), the CLI exits with code 1.
- **Authorization failure**: If the server returns HTTP 403, the CLI exits with code 2.

## Dependencies

- **Commons**: Message contracts (`Messages/Management/`) for command type definitions and registry.
- **System.CommandLine**: Command-line argument parsing.
- **Microsoft.AspNetCore.SignalR.Client**: SignalR client for the `debug stream` command's WebSocket connection.
- **Management Service (#18)**: The CLI hits the central cluster via the existing HTTP Management API (`POST /management`), which dispatches to the ManagementActor. The `scadabridge audit` command group rides a parallel REST surface on the same Host (`GET /api/audit/query` and `GET /api/audit/export`), sharing HTTP Basic Auth with `/management` but bypassing the actor for read-only, keyset-paged / streaming workloads.
- **Audit Log (#23)**: The `scadabridge audit query`, `audit export`, `audit tree`, and `audit backfill-source-node` subcommands target the centralized Audit Log component's REST endpoints (`GET /api/audit/query`, `GET /api/audit/export`, `GET /api/audit/tree`, `POST /api/audit/backfill-source-node`) on the Host's Management API surface; `audit verify-chain` is a client-side no-op today (hash-chain deferred to v1.x). Permission checks (`OperationalAudit`, `AuditExport`, `Admin`) are enforced server-side by `AuditEndpoints`.

## Interactions

- **Management Service (via HTTP)**: The primary runtime dependency. All operations except `debug stream` are sent as HTTP POST requests to the Management API endpoint on a central node, which dispatches to the ManagementActor.
- **Central Host**: Serves the Management API at `POST /management` and the debug stream SignalR hub at `/hubs/debug-stream`. Handles LDAP authentication, role resolution, and ManagementActor dispatch.
- **Debug Stream Hub (via SignalR WebSocket)**: The `debug stream` command connects to the `/hubs/debug-stream` hub on the central server for real-time event streaming. This is the only CLI command that uses a persistent connection rather than request/response.
