Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ A **Project** is a folder. A **Service** is one long-running command in it: `pnp
- **Keyboard first**: a command palette, a shortcut for every action, arrow keys across the window. Screen readers, light and dark, reduced motion.
- **Coding agents included**: Claude Code, Cursor, Codex and other MCP clients start, stop and inspect your Services through `localhost mcp` instead of a hidden shell. The agent and the app share one background core, so what the agent starts shows in the app, with its logs. See [Coding agents (MCP)](#coding-agents-mcp).
- **Updates that ask first**: a check at launch and every 6 hours, a notice in the sidebar, and Restart to Update stops your running Services only after asking. See [Updates](#updates).
- **Your other computers too**: a dev box you reach with `ssh` is a **Machine**. Its Projects show below yours, and start, stop and open in your browser from here, while they run on it. See [Run Projects on another computer](#8-run-projects-on-another-computer).
- **Local**: no account, no cloud, works offline. Configuration lives in localhost's own storage, never in your project folders.

A native SwiftUI app on macOS with a menu bar companion; one app for Linux and Windows with a tray icon. On Windows, Projects run on Windows itself or inside a WSL 2 distro. All of them, the `localhost` command and the MCP server run on one Rust core.
Expand Down Expand Up @@ -159,9 +160,20 @@ Before starting a Service with a Port, localhost checks who holds it. If somethi

The **Ports** view (**⌘0**) lists every process listening on a TCP port and whom it belongs to: one of your Services, a process running inside one of your Projects' folders, or nothing localhost knows (other apps and system services are folded below). Select one and press ⌘. to stop it; localhost asks first for anything it didn't start.

### 7. Move a Project to another machine
### 7. Copy a Project's configuration to another computer

**Export Project…** saves a Project as a YAML Project file, and **Import Project…** reads one back, showing what it contains before adding anything; choose the folder if it is somewhere else on this machine. Secret values are never exported: the file lists Secret names, and the import shows which ones to fill in. The format is in [docs/configuration.md](docs/configuration.md).
**Export Project…** saves a Project as a YAML Project file, and **Import Project…** reads one back, showing what it contains before adding anything; choose the folder if it is somewhere else on this computer. Secret values are never exported: the file lists Secret names, and the import shows which ones to fill in. The format is in [docs/configuration.md](docs/configuration.md).

### 8. Run Projects on another computer

A dev box or home server you reach with `ssh` can be a **Machine**: its Projects stay and run there, with its own configuration and Secrets, and this app shows and controls them.

1. Check that `ssh devbox localhost --version` works from a terminal without asking anything (a key, in the SSH agent if it has a passphrase, and a host key you already trusted). It runs without your interactive profile: with zsh, put `localhost` on the `PATH` in `~/.zshenv`, not `~/.zshrc`.
2. Open **Settings → Machines → Add Machine…**. In **SSH host**, type a `Host` from `~/.ssh/config` (the field lists them), or `user@host:port`. localhost connects first, and says why and what to do if it can't.
3. The Machine's Projects show in the sidebar under its name, below this computer's: Start, Stop, Restart, logs. They are edited on the Machine.
4. **Open in Browser** forwards the Service's Port to the same port here, through the SSH connection and on loopback only, then opens it.

Any network `ssh` uses works: a LAN, a VPN, `ProxyJump`, Tailscale. Nothing new listens on either computer. A lost connection is made again on its own; Services you started from here stop on the Machine when the connection ends, unless localhost is open there. More in the [Machines docs](https://www.getlocalhost.app/docs/machines).

## Keyboard shortcuts

Expand Down Expand Up @@ -272,6 +284,7 @@ Inside that folder:
- `projects/`: one JSON file per Project. A file that can't be read is renamed to `….unreadable` and reported at launch, never deleted.
- `logs/`: each Run's output, the last 5 Runs per Service.
- `runs/`: the Runs active right now, to find leftovers after a crash.
- `machines.json`: the Machines you added, their names and SSH destinations; `machines/` holds their SSH connections' sockets while they are open.
- `agent-backups/`: each coding agent's configuration file as it was before localhost first connected it.
- `bin/`: the AppImage's `localhost` command, copied out for coding agents (Linux AppImage only).

Expand Down Expand Up @@ -393,6 +406,10 @@ Add the Project’s folder and localhost suggests Services from `package.json` s

Yes. `localhost mcp` is an MCP server for Claude Code, Cursor, Codex and other coding agents: they list your Projects, start and stop Services, and read their status, logs and ports, instead of running `pnpm dev` in a shell you can’t see. They can also add, edit and remove Projects and Services, checked as the app checks them: removing needs `confirm: true` and never touches a Project’s folder, and `localhost mcp --read-only` leaves those tools out. The agent and the app share one background core, so what the agent starts shows in the app, and the Services it started stop when it disconnects, unless the app is open.

### Can I run my dev servers on a dev box or home server and open them from my laptop?

Yes. Add it in Settings → Machines with the name you give `ssh` (a `Host` from `~/.ssh/config`, or `user@host:port`), and its Projects show in the sidebar below your laptop’s. Start, stop and read their logs from there; Open in Browser forwards the Port to the same port on your laptop through the SSH connection, so `http://localhost:5173` works as if it ran locally. Its Services, configuration and Secrets stay on it. It works over any network `ssh` reaches, such as Tailscale or a VPN, and adds no account or listening port. localhost has to be installed on both computers.

