Skip to main content

Cross-harness HTML artifact toolkit for AI coding agents. Native plugins for Claude Code, Codex CLI, and GitHub Copilot CLI + GitHub Actions recipe for the Copilot Coding Agent. Enforce, render, persist, compound.

Project description

atv-paperboard

Cross-harness HTML artifact toolkit for AI coding agents. One spec. Three native adapters. Zero lock-in.

Drop it into Claude Code, Codex CLI, or GitHub Copilot CLI and your agent stops dumping markdown into the chat — it emits paired .html + .DESIGN.md artifacts governed by Google's DESIGN.md spec, persisted to disk, and rolled into a compounding gallery you can actually share.

https://github.com/user-attachments/assets/471627d0-48b3-4a2d-aa15-7fd751f374a6

▶ Fallback: open the 60-second teaser file


Why this exists

Every coding agent today renders status updates the same way: a wall of monospace markdown in a chat window. That output is ephemeral, ugly, and impossible to share with stakeholders. Paperboard intercepts the moment an agent emits structured data (a status table, a triage list, a comparison matrix) and turns it into a lint-clean HTML artifact that lives on disk, links into a gallery, and renders identically across harnesses.

It's the same toolkit, the same contract, the same core/ Python package — wired into three different agent runtimes via thin native adapters, plus a GitHub Actions recipe for the Copilot Coding Agent.

Where it runs (today)

Harness Type Status
Claude Code Native plugin (adapters/claude-code/) ✅ Shipping
Codex CLI Native plugin (adapters/codex/) ✅ Shipping
GitHub Copilot CLI Native plugin (adapters/copilot-cli/) ✅ Shipping — validated end-to-end against copilot.exe v1.0.49 in an isolated sandbox
GitHub Copilot Coding Agent GitHub Actions recipe (recipes/github-actions/) ✅ Shipping
OpenCode Native plugin 🚧 Deferred to v0.1.2 (TS plugin model has 5 breaking upstream defects that need an empirical-verification cycle)

The Copilot CLI integration was validated against the real binary inside a fully isolated sandbox (USERPROFILE/HOME/COPILOT_HOME pinned). Hook fires, payload parses, suggestion injects, file lands, exit=0.

The four pillars

Pillar What it does How you'd break it
Enforce Validates the paired DESIGN.md via the upstream Google CLI and an HTML-side color-token trace Reference {colors.nonexistent} in DESIGN.md → lint rejects
Render Single-file HTML → loopback HTTP server → auto-opens a browser tab (skipped in headless/remote) paperboard render --input data.json → triple on disk + tab in <2s
Persist Writes {name}.html + {name}.DESIGN.md + {name}.meta.yaml to the harness-resolved persistence path After 3 emissions → 3 triples at the right path for that harness
Compound Auto-regenerates a gallery.html that reflects every prior artifact, governed by its own DESIGN.md Add a new artifact → gallery reflects it within 1s, gallery's own lint passes

Install — pick your harness

Prereq for every native plugin: pip install atv-paperboard (Python 3.10+). The Python package ships the paperboard binary that every harness's hook invokes; the plugin manifest itself only registers the agent, skills, and hook.

GitHub Copilot CLI (native plugin)

pip install atv-paperboard
# Then inside copilot (interactive):
/plugin marketplace add All-The-Vibes/ATV-PaperBoard
/plugin install atv-paperboard@atv-paperboard

Full instructions: adapters/copilot-cli/INSTALL.md

Claude Code (native plugin)

pip install atv-paperboard
# Then inside Claude Code:
/plugin marketplace add All-The-Vibes/ATV-PaperBoard
/plugin install atv-paperboard@atv-paperboard

Full instructions: adapters/claude-code/INSTALL.md

Codex CLI (native plugin)

pip install atv-paperboard
git clone https://github.com/All-The-Vibes/ATV-PaperBoard ~/.agents/skills/atv-paperboard
# Optional: add the hook to ~/.codex/config.toml (see INSTALL.md)

Full instructions: adapters/codex/INSTALL.md

GitHub Copilot Coding Agent (Actions recipe)

