Configuration

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.

config.toml can include other files, and TUIOS also reads the *.toml files in ~/.config/tuios/config.d/. config.toml wins over all of them. See Split the Config.

[appearance]
theme = "nord"

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

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

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

TableWhat 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:

ChangeWhen 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_linesPanes opened afterwards
Custom theme filesSee 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.

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 How TUIOS saves a change.

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.

FlagOption
--themeappearance.theme
--border-styleappearance.border_style
--dockbar-positionappearance.dockbar_position
--window-title-positionappearance.window_title_position
--window-button-styleappearance.window_button_style
--window-button-positionappearance.window_button_position
--hide-window-buttonsappearance.hide_window_buttons
--hide-scrollbarappearance.hide_scrollbar
--shared-bordersappearance.shared_borders
--scrollback-linesappearance.scrollback_lines
--zoom-max-widthappearance.zoom_max_width
--no-animationsappearance.motion = "none"
--confirm-quitappearance.confirm_quit
--show-clock, --show-cpu, --show-ramappearance.show_clock, show_cpu, show_ram
--show-keysdebug.show_key_events
--ascii-onlynone. Draws ASCII instead of Nerd Font glyphs

Appearance

All keys below go under [appearance].

Theme and chrome

KeyDefaultValuesWhat it does
theme""a theme idColour theme. Empty keeps your terminal's own colours
glyphs"default"a glyph set idShapes of the chrome. See Glyph sets
border_style"rounded"see belowBorder drawn around every pane
border_focused_color""#RRGGBBOverride the focused border colour
border_unfocused_color""#RRGGBBOverride the unfocused border colour
shared_bordersfalseboolDraw one border between neighbouring tiled panes, in every layout
gap00 to 8Empty cells between tiled panes
dim_unfocused00 to 90Fade panes you are not in, in percent
dim_multifocusfalseboolDim the panes in the multifocus set like other panes you are not in
session_colorstrueboolGive each session its own colour on the rail and in the switcher
session_borderfalseboolAlso draw that colour on every pane border
zen_mode"disabled"disabled, always, mouseHide borders never, always, or while the mouse is idle
panel_padding21 to 6Columns of padding inside overlay panels
motion"full"none, basic, fullHow 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
modal_dim300 to 90Dim the screen behind a dialog, in percent. 0 turns it off. At 16 colours the text goes faint instead
max_fps00, 10 to 240, or "auto"The highest frame rate TUIOS draws at. 0 means 60. See below
wrap_liststrueboolMoving 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
ValueFrame rate
060. This is the default
10 to 240That 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.

KeySurfaceDefault
backgroundEvery surface below that is not set on its own"off"
pane_backgroundPane content, the thin scrollbar over it, and the scrollback browser"" (follows background)
desktop_backgroundBehind and between panes: gaps, the space around floating panes, an empty workspace and its welcome screen""
window_chrome_backgroundPane borders and title bars, and the lines between shared borders""
dock_backgroundThe dock""
sidebar.backgroundThe 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

KeyDefaultValuesWhat it does
window_title_position"top"top, bottom, hiddenWhere the title is drawn. hidden also disables rename
window_title_format""templateTitle template. See below
window_button_style"dots"dots, pillmacOS-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, rightWhich end of the title bar holds the buttons
hide_window_buttonsfalseboolHide the minimize, zoom and close buttons
window_button_zoomtrueboolShow the zoom button on tiled panes
hide_scrollbarfalseboolHide the scrollbar on the pane border
confirm_quitfalseboolAlways confirm on quit, not only when processes are running
preferred_shell""pathShell for new panes. Empty picks one for your platform
new_window_inherit_cwdtrueboolStart 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_sshfalseboolA split or new window of a pane that runs ssh runs the same ssh. See below
whichkey_enabledtrueboolShow the which-key popup after the leader key
whichkey_position"bottom-right"bottom-right, bottom-left, top-right, top-left, centerWhere the popup opens
prefix_repeat_time5000 to 5000Milliseconds 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

