@mearl/client 2.8.1 → 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
@@ -55,6 +55,7 @@ mearl set_mock --payload-file ./mock-data.json
55
55
 
56
56
  # 环境自检
57
57
  mearl check
58
+ mearl check --local --json
58
59
 
59
60
  # 更新本地环境
60
61
  npx @mearl/setup update --local
@@ -68,6 +69,7 @@ mearl browser_launch --payload '{"name":"account-b","query":"test_account"}'
68
69
  mearl browser_launch --payload '{"name":"cookie-copy","copyCookieDomains":["example.com"]}'
69
70
  mearl browser_launch --payload '{"name":"cloud-login","provider":"agentbay","persistent":true,"userAgentMode":"desktop","copyCookieDomains":["example.com"],"url":"https://example.com/account"}'
70
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"}'
71
73
  mearl browser_launch --payload '{"name":"figma","userAgentMode":"desktop","copyCookieDomains":["figma.com"]}'
72
74
  mearl browser_list
73
75
  mearl browser_release
@@ -110,13 +112,13 @@ mearl browser_release
110
112
 
111
113
  `page_snapshot` 默认返回完整 AX Tree;长列表可传 `mode: "viewport"`,只需要当前视口内的控件时传 `mode: "interactive"`,已知 CSS 区域时传 `rootSelector`,已有 ref 时传 `rootRef`(可用 `ancestorDepth` 向上补充上下文),只查找特定文案或角色时传 `query`。视口内缺少 AX 控件语义时,`interactive` 会自动回退到 viewport,并返回 `fallbackMode: "viewport"`。`maxNodes` / `maxChars` 截断会同时保留首尾内容。
112
114
 
113
- 重复文本点击可用 `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"` 自动解析最近可滚动祖先。
114
116
 
115
117
  `browser_list` 统一列出普通浏览器和托管浏览器。`type` 区分 `regular` / `managed`,`status` 区分 `connected` / `running_disconnected` / `stopped`;只有 `connected` 的浏览器可作为操作目标。
116
118
 
117
119
  `browser_release` 仅在用户明确要求结束调试、退出接管或清理会话资源时使用,不作为普通任务收尾。它会释放当前目标浏览器的 debugger/CDP 控制和会话级临时状态(包括设备/时区模拟),保留浏览器及全部标签页;后续浏览器操作可自动重新建立控制。
118
120
 
119
- 托管浏览器使用独立 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)。
120
122
 
121
123
  ## 架构
122
124
 
package/dist/check.d.ts CHANGED
@@ -1,4 +1,50 @@
1
+ import type { CloudConnectorInfo } from '@mearl/cloud-types';
1
2
  import { type UnifiedClientOptions, type UnifiedInvokeOptions } from './unified.js';
