@mearl/client 2.5.1 → 2.6.1
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 +13 -7
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +3 -0
- package/dist/check.d.ts +22 -1
- package/dist/check.js +269 -127
- package/dist/cli.d.ts +5 -14
- package/dist/cli.js +203 -223
- package/dist/generated-browser-action-protocol.d.ts +11 -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 +25 -4
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
|
|
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';
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
18
3
|
import { resolveActionTimeoutSec } from './actionTimeouts.js';
|
|
19
|
-
|
|
4
|
+
import { runUnifiedCheck, parseCheckTimeout } from './check.js';
|
|
5
|
+
import { COMMAND_MAP } from './commands.js';
|
|
6
|
+
import { CLIENT_VERSION } from './socket.js';
|
|
7
|
+
import { UnifiedClient } from './unified.js';
|
|
8
|
+
import { getCloudServerConfigPath, loadCloudServerConfig } from './unifiedConfig.js';
|
|
20
9
|
const CATEGORIES = [
|
|
21
10
|
{
|
|
22
11
|
title: 'API 调试',
|
|
@@ -56,275 +45,266 @@ const CATEGORIES = [
|
|
|
56
45
|
names: ['browser_list', 'browser_release', 'browser_launch', 'browser_close'],
|
|
57
46
|
},
|
|
58
47
|
];
|
|
59
|
-
function
|
|
48
|
+
function optionValue(args, name) {
|
|
49
|
+
const index = args.indexOf(name);
|
|
50
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
51
|
+
}
|
|
52
|
+
function printUsage(commandName, version) {
|
|
60
53
|
const lines = [
|
|
54
|
+
`${commandName} v${version}`,
|
|
55
|
+
'',
|
|
61
56
|
'用法:',
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
` ${commandName} <action> [选项]`,
|
|
58
|
+
` ${commandName} <action> --help`,
|
|
59
|
+
` ${commandName} check [--browser <id|名称>] [--connector <id|名称>]`,
|
|
60
|
+
'',
|
|
61
|
+
'统一目标发现:',
|
|
62
|
+
' browser_list 默认列出当前机器和 cloud-server 下所有 connector 的浏览器。',
|
|
63
|
+
' 后续操作使用列表返回的全局 browserId,客户端会自动选择本地或远端传输。',
|
|
65
64
|
'',
|
|
66
65
|
'通用选项:',
|
|
67
66
|
' --payload <json> 直接传入 JSON 参数',
|
|
68
|
-
' --payload-file <path> 从文件读取整个 JSON
|
|
69
|
-
' --timeout <seconds> 超时时间(默认按 action
|
|
67
|
+
' --payload-file <path> 从文件读取整个 JSON 参数',
|
|
68
|
+
' --timeout <seconds> 超时时间(默认按 action 推导)',
|
|
70
69
|
' --compact 输出紧凑格式 JSON',
|
|
71
|
-
' --output <path>
|
|
72
|
-
' --
|
|
73
|
-
' --
|
|
74
|
-
'',
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
' 默认按 JSON 解析、失败按原始文本;找不到文件则原样保留该字符串。',
|
|
78
|
-
` 例:set_mock --payload '{"apiName":"x","mockData":"@/abs/resp.json"}'`,
|
|
70
|
+
' --output <path> 保存输出或截图',
|
|
71
|
+
' --browser <id|名称> 指定 browser_list 返回的浏览器',
|
|
72
|
+
' --connector <id|名称> 限制到一台远端 connector',
|
|
73
|
+
' --local 只使用当前机器上的浏览器',
|
|
74
|
+
' --cdp 当前机器强制使用 CDP(隐含 --local)',
|
|
75
|
+
' --server <url> 覆盖 cloud-server WebSocket URL',
|
|
79
76
|
'',
|
|
80
77
|
'内置命令:',
|
|
81
|
-
' check
|
|
78
|
+
' check 检查指定浏览器,未指定时检查全部已连接浏览器',
|
|
79
|
+
' connector_list 列出 cloud-server 下的 connector',
|
|
82
80
|
];
|
|
83
|
-
for (const
|
|
84
|
-
lines.push('');
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
lines.push(` ${name.padEnd(26)}${cmd.description}`);
|
|
90
|
-
}
|
|
81
|
+
for (const category of CATEGORIES) {
|
|
82
|
+
lines.push('', ` ${category.title}`);
|
|
83
|
+
for (const name of category.names) {
|
|
84
|
+
const command = COMMAND_MAP.get(name);
|
|
85
|
+
if (command)
|
|
86
|
+
lines.push(` ${name.padEnd(26)}${command.description}`);
|
|
91
87
|
}
|
|
92
88
|
}
|
|
93
|
-
lines.push('');
|
|
94
|
-
lines.push('运行 mearl <action> --help 查看某个命令的详细参数');
|
|
89
|
+
lines.push('', `运行 ${commandName} <action> --help 查看 action 参数。`);
|
|
95
90
|
console.error(lines.join('\n'));
|
|
96
91
|
}
|
|
97
|
-
function printCommandHelp(
|
|
98
|
-
const outputOption = cmd.outputFlag ? ' [--output <path>]' : '';
|
|
92
|
+
function printCommandHelp(commandName, command) {
|
|
99
93
|
const lines = [
|
|
100
|
-
`${
|
|
101
|
-
` ${
|
|
94
|
+
`${command.name}`,
|
|
95
|
+
` ${command.description}`,
|
|
102
96
|
'',
|
|
103
97
|
'用法:',
|
|
104
|
-
`
|
|
98
|
+
` ${commandName} ${command.name} [--payload <json>] [--browser <id|名称>]`,
|
|
105
99
|
'',
|
|
106
100
|
];
|
|
107
|
-
if (
|
|
101
|
+
if (command.params?.length) {
|
|
108
102
|
lines.push('参数(--payload JSON 字段):');
|
|
109
|
-
for (const
|
|
110
|
-
const
|
|
111
|
-
lines.push(` ${
|
|
103
|
+
for (const parameter of command.params) {
|
|
104
|
+
const required = parameter.required ? '必填' : '可选';
|
|
105
|
+
lines.push(` ${parameter.name.padEnd(22)}${required.padEnd(6)}${parameter.type.padEnd(28)}${parameter.description}`);
|
|
112
106
|
}
|
|
113
107
|
lines.push('');
|
|
114
108
|
}
|
|
115
|
-
if (
|
|
116
|
-
lines.push('CLI 选项:');
|
|
117
|
-
lines.push(' --output <path> 将截图保存到本地文件');
|
|
118
|
-
lines.push('');
|
|
119
|
-
}
|
|
120
|
-
if (cmd.examples && cmd.examples.length > 0) {
|
|
109
|
+
if (command.examples?.length) {
|
|
121
110
|
lines.push('示例:');
|
|
122
|
-
for (const
|
|
123
|
-
lines.push(`
|
|
124
|
-
}
|
|
125
|
-
lines.push('');
|
|
111
|
+
for (const example of command.examples)
|
|
112
|
+
lines.push(` ${commandName} ${example}`);
|
|
126
113
|
}
|
|
127
114
|
console.error(lines.join('\n'));
|
|
128
115
|
}
|
|
129
|
-
function
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
116
|
+
function parsePositiveTimeout(value, fallback) {
|
|
117
|
+
if (value === undefined)
|
|
118
|
+
return fallback;
|
|
119
|
+
const parsed = Number(value);
|
|
120
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
121
|
+
throw new Error('--timeout 必须是大于 0 的数字');
|
|
133
122
|
}
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
return parsed;
|
|
124
|
+
}
|
|
125
|
+
function parsePayload(args) {
|
|
126
|
+
const payloadRaw = optionValue(args, '--payload');
|
|
127
|
+
const payloadFile = optionValue(args, '--payload-file');
|
|
128
|
+
if (payloadRaw !== undefined && payloadFile !== undefined) {
|
|
129
|
+
throw new Error('--payload 与 --payload-file 不能同时使用');
|
|
136
130
|
}
|
|
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;
|
|
131
|
+
if (payloadRaw !== undefined) {
|
|
132
|
+
try {
|
|
133
|
+
return JSON.parse(payloadRaw);
|
|
161
134
|
}
|
|
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;
|
|
135
|
+
catch (error) {
|
|
136
|
+
throw new Error('无效的 JSON payload', { cause: error });
|
|
171
137
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
138
|
+
}
|
|
139
|
+
if (payloadFile !== undefined) {
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(readFileSync(resolve(payloadFile), 'utf8'));
|
|
176
142
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
i += 1;
|
|
180
|
-
continue;
|
|
143
|
+
catch (error) {
|
|
144
|
+
throw new Error(`无法读取或解析 payload 文件: ${payloadFile}`, { cause: error });
|
|
181
145
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
146
|
+
}
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
function validateOptions(args) {
|
|
150
|
+
const optionsWithValues = new Set([
|
|
151
|
+
'--payload',
|
|
152
|
+
'--payload-file',
|
|
153
|
+
'--timeout',
|
|
154
|
+
'--output',
|
|
155
|
+
'--browser',
|
|
156
|
+
'--connector',
|
|
157
|
+
'--server',
|
|
158
|
+
]);
|
|
159
|
+
const flags = new Set(['--compact', '--local', '--cdp', '--help', '-h']);
|
|
160
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
161
|
+
const arg = args[index];
|
|
162
|
+
if (optionsWithValues.has(arg)) {
|
|
163
|
+
if (!args[index + 1] || args[index + 1].startsWith('--')) {
|
|
164
|
+
throw new Error(`缺少 ${arg} 的参数值`);
|
|
165
|
+
}
|
|
166
|
+
index += 1;
|
|
187
167
|
continue;
|
|
188
168
|
}
|
|
189
|
-
if (arg
|
|
190
|
-
if (i + 1 >= argv.length)
|
|
191
|
-
throw new Error('缺少 --output 的参数值');
|
|
192
|
-
outputPath = argv[i + 1];
|
|
193
|
-
i += 2;
|
|
169
|
+
if (flags.has(arg))
|
|
194
170
|
continue;
|
|
195
|
-
}
|
|
196
171
|
throw new Error(`未知参数: ${arg}`);
|
|
197
172
|
}
|
|
173
|
+
}
|
|
174
|
+
function resolveServer(args) {
|
|
175
|
+
const explicit = optionValue(args, '--server');
|
|
176
|
+
if (explicit)
|
|
177
|
+
return { serverUrl: explicit, serverUrlSource: '--server' };
|
|
178
|
+
if (process.env.MEARL_SERVER_URL) {
|
|
179
|
+
return { serverUrl: process.env.MEARL_SERVER_URL, serverUrlSource: 'MEARL_SERVER_URL' };
|
|
180
|
+
}
|
|
181
|
+
const config = loadCloudServerConfig();
|
|
182
|
+
return config?.server
|
|
183
|
+
? { serverUrl: config.server, serverUrlSource: getCloudServerConfigPath() }
|
|
184
|
+
: {};
|
|
185
|
+
}
|
|
186
|
+
function parseArgs(args) {
|
|
187
|
+
validateOptions(args);
|
|
188
|
+
const action = args[0];
|
|
189
|
+
const payload = parsePayload(args);
|
|
190
|
+
const fallbackTimeout = resolveActionTimeoutSec(action, payload, 60);
|
|
191
|
+
const forceCdp = args.includes('--cdp');
|
|
192
|
+
const local = forceCdp || args.includes('--local');
|
|
193
|
+
const connector = optionValue(args, '--connector');
|
|
194
|
+
if (local && connector)
|
|
195
|
+
throw new Error('--local/--cdp 不能与 --connector 同时使用');
|
|
198
196
|
return {
|
|
199
|
-
showHelp: false,
|
|
200
197
|
action,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
compact,
|
|
205
|
-
outputPath,
|
|
198
|
+
payload,
|
|
199
|
+
timeoutSec: parsePositiveTimeout(optionValue(args, '--timeout'), fallbackTimeout),
|
|
200
|
+
compact: args.includes('--compact'),
|
|
206
201
|
forceCdp,
|
|
207
|
-
|
|
202
|
+
local,
|
|
203
|
+
...(optionValue(args, '--output') ? { outputPath: optionValue(args, '--output') } : {}),
|
|
204
|
+
...(optionValue(args, '--browser') ? { browser: optionValue(args, '--browser') } : {}),
|
|
205
|
+
...(connector ? { connector } : {}),
|
|
206
|
+
...resolveServer(args),
|
|
208
207
|
};
|
|
209
208
|
}
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
return
|
|
209
|
+
function screenshotData(action, result) {
|
|
210
|
+
if (typeof result === 'string' && result.startsWith('data:image')) {
|
|
211
|
+
return result.split(',')[1] ?? null;
|
|
213
212
|
}
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
if (action === 'page_screenshot' && typeof result?.data === 'string')
|
|
214
|
+
return result.data;
|
|
215
|
+
if (action === 'page_selected_element' && typeof result?.screenshot?.data === 'string') {
|
|
216
|
+
return result.screenshot.data;
|
|
216
217
|
}
|
|
218
|
+
return null;
|
|
217
219
|
}
|
|
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
|
-
};
|
|
220
|
+
function writeOutput(action, result, outputPath) {
|
|
221
|
+
const image = screenshotData(action, result);
|
|
222
|
+
if (image) {
|
|
223
|
+
writeFileSync(outputPath, Buffer.from(image, 'base64'));
|
|
229
224
|
}
|
|
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
|
-
};
|
|
225
|
+
else {
|
|
226
|
+
writeFileSync(outputPath, typeof result === 'string' ? result : `${JSON.stringify(result, null, 2)}\n`, 'utf8');
|
|
240
227
|
}
|
|
241
|
-
|
|
228
|
+
console.error(`✓ Output saved to: ${outputPath}`);
|
|
242
229
|
}
|
|
243
|
-
async function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
230
|
+
export async function runMearlCli(args, options = {}) {
|
|
231
|
+
const commandName = options.commandName ?? 'mearl';
|
|
232
|
+
const clientVersion = options.clientVersion ?? CLIENT_VERSION;
|
|
233
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
234
|
+
console.log(clientVersion);
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
if (args.length === 0) {
|
|
238
|
+
printUsage(commandName, clientVersion);
|
|
239
|
+
return 1;
|
|
240
|
+
}
|
|
241
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
242
|
+
const command = COMMAND_MAP.get(args[0]);
|
|
243
|
+
if (command)
|
|
244
|
+
printCommandHelp(commandName, command);
|
|
245
|
+
else
|
|
246
|
+
printUsage(commandName, clientVersion);
|
|
247
|
+
return 0;
|
|
248
248
|
}
|
|
249
|
-
let
|
|
249
|
+
let parsed;
|
|
250
250
|
try {
|
|
251
|
-
|
|
251
|
+
parsed = parseArgs(args);
|
|
252
252
|
}
|
|
253
253
|
catch (error) {
|
|
254
254
|
console.error(error instanceof Error ? error.message : String(error));
|
|
255
|
-
|
|
256
|
-
process.exit(2);
|
|
255
|
+
return 2;
|
|
257
256
|
}
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
257
|
+
if (parsed.action === 'check') {
|
|
258
|
+
const timeoutSec = parseCheckTimeout(optionValue(args, '--timeout'));
|
|
259
|
+
if (timeoutSec === null) {
|
|
260
|
+
console.error('--timeout 必须是正整数秒');
|
|
261
|
+
return 2;
|
|
261
262
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
const ok = await runUnifiedCheck({
|
|
264
|
+
clientVersion,
|
|
265
|
+
timeoutSec,
|
|
266
|
+
...(parsed.serverUrl ? { serverUrl: parsed.serverUrl } : {}),
|
|
267
|
+
...(parsed.serverUrlSource ? { serverUrlSource: parsed.serverUrlSource } : {}),
|
|
268
|
+
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
269
|
+
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
270
|
+
...(parsed.local ? { local: true } : {}),
|
|
271
|
+
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
272
|
+
});
|
|
273
|
+
return ok ? 0 : 1;
|
|
266
274
|
}
|
|
267
|
-
if (!COMMAND_MAP.has(
|
|
268
|
-
console.error(`不支持的 action: ${
|
|
269
|
-
console.error(`运行
|
|
270
|
-
|
|
275
|
+
if (parsed.action !== 'connector_list' && !COMMAND_MAP.has(parsed.action)) {
|
|
276
|
+
console.error(`不支持的 action: ${parsed.action}`);
|
|
277
|
+
console.error(`运行 ${commandName} --help 查看所有可用命令`);
|
|
278
|
+
return 2;
|
|
271
279
|
}
|
|
272
|
-
|
|
280
|
+
if (parsed.action === 'page_selected_element' && parsed.outputPath) {
|
|
281
|
+
parsed.payload = { ...parsed.payload, includeScreenshot: true };
|
|
282
|
+
}
|
|
283
|
+
const client = new UnifiedClient({
|
|
284
|
+
...(parsed.serverUrl ? { serverUrl: parsed.serverUrl } : {}),
|
|
285
|
+
requestTimeoutSec: parsed.timeoutSec,
|
|
286
|
+
});
|
|
273
287
|
try {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
288
|
+
const result = await client.invoke(parsed.action, parsed.payload, {
|
|
289
|
+
timeoutSec: parsed.timeoutSec,
|
|
290
|
+
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
291
|
+
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
292
|
+
...(parsed.local ? { local: true } : {}),
|
|
293
|
+
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
294
|
+
});
|
|
295
|
+
if (parsed.outputPath) {
|
|
296
|
+
writeOutput(parsed.action, result, parsed.outputPath);
|
|
280
297
|
}
|
|
281
298
|
else {
|
|
282
|
-
|
|
299
|
+
process.stdout.write(`${parsed.compact ? JSON.stringify(result) : JSON.stringify(result, null, 2)}\n`);
|
|
283
300
|
}
|
|
301
|
+
return 0;
|
|
284
302
|
}
|
|
285
303
|
catch (error) {
|
|
286
304
|
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');
|
|
305
|
+
return 1;
|
|
324
306
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
process.exit(1);
|
|
307
|
+
finally {
|
|
308
|
+
client.disconnect();
|
|
328
309
|
}
|
|
329
310
|
}
|
|
330
|
-
main();
|
|
@@ -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';
|
|
@@ -148,8 +157,6 @@ export interface ManagedBrowserInfo extends BrowserInfoBase {
|
|
|
148
157
|
agentbayContextId?: string;
|
|
149
158
|
/** Stable AgentBay browser context name derived from the managed browser name. */
|
|
150
159
|
agentbayContextName?: string;
|
|
151
|
-
/** Browser-accessible AgentBay session URL when returned by the provider. */
|
|
152
|
-
agentbayAccessUrl?: string;
|
|
153
160
|
createdAt: string;
|
|
154
161
|
account?: ManagedBrowserAccount;
|
|
155
162
|
copiedCookies?: {
|
|
@@ -163,6 +170,8 @@ export interface BrowserListResult {
|
|
|
163
170
|
connectedCount: number;
|
|
164
171
|
defaultBrowserId: string | null;
|
|
165
172
|
browsers: BrowserInfo[];
|
|
173
|
+
/** Partial discovery failures; successfully inspected browser sources remain in the result. */
|
|
174
|
+
warnings?: string[];
|
|
166
175
|
}
|
|
167
176
|
export interface SelectBrowserParams {
|
|
168
177
|
browser?: string;
|