Skip to content

Sessions and Daemon Mode

Gaurav Gosain edited this page Oct 17, 2026 · 29 revisions

Persistent sessions that survive detaching, shared sessions, and what comes back after a restart.

A session is a set of windows and workspaces. By default a background daemon holds your sessions, so they keep running when you close the terminal, and you can attach to them again later, from the same terminal or several at once.

Quick start

tuios                   # attach to the session you used last, or create one
tuios new work          # create a session called "work" and attach to it
# ... work, then press Ctrl+B d to detach ...
tuios ls                # list sessions
tuios attach work       # attach to "work" again
tuios kill-session work # end "work" and every window in it

Daemon sessions and standalone sessions

TUIOS runs a session in one of two ways.

Daemon session (the default). A separate tuios daemon process owns the shells and runs a terminal emulator for each one. The TUIOS you see is a client: it draws what the daemon sends and forwards your keys. Closing the client does not stop anything.

Standalone session. Everything lives in the one tuios process. No daemon starts, no socket is created and nothing is saved. When the process exits, the session is gone.

Ask for a standalone session in any of these ways:

tuios --standalone      # this run only
TUIOS_NO_DAEMON=1 tuios # every tuios started from this shell
[startup]
daemon = false          # every tuios, from the config file

The flag and the environment variable win over the config file. If the daemon fails to start, tuios says so and runs standalone for that run, so you always get a terminal.

The rest of this page is about daemon sessions.

Note

Daemon sessions work on Linux, macOS and Windows. Windows needs build 17063 or later, which added Unix socket support.

Creating sessions

tuios                   # attach to the session you used last, creating one if none exist
tuios new               # create a session with a generated name (session-0, session-1, ...)
tuios new work          # create a session called "work" and attach to it
tuios attach work -c    # attach to "work", creating it if it does not exist

A session's windows start in the directory where you run tuios new. --cwd names a different directory. The directory applies to the first window, and to each later window that has no pane to take a directory from. The session keeps this directory after a daemon restart, also when the directory is missing while the daemon starts.

tuios new api --cwd ~/dev/api

Inside TUIOS, press N in window management mode to create a session.

The daemon starts on its own the first time you need it.

A session name cannot contain / or \, start or end with whitespace, contain control characters, or be . or ... The name is also the name of the file the session is saved to.

Headless sessions

--detach (short -d) creates a session without attaching to it. The session gets one window and is ready for send-text, capture-pane, wait-for and the control protocol straight away. This is the starting point for scripts. A session made by a command with no terminal on stdout (a script, a service, CI) gives its panes TERM=xterm-256color and COLORTERM=truecolor, so programs in them draw as they would under an attached client.

tuios new build --detach
tuios send-text -s build 'make all
'
tuios wait-for window-output -s build --pattern 'make: \*\*\*|Build complete'

Warning

A headless session is 80 columns by 24 rows until a client attaches. Anything you capture before then is 80 columns wide.

Appearance flags such as --theme and --show-keys do nothing with --detach. They apply to a client, and a headless session has none. Passing them is not an error.

Listing sessions

tuios ls
╭──────┬─────────┬──────────┬─────────────┬─────────────╮
│ NAME │ WINDOWS │ STATUS   │ CREATED     │ LAST ACTIVE │
├──────┼─────────┼──────────┼─────────────┼─────────────┤
│ work │ 3       │ detached │ 2 hours ago │ 5 mins ago  │
│ dev  │ 2       │ attached │ 1 day ago   │ just now    │
╰──────┴─────────┴──────────┴─────────────┴─────────────╯
2 sessions

tuios ls --json prints the same list for scripts. When no daemon is running, tuios ls lists the sessions saved on disk instead, marked saved, and exits with status 3.

A session with a label (tuios set-session-name) shows in the table as name (label). The label is not the session's name: scripts and -s keep using the real name.

Spaces

(Added in v0.10.0)

A space is a named group of related sessions. A space is only a label on its sessions. It exists while at least one session is in it.

tuios new api --space payments            # make a session in the space payments
tuios set-session-space payments -s work  # file a session that exists
tuios set-session-space -s work           # take it out of its space

When a session is in a space, tuios ls shows one table for each space, in name order, under the name of the space. The sessions in no space come last, with no heading. tuios ls --json stays one list, with space on each row that has one.

