OpenBoardCLI reads the receipts in your Gmail and turns them into
per-biller spending dashboards — on a schedule, as tabs in one
authenticated React app, under a master Overview tab that
tracks spend across all of them. Seven fetchers ship ready;
Biller Studio writes one for any biller you name, showing
you the exact email before your model ever sees it. Privacy-first:
pick Local only (Ollama / LM Studio, nothing leaves your
machine), Hybrid, or All remote before anything
else. Any CSV, Excel, or JSON file joins the same app the same way.
A CSV, Excel, or JSON file you already have skips to step 03.
01connect gmail
Pick your privacy mode first — Local only, Hybrid, or All remote — then an LLM provider. Add your inbox under Integrations → Gmail: the App Password is encrypted in ~/.openboard and reaches each fetcher through OPENBOARD_GMAIL_* in its environment at run time, never on disk.
02pick your billers
Seven fetchers ship ready — Amazon, Amazon Pay, Rapido, Swiggy Food & Instamart, Uber, Urban Company, Zomato. For anything else, Biller Studio takes a sender address and a subject fragment and writes the fetcher for you.
03let it fetch
Runs happen on a schedule or on demand, each biller's receipts landing in its own CSV. A CSV that gained rows refreshes that biller's dashboard; a biller with data but no dashboard gets one built. Watch the fetch log, or hit Stop fetch mid-run.
04the overview tab
A master tab is generated across all your dashboards — total spend, past 7 days, cross-app trend, per-app breakdown. In All remote mode the app builds, pushes to GitHub, and deploys to Vercel, health-checked and tagged for rollback.
Run it once by hand — or host it and let the loop close by
itself. In All remote mode, with agent billers sync
on a scheduler (GitHub Actions, a VPS cron, any runner), steps 03
and 04 repeat unattended: receipts arrive, the dashboards that
changed regenerate, and the live app redeploys and health-checks
itself. You only ever open the Overview tab.
# biller studio
No shipped list covers your billers — so it writes the fetcher.
integrations → gmail → biller studio
› sender billing@yourgym.example› subject contains "Membership receipt"sampling one matching email…┌ this is the exact text your model will see ────────────│ Membership receipt · 12 Jun 2026│ Plan: Annual Amount: ₹4,200 Ref: GYM-88213└────────────────────────────────────────────────────────proposed fields date · amount · plan · referencewriting fetch_yourgym.py · compiling · dry-run…✓ scanned — reads mail, writes csv, nothing else✓ 14 receipts parsed · saved›▌
Forwarded receipts and PDF attachments count too. The model writes
only the biller-specific regions and OpenBoardCLI splices them into
its own skeleton — about 5× less output, so even a slow local
model finishes without truncating. Ready-made fetchers ship for
Amazon, Amazon Pay, Rapido,
Swiggy Food & Instamart, Uber,
Urban Company and Zomato; Biller Studio covers
everything after that.
# what's in the box
Gmail in, dashboards out
Gmail lives under the Integrations menu and turns receipts into per-biller spending dashboards on a schedule. Seven fetchers ship ready. Your App Password is encrypted and reaches each one through its environment at run time — never written to disk.
Biller Studio — it writes the fetcher
Give it a sender address and a subject fragment. It samples one real email, shows you the exact text before anything reaches your model, then writes, compiles and dry-runs the fetcher. PDF and forwarded receipts included. Generated code is scanned — it reads mail and writes a CSV, nothing else.
Runs itself in the cloud
Put agent billers sync on any scheduler — GitHub Actions, a VPS cron, your own runner — and the loop closes without you. New receipts land, changed dashboards regenerate, and the live app redeploys itself. Runs are lock-serialised, so a cron tick overlapping an open TUI is safe.
Master Overview tab
A landing tab across all your dashboards: total spend, past 7 days, cross-app trend, per-app breakdown, and top spending + saving insights. One place to see what every biller cost you.
Three privacy modes
Your receipts are your bank statement, so the mode comes first. Local only — local LLM and local preview, nothing leaves your machine. Hybrid — cloud LLM, local preview. All remote — cloud LLM, GitHub, live Vercel app.
Bring your own model
Ollama or LM Studio for fully local generation — or OpenAI, Anthropic, Codex, Gemini, Moonshot, xAI, Mistral, OpenRouter. Pick a model, set the effort dial from low to max, switch any time with /model. Keys are encrypted with AES-256-GCM.
Fetches you can watch — and stop
Scheduled runs write to a persistent fetch log, and the screen says fetching now while one is in flight. Stop fetch aborts mid-run — even one the scheduler started.
Headless & scriptable
Drive the whole pipeline from an agent — NDJSON events, step-labelled progress, stable error codes, dry runs, idempotency keys. Flip to the view for the command contract.
Any file works too
Email isn't the only input. Hand it a CSV, Excel or JSON path and it parses, infers types, and summarizes the columns — then an internal chat drives the model. The result is another tab in the same app.
# quickstart
Node 18+. Fetchers need Python 3 with beautifulsoup4 (plus pdfplumber for PDF billers).
# install from npm
npm install -g openboard-cli
# launch the TUI — Integrations → Gmail to add billers and open Biller Studio
openboard
# …or wire the inbox up headlessly, then sync on a cron
openboard agent setup billers --scripts-dir ./fetchers \
--biller-email "you@gmail.com" --biller-app-password "<16-char App Password>" --json
openboard agent billers sync --json
# any file works the same way (csv, xlsx, or json)
openboard agent create --data ./invoices.xlsx --name "Invoices" --type invoices
openboard agent update --dashboard invoices --prompt "add a month-over-month chart"
# from source instead
git clone https://github.com/syedateebulislam/openboard
cd openboard && npm install && npm run build
Built with TypeScript, Ink, and a pluggable LLM layer.
Generated apps use React, Vite, Recharts, and Tailwind.
AGENT INTERFACE · NON-INTERACTIVE · JSON IN/OUT
Drive OpenBoardCLI from your agent.
OpenBoardCLI ships a stable, non-interactive command contract. Put
agent billers sync on a cron and your spending
dashboards keep themselves current from your inbox; point
OpenClaw — or any agent runner or CI step — at a data file
and it runs the whole pipeline: parse, generate, build, push,
deploy. No TUI, no prompts, machine-readable output.
openclaw / cron / ci / llm agent└─ billers sync — fetchers read gmail, one csv per biller└─ or a csv / xlsx / json you point at · analyze columns + rows
└─ configured llm writes react files
└─ build (auto-repairs on failure) · commit · push to github
└─ deploy to vercel (or git-integration fallback)└─ verify the live url · tag deploy-N for rollback
Configure OpenBoardCLI once (provider key, Gmail App Password, GitHub token, Vercel token, dashboard login). After that the contract below is fully hands-off. Each run streams NDJSON phase events on stderr and persists resumable state.
# command contract
setup — configure the LLM headlessly
openboard agent setup mode --mode local|hybrid|remote
openboard agent setup llm --provider openai|openai-codex|anthropic|gemini|moonshot|xai|mistral|openrouter|ollama|lmstudio \
[--model "<model>"] [--effort low|medium|high|max] [--api-key "sk-..."] [--json]
openboard agent setup status --json
# --effort maps to the provider's reasoning knob (reasoning_effort / extended thinking)# secrets can come from OPENBOARD_* env vars instead of flags
billers — turn a Gmail inbox into per-biller dashboards
openboard agent setup billers --scripts-dir "<folder of fetch_<biller>.py>" \
--biller-email "you@gmail.com" --biller-app-password "<16-char App Password>" \
[--biller-key zomato --biller-key uber_rides] [--json]
openboard agent billers status --json # discovered fetchers + which are enabled
openboard agent billers sync [--biller zomato] --json
# billers are DISCOVERED, not hardcoded: fetch_*.py in --scripts-dir, each with its own KEY# --biller-key accepts only discovered keys and REPLACES the enabled set# credentials reach each fetcher via OPENBOARD_GMAIL_* in its environment, never on disk# results[]: key, displayName, ok, changed, dashboardUpdated, dashboardExists, error# sync exits 1 if any enabled biller failed; branch on dashboardExists, not changed alone# fetches are lock-serialised — a cron sync overlapping an open TUI is safe# creating a NEW fetcher (Biller Studio) is TUI-only: it shows you the email first# in the TUI all of this lives under Integrations → Gmail
create — onboard a new dataset as a tab
openboard agent create --data "<csv|xlsx|json>" --name "<title>" \
[--type health|finance|grocery|travel|food|shopping|subscriptions|utilities|invoices|custom] \
[--prompt "<intent>"] [--dry-run] [--idempotency-key "<key>"] [--json]
# alias: openboard agent onboard · returns a reusable dashboard selector# --type picks a tuned category prompt; the Overview master tab updates automatically# --dry-run returns the plan (rows, columns, selector) without an LLM call# --idempotency-key makes orchestrator retries safe — same key, same result
update — change an existing dashboard
openboard agent update --dashboard "<selector>" --prompt "<request>" \
[--data "<csv|xlsx|json>"] [--dry-run] [--json]
# e.g. --prompt "add a weekday vs weekend chart and flag pricey rides"
refresh — regenerate from saved prompt history
openboard update --dashboard "<selector>" # one board, latest data
openboard update --all # every registered board
query — discover state before acting
openboard agent list --json # all dashboards + deploy urls
openboard agent status --dashboard "<selector>" --json
# incl. dataStale: data changed since last gen?
openboard agent runs --json # recent runs + failure summary
recover — resume a failed run, or roll back a bad deploy
openboard agent resume "<run-id>" --json # re-runs build→deploy only if generation succeeded
openboard agent rollback --dashboard "<selector>" --json
# restore + redeploy the previous deploy-N tag
Selectors come back from create and are reused by update. Run ids come back from every run and feed resume. Quote Windows paths. From source, swap openboard for node dist/index.js.
Exit code 0 on success, non-zero on failure. With --json, the final result is the only thing on stdout; stderr streams NDJSON progress events ({"event":"phase","phase":"build","pct":52,"step":5,"totalSteps":8}) — track pct or step/totalSteps for progress; every provider emits ~10s generation heartbeats, so a phase silent for minutes is wedged. Without --json, stdout lines carry the same [step/8] labels. verified is the post-deploy health check. Branch on errorCode (E_BUILD_FAILED, E_DEPLOY_AUTH, E_LOCKED, E_NO_LLM, …), never on the prose error — the full table is in Agent.md.
# wire it into OpenClaw
OpenClaw (or any runner) just shells out to the contract and reads the JSON back.
# nightly: pull new receipts and refresh the boards that changed# exits 1 if any enabled biller failed — each result carries its own error
openboard agent billers sync --json | jq -r '.results[] | "\(.key) ok=\(.ok) changed=\(.changed) board=\(.dashboardExists)"'
# an OpenClaw task / any agent step — idempotency key makes retries safe
result=$(openboard agent create --data "$DATA" --name "$NAME" --idempotency-key "$TASK_ID" --json)
selector=$(echo "$result" | jq -r '.dashboardSelector')
# later, on a user request
openboard agent update --dashboard "$selector" --prompt "$REQUEST" --json
# on failure: resume from the failed phase (no second LLM bill after generation)
runid=$(echo "$result" | jq -r '.runId')
openboard agent resume "$runid" --json
# bad deploy? restore the previous tagged deploy
openboard agent rollback --dashboard "$selector" --json
# nightly, after data files refresh — skip boards whose data didn't change
stale=$(openboard agent status --dashboard "$selector" --json | jq -r '.dataStale')
[ "$stale" = "true" ] && openboard update --dashboard "$selector"
Full contract, flag tables, and decision rules live in Agent.md. A plain-text summary for LLMs is at /llms.txt.