- TypeScript 93.1%
- G-code 5.3%
- CSS 1.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
A cut-out can have its top edge beveled, once the person ticks Chamfer
the top edge for it and gives a width (2 mm to start). A step of its own,
"Chamfer {label}", runs a V-bit's tip right on every ring of the parts,
holes too, as deep as the width over tan of half the bit's angle, in
V-carve passes, before any cut-out, grouped with the job's other steps on
that V-bit. The V-bit is picked from My bits as any bit is, one the job
already uses first; none, none wide enough, or one that would go through
the board blocks the plan. Joined cut-outs share it, and open lines get
none. The panel's hint says how deep the plan's V-bit cuts.
|
||
| apps | ||
| e2e | ||
| packages | ||
| tools/oxlint/anti-slop | ||
| .dockerignore | ||
| .env.local.example | ||
| .env.schema | ||
| .gitignore | ||
| .oxlintrc.json | ||
| .pre-commit-config.yaml | ||
| .prettierignore | ||
| .prettierrc.json | ||
| compose.yml | ||
| Dockerfile | ||
| justfile | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| Procfile.dev | ||
| README.md | ||
| tsconfig.base.json | ||
| tsconfig.json | ||
| vitest.config.ts | ||
OpenSwarf
A browser-based app for running and managing a CNC machine.
Quickstart
just setup && just dev
Then open http://localhost:5173 in Chrome (or Edge) on a computer. Connecting to the machine uses Web Serial, which only works there.
Needs Node 24 or newer, just, tmux, overmind
and varlock (npm i -g varlock).
Day to day: just test, just lint, just typecheck. Run just to list every recipe.
just e2e runs the Design editor's gestures in Chrome, against a server of its own on
port 8090 with its data in e2e/.data.
Your first sign
Your first sign is a board with your words carved into it. It takes two bits: a 60° V-bit ½ in across for the lettering, and a ¼ in flat end mill for anything cut out.
- Switch the machine on and plug it into the laptop. Open Machine, press Connect, then press Home.
- Open My bits and add your 60° ½ in V-bit and your ¼ in flat end mill.
- Open Projects and press New project. Name it, type in the board's width, height and thickness in millimetres, pick its material, and press Create project. It opens in Design.
- In Design, pick Text on the left, click the board where the words go, type them, and press Esc. With the words selected, the panel on the right sets their font and letter height, and What should the machine do with it? picks how they are cut: V-carve, Engrave, Pocket or Cut out. Words are V-carved unless you pick another. A Rectangle drawn round them is cut out, with small tabs that hold the sign in place until you snap them.
- Press Preview the job at the bottom of the panel. Preview shows the board in 3D as each bit cuts it, with the job's steps beside it; press a step to see the board after it.
- Press Send to machine. Measure your board with calipers twice, near two opposite corners, and type in both readings. A cut-out goes as deep as the board you measured, and half a millimetre more.
- Press Start.
- Follow the Run screen. It takes you through clamping the board, putting in each bit, choosing where to start and probing each bit's height, then makes the cuts one at a time.
To see a finished design first, open Projects and press Try the example sign: a 400 × 250 mm pine board that says WELCOME, with EST. 2026 engraved under it and a border cut out round it, which stays about 23 mm in from every edge to leave room for the clamps.
Before your first real cut, rehearse:
- Walk the whole job once on the simulated machine to learn the screens. In a dev build, open Machine and press Use simulated machine. Nothing done there is saved, and your real machine's setup stays as it was.
- On the real machine, run Diagnostics → Dry run in the air to check that it moves as it should.
The sign itself can't be run in the air: each probe sets the bit's height from the real top of the board, wherever the bit was jogged to, so the cuts still go into the wood. For the first cut, clamp a scrap board, switch the router on, and keep a hand near Stop.
Run with Docker
just docker
This builds one image and starts it on port 8080. Open http://localhost:8080 on the
machine running Docker, or http://<server-ip>:8080 from another computer. Your data
lives in the openswarf-data volume, so it survives restarts and rebuilds.
just docker-logs follows the logs. just docker-stop stops the app and keeps the data.
Connecting to the machine needs Web Serial, and the browser only turns that on for
https:// pages and http://localhost. Opening http://<server-ip>:8080 from your
laptop leaves Connect switched off. Everything else works over plain HTTP. The sign
of this, even in Chrome or Edge, is the app's banner "Connecting to the machine needs
Chrome or Edge on a computer. Everything else works here." There are three ways to
get Connect:
-
Run the container on the laptop that the machine is plugged into, and open http://localhost:8080.
-
Put HTTPS in front of the server.
-
With Tailscale, which works on a home network with nothing open to the internet: first turn on HTTPS certificates in the Tailscale admin console (DNS → HTTPS Certificates), then run
tailscale serve --bg 8080on the server and open thehttps://address it prints. -
With a reverse proxy such as Caddy, a Caddyfile of three lines does it. Caddy gets its certificate from Let's Encrypt, so it needs a real domain name pointing at the server, with ports 80 and 443 reachable from the internet. If the server is only on your home network, use Tailscale instead.
openswarf.example.com { reverse_proxy localhost:8080 }
-
-
As a quick fix on a home network, open
chrome://flags/#unsafely-treat-insecure-origin-as-securein Chrome (in Edge,edge://flags/#unsafely-treat-insecure-origin-as-secure). Addhttp://<server-ip>:8080to the box, set the flag to Enabled, and relaunch. This lowers the protection for that one address, so only do it on a network you trust.
Env setup
Config goes through varlock. The committed .env.schema declares
every key with its type and default; the gitignored .env.local overrides it. Recipes
that start the app load the schema first and stop if a value is invalid.
Bootstrap with just init (just setup does it too). It copies .env.local.example to
.env.local if you don't have one yet.
No key is required and none is secret today. The keys, all optional:
| Key | Default | What it does |
|---|---|---|
PORT |
8080 |
Port the server listens on |
DATA_DIR |
./data |
Where the server keeps profiles, jobs and settings backups |
WEB_DIST |
empty | Built web app for the server to serve; empty in dev |
LOG_LEVEL |
info |
One of fatal, error, warn, info, debug |
DEV_ALLOWED_HOSTS |
empty | Extra hostnames, comma-separated, for reaching the dev server (port 5173) through a reverse proxy. A leading dot allows every subdomain |
A relative DATA_DIR is taken from the folder the server runs in, which is
apps/server under just dev and just server, so the dev database lands in
apps/server/data.
To change one, uncomment its line in .env.local and set your value. If a secret key is
ever added, point it at your own source in .env.local: a literal value, or a resolver
from a @plugin(...) declared in .env.schema (for example 1Password:
XYZ_API_KEY=op(op://your-vault/item/field), each contributor signing in with their own
token).