Skip to content

Installation ​

There are three ways to install Ship of Tools. GitHub Releases ship prebuilt artifacts for Linux x86_64, Windows x86_64 and macOS aarch64, so the default path is from release artifacts.

  • Agent-driven — start a Claude Code or Codex session on the target machine and tell it:

    text
    Install Ship of Tools: fetch https://raw.githubusercontent.com/kalidke/ship-of-tools/main/docs/INSTALL-AGENT.md and follow it.

    The agent checks the machine, confirms the release assets match the platform, falls back to the source path when they do not, and checks that the result answers before it reports success. The runbook it follows is docs/INSTALL-AGENT.md in this repo.

  • From release artifacts — prebuilt binaries via the install.sh installer. No Rust toolchain is needed.

  • From source — clone, build the Rust workspace, instantiate the Julia environments. This is the path for developing Ship of Tools; Per-machine setup adds the config and launcher checklist.

From release artifacts (Linux/macOS) ​

scripts/install.sh downloads a GitHub Release, verifies its checksums, and sets everything up under ~/.local/share/sot by default:

bash
curl -fsSL https://raw.githubusercontent.com/kalidke/ship-of-tools/main/scripts/install.sh | bash -s -- --local

The script fetched from main only resolves the latest release and runs that release's own install.sh, so what gets installed is always a tagged release.

With no role flag the installer asks on the terminal which role this machine should play; with no terminal (cron, CI, an agent's shell) it stops, so pass the role explicitly:

FlagRole
--localall-in-one: frontend and backend on this machine
--backend <ssh-alias>frontend here, backend on a remote host over SSH; the flag names where the backend lives
--be-onlyheadless backend only (servers)

Plus --version vX.Y.Z to pin a release (default: latest), --prefix <dir> to relocate the install, --no-service on backend roles when something else supervises sotd, --hub <ssh-alias> to copy the hub's ~/.config/sot/hosts.toml to this machine first (for a machine that does not share the hub's home directory), and --force-role-change (see the next section).

If ~/.config/sot/hosts.toml already names this host, that entry decides which parts are installed and enabled here, and a role flag on the command line is ignored.

A home directory shared by several Linux hosts shows them all the same sotd.service and the same boot start. The installer pins the unit with sotd topology pin: a drop-in, sotd.service.d/topology.conf, under which systemd starts it only on the hosts hosts.toml declares daemon or frontend, so on a shared home that file must list every host. With no hosts.toml the pin names the host that ran the install, and only that host starts the daemon. The pin compares the kernel hostname, so a host that SOT_SELF_HOST renames is not matched. An install or update rewrites the pin from hosts.toml (an update with no hosts.toml leaves it as the install wrote it), and so do the hub's daemon at each start and sotd topology apply there; a host whose entry changed reads it at its next boot or systemctl --user daemon-reload. The unit has no [Install] section: a drop-in, default.target.d/sotd.conf (Wants=sotd.service), starts it at boot, and the pin above picks the hosts. So systemctl --user enable and disable change no file for sotd.service, on any host, and a hand-typed one on one host cannot turn boot start off for the others. Stop the daemon on a host now with systemctl --user stop sotd.service. What it does at the next boot is what the pin and hosts.toml decide: to move it, change hosts.toml (or, with none, run sotd topology pin --dir ~/.config/systemd/user --claim on the host that should run it). On a home no other host shares, turn boot start off with rm -f ~/.config/systemd/user/default.target.d/sotd.conf; write that file again ([Unit] and Wants=sotd.service) to turn it back on. One rule governs both ways to update, running the installer again and the background update: a run that finds no sotd.service wires boot start, and a run over a unit moves an old enable link onto the drop-in or keeps the drop-in, and with neither writes nothing, so boot start you turned off stays off; an install.sh run says in a line before its verdict whether boot start is on, and when off, the command that turns it on, and the background update says so when it leaves boot start off (The daemon no longer starts at boot). An install that runs no daemon on this host only stops this host's, and moves an old link onto the drop-in, unless the pin names this host alone: then it also removes the drop-in. The relay sockets and tunnels keep their rule: never disable them by hand.

