Skip to content

Getting Started

This guide takes you from zero to seeing your AI coding agent telemetry in the dashboard. It covers installing the hook, wiring up any of the seven supported agents, importing existing session history, and verifying your data appears.

Prerequisite: an observability server must be running. If you are setting up the platform itself, see the root README for local development and deployment docs for production.

Supported agents

The aiot hook binary captures events from seven AI coding agents. You can install it for one or more — each agent is wired independently.

Agent --agent flag Config location Transcripts
Claude Code claude-code (default) ~/.claude/settings.json yes
OpenAI Codex CLI codex ~/.codex/hooks.json or ~/.codex/config.toml yes
Gemini CLI gemini-cli ~/.gemini/settings.json yes
GitHub Copilot CLI copilot ~/.copilot/hooks/aiot.json no
Pi pi ~/.pi/agent/extensions/telemetry.ts yes
omp (oh-my-pi) omp ~/.omp/agent/hooks/telemetry.ts yes
opencode opencode ~/.config/opencode/plugin/telemetry.ts yes

1. Install the hook binary

Terminal window
curl -fsSL https://raw.githubusercontent.com/yorch/ai-agents-observability/main/scripts/install.sh | bash

Use bash -s -- --version v1.2.0 to pin a release or bash -s -- --prefix "$HOME/.local/bin" to install without sudo:

Terminal window
curl -fsSL https://raw.githubusercontent.com/yorch/ai-agents-observability/main/scripts/install.sh | bash -s -- --prefix "$HOME/.local/bin"

Option B — Manual download

Download the binary for your platform from the GitHub releases page:

Platform Binary name
macOS (Apple Silicon) aiot-darwin-arm64
macOS (Intel) aiot-darwin-x64
Linux (ARM64) aiot-linux-arm64
Linux (x86-64) aiot-linux-x64

Then make it executable and move it to your PATH:

Terminal window
chmod +x aiot-<os>-<arch>
mkdir -p ~/.local/bin
mv aiot-<os>-<arch> ~/.local/bin/aiot

For checksum verification and air-gapped installation, see docs/deploy/hook-binary.md.

2. Configure the server endpoints

If the observability server is running on localhost (the default during local development), you can skip this step. For a remote deployment:

Terminal window
aiot config set web-url https://observability.example.com
aiot config set ingest-url https://ingest.example.com

Verify with:

Terminal window
aiot config show

3. Authenticate

Link the hook to your account so telemetry is routed to your dashboard:

Terminal window
aiot login

This prints a URL and a short device code. Open the URL in your browser, enter the code, and authorize. Your auth token is stored locally in ~/.aiot/identity.json.

4. Install hooks for your agent

The install command writes background service files (launchd on macOS, systemd on Linux) for the flusher and shipper daemons, and auto-detects and auto-wires any supported agent harnesses it finds on your machine. Snippets are printed only for agents it could not detect, so you can wire them by hand.

Terminal window
# Default: Claude Code
aiot install
# For any other agent
aiot install --agent codex
aiot install --agent gemini-cli
aiot install --agent copilot
aiot install --agent pi
aiot install --agent omp
aiot install --agent opencode

If install could not auto-wire an agent, copy the printed snippet into the file indicated by the output. The exact snippet for each agent is also documented below as a fallback reference.

The snippets below are generated by aiot install --agent <name>. If something does not match, re-run install — the binary is the source of truth and the snippets may have been updated since this doc was written.

Claude Code

Add to ~/.claude/settings.json (merge with any existing hooks object):

