windscout 0.5.1 → 0.5.2
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 +1 -1
- package/dist/cli.js +35 -4
- package/dist/tui/report.js +16 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ Run `windscout` with no arguments to open the interactive picker:
|
|
|
59
59
|
|
|
60
60
|
Your selection is saved to `~/.config/windscout/config.json` (`%USERPROFILE%\.config\windscout\config.json` on Windows) and preselected next time.
|
|
61
61
|
|
|
62
|
-
While scanning, WindScout shows a progress bar, elapsed time and remaining time. It also shows what it's doing right now (connecting, measuring latency or measuring speed). The table of working locations refreshes after each country. When the scan finishes, the final report is printed to your normal terminal.
|
|
62
|
+
While scanning, WindScout shows a progress bar, elapsed time and remaining time. It also shows what it's doing right now (connecting, measuring latency or measuring speed). The table of working locations refreshes after each country. When the scan finishes, the final report is printed to your normal terminal. It ends with the best connection and the other working ones, each with its `windscribe-cli connect` command; type a number at the prompt to connect to it.
|
|
63
63
|
|
|
64
64
|
### Non-interactive
|
|
65
65
|
|
package/dist/cli.js
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
// WindScout: find which Windscribe locations and protocols actually work from here.
|
|
3
3
|
// Drives the official windscribe-cli: connects to each country/protocol pair,
|
|
4
4
|
// measures latency and download speed through the tunnel, prints a ranked table.
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
5
6
|
import { parseArgs } from "node:util";
|
|
6
7
|
import { CONFIG, DEFAULT_TIMEOUT, loadConfig, MIN_TIMEOUT } from "./config.js";
|
|
7
8
|
import { Scan } from "./scan.js";
|
|
8
9
|
import { pick } from "./tui/picker.js";
|
|
9
10
|
import { logProgress, showProgress } from "./tui/progress.js";
|
|
10
|
-
import { report } from "./tui/report.js";
|
|
11
|
+
import { command, report, target } from "./tui/report.js";
|
|
11
12
|
import { leaveScreen, out } from "./tui/terminal.js";
|
|
12
|
-
import { yellow } from "./tui/text.js";
|
|
13
|
+
import { cyan, green, yellow } from "./tui/text.js";
|
|
14
|
+
import { now, sleep } from "./time.js";
|
|
13
15
|
import { checkUpdate, updateNotice, VERSION } from "./update.js";
|
|
14
|
-
import { blockInternet, CLI, isFirewallOn, PROTOCOLS, restoreFirewall, waitForLogin } from "./windscribe.js";
|
|
16
|
+
import { blockInternet, CLI, connectState, isFirewallOn, PROTOCOLS, restoreFirewall, waitForLogin, windscribe } from "./windscribe.js";
|
|
15
17
|
const HELP = `Usage: windscout [options]
|
|
16
18
|
|
|
17
19
|
Find which Windscribe locations and protocols work from your network, ranked by speed.
|
|
@@ -31,6 +33,33 @@ Selection is stored in ${CONFIG}
|
|
|
31
33
|
Example: windscout -c DE,NL,TR -p stealth,tcp:443`;
|
|
32
34
|
// "DE, NL,,TR" → ["DE", "NL", "TR"]
|
|
33
35
|
const list = (s) => s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
36
|
+
const ask = (question) => new Promise((resolve) => {
|
|
37
|
+
const rl = createInterface({ input: process.stdin, output: out });
|
|
38
|
+
rl.on("SIGINT", () => rl.close()); // Ctrl-C / Ctrl-D: same as enter
|
|
39
|
+
rl.on("close", () => resolve(""));
|
|
40
|
+
rl.question(question, (a) => (resolve(a.trim()), rl.close()));
|
|
41
|
+
});
|
|
42
|
+
/** Asks which numbered connection to make, then runs its command and waits for it to connect */
|
|
43
|
+
async function offerConnect(choices, timeout) {
|
|
44
|
+
const others = choices.length > 1 ? `, 1-${choices.length - 1} = other` : "";
|
|
45
|
+
for (;;) {
|
|
46
|
+
const a = await ask(`\nconnect to # (0 = best${others}, enter to quit): `);
|
|
47
|
+
if (!a)
|
|
48
|
+
return;
|
|
49
|
+
const r = /^\d+$/.test(a) ? choices[Number(a)] : undefined;
|
|
50
|
+
if (!r) {
|
|
51
|
+
console.log(yellow(`no #${a} in the tables`));
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
console.log(cyan(command(r)));
|
|
55
|
+
await windscribe("connect", target(r), r.protocol);
|
|
56
|
+
let state = "";
|
|
57
|
+
for (const t = now(); now() - t < timeout && !(state = await connectState()).startsWith("Connected");)
|
|
58
|
+
await sleep(1000);
|
|
59
|
+
console.log(state.startsWith("Connected") ? green(state) : yellow(`${state || "not connected"} after ${timeout} s`));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
34
63
|
async function main() {
|
|
35
64
|
const { values: a } = parseArgs({
|
|
36
65
|
options: {
|
|
@@ -94,11 +123,13 @@ async function main() {
|
|
|
94
123
|
view.close();
|
|
95
124
|
if (scan.stopping)
|
|
96
125
|
console.log(yellow("interrupted, showing partial results"));
|
|
97
|
-
report(scan.results, protocols, { all: a.all, speedTest });
|
|
126
|
+
const choices = report(scan.results, protocols, { all: a.all, speedTest });
|
|
98
127
|
await checkUpdate();
|
|
99
128
|
const notice = updateNotice();
|
|
100
129
|
if (notice)
|
|
101
130
|
console.error("\n" + yellow(notice));
|
|
131
|
+
if (tty && choices.length)
|
|
132
|
+
await offerConnect(choices, timeout);
|
|
102
133
|
}
|
|
103
134
|
main().catch(async (e) => {
|
|
104
135
|
leaveScreen();
|
package/dist/tui/report.js
CHANGED
|
@@ -5,26 +5,35 @@ import { flag } from "./country.js";
|
|
|
5
5
|
import { matrix, resultTable } from "./tables.js";
|
|
6
6
|
import { bold, cyan, dim, fmt, green, vw, yellow } from "./text.js";
|
|
7
7
|
import { box, grid } from "./widgets.js";
|
|
8
|
+
// nickname pins the exact datacenter
|
|
9
|
+
export const target = (r) => r.location.split(" - ").at(-1) || r.query;
|
|
10
|
+
export const command = (r) => `${CLI} connect "${target(r)}" ${r.protocol}`;
|
|
11
|
+
/** Prints the report; returns the connections it numbered: the best (#0), then the others (#1…) */
|
|
8
12
|
export function report(results, protocols, { all, speedTest }) {
|
|
9
13
|
const ok = sortOk(results);
|
|
10
14
|
const rows = [...ok, ...(all ? results.filter((r) => r.status !== "ok") : [])];
|
|
15
|
+
const others = ok.slice(1).sort((x, y) => (x.latencyMs ?? Infinity) - (y.latencyMs ?? Infinity));
|
|
11
16
|
// render once to learn the widest table, then again so every box lines up
|
|
12
17
|
const build = (minWidth) => ({
|
|
13
18
|
avail: matrix(results, protocols, { minWidth }),
|
|
14
19
|
main: rows.length
|
|
15
20
|
? resultTable(rows, speedTest, { caption: bold("Results"), captionRight: dim(`${ok.length} working`), minWidth })
|
|
16
21
|
: [],
|
|
22
|
+
others: others.length
|
|
23
|
+
? grid([{ title: "#", align: "right" }, { title: "Location" }, { title: "Protocol" }, { title: "Latency", align: "right" }, { title: "Connect with" }], others.map((r, i) => [dim(String(i + 1)), `${flag(r.query)} ${r.location}`, r.protocol,
|
|
24
|
+
r.latencyMs === null ? "-" : `${fmt(r.latencyMs, 0)} ms`, cyan(command(r))]), { caption: bold("Other working locations"), captionRight: dim("by latency"), minWidth })
|
|
25
|
+
: [],
|
|
17
26
|
});
|
|
18
27
|
const first = build(0);
|
|
19
|
-
const width = Math.max(64, ...[first.avail[0], first.main[0]].filter(Boolean).map(vw));
|
|
20
|
-
const { avail, main } = build(width);
|
|
28
|
+
const width = Math.max(64, ...[first.avail[0], first.main[0], first.others[0]].filter(Boolean).map(vw));
|
|
29
|
+
const { avail, main, others: rest } = build(width);
|
|
21
30
|
console.log();
|
|
22
31
|
if (avail.length)
|
|
23
32
|
console.log(avail.join("\n"));
|
|
24
33
|
if (!ok.length) {
|
|
25
34
|
console.log(box([yellow("Nothing connected."),
|
|
26
35
|
dim("Try other countries, ports (e.g. -p stealth:443,tcp:443) or a longer --timeout.")], { title: bold(cyan("WindScout")), width: width - 4 }).join("\n"));
|
|
27
|
-
return;
|
|
36
|
+
return [];
|
|
28
37
|
}
|
|
29
38
|
console.log(main.join("\n"));
|
|
30
39
|
const stats = protocols.map((p) => {
|
|
@@ -35,10 +44,12 @@ export function report(results, protocols, { all, speedTest }) {
|
|
|
35
44
|
const bestValue = (r) => (speedTest ? `${fmt(r.speedMbps, 1)} Mbps` : `${fmt(r.latencyMs, 0)} ms`);
|
|
36
45
|
console.log(grid([{ title: "Protocol" }, { title: "Worked", align: "right" }, { title: speedTest ? "Best speed" : "Best latency", align: "right" }, { title: "Best location" }], stats.map((s) => [s.p, `${s.good}/${s.tried}`, s.best ? bestValue(s.best) : "-", s.best ? `${flag(s.best.query)} ${s.best.location}` : "-"]), { caption: bold("Per protocol"), minWidth: width }).join("\n"));
|
|
37
46
|
const b = ok[0];
|
|
38
|
-
const target = b.location.split(" - ").at(-1) || b.query; // nickname pins the exact datacenter
|
|
39
47
|
const facts = [speedTest && `${fmt(b.speedMbps, 1)} Mbps`, `${fmt(b.latencyMs, 0)} ms`].filter(Boolean).join(" · ");
|
|
40
48
|
console.log(box([
|
|
41
49
|
`${flag(b.query)} ${bold(b.location)} over ${bold(b.protocol)} ${dim("· " + facts)}`,
|
|
42
|
-
cyan(
|
|
50
|
+
cyan(command(b)),
|
|
43
51
|
], { title: green(speedTest ? "★ Best" : "★ Best (lowest latency)"), width: width - 4 }).join("\n"));
|
|
52
|
+
if (rest.length)
|
|
53
|
+
console.log(rest.join("\n"));
|
|
54
|
+
return [b, ...others];
|
|
44
55
|
}
|