@mearl/client 2.5.1 → 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 +13 -7
- 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/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 +23 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @mearl/client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
统一浏览器 SDK 与 CLI。它会发现当前机器及已配置 cloud-server 下所有 connector 的浏览器,并根据 `browser_list` 返回的全局 `browserId` 自动选择本地 Socket 或云端 WebSocket 链路。
|
|
4
4
|
|
|
5
5
|
提供两种用法:类型安全的具名方法(`getRequests`、`sendRequest` 等),以及灵活的底层 `invoke(action, payload)`。
|
|
6
6
|
|
|
@@ -27,6 +27,10 @@ const result = await client.sendRequest({
|
|
|
27
27
|
|
|
28
28
|
// 底层通用方法(灵活扩展)
|
|
29
29
|
const data = await client.invoke('send_request', { url: '...' });
|
|
30
|
+
|
|
31
|
+
// browser_list 默认聚合本机和所有远端 connector
|
|
32
|
+
const browsers = await client.browserList();
|
|
33
|
+
await client.getLogs({}, { browser: browsers.browsers[0].browserId });
|
|
30
34
|
```
|
|
31
35
|
|
|
32
36
|
## CLI 使用
|
|
@@ -78,6 +82,9 @@ mearl browser_release
|
|
|
78
82
|
| `--compact` | 紧凑输出 |
|
|
79
83
|
| `--output <path>` | 将结果写入文件(如截图) |
|
|
80
84
|
| `--browser <id\|名称>` | 指定目标浏览器 |
|
|
85
|
+
| `--connector <id\|名称>` | 限制到一台远端机器 |
|
|
86
|
+
| `--local` | 只使用当前机器 |
|
|
87
|
+
| `--server <url>` | 覆盖 cloud-server 地址 |
|
|
81
88
|
|
|
82
89
|
## 支持的操作
|
|
83
90
|
|
|
@@ -105,19 +112,18 @@ mearl browser_release
|
|
|
105
112
|
|
|
106
113
|
`browser_release` 仅在用户明确要求结束调试、退出接管或清理会话资源时使用,不作为普通任务收尾。它会释放当前目标浏览器的 debugger/CDP 控制和会话级临时状态(包括设备/时区模拟),保留浏览器及全部标签页;后续浏览器操作可自动重新建立控制。
|
|
107
114
|
|
|
108
|
-
托管浏览器使用独立 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。`copyCookieDomains` 可在首次启动或需要刷新登录态时把控制浏览器指定域的 Cookie 复制到新实例,Cookie 值不会经过 cloud-server,也不会出现在命令结果或日志中;`userAgentMode: "desktop"` 让本地实例使用匹配本机 Chrome 版本的桌面 UA,AgentBay 则复用当前控制浏览器的桌面 UA。`browser_list`
|
|
115
|
+
托管浏览器使用独立 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。`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)。
|
|
109
116
|
|
|
110
117
|
## 架构
|
|
111
118
|
|
|
112
119
|
```
|
|
113
120
|
@mearl/client (CLI / SDK)
|
|
114
|
-
|
|
115
|
-
@mearl/
|
|
116
|
-
|
|
117
|
-
Chrome Extension / CDP
|
|
121
|
+
├─ 本机 → @mearl/native-host → Chrome Extension / CDP
|
|
122
|
+
└─ WebSocket → @mearl/cloud-server → @mearl/cloud-connector
|
|
123
|
+
→ @mearl/native-host → Chrome Extension / CDP
|
|
118
124
|
```
|
|
119
125
|
|
|
120
|
-
|
|
126
|
+
基础设施组件必须避免递归路由:`@mearl/cloud-connector` 使用 `@mearl/client/local` 子路径直连所在机器的 native-host。普通 Agent、CLI 与 MCP 集成均使用包根入口。
|
|
121
127
|
|
|
122
128
|
## 构建
|
|
123
129
|
|
package/dist/check.d.ts
CHANGED
|
@@ -1 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
import { type UnifiedClientOptions, type UnifiedInvokeOptions } from './unified.js';
|
|
2
|
+
interface CheckClient {
|
|
3
|
+
invoke(action: string, data?: Record<string, any>, options?: UnifiedInvokeOptions): Promise<any>;
|
|
4
|
+
disconnect(): void;
|
|
5
|
+
}
|
|
6
|
+
export interface UnifiedCheckOptions {
|
|
7
|
+
clientVersion: string;
|
|
8
|
+
serverUrl?: string;
|
|
9
|
+
serverUrlSource?: string;
|
|
10
|
+
timeoutSec?: number;
|
|
11
|
+
browser?: string;
|
|
12
|
+
connector?: string;
|
|
13
|
+
local?: boolean;
|
|
14
|
+
transport?: 'cdp';
|
|
15
|
+
}
|
|
16
|
+
export interface UnifiedCheckDependencies {
|
|
17
|
+
createClient?: (options: UnifiedClientOptions) => CheckClient;
|
|
18
|
+
writeLine?: (line: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function parseCheckTimeout(value: string | undefined): number | null;
|
|
21
|
+
export declare function runUnifiedCheck(options: UnifiedCheckOptions, dependencies?: UnifiedCheckDependencies): Promise<boolean>;
|
|
22
|
+
export {};
|
package/dist/check.js
CHANGED
|
@@ -1,146 +1,288 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { execSync } from 'node:child_process';
|
|
5
|
-
import { invoke, CLIENT_VERSION, SOCKET_PATH } from './socket.js';
|
|
6
|
-
import { listLiveBrowsers, describeBrowser } from './browserRegistry.js';
|
|
7
|
-
const EXTENSION_INSTALL_URL = 'https://chromewebstore.google.com/detail/mearl/aoehhjnofngknnjefamjbplchbolghkm';
|
|
8
|
-
function resolveManifestDir() {
|
|
9
|
-
switch (process.platform) {
|
|
10
|
-
case 'darwin':
|
|
11
|
-
return path.join(os.homedir(), 'Library/Application Support/Google/Chrome/NativeMessagingHosts');
|
|
12
|
-
case 'linux':
|
|
13
|
-
return path.join(os.homedir(), '.config/google-chrome/NativeMessagingHosts');
|
|
14
|
-
case 'win32':
|
|
15
|
-
return path.join(process.env.PROGRAMDATA || 'C:\\ProgramData', 'Google\\Chrome\\NativeMessagingHosts');
|
|
16
|
-
default:
|
|
17
|
-
return '';
|
|
18
|
-
}
|
|
1
|
+
import { UnifiedClient } from './unified.js';
|
|
2
|
+
function formatError(error) {
|
|
3
|
+
return error instanceof Error ? error.message : String(error);
|
|
19
4
|
}
|
|
20
|
-
function
|
|
5
|
+
function redactServerUrl(serverUrl) {
|
|
21
6
|
try {
|
|
22
|
-
|
|
7
|
+
const parsed = new URL(serverUrl);
|
|
8
|
+
for (const key of parsed.searchParams.keys()) {
|
|
9
|
+
if (key.toLowerCase().includes('token'))
|
|
10
|
+
parsed.searchParams.set(key, 'redacted');
|
|
11
|
+
}
|
|
12
|
+
return parsed.toString();
|
|
23
13
|
}
|
|
24
14
|
catch {
|
|
25
|
-
return
|
|
15
|
+
return serverUrl.replace(/([?&][^=&\s]*token[^=&\s]*=)[^&\s]*/gi, '$1redacted');
|
|
26
16
|
}
|
|
27
17
|
}
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
18
|
+
function browserMatches(browser, selector) {
|
|
19
|
+
const normalized = selector.trim().toLowerCase();
|
|
20
|
+
return (browser.browserId.toLowerCase() === normalized ||
|
|
21
|
+
browser.originalBrowserId?.toLowerCase() === normalized ||
|
|
22
|
+
browser.name.toLowerCase() === normalized ||
|
|
23
|
+
browser.originalName?.toLowerCase() === normalized);
|
|
24
|
+
}
|
|
25
|
+
function findBrowsers(browsers, selector) {
|
|
26
|
+
const exact = browsers.filter(browser => browserMatches(browser, selector));
|
|
27
|
+
if (exact.length > 0)
|
|
28
|
+
return exact;
|
|
29
|
+
const normalized = selector.trim().toLowerCase();
|
|
30
|
+
return browsers.filter(browser => browser.browserId.toLowerCase().includes(normalized) ||
|
|
31
|
+
browser.originalBrowserId?.toLowerCase().includes(normalized) ||
|
|
32
|
+
browser.name.toLowerCase().includes(normalized) ||
|
|
33
|
+
browser.originalName?.toLowerCase().includes(normalized));
|
|
34
|
+
}
|
|
35
|
+
function shortBrowserId(browserId) {
|
|
36
|
+
const parts = browserId.split(':');
|
|
37
|
+
const raw = decodeURIComponent(parts.at(-1) ?? browserId);
|
|
38
|
+
return raw.length > 12 ? `${raw.slice(0, 8)}…` : raw;
|
|
39
|
+
}
|
|
40
|
+
function formatBrowser(browser, isDefault) {
|
|
41
|
+
const transport = browser.transport === 'cdp'
|
|
42
|
+
? ' [CDP]'
|
|
43
|
+
: browser.transport === 'extension'
|
|
44
|
+
? ' [Extension]'
|
|
45
|
+
: '';
|
|
46
|
+
const type = browser.type === 'managed' ? ' [Managed]' : '';
|
|
47
|
+
return ` ${isDefault ? '★' : ' '} ${browser.name} [${shortBrowserId(browser.browserId)}]${type}${transport}`;
|
|
48
|
+
}
|
|
49
|
+
function formatConnector(connector) {
|
|
50
|
+
const id = connector.connectorId.length > 12
|
|
51
|
+
? `${connector.connectorId.slice(0, 8)}…`
|
|
52
|
+
: connector.connectorId;
|
|
53
|
+
const version = connector.version ? ` [v${connector.version}]` : '';
|
|
54
|
+
return ` ${connector.name} [${id}]${version}`;
|
|
55
|
+
}
|
|
56
|
+
function printSection(writeLine, title, items) {
|
|
57
|
+
writeLine(title);
|
|
58
|
+
for (const item of items) {
|
|
59
|
+
const marker = item.status === 'ok' ? '✅' : item.status === 'warning' ? '⚠️ ' : '❌';
|
|
60
|
+
writeLine(` ${marker} ${item.label}`);
|
|
61
|
+
for (const detail of item.details ?? [])
|
|
62
|
+
writeLine(` ${detail}`);
|
|
69
63
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
writeLine('');
|
|
65
|
+
}
|
|
66
|
+
export function parseCheckTimeout(value) {
|
|
67
|
+
const input = value ?? '5';
|
|
68
|
+
if (!/^[1-9]\d*$/.test(input))
|
|
69
|
+
return null;
|
|
70
|
+
const timeoutSec = Number(input);
|
|
71
|
+
return Number.isSafeInteger(timeoutSec) ? timeoutSec : null;
|
|
72
|
+
}
|
|
73
|
+
export async function runUnifiedCheck(options, dependencies = {}) {
|
|
74
|
+
const timeoutSec = options.timeoutSec ?? 5;
|
|
75
|
+
const writeLine = dependencies.writeLine ?? (line => console.error(line));
|
|
76
|
+
const createClient = dependencies.createClient ??
|
|
77
|
+
((clientOptions) => new UnifiedClient(clientOptions));
|
|
78
|
+
const client = createClient({
|
|
79
|
+
...(options.serverUrl ? { serverUrl: options.serverUrl } : {}),
|
|
80
|
+
connectTimeoutSec: timeoutSec,
|
|
81
|
+
requestTimeoutSec: timeoutSec,
|
|
82
|
+
});
|
|
83
|
+
const environment = [
|
|
84
|
+
{ status: 'ok', label: `Unified client v${options.clientVersion}` },
|
|
85
|
+
];
|
|
86
|
+
let connectors = [];
|
|
87
|
+
let browserList = null;
|
|
88
|
+
try {
|
|
89
|
+
if (options.local) {
|
|
90
|
+
environment.push({
|
|
91
|
+
status: 'ok',
|
|
92
|
+
label: options.transport === 'cdp'
|
|
93
|
+
? 'Browser scope: this machine (forced CDP)'
|
|
94
|
+
: 'Browser scope: this machine',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else if (options.serverUrl) {
|
|
98
|
+
const source = options.serverUrlSource ? ` (${options.serverUrlSource})` : '';
|
|
99
|
+
try {
|
|
100
|
+
const connectorList = (await client.invoke('connector_list', {}, { timeoutSec }));
|
|
101
|
+
connectors = Array.isArray(connectorList?.connectors) ? connectorList.connectors : [];
|
|
102
|
+
environment.push({
|
|
103
|
+
status: 'ok',
|
|
104
|
+
label: `Cloud server${source}`,
|
|
105
|
+
details: [
|
|
106
|
+
redactServerUrl(options.serverUrl),
|
|
107
|
+
`${connectors.length} connector${connectors.length === 1 ? '' : 's'} connected`,
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
environment.push({
|
|
113
|
+
status: 'error',
|
|
114
|
+
label: `Cloud server or connector discovery failed${source}`,
|
|
115
|
+
details: [formatError(error), redactServerUrl(options.serverUrl)],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
environment.push({ status: 'ok', label: 'Local mode (no cloud-server configuration)' });
|
|
121
|
+
}
|
|
122
|
+
const inventoryOptions = {
|
|
123
|
+
timeoutSec,
|
|
124
|
+
...(options.local ? { local: true } : {}),
|
|
125
|
+
...(options.transport ? { transport: options.transport } : {}),
|
|
126
|
+
...(!options.local && options.connector ? { connector: options.connector } : {}),
|
|
127
|
+
};
|
|
74
128
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
129
|
+
if (options.serverUrl &&
|
|
130
|
+
!options.local &&
|
|
131
|
+
environment.some(item => item.status === 'error')) {
|
|
132
|
+
browserList = (await client.invoke('browser_list', {}, {
|
|
133
|
+
timeoutSec,
|
|
134
|
+
local: true,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
browserList = (await client.invoke('browser_list', {}, inventoryOptions));
|
|
139
|
+
}
|
|
140
|
+
environment.push({ status: 'ok', label: 'Browser inventory' });
|
|
141
|
+
if (browserList.warnings?.length) {
|
|
142
|
+
environment.push({
|
|
143
|
+
status: 'warning',
|
|
144
|
+
label: 'Some browser sources could not be inspected',
|
|
145
|
+
details: browserList.warnings,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
77
148
|
}
|
|
78
149
|
catch (error) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
150
|
+
environment.push({
|
|
151
|
+
status: 'error',
|
|
152
|
+
label: 'Browser inventory failed',
|
|
153
|
+
details: [formatError(error)],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const connected = (browserList?.browsers ?? []).filter(browser => browser.status === 'connected');
|
|
157
|
+
let targets = connected;
|
|
158
|
+
if (options.browser) {
|
|
159
|
+
const matches = findBrowsers(connected, options.browser);
|
|
160
|
+
if (matches.length === 0) {
|
|
161
|
+
environment.push({
|
|
162
|
+
status: 'error',
|
|
163
|
+
label: `Browser "${options.browser}" is not connected`,
|
|
164
|
+
details: ['Run `mearl browser_list` and use an exact global browserId.'],
|
|
165
|
+
});
|
|
166
|
+
targets = [];
|
|
167
|
+
}
|
|
168
|
+
else if (matches.length > 1) {
|
|
169
|
+
environment.push({
|
|
170
|
+
status: 'error',
|
|
171
|
+
label: `Browser selector "${options.browser}" is ambiguous`,
|
|
172
|
+
details: matches.map(browser => browser.browserId),
|
|
173
|
+
});
|
|
174
|
+
targets = [];
|
|
84
175
|
}
|
|
85
176
|
else {
|
|
86
|
-
|
|
177
|
+
targets = matches;
|
|
87
178
|
}
|
|
88
179
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
if (runtime?.browser) {
|
|
99
|
-
versions.push(['browser', `Chrome ${runtime.browser}`]);
|
|
100
|
-
}
|
|
101
|
-
// Print Versions section
|
|
102
|
-
const nameWidth = Math.max(...versions.map(([n]) => n.length));
|
|
103
|
-
console.error('\nVersions');
|
|
104
|
-
for (const [name, value] of versions) {
|
|
105
|
-
console.error(` ${name.padEnd(nameWidth)} ${value}`);
|
|
106
|
-
}
|
|
107
|
-
console.error('');
|
|
108
|
-
// Print Environment section
|
|
109
|
-
console.error('Environment');
|
|
110
|
-
let hasError = false;
|
|
111
|
-
for (const e of env) {
|
|
112
|
-
console.error(` ${e.ok ? '✅' : '❌'} ${e.label}`);
|
|
113
|
-
if (!e.ok) {
|
|
114
|
-
hasError = true;
|
|
115
|
-
for (const fix of e.fix ?? [])
|
|
116
|
-
console.error(` ${fix}`);
|
|
180
|
+
if (!options.browser && connected.length === 0 && browserList) {
|
|
181
|
+
environment.push({
|
|
182
|
+
status: 'error',
|
|
183
|
+
label: 'No connected browser',
|
|
184
|
+
details: [
|
|
185
|
+
'Open Chrome with the Mearl extension, start a managed browser, or connect a browser host.',
|
|
186
|
+
],
|
|
187
|
+
});
|
|
117
188
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
189
|
+
const browserChecks = [];
|
|
190
|
+
await Promise.all(targets.map(async (browser) => {
|
|
191
|
+
try {
|
|
192
|
+
const versions = (await client.invoke('get_versions', {}, {
|
|
193
|
+
timeoutSec,
|
|
194
|
+
browser: browser.browserId,
|
|
195
|
+
...(options.transport ? { transport: options.transport } : {}),
|
|
196
|
+
}));
|
|
197
|
+
const details = [
|
|
198
|
+
versions.nativeHost ? `native-host v${versions.nativeHost}` : null,
|
|
199
|
+
versions.extension ? `extension v${versions.extension}` : null,
|
|
200
|
+
versions.browser ? `Chrome ${versions.browser}` : null,
|
|
201
|
+
].filter((value) => value !== null);
|
|
202
|
+
let accessUrlWarning;
|
|
203
|
+
if (browser.provider === 'agentbay' && options.browser) {
|
|
204
|
+
try {
|
|
205
|
+
const access = (await client.invoke('get_agentbay_access_url', {}, {
|
|
206
|
+
timeoutSec,
|
|
207
|
+
browser: browser.browserId,
|
|
208
|
+
}));
|
|
209
|
+
if (!access.agentbayAccessUrl) {
|
|
210
|
+
throw new Error('AgentBay did not return a browser access URL');
|
|
211
|
+
}
|
|
212
|
+
details.push(`AgentBay access URL (temporary, sensitive): ${access.agentbayAccessUrl}`);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
accessUrlWarning = formatError(error);
|
|
216
|
+
details.push(`AgentBay access URL unavailable: ${accessUrlWarning}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
browserChecks.push({
|
|
220
|
+
browser,
|
|
221
|
+
item: {
|
|
222
|
+
status: accessUrlWarning ? 'warning' : 'ok',
|
|
223
|
+
label: accessUrlWarning
|
|
224
|
+
? 'Browser command path ready; AgentBay access URL unavailable'
|
|
225
|
+
: 'Browser command path',
|
|
226
|
+
details,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
const message = formatError(error);
|
|
232
|
+
browserChecks.push({
|
|
233
|
+
browser,
|
|
234
|
+
item: message.includes('Unknown action')
|
|
235
|
+
? {
|
|
236
|
+
status: 'warning',
|
|
237
|
+
label: 'Browser is reachable but runtime version details are unavailable',
|
|
238
|
+
details: ['Update @mearl/native-host for complete diagnostics.'],
|
|
239
|
+
}
|
|
240
|
+
: {
|
|
241
|
+
status: 'error',
|
|
242
|
+
label: 'Browser command path failed',
|
|
243
|
+
details: [message],
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}));
|
|
248
|
+
writeLine('');
|
|
249
|
+
printSection(writeLine, 'Environment', environment);
|
|
250
|
+
if (connectors.length > 0) {
|
|
251
|
+
writeLine('Cloud connectors');
|
|
252
|
+
for (const connector of connectors)
|
|
253
|
+
writeLine(formatConnector(connector));
|
|
254
|
+
writeLine('');
|
|
134
255
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
256
|
+
const listedBrowsers = browserList?.browsers ?? [];
|
|
257
|
+
if (listedBrowsers.length > 0) {
|
|
258
|
+
writeLine('Browsers');
|
|
259
|
+
for (const browser of listedBrowsers) {
|
|
260
|
+
writeLine(formatBrowser(browser, browser.browserId === browserList?.defaultBrowserId));
|
|
261
|
+
const check = browserChecks.find(item => item.browser.browserId === browser.browserId);
|
|
262
|
+
if (check) {
|
|
263
|
+
const marker = check.item.status === 'ok' ? '✅' : check.item.status === 'warning' ? '⚠️ ' : '❌';
|
|
264
|
+
writeLine(` ${marker} ${check.item.label}`);
|
|
265
|
+
for (const detail of check.item.details ?? [])
|
|
266
|
+
writeLine(` ${detail}`);
|
|
267
|
+
}
|
|
268
|
+
else if (browser.status !== 'connected') {
|
|
269
|
+
writeLine(` ⚪ ${browser.status}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
writeLine('');
|
|
273
|
+
}
|
|
274
|
+
const hasError = environment.some(item => item.status === 'error') ||
|
|
275
|
+
browserChecks.some(check => check.item.status === 'error');
|
|
276
|
+
if (hasError) {
|
|
277
|
+
writeLine('⚠️ Mearl checks failed. See the browser or source details above.');
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
writeLine(options.browser
|
|
281
|
+
? '🎉 Selected browser is ready!'
|
|
282
|
+
: `🎉 All ${targets.length} connected browser${targets.length === 1 ? '' : 's'} are ready!`);
|
|
283
|
+
return true;
|
|
142
284
|
}
|
|
143
|
-
|
|
144
|
-
|
|
285
|
+
finally {
|
|
286
|
+
client.disconnect();
|
|
145
287
|
}
|
|
146
288
|
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
export {};
|
|
2
|
+
export interface MearlCliOptions {
|
|
3
|
+
commandName?: string;
|
|
4
|
+
clientVersion?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function runMearlCli(args: string[], options?: MearlCliOptions): Promise<number>;
|