The saved listing (tuios ls with no daemon) and tuios ls --all-hosts group sessions by space too. (Added in v0.10.1) With --all-hosts, the spaces sit inside each host, because a space is a label on one machine. The rail's sessions section shows one heading for each space. (Added in v0.10.1)

  • A space name follows the session name rules and is at most 64 bytes.
  • A daemon restart keeps the space of each session.
  • A global session cannot be in a space.
  • --space does not work with --host.

Moving a window to another session

(Added in v0.10.0)

tuios move-window --to-session NAME moves a window to another session. Its program keeps running, its scrollback stays and it keeps its id. The window goes to the workspace that the session shows, or to the workspace you name. --follow focuses it there.

tuios move-window --to-session review -w build-log

A popup, a scratch pane and a pane on another machine cannot move. The tmux shim's join-pane and move-pane, and herdr's pane.move, also move a pane to another session.

Attaching and detaching

Action How What happens
Attach tuios attach [name] Draws the session in this terminal. With no name, attaches to the session you last typed or clicked in.
Detach Ctrl+B d Saves this client's view to the daemon and exits. The session and its shells keep running.
Quit Ctrl+B q, or q in window mode Opens the quit menu: detach (the default, so q q detaches), switch session, or kill this session.

When you attach, you get the session exactly as you left it: the same windows, positions and screen contents, because the shells never stopped.

A client that dies without detaching, because its terminal closed or its SSH connection dropped, counts as a detach. The daemon notices the connection close and keeps the session running.

Nested attach

Inside a TUIOS pane, tuios attach refuses to attach the session that holds the pane. A bare tuios attach or tuios in a pane also refuses, because it names no session.

  • To show a different session in the pane, name that session: tuios attach other.
  • To attach the same session, open a terminal outside TUIOS.
  • To attach anyway, use tuios attach --force, or set TUIOS_ALLOW_NESTED=1.

The same refusal applies to a chain. For example, session A shows session B in a pane, and a pane of B then attaches A.

TUIOS finds the pane in these ways:

  • The client runs on the pane's terminal.
  • The client's output reaches the pane. This finds script, and ssh from the pane to the same machine or to the TUIOS SSH server. The attach does not wait for this check. If TUIOS finds the pane in the first 2 seconds, it detaches the client with the same message.
  • A client with no terminal runs under the pane's shell, or has the pane's TUIOS_ variables.

A terminal window that you start from a pane can attach, because its output does not reach the pane. New panes do not get the TUIOS_ variables of an outer TUIOS pane.

TUIOS cannot find a client behind tmux or mosh in a pane. These redraw the screen and do not pass the output through. Such an attach goes through, and the session shrinks to 20x6 until the inner client exits. A forced attach also stays at 20x6 or more.

Switching sessions

Ctrl+B S opens the session switcher.

Key Action
Type Filter sessions by name
↑ ↓, Ctrl+P Ctrl+N Move the selection
Enter Switch to the selected session. If nothing matches, create a session with the name you typed.
Ctrl+R Rename the selected session. This changes its real name, not only a display label.
Ctrl+D Kill the selected session, after a y/n confirmation. You cannot kill the session you are on this way.
Ctrl+U Clear the filter
Esc Close without switching

Ctrl+R, the rail's rename, and tuios rename-session all change the session's real name in the daemon. tuios ls, attach and every client show the new name at once, and the rename clears any label set with set-session-name. A pane that is already running keeps the old name in TUIOS_SESSION, and TUIOS commands from it still reach the session, until the session closes or a new session takes that name. attach and kill-session by the old name fail and name the new one. A name that another running or saved session has is refused, and so is an empty name or one with a slash or a control character.

With remote hosts, the switcher also lists the sessions on your other machines. A remote session shows as name @ host, and the filter matches that text. Enter switches to it, and back again, in either direction. The rail, the palette and session cycling switch across machines the same way. Rename and kill work only on sessions on the machine you are on. A rename on another machine reaches this one as that session closing and a new one appearing under the new name.

The session you leave keeps running. Ctrl+B ( and Ctrl+B ) step to the previous and next session without opening the switcher.

tuios switch-session switches a client from the command line, also from a pane. When the target session is not arranged yet, [startup] applies to it, as on a first attach. For example, startup.tiled = true tiles a session that tuios new --detach made.

