polaris-vpn 0.4.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.5.0] - 2026-07-12
9
+
10
+ ### Added
11
+
12
+ - **Server Health Dashboard**: `polaris status --full` now queries GeoIP data, tests ping latency directly to the server, and retrieves live WireGuard rx/tx data limits.
13
+ - **Auto-Updater**: Introduced `polaris update` and background update notifications via `update-notifier`.
14
+
8
15
  ## [0.4.0] - 2026-07-12
9
16
 
10
17
  ### Added
package/README.md CHANGED
@@ -45,13 +45,14 @@ polaris stop
45
45
  | --- | --- |
46
46
  | `polaris start --server user@host [--port 1080]` | Start the encrypted SSH tunnel. |
47
47
  | `polaris stop` | Stop the active tunnel. |
48
- | `polaris status` | Show current tunnel status, IP, and uptime. |
48
+ | `polaris status [--full]` | Show current tunnel status. `--full` shows GeoIP, ping latency, and WG stats. |
49
49
  | `polaris monitor` | Live bandwidth monitor for WireGuard tunnels. |
50
50
  | `polaris check` | Run a 3-point privacy check (IP, DNS leak, IPv6 leak). |
51
51
  | `polaris add <alias> --server user@host` | Save a server profile for quick access. |
52
52
  | `polaris list` | List all saved server profiles. |
53
53
  | `polaris use <alias>` | Set a saved profile as the active default. |
54
54
  | `polaris server start` | Start a local REST API on `127.0.0.1:7070`. |
55
+ | `polaris update` | Update `polaris-vpn` to the latest version via npm. |
55
56
 
56
57
  > All commands support the `--json` flag for machine-readable output.
57
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polaris-vpn",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Your True North in Digital Privacy. A self-hosted VPN CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,6 +44,7 @@
44
44
  "qrcode-terminal": "^0.12.0",
45
45
  "socks-proxy-agent": "^10.1.0",
46
46
  "ssh2": "^1.17.0",
47
+ "update-notifier": "^7.3.1",
47
48
  "wireguard-tools": "^0.1.0"
48
49
  }
49
50
  }
package/src/cli.js CHANGED
@@ -3,12 +3,17 @@ import { Command } from 'commander';
3
3
  import { readFileSync } from 'fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import path from 'path';
6
- import { printBanner, printError } from './utils/display.js';
6
+ import updateNotifier from 'update-notifier';
7
+ import { spawnSync } from 'child_process';
8
+ import { printBanner, printError, printSuccess } from './utils/display.js';
9
+ import chalk from 'chalk';
7
10
 
8
11
  const __filename = fileURLToPath(import.meta.url);
9
12
  const __dirname = path.dirname(__filename);
10
13
  const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
11
14
 
15
+ updateNotifier({ pkg }).notify();
16
+
12
17
  const program = new Command();
13
18
 
14
19
  program
@@ -20,6 +25,20 @@ program
20
25
  // Helper to check if we should print the banner
21
26
  const willPrintJson = () => process.argv.includes('--json');
22
27
 
28
+ program
29
+ .command('update')
30
+ .description('Update polaris-vpn to the latest version')
31
+ .action(() => {
32
+ console.log(chalk.cyan('Updating polaris-vpn...'));
33
+ const res = spawnSync('npm', ['install', '-g', 'polaris-vpn@latest'], { stdio: 'inherit' });
34
+ if (res.status === 0) {
35
+ printSuccess('Successfully updated to the latest version!');
36
+ } else {
37
+ printError('Failed to update polaris-vpn.');
38
+ process.exitCode = 1;
39
+ }
40
+ });
41
+
23
42
  program
24
43
  .command('start')
25
44
  .description('Start the encrypted SSH, TLS or WireGuard tunnel')
@@ -54,6 +73,7 @@ program
54
73
  program
55
74
  .command('status')
56
75
  .description('Show current tunnel status, IP, and uptime')
