@mearl/client 3.0.0-beta.5 → 3.0.0-beta.7

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
@@ -224,8 +224,8 @@ stdout 在管道或重定向时默认输出紧凑 JSON,在交互终端默认
224
224
  未指定字段时默认返回正文。
225
225
 
226
226
  `send_request` 默认携带所选浏览器中匹配完整目标 URL 的普通 Cookie,以及目标 URL 第一方站点对应的 CHIPS Cookie。例如 `https://app.example.com/` 使用 `https://example.com` 分区,不依赖已打开页面。站点按协议和公共后缀列表(含私有后缀)确定;排除其他站点、其他协议、跨站祖先及不透明分区。保留不同路径下的同名 Cookie,不额外拼入父站点的 host-only Cookie。嵌入其他站点的第三方分区、页面签名或完整页面身份仍需使用目标页面中的 `page_eval` 请求。
227
- 响应中的 `Set-Cookie` 会更新原浏览器,后续调用可以使用刷新后的登录态;`withCookies:false` 同时关闭自动携带与响应回写。扩展由 Chrome 处理响应 Cookie;CDP 的 Node 请求在返回前通过原页面的浏览器连接回写,支持 HttpOnly、默认路径、过期时间和删除。CDP 响应存在 `Set-Cookie` 时附带 `cookieSync: { updatedCount, rejectedCount, failedCount }`,不返回 Cookie 值;无法确定分区的 Partitioned 响应 Cookie 计入拒绝数。同步失败不撤销已完成的 HTTP 操作,应检查状态而非直接重发业务请求。MTop 普通请求及流式请求使用相同的响应刷新机制。
228
- 扩展的临时凭据注入仅匹配原始完整请求 URL,重定向由 Chrome 按新 URL 选择 Cookie。CDP 的 Node 请求(含 MTop 流式请求)不自动跟随重定向,避免将原 URL 的 Cookie 或签名传到其他地址。
227
+ 响应中的 `Set-Cookie` 会更新原浏览器,后续调用可以使用刷新后的登录态;`withCookies:false` 同时关闭自动携带与响应回写。扩展由 Chrome 处理响应 Cookie;CDP 的 Node 请求在返回前通过原页面的浏览器连接回写,支持 HttpOnly、默认路径、过期时间和删除。CDP 响应存在 `Set-Cookie` 时附带 `cookieSync: { updatedCount, rejectedCount, failedCount }`,不返回 Cookie 值;无法确定分区的 Partitioned 响应 Cookie 计入拒绝数。同步失败不撤销已完成的 HTTP 操作,应检查状态而非直接重发业务请求。Protocol 普通请求及流式请求使用相同的响应刷新机制。
228
+ 扩展的临时凭据注入仅匹配原始完整请求 URL,重定向由 Chrome 按新 URL 选择 Cookie。CDP 的 Node 请求(含 Protocol 流式请求)不自动跟随重定向,避免将原 URL 的 Cookie 或签名传到其他地址。
229
229
 
230
230
  Mearl 默认聚合当前机器和已配置 cloud-server 下的浏览器,并通过 `browser_list` 返回的全局
231
231
  `browserId` 自动路由。以下覆盖参数仅在限定宿主或连接、传输排障时使用,因此不出现在默认 help:
@@ -267,7 +267,7 @@ client、native-host、cloud-server 和 MCP server 的已安装版本。发现
267
267
  安装 skill 并自动注册命令,或直接注册已有入口:
268
268
 
