windscout 0.3.1 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +19 -14
  2. package/package.json +14 -3
  3. package/windscout.js +25 -15
package/README.md CHANGED
@@ -4,6 +4,8 @@ Find which Windscribe locations and protocols actually work from your network, r
4
4
 
5
5
  WindScout drives the official `windscribe-cli`: it connects to each country × protocol pair, measures latency and download speed through the tunnel, disconnects, and prints a ranked table.
6
6
 
7
+ ![WindScout picking countries, scanning, and printing the availability grid and ranked results](demo.gif)
8
+
7
9
  > Unofficial tool, not affiliated with Windscribe.
8
10
 
9
11
  ## Requirements
@@ -27,6 +29,7 @@ Run `windscout` with no arguments to open the interactive picker:
27
29
  ```
28
30
  ╭─ WindScout ──────────────────────────────────── 2/68 selected ─╮
29
31
  │ 1 ● wireguard 2 ● stealth 3 ○ wstunnel 4 ● udp 5 ● tcp │
32
+ │ s ○ speed test off: connect-only, ranks by latency │
30
33
  ╰────────────────────────────────────────────────────────────────╯
31
34
  ╭────────────────────────────────────────────────────────────────╮
32
35
  │ 🔍 an▏ 1–16 of 18 │
@@ -51,7 +54,7 @@ Run `windscout` with no arguments to open the interactive picker:
51
54
  | `enter` | start scanning |
52
55
  | `q` | quit (during a scan: stop and show partial results; press again to force quit) |
53
56
 
54
- **Mouse:** click a country to select or unselect it, click a protocol or the speed-test line to toggle it, click the search box to start typing (click anywhere else to leave it), and scroll the list with the wheel. The `[ ▶ Start ] [ All ] [ None ] [ Quit ]` buttons under the table do the same as their keys. This needs a terminal with mouse support, which most have. On Windows, use Windows Terminal; if clicks do nothing there, the keyboard does everything.
57
+ **Mouse:** click a country to select or unselect it, click a protocol or the speed-test line to toggle it, click the search box to start typing (click anywhere else to leave it), and scroll the list with the wheel. The `[ ▶ Start ] [ All ] [ None ] [ Quit ]` buttons under the table do the same as their keys; the shortcut key is underlined on screen (on the buttons, the protocol numbers and `s`). This needs a terminal with mouse support, which most have. On Windows, use Windows Terminal; if clicks do nothing there, the keyboard does everything.
55
58
 
56
59
  Your selection is saved to `~/.config/windscout/config.json` (`%USERPROFILE%\.config\windscout\config.json` on Windows) and preselected next time.
57
60
 
@@ -73,25 +76,26 @@ windscout -t 40 -a # longer connect timeout, also list
73
76
  | `-p, --protocols` | comma-separated: `wireguard`, `ikev2` (macOS and Windows), `stealth`, `wstunnel`, `udp`, `tcp`, optionally `:port` | saved, or all |
74
77
  | `-t, --timeout` | seconds to wait for each connection | 20 |
75
78
  | `-a, --all` | include failed attempts in the final table | off |
76
- | `-n, --no-speed` | connect-only mode: skip the download test, rank by latency | saved, or off |
79
+ | `-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 |
77
80
  | `-v, --version` | print the installed version, and the update command if a newer one is on npm | |
78
81
 
79
82
  ## Connect-only mode
80
83
 
81
- Often you only need to know **what connects**, not how fast it is. Turn the speed test off with `s` in the picker or with `--no-speed`. Each working connection is then checked with a quick latency test only. That saves about 15 s per working connection, and results are ranked by latency. The choice is remembered.
84
+ Often you only need to know **what connects**, not how fast it is. Turn the speed test off with `s` in the picker or with `--no-speed`. Each working connection is then checked with a quick latency test only. That saves about 15 s per working connection, and results are ranked by latency. Connect-only is the default on the first run, and the choice is remembered.
82
85
 
83
- Both modes show an **availability grid**: one row per country, and ✓ / ✗ for each protocol (`·` = not tried yet):
86
+ Both modes show an **availability grid**: one row per country, and ✓ / ✗ for each protocol (`·` = not tried yet). Location(s) lists each datacenter that connected, one per line, since protocols can land on different ones:
84
87
 