The install itself is shared the same way: one ~/.local/share/sot and one install.json serve every host that runs sotd. Run the installer on one of those hosts. On a host hosts.toml leaves out or lists with neither key, the installer refuses to write over an install already there, whatever role its flags ask for; on a host that does run sotd, it refuses --no-service over an install whose record names the systemd unit. Either refusal comes before it writes anything under ~/.local/share/sot or ~/.local/bin. The remedy for the first is to declare the host in hosts.toml (frontend for a machine that runs a window) and re-run; a machine with a home of its own is declared in the hub's hosts.toml and re-runs with --hub <hub alias>, which fetches it. The remedy for the second is to drop --no-service. --force-role-change overrides either refusal, for a machine whose own role changed.

Every machine that runs a window also runs its own private local daemon, so an entry with frontend = true alone installs one as --local does, and the window shows that daemon's sessions beside those of every daemon host the topology lists. daemon = true means other machines dial that daemon through the hub; the installer never adds this machine to anyone's dial list. A first install with --backend <ssh-alias>, made before the hub's hosts.toml lists this machine, installs no local daemon. Once the hub lists it, the topology decides its role on every later run of the installer, and its window still reaches every daemon host the topology lists.

The checkout under the prefix is part of the installed product: a git clone --filter=blob:none of the selected tag, so history is available but only that tag's files are downloaded. The runtime resources and the in-app manual resolve from it. For roles that run a backend on this machine (--local and --be-only), the installer instantiates the Julia environments inside the checkout (julia/kernel, julia/repl, julia/pluto). --backend <ssh-alias> is a frontend-only install and uses Julia locally only to install the agent skills; install the remote machine as a backend role. Julia runs the installer's comm step on every role, which is why it is needed here too; the skills and hooks it installs are inert unless a session runs on this machine.