269
269
  ```bash
270
- npx @mearl/setup plugin install https://github.com/F-loat/mearl --skill browser-tab-manager --yes
270
+ npx @mearl/setup plugin install https://github.com/mearlhq/mearl --skill browser-tab-manager --yes
271
271
  npx @mearl/setup plugin /absolute/path/to/browser-tab-manager
272
272
  npx @mearl/setup plugin uninstall tabs
273
273
  mearl tabs summary
@@ -445,7 +445,7 @@ Node 插件在运行 CLI、SDK 或 MCP Server 的宿主上执行,不随浏览
445
445
  | ------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
446
446
  | API 调试 | `tab_checkpoint` `get_requests` `get_logs` `get_events` |
447
447
  | Mock & 规则 | `set_mock` `get_mocks` `set_rule` `get_rules` |
448
- | 网络请求 | `send_request` `send_mtop_request` `request_domain_permission` |
448
+ | 网络请求 | `send_request` `send_protocol_request` `request_permission` |
449
449
  | 标签页 | `tab_open` `tab_list` `tab_close` |
450
450
  | 页面感知 | `page_snapshot` `page_inspect` `page_screenshot` `page_frames` |
451
451
  | 页面操作 | `page_click` `page_drag` `page_type` `page_hover` `page_scroll` `page_press` `page_wait` `page_navigate` `page_upload` `page_eval` |
@@ -26,7 +26,7 @@ export function resolveActionTimeoutSec(action, data, fallback = 15) {
26
26
  : 0;
27
27
  return Math.max(fallback, Math.ceil(((data.timeout ?? 30000) + transferTimeoutMs + 5000) / 1000));
28
28
  }
29
- if (action === 'send_mtop_request') {
29
+ if (action === 'send_protocol_request') {
30
30
  return Math.max(fallback, Math.ceil(((data.timeout ?? 10000) + 5000) / 1000));
31
31
  }
32
32
  if (action === 'page_download')
@@ -4,6 +4,7 @@ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync,
4
4
  import { homedir } from 'node:os';
5
5
  import { delimiter, dirname, join, resolve, win32 } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
+ import { npmExecEnvironment } from '@mearl/daemon-core';
7
8
  import { isNewerRelease, parseReleaseVersion } from './check.js';
8
9
  import { loadCliAutoUpdateIntervalDays } from './unifiedConfig.js';
9
10
  const CONFIG_DIR = join(homedir(), '.mearl');
@@ -207,7 +208,7 @@ export function setupUpdateCommand(version, packageRoot, profiles, updateClient)
207
208
  if (!parseReleaseVersion(version))
208
209
  throw new Error('A valid Mearl release is required');
209
210
  const [major, minor] = version.split('.');
210
- const env = { ...process.env };
211
+ const env = npmExecEnvironment();
211
212
  delete env.MEARL_AUTO_UPDATE_CLIENT_ROOT;
212
213
  delete env.MEARL_AUTO_UPDATE_CLIENT_ONLY;
213
214
  delete env.MEARL_AUTO_UPDATE_PROFILES;
@@ -0,0 +1,4 @@
1
+ import type { BrowserCapabilityCatalog } from '@mearl/browser-core';
2
+ /** Help reads an existing target with a short budget; it never installs an environment. */
3
+ export declare function readHelpCapabilities(args: string[]): Promise<BrowserCapabilityCatalog | undefined>;
4
+ export declare function formatCapabilityHelp(catalog: BrowserCapabilityCatalog | undefined, detailed?: boolean): string;
@@ -0,0 +1,69 @@
1
+ import { resolveServer } from './unifiedConfig.js';
2
+ import { listLiveBrowsers } from './browserRegistry.js';
3
+ import { UnifiedClient } from './unified.js';
4
+ /** Help reads an existing target with a short budget; it never installs an environment. */
5
+ export async function readHelpCapabilities(args) {
6
+ const value = (flag) => {
7
+ const index = args.indexOf(flag);
8
+ return index < 0 ? args.find(arg => arg.startsWith(`${flag}=`))?.slice(flag.length + 1) : args[index + 1];
9
+ };
10
+ const browser = value('--browser');
11
+ const session = value('--session');
12
+ const connector = value('--connector');
13
+ const { serverUrl } = resolveServer(value('--server'));
14
+ const local = args.includes('--local') || args.includes('--cdp');
15
+ if (local && connector || session === 'new')
16
+ return;
17
+ if (!browser && !connector && (local || !serverUrl) && !session && !listLiveBrowsers().length)
18
+ return;
19
+ const client = new UnifiedClient({ connectTimeoutSec: 1, requestTimeoutSec: 1, ...(serverUrl ? { serverUrl } : {}) });
20
+ let timer;
21
+ try {
22
+ const result = await Promise.race([
23
+ client.invoke('get_capabilities', {}, {
24
+ ...(browser ? { browser } : {}), ...(session ? { session } : {}),
25
+ ...(connector ? { connector } : {}), ...(local ? { local: true } : {}),
26
+ ...(args.includes('--cdp') ? { transport: 'cdp' } : {}),
27
+ }),
28
+ new Promise(resolve => { timer = setTimeout(() => resolve(undefined), 1500); }),
29
+ ]);
30
+ if (result && Array.isArray(result.actions) && Array.isArray(result.apps?.entries))
31
+ return result;
32
+ }
33
+ catch {
34
+ // Static help remains usable when the target is offline or does not expose a catalog.
35
+ }
36
+ finally {
37
+ clearTimeout(timer);
38
+ client.disconnect();
39
+ }
40
+ }
41
+ export function formatCapabilityHelp(catalog, detailed = false) {
42
+ if (!catalog)
43
+ return '当前浏览器能力暂不可读;以下为静态帮助。连接后再次运行 --help 可查看已安装 App。';
44
+ const lines = [
45
+ ...(catalog.actions.includes('send_protocol_request') ? ['已安装的可选协议命令:send_protocol_request(沿用 --payload 参数;--help 查看用法)', ''] : []),
46
+ '当前浏览器已安装的 App:',
47
+ ];
48
+ if (!catalog.apps.entries.length)
49
+ lines.push(catalog.apps.status === 'available' ? ' 暂无 App 档案;设备模拟可独立使用。' : ' App 目录暂不可读,不代表未安装。');
50
+ for (const app of catalog.apps.entries) {
51
+ lines.push(` ${app.id} — ${app.label}${app.available ? '' : '(暂不可用)'}`);
52
+ if (!detailed)
53
+ continue;
54
+ lines.push(` 能力:${app.features.join(', ')}`);
55
+ if (app.usage?.skills?.length)
56
+ lines.push(` Skill:${app.usage.skills.join(', ')}`);
57
+ if (app.usage?.description)
58
+ lines.push(` ${app.usage.description}`);
59
+ if (app.usage?.limitations?.length)
60
+ lines.push(...app.usage.limitations.map(line => ` 限制:${line}`));
61
+ if (app.usage?.documentation?.length)
62
+ lines.push(...app.usage.documentation.map(doc => ` 说明:${doc.title} ${doc.url}`));
63
+ }
64
+ if (detailed)
65
+ lines.push(...catalog.limitations.map(line => ` ${line}`));
66
+ else
67
+ lines.push(' App 用法与限制:mearl set_app_profile --help');
68
+ return lines.join('\n');
69
+ }
package/dist/check.js CHANGED
@@ -4,7 +4,7 @@ const UPDATE_SOURCES = {
4
4
  client: 'https://registry.npmjs.org/@mearl%2Fclient/latest',
5
5
  'native-host': 'https://registry.npmjs.org/@mearl%2Fnative-host/latest',
6
6
  'cloud-connector': 'https://registry.npmjs.org/@mearl%2Fcloud-connector/latest',
7
- extension: 'https://github.com/F-loat/mearl/releases/latest/download/config.json',
7
+ extension: 'https://github.com/mearlhq/mearl/releases/latest/download/config.json',
8
8
  };
9
9
  export function parseReleaseVersion(value) {
10
10
  if (typeof value !== 'string')
package/dist/cli.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { readHelpCapabilities, formatCapabilityHelp } from './capabilityHelp.js';
1
2
  import { readFileSync, writeFileSync } from 'node:fs';
2
3
  import { resolve } from 'node:path';
3
4
  import { Command, CommanderError } from 'commander';
@@ -10,7 +11,7 @@ import { formatCommandHelp } from './commandHelp.js';
10
11
  import { CLIENT_VERSION } from './socket.js';
11
12
  import { UnifiedClient } from './unified.js';
12
13
  import { findPluginEntry, findPluginRegistration, findOfficialPlugin, listPluginNames, listOfficialPluginNames, loadRegisteredPlugin, PluginError, } from './plugins/sdk/plugin.js';
13
- import { getCloudServerConfigPath, loadCloudServerConfig } from './unifiedConfig.js';
14
+ import { resolveServer } from './unifiedConfig.js';
14
15
  const TOP_LEVEL_COMMAND_NAME_WIDTH = 22;
15
16
  const CATEGORIES = [
16
17
  {
@@ -20,9 +21,9 @@ const CATEGORIES = [
20
21
  { title: 'Mock & 规则', names: ['set_mock', 'get_mocks', 'set_rule', 'get_rules'] },
21
22
  {
22
23
  title: '网络请求',
23
- names: ['send_request', 'send_mtop_request', 'request_domain_permission'],
24
+ names: ['send_request', 'request_permission'],
24
25
  },
25
- { title: '标签页', names: ['tab_open', 'tab_list', 'tab_group', 'tab_close', 'get_capabilities'] },
26
+ { title: '标签页', names: ['tab_open', 'tab_list', 'tab_group', 'tab_close'] },
26
27
  {
27
28
  title: '页面感知',
28
29
  names: ['page_snapshot', 'page_inspect', 'page_screenshot', 'page_frames'],
@@ -351,17 +352,6 @@ function parsePayload(payloadRaw, payloadFile) {
351
352
  }
352
353
  return {};
353
354
  }
354
- function resolveServer(explicit) {
355
- if (explicit)
356
- return { serverUrl: explicit, serverUrlSource: '--server' };
357
- if (process.env.MEARL_SERVER_URL) {
358
- return { serverUrl: process.env.MEARL_SERVER_URL, serverUrlSource: 'MEARL_SERVER_URL' };
359
- }
360
- const config = loadCloudServerConfig();
361
- return config?.server
362
- ? { serverUrl: config.server, serverUrlSource: getCloudServerConfigPath() }
363
- : {};
364
- }
365
355
  function normalizeScreenshotOutput(action, payload, explicitOutput) {
366
356
  if (action !== 'page_screenshot' || !Object.hasOwn(payload, 'output')) {
367
357
  return { payload, ...(explicitOutput ? { outputPath: explicitOutput } : {}) };
@@ -612,8 +602,11 @@ export async function runMearlCli(args, options = {}) {
612
602
  }
613
603
  if (args.includes('--help') || args.includes('-h')) {
614
604
  const command = COMMAND_MAP.get(args[0]);
615
- if (command)
605
+ if (command) {
616
606
  console.error(formatCommandHelp(commandName, command));
607
+ if (command.name === 'set_app_profile' || command.name === 'tab_open')
608
+ console.error(formatCapabilityHelp(await readHelpCapabilities(args), true));
609
+ }
617
610
  else if (args[0] === 'check')
618
611
  printCheckHelp(commandName);
619
612
  else if (ADVANCED_BUILTIN_COMMANDS.has(args[0])) {
@@ -621,6 +614,7 @@ export async function runMearlCli(args, options = {}) {
621
614
  }
622
615
  else if (args[0]?.startsWith('-')) {
623
616
  printUsage(commandName, clientVersion);
617
+ console.error(formatCapabilityHelp(await readHelpCapabilities(args)));
624
618
  }
625
619
  else {
626
620
  printUnknownCommand(commandName, args);
@@ -39,10 +39,10 @@ export function formatCommandHelp(commandName, command) {
39
39
  ];
40
40
  const method = [...SDK_METHOD_ACTIONS].find(([, action]) => action === command.name)?.[0];
41
41
  if (method)
42
- lines.push('SDK:', ...(method === 'browserRelease'
42
+ lines.push('SDK:', ...(method === 'browserRelease' || method === 'browserList'
43
43
  ? [
44
- ' await mearl.browserRelease(options?)',
45
- ' await mearl.browserRelease(params, options)',
44
+ ` await mearl.${method}(options?)`,
45
+ ` await mearl.${method}(params, options)`,
46
46
  ]
47
47
  : [
48
48
  ` await mearl.${method}(${SDK_OPTIONS_ONLY_METHODS.has(method) ? 'options?' : 'params, options?'})`,
@@ -1,5 +1,7 @@
1
1
  export interface SuggestableCommand {
2
2
  readonly aliases?: readonly string[];
3
+ /** Suggestion-only expressions whose canonical invocation needs a specific payload. */
4
+ readonly aliasExamples?: Readonly<Record<string, string>>;
3
5
  readonly description: string;
4
6
  readonly examples?: readonly string[];
5
7
  readonly name: string;
@@ -7,6 +9,7 @@ export interface SuggestableCommand {
7
9
  export interface CommandSuggestion {
8
10
  command: SuggestableCommand;
9
11
  score: number;
12
+ example?: string;
10
13
  }
11
14
  export declare function commandInput(args: readonly string[]): string;
12
15
  export declare function findCommandSuggestions(args: readonly string[], commands: readonly SuggestableCommand[], limit?: number): CommandSuggestion[];
@@ -40,9 +40,20 @@ export function findCommandSuggestions(args, commands, limit = 3) {
40
40
  return [];
41
41
  const ranked = commands
42
42
  .map(command => {
43
- const keys = [command.name, ...(command.aliases ?? [])].map(normalizeCommand);
44
- const score = Math.max(...queries.flatMap(query => keys.map(key => similarity(query, key))));
45
- return { command, score };
43
+ const keys = [command.name, ...(command.aliases ?? [])].map(name => ({ name }));
44
+ for (const [name, example] of Object.entries(command.aliasExamples ?? {})) {
45
+ keys.push({ name, example });
46
+ }
47
+ let score = -1;
48
+ let example;
49
+ for (const key of keys) {
50
+ const candidate = Math.max(...queries.map(query => similarity(query, normalizeCommand(key.name))));
51
+ if (candidate > score) {
52
+ score = candidate;
53
+ example = key.example;
54
+ }
55
+ }
56
+ return { command, score, ...(example ? { example } : {}) };
46
57
  })
47
58
  .filter(suggestion => suggestion.score >= MIN_SIMILARITY)
48
59
  .sort((left, right) => right.score - left.score || left.command.name.localeCompare(right.command.name));
@@ -52,8 +63,8 @@ export function findCommandSuggestions(args, commands, limit = 3) {
52
63
  return ranked.filter(suggestion => bestScore - suggestion.score <= MAX_SCORE_GAP).slice(0, limit);
53
64
  }
54
65
  export function formatCommandSuggestions(commandName, suggestions) {
55
- return suggestions.map(({ command }) => {
56
- const invocation = command.examples?.[0] ?? command.name;
66
+ return suggestions.map(({ command, example }) => {
67
+ const invocation = example ?? command.examples?.[0] ?? command.name;
57
68
  return ` ${commandName} ${invocation}\n ${command.description}`;
58
69
  });
59
70
  }
@@ -14,6 +14,8 @@ export interface CommandDef {
14
14
  summary?: string;
15
15
  /** CLI 中用于推荐 canonical action 的常见表达,不会作为可执行 action。 */
16
16
  aliases?: readonly string[];
17
+ /** 仅用于错误建议;按表达给出 canonical action 的适用示例,不增加可执行别名。 */
18
+ aliasExamples?: Readonly<Record<string, string>>;
17
19
  /** payload 字段说明 */
18
20
  params?: ParamDef[];
19
21
  /** 是否支持 --output */
@@ -86,7 +88,7 @@ export declare const COMMANDS: readonly [{
86
88
  readonly examples: ["tab_checkpoint --payload '{\"tabId\":12345}'"];
87
89
  }, {
88
90
  readonly name: "get_requests";
89
- readonly description: "获取最近的网络请求(mtop 或普通 xhr/fetch)";
91
+ readonly description: "获取最近的网络请求(协议请求或普通 xhr/fetch)";
90
92
  readonly params: [{
91
93
  readonly name: "tabId";
92
94
  readonly type: "number";
@@ -98,8 +100,8 @@ export declare const COMMANDS: readonly [{
98
100
  readonly description: "返回条数,默认 10";
99
101
  }, {
100
102
  readonly name: "source";
101
- readonly type: "\"mtop\" | \"requests\" | \"all\"";
102
- readonly description: "数据来源,不填时默认 mtop;all 返回全部业务请求";
103
+ readonly type: "\"protocol\" | \"requests\" | \"all\"";
104
+ readonly description: "数据来源,不填时默认 all;all 返回全部业务请求";
103
105
  }, {
104
106
  readonly name: "filter";
105
107
  readonly type: "string";
@@ -133,9 +135,9 @@ export declare const COMMANDS: readonly [{
133
135
  readonly type: "boolean";
134
136
  readonly description: "只返回数据结构,不返回叶子值";
135
137
  }];
136
- readonly notes: readonly ["流式请求开始后即进入缓冲区;页面及 App 桥代发的 MTop SSE 都归入 mtop。无法归属当前页面 target 的 Service Worker 请求不在采集范围内。"];
138
+ readonly notes: readonly ["流式请求开始后即进入缓冲区;页面及 App 桥代发的 协议 SSE 都归入 protocol。无法归属当前页面 target 的 Service Worker 请求不在采集范围内。"];
137
139
  readonly result: readonly ["nextCursor 用于同类查询续读,hasMore 表示还有数据,gap 表示缓冲区淘汰造成缺口;checkpoint cursor 可分别用于三类查询的初始边界。", "省略正文时不返回 postData / responseBody 字段;指定 fields 后需要同时读取全文时显式传 includeBody:true。", "isPending 标记未完成请求;SSE 的 responseBody 保存按序到达的 event/data/timestamp/duration 分片。敏感请求头返回脱敏值。"];
138
- readonly examples: ["get_requests --payload '{\"tabId\":12345,\"count\":5}'", "get_requests --payload '{\"tabId\":12345,\"source\":\"mtop\",\"filter\":\"cart\"}'"];
140
+ readonly examples: ["get_requests --payload '{\"tabId\":12345,\"count\":5}'", "get_requests --payload '{\"tabId\":12345,\"source\":\"protocol\",\"filter\":\"cart\"}'"];
139
141
  }, {
140
142
  readonly name: "get_logs";
141
143
  readonly description: "获取浏览器控制台日志";
@@ -242,7 +244,7 @@ export declare const COMMANDS: readonly [{
242
244
  readonly description: "请求 query/body 字段子集匹配;$index 从 0 开始指定调用顺序";
243
245
  }];
244
246
  readonly notes: readonly ["启用时至少提供 mockData 或非空 fields;非空 fields 优先。fields 每项 { path: string, value: any },path 支持点号和数组索引。", "相同 apiName 的多条无条件 Mock 会覆盖;conditions 子集匹配 query/body(含 data 内层),不同条件可共存;$index 从 0 起控制调用顺序,不参与字段匹配。", "大字段可用顶层 @<绝对路径> 文件引用或 --payload-file;文件展开后仍受 Native Messaging 单条消息大小限制。"];
245
- readonly examples: ["set_mock --payload '{\"tabId\":12345,\"apiName\":\"mtop.cart.query\",\"mockData\":{\"ret\":[\"SUCCESS::调用成功\"]}}'", "set_mock --payload-file ./mock-data.json"];
247
+ readonly examples: ["set_mock --payload '{\"tabId\":12345,\"apiName\":\"cart.query\",\"mockData\":{\"ret\":[\"SUCCESS::调用成功\"]}}'", "set_mock --payload-file ./mock-data.json"];
246
248
  }, {
247
249
  readonly name: "get_mocks";
248
250
  readonly description: "查看当前生效的 mock 列表";
@@ -307,7 +309,7 @@ export declare const COMMANDS: readonly [{
307
309
  readonly type: "string";
308
310
  readonly description: "规则说明";
309
311
  }];
310
- readonly notes: readonly ["扩展模式规则持久化;CDP 仅支持会话级 modifyHeaders 请求头 set,不支持响应头、initiatorDomains 或其他动作。Provider 是否支持以其能力声明为准。", "扩展模式会检查规则请求 URL 和显式来源域名的访问权限;缺失时仍保存规则,返回 warnings 和 missingOrigins,不自动弹窗。可在请求规则页面授权,或按提示调用 request_domain_permission。复杂 URL 表达式无法完整推断域名时会提示手动检查。"];
312
+ readonly notes: readonly ["扩展模式规则持久化;CDP 仅支持会话级 modifyHeaders 请求头 set,不支持响应头、initiatorDomains 或其他动作。Provider 是否支持以其能力声明为准。", "扩展模式会检查规则请求 URL 和显式来源域名的访问权限;缺失时仍保存规则,返回 warnings 和 missingOrigins,不自动弹窗。可在请求规则页面授权,或按提示调用 request_permission。复杂 URL 表达式无法完整推断域名时会提示手动检查。"];
311
313
  readonly examples: ["set_rule --payload '{\"actionType\":\"block\",\"filter\":\"^https://example\\\\.com/ads/.*\"}'", "set_rule --payload '{\"ruleId\":42,\"enabled\":false}'"];
312
314
  }, {
313
315
  readonly name: "get_rules";
@@ -367,46 +369,30 @@ export declare const COMMANDS: readonly [{
367
369
  readonly result: readonly ["返回 status、statusText、headers、body、bodyType、attachedCookieCount;bodyType 为 json/text/base64,二进制 body 需解码后保存。"];
368
370
  readonly examples: ["send_request --payload '{\"url\":\"https://api.example.com/data\",\"method\":\"GET\"}'", "send_request --payload '{\"url\":\"https://api.example.com/upload\",\"method\":\"POST\",\"formData\":{\"folder\":\"assets\"},\"files\":[{\"fieldName\":\"file\",\"filePath\":\"/absolute/path/image.png\"}]}'", "send_request --payload '{\"url\":\"https://api.example.com/upload\",\"method\":\"POST\",\"bodyFile\":{\"filePath\":\"/absolute/path/image.png\",\"contentType\":\"image/png\"}}'"];
369
371
  }, {
370
- readonly name: "send_mtop_request";
371
- readonly description: "使用目标浏览器 Cookie 发起 mtop 请求,自动处理签名和 token";
372
+ readonly name: "send_protocol_request";
373
+ readonly description: "通过已安装的请求协议能力,使用目标浏览器 Cookie 发送请求";
372
374
  readonly params: [{
373
- readonly name: "api";
374
- readonly type: "string";
375
- readonly required: true;
376
- readonly description: "mtop API 名称";
377
- }, {
378
- readonly name: "data";
379
- readonly type: "object";
380
- readonly description: "请求数据";
381
- }, {
382
- readonly name: "version";
383
- readonly type: "string";
384
- readonly description: "API 版本,默认 1.0";
385
- }, {
386
- readonly name: "method";
387
- readonly type: "string";
388
- readonly description: "HTTP 方法,默认 GET";
389
- }, {
390
- readonly name: "appKey";
391
- readonly type: "string";
392
- readonly description: "签名使用的 appKey,默认 12574478";
393
- }, {
394
- readonly name: "env";
395
- readonly type: "\"prod\" | \"pre\"";
396
- readonly description: "请求环境,默认 prod";
397
- }, {
398
375
  readonly name: "timeout";
399
376
  readonly type: "number";
400
- readonly description: "超时毫秒数,默认 10000";
377
+ readonly description: "宿主超时毫秒数;协议能力负责校验具体参数";
401
378
  }];
402
- readonly notes: readonly ["需要目标浏览器有对应登录态;Mearl 自动处理签名,不依赖页面脚本。api 以 mopen. 开头时使用 mopen 协议。", "env=prod/pre 的网关由已安装的请求协议能力提供。"];
403
- readonly result: readonly ["返回 success、response、requestData;业务成功需检查 response.ret。"];
404
- readonly examples: ["send_mtop_request --payload '{\"api\":\"mtop.trade.order.detail\",\"data\":{\"orderId\":\"123\"}}'"];
379
+ readonly notes: readonly ["需要安装提供 request.protocol 的能力包;参数、签名、网关与响应协议由能力包定义。", "使用 --payload 传递协议文档声明的参数;核心不设置业务参数默认值。"];
380
+ readonly result: readonly ["返回 success、response、requestData;业务结果由协议定义。"];
381
+ readonly examples: ["send_protocol_request --payload '{\"operation\":\"example\",\"data\":{}}'"];
405
382
  }, {
406
383
  readonly name: "browser_list";
407
384
  readonly description: "列出所有浏览器及其连接状态、类型和 Provider 托管实例信息";
408
385
  readonly summary: "列出本地及远端浏览器与连接状态";
409
- readonly examples: ["browser_list"];
386
+ readonly params: [{
387
+ readonly name: "provider";
388
+ readonly type: "string";
389
+ readonly description: "配合 launchForm,仅查询一个本机已安装 Provider 的启动候选;需 --local";
390
+ }, {
391
+ readonly name: "launchForm";
392
+ readonly type: "object";
393
+ readonly description: "现有动态启动表单请求:{variant?,values:{}},不创建或接入实例";
394
+ }];
395
+ readonly examples: ["browser_list", "browser_list --local"];
410
396
  }, {
411
397
  readonly name: "browser_release";
412
398
  readonly description: "释放当前浏览器的调试控制和会话级临时效果(含设备/时区模拟);不用于普通任务收尾,Provider 实例的行为以 mearl <providerId> 为准";
@@ -489,6 +475,9 @@ export declare const COMMANDS: readonly [{
489
475
  readonly name: "tab_open";
490
476
  readonly description: "打开或按 URL 规则复用 Tab,并等待新页面加载完成";
491
477
  readonly aliases: readonly ["tab_new", "new_tab", "tab_create", "create_tab"];
478
+ readonly aliasExamples: {
479
+ readonly tab_activate: "tab_open --payload '{\"url\":\"https://example.com\",\"reuse\":\"require\",\"active\":true}'";
480
+ };
492
481
  readonly params: [{
493
482
  readonly name: "url";
494
483
  readonly type: "string";
@@ -529,7 +518,7 @@ export declare const COMMANDS: readonly [{
529
518
  }, {
530
519
  readonly name: "appProfile";
531
520
  readonly type: "string";
532
- readonly description: "按已安装档案应用 App 环境,ID 与限制通过 get_capabilities 查询。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true";
521
+ readonly description: "按已安装档案应用 App 环境,ID 与限制见当前动态帮助。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true";
533
522
  }, {
534
523
  readonly name: "os";
535
524
  readonly type: "\"ios\" | \"android\"";
@@ -581,6 +570,9 @@ export declare const COMMANDS: readonly [{
581
570
  }, {
582
571
  readonly name: "page_click";
583
572
  readonly description: "点击页面元素,支持 CSS/@ref、可见文本或视口坐标定位";
573
+ readonly aliasExamples: {
574
+ readonly page_select: "page_click --payload '{\"tabId\":12345,\"text\":\"选项文案\",\"role\":\"option\"}'";
575
+ };
584
576
  readonly params: [{
585
577
  readonly name: "selector";
586
578
  readonly type: "string";
@@ -624,7 +616,7 @@ export declare const COMMANDS: readonly [{
624
616
  readonly description: "目标标签页 ID";
625
617
  }];
626
618
  readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
627
- readonly notes: readonly ["selector / text / point 必须且只能提供一个。隐藏页 auto 使用 DOM;只有明确依赖真实用户手势时才激活页面。", "原生 select 可按 option 文本点击;checkbox/radio 返回 controlState.checkedBefore/checkedAfter。"];
619
+ readonly notes: readonly ["selector / text / point 必须且只能提供一个。隐藏页 auto 使用 DOM;只有明确依赖真实用户手势时才激活页面。", "auto 对原生 option、checkbox/radio 使用状态感知的 DOM 路径;显式 mouse/touch 保留可信输入。", "原生 select 可按 option 文本点击;auto/dom 返回 selectionState.valueBefore/valueAfter/selectedAfter,检查选中状态是否保持。checkbox/radio 返回 controlState.checkedBefore/checkedAfter。"];
628
620
  readonly examples: ["page_click --payload '{\"tabId\":12345,\"text\":\"提交\"}'", "page_click --payload '{\"tabId\":12345,\"text\":\"退款明细\",\"role\":\"button\"}'", "page_click --payload '{\"tabId\":12345,\"text\":\"确定\",\"scope\":\"@e12\"}'", "page_click --payload '{\"tabId\":12345,\"selector\":\"@e3\"}'", "page_click --payload '{\"tabId\":12345,\"selector\":\".submit-btn\"}'", "page_click --payload '{\"tabId\":12345,\"selector\":\".gesture-btn\",\"clickMode\":\"touch\"}'", "page_click --payload '{\"tabId\":12345,\"point\":{\"x\":336,\"y\":117}}'", "page_click --payload '{\"tabId\":12345,\"point\":{\"x\":196,\"y\":240},\"coordinateSpace\":\"active-frame\"}'"];
629
621
  }, {
630
622
  readonly name: "page_drag";
@@ -659,6 +651,9 @@ export declare const COMMANDS: readonly [{
659
651
  }, {
660
652
  readonly name: "page_type";
661
653
  readonly description: "向输入框填写文本,兼容 React 受控组件";
654
+ readonly aliasExamples: {
655
+ readonly page_fill: "page_type --payload '{\"tabId\":12345,\"selector\":\"@e6\",\"text\":\"填写内容\",\"clearFirst\":true}'";
656
+ };
662
657
  readonly params: [{
663
658
  readonly name: "selector";
664
659
  readonly type: "string";
@@ -820,6 +815,14 @@ export declare const COMMANDS: readonly [{
820
815
  readonly name: "modifiers";
821
816
  readonly type: "string[]";
822
817
  readonly description: "修饰键数组:ctrl/alt/shift/meta/cmd";
818
+ }, {
819
+ readonly name: "dialogId";
820
+ readonly type: "string";
821
+ readonly description: "原生弹窗回执或 page_snapshot 中的 dialog.id;仅配合 Enter 接受或 Escape 取消";
822
+ }, {
823
+ readonly name: "promptText";
824
+ readonly type: "string";
825
+ readonly description: "仅用于 dialogId + Enter 接受原生 prompt 时填写文本";
823
826
  }, {
824
827
  readonly name: "pressMode";
825
828
  readonly type: "\"auto\" | \"dom\" | \"keyboard\"";
@@ -830,7 +833,7 @@ export declare const COMMANDS: readonly [{
830
833
  readonly required: true;
831
834
  readonly description: "目标标签页 ID";
832
835
  }];
833
- readonly notes: readonly ["隐藏页 DOM 模式支持 Tab 焦点移动、原生控件 Enter/Space 激活和输入框 Enter 提交;编辑、选择、方向键和页面滚动等原生行为需可见页与 pressMode=keyboard。"];
836
+ readonly notes: readonly ["隐藏页 DOM 模式支持 Tab 焦点移动、原生控件 Enter/Space 激活和输入框 Enter 提交;编辑、选择、方向键和页面滚动等原生行为需可见页与 pressMode=keyboard。", "原生 alert/confirm/prompt/beforeunload 弹窗需显式传 dialogId;普通 Enter 不处理弹窗。弹窗处理绕过 DOM 观察,不自动重放被中断的动作;处理后重新读取页面。"];
834
837
  readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。", "action.data.dispatchMode / fallbackReason / defaultAction 说明实际输入路径。"];
835
838
  readonly examples: ["page_press --payload '{\"tabId\":12345,\"key\":\"Enter\"}'"];
836
839
  }, {
@@ -874,11 +877,14 @@ export declare const COMMANDS: readonly [{
874
877
  readonly required: true;
875
878
  readonly description: "目标标签页 ID";
876
879
  }];
877
- readonly notes: readonly ["time / selector / text / condition 必须且只能提供一个;time 范围 0–60000ms。页面交互已有内置观察,仅在需要独立等待条件时使用。"];
880
+ readonly notes: readonly ["time / selector / text / condition 必须且只能提供一个;time 范围 0–60000ms。页面交互已有内置观察,仅在需要独立等待条件时使用。", "等待后须立即执行已知动作时,用 mearl run 在同一脚本中 await pageWait 后紧接 pageClick,避免模型往返。整体 --timeout 须覆盖等待及后续动作;等待失败不继续点击。"];
878
881
  readonly examples: ["page_wait --payload '{\"tabId\":12345,\"time\":1000}'", "page_wait --payload '{\"tabId\":12345,\"selector\":\".dialog\",\"state\":\"visible\"}'", "page_wait --payload '{\"tabId\":12345,\"selector\":\".loading\",\"state\":\"hidden\"}'", "page_wait --payload '{\"tabId\":12345,\"text\":\"提交成功\"}'", "page_wait --payload '{\"tabId\":12345,\"condition\":\"window.appReady === true\",\"timeout\":30000}'"];
879
882
  }, {
880
883
  readonly name: "page_navigate";
881
884
  readonly description: "在当前标签页内导航到 URL、刷新页面,或沿浏览历史后退/前进";
885
+ readonly aliasExamples: {
886
+ readonly page_reload: "page_navigate --payload '{\"tabId\":12345,\"refresh\":true}'";
887
+ };
882
888
  readonly params: [{
883
889
  readonly name: "url";
884
890
  readonly type: "string";
@@ -1065,7 +1071,7 @@ export declare const COMMANDS: readonly [{
1065
1071
  readonly examples: ["set_device_emulation --payload '{\"tabId\":12345,\"enabled\":true,\"preset\":\"iphone-17-pro\"}'", "set_device_emulation --payload '{\"tabId\":12345,\"enabled\":true,\"preset\":\"pixel-11\",\"orientation\":\"landscape\"}'", "set_device_emulation --payload '{\"tabId\":12345,\"enabled\":true,\"preset\":\"ipad-mini-a17-pro\",\"orientation\":\"landscape\",\"deviceShell\":true,\"splitScreen\":true}'", "set_device_emulation --payload '{\"tabId\":12345,\"enabled\":false}'"];
1066
1072
  }, {
1067
1073
  readonly name: "set_app_profile";
1068
- readonly description: "设置已安装的 App 档案;完整 JSBridge 由能力包提供。可同时设置 MTop 页面接口环境,传 emulation 时联动设备模拟";
1074
+ readonly description: "设置已安装的 App 档案;完整 JSBridge 由能力包提供。可同时设置已安装的网络控件,传 emulation 时联动设备模拟";
1069
1075
  readonly summary: "设置或清除目标标签页的 App 容器模拟";
1070
1076
  readonly params: [{
1071
1077
  readonly name: "enabled";
@@ -1075,7 +1081,7 @@ export declare const COMMANDS: readonly [{
1075
1081
  }, {
1076
1082
  readonly name: "profile";
1077
1083
  readonly type: "string";
1078
- readonly description: "已安装的容器档案 ID(enabled=true 时必填);通过 get_capabilities 查询当前浏览器目录";
1084
+ readonly description: "已安装的容器档案 ID(enabled=true 时必填);当前动态帮助直接展示目标浏览器目录";
1079
1085
  }, {
1080
1086
  readonly name: "os";
1081
1087
  readonly type: "\"ios\" | \"android\"";
@@ -1101,9 +1107,9 @@ export declare const COMMANDS: readonly [{
1101
1107
  readonly type: "object";
1102
1108
  readonly description: "声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本";
1103
1109
  }, {
1104
- readonly name: "mtopEnv";
1105
- readonly type: "\"default\" | \"prod\" | \"pre\"";
1106
- readonly description: "可选 MTop 页面接口环境;default 关闭环境改写,省略时保持当前设置。仅作用于目标 tab";
1110
+ readonly name: "networkControl";
1111
+ readonly type: "object | null";
1112
+ readonly description: "可选已安装网络控件 {id,value};null 清除,省略时保持当前设置。仅作用于目标 tab";
1107
1113
  }, {
1108
1114
  readonly name: "emulation";
1109
1115
  readonly type: "object";
@@ -1119,8 +1125,8 @@ export declare const COMMANDS: readonly [{
1119
1125
  readonly description: "目标标签页 ID";
1120
1126
  }];
1121
1127
  readonly notes: readonly ["只有用户明确要求 App/客户端/容器/JSBridge 模拟时启用。移动视口或普通 H5 调试使用 set_device_emulation。", "emulation={} 使用档案推荐设备;enabled=false 时省略 emulation 只清除 App 注入,传 {} 同时清除设备模拟。", "bridgeConfig.methods 按方法声明 behavior / response / noop 三选一,可传 silent;只支持受控行为、固定 JSON 回包或成功空实现,不执行配置脚本。"];
1122
- readonly result: readonly ["返回 enabled、profile、label、userAgent、os、mtopEnv、reloaded;联动设备时额外返回实际 emulation。"];
1123
- readonly examples: ["set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"mtopEnv\":\"pre\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"mtopEnv\":\"prod\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"emulation\":{}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"wallet\",\"emulation\":{\"preset\":\"pixel-11\",\"deviceShell\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"emulation\":{\"preset\":\"ipad-mini-a17-pro\",\"orientation\":\"landscape\",\"deviceShell\":true,\"splitScreen\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"emulation\":{}}'"];
1128
+ readonly result: readonly ["返回 enabled、profile、label、userAgent、os、networkControl、reloaded;联动设备时额外返回实际 emulation。"];
1129
+ readonly examples: ["set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"networkControl\":{\"id\":\"environment\",\"value\":\"pre\"}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"networkControl\":null}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"emulation\":{}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"wallet\",\"emulation\":{\"preset\":\"pixel-11\",\"deviceShell\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"emulation\":{\"preset\":\"ipad-mini-a17-pro\",\"orientation\":\"landscape\",\"deviceShell\":true,\"splitScreen\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"emulation\":{}}'"];
1124
1130
  }, {
1125
1131
  readonly name: "set_timezone";
1126
1132
  readonly description: "覆盖目标 tab 的时区,传空字符串恢复浏览器默认时区";
@@ -1213,7 +1219,7 @@ export declare const COMMANDS: readonly [{
1213
1219
  readonly result: readonly ["frames[] 包含 frameId、url、name 和 isMainFrame;frameId 可用于 page_eval 等支持 frame 定位的 action。"];
1214
1220
  readonly examples: ["page_frames --payload '{\"tabId\":12345}'"];
1215
1221
  }, {
1216
- readonly name: "request_domain_permission";
1222
+ readonly name: "request_permission";
1217
1223
  readonly description: "申请访问指定域名的权限(必要时弹窗让用户授权)";
1218
1224
  readonly params: [{
1219
1225
  readonly name: "domain";
@@ -1223,7 +1229,7 @@ export declare const COMMANDS: readonly [{
1223
1229
  }];
1224
1230
  readonly notes: readonly ["domain 使用纯主机名,不传 URL。在 send_request 或 set_rule 提示缺少浏览器域名权限时申请。", "弹窗等待用户选择,90 秒超时或关闭按拒绝处理;granted=true 后才重试原请求。"];
1225
1231
  readonly result: readonly ["返回 granted、domain,授权成功时包含 origin。"];
1226
- readonly examples: ["request_domain_permission --payload '{\"domain\":\"example.com\"}'"];
1232
+ readonly examples: ["request_permission --payload '{\"domain\":\"example.com\"}'"];
1227
1233
  }, {
1228
1234
  readonly name: "run_actions";
1229
1235
  readonly description: "批量顺序执行多个 action,减少往返延迟。遇错即停,返回已完成步骤结果;步骤内的页面动作不带内置观察";
package/dist/commands.js CHANGED
@@ -101,7 +101,7 @@ export const COMMANDS = [
101
101
  },
102
102
  {
103
103
  name: 'get_requests',
104
- description: '获取最近的网络请求(mtop 或普通 xhr/fetch)',
104
+ description: '获取最近的网络请求(协议请求或普通 xhr/fetch)',
105
105
  params: [
106
106
  {
107
107
  name: 'tabId',
@@ -112,8 +112,8 @@ export const COMMANDS = [
112
112
  { name: 'count', type: 'number', description: '返回条数,默认 10' },
113
113
  {
114
114
  name: 'source',
115
- type: '"mtop" | "requests" | "all"',
116
- description: '数据来源,不填时默认 mtop;all 返回全部业务请求',
115
+ type: '"protocol" | "requests" | "all"',
116
+ description: '数据来源,不填时默认 all;all 返回全部业务请求',
117
117
  },
118
118
  { name: 'filter', type: 'string', description: '按 URL 或 API 名称过滤,支持正则' },
119
119
  { name: 'since', type: 'number', description: '只返回最近 N 秒内的请求' },
@@ -133,7 +133,7 @@ export const COMMANDS = [
133
133
  { name: 'keysOnly', type: 'boolean', description: '只返回数据结构,不返回叶子值' },
134
134
  ],
135
135
  notes: [
136
- '流式请求开始后即进入缓冲区;页面及 App 桥代发的 MTop SSE 都归入 mtop。无法归属当前页面 target 的 Service Worker 请求不在采集范围内。',
136
+ '流式请求开始后即进入缓冲区;页面及 App 桥代发的 协议 SSE 都归入 protocol。无法归属当前页面 target 的 Service Worker 请求不在采集范围内。',
137
137
  ],
138
138
  result: [
139
139
  'nextCursor 用于同类查询续读,hasMore 表示还有数据,gap 表示缓冲区淘汰造成缺口;checkpoint cursor 可分别用于三类查询的初始边界。',
@@ -142,7 +142,7 @@ export const COMMANDS = [
142
142
  ],
143
143
  examples: [
144
144
  `get_requests --payload '{"tabId":12345,"count":5}'`,
145
- `get_requests --payload '{"tabId":12345,"source":"mtop","filter":"cart"}'`,
145
+ `get_requests --payload '{"tabId":12345,"source":"protocol","filter":"cart"}'`,
146
146
  ],
147
147
  },
148
148
  {
@@ -249,7 +249,7 @@ export const COMMANDS = [
249
249
  '大字段可用顶层 @<绝对路径> 文件引用或 --payload-file;文件展开后仍受 Native Messaging 单条消息大小限制。',
250
250
  ],
251
251
  examples: [
252
- `set_mock --payload '{"tabId":12345,"apiName":"mtop.cart.query","mockData":{"ret":["SUCCESS::调用成功"]}}'`,
252
+ `set_mock --payload '{"tabId":12345,"apiName":"cart.query","mockData":{"ret":["SUCCESS::调用成功"]}}'`,
253
253
  `set_mock --payload-file ./mock-data.json`,
254
254
  ],
255
255
  },
@@ -303,7 +303,7 @@ export const COMMANDS = [
303
303
  ],
304
304
  notes: [
305
305
  '扩展模式规则持久化;CDP 仅支持会话级 modifyHeaders 请求头 set,不支持响应头、initiatorDomains 或其他动作。Provider 是否支持以其能力声明为准。',
306
- '扩展模式会检查规则请求 URL 和显式来源域名的访问权限;缺失时仍保存规则,返回 warnings 和 missingOrigins,不自动弹窗。可在请求规则页面授权,或按提示调用 request_domain_permission。复杂 URL 表达式无法完整推断域名时会提示手动检查。',
306
+ '扩展模式会检查规则请求 URL 和显式来源域名的访问权限;缺失时仍保存规则,返回 warnings 和 missingOrigins,不自动弹窗。可在请求规则页面授权,或按提示调用 request_permission。复杂 URL 表达式无法完整推断域名时会提示手动检查。',
307
307
  ],
308
308
  examples: [
309
309
  `set_rule --payload '{"actionType":"block","filter":"^https://example\\\\.com/ads/.*"}'`,
@@ -369,32 +369,28 @@ export const COMMANDS = [
369
369
  ],
370
370
  },
371
371
  {
372
- name: 'send_mtop_request',
373
- description: '使用目标浏览器 Cookie 发起 mtop 请求,自动处理签名和 token',
372
+ name: 'send_protocol_request',
373
+ description: '通过已安装的请求协议能力,使用目标浏览器 Cookie 发送请求',
374
374
  params: [
375
- { name: 'api', type: 'string', required: true, description: 'mtop API 名称' },
376
- { name: 'data', type: 'object', description: '请求数据' },
377
- { name: 'version', type: 'string', description: 'API 版本,默认 1.0' },
378
- { name: 'method', type: 'string', description: 'HTTP 方法,默认 GET' },
379
- { name: 'appKey', type: 'string', description: '签名使用的 appKey,默认 12574478' },
380
- { name: 'env', type: '"prod" | "pre"', description: '请求环境,默认 prod' },
381
- { name: 'timeout', type: 'number', description: '超时毫秒数,默认 10000' },
375
+ { name: 'timeout', type: 'number', description: '宿主超时毫秒数;协议能力负责校验具体参数' },
382
376
  ],
383
377
  notes: [
384
- '需要目标浏览器有对应登录态;Mearl 自动处理签名,不依赖页面脚本。api 以 mopen. 开头时使用 mopen 协议。',
385
- 'env=prod/pre 的网关由已安装的请求协议能力提供。',
386
- ],
387
- result: ['返回 success、response、requestData;业务成功需检查 response.ret。'],
388
- examples: [
389
- `send_mtop_request --payload '{"api":"mtop.trade.order.detail","data":{"orderId":"123"}}'`,
378
+ '需要安装提供 request.protocol 的能力包;参数、签名、网关与响应协议由能力包定义。',
379
+ '使用 --payload 传递协议文档声明的参数;核心不设置业务参数默认值。',
390
380
  ],
381
+ result: ['返回 success、response、requestData;业务结果由协议定义。'],
382
+ examples: [`send_protocol_request --payload '{"operation":"example","data":{}}'`],
391
383
  },
392
384
  // ── 浏览器 ────────────────────────────────────────────────────────────────
393
385
  {
394
386
  name: 'browser_list',
395
387
  description: '列出所有浏览器及其连接状态、类型和 Provider 托管实例信息',
396
388
  summary: '列出本地及远端浏览器与连接状态',
397
- examples: ['browser_list'],
389
+ params: [
390
+ { name: 'provider', type: 'string', description: '配合 launchForm,仅查询一个本机已安装 Provider 的启动候选;需 --local' },
391
+ { name: 'launchForm', type: 'object', description: '现有动态启动表单请求:{variant?,values:{}},不创建或接入实例' },
392
+ ],
393
+ examples: ['browser_list', 'browser_list --local'],
398
394
  },
399
395
  {
400
396
  name: 'browser_release',
@@ -501,6 +497,9 @@ export const COMMANDS = [
501
497
  name: 'tab_open',
502
498
  description: '打开或按 URL 规则复用 Tab,并等待新页面加载完成',
503
499
  aliases: ['tab_new', 'new_tab', 'tab_create', 'create_tab'],
500
+ aliasExamples: {
501
+ tab_activate: `tab_open --payload '{"url":"https://example.com","reuse":"require","active":true}'`,
502
+ },
504
503
  params: [
505
504
  { name: 'url', type: 'string', required: true, description: '要打开的 URL' },
506
505
  {
@@ -546,7 +545,7 @@ export const COMMANDS = [
546
545
  {
547
546
  name: 'appProfile',
548
547
  type: 'string',
549
- description: '按已安装档案应用 App 环境,ID 与限制通过 get_capabilities 查询。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true',
548
+ description: '按已安装档案应用 App 环境,ID 与限制见当前动态帮助。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true',
550
549
  },
551
550
  {
552
551
  name: 'os',
@@ -618,6 +617,9 @@ export const COMMANDS = [
618
617
  {
619
618
  name: 'page_click',
620
619
  description: '点击页面元素,支持 CSS/@ref、可见文本或视口坐标定位',
620
+ aliasExamples: {
621
+ page_select: `page_click --payload '{"tabId":12345,"text":"选项文案","role":"option"}'`,
622
+ },
621
623
  params: [
622
624
  {
623
625
  name: 'selector',
@@ -671,7 +673,8 @@ export const COMMANDS = [
671
673
  result: PAGE_ACTION_RESULT,
672
674
  notes: [
673
675
  'selector / text / point 必须且只能提供一个。隐藏页 auto 使用 DOM;只有明确依赖真实用户手势时才激活页面。',
674
- '原生 select 可按 option 文本点击;checkbox/radio 返回 controlState.checkedBefore/checkedAfter。',
676
+ 'auto 对原生 option、checkbox/radio 使用状态感知的 DOM 路径;显式 mouse/touch 保留可信输入。',
677
+ '原生 select 可按 option 文本点击;auto/dom 返回 selectionState.valueBefore/valueAfter/selectedAfter,检查选中状态是否保持。checkbox/radio 返回 controlState.checkedBefore/checkedAfter。',
675
678
  ],
676
679
  examples: [
677
680
  `page_click --payload '{"tabId":12345,"text":"提交"}'`,
@@ -726,6 +729,9 @@ export const COMMANDS = [
726
729
  {
727
730
  name: 'page_type',
728
731
  description: '向输入框填写文本,兼容 React 受控组件',
732
+ aliasExamples: {
733
+ page_fill: `page_type --payload '{"tabId":12345,"selector":"@e6","text":"填写内容","clearFirst":true}'`,
734
+ },
729
735
  params: [
730
736
  {
731
737
  name: 'selector',
@@ -911,6 +917,8 @@ export const COMMANDS = [
911
917
  description: '按键名称:Enter/Tab/Escape/Backspace 等',
912
918
  },
913
919
  { name: 'modifiers', type: 'string[]', description: '修饰键数组:ctrl/alt/shift/meta/cmd' },
920
+ { name: 'dialogId', type: 'string', description: '原生弹窗回执或 page_snapshot 中的 dialog.id;仅配合 Enter 接受或 Escape 取消' },
921
+ { name: 'promptText', type: 'string', description: '仅用于 dialogId + Enter 接受原生 prompt 时填写文本' },
914
922
  {
915
923
  name: 'pressMode',
916
924
  type: '"auto" | "dom" | "keyboard"',
@@ -922,6 +930,7 @@ export const COMMANDS = [
922
930
  ],
923
931
  notes: [
924
932
  '隐藏页 DOM 模式支持 Tab 焦点移动、原生控件 Enter/Space 激活和输入框 Enter 提交;编辑、选择、方向键和页面滚动等原生行为需可见页与 pressMode=keyboard。',
933
+ '原生 alert/confirm/prompt/beforeunload 弹窗需显式传 dialogId;普通 Enter 不处理弹窗。弹窗处理绕过 DOM 观察,不自动重放被中断的动作;处理后重新读取页面。',
925
934
  ],
926
935
  result: [
927
936
  ...PAGE_ACTION_RESULT,
@@ -961,6 +970,7 @@ export const COMMANDS = [
961
970
  ],
962
971
  notes: [
963
972
  'time / selector / text / condition 必须且只能提供一个;time 范围 0–60000ms。页面交互已有内置观察,仅在需要独立等待条件时使用。',
973
+ '等待后须立即执行已知动作时,用 mearl run 在同一脚本中 await pageWait 后紧接 pageClick,避免模型往返。整体 --timeout 须覆盖等待及后续动作;等待失败不继续点击。',
964
974
  ],
965
975
  examples: [
966
976
  `page_wait --payload '{"tabId":12345,"time":1000}'`,
@@ -973,6 +983,9 @@ export const COMMANDS = [
973
983
  {
974
984
  name: 'page_navigate',
975
985
  description: '在当前标签页内导航到 URL、刷新页面,或沿浏览历史后退/前进',
986
+ aliasExamples: {
987
+ page_reload: `page_navigate --payload '{"tabId":12345,"refresh":true}'`,
988
+ },
976
989
  params: [
977
990
  {
978
991
  name: 'url',
@@ -1172,14 +1185,14 @@ export const COMMANDS = [
1172
1185
  },
1173
1186
  {
1174
1187
  name: 'set_app_profile',
1175
- description: '设置已安装的 App 档案;完整 JSBridge 由能力包提供。可同时设置 MTop 页面接口环境,传 emulation 时联动设备模拟',
1188
+ description: '设置已安装的 App 档案;完整 JSBridge 由能力包提供。可同时设置已安装的网络控件,传 emulation 时联动设备模拟',
1176
1189
  summary: '设置或清除目标标签页的 App 容器模拟',
1177
1190
  params: [
1178
1191
  { name: 'enabled', type: 'boolean', required: true, description: 'true=启用,false=清除' },
1179
1192
  {
1180
1193
  name: 'profile',
1181
1194
  type: 'string',
1182
- description: '已安装的容器档案 ID(enabled=true 时必填);通过 get_capabilities 查询当前浏览器目录',
1195
+ description: '已安装的容器档案 ID(enabled=true 时必填);当前动态帮助直接展示目标浏览器目录',
1183
1196
  },
1184
1197
  { name: 'os', type: '"ios" | "android"', description: '容器系统,默认 ios' },
1185
1198
  {
@@ -1204,9 +1217,9 @@ export const COMMANDS = [
1204
1217
  description: '声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本',
1205
1218
  },
1206
1219
  {
1207
- name: 'mtopEnv',
1208
- type: '"default" | "prod" | "pre"',
1209
- description: '可选 MTop 页面接口环境;default 关闭环境改写,省略时保持当前设置。仅作用于目标 tab',
1220
+ name: 'networkControl',
1221
+ type: 'object | null',
1222
+ description: '可选已安装网络控件 {id,value};null 清除,省略时保持当前设置。仅作用于目标 tab',
1210
1223
  },
1211
1224
  {
1212
1225
  name: 'emulation',
@@ -1222,12 +1235,12 @@ export const COMMANDS = [
1222
1235
  'bridgeConfig.methods 按方法声明 behavior / response / noop 三选一,可传 silent;只支持受控行为、固定 JSON 回包或成功空实现,不执行配置脚本。',
1223
1236
  ],
1224
1237
  result: [
1225
- '返回 enabled、profile、label、userAgent、os、mtopEnv、reloaded;联动设备时额外返回实际 emulation。',
1238
+ '返回 enabled、profile、label、userAgent、os、networkControl、reloaded;联动设备时额外返回实际 emulation。',
1226
1239
  ],
1227
1240
  examples: [
1228
1241
  `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader"}'`,