85
88
  ```
86
- ╭────────────────────────────────────────────────────────────────────────╮
87
- │ Availability 2/3 countries reachable │
88
- ├─────────────────────────┬──────────────────┬─────────┬───────────┬─────┤
89
- │ Country │ City │ stealth │ wireguard │ tcp │
90
- ├─────────────────────────┼──────────────────┼─────────┼───────────┼─────┤
91
- │ 🇩🇪 Germany │ Frankfurt │ ✓ │ ✓ │ ✓ │
92
- │ 🇳🇱 Netherlands │ Amsterdam │ ✓ │ ✓ │ · │
93
- │ 🇦🇪 United Arab Emirates │ Dubai │ ✗ │ ✗ │ ✗ │
94
- ╰─────────────────────────┴──────────────────┴─────────┴───────────┴─────╯
89
+ ╭──────────────────────────────────────────────────────────────────────────────╮
90
+ │ Availability 2/3 countries reachable │
91
+ ├─────────────────────────┬────────────────────────┬─────────┬───────────┬─────┤
92
+ │ Country │ Location(s) │ stealth │ wireguard │ tcp │
93
+ ├─────────────────────────┼────────────────────────┼─────────┼───────────┼─────┤
94
+ │ 🇩🇪 Germany │ Frankfurt - Castle │ ✓ │ ✓ │ ✓ │
95
+ │ │ Frankfurt - Wurst │ │ │ │
96
+ │ 🇳🇱 Netherlands │ Amsterdam - Tulip │ ✓ │ ✓ │ · │
97
+ │ 🇦🇪 United Arab Emirates │ Dubai │ ✗ │ ✗ │ ✗ │
98
+ ╰─────────────────────────┴────────────────────────┴─────────┴───────────┴─────╯
95
99
  ```
96
100
 
97
101
  While scanning, press `tab` to switch between the availability grid and the speed or latency ranking. In connect-only mode the scan starts on the availability grid.
@@ -127,9 +131,10 @@ At the end, the results, a per-protocol summary and the best pick are printed to
127
131
 
128
132
  ## Notes
129
133
 
130
- - **Connecting by country picks a random datacenter.** A single run tests one per country. Pass city names or nicknames with `-c` to test specific datacenters.
134
+ - **Connecting by country picks a random datacenter.** A single run tests one per country and protocol, so different protocols may land on different datacenters. Pass city names or nicknames with `-c` to test specific datacenters.
131
135
  - **The country list is built in.** On v2.24, `windscribe-cli locations` returns nothing, so WindScout uses a fixed list of country codes. Codes Windscribe doesn't serve are skipped quickly.
132
136
  - **A full scan takes a while.** The worst case is countries × protocols × timeout. Narrow it with `-c` and `-p`.
137
+ - **No flags in macOS Terminal.** Terminal.app draws flag emoji wider than their space, which pushes table borders out of line, so WindScout leaves them out there (as on Windows). iTerm2 and other terminals show them.
133
138
  - **Your connection changes during the scan.** WindScout connects and disconnects the VPN repeatedly, so don't run it while you need a stable connection.
134
139
 
135
140
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windscout",
3
- "version": "0.3.1",
3
+ "version": "0.4.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": {
@@ -23,8 +23,19 @@
23
23
  "speedtest",
24
24
  "wireguard",
25
25
  "stealth",
26
- "cli"
26
+ "cli",
27
+ "censorship",
28
+ "firewall",
29
+ "unblock",
30
+ "vpn-speed-test",
31
+ "server-finder"
27
32
  ],
28
33
  "author": "Dier Cohen <i.am.dier.cohen@gmail.com>",
29
- "license": "MIT"
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/Diercohen/windscout.git"
38
+ },
39
+ "homepage": "https://github.com/Diercohen/windscout#readme",
40
+ "bugs": "https://github.com/Diercohen/windscout/issues"
30
41
  }
package/windscout.js CHANGED
@@ -41,8 +41,10 @@ const countryName = (code) => {
41
41
  try { return regionName.of(code); } catch { return code; } // city names / nicknames aren't region codes
42
42
  };
43
43
  // two regional-indicator letters render as the country's flag emoji (Windows has no flag glyphs: it would show
44
- // two boxed letters of unpredictable width and break the table borders, so leave the slot blank there)
45
- const flag = (code) => process.platform !== "win32" && /^[A-Z]{2}$/.test(code) ? String.fromCodePoint(...[...code].map((c) => 0x1f1a5 + c.charCodeAt(0))) : " ";
44
+ // two boxed letters of unpredictable width and break the table borders, so leave the slot blank there).
45
+ // macOS Terminal draws flags ~half a column wider than the 2 it advances, nudging table borders right: blank there too
46
+ const noFlags = process.platform === "win32" || process.env.TERM_PROGRAM === "Apple_Terminal";
47
+ const flag = (code) => !noFlags && /^[A-Z]{2}$/.test(code) ? String.fromCodePoint(...[...code].map((c) => 0x1f1a5 + c.charCodeAt(0))) : " ";
46
48
  let stop = 0;
47
49
  let speedTest = true; // false = connect-only mode: skip the download test, rank by latency
48
50
 
