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 askingWrite 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
| 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 explorertuios 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.
| 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 | Colour theme. Empty keeps your terminal's own colours |
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_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 |
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_fpswhile 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
hyprctlon Hyprland,nirion niri andwlr-randron other wlroots compositors. On X11, or on another compositor, it asksxrandr --current. On macOS it askssystem_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-weband 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:
offpaints nothing, so your terminal's background shows through.themepaints the active theme's background, with the theme's foreground on default-coloured text. With no theme set, it behaves asoff.#RRGGBBpaints 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_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 |
image_symbols | "auto" | auto, octant, sextant, quadrant, half, off | The block characters for a sixel image on a terminal without graphics. See Images Without Graphics |
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 |
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}. Empty means {name} |
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 |
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
:Nfor a share in percent. - The section names are
sessions,terminals,files,agents,gitandcustom. 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.
spacerdraws 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
nanoorvim -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
editoris empty, TUIOS uses$EDITOR, then$VISUAL, thenvi. - 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.
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 = truetuios 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,ForkAfterAuthenticationandRequestTTY. - Files, sockets, environment and forwarding:
ControlMaster,ControlPersist,ControlPath,UserKnownHostsFile,GlobalKnownHostsFile,HostKeyAlias,IdentityAgent,SendEnv,SetEnv,ForwardAgent,ForwardX11andForwardX11Trusted. - The flags
-A,-X,-Y,-S,-L,-R,-D,-W,-w,-N,-f,-n,-T,-t,-Mand-s.
These ssh lines get an ordinary pane:
- An
-ooption that is not on the two lists above.ProxyCommand,LocalCommand,XAuthLocationandMatchare examples. - An option or a value with a control character, such as a line break.
-E,-F,-I,-G,-V,-Qand-O, and an option that tuios does not know.- A
-JorProxyJumphop that is not[user@]host[:port]or thessh://form, made of letters, digits and._:@[]-, or a hop that starts with-. - A mosh destination that starts with
-. - mosh with
--ssh,--clientor--server. - ssh that another program starts, such as
scp,rsync,gitorsftp.
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_osc7To 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 junctionsmiddle,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"
}iddefaults to the file name, lowercased.display_namedefaults toid.- The colour names are
black,red,green,yellow,blue,purple,cyan,white, each with abright_variant, plusfg,bgandcursor. Notepurple, notmagenta. - 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
chromeobject 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:
audioplays a short cue through the audio player of the machine that runs TUIOS:paplay,pw-playoraplayon Linux,afplayon macOS, PowerShell on Windows. If the machine has no player, the alert is silent and TUIOS shows no message.bellsends a BEL character to your terminal. Your terminal decides what a bell does, and some terminals make no sound. In kitty, check thatenable_audio_bellisyes. Other terminals have a similar bell setting.bothdoes 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+ (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.
| 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 |
Key syntax
- Modifiers:
ctrl+,alt+,shift+,super+. On macOS, alsoopt+andoption+, which mean the same asalt+. - Special keys:
enter,return,esc,escape,tab,space,backspace,delete,up,down,left,right,home,end,pgup,pageup,pgdown,pagedown,f1tof12. - Any single character: letters, digits, symbols, and accented letters.
- Held modifiers:
leftalt,rightalt,leftctrl,leftsuperand the like, forhold_window_modeonly. 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 paneAn 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 |
single_client | false | Keep 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_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 |
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.
| 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.
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:
- For a workspace that
tuios xpanesopened, the workspace where you rantuios xpanes. - The workspace you showed last that still has panes. After a chain of empty workspaces, TUIOS goes back past each one.
- 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 = falseThe 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 = trueThe 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_followand other moves that take a pane with them.tuios xpanes,tuios select-workspace,tuios run-commandand tape scripts.- The switch back that
return_when_emptymakes. - 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.
| 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 = 24png 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 |
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 = ["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.
| 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 |
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. -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 buildA 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, theServerAliveoptions and the algorithm lists. - Refused:
ProxyCommand,LocalCommand, the known hosts files,ControlPath,-Fand socket forwards. - Refused: port forwards (
-L,-R,-D). -JandProxyJumptake host names only.ForwardAgenttakes onlyyesorno.- A host
addrorcommandcannot 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"]| 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 |
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=22Then add a host to ~/.ssh/config that uses it:
Host laptop-tailcat
HostName laptop
User gaurav
ProxyCommand tailcat <token> 22Name 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 = falseenabled (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 |
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 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.
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: cmdOne 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.
- Check the table of what applies live. A saved keybinding works at the next key press.
- Check that the file parsed. Look for an error notification, or run
tuios keybinds list. - Check whether a command-line flag is overriding it.
- Check whether a later file sets the key. Run
tuios config origin <key>. An oldconfig.tomlthat sets every key hides the other files. Runtuios config pruneto remove its default values. See Split the Config. - 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