中文

Three lanes,
one local proxy.

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.

rowt1 / 25
Why it exists

Two tunnels, one default route.

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.

The fight

Two utun devices, one routing table. Whoever connected last takes the default route — the other silently stops working.

What you actually want

A decision per connection, not per machine. google.com one way, jira.corp another, baidu.com a third.

rowt2 / 25
What it is

Not a tunnel. A proxy.

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 approwt
MechanismPacket tunnel (utun)Local proxy on a port
Routing tableClaims the default routeNever writes to it
Runs beside corp VPNNo — one winsYes, by construction
GranularityAll traffic, or a split you don't controlPer connection, per domain
Needs rootYesNo
Decides usingThe destination IPThe 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.

rowt3 / 25
What it is

Four lanes.

escape

Personal tunnel

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

corp

Into the corporate VPN

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

direct

Straight out

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

block

Sinkhole

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

rowt4 / 25
How it works

Architecture

How traffic reaches rowt, how it is classified, and where it leaves.

ON YOUR MAC Browsers & GUI apps Safari · Chrome · Slack CLI tools git · npm · curl · claude Raw sockets ping · ssh · overlay peers macOS system proxy http_proxy env vars never sees the proxy rowt proxy on rowt-proxy-on · rowt run rowt sing-box · mixed HTTP+SOCKS · 127.0.0.1:7890 sniff the hostname → lane classification escape corp direct block your VPS bound to en0 utunN corp VPN en0 physical NIC refused no DNS, no dial OS routing table corp VPN · Tailscale · en0 the corp lane is UNBOUND — it follows this table into the tunnel watch reload on network change collector bytes per domain → SQLite
rowt5 / 25
How it works

Lane classification

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.

# route.rules, in order sniff domain exact host → --domain entries domain_suffix longest match → the three lane files rule_set geosite:… → whole services ip_cidr corp ranges → from corp sync ip_cidr private/CGNAT → overlay peers rule_set ads → the broad blocklist final direct

Longest suffix wins across all lanes, so an explicit api.foo.com in escape beats foo.com in block — lane order never decides it.

Why hostname, not IP

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.

Ask it anything

rowt explain www.google.com www.google.com → ESCAPE matched: longest-match escape suffix 'google.com'

It mirrors the real rules, then adds a live HTTP check when the router is up.

rowt6 / 25
How it works

Three planes — and rowt owns two.

PlaneQuestionOwner
LaneWhich exit a proxied connection takesrowt
DNSWhich resolver answers a namerowt
OS routesWhich interface unbound traffic leaves bydeliberately not rowt

DNS follows the lane

  • corp names → the system resolver, so they get intranet answers
  • escape names → resolved VPS-side, so the exit sees what you see
  • everything else → DoH over the physical NIC

Why it refuses the third

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.

rowt7 / 25
Getting started

Onboarding, step by step.

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.

01

Install

brew install tanghong123/tap/rowt
02

Bring your servers in

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.

rowt server import --from shadowrocket rowt server import --from clash-verge # accumulates $EDITOR ~/.config/rowt/import-review.json rowt server import --apply

Or add a share link directly: rowt server add '<vless://…>'

03

Start it

Fetches sing-box if missing, probes the right mode, renders the config, starts the router, turns the proxy on.

rowt up
04

Pick a server

rowt ping # rank by latency rowt use JP # or: rowt use auto
05

Switch networks, then verify

Quit the old VPN, connect corp, and check a destination from each lane.

rowt explain www.google.com rowt status
06

Fill the lanes

escape ships pre-seeded (Google, GitHub, Wikipedia, X…). corp fills itself from the VPN's routes. Add what you need:

rowt escape add youtube.com googlevideo.com rowt escape add geosite:google # a whole service rowt corp add '*.intranet.example.com' 10.0.0.0/8 rowt block add ads.example.com

geosite:<name> pulls a maintained rule-set — every Google domain and ccTLD — instead of you enumerating suffixes. A specific entry always beats it.

rowt corp suggest # from DHCP search domains rowt corp sync # with the corp VPN UP — mirror its routes

Run corp sync once while connected; after that the watchdog repeats it every tick.

07

Let it manage itself

Install the watchdog: auto-reload on network change, recovery from a wedged or crashed tunnel, and a stale proxy cleared at login.

rowt watch install rowt watch status
rowt8 / 25
Getting started

Getting your terminal to use it.

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.