@@ -175,6 +177,8 @@ const updateNotice = () => `update available: ${VERSION} → ${update} · run: $
175
177
 
176
178
  const color = (code, s) => (process.env.NO_COLOR || !process.stdout.isTTY ? s : `\x1b[${code}m${s}\x1b[0m`);
177
179
  const bold = (s) => color(1, s), dim = (s) => color(2, s), green = (s) => color(32, s);
180
+ // underline off (24) instead of a full reset, so it nests inside dim/bold
181
+ const under = (s) => (process.env.NO_COLOR || !process.stdout.isTTY ? s : `\x1b[4m${s}\x1b[24m`);
178
182
  const cyan = (s) => color(36, s), yellow = (s) => color(33, s), red = (s) => color(31, s);
179
183
  const fmt = (v, digits) => (v === null || v === undefined ? "-" : v.toFixed(digits));
180
184
  const list = (s) => s.split(",").map((x) => x.trim()).filter(Boolean);
@@ -269,19 +273,25 @@ function matrix(results, protocols, { minWidth = 0, limit = Infinity } = {}) {
269
273
  const ranked = [...byCountry].map(([q, rs]) => ({ q, rs, good: rs.filter((r) => r.status === "ok").length }))
270
274
  .sort((a, b) => b.good - a.good); // stable: ties keep scan order
271
275
  const reachable = ranked.filter((c) => c.good).length;
272
- const rows = ranked.slice(0, limit).map(({ q, rs }) => {
276
+ const rows = ranked.slice(0, limit).flatMap(({ q, rs }) => {
273
277
  const seen = rs.find((r) => r.status === "ok") ?? rs.find((r) => r.location);
278
+ // protocols can land on different datacenters (even cities), so list each one that connected, one per line
279
+ const places = [...new Set(rs.filter((r) => r.status === "ok").map((r) => r.location).filter(Boolean))];
280
+ const [first = seen?.location?.split(" - ")[0] || dim("-"), ...more] = places;
274
281
  return [
275
- `${flag(q)} ${countryName(q)}`,
276
- seen?.location?.split(" - ")[0] || dim("-"),
277
- ...protocols.map((p) => {
278
- const r = rs.find((x) => x.protocol === p);
279
- return !r ? dim("·") : r.status === "ok" ? green("✓") : red("✗");
280
- }),
282
+ [
283
+ `${flag(q)} ${countryName(q)}`,
284
+ first,
285
+ ...protocols.map((p) => {
286
+ const r = rs.find((x) => x.protocol === p);
287
+ return !r ? dim("·") : r.status === "ok" ? green("✓") : red("✗");
288
+ }),
289
+ ],
290
+ ...more.map((place) => ["", place, ...protocols.map(() => "")]),
281
291
  ];
282
- });
292
+ }).slice(0, limit);
283
293
  return grid(
284
- [{ title: "Country" }, { title: "City" }, ...protocols.map((p) => ({ title: p, align: "center" }))],
294
+ [{ title: "Country" }, { title: "Location(s)" }, ...protocols.map((p) => ({ title: p, align: "center" }))],
285
295
  rows,
286
296
  {
287
297
  caption: bold("Availability"),
@@ -409,8 +419,8 @@ function pick(saved, timeout) {
409
419
  top = Math.max(0, Math.min(top, list.length - height));
410
420
 
411
421
  const protoLine = PROTOCOLS.map((p, i) =>
412
- `${dim(String(i + 1))} ${protos.has(p) ? green("● " + p) : dim("○ " + p)}`).join(" ");
413
- const speedLine = `${dim("s")} ${speedTest ? green("● speed test") : dim("○ speed test")} ${dim(speedTest
422
+ `${dim(under(String(i + 1)))} ${protos.has(p) ? green("● " + p) : dim("○ " + p)}`).join(" ");
423
+ const speedLine = `${dim(under("s"))} ${speedTest ? green("● speed test") : dim("○ speed test")} ${dim(speedTest
414
424
  ? "ranks by download speed · adds ~15s per working connection"
415
425
  : "off: connect-only, ranks by latency")}`;
416
426
  const search = searching
@@ -425,7 +435,7 @@ function pick(saved, timeout) {
425
435
  return [`${here ? cyan("❯") : " "} ${mark}`, here ? bold(code) : dim(code), here ? bold(cyan(name)) : name];
426
436
  });
427
437
  if (!body.length) body.push([" ", "", dim("no country matches")]);
428
- const buttons = [["start", "▶ Start"], ["all", query ? "All shown" : "All"], ["none", query ? "None shown" : "None"], ["quit", "Quit"]];
438
+ const buttons = [["start", "▶ Start"], ["all", under("A") + (query ? "ll shown" : "ll")], ["none", under("N") + (query ? "one shown" : "one")], ["quit", under("Q") + "uit"]];
429
439
  const buttonLine = buttons.map(([id, label]) => (id === "start" ? bold(cyan(`[ ${label} ]`)) : dim(`[ ${label} ]`))).join(" ");
430
440
 
431
441
  const table = grid([{ title: "" }, { title: "Code" }, { title: "Country" }], body,
@@ -796,7 +806,7 @@ async function main() {
796
806
  if (!tty) throw new Error("no terminal for the picker: pass countries with -c");
797
807
  const saved = loadConfig();
798
808
  if (protocols && saved) saved.protocols = protocols.filter((p) => PROTOCOLS.includes(p));
799
- speedTest = !a["no-speed"] && (saved?.speed ?? true);
809
+ speedTest = !a["no-speed"] && (saved?.speed ?? false); // first run: connect-only
800
810
  const choice = await pick(saved, timeout);
801
811
  if (!choice) return;
802
812
  countries = choice.countries;