windscout 0.5.1 → 0.5.3

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 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
 
@@ -79,7 +79,7 @@ windscout -t 40 -a # longer connect timeout, also list
79
79
  | `-a, --all` | include failed attempts in the final table | off |
80
80
  | `-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
81
  | `-f, --firewall` | block internet while scanning, except through the VPN | picker: saved, or off; with `-c`: off |
82
- | `-v, --version` | print the installed version, and the update command if a newer one is on npm | |
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
83
 
84
84
  ## Connect-only mode
85
85
 
package/dist/cli.js CHANGED
@@ -2,16 +2,19 @@
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";
6
+ import { spawnSync } from "node:child_process";
5
7
  import { parseArgs } from "node:util";
6
8
  import { CONFIG, DEFAULT_TIMEOUT, loadConfig, MIN_TIMEOUT } from "./config.js";
7
9
  import { Scan } from "./scan.js";
8
10
  import { pick } from "./tui/picker.js";
9
11
  import { logProgress, showProgress } from "./tui/progress.js";
10
- import { report } from "./tui/report.js";
12
+ import { command, report, target } from "./tui/report.js";
11
13
  import { leaveScreen, out } from "./tui/terminal.js";
12
- import { yellow } from "./tui/text.js";
13
- import { checkUpdate, updateNotice, VERSION } from "./update.js";
14
- import { blockInternet, CLI, isFirewallOn, PROTOCOLS, restoreFirewall, waitForLogin } from "./windscribe.js";
14
+ import { cyan, green, yellow } from "./tui/text.js";
15
+ import { now, sleep } from "./time.js";
16
+ import { checkUpdate, UPDATE_COMMAND, updateNotice, VERSION, viaNpx } from "./update.js";
17
+ import { blockInternet, CLI, connectState, isFirewallOn, PROTOCOLS, restoreFirewall, waitForLogin, windscribe } from "./windscribe.js";
15
18
  const HELP = `Usage: windscout [options]
16
19
 
17
20
  Find which Windscribe locations and protocols work from your network, ranked by speed.
@@ -24,13 +27,40 @@ Options:
24
27
  -a, --all also show failed attempts in the final table
25
28
  -n, --no-speed connect-only: skip the download test, rank by latency (faster scan)
26
29
  -f, --firewall block internet while scanning, except through the VPN (turned back off afterwards)
27
- -v, --version show the installed version (and whether a newer one is out)
30
+ -v, --version show the installed version (and offer to update if a newer one is out)
28
31
  -h, --help show this help
29
32
 
30
33
  Selection is stored in ${CONFIG}
31
34
  Example: windscout -c DE,NL,TR -p stealth,tcp:443`;
32
35
  // "DE, NL,,TR" → ["DE", "NL", "TR"]
33
36
  const list = (s) => s.split(",").map((x) => x.trim()).filter(Boolean);
37
+ const ask = (question) => new Promise((resolve) => {
38
+ const rl = createInterface({ input: process.stdin, output: out });
39
+ rl.on("SIGINT", () => rl.close()); // Ctrl-C / Ctrl-D: same as enter
40
+ rl.on("close", () => resolve(""));
41
+ rl.question(question, (a) => (resolve(a.trim()), rl.close()));
42
+ });
43
+ /** Asks which numbered connection to make, then runs its command and waits for it to connect */
44
+ async function offerConnect(choices, timeout) {
45
+ const others = choices.length > 1 ? `, 1-${choices.length - 1} = other` : "";
46
+ for (;;) {
47
+ const a = await ask(`\nconnect to # (0 = best${others}, enter to quit): `);
48
+ if (!a)
49
+ return;
50
+ const r = /^\d+$/.test(a) ? choices[Number(a)] : undefined;
51
+ if (!r) {
52
+ console.log(yellow(`no #${a} in the tables`));
53
+ continue;
54
+ }
55
+ console.log(cyan(command(r)));
56
+ await windscribe("connect", target(r), r.protocol);
57
+ let state = "";
58
+ for (const t = now(); now() - t < timeout && !(state = await connectState()).startsWith("Connected");)
59
+ await sleep(1000);
60
+ console.log(state.startsWith("Connected") ? green(state) : yellow(`${state || "not connected"} after ${timeout} s`));
61
+ return;
62
+ }
63
+ }
34
64
  async function main() {
35
65
  const { values: a } = parseArgs({
36
66
  options: {
@@ -51,8 +81,16 @@ async function main() {
51
81
  console.log(`windscout ${VERSION}`);
52
82
  await checkUpdate();
53
83
  const notice = updateNotice();
54
- if (notice)
55
- console.error(yellow(notice));
84
+ if (!notice)
85
+ return;
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
+ }
56
94
  return;
57
95
  }
58
96
  let timeout = a.timeout ? Number(a.timeout) : undefined;
@@ -94,11 +132,13 @@ async function main() {
94
132
  view.close();
95
133
  if (scan.stopping)
96
134
  console.log(yellow("interrupted, showing partial results"));
97
- report(scan.results, protocols, { all: a.all, speedTest });
135
+ const choices = report(scan.results, protocols, { all: a.all, speedTest });
98
136
  await checkUpdate();
99
137
  const notice = updateNotice();
100
138
  if (notice)
101
139
  console.error("\n" + yellow(notice));
140
+ if (tty && choices.length)
141
+ await offerConnect(choices, timeout);
102
142
  }
103
143
  main().catch(async (e) => {
104
144
  leaveScreen();
@@ -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(`${CLI} connect "${target}" ${b.protocol}`),
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
  }
package/dist/update.js CHANGED
@@ -20,9 +20,12 @@ export function checkUpdate() {
20
20
  .catch(() => { });
21
21
  return check;
22
22
  }
23
+ // run through npx: there's nothing installed to update, the next npx windscout@latest fetches it
24
+ export const viaNpx = () => process.env.npm_command === "exec";
25
+ export const UPDATE_COMMAND = "npm install -g windscout@latest";
23
26
  /** "update available: …" with the command to run, once checkUpdate() found a newer version */
24
27
  export function updateNotice() {
25
28
  if (!latest)
26
29
  return null;
27
- return `update available: ${VERSION} → ${latest} · run: ${process.env.npm_command === "exec" ? "npx windscout@latest" : "npm install -g windscout@latest"}`;
30
+ return `update available: ${VERSION} → ${latest} · run: ${viaNpx() ? "npx windscout@latest" : UPDATE_COMMAND}`;
28
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windscout",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Find which Windscribe locations and protocols work from your network, ranked by speed (unofficial)",
5
5
  "type": "module",
6
6
  "bin": {