Agent-native CLI for Google Analytics 4
Project description
ga-cli
Agent-native command-line interface for Google Analytics 4. One binary (ga),
task-first commands, flattened JSON that agents and scripts can consume directly,
stable exit codes for branching, and zero interactive prompts. Read-only by
construction: only read methods of the GA4 Data and Admin APIs are ever bound.
ga trend -r 7d # daily users/sessions/views
ga pages -f 'pagePath=^/blog' --limit 10 # top blog posts
ga report -m sessions -d sessionDefaultChannelGroup --compare prev
ga realtime -d unifiedScreenName # who is on the site now
Install
| Channel | Command |
|---|---|
| PyPI (primary) | uv tool install ga-agent-cli (or pipx install ga-agent-cli, pip install ga-agent-cli) |
| Homebrew | brew install sajal2692/tap/ga-agent-cli |
| From source | uv tool install git+https://github.com/sajal2692/ga-cli |
| Local dev | uv tool install --editable ~/code/active/tools/ga-cli |
The PyPI distribution is named ga-agent-cli (the name ga-cli was unavailable), but the
installed command is ga and the import package is ga_cli.
Verify with ga --version.
Auth setup
ga auth guide prints the full copy-pasteable steps. Summary:
- Create a Google Cloud project and enable
analyticsdata.googleapis.comandanalyticsadmin.googleapis.com. - Create a service account and download a JSON key.
- In GA4 Admin > Property access management, add the service account email as Viewer.
- Save the key and point ga-cli at it in
~/.config/ga-cli/config.toml:
default_property = "properties/123456789"
credentials = "~/.config/ga-cli/sa.json"
[properties] # optional aliases, usable anywhere a property is accepted
blog = "properties/123456789"
site = "properties/987654321"
- Verify end to end:
ga auth check --ping.
Credential resolution order: --credentials > GA_CREDENTIALS > config credentials >
GOOGLE_APPLICATION_CREDENTIALS > application default credentials. The only scope ever
requested is analytics.readonly.
Property resolution order: -P/--property > GA_PROPERTY > config default_property >
the sole accessible property (when the credentials can see exactly one).
Commands
| Command | Purpose |
|---|---|
ga report |
Core reporting primitive: metrics, dimensions, ranges, filters, ordering, paging |
ga realtime |
Last-30-minutes activity |
ga pages / ga sources / ga events / ga trend |
Presets for the most common questions |
ga accounts / ga properties [list|get] |
Account and property discovery |
ga streams / ga custom-dimensions / ga custom-metrics / ga key-events |
Property metadata |
ga meta |
Valid metric/dimension names (including custom fields); never guess names |
ga compat |
Check a metric/dimension combination before spending report tokens |
ga quota |
Remaining report token quota |
ga auth check [--ping] / ga auth guide |
Credential verification and setup steps |
ga skill install [--project] [--force] / show / path |
Claude Code skill management |
Full flag reference: skills/ga-cli/references/commands.md.
Output contract
- JSON on stdout is the default. Data goes to stdout; everything else (progress, hints, warnings, errors) goes to stderr. stdout carries exactly one JSON document (or one table) per invocation, or nothing on error.
--compactfor single-line JSON,--tablefor a human table (no compatibility guarantee),GA_AUTO_TABLE=1renders tables only when stdout is a TTY.- Report rows are flattened objects: dimension values as strings (date-like dimensions normalized to ISO form), metric values coerced to int/float per the declared type.
- Reports return an envelope (
property,date_ranges,rows,row_count,returned,has_more);--results-onlyemits just the rows array. Complete result sets (accounts, properties, streams, ...) are bare arrays; single entities are bare objects. - Command names, flags, env vars, exit codes, and JSON field names are compatibility surfaces, frozen at v1.0.0. Breaking any of them is a major-version event.
Exit codes
| Code | Name | Meaning |
|---|---|---|
| 0 | ok | success (including empty results without --fail-empty) |
| 1 | error | unclassified runtime failure |
| 2 | usage | bad flags/args, DSL parse errors, invalid combinations |
| 3 | empty | zero rows and --fail-empty was set |
| 4 | auth | no credentials found, or credentials rejected |
| 5 | not_found | property or resource does not exist |
| 6 | permission | credentials valid but lack access |
| 7 | quota | rate limited or property token quota exhausted |
| 8 | retryable | 5xx, network timeout, connection failure |
Agents should branch on codes, not stderr text.
Filters, ranges, ordering
-f 'pagePath=^/blog' # begins with
-f 'country==United States|Canada' # in-list (escape a literal pipe as \|)
-f 'sessions>100' # metric filter (field is in -m)
--filter-any 'source=@github' --filter-any 'source=@linkedin' # OR group
-r 7d -r mtd -r 2026-06-01:2026-06-15 -r 28daysAgo:yesterday # ranges
--compare prev # same-length preceding period
-o -sessions -o pagePath # order: -field desc, field asc
Operators: == != =@ (contains) !@ =^ (begins) =$ (ends) =~ (regex) !~
> >= < <=. String matching is case-insensitive unless --case-sensitive.
Anything the DSL cannot express: --filter-json '<FilterExpression JSON>' or @file.
GA4 standard properties allow roughly 200k core tokens/day, 40k/hour, and 10 concurrent
requests; check live state with ga quota. Token cost scales with rows, date range,
and cardinality.
Claude Code skill
The wheel bundles a Claude Code skill so agents know how to drive the CLI:
| Method | Command |
|---|---|
| Bundled installer (recommended) | ga skill install (--project for a repo-local install) |
| Homebrew | brew install sajal2692/tap/ga-agent-cli then ga skill install |
| skills CLI | npx skills add sajal2692/ga-cli |
| Manual | cp -r skills/ga-cli ~/.claude/skills/ |
Reinstall after upgrading the CLI: ga skill install --force.
Development
uv sync # install deps + dev tools
uv run pytest # unit, golden, and contract tests
uv run pytest -m e2e # opt-in live tests (needs GA_E2E=1 + credentials)
uv run ruff check . && uv run mypy
Tests never touch the network: the two client factories in clients.py are
monkeypatched with fakes returning real proto messages.
Releasing
- Update
CHANGELOG.mdand bumpversioninpyproject.toml. - Tag
vX.Y.Zand push;release.ymlruns checks, builds, publishes to PyPI via trusted publishing, and creates the GitHub release. - Verify
uv tool install ga-agent-cli==X.Y.Z. - Update the Homebrew formula in
sajal2692/homebrew-tap(bumpurl/sha256, rerunbrew update-python-resources ga-agent-cli), thenbrew installsmoke test. - Verify
ga skill installfrom the brew build.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ga_agent_cli-1.0.0.tar.gz.
File metadata
- Download URL: ga_agent_cli-1.0.0.tar.gz
- Upload date:
- Size: 55.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5459902b92eaee1b4fd7fdef729547569c913ad108bbeaa267fe4a7d22254c89
|
|
| MD5 |
0fb5ab8e0405172653aa5badb2719450
|
|
| BLAKE2b-256 |
ca338903c976d8a01775787b5e28e4fd318bbe06148df49fba3b71defcc07c40
|
Provenance
The following attestation bundles were made for ga_agent_cli-1.0.0.tar.gz:
Publisher:
release.yml on sajal2692/ga-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ga_agent_cli-1.0.0.tar.gz -
Subject digest:
5459902b92eaee1b4fd7fdef729547569c913ad108bbeaa267fe4a7d22254c89 - Sigstore transparency entry: 2124732084
- Sigstore integration time:
-
Permalink:
sajal2692/ga-cli@5c0dfadc5b82e0489d2c8a52199023a278af7b73 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/sajal2692
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5c0dfadc5b82e0489d2c8a52199023a278af7b73 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ga_agent_cli-1.0.0-py3-none-any.whl.
File metadata
- Download URL: ga_agent_cli-1.0.0-py3-none-any.whl
- Upload date:
- Size: 38.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
25371e547926feba1aa97606582c49b0e2fbeb6fb480a58e0522b8e18fc517c7
|
|
| MD5 |
7140956c9e56296d7e5abeb9d2a376e1
|
|
| BLAKE2b-256 |
58bed3397c37104307f876eb3c683dc1e57c8c487b7d4f003cceee64b7b877c0
|
Provenance
The following attestation bundles were made for ga_agent_cli-1.0.0-py3-none-any.whl:
Publisher:
release.yml on sajal2692/ga-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ga_agent_cli-1.0.0-py3-none-any.whl -
Subject digest:
25371e547926feba1aa97606582c49b0e2fbeb6fb480a58e0522b8e18fc517c7 - Sigstore transparency entry: 2124732139
- Sigstore integration time:
-
Permalink:
sajal2692/ga-cli@5c0dfadc5b82e0489d2c8a52199023a278af7b73 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/sajal2692
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5c0dfadc5b82e0489d2c8a52199023a278af7b73 -
Trigger Event:
push
-
Statement type: