Metadata-Version: 2.4
Name: atv-paperboard
Version: 0.1.2
Summary: 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.
Author: All The Vibes
License: Apache-2.0
Project-URL: Homepage, https://github.com/All-The-Vibes/ATV-PaperBoard
Project-URL: Repository, https://github.com/All-The-Vibes/ATV-PaperBoard
Project-URL: Issues, https://github.com/All-The-Vibes/ATV-PaperBoard/issues
Project-URL: Changelog, https://github.com/All-The-Vibes/ATV-PaperBoard/blob/main/CHANGELOG.md
Keywords: ai-agents,ai-coding,llm-tools,claude-code,codex-cli,copilot-cli,github-copilot,copilot-coding-agent,agent-plugin,agent-skills,agent-hooks,cross-harness,design-md,design-system,html-generator,html-artifact,dashboard,cli-tool,developer-tools,python
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: jinja2>=3.1
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Dynamic: license-file

# 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](https://github.com/google-labs-code/design.md), persisted to disk, and rolled into a compounding gallery you can actually share.

<!-- Inline-playable on github.com — keep this URL bare and on its own line. -->

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

<p align="center">
  <sub><strong><a href="assets/paperboard-teaser.mp4">▶ Fallback: open the 60-second teaser file</a></strong></sub>
</p>

---

## 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)

```bash
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`](adapters/copilot-cli/INSTALL.md)

### Claude Code (native plugin)

```bash
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`](adapters/claude-code/INSTALL.md)

### Codex CLI (native plugin)

```bash
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`](adapters/codex/INSTALL.md)

### GitHub Copilot Coding Agent (Actions recipe)

```bash
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`](recipes/github-actions/INSTALL.md)

## Quick start (standalone — no harness needed)

Requires Node.js + Python 3.10+.

```bash
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/`:

- [`build-status.html`](examples/output/build-status.html) — CI dashboard
- [`harness-comparison.html`](examples/output/harness-comparison.html) — side-by-side adapter feature matrix
- [`bug-hunt.html`](examples/output/bug-hunt.html) — triage snapshot
- [`gallery.html`](examples/output/gallery.html) — the compounding index, auto-regenerated on every render

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](https://github.com/google-labs-code/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](https://picocss.com/). Sub-20KB HTML. Use for dashboards, status snapshots, plain reports. Default.
- **`daisy-tier`** — richer component palette via [daisyUI](https://daisyui.com/). 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

```bash
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](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](LICENSE).

## Contributing

PRs welcome. Read [CONTRIBUTING.md](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.


