AgentStack
SKILL verified MIT Self-run

Report Builder

skill-kevin-burns-claude-skills-report-builder · by kevin-burns

Build self-contained, single-page HTML reports and dashboards from data using Python + Jinja2 + Bootstrap 5 with Chart.js or Plotly. Use whenever the task is to produce an HTML report, data dashboard, metrics page, status page, or any single-file web artifact rendered from data — even if the user only says "report", "dashboard", "summary page", "visualize this", or hands you a CSV/DataFrame/JSON…

No reviews yet
0 installs
14 views
0.0% view→install

Install

$ agentstack add skill-kevin-burns-claude-skills-report-builder

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Report Builder? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Report Builder

The job is almost always the same shape: data in → one HTML file out, rendered by a template, viewable by double-clicking with no server. Keep that shape and the work stays small and reproducible. The two failure modes to design against are (1) non-determinism (charts that fetch live, timestamps that drift) and (2) unsafe data interpolation (raw values dropped into HTML or ``). Both are solved below.

The pattern

data (CSV / DataFrame / JSON)
   └─ your prep code (pandas, optional)  →  a plain JSON "context"
        └─ Jinja2 template (.html.j2)    →  scripts/render.py
             └─ one self-contained report.html

Separate prep (project-specific, may use pandas) from render (generic, reusable). scripts/render.py is the generic half — point it at a template and a JSON context file and it emits HTML. Don't rewrite it per project.

Run render.py by its absolute path — it lives in this skill's base directory (announced when the skill loads, usually ~/.claude/skills/report-builder); a relative report-builder/scripts/render.py won't resolve from another repo. It has PEP 723 inline deps (jinja2), so it must run under uv — never bare python. Define an rbuild function per block (a function passes arguments correctly under bash and zsh, where a plain $VAR doesn't word-split; re-declare it per block since shell state doesn't persist between tool calls):

# uv is required here (jinja2); resolve it even when it's not on a bare PATH (non-interactive
# shells often drop ~/.local/bin or the Homebrew bin -> `uv: command not found`):
UV="$(command -v uv || ls "$HOME/.local/bin/uv" "$HOME/.cargo/bin/uv" /opt/homebrew/bin/uv /usr/local/bin/uv 2>/dev/null | head -1)"
rbuild() { "$UV" run "$HOME/.claude/skills/report-builder/scripts/render.py" "$@"; }

rbuild --template my_report.html.j2 \
  --data context.json \
  --out report.html --title "Q2 Cost Report"

Two rules that prevent the common bugs

  1. Autoescape stays ON. render.py enables it and uses StrictUndefined, so a typo'd

variable fails loudly instead of rendering blank, and text values can't inject markup. Only reach for | safe on content you generated and trust (e.g. a Markdown→HTML block you produced). Never | safe on data that originated from a user, a file, or an API.

  1. Pass data to JavaScript with | tojson, never string-building. To hand a dataset to

Chart.js/Plotly, serialize it in the template: ```html

const chartData = {{ data | tojson }}; {# safe: escapes , quotes, unicode #}

``` This is correct and XSS-safe. Building JS by concatenating values is the classic injection hole — don't.

Chart.js or Plotly — pick per report

| Use Chart.js when | Use Plotly when | |---|---| | Standard charts: bar, line, pie, doughnut, radar | Interactive exploration: zoom, pan, rich hover, selection | | Small bundle, fast, dashboard-y look | Statistical/scientific: box, violin, heatmap, 3D, contour | | Few hundred → few thousand points | Large datasets, or users will drill into the data | | You want a clean default aesthetic with little config | You need subplots, dual axes, or precise scientific layout |

Default to Chart.js for status/metrics dashboards; reach for Plotly when the reader needs to interrogate the data, not just read it. Don't load both unless a report truly needs each.

Self-contained vs. CDN

  • CDN (default for internal/online reports): Bootstrap 5, Chart.js, Plotly via

``. Smallest file, but needs network at view time.

  • Vendored / inline (for portable, air-gapped, or archived reports): download the libs

and inline them, or commit them next to the report. Choose this when the report must open with no network, or must look identical years later. State which mode you chose in the report's provenance footer.

The starter template (assets/report-template.html.j2) uses CDN with the swap point marked. Pin a major version in the URL; verify the current version rather than guessing a patch number (see Fact-discipline).

Layout with Bootstrap 5

Lean on the grid and components instead of hand-rolling CSS:

  • Page frame: a container/container-fluid, a header row, then row/col-* for content.
  • KPIs: card components in a responsive row-cols-1 row-cols-md-3 grid.
  • Tables: ``; for long tables, wrap in

table-responsive.

  • Keep custom CSS to a small `` block for things Bootstrap doesn't cover (chart

container heights, print tweaks). Don't fight the framework.

Reproducibility (so the same data gives the same report)

  • Pass any "generated at" timestamp in via the context (--data), don't call

datetime.now() inside the template — that way a re-render of the same inputs is byte-stable, and tests can assert on output.

  • Record provenance in a footer: source of the data, the render command, lib versions/mode

(CDN vs vendored). A report a reader can't trace is a report they can't trust.

Fact-discipline (library versions & APIs)

Don't assert a current Bootstrap/Chart.js/Plotly version or a chart-config field from memory — these drift. Confirm the version and the option name before pinning:

# resolve then fetch — see the c7search skill (avoid `ask`)
LIB=$(c7search resolve --library-name "chart.js" "bar chart options" --json --limit 1 | jq -r '.[0].id')
c7search docs "$LIB" --topic "bar chart options" --tokens 3000

Or check the official docs/CDN. If you can't verify, leave the version as a clearly-marked placeholder rather than inventing a number. (Defer to the fact-verifier agent when a version gates the build.)

Files in this skill

  • scripts/render.py — generic Jinja2 renderer (autoescape + StrictUndefined; PEP 723

inline deps; uv run-ready). Reuse it; don't reinvent it.

  • assets/report-template.html.j2 — starter: Bootstrap 5 frame, KPI cards, a Chart.js

example wired through | tojson, a data table, and a provenance footer. Copy and adapt.

Quick start

  1. Copy assets/report-template.html.j2 into the project; adapt the markup.
  2. Write a small prep step that produces a JSON context (`{"title": ..., "kpis": [...],

"chart": {...}, "rows": [...], "generated_at": "...", "provenance": {...}}`).

  1. rbuild --template --data --out report.html (define rbuild as shown above).
  2. Open report.html. Verify chart renders, table populates, no Undefined errors.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.