{
"hooks": {
"Notification": [
{ "hooks": [{ "args": ["hook", "notification"], "command": "~/.local/bin/aiot", "type": "command" }] }
],
"PostToolUse": [
{ "hooks": [{ "args": ["hook", "post-tool-use"], "command": "~/.local/bin/aiot", "type": "command" }] }
],
"PreCompact": [
{ "hooks": [{ "args": ["hook", "pre-compact"], "command": "~/.local/bin/aiot", "type": "command" }] }
],
"PreToolUse": [
{ "hooks": [{ "args": ["hook", "pre-tool-use"], "command": "~/.local/bin/aiot", "type": "command" }] }
],
"SessionStart": [
{ "hooks": [{ "args": ["hook", "session-start"], "command": "~/.local/bin/aiot", "type": "command" }] }
],
"Stop": [
{ "hooks": [{ "args": ["hook", "stop"], "command": "~/.local/bin/aiot", "type": "command" }] }
],
"SubagentStop": [
{ "hooks": [{ "args": ["hook", "subagent-stop"], "command": "~/.local/bin/aiot", "type": "command" }] }
],
"UserPromptSubmit": [
{ "hooks": [{ "args": ["hook", "user-prompt-submit"], "command": "~/.local/bin/aiot", "type": "command" }] }
]
}
}

Replace ~/.local/bin/aiot with the actual path to your binary if you installed it elsewhere.

Codex CLI

Codex has two wiring modes. The install command detects which one to use.

Default (notify wrapper): Save this as ~/.codex/aiot-notify.sh and chmod +x it:

#!/bin/sh
printf '%s' "$1" | ~/.local/bin/aiot hook turn-complete --agent codex

Then point Codex at the wrapper in ~/.codex/config.toml:

notify = ["/home/user/.codex/aiot-notify.sh"]

Richer capture (lifecycle hooks): If you enable experimental hooks in ~/.codex/config.toml:

[features]
hooks = true

then re-run aiot install --agent codex and write the output to ~/.codex/hooks.json. This captures per-tool events in addition to turn-level data. Not available on Windows.

Gemini CLI

Add to ~/.gemini/settings.json (or .gemini/settings.json in a project):

{
"hooks": {
"AfterAgent": [
{ "hooks": [{ "command": "\"~/.local/bin/aiot\" hook after-agent --agent gemini-cli", "name": "aiot-after-agent", "timeout": 5000, "type": "command" }] }
],
"AfterModel": [
{ "hooks": [{ "command": "\"~/.local/bin/aiot\" hook after-model --agent gemini-cli", "name": "aiot-after-model", "timeout": 5000, "type": "command" }] }
],
"AfterTool": [
{ "hooks": [{ "command": "\"~/.local/bin/aiot\" hook after-tool --agent gemini-cli", "name": "aiot-after-tool", "timeout": 5000, "type": "command" }] }
],
"BeforeAgent": [
{ "hooks": [{ "command": "\"~/.local/bin/aiot\" hook before-agent --agent gemini-cli", "name": "aiot-before-agent", "timeout": 5000, "type": "command" }] }
],
"BeforeTool": [
{ "hooks": [{ "command": "\"~/.local/bin/aiot\" hook before-tool --agent gemini-cli", "name": "aiot-before-tool", "timeout": 5000, "type": "command" }] }
],
"Notification": [
{ "hooks": [{ "command": "\"~/.local/bin/aiot\" hook notification --agent gemini-cli", "name": "aiot-notification", "timeout": 5000, "type": "command" }] }
],
"PreCompress": [
{ "hooks": [{ "command": "\"~/.local/bin/aiot\" hook pre-compress --agent gemini-cli", "name": "aiot-pre-compress", "timeout": 5000, "type": "command" }] }
],
"SessionEnd": [
{ "hooks": [{ "command": "\"~/.local/bin/aiot\" hook session-end --agent gemini-cli", "name": "aiot-session-end", "timeout": 5000, "type": "command" }] }
],
"SessionStart": [
{ "hooks": [{ "command": "\"~/.local/bin/aiot\" hook session-start --agent gemini-cli", "name": "aiot-session-start", "timeout": 5000, "type": "command" }] }
]
}
}

GitHub Copilot CLI

Write to ~/.copilot/hooks/aiot.json:

