@mearl/client 2.8.2 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,6 +69,7 @@ mearl browser_launch --payload '{"name":"account-b","query":"test_account"}'
69
69
  mearl browser_launch --payload '{"name":"cookie-copy","copyCookieDomains":["example.com"]}'
70
70
  mearl browser_launch --payload '{"name":"cloud-login","provider":"agentbay","persistent":true,"userAgentMode":"desktop","copyCookieDomains":["example.com"],"url":"https://example.com/account"}'
71
71
  mearl browser_launch --browser '<控制浏览器 browserId>' --payload '{"name":"cloud-login","provider":"agentbay","reuse":"require","copyCookieDomains":["example.com"]}'
72
+ mearl browser_launch --payload '{"name":"cloud-tools","provider":"agentbay","copyCookieDomains":"mearl-services"}'
72
73
  mearl browser_launch --payload '{"name":"figma","userAgentMode":"desktop","copyCookieDomains":["figma.com"]}'
73
74
  mearl browser_list
74
75
  mearl browser_release
@@ -111,13 +112,13 @@ mearl browser_release
111
112
 
112
113
  `page_snapshot` 默认返回完整 AX Tree;长列表可传 `mode: "viewport"`,只需要当前视口内的控件时传 `mode: "interactive"`,已知 CSS 区域时传 `rootSelector`,已有 ref 时传 `rootRef`(可用 `ancestorDepth` 向上补充上下文),只查找特定文案或角色时传 `query`。视口内缺少 AX 控件语义时,`interactive` 会自动回退到 viewport,并返回 `fallbackMode: "viewport"`。`maxNodes` / `maxChars` 截断会同时保留首尾内容。
113
114
 
114
- 重复文本点击可用 `page_click.scope` 限定 CSS / ref 子树;ref 指向滚动容器内的子节点时,可用 `page_scroll.containerPolicy: "nearest"` 自动解析最近可滚动祖先。点击结果的 `resolvedTarget` 和滚动结果的前后位置、边界字段可用于诊断实际目标与效果。
115
+ 重复文本点击可用 `page_click.scope` 限定 CSS / ref 子树;浏览器扩展后端中,容器或 iframe 内的点坐标使用 `coordinateSpace: "active-frame"`,CSS/text 查询可用 `frameId` 显式指定 frame。点击结果通过 `requestedTarget`、`resolvedTarget`、`dispatchTarget`、`hitTarget` 和 `coordinates` 区分请求语义、DOM 解析、派发节点与可信输入实际命中;ref 指向滚动容器内的子节点时,可用 `page_scroll.containerPolicy: "nearest"` 自动解析最近可滚动祖先。
115
116
 
116
117
  `browser_list` 统一列出普通浏览器和托管浏览器。`type` 区分 `regular` / `managed`,`status` 区分 `connected` / `running_disconnected` / `stopped`;只有 `connected` 的浏览器可作为操作目标。
117
118
 
118
119
  `browser_release` 仅在用户明确要求结束调试、退出接管或清理会话资源时使用,不作为普通任务收尾。它会释放当前目标浏览器的 debugger/CDP 控制和会话级临时状态(包括设备/时区模拟),保留浏览器及全部标签页;后续浏览器操作可自动重新建立控制。
119
120
 
