the-invisible-billion-cli 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/bin/ib.js +90 -0
- package/package.json +28 -0
- package/src/cli/commands/file.js +20 -0
- package/src/cli/commands/scan.js +50 -0
- package/src/cli/commands/send.js +113 -0
- package/src/cli/commands/start.js +126 -0
- package/src/cli/commands/status.js +43 -0
- package/src/cli/commands/stop.js +15 -0
- package/src/cli/commands/sync.js +31 -0
- package/src/cli/ipc.js +127 -0
- package/src/crypto/encrypt.js +100 -0
- package/src/crypto/keys.js +95 -0
- package/src/daemon/epidemic.js +103 -0
- package/src/daemon/index.js +683 -0
- package/src/daemon/network.js +84 -0
- package/src/daemon/tcp.js +200 -0
- package/src/daemon/udp.js +282 -0
- package/src/db/cleanup.js +39 -0
- package/src/db/index.js +74 -0
- package/src/db/messages.js +213 -0
- package/src/identity/index.js +92 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sudogetshivam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Pollen
|
|
2
|
+
|
|
3
|
+
> Fully offline, zero-internet, peer-to-peer messenger.
|
|
4
|
+
> Messages spread like a virus through human movement. No servers. No cloud. Just people carrying bytes.
|
|
5
|
+
|
|
6
|
+
You know how the plague spread across medieval Europe? Person to person, city to city, through nothing but human contact? Yeah, Pollen does that — but with your encrypted messages, and hopefully with less death involved.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Why Does This Exist?](#why-does-this-exist)
|
|
13
|
+
- [How It Works](#how-it-works)
|
|
14
|
+
- [Prerequisites](#prerequisites)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Quick Start](#quick-start)
|
|
17
|
+
- [Commands](#commands)
|
|
18
|
+
- [Architecture](#architecture)
|
|
19
|
+
- [Encryption](#encryption)
|
|
20
|
+
- [Data Storage](#data-storage)
|
|
21
|
+
- [Configuration & Limits](#configuration--limits)
|
|
22
|
+
- [FAQ](#faq)
|
|
23
|
+
- [Contributing](#contributing)
|
|
24
|
+
- [License](#license)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Why Does This Exist?
|
|
29
|
+
|
|
30
|
+
Because sometimes the internet just... isn't there. Natural disasters. Authoritarian shutdowns. That one corner of your college campus where WiFi goes to die. Remote villages. Underground bunkers (no judgment).
|
|
31
|
+
|
|
32
|
+
Pollen implements **Epidemic Routing**, a real protocol from Delay-Tolerant Networking (DTN) research — the same class of protocols NASA uses to communicate with spacecraft millions of miles away. If it's good enough for Mars, it's good enough for your group chat.
|
|
33
|
+
|
|
34
|
+
The idea is simple: your message copies itself to every Pollen user nearby. They walk away, go to a coffee shop, an airport, another city — and their device spreads it further. Eventually, someone physically encounters the recipient, and the message delivers itself. All encrypted. All automatic. Zero internet at any point.
|
|
35
|
+
|
|
36
|
+
**Think of it as WhatsApp if WhatsApp had to survive the apocalypse.**
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## How It Works
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
You Strangers Recipient
|
|
44
|
+
| | |
|
|
45
|
+
|-- encrypt & store ----->| |
|
|
46
|
+
| (WiFi/hotspot) | |
|
|
47
|
+
| |-- carry physically -->|
|
|
48
|
+
| | (walk, drive, fly) |
|
|
49
|
+
| | |
|
|
50
|
+
| |-- deliver on LAN ---->|
|
|
51
|
+
| | |-- decrypt
|
|
52
|
+
| | |
|
|
53
|
+
|<------------- ACK (travels back the same way) -|
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
1. You send a message. It gets **encrypted** with the recipient's public key and stored locally.
|
|
57
|
+
2. Your device broadcasts it over **local WiFi/hotspot** to every nearby Pollen user.
|
|
58
|
+
3. Those users don't even know they're carrying it. Their devices just... have it. Like a cold.
|
|
59
|
+
4. When they move to a new network, the message spreads to everyone there too.
|
|
60
|
+
5. The moment a carrier lands on the same network as the recipient, the message delivers automatically.
|
|
61
|
+
6. An **ACK** receipt travels back to you the exact same way. Epidemically.
|
|
62
|
+
|
|
63
|
+
No central server. No internet backbone. Just mesh networking powered by human commutes.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Prerequisites
|
|
68
|
+
|
|
69
|
+
- **Node.js >= 18** ([download](https://nodejs.org))
|
|
70
|
+
- A **C++ build toolchain** (required by `better-sqlite3`, the native SQLite addon):
|
|
71
|
+
|
|
72
|
+
| OS | Install |
|
|
73
|
+
|---|---|
|
|
74
|
+
| **Windows** | [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) + Python 3 |
|
|
75
|
+
| **macOS** | `xcode-select --install` |
|
|
76
|
+
| **Linux** | `sudo apt install build-essential python3` |
|
|
77
|
+
|
|
78
|
+
Verify your setup:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
node -v # should print v18.x.x or higher
|
|
82
|
+
npm -v # should print a version number
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Installation
|
|
88
|
+
|
|
89
|
+
### From npm (recommended)
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install -g pollen-transmit
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> On macOS/Linux, you may need `sudo npm install -g pollen-transmit` if you get permission errors.
|
|
96
|
+
|
|
97
|
+
### From source
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
git clone https://github.com/sudogetshivam/pollen-transmit.git
|
|
101
|
+
cd pollen-transmit
|
|
102
|
+
npm install
|
|
103
|
+
npm link # makes the 'pollen' command available globally
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Verify installation:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pollen --version
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Quick Start
|
|
115
|
+
|
|
116
|
+
**1. Start the daemon**
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pollen start
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
On first run, Pollen will ask for a username and generate your identity (e.g., `alice@f7a2`) along with an RSA-2048 keypair. Think of the identity as your Pollen phone number — share it with friends so they can message you.
|
|
123
|
+
|
|
124
|
+
> Keep this terminal window open. The daemon runs in the background, silently discovering peers and relaying messages. Open a new terminal for commands.
|
|
125
|
+
|
|
126
|
+
**2. Find people nearby**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pollen scan
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Lists all Pollen users discovered on your current WiFi or hotspot. If your friend started their daemon nearby, they'll show up here.
|
|
133
|
+
|
|
134
|
+
**3. Send a message**
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pollen send bob@b7d9 "Internet is overrated anyway"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This encrypts and queues the message. If Bob is on your network, it delivers instantly. If not, it starts spreading epidemically through everyone around you. The command returns a **Message ID** for tracking.
|
|
141
|
+
|
|
142
|
+
**4. Check delivery status**
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pollen status <message-id>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Shows one of three states:
|
|
149
|
+
- **Undelivered** — sitting on your device, waiting for carriers
|
|
150
|
+
- **In Transit** — carriers are spreading it across networks
|
|
151
|
+
- **Delivered** — the recipient got it, and an ACK is on its way back to you
|
|
152
|
+
|
|
153
|
+
**5. Read incoming messages**
|
|
154
|
+
|
|
155
|
+
Messages arrive automatically while your daemon runs. Check your inbox:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# macOS / Linux
|
|
159
|
+
cat ~/.pollen/inbox.log
|
|
160
|
+
|
|
161
|
+
# Windows
|
|
162
|
+
type %USERPROFILE%\.pollen\inbox.log
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**6. Stop the daemon**
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pollen stop
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Commands
|
|
174
|
+
|
|
175
|
+
| Command | Description |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `pollen start` | Start the background daemon. First run creates your identity and keys. |
|
|
178
|
+
| `pollen stop` | Shut down the daemon gracefully. |
|
|
179
|
+
| `pollen scan` | Discover Pollen peers on your current network. |
|
|
180
|
+
| `pollen send <identity> "<message>"` | Send an end-to-end encrypted message. |
|
|
181
|
+
| `pollen status <messageId>` | Track a message's delivery journey. |
|
|
182
|
+
| `pollen sync` | Force an immediate epidemic exchange with all known peers. |
|
|
183
|
+
| `pollen file <identity> <filepath>` | Send a file via epidemic routing. |
|
|
184
|
+
| `pollen --version` | Print the installed version. |
|
|
185
|
+
| `pollen --help` | Show all available commands. |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Architecture
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
pollen-transmit/
|
|
193
|
+
├── bin/
|
|
194
|
+
│ └── pollen.js # CLI entry point (Commander.js)
|
|
195
|
+
└── src/
|
|
196
|
+
├── cli/
|
|
197
|
+
│ ├── ipc.js # CLI <-> Daemon communication (IPC client)
|
|
198
|
+
│ └── commands/
|
|
199
|
+
│ ├── start.js # Spawn daemon, create identity on first run
|
|
200
|
+
│ ├── stop.js # Send shutdown signal
|
|
201
|
+
│ ├── scan.js # Query daemon for discovered peers
|
|
202
|
+
│ ├── send.js # Encrypt and queue a message
|
|
203
|
+
│ ├── status.js # Query message delivery status
|
|
204
|
+
│ ├── sync.js # Trigger manual epidemic exchange
|
|
205
|
+
│ └── file.js # File transfer command
|
|
206
|
+
├── identity/
|
|
207
|
+
│ └── index.js # Username + shortID generation (e.g., alice@f7a2)
|
|
208
|
+
├── crypto/
|
|
209
|
+
│ ├── keys.js # RSA-2048 keypair generation and storage
|
|
210
|
+
│ └── encrypt.js # Hybrid AES-256-GCM + RSA-OAEP encryption
|
|
211
|
+
├── db/
|
|
212
|
+
│ ├── index.js # SQLite connection and schema setup
|
|
213
|
+
│ ├── messages.js # Message CRUD + peer key/IP storage
|
|
214
|
+
│ └── cleanup.js # TTL expiry and stale data cleanup
|
|
215
|
+
└── daemon/
|
|
216
|
+
├── index.js # Main daemon process (IPC server, event loop)
|
|
217
|
+
├── network.js # IP polling and network change detection
|
|
218
|
+
├── udp.js # UDP broadcast for peer discovery
|
|
219
|
+
├── tcp.js # TCP for reliable message transfer
|
|
220
|
+
└── epidemic.js # Epidemic routing engine + ACK generation
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### How the pieces fit together
|
|
224
|
+
|
|
225
|
+
The CLI is just a thin client. It does **zero networking**. Every command serializes a request over IPC (Unix socket on Linux/macOS, named pipe on Windows) to the daemon, which does all the heavy lifting.
|
|
226
|
+
|
|
227
|
+
The daemon runs detached in the background and manages:
|
|
228
|
+
|
|
229
|
+
- **UDP broadcast** every 15 seconds for peer discovery — announces your identity and public key
|
|
230
|
+
- **TCP connections** for reliable message transfer between peers
|
|
231
|
+
- **Epidemic forwarding** — continuously spreads pending messages to all known peers
|
|
232
|
+
- **Network change detection** — when you switch WiFi, triggers a fresh peer scan and epidemic exchange
|
|
233
|
+
- **Startup delivery sweep** — on boot, retries all pending messages against known peers
|
|
234
|
+
- **Stale peer cleanup** — removes peers not seen in 3+ minutes
|
|
235
|
+
|
|
236
|
+
### Key Design Decisions
|
|
237
|
+
|
|
238
|
+
| Decision | Why |
|
|
239
|
+
|---|---|
|
|
240
|
+
| IPC via Unix socket / named pipe | No port conflicts, no firewall headaches |
|
|
241
|
+
| Hybrid AES-256-GCM + RSA-OAEP | RSA alone caps at ~190 bytes. Hybrid handles messages of any length. |
|
|
242
|
+
| Daemon spawned detached | CLI exits immediately. Daemon lives independently. Your terminal is free. |
|
|
243
|
+
| `windowsHide: true` | No random console window popping up on Windows (you're welcome) |
|
|
244
|
+
| Max 20 hops | Prevents messages from circulating forever. If it hasn't arrived in 20 hops, it probably won't. |
|
|
245
|
+
| 7-day TTL | Messages expire after a week. Keeps carrier devices from becoming digital hoarders. |
|
|
246
|
+
| TCP with UDP fallback | TCP for reliability, UDP when firewalls get in the way |
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Encryption
|
|
251
|
+
|
|
252
|
+
Every message is end-to-end encrypted. Relay nodes (the strangers carrying your messages) can see *who* the message is for, but **cannot read the contents**. Period.
|
|
253
|
+
|
|
254
|
+
Here's what happens under the hood:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
Sender Relay / Carrier Recipient
|
|
258
|
+
| | |
|
|
259
|
+
| 1. Generate random AES-256 key | |
|
|
260
|
+
| 2. Encrypt message with AES-GCM | |
|
|
261
|
+
| 3. Encrypt AES key with | |
|
|
262
|
+
| recipient's RSA public key | |
|
|
263
|
+
| | |
|
|
264
|
+
|--- encrypted blob (opaque) ------->| |
|
|
265
|
+
| (can see destination, | |
|
|
266
|
+
| cannot read content) |--- encrypted blob ----->|
|
|
267
|
+
| | |
|
|
268
|
+
| | 4. Decrypt AES key |
|
|
269
|
+
| | with private key |
|
|
270
|
+
| | 5. Decrypt message |
|
|
271
|
+
| | with AES key |
|
|
272
|
+
| | |
|
|
273
|
+
|<============ ACK (epidemic route back) ======================|
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
- **AES-256-GCM** handles the actual message encryption (fast, no size limit, authenticated)
|
|
277
|
+
- **RSA-2048 OAEP** wraps the AES key so only the recipient can unwrap it
|
|
278
|
+
- A fresh AES key is generated **per message** — compromising one key reveals nothing about any other message
|
|
279
|
+
|
|
280
|
+
Even if every single relay node was actively malicious, they'd just be carrying encrypted gibberish. The only device that can read the message is the one holding the recipient's private key.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Data Storage
|
|
285
|
+
|
|
286
|
+
Everything lives in `~/.pollen/`:
|
|
287
|
+
|
|
288
|
+
| File/Directory | Purpose |
|
|
289
|
+
|---|---|
|
|
290
|
+
| `identity.json` | Your username and short ID |
|
|
291
|
+
| `keys/public.pem` | Your RSA public key (shared with peers) |
|
|
292
|
+
| `keys/private.pem` | Your RSA private key (never leaves your machine, mode `0600`) |
|
|
293
|
+
| `pollen.db` | SQLite database: messages, peer keys, peer IPs |
|
|
294
|
+
| `inbox.log` | Received messages log |
|
|
295
|
+
| `daemon.pid` | PID of the running daemon process |
|
|
296
|
+
| `daemon.log` | Daemon runtime logs |
|
|
297
|
+
|
|
298
|
+
> Your private key is stored with restricted file permissions. Don't share it. Don't back it up to Google Drive. Don't tattoo it on your arm. Basically, just leave it alone.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Configuration & Limits
|
|
303
|
+
|
|
304
|
+
| Parameter | Value | Notes |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| Max hop count | 20 | Message is dropped after 20 relays |
|
|
307
|
+
| Default TTL | 7 days | Messages expire and get cleaned up |
|
|
308
|
+
| UDP heartbeat | Every 15s | How often your device announces itself |
|
|
309
|
+
| Stale peer timeout | 3 minutes | Peers not seen in 3 min are removed |
|
|
310
|
+
| RSA key size | 2048 bits | Generated once on first `pollen start` |
|
|
311
|
+
| AES key size | 256 bits | Fresh random key per message |
|
|
312
|
+
| AES mode | GCM | Authenticated encryption with 96-bit IV |
|
|
313
|
+
|
|
314
|
+
These are sensible defaults. The hop count prevents messages from bouncing around the network forever (imagine a message that just won't take the hint). The 7-day TTL keeps your database from slowly becoming a digital landfill.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## FAQ
|
|
319
|
+
|
|
320
|
+
**Q: Do both users need to be online at the same time?**
|
|
321
|
+
No. That's the entire point. You send a message, it starts spreading. The recipient can come online days later in a completely different city. As long as the epidemic chain reaches them within the TTL window, they'll get it.
|
|
322
|
+
|
|
323
|
+
**Q: What if nobody is running Pollen near me?**
|
|
324
|
+
Then your message waits patiently on your device. The moment someone running Pollen connects to your network, it copies over. Patience is a virtue, and your message has plenty of it.
|
|
325
|
+
|
|
326
|
+
**Q: Is this actually secure?**
|
|
327
|
+
Yes. AES-256-GCM + RSA-2048 is the same class of encryption used by banks, governments, and that one friend who encrypts their grocery lists. Relay nodes carry your messages but literally cannot read them.
|
|
328
|
+
|
|
329
|
+
**Q: How do I share my identity with someone?**
|
|
330
|
+
The old-fashioned way. Tell them in person, text it, write it on a napkin, send it via carrier pigeon — whatever works. Your identity (`username@shortid`) is not a secret; it's like a phone number.
|
|
331
|
+
|
|
332
|
+
**Q: Can I use this over a mobile hotspot?**
|
|
333
|
+
Absolutely. Any local network works — WiFi, hotspot, even a direct device-to-device connection. Pollen doesn't care how you're connected, just that you are.
|
|
334
|
+
|
|
335
|
+
**Q: Why SQLite?**
|
|
336
|
+
Because it's embedded, requires zero configuration, handles concurrent reads well, and the binary size is smaller than your average npm dependency list. Plus, it won't ghost you with connection pool errors at 3 AM.
|
|
337
|
+
|
|
338
|
+
**Q: What happens if two people send messages at the same time?**
|
|
339
|
+
They both work fine. Messages have unique UUIDs and the epidemic protocol handles deduplication. If a device already has a copy of a message, it rejects the duplicate. No echoes, no infinite loops.
|
|
340
|
+
|
|
341
|
+
**Q: Is this a blockchain?**
|
|
342
|
+
No. Please stop asking. There is no ledger, no mining, no tokens, no gas fees. Just encrypted messages being carried by humans. Like pigeons, but with RSA keys.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Contributing
|
|
347
|
+
|
|
348
|
+
Contributions are welcome. The codebase is intentionally straightforward — no framework soup, no transpilers, no build steps beyond `npm install`. Just Node.js doing what Node.js does.
|
|
349
|
+
|
|
350
|
+
1. Fork the repository
|
|
351
|
+
2. Create your feature branch: `git checkout -b feature/your-feature`
|
|
352
|
+
3. Make your changes
|
|
353
|
+
4. Test locally by running `pollen start` on two machines on the same network
|
|
354
|
+
5. Commit: `git commit -m "Add your feature"`
|
|
355
|
+
6. Push: `git push origin feature/your-feature`
|
|
356
|
+
7. Open a pull request
|
|
357
|
+
|
|
358
|
+
If you find a bug, [open an issue](https://github.com/sudogetshivam/pollen-transmit/issues). If you find a security vulnerability, please **do not** open a public issue — reach out directly.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
[MIT](LICENSE) — do whatever you want with it. Just don't blame us if your epidemic messages accidentally take over a small town's WiFi network.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
<p align="center">
|
|
369
|
+
<strong>Built for a world where the internet isn't guaranteed.</strong><br>
|
|
370
|
+
<em>Because sometimes the most reliable network is the one that walks.</em>
|
|
371
|
+
</p>
|
package/bin/ib.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ib — CLI entry point
|
|
6
|
+
* All commands delegate to the daemon via IPC.
|
|
7
|
+
* The CLI itself does zero networking.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { program } = require('commander');
|
|
11
|
+
const { startCommand } = require('../src/cli/commands/start');
|
|
12
|
+
const { stopCommand } = require('../src/cli/commands/stop');
|
|
13
|
+
const { scanCommand } = require('../src/cli/commands/scan');
|
|
14
|
+
const { sendCommand } = require('../src/cli/commands/send');
|
|
15
|
+
const { statusCommand } = require('../src/cli/commands/status');
|
|
16
|
+
const { syncCommand } = require('../src/cli/commands/sync');
|
|
17
|
+
const { fileCommand } = require('../src/cli/commands/file');
|
|
18
|
+
|
|
19
|
+
const VERSION = require('../package.json').version;
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.name('ib')
|
|
23
|
+
.description(
|
|
24
|
+
'🌍 The Invisible Billion — Fully offline, peer-to-peer epidemic routing messenger.\n' +
|
|
25
|
+
' Messages spread like a virus through human movement. Zero internet.'
|
|
26
|
+
)
|
|
27
|
+
.version(VERSION);
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.command('start')
|
|
31
|
+
.description('Start The Invisible Billion background daemon')
|
|
32
|
+
.action(async () => {
|
|
33
|
+
await startCommand();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.command('stop')
|
|
38
|
+
.description('Stop The Invisible Billion background daemon')
|
|
39
|
+
.action(async () => {
|
|
40
|
+
await stopCommand();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
program
|
|
44
|
+
.command('scan')
|
|
45
|
+
.description('List all IB peers discovered on the current network')
|
|
46
|
+
.action(async () => {
|
|
47
|
+
await scanCommand();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.command('send <identity> <message>')
|
|
52
|
+
.description('Send an encrypted message to an IB user')
|
|
53
|
+
.addHelpText('after', '\nExample:\n ib send raj@a3f2 "bhai notes bhej"')
|
|
54
|
+
.action(async (identity, message) => {
|
|
55
|
+
await sendCommand(identity, message);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
program
|
|
59
|
+
.command('status <messageId>')
|
|
60
|
+
.description('Check delivery status of a message')
|
|
61
|
+
.action(async (messageId) => {
|
|
62
|
+
await statusCommand(messageId);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
program
|
|
66
|
+
.command('sync')
|
|
67
|
+
.description('Manually trigger epidemic sync with peers on current network')
|
|
68
|
+
.action(async () => {
|
|
69
|
+
await syncCommand();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
program
|
|
73
|
+
.command('file <identity> <filepath>')
|
|
74
|
+
.description('Send a file offline via epidemic routing (Phase 5)')
|
|
75
|
+
.addHelpText('after', '\nExample:\n ib file raj@a3f2 ./notes.pdf')
|
|
76
|
+
.action(async (identity, filepath) => {
|
|
77
|
+
await fileCommand(identity, filepath);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
program.on('command:*', () => {
|
|
81
|
+
console.error(`\n❌ Unknown command: ${program.args.join(' ')}\n`);
|
|
82
|
+
program.help();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
program.parse(process.argv);
|
|
86
|
+
|
|
87
|
+
// Show help if no command given
|
|
88
|
+
if (process.argv.length <= 2) {
|
|
89
|
+
program.help();
|
|
90
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "the-invisible-billion-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Fully offline, zero-internet, peer-to-peer epidemic routing CLI messenger — The Invisible Billion",
|
|
5
|
+
"main": "src/daemon/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ib": "bin/ib.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/ib.js start"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"better-sqlite3": "^12.6.2",
|
|
14
|
+
"commander": "^12.1.0"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"p2p",
|
|
21
|
+
"mesh",
|
|
22
|
+
"offline",
|
|
23
|
+
"dtn",
|
|
24
|
+
"epidemic-routing",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT"
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ib file <identity> <filepath>
|
|
5
|
+
*/
|
|
6
|
+
async function fileCommand(destination, filePath) {
|
|
7
|
+
if (!destination || !filePath) {
|
|
8
|
+
console.error('Usage: ib file <identity> <filepath>');
|
|
9
|
+
console.error('Example: ib file raj@a3f2 ./notes.pdf');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
console.log('\n File transfer is coming in Phase 5.');
|
|
14
|
+
console.log(' Phase 5 will support:');
|
|
15
|
+
console.log(' • Chunked file encryption (AES-256-GCM per chunk)');
|
|
16
|
+
console.log(' • Fully offline transfer over TCP, same epidemic routing');
|
|
17
|
+
console.log(' • Automatic reassembly at the destination\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { fileCommand };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { runCommand } = require('../ipc');
|
|
4
|
+
|
|
5
|
+
async function scanCommand() {
|
|
6
|
+
await runCommand({ type: 'scan' }, (res) => {
|
|
7
|
+
if (!res.ok) {
|
|
8
|
+
console.error('❌', res.error);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const peers = res.peers || [];
|
|
13
|
+
if (peers.length === 0) {
|
|
14
|
+
console.log('\n🔍 No IB peers found on current network.\n');
|
|
15
|
+
console.log(' Peers appear automatically when other IB daemons');
|
|
16
|
+
console.log(' start broadcasting on the same WiFi/LAN.\n');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(`\n🌍 IB peers on this network:\n`);
|
|
21
|
+
console.log(' Identity IP Address Last Seen Status');
|
|
22
|
+
console.log(' ───────────────── ──────────────── ──────────── ──────');
|
|
23
|
+
for (const peer of peers) {
|
|
24
|
+
const ago = formatAgo(peer.last_seen);
|
|
25
|
+
const ip = (peer.ip || 'unknown').padEnd(16);
|
|
26
|
+
const status = isOnline(peer.last_seen) ? '🟢 Online' : '⚪ Stale';
|
|
27
|
+
console.log(` ${peer.identity.padEnd(18)} ${ip} ${ago.padEnd(12)} ${status}`);
|
|
28
|
+
}
|
|
29
|
+
console.log(`\n Total: ${peers.length} peer(s)\n`);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isOnline(epochMs) {
|
|
34
|
+
if (!epochMs) return false;
|
|
35
|
+
return (Date.now() - epochMs) < 90_000; // 90 s (6 heartbeats at 15s)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatAgo(epochMs) {
|
|
39
|
+
if (!epochMs) return 'unknown';
|
|
40
|
+
const diff = Date.now() - epochMs;
|
|
41
|
+
const s = Math.floor(diff / 1000);
|
|
42
|
+
if (s < 60) return `${s}s ago`;
|
|
43
|
+
const m = Math.floor(s / 60);
|
|
44
|
+
if (m < 60) return `${m}m ago`;
|
|
45
|
+
const h = Math.floor(m / 60);
|
|
46
|
+
return `${h}h ago`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { scanCommand };
|
|
50
|
+
|