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 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();
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windscout",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Find which Windscribe locations and protocols work from your network, ranked by speed (unofficial)",
5
5
  "type": "module",
6
6
  "bin": {