Per shell
eval "$(rowt shell-init)" # in ~/.zshrc, once rowt-proxy-on rowt-proxy-off

Both aliases come from rowt shell-init, which sets and clears the variables in this shell and installs tab-completion.

Per command
rowt run claude rowt run git pull

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.

Inspect
rowt proxy status rowt proxy env

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.

rowt9 / 25
Every day

The commands you actually use.

Constantly
rowt status
mode, server, proxy, reachability, config validity
rowt monitor
the live full-screen view
rowt escape add <d>
send a site through the tunnel
rowt reload
after switching Wi-Fi ↔ wired ↔ hotspot
Often
rowt explain <d>
which lane, and which rule matched
rowt direct errors
what failed direct — your escape candidates
rowt ping / use
rank servers, pin one or go auto
rowt connections
what's flowing right now, per lane
When something is odd
rowt audit
who changed what — you or the watchdog
rowt report
full diagnostic, secrets masked, shareable
rowt corp sync
re-mirror the corp VPN's live routes
rowt metrics top
heaviest domains over a window

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.

rowt10 / 25
The monitor

One screen, the whole picture.

rowt monitor — demo fixture
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   _ __ _____      __| |_   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   
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
1
2
3
4

Four regions, each covered next.

1 · identity band mode, active server, proxy, router health, sidecars — is it working?
2 · live connections what is flowing, per lane, and which rule sent it there
3 · errors & blocked what is failing over a rolling window — your escape candidates
4 · server health the pool, measured latency, and the active outbound

Everything derives from a 2-second tick, and nothing is mutated unless you press a control key.

rowt11 / 25
The monitor · region 1 of 4

Identity band — is it working?

rowt monitor — demo fixture
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   _ __ _____      __| |_   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   
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
identity band

The answer to "is anything wrong?" without reading any further.

● LIVE
breathing heartbeat — if it stops, the data is stale. Also reads DOWN, ERROR or PAUSED
mode
host / vm / local, and the interface in use
server
the active outbound and its measured latency
sys proxy
whether macOS is actually pointing at rowt
router
running state and CPU — a spinning router shows here first
collector · watch
whether history recording and the auto-reload watchdog are up

What you can do here

  • o toggles the system proxy — or click the sys proxy cell
  • p pauses sampling, so the screen holds still while you read
  • r re-probes every server now instead of waiting for the timer
rowt12 / 25
The monitor · region 2 of 4

Live connections — what is flowing.

rowt monitor — demo fixture
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   _ __ _____      __| |_   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   
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
live connections

A per-lane rate summary, then one row per host.

LANE
which exit this connection took
HOST:PORT
the sniffed destination
#
concurrent connections to it
UP · DOWN
bytes each way
RULE
why it went theredomain_suffix, domain, rule_set or final

That 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.

rowt13 / 25
The monitor · region 3 of 4

Errors & blocked — what is failing.

rowt monitor — demo fixture
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   _ __ _____      __| |_   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   
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
errors & blocked

Aggregated over a rolling window — w or [ ] cycles 5m → 10m → 1h → 24h → all.

transient
DNS failures — usually resolve themselves
persistent
timeout, reset, refused — these are your escape candidates
blocked
what the block lane and the ad rule-set sank

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.

rowt14 / 25
The monitor · region 4 of 4

Server health — where you go out.

rowt monitor — demo fixture
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   _ __ _____      __| |_   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   
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
server health

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 active server, pinned at the left edge so it never scrolls out of view
marquee
when the pool overflows the row it scrolls, and freezes the moment you select something
counts
total / up / down summarises the pool at a glance

What you can do here

  • select a chip — or click one
  • u switches the active outbound to it, immediately
  • r re-probes the whole pool now

The same thing as rowt ping and rowt use <tag>, without leaving the screen.

rowt15 / 25
The monitor · navigating

The keys are global.

Filtering, searching and windowing apply to the whole screen — you never have to focus a pane first.

rowt monitor — demo fixture
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   _ __ _____      __| |_   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   
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Lane filter set to escape: both panes scope to it at once. Press 0 to clear.

Narrow

f · 1 2 3 0cycle the lane filter, or jump to escape / corp / direct; 0 clears
/regex search over hosts, filtering both panes as you type
w · [ ]errors window: 5m → 10m → 1h → 24h → all

Move

↑↓ · j kmove the selection; the first press locks a row by domain
←→ · h lswitch pane, or pick a server chip
Tabcycle connections → errors → server strip

Other