Sessionizer

A sessionizer opens a project as a session. You pick a folder, and TUIOS shows its session. When the session does not exist, TUIOS makes it in that folder first.

Add this command key to config.toml. It opens a popup with fzf over the folders in ~/dev:

[[keybindings.command]]
key = "prefix+alt+s"
type = "popup"
command = 'dir=$(find ~/dev -mindepth 1 -maxdepth 1 -type d | fzf) && tuios switch-session --create --cwd "$dir" "$(basename "$dir")"'
description = "Open a project"

Press Ctrl+B Alt+S, pick a folder, and press Enter. The client switches to the session of that folder, and its windows start in the folder.

  • tuios switch-session switches the client that shows the popup. Nothing is nested.
  • --create makes the session when it is missing. --cwd sets the folder of its windows. The session keeps this folder after a daemon restart.
  • With startup.tiled = true, the new session is tiled. TUIOS reads this setting from the config of the client that switches. This is also true for a session on a host.
  • To open a project on a remote host, put the host before the name: "build:$(basename "$dir")". The folder is a path on that host.

From a shell outside TUIOS, name the client with -s:

tuios switch-session -s work --create --cwd ~/dev/api api

Scratch terminal

Ctrl+B g shows the scratch terminal in a box over the current layout. Press Ctrl+B g again to hide it. tmux-floax does the same for tmux.

The scratch terminal is a small layout of its own. It starts as one shell. You can split it into more panes, and move, resize and zoom them, as on a workspace. The key shows and hides all of its panes together. For example, keep log tails of three services and a shell for tests in the scratch terminal, and go back to your code with one key.

Hiding does not close a pane. Each shell keeps running with its scrollback. The next Ctrl+B g shows the panes as you left them. The keyboard goes to the pane that had the focus, in terminal mode.

  • Each session has one scratch terminal. TUIOS creates it the first time you press the key.
  • The first shell starts in the folder of the focused pane, or in your home folder.
  • The box shows over the workspace you are on. Its frame shows the name.
  • When you hide it, the focus goes back to the pane you used before, in the mode you used before.
  • The window keys act inside the box while a pane of the scratch terminal has the focus: split, focus movement, resize, swap, zoom, close and multifocus. tuios xpanes that you run in a pane of the scratch terminal opens its panes in the scratch terminal.
  • A pane of the scratch terminal is not minimized, not floated and not moved to a workspace. The dock shows a message for a move.
  • Closing a pane closes it. When you close the last pane, the scratch terminal ends and the box goes away. The next Ctrl+B g starts a new one.
  • A focus on a pane of the hidden scratch terminal, for example with tuios focus-window, shows the box first.
  • A click outside the box hides it and focuses the pane under the pointer. The click does nothing else. A focus on a pane outside the box, by a key or from the sidebar, also hides it.
  • The mouse acts on the panes inside the box, as on a workspace. The box itself does not move or change size. Its size comes from [scratch].
  • The key works from inside the box. TUIOS reads Ctrl+B before the shell gets it.
  • The sidebar, the window list, the window count and the workspace list do not show the panes of the scratch terminal. The tuios list-windows table does not show them. tuios list-windows --json shows each with "scratch": true, its "scratch_name", and a workspace number of 1000 or more.
  • The focus is part of the session. When a client shows the scratch terminal, every client of the session shows it.
  • When you detach, the scratch terminal stays as it is. It is there when you attach again.
  • After a daemon restart, the scratch terminal comes back hidden. Each pane has a new shell in its own folder, under its saved history, in the same layout.
  • In a session on a different machine, the key does not open a scratch terminal. The dock shows a message.
  • Until the daemon restarts after an upgrade, and while a client of an older TUIOS is attached to the session, the scratch terminal is one pane in a popup, as in older versions. The key shows and hides it the same way.
[scratch]
width = "80%"  # cells (100) or percent (80%)
height = "80%"

Set these with tuios set-config, or on the in-app settings page (Ctrl+B ,). The session key is no longer used. TUIOS ignores it, and the config warnings tell you to remove it.

The scratch terminal is part of the current session. It does not make a separate session, so it does not change the session that a bare tuios attach picks.

The first version of this key made a session called scratch. TUIOS does not use that session any more. To remove it, run tuios kill-session scratch.

