@mearl/client 2.5.0 → 2.6.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 +14 -8
- package/dist/actionTimeouts.js +5 -1
- package/dist/check.d.ts +22 -1
- package/dist/check.js +269 -127
- package/dist/cli.d.ts +5 -13
- package/dist/cli.js +208 -222
- package/dist/commands.d.ts +5 -5
- package/dist/commands.js +9 -5
- package/dist/generated-browser-action-protocol.d.ts +15 -2
- package/dist/index.d.ts +46 -45
- package/dist/index.js +83 -83
- package/dist/local.d.ts +7 -0
- package/dist/local.js +6 -0
- package/dist/unified.d.ts +64 -0
- package/dist/unified.js +562 -0
- package/dist/unifiedConfig.d.ts +6 -0
- package/dist/unifiedConfig.js +35 -0
- package/package.json +23 -2
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* 用法:
|
|
6
|
-
* mearl <action> [--payload <json> | --payload-file <path>] [--timeout <seconds>] [--compact] [--output <path>]
|
|
7
|
-
*
|
|
8
|
-
* 示例:
|
|
9
|
-
* mearl get_requests --payload '{"count":5}'
|
|
10
|
-
* mearl send_request --payload '{"url":"https://api.example.com","method":"GET"}'
|
|
11
|
-
* mearl page_screenshot --output ./screenshot.png
|
|
12
|
-
* mearl set_mock --payload-file ./mock-data.json
|
|
13
|
-
*/
|
|
14
|
-
import fs from 'node:fs';
|
|
15
|
-
import { invoke, CLIENT_VERSION } from './socket.js';
|
|
16
|
-
import { COMMAND_MAP } from './commands.js';
|
|
17
|
-
import { runCheck } from './check.js';
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import path, { resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
18
5
|
import { resolveActionTimeoutSec } from './actionTimeouts.js';
|
|
19
|
-
|
|
6
|
+
import { runUnifiedCheck, parseCheckTimeout } from './check.js';
|
|
7
|
+
import { COMMAND_MAP } from './commands.js';
|
|
8
|
+
import { CLIENT_VERSION } from './socket.js';
|
|
9
|
+
import { UnifiedClient } from './unified.js';
|
|
10
|
+
import { getCloudServerConfigPath, loadCloudServerConfig } from './unifiedConfig.js';
|
|
20
11
|
const CATEGORIES = [
|
|
21
12
|
{
|
|
22
13
|
title: 'API 调试',
|
|
@@ -56,275 +47,270 @@ const CATEGORIES = [
|
|
|
56
47
|
names: ['browser_list', 'browser_release', 'browser_launch', 'browser_close'],
|
|
57
48
|
},
|
|
58
49
|
];
|
|
59
|
-
function
|
|
50
|
+
function optionValue(args, name) {
|
|
51
|
+
const index = args.indexOf(name);
|
|
52
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
53
|
+
}
|
|
54
|
+
function printUsage(commandName, version) {
|
|
60
55
|
const lines = [
|
|
56
|
+
`${commandName} v${version}`,
|
|
57
|
+
'',
|
|
61
58
|
'用法:',
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
` ${commandName} <action> [选项]`,
|
|
60
|
+
` ${commandName} <action> --help`,
|
|
61
|
+
` ${commandName} check [--browser <id|名称>] [--connector <id|名称>]`,
|
|
62
|
+
'',
|
|
63
|
+
'统一目标发现:',
|
|
64
|
+
' browser_list 默认列出当前机器和 cloud-server 下所有 connector 的浏览器。',
|
|
65
|
+
' 后续操作使用列表返回的全局 browserId,客户端会自动选择本地或远端传输。',
|
|
65
66
|
'',
|
|
66
67
|
'通用选项:',
|
|
67
68
|
' --payload <json> 直接传入 JSON 参数',
|
|
68
|
-
' --payload-file <path> 从文件读取整个 JSON
|
|
69
|
-
' --timeout <seconds> 超时时间(默认按 action
|
|
69
|
+
' --payload-file <path> 从文件读取整个 JSON 参数',
|
|
70
|
+
' --timeout <seconds> 超时时间(默认按 action 推导)',
|
|
70
71
|
' --compact 输出紧凑格式 JSON',
|
|
71
|
-
' --output <path>
|
|
72
|
-
' --
|
|
73
|
-
' --
|
|
74
|
-
'',
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
' 默认按 JSON 解析、失败按原始文本;找不到文件则原样保留该字符串。',
|
|
78
|
-
` 例:set_mock --payload '{"apiName":"x","mockData":"@/abs/resp.json"}'`,
|
|
72
|
+
' --output <path> 保存输出或截图',
|
|
73
|
+
' --browser <id|名称> 指定 browser_list 返回的浏览器',
|
|
74
|
+
' --connector <id|名称> 限制到一台远端 connector',
|
|
75
|
+
' --local 只使用当前机器上的浏览器',
|
|
76
|
+
' --cdp 当前机器强制使用 CDP(隐含 --local)',
|
|
77
|
+
' --server <url> 覆盖 cloud-server WebSocket URL',
|
|
79
78
|
'',
|
|
80
79
|
'内置命令:',
|
|
81
|
-
' check
|
|
80
|
+
' check 检查指定浏览器,未指定时检查全部已连接浏览器',
|
|
81
|
+
' connector_list 列出 cloud-server 下的 connector',
|
|
82
82
|
];
|
|
83
|
-
for (const
|
|
84
|
-
lines.push('');
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
lines.push(` ${name.padEnd(26)}${cmd.description}`);
|
|
90
|
-
}
|
|
83
|
+
for (const category of CATEGORIES) {
|
|
84
|
+
lines.push('', ` ${category.title}`);
|
|
85
|
+
for (const name of category.names) {
|
|
86
|
+
const command = COMMAND_MAP.get(name);
|
|
87
|
+
if (command)
|
|
88
|
+
lines.push(` ${name.padEnd(26)}${command.description}`);
|
|
91
89
|
}
|
|
92
90
|
}
|
|
93
|
-
lines.push('');
|
|
94
|
-
lines.push('运行 mearl <action> --help 查看某个命令的详细参数');
|
|
91
|
+
lines.push('', `运行 ${commandName} <action> --help 查看 action 参数。`);
|
|
95
92
|
console.error(lines.join('\n'));
|
|
96
93
|
}
|
|
97
|
-
function printCommandHelp(
|
|
98
|
-
const outputOption = cmd.outputFlag ? ' [--output <path>]' : '';
|
|
94
|
+
function printCommandHelp(commandName, command) {
|
|
99
95
|
const lines = [
|
|
100
|
-
`${
|
|
101
|
-
` ${
|
|
96
|
+
`${command.name}`,
|
|
97
|
+
` ${command.description}`,
|
|
102
98
|
'',
|
|
103
99
|
'用法:',
|
|
104
|
-
`
|
|
100
|
+
` ${commandName} ${command.name} [--payload <json>] [--browser <id|名称>]`,
|
|
105
101
|
'',
|
|
106
102
|
];
|
|
107
|
-
if (
|
|
103
|
+
if (command.params?.length) {
|
|
108
104
|
lines.push('参数(--payload JSON 字段):');
|
|
109
|
-
for (const
|
|
110
|
-
const
|
|
111
|
-
lines.push(` ${
|
|
105
|
+
for (const parameter of command.params) {
|
|
106
|
+
const required = parameter.required ? '必填' : '可选';
|
|
107
|
+
lines.push(` ${parameter.name.padEnd(22)}${required.padEnd(6)}${parameter.type.padEnd(28)}${parameter.description}`);
|
|
112
108
|
}
|
|
113
109
|
lines.push('');
|
|
114
110
|
}
|
|
115
|
-
if (
|
|
116
|
-
lines.push('CLI 选项:');
|
|
117
|
-
lines.push(' --output <path> 将截图保存到本地文件');
|
|
118
|
-
lines.push('');
|
|
119
|
-
}
|
|
120
|
-
if (cmd.examples && cmd.examples.length > 0) {
|
|
111
|
+
if (command.examples?.length) {
|
|
121
112
|
lines.push('示例:');
|
|
122
|
-
for (const
|
|
123
|
-
lines.push(`
|
|
124
|
-
}
|
|
125
|
-
lines.push('');
|
|
113
|
+
for (const example of command.examples)
|
|
114
|
+
lines.push(` ${commandName} ${example}`);
|
|
126
115
|
}
|
|
127
116
|
console.error(lines.join('\n'));
|
|
128
117
|
}
|
|
129
|
-
function
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
118
|
+
function parsePositiveTimeout(value, fallback) {
|
|
119
|
+
if (value === undefined)
|
|
120
|
+
return fallback;
|
|
121
|
+
const parsed = Number(value);
|
|
122
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
123
|
+
throw new Error('--timeout 必须是大于 0 的数字');
|
|
133
124
|
}
|
|
134
|
-
|
|
135
|
-
|
|
125
|
+
return parsed;
|
|
126
|
+
}
|
|
127
|
+
function parsePayload(args) {
|
|
128
|
+
const payloadRaw = optionValue(args, '--payload');
|
|
129
|
+
const payloadFile = optionValue(args, '--payload-file');
|
|
130
|
+
if (payloadRaw !== undefined && payloadFile !== undefined) {
|
|
131
|
+
throw new Error('--payload 与 --payload-file 不能同时使用');
|
|
136
132
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
let timeoutSec;
|
|
141
|
-
let compact = false;
|
|
142
|
-
let outputPath = null;
|
|
143
|
-
let forceCdp = false;
|
|
144
|
-
let browser;
|
|
145
|
-
let i = 1;
|
|
146
|
-
while (i < argv.length) {
|
|
147
|
-
const arg = argv[i];
|
|
148
|
-
if (arg === '--payload') {
|
|
149
|
-
if (i + 1 >= argv.length)
|
|
150
|
-
throw new Error('缺少 --payload 的参数值');
|
|
151
|
-
payloadRaw = argv[i + 1];
|
|
152
|
-
i += 2;
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (arg === '--payload-file') {
|
|
156
|
-
if (i + 1 >= argv.length)
|
|
157
|
-
throw new Error('缺少 --payload-file 的参数值');
|
|
158
|
-
payloadFile = argv[i + 1];
|
|
159
|
-
i += 2;
|
|
160
|
-
continue;
|
|
133
|
+
if (payloadRaw !== undefined) {
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(payloadRaw);
|
|
161
136
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
throw new Error('缺少 --timeout 的参数值');
|
|
165
|
-
const value = Number(argv[i + 1]);
|
|
166
|
-
if (!Number.isFinite(value) || value <= 0)
|
|
167
|
-
throw new Error('--timeout 必须是大于 0 的数字');
|
|
168
|
-
timeoutSec = value;
|
|
169
|
-
i += 2;
|
|
170
|
-
continue;
|
|
137
|
+
catch (error) {
|
|
138
|
+
throw new Error('无效的 JSON payload', { cause: error });
|
|
171
139
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
140
|
+
}
|
|
141
|
+
if (payloadFile !== undefined) {
|
|
142
|
+
try {
|
|
143
|
+
return JSON.parse(readFileSync(resolve(payloadFile), 'utf8'));
|
|
176
144
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
i += 1;
|
|
180
|
-
continue;
|
|
145
|
+
catch (error) {
|
|
146
|
+
throw new Error(`无法读取或解析 payload 文件: ${payloadFile}`, { cause: error });
|
|
181
147
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
148
|
+
}
|
|
149
|
+
return {};
|
|
150
|
+
}
|
|
151
|
+
function validateOptions(args) {
|
|
152
|
+
const optionsWithValues = new Set([
|
|
153
|
+
'--payload',
|
|
154
|
+
'--payload-file',
|
|
155
|
+
'--timeout',
|
|
156
|
+
'--output',
|
|
157
|
+
'--browser',
|
|
158
|
+
'--connector',
|
|
159
|
+
'--server',
|
|
160
|
+
]);
|
|
161
|
+
const flags = new Set(['--compact', '--local', '--cdp', '--help', '-h']);
|
|
162
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
163
|
+
const arg = args[index];
|
|
164
|
+
if (optionsWithValues.has(arg)) {
|
|
165
|
+
if (!args[index + 1] || args[index + 1].startsWith('--')) {
|
|
166
|
+
throw new Error(`缺少 ${arg} 的参数值`);
|
|
167
|
+
}
|
|
168
|
+
index += 1;
|
|
187
169
|
continue;
|
|
188
170
|
}
|
|
189
|
-
if (arg
|
|
190
|
-
if (i + 1 >= argv.length)
|
|
191
|
-
throw new Error('缺少 --output 的参数值');
|
|
192
|
-
outputPath = argv[i + 1];
|
|
193
|
-
i += 2;
|
|
171
|
+
if (flags.has(arg))
|
|
194
172
|
continue;
|
|
195
|
-
}
|
|
196
173
|
throw new Error(`未知参数: ${arg}`);
|
|
197
174
|
}
|
|
175
|
+
}
|
|
176
|
+
function resolveServer(args) {
|
|
177
|
+
const explicit = optionValue(args, '--server');
|
|
178
|
+
if (explicit)
|
|
179
|
+
return { serverUrl: explicit, serverUrlSource: '--server' };
|
|
180
|
+
if (process.env.MEARL_SERVER_URL) {
|
|
181
|
+
return { serverUrl: process.env.MEARL_SERVER_URL, serverUrlSource: 'MEARL_SERVER_URL' };
|
|
182
|
+
}
|
|
183
|
+
const config = loadCloudServerConfig();
|
|
184
|
+
return config?.server
|
|
185
|
+
? { serverUrl: config.server, serverUrlSource: getCloudServerConfigPath() }
|
|
186
|
+
: {};
|
|
187
|
+
}
|
|
188
|
+
function parseArgs(args) {
|
|
189
|
+
validateOptions(args);
|
|
190
|
+
const action = args[0];
|
|
191
|
+
const payload = parsePayload(args);
|
|
192
|
+
const fallbackTimeout = resolveActionTimeoutSec(action, payload, 60);
|
|
193
|
+
const forceCdp = args.includes('--cdp');
|
|
194
|
+
const local = forceCdp || args.includes('--local');
|
|
195
|
+
const connector = optionValue(args, '--connector');
|
|
196
|
+
if (local && connector)
|
|
197
|
+
throw new Error('--local/--cdp 不能与 --connector 同时使用');
|
|
198
198
|
return {
|
|
199
|
-
showHelp: false,
|
|
200
199
|
action,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
compact,
|
|
205
|
-
outputPath,
|
|
200
|
+
payload,
|
|
201
|
+
timeoutSec: parsePositiveTimeout(optionValue(args, '--timeout'), fallbackTimeout),
|
|
202
|
+
compact: args.includes('--compact'),
|
|
206
203
|
forceCdp,
|
|
207
|
-
|
|
204
|
+
local,
|
|
205
|
+
...(optionValue(args, '--output') ? { outputPath: optionValue(args, '--output') } : {}),
|
|
206
|
+
...(optionValue(args, '--browser') ? { browser: optionValue(args, '--browser') } : {}),
|
|
207
|
+
...(connector ? { connector } : {}),
|
|
208
|
+
...resolveServer(args),
|
|
208
209
|
};
|
|
209
210
|
}
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
return
|
|
211
|
+
function screenshotData(action, result) {
|
|
212
|
+
if (typeof result === 'string' && result.startsWith('data:image')) {
|
|
213
|
+
return result.split(',')[1] ?? null;
|
|
213
214
|
}
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
if (action === 'page_screenshot' && typeof result?.data === 'string')
|
|
216
|
+
return result.data;
|
|
217
|
+
if (action === 'page_selected_element' && typeof result?.screenshot?.data === 'string') {
|
|
218
|
+
return result.screenshot.data;
|
|
216
219
|
}
|
|
220
|
+
return null;
|
|
217
221
|
}
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (action === 'page_screenshot' && result?.meta && result?.data) {
|
|
223
|
-
return {
|
|
224
|
-
label: 'Screenshot',
|
|
225
|
-
meta: result.meta,
|
|
226
|
-
data: result.data,
|
|
227
|
-
extraLines: [` Page: ${result.meta.title}`, ` URL: ${result.meta.url}`],
|
|
228
|
-
};
|
|
222
|
+
function writeOutput(action, result, outputPath) {
|
|
223
|
+
const image = screenshotData(action, result);
|
|
224
|
+
if (image) {
|
|
225
|
+
writeFileSync(outputPath, Buffer.from(image, 'base64'));
|
|
229
226
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
label: 'Selected element screenshot',
|
|
233
|
-
meta: result.screenshot.meta,
|
|
234
|
-
data: result.screenshot.data,
|
|
235
|
-
extraLines: [
|
|
236
|
-
` Element: ${result.tagName || 'unknown'}`,
|
|
237
|
-
` Selector: ${result.selector || 'N/A'}`,
|
|
238
|
-
],
|
|
239
|
-
};
|
|
227
|
+
else {
|
|
228
|
+
writeFileSync(outputPath, typeof result === 'string' ? result : `${JSON.stringify(result, null, 2)}\n`, 'utf8');
|
|
240
229
|
}
|
|
241
|
-
|
|
230
|
+
console.error(`✓ Output saved to: ${outputPath}`);
|
|
242
231
|
}
|
|
243
|
-
async function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
232
|
+
export async function runMearlCli(args, options = {}) {
|
|
233
|
+
const commandName = options.commandName ?? 'mearl';
|
|
234
|
+
const clientVersion = options.clientVersion ?? CLIENT_VERSION;
|
|
235
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
236
|
+
console.log(clientVersion);
|
|
237
|
+
return 0;
|
|
248
238
|
}
|
|
249
|
-
|
|
239
|
+
if (args.length === 0) {
|
|
240
|
+
printUsage(commandName, clientVersion);
|
|
241
|
+
return 1;
|
|
242
|
+
}
|
|
243
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
244
|
+
const command = COMMAND_MAP.get(args[0]);
|
|
245
|
+
if (command)
|
|
246
|
+
printCommandHelp(commandName, command);
|
|
247
|
+
else
|
|
248
|
+
printUsage(commandName, clientVersion);
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
let parsed;
|
|
250
252
|
try {
|
|
251
|
-
|
|
253
|
+
parsed = parseArgs(args);
|
|
252
254
|
}
|
|
253
255
|
catch (error) {
|
|
254
256
|
console.error(error instanceof Error ? error.message : String(error));
|
|
255
|
-
|
|
256
|
-
process.exit(2);
|
|
257
|
+
return 2;
|
|
257
258
|
}
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
printUsage();
|
|
259
|
+
if (parsed.action === 'check') {
|
|
260
|
+
const timeoutSec = parseCheckTimeout(optionValue(args, '--timeout'));
|
|
261
|
+
if (timeoutSec === null) {
|
|
262
|
+
console.error('--timeout 必须是正整数秒');
|
|
263
|
+
return 2;
|
|
264
264
|
}
|
|
265
|
-
|
|
265
|
+
const ok = await runUnifiedCheck({
|
|
266
|
+
clientVersion,
|
|
267
|
+
timeoutSec,
|
|
268
|
+
...(parsed.serverUrl ? { serverUrl: parsed.serverUrl } : {}),
|
|
269
|
+
...(parsed.serverUrlSource ? { serverUrlSource: parsed.serverUrlSource } : {}),
|
|
270
|
+
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
271
|
+
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
272
|
+
...(parsed.local ? { local: true } : {}),
|
|
273
|
+
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
274
|
+
});
|
|
275
|
+
return ok ? 0 : 1;
|
|
276
|
+
}
|
|
277
|
+
if (parsed.action !== 'connector_list' && !COMMAND_MAP.has(parsed.action)) {
|
|
278
|
+
console.error(`不支持的 action: ${parsed.action}`);
|
|
279
|
+
console.error(`运行 ${commandName} --help 查看所有可用命令`);
|
|
280
|
+
return 2;
|
|
266
281
|
}
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
console.error(`运行 mearl --help 查看所有可用命令`);
|
|
270
|
-
process.exit(2);
|
|
282
|
+
if (parsed.action === 'page_selected_element' && parsed.outputPath) {
|
|
283
|
+
parsed.payload = { ...parsed.payload, includeScreenshot: true };
|
|
271
284
|
}
|
|
272
|
-
|
|
285
|
+
const client = new UnifiedClient({
|
|
286
|
+
...(parsed.serverUrl ? { serverUrl: parsed.serverUrl } : {}),
|
|
287
|
+
requestTimeoutSec: parsed.timeoutSec,
|
|
288
|
+
});
|
|
273
289
|
try {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
290
|
+
const result = await client.invoke(parsed.action, parsed.payload, {
|
|
291
|
+
timeoutSec: parsed.timeoutSec,
|
|
292
|
+
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
293
|
+
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
294
|
+
...(parsed.local ? { local: true } : {}),
|
|
295
|
+
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
296
|
+
});
|
|
297
|
+
if (parsed.outputPath) {
|
|
298
|
+
writeOutput(parsed.action, result, parsed.outputPath);
|
|
280
299
|
}
|
|
281
300
|
else {
|
|
282
|
-
|
|
301
|
+
process.stdout.write(`${parsed.compact ? JSON.stringify(result) : JSON.stringify(result, null, 2)}\n`);
|
|
283
302
|
}
|
|
303
|
+
return 0;
|
|
284
304
|
}
|
|
285
305
|
catch (error) {
|
|
286
306
|
console.error(error instanceof Error ? error.message : String(error));
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
try {
|
|
290
|
-
if (args.action === 'page_selected_element' && args.outputPath) {
|
|
291
|
-
data = { ...data, includeScreenshot: true };
|
|
292
|
-
}
|
|
293
|
-
const result = await invoke(args.action, data, {
|
|
294
|
-
timeoutSec: args.timeoutSec ?? resolveActionTimeoutSec(args.action, data),
|
|
295
|
-
transport: args.forceCdp ? 'cdp' : undefined,
|
|
296
|
-
browser: args.browser,
|
|
297
|
-
});
|
|
298
|
-
const screenshot = extractScreenshotResult(args.action, result);
|
|
299
|
-
if (screenshot) {
|
|
300
|
-
const sizeKB = Math.round(screenshot.meta.size / 1024);
|
|
301
|
-
if (args.outputPath) {
|
|
302
|
-
try {
|
|
303
|
-
saveBase64Image(args.outputPath, screenshot.data);
|
|
304
|
-
console.error(`✓ ${screenshot.label} saved to: ${args.outputPath}`);
|
|
305
|
-
}
|
|
306
|
-
catch (err) {
|
|
307
|
-
console.error(`✗ Failed to save ${screenshot.label.toLowerCase()}: ${err instanceof Error ? err.message : String(err)}`);
|
|
308
|
-
process.exit(1);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
console.error(`✓ ${screenshot.label} captured`);
|
|
312
|
-
console.error(` Format: ${screenshot.meta.format.toUpperCase()}`);
|
|
313
|
-
console.error(` Size: ${screenshot.meta.width}x${screenshot.meta.height}`);
|
|
314
|
-
console.error(` File size: ~${sizeKB}KB`);
|
|
315
|
-
screenshot.extraLines.forEach(line => console.error(line));
|
|
316
|
-
if (!args.outputPath) {
|
|
317
|
-
const output = args.compact ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
318
|
-
process.stdout.write(output + '\n');
|
|
319
|
-
}
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
const output = args.compact ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
323
|
-
process.stdout.write(output + '\n');
|
|
307
|
+
return 1;
|
|
324
308
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
process.exit(1);
|
|
309
|
+
finally {
|
|
310
|
+
client.disconnect();
|
|
328
311
|
}
|
|
329
312
|
}
|
|
330
|
-
|
|
313
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
314
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === currentFile) {
|
|
315
|
+
process.exitCode = await runMearlCli(process.argv.slice(2));
|
|
316
|
+
}
|
package/dist/commands.d.ts
CHANGED
|
@@ -294,7 +294,7 @@ export declare const COMMANDS: readonly [{
|
|
|
294
294
|
readonly examples: ["tdbank_account --payload '{\"operation\":\"current\"}'", "tdbank_account --payload '{\"operation\":\"list\",\"refresh\":true}'", "tdbank_account --payload '{\"operation\":\"switch\",\"accountId\":12345}'", "tdbank_account --payload '{\"operation\":\"borrow\",\"query\":\"test_account\"}'"];
|
|
295
295
|
}, {
|
|
296
296
|
readonly name: "browser_list";
|
|
297
|
-
readonly description: "列出所有浏览器及其连接状态、类型和托管实例信息;AgentBay
|
|
297
|
+
readonly description: "列出所有浏览器及其连接状态、类型和托管实例信息;AgentBay 可包含镜像、持久化 Context 和串流地址";
|
|
298
298
|
readonly examples: ["browser_list"];
|
|
299
299
|
}, {
|
|
300
300
|
readonly name: "browser_release";
|
|
@@ -327,7 +327,7 @@ export declare const COMMANDS: readonly [{
|
|
|
327
327
|
}, {
|
|
328
328
|
readonly name: "persistent";
|
|
329
329
|
readonly type: "boolean";
|
|
330
|
-
readonly description: "
|
|
330
|
+
readonly description: "关闭后是否保留浏览器状态,默认 false;本地保留 Profile,AgentBay 同名实例复用云端 Context";
|
|
331
331
|
}, {
|
|
332
332
|
readonly name: "url";
|
|
333
333
|
readonly type: "string";
|
|
@@ -361,10 +361,10 @@ export declare const COMMANDS: readonly [{
|
|
|
361
361
|
readonly type: "string[]";
|
|
362
362
|
readonly description: "从控制浏览器复制到本地或 AgentBay 新实例的 Cookie 域名列表,可与本地 TDBank 登录组合";
|
|
363
363
|
}];
|
|
364
|
-
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\",\"copyCookieDomains\":[\"example.com\"],\"url\":\"https://example.com/account\"}'", "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\"]}'"];
|
|
364
|
+
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\":\"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\"]}'"];
|
|
365
365
|
}, {
|
|
366
366
|
readonly name: "browser_close";
|
|
367
|
-
readonly description: "
|
|
367
|
+
readonly description: "关闭指定托管浏览器并按策略保留或清理本地 Profile / AgentBay Context";
|
|
368
368
|
readonly params: [{
|
|
369
369
|
readonly name: "browser";
|
|
370
370
|
readonly type: "string";
|
|
@@ -373,7 +373,7 @@ export declare const COMMANDS: readonly [{
|
|
|
373
373
|
}, {
|
|
374
374
|
readonly name: "deleteProfile";
|
|
375
375
|
readonly type: "boolean";
|
|
376
|
-
readonly description: "
|
|
376
|
+
readonly description: "强制删除持久化本地 Profile 或 AgentBay Context,默认 false";
|
|
377
377
|
}];
|
|
378
378
|
readonly examples: ["browser_close --payload '{\"browser\":\"managed:account-a\"}'", "browser_close --payload '{\"browser\":\"managed:agentbay:cloud-a\"}'", "browser_close --payload '{\"browser\":\"account-b\",\"deleteProfile\":true}'"];
|
|
379
379
|
}, {
|
package/dist/commands.js
CHANGED
|
@@ -210,7 +210,7 @@ export const COMMANDS = [
|
|
|
210
210
|
// ── 浏览器 ────────────────────────────────────────────────────────────────
|
|
211
211
|
{
|
|
212
212
|
name: 'browser_list',
|
|
213
|
-
description: '列出所有浏览器及其连接状态、类型和托管实例信息;AgentBay
|
|
213
|
+
description: '列出所有浏览器及其连接状态、类型和托管实例信息;AgentBay 可包含镜像、持久化 Context 和串流地址',
|
|
214
214
|
examples: ['browser_list'],
|
|
215
215
|
},
|
|
216
216
|
{
|
|
@@ -246,7 +246,7 @@ export const COMMANDS = [
|
|
|
246
246
|
{
|
|
247
247
|
name: 'persistent',
|
|
248
248
|
type: 'boolean',
|
|
249
|
-
description: '
|
|
249
|
+
description: '关闭后是否保留浏览器状态,默认 false;本地保留 Profile,AgentBay 同名实例复用云端 Context',
|
|
250
250
|
},
|
|
251
251
|
{ name: 'url', type: 'string', description: '登录完成后打开的初始页面' },
|
|
252
252
|
{ name: 'executablePath', type: 'string', description: '自定义 Chrome 可执行文件路径' },
|
|
@@ -272,7 +272,7 @@ export const COMMANDS = [
|
|
|
272
272
|
examples: [
|
|
273
273
|
`browser_launch --payload '{"name":"cloud-a","provider":"agentbay","userAgentMode":"desktop","url":"https://example.com"}'`,
|
|
274
274
|
`browser_launch --payload '{"name":"cloud-public","provider":"agentbay","imageId":"browser_latest","url":"https://example.com"}'`,
|
|
275
|
-
`browser_launch --payload '{"name":"cloud-login","provider":"agentbay","copyCookieDomains":["example.com"],"url":"https://example.com/account"}'`,
|
|
275
|
+
`browser_launch --payload '{"name":"cloud-login","provider":"agentbay","persistent":true,"copyCookieDomains":["example.com"],"url":"https://example.com/account"}'`,
|
|
276
276
|
`browser_launch --payload '{"name":"account-a","headless":true,"accountId":12345}'`,
|
|
277
277
|
`browser_launch --payload '{"name":"figma","userAgentMode":"desktop","copyCookieDomains":["figma.com"]}'`,
|
|
278
278
|
`browser_launch --payload '{"name":"account-b","query":"test_account","url":"https://www.taobao.com"}'`,
|
|
@@ -281,7 +281,7 @@ export const COMMANDS = [
|
|
|
281
281
|
},
|
|
282
282
|
{
|
|
283
283
|
name: 'browser_close',
|
|
284
|
-
description: '
|
|
284
|
+
description: '关闭指定托管浏览器并按策略保留或清理本地 Profile / AgentBay Context',
|
|
285
285
|
params: [
|
|
286
286
|
{
|
|
287
287
|
name: 'browser',
|
|
@@ -289,7 +289,11 @@ export const COMMANDS = [
|
|
|
289
289
|
required: true,
|
|
290
290
|
description: '托管浏览器的 browserId 或名称',
|
|
291
291
|
},
|
|
292
|
-
{
|
|
292
|
+
{
|
|
293
|
+
name: 'deleteProfile',
|
|
294
|
+
type: 'boolean',
|
|
295
|
+
description: '强制删除持久化本地 Profile 或 AgentBay Context,默认 false',
|
|
296
|
+
},
|
|
293
297
|
],
|
|
294
298
|
examples: [
|
|
295
299
|
`browser_close --payload '{"browser":"managed:account-a"}'`,
|
|
@@ -118,10 +118,19 @@ export interface TdbankAccountParams {
|
|
|
118
118
|
tabId?: number;
|
|
119
119
|
}
|
|
120
120
|
export type BrowserStatus = 'connected' | 'running_disconnected' | 'stopped';
|
|
121
|
+
export interface BrowserSource {
|
|
122
|
+
kind: 'local' | 'remote';
|
|
123
|
+
connectorId?: string;
|
|
124
|
+
connectorName?: string;
|
|
125
|
+
}
|
|
121
126
|
export interface BrowserInfoBase {
|
|
122
127
|
browserId: string;
|
|
123
128
|
name: string;
|
|
124
129
|
status: BrowserStatus;
|
|
130
|
+
/** Source-local values retained when the unified client returns a global browser id and label. */
|
|
131
|
+
originalBrowserId?: string;
|
|
132
|
+
originalName?: string;
|
|
133
|
+
source?: BrowserSource;
|
|
125
134
|
extensionVersion?: string;
|
|
126
135
|
nativeHostVersion?: string;
|
|
127
136
|
transport?: 'extension' | 'cdp';
|
|
@@ -144,8 +153,10 @@ export interface ManagedBrowserInfo extends BrowserInfoBase {
|
|
|
144
153
|
agentbaySessionId?: string;
|
|
145
154
|
/** AgentBay image used to create the session. */
|
|
146
155
|
agentbayImageId?: string;
|
|
147
|
-
/**
|
|
148
|
-
|
|
156
|
+
/** Persistent AgentBay browser context ID when enabled for this instance. */
|
|
157
|
+
agentbayContextId?: string;
|
|
158
|
+
/** Stable AgentBay browser context name derived from the managed browser name. */
|
|
159
|
+
agentbayContextName?: string;
|
|
149
160
|
createdAt: string;
|
|
150
161
|
account?: ManagedBrowserAccount;
|
|
151
162
|
copiedCookies?: {
|
|
@@ -159,6 +170,8 @@ export interface BrowserListResult {
|
|
|
159
170
|
connectedCount: number;
|
|
160
171
|
defaultBrowserId: string | null;
|
|
161
172
|
browsers: BrowserInfo[];
|
|
173
|
+
/** Partial discovery failures; successfully inspected browser sources remain in the result. */
|
|
174
|
+
warnings?: string[];
|
|
162
175
|
}
|
|
163
176
|
export interface SelectBrowserParams {
|
|
164
177
|
browser?: string;
|