The ah CLI
Everything in this dashboard, in your terminal — and in a shape agents read well: dense aligned tables, --json on every verb, and no clicking. Point Claude Code at it and ask questions about your traffic in plain English.
- 1
Install
Needs Node 20 or newer.
npm i -g @brightmotion/agenthog
- 2
Sign in
Opens your browser, mints a read token, and stores it in
~/.config/agenthog/config.json. One time, per machine. In several organizations? Tick the ones you want and every project shows up in one place.ah login ah digest
- 3
Let Claude Code use it
Once
ah loginis done, Claude Code can runahlike any other command — no plugin, no MCP server. The one thing worth doing is telling it the tool exists. Paste this into yourCLAUDE.md:## Analytics (AgentHog) Web analytics for this project live in AgentHog. The `ah` CLI is installed and authenticated — read from it directly instead of guessing. - `ah digest` — start here: one-call overview with deltas vs the previous window - `ah traffic --since 30d` — sessions per day by classification, plus top sources - `ah funnel signup` — or `ah funnel <step1> <step2> ...` for an ad-hoc funnel - `ah sessions list --converted` — then `ah sessions show <id>` for one timeline - `ah events top` / `ah clicks /pricing` — what people actually do - `ah revenue` — net/gross/refunds, ARPU, ARPPU, AOV (`ah revenue suggest` sets it up) - `ah ltv` — cumulative revenue per person by acquisition cohort - `ah economy` — in-game currency flows per currency (`ah economy suggest` sets it up) - `ah track <event> --prop k=v` — record something no SDK saw: a deploy, a migration, a correction (needs a write token; `--at` backdates, `--file` imports history) Every verb takes `--json`. Bots are excluded by default (`--all` includes them). Run `ah --help` for the full verb list.
Then just ask: “did last week’s pricing change move signups?” — it will reach for
ah digestandah funnelon its own. - 4
Unattended agents and CI
ah loginneeds a browser. For anything headless, mint a token on the Tokens page and pass it by env — it takes precedence over the stored login and never prompts.AH_TOKEN=ah_tok_… ah digest --json
Worth knowing
ah whoami— which account, token, and scope you are using, and where it came from.- Read scope is the default. Write tokens (for
goals set,changes add,track) need an org admin:ah login --write. - One project? No flags needed. Otherwise pass
--project <name>. ah logoutrevokes the token(s) server-side, not just locally.- Several organizations:
ah projectslists them all with anorgcolumn, and a project key picks the right one automatically. Account-wide verbs (usage,test-ips add,projects create) take--account <org>. Joined a new org?ah login --force. - Self-hosted?
ah login --host https://your-agenthog.example, or setAH_API_URLalongsideAH_TOKEN.
Common verbs
ah --help lists everything. The ones worth knowing on day one:
ah digest --since 30d # the daily standup view ah traffic --since 7d # sessions per day + top sources ah campaigns --since 30d # which ad is actually working ah paths --to "form_submit: waitlist" # discover the real funnel ah funnel signup --by device_type # then see where it breaks down ah sessions list --converted # who converted ah sessions show 831ac66d # one person's full visit ah clicks / # what people click, incl. dead clicks ah flags create cta --variants control:50,b:50 # feature flags (write scope) ah experiments start cta --metric signup # A/B test on the flag ah experiments results cta # per-variant conversion + chance-to-beat ah funnel signup --by flag:cta # any metric splits by variant
Writing events
Everything above reads. ah track is the one verb that writes an event — for anything that happened outside an SDK: a deploy, a migration, a billing correction, hours logged, or a one-shot import of history from another tool. It is a thin wrapper over the same token-authed server ingest a webhook relay uses, with the conventions applied for you: the event lands on a server session (out of ah traffic, visible in ah events, ah sql, funnels, revenue and economy), --person stitches it to the same person the app’s identify() reports, and bare numbers in --prop are sent as numbers. Needs a write token.
ah track deploy_completed --prop service=app --prop version=1.9.2
ah track time_logged --at 2026-08-25 --person eric --prop project=move --prop hours=5
ah track --file hours.csv --event time_logged --ts-col Date --person-col Person \
--prop-cols Project=project,Work=work,Hours=hours --dry-run
ah track --file hours.csv ... # the real run (--skip-bad drops the rows the dry run listed)
ah track --file export.ndjson # NDJSON rows carry their own fieldsTwo things the verb tells you every time because they are easy to get wrong. --at 2026-08-25 is UTC midnight, while --at "2026-08-25 21:30" (no offset) is your local wall clock — and reports bucket days in UTC, so a late evening in a negative-offset zone lands on the next day. The resolved instant is echoed on every write; pass an ISO time with an offset when the exact moment matters. And there is no idempotency key: running an import twice writes every row twice, so --dry-run first, and carry a stable id in props if you may need to dedupe later. Bad rows are reported by line number and the import refuses to run until they are fixed or --skip-bad is passed — an import is never silently partial.
Revenue
Revenue is config, not a second pipeline. You declare which events you are already sending carry money and where in their props the amount lives — and the mapping applies retroactively over all history, immediately. ah revenue suggest reads the props already in your data and prints the exact commands.
ah revenue suggest # propose mappings (--apply writes them) ah revenue map checkout_completed --amount cents --currency USD ah revenue config # what's mapped + a live sanity count per event ah revenue # net, gross, refunds, orders, payers, ARPU, ARPPU, AOV ah revenue --series week # revenue over time ah revenue --by utm_source # which channel earns ah revenue --by props.plan # which plan earns ah revenue --by flag:cta # revenue per A/B variant ah ltv --by utm_source # which channel buys the better customers
Two things worth knowing up front. Amounts are summed per currency and never across, so a multi-currency project gets a block each rather than one wrong total. And a purchase relayed by a webhook lands on a server session with no channel of its own, so --by attributes it to the channel that acquired the payer. Full walkthrough: Revenue & LTV.
Not integrated yet?
The CLI reads data the tracker sends. If this project has no data yet, start with how to integrate — or have your agent do it with the integration skill.