# localhost.run — instructions for AI agents

You are connecting a tunnel so a local app is reachable on the internet at a given subdomain.
localhost.run uses plain SSH. Do NOT install any client, binary or package.

## Inputs you need

- `SUBDOMAIN` — the full hostname to serve, e.g. `my-app.lhr.rocks` or `app1.example.com`.
- `PORT` — the local port the app listens on, e.g. `3000`.
- An SSH private key whose public key is registered on the localhost.run account that owns `SUBDOMAIN`.

If any input is missing, ask the user. Never invent a subdomain.

## 1. Preconditions

1. Confirm the app answers locally:
   `curl -sS -o /dev/null -w "%{http_code}" http://127.0.0.1:PORT/`
   Any HTTP status means it is up. Connection refused means start the app first.
2. Confirm `ssh` exists: `ssh -V`.
3. Confirm a key exists: `ls ~/.ssh/id_ed25519.pub ~/.ssh/id_rsa.pub`.
   If none exists, generate one with `ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519`,
   then STOP and ask the user to add the contents of `~/.ssh/id_ed25519.pub` at
   https://admin.localhost.run and to add `SUBDOMAIN` to their account.
   `.lhr.rocks` subdomains need no DNS. Own domains need DNS set up (see https://localhost.run/docs/custom-domains).

## 2. Connect

Run this as a long-lived background process (it must keep running for the tunnel to stay up):

```bash
ssh -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -o StrictHostKeyChecking=accept-new \
  -R SUBDOMAIN:80:localhost:PORT plan@localhost.run -- --output json
```

- `plan@` selects your account plan; required for custom domains when several keys exist.
- Add `-i /path/to/key` if the key is not in the default location.
- `--output json` makes event messages machine-readable. Read stdout to confirm the tunnel is connected and to see the address.
- If the app only listens on IPv4, use `127.0.0.1:PORT` instead of `localhost:PORT`.
- Use `-R SUBDOMAIN:443:localhost:PORT` only if the app terminates TLS itself (TLS passthru). Default to 80.

## 3. Verify

```bash
curl -sS -o /dev/null -w "%{http_code}" https://SUBDOMAIN/
```

HTTPS is provided automatically; your app receives plain HTTP with `X-Forwarded-*` and `Forwarded` headers.
Report `https://SUBDOMAIN` to the user.

## 4. Keep it alive

To auto-reconnect, wrap the command in a loop with a sleep:

```bash
while true; do ssh -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -R SUBDOMAIN:80:localhost:PORT plan@localhost.run; sleep 5; done
```

Or use `autossh -M 0` with the same options.

## Troubleshooting

| Symptom | Action |
| --- | --- |
| `Permission denied (publickey)` | Key not registered, wrong key, or wrong user. Use `plan@`, pass `-i`, ask the user to register the key. |
| Domain not accepted / random domain assigned | `SUBDOMAIN` is not on the account, or DNS/TXT verification is incomplete. Ask the user to check the admin portal. |
| Error page at the URL | Local app is not reachable on `PORT`. Re-check step 1; try `127.0.0.1`. |
| Links point to `localhost:PORT` | The app's framework needs reverse proxy / trusted proxy settings. |
| Connection drops | Keep `ServerAliveInterval=60` and use the reconnect loop. |

## Limits

- Up to 5 simultaneous tunnels per custom domain plan.
- No subdomain given and only a quick share needed? A free tunnel works without an account:
  `ssh -R 80:localhost:PORT nokey@localhost.run` (the domain is random and changes).

Full docs: https://localhost.run/docs/ — Help: help@localhost.run
