netsweep 1.0.0 → 1.0.1
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/dist/index.js +7546 -0
- package/package.json +9 -7
- package/src/commands/scan.ts +0 -107
- package/src/index.ts +0 -52
- package/src/scanners/connection.ts +0 -33
- package/src/scanners/devices.ts +0 -76
- package/src/scanners/ports.ts +0 -66
- package/src/scanners/speed.ts +0 -85
- package/src/ui/output.ts +0 -131
- package/src/ui/spinner.ts +0 -29
- package/src/utils/exec.ts +0 -10
- package/src/utils/format.ts +0 -18
- package/src/utils/vendor.ts +0 -1442
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netsweep",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Network Swiss Army Knife - CLI tool for comprehensive network diagnostics",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
7
|
+
"netsweep": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"
|
|
10
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"start": "node dist/index.js",
|
|
11
13
|
"dev": "bun run --watch src/index.ts"
|
|
12
14
|
},
|
|
13
15
|
"keywords": [
|
|
@@ -31,11 +33,10 @@
|
|
|
31
33
|
},
|
|
32
34
|
"homepage": "https://github.com/Johannes-Berggren/netprobe#readme",
|
|
33
35
|
"engines": {
|
|
34
|
-
"node": ">=18"
|
|
35
|
-
"bun": ">=1.0"
|
|
36
|
+
"node": ">=18"
|
|
36
37
|
},
|
|
37
38
|
"files": [
|
|
38
|
-
"
|
|
39
|
+
"dist",
|
|
39
40
|
"README.md"
|
|
40
41
|
],
|
|
41
42
|
"dependencies": {
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"ora": "^8.0.1"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
|
-
"@types/bun": "latest"
|
|
49
|
+
"@types/bun": "latest",
|
|
50
|
+
"@types/node": "^20.0.0"
|
|
49
51
|
}
|
|
50
52
|
}
|
package/src/commands/scan.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { getConnectionInfo } from '../scanners/connection';
|
|
2
|
-
import { scanDevices } from '../scanners/devices';
|
|
3
|
-
import { runSpeedTest } from '../scanners/speed';
|
|
4
|
-
import { scanPorts } from '../scanners/ports';
|
|
5
|
-
import {
|
|
6
|
-
header,
|
|
7
|
-
connectionSection,
|
|
8
|
-
speedSection,
|
|
9
|
-
devicesSection,
|
|
10
|
-
portsSection,
|
|
11
|
-
outputJson,
|
|
12
|
-
type ScanResults,
|
|
13
|
-
} from '../ui/output';
|
|
14
|
-
import { startSpinner, updateSpinner, stopSpinner } from '../ui/spinner';
|
|
15
|
-
|
|
16
|
-
export interface ScanOptions {
|
|
17
|
-
devices: boolean;
|
|
18
|
-
speed: boolean;
|
|
19
|
-
ports: boolean;
|
|
20
|
-
json: boolean;
|
|
21
|
-
target?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function scan(options: ScanOptions): Promise<void> {
|
|
25
|
-
const results: ScanResults = {};
|
|
26
|
-
|
|
27
|
-
// Always get connection info first (needed for other scans)
|
|
28
|
-
if (!options.json) {
|
|
29
|
-
startSpinner('Getting connection info...');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
results.connection = await getConnectionInfo();
|
|
34
|
-
results.gateway = options.target || results.connection.gateway;
|
|
35
|
-
} catch (error) {
|
|
36
|
-
stopSpinner();
|
|
37
|
-
if (!options.json) {
|
|
38
|
-
console.error('Failed to get connection info');
|
|
39
|
-
}
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Run device scan
|
|
44
|
-
if (options.devices) {
|
|
45
|
-
if (!options.json) {
|
|
46
|
-
updateSpinner('Scanning for devices...');
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
results.devices = await scanDevices(results.connection.localIP);
|
|
50
|
-
} catch {
|
|
51
|
-
// Continue with other scans
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Run speed test
|
|
56
|
-
if (options.speed) {
|
|
57
|
-
if (!options.json) {
|
|
58
|
-
updateSpinner('Running speed test...');
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
results.speed = await runSpeedTest((stage) => {
|
|
62
|
-
if (!options.json) {
|
|
63
|
-
updateSpinner(stage);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
} catch {
|
|
67
|
-
// Continue with other scans
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Run port scan
|
|
72
|
-
if (options.ports && results.gateway) {
|
|
73
|
-
if (!options.json) {
|
|
74
|
-
updateSpinner(`Scanning ports on ${results.gateway}...`);
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
results.ports = await scanPorts(results.gateway);
|
|
78
|
-
} catch {
|
|
79
|
-
// Continue
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
stopSpinner();
|
|
84
|
-
|
|
85
|
-
// Output results
|
|
86
|
-
if (options.json) {
|
|
87
|
-
outputJson(results);
|
|
88
|
-
} else {
|
|
89
|
-
header();
|
|
90
|
-
|
|
91
|
-
if (results.connection) {
|
|
92
|
-
connectionSection(results.connection);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (results.speed) {
|
|
96
|
-
speedSection(results.speed);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (results.devices) {
|
|
100
|
-
devicesSection(results.devices);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (results.ports !== undefined && results.gateway) {
|
|
104
|
-
portsSection(results.ports, results.gateway);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { scan } from './commands/scan';
|
|
3
|
-
|
|
4
|
-
const args = process.argv.slice(2);
|
|
5
|
-
|
|
6
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
7
|
-
console.log(`
|
|
8
|
-
${'\x1b[36m'}netprobe${'\x1b[0m'} - Network Swiss Army Knife
|
|
9
|
-
|
|
10
|
-
${'\x1b[1m'}Usage:${'\x1b[0m'} netprobe [options]
|
|
11
|
-
|
|
12
|
-
${'\x1b[1m'}Options:${'\x1b[0m'}
|
|
13
|
-
--all, -a Run all scans (default)
|
|
14
|
-
--devices, -d Only scan for devices
|
|
15
|
-
--speed, -s Only run speed test
|
|
16
|
-
--ports, -p Only scan gateway ports
|
|
17
|
-
--target, -t <ip> Scan specific IP for ports
|
|
18
|
-
--json Output as JSON
|
|
19
|
-
--help, -h Show help
|
|
20
|
-
|
|
21
|
-
${'\x1b[1m'}Examples:${'\x1b[0m'}
|
|
22
|
-
netprobe Full network scan
|
|
23
|
-
netprobe -d List network devices only
|
|
24
|
-
netprobe -s Speed test only
|
|
25
|
-
netprobe -p Scan gateway ports
|
|
26
|
-
netprobe -p -t 192.168.0.7 Scan ports on specific host
|
|
27
|
-
netprobe --json Output results as JSON
|
|
28
|
-
`);
|
|
29
|
-
process.exit(0);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Parse target option
|
|
33
|
-
let target: string | undefined;
|
|
34
|
-
const targetIndex = args.findIndex(a => a === '-t' || a === '--target');
|
|
35
|
-
if (targetIndex !== -1 && args[targetIndex + 1]) {
|
|
36
|
-
target = args[targetIndex + 1];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Determine which scans to run
|
|
40
|
-
const hasSpecificFlags = args.includes('-d') || args.includes('--devices') ||
|
|
41
|
-
args.includes('-s') || args.includes('--speed') ||
|
|
42
|
-
args.includes('-p') || args.includes('--ports');
|
|
43
|
-
|
|
44
|
-
const runAll = args.includes('-a') || args.includes('--all') || !hasSpecificFlags;
|
|
45
|
-
|
|
46
|
-
await scan({
|
|
47
|
-
devices: runAll || args.includes('-d') || args.includes('--devices'),
|
|
48
|
-
speed: runAll || args.includes('-s') || args.includes('--speed'),
|
|
49
|
-
ports: runAll || args.includes('-p') || args.includes('--ports'),
|
|
50
|
-
json: args.includes('--json'),
|
|
51
|
-
target,
|
|
52
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { exec } from '../utils/exec';
|
|
2
|
-
|
|
3
|
-
export interface ConnectionInfo {
|
|
4
|
-
interface: string;
|
|
5
|
-
localIP: string;
|
|
6
|
-
gateway: string;
|
|
7
|
-
externalIP: string;
|
|
8
|
-
dns: string[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function getConnectionInfo(): Promise<ConnectionInfo> {
|
|
12
|
-
// Try to find the active interface
|
|
13
|
-
const [localIPEn0, localIPEn1, gateway, externalIP, dns] = await Promise.all([
|
|
14
|
-
exec('ipconfig getifaddr en0'),
|
|
15
|
-
exec('ipconfig getifaddr en1'),
|
|
16
|
-
exec("netstat -nr | grep default | head -1 | awk '{print $2}'"),
|
|
17
|
-
fetch('https://api.ipify.org', { signal: AbortSignal.timeout(5000) })
|
|
18
|
-
.then(r => r.text())
|
|
19
|
-
.catch(() => 'Unknown'),
|
|
20
|
-
exec("scutil --dns | grep 'nameserver\\[' | head -4 | awk '{print $3}'"),
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
const localIP = localIPEn0.trim() || localIPEn1.trim() || 'Unknown';
|
|
24
|
-
const interfaceName = localIPEn0.trim() ? 'Wi-Fi (en0)' : localIPEn1.trim() ? 'Ethernet (en1)' : 'Unknown';
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
interface: interfaceName,
|
|
28
|
-
localIP,
|
|
29
|
-
gateway: gateway.trim() || 'Unknown',
|
|
30
|
-
externalIP: externalIP.trim(),
|
|
31
|
-
dns: dns.trim().split('\n').filter(Boolean),
|
|
32
|
-
};
|
|
33
|
-
}
|
package/src/scanners/devices.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { exec } from '../utils/exec';
|
|
2
|
-
import { lookupVendor } from '../utils/vendor';
|
|
3
|
-
|
|
4
|
-
export interface Device {
|
|
5
|
-
ip: string;
|
|
6
|
-
mac: string;
|
|
7
|
-
vendor: string;
|
|
8
|
-
hostname?: string;
|
|
9
|
-
isCurrentDevice?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Normalize MAC address to have leading zeros (0:17:88 -> 00:17:88)
|
|
13
|
-
function normalizeMac(mac: string): string {
|
|
14
|
-
return mac
|
|
15
|
-
.split(':')
|
|
16
|
-
.map(part => part.padStart(2, '0'))
|
|
17
|
-
.join(':')
|
|
18
|
-
.toUpperCase();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Check if IP is a multicast or broadcast address
|
|
22
|
-
function isSpecialAddress(ip: string): boolean {
|
|
23
|
-
const firstOctet = parseInt(ip.split('.')[0]);
|
|
24
|
-
// Filter multicast (224-239), broadcast (.255), and link-local (169.254)
|
|
25
|
-
if (firstOctet >= 224 && firstOctet <= 239) return true;
|
|
26
|
-
if (ip.endsWith('.255')) return true;
|
|
27
|
-
if (ip.startsWith('169.254.')) return true;
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function scanDevices(localIP?: string): Promise<Device[]> {
|
|
32
|
-
// Get current device's MAC
|
|
33
|
-
const ifconfigOutput = await exec('ifconfig en0 | grep ether');
|
|
34
|
-
const currentMacMatch = ifconfigOutput.match(/ether\s+([0-9a-f:]+)/i);
|
|
35
|
-
const currentMac = currentMacMatch ? normalizeMac(currentMacMatch[1]) : '';
|
|
36
|
-
|
|
37
|
-
// Read ARP table
|
|
38
|
-
const arpOutput = await exec('arp -a');
|
|
39
|
-
|
|
40
|
-
// Parse: "? (192.168.0.1) at f0:81:75:22:32:22 on en0 [ethernet]"
|
|
41
|
-
// or: "hostname (192.168.0.1) at f0:81:75:22:32:22 on en0 [ethernet]"
|
|
42
|
-
const devices = arpOutput
|
|
43
|
-
.split('\n')
|
|
44
|
-
.filter(line => line.includes(' at ') && !line.includes('incomplete'))
|
|
45
|
-
.map(line => {
|
|
46
|
-
const ipMatch = line.match(/\(([0-9.]+)\)/);
|
|
47
|
-
const macMatch = line.match(/at ([0-9a-f:]+)/i);
|
|
48
|
-
const hostMatch = line.match(/^(\S+)\s+\(/);
|
|
49
|
-
|
|
50
|
-
if (!ipMatch || !macMatch) return null;
|
|
51
|
-
|
|
52
|
-
const ip = ipMatch[1];
|
|
53
|
-
|
|
54
|
-
// Skip multicast and broadcast addresses
|
|
55
|
-
if (isSpecialAddress(ip)) return null;
|
|
56
|
-
|
|
57
|
-
const mac = normalizeMac(macMatch[1]);
|
|
58
|
-
const isCurrentDevice = localIP === ip || mac === currentMac;
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
ip,
|
|
62
|
-
mac,
|
|
63
|
-
vendor: lookupVendor(mac),
|
|
64
|
-
hostname: hostMatch?.[1] !== '?' ? hostMatch[1] : undefined,
|
|
65
|
-
isCurrentDevice,
|
|
66
|
-
};
|
|
67
|
-
})
|
|
68
|
-
.filter(Boolean) as Device[];
|
|
69
|
-
|
|
70
|
-
// Sort by IP numerically
|
|
71
|
-
return devices.sort((a, b) => {
|
|
72
|
-
const aNum = a.ip.split('.').reduce((acc, n, i) => acc + parseInt(n) * Math.pow(256, 3 - i), 0);
|
|
73
|
-
const bNum = b.ip.split('.').reduce((acc, n, i) => acc + parseInt(n) * Math.pow(256, 3 - i), 0);
|
|
74
|
-
return aNum - bNum;
|
|
75
|
-
});
|
|
76
|
-
}
|
package/src/scanners/ports.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { connect, type Socket } from 'net';
|
|
2
|
-
|
|
3
|
-
const COMMON_PORTS: Record<number, string> = {
|
|
4
|
-
21: 'FTP',
|
|
5
|
-
22: 'SSH',
|
|
6
|
-
23: 'Telnet',
|
|
7
|
-
25: 'SMTP',
|
|
8
|
-
53: 'DNS',
|
|
9
|
-
80: 'HTTP',
|
|
10
|
-
110: 'POP3',
|
|
11
|
-
143: 'IMAP',
|
|
12
|
-
443: 'HTTPS',
|
|
13
|
-
445: 'SMB',
|
|
14
|
-
548: 'AFP',
|
|
15
|
-
3306: 'MySQL',
|
|
16
|
-
3389: 'RDP',
|
|
17
|
-
5432: 'PostgreSQL',
|
|
18
|
-
5900: 'VNC',
|
|
19
|
-
8080: 'HTTP-Alt',
|
|
20
|
-
8443: 'HTTPS-Alt',
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export interface PortResult {
|
|
24
|
-
port: number;
|
|
25
|
-
service: string;
|
|
26
|
-
open: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function scanPorts(
|
|
30
|
-
host: string,
|
|
31
|
-
ports = Object.keys(COMMON_PORTS).map(Number)
|
|
32
|
-
): Promise<PortResult[]> {
|
|
33
|
-
const results = await Promise.all(
|
|
34
|
-
ports.map(port => checkPort(host, port))
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return results
|
|
38
|
-
.filter(r => r.open)
|
|
39
|
-
.map(r => ({ ...r, service: COMMON_PORTS[r.port] || 'Unknown' }));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function checkPort(host: string, port: number, timeout = 1000): Promise<PortResult> {
|
|
43
|
-
return new Promise(resolve => {
|
|
44
|
-
const socket: Socket = connect({ host, port, timeout });
|
|
45
|
-
|
|
46
|
-
const cleanup = () => {
|
|
47
|
-
socket.removeAllListeners();
|
|
48
|
-
socket.destroy();
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
socket.on('connect', () => {
|
|
52
|
-
cleanup();
|
|
53
|
-
resolve({ port, service: '', open: true });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
socket.on('error', () => {
|
|
57
|
-
cleanup();
|
|
58
|
-
resolve({ port, service: '', open: false });
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
socket.on('timeout', () => {
|
|
62
|
-
cleanup();
|
|
63
|
-
resolve({ port, service: '', open: false });
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
}
|
package/src/scanners/speed.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
export interface SpeedResult {
|
|
2
|
-
download: number; // Mbps
|
|
3
|
-
upload: number; // Mbps
|
|
4
|
-
latency: number; // ms
|
|
5
|
-
jitter: number; // ms
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export async function runSpeedTest(
|
|
9
|
-
onProgress?: (stage: string) => void
|
|
10
|
-
): Promise<SpeedResult> {
|
|
11
|
-
// Latency test
|
|
12
|
-
onProgress?.('Testing latency...');
|
|
13
|
-
const latencies = await Promise.all([
|
|
14
|
-
pingHost('1.1.1.1'),
|
|
15
|
-
pingHost('8.8.8.8'),
|
|
16
|
-
pingHost('208.67.222.222'),
|
|
17
|
-
]);
|
|
18
|
-
const validLatencies = latencies.filter(l => l > 0);
|
|
19
|
-
const latency = validLatencies.length > 0 ? average(validLatencies) : 0;
|
|
20
|
-
const jitter = validLatencies.length > 1 ? standardDeviation(validLatencies) : 0;
|
|
21
|
-
|
|
22
|
-
// Download test using Cloudflare's speed test endpoint
|
|
23
|
-
onProgress?.('Testing download...');
|
|
24
|
-
const downloadBytes = 10_000_000; // 10MB for faster results
|
|
25
|
-
const downloadStart = performance.now();
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const response = await fetch(`https://speed.cloudflare.com/__down?bytes=${downloadBytes}`, {
|
|
29
|
-
signal: AbortSignal.timeout(30000),
|
|
30
|
-
});
|
|
31
|
-
await response.arrayBuffer(); // Consume the response
|
|
32
|
-
} catch {
|
|
33
|
-
// If Cloudflare fails, return partial results
|
|
34
|
-
return { download: 0, upload: 0, latency, jitter };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const downloadTime = (performance.now() - downloadStart) / 1000;
|
|
38
|
-
const download = (downloadBytes * 8) / downloadTime / 1_000_000;
|
|
39
|
-
|
|
40
|
-
// Upload test
|
|
41
|
-
onProgress?.('Testing upload...');
|
|
42
|
-
const uploadData = new Uint8Array(5_000_000); // 5MB
|
|
43
|
-
const uploadStart = performance.now();
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
await fetch('https://speed.cloudflare.com/__up', {
|
|
47
|
-
method: 'POST',
|
|
48
|
-
body: uploadData,
|
|
49
|
-
signal: AbortSignal.timeout(30000),
|
|
50
|
-
});
|
|
51
|
-
} catch {
|
|
52
|
-
return { download, upload: 0, latency, jitter };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const uploadTime = (performance.now() - uploadStart) / 1000;
|
|
56
|
-
const upload = (uploadData.length * 8) / uploadTime / 1_000_000;
|
|
57
|
-
|
|
58
|
-
return { download, upload, latency, jitter };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function pingHost(host: string): Promise<number> {
|
|
62
|
-
const start = performance.now();
|
|
63
|
-
try {
|
|
64
|
-
await fetch(`https://${host}`, {
|
|
65
|
-
method: 'HEAD',
|
|
66
|
-
mode: 'no-cors',
|
|
67
|
-
signal: AbortSignal.timeout(5000),
|
|
68
|
-
});
|
|
69
|
-
return performance.now() - start;
|
|
70
|
-
} catch {
|
|
71
|
-
return 0;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function average(arr: number[]): number {
|
|
76
|
-
if (arr.length === 0) return 0;
|
|
77
|
-
return arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function standardDeviation(arr: number[]): number {
|
|
81
|
-
if (arr.length < 2) return 0;
|
|
82
|
-
const avg = average(arr);
|
|
83
|
-
const squareDiffs = arr.map(value => Math.pow(value - avg, 2));
|
|
84
|
-
return Math.sqrt(average(squareDiffs));
|
|
85
|
-
}
|
package/src/ui/output.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import boxen from 'boxen';
|
|
3
|
-
import Table from 'cli-table3';
|
|
4
|
-
import type { Device } from '../scanners/devices';
|
|
5
|
-
import type { ConnectionInfo } from '../scanners/connection';
|
|
6
|
-
import type { SpeedResult } from '../scanners/speed';
|
|
7
|
-
import type { PortResult } from '../scanners/ports';
|
|
8
|
-
import { formatSpeed, formatLatency, padRight } from '../utils/format';
|
|
9
|
-
|
|
10
|
-
const BOX_WIDTH = 62;
|
|
11
|
-
|
|
12
|
-
export function header() {
|
|
13
|
-
console.log(
|
|
14
|
-
boxen(chalk.bold.cyan(' NETPROBE - Network Diagnostics '), {
|
|
15
|
-
padding: 0,
|
|
16
|
-
margin: { top: 1, bottom: 0, left: 0, right: 0 },
|
|
17
|
-
borderStyle: 'round',
|
|
18
|
-
borderColor: 'cyan',
|
|
19
|
-
})
|
|
20
|
-
);
|
|
21
|
-
console.log();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function section(title: string, content: string[]) {
|
|
25
|
-
const titleLine = `─ ${chalk.white.bold(title)} `;
|
|
26
|
-
const dashesNeeded = BOX_WIDTH - title.length - 4;
|
|
27
|
-
|
|
28
|
-
console.log(chalk.gray('┌' + titleLine + '─'.repeat(dashesNeeded) + '┐'));
|
|
29
|
-
|
|
30
|
-
content.forEach(line => {
|
|
31
|
-
const visibleLen = stripAnsi(line).length;
|
|
32
|
-
const padding = BOX_WIDTH - visibleLen - 4;
|
|
33
|
-
console.log(chalk.gray('│ ') + line + ' '.repeat(Math.max(0, padding)) + chalk.gray(' │'));
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
console.log(chalk.gray('└' + '─'.repeat(BOX_WIDTH) + '┘'));
|
|
37
|
-
console.log();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function connectionSection(info: ConnectionInfo) {
|
|
41
|
-
section('CONNECTION', [
|
|
42
|
-
`${chalk.gray('Interface:')} ${chalk.white(info.interface)}`,
|
|
43
|
-
`${chalk.gray('Local IP:')} ${chalk.white(info.localIP)}`,
|
|
44
|
-
`${chalk.gray('Gateway:')} ${chalk.white(info.gateway)}`,
|
|
45
|
-
`${chalk.gray('External IP:')} ${chalk.white(info.externalIP)}`,
|
|
46
|
-
`${chalk.gray('DNS:')} ${chalk.white(info.dns.join(', '))}`,
|
|
47
|
-
]);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function speedSection(speed: SpeedResult) {
|
|
51
|
-
section('SPEED', [
|
|
52
|
-
`${chalk.green('↓')} ${chalk.gray('Download:')} ${chalk.white.bold(formatSpeed(speed.download))}`,
|
|
53
|
-
`${chalk.blue('↑')} ${chalk.gray('Upload:')} ${chalk.white.bold(formatSpeed(speed.upload))}`,
|
|
54
|
-
`${chalk.gray('Latency:')} ${chalk.white(formatLatency(speed.latency))} ${chalk.gray(`(jitter: ${formatLatency(speed.jitter)})`)}`,
|
|
55
|
-
]);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function devicesSection(devices: Device[]) {
|
|
59
|
-
const table = new Table({
|
|
60
|
-
head: ['IP', 'MAC', 'Vendor', 'Name'].map(h => chalk.cyan(h)),
|
|
61
|
-
style: {
|
|
62
|
-
head: [],
|
|
63
|
-
border: ['gray'],
|
|
64
|
-
},
|
|
65
|
-
chars: {
|
|
66
|
-
'top': '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐',
|
|
67
|
-
'bottom': '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘',
|
|
68
|
-
'left': '│', 'left-mid': '├', 'mid': '─', 'mid-mid': '┼',
|
|
69
|
-
'right': '│', 'right-mid': '┤', 'middle': '│'
|
|
70
|
-
},
|
|
71
|
-
colWidths: [17, 20, 14, 14],
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
devices.forEach(d => {
|
|
75
|
-
const name = d.isCurrentDevice
|
|
76
|
-
? chalk.green('This Mac')
|
|
77
|
-
: d.hostname
|
|
78
|
-
? truncate(d.hostname, 12)
|
|
79
|
-
: chalk.gray('-');
|
|
80
|
-
|
|
81
|
-
table.push([
|
|
82
|
-
d.ip,
|
|
83
|
-
d.mac,
|
|
84
|
-
truncate(d.vendor, 12),
|
|
85
|
-
name,
|
|
86
|
-
]);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
console.log(chalk.gray(`┌─ ${chalk.white.bold('DEVICES')} (${devices.length} found) ${'─'.repeat(BOX_WIDTH - 18 - String(devices.length).length)}┐`));
|
|
90
|
-
console.log(table.toString());
|
|
91
|
-
console.log();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function portsSection(ports: PortResult[], host: string) {
|
|
95
|
-
if (ports.length === 0) {
|
|
96
|
-
section(`GATEWAY PORTS (${host})`, [
|
|
97
|
-
chalk.gray('No open ports found'),
|
|
98
|
-
]);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const lines = ports.map(p => {
|
|
103
|
-
const portStr = padRight(`${p.port}/tcp`, 10);
|
|
104
|
-
const serviceStr = padRight(p.service, 10);
|
|
105
|
-
return `${chalk.white(portStr)}${chalk.gray(serviceStr)}${chalk.green('OPEN')}`;
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
section(`GATEWAY PORTS (${host})`, lines);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function truncate(str: string, len: number): string {
|
|
112
|
-
if (str.length <= len) return str;
|
|
113
|
-
return str.slice(0, len - 1) + '…';
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function stripAnsi(str: string): string {
|
|
117
|
-
// eslint-disable-next-line no-control-regex
|
|
118
|
-
return str.replace(/\x1B\[[0-9;]*m/g, '');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface ScanResults {
|
|
122
|
-
connection?: ConnectionInfo;
|
|
123
|
-
devices?: Device[];
|
|
124
|
-
speed?: SpeedResult;
|
|
125
|
-
ports?: PortResult[];
|
|
126
|
-
gateway?: string;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function outputJson(results: ScanResults) {
|
|
130
|
-
console.log(JSON.stringify(results, null, 2));
|
|
131
|
-
}
|
package/src/ui/spinner.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import ora, { type Ora } from 'ora';
|
|
2
|
-
|
|
3
|
-
let currentSpinner: Ora | null = null;
|
|
4
|
-
|
|
5
|
-
export function startSpinner(text: string): Ora {
|
|
6
|
-
if (currentSpinner) {
|
|
7
|
-
currentSpinner.stop();
|
|
8
|
-
}
|
|
9
|
-
currentSpinner = ora({
|
|
10
|
-
text,
|
|
11
|
-
spinner: 'dots',
|
|
12
|
-
}).start();
|
|
13
|
-
return currentSpinner;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function updateSpinner(text: string): void {
|
|
17
|
-
if (currentSpinner) {
|
|
18
|
-
currentSpinner.text = text;
|
|
19
|
-
} else {
|
|
20
|
-
startSpinner(text);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function stopSpinner(): void {
|
|
25
|
-
if (currentSpinner) {
|
|
26
|
-
currentSpinner.stop();
|
|
27
|
-
currentSpinner = null;
|
|
28
|
-
}
|
|
29
|
-
}
|
package/src/utils/exec.ts
DELETED
package/src/utils/format.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export function formatSpeed(mbps: number): string {
|
|
2
|
-
if (mbps >= 1000) {
|
|
3
|
-
return `${(mbps / 1000).toFixed(1)} Gbps`;
|
|
4
|
-
}
|
|
5
|
-
return `${mbps.toFixed(1)} Mbps`;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function formatLatency(ms: number): string {
|
|
9
|
-
return `${Math.round(ms)}ms`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function padRight(str: string, len: number): string {
|
|
13
|
-
return str.length >= len ? str.slice(0, len) : str + ' '.repeat(len - str.length);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function padLeft(str: string, len: number): string {
|
|
17
|
-
return str.length >= len ? str.slice(0, len) : ' '.repeat(len - str.length) + str;
|
|
18
|
-
}
|