@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 +4 -2
- package/dist/check.d.ts +51 -0
- package/dist/check.js +64 -3
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +57 -51
- package/dist/commands.d.ts +35 -7
- package/dist/commands.js +42 -3
- package/dist/generated-browser-action-protocol.d.ts +28 -2
- package/package.json +6 -2
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
|
|
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`
|
|
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(
|
|
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
|
|
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
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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 =
|
|
228
|
-
const local = forceCdp ||
|
|
229
|
-
const 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(
|
|
236
|
-
|
|
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
|
-
...(
|
|
240
|
-
...(
|
|
241
|
+
...(options.output ? { outputPath: options.output } : {}),
|
|
242
|
+
...(options.browser ? { browser: options.browser } : {}),
|
|
241
243
|
...(connector ? { connector } : {}),
|
|
242
|
-
...resolveServer(
|
|
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 =
|
|
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(
|
|
312
|
+
const timeoutSec = parseCheckTimeout(parsed.timeoutOption);
|
|
311
313
|
if (timeoutSec === null) {
|
|
312
314
|
console.error('--timeout 必须是正整数秒');
|
|
313
315
|
return 2;
|
|
314
316
|
}
|
|
315
|
-
const
|
|
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
|
-
|
|
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 };
|
package/dist/commands.d.ts
CHANGED
|
@@ -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: "
|
|
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
|
|
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: '
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
58
|
+
"@mearl/cloud-types": "2.9.0"
|
|
55
59
|
},
|
|
56
60
|
"devDependencies": {
|
|
57
61
|
"@types/node": "^24.9.1",
|