From zero to a live dashboard
Five moves: plant the skill, connect a source, rehydrate, ask in both altitudes, pin it live. Run them yourself, or just tell your agent what you want — once the skill is installed it knows the commands.
Plant the skill
Drops a skill into Claude Code (~/.claude/skills) or Codex (~/.agents/skills) so the agent knows TableBI exists and when to reach for it. Binary on PATH ≠ agent knows it.
# teach your agent the CLI exists + how to drive it
tablebi install
# optional: also register the MCP server (default off)
tablebi install --mcp
Connect a source
A CSV is one shot. Live sources connect once via the web OAuth flow, then sync pulls them into facts — 口径-normalized, raw kept, refreshed on a schedule.
# one-shot: a Meta export, definitions unified on ingest
tablebi connect csv --file meta-ads.csv --platform meta_ads -w acme
# live: list what you can connect, then sync it in
tablebi discover ga4 -w acme
tablebi sync --property-id 123456789 -w acme
Start every session here
context returns the 口径 definitions, connected sources, freshness, a data overview, and the command cheatsheet — in one call. status shows what needs you.
# the live manifest: 口径 + sources + freshness + overview
tablebi context -w acme --json
# what's pending: reports due, stale / unsynced sources
tablebi status --json
Both altitudes
metrics for the trusted, cross-channel 口径 view; query --platform for a single platform's native fields at full granularity. Same data underneath.
# 口径 — unified, cross-channel, trusted
tablebi metrics --metric roas --group-by channel \
--period last-week -w acme --json
# raw — native, single-platform, full granularity
tablebi query --platform tiktok_ads \
--select "Video views,Profile visits" \
--group-by "Ad name" -w acme --json
Publish it live
dashboard publish hands back a hosted read-only URL. It stores the query (the spec), not a snapshot — so it refreshes itself every time the data does.
# publish a hosted, self-refreshing dashboard URL
tablebi dashboard publish -w acme
→ https://console.tablebi.com/d/acme # read-only, live
One workspace = one client/brand (default default). Most commands take -w <ws>. Every read command accepts --json — so your agent parses, never guesses.
What TableBI does, and what your agent does
TableBI gives you trustworthy numbers — it does the data cleaning, the 口径 unification, and the freshness accounting — plus the lossless raw underneath. The reasoning runs on your own Claude Code. The line is clean, and it never blurs.
- — Deterministic numbers: cleaning, 口径 unification, WoW math.
- — Lossless raw: every native record kept, queryable.
- — Trust metadata: freshness + the 口径 lineage on every number.
- — Managed connectors, sync, and a live dashboard URL.
- — The analysis: why it changed, what to do next.
- — Reading the JSON and reasoning over it.
- — Picking the altitude per question (口径 vs raw).
- — The brain. $0 inference — your key, your agent.
Numbers come from the CLI, never from the model — so they're reproducible and traceable. Every --json payload carries a trust block: freshness per source, and the lineage (each metric's formula) to cite when you explain a result.
Connect once, kept synced
CSV is one-shot. Live connectors are authorized once via the web OAuth flow, then synced into facts on a schedule — incremental by default, --full to re-pull. Set the per-source target on the first sync; it persists after.
# list connectable accounts — provider =
# ga4 | meta | gsc | google_ads | google_sheets
tablebi discover google_ads -w acme
# set the target on the first sync (persisted afterward)
tablebi sync --customer-id 1234567890 -w acme # Google Ads
tablebi sync --site-url "sc-domain:acme.com" -w acme # Search Console
tablebi sync --spreadsheet-id <id> --range "Sheet1!A:Z" -w acme
# incremental by default; --full --since to backfill history
tablebi sync -w acme --full --since 2026-01-01
Data lags are real and TableBI accounts for them: Search Console finalizes ~2–3 days late; Meta re-attributes conversions over a trailing ~7-day window (sync re-pulls it automatically). Don't report "today/yesterday" as complete — check freshness first. Google Ads needs GOOGLE_ADS_DEVELOPER_TOKEN in the environment.
It interrogates the data before it queries
An agent only knows what's in its context — and capabilities and data detail live in the CLI, not in this page, so they never drift. The loop, cheapest first: rehydrate with context, then interrogate the warehouse with schema, values, sample. Output is always bounded — safe to read into context.
Rehydrate
口径, connected sources, freshness, data overview, command cheatsheet — one call.
See the fields
Dimensions + metrics (口径) with a present flag, plus nativeFields per platform. --stats adds counts.
See the values
The distinct values a dimension takes (bounded top-N by volume), so filters hit real data. --search to find one.
See the shape
A few real rows at full grain, so the agent reads the actual value shapes before it writes a query.
This is the discovery layer under Pipe → Ask → Pin. Because the CLI is its own source of truth — context, --help and schema are generated from the code and your live warehouse — the agent reads current capabilities, not a doc that rots.
Trusted numbers. Full granularity. Both.
Two altitudes over the same files. Your agent moves between them in one breath: 口径 for the cross-channel story, raw when it needs a platform's own metric. Nothing is thrown away at ingest — you never trade trust for power.
One set of numbers
you can trust.
One definition of cost, conversions, ROAS — reconciled across every platform. The number you quote the client.
The platform's own
fields, intact.
Every native column the platform reports. Full granularity — and not raw SQL: it's a structured, single-platform, 口径-safe surface.
| Altitude | Answers | Command |
|---|---|---|
| 口径 · unified | Overall? Blended ROAS? Cross-channel compare? | metrics · report · dashboard |
| raw · native | This platform's own metric, at full granularity? | query --platform --select |
Cross-platform blending always goes through metrics, never raw. Dimensions: date · platform · account · campaign · adGroup · ad · channel · country · query · page · device (query/page/device = Search Console / organic). Periods: auto · last-7-days · last-14-days · last-30-days · last-month · all · YYYY-MM-DD:YYYY-MM-DD.
Pin a dashboard that refreshes itself
A pinned dashboard is a spec — declarative widgets — not a snapshot. Publishing hands back a hosted, read-only URL; the engine re-runs the query every time the data moves, so the link is always live. You build and edit it from Claude Code by editing the spec.
# publish → a hosted, self-refreshing read-only URL
tablebi dashboard publish -w acme
# inspect the current dashboard payload
tablebi dashboard show --period auto -w acme --json
# build/edit: read the spec → edit JSON → save it back
tablebi dashboard spec --json > /tmp/spec.json
tablebi dashboard set-spec --file /tmp/spec.json --confirm -w acme
Widgets are { id, type: kpis|timeseries|breakdown, title, metrics?, dimension?, limit? }. Convenience commands: dashboard add-widget, remove-widget, reset --template ecommerce|saas|agency|seo. Changing the spec changes the 口径, so it's gated — without --confirm, set-spec only previews the change. The web UI renders whatever spec is saved; the entry point stays the agent, not a GUI.
Sources, kept connected
Connectors handle auth, incremental fetch and an encrypted credential vault, so each source stays live and refreshed — not imported once and forgotten. CSV is one-shot; the rest connect once via OAuth, then sync.
| Source | Provider | Connect via | First-sync target |
|---|---|---|---|
| CSV / export | — | File | --platform |
| Google Analytics 4 | ga4 | OAuth | --property-id |
| Google Search Console | gsc | OAuth | --site-url |
| Google Ads | google_ads | OAuth + dev token | --customer-id |
| Meta Ads | meta | OAuth | --ad-account-id |
| Google Sheets | google_sheets | OAuth | --spreadsheet-id |
| TikTok Ads | tiktok_ads | OAuth | incremental |
Sheets map row 1 as headers into the 口径 like a CSV (--range "Sheet1!A:Z", default = first tab). Google Ads MCC routing uses GOOGLE_ADS_LOGIN_CUSTOMER_ID. New sources implement one interface — auth plus incremental fetch — and inherit 口径 normalization for free.
Claude Code or Codex. Your brain, your key.
TableBI ships the data and the deterministic engine; the reasoning runs on your own agent. The CLI is the primary surface — CLI-native agents are great at running commands and need no setup. tablebi install plants the skill so the agent learns the commands, the 口径, and the workflows.
Claude Code
Skill lands in ~/.claude/skills. Your agent reads context first, then drives the CLI in plain English — no prompt-engineering.
Codex
Skill lands in ~/.agents/skills (with an AGENTS.md). Same CLI, same 口径, same --json contract.
We host no LLM. Inference is $0 and numbers are deterministic & traceable — they come from the CLI, not a model. MCP exists only as a fallback for the rare case the CLI can't express something; register it with tablebi install --mcp. Otherwise, the CLI is the way.
A deterministic engine. No LLM inside.
"What happened" is plain, explainable computation — connectors pull raw data, normalization unifies the definitions, metrics derive once, and the same numbers feed metrics, query, the dashboard spec and the report alike. That's why a number is the same no matter how you ask for it.
Facts
Per-tenant Parquet on object storage (Tigris/S3) + a DuckDB pod for the full history — both altitudes (口径 + raw) live here.
Metadata
Postgres holds meta + encrypted secrets and a hot recent window, so common reads stay fast.
Engine
One shared service layer derives every metric once — deterministic, traceable, $0 inference. The CLI and web are thin shells over it.
Give your Claude Code
a data backend.
Install the skill, connect your sources, ask in two altitudes, pin a live dashboard. Your key, your agent, your numbers.