Connection behavior is role-specific:

  • --local runs frontend and backend on one machine over the backend user's per-user socket. There is no SSH-to-localhost requirement.

  • --backend <ssh-alias> writes a launcher that passes --dial <host>=ssh:<host> to the frontend, which reaches the remote user's per-user backend socket by spawning its own ssh child — no forward. The launcher builds every --dial from the hosts the hub's hosts.toml declares, and fetches that file from the hub, named by this alias, at each launch; it refuses a file that does not list this machine. So install and enroll the backend first (--be-only on the server, then the hub's ~/.config/sot/hosts.toml lists the hub, the server as a daemon host and this machine as frontend = true; see Going remote and Enrolling a host), and only then run --backend. Without that entry the install ends in a FAIL: enroll: line that names the command to run on the hub (sotd topology set add <this machine> --frontend); run it there, then run the installer again.

  • --be-only installs the headless backend and, unless --no-service, runs it as the systemd --user unit sotd.service.

Re-running on a machine that already has an install ​

A re-run is an upgrade. The installer refuses to overwrite files that belong to another install: the sotd.service unit, the sot-launch wrapper, the desktop entry and the macOS app. If one of these already exists under a different prefix (a source checkout, another --prefix, an earlier trial install), the installer stops before writing anything and names the file and its owner. --force-role-change overrides that. A file whose owner cannot be determined (an unrecognized sot-launch shape) is refused with no override; move it aside and re-run.

What the installer changes ​

Everything below is written under your home directory, with one exception: on Linux backend roles the installer runs loginctl enable-linger for your user, a systemd setting kept outside your home (it continues without it if that call is refused). Nothing needs root. The default prefix is ~/.local/share/sot (--prefix moves it). There is no uninstall script; Uninstall lists the commands to remove each item by hand.

Roles: all is every install; backend is --local and --be-only; frontend is --local and --backend. A host that hosts.toml declares gets the parts its entry names (daemon, frontend) instead of its flags.

PathWhat
~/.local/share/sot/bin/all · sot (frontend), sotd (backend daemon), sot-capsule (session supervisor), sot-apply (update applier)
~/.local/share/sot/repo/all · the release checkout, one directory per version under repo/versions/ (the current and previous one are kept); repo/current links to the installed one
~/.local/share/sot/julia/currentall · a link to the same checkout
~/.local/share/sot/updates/all · staging area for downloaded updates
~/.local/share/sot/install.jsonall · install manifest: prefix, version, and which parts (daemon, frontend) were installed
~/.config/sot/settings.tomlall · a [trust] table with root_prefix set to your home directory, written when the file is missing and appended when the file has no [trust] table; an existing [trust] is kept. The daemon uses it to skip Claude Code's folder-trust dialog for projects under your home (see Configuration files)
~/.config/sot/hosts.tomlall · written only with --hub, as a copy fetched from the hub; otherwise only read, except that a pre-0.6 file (top-level default_host) is moved aside to hosts.toml.v1-<timestamp>
~/.juliaup/all · Julia 1.12, installed with the juliaup installer, only if no Julia 1.12 or newer is on PATH (an existing juliaup gets the 1.12 channel added instead)
the first depot in DEPOT_PATH (usually ~/.julia)backend · packages for the kernel, REPL and Pluto environments
~/.config/systemd/user/sotd.servicebackend, Linux, unless --no-service · the backend service, started (its boot start is the drop-in below); loginctl enable-linger is also run so it survives logout. The service sources ~/.bashrc before it starts sotd, so the backend and its sessions inherit your shell profile's exports.
~/.config/systemd/user/default.target.d/sotd.confbackend, Linux, unless --no-service · the boot drop-in (Wants=sotd.service) that starts the backend service at boot; written on a fresh install and kept or moved from an old enable link on a later one
~/.config/systemd/user/sotd.service.d/topology.confbackend, Linux, unless --no-service · the host pin described above
~/.local/bin/sot-launchfrontend · the launcher wrapper
~/.local/share/applications/ship-of-tools.desktopfrontend, Linux · the desktop entry
~/.local/share/icons/hicolor/frontend, Linux · the icon, 256x256/apps/ship-of-tools.png
~/Applications/Ship of Tools.appfrontend, macOS · app bundle

The installer then runs ShipTools.update_comm() from the checkout, which installs the agent integration:

PathWhat
~/.sot-comm/the session messaging scripts (bin/) and their runtime state
~/.local/bin/ccb (Claude Code launcher), ccx (Codex launcher), sot-fe, show-result, sot-gh-auth
~/.claude/skills/the thirteen Claude Code skills, one folder each: julia-repl, show-result, sot-comm, sot-session-start, sot-status, sitrep, worktree, project-log, reauth, sot-install, sot-setup, sot-statusline-setup, sot-gh-auth
~/.claude/settings.jsonhook entries for UserPromptSubmit, PreToolUse (on AskUserQuestion), PostToolUse and Stop, each calling a comm-*.sh script in ~/.sot-comm/bin; merged in without removing existing hooks
~/.claude-auth/<name>/settings.jsonthe same hook entries, merged the same way into each Claude account that has a settings.json; a link is followed and its target written once, and an account folder with none is skipped and none is created there (the backend links the shared one in at the account's first spawn, or on Windows without symlinks copies it once, and that copy then gets the hooks like any account's own)
~/.codex/skills/the Codex skills sot-comm and sot-session-start
~/.codex/AGENTS.mdthe repo's AGENTS.md, installed when none is there and replaced only while the installed file holds "Codex sessions in Ship of Tools"; any other file there is left alone and a warning says to merge by hand
~/.agents/plugins/sot-comm/, ~/.agents/plugins/marketplace.jsonthe Codex work-state hook plugin; written whether or not Codex is installed, and registered with codex plugin add sot-comm@sot-local when it is

~/.claude and ~/.codex follow CLAUDE_CONFIG_DIR and CODEX_HOME when those are set; ~/.sot-comm follows SOT_COMM_HOME. The other paths do not move: ~/.local/bin, ~/.agents/plugins, ~/.claude-auth and everything in the first table are always written in your home directory, and an existing ~/.claude/settings.json gets the hooks even when CLAUDE_CONFIG_DIR points elsewhere. If CLAUDE_CONFIG_DIR names an account folder under ~/.claude-auth that has no settings.json, none is created there; the hooks go into ~/.claude/settings.json (created if missing), which the backend links into the account (on Windows without symlinks, copies in once). The install ends by naming every Claude settings file it left without the hooks, with the reason (an account with no settings.json, a file made read-only, one that could not be read or parsed). Setting those three variables is therefore not an isolated try-out; for that, install under a separate user account or in a virtual machine.

The hooks do nothing in an agent session that has not joined the Ship of Tools session registry. Each hook first looks the session up in the session registry (~/.sot-comm/registry.json) and exits without writing anything when it is not there, which is the case for a claude or codex you start yourself in a terminal. Setting SOT_COMM_HOOKS=off turns the Claude Code work-state hooks off entirely.

The skills are likewise available in a plain terminal claude, but their commands need a Ship of Tools session: julia-repl and show-result reach sot-fe, which has nothing to talk to outside one.

A skill directory with the same name as one of the skills above is overwritten: each shipped file replaces the file of the same name, and any other file in that directory is deleted. No backup is made. Every re-run of the installer, including each update, repeats this. The six skills without an sot- prefix are the likeliest to collide with one of your own: julia-repl, show-result, sitrep, worktree, project-log and reauth.

The installer itself edits no shell profile. If no Julia 1.12 or newer is found and juliaup is not installed, it runs the juliaup installer, which adds its PATH block to your shell startup files; juliaup self uninstall removes it.

How agent sessions are launched ​

Sessions you create in the window start their agent with these permission settings:

  • Claude Code: claude --permission-mode auto.

  • Codex: codex --dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust, through the ccx launcher, which also marks the session's directory as trusted in ~/.codex/config.toml.

Claude Code's auto mode is not a bypass. It runs without routine permission prompts, but a separate classifier model reviews each action before it runs and blocks anything that goes beyond your request, targets infrastructure it does not recognize, or looks driven by hostile content; your own ask rules still prompt. See Claude Code's permission modes. Codex has no equivalent reviewed mode here: its flags turn approvals and the sandbox off.

These flags are fixed: no setting or environment variable changes them. The Claude Code flags are built into the daemon, and the Codex flags are in the ccx script, which every update overwrites. Ship of Tools adds no permission layer of its own on top. A claude or codex you start yourself, outside Ship of Tools, uses whatever flags you give it. See Agent sessions.

Uninstall ​

There is no uninstall script. To remove an install made with the default prefix, end the sessions, stop the service and delete what the installer wrote. Sessions outlive the service, so end them first (in Sessions mode, Shift+D twice on each row, or close the last window): deleting the install under a running session leaves it running from a deleted binary. On a home several hosts share, these steps delete the shared install, so they uninstall every host:

bash
systemctl --user stop sotd.service
rm -rf ~/.config/systemd/user/sotd.service ~/.config/systemd/user/sotd.service.d \
      ~/.config/systemd/user/default.target.d/sotd.conf ~/.config/systemd/user/default.target.wants/sotd.service
systemctl --user daemon-reload
loginctl disable-linger "$USER"   # skip if something else needs linger
rm -rf ~/.local/share/sot ~/.config/sot ~/.sot-comm
rm -f ~/.local/bin/sot-launch ~/.local/bin/ccb ~/.local/bin/ccx ~/.local/bin/sot-fe \
      ~/.local/bin/show-result ~/.local/bin/sot-gh-auth
rm -f ~/.local/share/applications/ship-of-tools.desktop \
      ~/.local/share/icons/hicolor/256x256/apps/ship-of-tools.png
rm -rf ~/.claude/skills/{julia-repl,show-result,sot-comm,sot-session-start,sot-status,\
sitrep,worktree,project-log,reauth,sot-install,sot-setup,sot-statusline-setup,sot-gh-auth}
rm -rf ~/.codex/skills/{sot-comm,sot-session-start}
rm -rf ~/.agents/plugins/sot-comm

These commands use the default locations. If CLAUDE_CONFIG_DIR, CODEX_HOME or SOT_COMM_HOME is set, use those directories instead of ~/.claude, ~/.codex and ~/.sot-comm. Deleting ~/.config/sot also deletes your hosts.toml and any settings you edited; keep a copy if you want them.

Then, by hand:

  • remove the hook entries that call ~/.sot-comm/bin/comm-*.sh from ~/.claude/settings.json (even when CLAUDE_CONFIG_DIR is set), from $CLAUDE_CONFIG_DIR/settings.json when it is set, and from each ~/.claude-auth/<name>/settings.json; where one of these is a link, edit the file it points to, once;

  • if Codex is installed, remove the sot-comm@sot-local plugin (it is listed by codex plugin list);

  • remove the sot-local entry from ~/.agents/plugins/marketplace.json, or delete the file if nothing else is listed in it;

  • remove the [projects."<dir>"] blocks with trust_level = "trusted" that ccx appended to ~/.codex/config.toml, one per directory a Codex session ran in;

  • delete ~/.codex/AGENTS.md if the installer created it;

  • on macOS, delete ~/Applications/Ship of Tools.app;

  • ~/.juliaup and the Julia depot are ordinary Julia installs; keep them or remove them with juliaup (juliaup self uninstall also removes the PATH block it added to your shell startup files).

Sessions also keep runtime state under ~/.local/state (or $XDG_STATE_HOME); see Sessions and persistence.

Updating ​

Re-running the install command is also the updater. The built-in update check, pinning a version, and rolling back are in Updating and rollback.

Requirements ​

Platforms ​

  • Linux x86_64: all roles.

  • Windows: the frontend, against a Linux backend (see below).

  • macOS (Apple Silicon): experimental; the tested use is a frontend against a Linux backend (--backend) — the other roles install but agent sessions on a Mac backend are not supported yet.

Prerequisites ​

ToolNeededWhat needs itInstall
git, curl, tarrequiredthe installeryour package manager
jqrequiredthe comm scripts read and write JSON with it; the installer stops before it changes anything, naming the commandsudo apt-get install jq (macOS: brew install jq)
Claude Code or Codexrequired, logged in, on every machine that runs sessionsthe agent; the installer does not install onethe agent's own installer
Julia 1.12 or newerrequiredthe kernel, the REPL, Pluto; the installer installs it with juliaup when missing, and an existing juliaup only gets the 1.12 channel added, not made the default. The daemon runs the julia that SOT_JULIA_BIN names, else juliaup's default channel, else the one on PATH; the comm refresh runs the julia on PATH. When the julia on PATH is older than 1.12 the install stops there, before any step runs on it; when only the daemon's is older, or either cannot be asked, it ends ONE STEP LEFT. Either line names the version and the command: juliaup default 1.12 (after juliaup add 1.12 when that channel is not installed), or installing a newer Juliajuliaup
Xvfboptional, Linux backendGLMakie in the REPL and in Pluto: each gets an X display of its own. Without it the REPL's first run says it has no display of its own, and CairoMakie and WGLMakie still work; the installer does not check for itsudo apt-get install xvfb
node/npmoptional, backendtypeset math in markdown previews renders in a long-lived node process, whose dependencies the installer fetches with npm ci. Without node the installer warns and continues, and math renders as raw LaTeX. To add math later, install node and re-run the installer (or npm ci in <checkout>/rust/backend/sidecars/mathjax)your package manager
poppler-utils (pdftoppm, pdfinfo)optional, backendPDF pages in previews; the installer does not check for them, and without them the preview shows a note naming the missing toolsudo apt-get install poppler-utils
quartooptional, backendo on a .qmd file renders it with Quartothe Quarto installer
ffmpegoptional, backendvideo poster frames in previews; the installer does not check for it, and without it the preview shows a note naming the missing toolsudo apt-get install ffmpeg

Beyond the tools:

  • linux-x86_64 or macos-aarch64 release artifacts (a windows-x86_64 zip also ships for the Windows frontend path below).

  • Frontend roles need glibc 2.35 or newer (Ubuntu 22.04 or newer) and a desktop session (X11 or Wayland): the frontend is a native window. It renders with wgpu through Vulkan on Linux, Metal on macOS and DirectX 12 or Vulkan on Windows, so a Linux frontend needs a working Vulkan driver. The backend binary is static musl and runs on any distribution; --be-only has no glibc floor.

  • --backend <ssh-alias> needs key-based SSH to the remote backend host.

  • No GitHub authentication: the repo is public and releases download from fixed URLs.

  • A home directory on NFS (or another remote filesystem) needs a systemd drop-in pointing the daemon's state root at local disk before sessions will start; see A session will not start on a shared home.

On Windows the installer is scripts/install.ps1 (Windows PowerShell 5.1; it needs git and curl.exe on PATH), and no Rust toolchain is needed: it downloads and verifies the release's sot-<ver>-windows-x86_64.zip, puts sot.exe, sotd.exe and sot-capsule.exe in %LOCALAPPDATA%\sot\bin, lays out repo\base, repo\versions\v<ver> and the repo\current junction, installs the comm layer, and creates the desktop shortcut (and re-syncs any taskbar pin) that runs repo\current\scripts\launch-sot.ps1. On a fresh box fetch the installer of the latest stable release and run it with -Hub (the hub's ssh alias) and no version; to update, run it again from the installed checkout with no options. -Version names a release instead and pins the install, so its automatic updates stay off until a run that names no version (while the latest stable release has no install.ps1, naming a pre-release is the way in). It ends with one PASS, ONE STEP LEFT or FAIL line. Do not point a shortcut at bare sot.exe, which skips the launcher's update and relaunch handling. The launcher also declares the home folder as the trusted folder prefix in %LOCALAPPDATA%\sot\config\settings.toml (a [trust] table, written once and never rewritten), so a session the daemon starts there does not stop at the agent's folder-trust dialog. The step-by-step walkthrough is INSTALL-AGENT.md §2b (written for a coding agent, equally followable by hand). A Windows box's comm scripts need jq on PATH too (winget install jqlang.jq): the installer's last line names that command when it is missing.

