cfpilot-scanner 0.1.0 → 0.2.0

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 +119 -89
  2. package/dist/cli.js +83 -31
  3. package/package.json +41 -41
package/README.md CHANGED
@@ -1,89 +1,119 @@
1
- # cfpilot-scanner
2
-
3
- **CFPilot 本地真连接优选器** —— 用你自己的 Worker 域名做 SNI/Host,完成
4
- **WebSocket 101 真连接校验**,只保留真正能连通的 Cloudflare 优选 IP,
5
- 彻底消灭客户端里的 `-1`。
6
-
7
- > 绝大多数"优选 IP"工具只做 TCP 握手(端口通就算可用),
8
- > 结果填进客户端大量 `-1`。本工具会真的发起 TLS + WebSocket 升级,
9
- > 走完你自己 Worker 的入站路径,只有返回 `101` 的 IP 才会被保留。
10
-
11
- ## 安装
12
-
13
- ```bash
14
- npx -y cfpilot-scanner --help
15
- ```
16
-
17
- 无需安装,`npx` 直接运行;也可 `npm i -g cfpilot-scanner`。
18
-
19
- ## 快速开始
20
-
21
- ```bash
22
- # 1. 在你的面板里生成一次性扫描令牌(有效期短,用完即废)
23
- # 2. 执行优选(域名必须是绑好自定义域名的那个)
24
- npx -y cfpilot-scanner scan --host cfpilot.example.com --token <令牌>
25
-
26
- # 结果会自动回填到面板的「手动优选 IP」池
27
- ```
28
-
29
- 只打印不回填:
30
-
31
- ```bash
32
- npx -y cfpilot-scanner scan --host cfpilot.example.com --no-callback --top 20
33
- ```
34
-
35
- ## 三阶扫描
36
-
37
- | 阶段 | 做什么 | 默认开销 |
38
- |------|--------|----------|
39
- | 一阶 · 延迟探测 | 采样大量 IP 做 TCP 连接测延迟 | 采样 20000,并发 300 |
40
- | 二阶 · 真连接校验 | 对候选发起 **TLS + WebSocket 101** 握手 | 120 个候选 |
41
- | 三阶 · 下行测速 | 通过 `/__edge_probe` 流式下载测真实速度 | 30 个候选,5 MB |
42
-
43
- 只有通过二阶的 IP 才算"可用",三阶用于排序。
44
-
45
- ## 常用参数
46
-
47
- ```
48
- --host <domain> 你的 Worker 自定义域名(SNI 与 Host)
49
- --token <token> 面板生成的一次性令牌
50
- --top <n> 保留条数(默认 15)
51
- --concurrency <n> 一阶并发(默认 300)
52
- --sample <n> 候选采样数(默认 20000)
53
- --cidr <a,b> 自定义网段
54
- --ip-file <path> 使用本地 IP 列表(支持 CloudflareSpeedTest 的 result.csv)
55
- --max-latency <ms> 一阶延迟上限
56
- --json 以 JSON 输出
57
- ```
58
-
59
- ## 本地面板桥接
60
-
61
- ```bash
62
- npx -y cfpilot-scanner serve
63
- ```
64
-
65
- 在 `127.0.0.1:19527` 起一个本地服务,面板里点「本地真连接优选」会自动唤醒它,
66
- 通过 SSE 实时推送进度,不需要手动复制令牌。
67
-
68
- ## 输出示例
69
-
70
- ```
71
- 阶段一:采样 20000 个候选,并发 300 …
72
- 阶段二:120 个候选进入 101 校验 …
73
- 阶段三:30 个候选测速(5 MB)…
74
- ✅ 可用 14 个,已回填面板
75
-
76
- 104.17.242.168:443 12ms 18.4 MB/s
77
- 104.19.73.179:443 18ms 15.1 MB/s
78
- ...
79
- ```
80
-
81
- ## 为什么需要它
82
-
83
- 随机抽取的 Cloudflare 官方 IP 里,能真正完成 WebSocket 升级的比例只有约 1/4,
84
- 而且**因地区、运营商、时间而异**。只有用你自己的域名实测过,
85
- 才能得到当前网络下真正可用的那批 IP。
86
-
87
- ## 许可
88
-
89
- MIT
1
+ # cfpilot-scanner
2
+
3
+ **CFPilot 本地真连接优选器** —— 用你自己的 Worker 域名做 SNI/Host,完成
4
+ **WebSocket 101 真连接校验**,只保留真正能连通的 Cloudflare 优选 IP,
5
+ 彻底消灭客户端里的 `-1`。
6
+
7
+ > 绝大多数"优选 IP"工具只做 TCP 握手(端口通就算可用),
8
+ > 结果填进客户端大量 `-1`。本工具会真的发起 TLS + WebSocket 升级,
9
+ > 走完你自己 Worker 的入站路径,只有返回 `101` 的 IP 才会被保留。
10
+
11
+ ## 安装
12
+
13
+ ```bash
14
+ npx -y cfpilot-scanner --help
15
+ ```
16
+
17
+ 无需安装,`npx` 直接运行;也可 `npm i -g cfpilot-scanner`。
18
+
19
+ ## 快速开始
20
+
21
+ ```bash
22
+ # 1. 在你的面板里生成一次性扫描令牌(有效期短,用完即废)
23
+ # 2. 执行优选(域名必须是绑好自定义域名的那个)
24
+ npx -y cfpilot-scanner scan --host cfpilot.example.com --token <令牌>
25
+
26
+ # 结果会自动回填到面板的「手动优选 IP」池
27
+ ```
28
+
29
+ 只打印不回填:
30
+
31
+ ```bash
32
+ npx -y cfpilot-scanner scan --host cfpilot.example.com --no-callback --top 20
33
+ ```
34
+
35
+ ## 对接任意 VLESS-WS 项目(CFNext / cfnew / BPB / edgetunnel)
36
+
37
+ 不依赖 CFPilot 自己的探针端点,只要知道你项目的 **WebSocket 路径**即可:
38
+
39
+ ```bash
40
+ # CFNext:路径通常就是 UUID(或面板里配置的 path)
41
+ npx -y cfpilot-scanner scan --host your-domain.com --path /<uuid> --no-callback
42
+
43
+ # 常见约定:/vless
44
+ npx -y cfpilot-scanner scan --host your-domain.com --path /vless --no-callback
45
+ ```
46
+
47
+ - 阶段二会对 `<path>` 发起真实的 **TLS + WebSocket 101** 握手,只有 101 才算可用
48
+ - 如果目标没有测速端点,会自动跳过测速、只按延迟排序(不会再干等 12 秒)
49
+ - 想测速可加 `--probe-path <路径>` 与 `--no-speed` 控制行为
50
+
51
+ ## 输出:直接可复制的列表
52
+
53
+ 扫描结束后会额外打印一段,**一行一个,直接粘进同类项目的「自定义 IP / 优选 IP」输入框**:
54
+
55
+ ```
56
+ ── 可直接复制(粘贴到「自定义 IP / 优选 IP」输入框)──
57
+ 104.19.73.179:443#LAX
58
+ 104.17.242.168:443#LAX
59
+ 104.16.59.243:443#LAX
60
+ 104.27.19.224:443#AMS
61
+ ```
62
+
63
+ (`IP:端口#机房` 是这类项目通用的语法;优选几个就有几行,不用再手动删改)
64
+
65
+ ## 三阶扫描
66
+
67
+ | 阶段 | 做什么 | 默认开销 |
68
+ |------|--------|----------|
69
+ | 一阶 · 延迟探测 | 采样大量 IP 做 TCP 连接测延迟 | 采样 20000,并发 300 |
70
+ | 二阶 · 真连接校验 | 对候选发起 **TLS + WebSocket 101** 握手 | 120 个候选 |
71
+ | 三阶 · 下行测速 | 通过 `/__edge_probe` 流式下载测真实速度 | 30 个候选,5 MB |
72
+
73
+ 只有通过二阶的 IP 才算"可用",三阶用于排序。
74
+
75
+ ## 常用参数
76
+
77
+ ```
78
+ --host <domain> 你的 Worker 自定义域名(SNI 与 Host)
79
+ --token <token> 面板生成的一次性令牌
80
+ --top <n> 保留条数(默认 15)
81
+ --concurrency <n> 一阶并发(默认 300)
82
+ --sample <n> 候选采样数(默认 20000)
83
+ --cidr <a,b> 自定义网段
84
+ --ip-file <path> 使用本地 IP 列表(支持 CloudflareSpeedTest 的 result.csv)
85
+ --max-latency <ms> 一阶延迟上限
86
+ --json 以 JSON 输出
87
+ ```
88
+
89
+ ## 本地面板桥接
90
+
91
+ ```bash
92
+ npx -y cfpilot-scanner serve
93
+ ```
94
+
95
+ 在 `127.0.0.1:19527` 起一个本地服务,面板里点「本地真连接优选」会自动唤醒它,
96
+ 通过 SSE 实时推送进度,不需要手动复制令牌。
97
+
98
+ ## 输出示例
99
+
100
+ ```
101
+ 阶段一:采样 20000 个候选,并发 300 …
102
+ 阶段二:120 个候选进入 101 校验 …
103
+ 阶段三:30 个候选测速(5 MB)…
104
+ ✅ 可用 14 个,已回填面板
105
+
106
+ 104.17.242.168:443 12ms 18.4 MB/s
107
+ 104.19.73.179:443 18ms 15.1 MB/s
108
+ ...
109
+ ```
110
+
111
+ ## 为什么需要它
112
+
113
+ 随机抽取的 Cloudflare 官方 IP 里,能真正完成 WebSocket 升级的比例只有约 1/4,
114
+ 而且**因地区、运营商、时间而异**。只有用你自己的域名实测过,
115
+ 才能得到当前网络下真正可用的那批 IP。
116
+
117
+ ## 许可
118
+
119
+ MIT
package/dist/cli.js CHANGED
@@ -337,7 +337,7 @@ function extractColo(text) {
337
337
  const match = /cf-ray:\s*[0-9a-f]+-([A-Z]{3})/i.exec(text);
338
338
  return match ? match[1].toUpperCase() : "";
339
339
  }
