Canvas Skill

SkillDev tools

The canvas skill is a tool for AI agents that displays HTML content on a connected canvas-capable display surface. It serves files from a local canvas host and sends a URL to a chosen Brigade node, which renders the page in a WebView. Use it to show games, visualizations, dashboards, generated HTML, or interactive demos on a screen.

Then ask your AI: use the Canvas Skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Have a connected Brigade node that supports canvas display, such as the Mac app, iOS, or Android.

What your AI can do with it

  • Present HTML content on a connected canvas-capable display
  • Navigate the canvas to a new URL
  • Hide the canvas
  • Capture a screenshot of the canvas
  • Execute JavaScript in the canvas
  • Live reload pages when files in the root directory change

Getting started

  1. Have a connected Brigade node that supports canvas display, such as the Mac app, iOS, or Android.
  2. Enable canvasHost in the active Brigade config file and set its root directory to the folder holding your HTML files.
  3. Set the gateway bind mode so the canvas host server binds to the address your nodes can reach.
  4. Place your HTML, CSS, and JS files in the canvas root directory.
  5. Ask the agent to present the canvas with the URL of the file you want to show.

What this skill tells your AI

The instructions your AI receives, as published by spinabot/brigade in skills/canvas/SKILL.md and read by Ahel’s review.

Display HTML content on connected Brigade nodes (Mac app, iOS, Android).

Overview

The canvas tool lets you present web content on any connected node's canvas view. Great for:

  • Displaying games, visualizations, dashboards
  • Showing generated HTML content
  • Interactive demos

How It Works

Architecture

┌─────────────────┐     ┌──────────────────┐     ┌─────────────┐
│  Canvas Host    │────▶│   Node Bridge    │────▶│  Node App   │
│  (HTTP Server)  │     │  (TCP Server)    │     │ (Mac/iOS/   │
│  Port 18793     │     │  Port 18790      │     │  Android)   │
└─────────────────┘     └──────────────────┘     └─────────────┘
  1. Canvas Host Server: Serves static HTML/CSS/JS files from canvasHost.root directory
  2. Node Bridge: Communicates canvas URLs to connected nodes
  3. Node Apps: Render the content in a WebView

Tailscale Integration

The canvas host server binds based on gateway.bind setting:

Bind ModeServer Binds ToCanvas URL Uses
loopback127.0.0.1localhost (local only)
lanLAN interfaceLAN IP address
tailnetTailscale interfaceTailscale hostname
autoBest availableTailscale > LAN > loopback

Key insight: The canvasHostHostForBridge is derived from bridgeHost. When bound to Tailscale, nodes receive URLs like:

http://<tailscale-hostname>:18793/__brigade__/canvas/<file>.html

This is why localhost URLs don't work - the node receives the Tailscale hostname from the bridge!

Actions

ActionDescription
presentShow canvas with optional target URL
hideHide the canvas
navigateNavigate to a new URL
evalExecute JavaScript in the canvas
snapshotCapture screenshot of canvas

Configuration

In the active Brigade config file ($BRIGADE_CONFIG_PATH, default ~/.brigade/brigade.json):

{
  "canvasHost": {
    "enabled": true,
    "port": 18793,
    "root": "/Users/you/brigade/canvas",
    "liveReload": true
  },
  "gateway": {
    "bind": "auto"
  }
}

Live Reload

When liveReload: true (default), the canvas host:

  • Watches the root directory for changes (via chokidar)
  • Injects a WebSocket client into HTML files
  • Automatically reloads connected canvases when files change

Great for development!

Workflow

1. Create HTML content

Place files in the canvas root directory (default ~/brigade/canvas/):

cat > ~/brigade/canvas/my-game.html << 'HTML'
<!DOCTYPE html>
<html>
<head><title>My Game</title></head>
<body>
  <h1>Hello Canvas!</h1>
</body>
</html>
HTML

2. Find your canvas host URL

Check how your gateway is bound:

CONFIG_PATH="${BRIGADE_CONFIG_PATH:-${BRIGADE_STATE_DIR:-$HOME/.brigade}/brigade.json}"
cat "$CONFIG_PATH" | jq '.gateway.bind'

Then construct the URL:

  • loopback: http://127.0.0.1:18793/__brigade__/canvas/<file>.html
  • lan/tailnet/auto: http://<hostname>:18793/__brigade__/canvas/<file>.html

Find your Tailscale hostname:

tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//'

3. Find connected nodes

brigade nodes list

Look for Mac/iOS/Android nodes with canvas capability.

4. Present content

canvas action:present node:<node-id> target:<full-url>

Example:

canvas action:present node:mac-63599bc4-b54d-4392-9048-b97abd58343a target:http://peters-mac-studio-1.sheep-coho.ts.net:18793/__brigade__/canvas/snake.html

5. Navigate, snapshot, or hide

canvas action:navigate node:<node-id> url:<new-url>
canvas action:snapshot node:<node-id>
canvas action:hide node:<node-id>

Debugging

White screen / content not loading

Cause: URL mismatch between server bind and node expectation.

Debug steps:

  1. Check server bind: CONFIG_PATH="${BRIGADE_CONFIG_PATH:-${BRIGADE_STATE_DIR:-$HOME/.brigade}/brigade.json}"; cat "$CONFIG_PATH" | jq '.gateway.bind'
  2. Check what port canvas is on: lsof -i :18793
  3. Test URL directly: curl http://<hostname>:18793/__brigade__/canvas/<file>.html

Solution: Use the full hostname matching your bind mode, not localhost.

"node required" error

Always specify node:<node-id> parameter.

"node not connected" error

Node is offline. Use brigade nodes list to find online nodes.

Content not updating

If live reload isn't working:

  1. Check liveReload: true in config
  2. Ensure file is in the canvas root directory
  3. Check for watcher errors in logs

URL Path Structure

The canvas host serves from /__brigade__/canvas/ prefix:

http://<host>:18793/__brigade__/canvas/index.html  → ~/brigade/canvas/index.html
http://<host>:18793/__brigade__/canvas/games/snake.html → ~/brigade/canvas/games/snake.html

The /__brigade__/canvas/ prefix is defined by CANVAS_HOST_PATH constant.

Tips

  • Keep HTML self-contained (inline CSS/JS) for best results
  • Use the default index.html as a test page (has bridge diagnostics)
  • The canvas persists until you hide it or navigate away
  • Live reload makes development fast - just save and it updates!
  • A2UI JSON push is WIP - use HTML files for now

Signals

GitHub stars
11k
Forks
70
Last commit
Oct 2026

Questions

What kinds of content can I display with the canvas skill?
Games, visualizations, dashboards, generated HTML content, and interactive demos. Any static HTML, CSS, and JS files served from the canvas host root directory can be presented on a connected node.
Why do localhost URLs not work when presenting the canvas?
The canvas URL is derived from the bridge host. When the server is bound to Tailscale, nodes receive a Tailscale hostname instead of localhost, so a localhost URL will not resolve on the node.
How does live reload work?
When liveReload is true, the canvas host watches the root directory for changes, injects a WebSocket client into HTML files, and automatically reloads connected canvases when files change.
Advanced
Item type
skill
Key
canvas-spinabot
Source
github.com/spinabot/brigade