KeyDefaultValuesWhat it does
click_to_type"double"double, single, offIn 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, allStart typing when a keyboard focus command lands on a pane. targeted covers numbered select and arrows, all adds Tab
focus_follows_mousefalseboolFocus the pane under the pointer
alt_dragtrueboolAlt + left-drag moves a pane
right_click_opens_menufalseboolIn terminal mode, a plain right-click opens the pane menu
copy_on_selecttrueboolCopy a mouse selection when you release the button
word_characters"@-./_~?&=%+#"stringPunctuation that counts as part of a word on double-click
links"all"all, marked, offThe links TUIOS finds. marked finds only OSC 8 links. See Links
link_click"both"both, ctrl, shift, offThe click that opens a link. both is Ctrl+click and Shift+click
link_opener""a commandThe 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_navigationfalseboolLet the terminal-mode focus keys move through Neovim splits first. See Neovim pane navigation
kitty_placeholders"auto"auto, on, offDraw images placed with kitty Unicode placeholders
image_symbols"auto"auto, octant, sextant, quadrant, half, offThe block characters for a sixel image on a terminal without graphics. See Images Without Graphics

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

KeyDefaultValuesWhat it does
scrollback_lines10000100 to 1000000Lines each pane keeps. Read when the pane is created
scroll_lines31 to 50Lines per mouse wheel notch

Tiling and zoom

KeyDefaultValuesWhat it does
tiling_scheme"spiral"spiral, longest_side, alternate, smart_splitBSP scheme a workspace starts with the first time it is tiled. A workspace already tiled keeps its own scheme. See BSP tiling
master_ratio5010 to 90The share of the screen the master panes take in the master-stack layout, in percent
master_position"left"left, right, top, bottom, centerThe side the master panes take. With center, the stack panes go to the right and the left in turn
master_count11 to 9How many panes are master panes
master_gridtrueboolWith one master on the left, show four or more panes as a grid
scroll_column_width5520 to 100Column width in the scrolling layout, in percent
scroll_column_max9020 to 100Widest a scrolling column may be set to. 100 lets a column fill the screen
niri_scroll_cells81 to 200Cells the scrolling layout moves per wheel event
niri_reverse_scrollfalseboolReverse the wheel in the scrolling layout
niri_click_revealstrueboolClicking a partly hidden column brings it fully on screen
niri_hover_revealstrueboolWith focus_follows_mouse, hovering does the same
zoom_size9550 to 100Share of the screen a zoomed pane takes, in percent. Below 100 the layout stays visible at the edges. 100 is fullscreen
zoom_max_width0cellsWidth cap for a zoomed pane. 0 means no cap
zoom_borderlessfalseboolShow 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_animationtrueboolSlide a pane into and out of zoom
zoom_follows_focustrueboolMoving focus while zoomed zooms the pane you move to

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

KeyDefaultValuesWhat it does
dockbar_position"top"top, bottom, hiddenEdge the dock sits on
dock_workspace_tabstrueboolShow the clickable workspace strip
dock_workspace_tab_format""templateTab label with {index} and {name}. Empty means {name}
dock_workspace_label_max12cells, 0 for no capLongest workspace tab label. The cap counts the characters of the tab format
dock_workspace_tooltiptrueboolShow a truncated workspace name in full on hover
dock_pill_capstrueboolRounded caps on every dock pill: the mode chip, the workspace pills and the minimized windows. false draws flat pills
dock_compactfalseboolDraw the dock as one row with no rule. The panes get one more row
dock_mode_icon_windowunsettext, "" or defaultIcon 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_terminalunsettext, "" or defaultIcon in the mode pill in terminal mode
dock_mode_icon_tilingunsettext, "" or defaultIcon in the mode pill while tiling is on
show_clockfalseboolShow the clock
clock_format"15:04:05"Go time layoutClock format, for example 15:04 or Mon 3:04PM
show_cpufalseboolShow a CPU graph
show_ramfalseboolShow RAM use
hide_clockfalseboolDeprecated. 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]:

KeyDefaultWhat it does
global_sessiontrueOffer a session on the rail that holds panes from several machines, once a second machine is reachable
git_dirtytrueCount 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]:

