# 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)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:300)--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", "displayName": "default" } }The
accountobject is present on every usage snapshot. Account IDs are provider-scoped — each provider defines its own account namespace. Current providers always return"id": "default".The following applies when the plugin does not export
discoverAccounts(legacy single-probe mode). For discovery mode, see Multi-account discovery.A plugin may return an
accountobject inside its probe result alongsideplanandlines:{ account: { id: "my-account", displayName: "My Account" }, plan: "Pro plan", lines: [/* ... */] }
- If
accountis absent,null, orundefined, the runtime assigns{ id: "default", displayName: "default" }. - If
accountis present, it must be an object with a non-empty stringidand a non-empty stringdisplayName. Any violation (non-object, missing field, empty string, or an accessor that throws) produces a provider-level error output with the default account. - Provider-level error snapshots (runtime failures, script errors, validation failures) always carry the default account.
- The returned
accountvalue is preserved unchanged through the runtime output, daemon cache, and serialized API responses.
In discovery mode, host identity is authoritative and account-probe errors retain the discovered account (see below).
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", displayName: "Work Account" }, { id: "personal", displayName: "Personal Account" } ]; } }
Capability detection:
discoverAccountsis detected only after the plugin script and any override script are evaluated. If absent,null, orundefined, the runtime uses legacy single-probe 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 legacy mode.Account context: Each discovered account receives a child context that inherits from the base
__openusage_ctx. Thectx.accountobject has immutableidanddisplayNamefields matching the discovered descriptor. In legacy mode, the probe receives a child context withctx.account = { id: "default", displayName: "default" }whose identity fields are similarly immutable.Validation: The returned array must contain 0–32 items. Each item must be an object with a non-empty string
id(unique within the array) and a non-empty stringdisplayName. Violations (non-array, missing or empty fields, duplicate ids, over 32 items, or an exception) produce a single provider-level error output with the default account.Empty list: A valid empty array produces zero outputs for that provider, clearing any previously cached snapshots.
Host-authoritative identity: In discovery mode, if the probe result omits
account, sets it tonull/undefined, or returns an explicit{id:"default",displayName:"default"}, the runtime assigns the discovered account. If the probe returns an explicitaccountthat differs in eitheridordisplayName, the runtime produces an account-specific error carrying the discovered identity.Failure isolation: A per-account probe failure produces an error snapshot carrying that account's identity. Other accounts are unaffected.
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.
No bundled plugin has adopted
discoverAccountsyet. This is a forward-looking capability for multi-account provider support.Future multi-account providers will add additional items with the same
providerIdand a distinct 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).
-
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.