Ship reads ~/.config/ship/config.toml, or ship/config.toml under $XDG_CONFIG_HOME. Without one, it uses its defaults. ship config defaults prints them all, with what each setting accepts:
ship config defaultsYour file only needs what you change. Copy just those lines rather than the whole output: anything you copy stays fixed, and stops following Ship's defaults when they change. --config <file> or SHIP_CONFIG points somewhere else.
Open windows pick up changes when you save. If the file has a mistake, open windows keep their last working settings and show the error. Run ship config check to list the findings and their locations.
Server settings
Section titled “Server settings”[server.defaults.pane]shell = "/bin/sh" # default: your login shellscrollback_bytes = 10_000_000 # bytes of history per paneright_click_passthrough = falseexit_behavior = "close" # "close", "keep" or "keep_failed"These are defaults for new panes, including a new tab's starter pane. Save the file and the next pane takes them; panes already open keep their values. A relative shell path resolves from the config file's folder. If the file can't be read or has errors, new panes use the built-in defaults and the server logs the error.
ship pane create --exit-behavior keep --scrollback-bytes 50000000 overrides the defaults for that pane. Panes explains the exit behaviors. --right-click-passthrough turns passthrough on, and --right-click-passthrough=false turns it off even when the default is on.
You can change an existing pane's history limit without restarting its program:
ship pane set --pane "$PANE" --scrollback-bytes 50000000Raising the limit keeps the history already there and lets it grow. Lowering it drops the oldest history beyond the new limit right away. The limit is in bytes, not lines.
Client settings
Section titled “Client settings”[client]sidebar_width = 40 # default: 26 columnsmouse = true # false leaves the mouse to your terminalTab rows
Section titled “Tab rows”With the git example plugin running, this puts each tab's branch and pull request under its name:
[client.tabs]rows = [["name"], [{ tag = "git.branch" }, { tag = "git.pr" }]] # default: [["name"]] ▲ ▾ ship main · ○ #112 ✓ ▲ api fix-login · ◆ #118 ● site main notesrows has one list for each line of a tab's entry. A piece on a line is name or a tag such as { tag = "git.branch" }, and the pieces on a line are joined with ·.
- A tag shows the tab's own value, or else its first pane's, the way a tab without a name takes its first pane's folder.
- A piece without a value is left out, and a line with nothing on it disappears. That's why
notesabove stays on one line. - A long piece is cut with
…, as in agent rows.
Updates
Section titled “Updates”Ship checks for new releases at startup and about once an hour, and shows what it finds in the sidebar and in the updates panel. These settings change that:
[client.updates]check = true # default; false turns automatic checks off# command = "brew upgrade ship" # override the build's update method# latest_command = "echo 9.9.9" # override where the latest version comes fromWith check = false Ship stops looking, but you can still ask:
ship update --check0.1.0 → 0.2.0It prints up to date when there's nothing newer, and no releases yet when there are none. It always asks for a fresh result, even with automatic checks off, and never starts a server.
command and latest_command are both unset by default, and each is a shell line: sh -c on Unix, cmd /C elsewhere.
commandis whatship updateruns. It comes ahead of the build-timeSHIP_UPDATE_COMMAND.latest_commandprints a version, instead of Ship asking GitHub for one. It comes ahead of the build-timeSHIP_LATEST_COMMAND. Without either, Ship uses the newest stable GitHub release, and release notes still come from GitHub when a command supplies the version.
Ship accepts versions such as 0.2.0, v0.2.0 and 0.2.0-1, and ignores a packager suffix when comparing them.
Agent settings
Section titled “Agent settings”[client.agents]rows = [["icon", "app", "state"], ["tab"]] # what each sidebar entry showsdelay_ms = 1000 # how long a state must hold before notifyingpopups = false # true also sends desktop notifications
[client.agents.sounds.default]done = { path = "~/sounds/ding.mp3" }attention = { builtin = "attention" }
[client.agents.sounds.apps.codex]done = "off"rows lists the lines of each entry. The pieces you can use are icon, app, state, tab, pane (the pane's label), and title and msg (whatever the agent reported). A tag on the agent's pane goes in as { tag = "git.branch" }. Anything without a value is left out, along with its space, and a line with nothing on it disappears. When a line is too wide for the sidebar, its pieces share the width and the long ones are cut with ….
A sound is "off", a built-in ({ builtin = "done" } or { builtin = "attention" }), or a file ({ path = "..." }) in MP3, WAV, Ogg Vorbis or FLAC. done plays when an agent finishes and attention when it's blocked or errors. [client.agents.sounds.apps.<name>] overrides sounds for one agent. If a sound file is missing or broken, Ship plays the built-in one instead and says so in the client log.
The built-in sounds are from Kenney's Interface Sounds pack (attribution).
Keys and modes
Section titled “Keys and modes”Keys live in modes, and you start in normal. Binding a key replaces only that key; everything else keeps its default. In normal mode, {} unbinds a key so it reaches the program instead:
[client.modes.normal.keys]"alt-q" = {} # alt-q goes to the program"alt-t" = { action = { server.tab.create = {} } } # new empty tab"ctrl-b" = { action = { client.mode = "prefix" } } # tmux habits
[client.modes.prefix]kind = "oneshot" # back to normal after one key
[client.modes.prefix.keys]"c" = { action = { server.tab.create = { starter = "shell", select = true } } }"%" = { action = { server.pane.create = { place = "right", select = true } } }"ctrl-b" = { action = { client.send = "ctrl-b" } } # a real ctrl-b for the paneThere are two kinds of actions:
server.actions normally have a CLI command.server.tab.create = { starter = "shell" }does the same asship tab create --starter shell, soship <command> --helpdocuments its options. The exception isserver.run, which starts a program on the server's machine and has no CLI form.client.actions work in your window, likeclient.pane.focus,client.sidebar.toggle,client.modeandclient.detach.client.runstarts a program on your machine.
A oneshot mode takes one key and returns to normal, unless that key enters another mode. A sticky mode stays until a binding leaves it. Outside normal mode, unbound keys are ignored rather than sent to the program. clear_defaults = true starts a mode with none of Ship's bindings. Add label = "..." to a binding to change how it's shown in the hints.
When you're in a mode other than normal, a hint bar shows at the bottom with that mode's keys, wrapping into aligned columns when they don't fit on one line. The same bar shows errors and when a pane's program has exited.
The default keys are in defaults.toml. Key names like ctrl-alt-x and shift-tab follow crokey.
Running programs from keys
Section titled “Running programs from keys”Open the docs in your browser with alt-u, or name the selected pane picked with alt-i:
[client.modes.normal.keys]"alt-u" = { action = { client.run = { command = ["xdg-open", "https://shipmux.github.io/ship/"] } } }"alt-i" = { action = { server.run = { command = ["sh", "-c", "\"$SHIP_BIN\" pane set --name picked"] } } }The browser example uses Linux's xdg-open. On macOS, replace it with open.
client.run starts a program on the client's machine, in the directory you started the client from. Use it for things that need your desktop or login session, like opening a URL.
server.run starts a program on the server's machine, with the same environment and starting directory as a new pane created from the selected pane. With nothing selected, it starts in the server's home directory. The example renames the selected pane to picked by calling Ship back.
Both run in the background. Each press starts another run without waiting for earlier ones, and you can keep typing while they run. Menus accept the same actions.
command must be a non-empty array of strings. The first string is the program and the rest are its arguments. There is no shell expansion unless you name a shell, as the rename example does.
The program gets SHIP_BIN, SHIP_SERVER_URL and SHIP_ATTACHMENT_ID so it can call Ship back, plus SHIP_PANE_ID when a pane is selected. It gets no input, and its output goes to that side's log, never onto the screen.
A program that can't start shows an error naming it in the status line. A non-zero exit or signal also shows there for client.run, but is only logged for server.run.
Ship never stops a run, even when you quit the client, close its terminal or stop the server. Put a program that should end with Ship in a pane instead.
Each right-click menu can be replaced with your own list of actions, written like key bindings:
[client.menus]pane = [ { action = { client.pane.zoom = { zoomed = "toggle" } }, label = "fill" }, { action = { server.pane.close = {} } },]tab = [] # no tab menuThe menus are pane, tab and global. Any you leave out keep their defaults.