skills/channel-artifact/skills/channel-artifact/SKILL.md
Build ANY interactive HTML artifact backed by a Claude Code channel plugin. The user gets a localhost UI where they can pin comments anywhere on the page or on specific data points; each pin push-delivers to the live Claude session as a channel notification; Claude addresses the question (queries MCPs, runs code, etc.) and updates the artifact in place via tool calls. Use this skill whenever the user wants an interactive visualization, dashboard, explorer, annotation tool, design canvas, data tool, or ANY UI they can "poke at and have Claude answer". Trigger on phrases like "interactive artifact", "pin comments", "channel plugin", "Figma-style comments on data", "annotations that Claude responds to", "build me a dashboard with feedback", "dashboard I can chat with", "tool I can leave notes on", "make an artifact that updates when I ask it questions", "interactive thing backed by a channel", or any reference to the fakechat plugin pattern. This is the right skill any time the user wants more than a one-shot answer — they want a persistent visual artifact they can interrogate over time. The plumbing (MCP stdio + HTTP + WebSocket + channel notifications + tool calls) is fully generic — funnel dashboards, graph editors, kanban boards, sequencers, map annotators, scatterplot explorers, anything at all. Don't reach for this for static charts or one-off plots.
npx skillsauth add back1ply/LLM-Skills channel-artifactInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build any interactive HTML artifact served at localhost:<PORT> by a Bun-backed MCP channel plugin. The user pins comments on specific nodes OR anywhere on the page; each pin push-delivers to the live Claude session as a notifications/claude/channel event; Claude addresses the question and calls back via tool calls to update the artifact in place. Comments persist; addressed comments stay pinned with Claude's reply rendered beneath them; the UI updates live over WebSocket.
Before scaffolding anything, ASK. This skill is domain-agnostic — the user's intent shapes everything downstream. Cover these with short, targeted questions:
tools/<plugin-name>/ in the current project. Alternatives: ~/tools/<plugin-name>/ for user-level, or inside an existing repo.class="redacted") or not at all.Don't proceed until you have answers. If the user says "just vibe with me and build something simple", pick sensible defaults and call them out explicitly.
[Browser] ←──── HTTP + WebSocket ────→ [Bun server] ←─── stdio MCP ───→ [Claude Code session]
↑ │ │
│ │ notifications/claude/channel │
│ ├─── (push: comment + meta) ─────────→│
│ │ │
│ │←─── tool calls (mark_addressed, │
│ │ update_node, update_data) ──────│
│ ↓
│ [data.json + comments.json on disk]
│ │
└────── live refresh via WebSocket ────────┘
This plumbing is domain-agnostic. The only parts that change per artifact:
data.json shape — whatever structure your viz needs.render function in index.html — how to paint DATA into the DOM.mark_addressed is always there; update_node / update_data are optional.instructions field — tell Claude how to interpret the specific comments.Create this layout:
<plugin-name>/
├── .claude-plugin/plugin.json
├── .mcp.json
├── package.json
├── server.ts
├── index.html
├── data.json # your viz state shape
└── comments.json # { "comments": [] }
Start from the bundled templates in assets/:
assets/plugin.json.template → .claude-plugin/plugin.jsonassets/mcp.json.template → .mcp.jsonassets/package.json.template → package.jsonassets/server.ts.template → server.tsassets/index.html.template → index.html (minimal — has pin overlay, comment panel, WebSocket, no viz)assets/data.json.template → data.jsonassets/comments.json.template → comments.jsonReplace __PLUGIN_NAME__, __PLUGIN_NAME_UPPER__, __PORT__, __TITLE__, __DESCRIPTION__ placeholders. Run bun install in the plugin directory.
The template index.html is a blank canvas with all the comment plumbing already wired. Implement window.renderArtifact(DATA) to paint DATA into #content. Every element that should be commentable gets data-node-id="<your-id>". Elements without data-node-id are still commentable via freeform pins (the script auto-detects the nearest ancestor with an id as the anchor).
No runtime dependencies are required — the template is vanilla JS + Bun. Pull in only what you need via ESM CDN imports (https://cdn.jsdelivr.net/npm/<pkg>@<version>/+esm). Pick based on the artifact shape:
| Need | Library | Notes |
|---|---|---|
| Any data viz | D3 v7 | The Swiss Army knife. d3-sankey, d3-scale, d3-shape, d3-force etc. are all available. Gotcha: d3-sankey 0.12 UMD is broken on d3 v7 — use ESM and spread the frozen namespace. See references/troubleshooting.md. |
| Declarative charts | Chart.js / Plotly.js / ApexCharts | Chart.js is lightest; Plotly supports more chart types; ApexCharts is the prettiest out of the box. |
| Grammar-of-graphics | Observable Plot | @observablehq/plot — built on D3, much less ceremony. Great for quick analytical viz. |
| Network / graph editors | Cytoscape.js / Sigma.js / vis-network | Cytoscape if you need styling power; Sigma for very large graphs; vis-network for quick wins. |
| Canvas / interactive 2D | Konva.js / Fabric.js / Pixi.js | Konva is most ergonomic for "shapes you can drag around". Pixi is best for perf-critical cases. |
| 3D / WebGL | Three.js / regl | Three.js for scenes; regl for pure shader work. |
| Flow diagrams | Mermaid.js / React Flow / @xyflow/react | Mermaid is zero-config but read-only; React Flow lets users drag nodes. |
| Maps | Leaflet / MapLibre GL | Leaflet is the simplest; MapLibre is vector + GPU. |
| Tables | Tabulator / AG Grid (community) | Tabulator for ergonomics; AG Grid for scale. |
| Code editors | CodeMirror 6 / Monaco | CodeMirror ships lighter; Monaco is VS Code's editor verbatim. |
| Markdown | marked / markdown-it | Both fine; marked is smaller. |
| State | Nanostores / Zustand | Only if vanilla JS state becomes unmanageable. Most artifacts don't need this. |
| Animation | Motion One / GSAP | Motion One is tiny and web-native. GSAP for timeline-style animation. |
| Icons | Lucide / Heroicons / emoji | Inline SVG is fastest; Lucide has a clean aesthetic. |
For styling: plain CSS works. If you want utility-first, use Tailwind via the CDN JIT (https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4). Skip it if the artifact is small — the template already has a clean design-system.
data.json for your artifactWhatever your viz needs. Common patterns:
// Funnel/dashboard style
{ "nodes": [{"id": "...", "label": "...", "count": 123 }], "edges": [{"from": "...", "to": "..."}] }
// Graph/network
{ "nodes": [{"id": "n1", "label": "A", "x": 100, "y": 50 }], "links": [{"source": "n1", "target": "n2"}] }
// Kanban
{ "columns": [{"id": "todo", "title": "Todo", "cards": [{"id": "c1", "title": "..."}]}] }
// Timeline
{ "events": [{"id": "e1", "ts": "2026-01-01", "label": "..." }] }
No schema is enforced by the server beyond "valid JSON object". The update_node tool walks data.funnel and data.sideEvents by default — rename or generalize that lookup in server.ts if your shape differs.
.mcp.jsonAdd to <project-root>/.mcp.json:
{
"mcpServers": {
"<plugin-name>": {
"command": "bun",
"args": ["run", "--cwd", "<absolute path to plugin dir>", "--silent", "start"]
}
}
}
Use the absolute path. .mcp.json is project-scoped — Claude loads it only when launched from the project root.
Give the user a copy-pasteable block. Fill in the REAL port and URL you picked — don't leave __PORT__ placeholders in the message you send. Also list the REAL tools you exposed in server.ts (may just be mark_addressed, or include update_node / custom tools if you added them).
Setup complete. ⚠️ The current session (this one) CANNOT receive channel events — you must spawn a new one.
1. Stop anything on port <PORT>:
kill $(lsof -ti:<PORT>) 2>/dev/null
2. Launch a NEW Claude Code session with the channel attached:
claude --dangerously-load-development-channels server:<plugin-name>
Approve the MCP server prompt the first time it appears.
Optional: add --dangerously-skip-permissions so the new session auto-approves
tool calls while it's iterating on the artifact (no "approve?" prompts):
claude --dangerously-load-development-channels server:<plugin-name> --dangerously-skip-permissions
3. The new session prints to stderr (confirm the URL it actually binds to):
<plugin-name>: http://localhost:<PORT>
4. Open http://localhost:<PORT> in your browser.
• Toggle "Pin mode: on" in the top-right — pins ONLY drop while pin mode
is enabled. Click it again (or press Esc) to turn it off so you can
interact with the artifact normally.
• With pin mode on: click anywhere → type → Submit. Each pin push-delivers
to the new session with position, anchor element, and a context snippet.
Claude responds via mark_addressed; the pin turns green; the reply
renders beneath.
To verify wiring, run /mcp in the new session:
<plugin-name> · ✔ connected — tools: <list-your-actual-tools>
If anything misbehaves, see references/troubleshooting.md.
Critical reminders to state plainly in your reply, not just inside the code block:
--dangerously-load-development-channels. Spawning a new terminal is not optional.http://localhost:<PORT> (with whatever port you picked) in your reply so they can click it. Don't leave __PORT__ placeholders.--dangerously-skip-permissions is optional, not default. Offer it but don't require it — some users want to review every tool call.id, title, description. No external data source; user will add cards by hand. No redaction. Plugin name: kanban.kanban.data.json — { "columns": [{ "id": "todo", "title": "Todo", "cards": [] }, ...] }.renderArtifact — paint three columns, each card rendered as a <div data-node-id="card_<id>">. Columns themselves get data-node-id="col_<id>".add_card(column_id, title, description) and move_card(card_id, to_column_id) tool handlers alongside mark_addressed. Update server.ts tool list + CallToolRequestSchema switch.schema-explorer.data.json — { "tables": [{ "id": "User", "columns": [...], "x": 100, "y": 50 }], "edges": [...] }. Initial positions via a force layout on first render.prisma/schema.prisma and bake the graph into data.json.mark_addressed, update_node (to reposition a table), add_note(node_id, text) to attach Claude's insight as a sticky note rendered on the table card.image-annotator.data.json — { "imagePath": "/path/to/image.png", "regions": [] }.renderArtifact — render the image; overlay a canvas for clicks; freeform pin coordinates directly encode the region of interest.mark_addressed is all you need (regions ARE the comments, since every click is a freeform pin at (x,y)).references/troubleshooting.md for full diagnoses)Record<string, string>. Numbers, nulls, booleans → Zod throws → MCP transport closes silently. Always String() everything and drop null/undefined keys via spread.EADDRINUSE, log "running stdio-only" and process.stdin.resume(). Don't crash — multiple Claude sessions can share state files.bun server.ts standalone, killing the parent doesn't kill it. lsof -ti:<port> | xargs kill to free..attr("fill", ...), not var(--green).server.ts don't hot-reload. After editing, /mcp reconnect in the channel session — or kill the bun process and Claude respawns.assets/plugin.json.template — .claude-plugin/plugin.jsonassets/mcp.json.template — .mcp.json (plugin-internal, references ${CLAUDE_PLUGIN_ROOT})assets/package.json.template — package.json with the MCP SDK depassets/server.ts.template — Bun HTTP + WS + MCP stdio + tools, with EADDRINUSE handling and string-only channel metaassets/index.html.template — minimal artifact scaffold: pin overlay, comment panel near click, WebSocket, delete-without-notify. Paint your viz into #content via window.renderArtifact(DATA).assets/data.json.template — empty shape placeholderassets/comments.json.template — { "comments": [] }references/troubleshooting.md — every gotcha we've hit, with one-liner fixesreferences/architecture.md — deeper rationale (why channels > polling, why stdio+HTTP in one process, when to deviate)development
Query 2–3 AI models in parallel via OpenRouter and synthesize their responses into a unified review. Use when the user says "get a second opinion", "ask GPT", "ask Gemini", "multi-model review", "council review", "validate this", "what does [model] think", or wants cross-model validation of code, architecture, security, writing, math, or documents. Requires OPENROUTER_API_KEY set in the environment.
documentation
This skill should be used when the user asks to "write DAX measures", "create Power BI calculations", "help with DAX formulas", "write time intelligence", or mentions aggregations, filters, or DAX performance. Ensures correct syntax, optimal performance, and best practices on the first attempt.
tools
This skill should be used when the user asks to "find a skill", "discover plugins", "search for an MCP", "what plugins exist for X", "fill my skill gaps", "improve my setup", or when Claude recognizes it lacks tools for a task. Searches GitHub and marketplaces to suggest installations.
development
This skill should be used when the user asks to "review installed skills", "find duplicates", "detect skill overlaps", "identify skill gaps", "optimize skills", "audit my skills", or "troubleshoot skill conflicts". Supports Gemini, Claude Code, Cursor, Copilot, Windsurf, and custom setups.