@mearl/client 2.19.2 → 2.20.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 +35 -3
- package/dist/actionTimeouts.js +2 -0
- package/dist/artifacts.d.ts +17 -0
- package/dist/artifacts.js +80 -0
- package/dist/check.js +16 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +47 -3
- package/dist/commands.d.ts +138 -14
- package/dist/commands.js +276 -9
- package/dist/generated-browser-action-protocol.d.ts +122 -9
- package/dist/generated-browser-action-protocol.js +24 -6
- package/dist/index.d.ts +10 -1
- package/dist/index.js +15 -3
- package/dist/plugin.d.ts +5 -2
- package/dist/plugin.js +66 -21
- package/dist/pluginPageBundle.d.ts +1 -0
- package/dist/pluginPageBundle.js +54 -1
- package/dist/pluginRegistry.d.ts +5 -0
- package/dist/pluginRegistry.js +28 -0
- package/dist/pluginSource.d.ts +2 -0
- package/dist/pluginSource.js +8 -0
- package/dist/socket.d.ts +1 -0
- package/dist/socket.js +208 -52
- package/dist/unified.d.ts +17 -0
- package/dist/unified.js +119 -9
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -72,6 +72,29 @@ mearl browser_list
|
|
|
72
72
|
mearl browser_release
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
### 控制会话与文件
|
|
76
|
+
|
|
77
|
+
多步骤任务首次 `tab_open` 或 `page_snapshot` 使用 `--session new`,响应在原结果旁附带 `session`,后续用其 `sessionId` 自动路由,无需额外创建/查询 action。Session 绑定一个浏览器与传输;页面首次使用自动关联,复用页面与新建页面分别记录。同一页面只允许一个会话控制,旧调用不能绕过占用;Session 不隔离 Cookie 或存储。
|
|
78
|
+
|
|
79
|
+
旧调用不必传 `--session`,现有动作、`run_actions` 批次和上传 `filePaths` 参数继续可用。活动会话存在时,旧调用不能操作其占用页面、执行浏览器级释放/关闭或修改共享设置;旧批次中的开页/关闭浏览器需改为独立调用。CDP Mock 按标签页生效,跨页使用同一 Mock 时需分别配置。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
mearl tab_open --session new --payload '{"url":"https://example.com"}'
|
|
83
|
+
mearl page_snapshot --session '<sessionId>' --payload '{"tabId":12345}'
|
|
84
|
+
mearl browser_control --session '<sessionId>' --payload '{"operation":"handoff","reason":"请完成扫码"}'
|
|
85
|
+
mearl page_download --session '<sessionId>' --payload '{"tabId":12345,"trigger":{"selector":"#export"}}' --output /absolute/path/report.csv
|
|
86
|
+
mearl page_upload --session '<sessionId>' --payload '{"tabId":12345,"selector":"input[type=file]","files":[{"filePath":"/absolute/path/report.csv"}]}'
|
|
87
|
+
mearl browser_release --session '<sessionId>' --payload '{"closeCreatedTabs":true,"keepTabIds":[12345]}'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
用户在 Popup 的浏览器管理页接管、交还或结束会话。交接后排队旧请求和批次后续步骤停止;已派发动作可能仍然生效。交还后重新取快照。CDP-only 环境可在用户明确确认后用同一 `browser_control` 的 `operation:"resume"` 交还,无需额外 action。会话跨 CLI 连接保留,宿主进程退出后失效;不会根据空闲时间清理用户正在操作的会话。
|
|
91
|
+
|
|
92
|
+
带 Session 的释放只影响该会话,默认保留页面及环境;`closeCreatedTabs` 只关闭新建页,`keepTabIds` 保留结果页,复用的用户页面始终保留。`preserveEnvironment:false` 清理这些页面的环境。活动会话存在时,浏览器级释放与关闭会被拒绝。
|
|
93
|
+
|
|
94
|
+
`page_download` 先监听再点击,返回浏览器文件产物;CLI `--output` 或 SDK `saveArtifact(artifact, absolutePath, { session })` 以 256 KiB 分块导出到调用方。`page_upload.files` 接受调用方绝对路径 `{filePath}` 或浏览器 `{artifactId}`,`filePaths` 仍指浏览器宿主路径。单文件最多 256 MiB;保存到调用方的结果独立于临时产物。会话释放清理临时产物,`keepArtifactIds` 可保留指定产物;无会话产物空闲一小时回收,宿主退出清理暂存。
|
|
95
|
+
|
|
96
|
+
扩展捕获下载需要匹配来源页 referrer 或明确的预期 `url`,无法归属时不会认领最近下载。CDP 只在托管浏览器中使用临时下载目录,共享浏览器保持默认目录,Chrome 未返回完成文件路径时明确报错。下载超时可能已经触发,需检查现场再决定下一步。当前会话与下载适配 Extension/CDP;直接处理页面动作的外部 Provider 对不支持的能力明确报错。
|
|
97
|
+
|
|
75
98
|
### 外部浏览器 Provider
|
|
76
99
|
|
|
77
100
|
Provider 浏览器与扩展、CDP 浏览器一起出现在 `browser_list` 中,并使用同一个
|
|
@@ -123,7 +146,7 @@ Mearl 默认聚合当前机器和已配置 cloud-server 下的浏览器,并通
|
|
|
123
146
|
| ------------------------ | ------------------------------------- |
|
|
124
147
|
| `--connector <id\|名称>` | 限制到一台远端机器 |
|
|
125
148
|
| `--local` | 只使用当前机器 |
|
|
126
|
-
| `--cdp` | 当前机器强制使用 CDP
|
|
149
|
+
| `--cdp` | 当前机器强制使用 CDP;清单只列 CDP 浏览器,并隐含 `--local` |
|
|
127
150
|
| `--server <url>` | 覆盖自动读取的 cloud-server 地址 |
|
|
128
151
|
|
|
129
152
|
`browser_list --help` 和 `browser_launch --help` 会按场景展示宿主选择参数;连接或传输排障运行
|
|
@@ -268,13 +291,18 @@ Preact、Hooks 和 HTM 运行时。页面从 `window.mearl.ui` 读取 `html`、`
|
|
|
268
291
|
`--mearl-accent` 等语义变量;主操作按钮使用 `--mearl-action-primary`、
|
|
269
292
|
`--mearl-action-primary-hover` 和 `--mearl-action-primary-text`,以保持与扩展按钮一致。
|
|
270
293
|
|
|
271
|
-
需要 BUC 身份的内部 HTTP 接口通过官方 `buc` 插件调用。CLI 使用 `mearl buc request`,业务插件使用
|
|
294
|
+
需要 BUC 身份的内部 HTTP 接口通过官方 `buc` 插件调用。CLI 使用 `mearl buc request <url> [app]`,业务插件使用
|
|
272
295
|
`app.invoke('buc', { command: 'request', input })`;当前身份和人员资料分别使用 `whoami` 与 `profile`
|
|
273
296
|
子命令。BUC 插件按请求创建一次性凭据,只接受无 URL 凭据的 HTTPS 地址,过滤认证字段与 Cookie;
|
|
274
297
|
调用方须按已验证的业务契约确认目标服务可信。请求始终在运行 CLI、SDK 或 MCP Server 的机器上执行,
|
|
275
298
|
不随浏览器或 connector 转发。
|
|
276
299
|
`buc request` 支持与 `send_request` 同结构的 `formData` 和 `files`;`files[].filePath` 从该插件执行
|
|
277
300
|
宿主读取,而不是浏览器 native-host,无法引用宿主路径时使用 Base64 内容与 `filename`。
|
|
301
|
+
省略 `app` 时从内置 URL 映射选择;Mearl 正式与预发的 `/api` 地址均映射到 `Mearl`。未登记的地址
|
|
302
|
+
必须显式传入服务登记的 `app`,显式值优先;SDK、MCP 与插件调用的 `input.app` 遵循相同规则。
|
|
303
|
+
AuthX 对应 `mearl authx request <url> [audience]`,省略 `audience` 时同样使用已登记的 URL 映射。
|
|
304
|
+
两者的 `--help` 和 `request --help` 均包含完整 `--payload '{"command":"request","input":{...}}'`
|
|
305
|
+
示例,涵盖 JSON 请求与文件上传。
|
|
278
306
|
|
|
279
307
|
## 支持的操作
|
|
280
308
|
|
|
@@ -308,7 +336,11 @@ Preact、Hooks 和 HTM 运行时。页面从 `window.mearl.ui` 读取 `html`、`
|
|
|
308
336
|
|
|
309
337
|
`browser_list` 统一列出普通浏览器、Provider 浏览器和托管浏览器。`type` 区分
|
|
310
338
|
`regular` / `managed`,`status` 区分 `connected` / `pairing` / `running_disconnected` /
|
|
311
|
-
`stopped`;只有 `connected`
|
|
339
|
+
`stopped`;只有 `connected` 的浏览器可作为常规操作目标。本机没有已连接的插件浏览器时,
|
|
340
|
+
`browser_list` 会自动探测默认 CDP 端点,即使已有 Provider 配对记录也无需传 `--cdp`。
|
|
341
|
+
`browser_list --cdp` 在没有已注册的普通 CDP 浏览器时会主动探测该端点,并只返回 CDP 浏览器;
|
|
342
|
+
插件浏览器已连接也不影响这次探测。设置 `MEARL_SOCKET_PATH` 时,清单请求沿用该实例 Socket。
|
|
343
|
+
`check --cdp` 也只检查这些 CDP 浏览器。
|
|
312
344
|
|
|
313
345
|
`browser_release` 仅在用户明确要求结束调试、退出接管或清理会话资源时调用;它会释放
|
|
314
346
|
debugger/CDP 控制和会话级临时状态,保留浏览器及全部标签页。托管实例的启动、关闭、持久化、
|
package/dist/actionTimeouts.js
CHANGED
|
@@ -29,6 +29,8 @@ export function resolveActionTimeoutSec(action, data, fallback = 15) {
|
|
|
29
29
|
if (action === 'send_mtop_request') {
|
|
30
30
|
return Math.max(fallback, Math.ceil(((data.timeout ?? 10000) + 5000) / 1000));
|
|
31
31
|
}
|
|
32
|
+
if (action === 'page_download')
|
|
33
|
+
return Math.max(fallback, Math.ceil((Number(data.timeout ?? 30000) + 10000) / 1000));
|
|
32
34
|
if (action === 'page_wait') {
|
|
33
35
|
const waitMs = data.time ?? data.timeout ?? 10000;
|
|
34
36
|
return Math.max(fallback, Math.ceil((Number(waitMs) + 5000) / 1000));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BrowserArtifact } from './generated-browser-action-protocol.js';
|
|
2
|
+
import type { UnifiedInvokeOptions } from './unified.js';
|
|
3
|
+
interface Invoker {
|
|
4
|
+
invoke(action: string, data?: Record<string, any>, options?: UnifiedInvokeOptions): Promise<any>;
|
|
5
|
+
}
|
|
6
|
+
export declare function stageFile(client: Invoker, filePath: string, options: UnifiedInvokeOptions): Promise<{
|
|
7
|
+
artifactId: string;
|
|
8
|
+
options: UnifiedInvokeOptions;
|
|
9
|
+
}>;
|
|
10
|
+
/** Atomically export browser-host data to the calling machine without large JSON responses. */
|
|
11
|
+
export declare function exportArtifact(client: Invoker, artifact: BrowserArtifact & {
|
|
12
|
+
browserId?: string;
|
|
13
|
+
}, outputPath: string, options?: UnifiedInvokeOptions): Promise<{
|
|
14
|
+
outputPath: string;
|
|
15
|
+
bytes: number;
|
|
16
|
+
}>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { open, mkdir, rename, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
const CHUNK_BYTES = 256 * 1024;
|
|
5
|
+
export async function stageFile(client, filePath, options) {
|
|
6
|
+
if (!path.isAbsolute(filePath))
|
|
7
|
+
throw new Error('Caller-local file paths must be absolute');
|
|
8
|
+
const source = await open(filePath, 'r');
|
|
9
|
+
let id;
|
|
10
|
+
let pinned = options;
|
|
11
|
+
try {
|
|
12
|
+
const metadata = await source.stat();
|
|
13
|
+
if (!metadata.isFile() || metadata.size > 256 * 1024 * 1024)
|
|
14
|
+
throw new Error('Upload requires a regular file of at most 256 MiB');
|
|
15
|
+
const created = await client.invoke('__artifact_init', { filename: path.basename(filePath) }, options);
|
|
16
|
+
pinned = { ...options, browser: created.browserId };
|
|
17
|
+
id = created.artifact.artifactId;
|
|
18
|
+
let offset = 0;
|
|
19
|
+
for await (const chunk of source.createReadStream({
|
|
20
|
+
highWaterMark: CHUNK_BYTES,
|
|
21
|
+
autoClose: false,
|
|
22
|
+
})) {
|
|
23
|
+
await client.invoke('__artifact_write', { artifactId: id, offset, base64: chunk.toString('base64') }, pinned);
|
|
24
|
+
offset += chunk.length;
|
|
25
|
+
}
|
|
26
|
+
await client.invoke('__artifact_complete', { artifactId: id, bytes: offset }, pinned);
|
|
27
|
+
return { artifactId: id, options: pinned };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (id)
|
|
31
|
+
await client.invoke('__artifact_remove', { artifactId: id }, pinned).catch(() => { });
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
await source.close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Atomically export browser-host data to the calling machine without large JSON responses. */
|
|
39
|
+
export async function exportArtifact(client, artifact, outputPath, options = {}) {
|
|
40
|
+
if (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0)
|
|
41
|
+
throw new Error('Invalid artifact size');
|
|
42
|
+
if (!path.isAbsolute(outputPath))
|
|
43
|
+
throw new Error('Artifact output paths must be absolute');
|
|
44
|
+
if (!options.session && !artifact.browserId && !options.browser)
|
|
45
|
+
throw new Error('An artifact requires its originating browser');
|
|
46
|
+
const pinned = { ...options, ...(artifact.browserId ? { browser: artifact.browserId } : {}) };
|
|
47
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
48
|
+
const temporary = `${outputPath}.${randomUUID()}.part`;
|
|
49
|
+
const target = await open(temporary, 'wx', 0o600);
|
|
50
|
+
try {
|
|
51
|
+
let offset = 0;
|
|
52
|
+
if (artifact.bytes === 0) {
|
|
53
|
+
const empty = await client.invoke('__artifact_read', { artifactId: artifact.artifactId, offset: 0 }, pinned);
|
|
54
|
+
if (empty.totalBytes !== 0 || empty.bytes !== 0 || empty.base64 !== '')
|
|
55
|
+
throw new Error('Artifact export size mismatch');
|
|
56
|
+
}
|
|
57
|
+
while (offset < artifact.bytes) {
|
|
58
|
+
const chunk = await client.invoke('__artifact_read', { artifactId: artifact.artifactId, offset }, pinned);
|
|
59
|
+
const bytes = Buffer.from(chunk.base64, 'base64');
|
|
60
|
+
if (!bytes.length ||
|
|
61
|
+
bytes.length !== chunk.bytes ||
|
|
62
|
+
chunk.totalBytes !== artifact.bytes ||
|
|
63
|
+
offset + bytes.length > artifact.bytes)
|
|
64
|
+
throw new Error('Invalid artifact chunk or truncated transfer');
|
|
65
|
+
await target.write(bytes);
|
|
66
|
+
offset += bytes.length;
|
|
67
|
+
}
|
|
68
|
+
await target.sync();
|
|
69
|
+
await target.close();
|
|
70
|
+
if ((await stat(temporary)).size !== artifact.bytes)
|
|
71
|
+
throw new Error('Artifact export size mismatch');
|
|
72
|
+
await rename(temporary, outputPath);
|
|
73
|
+
return { outputPath, bytes: artifact.bytes };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
await target.close().catch(() => { });
|
|
77
|
+
await rm(temporary, { force: true });
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/check.js
CHANGED
|
@@ -174,7 +174,11 @@ export async function runUnifiedCheck(options, dependencies = {}) {
|
|
|
174
174
|
environment.push({
|
|
175
175
|
status: 'error',
|
|
176
176
|
label: `Cloud server or connector discovery failed${source}`,
|
|
177
|
-
details: [
|
|
177
|
+
details: [
|
|
178
|
+
formatError(error),
|
|
179
|
+
redactServerUrl(options.serverUrl),
|
|
180
|
+
'On the Agent machine, run `mearl-cloud-server status` to verify the server and current connection command. Use `mearl check --local` to isolate this machine.',
|
|
181
|
+
],
|
|
178
182
|
});
|
|
179
183
|
}
|
|
180
184
|
}
|
|
@@ -215,7 +219,8 @@ export async function runUnifiedCheck(options, dependencies = {}) {
|
|
|
215
219
|
details: [formatError(error)],
|
|
216
220
|
});
|
|
217
221
|
}
|
|
218
|
-
const connected = (browserList?.browsers ?? []).filter(browser => browser.status === 'connected'
|
|
222
|
+
const connected = (browserList?.browsers ?? []).filter(browser => browser.status === 'connected' &&
|
|
223
|
+
(options.transport !== 'cdp' || browser.transport === 'cdp'));
|
|
219
224
|
let targets = connected;
|
|
220
225
|
if (options.browser) {
|
|
221
226
|
const matches = findBrowsers(connected, options.browser);
|
|
@@ -242,9 +247,16 @@ export async function runUnifiedCheck(options, dependencies = {}) {
|
|
|
242
247
|
if (!options.browser && connected.length === 0 && browserList) {
|
|
243
248
|
environment.push({
|
|
244
249
|
status: 'error',
|
|
245
|
-
label: 'No connected browser',
|
|
250
|
+
label: options.transport === 'cdp' ? 'No connected CDP browser' : 'No connected browser',
|
|
246
251
|
details: [
|
|
247
|
-
|
|
252
|
+
options.transport === 'cdp'
|
|
253
|
+
? 'Enable a local CDP endpoint (see `mearl-native-host --help`) and run `mearl browser_list --cdp` to inspect discovery.'
|
|
254
|
+
: 'Open Chrome with the Mearl extension, start a managed browser, or connect a browser host.',
|
|
255
|
+
...(!options.local && connectors.length > 0
|
|
256
|
+
? [
|
|
257
|
+
'Connectors are connected but expose no ready browser. On the browser machine, run `mearl check --local`; use `mearl connector_list` for versions and heartbeat details.',
|
|
258
|
+
]
|
|
259
|
+
: []),
|
|
248
260
|
],
|
|
249
261
|
});
|
|
250
262
|
}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -38,6 +38,7 @@ const CATEGORIES = [
|
|
|
38
38
|
'page_wait',
|
|
39
39
|
'page_navigate',
|
|
40
40
|
'page_upload',
|
|
41
|
+
'page_download',
|
|
41
42
|
'page_eval',
|
|
42
43
|
],
|
|
43
44
|
},
|
|
@@ -48,7 +49,13 @@ const CATEGORIES = [
|
|
|
48
49
|
},
|
|
49
50
|
{
|
|
50
51
|
title: '浏览器',
|
|
51
|
-
names: [
|
|
52
|
+
names: [
|
|
53
|
+
'browser_list',
|
|
54
|
+
'browser_launch',
|
|
55
|
+
'browser_release',
|
|
56
|
+
'browser_close',
|
|
57
|
+
'browser_control',
|
|
58
|
+
],
|
|
52
59
|
},
|
|
53
60
|
];
|
|
54
61
|
const BUILTIN_COMMANDS = [
|
|
@@ -119,6 +126,7 @@ function printUsage(commandName, version) {
|
|
|
119
126
|
' --timeout <seconds> 超时时间(默认按 action 推导)',
|
|
120
127
|
' --compact stdout 输出紧凑格式 JSON',
|
|
121
128
|
' --output <path> 保存输出或截图',
|
|
129
|
+
' --session <new|id> 首次建立或复用控制会话',
|
|
122
130
|
' --browser <id|名称> 指定 browser_list 返回的浏览器',
|
|
123
131
|
'',
|
|
124
132
|
'其他选项:',
|
|
@@ -270,6 +278,9 @@ function printCheckHelp(commandName) {
|
|
|
270
278
|
' --json 输出机器可读 JSON',
|
|
271
279
|
' --updates 查询已安装组件的发布版本',
|
|
272
280
|
' -h, --help 查看帮助',
|
|
281
|
+
'',
|
|
282
|
+
'检查本机及远端控制链路;发现错误时退出码为 1。',
|
|
283
|
+
'Server 地址优先级:--server > MEARL_SERVER_URL > ~/.mearl/cloud-server.json。',
|
|
273
284
|
].join('\n'));
|
|
274
285
|
}
|
|
275
286
|
function printAdvancedCommandHelp(commandName, action) {
|
|
@@ -281,6 +292,7 @@ function printAdvancedCommandHelp(commandName, action) {
|
|
|
281
292
|
'',
|
|
282
293
|
'用法:',
|
|
283
294
|
` ${commandName} connector_list [--server <url>] [--timeout <seconds>]`,
|
|
295
|
+
'结果包含 count 与 connectors;每项包含 ID、名称、可选版本、连接时间和最后心跳。',
|
|
284
296
|
].join('\n'));
|
|
285
297
|
}
|
|
286
298
|
function printCommandHelp(commandName, command) {
|
|
@@ -298,6 +310,8 @@ function printCommandHelp(commandName, command) {
|
|
|
298
310
|
for (const parameter of command.params) {
|
|
299
311
|
const required = parameter.required ? '必填' : '可选';
|
|
300
312
|
lines.push(` ${parameter.name.padEnd(22)}${required.padEnd(6)}${parameter.type.padEnd(28)} ${parameter.description}`);
|
|
313
|
+
for (const detail of parameter.details ?? [])
|
|
314
|
+
lines.push(` ${detail}`);
|
|
301
315
|
}
|
|
302
316
|
lines.push('');
|
|
303
317
|
}
|
|
@@ -307,6 +321,12 @@ function printCommandHelp(commandName, command) {
|
|
|
307
321
|
else if (command.name === 'browser_launch') {
|
|
308
322
|
lines.push('宿主选择(本机与远端同时可用时):', ' --local 在当前机器启动', ' --connector <id|名称> 在指定远端 connector 启动', ' --browser <id|名称> 在控制浏览器所在宿主启动,并可作为 Cookie/UA 来源', '');
|
|
309
323
|
}
|
|
324
|
+
if (command.notes?.length) {
|
|
325
|
+
lines.push('约束与行为:', ...command.notes.map(note => ` ${note}`), '');
|
|
326
|
+
}
|
|
327
|
+
if (command.result?.length) {
|
|
328
|
+
lines.push('结果:', ...command.result.map(line => ` ${line}`), '');
|
|
329
|
+
}
|
|
310
330
|
if (command.examples?.length) {
|
|
311
331
|
lines.push('示例:');
|
|
312
332
|
for (const example of command.examples)
|
|
@@ -410,6 +430,7 @@ export function parseClientArgs(args, plugin = false) {
|
|
|
410
430
|
.option('--json')
|
|
411
431
|
.option('--output <path>')
|
|
412
432
|
.option('--browser <id-or-name>')
|
|
433
|
+
.option('--session <new-or-id>')
|
|
413
434
|
.option('--connector <id-or-name>')
|
|
414
435
|
.option('--local')
|
|
415
436
|
.option('--updates')
|
|
@@ -472,6 +493,7 @@ export function parseClientArgs(args, plugin = false) {
|
|
|
472
493
|
updates: options.updates === true,
|
|
473
494
|
...(outputPath ? { outputPath } : {}),
|
|
474
495
|
...(options.browser ? { browser: options.browser } : {}),
|
|
496
|
+
...(options.session ? { session: options.session } : {}),
|
|
475
497
|
...(connector ? { connector } : {}),
|
|
476
498
|
...resolveServer(options.server),
|
|
477
499
|
...(plugin
|
|
@@ -500,6 +522,7 @@ async function runPluginCli(args, commandName) {
|
|
|
500
522
|
const invokeOptions = {
|
|
501
523
|
...(parsed.timeoutOption ? { timeoutSec: parsed.timeoutSec } : {}),
|
|
502
524
|
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
525
|
+
...(parsed.session ? { session: parsed.session } : {}),
|
|
503
526
|
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
504
527
|
...(parsed.local ? { local: true } : {}),
|
|
505
528
|
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
@@ -653,6 +676,7 @@ export async function runMearlCli(args, options = {}) {
|
|
|
653
676
|
...(parsed.serverUrl ? { serverUrl: parsed.serverUrl } : {}),
|
|
654
677
|
...(parsed.serverUrlSource ? { serverUrlSource: parsed.serverUrlSource } : {}),
|
|
655
678
|
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
679
|
+
...(parsed.session ? { session: parsed.session } : {}),
|
|
656
680
|
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
657
681
|
...(parsed.local ? { local: true } : {}),
|
|
658
682
|
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
@@ -672,12 +696,25 @@ export async function runMearlCli(args, options = {}) {
|
|
|
672
696
|
const result = await client.invoke(parsed.action, parsed.payload, {
|
|
673
697
|
timeoutSec: parsed.timeoutSec,
|
|
674
698
|
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
699
|
+
...(parsed.session ? { session: parsed.session } : {}),
|
|
675
700
|
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
676
701
|
...(parsed.local ? { local: true } : {}),
|
|
677
702
|
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
678
703
|
});
|
|
679
|
-
if (parsed.outputPath) {
|
|
704
|
+
if (parsed.outputPath && parsed.action === 'page_download') {
|
|
705
|
+
await client.saveArtifact(result.download, resolve(parsed.outputPath), {
|
|
706
|
+
...(parsed.session ? { session: parsed.session } : {}),
|
|
707
|
+
});
|
|
708
|
+
process.stdout.write(JSON.stringify({ ...result, outputPath: resolve(parsed.outputPath) }) + '\n');
|
|
709
|
+
if (!parsed.session)
|
|
710
|
+
await client.invoke('__artifact_remove', { artifactId: result.download.artifactId }, { browser: result.download.browserId });
|
|
711
|
+
}
|
|
712
|
+
else if (parsed.outputPath) {
|
|
680
713
|
writeOutput(parsed.action, result, parsed.outputPath);
|
|
714
|
+
if (parsed.action === 'page_screenshot' && result?.session) {
|
|
715
|
+
const { data: _image, ...metadata } = result;
|
|
716
|
+
process.stdout.write(JSON.stringify({ ...metadata, outputPath: resolve(parsed.outputPath) }) + '\n');
|
|
717
|
+
}
|
|
681
718
|
}
|
|
682
719
|
else {
|
|
683
720
|
process.stdout.write(`${parsed.compact ? JSON.stringify(result) : JSON.stringify(result, null, 2)}\n`);
|
|
@@ -685,7 +722,14 @@ export async function runMearlCli(args, options = {}) {
|
|
|
685
722
|
return actionInvocationFailed(parsed.action, result) ? 1 : 0;
|
|
686
723
|
}
|
|
687
724
|
catch (error) {
|
|
688
|
-
|
|
725
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
726
|
+
console.error(message);
|
|
727
|
+
if (/connection timeout/i.test(message)) {
|
|
728
|
+
console.error('连接未就绪,请运行 mearl check 定位控制链路故障。');
|
|
729
|
+
}
|
|
730
|
+
else if (/请求超时|(?:request|command) timeout|timed out/i.test(message)) {
|
|
731
|
+
console.error('超时不代表动作未执行。点击、提交或写入后先读取当前状态确认结果;只读查询可缩小范围或增加 --timeout(秒)后重试。');
|
|
732
|
+
}
|
|
689
733
|
return 1;
|
|
690
734
|
}
|
|
691
735
|
finally {
|