[{"href":"/installation","page":"Installation","heading":"","text":"**At the current stage of beta testing, the macOS application and the Linux and FreeBSD server are available to all testers. The Windows server is available to testers with the Windows role in Discord.** We have Rex clients for iOS, Linux, and Windows at varying stages of readiness. We hope to expand the scope of public testing quickly, but they aren't part of testing yet. The macOS client is fully self-contained with a local server that lets you get up and running quickly. We plan on adding support for more platforms for the client in the future. Linux Server only See Installing on Linux for the details. FreeBSD Server only See Installing on FreeBSD for the details. Windows Server only See Installing on Windows for the details. {#if windowsMethods} Adding the Rex WinGet source needs administrator rights. {/if}"},{"href":"/customize/config","page":"Configuration File","heading":"","text":"Rex configuration is written in Lua and lives in a single file: init.lua. It binds keys, defines custom actions, declares the other servers you connect to, and more. It can load other Lua files (via require) so you can split your configuration up as you'd like. Rex doesn't require any configuration, so start using Rex without it and start a configuration file when there is something you want to change."},{"href":"/customize/config#file-location","page":"Configuration File","heading":"File Location","text":"On macOS and Linux, the file is at $XDG_CONFIG_HOME/rex/init.lua when that environment variable is set and ~/.config/rex/init.lua when it isn't. On Windows, the file is at %AppData%\\rex\\init.lua. Set REX_CONFIG to the path of another file to use that one instead. This environment variable must be set in the environment of the Rex server process, since the server is what reads the file."},{"href":"/customize/config#quick-example","page":"Configuration File","heading":"Quick Example","text":"Here is a minimal configuration example with one keybinding: Reload the configuration with the CLI. Connected clients will be notified of the new configuration and it will take effect immediately. Rex does not currently watch for file changes; you must use the CLI. The configuration file is read by the Rex server, so it lives on the same machine as the server. rex config reload must also be run on that machine; it is refused over a remote connection. Press Cmd+D and the focused pane splits to the right. The Lua API reference lists all available Lua APIs."},{"href":"/customize/config#validation","page":"Configuration File","heading":"Validation","text":"rex config check loads the file without changing anything and reports any issues. If there are no issues, it reports the changes it found: Both commands print the file's mistakes and exit with status 1 if any of them is an error. Give rex config check a path to check a file other than your own, such as a draft."},{"href":"/customize/config#validation-errors","page":"Configuration File","heading":"Validation Errors","text":"A bad call to a rex function is skipped, and the rest of the file still loads. Here line 2 passes a table where a key belongs: The other two bindings still work. A mistake Lua itself can't get past, such as a syntax error, fails the whole load. When that happens on a reload, the server keeps the bindings and actions from the last load that worked. Some mistakes can't be seen from the file alone. Whether pane.split is a real action depends on the client, because clients provide those actions. rex keymap asks the running server for the merged result and reports these."},{"href":"/customize/config#splitting-configuration","page":"Configuration File","heading":"Splitting Configuration","text":"require loads other Lua files stored beside it: Mistakes are reported with the name of the file they are in, such as keys.lua:2."},{"href":"/customize/config#execution-model","page":"Configuration File","heading":"Execution Model","text":"The file runs from top to bottom each time it loads. This load-time execution is for defining bindings, actions, etc. The callbacks for these are deferred until they're invoked. Rex APIs such as listing sessions or creating a new window do not work at load-time, since API calls are made on behalf of a client and load-time has no client. They're reported as a warning and the rest of the file still loads. Example: This defines the scratch action, but it doesn't invoke the callback with rex.session.new_window until the action is actually invoked via the command palette, a keybinding, etc. To run Lua once from top to bottom against a live session, write a separate script and run it with rex do instead."},{"href":"/customize/keyboard-shortcuts","page":"Keyboard Shortcuts","heading":"","text":"Keyboard shortcuts are defined in the configuration file with rex.bind and removed with rex.unbind. A shortcut binds a key to an action, such as splitting a pane or switching sessions. Run rex config reload after changing the file for the shortcuts to take effect."},{"href":"/customize/keyboard-shortcuts#binding-a-key","page":"Keyboard Shortcuts","heading":"Binding a Key","text":"rex.bind takes a key, the name of an action, and optionally a table of arguments for that action: Some actions require arguments and some have optional arguments. For example, pane.resize requires a direction and accepts an optional amount: Binding a key that already has a binding replaces it. The newest binding for a key always wins."},{"href":"/customize/keyboard-shortcuts#finding-actions","page":"Keyboard Shortcuts","heading":"Finding Actions","text":"Actions are provided by the client, so the list depends on which client you use. List the actions of a connected client with the CLI: The ARGS column lists the arguments each action takes. An argument followed by ? is optional. You can also define your own actions in Lua and bind keys to them. See Custom Actions."},{"href":"/customize/keyboard-shortcuts#writing-keys","page":"Keyboard Shortcuts","heading":"Writing Keys","text":"A key is written as zero or more modifiers followed by one key, joined by +. Case and the order of modifiers don't matter, so shift+cmd+d and Cmd+Shift+D are the same key. Modifier Also Written cmd command, super, meta, win ctrl control alt opt, option shift The key is a character such as d, 1, or , or a name such as enter, escape, tab, space, backspace, delete, left, right, up, down, home, end, pageup, pagedown, or f1 through f12. Keys follow your keyboard layout: ctrl+a is whichever key types \"a\". To bind a physical key regardless of layout, write its US-layout name in brackets, such as ctrl+[KeyA]. The names are the W3C KeyboardEvent.code values, such as [Digit1], [Slash], and [ArrowLeft]. Write the key without shift applied. On a US layout, plus is cmd+shift+=, not cmd++. Bindings are always matched on unshifted keys. A shortcut can also be several keys pressed one after another, such as ctrl+b>c. See Key Sequences. Shortcuts can also be grouped into a mode that is only active when you enter it, such as a mode for resizing panes. See Key Modes."},{"href":"/customize/keyboard-shortcuts#removing-a-shortcut","page":"Keyboard Shortcuts","heading":"Removing a Shortcut","text":"rex.unbind removes every binding for a key. It can be used to remove a binding you set as well as unbind a key that the client defaults with: Pass an action name to remove the key only when it is bound to that action: An unbound key isn't handled by Rex at all, so it is sent to the focused terminal. To change what a default shortcut does, bind it. There is no need to unbind it first:"},{"href":"/customize/keyboard-shortcuts#how-shortcuts-are-resolved","page":"Keyboard Shortcuts","heading":"How Shortcuts Are Resolved","text":"A key can get its meaning from three places. From lowest to highest priority: The default shortcuts the client ships with. Your configuration file. Shortcuts you set in the client itself, such as with the app's Change Shortcut... command. A shortcut set in the app therefore wins over the configuration file. If a binding in your file appears to do nothing, check that the app doesn't have its own shortcut for the same key."},{"href":"/customize/keyboard-shortcuts#binding-a-lua-function","page":"Keyboard Shortcuts","heading":"Binding a Lua Function","text":"A key can run a Lua function instead of an action. Return true to say the key was handled: The function can call any Rex API, such as rex.session.new_window above. The Lua API reference has the full list. If the function returns false or nothing, Rex tries the next-newest function bound to the same key, and sends the key to the focused terminal if none of them handle it. A function binding has no name, so it doesn't appear in the command palette and can't be run from the CLI. For anything you'd want to do more than one way, define a custom action and bind the key to that."},{"href":"/customize/keyboard-shortcuts#inspecting-bindings","page":"Keyboard Shortcuts","heading":"Inspecting Bindings","text":"A configuration that loads other files with require can look at what they bound and remove what it doesn't want. rex.bindings() returns every binding made so far, in the order they were made. Each binding is a table with key, mode, action, args, and source (the file and line that made it). Passing one to rex.unbind removes exactly that binding. rex.bindings also accepts a filter on key, mode, or action, such as rex.bindings{ action = \"pane.split\" }. rex.modes() does the same for key modes. These functions only see your configuration file and the files it loads. They can't see the client's default shortcuts or the ones set in the app."},{"href":"/customize/keyboard-shortcuts#debugging","page":"Keyboard Shortcuts","heading":"Debugging","text":"rex config check reports mistakes in the file itself, such as a key that can't be parsed: rex keymap asks the running server what it loaded and lists each binding with the line that made it: A misspelled action name is not an error in rex config check, because the file alone can't know which actions a client has. The client reports it as a warning when it loads the keymap."},{"href":"/customize/key-sequences","page":"Key Sequences","heading":"","text":"A key sequence is two or more keys pressed one after another, such as Ctrl+B and then C. Sequences are bound with rex.bind like any other keyboard shortcut, with the keys joined by >: Each key in a sequence is written the same way as a single key, modifiers included. A sequence can be longer than two keys: Press Escape partway through a sequence to cancel it."},{"href":"/customize/key-sequences#example-tmux-bindings","page":"Key Sequences","heading":"Example: Tmux Bindings","text":"Note the last binding. Once Ctrl+B starts a sequence it no longer reaches the terminal by itself, so programs that use it need another way to receive it."},{"href":"/customize/key-sequences#binding-vs-sequence-conflict","page":"Key Sequences","heading":"Binding vs. Sequence Conflict","text":"A key either runs an action immediately or starts a sequence. It can't do both, because Rex would have to wait to find out which you meant. When a key is bound both ways, the newer binding wins and the older one never runs: With the two lines swapped, Cmd+K zooms immediately and the sequence can never start. The same rule applies across the three places shortcuts come from. A sequence in your configuration file hides the client's default shortcut for its first key, and a shortcut set in the app hides a sequence in the file that starts with the same key. Rex doesn't reorder anything to avoid this. Instead, the client is given a warning that names both bindings and where each was written."},{"href":"/customize/key-sequences#removing-a-sequence","page":"Key Sequences","heading":"Removing a Sequence","text":"rex.unbind removes exactly one binding: Unbinding a key never removes the sequences that start with it. Each sequence has to be unbound by its full name. A future change to Rex will allow removing all sequences by prefix."},{"href":"/customize/key-sequences#binding-a-lua-function","page":"Key Sequences","heading":"Binding a Lua Function","text":"A sequence can run a Lua function. The function is called when the last key of the sequence is pressed: ev.keybinding is the last key and ev.prefix is a list of the keys before it. One function bound to several sequences can use these to tell which one was pressed. A function bound to a single key has no ev.prefix. If the function returns false or nothing, only the last key is sent to the focused terminal. The earlier keys were already consumed by the client."},{"href":"/customize/key-modes","page":"Key Modes","heading":"","text":"A key mode is a named set of keyboard shortcuts that only apply while the mode is active. Modes let ordinary keys do something else for a while, such as making the arrow keys resize panes. They are also called \"key tables\" in other software. Press Cmd+R to enter the mode. The arrow keys now resize the focused pane. Press Escape to leave the mode, and the arrow keys go back to what they did before."},{"href":"/customize/key-modes#binding-a-key-in-a-mode","page":"Key Modes","heading":"Binding a Key in a Mode","text":"To bind a key in a mode, write the mode's name and a slash / before the key: A mode exists as soon as a key is bound in it. A mode name may contain only lowercase letters, digits, underscores, and hyphens. The key after the slash can be anything a shortcut outside a mode can be, including a key sequence such as resize/g>g. Bindings in a mode are separate from bindings outside it. Binding resize/left doesn't change what Left does outside the mode. A binding in a mode must name an action. To run Lua from a mode, define a custom action and bind that."},{"href":"/customize/key-modes#entering-and-leaving-a-mode","page":"Key Modes","heading":"Entering and Leaving a Mode","text":"The client provides three actions for moving between modes. Bind them like any other action: Action Description client.mode.enter Enters the mode given by name. client.mode.exit Leaves the current mode. client.mode.exit_all Leaves every mode. Escape also leaves the current mode, unless the mode binds Escape to something else. A mode stays active until you leave it. To leave automatically after one shortcut, enter it with once: Modes can be entered from inside other modes. The most recently entered mode is checked first, and client.mode.exit leaves only that one."},{"href":"/customize/key-modes#unbound-keys-in-a-mode","page":"Key Modes","heading":"Unbound Keys in a Mode","text":"By default, a key the mode doesn't bind does what it does outside the mode. In the resize example, you can still type in the terminal while the mode is active. Declare the mode as exclusive to ignore those keys instead: While an exclusive mode is active, only its own bindings do anything. This avoids typing into a terminal by accident while you think you're resizing. rex.mode is only needed to set options. exclusive is the only option."},{"href":"/customize/key-modes#removing-bindings","page":"Key Modes","heading":"Removing Bindings","text":"rex.unbind with a mode and a key removes that key's bindings in the mode and blocks the key there. A blocked key does nothing while the mode is active, even if it is bound outside the mode: Pass an action name to remove one binding without blocking the key: To remove a whole mode, including its bindings, blocked keys, and options, pass a table: rex.unbind(\"left\") never touches a mode, and rex.unbind(\"resize/left\") never touches Left outside the mode."},{"href":"/customize/key-modes#example-resize-mode","page":"Key Modes","heading":"Example: Resize Mode","text":"A complete resize mode, entered with Cmd+R:"},{"href":"/customize/key-modes#debugging","page":"Key Modes","heading":"Debugging","text":"rex keymap lists every mode the server loaded after its bindings, along with whether it is exclusive and which keys it blocks:"},{"href":"/customize/actions","page":"Custom Actions","heading":"","text":"A custom action is a named Lua function defined in the configuration file with rex.action. Once defined, it works like any built-in action: it can be bound to a key, it appears in the command palette, and it can be run from the CLI. Run rex config reload after changing the file for the action to become available. Then press Cmd+Shift+N, search for \"Open Scratch Window\" in the command palette, or run it from the CLI:"},{"href":"/customize/actions#defining-an-action","page":"Custom Actions","heading":"Defining an Action","text":"rex.action takes a table. name, title, and run are required. Field Description name How the action is referred to in rex.bind and on the CLI. Lowercase letters, digits, and underscores only. title The text shown in the command palette and in menus. run The function to call when the action is run. description A longer explanation for the command palette. category A group for the command palette. Defaults to Actions. keywords Extra words the command palette search should match. args The arguments the action takes. See Arguments. repeats Set to true to run the action again while its key is held down. A custom action's name can't contain a dot. Dotted names such as pane.split belong to clients and blocks. All custom actions have no dot. This is a small visual language that makes it easier to tell where an action comes from. Defining the same name twice is reported as a warning and the later definition wins."},{"href":"/customize/actions#arguments","page":"Custom Actions","heading":"Arguments","text":"args declares each argument an action takes and its type. A trailing ? marks an argument as optional: The types are string, number, integer, boolean, object, array, and any. Declaring arguments lets Rex catch mistakes before the action runs. A binding that passes a misspelled or wrongly-typed argument is reported as a warning when the client loads its keymap, and the CLI rejects it outright: An action that doesn't declare args accepts anything. For more control, args can instead be a JSON Schema written as a Lua table. A table with a type key is treated as a schema and used exactly as written."},{"href":"/customize/actions#the-run-function","page":"Custom Actions","heading":"The Run Function","text":"run is called with two tables: ctx describes where the action was run and args holds its arguments. args is always a table, even when no arguments were passed. Field Description ctx.session_id The session the action applies to. ctx.block_id The block the action applies to, usually the focused one. ctx.client_id The client that asked for the action. ctx.origin How the action was run: key, palette, cli, api, or script. A field is absent when Rex doesn't know its value. Rex API calls inside run default to the action's own session, so the examples on this page never pass a session_id. A call that acts on one block, such as rex.session.set_block_label, still needs a block_id. Pass ctx.block_id for the focused one. The Lua API reference has the full list of calls."},{"href":"/customize/actions#errors","page":"Custom Actions","heading":"Errors","text":"A failed API call doesn't stop the function. It returns nil and an error message, so check the second return value when the result matters: Raising an error with error fails the action and reports the message to whoever ran it."},{"href":"/customize/actions#return-values","page":"Custom Actions","heading":"Return Values","text":"Whatever run returns is the action's result, which the CLI prints as JSON: Returning nothing, nil, or true means there is no result."},{"href":"/customize/actions#running-an-action","page":"Custom Actions","heading":"Running an Action","text":"From a key. Bind it by name, the same as a built-in action. See Keyboard Shortcuts. From the command palette. Search for the action's title. From the CLI. Use rex do with the action's name and its arguments as key=value pairs. Add --json for formatted output. rex actions lists the custom actions the server has loaded."},{"href":"/customize/actions#calling-other-actions","page":"Custom Actions","heading":"Calling Other Actions","text":"rex.invoke runs another custom action from inside run and returns its result. It runs with the same session, block, and client. rex.client.queue asks the client that ran the action to perform one of its own actions, such as closing the command palette or zooming a pane. Queued actions run in order after run returns, and are discarded if run fails. rex.client.queue needs a client, so it raises an error when the action is run from the CLI and no client can be found. The app also only accepts actions from the CLI when Remote Control is turned on in its settings."},{"href":"/customize/actions#running-commands","page":"Custom Actions","heading":"Running Commands","text":"rex.exec runs a command and returns what it printed and how it exited: While an action waits for a command, the server keeps handling keys and other actions, so a slow command doesn't hold anything else up. The action still has 30 seconds to finish, and any command it started is stopped when it returns. Use rex.exec.start with detach = true for a command that should keep running. Lua Scripts covers rex.exec in more detail."},{"href":"/customize/actions#trying-an-action-before-reloading","page":"Custom Actions","heading":"Trying an Action Before Reloading","text":"rex do can load a file and run one of its actions without touching the running configuration. Pass the path to the file and name the action with --action: This is a quick way to iterate on an action. The action really runs, against the session you run the command in."},{"href":"/automate/cli","page":"The CLI","heading":"","text":"rex is the command-line interface to Rex. rex is both an administrative tool for Rex as well as a foundation for automation. As an administrative tool, it can create sessions, split panes, list active clients, force disconnect a client, etc. As an automation tool, it has a complete, powerful Lua scripting interface. Rex adds rex to the PATH of every terminal it opens, and sets up the context so that the CLI already knows what session you're in, which block you're looking at, and so on. If rex isn't found, see PATH. Open a terminal and try it: Run rex help for every command, or rex help <command> for the details of one."},{"href":"/automate/cli#taxonomy","page":"The CLI","heading":"Taxonomy","text":"Rex organizes work into four kinds of things. Every command acts on one or more of them. A session is a group of related work, such as one project. The server keeps a session running whether or not anything is looking at it. A window is a tab within a session. It holds a layout of blocks. A block is one application in a window. Today that means a terminal. A client is a program connected to the server, such as the app. Clients matter when you ask the app itself to do something, like open its command palette. Each one has an ID that never changes and is never reused, such as block:01a10d99-0c06-7795-bf67-798ce4c36a33. Sessions, windows, blocks, and clients can also have a label, which is a name you choose and can change."},{"href":"/automate/cli#command-targets","page":"The CLI","heading":"Command Targets","text":"With no options, a CLI command acts based on its context. Inside a Rex terminal that is the terminal's own block and session, so rex split splits the terminal you typed it in. Rex knows where you are from three environment variables it sets in every terminal: Variable Holds REX_SESSION The ID of the session the terminal belongs to REX_BLOCK The ID of the terminal's own block REX_SERVER The address of the server that runs the terminal To act somewhere else, specify it with a flag: Option Chooses -s, --session A session -w, --window A window within the session -b, --block A block -C, --client A client. REX_CLIENT sets a default. -S, --server A server, by the label of a host from rex hosts or by address. REX_SERVER sets a default."},{"href":"/automate/cli#targets","page":"The CLI","heading":"Targets","text":"Rex tries to match a session, window, block, or client target (such as -C) in this order and stops at the first kind of match it finds: A full ID, such as session:01a10cf3-2655-7e48-9b9c-43dd6612bef5. A case-insensitive label, such as build. A position, counting from one. Windows are numbered in tab order, so -w 2 is the second tab. The end of an ID, at least four characters long, such as 12bef5. Listing commands print these short IDs. If a target matches more than one thing, the command stops without changing anything, lists what matched, and exits with status 3: A server is the exception. -S takes only a host label or an address, and a value with a port is always an address. Labels are convenient at a prompt, but they can change and two things can share one. In a script that has to keep working, save the ID a command returns and use that."},{"href":"/automate/cli#machine-readable-output","page":"The CLI","heading":"Machine-Readable Output","text":"Most commands print a table meant for reading. Pass --json to get JSON instead, which is stable to parse and includes full IDs: The exit status says how a command ended: Status Meaning 0 The command succeeded 1 The command failed 2 The command line was invalid 3 A target was not found or was ambiguous 4 The server could not be reached 130 The command was interrupted Commands that wait for a program, such as rex wait and rex run --wait, exit with that program's status instead."},{"href":"/automate/cli#path","page":"The CLI","heading":"PATH","text":"When Rex opens a terminal, it puts the directory that holds the server's rex executable at the front of PATH. It also sets REX_BIN_DIR to the same directory. Your shell's startup files run after that. A startup file that sets PATH to a fixed list leaves rex out. One that reorders PATH can put a different copy of rex ahead of the one that matches your server. To check, compare the two: If PATH doesn't start with REX_BIN_DIR, add it back to the front at the end of your shell's startup file. Adding it to the end of PATH instead can still pick up another copy of rex first."},{"href":"/automate/cli#nix","page":"The CLI","heading":"Nix","text":"NixOS and nix-darwin set PATH to a fixed list each time a shell starts. This affects you if your PATH includes /run/current-system/sw/bin, even if you only used Nix to install your shell. Add the same check to your system configuration so it runs in every interactive bash and zsh: For fish, put the fish example above in programs.fish.interactiveShellInit. With home-manager, use your shell's init option, such as programs.zsh.initContent. Don't add REX_BIN_DIR to home.sessionPath. Outside Rex the variable is empty, which adds an empty entry to PATH."},{"href":"/automate/agent-skill","page":"Agents","heading":"","text":"Rex ships with a built-in agent skill that enables agents such as Claude Code or Codex to efficiently open tabs, split panes, read a terminal's screen, control connected Rex apps, and more. Even without the skill, a modern agent is smart enough to discover and use the rex CLI to perform tasks, but the skill makes it far more efficient and is recommended. You'll use fewer tokens and take less time to achieve the same tasks."},{"href":"/automate/agent-skill#what-agents-can-do","page":"Agents","heading":"What Agents Can Do","text":"Here are some examples of what your agent can do with Rex: You ask The agent \"Open nvim with top in a narrow pane on the right.\" Opens a tab with the editor focused and gives top the right third. \"Start the dev server in another tab and tell me when it's ready.\" Starts the server without taking focus, then reads its tab until the server says it's listening. \"Run the tests beside you and fix whatever fails.\" Runs the tests in a split, waits for them to finish, and reads the failures from the screen before fixing them. \"Run \\dt in the psql pane and tell me what's there.\" Types the command into psql, presses Return, and reads the result. \"Switch my Mac to this session and make the text bigger.\" Asks the app on that Mac to show the session and increase its font size. \"Close everything you opened.\" Closes the tabs and panes it created and leaves the rest alone. Requests that change the app itself, such as switching your Mac to a session, need the app running with Remote Control turned on in its settings."},{"href":"/automate/agent-skill#agent-setup","page":"Agents","heading":"Agent Setup","text":"On macOS, run Install Rex for Agents from the command palette. Press Cmd+Shift+P to open the palette. From the CLI, rex skill setup finds the agents installed on this machine and sets up each one. If you install new agents in the future, it is safe to run multiple times. This installs two things: The skill teaches the agent the rex commands and when to use them. It reads its guide from the installed rex, so upgrading Rex updates what the agent knows without running setup again. A hook tells the agent, at the start of each session, that it is running in Rex and which session and terminal it is in, so it doesn't spend time or tokens finding out. Rex adds its hook to the agent's settings file and leaves the rest of the file as it was. Running setup again reports what is already current as unchanged and updates anything an older Rex installed. Rex finds an agent by its configuration directory, such as ~/.claude, or by its program on your PATH. To set up an agent Rex didn't find, name it with --agent. These are all of the options: Option Does --agent NAME Sets up only the named agent, whether or not Rex found it. Repeat it to name several. --dry-run Reports what would change without changing anything. --project Sets up the current repository instead of your account. --force Replaces a skill named rex that Rex didn't install. --session-naming Has agents name the session after their task without being asked. Running setup again without it keeps the choice already installed. --json Prints the result as JSON."},{"href":"/automate/agent-skill#approving-the-hook","page":"Agents","heading":"Approving the Hook","text":"Some agents won't run a new hook until you allow it: Codex asks before it runs a new or changed hook. Approve Rex's hook with /hooks in Codex. Until then, Codex skips it. Claude Code doesn't ask about each hook, but it runs no hooks at all until you trust the folder it starts in."},{"href":"/automate/agent-skill#supported-agents","page":"Agents","heading":"Supported Agents","text":"rex skill setup can set up these agents: Agent --agent Skill Hook Claude Code claude ~/.claude/skills/rex ~/.claude/settings.json Codex codex ~/.agents/skills/rex ~/.codex/hooks.json OpenCode opencode ~/.agents/skills/rex Not supported Pi pi ~/.agents/skills/rex Not supported Rex follows the environment variables that move an agent's directory, such as CLAUDE_CONFIG_DIR and CODEX_HOME. Any other agent that can run commands in a Rex terminal can use Rex too. rex skill show prints the guide the skill gives agents, so tell the agent to run it and follow the guide. We'll continue to add support for more common agents over time."},{"href":"/automate/agent-skill#how-it-works","page":"Agents","heading":"How It Works","text":"An agent runs in a Rex terminal like any other program, so it has the same REX_SESSION and REX_BLOCK environment variables as your shell. They tell the agent which session and terminal it is in. You may be looking at a different session than the agent is in. The agent chooses where to act from what you say: \"Here\", \"beside you\", or nothing at all. The agent's own session and terminal. \"The focused pane\" or \"the current tab\". The pane or tab that has focus in the session you're viewing. A name. The session, tab, or pane with that label. A name always wins over the agent's own location and the app's focus. An agent started outside Rex has no REX_SESSION. It lists the sessions with rex ls and works in the one that matches your request, or asks you to choose when more than one does."},{"href":"/automate/agent-skill#removing-the-skill","page":"Agents","heading":"Removing the Skill","text":"rex skill remove removes the skill and hooks from every agent Rex knows, including agents you've since uninstalled: Only what Rex installed is removed, including copies of the skill an older Rex left in other directories the agent reads. Other skills, other hooks, and the rest of each settings file stay as they were. --agent, --project, and --dry-run work the same as for rex skill setup. Codex remembers which hooks you approved by their place in hooks.json. If removing or updating Rex's hook moves the hooks after it, Codex asks you to approve those again, and Rex says so in its output."},{"href":"/automate/agent-skill#debugging","page":"Agents","heading":"Debugging","text":"rex skill status reports what is installed in each agent, without changing anything: State Meaning current Installed and up to date. outdated Installed by an older Rex, or edited since. rex skill setup updates it. missing Not installed. foreign A skill named rex that Rex didn't install. rex skill setup leaves it alone unless you pass --force. unreadable The file couldn't be read. unsupported The agent can't hold this item, such as a hook in an agent without hooks. If an agent doesn't seem to know it's in Rex, check that its session started after rex skill setup ran, since the hook only runs when a session starts. In Codex, check that the hook is approved with /hooks. In Claude Code, check that you trusted the folder."},{"href":"/automate/lua-scripts","page":"Lua Scripts","heading":"","text":"rex do runs a Lua script against a Rex server. A script is extremely powerful. It can do anything a full-fledged client can: inspect servers, create sessions, create windows, start terminals, read a terminal screen, react to events, and more. This script opens a new window running htop: A script uses the same rex API as the configuration file. Unlike the configuration file, a script runs once from top to bottom and exits (unless there are active event handlers or timers running)."},{"href":"/automate/lua-scripts#running-a-script","page":"Lua Scripts","heading":"Running a Script","text":"rex do takes a script in three ways: The argument is treated as a file when it ends in .lua or contains a slash. Anything else is the name of an action to run. A script run inside a Rex terminal acts on that terminal's session and block. Use -s and -b to target a different one: Scripts have Lua's full standard library, including io and os, and require loads other Lua files. The exception is io.popen and os.execute. Use rex.exec to run commands instead. Scripts are executed locally where the rex CLI is, so security and program access is up to you."},{"href":"/automate/lua-scripts#arguments","page":"Lua Scripts","heading":"Arguments","text":"Arguments are passed as key=value pairs after the script and read from the rex.args table: A value's type comes from how it is written. n=2 is a number, on=true is a boolean, and name=x is a string. To pass a string that looks like something else, or a nested value, pass all the arguments as JSON with --args:"},{"href":"/automate/lua-scripts#results-and-errors","page":"Lua Scripts","heading":"Results and Errors","text":"The value a script returns is printed as JSON. Tables become objects or arrays. Add --json for indented output. A script that returns nothing prints nothing. A script that raises an error prints the message and exits with status 1: To exit with a status of your choosing, call rex.stop with it. The script ends immediately: print writes to the terminal as usual, and rex.log writes a log line."},{"href":"/automate/lua-scripts#calling-rex","page":"Lua Scripts","heading":"Calling Rex","text":"A script starts with a few values describing where it was run: Value Description rex.session_id The session the script is running against. rex.block_id The block the script is running against, when there is one. rex.args The script's arguments. Rex's API is grouped under rex.session. Each call takes one table of arguments and returns one table: Calls fill in the script's session, so most calls don't need a session_id. A call that acts on one block, such as set_block_label, still needs its block_id. A failed call doesn't stop the script. It returns nil and an error message, so check the second value when the result matters: Three more ways to call Rex: rex.call(method, args) calls any server method by name, including those outside rex.session, such as rex.call(\"session.list\"). rex.block.call(creator, method, args) calls a method on a block, such as reading a terminal's screen. rex.invoke(name, args) runs an action, either one the script defines or one from your configuration file. rex.client.queue(name, args) asks the app to perform one of its own. The Lua API reference has the full list."},{"href":"/automate/lua-scripts#layouts","page":"Lua Scripts","heading":"Layouts","text":"Calls that create blocks take a layout. Build one with rex.layout: rex.layout.block is one block. rex.layout.horizontal and rex.layout.vertical split the space between two layouts, with the first argument giving the first one's share. They can be nested."},{"href":"/automate/lua-scripts#sleeping-and-waiting","page":"Lua Scripts","heading":"Sleeping and Waiting","text":"A script often needs to start something and then act on how it ended. rex.sleep(seconds) pauses the script: rex.wait(event, filter, timeout) pauses until an event arrives: event is the name of the event to wait for. filter is optional. A table matches an event whose fields equal the table's. A function is called with the event and matches when it returns true. timeout is optional, in seconds. Without it, rex.wait waits forever. rex.wait returns the event as a table. If the timeout passes first, it returns nil and \"timeout\". The events reference lists the events and their fields."},{"href":"/automate/lua-scripts#timers","page":"Lua Scripts","heading":"Timers","text":"rex.every calls a function at a fixed interval, and rex.after calls one once after a delay: Unlike rex.sleep, a timer doesn't pause the script. The script carries on, and the function is called later. Both functions return a timer. Call t:cancel() to stop it. A timer keeps running even if the script drops it, so keep it around if it may need to be stopped. Timers can be created anywhere, including inside an event handler or another timer. The function's ctx and API calls always default to the script's own session and block, even when the timer was created in a handler. Timers never run at the same time as other Lua in the script. A timer that comes due while a handler is running waits for it to return. If the script is still busy when the next tick of rex.every comes, the function runs once when it is free rather than once for each tick it missed. An error in a timer is printed and the timer keeps running, the same as an error in a handler. A script can have at most 1,024 timers at once. A cancelled timer, or a rex.after timer that has already run, doesn't count. Going past the limit stops the script with status 1, even inside pcall, because it almost always means a handler creates a timer for every event and never cancels it."},{"href":"/automate/lua-scripts#running-commands","page":"Lua Scripts","heading":"Running Commands","text":"rex.exec runs a command and returns what it printed and how it exited: The command is a list: the program, then each of its arguments. No shell reads it, so a path with spaces or a value from rex.args needs no quoting. To use a shell, ask for one: { \"sh\", \"-c\", line }. A command that exits with a non-zero status still returns a result. Check r.ok, or r.code for the exact status, and r.stderr for what it printed to standard error. rex.exec only returns nil and an error message when the command couldn't run at all, such as when the program isn't installed. Options go in a second table: The Lua API reference lists every option and field."},{"href":"/automate/lua-scripts#streaming-output","page":"Lua Scripts","heading":"Streaming Output","text":"rex.exec.start starts a command without waiting for it and returns a handle. Pass a function as stdout or stderr to receive output as it arrives: A command lasts as long as the part of the script that started it. When the script's top level finishes, or a handler or timer returns, Rex stops the commands it started. Pass detach = true for a command that should keep running. To watch a command run, start it in a terminal block instead, as in the example below."},{"href":"/automate/lua-scripts#example-run-tests-in-a-split","page":"Lua Scripts","heading":"Example: Run Tests in a Split","text":"This script runs a command in a new split beside the current terminal. If the command fails, it zooms the split so the failure is front and center."},{"href":"/automate/lua-scripts#example-export-the-session","page":"Lua Scripts","heading":"Example: Export the Session","text":"This script keeps a copy of the session's layout in a JSON file, written every five seconds, for another program to read. rex.json.encode converts a table the same way an API call does, so the file matches what the server returned. rex.json.decode reads it back. If the server is briefly unreachable, export raises an error, which is printed, and the timer tries again five seconds later."},{"href":"/automate/lua-events","page":"Lua Events","heading":"","text":"A Lua script can keep running and react to what happens in Rex: a terminal changes directory, a program exits, a window closes, a session is created. A script asks for an event with rex.on. Change directory in any terminal in the session and its label will update."},{"href":"/automate/lua-events#handlers","page":"Lua Events","heading":"Handlers","text":"rex.on takes the name of an event and a function to call each time that event happens: A script that calls rex.on doesn't exit when it reaches its last line. rex do keeps it running and calls its handlers as events arrive, one at a time. Handlers are registered while the script first runs. Calling rex.on later, from inside a handler, is an error. ev holds the event's fields, and ctx says where it happened: Field Description ctx.session_id The session the event happened in. ctx.block_id The block the event came from, for a block event. API calls inside a handler default to the event's session rather than the one the script was started in, so one handler works across every session it hears."},{"href":"/automate/lua-events#event-names","page":"Lua Events","heading":"Event Names","text":"There are three kinds of event name: Name Matches block_closed, window_created, ... A session or server event, by its type. terminal.pwd_changed, terminal.bell, ... One kind of event from a block. block_event Every event from every block. A block event is named for the kind of block and the event, joined by a dot. terminal.bell is short for com.superlogical.terminal.bell. Either spelling works. These are common events: Event When Fields on ev terminal.pwd_changed A terminal changes directory. pwd, a file:// URL terminal.title_changed A terminal's title changes. title terminal.bell A terminal rings its bell. terminal.child_exited A terminal's program exits. exit_code block_created, block_closed A block is created or closed. block_id window_created, window_closed A window is created or closed. window_id session_created, session_destroyed A session is created or destroyed. The events reference has the full list. Rex doesn't check an event name against a list, so a misspelled name is not an error. The handler is registered and never called."},{"href":"/automate/lua-events#event-scope","page":"Lua Events","heading":"Event Scope","text":"By default, a script reacts to events of one session: the one it was run in, or the one named with -s. Pass --all to hear every session, including ones created after the script starts:"},{"href":"/automate/lua-events#stopping","page":"Lua Events","heading":"Stopping","text":"A script with handlers runs until one of these happens: A handler calls rex.stop(code). rex do exits with that status, or 0 when it is left out. The time given with --for runs out, such as --for 10m. The session it hears is destroyed. A script run with --all never stops this way. You press Ctrl+C."},{"href":"/automate/lua-events#handler-failures","page":"Lua Events","heading":"Handler Failures","text":"An error in a handler or a timer is printed and the script keeps running, so one bad event doesn't end a long-lived script: Pass --fail-fast to exit with status 1 at the first failure instead. Remember that a failed API call returns nil and a message rather than raising an error. Check the second return value where it matters."},{"href":"/automate/lua-events#handlers-or-rexwait","page":"Lua Events","heading":"Handlers or rex.wait","text":"rex.wait and rex.on both receive events, and suit different scripts. Use rex.wait when the script does one thing after another: start a command, wait for it to exit, then act on the result. Use rex.on when the script should respond every time something happens, for as long as it runs. rex.wait and rex.sleep also work inside a handler. No other event is handled until they return."},{"href":"/automate/lua-events#timers-in-handlers","page":"Lua Events","heading":"Timers in Handlers","text":"A handler that needs to do something later can start a timer and return, rather than sleep and hold up every other event. This is useful for events that arrive in bursts. session_view_changed follows almost every change to the layout, so this calls an export function once, half a second after the last change of a burst: A timer's API calls default to the session the script was started in, not the event's. To act on the event's session, pass ctx.session_id along:"},{"href":"/automate/lua-events#invalid-in-configuration-files","page":"Lua Events","heading":"Invalid in Configuration Files","text":"rex.on only works in a script run with rex do. The configuration file is never sent events, so a handler registered there is reported as a warning and never called. To have a handler running all the time, start the script when you start your session, for example from a terminal you keep open for it."},{"href":"/automate/lua-events#example-notify-on-bell","page":"Lua Events","heading":"Example: Notify on Bell","text":"Many programs ring the terminal bell when they finish or need attention. This script turns a bell in any terminal, in any session, into a macOS notification that names the session: rex.session.view is called without a session_id, so it describes the session the bell rang in. That is how one handler can name whichever session needs you."},{"href":"/automate/shell-scripting","page":"Shell Scripting","heading":"","text":"You can also use the rex CLI with typical shell scripts to automate everything in Rex: For anything beyond a few commands in a row, consider a Lua script instead. Lua can do everything on this page with one connection to the server and without parsing JSON."},{"href":"/automate/shell-scripting#keeping-track-of-ids","page":"Shell Scripting","heading":"Keeping Track of IDs","text":"Commands that create something print its ID with --json. Save the ID and pass it to later commands, so the script keeps working when a label changes or focus moves. jq is the easiest way to pull the ID out: Command Creates ID is at rex new --json A session with one window and one terminal .session_id rex run --json A terminal in a new window .block_ids[0], .window_id rex split --json A terminal beside an existing one .block_ids[0] A label is a fine alternative when the script sets it. Give the block a label with --label and use that as the target:"},{"href":"/automate/shell-scripting#building-a-layout","page":"Shell Scripting","heading":"Building a Layout","text":"rex new creates a session, rex run adds a window to it, and rex split splits an existing block. These options are shared by rex run and rex split: Option Does -- COMMAND Runs a command instead of an interactive shell. --cwd DIR Sets the working directory. --label TEXT Labels the new block. --split DIRECTION Places the block right, left, above, or below its anchor. --ratio PCT Gives the new block that percentage of the split. --focus=false Leaves focus where it was. --keep-open Keeps the terminal open after its command exits. A command runs with the environment of your login shell, so your PATH applies but aliases don't. It isn't parsed by a shell. To use pipes or &&, run a shell explicitly:"},{"href":"/automate/shell-scripting#typing-into-a-terminal","page":"Shell Scripting","heading":"Typing Into a Terminal","text":"rex send types text into a terminal, and rex send-key presses keys: rex send doesn't press Enter for you. Follow it with rex send-key enter, or include the newline in the text. It also reads from stdin: rex send-key uses the same key names as keyboard shortcuts, so rex send-key ctrl+c interrupts whatever is running."},{"href":"/automate/shell-scripting#reading-a-terminal","page":"Shell Scripting","heading":"Reading a Terminal","text":"rex capture prints a terminal's screen and its scrollback: Option Does --trim Removes trailing blank lines. --unwrap Joins lines the terminal wrapped. --format html Keeps colors and styling, as HTML. --format vt Keeps the terminal's control sequences. A terminal keeps about 64 MiB of scrollback. That is roughly 100,000 lines of plain text at 80 columns, and fewer in a wider terminal or with styled output. Once the limit is reached, the oldest lines are dropped. To keep everything a long command prints, redirect its output to a file. Full-screen programs, such as editors, pagers, and many TUI apps, draw on a separate screen that has no scrollback. While one is running, rex capture returns only what that program shows right now. Any history it has, like an editor's file or a chat app's earlier messages, stays inside the program."},{"href":"/automate/shell-scripting#running-a-command-and-waiting","page":"Shell Scripting","heading":"Running a Command and Waiting","text":"Add --wait to rex run or rex split to wait for the command to finish. The rex command then exits with the command's own status: rex wait does the same for a terminal that is already running, given its ID or label: rex wait also accepts a window or a session, and returns when it closes. Use --for to give up after a while. A wait that times out exits with status 1:"},{"href":"/automate/shell-scripting#performing-actions","page":"Shell Scripting","heading":"Performing Actions","text":"rex do performs an action by name, with arguments as key=value pairs. It runs both the app's actions and the custom actions in your configuration file: Actions such as pane.split are performed by the app, so the app must be running, with Remote Control turned on in its settings."},{"href":"/automate/shell-scripting#watching-events","page":"Shell Scripting","heading":"Watching Events","text":"rex events prints events as they happen and runs until interrupted. With -s it follows one session: With --json it prints one JSON object per line, ready for jq. This prints the ID of each terminal that rings its bell: The events reference describes the JSON and lists every event. To act on events rather than print them, Lua event handlers are much less work than a pipeline."},{"href":"/automate/shell-scripting#example-run-tests-in-a-split","page":"Shell Scripting","heading":"Example: Run Tests in a Split","text":"This script runs a command in a split beside the current terminal. If the command fails, it zooms the split and exits with a failure. The same script in Lua is a useful comparison."},{"href":"/automate/server-api","page":"Server API","heading":"","text":"The server documents its own API. You can list every method, read a method's arguments and result, and call it, all from the CLI: Because the documentation comes from the server you are connected to, it always matches that server's version. The CLI and Lua APIs all derive dynamically from the server API it is connected to, so these commands are also important for scripting and automation at a higher level."},{"href":"/automate/server-api#discovering-methods","page":"Server API","heading":"Discovering Methods","text":"rex api list prints every method with a short description: A second table lists the methods that blocks provide, such as reading a terminal's screen. Use -q to print only the names, which is handy for searching:"},{"href":"/automate/server-api#reading-a-methods-documentation","page":"Server API","heading":"Reading a Method's Documentation","text":"rex api describe prints everything the server knows about one method: what it does, the arguments it takes, what it returns, and an example of each. The schemas are JSON Schema. To read one: properties lists each argument and its type. required lists the arguments that must be present. additionalProperties: false means a misspelled argument is an error rather than being ignored. The request example is usually the quickest way to see how a call should look. Some methods also print validation notes, which describe rules the schema can't express."},{"href":"/automate/server-api#calling-a-method","page":"Server API","heading":"Calling a Method","text":"rex api call calls a method and prints its result as JSON: Arguments are a single JSON object. Pass it directly, read it from a file with @, or read it from stdin with -: Most session. methods need a session_id. Name the session with -s and rex api call adds it to the arguments for you: Unlike other rex commands, rex api call doesn't default to the session you are in. Inside a Rex terminal, pass -s \"$REX_SESSION\" to use it. Other IDs are never filled in. A method that acts on a block needs its block_id in the arguments. The result is indented when printed to a terminal and kept on one line when piped, so it is ready for jq:"},{"href":"/automate/server-api#errors","page":"Server API","heading":"Errors","text":"A call that fails prints the server's message and exits with a nonzero status: The code says what kind of failure it was. bad_request means the arguments were wrong, and method_not_found means the server has no method by that name, which can happen when the server is older than the docs you are reading."},{"href":"/automate/server-api#calling-from-lua","page":"Server API","heading":"Calling From Lua","text":"rex.call takes a method's full name and its arguments as a table: Every session. method that acts on one session also has a helper under rex.session, named after the method: API method Lua session.view rex.session.view{} session.set_label rex.session.set_label{ label = \"work\" } session.new_split rex.session.new_split{ ... } The arguments and result are the same as rex api describe shows, written as Lua tables instead of JSON. The helpers fill in session_id when it is left out. rex.call doesn't, so pass rex.session_id yourself when calling a session. method through it. A few session. methods have no helper because they don't act on an existing session, such as session.list and session.create. Call those with rex.call. A failed call returns nil and the server's error message: The Lua API reference covers these functions in full."},{"href":"/automate/server-api#block-methods","page":"Server API","heading":"Block Methods","text":"Blocks provide methods of their own. rex api list prints them in a second table, named for the kind of block that provides them: Describe one the same way as any other method: To call one, pass the block_id to call it on, and put the method's own arguments under args: In Lua, rex.block.call builds that wrapper for you and fills in the current block:"},{"href":"/automate/server-api#machine-readable-documentation","page":"Server API","heading":"Machine-Readable Documentation","text":"Both discovery commands take --json, which prints the server's own description of its API: Each method has a name, a description, an input_schema, an output_schema, and, where the server has them, a request_example, a response_example, and validation_notes. This is useful for generating bindings, for checking a script against a server before running it, and for giving a coding agent the exact shape of the API it is about to call."},{"href":"/build/program-status","page":"Program Status Protocol","heading":"","text":"A terminal escape sequence that lets a program tell the terminal what it is doing: idle, working, waiting on the user, finished, or failed, and why. The protocol shares state and leaves presentation to the terminal. It does not say how anything is shown, and it avoids terms tied to particular GUI elements (\"notification\", \"attention\", \"focus\", etc.). Note: This is a generic terminal protocol. While it is used for rich functionality within Rex, the protocol itself has no Superlogical-specific functionality or language. It fills a broad need within terminal applications."},{"href":"/build/program-status#motivation","page":"Program Status Protocol","heading":"Motivation","text":"Long-running work is common in terminals: builds, deployments, package upgrades, data processing, coding agents. Such a program alternates between working on its own, waiting on the user, and finishing. Meanwhile the user is usually doing something else and wants to know when the work finishes or needs them. Existing sequences cover only slices of this. OSC 9;4 progress can say busy or not busy, but not that a program is waiting on the user or what it is working on. Desktop notifications are one-time events with free-form text. Tools that show many programs at once fall back to heuristics such as parsing window titles or matching screen contents, or to program-specific plugins. Relationship to other sequences goes into detail. With this protocol the program that knows its state reports it, and the terminal keeps a current picture of every program running in it."},{"href":"/build/program-status#in-rex","page":"Program Status Protocol","heading":"In Rex","text":"When your program adopts the Program Status Protocol in Rex, we show the current active status in a number of ways today: Sessions with completed or blocked tasks show up with an indicator in the session picker. Tab headers for unfocused tabs show an in-progress spinner and a variety symbols for blocked states. Lua scripts can react to terminal.program_status_changed events. In the future, we plan to expand this functionality in many more ways, such as push notifying our mobile applications, a unified priority inbox view, and many more."},{"href":"/build/program-status#conventions","page":"Program Status Protocol","heading":"Conventions","text":"The key words MUST, MUST NOT, SHOULD, and MAY are used as described in RFC 2119 and RFC 8174, and only when they appear in capitals. Name Bytes ESC 0x1B OSC ESC ], that is 0x1B 0x5D ST ESC \\, that is 0x1B 0x5C, or BEL, that is 0x07 A terminal is one window, tab, split, or pane attached to one pseudo-terminal. Rules that say \"the terminal\" apply to each one separately and are implemented by whatever emulates it. A program writes to a terminal. A report is one sequence sent by a program. A record is what the terminal stores as a result."},{"href":"/build/program-status#syntax","page":"Program Status Protocol","heading":"Syntax","text":"The body is a list of key=value pairs separated by : and nothing else. The value character set excludes : and ;, so nothing needs escaping. Whitespace around keys and values is removed. A malformed pair (no =, empty key, or a byte outside the value set) is skipped and the rest of the report is still processed. Unknown keys MUST be ignored. This is how the protocol is extended. If a key repeats, the last value wins. Free-text values (msg, title) are standard base64 of UTF-8 text. Padding is optional. The decoded text MUST NOT contain control characters (U+0000–U+001F, U+007F, U+0080–U+009F), including newline or tab. These values are meant to be single line values. A report is discarded whole if it exceeds one of the limits, if its base64 does not decode, or if decoded text contains a control character. Nothing from a discarded report is applied."},{"href":"/build/program-status#records-and-ids","page":"Program Status Protocol","heading":"Records and ids","text":"A terminal holds a set of records, one per id. Each record holds exactly the keys of the last report that used that id. A report with no id addresses the root record. A program that only reports its own state uses the root record and can skip the rest of this section. Each report replaces its record completely. A key missing from the report is missing from the record afterwards. A program that wants app or title on a record includes them in every report. A report whose id does not match this grammar is ignored. Falling back to the root record would let a malformed id overwrite it. / gives records a parent and child relationship: build/test is a child of build, and build is a child of the root record. A parent does not have to exist. Terminals MUST use this relationship for clearing (see States) and app inheritance (see Keys), and MAY use it for grouping. It means nothing else. Root and child records exist at the same time. A program can be idle at its prompt while one of its workers is blocked. Records belong to the terminal, not to a screen. Switching between the primary and alternate screen has no effect on them. A full reset (RIS) removes every record. A soft reset (DECSTR) does not."},{"href":"/build/program-status#states","page":"Program Status Protocol","heading":"States","text":"The state key is required. A report with no state, or one the terminal does not recognize, is ignored. state Meaning Lifetime idle At rest, waiting for the user's next instruction. Until replaced or cleared. working Running. May carry progress. Until replaced, cleared, process exit, or next shell prompt. done Finished a piece of work. The result is ready to look at and the user has not seen it yet. Until replaced or cleared. Survives process exit and shell prompt. blocked Cannot continue until the user does something. kind says what, msg says why. May carry progress. Same as working. error Failed and stopped. Same as done. clear Not a state. Removes the addressed record and every record beneath it. With no id, removes every record on the terminal. When the user interrupts or cancels a program, report idle. A program that exits as soon as it finishes SHOULD report done or error right before exiting, so a record is left behind for the user to find. Lifetime. Records are tied to events the terminal already sees. There is no heartbeat, and terminals MUST NOT require re-sending to keep a record alive. When the process attached to the terminal exits, or a new shell prompt begins (OSC 133 A), the terminal MUST drop working and blocked records and MAY drop idle ones. done and error survive both events. The terminal decides when to stop showing them, for example when the user returns to the terminal and presses a key."},{"href":"/build/program-status#keys","page":"Program Status Protocol","heading":"Keys","text":"Key Value Notes state idle \\ working \\ done \\ blocked \\ error \\ clear Required. An unrecognized value causes the whole report to be ignored, so a state added later never turns into idle on an older terminal. id path, see Records and ids Absent means the root record. kind permission \\ question \\ auth With blocked only, ignored otherwise. permission: approval to do something. question: the user must type an answer. auth: a login, token, or credential. Unrecognized values are treated as absent. progress integer 0 to 100 With working or blocked, ignored otherwise. Absent means indeterminate: the program is busy but has no percentage to report. Anything else is treated as absent. app [A-Za-z0-9_.+-]{1,32} Stable identifier for the program, such as cargo, terraform, or claude-code. A key for grouping, filtering, icons, etc. This is not a label; title is the label. A record without app takes it from its nearest ancestor that has one. A value outside the character set is treated as absent. title base64 UTF-8 Short human-readable label for the record. For programs that report several records. The root record's label is normally the window title. msg base64 UTF-8 One human-readable line: what the record is doing, waiting for, or finished. Terminals MAY shorten it and MUST NOT read meaning into it."},{"href":"/build/program-status#feature-detection","page":"Program Status Protocol","heading":"Feature detection","text":"Detection is optional. Terminals MUST ignore unknown OSCs, so a program MAY send reports without querying first, for example when it cannot read replies. A supporting terminal MUST reply with ?. It MAY add states, the comma-separated state values it accepts (not counting clear), and kinds, the kind values it accepts. A reply without states means exactly idle, working, done, blocked, and error. A reply without kinds means exactly permission, question, and auth. Those lists are fixed and do not change in later revisions. This is how states and kinds are added: a program that wants a state from a later revision checks the list and falls back to one that is present. Programs MUST ignore pairs in the reply that they do not understand. A program that gets no reply within a time of its choosing treats the protocol as unsupported. To avoid waiting, send primary device attributes (CSI c) right after the query. Every terminal answers that, so if its reply arrives first, the protocol is unsupported."},{"href":"/build/program-status#security","page":"Program Status Protocol","heading":"Security","text":"Everything in a report is untrusted input from a program that already controls the screen. Terminals MUST refuse a report whose decoded msg or title contains a control character, MUST NOT treat either as markup, and SHOULD disarm text direction overrides and other invisible formatting characters when showing them outside the terminal grid. The only bytes a terminal writes back are the feature detection reply, which contains nothing from any report. Ids, titles, and messages are never sent back, and there is no way to read records. Anything a terminal shows from a record SHOULD say which terminal it came from, so a program cannot pretend to be a program in another terminal. A program can change state as fast as it can write. Terminals SHOULD rate-limit anything a record causes outside the terminal, such as desktop notifications or sounds."},{"href":"/build/program-status#limits","page":"Program Status Protocol","heading":"Limits","text":"Every limit is a hard cap on what a terminal has to store or do for one report, so a hostile or buggy program cannot make memory grow or cause unbounded work. Terminals MAY choose lower limits. A report that breaks a limit is discarded whole. Check every pair before touching any stored record. Item Limit Whole sequence, OSC through ST 4096 bytes. The largest legal report is under 3300. Key length 16 bytes msg 2732 bytes encoded, 2048 decoded. Check the encoded size before decoding. title 256 bytes encoded, 192 decoded app 32 bytes id 128 bytes total, 32 per segment, 8 levels deep Records per terminal 256. Terminals MUST support at least 64. When a report would create a record past the cap, the record that was updated least recently is removed to make room."},{"href":"/build/program-status#relationship-to-other-sequences","page":"Program Status Protocol","heading":"Relationship to other sequences","text":"OSC 9;4 (progress). Says only busy, a percentage, or error. It cannot say that a program is waiting on the user or what it wants, and it carries no message or identity. It also shares the number 9 with iTerm2's notification sequence, which has caused garbled output in terminals that know only one of the two. A program may send both. Terminals MAY map OSC 9;4 to the root record of this protocol. Because each report replaces its record completely, a mapped OSC 9;4 would wipe out the kind and msg of a real report. A terminal SHOULD stop mapping OSC 9;4 once it has received an OSC 7501 report, until the next full reset. OSC 9, OSC 99, OSC 777 (desktop notifications). A notification is a one-time event. Once it is shown, the terminal has no record of what is currently true, so it cannot tell that a program is still waiting or that it recovered. A notification also implies interrupting the user, which is not always the right behavior. A terminal MAY produce a notification when a record changes. OSC 133 (shell integration). Marks where prompts and commands begin and end. It is emitted by the shell, not the program, so it can only say that a command is running or has finished with an exit code, never what the program is doing or whether it is stuck. OSC 0 and 2 (window title). A single free-form string with no structure. Programs that need to show state today overload it with glyphs and spinners, but this isn't a reliable machine-readable format for progress. OSC 21337 (iTerm2 session status) and OSC 9999 (Orca). Earlier work on the same idea. They describe presentation, with colors and indicators, or require JSON parsing, and overall I believe are not aligned with the right goals."},{"href":"/build/program-status#examples","page":"Program Status Protocol","heading":"Examples","text":"Base64 values are shown decoded in a trailing comment."},{"href":"/build/program-status#one-program","page":"Program Status Protocol","heading":"One program","text":"Every report repeats app because a report replaces its record completely. The done record survives the shell prompt that follows."},{"href":"/build/program-status#several-records","page":"Program Status Protocol","heading":"Several records","text":"The region records take app=deploy from the root when displayed. While eu-west is blocked the root still says working. Both are true, and the terminal decides which to show."},{"href":"/build/program-status#a-shell-function","page":"Program Status Protocol","heading":"A shell function","text":""},{"href":"/build/program-status#revision-history","page":"Program Status Protocol","heading":"Revision history","text":"Date Revision Changes 2026-10-10 0.4 Feature detection reply may list supported states and kinds. Say why control characters are refused and what app is for. Remove terminfo. 2026-10-07 0.3 Absent progress means indeterminate. Programs may send reports without feature detection. 2026-10-06 0.2 Clarify MUST NOT and apply keywords in more places. 2026-09-28 0.1 Initial draft."},{"href":"/reference/cli","page":"CLI Commands","heading":"","text":"This page lists every public rex command and flag. See The CLI for how to use them. Anything tagged Snapshot is on the snapshot channel and not yet in preview."},{"href":"/reference/cli#common-commands","page":"CLI Commands","heading":"Common Commands","text":""},{"href":"/reference/cli#rex-new-label----command","page":"CLI Commands","heading":"rex new [LABEL] [-- COMMAND...]","text":"Create a session. Flag Description -c, --cwd string Working directory for the terminal. --keep-open Keep the terminal open after its command exits. --shell SHELL Run COMMAND through SHELL's login environment (none starts it directly). --interactive Also read the shell's interactive startup files, such as ~/.zshrc. --window string Label for the first window. --json Output the created session as JSON."},{"href":"/reference/cli#rex-ls","page":"CLI Commands","heading":"rex ls","text":"List sessions. Flag Description --json Output sessions as JSON. -q, --quiet Print IDs only, one per line. --short Show short IDs (--short=false shows full IDs). Defaults to true."},{"href":"/reference/cli#rex-run-flags----command","page":"CLI Commands","heading":"rex run [flags] -- COMMAND...","text":"Run a command in a new block. Run a command in a new terminal block. COMMAND runs through your login shell's environment but not its interactive configuration, so profile variables and PATH apply and aliases do not. Use --interactive to read that configuration too, such as ~/.zshrc, for a program whose directory is added to PATH there. Use --shell to pick another shell, or --shell=none to start COMMAND directly. By default, Rex places the block in a new window. Use --split to place it beside an existing block. Use --focus=false to preserve the active window. With --wait, Rex writes the creation result, then waits and returns the terminal child's exit status. Flag Description --split DIRECTION Place the new block in DIRECTION relative to the anchor (right, left, above, or below). --ratio PCT Give the new block PCT percent of the split. Defaults to 50. -b, --block BLOCK Use BLOCK as the split anchor (defaults to focused). -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. -c, --cwd string Working directory for the terminal. --keep-open Keep the terminal open after its command exits. --shell SHELL Run COMMAND through SHELL's login environment (none starts it directly). --interactive Also read the shell's interactive startup files, such as ~/.zshrc. --label TEXT Set the new block label to TEXT. --focus Focus the new block. Defaults to true. --json Output the creation result as JSON. --wait Wait and return the terminal child's exit status."},{"href":"/reference/cli#rex-split-flags----command","page":"CLI Commands","heading":"rex split [flags] [-- COMMAND...]","text":"Split the focused block. Create a terminal block for a shell or command. A COMMAND runs through your login shell's environment but not its interactive configuration. Use --interactive to read that configuration too, --shell to pick another shell, or --shell=none to start COMMAND directly. By default, Rex splits the focused block and places the new block on its right. Use -b BLOCK to select another anchor or --split to choose another direction. Use --focus=false to preserve the current focus. With --wait, Rex writes the creation result, then waits and returns the terminal child's exit status. Use rex run to place the block in a new window. Flag Description --split DIRECTION Place the new block in DIRECTION relative to the anchor (right, left, above, or below). Defaults to right. --ratio PCT Give the new block PCT percent of the split. Defaults to 50. -b, --block BLOCK Use BLOCK as the split anchor (defaults to focused). -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. -c, --cwd string Working directory for the terminal. --keep-open Keep the terminal open after its command exits. --shell SHELL Run COMMAND through SHELL's login environment (none starts it directly). --interactive Also read the shell's interactive startup files, such as ~/.zshrc. --label TEXT Set the new block label to TEXT. --focus Focus the new block. Defaults to true. --json Output the creation result as JSON. --wait Wait and return the terminal child's exit status."},{"href":"/reference/cli#rex-do-action--script---e-code-keyvalue","page":"CLI Commands","heading":"rex do (ACTION | SCRIPT | -e CODE) [KEY=VALUE...]","text":"Perform an action or run a Lua script. Perform an action by name, or run a Lua script. A name without a dot is an action init.lua defines with rex.action. A name that starts with a client prefix, such as pane.split, is performed by the targeted client. A name that starts with a block type's creator name, such as com.superlogical.terminal.reset, is a block action or method. Run \"rex actions\" to list them. To run a Lua script instead, give its path. The first argument is a script path when it contains a slash (or a backslash on Windows) or ends in .lua. Use - to read the script from stdin, or -e to give it on the command line. A script can call the same API as init.lua. Its return value prints the same way as an action's result. In a script, rex.invoke runs the actions the script defines and asks the server to run any others. A script can also call the hosts that \"rex hosts\" lists. rex.servers() returns their labels, and rex.server(LABEL) returns the same API helpers as rex, with calls going to that host. rex do connects to a host on the script's first call to it, and stays connected until the script ends. --server chooses only the server that rex itself calls. With --action NAME, rex do runs the script, then runs the action the script defines as NAME and prints that action's result. Pairs are typed against that action's schema. Use this to try an action in init.lua before the server reloads it. A script that registers rex.on handlers keeps running after its last line, and runs its handlers as events arrive, one at a time. It hears the session that -s, -b, or the environment names, or every session if none is named. --all makes it hear every session, including ones created later. While it runs, rex.session.attach and rex.session.detach change what it hears. Such a script runs until it calls rex.stop(CODE), and rex do exits with CODE, or 0 if it is left out. The script also stops, with status 0, when --for DURATION has passed, or when the last session it hears is destroyed, unless it hears every session. Ctrl-C stops it with status 130. A failed handler is reported and the script keeps going. With --fail-fast, the first failure exits with status 1. Any script can pause. rex.sleep(SECONDS) waits for time to pass, and rex.wait(EVENT, FILTER, TIMEOUT) waits for an event, such as the exit of a command the script started. Nothing else in the script runs meanwhile. --for does not interrupt a pause at the top level of the script. Arguments are KEY=VALUE pairs typed against the action's schema. Use --args to pass a JSON object instead, or @FILE to read one, or - for stdin. A script reads its arguments from rex.args. Scripts have no schema, so each value's type comes from how it is written: n=2 is a number and name=x is a string. A client action needs a client. Use -C to name one, or REX_CLIENT. With neither, the app client is used when exactly one is connected. An init.lua action's queued client actions go to the same client, and so do a script's calls to rex.client.queue. Flag Description --args JSON Pass the arguments as a JSON object (@FILE, or - for stdin). -s, --session string Scope to session SESSION. -b, --block BLOCK Apply the action to BLOCK (default: the focused block). --origin ORIGIN Report the origin as ORIGIN: key, palette, api, or cli. Defaults to cli. -e, --eval CODE Run CODE as a Lua script. -a, --action NAME Run the action NAME that the script defines. --all Listen for events from every session. --for DURATION Stop listening for events after DURATION. --fail-fast Exit at the first rex.on handler that fails. --json Print the result as JSON."},{"href":"/reference/cli#rex-send-text","page":"CLI Commands","heading":"rex send [TEXT]","text":"Send input to a terminal Flag Description -b, --block string Send to block BLOCK. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-send-key-key","page":"CLI Commands","heading":"rex send-key KEY...","text":"Send key presses to a terminal Each KEY uses Rex's key binding syntax. Join modifiers with +, as in ctrl+c or shift+left. A bracketed name such as [Slash] selects a physical key. By default, Rex sends a press followed by a release for each key. Use --action to send one action instead. Rex interprets printable keys using a US keyboard layout because send-key does not receive physical keyboard input. The generated text and physical key may differ on other layouts. Use rex send when exact text matters. Flag Description -b, --block string Send to block BLOCK. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. --action ACTION Send one ACTION for each key: press, release, or repeat."},{"href":"/reference/cli#rex-capture","page":"CLI Commands","heading":"rex capture","text":"Print a terminal's screen and scrollback. Print the screen and scrollback of a terminal block. When --block is omitted, Rex uses REX_BLOCK if it names a live block. Otherwise, it uses the focused block of the default window. Text is the default format. HTML preserves rendered styling. VT preserves terminal control sequences. Output always ends with a newline. A terminal keeps about 64 MiB of scrollback. That is roughly 100,000 lines of plain text at 80 columns, and fewer in a wider terminal or with styled output. Once the limit is reached, the oldest lines are dropped. Flag Description -b, --block BLOCK Select block BLOCK. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. --format FORMAT Output FORMAT: text, html, or vt. Defaults to text. --trim Remove trailing blank lines. --unwrap Join soft-wrapped lines."},{"href":"/reference/cli#rex-kill-session","page":"CLI Commands","heading":"rex kill SESSION...","text":"Kill sessions."},{"href":"/reference/cli#layout","page":"CLI Commands","heading":"Layout","text":""},{"href":"/reference/cli#rex-focus-targetdirection","page":"CLI Commands","heading":"rex focus TARGET|DIRECTION","text":"Focus a block or window. The directions left, right, up, and down move between blocks by position. Next and prev move through the blocks in layout order. Focus stops at the edge of the layout or the end of the order. Use --wrap to continue from the other side. A target can be a block or window ID, label, or ID suffix. A positive integer selects a window by its one-based position. If a target matches both a block and a window, use a full ID to select the intended resource. Flag Description -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. --wrap Continue from the other side at the edge."},{"href":"/reference/cli#rex-zoom-block","page":"CLI Commands","heading":"rex zoom [BLOCK]","text":"Zoom a block to fill its window. Zoom changes only how the block is displayed. It does not change the underlying window layout. Without --on or --off, the command toggles the current zoom state. Use --on or --off to set the state explicitly. BLOCK may be passed as an argument or with --block. If BLOCK is omitted, Rex uses REX_BLOCK when it identifies a live block. Otherwise, Rex uses the focused block in the default window. Flag Description -b, --block BLOCK Select block BLOCK. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. --on Enable zoom. --off Disable zoom."},{"href":"/reference/cli#rex-move-block-direction-anchor","page":"CLI Commands","heading":"rex move [BLOCK] DIRECTION ANCHOR","text":"Move a block next to another. Move a block next to an anchor block. DIRECTION selects BLOCK's new position relative to ANCHOR. It must be right, left, above, or below. The two blocks may be in different windows, but both must be visible and belong to the same session. If BLOCK is omitted, Rex uses REX_BLOCK when it identifies a visible block. Otherwise, Rex uses the focused block in the default window. The --ratio flag sets the percentage of the resulting split assigned to BLOCK. PCT must be between 1 and 99. If a block label is also a direction word, identify the block by its full block ID or a hexadecimal ID suffix. Flag Description --ratio PCT Give the moved block PCT percent of the split. Defaults to 50. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-swap-block-other","page":"CLI Commands","heading":"rex swap BLOCK OTHER","text":"Swap two blocks. Swap the layout positions of two visible blocks. The blocks may be in different windows, but they must belong to the same session. Rex resolves both block arguments before changing the layout. The surrounding layout is unchanged. Flag Description -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-resize-block","page":"CLI Commands","heading":"rex resize [BLOCK]","text":"Resize a block."},{"href":"/reference/cli#resources","page":"CLI Commands","heading":"Resources","text":""},{"href":"/reference/cli#rex-session","page":"CLI Commands","heading":"rex session","text":"Manage sessions."},{"href":"/reference/cli#rex-session-new-label----command","page":"CLI Commands","heading":"rex session new [LABEL] [-- COMMAND...]","text":"Create a session. Flag Description -c, --cwd string Working directory for the terminal. --keep-open Keep the terminal open after its command exits. --shell SHELL Run COMMAND through SHELL's login environment (none starts it directly). --interactive Also read the shell's interactive startup files, such as ~/.zshrc. --window string Label for the first window. --json Output the created session as JSON."},{"href":"/reference/cli#rex-session-ls","page":"CLI Commands","heading":"rex session ls","text":"List sessions. Flag Description --json Output sessions as JSON. -q, --quiet Print IDs only, one per line. --short Show short IDs (--short=false shows full IDs). Defaults to true."},{"href":"/reference/cli#rex-session-inspect-session","page":"CLI Commands","heading":"rex session inspect [SESSION]","text":"Inspect a session. Flag Description --json Output session information as JSON."},{"href":"/reference/cli#rex-session-rename-session-label","page":"CLI Commands","heading":"rex session rename [SESSION] LABEL","text":"Rename a session."},{"href":"/reference/cli#rex-session-move-session---before-s----after-s----index-n","page":"CLI Commands","heading":"rex session move [SESSION] (--before S | --after S | --index N)","text":"Move a session to a new place in the session order. The session order is the order that session ls prints. It also decides which session a number names, so after a move, \"rex session inspect 1\" shows whichever session is now first. Exactly one of --before, --after, or --index is required. Positions count from one. An index past the end moves the session last. Moving a session to where it already is does nothing and succeeds. If SESSION is omitted, Rex uses REX_SESSION when it is set. Otherwise, Rex uses the default session. Flag Description --before S Move immediately before session S. --after S Move immediately after session S. --index N Move to one-based position N."},{"href":"/reference/cli#rex-session-kill-session","page":"CLI Commands","heading":"rex session kill SESSION...","text":"Kill sessions."},{"href":"/reference/cli#rex-window","page":"CLI Commands","heading":"rex window","text":"Manage windows."},{"href":"/reference/cli#rex-window-new--s-session-label----command","page":"CLI Commands","heading":"rex window new [-s SESSION] [LABEL] [-- COMMAND...]","text":"Create a window in the selected session. The new window becomes active by default. Use --focus=false to keep the active window and focused block unchanged. If the session has no active window, the new window becomes active even with --focus=false. Flag Description -s, --session string Scope to session SESSION. -c, --cwd string Working directory for the terminal. --keep-open Keep the terminal open after its command exits. --shell SHELL Run COMMAND through SHELL's login environment (none starts it directly). --interactive Also read the shell's interactive startup files, such as ~/.zshrc. --focus Make the new window active (--focus=false leaves the current window active). Defaults to true. --json Output the created window as JSON."},{"href":"/reference/cli#rex-window-ls","page":"CLI Commands","heading":"rex window ls","text":"List windows. Flag Description -s, --session string Scope to session SESSION. -A, --all List windows in every session. --json Output windows as JSON. -q, --quiet Print IDs only, one per line. --short Show short IDs (--short=false shows full IDs). Defaults to true."},{"href":"/reference/cli#rex-window-inspect-window","page":"CLI Commands","heading":"rex window inspect [WINDOW]","text":"Inspect a window. Flag Description -s, --session string Scope to session SESSION. --json Output window information as JSON."},{"href":"/reference/cli#rex-window-focus-windownextprev","page":"CLI Commands","heading":"rex window focus [WINDOW|next|prev]","text":"Focus a window by ID, label, position, or ID suffix. If WINDOW is omitted, focus the window containing REX_BLOCK when available. Otherwise, focus the active window in the default session. Use next or prev to move through window order. Flag Description -s, --session string Scope to session SESSION. --wrap Wrap next and prev at the ends."},{"href":"/reference/cli#rex-window-move-window---before-w----after-w----index-n--s-session","page":"CLI Commands","heading":"rex window move [WINDOW] (--before W | --after W | --index N) [-s SESSION]","text":"Move a window within its session's tab order. Exactly one of --before, --after, or --index is required. Window positions are numbered from one in the same order shown by window ls. An index past the end moves the window last. If WINDOW is omitted, Rex uses the window containing REX_BLOCK when available. Otherwise, Rex uses the active window in the default session. Flag Description --before W Move immediately before window W. --after W Move immediately after window W. --index N Move to one-based position N. -s, --session string Scope to session SESSION."},{"href":"/reference/cli#rex-window-rename-window-label","page":"CLI Commands","heading":"rex window rename [WINDOW] LABEL","text":"Rename a window. Flag Description -s, --session string Scope to session SESSION."},{"href":"/reference/cli#rex-window-close-window","page":"CLI Commands","heading":"rex window close WINDOW...","text":"Close windows. Flag Description -s, --session string Scope to session SESSION."},{"href":"/reference/cli#rex-block","page":"CLI Commands","heading":"rex block","text":"Manage blocks."},{"href":"/reference/cli#rex-block-new-flavor","page":"CLI Commands","heading":"rex block new FLAVOR","text":"Create a block."},{"href":"/reference/cli#rex-block-ls","page":"CLI Commands","heading":"rex block ls","text":"List blocks. Flag Description -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. -A, --all List blocks in every session. --json Output blocks as JSON. -q, --quiet Print IDs only, one per line. --short Show short IDs (--short=false shows full IDs). Defaults to true."},{"href":"/reference/cli#rex-block-inspect-block","page":"CLI Commands","heading":"rex block inspect [BLOCK]","text":"Inspect a block. Flag Description -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. --json Output block information as JSON."},{"href":"/reference/cli#rex-block-focus-block","page":"CLI Commands","heading":"rex block focus [BLOCK]","text":"Focus a block by ID, label, or ID suffix. If BLOCK is omitted, focus the block named by REX_BLOCK when available. Otherwise, focus the current block in the default window. Flag Description -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-block-types","page":"CLI Commands","heading":"rex block types","text":"List block types. Flag Description --json Output block types as JSON."},{"href":"/reference/cli#rex-block-send-text","page":"CLI Commands","heading":"rex block send [TEXT]","text":"Send input to a terminal Flag Description -b, --block string Send to block BLOCK. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-block-send-key-key","page":"CLI Commands","heading":"rex block send-key KEY...","text":"Send key presses to a terminal Each KEY uses Rex's key binding syntax. Join modifiers with +, as in ctrl+c or shift+left. A bracketed name such as [Slash] selects a physical key. By default, Rex sends a press followed by a release for each key. Use --action to send one action instead. Rex interprets printable keys using a US keyboard layout because send-key does not receive physical keyboard input. The generated text and physical key may differ on other layouts. Use rex send when exact text matters. Flag Description -b, --block string Send to block BLOCK. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. --action ACTION Send one ACTION for each key: press, release, or repeat."},{"href":"/reference/cli#rex-block-capture","page":"CLI Commands","heading":"rex block capture","text":"Print a terminal's screen and scrollback. Print the screen and scrollback of a terminal block. When --block is omitted, Rex uses REX_BLOCK if it names a live block. Otherwise, it uses the focused block of the default window. Text is the default format. HTML preserves rendered styling. VT preserves terminal control sequences. Output always ends with a newline. A terminal keeps about 64 MiB of scrollback. That is roughly 100,000 lines of plain text at 80 columns, and fewer in a wider terminal or with styled output. Once the limit is reached, the oldest lines are dropped. Flag Description -b, --block BLOCK Select block BLOCK. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. --format FORMAT Output FORMAT: text, html, or vt. Defaults to text. --trim Remove trailing blank lines. --unwrap Join soft-wrapped lines."},{"href":"/reference/cli#rex-block-zoom-block","page":"CLI Commands","heading":"rex block zoom [BLOCK]","text":"Zoom a block to fill its window. Zoom changes only how the block is displayed. It does not change the underlying window layout. Without --on or --off, the command toggles the current zoom state. Use --on or --off to set the state explicitly. BLOCK may be passed as an argument or with --block. If BLOCK is omitted, Rex uses REX_BLOCK when it identifies a live block. Otherwise, Rex uses the focused block in the default window. Flag Description -b, --block BLOCK Select block BLOCK. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. --on Enable zoom. --off Disable zoom."},{"href":"/reference/cli#rex-block-move-block-direction-anchor","page":"CLI Commands","heading":"rex block move [BLOCK] DIRECTION ANCHOR","text":"Move a block next to another. Move a block next to an anchor block. DIRECTION selects BLOCK's new position relative to ANCHOR. It must be right, left, above, or below. The two blocks may be in different windows, but both must be visible and belong to the same session. If BLOCK is omitted, Rex uses REX_BLOCK when it identifies a visible block. Otherwise, Rex uses the focused block in the default window. The --ratio flag sets the percentage of the resulting split assigned to BLOCK. PCT must be between 1 and 99. If a block label is also a direction word, identify the block by its full block ID or a hexadecimal ID suffix. Flag Description --ratio PCT Give the moved block PCT percent of the split. Defaults to 50. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-block-swap-block-other","page":"CLI Commands","heading":"rex block swap BLOCK OTHER","text":"Swap two blocks. Swap the layout positions of two visible blocks. The blocks may be in different windows, but they must belong to the same session. Rex resolves both block arguments before changing the layout. The surrounding layout is unchanged. Flag Description -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-block-stream-block-data_ref","page":"CLI Commands","heading":"rex block stream BLOCK [DATA_REF]","text":"Stream block data."},{"href":"/reference/cli#rex-block-call-method-args","page":"CLI Commands","heading":"rex block call METHOD [ARGS]","text":"Call a block method. Call one method exposed by a block. METHOD is the block method name. ARGS must be a JSON object. Pass the object directly, use @path to read it from a file, or use - to read it from stdin. When ARGS is omitted, the request sends no arguments. When --block is omitted, Rex uses REX_BLOCK if it names a live block. Otherwise, it uses the focused block of the default window. Method results are JSON. Output written to a terminal uses two-space indentation. Redirected output stays on one line for use in scripts. Use rex block inspect to list methods and rex block inspect --json to view their schemas. Flag Description -b, --block BLOCK Select block BLOCK. -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-block-rename-block-label","page":"CLI Commands","heading":"rex block rename [BLOCK] LABEL","text":"Rename a block. Flag Description -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-block-close-block","page":"CLI Commands","heading":"rex block close BLOCK...","text":"Close blocks. Flag Description -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW."},{"href":"/reference/cli#rex-client","page":"CLI Commands","heading":"rex client","text":"Manage clients. Inspect the clients connected to the server. A client is one program connected to the server, such as Rex.app or this command. Each client says what it is when it connects, and a person may give it a label."},{"href":"/reference/cli#rex-client-ls","page":"CLI Commands","heading":"rex client ls","text":"List clients. List the clients connected to the server. Each client says what it is when it connects. The label is the name a person gave it, and two clients may share one. Command line clients such as this one are hidden unless --all is given. With --json, the output is the server's full reply, including command line clients. Use -s to list only the clients attached to one session. Flag Description -s, --session string Scope to session SESSION. --all Include command line clients. --json Output clients as JSON. -q, --quiet Print IDs only, one per line. --short Show short IDs (--short=false shows full IDs). Defaults to true."},{"href":"/reference/cli#rex-client-inspect-client","page":"CLI Commands","heading":"rex client inspect [CLIENT]","text":"Inspect a client. Show one client and the actions it offers. CLIENT is a client ID, label, or ID suffix. With no CLIENT, -C or REX_CLIENT names the client, and otherwise the app client is used when exactly one is connected. Flag Description --json Output the client and its actions as JSON."},{"href":"/reference/cli#automation","page":"CLI Commands","heading":"Automation","text":""},{"href":"/reference/cli#rex-events","page":"CLI Commands","heading":"rex events","text":"Stream server events as they occur. Without --session, events follows session creation, removal, state changes, and server connectivity for the whole server. Use --session to follow one session, including custom events emitted by its blocks. Use --all to follow every server event, including clients joining, leaving, and changing. The default format writes one human-readable line per event. With --json, each line contains the same event JSON object sent by the server, compacted onto one line. Output is flushed after every event so pipelines can process events as they arrive. The command runs until interrupted or the server disconnects. --timeout limits connecting and starting event delivery. It does not limit how long events are streamed. Flag Description -s, --session string Scope to session SESSION. --all Follow every server event, including clients joining, leaving, and changing. --json Output events as newline-delimited JSON."},{"href":"/reference/cli#rex-wait-target","page":"CLI Commands","heading":"rex wait TARGET","text":"Wait for a block, window, or session to end. For a terminal block, wait returns the child process's exit status. It does not wait for the block itself to close, so it also works with terminals created with --keep-open. Waiting for any other block, a window, or a session succeeds when that resource closes or is destroyed. If a terminal closes before reporting a child exit, wait returns 1. By default, wait runs until the target ends or the server disconnects. Use --for to limit the wait. The global --timeout still limits connections and individual API requests, but it does not limit event delivery. With --json, the command writes the payload that ended the wait as one line of JSON. If Rex cannot determine a terminal child's exit status, wait returns 1. Statuses above 255 are reduced modulo 256 to match shell behavior. Targets may be IDs, labels, ID suffixes, or a window's one-based position. If a target matches more than one resource kind, use a full ID to select the intended resource. Flag Description -s, --session string Scope to session SESSION. -w, --window string Scope to window WINDOW. --for DURATION Give up after DURATION. --json Output the terminating payload as JSON."},{"href":"/reference/cli#rex-api","page":"CLI Commands","heading":"rex api","text":"Inspect and call the server API. Inspect and call methods exposed by the server's control API. Use call to run a method by name. Use list to discover the methods exposed by the connected server, including methods provided by block creators. Use describe to read one method's schemas, examples, and validation rules."},{"href":"/reference/cli#rex-api-call-method-args","page":"CLI Commands","heading":"rex api call METHOD [ARGS]","text":"Call a server API method. Call one method exposed by the server's control API. METHOD is the API method name, such as server.status. ARGS must be a JSON object. Pass the object directly, use @path to read it from a file, or use - to read it from stdin. When ARGS is omitted, the request sends no arguments. Methods that take a session_id require the connection to attach to that session. Use --session to resolve a session by ID or label and attach before the request. The command adds session_id to ARGS when it is missing. If ARGS already contains session_id, the value is preserved and must match the selected session. Without --session, a top-level session_id in ARGS is used for the attachment. Method results are JSON. Output written to a terminal uses two-space indentation. Redirected output stays on one line for use in scripts. Flag Description -s, --session string Scope to session SESSION."},{"href":"/reference/cli#rex-api-list","page":"CLI Commands","heading":"rex api list","text":"List server API methods. List methods exposed by the connected server. The default output has separate tables for regular API methods and methods provided by block creators. Use --quiet to print only method names, one per line. Use --json to print the complete help document returned by the server. Flag Description -q, --quiet Print method names only. --json Output the API help document as JSON."},{"href":"/reference/cli#rex-api-describe-method","page":"CLI Commands","heading":"rex api describe METHOD","text":"Describe a server API method. Describe one method exposed by the connected server. The default output includes the method's description and any available schemas, examples, and validation rules. Use --json to print the method's entry from the server's help document. Flag Description --json Output the method description as JSON."},{"href":"/reference/cli#rex-actions","page":"CLI Commands","heading":"rex actions","text":"List the actions a client or the server offers. List the actions that a client or the server offers. With -C or REX_CLIENT, this lists the actions that client offers, as it most recently reported them to the server. Otherwise it lists the actions the server offers: those that init.lua defines with rex.action, followed by those of every block type. A key binding can name any action in either list. The ARGS column lists each action's arguments. An argument followed by ? is optional. Flag Description --json Output actions as JSON."},{"href":"/reference/cli#server","page":"CLI Commands","heading":"Server","text":""},{"href":"/reference/cli#rex-server","page":"CLI Commands","heading":"rex server","text":"Manage the Rex server."},{"href":"/reference/cli#rex-server-run","page":"CLI Commands","heading":"rex server run","text":"Run a foreground server. Run a Rex server in the foreground until it is stopped. Ctrl+C, SIGTERM, and rex server stop end the server after it saves its sessions. On Unix, what SIGHUP does depends on how the server runs. A server in a terminal saves and stops, because closing a terminal sends SIGHUP. A server with no terminal, such as one that systemd, launchd, or a container runtime runs, logs the signal and keeps running. A server started under nohup keeps ignoring the signal. A server that rex server start launched reopens its log file instead. See rex server logs --help. Flag Description --listen ENDPOINT Listen on ENDPOINT (default: the local Rex Unix socket, or the Rex named pipe on Windows). --extra-listen ENDPOINT Also listen on unix, http, tailnet, tailnet-serve, or login-server ENDPOINT (repeatable, but not tailnet-serve or login-server on Windows). --socket-mode MODE Set Unix socket permissions to MODE, owner and group bits only (default 0600, not available on Windows). --allow-anonymous Admit anonymous callers on plain TCP listeners (every connection gets full access). --tailnet-allow-identified Admit every identified caller through the host's tailscaled (default: node owner). --tailscaled-socket PATH Use the tailscaled LocalAPI socket at PATH (default: platform socket). --tailscale-allow-tailnet-members Allow any tailnet member permitted by ACLs to reach Rex. --exit-empty Stop when no sessions or control connections remain. --websocket-origin-pattern PATTERN Allow browser WebSockets from cross-origin PATTERN (repeatable). --block-dir DIR Discover executable block plugins in DIR (repeatable). --block-plugin-reload Reload block plugins after source changes. --state-dir DIR Store persistent server state below DIR. --restore Restore the saved sessions (false starts without them and saves nothing). Defaults to true. --config PATH Read the config file at PATH (default: REX_CONFIG, else rex/init.lua in the config directory). --max-sessions N Refuse new sessions once the server holds N (0 for no limit). Defaults to 64. --max-blocks N Refuse new blocks once the server holds N across all sessions (0 for no limit). Defaults to 1024. --max-windows N Refuse new windows once a session holds N (0 for no limit). Defaults to 64. --max-panes N Refuse new panes once a window holds N (0 for no limit). Defaults to 64. --tailscale Also serve the API over the tailnet as an embedded tsnet node. --tailscale-hostname NAME Tailnet device NAME for --tailscale. Defaults to rex. --tailscale-control-url URL Register the tailnet node with the control server at URL, such as Headscale. --tailscale-auth-key-file FILE Enroll --tailscale with the one-time auth key read from FILE ('-' for stdin). --tailscale-advertise-tag TAG Advertise ACL TAG (tag:name) when --tailscale enrolls the node (repeatable). --log-format FORMAT Write server logs as FORMAT: text or json. Defaults to text."},{"href":"/reference/cli#rex-server-start","page":"CLI Commands","heading":"rex server start","text":"Start a persistent server. Start is idempotent. Start options apply only when a new server is launched. An existing server retains its active configuration until explicitly restarted. The log size limit applies only to the file created for a detached server. Rex clears that file before a write would exceed --log-max-size. Flag Description --extra-listen ENDPOINT Also listen on unix, http, tailnet, tailnet-serve, or login-server ENDPOINT (repeatable, but not tailnet-serve or login-server on Windows). --socket-mode MODE Set Unix socket permissions to MODE, owner and group bits only (default 0600, not available on Windows). --allow-anonymous Admit anonymous callers on plain TCP listeners (every connection gets full access). --tailnet-allow-identified Admit every identified caller through the host's tailscaled (default: node owner). --tailscaled-socket PATH Use the tailscaled LocalAPI socket at PATH (default: platform socket). --websocket-origin-pattern PATTERN Allow browser WebSockets from cross-origin PATTERN (repeatable). --block-dir DIR Discover executable block plugins in DIR (repeatable). --block-plugin-reload Reload block plugins after source changes. --state-dir DIR Store persistent server state below DIR. --restore Restore the saved sessions (false starts without them and saves nothing). Defaults to true. --config PATH Read the config file at PATH (default: REX_CONFIG, else rex/init.lua in the config directory). --max-sessions N Refuse new sessions once the server holds N (0 for no limit). Defaults to 64. --max-blocks N Refuse new blocks once the server holds N across all sessions (0 for no limit). Defaults to 1024. --max-windows N Refuse new windows once a session holds N (0 for no limit). Defaults to 64. --max-panes N Refuse new panes once a window holds N (0 for no limit). Defaults to 64. --tailscale-allow-tailnet-members Allow any tailnet member permitted by ACLs to reach Rex. --tailscale Also serve the API over the tailnet as an embedded tsnet node. --tailscale-hostname NAME Tailnet device NAME for --tailscale. Defaults to rex. --tailscale-control-url URL Register the tailnet node with the control server at URL, such as Headscale. --tailscale-auth-key-file FILE Enroll --tailscale with the one-time auth key read from FILE. --tailscale-advertise-tag TAG Advertise ACL TAG (tag:name) when --tailscale enrolls the node (repeatable). --log-format FORMAT Write server logs as FORMAT: text or json. Defaults to text. --log-max-size SIZE Limit the active detached log to SIZE by clearing old contents (0 disables). Defaults to 50MiB. --env NAME Pass environment variable NAME or NAME=VALUE to a new server (repeatable). --json Output server information as JSON."},{"href":"/reference/cli#rex-server-status","page":"CLI Commands","heading":"rex server status","text":"Show server status. Flag Description --json Output server information as JSON."},{"href":"/reference/cli#rex-server-logs","page":"CLI Commands","heading":"rex server logs","text":"Show detached server logs. Show logs captured for a server launched by rex server start. On Windows, the server that rex server install runs as a Windows Service writes to this log too, so this command shows its output as well. As with rex server start, Rex clears that log before it grows past 50MiB. Servers started by other service managers, such as systemd or launchd, log through that manager instead. For a personal server run by the systemd user unit that the Linux packages and the home-manager module install, run journalctl --user -u rex. For the shared server that the Linux packages and the NixOS module install, run journalctl -u rex.service. On Unix, rename/create rotation must send SIGHUP to the server after creating the replacement file. Only a server that rex server start launched reopens its log on SIGHUP. See rex server run --help for what the signal does to other servers. Windows uses copy-truncate or --log-max-size. --follow continues across log rotation. Flag Description --follow Follow appended log output until the server stops. --state-dir DIR Read server logs below DIR. -n, --lines N Show the newest N log lines. Defaults to 200. --all Show the entire available detached log."},{"href":"/reference/cli#rex-server-tailscale","page":"CLI Commands","heading":"rex server tailscale","text":"Manage the server's Tailscale node."},{"href":"/reference/cli#rex-server-tailscale-on","page":"CLI Commands","heading":"rex server tailscale on","text":"Serve the API over the tailnet. On is idempotent and runtime-only: a restarted server returns to its start flags. To keep Tailscale on, start the server with --tailscale. On Windows, \"rex server install -- --tailscale\" does that each time the server starts. A node enabled for the first time begins Tailscale's normal authentication flow; the login URL appears in the server log and in rex server status. A one-time auth key enrolls a new node without that browser flow: pass --auth-key-file with a path, or '-' to read the key from stdin. There is deliberately no way to pass the key itself on the command line, where it would be visible in the process list. The key is needed only for a node's first enrollment — the enrolled identity is kept on the server — and is ignored while the node is already enabled. ACL tags given with --advertise-tag are applied the same way: only when this call enrolls the node for the first time. --control-url registers a node this call starts with another control server, such as Headscale. Each control server keeps its own saved login, so a node moved to a new one signs in again. To move a node that is already on, turn it off first. The server's --tailscale-control-url is used when this flag is left out. Flag Description --hostname NAME Tailnet device NAME when this call starts the node (defaults to the server's configured name). --allow-tailnet-members Allow any caller admitted by the tailnet instead of only this node's owner. --auth-key-file FILE Enroll a new node with the one-time tailscale auth key read from FILE ('-' for stdin). --advertise-tag TAG Advertise ACL TAG (tag:name) when this call enrolls a new node (repeatable). --control-url URL Register a node this call starts with the control server at URL, such as Headscale. --json Output the resulting state as JSON."},{"href":"/reference/cli#rex-server-tailscale-off","page":"CLI Commands","heading":"rex server tailscale off","text":"Stop serving the API over the tailnet. Running 'off' more than once succeeds. Tailnet connections close while sessions keep running. Keeps you signed in. The next 'on' reconnects without signing in. Use 'logout' to forget this device. Flag Description --json Output the resulting state as JSON."},{"href":"/reference/cli#rex-server-tailscale-logout","page":"CLI Commands","heading":"rex server tailscale logout","text":"Sign out of Tailscale and stop tailnet serving. Sign out of the enabled Tailscale node and stop tailnet serving. The next 'on' must sign in again. If already off, this leaves the saved login unchanged. Flag Description --json Output the resulting state as JSON."},{"href":"/reference/cli#rex-server-tailscale-login","page":"CLI Commands","heading":"rex server tailscale login","text":"Request a Tailscale sign-in link. Request a sign-in link for the enabled Tailscale node. Tailscale can take a few seconds to produce the link. If none is shown, run the command again or check rex server status. Flag Description --json Output the resulting state as JSON."},{"href":"/reference/cli#rex-server-tailscale-rename-name","page":"CLI Commands","heading":"rex server tailscale rename NAME","text":"Rename the enabled Tailscale node without restarting or signing in again. NAME must be one DNS label of 1 to 63 ASCII letters, digits, or hyphens, with no leading or trailing hyphen. The new DNS name may appear after this command returns. Flag Description --json Output the resulting state as JSON."},{"href":"/reference/cli#rex-server-tailscale-pair","page":"CLI Commands","heading":"rex server tailscale pair","text":"Show a QR code to pair another device. Show the server's tailnet address as a QR code to scan with Rex on another device. The code holds only the address, not a credential. The other device must be on the same tailnet. Tailscale must be on and its HTTPS address ready. Flag Description --json Output the address as JSON."},{"href":"/reference/cli#rex-server-stop","page":"CLI Commands","heading":"rex server stop","text":"Stop the server. Stopping the server ends every program running in its sessions. With --if-idle, the server stops only if it has no sessions and no other client is connected. Otherwise it keeps running, and the command says what is using it and exits with status 1. A client that connects at the same moment is refused, so it never starts work that the stop would end. With --discard, the command also deletes the server's saved sessions once it has stopped, so the next start begins with none. If no server is running, it only deletes them. If the stop is refused, nothing is deleted. Pass --state-dir if the server was started with one. --if-idle needs a server recent enough to support it. With an older server, the command leaves the server running and exits with status 4. Without --if-idle, rex server stop works with a server of any version. Flag Description --discard Delete the saved sessions once the server has stopped. --if-idle Stop only if the server has no sessions and no other connected clients. --state-dir DIR Find the saved sessions for --discard below DIR."},{"href":"/reference/cli#rex-server-install-flags-sshuserhostport----server-flag","page":"CLI Commands","heading":"rex server install [flags] [ssh://[USER@]HOST[:PORT]] [-- SERVER-FLAG...]","text":"Start your server at boot and keep it running. Set up your personal Rex server to start on its own. By default, it starts when the machine boots and keeps running while you are logged out. On Linux, this enables rex.socket, a systemd user unit that starts the server the first time a rex command connects. It also turns on lingering for your account with \"loginctl enable-linger\". Lingering keeps your systemd user manager, and the server with it, running while you are logged out. No sudo is needed. If you run the rex that a Rex package installed, the package's units are enabled as they are. For any other rex, copies of the units that run that binary are written to ~/.config/systemd/user. Each command run and each file written is printed first. With --user, the server starts when you log in and stops when you log out, and lingering is left as it is. If a rex command already started a server, that server keeps its sessions. An idle server is stopped so that the unit or service takes over right away. A server with sessions keeps running, and the unit or service starts at the next boot or login instead. If the system does not let you turn on lingering, the server still starts when you log in, and the command says how an administrator can turn lingering on. If ~/.config/systemd/user already has a rex.service or rex.socket that rex did not write, such as one from the home-manager module, the command leaves it alone and fails. On Windows, this creates a Windows Service that runs \"rex server run\" as your account. It starts at boot, shortly after the other automatic services, and keeps running while nobody is signed in and after you sign out. Creating it needs administrator rights, so run the command from a terminal started with \"Run as administrator\". The command also grants your account the right to log on as a service, which Windows needs to start the service as you, and registers \"Rex Server\" as a source of the Event Log. It deletes the sign-in entry that --user adds, because only one of the two can run your server. Each change is printed first. The server and every program it starts run without administrator rights, as they would after a normal sign-in, even if your account is an administrator. The command asks for your Windows password, because Windows needs it to start the service as your account while you are not signed in. Windows keeps it with the service. Rex does not keep it. Use the password of your Windows account, not a Windows Hello PIN. If you sign in with a Microsoft account, use the password of that Microsoft account. Without a terminal, the command reads the password from its standard input. If your password changes, run the command again, or the service cannot start at the next boot. With --user on Windows, no administrator rights are needed. The command adds a value named \"Rex Server\" to HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run, which runs \"rex server start\" each time you sign in. It also starts a server now if none is running, and leaves a running server alone. The server stops when you sign out of Windows, and starts again the next time you sign in. If the value is turned off in the Startup tab of Task Manager, the command says so. While the service exists, the command fails. Run \"rex server uninstall\" first. On Windows, you can give the server flags after --. The server gets them each time it starts, so use them for settings that must last when the server restarts. For example, \"rex server tailscale on\" turns Tailscale on only until the server stops. This turns it on at every start: rex server install -- --tailscale The service runs \"rex server run\" with the flags, and the \"Rex Server\" value that --user adds runs \"rex server start\" with them. The command refuses flags that the server would refuse, and then changes nothing. It also refuses --listen, because the server must stay on the named pipe where rex commands look for it. Use --extra-listen to add another endpoint. Running the command again without -- keeps the server flags that are already set. Give new flags after -- to replace them, or -- on its own to remove them. A server that is already running keeps its old flags until it restarts. On Linux, systemd starts the server, so the command takes no server flags there. Set them in a drop-in file instead, with \"systemctl --user edit rex.service\". Running the command again is safe. Run it again after moving rex. Linux with systemd and Windows are supported so far. Elsewhere, \"rex server start\" starts a server that runs until the machine restarts. With an ssh:// endpoint, the command sets up your server on another machine that you can log in to with ssh, and installs Rex there first. It runs the Rex installer there with --user, which puts rex in ~/.local/bin and needs no sudo. As root, or with --package, it installs the Rex package for the whole machine instead, through sudo. Then it runs \"rex server install\" there, connects to the new server through ssh, and adds the machine to your hosts, so that \"rex -S LABEL\" reaches it. Only Linux machines are supported so far. Each command that runs on the other machine is printed first, and the command asks before each change. --yes answers yes to every question. Without a terminal to ask on, --yes is required. Flag Description --user Start the server when you sign in and stop it when you sign out, instead of at boot. --json Output what the command did as JSON. --package Over ssh, install the Rex package for the whole machine through sudo. --label LABEL Over ssh, add the machine to your hosts as LABEL (default its host name). --channel NAME Over ssh, install from the release channel NAME: stable, preview, or snapshot. --yes Over ssh, answer yes to every question."},{"href":"/reference/cli#rex-server-uninstall-sshuserhostport","page":"CLI Commands","heading":"rex server uninstall [ssh://[USER@]HOST[:PORT]]","text":"Stop starting your server on its own. Undo \"rex server install\". On Linux, this disables and stops rex.socket and rex.service, and removes the copies of them that rex server install wrote to ~/.config/systemd/user. Stopping the server ends every program running in its sessions. Units that a package installed stay in place, disabled. If ~/.config/systemd/user has a rex.service or rex.socket that rex did not write, such as one from the home-manager module, nothing is disabled or removed. Lingering stays on, because other services of your account may need it. Turn it off with \"loginctl disable-linger\". On Windows, this stops and deletes the Windows Service that rex server install created, which ends every program running in its sessions. It also unregisters the \"Rex Server\" source of the Event Log, unless the service of another account still uses it. Removing the service needs a terminal started with \"Run as administrator\". Your account keeps the right to log on as a service, because other services may need it. It also deletes the value named \"Rex Server\" that rex server install --user added to HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run, which needs no administrator rights. A server that the value started keeps running, along with its sessions. Stop it with \"rex server stop\". --user is accepted so that both commands take the same flags. Uninstall does the same with or without it. With an ssh:// endpoint, the command runs \"rex server uninstall\" on that machine instead, after asking. Rex stays installed there, and the machine stays in your hosts. The command says how to remove each. Flag Description --user Undo a rex server install --user. --json Output what the command did as JSON. --yes Over ssh, answer yes to every question."},{"href":"/reference/cli#rex-config","page":"CLI Commands","heading":"rex config","text":"Work with Rex's config file. Rex reads one configuration file per account, init.lua. It binds keys with rex.bind and takes them away with rex.unbind. The file is REX_CONFIG when that variable is set. Otherwise it is rex/init.lua in the account's configuration directory, which is $XDG_CONFIG_HOME/rex or ~/.config/rex on macOS and Linux, and %AppData%\\rex on Windows."},{"href":"/reference/cli#rex-config-check-path","page":"CLI Commands","heading":"rex config check [PATH]","text":"Load the config file and report what it found. Load the config file and report where it is, whether it is there, how many keys it binds and unbinds, how many modes it uses, how many actions it defines, and everything loading it had to complain about. Without PATH the account's own init.lua is used. Only loading happens here. Some mistakes take more than the file to see, such as binding an action that neither this file nor the client has, or unbinding a key that nothing was bound to. Those depend on which client is asking, and \"rex keymap\" reports them. The file is loaded by this rex binary, so it is checked against the functions and fields this version of Rex knows. A server running a different version may read it differently. \"rex keymap\" asks the running server instead. The exit status is 1 when any complaint is an error. Flag Description --json Output the result as JSON."},{"href":"/reference/cli#rex-config-reload","page":"CLI Commands","heading":"rex config reload","text":"Make the server read the config file again and tell every client connected to it that their keys may have changed. The generation counts the times this server has read the file. A client compares it against its own to notice that its keymap is out of date. It advances on every read, including one that failed. A read that fails keeps the keys of the last one that worked. Only the file's own complaints are reported here. For the ones that take a client's own bindings to find, use \"rex keymap\". This runs on the server's own machine, over its local socket. Whoever can edit the file is already there, and a remote caller is refused. The exit status is 1 when any complaint is an error. Flag Description --json Output the result as JSON."},{"href":"/reference/cli#rex-keymap","page":"CLI Commands","heading":"rex keymap","text":"Show what each key does, as the server resolves it. The answer is the config file's bindings stacked over the bindings the asking client ships with, and under the shortcuts recorded in that client's own settings. Without --request this command sends no layers and declares no actions of its own. The output is then the config file's bindings alone, and a binding that names an action is not checked against any client's list of actions. Pass a client's own request to see the keymap that client receives. If that request sets server_actions, the output lists the same actions the client receives. Bindings are listed with the top of each key's stack first, which is the order a client tries them in. A binding in a mode is listed as the config file writes it, such as resize/left, and applies only while a client has that mode active. When there are modes, a table of them follows the bindings. The exit status is 1 when any complaint is an error. Flag Description --request FILE Send the client.keymap request in FILE (\"-\" reads stdin). --json Output the response as JSON."},{"href":"/reference/cli#rex-login-server","page":"CLI Commands","heading":"rex login-server","text":"Run the multi-account login server. The login server runs as root. It identifies each caller, selects an operating system account through the accounts file, and starts or reuses a Rex server for that account. It proxies each accepted connection to the selected server. Clients select an account with https://ACCOUNT@host:port. Tailnet listeners admit every identified caller because the accounts file controls authorization. Local users use sudo to request another account. Send SIGHUP to reload the accounts file. Reloading closes revoked connections but leaves account servers and sessions running. Flag Description --listen ENDPOINT Listen on the root-only Unix ENDPOINT (default: unix://RUNTIME-ROOT/login-server.sock). --extra-listen ENDPOINT Also listen on unix, http, tailnet, or tailnet-serve ENDPOINT (repeatable). --allow-anonymous Admit anonymous callers on plain TCP listeners (every connection gets full access). --tailscaled-socket PATH Use the tailscaled LocalAPI socket at PATH (default: platform socket). --accounts FILE Map callers to accounts with the HuJSON FILE (default /etc/rex/accounts.jsonc, or /usr/local/etc/rex/accounts.jsonc on FreeBSD). --runtime-root DIR Keep the socket and accounts/ below DIR (default: $REX_RUNTIME_DIR, else /run/rex, or /var/run/rex on FreeBSD). --pam Open PAM sessions and run PAM account checks (default true; --pam=false uses shadow-based checks and no login session). --pam-service NAME Use the PAM policy for service NAME (default rex-login-server). --log-format FORMAT Write server logs as FORMAT: text or json. Defaults to text."},{"href":"/reference/cli#rex-home","page":"CLI Commands","heading":"rex home","text":"Show or choose your home server. Show or choose your home, the server this device follows for hosts and shortcuts. Your home is this machine unless you choose another. Any Rex server can be a home. A server is not told that it is someone's home, and admits you the same way either way. Without a command, connect to your home and show what it reports about you. Your choice is kept in home.json in Rex's state directory. Setting REX_HOME chooses a home for one environment and takes precedence over the file. The --server option and REX_SERVER choose the server other commands talk to. They do not change your home. Flag Description --json Output the home and your identity as JSON."},{"href":"/reference/cli#rex-home-set-endpoint","page":"CLI Commands","heading":"rex home set ENDPOINT","text":"Follow ENDPOINT for hosts and shortcuts. Make the server at ENDPOINT your home. ENDPOINT takes the same forms as --server. The server is asked for its status first. If it cannot be reached, your home is not changed, so a mistyped address or a server that is down never becomes your home. The home is named by --label, or by the endpoint's host or socket name if no label is given. Your choice is written to home.json in Rex's state directory, along with the host ID the server reports. rex home uses the host ID to warn you if the address later leads to a different machine. This command fails while REX_HOME is set, because that variable takes precedence over the file. Flag Description --label NAME Call the home NAME in output."},{"href":"/reference/cli#rex-home-unset","page":"CLI Commands","heading":"rex home unset","text":"Follow this machine again. Forget the home you chose, so this machine's own server is your home again. This removes home.json. It succeeds if no home was chosen. This command fails while REX_HOME is set, because that variable takes precedence over the file."},{"href":"/reference/cli#rex-hosts","page":"CLI Commands","heading":"rex hosts","text":"List and change the hosts you connect to. List and change your hosts, the other Rex servers you connect to by label. Your home keeps your hosts, so every device that follows the same home sees the same list. The hosts of this machine's own server always count too, whatever your home is. If a host of this machine and a host of your home have the same label, this machine's host wins. A new host goes to your home. A change to a host goes to the server the host came from. If your home is away, the change is held on this machine and sent the next time rex hosts reaches your home. Until then, the list shows the hosts your home had when it was last reached. To use a host, give its label to --server, as in \"rex -S studio session ls\". Without a command, list your hosts. The --server option and REX_SERVER do not change which servers this command talks to. Flag Description --json Output the hosts as JSON."},{"href":"/reference/cli#rex-hosts-list","page":"CLI Commands","heading":"rex hosts list","text":"List your hosts. List the hosts your home keeps, including changes held for it, and the hosts of this machine's own server. FROM says which server a host came from, and is left out when your home is this machine. SOURCE is the init.lua line that declares a host, \"stored\" for a host added with rex hosts, or \"held\" for a host not yet sent to your home. If your home is away, the list shows the hosts it had when it was last reached, and says how long ago that was. Warnings go to standard error. Flag Description --json Output the hosts as JSON."},{"href":"/reference/cli#rex-hosts-add-label-endpoint","page":"CLI Commands","heading":"rex hosts add LABEL ENDPOINT...","text":"Add a host to your home. Add a host called LABEL, reached at each ENDPOINT in turn. An endpoint takes the same forms as --server. A socket path, such as unix:///tmp/rex.sock, works only on a device where the path exists, such as the local end of an ssh -L forward. Other devices skip it and try the next endpoint. The host goes to your home, so every device that follows your home sees it. No other host of yours may have the same label, ignoring case. If your home is away, the host is held on this machine and listed at once. It is sent the next time rex hosts reaches your home. Flag Description --icon NAME Show the icon NAME next to the host."},{"href":"/reference/cli#rex-hosts-edit-label","page":"CLI Commands","heading":"rex hosts edit LABEL","text":"Change a host. Change the host called LABEL, ignoring case. Give at least one option. --endpoint replaces all of the host's endpoints, and may be given more than once. The change goes to the server the host came from: your home, or this machine. Editing a host declared in init.lua stores a host that takes the declaration's place. The line stays in the file, but no longer has an effect. If the host is your home's and your home is away, the change is held on this machine and sent the next time rex hosts reaches your home. Flag Description --label NEW Rename the host to NEW. --endpoint ENDPOINT Reach the host at ENDPOINT (repeatable, replaces all endpoints). --icon NAME Show the icon NAME next to the host (empty removes it)."},{"href":"/reference/cli#rex-hosts-remove-label","page":"CLI Commands","heading":"rex hosts remove LABEL","text":"Remove a host. Remove the host called LABEL, ignoring case, from the server it came from: your home, or this machine. Removing a host declared in init.lua hides it. The line stays in the file, but no longer has an effect. If the host is your home's and your home is away, the removal is held on this machine and sent the next time rex hosts reaches your home."},{"href":"/reference/cli#rex-hosts-check-label","page":"CLI Commands","heading":"rex hosts check [LABEL...]","text":"Check which hosts can be reached from here. Try to reach each of your hosts, or only the hosts called LABEL, ignoring case. All hosts are tried at once, and each one has up to --timeout to answer. STATUS is one of: reachable a Rex server answered at ENDPOINT not reachable from here nothing answered, but the host may work from another network or device down a machine answered, but not a Rex server DETAIL says why a host could not be reached. For a reachable host, it says when the address now leads to a different machine than the one you added. The check succeeds whatever it finds. Commands that connect to a host try the address that answered first. Flag Description --json Output the results as JSON."},{"href":"/reference/cli#rex-whoami","page":"CLI Commands","heading":"rex whoami","text":"Show your identity, server, and Rex location. Flag Description --json Output identity, server, and location as JSON."},{"href":"/reference/cli#other","page":"CLI Commands","heading":"Other","text":""},{"href":"/reference/cli#rex-completion","page":"CLI Commands","heading":"rex completion","text":"Generate shell completion scripts for Rex."},{"href":"/reference/cli#rex-completion-bash","page":"CLI Commands","heading":"rex completion bash","text":"Generate Bash completion. Load it with source <(rex completion bash), or install it in a bash-completion directory."},{"href":"/reference/cli#rex-completion-zsh","page":"CLI Commands","heading":"rex completion zsh","text":"Generate Zsh completion. Install it with rex completion zsh > \"${fpath[1]}/_rex\"."},{"href":"/reference/cli#rex-completion-fish","page":"CLI Commands","heading":"rex completion fish","text":"Generate Fish completion. Install it with rex completion fish > ~/.config/fish/completions/rex.fish."},{"href":"/reference/cli#rex-completion-powershell","page":"CLI Commands","heading":"rex completion powershell","text":"Generate PowerShell completion. Add the output of rex completion powershell to your PowerShell profile."},{"href":"/reference/cli#rex-completion-nu","page":"CLI Commands","heading":"rex completion nu","text":"Generate Nushell completion. Add the output of rex completion nu to your config.nu."},{"href":"/reference/cli#rex-terminfo","page":"CLI Commands","heading":"rex terminfo","text":"Set up and report Rex's terminal type. Rex carries its own terminfo entry, xterm-rex, describing what its terminals support. A terminal claims it wherever this account is set up for it, unless REX_TERM names another entry. Run \"rex terminfo setup\" once per account, and \"rex terminfo status\" to see what this machine would decide."},{"href":"/reference/cli#rex-terminfo-status","page":"CLI Commands","heading":"rex terminfo status","text":"Report the TERM a new terminal would be given, and why. Report the TERM a new terminal would be given, what was examined to decide it, and, when this account is not set up, which command fixes it. A server claims xterm-rex wherever that is safe and keeps xterm-256color everywhere else. Naming an entry in REX_TERM overrides that and gives every terminal that name, which makes REX_TERM=xterm-256color the off switch. The value \"auto\" asks for the rule the server already follows. The report covers the terminfo entry, the ssh client, both guard files, the hosts that have been recorded, and which configuration is responsible for what ssh would actually send. Nothing of yours is changed and no host is contacted. The one file written is a throwaway copy of Rex's guard, used to ask ssh what that guard would do on its own. Flag Description --json Output the report as JSON."},{"href":"/reference/cli#rex-terminfo-setup","page":"CLI Commands","heading":"rex terminfo setup","text":"Install Rex's terminfo entry and ssh guard. Install xterm-rex where programs find it, and configure ssh so that the name never reaches a host that has never heard of it. Three things happen. The entry is installed in ~/.terminfo. A guard is written to Rex's configuration directory. One Include line is added at the top of ~/.ssh/config. ssh is then asked what it would send to a host without the entry, and the answer is reported. If your ssh configuration already includes a guard, as it does when home-manager manages it with Rex's module, the guard and the Include line are skipped, because they would add nothing. The guard needs OpenSSH 7.8 or later, because an older ssh refuses any configuration that contains it. With an older ssh, only the entry is installed, and the command fails with a message that says why. With --system the entry goes into /usr/share/terminfo and the guard into /etc/ssh/ssh_config.d/50-rex.conf, so that every account on this machine has both, root included. That needs root, and it is what makes programs started through sudo and su find the entry. On FreeBSD, where /etc/ssh/ssh_config reads no other files, it also adds an Include line for the guard's directory to the top of that file. Run this once per account. Repeating it is harmless. Two flags are meant for a program that sets an account up on someone's behalf. --entry-only installs the entry and leaves ssh configuration alone, which is the half that needs no permission. --json reports what the run did as one object instead of the lines a person reads. Installing the entry is not the whole story. A terminal claims the name once the guard is in place as well, and REX_TERM can name another entry instead. \"rex terminfo status\" reports what this machine would do. Flag Description --entry-only Install the entry alone, for a program that adds the guard later (the name is claimed only once the guard is in place too). --dry-run Report what would change without writing anything. --system Install the entry and the guard for every account on this machine. --json Output what the run did as JSON."},{"href":"/reference/cli#rex-terminfo-remove","page":"CLI Commands","heading":"rex terminfo remove","text":"Remove the ssh guard, leaving the terminfo entry installed. Remove Rex's ssh guard and the Include line that puts it into effect, so that ssh goes back to forwarding whatever TERM it is given. With --system the machine-wide guard /etc/ssh/ssh_config.d/50-rex.conf goes instead. On FreeBSD, so does the Include line the setup added to /etc/ssh/ssh_config. That needs root. The terminfo entry stays where it was installed, in both cases. Shells that are already running with TERM=xterm-rex stop working the moment the entry disappears, so there is no command that deletes it. Remove the files by hand if you really want them gone. --json reports what the run did as one object instead of the lines a person reads, in the shape the setup uses. Flag Description --dry-run Report what would change without writing anything. --system Remove the guard that every account on this machine shares. --json Output what the run did as JSON."},{"href":"/reference/cli#rex-terminfo-show","page":"CLI Commands","heading":"rex terminfo show","text":"Print the terminfo source. Compile it on another machine with: rex terminfo show &#124; tic -x -"},{"href":"/reference/cli#rex-terminfo-host","page":"CLI Commands","heading":"rex terminfo host","text":"Install Rex's terminfo entry on other machines. Install xterm-rex on a remote host and record that it is there, so that ssh stops announcing xterm-256color to it. A record is compared, exactly, against the destination as ssh resolves it, so one machine can be recorded under more than one spelling."},{"href":"/reference/cli#rex-terminfo-host-install-destination----ssh_options","page":"CLI Commands","heading":"rex terminfo host install DESTINATION [-- SSH_OPTIONS...]","text":"Install Rex's terminfo entry on a remote host. Install xterm-rex on DESTINATION over ssh, and record the host so that ssh stops announcing xterm-256color to it. The entry's source is piped to the remote tic, which compiles it into ~/.terminfo there. Nothing else on the remote host is touched, and no interactive session is started. Options after -- are passed to ssh, so \"-- -p 2222\" and \"-- -J jump.example.com\" work as usual. The entry lands in the home directory of the account you connect as, while the record is kept per host. Another account on that host will not have the entry. Use \"rex terminfo host forget\" to undo a record. What gets recorded is the spelling you typed, because that is what the guard compares against. When your ssh config rewrites it with HostName, that name is recorded as well, because a machine-wide guard sees the rewritten one. Either way, other spellings of the same machine keep the old terminal type until you install to them too."},{"href":"/reference/cli#rex-terminfo-host-forget-host","page":"CLI Commands","heading":"rex terminfo host forget HOST","text":"Forget that a host has Rex's terminfo entry. Remove HOST from the hosts file, so that ssh goes back to announcing xterm-256color to it. Use this when the entry is gone from that host, or when you connect to it as an account that does not have it. HOST is the spelling that was recorded. Run \"rex terminfo host ls\" to see them."},{"href":"/reference/cli#rex-terminfo-host-ls","page":"CLI Commands","heading":"rex terminfo host ls","text":"List the hosts recorded as having Rex's terminfo entry, one per line, in the order they were added. Each line is compared, exactly, against the destination as ssh resolves it. One machine can appear under more than one spelling, and each spelling is its own record. Flag Description --json Output the list as JSON."},{"href":"/reference/cli#rex-skill","page":"CLI Commands","heading":"rex skill","text":"Set up agents to use Rex. Set up agents such as Claude Code and Codex to use Rex. Rex installs two things into each agent. A skill teaches the agent how to use the rex command. A hook, in agents that support hooks, tells the agent at the start of each session which Rex session and terminal it is running in. Both get their text from the rex command each time the agent uses them, so upgrading Rex also updates what agents are told. Run \"rex skill setup\" once per account, and \"rex skill status\" to see what is installed."},{"href":"/reference/cli#rex-skill-status","page":"CLI Commands","heading":"rex skill status","text":"Report whether Rex's skill and hooks are installed. Report, for each agent Rex knows, whether the agent was found on this machine and whether Rex's skill and hooks are installed in it. Nothing is changed. Each item is reported as one of: current installed and up to date outdated installed by an older Rex, or edited since missing not installed foreign a file of the same name that Rex did not install unreadable the file could not be read unsupported the agent cannot hold the item here Flag Description --project Report on the current repository instead of your account. --json Print the report as JSON."},{"href":"/reference/cli#rex-skill-setup","page":"CLI Commands","heading":"rex skill setup","text":"Install Rex's skill and hooks into agents. Install Rex's skill and hooks into the agents found on this machine, or into the agents named with --agent. If no agent is found, the skill alone goes into the shared ~/.agents/skills directory that most agents read. The skill goes into each agent's skill directory. Some agents share a directory, and it is written once. Hooks are added to each agent's settings file, and the rest of the file is left as it was. Some agents ask you to approve a new hook before they run it, and the output says how. Run this once per account. Running it again is safe, and updates anything an older Rex installed. Pass --session-naming to have agents name the session after their task. A new setup leaves that off, and running setup again keeps the choice already installed unless you pass the flag. If a skill named rex is already installed and Rex did not install it, it is left alone and reported as a conflict. Use --force to replace it. Flag Description --agent AGENT Use only AGENT, which is claude, codex, opencode or pi (repeatable). --project Set up the current repository instead of your account. --dry-run Report what would change without changing anything. --force Replace a skill named rex that Rex did not install. --session-naming Tell agents to name the session after their task (omit to keep the installed choice). --json Print the result as JSON."},{"href":"/reference/cli#rex-skill-remove","page":"CLI Commands","heading":"rex skill remove","text":"Remove Rex's skill and hooks from agents. Remove Rex's skill and hooks from every agent Rex knows, or from the agents named with --agent. Agents that are no longer installed are included, so nothing Rex installed is left behind. Only files and hooks that Rex installed are removed, including older copies of the skill in other directories the agent reads. Other skills, hooks and settings are left as they were. Flag Description --agent AGENT Use only AGENT, which is claude, codex, opencode or pi (repeatable). --project Remove from the current repository instead of your account. --dry-run Report what would change without changing anything. --json Print the result as JSON."},{"href":"/reference/cli#rex-skill-show","page":"CLI Commands","heading":"rex skill show","text":"Print the guide that Rex's skill gives agents. An agent with the skill installed reads the guide through the skill. An agent without it can run this command to read the same guide."},{"href":"/reference/cli#rex-version","page":"CLI Commands","heading":"rex version","text":"Print version information. Flag Description --json Output version information as JSON."},{"href":"/reference/cli#global-options","page":"CLI Commands","heading":"Global Options","text":"Every command accepts these. Flag Description --autostart Start a missing local server. Defaults to true. -C, --client CLIENT Act on client CLIENT, given as a label or ID. --color WHEN Set color WHEN to auto, always, or never. Defaults to auto. -S, --server SERVER Connect to SERVER, a host's label or a server address (a value with a port is always an address). --timeout DURATION Limit each connection and nonstreaming request to DURATION. Defaults to 10s. -v, --verbose count Increase diagnostic verbosity (repeatable)."},{"href":"/reference/api","page":"Server API Methods","heading":"","text":"This page lists every method in the control API. See Server API for how to call them. Anything tagged Snapshot is on the snapshot channel and not yet in preview."},{"href":"/reference/api#block","page":"Server API Methods","heading":"block","text":""},{"href":"/reference/api#blockclose","page":"Server API Methods","heading":"block.close","text":"Close a block and end the program running in it. Field Required Description session_id Yes The session that holds the block. block_id The block to close. It may be a detached block. Send this or window_id, not both. window_id The window whose focused block to close. It doesn't have to be the active window. Send this or block_id, not both. keep_last If true, fail rather than close the only block left in a window, counting floating layers. Ignored for detached blocks. Send exactly one of block_id and window_id. If you send both, or neither, the call fails with bad_request. The server sends block_closed when the block closes. If it was the last block in its window, the window closes too and the server sends window_closed. If keep_last is true and the block is the only one left in its window, counting blocks in floating layers, the call fails with bad_request and closes nothing. If the block's creator is still placing it, the call returns at once and the block closes when placement finishes. On one connection, block.close waits for earlier requests that can change focus, such as session.new_split and session.focus_block, to finish. A close by window_id therefore closes the block those requests left focused. If the session doesn't exist, the call fails with session_not_found. If block_id or window_id names something that isn't in the session, or the window has no focused block, it fails with not_found."},{"href":"/reference/api#blockevent","page":"Server API Methods","heading":"block.event","text":"Send an input event, such as a key press or a paste, to a block. Field Required Description session_id Yes The session that holds the block. block_id Yes The block to send the event to. event Yes The event to send: its type, such as paste, and its payload. A client may send only key, paste, mouse, wheel, and focus events. Every other event type is sent only by the server. A paste event's payload is {\"text\": \"...\"}, and a focus event's payload is {\"action\": \"in\"} or {\"action\": \"out\"}. The event goes straight to the block, so Lua key bindings never see it. To give key bindings the first chance at a key, use client.key instead. On one connection, block.event calls run one at a time, in order with client.key, so the block receives events in the order you send them. The call returns once the block has received the event, which may be before the block has finished acting on it. The call fails with bad_request if event is missing or its payload has the wrong shape for its type. It also fails with bad_request if event.type is empty, names an event only the server sends, or names an event this server doesn't know. If the session doesn't exist, the call fails with session_not_found, and if block_id names a block that isn't in the session, it fails with not_found."},{"href":"/reference/api#blockmethod","page":"Server API Methods","heading":"block.method","text":"Call a method that a block provides and return its result. Field Required Description session_id Yes The session that holds the block. block_id Yes The block to call the method on. method Yes The block method's short name, such as write or format. args The block method's own arguments. Leaving it out is the same as an empty object. Send the block method's short name, such as write, in method. help.all lists the methods each creator's blocks provide under block_types, with each method's args_schema and output_schema. Each block method can also be called by its full name, such as com.superlogical.terminal.write, with the same session_id, block_id, and args. The full name names the block's creator, so the call works only on a block that creator made. On any other block, it fails with conflict. The result is the block method's own result, as its output_schema describes it. block.method has no result schema of its own. block.method doesn't wait for earlier requests on the same connection, and it can run at the same time as client.key. If a call depends on an earlier request, wait for that request's reply first. To call a block method in order with client.key, such as from a key binding, use client.action. If block_id is missing, the call fails with bad_request. If the session doesn't exist, it fails with session_not_found. If block_id names a block that isn't in the session, it fails with not_found, and if the block has no method named method, it fails with method_not_found. If args is missing a required field or has a value of the wrong type, it fails with bad_request."},{"href":"/reference/api#block_creator","page":"Server API Methods","heading":"block_creator","text":""},{"href":"/reference/api#block_creatorlist","page":"Server API Methods","heading":"block_creator.list","text":"List the creators the server can make blocks with, and their flavors. Creators are sorted by name. To create a block, pass a flavor's flavor to session.new_block or in the block of a layout, with options that match its options_schema. api.methods lists the methods that every block from the creator provides. Call one as <creator>.<method>, such as com.superlogical.terminal.title, with session_id, block_id, and its arguments under args. A method's actions are things a person can do with a block, such as choosing an item in Rex.app's command palette or pressing a key. Performing one calls the method with the action's args, which are fixed values, not a schema like the args that client.keymap lists for an action."},{"href":"/reference/api#client","page":"Server API Methods","heading":"client","text":""},{"href":"/reference/api#clientaction","page":"Server API Methods","heading":"client.action","text":"Perform an action from the configuration file or a block, as this client. Field Required Description action Yes The full name of the action, such as dev_layout or com.superlogical.terminal.reset. client.keymap lists them. args The action's arguments, as a JSON object. Leave it out if there are none, and always for an action a creator declares. origin What caused the request: key, palette, or api (the default). The action reads it as ctx.origin. Other values count as api, but script makes the call fail with bad_request. session_id The session the action applies to. Required for a block action. A configuration file action may leave it out, and then has no default session. block_id The block the action applies to. Defaults to the session's focused block, unless server is set. Requires session_id. servers Other servers this client is connected to, which a configuration file action can call with rex.server(name). Don't list the server you're sending this to. server The name, from servers, of the server that holds session_id and block_id. Leave it out when they're on the server you're sending this to. action decides what runs. A name without a dot, such as dev_layout, is an action that the configuration file defines with rex.action. A name with a dot, such as com.superlogical.terminal.reset, is an action or method of a block, named after its creator. client.keymap lists every name in actions. A block action, such as com.superlogical.terminal.reset, has fixed arguments, so a request for one with args fails with bad_request. A block method, such as com.superlogical.terminal.write, takes its arguments from args. If args isn't an object, the call fails with bad_request. null counts as leaving it out. The client's own actions, such as pane.split, run in the client and can't be sent here. A request for one, or for any name the server doesn't know, fails with not_found. On each connection, client.key, client.action, block.event, and the methods that change focus or the layout run one at a time, in the order they arrive. For an action bound to a key, use this method rather than calling the block method by name, such as com.superlogical.terminal.write, so the action takes effect after the keys typed before it. When session_id is set, the connection must be attached to that session, with session.attach, session.bootstrap, or session.attach_all, or the call fails with conflict. If session_id names no session, the call fails with session_not_found. Neither check applies when server is set. Send session_id with any block action or method, and whenever you send block_id, or the call fails with bad_request. A block action or method runs on block_id, or on the session's focused block if you leave that out. If the session has no focused block, the call fails with conflict. If the block isn't the kind the action or method belongs to, the call fails with conflict. If block_id names a block that isn't in the session, it fails with not_found. An action from the configuration file may leave out session_id. It then has no ctx.session_id, and its rex.session calls have no default session. With session_id, it sees block_id, or the focused block, as ctx.block_id. If origin is script, the call fails with bad_request, because only rex.invoke may report that origin. An action from the configuration file may run for 30 seconds, including time spent waiting on other servers. If it runs longer, the call fails with timeout. If the file is reloaded while it runs, the call fails with conflict. If an action from the configuration file raises an error, the call fails with a message that starts with where it happened, such as init.lua:4: attempt to index a nil value. However the action fails, part of it may already have run. Each entry in servers needs a unique, non-empty name and a non-empty id, and server must name one of them. Otherwise the call fails with bad_request. The server doesn't interpret either value. Set server when the session belongs to one of servers. The IDs then go to the action unchecked, and the focused block isn't filled in, so send block_id yourself. The action's rex.session, rex.block.call, and rex.call calls go to that server. When server is set, any name with a dot fails with bad_request, because a block action must run on the server that holds the block. Send it to that server instead. When the action calls another server, this connection receives a client_call_requested event. Make the call there and report the outcome with client.reply. If no reply comes within 10 seconds, or the reply is an error, the action's call fails and the action keeps running."},{"href":"/reference/api#clientinspect","page":"Server API Methods","heading":"client.inspect","text":"Get one connected client's details, including the actions it offers. Field Required Description client_id Yes The client to describe. It can be any client, not only the one calling. If client_id is missing or isn't a client ID, the call fails with bad_request. If it names no connected client, including one that has left, the call fails with not_found. To ask the client to perform one of the actions in actions, call client.perform."},{"href":"/reference/api#clientkey","page":"Server API Methods","heading":"client.key","text":"Send a key to a block, giving Lua key bindings the first chance at it. Field Required Description session_id Yes The session the key is typed in. key Yes The key event, as the keyboard reported it. block_id The block to send the key to. Defaults to the session's focused block. prefix The keys pressed before key when key ends a key sequence, in order. For ctrl+b then x, send x as key and ctrl+b here. The connection must be attached to session_id, with session.attach, session.bootstrap, or session.attach_all, or the call fails with conflict. If session_id names no session, the call fails with session_not_found. Without block_id, the key goes to the session's focused block. The server reads it once, before any Lua function runs, so a function that moves focus doesn't change where the key goes. Either way, Lua functions see the block the key goes to as ctx.block_id. On each connection, client.key, client.action, block.event, and the methods that change focus or the layout run one at a time, in the order they arrive. That keeps keys in the order you send them. The key goes first to the Lua functions bound to it in this client's scripts, then to those in the configuration file. If none of them takes the key, it goes to the block. Only a key bound to a Lua function reaches Lua, and only when it's pressed or repeating. Every other key goes straight to the block. client.keymap reports the bound keys as bindings for client.key.forward. If no Lua function takes the key and the session has no focused block, the key goes nowhere. The call still succeeds, with handled set to false. To send a key sequence, wait until it's complete. Then send its last key as key and the keys before it in prefix, such as [\"ctrl+b\"]. The server doesn't remember keys between requests. A key with a prefix reaches only the functions bound to that exact sequence. A key without one reaches only the functions bound to that key alone. If no function takes a key sequence, only key goes on to the block. The keys in prefix never do. A Lua function bound to a key may run for 5 seconds. If it raises an error or runs longer, the call fails and the key doesn't reach the block. If an element of prefix isn't a key the server can read, the call fails with bad_request, and the message names the element. If no Lua function takes the key and block_id names a block that isn't in the session, the call fails with not_found."},{"href":"/reference/api#clientkeymap","page":"Server API Methods","heading":"client.keymap","text":"Get what each key does for this client. The result combines the client's own bindings with the configuration file and the client's Lua scripts. Field Required Description actions The actions the client performs itself. Leave it out to skip the checks for unknown actions and wrong arguments. The server keeps the list for client.inspect and client.perform. server_actions Which of the server's actions the reply lists: all (the default), configured (only the configuration file's), or none. An unknown value means all. defaults The client's built-in layers, lowest first. They sit beneath the configuration file. Problems found only in them aren't reported, because the user can't edit them. overrides Layers of shortcuts a person recorded in the client, lowest first. They sit above the configuration file and this client's scripts. Problems in them come back as warnings. Bindings stack in layers, from bottom to top: your defaults, then the account's configuration file, then the scripts this client loaded with client.set_script, then your overrides. A layer's name becomes the source of its bindings and of warnings about it. If a name is empty, contains a colon, or is used by another layer in either list, the call fails with bad_request. The keymap is always the calling client's, so a request that includes client_id fails with bad_request. So does a top-level bindings list, which older servers accepted. Send those bindings as a layer in defaults instead. A key sequence such as ctrl+b followed by c is [\"ctrl+b\", \"c\"] in keys. The configuration file writes it as ctrl+b>c, but the result always lists the keys separately. The server doesn't split an element such as ctrl+b>c. It treats it as a key it can't read. Keys the server can read come back in their standard spelling. cmd+d and cmd+[KeyD] are still different keys: the first follows the keyboard layout and the second names a physical key. A key spelling the server can't read is kept as written, in either list, so a newer client keeps its keys. Only a removal with the same spelling can take it off. A sequence with any such key is kept the same way. A sequence sits on the stack of its first key, in order among that key's other bindings. If the top binding a client can perform is a sequence, the client waits for the next key. The server keeps no state between keys, so waiting is the client's job. A key bound to a Lua function, in the configuration file or this client's scripts, comes back as a binding for client.key.forward. To perform it, send the key to client.key. Treat it as always available: the bindings below it on that key never run, even when no function takes the key. For a sequence bound to a function, send its last key to client.key with the earlier keys in prefix. The binding ends the stack only for that exact sequence, so other sequences that start with the same key still work. A key can't both run a binding of its own and start a sequence, so the higher of the two wins. The server keeps both and adds one warning per key, with the source, action, and key of the binding that can't run. It warns even about a binding in defaults, unless the one above it is in defaults too. A removal matches keys in full. Removing [\"ctrl+b\"] leaves every sequence that starts with ctrl+b, and removing [\"ctrl+b\", \"c\"] leaves ctrl+b. A removal that removes nothing gets a warning, unless it's in defaults, names a key the server can't read, or blocks a key in a mode. A binding or removal with a mode belongs to that mode, a set of bindings the client uses only while the mode is active. If a mode name contains anything but lowercase letters, digits, _, and -, or is root, the call fails with bad_request. Bindings in a mode stack and are checked like any others. A key in a mode never hides the same key outside it, and a removal affects only its own mode. The configuration file writes a key in a mode as resize/left. modes lists a mode if any layer binds a key in it, blocks a key in it, or declares it with rex.mode. A removal in a mode that names no action always blocks its key, so the key is listed in the mode's blocked, whether or not anything was removed. A removal that names an action never blocks. A removal with a mode and no keys removes the whole mode: its bindings, its blocked keys, and its declarations in the layers beneath. If it also names an action, the call fails with bad_request. Above a removed mode, a layer that binds, blocks, or declares a key in the mode brings the mode back, with only what that layer and the ones above it say. Otherwise the mode isn't in modes. The client decides which mode is active, with its own client.mode.enter, client.mode.exit, and client.mode.exit_all actions. The server keeps no mode state. The server warns about a client.mode.enter binding whose name argument isn't a mode in the keymap, and about a declared mode with nothing bound in it. An empty request returns the bindings of the configuration file and this client's scripts, without checking for unknown actions or wrong arguments. When actions isn't empty, the server adds its own actions before checking: those the configuration file defines with rex.action, every block method, and every block action. A binding to one of these is never reported as unknown. A binding with wrong arguments is kept, with a warning. A binding to a block method must pass arguments that match the method's input schema, and passing none counts as {}. A binding to a block action, or to a method with no input schema, must pass no arguments. A binding to an action from the configuration file must match the args schema the action declares, if it declares one. The client's own actions aren't checked, because the client checks their arguments itself. actions in the result lists the configuration file's actions in the order it defines them, then each creator's methods and block actions, sorted by creator and then by method name. It doesn't repeat the actions you sent. A block method in actions has no title, and its args is its input schema. Bind it, but don't show it in a menu. A block action has a title and no args. server_actions changes only what actions lists in the result, not which actions bindings are checked against. To show a diagnostic next to the binding it's about, match its action, key, and mode to the binding's. A client can keep using a stored result while its request is unchanged and config.instance_id and config.hash both match. Call this method again after every client.set_script call, and after every keymap_changed event whose hash differs from the stored one."},{"href":"/reference/api#clientlist","page":"Server API Methods","heading":"client.list","text":"List the clients connected to the server, in the order they connected. Field Required Description session_id List only the clients attached to this session with session.attach or session.bootstrap. kinds List only the clients of these kinds, such as app. A client that didn't report a kind matches none. With no arguments, the result lists every connected client, including short-lived ones such as the rex CLI. session_id counts only clients attached to that session with session.attach or session.bootstrap. A connection that only called session.attach_all doesn't count. Any client may use this filter, attached or not. In kinds, a kind this server doesn't know counts as other. A client that didn't report a kind matches no filter, not even other. If a client's connection drops without closing, the client stays in the list for up to 60 seconds, with connection_state set to lost, so it can reconnect. A client that wasn't attached to anything and had no scripts leaves at once. To keep a copy of this list up to date, call session.attach_all with event_scope set to all. The connection then receives client_joined, client_left, and client_changed events."},{"href":"/reference/api#clientperform","page":"Server API Methods","heading":"client.perform","text":"Ask another client to perform one of its actions and wait for its answer. Field Required Description client_id Yes The client to ask. To find one by its label, call target.resolve with kinds set to client. action Yes The name of an action the client declared, such as pane.split. client.inspect lists them. args The action's arguments, as a JSON object. Leave it out if there are none. origin What caused the request, such as key, palette, api, or cli. The client receives it unchanged. Empty means api. session_id The session the action applies to, if any. The client receives it without the server checking it. block_id The block to act on instead of the client's focused block, if any. The client receives it without the server checking it. timeout_ms How long to wait for the answer, in milliseconds, once the client is sent the request. Defaults to 10000. Values over 30000 count as 30000. The server sends the named client a client_perform_requested event and waits for it to answer with client.reply. The server tells the named client which client asked, based on the connection the request arrived on, so a client can't claim to be another. The connection doesn't need to be attached to session_id, because the server only passes it on to the client. Requests to one client wait in line, and the client answers them one at a time in the order they arrived. timeout_ms counts from when a request reaches the front of the line. A request without client_id or action, with a negative timeout_ms, or with args that aren't an object or don't match the action's schema fails with bad_request. If client_id names no connected client, the call fails with not_found. If the client has listed its actions and action isn't one of them, the call fails with not_supported. A client that listed none is asked anyway, and it may answer not_supported itself. A client that declines answers refused, and the call fails with that code and a message that says why. If the client is reconnecting, already has 16 requests waiting for it, or disconnects while the request waits, the call fails with client_unavailable. If the client doesn't answer in time, the call fails with timeout. The client isn't told, so it may still perform the action later. When a request ends, the server sends a client_performed event with the outcome to connections that called session.attach_all with event_scope set to all. A request that names no connected client sends no event."},{"href":"/reference/api#clientreply","page":"Server API Methods","heading":"client.reply","text":"Answer a client_call_requested or client_perform_requested event. Field Required Description call_id Yes The call_id of the event you're answering. result The call's result, or what the action returned. Use null if there is none. Set this or error, not both. error Why the call failed or the action wasn't performed. Set this or result, not both. A client_call_requested event asks this client to make one call on another server, for a client.action request that is still running. Reply to every one, even when the call fails. Send the event's call_id, on the connection that received the event. A client_perform_requested event asks this client to perform one of its own actions for another client's client.perform request. Reply the same way. result is what the action returned, or null. A reply that sets both result and error, or neither, fails with bad_request. For a call with no result, send \"result\": null. For a call that failed, use the code and message the other server returned, or explain why you couldn't make the call. The waiting action sees only the message. For an action you didn't perform, set error.code to not_supported, refused, or internal. The client that sent client.perform gets that code unchanged. An empty code is sent as internal. A reply runs as soon as it arrives. It doesn't wait behind this connection's earlier client.key and client.action requests, because a client.action request that is waiting for the reply holds that line. A call_id that nothing is waiting for fails with not_found. That includes a call that was already answered and one that timed out."},{"href":"/reference/api#clientset_label","page":"Server API Methods","heading":"client.set_label","text":"Set or remove this client's label. Field Required Description label Yes The label to set, such as studio. An empty string removes it. At most 64 bytes, with no control characters. The label always belongs to the client that sends the request. A request that includes client_id fails with bad_request. The server removes whitespace from both ends of label before it checks the label, so a label of only spaces removes the current one. A label longer than 64 bytes, or one that isn't valid UTF-8 or contains a control character, fails with bad_request. The message says which rule it breaks. Labels don't have to be unique. Two clients with the same label are told apart by their IDs. Labels are for people to read. The server never uses one to decide what a client may do. When the label changes, the server sends a client_changed event to connections that called session.attach_all with event_scope set to all. A client can also send a label when it connects, in the client field of its hello message. The same rules apply, but the server fixes the label instead of failing: it cuts a label longer than 64 bytes down to size and drops one that is otherwise invalid."},{"href":"/reference/api#clientset_script","page":"Server API Methods","heading":"client.set_script","text":"Load Lua scripts for this client, replacing any it loaded before. Field Required Description scripts Yes Every Lua source to load, run in order. They replace all the scripts loaded before. An empty list unloads them. The scripts belong to this client and apply in every session it's attached to. A request that includes session_id or client_id fails with bad_request. If a script has a syntax error, raises an error, or runs longer than 5 seconds while it loads, the call fails with bad_request. The scripts loaded before keep running. To see every key and key sequence the scripts bound to a function, call client.keymap. It reports each one as a binding for client.key.forward. The keys that reach the scripts are fixed when they load. A function that a script binds later, while it runs, receives a key only if a function was already bound to that key or sequence when the scripts loaded. A successful call can still return diagnostics, even with severity error. An error marks a part of a script the server skipped, such as a binding to a key it can't read. The rest of the scripts still load. Loading scripts changes this client's keymap without a keymap_changed event. Call client.keymap again after each call to this method."},{"href":"/reference/api#data","page":"Server API Methods","heading":"data","text":""},{"href":"/reference/api#dataconnect","page":"Server API Methods","heading":"data.connect","text":"Get a one-time ticket for opening a data connection to a block. A data connection streams a block's content as it changes, such as a terminal's output. Field Required Description session_id Yes The session that holds the block. The connection must be attached to it with session.attach or session.bootstrap. block_id Yes The block that provides the data. data_ref_id Yes Which of the block's data sources to open, such as terminal. subprotocol The subprotocol the data connection will use, such as rex.data.terminal.v1. If set, the ticket works only on a connection that uses it. The connection must be attached to session_id with session.attach or session.bootstrap. session.attach_all isn't enough. If the connection isn't attached, the call fails with conflict. To use the ticket, open a WebSocket to /api/data on the server and send the ticket in the HELLO message. A ticket works once, even if the handshake fails, and expires after expires_in_ms. If another user presents the ticket, the data connection gets REJECT reason 0x01, as it would for an unknown ticket. The ticket is then used up, so your own data connection can't use it either. The server checks only that data_ref_id is well formed. If the block has no data by that name, the server closes the data connection with WebSocket status 1008 after the handshake. Set subprotocol to the subprotocol the data connection will use, such as rex.data.terminal.v1. The server then refuses a data connection that uses a different one with REJECT reason 0x07, before any data is sent. If subprotocol is left out, the block checks the subprotocol after the handshake and closes the data connection with status 1008 if it doesn't support it. The call fails with bad_request if data_ref_id or subprotocol is malformed, and with not_found if the block isn't in the session or has no data to offer."},{"href":"/reference/api#help","page":"Server API Methods","heading":"help","text":""},{"href":"/reference/api#helpall","page":"Server API Methods","heading":"help.all","text":"Describe every method the server provides, including block methods. The result has two lists. methods describes each method with its arguments, result, examples, and notes. block_types lists each creator whose blocks provide methods, with its flavors and those methods. The result describes the server you are connected to, so it always matches that server's version. Over HTTP, GET /api/help returns the same document with an added http field, and /llms.txt shows it as Markdown."},{"href":"/reference/api#hosts","page":"Server API Methods","heading":"hosts","text":""},{"href":"/reference/api#hostslist","page":"Server API Methods","heading":"hosts.list","text":"List the server's hosts: the other Rex servers you connect to by label. Every Rex server keeps a host list. Each of your devices reads its hosts from the one server it uses as its home, so they all see the same list. The list holds the hosts declared with rex.host in the server's configuration file, and the hosts that clients added with hosts.put. A declared host has a source, such as init.lua:4. A host added with hosts.put has none. Each host comes back exactly as it was stored, including fields this server doesn't know. A client that sends a host back with hosts.put must keep those fields. A declared host with a mistake in it, or whose label another host already uses, is left out of hosts. A warning in diagnostics names the line that declared it. A server started without a state directory can't store hosts, so it lists only the declared ones. If a hand edit broke the file that stores the hosts, the call fails with conflict and a message that names the file."},{"href":"/reference/api#hostsput","page":"Server API Methods","heading":"hosts.put","text":"Add a host to the server's host list, or replace the host with the same ID. Field Required Description host Yes The host to add, or to store in place of the host with the same id. The device that adds a host chooses its id, so sending the same request again is safe and adds nothing. If host has no id, the server creates one. If host matches the stored host in every field except updated_at, nothing changes and the result holds the stored host. label can't be empty, can't start or end with white space, and has at most 64 characters. No other host in the list may have the same label, ignoring case. If one does, the call fails with conflict and a message that names that host and, for a declared host, the line that declared it. endpoints needs at least one endpoint, each in a form that the -S option of rex accepts. Unix sockets and named pipes aren't allowed, because their paths work only on their own machine. To change a declared host, put a host with its id. The server never writes the configuration file. It stores the host you send, which takes the place of the declared one. A put also brings back a host that hosts.remove removed. The server sets updated_at and drops source. It stores every other field as sent, including fields it doesn't know. When the put changes the list, every connected client receives a hosts_changed event. If host fails a check, the call fails with bad_request and a message that names the field. A server started without a state directory can't store hosts, and fails with not_supported. A hand edit that broke the file that stores the hosts makes the call fail with conflict. The file that stores the hosts can hold at most 1 MiB, which is room for thousands of hosts. A put that would make it larger fails with too_large."},{"href":"/reference/api#hostsremove","page":"Server API Methods","heading":"hosts.remove","text":"Remove a host from the server's host list. Field Required Description id Yes The host to remove. Removing a host that isn't in the list isn't an error. removed is false, nothing changes, and no event is sent, so calling it again is safe. The same happens if id is left out. The server never writes the configuration file, so removing a declared host hides it. Removing a stored host that took the place of a declared one removes both. Either way, only a later hosts.put with the same id brings the host back. When a host is removed, every connected client receives a hosts_changed event. If id isn't a valid host ID, the call fails with bad_request. A server started without a state directory can't store hosts, and fails with not_supported. A hand edit that broke the file that stores the hosts makes the call fail with conflict."},{"href":"/reference/api#server","page":"Server API Methods","heading":"server","text":""},{"href":"/reference/api#serverapplegrant_file_access","page":"Server API Methods","heading":"server.apple.grant_file_access","text":"Let the server, and every program it runs, read files a Mac app was given. Field Required Description files Yes The files to make readable, in the order they were dropped or pasted. At most 256. A Mac app calls this when a file is dropped on or pasted into a terminal. macOS lets the app open the file, but not always the server, which macOS treats as a different app. For each file, the server first tries to open path. It uses token only if it can't, so most files, such as those in a project, come back readable and their tokens go unused. A file the server can't open doesn't fail the call. A relative path is always denied. Access lasts until the server exits. If a file comes back denied, or with an access value the client doesn't know, copy the file somewhere the server can read instead. A request that names more than 256 files fails with bad_request. The server accepts this method only over its local Unix socket, or its named pipe on Windows. Over any other connection, such as TCP or Tailscale, the call fails with status 403."},{"href":"/reference/api#serverappleprivacy_status","page":"Server API Methods","heading":"server.apple.privacy_status","text":"Report the macOS privacy permissions the server and its programs have. macOS decides privacy permissions for the app it holds responsible for a process. Every shell and program the server runs belongs to the server, so they all get the permissions listed here, whatever the app that shows their terminals was allowed. Only the server can read its own permissions. A Mac app calls this to show them, and calls it again after the person changes one in System Settings. Reading the permissions never makes macOS ask the person anything. responsible_path is the executable macOS holds responsible for the server. The permissions belong to the app that contains it. When the server was started in the foreground from a terminal app, that is the terminal app. Skip a service you don't know, and treat a status you don't know as unknown. A server that isn't running on macOS lists no services. The server accepts this method only over its local Unix socket, or its named pipe on Windows. Over any other connection, such as TCP or Tailscale, the call fails with status 403."},{"href":"/reference/api#serverconfig_reload","page":"Server API Methods","heading":"server.config_reload","text":"Reload the configuration file and tell every client to fetch its keymap again. The server reads the same configuration file it read when it started, usually init.lua, along with the modules that file loads with require. The next client.keymap result reports the same generation in config.generation, unless the file is reloaded again first. A mistake that stops the file from loading, such as a Lua syntax error, doesn't fail the call. The server keeps the bindings, actions, and hosts from the last load that worked, and diagnostics reports the mistake. If the file no longer exists, the reload removes the bindings, actions, and hosts it used to declare. That isn't an error. diagnostics lists only problems in the file itself. A problem that depends on a client, such as a binding to an action that client doesn't have, is reported by client.keymap instead. After the load, every connected client receives a keymap_changed event with the new generation and hash. A client can keep its keymap if its last client.keymap result has the same hash and came from the same server process, that is, the same instance_id. If the reload changes the host list, such as by adding or removing a rex.host declaration, every connected client also receives a hosts_changed event. The server accepts this method only over its local Unix socket, or its named pipe on Windows. Over any other connection, such as TCP or Tailscale, the call fails with status 403."},{"href":"/reference/api#serverstatus","page":"Server API Methods","heading":"server.status","text":"Get the server's identity, session count, configuration, and Tailscale status. Two servers report the same host_id only when they see the same files, as two servers run by one user on one machine usually do. If the values differ, or either one is missing, assume the servers don't share files. The value means nothing else. config describes the last load of the configuration file. Its generation and hash match the config of a client.keymap result taken at the same moment. A client that saved a keymap can compare config.hash and instance_id with the values it saved. If either differs, call client.keymap again. Later changes to tailscale arrive as tailscale_status_changed events, which only connections that called session.attach_all or session.bootstrap receive."},{"href":"/reference/api#serverstop","page":"Server API Methods","heading":"server.stop","text":"Shut the server down, or with if_idle, only if nothing is using it. Field Required Description if_idle Stop only if the server has no sessions and no other client is connected. A server that is in use refuses with conflict and keeps running. Only a client on the server's own machine, connected over its Unix socket or named pipe, can call this method. Without if_idle, the server stops no matter what it is doing, and every program running in its sessions ends. With if_idle, the server stops only if it has no sessions, including any still being created or closed, and no client is connected except the caller. Otherwise it keeps running and the call fails with conflict. The error message says what is using the server. The server checks and stops in one step. A client that connects just after the check is refused. The server replies before it shuts down. Close the connection after reading the reply. The server waits up to a second for the connection to close before it stops. A server running inside another program, with no process of its own, fails the call with not_supported. The server accepts this method only over its local Unix socket, or its named pipe on Windows. Over any other connection, such as TCP or Tailscale, the call fails with status 403."},{"href":"/reference/api#servertailscaledisable","page":"Server API Methods","heading":"server.tailscale.disable","text":"Turn off built-in Tailscale, keeping its sign-in for the next enable. Turning off built-in Tailscale closes every connection that came in over the tailnet. Sessions keep running. The device keeps its sign-in, so the next server.tailscale.enable reconnects without signing in. To sign out instead, call server.tailscale.logout. If built-in Tailscale is already off, the call changes nothing and returns the current status, so calling it again is safe. If the server can't run built-in Tailscale, the call fails with not_supported. The server accepts this method only over its local Unix socket, or its named pipe on Windows. Over any other connection, such as TCP or Tailscale, the call fails with status 403."},{"href":"/reference/api#servertailscaleenable","page":"Server API Methods","heading":"server.tailscale.enable","text":"Turn on built-in Tailscale so devices on your tailnet can reach the server. Field Required Description hostname The device name to use if this call starts the device, such as studio. If left out, the server uses its configured name, or rex. allow_tailnet_members Yes True lets in anyone your tailnet's access rules allow. False lets in only the Tailscale user who signed the device in. Applies on every call, even when the device is already on. auth_key A Tailscale auth key that signs the device in without a browser. Used only when this call starts the device. Never logged or saved. advertise_tags Tailscale tags, such as tag:rex, for the device to sign in with. Used only when this call starts the device. Needs allow_tailnet_members set to true. control_url The control server to sign in with if this call starts the device, such as https://headscale.example.com. If left out, the server uses its configured control server, or Tailscale's own. Built-in Tailscale is a copy of Tailscale inside Rex. It makes the server join your tailnet as a device of its own, so the machine doesn't need Tailscale installed. If built-in Tailscale is already on, the call only applies allow_tailnet_members, without restarting the device or dropping connections. It ignores hostname, auth_key, and advertise_tags. The first time the device starts, it must sign in to Tailscale. With a valid auth_key, it signs in without a browser. Otherwise state becomes needs_login, and the sign-in link appears in auth_url and in the server's log. The device keeps its sign-in, so later calls need no auth_key. The server never logs, saves, or returns the key. advertise_tags signs the device in as a tagged device, which has no owner, so it needs allow_tailnet_members set to true. The auth key or OAuth client that signs the device in must be allowed to grant the tags. The call returns once the device has started, before it has finished connecting. The server is reachable over the tailnet only once https.state is available. Follow the progress with tailscale_status_changed events, on a connection that called session.attach_all or session.bootstrap. If the HTTPS listener fails later, built-in Tailscale turns off: state becomes disabled and error says why. When the call starts the device, it fails with bad_request for a tag that isn't a valid Tailscale tag, and for tags with allow_tailnet_members set to false. While the server is shutting down, the call fails with conflict. If the server can't run built-in Tailscale, the call fails with not_supported. The server accepts this method only over its local Unix socket, or its named pipe on Windows. Over any other connection, such as TCP or Tailscale, the call fails with status 403."},{"href":"/reference/api#servertailscalelogin","page":"Server API Methods","heading":"server.tailscale.login","text":"Ask Tailscale for a sign-in link for the server's Tailscale device. The call starts the sign-in and returns without waiting for the link. If auth_url is empty, the link arrives later in a tailscale_status_changed event, which only connections that called session.attach_all or session.bootstrap receive. server.status also shows it once it is ready. Calling it again while a sign-in is already under way is safe and doesn't start another one. If built-in Tailscale is off, the call fails with conflict. Turn it on with server.tailscale.enable first. If the request fails, or Tailscale doesn't accept it within 10 seconds, the call fails. The server keeps trying in the background. If the server can't run built-in Tailscale, the call fails with not_supported. The server accepts this method only over its local Unix socket, or its named pipe on Windows. Over any other connection, such as TCP or Tailscale, the call fails with status 403."},{"href":"/reference/api#servertailscalelogout","page":"Server API Methods","heading":"server.tailscale.logout","text":"Sign the server out of Tailscale and turn off built-in Tailscale. After signing out, built-in Tailscale turns off. The next server.tailscale.enable must sign in again. If built-in Tailscale is off, the call changes nothing and the saved sign-in stays. Turn it on first to sign out. If signing out fails, or Tailscale doesn't answer within 10 seconds, the call fails and built-in Tailscale stays on, so you can try again. If the server can't run built-in Tailscale, the call fails with not_supported. The server accepts this method only over its local Unix socket, or its named pipe on Windows. Over any other connection, such as TCP or Tailscale, the call fails with status 403."},{"href":"/reference/api#servertailscalerename","page":"Server API Methods","heading":"server.tailscale.rename","text":"Change the server's Tailscale device name without signing in again. Field Required Description hostname Yes The new device name, such as studio: 1 to 63 ASCII letters, digits, and hyphens, not starting or ending with a hyphen. The device keeps its identity and its sign-in. Until the server exits, a later server.tailscale.enable without a hostname uses the new name too. dns_name in the result may still show the old name. The new one arrives in a tailscale_status_changed event, on a connection that called session.attach_all or session.bootstrap. If built-in Tailscale is off, the call fails with conflict. To choose the name then, pass hostname to server.tailscale.enable instead. If hostname isn't a valid device name, the call fails with bad_request. If the server can't run built-in Tailscale, the call fails with not_supported. The server accepts this method only over its local Unix socket, or its named pipe on Windows. Over any other connection, such as TCP or Tailscale, the call fails with status 403."},{"href":"/reference/api#serverversion","page":"Server API Methods","heading":"server.version","text":"Get the version of Rex that the server is running and how it was built. A missing api_revision or api_min_revision means 0. Servers from before these fields existed leave both out."},{"href":"/reference/api#serverwhoami","page":"Server API Methods","heading":"server.whoami","text":"Get how the server identified you, and the account the server runs as. Which fields principal has depends on its kind. A peer has uid and gid, and usually pid. A windows_peer has sid, and usually pid. A tailscale client has user_id, login_name, and node, plus tags if its device is tagged. An anonymous client has none of these. The server identifies a client when its connection opens. principal stays the same for as long as the connection lasts. principal describes a decision the server already made when it let the client in. Use it to show who is connected, not to decide what a client may do."},{"href":"/reference/api#session","page":"Server API Methods","heading":"session","text":""},{"href":"/reference/api#sessionattach","page":"Server API Methods","heading":"session.attach","text":"Attach to a session: start receiving its events and get its current view. Field Required Description session_id Yes The session to attach to. The connection starts receiving every event from the session, including block_event, before the server reads view. Apply view first, then each event whose seq is higher than the response's seq, even one that arrived before the response. Most methods that take a session_id work only on a connection attached to that session, with this method, session.bootstrap, or session.attach_all. On a connection that isn't attached, they fail with conflict. Attaching makes the connection's client one of the session's clients. The server sends client_connected, and session.list_clients lists the client. data.connect, session.own_block, and session.disown_block need this kind of attachment. Attaching with session.attach_all isn't enough for them. Calling it again for the same session is safe. It returns the same client_id and a fresh view. If session_id names no session, the call fails with session_not_found."},{"href":"/reference/api#sessionattach_all","page":"Server API Methods","heading":"session.attach_all","text":"Start receiving events from the whole server and get the list of sessions. Field Required Description event_scope Yes Which events to receive. 'session' sends every session's events except block_event, plus session_created, session_destroyed, session_moved, and tailscale_status_changed. 'all' also sends block_event and the client events, such as client_joined. Use session unless you need block_event or the client events. With all, the client events are client_joined, client_left, client_changed, client_performed, and client_script_events_dropped. Any value other than session or all fails with bad_request. The connection still receives every event from a session it attached to with session.attach, whatever the scope. The connection doesn't join any session. It can call methods on every session, but it isn't listed in session.list_clients and can't call data.connect or own blocks. To join a session too, call session.attach, or use session.bootstrap to do both in one call. Calling it again replaces the scope with the new event_scope. The session list is a snapshot taken after events start. Apply each event whose seq is higher than the response message's seq on top of it. Events arrive in order, and their seq has no gaps. If the connection falls more than 8 MiB of events behind, the server closes it rather than skip events. Open a new connection to get a fresh snapshot."},{"href":"/reference/api#sessionbootstrap","page":"Server API Methods","heading":"session.bootstrap","text":"Set up a new connection in one call: receive events, list sessions, and attach. Field Required Description event_scope Yes Which events to receive. 'session' sends every session's events except block_event, plus session_created, session_destroyed, session_moved, and tailscale_status_changed. 'all' also sends block_event and the client events, such as client_joined. preferred_session_id The session to attach to, if it exists. Otherwise the server attaches the first session. Ignored when attach_session_ids is set. attach_session_ids The sessions to attach to, in order. Unknown and repeated IDs are skipped, and an empty list attaches none. Leave it out to let the server pick one. data_tickets The kinds of block to issue data tickets for. The response has a ticket for each visible block in an attached session that matches one. At most 8 entries. Send it as the first request on a new connection. Apply its result first, then each event whose seq is higher than the response's seq, even one that arrived before the response. event_scope works as it does for session.attach_all, and the connection can then call methods on any session. A value other than session or all fails with bad_request. If attach_session_ids is left out, the server attaches one session and returns it in selected_session, which is null when the server has no sessions. If attach_session_ids is set, even to an empty array, the server returns the sessions it attached in attached_sessions and leaves selected_session null. A session in attach_session_ids that is destroyed during the call is left out of attached_sessions. If the session chosen for selected_session is destroyed during the call, the call fails with session_not_found. Any other failure to attach also fails the whole call. If the connection falls more than 8 MiB of events behind, the server closes it rather than skip events. Open a new connection and call session.bootstrap again. If the call fails, close the connection and open a new one before you try again. Part of the setup may already have taken effect."},{"href":"/reference/api#sessionclose_layer","page":"Server API Methods","heading":"session.close_layer","text":"Close a floating layer and every block in it. Field Required Description session_id Yes The session to act on. layer_id Yes The floating layer to close. Every block in it closes too. layer_id must name a floating layer. Naming the window's layout, the layer of kind tiled in session.view, fails the call with bad_request. Close the window with session.close_window instead. The server closes every block in the layer, which ends the programs running in them. The call returns once the blocks have closed. If the layer had focus, focus returns to the layer that had it before, at the block last focused there. Otherwise focus doesn't change. If the layer held the window's last blocks, the server closes the window too. If the session doesn't exist, the call fails with session_not_found. If layer_id names no layer in the session, it fails with not_found."},{"href":"/reference/api#sessionclose_window","page":"Server API Methods","heading":"session.close_window","text":"Close a window and every block in it. Field Required Description session_id Yes The session to act on. window_id The window to close. Leave it out to close the active window. Every block in it closes too. The server closes every block in the window, which ends the programs running in them. The call returns once the blocks have closed. If the window was active, the window after it becomes active, or the one before it if it was last. If the session doesn't exist, the call fails with session_not_found. If window_id names a window that isn't in the session, it fails with not_found. A window leaves the session as soon as a close starts, so closing the same window twice also fails with not_found. If window_id is left out and the session has no windows, the call fails with conflict."},{"href":"/reference/api#sessioncreate","page":"Server API Methods","heading":"session.create","text":"Create a session, optionally with its first windows already in it. Field Required Description label The session's label, up to 128 characters. Leave it out to let the server choose one, such as quiet-meadow. initial_windows Windows to create with the session, in order. The last one becomes the active window. If any of them fails, the server creates no session. Every argument is optional. With none, the server creates a session with no windows. If label is left out or empty, the server picks one that no other session has, such as quiet-meadow. The server sends session_created only once every window in initial_windows is ready, so no client sees a half-built session. If any window fails, the server closes the session and its blocks, and the call fails. The calling client becomes the owner of every block in the initial windows. The call doesn't attach the connection to the new session. Call session.attach to receive its events. If a layout is malformed, the call fails with bad_request before any block is created. A flavor that no creator provides fails with not_found. If the server already has as many sessions as it allows, the call fails with conflict."},{"href":"/reference/api#sessiondescribe_block","page":"Server API Methods","heading":"session.describe_block","text":"Get a block's details and the methods, events, and actions it provides. Field Required Description session_id Yes The session to act on. block_id Yes The block to describe. It may be a detached block. api lists the methods and events the block provides, in the same form as block_creator.list. Call a method as <creator>.<method> with session_id, block_id, and its arguments under args. If block_id names a block that isn't in the session, or one that is closing, the call fails with not_found."},{"href":"/reference/api#sessiondestroy","page":"Server API Methods","heading":"session.destroy","text":"Destroy a session and close every block in it. Field Required Description session_id Yes The session to destroy. Every block in it closes. The server closes every window and block in the session. Closing a terminal hangs up the program running in it, as closing a terminal window does. The call returns once the session has closed. session_destroyed is the last event about the session, though a client_script_events_dropped event for it can still follow. The connection must be attached to the session, with session.attach, session.bootstrap, or session.attach_all, or the call fails with conflict. If session_id names no session, the call fails with session_not_found."},{"href":"/reference/api#sessiondetach","page":"Server API Methods","heading":"session.detach","text":"Detach the connection from a session. The connection stays open and the session keeps running. Field Required Description session_id Yes The session to detach from. The connection's other attachments, and any events it receives through session.attach_all, continue as before. The client leaves the session. The server sends client_disconnected, and each block the client owned passes to the next client in line for it, if there is one. If the connection isn't attached to the session, the call succeeds and changes nothing, even if the session doesn't exist. A session.attach for the same session that is still in progress isn't canceled. This call succeeds without detaching, and the attach completes. If the connection attaches to the session again later, its client keeps the same client_id."},{"href":"/reference/api#sessiondetach_block","page":"Server API Methods","heading":"session.detach_block","text":"Take a block out of its window without closing it. The block keeps running. Field Required Description session_id Yes The session to act on. block_id Yes The block to detach. It keeps running but leaves its window. session.list_blocks lists the block with placement set to detached, and session.move_block places it in a window again. Detaching a block that is already detached succeeds and changes nothing. If the block was the last one in its window, the window closes too. If it was the only block in a floating layer, the layer closes. If the block had focus, another block in its window gets focus. To close the block instead, call block.close. If block_id names a block that isn't in the session, the call fails with not_found."},{"href":"/reference/api#sessiondisown_block","page":"Server API Methods","heading":"session.disown_block","text":"Give up the calling client's claim on blocks. The result lists each block's owner. Field Required Description session_id Yes The session to act on. block_ids The blocks to give up. Leave it out or empty to give up every block in the session. The calling client must be attached to the session, with session.attach or session.bootstrap. A connection that only called session.attach_all fails with bad_request. A block the caller owned passes to the next connected client in line for it, or is left with no owner. For a block the caller doesn't own, the caller only leaves the line. blocks lists each block's owner after the call, in the order of block_ids, with a repeated ID listed once. When you give up every block, they are sorted by block_id. Every change of owner sends block_owner_changed. If a block in a window changes owner, the call also sends one session_view_changed. If any ID in block_ids isn't a block in the session, the call fails with bad_request and changes nothing."},{"href":"/reference/api#sessionfloat_block","page":"Server API Methods","heading":"session.float_block","text":"Move a block out of its window's layout into a new floating layer. Field Required Description session_id Yes The session to act on. block_id Yes The block to float. It must be in its window's layout, not already in a floating layer. bounds Yes Where the new floating layer sits, as [x, y, w, h] fractions of the window's width and height. It must fit inside the window. bounds is measured from the window's top-left corner. Its width and height must be greater than 0. The new layer is in the block's own window, in front of every other floating layer there. Focus and the active window don't change. If the window was zoomed, the zoom ends. Floating the last block of a window's layout leaves the layout empty. The window stays open, and its layer of kind tiled in session.view keeps the same layer_id. If the session doesn't exist, the call fails with session_not_found. If block_id names a block that isn't in any window, including a detached block, it fails with not_found, and bad bounds fail with bad_request. If the block is already in a floating layer, the call fails with bad_request."},{"href":"/reference/api#sessionfocus_block","page":"Server API Methods","heading":"session.focus_block","text":"Focus a block, and make its window the active window if it isn't. Field Required Description session_id Yes The session to act on. block_id Yes The block to focus. It must be in a window, which becomes the active window. The block can be in the window's layout or in a floating layer. If it isn't already its window's focused block, any zoom in that window ends. If the block already has focus in the active window, the call changes nothing. If the session doesn't exist, the call fails with session_not_found. If block_id names a block that isn't in any window, including a detached block, it fails with not_found."},{"href":"/reference/api#sessionfocus_direction","page":"Server API Methods","heading":"session.focus_direction","text":"Move focus to the nearest block to the left, right, above, or below. Field Required Description session_id Yes The session to act on. direction Yes Which way to move focus from the focused block in the active window. wrap Whether to continue from the opposite edge when no block lies that way. Defaults to false. Older servers ignore it and always wrap. The search starts at the focused block of the active window. It covers every block in that window, floating layers included, by where each appears on screen. When two blocks are equally close, the one in front wins. If no block lies in direction, focus doesn't change and the call still succeeds. Moving focus ends any zoom in the window. If the session doesn't exist, the call fails with session_not_found. If the session has no windows, it fails with conflict."},{"href":"/reference/api#sessionfocus_next_pane","page":"Server API Methods","heading":"session.focus_next_pane","text":"Move focus to the next block in the active window. Field Required Description session_id Yes The session to act on. wrap Whether to go from the last block back to the first. Defaults to false. Blocks are in layout order: the window's layout first, reading each split's before side ahead of its after side, then each floating layer from back to front. If there's no next block, focus doesn't change and the call still succeeds. Moving focus ends any zoom in the window. If the session doesn't exist, the call fails with session_not_found. If the session has no windows, it fails with conflict."},{"href":"/reference/api#sessionfocus_next_window","page":"Server API Methods","heading":"session.focus_next_window","text":"Make the next window the active window. Field Required Description session_id Yes The session to act on. wrap Whether to go from the last window back to the first. Defaults to false. The window order is the order of windows in session.view. Each window keeps its own focused block, which then receives typing. If there's no next window, nothing changes and the call still succeeds. If the session doesn't exist, the call fails with session_not_found. If the session has no windows, it fails with conflict."},{"href":"/reference/api#sessionfocus_previous_pane","page":"Server API Methods","heading":"session.focus_previous_pane","text":"Move focus to the previous block in the active window. Field Required Description session_id Yes The session to act on. wrap Whether to go from the first block back to the last. Defaults to false. Blocks are in layout order: the window's layout first, reading each split's before side ahead of its after side, then each floating layer from back to front. If there's no previous block, focus doesn't change and the call still succeeds. Moving focus ends any zoom in the window. If the session doesn't exist, the call fails with session_not_found. If the session has no windows, it fails with conflict."},{"href":"/reference/api#sessionfocus_previous_window","page":"Server API Methods","heading":"session.focus_previous_window","text":"Make the previous window the active window. Field Required Description session_id Yes The session to act on. wrap Whether to go from the first window back to the last. Defaults to false. The window order is the order of windows in session.view. Each window keeps its own focused block, which then receives typing. If there's no previous window, nothing changes and the call still succeeds. If the session doesn't exist, the call fails with session_not_found. If the session has no windows, it fails with conflict."},{"href":"/reference/api#sessionfocus_window","page":"Server API Methods","heading":"session.focus_window","text":"Make a window the active window. Focus goes to the block it last had focused. Field Required Description session_id Yes The session to act on. window_id Yes The window to make active. Focus goes to the block that was focused in it. If the window is already active, the call changes nothing. If the session doesn't exist, the call fails with session_not_found. If window_id names a window that isn't in the session, it fails with not_found."},{"href":"/reference/api#sessionhelp","page":"Server API Methods","heading":"session.help","text":"Describe the methods this session accepts and the methods its blocks provide. Field Required Description session_id Yes The session to act on. The result has the same form as help.all, with an added http field. It describes only the methods the session answers itself, such as session.view and session.new_split, and the block methods of the creators that session can use. Most clients call help.all instead, which describes every method the server provides."},{"href":"/reference/api#sessionlist","page":"Server API Methods","heading":"session.list","text":"List the sessions on the server. Sessions are listed in the order they were created, until session.move changes that order. session.attach_all and session.bootstrap list them in the same order."},{"href":"/reference/api#sessionlist_blocks","page":"Server API Methods","heading":"session.list_blocks","text":"List every block in the session, including detached blocks. Field Required Description session_id Yes The session to act on. Treat a placement you don't recognize like detached. The block exists and you can call its methods, but don't assume where it is shown. Blocks are sorted by block_id. To see where each block sits in its window and which one has focus, call session.view."},{"href":"/reference/api#sessionlist_clients","page":"Server API Methods","heading":"session.list_clients","text":"List the clients attached to the session. Field Required Description session_id Yes The session to act on. Only clients attached with session.attach or session.bootstrap are listed. A connection that only called session.attach_all isn't. To list every connected client, call client.list."},{"href":"/reference/api#sessionlower_layer","page":"Server API Methods","heading":"session.lower_layer","text":"Move a floating layer to the back, or just below another floating layer. Field Required Description session_id Yes The session to act on. layer_id Yes The floating layer to move back. relative_layer_id Another floating layer in the same window to place it just below. Leave it out to move it behind every other floating layer. layer_id must name a floating layer. Naming the window's layout, the layer of kind tiled in session.view, fails the call with bad_request. A floating layer never goes behind the window's layout. At the back, it sits behind every other floating layer and in front of the layout. If the layer is already in place, the call changes nothing. Focus and the layer's bounds never change. If the session doesn't exist, the call fails with session_not_found. If layer_id names no layer in the session, it fails with not_found, and if relative_layer_id is the same as layer_id, it fails with bad_request. If relative_layer_id names a layer of kind tiled, or a layer in another window, the call fails with bad_request. If it names no layer in the session, it fails with not_found."},{"href":"/reference/api#sessionmove","page":"Server API Methods","heading":"session.move","text":"Move a session to another place in the order that session.list returns. Field Required Description session_id Yes The session to move. before_session_id The session to place it just before. Leave it out to move the session to the end. The order is the same for every client. Each move that changes it sends session_moved with the same session_id and before_session_id. Moving a session to where it already is succeeds and changes nothing. The connection must be attached to the session it moves, with session.attach, session.bootstrap, or session.attach_all, or the call fails with conflict. If before_session_id names the session being moved, the call fails with bad_request. If either ID names no session, the call fails with session_not_found, and the message says which one."},{"href":"/reference/api#sessionmove_block","page":"Server API Methods","heading":"session.move_block","text":"Move a block, or place a detached one, in a new split beside another block. Field Required Description session_id Yes The session to act on. block_id Yes The block to move. It may be detached or placed in any window. anchor_block_id Yes The block to place it beside. It must be placed in a window. direction How to divide the anchor's space: 'horizontal' for side by side or 'vertical' for one above the other. Defaults to horizontal. side Where the moved block goes: 'before' for the left or top part, or 'after' for the right or bottom part. Defaults to after. ratio The fraction of the space that the before part gets, greater than 0 and less than 1. Defaults to 0.5. The block and the anchor can be in different windows or layers. If the block had focus in its window, focus moves with it and its new window becomes active. A detached block doesn't take focus. Zoom ends in every window the move changes. If the move empties a floating layer, the server removes it. If it empties a whole window, the server closes the window. A window's layout that empties while the window still has floating layers stays, with the same layer_id. Apart from the two splits named in the result, every split keeps its ID. If the session doesn't exist, the call fails with session_not_found. If block_id and anchor_block_id are the same, or ratio is out of range, it fails with bad_request. If either block isn't in the session, or the anchor isn't in a window, it fails with not_found. If the block is detached or in another window, and the anchor's window already has as many blocks as it allows, the call fails with conflict."},{"href":"/reference/api#sessionmove_window","page":"Server API Methods","heading":"session.move_window","text":"Move a window to another place in the session's window order. Field Required Description session_id Yes The session to act on. window_id Yes The window to move. before_window_id The window to place it just before. Leave it out to move the window to the end. The window order is the order of windows in session.view and of the tabs in Rex.app. session.focus_next_window and session.focus_previous_window follow it. The active window, focus, and the windows' blocks don't change. If the window is already in place, the call changes nothing. If the session doesn't exist, the call fails with session_not_found. If either window isn't in the session, it fails with not_found, and if before_window_id is the same as window_id, it fails with bad_request."},{"href":"/reference/api#sessionnew_block","page":"Server API Methods","heading":"session.new_block","text":"Create a block without placing it in a window. Place it later with session.move_block. Field Required Description session_id Yes The session to act on. flavor Yes The flavor of block to create, such as com.superlogical.terminal.shell. block_creator.list lists them. options Settings for the block's creator, in the shape of the flavor's options_schema from block_creator.list. label The block's label, up to 128 characters. Defaults to the flavor's short name, such as shell. The server passes options to the flavor's creator unchanged. The block starts detached: it runs, sends events, and answers method calls, but no window shows it. session.move_block can place it beside a block that is in a window. The calling client becomes the block's owner. A malformed flavor or label fails with bad_request. A flavor that no creator provides fails with not_found. If the server already has as many blocks as it allows, the call fails with conflict."},{"href":"/reference/api#sessionnew_layer","page":"Server API Methods","heading":"session.new_layer","text":"Create blocks in a new floating layer at the front of a window. Field Required Description session_id Yes The session to act on. window_id The window to add the floating layer to. Leave it out to use the active window. bounds Yes Where the new floating layer sits, as [x, y, w, h] fractions of the window's width and height. It must fit inside the window. layout Yes The blocks to create in the floating layer and how to arrange them. Each node holds either block, with a flavor and optional options and label, or split, with direction, before, after, and an optional ratio. focus Whether to make the window active and focus the first new block. Defaults to false. bounds is measured from the window's top-left corner. Its width and height must be greater than 0. A block node in layout starts one new block from a flavor, such as com.superlogical.terminal.shell. A split node divides its space in two and holds another node in each of before and after. In a split node, direction is horizontal to put before on the left of after, or vertical to put it on top. ratio is the fraction of the space that before gets, greater than 0 and less than 1. It defaults to 0.5. layout always starts new blocks. To move a block that is already in a window's layout into a floating layer, use session.float_block. The new layer starts in front of every other floating layer in the window. Either the whole layout is created or none of it is. If a block fails to start, the server closes the blocks it already started, and the call fails. If the session doesn't exist, the call fails with session_not_found. If window_id names a window that isn't in the session, or no creator provides a flavor, it fails with not_found. Bad bounds, or a malformed layout or flavor, fail with bad_request. If window_id is left out and the session has no windows, the call fails with conflict. If the call would go past a limit on blocks per window or blocks on the server, it fails with conflict."},{"href":"/reference/api#sessionnew_split","page":"Server API Methods","heading":"session.new_split","text":"Create blocks in a new split beside an existing block or split. Field Required Description session_id Yes The session to act on. anchor_id The block or split whose space to split. A block ID splits that one block. A split ID puts the new blocks beside that split and everything under it. Leave it out to split the focused block of window_id, or of the active window. Don't send it with window_id. window_id The window whose focused block to split. Send it only when anchor_id is left out. direction Yes How to divide the anchor's space: 'horizontal' for side by side or 'vertical' for one above the other. side Yes Where the new blocks go: 'before' for the left or top part, or 'after' for the right or bottom part. ratio The fraction of the space that the before part gets, greater than 0 and less than 1. Defaults to 0.5. layout Yes The blocks to create beside the anchor and how to arrange them. Each node holds either block, with a flavor and optional options and label, or split, with direction, before, after, and an optional ratio. focus Whether to make the anchor's window active and focus the first new block. Defaults to false. The server picks the block to split before it starts the new blocks, so a focus change while they start doesn't move the split. A block node in layout starts one new block from a flavor, such as com.superlogical.terminal.shell. A split node divides its space in two and holds another node in each of before and after. In a split node, direction is horizontal to put before on the left of after, or vertical to put it on top. ratio is the fraction of the space that before gets, greater than 0 and less than 1. It defaults to 0.5. Any zoom in the window ends, whether or not focus is set. If anchor_id is a split ID, the new split takes that split's place. The old split, with everything under it, goes on one side and layout on the other. Anchoring a layer's top split makes the new blocks span the whole layer. A layer with one block has no split, so anchor its block instead. Splits that already existed keep their IDs. Either the whole layout is created or none of it is. If a block fails to start, the server closes the blocks it already started, and the call fails. If the session doesn't exist, the call fails with session_not_found. If window_id names a window that isn't in the session, or no creator provides a flavor, it fails with not_found. Sending both anchor_id and window_id, a bad ratio, or a malformed layout or flavor fail with bad_request. If anchor_id names a block that isn't in a window, including a detached block, or a split that isn't in any window, the call fails with not_found. If both anchor_id and window_id are left out and the session has no windows, it fails with conflict. If the call would go past a limit on blocks per window or blocks on the server, it fails with conflict."},{"href":"/reference/api#sessionnew_window","page":"Server API Methods","heading":"session.new_window","text":"Create a window and the blocks in its layout. It becomes the active window by default. Field Required Description session_id Yes The session to act on. window_label The window's label, up to 128 characters. Leave it out for a label made from the window's ID, such as Window 89abcdef. layout The blocks to create in the window and how to arrange them. Required. Each node holds either block, with a flavor and optional options and label, or split, with direction, before, after, and an optional ratio. focus Whether to make the new window the active window. Defaults to true. A session with no active window always makes the new one active. A block node in layout starts one new block from a flavor, such as com.superlogical.terminal.shell. A split node divides its space in two and holds another node in each of before and after. In a split node, direction is horizontal to put before on the left of after, or vertical to put it on top. ratio is the fraction of the space that before gets, greater than 0 and less than 1. It defaults to 0.5. layout always starts new blocks. To place a detached block that already exists, use session.move_block. In the new window, the first block in block_ids has focus. Either the whole layout is created or none of it is. If a block fails to start, the server closes the blocks it already started, and the call fails. If the session doesn't exist, the call fails with session_not_found. If layout is missing or malformed, or a flavor is malformed, it fails with bad_request, and if no creator provides a flavor, it fails with not_found. If the call would go past a limit on windows per session, blocks per window, or blocks on the server, it fails with conflict."},{"href":"/reference/api#sessionown_block","page":"Server API Methods","heading":"session.own_block","text":"Claim blocks for the calling client. The result lists each block's owner. Field Required Description session_id Yes The session to act on. block_ids The blocks to claim. Leave it out or empty to claim every block in the session. mode How to claim them. 'take' makes the calling client the owner right away. 'request' makes it the owner only of blocks that have none, and it may get the rest when their owner gives them up or leaves. Defaults to take. When the clients attached to a session want different things for a block, such as a terminal's size, the block follows its owner. Owning a block gives no other rights over it. The calling client must be attached to the session, with session.attach or session.bootstrap. A connection that only called session.attach_all fails with bad_request. Either mode puts the caller in line for the block. When the owner gives it up, detaches, or loses its connection, the block passes to the connected client in line that claimed it most recently, with any take ahead of every request. An owner whose connection drops gets each of its blocks back when it reconnects, unless that block's owner has changed again since. blocks lists each block's owner after the call, in the order of block_ids, with a repeated ID listed once. When you claim every block, they are sorted by block_id. Every change of owner sends block_owner_changed. If a block in a window changes owner, the call also sends one session_view_changed, because session.view shows owners. A mode other than take or request fails with bad_request. If any ID in block_ids isn't a block in the session, the call fails with bad_request and changes nothing."},{"href":"/reference/api#sessionraise_layer","page":"Server API Methods","heading":"session.raise_layer","text":"Move a floating layer to the front, or just above another floating layer. Field Required Description session_id Yes The session to act on. layer_id Yes The floating layer to move forward. relative_layer_id Another floating layer in the same window to place it just above. Leave it out to move it in front of every other floating layer. layer_id must name a floating layer. Naming the window's layout, the layer of kind tiled in session.view, fails the call with bad_request. If the layer is already in place, the call changes nothing. Focus and the layer's bounds never change. If the session doesn't exist, the call fails with session_not_found. If layer_id names no layer in the session, it fails with not_found, and if relative_layer_id is the same as layer_id, it fails with bad_request. If relative_layer_id names a layer of kind tiled, or a layer in another window, the call fails with bad_request. If it names no layer in the session, it fails with not_found."},{"href":"/reference/api#sessionresize_layer","page":"Server API Methods","heading":"session.resize_layer","text":"Move or resize a floating layer. Field Required Description session_id Yes The session to act on. layer_id Yes The floating layer to move or resize. bounds Yes The layer's new position and size, as [x, y, w, h] fractions of the window's width and height. It must fit inside the window. layer_id must name a floating layer. Naming the window's layout, the layer of kind tiled in session.view, fails the call with bad_request. bounds is measured from the window's top-left corner. Its width and height must be greater than 0. Focus and the order of floating layers don't change. Setting the bounds the layer already has changes nothing. If the session doesn't exist, the call fails with session_not_found. If layer_id names no layer in the session, it fails with not_found, and bad bounds fail with bad_request."},{"href":"/reference/api#sessionresize_pane","page":"Server API Methods","heading":"session.resize_pane","text":"Move the border between two blocks to change their sizes. Field Required Description session_id Yes The session to act on. direction Yes The kind of split between the two blocks: 'horizontal' for a border between left and right, or 'vertical' for one between top and bottom. before_block_id Yes A block on the left of the border, or above it when direction is vertical. after_block_id Yes A block on the right of the border, or below it when direction is vertical. ratio Yes The fraction of the split's space that the before part gets, greater than 0 and less than 1. The two blocks pick the border. The server finds the innermost split in direction that has before_block_id on its left or top side and after_block_id on its right or bottom side. The blocks don't need to touch. ratio has no default. Unlike in session.new_split, 0 doesn't mean an even split. The blocks can be in any window, active or not. Focus doesn't change. The call always raises revision and sends session_view_changed, even if the border was already where you asked. If the session doesn't exist, the call fails with session_not_found. If the two block IDs are the same, or ratio is out of range, it fails with bad_request, and if either block isn't in any window, it fails with not_found. If the blocks are in different windows or layers, or no split between them matches, the call fails with bad_request."},{"href":"/reference/api#sessionset_block_label","page":"Server API Methods","heading":"session.set_block_label","text":"Set a block's label. Field Required Description session_id Yes The session to act on. block_id The block to relabel. Required. label The new label, up to 128 characters. An empty or missing label restores a default made from the block's ID. block_id is required. Leaving it out fails with bad_request. The server trims whitespace from both ends of label. An empty label restores a default made from the block's ID, such as Block 89abcdef, rather than the flavor name a new block starts with. A label longer than 128 characters, or one that contains a control character, fails with bad_request. Relabeling a block that is in a window sends session_view_changed. A detached block isn't part of the view, so relabeling it sends no event and leaves revision unchanged. If block_id names a block that isn't in the session, the call fails with not_found."},{"href":"/reference/api#sessionset_label","page":"Server API Methods","heading":"session.set_label","text":"Set the session's label. Field Required Description session_id Yes The session to act on. label The new label, up to 128 characters. An empty or missing label restores the label the server chose when it created the session. The server trims whitespace from both ends of label. An empty label restores the label the server chose when it created the session, such as quiet-meadow. It does so even if the session was created with a label. A label longer than 128 characters, or one that contains a control character, fails with bad_request. A new label sends session_label_changed, then session_view_changed. Setting the label the session already has changes nothing."},{"href":"/reference/api#sessionset_layer_state","page":"Server API Methods","heading":"session.set_layer_state","text":"Minimize a floating layer or return it to normal, optionally raising and focusing it. Field Required Description session_id Yes The session to act on. layer_id Yes The layer to change. state Yes The state to set: 'normal' or 'minimized'. focus Whether to also raise the layer, focus the block last focused in it, and make its window active, all in one change. Needs state to be 'normal' and the layer to have a block. Defaults to false. Only a floating layer can be minimized. Minimizing the window's layout, the layer of kind tiled in session.view, fails with bad_request. Setting it to normal succeeds and changes nothing. With focus set to true, the layer is also raised, the block last focused in it takes focus, or its first block, and its window becomes active, all in one revision. This also works on a layer that is already normal and on the tiled layer. focus with minimized, or on a layer with no blocks, fails with bad_request and changes nothing. state must be normal or minimized. Any other value fails with bad_request. A minimized layer stays in session.view with state set to minimized, its blocks keep running, and it keeps its bounds and its place in the stack. state is left out for a normal layer. Minimizing the layer that has focus moves focus to the layer used before it, or to the nearest visible pane, and clears zoom. Returning a layer to normal leaves focus where it is. Setting the state the layer already has changes nothing. A window must keep a visible pane, so minimizing the layer that holds its last visible panes fails with bad_request. If the session doesn't exist, the call fails with session_not_found. If layer_id names no layer in the session, it fails with not_found."},{"href":"/reference/api#sessionset_window_label","page":"Server API Methods","heading":"session.set_window_label","text":"Set a window's label. Field Required Description session_id Yes The session to act on. window_id Yes The window to relabel. label The new label, up to 128 characters. An empty or missing label restores a default made from the window's ID. The server trims whitespace from both ends of label. An empty label restores a default made from the window's ID, such as Window 89abcdef. A label longer than 128 characters, or one that contains a control character, fails with bad_request. A new label sends window_label_changed, then session_view_changed. Setting the label the window already has changes nothing. If window_id names a window that isn't in the session, the call fails with not_found."},{"href":"/reference/api#sessionswap_blocks","page":"Server API Methods","heading":"session.swap_blocks","text":"Swap the places of two blocks. Field Required Description session_id Yes The session to act on. block_id Yes One of the two blocks to swap. other_block_id Yes The other block. The order of the two doesn't matter. Both blocks must be in a window. They can be in different windows or layers. Only the two blocks move. Splits, their sizes and IDs, floating layers, and the window order stay the same. If one of the blocks has focus in the active window, focus follows it to its new place, and that place's window becomes active. If the session doesn't exist, the call fails with session_not_found. If block_id and other_block_id are the same, it fails with bad_request. If either block isn't in any window, including a detached block, it fails with not_found."},{"href":"/reference/api#sessionview","page":"Server API Methods","heading":"session.view","text":"Get the session's windows, their layouts, and their blocks in one snapshot. Field Required Description session_id Yes The session to act on. revision starts at zero for a new session and goes up by one each time the view changes. Compare revisions only within one session, and keep the view with the highest revision. A session_view_changed event carries only the new revision, not the view. These events can arrive out of revision order, so call session.view only when an event's revision is higher than the one in the view you hold. window_created, window_closed, window_label_changed, active_window_changed, and session_label_changed arrive before the session_view_changed event for the same change. The view holds only blocks that are in a window. session.list_blocks also lists detached blocks. The view describes structure, not content. To read what a block shows, call one of its own methods, such as com.superlogical.terminal.format."},{"href":"/reference/api#sessionzoom_block","page":"Server API Methods","heading":"session.zoom_block","text":"Zoom a block to fill its window, or undo the zoom. Field Required Description session_id Yes The session to act on. block_id The block to zoom. It takes focus, and its window becomes active. Leave it out to use the focused block of window_id, or of the active window. window_id A window whose focused block to zoom. The active window doesn't change. Send it only when block_id is left out. zoom Set true to zoom or false to restore the layout. Leave it out to switch between the two. With block_id, the server first focuses the block and makes its window active, as session.focus_block does. With window_id, focus and the active window don't change. Zoom changes only what is shown. The layout stays the same, and the zoom ends when focus moves to another block in the window. If the session doesn't exist, the call fails with session_not_found. If block_id names a block that isn't in any window, or window_id names a window that isn't in the session, it fails with not_found. Sending both fails with bad_request. If both block_id and window_id are left out and the session has no windows, the call fails with conflict."},{"href":"/reference/api#target","page":"Server API Methods","heading":"target","text":""},{"href":"/reference/api#targetcomplete","page":"Server API Methods","heading":"target.complete","text":"List the labels, window positions, and short IDs that start with a prefix. Use it to complete a name as someone types it. Field Required Description kinds Yes What to complete: session, window, block, or client, with the same rules as in target.resolve. Completions come back in this order. prefix Yes The text typed so far. It matches the start of labels (ignoring case), window positions, and short IDs. An empty prefix matches everything. context Where the caller is. Only session_id is used: without scope.session, windows and blocks come from that session. scope Limits completion to one session, or block completion to one window, as in target.resolve. kinds, scope, and context.session_id choose what to search the same way as in target.resolve, and the call fails with bad_request in the same cases. prefix matches the start of labels, ignoring case, and label completions keep the label's own case. White space around prefix is ignored. A control character inside it, such as a tab, fails the call with bad_request. Windows also match by position, so the prefix 1 matches windows 1, 10, 11, and so on. An empty prefix matches every position. An empty prefix, or one made only of hexadecimal digits, also matches the start of short IDs. A short ID is the last six hexadecimal digits of an ID, such as 12bef5, and target.resolve accepts it as the end of an ID. One thing can appear more than once, such as a window whose label and short ID both start with the prefix. Completions are grouped by kind, in the order of kinds. Within each kind, labels come first, sorted ignoring case, then window positions in order, then short IDs in sorted order. If scope.session or scope.window matches nothing or several things, completions is empty. The call doesn't fail. Detached blocks are included unless scope.window is set. To complete a client, set kinds to [\"client\"]. Clients complete by label and short ID, and each target's where names the program, such as Rex.app 1.6.0."},{"href":"/reference/api#targetresolve","page":"Server API Methods","heading":"target.resolve","text":"Look up sessions, windows, blocks, or clients by ID, label, or position. Each query gets its own result, which says whether it matched one thing, nothing, or several. Field Required Description kinds Yes What each query may name: session, window, block, or client. List each kind once, and list client only on its own. queries Yes The names to look up. Each is a full ID, a label, a window position, the end of an ID, or an empty string for the default. context Where the caller is, such as the terminal a script runs in. IDs in it that no longer exist are ignored. scope Limits the search to one session, or a block search to one window. Not allowed when kinds includes session. List at least one kind in kinds, each only once, and client only on its own, or the call fails with bad_request. A kind the server doesn't know fails the same way, which can happen when the server is older than your client. Send at least one query, or the call fails with bad_request. Each query gets one entry in results, in the same order. A query that matches nothing or several things doesn't fail the call: its status is not_found or ambiguous. The server tries each query in four steps and stops at the first step that matches anything: full ID, label (ignoring case), window position, then the end of an ID (4 to 32 hexadecimal digits). So a window labeled 2 wins over the second window. If the winning step matches more than one thing, the result is ambiguous. A query that starts like a full ID, such as block:, is only tried as a full ID. Its result is not_found if the ID is malformed, if kinds doesn't include that kind, or if nothing has that ID. Only windows have positions. 1 is the first window in its session, in the order Rex.app shows them as tabs. When several sessions are searched, a position can match a window in each one. When kinds lists more than one kind, one query can match different kinds of things, such as a session and a window with the same label. The result is then ambiguous, and each candidate's kind says what it is. Without scope.session, windows and blocks are searched only in the session named by context.session_id. If that's left out or the session no longer exists, every session is searched. This applies to full IDs too, so the full ID of a block in another session is not_found. If a scope matches nothing or several things, every result gets that not_found or ambiguous status, and its reason says which scope failed. For an ambiguous scope, candidates lists what the scope could mean. An empty query, or one of only spaces, asks for a default, such as the block a script runs in. When kinds lists several kinds, an empty query gets the default for the most specific one: block, then window, then session. An empty session query selects context.session_id, or the only session if there's just one. With no sessions it's not_found, and with several it's ambiguous. An empty window query selects the window that holds context.block_id, or else the active window of the session being searched. If several sessions are searched, it's ambiguous even with context.block_id set, and candidates lists the sessions to choose from. An empty block query selects context.block_id if that block is among those searched. Otherwise it selects the focused block of the window an empty window query would choose. With scope.window, it ignores context.block_id and selects that window's focused block. Block searches include detached blocks, which are running but aren't placed in any window. scope.window leaves them out. A block's where reads window 2 or detached. When several sessions are searched, it names the session too, as in window 2 of work, and a window's where reads in work. To look up a connected client, such as Rex.app, set kinds to [\"client\"]. A client query matches a full ID, a label, or the end of an ID, since clients have no positions. Every connected client is searched, unless scope.session limits the search to clients attached to that session. An empty client query selects context.client_id if that client is among those searched. Otherwise it selects the only client that reported its kind as app when it connected, such as Rex.app. With no app it's not_found, and with several it's ambiguous. scope.window is looked up among the sessions being searched. The call fails with bad_request if kinds includes session and scope is sent, even as an empty object, or if scope.window is set and kinds isn't just block. A query or scope that contains a control character, such as a newline, fails the same way."},{"href":"/reference/mac-actions","page":"Mac Actions","heading":"","text":"This page lists every action in the Rex for Mac command palette. Use the name to bind a key or to run it with rex do. Anything tagged Bleeding Edge is on that update channel and not yet in Preview."},{"href":"/reference/mac-actions#action-client-window-new","page":"Mac Actions","heading":"New Window","text":"New Window client.window.new create workspace window Default shortcut:cmd+n"},{"href":"/reference/mac-actions#action-client-window-close","page":"Mac Actions","heading":"Close Window","text":"Close Window client.window.close close workspace window Default shortcut:shift+cmd+w"},{"href":"/reference/mac-actions#action-client-window-fullscreen-toggle","page":"Mac Actions","heading":"Toggle Full Screen","text":"Toggle Full Screen client.window.fullscreen.toggle full screen fullscreen enter exit window Default shortcut:ctrl+cmd+f"},{"href":"/reference/mac-actions#action-client-close","page":"Mac Actions","heading":"Close","text":"Close Closes the focused pane, else the selected tab, else the window. client.close close pane tab window focused Default shortcut:cmd+w"},{"href":"/reference/mac-actions#window","page":"Mac Actions","heading":"Window","text":"Action Name Default Shortcut"},{"href":"/reference/mac-actions#action-client-palette-toggle","page":"Mac Actions","heading":"Command Palette…","text":"Command Palette… client.palette.toggle command palette search commands actions Default shortcut:shift+cmd+p"},{"href":"/reference/mac-actions#action-client-settings","page":"Mac Actions","heading":"Settings…","text":"Settings… client.settings preferences settings options configure Default shortcut:cmd+,"},{"href":"/reference/mac-actions#action-client-diagnostics","page":"Mac Actions","heading":"Rex Diagnostics","text":"Rex Diagnostics client.diagnostics diagnostics logs debug troubleshoot server report Default shortcut:shift+cmd+,"},{"href":"/reference/mac-actions#action-client-onboarding","page":"Mac Actions","heading":"Open Onboarding Session…","text":"Open Onboarding Session… client.onboarding onboarding welcome setup get started tour walkthrough migrate import keybindings theme appearance ghostty tmux zellij agent"},{"href":"/reference/mac-actions#action-client-permissions","page":"Mac Actions","heading":"Grant System Permissions…","text":"Grant System Permissions… client.permissions permissions privacy security full disk access screen recording system audio grant allow system settings tcc"},{"href":"/reference/mac-actions#action-client-visibility-toggle","page":"Mac Actions","heading":"Toggle Visibility","text":"Toggle Visibility Shows Rex from any app, or hides it when it's in front. client.visibility.toggle show hide rex global hotkey summon bring to front focus toggle_visibility"},{"href":"/reference/mac-actions#action-client-version-copy","page":"Mac Actions","heading":"Copy Version Info","text":"Copy Version Info Copies the app and server versions to the clipboard. client.version.copy version build number copy clipboard about app server release"},{"href":"/reference/mac-actions#action-client-debug-copy","page":"Mac Actions","heading":"Copy Debug Info","text":"Copy Debug Info Copies a report of the app, system, server, and host state to the clipboard. client.debug.copy debug debugging info report copy clipboard diagnostics troubleshoot support bug commit build environment"},{"href":"/reference/mac-actions#action-client-pair-device","page":"Mac Actions","heading":"Pair Device with QR Code…","text":"Pair Device with QR Code… client.pair_device pair device qr code scan iphone ipad ios mobile tailscale connect link"},{"href":"/reference/mac-actions#action-client-update-accept","page":"Mac Actions","heading":"Install Update","text":"Install Update The menu shows the current update's own title; this is the static fallback. client.update.accept update install retry app application version upgrade sparkle Default shortcut:shift+cmd+u"},{"href":"/reference/mac-actions#action-client-update-accept-when-idle","page":"Mac Actions","heading":"Relaunch when Idle","text":"Relaunch when Idle Relaunches into a downloaded update once Rex's windows are out of sight. client.update.accept_when_idle update install relaunch restart idle later automatic Default shortcut:alt+shift+cmd+u"},{"href":"/reference/mac-actions#action-client-update-check","page":"Mac Actions","heading":"Check for Updates in Background","text":"Check for Updates in Background Checks for a Rex update without showing anything, and returns what it found. client.update.check update check background quiet silent app application version sparkle"},{"href":"/reference/mac-actions#action-client-update-status","page":"Mac Actions","heading":"Report Update Status","text":"Report Update Status Returns the running version, the update settings, and the updater's state. client.update.status update status version build channel feed state report sparkle"},{"href":"/reference/mac-actions#action-client-update-install","page":"Mac Actions","heading":"Install Update and Relaunch","text":"Install Update and Relaunch Downloads the available Rex update if needed, installs it, and relaunches without asking. client.update.install update install download relaunch restart upgrade app application sparkle unattended"},{"href":"/reference/mac-actions#action-client-update-set-automation","page":"Mac Actions","heading":"Set Update Automation","text":"Set Update Automation Chooses whether Rex only offers updates, downloads them, or installs them. client.update.set_automation update automatic automation download install notify setting preference"},{"href":"/reference/mac-actions#action-client-update-set-channel","page":"Mac Actions","heading":"Set Update Channel","text":"Set Update Channel Chooses the release channel Rex updates from. client.update.set_channel update channel feed preview snapshot bleeding edge release setting preference"},{"href":"/reference/mac-actions#action-client-notice-dismiss","page":"Mac Actions","heading":"Dismiss Notice","text":"Dismiss Notice client.notice.dismiss dismiss notice banner alert hide Default shortcut:shift+cmd+."},{"href":"/reference/mac-actions#action-client-shortcuts-edit","page":"Mac Actions","heading":"Change Shortcut…","text":"Change Shortcut… client.shortcuts.edit keyboard key binding bindings keybinding keybindings rebind record hotkey shortcut"},{"href":"/reference/mac-actions#action-client-agents-setup","page":"Mac Actions","heading":"Install Rex for Agents","text":"Install Rex for Agents Installs the Rex skill and hooks for Claude Code, Codex, and other agents. client.agents.setup rex skill install agent agents coding ai claude code codex cursor copilot gemini hook hooks setup"},{"href":"/reference/mac-actions#action-client-server-restart","page":"Mac Actions","heading":"Restart Server…","text":"Restart Server… Stops the local Rex server and starts it again. client.server.restart restart reload relaunch reboot stop start server rex daemon process local"},{"href":"/reference/mac-actions#app","page":"Mac Actions","heading":"App","text":"Action Name Default Shortcut"},{"href":"/reference/mac-actions#action-session-new","page":"Mac Actions","heading":"New Session","text":"New Session session.new create session add Default shortcut:shift+cmd+n"},{"href":"/reference/mac-actions#action-session-switch","page":"Mac Actions","heading":"Change Session…","text":"Change Session… session.switch session switch jump go Default shortcut:cmd+k"},{"href":"/reference/mac-actions#action-session-select","page":"Mac Actions","heading":"Go to Session","text":"Go to Session session.select session select show jump go identifier id"},{"href":"/reference/mac-actions#action-session-next","page":"Mac Actions","heading":"Next Session","text":"Next Session session.next cycle session forward"},{"href":"/reference/mac-actions#action-session-previous","page":"Mac Actions","heading":"Previous Session","text":"Previous Session session.previous cycle session back"},{"href":"/reference/mac-actions#action-session-rename","page":"Mac Actions","heading":"Rename Session","text":"Rename Session session.rename rename session name title"},{"href":"/reference/mac-actions#action-session-close","page":"Mac Actions","heading":"Close Session","text":"Close Session session.close close kill destroy delete session"},{"href":"/reference/mac-actions#action-session-refresh","page":"Mac Actions","heading":"Refresh Sessions","text":"Refresh Sessions session.refresh reload sync sessions Default shortcut:cmd+r"},{"href":"/reference/mac-actions#action-session-collapse","page":"Mac Actions","heading":"Collapse Session","text":"Collapse Session session.collapse collapse fold hide session sidebar tabs"},{"href":"/reference/mac-actions#action-session-expand","page":"Mac Actions","heading":"Expand Session","text":"Expand Session session.expand expand unfold show open session sidebar tabs"},{"href":"/reference/mac-actions#action-session-collapse-others","page":"Mac Actions","heading":"Collapse Other Sessions","text":"Collapse Other Sessions session.collapse_others collapse fold hide other all sessions sidebar focus"},{"href":"/reference/mac-actions#session","page":"Mac Actions","heading":"Session","text":"Action Name Default Shortcut"},{"href":"/reference/mac-actions#action-client-host-switch","page":"Mac Actions","heading":"Switch Host","text":"Switch Host client.host.switch host remote machine server switch go"},{"href":"/reference/mac-actions#action-client-host-add","page":"Mac Actions","heading":"Add Remote Host…","text":"Add Remote Host… client.host.add host remote add server machine url http https connect"},{"href":"/reference/mac-actions#action-client-host-remove","page":"Mac Actions","heading":"Remove Host…","text":"Remove Host… client.host.remove remove forget delete disconnect host remote server machine"},{"href":"/reference/mac-actions#host","page":"Mac Actions","heading":"Host","text":"Action Name Default Shortcut"},{"href":"/reference/mac-actions#action-client-tab-new","page":"Mac Actions","heading":"New Tab","text":"New Tab client.tab.new create tab window add Default shortcut:cmd+t"},{"href":"/reference/mac-actions#action-client-tab-close","page":"Mac Actions","heading":"Close Tab","text":"Close Tab client.tab.close close tab window Default shortcut:alt+cmd+w"},{"href":"/reference/mac-actions#action-client-tab-close-others","page":"Mac Actions","heading":"Close Other Tabs","text":"Close Other Tabs client.tab.close_others close other tabs windows kill only keep"},{"href":"/reference/mac-actions#action-client-tab-rename","page":"Mac Actions","heading":"Rename Tab","text":"Rename Tab client.tab.rename rename tab window name title"},{"href":"/reference/mac-actions#action-client-tab-next","page":"Mac Actions","heading":"Show Next Tab","text":"Show Next Tab client.tab.next cycle tab window forward Default shortcut:ctrl+tab, shift+cmd+]"},{"href":"/reference/mac-actions#action-client-tab-previous","page":"Mac Actions","heading":"Show Previous Tab","text":"Show Previous Tab client.tab.previous cycle tab window back Default shortcut:ctrl+shift+tab, shift+cmd+["},{"href":"/reference/mac-actions#action-client-tab-goto","page":"Mac Actions","heading":"Go to Tab","text":"Go to Tab client.tab.goto tab window select number index jump go Default shortcut:cmd+[Digit1], cmd+[Digit2], cmd+[Digit3], cmd+[Digit4], cmd+[Digit5], cmd+[Digit6], cmd+[Digit7], cmd+[Digit8], cmd+[Digit9]"},{"href":"/reference/mac-actions#action-client-tab-move","page":"Mac Actions","heading":"Move Tab","text":"Move Tab client.tab.move move tab window reorder"},{"href":"/reference/mac-actions#action-client-tab-move-backward","page":"Mac Actions","heading":"Move Tab Backward","text":"Move Tab Backward client.tab.move.backward move tab window reorder left up earlier Default shortcut:ctrl+shift+pageup"},{"href":"/reference/mac-actions#action-client-tab-move-forward","page":"Mac Actions","heading":"Move Tab Forward","text":"Move Tab Forward client.tab.move.forward move tab window reorder right down later Default shortcut:ctrl+shift+pagedown"},{"href":"/reference/mac-actions#action-client-tab-move-to-new-window","page":"Mac Actions","heading":"Move Tab to New Window","text":"Move Tab to New Window client.tab.move_to_new_window move tab new window tear off detach separate pop out own"},{"href":"/reference/mac-actions#tab","page":"Mac Actions","heading":"Tab","text":"Action Name Default Shortcut"},{"href":"/reference/mac-actions#action-pane-split","page":"Mac Actions","heading":"Split Pane","text":"Split Pane pane.split pane split terminal divide new"},{"href":"/reference/mac-actions#action-pane-split-left","page":"Mac Actions","heading":"Split Pane Left","text":"Split Pane Left pane.split.left pane split terminal divide new left Default shortcut:shift+cmd+left"},{"href":"/reference/mac-actions#action-pane-split-right","page":"Mac Actions","heading":"Split Pane Right","text":"Split Pane Right pane.split.right pane split terminal divide new right Default shortcut:cmd+d, shift+cmd+right"},{"href":"/reference/mac-actions#action-pane-split-up","page":"Mac Actions","heading":"Split Pane Up","text":"Split Pane Up pane.split.up pane split terminal divide new above up Default shortcut:shift+cmd+up"},{"href":"/reference/mac-actions#action-pane-split-down","page":"Mac Actions","heading":"Split Pane Down","text":"Split Pane Down pane.split.down pane split terminal divide new below down Default shortcut:shift+cmd+d, shift+cmd+down"},{"href":"/reference/mac-actions#action-pane-split-auto","page":"Mac Actions","heading":"Split Pane Best Fit","text":"Split Pane Best Fit pane.split.auto pane split terminal divide new automatic best fit Default shortcut:cmd+enter"},{"href":"/reference/mac-actions#action-pane-focus","page":"Mac Actions","heading":"Focus Pane","text":"Focus Pane pane.focus pane focus navigate"},{"href":"/reference/mac-actions#action-pane-focus-left","page":"Mac Actions","heading":"Focus Pane Left","text":"Focus Pane Left pane.focus.left pane focus navigate left Default shortcut:alt+cmd+left"},{"href":"/reference/mac-actions#action-pane-focus-right","page":"Mac Actions","heading":"Focus Pane Right","text":"Focus Pane Right pane.focus.right pane focus navigate right Default shortcut:alt+cmd+right"},{"href":"/reference/mac-actions#action-pane-focus-up","page":"Mac Actions","heading":"Focus Pane Up","text":"Focus Pane Up pane.focus.up pane focus navigate up above Default shortcut:alt+cmd+up"},{"href":"/reference/mac-actions#action-pane-focus-down","page":"Mac Actions","heading":"Focus Pane Down","text":"Focus Pane Down pane.focus.down pane focus navigate down below Default shortcut:alt+cmd+down"},{"href":"/reference/mac-actions#action-pane-focus-next","page":"Mac Actions","heading":"Focus Next Pane","text":"Focus Next Pane pane.focus_next pane focus next forward Default shortcut:cmd+]"},{"href":"/reference/mac-actions#action-pane-focus-previous","page":"Mac Actions","heading":"Focus Previous Pane","text":"Focus Previous Pane pane.focus_previous pane focus previous back Default shortcut:cmd+["},{"href":"/reference/mac-actions#action-pane-move","page":"Mac Actions","heading":"Move Pane","text":"Move Pane pane.move pane move swap rearrange"},{"href":"/reference/mac-actions#action-pane-move-left","page":"Mac Actions","heading":"Move Pane Left","text":"Move Pane Left pane.move.left pane move swap rearrange left Default shortcut:alt+shift+cmd+left"},{"href":"/reference/mac-actions#action-pane-move-right","page":"Mac Actions","heading":"Move Pane Right","text":"Move Pane Right pane.move.right pane move swap rearrange right Default shortcut:alt+shift+cmd+right"},{"href":"/reference/mac-actions#action-pane-move-up","page":"Mac Actions","heading":"Move Pane Up","text":"Move Pane Up pane.move.up pane move swap rearrange up above Default shortcut:alt+shift+cmd+up"},{"href":"/reference/mac-actions#action-pane-move-down","page":"Mac Actions","heading":"Move Pane Down","text":"Move Pane Down pane.move.down pane move swap rearrange down below Default shortcut:alt+shift+cmd+down"},{"href":"/reference/mac-actions#action-pane-float","page":"Mac Actions","heading":"Float Pane","text":"Float Pane pane.float pane float floating window dock undock detach overlay pop out tile Default shortcut:alt+cmd+enter"},{"href":"/reference/mac-actions#action-pane-new-floating","page":"Mac Actions","heading":"New Floating Pane","text":"New Floating Pane pane.new_floating pane new create open float floating window terminal overlay popup scratchpad"},{"href":"/reference/mac-actions#action-pane-minimize-floating","page":"Mac Actions","heading":"Minimize Floating Window","text":"Minimize Floating Window pane.minimize_floating pane floating window hide show toggle minimize minimise restore taskbar capsule stash scratchpad Default shortcut:alt+cmd+m"},{"href":"/reference/mac-actions#action-pane-minimize-all-floating","page":"Mac Actions","heading":"Minimize Floating Windows","text":"Minimize Floating Windows pane.minimize_all_floating pane floating windows all hide show toggle minimize minimise restore taskbar capsule stash scratchpad Default shortcut:alt+shift+cmd+enter"},{"href":"/reference/mac-actions#action-pane-zoom","page":"Mac Actions","heading":"Zoom Pane","text":"Zoom Pane pane.zoom pane zoom maximize restore terminal Default shortcut:shift+cmd+enter"},{"href":"/reference/mac-actions#action-pane-close","page":"Mac Actions","heading":"Close Pane","text":"Close Pane pane.close pane close remove terminal Default shortcut:cmd+w"},{"href":"/reference/mac-actions#action-pane-close-others","page":"Mac Actions","heading":"Close Other Panes","text":"Close Other Panes pane.close_others close other panes kill only keep focused"},{"href":"/reference/mac-actions#action-pane-balance","page":"Mac Actions","heading":"Balance Splits","text":"Balance Splits pane.balance pane split balance equalize equalise evenly distribute resize layout"},{"href":"/reference/mac-actions#action-pane-resize","page":"Mac Actions","heading":"Resize Pane","text":"Resize Pane pane.resize pane resize grow shrink divider wider narrower taller shorter"},{"href":"/reference/mac-actions#action-pane-move-to-new-tab","page":"Mac Actions","heading":"Move Pane to New Tab","text":"Move Pane to New Tab pane.move_to_new_tab pane block move new window tab detach split"},{"href":"/reference/mac-actions#action-pane-move-to-tab","page":"Mac Actions","heading":"Move Pane to Tab…","text":"Move Pane to Tab… pane.move_to_tab pane block move tab window send other existing join"},{"href":"/reference/mac-actions#action-pane-go-to-directory","page":"Mac Actions","heading":"Go to Directory…","text":"Go to Directory… pane.go_to_directory go to directory folder path cd new tab open navigate Default shortcut:shift+cmd+g"},{"href":"/reference/mac-actions#action-pane-go-to","page":"Mac Actions","heading":"Go to Anything…","text":"Go to Anything… pane.go_to go to anything pane block tab session agent jump switch navigate find search tree overview Default shortcut:cmd+p"},{"href":"/reference/mac-actions#action-pane-view-activity","page":"Mac Actions","heading":"View Activity…","text":"View Activity… pane.view_activity view activity active working blocked failed done agents programs status priority"},{"href":"/reference/mac-actions#action-pane-insert-path","page":"Mac Actions","heading":"Insert Path…","text":"Insert Path… pane.insert_path insert path file folder directory cursor type paste complete browse Default shortcut:ctrl+cmd+i"},{"href":"/reference/mac-actions#action-pane-copy-id","page":"Mac Actions","heading":"Copy Pane ID","text":"Copy Pane ID Copies the focused pane's block ID to the clipboard. pane.copy_id pane block id identifier copy clipboard target agent capture"},{"href":"/reference/mac-actions#action-pane-rename","page":"Mac Actions","heading":"Rename Pane","text":"Rename Pane pane.rename rename pane block name title label"},{"href":"/reference/mac-actions#action-pane-paste","page":"Mac Actions","heading":"Paste into Pane","text":"Paste into Pane Pastes the clipboard into the focused terminal. pane.paste paste clipboard insert text terminal"},{"href":"/reference/mac-actions#action-pane-copy","page":"Mac Actions","heading":"Copy from Pane","text":"Copy from Pane Copies the focused terminal's selection to the clipboard. pane.copy copy selection clipboard text terminal"},{"href":"/reference/mac-actions#action-pane-select-all","page":"Mac Actions","heading":"Select All in Pane","text":"Select All in Pane Selects everything in the focused terminal, including its scrollback. pane.select_all select all selection text terminal scrollback"},{"href":"/reference/mac-actions#action-pane-send-key","page":"Mac Actions","heading":"Send Key","text":"Send Key Sends one key to the focused terminal, as if it had been pressed there. pane.send_key send key terminal press chord binding Default shortcut:cmd+backspace, cmd+left, cmd+right, alt+left, alt+right"},{"href":"/reference/mac-actions#action-client-key-forward","page":"Mac Actions","heading":"Send Key to Server","text":"Send Key to Server Sends the key that performed it to the focused terminal, for a script to answer. client.key.forward send key forward server script lua binding"},{"href":"/reference/mac-actions#action-client-prompt-previous","page":"Mac Actions","heading":"Jump to Previous Prompt","text":"Jump to Previous Prompt client.prompt.previous jump scroll previous prompt command mark shell integration up back history Default shortcut:cmd+up"},{"href":"/reference/mac-actions#action-client-prompt-next","page":"Mac Actions","heading":"Jump to Next Prompt","text":"Jump to Next Prompt client.prompt.next jump scroll next prompt command mark shell integration down forward Default shortcut:cmd+down"},{"href":"/reference/mac-actions#action-client-scroll-page-up","page":"Mac Actions","heading":"Scroll Page Up","text":"Scroll Page Up client.scroll.page_up scroll page up back scrollback history"},{"href":"/reference/mac-actions#action-client-scroll-page-down","page":"Mac Actions","heading":"Scroll Page Down","text":"Scroll Page Down client.scroll.page_down scroll page down forward scrollback history"},{"href":"/reference/mac-actions#action-client-quick-look","page":"Mac Actions","heading":"Quick Look","text":"Quick Look Previews the files and web links on screen in Quick Look. client.quick_look quick look preview file image link path url web pointer hover Default shortcut:cmd+y"},{"href":"/reference/mac-actions#pane","page":"Mac Actions","heading":"Pane","text":"Action Name Default Shortcut"},{"href":"/reference/mac-actions#action-client-mode-enter","page":"Mac Actions","heading":"Enter Mode","text":"Enter Mode Makes the keys a mode binds perform, until the mode is left. client.mode.enter mode enter key table prefix resize modal"},{"href":"/reference/mac-actions#action-client-mode-exit","page":"Mac Actions","heading":"Exit Mode","text":"Exit Mode client.mode.exit mode exit leave quit key table escape"},{"href":"/reference/mac-actions#action-client-mode-exit-all","page":"Mac Actions","heading":"Exit All Modes","text":"Exit All Modes client.mode.exit_all mode exit leave quit all key table escape"},{"href":"/reference/mac-actions#keys","page":"Mac Actions","heading":"Keys","text":"Action Name Default Shortcut"},{"href":"/reference/mac-actions#action-client-find-open","page":"Mac Actions","heading":"Find…","text":"Find… client.find.open find search text scrollback highlight Default shortcut:cmd+f"},{"href":"/reference/mac-actions#action-client-find-next","page":"Mac Actions","heading":"Find Next","text":"Find Next client.find.next find search next forward match Default shortcut:cmd+g"},{"href":"/reference/mac-actions#action-client-find-previous","page":"Mac Actions","heading":"Find Previous","text":"Find Previous client.find.previous find search previous back match Default shortcut:shift+cmd+g"},{"href":"/reference/mac-actions#find","page":"Mac Actions","heading":"Find","text":"Action Name Default Shortcut"},{"href":"/reference/mac-actions#action-client-font-increase","page":"Mac Actions","heading":"Increase Font Size","text":"Increase Font Size client.font.increase terminal font size larger bigger increase zoom text typography Default shortcut:shift+cmd+=, cmd+="},{"href":"/reference/mac-actions#action-client-font-decrease","page":"Mac Actions","heading":"Decrease Font Size","text":"Decrease Font Size client.font.decrease terminal font size smaller decrease zoom text typography Default shortcut:cmd+-"},{"href":"/reference/mac-actions#action-client-font-reset","page":"Mac Actions","heading":"Reset Font Size","text":"Reset Font Size client.font.reset terminal font size reset default original text typography Default shortcut:cmd+[Digit0]"},{"href":"/reference/mac-actions#action-client-sidebar-toggle","page":"Mac Actions","heading":"Toggle Sidebar","text":"Toggle Sidebar client.sidebar.toggle sidebar collapse expand fold hide show tabs Default shortcut:cmd+/"},{"href":"/reference/mac-actions#action-client-sidebar-activity-toggle","page":"Mac Actions","heading":"Toggle Activity","text":"Toggle Activity client.sidebar.activity.toggle activity mode view filter working waiting attention programs status sidebar tabs Default shortcut:cmd+."},{"href":"/reference/mac-actions#action-client-sidebar-activity-by-session","page":"Mac Actions","heading":"Show Activity by Session","text":"Show Activity by Session client.sidebar.activity.by_session activity mode view filter group grouping by session sessions sidebar tabs"},{"href":"/reference/mac-actions#action-client-sidebar-activity-by-priority","page":"Mac Actions","heading":"Show Activity by Priority","text":"Show Activity by Priority client.sidebar.activity.by_priority activity mode view filter group grouping by priority blocked failed done working idle sidebar tabs"},{"href":"/reference/mac-actions#action-client-sidebar-filter-focus","page":"Mac Actions","heading":"Focus Tab Filter","text":"Focus Tab Filter client.sidebar.filter.focus filter search find type query focus keyboard sidebar activity tabs sessions hosts"},{"href":"/reference/mac-actions#action-client-tab-style-toggle","page":"Mac Actions","heading":"Toggle Toolbar Layout","text":"Toggle Toolbar Layout client.tab_style.toggle toolbar layout tab style orientation sidebar horizontal vertical view Default shortcut:shift+cmd+s"},{"href":"/reference/mac-actions#action-client-toolbar-reveal","page":"Mac Actions","heading":"Reveal Toolbar","text":"Reveal Toolbar client.toolbar.reveal show reveal peek toolbar tab bar title row hover auto-hide hidden view Default shortcut:alt+cmd+t"},{"href":"/reference/mac-actions#action-client-pane-header-reveal","page":"Mac Actions","heading":"Reveal Pane Header","text":"Reveal Pane Header client.pane_header.reveal show reveal peek pane header title hover hidden focused pane view Default shortcut:alt+shift+cmd+t"},{"href":"/reference/mac-actions#action-client-theme-change","page":"Mac Actions","heading":"Change Theme…","text":"Change Theme… client.theme.change theme colour color scheme palette appearance"},{"href":"/reference/mac-actions#action-client-theme-import","page":"Mac Actions","heading":"Import Theme File","text":"Import Theme File Imports a Ghostty theme or iTerm color file and selects it. client.theme.import theme import ghostty iterm file colour color scheme palette"},{"href":"/reference/mac-actions#action-client-appearance-switch-mode","page":"Mac Actions","heading":"Switch Appearance Mode","text":"Switch Appearance Mode client.appearance.switch_mode appearance mode light dark toggle color scheme"},{"href":"/reference/mac-actions#action-client-config-copy-path","page":"Mac Actions","heading":"Copy Config File Path","text":"Copy Config File Path client.config.copy_path config configuration file path copy clipboard init.lua lua keybindings shortcuts settings location where"},{"href":"/reference/mac-actions#action-client-config-reload","page":"Mac Actions","heading":"Reload Config","text":"Reload Config client.config.reload config configuration reload reread apply init.lua lua keybindings shortcuts settings"},{"href":"/reference/mac-actions#view","page":"Mac Actions","heading":"View","text":"Action Name Default Shortcut"},{"href":"/reference/mac-settings","page":"Mac Settings","heading":"","text":"This page lists every setting in Rex for Mac and where to find it. Anything tagged Bleeding Edge is on that update channel and not yet in Preview."},{"href":"/reference/mac-settings#integrations","page":"Mac Settings","heading":"Integrations","text":""},{"href":"/reference/mac-settings#setting-coding-agents-integrate-with-available-agents","page":"Mac Settings","heading":"Integrate with available agents","text":"Integrate with available agents Installs a Rex skill and session hooks for supported agents. In Settings > Integrations > Coding Agents. install skill, remove skill, set up agents, uninstall, enable agents, disable agents, update skill"},{"href":"/reference/mac-settings#coding-agents","page":"Mac Settings","heading":"Coding Agents","text":"Teach Claude Code, Codex, OpenCode and Pi how to use Rex. In Settings > Integrations > Coding Agents. agents, ai, claude, claude code, codex, opencode, pi, skill, hooks, rex skill setup, llm, assistant, integrations Setting Description"},{"href":"/reference/mac-settings#setting-agent-behavior-let-agents-name-sessions","page":"Mac Settings","heading":"Let agents name sessions","text":"Let agents name sessions Let agents label the session after the task they're working on. Shown while Integrate with available agents is on. In Settings > Integrations > Agent Behavior. rename session, session label, session name, auto name, title, label sessions"},{"href":"/reference/mac-settings#agent-behavior","page":"Mac Settings","heading":"Agent Behavior","text":"Choose what Rex tells coding agents running in its terminals. In Settings > Integrations > Agent Behavior. agent instructions, agent rules, session hook Setting Description"},{"href":"/reference/mac-settings#notifications","page":"Mac Settings","heading":"Notifications","text":""},{"href":"/reference/mac-settings#setting-in-app-notifications-notification-position","page":"Mac Settings","heading":"Notification position","text":"Notification position Click a position in the window diagram to place notifications at the top or bottom, aligned left, center, or right. In Settings > Notifications > In-App Notifications. notification position, notification placement, banner location, anchor, top left, top center, top right, bottom left, bottom center, bottom right"},{"href":"/reference/mac-settings#in-app-notifications","page":"Mac Settings","heading":"In-App Notifications","text":"Choose where floating notifications appear in the window. In Settings > Notifications > In-App Notifications. notifications, notification settings, alerts, banners, notification center, in app notifications, customize notifications Setting Description"},{"href":"/reference/mac-settings#setting-program-activity-show-activity-in-tabs","page":"Mac Settings","heading":"Show activity in tabs","text":"Show activity in tabs Show working dots, progress pies, and needs-attention dots and messages on tabs when a program in them reports its status. In Settings > Notifications > Program Activity. program status, process status, osc 7501, agent, claude, progress, progress pie, blocked, waiting, question, permission, error, failed, status dot, status badges, notification dot, badge, working, tab status, indicator, needs attention"},{"href":"/reference/mac-settings#setting-program-activity-include-idle-processes-in-activity","page":"Mac Settings","heading":"Include idle processes in activity","text":"Include idle processes in activity Keep tabs whose programs report that they're idle in the Activity filter and popup, even after you've seen them. In Settings > Notifications > Program Activity. idle, idle processes, idle agents, ready, activity filter, activity popup, keep idle, show idle, include idle, always show idle processes in activity, eye"},{"href":"/reference/mac-settings#setting-program-activity-show-each-process-in-tabs","page":"Mac Settings","heading":"Show each process in tabs","text":"Show each process in tabs Split a tab with more than one active pane into a row for each pane in the Activity filter and popup. Clicking a row focuses its pane. In Settings > Notifications > Program Activity. each process, each process in activity, every process, all processes, split processes, split tabs, panes, pane rows, agents, multiple agents, expand tabs, activity filter, activity popup, eye"},{"href":"/reference/mac-settings#setting-program-activity-show-system-notifications","page":"Mac Settings","heading":"Show system notifications","text":"Show system notifications Send a macOS notification when a program reports a status while Rex is in the background, independently of tab indicators. Clicking it opens the program's tab. In Settings > Notifications > Program Activity. program notifications, agent notifications, background notifications, claude notification, notify when done, notify when blocked, notifications without tab indicators"},{"href":"/reference/mac-settings#setting-program-activity-badge-dock-icon","page":"Mac Settings","heading":"Badge Dock icon","text":"Badge Dock icon Show on Rex's Dock icon how many tabs have a program that's blocked, failed, or finished that you haven't seen. In Settings > Notifications > Program Activity. dock badge, badge count, app icon badge, unread count, red badge, badge application icon, dock icon"},{"href":"/reference/mac-settings#setting-program-activity-play-sounds","page":"Mac Settings","heading":"Play sounds","text":"Play sounds Play a sound when a program in a tab is blocked, reports an error, or finishes. In Settings > Notifications > Program Activity. sound, audio, chime, alert sound, beep, bell, audible, monoh, sound effects, program finished, done sound, mute, silence"},{"href":"/reference/mac-settings#setting-program-activity-sound-volume","page":"Mac Settings","heading":"Sound volume","text":"Sound volume Set how loud the blocked, error, and done sounds play, from quieter to louder than the default. In Settings > Notifications > Program Activity. volume, loudness, louder, quieter, too quiet, too loud, sound level, alert volume, notification volume, boost, gain"},{"href":"/reference/mac-settings#setting-program-activity-working","page":"Mac Settings","heading":"Working","text":"Working Enable or disable all activity for working programs, or configure their tab indicators, notifications, Dock badges, and sounds individually. In Settings > Notifications > Program Activity. working status, busy, progress, spinner, running, disable all working, enable working status"},{"href":"/reference/mac-settings#setting-program-activity-blocked","page":"Mac Settings","heading":"Blocked","text":"Blocked Enable or disable all activity for programs waiting for you, or configure their tab indicators, notifications, Dock badges, and sounds individually. In Settings > Notifications > Program Activity. blocked status, waiting, question, permission, approval, sign in, attention sound, blocked notifications, disable all blocked, enable blocked status"},{"href":"/reference/mac-settings#setting-program-activity-error","page":"Mac Settings","heading":"Error","text":"Error Enable or disable all activity for programs reporting errors, or configure their tab indicators, notifications, Dock badges, and sounds individually. In Settings > Notifications > Program Activity. error status, failed, failure, error sound, disable all errors, enable error status"},{"href":"/reference/mac-settings#setting-program-activity-done","page":"Mac Settings","heading":"Done","text":"Done Enable or disable all activity for finished programs, or configure their tab indicators, notifications, Dock badges, and sounds individually. In Settings > Notifications > Program Activity. done status, finished, complete, completed, success sound, finish sound, disable all done, enable done status"},{"href":"/reference/mac-settings#program-activity","page":"Mac Settings","heading":"Program Activity","text":"Independently configure tab indicators, system notifications, Dock badges, and sounds for program activity. In Settings > Notifications > Program Activity. program activity, tab activity, activity indicators Setting Description"},{"href":"/reference/mac-settings#setting-system-notifications-terminal-notifications","page":"Mac Settings","heading":"Terminal notifications","text":"Terminal notifications Let programs send native notifications with OSC 9 or OSC 777. In Settings > Notifications > System Notifications. osc 9, osc 777, osc9, osc777, desktop notification, notify-send, escape sequence, program notifications, printf notify"},{"href":"/reference/mac-settings#setting-system-notifications-tailscale-sign-in","page":"Mac Settings","heading":"Tailscale sign-in","text":"Tailscale sign-in A native notification when Rex needs you to sign in to Tailscale. In Settings > Notifications > System Notifications. tailscale login notification, sign in, authentication, account"},{"href":"/reference/mac-settings#setting-system-notifications-tailscale-device-approval","page":"Mac Settings","heading":"Tailscale device approval","text":"Tailscale device approval A native notification when a tailnet administrator must approve this Mac. In Settings > Notifications > System Notifications. device approval notification, approve mac, tailnet administrator, authorization"},{"href":"/reference/mac-settings#setting-system-notifications-tailscale-failures","page":"Mac Settings","heading":"Tailscale failures","text":"Tailscale failures A native notification when Tailscale stops or reports an error. In Settings > Notifications > System Notifications. tailscale failure notification, tailscale stopped, connection error, offline"},{"href":"/reference/mac-settings#system-notifications","page":"Mac Settings","heading":"System Notifications","text":"Choose whether terminal programs can send native macOS notifications, and see the ones Rex sends for Tailscale. In Settings > Notifications > System Notifications. native notifications, macOS notifications, mac notifications, tailscale alerts, terminal notifications Setting Description"},{"href":"/reference/mac-settings#server","page":"Mac Settings","heading":"Server","text":""},{"href":"/reference/mac-settings#setting-rex-server-server-status","page":"Mac Settings","heading":"Server status","text":"Server status See whether the local server is running and for how long, with version and API details in its info popover. In Settings > Server > Rex Server. health, running, stopped, preparing server, failed, error, source checkout, server details, server version, api revision, uptime, running time, how long, started, duration"},{"href":"/reference/mac-settings#setting-rex-server-compatibility","page":"Mac Settings","heading":"Compatibility","text":"Compatibility When an update, compatibility issue, or pending settings change is detected, see the discrepancy and update or restart the server when appropriate. In Settings > Server > Rex Server. incompatible server, restart recommended, update rex app, restart server, upgrade server, newer server, available version, restart required, new features, apply settings, use bundled server, older app, backward compatible, a newer server is available, updating is optional, optional update, new API features are available, newer API features, unavailable features, server settings have changed, cannot connect to this server, this server requires a newer rex app, server and app are incompatible, reconnect, server compatibility, pending changes, update server, restart using included server, restart from source"},{"href":"/reference/mac-settings#rex-server","page":"Mac Settings","heading":"Rex Server","text":"Manage the local server, compatibility, and diagnostics. In Settings > Server > Rex Server. start, stop, restart, status, version, api, diagnostics Setting Description"},{"href":"/reference/mac-settings#setting-remote-address-remote-address","page":"Mac Settings","heading":"Remote Address","text":"Remote Address Check serving progress and certificate readiness, retry, copy the address, or pair a device using its QR code. In Settings > Server > Remote Address. copy remote address, tailscale connection progress, https, dns, hostname, endpoint, copy, qr, scan, pair, device, iphone, ipad, certificate, waiting for certificate, tls, ssl, secure, starting, stopping, connection progress, retry, connected, disconnected, approval, error, dns recovery"},{"href":"/reference/mac-settings#remote-address","page":"Mac Settings","heading":"Remote Address","text":"Check serving progress, copy the address, or pair another device. In Settings > Server > Remote Address. tailnet, https, endpoint, connectivity Setting Description"},{"href":"/reference/mac-settings#setting-tailscale-serve-over-tailscale","page":"Mac Settings","heading":"Serve over Tailscale","text":"Serve over Tailscale Enable or disable access to Rex over your tailnet. In Settings > Server > Tailscale. connect, network, vpn, share, enable, disable"},{"href":"/reference/mac-settings#setting-tailscale-preferred-hostname","page":"Mac Settings","heading":"Preferred Hostname","text":"Preferred Hostname Choose this Mac’s Tailscale hostname while serving. The assigned address may differ. In Settings > Server > Tailscale. hostname, device name, dns, rename, machine, address, assigned address, host id, durable host id, identity"},{"href":"/reference/mac-settings#setting-tailscale-limit-access-to-your-tailscale-account","page":"Mac Settings","heading":"Limit access to your Tailscale account","text":"Limit access to your Tailscale account Restrict callers to your account or allow tailnet members. In Settings > Server > Tailscale. security, authentication, identity, permissions, owner, members, acl"},{"href":"/reference/mac-settings#setting-tailscale-tags","page":"Mac Settings","heading":"Tags","text":"Tags See this device’s Tailscale tags. Sign out and back in to change them. In Settings > Server > Tailscale. tag, tagged, acl tags, advertise tags, tailnet policy, machine identity, owner, access"},{"href":"/reference/mac-settings#setting-tailscale-control-server","page":"Mac Settings","heading":"Control Server","text":"Control Server See which control server this device signs in with. Sign out and back in to change it. In Settings > Server > Tailscale. control url, control plane, coordination server, login server, self-hosted, custom server"},{"href":"/reference/mac-settings#setting-tailscale-sign-in-to-tailscale","page":"Mac Settings","heading":"Sign in to Tailscale","text":"Sign in to Tailscale Check your account and sign-out progress, configure a hostname and sign in, reopen or cancel enrollment, or sign out. In Settings > Server > Tailscale. login, tailscale login, sign in, sign in to tailscale, log in, sign out, account, status, signed in to tailscale, enrolled account, signing in to tailscale, browser sign in, open browser, reopen browser, preparing to sign in, this may take a moment, ready to connect, awaiting connection, finish the process in your browser, taking longer than usual, slow sign in, cancel sign in, cancel login, signing out of tailscale, remove saved login, sign out progress"},{"href":"/reference/mac-settings#tailscale","page":"Mac Settings","heading":"Tailscale","text":"Connect Rex to your tailnet and control who can access it. In Settings > Server > Tailscale. network, vpn, remote, secure, sharing Setting Description"},{"href":"/reference/mac-settings#setting-client-info-label","page":"Mac Settings","heading":"Label","text":"Label Name this Mac in client lists and for targeting it from rex commands. In Settings > Server > Client Info. device label, client label, name, computer, identify, rename, rename this mac, this device, -C, target, targeting, client ls"},{"href":"/reference/mac-settings#setting-client-info-remote-control","page":"Mac Settings","heading":"Remote Control","text":"Remote Control Let Rex and other clients perform actions in this app. In Settings > Server > Client Info. remote, control, allow, allow actions, actions, command line, rex do, perform, security"},{"href":"/reference/mac-settings#client-info","page":"Mac Settings","heading":"Client Info","text":"Choose how this Mac appears to Rex servers and the command line. In Settings > Server > Client Info. client, device, identity, rex client ls, command line, cli Setting Description"},{"href":"/reference/mac-settings#interface","page":"Mac Settings","heading":"Interface","text":""},{"href":"/reference/mac-settings#setting-appearance-color-scheme","page":"Mac Settings","heading":"Color scheme","text":"Color scheme Select a live Auto, Light, or Dark material preview. Auto follows the system appearance. In Settings > Interface > Appearance. colour, light, dark, auto, automatic, system, night mode, cards"},{"href":"/reference/mac-settings#setting-appearance-interface-style","page":"Mac Settings","heading":"Interface style","text":"Interface style Preview Glass or Classic materials with the current theme tint and select a surface style. In Settings > Interface > Appearance. glass, classic, liquid glass, material, native, system, modern, opaque, translucent, cards"},{"href":"/reference/mac-settings#setting-appearance-tint-with-theme","page":"Mac Settings","heading":"Tint with theme","text":"Tint with theme Apply the selected theme's colors to interface surfaces. In Settings > Interface > Appearance. tint, theme colors, theme colours, palette, themed, blended, blend theme, use theme"},{"href":"/reference/mac-settings#setting-appearance-use-glass-for-panes","page":"Mac Settings","heading":"Use glass for panes","text":"Use glass for panes Available with the Glass interface style: add glass to panes alongside window and tab materials. In Settings > Interface > Appearance. pane glass, no glass, remove glass, liquid glass, pane material, transparency"},{"href":"/reference/mac-settings#appearance","page":"Mac Settings","heading":"Appearance","text":"Choose the color scheme, interface materials, and theme tint. In Settings > Interface > Appearance. look, visual, display, theme Setting Description"},{"href":"/reference/mac-settings#setting-toolbar-toolbar-layout","page":"Mac Settings","heading":"Toolbar layout","text":"Toolbar layout Place the toolbar and its tabs Above, on the Left Side, or on the Right Side of the window. In Settings > Interface > Toolbar. tab style, tab placement, tab layout, horizontal tabs, vertical tabs, vertical, horizontal, sidebar, navigation, orientation, sidebar position, above, left side, right side, left sidebar, right sidebar, edge, location"},{"href":"/reference/mac-settings#setting-toolbar-toolbar-behavior","page":"Mac Settings","heading":"Toolbar behavior","text":"Toolbar behavior Always show the toolbar, Show on hover: hide the tab bar, or the sidebar's title row, until the pointer reaches the top of the window, or keep it Hidden until Reveal Toolbar. In Settings > Interface > Toolbar. always show, on hover, show toolbar on hover, hidden, never, never show toolbar, reveal toolbar, toolbar visibility, auto-hide toolbar, autohide, hide toolbar, hide tab bar, floating toolbar, reveal, tab bar, title bar, title row, traffic lights, distraction free, minimal, full height, chrome, hot corner, hot corners"},{"href":"/reference/mac-settings#toolbar","page":"Mac Settings","heading":"Toolbar","text":"Choose the toolbar layout and whether the toolbar always shows or shows on hover. In Settings > Interface > Toolbar. workspace, chrome, layout, navigation, tab bar, title bar Setting Description"},{"href":"/reference/mac-settings#setting-tab-behavior-tabs-fill-available-space","page":"Mac Settings","heading":"Tabs fill available space","text":"Tabs fill available space Available with horizontal tabs: expand tabs equally to fill the bar, showing backgrounds only for two or more tabs. In Settings > Interface > Tab behavior. fill available space, tab width, stretch, expand, full width, equal width, maximum, capped"},{"href":"/reference/mac-settings#tab-behavior","page":"Mac Settings","heading":"Tab behavior","text":"Choose whether tabs above the panes fill the tab bar. Opens from Customize tabs under Toolbar. In Settings > Interface > Tab behavior. tabs, tab bar, tab sizing, customize tabs Setting Description"},{"href":"/reference/mac-settings#setting-tab-icons-use-color-for-tab-icons","page":"Mac Settings","heading":"Use color for tab icons","text":"Use color for tab icons Show colored icons Always, For active panes, For active tabs, or Never. In Settings > Interface > Tab icons. colour, monochrome, grayscale, greyscale, saturation, always, never, active panes, active tabs, focused pane, selected tab"},{"href":"/reference/mac-settings#setting-tab-icons-shell-icon-theme","page":"Mac Settings","heading":"Shell icon theme","text":"Shell icon theme Choose Default artwork, Use theme for background and foreground colors, or Use theme accent for accents. In Settings > Interface > Tab icons. shell colors, shell colours, terminal icon, shell palette, default, use theme, use theme accent, accent, background, foreground, glyph"},{"href":"/reference/mac-settings#setting-tab-icons-show-tab-icons","page":"Mac Settings","heading":"Show tab icons","text":"Show tab icons Show zero to three pane icons in vertical tabs, or up to five in horizontal tabs. In Settings > Interface > Tab icons. icon count, icons per tab, number of icons, stack size, stack limit, icon count slider, slider, maximum, none, hide icons, no icons, zero icons, one icon, two icons, three icons, four icons, five icons, 0, 1, 2, 3, 4, 5, pane icons"},{"href":"/reference/mac-settings#tab-icons","page":"Mac Settings","heading":"Tab icons","text":"Choose how many pane icons tabs show, and how tab and shell icons are colored. Opens from Customize tabs under Toolbar. In Settings > Interface > Tab icons. workspace, icons, symbols, pane cards, icon stack, tab icons, tabs, customize tabs Setting Description"},{"href":"/reference/mac-settings#setting-panes-pane-style","page":"Mac Settings","heading":"Pane style","text":"Pane style Choose Comfortable or Compact pane surfaces and spacing. In Settings > Interface > Panes. compact, comfortable, pane density, padding, gaps, spacing"},{"href":"/reference/mac-settings#setting-panes-pane-header-behavior","page":"Mac Settings","heading":"Pane header behavior","text":"Pane header behavior Show the title and controls above each pane: Always show, Show on hover, or Hidden. In Settings > Interface > Panes. pane headers, show pane headers, titles, toolbar, chrome, hide headers, always, always show, on hover, show on hover, hidden, never, hover, floating header, auto-hide header, pane header visibility"},{"href":"/reference/mac-settings#panes","page":"Mac Settings","heading":"Panes","text":"Adjust pane spacing and headers. In Settings > Interface > Panes. workspace, chrome, layout, density, spacing, split Setting Description"},{"href":"/reference/mac-settings#setting-window-sidebar-text-size","page":"Mac Settings","heading":"Sidebar text size","text":"Sidebar text size Available with vertical tabs: set the size of session and tab titles in the sidebar, or follow the Sidebar icon size in System Settings. In Settings > Interface > Window. sidebar text, text size, tab title size, session title size, larger text, bigger text, sidebar icon size, sidebar row size, row height, follow system size"},{"href":"/reference/mac-settings#setting-window-preserve-width-when-resizing-sidebar","page":"Mac Settings","heading":"Preserve width when resizing sidebar","text":"Preserve width when resizing sidebar Available with vertical tabs: resize panes when showing or hiding the sidebar, keeping the window width unchanged. In Settings > Interface > Window. keep window width, preserve window width, fixed window width, resizing sidebar, window size, resize window, shrink window, grow window, keep window size, show sidebar, hide sidebar, toggle sidebar, collapse sidebar, fold sidebar, pane width, maximized, full screen"},{"href":"/reference/mac-settings#setting-window-use-a-gesture-to-toggle-the-sidebar","page":"Mac Settings","heading":"Use a gesture to toggle the sidebar","text":"Use a gesture to toggle the sidebar Available with vertical tabs: choose whether a swipe or an Option-swipe shows or hides the sidebar, or turn the gesture off. Hold the ⌥ key to scroll content sideways instead. In Settings > Interface > Window. show or hide sidebar, option-swipe, option swipe, ⌥, off, disable swipe, turn off swipe, no gesture, disable gesture, magic mouse, mouse, one finger, option scroll, alt scroll, option key, alt key, hold option, modifier, swipe, two finger swipe, two fingers, trackpad, gesture, trackpad gesture, sidebar swipe, swipe sidebar, fold sidebar, horizontal scroll, scroll sideways, sideways, side scroll, scroll left, scroll right, tui, diff, long lines"},{"href":"/reference/mac-settings#window","page":"Mac Settings","heading":"Window","text":"Available with sidebar toolbar layouts: choose whether the sidebar resizes the window, which swipe shows or hides it, and the size of its text. In Settings > Interface > Window. window sizing, window width, sidebar, layout, swipe, trackpad, text size Setting Description"},{"href":"/reference/mac-settings#setting-padding-padding-x","page":"Mac Settings","heading":"Padding X","text":"Padding X Set the space to the left and right of the terminal text. In Settings > Interface > Padding. horizontal padding, x padding, left padding, right padding, side padding, horizontal inset, horizontal margin"},{"href":"/reference/mac-settings#setting-padding-padding-y","page":"Mac Settings","heading":"Padding Y","text":"Padding Y Set the space below the terminal text, and above it when pane headers are hidden. In Settings > Interface > Padding. vertical padding, y padding, bottom padding, top padding, vertical inset, vertical margin"},{"href":"/reference/mac-settings#setting-padding-fill-with-program-colors","page":"Mac Settings","heading":"Fill with program colors","text":"Fill with program colors Let a full-screen program color the padding Off, Automatic, or Always. In Settings > Interface > Padding. padding color, colour, off, automatic, always, extend, extend always, background, edge cells, window-padding-color, program background, editor, full screen, tui"},{"href":"/reference/mac-settings#setting-padding-include-pane-header","page":"Mac Settings","heading":"Include pane header","text":"Include pane header Color the pane header to match the program too, while the padding fills. In Settings > Interface > Padding. match program backgrounds, tint, pane color, header color, pane background, terminal background, program background, editor, full screen, tui, neovim, vim, blend, match terminal"},{"href":"/reference/mac-settings#padding","page":"Mac Settings","heading":"Padding","text":"Set the space between a pane's edges and its terminal text, and whether a program's colors fill it. Opens from Customize panes under Interface › Panes. In Settings > Interface > Padding. pane padding, terminal padding, inset, margin, margins, gutter, spacing around text, edges, window padding, pane background, background Setting Description"},{"href":"/reference/mac-settings#setting-active-pane-highlight-active-pane-edges","page":"Mac Settings","heading":"Highlight active pane edges","text":"Highlight active pane edges Highlight the edges of the focused pane, following focus. In Settings > Interface > Active Pane. border, outline, focus ring, active border, focused pane, highlight, highlight active pane, edge highlight, indicator, focus indicator, show border on active pane"},{"href":"/reference/mac-settings#setting-active-pane-highlight-color","page":"Mac Settings","heading":"Highlight color","text":"Highlight color Draw the highlight in the accent, a terminal palette color, or the terminal text color. In Settings > Interface > Active Pane. accent, accent color, theme color, terminal text, terminal foreground, foreground color, text color, highlight color, border color, focus ring color, active pane border color, swatch, color swatches, palette, red, blue, green, yellow, cyan, magenta"},{"href":"/reference/mac-settings#setting-active-pane-highlight-opacity","page":"Mac Settings","heading":"Highlight opacity","text":"Highlight opacity Set how strongly the highlight draws. In Settings > Interface > Active Pane. opacity, transparency, alpha, translucent, faint, subtle, strength, border opacity, border strength, focus ring opacity, highlight strength, active pane border opacity"},{"href":"/reference/mac-settings#setting-active-pane-animate-active-pane-highlight","page":"Mac Settings","heading":"Animate active pane highlight","text":"Animate active pane highlight Fade the highlight to the newly focused pane, move it there, or switch it at once. In Settings > Interface > Active Pane. fade, move, crossfade, glide, slide, animate, animation, transition, border motion, focus animation, highlight animation, border fade, border move, off, instant, no animation, active pane border animation"},{"href":"/reference/mac-settings#setting-active-pane-fade-inactive-panes","page":"Mac Settings","heading":"Fade inactive panes","text":"Fade inactive panes Fade unfocused pane surfaces, content, both, or neither. In Settings > Interface > Active Pane. dim, opacity, unfocused, focus, inactive, off, none, surface, surface only, content, content only, surface & content, surface and content, both, neither, decorations, styling, text, background, borders, shadows, glass, fade inactive pane content"},{"href":"/reference/mac-settings#setting-active-pane-fade-panes-for-inactive-windows","page":"Mac Settings","heading":"Fade panes for inactive windows","text":"Fade panes for inactive windows Fade inactive windows while respecting the pane fade choice. Glass content fades equally in every pane. In Settings > Interface > Active Pane. window inactive, window focus, background window, switch apps, window deactivation, standard window styles, theme defaults, all panes, fade panes in inactive windows, use pane fade choice, fade content for inactive windows, liquid glass content"},{"href":"/reference/mac-settings#active-pane","page":"Mac Settings","heading":"Active Pane","text":"Highlight the edges of the active pane and fade inactive ones. Opens from Customize panes under Interface › Panes. In Settings > Interface > Active Pane. customize panes, customize pane behavior, pane behavior, active pane, active panes, inactive panes, focused pane, unfocused panes, pane focus, focus appearance, dim inactive Setting Description"},{"href":"/reference/mac-settings#setting-splits-focus-autobalance-splits","page":"Mac Settings","heading":"Autobalance splits","text":"Autobalance splits Evenly resize a tab's panes when one opens or closes, keeping dividers you drag. In Settings > Interface > Splits &amp; Focus. balance panes, equalize, even, equal size, new pane, close pane, split, resize, divider, keep divider, dragged divider, manual split size"},{"href":"/reference/mac-settings#setting-splits-focus-focus-follows-mouse","page":"Mac Settings","heading":"Focus follows mouse","text":"Focus follows mouse Focus a pane by moving the pointer over it, without clicking. In Settings > Interface > Splits &amp; Focus. hover, pointer, cursor, sloppy focus, mouse focus, active pane, select pane, without clicking"},{"href":"/reference/mac-settings#splits--focus","page":"Mac Settings","heading":"Splits & Focus","text":"Balance a tab's splits and let focus follow the pointer. Opens from Customize panes under Interface › Panes. In Settings > Interface > Splits & Focus. splits, split panes, split balancing, pointer focus, pane layout Setting Description"},{"href":"/reference/mac-settings#setting-gestures-use-trackpad-gestures","page":"Mac Settings","heading":"Use trackpad gestures","text":"Use trackpad gestures Spread two fingers over a pane to zoom it and pinch them to restore it, and swipe to show or hide the sidebar. In Settings > Interface > Gestures. use gestures, pinch, pinch to zoom, pinch zoom, magnify, magnification, spread, pinch zoom pane, unzoom, trackpad zoom, trackpad pinch, two finger pinch, swipe, sidebar swipe, disable gestures, turn off gestures, no gestures, trackpad gestures"},{"href":"/reference/mac-settings#gestures","page":"Mac Settings","heading":"Gestures","text":"Turn trackpad gestures for panes and the sidebar on or off. In Settings > Interface > Gestures. gestures, trackpad, multi-touch, fingers Setting Description"},{"href":"/reference/mac-settings#setting-animation-animation-style","page":"Mac Settings","heading":"Animation style","text":"Animation style Choose Snappy, Natural, or Off for pane motion, the active border, and showing or hiding the sidebar. Off while Reduce Motion is on. In Settings > Interface > Animation. snappy, natural, off, spring, bounce, no bounce, overshoot, disable animations, turn off animations, reduce motion, instant, fast, speed, pane animation, split animation, hyprland, sidebar animation"},{"href":"/reference/mac-settings#animation","page":"Mac Settings","heading":"Animation","text":"Choose how panes move, open, and close. In Settings > Interface > Animation. motion, animations, transitions, movement Setting Description"},{"href":"/reference/mac-settings#setting-app-icon-app-icon","page":"Mac Settings","heading":"App Icon","text":"App Icon Choose Default — Dynamic, Bright, or Twilight artwork for the app icon. In Settings > Interface > App Icon. app icon, application icon, dock icon, custom icon, alternate icon, bright, bright icon, twilight, twilight icon, light icon, dark icon, beta, artwork, dynamic, dynamic icon"},{"href":"/reference/mac-settings#setting-app-icon-show-app-icon","page":"Mac Settings","heading":"Show app icon","text":"Show app icon Show Rex's icon in the Dock and menu bar, the Dock only, or the menu bar only while no window is open. In Settings > Interface > App Icon. dock and menu bar, dock only, menubar only, menu bar only, dock icon, hide dock icon, show dock icon, dock visibility, application switcher, app switcher, command tab, cmd tab, menubar, menu bar icon, status item, status bar, tray icon, hide menu bar icon, show menu bar icon, hide, show, visibility"},{"href":"/reference/mac-settings#app-icon","page":"Mac Settings","heading":"App Icon","text":"Customize Rex's app icon and Dock and menu bar icon visibility. In Settings > Interface > App Icon. app icon, application icon, dock icon, artwork, beta icon Setting Description"},{"href":"/reference/mac-settings#setting-launch-show-window-on-launch","page":"Mac Settings","heading":"Show window on launch","text":"Show window on launch Automatically open workspace windows when Rex launches. In Settings > Interface > Launch. show window on launch, open window on startup, launch in background, hide window on launch, restore windows, start minimized"},{"href":"/reference/mac-settings#launch","page":"Mac Settings","heading":"Launch","text":"Choose whether Rex opens workspace windows at startup. In Settings > Interface > Launch. launch, startup, start up, opening, background Setting Description"},{"href":"/reference/mac-settings#themes","page":"Mac Settings","heading":"Themes","text":""},{"href":"/reference/mac-settings#setting-light-mode-light-theme","page":"Mac Settings","heading":"Light theme","text":"Light theme Choose a custom, default, or built-in palette used in light mode. In Settings > Themes > Light Mode. day, bright, light colors, colour scheme, new light theme, edit light theme, customize light theme, duplicate light theme, fork light theme, delete light theme, theme customizer, ansi palette, ansi standard, ansi bright, terminal colors, preview, live preview, live update, pause preview, freeze, theme editor"},{"href":"/reference/mac-settings#setting-light-mode-use-extended-theme-palette","page":"Mac Settings","heading":"Use extended theme palette","text":"Use extended theme palette Use the theme’s colors 16–255 in light mode, or xterm’s standard palette when off. In Settings > Themes > Light Mode. extended palette, extended colors, extended colours, xterm, xterm palette, standard palette, color cube, colour cube, grayscale ramp, greyscale ramp, generated palette, palette-generate, compatibility, on, off"},{"href":"/reference/mac-settings#light-mode","page":"Mac Settings","heading":"Light Mode","text":"Select the theme palette used in light appearance. In Settings > Themes > Light Mode. colors, colours, appearance, theme, palette, day, bright Setting Description"},{"href":"/reference/mac-settings#setting-dark-mode-dark-theme","page":"Mac Settings","heading":"Dark theme","text":"Dark theme Choose a custom, default, or built-in palette used in dark mode. In Settings > Themes > Dark Mode. night, dim, dark colors, colour scheme, new dark theme, edit dark theme, customize dark theme, duplicate dark theme, fork dark theme, delete dark theme, theme customizer, ansi palette, ansi standard, ansi bright, terminal colors, preview, live preview, live update, pause preview, freeze, theme editor"},{"href":"/reference/mac-settings#setting-dark-mode-use-extended-theme-palette","page":"Mac Settings","heading":"Use extended theme palette","text":"Use extended theme palette Use the theme’s colors 16–255 in dark mode, or xterm’s standard palette when off. In Settings > Themes > Dark Mode. extended palette, extended colors, extended colours, xterm, xterm palette, standard palette, color cube, colour cube, grayscale ramp, greyscale ramp, generated palette, palette-generate, compatibility, on, off"},{"href":"/reference/mac-settings#dark-mode","page":"Mac Settings","heading":"Dark Mode","text":"Select the theme palette used in dark appearance. In Settings > Themes > Dark Mode. colors, colours, appearance, theme, palette, night, dim Setting Description"},{"href":"/reference/mac-settings#import-theme","page":"Mac Settings","heading":"Import Theme","text":"Add a Ghostty or iTerm palette to your custom themes. In Settings > Themes > Import Theme. import, file, ghostty, iterm, itermcolors, palette, load, colors, colours, appearance, background, light, dark"},{"href":"/reference/mac-settings#terminal","page":"Mac Settings","heading":"Terminal","text":""},{"href":"/reference/mac-settings#setting-typography-terminal-font","page":"Mac Settings","heading":"Terminal font","text":"Terminal font Browse monospaced or all installed fonts to choose the terminal typeface. In Settings > Terminal > Typography. font, family, typeface, monospace, menlo, font picker, monospaced fonts, all fonts"},{"href":"/reference/mac-settings#setting-typography-font-size","page":"Mac Settings","heading":"Font size","text":"Font size Adjust terminal text size in points. In Settings > Terminal > Typography. text size, larger, smaller, zoom, pt"},{"href":"/reference/mac-settings#setting-typography-font-style","page":"Mac Settings","heading":"Font style","text":"Font style Choose an installed face of the terminal font, such as Light or Book. In Settings > Terminal > Typography. face, variant, italic, oblique, regular, light, medium, semibold, book"},{"href":"/reference/mac-settings#setting-typography-font-weight","page":"Mac Settings","heading":"Font weight","text":"Font weight Adjust the stroke weight of a variable terminal font. In Settings > Terminal > Typography. thickness, thin, light, medium, bold, heavy, variable font, wght"},{"href":"/reference/mac-settings#setting-typography-line-height","page":"Mac Settings","heading":"Line height","text":"Line height Add or remove space between terminal lines. In Settings > Terminal > Typography. cell height, line spacing, leading, adjust-cell-height, ghostty, spacing, taller, compact"},{"href":"/reference/mac-settings#typography","page":"Mac Settings","heading":"Typography","text":"Preview and choose the terminal font, style, size, weight, and line height. In Settings > Terminal > Typography. text, typeface, readability, typography preview, font preview Setting Description"},{"href":"/reference/mac-settings#setting-advanced-font-settings-character-spacing","page":"Mac Settings","heading":"Character spacing","text":"Character spacing Tighten or widen terminal character spacing in pixels or percent. In Settings > Terminal > Advanced Font Settings. letter spacing, kerning, tracking, cell width, adjust-cell-width, ghostty, maple mono, tighter, wider, pixels, percentage"},{"href":"/reference/mac-settings#setting-advanced-font-settings-ligatures","page":"Mac Settings","heading":"Ligatures","text":"Ligatures Join characters like -> and != into single symbols. In Settings > Terminal > Advanced Font Settings. font features, font-feature, calt, liga, arrows, operators, fira code, jetbrains mono"},{"href":"/reference/mac-settings#setting-advanced-font-settings-render-bold-and-italic","page":"Mac Settings","heading":"Render bold and italic","text":"Render bold and italic Draw bold and italic text, only one of them, or neither. In Settings > Terminal > Advanced Font Settings. font-style-bold, font-style-italic, disable bold, no bold, never bold, disable italic, no italic, never italic, upright, bold face, italic face, font weight, regular, both, bold only, italic only, neither, bold, italic, ghostty"},{"href":"/reference/mac-settings#setting-advanced-font-settings-force-bold-and-italic-styles","page":"Mac Settings","heading":"Force bold and italic styles","text":"Force bold and italic styles Use faux bold and italic rendering for fonts without native styles. Shown only for such fonts. In Settings > Terminal > Advanced Font Settings. font-synthetic-style, synthetic style, synthesize, synthesized, pseudo bold, pseudo italic, fake bold, faux bold, fake italic, faux italic, oblique, slant, slanted, outline, emboldened, missing bold, missing italic, pixel font, bitmap font, monaco, ghostty"},{"href":"/reference/mac-settings#setting-advanced-font-settings-font-smoothing","page":"Mac Settings","heading":"Font smoothing","text":"Font smoothing Smooth the edges of terminal text. In Settings > Terminal > Advanced Font Settings. antialiasing, anti aliasing, text rendering, sharp, font-thicken, thicken"},{"href":"/reference/mac-settings#setting-advanced-font-settings-fallback-fonts","page":"Mac Settings","heading":"Fallback fonts","text":"Fallback fonts Choose fonts, in order, for characters the terminal font doesn't have. In Settings > Terminal > Advanced Font Settings. fallback, font fallback, font-family, missing glyphs, missing characters, symbols, icons, nerd font, cjk, chinese, japanese, korean, emoji, font order, ghostty"},{"href":"/reference/mac-settings#advanced-font-settings","page":"Mac Settings","heading":"Advanced Font Settings","text":"Adjust character spacing, ligatures, bold and italic, synthesized styles, smoothing, and fallback fonts. Opens from Advanced font settings under Typography. In Settings > Terminal > Advanced Font Settings. advanced font settings, advanced typography, advanced font options, advanced fonts, font rendering, text rendering, font options Setting Description"},{"href":"/reference/mac-settings#setting-colors-improve-text-contrast","page":"Mac Settings","heading":"Improve text contrast","text":"Improve text contrast Adjust low-contrast text against its background; included when using the theme palette. In Settings > Terminal > Colors. minimum contrast, automatic, legibility, accessibility, unreadable, faint, pale, yellow, ansi, truecolor, theme, on, off"},{"href":"/reference/mac-settings#setting-colors-force-theme-palette-for-text","page":"Mac Settings","heading":"Force theme palette for text","text":"Force theme palette for text Map RGB and extended text colors to the nearest theme color, then correct contrast. In Settings > Terminal > Colors. force theme colors, force theme colours, remap, match colors, palette, ansi, rgb, truecolor, 256 colors, extended colors, replace, consistent, on, off"},{"href":"/reference/mac-settings#setting-colors-use-display-p3-color-space","page":"Mac Settings","heading":"Use Display P3 color space","text":"Use Display P3 color space Read theme and program colors as Display P3, like Ghostty's window-colorspace. In Settings > Terminal > Colors. p3, display p3, wide gamut, wide colour, color space, colour space, colorspace, window-colorspace, ghostty, srgb, saturated, vivid, on, off"},{"href":"/reference/mac-settings#colors","page":"Mac Settings","heading":"Colors","text":"Control terminal text colors and readability. In Settings > Terminal > Colors. colour, color, text, readability Setting Description"},{"href":"/reference/mac-settings#setting-cursor-blink-speed","page":"Mac Settings","heading":"Blink speed","text":"Blink speed Choose Off, Slow, Normal, Fast, or Custom to edit the blink interval. In Settings > Terminal > Cursor. flashing, steady, slow, fast, normal, off, custom"},{"href":"/reference/mac-settings#setting-cursor-blink-interval","page":"Mac Settings","heading":"Blink interval","text":"Blink interval Set the time between cursor blinks in milliseconds when Blink speed is Custom. In Settings > Terminal > Cursor. speed, duration, frequency, ms, custom"},{"href":"/reference/mac-settings#setting-cursor-cursor-style","page":"Mac Settings","heading":"Cursor style","text":"Cursor style Choose a block, bar, or underline cursor. In Settings > Terminal > Cursor. shape, beam, caret, block, bar, underline"},{"href":"/reference/mac-settings#setting-cursor-force-style","page":"Mac Settings","heading":"Force style","text":"Force style Ignore application requests to change cursor shape. In Settings > Terminal > Cursor. override, application, ignore, shape"},{"href":"/reference/mac-settings#setting-cursor-cursor-shaders","page":"Mac Settings","heading":"Cursor shaders","text":"Cursor shaders Bleeding Edge Draw a trail shader, such as Warp, Comet, or Blaze, behind terminal cursors. In Settings > Terminal > Cursor. cursor trail, cursor effect, cursor animation, smear, glow, ghostty shader, custom-shader, shadertoy, on, off"},{"href":"/reference/mac-settings#setting-cursor-shader","page":"Mac Settings","heading":"Shader","text":"Shader Bleeding Edge With Cursor shaders on, choose the effect they draw, such as Warp, Comet, or Blaze. In Settings > Terminal > Cursor. cursor shader, trail style, effect, warp, sweep, comet, blaze, tapered, rainbow, gradient, frozen"},{"href":"/reference/mac-settings#cursor","page":"Mac Settings","heading":"Cursor","text":"Preview and adjust the terminal cursor shape, blinking, and shader effect. In Settings > Terminal > Cursor. caret, insertion, indicator, cursor preview Setting Description"},{"href":"/reference/mac-settings#setting-keyboard-show-key-sequence-hints","page":"Mac Settings","heading":"Show key sequence hints","text":"Show key sequence hints Show the keys that can come next after the first key of a sequence, such as a tmux-style prefix. In Settings > Terminal > Keyboard. which-key, which key, prefix, leader, chord, key sequence, popup, overlay, pill"},{"href":"/reference/mac-settings#setting-keyboard-option-key","page":"Mac Settings","heading":"Option key","text":"Option key Choose whether the Option key sends Alt or types special characters. In Settings > Terminal > Keyboard. option, alt, meta, esc, escape, emacs, readline, tmux, compose, accents, special characters, left, right, ∫"},{"href":"/reference/mac-settings#keyboard","page":"Mac Settings","heading":"Keyboard","text":"Show key sequence hints and choose how modifier keys behave in terminal input. In Settings > Terminal > Keyboard. input, keys, shortcuts, modifier Setting Description"},{"href":"/reference/mac-settings#setting-scrolling-smooth-scrolling","page":"Mac Settings","heading":"Smooth scrolling","text":"Smooth scrolling Move through terminal history by pixels instead of whole lines. In Settings > Terminal > Scrolling. smooth, pixel, precise, sub-line, fluid, momentum, inertia, trackpad, line by line, on, off"},{"href":"/reference/mac-settings#scrolling","page":"Mac Settings","heading":"Scrolling","text":"Choose how trackpad and Magic Mouse scrolling move through terminal history. In Settings > Terminal > Scrolling. scroll, scrollback, history, trackpad, touchpad Setting Description"},{"href":"/reference/mac-settings#setting-resizing-smooth-terminal-resizing","page":"Mac Settings","heading":"Smooth terminal resizing","text":"Smooth terminal resizing Reflow terminal text on the frame a pane changes size, without waiting for the server. In Settings > Terminal > Resizing. smooth resize, resize, reflow, jitter, glitch, jump, flicker, live resize, resize preview, zoom, split, sidebar, window drag, drag, re-wrap, rewrap, on, off"},{"href":"/reference/mac-settings#resizing","page":"Mac Settings","heading":"Resizing","text":"Choose how terminal text follows a pane as it changes size. In Settings > Terminal > Resizing. resize, window size, pane size Setting Description"},{"href":"/reference/mac-settings#setting-selection-copy-on-selection","page":"Mac Settings","heading":"Copy on selection","text":"Copy on selection Automatically copy selected terminal text to the clipboard. In Settings > Terminal > Selection. automatic copy, clipboard, selection, mouse, on, off"},{"href":"/reference/mac-settings#setting-selection-play-animation-on-copy","page":"Mac Settings","heading":"Play animation on copy","text":"Play animation on copy Shine across the selection to confirm each copy. In Settings > Terminal > Selection. shine, highlight, sheen, glint, pulse, effect, feedback, motion, animate, on, off"},{"href":"/reference/mac-settings#setting-selection-selection-foreground","page":"Mac Settings","heading":"Selection foreground","text":"Selection foreground Choose the text color inside a selection. In Settings > Terminal > Selection. foreground, text color, colour, contrast"},{"href":"/reference/mac-settings#setting-selection-selection-background","page":"Mac Settings","heading":"Selection background","text":"Selection background Choose the fill color behind selected text. In Settings > Terminal > Selection. background, highlight color, colour, fill"},{"href":"/reference/mac-settings#setting-selection-rounded-corners","page":"Mac Settings","heading":"Rounded corners","text":"Rounded corners Round the corners of terminal text selections. In Settings > Terminal > Selection. radius, rounding, square"},{"href":"/reference/mac-settings#selection","page":"Mac Settings","heading":"Selection","text":"Preview selection colors and corners, and customize copying with its animation. In Settings > Terminal > Selection. highlight, selected text, copy, colour, animation, selection preview Setting Description"},{"href":"/reference/mac-settings#setting-links-links-that-open-apps","page":"Mac Settings","heading":"Links that open apps","text":"Links that open apps Ask before, always, or never open links such as ssh:// that launch another app. In Settings > Terminal > Links. ask first, ask before opening, always open, never open, confirm, confirmation, prompt, allow, block, vscode, ssh, custom scheme, launch app, open app"},{"href":"/reference/mac-settings#setting-links-trusted-links","page":"Mac Settings","heading":"Trusted links","text":"Trusted links Review and remove the links you chose to always open, once Always Open adds one. In Settings > Terminal > Links. always open, allowed links, allowlist, trusted apps, remembered, forget, remove, revoke, permissions"},{"href":"/reference/mac-settings#links","page":"Mac Settings","heading":"Links","text":"Choose whether terminal links that launch other apps ask first. In Settings > Terminal > Links. hyperlink, url, osc 8, command click, cmd click, open link, security, safety, custom scheme, url scheme Setting Description"},{"href":"/reference/mac-settings#setting-integrated-path-browser-show-recently-used-paths","page":"Mac Settings","heading":"Show recently used paths","text":"Show recently used paths List the paths you chose most recently first, shared by every session on a host. In Settings > Terminal > Integrated Path Browser. recents, recent files, history, recently used, mru, frequent, remember, on, off"},{"href":"/reference/mac-settings#integrated-path-browser","page":"Mac Settings","heading":"Integrated Path Browser","text":"Choose what Insert Path lists when it types a path at the cursor. In Settings > Terminal > Integrated Path Browser. insert path, path browser, file picker, path picker, paths, files, folders, autocomplete, completion Setting Description"},{"href":"/reference/mac-settings#setting-titles-prefer-generated-titles","page":"Mac Settings","heading":"Prefer generated titles","text":"Prefer generated titles Show the running program and its directory instead of titles that shells set. In Settings > Terminal > Titles. shell title, generated title, program name, process name, directory, working directory, path, ignore shell titles, always use generated titles, automatic, on, off"},{"href":"/reference/mac-settings#titles","page":"Mac Settings","heading":"Titles","text":"Choose how terminal tabs and pane headers are named. In Settings > Terminal > Titles. tab, window, pane, name, label Setting Description"},{"href":"/reference/mac-settings#setting-shell-startup-command","page":"Mac Settings","heading":"Startup command","text":"Startup command Set the executable and arguments, or leave empty for the default login shell. In Settings > Terminal > Shell. shell, zsh, bash, fish, executable, arguments, launch, startup, working directory"},{"href":"/reference/mac-settings#shell","page":"Mac Settings","heading":"Shell","text":"Choose the command run when a new local terminal opens. In Settings > Terminal > Shell. startup, session, command line, login Setting Description"},{"href":"/reference/mac-settings#remote-hosts","page":"Mac Settings","heading":"Remote Hosts","text":""},{"href":"/reference/mac-settings#remote-hosts-1","page":"Mac Settings","heading":"Remote hosts","text":"Add, reorder, and manage remote Rex hosts, view connection status, answer SSH questions, and retry connection failures. In Settings > Remote Hosts > Remote hosts. remote, connections, machines, servers, add host, remove host, retry, host details, remote host status, remote host reconnect, connection warning, connection failure, host address, reorder hosts, host order, sort hosts, arrange hosts, move host up, move host down, drag hosts, ssh, ssh host, ssh passphrase, ssh password, host key, answer ssh question, waiting for an answer, needs your answer, finish setup, trust host"},{"href":"/reference/mac-settings#setting-developer-developer-mode","page":"Mac Settings","heading":"Developer Mode","text":"Developer Mode Show update-channel, installation, local API, web-client origin, and experiment settings. In Settings > Developer. enable, advanced, development, debugging"},{"href":"/reference/mac-settings#developer","page":"Mac Settings","heading":"Developer","text":"Developer Mode Reveal settings for development and debugging. In Settings > Developer. advanced, debug, tools Setting Description"},{"href":"/reference/mac-settings#setting-experimental-download-new-updates-when-available","page":"Mac Settings","heading":"Download new updates when available","text":"Download new updates when available Download updates in the background and ask before restarting. In Settings > Developer > Experimental. Needs Developer Mode. auto download, background download, install on quit, download new updates, auto update, sparkle"},{"href":"/reference/mac-settings#setting-experimental-install-updates-automatically","page":"Mac Settings","heading":"Install updates automatically","text":"Install updates automatically Relaunch into the new version while Rex is not visible. In Settings > Developer > Experimental. Needs Developer Mode. auto install, auto relaunch, auto restart, silent update, unattended update, relaunch when idle"},{"href":"/reference/mac-settings#setting-experimental-terminal-anchoring","page":"Mac Settings","heading":"Terminal anchoring","text":"Terminal anchoring Pin terminal text to the top or the bottom of each pane. In Settings > Developer > Experimental. Needs Developer Mode. anchor, terminal alignment, grid gravity, leftover row"},{"href":"/reference/mac-settings#setting-experimental-new-quick-switcher","page":"Mac Settings","heading":"New quick switcher","text":"New quick switcher Open one list of every session, block, and reporting process from Go to Anything. In Settings > Developer > Experimental. Needs Developer Mode. go to anything, go to session, view activity, switcher, command palette, cmd p, jump, navigate, panes, blocks, sessions, experiment"},{"href":"/reference/mac-settings#experimental","page":"Mac Settings","heading":"Experimental","text":"Try features that aren't finished when Developer Mode is enabled. In Settings > Developer > Experimental. experiment, beta, unfinished, labs Setting Description"},{"href":"/reference/mac-settings#setting-app-version-update-channel","page":"Mac Settings","heading":"Update Channel","text":"Update Channel Choose Preview or Bleeding Edge updates when Developer Mode is enabled. In Settings > Developer > App Version. Needs Developer Mode. bleeding edge, testing, snapshot, preview, version, upgrade"},{"href":"/reference/mac-settings#app-version","page":"Mac Settings","heading":"App Version","text":"Choose how Rex.app receives updates when Developer Mode is enabled. In Settings > Developer > App Version. application, release, update, channel, upgrade Setting Description"},{"href":"/reference/mac-settings#setting-rex-installation-location","page":"Mac Settings","heading":"Location","text":"Location Use the server included with Rex.app or a local source checkout. In Settings > Developer > Rex Installation. Needs Developer Mode. bundled, included, source checkout, installation"},{"href":"/reference/mac-settings#setting-rex-installation-source-directory","page":"Mac Settings","heading":"Source Directory","text":"Source Directory Choose the folder containing the Rex source checkout. In Settings > Developer > Rex Installation. Needs Developer Mode. folder, path, repository, repo, checkout, choose"},{"href":"/reference/mac-settings#setting-rex-installation-installation-status","page":"Mac Settings","heading":"Installation status","text":"Installation status Check whether the selected Rex installation is ready to run. In Settings > Developer > Rex Installation. Needs Developer Mode. show details, hide details, version, binary, executable, copy, paths, status"},{"href":"/reference/mac-settings#setting-rex-installation-version","page":"Mac Settings","heading":"Version","text":"Version View and copy the included server version under Show Details. In Settings > Developer > Rex Installation. Needs Developer Mode. release, build, included, bundled, copy"},{"href":"/reference/mac-settings#setting-rex-installation-commit","page":"Mac Settings","heading":"Commit","text":"Commit View and copy the source commit of the included server under Show Details. In Settings > Developer > Rex Installation. Needs Developer Mode. revision, git, hash, build, copy"},{"href":"/reference/mac-settings#setting-rex-installation-final-sha-256","page":"Mac Settings","heading":"Final SHA-256","text":"Final SHA-256 View and copy the included server checksum under Show Details. In Settings > Developer > Rex Installation. Needs Developer Mode. checksum, digest, hash, sha256, integrity, copy"},{"href":"/reference/mac-settings#rex-installation","page":"Mac Settings","heading":"Rex Installation","text":"Choose the included server or run Rex from a source checkout. In Settings > Developer > Rex Installation. binary, install, source, development Setting Description"},{"href":"/reference/mac-settings#setting-local-api-local-api-url","page":"Mac Settings","heading":"Local API URL","text":"Local API URL View and copy the local server endpoint when Developer Mode is enabled. In Settings > Developer > Local API. Needs Developer Mode. address, localhost, port, http, copy local endpoint, unix socket, socket path"},{"href":"/reference/mac-settings#local-api","page":"Mac Settings","heading":"Local API","text":"Inspect the local server's API endpoint when Developer Mode is enabled. In Settings > Developer > Local API. local server, control api, development, connection Setting Description"},{"href":"/reference/mac-settings#setting-web-client-origins-allowed-origins","page":"Mac Settings","heading":"Allowed origins","text":"Allowed origins Review the permitted origins and remove custom entries. In Settings > Developer > Web Client Origins. Needs Developer Mode. allowlist, delete, remove, hosted client, default, permissions"},{"href":"/reference/mac-settings#setting-web-client-origins-origin-pattern","page":"Mac Settings","heading":"Origin pattern","text":"Origin pattern Enter a browser origin or wildcard subdomain to allow after restarting Rex. In Settings > Developer > Web Client Origins. Needs Developer Mode. add origin, allowed origin, wildcard, subdomain, domain, https, web client"},{"href":"/reference/mac-settings#web-client-origins","page":"Mac Settings","heading":"Web Client Origins","text":"Manage browser origins allowed to open Rex WebSocket streams. In Settings > Developer > Web Client Origins. browser, web, websocket, security, cors Setting Description"},{"href":"/reference/ios-actions","page":"iOS Actions","heading":"","text":"This page lists every action in the Rex for iOS command palette. Use the name to run it with rex do."},{"href":"/reference/ios-actions#action-client-window-new","page":"iOS Actions","heading":"New Window","text":"New Window client.window.new create workspace window"},{"href":"/reference/ios-actions#action-client-window-close","page":"iOS Actions","heading":"Close Window","text":"Close Window client.window.close close workspace window"},{"href":"/reference/ios-actions#action-client-close","page":"iOS Actions","heading":"Close","text":"Close Closes the focused pane, else the selected tab, else the window. client.close close pane tab window focused"},{"href":"/reference/ios-actions#window","page":"iOS Actions","heading":"Window","text":"Action Name"},{"href":"/reference/ios-actions#action-client-settings","page":"iOS Actions","heading":"Settings…","text":"Settings… client.settings preferences settings options configure"},{"href":"/reference/ios-actions#action-client-palette-toggle","page":"iOS Actions","heading":"Command Palette…","text":"Command Palette… client.palette.toggle command palette search commands actions"},{"href":"/reference/ios-actions#action-client-version-copy","page":"iOS Actions","heading":"Copy Version Info","text":"Copy Version Info Copies the app and server versions to the clipboard. client.version.copy version build number copy clipboard about app server release"},{"href":"/reference/ios-actions#action-client-debug-copy","page":"iOS Actions","heading":"Copy Debug Info","text":"Copy Debug Info Copies a report of the app, system, server, and host state to the clipboard. client.debug.copy debug debugging info report copy clipboard diagnostics troubleshoot support bug commit build environment"},{"href":"/reference/ios-actions#action-client-diagnostics","page":"iOS Actions","heading":"Rex Diagnostics","text":"Rex Diagnostics client.diagnostics diagnostics logs debug troubleshoot server report"},{"href":"/reference/ios-actions#action-client-agents-setup","page":"iOS Actions","heading":"Install Rex for Agents","text":"Install Rex for Agents Installs the Rex skill and hooks for Claude Code, Codex, and other agents. client.agents.setup rex skill install agent agents coding ai claude code codex cursor copilot gemini hook hooks setup"},{"href":"/reference/ios-actions#app","page":"iOS Actions","heading":"App","text":"Action Name"},{"href":"/reference/ios-actions#action-client-sidebar-toggle","page":"iOS Actions","heading":"Toggle Sidebar","text":"Toggle Sidebar client.sidebar.toggle sidebar collapse expand fold hide show tabs"},{"href":"/reference/ios-actions#action-client-sidebar-activity-toggle","page":"iOS Actions","heading":"Toggle Activity","text":"Toggle Activity client.sidebar.activity.toggle activity mode view filter working waiting attention programs status sidebar tabs"},{"href":"/reference/ios-actions#action-client-config-copy-path","page":"iOS Actions","heading":"Copy Config File Path","text":"Copy Config File Path client.config.copy_path config configuration file path copy clipboard init.lua lua keybindings shortcuts settings location where"},{"href":"/reference/ios-actions#action-client-font-increase","page":"iOS Actions","heading":"Increase Font Size","text":"Increase Font Size client.font.increase terminal font size larger bigger increase zoom text typography"},{"href":"/reference/ios-actions#action-client-font-decrease","page":"iOS Actions","heading":"Decrease Font Size","text":"Decrease Font Size client.font.decrease terminal font size smaller decrease zoom text typography"},{"href":"/reference/ios-actions#action-client-font-reset","page":"iOS Actions","heading":"Reset Font Size","text":"Reset Font Size client.font.reset terminal font size reset default original text typography"},{"href":"/reference/ios-actions#action-client-theme-change","page":"iOS Actions","heading":"Change Theme…","text":"Change Theme… client.theme.change theme colour color scheme palette appearance"},{"href":"/reference/ios-actions#action-client-appearance-switch-mode","page":"iOS Actions","heading":"Switch Appearance Mode","text":"Switch Appearance Mode client.appearance.switch_mode appearance mode light dark toggle color scheme"},{"href":"/reference/ios-actions#action-client-sidebar-filter-focus","page":"iOS Actions","heading":"Focus Tab Filter","text":"Focus Tab Filter client.sidebar.filter.focus filter search find type query focus keyboard sidebar activity tabs sessions hosts"},{"href":"/reference/ios-actions#action-client-keyboard-show","page":"iOS Actions","heading":"Show Keyboard","text":"Show Keyboard client.keyboard.show keyboard show open type input"},{"href":"/reference/ios-actions#action-client-keyboard-hide","page":"iOS Actions","heading":"Hide Keyboard","text":"Hide Keyboard client.keyboard.hide keyboard hide close dismiss"},{"href":"/reference/ios-actions#view","page":"iOS Actions","heading":"View","text":"Action Name"},{"href":"/reference/ios-actions#action-session-new","page":"iOS Actions","heading":"New Session","text":"New Session session.new create session add"},{"href":"/reference/ios-actions#action-session-select","page":"iOS Actions","heading":"Go to Session","text":"Go to Session session.select session select show jump go identifier id"},{"href":"/reference/ios-actions#action-session-next","page":"iOS Actions","heading":"Next Session","text":"Next Session session.next cycle session forward"},{"href":"/reference/ios-actions#action-session-previous","page":"iOS Actions","heading":"Previous Session","text":"Previous Session session.previous cycle session back"},{"href":"/reference/ios-actions#action-session-rename","page":"iOS Actions","heading":"Rename Session","text":"Rename Session session.rename rename session name title"},{"href":"/reference/ios-actions#action-session-close","page":"iOS Actions","heading":"Close Session","text":"Close Session session.close close kill destroy delete session"},{"href":"/reference/ios-actions#action-session-refresh","page":"iOS Actions","heading":"Refresh Sessions","text":"Refresh Sessions session.refresh reload sync sessions"},{"href":"/reference/ios-actions#action-session-switch","page":"iOS Actions","heading":"Change Session…","text":"Change Session… session.switch session switch jump go"},{"href":"/reference/ios-actions#session","page":"iOS Actions","heading":"Session","text":"Action Name"},{"href":"/reference/ios-actions#action-client-host-add","page":"iOS Actions","heading":"Add Remote Host…","text":"Add Remote Host… client.host.add host remote add server machine url http https connect"},{"href":"/reference/ios-actions#action-client-host-remove","page":"iOS Actions","heading":"Remove Host…","text":"Remove Host… client.host.remove remove forget delete disconnect host remote server machine"},{"href":"/reference/ios-actions#host","page":"iOS Actions","heading":"Host","text":"Action Name"},{"href":"/reference/ios-actions#action-client-tab-new","page":"iOS Actions","heading":"New Tab","text":"New Tab client.tab.new create tab window add"},{"href":"/reference/ios-actions#action-client-tab-previous","page":"iOS Actions","heading":"Show Previous Tab","text":"Show Previous Tab client.tab.previous cycle tab window back"},{"href":"/reference/ios-actions#action-client-tab-next","page":"iOS Actions","heading":"Show Next Tab","text":"Show Next Tab client.tab.next cycle tab window forward"},{"href":"/reference/ios-actions#action-client-tab-goto","page":"iOS Actions","heading":"Go to Tab","text":"Go to Tab client.tab.goto tab window select number index jump go"},{"href":"/reference/ios-actions#action-client-tab-move-backward","page":"iOS Actions","heading":"Move Tab Backward","text":"Move Tab Backward client.tab.move.backward move tab window reorder left up earlier"},{"href":"/reference/ios-actions#action-client-tab-move-forward","page":"iOS Actions","heading":"Move Tab Forward","text":"Move Tab Forward client.tab.move.forward move tab window reorder right down later"},{"href":"/reference/ios-actions#action-client-tab-rename","page":"iOS Actions","heading":"Rename Tab","text":"Rename Tab client.tab.rename rename tab window name title"},{"href":"/reference/ios-actions#action-client-tab-close","page":"iOS Actions","heading":"Close Tab","text":"Close Tab client.tab.close close tab window"},{"href":"/reference/ios-actions#action-client-tab-close-others","page":"iOS Actions","heading":"Close Other Tabs","text":"Close Other Tabs client.tab.close_others close other tabs windows kill only keep"},{"href":"/reference/ios-actions#action-client-tab-move","page":"iOS Actions","heading":"Move Tab","text":"Move Tab client.tab.move move tab window reorder"},{"href":"/reference/ios-actions#action-client-tab-move-to-new-window","page":"iOS Actions","heading":"Move Tab to New Window","text":"Move Tab to New Window client.tab.move_to_new_window move tab new window tear off detach separate pop out own"},{"href":"/reference/ios-actions#tab","page":"iOS Actions","heading":"Tab","text":"Action Name"},{"href":"/reference/ios-actions#action-pane-split-left","page":"iOS Actions","heading":"Split Pane Left","text":"Split Pane Left pane.split.left pane split terminal divide new left"},{"href":"/reference/ios-actions#action-pane-split-right","page":"iOS Actions","heading":"Split Pane Right","text":"Split Pane Right pane.split.right pane split terminal divide new right"},{"href":"/reference/ios-actions#action-pane-split-up","page":"iOS Actions","heading":"Split Pane Up","text":"Split Pane Up pane.split.up pane split terminal divide new above up"},{"href":"/reference/ios-actions#action-pane-split-down","page":"iOS Actions","heading":"Split Pane Down","text":"Split Pane Down pane.split.down pane split terminal divide new below down"},{"href":"/reference/ios-actions#action-pane-focus-previous","page":"iOS Actions","heading":"Focus Previous Pane","text":"Focus Previous Pane pane.focus_previous pane focus previous back"},{"href":"/reference/ios-actions#action-pane-focus-next","page":"iOS Actions","heading":"Focus Next Pane","text":"Focus Next Pane pane.focus_next pane focus next forward"},{"href":"/reference/ios-actions#action-pane-focus-left","page":"iOS Actions","heading":"Focus Pane Left","text":"Focus Pane Left pane.focus.left pane focus navigate left"},{"href":"/reference/ios-actions#action-pane-focus-right","page":"iOS Actions","heading":"Focus Pane Right","text":"Focus Pane Right pane.focus.right pane focus navigate right"},{"href":"/reference/ios-actions#action-pane-focus-up","page":"iOS Actions","heading":"Focus Pane Up","text":"Focus Pane Up pane.focus.up pane focus navigate up above"},{"href":"/reference/ios-actions#action-pane-focus-down","page":"iOS Actions","heading":"Focus Pane Down","text":"Focus Pane Down pane.focus.down pane focus navigate down below"},{"href":"/reference/ios-actions#action-pane-move-left","page":"iOS Actions","heading":"Move Pane Left","text":"Move Pane Left pane.move.left pane move swap rearrange left"},{"href":"/reference/ios-actions#action-pane-move-right","page":"iOS Actions","heading":"Move Pane Right","text":"Move Pane Right pane.move.right pane move swap rearrange right"},{"href":"/reference/ios-actions#action-pane-move-up","page":"iOS Actions","heading":"Move Pane Up","text":"Move Pane Up pane.move.up pane move swap rearrange up above"},{"href":"/reference/ios-actions#action-pane-move-down","page":"iOS Actions","heading":"Move Pane Down","text":"Move Pane Down pane.move.down pane move swap rearrange down below"},{"href":"/reference/ios-actions#action-pane-zoom","page":"iOS Actions","heading":"Zoom Pane","text":"Zoom Pane pane.zoom pane zoom maximize restore terminal"},{"href":"/reference/ios-actions#action-pane-move-to-new-tab","page":"iOS Actions","heading":"Move Pane to New Tab","text":"Move Pane to New Tab pane.move_to_new_tab pane block move new window tab detach split"},{"href":"/reference/ios-actions#action-pane-close","page":"iOS Actions","heading":"Close Pane","text":"Close Pane pane.close pane close remove terminal"},{"href":"/reference/ios-actions#action-pane-split","page":"iOS Actions","heading":"Split Pane","text":"Split Pane pane.split pane split terminal divide new"},{"href":"/reference/ios-actions#action-pane-split-auto","page":"iOS Actions","heading":"Split Pane Best Fit","text":"Split Pane Best Fit pane.split.auto pane split terminal divide new automatic best fit"},{"href":"/reference/ios-actions#action-pane-close-others","page":"iOS Actions","heading":"Close Other Panes","text":"Close Other Panes pane.close_others close other panes kill only keep focused"},{"href":"/reference/ios-actions#action-pane-move-to-tab","page":"iOS Actions","heading":"Move Pane to Tab…","text":"Move Pane to Tab… pane.move_to_tab pane block move tab window send other existing join"},{"href":"/reference/ios-actions#action-pane-go-to-directory","page":"iOS Actions","heading":"Go to Directory…","text":"Go to Directory… pane.go_to_directory go to directory folder path cd new tab open navigate"},{"href":"/reference/ios-actions#action-pane-insert-path","page":"iOS Actions","heading":"Insert Path…","text":"Insert Path… pane.insert_path insert path file folder directory cursor type paste complete browse"},{"href":"/reference/ios-actions#action-pane-copy-id","page":"iOS Actions","heading":"Copy Pane ID","text":"Copy Pane ID Copies the focused pane's block ID to the clipboard. pane.copy_id pane block id identifier copy clipboard target agent capture"},{"href":"/reference/ios-actions#action-pane-rename","page":"iOS Actions","heading":"Rename Pane","text":"Rename Pane pane.rename rename pane block name title label"},{"href":"/reference/ios-actions#action-pane-balance","page":"iOS Actions","heading":"Balance Splits","text":"Balance Splits pane.balance pane split balance equalize equalise evenly distribute resize layout"},{"href":"/reference/ios-actions#action-pane-resize","page":"iOS Actions","heading":"Resize Pane","text":"Resize Pane pane.resize pane resize grow shrink divider wider narrower taller shorter"},{"href":"/reference/ios-actions#action-pane-float","page":"iOS Actions","heading":"Float Pane","text":"Float Pane pane.float pane float floating window dock undock detach overlay pop out tile"},{"href":"/reference/ios-actions#action-pane-new-floating","page":"iOS Actions","heading":"New Floating Pane","text":"New Floating Pane pane.new_floating pane new create open float floating window terminal overlay popup scratchpad"},{"href":"/reference/ios-actions#action-pane-minimize-floating","page":"iOS Actions","heading":"Minimize Floating Window","text":"Minimize Floating Window pane.minimize_floating pane floating window hide show toggle minimize minimise restore taskbar capsule stash scratchpad"},{"href":"/reference/ios-actions#action-pane-minimize-all-floating","page":"iOS Actions","heading":"Minimize Floating Windows","text":"Minimize Floating Windows pane.minimize_all_floating pane floating windows all hide show toggle minimize minimise restore taskbar capsule stash scratchpad"},{"href":"/reference/ios-actions#action-pane-paste","page":"iOS Actions","heading":"Paste into Pane","text":"Paste into Pane Pastes the clipboard into the focused terminal. pane.paste paste clipboard insert text terminal"},{"href":"/reference/ios-actions#action-pane-copy","page":"iOS Actions","heading":"Copy from Pane","text":"Copy from Pane Copies the focused terminal's selection to the clipboard. pane.copy copy selection clipboard text terminal"},{"href":"/reference/ios-actions#action-pane-select-all","page":"iOS Actions","heading":"Select All in Pane","text":"Select All in Pane Selects everything in the focused terminal, including its scrollback. pane.select_all select all selection text terminal scrollback"},{"href":"/reference/ios-actions#action-pane-send-key","page":"iOS Actions","heading":"Send Key","text":"Send Key Sends one key to the focused terminal, as if it had been pressed there. pane.send_key send key terminal press chord binding"},{"href":"/reference/ios-actions#action-client-prompt-previous","page":"iOS Actions","heading":"Jump to Previous Prompt","text":"Jump to Previous Prompt client.prompt.previous jump scroll previous prompt command mark shell integration up back history"},{"href":"/reference/ios-actions#action-client-prompt-next","page":"iOS Actions","heading":"Jump to Next Prompt","text":"Jump to Next Prompt client.prompt.next jump scroll next prompt command mark shell integration down forward"},{"href":"/reference/ios-actions#action-client-scroll-page-up","page":"iOS Actions","heading":"Scroll Page Up","text":"Scroll Page Up client.scroll.page_up scroll page up back scrollback history"},{"href":"/reference/ios-actions#action-client-scroll-page-down","page":"iOS Actions","heading":"Scroll Page Down","text":"Scroll Page Down client.scroll.page_down scroll page down forward scrollback history"},{"href":"/reference/ios-actions#action-pane-go-to","page":"iOS Actions","heading":"Go to Anything…","text":"Go to Anything… pane.go_to go to anything pane block tab session agent jump switch navigate find search tree overview"},{"href":"/reference/ios-actions#action-pane-view-activity","page":"iOS Actions","heading":"View Activity…","text":"View Activity… pane.view_activity view activity active working blocked failed done agents programs status priority"},{"href":"/reference/ios-actions#pane","page":"iOS Actions","heading":"Pane","text":"Action Name"},{"href":"/reference/ios-settings","page":"iOS Settings","heading":"","text":"This page lists every setting in Rex for iOS and where to find it."},{"href":"/reference/ios-settings#setting-interface-color-scheme","page":"iOS Settings","heading":"Color scheme","text":"Color scheme Appearance · Use Auto, Light, or Dark appearance. In Settings > Interface. appearance, display, system, device, mode, automatic, light mode, dark mode"},{"href":"/reference/ios-settings#setting-interface-tint-with-theme","page":"iOS Settings","heading":"Tint with theme","text":"Tint with theme Appearance · Match terminal colors or use standard iOS colors. In Settings > Interface. theme tint, use theme tint, tinting, matching, neutral, system colors, on, off"},{"href":"/reference/ios-settings#setting-interface-match-program-backgrounds","page":"iOS Settings","heading":"Match program backgrounds","text":"Match program backgrounds Panes · Color the screen with a full-screen program's background. In Settings > Interface. tint, screen color, header color, terminal background, program background, editor, full screen, tui, neovim, vim, extend, blend, match terminal"},{"href":"/reference/ios-settings#interface","page":"iOS Settings","heading":"Interface","text":"Choose the color scheme, theme tint, panes, and how panes move. In Settings > Interface. display, automatic, system, light, dark, animation, motion Setting Description"},{"href":"/reference/ios-settings#setting-themes-light-theme","page":"iOS Settings","heading":"Light theme","text":"Light theme Light Mode · Choose a terminal palette for light appearance. In Settings > Themes. colors, day, scheme, light mode"},{"href":"/reference/ios-settings#setting-themes-dark-theme","page":"iOS Settings","heading":"Dark theme","text":"Dark theme Dark Mode · Choose a terminal palette for dark appearance. In Settings > Themes. colors, night, scheme, dark mode"},{"href":"/reference/ios-settings#themes","page":"iOS Settings","heading":"Themes","text":"Choose the terminal colors for each appearance. In Settings > Themes. palette, color scheme, light, dark Setting Description"},{"href":"/reference/ios-settings#setting-terminal-terminal-font","page":"iOS Settings","heading":"Terminal font","text":"Terminal font Typography · Choose the terminal's font family. In Settings > Terminal. font, typeface, monospace, family, menlo, sf mono, system font, jetbrains mono, geist mono, ioskeley mono, roboto mono"},{"href":"/reference/ios-settings#setting-terminal-font-size","page":"iOS Settings","heading":"Font size","text":"Font size Typography · Set the size of terminal text on this device. In Settings > Terminal. text size, bigger, larger, smaller, zoom, points, pt"},{"href":"/reference/ios-settings#setting-terminal-improve-text-contrast","page":"iOS Settings","heading":"Improve text contrast","text":"Improve text contrast Colors · Adjust low-contrast text against its background; included when forcing the theme palette. In Settings > Terminal. minimum contrast, automatic, legibility, readability, accessibility, unreadable, faint, pale, colors, on, off"},{"href":"/reference/ios-settings#setting-terminal-force-theme-palette-for-text","page":"iOS Settings","heading":"Force theme palette for text","text":"Force theme palette for text Colors · Map RGB and extended text colors to the nearest theme color, then correct contrast. In Settings > Terminal. force theme colors, 256 colors, truecolor, rgb, ansi, extended colors, nearest, palette"},{"href":"/reference/ios-settings#setting-terminal-cursor-style","page":"iOS Settings","heading":"Cursor style","text":"Cursor style Cursor · Choose a block, bar, or underline cursor. In Settings > Terminal. cursor, caret, shape, block, bar, beam, underline"},{"href":"/reference/ios-settings#setting-terminal-blink-speed","page":"iOS Settings","heading":"Blink speed","text":"Blink speed Cursor · Make the cursor blink slowly, normally, quickly, or not at all. In Settings > Terminal. cursor, blink, blinking, flash, solid, steady, off, slow, fast"},{"href":"/reference/ios-settings#setting-terminal-force-style","page":"iOS Settings","heading":"Force style","text":"Force style Cursor · Keep your cursor style when programs ask for another. In Settings > Terminal. cursor, override, ignore, lock, shape, decscusr"},{"href":"/reference/ios-settings#setting-terminal-autocorrect","page":"iOS Settings","heading":"Autocorrect","text":"Autocorrect Keyboard · Let the keyboard correct and predict words. In Settings > Terminal. typing, auto correction, autocorrection, predictive text, spelling"},{"href":"/reference/ios-settings#setting-terminal-keep-keyboard-open","page":"iOS Settings","heading":"Keep keyboard open","text":"Keep keyboard open Keyboard · Open the keyboard again when you come back to a terminal. In Settings > Terminal. keyboard, background, reopen, restore, focus"},{"href":"/reference/ios-settings#setting-terminal-key-repeat","page":"iOS Settings","heading":"Key repeat","text":"Key repeat Hardware Keyboard · Repeat a key you hold down on a hardware keyboard. In Settings > Terminal. hardware keyboard, magic keyboard, external keyboard, hold, held, repeating, backspace, delete, arrows, accessibility"},{"href":"/reference/ios-settings#setting-terminal-delay-until-repeat","page":"iOS Settings","heading":"Delay until repeat","text":"Delay until repeat Hardware Keyboard · How long to hold a key before it starts repeating. In Settings > Terminal. hardware keyboard, key repeat, delay, wait, hold, seconds, speed"},{"href":"/reference/ios-settings#setting-terminal-key-repeat-interval","page":"iOS Settings","heading":"Key repeat interval","text":"Key repeat interval Hardware Keyboard · How quickly a held key repeats. In Settings > Terminal. hardware keyboard, key repeat, rate, speed, fast, slow, seconds, interval"},{"href":"/reference/ios-settings#setting-terminal-repeat-held-letters","page":"iOS Settings","heading":"Repeat held letters","text":"Repeat held letters Hardware Keyboard · Repeat a held letter instead of showing its accents. In Settings > Terminal. hardware keyboard, accents, accented, accent menu, alternate characters, diacritics, long press, press and hold, letters, symbols"},{"href":"/reference/ios-settings#setting-terminal-key-bar-layout","page":"iOS Settings","heading":"Key Bar Layout","text":"Key Bar Layout Key Bar · Arrange, hide, and reorder the key bar's built-in keys. In Settings > Terminal. arrows, escape, tab, control, option, shift, paste, home, end, page up, page down, delete, return, rearrange, reorder, hide keys"},{"href":"/reference/ios-settings#setting-terminal-shortcut-groups","page":"iOS Settings","heading":"Shortcut Groups","text":"Shortcut Groups Key Bar · Add Control shortcuts, function keys, and your own. In Settings > Terminal. function keys, f1, ctrl, control, custom keys, slash commands, hotkeys, accessory"},{"href":"/reference/ios-settings#setting-terminal-characters","page":"iOS Settings","heading":"Characters","text":"Characters Choose the punctuation keys. In Settings > Terminal. punctuation, shell characters, symbols, tilde, pipe, slash"},{"href":"/reference/ios-settings#setting-terminal-use-symbols","page":"iOS Settings","heading":"Use symbols","text":"Use symbols Key Bar · Show symbols instead of abbreviated key names. In Settings > Terminal. keyboard, key bar, icons, control, option, shift"},{"href":"/reference/ios-settings#setting-terminal-haptic-feedback","page":"iOS Settings","heading":"Haptic feedback","text":"Haptic feedback Key Bar · Feel a light tap when pressing key bar buttons. In Settings > Terminal. keyboard, vibration, vibrate, touch"},{"href":"/reference/ios-settings#setting-terminal-command-palette","page":"iOS Settings","heading":"Command palette","text":"Command palette Key Bar · Learn how to open commands and use modifier keys. In Settings > Terminal. keyboard, shortcuts, command shift p, control, option, shift, paste"},{"href":"/reference/ios-settings#setting-terminal-smooth-scrolling","page":"iOS Settings","heading":"Smooth scrolling","text":"Smooth scrolling Scrolling · Move through history smoothly instead of a line at a time. In Settings > Terminal. scroll, scrollback, history, pixel, fling, momentum, drag"},{"href":"/reference/ios-settings#setting-terminal-smooth-terminal-resizing","page":"iOS Settings","heading":"Smooth terminal resizing","text":"Smooth terminal resizing Resizing · Reflow text as soon as a terminal changes size. In Settings > Terminal. smooth resize, resizing, reflow, re-wrap, rewrap, rotation, rotate, orientation, jitter, jump, flicker, stage manager, split view, preview, on, off"},{"href":"/reference/ios-settings#setting-terminal-prefer-generated-titles","page":"iOS Settings","heading":"Prefer generated titles","text":"Prefer generated titles Titles · Show the running program and its directory instead of shell titles. In Settings > Terminal. titles, tab title, window title, program name, directory, shell title, osc, rename"},{"href":"/reference/ios-settings#terminal","page":"iOS Settings","heading":"Terminal","text":"Configure the font, text colors, cursor, keyboard, key bar, scrolling, and titles. In Settings > Terminal. keyboard, input, accessory, shortcuts Setting Description"},{"href":"/reference/ios-settings#setting-remote-hosts-ssh-key","page":"iOS Settings","heading":"SSH Key","text":"SSH Key Create or import the key Rex logs in to ssh:// hosts with, and the host keys it trusts. In Settings > Remote Hosts. ssh, key, private key, public key, authorized_keys, secure enclave, import, passphrase, ed25519, ecdsa, host key, fingerprint, known hosts, trust"},{"href":"/reference/ios-settings#remote-hosts","page":"iOS Settings","heading":"Remote Hosts","text":"Manage saved hosts, their connections, and the shell each one starts. In Settings > Remote Hosts. server, add, pair, connect, reconnect, edit, delete, remove, startup command, shell, default shell, login shell, nushell, zsh, fish Setting Description"},{"href":"/reference/ios-settings#setting-developer-mode-network-diagnostics","page":"iOS Settings","heading":"Network Diagnostics","text":"Network Diagnostics With Developer Mode on, float the network path and host connections over the screen. In Settings > Developer Mode. Needs Developer Mode. debug, network, path, wi-fi, cellular, reconnect, overlay"},{"href":"/reference/ios-settings#setting-developer-mode-cursor-shaders","page":"iOS Settings","heading":"Cursor shaders","text":"Cursor shaders Experimental · With Developer Mode on, draw a trail shader, such as Warp, Comet, or Blaze, behind the cursor. In Settings > Developer Mode. Needs Developer Mode. cursor, cursor trail, cursor effect, cursor animation, smear, glow, warp, sweep, comet, blaze, tapered, rainbow, gradient, frozen, ghostty shader, custom-shader, shadertoy, shader, experimental"},{"href":"/reference/ios-settings#developer-mode","page":"iOS Settings","heading":"Developer Mode","text":"Developer · Expose settings for development and debugging. In Settings > Developer Mode. developer, debug, diagnostics Setting Description"},{"href":"/reference/ios-settings#about","page":"iOS Settings","heading":"About","text":"About · See the app's version and device, and copy them for a bug report. In Settings > About. version, build, commit, device, model, ios, system, bug report, feedback, copy version info, copy debug info, diagnostics, support, license, open source, acknowledgements, fonts"},{"href":"/reference/lua","page":"Lua API","heading":"","text":"This page lists the full Lua API Rex exposes. Rex only adds API to the global rex table. Rex runs Lua 5.1 with the full standard library, including io, os, and require. The one exception is io.popen and os.execute, which raise an error. Use rex.exec to run commands instead."},{"href":"/reference/lua#where-each-function-works","page":"Lua API","heading":"Where Each Function Works","text":"Rex runs Lua in two places, the configuration file and scripts, and each function below is tagged with where it works: Tag Description Config Works in the configuration file. Script Works in scripts run with rex do. Config at load Only at load, while the file runs from top to bottom. Either tag can be narrowed this way. Config later Only later, inside an action, a function bound to a key, or an event handler. Anything short of working is noted at the top of the function's docs. <!-- This table, headed Function, isn't shown. The build turns each row into the tags and notes on its functions (see src/lib/disclosures.js), so edit it here. --> Function Config, at load Config, later Script, at load Script, later rex.bind, rex.unbind Yes Functions only No effect No effect rex.mode, rex.bindings, rex.modes Yes Error No effect Error rex.action Yes Error Yes Error rex.host Yes Error No effect Error rex.on Warning Error Yes Error rex.call, rex.session.*, rex.block.call Fails Yes Yes Yes rex.invoke Warning Yes Yes Yes rex.client.queue Error Actions only Yes Yes rex.exec, rex.exec.start Yes Yes Yes Yes rex.layout.*, rex.json.*, rex.log Yes Yes Yes Yes rex.sleep, rex.wait, rex.stop Missing Missing Yes Yes rex.after, rex.every Missing Missing Yes Yes rex.args, rex.session_id, rex.block_id Missing Missing Yes Yes No effect means the call is accepted and does nothing useful. A script's key bindings and hosts never reach a keymap or the server. Missing means the name is nil. Calling it is an error, so a file shared between both should check for it first. Fails means the call returns nil and an error message, and is reported as a warning."},{"href":"/reference/lua#key-bindings","page":"Lua API","heading":"Key Bindings","text":"See Keyboard Shortcuts, Key Sequences, and Key Modes."},{"href":"/reference/lua#rexbindkey-action-args","page":"Lua API","heading":"rex.bind(key, action[, args])","text":"Binds a key to an action by name. args is an optional table of arguments passed to the action. key is a single key, a sequence written with >, or either one prefixed with a mode and a slash."},{"href":"/reference/lua#rexbindkey-fn","page":"Lua API","heading":"rex.bind(key, fn)","text":"Binds a key to a Lua function called as fn(ctx, ev). Return true to mark the key as handled. Not allowed in a mode. ev.keybinding is the key that was pressed. For a sequence, ev.prefix is a list of the keys before it."},{"href":"/reference/lua#rexunbindkey-target","page":"Lua API","heading":"rex.unbind(key[, target])","text":"Removes the bindings of a key in this file and in the client's defaults. Returns the number of this file's bindings it removed. target is an action name or a function, and limits the removal to bindings with that target. rex.unbind(\"mode/key\") also blocks the key in that mode."},{"href":"/reference/lua#rexunbind123-mode--name-125","page":"Lua API","heading":"rex.unbind{ mode = name }","text":"Removes a whole mode: its bindings, its blocked keys, and its options."},{"href":"/reference/lua#rexunbindbinding","page":"Lua API","heading":"rex.unbind(binding)","text":"Removes one binding, given a table returned by rex.bindings. Returns 1."},{"href":"/reference/lua#rexmodename-options","page":"Lua API","heading":"rex.mode(name[, options])","text":"Declares a mode and sets its options. The only option is exclusive, a boolean that defaults to false."},{"href":"/reference/lua#rexbindingsfilter","page":"Lua API","heading":"rex.bindings([filter])","text":"Returns the bindings this file and the files it requires have made so far, in order. filter is an optional table with any of key, mode, and action. Each binding is a table: Field Description key The key or sequence, without its mode. mode The mode, or nil. action The action name, or nil for a function binding. fn The function, or nil for an action binding. args The arguments table, or nil. source Where it was bound, such as keys.lua:3."},{"href":"/reference/lua#rexmodes","page":"Lua API","heading":"rex.modes()","text":"Returns the modes declared or bound so far, each a table with name, exclusive, and source."},{"href":"/reference/lua#actions","page":"Lua API","heading":"Actions","text":"See Custom Actions."},{"href":"/reference/lua#rexaction123--125","page":"Lua API","heading":"rex.action{ ... }","text":"Defines an action. Field Type Description name string Required. Lowercase letters, digits, and underscores. title string Required. Shown in the command palette. run function Required. Called as run(ctx, args). description string A longer explanation. category string A group for the palette. Defaults to Actions. keywords string Extra words for search. args table Argument types, or a JSON Schema. repeats boolean Run again while the key is held. args types are string, number, integer, boolean, object, array, and any. A trailing ? marks an argument as optional. ctx has session_id, block_id, client_id, origin, and server. A field Rex doesn't know is absent. The value run returns is the action's result."},{"href":"/reference/lua#rexinvokename-args","page":"Lua API","heading":"rex.invoke(name[, args])","text":"Runs an action and returns its result, or nil and an error message. In the configuration file, only that file's own actions can be invoked. In a script, a name the script doesn't define is run by the server, so a script can invoke actions from the configuration file. Calls can nest up to 16 deep."},{"href":"/reference/lua#rexclientqueuename-args","page":"Lua API","heading":"rex.client.queue(name[, args])","text":"Asks the client to perform one of its own actions, such as pane.zoom. In an action, queued actions run in order after run returns, and are discarded if it fails. In a script, each one is performed immediately. Raises an error when there is no client to ask."},{"href":"/reference/lua#calling-rex","page":"Lua API","heading":"Calling Rex","text":"See Lua Scripts. Every call in this section returns a result table, or nil and an error message. A failure never raises an error."},{"href":"/reference/lua#rexsessionltmethodgtargs","page":"Lua API","heading":"rex.session.<method>(args)","text":"Calls a session method. args is a table. session_id is filled in when it is left out. Other IDs, such as block_id, are not. Method Description view Return the session's windows, layouts, and blocks. list_blocks List every block and whether it is placed or detached. list_clients List the clients attached to the session. describe_block Return a block's placement and the methods it offers. new_window Create a window from a layout. new_split Create a split beside an existing block. new_block Create a block without placing it in a layout. new_layer Create a floating layer in a window. close_window Close a window and every block in it. close_layer Close a floating layer and every block in it. move_block Move a block into a new split beside another. swap_blocks Exchange the positions of two blocks. detach_block Remove a block from its layout without closing it. float_block Move a tiled block into a new floating layer. zoom_block Toggle or set whether a block fills its window. resize_pane Move the border between two blocks. resize_layer Set a floating layer's position and size. raise_layer, lower_layer Change a floating layer's stacking order. move_window Move a window in the tab order. focus_block Focus a block. focus_window Focus a window. focus_direction Focus the nearest block in a direction. focus_next_pane, focus_previous_pane Focus the next or previous pane. focus_next_window, focus_previous_window Focus the next or previous window. set_label Set the session's label. set_window_label Set a window's label. set_block_label Set a block's label. attach, detach Change which session a script hears events from. help Describe the methods available through the session. For the arguments and result of a method, run rex api describe with its full name:"},{"href":"/reference/lua#rexcallmethod-args","page":"Lua API","heading":"rex.call(method[, args])","text":"Calls any server method by its full name, including the ones rex.session doesn't cover. rex api list prints every method."},{"href":"/reference/lua#rexblockcallcreator-method-args","page":"Lua API","heading":"rex.block.call(creator, method[, args])","text":"Calls a method on a block. creator is the full name of the kind of block. block_id is filled in from the current block when it is left out. The terminal's methods are: Method Description format Return the screen as text, HTML, or VT sequences. process Describe the terminal's child and foreground processes. pwd Return the working directory the terminal last reported. title Return the terminal's title. size Return the terminal's size in cells and pixels. write Write bytes to the terminal as if they were typed. reset Reset the terminal. resize Request a size for the terminal. set_theme Replace the terminal's default colors. program_status Return the status records programs have reported. list_dir List the directories under a path."},{"href":"/reference/lua#rexlayout","page":"Lua API","heading":"rex.layout","text":"Functions that build the layout table taken by new_window, new_split, and new_layer. They only build tables and call nothing. Function Description rex.layout.block{ flavor, options } One block. flavor names what to create, such as com.superlogical.terminal.shell. rex.layout.horizontal(ratio, a, b) Two layouts split horizontally. ratio is the share given to a, from 0 to 1. rex.layout.vertical(ratio, a, b) Two layouts split vertically. The options for a terminal are: Option Description command A list with the program to run and its arguments. Without it, an interactive shell starts. shell The shell to use. none starts command directly. cwd The working directory. initial_input Text typed into the terminal once it starts. exit { on_completion = false } keeps the terminal open after its program exits."},{"href":"/reference/lua#events","page":"Lua API","heading":"Events","text":"See Lua Events. These work in scripts run with rex do only."},{"href":"/reference/lua#rexonevent-fn","page":"Lua API","heading":"rex.on(event, fn)","text":"Calls fn(ctx, ev) each time event happens. ev holds the event's fields. ctx has session_id, and block_id for a block event. The events reference lists the event names."},{"href":"/reference/lua#rexwaitevent-filter-timeout","page":"Lua API","heading":"rex.wait(event[, filter][, timeout])","text":"Blocks until event happens, and returns ev and ctx. Returns nil and \"timeout\" if timeout seconds pass first. filter is a table of fields the event must match, or a function called as filter(ev, ctx) that returns true for a match."},{"href":"/reference/lua#rexsleepseconds","page":"Lua API","heading":"rex.sleep(seconds)","text":"Blocks for a number of seconds, which may be fractional."},{"href":"/reference/lua#rexstopcode","page":"Lua API","heading":"rex.stop([code])","text":"Ends the script immediately. rex do exits with code, or 0."},{"href":"/reference/lua#hosts-and-other-servers","page":"Lua API","heading":"Hosts and Other Servers","text":""},{"href":"/reference/lua#rexhost123--125","page":"Lua API","heading":"rex.host{ ... }","text":"Declares another Rex server you connect to. Configuration file only. Field Description label Required. The host's name. endpoints Required. A list of addresses, tried in order. endpoint A single address, in place of endpoints. id A stable ID, so the host keeps its identity when renamed. icon An icon name for clients to show."},{"href":"/reference/lua#rexservers","page":"Lua API","heading":"rex.servers()","text":"Returns the labels of the other servers available to the running action or script, as a list."},{"href":"/reference/lua#rexserverlabel","page":"Lua API","heading":"rex.server(label)","text":"Returns a table with the same session, block, and call functions as rex, whose calls go to that server. Raises an error for an unknown label. Calls made through it don't fill in a session_id or block_id."},{"href":"/reference/lua#other-functions","page":"Lua API","heading":"Other Functions","text":"Functions for running commands, timers, JSON, and logging. See Running Commands and Timers."},{"href":"/reference/lua#rexexecargv-opts","page":"Lua API","heading":"rex.exec(argv[, opts])","text":"Runs a command, waits for it to exit, and returns a result table. Returns nil and an error message only when the command never ran, such as when the program doesn't exist. argv is a list of strings: the program, then each of its arguments. No shell reads it, so arguments need no quoting. A program without a slash in its name is looked up in the command's PATH. opts is an optional table: Option Description cwd The working directory. Defaults to Rex's own. env Environment variables to set, such as { GIT_DIR = \"/repo/.git\" }. false removes one. clear_env true starts from an empty environment. env still applies. stdin A string written to the command's standard input. timeout Seconds until the command is sent SIGTERM, then SIGKILL two seconds later. stdout, stderr false discards the stream. Both are captured by default. text false keeps \\r\\n in the output on Windows. An option Rex doesn't know is ignored with a warning. The result has: Field Description code The exit status, or -1 if a signal ended the command. signal The name of that signal, such as SIGTERM, or nil. stdout, stderr What the command printed. \"\" when it printed nothing. timed_out true if timeout stopped the command. ok true if code is 0 and the command didn't time out. A non-zero exit is still a result, not an error. Check ok. While an action in the configuration file waits for a command, the server keeps handling keys and other actions. Everywhere else, the wait blocks the script."},{"href":"/reference/lua#rexexecstartargv-opts","page":"Lua API","heading":"rex.exec.start(argv[, opts])","text":"Starts a command and returns a handle without waiting, or nil and an error message. It takes the same arguments as rex.exec, and a few more options: Option Description stdin true keeps standard input open for h:write. stdout, stderr A function called with each piece of output as it arrives. A piece may end mid-line. detach true lets the command outlive the work that started it. The handle has: Method Description h:wait([seconds]) Waits for the command to exit and returns the same result as rex.exec. Returns nil and \"timeout\" if seconds pass first. h:write(data) Writes to standard input. Returns true, or nil and an error message. h:close_stdin() Closes standard input. h:kill([signal]) Sends a signal, SIGTERM by default. signal is a name, such as SIGINT, or a number. h:running() Returns whether the command is still running. h.pid The command's process ID. A command lasts as long as the work that started it. When an action, handler, or timer returns, or a script's top level finishes, Rex stops the commands it started. A command started with detach = true is never stopped this way."},{"href":"/reference/lua#rexafterseconds-fn","page":"Lua API","heading":"rex.after(seconds, fn)","text":"Calls fn(ctx) once, after seconds, and returns a timer with the same methods as rex.every's. seconds may be zero or fractional. ctx is the same table an action's run gets, with ctx.origin set to timer. API calls default to the script's session and block."},{"href":"/reference/lua#rexeveryseconds-fn","page":"Lua API","heading":"rex.every(seconds, fn)","text":"Calls fn(ctx) every seconds until the timer is cancelled, and returns a timer. seconds must be more than zero. If the script is still busy when a tick comes, fn runs once when it is free, and the missed ticks are dropped. A timer has two methods: Method Description t:cancel() Stops the timer. t:active() Returns true until the timer is cancelled or has fired for the last time. An error in fn is reported and the timer keeps running. A script can have at most 1,024 timers at once. Creating one more stops the script with status 1."},{"href":"/reference/lua#rexjsonencodevalue-opts","page":"Lua API","heading":"rex.json.encode(value[, opts])","text":"Turns a Lua value into JSON text, or returns nil and an error message. opts.indent = true indents the text by two spaces. Tables convert the same way they do in an API call. A table whose keys are 1 to n becomes an array, and any other table, including an empty one, becomes an object. A function, or a table that contains itself, can't be encoded."},{"href":"/reference/lua#rexjsondecodetext","page":"Lua API","heading":"rex.json.decode(text)","text":"Turns JSON text into a Lua value, or returns nil and an error message. null becomes nil, and a null in an array is dropped."},{"href":"/reference/lua#rexloglevel-message","page":"Lua API","heading":"rex.log([level, ]message)","text":"Writes a log message. level is debug, info, warn, or error, and defaults to info. In a script the message is printed to the terminal."},{"href":"/reference/lua#script-values","page":"Lua API","heading":"Script Values","text":"Set in scripts run with rex do only. Value Description rex.args The script's arguments, as a table. Empty when none were passed. rex.session_id The ID of the session the script runs against. rex.block_id The ID of the block the script runs against, or nil."},{"href":"/reference/events","page":"Events","heading":"","text":"Rex emits an event whenever something changes: a window opens, a terminal changes directory, a program exits, a session is destroyed. Events can be handled in Lua with rex.on and rex.wait, or streamed from the CLI with rex events. Events come from three places: Kind Describes Lua name Session events Changes to one session's windows, blocks, and clients. The event's type, such as block_closed. Terminal events Things a terminal reports. terminal. and the event, such as terminal.bell. Server events Changes to the server as a whole. The event's type, such as session_created."},{"href":"/reference/events#events-in-lua","page":"Events","heading":"Events in Lua","text":"A handler is called with ctx and ev. The fields listed for each event on this page are found directly on ev: Every ev also has these fields: Field Description ev.type The event's type. For every terminal event this is block_event. ev.session_id The session the event happened in. Absent for most server events. ev.payload The event's own fields again, as a table of their own. A terminal event, and any other block event, adds: Field Description ev.block_id The block the event came from. ev.creator_name The kind of block, such as com.superlogical.terminal. ev.name The event's name without the creator, such as bell. ctx.session_id and ctx.block_id hold the same IDs, and are what API calls in the handler default to. Register for block_event to receive every event from every block, and use ev.creator_name and ev.name to tell them apart."},{"href":"/reference/events#session-events","page":"Events","heading":"Session Events","text":"These describe one session. A script hears them for the session it runs against, or for every session with --all. Event When Fields window_created A window is created. window_id, label window_closed A window is closed. window_id window_label_changed A window is renamed. window_id, label active_window_changed A different window becomes the active one. window_id block_created A block is created. block_id block_closed A block is closed. block_id session_label_changed The session is renamed. label session_view_changed The session's layout changes in any way. revision client_connected A client attaches to the session. client_id, principal, connected_at client_disconnected A client detaches from the session. client_id, principal client_connection_changed An attached client's connection is lost or restored. client_id, principal, connection_state creator_changed The kinds of block the session can create have changed. session_view_changed follows almost every other event in this table, because most of them change the layout. Handle it to react to any structural change, and call rex.session.view to get the new layout."},{"href":"/reference/events#terminal-events","page":"Events","heading":"Terminal Events","text":"These come from terminal blocks. In Lua, write them as terminal. followed by the name, or in full as com.superlogical.terminal. followed by the name. Event When Fields terminal.child_exited The terminal's program exits. exit_code, and error when it is not zero terminal.pwd_changed The program reports a new working directory. pwd, owner terminal.title_changed The terminal's title changes. title, owner terminal.bell The terminal receives a bell character. terminal.process_changed The foreground process changes. generation, foreground, ancestors terminal.desktop_notification The program asks for a desktop notification. title, body terminal.progress_report The program reports progress. state, progress terminal.clipboard_written The program writes to the clipboard. text terminal.program_status_changed A program reports or updates its status. id, record, reason terminal.program_status_removed Program status records are removed. ids, reason A few notes on these: terminal.child_exited is about the program the terminal started, which is usually your shell. It doesn't fire for each command you run inside a shell. A terminal started with a command fires it when that command exits. pwd is a URL such as file://hostname/Users/you/src, not a plain path. It is only reported by shells set up to report it. owner is a table with the pid and name of the process that made the change. foreground is a table describing the process in the foreground, with name, cwd, pid, and more. It can be nil."},{"href":"/reference/events#server-events","page":"Events","heading":"Server Events","text":"These describe the server rather than one session. In Lua, a script hears them when run with --all. Event When Fields session_created A session is created. session, a table with session_id and label session_destroyed A session is destroyed. session_id client_joined A client connects to the server. client client_left A client disconnects from the server. client_id client_changed A connected client's details change. client keymap_changed The configuration file is reloaded. generation, hash hosts_changed The list of hosts changes. hash tailscale_status_changed The server's Tailscale status changes. client is a table with the client's client_id, info (its kind, name, platform, and version), principal, and connected_at."},{"href":"/reference/events#events-on-the-cli","page":"Events","heading":"Events on the CLI","text":"rex events --json prints one JSON object per line. Each has a type and a payload holding the fields listed on this page: A session event arrives wrapped in a session_event, which adds the session it belongs to. A terminal event is wrapped once more, in a block_event (shown here on several lines): Lua removes both layers of wrapping, which is why a handler sees exit_code directly on ev. What rex events prints depends on its scope: Command Prints rex events Sessions being created, destroyed, and changed. rex events -s SESSION Everything in one session, including terminal events. rex events --all Everything, including clients joining and leaving."},{"href":"/reference/events#unknown-events","page":"Events","heading":"Unknown Events","text":"New versions of Rex add events and fields. Rex never checks an event name against a list, so a handler can be registered for an event that only a newer server sends. Ignore fields you don't recognize rather than failing on them."},{"href":"/reference/privacy","page":"Privacy & Telemetry","heading":"","text":"Rex doesn't collect telemetry. It has no usage analytics, no crash reporting of its own, and no account or license checks. This page lists every network connection Rex makes, when it makes it, and what it sends, for each part of Rex. Programs you run inside Rex aren't covered. They make their own connections, and we don't control them. Rex is still in testing. Rex changes quickly and this page could fall behind. If anything here is wrong, please apply Hanlon's razor: assume it's a mistake, not malice, and report it in the beta Discord so we can fix it."},{"href":"/reference/privacy#outbound-connections","page":"Privacy & Telemetry","heading":"Outbound Connections","text":"This is the list of outbound connections that Rex may make. Connection When Where Update check Rex for Mac, at launch and once a day Superlogical's update server Remote servers Only servers you add The servers you add Crash reports Rex for iOS, if you share them or use TestFlight Apple, then Superlogical Package updates Linux and WinGet, when your package manager updates Superlogical's package repository Tailscale Only if you turn it on Tailscale's servers"},{"href":"/reference/privacy#rex-for-mac","page":"Privacy & Telemetry","heading":"Rex for Mac","text":""},{"href":"/reference/privacy#update-checks","page":"Privacy & Telemetry","heading":"Update Checks","text":"Rex for Mac checks for new versions at launch and then once a day by downloading a signed feed of releases from Superlogical's update server. Update checks can't be turned off. Each check sends only what any web request does: Your IP address. A user agent with the app's name and version, such as Rex Beta/0.1.0 Sparkle/2.9.6. It doesn't send an account, a device identifier, a system profile, or anything about how you use Rex. An update is downloaded only when you choose to install it."},{"href":"/reference/privacy#the-local-server","page":"Privacy & Telemetry","heading":"The Local Server","text":"Rex for Mac runs a Rex server on your Mac and talks to it over a Unix socket, so that traffic never leaves your machine. The app starts it without any network listeners or Tailscale."},{"href":"/reference/privacy#remote-servers","page":"Privacy & Telemetry","heading":"Remote Servers","text":"Rex for Mac connects only to the servers you add. It stays connected to them, and reconnects at launch, until you remove them. It doesn't look for servers on your network."},{"href":"/reference/privacy#rex-for-ios","page":"Privacy & Telemetry","heading":"Rex for iOS","text":"Rex for iOS doesn't collect telemetry of its own. Apple collects some data for every app, and shares it with Superlogical only if you allow it."},{"href":"/reference/privacy#crash-reports-and-analytics","page":"Privacy & Telemetry","heading":"Crash Reports and Analytics","text":"If Share With App Developers is on, in **Settings > Privacy & Security > Analytics & Improvements**, Apple sends us: Crash reports, with the device model, iOS version, app version, and where in Rex's code the crash happened. Diagnostics such as hangs, slow launches, and heavy battery or disk use. App Store analytics, such as sessions and active devices. This data is combined with other users' so no one person can be identified. None of this data includes any extra app-specific metadata such as your server list, names, session layouts, terminal contents, etc. It is only the generic app data that all App Store apps report. With Share With App Developers off, Apple doesn't send us any of it. Apple always reports how many times Rex was downloaded, as it does for every app, whatever this setting is. If you use a TestFlight build (a preview release), TestFlight sends us crash reports and any feedback you submit, regardless of the **Share With App Developers** setting. This is a requirement of TestFlight."},{"href":"/reference/privacy#updates","page":"Privacy & Telemetry","heading":"Updates","text":"Rex for iOS never checks for updates itself. The App Store or TestFlight updates it."},{"href":"/reference/privacy#remote-servers-1","page":"Privacy & Telemetry","heading":"Remote Servers","text":"Rex for iOS connects only to the servers you add, the same way Rex for Mac does. It doesn't look for servers on your network."},{"href":"/reference/privacy#rex-server-all-platforms","page":"Privacy & Telemetry","heading":"Rex Server (All Platforms)","text":"With default settings, the Rex server and the rex CLI make no outbound connections."},{"href":"/reference/privacy#updates-1","page":"Privacy & Telemetry","heading":"Updates","text":"The server never checks for updates itself. On Linux, your system's package manager updates it along with your other packages, from Superlogical's package repository. With Nix, it's updated from Superlogical's servers when you update your flake. On Windows, WinGet checks Superlogical's WinGet source and downloads from Superlogical's servers, but only when a winget command names it with --source rex. These requests send only what the package manager or Nix always sends, such as your IP address and its user agent. winget list and winget upgrade normally send each source the product codes, names and publishers of the programs installed on your PC. Rex's WinGet source declares those fields unsupported, so WinGet leaves them out of its searches, and the source never learns what else you have installed. It answers from the list of Rex releases, and doesn't record the searches it receives."},{"href":"/reference/privacy#remote-servers-2","page":"Privacy & Telemetry","heading":"Remote Servers","text":"The CLI connects to another server only when you name one, with --server, REX_SERVER, rex home, or rex hosts."},{"href":"/reference/privacy#built-in-tailscale","page":"Privacy & Telemetry","heading":"Built-in Tailscale","text":"Built-in Tailscale is off until you turn it on. When it's on, the server runs its own Tailscale device, which connects to Tailscale like any other device does: Tailscale's coordination server, to sign in and join your tailnet. Tailscale's relay servers, and other devices on your tailnet. Your router, to open a port with UPnP, NAT-PMP, or PCP. Let's Encrypt, for the server's HTTPS certificate. The Tailscale device also uploads its logs and client metrics to Tailscale, as Tailscale's own apps do by default. These are Tailscale's logs about the connection, not your terminal contents. If your tailnet's admin turns on network flow logs, those go to Tailscale too. Tailscale's privacy policy covers this data. None of it goes to Superlogical."},{"href":"/reference/privacy#your-machines-tailscale","page":"Privacy & Telemetry","heading":"Your Machine's Tailscale","text":"With Tailscale Serve or a tailnet address, Rex only asks the Tailscale already installed on your machine who is connecting, over its local socket, and makes no connections of its own."},{"href":"/linux/installation","page":"Installing on Linux","heading":"","text":"Rex can run as a server on Linux so that clients can access it as a remote host. IMPORTANT: Rex on Linux is in testing. The install commands show below once you've logged in with Discord."},{"href":"/linux/installation#quickstart","page":"Installing on Linux","heading":"Quickstart","text":"This section documents the recommended setup to get up and running quickly. It reaches your own personal server from your clients over SSH, with nothing else to set up. We recommend you read the later sections, and Remote Access, to understand the details and other access options. On the Linux machine, install Rex as Install shows. Start your personal server at boot, and keep it running while you're logged out. This needs no sudo, run it as the user you want to connect with: Without systemd, such as on Alpine, skip this step. A server that the first connection starts then runs until the machine restarts. Check that you can log in with your SSH key from your client, and that SSH finds rex there: Connect from your client: In Rex for Mac, choose Add Remote Host… and enter ssh://alice@build.example.com. If several people need Rex on one machine, see Server Modes."},{"href":"/linux/installation#system-requirements","page":"Installing on Linux","heading":"System Requirements","text":"Requirement Supported Architecture x86-64, ARM64 C library glibc 2.26 or later (Amazon Linux 2, Ubuntu 18.04, Debian 10, RHEL 8, or newer), or musl on Alpine 3.21 or later Init system systemd, or OpenRC on Alpine, for the shared server and the login server. The rex command runs without either. Packages .deb (Debian, Ubuntu), .rpm (Fedora, RHEL and its rebuilds, openSUSE), .pkg.tar.zst (Arch Linux and derivatives), .apk (Alpine), Nix The Alpine package holds the musl build of Rex. It's dynamically linked, not static, because Rex loads PAM at run time. On other distributions, including other musl ones, install the binary alone with --tarball, which picks the musl build on a musl system."},{"href":"/linux/installation#install","page":"Installing on Linux","heading":"Install","text":"The install script (install.sh) is a convenience that adds the Rex repository to apt, dnf, zypper, pacman, or apk and installs the rex package, so updates arrive with your normal system updates. Running it again upgrades Rex, as Upgrading covers. To see what it would do first, add -s -- --dry-run after sh. On NixOS and other distributions without a Rex repository, it explains the options and exits. Option Effect --channel NAME Install from stable, preview, or snapshot. --tarball Install only the rex binary, into /usr/local/bin, with no services. Needs glibc 2.26 or later, or musl, where it installs the musl build. Run the script again to update. --dry-run Print every command without running it. Pass options after sh -s --. For example, to install only the binary, with no services:"},{"href":"/linux/installation#channels","page":"Installing on Linux","heading":"Channels","text":"Rex publishes to three channels: Channel What it gets stable Tagged releases (none right now) preview Tagged previews of the next release snapshot A frequent, likely unstable build The install script defaults to stable but until the first stable release exists, will fall back to preview and say so in the output. To switch channels later, change the channel name in the repository file: Suites: in /etc/apt/sources.list.d/rex.sources, or the name and baseurl lines in /etc/yum.repos.d/rex.repo or zypper's repository, or the Server line in /etc/pacman.d/rex.conf. On Alpine, change it in /etc/apk/repositories.d/rex.list if the install script set up the repository, or in the Rex line in /etc/apk/repositories if you added it yourself, then run apk update. Moving from snapshot to preview or stable upgrades Rex. Moving back to snapshot is a downgrade, which you have to ask for. See Switching Channels and Downgrading."},{"href":"/linux/installation#package-contents","page":"Installing on Linux","heading":"Package Contents","text":"The rex package installs: Path What it is /usr/bin/rex The server and the CLI, in one binary rex.service One shared server, run as the rex account. Not enabled. rex.socket Starts rex.service when a client first connects. Not enabled. rex-login-server.service One server per account, after a login. Not enabled. rex-login-server.socket Starts rex-login-server.service when a client first connects. Not enabled. rex account and group A locked system account, with its home at /var/lib/rex /etc/rex/accounts.jsonc The login server's accounts. It ships empty, which admits nobody. /etc/pam.d/rex-login-server The login server's PAM policy, for your distribution xterm-rex terminfo Rex's terminal type /etc/ssh/ssh_config.d/50-rex.conf Falls back to xterm-256color when you SSH from Rex to a host that doesn't know xterm-rex To run a server, enable one of the four units. Each socket starts its service the first time a client connects, instead of at boot. Server Modes helps you choose. On Alpine, OpenRC scripts at /etc/init.d/rex and /etc/init.d/rex-login-server take the place of the two services. The package never ships /etc/conf.d/rex or /etc/conf.d/rex-login-server, so your changes to the services go there. On Arch, the package creates the rex account through /usr/lib/sysusers.d/rex.conf, and its home through /usr/lib/tmpfiles.d/rex.conf. Upgrades keep your changes to the accounts file and the PAM policy. See Keeping Your Changes."},{"href":"/linux/installation#the-ssh-fallback","page":"Installing on Linux","heading":"The SSH Fallback","text":"50-rex.conf sets TERM with ssh's SetEnv keyword, so what it does depends on your OpenSSH version: OpenSSH What happens 8.7 or later ssh sends xterm-256color to hosts that don't know xterm-rex. 7.8 to 8.6 ssh reads the fallback but still sends xterm-rex. RHEL 8 has 8.0. Older than 7.8 ssh rejects any configuration that contains the fallback, so every ssh command fails. Amazon Linux 2 has 7.4, Ubuntu 18.04 has 7.6. rex terminfo setup, with or without --system, checks the version first. On an ssh older than 7.8, it installs the terminfo entry, skips the fallback, says why, and exits with status 1. The package can't check, so it installs 50-rex.conf everywhere. That's safe on Ubuntu 18.04 only because its ssh never reads the file, as Debian and Ubuntu explains. To check that ssh still parses its configuration, run:"},{"href":"/linux/installation#debian-and-ubuntu","page":"Installing on Linux","heading":"Debian and Ubuntu","text":"Debian and Ubuntu ship an unrelated package also named rex: (R)?ex, a Perl automation framework. The install script and the Debian tab pin the Rex repository above it, so apt installs Rex, and replaces (R)?ex if it's already installed. If rex -v prints (R)?ex, your machine's own apt preferences outranked the pin. Run apt-cache policy rex to see which rule won, fix it, then run the install script again. On Ubuntu 18.04, /etc/ssh/ssh_config has no Include line, so ssh never reads /etc/ssh/ssh_config.d/50-rex.conf. Its OpenSSH 7.6 is too old for the SSH fallback. **Don't add an Include line for /etc/ssh/ssh_config.d on Ubuntu 18.04.** It breaks ssh for every account on the machine."},{"href":"/linux/installation#fedora","page":"Installing on Linux","heading":"Fedora","text":"Fedora ships (R)?ex, the same unrelated tool, as Rex. Both packages own /usr/bin/rex, so remove it with sudo dnf remove Rex before you install Rex."},{"href":"/linux/installation#amazon-linux-2","page":"Installing on Linux","heading":"Amazon Linux 2","text":"Amazon Linux 2 reached the end of its support on 2026-06-30, and its rpm and systemd are too old for the Rex package. So the install script installs only the rex binary there, into /usr/local/bin, as --tarball does, and prints a notice saying so. The Fedora tab doesn't work on Amazon Linux 2, so use the install script. Amazon Linux 2023 uses the Rex repository, like Fedora. There are no services and no login server. To start a personal server for your account, run: To update Rex, run the install script again. A server that's already running keeps the old version until it stops. If an earlier run of the script left /etc/yum.repos.d/rex.repo behind, the script removes it. Its OpenSSH 7.4 is too old for the SSH fallback, so rex terminfo setup installs only the terminfo entry."},{"href":"/linux/installation#arch-linux","page":"Installing on Linux","heading":"Arch Linux","text":"On Arch, installing a package also upgrades the system, because pacman doesn't support partial upgrades. So the Arch Linux tab and the install script both run pacman -Syu. The script also passes --needed, so running it again with Rex up to date doesn't reinstall Rex or restart its services. Derivatives that set ID_LIKE=arch in /etc/os-release, such as Manjaro, EndeavourOS, CachyOS, and Garuda, install the same way. SteamOS doesn't, because its root file system is read-only. Omarchy needs one change, which Omarchy covers. The repository lists one version of Rex at a time, the newest on its channel. Older packages stay in the channel's directory, so to go back to one, pass its URL to pacman -U: If you installed rex-bin from the AUR, remove it first with sudo pacman -R rex-bin. It conflicts with rex, so the install script stops until it's gone. Unlike rex, removing rex-bin doesn't stop or disable the Rex units, so stop and disable any you enabled first:"},{"href":"/linux/installation#omarchy","page":"Installing on Linux","heading":"Omarchy","text":"Omarchy upgrades the system only through omarchy update, and it cancels pacman -Syu with a \"Woah partner\" message. The install script handles this. On Omarchy, it installs Rex without upgrading anything else, and omarchy update keeps Rex up to date from then on. To install by hand, run the Arch Linux tab without its last line, sudo pacman -Syu rex, then run: omarchy refresh pacman and omarchy channel set replace /etc/pacman.conf, which removes the Rex repository. Rex stays installed but stops getting updates until you run the install script again."},{"href":"/linux/installation#alpine","page":"Installing on Linux","heading":"Alpine","text":"Alpine has no sudo by default, so run the commands on these pages as root. Alpine uses OpenRC instead of systemd. Where a page gives a systemctl command, use the OpenRC form: On systemd On Alpine sudo systemctl enable --now rex.service rc-update add rex, then rc-service rex start sudo systemctl enable --now rex-login-server.service rc-update add rex-login-server, then rc-service rex-login-server start sudo systemctl reload rex-login-server.service rc-service rex-login-server reload sudo systemctl disable --now rex.service rc-service rex stop, then rc-update del rex sudo systemctl edit rex.service Set command_args in /etc/conf.d/rex journalctl -u rex.service -f tail -f /var/log/rex/server.log journalctl -u rex-login-server.service cat /var/log/rex-login-server.log sudo usermod --append --groups rex alice addgroup alice rex OpenRC has no socket activation, so rex.socket and rex-login-server.socket have no Alpine form. Use the services instead. /etc/conf.d/rex is a shell script that OpenRC reads before the service's own script, so a command_args set there replaces the server's command line. Shared Server and Remote Access show what to put in it. The services need OpenRC, which a full Alpine install has. Containers often don't. To install it, run apk add openrc. On musl, who and last don't list Rex sessions, because musl keeps no utmp or wtmp. lastlog records logins as usual."},{"href":"/linux/installation#nix-and-nixos","page":"Installing on Linux","heading":"Nix and NixOS","text":"Rex has a flake for each channel, at https://pkgs.superlogical.com/rex/nix/<channel>.tar.gz. It packages the release binary rather than building Rex from source, for x86-64 and ARM64 Linux. The Nix tab installs it into your profile. Older versions of Nix call nix profile add by its old name, nix profile install. To try Rex without installing it: The channel URL always serves the newest release on its channel, but flake.lock records the exact release, so a lock keeps working after newer releases come out. To move to the newest one, run nix flake update rex."},{"href":"/linux/installation#the-nixos-module","page":"Installing on Linux","heading":"The NixOS Module","text":"On NixOS, add the flake as an input and import rex.nixosModules.default. The module runs Rex as a systemd service in one of two modes, the same two the packages ship: services.rex.enable runs one shared server as the rex account, on /run/rex/server.sock. Members of the rex group can connect. services.rex.loginServer.enable runs one root process that identifies each caller, maps them to an account, and runs a server per account as that account. It listens on /run/rex/login-server.sock. They can't both be on. Either mode also installs rex for every user, puts Rex's terminal type where the service can find it, and adds the SSH fallback to /etc/ssh/ssh_config. services.rex.socketActivation and services.rex.loginServer.socketActivation start the server the first time a client connects, through rex.socket or rex-login-server.socket, instead of at boot. Each needs its mode's enable, and services.rex.socketActivation also needs a Unix socketPath. The module keeps the socket unit and the server's command line in agreement. With tailscaleServe.enable, the socket unit holds the Serve socket too, so a request through Tailscale Serve also starts the server. Unlike the packages' services, the module's services require their sockets, so stopping rex.socket alone stops the server too. Turning either option on or off restarts a running server at the next nixos-rebuild switch, which ends every session on a shared server. A shared server that one person can use: A login server that lets Alice use her own account over Tailscale Serve. The module writes the accounts to /etc/rex/accounts.jsonc, but doesn't create them: The host must be logged in to Tailscale, with HTTPS enabled for the tailnet. Changes to the accounts reload the login server without ending sessions. Connections whose account is no longer allowed are closed."},{"href":"/linux/installation#the-home-manager-module","page":"Installing on Linux","heading":"The home-manager Module","text":"The home-manager module sets up Rex for your own account. It works on any Linux with Nix and systemd, such as Fedora or Amazon Linux 2023, not only on NixOS. Add the flake as an input and import rex.homeManagerModules.default. It's also exported as rex.homeModules.default, the name some setups expect. A personal server that keeps running between sessions: The module has two parts: Option Effect programs.rex.enable Installs rex, writes the xterm-rex terminfo entry to ~/.terminfo, and adds the SSH fallback to ~/.ssh/config. programs.rex.config writes ~/.config/rex/init.lua. services.rex.enable Runs a personal server as your account, in a systemd user service named rex. Also turns on programs.rex. The SSH fallback needs programs.ssh.enable = true. Without it, the module prints a warning, and you can run rex terminfo setup once instead. Running rex terminfo setup with the module in place is harmless. It sees the guard home-manager already includes and writes nothing to your ssh configuration, and rex terminfo status reports that guard as in effect. Unlike rex terminfo setup, the module doesn't check your OpenSSH version. On an ssh older than 7.8, set programs.rex.sshGuard = false, because the SSH fallback breaks every ssh command there. The service uses the same socket as every rex command, so rex ls, rex server status, and rex server stop all reach it. systemd restarts it if it fails, but not after rex server stop. To start it again, run systemctl --user start rex. It returns once the server is accepting connections, so a rex command you run right after it works. Its log is in journalctl --user -u rex. The service starts when you log in and stops when you log out, which ends every session. home-manager switch warns about this each time. To start it at boot and keep it running while you're logged out, run this once: For remote access, services.rex.tailscale.enable turns on built-in Tailscale. authKeyFile needs systemd 250 or later (Fedora 36, Amazon Linux 2023). The other options, such as extraListen, blockDirs, and maxSessions, are described in home-manager-module.nix, which comes with the flake. On macOS, turn on only programs.rex. The Rex app runs the server, so services.rex stops the build with an error there. The shared server and the login server need root, so home-manager can't run them. Use the NixOS module, or the packages."},{"href":"/freebsd/installation","page":"Installing on FreeBSD","heading":"","text":"Rex can run as a server on FreeBSD so that clients can access it as a remote host. IMPORTANT: Rex on FreeBSD is in testing. The install commands show below once you've logged in with Discord. The other server pages, starting with Server Modes, apply to FreeBSD too. Where they give a Linux command or path, use the one in Commands and Paths."},{"href":"/freebsd/installation#quickstart","page":"Installing on FreeBSD","heading":"Quickstart","text":"This section documents the recommended setup to get up and running quickly. It reaches your own personal server from your clients over SSH, with nothing else to set up. We recommend you read the later sections, and Remote Access, to understand the details and other access options. On the FreeBSD machine, install Rex as Install documents. Check that you can log in with your SSH key from your client, and that SSH can access rex there: Connect from your client: In Rex for Mac, choose Add Remote Host… and enter ssh://alice@build.example.com. The first connection starts your personal server, and it keeps running after you log out, until you stop it or the machine restarts. If several people need Rex on one machine, see Server Modes."},{"href":"/freebsd/installation#system-requirements","page":"Installing on FreeBSD","heading":"System Requirements","text":"Requirement Supported Release FreeBSD 14.0 or later Architecture x86-64, ARM64 Dependencies None. Rex uses only the base system. Services rc.d, for the shared server and the login server. The rex command runs without them. Packages .pkg, from the Rex repository One package serves FreeBSD 14 and every later release."},{"href":"/freebsd/installation#install","page":"Installing on FreeBSD","heading":"Install","text":"The install script (install.sh) adds the Rex repository to pkg and installs the rex package, so pkg upgrade updates Rex along with your other packages. It takes the same options as on Linux, such as --channel, --tarball, and --dry-run. Pass them after sh -s --. FreeBSD has no sudo by default, so run the pkg tab's commands as root. The first, umask 022, makes the files they create readable by every user, whatever root's umask is. The install script needs root too, or sudo if you've installed it. FreeBSD has no curl either, so the commands download with FreeBSD's own fetch. If a channel has no FreeBSD package yet, the script installs only the rex binary into /usr/local/bin, as --tarball does, with no services. Run the script again once the channel has a package, and it switches to the package. pkg trusts the Rex repository by the fingerprint of its signing key, in /usr/local/etc/pkg/fingerprints/rex. If the key changes, run the install script again to trust the new one."},{"href":"/freebsd/installation#channels","page":"Installing on FreeBSD","heading":"Channels","text":"FreeBSD has the same three channels as Linux. To switch, change the channel name in the url line of /usr/local/etc/pkg/repos/rex.conf, then update the catalogue: Moving from snapshot to preview or stable upgrades Rex. Moving back to snapshot is a downgrade, which you have to ask for. See Upgrading on FreeBSD."},{"href":"/freebsd/installation#package-contents","page":"Installing on FreeBSD","heading":"Package Contents","text":"The rex package installs: Path What it is /usr/local/bin/rex The server and the CLI, in one binary /usr/local/etc/rc.d/rex One shared server, run as the rex account. Not enabled. /usr/local/etc/rc.d/rex-login-server One server per account, after a login. Not enabled. rex account and group A locked system account with the ID 729, or the next free one, and its home at /var/db/rex /usr/local/etc/rex/accounts.jsonc The login server's accounts. It ships empty, which admits nobody. /usr/local/etc/pam.d/rex-login-server The login server's PAM policy xterm-rex terminfo Rex's terminal type, in /usr/local/share/terminfo Unlike the Linux packages, it has no SSH fallback, because FreeBSD's /etc/ssh/ssh_config reads no other files. See Terminfo and SSH. Upgrades keep your changes to the accounts file, but replace the PAM policy. See Upgrading on FreeBSD. To see the package's setup notes again, run pkg info -D rex."},{"href":"/freebsd/installation#commands-and-paths","page":"Installing on FreeBSD","heading":"Commands and Paths","text":"The other server pages give systemd commands. On FreeBSD, use these instead: On systemd On FreeBSD sudo systemctl enable --now rex.service sysrc rex_enable=YES, then service rex start sudo systemctl enable --now rex-login-server.service sysrc rex_login_server_enable=YES, then service rex-login-server start sudo systemctl reload rex-login-server.service service rex-login-server reload sudo systemctl restart rex.service service rex restart sudo systemctl disable --now rex.service service rex stop, then sysrc rex_enable=NO sudo systemctl edit rex.service Set rex_flags with sysrc. See Settings. journalctl -u rex.service grep ' rex\\[' /var/log/messages journalctl -u rex-login-server.service grep ' rex-login-server\\[' /var/log/messages sudo usermod --append --groups rex alice pw groupmod rex -m alice sudo usermod --shell /bin/bash rex pw usermod rex -s /bin/sh FreeBSD has no socket activation, so rex.socket and rex-login-server.socket have no FreeBSD form. Use the services instead. Files are in different places too: On Linux On FreeBSD /usr/bin/rex /usr/local/bin/rex /run/rex /var/run/rex /var/lib/rex /var/db/rex /etc/rex/accounts.jsonc /usr/local/etc/rex/accounts.jsonc /etc/pam.d/rex-login-server /usr/local/etc/pam.d/rex-login-server /etc/nologin /var/run/nologin /run/user/<uid>/rex ~/.local/state/rex/run Both services log to syslog, which writes to /var/log/messages by default. The servers that the login server starts for each account log to /var/run/rex/accounts/<account>/server.log instead. If a server exits for any reason, its service starts it again 5 seconds later. Only service rex stop stops it for good. service rex start returns as soon as the server's process exists, which can be a moment before it accepts connections."},{"href":"/freebsd/installation#settings","page":"Installing on FreeBSD","heading":"Settings","text":"The services read their settings from /etc/rc.conf. Set them with sysrc, then restart the service to apply them. Upgrades replace the rc.d scripts but never change /etc/rc.conf. This example raises the shared server's session limit: Setting Default Effect rex_flags None More options for rex server run rex_umask 0007 The file mode mask for the server and every program it starts rex_user rex The account the server runs as rex_group rex The group that may use the socket rex_runtime_dir /var/run/rex The directory that holds the socket rex_state_dir /var/db/rex The server's data rex_login_server_flags None More options for rex login-server rex_login_server_accounts /usr/local/etc/rex/accounts.jsonc The accounts file rex_login_server_runtime_dir /var/run/rex The login server's socket and each account server's directory rex_flags and rex_login_server_flags add options to the service's command line. A systemd drop-in replaces the command line instead, so where another page shows a whole ExecStart line, put only the options it adds in the flags. The comment at the top of each rc.d script explains every setting. The shared server can run as another account instead of rex. This example runs it as alice: The files the server wrote before the change still belong to the old account. On the next start, the service gives them to the new one and prints a line for each directory it changes: The server keeps its saved sessions, and the programs in them now run as alice. Changing back to rex works the same way."},{"href":"/freebsd/installation#terminfo-and-ssh","page":"Installing on FreeBSD","heading":"Terminfo and SSH","text":"Programs in Rex terminals see TERM=xterm-rex, and the package installs that terminal type for every account. ssh passes TERM on to other hosts, which usually don't know it. To make ssh send xterm-256color to those hosts instead, for every account, run as root: FreeBSD's /etc/ssh/ssh_config reads no other files, so this also adds a marked Include line to it. rex terminfo remove --system takes the line out again. To set it up for your own account only, run rex terminfo setup."},{"href":"/windows/installation","page":"Installing on Windows","heading":"","text":"Rex can run as a server on Windows so that clients can access it as a remote host. IMPORTANT: Rex on Windows is in testing and is currently open to a subset of testers in Discord. We will extend testing to more people soon. On Windows, every server is a personal server, which runs as your own account. You can configure the Rex server to start at boot or at sign-in, as Running on Windows explains. Server Modes, Shared Server and Login Server do not apply to Windows."},{"href":"/windows/installation#system-requirements","page":"Installing on Windows","heading":"System Requirements","text":"Requirement Supported Release Windows 10 1809 or later Architecture x86-64, ARM64 Packages WinGet, MSI, or Zip"},{"href":"/windows/installation#install","page":"Installing on Windows","heading":"Install","text":"Rex isn't signed yet, so while it's in testing, Windows may warn that the publisher is unknown. Every installation scenario installs rex.exe and puts it on your PATH, but does not automatically start a server or install a Windows Service. You must do this manually with rex server install after Rex is installed."},{"href":"/windows/installation#winget-recommended","page":"Installing on Windows","heading":"WinGet (Recommended)","text":"WinGet is the recommended way to install Rex Server. You can install via WinGet from either a Zip source (default) or MSI: Administrator rights are required to add a new WinGet source, so run the first line in a terminal with \"Run as Administrator\" rights. The WinGet tab installs Rex for your own account, with no administrator rights. The WinGet (MSI) tab installs it for the whole machine instead, as explained in MSI. After installation, rex is added to your PATH. PATH changes require opening a new terminal, so open a new terminal to verify this."},{"href":"/windows/installation#msi","page":"Installing on Windows","heading":"MSI","text":"The MSI installs Rex for every account, into %ProgramFiles%\\Rex Server, and adds that folder to the system PATH. It needs administrator rights. The download above is the x86-64 MSI. On ARM64, use the WinGet (MSI) tab or the zip. Open the MSI to run its setup wizard, or install it without a window from a terminal opened with Run as administrator: To install somewhere other than %ProgramFiles%\\Rex Server, add INSTALLDIR=\"D:\\Tools\\Rex Server\"."},{"href":"/windows/installation#zip","page":"Installing on Windows","heading":"Zip","text":"The zip holds Rex's files with no installer. {#if download} Download the newest preview for your machine: {#each download.zips as zip} {zip.filename} ({zip.arch}) {/each} {:else} The downloads show here for testers with the Windows role. {/if} If you are using the Zip, it is expected that you know how to move it into a proper location and add rex.exe to your PATH."},{"href":"/windows/installation#channels","page":"Installing on Windows","heading":"Channels","text":"Rex publishes to three channels, each with its own WinGet package: Channel Package What it gets stable Superlogical.RexServer Tagged stable releases preview Superlogical.RexServer.Preview Tagged previews of the next release snapshot Superlogical.RexServer.Snapshot Development builds The install commands use preview. Until the first stable release, winget install Superlogical.RexServer fails, because WinGet treats a channel with no release as a package that doesn't exist. To switch channels, remove the package you have, as Removing Rex shows, then install the other one with --source rex."},{"href":"/windows/installation#package-contents","page":"Installing on Windows","heading":"Package Contents","text":"All packages have the same set of files: File What it is rex.exe The server and the CLI, in one binary OpenConsole.exe The console host from Windows Terminal LICENSE-OpenConsole.txt The license of OpenConsole.exe Each way of installing puts them in its own folder: Package Folder Added to WinGet %LocalAppData%\\Microsoft\\WinGet\\Packages\\Superlogical.RexServer.Preview_… User PATH MSI %ProgramFiles%\\Rex Server System PATH Zip The folder you extract it into User PATH, once you add it"},{"href":"/windows/installation#removing-rex","page":"Installing on Windows","heading":"Removing Rex","text":"Run rex server uninstall before you remove the package. Otherwise the Windows Service or sign-in entry that rex server install made is left pointing at a rex.exe that's gone. Removing the service needs a terminal opened with Run as administrator. Removing the package doesn't stop a server that's running, so stop it with rex server stop. Then remove the package. For the zip, use a normal terminal, because WinGet won't remove it from one opened with **Run as administrator**: For an MSI you installed yourself, remove \"Rex Server\" in Add or Remove Programs instead. For a zip, delete its folder and take it out of your user PATH. WinGet leaves the zip's folder on your user PATH, though the folder is gone. You can take it out of your PATH by hand. Your configuration, %AppData%\\rex\\init.lua, and Rex's state and logs, in %LocalAppData%\\rex, stay. Delete them to remove every trace of Rex."},{"href":"/windows/running","page":"Running on Windows","heading":"","text":"On Windows, every Rex server can only login as a single account: the account that installs the Windows service. Server Modes, Shared Server and Login Server are for Linux and FreeBSD, and don't apply here."},{"href":"/windows/running#quickstart","page":"Running on Windows","heading":"Quickstart","text":"This section documents the recommended setup to get up and running quickly. It purposely omits many details about how everything works. We recommend you read the later sections in detail to understand. Install Rex with WinGet, as Installing on Windows shows. Open a terminal with Run as administrator, and install and start Microsoft's OpenSSH Server: In the same terminal, add your public key, such as the contents of ~/.ssh/id_ed25519.pub. For an administrator account, sshd reads it from this file, and only if only administrators can change it: For a standard account, add the key to ~\\.ssh\\authorized_keys instead. On your Mac, connect to the PC, where pc is its name or address: In Rex for Mac, choose Add Remote Host… and enter ssh://alice@pc. The first connection starts your server, and it keeps running until you stop it. Windows lets SSH in only on networks set to Private. For Tailscale and other ways to connect, see Remote Access."},{"href":"/windows/running#server-lifetimes","page":"Running on Windows","heading":"Server Lifetimes","text":"How you run or install the server determines how long it stays running: Started by Starts Keeps running Needs administrator Any rex command When you run it Until about 10 s after its last client No An ssh:// connection When it connects Until you stop it No rex server start Now Until you stop it or sign out No rex server install --user Each time you sign in Until you sign out No rex server install At boot, as a Windows Service Until shutdown, signed in or not Yes If you only use Rex locally while you're at your PC, let rex commands autostart the server. If you want to connect to Rex remotely, start it at boot with rex server install. If you can't get administrator rights, then start it at sign in with rex server install --user."},{"href":"/windows/running#cli-triggered-servers","page":"Running on Windows","heading":"CLI-Triggered Servers","text":"Any rex command automatically starts a server if none is running: An automatically started server stops on its own about 10 seconds after its last session and its last connection close. To start one that keeps running until you stop it or sign out, start it explicitly: rex server status, rex server logs and rex server stop work as they do on Linux. See Personal Server. The server listens on the named pipe \\\\.\\pipe\\rex-<SID>, where <SID> is your account's security identifier. If you set REX_RUNTIME_DIR, the name gets a suffix, so servers with different runtime directories don't collide. To keep rex commands from starting a server at all, set REX_AUTOSTART=0."},{"href":"/windows/running#starting-at-boot","page":"Running on Windows","heading":"Starting at Boot","text":"rex server install creates a Windows Service that starts your server at boot and keeps it running while nobody is signed in. Run it from a terminal started with Run as administrator: The service runs as your own account, not as LocalSystem. The command prints each change before it makes it: It creates the service RexServer-<SID>, shown as \"Rex Server (PC\\alice)\" in the Services console. It's set to Automatic (Delayed Start) and runs \"<path to rex.exe>\" server run with your server flags. It grants your account the right to log on as a service. It registers \"Rex Server\" as a source of the Event Log. It deletes the sign-in entry, if you added one. If a server is already running and nothing is using it, the command stops it and starts the service in its place. If the server has sessions or clients, it keeps running, and the service starts at the next boot. The command says which happened. Running the command again is safe, and keeps your server flags. Run it again after you move rex.exe, such as when you switch between the WinGet zip and the MSI, so that the service runs the new path."},{"href":"/windows/running#starting-at-sign-in","page":"Running on Windows","heading":"Starting at Sign-In","text":"rex server install --user starts your server each time you sign in to Windows, and needs no administrator rights: It adds a value named \"Rex Server\" to HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run, which runs rex server start with your server flags. It also starts a server now if none is running. The server stops when you sign out. Running the command again is safe, and keeps your server flags. The value shows in the Startup tab of Task Manager, and in Settings under Apps > Startup. If it's turned off there, Windows skips it, and the command says so rather than turning it back on. The sign-in entry and the Windows Service can't both start your server. While the service exists, rex server install --user fails. Remove the service first, as Removing the Service shows."},{"href":"/windows/running#server-flags","page":"Running on Windows","heading":"Server Flags","text":"rex server tailscale on and other changes to a running server last only until it stops. To give your server flags each time it starts, put them after --: The Windows Service runs server run with the flags, and the sign-in entry runs server start with them. With --user, the server that the command starts right away gets them too. rex server run --help and rex server start --help list the flags that each one takes. What -- does when you run the command again: Command Server flags rex server install Keeps the flags already set rex server install -- --tailscale Replaces them with the new ones rex server install -- Removes them The same goes for --user. Moving from --user to the Windows Service carries the sign-in entry's flags over to the service. The command checks the flags before it changes anything, and refuses those that the server would refuse. It also refuses these, which don't work for a server that Windows starts: Flag Why --listen rex commands look for the server on its named pipe. Use --extra-listen to add an endpoint. --exit-empty The service would stop once it's idle, and Windows wouldn't start it again until the next boot. --tailscale-auth-key-file - Windows gives the server no standard input. Give the path of a file instead. A server that's already running keeps its old flags until it restarts. See Stopping and Restarting. rex server start doesn't read the sign-in entry, so to restart that server with its flags, stop it and run rex server install --user again, or sign in again."},{"href":"/windows/running#administrator-rights","page":"Running on Windows","heading":"Administrator Rights","text":"**A server that the Windows Service starts runs without administrator rights, even if your account is an administrator.** The service starts it with a reduced copy of its token, so every terminal, block plugin and rex.exec program runs as it would after a normal sign-in. A server that rex server start or any rex command starts gets the same reduced rights, even from a terminal opened with **Run as administrator**, or from an administrator's ssh session. If Windows can't make the reduced token, server startup fails. **rex server run in a terminal opened with Run as administrator keeps full administrator rights.** We assume in this scenario you explicitly want this."},{"href":"/windows/running#logs","page":"Running on Windows","heading":"Logs","text":"rex server logs shows the log of your server however it was started, the service included: The log is in %LocalAppData%\\rex\\servers. Rex clears it before it grows past 50MiB. The Windows Service also writes to the Application log in Event Viewer, under the source \"Rex Server\": Event ID Level Meaning 1 Information The service is starting 2 Information The service is running 3 Information Windows asked it to stop 4 Information The service stopped 5 Error The service stopped with an error, which the event names"},{"href":"/windows/running#stopping-and-restarting","page":"Running on Windows","heading":"Stopping and Restarting","text":"rex server stop stops your server, however it was started. This ends every program running in its sessions. The server saves its session layouts, and rex ls lists them again once it restarts: When the service started it, the service stays stopped until the next boot, or until you start it again. To start or stop the service, run these from a terminal started with Run as administrator: The Services console works too. Look for \"Rex Server (PC\\alice)\". When the service stops, it gives the server 15 seconds to save its sessions and exit before it ends it. While the service is stopped, a rex command can start a server on its own, and sc.exe start then fails until that server stops. Set REX_AUTOSTART=0 if you want to keep it from happening."},{"href":"/windows/running#removing-the-service","page":"Running on Windows","heading":"Removing the Service","text":"rex server uninstall stops the Windows Service, which ends every program running in its sessions, and then deletes it. Run it from a terminal started with Run as administrator: It also unregisters \"Rex Server\" as a source of the Event Log, unless another account's service still uses it, and deletes the sign-in entry. Once a service exists, the command needs administrator rights, even to delete only the sign-in entry. Without a service, it needs none, and a server that the sign-in entry started keeps running. Stop it with rex server stop. Your account keeps the right to log on as a service, because other services may need it. To remove it, open Local Security Policy (secpol.msc) and go to **Local Policies > User Rights Assignment > Log on as a service**."},{"href":"/windows/running#terminals-and-shells","page":"Running on Windows","heading":"Terminals and Shells","text":"Each terminal runs the first of pwsh.exe and powershell.exe on the server's PATH, or %ComSpec% if neither is there. There's no setting to change the default. To run another shell, give it as the command: A server started by a rex command, by rex server start or by the sign-in entry passes terminals only an allowlist of variables: the usual Windows ones, such as USERPROFILE, APPDATA and ProgramFiles, common toolchain variables, such as JAVA_HOME and GOPATH, and PATH, REX_*, XDG_*, LC_* and the proxy variables. To pass others, start the server with --env: --env applies only when it starts a new server. To change it, stop the server and start it again. To keep it each time you sign in, give it to the sign-in entry as a server flag: A server that the Windows Service starts gets the system and user variables Windows had when the service started. A variable you add in Settings reaches its terminals after the service restarts. The service refuses --env, because only rex server start takes it. Terminals have TERM set to xterm-256color, and rex terminfo setup is refused on Windows. Closing a terminal ends every process in it at once."},{"href":"/windows/running#not-available-on-windows","page":"Running on Windows","heading":"Not Available on Windows","text":"These parts of Rex need a Unix system: Feature On Windows The shared server and the login server Each account runs its own server Starting on demand through socket activation Any rex command starts the server --socket-mode Only your account can open the pipe tailnet-serve:// and login-server:// listeners Rex's own Tailscale node, with rex server install -- --tailscale tailnet:// with a userspace tailscaled tailscaled with its normal network adapter rex terminfo setup Terminals keep TERM=xterm-256color rex server install ssh:// to a Windows PC Install Rex on the PC, then reach it with an ssh:// endpoint Reopening the log with SIGHUP rex server logs and the 50MiB limit"},{"href":"/linux/server-modes","page":"Server Modes","heading":"","text":"How you run a Rex server decides which account it runs as and who can connect to it. **On Windows, there's only the personal server, as Running on Windows explains.** **If you only want to reach Rex on one machine from your Mac, start with a quickstart.** Each sets up a personal server that you reach over SSH: Linux, FreeBSD, or Windows. There are three ways to run the Rex server on Linux and FreeBSD: Mode Who it's for Runs as Started by Personal Just you Your account Any rex command, or rex server start Shared A few people you trust with one account rex rex.service, or rex.socket on demand Login Several people, each as themselves root, then each person's account rex-login-server.service, or rex-login-server.socket on demand If you're the only person using Rex on the machine, run a personal server. If a few people need it and can share one account, use the shared server. If you or others need to access and login as separate accounts, use the login server. The shared server and the login server can't run together. Their services and sockets all use /run/rex, so starting the service or socket of one stops both units of the other. On Alpine and FreeBSD, each refuses to start while the other is running. Personal servers can run alongside either one. With the login server, your personal server is the same server the login server connects you to, so local rex commands and remote connections see the same sessions. Every mode listens only on Unix sockets on this machine by default. To connect from another computer, see Remote Access."},{"href":"/linux/server-modes#personal-server","page":"Server Modes","heading":"Personal Server","text":"A personal server runs as your own user account and doesn't require any special setup or root access. Any rex command automatically starts a personal server if none is running: An automatically started personal server stops on its own around 10 seconds after its last session and its last connection close. To start one that remains running you must explicitly start it: rex server start does nothing if a server is already running, including one that a rex command started, so its safe to call multiple times. Its options, such as --max-sessions, apply only when it starts a new server. To change them, stop the server and start it again. Command Effect rex server status Show the running server's status. rex server logs --follow Show and follow the log of a server started by rex server start. rex server stop Stop the server. This ends every session on it. The socket is in $XDG_RUNTIME_DIR/rex, which is usually /run/user/<uid>/rex. Logs and other state are in ~/.local/state/rex, or $XDG_STATE_HOME/rex if you've set it. FreeBSD has no /run/user, so there the socket is in ~/.local/state/rex/run. The same goes for Linux machines without it, such as many containers. A rex command starts a server on its own only for a socket in that directory, or in REX_RUNTIME_DIR instead if you've set it. Given any other socket with -S, such as the shared server's, it starts nothing and says how to start a server there. To run one of your own at another path, start it yourself. Your account must be able to create files in the socket's directory: To keep rex commands from starting a server at all, set REX_AUTOSTART=0."},{"href":"/linux/server-modes#shared-server","page":"Server Modes","heading":"Shared Server","text":"A shared server is one server that runs as the rex system account. Members of the rex group can connect to it, and every program they start runs as rex. It's the simplest way to let more than one person use Rex. Access to the shared server is access to the rex account. Anyone in the group can see and type into every session on it. Only add people you would trust with that account. Add each person to the group, then start the service now and at every boot: A group change applies the next time that person logs in. The server keeps its data in /var/lib/rex, or /var/db/rex on FreeBSD. On Alpine, the OpenRC commands do both steps, and on FreeBSD, the FreeBSD commands. To start the server only when someone first connects, enable rex.socket instead of rex.service. See Starting on Demand. A package upgrade restarts the service, which automatically ends every session on it. See Upgrading. Read Shared Server before you set it up for other people. It covers giving them access, configuring the server, and changing the service without losing your changes on upgrade."},{"href":"/linux/server-modes#login-server","page":"Server Modes","heading":"Login Server","text":"The login server gives each person a server that runs as their own account, with their own files and permissions. It runs as root. When someone connects, it identifies them, checks /etc/rex/accounts.jsonc for the accounts they may use, and hands the connection to a server running as that account. It starts that server if needed. The login server is the most similar to SSH: one root service that lets each person in as their own account. Unlike SSH, it has no passwords or keys. It identifies people only by how they connect, such as the local account on the other end of its socket or their Tailscale login. Anyone who can reach it that way can use the accounts listed for them. Each account's server keeps running when the login server stops, restarts, or is upgraded, so sessions survive all three. After an upgrade, each one keeps the old version until its sessions end. The accounts file ships empty, which lets nobody in. Add at least one account, then start the service now and at every boot: On Alpine, use the OpenRC commands instead. On FreeBSD, use the FreeBSD commands, and edit /usr/local/etc/rex/accounts.jsonc. To start the login server only when someone first connects, enable rex-login-server.socket instead of rex-login-server.service. See Starting on Demand. After you change the accounts file, reload it. This doesn't drop any connections: On Alpine, run rc-service rex-login-server reload. On FreeBSD, run service rex-login-server reload. Read Login Server before you add accounts. It covers the accounts file, how people choose an account when they connect, and the PAM policy the login server checks accounts against."},{"href":"/linux/server-modes#switching-between-the-services","page":"Server Modes","heading":"Switching Between the Services","text":"Starting either server stops the other, but disable the old server's service and socket too, so that neither comes back at the next boot: To start the new server on demand, enable its socket instead, such as rex-login-server.socket. To switch back, swap the names: disable rex-login-server.service and rex-login-server.socket, then enable rex.service or rex.socket. On Alpine, starting either service fails while the other is running, so stop the old one first: On FreeBSD, the same applies. Stop and disable the old one first: Switching from the shared server ends every session on it. Switching from the login server leaves each account's server running. On machines without systemd-logind, or with a login server run with --pam=false, those servers keep their sockets in /run/rex/accounts, and rex.service and rex.socket refuse to start while that directory exists. Once nobody needs those sessions, clear the directory and start the shared server: To start the shared server on demand, start rex.socket instead. A refused start can still stop the login server's units, so if you back out, start the login server's service or socket again. Alpine has no systemd-logind, so the account servers always keep their sockets in /run/rex/accounts there. Clear the directory the same way, then run rc-service rex start. FreeBSD has no systemd-logind either, and keeps /var/run across reboots, so there the rex service refuses to start only while /var/run/rex/accounts holds a socket. Clear it the same way with rm -rf /var/run/rex, then run service rex start."},{"href":"/linux/shared-server","page":"Shared Server","heading":"","text":"The shared server is one Rex server that runs as the rex system account, as rex.service. Members of the rex group can connect to it, and every program they start runs as rex. It's the simplest server mode for a few people who are comfortable sharing one account. Shared servers are only on Linux and FreeBSD. On Windows, see Running on Windows. **If you're the only one using Rex on the machine, you don't need the shared server.** Start with the quickstart for Linux or FreeBSD instead, which sets up a personal server that you reach over SSH. Access to the shared server is access to the rex account. Anyone in the group can see and type into every session on it and start programs as rex. Only add people you would trust with that account. If people need their own files and permissions, use the login server instead."},{"href":"/linux/shared-server#starting-the-server","page":"Shared Server","heading":"Starting the Server","text":"The package creates the rex account with nologin as its shell, and Rex starts each terminal with its account's shell. Give the account a real shell first, or every terminal on the shared server exits as soon as it starts: Alpine has neither usermod nor bash by default. Install usermod with the shadow package and use /bin/sh: FreeBSD has no bash by default either. Use pw and /bin/sh there: The account stays locked, with no password, so nobody can log in as rex this way. Then start the service now and at every boot: On Alpine, run rc-update add rex, then rc-service rex start. On FreeBSD, run sysrc rex_enable=YES, then service rex start. To start the server only when someone first connects, enable rex.socket instead. See Starting on Demand. The server listens on /run/rex/server.sock and keeps its data in /var/lib/rex, the rex account's home directory. On FreeBSD, those are /var/run/rex/server.sock and /var/db/rex."},{"href":"/linux/shared-server#starting-on-demand","page":"Shared Server","heading":"Starting on Demand","text":"Rex can be paired with systemd to automatically start the shared server when a client connections, instead of at boot using rex.socket. When this is enabled, systemd creates /run/rex/server.sock and waits. When a client connects, systemd starts rex.service with the same command line and hands it the socket. The client waits a moment while the server starts, then works as usual. Access is the same: members of the rex group can use the socket. Enable the socket instead of the service: Enable one or the other, not both. The socket starts the service when it needs to. systemctl status rex.socket shows \"active (listening)\" until the first client connects. Once it starts, the server keeps running, even when nobody is using it. The socket changes when the server starts, not how long it runs. While the socket is active, any connection starts the server, including rex server status. To check on it without starting it, ask systemd: Stopping rex.service alone lasts only until the next connection. To stop the server and keep it stopped, stop both units in one command. This ends every session on it: Stopping only rex.socket leaves the server running without its socket file, so nobody new can connect to it. Don't use rex server stop either. It connects to the socket to check that the server stopped, which can start it again. To switch to a server that always runs, disable both units, then enable the service: To switch back, stop the service first. systemd won't listen on the socket while the server is running: Either switch ends every session on a running shared server. Alpine and FreeBSD have no socket activation, so there the service is the only way to run the shared server. On NixOS, set services.rex.socketActivation. See The NixOS Module."},{"href":"/linux/shared-server#giving-people-access","page":"Shared Server","heading":"Giving People Access","text":"Add each person to the rex group. The change applies the next time they log in: On Alpine, run addgroup alice rex. On FreeBSD, run pw groupmod rex -m alice. Their rex commands look for their own [personal server](/linux/server-modes#personal-server) by default, and start one if it isn't running. To use the shared server instead, pass its socket with -S, or set REX_SERVER in their shell profile so every rex command uses it: On FreeBSD, the socket is /var/run/rex/server.sock. Inside the shared server's own terminals, rex commands already use the shared server. A rex command never starts a personal server at the shared server's socket. If the shared server isn't running, the command fails, and on Linux the error names the service to start. With rex.socket enabled, the connection starts the shared server instead. People outside the rex group get a permission error, because only the group can open /run/rex. The service isn't sandboxed on purpose. Sandboxing would also restrict the programs people run in it, so the rex account is the security boundary. To reach the shared server from another computer, see Remote Access."},{"href":"/linux/shared-server#configuration","page":"Shared Server","heading":"Configuration","text":"The shared server reads its configuration file from the rex account's home: On FreeBSD, it's /var/db/rex/.config/rex/init.lua. Only root can edit it, because the home directory is private to the rex account. After you change it, any member of the group can reload it: To keep the file somewhere else, pass --config PATH in a drop-in, as below."},{"href":"/linux/shared-server#customizing-the-service","page":"Shared Server","heading":"Customizing the Service","text":"Package upgrades replace rex.service, so put changes in a drop-in file instead. This command creates one and opens it in your editor: To change the server's options, clear ExecStart and set it again. This example raises the session limit: On Alpine, upgrades replace /etc/init.d/rex, so set command_args in /etc/conf.d/rex instead. The package never creates or changes that file. The same example there leaves out /usr/bin/rex: Keep the four options from the original command line. The same pattern adds a network listener for Remote Access or a block plugin directory with --block-dir. Run rex server run --help for every option. With rex.socket, the same drop-in applies, because the socket starts rex.service. The socket's path and mode come from the socket unit, so to change them, edit both units. See Customizing the Socket. On FreeBSD, upgrades replace /usr/local/etc/rc.d/rex but never change /etc/rc.conf, so set rex_flags there instead. It adds options to the service's command line rather than replacing it, so leave out the four it already has: Settings lists the service's other settings, such as rex_umask."},{"href":"/linux/shared-server#customizing-the-socket","page":"Shared Server","heading":"Customizing the Socket","text":"With rex.socket, systemd creates the server's socket and hands it to the server, which serves it instead of creating its own. Package upgrades replace rex.socket too, so put changes in a drop-in for it: The server serves only the sockets its command line names. Every ListenStream= path in the socket unit needs a matching --listen or --extra-listen in rex.service. Otherwise the server refuses to start, and clients wait until they time out. To move the socket to /run/rex/rex.sock, stop both units first. systemd removes the socket file it knows about when the unit stops, so stopping after the edit would leave the old file behind: Then run sudo systemctl edit rex.socket and set the new path. The empty ListenStream= clears the one in the unit: Run sudo systemctl edit rex.service and set the same path: Then start the socket again: Clients then connect with rex -S /run/rex/rex.sock. To serve a second socket, add another ListenStream= line to the socket unit and a matching --extra-listen unix://PATH to the service. A socket for Tailscale Serve needs tailnet-serve:// instead, as Tailscale Serve shows. The server keeps the mode that SocketMode= gives the socket, and ignores --socket-mode for it. It refuses a mode that lets every user on the machine connect, such as 0666, so keep it at 0660 or narrower. 0600 works, but locks out the rex group. The server takes only listening stream sockets, from ListenStream=, and refuses Accept=yes. A TCP ListenStream= needs --extra-listen with the same port and --allow-anonymous, as Plain HTTP describes. Give it an IP address, not a host name. To apply any later change to rex.socket, stop both units and start the socket again, as above. sudo systemctl restart rex.socket fails while the server is running, and leaves the server without its socket file."},{"href":"/linux/shared-server#logs","page":"Shared Server","heading":"Logs","text":"The shared server logs to the journal: Alpine has no journal, so there the server logs to a file: On FreeBSD, the server logs to syslog, which writes to /var/log/messages: The logs are plain text. For JSON, change --log-format text to --log-format json in a drop-in. With rex.socket, the server still logs as rex.service. If it refuses to start, such as for a socket it isn't configured to serve, the journal names the socket and the option to add. Clients aren't refused meanwhile. Each one waits until its --timeout runs out, 10 seconds by default. rex server logs shows only servers started with rex server start, not the service. Use the journal instead."},{"href":"/linux/shared-server#upgrading","page":"Shared Server","heading":"Upgrading","text":"Upgrading the package restarts a running shared server, so it runs the new version right away. On FreeBSD, pkg restarts nothing, so the server keeps the old version until you run service rex restart. With rex.socket, a running server restarts the same way, and one that hasn't started yet runs the new version when the first client connects. Upgrades never restart rex.socket itself. If a new version changes the socket unit, the change applies once you stop both units and start the socket again, or at the next reboot. Restarting ends every session and every program in them. Upgrade when nobody has work running, or hold the package until then. The login server doesn't have this problem. Its sessions survive upgrades. Upgrading covers the rest."},{"href":"/linux/login-server","page":"Login Server","heading":"","text":"The login server lets several people use Rex on one machine, each as their own operating system account, with their own files and permissions. It's the server mode to use when people shouldn't share an account. Login servers are only on Linux and FreeBSD. On Windows, see Running on Windows. **If you're the only one using Rex on the machine, you don't need the login server.** Start with the quickstart for Linux or FreeBSD instead, which sets up a personal server that you reach over SSH. The login server has no passwords or keys. It identifies people only by how they connect, such as their Tailscale login. Anyone who can reach it that way can use every account the accounts file lists for them. This is only a short-term limitation, we have a full authentication and authorization system planned (including supporting legacy SSH keys), but until then we recommend using something like Tailscale."},{"href":"/linux/login-server#how-it-works","page":"Login Server","heading":"How It Works","text":"The login server runs as root, as rex-login-server.service. It can also start when the first client connects, through rex-login-server.socket. See Starting on Demand. When a client connects: The login server identifies the caller, such as by their Tailscale login or their local user ID. It checks /etc/rex/accounts.jsonc for the accounts that caller may use, and picks one. It opens a login session for that account through PAM, the same as a console or SSH login, and starts a Rex server as that account. If one is already running, it uses that one instead. It hands the connection to that server. Each account's server keeps running when the login server stops, restarts, or is upgraded, so sessions survive all three. After an upgrade, each one keeps the old version until its sessions end. See What Restarts. On machines with systemd-logind, an account's server is the same as that person's personal server. Sessions they start with rex on the machine and sessions they start from another computer are on the same server."},{"href":"/linux/login-server#accounts-file","page":"Login Server","heading":"Accounts File","text":"/etc/rex/accounts.jsonc lists the accounts people may use and who may use each one. It's JSON, with comments and trailing commas allowed. The file ships empty, which lets nobody in by default. This example lets Alice use her own account and root. She connects as alice unless she asks for root: Under accounts, each key is an account name, and principals lists the callers allowed to use it. A caller may use an account if any of its principals match: Principal Matches tailscale:alice@example.com A person, by their Tailscale login tailscale:tag:ci A device with the Tailscale tag tag:ci tailscale:* Any person on the tailnet tailscale:tag:* Any tagged device peer:1000 A local user, by user ID identified Any caller Rex can identify anonymous Callers Rex can't identify, such as on a plain TCP listener A tagged device never matches a person's login or tailscale:*."},{"href":"/linux/login-server#any-account","page":"Login Server","heading":"Any Account","text":"The special account \"*\" lets its callers use any regular account, meaning any account with a user ID of 1000 or higher, whether or not the file names it. Root and system accounts must always be listed by name. For safety, anonymous can't appear under \"*\", and anonymous callers can never use root."},{"href":"/linux/login-server#defaults","page":"Login Server","heading":"Defaults","text":"When a client doesn't ask for a specific account, defaults picks one. A default only chooses an account. The caller must still be allowed to use it. If several defaults match a caller, the most specific one wins: a login or user ID first, then a tag, then a Tailscale wildcard, then identified, then anonymous. Between equally specific matches, the first in the file wins. With no matching default, a caller allowed exactly one account connects as that account. Otherwise, the client must name the account it wants."},{"href":"/linux/login-server#starting-and-reloading","page":"Login Server","heading":"Starting and Reloading","text":"Add at least one account so a user can access, then start the service: This will enable it to start now and for every reboot. On Alpine, use the OpenRC commands instead, and on FreeBSD, the FreeBSD commands. Reload the server anytime you change the accounts file: On Alpine, run rc-service rex-login-server reload. On FreeBSD, run service rex-login-server reload. Reloading doesn't drop connections that are still allowed. It closes connections whose access you removed, but leaves every account's server and its sessions running. The journal shows account policy reloaded, or account policy reload failed; previous policy retained if the new file has an error. Package upgrades keep your changes to the accounts file. The login server's logs are in the journal: Alpine has no journal, so there the logs are in /var/log/rex-login-server.log. On FreeBSD, they're in /var/log/messages. See FreeBSD. The login server listens only on /run/rex/login-server.sock, which only root can use. To let people connect from other computers, see Remote Access."},{"href":"/linux/login-server#starting-on-demand","page":"Login Server","heading":"Starting on Demand","text":"Rex can be paired with systemd to automatically start the login server when a client connections, instead of at boot using rex-login-server.socket. When this is enabled, systemd creates /run/rex/login-server.sock and waits. When a client connects, systemd starts rex-login-server.service and hands it the socket. The client waits a moment while the login server starts, then works as usual. Only root can use the socket, the same as when the login server creates it. Add at least one account, then enable the socket instead of the service: Enable one or the other, not both. The socket starts the service when it needs to. systemctl status rex-login-server.socket shows \"active (listening)\" until the first client connects. While the socket is active, any connection starts the login server, including rex server status. To check on it without starting it, run systemctl status rex-login-server.service rex-login-server.socket. Reload the accounts file the same way as above. Before any client has connected, the login server isn't running, so the reload fails. There's nothing to do then: the login server reads the new file when it starts. If the accounts file is missing or invalid, the login server exits with loading accounts file: and the reason, and starts again 5 seconds later. Clients aren't refused meanwhile. Each one waits until its --timeout runs out, 10 seconds by default. The journal shows why. Stopping rex-login-server.service alone lasts only until the next connection. To stop the login server and keep it stopped, stop both units in one command: Stopping only the socket leaves the login server running without its socket file, so nobody new can connect to it. Either way, each account's server and its sessions keep running. Clients reach them again once the login server is back. To switch to a login server that always runs, disable both units, then enable the service: To switch back, stop the service first. systemd won't listen on the socket while the login server is running: To change the socket, run sudo systemctl edit rex-login-server.socket. Every ListenStream= in it needs a matching option on the login server's command line. It works the same way as for the shared server. See Customizing the Socket. To have a request through Tailscale Serve start the login server too, see Tailscale Serve. Alpine and FreeBSD have no socket activation. On NixOS, set services.rex.loginServer.socketActivation. See NixOS."},{"href":"/linux/login-server#choosing-an-account","page":"Login Server","heading":"Choosing an Account","text":"To ask for a specific account, put it before the host in the server's address: The same address works in the Mac app's Add Remote Host…. Without an account in the address, the caller's default applies. Accounts can only be chosen in an https:// or http:// address, so the login server needs a network listener first. Remote Access covers setting one up and connecting to it as each account."},{"href":"/linux/login-server#pam","page":"Login Server","heading":"PAM","text":"The login server uses PAM each time it starts a server for an account. PAM decides whether the account may log in at all, such as whether it's locked or expired, and sets up the login session: resource limits, environment variables, and a systemd-logind session on machines that have one. Rex has already identified the caller before PAM runs, so its policy never asks for a password, the same as SSH with keys. While /etc/nologin exists, only root may log in. Accounts whose shell is nologin or false are always refused. The package installs a policy at /etc/pam.d/rex-login-server that fits your distribution: Debian and Ubuntu, Red Hat and its relatives, SUSE, or Alpine. Upgrades keep your changes to it. FreeBSD is different. See FreeBSD. To skip PAM, run sudo systemctl edit rex-login-server.service and replace the service's command line with one that adds --pam=false. Rex then checks /etc/shadow and /etc/nologin itself and opens no login session. On Alpine, set command_args in /etc/conf.d/rex-login-server instead, to the service's command line with --pam=false added:"},{"href":"/linux/login-server#nixos","page":"Login Server","heading":"NixOS","text":"The NixOS module writes the accounts file from services.rex.loginServer.accounts and services.rex.loginServer.defaults. It doesn't create the accounts, so declare them with users.users: To keep the order of defaults, which decides ties, use services.rex.loginServer.accountsFile to load your own file instead. To start the login server only when the first client connects, set services.rex.loginServer.socketActivation = true;. To stop it and keep it stopped, stop rex-login-server.socket and rex-login-server.service together. See The NixOS Module."},{"href":"/linux/login-server#freebsd","page":"Login Server","heading":"FreeBSD","text":"On FreeBSD, the accounts file is /usr/local/etc/rex/accounts.jsonc, and the FreeBSD commands start and reload the service. The login server logs to syslog, in /var/log/messages. Each account's server logs to /var/run/rex/accounts/<account>/server.log instead, and can be read by the person using that account, too. The package installs its PAM policy at /usr/local/etc/pam.d/rex-login-server, and every upgrade replaces it. To change the rules, copy it to /etc/pam.d and edit the copy. OpenPAM reads /etc/pam.d first, and upgrades never touch it: OpenPAM ignores a policy that root doesn't own, so keep the copy owned by root. The policy checks accounts the same way sshd does. An account locked with pw lock is refused, /etc/login.access applies, and while /var/run/nologin exists, only root may log in. Rex applies each account's login class from /etc/login.conf to its server, such as its resource limits. To skip PAM, add --pam=false to rex_login_server_flags, then restart the service: Rex then checks /etc/master.passwd and /var/run/nologin itself."},{"href":"/linux/remote-access","page":"Remote Access","heading":"","text":"This page documents how you access your servers remotely once you have Rex Server installed and running. On Linux or FreeBSD, Rex Server listens only on Unix sockets on that machine by default. On Windows, it listens only on a named pipe that only the account owner can open. To connect to your server from a client on another machine, you must specify a network listener or access it over SSH. This page covers each option and the details of it. Rex trusts a caller based only on how they connect today, so it can't check who someone is unless the network tells it. That's why servers listen only locally by default and why every listener below is cautious about who it lets in. This is a short-term limitation. A full authentication and authorization system is planned, including support for existing SSH keys. Until then, we recommend SSH. Most machines already run it, it uses the keys you already have, and the server sees your SSH login, which is how Rex decides who gets in. Nothing listens on the network but sshd. Listener Needs Works with Identifies callers SSH SSH access to the machine Personal and shared server Yes Built-in Tailscale A Tailscale account Personal and shared server Yes Tailscale Serve Tailscale on the machine Shared and login server Yes Tailnet address Tailscale on the machine Shared and login server Yes Plain HTTP --allow-anonymous Every mode No Another VPN --allow-anonymous Every mode No"},{"href":"/linux/remote-access#ssh","page":"Remote Access","heading":"SSH","text":"SSH is installed on almost every network-connected machine already, and you can connect to Rex using your existing SSH credentials. This is the recommended starting path for accessing Rex remotely. You can use an ssh:// address to connect to a server via SSH. Rex runs your own system ssh if available, so it uses your own keys, agents, config, etc. The address form is ssh://[USER@]HOST[:PORT][/SOCKET]. Without a socket, Rex finds the SSH user's personal server or starts it if none is running. If REX_AUTOSTART=0 is set on the machine, Rex will not autostart your server over SSH. On Linux, your personal server's socket goes away when you log out which will kill all your running sessions.* Enable linger with loginctl enable-linger to prevent this. For SSH to work, rex must be on the PATH for SSH connections, or in ~/.local/bin which we check explicitly since that's where our unprivileged user install goes."},{"href":"/linux/remote-access#built-in-tailscale","page":"Remote Access","heading":"Built-in Tailscale","text":"With --tailscale, the server joins your tailnet as its own device, using Tailscale built into Rex. The machine doesn't need Tailscale installed. The first time, the server needs to sign in to Tailscale. Its sign-in link appears in rex server status and the server's log. To sign in without a browser, pass a one-time auth key with --tailscale-auth-key-file FILE, or - to read it from standard input. The server keeps its login, so it needs the key only once. The device is named rex unless you choose another name with --tailscale-hostname. Its address is https://rex.<tailnet>.ts.net. Your tailnet must have MagicDNS and HTTPS certificates turned on. To sign it in as a tagged device, add --tailscale-advertise-tag tag:NAME. A tagged device has no owner, so this also needs --tailscale-allow-tailnet-members. See Who Can Connect. On Windows, you must specify these options to rex server install after --. A Windows Service has no standard input, so give the auth key as a file:"},{"href":"/linux/remote-access#tailscale-serve","page":"Remote Access","heading":"Tailscale Serve","text":"If the machine already runs Tailscale, Tailscale Serve can forward HTTPS connections to a socket that Rex listens on. This is the usual way to reach the shared server or the login server. Run sudo systemctl edit rex.service (or rex-login-server.service) and add the socket to its command line. Package upgrades keep this file. For the shared server: For the login server, the command line is: On Alpine, set command_args in /etc/conf.d/rex (or /etc/conf.d/rex-login-server) to the same command line, without /usr/bin/rex. rc_need takes the place of After= and Requires=. For the shared server: On FreeBSD, add the socket to rex_flags (or rex_login_server_flags), then restart the service. Its socket is in /var/run/rex: Then point Tailscale Serve at the socket: On FreeBSD, that's unix:/var/run/rex/serve.sock. The address is the machine's own tailnet name, such as https://build.<tailnet>.ts.net. Tailscale Serve doesn't tell Rex who a tagged device is, so tagged devices such as CI runners can't connect this way. Use a tailnet address for them instead. Use tailnet-serve:// for the Serve socket, never unix://. With unix://, the server starts without an error, but treats every tailnet caller as the account that runs tailscaled, usually root."},{"href":"/linux/remote-access#socket-activated-servers","page":"Remote Access","heading":"Socket-Activated Servers","text":"With rex.socket or rex-login-server.socket, the drop-in above works unchanged, but the server creates the Serve socket only once it's running, so a request through Tailscale Serve can't start it. To let it, add the Serve socket to the socket unit too. Run sudo systemctl edit rex.socket (or rex-login-server.socket) and add: Keep the --extra-listen option in the service's drop-in. Then stop both units and start the socket again, so that systemd creates the new socket: For the login server, that's rex-login-server.socket and rex-login-server.service. The Serve socket gets the socket unit's mode, and Tailscale Serve still reaches it, because tailscaled normally runs as root. See Customizing the Socket."},{"href":"/linux/remote-access#tailnet-address","page":"Remote Access","heading":"Tailnet Address","text":"A server can also listen directly on the machine's tailnet addresses, using the machine's own Tailscale to identify callers. Add this to the server's command line, the same way as the Serve socket above: The port is required. The connection is plain HTTP, encrypted by Tailscale, at http://build.<tailnet>.ts.net:7297."},{"href":"/linux/remote-access#who-can-connect","page":"Remote Access","heading":"Who Can Connect","text":"A connection to a server can do anything that server can, including starting programs as its account. Control access with care. For a personal server or the shared server, only the Tailscale user who owns the machine (or the Rex device, with built-in Tailscale) is let in by default. To let in other people: Listener Option Lets in Built-in Tailscale --tailscale-allow-tailnet-members Anyone on the tailnet your ACLs allow Serve or address --tailnet-allow-identified Anyone Tailscale can identify A tagged machine or device has no owner, so it needs one of these options to let anyone in. The login server is different. It lets in everyone Tailscale can identify, because its accounts file decides which accounts each person may use. To see who's connected to a server, run rex client ls on it. Its principal column shows the identity of each client."},{"href":"/linux/remote-access#plain-http","page":"Remote Access","heading":"Plain HTTP","text":"A plain HTTP listener can't tell callers apart, so Rex refuses one unless you also pass --allow-anonymous: Every connection then has full access. Only do this on a network you completely control. The login server lets such callers use only accounts whose principals include anonymous, and never root. Again, on an open network this is super dangerous and you probably shouldn't do it. This option exists so that you can pair it with a network that limits who can reach the server, such as your own VPN."},{"href":"/linux/remote-access#vpns","page":"Remote Access","heading":"VPNs","text":"You can use any network device or custom VPN you want, such as WireGuard, ZeroTier, NetBird, etc. To do this, listen directly on the proper network device: Use the VPN address itself. An address without a host, such as :7297, listens on every address the machine has, including those on its other networks. Everyone who can reach that address has full access. Rex can't tell callers apart, so each one can do anything the server can, including starting programs as its account. On a private VPN, that means everyone on the VPN and every account on this machine. If your VPN has access rules, use them to limit which devices can reach this port. The login server limits these callers as described in Plain HTTP. The listener depends on the VPN being up. A server that can't listen on an address doesn't start at all, not even on its Unix socket. So put these options on the service's command line, as in Tailscale Serve, rather than relying on rex server start, which doesn't save its options or change a server that's already running. systemd and FreeBSD try again every 5 seconds, so the server comes up soon after the VPN connects. To connect, add http://10.0.0.5:7297 as a host on a device that's on the VPN. Some devices run only one VPN at a time, so turning on another one, including Tailscale, disconnects Rex."},{"href":"/linux/remote-access#adding-the-server-to-a-client","page":"Remote Access","heading":"Adding the Server to a Client","text":"In Rex for Mac, choose Add Remote Host… and enter the server's address. On the command line, pass the address with -S, or save it as a host and use its label: A bare host name, such as build.example.ts.net, tries HTTPS on port 443, then HTTPS on port 7297, then HTTP on port 7297. To connect to the login server as a specific account, put it before the host, such as https://root@build.example.ts.net. See Choosing an Account. Note: rex hosts today is CLI-only. It doesn't sync to your GUI client or across machines. This will change in the future."},{"href":"/linux/remote-access#nixos","page":"Remote Access","heading":"NixOS","text":"The NixOS module has options for each listener: Option Sets up services.rex.tailscale.enable Built-in Tailscale, with hostname, authKeyFile, and advertiseTags services.rex.tailscaleServe.enable Tailscale Serve for the shared server services.rex.loginServer.tailscaleServe.enable Tailscale Serve for the login server services.rex.allowAnonymous Plain HTTP for the shared server. openFirewall opens its port. services.rex.loginServer.allowAnonymous Plain HTTP listeners for the login server With socketActivation on, the module's socket unit also holds the Serve socket, so a request through Tailscale Serve starts the server."},{"href":"/linux/remote-access#windows","page":"Remote Access","heading":"Windows","text":"On Windows, each listener works as follows: Listener On Windows SSH Works, with Microsoft's OpenSSH Server. See SSH with Windows Built-in Tailscale Works, with --tailscale Tailscale Serve Refused, and so is login-server:// Tailnet address Works through tailscaled with its normal network adapter. Refused with a userspace tailscaled Plain HTTP Works, with --extra-listen and --allow-anonymous Another VPN Works, with --extra-listen and --allow-anonymous To keep listeners each time the server starts, give them to rex server install after --, with or without --user: rex server start takes the same options, but they last only until the server stops. See Server Flags."},{"href":"/linux/remote-access#ssh-with-windows","page":"Remote Access","heading":"SSH with Windows","text":"SSH needs Microsoft's OpenSSH Server installed and configured. From a terminal with Run as administrator: Its firewall rule lets SSH in only on networks set to Private. For an administrator account, sshd reads keys from C:\\ProgramData\\ssh\\administrators_authorized_keys, not from ~\\.ssh\\authorized_keys."},{"href":"/linux/remote-access#the-firewall","page":"Remote Access","heading":"The Firewall","text":"The first time a server that you started listens on a TCP port, Windows Security asks \"Do you want to allow public and private networks to access this app?\" for Rex Server. Choose Allow. Cancel leaves the port blocked. Windows adds Block rules named \"Rex Server\" as soon as the prompt appears, and Cancel keeps them. Later starts don't ask again, and connections time out. To undo it, go to **Windows Security > Firewall & network protection > Allow an app through firewall** and allow Rex Server, or delete the rules from a terminal started with Run as administrator: The Windows Service requires manual setup. Nobody may be signed in to see a prompt, so Windows blocks its port without one, and adds no rules. Allow rex.exe ahead of time from a terminal started with Run as administrator: For the WinGet zip, use the path that (Get-Command rex).Source prints. A Block rule for the same path wins over this one, so delete those first. Each rule names one path of rex.exe. Switching to another WinGet channel, or between the WinGet zip and the MSI, moves rex.exe, so you need to allow it again. An MSI upgrade keeps its folder, and its rules."},{"href":"/linux/upgrading","page":"Upgrading","heading":"","text":"You can upgrade Rex using your standard package manager that you installed with. If you used the install.sh script, running that again is also safe; it merely detects and invokes your package manager. On Linux, install.sh and standard package managers auto-restart the running server. On FreeBSD and Windows, nothing restarts automatically; you must manually restart the server into the latest version. See FreeBSD and Windows. Upgrading ends every session on a running shared server. The login server and personal servers keep their sessions. See What Restarts."},{"href":"/linux/upgrading#upgrading-the-package","page":"Upgrading","heading":"Upgrading the Package","text":"Installed with Upgrade Debian, Ubuntu sudo apt-get update && sudo apt-get install --only-upgrade rex Fedora, RHEL sudo dnf upgrade --refresh rex openSUSE sudo zypper update rex Arch Linux sudo pacman -Syu Omarchy omarchy update Alpine apk update && apk add -u rex FreeBSD pkg upgrade -r rex rex Nix profile nix profile upgrade --regex '.*superlogical.*' Install script Run the same command you installed with. --tarball Run the install script again with --tarball. Amazon Linux 2 Run the install script again. Windows See Windows. When you run the install script again, pass the same --channel you installed with. Without it, the script switches the repository to the default channel."},{"href":"/linux/upgrading#what-restarts","page":"Upgrading","heading":"What Restarts","text":"The package restarts the Rex services that are running, and leaves stopped ones stopped. In containers without a running init system, it restarts nothing, and neither does the FreeBSD package. See FreeBSD. The package never restarts rex.socket or rex-login-server.socket. A server that a socket started restarts like any other, and one that hasn't started yet runs the new version when the first client connects. If a new version changes a socket unit, the change applies once you stop the socket and its service and start the socket again, or at the next reboot. Server On upgrade Sessions Runs the new version Shared server Restarts End Right away Login server Restarts Survive Right away Account servers Keep running Survive When their sessions end Personal server Keeps running Survive When it stops **Restarting the shared server ends every session and every program in them.** Upgrade when nobody has work running, or hold the package until then. The servers the login server starts for each account keep running the version they started with. Each one stops around 10 seconds after its last session ends and its last client disconnects, and the next connection starts it again on the new version. On machines with systemd-logind, a person can switch sooner by running rex server stop on the machine as themselves, which ends their sessions. A personal server that a rex command started stops on its own once it's idle, so it picks up the new version by itself. One started with rex server start keeps running the old version until you stop it and start it again:"},{"href":"/linux/upgrading#checking-the-version","page":"Upgrading","heading":"Checking the Version","text":"rex version shows the version that's installed, and rex server status shows the version of the server that's running: If the first line of rex version doesn't match the version: line of rex server status, that server is still running the old version. Through the login server, rex server status shows the version of your account's server."},{"href":"/linux/upgrading#holding-an-upgrade","page":"Upgrading","heading":"Holding an Upgrade","text":"To keep Rex at its current version until you're ready: Package manager Hold Release apt sudo apt-mark hold rex sudo apt-mark unhold rex dnf sudo dnf versionlock add rex sudo dnf versionlock delete rex zypper sudo zypper addlock rex sudo zypper removelock rex pacman Add IgnorePkg = rex under [options] in /etc/pacman.conf Remove that line apk apk add rex=VERSION apk add -u rex pkg pkg lock rex pkg unlock rex On RHEL and its rebuilds, versionlock is a plugin. Install it with sudo dnf install 'dnf-command(versionlock)'. Fedora has it built in. On Alpine, VERSION is the full version, such as 0.1.0-r0. apk info -e -v rex shows the one that's installed."},{"href":"/linux/upgrading#keeping-your-changes","page":"Upgrading","heading":"Keeping Your Changes","text":"Upgrades keep your changes to: The accounts file, /etc/rex/accounts.jsonc The PAM policy, /etc/pam.d/rex-login-server Drop-ins for rex.service, rex-login-server.service, and their sockets /etc/conf.d/rex and /etc/conf.d/rex-login-server on Alpine /etc/rc.conf on FreeBSD Upgrades replace the services themselves, rex.service, rex-login-server.service, rex.socket, rex-login-server.socket, the scripts in /etc/init.d, and the scripts in /usr/local/etc/rc.d on FreeBSD. On FreeBSD, they replace the PAM policy too. See FreeBSD. Put your changes in a drop-in or /etc/conf.d instead, as Customizing the Service shows. If you changed a config file and a new version of Rex changes it too, you keep yours, and the new one is saved beside it: Package manager New file saved as Notes apt .dpkg-dist apt asks first. The install script doesn't ask. dnf, zypper .rpmnew apk .apk-new pkg .sample Only the accounts file. pkg updates the sample on every upgrade. Compare the two and copy over any changes you want. The SSH fallback, /etc/ssh/ssh_config.d/50-rex.conf, is the exception on dnf and zypper: an upgrade replaces it and saves yours as .rpmsave."},{"href":"/linux/upgrading#client-and-server-versions","page":"Upgrading","heading":"Client and Server Versions","text":"Rex for Mac and the server don't need to be the same version, and either one can be newer. A newer client can't use features an older server doesn't have. When it needs one, it says so: If the two are too far apart to work together at all, the server refuses the connection, and the message ends with update the server or update the client."},{"href":"/linux/upgrading#switching-channels-and-downgrading","page":"Upgrading","heading":"Switching Channels and Downgrading","text":"Switching channels from snapshot to preview or stable upgrades Rex as usual. Switching back to snapshot is a downgrade, because snapshot versions sort below every release. A downgrade doesn't happen on its own, so ask for the version you want: Package manager List versions Downgrade apt apt-cache madison rex sudo apt-get install --allow-downgrades rex=VERSION dnf dnf list --showduplicates rex sudo dnf downgrade rex-VERSION zypper zypper search -s -x rex sudo zypper install --oldpackage rex-VERSION pacman One version only. See Arch Linux. sudo pacman -U URL apk apk search -a -x rex apk add rex=VERSION pkg pkg search -r rex rex See FreeBSD. A downgrade restarts services the same way an upgrade does. Your data is safe across a downgrade. A version of Rex that finds data written in a newer format refuses to use it instead of overwriting it."},{"href":"/linux/upgrading#changing-how-rex-is-installed","page":"Upgrading","heading":"Changing How Rex Is Installed","text":"Keep one copy of Rex on a machine. The --tarball binary in /usr/local/bin comes before the package's /usr/bin/rex on the usual PATH, so with both installed, rex commands and the personal servers they start run the tarball's version, whichever one you upgrade. To see every rex on your PATH: To move from --tarball to a package, install the package, then remove the binary: To move from a package to --tarball or Nix, remove the package first: Package manager Remove apt sudo apt-get remove rex dnf sudo dnf remove rex zypper sudo zypper remove rex pacman sudo pacman -R rex apk apk del rex pkg pkg delete rex Removing the package stops and disables its services and sockets, which ends every session on the shared server. rex-bin from the AUR doesn't, so stop and disable them yourself first. See Arch Linux. Removing the package keeps /var/lib/rex and the rex account, so installing the package again picks up the shared server's data. On FreeBSD, it keeps /var/db/rex, and your settings in /etc/rc.conf. The install script and the commands on the other install tabs set up the same repository, so you can switch between them. On Alpine, the script puts the repository in /etc/apk/repositories.d/rex.list, and the Alpine tab adds it to /etc/apk/repositories. Keep only one of them."},{"href":"/linux/upgrading#nixos","page":"Upgrading","heading":"NixOS","text":"Update the Rex input in your flake, then switch: nixos-rebuild switch restarts the Rex service when Rex changes, the same as a package upgrade: the shared server's sessions end, and account servers keep the old version until their sessions end. flake.lock holds Rex at its current version until you update it, so there's nothing else to do to hold an upgrade."},{"href":"/linux/upgrading#freebsd","page":"Upgrading","heading":"FreeBSD","text":"pkg can't restart services, so after an upgrade, the services that were running keep the old version. pkg says which ones at the end of its output, and so does the install script. Restart each one when you're ready: Restarting rex ends every session on the shared server, the same as on Linux. Restarting rex-login-server keeps each account's server and its sessions, and they keep the old version until their sessions end. Upgrades keep your changes to the accounts file, but replace the PAM policy in /usr/local/etc/pam.d. To change the policy, edit a copy in /etc/pam.d instead, as Login Server shows. pkg doesn't downgrade with pkg install, so delete the newer version first. Deleting stops both services, which ends every session on the shared server: VERSION is the full version, such as 0.0.1792508098.g07b896. pkg info rex shows the one that's installed."},{"href":"/linux/upgrading#windows","page":"Upgrading","heading":"Windows","text":"Upgrade Rex the same way you installed it: Installed with Upgrade WinGet winget upgrade --id Superlogical.RexServer.Preview --source rex Manual MSI Download the new MSI and run it. Manual Zip Stop the server, then extract the new zip over the old folder. Stop the server before you upgrade with WinGet. WinGet can't replace the files of a running server, so the upgrade fails with \"Access is denied\" and Rex stays at the old version. In all cases, nothing automatically restarts. How you restart your server depends on how it was started, as Server Lifetimes explains: Started by To run the new version Any rex command Nothing. It stops once it's idle, and the next command starts the new version. rex server start rex server stop, then rex server start rex server install --user rex server stop, then rex server install --user, or sign in again rex server install rex server stop, then sc.exe start RexServer-<SID> sc.exe start needs a terminal opened with Run as administrator. See Stopping and Restarting. Warning! Restarting the server ends every program running in its sessions. The server saves its session layouts but the programs that were running in them don't come back today."},{"href":"/linux/upgrading#holding-an-upgrade-1","page":"Upgrading","heading":"Holding an Upgrade","text":"To keep WinGet from upgrading Rex until you're ready, pin it: To see your pins, and to remove this one: winget pin list shows nothing without --source rex, because the source is explicit. An MSI or zip you installed yourself is never upgraded until you install a new one."},{"href":"/beta/community-mcp","page":"Community MCP","heading":"","text":"Rex documentation, the changelog, and current and past discussions from the Rex Beta Discord are available to agents as an MCP server. An agent can use it to search documentation and past commentary and issues from the community, along with the Superlogical team's commentary and feedback. It can also read the release notes for every Rex version posted in #changelog. It's useful for learning about Rex, for seeing what's new or coming, and for finding the thinking behind decisions that is buried in archived threads. It is behind Discord OAuth, and you need to be a member of the Rex Beta Discord server."},{"href":"/beta/community-mcp#install","page":"Community MCP","heading":"Install","text":"Note: We recommend naming the server superlogical rather than rex, so it doesn't conflict with Rex skills, instructions, and the CLI."}]