{
"disableAllHooks": false,
"hooks": {
"agentStop": [{ "command": ["~/.local/bin/aiot", "hook", "agent-stop", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }],
"notification": [{ "command": ["~/.local/bin/aiot", "hook", "notification", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }],
"postToolUse": [{ "command": ["~/.local/bin/aiot", "hook", "post-tool-use", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }],
"postToolUseFailure": [{ "command": ["~/.local/bin/aiot", "hook", "post-tool-use-failure", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }],
"preCompact": [{ "command": ["~/.local/bin/aiot", "hook", "pre-compact", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }],
"preToolUse": [{ "command": ["~/.local/bin/aiot", "hook", "pre-tool-use", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }],
"sessionEnd": [{ "command": ["~/.local/bin/aiot", "hook", "session-end", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }],
"sessionStart": [{ "command": ["~/.local/bin/aiot", "hook", "session-start", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }],
"subagentStop": [{ "command": ["~/.local/bin/aiot", "hook", "subagent-stop", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }],
"userPromptSubmitted": [{ "command": ["~/.local/bin/aiot", "hook", "user-prompt-submitted", "--agent", "copilot"], "timeoutSec": 5, "type": "command" }]
},
"version": 1
}

Copilot CLI does not expose transcript paths, so transcript archiving is not available for this agent.

Pi

Create ~/.pi/agent/extensions/telemetry.ts (or .pi/extensions/telemetry.ts in a project):

// ~/.pi/agent/extensions/telemetry.ts (or .pi/extensions/telemetry.ts)
import { spawn } from "node:child_process";
const KINDS: Record<string, string> = {
before_agent_start: 'user-prompt-submit',
session_before_compact: 'pre-compact',
session_shutdown: 'session-end',
session_start: 'session-start',
tool_call: 'pre-tool-use',
tool_result: 'post-tool-use',
turn_end: 'stop',
};
export default function (pi: any) {
for (const [native, kind] of Object.entries(KINDS)) {
pi.on(native, async (event: any, ctx: any) => {
try {
const payload = {
...event,
cwd: ctx?.cwd ?? process.cwd(),
sessionId: ctx?.sessionManager?.sessionId ?? event?.sessionId,
sessionFile: ctx?.sessionManager?.path ?? undefined,
};
const p = spawn("~/.local/bin/aiot", ['hook', kind, '--agent', 'pi'], {
stdio: ['pipe', 'ignore', 'ignore'],
detached: true,
});
p.stdin.end(JSON.stringify(payload));
p.unref();
} catch {
// Telemetry must never break the agent: swallow and continue.
}
// Observe only: this handler never blocks a tool call and never
// rewrites a result, even though the API allows both.
});
}
}

omp (oh-my-pi)

Create ~/.omp/agent/hooks/telemetry.ts (or ~/.oh-omp/agent/hooks/, or .omp/hooks/ in a project):

// ~/.omp/agent/hooks/telemetry.ts (or ~/.oh-omp/agent/hooks/, or .omp/hooks/)
import { spawn } from "node:child_process";
const KINDS: Record<string, string> = {
before_agent_start: 'user-prompt-submit',
session_before_compact: 'pre-compact',
session_shutdown: 'session-end',
session_start: 'session-start',
tool_call: 'pre-tool-use',
tool_result: 'post-tool-use',
turn_end: 'stop',
};
export default function (omp: any) {
for (const [native, kind] of Object.entries(KINDS)) {
omp.on(native, async (event: any, ctx: any) => {
try {
const payload = {
...event,
cwd: ctx?.cwd ?? process.cwd(),
sessionId: ctx?.session?.id ?? event?.sessionId,
sessionFile: ctx?.session?.path ?? undefined,
};
const p = spawn("~/.local/bin/aiot", ['hook', kind, '--agent', 'omp'], {
stdio: ['pipe', 'ignore', 'ignore'],
detached: true,
});
p.stdin.end(JSON.stringify(payload));
p.unref();
} catch {
// Telemetry must never break the agent: swallow and continue.
}
// Observe only: this handler never blocks a tool call and never
// rewrites a result, even though the API allows both.
});
}
}
// Alternative, if you already run the third-party `omp-hooks` plugin: it makes
// OMP execute settings.json command hooks, so you can wire
// "~/.local/bin/aiot hook <kind> --agent omp" there instead. We ship the native module
// because it needs no third-party package to keep working.

opencode

Create ~/.config/opencode/plugin/telemetry.ts:

~/.config/opencode/plugin/telemetry.ts
import type { Plugin } from '@opencode-ai/plugin';
export const telemetry: Plugin = async () => ({
event: async ({ event }) => {
const map = {
'session.created': 'session-start',
'tool.execute.before': 'pre-tool-use',
'tool.execute.after': 'post-tool-use',
'session.idle': 'session-idle',
};
const kind = map[event.type];
if (!kind) return;
const p = Bun.spawn(['~/.local/bin/aiot', 'hook', kind, '--agent', 'opencode'], { stdin: 'pipe' });
p.stdin.write(JSON.stringify(event.properties ?? {}));
await p.stdin.end();
},
});

5. Verify

Check that everything is healthy:

Terminal window
aiot status

This shows your login state, queue depth, and whether the flusher and shipper services are running.

Then start a session in your agent. After it ends, refresh your My Agents page — the session should appear within a few seconds.

6. Import existing session data

If you have historical sessions from before the hook was installed, you can backfill them. Imported events use deterministic IDs and the server deduplicates them, so imports are safe to re-run.

Supported agents for import

Agent --agent flag Historical source
Claude Code claude-code (default) ~/.claude/projects/**/*.jsonl
Codex CLI codex ~/.codex/sessions/**/rollout-*.jsonl
opencode opencode ~/.local/share/opencode/opencode.db
Pi pi ~/.pi/agent/sessions/**/*.jsonl
omp omp ~/.omp/agent/sessions/**/*.jsonl (also probes ~/.oh-omp)

Gemini CLI and Copilot CLI do not have import sources. Their session data is not stored in a scannable local format that the hook can read. Live capture works for both; only historical backfill is unavailable.

Import commands

Terminal window
# Preview what would be imported without sending anything
aiot import --dry-run
# Import all history from the default agent (Claude Code)
aiot import
# Import from a specific date
aiot import --since 2026-01-01
# Import from another agent
aiot import --agent codex --dry-run
aiot import --agent opencode --since 2026-01-01
aiot import --agent pi
aiot import --agent omp
# Import a single session (events only, no transcripts)
aiot import --agent codex --session <session-id> --no-transcripts

Import flags

Flag Description
--agent <name> Select claude-code, codex, opencode, pi, or omp
--since YYYY-MM-DD Skip events older than this date
--session <id> Import only one native or normalized session ID
--no-transcripts Skip transcript uploads
--dry-run Parse and count without posting anything
--quiet Suppress per-session progress output

Authentication (aiot login) is required unless --dry-run is passed. Transcripts pass through the same client-side redaction and compression as live uploads.

Managing telemetry

Command Description
aiot status Show auth status, queue depth, service state
aiot pause Temporarily stop sending telemetry
aiot resume Re-enable telemetry
aiot uninstall Remove hooks and background services
aiot purge-local Delete all local data (queue, logs, identity)

You can also manage privacy settings from the Privacy page in the dashboard.

What to look at next

Once data is flowing, explore the dashboard:

  • My Agents — your sessions, costs, and trends
  • Sessions — searchable session list with transcript viewer
  • Insights — effectiveness signals and friction patterns
  • Trends — your activity over time
  • PRs — sessions correlated to your GitHub pull requests

For the full CLI reference, see apps/hook/README.md. For deployment details, see docs/deploy/.