Skip to main content

skaldr

Turn one YAML file into one polished, self-contained HTML report page. You describe what the report says — findings, tables, a pipeline, the numbers — and skaldr owns how it looks: layout, spacing, colour, light/dark, all decided once, here. No design work, no CSS, no drift.

See it → sales pipeline · warehouse count (rendered from examples/sales-pipeline.yaml and data/example.yaml).

Install

brew install alex-yanchenko/tap/skaldr     # recommended (macOS/Linux)
uv tool install skaldr                     # or, with uv
pipx install skaldr                        # or, with pipx

All three put a skaldr command on your PATH. (From a checkout, uv run skaldr … works without installing.)

Use

skaldr report.yaml                 # → out/report.html
skaldr report.yaml -o review.html  # choose the output path
skaldr report.yaml --watch -o review.html  # re-render on every save (live edit→preview; Ctrl-C to stop)
skaldr report.yaml --pdf report.pdf  # a ready-to-share PDF (drives a headless Chrome/Chromium)
open review.html                   # a self-contained file — open it, host it, or share it

Live preview without a watcher process. --watch needs a process that stays alive, which an agent harness will not give you: Claude Code reaps background jobs between turns, so the watcher dies and the HTML goes quietly stale. Two flags cover the same ground with no daemon:

skaldr report.yaml -o review.html --live      # the page re-reads itself when you return to the tab
skaldr report.yaml -o review.html --if-stale  # a no-op when the HTML is already current

Render once with --live, open the file, and leave the tab. Re-render after every edit with --if-stale (free when nothing changed), and the open tab picks it up on focus with your scroll position and open sections intact. --live 2000 also polls every two seconds, for a second monitor where the tab never loses focus. A small "live" badge marks a self-refreshing page and turns it off when clicked.

The page cannot poll for changes: its own CSP is default-src 'none', which blocks every scripted network request, and a file:// page could not fetch its own source anyway. So --live reloads on a signal it already has, which is you looking at the tab.

That's the whole tool: point it at a content file, get an HTML page (or a PDF). A few more commands help you write the content file and share the result:

