@ada-mcp/launcher 0.1.41 → 0.1.42

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
@@ -8,7 +8,7 @@
8
8
  "mcpServers": {
9
9
  "ada-mcp": {
10
10
  "command": "pnpm",
11
- "args": ["dlx", "@ada-mcp/launcher@0.1.41"]
11
+ "args": ["dlx", "@ada-mcp/launcher@0.1.42"]
12
12
  }
13
13
  }
14
14
  }
@@ -17,12 +17,12 @@
17
17
  命令行:
18
18
 
19
19
  ```bash
20
- pnpm dlx @ada-mcp/launcher@0.1.41
20
+ pnpm dlx @ada-mcp/launcher@0.1.42
21
21
  ```
22
22
 
23
23
  npx 等价�?
24
24
  ```bash
25
- npx -y @ada-mcp/launcher@0.1.41
25
+ npx -y @ada-mcp/launcher@0.1.42
26
26
  ```
27
27
 
28
28
  **npx 示例**�?
@@ -31,14 +31,14 @@ npx -y @ada-mcp/launcher@0.1.41
31
31
  "mcpServers": {
32
32
  "ada-mcp": {
33
33
  "command": "npx",
34
- "args": ["-y", "@ada-mcp/launcher@0.1.41"]
34
+ "args": ["-y", "@ada-mcp/launcher@0.1.42"]
35
35
  }
36
36
  }
37
37
  }
