Agent-native CLI for Google Analytics 4
Project description
ga-cli
Agent-native command-line interface for Google Analytics 4. One binary (ga4),
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.
ga4 trend -r 7d # daily users/sessions/views
ga4 pages -f 'pagePath=^/blog' --limit 10 # top blog posts
ga4 report -m sessions -d sessionDefaultChannelGroup --compare prev
ga4 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 ga4 and the import package is ga_cli.
Verify with ga4 --version.
Auth setup
ga4 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:
ga4 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 |
|---|---|
ga4 report |
Core reporting primitive: metrics, dimensions, ranges, filters, ordering, paging |
ga4 realtime |
Last-30-minutes activity |
ga4 pages / ga4 sources / ga4 events / ga4 trend |
Presets for the most common questions |
ga4 accounts / ga4 properties [list|get] |
Account and property discovery |
ga4 streams / ga4 custom-dimensions / ga4 custom-metrics / ga4 key-events |
Property metadata |
ga4 meta |
Valid metric/dimension names (including custom fields); never guess names |
ga4 compat |
Check a metric/dimension combination before spending report tokens |
ga4 quota |
Remaining report token quota |
ga4 auth check [--ping] / ga4 auth guide |
Credential verification and setup steps |
ga4 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 ga4 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) | ga4 skill install (--project for a repo-local install) |
| Homebrew | brew install sajal2692/tap/ga-agent-cli then ga4 skill install |
| skills CLI | npx skills add sajal2692/ga-cli |
| Manual | cp -r skills/ga-cli ~/.claude/skills/ |
Reinstall after upgrading the CLI: ga4 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
ga4 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.1.tar.gz.
File metadata
- Download URL: ga_agent_cli-1.0.1.tar.gz
- Upload date:
- Size: 55.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
402e4a4acbc3d47a0f5b8f88402318068f99f5583eed38eba17665f487b1931c
|
|
| MD5 |
16f5f1a413e38e87508e6ff024e9341f
|
|
| BLAKE2b-256 |
a1dcd847ec6ad821054e19258c5f8fbf936b6bec66b01f4bedd62a1e5b646302
|
Provenance
The following attestation bundles were made for ga_agent_cli-1.0.1.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.1.tar.gz -
Subject digest:
402e4a4acbc3d47a0f5b8f88402318068f99f5583eed38eba17665f487b1931c - Sigstore transparency entry: 2124870290
- Sigstore integration time:
-
Permalink:
sajal2692/ga-cli@95b676ee686515bcc8931132955f253939bc0fbe -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/sajal2692
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@95b676ee686515bcc8931132955f253939bc0fbe -
Trigger Event:
push
-
Statement type:
File details
Details for the file ga_agent_cli-1.0.1-py3-none-any.whl.
File metadata
- Download URL: ga_agent_cli-1.0.1-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 |
b927b31b672b95fb6dac43ee4c7de05c5c4d0839289c7fb3c028b678afe0fd3a
|
|
| MD5 |
1e38e37c6972cf00db804333ebbcea72
|
|
| BLAKE2b-256 |
a124ea30d72b31318da7d94861d5cf5475b333f1d7cd4cc49868370ca268b631
|
Provenance
The following attestation bundles were made for ga_agent_cli-1.0.1-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.1-py3-none-any.whl -
Subject digest:
b927b31b672b95fb6dac43ee4c7de05c5c4d0839289c7fb3c028b678afe0fd3a - Sigstore transparency entry: 2124870313
- Sigstore integration time:
-
Permalink:
sajal2692/ga-cli@95b676ee686515bcc8931132955f253939bc0fbe -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/sajal2692
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@95b676ee686515bcc8931132955f253939bc0fbe -
Trigger Event:
push
-
Statement type: