### Requirement: Fetch subscription detail from ZenMux per account The CLI SHALL call `GET https://zenmux.ai/api/v1/management/subscription/detail` with an `Authorization: Bearer ` header for each resolved account and parse the JSON response into typed structures covering `plan`, `currency`, `base_usd_per_flow`, `effective_usd_per_flow`, `account_status`, `quota_5_hour`, `quota_7_day`, and `quota_monthly`. Accounts MUST be fetched sequentially in the order they were resolved. A failure on one account MUST NOT abort fetches for the remaining accounts. #### Scenario: Single-account fetch - **WHEN** the user runs `zenmux-usage` with exactly one resolved account - **THEN** the CLI issues exactly one GET request to the subscription detail endpoint with `Authorization: Bearer ` and parses `success: true` into typed structs #### Scenario: Multi-account fetch order - **WHEN** the user runs `zenmux-usage` with accounts `personal` then `work` resolved - **THEN** the CLI first fetches `personal`, then `work`, rendering each as soon as its response is available #### Scenario: Partial failure does not abort - **WHEN** three accounts are resolved and the second account returns HTTP 401 - **THEN** the CLI still fetches the third account and renders blocks for all three (one with an error body) #### Scenario: Request respects a configurable timeout - **WHEN** the user runs `zenmux-usage --timeout 2s` and a server has not responded within 2 seconds - **THEN** the CLI aborts that account's request and records it as a timeout error while continuing with any remaining accounts ### Requirement: Render two quota windows per account In human output mode the CLI SHALL render, for each resolved account, one labeled row per rolling window — `5 hour` and `7 day`. Each row MUST include a fixed-width progress bar, the usage percentage to two decimals, the used-vs-max flows, and the used-vs-max USD value. Bars MUST be color-coded by usage band: green below 60%, yellow from 60% through 85%, red above 85%. The monthly quota MUST NOT be rendered as a third progress-bar row; it is emitted separately by the "Display monthly cap" requirement. #### Scenario: Healthy 5-hour window - **WHEN** the API returns `quota_5_hour.usage_percentage = 0.0715`, `used_flows = 57.2`, `max_flows = 800`, `used_value_usd = 1.88`, `max_value_usd = 26.26` for an account - **THEN** that account's block contains a row labeled `5 hour`, a green-colored bar roughly 7% filled, `7.15%`, `57.2 / 800 flows`, and `$1.88 / $26.26` #### Scenario: No monthly progress row rendered - **WHEN** an account is rendered in human mode - **THEN** the output MUST NOT contain a row labeled `month` inside the progress-bar block, and MUST NOT contain an em-dash (`—`) placeholder or `n/a` percentage inside the bar rows #### Scenario: Color disabled via flag - **WHEN** the user runs `zenmux-usage --no-color` - **THEN** no ANSI color escape sequences appear in the output #### Scenario: Non-TTY output is automatically plain - **WHEN** stdout is piped or redirected to a file - **THEN** the CLI omits ANSI color escape sequences regardless of the `--no-color` flag ### Requirement: Render per-account header block For every resolved account the CLI SHALL print, before the quota rows, a header that identifies the account by name followed by a plan/status line. The account header MUST be visually distinct (e.g., `━━━ ━━━` with bold styling when colors are enabled). The plan line MUST include the plan tier (capitalized), the monthly `amount_usd`, the `account_status`, and the `effective_usd_per_flow` rate. #### Scenario: Single account header - **WHEN** one resolved account named `personal` returns `plan.tier = "ultra"`, `plan.amount_usd = 200`, `account_status = "healthy"`, `effective_usd_per_flow = 0.03283` - **THEN** the output contains a header block showing `personal`, and a plan line containing `Ultra plan`, `$200/mo`, `healthy`, and `$0.03283/flow` #### Scenario: Multiple accounts separated by blank lines - **WHEN** two accounts `personal` and `work` are rendered in human mode - **THEN** each account has its own header block and the two blocks are separated by at least one blank line, in the order they were resolved ### Requirement: Display token USD value consumed per account Below each account's quota rows the CLI SHALL print a summary line labeled "Tokens consumed (estimated USD value)" showing the `used_value_usd` from the 7-day window for that account, formatted as USD to two decimals. #### Scenario: 7-day used value present - **WHEN** an account's `quota_7_day.used_value_usd = 13.66` - **THEN** that account's summary line reads `Tokens consumed (estimated USD value): $13.66` ### Requirement: JSON passthrough mode When invoked with `--json` the CLI SHALL write machine-readable JSON to stdout and MUST NOT print any human-formatted output. - If exactly one account was resolved, the output MUST be the single API response body unchanged (pretty-printing is not required; a trailing newline is permitted). - If more than one account was resolved, the output MUST be a JSON array whose elements are objects with shape `{"account": , "success": , "data": , "error": }` in the order accounts were resolved. Successful accounts MUST have `error: null`; failed accounts MUST have `data: null` and a human-readable `error` string. Exit codes behave identically to human mode. #### Scenario: Single-account JSON - **WHEN** exactly one account is resolved and the user runs `zenmux-usage --json | jq .data.plan.tier` - **THEN** `jq` receives valid JSON and extracts the plan tier from `data.plan.tier` #### Scenario: Multi-account JSON - **WHEN** two accounts `personal` and `work` are resolved and `work` returns HTTP 429 - **THEN** stdout is a JSON array of length 2; the `personal` element has `success: true` and a populated `data` field; the `work` element has `success: false`, `data: null`, and a non-null `error` #### Scenario: JSON mode on full failure - **WHEN** the CLI fails before any account was fetched (e.g., config parse error) - **THEN** the CLI writes the error message to stderr, exits with the corresponding non-zero code, and writes nothing to stdout ### Requirement: Map API and transport failures to distinct exit codes The CLI SHALL exit with these codes and write a single human-readable line to stderr for CLI-global failures: - `2` — invalid flag or argument (including unknown `--account` name) - `3` — no account resolvable (missing config and env, or explicit `--config` file missing) - `4` — authentication rejected (HTTP 401 or 403) — only when every fetched account failed with this cause - `5` — rate limited (HTTP 422) — only when every fetched account failed with this cause - `6` — network error or timeout — only when every fetched account failed with this cause - `7` — config file parse error or schema validation failure - `1` — any other unexpected error, or a mix of failure causes across accounts, or at least one account failed while others succeeded On `success: false` in a response body the CLI SHALL treat that account as a failure and surface any error message from the payload. #### Scenario: All accounts auth-rejected - **WHEN** every resolved account returns HTTP 401 - **THEN** the CLI exits with code 4 after rendering each account's error block #### Scenario: Mixed outcomes - **WHEN** account `personal` returns 200 and account `work` returns 401 - **THEN** the CLI exits with code 1 after rendering both accounts #### Scenario: All transport timeouts - **WHEN** every resolved account's HTTP request exceeds the configured timeout - **THEN** the CLI exits with code 6 after rendering each account's timeout block #### Scenario: Single-account run preserves specific code - **WHEN** a single-account run (`--account work`, `--api-key`, or env-var fallback) fails with HTTP 401 - **THEN** the CLI exits with code 4 ### Requirement: Display monthly cap Below the "Tokens consumed" summary line and above any "Next reset" line the CLI SHALL, in human output mode, emit a one-line summary of the monthly quota cap using `quota_monthly.max_flows` and `quota_monthly.max_value_usd`. The line MUST be formatted as `Monthly cap: flows · $` with `max_flows` printed without trailing zeros and `max_value_usd` formatted as USD to two decimals. #### Scenario: Monthly cap present - **WHEN** an account returns `quota_monthly.max_flows = 34560` and `quota_monthly.max_value_usd = 1134.33` - **THEN** the output contains a line `Monthly cap: 34560 flows · $1134.33` #### Scenario: Cap line placement - **WHEN** the human-mode block is rendered for an account - **THEN** the "Monthly cap:" line appears after the "Tokens consumed (estimated USD value):" line and before the "Next reset:" line (when a reset line is emitted) #### Scenario: JSON mode unchanged - **WHEN** the user runs `zenmux-usage --json` - **THEN** no "Monthly cap" line is injected into the JSON output; the `quota_monthly` field from the API response is the only monthly representation