kolu

alpha — kaval and kaval-tui are an early preview. Commands, flags, and output formats will change before they're finalized for production use; don't script against them yet.

the PTY daemon & its client · alpha

a watch over
your terminals.

kaval (Tamil kāval — watch, guard; said KAH-val, the first a long, as in father) is a small, standalone PTY daemon: it owns your shells, mirrors their screens, and serves them over a local unix socket — outliving the clients that come and go. kaval-tui is its terminal client: list your shells, create new ones, dump their scrollback, or attach and type — locally, or on a remote machine over ssh (--host). Run the pair on a box where kolu has never been installed — a tmux/zellij-shaped duo, minus the multiplexer's session model.

run the pair

  1. 1

    Start the daemon. It claims its socket and stands watch — leave it running in a pane, a service, wherever.

    nix run github:juspay/kolu#kaval

  2. 2

    Drive it from any other shell. kaval-tui finds the running daemon on its own — no socket path to remember.

    nix run github:juspay/kolu#kaval-tui -- create
    nix run github:juspay/kolu#kaval-tui -- list
    nix run github:juspay/kolu#kaval-tui -- attach <id>

drive a running kolu

kolu doesn't run terminals in-process anymore — it spawns a kaval daemon of its own and is just another client of it. So the same kaval-tui reaches the terminals you have open in kolu, from the shell, with no flags:

kaval-tui list # the terminals open in your kolu
kaval-tui snapshot <id> | grep BUILD-

kaval-tui discovers the daemon by scanning the per-user runtime dir — both a standalone kaval and every kolu (each kolu-server namespaces its daemon by listen port). One running → it's picked automatically. More than one → kaval-tui lists them and asks you to choose with --socket <path>.

reach a remote kaval — over ssh

--host <ssh> drives a kaval on another machine, with nothing to install there first. kaval-tui provisions the daemon with Nix (ships the right-arch build over ssh, realises it), runs it, and dials it — every subcommand works exactly as it does locally, just pointed at the remote.

kaval-tui create --host nix@prod
kaval-tui list   --host nix@prod
kaval-tui attach --host nix@prod <id>

The remote daemon is durable: a terminal you create outlives the ssh link, so create on prod and attach to it later — even after you closed the laptop and changed networks. One shared daemon per host. --host is mutually exclusive with --socket; it needs passwordless ssh and your user trusted by the remote's Nix daemon.

A remote terminal runs in the host's environment, not yours: its $SHELL, $HOME, and $PATH come from the remote machine (so its own commands resolve), and only your terminal's presentation vars (TERM, COLORTERM, LANG/LC_*) are carried across. Your local environment — and any secrets in it — never crosses the wire.

what kaval owns

Under the daemon sits a single primitive — a multi-client PTY owner. One host owns any number of PTYs; each PTY is a real shell child paired with a headless screen mirror, fanned out to any number of consumers. It owns only the PTY: it knows nothing about git, pull requests, agent detection, or any wire protocol — those compose on top. The same primitive backs kolu's terminals; kaval just serves it over a socket.

race-free attach

A late-joining client gets a screen snapshot and then live deltas with no gap and no overlap — every byte lands in exactly one of the two, so the screen reconstructs perfectly and then streams.

drop-slow-subscriber

A wedged client that stops draining its output is dropped rather than pinning the daemon's memory without bound — kaval-tui then transparently re-subscribes and gets a fresh snapshot.

taps kaval surfaces per terminal

screen

snapshot + live deltas

cwd

from OSC 7 reports

title

from OSC 0/2 changes

command

from OSC 633 preexec

exit

the child's exit code

commands · kaval-tui

kaval-tui list
--json

One row per live terminal — id · pid · idle · cmd · cwd. --json emits a top-level array for jq.

kaval-tui create
[-- cmd] · --json

Spawn a new terminal on the daemon and print its short id — a plain $SHELL, or a command you pass (create -- htop -d 5). A freshly-started daemon owns nothing, so create is what attach needs first; the daemon then holds the terminal until something kills it. --json emits { id, pid, cwd }.

kaval-tui snapshot <id>

Print a terminal's current scrollback as plain text and exit — built for piping and grepping (a trailer line goes to stderr, so stdout stays clean).

kaval-tui attach <id>
--escape <char>

Take the terminal over, full screen. Raw passthrough — every keystroke and chord reaches the inner program; your window size follows along. Detach with the escape below.

detaching — the ssh model

While attached, nothing is intercepted except a ~ typed at the start of a line (right after Enter — session start counts too). Mid-line tildes, every Ctrl chord, and pasted text all pass straight through, so the program inside never loses a key.

~.

detach — kaval-tui exits, the daemon keeps the terminal; re-attach anytime

~~

send one literal ~ to the shell

~?

show this escape help

// ~ clashes (nested ssh?) — rebind it: kaval-tui attach <id> --escape %

fine print