1229
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","mtopEnv":"pre"}'`,
1230
- `set_app_profile --payload '{"tabId":12345,"enabled":false,"mtopEnv":"prod"}'`,
1242
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","networkControl":{"id":"environment","value":"pre"}}'`,
1243
+ `set_app_profile --payload '{"tabId":12345,"enabled":false,"networkControl":null}'`,
1231
1244
  `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","emulation":{}}'`,
1232
1245
  `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"wallet","emulation":{"preset":"pixel-11","deviceShell":true}}'`,
1233
1246
  `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","emulation":{"preset":"ipad-mini-a17-pro","orientation":"landscape","deviceShell":true,"splitScreen":true}}'`,
@@ -1347,7 +1360,7 @@ export const COMMANDS = [
1347
1360
  examples: [`page_frames --payload '{"tabId":12345}'`],
1348
1361
  },
1349
1362
  {
1350
- name: 'request_domain_permission',
1363
+ name: 'request_permission',
1351
1364
  description: '申请访问指定域名的权限(必要时弹窗让用户授权)',
1352
1365
  params: [
1353
1366
  {
@@ -1362,7 +1375,7 @@ export const COMMANDS = [
1362
1375
  '弹窗等待用户选择,90 秒超时或关闭按拒绝处理;granted=true 后才重试原请求。',
1363
1376
  ],
1364
1377
  result: ['返回 granted、domain,授权成功时包含 origin。'],
1365
- examples: [`request_domain_permission --payload '{"domain":"example.com"}'`],
1378
+ examples: [`request_permission --payload '{"domain":"example.com"}'`],
1366
1379
  },
1367
1380
  // ── 组合调用 ────────────────────────────────────────────────────────────────
1368
1381
  {
package/dist/index.d.ts CHANGED
@@ -13,7 +13,7 @@ export { UnifiedClient, UnifiedClient as MearlClient, decodeBrowserTarget, invok
13
13
  export type { UnifiedClientOptions, UnifiedInvokeOptions, UnifiedInvokeOptions as InvokeOptions, } from './unified.js';
14
14
  export type { PluginInvocation, PluginInvokeOptions, PluginProgress, StructuredPluginProgress, } from './plugins/sdk/plugin.js';
15
15
  export { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, BROWSER_ACTIONS, BROWSER_CAPABILITY_ACTIONS, BROWSER_COMMAND_ACTIONS, EXTENSION_BROWSER_ACTIONS, PAGE_ACT_ACTIONS, SEND_REQUEST_MAX_FILES, SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES, SEND_REQUEST_MAX_TOTAL_FILE_BYTES, isBrowserAction, isBrowserCapabilityAction, isBrowserCommandAction, isExtensionBrowserAction, } from '@mearl/browser-core';
16
- export type { ActionStep, AppProfileName, BrowserAction, BrowserActionMap, BrowserActionRequest, BrowserActionResponse, BrowserCapabilities, BrowserCapabilityAction, BrowserCloseParams, BrowserCommandAction, BrowserInfo, BrowserInfoBase, BrowserJsonValue, BrowserLaunchParams, BrowserProviderOperation, BrowserListResult, BrowserPairingInfo, BrowserReleaseFailure, BrowserReleaseResult, BrowserProviderDefinition, BrowserProviderIdentity, BrowserProviderInputSchema, BrowserProviderListResult, BrowserReference, BrowserDetails, BrowserSource, BrowserStatus, BrowserScreenshotKind, BrowserTransport, CaptureCheckpointParams, CaptureCheckpointResult, BrowserDevicePresetName, BrowserEmulationOptions, ExtensionBrowserAction, GetCookieParams, GetCookieResult, GetEventsParams, GetLogsParams, GetMocksParams, GetRequestsParams, ManagedBrowserInfo, PageClickParams, PageDragEndpoint, PageDragParams, PageDragPoint, PageActAction, PageDiagnosticsOptions, PageDiagnosticsSetting, PageEvalParams, PageFramesParams, PageHoverParams, PageInspectFilter, PageInspectParams, PageNavigateParams, PageObservationWaitOptions, PagePressParams, PageScreenshotParams, PageScrollParams, PageSnapshotParams, PageSnapshotQuery, PageTypeParams, PageUploadParams, PageWaitParams, RegularBrowserInfo, RequestDomainPermissionParams, RulesResult, RunActionName, RunActionsParams, SelectBrowserParams, SelectBrowserResult, SendRequestFile, SendRequestBinaryBody, SendRequestFormValue, SendMtopRequestParams, SendRequestParams, SetAppProfileParams, SetCookieParams, SetCookieResult, SetDeviceEmulationParams, SetMockParams, SetRuleParams, SetTimezoneParams, SetTimezoneResult, TabCloseParams, TabOpenParams, } from '@mearl/browser-core';
16
+ export type { ActionStep, AppProfileName, BrowserAction, BrowserActionMap, BrowserActionRequest, BrowserActionResponse, BrowserCapabilities, BrowserCapabilityAction, BrowserCloseParams, BrowserCommandAction, BrowserInfo, BrowserInfoBase, BrowserJsonValue, BrowserLaunchParams, BrowserProviderOperation, BrowserListResult, BrowserListParams, BrowserPairingInfo, BrowserReleaseFailure, BrowserReleaseResult, BrowserProviderDefinition, BrowserProviderIdentity, BrowserProviderInputSchema, BrowserProviderListResult, BrowserReference, BrowserDetails, BrowserSource, BrowserStatus, BrowserScreenshotKind, BrowserTransport, CaptureCheckpointParams, CaptureCheckpointResult, BrowserDevicePresetName, BrowserEmulationOptions, ExtensionBrowserAction, GetCookieParams, GetCookieResult, GetEventsParams, GetLogsParams, GetMocksParams, GetRequestsParams, ManagedBrowserInfo, PageClickParams, PageDragEndpoint, PageDragParams, PageDragPoint, PageActAction, PageDiagnosticsOptions, PageDiagnosticsSetting, PageEvalParams, PageFramesParams, PageHoverParams, PageInspectFilter, PageInspectParams, PageNavigateParams, PageObservationWaitOptions, PagePressParams, PageScreenshotParams, PageScrollParams, PageSnapshotParams, PageSnapshotQuery, PageTypeParams, PageUploadParams, PageWaitParams, RegularBrowserInfo, RequestPermissionParams, RulesResult, RunActionName, RunActionsParams, SelectBrowserParams, SelectBrowserResult, SendRequestFile, SendRequestBinaryBody, SendRequestFormValue, SendProtocolRequestParams, SendRequestParams, SetAppProfileParams, SetCookieParams, SetCookieResult, SetDeviceEmulationParams, SetMockParams, SetRuleParams, SetTimezoneParams, SetTimezoneResult, TabCloseParams, TabOpenParams, } from '@mearl/browser-core';
17
17
  import { type UnifiedInvokeOptions } from './unified.js';
18
18
  import type * as Protocol from '@mearl/browser-core';
19
19
  export declare function tabCheckpoint(params: Protocol.CaptureCheckpointParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_checkpoint'>>;
@@ -25,8 +25,9 @@ export declare function getEvents(params: Protocol.GetEventsParams, options?: Un
25
25
  export declare function setMock(params: Protocol.SetMockParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'set_mock'>>;
26
26
  export declare function getMocks(params: Protocol.GetMocksParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'get_mocks'>>;
27
27
  export declare function sendRequest(params: Protocol.SendRequestParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'send_request'>>;
28
- export declare function sendMtopRequest(params: Protocol.SendMtopRequestParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'send_mtop_request'>>;
28
+ export declare function sendProtocolRequest(params: Protocol.SendProtocolRequestParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'send_protocol_request'>>;
29
29
  export declare function browserList(options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_list'>>;
30
+ export declare function browserList(params: Protocol.BrowserListParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_list'>>;
30
31
  export declare function selectBrowser(params?: Protocol.SelectBrowserParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_select_browser'>>;
31
32
  export declare function browserRelease(options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_release'>>;
32
33
  export declare function browserRelease(params: Protocol.BrowserReleaseParams, options: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_release'>>;
package/dist/index.js CHANGED
@@ -36,11 +36,13 @@ export function getMocks(params, options) {
36
36
  export function sendRequest(params, options) {
37
37
  return invoke('send_request', params, options);
38
38
  }
39
- export function sendMtopRequest(params, options) {
40
- return invoke('send_mtop_request', params, options);
39
+ export function sendProtocolRequest(params, options) {
40
+ return invoke('send_protocol_request', params, options);
41
41
  }
42
- export function browserList(options) {
43
- return invoke('browser_list', {}, options);
42
+ export function browserList(paramsOrOptions = {}, options) {
43
+ return 'provider' in paramsOrOptions || 'launchForm' in paramsOrOptions
44
+ ? invoke('browser_list', paramsOrOptions, options)
45
+ : invoke('browser_list', {}, paramsOrOptions);
44
46
  }
45
47
  export function selectBrowser(params = {}, options) {
46
48
  return invoke('browser_select_browser', params, options);
@@ -2,6 +2,7 @@ import { closeSync, mkdirSync, openSync, rmSync, statSync, writeFileSync } from
2
2
  import { CLIENT_VERSION } from '../runtimePaths.js';
3
3
  import { join } from 'node:path';
4
4
  import { spawn } from 'node:child_process';
5
+ import { npmExecEnvironment } from '@mearl/daemon-core';
5
6
  import { findOfficialPlugin, readOfficialPluginCatalog } from './officialPluginCatalog.js';
6
7
  import { findPluginRegistration, pluginDirectory } from './pluginRegistry.js';
7
8
  const INSTALL_TIMEOUT_MS = 180_000;
@@ -60,7 +61,7 @@ function appendOutput(current, chunk) {
60
61
  function installWithSetup(name) {
61
62
  return new Promise((resolve, reject) => {
62
63
  const command = process.platform === 'win32' ? 'npx.cmd' : 'npx';
63
- const child = spawn(command, ['-y', '--@mearl:registry=https://registry.npmjs.org', `@mearl/setup@${CLIENT_VERSION}`, 'plugin', 'ensure', name], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
64
+ const child = spawn(command, ['-y', '--@mearl:registry=https://registry.npmjs.org', `@mearl/setup@${CLIENT_VERSION}`, 'plugin', 'ensure', name], { env: npmExecEnvironment(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
64
65
  let stdout = '';
65
66
  let stderr = '';
66
67
  let settled = false;
@@ -884,7 +884,7 @@ export class PluginTaskManager {
884
884
  });
885
885
  }
886
886
  if (restrictedTarget &&
887
- !['send_request', 'send_mtop_request', 'browser_list', 'connector_list'].includes(action))
887
+ !['send_request', 'send_protocol_request', 'browser_list', 'connector_list'].includes(action))
888
888
  throw error('PLUGIN_PAGE_TARGET_CHANGED', 'Runtime tasks must access the document through context.page');
889
889
  this.assertRouting(options, request.options);
890
890
  if (client) {
package/dist/script.js CHANGED
@@ -208,7 +208,8 @@ export async function executeScript(code, options = {}) {
208
208
  session: sessionId,
209
209
  ...args[2],
210
210
  });
211
- const optionsOnly = OPTIONS_ONLY.has(method) || (method === 'browserRelease' && args.length < 2);
211
+ const optionsOnly = (OPTIONS_ONLY.has(method) && !(method === 'browserList' && args[0] &&
212
+ ('provider' in args[0] || 'launchForm' in args[0]))) || (method === 'browserRelease' && args.length < 2);
212
213
  return invoke(METHOD_ACTIONS.get(method), optionsOnly ? {} : (args[0] ?? {}), optionsOnly ? args[0] : args[1]);
213
214
  })
214
215
  .then(result => send({ type: 'response', id, result, session }), error => send({ type: 'response', id, error: serializeError(error), session }))
package/dist/socket.js CHANGED
@@ -13,13 +13,13 @@ import os from 'node:os';
13
13
  import path from 'node:path';
14
14
  import { CLIENT_VERSION } from './runtimePaths.js';
15
15
  import { StringDecoder } from 'node:string_decoder';
16
- import { ensureProviderControlHost, listInstalledProviders } from '@mearl/provider/host';
17
- import { MEARL_PROVIDER_PROTOCOL_VERSION } from '@mearl/provider';
18
- import { readLiveProvider } from '@mearl/provider/runtime';
16
+ import { assertProviderControlSessionMode, ensureProviderControlHost, listInstalledProviders } from '@mearl/provider/host';
17
+ import { MEARL_PROVIDER_PROTOCOL_VERSION, assertProviderProtocolVersion, validateProviderLaunchForm } from '@mearl/provider';
18
+ import { readLiveProvider, ensureInstalledProvidersRunning } from '@mearl/provider/runtime';
19
19
  import { resolveFileRefs } from './fileRefs.js';
20
20
  import { listLiveBrowsers, findBrowser, describeBrowser, } from './browserRegistry.js';
21
21
  import { resolveActionTimeoutSec } from './actionTimeouts.js';
22
- import { isBrowserCapabilityAction } from '@mearl/browser-core';
22
+ import { isBrowserCapabilityAction, parseBrowserListParams } from '@mearl/browser-core';
23
23
  export { CLIENT_VERSION };
24
24
  const MAX_SOCKET_BUFFER_SIZE = 64 * 1024 * 1024;
25
25
  /**
@@ -376,6 +376,29 @@ export function invoke(action, data = {}, options = {}) {
376
376
  const resolvedData = resolveFileRefs(data);
377
377
  Object.defineProperty(resolvedData, SESSION_OPTION, { value: options.controlSessionId });
378
378
  if (action === 'browser_list') {
379
+ const discovery = parseBrowserListParams(resolvedData);
380
+ if (discovery) {
381
+ return (async () => {
382
+ if (transport || options.socketPath || controlSource !== 'local' || options.controlSessionId)
383
+ throw new Error('Provider discovery requires the local source without a transport, socket or session override');
384
+ const provider = listInstalledProviders().find(provider => provider.providerId === discovery.provider);
385
+ if (!provider?.browserType.dynamicLaunchForm)
386
+ throw new Error(`Provider does not support dynamic launch forms: ${discovery.provider}`);
387
+ const warnings = await ensureInstalledProvidersRunning([discovery.provider]);
388
+ if (warnings.length)
389
+ throw new Error(warnings.join('; '));
390
+ const live = readLiveProvider(discovery.provider);
391
+ if (!live)
392
+ throw new Error(`Provider did not start: ${discovery.provider}`);
393
+ assertProviderProtocolVersion(live.providerProtocolVersion, discovery.provider);
394
+ const form = await invokeOnce('browser_providers', { operation: 'launch_form', ...discovery.launchForm }, timeoutSec, undefined, live.socketPath, 'local');
395
+ validateProviderLaunchForm(form);
396
+ return decorateBrowserListResult({
397
+ ...browserListFromProviderRecords(listLiveBrowsers().filter(browser => browser.provider?.id === discovery.provider)),
398
+ discovery: { provider: discovery.provider, launchForm: form },
399
+ });
400
+ })();
401
+ }
379
402
  return dispatchInvoke(action, resolvedData, timeoutSec, transport, undefined, controlSource, options.socketPath).then(result => decorateBrowserListResult(transport === 'cdp' ? cdpOnlyBrowserList(result) : result));
380
403
  }
381
404
  const selector = browser ?? defaultBrowser ?? process.env.MEARL_BROWSER ?? undefined;
@@ -510,7 +533,7 @@ async function dispatchInvoke(action, data, timeoutSec, transport, selector, con
510
533
  throw new Error(`Browser ${describeBrowser(match)} does not support ${action}`);
511
534
  }
512
535
  try {
513
- return await invokeOnce(action, data, timeoutSec, transport, match.socketPath, controlSource, match.transport === 'provider' ? match.browserId : undefined);
536
+ return await invokeOnce(action, data, timeoutSec, transport, match.socketPath, controlSource, match.transport === 'provider' ? match.browserId : undefined, match.capabilities?.controlSessionMode);
514
537
  }
515
538
  catch (error) {
516
539
  if (isConnError(error) || isSocketSandboxError(error)) {
@@ -527,7 +550,7 @@ async function dispatchInvoke(action, data, timeoutSec, transport, selector, con
527
550
  if (candidates.length > 0) {
528
551
  const target = candidates[0];
529
552
  try {
530
- return await invokeOnce(action, data, timeoutSec, transport, target.socketPath, controlSource, target.transport === 'provider' ? target.browserId : undefined);
553
+ return await invokeOnce(action, data, timeoutSec, transport, target.socketPath, controlSource, target.transport === 'provider' ? target.browserId : undefined, target.capabilities?.controlSessionMode);
531
554
  }
532
555
  catch (error) {
533
556
  if (isSocketSandboxError(error)) {
@@ -575,7 +598,7 @@ async function invokeViaLegacy(action, data, timeoutSec, transport, controlSourc
575
598
  !(transport === 'cdp' && browser.transport === 'provider') &&
576
599
  supportsAction(browser, action, data));
577
600
  if (target) {
578
- return invokeOnce(action, data, timeoutSec, transport, target.socketPath, controlSource, target.transport === 'provider' ? target.browserId : undefined);
601
+ return invokeOnce(action, data, timeoutSec, transport, target.socketPath, controlSource, target.transport === 'provider' ? target.browserId : undefined, target.capabilities?.controlSessionMode);
579
602
  }
580
603
  return invokeViaLegacy(action, data, timeoutSec, transport, controlSource,
581
604
  /* isRetry */ true, localOnly);
@@ -586,9 +609,10 @@ async function invokeViaLegacy(action, data, timeoutSec, transport, controlSourc
586
609
  throw new Error(formatConnectError(err, SOCKET_PATH), { cause: error });
587
610
  }
588
611
  }
589
- async function invokeOnce(action, data, timeoutSec, transport, socketPath, controlSource, providerTarget) {
612
+ async function invokeOnce(action, data, timeoutSec, transport, socketPath, controlSource, providerTarget, controlSessionMode) {
590
613
  if (providerTarget) {
591
614
  const host = await ensureProviderControlHost(Math.min(timeoutSec * 1000, 10_000));
615
+ assertProviderControlSessionMode(host, controlSessionMode);
592
616
  socketPath = host.socketPath;
593
617
  }
594
618
  const id = `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
package/dist/unified.js CHANGED
@@ -3,7 +3,7 @@ import { stageFile, exportArtifact } from './artifacts.js';
3
3
  import WebSocket from 'ws';
4
4
  import { DEFAULT_CONNECT_TIMEOUT, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_REQUEST_TIMEOUT, MAX_BUFFER_SIZE, isHeartbeatMessage, resolveCloudConnectorSelector, } from '@mearl/cloud-types';
5
5
  import { resolveActionTimeoutSec } from './actionTimeouts.js';
6
- import { validateRequiredActionTabId, } from '@mearl/browser-core';
6
+ import { validateRequiredActionTabId, parseBrowserListParams, } from '@mearl/browser-core';
7
7
  import { invoke as invokeLocalBrowser, resolveLocalActionTarget } from './socket.js';
8
8
  import { loadCloudServerConfig, loadLocalConnectorId } from './unifiedConfig.js';
9
9
  const RESPONSE_GRACE_MS = 1_000;
@@ -146,6 +146,9 @@ export class UnifiedClient {
146
146
  }, options);
147
147
  }
148
148
  async invoke(action, data = {}, options = {}) {
149
+ const discovery = action === 'browser_list' ? parseBrowserListParams(data) : undefined;
150
+ if (discovery && (!options.local || options.connector || options.transport || options.session || options.controlSessionId))
151
+ throw new Error('Provider discovery requires explicit --local without connector, transport, socket or session overrides');
149
152
  if (action === 'plugin_state') {
150
153
  const { PluginStateClient } = await import('./plugins/clients/pluginState.js');
151
154
  const state = new PluginStateClient();
@@ -350,6 +353,12 @@ export class UnifiedClient {
350
353
  if (action === 'connector_list')
351
354
  return this.listConnectors(timeoutSec);
352
355
  if (action === 'browser_list') {
356
+ if (parseBrowserListParams(data)) {
357
+ const result = await invokeLocalBrowser('browser_list', data, { timeoutSec, controlSource: 'local' });
358
+ if (!result?.discovery)
359
+ throw new Error('Provider launch-form discovery is not supported by this host');
360
+ return this.applyConfiguredDefault(this.decorateListing(result, { kind: 'local' }));
361
+ }
353
362
  if (options.local || options.transport === 'cdp') {
354
363
  return this.listLocalBrowsers(timeoutSec, options.transport);
355
364
  }
@@ -543,6 +552,7 @@ export class UnifiedClient {
543
552
  })
544
553
  : null,
545
554
  browsers,
555
+ ...(result?.discovery ? { discovery: result.discovery } : {}),
546
556
  ...(resultWarnings.length > 0 ? { warnings: resultWarnings } : {}),
547
557
  };
548
558
  }
@@ -6,3 +6,7 @@ export declare function loadCliAutoUpdateIntervalDays(configPath?: string): numb
6
6
  export declare function getCloudServerConfigPath(): string;
7
7
  export declare function loadCloudServerConfig(): CloudServerConfig | null;
8
8
  export declare function loadLocalConnectorId(): string | null;
9
+ export declare function resolveServer(explicit?: string): {
10
+ serverUrl?: string;
11
+ serverUrlSource?: string;
12
+ };
@@ -56,3 +56,14 @@ export function loadLocalConnectorId() {
56
56
  return null;
57
57
  }
58
58
  }
59
+ export function resolveServer(explicit) {
60
+ if (explicit)
61
+ return { serverUrl: explicit, serverUrlSource: '--server' };
62
+ if (process.env.MEARL_SERVER_URL) {
63
+ return { serverUrl: process.env.MEARL_SERVER_URL, serverUrlSource: 'MEARL_SERVER_URL' };
64
+ }
65
+ const config = loadCloudServerConfig();
66
+ return config?.server
67
+ ? { serverUrl: config.server, serverUrlSource: getCloudServerConfigPath() }
68
+ : {};
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/client",
3
- "version": "3.0.0-beta.5",
3
+ "version": "3.0.0-beta.7",
4
4
  "description": "Unified Mearl SDK & CLI for local and remote browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -58,20 +58,20 @@
58
58
  ],
59
59
  "license": "Apache-2.0",
60
60
  "engines": {
61
- "node": ">=22.12.0"
61
+ "node": ">=20.18.0"
62
62
  },
63
63
  "publishConfig": {
64
64
  "registry": "https://registry.npmjs.org"
65
65
  },
66
66
  "dependencies": {
67
- "commander": "^15.0.0",
67
+ "commander": "^14.0.3",
68
68
  "fastest-levenshtein": "^1.0.16",
69
69
  "ws": "^8.18.0",
70
- "@mearl/provider": "3.0.0-beta.5",
71
- "@mearl/agent-runtime": "3.0.0-beta.5",
72
- "@mearl/browser-core": "3.0.0-beta.5",
73
- "@mearl/cloud-types": "3.0.0-beta.5",
74
- "@mearl/daemon-core": "3.0.0-beta.5"
70
+ "@mearl/daemon-core": "3.0.0-beta.7",
71
+ "@mearl/cloud-types": "3.0.0-beta.7",
72
+ "@mearl/provider": "3.0.0-beta.7",
73
+ "@mearl/agent-runtime": "3.0.0-beta.7",
74
+ "@mearl/browser-core": "3.0.0-beta.7"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@types/node": "^24.9.1",
@@ -80,7 +80,7 @@
80
80
  },
81
81
  "repository": {
82
82
  "type": "git",
83
- "url": "git+https://github.com/F-loat/mearl.git",
83
+ "url": "git+https://github.com/mearlhq/mearl.git",
84
84
  "directory": "packages/client"
85
85
  },
86
86
  "scripts": {