plexo-cli 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plexo_cli-0.1.2/.gitignore +9 -0
- plexo_cli-0.1.2/PKG-INFO +231 -0
- plexo_cli-0.1.2/README.md +199 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/pyproject.toml +1 -1
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/__init__.py +1 -1
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/cli.py +118 -11
- plexo_cli-0.1.2/src/plexo/core/client.py +304 -0
- plexo_cli-0.1.2/src/plexo/core/engine.py +476 -0
- plexo_cli-0.1.2/src/plexo/core/qos.py +125 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/core/sparse.py +15 -6
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/torrent/client.py +26 -2
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/torrent/metadata.py +74 -17
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/ui/dashboard.py +19 -3
- plexo_cli-0.1.2/src/plexo/ui/watcher.py +311 -0
- plexo_cli-0.1.2/tests/test_engine.py +239 -0
- plexo_cli-0.1.2/tests/test_mirror.py +426 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/tests/test_torrent.py +46 -0
- plexo_cli-0.1.2/tests/test_watcher.py +182 -0
- plexo_cli-0.1.0/.gitignore +0 -9
- plexo_cli-0.1.0/PKG-INFO +0 -91
- plexo_cli-0.1.0/README.md +0 -59
- plexo_cli-0.1.0/src/plexo/core/client.py +0 -148
- plexo_cli-0.1.0/src/plexo/core/engine.py +0 -266
- plexo_cli-0.1.0/src/plexo/core/qos.py +0 -76
- plexo_cli-0.1.0/tests/test_engine.py +0 -93
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/core/__init__.py +0 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/core/adapter.py +0 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/proxy/__init__.py +0 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/proxy/server.py +0 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/torrent/__init__.py +0 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/torrent/bencode.py +0 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/src/plexo/ui/__init__.py +0 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/tests/__init__.py +0 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/tests/test_adapter.py +0 -0
- {plexo_cli-0.1.0 → plexo_cli-0.1.2}/tests/test_sparse.py +0 -0
plexo_cli-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: plexo-cli
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Multi-network bandwidth bonding download engine & CLI
|
|
5
|
+
Author-email: Jagjot Singh <gotogether@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: bandwidth-aggregation,download-accelerator,multi-interface,network-bonding,proxy,torrent
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Internet
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Requires-Dist: click>=8.1.0
|
|
24
|
+
Requires-Dist: httpx>=0.27.0
|
|
25
|
+
Requires-Dist: psutil>=5.9.0
|
|
26
|
+
Requires-Dist: rich>=13.7.0
|
|
27
|
+
Requires-Dist: urllib3>=2.0.0
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
|
|
30
|
+
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# ⚡ Plexo CLI (`plexo-cli`)
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/plexo-cli/)
|
|
36
|
+
[](https://pypi.org/project/plexo-cli/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
[](tests/)
|
|
39
|
+
|
|
40
|
+
> **Universal, lightweight, terminal-first multi-network bandwidth bonding and torrent engine.**
|
|
41
|
+
> Combines Wi-Fi, Ethernet, and Mobile 5G tethering into one unified high-speed pipe directly from your command line.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 🚀 Key Features
|
|
46
|
+
|
|
47
|
+
* **🌐 Multi-Socket Bandwidth Bonding:** Binds parallel download workers to distinct physical network interfaces using source-IP socket routing (`urllib3.PoolManager(source_address=(NIC_IP, 0))`).
|
|
48
|
+
* **🧲 BitTorrent Multi-Homing Swarm:** Slices BitTorrent peer swarms across multiple network adapters simultaneously with SHA-1 piece verification.
|
|
49
|
+
* **🎬 Watch While Downloading (`--sequential`):** Prioritizes file headers (moov container) and opening frames so media players like **VLC** can stream video immediately from byte 0.
|
|
50
|
+
* **🪞 Multi-Source Mirror Bonding (`-m / --mirror`):** Pulls chunks simultaneously across distinct server mirrors (e.g. Mirror A on Wi-Fi, Mirror B on 5G) to bypass server-side IP throttling.
|
|
51
|
+
* **🛡️ Per-Adapter Data Quotas (`-q / --quota`):** Protects mobile hotspot data caps (e.g. `-q usb=1.5GB`). When the cap is reached, that connection rests while unlimited Wi-Fi finishes the rest.
|
|
52
|
+
* **📂 Selective Torrent Downloads (`--list`, `--select`):** Inspect contents of multi-file season packs and download only the specific episodes you need.
|
|
53
|
+
* **📋 Background Clipboard Watcher (`plexo watch`):** IDM-style daemon that monitors the system clipboard and auto-captures `.mp4`, `.mkv`, `.zip`, `.iso`, or `magnet:` links.
|
|
54
|
+
* **⚡ Zero-Touch Local Acceleration Proxy (`plexo proxy`):** Runs a local forward proxy (`127.0.0.1:8899`) to bond traffic for `pip`, `curl`, `git`, and `huggingface-cli`.
|
|
55
|
+
* **💾 Smart Checkpointing & Instant Resume (`.plexo`):** Non-destructive NTFS/ext4 sparse file pre-allocation with zero-overhead resume on interrupted downloads.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 📦 Installation
|
|
60
|
+
|
|
61
|
+
### From PyPI
|
|
62
|
+
```bash
|
|
63
|
+
pip install plexo-cli
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### From Source (Editable Mode)
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/anmolkapil/plexo.git
|
|
69
|
+
cd plexo/packages/plexo-cli
|
|
70
|
+
pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 🛠️ CLI Command Reference
|
|
76
|
+
|
|
77
|
+
### 1. Discover Active Interfaces: `plexo adapters`
|
|
78
|
+
Scans and validates all active physical, Ethernet, and wireless interfaces, verifying outward internet reachability via canary probes:
|
|
79
|
+
```bash
|
|
80
|
+
plexo adapters
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
*Example Output:*
|
|
84
|
+
```text
|
|
85
|
+
+-------------------+---------------+----------+---------+--------+
|
|
86
|
+
| Adapter Name | IP Address | Type | State | Canary |
|
|
87
|
+
+-------------------+---------------+----------+---------+--------+
|
|
88
|
+
| Wi-Fi (Hostel) | 192.168.1.104 | Wi-Fi | UP | ONLINE |
|
|
89
|
+
| Phone 5G USB | 192.168.43.55 | Ethernet | UP | ONLINE |
|
|
90
|
+
+-------------------+---------------+----------+---------+--------+
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### 2. Bonded HTTP Downloads: `plexo download`
|
|
96
|
+
|
|
97
|
+
#### Basic Parallel Download
|
|
98
|
+
```bash
|
|
99
|
+
plexo download "https://example.com/largefile.zip" -o "largefile.zip"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Watch While Downloading (`--sequential`)
|
|
103
|
+
Download chunks in strict chronological order so you can open and watch the video in VLC within 30 seconds:
|
|
104
|
+
```bash
|
|
105
|
+
plexo download "https://example.com/movie.mp4" --sequential -o "movie.mp4"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Multi-Source Mirror Bonding (`-m / --mirror`)
|
|
109
|
+
Distribute chunk downloads across multiple server mirrors and multiple adapters:
|
|
110
|
+
```bash
|
|
111
|
+
plexo download "https://mirror1.com/ubuntu.iso" -m "https://mirror2.com/ubuntu.iso" -o "ubuntu.iso"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Protect Mobile Data Caps (`-q / --quota`)
|
|
115
|
+
Specify a maximum data quota for any adapter by name or IP:
|
|
116
|
+
```bash
|
|
117
|
+
# Cap Phone USB tethering to 1.5 GB, let Wi-Fi pull the rest:
|
|
118
|
+
plexo download "<url>" -q "usb=1.5GB"
|
|
119
|
+
|
|
120
|
+
# Cap a specific network IP to 500 MB:
|
|
121
|
+
plexo download "<url>" -q "192.168.43.55=500MB"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### Authenticated / Protected Filelockers (`--cookie`, `--referer`, `-H`)
|
|
125
|
+
```bash
|
|
126
|
+
plexo download "<protected-url>" \
|
|
127
|
+
--cookie "session_token=abcdef123456" \
|
|
128
|
+
--referer "https://filelocker.com/view" \
|
|
129
|
+
-H "Authorization: Bearer my_token"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 3. BitTorrent Swarm Multi-Homing: `plexo torrent`
|
|
135
|
+
|
|
136
|
+
#### Magnet Links & `.torrent` Files
|
|
137
|
+
```bash
|
|
138
|
+
# Magnet URI:
|
|
139
|
+
plexo torrent "magnet:?xt=urn:btih:EXAMPLE_HASH&dn=Debian" -o "D:\Downloads"
|
|
140
|
+
|
|
141
|
+
# Local .torrent file:
|
|
142
|
+
plexo torrent "debian.torrent" -o "D:\Downloads"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### Inspect Torrent Files (`--list`)
|
|
146
|
+
```bash
|
|
147
|
+
plexo torrent "magnet:?xt=urn:btih:..." --list
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Download Selected Files (`--select`)
|
|
151
|
+
Download only specific files or episodes from large torrents:
|
|
152
|
+
```bash
|
|
153
|
+
# Download only Episode 1 (index 1):
|
|
154
|
+
plexo torrent "magnet:?xt=urn:btih:..." --select 1
|
|
155
|
+
|
|
156
|
+
# Download multiple episodes:
|
|
157
|
+
plexo torrent "magnet:?xt=urn:btih:..." --select 1,2,5
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### Sequential Torrent Playback (`--sequential`)
|
|
161
|
+
```bash
|
|
162
|
+
plexo torrent "magnet:?xt=urn:btih:..." --sequential -o "D:\Movies"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### 4. Background Clipboard Watcher: `plexo watch`
|
|
168
|
+
Listens in the background for copied download links (`.mp4`, `.mkv`, `.zip`, `.iso`, `magnet:`, etc.):
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Interactive mode (prompts [Y/n] when a downloadable URL is copied):
|
|
172
|
+
plexo watch
|
|
173
|
+
|
|
174
|
+
# Headless / Auto-download mode:
|
|
175
|
+
plexo watch -a -o "D:\Downloads"
|
|
176
|
+
|
|
177
|
+
# Auto-download with sequential streaming:
|
|
178
|
+
plexo watch -a --sequential -o "D:\Movies"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### 5. Local Acceleration Proxy: `plexo proxy`
|
|
184
|
+
Accelerate development tools across bonded network connections:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# Start proxy on default port 8899:
|
|
188
|
+
plexo proxy --port 8899
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
In another terminal, direct tools to use the proxy:
|
|
192
|
+
```bash
|
|
193
|
+
# Accelerate pip:
|
|
194
|
+
pip install torch torchvision --proxy http://127.0.0.1:8899
|
|
195
|
+
|
|
196
|
+
# Accelerate curl / Hugging Face CLI:
|
|
197
|
+
export HTTPS_PROXY="http://127.0.0.1:8899"
|
|
198
|
+
curl -O https://example.com/dataset.tar.gz
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### 6. Probe Server Capabilities: `plexo info`
|
|
204
|
+
Inspect remote server headers, file size, MIME type, and byte-range support:
|
|
205
|
+
```bash
|
|
206
|
+
plexo info "https://example.com/file.zip"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 🧪 Verification & Testing
|
|
212
|
+
|
|
213
|
+
The package includes a comprehensive automated test suite testing live sockets, mock mirrors, sparse file allocation, torrent bencoding, QoS quota enforcement, and clipboard regex extractors:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pytest packages/plexo-cli -v
|
|
217
|
+
```
|
|
218
|
+
```text
|
|
219
|
+
tests/test_adapter.py PASSED (3/3)
|
|
220
|
+
tests/test_sparse.py PASSED (3/3)
|
|
221
|
+
tests/test_torrent.py PASSED (4/4)
|
|
222
|
+
tests/test_engine.py PASSED (5/5)
|
|
223
|
+
tests/test_watcher.py PASSED (12/12)
|
|
224
|
+
tests/test_mirror.py PASSED (9/9)
|
|
225
|
+
============================= 36 passed in 10.10s =============================
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 📄 License
|
|
231
|
+
MIT © Anmol Kapil & Jagjot Singh
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# ⚡ Plexo CLI (`plexo-cli`)
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/plexo-cli/)
|
|
4
|
+
[](https://pypi.org/project/plexo-cli/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](tests/)
|
|
7
|
+
|
|
8
|
+
> **Universal, lightweight, terminal-first multi-network bandwidth bonding and torrent engine.**
|
|
9
|
+
> Combines Wi-Fi, Ethernet, and Mobile 5G tethering into one unified high-speed pipe directly from your command line.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🚀 Key Features
|
|
14
|
+
|
|
15
|
+
* **🌐 Multi-Socket Bandwidth Bonding:** Binds parallel download workers to distinct physical network interfaces using source-IP socket routing (`urllib3.PoolManager(source_address=(NIC_IP, 0))`).
|
|
16
|
+
* **🧲 BitTorrent Multi-Homing Swarm:** Slices BitTorrent peer swarms across multiple network adapters simultaneously with SHA-1 piece verification.
|
|
17
|
+
* **🎬 Watch While Downloading (`--sequential`):** Prioritizes file headers (moov container) and opening frames so media players like **VLC** can stream video immediately from byte 0.
|
|
18
|
+
* **🪞 Multi-Source Mirror Bonding (`-m / --mirror`):** Pulls chunks simultaneously across distinct server mirrors (e.g. Mirror A on Wi-Fi, Mirror B on 5G) to bypass server-side IP throttling.
|
|
19
|
+
* **🛡️ Per-Adapter Data Quotas (`-q / --quota`):** Protects mobile hotspot data caps (e.g. `-q usb=1.5GB`). When the cap is reached, that connection rests while unlimited Wi-Fi finishes the rest.
|
|
20
|
+
* **📂 Selective Torrent Downloads (`--list`, `--select`):** Inspect contents of multi-file season packs and download only the specific episodes you need.
|
|
21
|
+
* **📋 Background Clipboard Watcher (`plexo watch`):** IDM-style daemon that monitors the system clipboard and auto-captures `.mp4`, `.mkv`, `.zip`, `.iso`, or `magnet:` links.
|
|
22
|
+
* **⚡ Zero-Touch Local Acceleration Proxy (`plexo proxy`):** Runs a local forward proxy (`127.0.0.1:8899`) to bond traffic for `pip`, `curl`, `git`, and `huggingface-cli`.
|
|
23
|
+
* **💾 Smart Checkpointing & Instant Resume (`.plexo`):** Non-destructive NTFS/ext4 sparse file pre-allocation with zero-overhead resume on interrupted downloads.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 📦 Installation
|
|
28
|
+
|
|
29
|
+
### From PyPI
|
|
30
|
+
```bash
|
|
31
|
+
pip install plexo-cli
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### From Source (Editable Mode)
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/anmolkapil/plexo.git
|
|
37
|
+
cd plexo/packages/plexo-cli
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🛠️ CLI Command Reference
|
|
44
|
+
|
|
45
|
+
### 1. Discover Active Interfaces: `plexo adapters`
|
|
46
|
+
Scans and validates all active physical, Ethernet, and wireless interfaces, verifying outward internet reachability via canary probes:
|
|
47
|
+
```bash
|
|
48
|
+
plexo adapters
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
*Example Output:*
|
|
52
|
+
```text
|
|
53
|
+
+-------------------+---------------+----------+---------+--------+
|
|
54
|
+
| Adapter Name | IP Address | Type | State | Canary |
|
|
55
|
+
+-------------------+---------------+----------+---------+--------+
|
|
56
|
+
| Wi-Fi (Hostel) | 192.168.1.104 | Wi-Fi | UP | ONLINE |
|
|
57
|
+
| Phone 5G USB | 192.168.43.55 | Ethernet | UP | ONLINE |
|
|
58
|
+
+-------------------+---------------+----------+---------+--------+
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
### 2. Bonded HTTP Downloads: `plexo download`
|
|
64
|
+
|
|
65
|
+
#### Basic Parallel Download
|
|
66
|
+
```bash
|
|
67
|
+
plexo download "https://example.com/largefile.zip" -o "largefile.zip"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Watch While Downloading (`--sequential`)
|
|
71
|
+
Download chunks in strict chronological order so you can open and watch the video in VLC within 30 seconds:
|
|
72
|
+
```bash
|
|
73
|
+
plexo download "https://example.com/movie.mp4" --sequential -o "movie.mp4"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### Multi-Source Mirror Bonding (`-m / --mirror`)
|
|
77
|
+
Distribute chunk downloads across multiple server mirrors and multiple adapters:
|
|
78
|
+
```bash
|
|
79
|
+
plexo download "https://mirror1.com/ubuntu.iso" -m "https://mirror2.com/ubuntu.iso" -o "ubuntu.iso"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### Protect Mobile Data Caps (`-q / --quota`)
|
|
83
|
+
Specify a maximum data quota for any adapter by name or IP:
|
|
84
|
+
```bash
|
|
85
|
+
# Cap Phone USB tethering to 1.5 GB, let Wi-Fi pull the rest:
|
|
86
|
+
plexo download "<url>" -q "usb=1.5GB"
|
|
87
|
+
|
|
88
|
+
# Cap a specific network IP to 500 MB:
|
|
89
|
+
plexo download "<url>" -q "192.168.43.55=500MB"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Authenticated / Protected Filelockers (`--cookie`, `--referer`, `-H`)
|
|
93
|
+
```bash
|
|
94
|
+
plexo download "<protected-url>" \
|
|
95
|
+
--cookie "session_token=abcdef123456" \
|
|
96
|
+
--referer "https://filelocker.com/view" \
|
|
97
|
+
-H "Authorization: Bearer my_token"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### 3. BitTorrent Swarm Multi-Homing: `plexo torrent`
|
|
103
|
+
|
|
104
|
+
#### Magnet Links & `.torrent` Files
|
|
105
|
+
```bash
|
|
106
|
+
# Magnet URI:
|
|
107
|
+
plexo torrent "magnet:?xt=urn:btih:EXAMPLE_HASH&dn=Debian" -o "D:\Downloads"
|
|
108
|
+
|
|
109
|
+
# Local .torrent file:
|
|
110
|
+
plexo torrent "debian.torrent" -o "D:\Downloads"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Inspect Torrent Files (`--list`)
|
|
114
|
+
```bash
|
|
115
|
+
plexo torrent "magnet:?xt=urn:btih:..." --list
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Download Selected Files (`--select`)
|
|
119
|
+
Download only specific files or episodes from large torrents:
|
|
120
|
+
```bash
|
|
121
|
+
# Download only Episode 1 (index 1):
|
|
122
|
+
plexo torrent "magnet:?xt=urn:btih:..." --select 1
|
|
123
|
+
|
|
124
|
+
# Download multiple episodes:
|
|
125
|
+
plexo torrent "magnet:?xt=urn:btih:..." --select 1,2,5
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Sequential Torrent Playback (`--sequential`)
|
|
129
|
+
```bash
|
|
130
|
+
plexo torrent "magnet:?xt=urn:btih:..." --sequential -o "D:\Movies"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### 4. Background Clipboard Watcher: `plexo watch`
|
|
136
|
+
Listens in the background for copied download links (`.mp4`, `.mkv`, `.zip`, `.iso`, `magnet:`, etc.):
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Interactive mode (prompts [Y/n] when a downloadable URL is copied):
|
|
140
|
+
plexo watch
|
|
141
|
+
|
|
142
|
+
# Headless / Auto-download mode:
|
|
143
|
+
plexo watch -a -o "D:\Downloads"
|
|
144
|
+
|
|
145
|
+
# Auto-download with sequential streaming:
|
|
146
|
+
plexo watch -a --sequential -o "D:\Movies"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### 5. Local Acceleration Proxy: `plexo proxy`
|
|
152
|
+
Accelerate development tools across bonded network connections:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Start proxy on default port 8899:
|
|
156
|
+
plexo proxy --port 8899
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
In another terminal, direct tools to use the proxy:
|
|
160
|
+
```bash
|
|
161
|
+
# Accelerate pip:
|
|
162
|
+
pip install torch torchvision --proxy http://127.0.0.1:8899
|
|
163
|
+
|
|
164
|
+
# Accelerate curl / Hugging Face CLI:
|
|
165
|
+
export HTTPS_PROXY="http://127.0.0.1:8899"
|
|
166
|
+
curl -O https://example.com/dataset.tar.gz
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### 6. Probe Server Capabilities: `plexo info`
|
|
172
|
+
Inspect remote server headers, file size, MIME type, and byte-range support:
|
|
173
|
+
```bash
|
|
174
|
+
plexo info "https://example.com/file.zip"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 🧪 Verification & Testing
|
|
180
|
+
|
|
181
|
+
The package includes a comprehensive automated test suite testing live sockets, mock mirrors, sparse file allocation, torrent bencoding, QoS quota enforcement, and clipboard regex extractors:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pytest packages/plexo-cli -v
|
|
185
|
+
```
|
|
186
|
+
```text
|
|
187
|
+
tests/test_adapter.py PASSED (3/3)
|
|
188
|
+
tests/test_sparse.py PASSED (3/3)
|
|
189
|
+
tests/test_torrent.py PASSED (4/4)
|
|
190
|
+
tests/test_engine.py PASSED (5/5)
|
|
191
|
+
tests/test_watcher.py PASSED (12/12)
|
|
192
|
+
tests/test_mirror.py PASSED (9/9)
|
|
193
|
+
============================= 36 passed in 10.10s =============================
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 📄 License
|
|
199
|
+
MIT © Anmol Kapil & Jagjot Singh
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Command Line Interface for Plexo.
|
|
2
2
|
|
|
3
|
-
Exposes subcommands: download, adapters, proxy, info.
|
|
3
|
+
Exposes subcommands: download, adapters, proxy, torrent, watch, info.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -76,16 +76,56 @@ def list_adapters(show_all: bool):
|
|
|
76
76
|
|
|
77
77
|
@main.command(name="download")
|
|
78
78
|
@click.argument("url")
|
|
79
|
+
@click.option("-m", "--mirror", "mirrors", multiple=True, help="Alternative mirror URL(s) for the same file.")
|
|
79
80
|
@click.option("-o", "--output", "output_path", type=click.Path(), default=None, help="Output destination path.")
|
|
80
81
|
@click.option("--chunk-size", default=8, type=int, help="Chunk size in MB (default: 8 MB).")
|
|
81
82
|
@click.option("--workers", default=2, type=int, help="Workers per network adapter (default: 2).")
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
@click.option("-H", "--header", "headers_list", multiple=True, help="Custom HTTP header (e.g. -H 'Auth: token').")
|
|
84
|
+
@click.option("--cookie", default=None, help="Custom Cookie header for protected sites/filelockers.")
|
|
85
|
+
@click.option("--referer", default=None, help="Custom Referer URL for sites requiring origin verification.")
|
|
86
|
+
@click.option("--sequential", is_flag=True, help="Download in sequential order (watch media while downloading).")
|
|
87
|
+
@click.option("-q", "--quota", "quota_list", multiple=True, help="Data cap for adapter (e.g. -q usb=1.5GB).")
|
|
88
|
+
def download_file(
|
|
89
|
+
url: str,
|
|
90
|
+
mirrors: tuple[str, ...],
|
|
91
|
+
output_path: Optional[str],
|
|
92
|
+
chunk_size: int,
|
|
93
|
+
workers: int,
|
|
94
|
+
headers_list: tuple[str, ...],
|
|
95
|
+
cookie: Optional[str],
|
|
96
|
+
referer: Optional[str],
|
|
97
|
+
sequential: bool,
|
|
98
|
+
quota_list: tuple[str, ...],
|
|
99
|
+
):
|
|
100
|
+
"""Download a file using bonded multi-network connections and mirrors."""
|
|
101
|
+
from .core.qos import parse_size_str
|
|
84
102
|
adapters = get_bonded_pool()
|
|
85
103
|
if not adapters:
|
|
86
104
|
console.print("[bold red]Error: No active online network adapters detected.[/bold red]")
|
|
87
105
|
raise click.Abort()
|
|
88
106
|
|
|
107
|
+
# Parse custom headers
|
|
108
|
+
parsed_headers = {}
|
|
109
|
+
for h in headers_list:
|
|
110
|
+
if ":" in h:
|
|
111
|
+
k, v = h.split(":", 1)
|
|
112
|
+
parsed_headers[k.strip()] = v.strip()
|
|
113
|
+
|
|
114
|
+
# Parse adapter data quotas
|
|
115
|
+
parsed_quotas = {}
|
|
116
|
+
for q in quota_list:
|
|
117
|
+
if "=" in q:
|
|
118
|
+
ident, s_val = q.split("=", 1)
|
|
119
|
+
parsed_quotas[ident.strip()] = parse_size_str(s_val.strip())
|
|
120
|
+
|
|
121
|
+
# Parse alternative mirror URLs
|
|
122
|
+
parsed_mirrors = []
|
|
123
|
+
for m in mirrors:
|
|
124
|
+
for sub_m in m.split(","):
|
|
125
|
+
sub_m = sub_m.strip()
|
|
126
|
+
if sub_m and sub_m not in parsed_mirrors and sub_m != url:
|
|
127
|
+
parsed_mirrors.append(sub_m)
|
|
128
|
+
|
|
89
129
|
dashboard = TerminalDashboard(console=console)
|
|
90
130
|
chunk_bytes = chunk_size * 1024 * 1024
|
|
91
131
|
|
|
@@ -95,10 +135,16 @@ def download_file(url: str, output_path: Optional[str], chunk_size: int, workers
|
|
|
95
135
|
adapters=adapters,
|
|
96
136
|
chunk_size=chunk_bytes,
|
|
97
137
|
workers_per_adapter=workers,
|
|
138
|
+
custom_headers=parsed_headers,
|
|
139
|
+
cookie=cookie,
|
|
140
|
+
referer=referer,
|
|
141
|
+
sequential=sequential,
|
|
142
|
+
quotas=parsed_quotas,
|
|
143
|
+
mirrors=parsed_mirrors,
|
|
98
144
|
)
|
|
99
145
|
|
|
100
146
|
probe = engine._prepare()
|
|
101
|
-
dashboard.setup(probe.filename, probe.total_bytes, adapters)
|
|
147
|
+
dashboard.setup(probe.filename, probe.total_bytes, adapters, mirrors=engine.mirrors)
|
|
102
148
|
|
|
103
149
|
start_time = time.time()
|
|
104
150
|
with dashboard.progress:
|
|
@@ -112,7 +158,7 @@ def download_file(url: str, output_path: Optional[str], chunk_size: int, workers
|
|
|
112
158
|
with SparseFileWriter(final_path, total_bytes) as writer:
|
|
113
159
|
checksum = writer.calculate_checksum()
|
|
114
160
|
|
|
115
|
-
dashboard.finish(final_path.name, total_bytes, elapsed, checksum)
|
|
161
|
+
dashboard.finish(final_path.name, total_bytes, elapsed, checksum, mirrors=engine.mirrors)
|
|
116
162
|
|
|
117
163
|
|
|
118
164
|
@main.command(name="proxy")
|
|
@@ -126,26 +172,64 @@ def start_proxy(host: str, port: int):
|
|
|
126
172
|
@main.command(name="torrent")
|
|
127
173
|
@click.argument("source")
|
|
128
174
|
@click.option("-o", "--output-dir", default=".", help="Directory to save downloaded files.")
|
|
129
|
-
|
|
175
|
+
@click.option("--sequential", is_flag=True, help="Download pieces in sequential order (watch media while downloading).")
|
|
176
|
+
@click.option("--list", "list_files", is_flag=True, help="List all files in the torrent and exit.")
|
|
177
|
+
@click.option("--select", "select_files", default=None, help="Download only specific file numbers (e.g. --select 1,2).")
|
|
178
|
+
@click.option("-q", "--quota", "quota_list", multiple=True, help="Data cap for adapter (e.g. -q usb=1.5GB).")
|
|
179
|
+
def download_torrent(
|
|
180
|
+
source: str,
|
|
181
|
+
output_dir: str,
|
|
182
|
+
sequential: bool,
|
|
183
|
+
list_files: bool,
|
|
184
|
+
select_files: Optional[str],
|
|
185
|
+
quota_list: tuple[str, ...],
|
|
186
|
+
):
|
|
130
187
|
"""Download a torrent or magnet link across bonded network adapters."""
|
|
188
|
+
from .core.qos import parse_size_str
|
|
131
189
|
from .torrent.metadata import parse_magnet, parse_torrent_file
|
|
132
190
|
from .torrent.client import TorrentBondingEngine
|
|
133
191
|
|
|
192
|
+
if source.startswith("magnet:?"):
|
|
193
|
+
meta = parse_magnet(source)
|
|
194
|
+
else:
|
|
195
|
+
meta = parse_torrent_file(source)
|
|
196
|
+
|
|
197
|
+
if list_files:
|
|
198
|
+
table = Table(title=f"[bold green]Torrent Contents: {meta.name}[/bold green]")
|
|
199
|
+
table.add_column("#", style="bold yellow", justify="right")
|
|
200
|
+
table.add_column("File Name / Path", style="cyan")
|
|
201
|
+
table.add_column("Size", style="magenta", justify="right")
|
|
202
|
+
for f in meta.files:
|
|
203
|
+
size_str = f"{f.size_mb:.2f} MB" if f.size_mb < 1024 else f"{f.size_gb:.2f} GB"
|
|
204
|
+
table.add_row(str(f.index), f.path, size_str)
|
|
205
|
+
console.print(table)
|
|
206
|
+
console.print(
|
|
207
|
+
"\n[dim]To download specific files, run:[/dim]\n"
|
|
208
|
+
f" plexo torrent \"{source}\" --select 1,2 -o \"{output_dir}\"\n"
|
|
209
|
+
)
|
|
210
|
+
return
|
|
211
|
+
|
|
134
212
|
adapters = get_bonded_pool()
|
|
135
213
|
if not adapters:
|
|
136
214
|
console.print("[bold red]Error: No active online network adapters detected.[/bold red]")
|
|
137
215
|
raise click.Abort()
|
|
138
216
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
217
|
+
selected_indices = None
|
|
218
|
+
if select_files:
|
|
219
|
+
selected_indices = [int(s.strip()) for s in select_files.split(",") if s.strip().isdigit()]
|
|
220
|
+
|
|
221
|
+
parsed_quotas = {}
|
|
222
|
+
for q in quota_list:
|
|
223
|
+
if "=" in q:
|
|
224
|
+
ident, s_val = q.split("=", 1)
|
|
225
|
+
parsed_quotas[ident.strip()] = parse_size_str(s_val.strip())
|
|
143
226
|
|
|
144
227
|
console.print(
|
|
145
228
|
Panel.fit(
|
|
146
229
|
f"[bold cyan]Plexo Torrent Swarm Engine[/bold cyan]\n"
|
|
147
230
|
f"Name: [bold yellow]{meta.name}[/bold yellow]\n"
|
|
148
231
|
f"Info Hash: [dim]{meta.info_hash_hex}[/dim]\n"
|
|
232
|
+
f"Selected Files: [green]{len(selected_indices) if selected_indices else 'All'}[/green] / {len(meta.files)}\n"
|
|
149
233
|
f"Active Network Links: [bold green]{len(adapters)}[/bold green]",
|
|
150
234
|
border_style="cyan",
|
|
151
235
|
)
|
|
@@ -154,7 +238,14 @@ def download_torrent(source: str, output_dir: str):
|
|
|
154
238
|
dashboard = TerminalDashboard(console=console)
|
|
155
239
|
dashboard.setup(meta.name, meta.total_length, adapters)
|
|
156
240
|
|
|
157
|
-
engine = TorrentBondingEngine(
|
|
241
|
+
engine = TorrentBondingEngine(
|
|
242
|
+
metadata=meta,
|
|
243
|
+
output_dir=output_dir,
|
|
244
|
+
adapters=adapters,
|
|
245
|
+
sequential=sequential,
|
|
246
|
+
selected_files=selected_indices,
|
|
247
|
+
quotas=parsed_quotas,
|
|
248
|
+
)
|
|
158
249
|
|
|
159
250
|
start_time = time.time()
|
|
160
251
|
with dashboard.progress:
|
|
@@ -170,6 +261,22 @@ def download_torrent(source: str, output_dir: str):
|
|
|
170
261
|
dashboard.finish(final_path.name, total_bytes, elapsed, checksum)
|
|
171
262
|
|
|
172
263
|
|
|
264
|
+
@main.command(name="watch")
|
|
265
|
+
@click.option("--auto-download", "-y", is_flag=True, help="Automatically download detected links without prompting.")
|
|
266
|
+
@click.option("-o", "--output-dir", default=None, help="Directory to save downloaded files.")
|
|
267
|
+
@click.option("-i", "--poll-interval", default=1.0, type=float, help="Clipboard polling interval in seconds (default: 1.0).")
|
|
268
|
+
def watch_clipboard(auto_download: bool, output_dir: Optional[str], poll_interval: float):
|
|
269
|
+
"""Monitor clipboard for download URLs and magnet links."""
|
|
270
|
+
from .ui.watcher import ClipboardWatcher
|
|
271
|
+
watcher = ClipboardWatcher(
|
|
272
|
+
poll_interval=poll_interval,
|
|
273
|
+
auto_download=auto_download,
|
|
274
|
+
output_dir=output_dir,
|
|
275
|
+
console=console,
|
|
276
|
+
)
|
|
277
|
+
watcher.run()
|
|
278
|
+
|
|
279
|
+
|
|
173
280
|
@main.command(name="info")
|
|
174
281
|
def show_info():
|
|
175
282
|
"""Display Plexo environment and bonding capabilities."""
|