# Installation

> Get the binary, give it a model, serve it, keep it up to date, and run it as a service.

Shelley is a single executable for macOS and Linux, on amd64 or arm64. The web
UI is embedded in it. Getting it running takes about three minutes, give or
take a download: get the binary, give it a model, serve it.

Rather not install anything? Shelley comes running on
[exe.dev](/docs/exe-dev) VMs: a machine of its own, already behind auth, no API
keys required. [Try it](https://exe.dev/new).

## Get the binary

### curl

This grabs the latest release for your OS and CPU:

```sh
os=$(uname -s | tr '[:upper:]' '[:lower:]')
arch=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
url=https://github.com/boldsoftware/shelley/releases/latest/download
curl -fLo shelley "$url/shelley_${os}_${arch}"
chmod +x shelley
```

Each release also has a `checksums.txt`. To check the download, in the same
shell (use `sha256sum` if you don't have `shasum`):

```sh
curl -fsSL "$url/checksums.txt" | grep "shelley_${os}_${arch}$"
shasum -a 256 shelley
```

The two hashes should match. Then put it on your `PATH`; the agent runs
`shelley` itself to load [skills](/docs/skills) and reach
[MCP servers](/docs/mcp):

```sh
sudo mv shelley /usr/local/bin/
```

Prefer clicking? Binaries and checksums are on the
[releases page](https://github.com/boldsoftware/shelley/releases/latest).

### Homebrew

The cask works on macOS and Linux and puts `shelley` on your `PATH`:

```sh
brew install --cask boldsoftware/tap/shelley
```

### From source

You need Go (the version in
[go.mod](https://github.com/boldsoftware/shelley/blob/main/go.mod)), Node.js,
and Python 3. The Makefile fetches the pinned pnpm through `npx` and downloads a
pinned `exe-scroll` release from GitHub, so it needs network access too.

```sh
git clone https://github.com/boldsoftware/shelley.git
cd shelley
make
```

The binary lands in `bin/shelley`; copy it onto your `PATH` (for example,
`sudo cp bin/shelley /usr/local/bin/`). If you're hacking on Shelley itself,
`make serve` builds the UI and runs Shelley from source.

## Give it a model

Shelley picks up API keys from the environment. Set one or more:

```sh
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...      # also enables voice recordings
export GEMINI_API_KEY=...
export FIREWORKS_API_KEY=...
```

Check what it found with `shelley models`. Self-hosted and other compatible
endpoints can be added later from **Manage…** in the model picker. See
[Models and API keys](/docs/models) for the details.

No key yet? `shelley --predictable-only serve` runs against a built-in fake
model, so you can kick the tires for free.

## Serve it

```sh
mkdir -p ~/.shelley
shelley --db ~/.shelley/shelley.db serve
```

Then open [http://localhost:9000](http://localhost:9000).

- **Pick a database path.** `--db` defaults to `shelley.db` in the current
  directory, so starting Shelley from somewhere else gets you a fresh, empty
  database. Hence the fixed path above.
- **Flag order matters.** Global flags (`--db`, `--config`, `--default-model`,
  `--predictable-only`, `--debug`) go before `serve`. Server flags (`--port`,
  `-socket`, `-require-header`) go after it. One dash or two, either works:

  ```sh
  shelley --db ~/.shelley/shelley.db serve --port 8080
  ```

  `--port 0` picks a free port; add `--port-file <path>` to have Shelley write
  down the one it got.
- **Working directory.** Your first conversation starts in the directory you
  launched Shelley from; after that, new ones default to the last directory
  you picked. You can change it per conversation.
- **The browser tool** drives a local Chrome or Chromium if it can find one.
- **More flags.** `shelley -h` and `shelley serve -h` list the rest; see also
  [Command line](/docs/cli).

> **Mind the door.** Shelley is single-user and ships with no authentication
> and no sandbox. It runs commands as you, and it listens on all network
> interfaces. On a laptop, keep it behind a firewall, or put it behind
> something that does auth (a VPN like Tailscale, an authenticating proxy).
> Better yet, give it a machine of its own. See
> [Running it safely](/docs/security).

## Upgrading

A new release is cut automatically on every push to `main` that passes tests.
Tags look like `v0.N.9OCTAL`, where N is the commit count and the digits after
the 9 are the short (six-hex-digit) commit SHA in octal. `shelley version`
prints what you're running.

Shelley checks for new releases itself: the server fetches release metadata
from `boldsoftware.github.io/shelley` when the UI loads (cached for six hours).
If the latest release is five or more days newer than yours, a dot appears on
the ⋮ menu.

To upgrade, open the ⋮ menu and choose **Check for New Version**. The Version
dialog shows what changed and offers an upgrade button. Upgrading downloads the
release binary for your platform, checks it against the release's
`checksums.txt`, and replaces the running executable in place. If you can't
write to that file, Shelley tries passwordless `sudo`. Then the process exits:

- Under systemd, the button says **Upgrade Shelley & Restart**, and with a
  setup like the [one below](#running-it-as-a-service), systemd starts it again.
- Otherwise it says **Upgrade & Kill Shelley Server**, and you start it again
  yourself.

Either way, conversations that were in the middle of a turn carry on once the
new version is up.

The same dialog has an **Auto-upgrade when idle (checks daily)** checkbox,
off by default. With it on, Shelley checks a minute after starting and then
daily, waits up to an hour for a moment when no conversation is working,
upgrades, and exits. That's only useful if something restarts it.

On exe.dev, `shelley install <vmname>` in the exe.dev shell also works; see
[Shelley on exe.dev](/docs/exe-dev#upgrading).

You can also upgrade by hand: download the new binary the same way you got the
first one (or `brew upgrade --cask shelley`), and restart. To turn update
checks off entirely, set `SHELLEY_SKIP_VERSION_CHECK=true` in Shelley's
environment.

## Running it as a service

Shelley doesn't ship a service file, but `serve` supports systemd socket
activation with `-systemd-activation`: systemd owns the listening socket and
hands it to Shelley. That is also the only way to have Shelley listen on one
address, such as `127.0.0.1`, rather than all of them. A minimal pair of units,
modeled on how exe.dev runs Shelley (replace `you` with your user):

```ini
# /etc/systemd/system/shelley.socket
[Unit]
Description=Shelley socket

[Socket]
ListenStream=127.0.0.1:9000

[Install]
WantedBy=sockets.target
```

```ini
# /etc/systemd/system/shelley.service
[Unit]
Description=Shelley
BindsTo=shelley.socket
After=shelley.socket

[Service]
Type=exec
User=you
WorkingDirectory=/home/you
EnvironmentFile=/home/you/.shelley/env
ExecStart=/usr/local/bin/shelley --db /home/you/.shelley/shelley.db serve -systemd-activation
Restart=on-failure
KillMode=process

[Install]
WantedBy=multi-user.target
```

Put your API keys in the `EnvironmentFile`, one `NAME=value` per line, and
`chmod 600` it. Then:

```sh
sudo systemctl daemon-reload
sudo systemctl enable --now shelley.socket shelley.service
journalctl -u shelley -f
```

A few notes on those choices:

- `ListenStream=127.0.0.1:9000` keeps Shelley on loopback. Reach it through an
  SSH tunnel or a proxy ([Running it safely](/docs/security)), or use
  `ListenStream=9000` for all interfaces.
- `WorkingDirectory` is where your first conversation starts.
- `KillMode=process` means restarting Shelley doesn't kill the processes the
  agent started, like the dev server it just spun up.
- Commands the agent runs inherit the service's environment, including systemd's
  short default `PATH`. Add an `Environment=PATH=...` line if your tools live
  elsewhere.
- After an upgrade Shelley exits cleanly, so `Restart=on-failure` leaves it
  stopped. The socket stays open, and the next request starts it again.

## Where files live

| What | Where |
|---|---|
| Conversations, settings, custom models, MCP servers | The `--db` file, plus its `-wal` and `-shm` files |
| Terminal sessions | `terminals/`, next to the database |
| Unix socket for the CLI | `~/.config/shelley/shelley.sock` (honors `$XDG_CONFIG_HOME`) |
| Hooks | `~/.config/shelley/hooks/` |
| Skills you create | `~/.config/shelley/<name>/SKILL.md` |
| Your [customized Shelley](/docs/self-modifying)'s source | `~/.config/shelley/shelley-customization` |
| Your AGENTS.md | `~/.config/shelley/AGENTS.md`, among [other places](/docs/agents-md) |
| History of AGENTS.md edits made in the UI | `~/.local/state/shelley/agents-md.git` |
| Uploads, screenshots, recordings, browser downloads | `/tmp/shelley-*` |
| Terminal helper binary (macOS only) | `~/Library/Caches/shelley/` |

## Uninstalling

Stop Shelley (and `sudo systemctl disable --now shelley.socket shelley.service`
if you set up the units), then remove the binary and its data. Look before you
delete `~/.config/shelley`: your hooks, skills, AGENTS.md, and any
customizations to Shelley itself live there.

```sh
sudo rm /usr/local/bin/shelley    # or: brew uninstall --cask shelley
rm -rf ~/.shelley                 # the --db directory used above
rm -rf ~/.config/shelley ~/.local/state/shelley
rm -rf /tmp/shelley-*
rm -rf ~/Library/Caches/shelley   # macOS only
```
