Skip to content

Configuration

Gaurav Gosain edited this page Oct 15, 2026 · 40 revisions

Every table and option in the TUIOS config.toml, with defaults and how changes apply.

TUIOS reads ~/.config/tuios/config.toml. If XDG_CONFIG_HOME is set, the file is $XDG_CONFIG_HOME/tuios/config.toml. On the first run, TUIOS writes a config.toml with comments and no settings, except [startup].

tuios config path     # print the path
tuios config edit     # open it in $EDITOR
tuios config reset    # overwrite it with the defaults, after asking

Write only what you want to change. Missing keys and missing tables take their defaults.

[appearance]
theme = "nord"

[keybindings.window_management]
new_window = ["ctrl+t"]

Split the config into several files

config.toml can include other files. TUIOS also reads the *.toml files in a config.d directory next to config.toml.

include = ["shared.toml", "?work.toml"]   # put this before the first table
  • A relative path is relative to the file that has the include line. A name that starts with ? is optional.
  • The included files come first, then the config.d files in name order, then config.toml. A later file wins.
  • config.toml holds only the keys that you changed. TUIOS writes a change to the file that sets the key, and keeps its comments.
  • TUIOS never writes a read-only file, such as a file from Nix or home-manager.
  • Add disabled = true to an array entry to remove an earlier entry with the same name or key.
tuios config files               # every file, in merge order
tuios config origin [key]        # the file that sets each key
tuios config prune --dry-run     # the keys of config.toml that have their default value

An old config.toml sets every key and hides the other files. Run tuios config prune to remove its default values.

For the merge rules, read-only setups and a dotfiles example, see Split the Config.

Two things live outside the file: custom themes in ~/.config/tuios/themes/ and custom glyph sets in ~/.config/tuios/glyphs/.

Note

The binary is the source of truth

tuios list-options prints every settable option with its type, default and accepted values. It reads the same registry the validator and the settings page use, so it always matches the build you run.

Tables

Table What it holds
[appearance] Theme, backgrounds, borders, windows, mouse, tiling, dock. Sub-tables scrollbar, selection and sidebar
[notifications] How long messages stay up, and agent alerts
[keybindings] The leader key and 23 sections of bindings
[daemon] Daemon log level, agent detection, resuming agents, saved history and the size of a shared session
[startup] How a new session starts
[workspaces] What happens when a workspace becomes empty
[launcher] How the app launcher starts a graphical program
[tape] Project tapes
[debug] The keycast overlay
[screenshot] Capture format, frame and fonts
[screensaver] The idle animation
[hints] What hints mode labels, the label letters and how much the pane dims
[panes] The keys of the pane labels that Ctrl+B Q shows
[scratch] The size of the scratch terminal that Ctrl+B g shows
[pip] The size and corner of the picture-in-picture view that p pins
[spotlight] The beam that dims all but one part of the screen
[dock] Which components the bar draws, and your own
[hosts] Other machines this daemon links to, and what each may do here
[tailscale] Which tailnet machines tuios hosts add offers
[agents] The switch for agent features, approvals from the Inbox, risk rules, what a pane may do, the away recap, the queue and checkpoints
[notify] Push notifications to your phone for Inbox items
[hooks] Shell commands run on events

Applying changes

TUIOS watches the file. A save is applied about 200 ms later, with no restart. The command palette also has a Reload Config entry.

A file with an error is not applied. The running settings stay, and the error is shown on screen.

Most of the file applies live. These parts do not:

Change When it applies
[hooks] The daemon reads it at start. Run tuios kill-server after editing a hook the daemon runs
[daemon] The daemon reads it at start. Run tuios kill-server after editing it. tuios set-config daemon.log_level also changes the log level of a running daemon
[startup] The next new session
scrollback_lines Panes opened afterwards
Custom theme files See Custom themes

The daemon also follows the file for [hosts], [agents], [notify] and preferred_shell. Removing a host closes its link at once. A change that gives panes or other machines more waits for tuios config apply. This covers a new host, a wider [agents.permissions] and a wider link policy. See Changes that wait.

Changing a setting at runtime

tuios list-options                       # every path, with default and accepted values
tuios list-options --section sidebar     # one group
tuios list-options --search "pane bg"    # fuzzy search, best match first
tuios get-config appearance.border_style
tuios set-config appearance.border_style thick
tuios config browse                      # search the options and set one, in an explorer

tuios config browse shows each option with its type, default, current value and source. Press / to search. Press Enter on an option to set it the same way as set-config. q or Esc leaves it. It needs a running daemon. See Explorers.

set-config changes a running session. With a client attached, the client applies the value and writes it to the config. With no client attached, the daemon keeps the value for this session and does not write the file. A path the registry does not know is refused. An [appearance] option also answers to its bare name, so border_style and appearance.border_style are the same path. set-config cannot reach [keybindings], [hooks], [hosts], [tailscale], [agents] or the [dock] lists, because those are maps and lists rather than single values. For [agents] this is also a safeguard: a program in a pane cannot loosen it through TUIOS. dock.clock.format is the one [dock] key it can set.

The in-app settings page

The settings page edits every option in the registry, in the same groups list-options prints. Open it with:

  • , in window mode (open_settings)
  • Ctrl+B , (prefix_settings)
  • Settings in the Ctrl+P command palette

Most rows apply as you change them. Colour options open a colour picker. Two rows open editors of their own: the rail's sections and the dock's components.

The page reopens on the tab, row and search you left it on. 1 to 9 go to a tab. A row whose value in force differs from its default has a dot after its name. That includes the rows the page draws by hand, such as the theme, glyph set, shared borders, pane gap, the master and column ratios, the rail's sections, max FPS, show keys, spotlight and the screen saver effect. For the pane geometry rows the value compared is the session's, not the config's. Backspace (or Delete) resets a changed row to its default, and its description says what the default is. The hand-drawn rows have no reset: Backspace on one says so, and you change it back with its own control. Ctrl+Z undoes the last change made on the page.

/, or any letter the page does not use, searches every tab. It ranks each row by its name, its config key, its value and its description, and each result names its tab. In the results, ←, → and Enter change the row where it is, Tab goes to the row on its own tab, and Esc clears the search. The command palette reaches the same rows by name (settings: pane background), and tuios list-options --search runs the same search from a shell.

Note

A save changes only the key

The settings page writes only the key that you changed. Your comments, blank lines and key order stay. When the config has more than one file, the change goes to the file that sets the key. See Split the config into several files.

Command-line flags

Interface flags override the file for one run. They are accepted by tuios, tuios new, tuios attach, tuios ssh and tuios tape play.

Flag Option
--theme appearance.theme
--border-style appearance.border_style
--dockbar-position appearance.dockbar_position
--window-title-position appearance.window_title_position
--window-button-style appearance.window_button_style
--window-button-position appearance.window_button_position
--hide-window-buttons appearance.hide_window_buttons
--hide-scrollbar appearance.hide_scrollbar
--shared-borders appearance.shared_borders
--scrollback-lines appearance.scrollback_lines
--zoom-max-width appearance.zoom_max_width
--no-animations appearance.motion = "none"
--confirm-quit appearance.confirm_quit
--show-clock, --show-cpu, --show-ram appearance.show_clock, show_cpu, show_ram
--show-keys debug.show_key_events
--ascii-only none. Draws ASCII instead of Nerd Font glyphs

Appearance

All keys below go under [appearance].

Theme and chrome

Key Default Values What it does
theme "" a theme id, none, omarchy or omarchy/NAME Colour theme. Empty keeps your terminal's own colours. On a machine with Omarchy, empty uses the Omarchy theme (Added in v0.10.0). See Omarchy
glyphs "default" a glyph set id Shapes of the chrome. See Glyph sets
border_style "rounded" see below Border drawn around every pane
border_focused_color "" #RRGGBB Override the focused border colour
border_unfocused_color "" #RRGGBB Override the unfocused border colour
shared_borders false bool Draw one border between neighbouring tiled panes, in every layout
gap 0 0 to 8 Empty cells between tiled panes
dim_unfocused 0 0 to 90 Fade panes you are not in, in percent
dim_foreground (Added in v0.10.0) true bool Let dim_unfocused dim the text of a pane you are not in, as well as its background. false keeps the text at full strength and dims only the background
dim_multifocus false bool Dim the panes in the multifocus set like other panes you are not in
session_colors true bool Give each session its own colour on the rail and in the switcher
session_border false bool Also draw that colour on every pane border
zen_mode "disabled" disabled, always, mouse Hide borders never, always, or while the mouse is idle
panel_padding 2 1 to 6 Columns of padding inside overlay panels
motion "full" none, basic, full How much moves. basic animates window slides, zoom and the copy sweep. full adds the fade of a dialog opening (truecolor only), the shimmer on a working agent's row and the confetti. none moves nothing
animation_speed (Added in v0.10.0) 100 25 to 400 The speed of the window animations, in percent: minimize and restore, snap, swap and zoom. 200 runs them in half the time. 0 reads as 100. A value out of range moves to the nearest end. motion = "none" still turns them off
theme_transition (Added in v0.10.0) "none" none, circle, fade How a theme change shows. circle grows the new theme from the middle of the screen. fade changes every cell from the old colours to the new colours, and needs a truecolor terminal. none changes the theme in one frame. motion = "none" and --no-animations turn it off
theme_transition_duration (Added in v0.10.0) 450 100 to 2000 The time a theme transition takes, in milliseconds. animation_speed scales it
modal_dim 30 0 to 90 Dim the screen behind a dialog, in percent. 0 turns it off. At 16 colours the text goes faint instead
max_fps 0 0, 10 to 240, or "auto" The highest frame rate TUIOS draws at. 0 means 60. See below
wrap_lists true bool Moving past the end of a list wraps to the other end

animations_enabled, the key motion replaces, is still read from older files: false reads as motion = "none", and true changes nothing. Where both are set, motion wins.

border_style

Values: rounded (default), normal, thick, double, block, outer-half-block, inner-half-block, ascii, hidden, glyphs.

glyphs uses the border of the active glyph set. hidden also hides the window buttons and the scrollbar, because both are drawn on the border.

max_fps

max_fps is the highest frame rate TUIOS draws at. Your terminal and your monitor can show fewer frames. A value above what they can show does not make TUIOS smoother.

[appearance]
max_fps = 144       # a number from 10 to 240
# max_fps = "auto"  # the refresh rate of your display
# max_fps = 0       # 60, the default
Value Frame rate
0 60. This is the default
10 to 240 That number. A value outside the range moves to the nearest end
"auto" The refresh rate of your display, from 10 to 240. If TUIOS cannot find the rate, it uses 60

