Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fiend

A live 3D asset workshop for you and your agents. Build with constrained modeling tools, refine together in the Three.js editor, and export reusable GLB assets into a larger game.

Try the Witchlight lantern scene, built entirely through MCP tools, or download its GLB.

The homepage's twisted mask was also built and exported through Fiend. Its GLB and public provenance are in src/client/assets/; the homepage displays a slow, one-minute turntable rotation.

No account or global API key. create_scene returns a public id, a private secret, a read-only url, a collaborative edit_url, and a glb_url. Every other tool requires scene_id; write tools also require secret. The ID alone grants read and download access, never scene-write access. There is no public scene index.

The edit secret is a random 256-bit capability, returned only at creation. Only its SHA-256 hash is stored in a separate Durable Object table. The secret is excluded from scene documents, history, snapshots, WebSocket messages, asset downloads, and ordinary tool results. The edit link carries it in a URL fragment, which is not sent in page requests or referrer headers. The editor sends it only in the Authorization: Bearer ... header for access verification and writes. Public view links carry no credential, even in a browser that also has the edit link open.

The editor's Public link button shares a live read-only viewer with orbit, zoom, inspection, and local GLB download. The private edit URL stays in the browser address bar; it grants collaboration. Agents use the { scene_id, secret } returned at creation. Keep edit links and credentials private to collaborators.

Public viewers can choose solid, realistic (path-traced), normals, or wireframe rendering. Double-click an object in the tree, or press F, to frame it locally. These viewing preferences survive incoming scene updates, never create revisions, and do not affect downloaded assets.

Scenes created before edit secrets were introduced are preserved as read-only. The public viewer's Edit button, or create_scene({ source_id, name }), creates an independent copy with its own ID and secret. Knowing an old public ID cannot claim ownership of the original scene.

Link previews

Public scene links include server-rendered Open Graph and Twitter metadata. Preview images are real 1200×630 scene renders generated by Cloudflare Browser Run at /labs/fiend/api/scenes/{scene_id}/og.png?revision={revision} and cached by scene revision. Preview metadata and image URLs contain only public IDs, never edit credentials.

Agent workflow

  1. Create: call create_scene with a descriptive name and template: "empty" for a new asset, or "starter" for a sample sculpture. Retain secret privately. Give the owner edit_url; use url for public read-only sharing. To copy an existing scene, supply its public ID as source_id.
  2. Assemble: pass scene_id and secret to write tools. Create a named group for the asset and build its parts with meshes, extrusions, lathed profiles, and tubes. Use edit_scene to apply related operations atomically in one undo step. Later operations can reference names created earlier in the same batch.
  3. Inspect: use inspect_scene for the hierarchy and inspect_object for materials, transforms and world-space bounds. Names should be unique; UUIDs always work.
  4. Refine: use frame_object, then capture_scene to see a PNG rendered on Cloudflare. Adjust shapes, placement, lighting and PBR materials. A user can make direct editor changes simultaneously.
  5. Download: fetch https://anoma.ly/labs/fiend/s/{scene_id}.glb directly. Add ?object={URL-encoded UUID or unique name} for a particular object/group. No tool call or secret is required. export_asset is a convenience tool that returns this URL immediately. The file is generated on demand with preview lights/cameras excluded; results are cached per scene revision and object. The URL follows the current scene, so download it into your game's assets for a fixed copy.

Coordinates are Y-up, with meters as the convention. Euler rotations use radians, XYZ order. Colors are #rrggbb. Group origins define asset pivots. Exports retain each selected object's local transform and its child hierarchy; an ancestor's transforms are not baked into a selected child.

Feedback

Open Feedback in the editor to capture your current view. Select objects in that captured view, draw freehand marks, arrows, boxes or circles, and leave a note. Use Live view to adjust the camera, then Capture view to start from a new view. Notes capture text, selected object UUIDs/names and hit points, camera, rendering mode, scene revision, and an annotated screenshot. General notes work without a selection or drawing.

The captured image stays fixed while annotating, so orbiting, resizing or incoming scene changes cannot misalign the drawings. View on a saved note restores its local camera/shading and shows the original annotated image, including when the scene has since changed. Notes and resolutions update live and do not create scene-edit revisions or enter undo history.

Tell your agent: “I left some feedback in Fiend. Check it and make the changes.”

  • get_feedback(scene_id) returns pending notes and annotated images, with pagination. It does not consume or clear notes. Drawing summaries keep the response compact; include_strokes: true includes the normalized points when needed.
  • resolve_feedback(scene_id, secret, feedback_ids) clears only the addressed notes and their screenshots. New notes added while the agent works remain pending. Repeated resolution is safe.

Only edit-link holders can leave or resolve feedback. Public-link viewers can read notes and inspect their saved views, but cannot submit or clear them. Feedback is stored separately in the scene's Durable Object. Resolving a note removes its text and image; only its ID is retained to prevent a retried submission from recreating resolved feedback.

Tools

Purpose Tools
Scenes create_scene, inspect_scene, get_scene_link
Constrained modeling add_mesh, add_group, add_extrusion, add_lathe, add_tube
Editing update_object, duplicate_object, rename_object, remove_object, reparent_object
Appearance set_material, add_light, set_light, set_background
Viewing inspect_object, set_camera, frame_object, capture_scene
Transactions/history edit_scene, undo_scene, redo_scene
Feedback get_feedback, resolve_feedback
Export export_asset (GLB), export_scene (Three.js editor JSON)

