@miraj181/ipingyou 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 +133 -0
- package/package.json +62 -0
- package/src/cli.js +241 -0
- package/src/lib/animations.js +226 -0
- package/src/lib/cleanup.js +131 -0
- package/src/lib/crypto.js +53 -0
- package/src/lib/platform.js +179 -0
- package/src/lib/uid.js +23 -0
- package/src/modes/client.js +322 -0
- package/src/modes/host.js +317 -0
- package/src/server.js +144 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 skmirajulislam
|
|
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,133 @@
|
|
|
1
|
+
# π iPingYou β SecureLink CLI
|
|
2
|
+
|
|
3
|
+
Secure peer-to-peer remote access via SSH & Cloudflare Tunnels. A Node.js CLI tool that lets two machines establish an encrypted SSH connection through Cloudflare's network, coordinated by a lightweight broker.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Host Machine Broker (Express.js) Client Machine
|
|
9
|
+
ββββββββββββββββ POST ββββββββββββββββββββ GET ββββββββββββββββ
|
|
10
|
+
β cloudflared βββ/registerβββΆβ In-Memory Map βββ/resolveβββ ipingyou β
|
|
11
|
+
β tunnel :22 β {uid,url} β AES-256-CBC β /:uid β connect β
|
|
12
|
+
β β β 1hr TTL expiry β β β
|
|
13
|
+
β SSH daemon βββββββββββββββββββββββββββββββββββββββββββββββββ SSH via β
|
|
14
|
+
β β cloudflared β β ProxyCmd β ProxyCmd β
|
|
15
|
+
ββββββββββββββββ TCP proxy ββββββββββββββββββββ ββββββββββββββββ
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Flow
|
|
19
|
+
1. **Host** starts `ipingyou host` β generates an 8-char UID, spins up `cloudflared` tunnel, registers with broker
|
|
20
|
+
2. **Host** shares UID with the client (verbally, chat, etc.)
|
|
21
|
+
3. **Client** runs `ipingyou connect` β enters UID, broker resolves it to tunnel URL β SSH connects via cloudflared proxy
|
|
22
|
+
4. On `Ctrl+C`, all spawned processes are killed via `tree-kill` and the UID is revoked from the broker
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### 1. Start the Broker (self-hosted or Render)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd ipingyou
|
|
30
|
+
cp .env.example .env
|
|
31
|
+
npm install
|
|
32
|
+
npm run broker
|
|
33
|
+
# Broker: http://localhost:4000
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Host Mode (Machine being accessed)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx ipingyou host
|
|
40
|
+
# or interactively:
|
|
41
|
+
npx ipingyou
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Client Mode (Machine accessing)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx ipingyou connect
|
|
48
|
+
# or interactively:
|
|
49
|
+
npx ipingyou
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## CLI Commands
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
ipingyou Interactive mode (prompts for host/client)
|
|
56
|
+
ipingyou host Start as host β allow remote access
|
|
57
|
+
ipingyou connect Start as client β access a remote machine
|
|
58
|
+
ipingyou broker Start the central broker server
|
|
59
|
+
ipingyou broker -p 5000 Start broker on custom port
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Prerequisites
|
|
63
|
+
|
|
64
|
+
| Tool | Required | Install |
|
|
65
|
+
|------|----------|---------|
|
|
66
|
+
| Node.js β₯18 | β
| [nodejs.org](https://nodejs.org) |
|
|
67
|
+
| `ssh` | β
| Ships with macOS/Linux; `winget install Microsoft.OpenSSH.Client` on Windows |
|
|
68
|
+
| `cloudflared` | β
| `brew install cloudflared` / [download](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) |
|
|
69
|
+
|
|
70
|
+
The CLI auto-detects your OS and will attempt to install missing dependencies on Linux (apt/pacman) and guide you on macOS/Windows.
|
|
71
|
+
|
|
72
|
+
## Project Structure
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
ipingyou/
|
|
76
|
+
βββ src/
|
|
77
|
+
β βββ cli.js β Main CLI entry (npx-ready, shebang)
|
|
78
|
+
β βββ server.js β Central Broker (Express.js)
|
|
79
|
+
β βββ lib/
|
|
80
|
+
β β βββ cleanup.js β Graceful shutdown + tree-kill
|
|
81
|
+
β β βββ crypto.js β AES-256-CBC encrypt/decrypt
|
|
82
|
+
β β βββ platform.js β OS detection + dependency check
|
|
83
|
+
β β βββ uid.js β Random 8-char UID generator (nanoid)
|
|
84
|
+
β βββ modes/
|
|
85
|
+
β βββ host.js β Host mode logic
|
|
86
|
+
β βββ client.js β Client mode logic
|
|
87
|
+
βββ .env.example
|
|
88
|
+
βββ .gitignore
|
|
89
|
+
βββ package.json
|
|
90
|
+
βββ render.yaml β One-click Render deploy
|
|
91
|
+
βββ README.md
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Security
|
|
95
|
+
|
|
96
|
+
- **AES-256-CBC** β Tunnel URLs are encrypted at rest in the broker's memory
|
|
97
|
+
- **Random UIDs** β 8-char nanoid, not hardware-based; session dies = door locked forever
|
|
98
|
+
- **1-hour TTL** β Broker auto-expires entries; no stale data
|
|
99
|
+
- **Auto-revoke** β On `Ctrl+C`, UID is deleted from broker before exit
|
|
100
|
+
- **tree-kill** β All child processes (cloudflared, SSH) are recursively killed on shutdown
|
|
101
|
+
- **No persistence** β Broker uses in-memory Map only; server restart = clean slate
|
|
102
|
+
|
|
103
|
+
## Deploy Broker to Render (Free)
|
|
104
|
+
|
|
105
|
+
1. Push this repo to GitHub
|
|
106
|
+
2. Go to [render.com/new](https://render.com/new)
|
|
107
|
+
3. Click **"New Web Service"** β connect your repo
|
|
108
|
+
4. Render auto-detects `render.yaml` β click **Deploy**
|
|
109
|
+
5. Set `SECRET_KEY` in Render Dashboard β Environment
|
|
110
|
+
6. Set `BROKER_URL` in your local `.env` to your Render URL
|
|
111
|
+
|
|
112
|
+
## Environment Variables
|
|
113
|
+
|
|
114
|
+
| Variable | Default | Description |
|
|
115
|
+
|----------|---------|-------------|
|
|
116
|
+
| `SECRET_KEY` | Dev key | AES-256 hex key (64 chars) |
|
|
117
|
+
| `BROKER_URL` | `http://localhost:4000` | Broker endpoint for CLI |
|
|
118
|
+
| `BROKER_PORT` | `4000` | Port for broker server |
|
|
119
|
+
|
|
120
|
+
## Differences from remote-penitrator
|
|
121
|
+
|
|
122
|
+
| Aspect | remote-penitrator | iPingYou |
|
|
123
|
+
|--------|-------------------|----------|
|
|
124
|
+
| Interface | Shell scripts + web dashboard | Interactive Node.js CLI |
|
|
125
|
+
| Direction | One-way (runner β C2) | Peer-to-peer (host β client) |
|
|
126
|
+
| UID System | Hardcoded PC IDs | Random per-session UIDs |
|
|
127
|
+
| Process Mgmt | Manual | tree-kill auto-cleanup |
|
|
128
|
+
| Install | Copy scripts | `npx ipingyou` |
|
|
129
|
+
| Modes | Reporter only | Host + Client + Broker |
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@miraj181/ipingyou",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SecureLink-CLI β Secure peer-to-peer remote access via SSH & Cloudflare Tunnels",
|
|
5
|
+
"main": "src/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ipingyou": "src/cli.js",
|
|
8
|
+
"securelink": "src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node src/cli.js",
|
|
17
|
+
"broker": "node src/server.js",
|
|
18
|
+
"dev:broker": "nodemon src/server.js",
|
|
19
|
+
"dev:cli": "node src/cli.js",
|
|
20
|
+
"prepublishOnly": "node -e \"require('fs').accessSync('LICENSE')\" && node src/cli.js --help"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"ssh",
|
|
24
|
+
"remote-access",
|
|
25
|
+
"cloudflare-tunnel",
|
|
26
|
+
"cloudflared",
|
|
27
|
+
"cli",
|
|
28
|
+
"peer-to-peer",
|
|
29
|
+
"securelink",
|
|
30
|
+
"tunnel",
|
|
31
|
+
"remote-desktop",
|
|
32
|
+
"p2p"
|
|
33
|
+
],
|
|
34
|
+
"author": "skmirajulislam",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/skmirajulislam/ipingyou.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/skmirajulislam/ipingyou#readme",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/skmirajulislam/ipingyou/issues"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"chalk": "^5.3.0",
|
|
46
|
+
"commander": "^12.1.0",
|
|
47
|
+
"dotenv": "^16.4.5",
|
|
48
|
+
"execa": "^9.5.2",
|
|
49
|
+
"express": "^4.21.0",
|
|
50
|
+
"inquirer": "^12.3.2",
|
|
51
|
+
"nanoid": "^5.0.9",
|
|
52
|
+
"ora": "^8.1.1",
|
|
53
|
+
"tree-kill": "^1.2.2"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"nodemon": "^3.1.4"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
},
|
|
61
|
+
"type": "module"
|
|
62
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================
|
|
5
|
+
* SecureLink-CLI (ipingyou)
|
|
6
|
+
* ============================================================
|
|
7
|
+
* Secure peer-to-peer remote access via SSH & Cloudflare
|
|
8
|
+
* Tunnels. Designed to run via npx or as a global install.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx ipingyou β Interactive mode
|
|
12
|
+
* npx ipingyou host β Start as host directly
|
|
13
|
+
* npx ipingyou connect β Start as client directly
|
|
14
|
+
* npx ipingyou broker β Start the central broker
|
|
15
|
+
*
|
|
16
|
+
* Security:
|
|
17
|
+
* All tunnel URLs are AES-256-CBC encrypted on the CLI side.
|
|
18
|
+
* The broker is a zero-knowledge relay β it NEVER sees plaintext.
|
|
19
|
+
* ============================================================
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import 'dotenv/config';
|
|
23
|
+
import { Command } from 'commander';
|
|
24
|
+
import inquirer from 'inquirer';
|
|
25
|
+
import chalk from 'chalk';
|
|
26
|
+
|
|
27
|
+
import { detectOS, checkDependencies } from './lib/platform.js';
|
|
28
|
+
import { installShutdownHandlers } from './lib/cleanup.js';
|
|
29
|
+
import { startHostMode } from './modes/host.js';
|
|
30
|
+
import { startClientMode } from './modes/client.js';
|
|
31
|
+
|
|
32
|
+
// βββ ASCII Banner ββββββββββββββββββββββββββββββββββββββββββββ
|
|
33
|
+
function showBanner() {
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(chalk.cyan.bold(' βββββββββββββββββββββββββββββββββββββββββββββ'));
|
|
36
|
+
console.log(chalk.cyan.bold(' β β'));
|
|
37
|
+
console.log(chalk.cyan.bold(' β') + chalk.white.bold(' π iPingYou β SecureLink CLI ') + chalk.cyan.bold(' β'));
|
|
38
|
+
console.log(chalk.cyan.bold(' β β'));
|
|
39
|
+
console.log(chalk.cyan.bold(' β') + chalk.dim(' Secure P2P Remote Access via SSH + ') + chalk.cyan.bold(' β'));
|
|
40
|
+
console.log(chalk.cyan.bold(' β') + chalk.dim(' Cloudflare Tunnels | AES-256-CBC ') + chalk.cyan.bold(' β'));
|
|
41
|
+
console.log(chalk.cyan.bold(' β β'));
|
|
42
|
+
console.log(chalk.cyan.bold(' βββββββββββββββββββββββββββββββββββββββββββββ'));
|
|
43
|
+
console.log('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function showSystemInfo() {
|
|
47
|
+
const osInfo = detectOS();
|
|
48
|
+
const platform = osInfo.isLinux ? 'π§ Linux' : osInfo.isMac ? 'π macOS' : 'πͺ Windows';
|
|
49
|
+
console.log(chalk.dim(` ${platform} | ${osInfo.arch} | ${osInfo.hostname} | Node ${process.version}`));
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function showRichHelp() {
|
|
54
|
+
console.log(chalk.bold.yellow(' β¨ Welcome to iPingYou SecureLink CLI! β¨'));
|
|
55
|
+
console.log(chalk.dim(' βββββββββββββββββββββββββββββββββββββββββββββββββββββββ'));
|
|
56
|
+
console.log(chalk.cyan(' A zero-knowledge peer-to-peer remote access tool.'));
|
|
57
|
+
console.log(chalk.cyan(' Securely share your local SSH terminal with anyone over the internet.'));
|
|
58
|
+
console.log('');
|
|
59
|
+
|
|
60
|
+
console.log(chalk.bold.white(' π Usage Modes:'));
|
|
61
|
+
console.log(` ${chalk.green('host')} : Generates a secure session UID and exposes your local machine.`);
|
|
62
|
+
console.log(` ${chalk.blue('connect')} : Prompts for a UID to connect to a remote host.`);
|
|
63
|
+
console.log(` ${chalk.dim('Supports Interactive SSH Shell & SCP File Transfers')}`);
|
|
64
|
+
console.log(` ${chalk.yellow('broker')} : Start your own relay server (for self-hosting).`);
|
|
65
|
+
console.log('');
|
|
66
|
+
|
|
67
|
+
console.log(chalk.bold.white(' π Security Architecture:'));
|
|
68
|
+
console.log(` β’ Cloudflare Tunnels punch through NAT/Firewalls securely.`);
|
|
69
|
+
console.log(` β’ ${chalk.green('End-to-End Encryption')}: Tunnel URLs are AES-256 encrypted locally.`);
|
|
70
|
+
console.log(` β’ The Broker never sees your plaintext URL, only ciphertext.`);
|
|
71
|
+
console.log('');
|
|
72
|
+
|
|
73
|
+
console.log(chalk.bold.white(' π‘ Examples:'));
|
|
74
|
+
console.log(` $ npx ipingyou ${chalk.dim('# Interactive wizard (Recommended)')}`);
|
|
75
|
+
console.log(` $ npx ipingyou host ${chalk.dim('# Quick start as Host')}`);
|
|
76
|
+
console.log(` $ npx ipingyou connect ${chalk.dim('# Quick start as Client')}`);
|
|
77
|
+
console.log(` $ npx ipingyou broker ${chalk.dim('# Start Relay')}`);
|
|
78
|
+
console.log('');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Fatal error handler β logs and exits with code 1.
|
|
83
|
+
* @param {string} context β which command/mode failed
|
|
84
|
+
* @param {Error} err β the error object
|
|
85
|
+
*/
|
|
86
|
+
function fatal(context, err) {
|
|
87
|
+
console.error('');
|
|
88
|
+
console.error(chalk.red(` β FATAL [${context}]`));
|
|
89
|
+
console.error(chalk.red(` ${err.message}`));
|
|
90
|
+
if (err.stack) {
|
|
91
|
+
const stackLines = err.stack.split('\n').slice(1, 4);
|
|
92
|
+
stackLines.forEach(line => console.error(chalk.dim(` ${line.trim()}`)));
|
|
93
|
+
}
|
|
94
|
+
console.error('');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// βββ Interactive Mode Selection ββββββββββββββββββββββββββββββ
|
|
99
|
+
async function interactiveMode() {
|
|
100
|
+
showBanner();
|
|
101
|
+
showSystemInfo();
|
|
102
|
+
|
|
103
|
+
// Check dependencies first
|
|
104
|
+
const deps = await checkDependencies();
|
|
105
|
+
|
|
106
|
+
if (!deps.ssh || !deps.cloudflared) {
|
|
107
|
+
const { proceed } = await inquirer.prompt([
|
|
108
|
+
{
|
|
109
|
+
type: 'confirm',
|
|
110
|
+
name: 'proceed',
|
|
111
|
+
message: 'Some dependencies are missing. Continue anyway?',
|
|
112
|
+
default: false,
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
if (!proceed) {
|
|
116
|
+
console.log(chalk.dim(' Install the missing tools and try again.'));
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Mode selection
|
|
122
|
+
const { mode } = await inquirer.prompt([
|
|
123
|
+
{
|
|
124
|
+
type: 'list',
|
|
125
|
+
name: 'mode',
|
|
126
|
+
message: 'What would you like to do?',
|
|
127
|
+
choices: [
|
|
128
|
+
{
|
|
129
|
+
name: `${chalk.green('π‘οΈ Allow Remote Access')} ${chalk.dim('β Let someone connect to this machine')}`,
|
|
130
|
+
value: 'host',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: `${chalk.blue('π Access a Remote Machine')} ${chalk.dim('β Connect to a host via their UID (SSH/SCP)')}`,
|
|
134
|
+
value: 'client',
|
|
135
|
+
},
|
|
136
|
+
new inquirer.Separator(),
|
|
137
|
+
{
|
|
138
|
+
name: `${chalk.yellow('π‘ Start Broker Server')} ${chalk.dim('β Run the central relay (for self-hosting)')}`,
|
|
139
|
+
value: 'broker',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: `${chalk.magenta('π Help / Information')} ${chalk.dim('β Learn how iPingYou works')}`,
|
|
143
|
+
value: 'help',
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
switch (mode) {
|
|
150
|
+
case 'host':
|
|
151
|
+
await startHostMode();
|
|
152
|
+
break;
|
|
153
|
+
case 'client':
|
|
154
|
+
await startClientMode();
|
|
155
|
+
break;
|
|
156
|
+
case 'broker':
|
|
157
|
+
await startBroker();
|
|
158
|
+
break;
|
|
159
|
+
case 'help':
|
|
160
|
+
showRichHelp();
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// βββ Broker Start ββββββββββββββββββββββββββββββββββββββββββββ
|
|
166
|
+
async function startBroker() {
|
|
167
|
+
console.log(chalk.cyan(' Starting broker server...'));
|
|
168
|
+
console.log('');
|
|
169
|
+
// Dynamically import the server (it self-starts on import)
|
|
170
|
+
await import('./server.js');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// βββ Commander Setup βββββββββββββββββββββββββββββββββββββββββ
|
|
174
|
+
const program = new Command();
|
|
175
|
+
|
|
176
|
+
program
|
|
177
|
+
.name('ipingyou')
|
|
178
|
+
.description('SecureLink-CLI β Secure P2P remote access via SSH & Cloudflare Tunnels')
|
|
179
|
+
.version('1.0.0')
|
|
180
|
+
.addHelpText('beforeAll', () => {
|
|
181
|
+
showBanner();
|
|
182
|
+
showRichHelp();
|
|
183
|
+
return '';
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
program
|
|
187
|
+
.command('host')
|
|
188
|
+
.description('Start host mode β allow remote access to this machine')
|
|
189
|
+
.action(async () => {
|
|
190
|
+
try {
|
|
191
|
+
showBanner();
|
|
192
|
+
showSystemInfo();
|
|
193
|
+
installShutdownHandlers();
|
|
194
|
+
await checkDependencies();
|
|
195
|
+
await startHostMode();
|
|
196
|
+
} catch (err) {
|
|
197
|
+
fatal('host', err);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
program
|
|
202
|
+
.command('connect')
|
|
203
|
+
.description('Connect to a remote machine via its UID (SSH or SCP)')
|
|
204
|
+
.option('-u, --uid <uid>', 'The remote host UID')
|
|
205
|
+
.action(async () => {
|
|
206
|
+
try {
|
|
207
|
+
showBanner();
|
|
208
|
+
showSystemInfo();
|
|
209
|
+
installShutdownHandlers();
|
|
210
|
+
await checkDependencies();
|
|
211
|
+
await startClientMode();
|
|
212
|
+
} catch (err) {
|
|
213
|
+
fatal('connect', err);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
program
|
|
218
|
+
.command('broker')
|
|
219
|
+
.description('Start the central broker server')
|
|
220
|
+
.option('-p, --port <port>', 'Port to listen on', '4000')
|
|
221
|
+
.action(async (opts) => {
|
|
222
|
+
try {
|
|
223
|
+
if (opts.port) process.env.BROKER_PORT = opts.port;
|
|
224
|
+
showBanner();
|
|
225
|
+
await startBroker();
|
|
226
|
+
} catch (err) {
|
|
227
|
+
fatal('broker', err);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// βββ Default: interactive mode ββββββββββββββββββββββββββββββ
|
|
232
|
+
program.action(async () => {
|
|
233
|
+
try {
|
|
234
|
+
installShutdownHandlers();
|
|
235
|
+
await interactiveMode();
|
|
236
|
+
} catch (err) {
|
|
237
|
+
fatal('interactive', err);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
program.parse();
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* CLI Animations & Visual Effects
|
|
4
|
+
* ============================================================
|
|
5
|
+
* Custom spinners, progress bars, and animated text effects
|
|
6
|
+
* for a premium CLI experience.
|
|
7
|
+
* ============================================================
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
|
|
13
|
+
// βββ Custom Spinner Frames ββββββββββββββββββββββββββββββββββββ
|
|
14
|
+
|
|
15
|
+
/** Cyber-themed spinner for encryption operations */
|
|
16
|
+
export const cryptoSpinner = {
|
|
17
|
+
interval: 100,
|
|
18
|
+
frames: [
|
|
19
|
+
chalk.green('π β°β±β±β±β±β±β±'),
|
|
20
|
+
chalk.green('π β°β°β±β±β±β±β±'),
|
|
21
|
+
chalk.green('π β°β°β°β±β±β±β±'),
|
|
22
|
+
chalk.green('π β°β°β°β°β±β±β±'),
|
|
23
|
+
chalk.green('π β°β°β°β°β°β±β±'),
|
|
24
|
+
chalk.green('π β°β°β°β°β°β°β±'),
|
|
25
|
+
chalk.green('π β°β°β°β°β°β°β°'),
|
|
26
|
+
chalk.cyan('π β°β°β°β°β°β°β°'),
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Network pulse for connection operations */
|
|
31
|
+
export const networkSpinner = {
|
|
32
|
+
interval: 120,
|
|
33
|
+
frames: [
|
|
34
|
+
chalk.cyan('π‘ Β· '),
|
|
35
|
+
chalk.cyan('π‘ Β·Β· '),
|
|
36
|
+
chalk.cyan('π‘ Β·Β·Β· '),
|
|
37
|
+
chalk.cyan('π‘ Β·Β·Β·Β· '),
|
|
38
|
+
chalk.cyan('π‘ Β·Β·Β·Β·Β· '),
|
|
39
|
+
chalk.cyan('π‘ Β·Β·Β·Β·Β·Β·'),
|
|
40
|
+
chalk.blue('π‘ ββββββ'),
|
|
41
|
+
chalk.green('π‘ ββββββΊ'),
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Tunnel establishment animation */
|
|
46
|
+
export const tunnelSpinner = {
|
|
47
|
+
interval: 150,
|
|
48
|
+
frames: [
|
|
49
|
+
chalk.yellow('π βΈ '),
|
|
50
|
+
chalk.yellow('π ββΈ '),
|
|
51
|
+
chalk.yellow('π βββΈ '),
|
|
52
|
+
chalk.cyan('π ββββΈ '),
|
|
53
|
+
chalk.cyan('π βββββΈ '),
|
|
54
|
+
chalk.blue('π ββββββΈ '),
|
|
55
|
+
chalk.blue('π βββββββΈ '),
|
|
56
|
+
chalk.green('π ββββββββΊ'),
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** File transfer animation */
|
|
61
|
+
export const fileTransferSpinner = {
|
|
62
|
+
interval: 100,
|
|
63
|
+
frames: [
|
|
64
|
+
chalk.yellow('π¦ [ ] 0% '),
|
|
65
|
+
chalk.yellow('π¦ [β ] 10% '),
|
|
66
|
+
chalk.yellow('π¦ [ββ ] 20% '),
|
|
67
|
+
chalk.cyan( 'π¦ [βββ ] 30% '),
|
|
68
|
+
chalk.cyan( 'π¦ [ββββ ] 40% '),
|
|
69
|
+
chalk.cyan( 'π¦ [βββββ ] 50% '),
|
|
70
|
+
chalk.blue( 'π¦ [ββββββ ] 60% '),
|
|
71
|
+
chalk.blue( 'π¦ [βββββββ ] 70% '),
|
|
72
|
+
chalk.green( 'π¦ [ββββββββ ] 80% '),
|
|
73
|
+
chalk.green( 'π¦ [βββββββββ ] 90% '),
|
|
74
|
+
chalk.green( 'π¦ [ββββββββββ] 100%'),
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** SSH handshake animation */
|
|
79
|
+
export const sshSpinner = {
|
|
80
|
+
interval: 130,
|
|
81
|
+
frames: [
|
|
82
|
+
chalk.yellow('π Β· '),
|
|
83
|
+
chalk.yellow('π Β·Β· '),
|
|
84
|
+
chalk.cyan( 'π Β·Β·Β· '),
|
|
85
|
+
chalk.cyan( 'π Β·Β·Β·Β· '),
|
|
86
|
+
chalk.blue( 'π Β·Β·Β·Β·Β· '),
|
|
87
|
+
chalk.blue( 'π Β·Β·Β·Β·Β·Β· '),
|
|
88
|
+
chalk.green( 'π Β·Β·Β·Β·Β·Β·Β· '),
|
|
89
|
+
chalk.green( 'π β verified'),
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// βββ Animated Spinner Helpers βββββββββββββββββββββββββββββββββ
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create a styled ora spinner with a custom animation.
|
|
97
|
+
* @param {string} text β spinner label
|
|
98
|
+
* @param {object} spinnerDef β { interval, frames }
|
|
99
|
+
* @returns {import('ora').Ora}
|
|
100
|
+
*/
|
|
101
|
+
export function createSpinner(text, spinnerDef) {
|
|
102
|
+
return ora({ text, spinner: spinnerDef });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Animated multi-step progress display.
|
|
107
|
+
* Runs through each step with a delay and animation.
|
|
108
|
+
*
|
|
109
|
+
* @param {Array<{text: string, duration: number, spinner?: object}>} steps
|
|
110
|
+
*/
|
|
111
|
+
export async function animatedSteps(steps) {
|
|
112
|
+
for (const step of steps) {
|
|
113
|
+
const spinnerDef = step.spinner || networkSpinner;
|
|
114
|
+
const spinner = ora({ text: step.text, spinner: spinnerDef }).start();
|
|
115
|
+
await sleep(step.duration);
|
|
116
|
+
spinner.succeed(step.text);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Display an animated connection trace.
|
|
122
|
+
* Shows a visual path from local β tunnel β remote.
|
|
123
|
+
*/
|
|
124
|
+
export async function showConnectionTrace(localLabel, remoteLabel) {
|
|
125
|
+
const width = 40;
|
|
126
|
+
const steps = [
|
|
127
|
+
{ pos: 0, char: 'βΈ', color: chalk.yellow },
|
|
128
|
+
{ pos: 5, char: 'βββΈ', color: chalk.yellow },
|
|
129
|
+
{ pos: 10, char: 'βββββΈ', color: chalk.cyan },
|
|
130
|
+
{ pos: 15, char: 'βββββββΈ', color: chalk.cyan },
|
|
131
|
+
{ pos: 20, char: 'βββββββββΈ', color: chalk.blue },
|
|
132
|
+
{ pos: 25, char: 'βββββββββββΈ', color: chalk.blue },
|
|
133
|
+
{ pos: 30, char: 'βββββββββββββΈ', color: chalk.green },
|
|
134
|
+
{ pos: 35, char: 'βββββββββββββββΊ', color: chalk.green },
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(chalk.dim(' βββββββββββββββββββββββββββββββββββββββββββββββββββββββ'));
|
|
139
|
+
console.log(chalk.dim(' β') + ` ${chalk.cyan(localLabel)} β ${chalk.magenta('β Cloudflare')} β ${chalk.green(remoteLabel)} ` + chalk.dim('β'));
|
|
140
|
+
console.log(chalk.dim(' βββββββββββββββββββββββββββββββββββββββββββββββββββββββ'));
|
|
141
|
+
|
|
142
|
+
for (const step of steps) {
|
|
143
|
+
const line = ` ${step.color(step.char)}`;
|
|
144
|
+
process.stdout.write(`\r${line}`);
|
|
145
|
+
await sleep(120);
|
|
146
|
+
}
|
|
147
|
+
process.stdout.write('\r' + ' '.repeat(60) + '\r');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Show a pulsing "LIVE" indicator.
|
|
152
|
+
* @param {string} message
|
|
153
|
+
* @param {number} pulses β how many times to pulse
|
|
154
|
+
*/
|
|
155
|
+
export async function pulseText(message, pulses = 3) {
|
|
156
|
+
const colors = [chalk.red.bold, chalk.red, chalk.yellow, chalk.green, chalk.green.bold];
|
|
157
|
+
for (let i = 0; i < pulses; i++) {
|
|
158
|
+
for (const colorFn of colors) {
|
|
159
|
+
process.stdout.write(`\r ${colorFn('β')} ${message}`);
|
|
160
|
+
await sleep(100);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
process.stdout.write(`\r ${chalk.green.bold('β')} ${message}\n`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Typing effect β prints text character by character.
|
|
168
|
+
* @param {string} text
|
|
169
|
+
* @param {number} charDelay β ms per character
|
|
170
|
+
*/
|
|
171
|
+
export async function typeText(text, charDelay = 25) {
|
|
172
|
+
for (const char of text) {
|
|
173
|
+
process.stdout.write(char);
|
|
174
|
+
await sleep(charDelay);
|
|
175
|
+
}
|
|
176
|
+
console.log('');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Draw a gradient progress bar.
|
|
181
|
+
* @param {number} percent β 0-100
|
|
182
|
+
* @param {number} width β bar width in chars
|
|
183
|
+
*/
|
|
184
|
+
export function progressBar(percent, width = 30) {
|
|
185
|
+
const filled = Math.round((percent / 100) * width);
|
|
186
|
+
const empty = width - filled;
|
|
187
|
+
const filledStr = 'β'.repeat(filled);
|
|
188
|
+
const emptyStr = 'β'.repeat(empty);
|
|
189
|
+
|
|
190
|
+
let coloredFilled;
|
|
191
|
+
if (percent < 30) coloredFilled = chalk.red(filledStr);
|
|
192
|
+
else if (percent < 60) coloredFilled = chalk.yellow(filledStr);
|
|
193
|
+
else if (percent < 90) coloredFilled = chalk.cyan(filledStr);
|
|
194
|
+
else coloredFilled = chalk.green(filledStr);
|
|
195
|
+
|
|
196
|
+
return `${coloredFilled}${chalk.dim(emptyStr)} ${chalk.white.bold(`${percent}%`)}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Simulate a file transfer progress animation.
|
|
201
|
+
* @param {string} filename
|
|
202
|
+
* @param {string} direction β 'upload' or 'download'
|
|
203
|
+
* @param {number} durationMs β total animation time
|
|
204
|
+
*/
|
|
205
|
+
export async function simulateTransferProgress(filename, direction, durationMs = 2000) {
|
|
206
|
+
const icon = direction === 'upload' ? 'π€' : 'π₯';
|
|
207
|
+
const verb = direction === 'upload' ? 'Sending' : 'Receiving';
|
|
208
|
+
const steps = 20;
|
|
209
|
+
const stepDelay = durationMs / steps;
|
|
210
|
+
|
|
211
|
+
console.log('');
|
|
212
|
+
for (let i = 0; i <= steps; i++) {
|
|
213
|
+
const pct = Math.round((i / steps) * 100);
|
|
214
|
+
const bar = progressBar(pct);
|
|
215
|
+
process.stdout.write(`\r ${icon} ${verb} ${chalk.white.bold(filename)} ${bar}`);
|
|
216
|
+
await sleep(stepDelay);
|
|
217
|
+
}
|
|
218
|
+
console.log('');
|
|
219
|
+
console.log(` ${chalk.green('β')} ${verb} complete: ${chalk.cyan(filename)}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// βββ Utility ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
223
|
+
|
|
224
|
+
function sleep(ms) {
|
|
225
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
226
|
+
}
|