Using Fut
TL;DR: Run
futin a project. Most interactive actions start withCtrl-b; pause after the prefix to see every binding, or pressCtrl-b :to search the command palette. Detach withCtrl-b dand return withfut.
Start and return
cd ~/code/my-project
fut
Bare fut starts the daemon when needed, opens the current directory, and
attaches a client. The daemon keeps sessions and terminal processes alive after
you detach. Detached terminals keep consuming and parsing their output, but Fut
defers screen rendering until a client observes them again. Runtime state is not
restored after the daemon exits or the machine restarts.
fut daemon shutdown gracefully closes every session. If an upgrade leaves an
older, protocol-incompatible daemon running, Fut first asks that daemon’s own
executable to shut it down. As a final recovery path,
fut daemon shutdown --force verifies the Unix socket belongs to your user and
terminates its owning process without speaking the incompatible protocol.
Run fut from another directory to open it in the existing daemon. Fut groups
resources like this:
session a project-level container
└─ workspace a user-defined context, often a checkout or worktree
└─ tab a named or process-titled terminal layout
└─ pane a placement of one running terminal
Use fut attach to open the global navigator before attaching, or target an
unambiguous resource directly with fut session attach, fut workspace
attach, fut tab attach, fut pane attach, or fut terminal attach.
Local clients negotiate a stable protocol generation and capabilities, so
compatible Fut releases can share a daemon without restarting its terminals.
Daemons predating this handshake need one explicit shutdown and restart.
For temporary legacy access, fut attach
--ignore-protocol-mismatch retries using the daemon’s protocol. This is an
unsafe compatibility escape hatch; it does not override negotiated generation
or capability failures, and unsupported legacy changes may still fail.
Fut refuses to start a nested interactive client inside one of its terminals.
If nesting is intentional, run FUT_ALLOW_NESTED=1 fut.
Attach to a remote machine
There are two distinct SSH workflows:
ssh -t workbox futruns both Fut’s client and daemon onworkbox; the SSH session owns terminal integration and clipboard availability.fut --machine workboxruns the interface, theme, input handling, and clipboard locally while the daemon and PTYs remain onworkbox.
Run Fut’s interface locally while its daemon and terminals remain on an SSH host:
fut --machine workbox
workbox can be a saved machine label or ID, or a host or alias accepted by
OpenSSH. Saved machines use their configured SSH target; explicit attachment
also works for disabled profiles. --remote remains an alias for --machine.
Fut uses the normal SSH configuration, authentication, host keys, and jump
hosts, then opens the global
navigator for the remote daemon. fut --machine workbox attach is equivalent.
The local process renders the interface and uses the local theme, keybindings,
and clipboard; SSH carries the Fut protocol to the remote daemon without
exposing a network listener.
Open and attach directly to a project from the remote machine’s catalog:
fut p 10er --machine workbox
To attach without starting a missing daemon, run
fut --machine workbox --attach-only. To make that your default, set
autostart = false in the local config.toml:
[remote]
autostart = false
--attach-only applies to this invocation even if autostart is enabled in
config. It does not change the remote host’s configuration.
Fut must already be installed on the remote host. If its daemon is not running,
fut --machine workbox starts it there by default, using that host’s normal
socket, configuration and working directory. The client and daemon may use
different Fut versions when they support the same remote protocol generation,
codec, and required capabilities. Older releases without the remote handshake
(or the remote bridge startup option) need upgrading before remote attachment
can work.
Remote attachment never stops, upgrades, or replaces an existing daemon, even if
it is incompatible. Saved-machine background monitoring and fut doctor remain
attach-only and never start remote daemons. The interactive project picker,
configuration reload, and client lifecycle hooks remain unavailable because
they assume the client and daemon share a filesystem. Configured commands and
extension command actions run on the attached machine when its daemon supports
remote commands. Interactive popups, form defaults, extension configuration,
working directories, and Fut CLI context use that machine; rendering and
clipboard access stay local. Commands require their tools to be installed on
the remote machine; ~/ in a command’s program path resolves to the remote
user’s home directory. Older daemons report that commands are unavailable instead
of running them locally. Remote terminal links are limited to HTTP and HTTPS. Ordinary
terminal input, navigation, layout actions, copy mode, and pane links continue
to work.
Fut supplies SSH keepalive defaults of ServerAliveInterval 30 and
ServerAliveCountMax 6 through a private temporary config file. Your user and
system SSH settings take precedence, including an explicit interval of zero.
fut doctor checks the effective settings for enabled saved machines and
suggests settings when keepalives are disabled.
An interrupted active remote attachment automatically reconnects to the same terminal, without reopening its project or replaying commands. The reconnect screen pauses input; press Esc or Ctrl+C to detach. Retries back off from one second to two minutes and reset after a minute of stable connectivity. A fresh screen and resource list are required before input resumes. Quiet connections are probed after 30 seconds, with a 15-second response deadline. Older peers without health support use resource queries instead.
Reconnect attempts never start a daemon or prompt for credentials. If SSH needs
attention, verify access with ssh HOST in another terminal (or use ssh-add),
then attach again. Incompatible peers or a terminal that no longer exists fail
instead of reopening it. Remote panes keep running when the SSH connection drops.
The navigator requires metadata support, and attachment additionally requires interactive terminal support. Bell alerts, extension catalogs, and health checks are optional: a peer without one of these disables only that feature. A missing extension catalog also disables its presentation styles and command listings; local theme and ordinary keybindings still work. An incompatible or malformed endpoint fails without restarting either daemon or retrying the private local protocol. Local connections use a separate local contract, without exposing local-only methods to remote peers. See the remote protocol contract.
Remote Fut currently requires macOS or Linux on both ends. Windows clients and Windows remote daemons are unsupported because the bridge connects to Fut’s private Unix socket. Fut opens outbound OpenSSH processes only: no Fut daemon listens on TCP and there is no central Fut server.
Saved machines
Keep a catalog of the SSH machines you attach to:
fut machine add workbox
fut machine add deploy@10.0.0.7 --label prod
fut machine list
fut machine show prod
fut machine rename prod production
fut machine disable production
fut machine enable production
fut machine remove production
fut machine add connects through SSH, confirms that a compatible Fut daemon
is already running there, and only then saves the profile. A failed or
cancelled check saves nothing and never changes the terminal. The other
commands edit the catalog locally and never contact SSH.
A profile holds only a stable ID, a unique label, the SSH target, and whether
the machine is enabled. Several profiles may point at the same target.
Passwords, keys, agent sockets, and control sockets stay with OpenSSH, and
targets that embed a password or use URI syntax are rejected. The catalog is
the private file $XDG_STATE_HOME/fut/machines.toml
(~/.local/state/fut/machines.toml by default). Disabling or removing a
profile only affects the catalog: the remote daemon and its panes keep running.
Open the navigator (Ctrl-b s) in a local client to browse Local and every
saved machine together. Fut adds the machine level only when a saved profile
exists, so the one-machine hierarchy and keybindings remain unchanged. Online
resources can be selected directly; Fut freezes input while it prepares the
new attachment, validates a fresh whole-tab view, applies geometry, and then
atomically changes ownership. Tabs never combine panes from different machines.
Agent search and notifications include machine labels when federation is
configured. Cached resources from reconnecting or disabled machines remain
visible as stale summaries but cannot be selected, acknowledged, resized, or
sent commands.
While a local Fut interface is open, it independently watches Local and every
enabled saved machine for resource, presence, agent, alert, and extension
metadata. These background connections never request terminal screens or start,
stop, or upgrade a daemon. Background SSH is non-interactive and requires an
already trusted host key and non-prompting authentication (normally an
ssh-agent, hardware agent, or an OpenSSH configuration that needs no prompt).
Connect with ordinary ssh first to review a new host key; Fut never accepts or
repairs host keys and stores no credentials. Transient disconnects
retain clearly stale metadata and retry with a capped delay; host-key,
authentication, installation, and protocol problems wait for interactive repair.
Changes made with fut machine enable, disable, rename, or remove are
picked up by an open interface without changing its active machine.
The catalog accepts at most 256 profiles. Retry delays grow from one to at most
30 seconds independently per endpoint, so one failed machine neither blocks
another machine nor steals focus when it reconnects.
For Git repositories, Fut groups linked worktrees from the same repository as peer workspaces in one session. Ordinary directories get their own session. Opening the same location again reuses it; bare Git repositories are not valid workspaces.
Everyday controls
These default bindings follow the Ctrl-b prefix, configurable as ui.prefix.
The configuration name is the key to override under [ui.bindings], or to
assign directly without a prefix under [ui.hotkeys].
| Key | Action | Configuration name |
|---|---|---|
: |
Search every command and configured extension action | open_command_bar |
m |
Show messages from the current client session | open_messages |
Shift-S |
Open a configured project or path | open_project |
R |
Reload global and focused-project configuration | reload_config |
[ |
Enter copy mode | enter_copy_mode |
s |
Search sessions, workspaces, tabs, and panes | open_navigator |
a |
Search agents and see their live status | open_agents |
w |
Open the left sidebar | open_left_sidebar |
] |
Open the right sidebar | open_right_sidebar |
t |
Focus the tab bar | open_tab_bar |
u |
List agent notifications and terminal alerts | open_notifications |
Prefix |
Jump to the next notification or terminal alert | focus_next_notification |
C |
Create a workspace | create_workspace |
c |
Create a tab | create_tab |
n |
Switch to the next tab | focus_next_tab |
p |
Switch to the previous tab | focus_previous_tab |
Down |
Switch to the next workspace | focus_next_workspace |
Up |
Switch to the previous workspace | focus_previous_workspace |
| |
Split the pane right | split_pane_right |
_ |
Split the pane down | split_pane_down |
o |
Focus the next pane | focus_next_pane |
; |
Focus the previous pane | focus_previous_pane |
h / j / k / l |
Focus the pane left / down / up / right | focus_pane_left / focus_pane_down / focus_pane_up / focus_pane_right |
P |
Switch to the last active pane | focus_last_pane |
Ctrl-t |
Switch to the last active tab | focus_last_tab |
Ctrl-w |
Switch to the last active workspace | focus_last_workspace |
Ctrl-s |
Switch to the last active session | focus_last_session |
1–9, 0 |
Select tab 1–10 | focus_tab_1 through focus_tab_10 |
z |
Toggle pane zoom | toggle_pane_zoom |
x |
Close the focused pane | close_pane |
d |
Detach | detach |
Pause for 700 ms after Ctrl-b to see the complete, current binding list.
Bindings can be changed in Configuration.
The command palette also provides unbound rename-session, rename-workspace,
rename-tab, kill-session, kill-workspace, and kill-tab actions for the
focused resource. Session actions target the session to which the client is
attached; closing it ends that attachment along with its terminals.
Press Ctrl-b Shift-S to open a project without leaving the client. Fut fuzzy
filters only the explicit [projects] catalog and never scans for repositories.
Typing also adds an Open path row for the exact value, resolved relative to
the focused workspace; obvious paths and queries without a project match select
that row automatically. Opening a live project navigates to its existing
terminal. A new project applies its recipe and focuses the terminal selected by
that recipe.
If a repository recipe is not yet trusted, Fut shows its exact contents for
review and accepts or declines machine-local approval in the same dialog.
The tab bar and workspace rows also support the mouse: left-click to switch or activate a clickable extension token, and right-click for create, rename, close, and sidebar display actions. Token clicks include their visible affixes and pills and take precedence over switching the surrounding tab or expanded workspace row. Click a toast to open the complete message log for the current client session. Drag pane or sidebar dividers to resize them. Fut preserves application mouse reporting; when an application does not claim the mouse, the wheel scrolls client-local history and dragging selects text. Hold Shift to force Fut selection.
Copy and scrollback
Press Ctrl-b [ for copy mode. Move with arrows, hjkl, Home/End, or page
keys. Space starts or clears a selection; y or Enter copies it; Escape or q
cancels. / searches literal text and n/N repeats the search.
Clipboard writes use pbcopy on macOS. On Linux, Fut automatically uses
wl-copy in Wayland sessions or xclip/xsel in X11 sessions. Install
wl-clipboard, xclip, or xsel if your desktop does not provide one. In an
SSH or headless session without access to a graphical clipboard, Fut reports
that the clipboard is unavailable. A failed copy keeps the selection active
for another attempt.
Paste a clipboard image
Press Ctrl-b Ctrl-v, or search for Paste clipboard image in the command
palette. Fut reads an image from your local clipboard, uploads it to the daemon
hosting the terminal, and inserts a quoted PNG file path without pressing Enter.
The target is captured when you invoke the action, so switching panes does not
redirect the paste. Ordinary text paste is unchanged.
This works locally and with fut --machine workbox; the remote daemon must
support image attachments. Running ssh workbox and then starting Fut there
does not give Fut access to your laptop’s clipboard.
macOS uses the system clipboard, including TIFF screenshot conversion. On Linux,
install wl-clipboard for Wayland or xclip for X11; the clipboard must offer
PNG data. Images are limited to 20 MiB. Uploaded files are private and retained
until the daemon exits, with a 256 MiB shared storage budget. Incomplete uploads
are discarded on disconnect, and transfers are never replayed after reconnect.
Drop an image into a remote terminal
When your terminal delivers a file drop as a bracketed paste, Fut recognizes a single absolute path to a local PNG, uploads the image, and inserts its remote path without pressing Enter. Shell-quoted and backslash-escaped paths, including filenames with spaces, are supported. Pasting an image path manually behaves the same way: terminals do not provide a separate file-drop event to Fut.
This only applies to ordinary terminal input on remote connections that support attachments. Local connections, other paste text, missing files, and files that are not PNGs retain normal paste behavior. Multiple files, JPEGs, and terminals that deliver drops as ordinary keystrokes are not supported yet. The same image size and storage limits apply as for clipboard image paste.
To disable automatic image-path uploads, add this to your local configuration and reattach:
[remote]
upload_image_paths = false # Default: true.
Workspaces, tabs, and panes
Workspaces are organizational contexts, not Git objects. Fut uses their root as a working-directory fallback but does not create or manage worktrees. Unnamed workspaces show their live directory or Git work tree; unnamed tabs follow the oldest surviving pane’s foreground process. Focusing a pane does not change the shared tab label. Rename either to keep a fixed label, or submit an empty name to restore the automatic label.
fut open --parent-workspace WORKSPACE_ID PATH explicitly nests a newly
created workspace beneath another workspace in the same session. Nesting is
display and provenance metadata: the navigator, workspace sidebar, fut list,
and next/previous workspace traversal use depth-first tree order, while JSON
snapshots expose parent_workspace_id. Closing a parent never closes its
children; when it disappears, its direct children inherit its parent and keep
their own descendants. Children become top-level only when their removed parent
was top-level. Reopening an existing location with this creation-only option is
rejected rather than silently changing its parent.
Pane splits and divider sizes are shared daemon state. Client focus, zoom,
scrollback, dialogs, and configuration are local. Multiple clients may attach
to the same session. Concurrent divider drags use the last ratio accepted by
the daemon, and every client reconciles to that shared ratio. If another client
changes the pane topology during a drag, Fut cancels the stale drag and shows
Layout changed in another client. Attached clients also share terminal input
and output, and shared PTYs use the smallest attached client’s dimensions.
A terminal may be up to 65,535 columns or rows and 1,048,576 cells in all, enough for an 8K display at a 4x8-pixel font (1920x540). The cell bound is a memory bound: one copy of the largest grid is 40 MiB. Remote machines running Fut 0.37 or earlier keep their own limit of 150,000 cells (50,000 before 0.35).
Terminal alerts
Fut reports real BEL characters as terminal-native attention without interpreting ordinary output as agent completion or blocking. An OSC string terminator is not a bell, and repeated bells are represented by a bounded counter rather than an event log.
Bells roll up to tabs, workspace rows, sessions, and navigator results. Press
Ctrl-b u to inspect them, Enter to switch to the pane, or c to acknowledge
the selected bell or agent notification without changing agent lifecycle state.
Ctrl-b Ctrl-b moves to the next alert using typed pane navigation, considering
the current terminal last; when it is the only waiting terminal, Fut reveals and
acknowledges it or confirms that its already-visible attention was cleared.
Rendering the focused pane acknowledges its current bell for this client only;
another attached client keeps its own seen state. A new outer terminal starts at
the current bell baseline. Agent lifecycle state remains separate.
See Configuration for the opt-in outer-terminal BEL.
Fut renders standard terminal mouse modes, indexed and RGB color, OSC 8
hyperlinks, cursor shapes, bracketed paste, alternate screens, application
cursor keys, mode-aware modified keys, and Kitty graphics used by terminals
such as Ghostty, Kitty, and WezTerm. When attached directly from Ghostty,
Kitty, WezTerm, foot, or Alacritty, Fut negotiates the Kitty keyboard protocol
with the outer terminal. This keeps keys such as Ctrl-I distinct from Tab,
preserves modified navigation keys, and lets inner applications request
modifyOtherKeys or Kitty press/repeat/release reporting. Plain text stays on the
traditional UTF-8 path so input generated by accessibility tools and input
methods retains its associated text. Other outer terminals, including terminal
chains that do not advertise this capability, stay on the traditional input
path: text, Unicode, Ctrl/Alt chords, navigation keys, and F1–F12 remain
compatible, but ambiguities already present in legacy terminal input cannot be
recovered.
CLI and automation
The CLI is noun-first. Ask the installed version for exact arguments:
fut --help
fut pane --help
fut pane split --help
Common control commands include:
fut open ../api --name api -- zsh
fut open -b ../api
fut tab new --name tests -- mise run test
fut pane split right --cwd ../api -- zsh
fut pane move PANE_ID DESTINATION_TAB_ID
fut list
fut list --verbose
fut events
fut extension list
fut extension show EXTENSION_ID
fut extension validate PATH
fut extension install PATH
fut extension install-git URL --rev COMMIT [--sha256 DIGEST]
fut extension update EXTENSION_ID --rev COMMIT [--sha256 DIGEST]
fut extension enable EXTENSION_ID
fut extension disable EXTENSION_ID
fut extension remove EXTENSION_ID
fut extension reload
fut machine add TARGET [--label LABEL]
fut machine list
fut machine show MACHINE
fut machine rename MACHINE LABEL
fut machine enable MACHINE
fut machine disable MACHINE
fut machine remove MACHINE
Commands after -- are passed directly, without shell evaluation. Creation
and mutation commands do not change another client’s visual focus. Inside Fut,
many resource IDs may be omitted and are resolved from the caller’s live
terminal ancestry. Human-facing output and shell completion use 23-character
compact Fut IDs. They encode the complete 128-bit identity, so they are stable
and do not depend on the current set of resources. Every ID argument also
accepts the canonical UUID form. Automation should use --json, retain the
returned canonical UUIDs, and pass explicit IDs to later commands.
For terminal I/O, lifecycle-aware agent control, and event streaming, see
Agent activity. Fut also bundles machine-readable operating
instructions through fut agent skill.
Enable dynamic shell completion in your startup file:
# zsh
source <(COMPLETE=zsh fut)
# bash
source <(COMPLETE=bash fut)
# fish
COMPLETE=fish fut | source
Re-source completion after upgrading. Resource completion is bounded and read-only; it never starts a daemon. Active extension-ID completion uses the same bounded, read-only daemon catalog lookup. Package validation itself is daemonless and never executes extension code.
Next steps
- Agent activity — integrations, notifications, and automation
- Configuration — bindings, layout, sidebars, and styles
- Extensions — trusted local commands, hooks, and tokens
- Diagnostics — check configuration and terminal compatibility