@mearl/client 3.0.0-beta.5 → 3.0.0-beta.8
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 +18 -14
- package/dist/actionTimeouts.js +0 -3
- package/dist/autoUpdate.js +2 -1
- package/dist/bin.js +12 -5
- package/dist/capabilityHelp.d.ts +4 -0
- package/dist/capabilityHelp.js +68 -0
- package/dist/check.js +1 -1
- package/dist/cli.js +17 -16
- package/dist/commandHelp.js +3 -3
- package/dist/commandSuggestions.d.ts +3 -0
- package/dist/commandSuggestions.js +16 -5
- package/dist/commands.d.ts +50 -55
- package/dist/commands.js +41 -41
- package/dist/guide.d.ts +46 -0
- package/dist/guide.js +71 -0
- package/dist/guides/api-browser-setup.md +93 -0
- package/dist/guides/api-page-automation.md +140 -0
- package/dist/guides/api-page-debugging.md +39 -0
- package/dist/guides/api-page-environment.md +37 -0
- package/dist/guides/api-request-debugging.md +54 -0
- package/dist/guides/browser-tool-cooperation.md +23 -0
- package/dist/guides/core.md +60 -0
- package/dist/guides/related-skills.md +32 -0
- package/dist/guides/scripting.md +21 -0
- package/dist/guides/troubleshooting.md +170 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -5
- package/dist/plugins/officialPluginInstaller.js +2 -1
- package/dist/plugins/pluginRegistry.js +9 -3
- package/dist/plugins/runtime/pluginDiagnostics.js +1 -1
- package/dist/plugins/runtime/pluginHostServer.js +1 -1
- package/dist/plugins/runtime/pluginPageManager.js +2 -2
- package/dist/plugins/runtime/pluginStateManager.js +1 -1
- package/dist/plugins/runtime/pluginTaskManager.js +2 -2
- package/dist/plugins/sdk/plugin.d.ts +2 -0
- package/dist/plugins/sdk/plugin.js +17 -9
- package/dist/script.js +2 -1
- package/dist/socket.js +32 -8
- package/dist/unified.js +11 -1
- package/dist/unifiedConfig.d.ts +4 -0
- package/dist/unifiedConfig.js +11 -0
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @mearl/client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI、TypeScript SDK 与脚本入口共用浏览器路由和插件运行时。运行时连接与安装由 `@mearl/setup` 管理。
|
|
4
|
+
|
|
5
|
+
首次使用当前版本可运行 `mearl guide`,默认只读取核心工作流。`mearl guide --list` 列出专题;例如 `mearl guide run` 读取 SDK 组合调用,`mearl guide environment` 读取设备与 App 指引。指南只读 npm 包内资源,不连接浏览器、加载插件、安装环境或触发自动更新。动作参数继续使用 `mearl <action> --help`,目标的已安装能力继续按现有目录查询。
|
|
6
|
+
|
|
7
|
+
指南正文唯一维护在本包的 `guides/`,构建时复制到 `dist/guides/`,`pnpm check:skills` 校验一致性。CLI 输出将本地文档链接转换为下一条指南命令。`skills/mearl/` 只分发精简的 `SKILL.md`,不附带正文副本;仅使用 MCP、无法执行 CLI 时,直接使用当前工具说明与 schema。组织发行版的安装、认证与业务补充仍由其独立 Skill 提供。
|
|
4
8
|
|
|
5
9
|
统一浏览器 SDK 与 CLI。它会发现当前机器及已配置 cloud-server 下所有 connector 的浏览器,并根据 `browser_list` 返回的全局 `browserId` 自动选择本地 Socket 或云端 WebSocket 链路。
|
|
6
10
|
|
|
@@ -9,9 +13,9 @@
|
|
|
9
13
|
## 安装
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
|
-
npm install @mearl/client
|
|
16
|
+
npm install @mearl/client@beta
|
|
13
17
|
# 或
|
|
14
|
-
pnpm add @mearl/client
|
|
18
|
+
pnpm add @mearl/client@beta
|
|
15
19
|
```
|
|
16
20
|
|
|
17
21
|
## SDK 使用
|
|
@@ -60,7 +64,7 @@ mearl check
|
|
|
60
64
|
mearl check --local --json
|
|
61
65
|
|
|
62
66
|
# 更新本地环境
|
|
63
|
-
npx @mearl/setup update --local
|
|
67
|
+
npx @mearl/setup@beta update --local
|
|
64
68
|
|
|
65
69
|
# 查看某个 action 的帮助
|
|
66
70
|
mearl <action> --help
|
|
@@ -224,8 +228,8 @@ stdout 在管道或重定向时默认输出紧凑 JSON,在交互终端默认
|
|
|
224
228
|
未指定字段时默认返回正文。
|
|
225
229
|
|
|
226
230
|
`send_request` 默认携带所选浏览器中匹配完整目标 URL 的普通 Cookie,以及目标 URL 第一方站点对应的 CHIPS Cookie。例如 `https://app.example.com/` 使用 `https://example.com` 分区,不依赖已打开页面。站点按协议和公共后缀列表(含私有后缀)确定;排除其他站点、其他协议、跨站祖先及不透明分区。保留不同路径下的同名 Cookie,不额外拼入父站点的 host-only Cookie。嵌入其他站点的第三方分区、页面签名或完整页面身份仍需使用目标页面中的 `page_eval` 请求。
|
|
227
|
-
响应中的 `Set-Cookie` 会更新原浏览器,后续调用可以使用刷新后的登录态;`withCookies:false` 同时关闭自动携带与响应回写。扩展由 Chrome 处理响应 Cookie;CDP 的 Node 请求在返回前通过原页面的浏览器连接回写,支持 HttpOnly、默认路径、过期时间和删除。CDP 响应存在 `Set-Cookie` 时附带 `cookieSync: { updatedCount, rejectedCount, failedCount }`,不返回 Cookie 值;无法确定分区的 Partitioned 响应 Cookie 计入拒绝数。同步失败不撤销已完成的 HTTP 操作,应检查状态而非直接重发业务请求。
|
|
228
|
-
扩展的临时凭据注入仅匹配原始完整请求 URL,重定向由 Chrome 按新 URL 选择 Cookie。CDP 的 Node 请求(含
|
|
231
|
+
响应中的 `Set-Cookie` 会更新原浏览器,后续调用可以使用刷新后的登录态;`withCookies:false` 同时关闭自动携带与响应回写。扩展由 Chrome 处理响应 Cookie;CDP 的 Node 请求在返回前通过原页面的浏览器连接回写,支持 HttpOnly、默认路径、过期时间和删除。CDP 响应存在 `Set-Cookie` 时附带 `cookieSync: { updatedCount, rejectedCount, failedCount }`,不返回 Cookie 值;无法确定分区的 Partitioned 响应 Cookie 计入拒绝数。同步失败不撤销已完成的 HTTP 操作,应检查状态而非直接重发业务请求。Protocol 普通请求及流式请求使用相同的响应刷新机制。
|
|
232
|
+
扩展的临时凭据注入仅匹配原始完整请求 URL,重定向由 Chrome 按新 URL 选择 Cookie。CDP 的 Node 请求(含 Protocol 流式请求)不自动跟随重定向,避免将原 URL 的 Cookie 或签名传到其他地址。
|
|
229
233
|
|
|
230
234
|
Mearl 默认聚合当前机器和已配置 cloud-server 下的浏览器,并通过 `browser_list` 返回的全局
|
|
231
235
|
`browserId` 自动路由。以下覆盖参数仅在限定宿主或连接、传输排障时使用,因此不出现在默认 help:
|
|
@@ -242,7 +246,7 @@ Mearl 默认聚合当前机器和已配置 cloud-server 下的浏览器,并通
|
|
|
242
246
|
client、native-host、cloud-connector 和扩展的最新发布版本。发布版本查询最长等待约 1.5 秒,
|
|
243
247
|
无法访问发布源时会提示无法确认更新,不影响浏览器可用性检查。`--json` 输出机器可读的检查结果,
|
|
244
248
|
与 `--updates` 同用时包含更新状态。发现更新时会提示适用的命令:client/native-host 使用
|
|
245
|
-
`npx @mearl/setup update`,setup 管理的扩展使用 `npx @mearl/setup extension`。
|
|
249
|
+
`npx @mearl/setup@beta update`,setup 管理的扩展使用 `npx @mearl/setup extension`。
|
|
246
250
|
|
|
247
251
|
全局安装的 `mearl` CLI 默认在每天首次使用后启动独立后台进程,查询最新发布版本,并比较
|
|
248
252
|
client、native-host、cloud-server 和 MCP server 的已安装版本。发现更新时调用
|
|
@@ -267,7 +271,7 @@ client、native-host、cloud-server 和 MCP server 的已安装版本。发现
|
|
|
267
271
|
安装 skill 并自动注册命令,或直接注册已有入口:
|
|
268
272
|
|
|
269
273
|
```bash
|
|
270
|
-
npx @mearl/setup plugin install https://github.com/
|
|
274
|
+
npx @mearl/setup plugin install https://github.com/mearlhq/mearl --skill browser-tab-manager --yes
|
|
271
275
|
npx @mearl/setup plugin /absolute/path/to/browser-tab-manager
|
|
272
276
|
npx @mearl/setup plugin uninstall tabs
|
|
273
277
|
mearl tabs summary
|
|
@@ -309,7 +313,7 @@ const result = await invokePlugin(
|
|
|
309
313
|
);
|
|
310
314
|
```
|
|
311
315
|
|
|
312
|
-
MCP Server 通过固定工具 `plugin_invoke`
|
|
316
|
+
MCP Server 通过固定工具 `plugin_invoke` 接受 `plugin`、`command` 和 `input`。SDK 与 MCP 使用已注册插件;安装宿主管理的插件目录可触发匹配当前 client 版本的 setup 安装与一次重试,其他未注册名称返回 `PLUGIN_NOT_REGISTERED`。
|
|
313
317
|
|
|
314
318
|
长命令使用本机持久任务服务。服务按注册目录隔离并自动启动,CLI、SDK、MCP 与工具箱共享任务记录;调用方退出不取消执行。服务重启后将未完成任务标记为 `interrupted`,不自动重放。后台任务固定接受时的源码快照、工作目录与环境;第三方 JS 依赖应预先打包进 Skill,状态文件放在源码目录之外。快照只固定源码和相对资源,不预解析 JS 依赖;缺失依赖和语法错误由执行器加载代码时报告。
|
|
315
319
|
|
|
@@ -435,9 +439,9 @@ Preact、Hooks 和 HTM 运行时。页面从 `window.mearl.ui` 读取 `html`、`
|
|
|
435
439
|
|
|
436
440
|
工具箱面向人,CLI 面向 Agent:复用业务操作、保持结果一致即可,不要求共享中间态。表单草稿、选择、预览等纯 UI 状态默认放浏览器内存;确需刷新或重新打开后恢复时再用浏览器存储。sandbox 页不能假定可直接访问 `localStorage`、IndexedDB 或 `chrome.storage`,应使用宿主提供的能力。只有业务记录、关闭页面后继续的任务、安全重试回执或受消息大小限制的资源传输才需要 Node 状态,并说明其生命周期及清理方式。
|
|
437
441
|
|
|
438
|
-
|
|
442
|
+
Skill 插件可提供自定义请求与数据处理命令。CLI 使用 `mearl <plugin> <command>`,业务插件通过 `app.invoke(plugin, { command, input })` 复用同一操作;插件名称、认证方式、请求字段和上传约定以对应 Skill 的契约为准。
|
|
439
443
|
|
|
440
|
-
Node 插件在运行 CLI、SDK 或 MCP Server 的宿主上执行,不随浏览器或 connector
|
|
444
|
+
Node 插件在运行 CLI、SDK 或 MCP Server 的宿主上执行,不随浏览器或 connector 转发。插件读取的文件路径属于该执行宿主;需要浏览器执行的操作应使用明确的浏览器目标。安装宿主可通过能力目录提供按需安装信息。
|
|
441
445
|
|
|
442
446
|
## 支持的操作
|
|
443
447
|
|
|
@@ -445,7 +449,7 @@ Node 插件在运行 CLI、SDK 或 MCP Server 的宿主上执行,不随浏览
|
|
|
445
449
|
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
446
450
|
| API 调试 | `tab_checkpoint` `get_requests` `get_logs` `get_events` |
|
|
447
451
|
| Mock & 规则 | `set_mock` `get_mocks` `set_rule` `get_rules` |
|
|
448
|
-
| 网络请求 | `send_request` `
|
|
452
|
+
| 网络请求 | `send_request` `request_permission` |
|
|
449
453
|
| 标签页 | `tab_open` `tab_list` `tab_close` |
|
|
450
454
|
| 页面感知 | `page_snapshot` `page_inspect` `page_screenshot` `page_frames` |
|
|
451
455
|
| 页面操作 | `page_click` `page_drag` `page_type` `page_hover` `page_scroll` `page_press` `page_wait` `page_navigate` `page_upload` `page_eval` |
|
|
@@ -586,7 +590,7 @@ SDK 使用 `tasks.checkpoint(taskId)` / `tasks.resume(taskId)`。当前仅支持
|
|
|
586
590
|
|
|
587
591
|
插件 CLI 调用失败以非零退出码和 stderr JSON `{ "error": { "code", "message" } }` 返回,保留 SDK/MCP 的 code,不输出任意异常附加字段。`PLUGIN_TASK_WAIT_TIMEOUT` 仅停止等待;`PLUGIN_EXECUTION_CANCELLED` / `PLUGIN_EXECUTION_DEADLINE` 表示执行取消或到期;`PLUGIN_PAGE_TARGET_CHANGED` / `PLUGIN_PAGE_INVALIDATED` 要求重新获取页面实例;`PLUGIN_STATE_CONFLICT` 需重新读取 CAS 版本;`PLUGIN_MODEL_CAPABILITY_UNSUPPORTED` 表示声明能力不支持;`PLUGIN_INVOCATION_CYCLE` / `PLUGIN_INVOCATION_DEPTH` 拒绝循环或过深组合。进程树无法确认停止时保留失败记录并拒绝恢复。
|
|
588
592
|
|
|
589
|
-
|
|
593
|
+
工具箱内显式页面 attach 缺少站点授权时会申请权限,由用户确认后继续;后台页面运行时不会自动弹窗申请。
|
|
590
594
|
|
|
591
595
|
模型列表可通过 SDK `PluginModelClient.list()` 或工具箱 `host.model.list` 查询。模型项携带可用时的 `reasoningEfforts` 和 `defaultReasoningEffort`,界面按模型支持范围提供思考级别。宿主合并并发发现请求,已有模型目录立即返回,过期后后台刷新;刷新失败保留可用目录并短暂退避,不创建推理会话。状态批量读取使用 `get-many` 和 `keys`(SDK `getMany`、工具箱 `host.state.getMany`),一次读取同一 revision 的最多 128 个键,含缺失键的 CAS version,结果上限 1 MiB。
|
|
592
596
|
|
|
@@ -597,7 +601,7 @@ SDK 使用 `tasks.checkpoint(taskId)` / `tasks.resume(taskId)`。当前仅支持
|
|
|
597
601
|
|
|
598
602
|
## 受管理插件目录
|
|
599
603
|
|
|
600
|
-
安装宿主可通过 client/plugin 的 replaceOfficialPluginCatalog(revision, plugins) 保存已选 preset 的插件目录。revision 为确切清单摘要,每项声明 description、skill 和安装器参数 installArgs,可选 setup 分组。目录以原子文件替换保存于当前插件注册目录的 .official-catalog.json
|
|
604
|
+
安装宿主可通过 client/plugin 的 replaceOfficialPluginCatalog(revision, plugins) 保存已选 preset 的插件目录。revision 为确切清单摘要,每项声明 description、skill 和安装器参数 installArgs,可选 setup 分组。目录以原子文件替换保存于当前插件注册目录的 .official-catalog.json;目录默认为空。安装源码与 preset 的信任校验由安装宿主负责,目录本身不触发安装。
|
|
601
605
|
|
|
602
606
|
findOfficialPlugin/listOfficialPluginNames 每次读取当前目录,继续复用同一 setup 入口完成缺失插件安装。readOfficialPluginCatalog 向安装状态页报告损坏;普通插件和浏览器命令忽略损坏的可选目录,不影响已注册能力。OFFICIAL_PLUGINS 兼容导出反映本进程最近读取的目录。
|
|
603
607
|
|
package/dist/actionTimeouts.js
CHANGED
|
@@ -26,9 +26,6 @@ export function resolveActionTimeoutSec(action, data, fallback = 15) {
|
|
|
26
26
|
: 0;
|
|
27
27
|
return Math.max(fallback, Math.ceil(((data.timeout ?? 30000) + transferTimeoutMs + 5000) / 1000));
|
|
28
28
|
}
|
|
29
|
-
if (action === 'send_mtop_request') {
|
|
30
|
-
return Math.max(fallback, Math.ceil(((data.timeout ?? 10000) + 5000) / 1000));
|
|
31
|
-
}
|
|
32
29
|
if (action === 'page_download')
|
|
33
30
|
return Math.max(fallback, Math.ceil((Number(data.timeout ?? 30000) + 10000) / 1000));
|
|
34
31
|
if (action === 'page_wait') {
|
package/dist/autoUpdate.js
CHANGED
|
@@ -4,6 +4,7 @@ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync,
|
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { delimiter, dirname, join, resolve, win32 } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { npmExecEnvironment } from '@mearl/daemon-core';
|
|
7
8
|
import { isNewerRelease, parseReleaseVersion } from './check.js';
|
|
8
9
|
import { loadCliAutoUpdateIntervalDays } from './unifiedConfig.js';
|
|
9
10
|
const CONFIG_DIR = join(homedir(), '.mearl');
|
|
@@ -207,7 +208,7 @@ export function setupUpdateCommand(version, packageRoot, profiles, updateClient)
|
|
|
207
208
|
if (!parseReleaseVersion(version))
|
|
208
209
|
throw new Error('A valid Mearl release is required');
|
|
209
210
|
const [major, minor] = version.split('.');
|
|
210
|
-
const env =
|
|
211
|
+
const env = npmExecEnvironment();
|
|
211
212
|
delete env.MEARL_AUTO_UPDATE_CLIENT_ROOT;
|
|
212
213
|
delete env.MEARL_AUTO_UPDATE_CLIENT_ONLY;
|
|
213
214
|
delete env.MEARL_AUTO_UPDATE_PROFILES;
|
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { CLIENT_VERSION } from './runtimePaths.js';
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
if (args[0] === 'guide') {
|
|
5
|
+
const { runGuideCli } = await import('./guide.js');
|
|
6
|
+
process.exitCode = runGuideCli(args.slice(1));
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
const { runMearlCli } = await import('./cli.js');
|
|
10
|
+
process.exitCode = await runMearlCli(args);
|
|
11
|
+
const { scheduleCliAutoUpdate } = await import('./autoUpdate.js');
|
|
12
|
+
scheduleCliAutoUpdate(CLIENT_VERSION);
|
|
13
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BrowserCapabilityCatalog } from '@mearl/browser-core';
|
|
2
|
+
/** Help reads an existing target with a short budget; it never installs an environment. */
|
|
3
|
+
export declare function readHelpCapabilities(args: string[]): Promise<BrowserCapabilityCatalog | undefined>;
|
|
4
|
+
export declare function formatCapabilityHelp(catalog: BrowserCapabilityCatalog | undefined, detailed?: boolean): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { resolveServer } from './unifiedConfig.js';
|
|
2
|
+
import { listLiveBrowsers } from './browserRegistry.js';
|
|
3
|
+
import { UnifiedClient } from './unified.js';
|
|
4
|
+
/** Help reads an existing target with a short budget; it never installs an environment. */
|
|
5
|
+
export async function readHelpCapabilities(args) {
|
|
6
|
+
const value = (flag) => {
|
|
7
|
+
const index = args.indexOf(flag);
|
|
8
|
+
return index < 0 ? args.find(arg => arg.startsWith(`${flag}=`))?.slice(flag.length + 1) : args[index + 1];
|
|
9
|
+
};
|
|
10
|
+
const browser = value('--browser');
|
|
11
|
+
const session = value('--session');
|
|
12
|
+
const connector = value('--connector');
|
|
13
|
+
const { serverUrl } = resolveServer(value('--server'));
|
|
14
|
+
const local = args.includes('--local') || args.includes('--cdp');
|
|
15
|
+
if (local && connector || session === 'new')
|
|
16
|
+
return;
|
|
17
|
+
if (!browser && !connector && (local || !serverUrl) && !session && !listLiveBrowsers().length)
|
|
18
|
+
return;
|
|
19
|
+
const client = new UnifiedClient({ connectTimeoutSec: 1, requestTimeoutSec: 1, ...(serverUrl ? { serverUrl } : {}) });
|
|
20
|
+
let timer;
|
|
21
|
+
try {
|
|
22
|
+
const result = await Promise.race([
|
|
23
|
+
client.invoke('get_capabilities', {}, {
|
|
24
|
+
...(browser ? { browser } : {}), ...(session ? { session } : {}),
|
|
25
|
+
...(connector ? { connector } : {}), ...(local ? { local: true } : {}),
|
|
26
|
+
...(args.includes('--cdp') ? { transport: 'cdp' } : {}),
|
|
27
|
+
}),
|
|
28
|
+
new Promise(resolve => { timer = setTimeout(() => resolve(undefined), 1500); }),
|
|
29
|
+
]);
|
|
30
|
+
if (result && Array.isArray(result.actions) && Array.isArray(result.apps?.entries))
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Static help remains usable when the target is offline or does not expose a catalog.
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
client.disconnect();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function formatCapabilityHelp(catalog, detailed = false) {
|
|
42
|
+
if (!catalog)
|
|
43
|
+
return '当前浏览器能力暂不可读;以下为静态帮助。连接后再次运行 --help 可查看已安装 App。';
|
|
44
|
+
const lines = [
|
|
45
|
+
'当前浏览器已安装的 App:',
|
|
46
|
+
];
|
|
47
|
+
if (!catalog.apps.entries.length)
|
|
48
|
+
lines.push(catalog.apps.status === 'available' ? ' 暂无 App 档案;设备模拟可独立使用。' : ' App 目录暂不可读,不代表未安装。');
|
|
49
|
+
for (const app of catalog.apps.entries) {
|
|
50
|
+
lines.push(` ${app.id} — ${app.label}${app.available ? '' : '(暂不可用)'}`);
|
|
51
|
+
if (!detailed)
|
|
52
|
+
continue;
|
|
53
|
+
lines.push(` 能力:${app.features.join(', ')}`);
|
|
54
|
+
if (app.usage?.skills?.length)
|
|
55
|
+
lines.push(` Skill:${app.usage.skills.join(', ')}`);
|
|
56
|
+
if (app.usage?.description)
|
|
57
|
+
lines.push(` ${app.usage.description}`);
|
|
58
|
+
if (app.usage?.limitations?.length)
|
|
59
|
+
lines.push(...app.usage.limitations.map(line => ` 限制:${line}`));
|
|
60
|
+
if (app.usage?.documentation?.length)
|
|
61
|
+
lines.push(...app.usage.documentation.map(doc => ` 说明:${doc.title} ${doc.url}`));
|
|
62
|
+
}
|
|
63
|
+
if (detailed)
|
|
64
|
+
lines.push(...catalog.limitations.map(line => ` ${line}`));
|
|
65
|
+
else
|
|
66
|
+
lines.push(' App 用法与限制:mearl set_app_profile --help');
|
|
67
|
+
return lines.join('\n');
|
|
68
|
+
}
|
package/dist/check.js
CHANGED
|
@@ -4,7 +4,7 @@ const UPDATE_SOURCES = {
|
|
|
4
4
|
client: 'https://registry.npmjs.org/@mearl%2Fclient/latest',
|
|
5
5
|
'native-host': 'https://registry.npmjs.org/@mearl%2Fnative-host/latest',
|
|
6
6
|
'cloud-connector': 'https://registry.npmjs.org/@mearl%2Fcloud-connector/latest',
|
|
7
|
-
extension: 'https://github.com/
|
|
7
|
+
extension: 'https://github.com/mearlhq/mearl/releases/latest/download/config.json',
|
|
8
8
|
};
|
|
9
9
|
export function parseReleaseVersion(value) {
|
|
10
10
|
if (typeof value !== 'string')
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readHelpCapabilities, formatCapabilityHelp } from './capabilityHelp.js';
|
|
1
2
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import { resolve } from 'node:path';
|
|
3
4
|
import { Command, CommanderError } from 'commander';
|
|
@@ -10,7 +11,7 @@ import { formatCommandHelp } from './commandHelp.js';
|
|
|
10
11
|
import { CLIENT_VERSION } from './socket.js';
|
|
11
12
|
import { UnifiedClient } from './unified.js';
|
|
12
13
|
import { findPluginEntry, findPluginRegistration, findOfficialPlugin, listPluginNames, listOfficialPluginNames, loadRegisteredPlugin, PluginError, } from './plugins/sdk/plugin.js';
|
|
13
|
-
import {
|
|
14
|
+
import { resolveServer } from './unifiedConfig.js';
|
|
14
15
|
const TOP_LEVEL_COMMAND_NAME_WIDTH = 22;
|
|
15
16
|
const CATEGORIES = [
|
|
16
17
|
{
|
|
@@ -20,9 +21,9 @@ const CATEGORIES = [
|
|
|
20
21
|
{ title: 'Mock & 规则', names: ['set_mock', 'get_mocks', 'set_rule', 'get_rules'] },
|
|
21
22
|
{
|
|
22
23
|
title: '网络请求',
|
|
23
|
-
names: ['send_request', '
|
|
24
|
+
names: ['send_request', 'request_permission'],
|
|
24
25
|
},
|
|
25
|
-
{ title: '标签页', names: ['tab_open', 'tab_list', 'tab_group', 'tab_close'
|
|
26
|
+
{ title: '标签页', names: ['tab_open', 'tab_list', 'tab_group', 'tab_close'] },
|
|
26
27
|
{
|
|
27
28
|
title: '页面感知',
|
|
28
29
|
names: ['page_snapshot', 'page_inspect', 'page_screenshot', 'page_frames'],
|
|
@@ -60,6 +61,7 @@ const CATEGORIES = [
|
|
|
60
61
|
},
|
|
61
62
|
];
|
|
62
63
|
const BUILTIN_COMMANDS = [
|
|
64
|
+
{ name: 'guide', description: '按需读取与当前 CLI 配套的本地指南', examples: ['guide', 'guide environment'] },
|
|
63
65
|
{
|
|
64
66
|
name: 'run',
|
|
65
67
|
description: '执行预注入 mearl SDK 的 Node.js 脚本',
|
|
@@ -127,6 +129,7 @@ function printUsage(commandName, version) {
|
|
|
127
129
|
` ${commandName} <action> --help`,
|
|
128
130
|
` ${commandName} check [选项]`,
|
|
129
131
|
` ${commandName} run [--file <path>] [选项]`,
|
|
132
|
+
` ${commandName} guide [topic]`,
|
|
130
133
|
'',
|
|
131
134
|
'常用选项:',
|
|
132
135
|
' --payload <json> 直接传入 JSON 参数',
|
|
@@ -142,6 +145,7 @@ function printUsage(commandName, version) {
|
|
|
142
145
|
' -v, --version 查看版本',
|
|
143
146
|
'',
|
|
144
147
|
'内置命令:',
|
|
148
|
+
` ${'guide'.padEnd(TOP_LEVEL_COMMAND_NAME_WIDTH)}读取本地核心流程或单个专题;--list 查看主题`,
|
|
145
149
|
` ${'run'.padEnd(TOP_LEVEL_COMMAND_NAME_WIDTH)}执行 Node.js 脚本,支持浏览器 API 与 Skill 命令`,
|
|
146
150
|
` ${'check'.padEnd(TOP_LEVEL_COMMAND_NAME_WIDTH)}检查指定浏览器,未指定时检查全部已连接浏览器`,
|
|
147
151
|
];
|
|
@@ -351,17 +355,6 @@ function parsePayload(payloadRaw, payloadFile) {
|
|
|
351
355
|
}
|
|
352
356
|
return {};
|
|
353
357
|
}
|
|
354
|
-
function resolveServer(explicit) {
|
|
355
|
-
if (explicit)
|
|
356
|
-
return { serverUrl: explicit, serverUrlSource: '--server' };
|
|
357
|
-
if (process.env.MEARL_SERVER_URL) {
|
|
358
|
-
return { serverUrl: process.env.MEARL_SERVER_URL, serverUrlSource: 'MEARL_SERVER_URL' };
|
|
359
|
-
}
|
|
360
|
-
const config = loadCloudServerConfig();
|
|
361
|
-
return config?.server
|
|
362
|
-
? { serverUrl: config.server, serverUrlSource: getCloudServerConfigPath() }
|
|
363
|
-
: {};
|
|
364
|
-
}
|
|
365
358
|
function normalizeScreenshotOutput(action, payload, explicitOutput) {
|
|
366
359
|
if (action !== 'page_screenshot' || !Object.hasOwn(payload, 'output')) {
|
|
367
360
|
return { payload, ...(explicitOutput ? { outputPath: explicitOutput } : {}) };
|
|
@@ -523,7 +516,7 @@ async function runPluginCli(args, commandName) {
|
|
|
523
516
|
console.log(plugin.version ?? 'unknown');
|
|
524
517
|
return 0;
|
|
525
518
|
}
|
|
526
|
-
if (parsed.help || (!parsed.pluginArgs?.length && !parsed.hasPayload)) {
|
|
519
|
+
if (parsed.help || (!plugin.defaultCommand && !parsed.pluginArgs?.length && !parsed.hasPayload && !Object.keys(parsed.pluginInput ?? {}).length)) {
|
|
527
520
|
console.error(plugin.help(`${commandName} ${parsed.action}`, parsed.pluginArgs?.[0]));
|
|
528
521
|
return 0;
|
|
529
522
|
}
|
|
@@ -573,6 +566,10 @@ function writeOutput(action, result, outputPath) {
|
|
|
573
566
|
export async function runMearlCli(args, options = {}) {
|
|
574
567
|
const commandName = options.commandName ?? 'mearl';
|
|
575
568
|
const clientVersion = options.clientVersion ?? CLIENT_VERSION;
|
|
569
|
+
if (args[0] === 'guide') {
|
|
570
|
+
const { runGuideCli } = await import('./guide.js');
|
|
571
|
+
return runGuideCli(args.slice(1), commandName, clientVersion);
|
|
572
|
+
}
|
|
576
573
|
if (args[0] === 'run') {
|
|
577
574
|
const { runScriptCli } = await import('./scriptCli.js');
|
|
578
575
|
return runScriptCli(args.slice(1), commandName);
|
|
@@ -612,8 +609,11 @@ export async function runMearlCli(args, options = {}) {
|
|
|
612
609
|
}
|
|
613
610
|
if (args.includes('--help') || args.includes('-h')) {
|
|
614
611
|
const command = COMMAND_MAP.get(args[0]);
|
|
615
|
-
if (command)
|
|
612
|
+
if (command) {
|
|
616
613
|
console.error(formatCommandHelp(commandName, command));
|
|
614
|
+
if (command.name === 'set_app_profile' || command.name === 'tab_open')
|
|
615
|
+
console.error(formatCapabilityHelp(await readHelpCapabilities(args), true));
|
|
616
|
+
}
|
|
617
617
|
else if (args[0] === 'check')
|
|
618
618
|
printCheckHelp(commandName);
|
|
619
619
|
else if (ADVANCED_BUILTIN_COMMANDS.has(args[0])) {
|
|
@@ -621,6 +621,7 @@ export async function runMearlCli(args, options = {}) {
|
|
|
621
621
|
}
|
|
622
622
|
else if (args[0]?.startsWith('-')) {
|
|
623
623
|
printUsage(commandName, clientVersion);
|
|
624
|
+
console.error(formatCapabilityHelp(await readHelpCapabilities(args)));
|
|
624
625
|
}
|
|
625
626
|
else {
|
|
626
627
|
printUnknownCommand(commandName, args);
|
package/dist/commandHelp.js
CHANGED
|
@@ -39,10 +39,10 @@ export function formatCommandHelp(commandName, command) {
|
|
|
39
39
|
];
|
|
40
40
|
const method = [...SDK_METHOD_ACTIONS].find(([, action]) => action === command.name)?.[0];
|
|
41
41
|
if (method)
|
|
42
|
-
lines.push('SDK:', ...(method === 'browserRelease'
|
|
42
|
+
lines.push('SDK:', ...(method === 'browserRelease' || method === 'browserList'
|
|
43
43
|
? [
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
` await mearl.${method}(options?)`,
|
|
45
|
+
` await mearl.${method}(params, options)`,
|
|
46
46
|
]
|
|
47
47
|
: [
|
|
48
48
|
` await mearl.${method}(${SDK_OPTIONS_ONLY_METHODS.has(method) ? 'options?' : 'params, options?'})`,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export interface SuggestableCommand {
|
|
2
2
|
readonly aliases?: readonly string[];
|
|
3
|
+
/** Suggestion-only expressions whose canonical invocation needs a specific payload. */
|
|
4
|
+
readonly aliasExamples?: Readonly<Record<string, string>>;
|
|
3
5
|
readonly description: string;
|
|
4
6
|
readonly examples?: readonly string[];
|
|
5
7
|
readonly name: string;
|
|
@@ -7,6 +9,7 @@ export interface SuggestableCommand {
|
|
|
7
9
|
export interface CommandSuggestion {
|
|
8
10
|
command: SuggestableCommand;
|
|
9
11
|
score: number;
|
|
12
|
+
example?: string;
|
|
10
13
|
}
|
|
11
14
|
export declare function commandInput(args: readonly string[]): string;
|
|
12
15
|
export declare function findCommandSuggestions(args: readonly string[], commands: readonly SuggestableCommand[], limit?: number): CommandSuggestion[];
|
|
@@ -40,9 +40,20 @@ export function findCommandSuggestions(args, commands, limit = 3) {
|
|
|
40
40
|
return [];
|
|
41
41
|
const ranked = commands
|
|
42
42
|
.map(command => {
|
|
43
|
-
const keys = [command.name, ...(command.aliases ?? [])].map(
|
|
44
|
-
const
|
|
45
|
-
|
|
43
|
+
const keys = [command.name, ...(command.aliases ?? [])].map(name => ({ name }));
|
|
44
|
+
for (const [name, example] of Object.entries(command.aliasExamples ?? {})) {
|
|
45
|
+
keys.push({ name, example });
|
|
46
|
+
}
|
|
47
|
+
let score = -1;
|
|
48
|
+
let example;
|
|
49
|
+
for (const key of keys) {
|
|
50
|
+
const candidate = Math.max(...queries.map(query => similarity(query, normalizeCommand(key.name))));
|
|
51
|
+
if (candidate > score) {
|
|
52
|
+
score = candidate;
|
|
53
|
+
example = key.example;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { command, score, ...(example ? { example } : {}) };
|
|
46
57
|
})
|
|
47
58
|
.filter(suggestion => suggestion.score >= MIN_SIMILARITY)
|
|
48
59
|
.sort((left, right) => right.score - left.score || left.command.name.localeCompare(right.command.name));
|
|
@@ -52,8 +63,8 @@ export function findCommandSuggestions(args, commands, limit = 3) {
|
|
|
52
63
|
return ranked.filter(suggestion => bestScore - suggestion.score <= MAX_SCORE_GAP).slice(0, limit);
|
|
53
64
|
}
|
|
54
65
|
export function formatCommandSuggestions(commandName, suggestions) {
|
|
55
|
-
return suggestions.map(({ command }) => {
|
|
56
|
-
const invocation = command.examples?.[0] ?? command.name;
|
|
66
|
+
return suggestions.map(({ command, example }) => {
|
|
67
|
+
const invocation = example ?? command.examples?.[0] ?? command.name;
|
|
57
68
|
return ` ${commandName} ${invocation}\n ${command.description}`;
|
|
58
69
|
});
|
|
59
70
|
}
|