TUI customization
The devenv TUI reads personal settings from a versioned YAML file. These settings affect presentation and interaction only. They are separate from the reproducible project configuration in devenv.nix and devenv.yaml.
The default path is $XDG_CONFIG_HOME/devenv/config.yaml, falling back to ~/.config/devenv/config.yaml. This path is the same on Linux, macOS, and Linux distributions running under WSL.
Override it for one invocation with --user-config or for a shell with DEVENV_USER_CONFIG:
$ devenv --user-config ./demo.yaml up$ DEVENV_USER_CONFIG=./demo.yaml devenv upThe flag takes precedence over the environment variable. Relative paths are resolved from the directory in which devenv was invoked, even when devenv later discovers and enters a project root. An explicitly selected file must exist. A missing default file uses the built-in settings.
Start a configuration
Section titled “Start a configuration”Every file declares its schema version. Unknown fields, invalid colors, unsupported placeholders, duplicate statusline components, conflicting keys, and ambiguous key-sequence prefixes are errors.
# yaml-language-server: $schema=https://devenv.sh/devenv.user.schema.json
version: 1The yaml-language-server comment associates the file with the generated JSON Schema for editor completion and validation of static structure, ranges, action names, colors, and key syntax. devenv ignores it as ordinary YAML commentary. user-config validate additionally checks relationships between settings, custom names, key conflicts, and statusline formats. The CLI can inspect the active path and configuration without loading a project:
$ devenv user-config path$ devenv user-config validate$ devenv user-config show$ devenv user-config schemauser-config validate validates the file at the resolved path. user-config show prints structural defaults and configured overrides. Empty keybinding tables inherit the effective defaults listed below.
Complete example
Section titled “Complete example”This example keeps the TUI at the command’s terminal position, adds profiles and the project name to the main statusline, colors profiles with a custom palette entry, moves the activity summary, uses a two-key shortcut in the log viewer, and changes log behavior.
# yaml-language-server: $schema=https://devenv.sh/devenv.user.schema.json
version: 1tui: viewport: inline theme: preset: devenv palette: profile: "#cba6f7" surface: ansi:236 styles: statusline: background: surface statusline.profiles: foreground: profile modifiers: [bold] statusline: position: inline layouts: main: left: [profiles, summary] center: [project] right: [elapsed, key_hints] components: profiles: format: "profile {profiles}" compact_format: "{profiles}" priority: 90 elapsed: format: "elapsed {elapsed}" compact_format: "{elapsed}" priority: 20 overflow: hide keybindings: sequence_timeout_ms: 750 logs: top: [home, "g g"] bottom: [end, "shift+g"] behavior: mouse: true follow_logs: true hide_stopped_processes: false log_preview_lines: 12 log_history_lines: 10000Viewport placement
Section titled “Viewport placement”tui.viewport accepts inline (the default) or top. inline begins rendering where devenv was invoked and preserves the terminal content above it. The viewport stays there while it fits, then scrolls upward only by the rows needed when its content reaches the terminal bottom. This matches ordinary terminal output.
top moves the existing visible terminal content into scrollback and claims the terminal from its first row. It is useful when you prefer a stable top-aligned workspace regardless of where devenv was invoked:
tui: viewport: topStatusline layouts
Section titled “Statusline layouts”tui.statusline.position accepts inline (the default), top, or bottom. inline keeps devenv’s normal terminal behavior by placing the statusline directly after the current activity output without reserving the terminal height. top and bottom are sticky modes that pin the statusline to the chosen terminal edge as activities expand or collapse. Fullscreen logs always fill the terminal. In fullscreen logs, inline uses the conventional bottom footer.
Each TUI mode has independent left, center, and right component lists:
tui: statusline: layouts: main: left: [profiles, summary] center: [project] right: [elapsed, key_hints] logs: left: [log_mode, log_position] center: [project] right: [retained_logs, key_hints] search: left: [search] center: [] right: [pending_key, key_hints] prompt: left: [prompt] center: [] right: [key_hints]The built-in components are:
| Component | Available values |
|---|---|
summary |
{summary} |
builds, downloads, queries, tasks |
{active}, {completed}, {failed}, {total}, {expected} |
processes |
{running}, {stopped}, {failed}, {hidden}, {total} |
profiles |
{profiles}, {count} |
project |
{name}, {path} |
command |
{command} |
shell |
{shell} |
elapsed |
{elapsed} |
selected |
{name}, {status} |
log_mode |
{mode} |
log_position |
{current}, {total}, {percent} |
retained_logs |
{retained}, {discarded}, {total} |
search |
{query}, {current}, {total}, {result} |
prompt |
{prompt} |
pending_key |
{keys} |
key_hints |
{hints} |
Components with no runtime value are omitted unless show_empty: true. Profiles are the fully resolved profiles for the current invocation, including command-line and trusted auto-activation profiles.
Each built-in component can be customized under tui.statusline.components.<name>. A custom name must declare a type:
tui: statusline: components: brand: type: text text: devenv format: "[{text}]" required: true workspace: type: project format: "project {name}" compact_format: "{name}" priority: 80 max_width: 30 overflow: truncateformat is used first. If the terminal is too narrow, the renderer tries compact_format, then removes lower-priority components whose overflow is hide, then truncates by terminal display width. A larger priority preserves a component longer. required prevents the hide step, but still permits final truncation so the TUI never writes beyond the terminal width. Literal braces are written as {{ and }}.
max_width limits a component to that many terminal columns before overflow handling. It must be at least 1. type: text requires text, and text is only valid for text components. overflow accepts hide or truncate.
Set tui.statusline.enabled: false to hide ordinary TUI statusline content and the persistent statusline in an interactive devenv shell. Search and Ctrl-C confirmation prompts remain visible in activity views so active interactions and their actions are never hidden. Set tui.statusline.position to top or bottom for a sticky statusline, or leave it as inline to keep it directly after activity output. The separator must be single-line text no wider than eight terminal columns.
Colors and styles
Section titled “Colors and styles”The devenv preset retains the built-in palette. terminal uses terminal-native colors where possible, and none removes preset statusline colors. Explicit styles work with every preset. Theme customization currently applies to the statusline. Activity rows, process states, and log content retain their built-in semantic colors.
Colors accept named terminal colors such as yellow, dark_grey, and default, indexed colors as ansi:0 through ansi:255, RGB colors as #RRGGBB, or a name from tui.theme.palette.
Styles can target the entire statusline, its separator, or one component:
tui: theme: styles: statusline: foreground: default statusline.separator: foreground: dark_grey statusline.profiles: foreground: magenta background: ansi:236 modifiers: [bold, underline]Component-local style settings take precedence over theme scopes. Supported modifiers are bold, dim, italic, underline, and reverse.
Keybindings
Section titled “Keybindings”Keybindings are grouped by interaction context. Assigning an empty array disables an action’s defaults.
tui: keybindings: main: move_down: [down, j] move_up: [up, k] open_logs: ["ctrl+e", "g l"] toggle_stopped: []Chords use lowercase modifiers in the order ctrl, alt, shift, followed by a key. Named keys include enter, esc, backspace, delete, insert, arrow keys, home, end, page_up, page_down, tab, back_tab, space, and f1 through f24. Separate chords in a sequence with spaces. Sequences can contain up to four chords.
tui.keybindings.sequence_timeout_ms sets how long devenv waits for the next chord in a sequence. It accepts 100 through 5000 milliseconds and defaults to 750.
The contexts and actions are:
| Context | Actions |
|---|---|
main |
move_down, move_up, half_page_down, half_page_up, activate, expand, collapse, open_logs, search, restart_process, stop_process, toggle_stopped, cancel |
process_search |
next_match, previous_match, accept, cancel |
logs |
line_down, line_up, half_page_down, half_page_up, page_down, page_up, top, bottom, search, next_match, previous_match, copy, back |
log_search |
accept, cancel |
prompt |
cancel, quit, stop_manager |
Within one context, a chord cannot be assigned to two actions and a sequence cannot be the prefix of another sequence. ctrl+c is reserved for emergency interruption and copying selected log text, so it cannot be rebound.
Displayed key hints are derived from the resolved bindings. When a multi-key sequence is pending, the pending_key component can show its accepted prefix.
Terminal input protocols differ. Basic characters, arrows, and common control chords are broadly portable, while multiple modifiers and modified special keys require support from the terminal emulator. Validation proves that a binding is well-formed, not that every terminal can emit it distinctly.
Default keybindings
Section titled “Default keybindings”tui: keybindings: main: move_down: [down, j] move_up: [up, k] half_page_down: ["ctrl+d"] half_page_up: ["ctrl+u"] activate: [enter] expand: [right, l] collapse: [left, h] open_logs: ["ctrl+e"] search: ["/"] restart_process: ["ctrl+r"] stop_process: ["ctrl+x"] toggle_stopped: ["ctrl+h"] cancel: [esc] process_search: next_match: [down] previous_match: [up] accept: [enter] cancel: [esc] logs: line_down: [down, j] line_up: [up, k] half_page_down: ["ctrl+d"] half_page_up: ["ctrl+u"] page_down: [page_down, space, "ctrl+f"] page_up: [page_up, "ctrl+b"] top: [home, g] bottom: [end, "shift+g"] search: ["/"] next_match: [n] previous_match: ["shift+n"] copy: [y] back: [q, esc, "ctrl+e"] log_search: accept: [enter] cancel: [esc] prompt: cancel: [c, esc] quit: [q] stop_manager: [s]Shell settings
Section titled “Shell settings”shell.prompt_prefix controls the (devenv) prefix in interactive shell prompts and defaults to true.
Disable it to keep your own prompt, for example when using Starship:
version: 1shell: prompt_prefix: falseA project’s prompt_prefix setting in devenv.yaml or devenv.local.yaml takes precedence.
Changes take effect when you start a new shell.
Shell keybindings
Section titled “Shell keybindings”shell.keybindings controls shortcuts claimed inside an interactive devenv shell. Each action accepts a list of single key chords. Omit an action to keep its default. Set it to [] to release every shortcut for that action.
shell: keybindings: toggle_pause: ["ctrl+alt+d"] list_watched_files: ["ctrl+alt+w"] toggle_error: ["ctrl+alt+e"] reload: ["ctrl+alt+r"]Shell keybindings use the same key and modifier syntax as TUI keybindings. Multi-key sequences are not supported. Ctrl+C remains reserved.
toggle_pause, list_watched_files, and toggle_error have the same defaults in every shell. reload defaults to Ctrl+Alt+R in Fish, Nushell, and Zsh, and is unbound in Bash. An explicit reload binding applies to Bash, Fish, Nushell, and Zsh.
DEVENV_RELOAD_KEYBIND remains a Zsh-only fallback when reload is omitted.
Behavior
Section titled “Behavior”tui: behavior: mouse: true hide_stopped_processes: false follow_logs: true log_preview_lines: 10 log_history_lines: 1000mouseenables selection and wheel scrolling in fullscreen logs.hide_stopped_processescontrols the initial process filter.follow_logscontrols the initial fullscreen log mode.log_preview_linessets the maximum collapsed preview size from 1 through 1000.log_history_linessets retained lines per build fromlog_preview_linesthrough 1,000,000.

