CLI Reference
FluidCAD ships a command-line tool alongside the library. It opens FluidCAD's start screen in your browser, scaffolds a project, opens one project in your browser, writes STEP/STL/PNG files without opening the UI, and exposes the MCP server that lets AI agents drive your workspace.
FluidCAD is installed per-project, so the CLI is run through npx from inside the project:
npm i fluidcad
npx fluidcad --help
The start screen, npx fluidcad on its own, is the exception: it runs from any
folder, and the projects it opens need no npm i of their own.
A global install (npm install -g fluidcad) is not supported — the engine and your editor's type hints both resolve from the project's node_modules. If you see Cannot find module 'fluidcad' imported from …, run npm i fluidcad inside the project.
| Command | What it does |
|---|---|
fluidcad | Open the start screen in the browser: your projects, new projects, and the engine each one runs on |
fluidcad init | Scaffold a new project in the current directory |
fluidcad serve | Open one project in the browser and watch its workspace |
fluidcad export step | Write a STEP file |
fluidcad export stl | Write an STL mesh |
fluidcad export png | Capture a PNG of the viewport |
fluidcad mcp | Run the MCP server over stdio |
Every command accepts --help, and npx fluidcad --version prints the installed version.
fluidcad
npx fluidcad
Opens FluidCAD's start screen in your browser: the same one the desktop app
shows, with your recent projects, New Project and Open Project, and the
engine each project runs on. It is the one command you can run from anywhere;
it is not tied to a project folder. (npx fluidcad start is the same command
by its name.)
Every project opens in a tab of its own, and the start screen stays in its tab.
Opening a project that is already open brings its tab forward. New Project
asks where the project should live and what to call it, creates the folder, and
sets the project up in it, the same scaffold as fluidcad init.
Close a project from the ⋯ menu on its card; FluidCAD refuses while its tab
has unsaved changes.
Each project runs on the engine its fluidcad.json pins, the way the desktop
app runs it: a pin that is not on your machine is downloaded once and kept in
~/.fluidcad/engines, and an unpinned project is pinned to the version of
fluidcad you ran. The start screen and the desktop app share the recent
projects, the previews and the downloaded engines, so a project opened in one
is on the other's list too. See Project
configuration.
Leave the command running while you work. Ctrl+C stops FluidCAD and every
project it opened. Running npx fluidcad again while it runs opens the running
one in a new tab instead of starting a second.
| Flag | Description | Default |
|---|---|---|
-p, --port <port> | Port for the start screen — if it's taken, the first free port above it is used | 3100 |
--no-open | Don't launch a browser, only print the start screen's link | opens by default |
--projects <dir> | Keep every project in this folder (see below) | projects live anywhere |
--host <address> | The address to listen on; 0.0.0.0 lets other machines reach FluidCAD (see below) | 127.0.0.1 |
--public-url <url> | The origin browsers reach FluidCAD at behind a reverse proxy, such as https://cad.example.com. Also FLUIDCAD_PUBLIC_URL | none |
--no-auth | No sign-in link: anyone who reaches the start screen can use it (see below). Also FLUIDCAD_NO_AUTH=1 | sign-in link |
--allowed-host <name> | A host name browsers reach FluidCAD by; any other is refused. Repeatable, or FLUIDCAD_ALLOWED_HOSTS=a,b | any, when bound beyond loopback |
The link it prints carries a key for the session, so that no other page or user on your machine can drive FluidCAD through it. Open it yourself when the browser does not open on its own (over SSH, say), and don't share it.
Every project's page is served by the start server itself, at /p/<name>/,
which stands in for the project's engine: the engine runs on this machine's
loopback address only, and the start server forwards its page, its calls and
its live connection to it. That is what lets a browser on another machine work
on a project, below.
A projects folder
npx fluidcad --projects ~/cad
With --projects, one folder holds every project. The start screen lists the
projects in it, all of them rather than the recent ones. New Project asks
only for a name, creates <dir>/<name> and sets the project up there. Open
Project is not offered, since every project in the folder is already on the
screen, and nothing outside the folder can be opened: the start server refuses
any path that is not a project in it, which is what makes the folder the
boundary when the start screen is reached from another machine. The folder has
to exist.
Reaching FluidCAD from other machines
npx fluidcad --projects /srv/cad --host 0.0.0.0 --no-open
--host 0.0.0.0 binds the start screen to every address of the machine, so a
browser on another machine (or in a container's network) can open it, and open
projects in it: each project's page goes through the start server, at
/p/<name>/, and the engines themselves stay on loopback where nothing else
can reach them. Pair it with --projects, so that the machine's other folders
stay out of reach.
What keeps it yours:
- The link
npx fluidcadprints carries the session's key. Anyone who has it can use FluidCAD as you, and can read and change every project it opens, so keep it private. Without it a browser gets a page saying where the link is. - The key becomes an
HttpOnly,SameSite=Strictcookie, and every call that changes something has to come from FluidCAD's own pages. - Nothing is encrypted by FluidCAD itself. Before leaving a private network, put an HTTPS reverse proxy in front of it (Caddy, nginx, Tailscale Serve, Cloudflare Tunnel), forwarding WebSocket upgrades too, and tell FluidCAD the origin it is reached at:
npx fluidcad --projects /srv/cad --host 127.0.0.1 --public-url https://cad.example.com --no-open
With --public-url, the link it prints uses that origin, calls from pages at
that origin are accepted, and, when it is https://, the session cookie is
marked Secure so it is never sent in the clear. Bound to 127.0.0.1 behind
the proxy on the same machine, the start server is not reachable any other
way.
One start server is one session, for one person or a group who share the link. It has no accounts of its own.
Without a sign-in link
npx fluidcad --projects /srv/cad --host 0.0.0.0 --no-auth --allowed-host cad-server --no-open
On a network you trust, --no-auth drops the link and its cookie: http://cad-server:3100 opens the
start screen on any device, every time. Anyone who can reach it can read and
change every project, and run code on the machine through them, so never
use it on a server the internet can reach.
Bound beyond loopback, --no-auth needs --allowed-host for every name the
start screen is reached by (cad-server, 192.0.2.20, a Tailscale name),
and refuses to start without one. A request under any other name is turned
away, which keeps out a hostile web page that points its own name at your
machine: without the cookie, its Host is what gives it away. The
--public-url name is always allowed. Calls that change something still have
to come from FluidCAD's own pages.
--allowed-host also works with the link, to refuse every other name.
fluidcad init
npx fluidcad init
Scaffolds a project in the current directory:
my-app/
├── init.js ← required project entry point
├── part1.part.js ← an empty part to start modeling in
├── jsconfig.json ← enables editor type hints
└── fluidcad.json ← project configuration: engine pin, unit
init.js is required — the viewer cannot run a .part.js file without it. Don't edit it.
The command refuses to run if init.js already exists, so it can't clobber a project. part1.part.js, jsconfig.json and fluidcad.json are only written when they're missing, so re-running it in a half-set-up folder fills in the gaps.
| Flag | Description | Default |
|---|---|---|
--unit <unit> | Project length unit written to fluidcad.json: mm, cm, m, in or ft | the unit for new projects from the Settings dialog, mm unless changed |
npx fluidcad init --unit in # an inch project
Without the flag, the unit comes from Settings ▸ Units ▸ Unit for new projects in the FluidCAD UI (the gear in the top bar). The desktop app's New Project uses the same setting.
See Project configuration for what fluidcad.json holds.
fluidcad serve
npx fluidcad serve
Opens FluidCAD in your browser: the 3D viewport, every interactive modelling
tool, and a code editor. It renders the model you had open last (or the first
.fluid.js it finds in a new project) and watches the workspace, so an edit
made anywhere — the in-page editor, your own editor, an AI agent through MCP —
rebuilds the model. Press Ctrl+C to stop.
Where fluidcad opens the start screen, serve opens one
project, runs it on the fluidcad it was installed with, and suits CI and
scripts. It also pairs with your own editor: see
Editor setup.
| Flag | Description | Default |
|---|---|---|
-w, --workspace <path> | Path to your project | Current directory |
-p, --port <port> | Server port — if it's taken, the first free port above it is used | 3100 |
--no-open | Don't launch a browser — for CI and remote sessions | opens by default |
npx fluidcad serve # open FluidCAD in your browser
npx fluidcad serve --no-open # start the server only (CI, SSH)
npx fluidcad serve -w ./my-app # serve a project in another directory
npx fluidcad serve -p 4000 # start at port 4000 (4001, 4002, … if busy)
The startup banner names the engine version that is actually running, so a
project whose fluidcad.json pin disagrees with it says so before anything is
rebuilt.
The code editor is hidden until you open it, from the ☰ menu or with Ctrl+B. It docks to the left and takes width from the scene rather than covering it, so the model stays fully visible.
fluidcad export
npx fluidcad export step # every shape → <entry>.step
npx fluidcad export stl --resolution fine -o parts/bracket.stl
npx fluidcad export png --view front --open
Writes a STEP, STL, or PNG file straight from the terminal — the same exports the Export dialog produces, without opening the UI.
How the model gets rendered
The CLI works in one of two modes, chosen automatically:
- Attached — a FluidCAD server is already running for the workspace (started by
fluidcad serveor an editor extension). The CLI finds it through.fluidcad/instance.jsonand exports the scene that server is currently showing. Nothing is re-rendered, and no second server is started. - Ephemeral — nothing is running. The CLI forks its own server on a free port, renders your entry file, exports, and shuts the server down again.
Because an attached server's scene wins, passing --entry while one is running only prints a warning — what the server shows is what gets exported. Stop the server to export a different file.
Common flags
These apply to all three formats:
| Flag | Description | Default |
|---|---|---|
-w, --workspace <path> | Path to your project | Current directory |
-e, --entry <file> | Which .fluid.js to render | The workspace's only one |
-o, --out <path> | Output file | <entry>.<ext> in the current directory |
-p, --port <port> | Export from the running server on this port instead of discovering one | Auto-discovered |
--timeout <sec> | Seconds to wait for the server (and, for png, for a browser) | 60 |
If the workspace holds more than one .fluid.js file, --entry is required — the CLI won't guess.
fluidcad export step
| Flag | Description | Default |
|---|---|---|
--shapes <ids...> | Export a subset of the scene's shapes | Every solid (an assembly: the whole assembly) |
--list-shapes | Print the scene's shapes and exit without exporting | — |
--no-colors | Write plain geometry instead of per-shape colors | colors on |
--configuration <name> | Export this configuration (Default for the param() literals) | The one on screen |
--all-configurations | Write one file per configuration: <model>.step for Default, <model>-<Row>.step per row | — |
npx fluidcad export step --no-colors -o build/housing.step
fluidcad export stl
| Flag | Description | Default |
|---|---|---|
--shapes <ids...> | Export a subset of the scene's shapes | Every solid (an assembly: the whole assembly) |
--list-shapes | Print the scene's shapes and exit without exporting | — |
--resolution <r> | Mesh resolution: coarse, medium, fine, or custom | medium |
--linear-deflection <length> | Custom linear deflection, in document units | — |
--angular-deflection <deg> | Custom angular deflection, in degrees | — |
--scale-to <mm|document> | Write the STL in millimetres (what slicers expect) or keep the document's unit | mm |
--configuration <name> | Export this configuration (Default for the param() literals) | The one on screen |
--all-configurations | Write one file per configuration: <model>.stl for Default, <model>-<Row>.stl per row | — |
The two deflection flags imply --resolution custom, and custom needs both of them:
npx fluidcad export stl --linear-deflection 0.01 --angular-deflection 0.1
Passing a deflection alongside --resolution fine is an error rather than a silent override.
Picking shapes
--list-shapes prints what the scene contains, numbered:
3 shapes:
1 a1f2… SOLID Extrude
2 b7c4… SOLID Fillet
3 c9e0… SOLID Housing
--shapes then takes any mix of:
- a position from that listing —
--shapes 1 2 - a feature name —
--shapes Housing(case-insensitive; exports every shape that feature produced) - a shape id —
--shapes a1f2…
Positions and names survive a re-render; shape ids are minted fresh every time the model rebuilds, so don't hard-code them in a script.
npx fluidcad export step --list-shapes
npx fluidcad export step --shapes 1,3 -o build/plates.step
npx fluidcad export stl --shapes Housing --resolution fine
Assemblies
When the scene is a *.assembly.js file, export step and export stl write the whole assembly by default: every inserted part where it sits. STEP keeps the structure — one product per part, one component per instance, sub-assemblies nested — so it opens as an assembly tree elsewhere. STL flattens every placed part into one mesh.
npx fluidcad export step # robot.assembly.js → robot.step, the full tree
npx fluidcad export stl --resolution fine
npx fluidcad export step --shapes 1 # a single part template instead, in its own frame
Mates are solved in the viewer, and the CLI has no viewer, so parts land where the source places them (insert().translate() / .rotate()). The CLI prints a note when that is the case; for the mated layout, export from the viewer's Export menu.
Configurations
A model with a configurations() table (parts, assemblies) exports the configuration on screen unless --configuration names another; the configuration on screen is left as it is. Without --shapes, a part file exports every solid that configuration builds, so a row that adds or leaves out a body exports exactly its own bodies. With --shapes, each listed solid is found in the configuration by its history, and one the configuration does not build fails the export with a message naming it. --all-configurations writes one file per configuration, with row names made file-safe (anything but letters, digits, ., _ and - becomes -); with -o, that path minus its extension is the base name:
npx fluidcad export step --configuration Wide # frame.step, the Wide row
npx fluidcad export step --all-configurations # frame.step, frame-Compact.step, frame-Wide.step
npx fluidcad export stl --all-configurations -o build/frame.stl
--all-configurations renders every configuration before writing anything: if one fails, no file is written. Pass one flag or the other, not both. --all-configurations is not available for png, which captures one image. In a STEP file, an instance inserted with a configuration carries it in its name: frame (Wide).
fluidcad export png
| Flag | Description | Default |
|---|---|---|
--width <px> | Image width | 800 |
--height <px> | Image height | 800 |
--view <name> | Camera view (see below) | iso-ftr |
--margin <px> | Crop/fit margin | 20 |
--transparent | Transparent background | off |
--show-axes | Show the origin axes | off |
--no-grid | Hide the ground grid | grid shown |
--no-auto-crop | Keep the full frame instead of cropping to the model | crop on |
--no-fit | Don't fit the camera to the model | fit on |
--open | Open the viewport in a browser to render the capture | off |
--configuration <name> | Capture this configuration: the viewer shows it for the capture, then switches back | The one on screen |
--view takes current (whatever the connected browser is looking at) or a named view:
| Orthographic | front, back, left, right, top, bottom |
| Isometric | iso-ftr, iso-fbr, iso-ftl, iso-fbl, iso-btr, iso-bbr, iso-btl, iso-bbl |
Isometric names read as front/back, top/bottom, left/right — iso-fbl is the front-bottom-left corner.
npx fluidcad export png --view top --width 1600 --height 1200
npx fluidcad export png --transparent --no-grid -o docs/hero.png
PNG capture needs a browser. Screenshots are rendered by the FluidCAD viewport itself, so a browser has to be connected to the server. Pass --open and the CLI launches one and waits for it; otherwise open the server URL yourself before the --timeout runs out.
If a server is already attached and no browser is on it, the command fails immediately and tells you the URL to open — re-run with --open, or stop that server so the CLI can spawn its own.
fluidcad mcp
npx fluidcad mcp
Runs the FluidCAD MCP server over stdio, so an LLM agent can drive your workspace: take screenshots, inspect geometry, measure, edit source, and look up the API. It's bundled with the fluidcad package — there's nothing extra to install.
You don't normally run this by hand; you register it with an MCP client.
Claude Code — register at user scope so it's available in every project:
claude mcp add --scope user FluidCAD -- npx -y fluidcad mcp
Claude Desktop / Cursor — add to claude_desktop_config.json or ~/.cursor/mcp.json:
{
"mcpServers": {
"FluidCAD": {
"command": "npx",
"args": ["-y", "fluidcad", "mcp"]
}
}
}
opencode — run opencode mcp add, or add to ~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"FluidCAD": {
"type": "local",
"command": ["npx", "-y", "fluidcad", "mcp"],
"enabled": true
}
}
}
The server serves the companion FluidCAD skills to the agent itself, at the version matching the server: it implements the MCP Skills Extension (SEP-2640), so hosts that support it list skill://fluidcad/SKILL.md and its siblings next to local skills, and every other client gets them through the list_skills / read_skill tools, which the startup instructions point at before modeling. Clients with native skill support can additionally install them for automatic triggering:
npx skills add Fluid-CAD/FluidCAD
An installed copy is frozen at install time, so re-run that after upgrading fluidcad.
The agent drives a running workspace, so start one first — with fluidcad serve or an editor extension — and the MCP server attaches to it.
See the MCP README for the full tool surface and transport details.