3
+ export interface UnifiedCheckVersions {
4
+ nativeHost?: string;
5
+ extension?: string | null;
6
+ browser?: string | null;
7
+ }
8
+ interface BrowserInfo {
9
+ browserId: string;
10
+ extensionVersion?: string;
11
+ name: string;
12
+ nativeHostVersion?: string;
13
+ originalBrowserId?: string;
14
+ originalName?: string;
15
+ status: string;
16
+ type?: string;
17
+ provider?: 'local' | 'agentbay';
18
+ transport?: 'extension' | 'cdp';
19
+ }
20
+ interface CheckItem {
21
+ status: 'ok' | 'warning' | 'error';
22
+ label: string;
23
+ details?: string[];
24
+ }
25
+ export interface ExtensionInstallationInfo {
26
+ extensionId: string;
27
+ setupManagedPath: string | null;
28
+ }
29
+ export interface UnifiedCheckBrowserResult extends BrowserInfo {
30
+ agentbayAccessUrl?: string;
31
+ check?: {
32
+ errors?: string[];
33
+ label: string;
34
+ status: CheckItem['status'];
35
+ warnings?: string[];
36
+ };
37
+ extensionInstallation?: ExtensionInstallationInfo;
38
+ selected: boolean;
39
+ versions?: UnifiedCheckVersions;
40
+ }
41
+ export interface UnifiedCheckResult {
42
+ browsers: UnifiedCheckBrowserResult[];
43
+ clientVersion: string;
44
+ connectors: CloudConnectorInfo[];
45
+ environment: CheckItem[];
46
+ ok: boolean;
47
+ }
2
48
  interface CheckClient {
3
49
  invoke(action: string, data?: Record<string, any>, options?: UnifiedInvokeOptions): Promise<any>;
4
50
  disconnect(): void;
@@ -12,11 +58,16 @@ export interface UnifiedCheckOptions {
12
58
  connector?: string;
13
59
  local?: boolean;
14
60
  transport?: 'cdp';
61
+ json?: boolean;
15
62
  }
16
63
  export interface UnifiedCheckDependencies {
17
64
  createClient?: (options: UnifiedClientOptions) => CheckClient;
65
+ writeJson?: (json: string) => void;
18
66
  writeLine?: (line: string) => void;
19
67
  }
20
68
  export declare function parseCheckTimeout(value: string | undefined): number | null;
69
+ export declare function runUnifiedCheck(options: UnifiedCheckOptions & {
70
+ json: true;
71
+ }, dependencies?: UnifiedCheckDependencies): Promise<UnifiedCheckResult>;
21
72
  export declare function runUnifiedCheck(options: UnifiedCheckOptions, dependencies?: UnifiedCheckDependencies): Promise<boolean>;
22
73
  export {};
package/dist/check.js CHANGED
@@ -73,6 +73,7 @@ export function parseCheckTimeout(value) {
73
73
  export async function runUnifiedCheck(options, dependencies = {}) {
74
74
  const timeoutSec = options.timeoutSec ?? 5;
75
75
  const writeLine = dependencies.writeLine ?? (line => console.error(line));
76
+ const writeJson = dependencies.writeJson ?? (json => process.stdout.write(`${json}\n`));
76
77
  const createClient = dependencies.createClient ??
77
78
  ((clientOptions) => new UnifiedClient(clientOptions));
78
79
  const client = createClient({
@@ -194,11 +195,28 @@ export async function runUnifiedCheck(options, dependencies = {}) {
194
195
  browser: browser.browserId,
195
196
  ...(options.transport ? { transport: options.transport } : {}),
196
197
  }));
198
+ const publicVersions = {
199
+ ...(versions.nativeHost ? { nativeHost: versions.nativeHost } : {}),
200
+ ...(versions.extension !== undefined ? { extension: versions.extension } : {}),
201
+ ...(versions.browser !== undefined ? { browser: versions.browser } : {}),
202
+ };
197
203
  const details = [
198
204
  versions.nativeHost ? `native-host v${versions.nativeHost}` : null,
199
205
  versions.extension ? `extension v${versions.extension}` : null,
200
206
  versions.browser ? `Chrome ${versions.browser}` : null,
201
207
  ].filter((value) => value !== null);
208
+ let extensionInstallation;
209
+ if (options.local &&
210
+ browser.transport === 'extension' &&
211
+ typeof versions.extensionId === 'string' &&
212
+ typeof versions.extension === 'string' &&
213
+ (typeof versions.setupManagedPath === 'string' || versions.setupManagedPath === null)) {
214
+ extensionInstallation = {
215
+ extensionId: versions.extensionId,
216
+ setupManagedPath: versions.setupManagedPath,
217
+ };
218
+ }
219
+ let agentbayAccessUrl;
202
220
  let accessUrlWarning;
203
221
  if (browser.provider === 'agentbay' && options.browser) {
204
222
  try {
@@ -209,6 +227,7 @@ export async function runUnifiedCheck(options, dependencies = {}) {
209
227
  if (!access.agentbayAccessUrl) {
210
228
  throw new Error('AgentBay did not return a browser access URL');
211
229
  }
230
+ agentbayAccessUrl = access.agentbayAccessUrl;
212
231
  details.push(`AgentBay access URL (temporary, sensitive): ${access.agentbayAccessUrl}`);
213
232
  }
214
233
  catch (error) {
@@ -217,7 +236,9 @@ export async function runUnifiedCheck(options, dependencies = {}) {
217
236
  }
218
237
  }
219
238
  browserChecks.push({
239
+ ...(agentbayAccessUrl ? { agentbayAccessUrl } : {}),
220
240
  browser,
241
+ ...(extensionInstallation ? { extensionInstallation } : {}),
221
242
  item: {
222
243
  status: accessUrlWarning ? 'warning' : 'ok',
223
244
  label: accessUrlWarning
@@ -225,12 +246,17 @@ export async function runUnifiedCheck(options, dependencies = {}) {
225
246
  : 'Browser command path',
226
247
  details,
227
248
  },
249
+ versions: publicVersions,
250
+ ...(accessUrlWarning ? { warnings: [accessUrlWarning] } : {}),
228
251
  });
229
252
  }
230
253
  catch (error) {
231
254
  const message = formatError(error);
232
255
  browserChecks.push({
233
256
  browser,
257
+ ...(message.includes('Unknown action')
258
+ ? { warnings: ['Update @mearl/native-host for complete diagnostics.'] }
259
+ : { errors: [message] }),
234
260
  item: message.includes('Unknown action')
235
261
  ? {
236
262
  status: 'warning',
@@ -245,6 +271,44 @@ export async function runUnifiedCheck(options, dependencies = {}) {
245
271
  });
246
272
  }
247
273
  }));
274
+ const hasError = environment.some(item => item.status === 'error') ||
275
+ browserChecks.some(check => check.item.status === 'error');
276
+ const listedBrowsers = browserList?.browsers ?? [];
277
+ if (options.json) {
278
+ const result = {
279
+ browsers: listedBrowsers.map(browser => {
280
+ const check = browserChecks.find(item => item.browser.browserId === browser.browserId);
281
+ const browserIdentity = { ...browser };
282
+ delete browserIdentity.extensionVersion;
283
+ delete browserIdentity.nativeHostVersion;
284
+ return {
285
+ ...browserIdentity,
286
+ selected: targets.some(target => target.browserId === browser.browserId),
287
+ ...(check?.agentbayAccessUrl ? { agentbayAccessUrl: check.agentbayAccessUrl } : {}),
288
+ ...(check?.item
289
+ ? {
290
+ check: {
291
+ label: check.item.label,
292
+ status: check.item.status,
293
+ ...(check.errors ? { errors: check.errors } : {}),
294
+ ...(check.warnings ? { warnings: check.warnings } : {}),
295
+ },
296
+ }
297
+ : {}),
298
+ ...(check?.extensionInstallation
299
+ ? { extensionInstallation: check.extensionInstallation }
300
+ : {}),
301
+ ...(check?.versions ? { versions: check.versions } : {}),
302
+ };
303
+ }),
304
+ clientVersion: options.clientVersion,
305
+ connectors,
306
+ environment,
307
+ ok: !hasError,
308
+ };
309
+ writeJson(JSON.stringify(result, null, 2));
310
+ return result;
311
+ }
248
312
  writeLine('');
249
313
  printSection(writeLine, 'Environment', environment);
250
314
  if (connectors.length > 0) {
@@ -253,7 +317,6 @@ export async function runUnifiedCheck(options, dependencies = {}) {
253
317
  writeLine(formatConnector(connector));
254
318
  writeLine('');
255
319
  }
256
- const listedBrowsers = browserList?.browsers ?? [];
257
320
  if (listedBrowsers.length > 0) {
258
321
  writeLine('Browsers');
259
322
  for (const browser of listedBrowsers) {
@@ -271,8 +334,6 @@ export async function runUnifiedCheck(options, dependencies = {}) {
271
334
  }
272
335
  writeLine('');
273
336
  }
274
- const hasError = environment.some(item => item.status === 'error') ||
275
- browserChecks.some(check => check.item.status === 'error');
276
337
  if (hasError) {
277
338
  writeLine('⚠️ Mearl checks failed. See the browser or source details above.');
278
339
  return false;
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}`,
@@ -81,6 +78,7 @@ function printUsage(commandName, version) {
81
78
  ' --payload-file <path> 从文件读取整个 JSON 参数',
82
79
  ' --timeout <seconds> 超时时间(默认按 action 推导)',
83
80
  ' --compact 输出紧凑格式 JSON',
81
+ ' --json check 输出机器可读 JSON',
84
82
  ' --output <path> 保存输出或截图',
85
83
  ' --browser <id|名称> 指定 browser_list 返回的浏览器',
86
84
  ' --connector <id|名称> 限制到一台远端 connector',
@@ -158,9 +156,7 @@ function parsePositiveTimeout(value, fallback) {
158
156
  }
159
157
  return parsed;
160
158
  }
161
- function parsePayload(args) {
162
- const payloadRaw = optionValue(args, '--payload');
163
- const payloadFile = optionValue(args, '--payload-file');
159
+ function parsePayload(payloadRaw, payloadFile) {
164
160
  if (payloadRaw !== undefined && payloadFile !== undefined) {
165
161
  throw new Error('--payload 与 --payload-file 不能同时使用');
166
162
  }
@@ -182,33 +178,7 @@ function parsePayload(args) {
182
178
  }
183
179
  return {};
184
180
  }
185
- function validateOptions(args) {
186
- const optionsWithValues = new Set([
187
- '--payload',
188
- '--payload-file',
189
- '--timeout',
190
- '--output',
191
- '--browser',
192
- '--connector',
193
- '--server',
194
- ]);
195
- const flags = new Set(['--compact', '--local', '--cdp', '--help', '-h']);
196
- for (let index = 1; index < args.length; index += 1) {
197
- const arg = args[index];
198
- if (optionsWithValues.has(arg)) {
199
- if (!args[index + 1] || args[index + 1].startsWith('--')) {
200
- throw new Error(`缺少 ${arg} 的参数值`);
201
- }
202
- index += 1;
203
- continue;
204
- }
205
- if (flags.has(arg))
206
- continue;
207
- throw new Error(`未知参数: ${arg}`);
208
- }
209
- }
210
- function resolveServer(args) {
211
- const explicit = optionValue(args, '--server');
181
+ function resolveServer(explicit) {
212
182
  if (explicit)
213
183
  return { serverUrl: explicit, serverUrlSource: '--server' };
214
184
  if (process.env.MEARL_SERVER_URL) {
@@ -219,27 +189,59 @@ function resolveServer(args) {
219
189
  ? { serverUrl: config.server, serverUrlSource: getCloudServerConfigPath() }
220
190
  : {};
221
191
  }
222
- function parseArgs(args) {
223
- validateOptions(args);
224
- const action = args[0];
225
- const payload = parsePayload(args);
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;
223
+ if (json && action !== 'check')
224
+ throw new Error('--json 仅适用于 check');
225
+ const payload = parsePayload(options.payload, options.payloadFile);
226
226
  const fallbackTimeout = resolveActionTimeoutSec(action, payload, 60);
227
- const forceCdp = args.includes('--cdp');
228
- const local = forceCdp || args.includes('--local');
229
- const connector = optionValue(args, '--connector');
227
+ const forceCdp = options.cdp === true;
228
+ const local = forceCdp || options.local === true;
229
+ const connector = options.connector;
230
230
  if (local && connector)
231
231
  throw new Error('--local/--cdp 不能与 --connector 同时使用');
232
232
  return {
233
233
  action,
234
234
  payload,
235
- timeoutSec: parsePositiveTimeout(optionValue(args, '--timeout'), fallbackTimeout),
236
- compact: args.includes('--compact'),
235
+ timeoutSec: parsePositiveTimeout(options.timeout, fallbackTimeout),
236
+ ...(options.timeout !== undefined ? { timeoutOption: options.timeout } : {}),
237
+ compact: options.compact === true,
237
238
  forceCdp,
239
+ json,
238
240
  local,
239
- ...(optionValue(args, '--output') ? { outputPath: optionValue(args, '--output') } : {}),
240
- ...(optionValue(args, '--browser') ? { browser: optionValue(args, '--browser') } : {}),
241
+ ...(options.output ? { outputPath: options.output } : {}),
242
+ ...(options.browser ? { browser: options.browser } : {}),
241
243
  ...(connector ? { connector } : {}),
242
- ...resolveServer(args),
244
+ ...resolveServer(options.server),
243
245
  };
244
246
  }
245
247
  function screenshotData(action, result) {
@@ -300,19 +302,19 @@ export async function runMearlCli(args, options = {}) {
300
302
  }
301
303
  let parsed;
302
304
  try {
303
- parsed = parseArgs(args);
305
+ parsed = parseClientArgs(args);
304
306
  }
305
307
  catch (error) {
306
308
  console.error(error instanceof Error ? error.message : String(error));
307
309
  return 2;
308
310
  }
309
311
  if (parsed.action === 'check') {
310
- const timeoutSec = parseCheckTimeout(optionValue(args, '--timeout'));
312
+ const timeoutSec = parseCheckTimeout(parsed.timeoutOption);
311
313
  if (timeoutSec === null) {
312
314
  console.error('--timeout 必须是正整数秒');
313
315
  return 2;
314
316
  }
315
- const ok = await runUnifiedCheck({
317
+ const checkOptions = {
316
318
  clientVersion,
317
319
  timeoutSec,
318
320
  ...(parsed.serverUrl ? { serverUrl: parsed.serverUrl } : {}),
@@ -321,8 +323,12 @@ export async function runMearlCli(args, options = {}) {
321
323
  ...(parsed.connector ? { connector: parsed.connector } : {}),
322
324
  ...(parsed.local ? { local: true } : {}),
323
325
  ...(parsed.forceCdp ? { transport: 'cdp' } : {}),
324
- });
325
- return ok ? 0 : 1;
326
+ };
327
+ if (parsed.json) {
328
+ const result = await runUnifiedCheck({ ...checkOptions, json: true });
329
+ return result.ok ? 0 : 1;
330
+ }
331
+ return (await runUnifiedCheck(checkOptions)) ? 0 : 1;
326
332
  }
327
333
  if (parsed.action === 'page_selected_element' && parsed.outputPath) {
328
334
  parsed.payload = { ...parsed.payload, includeScreenshot: true };
@@ -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";
@@ -502,8 +502,20 @@ export declare const COMMANDS: readonly [{
502
502
  readonly name: "appProfile";
503
503
  readonly type: "string";
504
504
  readonly description: "开 tab 即进入 App 容器模拟态(客户端 UA + JSBridge 托管 + iframe 容器栈 + 自适应系统标题栏,首屏即容器态)。系统栏由 iframe 外层统一渲染,并根据页面协议与桥调用切换普通、沉浸或隐藏状态;档案名同 set_app_profile,如 \"fliggy\"";
505
+ }, {
506
+ readonly name: "os";
507
+ readonly type: "\"ios\" | \"android\"";
508
+ readonly description: "与 appProfile 配合;容器系统,默认 ios";
509
+ }, {
510
+ readonly name: "simulateSystemBars";
511
+ readonly type: "boolean";
512
+ readonly description: "与 appProfile 配合;是否模拟顶部状态/导航栏与底部手势栏,默认 true";
513
+ }, {
514
+ readonly name: "bridgeConfig";
515
+ readonly type: "object";
516
+ readonly description: "与 appProfile 配合;声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}}";
505
517
  }];
506
- 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\",\"group\":\"验证任务\"}'"];
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\":\"验证任务\"}'"];
507
519
  }, {
508
520
  readonly name: "tab_close";
509
521
  readonly description: "关闭指定标签页";
@@ -534,7 +546,15 @@ export declare const COMMANDS: readonly [{
534
546
  }, {
535
547
  readonly name: "point";
536
548
  readonly type: "{ x: number; y: number }";
537
- 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 偏移与缩放后派发可信输入";
538
558
  }, {
539
559
  readonly name: "role";
540
560
  readonly type: "string";
@@ -557,7 +577,7 @@ export declare const COMMANDS: readonly [{
557
577
  readonly required: true;
558
578
  readonly description: "目标标签页 ID";
559
579
  }];
560
- 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\"}'"];
561
581
  }, {
562
582
  readonly name: "page_drag";
563
583
  readonly description: "拖动页面元素或视口坐标,适用于轮播图、滑块和拖放";
@@ -952,10 +972,18 @@ export declare const COMMANDS: readonly [{
952
972
  readonly name: "deviceModel";
953
973
  readonly type: "string";
954
974
  readonly description: "Android 机型名(拼入 UA 与 __DEVICE_MODEL__)";
975
+ }, {
976
+ readonly name: "simulateSystemBars";
977
+ readonly type: "boolean";
978
+ readonly description: "是否模拟顶部状态/导航栏与底部手势栏,默认 true";
955
979
  }, {
956
980
  readonly name: "emulation";
957
981
  readonly type: "object";
958
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?}}};不执行配置脚本";
959
987
  }, {
960
988
  readonly name: "reload";
961
989
  readonly type: "boolean";
@@ -966,7 +994,7 @@ export declare const COMMANDS: readonly [{
966
994
  readonly required: true;
967
995
  readonly description: "目标标签页 ID";
968
996
  }];
969
- readonly examples: ["set_app_profile --payload '{\"enabled\":true,\"profile\":\"fliggy\"}'", "set_app_profile --payload '{\"enabled\":true,\"profile\":\"alipay\",\"os\":\"android\",\"deviceModel\":\"Pixel 8 Pro\"}'", "set_app_profile --payload '{\"enabled\":false}'"];
997
+ readonly examples: ["set_app_profile --payload '{\"enabled\":true,\"profile\":\"fliggy\"}'", "set_app_profile --payload '{\"enabled\":true,\"profile\":\"fliggy\",\"simulateSystemBars\":false}'", "set_app_profile --payload '{\"enabled\":true,\"profile\":\"alipay\",\"os\":\"android\",\"deviceModel\":\"Pixel 8 Pro\"}'", "set_app_profile --payload '{\"enabled\":false}'"];
970
998
  }, {
971
999
  readonly name: "set_timezone";
972
1000
  readonly description: "覆盖目标 tab 的时区,传空字符串恢复浏览器默认时区";
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"}'`,
@@ -410,12 +411,28 @@ export const COMMANDS = [
410
411
  type: 'string',
411
412
  description: '开 tab 即进入 App 容器模拟态(客户端 UA + JSBridge 托管 + iframe 容器栈 + 自适应系统标题栏,首屏即容器态)。系统栏由 iframe 外层统一渲染,并根据页面协议与桥调用切换普通、沉浸或隐藏状态;档案名同 set_app_profile,如 "fliggy"',
412
413
  },
414
+ {
415
+ name: 'os',
416
+ type: '"ios" | "android"',
417
+ description: '与 appProfile 配合;容器系统,默认 ios',
418
+ },
419
+ {
420
+ name: 'simulateSystemBars',
421
+ type: 'boolean',
422
+ description: '与 appProfile 配合;是否模拟顶部状态/导航栏与底部手势栏,默认 true',
423
+ },
424
+ {
425
+ name: 'bridgeConfig',
426
+ type: 'object',
427
+ description: '与 appProfile 配合;声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}}',
428
+ },
413
429
  ],
414
430
  examples: [
415
431
  `tab_open --payload '{"url":"https://example.com"}'`,
416
432
  `tab_open --payload '{"url":"https://example.com/page","reuse":"prefer","match":{"ignoreSearch":true}}'`,
417
433
  `tab_open --payload '{"url":"https://m.example.com","emulation":{"preset":"iphone-15-pro"}}'`,
418
434
  `tab_open --payload '{"url":"https://market.m.taobao.com/app/trip/rx-home/pages/home","appProfile":"fliggy"}'`,
435
+ `tab_open --payload '{"url":"https://example.com","appProfile":"fliggy","os":"android"}'`,
419
436
  `tab_open --payload '{"url":"https://example.com","group":"验证任务"}'`,
420
437
  ],
421
438
  },
@@ -450,7 +467,17 @@ export const COMMANDS = [
450
467
  {
451
468
  name: 'point',
452
469
  type: '{ x: number; y: number }',
453
- 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 偏移与缩放后派发可信输入',
454
481
  },
455
482
  {
456
483
  name: 'role',
@@ -484,6 +511,7 @@ export const COMMANDS = [
484
511
  `page_click --payload '{"selector":".submit-btn"}'`,
485
512
  `page_click --payload '{"selector":".gesture-btn","clickMode":"touch"}'`,
486
513
  `page_click --payload '{"point":{"x":336,"y":117}}'`,
514
+ `page_click --payload '{"point":{"x":196,"y":240},"coordinateSpace":"active-frame"}'`,
487
515
  ],
488
516
  },
489
517
  {
@@ -849,16 +877,27 @@ export const COMMANDS = [
849
877
  type: 'string',
850
878
  description: 'Android 机型名(拼入 UA 与 __DEVICE_MODEL__)',
851
879
  },
880
+ {
881
+ name: 'simulateSystemBars',
882
+ type: 'boolean',
883
+ description: '是否模拟顶部状态/导航栏与底部手势栏,默认 true',
884
+ },
852
885
  {
853
886
  name: 'emulation',
854
887
  type: 'object',
855
888
  description: '覆盖档案推荐的设备模拟参数,字段同 set_device_emulation 去掉 enabled',
856
889
  },
890
+ {
891
+ name: 'bridgeConfig',
892
+ type: 'object',
893
+ description: '声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本',
894
+ },
857
895
  { name: 'reload', type: 'boolean', description: '应用/清除后是否重载页面,默认 true' },
858
896
  { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' },
859
897
  ],
860
898
  examples: [
861
899
  `set_app_profile --payload '{"enabled":true,"profile":"fliggy"}'`,
900
+ `set_app_profile --payload '{"enabled":true,"profile":"fliggy","simulateSystemBars":false}'`,
862
901
  `set_app_profile --payload '{"enabled":true,"profile":"alipay","os":"android","deviceModel":"Pixel 8 Pro"}'`,
863
902
  `set_app_profile --payload '{"enabled":false}'`,
864
903
  ],
@@ -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;
@@ -293,6 +305,12 @@ export interface TabOpenParams {
293
305
  * 并根据页面协议与桥调用切换普通、沉浸或隐藏状态。可用档案见 APP_PROFILE_NAMES。
294
306
  */
295
307
  appProfile?: AppProfileName;
308
+ /** 与 appProfile 配合使用;容器系统,默认 ios */
309
+ os?: 'ios' | 'android';
310
+ /** 与 appProfile 配合使用;是否模拟顶部状态/导航栏与底部手势栏,默认 true */
311
+ simulateSystemBars?: boolean;
312
+ /** 与 appProfile 配合使用;按方法覆盖档案内置桥配置 */
313
+ bridgeConfig?: AppBridgeConfig;
296
314
  }
297
315
  /**
298
316
  * 客户端容器模拟可用档案名。
@@ -313,6 +331,10 @@ export interface SetAppProfileParams {
313
331
  os?: 'ios' | 'android';
314
332
  appVersion?: string;
315
333
  deviceModel?: string;
334
+ /** 是否模拟顶部状态/导航栏与底部手势栏,默认 true */
335
+ simulateSystemBars?: boolean;
336
+ /** 按方法覆盖档案内置桥配置,仅支持受控行为、固定 JSON 回包和成功空实现 */
337
+ bridgeConfig?: AppBridgeConfig;
316
338
  /** 覆盖档案推荐的设备模拟参数(视口/UA 默认取档案值) */
317
339
  emulation?: BrowserEmulationOptions;
318
340
  /** 默认 true:应用/清除后刷新页面使注入生效 */
@@ -344,6 +366,10 @@ export interface PageClickParams {
344
366
  x: number;
345
367
  y: number;
346
368
  };
369
+ /** CSS/text 查询的目标 frame;省略时容器模式自动使用当前业务 frame。 */
370
+ frameId?: string;
371
+ /** point 的坐标空间;默认 outer-viewport 以保持兼容。 */
372
+ coordinateSpace?: 'outer-viewport' | 'active-frame';
347
373
  role?: string;
348
374
  exact?: boolean;
349
375
  scope?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/client",
3
- "version": "2.8.1",
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.1"
58
+ "@mearl/cloud-types": "2.9.0"
55
59
  },
56
60
  "devDependencies": {
57
61
  "@types/node": "^24.9.1",