@mearl/client 3.0.0-beta.9 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # @mearl/client
2
2
 
3
- CLI、TypeScript SDK 与脚本入口共用浏览器路由和插件运行时。运行时连接与安装由 `@mearl/setup` 管理。
3
+ [简体中文](README.md) | [English](README.en.md)
4
+
5
+ 在终端使用 `mearl`,或在 TypeScript 中使用 `@mearl/client`,控制浏览器、查看请求和日志,或执行 Skill 命令。CLI、SDK 和脚本运行器共用浏览器路由与插件运行时,安装和连接由 `@mearl/setup` 管理。
4
6
 
5
7
  首次使用当前版本可运行 `mearl guide`,默认只读取核心工作流。`mearl guide --list` 列出专题;例如 `mearl guide run` 读取 SDK 组合调用,`mearl guide environment` 读取设备与 App 指引。指南只读 npm 包内资源,不连接浏览器、加载插件、安装环境或触发自动更新。动作参数继续使用 `mearl <action> --help`,目标的已安装能力继续按现有目录查询。
6
8
 
7
- 指南正文唯一维护在本包的 `guides/`,构建时复制到 `dist/guides/`,`pnpm check:skills` 校验一致性。CLI 输出将本地文档链接转换为下一条指南命令。`skills/mearl/` 只分发精简的 `SKILL.md`,不附带正文副本;仅使用 MCP、无法执行 CLI 时,直接使用当前工具说明与 schema。组织发行版的安装、认证与业务补充仍由其独立 Skill 提供。
9
+ 指南正文唯一维护在本包的 `guides/`,构建时复制到 `dist/guides/`,`pnpm check:skills` 校验一致性。CLI 输出将本地文档链接转换为下一条指南命令。`skills/mearl/` 只分发精简的 `SKILL.md`,不附带正文副本;仅使用 MCP、无法执行 CLI 时,直接使用当前工具说明与 schema。
8
10
 
9
11
  统一浏览器 SDK 与 CLI。它会发现当前机器及已配置 cloud-server 下所有 connector 的浏览器,并根据 `browser_list` 返回的全局 `browserId` 自动选择本地 Socket 或云端 WebSocket 链路。
10
12
 
@@ -13,9 +15,9 @@ CLI、TypeScript SDK 与脚本入口共用浏览器路由和插件运行时。
13
15
  ## 安装
14
16
 
15
17
  ```bash
16
- npm install @mearl/client@beta
18
+ npm install @mearl/client@3
17
19
  # 或
18
- pnpm add @mearl/client@beta
20
+ pnpm add @mearl/client@3
19
21
  ```
20
22
 
21
23
  ## SDK 使用
@@ -64,7 +66,7 @@ mearl check
64
66
  mearl check --local --json
65
67
 
66
68
  # 更新本地环境
67
- npx @mearl/setup@beta update --local
69
+ npx @mearl/setup@3 update --local
68
70
 
69
71
  # 查看某个 action 的帮助
70
72
  mearl <action> --help
@@ -195,8 +197,8 @@ mearl browser_list
195
197
  mearl tab_list --browser '<browserId>'
196
198
  mearl page_snapshot --payload '{"tabId":12345}' --browser '<browserId>'
197
199
 
198
- npx @mearl/setup provider update brow
199
- npx @mearl/setup provider uninstall brow
200
+ npx @mearl/setup provider update example-device
201
+ npx @mearl/setup provider uninstall example-device
200
202
  ```
201
203
 
202
204
  `browser_launch` 创建 `pairing` 实例时会直接返回 `pairing.url` 和可选的配对码;Agent 应把 URL 直接渲染为
@@ -246,7 +248,7 @@ Mearl 默认聚合当前机器和已配置 cloud-server 下的浏览器,并通
246
248
  client、native-host、cloud-connector 和扩展的最新发布版本。发布版本查询最长等待约 1.5 秒,
247
249
  无法访问发布源时会提示无法确认更新,不影响浏览器可用性检查。`--json` 输出机器可读的检查结果,
248
250
  与 `--updates` 同用时包含更新状态。发现更新时会提示适用的命令:client/native-host 使用
249
- `npx @mearl/setup@beta update`,setup 管理的扩展使用 `npx @mearl/setup extension`。
251
+ `npx @mearl/setup@3 update`,setup 管理的扩展使用 `npx @mearl/setup extension`。
250
252
 
251
253
  全局安装的 `mearl` CLI 默认在每天首次使用后启动独立后台进程,查询最新发布版本,并比较
252
254
  client、native-host、cloud-server 和 MCP server 的已安装版本。发现更新时调用
@@ -290,6 +292,8 @@ Provider 与插件仍可独立安装、更新和卸载,帮助聚合不会启
290
292
  注册名优先使用显式名称,其次读取入口的 `name` metadata,最后回退到 skill 目录名;常规注册只传路径。
291
293
  setup 会补齐缺失或低于自身版本的 client、native-host;安装参数由 setup 的 Skills 安装器处理,并固定全局共享安装。
292
294
 
295
+ JavaScript 入口可导出 `setup({ runCommand })` 准备 Skill 自己的依赖。setup 的两种安装入口都会等待该钩子成功后再注册,失败时停止注册。查看帮助、加载入口及普通调用不执行此钩子;Skill 在首次使用时自行检查依赖。运行代码与资源随 Skill 分发。
296
+
293
297
  需要单独检查插件时运行 `mearl <name> --version`。命令会解析注册记录并加载入口,但不连接浏览器;
294
298
  退出码为 0 表示插件已注册且入口可加载,stdout 返回同目录 `SKILL.md` 的版本,缺少版本元数据时返回 `unknown`。
295
299
  未注册、入口丢失或加载失败会以退出码 2 和对应的 `PLUGIN_*` 错误结束。普通业务调用无需预先检查,直接处理同样的错误即可。
@@ -364,17 +368,17 @@ Mearl 将入口 HTML 引用的本地 CSS、JavaScript 和图片内联为不可
364
368
  ```javascript