To add more scratch terminals, each on its own key and with its own command, add [[keybindings.command]] entries of type scratch. See Command keys. Each one has its own layout, and the rules above apply to each of them. One box is on the screen at a time: a show replaces the other. After a daemon restart, the first pane of an entry has a shell, not the command of the entry. To start the command again, close the pane.

The action is toggle_scratch. To use a different key, bind the action in [keybindings.prefix_mode] or in a different section. If your config puts g on a different prefix action, toggle_scratch has no key. tuios keybinds doctor shows this. See Keybindings.

Picture in picture

The picture-in-picture view is a small live copy of one pane in a corner of the screen. Use it to watch a pane, such as a coding agent, while you work in a different pane.

To pin the focused pane, press p in window mode. Press p again, on any pane, to unpin it. The command palette has the same command. From a shell, run tuios pip <window> to pin a pane by ID or name, and tuios pip --off to unpin it.

  • The view is not the pane. The pane stays where it is in the layout. It keeps running when it is on a different workspace, minimized or in a hidden scratch terminal.
  • The view shows the last rows of the pane that hold text, from the left edge, at one cell for each cell. Its border shows the pane name and the agent state mark. It shows text only. Images do not show in it.
  • The view is on top of the tiled panes and a zoomed pane. Popups, the scratch terminal, menus and panels are on top of the view.
  • The view never takes the focus or a key.
  • A click on the view focuses the pane. TUIOS goes to the workspace of the pane, restores a minimized pane and shows a hidden scratch terminal.
  • The view does not show while its pane has the focus.
  • The view does not cover the cursor of the focused pane or the text to the left of the cursor. When that text reaches the view, the view moves to a different corner.
  • When the pane closes, the view closes and the dock shows a message.
  • Each client has its own view. The view is not in the session state, so a different client does not see it. A detach or a switch to a different session removes it.
  • tuios pip reaches one client. With more than one client attached, it acts on the client that tuios run-command reaches. When you attach to a session on a different machine, that machine cannot pin a pane on your screen.
  • A kitty image in a pane under the view is cut around the view. A sixel image is not, so it can draw over the view. Floating panes have the same limit.
  • One pane at a time is pinned. A pin replaces the previous one.
  • The view reads the cells that the pane already holds. It adds no timer and does no work while the pane is quiet.
[pip]
width = 40               # cells, border included
height = 12
corner = "bottom-right"  # bottom-right, bottom-left, top-right or top-left

The action is toggle_pip. If your config puts p on a different window mode action, toggle_pip has no key. tuios keybinds doctor shows this. See Keybindings.

Sharing a session

Several clients can attach to one session at the same time, for example two terminals on one machine, or a terminal and an SSH login.

  • Every client sees the same windows, workspaces and output, and changes made in one show up in the others.
  • The session has one size for all of its clients. daemon.window_size selects the client that sets it. See Session size with more than one client.
  • Each client keeps its own input mode and copy mode position, so one person entering terminal mode does not change what another is doing.
  • Focus is shared. The newest focus change wins, from any client or from a command such as tuios focus-window.
  • Each change to the tiling tree goes to the daemon, which applies it and gives it a number. Two clients that change one layout at the same time end on the same layout.
  • Each client shows a short notice when another client joins or leaves.
# Terminal 1
tuios new pairing

# Terminal 2
tuios attach pairing

Warning

Every attached client has full control: it sees all output, types into every shell and can close windows. The one exception is tuios-web --read-only, which ignores all input from browsers. Local clients are limited to your own user by the socket's permissions. SSH clients are limited by tuios ssh authentication, and browser clients by the tuios-web password. See Who can connect.

Session size with more than one client

A pane has one size, so a session has one size for all of its clients. window_size in [daemon] selects the client that sets it. The values are those of the tmux window-size option.

Value The session uses
smallest The smallest client. This is the default. Every client shows the full session. A larger client shows empty space around it
largest The largest client
latest The client that last had input
[daemon]
window_size = "latest"

For latest, input is a key, a paste, a click, a drag or a turn of the mouse wheel. A resize, a focus change and a reply that your terminal sends by itself are not input. So a phone that only attaches does not take the session from the person who types on a laptop. A client keeps the session for 1 second after its last input, so two people who type at the same time do not resize the session on each key. When the latest client detaches, the session goes to the client with the most recent input.