skaldr --guide                     # the authoring guide: every block, the rules, a full example
skaldr --write-schema page.schema.json   # JSON Schema for your editor's YAML language server
skaldr report.yaml --embed -o out.html   # Artifact-ready fragment (no <html> skeleton) to publish as a claude.ai Artifact
skaldr --check report.yaml         # validate against the schema, write nothing (exits non-zero on error)
skaldr --check reports/*.yaml      # validate a whole set at once — for a pre-commit hook or CI
skaldr --check report.yaml -o report.html   # gate the render on the check: nothing reaches disk unless it passes
skaldr --emit-json report.yaml     # print the normalised model as JSON on stdout (for tooling/agents)
skaldr --extract-source report.html  # recover the YAML source embedded in a render (a file or an http(s) URL)

For a PDF, use --pdf (above): it prints the page's print styling with a headless browser you already have — the reliable way to a shareable PDF. (Printing a published Artifact doesn't work: it's a sandboxed frame the browser flattens to a snapshot, so the print CSS never applies.) --pdf needs a Chrome/Chromium/Edge on the machine; set SKALDR_BROWSER to point at one if it isn't auto-found.

There are no styling flags — everything is in the content file.

The content file

version: 1
meta:
  title: "Q3 Warehouse Inventory Count — Discrepancies & Fixes"
  subtitle: ["Reconciled review of the 10,000-unit cycle count."]
  source: "WMS export"          # optional; feeds the provenance footer
  date: "Q3 2026"               # optional; never auto-now (builds are reproducible)
  toc: true                     # optional; auto table-of-contents from level-2 headings
  hero: true                    # optional; larger display title + subtitle in a tinted band
badges:                         # author-declared vocabulary (see below)
  FLOOR:  { label: "Floor",  tone: amber, legend: "Fixable on the floor before the next count." }
  SYSTEM: { label: "System", tone: blue,  legend: "Defect in the scanning/labeling pipeline." }
blocks:
  - { type: heading, text: "Overview" }
  - { type: text, body: "Prose with **bold**, *italic*, `code`, ~~strike~~ and [links](https://x)." }
  - { type: cards, items: [{ label: "Matched cleanly", value: 8500, of: 10000, tone: success }] }
  # … more blocks

Top level is version · meta · optional badges · blocks — nothing else. Every block carries a type discriminator; the model is a pydantic discriminated union, so an unknown type, a field from the wrong block, or an unknown key each fails with a precise blocks.3.items.2.value-style error before anything renders.

Blocks: heading · text · list · fact_strip · key_value · cards · badge_row · callout · status_list · meter · table · code · quote · image · timeline · flow (a directional pipeline — arrow or step style, optional loop) · section (collapsible) · grid (bounded 6-column layout, with optional per-cell emphasis panels). The table is the workhorse — typed columns, grouped subtotals, sub-rows, colour-only indicator dots, row-level tone, and a reconcile block that hard-fails the build if the counts don't sum to a declared total. Badges are declared once and chip onto table rows, cards, timeline entries, and flow nodes alike. Prose fields take a small markdown subset (**bold**, *italic*, `code`, ~~strike~~, links); raw HTML is never interpreted.

Full reference: skaldr --guide (source: src/skaldr/skill/GUIDE.md), data/example.yaml (a file exercising every block), and schema/page.schema.json.

Guarantees

  • One self-contained file — inline CSS, system fonts, no external resources; the page carries its own <!doctype> + <meta charset> so it renders correctly from file://, any static host, or a claude.ai Artifact.
  • Validation is the product — structural mistakes fail the build with a field path, never reach the reader's eyes.
  • Derived, not authored — number formatting, percentages, subtotals, the legend, the TOC, and the provenance footer are all computed, so they can't drift from the data.
  • Light & dark — the palette follows the viewer's OS theme; a small corner menu lets the reader switch theme and page width.

Let an AI write it

skaldr ships Claude skills, so you can skip the YAML and just ask. Install them once:

skaldr --install-skill      # copies skaldr's skills into ~/.claude/skills (survives upgrades)
skaldr --install-plan-rule  # optional: also have the AI keep its working plans as live skaldr docs

--install-skill installs the core authoring skill and task-specific ones: a presentation builder (skaldr-presentation) that writes a word-for-word teleprompter runbook (with color-coded live/recording cues) and drives the audience deck into the org's real brand template, and a reflection helper (skaldr-reflect) that turns your experience of authoring with skaldr into a ranked, actionable pain-points report for the maintainer. Each lands in its own ~/.claude/skills/<name>/.

Skills keep themselves current. After a brew upgrade skaldr, an installed skill refreshes itself the next time you run skaldr — no need to re-run --install-skill. It only ever refreshes a skill you already installed (never creates one), never touches a symlinked skill (a contributor's live-edit link), and never interferes with a render. Set SKALDR_SKILL_SYNC=0 to turn the auto-refresh off.

--install-plan-rule is a separate, optional step: it adds a short, marker-delimited rule to ~/.claude/CLAUDE.md that steers the AI to author its working plans as live skaldr docs (rendered with --watch so you can follow along). Delete that skaldr:plan-rule block to opt out; re-running it refreshes the block in place. --install-skill never touches CLAUDE.md on its own.

Then in Claude Code (or Cowork), ask in plain language — "make me a skaldr report on this data export: what's clean, what's broken, and the fix" — and it writes the content file and renders the page. The skill reads the current guide from the tool itself (skaldr --guide), so it stays correct across upgrades without reinstalling.

Development

uv run skaldr data/example.yaml -o out/example.html   # run from a checkout
uv run pytest                                          # tests

Release files for skaldr 3.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for skaldr 3.0.1
File Size Uploaded
skaldr-3.0.1.tar.gz 131.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for skaldr 3.0.1
File Interpreter ABI Platform
skaldr-3.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 272.6 kB

Release files / skaldr-3.0.1.tar.gz

Download URL skaldr-3.0.1.tar.gz
Size 131.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f802fdc5a98572a1280243c12a2926a08a9b927f2c3dd61d344b55dfdb36e184
BLAKE2b-256 checksum
How to use checksums
85eec8ad589e6fc75cb05819fc9280ef07259a4b931f5e6beeab37670413660b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / skaldr-3.0.1-py3-none-any.whl

Download URL skaldr-3.0.1-py3-none-any.whl
Size 141.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9bd7ff14f1efe5e6c43f33f99fca4dc25357479e6f3758b68d07580f2ee922ba
BLAKE2b-256 checksum
How to use checksums
b63bb96e5679f5edee5255285ec16363ae2cb8d8c2ae6c38896b98e43507a738
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

3.1.0

2 release files

This release

3.0.1 This release

2 release files

3.0.0

2 release files

2.12.1

2 release files

2.12.0

2 release files

2.11.2

2 release files

2.11.1

2 release files

2.11.0

2 release files

2.10.0

2 release files

2.9.0

2 release files

2.8.0

2 release files

2.7.0

2 release files

2.6.0

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.1

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.0

2 release files

1.4.0

2 release files

1.3.3

2 release files

1.3.2

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page