mcerqua/openvoiceui/threejs-canvas-integration
Claude Code and agent skill by mcerqua/openvoiceui
- community
- ext:[{"type":"plugin","description":"Custom plugin integration"}]
Checking that the SKILL.md file is still in the repository.
Build Three.js / WebGL games and 3D scenes as OpenVoiceUI canvas pages - jsdelivr import map, full-viewport shell, same-origin asset loading, server-side save state, and the postMessage bridge to the voice agent. Load before writing any 3D canvas page.
Install
npx vskill@latest install mcerqua/openvoiceui/threejs-canvas-integrationSemi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard
SKILL.md
Three.js on OpenVoiceUI canvas pages
A 3D game is an ordinary canvas page: one HTML file in the canvas-pages folder, opened with [CANVAS:<page-id>]. The general write / open / verify / lint workflow is in the canvas-pages skill; this skill covers what is specific to Three.js.
Examples use http://localhost:5001; if you changed PORT, use that port. API calls send -H "X-Agent-Key: ${AGENT_API_KEY:-}", which only matters when login is required and AGENT_API_KEY is set.
| What | Docker path (agent side) | Browser URL | |
|---|---|---|---|
| Page | /app/runtime/canvas-pages/<game>.html (also mounted at ~/.openclaw/workspace/canvas-pages/) | /pages/<game>.html | |
| Game assets | /app/runtime/canvas-pages/<game>-assets/... | /pages/<game>-assets/... | |
| Save data | /app/runtime/canvas-pages/_data/<name>.json | /api/canvas/data/<name>.json | |
| User uploads | /app/runtime/uploads/<file> | /uploads/<file> |
Native installs use the OpenVoiceUI runtime/canvas-pages folder (or CANVAS_PAGES_DIR). Only top-level *.html files become pages; the page id is the filename without .html.
What the canvas CSP allows
Canvas pages get their own Content-Security-Policy:
| Need | Allowed | Consequence | |
|---|---|---|---|
| Scripts / ES modules | same origin, inline, eval, WebAssembly, blob:, https://cdn.jsdelivr.net | Load Three.js from jsdelivr only. unpkg, esm.sh, skypack and cdnjs are blocked. | |
| Stylesheets | inline <style>, fonts.googleapis.com, cdn.jsdelivr.net | No same-origin <link rel="stylesheet">; keep all CSS inline. | |
| Fonts | same origin, fonts.gstatic.com, cdn.jsdelivr.net | System fonts or Google Fonts both work. | |
| Images, audio, video | same origin, data:, blob:, any https: | <img>-based loading (TextureLoader) can reach HTTPS hosts that send CORS headers. | |
fetch / XHR | same origin, blob:, a few AI API hosts | GLTFLoader, FileLoader, RGBELoader and the Draco/KTX2 decoders use fetch, so models, HDRs and decoders must be same-origin. | |
| Workers | blob: only | new Worker('/pages/x.js') is blocked; Blob-URL workers (DRACOLoader's) work. | |
| Frames | same origin plus a short list | Embed your own /pages/... pages, not arbitrary sites. |
Page shell
Copy this and replace the demo scene. Things the server adds to every served page, which the shell is written around:
html, body { padding: 25px !important }plus CSS variables--canvas-safe-top/right/bottom/left
(clearance for the app's own buttons). A position: fixed; inset: 0 canvas ignores the padding and goes full-bleed; place HUD elements with the safe-area variables.
- An error bridge: uncaught errors and unhandled rejections are posted to the app, shown in its
Action Console and attached to your next message for about 30 seconds.
- Helpers
nav(pageId),speak(text)andauthFetch(url, opts)(see below).
Rules the page lint enforces: a <meta name="viewport"> tag, a <meta name="page-icon"> tag within the first 2 KB of the file (game is a valid icon), no emoji as UI icons (use inline SVG), no purple/pink accent colors.
Ship it:
Common addons through the same import map: controls/OrbitControls.js, controls/PointerLockControls.js, loaders/GLTFLoader.js, loaders/DRACOLoader.js, loaders/RGBELoader.js, loaders/OBJLoader.js, loaders/FBXLoader.js, postprocessing/EffectComposer.js, postprocessing/RenderPass.js, postprocessing/UnrealBloomPass.js, geometries/TextGeometry.js, loaders/FontLoader.js, objects/Sky.js, objects/Water.js (all under three/addons/).
Assets
Keep every model, HDR and decoder same-origin: save it into the canvas-pages folder and load it by its /pages/ URL.
- Types:
.glbis served asmodel/gltf-binary,.gltfasmodel/gltf+json;.obj,.stl,
.usdz and .fbx also get explicit types.
- Prefer `.glb` (one file) and
.jpg/.png/.webptextures. On a login-required install,
/pages/ serves images, scripts, fonts, audio/video, .glb, .gltf and .wasm to anyone, but every other type (for example .bin, .hdr, .fbx, .obj, .ktx2, .json) needs the viewer's login. That is fine for the owner; it breaks those assets if the page is later made public.
- Draco-compressed models: the decoder is fetched, so host it yourself:
``bash mkdir -p /app/runtime/canvas-pages/_draco && cd /app/runtime/canvas-pages/_draco for f in draco_decoder.wasm draco_wasm_wrapper.js; do curl -sLO "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/libs/draco/gltf/$f"; done ` then dracoLoader.setDecoderPath('/pages/_draco/')`.
- User uploads:
POST /api/uploadstores files as<stem>__<8 hex chars><ext>and returns
{"filename", "url": "/uploads/<filename>", ...}. Use the returned url; to find an earlier upload, ls /app/runtime/uploads/.
- Generated assets (textures, models, sounds from any generation skill) cost money: save each
one into canvas-pages/<game>-assets/ the moment it is produced and reference it by URL. Never keep generated output only in page memory or as a data URL.
- Meshy 3D (optional): if
MESHY_API_KEYis configured,POST /api/meshy/text-to-3dwith
{"prompt": "..."} returns a task_id for an untextured preview (add "mode": "refine", "preview_task_id": "<id>" for PBR textures). Poll GET /api/meshy/task/<task_id>?name=ship until status is SUCCEEDED; the GLB is saved into canvas-pages and glb_url gives its URL (/pages/ship.glb, or ship-1.glb if taken). Stop polling once it succeeds: every later poll saves another copy. Each generation uses Meshy credits.
Saving game state
Never use browser storage (localStorage, sessionStorage, IndexedDB): state lives on the server. Use the canvas data store, which keeps a fixed filename:
- The name must end in
.json; each POST replaces the whole file. The body must be valid JSON
sent as application/json.
- Read it yourself with
cat /app/runtime/canvas-pages/_data/my-game-save.json. - Do not use
/api/uploadfor state: it renames every file, so you cannot read a fixed name back. - On a login-required install both calls need the viewer's session. The page's cookie and
authFetch provide it, so saving works for the logged-in user but not for anonymous visitors of a public page.
Talking to OpenVoiceUI
Page to app: window.parent.postMessage({ type: 'canvas-action', action, ... }, '*')
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/threejs-canvas-integration/SKILL.md
Popularity
- Repository stars
- 80
- Repository forks
- 16
- 7-day trend score
- 0
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Extension points
pluginCustom plugin integration
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/mcerqua/openvoiceui/threejs-canvas-integration)