= app-cli

== purpose

Command-line interface built with Clikt that connects to the Correx server to start, manage, and observe sessions. Designed for scripted/headless usage and non-interactive environments.

== responsibilities

* Start sessions by workflow ID via HTTP POST to the server
* Attach to a session's WebSocket stream and relay lifecycle events (session, stage, tool, approval) to stdout
* Prompt for approval decisions (approve / reject / steer) when a TTY is available
* Support `--json` output for machine parsing and `--quiet` for error-only mode
* List sessions, resume/cancel sessions, fetch session events
* List inference providers and their health status
* Send approval decisions to the server via HTTP

== non-responsibilities

* Does not maintain persistent state or local event cache
* Does not contain domain orchestration logic
* Does not perform inference or tool execution
* Does not implement the approval engine — only forwards user decisions
* Does not run a server

== key types

=== CorrexCli
* **kind**: `CliktCommand` (root)
* **purpose**: Top-level CLI command. Defines global flags `--json` and `--quiet`.

=== RunCommand
* **kind**: `CliktCommand` (subcommand)
* **purpose**: Starts a session for a workflow and streams events to stdout. Supports `--workflow`, `--session`, `--auto-approve`, `--host`, `--port`.

=== CliWsClient
* **kind**: class
* **purpose**: Thin Ktor WebSocket client wrapping `HttpClient.webSocket`. Calls `onMessage` callback per frame; returns `false` from callback to close the stream.

=== SystemExitException
* **kind**: exception
* **purpose**: Propagates exit codes (0 = success, 1 = session failure, 2 = rejection in non-TTY mode).

=== RunContext / ApprovalContext
* **kind**: internal data classes
* **purpose**: Carry session ID, JSON flag, auto-approve flag, TTY status through the message handling loop.

== event flow

*inbound:* WebSocket frames from server encoded as JSON `ServerMessage` variants. The CLI decodes the `type` field from a raw `JsonObject` (not deserialized via `ProtocolSerializer`).

*outbound:* `ApprovalResponsePayload` sent as text frames on the WebSocket session.

== integration points

* `apps:cli` depends only on external libraries: Clikt (CLI framework) and Ktor client (HTTP + WebSockets)
* Talks to the server at `http://{host}:{port}` via REST and `ws://{host}:{port}/sessions/{id}/stream`

== invariants

* `CliWsClient.sessionStream` breaks out of the frame loop when `onMessage` returns `false`
* Auto-approve without a TTY is impossible — if `--auto-approve` is not set and no TTY, all approvals are rejected
* Exit code 2 means the user rejected an approval in non-interactive mode
* `RunCommand` creates one session and one WebSocket connection per invocation

== PlantUML diagram

[plantuml, app-cli, "png"]
----
include::../../diagrams/app-cli.puml[]
----




== known issues

* JSON output format is hand-constructed in `printLine()` rather than using a serializer — field ordering and escaping may diverge from `ServerMessage` serialization
* `--session` (resume) acceptance on the server side is not yet implemented — the parameter is forwarded but the server's response is untested
* `resolveDecision` blocks on stdin in TTY mode — no timeout mechanism for user input

== open questions

* Should session resume reconnect to an existing WebSocket stream or replay from the event store? Currently the parameter is accepted but behavior is undefined.
* Should `RunCommand` support multiple sessions concurrently (e.g., `--watch` mode)?
