rowt runs a personal VPN, your corporate VPN, and a plain direct connection at the same time on macOS — by not being a VPN at all.
Per-connection routing · a live TUI · nothing in the routing table.
You want Google through a personal VLESS server, the intranet through the corporate VPN, and Baidu straight out — simultaneously.
You can't. Shadowrocket and the corp client are both packet tunnels: each creates
a utun interface and each wants to own the default route. macOS lets exactly one win.
So the usual day is manual: connect corp, lose Google. Connect the personal VPN, lose the intranet. Toggle, repeat.
Two utun devices, one routing table. Whoever connected last
takes the default route — the other silently stops working.
A decision per connection, not per machine. google.com
one way, jira.corp another, baidu.com a third.
rowt runs sing-box as a plain userspace proxy on
127.0.0.1:7890. A proxy never touches the routing table, so it cannot fight the corp
client. Apps hand it a connection; it picks an exit.
| A typical VPN app | rowt | |
|---|---|---|
| Mechanism | Packet tunnel (utun) | Local proxy on a port |
| Routing table | Claims the default route | Never writes to it |
| Runs beside corp VPN | No — one wins | Yes, by construction |
| Granularity | All traffic, or a split you don't control | Per connection, per domain |
| Needs root | Yes | No |
| Decides using | The destination IP | The sniffed hostname, before DNS |
The trade: it only catches what respects the proxy. Raw ping,
ssh, and anything ignoring system proxy settings go around it — by design, and
documented rather than hidden.
VLESS / VMess / AnyTLS / hysteria2 to your own VPS. The socket is bound to the physical NIC, so it leaves via your home router even while the corp VPN holds the default route.
google.com · youtube.com · geosite:github
An unbound socket resolved by the system resolver — so it follows
the OS routing table straight into utunN. Fills itself; see corp sync.
*.intranet.example.com · 10.0.0.0/8
Bound to the physical NIC, bypassing both tunnels. The default for everything unlisted — local sites stay fast and un-tunnelled.
baidu.com · mirrors.debian.org · everything else
Refused instantly — no DNS, no dial. Kills the ad/telemetry retry storm that otherwise spins CPU on the direct lane.
doubleclick.net · google-analytics.com
How traffic reaches rowt, how it is classified, and where it leaves.
Top to bottom, first match wins.
One connection arrives. sing-box sniffs the destination hostname — before any DNS — and walks the rules top to bottom.
Longest suffix wins across all lanes, so an explicit api.foo.com in
escape beats foo.com in block — lane order never decides it.
A CDN address tells you nothing — the same IP serves a blocked site and a fine one. The name is the only thing that identifies the destination, and sniffing gets it before a resolver can be poisoned.
It mirrors the real rules, then adds a live HTTP check when the router is up.
| Plane | Question | Owner |
|---|---|---|
| Lane | Which exit a proxied connection takes | rowt |
| DNS | Which resolver answers a name | rowt |
| OS routes | Which interface unbound traffic leaves by | deliberately not rowt |
Writing the route table needs a persistent root daemon — and a root daemon must never run user-writable code. rowt is brew-installed and updated constantly: exactly the wrong shape for root. It consumes the table; it never writes it.
Do all of it with an existing VPN on — rowt up
downloads sing-box for you. Only switch to the corp VPN once it works.
rowt onboard prints this list with your progress at any time.
Import from the clients you already use — Shadowrocket, Clash Verge, V2Box, FlClash. Run it once per client: entries accumulate into one review file, each tagged with where it came from. Prune, then apply.
Or add a share link directly:
rowt server add '<vless://…>'
Fetches sing-box if missing, probes the right mode, renders the config, starts the router, turns the proxy on.
Quit the old VPN, connect corp, and check a destination from each lane.
escape ships pre-seeded (Google, GitHub, Wikipedia, X…). corp fills itself from the VPN's routes. Add what you need:
geosite:<name> pulls a
maintained rule-set — every Google domain and ccTLD — instead of you enumerating suffixes. A
specific entry always beats it.
Run corp sync once while connected;
after that the watchdog repeats it every tick.
Install the watchdog: auto-reload on network change, recovery from a wedged or crashed tunnel, and a stale proxy cleared at login.
The macOS system proxy covers browsers and GUI apps. CLI tools ignore it entirely —
they only read http_proxy / https_proxy / all_proxy. rowt gives
you three ways to bridge that.
Both aliases come from rowt shell-init,
which sets and clears the variables in this shell and installs tab-completion.
Probes which path actually reaches the internet right now — your shell's setting, the system proxy, rowt's port, then direct — and runs the command with the first that works. Refuses to run if none do.
What the system proxy is set to, whether it points at rowt, and the exact exports to paste somewhere else.
Why rowt run exists: with several proxy apps
around and networks changing all day, the right value keeps moving. Guessing wrong fails in confusing
ways — so it measures instead of assuming.
The loop that matters: rowt direct errors 10m
shows what just failed on the pass-through lane — timeouts and resets are usually blocked sites. Add
the real ones to escape and move on.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ _ __ _____ __| |_ MONITOR uptime 3h 15m │ │ | '__/ _ \ \ /\ / /| __| ● LIVE mode host · en0 server JP-Tokyo 42 ms │ │ | | | (_) \ V V / | |_ sys proxy on router running · 3% │ │ |_| \___/ \_/\_/ \__| collector on watch on │ │ │ ├─┤ live connections ├───────────────────────────────────────────────────────┬─┤ errors & blocked ├────────────────────────────┤ │ all ↑ 2.9 MB/s ↓ 3.5 MB/s 27 426.4M 1.9G │ aggregated over 5m [10m] 1h 24h │ │ escape ↑ 2.7 MB/s ↓ 3.5 MB/s 18 417.1M 1.3G │ transient 12 · 1 dom │ │ corp ↑ 144 KB/s ↓ 44 KB/s 5 8.1M 414.6M │ persistent 50 · 5 dom │ │ direct ↑ 5 KB/s ↓ 2 KB/s 4 1.2M 176.9M │ blocked 57 · 5 dom │ ├────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤ │ LANE HOST:PORT # UP DOWN RULE │ COUNT TYPE DOMAIN │ │ escape i.ytimg.com:443 5 8.0M 352.2M domain_suffix │ 15 timeout vpn.corp.example.com │ │ escape raw.githubusercontent.com:443 2 915K 96.8M domain_suffix │ 13 blocked googlesyndication.com │ │ escape fonts.gstatic.com:443 3 12.5M 43.1M domain_suffix │ 13 blocked doubleclick.net │ │ escape www.youtube.com:443 5 18.2M 427.7M domain_suffix │ 12 dns gateway.icloud.com │ │ escape api.anthropic.com:443 2 376.1M 414.4M domain_suffix │ 11 timeout dl.google.com │ │ escape en.wikipedia.org:443 1 1.4M 1.8M domain_suffix │ 11 blocked criteo.com │ │ corp jira.corp.example.com:443 5 8.1M 414.6M domain_suffix │ 10 blocked adnxs.com │ │ direct mirrors.debian.org:443 1 509K 128.6M final │ 10 blocked google-analytics.com │ │ direct dl.google.com:443 1 104K 47.8M final │ 10 reset x.com │ │ direct gateway.icloud.com:443 2 610K 519K final │ 8 timeout rr5.googlevideo.com │ │ escape netflix.com 0 0B 0B │ │ │ corp backups.corp.example.com 0 0B 0B │ │ │ escape mail.google.com 0 0B 0B │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├────────────────────────────────────────────────────────────────────────────┴─┤ server health ├───────────────────────────────┤ │ 10 servers · 9 up · 1 down │ │ ▶ JP-Tokyo 42 ms JP-Osaka 175 ms KR-Seoul 72 ms DE-Frankfurt 195 ms HK-1 82 ms SG-1 81 ms TW-Taipei 110 ms │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Four regions, each covered next.
Everything derives from a 2-second tick, and nothing is mutated unless you press a control key.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ _ __ _____ __| |_ MONITOR uptime 3h 15m │ │ | '__/ _ \ \ /\ / /| __| ● LIVE mode host · en0 server JP-Tokyo 42 ms │ │ | | | (_) \ V V / | |_ sys proxy on router running · 3% │ │ |_| \___/ \_/\_/ \__| collector on watch on │ │ │ ├─┤ live connections ├───────────────────────────────────────────────────────┬─┤ errors & blocked ├────────────────────────────┤ │ all ↑ 2.9 MB/s ↓ 3.5 MB/s 27 426.4M 1.9G │ aggregated over 5m [10m] 1h 24h │ │ escape ↑ 2.7 MB/s ↓ 3.5 MB/s 18 417.1M 1.3G │ transient 12 · 1 dom │ │ corp ↑ 144 KB/s ↓ 44 KB/s 5 8.1M 414.6M │ persistent 50 · 5 dom │ │ direct ↑ 5 KB/s ↓ 2 KB/s 4 1.2M 176.9M │ blocked 57 · 5 dom │ ├────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤ │ LANE HOST:PORT # UP DOWN RULE │ COUNT TYPE DOMAIN │ │ escape i.ytimg.com:443 5 8.0M 352.2M domain_suffix │ 15 timeout vpn.corp.example.com │ │ escape raw.githubusercontent.com:443 2 915K 96.8M domain_suffix │ 13 blocked googlesyndication.com │ │ escape fonts.gstatic.com:443 3 12.5M 43.1M domain_suffix │ 13 blocked doubleclick.net │ │ escape www.youtube.com:443 5 18.2M 427.7M domain_suffix │ 12 dns gateway.icloud.com │ │ escape api.anthropic.com:443 2 376.1M 414.4M domain_suffix │ 11 timeout dl.google.com │ │ escape en.wikipedia.org:443 1 1.4M 1.8M domain_suffix │ 11 blocked criteo.com │ │ corp jira.corp.example.com:443 5 8.1M 414.6M domain_suffix │ 10 blocked adnxs.com │ │ direct mirrors.debian.org:443 1 509K 128.6M final │ 10 blocked google-analytics.com │ │ direct dl.google.com:443 1 104K 47.8M final │ 10 reset x.com │ │ direct gateway.icloud.com:443 2 610K 519K final │ 8 timeout rr5.googlevideo.com │ │ escape netflix.com 0 0B 0B │ │ │ corp backups.corp.example.com 0 0B 0B │ │ │ escape mail.google.com 0 0B 0B │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├────────────────────────────────────────────────────────────────────────────┴─┤ server health ├───────────────────────────────┤ │ 10 servers · 9 up · 1 down │ │ ▶ JP-Tokyo 42 ms JP-Osaka 175 ms KR-Seoul 72 ms DE-Frankfurt 195 ms HK-1 82 ms SG-1 81 ms TW-Taipei 110 ms │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
The answer to "is anything wrong?" without reading any further.
sys proxy cell╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ _ __ _____ __| |_ MONITOR uptime 3h 15m │ │ | '__/ _ \ \ /\ / /| __| ● LIVE mode host · en0 server JP-Tokyo 42 ms │ │ | | | (_) \ V V / | |_ sys proxy on router running · 3% │ │ |_| \___/ \_/\_/ \__| collector on watch on │ │ │ ├─┤ live connections ├───────────────────────────────────────────────────────┬─┤ errors & blocked ├────────────────────────────┤ │ all ↑ 2.9 MB/s ↓ 3.5 MB/s 27 426.4M 1.9G │ aggregated over 5m [10m] 1h 24h │ │ escape ↑ 2.7 MB/s ↓ 3.5 MB/s 18 417.1M 1.3G │ transient 12 · 1 dom │ │ corp ↑ 144 KB/s ↓ 44 KB/s 5 8.1M 414.6M │ persistent 50 · 5 dom │ │ direct ↑ 5 KB/s ↓ 2 KB/s 4 1.2M 176.9M │ blocked 57 · 5 dom │ ├────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤ │ LANE HOST:PORT # UP DOWN RULE │ COUNT TYPE DOMAIN │ │ escape i.ytimg.com:443 5 8.0M 352.2M domain_suffix │ 15 timeout vpn.corp.example.com │ │ escape raw.githubusercontent.com:443 2 915K 96.8M domain_suffix │ 13 blocked googlesyndication.com │ │ escape fonts.gstatic.com:443 3 12.5M 43.1M domain_suffix │ 13 blocked doubleclick.net │ │ escape www.youtube.com:443 5 18.2M 427.7M domain_suffix │ 12 dns gateway.icloud.com │ │ escape api.anthropic.com:443 2 376.1M 414.4M domain_suffix │ 11 timeout dl.google.com │ │ escape en.wikipedia.org:443 1 1.4M 1.8M domain_suffix │ 11 blocked criteo.com │ │ corp jira.corp.example.com:443 5 8.1M 414.6M domain_suffix │ 10 blocked adnxs.com │ │ direct mirrors.debian.org:443 1 509K 128.6M final │ 10 blocked google-analytics.com │ │ direct dl.google.com:443 1 104K 47.8M final │ 10 reset x.com │ │ direct gateway.icloud.com:443 2 610K 519K final │ 8 timeout rr5.googlevideo.com │ │ escape netflix.com 0 0B 0B │ │ │ corp backups.corp.example.com 0 0B 0B │ │ │ escape mail.google.com 0 0B 0B │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├────────────────────────────────────────────────────────────────────────────┴─┤ server health ├───────────────────────────────┤ │ 10 servers · 9 up · 1 down │ │ ▶ JP-Tokyo 42 ms JP-Osaka 175 ms KR-Seoul 72 ms DE-Frankfurt 195 ms HK-1 82 ms SG-1 81 ms TW-Taipei 110 ms │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
A per-lane rate summary, then one row per host.
domain_suffix, domain,
rule_set or finalThat last column answers "why is this going through the tunnel?" without leaving the screen.
Rows greyed with 0B are dormant — seen recently, idle now. They keep
their place so a host you are watching does not vanish the moment it goes quiet.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ _ __ _____ __| |_ MONITOR uptime 3h 15m │ │ | '__/ _ \ \ /\ / /| __| ● LIVE mode host · en0 server JP-Tokyo 42 ms │ │ | | | (_) \ V V / | |_ sys proxy on router running · 3% │ │ |_| \___/ \_/\_/ \__| collector on watch on │ │ │ ├─┤ live connections ├───────────────────────────────────────────────────────┬─┤ errors & blocked ├────────────────────────────┤ │ all ↑ 2.9 MB/s ↓ 3.5 MB/s 27 426.4M 1.9G │ aggregated over 5m [10m] 1h 24h │ │ escape ↑ 2.7 MB/s ↓ 3.5 MB/s 18 417.1M 1.3G │ transient 12 · 1 dom │ │ corp ↑ 144 KB/s ↓ 44 KB/s 5 8.1M 414.6M │ persistent 50 · 5 dom │ │ direct ↑ 5 KB/s ↓ 2 KB/s 4 1.2M 176.9M │ blocked 57 · 5 dom │ ├────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤ │ LANE HOST:PORT # UP DOWN RULE │ COUNT TYPE DOMAIN │ │ escape i.ytimg.com:443 5 8.0M 352.2M domain_suffix │ 15 timeout vpn.corp.example.com │ │ escape raw.githubusercontent.com:443 2 915K 96.8M domain_suffix │ 13 blocked googlesyndication.com │ │ escape fonts.gstatic.com:443 3 12.5M 43.1M domain_suffix │ 13 blocked doubleclick.net │ │ escape www.youtube.com:443 5 18.2M 427.7M domain_suffix │ 12 dns gateway.icloud.com │ │ escape api.anthropic.com:443 2 376.1M 414.4M domain_suffix │ 11 timeout dl.google.com │ │ escape en.wikipedia.org:443 1 1.4M 1.8M domain_suffix │ 11 blocked criteo.com │ │ corp jira.corp.example.com:443 5 8.1M 414.6M domain_suffix │ 10 blocked adnxs.com │ │ direct mirrors.debian.org:443 1 509K 128.6M final │ 10 blocked google-analytics.com │ │ direct dl.google.com:443 1 104K 47.8M final │ 10 reset x.com │ │ direct gateway.icloud.com:443 2 610K 519K final │ 8 timeout rr5.googlevideo.com │ │ escape netflix.com 0 0B 0B │ │ │ corp backups.corp.example.com 0 0B 0B │ │ │ escape mail.google.com 0 0B 0B │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├────────────────────────────────────────────────────────────────────────────┴─┤ server health ├───────────────────────────────┤ │ 10 servers · 9 up · 1 down │ │ ▶ JP-Tokyo 42 ms JP-Osaka 175 ms KR-Seoul 72 ms DE-Frankfurt 195 ms HK-1 82 ms SG-1 81 ms TW-Taipei 110 ms │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Aggregated over a rolling window — w or [ ] cycles 5m → 10m → 1h → 24h → all.
The workflow: filter to the direct lane, look at persistent failures, and route the real ones to escape — without leaving the monitor.
Only failed connections are logged. An empty pane means no errors — not no traffic.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ _ __ _____ __| |_ MONITOR uptime 3h 15m │ │ | '__/ _ \ \ /\ / /| __| ● LIVE mode host · en0 server JP-Tokyo 42 ms │ │ | | | (_) \ V V / | |_ sys proxy on router running · 3% │ │ |_| \___/ \_/\_/ \__| collector on watch on │ │ │ ├─┤ live connections ├───────────────────────────────────────────────────────┬─┤ errors & blocked ├────────────────────────────┤ │ all ↑ 2.9 MB/s ↓ 3.5 MB/s 27 426.4M 1.9G │ aggregated over 5m [10m] 1h 24h │ │ escape ↑ 2.7 MB/s ↓ 3.5 MB/s 18 417.1M 1.3G │ transient 12 · 1 dom │ │ corp ↑ 144 KB/s ↓ 44 KB/s 5 8.1M 414.6M │ persistent 50 · 5 dom │ │ direct ↑ 5 KB/s ↓ 2 KB/s 4 1.2M 176.9M │ blocked 57 · 5 dom │ ├────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤ │ LANE HOST:PORT # UP DOWN RULE │ COUNT TYPE DOMAIN │ │ escape i.ytimg.com:443 5 8.0M 352.2M domain_suffix │ 15 timeout vpn.corp.example.com │ │ escape raw.githubusercontent.com:443 2 915K 96.8M domain_suffix │ 13 blocked googlesyndication.com │ │ escape fonts.gstatic.com:443 3 12.5M 43.1M domain_suffix │ 13 blocked doubleclick.net │ │ escape www.youtube.com:443 5 18.2M 427.7M domain_suffix │ 12 dns gateway.icloud.com │ │ escape api.anthropic.com:443 2 376.1M 414.4M domain_suffix │ 11 timeout dl.google.com │ │ escape en.wikipedia.org:443 1 1.4M 1.8M domain_suffix │ 11 blocked criteo.com │ │ corp jira.corp.example.com:443 5 8.1M 414.6M domain_suffix │ 10 blocked adnxs.com │ │ direct mirrors.debian.org:443 1 509K 128.6M final │ 10 blocked google-analytics.com │ │ direct dl.google.com:443 1 104K 47.8M final │ 10 reset x.com │ │ direct gateway.icloud.com:443 2 610K 519K final │ 8 timeout rr5.googlevideo.com │ │ escape netflix.com 0 0B 0B │ │ │ corp backups.corp.example.com 0 0B 0B │ │ │ escape mail.google.com 0 0B 0B │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├────────────────────────────────────────────────────────────────────────────┴─┤ server health ├───────────────────────────────┤ │ 10 servers · 9 up · 1 down │ │ ▶ JP-Tokyo 42 ms JP-Osaka 175 ms KR-Seoul 72 ms DE-Frankfurt 195 ms HK-1 82 ms SG-1 81 ms TW-Taipei 110 ms │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Every server in the pool with its probe latency, measured through the tunnel on a gentle cadence — real network work, done off the UI thread so it never stutters.
The same thing as rowt ping and
rowt use <tag>, without leaving the screen.
Filtering, searching and windowing apply to the whole screen — you never have to focus a pane first.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ _ __ _____ __| |_ MONITOR uptime 3h 15m │ │ | '__/ _ \ \ /\ / /| __| ● LIVE mode host · en0 server JP-Tokyo 42 ms │ │ | | | (_) \ V V / | |_ sys proxy on router running · 3% │ │ |_| \___/ \_/\_/ \__| collector on watch on │ │ │ ├─┤ live connections · escape ├──────────────────────────────────────────────┬─┤ errors & blocked · escape ├───────────────────┤ │ all ↑ 2.9 MB/s ↓ 3.5 MB/s 27 426.4M 1.9G │ aggregated over 5m [10m] 1h 24h │ │ escape ↑ 2.7 MB/s ↓ 3.5 MB/s 18 417.1M 1.3G │ transient 12 · 1 dom │ │ corp ↑ 144 KB/s ↓ 44 KB/s 5 8.1M 414.6M │ persistent 50 · 5 dom │ │ direct ↑ 5 KB/s ↓ 2 KB/s 4 1.2M 176.9M │ blocked 57 · 5 dom │ ├────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤ │ LANE HOST:PORT # UP DOWN RULE │ COUNT TYPE DOMAIN │ │ escape i.ytimg.com:443 5 8.0M 352.2M domain_suffix │ 15 timeout vpn.corp.example.com │ │ escape raw.githubusercontent.com:443 2 915K 96.8M domain_suffix │ 13 blocked googlesyndication.com │ │ escape fonts.gstatic.com:443 3 12.5M 43.1M domain_suffix │ 13 blocked doubleclick.net │ │ escape www.youtube.com:443 5 18.2M 427.7M domain_suffix │ 12 dns gateway.icloud.com │ │ escape api.anthropic.com:443 2 376.1M 414.4M domain_suffix │ 11 timeout dl.google.com │ │ escape en.wikipedia.org:443 1 1.4M 1.8M domain_suffix │ 11 blocked criteo.com │ │ escape netflix.com 0 0B 0B │ 10 blocked adnxs.com │ │ escape mail.google.com 0 0B 0B │ 10 blocked google-analytics.com │ │ │ 10 reset x.com │ │ │ 8 timeout rr5.googlevideo.com │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├────────────────────────────────────────────────────────────────────────────┴─┤ server health ├───────────────────────────────┤ │ 10 servers · 9 up · 1 down │ │ ▶ JP-Tokyo 42 ms │ JP-Osaka 175 ms KR-Seoul 72 ms DE-Frankfurt 195 ms HK-1 82 ms SG-1 81 ms TW-Taipei 110 ms │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ _ __ _____ __| |_ MONITOR uptime 3h 15m │ │ | '__/ _ \ \ /\ / /| __| ● LIVE mode host · en0 server JP-Tokyo 42 ms │ │ | | | (_) \ V V / | |_ sys proxy on router running · 3% │ │ |_| \___/ \_/\_/ \__| collector on watch on │ │ │ ├─┤ connections · ▲ upload · recent ├────────────────────────────────────────┬─┤ errors & blocked ├────────────────────────────┤ │ all 8K/s 8K/s 29.7M 714.0M │ aggregated over 5m [10m] 1h 24h │ │ escape 7K/s 7K/s 26.5M 636.0M │ transient 12 · 1 dom │ │ corp 656B/s 656B/s 2.4M 56.7M │ persistent 50 · 5 dom │ │ direct 247B/s 247B/s 889K 21.3M │ blocked 57 · 5 dom │ ├────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤ │ LANE HOST ↑1m ↑5m ↑1h ↑24h │ COUNT TYPE DOMAIN │ │ escape i.ytimg.com:443 0B/s 0B/s 0B 0B │ 15 timeout vpn.corp.example.com │ │ escape raw.githubusercontent.com:443 0B/s 0B/s 0B 0B │ 13 blocked googlesyndication.com │ │ escape fonts.gstatic.com:443 0B/s 0B/s 0B 0B │ 13 blocked doubleclick.net │ │ escape www.youtube.com:443 2K/s 2K/s 6.8M 163.3M │ 12 dns gateway.icloud.com │ │ escape api.anthropic.com:443 2K/s 2K/s 8.6M 206.7M │ 11 timeout dl.google.com │ │ escape en.wikipedia.org:443 0B/s 0B/s 0B 0B │ 11 blocked criteo.com │ │ corp jira.corp.example.com:443 0B/s 0B/s 0B 0B │ 10 blocked adnxs.com │ │ direct mirrors.debian.org:443 247B/s 247B/s 889K 21.3M │ 10 blocked google-analytics.com │ │ direct dl.google.com:443 0B/s 0B/s 0B 0B │ 10 reset x.com │ │ direct gateway.icloud.com:443 0B/s 0B/s 0B 0B │ 8 timeout rr5.googlevideo.com │ │ escape netflix.com 3K/s 3K/s 10.4M 250.0M │ │ │ corp backups.corp.example.com 656B/s 656B/s 2.4M 56.7M │ │ │ escape mail.google.com 185B/s 185B/s 667K 16.0M │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├────────────────────────────────────────────────────────────────────────────┴─┤ server health ├───────────────────────────────┤ │ 10 servers · 9 up · 1 down │ │ ▶ JP-Tokyo 42 ms │ JP-Osaka 175 ms KR-Seoul 72 ms DE-Frankfurt 195 ms HK-1 82 ms SG-1 81 ms TW-Taipei 110 ms │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
A collector sidecar records bytes per domain and lane into a tiered SQLite store — 5-second buckets rolled up to a year.
Selection is by domain, so it rides the flip — line a host up in the live view and read its whole history without losing it.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ _ __ _____ __| |_ MONITOR uptime 3h 15m │ │ | '__/ _ \ \ /\ / /| __| ● LIVE mode host · en0 server JP-Tokyo 42 ms │ │ | | | (_) \ V V / | |_ sys proxy on router running · 3% │ │ |_| \___/ \_/\_/ \__| collector on watch on │ │ │ ├─┤ live connections ├───────────────────────────────────────────────────────┬─┤ errors & blocked ├────────────────────────────┤ │ all ↑ 2.9 MB/s ↓ 3.5 MB/s 27 426.4M 1.9G │ aggregated over 5m [10m] 1h 24h │ │ escape ↑ 2.7 MB/s ↓ 3.5 MB/s 18 417.1M 1.3G │ transient 12 · 1 dom │ │ corp ↑ 144 KB/s ↓ 44 KB/s 5 8.1M 414.6M │ persistent 50 · 5 dom │ │ direct ↑ 5 KB/s ↓ 2 KB/s 4 1.2M 176.9M │ blocked 57 · 5 dom │ ├────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────┤ │ LANE HOST:PORT # UP DOWN RULE │ COUNT TYPE DOMAIN │ │▎escape i.ytimg.com:443 5 8.0M 352.2M domain_suffix │ 15 timeout vpn.corp.example.com │ │ escape raw.githubusercontent.com:443 2 915K 96.8M domain_suffix │ 13 blocked googlesyndication.com │ │ escape fonts.gstatic.com:443 3 12.5M 43.1M domain_suffix │ 13 blocked doubleclick.net │ │ escape www.youtube.com:443 5 18.2M 427.7M domain_suffix │ 12 dns gateway.icloud.com │ │ escape api.anthropic.com:443 2 376.1M 414.4M domain_suffix │ 11 timeout dl.google.com │ │ escape en.wikipedia.org:443 1 1.4M 1.8M domain_suffix │ 11 blocked criteo.com │ │ corp jira.corp.example.com:443 5 8.1M 414.6M domain_suffix │ 10 blocked adnxs.com │ │ direct mirrors.debian.org:443 1 509K 128.6M final │ 10 blocked google-analytics.com │ │ direct dl.google.com:443 1 104K 47.8M final │ 10 reset x.com │ │ direct gateway.icloud.com:443 2 610K 519K final │ 8 timeout rr5.googlevideo.com │ │ escape netflix.com 0 0B 0B │ │ │ corp backups.corp.example.com 0 0B 0B │ │ │ escape mail.google.com 0 0B 0B │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├────────────────────────────────────────────────────────────────────────────┴─┤ server health ├───────────────────────────────┤ │ 10 servers · 9 up · 1 down │ │ ▶ JP-Tokyo 42 ms │ JP-Osaka 175 ms KR-Seoul 72 ms DE-Frankfurt 195 ms HK-1 82 ms SG-1 81 ms TW-Taipei 110 ms │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ CONFIRM i.ytimg.com → press e again to apply
x.y.z.com →
z.com, covering a whole service in one keystrokeEach is a front-end to the exact rowt command you could have
typed. Edits are batched: one router reload fires ~7s after the last settles.
An over-broad entry turns
red and is refused — com is not a host, it is
every .com.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ _ __ _____ __| |_ MONITOR uptime 3h 15m │ │ | '__/ _ \ \ /\ / /| __| ● LIVE mode host · en0 server JP-Tokyo 42 ms │ │ | | | (_) \ V V / | |_ sys proxy on router running · 3% │ │ |_| \___/ \_/\_/ ╭───────────────────────────────────────────────────────────────────────────────╮ │ │ │ rowt monitor — keys │ │ ├─┤ live connections ├─│ │───────────────────────┤ │ all ↑ 2.9 MB/s ↓│ ↑↓ / j k move selection (locks the row) │ 5m [10m] 1h 24h │ │ escape ↑ 2.7 MB/s ↓│ ←→ / h l switch pane · select server chip │ │ │ corp ↑ 144 KB/s ↓│ Tab cycle focus (conns/errors/health) │ │ │ direct ↑ 5 KB/s ↓│ v flip pane · live / ↑ upload / ↓ download │ │ ├──────────────────────│ s span — metrics band (recent/days/year); from Live, opens ↑ upload │───────────────────────┤ │ LANE HOST:PORT │ f 1 2 3 0 lane filter / jump / clear │N │ │ escape i.ytimg.com:44│ / search hosts (regex) · esc clears │orp.example.com │ │ escape raw.githubuser│ w [ ] errors window (rolling) │esyndication.com │ │ escape fonts.gstatic.│ y copy the selected domain │eclick.net │ │ escape www.youtube.co│ e c b d route selected → escape/corp/ │ay.icloud.com │ │ escape api.anthropic.│ block / direct (key×2 = apply) │ogle.com │ │ escape en.wikipedia.o│ E C B D same, on the parent suffix │o.com │ │ corp jira.corp.exam│ (x.y.z.com → z.com) │.com │ │ direct mirrors.debian│ after ½s the entry turns editable: │e-analytics.com │ │ direct dl.google.com:│ type · ^w drop leading label · ↵ apply │ │ │ direct gateway.icloud│ u use the selected server │ooglevideo.com │ │ escape netflix.com │ o toggle the system proxy on/off │ │ │ corp backups.corp.e│ r re-probe servers now │ │ │ escape mail.google.co│ p pause sampling · esc cancel │ │ │ │ ? toggle this help · q quit │ │ │ │ │ │ │ │ observe + confirmed, reversible overrides. │ │ │ ╰───────────────────────────────────────────────────────────────────────────────╯ │ ├────────────────────────────────────────────────────────────────────────────┴─┤ server health ├───────────────────────────────┤ │ 10 servers · 9 up · 1 down │ │ ▶ JP-Tokyo 42 ms │ JP-Osaka 175 ms KR-Seoul 72 ms DE-Frankfurt 195 ms HK-1 82 ms SG-1 81 ms TW-Taipei 110 ms │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
--theme light pins itOnly watch needs a one-time
install. The rest is on by default.
Reads the routing table and mirrors your corp VPN's live CIDRs into the corp lane, so proxied traffic to those ranges rides the tunnel. Re-run on every watchdog tick — roughly every two minutes and on each network change — so it tracks a VPN that reconnects with different routes. Hand-added entries are never touched.
A LaunchAgent that reloads on every network change, probes the tunnel on a timer and recovers a wedged or crashed router, and clears a stale system proxy at login so rowt's effect never outlives a reboot.
Hotel and airport logins break when a proxy is on. rowt detects the portal, drops the proxy so the login page loads, and restores it once you are through.
Add a whole maintained service — rowt escape add geosite:google
— instead of enumerating suffixes. A specific entry always beats it.
The collector records per-domain bytes into a tiered store, 5s buckets to a year, queryable from the CLI or read in the monitor's flip view.
Every state-changing operation is journalled with who ran it — you or the
watchdog — including commands that hang, since BEGIN is written before the work.
sing-box runs on the Mac and the escape socket is bound to the physical NIC. Works whenever the corp client lets a bound socket out.
Some corp clients enforce with a packet filter, so binding is not enough. The tunnel moves into a bridged Lima VM with its own LAN address — the filter sees a different machine.
No tunnel at all, for when you are on an uncensored network. The escape lane's rules retarget to direct rather than disappearing, so they keep their precedence over broader block entries. No server needed.
rowt up with no argument probes: it tests whether a bound socket reaches the
internet with the corp VPN up, and picks accordingly. It also checks the GFW canaries over the
physical NIC — if they answer, you are already outside, and it chooses local.
Detection is most accurate with the corp VPN connected, so re-run it once after switching networks.
config export bundles only the source of truth — the server pool, subscriptions and the
three lane files. The rendered config, state and binaries are machine-specific and regenerate
themselves.
The bundle holds credentials and subscription tokens. Move it encrypted.
rowt ships an agent skill: link it and a coding agent can run the setup, diagnose a lane, or explain a routing decision using the same commands you would.
Dependencies, configs, per-server reachability, DNS, through-proxy tests, log and audit tail — with secrets masked, written to a file you can hand over.
rowt began as one large shell script. It is being ported to Rust incrementally, and the port is held to the original by a differential harness rather than by hope.
Every command runs in a sandbox that cannot touch the machine, through both implementations, and four artifacts are compared: stdout, exit status, the whole config tree, and the trace of every effectful command.
The same discipline shows up in the product: a lane entry that would match a whole TLD is refused, an armed edit expires, and every mutation is reversible and logged.
Python is on its way out — since 3.3.7
bin/rowt runs the Rust helpers and python@3.12 is no longer a dependency
on either architecture.
A strangler: every phase
ships on its own, behind its own gate, with the shell still the daily driver.
ROWT_IMPL=rust already hands a whole invocation to the Rust CLI — off by default until
the shadow windows close.
Everything else is discoverable from there —
rowt help, rowt <command> --help, and the checklist that tells you what to
run next.
Docs: README.md · DESIGN.md · rowt-monitor/DESIGN.md