@ada-mcp/mcp-server 0.1.14 → 0.1.16

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 +139 -139
  2. package/dist/cli.cjs +455 -105
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,139 +1,139 @@
1
- # @ada-mcp/mcp-server
2
-
3
- ADA MCP server package that supports:
4
-
5
- - Local stdio mode (default) for MCP hosts
6
- - Remote HTTP mode (`server`) with API key authentication
7
- - MCP **Streamable HTTP** on `POST|GET|DELETE /mcp` (same port as legacy REST), with optional SSE per MCP spec (`@modelcontextprotocol/sdk` transport)
8
-
9
- ## 标准安装(Cursor / MCP)
10
-
11
- 请使用 **`@ada-mcp/launcher@0.1.11`** 拉起本包(见 [launcher README](../ada-mcp-launcher/README.md)):
12
-
13
- ```json
14
- {
15
- "mcpServers": {
16
- "ada-mcp": {
17
- "command": "pnpm",
18
- "args": ["dlx", "@ada-mcp/launcher@0.1.11"]
19
- }
20
- }
21
- }
22
- ```
23
-
24
- 本包版本:**`@ada-mcp/mcp-server@0.1.14`**(由 launcher 默认拉取;依赖锁定 `playwright@1.59.1`)。
25
-
26
- 直接调试本包(无 launcher 拉包前测速):
27
-
28
- ```bash
29
- pnpm dlx @ada-mcp/mcp-server@0.1.14
30
- # npx 等价:
31
- npx -y @ada-mcp/mcp-server@0.1.14
32
- ```
33
-
34
- ## 启动时自动安装依赖(默认仅 Playwright)
35
-
36
- 进程启动前会按配置自动执行 `install-deps`(日志在 stderr):
37
-
38
- | 配置 | 含义 |
39
- |------|------|
40
- | (未配置) | 仅安装 **Playwright + 浏览器** |
41
- | `playwright` | 仅 Playwright(显式写法,与默认相同) |
42
- | `selenium` | **仅** Selenium 原生驱动(GeckoDriver/ChromeDriver) |
43
- | `appium` | **仅** Appium 包 + 移动端驱动 |
44
- | `playwright,selenium` | 组合(逗号连接多类) |
45
- | `all` | 上述全部 |
46
- | `none` / `skip` | 不自动安装 |
47
-
48
- **环境变量**
49
-
50
- - `ADA_MCP_INSTALL_DEPS`:范围,如 `playwright`、`playwright,selenium`、`all`、`none`
51
- - `ADA_MCP_SKIP_INSTALL_DEPS=1`:跳过自动安装
52
- - `ADA_MCP_INSTALL_DEPS_FORCE=1`:强制重装
53
- - `ADA_MCP_GECKODRIVER_VERSION` / `ADA_MCP_CHROMEDRIVER_VERSION`:Selenium 驱动版本
54
- - `ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS`:浏览器下载超时(默认 15 分钟)
55
- - `ADA_INSTALL_STRATEGY_TIMEOUT_MS`:npm 装包超时(默认 2 分钟)
56
-
57
- ## 代理与镜像(`0.1.10+` 推荐)
58
-
59
- | 阶段 | 自动探测最快镜像 |
60
- |------|------------------|
61
- | `pnpm dlx @ada-mcp/launcher` | **是** — 拉包前测速(推荐) |
62
- | `pnpm dlx @ada-mcp/mcp-server` | tarball 仍走本机源;**同次安装的依赖**由 `preinstall` 测速(仅写入官方 Playwright CDN,`0.1.9+`) |
63
- | 启动后 `install-deps` | 是 — 内置国内镜像测速(**无需配置**) |
64
-
65
- 默认 npm 探测候选(按优先级,延迟相同取靠前):阿里云 npmmirror → 腾讯云 → 华为云 → npm 官方。
66
-
67
- | 变量 | 说明 |
68
- |------|------|
69
- | `npm_config_registry` | 可选;仅加速 **dlx** 拉包(推荐 `https://registry.npmmirror.com`) |
70
- | `ADA_REGISTRY_CANDIDATES` | 可选;在默认五镜像**之外**追加候选 |
71
- | `ADA_NPM_PROXY_REGISTRY` / `ADA_PNPM_PROXY_REGISTRY` | 可选;覆盖探测主候选(默认 npmmirror) |
72
- | `PLAYWRIGHT_DOWNLOAD_HOST` | 可选;Playwright 浏览器 CDN |
73
-
74
- 详见 [ADA-MCP-接入手册 §3.9.2](../../docs/ADA-MCP-接入手册.md#392-代理与镜像配置)。
75
-
76
- **CLI 参数**(写在 MCP `args` 末尾)
77
-
78
- - `--install-deps=playwright,selenium`
79
- - `--skip-install-deps`
80
- - `--install-deps-force`
81
- - `--geckodriver-version=latest` `--chromedriver-version=match-chrome`
82
-
83
- 在标准 `args` 后追加,例如安装全部依赖:
84
-
85
- ```json
86
- "args": ["dlx", "@ada-mcp/launcher@0.1.11", "--install-deps=all"]
87
- ```
88
-
89
- ## Cursor MCP 配置
90
-
91
- **pnpm(推荐)**:`pnpm` + `dlx @ada-mcp/launcher@0.1.11`
92
-
93
- **npx 等价**(`launcher@0.1.7+`):`npx` + `-y @ada-mcp/launcher@0.1.11`(内层同样 `npx -y` mcp-server,测速逻辑与 pnpm 一致)
94
-
95
- ```json
96
- {
97
- "mcpServers": {
98
- "ada-mcp": {
99
- "command": "npx",
100
- "args": ["-y", "@ada-mcp/launcher@0.1.11"]
101
- }
102
- }
103
- }
104
- ```
105
-
106
- Windows 若找不到 `pnpm`,可将 `command` 改为 `pnpm.cmd` 绝对路径;无 pnpm 时只能直接 `npx -y @ada-mcp/mcp-server@0.1.14`(无 launcher 拉包测速)。
107
-
108
- ## Remote mode
109
-
110
- Set API key in environment variable first:
111
-
112
- ```bash
113
- export ADA_MCP_REMOTE_API_KEY=your_token
114
- ```
115
-
116
- Windows PowerShell:
117
-
118
- ```powershell
119
- $env:ADA_MCP_REMOTE_API_KEY="your_token"
120
- ```
121
-
122
- Then run:
123
-
124
- ```bash
125
- pnpm dlx @ada-mcp/mcp-server server --host=127.0.0.1 --port=8787 --allow-risky=true --risky-mode=whitelist --risky-commands=custom
126
- ```
127
-
128
- ### Streamable HTTP (`/mcp`)
129
-
130
- - Endpoint: `http://<host>:<port>/mcp` — same API key headers as below (`x-api-key` or `Authorization: Bearer`).
131
- - First request: `POST /mcp` with JSON-RPC `initialize` (no `Mcp-Session-Id`); server returns a session id in `Mcp-Session-Id` response header.
132
- - Follow-up: `POST /mcp` with body + `Mcp-Session-Id`; for server-initiated streaming, open `GET /mcp` with `Accept: text/event-stream` and the same session header.
133
- - Session teardown: `DELETE /mcp` with `Mcp-Session-Id`.
134
-
135
- When listening on all interfaces (e.g. `--host=0.0.0.0`), set allowed Host headers to satisfy DNS rebinding checks:
136
-
137
- ```bash
138
- pnpm dlx @ada-mcp/mcp-server server --host=0.0.0.0 --port=8787 --api-key=your_token --allowed-hosts=localhost,127.0.0.1
139
- ```
1
+ # @ada-mcp/mcp-server
2
+
3
+ ADA MCP server package that supports:
4
+
5
+ - Local stdio mode (default) for MCP hosts
6
+ - Remote HTTP mode (`server`) with API key authentication
7
+ - MCP **Streamable HTTP** on `POST|GET|DELETE /mcp` (same port as legacy REST), with optional SSE per MCP spec (`@modelcontextprotocol/sdk` transport)
8
+
9
+ ## 标准安装(Cursor / MCP)
10
+
11
+ 请使用 **`@ada-mcp/launcher@0.1.13`** 拉起本包(见 [launcher README](../ada-mcp-launcher/README.md)):
12
+
13
+ ```json
14
+ {
15
+ "mcpServers": {
16
+ "ada-mcp": {
17
+ "command": "pnpm",
18
+ "args": ["dlx", "@ada-mcp/launcher@0.1.13"]
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ 本包版本:**`@ada-mcp/mcp-server@0.1.16`**(由 launcher 默认拉取;依赖锁定 `playwright@1.59.1`)。
25
+
26
+ 直接调试本包(无 launcher 拉包前测速):
27
+
28
+ ```bash
29
+ pnpm dlx @ada-mcp/mcp-server@0.1.16
30
+ # npx 等价:
31
+ npx -y @ada-mcp/mcp-server@0.1.16
32
+ ```
33
+
34
+ ## 启动时自动安装依赖(默认仅 Playwright)
35
+
36
+ 进程启动前会按配置自动执行 `install-deps`(日志在 stderr):
37
+
38
+ | 配置 | 含义 |
39
+ |------|------|
40
+ | (未配置) | 仅安装 **Playwright + 浏览器** |
41
+ | `playwright` | 仅 Playwright(显式写法,与默认相同) |
42
+ | `selenium` | **仅** Selenium 原生驱动(GeckoDriver/ChromeDriver) |
43
+ | `appium` | **仅** Appium 包 + 移动端驱动 |
44
+ | `playwright,selenium` | 组合(逗号连接多类) |
45
+ | `all` | 上述全部 |
46
+ | `none` / `skip` | 不自动安装 |
47
+
48
+ **环境变量**
49
+
50
+ - `ADA_MCP_INSTALL_DEPS`:范围,如 `playwright`、`playwright,selenium`、`all`、`none`
51
+ - `ADA_MCP_SKIP_INSTALL_DEPS=1`:跳过自动安装
52
+ - `ADA_MCP_INSTALL_DEPS_FORCE=1`:强制重装
53
+ - `ADA_MCP_GECKODRIVER_VERSION` / `ADA_MCP_CHROMEDRIVER_VERSION`:Selenium 驱动版本
54
+ - `ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS`:浏览器下载超时(默认 15 分钟)
55
+ - `ADA_INSTALL_STRATEGY_TIMEOUT_MS`:npm 装包超时(默认 2 分钟)
56
+
57
+ ## 代理与镜像(`0.1.10+` 推荐)
58
+
59
+ | 阶段 | 自动探测最快镜像 |
60
+ |------|------------------|
61
+ | `pnpm dlx @ada-mcp/launcher` | **是** — 拉包前测速(推荐) |
62
+ | `pnpm dlx @ada-mcp/mcp-server` | tarball 仍走本机源;**同次安装的依赖**由 `preinstall` 测速(仅写入官方 Playwright CDN,`0.1.9+`) |
63
+ | 启动后 `install-deps` | 是 — 内置国内镜像测速(**无需配置**) |
64
+
65
+ 默认 npm 探测候选(按优先级,延迟相同取靠前):阿里云 npmmirror → 腾讯云 → 华为云 → npm 官方。
66
+
67
+ | 变量 | 说明 |
68
+ |------|------|
69
+ | `npm_config_registry` | 可选;仅加速 **dlx** 拉包(推荐 `https://registry.npmmirror.com`) |
70
+ | `ADA_REGISTRY_CANDIDATES` | 可选;在默认五镜像**之外**追加候选 |
71
+ | `ADA_NPM_PROXY_REGISTRY` / `ADA_PNPM_PROXY_REGISTRY` | 可选;覆盖探测主候选(默认 npmmirror) |
72
+ | `PLAYWRIGHT_DOWNLOAD_HOST` | 可选;Playwright 浏览器 CDN |
73
+
74
+ 详见 [ADA-MCP-接入手册 §3.9.2](../../docs/ADA-MCP-接入手册.md#392-代理与镜像配置)。
75
+
76
+ **CLI 参数**(写在 MCP `args` 末尾)
77
+
78
+ - `--install-deps=playwright,selenium`
79
+ - `--skip-install-deps`
80
+ - `--install-deps-force`
81
+ - `--geckodriver-version=latest` `--chromedriver-version=match-chrome`
82
+
83
+ 在标准 `args` 后追加,例如安装全部依赖:
84
+
85
+ ```json
86
+ "args": ["dlx", "@ada-mcp/launcher@0.1.13", "--install-deps=all"]
87
+ ```
88
+
89
+ ## Cursor MCP 配置
90
+
91
+ **pnpm(推荐)**:`pnpm` + `dlx @ada-mcp/launcher@0.1.13`
92
+
93
+ **npx 等价**(`launcher@0.1.7+`):`npx` + `-y @ada-mcp/launcher@0.1.13`(内层同样 `npx -y` mcp-server,测速逻辑与 pnpm 一致)
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "ada-mcp": {
99
+ "command": "npx",
100
+ "args": ["-y", "@ada-mcp/launcher@0.1.13"]
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ Windows 若找不到 `pnpm`,可将 `command` 改为 `pnpm.cmd` 绝对路径;无 pnpm 时只能直接 `npx -y @ada-mcp/mcp-server@0.1.16`(无 launcher 拉包测速)。
107
+
108
+ ## Remote mode
109
+
110
+ Set API key in environment variable first:
111
+
112
+ ```bash
113
+ export ADA_MCP_REMOTE_API_KEY=your_token
114
+ ```
115
+
116
+ Windows PowerShell:
117
+
118
+ ```powershell
119
+ $env:ADA_MCP_REMOTE_API_KEY="your_token"
120
+ ```
121
+
122
+ Then run:
123
+
124
+ ```bash
125
+ pnpm dlx @ada-mcp/mcp-server server --host=127.0.0.1 --port=8787 --allow-risky=true --risky-mode=whitelist --risky-commands=custom
126
+ ```
127
+
128
+ ### Streamable HTTP (`/mcp`)
129
+
130
+ - Endpoint: `http://<host>:<port>/mcp` — same API key headers as below (`x-api-key` or `Authorization: Bearer`).
131
+ - First request: `POST /mcp` with JSON-RPC `initialize` (no `Mcp-Session-Id`); server returns a session id in `Mcp-Session-Id` response header.
132
+ - Follow-up: `POST /mcp` with body + `Mcp-Session-Id`; for server-initiated streaming, open `GET /mcp` with `Accept: text/event-stream` and the same session header.
133
+ - Session teardown: `DELETE /mcp` with `Mcp-Session-Id`.
134
+
135
+ When listening on all interfaces (e.g. `--host=0.0.0.0`), set allowed Host headers to satisfy DNS rebinding checks:
136
+
137
+ ```bash
138
+ pnpm dlx @ada-mcp/mcp-server server --host=0.0.0.0 --port=8787 --api-key=your_token --allowed-hosts=localhost,127.0.0.1
139
+ ```
package/dist/cli.cjs CHANGED
@@ -2887,7 +2887,7 @@ var init_config = __esm({
2887
2887
  ],
2888
2888
  nativeDriversDir: "dirver",
2889
2889
  geckodriverVersion: "latest",
2890
- chromedriverVersion: "latest"
2890
+ chromedriverVersion: "match-chrome"
2891
2891
  },
2892
2892
  appium: {
2893
2893
  serverUrl: "http://127.0.0.1:4723",
@@ -3277,30 +3277,164 @@ async function resolveChromedriverCfTVersion(requested) {
3277
3277
  }
3278
3278
  return hit.version;
3279
3279
  }
3280
- async function detectInstalledChromeMajorVersion() {
3281
- if (process.platform !== "win32") {
3280
+ function parseBrowserVersionString(raw) {
3281
+ const match = raw.match(/(\d+\.\d+(?:\.\d+)*(?:\.\d+)?)/);
3282
+ if (!match) {
3282
3283
  return void 0;
3283
3284
  }
3284
- const candidates = [
3285
- import_node_path4.default.join(process.env["ProgramFiles"] ?? "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
3286
- import_node_path4.default.join(
3287
- process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)",
3288
- "Google",
3289
- "Chrome",
3290
- "Application",
3291
- "chrome.exe"
3292
- )
3293
- ];
3294
- for (const chromePath of candidates) {
3295
- if (!await fileExists(chromePath)) {
3285
+ const version = match[1];
3286
+ return { version, major: version.split(".")[0] };
3287
+ }
3288
+ async function runCommandCapture(command, args) {
3289
+ return new Promise((resolve) => {
3290
+ const child = (0, import_node_child_process.spawn)(command, args, {
3291
+ stdio: ["ignore", "pipe", "ignore"],
3292
+ shell: process.platform === "win32"
3293
+ });
3294
+ let out = "";
3295
+ child.stdout?.on("data", (chunk) => {
3296
+ out += chunk.toString("utf8");
3297
+ });
3298
+ child.on("exit", (code) => resolve(code === 0 ? out.trim() : void 0));
3299
+ child.on("error", () => resolve(void 0));
3300
+ });
3301
+ }
3302
+ async function detectChromeFromExecutable(exePath) {
3303
+ if (!await fileExists(exePath)) {
3304
+ return void 0;
3305
+ }
3306
+ if (process.platform === "win32") {
3307
+ const version = await readWindowsFileVersion(exePath);
3308
+ if (!version) {
3309
+ return void 0;
3310
+ }
3311
+ const parsed2 = parseBrowserVersionString(version);
3312
+ if (!parsed2) {
3313
+ return void 0;
3314
+ }
3315
+ return { path: exePath, version: parsed2.version, major: parsed2.major };
3316
+ }
3317
+ const out = await runCommandCapture(exePath, ["--version"]);
3318
+ if (!out) {
3319
+ return void 0;
3320
+ }
3321
+ const parsed = parseBrowserVersionString(out);
3322
+ if (!parsed) {
3323
+ return void 0;
3324
+ }
3325
+ return { path: exePath, version: parsed.version, major: parsed.major };
3326
+ }
3327
+ async function detectFirefoxFromExecutable(exePath) {
3328
+ if (!await fileExists(exePath)) {
3329
+ return void 0;
3330
+ }
3331
+ if (process.platform === "win32") {
3332
+ const version = await readWindowsFileVersion(exePath);
3333
+ if (!version) {
3334
+ return void 0;
3335
+ }
3336
+ const parsed2 = parseBrowserVersionString(version);
3337
+ if (!parsed2) {
3338
+ return void 0;
3339
+ }
3340
+ return { path: exePath, version: parsed2.version, major: parsed2.major };
3341
+ }
3342
+ const out = await runCommandCapture(exePath, ["--version"]);
3343
+ if (!out) {
3344
+ return void 0;
3345
+ }
3346
+ const parsed = parseBrowserVersionString(out);
3347
+ if (!parsed) {
3348
+ return void 0;
3349
+ }
3350
+ return { path: exePath, version: parsed.version, major: parsed.major };
3351
+ }
3352
+ async function detectLocalBrowsers() {
3353
+ const result = {};
3354
+ if (process.platform === "win32") {
3355
+ const chromeCandidates = [
3356
+ import_node_path4.default.join(process.env["ProgramFiles"] ?? "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"),
3357
+ import_node_path4.default.join(
3358
+ process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)",
3359
+ "Google",
3360
+ "Chrome",
3361
+ "Application",
3362
+ "chrome.exe"
3363
+ ),
3364
+ import_node_path4.default.join(process.env["ProgramFiles"] ?? "C:\\Program Files", "Chromium", "Application", "chrome.exe")
3365
+ ];
3366
+ for (const p of chromeCandidates) {
3367
+ const hit = await detectChromeFromExecutable(p);
3368
+ if (hit) {
3369
+ result.chrome = hit;
3370
+ break;
3371
+ }
3372
+ }
3373
+ const firefoxCandidates = [
3374
+ import_node_path4.default.join(process.env["ProgramFiles"] ?? "C:\\Program Files", "Mozilla Firefox", "firefox.exe"),
3375
+ import_node_path4.default.join(
3376
+ process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)",
3377
+ "Mozilla Firefox",
3378
+ "firefox.exe"
3379
+ )
3380
+ ];
3381
+ for (const p of firefoxCandidates) {
3382
+ const hit = await detectFirefoxFromExecutable(p);
3383
+ if (hit) {
3384
+ result.firefox = hit;
3385
+ break;
3386
+ }
3387
+ }
3388
+ return result;
3389
+ }
3390
+ if (process.platform === "darwin") {
3391
+ const chromePaths = [
3392
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
3393
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
3394
+ ];
3395
+ for (const p of chromePaths) {
3396
+ const hit = await detectChromeFromExecutable(p);
3397
+ if (hit) {
3398
+ result.chrome = hit;
3399
+ break;
3400
+ }
3401
+ }
3402
+ const firefoxPath = "/Applications/Firefox.app/Contents/MacOS/firefox";
3403
+ const ff = await detectFirefoxFromExecutable(firefoxPath);
3404
+ if (ff) {
3405
+ result.firefox = ff;
3406
+ }
3407
+ return result;
3408
+ }
3409
+ const chromeCommands = ["google-chrome-stable", "google-chrome", "chromium-browser", "chromium"];
3410
+ for (const cmd of chromeCommands) {
3411
+ if (!await commandOnPath(cmd)) {
3296
3412
  continue;
3297
3413
  }
3298
- const version = await readWindowsFileVersion(chromePath);
3299
- if (version) {
3300
- return version.split(".")[0];
3414
+ const out = await runCommandCapture(cmd, ["--version"]);
3415
+ const parsed = out ? parseBrowserVersionString(out) : void 0;
3416
+ if (parsed) {
3417
+ result.chrome = { path: cmd, version: parsed.version, major: parsed.major };
3418
+ break;
3301
3419
  }
3302
3420
  }
3303
- return void 0;
3421
+ if (await commandOnPath("firefox")) {
3422
+ const out = await runCommandCapture("firefox", ["--version"]);
3423
+ const parsed = out ? parseBrowserVersionString(out) : void 0;
3424
+ if (parsed) {
3425
+ result.firefox = { path: "firefox", version: parsed.version, major: parsed.major };
3426
+ }
3427
+ }
3428
+ return result;
3429
+ }
3430
+ function logSeleniumDriverGuidance(onLogLine) {
3431
+ onLogLine?.(
3432
+ "[selenium] \u5C06\u68C0\u6D4B\u672C\u673A Chrome/Firefox \u5E76\u5C1D\u8BD5\u4E0B\u8F7D\u9A71\u52A8\u81F3\u76EE\u5F55\uFF1B\u5931\u8D25\u53EF\u624B\u52A8\u653E\u5165 geckodriver/chromedriver\uFF08\u89C1\u63A5\u5165\u624B\u518C Selenium \u8282\uFF09"
3433
+ );
3434
+ }
3435
+ async function detectInstalledChromeMajorVersion() {
3436
+ const browsers = await detectLocalBrowsers();
3437
+ return browsers.chrome?.major;
3304
3438
  }
3305
3439
  async function readWindowsFileVersion(exePath) {
3306
3440
  return new Promise((resolve) => {
@@ -3343,7 +3477,7 @@ async function downloadGeckodriver(driversDir, versionInput, onLogLine) {
3343
3477
  await import_promises4.default.mkdir(driversDir, { recursive: true });
3344
3478
  const zipPath = import_node_path4.default.join(driversDir, `_download_geckodriver_${version}.zip`);
3345
3479
  const extractDir = import_node_path4.default.join(driversDir, `_extract_geckodriver_${version}`);
3346
- onLogLine?.(`[selenium] \u4E0B\u8F7D geckodriver ${tag} \uFFFD\uFFFD?${driversDir}`);
3480
+ onLogLine?.(`[selenium] \u4E0B\u8F7D geckodriver ${tag} \u2192 ${driversDir}`);
3347
3481
  onLogLine?.(`[selenium] URL: ${url}`);
3348
3482
  await downloadToFile(url, zipPath);
3349
3483
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
@@ -3356,7 +3490,7 @@ async function downloadGeckodriver(driversDir, versionInput, onLogLine) {
3356
3490
  await copyExecutable(found, dest);
3357
3491
  await import_promises4.default.rm(zipPath, { force: true });
3358
3492
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
3359
- onLogLine?.(`[selenium] geckodriver \u5DF2\u5199\uFFFD\uFFFD? ${dest}`);
3493
+ onLogLine?.(`[selenium] geckodriver \u5DF2\u5199\u5165: ${dest}`);
3360
3494
  return { path: dest, version: tag };
3361
3495
  }
3362
3496
  async function downloadChromedriver(driversDir, versionInput, onLogLine) {
@@ -3376,7 +3510,7 @@ async function downloadChromedriver(driversDir, versionInput, onLogLine) {
3376
3510
  await import_promises4.default.mkdir(driversDir, { recursive: true });
3377
3511
  const zipPath = import_node_path4.default.join(driversDir, `_download_chromedriver_${major}.zip`);
3378
3512
  const extractDir = import_node_path4.default.join(driversDir, `_extract_chromedriver_${major}`);
3379
- onLogLine?.(`[selenium] \u4E0B\u8F7D chromedriver ${fullVersion} (\u4E3B\u7248\uFFFD\uFFFD?${major}) \uFFFD\uFFFD?${driversDir}`);
3513
+ onLogLine?.(`[selenium] \u4E0B\u8F7D chromedriver ${fullVersion} (\u4E3B\u7248\u672C ${major}) \u2192 ${driversDir}`);
3380
3514
  onLogLine?.(`[selenium] URL: ${url}`);
3381
3515
  await downloadToFile(url, zipPath);
3382
3516
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
@@ -3393,7 +3527,7 @@ async function downloadChromedriver(driversDir, versionInput, onLogLine) {
3393
3527
  }
3394
3528
  await import_promises4.default.rm(zipPath, { force: true });
3395
3529
  await import_promises4.default.rm(extractDir, { recursive: true, force: true });
3396
- onLogLine?.(`[selenium] chromedriver \u5DF2\u5199\uFFFD\uFFFD? ${destVersioned}`);
3530
+ onLogLine?.(`[selenium] chromedriver \u5DF2\u5199\u5165: ${destVersioned}`);
3397
3531
  return { path: destVersioned, version: fullVersion, major };
3398
3532
  }
3399
3533
  async function saveNativeDriverManifest(manifest, workspaceRoot) {
@@ -3403,38 +3537,91 @@ async function saveNativeDriverManifest(manifest, workspaceRoot) {
3403
3537
  const file = import_node_path4.default.join(dir, "native-drivers.json");
3404
3538
  await import_promises4.default.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
3405
3539
  }
3540
+ function resolveDefaultChromedriverVersion(requested, browsers) {
3541
+ if (requested && requested !== "latest") {
3542
+ return requested;
3543
+ }
3544
+ if (browsers.chrome?.major) {
3545
+ return "match-chrome";
3546
+ }
3547
+ return requested ?? "latest";
3548
+ }
3549
+ function formatDownloadError(error) {
3550
+ return error instanceof Error ? error.message : String(error);
3551
+ }
3406
3552
  async function ensureNativeWebDrivers(options = {}) {
3407
3553
  const root = options.workspaceRoot ?? await resolveWorkspaceRoot3();
3408
3554
  const driversDir = options.driversDir ?? await resolveNativeDriversDir(root);
3409
3555
  await import_promises4.default.mkdir(driversDir, { recursive: true });
3410
3556
  const log2 = options.onLogLine;
3557
+ logSeleniumDriverGuidance(log2);
3411
3558
  log2?.(`[selenium] \u539F\u751F\u9A71\u52A8\u76EE\u5F55: ${driversDir}`);
3559
+ const browsers = await detectLocalBrowsers();
3560
+ if (browsers.chrome) {
3561
+ log2?.(
3562
+ `[selenium] \u68C0\u6D4B\u5230\u672C\u673A Chrome/Chromium: ${browsers.chrome.version} (\u4E3B\u7248\u672C ${browsers.chrome.major}) \u2014 ${browsers.chrome.path}`
3563
+ );
3564
+ } else {
3565
+ log2?.("[selenium] \u672A\u68C0\u6D4B\u5230\u672C\u673A Chrome/Chromium\uFF08chromedriver \u5C06\u4F7F\u7528 latest \u6216\u663E\u5F0F\u6307\u5B9A\u7248\u672C\uFF09");
3566
+ }
3567
+ if (browsers.firefox) {
3568
+ log2?.(
3569
+ `[selenium] \u68C0\u6D4B\u5230\u672C\u673A Firefox: ${browsers.firefox.version} (\u4E3B\u7248\u672C ${browsers.firefox.major}) \u2014 ${browsers.firefox.path}`
3570
+ );
3571
+ } else {
3572
+ log2?.("[selenium] \u672A\u68C0\u6D4B\u5230\u672C\u673A Firefox\uFF08geckodriver \u5C06\u4F7F\u7528 latest \u6216\u663E\u5F0F\u6307\u5B9A\u7248\u672C\uFF09");
3573
+ }
3412
3574
  const localChromeVersions = await listLocalChromedriverVersions(driversDir);
3413
3575
  if (localChromeVersions.length > 0) {
3414
- log2?.(`[selenium] \u76EE\u5F55\u5185\u5DF2\uFFFD\uFFFD?chromedriver \u7248\u672C: ${localChromeVersions.join(", ")}`);
3576
+ log2?.(`[selenium] \u76EE\u5F55\u5185\u5DF2\u6709 chromedriver \u7248\u672C: ${localChromeVersions.join(", ")}`);
3577
+ }
3578
+ const chromedriverRequest = resolveDefaultChromedriverVersion(options.chromedriverVersion, browsers);
3579
+ if (chromedriverRequest === "match-chrome" && browsers.chrome) {
3580
+ log2?.(
3581
+ `[selenium] chromedriver \u5C06\u5C1D\u8BD5\u5339\u914D\u672C\u673A Chrome \u4E3B\u7248\u672C ${browsers.chrome.major}\uFF08chrome-for-testing \u76EE\u5F55\uFF09`
3582
+ );
3415
3583
  }
3416
3584
  const localGecko = await findGeckodriverInDir(driversDir, options.geckodriverVersion);
3417
3585
  const hasLocalGecko = Boolean(localGecko && await fileExists(localGecko));
3418
3586
  let geckoVersion = options.geckodriverVersion;
3419
3587
  if (options.geckodriverVersion !== "skip" && (options.force || !hasLocalGecko)) {
3420
- const downloaded = await downloadGeckodriver(driversDir, options.geckodriverVersion ?? "latest", log2);
3421
- geckoVersion = downloaded.version;
3588
+ try {
3589
+ const downloaded = await downloadGeckodriver(driversDir, options.geckodriverVersion ?? "latest", log2);
3590
+ geckoVersion = downloaded.version;
3591
+ } catch (error) {
3592
+ log2?.(`[selenium][warn] geckodriver \u81EA\u52A8\u4E0B\u8F7D\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7: ${formatDownloadError(error)}`);
3593
+ log2?.(
3594
+ "[selenium][warn] \u8BF7\u5C06 geckodriver \u653E\u5165\u4E0A\u8FF0\u76EE\u5F55\u6216\u914D\u7F6E PATH / ADA_GECKODRIVER_PATH\uFF1B\u53EF\u53C2\u8003 Mozilla \u53D1\u5E03\u9875\u624B\u52A8\u4E0B\u8F7D\u3002"
3595
+ );
3596
+ }
3422
3597
  } else if (hasLocalGecko) {
3423
3598
  log2?.(`[selenium] \u590D\u7528\u5DF2\u6709 geckodriver: ${localGecko}`);
3424
3599
  }
3425
- const localChrome = await findChromedriverInDir(driversDir, options.chromedriverVersion);
3600
+ const localChrome = await findChromedriverInDir(driversDir, chromedriverRequest);
3426
3601
  const hasLocalChrome = Boolean(localChrome && await fileExists(localChrome.path));
3427
- if (options.chromedriverVersion !== "skip" && (options.force || !hasLocalChrome)) {
3428
- await downloadChromedriver(driversDir, options.chromedriverVersion ?? "latest", log2);
3602
+ if (chromedriverRequest !== "skip" && (options.force || !hasLocalChrome)) {
3603
+ try {
3604
+ await downloadChromedriver(driversDir, chromedriverRequest ?? "latest", log2);
3605
+ } catch (error) {
3606
+ log2?.(`[selenium][warn] chromedriver \u81EA\u52A8\u4E0B\u8F7D\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7: ${formatDownloadError(error)}`);
3607
+ if (browsers.chrome) {
3608
+ log2?.(
3609
+ `[selenium][warn] \u672C\u673A Chrome \u4E3B\u7248\u672C\u4E3A ${browsers.chrome.major}\uFF0C\u8BF7\u4E0B\u8F7D\u5339\u914D\u7248\u672C\u7684 chromedriver \u653E\u5165\u76EE\u5F55\u6216\u914D\u7F6E ADA_CHROMEDRIVER_PATH\u3002`
3610
+ );
3611
+ }
3612
+ log2?.(
3613
+ "[selenium][warn] \u53EF\u53C2\u8003 chrome-for-testing / chromedriver.storage \u624B\u52A8\u4E0B\u8F7D\uFF1B\u5DF2\u653E\u5165 dirver/ \u7684\u9A71\u52A8\u4F1A\u5728\u4E0B\u6B21\u542F\u52A8\u65F6\u81EA\u52A8\u590D\u7528\u3002"
3614
+ );
3615
+ }
3429
3616
  } else if (hasLocalChrome) {
3430
- log2?.(`[selenium] \u590D\u7528\u5DF2\u6709 chromedriver: ${localChrome.path} (\u4E3B\u7248\uFFFD\uFFFD?${localChrome.version})`);
3617
+ log2?.(`[selenium] \u590D\u7528\u5DF2\u6709 chromedriver: ${localChrome.path} (\u4E3B\u7248\u672C ${localChrome.version})`);
3431
3618
  }
3432
3619
  const resolved = await resolveNativeDrivers({
3433
3620
  driversDir,
3434
3621
  workspaceRoot: root,
3435
3622
  selection: {
3436
3623
  geckodriverVersion: geckoVersion,
3437
- chromedriverVersion: options.chromedriverVersion ?? localChrome?.version
3624
+ chromedriverVersion: chromedriverRequest ?? localChrome?.version
3438
3625
  }
3439
3626
  });
3440
3627
  await saveNativeDriverManifest(
@@ -3449,10 +3636,17 @@ async function ensureNativeWebDrivers(options = {}) {
3449
3636
  if (resolved.geckodriverPath) {
3450
3637
  process.env.ADA_GECKODRIVER_PATH = resolved.geckodriverPath;
3451
3638
  log2?.(`[selenium] \u4F7F\u7528 geckodriver: ${resolved.geckodriverPath}`);
3639
+ } else if (options.geckodriverVersion !== "skip") {
3640
+ log2?.("[selenium][warn] \u672A\u627E\u5230\u53EF\u7528 geckodriver\uFF08\u81EA\u52A8\u4E0B\u8F7D\u5DF2\u8DF3\u8FC7\u6216\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5\uFF09");
3452
3641
  }
3453
3642
  if (resolved.chromedriverPath) {
3454
3643
  process.env.ADA_CHROMEDRIVER_PATH = resolved.chromedriverPath;
3455
3644
  log2?.(`[selenium] \u4F7F\u7528 chromedriver: ${resolved.chromedriverPath}`);
3645
+ } else if (chromedriverRequest !== "skip") {
3646
+ log2?.("[selenium][warn] \u672A\u627E\u5230\u53EF\u7528 chromedriver\uFF08\u81EA\u52A8\u4E0B\u8F7D\u5DF2\u8DF3\u8FC7\u6216\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5\uFF09");
3647
+ }
3648
+ if (!resolved.geckodriverOk && !resolved.chromedriverOk) {
3649
+ log2?.("[selenium][warn] \u5F53\u524D\u65E0\u53EF\u7528\u539F\u751F WebDriver\uFF1BSelenium \u4EFB\u52A1\u53EF\u80FD\u5931\u8D25\uFF0C\u8BF7\u6309\u4E0A\u6587\u53C2\u8003\u5730\u5740\u81EA\u884C\u4E0B\u8F7D\u3002");
3456
3650
  }
3457
3651
  return resolved;
3458
3652
  }
@@ -3469,6 +3663,13 @@ var init_src2 = __esm({
3469
3663
  });
3470
3664
 
3471
3665
  // ../ada-agent/src/dependency-installer.ts
3666
+ function playwrightInstallPackageSpec() {
3667
+ const fromEnv = process.env.ADA_PLAYWRIGHT_VERSION?.trim();
3668
+ return `playwright@${fromEnv || PINNED_PLAYWRIGHT_VERSION}`;
3669
+ }
3670
+ function resolveInstallPackageSpecs(packages) {
3671
+ return packages.map((pkg) => pkg === "playwright" ? playwrightInstallPackageSpec() : pkg);
3672
+ }
3472
3673
  function shouldUseShell(command) {
3473
3674
  if (process.platform !== "win32") {
3474
3675
  return false;
@@ -3483,6 +3684,69 @@ function hasPackage(packageName) {
3483
3684
  return false;
3484
3685
  }
3485
3686
  }
3687
+ function briefErrorMessage(error) {
3688
+ if (error instanceof Error) {
3689
+ return error.message.split(/\r?\n/)[0]?.trim() || error.message;
3690
+ }
3691
+ return String(error).split(/\r?\n/)[0]?.trim() || String(error);
3692
+ }
3693
+ function shouldEmitPlaywrightCliLine(line) {
3694
+ const t = line.trim();
3695
+ if (!t) {
3696
+ return false;
3697
+ }
3698
+ if (/^\s*at\s/.test(line) || t.includes("coreBundle.js") || t.includes("processTicksAndRejections")) {
3699
+ return false;
3700
+ }
3701
+ if (t.includes("<Error>") || t.includes("NoSuchKey") || t.startsWith("<?xml")) {
3702
+ return false;
3703
+ }
3704
+ if (t.includes("Download failure, code=") && !t.startsWith("Error:")) {
3705
+ return false;
3706
+ }
3707
+ return true;
3708
+ }
3709
+ function summarizePlaywrightCliLine(line) {
3710
+ const t = line.trim();
3711
+ if (/^Downloading Chrome for Testing/i.test(t) || /^Downloading chromium/i.test(t)) {
3712
+ const from = t.match(/\bfrom\s+(https?:\/\/\S+)/i)?.[1];
3713
+ return from ? `[playwright] \u6B63\u5728\u4E0B\u8F7D Chromium\uFF08${from}\uFF09` : "[playwright] \u6B63\u5728\u4E0B\u8F7D Chromium\u2026";
3714
+ }
3715
+ if (/^Error:\s*Download failed:/i.test(t)) {
3716
+ const code = t.match(/\bcode[=\s]+(\d{3})\b/i)?.[1] ?? t.match(/returned code (\d{3})/i)?.[1];
3717
+ return code ? `[playwright][warn] \u955C\u50CF\u8FD4\u56DE HTTP ${code}\uFF0C\u5C06\u5C1D\u8BD5\u4E0B\u4E00\u4E2A CDN` : "[playwright][warn] \u955C\u50CF\u4E0B\u8F7D\u5931\u8D25\uFF0C\u5C06\u5C1D\u8BD5\u4E0B\u4E00\u4E2A CDN";
3718
+ }
3719
+ if (/^Failed to install browsers/i.test(t) || /^Failed to download Chrome/i.test(t)) {
3720
+ return "[playwright][warn] \u5F53\u524D\u955C\u50CF\u672A\u5B89\u88C5\u6210\u529F";
3721
+ }
3722
+ if (/^Progress:/i.test(t)) {
3723
+ return null;
3724
+ }
3725
+ if (t.length > 200) {
3726
+ return `[playwright] ${t.slice(0, 120)}\u2026`;
3727
+ }
3728
+ return t.startsWith("[playwright]") ? t : `[playwright] ${t}`;
3729
+ }
3730
+ function createPlaywrightInstallLogSink(onLogLine) {
3731
+ if (!onLogLine) {
3732
+ return void 0;
3733
+ }
3734
+ const seen = /* @__PURE__ */ new Set();
3735
+ return (line) => {
3736
+ if (!shouldEmitPlaywrightCliLine(line)) {
3737
+ return;
3738
+ }
3739
+ const summary = summarizePlaywrightCliLine(line);
3740
+ if (!summary) {
3741
+ return;
3742
+ }
3743
+ if (seen.has(summary)) {
3744
+ return;
3745
+ }
3746
+ seen.add(summary);
3747
+ onLogLine(summary);
3748
+ };
3749
+ }
3486
3750
  function runCommand2(command, args, options) {
3487
3751
  return new Promise((resolve, reject) => {
3488
3752
  const onLogLine = options?.onLogLine;
@@ -3507,7 +3771,7 @@ function runCommand2(command, args, options) {
3507
3771
  buf = parts.pop() ?? "";
3508
3772
  for (const line of parts) {
3509
3773
  const t = line.trimEnd();
3510
- if (t.length > 0) {
3774
+ if (t.length > 0 && (options?.logFilter?.(t) ?? true)) {
3511
3775
  onLogLine(t);
3512
3776
  }
3513
3777
  }
@@ -3543,7 +3807,7 @@ function runCommand2(command, args, options) {
3543
3807
  });
3544
3808
  });
3545
3809
  }
3546
- function runCommandCapture(command, args) {
3810
+ function runCommandCapture2(command, args) {
3547
3811
  return new Promise((resolve) => {
3548
3812
  const child = (0, import_node_child_process2.spawn)(command, args, {
3549
3813
  stdio: ["ignore", "pipe", "pipe"],
@@ -3665,7 +3929,7 @@ async function ensureNodeEnvironmentForInstall(onLogLine) {
3665
3929
  const requiredNpmMajor = 10;
3666
3930
  const runtimeNode = process.versions.node;
3667
3931
  const runtimeMajor = majorOf(runtimeNode);
3668
- const nodeVersion = await runCommandCapture("node", ["-v"]);
3932
+ const nodeVersion = await runCommandCapture2("node", ["-v"]);
3669
3933
  if (nodeVersion.code === 0) {
3670
3934
  const nodeMajor = majorOf(nodeVersion.stdout);
3671
3935
  onLogLine?.(`[deps] Node \u7248\u672C\u68C0\u6D4B\uFF1A\u7CFB\u7EDF=${nodeVersion.stdout}\uFF0C\u5185\u7F6E=${runtimeNode}`);
@@ -3684,7 +3948,7 @@ async function ensureNodeEnvironmentForInstall(onLogLine) {
3684
3948
  `[deps][warn] \u672A\u4ECE PATH \u68C0\u6D4B\u5230 node\uFF0C\u5F53\u524D\u8FD0\u884C\u65F6 Node.js=${runtimeNode}\uFF08\u53EF\u6267\u884C\u7A0B\u5E8F\u5185\u7F6E\uFF09\uFF0C\u7EE7\u7EED\u5C1D\u8BD5\u5B89\u88C5\u3002`
3685
3949
  );
3686
3950
  }
3687
- const npmVersion = await runCommandCapture("npm", ["-v"]);
3951
+ const npmVersion = await runCommandCapture2("npm", ["-v"]);
3688
3952
  if (npmVersion.code !== 0) {
3689
3953
  const message = "\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u7684 npm\uFF08PATH \u4E2D\u4E0D\u53EF\u7528\uFF09\u3002\u8BF7\u5B89\u88C5 Node.js 22+\uFF08\u542B npm\uFF09\u5E76\u91CD\u542F\u7EC8\u7AEF\u540E\u91CD\u8BD5\u3002";
3690
3954
  onLogLine?.(`[deps] ${message}`);
@@ -3737,7 +4001,7 @@ async function resolveCompatibleDriverSpecs(driver) {
3737
4001
  const preferred = driver === "uiautomator2" ? process.env.ADA_APPIUM_DRIVER_SPEC_UIAUTOMATOR2 ?? `${pkg}@2` : process.env.ADA_APPIUM_DRIVER_SPEC_XCUITEST ?? `${pkg}@7`;
3738
4002
  const fallbackRange = driver === "uiautomator2" ? process.env.ADA_APPIUM_DRIVER_RANGE_UIAUTOMATOR2 ?? "<3" : process.env.ADA_APPIUM_DRIVER_RANGE_XCUITEST ?? "<8";
3739
4003
  const specs = [preferred];
3740
- const view = await runCommandCapture("npm", ["view", `${pkg}@${fallbackRange}`, "version"]);
4004
+ const view = await runCommandCapture2("npm", ["view", `${pkg}@${fallbackRange}`, "version"]);
3741
4005
  if (view.code === 0 && view.stdout) {
3742
4006
  const version = view.stdout.trim().split(/\r?\n/).pop()?.trim();
3743
4007
  if (version) {
@@ -3758,8 +4022,15 @@ function stepMeta(stage) {
3758
4022
  };
3759
4023
  }
3760
4024
  function progress(stage, details) {
4025
+ if (depsHumanLog) {
4026
+ const label = PROGRESS_HUMAN_LABELS[stage];
4027
+ if (label) {
4028
+ depsHumanLog(label);
4029
+ }
4030
+ return;
4031
+ }
3761
4032
  const meta = stepMeta(stage);
3762
- log("info", {
4033
+ depsStructuredLog("info", {
3763
4034
  event: "deps.progress",
3764
4035
  details: {
3765
4036
  stage,
@@ -3768,6 +4039,12 @@ function progress(stage, details) {
3768
4039
  }
3769
4040
  });
3770
4041
  }
4042
+ function depsStructuredLog(level, payload) {
4043
+ if (depsHumanLog) {
4044
+ return;
4045
+ }
4046
+ log(level, payload);
4047
+ }
3771
4048
  function normalizeRegistryUrl(url) {
3772
4049
  return url.replace(/\/$/, "");
3773
4050
  }
@@ -3840,7 +4117,7 @@ async function detectBestRegistry(config, baseProxy) {
3840
4117
  }
3841
4118
  }
3842
4119
  detectedBestRegistryByKey.set(cacheKey, best);
3843
- log("info", {
4120
+ depsStructuredLog("info", {
3844
4121
  event: "deps.registry.auto-selected",
3845
4122
  details: {
3846
4123
  selected: best,
@@ -3936,11 +4213,14 @@ async function rankPlaywrightHosts(config) {
3936
4213
  const withArtifact = reachable.filter((x) => x.artifactOk);
3937
4214
  const pool = withArtifact.length > 0 ? withArtifact : reachable;
3938
4215
  pool.sort((a, b) => {
3939
- if (a.artifactOk !== b.artifactOk) {
3940
- return a.artifactOk ? -1 : 1;
3941
- }
3942
- if (a.latency !== b.latency) {
3943
- return (a.latency ?? 0) - (b.latency ?? 0);
4216
+ if (withArtifact.length > 0) {
4217
+ if (a.artifactOk !== b.artifactOk) {
4218
+ return a.artifactOk ? -1 : 1;
4219
+ }
4220
+ if (a.latency !== b.latency) {
4221
+ return (a.latency ?? 0) - (b.latency ?? 0);
4222
+ }
4223
+ return a.priority - b.priority;
3944
4224
  }
3945
4225
  return a.priority - b.priority;
3946
4226
  });
@@ -3953,37 +4233,43 @@ async function rankPlaywrightHosts(config) {
3953
4233
  return ranked.length > 0 ? ranked : [...candidates];
3954
4234
  }
3955
4235
  async function runInstallWithPriority(config, packages, onLogLine) {
4236
+ const specs = resolveInstallPackageSpecs(packages);
3956
4237
  const npmProxy = await detectBestRegistry(config, npmProxyRegistry());
3957
4238
  const pnpmProxy = await detectBestRegistry(config, pnpmProxyRegistry());
3958
- progress("packages.install.start", { packages, npmProxy, pnpmProxy });
4239
+ progress("packages.install.start", { packages: specs, npmProxy, pnpmProxy });
4240
+ if (packages.includes("playwright")) {
4241
+ onLogLine?.(
4242
+ `[deps] playwright \u5C06\u5B89\u88C5\u9501\u5B9A\u7248\u672C ${specs.find((s) => s.startsWith("playwright@")) ?? playwrightInstallPackageSpec()}\uFF08\u907F\u514D\u955C\u50CF latest \u4E0E\u6D4F\u89C8\u5668\u5305\u4E0D\u540C\u6B65\uFF09`
4243
+ );
4244
+ }
3959
4245
  onLogLine?.(
3960
- `[deps] \u5728\u7EBF\u5B89\u88C5\u5305: ${packages.join(" ")} (registry \u63A2\u6D4B: npm=${npmProxy}, pnpm=${pnpmProxy}\uFF1B\u987A\u5E8F: pnpm -> pnpm-proxy -> npm -> npm-proxy)`
4246
+ `[deps] \u5728\u7EBF\u5B89\u88C5\u5305: ${specs.join(" ")} (registry \u63A2\u6D4B: npm=${npmProxy}, pnpm=${pnpmProxy}\uFF1B\u987A\u5E8F: pnpm -> pnpm-proxy -> npm -> npm-proxy)`
3961
4247
  );
3962
4248
  const strategies = [
3963
4249
  {
3964
4250
  name: "pnpm",
3965
- run: () => runCommand2("pnpm", ["add", ...packages], {
4251
+ run: () => runCommand2("pnpm", ["add", ...specs], {
3966
4252
  timeoutMs: installStrategyTimeoutMs(),
3967
4253
  onLogLine
3968
4254
  })
3969
4255
  },
3970
4256
  {
3971
4257
  name: "pnpm-proxy",
3972
- run: () => runCommand2("pnpm", ["add", ...packages, "--registry", pnpmProxy], {
4258
+ run: () => runCommand2("pnpm", ["add", ...specs, "--registry", pnpmProxy], {
3973
4259
  timeoutMs: installStrategyTimeoutMs(),
3974
4260
  onLogLine
3975
4261
  })
3976
4262
  },
3977
4263
  {
3978
4264
  name: "npm",
3979
- run: () => runCommand2("npm", ["install", ...packages], {
4265
+ run: () => runCommand2("npm", ["install", ...specs], {
3980
4266
  timeoutMs: installStrategyTimeoutMs(),
3981
4267
  onLogLine
3982
4268
  })
3983
4269
  },
3984
4270
  {
3985
4271
  name: "npm-proxy",
3986
- run: () => runCommand2("npm", ["install", ...packages, "--registry", npmProxy], {
4272
+ run: () => runCommand2("npm", ["install", ...specs, "--registry", npmProxy], {
3987
4273
  timeoutMs: installStrategyTimeoutMs(),
3988
4274
  onLogLine
3989
4275
  })
@@ -3992,21 +4278,21 @@ async function runInstallWithPriority(config, packages, onLogLine) {
3992
4278
  let lastError = void 0;
3993
4279
  for (const strategy of strategies) {
3994
4280
  try {
3995
- log("info", { event: "deps.install.strategy.try", details: { strategy: strategy.name, packages } });
4281
+ depsStructuredLog("info", { event: "deps.install.strategy.try", details: { strategy: strategy.name, packages } });
3996
4282
  await strategy.run();
3997
- log("info", { event: "deps.install.strategy.ok", details: { strategy: strategy.name } });
4283
+ depsStructuredLog("info", { event: "deps.install.strategy.ok", details: { strategy: strategy.name } });
3998
4284
  progress("packages.install.done", { strategy: strategy.name });
3999
4285
  return;
4000
4286
  } catch (error) {
4001
4287
  lastError = error;
4002
- log("warn", {
4288
+ depsStructuredLog("warn", {
4003
4289
  event: "deps.install.strategy.fail",
4004
4290
  details: { strategy: strategy.name, message: error instanceof Error ? error.message : String(error) }
4005
4291
  });
4006
4292
  }
4007
4293
  }
4008
- throw new Error(
4009
- `Dependency install failed after all strategies: ${lastError instanceof Error ? lastError.message : String(lastError)}`
4294
+ onLogLine?.(
4295
+ `[deps][warn] \u5305\u5B89\u88C5\u672A\u6210\u529F\uFF08${specs.join(" ")}\uFF09: ${briefErrorMessage(lastError)}\uFF1BMCP \u4ECD\u5C06\u542F\u52A8\uFF0C\u53EF\u7A0D\u540E\u91CD\u8BD5\u6216\u914D\u7F6E registry`
4010
4296
  );
4011
4297
  }
4012
4298
  async function runAppiumDriverInstallWithPriority(config, driver, onLogLine) {
@@ -4041,12 +4327,12 @@ async function runAppiumDriverInstallWithPriority(config, driver, onLogLine) {
4041
4327
  ];
4042
4328
  for (const strategy of strategies) {
4043
4329
  try {
4044
- log("info", {
4330
+ depsStructuredLog("info", {
4045
4331
  event: "appium.driver.install.strategy.try",
4046
4332
  details: { strategy: strategy.name, driver, target }
4047
4333
  });
4048
4334
  await strategy.run();
4049
- log("info", {
4335
+ depsStructuredLog("info", {
4050
4336
  event: "appium.driver.install.strategy.ok",
4051
4337
  details: { strategy: strategy.name, driver, target }
4052
4338
  });
@@ -4054,7 +4340,7 @@ async function runAppiumDriverInstallWithPriority(config, driver, onLogLine) {
4054
4340
  return;
4055
4341
  } catch (error) {
4056
4342
  lastError = error;
4057
- log("warn", {
4343
+ depsStructuredLog("warn", {
4058
4344
  event: "appium.driver.install.strategy.fail",
4059
4345
  details: {
4060
4346
  strategy: strategy.name,
@@ -4071,16 +4357,23 @@ async function runAppiumDriverInstallWithPriority(config, driver, onLogLine) {
4071
4357
  );
4072
4358
  }
4073
4359
  async function verifyPlaywrightSelfTest(onLogLine) {
4074
- onLogLine?.("[playwright] \u81EA\u68C0\uFF1A\u542F\u52A8 Chromium \u7A7A\u767D\u9875");
4075
- const moduleName = ["play", "wright"].join("");
4076
- const p = require2(moduleName);
4077
- const b = await p.chromium.launch({ headless: true });
4360
+ onLogLine?.("[playwright] \u81EA\u68C0\uFF1A\u542F\u52A8 Chromium");
4078
4361
  try {
4079
- const c = await b.newContext();
4080
- const page = await c.newPage();
4081
- await page.goto("about:blank");
4082
- } finally {
4083
- await b.close();
4362
+ const moduleName = ["play", "wright"].join("");
4363
+ const p = require2(moduleName);
4364
+ const b = await p.chromium.launch({ headless: true });
4365
+ try {
4366
+ const c = await b.newContext();
4367
+ const page = await c.newPage();
4368
+ await page.goto("about:blank");
4369
+ } finally {
4370
+ await b.close();
4371
+ }
4372
+ onLogLine?.("[playwright] \u81EA\u68C0\u901A\u8FC7");
4373
+ return true;
4374
+ } catch (error) {
4375
+ onLogLine?.(`[playwright][warn] \u81EA\u68C0\u672A\u901A\u8FC7: ${briefErrorMessage(error)}`);
4376
+ return false;
4084
4377
  }
4085
4378
  }
4086
4379
  async function installPlaywrightBrowser(config, onLogLine, options) {
@@ -4104,36 +4397,45 @@ async function installPlaywrightBrowser(config, onLogLine, options) {
4104
4397
  const rankedHosts = await rankPlaywrightHosts(config);
4105
4398
  const timeoutMs = playwrightInstallTimeoutMs();
4106
4399
  onLogLine?.(`[playwright] \u5B89\u88C5\u8D85\u65F6\u4E0A\u9650 ${Math.round(timeoutMs / 1e3)}s\uFF08\u53EF\u7528 ADA_PLAYWRIGHT_INSTALL_TIMEOUT_MS \u8C03\u6574\uFF09`);
4107
- let lastError;
4400
+ let lastHost = "";
4108
4401
  for (let i = 0; i < rankedHosts.length; i++) {
4109
4402
  const host = rankedHosts[i];
4403
+ lastHost = host;
4110
4404
  if (i > 0) {
4111
- onLogLine?.(`[playwright] \u4E0A\u4E00\u955C\u50CF\u5931\u8D25\uFF0C\u6539\u8BD5 ${host}`);
4405
+ onLogLine?.(`[playwright] \u6362\u955C\u50CF: ${host}`);
4406
+ } else {
4407
+ onLogLine?.(
4408
+ `[playwright] playwright@${version}\uFF0CCDN ${host}\uFF0C\u76EE\u6807 ${targets.length ? targets.join(",") : "chromium"}`
4409
+ );
4112
4410
  }
4113
- onLogLine?.(
4114
- `[playwright] \u4F7F\u7528\u5185\u7F6E playwright@${version} CLI\uFF0C\u955C\u50CF ${host}\uFF0C\u76EE\u6807: ${targets.length ? targets.join(",") : "all"}${options?.force ? " (--force)" : ""}`
4115
- );
4116
4411
  try {
4117
4412
  await runCommand2(command, installArgs, {
4118
4413
  env: { PLAYWRIGHT_DOWNLOAD_HOST: host },
4119
4414
  timeoutMs,
4120
- onLogLine
4415
+ onLogLine: createPlaywrightInstallLogSink(onLogLine)
4121
4416
  });
4122
4417
  progress("playwright.browser.install.done", { selectedHost: host, attempt: i + 1 });
4123
- return;
4418
+ onLogLine?.(`[playwright] \u6D4F\u89C8\u5668\u5B89\u88C5\u5B8C\u6210\uFF08${host}\uFF09`);
4419
+ return true;
4124
4420
  } catch (error) {
4125
- lastError = error;
4126
- log("warn", {
4421
+ onLogLine?.(`[playwright][warn] \u955C\u50CF ${host} \u5931\u8D25: ${briefErrorMessage(error)}`);
4422
+ depsStructuredLog("warn", {
4127
4423
  event: "deps.playwright.browser.install.host.fail",
4128
4424
  details: {
4129
4425
  host,
4130
4426
  attempt: i + 1,
4131
- message: error instanceof Error ? error.message : String(error)
4427
+ message: briefErrorMessage(error)
4132
4428
  }
4133
4429
  });
4134
4430
  }
4135
4431
  }
4136
- throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "playwright browser install failed"));
4432
+ onLogLine?.(
4433
+ `[playwright][warn] \u6D4F\u89C8\u5668\u672A\u5B89\u88C5\u5B8C\u6210\uFF08\u5DF2\u5C1D\u8BD5 ${rankedHosts.length} \u4E2A CDN\uFF09\u3002\u53EF\u8BBE\u7F6E PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.playwright.dev \u540E\u91CD\u8BD5\uFF0C\u6216\u6267\u884C: npx playwright@${PINNED_PLAYWRIGHT_VERSION} install chromium`
4434
+ );
4435
+ if (lastHost) {
4436
+ onLogLine?.(`[playwright][warn] \u6700\u540E\u5C1D\u8BD5: ${lastHost}`);
4437
+ }
4438
+ return false;
4137
4439
  }
4138
4440
  async function checkPlaywrightLaunchable() {
4139
4441
  try {
@@ -4159,7 +4461,7 @@ async function verifyAppiumCommand() {
4159
4461
  }
4160
4462
  }
4161
4463
  async function getInstalledAppiumDrivers() {
4162
- const check = await runCommandCapture("npm", ["exec", "appium", "driver", "list", "--installed", "--json"]);
4464
+ const check = await runCommandCapture2("npm", ["exec", "appium", "driver", "list", "--installed", "--json"]);
4163
4465
  if (check.code === 0 && check.stdout) {
4164
4466
  try {
4165
4467
  const parsed = JSON.parse(check.stdout);
@@ -4168,7 +4470,7 @@ async function getInstalledAppiumDrivers() {
4168
4470
  } catch {
4169
4471
  }
4170
4472
  }
4171
- const fallback = await runCommandCapture("npm", ["exec", "appium", "driver", "list", "--installed"]);
4473
+ const fallback = await runCommandCapture2("npm", ["exec", "appium", "driver", "list", "--installed"]);
4172
4474
  if (fallback.code !== 0) {
4173
4475
  return [];
4174
4476
  }
@@ -4376,6 +4678,28 @@ async function saveInstallState(state) {
4376
4678
  await import_promises5.default.writeFile(file, JSON.stringify(state, null, 2), "utf8");
4377
4679
  }
4378
4680
  async function ensureDriverDependencies(config, options) {
4681
+ const previousHumanLog = depsHumanLog;
4682
+ depsHumanLog = options?.onLogLine;
4683
+ try {
4684
+ return await ensureDriverDependenciesImpl(config, options);
4685
+ } catch (error) {
4686
+ options?.onLogLine?.(`[deps][warn] \u4F9D\u8D56\u5B89\u88C5\u5F02\u5E38\u5DF2\u5FFD\u7565: ${briefErrorMessage(error)}`);
4687
+ const only = options?.only ?? "all";
4688
+ return {
4689
+ scope: only,
4690
+ force: options?.force ?? false,
4691
+ elapsedMs: 0,
4692
+ requestedDrivers: [],
4693
+ installedPackages: [],
4694
+ skippedPackages: [],
4695
+ installedDrivers: [],
4696
+ skippedDrivers: []
4697
+ };
4698
+ } finally {
4699
+ depsHumanLog = previousHumanLog;
4700
+ }
4701
+ }
4702
+ async function ensureDriverDependenciesImpl(config, options) {
4379
4703
  const startedAt = Date.now();
4380
4704
  const only = options?.only ?? "all";
4381
4705
  const force = options?.force === true;
@@ -4416,12 +4740,12 @@ async function ensureDriverDependencies(config, options) {
4416
4740
  }
4417
4741
  if (packagesToInstall.length > 0) {
4418
4742
  progress("deps.package.missing", { missing, installing: packagesToInstall });
4419
- log("warn", { event: "deps.missing", details: { missing, installing: packagesToInstall } });
4743
+ onLogLine?.(`[deps] \u5C06\u5B89\u88C5: ${packagesToInstall.join(", ")}`);
4420
4744
  await runInstallWithPriority(config, packagesToInstall, onLogLine);
4421
4745
  installedPackages.push(...packagesToInstall);
4422
4746
  } else {
4423
4747
  progress("deps.package.ok", { missing: [] });
4424
- log("info", { event: "deps.check.ok", details: { missing: [] } });
4748
+ depsStructuredLog("info", { event: "deps.check.ok", details: { missing: [] } });
4425
4749
  }
4426
4750
  if (hasPackage("playwright") && needPlaywright) {
4427
4751
  progress("playwright.selfcheck.start");
@@ -4429,7 +4753,7 @@ async function ensureDriverDependencies(config, options) {
4429
4753
  const reinstallForTargets = force && Boolean(pwOverride?.length);
4430
4754
  const userRequestedBrowserTargets = Boolean(pwOverride?.length);
4431
4755
  if (!launchOk || reinstallForTargets || userRequestedBrowserTargets || force) {
4432
- log("warn", {
4756
+ depsStructuredLog("warn", {
4433
4757
  event: "deps.playwright.browser.missing",
4434
4758
  details: {
4435
4759
  action: "install-playwright-browser",
@@ -4442,19 +4766,21 @@ async function ensureDriverDependencies(config, options) {
4442
4766
  userRequestedBrowserTargets ? "[playwright] --force\uFF1A\u91CD\u65B0\u5B89\u88C5\u5F53\u524D\u52FE\u9009\u7684\u6D4F\u89C8\u5668\u901A\u9053" : "[playwright] --force\uFF1A\u6309\u914D\u7F6E\u6587\u4EF6\u4E2D\u7684\u76EE\u6807\u91CD\u65B0\u5B89\u88C5\u6D4F\u89C8\u5668"
4443
4767
  );
4444
4768
  }
4445
- await installPlaywrightBrowser(configForPlaywright, onLogLine, {
4769
+ const browserInstalled = await installPlaywrightBrowser(configForPlaywright, onLogLine, {
4446
4770
  force: force || reinstallForTargets || !launchOk
4447
4771
  });
4448
- progress("playwright.selfcheck.verify");
4449
- await verifyPlaywrightSelfTest(onLogLine);
4450
- state.playwrightReady = true;
4451
- progress("playwright.selfcheck.done");
4772
+ if (browserInstalled) {
4773
+ progress("playwright.selfcheck.verify");
4774
+ state.playwrightReady = await verifyPlaywrightSelfTest(onLogLine);
4775
+ progress("playwright.selfcheck.done");
4776
+ } else {
4777
+ state.playwrightReady = false;
4778
+ }
4452
4779
  } else if (!force && state.playwrightReady) {
4453
4780
  progress("playwright.selfcheck.skip.cache", { cached: true, healthy: true });
4454
4781
  } else {
4455
4782
  progress("playwright.selfcheck.verify");
4456
- await verifyPlaywrightSelfTest(onLogLine);
4457
- state.playwrightReady = true;
4783
+ state.playwrightReady = await verifyPlaywrightSelfTest(onLogLine);
4458
4784
  progress("playwright.selfcheck.done");
4459
4785
  }
4460
4786
  }
@@ -4468,12 +4794,16 @@ async function ensureDriverDependencies(config, options) {
4468
4794
  progress("selenium.check.done", sel);
4469
4795
  }
4470
4796
  if (hasPackage("appium") && needAppium) {
4471
- progress("appium.selfcheck.start");
4472
- await verifyAppiumCommand();
4473
- if (!force && state.appiumReady) {
4474
- progress("appium.selfcheck.skip.cache", { cached: true, healthy: true });
4475
- } else {
4476
- state.appiumReady = true;
4797
+ try {
4798
+ progress("appium.selfcheck.start");
4799
+ await verifyAppiumCommand();
4800
+ if (!force && state.appiumReady) {
4801
+ progress("appium.selfcheck.skip.cache", { cached: true, healthy: true });
4802
+ } else {
4803
+ state.appiumReady = true;
4804
+ }
4805
+ } catch (error) {
4806
+ onLogLine?.(`[appium][warn] ${briefErrorMessage(error)}`);
4477
4807
  }
4478
4808
  }
4479
4809
  if (hasPackage("appium") && needDrivers) {
@@ -4492,11 +4822,15 @@ async function ensureDriverDependencies(config, options) {
4492
4822
  requiredDrivers: required
4493
4823
  }
4494
4824
  };
4495
- await ensureAppiumDrivers(scopedConfig, onLogLine);
4496
- installedDrivers.push(...missingBefore);
4497
- skippedDrivers.push(...required.filter((x) => !missingBefore.includes(x)));
4498
- state.driversReady = true;
4499
- progress("appium.driver.ensure.done");
4825
+ try {
4826
+ await ensureAppiumDrivers(scopedConfig, onLogLine);
4827
+ installedDrivers.push(...missingBefore);
4828
+ skippedDrivers.push(...required.filter((x) => !missingBefore.includes(x)));
4829
+ state.driversReady = true;
4830
+ progress("appium.driver.ensure.done");
4831
+ } catch (error) {
4832
+ onLogLine?.(`[appium][warn] \u9A71\u52A8\u5B89\u88C5\u672A\u5B8C\u6210: ${briefErrorMessage(error)}`);
4833
+ }
4500
4834
  }
4501
4835
  }
4502
4836
  state.androidHome = homes.androidHome;
@@ -4504,7 +4838,7 @@ async function ensureDriverDependencies(config, options) {
4504
4838
  await saveInstallState(state);
4505
4839
  const installedPkgs = Array.from(new Set(installedPackages));
4506
4840
  progress("deps.ensure.done", { installedPackages: installedPkgs, missingDetected: missing });
4507
- log("info", { event: "deps.install.completed", details: { installedPackages: installedPkgs } });
4841
+ depsStructuredLog("info", { event: "deps.install.completed", details: { installedPackages: installedPkgs } });
4508
4842
  const requestedPackages = ["playwright", "appium"].filter((pkg) => pkg === "playwright" ? needPlaywright : needAppium);
4509
4843
  let nativeDriversDir;
4510
4844
  let geckodriverPath;
@@ -4588,7 +4922,7 @@ async function getDependencyHealth(config) {
4588
4922
  missingAppiumDrivers
4589
4923
  };
4590
4924
  }
4591
- var import_node_module, import_node_fs, import_node_child_process2, import_promises5, import_node_path5, require2, DEFAULT_NPM_REGISTRY_CANDIDATES, detectedBestRegistryByKey, PROGRESS_STEPS, DEFAULT_PLAYWRIGHT_HOST_CANDIDATES, PLAYWRIGHT_HOST_FALLBACK, PW_INSTALL_TARGETS;
4925
+ var import_node_module, import_node_fs, import_node_child_process2, import_promises5, import_node_path5, require2, PINNED_PLAYWRIGHT_VERSION, depsHumanLog, DEFAULT_NPM_REGISTRY_CANDIDATES, detectedBestRegistryByKey, PROGRESS_STEPS, PROGRESS_HUMAN_LABELS, DEFAULT_PLAYWRIGHT_HOST_CANDIDATES, PLAYWRIGHT_HOST_FALLBACK, PW_INSTALL_TARGETS;
4592
4926
  var init_dependency_installer = __esm({
4593
4927
  "../ada-agent/src/dependency-installer.ts"() {
4594
4928
  import_node_module = require("node:module");
@@ -4600,6 +4934,7 @@ var init_dependency_installer = __esm({
4600
4934
  init_config();
4601
4935
  init_src2();
4602
4936
  require2 = (0, import_node_module.createRequire)(import_node_path5.default.join(process.cwd(), "package.json"));
4937
+ PINNED_PLAYWRIGHT_VERSION = "1.59.1";
4603
4938
  DEFAULT_NPM_REGISTRY_CANDIDATES = [
4604
4939
  "https://registry.npmmirror.com",
4605
4940
  "https://mirrors.cloud.tencent.com/npm",
@@ -4616,6 +4951,15 @@ var init_dependency_installer = __esm({
4616
4951
  "appium.driver.ensure.start",
4617
4952
  "deps.ensure.done"
4618
4953
  ];
4954
+ PROGRESS_HUMAN_LABELS = {
4955
+ "deps.ensure.start": "[deps] \u5F00\u59CB\u68C0\u6D4B\u4F9D\u8D56",
4956
+ "registry.probe.start": "[deps] \u63A2\u6D4B npm \u955C\u50CF\u2026",
4957
+ "packages.install.start": "[deps] \u5B89\u88C5 npm \u5305\u2026",
4958
+ "playwright.host.probe.start": "[playwright] \u63A2\u6D4B\u6D4F\u89C8\u5668 CDN\u2026",
4959
+ "playwright.browser.install.start": "[playwright] \u5B89\u88C5\u6D4F\u89C8\u5668\u2026",
4960
+ "appium.driver.ensure.start": "[appium] \u5B89\u88C5\u9A71\u52A8\u2026",
4961
+ "deps.ensure.done": "[deps] \u4F9D\u8D56\u68C0\u6D4B\u5B8C\u6210"
4962
+ };
4619
4963
  DEFAULT_PLAYWRIGHT_HOST_CANDIDATES = [
4620
4964
  "https://cdn.playwright.dev",
4621
4965
  "https://playwright.azureedge.net",
@@ -9168,9 +9512,15 @@ async function runBootstrapInstallDeps(argv2) {
9168
9512
  console.error(`[ADA-MCP] dependency bootstrap start (scope=${label}, force=${plan.force})`);
9169
9513
  for (const scope of plan.scopes) {
9170
9514
  console.error(`[ADA-MCP] installing: ${scope}`);
9171
- await installDependencies(scope, plan.force, (line) => {
9172
- console.error(`[ADA-MCP] ${line}`);
9173
- }, plan.extras);
9515
+ try {
9516
+ await installDependencies(scope, plan.force, (line) => {
9517
+ console.error(`[ADA-MCP] ${line}`);
9518
+ }, plan.extras);
9519
+ } catch (error) {
9520
+ const msg = error instanceof Error ? error.message.split(/\r?\n/)[0] : String(error);
9521
+ console.error(`[ADA-MCP][warn] ${scope} \u4F9D\u8D56\u5B89\u88C5\u672A\u5B8C\u6210: ${msg}`);
9522
+ console.error("[ADA-MCP][warn] MCP \u4ECD\u5C06\u542F\u52A8\uFF1B\u53EF\u8BBE\u7F6E PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.playwright.dev \u6216 --skip-install-deps");
9523
+ }
9174
9524
  }
9175
9525
  console.error("[ADA-MCP] dependency bootstrap done");
9176
9526
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ada-mcp/mcp-server",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "ADA MCP server for web/mobile automation (stdio + remote HTTP)",
5
5
  "private": false,
6
6
  "type": "commonjs",