@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.
Files changed (42) hide show
  1. package/README.md +18 -14
  2. package/dist/actionTimeouts.js +0 -3
  3. package/dist/autoUpdate.js +2 -1
  4. package/dist/bin.js +12 -5
  5. package/dist/capabilityHelp.d.ts +4 -0
  6. package/dist/capabilityHelp.js +68 -0
  7. package/dist/check.js +1 -1
  8. package/dist/cli.js +17 -16
  9. package/dist/commandHelp.js +3 -3
  10. package/dist/commandSuggestions.d.ts +3 -0
  11. package/dist/commandSuggestions.js +16 -5
  12. package/dist/commands.d.ts +50 -55
  13. package/dist/commands.js +41 -41
  14. package/dist/guide.d.ts +46 -0
  15. package/dist/guide.js +71 -0
  16. package/dist/guides/api-browser-setup.md +93 -0
  17. package/dist/guides/api-page-automation.md +140 -0
  18. package/dist/guides/api-page-debugging.md +39 -0
  19. package/dist/guides/api-page-environment.md +37 -0
  20. package/dist/guides/api-request-debugging.md +54 -0
  21. package/dist/guides/browser-tool-cooperation.md +23 -0
  22. package/dist/guides/core.md +60 -0
  23. package/dist/guides/related-skills.md +32 -0
  24. package/dist/guides/scripting.md +21 -0
  25. package/dist/guides/troubleshooting.md +170 -0
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +4 -5
  28. package/dist/plugins/officialPluginInstaller.js +2 -1
  29. package/dist/plugins/pluginRegistry.js +9 -3
  30. package/dist/plugins/runtime/pluginDiagnostics.js +1 -1
  31. package/dist/plugins/runtime/pluginHostServer.js +1 -1
  32. package/dist/plugins/runtime/pluginPageManager.js +2 -2
  33. package/dist/plugins/runtime/pluginStateManager.js +1 -1
  34. package/dist/plugins/runtime/pluginTaskManager.js +2 -2
  35. package/dist/plugins/sdk/plugin.d.ts +2 -0
  36. package/dist/plugins/sdk/plugin.js +17 -9
  37. package/dist/script.js +2 -1
  38. package/dist/socket.js +32 -8
  39. package/dist/unified.js +11 -1
  40. package/dist/unifiedConfig.d.ts +4 -0
  41. package/dist/unifiedConfig.js +11 -0
  42. package/package.json +10 -10
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # @mearl/client
2
2
 
3
- 本仓库仍在迁移完整宿主。此包已可独立构建和测试,setup/native-host/extension 的公开发行接入尚未完成;下列产品命令描述其现有合同,不代表已发布 3.0。
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 操作,应检查状态而非直接重发业务请求。MTop 普通请求及流式请求使用相同的响应刷新机制。
228
- 扩展的临时凭据注入仅匹配原始完整请求 URL,重定向由 Chrome 按新 URL 选择 Cookie。CDP 的 Node 请求(含 MTop 流式请求)不自动跟随重定向,避免将原 URL 的 Cookie 或签名传到其他地址。
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/F-loat/mearl --skill browser-tab-manager --yes
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` 接受同样的 `plugin`、`command` 和 `input`,不会把插件子命令动态扩展成 MCP 工具。SDK 与 MCP 不接受任意入口路径;本地受管理目录中的插件未注册时,会通过匹配当前 client 版本的 setup 自动安装并重试一次。目录由安装宿主显式保存,核心没有组织专属名单;其他未注册名称返回 `PLUGIN_NOT_REGISTERED`。
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
- 组织认证及业务请求由已安装的 Skill 插件提供。CLI 使用 `mearl <plugin> <command>`,业务插件通过 `app.invoke(plugin, { command, input })` 复用同一操作;插件名称、认证方式、请求字段和上传约定以对应 Skill 的契约为准。
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` `send_mtop_request` `request_domain_permission` |
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
- 开发环境可运行 `pnpm verify:plugin-host:default` 验证 SDK 与 PATH CLI 在原配置、原认证和默认模型下的完整执行;原配置保持不变。工具箱内显式页面 attach 缺少站点授权时会申请权限,由用户确认后继续;后台页面运行时不会自动弹窗申请。
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;默认不存在任何预装组织项。安装源码与 preset 的信任校验由安装宿主负责,目录本身不触发安装。
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
 
@@ -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') {
@@ -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 = { ...process.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 { runMearlCli } from './cli.js';
3
- import { CLIENT_VERSION } from './socket.js';
4
- import { scheduleCliAutoUpdate } from './autoUpdate.js';
5
- process.exitCode = await runMearlCli(process.argv.slice(2));
6
- scheduleCliAutoUpdate(CLIENT_VERSION);
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/F-loat/mearl/releases/latest/download/config.json',
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 { getCloudServerConfigPath, loadCloudServerConfig } from './unifiedConfig.js';
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', 'send_mtop_request', 'request_domain_permission'],
24
+ names: ['send_request', 'request_permission'],
24
25
  },
25
- { title: '标签页', names: ['tab_open', 'tab_list', 'tab_group', 'tab_close', 'get_capabilities'] },
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);
@@ -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
- ' await mearl.browserRelease(options?)',
45
- ' await mearl.browserRelease(params, options)',
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(normalizeCommand);
44
- const score = Math.max(...queries.flatMap(query => keys.map(key => similarity(query, key))));
45
- return { command, score };
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
  }