Ten programs. One stubborn idea.
That software for your own life can be made at home, like bread: small, exact, and suited to one kitchen.
- At seaA binnacle on a Raspberry Pi, and a simulator that draws the wind
- WorkshopA 3D printer watched, fed and second-guessed
- TerminalPictures, CAD models and a phone, all in a grid of characters
- DeskA to-do list that lives in your source code
- Machine roomDashboards, and a log that cannot be quietly edited
at homeno cloud inside
nakterhus
A chart plotter, a ship’s log and a sail-trim coach, all living on a Raspberry Pi behind the companionway.
Nakterhus is Swedish for binnacle, the housing that keeps the compass where the helmsman can see it. This one keeps rather more than a compass.
Every instrument on the boat speaks SignalK, and nakterhus listens. It draws the boat on OpenStreetMap and OpenSeaMap charts, lays a route over them, watches AIS traffic for anything on a closing course, and records every delta to SQLite so that a passage can be replayed later with one cursor shared between the graph and the track: drag along the depth curve and a marker walks the chart.
There are seven workspaces, each built for a job: Navigate, Helm, Plan, History, Voyage, Dashboards and Sail. A digit key switches between them and nothing is unmounted when you do.
Which feed is live is observed, never set. If the simulator’s port answers, the boat is simulated, and a flashing pill says so.
It is built for a boat on metered 4G. Cameras are ffmpeg processes that exist only while somebody is watching. A viewer off the boat gets a slower feed and a smaller picture, and a phone page that is hidden polls nothing at all.
The web console is the command line: one argparse tree serves both, so every command has exactly one implementation. Dashboards are HTML fragments drawn twice, by the browser and natively for the e-paper panel, with a test that holds the two to the same picture.
And when the wind gets up, it gives advice. The Sail workspace works out the point of sail, says when to reef, and lists the trim steps in order, each with its reason.
- Runs on
- A Raspberry Pi 4 aboard, Debian 13
- Surfaces
- Web workspaces, a phone page, a CLI of some 35 command groups, a 7.5-inch e-paper panel
- Made of
- Python with three runtime dependencies; framework-free ES modules, Leaflet, three.js; SQLite
- Talks to
- SignalK, AIS, Victron power gear, USB cameras, a 4G router
- Status
- In use aboard. 1,058 commits since May 2026, some 1,860 tests
jsail
A sailing simulator that explains its forces. Every newton is computed, and every newton is drawn.
A survey of what already existed found simulators with physics that explain nothing, and trainers that explain everything with no physics behind it. So here is both.
Sail aerodynamics, hull, keel and rudder, wind, current and waves: jsail solves the lot, then draws what it solved. Pressure runs up the luff as a comb of arrows. Streamlines bend round the rig, blue where the air is slower than the wind and red where it is faster. The righting arm and the heeling arm are labelled in metres, on the boat, while she heels.
The rudder angle is solved together with yaw, so weather helm and a broach are things that happen to you, not settings. Under way she is stepped through time in six degrees of freedom, the crew trims at the pace of a winch, and a tack costs speed exactly as it should.
Ask too much of her and she answers plainly: “no sail set may be carried in 14 m/s.”
Trim is the crew’s or yours, item by item. Eight controls each carry an Auto box, and whatever you leave alone is optimised around what you insist on. Get it badly wrong and the instructor steps in: one correction at a time, each with its reason and what it is worth in knots, and a gold arrow pointing at the slider to move.
Every number in a boat file carries its provenance: declared, calculated or estimated. jsail boat check prints the estimates under the heading “the to-do list”.
The headless build is a 2.1 MB binary that computes a polar on a Raspberry Pi in under a tenth of a second.
- Surfaces
- A Godot 3D view and a headless
jsailCLI - Made of
- A Rust workspace of six crates, Godot 4 through gdext, GLSL for water, sails and flow
- Checked against
- WinVPP polars and the Delft Systematic Yacht Hull Series: 0.42 kn RMS over 224 cells
- Status
- First iteration stands. She can be sailed
rakemooner
A flight recorder for a 3D printer. Every print kept, every second of it, and one cursor to walk back through the lot.
Moonraker tells you what a Klipper printer is doing now. Rakemooner, which is Moonraker with its syllables swapped, remembers what it did.
While a print runs, a small daemon writes down everything the printer reports: temperatures and their targets, heater power, fans, speed, the humidity in the filament box, each console line, the bed mesh, the webcam. Afterwards the web UI lets you go back over it, and one time cursor drives the whole page. Scrub a chart and the readouts, the G-code line, the 3D toolpath and the video all move to that moment.
The toolpath is drawn as solid boxes of the true width and height of each extrusion, about a million and a half of them for a ten-hour print, in one draw call. Colour them by feature, by speed, by height, by filament slot, or by any field that was recorded.
Switching workspace unmounts nothing. The webcam keeps streaming and a half-typed note survives.
Prints gather themselves into projects without being filed. A model’s version is a hash of its meshes; the settings that changed between two prints are read from the block the slicer leaves at the end of the G-code. Tick two prints and it tells you what differed.
And then the Slicer workspace asks what if. It takes the slicer project a print was made from, slices it again under other settings with OrcaSlicer’s own command line, and sets the results side by side: 25 minutes and 6.5 grams saved, at two walls and less infill.
From the temperature and humidity in the filament box the daemon also works out the dew point, because somebody wanted to know.
- Surfaces
- A recorder daemon, a web UI with a phone form,
rm-cli, and a desktop shell - Made of
- Nine Rust crates on tokio, axum and Tauri; plain JavaScript and three.js, no build step
- Records
- Sensors at 1 Hz, G-code, console, bed mesh and webcam video
- Leans on
- Moonraker for the printer, ffmpeg for video, OrcaSlicer’s command line for second opinions
- Status
- In daily use. 243 commits since May 2026
bidibok
Four spools, one filament box, and a command line that knows what colour each of them is.
The Qidi Box feeds a Q2 printer from four spools and dries them while it waits. The touchscreen can drive it. So, now, can a script.
bidibok reads the box and its slots, loads and unloads filament, starts a drying cycle, reads the RFID tag on a spool and assigns filament, colour and vendor from it. Colours are shown as true 24-bit swatches, and vanish politely when the output is piped somewhere.
Your own filaments live in a small local file, layered over the printer’s list by index. A firmware update overwrites the printer’s copy; yours survives, and preset sync puts it back, touching only your entries and leaving the stock ones byte for byte as they were.
It ships with a printer to play with: loads block for seconds, drying heats the box, and humidity falls.
That simulator runs the G-code it receives against a model of the box, slowly, the way the firmware does. Everything on this page was shot against it.
Two generations of firmware are detected at run time. Exit codes mean something: 1 for “may work later”, 2 for “asking again won’t help”.
- Surfaces
- A CLI, and a library crate anything else can link
- Made of
- Rust: a sans-IO protocol core, a clap front end, a simulated printer
- Talks to
- Moonraker over HTTP. No SSH, nothing installed on the printer
- Tests
- 137 golden files; none of them needs a printer
jbuilder
Click a fillet. Land on the line of Python that made it.
build123d lets you write a solid model as a Python script. That is wonderful until the part has forty faces and you need the name of one of them.
jbuilder watches the script. Save it from any editor and the model rebuilds. Click a face, an edge or a vertex in the viewer and the source panel scrolls to the line that created it, because a trace hook was taking notes while your script ran. An untouched face keeps its line through every later cut and fillet.
Clicking also answers the harder question: how do I refer to this thing in code? jbuilder generates candidate selector expressions, evaluates each one, and keeps only those that resolve to exactly the shape you clicked. Up to three come back, simplest first.
The selector suggestions are a plain algorithm. Every one offered has been run and found to be right.
Type a selector of your own and the matches light up as you type, with a count. Click a line number and the viewport shows only what that line built. Call show() anywhere in the script to leave a named, coloured checkpoint; it costs nothing until somebody clicks it.
There is a constraint sketcher that opens on any flat face, a scratch buffer that shares the model’s namespace, and a neovim plugin that turns build errors into ordinary diagnostics and makes the viewer follow your cursor.
- Surfaces
- A web viewer, a desktop window,
jbuilder-cli, and a neovim plugin - Made of
- Python with FastAPI and build123d; Three.js and CodeMirror with no build step
- Exports
- STL, STEP, BREP, glTF and 3MF
- Rule of the house
- jbuilder never edits your model code
part = fillet(root, 5).jcat
cat, for pictures. Then somebody pointed it at a CAD file.
A terminal that speaks the kitty graphics protocol can show real pixels. jcat is a small viewer built on that: zoom with the wheel, pan with a drag, or use hjkl like a civilised person.
The image is uploaded once. After that each zoom or pan is one placement command of a few dozen bytes, and the graphics card does the scaling. Vector formats are rasterised at twice the size of the screen so they stay sharp when you lean in. Over SSH it notices, and streams the picture instead of handing the terminal a file.
Inside tmux none of that works, so it does something else entirely: Unicode placeholder cells, an image pyramid, and two image IDs taken in turn so there is never a blank frame. The implementation notes include a workaround for one tmux release that drops diacritics in panes away from the left edge.
Other programs borrow it as their image widget: --place draws one picture into a block of cells and leaves.Then there is modelrender, a second small program that turns a 3D model into a sheet of views with the hidden lines properly removed. It rasterises a depth buffer only to answer what is visible, then writes the answer as vector paths. All nine views share one scale, so sizes compare across them.
Put the two together and jcat bracket.stl works. While FreeCAD tessellates, a cube made of braille dots tumbles to pass the time.
- Surfaces
- Two commands:
jcatandmodelrender - Made of
- Python. Pillow and ImageMagick for jcat; trimesh, numpy and a headless FreeCAD for modelrender
- Reads
- Bitmaps, SVG, PDF, HEIC; STL, 3MF, OBJ, glTF; STEP, IGES, BREP and FreeCAD documents
- Status
- jcat calls itself a prototype, with notes written for whoever rewrites it
Without modelrender
n and p step through the files; a PDF and an SVG are waiting behind this one.With modelrender
jpim
Your projects already live on the filesystem. The to-do list may as well move in with them.
Most organisers want to be the place where everything lives. jpim would rather be a thin layer over the directories, notes and source files you have already.
Write @jpim { kind = "todo" } in a comment, in any text file, and a todo exists. The file stays the source of truth: edit the line and the item follows. The scanner honours .gitignore, remembers what it has seen, and goes back over a 37 GB project in about two seconds.
It will not touch your files unless asked. Ask, and it writes an eight-character id back into the line, surgically, so the item survives being moved.
A bare date means the end of that day. Midnight would make a thing overdue for the whole day it is due.
One quick-add grammar works everywhere: varnish the tiller #boat @boatyard due:fri +marigold reads the same at the shell, in the tmux popup, in the terminal UI and on the web. Both interfaces have a : command bar that takes any jpim command, for when the mouse is too far away.
The terminal calendar is not a second implementation of the calendar. It is the CLI’s own renderer, handing over the same lines. Markers mean something without colour: a dot for an appointment, a diamond for a deadline, a triangle for overdue.
Places are named circles on a map, and a todo can wait at one.
- Surfaces
- Daemon, CLI, terminal UI, web app, desktop shell, a neovim plugin and a tmux popup
- Made of
- Rust: axum, SQLite, ratatui, Tauri. The web app is Svelte, embedded in the daemon
- Endpoints
- 35, every one wrapped by a CLI command; a test fails the build otherwise
- Status
- Working on every surface it set out to have
@jpim found in a project’s source, with its file, line and the TOML as written.#varnish.jphone
The phone stays in the other room. Its texts, camera, contacts and whereabouts come to the keyboard.
Answering a text should not require picking up the device it arrived on. jphone puts the whole phone in one command and one terminal screen.
Five tabs: Messages, Contacts, Camera, Location, Status. Photos appear in the terminal as photos. The composer counts SMS parts while you type, and tells you which character just forced the message into the expensive alphabet, 70 to a part instead of 160.
Neither the terminal screen nor the web app ever reads a data file. Each tab is drawn from what the CLI answers and each action runs the CLI, so there is one implementation of everything and it can be scripted.
Unread is honest. A screen nobody looks at reads nothing.
The phone has no server to connect to. It dials in over SSH to a restricted login shell, and holds one long wait open as its push channel. Asleep on mobile data, it is woken by a single UDP packet, and is up from deep doze in two to four seconds.
Find-my-phone comes built in: a fresh fix, a recording of where it has been, and a ring at full volume even when silenced, with a “Found it” button for whoever digs it out of the sofa.
Comments and tags on a photo are written into the JPEG itself, so they go wherever the file goes.
- Surfaces
- A CLI, a five-tab terminal screen, a web app, and the Android app at the other end
- Made of
- Go and Bubble Tea on the desk; Svelte in the browser; Kotlin on the phone
- Travels by
- SSH over WireGuard, one command per request. No server port anywhere
- Borrows
- jcat to draw photos, jkarta to draw the map, jpim for what you owe each contact
p takes a new one from across the house.jstatus
Everything worth glancing at, on one screen, laid out the way a tiling window manager would do it.
A dashboard is a tab. A tab is made of widgets, framed panels in nested boxes. Each computer gets its own, because a laptop and a server do not want to know the same things.
The machine widgets cover the processor and its history, memory, sensors, disks and their traffic, the network, the busiest processes. Others look outward: which hosts answer and how quickly, the newest entries in jlogger, the calendar from jpim, a large clock, and whatever any command you name cares to print.
Layouts are edited in place, with the keys of a tiling window manager. Split with v and s, shove a panel about with HJKL, mark two and swap them, undo. A wireframe shows the result at its real size in cells.
There is a daemon so that “down for 3 h” is true whether or not anybody had a dashboard open.
That daemon has no authentication and no open port. A dashboard reaches it through ssh -W, speaking HTTP down the pipe, so an SSH login is the gate.
Every panel gives up detail before it gives up space: a chart becomes a sparkline, then nothing; columns leave in a documented order. And the sensors widget stops polling an idle graphics card, because asking how warm it is would keep it awake for ever.
A bad config file does not stop anything. It becomes a tab with a warning sign on it.
- Surfaces
- A terminal UI, a small daemon that keeps watch, and a borderless desktop window
- Made of
- Go with Bubble Tea and Lip Gloss; the window is 280 lines of C, GTK and VTE
- Configured in
- KDL, one small file per dashboard, or not at all: there is an editor
- Widgets
- Sixteen, from
cputocommand, which frames whatever a program prints
jlogger
One place for every machine in the house to say what happened, and a hash chain to keep it honest.
Cron jobs, backup scripts, a greenhouse sensor, a printer finishing a job at four in the morning. They all have something to report, and syslog on six machines is six places to look.
Each entry is stored with the hash of the one before it, back to a first entry whose predecessor is the word genesis. Change any row and every hash after it is wrong; jlog verify names the first bad id. A second, append-only copy and a service account stripped of every capability make the chain harder still to rewrite quietly.
Anyone on the network may log. Clients that care can sign what they send, and the server checks the signature before it accepts a word.
Sending a log entry always exits zero. A log server that is down must never be the reason the backup did not run.
Log content is treated as hostile. It is stored byte for byte, so the hashes hold, but the viewers escape control characters and bidirectional overrides, so no message can paint over your terminal or forge the line above it.
The web viewer is rendered on the server and asks every three seconds for anything newer. Sort by something else and it stops inserting rows under you, and offers a count of what is new instead.
- Surfaces
- A daemon, the
jlogCLI, a terminal viewer, a web viewer, and a zsh client for machines with nothing butjq - Made of
- About 2,600 lines of Go, SQLite in pure Go, server-rendered HTML
- Trust
- A SHA-256 chain over every entry; Ed25519 signatures for clients that want them
- Runs on
- amd64, arm64 and armv7, packaged for Arch and Debian
How this issue was made
The programs
Ten projects by one person and one LLM, written for one household: a workshop with a 3D printer in it, a sailing boat, a phone, and a great many terminals. They share habits more than code. Every one has a real command line. Daemons are small and say what they are. Data stays on machines you can put a hand on. Where two programs need each other, one runs the other’s CLI and reads what it prints.
The source
Not public yet. If a page here made you want to read the code behind it, that is exactly the sort of thing that might change it.
The paper
Static HTML, one stylesheet, a few dozen lines of script for the lightbox. The type is Noto Serif Display, cut down to the letters this issue uses and served from here. Nothing on this page phones anywhere.