Every knob thurbox reads, where it lives, and how it behaves. One file
per audience/lifecycle: hand-edited registries are TOML, the
machine-written keybindings are JSON, and concurrently-written runtime
state lives in SQLite (see ADR-8/ADR-19 in ARCHITECTURE.md for the
rationale).
Dev builds (version 0.0.0-dev) use thurbox-dev in place of
thurbox in every path below, plus a thurbox-dev tmux socket, so a
development checkout never touches your real setup.
| File | Format | Edited by | Read | Purpose |
|---|---|---|---|---|
~/.config/thurbox/agents.toml |
TOML | you | live (mtime poll) | coding-agent CLI definitions |
~/.config/thurbox/hosts.toml |
TOML | you | startup | remote SSH hosts + local WSL distros |
~/.config/thurbox/settings.toml |
TOML | you + Ctrl+, panel |
live (feature flags) / startup (rest) | tuning knobs + feature flags |
~/.config/thurbox/themes.toml |
TOML | you | startup | custom theme palettes |
~/.config/thurbox/keybindings.json |
JSON | F1 editor (or you) | live (mtime poll) | key chord overrides |
~/.config/thurbox/extensions/<name>.toml |
TOML | thurbox-cli extension install |
startup + tick | extension manifests (self-healed resources) |
~/.local/share/thurbox/thurbox.db |
SQLite | thurbox | live | sessions, automations, tasks, theme, editor command |
~/.local/share/thurbox/thurbox.log |
text | thurbox | — | logs (incl. config warnings) |
agents.toml, keybindings.json, and settings.toml reload live:
the TUI polls their mtime (~1/s) and applies edits with a confirmation
toast — no restart. For settings.toml only the feature flags that
gate UI panels (tasks, file_viewer, info_panel, global_search,
shell_pane, code_review, perf_hud, soft_delete) apply live; the restart-only values stay
published through a write-once global (so they can't drift mid-frame),
and the reload toast says when a restart is needed. hosts.toml (SSH
backends register at startup) and themes.toml need a restart.
settings.toml can also be edited from the TUI: Ctrl+, (alt F6)
opens a Settings panel listing every knob. It writes the file back
preserving its comments, and feature flags that gate UI panels apply
live on save; the rest (mouse, notifications, automations,
version_check, auto_update, the four editable [notifications] knobs,
and the scalars) take effect on the next launch — the panel marks those rows
with ⟳ and toasts a restart note. The panel exposes only the four
editable notification knobs (also_on_waiting, suppress_for_active,
sound, min_interval_secs); [notifications] backend is not in
the panel — set it only by hand-editing settings.toml. Hand-editing
the file (or the panel in another instance) is picked up the same way,
via the live mtime poll.
All paths respect $XDG_CONFIG_HOME / $XDG_DATA_HOME.
A task-to-file map so you don't have to scan every section to find the right knob:
| I want to… | Edit | Section |
|---|---|---|
| Add a coding agent, pin a model, change resume/fork flags | agents.toml |
agents.toml |
| Run sessions on a remote machine over SSH, or in a local WSL distro | hosts.toml |
hosts.toml |
| Turn a whole TUI feature on/off (tasks, mouse, notifications…) | settings.toml [features] |
[features] |
| Tune scrollback, panel breakpoints, audit retention | settings.toml |
settings.toml |
| Change when/how OS notifications fire | settings.toml [notifications] |
[notifications] |
| Add or recolour a TUI theme | themes.toml |
themes.toml |
| Rebind a key | keybindings.json (or the F1 editor) |
keybindings.json |
Set the Ctrl+O editor, pick a theme |
(runtime — SQLite) | SQLite-backed settings |
None of these files need to exist on a fresh install — every one is seeded (commented-out where applicable) on first run, and absent files fall back to built-in defaults.
Config problems are not silent: parse errors, unknown fields, invalid chords, and chord conflicts surface as a status-bar toast on startup (and in the log file). Unknown TOML keys are tolerated — stale keys from older versions or typos are reported by name but your file still loads — while syntax/type errors fall back to built-ins (agents), zero hosts, or defaults (settings).
agents.toml degrades per entry: a single malformed [[agents]]
block (e.g. args given a string instead of an array) is skipped with
a toast naming it, and your remaining agents still load — only a
document-level syntax error (or a file with no usable agents) falls
back to the built-ins.
Check everything from the command line:
thurbox-cli config validate # strict parse of every file; exit 1 on problems
thurbox-cli config show # effective config + where each value came fromvalidate fails on unknown keys (they are typos or leftovers either
way), making it usable as a dotfiles CI gate.
Declares the launchable coding agents. Seeded with the built-ins
(claude, codex, antigravity, opencode, aider, copilot, vibe, pi,
omp) on
first run; edit or add [[agents]] entries to support any CLI — no
recompile. A malformed [[agents]] entry is skipped (with a toast
naming it) and the rest still load; only a document-level syntax error
falls back to the built-ins. Either way the error is shown.
The file is seeded once and never rewritten — so it stays yours to
edit, but a thurbox update that adds a new built-in agent does not
merge it into an existing agents.toml. To pick up a newly-bundled
agent, add its [[agents]] block by hand (copy it from this file's
built-in list) or delete agents.toml to re-seed the full set. The
matching status hook is wired automatically once the agent's config dir
exists — the built-in hooks extension self-heals on every startup/tick,
independent of agents.toml.
config_version = 1
default = "claude" # agent preselected in the picker / headless spawns
[[agents]]
name = "claude" # display + lookup name (unique)
command = "claude" # executable
args = [] # always passed; bake a model here if you want one
resume_args = ["--resume", "{id}"] # emitted when resuming
fork_args = ["--resume", "{id}", "--fork-session"]
new_session_args = ["--session-id", "{id}"] # emitted on a fresh spawn
resume_latest = false # true = id-less "resume last session in cwd"
# hook_schema = "claude" # optional: name the hook FAMILY this CLI speaks so
# the built-in hooks extension wires its status
# hooks under this custom agent's name too{id} is substituted with the thurbox-generated session UUID. {home}
is substituted with the resolved home dir at spawn time (the remote home
for an SSH/WSL host) — for an agent that wants a session file path
rather than a bare id. The built-in omp (Oh My Pi) uses it: it generates
its own internal id and won't take thurbox's, but its --session <path>
creates a fresh session at a missing path, so thurbox maps its UUID to a
deterministic --session {home}/.omp/agent/sessions/thurbox-{id}.jsonl
(creation) / --resume the same (restart). {home} is expanded by
thurbox, not the shell — args are POSIX-quoted, so a literal ~ would
never expand. omp ships no fork_args, so Ctrl+F starts a fresh
session (OMP has no way to pin a fork's target file to a thurbox UUID).
Groups
are emitted only when their driving value exists; precedence is
fork > resume > new-session. See the seeded file's comments and
CLAUDE.md's Agent Definitions section for the resume_latest
semantics.
hook_schema is optional. Custom agents are agent-neutral, so the built-in
hooks extension normally wires status hooks only for the built-ins it knows
by name. Set hook_schema = "claude" on a rebranded agent (one whose
command runs claude under a different name) and it inherits claude's hook
wiring — the --settings patch locally, and the same rewrite on a remote/WSL
host. It names the family to imitate, not a boolean; today the useful value is
"claude" (the only family wired via a per-agent arg patch — codex/opencode/
antigravity/vibe/copilot are wired through their own config dir, so a rebrand
sharing that dir already reports status).
The seeded file also ships two commented, copy-pasteable templates
below the built-ins — Add your own agent (every field annotated)
and Pin a model (a claude-opus variant baking --model opus
into args). Both stay commented, so a fresh install still resolves
to exactly the nine built-ins.
For each built-in's exact config and runtime behavior (resume/fork semantics, ID model, and which status-hook mechanism it uses), and the checklist for adding a new built-in, see AGENTS.md.
Declares off-local hosts — remote SSH machines and local WSL
distros. Each [[hosts]] entry registers a session backend named
ssh:<name> (default kind) or wsl:<name>. Seeded fully commented-out
(fresh installs are local-only for SSH). Malformed file → zero
configured hosts, error shown.
| Field | Required | Default | Purpose |
|---|---|---|---|
name |
yes | — | backend id ssh:<name> / wsl:<name>; what --host expects |
kind |
no | ssh |
transport: ssh (remote machine) or wsl (local distro) |
destination |
for ssh | — | ssh target (user@host or ~/.ssh/config alias) |
distro |
no | name |
WSL distro name (kind = "wsl" only) |
ssh_opts |
no | [] |
extra ssh flags, one token per element (ssh only) |
socket |
no | thurbox |
host tmux -L socket |
session |
no | thurbox |
host tmux session name |
worktrees_dir |
no | host $HOME/.local/share/thurbox/worktrees |
absolute worktrees dir on the host/distro |
multiplexer |
no | tmux |
host multiplexer binary; set to psmux for a Windows SSH host |
SSH auth comes entirely from your ~/.ssh/config; thurbox never
handles credentials. WSL distros are reached with
wsl.exe -d <distro> and need no config entry at all — on Windows they
are auto-discovered (wsl.exe -l -q) and appear in the host picker
and --host automatically; add a kind = "wsl" entry only to override
a default (e.g. worktrees_dir). For both kinds, tmux, git, the agent,
and worktrees all run on the host / inside the distro at native
paths (a WSL distro's worktrees live in its own Linux filesystem, not on
/mnt/c); the distro needs tmux >= 3.2 and git. Host changes
require a restart (the registry is read once and each host's $HOME is
cached for the process lifetime).
Scalar tuning knobs plus the [features] switches, seeded fully
commented-out (defaults apply when absent). Only knobs a user plausibly
wants are exposed; internals stay hardcoded. The seed closes with a
Common recipes block — copy-pasteable groupings (bigger scrollback,
a minimal/focused TUI, notification tuning, enabling the update badge),
all commented so defaults still apply out of the box.
| Key | Default | Purpose |
|---|---|---|
scrollback_lines |
1000 |
terminal scrollback kept per session |
two_panel_min_cols |
80 |
width below which only the terminal renders |
three_panel_min_cols |
120 |
width unlocking the optional third column |
audit_retention_days |
90 |
audit-log history kept (pruned on startup) |
A complete settings.toml showing every knob at its default — copy
this, uncomment what you want to change, and restart:
config_version = 1
# Scalar tuning knobs (top level)
scrollback_lines = 1000 # terminal scrollback kept per session
two_panel_min_cols = 80 # width below which only the terminal renders
three_panel_min_cols = 120 # width unlocking the optional third column
audit_retention_days = 90 # audit-log history kept (pruned on startup)
[features]
tasks = true
automations = true
file_viewer = true
global_search = true
info_panel = true
shell_pane = true
code_review = true
perf_hud = true
mouse = true
notifications = true
soft_delete = true
version_check = true # on by default (1.0): makes a network call
auto_update = true # on by default (1.0): downloads + replaces binaries
[notifications]
also_on_waiting = false # also fire when a session finishes (Working → Done)
suppress_for_active = true # skip the session you're currently viewing
sound = true # play the OS default notification sound
min_interval_secs = 5 # per-session floor between notificationsTurn major TUI features off entirely. All default to true — as of 1.0
that includes version_check and auto_update, which both reach the
network (they were opt-in before 1.0). The UI-panel flags
(tasks, file_viewer, info_panel, global_search, shell_pane,
code_review, perf_hud, soft_delete) apply live on save; the rest
(automations, mouse, notifications, version_check, auto_update)
take effect on the next launch.
A disabled feature's pane never renders, its keybinding shows
a status toast instead of acting, and its global-search scope returns
no results. Data is never touched, so re-enabling a flag is lossless.
| Key | Default | Controls |
|---|---|---|
tasks |
true |
tasks panel (F5/Ctrl+W) and task search results |
automations |
true |
automations pane, Ctrl+P, TUI schedule firing, heartbeat arming |
file_viewer |
true |
file viewer column (F3) and file search results |
global_search |
true |
global search strip (Ctrl+/) |
info_panel |
true |
info panel column (F2) |
shell_pane |
true |
per-session shell toggle (Ctrl+T) |
code_review |
true |
native code-review view (diff + comments, Ctrl+X) |
perf_hud |
true |
perf HUD overlay (F12): live perf counters + frame/tick timing (see docs/PERFORMANCE.md) |
mouse |
true |
mouse capture: clicks, wheel, drag-select, hover, scrollbars |
notifications |
true |
OS desktop notifications when a session needs attention |
soft_delete |
true |
TUI Ctrl+D soft-deletes (Ctrl+Z undo); off = hard delete after a confirmation prompt |
version_check |
true |
GitHub update check: TUI header "update available" badge + thurbox-cli version --check |
auto_update |
true |
Silent self-update: download + verify + replace the binaries on startup + thurbox-cli update; also auto-refreshes stale extensions |
automations = false is a full stop on the TUI side: the pane
disappears (the session list takes the whole left column and j/k
wrap within it), and the TUI neither fires due schedules nor arms the
tmux heartbeat keeper on startup. Explicit thurbox-cli automation
commands still work — and automation create still arms the
heartbeat, so an already-armed keeper window (or an OS timer from
packaging/) keeps firing schedules externally. Disabling
shell_pane hides existing shell panes but never kills their
processes. mouse = false skips terminal mouse capture entirely, so
the terminal keeps its native mouse behavior (its own text selection,
URL handling, etc.) and no click/wheel/hover handling runs in the TUI.
notifications = false keeps the background dispatcher thread from
ever starting (zero overhead) and silently no-ops every transition;
the session status display itself is unaffected.
soft_delete = false turns the TUI's Ctrl+D into a destructive
hard delete: instead of marking the row deleted with a Ctrl+Z
undo window, it kills the session's tmux window, removes its worktrees
and symlink workspace, and disables any pending Send automations —
after a confirmation prompt (Enter/y to delete, Esc/n to
cancel), since the teardown is irreversible. The soft-deleted row is
still written last, so the session remains restorable via Ctrl+U
(which re-spawns it fresh). This flag governs the TUI only:
thurbox-cli session delete always soft-deletes unless you pass
--force, regardless of the setting.
version_check (on by default for 1.0) enables the update check — it
makes a network call. On launch the TUI reads a cached result
(~/.local/share/thurbox/version-check.json) and, if it is older than
24 h, fires a single best-effort background fetch of GitHub's latest
release (api.github.com/repos/Thurbeen/thurbox/releases/latest, via
curl/wget — no new dependency); a newer release shows a ⬆ vX.Y.Z available badge next to the version in the header. The fetch never runs
on the render path and never blocks startup; failures are silent. Dev
builds (0.0.0-dev) never show the badge. The same flag enables
thurbox-cli version --check, which fetches fresh on demand and reports
current vs. latest (thurbox-cli version with no flag always prints the
current version, regardless of the flag).
auto_update (on by default for 1.0) goes a step further than
version_check: instead of just showing a badge, the TUI silently
updates itself on startup. On every
launch (it does not reuse the version_check badge's 24 h cache — sharing
that gate let the badge keep the cache "fresh" and starve the updater) it
fetches the latest release tag; if a newer release exists it downloads that
release's tarball + checksums from GitHub Releases (curl/wget, no new
dependency), verifies the SHA256 (sha256sum/shasum), extracts it
(tar), and atomically replaces the installed thurbox/thurbox-cli
binaries in place — mirroring scripts/install.sh. The download is verified
before any installed file is touched, so a failed/corrupt download leaves
the current binaries untouched; the whole step runs before the TUI takes the
terminal and is best-effort (any failure is logged and startup continues on
the current version). The replaced binary takes effect on the next launch
(the running process keeps its open file), so the TUI shows an "Updated to
vX.Y.Z — restart to apply" status line. thurbox-cli update performs the
same update on demand (with --force to bypass the up-to-date and dev-build
guards); dev builds (0.0.0-dev) never auto-update. The default install
location (~/.local/bin) is user-writable; a system-wide install in a
root-owned directory will fail the replace (logged, non-fatal). version_check
and auto_update are independent — enable either or both.
auto_update = true also keeps installed extensions in step with the
binary. Extension versions are pinned to the binary's release tag, so an
extension only goes stale (installed_with ≠ the running binary) right after an
upgrade. The self-heal pass — which already runs on TUI startup and on the
headless automation tick — then refreshes each stale extension in place
(re-fetching it from its recorded source) instead of only nudging you to run
thurbox-cli extension update. The staleness check is local and network-free,
so a launch where nothing is stale does no extra work; a refresh runs at most
once per extension per binary version. With auto_update off, the nudge is
shown and you update extensions by hand.
Surfaces an OS notification when a session transitions into a state
that needs your attention. The trigger is the hooks-driven
session status (reported by the agent's hooks, not
the terminal bell / output): the notification fires when a session crosses
into Blocked (the agent needs input or approval) and, with
also_on_waiting = true, also when it finishes a turn (Working → Done).
The edge is observed once per tick in refresh_session_statuses — the
same place the session-list status icon is computed, so the banner can
never drift from the list — then deduped per session (min_interval_secs)
and skipped for the session you're currently viewing (suppress_for_active).
The notification body is the agent's last OSC 9 / OSC 777 message when
present (truncated to 200 chars), otherwise Waiting for input — the OSC
message is kept only for the body text, no longer for the trigger.
Only fires while the TUI is open — the dispatcher thread runs only
inside the TUI, so a headless automation tick never notifies.
Delivery backend (backend, default auto) is detected at startup:
| Backend | When | Click-to-focus |
|---|---|---|
dbus |
normal Linux desktop with a running notification daemon (org.freedesktop.Notifications) |
yes — clicking the banner writes a focus request the running TUI reads next tick and switches to that session |
windows (toast) |
native Windows, or WSL / any Linux with no dbus daemon — delivers a Windows toast via powershell.exe (WSL needs interop, on by default) |
no (a Windows toast can't call back into the thurbox process) |
macos |
macOS native banner. Uses terminal-notifier when it's in PATH (own bundle + icon, looks like a real app notification — brew install terminal-notifier), otherwise the built-in osascript display notification (attributed to Apple's Script Editor). The UNUserNotificationCenter click API needs a signed .app bundle, which thurbox is not, so the osascript path is informational |
with terminal-notifier — clicking the banner runs thurbox-cli session focus <id>, which writes the same metadata row the dbus path does and the TUI picks it up next tick. Without terminal-notifier (osascript fallback): no |
auto prefers dbus whenever a daemon answers, and only falls back to
the Windows toast when no dbus service is reachable. Force a specific
path or disable delivery with backend = "dbus" | "windows" | "off"
(off is a soft switch distinct from [features] notifications, which
stops the dispatcher thread entirely).
This auto-detection fixes a previously silent failure on WSL: the dbus path errored on connect, but the only signal was a line in the logfile, so the user saw nothing. Delivery errors are now recorded and surfaced by the diagnostic:
thurbox-cli notify # show the detected backend + last delivery error
thurbox-cli notify --test # fire a sample notification to confirm it works| Key | Default | Purpose |
|---|---|---|
also_on_waiting |
false |
also fire when a session finishes (Working → Done); the field name is historical |
suppress_for_active |
true |
skip the notification for the session you're currently viewing |
sound |
true |
play the OS default notification sound |
min_interval_secs |
5 |
per-session floor between two notifications (dedup) |
backend |
auto |
delivery backend: auto | dbus | windows | off |
Notifications fire on the hooks-driven status transitions (see
Session status): always on → Blocked (the agent needs
you), and with also_on_waiting = true also on Working → Done.
Each session's state (Blocked / Working / Done / Idle / Error) is driven by
agent hooks that call thurbox-cli session signal --state <working|blocked|done|idle>. The state is persisted on the sessions row
(hook_state, hook_state_at, seen_at — schema v34) and survives the TUI
being closed; a hook fired headlessly is picked up via PRAGMA data_version.
Identity comes from the injected THURBOX_SESSION env var, so a hook passes
no id. A finished turn shows Done (blue) — for the session you're watching too
— and becomes Idle once you switch focus off it.
The hooks are wired up automatically by the built-in hooks extension
(auto-activated on first run). Opt out with thurbox-cli extension deactivate hooks. The status colours are tunable theme keys (status_working /
status_blocked / status_done / status_idle / status_error /
status_unreachable — see themes.toml).
The wiring is applied only to agents thurbox launches — it never edits your
own global agent config (e.g. your personal ~/.claude/settings.json). thurbox's
managed hook config lives per agent, applied by injecting a flag into
agents.toml or by a reversible merge into / managed file in the agent's own
config dir:
| Agent | On-disk location | How it's applied |
|---|---|---|
| claude | ~/.config/thurbox/hooks/claude.json |
--settings flag (claude merges it with your own settings) |
| aider | — (no file) | --notifications-command flag |
| opencode | ~/.config/opencode/plugin/thurbox-status.js |
managed plugin file |
| codex | ~/.codex/hooks.json |
reversible JSON-merge of thurbox's entries |
| vibe | ~/.vibe/hooks.toml |
managed file (refused if you already have one) |
| antigravity | ~/.gemini/settings.json |
reversible JSON-merge of thurbox's entries |
The home dir is ~/.config/thurbox/hooks on a release build and
~/.config/thurbox-dev/hooks on a dev build. Because claude merges the
--settings file, your own hooks still fire inside a thurbox session — both run.
Hand-edits to a managed file are rewritten from the embedded payload on the next
TUI start / heartbeat tick; to customize, deactivate the extension and wire the
hook yourself, or edit the payload under extensions/hooks/ and reinstall. Full
per-agent detail: extensions/hooks/README.md.
User-defined themes, offered in the Ctrl+Y picker alongside the fifteen
built-in presets and persisted by name like any preset. Each
[[themes]] entry starts from a built-in base and overrides only the
colours it names:
[[themes]]
name = "my-mocha" # stable id; must not shadow a built-in
display_name = "My Mocha" # picker label (default: name)
base = "catppuccin-mocha" # starting palette (default: default)
accent = "#fab387"
app_bg = "reset" # keep the terminal's native backgroundColours accept anything ratatui parses: #rrggbb, ANSI names (red,
lightcyan), indexed (14), or reset. The seeded file lists every
overridable key — including the code-review diff colours diff_added /
diff_removed (added/removed line foreground) and diff_added_bg /
diff_removed_bg (the subtle full-row tint). Bad colours and built-in name
collisions degrade to startup warnings (the base colour / the built-in stays in
effect).
Maps Action names to one or more chord strings:
{ "QuitApp": ["ctrl+a"], "OpenThemePicker": ["ctrl+y", "f4"] }- Preferred editing path is the F1 panel (live capture, conflict stealing, immediate persistence). Hand-edits are read at startup.
- Chord syntax:
[ctrl+][alt+][shift+][cmd+]<key>where<key>is a letter,f1–f12, or a named key (enter,esc,tab, arrows,home,end,pageup,pagedown,backspace,delete,insert). Case-insensitive.cmd(aliasessuper,command,win) is the macOS Command key — delivered only by kitty-keyboard-protocol terminals (iTerm2 3.5+, kitty, WezTerm, Ghostty; not Terminal.app), and only for chords the emulator doesn't claim itself. - Unknown action names, invalid chords, and the same chord bound to two actions in overlapping contexts are reported at startup (the file still loads; bad entries fall back to defaults).
- Terminal passthrough. When a session terminal is focused, the
readline / shell line-editing chords (
Ctrl+Astart-of-line,Ctrl+Eend-of-line,Ctrl+Wdelete-word,Ctrl+Ukill-line,Ctrl+Rreverse-search,Ctrl+DEOF, plusCtrl+B/F/O/P/S) are forwarded to the agent CLI instead of triggering their thurbox command, so your terminal muscle memory works inside a session. Those thurbox commands stay reachable from the session list (focus it withCtrl+H) and via theirF-key alternates (F2info panel,F3file viewer,F5tasks). Rebinding such an action to a key that isn't a bareCtrl+<letter>makes it work in the terminal too. Navigation/quit chords (Ctrl+H/J/K/L,Ctrl+Q,Ctrl+N) are never forwarded — they're how you leave the terminal. - Action names and defaults: see the table in CLAUDE.md / README, or
src/session/keybindings.rs.
Each opt-in extension (see extensions/<name>/) is described by a single
extension.toml manifest. thurbox-cli extension install writes the
home-resolved copy to ~/.config/thurbox/extensions/<name>.toml (thurbox
never seeds this dir). The install home (where payload files land and the
session runs) defaults to ~/.config/thurbox/extensions/<name>/ — a sibling dir
of that manifest — unless the manifest pins a home or you pass --home. The
manifest has two halves — an install spec and a runtime spec:
name = "flow"
description = "Focus-protecting triage agent"
config_version = 1 # manifest *format* version (for migrations)
version = "1.0.0" # the extension's own version (bumped by its author)
min_thurbox_version = "0.113.0" # minimum thurbox; older binaries get a warning
# home = "~/flow" # OPTIONAL; default is <config>/extensions/<name>.
# {home} is substituted everywhere it appears
# install spec ---------------------------------------------------------------
[[agents]] # registered in agents.toml (existing kept)
name = "flow"
command = "claude"
args = ["--model", "claude-haiku-4-5"]
[[files]] # fetched from the source, written under home
path = "FLOW.md"
[[files]]
path = "scripts/create-task.sh"
executable = true # chmod +x
[[files]]
path = "repos.md"
if_absent = true # seed once; never clobbered on reinstall
[[files]]
path = ".claude/settings.json"
source = "claude-settings.json" # source path differs from dest
substitute = true # replace {home} in the content
[[symlinks]] # never clobbers a real file at `link`
link = "CLAUDE.md"
target = "FLOW.md"
# Reaching OUTSIDE the extension home (used by the built-in hooks extension):
[[external_files]] # write a file into an agent's OWN config dir
path = "~/.config/opencode/plugin/x.js"
source = "x.js"
requires_dir = "~/.config/opencode" # skip when that agent isn't installed
[[agent_patches]] # append args to an EXISTING agent (reversible)
name = "claude"
append_args = ["--settings", "{home}/claude.json"]
[[config_merges]] # reversibly deep-merge JSON into an agent's own
path = "~/.gemini/settings.json" # SHARED config file (never clobbered)
source = "antigravity-hooks.json" # objects recurse, arrays union; uninstall prunes
requires_dir = "~/.gemini" # exactly our entries (by marker). no-op write
# when unchanged; malformed target soft-skipped
# runtime spec (ensured on activate, self-healed if deleted) -----------------
[[sessions]]
name = "flow"
agent = "flow"
repo_path = "{home}" # absolute, `~`-relative, or `{home}`; resolved
# to an absolute path at install
# [[automations]] is an OPTIONAL runtime resource (flow itself ships none —
# it is purely event-driven). An extension that wants a scheduled tick declares:
[[automations]]
name = "example-tick"
trigger = "cron:*/10 * * * *" # same grammar as `automation create --trigger`
session_ref = "flow" # must match a [[sessions]] name above
prompt = "tick"Manage extensions with the CLI:
thurbox-cli extension install flow # fetch + lay files + agents + activate
thurbox-cli extension install ./extensions/flow # from a local dir
thurbox-cli extension install <url> --home ~/x # from a URL, custom home
thurbox-cli extension uninstall <name> # reverse install (keep home dir)
thurbox-cli extension uninstall <name> --purge # also delete the home dir
thurbox-cli extension list # installed + active/healthy + version/stale
thurbox-cli extension update <name> # re-fetch from recorded source (refresh)
thurbox-cli extension update --all # update every installed extension
thurbox-cli extension update <name> --force # also overwrite user-edited seed files
thurbox-cli extension activate <name> # (re)create resources + mark active
thurbox-cli extension deactivate <name> # tear down + stop self-heal
thurbox-cli extension deactivate <name> --force --purge # also kill tmux + drop manifest
thurbox-cli extension status [<name>] # per-resource presence + version/staleA bare name installs from the official source
(raw.githubusercontent.com/Thurbeen/thurbox/<ref>/extensions/<name>,
fetched via curl/wget) — <ref> is the running binary's release tag
(main for dev builds), so a fetched extension matches your binary. A
path or http(s):// URL installs from there instead. Payload paths are
validated against traversal (no absolute paths or ..), and a
substitute file you've edited isn't overwritten on reinstall (use
--force). Payload files are fetched as text (specs/scripts/JSON),
not binaries.
While an extension is active, thurbox self-heals its declared
resources: on TUI startup and on every automation tick it re-creates
any session/automation that has been deleted. So deleting them by hand is
a no-op (they come back); extension deactivate is the real off-switch.
Self-heal while the TUI is closed depends on the automation heartbeat
([features] automations = true); with automations off, healing happens
at the next TUI startup only.
Extensions carry two version markers, and the installer stamps two more into the discovery-dir copy so staleness can be detected:
| Field | Where set | Purpose |
|---|---|---|
version |
source manifest | the extension's own semver (author-bumped) |
min_thurbox_version |
source manifest | minimum thurbox; older binaries warn |
installed_with |
stamped on install | the thurbox version that installed it |
source |
stamped on install | the target it was installed from |
A bare-name install (extension install flow) fetches from the
official source pinned to the running binary's release tag, so the
extension you get always matches your thurbox. When you later upgrade
thurbox, the on-disk copy is now older than the binary — thurbox
flags it as stale (in extension list/status, and as a one-line
nudge from self-heal at startup). Run extension update <name> (or
--all) to re-fetch from the recorded source; because a bare name
re-resolves against the new binary's tag, this pulls the version that
matches your upgraded thurbox. Updates honour the same file rules as
install — user-edited substitute files and if_absent seeds are
preserved unless you pass --force.
min_thurbox_version is a soft gate: an extension authored for a
newer thurbox still installs on an older binary, but install/activate and
self-heal emit a compatibility warning so the mismatch is visible.
Dev builds (0.0.0-dev) skip both the staleness and compatibility
checks — their version doesn't order against release tags.
Rollback. There's no version snapshot store: to roll an extension
back, pin a specific thurbox tag — extension install https://raw.githubusercontent.com/Thurbeen/thurbox/v0.112.0/extensions/flow
— or downgrade the binary and run extension update, which re-resolves
the bare name to that older tag.
Live in the metadata table and apply immediately (no restart):
| Key | Set via | Purpose |
|---|---|---|
active_theme |
Ctrl+Y / F4 picker |
TUI palette (fifteen built-ins) |
editor_command |
thurbox-cli editor set "<cmd>" |
what Ctrl+O runs |
editor_mode |
thurbox-cli editor mode <auto|terminal|gui> |
how Ctrl+O runs the editor: auto (default) detects terminal vs GUI and gives terminal editors a real TTY (tmux popup / TUI suspend); terminal forces the TTY path; gui forces detached |
active_extensions |
thurbox-cli extension activate/deactivate |
JSON array of active extensions to self-heal |
builtin_hooks_optout |
thurbox-cli extension deactivate hooks |
1 when the user opted out of the auto-activated hooks extension |
perf_snapshot |
the TUI, while perf timing is active (THURBOX_PERF_LOG or an open perf HUD) |
JSON perf snapshot read by thurbox-cli perf (see docs/PERFORMANCE.md) |
These are in the DB rather than a file because they are written
concurrently by multiple thurbox processes (TUI, CLI, MCP) and picked
up live via PRAGMA data_version polling.
User-set (read by thurbox):
| Variable | Used for |
|---|---|
XDG_CONFIG_HOME, XDG_DATA_HOME |
config/data roots |
VISUAL, then EDITOR |
Ctrl+O editor when editor_command is unset |
SHELL |
the Ctrl+T companion shell pane (fallback /bin/sh). For a remote/WSL session the pane uses the host's $SHELL as an interactive login shell (the SSH-login environment), not the local one. |
RUST_LOG |
log filter for thurbox.log |
THURBOX_PERF_LOG |
opt-in performance logging: a one-shot startup phase breakdown at first paint, per-session restore_adopt/adopt_split lines, steady-state perf_window lines (~10 s cadence), and wall-clock frame/tick timing collection. Any value enables it. See docs/PERFORMANCE.md. |
THURBOX_SOCKET |
overrides the local multiplexer socket name (default thurbox; dev builds thurbox-dev). For test/sandbox tooling: Unix scoping uses TMUX_TMPDIR, but psmux (Windows) resolves every -L <name> machine-wide, so this is the only way to fully scope an instance there. Remote hosts are unaffected (socket from hosts.toml). Empty = unset. |
Set by thurbox into every spawned agent process (not user-set;
session_ops::inject_thurbox_env / App::build_spawn_inputs). An
agent — or a thurbox-cli call running inside the session — reads
these to prove its own identity without scraping panes or names:
| Variable | Set into agent process |
|---|---|
THURBOX_SESSION |
the stable thurbox SessionId (the registry key); read back by thurbox-cli message/inbox for self-identity |
THURBOX_SESSION_ID |
the agent's own conversation id (agent_session_id); consumed by the metrics statusline. Distinct from THURBOX_SESSION |
THURBOX_TASK |
the originating task id; task-spawned sessions only (headless task run) |
THURBOX_METRICS_DIR |
metrics output dir |
THURBOX_CONFIG_DIR / THURBOX_DATA_DIR |
the resolved config/data dirs, so the agent's thurbox-cli (its status hook) targets the same DB the TUI reads — independent of XDG, which thurbox-cli is on PATH, or a stale tmux-server env. Also honored if you set them yourself to relocate thurbox's state. |
Set at build time (not runtime):
| Variable | Used for |
|---|---|
THURBOX_RELEASE_VERSION |
read by build.rs to inject the binary version at build (CI release workflow sets it, e.g. v1.0.0); absent → falls back to CARGO_PKG_VERSION |
Editor resolution order: DB editor_command → $VISUAL → $EDITOR →
error toast.
The SQLite schema migrates automatically (schema_version in
metadata). The TOML files carry a config_version = 1 marker so a
future format change can migrate them too; current files are version 1
and the field is optional.