The settings page offers Auto, 30, 60, 90, 120, 144, 165 and 240. With Auto selected, the row shows the rate in use, for example Auto (144). A change applies at once, with no restart.

  • What you see. TUIOS sends a frame to your terminal, and the terminal draws it on its own schedule. kitty and Ghostty draw at most once for each refresh of the monitor, so a 60 Hz monitor shows at most 60 frames a second. Over SSH, the network and the remote terminal set the limit too.
  • What it costs. TUIOS draws only when something on the screen changes. The terminal library under it still wakes at max_fps while TUIOS is idle. An idle TUIOS at 240 wakes about three times as often as at 60.
  • How auto finds the rate. TUIOS looks once in the background when it starts, and again when the config file reloads. Until the answer arrives, it draws at 60. On Wayland it asks hyprctl on Hyprland, niri on niri and wlr-randr on other wlroots compositors. On X11, or on another compositor, it asks xrandr --current. On macOS it asks system_profiler SPDisplaysDataType. On Windows it does not look, and uses 60.
  • Several displays. A terminal window cannot tell which display it is on, so auto uses the highest rate among them.
  • Remote clients. Over SSH, in tuios-web and in the SSH server, auto uses 60, because the displays on the machine are not the ones you see.

Backgrounds

A cell with no background of its own is transparent, so your terminal's own background shows through. That is the default everywhere: pane content left on the default background, the space between panes, the borders, the dock and the rail. These options paint those cells instead, one surface at a time or all at once.

Key Surface Default
background Every surface below that is not set on its own "off"
pane_background Pane content, the thin scrollbar over it, and the scrollback browser "" (follows background)
desktop_background Behind and between panes: gaps, the space around floating panes, an empty workspace and its welcome screen ""
window_chrome_background Pane borders and title bars, and the lines between shared borders ""
dock_background The dock ""
sidebar.background The rail, under [appearance.sidebar] ""

Each takes off, theme or a #RRGGBB colour:

  • off paints nothing, so your terminal's background shows through.
  • theme paints the active theme's background, with the theme's foreground on default-coloured text. With no theme set, it behaves as off.
  • #RRGGBB paints that colour. With a theme set, default-coloured text takes the theme's foreground, lightened or darkened until it reads on the colour.

A surface's own option wins whenever it holds a value, off included. Left empty, it follows background:

[appearance]
background = "theme"          # every surface on the theme's background
dock_background = "#11111b"   # the dock a shade darker
pane_background = "off"       # panes keep the terminal's own background

[appearance.sidebar]
background = "#181825"

Only cells with no background of their own are painted. A background a program chose, the selection and search marks, and the colours the chrome sets itself always win. A value that is not a keyword or a #RRGGBB colour reads as off for that surface, with a validation warning.

While the pane background paints a colour, a program that asks the terminal for its colours (OSC 10 and 11) is told the colours it is drawn on, so it can pick a light or dark palette. All six options apply live, reach every attached client, and are on the Backgrounds tab of the settings page, where x clears a surface back to following background.

Windows and titles

Key Default Values What it does
window_title_position "top" top, bottom, hidden Where the title is drawn. hidden also disables rename
window_title_format "" template Title template. See below
window_button_style "dots" dots, pill macOS-style traffic lights, or a filled pill. On the left, the pill puts close at the outer corner: close, zoom, minimize
window_button_position "left" left, right Which end of the title bar holds the buttons
hide_window_buttons false bool Hide the minimize, zoom and close buttons
window_button_zoom true bool Show the zoom button on tiled panes
hide_scrollbar false bool Hide the scrollbar on the pane border
confirm_quit false bool Always confirm on quit, not only when processes are running
preferred_shell "" path Shell for new panes. Empty picks one for your platform
new_window_inherit_cwd true bool Start a new window in the focused pane's directory. On Windows, the shell must report its folder with OSC 7 or OSC 9;9
new_window_follow_ssh false bool A split or new window of a pane that runs ssh runs the same ssh. See below
whichkey_enabled true bool Show the which-key popup after the leader key
whichkey_position "bottom-right" bottom-right, bottom-left, top-right, top-left, center Where the popup opens
prefix_repeat_time 500 0 to 5000 Milliseconds the prefix stays armed after a repeatable command. 0 turns it off

window_title_format

A template for every title. It expands three placeholders:

  • {title}: the custom name, or the title the program set
  • {index}: the window's 1-based position in its workspace
  • {cwd}: the window's working directory
[appearance]
window_title_format = "{index}: {title} ({cwd})"

Any other {...} produces a validation warning and is drawn as written.

Mouse and input

Key Default Values What it does
click_to_type "double" double, single, off In window mode, what starts typing in a pane: a double click, a single click, or no click. A single click always focuses
auto_enter_terminal_on_focus "off" off, targeted, all Start typing when a keyboard focus command lands on a pane. targeted covers numbered select and arrows, all adds Tab
focus_follows_mouse false bool Focus the pane under the pointer
alt_drag true bool Alt + left-drag moves a pane
right_click_opens_menu false bool In terminal mode, a plain right-click opens the pane menu
copy_on_select true bool Copy a mouse selection when you release the button
word_characters "@-./_~?&=%+#" string Punctuation that counts as part of a word on double-click
links "all" all, marked, off The links TUIOS finds. marked finds only OSC 8 links. See Links
link_click "both" both, ctrl, shift, off The click that opens a link. both is Ctrl+click and Shift+click
link_hover (Added in v0.9.3) "safe" safe, always The hover underline and label on a link. safe skips a pane whose program tracks the mouse. always draws them in every pane
link_opener "" a command The command that opens a web link. TUIOS puts the URL where %s is, or at the end. Empty uses $BROWSER, then the system opener
nvim_navigation false bool Let the terminal-mode focus keys move through Neovim splits first. See Neovim pane navigation
kitty_placeholders "auto" auto, on, off Draw images placed with kitty Unicode placeholders

Links

Ctrl+click on a link opens it. Ctrl+drag still moves the pane: a link opens only when the pointer does not move. Only http, https, mailto, ftp and ftps links go to the opener. TUIOS copies other links. Under ssh, on a remote client, or on a machine with no desktop, TUIOS copies the URL, and your terminal can still open the link from the OSC 8 that TUIOS writes. If the opener fails, the dock says so and TUIOS copies the URL. See Links for the opener, the safety rules and a table of terminals.

Scrolling

Key Default Values What it does
scrollback_lines 10000 100 to 1000000 Lines each pane keeps. Read when the pane is created
scroll_lines 3 1 to 50 Lines per mouse wheel notch

Tiling and zoom

Key Default Values What it does
tiling_scheme "spiral" spiral, longest_side, alternate, smart_split BSP scheme a workspace starts with the first time it is tiled. A workspace already tiled keeps its own scheme. See BSP tiling
master_ratio 50 10 to 90 The share of the screen the master panes take in the master-stack layout, in percent
master_position "left" left, right, top, bottom, center The side the master panes take. With center, the stack panes go to the right and the left in turn
master_count 1 1 to 9 How many panes are master panes
master_grid true bool With one master on the left, show four or more panes as a grid
scroll_column_width 55 20 to 100 Column width in the scrolling layout, in percent
scroll_column_max 90 20 to 100 Widest a scrolling column may be set to. 100 lets a column fill the screen
niri_scroll_cells 8 1 to 200 Cells the scrolling layout moves per wheel event
niri_reverse_scroll false bool Reverse the wheel in the scrolling layout
niri_click_reveals true bool Clicking a partly hidden column brings it fully on screen
niri_hover_reveals true bool With focus_follows_mouse, hovering does the same
zoom_size 95 50 to 100 Share of the screen a zoomed pane takes, in percent. Below 100 the layout stays visible at the edges. 100 is fullscreen
zoom_max_width 0 cells Width cap for a zoomed pane. 0 means no cap
zoom_borderless false bool Show a zoomed pane on the whole pane region, with no border and no title bar. The pane's program gets the full size. With it on, zoom_size and zoom_max_width have no effect
zoom_animation true bool Slide a pane into and out of zoom
zoom_follows_focus true bool Moving focus while zoomed zooms the pane you move to
drop_preview (Added in v0.9.3) "fill" fill, outline, off The highlight while you drag a pane in BSP. fill draws a frame in the accent colour, with the border glyphs of the panes, and tints the area inside it (Added in v0.10.0). outline draws only the frame. off draws nothing, and a drop still splits or swaps. See Moving a window with the mouse
drop_preview_delay (Added in v0.9.3) "120ms" a duration, or 0 How long the pointer stays on one drop zone before its highlight shows. 0 shows it at once

The layout a new session starts in is startup.layout. See The startup table. Each workspace starts with the master-stack options, and the layout keys change the workspace on screen. See Master-stack.

Dock

Key Default Values What it does
dockbar_position "top" top, bottom, hidden Edge the dock sits on
dock_workspace_tabs true bool Show the clickable workspace strip
dock_workspace_tab_format "" template Tab label with {index} and {name}, and {icon} and {zoom} (Added in v0.10.0). Empty means {name}. See Workspace tab style
dock_workspace_label_max 12 cells, 0 for no cap Longest workspace tab label. The cap counts the characters of the tab format
dock_workspace_tooltip true bool Show a truncated workspace name in full on hover
dock_pill_caps true bool Rounded caps on every dock pill: the mode chip, the workspace pills and the minimized windows. false draws flat pills
dock_compact false bool Draw the dock as one row with no rule. The panes get one more row
dock_mode_icon_window unset text, "" or default Icon in the mode pill in window mode. Unset or default uses the icon of the glyph set. An empty value shows no icon
dock_mode_icon_terminal unset text, "" or default Icon in the mode pill in terminal mode
dock_mode_icon_tiling unset text, "" or default Icon in the mode pill while tiling is on
show_clock false bool Show the clock
clock_format "15:04:05" Go time layout Clock format, for example 15:04 or Mon 3:04PM
show_cpu false bool Show a CPU graph
show_ram false bool Show RAM use
hide_clock false bool Deprecated. Use show_clock

dock_workspace_label_max stops one long workspace name from pushing the other tabs off the dock. Set it to 0 to show the whole name. The workspace strip then scrolls. A dock with no room for one full tab still shows the current tab, cut to fit.

dock_compact works with the dock at the top and at the bottom. It has no effect when dockbar_position is hidden. The change applies at once. A compact dock does not show the rule that tells how long a dock message stays. When clients with different docks share a session, the panes keep the largest dock height. A client with a compact dock shows a blank row in that case.