Primitive meshes: box, sphere, cylinder, cone, torus, plane, icosahedron, capsule, torus knot and dodecahedron. size has shape-specific semantics documented in the tool schema. Use object scale for subsequent dimension changes. add_lathe exposes a segment count for low-poly profiles.

capture_scene renders the saved camera without needing a connected viewer. It returns both an image and named object positions in pixel coordinates, plus world-space bounds. inFrame describes a projected center, not an occlusion test. Browser editor helper overlays are not included.

Import into a game

curl -L "https://anoma.ly/labs/fiend/s/SCENE_ID.glb" -o asset.glb
# One group:
curl -L "https://anoma.ly/labs/fiend/s/SCENE_ID.glb?object=Lantern" -o lantern.glb
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"

// Use the URL returned by export_asset, or download it into your game's assets.
const asset = await new GLTFLoader().loadAsync(assetURL)
scene.add(asset.scene)

The GLB contains the geometry and PBR materials, with no runtime dependency on Fiend. Give it appropriate lighting in the destination game.

Architecture

MCP client ── Streamable HTTP ──┐
                              │
Three.js editor ── HTTP edits ─┼─ Worker ── SceneRoom Durable Object (one per ID)
                ← WebSocket ──┘            ├─ SQLite scene document + revision
                                           ├─ shared undo / redo history
Cloudflare Browser Run                     ├─ live, hibernatable WebSockets
  └─ headless render / GLB export           └─ immutable GLB export blobs

The public Worker serves the app's static assets, routes MCP requests, and selects a Durable Object using SCENES.getByName(scene_id). MCP uses the official SDK's stateless Web Standards Streamable HTTP transport. Scene state belongs to the Durable Object, not an MCP session or a browser tab.

The Durable Object checks the edit-secret hash before every existing-scene mutation, including browser patch saves, MCP commands, undo/redo, and persisted exports. Both transport paths enforce the same authorization boundary. Read-only WebSockets carry no credentials and only send snapshots; incoming WebSocket messages cannot mutate scenes.

MCP mutations operate on Three.js JSON without evaluating agent-written code. Each batch is applied to a copy, validated, then committed synchronously with its history entry and broadcast. Invalid operations leave the previous document intact.

Browser edits are property-level patches keyed by object and resource UUID, rather than full-scene replacements. Different properties merge automatically; the latest write to the same property wins. Pending local edits are rebased over incoming updates without a reload or conflict dialog. Deletions win over stale property edits, concurrent additions retain their geometry/material dependencies, and hierarchy cycles are rejected. Revisions remain an internal ordering and undo mechanism. A no-op save does not create a revision.

Procedural tubes, lathes and extrusions keep their compact Three.js recipes instead of storing expanded vertex arrays. Scene documents, undo/redo entries, and GLB exports can span 512 KiB SQLite chunks, avoiding Cloudflare's 2 MB per-row limit. Existing inline snapshots and exports remain readable. inspect_scene reports current JSON bytes and the scene allowance; use duplicate_object for repeated parts to share geometry. Splitting a batch does not reduce the final scene's size.

The scene persists even when every browser closes. Browser reconnects receive the latest full snapshot and automatically reconcile pending local edits. Orbiting, panning, zooming, selecting, and resizing are local observations: they do not save the scene or create revisions. Each viewer keeps their camera as other people edit. Explicit MCP camera/framing tools set the saved render camera and update viewers' framing. Undo/redo is shared by all editors and agents.

Initial bounds

  • 50 MiB per scene document; 64 MiB per JSON request; 2,000 objects; 64 hierarchy levels.
  • Up to 100 operations per atomic batch and 100 simultaneous viewers per scene.
  • Last 20 edits available in shared undo history.
  • Up to 100 outstanding feedback notes and 64 MiB of feedback images per scene; each screenshot is at most 1 MiB. Resolving notes frees their storage.
  • Older persisted GLB downloads remain available and immutable (8 MiB each, 64 MiB per scene). New on-demand .glb URLs stream the export and do not consume that storage allowance.
  • Shared persistence covers scene geometry, materials, textures, lights and camera. The upstream editor's executable project scripts are not part of the shared asset document.
  • Headless capture/export uses Cloudflare Browser Run and its account concurrency limits.

Development

bun install
bun run dev
# http://localhost:8787/labs/fiend

The local Worker and Durable Objects run through Wrangler. Local data is under .wrangler/state/. Cloudflare-rendered MCP capture/export should be exercised against a deployed Worker, since the remote browser must reach the scene URL. Browser-side GLB export works locally.

bun run check                # Worker TypeScript
bun run build                # static app + pinned upstream editor
bunx wrangler deploy --dry-run
bun run deploy

Routes are restricted to anoma.ly/labs/fiend and anoma.ly/labs/fiend/*. The existing site's catch-all route continues to serve other paths.

Layout

src/worker.ts       URL routing, assets and public HTTP API
src/download.ts     on-demand, revision-cached GLB download URLs
src/mcp.ts          MCP tools and Cloudflare rendering/export orchestration
src/room.ts         Durable Object persistence, history, exports and WebSockets
src/storage.ts      chunked SQLite storage for large documents and exports
src/scene.ts        constrained operation schemas and scene transformations
src/access.ts       edit-secret generation and verification
src/feedback.ts     feedback note/view schemas and pagination contracts
src/sync.ts         UUID-keyed collaborative patches, shared with the browser
src/client/         landing page, editor integration, render/export clients
scripts/build.ts   compose static assets under /labs/fiend
vendor/three/       pinned Three.js r186 editor and font assets

See vendor/README.md for upstream provenance and notices.

About

No description, website, or topics provided.

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages