New:Socket for Asana Is Now Available.Learn more
Get Started

@mearl/client

Package Overview
Dependencies
Maintainers
2
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mearl/client - npm Package Compare versions

Comparing version
2.8.2
to
2.9.0
+16
-0
dist/cli.d.ts

@@ -5,2 +5,18 @@ export interface MearlCliOptions {

}
export interface ParsedArgs {
action: string;
browser?: string;
compact: boolean;
connector?: string;
forceCdp: boolean;
json: boolean;
local: boolean;
outputPath?: string;
payload: Record<string, any>;
serverUrl?: string;
serverUrlSource?: string;
timeoutSec: number;
timeoutOption?: string;
}
export declare function parseClientArgs(args: string[]): ParsedArgs;
export declare function runMearlCli(args: string[], options?: MearlCliOptions): Promise<number>;
+46
-49
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { Command, CommanderError } from 'commander';
import { resolveActionTimeoutSec } from './actionTimeouts.js';

@@ -62,6 +63,2 @@ import { runUnifiedCheck, parseCheckTimeout } from './check.js';

const ADVANCED_BUILTIN_COMMANDS = new Set(['connector_list']);
function optionValue(args, name) {
const index = args.indexOf(name);
return index >= 0 ? args[index + 1] : undefined;
}
function printUsage(commandName, version) {

@@ -163,5 +160,3 @@ const lines = [

}
function parsePayload(args) {
const payloadRaw = optionValue(args, '--payload');
const payloadFile = optionValue(args, '--payload-file');
function parsePayload(payloadRaw, payloadFile) {
if (payloadRaw !== undefined && payloadFile !== undefined) {

@@ -188,29 +183,3 @@ throw new Error('--payload 与 --payload-file 不能同时使用');

}
function validateOptions(args) {
const optionsWithValues = new Set([
'--payload',
'--payload-file',
'--timeout',
'--output',
'--browser',
'--connector',
'--server',
]);
const flags = new Set(['--compact', '--json', '--local', '--cdp', '--help', '-h']);
for (let index = 1; index < args.length; index += 1) {
const arg = args[index];
if (optionsWithValues.has(arg)) {
if (!args[index + 1] || args[index + 1].startsWith('--')) {
throw new Error(`缺少 ${arg} 的参数值`);
}
index += 1;
continue;
}
if (flags.has(arg))
continue;
throw new Error(`未知参数: ${arg}`);
}
}
function resolveServer(args) {
const explicit = optionValue(args, '--server');
function resolveServer(explicit) {
if (explicit)

@@ -226,13 +195,40 @@ return { serverUrl: explicit, serverUrlSource: '--server' };

}
function parseArgs(args) {
validateOptions(args);
const action = args[0];
const json = args.includes('--json');
export function parseClientArgs(args) {
const program = new Command()
.name('mearl')
.helpOption(false)
.allowExcessArguments(false)
.exitOverride()
.configureOutput({ writeErr: () => { }, writeOut: () => { } })
.argument('<action>')
.option('--payload <json>')
.option('--payload-file <path>')
.option('--timeout <seconds>')
.option('--compact')
.option('--json')
.option('--output <path>')
.option('--browser <id-or-name>')
.option('--connector <id-or-name>')
.option('--local')
.option('--cdp')
.option('--server <url>');
try {
program.parse(['node', 'mearl', ...args]);
}
catch (error) {
if (error instanceof CommanderError) {
throw new Error(error.message.replace(/^error:\s*/i, ''), { cause: error });
}
throw error;
}
const action = program.processedArgs[0];
const options = program.opts();
const json = options.json === true;
if (json && action !== 'check')
throw new Error('--json 仅适用于 check');
const payload = parsePayload(args);
const payload = parsePayload(options.payload, options.payloadFile);
const fallbackTimeout = resolveActionTimeoutSec(action, payload, 60);
const forceCdp = args.includes('--cdp');
const local = forceCdp || args.includes('--local');
const connector = optionValue(args, '--connector');
const forceCdp = options.cdp === true;
const local = forceCdp || options.local === true;
const connector = options.connector;
if (local && connector)

@@ -243,11 +239,12 @@ throw new Error('--local/--cdp 不能与 --connector 同时使用');

payload,
timeoutSec: parsePositiveTimeout(optionValue(args, '--timeout'), fallbackTimeout),
compact: args.includes('--compact'),
timeoutSec: parsePositiveTimeout(options.timeout, fallbackTimeout),
...(options.timeout !== undefined ? { timeoutOption: options.timeout } : {}),
compact: options.compact === true,
forceCdp,
json,
local,
...(optionValue(args, '--output') ? { outputPath: optionValue(args, '--output') } : {}),
...(optionValue(args, '--browser') ? { browser: optionValue(args, '--browser') } : {}),
...(options.output ? { outputPath: options.output } : {}),
...(options.browser ? { browser: options.browser } : {}),
...(connector ? { connector } : {}),
...resolveServer(args),
...resolveServer(options.server),
};

@@ -313,3 +310,3 @@ }

try {
parsed = parseArgs(args);
parsed = parseClientArgs(args);
}

@@ -321,3 +318,3 @@ catch (error) {

if (parsed.action === 'check') {
const timeoutSec = parseCheckTimeout(optionValue(args, '--timeout'));
const timeoutSec = parseCheckTimeout(parsed.timeoutOption);
if (timeoutSec === null) {

@@ -324,0 +321,0 @@ console.error('--timeout 必须是正整数秒');

@@ -442,6 +442,6 @@ import { type BrowserCommandAction } from './generated-browser-action-protocol.js';

readonly name: "copyCookieDomains";
readonly type: "string[]";
readonly description: "从控制浏览器复制到本地新实例或 AgentBay 新建/复用实例的 Cookie 域名列表,可与本地 TDBank 登录组合";
readonly type: "string[] | \"mearl-services\"";
readonly description: "从控制浏览器复制 Cookie:传域名数组,或传 mearl-services 同步 Mearl 依赖平台登录态";
}];
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\"]}'"];
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\"]}'"];
}, {

@@ -514,2 +514,6 @@ readonly name: "browser_close";

readonly description: "与 appProfile 配合;是否模拟顶部状态/导航栏与底部手势栏,默认 true";
}, {
readonly name: "bridgeConfig";
readonly type: "object";
readonly description: "与 appProfile 配合;声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}}";
}];

@@ -547,4 +551,12 @@ 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\":\"验证任务\"}'"];

readonly type: "{ x: number; y: number }";
readonly description: "按视口坐标点击(CSS 像素)。selector/text 都不适用的兜底场景";
readonly description: "按视口坐标点击(CSS 像素)。默认使用 outer-viewport;容器或 iframe 内坐标配合 coordinateSpace: \"active-frame\"";
}, {
readonly name: "frameId";
readonly type: "string";
readonly description: "浏览器扩展后端:显式指定 CSS/text 查询或 active-frame 坐标所属的 frame;省略时容器模式自动使用当前业务 frame";
}, {
readonly name: "coordinateSpace";
readonly type: "\"outer-viewport\" | \"active-frame\"";
readonly description: "仅配合 point 使用;默认 outer-viewport;浏览器扩展后端的 active-frame 会换算 iframe 偏移与缩放后派发可信输入";
}, {
readonly name: "role";

@@ -571,3 +583,3 @@ readonly type: "string";

}];
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}}'"];
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\"}'"];
}, {

@@ -976,2 +988,6 @@ readonly name: "page_drag";

}, {
readonly name: "bridgeConfig";
readonly type: "object";
readonly description: "声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本";
}, {
readonly name: "reload";

@@ -978,0 +994,0 @@ readonly type: "boolean";

@@ -328,4 +328,4 @@ import { APP_PROFILE_NAMES, } from './generated-browser-action-protocol.js';

name: 'copyCookieDomains',
type: 'string[]',
description: '从控制浏览器复制到本地新实例或 AgentBay 新建/复用实例的 Cookie 域名列表,可与本地 TDBank 登录组合',
type: 'string[] | "mearl-services"',
description: '从控制浏览器复制 Cookie:传域名数组,或传 mearl-services 同步 Mearl 依赖平台登录态',
},

@@ -338,2 +338,3 @@ ],

`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}'`,

@@ -425,2 +426,7 @@ `browser_launch --payload '{"name":"figma","userAgentMode":"desktop","copyCookieDomains":["figma.com"]}'`,

},
{
name: 'bridgeConfig',
type: 'object',
description: '与 appProfile 配合;声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}}',
},
],

@@ -467,5 +473,15 @@ examples: [

type: '{ x: number; y: number }',
description: '按视口坐标点击(CSS 像素)。selector/text 都不适用的兜底场景',
description: '按视口坐标点击(CSS 像素)。默认使用 outer-viewport;容器或 iframe 内坐标配合 coordinateSpace: "active-frame"',
},
{
name: 'frameId',
type: 'string',
description: '浏览器扩展后端:显式指定 CSS/text 查询或 active-frame 坐标所属的 frame;省略时容器模式自动使用当前业务 frame',
},
{
name: 'coordinateSpace',
type: '"outer-viewport" | "active-frame"',
description: '仅配合 point 使用;默认 outer-viewport;浏览器扩展后端的 active-frame 会换算 iframe 偏移与缩放后派发可信输入',
},
{
name: 'role',

@@ -502,2 +518,3 @@ type: 'string',

`page_click --payload '{"point":{"x":336,"y":117}}'`,
`page_click --payload '{"point":{"x":196,"y":240},"coordinateSpace":"active-frame"}'`,
],

@@ -878,2 +895,7 @@ },

},
{
name: 'bridgeConfig',
type: 'object',
description: '声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本',
},
{ name: 'reload', type: 'boolean', description: '应用/清除后是否重载页面,默认 true' },

@@ -880,0 +902,0 @@ { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' },

@@ -222,4 +222,4 @@ export interface GetRequestsParams {

tabId?: number;
/** Copy matching cookies from the selected control browser into a new or reused instance. */
copyCookieDomains?: string[];
/** Copy explicit domains or the fixed Mearl-service preset from the control browser. */
copyCookieDomains?: string[] | 'mearl-services';
}

@@ -275,2 +275,14 @@ export interface BrowserCloseParams {

}
export type AppBridgeConfigBehavior = 'navigate' | 'memoryGet' | 'memorySet' | 'memoryDelete' | 'availabilityMap' | 'userInfo' | 'networkInfo' | 'geolocation' | 'permission' | 'prefetch' | 'fptTrack' | 'mtopStream' | 'previewFile' | 'toast' | 'unsupportedNativeInput' | 'payment' | 'userTrack' | 'calendar' | 'networkQualityMonitor' | 'beaconRequest';
/** 单个桥方法的声明式覆盖;行为、固定回包、成功空实现三选一。 */
export interface AppBridgeMethodConfig {
behavior?: AppBridgeConfigBehavior;
response?: unknown;
noop?: boolean;
silent?: boolean;
}
/** 追加或覆盖 App 档案中的桥方法。配置保持 JSON 可序列化,不执行配置提供的代码。 */
export interface AppBridgeConfig {
methods: Record<string, AppBridgeMethodConfig>;
}
export interface TabOpenParams {

@@ -302,2 +314,4 @@ url: string;

simulateSystemBars?: boolean;
/** 与 appProfile 配合使用;按方法覆盖档案内置桥配置 */
bridgeConfig?: AppBridgeConfig;
}

@@ -325,2 +339,4 @@ /**

simulateSystemBars?: boolean;
/** 按方法覆盖档案内置桥配置,仅支持受控行为、固定 JSON 回包和成功空实现 */
bridgeConfig?: AppBridgeConfig;
/** 覆盖档案推荐的设备模拟参数(视口/UA 默认取档案值) */

@@ -357,2 +373,6 @@ emulation?: BrowserEmulationOptions;

};
/** CSS/text 查询的目标 frame;省略时容器模式自动使用当前业务 frame。 */
frameId?: string;
/** point 的坐标空间;默认 outer-viewport 以保持兼容。 */
coordinateSpace?: 'outer-viewport' | 'active-frame';
role?: string;

@@ -359,0 +379,0 @@ exact?: boolean;

{
"name": "@mearl/client",
"version": "2.8.2",
"version": "2.9.0",
"description": "Unified Mearl SDK & CLI for local and remote browsers",

@@ -48,2 +48,5 @@ "type": "module",

"license": "ISC",
"engines": {
"node": ">=22.12.0"
},
"publishConfig": {

@@ -53,5 +56,6 @@ "registry": "https://registry.npmjs.org"

"dependencies": {
"commander": "^15.0.0",
"fastest-levenshtein": "^1.0.16",
"ws": "^8.18.0",
"@mearl/cloud-types": "2.8.2"
"@mearl/cloud-types": "2.9.0"
},

@@ -58,0 +62,0 @@ "devDependencies": {

@@ -72,2 +72,3 @@ # @mearl/client

mearl browser_launch --browser '<控制浏览器 browserId>' --payload '{"name":"cloud-login","provider":"agentbay","reuse":"require","copyCookieDomains":["example.com"]}'
mearl browser_launch --payload '{"name":"cloud-tools","provider":"agentbay","copyCookieDomains":"mearl-services"}'
mearl browser_launch --payload '{"name":"figma","userAgentMode":"desktop","copyCookieDomains":["figma.com"]}'

@@ -115,3 +116,3 @@ mearl browser_list

重复文本点击可用 `page_click.scope` 限定 CSS / ref 子树;ref 指向滚动容器内的子节点时,可用 `page_scroll.containerPolicy: "nearest"` 自动解析最近可滚动祖先。点击结果的 `resolvedTarget` 和滚动结果的前后位置、边界字段可用于诊断实际目标与效果。
重复文本点击可用 `page_click.scope` 限定 CSS / ref 子树;浏览器扩展后端中,容器或 iframe 内的点坐标使用 `coordinateSpace: "active-frame"`,CSS/text 查询可用 `frameId` 显式指定 frame。点击结果通过 `requestedTarget`、`resolvedTarget`、`dispatchTarget`、`hitTarget` 和 `coordinates` 区分请求语义、DOM 解析、派发节点与可信输入实际命中;ref 指向滚动容器内的子节点时,可用 `page_scroll.containerPolicy: "nearest"` 自动解析最近可滚动祖先。

@@ -122,3 +123,3 @@ `browser_list` 统一列出普通浏览器和托管浏览器。`type` 区分 `regular` / `managed`,`status` 区分 `connected` / `running_disconnected` / `stopped`;只有 `connected` 的浏览器可作为操作目标。

托管浏览器使用独立 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)。
托管浏览器使用独立 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)。

@@ -125,0 +126,0 @@ ## 架构