@mearl/client 2.0.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 +119 -0
- package/dist/actionTimeouts.d.ts +2 -0
- package/dist/actionTimeouts.js +49 -0
- package/dist/browserRegistry.d.ts +38 -0
- package/dist/browserRegistry.js +80 -0
- package/dist/check.d.ts +1 -0
- package/dist/check.js +146 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +324 -0
- package/dist/commands.d.ts +18 -0
- package/dist/commands.js +666 -0
- package/dist/fileRefs.d.ts +20 -0
- package/dist/fileRefs.js +52 -0
- package/dist/index.d.ts +402 -0
- package/dist/index.js +125 -0
- package/dist/socket.d.ts +35 -0
- package/dist/socket.js +306 -0
- package/package.json +44 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mearl CLI — 通过命令行调用 Mearl 浏览器插件能力
|
|
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';
|
|
18
|
+
import { resolveActionTimeoutSec } from './actionTimeouts.js';
|
|
19
|
+
// ─── Usage / Help ─────────────────────────────────────────────────────────────
|
|
20
|
+
const CATEGORIES = [
|
|
21
|
+
{ title: 'API 调试', names: ['get_requests', 'get_logs', 'get_events', 'get_api_schema'] },
|
|
22
|
+
{ title: 'Mock & 规则', names: ['set_mock', 'get_mocks', 'set_rule', 'get_rules'] },
|
|
23
|
+
{ title: '网络代理', names: ['send_request', 'send_mtop_request'] },
|
|
24
|
+
{ title: '标签页', names: ['tab_open', 'tab_close', 'tab_list'] },
|
|
25
|
+
{
|
|
26
|
+
title: '页面操作',
|
|
27
|
+
names: [
|
|
28
|
+
'page_click',
|
|
29
|
+
'page_type',
|
|
30
|
+
'page_hover',
|
|
31
|
+
'page_scroll',
|
|
32
|
+
'page_eval',
|
|
33
|
+
'page_press',
|
|
34
|
+
'page_wait',
|
|
35
|
+
'page_navigate',
|
|
36
|
+
'page_upload',
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{ title: '组合调用', names: ['run_actions'] },
|
|
40
|
+
{
|
|
41
|
+
title: '页面感知',
|
|
42
|
+
names: ['page_snapshot', 'page_screenshot', 'page_selected_element', 'page_frames'],
|
|
43
|
+
},
|
|
44
|
+
{ title: '环境模拟', names: ['set_device_emulation', 'set_timezone'] },
|
|
45
|
+
{ title: '用户信息', names: ['get_user_info'] },
|
|
46
|
+
{ title: '录制', names: ['record_start', 'record_stop'] },
|
|
47
|
+
{ title: 'TDBank', names: ['tdbank_account'] },
|
|
48
|
+
{
|
|
49
|
+
title: '浏览器',
|
|
50
|
+
names: ['browser_list', 'browser_launch', 'browser_close'],
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
function printUsage() {
|
|
54
|
+
const lines = [
|
|
55
|
+
'用法:',
|
|
56
|
+
' mearl <action> [选项]',
|
|
57
|
+
' mearl <action> --help',
|
|
58
|
+
' mearl check',
|
|
59
|
+
'',
|
|
60
|
+
'通用选项:',
|
|
61
|
+
' --payload <json> 直接传入 JSON 参数',
|
|
62
|
+
' --payload-file <path> 从文件读取整个 JSON 参数(适合大体积 payload)',
|
|
63
|
+
' --timeout <seconds> 超时时间(默认按 action 推导,普通 action 为 15 秒)',
|
|
64
|
+
' --compact 输出紧凑格式 JSON',
|
|
65
|
+
' --output <path> 输出文件路径(page_screenshot / page_selected_element 可用)',
|
|
66
|
+
' --cdp 强制使用 CDP 模式(跳过插件通道)',
|
|
67
|
+
' --browser <id|名称> 指定目标浏览器(多浏览器时;缺省用最近聚焦的那个)',
|
|
68
|
+
'',
|
|
69
|
+
'文件引用(任意 action 通用,减少上下文占用):',
|
|
70
|
+
' payload 顶层字段写成 "@<path>"(@ 开头的字符串)即从文件读取该字段值,',
|
|
71
|
+
' 默认按 JSON 解析、失败按原始文本;找不到文件则原样保留该字符串。',
|
|
72
|
+
` 例:set_mock --payload '{"apiName":"x","mockData":"@/abs/resp.json"}'`,
|
|
73
|
+
'',
|
|
74
|
+
'内置命令:',
|
|
75
|
+
' check 检测环境配置,排查连接问题',
|
|
76
|
+
];
|
|
77
|
+
for (const cat of CATEGORIES) {
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(` ${cat.title}`);
|
|
80
|
+
for (const name of cat.names) {
|
|
81
|
+
const cmd = COMMAND_MAP.get(name);
|
|
82
|
+
if (cmd) {
|
|
83
|
+
lines.push(` ${name.padEnd(26)}${cmd.description}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
lines.push('');
|
|
88
|
+
lines.push('运行 mearl <action> --help 查看某个命令的详细参数');
|
|
89
|
+
console.error(lines.join('\n'));
|
|
90
|
+
}
|
|
91
|
+
function printCommandHelp(cmd) {
|
|
92
|
+
const outputOption = cmd.outputFlag ? ' [--output <path>]' : '';
|
|
93
|
+
const lines = [
|
|
94
|
+
`${cmd.name}`,
|
|
95
|
+
` ${cmd.description}`,
|
|
96
|
+
'',
|
|
97
|
+
'用法:',
|
|
98
|
+
` mearl ${cmd.name} [--payload <json>] [--timeout <seconds>]${outputOption}`,
|
|
99
|
+
'',
|
|
100
|
+
];
|
|
101
|
+
if (cmd.params && cmd.params.length > 0) {
|
|
102
|
+
lines.push('参数(--payload JSON 字段):');
|
|
103
|
+
for (const p of cmd.params) {
|
|
104
|
+
const req = p.required ? '必填' : '可选';
|
|
105
|
+
lines.push(` ${p.name.padEnd(22)}${req.padEnd(6)}${p.type.padEnd(28)}${p.description}`);
|
|
106
|
+
}
|
|
107
|
+
lines.push('');
|
|
108
|
+
}
|
|
109
|
+
if (cmd.outputFlag) {
|
|
110
|
+
lines.push('CLI 选项:');
|
|
111
|
+
lines.push(' --output <path> 将截图保存到本地文件');
|
|
112
|
+
lines.push('');
|
|
113
|
+
}
|
|
114
|
+
if (cmd.examples && cmd.examples.length > 0) {
|
|
115
|
+
lines.push('示例:');
|
|
116
|
+
for (const ex of cmd.examples) {
|
|
117
|
+
lines.push(` mearl ${ex}`);
|
|
118
|
+
}
|
|
119
|
+
lines.push('');
|
|
120
|
+
}
|
|
121
|
+
console.error(lines.join('\n'));
|
|
122
|
+
}
|
|
123
|
+
function parseArgs(argv) {
|
|
124
|
+
if (argv.includes('--version') || argv.includes('-v')) {
|
|
125
|
+
console.log(CLIENT_VERSION);
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
|
|
129
|
+
return { showHelp: true, action: COMMAND_MAP.has(argv[0]) ? argv[0] : undefined };
|
|
130
|
+
}
|
|
131
|
+
const action = argv[0];
|
|
132
|
+
let payloadRaw = '{}';
|
|
133
|
+
let payloadFile = null;
|
|
134
|
+
let timeoutSec;
|
|
135
|
+
let compact = false;
|
|
136
|
+
let outputPath = null;
|
|
137
|
+
let forceCdp = false;
|
|
138
|
+
let browser;
|
|
139
|
+
let i = 1;
|
|
140
|
+
while (i < argv.length) {
|
|
141
|
+
const arg = argv[i];
|
|
142
|
+
if (arg === '--payload') {
|
|
143
|
+
if (i + 1 >= argv.length)
|
|
144
|
+
throw new Error('缺少 --payload 的参数值');
|
|
145
|
+
payloadRaw = argv[i + 1];
|
|
146
|
+
i += 2;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (arg === '--payload-file') {
|
|
150
|
+
if (i + 1 >= argv.length)
|
|
151
|
+
throw new Error('缺少 --payload-file 的参数值');
|
|
152
|
+
payloadFile = argv[i + 1];
|
|
153
|
+
i += 2;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (arg === '--timeout') {
|
|
157
|
+
if (i + 1 >= argv.length)
|
|
158
|
+
throw new Error('缺少 --timeout 的参数值');
|
|
159
|
+
const value = Number(argv[i + 1]);
|
|
160
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
161
|
+
throw new Error('--timeout 必须是大于 0 的数字');
|
|
162
|
+
timeoutSec = value;
|
|
163
|
+
i += 2;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (arg === '--compact') {
|
|
167
|
+
compact = true;
|
|
168
|
+
i += 1;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (arg === '--cdp') {
|
|
172
|
+
forceCdp = true;
|
|
173
|
+
i += 1;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (arg === '--browser') {
|
|
177
|
+
if (i + 1 >= argv.length)
|
|
178
|
+
throw new Error('缺少 --browser 的参数值');
|
|
179
|
+
browser = argv[i + 1];
|
|
180
|
+
i += 2;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (arg === '--output') {
|
|
184
|
+
if (i + 1 >= argv.length)
|
|
185
|
+
throw new Error('缺少 --output 的参数值');
|
|
186
|
+
outputPath = argv[i + 1];
|
|
187
|
+
i += 2;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
throw new Error(`未知参数: ${arg}`);
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
showHelp: false,
|
|
194
|
+
action,
|
|
195
|
+
payloadRaw,
|
|
196
|
+
payloadFile,
|
|
197
|
+
timeoutSec,
|
|
198
|
+
compact,
|
|
199
|
+
outputPath,
|
|
200
|
+
forceCdp,
|
|
201
|
+
browser,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function parsePayload(payloadRaw) {
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(payloadRaw);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
throw new Error(`无效的 JSON payload: ${payloadRaw}`, { cause: error });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function saveBase64Image(outputPath, base64Data) {
|
|
213
|
+
fs.writeFileSync(outputPath, Buffer.from(base64Data, 'base64'));
|
|
214
|
+
}
|
|
215
|
+
function extractScreenshotResult(action, result) {
|
|
216
|
+
if (action === 'page_screenshot' && result?.meta && result?.data) {
|
|
217
|
+
return {
|
|
218
|
+
label: 'Screenshot',
|
|
219
|
+
meta: result.meta,
|
|
220
|
+
data: result.data,
|
|
221
|
+
extraLines: [` Page: ${result.meta.title}`, ` URL: ${result.meta.url}`],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (action === 'page_selected_element' && result?.screenshot?.meta && result?.screenshot?.data) {
|
|
225
|
+
return {
|
|
226
|
+
label: 'Selected element screenshot',
|
|
227
|
+
meta: result.screenshot.meta,
|
|
228
|
+
data: result.screenshot.data,
|
|
229
|
+
extraLines: [
|
|
230
|
+
` Element: ${result.tagName || 'unknown'}`,
|
|
231
|
+
` Selector: ${result.selector || 'N/A'}`,
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
async function main() {
|
|
238
|
+
// Handle built-in subcommands before normal arg parsing
|
|
239
|
+
if (process.argv[2] === 'check') {
|
|
240
|
+
await runCheck();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
let args;
|
|
244
|
+
try {
|
|
245
|
+
args = parseArgs(process.argv.slice(2));
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
249
|
+
printUsage();
|
|
250
|
+
process.exit(2);
|
|
251
|
+
}
|
|
252
|
+
if (args.showHelp) {
|
|
253
|
+
if (args.action) {
|
|
254
|
+
printCommandHelp(COMMAND_MAP.get(args.action));
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
printUsage();
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (!COMMAND_MAP.has(args.action)) {
|
|
262
|
+
console.error(`不支持的 action: ${args.action}`);
|
|
263
|
+
console.error(`运行 mearl --help 查看所有可用命令`);
|
|
264
|
+
process.exit(2);
|
|
265
|
+
}
|
|
266
|
+
let data;
|
|
267
|
+
try {
|
|
268
|
+
if (args.payloadFile) {
|
|
269
|
+
if (!fs.existsSync(args.payloadFile)) {
|
|
270
|
+
throw new Error(`Payload file not found: ${args.payloadFile}`);
|
|
271
|
+
}
|
|
272
|
+
const fileContent = fs.readFileSync(args.payloadFile, 'utf-8');
|
|
273
|
+
data = JSON.parse(fileContent);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
data = parsePayload(args.payloadRaw);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
281
|
+
process.exit(2);
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
if (args.action === 'page_selected_element' && args.outputPath) {
|
|
285
|
+
data = { ...data, includeScreenshot: true };
|
|
286
|
+
}
|
|
287
|
+
const result = await invoke(args.action, data, {
|
|
288
|
+
timeoutSec: args.timeoutSec ?? resolveActionTimeoutSec(args.action, data),
|
|
289
|
+
transport: args.forceCdp ? 'cdp' : undefined,
|
|
290
|
+
browser: args.browser,
|
|
291
|
+
});
|
|
292
|
+
const screenshot = extractScreenshotResult(args.action, result);
|
|
293
|
+
if (screenshot) {
|
|
294
|
+
const sizeKB = Math.round(screenshot.meta.size / 1024);
|
|
295
|
+
if (args.outputPath) {
|
|
296
|
+
try {
|
|
297
|
+
saveBase64Image(args.outputPath, screenshot.data);
|
|
298
|
+
console.error(`✓ ${screenshot.label} saved to: ${args.outputPath}`);
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
console.error(`✗ Failed to save ${screenshot.label.toLowerCase()}: ${err instanceof Error ? err.message : String(err)}`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
console.error(`✓ ${screenshot.label} captured`);
|
|
306
|
+
console.error(` Format: ${screenshot.meta.format.toUpperCase()}`);
|
|
307
|
+
console.error(` Size: ${screenshot.meta.width}x${screenshot.meta.height}`);
|
|
308
|
+
console.error(` File size: ~${sizeKB}KB`);
|
|
309
|
+
screenshot.extraLines.forEach(line => console.error(line));
|
|
310
|
+
if (!args.outputPath) {
|
|
311
|
+
const output = args.compact ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
312
|
+
process.stdout.write(output + '\n');
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const output = args.compact ? JSON.stringify(result) : JSON.stringify(result, null, 2);
|
|
317
|
+
process.stdout.write(output + '\n');
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
main();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ParamDef {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
required?: boolean;
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CommandDef {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
/** payload 字段说明 */
|
|
11
|
+
params?: ParamDef[];
|
|
12
|
+
/** 是否支持 --output */
|
|
13
|
+
outputFlag?: boolean;
|
|
14
|
+
examples?: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare const COMMANDS: CommandDef[];
|
|
17
|
+
export type KnownAction = 'get_requests' | 'get_logs' | 'get_events' | 'get_api_schema' | 'set_mock' | 'get_mocks' | 'set_rule' | 'get_rules' | 'send_request' | 'send_mtop_request' | 'tab_open' | 'tab_close' | 'tab_list' | 'page_click' | 'page_type' | 'page_hover' | 'page_scroll' | 'page_eval' | 'page_press' | 'page_wait' | 'page_navigate' | 'page_upload' | 'page_snapshot' | 'page_screenshot' | 'page_selected_element' | 'page_frames' | 'set_device_emulation' | 'set_timezone' | 'record_start' | 'record_stop' | 'request_domain_permission' | 'get_user_info' | 'browser_list' | 'browser_launch' | 'browser_close' | 'run_actions';
|
|
18
|
+
export declare const COMMAND_MAP: Map<string, CommandDef>;
|