# one-shot query (default mode)
openusage-cli query
# same as above
openusage-cli
# run daemon (background by default)
openusage-cli run-daemon --host 127.0.0.1 --port 0Default daemon port is 0, which means "pick a free port".
- Returns JSON payload once, then exits
- Tries daemon first for fast responses
- Set
--use-daemon=falseto skip daemon discovery and force one-shot local execution - Falls back to local plugin execution if daemon is unavailable
- For
--type=usage, fallback performs provider polling and can take noticeable time
Include mode metadata in output:
openusage-cli query --with-statestate.queryMode is cache (daemon path) or direct (fallback path).
- Keeps daemon process running and cache populated; a QuickJS runtime is created per provider refresh and shared only by discovery and sequential probe calls within that refresh
- Serves data through the local REST API
- Also accelerates
query, becausequerycan reuse daemon data
For daemon operation patterns (standalone vs systemd), see daemon-modes.md.
query: one-shot JSON output (--type=usage|plugins|config)run-daemon: start daemon modeshow-default-config: print defaultconfig.yamltemplateinstall-systemd-unit: create~/.config/systemd/user/openusage-cli.serviceversion: print versionhelp: print help
Global flags:
--log-level <error|warn|info|debug|trace>
Runtime flags (query, run-daemon):
--plugins-dir <path>--enabled-plugins <csv-globs>(default:*)--app-data-dir <path>--plugin-overrides-dir <path>
query flags:
--use-daemon[=true|false](trueby default; setfalseto skip daemon discovery)
run-daemon flags:
-
--host <host>(default:127.0.0.1) -
--port <port>(default:0) -
--refresh-interval-secs <seconds>(default:180) -
--aggressive-refresh-interval-secs <seconds>(default:10)When a provider plan includes a
resetAttimestamp, the daemon switches to the aggressive interval for that provider after the reset time is reached, polling more frequently so the new quota snapshot is available promptly. -
--existing-instance <error|ignore|replace>(default:error) -
--service-mode <standalone|systemd>(default:standalone) -
--foreground[=true|false](--foregroundmeanstrue, default:false) -
--daemon-child(internal)
Base URL example:
http://127.0.0.1:6738
-
GET /health: service health and loaded plugin count. -
GET /v1/plugins: plugin metadata for discovered plugins. -
GET /v1/usage[?refresh=true][&pluginIds=codex,cursor]: usage snapshots.refresh(optional, defaultfalse): trigger fresh probe before returning data.pluginIds(optional): comma-separated plugin IDs filter.
Response shape (JSON array of objects):
{ "providerId": "codex", "displayName": "Codex", "plan": null, "lines": [ { "type": "text", "label": "Status", "value": "ok" } ], "iconUrl": "data:image/svg+xml;base64,...", "fetchedAt": "2026-07-30T12:00:00Z", "account": { "id": "default", "origin": "native" } }The
accountobject is present on every usage snapshot. Most providers return only"id": "default".account.idis a provider-scoped unique identifier in output. For current bundled plugins, explicit discovery ids are preserved when they are already unique. When discovery returns colliding ids, the runtime emits a deterministic host-generated id (acc_v1_<hash>) to keep output ids unique.account.nameis optional and intended for display labels from discovery. It does not participate in routing or identity checks.ctx.account.idused inside plugin/override probe logic may remain the descriptor id even when outputaccount.idis canonicalized for uniqueness.originfield: Every account object carries anoriginstring that identifies the credential source:"native"— credentials supplied by the original plugin (no override active)."opencode"— current Codex and Copilot override accounts that read from OpenCode auth files.- Future releases may introduce additional source identifiers (e.g.
"jetbrains","vscode").
The
originis assigned by the runtime based on the account descriptor or override context. It is preserved unchanged through the daemon cache and serialized API responses.When a discovery account has
errorPolicy: "hide-if-other-account"and at least one other account descriptor exists, policy-suppressed probe failures (context construction errors, probe exceptions, promise rejections, and invalid returned probe objects/lines) are intentionally omitted from output. Non-policy errors and unmarked account failures are always included.The following applies when the plugin does not export
discoverAccounts(single-account mode). For discovery mode, see Multi-account discovery.The account identity is not determined by the probe result. The runtime assigns
{ id: "default", origin: "native" }on every output. The probe returns onlyplanandlines— anyaccountfield in the probe result is ignored.Plugins may optionally export a
discoverAccounts(ctx)function that returns an array of account descriptors. When present, the runtime calls it to determine which accounts to probe. Each discovered account is probed sequentially with a fresh child context.// In plugin.js: { probe(ctx) { /* ... */ }, discoverAccounts(ctx) { return [ { id: "work", origin: "native" }, { id: "personal" } ]; } }
Capability detection:
discoverAccountsis detected only after the plugin script and any override script are evaluated. If absent,null, orundefined, the runtime uses single-account mode with the default account.If the
discoverAccountsproperty accessor throws an exception (e.g. a Proxy trap), the runtime produces a single provider-level error output with the default account — it does not fall back to single-account mode.Account context: Each discovered account receives a child context that inherits from the base
__openusage_ctx. Thectx.accountobject has an immutableidfield matching the discovered descriptor. Theoriginfield is not exposed onctx.account— it is a runtime output annotation only. In single-account mode, the probe receives a child context withctx.account = { id: "default" }whose identity field is similarly immutable.Validation: The returned array must contain 0–32 items. Each item must be an object with a non-empty string
id. An optionalerrorPolicyfield may be set to"hide-if-other-account"(see Discovery error policy). An optionaloriginstring may be provided; if absent, the runtime assigns"native"as the default. Optional descriptor fieldsname,stableSubjectKey, andsourceRefmay also be provided. Violations (non-array, missing or empty id, invalid errorPolicy, over 32 items, or an exception) produce a single provider-level error output with the default account. If effective output ids still collide after host normalization, the runtime returns a provider-level duplicate-output-id error.Empty list: A valid empty array produces zero outputs for that provider, clearing any previously cached snapshots.
Host-authoritative identity: In discovery mode, the runtime assigns account identity from the discovered descriptor (including
origin). The probe result'saccountfield is ignored and never used to override, validate, or reject discovered identity.Failure isolation: A per-account probe failure produces an error snapshot carrying that account's identity. Other accounts are unaffected. However, when a discovery account has
errorPolicy: "hide-if-other-account"and at least one other account descriptor exists, the policy-suppressed failure snapshot is intentionally omitted from output (see Discovery error policy).Subscriptions: All
subscribeFilecalls duringdiscoverAccountsand each account probe are collected into a single provider-level set. File-monitor commands are sent once per provider with the unioned subscriptions.Refresh atomicity: Each provider refresh atomically replaces that provider's entire cache vector. A valid empty discovery list clears the provider's cached snapshots.
Vendored plugins do not natively implement
discoverAccounts, but the bundled Codex and Copilot overrides add it. Alongsidedefault, they inspect these OpenCode auth candidates in stable path-index order:~/.local/share/opencode/auth.json(candidate 0)~/.config/opencode/auth.json(candidate 1)
A missing candidate file or provider key omits that account. An unreadable or malformed candidate produces an account-specific error. Only
defaulthas thehide-if-other-accountpolicy; OpenCode account errors remain visible. An OpenCode account uses only its selected source and never falls back to a different OpenCode file or native credentials.Future multi-account providers will add additional items with the same
providerIdand a distinct output accountid. The collection remains a flat array — no nesting or grouping changes. Provider filters (e.g.pluginIds=codex) remain provider-scoped; there is no account selector yet. -
GET /v1/usage/{provider}[?refresh=true]: usage snapshot for one provider.404with{"error":"provider_not_found"}for unknown provider.204 No Contentwhen provider exists but no cached snapshot is available.
Response shape is a single object matching the collection item shape above, including the
accountfield. The single-provider endpoint selects a snapshot in this order:- The snapshot with
account.id == "default"(when present). - Otherwise, the sole cached snapshot if exactly one exists.
- Otherwise,
204 No Content(multiple non-default accounts — account selection is deferred; a future account selector will resolve this).
-
GET /v1/config: return the daemon's active runtime configuration (host, port, refresh intervals, enabled plugins, etc.). -
POST /v1/probe: force refresh. Optional JSON body:{ "pluginIds": ["codex", "cursor"] }Returns the same flat account-bearing array of snapshots as
GET /v1/usage. -
query --type=usage(default mode): prints JSON to stdout. The response shape is the same flat array of account-bearing objects described above. When--with-stateis added, the output wraps in{"state":{...},"data":[...]}and every item indatacarries the sameaccountfield. -
POST /v1/shutdown: request graceful shutdown. -
POST /v1/restart: request daemon restart.
Control endpoint restrictions (/v1/shutdown, /v1/restart):
- Requests must come from loopback address (
127.0.0.1/::1). - If
Originheader is present, it must also be local (localhostor loopback IP). - Non-local remote/origin requests are rejected with
403.