spec.md 9.0 KB

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 <key> 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 <key> 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., ━━━ <name> ━━━ 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": <name>, "success": <bool>, "data": <API data object or null>, "error": <string or null>} 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: <max_flows> flows · $<max_value_usd> 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