Speed up downloads by combining multiple network connections in parallel
See the codeA fast download manager for Windows, macOS, and Linux that speeds up downloads by pulling chunks in parallel across multiple network connections at the same time.
For example, if your computer has:
Plexo can utilize all of them simultaneously to download the same file.
https://github.com/user-attachments/assets/e57728f4-fb63-441f-839c-174eef954b17
macOS does not natively provide an RNDIS driver, so Android phones with USB tethering enabled won't appear as network interfaces out of the box (this is also why legacy kernel extensions like HoRNDIS stopped working on Apple Silicon and modern macOS).
To use your Android phone's connection over USB, install TetherKit — a kext-free, user-space RNDIS driver.
See Using a USB-tethered Android phone for setup instructions.
Plexo can only route traffic through connections that your operating system recognizes as network interfaces.
A single TCP connection rarely saturates your actual bandwidth. Even when your computer has multiple active networks — such as Wi-Fi and a tethered mobile phone — the operating system routes all traffic through a single default gateway, leaving the other interfaces completely idle.
Plexo changes that: it splits the file into independent byte ranges and downloads them simultaneously through distinct physical network interfaces.
┌── Wi-Fi (IP: 192.168.1.40) ────┐
│ │
File ──→ Split ─────┼── Ethernet (IP: 10.0.0.12) ────┼──→ Assembled File
│ │
└── USB Tether (IP: 172.20.10.3) ┘
Multiple networks → concurrent HTTP range requests → aggregated bandwidth
Get-NetAdapter and macOS hardware ports via networksetup so Wi-Fi, Ethernet, tethered iPhones, and Thunderbolt bridges are labeled by real device names instead of bare BSD names (en0, en6).part-N chunk files on disk.ETag and Last-Modified validators before resuming, refusing to resume (rather than corrupting the file) if the server-side file has changed.Instead of downloading a file linearly over a single socket, Plexo requests arbitrary slices of the file simultaneously across multiple physical network interfaces. Three core technical primitives make this work:
206 Partial Content)Most modern HTTP servers support byte-level slicing:
GET /ubuntu-26.04.1-desktop-amd64.iso HTTP/1.1
Host: releases.ubuntu.com
Range: bytes=8388608-16777215
Servers advertise this capability with the Accept-Ranges: bytes response header and reply with HTTP status 206 Partial Content. Because byte slices are stateless and independent, Plexo can request dozens of chunks at once, in any order, and stitch them together later.
Before starting a multi-connection download, Plexo sends a 1-byte ranged GET (Range: bytes=0-0), following any redirects:
HEAD request (which servers and CDNs frequently misreport), receiving a 206 Partial Content response conclusively proves that range requests are supported and functional.Content-Range / Content-Length), suggested filename (Content-Disposition), and cache validators (ETag and Last-Modified).200 OK (ignoring the Range header), Plexo falls back to a standard single-connection stream instead of failing.localAddressEvery active network interface on your computer has its own local IP address — Wi-Fi might be 192.168.1.40, while a USB-tethered phone is 172.20.10.3.
A standard TCP socket leaves interface selection to the operating system's routing table. However, Node.js allows outbound HTTP/HTTPS requests to explicitly bind to a specific local IP using the localAddress option:
https.request({
hostname: 'releases.ubuntu.com',
path: '/ubuntu-26.04.1-desktop-amd64.iso',
localAddress: '172.20.10.3', // Forces this connection through the USB tether
headers: {
Range: 'bytes=8388608-16777215'
}
})
This single option is Plexo's entire multi-network routing engine:
kext) or root privilegesIf you statically divide a 6 GB file into equal shares (e.g. 3 GB on Wi-Fi and 3 GB on mobile data), the total download speed is bottlenecked by the slower network.
Instead, Plexo uses a dynamic work-stealing queue:
Shared Pending Queue: [Chunk #4] [Chunk #5] [Chunk #6] [Chunk #7] [Chunk #8] ...
↑ ↑ ↑
Worker 1 Worker 2 Worker 3
(Wi-Fi) (Ethernet) (USB Tether)
Work distribution is dynamically proportional to each interface's real-time throughput. If one network slows down or disconnects, remaining workers continue draining the queue without stalled shares.
Each worker writes its assigned byte range directly to an isolated temporary file on disk (part-0, part-1, ... part-N).
Once the queue is drained and all chunk promises resolve:
part-N file sequentially into the final destination file using Node.js streams (createReadStream piped into createWriteStream with { flags: 'a' }).When you pause a download:
AbortController.part-N files remain cached on disk in a temporary directory.When you resume:
ETag and Last-Modified headers against the values recorded when the download started.part-N files are already complete on disk, skips them, and queues only the remaining chunks.Download manifests and partial data are stored under Plexo's application-data directory. If Plexo quits or crashes during a transfer, it restores that transfer as paused on the next launch. Explicitly cancelling or removing a download still deletes its partial data.
A chunk is the atomic unit of work in Plexo:
Range: bytes=START-END).part-N file in the download's temp directory.Chunk #0 → Range: bytes=0-8388607 → part-0 (Wi-Fi)
Chunk #1 → Range: bytes=8388608-16777215 → part-1 (Ethernet)
Chunk #2 → Range: bytes=16777216-25165823 → part-2 (USB Tether)
8 MB provides the optimal balance: large enough to minimize HTTP connection overhead and TLS handshakes, yet small enough to keep the work-stealing queue fluid, ensure fine-grained load balancing across mismatched connections, and keep retries cheap (a failed or stalled connection only discards at most 8 MB).
The progress grid provides a real-time visual map of the entire download.
Every 8 MB chunk maps 1:1 to its own square in the grid. Square #N directly corresponds to the Chunk #N badge shown in the active streams table, allowing you to cross-reference active connections with their location in the file.
Active Streams:
[Wi-Fi] → Chunk #4
[Ethernet] → Chunk #5
[USB Tether] → Chunk #6
Progress Grid:
[#1][#2][#3][#4][#5][#6][#7][#8]...
Plexo currently doesn't have pre-built releases, so you'll need to run it from source.
networksetup; Linux provides fallback interface detection and desktop network settings integration.Clone the repository:
git clone https://github.com/anmolkapil/plexo.git
cd plexo
Install dependencies:
npm install
Start the application in development mode:
npm run dev
Plexo includes an automated end-to-end test suite driven by Playwright:
npm run test:e2e:smoke # quick smoke tests
npm run test:e2e # full E2E test suite (including integrity and chaos tests)
See CONTRIBUTING.md for testing options and debugging flags.
To package Plexo as a standalone macOS application bundle:
npm run build:mac
The compiled application will be generated at:
dist/mac/Plexo.app
Note on Gatekeeper: The app is unsigned because it is not distributed with a paid Apple Developer certificate. However, because you compile it locally on your machine, macOS will not apply the quarantine flag (
com.apple.quarantine). Gatekeeper only quarantines files downloaded from the web (via browsers, curl, etc.), so your locally builtPlexo.appwill launch cleanly without quarantine warnings.
Run these commands from PowerShell in the project directory:
npm install
npm run build:win
The installer is generated at dist/plexo-1.0.0-setup.exe. For an unpacked app, run
npm run build:unpack and launch dist/win-unpacked/plexo.exe.
Local builds are unsigned.
Windows uses native window controls, Ctrl+V hints, File Explorer integration, and Windows Network Settings. Download filenames are normalized to Windows filename rules.
Enable USB tethering on your phone and check that its adapter appears in Windows Network Settings. Install the phone manufacturer's Windows driver if Windows does not recognize it. TetherKit is only needed for the macOS setup below.
Each selected network needs a working IPv4 connection and a route to the download server. Adapter detection does not guarantee Internet access: VPN, virtual, and isolated adapters may also appear. Check per-network latency and transfer stats. Combined throughput depends on the networks, Windows routing, and the server; it needs testing with your particular connections.
Linux packaging remains available via npm run build:linux (see Build the Linux app).
To package Plexo for Linux:
npm run build:linux
The package will be generated in dist/.
Android USB tethering requires an additional setup step on macOS.
macOS lacks native support for the RNDIS (Remote Network Driver Interface Specification) protocol. An Android phone with USB tethering enabled will charge and support MTP/ADB, but macOS will not expose it as a network interface (this is also why legacy kernel extensions like HoRNDIS stopped functioning on modern macOS and Apple Silicon).
TetherKit is an open-source, kext-free, user-space RNDIS driver that makes Android USB tethering available as a standard network interface on macOS.
Install it via Homebrew:
brew install XiaoMiku01/tap/tetherkit
Special thanks to @XiaoMiku01 for developing and open-sourcing TetherKit!
Contributions are welcome! Whether you're optimizing download concurrency, improving UI responsiveness, or testing new tethering setups:
For development setup, coding standards, and PR workflows, see CONTRIBUTING.md.
Plexo is built with:
MIT — see LICENSE.
TypeScript
97.7%
CSS
1.8%
Speed up downloads by combining multiple network connections in parallel
See the codeA fast download manager for Windows, macOS, and Linux that speeds up downloads by pulling chunks in parallel across multiple network connections at the same time.
For example, if your computer has:
Plexo can utilize all of them simultaneously to download the same file.
https://github.com/user-attachments/assets/e57728f4-fb63-441f-839c-174eef954b17
macOS does not natively provide an RNDIS driver, so Android phones with USB tethering enabled won't appear as network interfaces out of the box (this is also why legacy kernel extensions like HoRNDIS stopped working on Apple Silicon and modern macOS).
To use your Android phone's connection over USB, install TetherKit — a kext-free, user-space RNDIS driver.
See Using a USB-tethered Android phone for setup instructions.
Plexo can only route traffic through connections that your operating system recognizes as network interfaces.
A single TCP connection rarely saturates your actual bandwidth. Even when your computer has multiple active networks — such as Wi-Fi and a tethered mobile phone — the operating system routes all traffic through a single default gateway, leaving the other interfaces completely idle.
Plexo changes that: it splits the file into independent byte ranges and downloads them simultaneously through distinct physical network interfaces.
┌── Wi-Fi (IP: 192.168.1.40) ────┐
│ │
File ──→ Split ─────┼── Ethernet (IP: 10.0.0.12) ────┼──→ Assembled File
│ │
└── USB Tether (IP: 172.20.10.3) ┘
Multiple networks → concurrent HTTP range requests → aggregated bandwidth
Get-NetAdapter and macOS hardware ports via networksetup so Wi-Fi, Ethernet, tethered iPhones, and Thunderbolt bridges are labeled by real device names instead of bare BSD names (en0, en6).part-N chunk files on disk.ETag and Last-Modified validators before resuming, refusing to resume (rather than corrupting the file) if the server-side file has changed.Instead of downloading a file linearly over a single socket, Plexo requests arbitrary slices of the file simultaneously across multiple physical network interfaces. Three core technical primitives make this work:
206 Partial Content)Most modern HTTP servers support byte-level slicing:
GET /ubuntu-26.04.1-desktop-amd64.iso HTTP/1.1
Host: releases.ubuntu.com
Range: bytes=8388608-16777215
Servers advertise this capability with the Accept-Ranges: bytes response header and reply with HTTP status 206 Partial Content. Because byte slices are stateless and independent, Plexo can request dozens of chunks at once, in any order, and stitch them together later.
Before starting a multi-connection download, Plexo sends a 1-byte ranged GET (Range: bytes=0-0), following any redirects:
HEAD request (which servers and CDNs frequently misreport), receiving a 206 Partial Content response conclusively proves that range requests are supported and functional.Content-Range / Content-Length), suggested filename (Content-Disposition), and cache validators (ETag and Last-Modified).200 OK (ignoring the Range header), Plexo falls back to a standard single-connection stream instead of failing.localAddressEvery active network interface on your computer has its own local IP address — Wi-Fi might be 192.168.1.40, while a USB-tethered phone is 172.20.10.3.
A standard TCP socket leaves interface selection to the operating system's routing table. However, Node.js allows outbound HTTP/HTTPS requests to explicitly bind to a specific local IP using the localAddress option:
https.request({
hostname: 'releases.ubuntu.com',
path: '/ubuntu-26.04.1-desktop-amd64.iso',
localAddress: '172.20.10.3', // Forces this connection through the USB tether
headers: {
Range: 'bytes=8388608-16777215'
}
})
This single option is Plexo's entire multi-network routing engine:
kext) or root privilegesIf you statically divide a 6 GB file into equal shares (e.g. 3 GB on Wi-Fi and 3 GB on mobile data), the total download speed is bottlenecked by the slower network.
Instead, Plexo uses a dynamic work-stealing queue:
Shared Pending Queue: [Chunk #4] [Chunk #5] [Chunk #6] [Chunk #7] [Chunk #8] ...
↑ ↑ ↑
Worker 1 Worker 2 Worker 3
(Wi-Fi) (Ethernet) (USB Tether)
Work distribution is dynamically proportional to each interface's real-time throughput. If one network slows down or disconnects, remaining workers continue draining the queue without stalled shares.
Each worker writes its assigned byte range directly to an isolated temporary file on disk (part-0, part-1, ... part-N).
Once the queue is drained and all chunk promises resolve:
part-N file sequentially into the final destination file using Node.js streams (createReadStream piped into createWriteStream with { flags: 'a' }).When you pause a download:
AbortController.part-N files remain cached on disk in a temporary directory.When you resume:
ETag and Last-Modified headers against the values recorded when the download started.part-N files are already complete on disk, skips them, and queues only the remaining chunks.Download manifests and partial data are stored under Plexo's application-data directory. If Plexo quits or crashes during a transfer, it restores that transfer as paused on the next launch. Explicitly cancelling or removing a download still deletes its partial data.
A chunk is the atomic unit of work in Plexo:
Range: bytes=START-END).part-N file in the download's temp directory.Chunk #0 → Range: bytes=0-8388607 → part-0 (Wi-Fi)
Chunk #1 → Range: bytes=8388608-16777215 → part-1 (Ethernet)
Chunk #2 → Range: bytes=16777216-25165823 → part-2 (USB Tether)
8 MB provides the optimal balance: large enough to minimize HTTP connection overhead and TLS handshakes, yet small enough to keep the work-stealing queue fluid, ensure fine-grained load balancing across mismatched connections, and keep retries cheap (a failed or stalled connection only discards at most 8 MB).
The progress grid provides a real-time visual map of the entire download.
Every 8 MB chunk maps 1:1 to its own square in the grid. Square #N directly corresponds to the Chunk #N badge shown in the active streams table, allowing you to cross-reference active connections with their location in the file.
Active Streams:
[Wi-Fi] → Chunk #4
[Ethernet] → Chunk #5
[USB Tether] → Chunk #6
Progress Grid:
[#1][#2][#3][#4][#5][#6][#7][#8]...
Plexo currently doesn't have pre-built releases, so you'll need to run it from source.
networksetup; Linux provides fallback interface detection and desktop network settings integration.Clone the repository:
git clone https://github.com/anmolkapil/plexo.git
cd plexo
Install dependencies:
npm install
Start the application in development mode:
npm run dev
Plexo includes an automated end-to-end test suite driven by Playwright:
npm run test:e2e:smoke # quick smoke tests
npm run test:e2e # full E2E test suite (including integrity and chaos tests)
See CONTRIBUTING.md for testing options and debugging flags.
To package Plexo as a standalone macOS application bundle:
npm run build:mac
The compiled application will be generated at:
dist/mac/Plexo.app
Note on Gatekeeper: The app is unsigned because it is not distributed with a paid Apple Developer certificate. However, because you compile it locally on your machine, macOS will not apply the quarantine flag (
com.apple.quarantine). Gatekeeper only quarantines files downloaded from the web (via browsers, curl, etc.), so your locally builtPlexo.appwill launch cleanly without quarantine warnings.
Run these commands from PowerShell in the project directory:
npm install
npm run build:win
The installer is generated at dist/plexo-1.0.0-setup.exe. For an unpacked app, run
npm run build:unpack and launch dist/win-unpacked/plexo.exe.
Local builds are unsigned.
Windows uses native window controls, Ctrl+V hints, File Explorer integration, and Windows Network Settings. Download filenames are normalized to Windows filename rules.
Enable USB tethering on your phone and check that its adapter appears in Windows Network Settings. Install the phone manufacturer's Windows driver if Windows does not recognize it. TetherKit is only needed for the macOS setup below.
Each selected network needs a working IPv4 connection and a route to the download server. Adapter detection does not guarantee Internet access: VPN, virtual, and isolated adapters may also appear. Check per-network latency and transfer stats. Combined throughput depends on the networks, Windows routing, and the server; it needs testing with your particular connections.
Linux packaging remains available via npm run build:linux (see Build the Linux app).
To package Plexo for Linux:
npm run build:linux
The package will be generated in dist/.
Android USB tethering requires an additional setup step on macOS.
macOS lacks native support for the RNDIS (Remote Network Driver Interface Specification) protocol. An Android phone with USB tethering enabled will charge and support MTP/ADB, but macOS will not expose it as a network interface (this is also why legacy kernel extensions like HoRNDIS stopped functioning on modern macOS and Apple Silicon).
TetherKit is an open-source, kext-free, user-space RNDIS driver that makes Android USB tethering available as a standard network interface on macOS.
Install it via Homebrew:
brew install XiaoMiku01/tap/tetherkit
Special thanks to @XiaoMiku01 for developing and open-sourcing TetherKit!
Contributions are welcome! Whether you're optimizing download concurrency, improving UI responsiveness, or testing new tethering setups:
For development setup, coding standards, and PR workflows, see CONTRIBUTING.md.
Plexo is built with:
MIT — see LICENSE.
TypeScript
97.7%
CSS
1.8%