On macOS aarch64 the bash installer works, but there is no launchd service; local roles start sotd on demand. See Platforms for what is and is not supported on a Mac.

From source ​

Ship of Tools is a Rust and Julia project: the frontend and backend are Rust binaries, the kernel and plugins are Julia. Installing from source means building the Rust workspace once and instantiating a handful of Julia environments. Machine-specific config and launchers are covered by Per-machine setup; run that page's checklist by hand or let the /sot-setup Claude Code skill drive it.

Prerequisites ​

ToolVersionWhy
Rust toolchaincurrent stablefrontend, backend, protocol crates
Julia1.12 or newerkernel, ConceptExplorerCore, plugins
gitanyclone the repo

Install Rust with rustup and Julia with juliaup.

Clone ​

bash
git clone https://github.com/kalidke/ship-of-tools
cd ship-of-tools

Build the Rust workspace ​

The Rust workspace lives under rust/ and has six members: protocol (shared line-protocol types), backend (the daemon), frontend (the native window), updater, log and vt100 (a terminal-emulator fork). One command builds all of them:

bash
cargo build --release --manifest-path rust/Cargo.toml

This produces the binaries the launcher runs:

BinaryPathRole
sotrust/target/release/native window, chrome, previews
sotdrust/target/release/project state, supervision, agent sessions
sot-capsulerust/target/release/sotd's capsule-runtime pair, one per session

