From a44f465125478c94c3cc46c67033eb4dcba57c5e Mon Sep 17 00:00:00 2001 From: Marcel Losso Date: Thu, 5 Nov 2026 00:18:24 -0300 Subject: [PATCH 1/5] Document Machines in the site docs: what they need, Forwards, reconnecting and every MachineProblem --- README.md | 1 + site/src/content/docs/data.md | 1 + site/src/content/docs/introduction.md | 3 + site/src/content/docs/machines.md | 79 +++++++++++++++++++++++++++ site/src/lib/docs.ts | 1 + 5 files changed, 85 insertions(+) create mode 100644 site/src/content/docs/machines.md diff --git a/README.md b/README.md index 8ff4a77..546d9ae 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,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). diff --git a/site/src/content/docs/data.md b/site/src/content/docs/data.md index 05982c2..6e0fd0f 100644 --- a/site/src/content/docs/data.md +++ b/site/src/content/docs/data.md @@ -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). diff --git a/site/src/content/docs/introduction.md b/site/src/content/docs/introduction.md index 98e7940..8e20afd 100644 --- a/site/src/content/docs/introduction.md +++ b/site/src/content/docs/introduction.md @@ -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. @@ -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). diff --git a/site/src/content/docs/machines.md b/site/src/content/docs/machines.md new file mode 100644 index 0000000..39b3777 --- /dev/null +++ b/site/src/content/docs/machines.md @@ -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 ` 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 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 `, 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. diff --git a/site/src/lib/docs.ts b/site/src/lib/docs.ts index 0a5d932..bf40cda 100644 --- a/site/src/lib/docs.ts +++ b/site/src/lib/docs.ts @@ -27,6 +27,7 @@ export const sections: { title: string; pages: DocPage[] }[] = [ { slug: 'logs', title: 'Logs', description: "Each Service's output in localhost app: the merged All services view, filtering, clickable URLs, and how many Runs are kept on disk, up to 10 MB each.", collection: 'docs', id: 'logs' }, { slug: 'environment', title: 'Environment and Secrets', description: "Variables on Projects, Services and Environment Profiles in localhost app, the order they apply in, and Secrets kept in your system's secret store.", collection: 'docs', id: 'environment' }, { slug: 'ports', title: 'Ports', description: 'How localhost app knows a Service is up from its Port, what happens when a port is taken, moving a Run to a free port, and the Ports view of listeners.', collection: 'docs', id: 'ports' }, + { slug: 'machines', title: 'Machines', seoTitle: 'Run dev servers on another computer over SSH', description: "Machines in localhost app: control another computer's Projects over the ssh you already use, open its Services through Forwards, and fix connections.", collection: 'docs', id: 'machines' }, { slug: 'updates', title: 'Updates', seoTitle: 'How localhost updates on macOS, Windows, Linux', description: 'How localhost app checks for, downloads and installs updates on macOS, Windows and Linux, why running Services stop first, and how to turn checks off.', collection: 'docs', id: 'updates' }, { slug: 'keyboard', title: 'Keyboard shortcuts', description: 'The command palette and every keyboard shortcut of localhost app, on macOS, Linux and Windows: start, stop, Start All, logs, Projects and Services.', collection: 'docs', id: 'keyboard' }, { slug: 'project-files', title: 'Project files', description: "The YAML Project file that localhost app's Export writes and Import reads: every field, Secrets listed by name only, and the review before an import.", collection: 'reference', id: 'configuration' }, From 4c8b6911b4aa1e619791c3bbfc5058b13446973b Mon Sep 17 00:00:00 2001 From: Marcel Losso Date: Thu, 5 Nov 2026 00:18:54 -0300 Subject: [PATCH 2/5] Add Run Projects on another computer to the README, rename the Project file step, and answer the dev box question in the FAQ --- README.md | 20 ++++++++++++++++++-- site/src/lib/faq.ts | 6 ++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 546d9ae..63592f7 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 @@ -394,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. diff --git a/site/src/lib/faq.ts b/site/src/lib/faq.ts index a326e91..7fefc84 100644 --- a/site/src/lib/faq.ts +++ b/site/src/lib/faq.ts @@ -36,6 +36,12 @@ export const faq: { question: string; answer: string[] }[] = [ '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.', ], }, + { + question: 'Can I run my dev servers on a dev box or home server and open them from my laptop?', + answer: [ + '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.', + ], + }, { question: 'Is it free and open source?', answer: ['Yes. localhost is free and open source under the MIT license. There is no account and no backend, and it works offline.'], From 53472283f1e61ca25cee94ce58dcbd06ddbeba77 Mon Sep 17 00:00:00 2001 From: Marcel Losso Date: Thu, 5 Nov 2026 00:19:50 -0300 Subject: [PATCH 3/5] Add a guide to running dev servers on a dev box and opening them from a laptop --- site/src/content/guides/remote-dev-box.md | 61 +++++++++++++++++++++++ site/src/lib/guides.ts | 30 +++++++++++ 2 files changed, 91 insertions(+) create mode 100644 site/src/content/guides/remote-dev-box.md diff --git a/site/src/content/guides/remote-dev-box.md b/site/src/content/guides/remote-dev-box.md new file mode 100644 index 0000000..aa6f883 --- /dev/null +++ b/site/src/content/guides/remote-dev-box.md @@ -0,0 +1,61 @@ +The quickest way is one SSH connection that forwards the dev server's port to your laptop, and the server started in it: + +```sh +ssh -L 5173:localhost:5173 devbox +cd ~/code/acme-shop && pnpm dev +``` + +Now `http://localhost:5173` on your laptop is the dev server on the dev box. Add one `-L` per port (`-L 8000:localhost:8000` for the API), and run it inside `tmux` on the dev box so closing the laptop doesn't stop it. + +## Why run dev servers on another computer? + +A desktop under the desk, a home server or a cloud VM often has more cores and memory than a laptop, stays on, and keeps the fans quiet. Builds, databases and Docker run there; the laptop only runs the browser and the editor. With VS Code's or Cursor's SSH remote, the editor already works on the dev box, and only the dev servers are left. + +## How do I reach the dev box from anywhere? + +Any network SSH reaches works. [Tailscale](https://tailscale.com) is the easiest from outside the house: install it on both computers and sign in with the same account, and the dev box gets a name such as `devbox.tail1234.ts.net` that works from any network, without opening a port on the router. A LAN, a VPN or a jump host (`ProxyJump`) work the same way. + +Then give it a short name in `~/.ssh/config` on the laptop: + +``` +Host devbox + HostName devbox.tail1234.ts.net + User you + IdentityFile ~/.ssh/id_ed25519 +``` + +and a key, so SSH never asks for a password: + +```sh +ssh-keygen -t ed25519 # if you don't have a key yet +ssh-copy-id devbox +ssh devbox true # should ask nothing, and print nothing +``` + +If the key has a passphrase, `ssh-add` keeps it in the SSH agent. The first `ssh devbox` asks whether to trust the dev box's host key: check it, and answer `yes`. + +## Where does `ssh -L` fall short? + +- **One port per flag, decided in advance.** A Service that moves to another port, or a new one, means a new connection. +- **It stops with the terminal.** The forward lasts as long as that `ssh`; the dev server as long as its shell, unless tmux holds it. +- **You can't see what runs.** Which servers are up, which crashed and why, is in a tmux pane on another computer. +- **Starting them is still by hand.** The order, the commands and the environment are in your head, or in a script on the dev box. + +## How does localhost run them from your laptop? + +[localhost](/) is a free, open-source desktop app that remembers how each Project runs. A dev box you reach with `ssh` can be one of its [Machines](/docs/machines): its Projects show in the window on your laptop, below the laptop's own, and you start and stop them there. + +1. [Install localhost](/docs/install) on both computers, and add your Projects on the dev box, in its app as on any computer ([Quick start](/docs/quick-start)), or through a coding agent there with [`localhost mcp`](/docs/mcp). Projects on the dev box are edited on the dev box; the app there doesn't have to stay open. +2. Check that the laptop's `ssh` finds it. A command run over SSH doesn't read your interactive profile, so with zsh, set `PATH` in `~/.zshenv`, not `~/.zshrc`: + + ```sh + ssh devbox localhost --version + ``` + +3. On the laptop, open **Settings → Machines → Add Machine…**, pick `devbox` from the hosts of `~/.ssh/config`, and click **Add**. localhost connects first; if it can't, it says why and what to do next. +4. The dev box's Projects show in the sidebar under its name. **Start All**, Stop, Restart and logs work as for a Project on the laptop, and the state of each Service is there at a glance. +5. **Open in Browser** forwards the Service's Port to the same port on your laptop, through the same SSH connection, then opens it: `http://localhost:5173` there is `http://localhost:5173` here, so OAuth callbacks and cookies keep working. + +The Services run on the dev box, with its shell, its files and its Secrets; no Secret value reaches the laptop. Nothing new listens on either computer's network: Forwards are bound to the laptop's loopback only. If the connection drops, localhost makes it again on its own, without starting or stopping anything. Services you started from the laptop stop when the connection ends, unless localhost is open on the dev box. + +When something doesn't connect, [Machines](/docs/machines#when-it-cant-connect) lists every message localhost shows and what to do. diff --git a/site/src/lib/guides.ts b/site/src/lib/guides.ts index 8de280d..3f63c26 100644 --- a/site/src/lib/guides.ts +++ b/site/src/lib/guides.ts @@ -114,6 +114,36 @@ export const guides: Guide[] = [ }, ], }, + { + slug: 'remote-dev-box', + title: 'Run dev servers on a remote dev box and open them from your laptop', + seoTitle: 'Run dev servers on a remote dev box over SSH', + description: 'Run dev servers on a dev box or home server and open them from your laptop: ssh -L port forwarding, Tailscale and ~/.ssh/config, then one window for both.', + summary: 'Forward the port with ssh -L, reach the box over Tailscale, then start and open its Services from your laptop.', + label: 'Dev servers on a dev box', + faq: [ + { + question: 'How do I open a dev server running on another computer in my browser?', + answer: ['Forward its port over SSH: `ssh -L 5173:localhost:5173 devbox`, then open `http://localhost:5173` on your laptop. The forward lasts as long as that `ssh`. In localhost, Open in Browser makes the same forward for you, on the same port number.'], + }, + { + question: 'Do I need Tailscale?', + answer: ['No. localhost uses whatever `ssh devbox` uses: a LAN, a VPN, a jump host with `ProxyJump`, or Tailscale. Tailscale is the easy way to reach a computer at home from anywhere, without opening a port on the router.'], + }, + { + question: 'Does localhost open a port on the dev box?', + answer: ['No. It runs `localhost remote --stdio` over your SSH connection, and Forwards ride on that connection, bound to your laptop’s loopback only. Nothing new listens on either computer’s network.'], + }, + { + question: 'What happens to the dev servers when I close my laptop?', + answer: ['The Services you started from the laptop stop on the dev box once the connection ends, about 45 seconds after the laptop goes quiet, unless localhost is open on the dev box. When the laptop is back, localhost connects again on its own, without starting anything.'], + }, + { + question: 'Why does it say localhost isn’t installed on the dev box when it is?', + answer: ['A command run over SSH gets a non-interactive shell, which doesn’t read `~/.zshrc`. Put the folder holding `localhost` on the `PATH` in `~/.zshenv` (zsh) instead, and check with `ssh devbox localhost --version`.'], + }, + ], + }, { slug: 'how-to-run-my-project', title: 'How do I run this project again?', From 7426c8a669072e4e0cbcb5da0bf5d5b52640a8b1 Mon Sep 17 00:00:00 2001 From: Marcel Losso Date: Thu, 5 Nov 2026 00:21:19 -0300 Subject: [PATCH 4/5] Add a Machines section to the landing page, with its place in the directory and the structured data --- site/src/pages/index.astro | 90 +++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index a1f0a97..9b3fd48 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -34,6 +34,7 @@ const structuredData = [ 'An MCP server (localhost mcp) for Claude Code, Cursor, Codex and other coding agents', 'In-app updates that stop running Services only after asking', 'Projects on Windows itself or in a WSL 2 distro', + 'Projects of other computers (Machines) over the system’s ssh, opened in the browser through forwarded ports', 'A command palette and a keyboard shortcut for every action', ], license: 'https://opensource.org/licenses/MIT', @@ -62,8 +63,10 @@ const directory = [ { id: 'ports', label: 'Ports', text: 'Who is on :3000' }, { id: 'environment', label: 'Environment', text: 'Variables, Profiles, Secrets' }, { id: 'agents', label: 'Coding agents', text: 'Claude Code, Cursor, Codex' }, + { id: 'machines', label: 'Machines', text: 'Your dev box, over ssh' }, { id: 'local', label: 'Local-first', text: 'No account, nothing silent' }, { id: 'install', label: 'Install', text: 'macOS, Windows, Linux' }, + { id: 'faq', label: 'Questions', text: 'Docker, pm2, platforms' }, ]; const commands = [ @@ -250,6 +253,29 @@ const problems = [ +
+
+
+

Your dev box, in the same window.

+

+ Add a computer you reach with ssh as a Machine, and its Projects show below yours: start, stop, logs and Open in + Browser, which forwards the Port to the same one here. Over the SSH you already use, with your keys, ~/.ssh/config + and Tailscale; nothing new listens on the network. Its Services, configuration and Secrets stay on it. +

+ How Machines work +
+ +
+
+

Your machine, your rules.

@@ -299,7 +325,7 @@ const problems = [
-
+

Questions

@@ -509,7 +535,7 @@ const problems = [ } .card ul { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0; padding: 0; list-style: none; @@ -558,6 +584,11 @@ const problems = [ font-size: 14px; color: #545c66; } + @media (max-width: 1100px) { + .card ul { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } @media (max-width: 900px) { .card ul { grid-template-columns: minmax(0, 1fr); @@ -823,6 +854,61 @@ const problems = [ color: #3fb950; } + .machine-list { + padding: 12px; + font-size: 14px; + } + .side-head { + padding: 14px 10px 6px; + font-family: var(--font-display); + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-secondary); + } + .side-head:first-child { + padding-top: 4px; + } + .side-row { + display: flex; + align-items: center; + gap: 10px; + height: 34px; + padding: 0 10px; + border-radius: 7px; + } + .side-row.selected { + background: color-mix(in srgb, var(--accent) 16%, transparent); + } + .side-meta { + margin-left: auto; + font-size: 12.5px; + color: var(--text-secondary); + } + /* State by shape: a dot with a halo for Running, a hollow ring for Stopped. */ + .glyph { + flex: none; + width: 9px; + height: 9px; + border-radius: 50%; + border: 1.5px solid var(--state-stopped); + } + .glyph.running { + border-color: var(--state-running); + background: var(--state-running); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--state-running) 25%, transparent); + } + .forward { + margin-top: 12px; + padding: 12px 10px 4px; + border-top: 1px solid var(--separator); + font-size: 14px; + color: var(--text-secondary); + } + .forward strong { + color: var(--text); + } + .rules { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); From 0745adb31615fe28c09de38ecad2ce897a19c6aa Mon Sep 17 00:00:00 2001 From: Marcel Losso Date: Thu, 5 Nov 2026 00:21:42 -0300 Subject: [PATCH 5/5] Add the 0.5.0 changelog, unreleased: Machines --- site/src/content/changelog/0.5.0.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 site/src/content/changelog/0.5.0.md diff --git a/site/src/content/changelog/0.5.0.md b/site/src/content/changelog/0.5.0.md new file mode 100644 index 0000000..8ab5a9c --- /dev/null +++ b/site/src/content/changelog/0.5.0.md @@ -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.