38
38
  ```
39
39
 
40
40
  ## 版本�?
41
- - **使用**:`pnpm dlx @ada-mcp/launcher` �?`@ada-mcp/launcher@0.1.41` 均可;不�?`@x.y.z` 时拉 npm **latest**(推荐生产环境钉版本)�?- **发布**:每�?`npm publish` 前必须在 `package.json` **递增 version**;不能重复发布同一版本�?
41
+ - **使用**:`pnpm dlx @ada-mcp/launcher` �?`@ada-mcp/launcher@0.1.42` 均可;不�?`@x.y.z` 时拉 npm **latest**(推荐生产环境钉版本)�?- **发布**:每�?`npm publish` 前必须在 `package.json` **递增 version**;不能重复发布同一版本�?
42
42
  ## 可选环境变�?
43
43
  | 变量 | 说明 |
44
44
  |------|------|
@@ -51,7 +51,7 @@ npx -y @ada-mcp/launcher@0.1.41
51
51
  ## 与直�?`dlx @ada-mcp/mcp-server` 的区�?
52
52
  | 方式 | �?MCP �?| 安装 playwright �?|
53
53
  |------|-----------|-------------------|
54
- | **`pnpm dlx @ada-mcp/launcher@0.1.41`**(标准) | launcher 测�?+ `.npmrc` �?`pnpm dlx` mcp-server | mcp-server preinstall + bootstrap |
55
- | **`npx -y @ada-mcp/launcher@0.1.41`** | 同上 �?**`npx -y` mcp-server** | 同上 |
56
- | `pnpm dlx @ada-mcp/mcp-server@0.1.41` | 本机默认�?| �?preinstall / bootstrap 测�?|
57
- | `npx -y @ada-mcp/mcp-server@0.1.41` | �?launcher 测�?| �?preinstall / bootstrap 测�?|
54
+ | **`pnpm dlx @ada-mcp/launcher@0.1.42`**(标准) | launcher 测�?+ `.npmrc` �?`pnpm dlx` mcp-server | mcp-server preinstall + bootstrap |
55
+ | **`npx -y @ada-mcp/launcher@0.1.42`** | 同上 �?**`npx -y` mcp-server** | 同上 |
56
+ | `pnpm dlx @ada-mcp/mcp-server@0.1.42` | 本机默认�?| �?preinstall / bootstrap 测�?|
57
+ | `npx -y @ada-mcp/mcp-server@0.1.42` | �?launcher 测�?| �?preinstall / bootstrap 测�?|
@@ -1,8 +1,114 @@
1
- export {
2
- formatDownloadProbeLine,
3
- pickBestDownloadProbe,
4
- pickFastestProbeUrl,
5
- probeDownloadSample,
6
- probeDownloadTimeoutMs,
7
- probeSampleBytes
8
- } from "@ada/download-probe";
1
+ /**
2
+ * 内联 @ada/download-probe(launcher 零 npm 依赖;与 packages/download-probe 保持同步)
3
+ * 同步:node ../../scripts/sync-download-probe-vendor.mjs
4
+ */
5
+
6
+ function parsePositiveInt(raw, fallback) {
7
+ const parsed = raw ? Number(raw) : fallback;
8
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
9
+ }
10
+
11
+ export function probeSampleBytes() {
12
+ return parsePositiveInt(process.env.ADA_PROBE_DOWNLOAD_BYTES, 512 * 1024);
13
+ }
14
+
15
+ export function probeDownloadTimeoutMs() {
16
+ return parsePositiveInt(process.env.ADA_PROBE_DOWNLOAD_TIMEOUT_MS, 20_000);
17
+ }
18
+
19
+ /** 拉取 url 的前 sampleBytes 字节,返回吞吐;失败返回 null */
20
+ export async function probeDownloadSample(url, options) {
21
+ const sampleBytes = options?.sampleBytes ?? probeSampleBytes();
22
+ const timeoutMs = options?.timeoutMs ?? probeDownloadTimeoutMs();
23
+ const controller = new AbortController();
24
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
25
+ const started = Date.now();
26
+ try {
27
+ const response = await fetch(url, {
28
+ method: "GET",
29
+ headers: { Range: `bytes=0-${sampleBytes - 1}`, Accept: "*/*" },
30
+ redirect: "follow",
31
+ signal: controller.signal
32
+ });
33
+ if (response.status !== 200 && response.status !== 206) {
34
+ return null;
35
+ }
36
+ const body = response.body;
37
+ if (!body) {
38
+ return null;
39
+ }
40
+ const reader = body.getReader();
41
+ let bytesRead = 0;
42
+ try {
43
+ while (bytesRead < sampleBytes) {
44
+ const { done, value } = await reader.read();
45
+ if (done || !value?.length) {
46
+ break;
47
+ }
48
+ bytesRead += value.length;
49
+ }
50
+ } finally {
51
+ try {
52
+ await reader.cancel();
53
+ } catch {
54
+ // ignore
55
+ }
56
+ }
57
+ if (bytesRead < Math.min(sampleBytes / 8, 32 * 1024)) {
58
+ return null;
59
+ }
60
+ const durationMs = Math.max(1, Date.now() - started);
61
+ const speedKBps = bytesRead / 1024 / (durationMs / 1000);
62
+ return { durationMs, bytesRead, speedKBps };
63
+ } catch {
64
+ return null;
65
+ } finally {
66
+ clearTimeout(timer);
67
+ }
68
+ }
69
+
70
+ /** 按 speedKBps 降序;相同时样本耗时更短优先 */
71
+ export function pickBestDownloadProbe(rows, priorityIndex) {
72
+ const ok = rows.filter((r) => r.probe !== null);
73
+ if (ok.length === 0) {
74
+ return null;
75
+ }
76
+ ok.sort((a, b) => {
77
+ if (b.probe.speedKBps !== a.probe.speedKBps) {
78
+ return b.probe.speedKBps - a.probe.speedKBps;
79
+ }
80
+ if (a.probe.durationMs !== b.probe.durationMs) {
81
+ return a.probe.durationMs - b.probe.durationMs;
82
+ }
83
+ return priorityIndex(a.candidate) - priorityIndex(b.candidate);
84
+ });
85
+ return ok[0] ?? null;
86
+ }
87
+
88
+ export function formatDownloadProbeLine(prefix, candidate, probe) {
89
+ if (!probe) {
90
+ return `${prefix} ${candidate} -> fail`;
91
+ }
92
+ const mib = (probe.bytesRead / (1024 * 1024)).toFixed(2);
93
+ return `${prefix} ${candidate} -> ${probe.speedKBps.toFixed(0)} KB/s(${mib} MiB / ${probe.durationMs}ms)`;
94
+ }
95
+
96
+ /** 对 URL 列表测速,返回最快项 */
97
+ export async function pickFastestProbeUrl(urls, onLogLine) {
98
+ let best = null;
99
+ for (const url of urls) {
100
+ onLogLine?.(`[probe] 探测下载速度: ${url}`);
101
+ const probe = await probeDownloadSample(url);
102
+ if (!probe) {
103
+ onLogLine?.(`[probe] ${url} -> fail`);
104
+ continue;
105
+ }
106
+ onLogLine?.(
107
+ `[probe] ${url} -> ${probe.speedKBps.toFixed(0)} KB/s(${(probe.bytesRead / (1024 * 1024)).toFixed(2)} MiB / ${probe.durationMs}ms)`
108
+ );
109
+ if (!best || probe.speedKBps > best.probe.speedKBps) {
110
+ best = { url, probe };
111
+ }
112
+ }
113
+ return best;
114
+ }
@@ -1,8 +1,48 @@
1
- export {
2
- CHINA_NPM_REGISTRY_HINTS,
3
- CHINA_PLAYWRIGHT_HOST_PRIORITY,
4
- DEFAULT_GECKODRIVER_MIRROR_CANDIDATES,
5
- DEFAULT_NPM_REGISTRY_CANDIDATES,
6
- DEFAULT_PLAYWRIGHT_HOST_CANDIDATES,
7
- isChinaFriendlyNpmRegistry
8
- } from "@ada/download-probe";
1
+ /**
2
+ * 内联 @ada/download-probe 镜像候选(launcher 零 npm 依赖)
3
+ * 同步:node ../../scripts/sync-download-probe-vendor.mjs
4
+ */
5
+
6
+ /** 国内优先 npmmirror,其次官方;测速相同时列表靠前者优先 */
7
+ export const DEFAULT_NPM_REGISTRY_CANDIDATES = [
8
+ "https://registry.npmmirror.com",
9
+ "https://registry.npmjs.org",
10
+ "https://mirrors.cloud.tencent.com/npm",
11
+ "https://mirrors.sjtug.sjtu.edu.cn/npm-registry",
12
+ "https://npmreg.proxy.ustclug.org",
13
+ "https://repo.huaweicloud.com/repository/npm"
14
+ ];
15
+
16
+ export const CHINA_NPM_REGISTRY_HINTS = [
17
+ "npmmirror",
18
+ "sjtug",
19
+ "sjtu",
20
+ "ustc",
21
+ "ustclug",
22
+ "tencent",
23
+ "huaweicloud",
24
+ "huawei.com"
25
+ ];
26
+
27
+ export const DEFAULT_PLAYWRIGHT_HOST_CANDIDATES = [
28
+ "https://cdn.playwright.dev",
29
+ "https://playwright.azureedge.net",
30
+ "https://cdn.npmmirror.com/binaries/playwright",
31
+ "https://npmmirror.com/mirrors/playwright"
32
+ ];
33
+
34
+ export const CHINA_PLAYWRIGHT_HOST_PRIORITY = [
35
+ "https://cdn.npmmirror.com/binaries/playwright",
36
+ "https://npmmirror.com/mirrors/playwright"
37
+ ];
38
+
39
+ export const DEFAULT_GECKODRIVER_MIRROR_CANDIDATES = [
40
+ "https://cdn.npmmirror.com/binaries/geckodriver",
41
+ "https://npmmirror.com/mirrors/geckodriver",
42
+ "https://mirrors.huaweicloud.com/geckodriver"
43
+ ];
44
+
45
+ export function isChinaFriendlyNpmRegistry(registry) {
46
+ const r = registry.toLowerCase();
47
+ return CHINA_NPM_REGISTRY_HINTS.some((hint) => r.includes(hint));
48
+ }
package/package.json CHANGED
@@ -1,15 +1,12 @@
1
1
  {
2
2
  "name": "@ada-mcp/launcher",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "description": "Probe fastest npm registry then pnpm dlx @ada-mcp/mcp-server (zero deps)",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "ada-mcp": "./launcher.mjs"
9
9
  },
10
- "dependencies": {
11
- "@ada/download-probe": "0.1.0"
12
- },
13
10
  "files": [
14
11
  "launcher.mjs",
15
12
  "mirror-candidates.mjs",