KeyDefaultValuesWhat it does
style"track"track, thinA full-height track, or a hairline thumb over the content
thumb""one-cell glyphThumb glyph. Empty uses the style's own
track""one-cell glyph, or noneTrack glyph. Empty uses the style's own
tint"quiet"quiet, border, muted, or #RRGGBBBar colour

Selection colours

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

KeyDefaultWhat it sets
bg"#45475A"Background of selected text
fg""Selected text. Empty keeps the text's own colour
boldfalseAlso 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
flashtrueSweep a band of light over text you just copied. appearance.motion = "none" also turns it off
flash_color""Colour of the sweep
flash_ms420Length 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.

ValueWhat happens
offThe host clipboard does not change.
askThe dock shows a message. Click the message to copy the text.
focusedThe focused pane copies. A write from another pane shows a message in the dock. This is the default.
onEvery 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.

KeyDefaultValuesWhat it does
enabledtrueboolShow the rail
position"right"right, left, hiddenEdge the rail sits on
width24columnsPreferred width on a wide screen
background""off, theme, #RRGGBBThe rail's background. Empty follows appearance.background. See Backgrounds
agent_rest_fold"1h"a duration, or offHow long an agent row rests (idle, unknown, or done and seen) before it folds into one +N at rest line
sections"sessions:25,terminals,files:25,agents:34"layout stringWhich sections to draw, in what order. See below
show_glyphstrueboolAgent-state glyph on each row
show_countstrueboolWindow count on each session row
show_numbersfalseboolSwitch number (the one switch_session_N opens) before each local session name, for sessions 1 to 9
marqueetrueboolScroll a hovered row's long title
tooltipstrueboolLabel the collapsed strip on hover
file_iconstrueboolA Nerd Font icon per file type in the files section
file_icon_colorstrueboolDraw each icon in its file type's colour
folder_click"navigate"navigate, cd, bothWhat a click on a folder row does
file_actionstrueboolAllow create, rename, delete, copy, cut and paste
file_delete"trash"trash, permanentWhere d sends a file
editor""a commandThe 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"
KeyWhat it does
titleThe section's heading. Defaults to Custom
commandRun with sh -c. Every line of stdout is a row
refreshonce (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.

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. 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
IdWhat it is
defaultRounded frame, Nerd Font powerline caps, ✕ and □ buttons
unicodeBox drawing and geometric shapes, with no Nerd Font glyphs
heavyOne stroke weight heavier throughout
ascii7-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.

KeyDefaultWhat it does
duration0Seconds an info or success message stays up
warning_duration0Seconds a warning stays up
error_duration0Seconds an error stays up when error_sticky is off
error_stickytrueErrors stay until you press Esc

Agent alerts

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

KeyDefaultWhat it does
enabledtrueTurn every agent alert on or off
notifytrueSend a desktop notification through the terminal (OSC 9, so it works over SSH)
docktrueShow the alert in the dock. Click it to go to the pane
soundfalseMake the alert audible
sound_mode"audio"audio plays a cue, bell sends BEL, both does both
sound_cooldown_seconds3Shortest gap between two sounds, across all panes
command""Shell command to run on an alert
settle_seconds2Wait this long, and drop the alert if the pane leaves the state
suppress_focusedtrueNo 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].

KeyDefaultWhat it does
enabledfollows notifications.agent.enabledTurn mail alerts on or off
notifyfollows notifications.agent.notifySend a desktop notification
dockfollows notifications.agent.dockShow the alert in the dock. Click it to open the thread
soundfollows notifications.agent.soundMake the alert audible
between_agentsfalseAlso 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+ (macOS only), 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.

SectionWhen it is active
window_managementWindow mode: windows, sessions, zoom, copy
workspacesWindow mode: switch and move between workspaces
layoutWindow mode: snapping, tiling, swaps, resizes, splits, preselect
mode_controlWindow mode: enter terminal mode, help, settings, quit
restore_minimizedWindow mode: restore a minimized window by number
systemWindow mode: the spotlight
navigationEmpty. Kept so older files still parse
terminal_modeTerminal mode, no prefix
globalBoth modes: the command palette and the launcher
prefix_modeAfter the leader key
window_prefixAfter leader, t
minimize_prefixAfter leader, m
workspace_prefixAfter leader, w
layout_prefixAfter leader, L
tape_prefixAfter leader, T
debug_prefixAfter leader, D
scriptWhile a tape plays
sidebarWhile the rail has the keyboard
sidebar_filesWhile the rail's cursor is on a file row. Checked before sidebar
sidebar_agentsWhile the rail's cursor is on an agent row. Checked before sidebar
inboxWhile the Inbox is open
inbox_peekWhile a prompt is open over the Inbox
mailWhile the mailbox is open