ycopy the selected domain
ppause sampling
?the full key list, any time
rowt16 / 25
The monitor · history

Where did the bytes actually go?

rowt monitor — demo fixture
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   _ __ _____      __| |_   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   
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
▲ upload
Press v for ▲ upload, again for ▼ download. Errors and server health are untouched — only this pane flips.

A collector sidecar records bytes per domain and lane into a tiered SQLite store — 5-second buckets rolled up to a year.

v
flip the pane: live → ▲ upload → ▼ download
s
span: recent (1m/5m/1h/24h) → days → 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.

rowt metrics top 3600 # heaviest, last hour rowt metrics query "<SQL>" # read-only passthrough
rowt17 / 25
The monitor · acting

Confirmed, reversible edits.

rowt monitor — demo fixture
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   _ __ _____      __| |_   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         
Armed on i.ytimg.com. Nothing is written until you confirm — and Esc, or simply walking away, cancels it.
e c b d
route the locked domain → escape / corp / block / back to direct
E C B D
the same on the parent suffixx.y.z.comz.com, covering a whole service in one keystroke
↵ / key×2
apply · esc cancels · idle 10s cancels itself
u · o
switch server · toggle the system proxy — both immediate, no arming

Each 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.

rowt18 / 25
The monitor · reference

Everything, on one key.

rowt monitor — demo fixture
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
   _ __ _____      __| |_   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   
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Press ? at any time; esc closes it.

Worth knowing

mousewheel scrolls the list under the pointer; click a row, a lane, a window tab, a server chip, or the proxy cell
themesauto-detected from the terminal background; --theme light pins it
fallbackwith no config or no router it renders a demo fixture rather than an error screen
read-onlythe data path never mutates anything — only the confirmed control keys do
cheapIO and CPU scale with new data, not with log or pool size
rowt19 / 25
Runs itself

What it does without being asked.

Only watch needs a one-time install. The rest is on by default.

corp sync

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.

watch

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.

captive portals

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.

geosite

Add a whole maintained service — rowt escape add geosite:google — instead of enumerating suffixes. A specific entry always beats it.

metrics

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.

audit

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.

rowt20 / 25
Advanced

Three modes for three situations.

host

The common one

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.

vm

When binding is blocked

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.

local

Already outside

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.

Choosing

rowt up # auto-detect rowt up vm # force a mode rowt probe # which one works here?

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.

rowt21 / 25
Advanced

Moving it, and handing it over.

Take your setup to another machine

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.

rowt config list # what would go in rowt config export # → a .tgz rowt config import setup.tgz # restore + re-render

The bundle holds credentials and subscription tokens. Move it encrypted.

Let an agent drive it

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.

rowt skill install rowt skill status

When you need to ask a human

rowt report

Dependencies, configs, per-server reachability, DNS, through-proxy tests, log and audit tail — with secrets masked, written to a file you can hand over.

rowt22 / 25
Under the hood

Why you can trust it with your traffic.

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.

cli surface 292/292 identical lane edits 28/28 render matrix 23/23 canonical JSON classifier 10/10 lane AND reason

What that buys

  • a rewrite that cannot quietly change behaviour
  • bugs found in the original — the harness has caught real routing faults the shell had for months
  • a shadow mode that runs both and records any disagreement, on the live machine, before anything is cut over

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.

rowt23 / 25
Under the hood

By the numbers.

43klines, all told
222commits since 3 Jul 2026
81tagged releases
366Rust unit + integration tests
353differential parity cases
14shipped binaries

Where the code lives

Rust · crates18,928
Rust · monitor8,871
Bash · bin/rowt5,027
Parity harness4,266
Docs · markdown3,480
Python · legacy2,529

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.

How it is built

languages
Bash, Rust, a little Python, and jq for the original render
runtime
sing-box · SQLite (bundled) · ratatui for the TUI
workspaces
two — the CLI/core crates, and the monitor
gates
292 CLI · 28 lane · 23 render · 10 classifier · 25 sandbox scenarios
packaging
Homebrew tap; Apple Silicon pours 5 prebuilt binaries, Intel builds from source
zero-config
no daemon runs as root, and nothing writes the routing table

The port, phase by phase

0 · harness2 · classify + lanes 4 · the whole CLI6 · importers 1 · render — shadow window3 · watchdog — tick cutover 5 · Linux + tun mode

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.

rowt24 / 25

Start here.

brew install tanghong123/tap/rowt rowt onboard

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

rowt25 / 25
← → or space to move