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
# also plant it for Codex
tablebi install --codex
Connect a source
connect opens the platform's OAuth screen, lists what you can pick, then runs the first sync and builds an overview dashboard. A CSV is one upload with a label you choose.
# live: authorize in the browser → pick the property → first sync
tablebi connect ga4 -w acme
# several properties? it lists them; rerun with the one you want
tablebi connect ga4 --account 123456789 -w acme
# a channel with no live connector: upload its export under a label
tablebi connect csv --file tiktok-ads.csv --platform tiktok_ads -w acme
Start every session here
context returns the metric definitions, connected sources, freshness, a data overview, and the command cheatsheet — in one call. pending lists sources that are stale or not connected yet.
# the live manifest: definitions + sources + freshness + dashboards
tablebi context -w acme --json
# what needs attention: stale or unconnected sources
tablebi pending -w acme --json
Both altitudes
query --view metrics for the cross-channel unified view; query --view a platform's own pack, or ask with SQL over its raw table, for its native fields. Same data underneath.
# unified — cross-channel, last 28 days vs the 28 before
tablebi query --kind compare --view metrics --metrics cost,roas \
--by platform -w acme --json
# raw — native, single-platform, full granularity
tablebi ask -w acme --json \
"SELECT campaign, SUM(clicks) AS clicks, SUM(impressions) AS impressions
FROM meta_ads_raw GROUP BY campaign ORDER BY clicks DESC LIMIT 10"
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.
# start from a template for the sources you connected, edit, then publish
tablebi dashboard template seo-overview -w acme > board.json
tablebi dashboard publish --file board.json --dry-run -w acme
tablebi dashboard publish --file board.json -w acme
→ https://acme.tablebi.com/d/dsh_7f2a… # 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 unified metric definitions, 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, unified definitions, WoW math.
- — Lossless raw: every native record kept, queryable.
- — Trust metadata: freshness + the definition 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 (unified 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
Live connectors are authorized once through the platform's own OAuth screen. After that, a source re-syncs when someone opens one of its dashboards and it has passed its cooldown (Google Ads 3 hours, Meta 6, GA4 12, Search Console 24), and a daily pass keeps the sources behind published dashboards fresh even when nobody opens them. Each refresh re-reads a trailing window, because every platform revises its recent days. The target you pick on the first connect persists. A CSV is one upload; upload again to add rows.
# provider = gsc | ga4 | google_ads | meta_ads; several targets → it lists them
tablebi connect google_ads -w acme
tablebi connect google_ads --account 1234567890 -w acme # Google Ads
tablebi connect gsc --site "sc-domain:acme.com" -w acme # Search Console
# pull again now; --days widens the window (pages and queries default to 90 days)
tablebi sync gsc --site "sc-domain:acme.com" --days 180 -w acme
# CSV: the label is yours — never the name of a platform that already syncs live
tablebi connect csv --file spend.csv --platform tiktok_ads -w acme
Data lags are real and TableBI accounts for them. Search Console is pulled as Google's fresh data, a few hours behind; the last two days are preliminary and Google revises them upward, so every sync re-pulls that range. GA4 keeps processing the most recent 24–48 hours. Meta keeps revising conversions after the click. Don't report "today" or "yesterday" as final — check freshness in context first.
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, look at real rows with context --sample, then ask with query and fall back to ask. Output is always bounded — safe to read into context.
Rehydrate
Metric definitions, connected sources, freshness, data overview, command cheatsheet — one call.
See the fields
For each connected metric pack: its dimensions, metrics and the tables behind it, plus the SQL views you can query directly.
See the values
The top values a dimension takes (--dimension campaign), so filters hit real data.
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 and --help 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: unified metrics for the cross-channel story, raw when it needs a platform's own metric. The rows each connector pulls are kept as the platform reported them — 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.
The native columns each connector pulls — Meta's reach and frequency, GA4's channels and AI-assistant sources, Search Console's queries and pages. Read-only SQL, one platform at a time.
| Altitude | Answers | Command |
|---|---|---|
| Unified | Overall? Blended ROAS? Cross-channel compare? | query --view metrics · dashboard |
| raw · native | This platform's own metric, at full granularity? | query --view <pack> · ask "<SQL>" |
Cross-platform blending always goes through the unified metrics view, never raw. Dimensions: date · platform · account · campaign (the live connectors sync at campaign level; Search Console's query / page / country / device stay in its own tables). Windows: last-7d · last-28d (default) · last-90d · last-<N>d · this-month · last-month · all · YYYY-MM-DD:YYYY-MM-DD, anchored on the last day that has data.
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.
# create or update from a spec file, after a dry run that compiles and runs every widget
tablebi dashboard publish --file board.json --dry-run -w acme
tablebi dashboard publish dsh_7f2a… --file board.json -w acme
# inspect the current dashboard payload
tablebi dashboard show dsh_7f2a… -w acme --json
# build/edit: read the spec → edit JSON → save it back
tablebi dashboard spec dsh_7f2a… -w acme --json > /tmp/spec.json
tablebi dashboard set-spec dsh_7f2a… --file /tmp/spec.json --confirm -w acme
A spec is { version: 2, title, defaults: { view, window, filter, lang }, widgets: [{ kind, view, metrics, by, window, compare, filter, limit }] }, where kind is kpi · trend · breakdown · table · compare, plus sql as a marked escape hatch. Up to 12 widgets per dashboard. dashboard template starts you from the sources you connected; unpublish takes a link down. 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 |
|---|---|---|---|
| Google Search Console | gsc | OAuth | --site |
| Google Analytics 4 | ga4 | OAuth | --account |
| Google Ads | google_ads | OAuth | --account |
| Meta Ads beta | meta_ads | OAuth | --account |
| CSV upload | csv | File upload | --file --platform |
| TikTok Ads · Google Sheets | — | on the roadmap | not shipped |
A CSV upload is tagged with a free-text --platform label and normalized into the same definitions — that is how channels without a live connector get in today. New sources implement one interface — auth plus incremental fetch — and inherit the unified 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 metric definitions, 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 (tablebi install --codex). Same CLI, same definitions, same --json contract.
We host no LLM. Inference is $0 and numbers are deterministic & traceable — they come from the CLI, not a model. Hosts that prefer MCP get the same CLI as a local stdio server: tablebi install --mcp registers tablebi mcp (context, query, sql, define, dashboards) — same endpoints, same login. There is no hosted MCP endpoint.
-w <workspace> and --json| tablebi login | browser (device-code) sign-in |
| tablebi logout | sign out |
| tablebi context [--full] | one-shot rehydrate: definitions, sources, freshness, dashboards, command signatures |
| tablebi query --kind <kpi|trend|breakdown|table|compare> --view <pack> --metrics <m,…> | ask for a metric by name; the engine picks the table, window and joins |
| tablebi define [<key> <value>] | workspace definitions: brand terms, site groups, conversion events, opportunity thresholds |
| tablebi mcp | built-in stdio MCP server (context, query, sql, define, dashboards); install --mcp registers it |
| tablebi disconnect <provider> --site|--account|--platform <target> --yes | stop syncing a source and delete its data (asks for confirmation without --yes) |
| tablebi workspace delete <name> --confirm <name> | delete a workspace with all its data, dashboards and authorizations |
| tablebi ask "<SQL>" | read-only SQL over metrics, facts and the per-platform raw tables (alias: sql) |
| tablebi scoreboard [--window <days>] | which Search Console sites are growing or slipping |
| tablebi gsc query | inspect | sitemaps --site <site> | live Search Console calls for what the synced tables cannot answer |
| tablebi connect <gsc|ga4|google_ads|meta_ads> · connect csv --file <f> --platform <label> | authorize and sync a source |
| tablebi sync <provider> --site <url> | --account <id> [--days <n>] | pull a connected source again |
| tablebi pin --file <spec.json> | save, validate and publish a live dashboard in one step |
| tablebi dashboard <list|show|spec|set-spec|publish|unpublish|annotate|template> | manage dashboards; publish --file <board.json> [--dry-run] ships a grammar dashboard |
| tablebi install [--codex] [--mcp] | plant the skill for Claude Code / Codex; --mcp also registers the MCP server |
| tablebi update | upgrade the CLI in place (volta or npm) |
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 compiler serves query, ask and every dashboard. 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 (unified + raw) live here.
Metadata
Postgres holds workspaces, sources, dashboard specs and encrypted credentials — small, transactional, nothing heavy.
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.