Repository navigation
Sessions and Daemon Mode
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.
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 itTUIOS 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 fileThe 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.
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 existA 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/apiInside 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.
--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.
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.
(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 spaceWhen 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.
-
--spacedoes not work with--host.
(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-logA 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.
| 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.
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 setTUIOS_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.
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.
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-sessionswitches the client that shows the popup. Nothing is nested. -
--createmakes the session when it is missing.--cwdsets 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 apiCtrl+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 xpanesthat 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-windowstable does not show them.tuios list-windows --jsonshows 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.
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 pipreaches one client. With more than one client attached, it acts on the client thattuios run-commandreaches. 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-leftThe 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.
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_sizeselects 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 pairingWarning
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.
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_heightUnder 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 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.
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 workSee Detaching other clients on tuios.dev.
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.
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.
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.
- 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.
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
SIGKILLmay be missing, and acdmade 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.
| 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 |
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
vimorhtopwas 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.
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 starttuios 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.
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 linkThen:
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 buildA 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.
| 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.
(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:
-
$XDG_RUNTIME_DIR/tuios, when the variable is set. -
/run/user/<uid>/tuios, when the variable is unset and/run/user/<uid>is a folder that belongs to you and has mode0700. systemd makes it at login, for ssh logins too. macOS skips this step. -
/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
/tmpanswers: 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/userwins. A command in a pane of the daemon in/tmpstill reaches its own daemon, whichTUIOS_SOCKETnames. 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=messagestuios 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.
(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=NAMEdoes the same for everytuioscommand in a shell.--serverwins over it. - A named daemon sets
TUIOS_SERVERin its panes. So atuioscommand in one of its panes reaches that daemon. Thetmuxandherdrlinks 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/NAMEin the socket folder of the default daemon. The state is in$XDG_STATE_HOME/tuios-servers/NAME. -
tuios ls --jsongives each session of a named daemon aserverfield. 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.
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.
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).
# 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.
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_idtuios 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.logwait-for blocks until the condition matches and exits non-zero on timeout, so you do not need a poll loop.
tuios list-windows --json # every window, with window_id, display_name, workspace and more
tuios get-window build # one window
tuios session-info # the sessiontuios 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 devWith --json, the result fields sit at the top level, so the new window's ID is at .window_id.
#!/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#!/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"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.
tuios kill-server # stops the daemon, or removes a stale socket if none is running
tuios # starts a fresh daemonWarning
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.
(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.sockWhen both daemons run, tuios uses the one in /run/user. To move the sessions of the daemon in /tmp:
- Stop it:
kill $(cat /tmp/tuios-$(id -u)/tuios.sock.pid). It saves every session when it stops. - List what it saved:
tuios resurrect. - 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.
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 -.
The name is wrong or the session was killed. tuios ls lists the live ones, and tuios resurrect lists the saved ones.
tuios kill-session stuck
tuios new freshtuios logs shows the daemon log.
- Control Protocol: Drive the daemon from scripts and programs
- CLI Reference: Every command and flag
- Keybindings: Keyboard shortcuts
- Hooks: Run commands on session and window events
- Introduction
- Getting Started
- Learn tuios, hands on
- Keybindings
- Hints
- Finding a Pane
- Links
- TUIOS vs Others
Windows and Layout
Sessions and Machines
Agents
- Agents
- Inbox and Review
- Agent Messaging
- Program Status
- Worktrees
- Checkpoints
- Ship
- Push Notifications
- Turn Off Agent Features
Scripting
Recording
Reference