agentleFS
TeamHelix-AITeamHelix-AI/helix/skills/helix-canvas/SKILL.md

canvas

Skill1 starsChanged 2 months agoApache-2.0View on GitHub

Run a visual planning/brainstorm/collaboration session on Helix Canvas — the local canvas app where Claude pushes cards (questions, options, decisions, diagrams, artifacts) and the user answers in the browser. Use when the user says "/helix-canvas", "visual brainstorm", "open a canvas", or wants to collaborate on the canvas instead of the terminal. The classic `brainstorm` skill stays untouched for terminal/doc sessions.

45 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use taskflow-inbox-triage, update-cli-config and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • AtomicBot-aiSkill

    taskflow-inbox-triage

    Also in 5 projects that use this

  • PandelisZSkill

    update-cli-config

    View and modify Cursor CLI configuration settings in ~/.cursor/cli-config.json. Use when the user wants to change CLI settings, configure permissions, switch approval mode, enable vim mode, toggle display options, configure sandbox, or manage any CLI preferences.

    Also in 6 projects that use this

  • jiulingyunSkill

    BlueBubbles

    为Clawdbot构建或更新BlueBubbles外部通道插件(扩展包,REST发送/探测,webhook入站)。

    Also in 15 projects that use this

  • seedprodSkill

    voice-call

    Start voice calls via the OpenClaw voice-call plugin.

    Also in 14 projects that use this

  • 32103210Skill

    migrate-to-skills

    Convert 'Applied intelligently' Cursor rules (.cursor/rules/*.mdc) and slash commands (.cursor/commands/*.md) to Agent Skills format (.cursor/skills/). Use when you want to migrate rules or commands to skills, convert .mdc rules to SKILL.md format, or consolidate commands into the skills directory.

    Also in 6 projects that use this

  • 32103210Skill

    update-cursor-settings

    Modify Cursor/VSCode user settings in settings.json. Use when you want to change editor settings, preferences, configuration, themes, font size, tab size, format on save, auto save, keybindings, or any settings.json values.

    Also in 6 projects that use this

  • +1 more from 32103210

The file

We split this skill into the rules it gives an agent

11 rules. 0 are common, 4 are distinctive, and 7 are very specific to this project.

  • 0

    Common

    Most of the 43 edited versions we read keep these.

  • 4

    Distinctive

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If HELIX_ORCHESTRATOR_RUN_ID or HELIX_SKILL_CREDENTIAL_FILE is set, this skill runs in Helix Console.

    line 11
  2. Very specific:

    Use the normal helix <surface> up, push, state, and wait commands.

    line 13
  3. Very specific:

    Do not start local servers or Fleet, open a browser, curl localhost, use --local, log in, or change profiles/projects.

    line 14
  4. Distinctive:

    Before proposing anything, read the rails that bind this project on this surface:

    line 63
  5. Very specific:

    Run explicit rounds: push the round's questions/options as cards (grouped by threadId — threads are the stages/columns), then wait.

    line 91
  6. Very specific:

    Use printf '%s' when piping JSON in shell — zsh echo mangles \n.

    line 184
  7. Distinctive:

    Call the binary by its full path — a shell variable or function holding it stops the skill's pre-approval from matching, and every call …

    line 186
  8. Distinctive:

    If a turn ends while you still have canvas work of your own, the same rule applies — push a status card first, do the work, and re-arm.

    line 220
  9. Very specific:

    Update = push the same JSON with the existing id.

    line 278
  10. Distinctive:

    Update the project's memory file with: canvas slug, port, cursor reached, open blockers, ratified decisions, artifact statuses, and gate …

    line 331
  11. Very specific:

    Before stopping, clear spent gates — every user-owned todo/kanban/gantt that is no longer waiting on the user gets updated to done or …

    line 333

“Common” is measured: kept by at least 6 in 10 of the 43 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Helix Canvas

Orchestrator cloud context (check before setup)

If HELIX_ORCHESTRATOR_RUN_ID or HELIX_SKILL_CREDENTIAL_FILE is set, this skill runs in Helix Console. This section takes precedence over local setup and browser instructions below.

  • First call the plugin's bin/helix orchestrator-protocol (Windows: bin/helix.cmd orchestrator-protocol). It must print 1. Otherwise stop with an actionable request to update the Helix plugin/CLI; never run an older binary's setup commands.
  • Use the normal helix <surface> up, push, state, and wait commands. The CLI selects cloud transport and the assigned project automatically. Keep the same slug within this task.
  • Do not start local servers or Fleet, open a browser, curl localhost, use --local, log in, or change profiles/projects. Do not read or print the credential file. Do not remove the injected environment variables.
  • up returns the Console session URL. Use that URL wherever the local instructions request Board/Fleet links. Events go to Console; human responses and gate approvals still come from the person through the normal wait protocol.
  • On cloud or compatibility errors, report the error. Never fall back to local mode. Outside this context, follow the local behavior below unchanged.

Drive a collaboration session through Helix Canvas (part of Team Helix). You push typed cards over a CLI; the user answers, comments, authors notes, and drags things in a browser. The append-only event log is the record; the canvas is the surface; the terminal is the fallback channel.

Setup

Three verbs, never more (ratified contract):

# start server, open browser; prints {url, port, cursor, viewers}
${CLAUDE_PLUGIN_ROOT}/bin/helix canvas up   --canvas <slug> --agent <name> --model <model-id> [--no-open] [--brief "<the job>"]

# card JSON on stdin -> {cardId, cursor}; existing id -> update
${CLAUDE_PLUGIN_ROOT}/bin/helix canvas push --canvas <slug>   # card JSON (one object, or an array of them) on stdin

# blocks; exit 0 delta, 3 timeout, 4 no-viewer, 5 server down
# --wake-on user returns only when a human acted (see Draining)
${CLAUDE_PLUGIN_ROOT}/bin/helix canvas wait --canvas <slug> --since <cursor> [--timeout <s>] [--wake-on user]

On Windows, ${CLAUDE_PLUGIN_ROOT}\bin\helix.cmd in place of bin/helix; the rest of the command is the same.

  • up opens the browser itself, from inside the binary. --no-open is for headless or scripted runs — never a way around a permission prompt. If a prompt is declined, hand the user ! open <url> and carry on.
  • up also brings up Fleet (mission control) when it is down, and returns fleet: {url, started}. Hand the user both links in one line — Board: <url> · Fleet: <fleet.url> — so they know the overview is there. up never opens Fleet in the browser; the user follows the link.
  • Identify yourself on up (ratified): pass --agent with your product name, lowercase (claude, codex, …) and --model with your exact model id (e.g. claude-fable-5). Fleet shows and filters sessions by them; a session that registers without them shows as anonymous. The session id is picked up from the environment on its own.

Canvas data lives in ~/.helix/canvas/<project>/<slug>/ (SQLite event log, server.json with port/pid, uploads) — one home for the whole machine, never inside a repo. The server watches files for file-window/artifact cards and streams changes to the browser.

Rails first

Before proposing anything, read the rails that bind this project on this surface:

${CLAUDE_PLUGIN_ROOT}/bin/helix rails for --surface canvas

What comes back is not advice — it is the standard the work is held to. Cite rail ids when you act on them; full text with helix rails get <id>. Details in the helix-rails skill.

Session flow

  1. Confirm the slug with the user (new canvas or resume — list ~/.helix/canvas/<project>/ if unsure). Never guess. Canvases are per-project by scope: the cwd names the project; pass --dir to target another one.
  2. ${CLAUDE_PLUGIN_ROOT}/bin/helix canvas up --canvas <slug> --brief "<the job, in your words>" — reuses a live server or starts one. Pass --brief whenever the user has already told you what this is about. An empty board with a brief opens working, that line under the mark; without one it asks What are we working on? — which reads, to someone who just told you, as though you did not hear them. The brief is also your first status pill.
  3. Blank canvas (no topic given): bare up, no --brief. The app shows the centered prompt; up and wait, and the user's opening prompt arrives as a prompt card in the delta — begin from it.
  4. On resume, fetch state first: curl -s http://127.0.0.1:<port>/api/state — cards, responses, cursor. Summarize open items to the user before pushing anything.
  5. Run explicit rounds: push the round's questions/options as cards (grouped by threadId — threads are the stages/columns), then wait.
  6. Between rounds, drain the delta continuously — react to every user note/comment within one cycle, even mid-build ("built", "on it", or a thread reply). Never let user input sit unacknowledged. Notes that add work fold into the existing claude-owned todo (push the same card id with the new items), or into one "additional requirements" list when they are genuinely a new track — never a new todo card per note; a board with a list for every remark is unreadable.
  7. Keep the status pill honest: --brief sets the first one; after that push {"type":"status","payload":{"text":"…"}} when starting a chunk of work, when going idle, and at hand-offs — it renders bottom-left and is how the user knows you're alive mid-build.
  8. Fold ratified decisions into the session's doc/memory as usual.

Ownership & gates (two-way cards)

todo, kanban, gantt, and prototype carry an optional payload.owner:

  • claude (default) — a progress display; read-only for the user; update it as you work (push with the same id).
  • user — a gate: rendered ochre and marked yours; the user clicks todo items (pending → active → done), drags kanban cards between columns, toggles their gantt lane. Every change arrives in your delta as card.updated with origin: "user" — treat these as the signals for when you may proceed. WATCH for updates on any card you gate on.

Pick the right gate. A user-owned checklist is for hand-offs where every item is genuinely required — the session (and Fleet's attention queue) treats the card as pending until all items are done. A confirmation or sign-off is not a checklist: push a decision card (Ratify/Decline — a Decline is what spawns the "why?" question, per the rejection protocol), or options when there are real alternatives. Never pad a checklist with filler items like "one more thing (leave a note)" — notes are always possible via the card's thread, and an untickable item keeps the session flagged in Fleet forever. An item that is truly optional gets optional: true; it stays clickable but never holds attention. When a gate has served its purpose, update it (all required items done) or delete it; user-owned gantt cards count as pending for as long as they exist, so hand them back (owner: "claude") or delete them when the hand-off ends.

Ratified gates. User-owned kanban and prototype cards close on an explicit Ratify button instead of board/dial state — the user decides when the arrangement or configuration is final. Ratifying freezes the card and answers it: the delta carries response.added with kind: "ratify" holding the final columns (kanban) or values (prototype). Gate on it with wait --card <id>, then read the response and apply the result. A claude-owned prototype is an example — playable, never pending.

The dual gantt uses payload.lanes: {claude: [tasks], user: [tasks]} — one chart, two lanes, the user lane toggleable.

The user's screenshots are yours to read

Attachments the user adds (📎 or clipboard paste) are stored in ~/.helix/canvas/<project>/<slug>/uploads/ and referenced as /api/upload/<file>. Read the file with the Read tool and look at it — users paste screenshots of visual bugs; diagnosing from the actual pixels is the highest-bandwidth feedback loop this system has. Reference the same uploads in gallery cards to show them back.

The wait loop (cursor discipline — CRITICAL)

Two symmetric failure modes, both real:

  • Skipping the past: --since from a push response silently drops events that arrived between your last drain and that push. Only use the cursor from your last drain or a fresh /api/state fetch.
  • Waiting for the past: a fresh state cursor sits after everything already logged — if the user answered while you were building, the wait never sees it. So when waiting on a specific card, always pass --card <id>: the server returns already-answered with the responses instantly if the log already holds them. When waiting generally, first inspect the /api/state snapshot for answers you haven't processed.

What counts as a human acting — and so what --wake-on user returns on — is user response.added (kind ≠ reaction), user card.created, user card.updated (gate ticks, board moves, lane toggles), and client.error. card.updated is in there deliberately: without it you would sit through answered gates.

During an active round, drain in the foreground (one Bash call holds ≤9 min):

${CLAUDE_PLUGIN_ROOT}/bin/helix canvas wait --canvas <slug> --since <cursor> --timeout 540 --wake-on user

--wake-on user does the holding: viewer churn, your own cards and reactions advance the cursor inside the call, and it returns only when a human actually acted. Exit 0 delta on stdout, 3 quiet, 5 server down — restart on the same port. Bake the slug and cursor in as literal values; the cursor is the one up printed, or the one your last wait returned.

  • Use printf '%s' when piping JSON in shell — zsh echo mangles \n.
  • Heredocs (<<'EOF') for push payloads, never echo.
  • Call the binary by its full path — a shell variable or function holding it stops the skill's pre-approval from matching, and every call prompts.
  • client.error events in the delta are app-side render failures — fix them; don't wait for the user to report.

Never end a turn without a wait running (ratified)

A canvas session has no "probably done". Gates the user hasn't ticked, threads they might reply in, notes they might drop at 2am — all of it must resume you. So the last tool call of every turn, for as long as the session is open, is an endless wait launched with run_in_background — even when nothing appears to be pending. When it exits you are re-invoked with the delta on stdout; drain, respond, and end the turn the same way.

# LAST tool call of the turn — Bash with run_in_background: true
${CLAUDE_PLUGIN_ROOT}/bin/helix canvas wait --canvas <slug> --since <cursor> --timeout 3600 --wake-on user

Exit 0 wakes you with the delta; 5 means the server died — restart it on the same port and re-arm. 3 is a quiet hour, not an ending: re-arm and carry on.

Rules:

  • Timeouts and retry counts bound a foreground drain, never the session. Quiet ≠ over. No-viewer ≠ over (the user reopens the tab and expects you live).
  • The slug and cursor must be baked in as literal values (background calls share no shell state, and a shell variable stops this skill's pre-approval from matching); the cursor is the one from your last drain.
  • The session ends only explicitly: the user says they're done (terminal or canvas), or ratifies a closing decision. Only then do the End-of- session steps and stop re-arming the wait.
  • If a turn ends while you still have canvas work of your own, the same rule applies — push a status card first, do the work, and re-arm.

The rejection protocol (ratified — never violate)

  1. The "why" question exists only once a Decline has landed. It goes out in the same push as your reaction to that verdict, edged declined — why? from the decision — never minutes later. Pushing it alongside the decision card, before any verdict, is a violation: it asks the user why they declined something they have not decided yet.
  2. Answered decision cards are spent. Re-deciding = push a retake decision, edge superseded by from the old one. Never edit a verdict.
  3. While building, drain and acknowledge between edits.
  4. Everything waiting on the user must be reachable from your turn; anything long-running on your side gets an in-progress card.

Card reference (push JSON on stdin)

Common: {"type", "threadId", "title"?, "payload"}. Unknown types render as a raw-payload fallback, so new types may be pushed before renderers exist.

typepayload
question{prompt} — free text, accepts multiple replies
options{prompt, options:[{id,label,description?}], mode?:"single"|"multi"} — user may attach a note
decision{topic, proposal} — explicit Ratify/Decline
markdown{markdown} (GFM: tables, fenced code)
note{markdown, attachments?} — user-authored; you may push them too
prompt{text} — the user's opening ask from a blank canvas (user-side)
diagram{mermaid} — renders serialized; errors report back as client.error
mindmap{tree:{label,children?[]}} — interactive: balanced two-side layout, click nodes to fold/unfold (+N badge); {mermaid} also accepted
gantt{sections:[{name,tasks:[{label,start,duration?|end?,done?,active?}]}]} or {lanes:{claude,user}} or {mermaid}; owner?
timeline{entries:[{period, items:[…]}]} or {mermaid}
kanban{columns:[{id,title,cards:[{id,label,body?,detail?}]}], owner?} — user-owned = drag & drop, pending until the user clicks Ratify (answers with kind: "ratify" + final columns). Each card may carry content the user opens in a centered dialog (sized to the type — compact for text, near-full-screen for visuals): body (markdown) or detail: {type, payload} embedding any display card type from this table (markdown, diagram, todo, code, table, image, …) — display only, never an interactive type
todo{items:[{id,label,state?:"pending"|"active"|"done",optional?}], owner?} — optional items never hold attention
chart{kind, labels?, datasets:[…], height?} — kinds: line, area, bar, hbar, stacked-bar, scatter, bubble, pie, doughnut, polar, radar; palette is built-in and CVD-validated
metric{tiles:[{label, value, unit?, delta?, tone?:"good"|"bad"|"neutral"}]} — KPI tile row
table{columns:[...], rows:[[...]]}
code{code, language?}
image{src} (http/data) or {path} (absolute file)
gallery{images:[{src|path, caption?}]} — first image is the hero
video{src|path, poster?} — local files served with range support
html{html, height?} — sandboxed iframe, scripts allowed
prototype{source, props?, values?, height?} — live React prototype with a Dials tab. source: JSX module that default-exports one component; only react is importable; dial values arrive as props (theme tokens like var(--card), var(--ink), var(--iris) work in styles). props declares the dials, DialKit grammar: n (inferred slider), [def,min,max,step?], true/false (toggle), "#hex" (color, 8-digit keeps alpha), {type:"select",options}, {type:"text"}, {type:"action"} (click count arrives as a prop under its name), nested object = folder (_collapsed?). The user tunes in the opened card's Dials tab; tuned values come back on the card via card.updated. owner?: user makes it a task — pending until the user clicks Ratify, which answers the card with kind: "ratify" + final values; default (claude) is an example, playable but never pending — read the ratified values and apply to the real code
file-window{path} — live view, line comments, md preview/raw
artifact{path, status?:"forming"|"stable"|"done"} — the deliverable; write the file with Write/Edit, the canvas streams it; modes preview/raw/meta + download
weave{text?} — renders this canvas's event log as the double helix; Claude's reflection card
diff{diff, path?} — unified-diff string, GitHub-style render; lines clickable for review comments (kind comment + line index in your delta). Review = diff card + line comments + decision card
terminal{output, command?, exitCode?} — console-styled command transcript
test-report{suites:[{name,tests:[{name,status:"pass"|"fail"|"skip",message?,duration?}]}]}
api{method, url, status?, duration?, requestBody?, responseBody?} — request/response pair
commit{message, hash?, author?, stats?:{additions,deletions,files}, files?:[{path,additions?,deletions?}]}
compare{left:{title}, right:{title}, rows:[{label,left,right,winner?:"left"|"right"}]}
comment{cardId, text, attachments?} — reply in a card's thread (no threadId)
edge{from, to, label?} — arrows; always link follow-up cards to what prompted them
status{text} — not a card: updates the agent-status pill
delete{cardId} — removes a card

Update = push the same JSON with the existing id.

Artifacts & graduation

Content starts as ordinary cards; when a decision stabilizes it, promote: push an artifact card bound to the repo path, edges from the source decisions (written into), keep the file written through with Write/Edit, and advance status forming → stable → done via update pushes. Graduation = every artifact card done; announce it and update the session's memory.

Server operations

  • A stale server restarts on the same port when you run ${CLAUDE_PLUGIN_ROOT}/bin/helix canvas up again; open tabs reconnect. Manual equivalent: kill the pid in server.json, relaunch with --port <same port>.
  • Kill by pid, never by name. pkill -f "helix canvas serve" matches every canvas server on the machine — other projects, other people's sessions, runs in the middle of their work. It looks local and is not. Read the pid instead: kill "$(sed -n 's/.*"pid": *\([0-9]*\).*/\1/p' ~/.helix/canvas/<project>/<slug>/server.json)".
  • The event log survives restarts; state is never in memory only.

What the app gives the user (so you can point them at it)

Amber N waiting on you chip → ● your turn: every card blocking the session, oldest first, each headed by the action it needs (Answer, Choose, Ratify or decline, Tick N items) and answerable in place; ⌖ jumps to the card on the canvas (jumping auto-unfolds whatever hides the target). Empty it reads Nothing is waiting on you — which is the answer when a person asks why you are still waiting. ▦ index → every card, findable: tabs by type group plus artifacts and attachments, search, stage/status filters, pagination; row click opens the card's full preview, ⌖ jumps back to the canvas focused on it. ◷ timeline → the session in order: cards added, answers, ratifications, replies and status changes, live-updating, filterable by actor, searchable; row click previews the card, ⌖ jumps to the canvas. ✓ decisions → the register: every decision card as a numbered entry (D1…) with verdict and timestamp, filterable by open/ratified/declined, searchable; retakes nest their superseded ancestors, declined entries link their "why" question. ☐ work → who is blocking whom: two columns — everything waiting on the user (open questions/options/decisions, gates with required items left) and everything Claude has committed to (open todos with progress, boards in play, artifacts still forming, the current status); the toolbar filters pending / done / all. ▣ stages → per-thread show/hide, collapse (collapsed stages become summary cards that re-anchor crossing edges), and ⤢ solo view. compact folds answered cards; arrange re-stacks columns; theme toggle; ⤢ maximize on every card (full mode + thread sidebar); 💬 threads (resizable panels); ⊘ hide + master reveal; double-click empty canvas → note editor (📎 + clipboard paste); reactions; activity toasts bottom-center; drag anything — user positions always win.

End of session

Update the project's memory file with: canvas slug, port, cursor reached, open blockers, ratified decisions, artifact statuses, and gate states. Before stopping, clear spent gates — every user-owned todo/kanban/gantt that is no longer waiting on the user gets updated to done or deleted, so the session drops out of Fleet's attention queue.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.