365
369
  export const toolbox = {
366
370
  version: 1,
367
- title: '迭代管理',
368
- description: '查看多个仓库的改动与发布状态',
369
- icon: 'git-branch',
371
+ title: '工作区笔记',
372
+ description: '整理工作区笔记',
373
+ icon: 'notebook-pen',
370
374
  locales: {
371
375
  en: {
372
- title: 'Iteration Management',
373
- description: 'Track changes and release status across repositories',
376
+ title: 'Workspace Notes',
377
+ description: 'Organize notes for a workspace',
374
378
  },
375
379
  },
376
380
  agent: {
377
- skill: 'iteration-manager',
381
+ skill: 'notebook-skill',
378
382
  layout: 'workspace',
379
383
  },
380
384
  page: {
@@ -395,6 +399,8 @@ export default function register(app) {
395
399
 
396
400
  `app.command(signature, handler, description?, examples?)` 注册 CLI、SDK 和 Agent 可调用的业务命令。
397
401
 
402
+ 工具箱的 Agent 对话默认显示页面元素选择和评论入口。将 `toolbox.agent.pageElementActions` 设为 `false` 可以隐藏这两个入口。
403
+
398
404
  `toolbox.icon` 支持以下字符串形式,工具列表、切换菜单与网站安装目录使用同一格式:
399
405
 
400
406
  ```js
@@ -462,6 +468,8 @@ Node 插件在运行 CLI、SDK 或 MCP Server 的宿主上执行,不随浏览
462
468
 
463
469
  `tab_open active:true` 请求激活目标页。扩展免打扰默认开启:HTTP(S) 页面在首次导航前启用后台焦点模拟,不切换标签页或窗口;关闭免打扰才会真实激活并恢复最小化窗口。`activation.mode` 区分 `background` / `foreground`,模拟后的 `visibilityState` 不代表窗口在前台。焦点模拟在交接、释放控制或扩展连接断开时关闭,`preserveEnvironment` 不保留它;交还后按需对同 URL 使用 `reuse:"require", active:true` 再次激活。不带会话时通过 `browser_release` 收尾。纯 CDP 和外部 Provider 遵循各自的激活能力。页面动作的稳定观察会在有限预算内等待有限时长 CSS 动画。selector/ref 点击遇到嵌套滚动裁剪时会先滚动目标,仍不可见时才以明确的裁剪错误停止。
464
470
 
471
+ 扩展模式下,`tab_open` 传入 `appProfile` 会应用 App 环境。需要模拟 WebView 和系统栏时,设置 `emulation.deviceShell: true`。对于使用单个 WebView 的受支持固定机型,扩展会沿用快捷设置里保存的**手机壳**偏好。Agent 的页面操作以当前业务 WebView 为目标。
472
+
465
473
  `page_click` 默认 `auto` 在隐藏页使用 DOM fallback,selector、文本、ref 和坐标定位都保持可用,并通过 `dispatchMode: "dom"`、`fallbackReason: "page-hidden"` 明确说明实际路径。普通后台任务不需要为了点击主动激活页面;只有站点明确拒绝非可信事件时才切换到可见页并强制 `mouse` / `touch`。
466
474
 
467
475
  `observation.effects.interactives` 会把内容未变化但 DOM 节点被框架重建的控件标记为 `recreated`,保留 selector/ref 与语义信息供后续定位,同时省略未变化的 value 和状态字段;值或状态确实变化时仍返回完整差异。
@@ -40,12 +40,12 @@ export async function readHelpCapabilities(args) {
40
40
  }
41
41
  export function formatCapabilityHelp(catalog, detailed = false) {
42
42
  if (!catalog)
43
- return '当前浏览器能力暂不可读;以下为静态帮助。连接后再次运行 --help 可查看已安装 App。';
43
+ return '当前浏览器能力暂不可读,以下为静态帮助。连接后再次运行 --help 可查看已安装的端模拟。';
44
44
  const lines = [
45
- '当前浏览器已安装的 App:',
45
+ '当前浏览器已安装的端模拟:',
46
46
  ];
47
47
  if (!catalog.apps.entries.length)
48
- lines.push(catalog.apps.status === 'available' ? ' 暂无 App 档案;设备模拟可独立使用。' : ' App 目录暂不可读,不代表未安装。');
48
+ lines.push(catalog.apps.status === 'available' ? ' 暂无已安装的端模拟。设备模拟可独立使用。' : ' 暂时无法读取端模拟列表,安装状态待确认。');
49
49
  for (const app of catalog.apps.entries) {
50
50
  lines.push(` ${app.id} — ${app.label}${app.available ? '' : '(暂不可用)'}`);
51
51
  if (!detailed)
@@ -445,7 +445,7 @@ export declare const COMMANDS: readonly [{
445
445
  readonly type: "string[] | string | { presets?: string[]; domains?: string[] }";
446
446
  readonly description: "从控制浏览器复制 Cookie:可传域名数组、已安装能力提供的范围名,或组合范围与额外域名";
447
447
  }];
448
- readonly examples: ["browser_launch --payload '{\"provider\":\"brow\",\"providerOptions\":{}}'", "browser_launch --payload '{\"name\":\"figma\",\"userAgentMode\":\"desktop\",\"copyCookieDomains\":[\"figma.com\"]}'", "browser_launch --payload '{\"name\":\"with-cookies\",\"copyCookieDomains\":[\"example.com\",\"sub.example.org\"]}'"];
448
+ readonly examples: ["browser_launch --payload '{\"provider\":\"example-device\",\"providerOptions\":{}}'", "browser_launch --payload '{\"name\":\"figma\",\"userAgentMode\":\"desktop\",\"copyCookieDomains\":[\"figma.com\"]}'", "browser_launch --payload '{\"name\":\"with-cookies\",\"copyCookieDomains\":[\"example.com\",\"sub.example.org\"]}'"];
449
449
  }, {
450
450
  readonly name: "browser_close";
451
451
  readonly description: "关闭指定托管浏览器,并按 Provider 策略保留或清理持久化数据";
@@ -507,7 +507,7 @@ export declare const COMMANDS: readonly [{
507
507
  }, {
508
508
  readonly name: "appProfile";
509
509
  readonly type: "string";
510
- readonly description: "按已安装档案应用 App 环境,ID 与限制见当前动态帮助。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true";
510
+ readonly description: "启用已安装的端模拟,ID 与限制见当前动态帮助。需要系统栏时同时传 emulation.deviceShell=true";
511
511
  }, {
512
512
  readonly name: "os";
513
513
  readonly type: "\"ios\" | \"android\"";
@@ -573,7 +573,7 @@ export declare const COMMANDS: readonly [{
573
573
  }, {
574
574
  readonly name: "point";
575
575
  readonly type: "{ x: number; y: number }";
576
- readonly description: "按视口坐标点击(CSS 像素)。默认使用 outer-viewport;容器或 iframe 内坐标配合 coordinateSpace: \"active-frame\"";
576
+ readonly description: "按视口坐标点击(CSS 像素)。手机壳预览默认使用业务视口坐标,其他页面默认使用外层视口坐标";
577
577
  }, {
578
578
  readonly name: "frameId";
579
579
  readonly type: "string";
@@ -581,7 +581,7 @@ export declare const COMMANDS: readonly [{
581
581
  }, {
582
582
  readonly name: "coordinateSpace";
583
583
  readonly type: "\"outer-viewport\" | \"active-frame\"";
584
- readonly description: "仅配合 point 使用;默认 outer-viewport;浏览器扩展后端的 active-frame 会换算 iframe 偏移与缩放后派发可信输入";
584
+ readonly description: "仅配合 point 使用;手机壳预览默认 active-frame,其他页面默认 outer-viewport;扩展后端会换算 iframe 偏移与缩放";
585
585
  }, {
586
586
  readonly name: "role";
587
587
  readonly type: "string";
@@ -1044,7 +1044,7 @@ export declare const COMMANDS: readonly [{
1044
1044
  }, {
1045
1045
  readonly name: "splitScreen";
1046
1046
  readonly type: "boolean";
1047
- readonly description: "是否将首页与业务页分置左右 WebView;仅在已启用 App 档案、deviceShell=true、横屏时生效,默认 false";
1047
+ readonly description: "是否将首页与业务页分置左右 WebView;仅在已启用端模拟、deviceShell=true、横屏时生效,默认 false";
1048
1048
  }, {
1049
1049
  readonly name: "reload";
1050
1050
  readonly type: "boolean";
@@ -1055,12 +1055,12 @@ export declare const COMMANDS: readonly [{
1055
1055
  readonly required: true;
1056
1056
  readonly description: "目标标签页 ID";
1057
1057
  }];
1058
- readonly notes: readonly ["无 preset 时 width/height 必须成对提供且 userAgent 必填;mobile 默认 true,touch 跟随 mobile。", "deviceShell 开启/移除时必须重载;splitScreen 仅在已启用 App 档案、deviceShell=true、横屏时生效。"];
1058
+ readonly notes: readonly ["无 preset 时 width/height 必须成对提供且 userAgent 必填;mobile 默认 true,touch 跟随 mobile。", "deviceShell 开启/移除时必须重载;splitScreen 仅在已启用端模拟、deviceShell=true、横屏时生效。"];
1059
1059
  readonly result: readonly ["返回实际 viewport(width/height/deviceScaleFactor/scale)、UA、mobile、touch、orientation、deviceShell、splitScreen 和 reloaded;应以响应确认生效状态。"];
1060
1060
  readonly examples: ["set_device_emulation --payload '{\"tabId\":12345,\"enabled\":true,\"preset\":\"iphone-17-pro\"}'", "set_device_emulation --payload '{\"tabId\":12345,\"enabled\":true,\"preset\":\"pixel-11\",\"orientation\":\"landscape\"}'", "set_device_emulation --payload '{\"tabId\":12345,\"enabled\":true,\"preset\":\"ipad-mini-a17-pro\",\"orientation\":\"landscape\",\"deviceShell\":true,\"splitScreen\":true}'", "set_device_emulation --payload '{\"tabId\":12345,\"enabled\":false}'"];
1061
1061
  }, {
1062
1062
  readonly name: "set_app_profile";
1063
- readonly description: "设置已安装的 App 档案;完整 JSBridge 由能力包提供。可同时设置已安装的网络控件,传 emulation 时联动设备模拟";
1063
+ readonly description: "设置目标标签页的端模拟;完整 JSBridge 由能力包提供。可同时设置已安装的网络控件,传 emulation 时联动设备模拟";
1064
1064
  readonly summary: "设置或清除目标标签页的 App 容器模拟";
1065
1065
  readonly params: [{
1066
1066
  readonly name: "enabled";
@@ -1070,7 +1070,7 @@ export declare const COMMANDS: readonly [{
1070
1070
  }, {
1071
1071
  readonly name: "profile";
1072
1072
  readonly type: "string";
1073
- readonly description: "已安装的容器档案 ID(enabled=true 时必填);当前动态帮助直接展示目标浏览器目录";
1073
+ readonly description: "已安装的端模拟 ID(enabled=true 时必填);当前动态帮助直接展示目标浏览器目录";
1074
1074
  }, {
1075
1075
  readonly name: "os";
1076
1076
  readonly type: "\"ios\" | \"android\"";
@@ -1078,7 +1078,7 @@ export declare const COMMANDS: readonly [{
1078
1078
  }, {
1079
1079
  readonly name: "refreshDefinition";
1080
1080
  readonly type: "boolean";
1081
- readonly description: "立即采用最新档案定义,保留用户显式设置";
1081
+ readonly description: "立即采用最新端模拟配置,保留用户显式设置";
1082
1082
  }, {
1083
1083
  readonly name: "lockDefinition";
1084
1084
  readonly type: "boolean";
@@ -1086,7 +1086,7 @@ export declare const COMMANDS: readonly [{
1086
1086
  }, {
1087
1087
  readonly name: "appVersion";
1088
1088
  readonly type: "string";
1089
- readonly description: "覆盖档案默认 App 版本号";
1089
+ readonly description: "覆盖配置中的默认 App 版本号";
1090
1090
  }, {
1091
1091
  readonly name: "deviceModel";
1092
1092
  readonly type: "string";
@@ -1102,7 +1102,7 @@ export declare const COMMANDS: readonly [{
1102
1102
  }, {
1103
1103
  readonly name: "emulation";
1104
1104
  readonly type: "object";
1105
- readonly description: "可选设备联动参数,字段同 set_device_emulation 去掉 enabled/reload;字段覆盖档案推荐设备,deviceShell 默认 true,横屏时可传 splitScreen=true。省略时不设置或清除设备";
1105
+ readonly description: "可选设备联动参数,字段同 set_device_emulation 去掉 enabled/reload;字段覆盖端模拟配置推荐的设备,deviceShell 默认 true,横屏时可传 splitScreen=true。省略时不设置或清除设备";
1106
1106
  }, {
1107
1107
  readonly name: "reload";
1108
1108
  readonly type: "boolean";
@@ -1113,7 +1113,7 @@ export declare const COMMANDS: readonly [{
1113
1113
  readonly required: true;
1114
1114
  readonly description: "目标标签页 ID";
1115
1115
  }];
1116
- readonly notes: readonly ["只有用户明确要求 App/客户端/容器/JSBridge 模拟时启用。移动视口或普通 H5 调试使用 set_device_emulation。", "emulation={} 使用档案推荐设备;enabled=false 时省略 emulation 只清除 App 注入,传 {} 同时清除设备模拟。", "bridgeConfig.methods 按方法声明 behavior / response / noop 三选一,可传 silent;只支持受控行为、固定 JSON 回包或成功空实现,不执行配置脚本。"];
1116
+ readonly notes: readonly ["只有用户明确要求 App/客户端/容器/JSBridge 模拟时启用。移动视口或普通 H5 调试使用 set_device_emulation。", "emulation={} 使用端模拟配置推荐的设备;enabled=false 时省略 emulation 只清除 App 注入,传 {} 同时清除设备模拟。", "bridgeConfig.methods 按方法声明 behavior / response / noop 三选一,可传 silent;只支持受控行为、固定 JSON 回包或成功空实现,不执行配置脚本。"];
1117
1117
  readonly result: readonly ["返回 enabled、profile、label、userAgent、os、networkControl、reloaded;联动设备时额外返回实际 emulation。"];
1118
1118
  readonly examples: ["set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"networkControl\":{\"id\":\"environment\",\"value\":\"pre\"}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"networkControl\":null}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"emulation\":{}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"wallet\",\"emulation\":{\"preset\":\"pixel-11\",\"deviceShell\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"emulation\":{\"preset\":\"ipad-mini-a17-pro\",\"orientation\":\"landscape\",\"deviceShell\":true,\"splitScreen\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"emulation\":{}}'"];
1119
1119
  }, {
package/dist/commands.js CHANGED
@@ -453,7 +453,7 @@ export const COMMANDS = [
453
453
  },
454
454
  ],
455
455
  examples: [
456
- `browser_launch --payload '{"provider":"brow","providerOptions":{}}'`,
456
+ `browser_launch --payload '{"provider":"example-device","providerOptions":{}}'`,
457
457
  `browser_launch --payload '{"name":"figma","userAgentMode":"desktop","copyCookieDomains":["figma.com"]}'`,
458
458
  `browser_launch --payload '{"name":"with-cookies","copyCookieDomains":["example.com","sub.example.org"]}'`,
459
459
  ],
@@ -532,7 +532,7 @@ export const COMMANDS = [
532
532
  {
533
533
  name: 'appProfile',
534
534
  type: 'string',
535
- description: '按已安装档案应用 App 环境,ID 与限制见当前动态帮助。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true',
535
+ description: '启用已安装的端模拟,ID 与限制见当前动态帮助。需要系统栏时同时传 emulation.deviceShell=true',
536
536
  },
537
537
  {
538
538
  name: 'os',
@@ -621,7 +621,7 @@ export const COMMANDS = [
621
621
  {
622
622
  name: 'point',
623
623
  type: '{ x: number; y: number }',
624
- description: '按视口坐标点击(CSS 像素)。默认使用 outer-viewport;容器或 iframe 内坐标配合 coordinateSpace: "active-frame"',
624
+ description: '按视口坐标点击(CSS 像素)。手机壳预览默认使用业务视口坐标,其他页面默认使用外层视口坐标',
625
625
  },
626
626
  {
627
627
  name: 'frameId',
@@ -631,7 +631,7 @@ export const COMMANDS = [
631
631
  {
632
632
  name: 'coordinateSpace',
633
633
  type: '"outer-viewport" | "active-frame"',
634
- description: '仅配合 point 使用;默认 outer-viewport;浏览器扩展后端的 active-frame 会换算 iframe 偏移与缩放后派发可信输入',
634
+ description: '仅配合 point 使用;手机壳预览默认 active-frame,其他页面默认 outer-viewport;扩展后端会换算 iframe 偏移与缩放',
635
635
  },
636
636
  {
637
637
  name: 'role',
@@ -1151,14 +1151,14 @@ export const COMMANDS = [
1151
1151
  {
1152
1152
  name: 'splitScreen',
1153
1153
  type: 'boolean',
1154
- description: '是否将首页与业务页分置左右 WebView;仅在已启用 App 档案、deviceShell=true、横屏时生效,默认 false',
1154
+ description: '是否将首页与业务页分置左右 WebView;仅在已启用端模拟、deviceShell=true、横屏时生效,默认 false',
1155
1155
  },
1156
1156
  { name: 'reload', type: 'boolean', description: '启用后是否重载页面,默认 true' },
1157
1157
  { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' },
1158
1158
  ],
1159
1159
  notes: [
1160
1160
  '无 preset 时 width/height 必须成对提供且 userAgent 必填;mobile 默认 true,touch 跟随 mobile。',
1161
- 'deviceShell 开启/移除时必须重载;splitScreen 仅在已启用 App 档案、deviceShell=true、横屏时生效。',
1161
+ 'deviceShell 开启/移除时必须重载;splitScreen 仅在已启用端模拟、deviceShell=true、横屏时生效。',
1162
1162
  ],
1163
1163
  result: [
1164
1164
  '返回实际 viewport(width/height/deviceScaleFactor/scale)、UA、mobile、touch、orientation、deviceShell、splitScreen 和 reloaded;应以响应确认生效状态。',
@@ -1172,27 +1172,27 @@ export const COMMANDS = [
1172
1172
  },
1173
1173
  {
1174
1174
  name: 'set_app_profile',
1175
- description: '设置已安装的 App 档案;完整 JSBridge 由能力包提供。可同时设置已安装的网络控件,传 emulation 时联动设备模拟',
1175
+ description: '设置目标标签页的端模拟;完整 JSBridge 由能力包提供。可同时设置已安装的网络控件,传 emulation 时联动设备模拟',
1176
1176
  summary: '设置或清除目标标签页的 App 容器模拟',
1177
1177
  params: [
1178
1178
  { name: 'enabled', type: 'boolean', required: true, description: 'true=启用,false=清除' },
1179
1179
  {
1180
1180
  name: 'profile',
1181
1181
  type: 'string',
1182
- description: '已安装的容器档案 ID(enabled=true 时必填);当前动态帮助直接展示目标浏览器目录',
1182
+ description: '已安装的端模拟 ID(enabled=true 时必填);当前动态帮助直接展示目标浏览器目录',
1183
1183
  },
1184
1184
  { name: 'os', type: '"ios" | "android"', description: '容器系统,默认 ios' },
1185
1185
  {
1186
1186
  name: 'refreshDefinition',
1187
1187
  type: 'boolean',
1188
- description: '立即采用最新档案定义,保留用户显式设置',
1188
+ description: '立即采用最新端模拟配置,保留用户显式设置',
1189
1189
  },
1190
1190
  {
1191
1191
  name: 'lockDefinition',
1192
1192
  type: 'boolean',
1193
1193
  description: '固定当前定义;默认在下次刷新或文档导航时自动更新',
1194
1194
  },
1195
- { name: 'appVersion', type: 'string', description: '覆盖档案默认 App 版本号' },
1195
+ { name: 'appVersion', type: 'string', description: '覆盖配置中的默认 App 版本号' },
1196
1196
  {
1197
1197
  name: 'deviceModel',
1198
1198
  type: 'string',
@@ -1211,14 +1211,14 @@ export const COMMANDS = [
1211
1211
  {
1212
1212
  name: 'emulation',
1213
1213
  type: 'object',
1214
- description: '可选设备联动参数,字段同 set_device_emulation 去掉 enabled/reload;字段覆盖档案推荐设备,deviceShell 默认 true,横屏时可传 splitScreen=true。省略时不设置或清除设备',
1214
+ description: '可选设备联动参数,字段同 set_device_emulation 去掉 enabled/reload;字段覆盖端模拟配置推荐的设备,deviceShell 默认 true,横屏时可传 splitScreen=true。省略时不设置或清除设备',
1215
1215
  },
1216
1216
  { name: 'reload', type: 'boolean', description: '应用/清除后是否重载页面,默认 true' },
1217
1217
  { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' },
1218
1218
  ],
1219
1219
  notes: [
1220
1220
  '只有用户明确要求 App/客户端/容器/JSBridge 模拟时启用。移动视口或普通 H5 调试使用 set_device_emulation。',
1221
- 'emulation={} 使用档案推荐设备;enabled=false 时省略 emulation 只清除 App 注入,传 {} 同时清除设备模拟。',
1221
+ 'emulation={} 使用端模拟配置推荐的设备;enabled=false 时省略 emulation 只清除 App 注入,传 {} 同时清除设备模拟。',
1222
1222
  'bridgeConfig.methods 按方法声明 behavior / response / noop 三选一,可传 silent;只支持受控行为、固定 JSON 回包或成功空实现,不执行配置脚本。',
1223
1223
  ],
1224
1224
  result: [
package/dist/guide.d.ts CHANGED
@@ -32,9 +32,9 @@ export declare const GUIDE_TOPICS: readonly [{
32
32
  readonly file: "browser-tool-cooperation.md";
33
33
  readonly title: "与其他浏览器工具协作";
34
34
  }, {
35
- readonly id: "extensions";
35
+ readonly id: "skills";
36
36
  readonly file: "related-skills.md";
37
- readonly title: "专用 Skill 与扩展开发入口";
37
+ readonly title: "专用 Skill 使用与安装";
38
38
  }, {
39
39
  readonly id: "troubleshooting";
40
40
  readonly file: "troubleshooting.md";
package/dist/guide.js CHANGED
@@ -10,7 +10,7 @@ export const GUIDE_TOPICS = [
10
10
  { id: 'diagnostics', file: 'api-request-debugging.md', title: '接口、日志、埋点、权限与 Mock' },
11
11
  { id: 'browsers', file: 'api-browser-setup.md', title: '目标、会话、远端与 Provider' },
12
12
  { id: 'cooperation', file: 'browser-tool-cooperation.md', title: '与其他浏览器工具协作' },
13
- { id: 'extensions', file: 'related-skills.md', title: '专用 Skill 与扩展开发入口' },
13
+ { id: 'skills', file: 'related-skills.md', title: '专用 Skill 使用与安装' },
14
14
  { id: 'troubleshooting', file: 'troubleshooting.md', title: '安装、连接与环境排查' },
15
15
  ];
16
16
  export function renderGuideLinks(source, commandName = 'mearl') {
@@ -42,7 +42,7 @@ export async function readGuide(id, commandName = 'mearl', version = CLIENT_VERS
42
42
  const installed = await readInstallationManagement();
43
43
  const command = setupManagementCommand(installed.setup ?? { package: '@mearl/setup', version: 'beta' });
44
44
  source = source.replaceAll('{{setup}}', command);
45
- management = installed.source ? `管理入口:${command};沿用本机已选 preset。` : `首次安装入口:${command};本机尚未选择 preset,组织发行环境使用其首次安装指引。`;
45
+ management = installed.source ? `管理入口:${command};沿用本机已选 preset。` : `首次安装入口:${command};本机尚未选择 preset。`;
46
46
  }
47
47
  return [
48
48
  `[mearl:guide] CLI ${version} · ${topic.id}`,
@@ -1,8 +1,8 @@
1
1
  # Mearl 场景:页面测试环境准备
2
2
 
3
- 需要联动设备/App、折叠屏/分屏、时区或 Cookie 时读取本文件。设备预设与 App 档案以当前 CLI 为准,不从静态文档推断当前列表。远端/Provider 选择见[浏览器管理](./api-browser-setup.md)。
3
+ 需要联动设备/App、折叠屏/分屏、时区或 Cookie 时读取本文件。设备预设与端模拟列表以当前 CLI 为准,不从静态文档推断当前列表。远端/Provider 选择见[浏览器管理](./api-browser-setup.md)。
4
4
 
5
- 设备模拟直接可用。App 模拟入口由已安装的能力包提供,档案可声明 UA、JSBridge 和容器行为。先查看 `mearl set_app_profile --help` 展示的目标浏览器动态目录;需要机器可读的桥方法、可调设置与默认版本时用 SDK `mearl.getCapabilities({ app: '目录中的完整 ID' })` 或 MCP `browser_get_capabilities`。按需读取 `usage.skills` / `usage.documentation`,再执行对应动作。
5
+ 设备模拟直接可用。App 模拟入口由已安装的能力包提供,配置可声明 UA、JSBridge 和容器行为。先查看 `mearl set_app_profile --help` 展示的目标浏览器动态目录;需要机器可读的桥方法、可调设置与默认版本时用 SDK `mearl.getCapabilities({ app: '目录中的完整 ID' })` 或 MCP `browser_get_capabilities`。按需读取 `usage.skills` / `usage.documentation`,再执行对应动作。
6
6
 
7
7
  ## 设备模拟与设备外壳
8
8
 
@@ -17,8 +17,8 @@ scale 只改变视觉比例,不改变页面读取的视口或 DPR。预设的
17
17
  只有用户明确要求 App、客户端、容器、JSBridge 或容器栈模拟时启用 set_app_profile / tab_open.appProfile;普通 H5、唤端链接、客户端分支或操作失败不是启用理由。
18
18
 
19
19
  - 只注入桥:省略 emulation,保留设备状态;App 注入本身不创建根 iframe 或系统栏。
20
- - 完整端模拟:同一个 set_app_profile 调用传 emulation;空对象采用档案推荐设备,设备外壳默认开启。
21
- - 左右分屏:App 档案与 deviceShell=true、orientation=landscape、splitScreen=true 必须同时满足。新建页需要首屏分屏时,在 tab_open 同时传 appProfile 与该 emulation 组合。
20
+ - 完整端模拟:同一个 set_app_profile 调用传 emulation;空对象采用端模拟配置推荐的设备,设备外壳默认开启。
21
+ - 左右分屏:端模拟与 deviceShell=true、orientation=landscape、splitScreen=true 必须同时满足。新建页需要首屏分屏时,在 tab_open 同时传 appProfile 与该 emulation 组合。
22
22
  - 桥方法覆盖:bridgeConfig 只声明受控行为、固定 JSON 回包或成功空实现,不执行配置脚本。
23
23
  - 恢复:enabled=false 且省略 emulation 只清除 App;传 emulation={} 同时清除设备模拟。
24
24
 
@@ -13,7 +13,7 @@ Mearl 单独使用时沿用主 Skill 的页面操作流程。任务已有 Ego、
13
13
  - 在 Ego Page 读取 `page.cdp("Page.getFrameTree")` 的主 frame ID,再用扩展 `tab_list` 与 `page_frames` 核对。只有 `isMainFrame: true` 的 `frameId` 唯一匹配时,才使用该扩展 `browserId + tabId`;URL、标题和其他工具的页面标识不能代替核验。
14
14
  - 每轮转入扩展写操作前,按 Ego 指南确认同一 Space 仍由 Agent 控制;用户接管、Space 失活或归属变化时两套工具均停止写入。扩展不会自动继承 Ego 的控制阻断,未知结果先观察,不换工具重放。
15
15
  - 设备或 App 模拟开启期间,导航、交互、快照与截图使用扩展 action;Ego 鼠标输入可能与触摸模拟冲突,原生导航或截图可能改变设备参数。撤销并确认模拟清除后,再恢复 Ego 原生页面交互。
16
- - App 档案须已安装并取得实际站点权限,以当前目标的帮助为准。桥对象存在不证明真实原生授权或支付能力;跨域 iframe 的原生 screen/DPR、媒体查询与 CSS 安全区也不保证与真机一致。
16
+ - 对应的端模拟须已安装并取得实际站点权限,以当前目标的帮助为准。桥对象存在不证明真实原生授权或支付能力;跨域 iframe 的原生 screen/DPR、媒体查询与 CSS 安全区也不保证与真机一致。
17
17
  - 普通打开后设置模拟并重载,不等于业务页首次访问即处于模拟环境。需要首屏模拟时先确认当前工具支持的准备流程。
18
18
  - 成功收尾遵守 Ego 的页面保留规则;保留结果页时保留用户需要的模拟状态。需要恢复时在结束前通过扩展显式恢复原配置;Mearl 会话释放不替代 Ego 收尾,也不保证清除模拟。
19
19
 
@@ -1,6 +1,6 @@
1
1
  # Mearl 核心工作流
2
2
 
3
- 本指南说明通用操作流程。参数、返回字段以当前 CLI 的 `mearl <action> --help`、SDK `mearl.help()` 或 MCP 工具 schema 为准;MCP 使用实际提供的工具名,不猜命令映射。仅能使用 MCP、无法执行 CLI 时,直接使用当前 MCP 工具说明与 schema。组织发行版附带的安装、认证及业务指引继续有效,公共指南不替代这些说明。
3
+ 本指南说明通用操作流程。参数、返回字段以当前 CLI 的 `mearl <action> --help`、SDK `mearl.help()` 或 MCP 工具 schema 为准;MCP 使用实际提供的工具名,不猜命令映射。仅能使用 MCP、无法执行 CLI 时,直接使用当前 MCP 工具说明与 schema。
4
4
 
5
5
  ## 进入任务
6
6
 
@@ -56,5 +56,5 @@ App 可用性以当前目标的 `set_app_profile --help` 或 `get_capabilities`
56
56
  - [接口与埋点调试](./api-request-debugging.md):请求、日志、权限、Mock。
57
57
  - [浏览器管理](./api-browser-setup.md):目标、会话、远端与 Provider。
58
58
  - [工具协作](./browser-tool-cooperation.md):与 Ego、agent-browser 配合。
59
- - [专用 Skill 路由](./related-skills.md):匹配站点/业务意图或开发扩展;不因可安装而主动安装。
59
+ - [专用 Skill 使用与安装](./related-skills.md):如何选用和按需安装本地微信小程序等专用能力,以及在哪里查找扩展开发指引。
60
60
  - [连接排查](./troubleshooting.md):连接失败与环境管理。
@@ -1,9 +1,10 @@
1
- # 相关 Skill 路由
1
+ # 专用 Skill 使用与安装
2
2
 
3
3
  站点或业务对象与任务意图都匹配时,优先使用已安装的专用 Skill。通用网页操作继续使用 Mearl;不要仅因关键词相同就改用其他能力。专用站点的路由说明由对应 Skill 提供。
4
4
 
5
5
  | 意图 | Skill |
6
6
  | --- | --- |
7
+ | 运行或验证本地微信小程序,包括 uni-app / Taro 的微信产物 | wechat-miniprogram |
7
8
  | 时间戳、Base64、图片编码、URL 编解码 | format-converter |
8
9
  | 记录或检索笔记与跨会话记忆 | notes-memory |
9
10
  | 管理已有浏览器标签分组、关闭受控标签页 | browser-tab-manager |
@@ -21,7 +22,25 @@
21
22
  {{setup}} plugin install https://github.com/mearlhq/mearl --skill <skill-name> --yes
22
23
  ```
23
24
 
24
- 已可用的专用 Skill 按其说明执行;普通业务操作不因存在可安装能力就主动安装。用户拒绝、要求继续或 Skill 不适用时回到通用流程。
25
+ 已可用的专用 Skill 按其说明执行。用户明确要求使用表中某项专用能力、且执行任务需要该插件时,可在缺失时按需安装并继续。普通网页操作使用通用流程。
26
+
27
+ ## 运行本地微信小程序
28
+
29
+ 任务要运行小程序工程或已编译的微信产物时,先用 `mearl --help` 检查已注册插件中是否有 `wxa`。缺失时,从已包含此 Skill 且当前可访问的官方来源补装:
30
+
31
+ ```bash
32
+ {{setup}} plugin install https://github.com/mearlhq/wechat-profile --skill wechat-miniprogram --yes
33
+ ```
34
+
35
+ 官方来源暂不可用且本机已有包含此 Skill 的源码时,可直接注册:
36
+
37
+ ```bash
38
+ {{setup}} plugin "/path/to/wechat-profile/skills/wechat-miniprogram"
39
+ ```
40
+
41
+ 两种方式都会准备运行依赖并注册 `wxa`。读取对应目录中的 `wechat-miniprogram` Skill,查看 `mearl wxa run --help` 后用 `mearl wxa run <项目路径>` 编译并打开预览。框架项目先构建微信产物。看完可以直接关闭预览标签页。若安装失败,报告具体阻塞。用户只要求普通微信 H5 / WebView 模拟时,查询目标浏览器是否支持微信端模拟。
42
+
43
+ ## 开发与协作指引
25
44
 
26
45
  `mearl-ego`、`mearl-skill-creator` 与 `mearl-provider-creator` 是指引型 Skill,没有可注册的脚本入口;直接用标准 Skills 工具安装,不运行 `setup plugin`:
27
46
 
@@ -1,4 +1,4 @@
1
- import { type AppProfileContribution } from '@mearl/browser-core';
1
+ import { type AppProfileContribution, type ToolboxAgentConfig } from '@mearl/browser-core';
2
2
  export declare class PluginError extends Error {
3
3
  readonly code: string;
4
4
  constructor(code: string, message: string, options?: ErrorOptions);
@@ -13,9 +13,8 @@ export interface PluginToolboxLocale {
13
13
  title?: string;
14
14
  description?: string;
15
15
  }
16
- export interface PluginToolboxAgentConfig {
16
+ export interface PluginToolboxAgentConfig extends Pick<ToolboxAgentConfig, 'layout' | 'pageElementActions'> {
17
17
  skill: string;
18
- layout?: 'drawer' | 'workspace';
19
18
  }
20
19
  export interface PluginToolboxContribution {
21
20
  version: 1;
@@ -229,11 +229,18 @@ export function normalizePluginToolboxContribution(value) {
229
229
  if (!skill || skill.length > 128 || !/^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/.test(skill)) {
230
230
  throw new Error('toolbox.agent.skill 必须是有效的 Skill 名称');
231
231
  }
232
- const layout = agentCandidate.layout;
232
+ const { layout, pageElementActions } = agentCandidate;
233
233
  if (layout !== undefined && layout !== 'drawer' && layout !== 'workspace') {
234
234
  throw new Error('toolbox.agent.layout 必须是 drawer 或 workspace');
235
235
  }
236
- agent = { skill, ...(layout ? { layout } : {}) };
236
+ if (pageElementActions !== undefined && typeof pageElementActions !== 'boolean') {
237
+ throw new Error('toolbox.agent.pageElementActions 必须是布尔值');
238
+ }
239
+ agent = {
240
+ skill,
241
+ ...(layout ? { layout } : {}),
242
+ ...(pageElementActions !== undefined ? { pageElementActions } : {}),
243
+ };
237
244
  }
238
245
  if (candidate.order !== undefined &&
239
246
  (!Number.isInteger(candidate.order) || candidate.order < 0 || candidate.order > 10_000)) {
@@ -1,8 +1,10 @@
1
1
  import { diagnosticBinding } from './pluginDiagnostics.js';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
3
  import { EventEmitter } from 'node:events';
4
- import { readFileSync, statSync } from 'node:fs';
5
- import { isAbsolute, join } from 'node:path';
4
+ import { lstatSync, readFileSync, statSync } from 'node:fs';
5
+ import { dirname, isAbsolute, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { keyedDir } from '@mearl/daemon-core';
6
8
  import { capturePluginRevision } from './pluginRevision.js';
7
9
  import { findPluginRegistration } from '../sdk/plugin.js';
8
10
  import { UnifiedClient, decodeBrowserTarget } from '../../unified.js';
@@ -361,6 +363,14 @@ export class PluginPageManager {
361
363
  throw failure('PLUGIN_PAGE_TARGET_CHANGED', 'Nested plugin must use the inherited document');
362
364
  if (!matchesPluginPageURL(runtime.matches, target.url))
363
365
  throw failure('PLUGIN_PAGE_URL_NOT_ALLOWED', 'Plugin page URL is outside its declared matches');
366
+ if (target.url.startsWith('file:')) {
367
+ const file = fileURLToPath(target.url);
368
+ const directory = keyedDir(join('plugin-previews', plugin));
369
+ if (dirname(file) !== directory ||
370
+ !lstatSync(directory, { throwIfNoEntry: false })?.isDirectory() ||
371
+ !lstatSync(file, { throwIfNoEntry: false })?.isFile())
372
+ throw failure('PLUGIN_PAGE_URL_NOT_ALLOWED', 'Local preview must be a regular file in the plugin preview directory');
373
+ }
364
374
  const existing = [...this.instances.values()].find(item => item.value.state === 'active' &&
365
375
  item.value.plugin === plugin &&
366
376
  sameTarget(item.value.target, target));
@@ -781,7 +791,7 @@ export class PluginPageManager {
781
791
  try {
782
792
  await this.validate(record);
783
793
  const binding = record.runtime.events[delivery.name];
784
- const started = this.start(record, binding.command, { event: { name: delivery.name, payload: delivery.payload, sequence: delivery.sequence } }, `page:${record.value.instanceId}:${delivery.sequence}`);
794
+ const started = this.start(record, binding.command, { event: { name: delivery.name, payload: delivery.payload, sequence: delivery.sequence } }, `page:${record.value.instanceId}:${delivery.sequence}`, undefined, binding.executionTimeoutMs);
785
795
  delivery.taskId = started.taskId;
786
796
  this.append(record, {
787
797
  type: 'running',
@@ -813,7 +823,7 @@ export class PluginPageManager {
813
823
  });
814
824
  }
815
825
  }
816
- start(record, command, input, key, parentIdentity) {
826
+ start(record, command, input, key, parentIdentity, executionTimeoutMs) {
817
827
  // The runtime delegates through the instance's current connection. Its identity follows
818
828
  // that connection so binding cannot replace the connection with this forwarding proxy.
819
829
  const bridge = {
@@ -838,6 +848,7 @@ export class PluginPageManager {
838
848
  environment: record.execution.environment,
839
849
  options: record.options,
840
850
  idempotencyKey: key,
851
+ executionTimeoutMs,
841
852
  }, bridge, {
842
853
  ...(parentIdentity?.taskId ? { parentTaskId: parentIdentity.taskId, parentIdentity } : {}),
843
854
  revision: record.revision,
@@ -1,3 +1,4 @@
1
+ import type { CommandRunner } from '@mearl/daemon-core';
1
2
  import type { PluginTaskRecoveryDeclaration, PluginTaskCheckpoint, PluginCheckpointMigrationInput } from '@mearl/browser-core';
2
3
  import type { PluginStateDeclaration } from '@mearl/browser-core';
3
4
  import type { PluginModelRequest, PluginModelResult } from '@mearl/browser-core';
@@ -71,6 +72,9 @@ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'opti
71
72
  onInvocation?: (identity: PluginInvocationIdentity | undefined) => void;
72
73
  }, pluginName?: string): Promise<{
73
74
  name: string | undefined;
75
+ setup: ((context: {
76
+ runCommand: CommandRunner;
77
+ }) => unknown | Promise<unknown>) | undefined;
74
78
  description: string | undefined;
75
79
  defaultCommand: string | undefined;
76
80
  toolbox: PluginToolboxContribution | undefined;
@@ -90,6 +94,9 @@ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'opti
90
94
  }>;
91
95
  export declare function loadRegisteredPlugin(name: string, context: PluginExecutionContext, options?: PluginInvokeOptions): Promise<{
92
96
  name: string | undefined;
97
+ setup: ((context: {
98
+ runCommand: CommandRunner;
99
+ }) => unknown | Promise<unknown>) | undefined;
93
100
  description: string | undefined;
94
101
  defaultCommand: string | undefined;
95
102
  toolbox: PluginToolboxContribution | undefined;
@@ -153,6 +153,7 @@ export async function loadPlugin(entry, context, pluginName) {
153
153
  let taskRecovery;
154
154
  let appProfiles;
155
155
  let profileSources;
156
+ let setup;
156
157
  const registerCommand = (signature, handler, description = '', examples = [], toolboxOnly = false, runtimeOnly = false) => {
157
158
  if (!registering)
158
159
  throw new Error('命令只能在入口注册阶段声明');
@@ -313,6 +314,9 @@ export async function loadPlugin(entry, context, pluginName) {
313
314
  if (module.description !== undefined && typeof module.description !== 'string')
314
315
  throw new Error('入口的 description 导出必须为字符串');
315
316
  description = module.description;
317
+ if (module.setup !== undefined && typeof module.setup !== 'function')
318
+ throw new Error('入口的 setup 导出必须为函数');
319
+ setup = module.setup;
316
320
  if (module.defaultCommand !== undefined && typeof module.defaultCommand !== 'string')
317
321
  throw new Error('入口的 defaultCommand 导出必须为字符串');
318
322
  defaultCommand = module.defaultCommand;
@@ -535,6 +539,7 @@ export async function loadPlugin(entry, context, pluginName) {
535
539
  };
536
540
  return {
537
541
  name,
542
+ setup,
538
543
  description,
539
544
  defaultCommand,
540
545
  toolbox,
@@ -37,6 +37,8 @@ export function normalizePluginPageRuntime(value) {
37
37
  (!Number.isSafeInteger(event.maxPending) ||
38
38
  event.maxPending < 1 ||
39
39
  event.maxPending > 64)) ||
40
+ (event.executionTimeoutMs !== undefined &&
41
+ (!Number.isSafeInteger(event.executionTimeoutMs) || event.executionTimeoutMs < 1 || event.executionTimeoutMs > 300000)) ||
40
42
  event.schema.$async)
41
43
  throw new Error(`Invalid page event binding: ${name}`);
42
44
  }