76
+ .option('--full', 'Show GeoIP, latency, and full data usage stats')
57
77
  .action(async (options, cmd) => {
58
78
  if (!cmd.optsWithGlobals().json) printBanner();
59
79
  try {
@@ -2,9 +2,64 @@ import { getActiveTunnel } from '../core/tunnel-service.js';
2
2
  import { getProxiedIp } from '../net/ip-check.js';
3
3
  import { createTable, createSpinner, printError } from '../utils/display.js';
4
4
  import chalk from 'chalk';
5
+ import fetch from 'node-fetch';
6
+ import { spawnSync } from 'child_process';
7
+ import os from 'os';
8
+
9
+ const pingServer = (ip) => {
10
+ const isWin = os.platform() === 'win32';
11
+ const args = isWin ? ['-n', '1', '-w', '2000', ip] : ['-c', '1', '-W', '2', ip];
12
+ const res = spawnSync('ping', args, { encoding: 'utf-8' });
13
+ if (res.status === 0) {
14
+ if (isWin) {
15
+ const match = res.stdout.match(/Average = (\d+)ms/);
16
+ if (match) return `${match[1]} ms`;
17
+ } else {
18
+ const match = res.stdout.match(/time=([\d.]+)\s*ms/);
19
+ if (match) return `${match[1]} ms`;
20
+ }
21
+ }
22
+ return 'Timeout';
23
+ };
24
+
25
+ const getGeoIp = async (ip) => {
26
+ try {
27
+ const res = await fetch(`http://ip-api.com/json/${ip}`);
28
+ const data = await res.json();
29
+ if (data.status === 'success') {
30
+ return `${data.city}, ${data.country} (${data.isp})`;
31
+ }
32
+ } catch (err) {}
33
+ return 'Unknown';
34
+ };
35
+
36
+ const formatBytes = (bytes) => {
37
+ if (bytes === 0) return '0 B';
38
+ const k = 1024;
39
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
40
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
41
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
42
+ };
43
+
44
+ const getWgStats = () => {
45
+ const dumpRes = spawnSync('sudo', ['wg', 'show', 'all', 'dump'], { encoding: 'utf-8' });
46
+ if (dumpRes.status !== 0) return null;
47
+ const lines = dumpRes.stdout.trim().split('\n');
48
+ if (lines.length <= 1) return null;
49
+
50
+ let totalRx = 0;
51
+ let totalTx = 0;
52
+ for (let i = 1; i < lines.length; i++) {
53
+ const peerInfo = lines[i].split('\t');
54
+ totalRx += parseInt(peerInfo[6], 10) || 0;
55
+ totalTx += parseInt(peerInfo[7], 10) || 0;
56
+ }
57
+ return { totalRx, totalTx };
58
+ };
5
59
 
6
60
  export default async (options) => {
7
61
  const isJson = options.json;
62
+ const isFull = options.full;
8
63
  const info = getActiveTunnel();
9
64
 
10
65
  if (!info) {
@@ -23,9 +78,33 @@ export default async (options) => {
23
78
  currentIp = await getProxiedIp(info.port);
24
79
  if (spinner) spinner.stop();
25
80
 
81
+ let geo = 'N/A';
82
+ let latency = 'N/A';
83
+ let dataUsage = 'N/A';
84
+
85
+ if (isFull) {
86
+ if (spinner) {
87
+ spinner.text = 'Fetching full health data...';
88
+ spinner.start();
89
+ }
90
+
91
+ const serverIp = info.server.split('@').pop();
92
+ latency = pingServer(serverIp);
93
+ geo = await getGeoIp(currentIp);
94
+
95
+ if (info.mode === 'wireguard') {
96
+ const stats = getWgStats();
97
+ if (stats) {
98
+ dataUsage = `${formatBytes(stats.totalRx)} / ${formatBytes(stats.totalTx)}`;
99
+ }
100
+ }
101
+
102
+ if (spinner) spinner.stop();
103
+ }
104
+
26
105
  if (isJson) {
27
106
  const uptimeMs = Date.now() - new Date(info.startTime).getTime();
28
- console.log(JSON.stringify({
107
+ const payload = {
29
108
  status: 'up',
30
109
  server: info.server,
31
110
  port: info.port,
@@ -33,7 +112,13 @@ export default async (options) => {
33
112
  mode: info.mode || 'ssh',
34
113
  ip: currentIp,
35
114
  uptimeMs
36
- }));
115
+ };
116
+ if (isFull) {
117
+ payload.geo = geo;
118
+ payload.latency = latency;
119
+ payload.dataUsage = dataUsage;
120
+ }
121
+ console.log(JSON.stringify(payload));
37
122
  } else {
38
123
  console.log(chalk.green.bold('\n● Tunnel is UP\n'));
39
124
 
@@ -49,6 +134,14 @@ export default async (options) => {
49
134
  ['Uptime', `${uptimeMin} minutes`],
50
135
  ['PID', info.pid.toString()]
51
136
  );
137
+
138
+ if (isFull) {
139
+ table.push(
140
+ ['Geo Location', geo],
141
+ ['Latency (Ping)', latency],
142
+ ['Data (Rx/Tx)', dataUsage]
143
+ );
144
+ }
52
145
 
53
146
  console.log(table.toString());
54
147
  console.log('');