kolu
Docs

remote terminals · alpha

Your terminals,
on other machines.

kolu can put terminals from other machines right on your canvas. A remote host’s terminals are first-class tiles — same Code tab and file preview, same paste, same session restore — just running on that host over ssh. The canvas shows one host at a time; a host strip in the top bar lets you switch which host you’re looking at, and switching swaps the whole canvas live, with no reload. Each host keeps its own arrangement, so you@box looks the same from your laptop, your desktop, or your phone.

Under the hood that’s padi — kolu’s per-host workspace daemon — bound to the remote over the same ssh transport kaval-tui uses from the shell; Architecture shows the full stack, and Across the hosts is the surface-mirror pattern behind live cross-host data. This is separate from Remote Access, which is about reaching the kolu browser app itself from another device.

Prerequisites

Passwordless ssh

You can ssh to the host without typing a password — an ~/.ssh/config alias or a user@host that just connects.

Nix on the remote

The host has Nix installed and your user is trusted by its nix-daemon — kolu provisions padi there by copying a Nix closure over ssh.

A Nix-wrapped kolu

Launch kolu through its Nix wrapper, which bakes in the per-architecture build map. Supported hosts are the CPU/OS pairs your kolu was built with — Linux and macOS, including cross-arch (a macOS kolu can bind a Linux box).

Enable multi-host

Multi-host is off by default: with nothing set, kolu shows exactly your local host, pixel-identical to single-host kolu. Turn it on by seeding KOLU_PADI_HOST with a comma-separated list of ssh targets:

KOLU_PADI_HOST=localhost,you@box kolu

That seeds the pool — your local host (always present, always first) plus each remote — and a host strip appears in the top bar. Until the pool holds more than the local default, the guest chips and the + add affordance stay hidden, so single-host kolu looks unchanged.

Add a host

The trailing + add on the host strip opens an inline input; what you type is added to the pool as a chip. Two formats work:

  • an ~/.ssh/config alias — zest
  • a user@host destination — srid@zest

Typing a bare loopback spelling — localhost, 127.0.0.1, or ::1 — maps to your existing local host instead of minting a duplicate chip. (user@localhost is not deduped: ssh-ing to the loopback as a different user is a genuinely different session, so it’s treated as a real remote.)

First connect — provisioning over ssh

The first time you switch to a host that has never run padi, kolu provisions it over ssh: it copies the padi closure to the machine with Nix. A cold copy can take minutes. You don’t install anything on the host by hand — the closure carries everything.

The host’s chip dot tells you where it is:

dot meaning
amber connecting or copying — coming up
green connected and live (shown only over a real link — never green over a dead host)
red provisioning or the link failed

Switch hosts, and per-host canvases

Click a chip to make that host active — the whole canvas switches to it live, no reload, bindings staying warm server-side. Each host owns its own canvas: its terminals, splits, and focused tile are restored as you left them when you switch back. Switching away doesn’t tear a host’s work down; it keeps running on that host. If the active host ever leaves the pool, kolu switches you back to local and says so.

A chip also carries an awaiting badge — a count of agents on that host waiting for your input — so a host you’re not looking at can tell you it needs you without your switching to it first. An installed PWA surfaces the same counts as an app-badge and notifications across all your hosts.

When a host is down

If a host’s padi can’t bind, its canvas is replaced by a card that names the cause in plain language and offers two actions — Reconnect (a real re-dial, once you’ve cleared the cause) and Switch to local (the always-available escape hatch). The causes it distinguishes:

card what happened
Another kolu owns this host a different kolu supervisor already holds this host’s padi
This host runs an older kolu the remote padi speaks an older contract and refused to bind
Can’t reach this host unreachable over ssh, or provisioning failed partway
This host’s padi wasn’t built with Nix kolu wasn’t launched through its Nix wrapper, so it has nothing to deploy
No build for this host’s architecture the baked build map has no derivation for the host’s CPU/OS
This host’s padi never settled a contract upgrade’s drain never provably finished

Known limitations

A kolu remote terminal is an ssh session — not the machine’s GUI login session. A class of tools that “just work” in the box’s own terminal lean on that GUI session, so they behave differently over the bind. The following follow from that; each is ssh reality, not a kolu bug, but you’ll meet them here first.

symptom you see why remedy
On macOS, gh reports The token in default is invalid gh keeps its token in the login Keychain, unlocked only for the GUI session a $SSH_CONNECTION-guarded GH_TOKEN (a scoped fine-grained PAT), or security unlock-keychain, or gh auth login --insecure-storage
git push / clone fails Permission denied (publickey) your keys and ssh-agent live in the local session, not the ssh one keep a key on the host, or forward your agent with ssh -A — confirm with ssh-add -l
pbcopy / pbpaste, launchctl, desktop notifications do nothing these are GUI-session services; an ssh session runs in a different session context run them from the box’s own terminal; for launchctl name the domain: launchctl print gui/$(id -u)
a command is “not found”, locale warnings, missing $PATH ssh starts a leaner login shell; GUI-launcher PATH isn’t inherited put PATH/locale in the rc both shells read (~/.zshrc); check with echo $PATH and locale

This table grows by incident: every future “works in my local terminal, breaks over a kolu remote host” report earns a row — symptom, one-line cause, remedy. If you hit one that isn’t here, that’s the signal to add it.