120
- 托管浏览器使用独立 Profile。控制浏览器需先登录 TDBank;生成的 SSO 地址在本地内部传递,本地实例可以使用 `headless: true`(默认)完成测试账号登录。AgentBay 固定为非 headless,传 `headless: true` 会被拒绝;`imageId` 可指定镜像别名或具体镜像 ID,省略时使用已验证的 Linux Browser Use 内网镜像。`persistent: true` 会让本地实例保留 Profile,让 AgentBay 同名实例绑定稳定的云端 Browser Context;正常关闭时同步 Cookie、LocalStorage、IndexedDB 等状态,下次启动无需再次复制本地登录态。`deleteProfile: true` 会显式删除对应 Profile 或 Context。AgentBay 的 `reuse` 支持 `never`(默认)、`prefer` 和 `require`:`prefer` 存在运行实例时复用、否则创建,`require` 只复用且不会意外创建计费会话。`copyCookieDomains` 可在首次启动或复用运行实例时把控制浏览器指定域的 Cookie 写入目标,Cookie 值不会经过 cloud-server,也不会出现在命令结果或日志中;`userAgentMode: "desktop"` 让本地实例使用匹配本机 Chrome 版本的桌面 UA,AgentBay 则复用当前控制浏览器的桌面 UA。`browser_list` 仅通过 `agentbayImageId`、`agentbayContextId` 和 `agentbayContextName` 返回 AgentBay 实例的稳定配置。无影浏览器串流入口包含临时访问凭据且会过期,因此不会写入实例记录或列表结果;需要时运行 `mearl check --browser <id|名称>` 实时获取,并按敏感信息处理。完整设计见 [托管浏览器与 TDBank 多账号设计](../../docs/managed-browsers.md)。
121
+ 托管浏览器使用独立 Profile。控制浏览器需先登录 TDBank;生成的 SSO 地址在本地内部传递,本地实例可以使用 `headless: true`(默认)完成测试账号登录。AgentBay 固定为非 headless,传 `headless: true` 会被拒绝;`imageId` 可指定镜像别名或具体镜像 ID,省略时使用已验证的 Linux Browser Use 内网镜像。`persistent: true` 会让本地实例保留 Profile,让 AgentBay 同名实例绑定稳定的云端 Browser Context;正常关闭时同步 Cookie、LocalStorage、IndexedDB 等状态,下次启动无需再次复制本地登录态。`deleteProfile: true` 会显式删除对应 Profile 或 Context。AgentBay 的 `reuse` 支持 `never`(默认)、`prefer` 和 `require`:`prefer` 存在运行实例时复用、否则创建,`require` 只复用且不会意外创建计费会话。`copyCookieDomains` 传数组时把控制浏览器指定域的 Cookie 写入目标,传 `"mearl-services"` 时同步 Mearl 依赖平台登录态。Cookie 值不会经过 cloud-server,也不会出现在命令结果或日志中;`userAgentMode: "desktop"` 让本地实例使用匹配本机 Chrome 版本的桌面 UA,AgentBay 则复用当前控制浏览器的桌面 UA。`browser_list` 仅通过 `agentbayImageId`、`agentbayContextId` 和 `agentbayContextName` 返回 AgentBay 实例的稳定配置。无影浏览器串流入口包含临时访问凭据且会过期,因此不会写入实例记录或列表结果;需要时运行 `mearl check --browser <id|名称>` 实时获取,并按敏感信息处理。完整设计见 [托管浏览器与 TDBank 多账号设计](../../docs/managed-browsers.md)。
121
122
 
122
123
  ## 架构
123
124
 
package/dist/cli.d.ts CHANGED
@@ -2,4 +2,20 @@ export interface MearlCliOptions {
2
2
  commandName?: string;
3
3
  clientVersion?: string;
4
4
  }
5
+ export interface ParsedArgs {
6
+ action: string;
7
+ browser?: string;
8
+ compact: boolean;
9
+ connector?: string;
10
+ forceCdp: boolean;
11
+ json: boolean;
12
+ local: boolean;
13
+ outputPath?: string;
14
+ payload: Record<string, any>;
15
+ serverUrl?: string;
16
+ serverUrlSource?: string;
17
+ timeoutSec: number;
18
+ timeoutOption?: string;
19
+ }
20
+ export declare function parseClientArgs(args: string[]): ParsedArgs;
5
21
  export declare function runMearlCli(args: string[], options?: MearlCliOptions): Promise<number>;
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, writeFileSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
+ import { Command, CommanderError } from 'commander';
3
4
  import { resolveActionTimeoutSec } from './actionTimeouts.js';
4
5
  import { runUnifiedCheck, parseCheckTimeout } from './check.js';
