Using Fut

TL;DR: Run fut in a project. Most interactive actions start with Ctrl-b; pause after the prefix to see every binding, or press Ctrl-b : to search the command palette. Detach with Ctrl-b d and return with fut.

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:

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