Key syntax

  • Modifiers: ctrl+, alt+, shift+, super+. On macOS, also opt+ and option+, which mean the same as alt+.
  • 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. cmd+ is not valid. On Linux, opt+ is an error.

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

KeyDefaultWhat it does
log_level"off"off, errors, basic, messages, verbose or trace
agent_autodetecttrueDetect an agent CLI running in a pane and show its state
agent_detect_seconds0Seconds 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_scrollbacktrueSave 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_lines0Most history lines one pane saves. 0 means 1000
persist_scrollback_kb0Most 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
single_clientfalseKeep one client for each session. Each attach detaches the other clients of the session, as tuios attach -d does. See One client for each session
ssh_agent"off""follow" gives each session a stable link to the ssh agent of the client used last. New panes get SSH_AUTH_SOCK set to the link. See SSH Agent
respond_from_shellfalseLet 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

A change to single_client or ssh_agent in the file applies to the next attach.

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.

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.

KeyDefaultWhat it does
daemontrueA bare tuios attaches to a daemon session instead of running standalone
tiledtrueStart with tiling on
layout"bsp"Tiling layout: bsp, master-stack or scrolling
open_default_windowfalseOpen one window when a session starts empty
start_in_terminal_modefalseStart 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.

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.

KeyDefaultWhat it does
return_when_emptytrueShow the workspace you came from when the workspace on screen becomes empty. false keeps the empty workspace on screen
new_window_when_emptyfalseOpen 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 --"

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.

KeyDefaultWhat 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_reviewfalseOpen 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

KeyDefaultWhat it does
show_key_eventsfalseShow 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.

KeyDefaultValuesWhat it does
format"png"png, svg, ansi, html, txtOutput format
directory"~/Pictures/tuios"pathWhere captures are saved
copytrueboolTry to copy the capture to the clipboard
previewtrueboolOpen the preview panel after a capture
frame"window"window, plain, noneA window card, a plain card, or nothing
background"auto"auto, none, #RRGGBB, #RRGGBB..#RRGGBBBackdrop. auto derives it from the theme. Two colours make a gradient
padding480 to 128Space around the card, in pixels
radius100 to 32Card corner radius, in pixels
shadowtrueboolSoft shadow under the card
controls"auto"auto, macos, glyphs, noneWindow buttons on the card
title_format"{title}"templateTitle bar text, with {title}, {index} and {cwd}
font_family"JetBrains Mono, monospace"stringFont to use when your terminal does not report its own
font_file""pathFont file to draw with. Wins over every other font choice, and is embedded in SVG and HTML output
scale21 to 4PNG size multiplier
cursorfalseboolDraw 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.

KeyDefaultWhat it does
enabledfalseStart after idle_minutes with no input
idle_minutes10Minutes of quiet before it starts, 1 to 240
effect"random"The effect, or random for a different one each time
while_busyfalseStart 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.

KeyDefaultValuesWhat it does
builtins"all"all, none, or names such as url,path,shaThe built-in patterns to look for
patterns[]list of Go regular expressionsYour own patterns. A group named match sets the part that is copied
alphabet"asdfghjkl"lowercase lettersThe letters of the labels, easiest first
opentrueboolLet Ctrl and a label open a URL or a path
dim6010 to 90How much the text around the labels dims, in percent
all_panesfalseboolPut 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 /).

KeyDefaultValuesWhat it does
label_keys"1234567890"letters a to z and digitsThe keys the labels are made of, first pane first. With more panes than keys, a label has two keys
navigator_layout"tree"tree, flat, cardsThe 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.

