# Tunlite
> **SSH tunnels for you and your Agent — kept alive.** Type the `-L`/`-R`/`-D` yourself, or
> just tell an AI Agent — tunlite builds the tunnel and keeps it connected.
**English** · [简体中文](README.zh-CN.md)
[](https://github.com/yuanyuanzijin/tunlite/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/tunlite)
[](https://www.npmjs.com/package/tunlite)
[](https://nodejs.org)
[](LICENSE)
Keeping SSH tunnels alive is a chore: pros juggle `autossh`, a `systemd` unit per tunnel,
and a pile of `-L`/`-R`/`-D` flags — and reconnect by hand when one drops; newcomers don't
even know where to start. **tunlite** folds it into one command: type it yourself, or just
tell an **AI Agent** in plain words — either way it builds the tunnel, keeps it alive, and
reconnects on its own. Pure Node.js, **zero dependencies**, wrapping the `ssh` you already
trust.
> 📖 **Full documentation → [tunlite.dev](https://tunlite.dev/)**
- **Agent-native** — `--json` + stable exit codes on every command, plus a bundled Agent skill: an AI Agent sets up, brings up, and troubleshoots tunnels end-to-end.
- **Zero third-party dependencies** — pure Node.js standard library; all it needs on the box is **Node ≥ 18** and the system `ssh` it wraps.
- **Auto-reconnect** — exponential backoff + jitter, keepalive, port health probes.
- **Start at login** — launchd (macOS) / systemd user service (Linux) / Task Scheduler (Windows — beta).
- **Passwordless setup** — connects directly if keys already work; installs your key only if needed.
- **Three forward types** — local `-L`, remote `-R`, dynamic SOCKS `-D`.
## For Agents
An **AI Agent** is a first-class user. Ask one in plain language and it drives `tunlite`
through the same `--json` surface you would — branching on exit codes, not scraping prose:
```text
you ▸ "Forward the Postgres on app01 to my laptop."
Agent ▸ tunlite add pg --to deploy@app01 -L 5432:localhost:5432 --json → {"ok":true,…}
Agent ▸ tunlite enable pg --json → exit 4 · needs-auth
Agent ▸ tunlite setup-key deploy@app01 → key installed
Agent ▸ tunlite enable pg --json → {"state":"connected"} · exit 0
Agent ▸ "Done — localhost:5432 reaches app01's Postgres; the daemon keeps it alive."
```
The bundled [`skill/ssh-tunnel`](skill/ssh-tunnel/SKILL.md) (installed by `tunlite install
skill`) tells an Agent exactly how: `--json`, branching on exit codes, and handling
`needs-auth`.
`tunlite monitor` gives you a live, top-style dashboard — every tunnel's state at a
glance, with the daemon auto-reconnecting a dropped one in front of you:
## Why tunlite?
If you keep a few SSH tunnels running — a reverse tunnel to a homelab box, a SOCKS
proxy through a bastion, a port-forward to a staging database — you've probably wired
up `autossh` plus a `systemd`/`launchd` unit for each, and memorized which
`-L`/`-R`/`-D` flag goes where. tunlite folds all of that into one declarative CLI on
top of the `ssh` you already trust: named tunnels a daemon keeps alive and the OS
restarts at boot — no new server, no account, no protocol. And because every command
is `--json` with stable exit codes, an Agent drives the exact same surface you do.
| | tunlite | autossh | plain `ssh -L/-R/-D` | sshuttle | frp · bore · chisel | ngrok |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| Agent-friendly (`--json`, stable exit codes) | ✅ | ❌ | ❌ | ❌ | ❌ | partial |
| Wraps your system `ssh` (keys, jump hosts, `ssh_config`) | ✅ | ✅ | ✅ | partial | ❌ own protocol | ❌ own service |
| Named, declarative tunnels | ✅ | ❌ | ❌ | ❌ | ✅ config | ✅ |
| Auto-reconnect (backoff, keepalive, health) | ✅ | basic | ❌ | ❌ | ✅ | ✅ |
| Start at login (launchd/systemd/Task Scheduler) | ✅ | DIY | DIY | DIY | DIY | ✅ |
| Local **+** remote **+** dynamic SOCKS | ✅ | ✅ | ✅ | transparent proxy | varies | varies |
| Zero deps · no server to run · self-hosted | ✅ | needs autossh | ✅ | needs python | needs a server | hosted/paid |
## Install
Prerequisite: **Node ≥ 18** and the system `ssh`, both on your PATH.
```bash
# Recommended — fetch + anchor (no global npm needed)
npx tunlite install
# Or a curl one-liner (just curl/wget + tar + node)
curl -fsSL https://raw.githubusercontent.com/yuanyuanzijin/tunlite/master/bootstrap.sh | sh
# Windows (PowerShell) — beta
irm https://raw.githubusercontent.com/yuanyuanzijin/tunlite/master/bootstrap.ps1 | iex
```
`tunlite install` copies the runtime to a fixed directory and writes a launcher that
**pins node's absolute path** (so switching nvm/fnm versions won't break it), then asks
whether to register login autostart, install the Agent skill, and enable shell
completion. Pass `-y` to say yes to all without prompting (for scripts/CI); with no
`-y` and no terminal it just anchors. To set up one piece on its own, use
`tunlite install service` / `install skill` / `install completion`. It also writes a
short `tun` alias when that name is free. **Windows (autostart, launcher, PATH) is
beta** — macOS/Linux are the CI-tested platforms.
## Quick start
```bash
# ssh-native forward flags (repeatable — one tunnel can carry several):
tunlite add web --to me@host -L 8080:localhost:80 # reach the server's :80 at localhost:8080
tunlite add rev --to me@host -R 9000:localhost:3000 # expose local 3000 as server:9000
tunlite add socks --to me@host -D 1080 # SOCKS5 proxy (local 1080)
tunlite status # aligned table: NAME STATE HOST TYPE ROUTE PID UP RESTARTS
tunlite logs web -f # follow logs
tunlite doctor # health check: why a tunnel won't connect
```
> **Upgrading from 0.9.x?** 0.10.0 leans into native `ssh`, so a couple of commands take a
> new shape — while your existing tunnel config keeps working, untouched.
> - **Forwards now speak ssh's own flags** — `add web --to me@host -L 8080:localhost:80 -D 1080`
> (repeatable; `set ` edits them in place). The earlier `add local/remote/dynamic`
> form gives way to this.
> - **Switching a tunnel on and off is now `enable` / `disable`** (it was `up`/`down`), each
> naming what it acts on — a name, `--tag