Skill: nanaco666/ga4-connector (version 0.1.1)

--- name: ga4-connector description: Query Google Analytics 4 (GA4) data through the Google Analytics Data API for trends, countries, sources, pages, and conversion analysis. Use when a user wants GA/GA4 metrics inside OpenClaw, wants to set up GA4 access, or needs GA4 reports such as activeUsers, sessions, country/source breakdowns, page trends, or key event analysis. For enterprise/internal GA data, first verify which Google account actually has permission before proposing service accounts or extra cloud setup. --- # GA4 Data API Use this skill to set up and query GA4 through OAuth user authorization. ## Default approach Prefer **OAuth user authorization first**. Use `scripts/install_ga4_openclaw.sh` + `scripts/ga4_query.py` for the normal path. Only suggest **service accounts** later, when there is a clear need for headless/server-side access. ## Hard rules 1. **Check account + permission before setup** - Ask which Google account can actually log into GA and has property/account access. - Do not start with service-account setup by default for enterprise/internal data. 2. **Be honest about blockers** - If the user cannot add users, say it is a permission blocker. - If the OAuth app is still in Testing, say the account must be added to test users. - If the user can see the property but lacks admin controls, say it is an access-level issue instead of pretending the button is hidden. ## Setup flow ### Step 1 — Verify access model Ask/confirm: - Which Google account has GA access? - Can the user log into that account? - What is the GA4 Property ID? - Is Google Analytics Data API already enabled? - Is there already a Desktop OAuth client JSON? ### Step 2 — Install and wire credentials Run: ```bash bash ~/.openclaw/workspace/skills/ga4-data-api/scripts/install_ga4_openclaw.sh <GA4_PROPERTY_ID> <PATH_TO_CLIENT_SECRET_JSON> ``` This installs Python deps, copies the OAuth client JSON into `~/.config/openclaw/ga4-client.json`, and writes `GA4_PROPERTY_ID` into shell config. ### Step 3 — First authorization Run: ```bash python3 ~/.openclaw/workspace/skills/ga4-data-api/scripts/ga4_query.py \ --metrics activeUsers,sessions \ --dimensions date \ --start 7daysAgo \ --end today \ --pretty ``` The browser will open. The user must choose the **same Google account that actually has GA access**. Successful authorization creates: - `~/.config/openclaw/ga4-token.json` ### Step 4 — Query examples Recent trend: ```bash python3 ~/.openclaw/workspace/skills/ga4-data-api/scripts/ga4_query.py \ --metrics activeUsers,sessions \ --dimensions date \ --start 7daysAgo \ --end yesterday \ --pretty ``` Country split: ```bash python3 ~/.openclaw/workspace/skills/ga4-data-api/scripts/ga4_query.py \ --metrics activeUsers \ --dimensions country \ --start 7daysAgo \ --end yesterday \ --limit 50 \ --pretty ``` Source split: ```bash python3 ~/.openclaw/workspace/skills/ga4-data-api/scripts/ga4_query.py \ --metrics sessions \ --dimensions sessionSourceMedium \ --start 7daysAgo \ --end yesterday \ --limit 50 \ --pretty ``` ## Analysis guidance For trend questions: - Prefer full days (`end=yesterday`) unless the user explicitly wants intraday. - Call out whether a same-day drop is just partial-day data. For conversion questions: - Separate **traffic events** from **true conversion events**. - If key events look too broad, recommend narrowing the event set before drawing product conclusions. ## References - Read `references/setup.md` when the user needs the shareable setup process. - Use `scripts/ga4_query.py` instead of hand-writing fresh GA SDK samples each time.

path: scripts/ga4_query.py
content: #!/usr/bin/env python3
import argparse
import json
import os
import sys
from pathlib import Path

from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import DateRange, Dimension, Metric, RunReportRequest, OrderBy
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow

SCOPES = ["https://www.googleapis.com/auth/analytics.readonly"]
CONFIG_DIR = Path.home() / ".config" / "openclaw"
DEFAULT_CLIENT_SECRET = CONFIG_DIR / "ga4-client.json"
DEFAULT_TOKEN_FILE = CONFIG_DIR / "ga4-token.json"


def get_env(name: str, default: str | None = None) -> str | None:
    val = os.environ.get(name)
    return val if val else default


def parse_args():
    p = argparse.ArgumentParser(description="Query GA4 Data API for OpenClaw")
    p.add_argument("--property-id", default=get_env("GA4_PROPERTY_ID"), help="GA4 property ID or set GA4_PROPERTY_ID")
    p.add_argument("--client-secret", default=str(DEFAULT_CLIENT_SECRET), help="Path to Desktop OAuth client JSON")
    p.add_argument("--token-file", default=str(DEFAULT_TOKEN_FILE), help="Path to OAuth token cache")
    p.add_argument("--start", default="7daysAgo", help="Start date, e.g. 7daysAgo or 2026-03-01")
    p.add_argument("--end", default="today", help="End date, e.g. today or 2026-03-12")
    p.add_argument("--metrics", required=True, help="Comma-separated metrics, e.g. activeUsers,sessions")
    p.add_argument("--dimensions", default="", help="Comma-separated dimensions, e.g. date,country")
    p.add_argument("--limit", type=int, default=100)
    p.add_argument("--pretty", action="store_true", help="Pretty-print JSON")
    return p.parse_args()


