Configuration
Every table and option in the TUIOS config.toml, with defaults and how changes apply.
TUIOS reads one TOML file, ~/.config/tuios/config.toml. If XDG_CONFIG_HOME is set, the file is $XDG_CONFIG_HOME/tuios/config.toml. TUIOS writes a commented default file on first run.
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.
[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, 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 19 sections of bindings |
[daemon] | Daemon log level and agent detection |
[startup] | How a new session starts |
[tape] | Project tapes |
[debug] | The keycast overlay |
[screenshot] | Capture format, frame and fonts |
[screensaver] | The idle animation |
[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 |
[tailscale] | Which tailnet machines tuios hosts add offers |
[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 |
|---|---|
Keybindings other than leader_key | The next client: detach and attach again, or restart |
[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, but only for [hosts] and preferred_shell. Adding or removing a host opens or closes its link at once.
Changing a setting at runtime
tuios list-options # every path, with default and accepted values
tuios list-options --section sidebar # one group
tuios get-config appearance.border_style
tuios set-config appearance.border_style thickset-config changes a running 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] or the [dock] lists, because those are maps rather than single values. 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 settings page rewrites the whole file
Each change is saved by writing the full config to config.toml. Your comments, key order and formatting are replaced by the generated header and every option with its value. tuios keybinds unbind and tuios keybinds free write the file the same way. Keep a copy in version control if you edit the file by hand.
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.animations_enabled = false |
--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 | Share one border between adjacent tiled panes |
gap | 0 | 0 to 8 | Empty cells between tiled panes |
dim_unfocused | 0 | 0 to 90 | Fade panes you are not in, in percent |
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 |
animations_enabled | true | bool | Animate transitions |
max_fps | 0 | 0, or 10 to 120 | Frame rate cap. 0 means 60 |
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.
Windows and titles
| Key | Default | Values | What it does |
|---|---|---|---|
window_title_position | "bottom" | bottom, top, 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 |
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 |
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 | "single" | single, double, off | In window mode, whether a click on a pane starts typing in it |
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 | Which links Shift+click opens. See below |
kitty_placeholders | "auto" | auto, on, off | Draw images placed with kitty Unicode placeholders |
links
Shift+click on a link opens it. Shift is the terminal's "this click is mine" modifier, so it works even when the program in the pane uses the mouse.
markedfinds only OSC 8 hyperlinks, where the program says a run of cells is a link.all(default) also finds plainhttp,httpsandfileURLs in the text.offfinds none.
Only http, https, mailto, ftp and ftps links go to your desktop's opener. Any other scheme is copied to the clipboard instead, with a notification. A file:// link opens a file in a new pane running your editor, and a directory in the rail's files section. A remote client cannot open links, so it copies the address.
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 |
|---|---|---|---|
master_ratio | 50 | 30 to 70 | Master pane width in the master-stack layout, in percent |
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 |
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 | 100 | 50 to 100 | Share of the screen a zoomed pane takes, in percent |
zoom_max_width | 0 | cells | Width cap for a zoomed pane. 0 fills the screen |
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.
Dock
| Key | Default | Values | What it does |
|---|---|---|---|
dockbar_position | "bottom" | bottom, top, 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_tooltip | true | bool | Show a truncated workspace name in full on hover |
dock_pill_caps | false | bool | Powerline caps on the dock's pills instead of flat ends |
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 |
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 | "thin" | thin, track | A hairline thumb, or a full-height track |
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 colours |
|---|---|---|
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 |
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 |
An empty foreground keeps the colour the program wrote. An empty background falls back to the default.
The session rail
[appearance.sidebar] configures the rail at the side of the screen.
| Key | Default | Values | What it does |
|---|---|---|---|
enabled | false | bool | Show the rail |
position | "left" | left, right, hidden | Edge the rail sits on |
width | 28 | columns | Preferred width on a wide screen |
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 |
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 |
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,agentsandgit. Leave a name out to hide that 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. Nothing polls, so a file written by another program appears on the next read.
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.
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", "harness", "name", "elapsed", "message"]
[[appearance.sidebar.agent_row.elapsed.rule]]
gt = 30 # minutes in the current state
fg = "warning"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.
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.
[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.
Keybindings
leader_key
The prefix key. The default is ctrl+b.
[keybindings]
leader_key = "ctrl+a"alt+space is already the app launcher.
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 |
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 |
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.
A session keeps its own layout once it is running. Attaching never rearranges a session to match your config.
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 spotlight table
[spotlight] lights one part of the screen and dims the rest, for screen sharing. b in window mode toggles it.
| 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.
[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.
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 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 |
You can also manage the table from the shell. These commands edit the file, and the daemon applies the change at once:
tuios hosts add build gaurav@buildbox
tuios hosts test build
tuios hosts remove buildThe 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.
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, and ssh_options is where you name it.
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 name it in the host entry:
[hosts.laptop]
addr = "gaurav@laptop"
ssh_options = ["-o", "ProxyCommand=tailcat <token> 22"]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"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.
A setting changed nothing.
- Check the table of what applies live. Keybindings need a new client.
- Check that the file parsed. Look for an error notification, or run
tuios keybinds list. - Check whether a command-line flag is overriding it.
- 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