The mode pill icons can be any text up to 8 cells wide. A wide character, such as 終, counts as two cells. The dock does not accept control characters. For an icon that it does not accept, the dock shows the default icon and the config check shows a warning. The pill adds one space on each side of the icon. If an icon is empty and the mode has no other text, the dock shows no pill in that mode. While tiling is on, the pill also shows the next split direction, V or H.

[appearance]
dock_mode_icon_window = "WM"
dock_mode_icon_terminal = ">_"
dock_mode_icon_tiling = ""

To remove the whole mode pill, remove "mode" from the left list in the [dock] table. You can also set the icons on the settings page, in the Dock section, or with tuios set-config appearance.dock_mode_icon_window WM. The change applies at once.

To reorder the dock or add your own cells, see The dock table.

Rail options

Two rail options sit directly in [appearance]:

Key Default What it does
global_session true Offer a session on the rail that holds panes from several machines, once a second machine is reachable
git_dirty true Count staged, changed and untracked paths in the rail's git section. This is the only part of that section that walks the working tree

The rest are in [appearance.sidebar].

The scrollbar

[appearance.scrollbar]:

Key Default Values What it does
style "track" track, thin A full-height track, or a hairline thumb over the content
thumb "" one-cell glyph Thumb glyph. Empty uses the style's own
track "" one-cell glyph, or none Track glyph. Empty uses the style's own
tint "quiet" quiet, border, muted, or #RRGGBB Bar colour

Selection colours

[appearance.selection] sets the colours TUIOS draws over pane content to mark text.

Key Default What it sets
bg "#45475A" Background of selected text
fg "" Selected text. Empty keeps the text's own colour
bold false Also draw selected text bold
search_bg, search_fg "#8A6D2F", "#F5E7C8" Every search match
match_bg, match_fg "#E5A93D", "#1C1B19" The match the cursor is on
cursor_bg, cursor_fg "#39C5CF", "#08222B" The copy mode cursor
flash true Sweep a band of light over text you just copied. appearance.motion = "none" also turns it off
flash_color "" Colour of the sweep
flash_ms 420 Length of the sweep, 80 to 3000 ms
flash_style "diagonal" diagonal, diagonal-reverse, horizontal, vertical
copy_entry "cursor" Where the copy cursor starts. cursor is the terminal cursor, usually the prompt line, as in tmux. center is the first column of the middle row
multi_format "plain" The format that multi copy mode starts with: plain, markdown or json
osc52_write "focused" What OSC 52 from a pane does to the host clipboard: off, ask, focused or on. See Clipboard writes from a pane
copy_command "" A command that gets each yank in copy mode on stdin. The clipboard gets its stdout. Empty copies the selection as it is. See Pipe a yank through a command

An empty foreground keeps the colour the program wrote. An empty background falls back to the default.

Clipboard writes from a pane

A program in a pane sets the clipboard with OSC 52. An editor uses OSC 52 to yank over ssh. Any output can also carry OSC 52, for example a file that a program prints. appearance.selection.osc52_write controls what happens.

Value What happens
off The host clipboard does not change.
ask The dock shows a message. Click the message to copy the text.
focused The focused pane copies. A write from another pane shows a message in the dock. This is the default.
on Every pane copies. This is the behaviour of v0.8.0.
[appearance.selection]
osc52_write = "focused"
  • When the focused pane copies text with a line break or a control character, the dock shows one line for that pane. A one-line copy shows nothing.
  • Each pane keeps one clipboard ask. A click copies only the text that the dock shows. If the text changes first, click the new message.
  • A pane that runs without the daemon keeps its own copy of the text. The program reads it back with an OSC 52 query. A pane under the daemon keeps no copy, so an OSC 52 query there always gets an empty answer.
  • TUIOS drops a clipboard reply that answers no read request of TUIOS.

