CLI — cw-* Read Commands
CritterWatch ships a server-less command-line surface for an embedded CritterWatch console: four read commands that emit the same JSON the MCP read tools produce, without ASP.NET Core, the MCP HTTP transport, or even starting the application. They exist for AI agents and scripts that want a point-in-time snapshot of a system's health from the shell.
Paid tier
Like the whole MCP/AI surface, the cw-* commands are license-gated. Without a valid CritterWatch license the commands exit non-zero with an explanatory message.
Setup
No extra package. The cw-* commands ship inside CritterWatch.Services, which every console package already brings in — reference CritterWatch for a Marten / PostgreSQL host, CritterWatch.SqlServer for Polecat / SQL Server, or CritterWatch.Sqlite for Fisher / SQLite, exactly as you would to mount the console:
dotnet add package CritterWatchThey also add nothing to your dependency closure: the commands are built on the same store-agnostic reporters the console already carries, and JasperFx.CommandLine arrives with Wolverine itself. No Marten or Polecat reference, no ASP.NET Core, no MCP SDK.
The commands are discovered by JasperFx's command runner — the same RunJasperFxCommands entry point a Wolverine application already uses:
// The cw-* read commands ship with the CritterWatch console packages, and JasperFx's command
// runner discovers them automatically — no registration call. `dotnet run` with no arguments
// starts the host normally; `dotnet run -- cw-health` (etc.) runs the command against a
// BUILT-but-not-started host and exits — a point-in-time read of the last persisted state.
return await app.RunJasperFxCommands(args);Nothing is started when a cw-* command runs: the host is built, not run, so the command reads the console's last persisted state and exits.
Commands
Every command supports --format json (the default — agent-first, stable shapes shared with the MCP tools) and --format text (human-readable).
--view is a closed set
Each command accepts a fixed list of --view values, given per command below. An unrecognised value is an error — the command prints the accepted set to stderr and exits non-zero rather than answering with some other report. Omitting --view is always fine: you get that command's documented default view.
This matters most for the audience these commands were built for. An agent that asks for degraded and silently receives a healthy-looking cluster summary has no way to tell it was misunderstood, so the surface fails closed instead.
cw-health
Accepted --view values: summary (default) · service · degraded
| Invocation | Returns |
|---|---|
dotnet run -- cw-health | Fleet-level health rollup (the default view) |
dotnet run -- cw-health --view summary | Fleet-level health rollup |
dotnet run -- cw-health --view service --service <name> | One service's health detail |
dotnet run -- cw-health --view degraded | Only the services with problems |
cw-performance
Accepted --view values: backlog (default) · hotspots · lag
| Invocation | Returns |
|---|---|
dotnet run -- cw-performance --view backlog | Queue/backlog pressure across endpoints |
dotnet run -- cw-performance --view hotspots | The slowest / busiest message types |
dotnet run -- cw-performance --view lag | Projection lag against the high-water marks |
cw-alerts
Accepted --view values: list (default) · summary · get
| Invocation | Returns |
|---|---|
dotnet run -- cw-alerts --view list | Active alerts |
dotnet run -- cw-alerts --view summary | Alert counts by severity/type |
dotnet run -- cw-alerts --view get --id <alert-stream-id> | One alert's full detail |
cw-routing
cw-routing takes no --view — it has a single report, shaped by whether --message is supplied.
| Invocation | Returns |
|---|---|
dotnet run -- cw-routing --service <name> | The service's message-routing topology |
dotnet run -- cw-routing --service <name> --message <type> | How one message type routes |
Reading the exit code
The commands separate "your invocation was wrong" from "there is nothing to report", because an agent has to branch on the difference:
| Situation | Exit code | Output |
|---|---|---|
| A successful read | 0 | The report on stdout |
| A lookup that found nothing (unknown service, unknown alert id) | 0 | A structured { "found": false, ... } payload on stdout |
| The console has never run (no data yet) | 0 | A structured { "error": "NoMonitoringData", ... } payload on stdout |
An unknown --view, or a missing flag the chosen view requires | non-zero | A message on stderr naming the accepted values |
| No valid CritterWatch license, or the host mounts no console | non-zero | An explanatory message on stderr |
So the empty and not-found cases stay parseable on stdout — scripts branch on the payload without reading stderr — while a malformed invocation is a hard failure that cannot be mistaken for an answer.
Relationship to MCP
The commands reuse the exact reporters behind the CritterWatch.Mcp read tools and serialize through the same JsonSerializerOptions instance, so the JSON is identical to what an MCP client sees — by construction, not by two configurations happening to agree. Pick by transport: MCP for a connected AI session over HTTP, cw-* for shells, cron, and agents that exec commands. See MCP — Model Context Protocol.
Smoke coverage
Every command × view × format combination is exercised by the repo's SmokeTestCli build target against both console hosts (Marten and Polecat / SQL Server), with an anti-drift check that fails the build if a new cw-* command ships without matrix coverage.
