Skip to main content

Tool integrations

Integrations install hook scripts and plugins that emit telemetry to the daemon as your AI tools run. With integrations active, every turn, message, and tool call is recorded the moment it happens — no polling lag, no gaps when the dashboard isn't open.

OpenUsage ships three official integrations.

IDToolHook artifactTool configFormat
claude_codeClaude Code~/.config/openusage/hooks/claude-hook.sh~/.claude/settings.jsonJSON
codexCodex~/.config/openusage/hooks/codex-notify.sh~/.codex/config.tomlTOML
opencodeOpenCode~/.config/opencode/plugins/openusage-telemetry.ts~/.config/opencode/opencode.jsonJSON

Listing integrations

openusage integrations list
openusage integrations list --all # include not-installed

Each row shows: ID, tool name, install state, version, and any pending upgrade.

Install

openusage integrations install <id>

The installer is symmetric and idempotent. On install it:

  1. Reads any existing template to detect a prior version.
  2. Creates parent directories.
  3. Renders the embedded template, expanding __OPENUSAGE_INTEGRATION_VERSION__ and __OPENUSAGE_BIN_DEFAULT__.
  4. Backs up any existing file to <file>.bak.
  5. Writes the rendered hook script (mode 0755) or plugin (mode 0644).
  6. Patches the tool's config file to register the hook entry.
  7. Writes the patched config (mode 0600) — preserving existing keys.
  8. Saves install state (version, timestamp) into ~/.config/openusage/settings.json under integrations.<id>.

Hook scripts are tiny shell or TS wrappers that pipe the tool's event payload into:

openusage telemetry hook <source>

…which forwards over the Unix socket (or to the spool, if the daemon is offline).

Uninstall

openusage integrations uninstall <id>

Uninstall is the inverse of install:

  1. Removes the hook script or plugin file.
  2. De-registers the entry from the tool's config (preserves siblings).
  3. Restores the most recent .bak if present and the config would otherwise be empty.
  4. Marks integrations.<id>.installed = false in settings.

No telemetry data is touched. Old events stay in telemetry.db until retention prunes them.

Upgrade

openusage integrations upgrade <id>
openusage integrations upgrade --all

Reinstalls only when the embedded template version is newer than the installed version. Existing config entries are preserved; only the script body and version stamp change.


claude_code

What it adds. A Hook entry in ~/.claude/settings.json that runs on every Claude Code turn. The hook delivers a JSON event with token counts, model id, message ids, and tool calls. Telemetry source string: anthropic (mapped to display provider claude_code by provider links).

Files written.

~/.config/openusage/hooks/claude-hook.sh (mode 0755)
~/.claude/settings.json (patched, mode 0600)

Install.

openusage integrations install claude_code

Uninstall.

openusage integrations uninstall claude_code

Override the Claude config path with CLAUDE_SETTINGS_FILE when needed.


codex

What it adds. A notify entry in ~/.codex/config.toml pointing at a shell wrapper. Codex invokes the script after each turn with a JSON payload on stdin. Telemetry source: codex.

Files written.

~/.config/openusage/hooks/codex-notify.sh (mode 0755)
~/.codex/config.toml (patched, mode 0600)

Install.

openusage integrations install codex

Example patched TOML.

[notify]
command = ["/Users/me/.config/openusage/hooks/codex-notify.sh"]

Override the Codex config directory with CODEX_CONFIG_DIR.


opencode

What it adds. A TypeScript plugin loaded by OpenCode at startup. The plugin subscribes to OpenCode's session events and POSTs them to the daemon's /v1/hook/opencode endpoint. Telemetry source: opencode.

Files written.

~/.config/opencode/plugins/openusage-telemetry.ts (mode 0644)
~/.config/opencode/opencode.json (patched, mode 0600)

Install.

openusage integrations install opencode

Example patched config.

{
"$schema": "https://opencode.ai/config.json",
"plugin": ["file:///Users/me/.config/opencode/plugins/openusage-telemetry.ts"]
}

The patcher writes the singular plugin key as a flat array of file:// URLs; existing entries are preserved.

The plugin uses OPENUSAGE_BIN and OPENUSAGE_TELEMETRY_SOCKET if set; otherwise it falls back to the embedded defaults captured at install time.


How hook events become snapshots

  1. Tool fires hook → wrapper script runs → openusage telemetry hook <source> reads stdin.
  2. Hook command opens the Unix socket and POSTs to /v1/hook/{source}. If the dial fails (socket missing, daemon down), the event is appended to the on-disk spool.
  3. Daemon pipeline ingests the event, dedups by tool_call_idmessage_idturn_id → fingerprint hash, and stores it in usage_events.
  4. Provider links map source → display provider id. Defaults: anthropic → claude_code, google → gemini_api, github-copilot → copilot. Override under telemetry.provider_links in settings.json.
  5. The TUI requests /v1/read-model on each refresh; the daemon hydrates a UsageSnapshot per provider for the current time window.

:::tip Verifying a hook Trigger one turn in your tool, then watch ~/.local/state/openusage/daemon.stderr.log (with OPENUSAGE_DEBUG=1). You should see one POST /v1/hook/<source> per turn. If you instead see entries written to telemetry-spool/, the daemon is not running. :::

FAQ

Why does the API key still have to be set when the OpenCode plugin is doing the work?

A dashboard tile is owned by a configured account, not by an integration. An account exists when the provider is auto-detected (env var present) or manually defined under accounts in settings.json. Telemetry events from integrations are tagged with a provider ID and routed to the matching tile — but if no tile owns that ID, the events stay in storage unused.

This matters because the OpenCode plugin (and the Claude Code hook, and the Codex notify hook) can tag events with the upstream provider that served the turn (anthropic, openai, google, github-copilot, …). For those events to surface, the upstream provider needs an account too.

A second reason to set the env var: the polled provider unlocks data the integration cannot provide — rate-limit headers, balance endpoints, model catalog, plan info. Spend from the plugin merges with those native fields on the same tile, giving you one row per upstream provider with both real-time spend and account context.

Practical setup for someone whose AI is routed entirely through OpenCode:

  • Set OPENCODE_API_KEY (or ZEN_API_KEY) — gives you the OpenCode tile with auth status and Zen models.
  • Set ANTHROPIC_API_KEY if turns route to Claude — that tile then absorbs the plugin's anthropic-tagged spend.
  • Set OPENAI_API_KEY if turns route to GPT — same logic for the OpenAI tile.
  • Set GEMINI_API_KEY if turns route to Gemini — same logic for the Gemini API tile.
  • Optionally remap with telemetry.provider_links if the default (google → gemini_api, github-copilot → copilot) does not match your account layout.

If you skip the env vars, the events still land in the SQLite store (and show up under telemetry_unmapped_providers diagnostics) but no tile renders them.