Pastes, notifications, OSC 66 text and link addresses lose their control characters. A paste into a pane keeps its tabs and line breaks. Text that holds ESC[201~ cannot end a bracketed paste early.

The session rail

[appearance.sidebar] configures the rail at the side of the screen.

Key Default Values What it does
enabled true bool Show the rail
position "right" right, left, hidden Edge the rail sits on
width 24 columns Preferred width on a wide screen
background "" off, theme, #RRGGBB The rail's background. Empty follows appearance.background. See Backgrounds
agent_rest_fold "1h" a duration, or off How long an agent row rests (idle, unknown, or done and seen) before it folds into one +N at rest line
subagent_rows (Added in v0.9.3) "rows" rows, tree, count How an agent row shows its subagents. rows draws a row for each subagent under its agent's row. tree draws the same rows on tree lines. count draws the count alone. See Subagent rows
subagent_collapse (Added in v0.9.3) "off" off, expanded, collapsed Draw a heading line, such as ▾ 4 subagents, over the subagent rows of each agent. The value is the state at the start
subagent_fields (Added in v0.9.3) "mark,name,time,note" a comma list of mark, name, type, time, note The parts each subagent row shows. type is the agent type, such as Explore. note is the second line
subagent_max_rows (Added in v0.9.3) 0 0 to 16 The most lines the subagents of one agent use, the +N more line included. 0 means no limit
subagent_hide_finished (Added in v0.9.3) "off" a duration, or off Remove the row of a finished subagent this long after it ends
header_case (Added in v0.9.3) "lowercase" lowercase, uppercase How the headers of the rail sections read. lowercase is the quiet default. uppercase draws every header as a bold heading, with a rule to the edge of the rail
sections "sessions:25,terminals,files:25,agents:34" layout string Which sections to draw, in what order. See below
show_glyphs true bool Agent-state glyph on each row
show_counts true bool Window count on each session row
show_numbers false bool Switch number (the one switch_session_N opens) before each local session name, for sessions 1 to 9
marquee true bool Scroll a hovered row's long title
tooltips true bool Label the collapsed strip on hover
file_icons true bool A Nerd Font icon per file type in the files section
file_icon_colors true bool Draw each icon in its file type's colour
folder_click "navigate" navigate, cd, both What a click on a folder row does
file_actions true bool Allow create, rename, delete, copy, cut and paste
file_delete "trash" trash, permanent Where d sends a file
editor "" a command The terminal editor that opens a text file. Empty uses $EDITOR, then $VISUAL, then vi. See The file editor

Deprecated keys still load: show_windows, show_agents and workspaces in this table, and the flat sidebar_* keys in [appearance]. They are folded into the current keys when the file is read.

sections

One string sets which sections the rail draws, their order from the top, and the share of the rail each may take.

[appearance.sidebar]
sections = "sessions:25,terminals,files:25,agents:34"
  • Separate sections with commas. Add :N for a share in percent.
  • The section names are sessions, terminals, files, agents, git and custom. Leave a name out to hide that section. See The custom section.
  • A share is a ceiling, not a reservation. A section only takes the lines its rows fill. A section with no share takes what the others leave.
  • spacer draws nothing and takes lines. It is the only name you can repeat. A spacer with a share keeps that share. A spacer with no share takes the lines nothing else wants, which pushes what follows to the bottom.
[appearance.sidebar]
# Sessions at the top, files at the bottom
sections = "sessions:30,terminals,spacer,files:40"

An unknown name, a bad percent or a repeated section is dropped and reported as a config warning.

The settings page has an editor for this: the Sections row under Sidebar. Arrows select, Shift+arrows move, Enter adds or removes a section, ←/→ change the share, r restores the default and u undoes your edits.

The files section

The files section lists the focused pane's directory. It is read again when the shell changes directory, when the focus moves and when a client attaches. It is also read again when a file in the listed folder is added, removed or renamed, by the pane or by another program. tuios asks the system to report these changes (inotify on Linux, kqueue on macOS and the BSDs, ReadDirectoryChangesW on Windows). Nothing polls, and a change to the contents of a file does not cause a read. For a pane on another machine, the list changes only when the shell changes directory.

folder_click decides what a click on a folder does: navigate opens it in the listing, cd sends the focused pane there, both does both. A cd is only typed into a pane that is at a shell prompt.

The file keys are in [keybindings.sidebar_files] and work while the rail has the keyboard and the cursor is on a file row. See Keybindings. Each action opens a centred dialog. The delete dialog opens on Cancel, so Enter on an untouched dialog deletes nothing.

Delete uses the freedesktop.org trash in your home directory, so your desktop's trash shows the file and can restore it. D deletes permanently. Set file_delete = "permanent" to make d permanent too. The per-volume trash is not implemented.

Y copies the absolute path of the selected file or folder. A folder's context menu has a Copy path row that does the same.

On Windows, a shell under MSYS2 or Cygwin reports its folder as /c/work. TUIOS reads that as C:\work, by the rules of cygpath, so the files section lists the right folder.

The file editor

Shift+Enter on a text file opens it in a terminal editor. editor sets the command. The settings page shows it as File editor under Sidebar.

[appearance.sidebar]
editor = "micro"
  • Other terminal editors and arguments work too, such as nano or vim -f.
  • Quote a path or an argument that contains spaces. TUIOS splits the command into arguments and runs no shell.
  • TUIOS adds the full path of the file at the end.
  • When editor is empty, TUIOS uses $EDITOR, then $VISUAL, then vi.
  • File links and hints open files with the same command.
  • TUIOS reads the first 512 bytes of a file to check that it is text. It refuses binary files and special files.

The custom section

The custom section shows the output of a command you write. Place custom in sections and describe it in [appearance.sidebar.custom]:

[appearance.sidebar]
sections = "sessions:25,terminals,custom:35,agents:30"

[appearance.sidebar.custom]
title   = "Brief"
command = "agent-brief render"
refresh = "event:window-focused,agent-state"
Key What it does
title The section's heading. Defaults to Custom
command Run with sh -c. Every line of stdout is a row
refresh once (the default), a duration such as "30s" (one second at least), or event:TYPE[,TYPE...] with the dock's event types. push is refused

The command follows the rules of a dock component. It runs in the client, under a three second timeout, with bounded output. SGR colour stays, and every other control sequence is removed. A command that fails, times out or prints nothing leaves the title over an empty section. Five failures in a row stop it until the config reloads or you run tuios refresh-dock rail/custom. tuios list-dock-components lists it as rail/custom. Rows are cut to the rail's width and to the section's lines, with … +N for the rows below.

Each run gets the client's environment and these variables: TUIOS_SESSION, TUIOS_SOCKET, TUIOS_RAIL_SECTION (custom), TUIOS_RAIL_WIDTH, TUIOS_RAIL_HEIGHT, TUIOS_ACTIVE_PANE_ID and TUIOS_ACTIVE_PANE_CWD. An event that arrives during a run causes one more run after it, so a quick focus change never leaves the old pane's rows on screen. The command does not run while the rail is hidden or folded.

sections works with tuios set-config. [appearance.sidebar.custom] is read from the file only, so a program in a pane cannot change the command.

Two edges

(Added in v0.9.3)

The rail can show on both edges of the screen at the same time. [appearance.sidebar] still configures the original rail. Add [appearance.sidebar.left] or [appearance.sidebar.right] to configure one edge:

[appearance.sidebar]
position = "right"
sections = "sessions,terminals,custom"

[appearance.sidebar.custom]
command = "right-summary"

[appearance.sidebar.left]
enabled = true
width = 28
sections = "sessions,files,custom"

[appearance.sidebar.left.custom]
command = "left-summary"
Key What it does
enabled Show a rail on this edge. The edge opposite position stays hidden until you set enabled = true
width The preferred width of the rail on this edge, in columns
sections The section layout of this edge. See sections
custom A table with the command of this edge's custom section
session (Added in v0.10.0) A daemon session whose panes this rail shows in place of its sections. See A daemon session in a rail
session_workspace (Added in v0.10.0) 0, the default, shows the session rail on every workspace. 1 to 9 shows it only on that workspace
  • On the edge of position, the edge table overrides only the keys it sets. The other keys come from [appearance.sidebar].
  • On the other edge, a key you do not set takes its default. An unset sections takes the default section order.
  • With position = "hidden", an edge table with enabled = true still draws. When both edges are on, the left one is the original rail.
  • Each rail has its own scroll, keyboard focus and custom command. Both read the same sessions, files and git state.
  • On a narrow screen, the second rail closes before the original rail.

The edge tables are read from the file only. tuios set-config cannot change them, because a custom command runs outside the panes. Ctrl+B H shows or hides the second rail. See Session Rail.

A daemon session in a rail

(Added in v0.10.0)

A rail can show the panes of a local daemon session. The panes stack top to bottom in the rail. You type in them as in any other pane. Make the sessions, then name them in the edge tables:

tuios new side-left --detach
tuios new side-right --detach
[appearance.sidebar.left]
enabled = true
width = 36
session = "side-left"

[appearance.sidebar.right]
session = "side-right"
session_workspace = 2   # show this rail only on workspace 2
  • Click a pane to type in it. With the rail focused, Ctrl+B - and Ctrl+B c make a new pane in that session, below the others. Ctrl+B n and p change the active pane.
  • A rail that is too short to give each pane six rows shows a group of panes around the active one.
  • The rail does not resize the panes of the session. It crops them. Another client of the session keeps its size.
  • While a session is in the rail, the rail's custom command does not run. An empty session gives the rail its sections back.
  • A workspace switch, a hidden rail or a detach does not stop the programs in the session. A daemon restart does.
  • A session that does not exist yet shows an error in the rail. The rail tries again when the session starts.
  • Settings, then Sidebar, has a Left session and a Right session field.

Agent rows

[appearance.sidebar.agent_row] chooses which tokens an agent row shows and styles each one, with optional rules that match on the token's value:

[appearance.sidebar.agent_row]
tokens = ["session", "need", "harness", "name", "elapsed", "context", "subagents", "pr", "meta", "now", "message"]

[[appearance.sidebar.agent_row.elapsed.rule]]
gt = 30          # minutes in the current state
fg = "warning"

The tokens above are the shipped list. The other tokens are state, host and prompt. A tokens list you wrote keeps its own order and gets no new tokens, so add subagents or pr to it to see them. need says what a row wants from you, such as approval or question. context shows ctx 84% once the agent's context is 80% full. now is what a working agent is doing, such as Bash: go test ./.... subagents shows how many subagents a Claude Code agent has at work, such as 3 subagents, on any row while any run. When the subagents have rows of their own, it reads 3 of 4 run. pr shows the pull request of the session's worktree branch and its checks, such as PR #12 open pass. meta draws the other keys the pane reported with tuios set-agent-meta, and $ followed by a key, such as $model or $cost, places one key. See Session Rail.

Each rule has one test: equals, contains, starts_with, gt or lt. The first matching rule wins. A mistake in this table is a warning, not an error.

Splits that follow ssh

The actions split_ssh_horizontal, split_ssh_vertical and new_window_ssh open a pane on the machine that the focused pane is connected to with ssh. See Keybindings.

appearance.new_window_follow_ssh makes the ordinary actions do the same: split_horizontal, split_vertical and new_window. When the focused pane does not run ssh, these actions open an ordinary pane. The default is false.

[appearance]
new_window_follow_ssh = true

tuios finds ssh in the foreground of the pane. ssh can run from a shell, or from sshpass, autossh or mosh. tuios reads only the processes that you own in the pane. It runs the ssh or mosh that it finds on its own PATH, with no shell on this machine. It never runs the program that the pane runs.

The new ssh gets the same environment as an ordinary new pane. If the ssh that tuios follows has an SSH_AUTH_SOCK that names your own agent socket, the new ssh gets that value too. An agent that your shell started in the pane then works in the split. The socket must not be a link, and its folder must be yours and closed to other users.

The new ssh line keeps only what it needs to reach the same host as the same user. It always gets -o ControlMaster=no. Your ~/.ssh/config applies to it as usual, so a setting that you put there still works in the split.

Lines that are not followed

tuios follows an ssh line only when each -o option is on one of two lists. The new pane keeps these options:

AddressFamily, BatchMode, CertificateFile, CheckHostIP, Ciphers, Compression, ConnectionAttempts, ConnectTimeout, EscapeChar, HostKeyAlgorithms, HostName, IdentitiesOnly, IdentityFile, KbdInteractiveAuthentication, KexAlgorithms, LogLevel, MACs, PasswordAuthentication, Port, PreferredAuthentications, ProxyJump, PubkeyAcceptedAlgorithms, PubkeyAuthentication, ServerAliveCountMax, ServerAliveInterval, StrictHostKeyChecking, TCPKeepAlive and User.

The new pane does not get these options, and tuios still follows the line:

  • Forwards and sessions: LocalForward, RemoteForward, DynamicForward, GatewayPorts, ExitOnForwardFailure, ClearAllForwardings, Tunnel, TunnelDevice, RemoteCommand, SessionType, StdinNull, ForkAfterAuthentication and RequestTTY.
  • Files, sockets, environment and forwarding: ControlMaster, ControlPersist, ControlPath, UserKnownHostsFile, GlobalKnownHostsFile, HostKeyAlias, IdentityAgent, SendEnv, SetEnv, ForwardAgent, ForwardX11 and ForwardX11Trusted.
  • The flags -A, -X, -Y, -S, -L, -R, -D, -W, -w, -N, -f, -n, -T, -t, -M and -s.

These ssh lines get an ordinary pane:

  • An -o option that is not on the two lists above. ProxyCommand, LocalCommand, XAuthLocation and Match are examples.
  • An option or a value with a control character, such as a line break.
  • -E, -F, -I, -G, -V, -Q and -O, and an option that tuios does not know.
  • A -J or ProxyJump hop that is not [user@]host[:port] or the ssh:// form, made of letters, digits and ._:@[]-, or a hop that starts with -.
  • A mosh destination that starts with -.
  • mosh with --ssh, --client or --server.
  • ssh that another program starts, such as scp, rsync, git or sftp.

Put a ProxyCommand or a ProxyJump in ~/.ssh/config to use it with a split. A split with -J on the command line is followed.

Start in the remote folder

The new pane starts in the remote folder when the remote shell reports its folder with OSC 7. The host name in the report must be the same as the host in the ssh line, or as the HostName that ssh -G gives for that line. reachy-mini matches pollen@reachy-mini. When ~/.ssh/config makes prod an alias for box.example.com, a report from box.example.com or from box matches ssh prod. A report from box.other.com does not. When there is no match, the new pane starts in the remote home folder.

fish sends the report by default. For bash, add this line to ~/.bashrc on the remote machine:

PROMPT_COMMAND='printf "\033]7;file://%s%s\033\\" "$HOSTNAME" "$PWD"'${PROMPT_COMMAND:+;$PROMPT_COMMAND}

For zsh, add these lines to ~/.zshrc on the remote machine:

autoload -Uz add-zsh-hook
_tuios_osc7() { printf '\e]7;file://%s%s\e\\' "$HOST" "$PWD" }
add-zsh-hook precmd _tuios_osc7

To go to the folder, tuios adds -t, -o RemoteCommand=none and the command exec sh -c 'cd "FOLDER" ...' to ssh. This works with every login shell that can start sh. If the folder is gone, the new pane starts in the home folder. A folder name with a quote, $, a backslash or ! is not used. A Windows folder, such as C:/Users/x or a network share //server/share, is not used either, because a Windows ssh server has no sh. The new pane then starts in the home folder.

Glyph sets

A theme sets the chrome's colours. A glyph set sets its shapes: border corners, window buttons, rules, separators and rail marks.

[appearance]
glyphs = "heavy"
tuios list-glyphs            # every set, and every role name
tuios list-glyphs heavy      # what this set draws, role by role
tuios set-config appearance.glyphs heavy
Id What it is
default Rounded frame, Nerd Font powerline caps, ✕ and □ buttons
unicode Box drawing and geometric shapes, with no Nerd Font glyphs
heavy One stroke weight heavier throughout
ascii 7-bit ASCII only

A set's border is used only when border_style = "glyphs". This keeps a set from silently overriding a border style you chose. A set that defines only some border runes gets the rounded border for the rest.

Writing a set

Save <id>.json in the glyphs directory. tuios list-glyphs prints its path.

{
  "display_name": "Mine",
  "inherits": "heavy",
  "bullet": "◦",
  "focus": "▐",
  "border": {
    "top_left": "╔",
    "top_right": "╗",
    "bottom_left": "╚",
    "bottom_right": "╝"
  }
}

Every field is optional. The id defaults to the file name. A role you do not set comes from the inherited set, and finally from default. Inheritance goes up to eight levels deep.

The roles:

  • Window buttons: close, maximize, minimize, dot, pill_left, pill_right
  • Rules and separators: rule, separator, arrow_left, arrow_right, dash_rule
  • Rail marks: focus, attention, bullet, add, collapse, expand, fold_open, fold_shut, tree_branch, tree_last
  • Files section: folder, parent, file
  • Scrollbar: scrollbar_thumb, scrollbar_track
  • Text: ellipsis, sigil
  • Border: border.top, border.bottom, border.left, border.right, the four corners, and the junctions middle, middle_top, middle_bottom, middle_left, middle_right

Most roles must be exactly one cell wide, because the window buttons' click areas are fixed offsets. A glyph of the wrong width falls back to the default. separator, ellipsis, collapse and expand may be any width. To see what was dropped:

tuios list-glyphs mine --json | jq -r '.problems[]?'

The directory is read again on every lookup, so a new file can be selected at once. A file that does not parse is skipped and listed under problems.

--ascii-only replaces non-ASCII glyphs role by role, so a set keeps every role it already spelled in ASCII. A glyph set never changes colours, and it does not change the dock's icons.

The settings page has a Glyph set row under Theme. Its picker applies each set as you move through the list.

Themes

342 themes ship with TUIOS.

tuios --list-themes                   # every theme id
tuios --preview-theme dracula         # the 16 ANSI colours
tuios list-themes --filter gruvbox    # from a running daemon, with --json
tuios list-themes catppuccin_mocha    # colours and their contrast
[appearance]
theme = "dracula"

With no theme, TUIOS uses your terminal's own colours. A theme only changes ANSI colours 0 to 15. 256-colour and truecolor output is drawn as the program wrote it.

To pick one in the app, press Enter on the Theme row of the settings page, or choose Theme Picker in the command palette. The picker previews as you move and Esc restores the theme you had. It has no key binding.

Custom themes

Save one theme per JSON file in ~/.config/tuios/themes/. Subdirectories are ignored.

{
  "id": "my-theme",
  "display_name": "My Theme",
  "fg": "#e0def4",
  "bg": "#191724",
  "black": "#26233a",
  "red": "#eb6f92",
  "green": "#31748f",
  "yellow": "#f6c177",
  "blue": "#9ccfd8",
  "purple": "#c4a7e7",
  "cyan": "#ebbcba",
  "white": "#e0def4"
}
  • id defaults to the file name, lowercased. display_name defaults to id.
  • The colour names are black, red, green, yellow, blue, purple, cyan, white, each with a bright_ variant, plus fg, bg and cursor. Note purple, not magenta.
  • A missing colour gets the xterm default. A missing bright_* colour copies its normal colour, so set them if you want bright text to look different.
  • An optional chrome object sets the UI colours directly (accent, accent_bright, success, warning, error, info, surface) without changing the pane palette.
  • A file that does not parse is skipped and logged.

tuios import-theme <file> converts a kitty, Ghostty, Alacritty or WezTerm scheme into a file here.

Selecting an id that is not registered yet, with set-config or the settings page, reads the directory again first. So does tuios list-themes. The picker builds its list when it opens.

Notifications

[notifications] sets how long messages stay on screen. 0 uses the built-in default.

Key Default What it does
duration 0 Seconds an info or success message stays up
warning_duration 0 Seconds a warning stays up
error_duration 0 Seconds an error stays up when error_sticky is off
error_sticky true Errors stay until you press Esc

Agent alerts

[notifications.agent] controls what happens when an agent in a pane changes state.

Key Default What it does
enabled true Turn every agent alert on or off
notify true Send a desktop notification through the terminal (OSC 9, so it works over SSH)
dock true Show the alert in the dock. Click it to go to the pane
sound false Make the alert audible
sound_mode "audio" audio plays a cue, bell sends BEL, both does both
sound_cooldown_seconds 3 Shortest gap between two sounds, across all panes
command "" Shell command to run on an alert
settle_seconds 2 Wait this long, and drop the alert if the pane leaves the state
suppress_focused true No alert for the pane you are looking at
quiet_hours "" HH:MM-HH:MM in local time when nothing alerts

[notifications.agent.states] turns alerts on per state: needs_input, errored and done default to true. idle and working default to false.

To hear an alert, set sound = true. sound_mode then decides how:

  • audio plays a short cue through the audio player of the machine that runs TUIOS: paplay, pw-play or aplay on Linux, afplay on macOS, PowerShell on Windows. If the machine has no player, the alert is silent and TUIOS shows no message.
  • bell sends a BEL character to your terminal. Your terminal decides what a bell does, and some terminals make no sound. In kitty, check that enable_audio_bell is yes. Other terminals have a similar bell setting.
  • both does the two.

Over SSH or in the browser, audio plays on the server, not where you sit. Use bell there. See Agents.

[notifications.agent.sounds] replaces the two built-in cues with your own files: needs_input (also used for errored) and done (also used for idle). A path that does not exist falls back to the built-in cue.

The after-agent-state hook follows the same rules, including the settle wait.

Mail alerts

[notifications.mail] controls the alerts for mail to you and for session notices. A key that you leave out uses the same key from [notifications.agent].

Key Default What it does
enabled follows notifications.agent.enabled Turn mail alerts on or off
notify follows notifications.agent.notify Send a desktop notification
dock follows notifications.agent.dock Show the alert in the dock. Click it to open the thread
sound follows notifications.agent.sound Make the alert audible
between_agents false Also alert on a message from one agent to another

TUIOS warns when between_agents is on and mail alerts are off.

The sound mode, the cooldown, the cue files and the quiet hours always come from [notifications.agent]. tuios set-config notifications.mail.sound "" clears a key, and get-config then prints (follows notifications.agent.sound). See Agent Messaging.

Keybindings

leader_key

The prefix key. The default is ctrl+b.

[keybindings]
leader_key = "ctrl+a"

alt+space is already the app launcher.

opt+ and option+ mean alt+ on every platform, cmd+ and command+ mean super+, and control+ means ctrl+. See Modifier spellings. Press the leader two times in terminal mode to send it to the pane.

Sections

Each section is a table under [keybindings]. Each action takes a list of keys. The Keybindings page lists every default.

Section When it is active
window_management Window mode: windows, sessions, zoom, copy
workspaces Window mode: switch and move between workspaces
layout Window mode: snapping, tiling, swaps, resizes, splits, preselect
mode_control Window mode: enter terminal mode, help, settings, quit
restore_minimized Window mode: restore a minimized window by number
system Window mode: the spotlight
navigation Empty. Kept so older files still parse
terminal_mode Terminal mode, no prefix
global Both modes: the command palette and the launcher
prefix_mode After the leader key
window_prefix After leader, t
minimize_prefix After leader, m
workspace_prefix After leader, w
layout_prefix After leader, L
tape_prefix After leader, T
debug_prefix After leader, D
script While a tape plays
sidebar While the rail has the keyboard
sidebar_files While the rail's cursor is on a file row. Checked before sidebar
sidebar_agents While the rail's cursor is on an agent row. Checked before sidebar
inbox While the Inbox is open
inbox_peek While a prompt is open over the Inbox
mail While the mailbox is open
lists (Added in v0.10.0) The keys that move the cursor in every list. See Lists and the settings page
gui (Added in v0.10.0) The keys of a native GUI client that uses tuios gui-bridge. A terminal client does not read it

Key syntax

  • Modifiers: ctrl+, alt+, shift+, super+. Also opt+ and option+ for alt+, cmd+ and command+ for super+, and control+ for ctrl+, on every platform.
  • Special keys: enter, return, esc, escape, tab, space, backspace, delete, up, down, left, right, home, end, pgup, pageup, pgdown, pagedown, f1 to f12.
  • Any single character: letters, digits, symbols, and accented letters.
  • Held modifiers: leftalt, rightalt, leftctrl, leftsuper and the like, for hold_window_mode only. They need the Kitty keyboard protocol.

super+ only reaches TUIOS in terminals that speak the Kitty keyboard protocol. On Linux, opt+1 is alt+1.

new_window = ["n", "ctrl+t"]   # several keys for one action
close_window = []              # no key: the key goes to the pane

An empty list is different from a missing line. A missing action gets its default back at the next load. An empty list stays empty.

shift+1 and ! are the same key, and so are shift+a and A. Binding one spelling binds both. Do not bind the two spellings to different actions.

Platform defaults

On macOS, the workspace and preselect defaults use opt+, and terminal_mode adds opt+tab, opt+shift+tab and opt+esc. Writing alt+ on macOS works but gives an advisory warning. Elsewhere the defaults use alt+. See Keybindings.

The daemon table

Key Default What it does
log_level "off" off, errors, basic, messages, verbose or trace
agent_autodetect true Detect an agent CLI running in a pane and show its state
agent_detect_seconds 0 Seconds between checks. 0 means 2. A negative number turns detection off
agent_binaries [] Extra program names to treat as agents, added to the built-in list
resume_agents "ask" After a daemon restart: ask puts a Resume row in the Inbox for each pane whose agent was running, auto types each harness's resume command into the new shell, off does neither
persist_scrollback true Save each pane's history with its session, and show it above a divider after a daemon restart or a reboot. The files can hold secrets. false stops this, and the daemon deletes the saved history when it next starts. See Pane history
persist_scrollback_lines 0 Most history lines one pane saves. 0 means 1000
persist_scrollback_kb 0 Most KiB one pane's saved history takes on disk, compressed. 0 means 2048
window_size "smallest" The size of a session that has more than one client: smallest, largest or latest (the client that last had input). The names are those of the tmux window-size option. See Session size
display (Added in v0.9.3) "follow" "follow" gives the new panes of a daemon with no display the display of the local client. "off" gives them none. See The display of the client
respond_from_shell false Let tuios respond, run from a shell outside every pane, answer an agent's prompt. A caller inside a pane is refused either way. Not settable with set-config

To change window_size for one session while it runs, use tuios set-config daemon.window_size latest -s NAME. That value is not saved to the file. A change in the file applies when the daemon starts again.

The display of the client

(Added in v0.9.3)

A daemon that a host link starts over ssh has no WAYLAND_DISPLAY or DISPLAY. Its panes have none either, so wl-copy, wl-paste, xclip and image paste fail. With display = "follow", the default, the new panes of such a daemon get the display of the local client that attached to the session or used it last.

[daemon]
display = "follow"
  • A daemon that has a WAYLAND_DISPLAY or a DISPLAY of its own keeps it. The option does nothing then.
  • Each session has a link to the Wayland socket of the client: wayland-<session id>.sock, in the folder of the daemon socket. A new pane gets WAYLAND_DISPLAY set to the full path of the link. The link moves to the socket of the newest client.
  • A new pane also gets DISPLAY and XAUTHORITY of the client.
  • A shell that started before a desktop client attached has no display. Run eval "$(tuios display-env 2>/dev/null)" in that shell, or add the line to your shell rc.
  • When the client detaches, the session uses the display of the client before it. When no attached client has a display, tuios removes the link.
  • tuios uses the display of tuios attach and tuios new only. The client must run on this machine, as your user, and outside every pane. tuios ignores a client that comes through a host link, the tuios SSH server or tuios-web.
  • DISPLAY must be :N or :N.S. tuios ignores a forwarded display such as localhost:10.0.
  • The option is read from the file only. tuios set-config cannot change it.

tuios set-config daemon.log_level messages changes the level of a running daemon at once. verbose and trace also log pane content, window titles and paths. tuios logs reads the log.

The startup table

[startup] decides how a new session starts. It never changes a running session.

Key Default What it does
daemon true A bare tuios attaches to a daemon session instead of running standalone
tiled true Start with tiling on
layout "bsp" Tiling layout: bsp, master-stack or scrolling
open_default_window false Open one window when a session starts empty
start_in_terminal_mode false Start in terminal mode when a window is present

--standalone and TUIOS_NO_DAEMON=1 override daemon for one run or one shell. If the daemon will not start, a bare tuios runs standalone and says so.

Note

Older config files keep their behaviour

daemon and tiled default to true in a new install. A config file that already exists and does not mention them reads them as false, so an upgrade keeps the floating, standalone start it had. Add the keys to opt in.

In the same way, v0.8.0 changed eight appearance defaults: the rail is on, on the right, 24 columns wide; the dock and the window titles are on top; a zoomed pane takes 95% of the screen; a double click starts typing; and the scrollbar is a track. A config file that sets one of these keys keeps its value. The first-run file older releases wrote names most of them, so such an install keeps the old look until you remove those lines.

A session keeps its own layout once it is running. Attaching never rearranges a session to match your config.

The workspaces table

When the last pane on the workspace on screen closes, TUIOS shows the workspace you came from. This happens when the pane's program stops, when you close the pane, when tuios xpanes -ss closes it, and with tuios close-workspace.

Key Default What it does
return_when_empty true Show the workspace you came from when the workspace on screen becomes empty. false keeps the empty workspace on screen
new_window_when_empty false Open a pane when you switch to a workspace that has no panes, as the new-window key does

TUIOS goes back in this order:

  1. For a workspace that tuios xpanes opened, the workspace where you ran tuios xpanes.
  2. The workspace you showed last that still has panes. After a chain of empty workspaces, TUIOS goes back past each one.
  3. The workspace with the lowest number that has panes.

When no workspace has panes, TUIOS shows the splash screen. The workspace on screen is the same for every client of the session, so every client goes back. A pane that moves to another workspace does not close, so TUIOS does not go back.

[workspaces]
return_when_empty = false

The daemon reads the setting when it starts and when the file changes. tuios set-config workspaces.return_when_empty false changes it while TUIOS runs.

A pane on an empty workspace

By default, a workspace with no panes shows the splash screen. To open a pane there when you switch to it, as tabs do in other multiplexers, turn on new_window_when_empty:

[workspaces]
new_window_when_empty = true

The new pane starts in the folder of the pane that had focus on the workspace you came from. It starts in the session's start folder (the folder that tuios new --cwd gave) when you came from a workspace with no pane, from a scratch terminal, or from a pane on another machine. With appearance.new_window_follow_ssh = true, a pane that runs ssh gives a new pane that runs the same ssh, as the new-window key does.

A pane opens only when you switch: with a workspace key, a click on the dock, the workspace switcher, the pane navigator or the command palette. These switches do not open a pane:

  • move_and_follow and other moves that take a pane with them.
  • tuios xpanes, tuios select-workspace, tuios run-command and tape scripts.
  • The switch back that return_when_empty makes.
  • The start of TUIOS and an attach to a session.

A workspace gets one pane, also when two clients switch to it at the same time. When you switch away before the pane opens, you stay where you went.

Each client reads the setting from its config file, and a change applies at once. The settings page lists it under Daemon, and tuios set-config workspaces.new_window_when_empty true changes it while TUIOS runs.

The daemon must be from the same release or newer. An older daemon opens no pane. Run tuios kill-server and start TUIOS again to load the new daemon.

The launcher table

The app launcher starts a desktop entry in a new pane. A graphical program opens its own window, so its pane stays empty. Set launcher.gui_command to start such an entry with another command. The launcher adds the entry's argv to that command. Entries with Terminal=true and programs from $PATH still open in a pane.

[launcher]
gui_command = "tuios-wayland launch --"   # or "niri msg action spawn --"

Warning

Use a command that runs an argv

Do not use a command that joins its arguments into a shell line, such as swaymsg exec -- or hyprctl dispatch exec. With such a command, the text of a desktop entry becomes shell code. Any installed package can write a desktop entry.

The tape table

[tape] controls project tapes: a .tuios.tape file in a directory. See Tape Scripting.

Key Default What it does
autorun "ask" off ignores project tapes. ask shows a badge and runs nothing until you choose. auto runs a trusted, unedited tape on entry
auto_review false Open the review dialog when a tape is found, instead of only showing the badge

A tape edited after you trusted it is untrusted again.

The debug table

Key Default What it does
show_key_events false Show the keycast of recent keypresses

--show-keys and Ctrl+B D k toggle the same overlay. See Showkeys.

The screenshot table

[screenshot] sets the defaults for tuios screenshot and the in-app capture (Ctrl+B C). Each key has a command flag that overrides it for one capture.

Key Default Values What it does
format "png" png, svg, ansi, html, txt Output format
directory "~/Pictures/tuios" path Where captures are saved
copy true bool Try to copy the capture to the clipboard
preview true bool Open the preview panel after a capture
frame "window" window, plain, none A window card, a plain card, or nothing
background "auto" auto, none, #RRGGBB, #RRGGBB..#RRGGBB Backdrop. auto derives it from the theme. Two colours make a gradient
padding 48 0 to 128 Space around the card, in pixels
radius 10 0 to 32 Card corner radius, in pixels
shadow true bool Soft shadow under the card
controls "auto" auto, macos, glyphs, none Window buttons on the card
title_format "{title}" template Title bar text, with {title}, {index} and {cwd}
font_family "JetBrains Mono, monospace" string Font to use when your terminal does not report its own
font_file "" path Font file to draw with. Wins over every other font choice, and is embedded in SVG and HTML output
scale 2 1 to 4 PNG size multiplier
cursor false bool Draw the cursor cell
[screenshot]
format = "svg"
frame = "plain"
background = "none"
padding = 24

png and svg carry the frame. ansi and txt are plain output, so the frame keys do not apply.

The screensaver table

[screensaver] animates the screen after a period with no input. It is off by default.

Key Default What it does
enabled false Start after idle_minutes with no input
idle_minutes 10 Minutes of quiet before it starts, 1 to 240
effect "random" The effect, or random for a different one each time
while_busy false Start even while a pane runs a command or an agent
[screensaver]
enabled = true
idle_minutes = 5
effect = "matrix"

There are 36 effects. tuios list-options screensaver.effect prints them. The settings page previews each effect on your current screen.

S in window mode starts the screen saver now, whatever enabled says.

The hints table

[hints] sets what hints mode (Ctrl+B F) labels.

Key Default Values What it does
builtins "all" all, none, or names such as url,path,sha The built-in patterns to look for
patterns [] list of Go regular expressions Your own patterns. A group named match sets the part that is copied
alphabet "asdfghjkl" lowercase letters The letters of the labels, easiest first
open true bool Let Ctrl and a label open a URL or a path
dim 60 10 to 90 How much the text around the labels dims, in percent
all_panes false bool Put labels on all panes on the workspace, not only on the focused pane. See All panes

See What hints mode finds for the pattern names.

The panes table

[panes] sets the keys of the pane labels (Ctrl+B Q) and the first layout of the pane navigator (Ctrl+B /).

Key Default Values What it does
label_keys "1234567890" letters a to z and digits The keys the labels are made of, first pane first. With more panes than keys, a label has two keys
navigator_layout "tree" tree, flat, cards The layout the pane navigator opens in. v changes it while the navigator is open

The scratch table

[scratch] sets the size of the scratch terminal that Ctrl+B g shows in a popup. See Scratch terminal.

Key Default Values What it does
width "80%" cells (100) or percent (80%) Width of the scratch terminal
height "80%" cells (30) or percent (80%) Height of the scratch terminal

The old session key is no longer used. TUIOS ignores it, and the config warnings tell you to remove it.

The pip table

[pip] sets the size and the first corner of the picture-in-picture view. See Picture in picture.

Key Default Values What it does
width 40 12 to 200 Width of the view in cells, border included. The view gets smaller on a small screen
height 12 3 to 100 Height of the view in cells, border included
corner "bottom-right" bottom-right, bottom-left, top-right, top-left The corner the view goes to first. The view moves to a different corner when the line you type reaches it

The paste_buffers table

[paste_buffers]
limit = 20     # how many unnamed buffers to keep. 0 keeps none.
max_kb = 16384 # most KiB all buffers hold together, at most 262144. 0 uses 16384.

limit counts only the buffers that TUIOS names (buffer0, buffer1). Past max_kb, the oldest unnamed buffer goes first, then the oldest named one. See Paste buffers on tuios.dev.

Command keys

[[keybindings.command]] binds a key to a command that you write: a scratch terminal, a popup, a pane or a command with no window. A mistake in an entry is a warning, and TUIOS ignores that entry. See Command keys.

The spotlight table

[spotlight] lights one part of the screen and dims the rest, for screen sharing. B in window mode, or Ctrl+B B in any mode, turns it on and off. While it is on, Esc in window mode or a click on the Spotlight chip in the dock turns it off. See Spotlight.

Key Default Values What it does
enabled false bool Turn the spotlight on
follow "mouse" mouse, cursor Follow the pointer, or the focused pane's cursor
radius 10 2 to 200 Half the beam's height, in rows
dim 75 10 to 95 How much an unlit cell is dimmed, in percent
edge "hard" hard, soft Cut the beam off, or fade it. A fade sends about three times the bytes
shake false bool Shake the mouse left and right to toggle the beam

follow = "cursor" sends fewer bytes to a remote client.

The dock table

[dock] sets what the bar draws: three ordered lists of component names, plus a table for each component that needs one. Edit it in the file, or with the Dock > Components editor on the settings page. set-config cannot change the lists.

[dock]
left   = ["mode", "workspaces", "trail", "tape"]
center = ["windows"]
right  = ["transfers", "notifications", "copy-help", "cpu", "ram", "clock", "session-controls"]

[dock.clock]
format = "15:04"

[dock.custom.branch]
command  = "git branch --show-current"
refresh  = "event:after-focus-change"
on-click = "tuios new-window log -- git log --oneline -20"

These lists are the default. A missing list uses the default. An empty list (left = []) draws nothing on that side.

The built-in components are mode, workspaces, trail, tape, windows, transfers, notifications, copy-help, cpu, ram, clock, session-controls and session (Added in v0.10.0). Listing one lets it draw. It still needs its own condition, so cpu still needs show_cpu. copy-help shows the keys of copy mode, multi copy mode and hints mode while the mode is open. See Mode keys in the dock.

transfers (Added in v0.9.3) shows the copies between machines. It shows nothing while no copy exists. One copy is a bar and a percent, [####--] 61%. More copies are a count and a percent, 3 copies 42%. waiting means a link is down. 1 failed stays until you open the Copies panel. A click opens the Copies panel.

[dock.clock] takes format, a Go time layout. A layout without seconds refreshes once a minute.

(Added in v0.10.0) When the center list has a clock or a custom cell, the centre is at the middle of the terminal. A notification does not move it, and it does not shorten the minimized pane labels. On a crowded bar, the centre moves left to keep the cells on the right on the screen. Click a cut notification to read the full message.

(Added in v0.10.0) session shows the session name, the workspace and the pane, such as main 2:1. It is in no default list. [dock.session] takes format, with {session}, {workspace} and {pane}. It also takes the cell style keys fg, bg, bold, cap_left and cap_right. [dock.custom.NAME] takes the same style keys. Colours are #rrggbb.

Workspace tab style

(Added in v0.10.0)

By default the workspace strip draws rounded pills. [dock.tabs] can draw flat tabs, the way a tmux status line draws its window list:

[appearance]
dock_workspace_tab_format = "{name}{zoom} {icon}"

[dock.tabs]
style         = "flat"        # pill (default) or flat
separator     = " • "         # drawn between flat tabs
separator_fg  = "#4c3743"
active_fg     = "#272e33"
active_bg     = "#a7c080"
active_bold   = true
inactive_fg   = "#4f5b58"
number_glyphs = ["I", "II", "III"]  # {icon}: the first entry is workspace 1
zoom_marker   = " [ ]"        # {zoom} on the current workspace while a pane is zoomed
show_add      = false         # keep the "+" tab in the flat style

inactive_bg and inactive_bold set the other tabs. Colours are #rrggbb, and they are overrides. With no colour set, a flat strip draws the current tab in bold on the theme's accent, and the other tabs in dim text. TUIOS ignores a colour that is not #rrggbb and shows a warning. A workspace past the end of number_glyphs shows its number. The flat style shows even one workspace, and drops the + tab unless show_add is on. With no [dock.tabs] table, the strip does not change.

Custom components

[dock.custom.NAME] defines a cell. Put custom/NAME in a list to place it.

Key Default What it does
command required Run with sh -c. The first line of output is the cell text
refresh "once" once, a duration such as "30s", push, or event:TYPE[,TYPE]
on-click "" Run with sh -c when the cell is clicked
max-width 24 Widest the cell may be, in cells
fg, bg, bold (Added in v0.10.0) unset The text colour, the ground colour and bold text of the cell. Colours are #rrggbb
cap_left, cap_right (Added in v0.10.0) unset Text drawn before and after the cell, in the colour of its ground

With push, the command keeps running and each line it prints replaces the cell. With event:, the command runs again when the daemon reports one of those hook events.

A component whose command fails is hidden. tuios list-dock-components shows what each component last did and why a cell is missing. tuios refresh-dock [name] runs a component again now.

A command runs in the client, with a three second timeout. TUIOS reads at most 64 KiB of its output and stops the command when it prints more. SGR colour stays. TUIOS removes other control sequences, C1 controls, bidirectional and invisible characters, and bytes that are not UTF-8. Wide characters and emoji stay.

The hosts table

[hosts] names the other machines this daemon links to. Each host is a table.

[hosts.build]
addr = "gaurav@buildbox"

[hosts.work]
addr = "workstation"
connect_timeout = 5
Key Default What it does
addr required Anything ssh accepts, including an ssh_config alias. A host with no addr, or with one that starts with -, is ignored and logged
connect_timeout 10 Seconds one dial may take before the host counts as unreachable
command "" The tuios binary on the host. Empty means the link finds it on the PATH, at the usual install paths, or through a login shell
ssh_options [] Extra ssh arguments, placed before the address. Only safe options are accepted. See Rules for ssh_options
repos_root "" Where the host keeps its checkouts, as the host reads it, such as ~/src. fan --host, worktree new --host and start-agent look for a repository there by its origin URL

You can also manage the table from the shell. These commands edit the file:

tuios hosts add build gaurav@buildbox
tuios hosts test build
tuios hosts remove build

A change that gives a machine less applies at once, also on links that are open. A new host, a host that dials another way, or a change that gives a machine more waits for tuios config apply from a terminal outside TUIOS. A daemon restart also applies it. So tuios hosts add from a pane waits. A host that you add with "Add a host" on the settings page applies at once. See Changes that wait.

Rules for ssh_options

ssh_options accepts only options that cannot run code or write files on this machine. Write each option as -o Keyword=value, with one plain value and no quotes.

  • Accepted examples: -p, -i, -l, -J, Port, User, HostName, HostKeyAlias, IdentityFile, StrictHostKeyChecking, the ServerAlive options and the algorithm lists.
  • Refused: ProxyCommand, LocalCommand, the known hosts files, ControlPath, -F and socket forwards.
  • Refused: port forwards (-L, -R, -D).
  • -J and ProxyJump take host names only. ForwardAgent takes only yes or no.
  • A host addr or command cannot start with a dash.

TUIOS drops a host with a refused option. tuios hosts and the Inbox name the reason. Put a refused option in ~/.ssh/config, and name that host in addr.

[hosts.build]
addr = "gaurav@buildbox"
ssh_options = ["-J", "bastion", "-o", "ServerAliveInterval=30"]

Warning

Upgrading from v0.8.0

After the upgrade, look in the Inbox or run tuios hosts. A host with a refused option is dropped. Move the option, such as ProxyCommand or LocalForward, into ~/.ssh/config. Keep port forwards in ~/.ssh/config too.

The daemon runs ssh with BatchMode on, so a link never asks for a password or about a host key. Run ssh to the host once by hand to accept its key.

What another machine may do here

The same [hosts.NAME] table, read on the machine a link arrives at, also says what the machine of that name may do there. [hosts."*"] covers every machine with no table of its own.

[hosts.laptop]
addr = "laptop"                  # optional: without it nothing is dialled
allow = ["list", "mail", "open", "write", "respond"]
hold_mail = true
hosted_grace = "10m"

[hosts."*"]
allow = ["list", "mail"]
Capability What it lets the other machine do here
list Read sessions, windows, captures, agent state, the Inbox, prompts, waits and events
mail Send and read agent mail, and use the stash
open Start sessions, windows, worktrees, fans and agents
write Type into panes, close and move windows, set options and names, report agent state, attach, and pull a worktree's work
respond Answer for you: prompts, held approvals, ask-human questions, dismissing Inbox items and passing on held mail. Also type, from a pane on the other machine, into a pane here that waits on a prompt. The person at the other machine, outside every pane, needs only write for that
files (Added in v0.9.3) Read and write files here, and copy files in and out. Writes land only in the folders of files_roots. See Files over a link

list also lets the other machine stream the bytes of a pane (stream-pane). (Added in v0.9.3)

With no table, a machine may list, mail, open, write and files. respond is opt-in. An allow that is set replaces that list, and allow = [] allows nothing. A table that sets allow without files keeps that machine from your files. Anything not allowed is refused with forbidden.

To relay on to this machine's own hosts, a machine needs list, mail, open, write and respond. A table written for an older tuios that allows those five still relays.

hold_mail = true holds mail from that machine to an agent here in your Inbox, marked held for NAME, until you pass it on with p. hosted_grace is how long a pane this machine runs for the other one (a window opened there with --host) keeps running after the link drops: a duration, "0" to end it with the link, 10 minutes when unset, at most 24 hours.

The name is the one the other machine gives for itself, its host name up to the first dot, unless the ssh key it logs in with pins one with command="tuios stdio-proxy --as laptop",restrict. Only a pinned name is a boundary. A change that allows less applies to the next call on every link. A change that allows more waits for tuios config apply.

Files over a link

(Added in v0.9.3)

A machine with files can read your files and write new ones. Three keys in its [hosts.NAME] table set the limits:

[hosts.laptop]
allow = ["list", "files"]
files_roots = ["~/Downloads/tuios", "~/dev"]   # where it may write
files_read_roots = ["~/share"]                 # where it may read
files_min_free = "5GiB"                        # the space a write must leave free
Key Default What it does
files_roots one receive folder The folders the machine may write in. The default is ~/Downloads/tuios on macOS and Windows, and $XDG_DOWNLOAD_DIR/tuios (or ~/Downloads/tuios) on other systems. tuios makes it on first use
files_read_roots not set The folders the machine may read. Not set means every file you can read, apart from the read deny list. When set, the machine also reads the folders of files_roots
files_min_free "1GiB" The free space a write must leave on its disk. A whole number with B, KB, MB, GB, TB, KiB, MiB, GiB or TiB. "0" turns the check off

A path is absolute or starts with ~. Some places are always closed, whatever the roots say:

  • Writes never go into ~/.ssh, ~/.gnupg, cloud and git credentials, shell start files such as .bashrc and .zshrc, login items, crontabs, /proc, or tuios's own config and state.
  • Reads never return keys, credentials, keyrings, shell histories, browser profiles, /proc, or tuios's own state. A public *.pub key in ~/.ssh is the one exception.
  • A file with more than one name (a hard link) is never read over a link. Copy it to a new file to share it.

A refused write says where writes may land and which key to change. A deny list can never name every secret on a machine, so files_read_roots is the stronger choice.

Other limits:

  • One machine may have at most 8 write streams and 64 unfinished copies here at once. Past that, a write is busy, and a copy waits and tries again.
  • Files dropped on a pane go to a private drop folder. It holds at most 256 MiB for all drops, for 24 hours.
  • A copy another machine starts here keeps to the same rules, also after tuios restarts. When files is taken away, its copies here stop.
  • A change that widens files_roots or files_read_roots, removes files_read_roots, or lowers files_min_free waits for tuios config apply. A change that narrows applies at once.

Warning

Give files only to machines you trust

A machine with files can write any file inside its roots and read any file outside the deny list. A file copy can run code: a binary in a folder on your PATH, or a config file that a program reads. Give files, and a wide files_roots, only to a machine you trust as you trust your own.

See Remote Hosts for the copies themselves.

Using a linked host

Once a host is linked, you can list its sessions and agents (tuios ls --all-hosts, tuios list-agents --host build), attach to or create sessions on it (tuios attach --host build api, tuios new --host build), and open a pane whose process runs there (tuios new-window --host build). See tuios hosts.

Reaching a host you cannot ssh to directly

A host behind NAT, with no port forward and no jump host, is a network problem to solve under TUIOS. Any tool that pipes stdio to a remote port works as an ssh ProxyCommand. ssh_options refuses ProxyCommand, so name it in ~/.ssh/config.

Tailscale's tailcat is one such tool. It carries a connection over WireGuard with no Tailscale account and no open port. Run it on the far side against sshd:

tailcat --serve=22

Then add a host to ~/.ssh/config that uses it:

Host laptop-tailcat
    HostName laptop
    User gaurav
    ProxyCommand tailcat <token> 22

Name that host in the host entry:

[hosts.laptop]
addr = "laptop-tailcat"

ssh still decides who may connect: the tunnel only carries bytes to sshd, which asks for your key as usual. A leaked tailcat token gives reachability, not access. TUIOS adds no identity system of its own and opens no listening port.

The tailscale table

[tailscale] changes which tailnet machines tuios hosts add and tuios hosts tailnet offer as addresses. It never adds a host by itself.

Key Default What it does
enabled true Offer tailnet machines. Empty on a machine that is not on a tailnet
addr "dns" Address form: dns (MagicDNS name), name (short name) or ip
user "" ssh login to put before every address
users none Per-machine logins, keyed by short name. Wins over user
os platforms that run TUIOS Operating systems to offer. An empty list offers every machine
offline false Also offer machines that are offline
self false Also offer this machine
shared false Also offer machines shared into your tailnet
include, exclude [] Glob patterns on the short name and the MagicDNS name. exclude wins
max built in Most machines to offer
socket "" Path to the tailscaled socket, if it is not in the usual place
[tailscale]
user = "ubuntu"
exclude = ["*-pad-*"]

[tailscale.users]
build = "admin"

The agents table

[agents] sets how TUIOS treats the coding agents in its panes. Except for enabled, it is outside list-options, and set-config cannot change it, so a program in a pane cannot loosen it. The daemon reads it at start and again whenever the file changes. See Inbox and Review for what each part does.

Turn off agent features

[agents]
enabled = false

enabled (default true) is the one switch for every agent feature. With it off, TUIOS is only a multiplexer: no agent detection, no agents section on the rail, no Inbox, mail, approvals or agent alerts, and agent commands stop with a message. The change applies at once. It is the first row of the settings page, "Agent features", and tuios set-config agents.enabled false from a shell outside TUIOS also sets it. A process in a pane cannot change it. See Turn Off Agent Features.

The other agent tables

[agents.approvals]
enabled = ["claude-code", "opencode"]   # harnesses whose permission prompts the Inbox may answer
hold_seconds = 120                      # 10 to 300
hold_plans = true                       # a plan follows enabled

[agents.approvals.risk]
builtin = true                          # keep the shipped risk rules
panes_may_allow = false                 # a pane with the respond grant may not allow a risky call

[[agents.approvals.risk.rule]]
name = "kubectl apply"
tools = ["Bash"]                        # empty: every tool
pattern = '\bkubectl\s+(apply|delete)\b'   # RE2, matched per command

[agents.permissions]
mode = "strict"                         # open (default) or strict
grants = ["read", "write", "fan"]       # what a pane holds under strict

[agents.recap]
mode = "toast"                          # toast, inbox or off
away = "10m"
test_patterns = ["go test", "pytest"]

[agents.queue]
max = 8                                 # at most 64

[agents.checkpoints]
enabled = true
keep = 50                               # at most 1000
max_untracked_mb = 50                   # a negative value means no limit
Key Default What it does
approvals.enabled [] Harnesses whose permission prompts wait for an answer from the Inbox: claude-code (or claude), opencode and kilo. Empty turns it off
approvals.hold_seconds 120 How long a prompt waits for your answer before the harness asks in its own pane
approvals.hold_plans true Also hold a plan an agent asks you to approve when it leaves plan mode
approvals.risk.builtin true Keep the shipped risk rules
approvals.risk.panes_may_allow false Let a pane holding the respond grant allow a risky call
approvals.risk.rule none Your own rules: a name, the tools it applies to, and an RE2 pattern
permissions.mode "open" open gives a pane started with no grants of its own admin. strict gives it grants. Any other value reads as strict
permissions.grants ["read", "write", "fan"] What such a pane holds under strict. An empty list gives nothing. Add files to let a pane copy files near its work. See Copying files with the files grant
recap.mode "toast" Where the away recap shows: toast in the dock and the Inbox, inbox only in the Inbox, off only in tuios agent-log --recap
recap.away "10m" How long you must have been away from a pane for the dock to show its recap
recap.test_patterns built in Commands that count as a test run in the recap, such as go test and npm test
queue.max 8 Messages one pane's queue holds
checkpoints.enabled true Save the git work tree of an agent's pane each time the agent finishes a turn that changed a file. See Checkpoints
checkpoints.keep 50 How many checkpoints one pane keeps, at most 1000
checkpoints.max_untracked_mb 50 Leave an untracked file larger than this many megabytes out of a checkpoint. tuios checkpoint list names the files left out, and a restore does not change them

After turning on approvals.enabled, run tuios integration install for that harness again, so its hook is the version that waits. The grants are read, write, fan, respond, files and admin. See What a pane may do.

A change to [agents.permissions] that gives panes less applies at once. A change that gives more, such as strict to open, waits. Run tuios config apply from a terminal outside TUIOS, or restart the daemon. See Changes that wait.

The notify table

[notify] sends a push notification when the Inbox gets an item that waits for you. The daemon sends it, so it works when no client is attached. The providers are ntfy, Pushover and a webhook.

[notify]
web_url = "https://term.example.com/"   # tuios-web, for a link to the item

[notify.ntfy]
url = "https://ntfy.sh/a-long-random-topic"
token_file = "~/.config/tuios/ntfy-token"   # optional
Key Default What it does
enabled true false turns every notification off. Nothing is sent without a provider
web_url "" The address of tuios-web. Each notification links to web_url/inbox?item=ID
content "summary" summary sends the kind, the pane name and the line the Inbox shows. title sends only the kind and the session
quiet_active_seconds 120 Hold a notification while you typed at an attached client in this many seconds. 0 sends at once
cooldown_seconds 60 The shortest time between two notifications for the same pane and kind
max_per_hour 30 The most notifications in one hour. 0 sets no limit
allow_http_redirects false Let a provider redirect to a plain http address

[notify.triggers] selects the Inbox kinds: approval, plan, question and ask are on by default, mail, errored and finished are off. Like [hosts], [notify] is not in list-options, and tuios set-config cannot change it. A new address or a new source for a secret waits for tuios config apply from a terminal outside TUIOS. tuios notify test sends a test notification through each provider. See Push Notifications for the providers and the secrets.

Web Push

(Added in v0.9.3)

[notify.webpush] configures the pushes the daemon sends to a phone that registered for Web Push. It needs no provider.

[notify.webpush]
subject = "https://example.com/tuios-contact"   # optional
allow_insecure = false
Key Default What it does
subject https://tuios.dev/push The VAPID subject: an https URL or a mailto: address that a push service can use to contact you
allow_insecure false Let tuios send to a push service on a loopback or private address, by https or http. A change to true waits for tuios config apply

See Push Notifications.

Hooks

[hooks] runs shell commands on window, workspace, session and agent events. Event names use hyphens, such as after-new-window. See Hooks and tuios list-hooks.

Validation

The file is checked when it loads. Problems are errors or warnings.

Errors are keys TUIOS cannot read, such as "ctrl+nope". TUIOS ignores each of these keys and loads the rest of the file. See When a key is wrong.

Warnings keep the key. They cover unknown values (which fall back to the default), unknown title placeholders, actions with no key, two actions on one key in the same section, and the macOS alt+ advice.

TUIOS shows the number of problems when it starts, for example 2 config problems, see the log viewer. The log viewer (leader D l) shows each problem. If the file is not valid TOML, TUIOS uses the built-in defaults.

When a key is wrong

TUIOS ignores a key that it cannot read and loads the rest of the file. The action gets its default key when it has no other key. If another action already has that default key, the action has no key. A leader that TUIOS cannot read changes to ctrl+b. tuios keybinds doctor and tuios config apply list the ignored keys. See When a key is wrong on tuios.dev.

One config on macOS and Linux

TUIOS reads opt+ and option+ as alt+ on every platform. On Linux, opt+1 is alt+1, so one config.toml works on both. See One config on macOS and Linux on tuios.dev.

Troubleshooting

My config seems ignored. TUIOS ignores only the keys that it cannot read. Open the log viewer (leader D l), or run tuios keybinds doctor, to see them. Run tuios config path to confirm which file TUIOS reads. See My config seems ignored on tuios.dev.

A setting changed nothing.

  1. Check the table of what applies live. A saved keybinding works at the next key press.
  2. Check that the file parsed. Look for an error notification, or run tuios keybinds list.
  3. Check whether a command-line flag is overriding it.
  4. For a hook, check the event name spelling with tuios list-hooks.

A key does nothing. Run tuios keybinds explain <key> to see every place it is bound, and tuios keybinds doctor for conflicts.

Examples

Browser-style keys

[keybindings.window_management]
new_window = ["ctrl+t"]
close_window = ["ctrl+w"]
next_window = ["ctrl+tab"]
prev_window = ["ctrl+shift+tab"]

Screen-style leader

[keybindings]
leader_key = "ctrl+a"

[keybindings.prefix_mode]
prefix_new_window = ["c"]
prefix_split_horizontal = ["S"]
prefix_split_vertical = ["V"]
prefix_session_switcher = ["s"]
prefix_scrollback = ["/"]

In this example, S and s are taken by the splits and the switcher, so the scrollback browser moves to /. Keys inside one section must not collide.

A quiet, dense layout

[appearance]
theme = "nord"
border_style = "hidden"
dockbar_position = "hidden"
shared_borders = true
whichkey_enabled = false
window_title_format = "{index}: {title}"
scrollback_lines = 50000
max_fps = 120

Related

Clone this wiki locally