5
6
  import { commandInput, findCommandSuggestions, formatCommandSuggestions, } from './commandSuggestions.js';
@@ -59,10 +60,6 @@ const BUILTIN_COMMANDS = [
59
60
  ];
60
61
  const SUGGESTABLE_COMMANDS = [...COMMANDS, ...BUILTIN_COMMANDS];
61
62
  const ADVANCED_BUILTIN_COMMANDS = new Set(['connector_list']);
62
- function optionValue(args, name) {
63
- const index = args.indexOf(name);
64
- return index >= 0 ? args[index + 1] : undefined;
65
- }
66
63
  function printUsage(commandName, version) {
67
64
  const lines = [
68
65
  `${commandName} v${version}`,
@@ -159,9 +156,7 @@ function parsePositiveTimeout(value, fallback) {
159
156
  }
160
157
  return parsed;
161
158
  }
162
- function parsePayload(args) {
163
- const payloadRaw = optionValue(args, '--payload');
164
- const payloadFile = optionValue(args, '--payload-file');
159
+ function parsePayload(payloadRaw, payloadFile) {
165
160
  if (payloadRaw !== undefined && payloadFile !== undefined) {
166
161
  throw new Error('--payload 与 --payload-file 不能同时使用');
167
162
  }
@@ -183,33 +178,7 @@ function parsePayload(args) {
183
178
  }
184
179
  return {};
185
180
  }
186
- function validateOptions(args) {
187
- const optionsWithValues = new Set([
188
- '--payload',
189
- '--payload-file',
190
- '--timeout',
191
- '--output',
192
- '--browser',
193
- '--connector',
194
- '--server',
195
- ]);
196
- const flags = new Set(['--compact', '--json', '--local', '--cdp', '--help', '-h']);
197
- for (let index = 1; index < args.length; index += 1) {
198
- const arg = args[index];
199
- if (optionsWithValues.has(arg)) {
200
- if (!args[index + 1] || args[index + 1].startsWith('--')) {
201
- throw new Error(`缺少 ${arg} 的参数值`);
202
- }
203
- index += 1;
204
- continue;
205
- }
206
- if (flags.has(arg))
207
- continue;
208
- throw new Error(`未知参数: ${arg}`);
209
- }
210
- }
211
- function resolveServer(args) {
212
- const explicit = optionValue(args, '--server');
181
+ function resolveServer(explicit) {
213
182
  if (explicit)
214
183
  return { serverUrl: explicit, serverUrlSource: '--server' };
215
184
  if (process.env.MEARL_SERVER_URL) {
@@ -220,31 +189,59 @@ function resolveServer(args) {
220
189
  ? { serverUrl: config.server, serverUrlSource: getCloudServerConfigPath() }
221
190
  : {};
222
191
  }
223
- function parseArgs(args) {
224
- validateOptions(args);
225
- const action = args[0];
226
- const json = args.includes('--json');
192
+ export function parseClientArgs(args) {
193
+ const program = new Command()
194
+ .name('mearl')
195
+ .helpOption(false)
196
+ .allowExcessArguments(false)
197
+ .exitOverride()
198
+ .configureOutput({ writeErr: () => { }, writeOut: () => { } })
199
+ .argument('<action>')
200
+ .option('--payload <json>')
201
+ .option('--payload-file <path>')
202
+ .option('--timeout <seconds>')
203
+ .option('--compact')
204
+ .option('--json')
205
+ .option('--output <path>')
206
+ .option('--browser <id-or-name>')
207
+ .option('--connector <id-or-name>')
208
+ .option('--local')
209
+ .option('--cdp')
210
+ .option('--server <url>');
211
+ try {
212
+ program.parse(['node', 'mearl', ...args]);
213
+ }
214
+ catch (error) {
215
+ if (error instanceof CommanderError) {
216
+ throw new Error(error.message.replace(/^error:\s*/i, ''), { cause: error });
217
+ }
218
+ throw error;
219
+ }
220
+ const action = program.processedArgs[0];
221
+ const options = program.opts();
222
+ const json = options.json === true;
227
223
  if (json && action !== 'check')
228
224
  throw new Error('--json 仅适用于 check');
229
- const payload = parsePayload(args);
225
+ const payload = parsePayload(options.payload, options.payloadFile);
230
226
  const fallbackTimeout = resolveActionTimeoutSec(action, payload, 60);
231
- const forceCdp = args.includes('--cdp');
232
- const local = forceCdp || args.includes('--local');
233
- const connector = optionValue(args, '--connector');
227
+ const forceCdp = options.cdp === true;
228
+ const local = forceCdp || options.local === true;
229
+ const connector = options.connector;
234
230
  if (local && connector)
235
231
  throw new Error('--local/--cdp 不能与 --connector 同时使用');
236
232
  return {
237
233
  action,
238
234
  payload,
239
- timeoutSec: parsePositiveTimeout(optionValue(args, '--timeout'), fallbackTimeout),
240
- compact: args.includes('--compact'),
235
+ timeoutSec: parsePositiveTimeout(options.timeout, fallbackTimeout),
236
+ ...(options.timeout !== undefined ? { timeoutOption: options.timeout } : {}),
237
+ compact: options.compact === true,
241
238
  forceCdp,
242
239
  json,
243
240
  local,
244
- ...(optionValue(args, '--output') ? { outputPath: optionValue(args, '--output') } : {}),
245
- ...(optionValue(args, '--browser') ? { browser: optionValue(args, '--browser') } : {}),
241
+ ...(options.output ? { outputPath: options.output } : {}),
242
+ ...(options.browser ? { browser: options.browser } : {}),
246
243
  ...(connector ? { connector } : {}),
247
- ...resolveServer(args),
244
+ ...resolveServer(options.server),
248
245
  };
249
246
  }
250
247
  function screenshotData(action, result) {
@@ -305,14 +302,14 @@ export async function runMearlCli(args, options = {}) {
305
302
  }
306
303
  let parsed;
307
304
  try {
308
- parsed = parseArgs(args);
305
+ parsed = parseClientArgs(args);
309
306
  }
310
307
  catch (error) {
311
308
  console.error(error instanceof Error ? error.message : String(error));
312
309
  return 2;
313
310
  }
314
311
  if (parsed.action === 'check') {
315
- const timeoutSec = parseCheckTimeout(optionValue(args, '--timeout'));
312
+ const timeoutSec = parseCheckTimeout(parsed.timeoutOption);
316
313
  if (timeoutSec === null) {
317
314
  console.error('--timeout 必须是正整数秒');
318
315
  return 2;
@@ -439,10 +439,10 @@ export declare const COMMANDS: readonly [{
439
439
  readonly description: "CDP 控制浏览器中用于读取 TDBank Cookie 的标签页";
440
440
  }, {
441
441
  readonly name: "copyCookieDomains";
442
- readonly type: "string[]";
443
- readonly description: "从控制浏览器复制到本地新实例或 AgentBay 新建/复用实例的 Cookie 域名列表,可与本地 TDBank 登录组合";
442
+ readonly type: "string[] | \"mearl-services\"";
443
+ readonly description: "从控制浏览器复制 Cookie:传域名数组,或传 mearl-services 同步 Mearl 依赖平台登录态";
444
444
  }];
445
- readonly examples: ["browser_launch --payload '{\"name\":\"cloud-a\",\"provider\":\"agentbay\",\"userAgentMode\":\"desktop\",\"url\":\"https://example.com\"}'", "browser_launch --payload '{\"name\":\"cloud-public\",\"provider\":\"agentbay\",\"imageId\":\"browser_latest\",\"url\":\"https://example.com\"}'", "browser_launch --payload '{\"name\":\"cloud-login\",\"provider\":\"agentbay\",\"persistent\":true,\"copyCookieDomains\":[\"example.com\"],\"url\":\"https://example.com/account\"}'", "browser_launch --payload '{\"name\":\"cloud-login\",\"provider\":\"agentbay\",\"reuse\":\"require\",\"copyCookieDomains\":[\"example.com\"]}'", "browser_launch --payload '{\"name\":\"account-a\",\"headless\":true,\"accountId\":12345}'", "browser_launch --payload '{\"name\":\"figma\",\"userAgentMode\":\"desktop\",\"copyCookieDomains\":[\"figma.com\"]}'", "browser_launch --payload '{\"name\":\"account-b\",\"query\":\"test_account\",\"url\":\"https://www.taobao.com\"}'", "browser_launch --payload '{\"name\":\"with-cookies\",\"copyCookieDomains\":[\"example.com\",\"sub.example.org\"]}'"];
445
+ readonly examples: ["browser_launch --payload '{\"name\":\"cloud-a\",\"provider\":\"agentbay\",\"userAgentMode\":\"desktop\",\"url\":\"https://example.com\"}'", "browser_launch --payload '{\"name\":\"cloud-public\",\"provider\":\"agentbay\",\"imageId\":\"browser_latest\",\"url\":\"https://example.com\"}'", "browser_launch --payload '{\"name\":\"cloud-login\",\"provider\":\"agentbay\",\"persistent\":true,\"copyCookieDomains\":[\"example.com\"],\"url\":\"https://example.com/account\"}'", "browser_launch --payload '{\"name\":\"cloud-login\",\"provider\":\"agentbay\",\"reuse\":\"require\",\"copyCookieDomains\":[\"example.com\"]}'", "browser_launch --payload '{\"name\":\"cloud-tools\",\"provider\":\"agentbay\",\"copyCookieDomains\":\"mearl-services\"}'", "browser_launch --payload '{\"name\":\"account-a\",\"headless\":true,\"accountId\":12345}'", "browser_launch --payload '{\"name\":\"figma\",\"userAgentMode\":\"desktop\",\"copyCookieDomains\":[\"figma.com\"]}'", "browser_launch --payload '{\"name\":\"account-b\",\"query\":\"test_account\",\"url\":\"https://www.taobao.com\"}'", "browser_launch --payload '{\"name\":\"with-cookies\",\"copyCookieDomains\":[\"example.com\",\"sub.example.org\"]}'"];
446
446
  }, {
447
447
  readonly name: "browser_close";
448
448
  readonly description: "关闭指定托管浏览器并按策略保留或清理本地 Profile / AgentBay Context";
@@ -510,6 +510,10 @@ export declare const COMMANDS: readonly [{
510
510
  readonly name: "simulateSystemBars";
511
511
  readonly type: "boolean";
512
512
  readonly description: "与 appProfile 配合;是否模拟顶部状态/导航栏与底部手势栏,默认 true";
513
+ }, {
514
+ readonly name: "bridgeConfig";
515
+ readonly type: "object";
516
+ readonly description: "与 appProfile 配合;声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}}";
513
517
  }];
514
518
  readonly examples: ["tab_open --payload '{\"url\":\"https://example.com\"}'", "tab_open --payload '{\"url\":\"https://example.com/page\",\"reuse\":\"prefer\",\"match\":{\"ignoreSearch\":true}}'", "tab_open --payload '{\"url\":\"https://m.example.com\",\"emulation\":{\"preset\":\"iphone-15-pro\"}}'", "tab_open --payload '{\"url\":\"https://market.m.taobao.com/app/trip/rx-home/pages/home\",\"appProfile\":\"fliggy\"}'", "tab_open --payload '{\"url\":\"https://example.com\",\"appProfile\":\"fliggy\",\"os\":\"android\"}'", "tab_open --payload '{\"url\":\"https://example.com\",\"group\":\"验证任务\"}'"];
515
519
  }, {
@@ -542,7 +546,15 @@ export declare const COMMANDS: readonly [{
542
546
  }, {
543
547
  readonly name: "point";
544
548
  readonly type: "{ x: number; y: number }";
545
- readonly description: "按视口坐标点击(CSS 像素)。selector/text 都不适用的兜底场景";
549
+ readonly description: "按视口坐标点击(CSS 像素)。默认使用 outer-viewport;容器或 iframe 内坐标配合 coordinateSpace: \"active-frame\"";
550
+ }, {
551
+ readonly name: "frameId";
552
+ readonly type: "string";
553
+ readonly description: "浏览器扩展后端:显式指定 CSS/text 查询或 active-frame 坐标所属的 frame;省略时容器模式自动使用当前业务 frame";
554
+ }, {
555
+ readonly name: "coordinateSpace";
556
+ readonly type: "\"outer-viewport\" | \"active-frame\"";
557
+ readonly description: "仅配合 point 使用;默认 outer-viewport;浏览器扩展后端的 active-frame 会换算 iframe 偏移与缩放后派发可信输入";
546
558
  }, {
547
559
  readonly name: "role";
548
560
  readonly type: "string";
@@ -565,7 +577,7 @@ export declare const COMMANDS: readonly [{
565
577
  readonly required: true;
566
578
  readonly description: "目标标签页 ID";
567
579
  }];
568
- readonly examples: ["page_click --payload '{\"text\":\"提交\"}'", "page_click --payload '{\"text\":\"退款明细\",\"role\":\"button\"}'", "page_click --payload '{\"text\":\"确定\",\"scope\":\"@e12\"}'", "page_click --payload '{\"selector\":\"@e3\"}'", "page_click --payload '{\"selector\":\".submit-btn\"}'", "page_click --payload '{\"selector\":\".gesture-btn\",\"clickMode\":\"touch\"}'", "page_click --payload '{\"point\":{\"x\":336,\"y\":117}}'"];
580
+ readonly examples: ["page_click --payload '{\"text\":\"提交\"}'", "page_click --payload '{\"text\":\"退款明细\",\"role\":\"button\"}'", "page_click --payload '{\"text\":\"确定\",\"scope\":\"@e12\"}'", "page_click --payload '{\"selector\":\"@e3\"}'", "page_click --payload '{\"selector\":\".submit-btn\"}'", "page_click --payload '{\"selector\":\".gesture-btn\",\"clickMode\":\"touch\"}'", "page_click --payload '{\"point\":{\"x\":336,\"y\":117}}'", "page_click --payload '{\"point\":{\"x\":196,\"y\":240},\"coordinateSpace\":\"active-frame\"}'"];
569
581
  }, {
570
582
  readonly name: "page_drag";
571
583
  readonly description: "拖动页面元素或视口坐标,适用于轮播图、滑块和拖放";
@@ -968,6 +980,10 @@ export declare const COMMANDS: readonly [{
968
980
  readonly name: "emulation";
969
981
  readonly type: "object";
970
982
  readonly description: "覆盖档案推荐的设备模拟参数,字段同 set_device_emulation 去掉 enabled";
983
+ }, {
984
+ readonly name: "bridgeConfig";
985
+ readonly type: "object";
986
+ readonly description: "声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本";
971
987
  }, {
972
988
  readonly name: "reload";
973
989
  readonly type: "boolean";
package/dist/commands.js CHANGED
@@ -325,8 +325,8 @@ export const COMMANDS = [
325
325
  },
326
326
  {
327
327
  name: 'copyCookieDomains',
328
- type: 'string[]',
329
- description: '从控制浏览器复制到本地新实例或 AgentBay 新建/复用实例的 Cookie 域名列表,可与本地 TDBank 登录组合',
328
+ type: 'string[] | "mearl-services"',
329
+ description: '从控制浏览器复制 Cookie:传域名数组,或传 mearl-services 同步 Mearl 依赖平台登录态',
330
330
  },
331
331
  ],
332
332
  examples: [
@@ -334,6 +334,7 @@ export const COMMANDS = [
334
334
  `browser_launch --payload '{"name":"cloud-public","provider":"agentbay","imageId":"browser_latest","url":"https://example.com"}'`,
335
335
  `browser_launch --payload '{"name":"cloud-login","provider":"agentbay","persistent":true,"copyCookieDomains":["example.com"],"url":"https://example.com/account"}'`,
336
336
  `browser_launch --payload '{"name":"cloud-login","provider":"agentbay","reuse":"require","copyCookieDomains":["example.com"]}'`,
337
+ `browser_launch --payload '{"name":"cloud-tools","provider":"agentbay","copyCookieDomains":"mearl-services"}'`,
337
338
  `browser_launch --payload '{"name":"account-a","headless":true,"accountId":12345}'`,
338
339
  `browser_launch --payload '{"name":"figma","userAgentMode":"desktop","copyCookieDomains":["figma.com"]}'`,
339
340
  `browser_launch --payload '{"name":"account-b","query":"test_account","url":"https://www.taobao.com"}'`,
@@ -420,6 +421,11 @@ export const COMMANDS = [
420
421
  type: 'boolean',
421
422
  description: '与 appProfile 配合;是否模拟顶部状态/导航栏与底部手势栏,默认 true',
422
423
  },
424
+ {
425
+ name: 'bridgeConfig',
426
+ type: 'object',
427
+ description: '与 appProfile 配合;声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}}',
428
+ },
423
429
  ],
424
430
  examples: [
425
431
  `tab_open --payload '{"url":"https://example.com"}'`,
@@ -461,7 +467,17 @@ export const COMMANDS = [
461
467
  {
462
468
  name: 'point',
463
469
  type: '{ x: number; y: number }',
464
- description: '按视口坐标点击(CSS 像素)。selector/text 都不适用的兜底场景',
470
+ description: '按视口坐标点击(CSS 像素)。默认使用 outer-viewport;容器或 iframe 内坐标配合 coordinateSpace: "active-frame"',
471
+ },
472
+ {
473
+ name: 'frameId',
474
+ type: 'string',
475
+ description: '浏览器扩展后端:显式指定 CSS/text 查询或 active-frame 坐标所属的 frame;省略时容器模式自动使用当前业务 frame',
476
+ },
477
+ {
478
+ name: 'coordinateSpace',
479
+ type: '"outer-viewport" | "active-frame"',
480
+ description: '仅配合 point 使用;默认 outer-viewport;浏览器扩展后端的 active-frame 会换算 iframe 偏移与缩放后派发可信输入',
465
481
  },
466
482
  {
467
483
  name: 'role',
@@ -495,6 +511,7 @@ export const COMMANDS = [
495
511
  `page_click --payload '{"selector":".submit-btn"}'`,
496
512
  `page_click --payload '{"selector":".gesture-btn","clickMode":"touch"}'`,
497
513
  `page_click --payload '{"point":{"x":336,"y":117}}'`,
514
+ `page_click --payload '{"point":{"x":196,"y":240},"coordinateSpace":"active-frame"}'`,
498
515
  ],
499
516
  },
500
517
  {
@@ -870,6 +887,11 @@ export const COMMANDS = [
870
887
  type: 'object',
871
888
  description: '覆盖档案推荐的设备模拟参数,字段同 set_device_emulation 去掉 enabled',
872
889
  },
890
+ {
891
+ name: 'bridgeConfig',
892
+ type: 'object',
893
+ description: '声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本',
894
+ },
873
895
  { name: 'reload', type: 'boolean', description: '应用/清除后是否重载页面,默认 true' },
874
896
  { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' },
875
897
  ],
@@ -219,8 +219,8 @@ export interface BrowserLaunchParams {
219
219
  site?: string | number;
220
220
  query?: string;
221
221
  tabId?: number;
222
- /** Copy matching cookies from the selected control browser into a new or reused instance. */
223
- copyCookieDomains?: string[];
222
+ /** Copy explicit domains or the fixed Mearl-service preset from the control browser. */
223
+ copyCookieDomains?: string[] | 'mearl-services';
224
224
  }
225
225
  export interface BrowserCloseParams {
226
226
  browser: string;
@@ -271,6 +271,18 @@ export interface BrowserEmulationOptions {
271
271
  orientation?: 'portrait' | 'landscape';
272
272
  touch?: boolean;
273
273
  }
274
+ export type AppBridgeConfigBehavior = 'navigate' | 'memoryGet' | 'memorySet' | 'memoryDelete' | 'availabilityMap' | 'userInfo' | 'networkInfo' | 'geolocation' | 'permission' | 'prefetch' | 'fptTrack' | 'mtopStream' | 'previewFile' | 'toast' | 'unsupportedNativeInput' | 'payment' | 'userTrack' | 'calendar' | 'networkQualityMonitor' | 'beaconRequest';
275
+ /** 单个桥方法的声明式覆盖;行为、固定回包、成功空实现三选一。 */
276
+ export interface AppBridgeMethodConfig {
277
+ behavior?: AppBridgeConfigBehavior;
278
+ response?: unknown;
279
+ noop?: boolean;
280
+ silent?: boolean;
281
+ }
282
+ /** 追加或覆盖 App 档案中的桥方法。配置保持 JSON 可序列化,不执行配置提供的代码。 */
283
+ export interface AppBridgeConfig {
284
+ methods: Record<string, AppBridgeMethodConfig>;
285
+ }
274
286
  export interface TabOpenParams {
275
287
  url: string;
276
288
  active?: boolean;
@@ -297,6 +309,8 @@ export interface TabOpenParams {
297
309
  os?: 'ios' | 'android';
298
310
  /** 与 appProfile 配合使用;是否模拟顶部状态/导航栏与底部手势栏,默认 true */
299
311
  simulateSystemBars?: boolean;
312
+ /** 与 appProfile 配合使用;按方法覆盖档案内置桥配置 */
313
+ bridgeConfig?: AppBridgeConfig;
300
314
  }
301
315
  /**
302
316
  * 客户端容器模拟可用档案名。
@@ -319,6 +333,8 @@ export interface SetAppProfileParams {
319
333
  deviceModel?: string;
320
334
  /** 是否模拟顶部状态/导航栏与底部手势栏,默认 true */
321
335
  simulateSystemBars?: boolean;
336
+ /** 按方法覆盖档案内置桥配置,仅支持受控行为、固定 JSON 回包和成功空实现 */
337
+ bridgeConfig?: AppBridgeConfig;
322
338
  /** 覆盖档案推荐的设备模拟参数(视口/UA 默认取档案值) */
323
339
  emulation?: BrowserEmulationOptions;
324
340
  /** 默认 true:应用/清除后刷新页面使注入生效 */
@@ -350,6 +366,10 @@ export interface PageClickParams {
350
366
  x: number;
351
367
  y: number;
352
368
  };
369
+ /** CSS/text 查询的目标 frame;省略时容器模式自动使用当前业务 frame。 */
370
+ frameId?: string;
371
+ /** point 的坐标空间;默认 outer-viewport 以保持兼容。 */
372
+ coordinateSpace?: 'outer-viewport' | 'active-frame';
353
373
  role?: string;
354
374
  exact?: boolean;
355
375
  scope?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/client",
3
- "version": "2.8.2",
3
+ "version": "2.9.0",
4
4
  "description": "Unified Mearl SDK & CLI for local and remote browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,13 +45,17 @@
45
45
  "socket-client"
46
46
  ],
47
47
  "license": "ISC",
48
+ "engines": {
49
+ "node": ">=22.12.0"
50
+ },
48
51
  "publishConfig": {
49
52
  "registry": "https://registry.npmjs.org"
50
53
  },
51
54
  "dependencies": {
55
+ "commander": "^15.0.0",
52
56
  "fastest-levenshtein": "^1.0.16",
53
57
  "ws": "^8.18.0",
54
- "@mearl/cloud-types": "2.8.2"
58
+ "@mearl/cloud-types": "2.9.0"
55
59
  },
56
60
  "devDependencies": {
57
61
  "@types/node": "^24.9.1",