The Problem

Starlink's built-in web app only shows live telemetry, and it stops being useful exactly when the connection drops. Users who want to know what happened during an outage, track usage over months, or see per-device data have to poll the dish manually or rely on Starlink's cloud account. Dishylink solves this by reading the dish and router directly over the LAN, recording everything locally, and rendering it in a desktop or browser UI.

What This Does

Dishylink is a Starlink monitoring and control suite: live stat tiles (throughput, latency, power draw, ping success, obstruction), charts with day/week/month history, a 3D sky-obstruction dome, per-device usage from router counters, and an outage/thermal event log. It also writes settings to the dish and router over the same LAN API, with optional authenticated Starlink cloud account integration.

The repo is a portfolio of 7 self-contained projects, not one codebase. The substantial pieces are src/ (261 files, the React UI), core/ (23 files, the dish/router client and alert engine), electron/ (11 files, the desktop shell), extension/ (20 files, the browser extension), and collector/ (18 files, the local historian daemon). landing/ is the marketing site.

How It Is Wired

Execution starts in electron/main.ts for the desktop app, which spawns the React UI from src/App.tsx. App is the most-connected module (30 imports, instability 0.97) — everything funnels through it. From there, core/dishClient.ts and core/routerClientUpdate.ts handle the actual gRPC-web calls to the dish and router over the LAN. core/alertEngine.ts evaluates alert definitions from core/alertDefinitions.ts and emits notifications via core/alertNotification.ts. The collector/historian.mts daemon persists time-series data to local JSONL files.

The import graph shows 320 internal modules with 551 edges and 5 modules in circular dependencies — src/lib/statDetails.ts, src/components/toolbar/AppToolbar.tsx, and src/components/dashboard/StatDetailPanel.tsx participate in cycles, which makes them harder to change in isolation. src/lib/format.ts is a hub (15 modules depend on it, zero outgoing imports), so churn there has wide blast radius.

How To Use It

git clone https://github.com/moses-y/Dishylink
cd Dishylink
npm install
npm run dev          # dev mode; electron main + React renderer
npm run build        # production build

The package.json implies npm. No environment variables are documented in the README; the app discovers the dish and router on the LAN automatically. Configuration lives in core/dishClient.ts (endpoint defaults) and electron/ for desktop-specific settings. The browser extension loads from extension/entrypoints/dashboard/index.html.

Real-World Use

A user with a flaky Starlink connection runs the desktop app on an always-on Mac mini. When the link drops at 3am, the app keeps recording locally because it reads the dish directly. The next morning, the user opens the outage log in src/components/alerts/ to see the thermal event that preceded the drop, checks the obstruction time-lapse in src/components/satellite/, and reviews per-device usage from the router's counters to see which client saturated the uplink.

Code Health & Issues

Static analysis found 35 findings (6 high, 29 medium) across 6 kinds:

  • High - Import cycle memberssrc/lib/statDetails.ts, src/components/toolbar/AppToolbar.tsx, src/components/dashboard/StatDetailPanel.tsx participate in circular imports. Break the cycle by extracting shared types.
  • High - Duplicated code blocks — 59 repeated 6-line blocks across 59 files, including core/alertEngine.ts, src/hooks/useDeviceAlerts.ts, and electron/cloud.ts. Extract shared helpers.
  • Medium - High branching densitysrc/lib/format.ts has 33 branch points over 92 lines; consider table/strategy dispatch.
  • Medium - Deep nestingsrc/hooks/useRouterNetwork.ts hits indentation depth 6; use early returns.
  • Medium - Hub modulessrc/lib/format.ts, src/lib/apiHost.ts, src/lib/dishMesh.ts are high-blast-radius; keep them stable and small.
  • Medium - Oversized filesrc/components/satellite/skyScene.ts is 660 lines; split by responsibility.

SDLC observations from the file structure: CI is present (GitHub Actions), tests exist (67 files), and a license is committed. The workflows declare no least-privilege GITHUB_TOKEN permissions, no Dependabot/Renovate config, no dependency vulnerability scan, and landing/public/satellite-view.mp4 is a 24.9MB binary that bloats every clone.

The Bottom Line

Dishylink is a genuinely useful tool for Starlink owners who want local, offline-capable monitoring — the LAN-first architecture is the right call for the use case. The codebase is TypeScript-heavy and well-tested, but the hub modules and duplicated logic will make larger changes slower than they need to be. Worth adopting if you need deep Starlink telemetry and don't want to depend on the cloud account.