pip install atv-paperboard
cp recipes/github-actions/*.template .github/
# Rename the .template extensions and fill in repo-specific values

Full instructions: recipes/github-actions/INSTALL.md

Quick start (standalone — no harness needed)

Requires Node.js + Python 3.10+.

git clone https://github.com/All-The-Vibes/ATV-PaperBoard
cd ATV-PaperBoard
npm install
pip install -e .
python -m core.cli render --input examples/inputs/build-status.json --output-dir ./out
python -m core.cli gallery --output-dir ./out

render auto-triggers gallery regeneration. --output-dir overrides the harness-resolved persistence path for every subcommand.

What you get

Three real example artifacts ship in examples/output/:

Each artifact is a paired .html + .DESIGN.md + .meta.yaml triple. The HTML uses only design tokens declared in the DESIGN.md. The lint trace will reject any drift.


How it actually works

The artifact triple

Every paperboard render writes three files that travel together:

File Purpose
{slug}.html Single-file, self-contained HTML. CSS inlined. No external runtime. Opens in any browser, embeds in any wiki, attaches to any PR.
{slug}.DESIGN.md The Google DESIGN.md contract: design tokens, type scale, do's & don'ts. The lint pass walks the HTML and rejects any color/typography token that isn't declared here.
{slug}.meta.yaml Sidecar metadata — source input path, design used, tier, harness that produced it, timestamp. The gallery walks this to compose its index.

The lint isn't generic "is this valid markdown" — it's a token-trace. If your HTML uses #e63946 and that hex never appears in any token in the DESIGN.md, lint fails with a fail-class of color-not-in-contract. That's how design quality stays consistent across renders without policing prose.

The render tiers (Pico vs daisyUI)

Two Jinja2 templates ship out of the box:

  • pico-tier — minimal, classless CSS via Pico. Sub-20KB HTML. Use for dashboards, status snapshots, plain reports. Default.
  • daisy-tier — richer component palette via daisyUI. Use for marketing-grade hero artifacts.

Both consume the same design tokens via a token-rename layer in core/render.py that bridges @google/design.md export tailwind output to each framework's CSS variables (--pico-primary for Pico, --p for daisyUI). Adding a new tier = new Jinja template + new rename block; no changes to validation or persistence.

Auto-detection — core/detect.py

The CLI resolves which harness it's running inside via a strict precedence ladder. Env vars beat filesystem heuristics so that a user with multiple harnesses installed gets detected by the one currently running, not the one whose dotfiles happen to exist:

Tier Signal Returns
1 CLAUDE_PLUGIN_ROOT or CLAUDE_PLUGIN_DATA in env claude-code
1 GITHUB_ACTIONS=true copilot-coding-agent
1 COPILOT_HOME in env, or ~/.copilot/installed-plugins/atv-paperboard/ exists copilot-cli
1 OPENCODE_CONFIG_DIR in env opencode (reserved for v0.1.2)
1 CODEX_HOME in env codex
2 ~/.codex/config.toml exists codex
3 VSCODE_PID AND TERM_PROGRAM=vscode copilot-ide (reserved for v0.2)
Default standalone

The IDE-pairing tier requires both signals to avoid false positives from terminals that re-export VSCODE_PID.

Per-harness persistence paths

core/persist.py resolves where artifacts land based on the detected harness. This is the load-bearing piece — get this wrong and the harness deletes your output:

Harness Persistence root
Claude Code ${CLAUDE_PLUGIN_DATA}/<date>/NOT ${CLAUDE_PLUGIN_ROOT}; the root is cleaned ~7 days after plugin updates
Codex CLI ~/.codex/atv-paperboard-artifacts/<date>/ — user-scoped (Codex has no CODEX_PLUGIN_DATA analogue)
Copilot CLI ${COPILOT_HOME:-~/.copilot}/plugin-data/atv-paperboard/artifacts/ — user-scoped
Copilot Coding Agent ${repo}/paperboard-artifacts/<date>/ — workspace-scoped (PR-attached)
OpenCode ${OPENCODE_CONFIG_DIR}/atv-paperboard-artifacts/reserved for v0.1.2
Standalone $(pwd)/paperboard-artifacts/

--output-dir overrides this for every subcommand.

The three integration patterns

Different harnesses expose different surfaces. We don't fight that — we use one of three patterns per harness:

Pattern Harnesses using it Mechanism
Native plugin Claude Code, Codex CLI, Copilot CLI Per-harness wrapper file (manifest/loader); same SKILL.md payloads; same Python core invoked via subprocess.
Instructions + CLI Codex CLI fallback AGENTS.md directs the agent to invoke paperboard render as a shell command — for users who don't want a full skill install.
GitHub Actions hook Copilot Coding Agent .github/workflows/copilot-coding-agent-paperboard.yml runs the CLI in CI for PR-attached artifacts.

Hook heuristic — why your terminal isn't on fire

A naive PostToolUse hook on Write would fire on every markdown table the agent emits and the user would get a <system-reminder> storm. The detect-artifact-candidate hook filters before suggesting anything:

  1. Numeric-or-status column requirement. Skip unless the table has ≥ 2 numeric columns OR a column whose header matches ^(status|state|result|pass|fail|score|count|%|cost|p\d{2,3})$ (case-insensitive). Plain task lists do not fire.
  2. Path-prefix skiplist. Skip if the written file is *.md and lives under docs/, CHANGELOG, README*, or .github/ — those are user-authored docs, not requested renders.
  3. Self-recursion guard. Skip if the path is under any artifact_dir(harness) — prevents the hook re-firing on artifacts it just wrote.
  4. Suppression window. Skip if the prior suggestion fired < 30 seconds ago (per-session). State stored in ${persist_dir}/.suggest-cooldown.
  5. Suggestion never auto-renders. The hook only emits a <system-reminder>-style note with a paste-ready command. Auto-render is explicitly out of scope (avoids "magic" mis-fires).

Copilot CLI — three Copilot surfaces, disambiguated

"Copilot" is now three meaningfully different products and Paperboard targets two of them:

Surface What it is Plugin model Paperboard support
Copilot Extensions (Copilot Apps) Remote HTTPS endpoints invoked from chat No local plugins; no FS access Out of scope (remote-HTTPS only)
Copilot Coding Agent GitHub-hosted agent on PRs Repo-level instructions + workflow YAML ✅ via recipes/github-actions/
Copilot CLI Interactive copilot terminal agent on your machine Full local plugin model (agents + skills + hooks + MCP) ✅ via adapters/copilot-cli/

The Copilot CLI adapter is a native plugin: a directory laid out as agents/, skills/, hooks.json, optional .mcp.json. There is no plugin.json — the presence of those well-known files is the manifest.

Hook event names (Copilot CLI accepts both camelCase and PascalCase): sessionStart, sessionEnd, userPromptSubmitted, preToolUse, postToolUse, errorOccurred, agentStop.

Architectural caveat — Copilot CLI hooks are FAIL-OPEN by default. Unlike Claude Code (where a non-zero exit blocks the tool call), a Copilot CLI hook returning non-zero is logged and ignored — execution continues. This means the Enforce pillar on Copilot CLI is advisory at the hook layer; hard enforcement lives in paperboard validate and the CI recipe.

The additionalContext channel. A Copilot CLI hook that exits 0 with {"additionalContext": "..."} on stdout gets that text injected into the agent's next turn — the cleanest channel for paperboard render to tell the agent "I rendered your output to <slug>.html" without polluting the bash tool's output stream.

What's NOT in v0.1.x

Explicit non-goals so you know what to expect:

  • Cross-harness artifact aggregation. Each harness maintains its own gallery from its own persistence root. One unified gallery showing artifacts from multiple harnesses on one machine is v0.1.2 work.
  • VS Code Chat Participant extension. The in-IDE Copilot path. Complex, low marginal value over the instructions+CLI pattern. Deferred to v0.2.
  • MCP server integration. Any harness. Deferred to v0.2.
  • Live-reload via WebSocket. Artifacts are static HTML by design.
  • Full HTML-side token trace. v0.1.x is color-only; spacing/typography/shadow lint is v0.2.
  • Plugin auto-update, telemetry, Anthropic Artifacts API adapter. None.

Threat model & mitigations

The contract is hostile to a few specific failure modes:

Threat Mitigation
@google/design.md schema drift (it's alpha, pinned to 0.1.1) tests/test_core_bridge.py is the drift guard; if it fails post-bump, do not auto-upgrade
npx zero-byte stdout on Windows Replaced with node-direct: node <bin>/dist/index.js ...
Auto-detect false negatives (Codex lacks a stable session env var) Tier-2 filesystem heuristic + "standalone" final fallback
Cross-harness state divergence Each harness has its own persistence path; v0.1.x makes this explicit (no aggregation)
Prompt injection in fetched DESIGN.md (e.g., --design <url> later) Loader must treat fetched content as data, lint before consumption, never expand ${...} from fetched content into commands
Hook re-firing on its own artifacts Self-recursion guard in heuristic rule 3

Apache-2.0 throughout

  • This toolkit: Apache-2.0.
  • @google/design.md@0.1.1 (pinned dev dep): Apache-2.0.
  • Starter designs in designs/starters/ carry per-file attribution: frontmatter with inspired_by, not_affiliated_with, source_repo, source_commit, source_license, redistributed_under. The test_starter_attribution.py lint fails the build if any starter lacks the required keys.

Architecture

atv-paperboard/
├── core/                                    # SHARED Python core (harness-agnostic)
│   ├── bridge.py                            # node + @google/design.md wrapper
│   ├── render.py                            # html generation, tier templates, token-rename
│   ├── validate.py                          # google lint + html-side color-trace
│   ├── regenerate.py                        # 3-step retry (same → switch tier → fall back)
│   ├── gallery.py                           # compounding artifact
│   ├── persist.py                           # harness-aware persistence paths
│   ├── detect.py                            # auto-detect harness via env + fs heuristics
│   └── cli.py                               # `paperboard` standalone CLI (universal)
│
├── skills/                                  # SHARED SKILL.md payloads (3 harnesses portable)
│   ├── render-artifact/SKILL.md
│   ├── validate-artifact/SKILL.md
│   ├── regenerate-artifact/SKILL.md
│   └── gallery/SKILL.md
│
├── adapters/                                # per-harness wrapper files (~50 LOC each)
│   ├── claude-code/                         # .claude-plugin/plugin.json + hooks/hooks.json
│   ├── codex/                               # agents/openai.yaml + AGENTS.md.template
│   └── copilot-cli/                         # agents/ + skills/ + hooks.json
│
├── recipes/                                 # CI/workflow recipes (not adapters)
│   └── github-actions/                      # copilot-instructions.md + workflow.yml templates
│
├── designs/
│   ├── paperboard.DESIGN.md                 # default (dialed-back neubrutalism)
│   ├── starters/                            # stripi-, lin-ear-, vercel-inspired (attributed)
│   └── glass.DESIGN.md                      # opt-in premium tier
│
├── templates/
│   ├── pico-tier.html.j2
│   ├── daisy-tier.html.j2
│   └── gallery.html.j2
│
├── examples/                                # 3 real artifact triples + gallery
└── tests/                                   # 130 passing, 2 live-harness gates skipped

The same SKILL.md files end up inside Claude Code's /plugin install, Codex's ~/.agents/skills/, and Copilot CLI's --plugin-dir via per-adapter build steps. Every adapter and recipe invokes the same core/cli.py entry point via subprocess.

CLI surface

paperboard render [--design <name|path|url>] [--tier pico|daisy] [--input <path>]
paperboard validate <slug>
paperboard regenerate <slug>
paperboard gallery
paperboard detect-artifact-candidate <tool-output>   # hook helper
paperboard doctor                                     # diagnose install

Every command resolves the harness via detect_harness() first, then routes to the harness-appropriate persistence path. Browser-open is guarded by CLAUDE_CODE_REMOTE / GITHUB_ACTIONS / no-display heuristics so headless and remote sessions don't try to pop a tab.

Roadmap

  • CHANGELOG.md — version history
  • v0.1.2 — OpenCode adapter (5 SPEC-level TS plugin defects need empirical-verification cycle), USPTO TESS clearance on "paperboard" / "atv", PyPI publish, Claude Code marketplace PR, cross-harness gallery aggregation
  • v0.2 — VS Code Chat Participant (Copilot in-IDE path), MCP server integration, full HTML-side token trace (spacing/typography/shadow)

License

Apache-2.0. See LICENSE.

Contributing

PRs welcome. Read CONTRIBUTING.md for project layout, dev setup, test markers, lint config, branching, commit conventions, recipes for common contributions (new harness adapter, new design starter, new CLI subcommand), and where to file bug reports vs security issues. No CLA — inbound = outbound under Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

atv_paperboard-0.1.2.tar.gz (75.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

atv_paperboard-0.1.2-py3-none-any.whl (62.4 kB view details)

Uploaded Python 3

File details

Details for the file atv_paperboard-0.1.2.tar.gz.

File metadata

  • Download URL: atv_paperboard-0.1.2.tar.gz
  • Upload date:
  • Size: 75.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for atv_paperboard-0.1.2.tar.gz
Algorithm Hash digest
SHA256 2da840c42e4083543ea721138a0b2a7f7881363df72ac704725bb881e9787675
MD5 c4e5086eec31d5b3b95d09d467bfd19f
BLAKE2b-256 605483715511897e77261c80b42c7326511f15ffbdc7da5d985bdb8ac93f27aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for atv_paperboard-0.1.2.tar.gz:

Publisher: pypi-publish.yml on All-The-Vibes/ATV-PaperBoard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file atv_paperboard-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: atv_paperboard-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 62.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for atv_paperboard-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 9f4c126b3c592008d5fec86f9cca28666f6bd7eeffe57dd12782f1580457173c
MD5 cb69267cc592071f9224b4335667b341
BLAKE2b-256 cb594c662127ffc73c04db4cbf4ed0470ec1216885a57d8c05642d9c6d4456d1

See more details on using hashes here.

Provenance

The following attestation bundles were made for atv_paperboard-0.1.2-py3-none-any.whl:

Publisher: pypi-publish.yml on All-The-Vibes/ATV-PaperBoard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page