A laptop that manages itself across metered and free connections — so you never have to think about which link you're on.
netmode is a single-machine multi-link manager for macOS. It meters every connection a laptop uses, scores them, and switches between them automatically — with one overriding rule it enforces on its own:
At home, ride the metered phone hotspot and never touch the Starlink (Starlink is reserved for the TV). Away, use anything — free Wi-Fi first, then the phone, then Starlink. You unplug, walk around, leave, come back — and it just follows.
The whole point is captured in one user sentence that drove the design:
"Why do I need to be thinking about this still? Why isn't this seamless?"
netmode's job is to make that question go away.
- What it does
- Quick start
- The seamless rule
- Commands
- Modes
- Metering
- How it senses home vs away
- Architecture
- Requirements
- Privacy
- Contributing
- License
- Meters each link separately — the phone hotspot and Starlink have different caps and different billing reset days; "free" Wi-Fi (café, friend's place) is tracked for information but never billed.
- Knows where it is. A learned Wi-Fi fingerprint plus live signals tell it home from away — no GPS, no cloud, no setup.
- Knows if the phone is here. USB tether, hotspot SSID visibility, and Bluetooth proximity together answer "is my phone in the room?" — so it never clings to a hotspot that walked out the door.
- Switches on network change, not on a timer. A launchd
WatchPathsagent fires the moment macOS rewrites its network config — no always-on daemon. - Keeps the hotspot alive while you work — a tiny gated heartbeat defeats iOS's ~90 s idle-disconnect, but only when the phone is your link and you're at the Mac.
- Surfaces everything — a text status, a
doctordiagnostic, a JSON API, and a local web dashboard.
# 1. clone into place (this repo IS ~/Library/Application Support/netmeter)
git clone git@github.com:4444J99/netmode.git "$HOME/Library/Application Support/netmeter"
cd "$HOME/Library/Application Support/netmeter"
# 2. create your private config from the example (real SSIDs live here; it's git-ignored)
cp config.example config
$EDITOR config
# 3. prove the logic is sound (no network changes are made)
bash netmode.sh selftest
# 4. see where you stand
bash netmode.sh # live status + "next move"
bash netmode.sh doctor # full diagnostic
# 5. make it seamless — fire on every network change (no daemon)
bash netmode.sh trigger install
# optional: a 5-minute safety tick + the hotspot keep-alive
bash netmode.sh keepalive onA convenience netmode alias:
alias netmode='bash "$HOME/Library/Application Support/netmeter/netmode.sh"'seamless is the default mode. It dispatches on location:
| Where | Behavior |
|---|---|
| home | Ride the iPhone (USB or its Wi-Fi). Remove Starlink from preferred networks so macOS won't auto-join it. Never Starlink — even over cap, even if the phone leaves (it holds offline and notifies instead). |
| away | The cascade: free/known Wi-Fi → iPhone hotspot → Starlink. Anything goes. |
| unknown | Conservative auto behavior until a signal arrives. |
Because netmode rewrites macOS's own preferred-network order to match, the OS does the right thing between ticks too. That's the seamless part.
Manual override when sensing is wrong:
netmode here # pin "home" for a few hours
netmode out # pin "away" for a few hours (e.g. a café where sensing lags)netmode live status + "next move"
netmode watch [secs] auto-refreshing status
netmode why explain the current decision + the inputs behind it
netmode doctor full diagnostic (links, scores, location, schedule, service order)
netmode report usage + projection
netmode export usage history as CSV
netmode setusage L GB anchor link L (phone|starlink) to a provider-reported GB number
netmode here | out pin location home/away (overrides sensing for a few hours)
netmode overnight ... set/clear the overnight Starlink window (see Modes)
netmode keepalive ... on | off | status — hotspot idle-drop heartbeat
netmode trigger ... install | uninstall the network-change LaunchAgent (no daemon)
netmode dish [--raw|--json] read-only Starlink dish stats (on the Starlink LAN)
netmode menubar install drop a SwiftBar/xbar plugin (opt-in, no daemon)
netmode json machine status (used by the dashboard)
netmode selftest assert decision logic & invariants (no network changes)
netmode ui open the dashboard (http://127.0.0.1:8765)
netmode version print the version
| Mode | Behavior |
|---|---|
seamless |
default — location-aware (see above) |
auto |
burn the phone first, auto-fall to Starlink at the cap |
failover |
cable = phone, unplug = Starlink (instant) |
anchor |
Starlink first even when the cable's plugged (saves the phone) |
solo |
phone only, never Starlink (Wi-Fi hotspot ok; offline if the phone's gone) |
ladder |
phone-USB → phone-Wi-Fi → Starlink |
Overnight anchor — a home-only, time-gated sub-rule of seamless (not a mode swap), so it can never strand you when you're traveling:
netmode overnight 0100 0700 # let backups ride Starlink overnight at home
netmode overnight off # disable
netmode overnight # showCaps are shared across devices, so the provider's number is ground truth:
netmode setusage starlink 803 # anchor Starlink to the figure on the bill/app
netmode setusage phone 84
netmode report # usage + projection per linkEach metered link tracks its own cycle; "free" Wi-Fi is logged but never billed.
Two pure decision functions fused from cheap signals (see docs/ARCHITECTURE.md):
- Phone presence →
tethered | near | gonefrom USB link, hotspot SSID visibility, and Bluetooth proximity. - Location →
home | away | unknownfrom Starlink visibility, an ambient Wi-Fi fingerprint learned automatically at home, and the current connection class — with asymmetric hysteresis (leaving home, which re-permits Starlink, demands more proof than returning) and a strong-away fast-path (a healthy foreign scan leaves home in one sample, so you're never stranded at a café).
┌──────────── sensing (pure fns + cheap IO) ────────────┐
│ phone_present: usb · ssid · bluetooth │
│ location: starlink-vis · fingerprint · conn │
└───────────────────────────┬───────────────────────────┘
▼
seamless policy (home | away | unknown)
▼
apply: service order · preferred-network rewrite · single-flight switch
▲
fired by: WatchPaths net-change LaunchAgent + 5-min tick
Everything lives in netmode.sh (engine) and netui.py (dashboard). Runtime
state and your real config stay on disk, git-ignored. No second process, no cloud.
See docs/ARCHITECTURE.md and
docs/DESIGN-PRINCIPLES.md.
- macOS (uses
route,networksetup,ipconfig,system_profiler,ioreg,launchctl) bash,python3(dashboard + JSON helpers)grpcurl(optional) — only fornetmode dishread-only Starlink stats- No
sudo. No Wi-Fi password is ever written to disk.
This repository is private by design — netmode is bespoke personal infrastructure,
and your real SSIDs, learned fingerprint, and usage live only in git-ignored files.
Before ever making a fork public, run scripts/sanitize-for-public.sh
and read SECURITY.md.
It's a single-owner project, but it's run like a real one — feature branches, conventional commits, CI, and PRs. See CONTRIBUTING.md.
See LICENSE. Personal project; not licensed for redistribution.