Remote→Local browser open bridge.
bob lets a process running on a remote machine ask your local machine to open a URL in your local browser.
MVP flow:
Remote app/tool -> bob CLI -> forwarded endpoint -> bobd local daemon -> local browser
Where:
bobis the remote CLIbobdis the local daemonforwarded endpointis the URL visible from the remote side that already reaches localbobd
Example:
local bobd listens on 127.0.0.1:7331
SSH makes that reachable on the remote side as 127.0.0.1:17331
remote bob sends POST http://127.0.0.1:17331/open
This repository now contains a minimal Go MVP scaffold:
bob <url>bob initbob open <url>bob code-server [path]bob doctorbob tunnel up/status/downbobd servebobd init
Current limitations:
bob openstill does not create the first SSH session from the remote side- duplicate suppression is not implemented yet
- default policy is localhost-only
Build with the default version:
just buildBuild with an explicit version:
VERSION=v0.5.0 just buildInstall to ~/.local/bin:
just installInstall to a custom directory:
just install BINDIR=/custom/binOr build directly with Go:
go build ./...Check CLI versions:
bob version
bobd version- Git tags use the
vX.Y.Zformat. - Application versions follow SemVer.
- Repository builds default to
v0.5.0.
Example release build:
VERSION=v0.5.0 just build-binaries
git tag v0.5.0Include commit and build date if needed:
VERSION=v0.5.0 COMMIT=$(git rev-parse --short HEAD) DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) just build-binariesThe common setup is:
- local machine: run
bobdand open the browser - remote machine: run
bobfrom dev tools / shells - SSH reverse forwarding makes remote
127.0.0.1:17331reach localbobd
- Generate a shared token and local daemon config:
bobd initThis writes the local daemon config to ~/.config/bob/bobd.json (or $XDG_CONFIG_HOME/bob/bobd.json) with 0600 permissions and prints a remote bob.json snippet.
- On the local machine:
bobd serve --tunnel-name devbox --ssh user@remote-host- On the remote machine, initialize
bobusing the token printed bybobd init:
bob init --token <shared-token> --session devboxThis writes ~/.config/bob/bob.json with 0600 permissions. You can also pass --endpoint, --timeout, and --force:
bob init --token <shared-token> --endpoint http://127.0.0.1:17331 --session devbox --timeout 5sAlternatively, create ~/.config/bob/bob.json manually using the snippet printed by bobd init:
{
"endpoint": "http://127.0.0.1:17331",
"token": "<shared-token>",
"session": "devbox"
}Then verify and open URLs:
bob doctor
bob open http://127.0.0.1:5173bob code-server [path] opens an already-running remote code-server instance in your local browser through the existing loopback mirror flow. It does not start code-server.
Run code-server on the remote machine bound to loopback, for example 127.0.0.1:8080, then run:
bob code-server .
bob code-server ~/repo --port 65508The command resolves the folder to an absolute path, builds http://127.0.0.1:<port>/?folder=<path>, and sends it through the normal bob open /v2/open flow. Because the URL is loopback, session / BOB_SESSION must match an active tunnel session so bobd can mirror the remote port locally.
If ~/.config/bob/bob.json already exists, bob init refuses to overwrite it. To intentionally update it, delete the config file first or run bob init --force --token <shared-token> --session <name> ....
If ~/.config/bob/bobd.json already exists, bobd init refuses to overwrite it to avoid accidentally rotating the token and breaking remote machines. To intentionally regenerate the token, delete the config file first or run:
bobd init --forceIf you prefer to keep the SSH session separate, you can still create the port forward explicitly.
Manual SSH example:
ssh -R 17331:127.0.0.1:7331 user@remote-hostOr via bob tunnel on the local machine:
bob tunnel up devbox --ssh user@remote-hostImportant:
BOB_ENDPOINTonly points tobobd.session/BOB_SESSIONshould match the tunnel name, e.g.devbox.- Loopback app URLs can be mirrored automatically after the control tunnel exists.
- If the same local port is busy,
bobdmay allocate another local port and rewrite the opened URL.
If automatic opening fails, bob open prints the URL so the user can open it manually.
bob and bobd read JSON config files from $XDG_CONFIG_HOME/bob/, falling back to ~/.config/bob/.
Environment variables override config file values, which is useful for temporary overrides, CI, and containers.
endpoint: the remote-visible URL for the local daemon. With the default SSH tunnel this ishttp://127.0.0.1:17331on the remote machine.session: the tunnel name used to mirror remote loopback URLs back to the local browser. For remote-hosted dev servers, set this during init withbob init --session <name>.
~/.config/bob/bob.json:
{
"endpoint": "http://127.0.0.1:17331",
"token": "<shared-token>",
"session": "devbox",
"timeout": "5s",
"codeServer": {
"port": 8080
}
}~/.config/bob/bobd.json:
{
"bind": "127.0.0.1:7331",
"token": "<shared-token>",
"localhost_only": true
}BOB_ENDPOINToverridesendpoint(default:http://127.0.0.1:17331)BOB_TOKENoverridestokenBOB_SESSIONoverridessession; required for auto-mirror, set to the tunnel nameBOB_TIMEOUToverridestimeout(default:5s)BOB_CODE_SERVER_PORToverridescodeServer.portforbob code-server(default:8080)
BOBD_BINDoverridesbind(default:127.0.0.1:7331)BOBD_TOKENoverridestoken(required via env or config)BOBD_LOCALHOST_ONLYoverrideslocalhost_only(default:true)
bobd serve flags:
--tunnel-nametunnel session name to create on startup--sshSSH target for that session--remote-bob-portremote loopback port forBOB_ENDPOINT(default:17331)--local-bobdlocal bobd address forwarded over SSH (default:BOBD_BIND)