340
- function probeWs101(ip, port, host, timeoutMs = 3e3) {
340
+ function probeWs101(ip, port, host, timeoutMs = 3e3, path = "/__edge_probe") {
341
341
  return new Promise((resolve) => {
342
342
  const started = Date.now();
343
343
  const key = crypto.randomBytes(16).toString("base64");
@@ -354,7 +354,7 @@ function probeWs101(ip, port, host, timeoutMs = 3e3) {
354
354
  resolve({ ...result, latencyMs: Date.now() - started });
355
355
  };
356
356
  const request = [
357
- `GET /__edge_probe HTTP/1.1`,
357
+ `GET ${path} HTTP/1.1`,
358
358
  `Host: ${host}`,
359
359
  `Upgrade: websocket`,
360
360
  `Connection: Upgrade`,
@@ -405,7 +405,7 @@ function probeWs101(ip, port, host, timeoutMs = 3e3) {
405
405
  });
406
406
  });
407
407
  }
408
- function measureDownload(ip, port, host, bytes, timeoutMs = 12e3) {
408
+ function measureDownload(ip, port, host, bytes, timeoutMs = 12e3, path = "/__edge_probe") {
409
409
  return new Promise((resolve) => {
410
410
  const started = Date.now();
411
411
  let headerEnd = -1;
@@ -423,7 +423,7 @@ function measureDownload(ip, port, host, bytes, timeoutMs = 12e3) {
423
423
  resolve({ ...result, ms: Date.now() - started });
424
424
  };
425
425
  const request = [
426
- `GET /__edge_probe?bytes=${bytes} HTTP/1.1`,
426
+ `GET ${path}?bytes=${bytes} HTTP/1.1`,
427
427
  `Host: ${host}`,
428
428
  `User-Agent: ${USER_AGENT}`,
429
429
  `Accept: */*`,
@@ -463,6 +463,11 @@ function measureDownload(ip, port, host, bytes, timeoutMs = 12e3) {
463
463
  socket.on("error", (err) => {
464
464
  finish({ ok: false, bytes: received, speedMBps: 0, error: err.message });
465
465
  });
466
+ const settle = () => {
467
+ if (settled) return;
468
+ const seconds = Math.max(1e-3, (Date.now() - started) / 1e3);
469
+ finish({ ok: received > 0, bytes: received, speedMBps: received / 1024 / 1024 / seconds });
470
+ };
466
471
  socket.on("data", (chunk) => {
467
472
  if (headerEnd < 0) {
468
473
  buffer += chunk.toString("latin1");
@@ -475,10 +480,13 @@ function measureDownload(ip, port, host, bytes, timeoutMs = 12e3) {
475
480
  const body = Buffer.from(buffer, "latin1").subarray(headerEnd);
476
481
  received += body.byteLength;
477
482
  headerEnd = 0;
478
- return;
483
+ } else {
484
+ received += chunk.byteLength;
479
485
  }
480
- received += chunk.byteLength;
486
+ if (received >= bytes) settle();
481
487
  });
488
+ socket.on("end", settle);
489
+ socket.on("close", settle);
482
490
  });
483
491
  }
484
492
 
@@ -554,7 +562,7 @@ async function runScan(options) {
554
562
  report({ type: "stage", stage: 2, name: "WebSocket 101 \u771F\u8FDE\u63A5\u6821\u9A8C", total: stage1.length });
555
563
  let probeDone = 0;
556
564
  const probeResults = await pool(stage1, probeConcurrency, async (item) => {
557
- const result = await probeWs101(item.ip, options.port, options.host, probeTimeoutMs);
565
+ const result = await probeWs101(item.ip, options.port, options.host, probeTimeoutMs, options.wsPath ?? "/__edge_probe");
558
566
  probeDone++;
559
567
  report({
560
568
  type: "progress",
@@ -567,33 +575,40 @@ async function runScan(options) {
567
575
  });
568
576
  const wsOk = probeResults.filter((r) => r.probe.ok);
569
577
  const stage2 = wsOk.sort((a, b) => a.latency - b.latency).slice(0, speedLimit);
570
- report({ type: "stage", stage: 3, name: "\u771F\u5B9E\u4E0B\u884C\u901F\u5EA6\u6D4B\u8BD5", total: stage2.length });
571
- let speedDone = 0;
572
- const speedResults = await pool(stage2, speedConcurrency, async (item) => {
573
- const result = await measureDownload(
574
- item.ip,
575
- options.port,
576
- options.host,
577
- speedBytes,
578
- speedTimeoutMs
579
- );
580
- speedDone++;
581
- report({
582
- type: "progress",
583
- stage: 3,
584
- done: speedDone,
585
- total: stage2.length,
586
- message: `\u6D4B\u901F ${speedDone}/${stage2.length} ${result.speedMBps.toFixed(1)} MB/s`
578
+ let speedResults;
579
+ if (options.skipSpeed) {
580
+ report({ type: "stage", stage: 3, name: "\u8DF3\u8FC7\u6D4B\u901F\uFF08\u6309\u5EF6\u8FDF\u6392\u5E8F\uFF09", total: stage2.length });
581
+ speedResults = stage2.map((r) => ({ ...r }));
582
+ } else {
583
+ report({ type: "stage", stage: 3, name: "\u771F\u5B9E\u4E0B\u884C\u901F\u5EA6\u6D4B\u8BD5", total: stage2.length });
584
+ let speedDone = 0;
585
+ speedResults = await pool(stage2, speedConcurrency, async (item) => {
586
+ const result = await measureDownload(
587
+ item.ip,
588
+ options.port,
589
+ options.host,
590
+ speedBytes,
591
+ speedTimeoutMs,
592
+ options.probePath ?? "/__edge_probe"
593
+ );
594
+ speedDone++;
595
+ report({
596
+ type: "progress",
597
+ stage: 3,
598
+ done: speedDone,
599
+ total: stage2.length,
600
+ message: `\u6D4B\u901F ${speedDone}/${stage2.length} ${result.speedMBps.toFixed(1)} MB/s`
601
+ });
602
+ return { ...item, download: result };
587
603
  });
588
- return { ...item, download: result };
589
- });
590
- const entries = speedResults.filter((r) => r.download.ok && r.download.speedMBps > 0).map((r) => ({
604
+ }
605
+ const entries = speedResults.filter((r) => options.skipSpeed ? true : Boolean(r.download && r.download.ok && r.download.speedMBps > 0)).map((r) => ({
591
606
  ip: r.ip,
592
607
  port: options.port,
593
608
  colo: r.probe.colo || "UNK",
594
609
  latency: r.latency,
595
- speed: Number(r.download.speedMBps.toFixed(1))
596
- })).sort((a, b) => b.speed - a.speed || a.latency - b.latency).slice(0, top);
610
+ speed: r.download ? Number(r.download.speedMBps.toFixed(2)) : 0
611
+ })).sort((a, b) => options.skipSpeed ? a.latency - b.latency : b.speed - a.speed || a.latency - b.latency).slice(0, top);
597
612
  for (const entry of entries) {
598
613
  report({ type: "result", entry });
599
614
  }
@@ -610,6 +625,9 @@ async function runScan(options) {
610
625
  report({ type: "done", count: entries.length });
611
626
  return summary;
612
627
  }
628
+ function formatCopyable(entries) {
629
+ return entries.map((e) => `${e.ip}:${e.port}#${e.colo}`).join("\n");
630
+ }
613
631
  function formatEntries(entries) {
614
632
  return entries.map((e) => `${e.ip}:${e.port}#${e.colo}_${e.latency}_${e.speed}`);
615
633
  }
@@ -639,7 +657,7 @@ async function postResults(callbackUrl, token, entries, timeoutMs = 15e3) {
639
657
 
640
658
  // src/server.ts
641
659
  import { createServer } from "node:http";
642
- var BRIDGE_VERSION = "0.1.0";
660
+ var BRIDGE_VERSION = "0.2.0";
643
661
  var DEFAULT_BRIDGE_PORT = 19527;
644
662
  function normalizeSample(value) {
645
663
  if (typeof value === "number" && Number.isFinite(value) && value > 0) {
@@ -906,7 +924,7 @@ async function handleRequest(req, res) {
906
924
  }
907
925
 
908
926
  // src/version.ts
909
- var VERSION = "0.1.0";
927
+ var VERSION = "0.2.0";
910
928
  var NAME = "cfpilot-scanner";
911
929
 
912
930
  // src/cli.ts
@@ -928,6 +946,10 @@ scan \u9009\u9879:
928
946
  --token <token> \u56DE\u586B\u4EE4\u724C\uFF08\u9762\u677F\u751F\u6210\u7684\u4E00\u6B21\u6027\u4EE4\u724C\u6216 SCANNER_TOKEN\uFF09
929
947
  --callback <url> \u56DE\u586B\u5730\u5740\uFF0C\u9ED8\u8BA4 https://<host>/api/v1/preferred-ips
930
948
  --no-callback \u53EA\u6253\u5370\u7ED3\u679C\uFF0C\u4E0D\u56DE\u586B
949
+ --path <path> \u9636\u6BB5\u4E8C\u6821\u9A8C\u7684 WebSocket \u8DEF\u5F84\uFF0C\u9ED8\u8BA4 /__edge_probe
950
+ \uFF08\u5BF9\u63A5\u5176\u5B83\u9879\u76EE\u65F6\u586B\u5B83\u81EA\u5DF1\u7684 WS \u8DEF\u5F84\uFF0C\u4F8B\u5982 /vless \u6216 /<uuid>\uFF09
951
+ --probe-path <path> \u9636\u6BB5\u4E09\u6D4B\u901F\u8DEF\u5F84\uFF0C\u9ED8\u8BA4 /__edge_probe
952
+ --no-speed \u8DF3\u8FC7\u6D4B\u901F\uFF0C\u53EA\u6309\u5EF6\u8FDF\u6392\u5E8F\uFF08\u76EE\u6807\u6CA1\u6709 CFPilot \u63A2\u9488\u65F6\u7528\uFF09
931
953
  --top <n> \u8FD4\u56DE\u6761\u6570\uFF0C\u9ED8\u8BA4 15
932
954
  --concurrency <n> \u9636\u6BB5\u4E00\u5E76\u53D1\uFF0C\u9ED8\u8BA4 300
933
955
  --sample <n> \u5019\u9009 IP \u91C7\u6837\u6570\uFF0C\u9ED8\u8BA4 20000
@@ -1057,6 +1079,11 @@ function printTable(entries) {
1057
1079
  `);
1058
1080
  });
1059
1081
  }
1082
+ function printCopyable(entries) {
1083
+ if (entries.length === 0) return;
1084
+ process.stdout.write("\n\u2500\u2500 \u53EF\u76F4\u63A5\u590D\u5236\uFF08\u7C98\u8D34\u5230\u300C\u81EA\u5B9A\u4E49 IP / \u4F18\u9009 IP\u300D\u8F93\u5165\u6846\uFF09\u2500\u2500\n");
1085
+ process.stdout.write(formatCopyable(entries) + "\n");
1086
+ }
1060
1087
  async function commandScan(args) {
1061
1088
  const host = flagString(args, "host");
1062
1089
  const quiet = flagBool(args, "quiet");
@@ -1070,6 +1097,9 @@ async function commandScan(args) {
1070
1097
  const top = flagNumber(args, "top", 15);
1071
1098
  const token = flagString(args, "token");
1072
1099
  const noCallback = flagBool(args, "no-callback");
1100
+ const wsPath = flagString(args, "path", "/__edge_probe");
1101
+ const probePath = flagString(args, "probe-path", "/__edge_probe");
1102
+ const noSpeed = flagBool(args, "no-speed");
1073
1103
  const callback = noCallback ? "" : flagString(args, "callback", token ? `https://${host}/api/v1/preferred-ips` : "");
1074
1104
  if (!quiet) {
1075
1105
  process.stdout.write(`
@@ -1086,10 +1116,31 @@ ${NAME} v${VERSION}
1086
1116
  }
1087
1117
  if (!quiet) process.stdout.write(`\u5019\u9009 IP\uFF1A${candidates.length} \u4E2A
1088
1118
  `);
1119
+ let skipSpeed = noSpeed;
1120
+ if (!skipSpeed) {
1121
+ try {
1122
+ const probe = await fetch(`https://${host}${probePath}?bytes=1024`, {
1123
+ method: "GET",
1124
+ signal: AbortSignal.timeout(8e3)
1125
+ });
1126
+ if (!probe.headers.get("x-probe-bytes")) {
1127
+ skipSpeed = true;
1128
+ if (!quiet) {
1129
+ process.stdout.write("\n\u63D0\u793A\uFF1A" + probePath + " \u4E0D\u662F CFPilot \u63A2\u9488\u7AEF\u70B9\uFF0C\u5DF2\u8DF3\u8FC7\u6D4B\u901F\uFF08\u53EA\u6309\u5EF6\u8FDF\u6392\u5E8F\uFF09\n");
1130
+ process.stdout.write(" \u5982\u9700\u6D4B\u901F\uFF0C\u8BF7\u7528 --probe-path \u6307\u5B9A\u76EE\u6807\u9879\u76EE\u7684\u6D4B\u901F\u8DEF\u5F84\n");
1131
+ }
1132
+ }
1133
+ } catch {
1134
+ skipSpeed = true;
1135
+ }
1136
+ }
1089
1137
  const summary = await runScan({
1090
1138
  host,
1091
1139
  port,
1092
1140
  candidates,
1141
+ wsPath,
1142
+ probePath,
1143
+ skipSpeed,
1093
1144
  concurrency: flagNumber(args, "concurrency", 300),
1094
1145
  tcpTimeoutMs: flagNumber(args, "timeout", 1200),
1095
1146
  maxLatencyMs: flagNumber(args, "max-latency", 0),
@@ -1108,6 +1159,7 @@ ${NAME} v${VERSION}
1108
1159
  );
1109
1160
  } else {
1110
1161
  printTable(summary.entries);
1162
+ printCopyable(summary.entries);
1111
1163
  }
1112
1164
  if (callback && token) {
1113
1165
  const result = await postResults(callback, token, formatEntries(summary.entries));
package/package.json CHANGED
@@ -1,41 +1,41 @@
1
- {
2
- "name": "cfpilot-scanner",
3
- "version": "0.1.0",
4
- "description": "CFPilot 本地真连接优选器:用你自己的 Worker 域名做 SNI/Host 完成 WebSocket 101 真连接校验,彻底消灭 -1 节点",
5
- "license": "MIT",
6
- "author": "CFPilot Contributors",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/xmwengxing/CFPilot.git"
10
- },
11
- "keywords": [
12
- "cloudflare",
13
- "ip",
14
- "optimize",
15
- "scanner",
16
- "vless",
17
- "websocket",
18
- "cfpilot"
19
- ],
20
- "type": "module",
21
- "bin": {
22
- "cfpilot-scanner": "dist/cli.js"
23
- },
24
- "files": [
25
- "dist",
26
- "README.md"
27
- ],
28
- "engines": {
29
- "node": ">=18.17.0"
30
- },
31
- "scripts": {
32
- "build": "node scripts/build.mjs",
33
- "typecheck": "tsc --noEmit -p tsconfig.json",
34
- "prepublishOnly": "npm run build"
35
- },
36
- "devDependencies": {
37
- "@types/node": "^26.6.5",
38
- "esbuild": "^0.28.2",
39
- "typescript": "^5.9.3"
40
- }
41
- }
1
+ {
2
+ "name": "cfpilot-scanner",
3
+ "version": "0.2.0",
4
+ "description": "CFPilot 优选器:用你自己的域名做 WebSocket 101 真连接校验,只保留真正可用的 Cloudflare IP;兼容任意 VLESS-WS 项目",
5
+ "license": "MIT",
6
+ "author": "CFPilot Contributors",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/xmwengxing/CFPilot.git"
10
+ },
11
+ "keywords": [
12
+ "cloudflare",
13
+ "ip",
14
+ "optimize",
15
+ "scanner",
16
+ "vless",
17
+ "websocket",
18
+ "cfpilot"
19
+ ],
20
+ "type": "module",
21
+ "bin": {
22
+ "cfpilot-scanner": "dist/cli.js"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md"
27
+ ],
28
+ "engines": {
29
+ "node": ">=18.17.0"
30
+ },
31
+ "scripts": {
32
+ "build": "node scripts/build.mjs",
33
+ "typecheck": "tsc --noEmit -p tsconfig.json",
34
+ "prepublishOnly": "npm run build"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^26.6.5",
38
+ "esbuild": "^0.28.2",
39
+ "typescript": "^5.9.3"
40
+ }
41
+ }