KeyDefaultValuesWhat 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.

KeyDefaultValuesWhat it does
width4012 to 200Width of the view in cells, border included. The view gets smaller on a small screen
height123 to 100Height of the view in cells, border included
corner"bottom-right"bottom-right, bottom-left, top-right, top-leftThe corner the view goes to first. The view moves to a different corner when the line you type reaches it

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.

KeyDefaultValuesWhat it does
enabledfalseboolTurn the spotlight on
follow"mouse"mouse, cursorFollow the pointer, or the focused pane's cursor
radius102 to 200Half the beam's height, in rows
dim7510 to 95How much an unlit cell is dimmed, in percent
edge"hard"hard, softCut the beam off, or fade it. A fade sends about three times the bytes
shakefalseboolShake 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  = ["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, notifications, copy-help, cpu, ram, clock and session-controls. 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.

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

Custom components

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

KeyDefaultWhat it does
commandrequiredRun 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-width24Widest the cell may be, in cells

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
KeyDefaultWhat it does
addrrequiredAnything ssh accepts, including an ssh_config alias. A host with no addr, or with one that starts with -, is ignored and logged
connect_timeout10Seconds 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. -A forwards your ssh agent to the host. See SSH Agent
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"]

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"]
CapabilityWhat it lets the other machine do here
listRead sessions, windows, captures, agent state, the Inbox, prompts, waits and events
mailSend and read agent mail, and use the stash
openStart sessions, windows, worktrees, fans and agents
writeType into panes, close and move windows, set options and names, report agent state, attach, and pull a worktree's work
respondAnswer 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

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

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.

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.

KeyDefaultWhat it does
enabledtrueOffer 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
usersnonePer-machine logins, keyed by short name. Wins over user
osplatforms that run TUIOSOperating systems to offer. An empty list offers every machine
offlinefalseAlso offer machines that are offline
selffalseAlso offer this machine
sharedfalseAlso offer machines shared into your tailnet
include, exclude[]Glob patterns on the short name and the MagicDNS name. exclude wins
maxbuilt inMost 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
KeyDefaultWhat 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_seconds120How long a prompt waits for your answer before the harness asks in its own pane
approvals.hold_planstrueAlso hold a plan an agent asks you to approve when it leaves plan mode
approvals.risk.builtintrueKeep the shipped risk rules
approvals.risk.panes_may_allowfalseLet a pane holding the respond grant allow a risky call
approvals.risk.rulenoneYour 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
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_patternsbuilt inCommands that count as a test run in the recap, such as go test and npm test
queue.max8Messages one pane's queue holds
checkpoints.enabledtrueSave the git work tree of an agent's pane each time the agent finishes a turn that changed a file. See Checkpoints
checkpoints.keep50How many checkpoints one pane keeps, at most 1000
checkpoints.max_untracked_mb50Leave 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 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
KeyDefaultWhat it does
enabledtruefalse 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_seconds120Hold a notification while you typed at an attached client in this many seconds. 0 sends at once
cooldown_seconds60The shortest time between two notifications for the same pane and kind
max_per_hour30The most notifications in one hour. 0 sets no limit
allow_http_redirectsfalseLet 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.

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 bindings TUIOS cannot parse, such as "cmd+t" or "ctrl+". At startup they print to stderr:

Config error in [window_management]: cmd+t - invalid modifier: cmd

One error at startup discards the whole file

If the file has an error when TUIOS starts, TUIOS uses the built-in defaults for everything, not just the broken key. The message prints before the screen is taken over, so it is easy to miss. Run tuios keybinds list to see it: that command prints Using default keybindings... when the file was rejected.

A file saved with an error while TUIOS runs is not applied, and the error is shown on screen.

Warnings keep the file. 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 them on screen.

Troubleshooting

The file is not loading. Run tuios config path to confirm which file TUIOS reads, then tuios keybinds list to see parse errors. Run tuios config files to see every file that TUIOS reads.

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. Check whether a later file sets the key. Run tuios config origin <key>. An old config.toml that sets every key hides the other files. Run tuios config prune to remove its default values. See Split the Config.
  5. 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

On this page