JSON Canvas
heyitsnoah/claudesidian
Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections.
Author animated technical explainer diagrams as .anim.json files for Nimbalyst's Animation editor.
$ npx skills add nimbalyst/nimbalyst --skill animation -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install nimbalyst/nimbalyst animation --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/nimbalyst/nimbalyst.git skills-src && mkdir -p .claude/skills && cp -r skills-src/packages/extensions/animation/claude-plugin/skills/animation .claude/skills/animation && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "animation" agent skill from https://github.com/nimbalyst/nimbalyst/tree/main/packages/extensions/animation/claude-plugin/skills/animation into .claude/skills/animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "animation", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/nimbalyst/nimbalyst/tree/main/packages/extensions/animation/claude-plugin/skills/animationType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add nimbalyst/nimbalyst --skill animation -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install nimbalyst/nimbalyst animation --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nimbalyst/nimbalyst.git skills-src && mkdir -p .agents/skills && cp -r skills-src/packages/extensions/animation/claude-plugin/skills/animation .agents/skills/animation && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "animation" agent skill from https://github.com/nimbalyst/nimbalyst/tree/main/packages/extensions/animation/claude-plugin/skills/animation into .agents/skills/animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "animation", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add nimbalyst/nimbalyst --skill animation -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install nimbalyst/nimbalyst animation --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nimbalyst/nimbalyst.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/packages/extensions/animation/claude-plugin/skills/animation .cursor/skills/animation && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "animation" agent skill from https://github.com/nimbalyst/nimbalyst/tree/main/packages/extensions/animation/claude-plugin/skills/animation into .cursor/skills/animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "animation", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/nimbalyst/nimbalyst.git --path packages/extensions/animation/claude-plugin/skills/animation--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add nimbalyst/nimbalyst --skill animation -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install nimbalyst/nimbalyst animation --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nimbalyst/nimbalyst.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/packages/extensions/animation/claude-plugin/skills/animation .gemini/skills/animation && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "animation" agent skill from https://github.com/nimbalyst/nimbalyst/tree/main/packages/extensions/animation/claude-plugin/skills/animation into .gemini/skills/animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "animation", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install nimbalyst/nimbalyst animationInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add nimbalyst/nimbalyst --skill animation -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/nimbalyst/nimbalyst.git skills-src && mkdir -p .github/skills && cp -r skills-src/packages/extensions/animation/claude-plugin/skills/animation .github/skills/animation && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "animation" agent skill from https://github.com/nimbalyst/nimbalyst/tree/main/packages/extensions/animation/claude-plugin/skills/animation into .github/skills/animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "animation", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add nimbalyst/nimbalyst --skill animation -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install nimbalyst/nimbalyst animation --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/nimbalyst/nimbalyst.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/packages/extensions/animation/claude-plugin/skills/animation .opencode/skills/animation && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "animation" agent skill from https://github.com/nimbalyst/nimbalyst/tree/main/packages/extensions/animation/claude-plugin/skills/animation into .opencode/skills/animation/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "animation", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
animationAuthor animated technical explainer diagrams as .anim.json files for Nimbalyst's Animation editor.
Animation is an agent skill from nimbalyst/nimbalyst. Author animated technical explainer diagrams as .anim.json files for Nimbalyst's Animation editor. Use when the user wants to animate a diagram, show how a system/protocol/algorithm behaves over time, build a motion explainer, or turn a static architecture diagram into something that plays.
Its SKILL.md is about 7.8k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development, covering Diagrams. The repository describes itself as: Nimbalyst - The open-source visual workspace for Claude Code, Codex, and OpenCode. Run multiple coding agents in parallel, edit their work visually in markdown, mockups, and… The licence is MIT.
3 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit a3dbdb4. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
nodeFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Animation loads about 7.8k tokens when it runs. Until then it costs about 75 tokens; SKILL.md has 4,158 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from nimbalyst/nimbalyst at commit a3dbdb4, republished under its MIT licence (© nimbalyst). 4,158 words, ~7,845 tokens.
.claude/skills/animation/SKILL.md (or your agent's skills folder)..anim.json files open in Nimbalyst's Animation editor: a named scene plus an ordered list of steps that assign states to the scene's parts. You write plain JSON. Any agent can author or edit one with Write and Edit -- there is no binary format and no tool call required.
Do not use it when a static diagram says the same thing. If nothing changes between the first frame and the last, you want Excalidraw or a Mermaid block, not an animation.
Three rules drive every decision in this format:
store, title-card, queueTask01. You will reference them constantly in set blocks; make them readable.Times are integer milliseconds. Never frame indices, never floats.
{
"version": 1,
"stage": { "width": 1200, "height": 640, "fps": 25 },
"parts": { "<id>": { "type": "node" | "edge" | "label" | "shape", ... } },
"steps": [ { "id": "...", "duration": 800, "caption": "...", "set": { ... } } ]
}| Field | Notes |
|---|---|
width, height | Clamped to 16..8192. The stage scales to fit the pane, so these set the aspect ratio and the coordinate system, not the pixel size. |
fps | Only affects frame snapping and the readout. Use 25 unless you have a reason. Whole-millisecond frame rates: 10, 20, 25, 50. |
background | Optional override. Omit it and the stage uses the theme surface, which is what you want -- the scene then follows the user's light/dark theme. |
1200x640 is a good default. Landscape, room for a header row and a bottom rail.
Part ids are the keys. All four types share label, tone, and state (their baseline, before any step runs).
node -- the workhorse. A titled card with an optional subtitle and key/value rows.
{ "type": "node", "label": "Object store", "x": 740, "y": 118, "w": 240, "h": 176,
"subtitle": "SHA -> BYTES",
"rows": [ { "key": "f7a9", "value": "commit 182 B" }, { "key": "e816" } ] }label is uppercased automatically. Write "Merge gate", it renders MERGE GATE. Falls back to the id.subtitle is a small mono line under the header. Keep it short and caps-ish; it is where model names, worktrees, and units go.rows render as boxed key/value pairs. value is optional. Key is left-aligned, value right-aligned.edge -- a line between two parts, optionally carrying packets.
{ "type": "edge", "from": "client", "to": "store", "text": "GET <sha>", "packets": 4 }from/to are part ids. Drawn centre-to-centre and trimmed to the box borders, so stacked and side-by-side both look right.from/to renders nothing at all -- it looks like a broken renderer, not a broken document. Check your ids.packets is how many squares travel the line while it is flowing (default 3). Set 0 for an edge that means a relationship rather than traffic. One trip takes 1.6s.text draws a caption at the midpoint on an opaque background plate roughly max(40, len × 7.6 + 16) px wide. It will punch a hole through anything behind it. Only put text on an edge whose gap is wider than the plate.label -- free-standing text.
{ "type": "label", "x": 56, "y": 48, "text": "COMMIT DAG", "align": "start", "caps": true }align: start | middle | end (the anchor, at x).caps: true gives the faint, tracked-out micro-caption style used for section headings.caps, tone, and position instead.shape -- a plain rect or circle, with optional centered text.
{ "type": "shape", "shape": "rect", "x": 89, "y": 438, "w": 40, "h": 18, "tone": "accent" }Shapes are how you show quantity, because text never changes (see Hard constraints). A grid of small shapes that go hidden one group at a time is a queue draining, a battery discharging, a work list being claimed.
html -- freeform markup, for the things the primitives above cannot express: real typography, a type scale, flow layout, a UI that has to look like a real product rather than like a diagram of it.
The markup comes from one of two places:
{ "type": "html", "x": 55, "y": 108, "w": 1090, "h": 534,
"htmlFile": "./partials/app-window.html",
"vars": { "title": "acme-api", "branch": "main" } }htmlFile is a path to a .html file next to the document. Relative only; .. is allowed, absolute is refused.html is markup inline. Right for a few lines, wrong for a widget.vars fills {{name}} placeholders in whichever source won. Values are HTML-escaped, so a var is text and cannot change the structure of the partial it lands in. An unfilled placeholder resolves to empty, never to its own name. That is the entire template language -- no conditionals, no loops, no expressions.
Three things about html parts that will otherwise cost you a build:
--anim-tone inherits into the markup. Give a border, a badge, a button color: var(--anim-tone) plus a transition, and a step's tone change animates the whole widget. This is how you avoid a second copy of the markup for a second state.<style> and non-https/data:image urls are stripped at the render boundary. Inline style= attributes are fine.html part hides every edge underneath it. Edges are drawn before all non-edge parts, so a background on a container is enough to make a nested diagram's lines vanish with no error. Keep containers transparent and set stage.background to supply the surface colour instead.stage may carry a stamped palette that every renderer reads:
"stage": { "width": 1200, "height": 675, "fps": 25, "theme": {
"bg": "#1c1c1c", "border": "#383838", "accent": "#3b82f6",
"--nim-panel": "#2f2f2f"
} }Keys naming a stage token (bg, surface, surfaceRaised, border, borderStrong, text, textMuted, textFaint, accent, success, warning, error, purple) override that token. Keys of the form --some-name are emitted as extra custom properties, which is how a project carries its own vocabulary into the stage. Anything else is dropped.
Values, not a theme name: no renderer then needs a theme registry, and the extension stays neutral about whose product this is. The cost is that editing a theme in the app does not reach existing documents until they are restamped.
Write markup against token names, not literal hex. var(--anim-border) rather than #383838 means switching palettes is a one-field edit with nothing to redraw. Reserve literal colours for things that genuinely are fixed -- macOS traffic lights.
Note that stage.background, if set, still wins over theme.bg.
Markup inside one html part can declare regions a step addresses individually:
<div class="anim-subpart" data-part="chrome/session-a">…</div>A step then writes "set": { "chrome/session-a": { "tone": "success" } }, and the stage drives it with the same querySelector + setAttribute it uses for a top-level part. Declare each one in the part's subParts map so the baseline resolver knows it exists:
"subParts": { "session-a": { "tone": "accent" }, "session-b": {} }Two things to know:
anim-subpart, never a nested anim-part. .anim-part sets --anim-tone to neutral unconditionally, so a nested one resets to grey instead of inheriting its container's tone.neutral means "inherit", not "grey". That is what lets a whole window go accent without every region inside it snapping back.Writing data-part by hand is fine for two or three regions. Past that, the markup is worth compiling from a component -- see the compiler below.
anim-spin)The stage ships one rotating primitive for html parts. Give any element class="anim-spin" and it becomes a lit ring turning at ~0.8s -- a running or loading indicator that reads as live rather than a static glyph. It is the rotational counterpart to the edge packet, and the only self-driven rotation the format has.
<span class="anim-spin" style="width:8px;height:8px"></span>currentColor, so whatever wraps it sets the hue -- drop it inside a blue "running" pill and the ring is blue, no extra styling.set; it is a CSS animation, not a state. To make it stop, hide the part or sub-part it lives in on the step the work finishes (put it in its own anim-subpart if only the spinner should disappear).prefers-reduced-motion, and both recorders capture it exactly as they capture packets -- so it survives export_html and export_gif.Reach for it wherever the real UI shows a spinner: an agent session mid-run, a build in progress, a request in flight. It is the honest way to show "this is working right now" without faking motion the format cannot do.
Nothing product-specific ships with this extension, and that is deliberate: the markup worth reusing is always your product, so anything bundled here could only ever be somebody else's app.
Instead, build a small kit once and reuse it across every animation you make:
docs/animations/
partials/
app-window.html <- your chrome: title bar, sidebar, main pane
list-row.html <- one row, used eight times with different vars
toolbar.html
onboarding.anim.json
sync-explainer.anim.jsonThree rules make a kit that lasts:
.html partial has no way to declare one, so with partials a list's eight resting rows are one file and the row that turns green is its own part with its own coordinates.A partial has no defaults -- a var you leave out renders as empty, not as an error -- so document the vars each one expects at the top of the file in a comment.
vars is deliberately not a template language: no conditionals, no loops, no types, no defaults. When that starts to hurt -- a list whose length varies, a phase colour repeated across fifty documents, a region you want a step to address -- author the markup as a .tsx component instead and compile it into the part:
{ "type": "html", "x": 55, "y": 108, "w": 1090, "h": 534,
"component": "./components/DesktopWindow.tsx",
"props": { "sessions": [ { "id": "sync", "title": "Sync explainer", "phase": "implementing" } ] } }node packages/extensions/animation/tools/anim-compile.mjs docs/onboarding.anim.json [--theme dark] [--check]The compiler renders the component with those props and writes three fields back: html (the markup, sanitizer-clean), subParts (the regions the markup actually declared) and build (hashes of the props and of the component sources). Nothing renders the component at play time. Every consumer keeps drawing the same static string, which is the only reason they agree frame for frame -- and one of them, the offscreen screenshot host, has no filesystem to resolve a component from at all.
What you get that vars cannot give you: real conditionals, typed props (a mistyped phase name is an error before anything renders), shared defaults, children for static panes, and AnimPart -- a wrapper that declares a step-addressable region from the data it was handed, so twelve kanban cards need no coordinates and a thirteenth needs no edit to the other twelve.
It lints as it goes and refuses to write on a failure: markup that the sanitizer would alter, a step addressing a part or sub-part that does not exist, components that do not typecheck. It warns about an opaque container root and about a part sized away from the component's declared size.
Constraints worth knowing before you reach for it:
sessions[0].id rewires the steps addressing it. The step-target lint turns that into an error rather than a region that silently stops animating.subParts is generated. The compiler rewrites it from the markup on every run, so edit the component, not the map. A label you add by hand survives as long as the region does.htmlFile and inline html need none of this and are unchanged.neutral accent data success warning error muted
They map to theme tokens, so they follow the user's theme: accent is blue, data purple, success green, warning amber, error red, neutral/muted a faint grey. Assign them semantically and keep the meaning fixed for the whole animation -- if amber means "under review" in step 4 it cannot mean "slow" in step 7.
The state vocabulary is defined by the stylesheet, not the schema. Any other string parses fine and renders as idle, so a typo fails silently.
| Type | States |
|---|---|
node | idle (default), active (tinted fill, tone border, status dot, first row highlighted), waiting (dashed amber border), offline (dashed red border, dimmed title), hidden |
edge | idle (default, dashed grey), flowing (line fills in, packets travel from -> to), returning (same, packets travel backwards), active (same as flowing), hidden |
label | idle (default), active (takes its tone colour), hidden |
shape | idle (default, 14% tone fill), active (65% tone fill, reads as solid), hidden |
returning is the most useful state in the set and the most under-used: a reply, a rejection, a rollback travelling back down the same wire. Reversing packets beats drawing a second edge -- two overlapping edges between one pair of nodes look like a rendering fault.
{ "id": "response", "duration": 1000,
"caption": "The objects come back down the same wire.",
"set": { "fetch": { "state": "returning", "tone": "success" } } }id is a readable slug, unique. It shows in the step strip.duration is how long this step holds before the next begins, in ms (1..600000; the editor's drag-to-retime floor is 40ms).caption is one sentence of narration. Read end to end, the captions should form a coherent paragraph -- they are also the animation's accessibility description.set maps part id -> { state?, tone? }. Omit either and it inherits.Playback loops by default, and the wrap is a hard cut: the stage jumps from your last step straight to the baseline-plus-first-step. Design that cut deliberately -- it should read as a reset, not as a glitch.
Layout is hand-placed, so these numbers matter.
Node internals. Header is 34px. The subtitle baseline sits at y+56. Rows start at y+72 (or y+56 with no subtitle), each row 26px tall with a 6px gap -- 32px of pitch. A row is dropped if it would come within 6px of the bottom.
Node height:
h = 32 × rows + 80with a subtitle,h = 32 × rows + 64without. Three rows plus a subtitle ->h = 176. Going much taller leaves a visible dead band under the last row.
Row text is 11px mono: key at x+28, value right-aligned at x + w − 28. At w = 228 you have room for roughly a 6-character key and a 12-character value.
Edges. Keep every edge between adjacent boxes. An edge routes as a straight centre-to-centre line trimmed to the borders, so a diagonal across a grid will run straight through the cards in between. If two boxes you want to connect are not neighbours, move them.
Leave the gap wide enough for what the edge carries: ~40px for a bare edge with packets, ~70px if it has text.
Layering. Edges draw first (behind everything), then all other parts in alphabetical id order. That is the only layering control there is. To draw a part on top of another, give it an id that sorts later: queue (the panel) then queueTask01..queueTask12 (the chiclets inside it).
The editor rewrites the file on save with a fixed key order. Hand-write it in canonical order or your first save will reformat the whole file and bury the real edit in the diff.
version, stage, parts, stepsstage: width, height, fps, backgroundparts: sorted alphabetically by id. Within a part: type, label, tone, state, thenx, y, w, h, subtitle, rowsfrom, to, text, packetsx, y, text, align, capsx, y, w, h, shape, textsteps: document order -- it is the animation. Within a step: id, duration, caption, set. set keys sorted alphabetically; each assignment state then tone.Unknown keys are preserved and written after the known ones in sorted order, so a field this build does not model still round-trips.
Design around these; they are not bugs to work around.
label, text, subtitle, or rows can differ between steps. A counter that ticks 36 -> 24 -> 12 is impossible. Show quantity with shapes going hidden, and write static captions that stay true for the whole run ("CLAIMED IN ORDER", not "16 REMAINING").x/y are fixed. Parts appear, disappear, and change colour; they do not travel. The only continuous motion in the format is edge packets and the anim-spin spinner (see sub-parts). Nothing else rotates, slides, or eases.Structure
Layout
Motion
idle once their traffic is done.waiting and returning for it. An explainer that only shows success explains nothing.Colour
neutral/muted do real work. Contrast comes from what is dim.<name>.anim.json, open it in Nimbalyst, and capture the stage with mcp__nimbalyst__capture_editor_screenshot. Scrub to a few different steps and capture each. Do not skip this -- coordinate arithmetic is exactly the kind of thing that is right in your head and wrong on screen.If the user gave you a style reference image, match its vocabulary -- caps micro-labels, card density, how much is dim at rest - rather than trying to reproduce it pixel for pixel. Say plainly which parts of it the format cannot express.
animation.export_html writes a .anim.json out as a self-contained HTML file that plays and loops on its own, with no external references. Point it at a path; it does not need the file open in an editor.
animation.export_html { filePath: "docs/cache.anim.json" }
-> docs/cache.htmlPass outputPath to put it somewhere else. It refuses to export a document with parse errors, and returns any warnings alongside the result. Clicking the exported page pauses it.
animation.export_gif writes an animated GIF instead, for destinations that cannot run a web page: a GitHub issue, a README, a chat message, a slide.
animation.export_gif { filePath: "docs/cache.anim.json", fps: 12, maxWidth: 720 }
-> docs/cache.gifPrefer export_html whenever the destination can display a web page. The GIF is recorded by playing the animation in an offscreen window and capturing it in real time, so it takes about as long as the animation runs, it is capped at 256 colours, and it is one to two orders of magnitude larger than the HTML. The HTML is instant, sharp at any size, and a few tens of kilobytes.
All four renderers -- the editor preview, export_html, both recorders -- use the palette in stage.theme, or a fixed dark fallback when the document names none. They agree by construction. This used to be untrue: the preview read the app's live theme while every export hardcoded the fallback, so the same document was two different colours depending on who asked, and the authoring advice worked around it by telling you to hardcode your palette. That advice is withdrawn.
Keep GIFs small: fps 8-12 is plenty for this kind of diagram, and maxWidth 720 is usually enough. Both file size and recording memory scale with the square of the width.
There is no video export. If someone wants MP4, say so rather than implying the GIF is a substitute.
To present a finished animation to someone inside Nimbalyst you do not export at all. A .anim.json has a registered custom editor, so a link to it alone on its own paragraph in a markdown document upgrades into the live, playing embed:
[Session kanban](/abs/path/board.anim.json "width=1040 height=585")That is the right way to hand over an existing animation -- it is live and full-fidelity, where a screenshot is frozen and (for an animation) misleading. Two things decide whether it works:
.md document and open it. Absolute paths work; the file may be anywhere on disk.When you author the .anim.json yourself, you do not need the link at all. This editor sets supportsTranscriptEmbed in its manifest, so the moment your Write/Edit creates or changes a .anim.json, the transcript renders it inline in the tool card as a live, click-to-activate stage. That is the meta payoff -- the agent writes the scene and it plays right there in the session. (This is a per-editor opt-in; editors that don't set the flag show a plain file row.)
capture_editor_screenshot is for your iteration in the workflow above (checking coordinates), not for presenting the finished piece -- present that with the inline embed.
Worth knowing, because it rules out the obvious shortcut: interpolation is CSS transitions and the packets are CSS animations, so writing data-state into a series of snapshots and stitching them gives a stepped slideshow with motionless packets. Any frame-based output has to drive a real browser through real playback. Do not build a frame stitcher.
| Symptom | Cause |
|---|---|
| An edge is invisible | from/to names a part that does not exist. Dangling edges render as nothing. |
| A row is missing from a node | h is too small. h = 32 × rows + 80 with a subtitle. |
| A state does nothing | Typo. Unknown state strings parse fine and render as idle. |
| An edge label sits on top of a card | The gap is narrower than the label plate. Widen the gap or drop the text. |
| A part is hidden behind another | Alphabetical draw order. Rename it to sort later. |
| Something stays lit forever | Cumulative states. You never set it back to idle. |
| The whole file reformats on first save | It was not written in canonical order. |
| A line crosses a card | The two boxes are not adjacent. Move them, do not fight the router. |
A complete, canonical, three-part file. Copy it and grow it.
{
"version": 1,
"stage": {
"width": 1080,
"height": 420,
"fps": 25
},
"parts": {
"caption": {
"type": "label",
"x": 80,
"y": 48,
"text": "READ THROUGH CACHE",
"caps": true
},
"cache": {
"type": "node",
"label": "Cache",
"x": 420,
"y": 130,
"w": 240,
"h": 176,
"subtitle": "LRU / 512 MB",
"rows": [
{ "key": "hit", "value": "0.4 ms" },
{ "key": "miss", "value": "18 ms" },
{ "key": "keys", "value": "12.4 K" }
]
},
"client": {
"type": "node",
"label": "Client",
"x": 80,
"y": 130,
"w": 220,
"h": 176,
"subtitle": "GET /user/42",
"rows": [
{ "key": "attempt", "value": "1" },
{ "key": "budget", "value": "50 ms" },
{ "key": "result" }
]
},
"fetch": {
"type": "edge",
"from": "client",
"to": "cache",
"text": "get",
"packets": 3
},
"load": {
"type": "edge",
"from": "cache",
"to": "origin",
"packets": 3
},
"origin": {
"type": "node",
"label": "Origin",
"x": 780,
"y": 130,
"w": 220,
"h": 176,
"subtitle": "POSTGRES",
"rows": [
{ "key": "rows", "value": "1" },
{ "key": "cost", "value": "18 ms" },
{ "key": "load", "value": "moderate" }
]
}
},
"steps": [
{
"id": "ask",
"duration": 800,
"caption": "The client asks the cache for a key.",
"set": {
"client": { "state": "active", "tone": "accent" },
"fetch": { "state": "flowing", "tone": "accent" }
}
},
{
"id": "miss",
"duration": 900,
"caption": "It is not there, so the client waits.",
"set": {
"cache": { "state": "active", "tone": "warning" },
"client": { "state": "waiting", "tone": "warning" },
"fetch": { "state": "idle" }
}
},
{
"id": "load",
"duration": 1100,
"caption": "The cache reads through to the origin.",
"set": {
"load": { "state": "flowing", "tone": "data" },
"origin": { "state": "active", "tone": "data" }
}
},
{
"id": "fill",
"duration": 900,
"caption": "The row comes back and the entry is filled.",
"set": {
"cache": { "state": "active", "tone": "success" },
"load": { "state": "returning", "tone": "success" }
}
},
{
"id": "serve",
"duration": 1000,
"caption": "The client gets its answer; the next read will hit.",
"set": {
"client": { "state": "active", "tone": "success" },
"fetch": { "state": "returning", "tone": "success" },
"load": { "state": "idle" },
"origin": { "state": "idle", "tone": "neutral" }
}
}
]
}Note what it does: starts quiet, holds longest on the beat that costs 18ms, uses waiting for the stall and returning for both replies, sets fetch and load back to idle once they are done, and ends on one tone with the origin dimmed back out.
If you are working in the Nimbalyst repo itself, packages/extensions/animation/samples/ has two longer references: git-storage.anim.json (a request crossing a network) and meta-agent-sessions.anim.json (a grid of cards, a draining queue, and a review that sends work back).
© nimbalyst, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in packages/extensions/animation/claude-plugin/skills/animation of nimbalyst/nimbalyst.
Open the folder on GitHubat commit a3dbdb4
Animation next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Animation this skillnimbalyst/nimbalyst | 1.8k | — | ~7.8k | Automated safety check: Pass | MIT | |
| JSON Canvasheyitsnoah/claudesidian | 2.6k | 18 repos | ~3.5k | Automated safety check: Pass | MIT | |
| Archify Diagramstt-a1i/archify | 79k | — | ~2.9k | Automated safety check: Pass | MIT | |
| Diagram Designcathrynlavery/diagram-design | 44k | 1 repos | ~7.5k | Automated safety check: Pass | MIT | |
| Fireworks Tech Graphtisfeng/Easydict | 15k | 1 repos | ~1.4k | Automated safety check: Pass | MIT | |
| Excalidraw Diagramcoleam00/excalidraw-diagram-skill | 4.9k | 2 repos | ~6.1k | Automated safety check: Pass | None |
heyitsnoah/claudesidian
Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections.
tt-a1i/archify
Creates interactive architecture, workflow, sequence, data-flow and lifecycle diagrams as standalone HTML with inline SVG, themes and image or video export.
cathrynlavery/diagram-design
Creates branded diagrams, from architecture, flowchart and sequence to charts and maps, as self-contained HTML with inline SVG, with import from draw.io, Mermaid and Excalidraw.
tisfeng/Easydict
Create precise SVG technical diagrams, export PNG or offline HTML, and animate supported semantic SVGs to GIF.
coleam00/excalidraw-diagram-skill
Create Excalidraw diagram JSON files that make visual arguments.
Agents365-ai/drawio-skill
Creates and edits editable draw.io diagrams from descriptions, code, infrastructure files, SQL and API schemas, with sync, review, test and export tools.
nimbalyst/nimbalyst
Write a project's knowledge pages in Nimbalyst Pages -- record what people said and decided in the page it affects, keep typed pages for the things the team tracks (its own types, such as modules…
nimbalyst/nimbalyst
Set up or check a project's knowledge pages in Nimbalyst Pages -- install the editable "How we write this wiki" guide page, define the team's own page types (and subtypes) and the named relations…
nimbalyst/nimbalyst
Author Nimbalyst Project Canvas boards (.canvas files) — an infinite canvas whose cards are live editors for real workspace files and shared documents, arranged spatially and wired with edges.
nimbalyst/nimbalyst
Create visual data models for database schemas using Nimbalyst's DataModelLM editor.
nimbalyst/nimbalyst
Create diagrams and visual drawings using Excalidraw (.excalidraw files).
nimbalyst/nimbalyst
Build, install, and hot-reload Nimbalyst extensions using MCP tools.
Categories
Author animated technical explainer diagrams as .anim.json files for Nimbalyst's Animation editor. Animation is an agent skill from nimbalyst/nimbalyst.json files for Nimbalyst's Animation editor.
Animation fits situations like: the user wants to animate a diagram; show how a system/protocol/algorithm behaves over time; build a motion explainer; turn a static architecture diagram into something that plays.
Run `npx skills add nimbalyst/nimbalyst --skill animation -a claude-code`. Or copy the skill folder (packages/extensions/animation/claude-plugin/skills/animation in nimbalyst/nimbalyst) into .claude/skills/animation in your project. Claude Code loads it when a task matches its description.
Run `npx skills add nimbalyst/nimbalyst --skill animation -a codex`. Or copy the skill folder (packages/extensions/animation/claude-plugin/skills/animation in nimbalyst/nimbalyst) into .agents/skills/animation in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add nimbalyst/nimbalyst --skill animation -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/animation, .gemini/skills/animation, .github/skills/animation and .opencode/skills/animation in your project.
Going by SKILL.md and its folder, Animation needs the command-line tools its instructions call (node).
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Animation is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.8k tokens (SKILL.md is roughly 31k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Animation: JSON Canvas (heyitsnoah/claudesidian, 2.6k stars), Archify Diagrams (tt-a1i/archify, 79k stars), Diagram Design (cathrynlavery/diagram-design, 44k stars) and Fireworks Tech Graph (tisfeng/Easydict, 15k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
nimbalyst (a GitHub organization) maintains it in nimbalyst/nimbalyst, which has 1,847 GitHub stars. The repository holds 14 skills in this directory. The repository was last updated on October 5, 2026.
Source: nimbalyst/nimbalyst on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.