A client that is smaller than the session shows the part around the cursor of the focused pane, as tmux does. In copy mode it follows the copy-mode cursor. An agent CLI hides its cursor in its input box, so you see the input box. A mark at the right end of the line above the dock shows the size of the session, with arrows that point to the parts you cannot see. The rail and the dock stay at the edges of your screen.

To change the value for one session while it runs:

tuios set-config daemon.window_size latest -s mysession
tuios session-info -s mysession --json   # window_size, session_width, session_height

Under the tmux shim, tmux set -g window-size latest does the same.

A client that cannot send input, such as tuios-web --read-only, does not set the size under largest or latest. Under smallest it counts like every other client. A tool that uses only the verb socket, such as Collie, does not change the size. A client from a version of TUIOS before window_size makes the session use smallest while it is attached.

The rail with more than one client

The rail is part of the session. When you show or hide it on one client, every client of the session shows or hides it. The panes then take the new space on every client.

A new session takes the rail setting of the first client that attaches, from appearance.sidebar.enabled. A later client with a different setting uses the session's value. When you toggle the rail, TUIOS also keeps the new value in your config for the next new session. A reload of the config file does not change the rail of a session that is running.

The width of the rail stays per client. A client too narrow to show the rail does not change it for the session. When the clients use different widths, the panes use the space next to the widest rail. A client with a narrower rail shows the difference as empty space.

A client from an older version of TUIOS keeps its rail to itself. A new client attached to an older daemon also keeps its rail to itself.

Detaching other clients

tuios attach -d NAME attaches and detaches every other client of the session, as tmux attach -d does. tuios detach-client detaches clients without an attach. single_client = true in [daemon] makes every attach detach the other clients. The session continues to run, and each detached client exits with a message.

tuios attach -d work
tuios detach-client -s work --all-other   # keep the client used last in work

See Detaching other clients on tuios.dev.

The ssh agent in panes

With ssh_agent = "follow" in [daemon], the panes of a session use the ssh agent of the client that you attached from last. See SSH Agent on tuios.dev.

Session resurrection

Detaching loses nothing, because the daemon keeps running. Resurrection is what happens when the daemon itself stops: tuios kill-server, a crash, or a reboot. It brings back the shape of each session and the history each pane showed, not what was running in it.

When state is saved

