github Gaurav-Gosain/tuios v0.9.3

4 hours ago

tuios 0.9.3 is a feature release. tuios cp copies files and folders between your machines, and a Copies panel shows each copy. Popup and scratch keys now work in a session on a host, and an image you paste goes to the machine where the pane runs.
window_size now defaults to latest, and each client keeps its rail at the edges of its own screen. Read Upgrading before you update: some defaults and security rules changed.

The full list is at tuios.dev/releases.

Install

brew install tuios                     # Homebrew (macOS, Linux)
yay -S tuios-bin                       # AUR (tuios-web-bin for the web server)
nix run github:Gaurav-Gosain/tuios     # Nix
curl -fsSL https://raw.githubusercontent.com/Gaurav-Gosain/tuios/main/install.sh | bash
go install github.com/Gaurav-Gosain/tuios/cmd/tuios@v0.9.3
docker run -it --rm ghcr.io/gaurav-gosain/tuios:v0.9.3

A binary installed by the script or from an archive updates with tuios update.

New commands

  • tuios cp. Copies files and folders from this machine to a host, from a host to this machine, and from one host to another (#620, #636).
    • tuios cp notes.md build: sends a file to the receive folder on build. That is ~/Downloads/tuios, or the first folder of files_roots on that host.
    • The daemon runs the copy. A copy continues after a dropped link or a daemon restart, from the part it already wrote.
    • Each file has a sha256 check, and lands with an atomic rename. tuios keeps the modification time and the permission bits.
    • A file with the same bytes is not sent again. For a different file with the same name, tuios asks you, or you set --conflict skip|replace|keep-both.
    • Many small files go in one stream. 2,000 small files over a link with 50 ms of delay took 500 s before and take 4.1 s now.
    • --json prints NDJSON. The exit status tells you what happened, for example 4 for a refused copy and 5 for a host that is away.
  • tuios transfers. Lists the copies, and pauses, resumes, cancels, waits on and clears them. tuios hosts test --speed measures a link.
  • tuios call. Sends one daemon verb with a JSON object of params, and prints the reply as JSON. Use it for a verb that has no command of its own. The daemon checks each call as it checks any other, so a pane keeps to its grants (#631).
  • tuios pane-view. Shows one pane with no tuios around it: tuios pane-view -s SESSION -w WINDOW. Keys go to the pane, and it never attaches. While no client shows the session, the pane takes the size and the colours of the viewer (#630).
  • tuios display-env. Prints the display variables of the client that is attached now, for a shell that started before. See display follow.
  • --server NAME. Runs a tuios command against a separate named daemon. That daemon has its own sockets, saved sessions and log. TUIOS_SERVER=NAME does the same. A tuios command in a pane of a named server reaches that server. The config stays shared. tuios --server scratch kill-server stops only that daemon (#680, from #618).
  • tuios pair. Shows a QR code that adds a phone. The phone sends its key, you check the fingerprint, and tuios adds the key to authorized_keys with a forced command (#607). When sshd has no host key, tuios pair says so and tells you to run sudo ssh-keygen -A and start sshd. Do not give --host-key a user key such as ~/.ssh/id_ed25519.pub (#692).

Copies and hosts

  • Copies in the TUI. tuios shows nothing until a copy starts. Then you get a dock item with a bar, a percent mark on the machine's row on the rail, and a notice when the copy ends. The Copies panel (prefix u, or the palette) pauses, cancels and retries copies, and asks about a file that is already there. The rail's files list has Send to machine. You can drop a file on a pane that runs on another machine (#636).
  • Popups on a host. A popup or scratch entry of [[keybindings.command]] now works in a session on another machine. Before, tuios refused it with "A popup command works only in a session on this machine." The command runs on that machine, in the folder of the focused pane there. The host's allow list applies, and a refusal says what to add to it (#667, from discussion #643).
  • Image paste on hosts. A local pane that runs ssh to another machine now gets a pasted image on that machine. Before, tuios saved the image here and sent a path that the far shell could not open. A tunnel (ssh -N) saves the image here. tuios refuses the paste when it cannot read the ssh line, for example with -F or a ProxyCommand (#638).
  • Display follow. A daemon that a host link started over ssh has no WAYLAND_DISPLAY or DISPLAY. Now, when you attach from the desktop of that machine, new panes get the display of your client. wl-copy, wl-paste, xclip and the image paste of Claude Code then work in those panes. A daemon that has a display of its own does not change. [daemon] display = "off" turns the follow off (#637).
  • One daemon per user. A desktop session uses $XDG_RUNTIME_DIR/tuios. A command over ssh often has no XDG_RUNTIME_DIR, and used /tmp/tuios-<uid>. So a host link, hosts sync or a paired phone started a second daemon that could not see your sessions. tuios now uses /run/user/<uid>/tuios when XDG_RUNTIME_DIR is not set (#635). See Upgrading.

Window size and the rail

  • window_size defaults to latest. The session takes the size of the client that last had input, as in tmux 3.1 and later (#641).
  • Each client keeps its own rail. A client that is larger than its session keeps its rail and its dock at the edges of its own screen. Dots fill the space that the session does not use, and a line says which client sets the size. Before, the rail sat just past the panes, with a blank strip after it.
  • No lost columns. Under latest, the panes reach the rail of the client that sets the size, also when another client has a wider rail.
  • A rail on both edges. [appearance.sidebar.left] and [appearance.sidebar.right] turn on a second rail. Each edge has its own sections, width, custom command, scrolling and keyboard focus (#600). Thanks to @drawde-nadroj.
  • New worktree from the rail. Right-click a repository's header or a worktree session's row, and select New worktree. Type a branch, and optionally a session name. tuios makes the worktree and attaches to its session (#652). Thanks to @vvicaretti.
  • Scroll thumb on the rail. A rail section with more rows than it can show draws a scroll thumb on the rail's edge rule. The rows keep their full width. The git section now redraws when you scroll it, and a git section of one line ends in its +N row (#685, fixes #683).
  • Headings on the rail. appearance.sidebar.header_case = "uppercase" draws the section headers as bold headings with a rule to the edge. The default, lowercase, keeps the current look (#604). Thanks to @davidreuss.

Panes

  • Drag a pane to split. In BSP tiling, drop a dragged pane on the edge of another pane. That pane splits, and the dragged pane takes the new half. A drop in the centre still swaps the two panes. While you drag, tuios highlights where the pane will land. appearance.drop_preview sets the look (fill, outline or off), and appearance.drop_preview_delay sets how long the pointer stays on a zone before it shows (#684, fixes #682).

Agents

  • Subagents on the rail. The rail draws a row for each subagent of a Claude Code pane: its mark, its description, and the tool it runs or how it ended. A pane that is done while its subagents still run shows waiting. The Inbox says how many subagents still run (#619, #623).
  • Subagent rail options. New keys in [appearance.sidebar] change these rows. Each key can also go in the table of one rail edge (#640).
    • subagent_rows: rows (the default), tree or count.
    • subagent_collapse: off, expanded or collapsed. A heading that you click, or press space on, opens and closes the rows.
    • subagent_fields: which of mark, name, type, time and note a row shows.
    • subagent_max_rows: the most rows for one pane.
    • subagent_hide_finished: how long a finished subagent stays.
  • pi resumes. A pi pane now gets a resume offer after a daemon restart, as the other bundled harnesses do (#648). Thanks to @davidreuss.
  • Seen mail. Agent mail has a seen state between unread and read. A --peek read of a pane's inbox marks the messages seen. list-agents, the mailbox and the Inbox show it (#622).
  • Phone support in the daemon. These are for a phone client. The protocol is in protocol.md.
    • stream-pane streams the bytes of one pane, and attach-presence proves the person without an attach. A size lease keeps a phone from shrinking your desktop panes (#602).
    • agent-transcript reads an agent's conversation as data: prompts, answers, tool calls, diffs and plans (#603).
    • [notify.webpush] sends Inbox items to a phone with encrypted Web Push, also while the phone sleeps (#608).

Keys and links

  • Cycle workspaces. The new next_workspace and prev_workspace actions go to the next and the previous workspace, and skip empty ones. They have no default key (#616, fixes #612). Thanks to @AdamMagued.
  • Link hover. appearance.link_hover = "always" underlines a link also in a pane whose program reads the mouse. The default, safe, works as before. A plain click still goes to the program (#610). Thanks to @davidreuss.
  • The palette shows your keys. The command palette now shows the key that each row has in your config. Before, it showed the default key, also after you changed or removed it (#645, fixes #634).

Security

  • Files over a link. A new host capability, files, controls the file verbs, apart from write (#620).
    • A machine writes only in its files_roots. The default is one receive folder, ~/Downloads/tuios.
    • A deny list holds inside every root. A link never writes ~/.ssh, ~/.gnupg, credentials, shell start files, login items or tuios's own config.
    • A link never reads keys, credentials, keyrings, shell histories, browser profiles, /proc or tuios's own state. files_read_roots limits reads to the folders you name.
    • files_min_free keeps free space on the disk, 1 GiB by default. A link can have 8 write streams and 64 open parts at most.
    • tuios checks a read on the file it opened, so a rename cannot swap in a denied file. A file with more than one name (a hard link) is not read or copied over a link.
  • The files grant. Under [agents.permissions] mode = "strict", a pane needs the files grant to copy files (#636).
    • The grant holds to the folder where the pane started, or to the session's worktree. A cd or an OSC 7 report does not move it.
    • Your home folder and every folder above it are refused. So is a folder that another user owns, such as /etc.
    • A pane cannot reach past its folder through a pane it starts or types into.
    • Each file of the copy is read and written through that folder.
  • The approval risk root. The outside the worktree mark now measures from a folder the pane cannot move (#650, #606).
    • The root is the session's worktree, then the folder where the pane started, then the folder where the pane's agent started.
    • A relative path is read from the folder where the call runs, and a cd earlier in the command moves that folder. tuios resolves links before it compares paths.
    • When no root can be used, every absolute path counts as outside.
    • A line that the Inbox clipped or redacted is read only as far as it shows, and is marked cut short.
  • Paste needs files. An image pasted over a link now needs the files capability on the far machine (#638).
  • Display follow checks. Only a client on this machine, on the daemon's own socket, as the same user, gives its display to panes. A client in a pane, over a host link, over SSH or from tuios-web never does (#637).
  • Control characters in replies. The daemon now writes DEL and the C1 controls as \u escapes in every reply and event line. A terminal reads a raw U+009B as CSI.
  • Tern panes. A daemon started in a Tern pane no longer gives that pane's identity to every tuios pane. Before, tern open and kitty @ in a tuios pane acted on the Tern pane (#629).

Windows

  • No windows from the daemon. The daemon now starts git, ssh and its other helper programs without a console window. Before, each agent checkpoint opened several windows and took 10 to 18 seconds (#665, fixes #656).
  • herdr plugins from plugins.json. A plugin that herdr lists in plugins.json now runs in a plain folder. Before, it ran in a \\?\C:\... folder, and Node plugins failed with EISDIR (#664, fixes #654).

tuios-web and Learn tuios

These come with sip v0.9.3.

  • Paste. Paste works again. Ctrl+V, Ctrl+Shift+V and Shift+Insert paste, and Cmd+V on macOS. tuios-web --ctrl-v-to-program sends Ctrl+V to the pane, for block select in vim. The clipboard note now names the keys that paste in your browser (#693).
  • Cursor. Cursor shapes and blinking draw correctly in every renderer. When the page loses focus, the cursor becomes a hollow block. The cursor colour that the focused pane sets with OSC 12 now reaches the browser, and OSC 112 resets it. This also works in a native terminal (#704).

Performance and binary size

  • The binary is larger, because of the file copies and the phone support. Two changes take back about 440 KB on linux/amd64 and 350 KB on darwin/arm64.
    • Code that runs rarely shares one sort, in place of a copy of the sort for each type (#669).
    • The skill files are in the binary as one gzipped archive. tuios --skill prints the same text (#670).
  • file-list sorts a large folder in a third of the time.
  • A large write that waits behind other writes to a pane that reads is no longer refused as busy (#611).

Fixes

  • Background in neovim. CSI 2 J now clears the screen to the current background, as the other erase forms do. Before, neovim showed the terminal's own colour past the end of each line (#627, fixes #624). The ghostty build still has this bug.
  • Links with Chinese text. A link whose address had Chinese text made its row wider than the pane, and moved the border and the rail. tuios now percent-encodes the address that it sends to your terminal. The text on screen does not change (#653, fixes #649). A URL in text now also ends at Chinese and Japanese punctuation.
  • Focused border colour. The border of the focused pane now changes colour at once when terminal mode starts. Before, it kept the window mode colour until the program drew something (#647, fixes #642).
  • Reattach. A pane snapshot now keeps the tab stops, the titles, the colours that a program set, insert and newline mode, and a sequence that was cut in the middle. Before, tabs went back to every 8 columns after a reattach, and a cut OSC 8 link printed its second half as text (#628).
  • Top row after a reattach. After a reattach, half an escape sequence no longer moves the cursor, and the output no longer paints over the top row (#701).
  • Focus with two clients. A resize from one client no longer undoes a focus move from another client (#701).
  • Line feed inside CSI. A line feed inside a cursor move no longer runs as a screen erase (#628).
  • Zoom in the scratch. A zoomed pane in the scratch no longer draws the other panes outside the scratch box (#702).
  • Worktrees on macOS. A worktree under /tmp or /var is found again by the herdr API. On macOS these folders are symlinks (#621).
  • The daemon in /tmp. A command in a pane of the daemon in /tmp/tuios-<uid> stays on that daemon. kill-server stops it at once and removes its socket (#635).

Upgrading

The daemon

The daemon protocol did not change, so a v0.9.2 daemon works with a v0.9.3 client, and the reverse. The new fields are optional in both directions. Most of this release is in the daemon. To get it, restart the daemon:

tuios kill-server

This closes every pane in every session, so save your work first. Copies that run when the daemon stops continue after it starts again.

window_size defaults to latest

[daemon] window_size now defaults to latest. The session takes the size of the client that last had input. To keep the old behaviour, set:

[daemon]
window_size = "smallest"

One daemon per user

On Linux, with no XDG_RUNTIME_DIR, tuios now looks for the daemon in this order:

  1. $XDG_RUNTIME_DIR/tuios, when XDG_RUNTIME_DIR is set.
  2. /run/user/<uid>/tuios, when you own /run/user/<uid> and its mode is 0700.
  3. /tmp/tuios-<uid>.

A client still finds a daemon that an older tuios started in /tmp/tuios-<uid>. When two daemons answer, the one in /run/user wins, and a command at a terminal shows a warning once. The warning gives the PID of the other daemon and the steps to move its sessions. macOS does not change.

The files grant for strict panes

Under [agents.permissions] mode = "strict", a pane needs the files grant to copy files. Add it to grants for the panes that need it. A pane started in your home folder, or in a folder above it, cannot copy files. Start it in a project folder or a worktree. Under the default mode = "open", nothing changes.

Approval risk root

The outside the worktree mark can change for some panes:

  • A pane started in your home folder that runs an agent in a project gets that project as its root.
  • A relative path that runs in a folder outside the root is now outside.
  • A path with a variable in it (other than a leading $HOME), or with ~user, is now outside.

Host allow lists

A machine with no [hosts] table gets files with the other default capabilities. A table that sets allow without files keeps that machine from file copies and from image paste. To allow them, add files:

[hosts.laptop]
allow = ["list", "mail", "open", "write", "files"]

Writes over a link land only in ~/Downloads/tuios, unless you set files_roots.

New config keys

The defaults keep the old behaviour, apart from display. The [hosts] lines are examples.

[daemon]
display = "follow"               # follow, off

[appearance]
link_hover = "safe"              # safe, always
drop_preview = "fill"            # fill, outline, off
drop_preview_delay = "120ms"

[appearance.sidebar]
header_case = "lowercase"        # lowercase, uppercase
subagent_rows = "rows"           # rows, tree, count
subagent_collapse = "off"        # off, expanded, collapsed
subagent_fields = "mark,name,time,note"
subagent_max_rows = 0            # 0 to 16, 0 is no limit
subagent_hide_finished = "off"   # a duration, or off

[hosts.laptop]
files_roots = ["~/Downloads/tuios", "~/dev"]   # where the machine may write
files_read_roots = ["~/share"]                 # unset: all but the read deny list
files_min_free = "1GiB"                        # "0" turns it off

[notify.webpush] and the files grant are also new. See CONFIGURATION.md.

For developers

progstatus, the parser and encoder of the Program Status Protocol, is now its own Go module, tuios.dev/progstatus v0.1.0 (#663, #686). The repository has a go.work. To build tuios as go install does, use GOWORK=off.

The repository has a justfile with the commands to build, test and debug tuios on one machine. Run just to list them. scripts/deploy-dev.sh is removed. Use just deploy (#703).

Contributors

Thank you to everyone who sent a pull request for this release:

And thanks to everyone who reported bugs and asked for features: @theutz for workspace cycling (#612), @LovesAsuna for the neovim background (#624) and the links with Chinese text (#649), @MunsuBahk for the palette keys (#634), @agustinvalencia for the border colour (#642), @adamcstephens for popups on hosts (discussion #643), and @EricSanzLuna for the Windows bugs (#654, #656).


Full Changelog: v0.9.2...v0.9.3


Full Changelog: v0.9.2...v0.9.3

Don't miss a new tuios release

NewReleases is sending notifications on new releases.