def load_creds(client_secret: str, token_file: str):
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    creds = None
    token_path = Path(token_file)
    if token_path.exists():
        creds = Credentials.from_authorized_user_file(str(token_path), SCOPES)
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
    if not creds or not creds.valid:
        flow = InstalledAppFlow.from_client_secrets_file(client_secret, SCOPES)
        creds = flow.run_local_server(port=0)
        token_path.write_text(creds.to_json())
    return creds


def main():
    args = parse_args()
    if not args.property_id:
        print("Missing --property-id (or GA4_PROPERTY_ID)", file=sys.stderr)
        sys.exit(2)
    if not Path(args.client_secret).exists():
        print(f"Missing client secret JSON: {args.client_secret}", file=sys.stderr)
        sys.exit(2)

    creds = load_creds(args.client_secret, args.token_file)
    client = BetaAnalyticsDataClient(credentials=creds)

    metrics = [Metric(name=m.strip()) for m in args.metrics.split(",") if m.strip()]
    dimensions = [Dimension(name=d.strip()) for d in args.dimensions.split(",") if d.strip()]

    order_bys = []
    if dimensions:
        order_bys.append(OrderBy(dimension=OrderBy.DimensionOrderBy(dimension_name=dimensions[0].name)))

    request = RunReportRequest(
        property=f"properties/{args.property_id}",
        dimensions=dimensions,
        metrics=metrics,
        date_ranges=[DateRange(start_date=args.start, end_date=args.end)],
        order_bys=order_bys,
        limit=args.limit,
    )
    response = client.run_report(request)

    rows = []
    for row in response.rows:
        item = {}
        for i, d in enumerate(dimensions):
            item[d.name] = row.dimension_values[i].value
        for i, m in enumerate(metrics):
            item[m.name] = row.metric_values[i].value
        rows.append(item)

    out = {
        "property": args.property_id,
        "start": args.start,
        "end": args.end,
        "metrics": [m.name for m in metrics],
        "dimensions": [d.name for d in dimensions],
        "rows": rows,
    }
    if args.pretty:
        print(json.dumps(out, ensure_ascii=False, indent=2))
    else:
        print(json.dumps(out, ensure_ascii=False))


if __name__ == "__main__":
    main()


path: scripts/install_ga4_openclaw.sh
content: #!/usr/bin/env bash
set -euo pipefail

if [[ $# -lt 2 ]]; then
  echo "Usage: $0 <GA4_PROPERTY_ID> <PATH_TO_CLIENT_SECRET_JSON>"
  exit 1
fi

PROPERTY_ID="$1"
CLIENT_JSON="$2"
CONFIG_DIR="$HOME/.config/openclaw"
mkdir -p "$CONFIG_DIR"

if [[ ! -f "$CLIENT_JSON" ]]; then
  echo "Client secret JSON not found: $CLIENT_JSON"
  exit 1
fi

python3 -m pip install --user google-analytics-data google-auth-oauthlib google-auth-httplib2
cp "$CLIENT_JSON" "$CONFIG_DIR/ga4-client.json"

SHELL_RC="$HOME/.zshrc"
if [[ -n "${BASH_VERSION:-}" ]]; then
  SHELL_RC="$HOME/.bashrc"
fi

python3 - <<PY
from pathlib import Path
import re
rc = Path("$SHELL_RC")
text = rc.read_text() if rc.exists() else ""
text = re.sub(r'^export GA4_PROPERTY_ID=.*\\n?', '', text, flags=re.M)
if text and not text.endswith('\\n'):
    text += '\\n'
text += 'export GA4_PROPERTY_ID="$PROPERTY_ID"\\n'
rc.write_text(text)
print(f"Updated {rc}")
PY

echo
echo "Installed GA4 deps and config."
echo "Next step: authorize once via"
echo "  python3 ~/.openclaw/workspace/scripts/ga4_query.py --metrics activeUsers,sessions --dimensions date --start 7daysAgo --end today --pretty"
echo


path: references/setup.md
content: # GA4 Data API → OpenClaw setup

## Best default path

Use OAuth user authorization first. It is the fastest way to get GA4 data working inside OpenClaw when the user already has a Google account with GA access.

## Preconditions

- A Google account that can log into the target GA4 property
- GA4 Property ID
- Google Analytics Data API enabled in Google Cloud
- Desktop OAuth client JSON

## One-command install

```bash
bash ~/.openclaw/workspace/skills/ga4-data-api/scripts/install_ga4_openclaw.sh <GA4_PROPERTY_ID> <PATH_TO_CLIENT_SECRET_JSON>
```

## First auth

```bash
python3 ~/.openclaw/workspace/skills/ga4-data-api/scripts/ga4_query.py \
  --metrics activeUsers,sessions \
  --dimensions date \
  --start 7daysAgo \
  --end today \
  --pretty
```

## Common pitfalls

1. Wrong Google account in browser → OAuth succeeds, but not for the intended account.
2. Testing-mode OAuth app → add the Gmail account to OAuth test users.
3. Enterprise/internal GA → verify account/permission first; do not jump straight to service account.
4. No visible “add user” button in GA → that is a permission blocker, not a UI misunderstanding.
5. Broad key-event definitions → do not treat all key events as true conversions.


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)