Each session is written to disk within a couple of seconds of any change to its windows or layout, every 30 seconds regardless (which keeps each window's working directory current), and once more on a clean shutdown. The write is atomic, so a crash while saving cannot leave a half-written file.

When the daemon starts, it restores every saved session before it accepts connections.

What comes back

  • Every window, with its position, size, title and custom name
  • Workspace assignment, minimize state and which window had focus
  • The BSP tree for each workspace and the layout mode
  • The working directory of each window
  • Session options
  • Each pane's history, above a dimmed divider. See Pane history.

What does not come back

Warning

Resurrection is crash recovery, not a snapshot. Your layout comes back and each window starts in the right directory, but whatever was running in it is gone.

  • Processes. Every window gets a fresh shell. A vim, a build or an SSH connection that was running is not restarted. A coding agent's conversation can come back, though: for a pane whose agent was running and whose hook reported the conversation id, the Inbox offers to resume it (daemon.resume_agents). See Resuming after a restart.
  • The last few seconds before a crash. A window created just before a SIGKILL may be missing, and a cd made less than 30 seconds before it may not be recorded.
  • Your terminal's environment. Restored shells inherit the daemon's environment and $SHELL, not those of the terminal you later attach from.
  • Working directories on Windows and the BSDs. The daemon asks the kernel where each shell is, which works on Linux and macOS. Elsewhere, restored windows open in the shell's default directory.

A restored window prints a dimmed line so you do not mistake it for the old shell:

-- tuios: session restored, fresh shell in /home/you/project --

When the pane has saved history, the line is a divider under that history, with the time of the save:

$ make
...the last lines of the build...
$
-- tuios: restored from Sep 30 14:02, fresh shell in /home/you/project --
$ _

The in <dir> part is left out when the directory is unknown. Restored shells also get TUIOS_RESTORED=1 in their environment, so your shell profile can react.

The session is marked too. It shows a restored tag in tuios ls, the sidebar and the session switcher, and tuios attach prints a notice before drawing it. The mark clears on the first attach.

What survives each kind of interruption

Detach, client crash, SSH drop kill-server, SIGTERM Daemon crash (SIGKILL, OOM) Reboot
Session exists afterwards Yes Yes, restored Yes, restored Yes, restored
Window layout Yes Yes As of the last save As of the last save
Working directories Yes Yes, on Linux and macOS As of the last save, on Linux and macOS As of the last save, on Linux and macOS
Shell processes and running programs Yes No No No
Screen contents and scrollback Yes Yes, as history above the divider As of the pane's last save, up to 30 seconds old Yes, as history above the divider
Rail shown or hidden Yes Yes As of the last save As of the last save
Copy mode position, input mode No, these are per client No No No

Pane history

The daemon saves each pane's history next to the session's state, and a restore shows it again. The history is text to read. It is not replayed into the new shell, and no command in it runs again. Copy mode, search and hints reach it like any other history.

  • What is saved. The last 1000 lines of scrollback by default, and the screen. Colors, styles, wide characters and wrapped lines come back as they were.
  • Full-screen programs. When vim or htop was open, the shell's screen under it is saved. The program's own screen is not.
  • Popups. The scratch terminal's history comes back. Other popups do not come back, so their history is not saved.
  • Other machines. A pane that ran on another machine comes back as a shell on this machine, without history.
  • Agents. The resume offer still works. The resumed agent starts under the divider.
  • When. At a clean stop, for every pane with new output. During the session, a pane with new output is saved at most once in 30 seconds. An idle pane is not written again.
  • Size. Each pane's file is compressed and at most 2 MiB by default. One session's files are at most 16 MiB together. A pane that does not fit saves fewer lines.
  • Cleanup. A pane's file goes when the pane closes. A session's files go when the session is killed. When the daemon starts, it deletes history that has no saved session.

The files are in $XDG_STATE_HOME/tuios/sessions/scrollback/<session>/, with mode 0600 in a directory with mode 0700.

Warning

The history files hold what your panes printed, which can include passwords, tokens and other secrets. Set persist_scrollback = false in [daemon] to stop saving it. The next time the daemon starts, it deletes the history it saved before.

[daemon]
persist_scrollback = true         # save each pane's history (default true)
persist_scrollback_lines = 1000   # most history lines one pane saves (0 = 1000)
persist_scrollback_kb = 2048      # most KiB one pane's file takes (0 = 2048)

The daemon reads these settings when it starts.

Controlling it

tuios resurrect            # list saved sessions, whether each is live, and when it was saved
tuios resurrect work       # restore "work" from saved state and attach to it
tuios daemon --no-restore  # start a daemon that restores nothing on start

tuios resurrect <name> starts the daemon if needed. If the daemon already restored that session, it simply attaches. restore is an alias.

tuios kill-session deletes the session's saved state, and so does killing it from the quit menu. A session you killed on purpose never comes back.

A state file that is corrupt, or written by a newer TUIOS, is moved to an archive directory and skipped, so one bad file never stops the daemon from starting.

Sessions on other machines

The daemon can reach TUIOS on other machines over ssh. Name a machine once:

tuios hosts add build you@buildbox   # any address ssh understands, including an ssh_config alias
tuios hosts test build               # check the link

Then:

tuios attach --host build api        # draw the session "api" on build, in this terminal
tuios new --host build ci -d         # create a session on build without attaching
tuios new-window deploy --host build # a window in this session whose shell runs on build

A window whose process runs elsewhere shows the machine in its title, as build:deploy. tuios new NAME --global creates a session meant to hold windows from several machines. The daemon runs ssh with BatchMode on, so ssh to the host once by hand first to accept its key.

See the CLI Reference for tuios hosts, including tuios hosts tailnet for machines on a Tailscale tailnet.

Daemon location

What Where
Socket, Linux and macOS $XDG_RUNTIME_DIR/tuios/tuios.sock. When XDG_RUNTIME_DIR is unset: /run/user/<uid>/tuios/tuios.sock on Linux if /run/user/<uid> is yours with mode 0700, and /tmp/tuios-<uid>/tuios.sock otherwise (Added in v0.9.3). See One daemon per user
Socket, Windows %LOCALAPPDATA%\tuios\tuios.sock
PID file The socket path with .pid appended
Saved sessions $XDG_STATE_HOME/tuios/sessions/<name>.json, usually ~/.local/state/tuios/sessions/, readable only by you
Archived bad state $XDG_STATE_HOME/tuios/sessions/archive/, pruned after 14 days

Whoever controls the socket's directory could put their own socket in its place and read what every client sends. When tuios falls back to /tmp/tuios-<uid>, the directory is a name any local user could create first. So TUIOS creates the directory with mode 0700, and before using one that exists it checks that it is a real directory (not a symlink) owned by you. It refuses one that is not, and tells you to set XDG_RUNTIME_DIR to a directory you own. A directory you own that others can open is closed to them.

One daemon per user

(Added in v0.9.3)

A desktop session sets XDG_RUNTIME_DIR. A command run over ssh often does not: a host link, tuios hosts sync, or a paired device. tuios picks the same socket for both, so one user on one machine always reaches one daemon:

  1. $XDG_RUNTIME_DIR/tuios, when the variable is set.
  2. /run/user/<uid>/tuios, when the variable is unset and /run/user/<uid> is a folder that belongs to you and has mode 0700. systemd makes it at login, for ssh logins too. macOS skips this step.
  3. /tmp/tuios-<uid> otherwise.

An older tuios started its daemon in /tmp/tuios-<uid> when it ran over ssh. When the socket comes from step 1 or 2, a command also looks for a live daemon in /tmp/tuios-<uid>:

  • Only the daemon in /tmp answers: the command uses it and does not start a second daemon. After that daemon stops, the next one starts in /run/user.
  • Both answer: the daemon in /run/user wins. A command in a pane of the daemon in /tmp still reaches its own daemon, which TUIOS_SOCKET names. A command at a terminal says once that the other daemon runs, and gives its process ID. See Two daemons on one machine.

tuios looks in /tmp/tuios-<uid> only when that folder belongs to you and only you can open it.

To run the daemon in the foreground with logging, for debugging:

tuios daemon --log-level=messages

tuios kill-server stops the daemon and every session. It waits until every session is saved and the socket is removed, so you can start a new daemon as soon as it returns.

A separate daemon

(Added in v0.9.3)

--server NAME runs a command against a separate daemon with the name NAME. This is like tmux -L NAME. A named daemon has its own socket, saved sessions and daemon log. The default daemon and other named daemons do not see its sessions. All daemons use the same config file.

tuios --server scratch new work --detach   # starts the scratch daemon
tuios --server scratch ls                  # lists its sessions
tuios ls                                   # the default daemon does not change
tuios --server scratch kill-server         # stops only the scratch daemon
  • TUIOS_SERVER=NAME does the same for every tuios command in a shell. --server wins over it.
  • A named daemon sets TUIOS_SERVER in its panes. So a tuios command in one of its panes reaches that daemon. The tmux and herdr links do too.
  • --server "" reaches the default daemon from such a pane.
  • A name has 1 to 14 characters: letters, digits, - and _.
  • The socket is in servers/NAME in the socket folder of the default daemon. The state is in $XDG_STATE_HOME/tuios-servers/NAME.
  • tuios ls --json gives each session of a named daemon a server field. A session of the default daemon has none.

XDG_RUNTIME_DIR and XDG_STATE_HOME do not change in a pane of a named daemon. Programs that use these folders, such as a Wayland client or systemctl --user, work as in any other pane.

How it works

Pick a scenario to see which process does the work:

Tip

This part is interactive on the docs site. Open it on tuios.dev.

The daemon owns the PTYs and keeps an emulator fed for each one, so output is parsed whether or not a client is watching. When a client attaches, it asks the daemon for each window's screen and scrollback and paints them.

Remote control

Scripts can drive a running session through CLI commands that talk to the daemon. They all take -s <session> (default: the most recently active session) and most take -w <window>, a window name or ID (default: the focused window).

Run commands in a window

# Type a line and press Enter (the trailing newline submits it)
tuios send-text -w build 'go test ./...
'

# Send keys by name, as if typed at TUIOS
tuios send-keys 'ctrl+b,c'

send-text writes its argument to the shell exactly as given. send-keys parses key names and splits on spaces and commas, which suits chords and control keys. Use send-text for anything that contains spaces.

Open windows

tuios new-window build                    # prints the new window's ID and name
tuios new-window htop /usr/bin/htop       # run a program instead of a shell
tuios new-window --json logs | jq -r .window_id

Wait and read

tuios wait-for window-output -w build --pattern 'PASS|FAIL' --timeout 600000
tuios wait-for window-idle -w build --idle 2000
tuios capture-pane -w build --lines 40
tuios capture-pane -w build --scrollback > build.log

wait-for blocks until the condition matches and exits non-zero on timeout, so you do not need a poll loop.

Inspect

tuios list-windows --json   # every window, with window_id, display_name, workspace and more
tuios get-window build      # one window
tuios session-info          # the session

Run any command

tuios run-command runs any tape command, and any keybinding action by its name. tuios run-command --list prints the commands, and tuios keybinds list the actions.

tuios run-command ToggleTiling
tuios run-command SwitchWorkspace 2
tuios run-command --json NewWindow dev

With --json, the result fields sit at the top level, so the new window's ID is at .window_id.

Example: a development layout

#!/bin/sh
tuios new dev --detach
tuios new-window -s dev editor
tuios new-window -s dev server
tuios new-window -s dev logs

tuios send-text -s dev -w editor 'nvim .
'
tuios send-text -s dev -w server 'npm run dev
'
tuios send-text -s dev -w logs 'tail -f app.log
'

tuios attach dev

Example: pick a window with fzf

#!/bin/sh
WINDOW=$(tuios list-windows --json |
  jq -r '.windows[] | "\(.display_name)\t\(.window_id)"' |
  fzf --with-nth=1 | cut -f2)

[ -n "$WINDOW" ] && tuios focus-window "$WINDOW"

Beyond the CLI

Each CLI call opens its own connection. For a long-running program, speak the JSON control protocol on the daemon socket directly: structured errors, blocking waits and a live event stream. Run tuios list-verbs to see every verb. The CLI Reference documents every command and flag.

Troubleshooting

"Daemon not running" or a stale socket

tuios kill-server   # stops the daemon, or removes a stale socket if none is running
tuios               # starts a fresh daemon

Warning

This does not give you a clean slate. kill-server saves every session on the way out, and the next daemon restores them. To start a daemon with no sessions, run tuios daemon --no-restore.

Two daemons on one machine

(Added in v0.9.3)

A session that you made on one machine does not show in tuios ls on the same machine, or a host link shows other sessions than your desktop. Two daemons run for your user, each with its own sessions.

The cause is an older tuios. It put the socket in /tmp/tuios-<uid> when a command ran without XDG_RUNTIME_DIR, as a command over ssh often does. The desktop used /run/user/<uid>/tuios. tuios now uses /run/user/<uid>/tuios for both. See One daemon per user.

To find the daemons, look at both sockets:

ls -l /run/user/$(id -u)/tuios/tuios.sock /tmp/tuios-$(id -u)/tuios.sock

When both daemons run, tuios uses the one in /run/user. To move the sessions of the daemon in /tmp:

  1. Stop it: kill $(cat /tmp/tuios-$(id -u)/tuios.sock.pid). It saves every session when it stops.
  2. List what it saved: tuios resurrect.
  3. Restore each session: tuios resurrect NAME.

A session name that both daemons used has one saved file. Rename one of the two sessions before you stop that daemon if you need both. Upgrade tuios on every machine. A machine with the older tuios still starts a daemon in /tmp over ssh.

Which TERM a pane gets

A pane gets the TERM of the client that made the session, if the machine that runs the pane has a terminfo entry for it. This also holds for a session that tuios call new-session, tuios switch-session --create, an attached tuios new, start-agent, new-worktree or fan makes. (Added in v0.10.0) The SSH server, tuios-web and the GUI bridge send no TERM, so their sessions get xterm-256color. If it has none, the pane gets xterm-256color, so clear, tput and curses programs still work. To keep your terminal's own TERM, install its terminfo entry on that machine, for example with infocmp -x xterm-kitty | ssh HOST tic -x -.

"Session not found"

The name is wrong or the session was killed. tuios ls lists the live ones, and tuios resurrect lists the saved ones.

A session stopped responding

tuios kill-session stuck
tuios new fresh

tuios logs shows the daemon log.

Related

Clone this wiki locally