The first release build compiles the full rendering stack (winit, wgpu, cosmic-text, glyphon, resvg) and takes a while; later builds are incremental.

Instantiate the Julia environments ​

The repo is a set of nested Julia environments. The umbrella environment at the repo root pins project-level dependencies such as CairoMakie:

bash
julia --project=. -e 'using Pkg; Pkg.instantiate()'

Then the core library, kernel, REPL shim, and Pluto sidecar:

bash
julia --project=core            -e 'using Pkg; Pkg.instantiate()'
julia --project=julia/kernel    -e 'using Pkg; Pkg.instantiate()'
julia --project=julia/repl      -e 'using Pkg; Pkg.instantiate()'
julia --project=julia/pluto     -e 'using Pkg; Pkg.instantiate()'

core/ is ConceptExplorerCore, the abstract types and dispatch contract that make up the extension ABI. The standard plugins live under julia/plugins/*; instantiate any plugin environment you intend to load the same way, e.g.:

bash
julia --project=julia/plugins/julia-source -e 'using Pkg; Pkg.instantiate()'

rust/backend/sidecars/mathjax needs npm ci for typeset math; without it math renders as raw LaTeX.

Verify the build ​

bash
ls rust/target/release/sot rust/target/release/sotd rust/target/release/sot-capsule
julia --project=core -e 'using ConceptExplorerCore; println("core OK")'

To start the window from the checkout, run scripts/launch-sot.sh.

Next steps ​

  • Quickstart — launch and start a first agent session.

  • Going remote — a backend on a server, the window on your laptop.

  • Per-machine setup — the source-checkout checklist: toolchains, config files, comm skills, and a launcher.

An agentic development environment for Julia. Released under the AGPL-3.0-or-later, with a commercial license on request.