windscout 0.5.6 → 0.5.7
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/README.md +3 -2
- package/dist/cli.js +19 -16
- package/dist/tui/picker.js +9 -9
- package/dist/tui/progress.js +4 -2
- package/dist/update.js +42 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ WindScout drives the official `windscribe-cli`: it connects to each country × p
|
|
|
20
20
|
npm install -g windscout # or run once with: npx windscout
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
On startup, WindScout checks npm for a newer version.
|
|
23
|
+
On startup, WindScout checks npm for a newer version. Above the repository link at the bottom of the screen, it shows the installed version in grey (`windscout 0.5.6 · latest` once npm confirms nothing newer is out), or, when a newer one is out, an update notice in yellow. In the picker, press `u` to update and restart on the new version. The update runs `npm install -g windscout@latest`, with `sudo` when your global npm folder belongs to root (sudo then asks for your password). The update command is also printed after the final report.
|
|
24
24
|
|
|
25
25
|
## Usage
|
|
26
26
|
|
|
@@ -52,6 +52,7 @@ Run `windscout` with no arguments to open the interactive picker:
|
|
|
52
52
|
| `1`–`6` | toggle a protocol (`1`–`5` on Linux, which has no IKEv2) |
|
|
53
53
|
| `s` | toggle the speed test (off = connect-only mode) |
|
|
54
54
|
| `f` | toggle "block internet while scanning" (firewall) |
|
|
55
|
+
| `u` | update to the newer version and restart (only when the picker shows one is out) |
|
|
55
56
|
| `enter` | start scanning |
|
|
56
57
|
| `q` | quit (during a scan: stop and show partial results; press again to force quit) |
|
|
57
58
|
|
|
@@ -79,7 +80,7 @@ windscout -t 40 -a # longer connect timeout, also list
|
|
|
79
80
|
| `-a, --all` | include failed attempts in the final table | off |
|
|
80
81
|
| `-n, --no-speed` | connect-only mode: skip the download test, rank by latency | picker: saved, or connect-only on first run; with `-c`: speed test on |
|
|
81
82
|
| `-f, --firewall` | block internet while scanning, except through the VPN | picker: saved, or off; with `-c`: off |
|
|
82
|
-
| `-v, --version` | print the installed version; if a newer one is on npm, show the update command and ask (y/n) to run it | |
|
|
83
|
+
| `-v, --version` | print the installed version; if a newer one is on npm, show the update command (with `sudo` when needed) and ask (y/n) to run it | |
|
|
83
84
|
|
|
84
85
|
## Connect-only mode
|
|
85
86
|
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { command, report, target } from "./tui/report.js";
|
|
|
13
13
|
import { leaveScreen, out } from "./tui/terminal.js";
|
|
14
14
|
import { cyan, green, yellow } from "./tui/text.js";
|
|
15
15
|
import { now, sleep } from "./time.js";
|
|
16
|
-
import { checkUpdate,
|
|
16
|
+
import { canUpdate, checkUpdate, runUpdate, updateNotice, versionLines } from "./update.js";
|
|
17
17
|
import { blockInternet, CLI, connectState, isFirewallOn, PROTOCOLS, restoreFirewall, waitForLogin, windscribe } from "./windscribe.js";
|
|
18
18
|
const HELP = `Usage: windscout [options]
|
|
19
19
|
|
|
@@ -61,6 +61,17 @@ async function offerConnect(choices, timeout) {
|
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
/** u in the picker: update, then run the new version in this one's place, with the same arguments */
|
|
65
|
+
function updateAndRestart() {
|
|
66
|
+
const status = runUpdate();
|
|
67
|
+
if (status) {
|
|
68
|
+
console.error(yellow(`update failed (exit ${status})`));
|
|
69
|
+
process.exitCode = status;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
console.log(green("updated · restarting windscout"));
|
|
73
|
+
process.exitCode = spawnSync(process.execPath, [...process.execArgv, ...process.argv.slice(1)], { stdio: "inherit" }).status ?? 1;
|
|
74
|
+
}
|
|
64
75
|
async function main() {
|
|
65
76
|
const { values: a } = parseArgs({
|
|
66
77
|
options: {
|
|
@@ -78,19 +89,10 @@ async function main() {
|
|
|
78
89
|
return console.log(HELP);
|
|
79
90
|
checkUpdate(); // runs while the app starts and the user picks, before the VPN starts switching
|
|
80
91
|
if (a.version) {
|
|
81
|
-
console.log(`windscout ${VERSION}`);
|
|
82
92
|
await checkUpdate();
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
console.error(yellow(notice));
|
|
87
|
-
if (process.stdin.isTTY && out.isTTY && !viaNpx() && /^y(es)?$/i.test(await ask("update now? (y/n) "))) {
|
|
88
|
-
console.log(cyan(UPDATE_COMMAND));
|
|
89
|
-
// shell: npm is npm.cmd on Windows
|
|
90
|
-
const { status } = spawnSync(UPDATE_COMMAND, { shell: true, stdio: "inherit" });
|
|
91
|
-
if (status)
|
|
92
|
-
process.exitCode = status;
|
|
93
|
-
}
|
|
93
|
+
console.log(versionLines().join("\n"));
|
|
94
|
+
if (canUpdate() && process.stdin.isTTY && out.isTTY && /^y(es)?$/i.test(await ask("update now? (y/n) ")))
|
|
95
|
+
process.exitCode = runUpdate();
|
|
94
96
|
return;
|
|
95
97
|
}
|
|
96
98
|
let timeout = a.timeout ? Number(a.timeout) : undefined;
|
|
@@ -116,6 +118,8 @@ async function main() {
|
|
|
116
118
|
});
|
|
117
119
|
if (!choice)
|
|
118
120
|
return;
|
|
121
|
+
if (choice === "update")
|
|
122
|
+
return updateAndRestart();
|
|
119
123
|
countries = choice.countries;
|
|
120
124
|
protocols ??= choice.protocols;
|
|
121
125
|
speedTest = choice.speed;
|
|
@@ -134,9 +138,8 @@ async function main() {
|
|
|
134
138
|
console.log(yellow("interrupted, showing partial results"));
|
|
135
139
|
const choices = report(scan.results, protocols, { all: a.all, speedTest });
|
|
136
140
|
await checkUpdate();
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
console.error("\n" + yellow(notice));
|
|
141
|
+
if (updateNotice())
|
|
142
|
+
console.error("\n" + versionLines().join("\n"));
|
|
140
143
|
if (tty && choices.length)
|
|
141
144
|
await offerConnect(choices, timeout);
|
|
142
145
|
}
|
package/dist/tui/picker.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { MIN_TIMEOUT, saveConfig } from "../config.js";
|
|
2
2
|
import { attemptSeconds, estimate } from "../scan.js";
|
|
3
3
|
import { duration } from "../time.js";
|
|
4
|
-
import { checkUpdate,
|
|
4
|
+
import { canUpdate, checkUpdate, versionLines } from "../update.js";
|
|
5
5
|
import { COUNTRIES, PROTOCOLS } from "../windscribe.js";
|
|
6
6
|
import { countryName, flag } from "./country.js";
|
|
7
7
|
import { draw, enterScreen, input, leaveScreen, out, repoLink } from "./terminal.js";
|
|
8
8
|
import { bold, cyan, dim, green, under, vw, yellow } from "./text.js";
|
|
9
9
|
import { box, centerIn, grid } from "./widgets.js";
|
|
10
10
|
const flip = (set, value) => (set.has(value) ? set.delete(value) : set.add(value));
|
|
11
|
-
/** Resolves to the choice (also saved for next time),
|
|
11
|
+
/** Resolves to the choice (also saved for next time), null when the user quits, or "update" for u */
|
|
12
12
|
export function pick(saved, { timeout, speedTest, firewall }) {
|
|
13
13
|
const selected = new Set(saved?.countries ?? []);
|
|
14
14
|
const protos = new Set(saved?.protocols.length ? saved.protocols : PROTOCOLS);
|
|
@@ -23,9 +23,9 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
23
23
|
return new Promise((resolve) => {
|
|
24
24
|
const render = () => {
|
|
25
25
|
const rows = out.rows || 24;
|
|
26
|
-
const
|
|
26
|
+
const version = versionLines(true);
|
|
27
27
|
const matches = visible();
|
|
28
|
-
const height = Math.max(3, rows - 21 -
|
|
28
|
+
const height = Math.max(3, rows - 21 - version.length);
|
|
29
29
|
cursor = Math.max(0, Math.min(cursor, matches.length - 1));
|
|
30
30
|
if (cursor < top)
|
|
31
31
|
top = cursor;
|
|
@@ -89,9 +89,7 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
89
89
|
];
|
|
90
90
|
if (message)
|
|
91
91
|
lines.push(centerIn(yellow(message), width));
|
|
92
|
-
lines.push("", centerIn(dim(repoLink), width));
|
|
93
|
-
if (notice)
|
|
94
|
-
lines.push(centerIn(yellow(notice), width));
|
|
92
|
+
lines.push("", ...version.map((l) => centerIn(l, width)), centerIn(dim(repoLink), width));
|
|
95
93
|
const at = draw(lines);
|
|
96
94
|
// click map, in screen coordinates
|
|
97
95
|
let px = at.left + 2; // "│ " before the protocol line
|
|
@@ -116,13 +114,13 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
116
114
|
};
|
|
117
115
|
};
|
|
118
116
|
let closed = false;
|
|
119
|
-
checkUpdate().then(() =>
|
|
117
|
+
checkUpdate().then(() => !closed && render()); // the check usually lands after the first draw
|
|
120
118
|
const finish = (value) => {
|
|
121
119
|
closed = true;
|
|
122
120
|
out.off("resize", render);
|
|
123
121
|
input.removeAllListeners("keypress");
|
|
124
122
|
input.removeAllListeners("mouse");
|
|
125
|
-
if (!value)
|
|
123
|
+
if (!value || value === "update")
|
|
126
124
|
leaveScreen();
|
|
127
125
|
resolve(value);
|
|
128
126
|
};
|
|
@@ -257,6 +255,8 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
257
255
|
firewall = !firewall;
|
|
258
256
|
else if (k === "t")
|
|
259
257
|
editing = true, seconds = "";
|
|
258
|
+
else if (k === "u" && canUpdate())
|
|
259
|
+
return finish("update");
|
|
260
260
|
else if (/^[1-9]$/.test(str ?? "") && PROTOCOLS[Number(str) - 1])
|
|
261
261
|
flip(protos, PROTOCOLS[Number(str) - 1]);
|
|
262
262
|
else if (k === "escape" && query)
|
package/dist/tui/progress.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sortOk } from "../probe.js";
|
|
2
2
|
import { attemptSeconds } from "../scan.js";
|
|
3
3
|
import { duration, now } from "../time.js";
|
|
4
|
+
import { versionLines } from "../update.js";
|
|
4
5
|
import { CLI, restoreFirewall } from "../windscribe.js";
|
|
5
6
|
import { countryName, flag } from "./country.js";
|
|
6
7
|
import { matrix, resultTable } from "./tables.js";
|
|
@@ -34,7 +35,8 @@ export function showProgress(scan, blocking) {
|
|
|
34
35
|
].join(dim(" │ "));
|
|
35
36
|
const minW = Math.max(64, vw(stats) + 6); // panel and table share one width
|
|
36
37
|
// results table gets whatever height the status panel and footer leave
|
|
37
|
-
const
|
|
38
|
+
const version = versionLines();
|
|
39
|
+
const room = Math.max(1, rows - 22 - version.length);
|
|
38
40
|
const table = view === "availability"
|
|
39
41
|
? matrix(results, protocols, { minWidth: minW, limit: room })
|
|
40
42
|
: ok.length
|
|
@@ -73,7 +75,7 @@ export function showProgress(scan, blocking) {
|
|
|
73
75
|
txt = txt.slice(0, -8) + "…"; // ponytail: crude trim, fine for a status line
|
|
74
76
|
lines.push("", centerIn(txt, width));
|
|
75
77
|
}
|
|
76
|
-
lines.push("", centerIn(dim(`tab ${view === "ranking" ? "availability" : "ranking"} view · p ${paused ? "resume" : "pause"} · q stop · q twice force quit`), width), "", centerIn(dim(repoLink), width));
|
|
78
|
+
lines.push("", centerIn(dim(`tab ${view === "ranking" ? "availability" : "ranking"} view · p ${paused ? "resume" : "pause"} · q stop · q twice force quit`), width), "", ...version.map((l) => centerIn(l, width)), centerIn(dim(repoLink), width));
|
|
77
79
|
draw(lines);
|
|
78
80
|
};
|
|
79
81
|
enterScreen();
|
package/dist/update.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
// The installed version,
|
|
2
|
-
import {
|
|
1
|
+
// The installed version, whether npm has a newer one, and updating to it.
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { accessSync, constants, readFileSync } from "node:fs";
|
|
4
|
+
import { cyan, dim, under, yellow } from "./tui/text.js";
|
|
3
5
|
export const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
4
6
|
let latest = null; // newer version on npm, once known
|
|
7
|
+
let upToDate = false; // npm answered, and has nothing newer
|
|
5
8
|
let check;
|
|
6
9
|
// numeric x.y.z compare; parseInt drops a prerelease suffix ("0-beta" → 0)
|
|
7
10
|
export function newer(a, b) {
|
|
@@ -15,17 +18,48 @@ export function newer(a, b) {
|
|
|
15
18
|
export function checkUpdate() {
|
|
16
19
|
check ??= fetch("https://registry.npmjs.org/windscout/latest", { signal: AbortSignal.timeout(3000) })
|
|
17
20
|
.then((r) => r.json())
|
|
18
|
-
.then(({ version }) => {
|
|
19
|
-
|
|
21
|
+
.then(({ version }) => {
|
|
22
|
+
if (version && newer(version, VERSION))
|
|
23
|
+
latest = version;
|
|
24
|
+
else
|
|
25
|
+
upToDate = Boolean(version);
|
|
26
|
+
})
|
|
20
27
|
.catch(() => { });
|
|
21
28
|
return check;
|
|
22
29
|
}
|
|
23
30
|
// run through npx: there's nothing installed to update, the next npx windscout@latest fetches it
|
|
24
31
|
export const viaNpx = () => process.env.npm_command === "exec";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
// npm replaces the folder this package sits in; a root-owned global folder (system Node) needs sudo for that
|
|
33
|
+
const sudo = (() => {
|
|
34
|
+
try {
|
|
35
|
+
accessSync(new URL("../..", import.meta.url), constants.W_OK);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
const INSTALL = "npm install -g windscout@latest";
|
|
43
|
+
export const UPDATE_COMMAND = (sudo ? "sudo " : "") + INSTALL;
|
|
44
|
+
// npm is Node too: through a slow tunnel its downloads time out like fetch did (see measure.ts) without these
|
|
45
|
+
const NODE_FIX = "--no-network-family-autoselection --dns-result-order=ipv4first";
|
|
46
|
+
/** A newer version is out, and npm can install it (under npx there's nothing installed to update) */
|
|
47
|
+
export const canUpdate = () => latest !== null && !viaNpx();
|
|
48
|
+
/** Runs the update in this terminal (sudo asks for the password here); returns npm's exit status */
|
|
49
|
+
export function runUpdate() {
|
|
50
|
+
console.log(cyan(UPDATE_COMMAND));
|
|
51
|
+
const NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ""} ${NODE_FIX}`.trim();
|
|
52
|
+
// sudo clears the environment, so env sets NODE_OPTIONS on its side; shell: npm is npm.cmd on Windows
|
|
53
|
+
return spawnSync(sudo ? `sudo env NODE_OPTIONS="${NODE_FIX}" ${INSTALL}` : INSTALL, { shell: true, stdio: "inherit", env: { ...process.env, NODE_OPTIONS } }).status ?? 1;
|
|
54
|
+
}
|
|
55
|
+
/** "update available: …" and how to get it, once checkUpdate() found a newer version; onKey: this screen updates on u */
|
|
56
|
+
export function updateNotice(onKey = false) {
|
|
28
57
|
if (!latest)
|
|
29
58
|
return null;
|
|
30
|
-
return `update available: ${VERSION} → ${latest}
|
|
59
|
+
return [`update available: ${VERSION} → ${latest}`,
|
|
60
|
+
viaNpx() ? "quit, then run: npx windscout@latest"
|
|
61
|
+
: onKey ? `press ${under("u")} to update and restart · runs ${UPDATE_COMMAND}`
|
|
62
|
+
: `run: ${UPDATE_COMMAND}, then restart windscout`];
|
|
31
63
|
}
|
|
64
|
+
/** The version line(s) above the repo link: the update notice in yellow, or the installed version in grey */
|
|
65
|
+
export const versionLines = (onKey = false) => updateNotice(onKey)?.map(yellow) ?? [dim(`windscout ${VERSION}${upToDate ? " · latest" : ""}`)];
|