tldraw-offline

Drive and script tldraw offline canvases with an agent.

  • tldraw
  • canvas
  • whiteboard
  • document-script
  • diagramming

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/optional-skills/creative/tldraw-offline'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

scripts/main.js

scripts/main.jsBrowse 4 files
View on GitHub
← Back to SKILL.md
// tldraw offline — document script (script/main.js)//// A document script's default export receives a ctx object and runs whenever the// document loads (and reruns when you save the script). Contract, verified against// the app's bundled script-context.d.ts:////   export default function ({ editor, helpers, signal }) { ... }////   editor   — the live tldraw Editor for this document//   helpers  — editor-bound conveniences: createShapeIfMissing, createShapesIfMissing,//              createArrowBetweenShapes, translateShapes, onShapeTranslate,//              richTextToPlainText, boxShapes, getLints//   signal   — an AbortSignal fired before the script reruns and when the board//              closes. Register ALL cleanup on it (this is how you avoid leaks).//// Pure tldraw primitives (createShapeId, toRichText, Vec, ...) are imported from// the `tldraw` app module — NOT globals. react / react-dom are also importable.// It is not a Node project; only those modules are available. import { createShapeId, toRichText } from 'tldraw' export default function ({ editor, helpers, signal }) {	const { createShapeIfMissing, createArrowBetweenShapes } = helpers 	// --- 1. Build durable "furniture" idempotently (stable ids, create-if-missing).	// Re-running the script must NOT duplicate or clobber user edits.	const nodes = [		{ id: createShapeId('node-ui'), x: 0, y: 0, color: 'blue', label: 'CLI / Gateway' },		{ id: createShapeId('node-core'), x: 280, y: 0, color: 'violet', label: 'Agent Core' },		{ id: createShapeId('node-tools'), x: 560, y: 0, color: 'green', label: 'Tools' },	] 	editor.run(() => {		for (const n of nodes) {			createShapeIfMissing({				id: n.id,				type: 'geo',				x: n.x,				y: n.y,				props: {					geo: 'rectangle',					w: 220,					h: 110,					color: n.color,					fill: 'solid',					richText: toRichText(n.label),				},			})		}	}) 	// Connect them (arrows bind to the shapes, so they follow when moved).	createArrowBetweenShapes(nodes[0].id, nodes[1].id, { arrowheadEnd: 'arrow' })	createArrowBetweenShapes(nodes[1].id, nodes[2].id, { arrowheadEnd: 'arrow' }) 	// --- 2. Add reactive behavior: recolor the last node based on arrow count.	// store.listen fires on the tick AFTER a commit — never read state you just	// wrote synchronously and expect the listener to have run yet.	const targetId = nodes[2].id	function update() {		const hasArrows = editor.getCurrentPageShapes().some((s) => s.type === 'arrow')		editor.run(			() =>				editor.updateShape({					id: targetId,					type: 'geo',					props: { fill: hasArrows ? 'solid' : 'none' },				}),			{ history: 'ignore' } // keep script-owned writes out of the user's undo stack		)	} 	const stop = editor.store.listen(update)	signal.addEventListener('abort', () => stop()) // <-- the one required cleanup	update() // run once on load 	editor.zoomToFit({ animation: { duration: 200 } })} 
Referenced from SKILL.md