### Is it free and open source?

Yes. localhost is free and open source under the MIT license. There is no account and no backend, and it works offline.
Expand Down
14 changes: 14 additions & 0 deletions site/src/content/changelog/0.5.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
version: 0.5.0
title: Your dev box's Projects, in the same window
date: 2026-11-05
released: false
---

- **Machines: another computer's Projects, from here.** Add a dev box, a home server or a cloud VM you reach with `ssh` in **Settings → Machines**: type a `Host` from `~/.ssh/config` (the field lists them) or `user@host:port`, and localhost connects before it keeps it. Its Projects show in the sidebar under its name, below this computer's, and you start, stop, restart and read their logs as you do here. They run on the Machine, with its configuration and Secrets, and are edited there. See [Machines](/docs/machines).
- **Open in Browser forwards the Port.** For a Machine's Service, Open in Browser first forwards its Port to the same port on this computer, through the SSH connection and bound to loopback only, so `http://localhost:5173` there is `http://localhost:5173` here. If that port is taken here, it says by whom rather than using another.
- **Over the SSH you already use.** localhost runs your system's `ssh`, so your keys, the SSH agent, `~/.ssh/config`, `ProxyJump` and Tailscale all apply, and it adds no sign-in and no port of its own. It never asks for a password, and never trusts a host key for you.
- **It says why it can't connect.** An untrusted or changed host key, a sign-in that needs a password, a host that isn't found or doesn't answer, localhost missing on the Machine or too old: each in plain words, with what to do next. A lost connection is made again on its own, and on a Mac as soon as it wakes; reconnecting never starts or stops a Service. See [When it can't connect](/docs/machines#when-it-cant-connect).
- **Quitting asks about Machines too.** Services you started from here on a Machine stop there once the connection ends, unless localhost is open on it, and quitting says so first.
- **An old background core says what to do.** When localhost was updated but an older version still runs in the background, often for a coding agent's `localhost mcp`, it now names that version and asks you to quit it and the agents using it, here and on a Machine.
- The first-run screen's three actions are stacked at one width, so they fit the column on every platform.
1 change: 1 addition & 0 deletions site/src/content/docs/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Inside that folder:
- `projects/`: one JSON file per Project. A file that can't be read is renamed to `….unreadable` and reported at launch, never deleted.
- `logs/`: each Run's output, the last 5 Runs per Service.
- `runs/`: the Runs active right now, to find leftovers after a crash.
- `machines.json`: the [Machines](/docs/machines) you added, their names and SSH destinations; `machines/` holds their SSH connections' sockets while they are open.
- `agent-backups/`: each coding agent's configuration file as it was before localhost first connected it.
- `bin/`: the AppImage's `localhost` command, copied out for coding agents (Linux AppImage only).

Expand Down
3 changes: 3 additions & 0 deletions site/src/content/docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ A **Project** is a folder. A **Service** is one long-running command in it: `pnp
- **Set up in seconds**: point it at a folder and it suggests Services from `package.json` scripts, Compose files, Python apps and Cargo binaries. You review every suggestion; nothing is applied silently.
- **Keyboard first**: a command palette, a shortcut for every action, arrow keys across the window. Screen readers, light and dark, reduced motion.
- **Coding agents included**: Claude Code, Cursor, Codex and other MCP clients start, stop and inspect your Services through [`localhost mcp`](/docs/mcp) instead of a hidden shell. The agent and the app share one background core, so what the agent starts shows in the app, with its logs, and the reverse. See [localhost for AI coding agents](/agents).
- **Your other computers too**: a dev box or home server you reach with `ssh` is a [Machine](/docs/machines). Its Projects show below yours, and you start, stop and open them from here; they run on it, with its Secrets.
- **Local**: no account, no cloud, works offline. Configuration lives in localhost's own storage, never in your project folders. Checking for [updates](/docs/updates) is the only thing it asks the network for, and you can turn it off.

A native SwiftUI app on macOS with a menu bar companion; one app for Linux and Windows with a tray icon. On Windows, Projects run on Windows itself or inside a WSL distro. The app updates itself, after asking, everywhere but the `.deb` and `.rpm` packages, which your package manager updates.
Expand All @@ -29,5 +30,7 @@ A native SwiftUI app on macOS with a menu bar companion; one app for Linux and W
| **Secret** | A variable whose value lives in your system's secret store. |
| **Run** | One execution of a Service, from launch until every process it started has ended. |
| **Execution Target** | Where a Project's Services run: this machine, or a WSL distro. |
| **Machine** | Another computer whose localhost this app shows and controls, over `ssh`. Its Projects, Secrets and Runs stay on it. |
| **Forward** | A Machine's Port made reachable on this computer's loopback, through the SSH connection, so Open in Browser works. |

Next: [install localhost](/docs/install), then [add your first Project](/docs/quick-start).
79 changes: 79 additions & 0 deletions site/src/content/docs/machines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
A **Machine** is another computer whose localhost this app shows and controls: a dev box under the desk, a home server, a cloud VM. localhost reaches it with your system's `ssh`, as you already do from a terminal. Its Projects show in the sidebar below this computer's, and you start, stop and watch them from here.

Everything stays on the Machine: its Project Configuration, its Secrets and its Runs. Its Services run there, in its login shell, with its files, and read its Secrets from its own secret store; no Secret value ever reaches this computer. Projects on a Machine are edited on that Machine.

## Add a Machine

1. Open **Settings → Machines** and click **Add Machine…**.
2. In **SSH host**, type what you would type after `ssh`: a `Host` from `~/.ssh/config` (the field lists them), `user@host`, or `user@host:port`.
3. Give it a **Name** if you like (it is the destination otherwise), and click **Add**.

localhost connects first and keeps the Machine only once it is reached. If it can't be, it says why and what to do next: see [When it can't connect](#when-it-cant-connect). Each Machine in Settings shows its destination and how it is (`Connected · localhost 0.5.0`, `Connecting…`, or what is wrong), with **Reconnect**, **Rename…** and **Remove…** in its `⋯` menu. Removing a Machine changes nothing on it: its Projects and Services stay as they are.

## What it needs

- **`ssh <destination>` works from a terminal without asking anything.** localhost runs `ssh` with `BatchMode=yes`, so it never types a password or a passphrase. Use a key, and if it has a passphrase, add it to the SSH agent with `ssh-add`.
- **Its host key is already trusted.** Connect once from a terminal and answer `yes`. localhost checks host keys as your configuration says, and never trusts one for you.
- **localhost is installed on the Machine, and on the `PATH` that ssh's commands get.** localhost runs `ssh <destination> localhost remote --stdio`. A command run over ssh gets a non-interactive shell, which doesn't read every profile: with zsh, `~/.zshrc` and `~/.zprofile` aren't read, only `~/.zshenv`. Check it from this computer:

```sh
ssh devbox localhost --version
```

If that says `command not found`, put the folder holding `localhost` on the `PATH` in `~/.zshenv` (zsh) or `~/.bashrc` (bash, before any line that returns early for non-interactive shells). For example, after [Add to PATH](/docs/mcp#use-it-in-a-terminal), which links `~/.local/bin/localhost`:

```sh
export PATH="$HOME/.local/bin:$PATH"
```

Homebrew's `bin` (`/opt/homebrew/bin` on Apple silicon) is often set only in `~/.zprofile`, so a Mac Machine with the [Homebrew cask](/docs/install#homebrew-macos) needs it in `~/.zshenv` too. The Linux `.deb` and `.rpm` put `localhost` in `/usr/bin`, which every shell finds.
- **A localhost with Machines on both computers.** They don't have to be the same version; one from before Machines can't be viewed, and says so.

## Any network

localhost uses whatever `ssh` uses: your keys and agent, `~/.ssh/config`, `ProxyJump`, a LAN, a VPN, or Tailscale. It adds no sign-in, no account and no port of its own: nothing new listens on either computer's network. On the Machine, `localhost remote --stdio` is one more client of its background core, as `localhost mcp` is.

One `ssh` connection per Machine carries everything, and is also an SSH ControlMaster. Its socket is in a folder only you can open, in localhost's storage. Forwards from your own `~/.ssh/config` are cleared on it, so only the ones localhost asks for are made.

## Open in Browser and Forwards

A Machine's Service opens in your browser here as a local one does. **Open in Browser** first forwards its Port to the same port on this computer, through the SSH connection, then opens the same URL: `http://localhost:5173` on the Machine is `http://localhost:5173` here. Same number, because `*.localhost` names, OAuth callbacks and cookies expect it.

A **Forward** is bound to this computer's loopback addresses only (`127.0.0.1`, and `::1` where it exists), so nothing else on your network reaches it. It reaches the Service on the Machine whether it listens on its loopback or on every address. Forwards end with the connection.

If that port is taken on this computer, Open in Browser says so and names who holds it, rather than using another port. Stop whatever holds it here (the [Ports view](/docs/ports#the-ports-view) shows it), then try again.

## In the sidebar

Each Machine has its own section below this computer's Projects, under its name. Its Projects look and act like local ones: Start, Stop, Restart, Start All, Stop All, logs and Open in Browser. While the Machine isn't connected, its Projects are dimmed and show what they showed last; the section says why, with **Reconnect** when it waits for you. A connected Machine with no Projects says so.

## Connections, sleep and quitting

- **A lost connection is made again on its own**, after 1 second, then 2, 5, 15, and every 30 seconds, and its Projects and statuses are read again. On a Mac, also as soon as it wakes from sleep. Reconnecting never starts or stops a Service.
- **A problem waiting won't fix** (a host key, a sign-in that needs a prompt, no localhost there, one too old, or anything SSH says that localhost doesn't recognize) is shown once, and waits for **Reconnect**.
- **Services you started from here** stop on the Machine once the connection ends, unless localhost is open on it: an app open there keeps everything it shows running. A Machine counts a connection as ended after 45 seconds without hearing from it, so closing a laptop's lid ends it too.
- **Quitting** asks first when Services started from here on another Machine are active, and says that they stop too, unless localhost is open on them.

## When it can't connect

localhost reads what `ssh` says and puts it in plain words, with what to do next. These are its messages, with `devbox` as the Machine:

| It says | What to do |
|---|---|
| devbox’s host key isn’t trusted yet. Connect once from a terminal with `ssh devbox`, then try again. | Run `ssh devbox`, check the fingerprint, answer `yes`, then **Reconnect**. |
| devbox’s host key changed. If you expected that, update ~/.ssh/known_hosts from a terminal; otherwise don’t connect. | If the Machine was reinstalled, run `ssh-keygen -R <host>`, then connect once from a terminal. If not, someone may be in between: don't connect. |
| SSH can’t sign in to devbox without asking for a password or passphrase. Add your key to the SSH agent with ssh-add, then try again. | `ssh-add`, or set up a key with `ssh-copy-id devbox` if you sign in with a password. Check that `ssh devbox true` asks nothing. |
| devbox wasn’t found. Check its name in ~/.ssh/config, and that you’re on its network. | Check the `Host` and `HostName`, and that Tailscale or the VPN is on. Retried on its own. |
| localhost can’t reach devbox: … | The network refused or timed out, in SSH's words: is the Machine on and awake, and is `sshd` running? Retried on its own. |
| localhost isn’t installed on devbox, or isn’t on its PATH. Install it there, then try again. | Install it, then check `ssh devbox localhost --version`. With zsh, set `PATH` in `~/.zshenv`: see [What it needs](#what-it-needs). |
| devbox runs a localhost that can’t be viewed from another Machine. Update localhost there. | Update localhost on the Machine, then **Reconnect**. |
| localhost on devbox couldn’t start: … | localhost is there, but couldn't serve this app, in its own words. Most often: see below. |
| The connection to devbox was lost. | Nothing: it tries again on its own. |
| SSH couldn’t connect to devbox: … | Anything else SSH said, often a mistake in `~/.ssh/config`. Check that `ssh devbox` works from a terminal, then **Reconnect**. |
| There is no ssh command on this computer. Install OpenSSH, then try again. | Install OpenSSH's client. |

### “localhost 0.4.0 is still running in the background”

> localhost on devbox couldn’t start: localhost 0.4.0 is still running in the background, perhaps for a coding agent, and can’t work with this version. Quit it and any agent using `localhost mcp`, then try again.

The Machine's localhost was updated, but its background core is still the old version's, kept alive by something using it, most often a coding agent's `localhost mcp` there. The new version can't share it. On the Machine, quit the localhost app and end the agent sessions using `localhost mcp` (or restart them, so they start the new version), then **Reconnect** here.
Loading
Loading