A while back I wrote about building a terminal because I could never remember the keybindings. That terminal is tsumugi, and I just shipped 0.3. This post is about the three things that went in — a workspace command, screenshot paste, and self-update — and why each one is shaped the way it is.
github.com/hyuga611/tsumugi — Rust, MIT OR Apache-2.0.
The premise
Short version, since the first post covered it. tsumugi is built on one claim:
The grid is a document. The process is appending to its end.
Your scrollback is not a log, it is what you just did, so it should be readable, selectable and reusable. Vim motions over scrollback, ac to select a command with its output, :e to turn the pane into an editor — they all fall out of that one line.
And one rule on top: every command has a mouse path, enforced in CI. A test walks the command registry and fails if any command lacks one.
Those two decided the shape of almost everything below.
1. go builds a workspace in one message
cd ~/dev/tsumugi
go
The pane you are in becomes the middle one, a directory tree opens on the left and an AI agent on the right, and the tab is renamed after the directory. 20 / 50 / 30. One tab per repository, and the tab names tell you which is which.
One message, not four
Sending "split", "open the tree", "start the agent", "rename the tab" separately costs you twice: intermediate layouts get broadcast and the screen jumps, and you end up needing "the id of the pane that was just created" in the next message — an async hole. So it is a single ClientMsg::Workspace. The server assembles it and broadcasts once.
The tree has no motions of its own
The tree implements Buffer. That is the whole trick: j k gg G /, visual selection and the mouse already work, because they are the same ones you use on the terminal. I did not write a single tree-specific motion.
Only a handful of keys are added inside the tree: Enter opens, l / h fold a branch, a / A create, r renames, R reloads. They are not in the default keymap — a and r meaning something else in a terminal would be wrong. Double-click opens, drag and drop moves. :q closes the tree and the pane goes back to being a shell, with the shell underneath still running.
There is no delete. An operation you cannot undo does not belong on a list your finger slides across. Delete from the shell in the middle pane.
The listing is read server-side
With [domains], the files are on the far machine, so the tree is read there. Only branches you have opened are walked, so a directory with node_modules or target in it stays fast.
It coexists with the Go toolchain
go is a function the shell integration installs; it calls tsg --workspace. Anything with arguments is forwarded to the real go, so go build still works, and a bare go outside tsumugi still runs Go. TSUMUGI_NO_GO=1 gives the name back. From the keyboard it is Space then w; from the palette, :go.
2. Paste a screenshot into what you are telling the agent
Ctrl+Shift+V writes the clipboard image out as a PNG and types its path into the prompt — it does not press Enter, so you can keep writing before you send. Dropping an image file on the window lands in the same place.
No new command was added. Paste is one verb that does the closest thing to what it is holding: text if text, a path if a picture. Adding verbs adds things to remember.
The server writes the file, into a per-session temp area — so when you are connected to a [domains] host, the file appears where the agent reading it lives. The last 32 are kept.
3. tsg update starts no external process
tsg update fetches the latest release and swaps it in. It downloads nothing when you are already on the latest.
Why the download-and-run shape had to go
The first version ran powershell -Command "irm <URL> | iex" inside itself, to keep exactly one way of installing. But that shape is indistinguishable from malware that downloads and runs code. Windows Defender on a corporate machine blocked it — the process was denied (os error 5) and the installed binary was removed along with it.
The machines that need it most are the strictest, so that is exactly where it broke. It now pulls the release over HTTPS itself (ureq / rustls) and replaces the file. No child process at all. The first-time install.ps1 / install.sh are unchanged — there is no tsg yet, so that one has to start from outside.
After the swap, no reopening and no --kill
tsg update reopens the window on the new build. The multiplexer is a separate process, so replacing only the window reconnects you with your shells and agents still alive. I watched the window process change while the multiplexer's PID stayed the same.
It checks before it commits: it asks the new binary for its protocol version (--protocol) and reopens only if it matches the running server. Only when they differ does it become a conversation about stopping sessions — and it does not stop them for you, because stopping one ends the shells and agents inside it.
Related decision: the protocol version is not bumped for additive changes. Bumping it means a running server stops accepting new windows, which means killing the shells inside. It moves only when the meaning of an existing message changes.
Install
No admin rights.
Windows (PowerShell)
irm https://raw.githubusercontent.com/hyuga611/tsumugi/main/install.ps1 | iex
That one line leaves you with a working setup: the binary in %USERPROFILE%\bin, Start Menu / PATH / folder context menu, the shell integration, and the Claude Code and Codex hooks when they are present.
It used to be three steps, and skipping the last two left you with a terminal where the gutter, [[, ac and go all did nothing — installed but not working. It was one errand for the person typing, so it is one command now. Every change is printed, and tsg --uninstall takes all of it back out.
The CRT is linked statically, so it runs on machines without the VC++ redistributable.
macOS / Linux (experimental)
curl -fsSL https://raw.githubusercontent.com/hyuga611/tsumugi/main/install.sh | sh
Untested. CI builds and tests on three OSes, but the window (winit / wgpu), the IME and
--installhave parts written against Windows APIs, and nobody has run them yet. Shipping the binaries is to make trying it easy, not a promise that it works. Reports very welcome.
Check it took — a shell reads its config at startup, so the window you typed in does not have it yet. Open a new one:
tsg -V # prints the version
Get-Command go # Function means the shell integration took
You need no configuration to use an agent. The waiting mark on the tab and the notifications come from the hooks, so typing claude or codex in a pane just works. Configuration only decides what go starts on the right.
[shell]
program = "pwsh"
[workspace]
agent = "claude"
Updating
tsg update
Windows SmartScreen will warn on first launch — the binary is unsigned. More info, then Run anyway.
Also fixed in 0.3.x
Twelve patch releases, almost all of them from running it on a different machine — a locked-down corporate laptop. A sample:
- Windows PowerShell 5.1 and PowerShell 7 read different profiles; the installer wrote to whichever it found first. Now it writes to all of them.
- It guessed where
$PROFILEwas. If you have never created one, neither directory exists and it gave up. Now it asks PowerShell. - The default shell was
cmd.exe(fromCOMSPEC) — and cmd.exe has no way to emit OSC 133, which everything in tsumugi is built on. Everyone who installed it started from a state where nothing worked. - Agent state was landing on the wrong pane: the hook ran
tsg --agent-statewithout naming a pane, so it attached to whichever was selected. Two agents, one badge that never clears. It readsTSUMUGI_PANEnow. - Three bugs on top of Claude Code, all one root cause: ownership was decided by alt screen alone, and some tools paint the whole screen without it.
And 0.3.13 is the four I hit while taking the screenshot for this post. Worst first:
-
[workspace] agent = "claude"built no workspace at all — the headline feature of this post, with the configuration this post recommends. npm puts bothclaude(an extensionless shell script) andclaude.cmdon Windows;CreateProcessWdoes not consult PATHEXT, so a bare name hits the former and dies withos error 193. A bare name is now resolved against the extensionsCreateProcessWcan actually start. -
That reason never reached the person who typed it.
tsg --workspacesent the request, slept 200ms and detached without reading the reply, so the server's error went nowhere.gocalls exactly that, so all you saw was nothing happening. -
A failed
goleaked a pane. The tree spawns before the agent; when the agent failed, the tree stayed alive and attached to no tab. One more every time you retried. -
The shell integration could be dead in every pane (PowerShell only). The guard against double-sourcing was an environment variable — and environment variables are inherited by children. Start the mux from a shell that already sourced it (that is: type
tsgin your terminal) and every pane's shell decides it has already run. Gutter,[[ ]],ac/ioandgoall go dark. Launch from the Start Menu and you never see it; typetsgin a terminal and you always do.
Both halves of that list say the same thing. "Installed but not working" never reproduces on the machine of the person who installed it. Every test passed here.
Full list in the CHANGELOG.
Top comments (0)