Dashboard briefing
Some numbers you can only get by looking at a dashboard: a SaaS vendor’s usage graphs with no export, a Grafana you can view but not query, an analytics share link. The charts are drawn in the browser after the app loads, so fetching those pages with a reader returns an empty shell.
This recipe reads the dashboard for you. It opens the page in a real browser, takes a screenshot, and asks a vision model to turn what it sees into a typed briefing:
DASHBOARD BRIEFING - https://plausible.io/plausible.ioPeriod: Last 30 days ↑ trending up
Unique visitors: 128k Pageviews: 340k
Worth a look: - Jun 24: spike to ~2x the surrounding baseline
Traffic is up over the period with a clear weekday rhythm. The Jun 24 spikestands out against an otherwise steady trend; worth checking what shippedor got posted that day.The output is a Schema, so the briefing is data rather than prose. You can
post it to Slack, diff it against last week’s, or alert when anomalies is
non-empty. Put it on a schedule and nobody has to remember to look.
Try it
Start a headless Chromium with its DevTools port open (screenshots need a real rendering engine; see the CDP provider for other ways to get one):
docker run -d --name chromium -p 127.0.0.1:9222:9222 chromedp/headless-shellThen run it. The default target is Plausible’s own public dashboard, live traffic for plausible.io, public on purpose:
GOOGLE_API_KEY=... pnpm tsx recipes/dashboard-briefing/run-node.tsPoint it at your own dashboard, e.g. a Plausible share link, a public Grafana, or any URL you can open in a browser:
DASHBOARD_URL="https://plausible.io/share/yoursite.com?auth=..." \ GOOGLE_API_KEY=... pnpm tsx recipes/dashboard-briefing/run-node.tsConfiguration
| Env var | Default | Meaning |
|---|---|---|
GOOGLE_API_KEY | (required) | Gemini API key for the vision model. |
DASHBOARD_URL | https://plausible.io/plausible.io | The dashboard to read. |
MODEL | gemini-3-flash-preview | Vision-capable model id. |
SETTLE | 2 seconds | How long to let the page render before the screenshot. |
CDP_URL | http://127.0.0.1:9222 | Chromium debug address (http:// is resolved to the ws:// endpoint automatically) or a full ws:// URL. |
LOG_LEVEL | Info | Set Debug for screenshot/turn details. |
How it works
There is no agent loop here. The recipe opens the page, waits a moment for
it to render, screenshots the full page, and decodes one vision
LanguageModel turn against the Briefing schema (period, trend, headline
metrics, anomalies, summary). The schema’s field annotations double as
instructions to the model, and the prompt holds it to what is visible:
values read verbatim, estimates marked with ~, no invented numbers.
recipe.ts: the briefing schema and the screenshot-then-decode flow.app.ts: composition (ChromiumBrowserLayer, GeminiLanguageModelLayer), env config, and the briefing formatter.run-node.ts: attaches the NodeHttpClientand starts the runtime.
Next to agent usability testing this is the
other half of the Browser story: that recipe acts on pages through a tool
loop; this one reads pixels through a single structured turn.