Skip to main content
Glama
README.md
# Mojulo

[![npm](https://img.shields.io/npm/v/mojulo)](https://www.npmjs.com/package/mojulo)
[![license](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
[![node](https://img.shields.io/badge/node-%E2%89%A522.14-brightgreen)](control/package.json)

![A coding agent wired to mojulo over MCP: "build a 20 by 24 ft living room with a door on the south wall" mints a 12-line floorplan recipe, the dashboard shows the furnished room shaded with turnable views and HTML / glb / STL downloads, "add pot lights to the ceiling" edits one field on the same recipe, a couch-facing fix lands in the kernel with the recipe unchanged, and the same recipe renders in Blender Cycles before and after — same seed, same camera](docs/images/lounge-handoff-demo.gif)

Mojulo is a **3D compiler for coding agents**: an MCP server that runs wherever your agent runs, on your machine or in the throwaway Linux box it gives itself, where the agent you already run (Claude Code, Codex, any MCP host, or a chat app that can open a Linux box: Claude, ChatGPT with Codex, Grok, Meta Muse, Google AI Studio) builds objects, walkable worlds and games by conversation, and what gets stored is source, not a mesh. Every artifact is a small deterministic **recipe**: a few hundred bytes of JSON, or an OpenSCAD program, that a kernel compiles back to the same geometry on every read (byte for byte on the same platform), and that emits to Godot, Blender, Unity, Unreal, glTF, OpenUSD, or print-ready STL / 3MF at true scale. Small enough to carry home from a box and re-mint on your own disk. A compiler, not a generator: you edit and diff the recipe like code, and renders are disposable. No API key, no account, no external telemetry. Your agent does the thinking; mojulo holds the state and does the geometry.

## Quickstart

```bash
npx mojulo init
```

Needs **Node 22.14+** and an MCP-capable coding agent (Claude Code or Codex; Claude Desktop works too). `init` finds the hosts on your machine, asks once per host, and opens the dashboard at `http://localhost:3001`. Everything lands in `~/.mojulo/`. Then open a fresh agent session and ask: **"what is this?"**

In Claude Code you can install the plugin instead: `/plugin marketplace add zombico/mojulo`, then `/plugin install mojulo@mojulo`. It starts the same server pinned to one version ([plugins/mojulo](plugins/mojulo/README.md)). The plugin build leaves out the handoff tools for AI image, voice and mesh generators and never downloads a browser, ffmpeg or the search model on its own. Use the plugin or `init` for Claude Code, not both: two registrations run two servers.

The first install is the big one: npx pulls a ~7 MB package that lands, with its dependencies, at about 227 MB on disk plus about 140 MB of npm cache (about 79 MB downloaded; measured for 3.0.0 on one macOS arm64 machine, where a cold start answered in about 4.5 s, nearly all of it npm's install). The biggest pieces are `node-web-audio-api` (audio), `manifold-3d` (exact booleans) and `better-sqlite3`. The dashboard is a separate package fetched the first time you open it, and the local search model is the opt-in `mojulo install recall` (`semantic_search` ranks lexically without it). Nothing in the list reaches the network on its own; the per-dependency sheet is in [docs/tech-requirements.md](docs/tech-requirements.md). Verified on macOS (Apple Silicon) and on native Windows under Claude Code (`init` and a first render). On Linux x64 the test suite runs in CI, and the agent-box path below has been run to a mint and an export from the Claude app and web, ChatGPT work mode with Codex, Grok chat, Meta Muse and Google AI Studio.

<details>
<summary>Wire it by hand instead</summary>

```bash
# Claude Code (--scope user makes mojulo available in every project):
claude mcp add --scope user mojulo -- npx -y mojulo

# Codex: add to ~/.codex/config.toml
[mcp_servers.mojulo]
command = "npx"
args = ["-y", "mojulo"]
```

```jsonc
// Claude Desktop: add under "mcpServers" in claude_desktop_config.json, then restart.
// If it fails with "spawn npx ENOENT", replace "npx" with the absolute path from `which npx`.
"mojulo": { "command": "npx", "args": ["-y", "mojulo"] }
```

Open the dashboard on its own with `npx -y mojulo-ui`. It is its own npm package since 3.0.0, so your agent's `npx mojulo` start never downloads it; `npx -y -p mojulo mojulo-ui` still works and fetches the matching version from npm on first use. The `mojulo` bin is also a CLI over the tool registry: `npx mojulo tools`, `npx mojulo help mint_solid`, `npx mojulo call version`.

</details>

---

## Where it runs, three things you can add

Mojulo runs wherever your agent runs, and a recipe minted in one place re-mints as the same geometry
in the other. Two shapes, one install.

**In your agent's box, nothing on your machine.** One sentence to the agent installs mojulo in the
Linux box it gives itself:

- The Claude app (macOS, Windows, web, iOS); Grok chat; Google AI Studio; Meta Muse (iOS, web, macOS
  app, one session across all three): *Open a Linux box and install the mojulo npm package in it.*
- ChatGPT (Codex included): *In work mode, open a Linux box and install the mojulo npm package in
  it.*

Each of those has been run this way, from the sentence to a mint and an export handed back. You get
the same recipes and the same exports, as files: the export result names this host's door, an
artifact page, a PR, a file card or Muse's Library. The box has no dashboard you can reach, a scene-to-PNG bake needs a
browser it may not be allowed to fetch, and on most hosts it is gone when the session ends, so ask for
the bundle (one zip: `world.html`, mesh, print STL for literal-scale objects, `recipe.json`, README)
and keep the recipe. Meta Muse is the exception: one VM behind all its clients, where `~/.mojulo`
stays across conversations. A box with no MCP client never sends `initialize`, so tell the agent to run
`npx mojulo orient` first: it prints what an MCP client is handed at connect, translated to the
shell, and points at the routing index. Claude's box built a 47-part phone at true scale this way and handed back the glTF;
Grok chat's sandbox minted a city from the shell. Blender installs in those boxes too.

**On your machine: macOS, Windows, Linux.** `npx mojulo init` wires mojulo into the agents it finds,
opens the dashboard at `localhost:3001`, keeps everything under `~/.mojulo/`, and probes your PATH for
the optional local workers (Blender, a slicer, OpenSCAD, the game engines). This is the whole loop, and
where a recipe from a box comes home to.

Who has run it where. <img alt="persistent" title="persistent" src="docs/images/tick-green.svg" width="14"> persistent (your machine, or a box that keeps `~/.mojulo/`) · <img alt="ephemeral" title="ephemeral" src="docs/images/tick-blue.svg" width="14"> ephemeral (the web
agent's throwaway Linux box; keep the recipe). Blank means not verified yet, not "does not work".

| agent | macOS | Windows | Web Agent Linux Box (Headless) |
|---|:-:|:-:|:-:|
| Claude Code | <img alt="persistent" title="persistent" src="docs/images/tick-green.svg" width="14"> | <img alt="persistent" title="persistent" src="docs/images/tick-green.svg" width="14"> | |
| Claude app (macOS, Windows, web, iOS) | <img alt="persistent" title="persistent" src="docs/images/tick-green.svg" width="14"> | <img alt="persistent" title="persistent" src="docs/images/tick-green.svg" width="14"> | <img alt="ephemeral" title="ephemeral" src="docs/images/tick-blue.svg" width="14"> |
| ChatGPT (work mode; Codex) | <img alt="persistent" title="persistent" src="docs/images/tick-green.svg" width="14"> Codex | | <img alt="ephemeral" title="ephemeral" src="docs/images/tick-blue.svg" width="14"> |
| Hermes Agent | <img alt="persistent" title="persistent" src="docs/images/tick-green.svg" width="14"> | | |
| Grok (Build; chat) | <img alt="persistent" title="persistent" src="docs/images/tick-green.svg" width="14"> Build | | <img alt="ephemeral" title="ephemeral" src="docs/images/tick-blue.svg" width="14"> |
| Meta Muse (iOS, web, macOS app; one session across them) | | | <img alt="persistent" title="persistent" src="docs/images/tick-green.svg" width="14"> |
| Google AI Studio | | | <img alt="ephemeral" title="ephemeral" src="docs/images/tick-blue.svg" width="14"> |

Two things are opt-in, and the choice is the same in both places:

| add | with | what you get |
|---|---|---|
| **creative** (installed by default) | plain `npm install` | worlds, audio, fonts for wordmarks, exact booleans, OpenSCAD-in-process, sharp for skins and sprite sheets. `npm install --omit=optional` sheds those helpers (about 115 MB); the studio tools still list and run, and a call that needs a missing helper says so. |
| **recall** | `mojulo install recall` | the embedding model behind `semantic_search`. Without it, search still answers, ranking by the words in your ask (about 480 MB of runtime plus a 130 MB model, kept under `~/.mojulo/` so it survives upgrades). Most sessions never need it: the agent reads the tool index and the vocab cards directly. |

`mojulo install` with no argument prints which of the two are present.

The chatbot factory is no longer part of mojulo as of 3.0 and is moving to its own project. Earlier 2.x versions that include it are unmaintained and have known security issues ([SECURITY.md](SECURITY.md#known-issues-in-2x)). `mojulo install chatbot` now installs nothing and prints this notice.

**Upgrading from 2.x?** Read [Upgrading from 2.x](control/CHANGELOG.md#upgrading-from-2x) first: an unpinned `npx mojulo` moves to 3.0 on its next start, and 3.0 re-encrypts saved provider keys so 2.x can no longer read them.

## Five things to say to it

Each one is a sentence to your agent, the tool it reaches for, and the recipe that gets stored. Every example below runs keyless and offline.

### 1. "Write me a Raspberry Pi 4 case tray in OpenSCAD"

The agent calls `mint_solid { kind: 'scad' }` and the program is the recipe, stored verbatim:

```openscad
board_w = 85; board_d = 56; clear = 1; wall = 2; floor_t = 2; wall_h = 12;
holes = [[3.5, 3.5], [61.5, 3.5], [3.5, 52.5], [61.5, 52.5]];   // the Pi's M2.5 pattern
module rounded_box(w, d, h, r) { hull() for (x = [r, w - r], y = [r, d - r]) translate([x, y, 0]) cylinder(r = r, h = h, $fn = 48); }
module tray() {
  color("#3a3f4b") difference() {
    rounded_box(board_w + 2 * (clear + wall), board_d + 2 * (clear + wall), floor_t + wall_h, 3);
    translate([wall, wall, floor_t]) rounded_box(board_w + 2 * clear, board_d + 2 * clear, wall_h + 1, 1.5);
    translate([board_w + 2 * clear + wall - 1, wall + 2, floor_t + 3]) cube([wall + 2, board_d + 2 * clear - 4, wall_h]);   // USB / Ethernet
    translate([wall + clear + 5, -1, floor_t + 3]) cube([55, wall + 2, wall_h]);                                            // USB-C, HDMI, audio
  }
}
module standoffs() {
  color("#c9a227") for (p = holes) translate([wall + clear + p[0], wall + clear + p[1], floor_t])
    difference() { cylinder(d = 6, h = 3, $fn = 32); translate([0, 0, -1]) cylinder(d = 2.5, h = 5, $fn = 24); }
}
```

with `parts: { tray: 'tray();', standoffs: 'standoffs();' }`. OpenSCAD itself meshes it on every read, in-process as WebAssembly with the Manifold backend, so booleans are exact and every edge is sharp. `color()` is the tint; each named part is a render group a hinge in `movers` can swing. The same ref serves the orbit view, the `.glb`, the engine packs and `model.stl` at 91 × 62 × 14 mm as written; `model.scad` hands the source back unchanged. Change `wall_h` with `update_sketch` and the readout names the one part that moved. For what OpenSCAD cannot say, a blended join, a stroked dent, seeded noise, the source calls `mojulo_field("<id>")` and a field solid from the same recipe is baked in at that spot.

### 2. "Build a 20 by 24 ft living room with a door on the south wall"

That is the GIF at the top. The agent calls `create_sketch { title, manifest: { kind: 'floorplan', … } }` and the stored manifest is this:

```json
{ "kind": "floorplan", "width": 24, "height": 28,
  "rooms": [ { "x": 2, "y": 2, "w": 20, "h": 24, "glyph": "L" } ],
  "doors": [ { "x": 12, "y": 26, "room": 0, "edge": "S" } ],
  "furnish": true, "view": "cutaway", "seed": 7 }
```

The `L` glyph and the seed furnish it: sofa, two chairs, rug, lamp, windows. "Add pot lights to the ceiling" adds `"potLights": true` and nothing else changes. `"levels": [...]` stacks it into a building with stairs through the slabs. The same recipe walks in the browser at `/world`, exports as a `.glb`, or goes to Blender as an art-pass pack, where a Cycles bake can write traced light back into the mesh's own vertex colours so the lit result runs anywhere at zero runtime cost.

### 3. "Build an oak dining table and show me how it goes together"

![An oak dining table in an exploded view: the top lifted clear, the four aprons pulled out of the legs with their tenons showing, and the angle brackets and screws floating where they fasten, on the World page](docs/images/oak-table-exploded.jpg)

The agent calls `mint_solid { kind: 'workbench' }` with one frame, and `build` writes the members and the joints:

```json
{ "units": "mm",
  "frames": [ { "id": "table", "unit": "mm", "explode": 90,
                "build": { "type": "table", "w": 1400, "d": 800, "h": 750, "species": "oak" } } ] }
```

The legs take the aprons on mortise and tenon, sized so the tenons stop short of each other inside the leg, and the top sits on angle brackets. Every joint is cut through the exact kernel, and the oak is sawn from a synthetic log, so its figure and its movement follow the cut. `explode: 90` pulls each piece 90 mm back along the way it seats; set it to 0 and the table stands assembled. The report is advisory, never a refusal: each member's cut and movement, a span check, the order the pieces go together, tipping, the hardware list and a cut list. The same `frames` entry builds carcasses with doors and drawers, sofas with their upholstery, a kigumi frame, steel and masonry. Gather the piece into a stash and cook `instruction_manual` for its wordless assembly pages, or set `layout: 'kit'` to print it flat as a model kit.

### 4. "Build a New York-style city at real scale"

![An aerial view of a generated New York-style metro: brick walk-ups with wooden water tanks on their roofs, glass and Art Deco towers, and avenues with traffic, from one compose_world call](docs/images/metro-new-york-aerial.jpg)

The agent calls `compose_world { base: 'city', seed: 11, overrides: { profile: 'metro', flavor: 'new-york' } }`. `metro` builds it in proportion at about 0.8 × 0.5 km, and the flavor picks the architecture: brick walk-ups, fire escapes, water tanks and Art Deco setbacks here, or `paris`, `london`, `tokyo` and more. The whole city is a pure function of the seed: change it for a new city, keep it and the same city regrows on any machine. `context: { time: 'night' }` lights it for night, and `asset: { monument: 'eiffel-tower' }` stands a landmark at its real size. Open `/world` to walk it with WASD or jump between its street, aerial and skyline views. Bases besides `city`: a transport hub, a K-12 campus, a torch-lit dungeon, a planetary body, a painted landscape, terrain at real scale, a walkable Cayley graph of a finite group.

### 5. "Make it walkable, then make it a game, then export it for Godot"

`compose_world { base: 'controllable' }` gives a live world you drive. Adding `game: { mechanics: [...] }` to a world makes it a level: reach the exit, survive twenty seconds, collect the relay core. `create_game` binds levels, a synthesized score and figures into one playable artifact with a typed store (inventory, party, flags) that carries between levels; every level must pass a contract dry-run and a traversal that reached the win condition before the game mints. `export_game { target: 'godot' }` writes a real Godot 4 project you open and extend. Unity and Unreal get the same data pack plus an importer; the worked Unreal example is [docs/examples/unreal-night-run/](docs/examples/unreal-night-run/).

**Also from a sentence:** a posed human figure or an animal mid-stride, rigged for glTF or VRM; a six-storey building with a set-back penthouse; an ambient loop or a full score synthesized from seeded math with no samples; a room's layout recovered from a photo you show your agent. The full catalog is in [docs/tour.md](docs/tour.md).

---

## Where it goes

One recipe, several targets, all off the same ref: `/api/sketches/<ref>/{svg,scene,world,model.glb,model.stl,model.3mf,model.usdz,model.scad}`. Each URL regenerates deterministically on request: byte for byte on the same platform (OS, CPU and Node version), and the same geometry to within floating-point rounding on any other, since V8's `Math` rounds its last bit differently by CPU and Node version (an embedded texture PNG can also differ in its compressed bytes, never in its pixels). Every handoff carries a ledger naming what did not travel.

| Target | What you get | The gate |
|---|---|---|
| **Browser** | A still SVG, a dependency-free CSS-3D scene, a walkable WebGL world. | Your eyes. |
| **`.glb` / OpenUSD** | glTF with lighting baked into vertex colours (or `lit` for PBR), rig clips, skinned meshes, VRM bone names; `usda` / `usdz` at true scale. | Blender and `usdcat`, when installed. |
| **Godot** (first-class) | A real Godot 4 project: `project.godot`, the versioned kernel scripts, the GLB, export presets. | Headless import and a one-frame run, when `MOJULO_GODOT` names the binary. |
| **Unity / Unreal** (gated legs) | A data pack plus a C# editor importer or a Python importer; game packs add a C++ kernel plugin. | A scratch project imported headless, when `MOJULO_UNITY` / `MOJULO_UNREAL` name the editor. |
| **Blender** (art pass) | An art-pass pack with importer scripts, and a Cycles bake of global illumination back into vertex colours. | The importer run headless, when `MOJULO_BLENDER` names the binary. |
| **STL / 3MF** | Print-ready at true scale: mm, z-up, colours and instanced repeats in 3MF, process-aware advisories (FDM, SLA, SLS, MJF). | A local slicer run over the 3MF, stamping layers, time and filament. PrusaSlicer and Bambu Studio verified. |
| **`.scad`** | A program, not a mesh. A `scad` recipe returns its own source verbatim; a workbench recipe is transpiled term by term into OpenSCAD solids and booleans, with a coverage ledger naming any term that arrived as a frozen `polyhedron()`. | OpenSCAD re-renders it and checks size and volume against the recipe, when the binary is installed. |

Every export works with nothing installed; the pack is written and the gate reports "skipped" with the reason. Installing the engine adds the machine gate. Gates advise and stamp, none refuse, and no gate ever claims a human looked. Doctrine: [docs/bicycles.md](docs/bicycles.md).

---

## What stays on your machine

- **Recipes** live in one SQLite file at `~/.mojulo/mojulo-lite.db`. Renders are derived and disposable.
- **Your cookbook.** `save_recipe` promotes a recipe to `~/.mojulo/data/cookbook`, plain `card.md` + `recipe.json` folders in a local git repo with no remote, recallable by intent months later through `semantic_search`. The public [mojulo-recipe-book](https://github.com/zombico/mojulo-recipe-book) is the same format: clone it, point `MOJULO_RECIPE_BOOK` at it, and it adds chapters and whole new kinds without touching core. Its new kinds are `builder.js` files that mojulo imports and runs with your privileges when it starts, so point it only at a book you trust.
- **Exports** are plain files you open in anything; the exported World page carries its own three.js unless you ask for the CDN build (`cdn: true`). Provider keys, if you ever save one, are AES-256-GCM encrypted at rest under a per-install key in `~/.mojulo/secret.key`.
- **No external telemetry.** Nothing is sent to the maintainer or an analytics service. Outbound traffic is npm at install, a few one-time downloads on first use (a browser for an explicit render if you have none, ffmpeg for the first MP4, the dashboard package when you first open it, the search model after `mojulo install recall`), and an update check when your agent asks for one; under the Claude plugin the browser, ffmpeg and model downloads never happen on their own. Full list: [docs/tech-requirements.md](docs/tech-requirements.md#network-posture).
- **A local tool-call log.** Each tool call is recorded in the same SQLite file: tool name, timing, status, argument names and sizes (not values), truncated error text, and the MCP client's name and session. It never leaves the machine, keeps 30 days or 50,000 rows, and `MOJULO_MCP_TELEMETRY=off` turns it off.
- **Recipes can carry code.** A recipe with a `program` (the code door) is JavaScript that runs with your privileges when it renders; treat a shared one like code.

The control plane is single-operator and localhost-only by default. The stdio MCP has no network surface; the HTTP MCP route 404s unless `CONTROL_PLANE_MCP_KEY` is set. Don't expose it to the public internet; use a tunnel or Tailscale if you need it remote. Threat model: [SECURITY.md](SECURITY.md). Terms and the operator-owns-consequences posture: [TERMS.md](TERMS.md), [docs/responsibility-model.md](docs/responsibility-model.md). The open-source mojulo is and stays Apache-2.0 with no external telemetry; if a hosted mojulo cloud is ever explored, it would be its own opt-in product and the local install would not change or depend on it.

---

## How it works

A Next.js app exposes two faces over one SQLite: an MCP server (stdio for the npm package, HTTP for remote clients) that your agent calls, and a dashboard at `localhost:3001` that renders what accumulates. The compiler shape: a recipe is the source (params plus a `kind`), a kernel is the backend that regenerates it on every read, and each emitter is a target that owns its own frame and unit conversion from the native z-up metre frame. A kernel's output for given params is a compatibility promise over already-minted rows, tested byte-for-byte. Optional local workers (Blender, slicers, mesh sculptors, ComfyUI, Kokoro) are operator-hosted and never dependencies; absence degrades one loop without breaking any.

Also in the box, present by default and never in the way: diagrams and charts, directed images an external model paints, publications and research notebooks, local apps whose inference parks back on your agent, and workflows over the MCPs you already run.

- [docs/tour.md](docs/tour.md) — the long tour: everything mojulo makes and who it is for
- [docs/AGENT-REFERENCE.md](docs/AGENT-REFERENCE.md) — the substrate, rings, data layout, daemons
- [docs/MCP-ARCHITECTURE.md](docs/MCP-ARCHITECTURE.md) — transport, session binding, deliberation surfaces
- [docs/POLYGONIZER-SYNTHESIS.md](docs/POLYGONIZER-SYNTHESIS.md) — the geometry substrate
- [docs/tech-requirements.md](docs/tech-requirements.md) — measured footprint, engine legs, slicers, workers, platform notes
- [docs/local-blender-worker.md](docs/local-blender-worker.md), [docs/local-slicer-worker.md](docs/local-slicer-worker.md), [docs/local-mesh-worker.md](docs/local-mesh-worker.md), [docs/local-image-worker.md](docs/local-image-worker.md) — the optional workers
- [AGENTS.md](AGENTS.md) — for non-Claude hosts

```
mojulo/
├── control/        Next.js control plane: MCP server, dashboard, kernels, emitters, runtime supervisor
└── docs/           Concept docs
```

## Contributing

One maintainer, no SLA. The wide door is the [recipe book](https://github.com/zombico/mojulo-recipe-book): a folder with a card and a recipe, or a pure builder for a whole new kind, needs no change here. In core, bug reports with a pasted recipe (they reproduce exactly), correctness fixes, translations and tests are welcome; concept PRs that change how mojulo works will likely sit, and forks are the open door. Straight up: the maintainer is one person and most read passes are AI-assisted. Full stance and the open requests: [CONTRIBUTING.md](CONTRIBUTING.md).

## License

[Apache License 2.0](LICENSE)