@mearl/client 3.0.0-beta.4 → 3.0.0-beta.7

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 (56) hide show
  1. package/README.md +95 -16
  2. package/dist/actionTimeouts.js +1 -1
  3. package/dist/autoUpdate.js +2 -1
  4. package/dist/capabilityHelp.d.ts +4 -0
  5. package/dist/capabilityHelp.js +69 -0
  6. package/dist/check.d.ts +5 -6
  7. package/dist/check.js +4 -7
  8. package/dist/cli.js +22 -56
  9. package/dist/commandHelp.d.ts +3 -0
  10. package/dist/commandHelp.js +79 -0
  11. package/dist/commandSuggestions.d.ts +3 -0
  12. package/dist/commandSuggestions.js +16 -5
  13. package/dist/commands.d.ts +78 -62
  14. package/dist/commands.js +67 -49
  15. package/dist/index.d.ts +6 -2
  16. package/dist/index.js +10 -4
  17. package/dist/nativeHost.js +1 -1
  18. package/dist/plugins/clients/pluginHostConnection.d.ts +2 -0
  19. package/dist/plugins/clients/pluginHostConnection.js +3 -1
  20. package/dist/plugins/officialPluginInstaller.js +2 -1
  21. package/dist/plugins/pluginRegistry.d.ts +1 -1
  22. package/dist/plugins/pluginRegistry.js +12 -4
  23. package/dist/plugins/runtime/pluginCommandWorker.js +8 -6
  24. package/dist/plugins/runtime/pluginExecutor.d.ts +5 -0
  25. package/dist/plugins/runtime/pluginExecutor.js +15 -2
  26. package/dist/plugins/runtime/pluginExecutorProtocol.d.ts +4 -0
  27. package/dist/plugins/runtime/pluginExecutorSupervisor.js +2 -1
  28. package/dist/plugins/runtime/pluginHostServer.js +4 -0
  29. package/dist/plugins/runtime/pluginPageManager.js +0 -4
  30. package/dist/plugins/runtime/pluginRevision.js +0 -51
  31. package/dist/plugins/runtime/pluginStateStore.js +3 -8
  32. package/dist/plugins/runtime/pluginTaskManager.js +4 -8
  33. package/dist/plugins/sdk/plugin.js +1 -1
  34. package/dist/plugins/sdk/pluginModelDefinition.js +0 -6
  35. package/dist/plugins/sdk/pluginPageDefinition.d.ts +0 -1
  36. package/dist/plugins/sdk/pluginPageDefinition.js +2 -18
  37. package/dist/plugins/sdk/pluginRecoveryDefinition.js +0 -3
  38. package/dist/plugins/sdk/pluginStateDefinition.d.ts +0 -1
  39. package/dist/plugins/sdk/pluginStateDefinition.js +0 -11
  40. package/dist/runtimePaths.d.ts +1 -0
  41. package/dist/runtimePaths.js +1 -0
  42. package/dist/script.d.ts +30 -0
  43. package/dist/script.js +261 -0
  44. package/dist/scriptCli.d.ts +1 -0
  45. package/dist/scriptCli.js +94 -0
  46. package/dist/scriptErrors.d.ts +8 -0
  47. package/dist/scriptErrors.js +21 -0
  48. package/dist/scriptWorker.d.ts +1 -0
  49. package/dist/scriptWorker.js +50 -0
  50. package/dist/sdkMethods.d.ts +3 -0
  51. package/dist/sdkMethods.js +9 -0
  52. package/dist/socket.js +34 -9
  53. package/dist/unified.js +11 -1
  54. package/dist/unifiedConfig.d.ts +4 -0
  55. package/dist/unifiedConfig.js +11 -0
  56. package/package.json +13 -11
package/dist/commands.js CHANGED
@@ -1,6 +1,4 @@
1
- import { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, } from '@mearl/browser-core';
2
- /** 容器档案名列表(拼进参数说明,新增档案无需改本文件) */
3
- const APP_PROFILE_NAME_LIST = APP_PROFILE_NAMES.join('/');
1
+ import { BROWSER_DEVICE_PRESET_NAMES, } from '@mearl/browser-core';
4
2
  const DEVICE_PRESET_TYPE = BROWSER_DEVICE_PRESET_NAMES.map(name => JSON.stringify(name)).join(' | ');
5
3
  const PAGE_DIAGNOSTICS_PARAM = {
6
4
  name: 'diagnostics',
@@ -30,13 +28,20 @@ const PAGE_OBSERVE_PARAM = {
30
28
  const PAGE_ACTION_RESULT = [
31
29
  '默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。',
32
30
  'observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。',
33
- '包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。失败以退出码 1 结束。',
31
+ '包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。',
34
32
  'observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。',
35
33
  'effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。',
36
34
  'fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。',
37
35
  '显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。',
38
36
  ];
39
37
  export const COMMANDS = [
38
+ {
39
+ name: 'get_capabilities',
40
+ description: '只读查询当前目标浏览器的动作、设备和已安装 App 能力、限制与 Skill/帮助入口;不打开页面或启用模拟',
41
+ params: [{ name: 'app', type: 'string', description: '可选,使用目录中的完整 App ID 查询桥方法、可调设置和默认版本详情' }],
42
+ notes: ['沿用 --browser / --session 路由;不传 app 返回摘要。apps.status=partial 表示目录未完整取得,unsupported 表示目标未提供目录;不能把两者当作未安装。', 'available=false 的 App 当前不可启用;支持某个 action 不代表已有域名权限或真实设备授权。按需读取 usage.skills / usage.documentation,查询不会自动安装或加载 Skill。'],
43
+ examples: ['get_capabilities', `get_capabilities --payload '{"app":"example/app"}'`],
44
+ },
40
45
  {
41
46
  name: 'browser_control',
42
47
  description: '把控制会话交给用户;用户在 Mearl 浏览器管理页交还后重新观察页面',
@@ -96,7 +101,7 @@ export const COMMANDS = [
96
101
  },
97
102
  {
98
103
  name: 'get_requests',
99
- description: '获取最近的网络请求(mtop 或普通 xhr/fetch)',
104
+ description: '获取最近的网络请求(协议请求或普通 xhr/fetch)',
100
105
  params: [
101
106
  {
102
107
  name: 'tabId',
@@ -107,8 +112,8 @@ export const COMMANDS = [
107
112
  { name: 'count', type: 'number', description: '返回条数,默认 10' },
108
113
  {
109
114
  name: 'source',
110
- type: '"mtop" | "requests" | "all"',
111
- description: '数据来源,不填时默认 mtop;all 返回全部业务请求',
115
+ type: '"protocol" | "requests" | "all"',
116
+ description: '数据来源,不填时默认 all;all 返回全部业务请求',
112
117
  },
113
118
  { name: 'filter', type: 'string', description: '按 URL 或 API 名称过滤,支持正则' },
114
119
  { name: 'since', type: 'number', description: '只返回最近 N 秒内的请求' },
@@ -128,7 +133,7 @@ export const COMMANDS = [
128
133
  { name: 'keysOnly', type: 'boolean', description: '只返回数据结构,不返回叶子值' },
129
134
  ],
130
135
  notes: [
131
- '流式请求开始后即进入缓冲区;页面及 App 桥代发的 MTop SSE 都归入 mtop。无法归属当前页面 target 的 Service Worker 请求不在采集范围内。',
136
+ '流式请求开始后即进入缓冲区;页面及 App 桥代发的 协议 SSE 都归入 protocol。无法归属当前页面 target 的 Service Worker 请求不在采集范围内。',
132
137
  ],
133
138
  result: [
134
139
  'nextCursor 用于同类查询续读,hasMore 表示还有数据,gap 表示缓冲区淘汰造成缺口;checkpoint cursor 可分别用于三类查询的初始边界。',
@@ -137,7 +142,7 @@ export const COMMANDS = [
137
142
  ],
138
143
  examples: [
139
144
  `get_requests --payload '{"tabId":12345,"count":5}'`,
140
- `get_requests --payload '{"tabId":12345,"source":"mtop","filter":"cart"}'`,
145
+ `get_requests --payload '{"tabId":12345,"source":"protocol","filter":"cart"}'`,
141
146
  ],
142
147
  },
143
148
  {
@@ -244,7 +249,7 @@ export const COMMANDS = [
244
249
  '大字段可用顶层 @<绝对路径> 文件引用或 --payload-file;文件展开后仍受 Native Messaging 单条消息大小限制。',
245
250
  ],
246
251
  examples: [
247
- `set_mock --payload '{"tabId":12345,"apiName":"mtop.cart.query","mockData":{"ret":["SUCCESS::调用成功"]}}'`,
252
+ `set_mock --payload '{"tabId":12345,"apiName":"cart.query","mockData":{"ret":["SUCCESS::调用成功"]}}'`,
248
253
  `set_mock --payload-file ./mock-data.json`,
249
254
  ],
250
255
  },
@@ -298,7 +303,7 @@ export const COMMANDS = [
298
303
  ],
299
304
  notes: [
300
305
  '扩展模式规则持久化;CDP 仅支持会话级 modifyHeaders 请求头 set,不支持响应头、initiatorDomains 或其他动作。Provider 是否支持以其能力声明为准。',
301
- '扩展模式会检查规则请求 URL 和显式来源域名的访问权限;缺失时仍保存规则,返回 warnings 和 missingOrigins,不自动弹窗。可在请求规则页面授权,或按提示调用 request_domain_permission。复杂 URL 表达式无法完整推断域名时会提示手动检查。',
306
+ '扩展模式会检查规则请求 URL 和显式来源域名的访问权限;缺失时仍保存规则,返回 warnings 和 missingOrigins,不自动弹窗。可在请求规则页面授权,或按提示调用 request_permission。复杂 URL 表达式无法完整推断域名时会提示手动检查。',
302
307
  ],
303
308
  examples: [
304
309
  `set_rule --payload '{"actionType":"block","filter":"^https://example\\\\.com/ads/.*"}'`,
@@ -364,32 +369,28 @@ export const COMMANDS = [
364
369
  ],
365
370
  },
366
371
  {
367
- name: 'send_mtop_request',
368
- description: '使用目标浏览器 Cookie 发起 mtop 请求,自动处理签名和 token',
372
+ name: 'send_protocol_request',
373
+ description: '通过已安装的请求协议能力,使用目标浏览器 Cookie 发送请求',
369
374
  params: [
370
- { name: 'api', type: 'string', required: true, description: 'mtop API 名称' },
371
- { name: 'data', type: 'object', description: '请求数据' },
372
- { name: 'version', type: 'string', description: 'API 版本,默认 1.0' },
373
- { name: 'method', type: 'string', description: 'HTTP 方法,默认 GET' },
374
- { name: 'appKey', type: 'string', description: '签名使用的 appKey,默认 12574478' },
375
- { name: 'env', type: '"prod" | "pre"', description: '请求环境,默认 prod' },
376
- { name: 'timeout', type: 'number', description: '超时毫秒数,默认 10000' },
375
+ { name: 'timeout', type: 'number', description: '宿主超时毫秒数;协议能力负责校验具体参数' },
377
376
  ],
378
377
  notes: [
379
- '需要目标浏览器有对应登录态;Mearl 自动处理签名,不依赖页面脚本。api 以 mopen. 开头时使用 mopen 协议。',
380
- 'env=prod 使用 h5api.m.taobao.com,pre 使用 h5api.wapa.taobao.com。',
381
- ],
382
- result: ['返回 success、response、requestData;业务成功需检查 response.ret。'],
383
- examples: [
384
- `send_mtop_request --payload '{"api":"mtop.trade.order.detail","data":{"orderId":"123"}}'`,
378
+ '需要安装提供 request.protocol 的能力包;参数、签名、网关与响应协议由能力包定义。',
379
+ '使用 --payload 传递协议文档声明的参数;核心不设置业务参数默认值。',
385
380
  ],
381
+ result: ['返回 success、response、requestData;业务结果由协议定义。'],
382
+ examples: [`send_protocol_request --payload '{"operation":"example","data":{}}'`],
386
383
  },
387
384
  // ── 浏览器 ────────────────────────────────────────────────────────────────
388
385
  {
389
386
  name: 'browser_list',
390
387
  description: '列出所有浏览器及其连接状态、类型和 Provider 托管实例信息',
391
388
  summary: '列出本地及远端浏览器与连接状态',
392
- examples: ['browser_list'],
389
+ params: [
390
+ { name: 'provider', type: 'string', description: '配合 launchForm,仅查询一个本机已安装 Provider 的启动候选;需 --local' },
391
+ { name: 'launchForm', type: 'object', description: '现有动态启动表单请求:{variant?,values:{}},不创建或接入实例' },
392
+ ],
393
+ examples: ['browser_list', 'browser_list --local'],
393
394
  },
394
395
  {
395
396
  name: 'browser_release',
@@ -496,6 +497,9 @@ export const COMMANDS = [
496
497
  name: 'tab_open',
497
498
  description: '打开或按 URL 规则复用 Tab,并等待新页面加载完成',
498
499
  aliases: ['tab_new', 'new_tab', 'tab_create', 'create_tab'],
500
+ aliasExamples: {
501
+ tab_activate: `tab_open --payload '{"url":"https://example.com","reuse":"require","active":true}'`,
502
+ },
499
503
  params: [
500
504
  { name: 'url', type: 'string', required: true, description: '要打开的 URL' },
501
505
  {
@@ -541,7 +545,7 @@ export const COMMANDS = [
541
545
  {
542
546
  name: 'appProfile',
543
547
  type: 'string',
544
- description: '按已安装档案应用 App 环境;内置基础档案只提供 UA/版本,完整桥由能力包提供。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true',
548
+ description: '按已安装档案应用 App 环境,ID 与限制见当前动态帮助。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true',
545
549
  },
546
550
  {
547
551
  name: 'os',
@@ -567,9 +571,9 @@ export const COMMANDS = [
567
571
  `tab_open --payload '{"url":"https://example.com"}'`,
568
572
  `tab_open --payload '{"url":"https://example.com/page","reuse":"prefer","match":{"ignoreSearch":true}}'`,
569
573
  `tab_open --payload '{"url":"https://m.example.com","emulation":{"preset":"iphone-17-pro"}}'`,
570
- `tab_open --payload '{"url":"https://example.com/app/home","appProfile":"fliggy"}'`,
571
- `tab_open --payload '{"url":"https://example.com/app/home","appProfile":"fliggy","emulation":{"preset":"ipad-mini-a17-pro","orientation":"landscape","deviceShell":true,"splitScreen":true}}'`,
572
- `tab_open --payload '{"url":"https://example.com","appProfile":"fliggy","os":"android"}'`,
574
+ `tab_open --payload '{"url":"https://example.com/app/home","appProfile":"reader"}'`,
575
+ `tab_open --payload '{"url":"https://example.com/app/home","appProfile":"reader","emulation":{"preset":"ipad-mini-a17-pro","orientation":"landscape","deviceShell":true,"splitScreen":true}}'`,
576
+ `tab_open --payload '{"url":"https://example.com","appProfile":"reader","os":"android"}'`,
573
577
  `tab_open --payload '{"url":"https://example.com","group":"验证任务"}'`,
574
578
  ],
575
579
  },
@@ -613,6 +617,9 @@ export const COMMANDS = [
613
617
  {
614
618
  name: 'page_click',
615
619
  description: '点击页面元素,支持 CSS/@ref、可见文本或视口坐标定位',
620
+ aliasExamples: {
621
+ page_select: `page_click --payload '{"tabId":12345,"text":"选项文案","role":"option"}'`,
622
+ },
616
623
  params: [
617
624
  {
618
625
  name: 'selector',
@@ -666,7 +673,8 @@ export const COMMANDS = [
666
673
  result: PAGE_ACTION_RESULT,
667
674
  notes: [
668
675
  'selector / text / point 必须且只能提供一个。隐藏页 auto 使用 DOM;只有明确依赖真实用户手势时才激活页面。',
669
- '原生 select 可按 option 文本点击;checkbox/radio 返回 controlState.checkedBefore/checkedAfter。',
676
+ 'auto 对原生 option、checkbox/radio 使用状态感知的 DOM 路径;显式 mouse/touch 保留可信输入。',
677
+ '原生 select 可按 option 文本点击;auto/dom 返回 selectionState.valueBefore/valueAfter/selectedAfter,检查选中状态是否保持。checkbox/radio 返回 controlState.checkedBefore/checkedAfter。',
670
678
  ],
671
679
  examples: [
672
680
  `page_click --payload '{"tabId":12345,"text":"提交"}'`,
@@ -721,6 +729,9 @@ export const COMMANDS = [
721
729
  {
722
730
  name: 'page_type',
723
731
  description: '向输入框填写文本,兼容 React 受控组件',
732
+ aliasExamples: {
733
+ page_fill: `page_type --payload '{"tabId":12345,"selector":"@e6","text":"填写内容","clearFirst":true}'`,
734
+ },
724
735
  params: [
725
736
  {
726
737
  name: 'selector',
@@ -906,6 +917,8 @@ export const COMMANDS = [
906
917
  description: '按键名称:Enter/Tab/Escape/Backspace 等',
907
918
  },
908
919
  { name: 'modifiers', type: 'string[]', description: '修饰键数组:ctrl/alt/shift/meta/cmd' },
920
+ { name: 'dialogId', type: 'string', description: '原生弹窗回执或 page_snapshot 中的 dialog.id;仅配合 Enter 接受或 Escape 取消' },
921
+ { name: 'promptText', type: 'string', description: '仅用于 dialogId + Enter 接受原生 prompt 时填写文本' },
909
922
  {
910
923
  name: 'pressMode',
911
924
  type: '"auto" | "dom" | "keyboard"',
@@ -917,6 +930,7 @@ export const COMMANDS = [
917
930
  ],
918
931
  notes: [
919
932
  '隐藏页 DOM 模式支持 Tab 焦点移动、原生控件 Enter/Space 激活和输入框 Enter 提交;编辑、选择、方向键和页面滚动等原生行为需可见页与 pressMode=keyboard。',
933
+ '原生 alert/confirm/prompt/beforeunload 弹窗需显式传 dialogId;普通 Enter 不处理弹窗。弹窗处理绕过 DOM 观察,不自动重放被中断的动作;处理后重新读取页面。',
920
934
  ],
921
935
  result: [
922
936
  ...PAGE_ACTION_RESULT,
@@ -956,6 +970,7 @@ export const COMMANDS = [
956
970
  ],
957
971
  notes: [
958
972
  'time / selector / text / condition 必须且只能提供一个;time 范围 0–60000ms。页面交互已有内置观察,仅在需要独立等待条件时使用。',
973
+ '等待后须立即执行已知动作时,用 mearl run 在同一脚本中 await pageWait 后紧接 pageClick,避免模型往返。整体 --timeout 须覆盖等待及后续动作;等待失败不继续点击。',
959
974
  ],
960
975
  examples: [
961
976
  `page_wait --payload '{"tabId":12345,"time":1000}'`,
@@ -968,6 +983,9 @@ export const COMMANDS = [
968
983
  {
969
984
  name: 'page_navigate',
970
985
  description: '在当前标签页内导航到 URL、刷新页面,或沿浏览历史后退/前进',
986
+ aliasExamples: {
987
+ page_reload: `page_navigate --payload '{"tabId":12345,"refresh":true}'`,
988
+ },
971
989
  params: [
972
990
  {
973
991
  name: 'url',
@@ -1167,14 +1185,14 @@ export const COMMANDS = [
1167
1185
  },
1168
1186
  {
1169
1187
  name: 'set_app_profile',
1170
- description: '设置已安装的 App 档案;完整 JSBridge 由能力包提供。可同时设置 MTop 页面接口环境,传 emulation 时联动设备模拟',
1188
+ description: '设置已安装的 App 档案;完整 JSBridge 由能力包提供。可同时设置已安装的网络控件,传 emulation 时联动设备模拟',
1171
1189
  summary: '设置或清除目标标签页的 App 容器模拟',
1172
1190
  params: [
1173
1191
  { name: 'enabled', type: 'boolean', required: true, description: 'true=启用,false=清除' },
1174
1192
  {
1175
1193
  name: 'profile',
1176
1194
  type: 'string',
1177
- description: `容器档案名(enabled=true 时必填);内置 ${APP_PROFILE_NAME_LIST}`,
1195
+ description: '已安装的容器档案 ID(enabled=true 时必填);当前动态帮助直接展示目标浏览器目录',
1178
1196
  },
1179
1197
  { name: 'os', type: '"ios" | "android"', description: '容器系统,默认 ios' },
1180
1198
  {
@@ -1199,9 +1217,9 @@ export const COMMANDS = [
1199
1217
  description: '声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本',
1200
1218
  },
1201
1219
  {
1202
- name: 'mtopEnv',
1203
- type: '"default" | "prod" | "pre"',
1204
- description: '可选 MTop 页面接口环境;default 关闭环境改写,省略时保持当前设置。仅作用于目标 tab',
1220
+ name: 'networkControl',
1221
+ type: 'object | null',
1222
+ description: '可选已安装网络控件 {id,value};null 清除,省略时保持当前设置。仅作用于目标 tab',
1205
1223
  },
1206
1224
  {
1207
1225
  name: 'emulation',
@@ -1217,15 +1235,15 @@ export const COMMANDS = [
1217
1235
  'bridgeConfig.methods 按方法声明 behavior / response / noop 三选一,可传 silent;只支持受控行为、固定 JSON 回包或成功空实现,不执行配置脚本。',
1218
1236
  ],
1219
1237
  result: [
1220
- '返回 enabled、profile、label、userAgent、os、mtopEnv、reloaded;联动设备时额外返回实际 emulation。',
1238
+ '返回 enabled、profile、label、userAgent、os、networkControl、reloaded;联动设备时额外返回实际 emulation。',
1221
1239
  ],
1222
1240
  examples: [
1223
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"fliggy"}'`,
1224
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"fliggy","mtopEnv":"pre"}'`,
1225
- `set_app_profile --payload '{"tabId":12345,"enabled":false,"mtopEnv":"prod"}'`,
1226
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"fliggy","emulation":{}}'`,
1227
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"alipay","emulation":{"preset":"pixel-11","deviceShell":true}}'`,
1228
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"fliggy","emulation":{"preset":"ipad-mini-a17-pro","orientation":"landscape","deviceShell":true,"splitScreen":true}}'`,
1241
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader"}'`,
1242
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","networkControl":{"id":"environment","value":"pre"}}'`,
1243
+ `set_app_profile --payload '{"tabId":12345,"enabled":false,"networkControl":null}'`,
1244
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","emulation":{}}'`,
1245
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"wallet","emulation":{"preset":"pixel-11","deviceShell":true}}'`,
1246
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","emulation":{"preset":"ipad-mini-a17-pro","orientation":"landscape","deviceShell":true,"splitScreen":true}}'`,
1229
1247
  `set_app_profile --payload '{"tabId":12345,"enabled":false}'`,
1230
1248
  `set_app_profile --payload '{"tabId":12345,"enabled":false,"emulation":{}}'`,
1231
1249
  ],
@@ -1280,9 +1298,9 @@ export const COMMANDS = [
1280
1298
  result: ['找到时 found=true 并返回 value/domain/path;不存在时 found=false。'],
1281
1299
  examples: [
1282
1300
  `get_cookie --payload '{"tabId":12345,"name":"feature_flag"}'`,
1283
- `get_cookie --payload '{"url":"https://www.taobao.com/","name":"tracknick"}'`,
1301
+ `get_cookie --payload '{"url":"https://www.shop.com/","name":"account_name"}'`,
1284
1302
  `get_cookie --payload '{"tabId":12345,"name":"feature_channel","domain":"example.com"}'`,
1285
- `get_cookie --payload '{"tabId":12345,"name":"tracknick","url":"https://www.taobao.com/","domain":"example.com","path":"/"}'`,
1303
+ `get_cookie --payload '{"tabId":12345,"name":"account_name","url":"https://www.shop.com/","domain":"example.com","path":"/"}'`,
1286
1304
  ],
1287
1305
  },
1288
1306
  {
@@ -1327,7 +1345,7 @@ export const COMMANDS = [
1327
1345
  result: ['返回 url/name/domain/path/reloaded,指定标签页时包含 tabId;不回显 Cookie 值。'],
1328
1346
  examples: [
1329
1347
  `set_cookie --payload '{"tabId":12345,"name":"feature_flag","value":"enabled"}'`,
1330
- `set_cookie --payload '{"url":"https://www.taobao.com/","name":"feature_flag","value":"enabled"}'`,
1348
+ `set_cookie --payload '{"url":"https://www.shop.com/","name":"feature_flag","value":"enabled"}'`,
1331
1349
  `set_cookie --payload '{"tabId":12345,"name":"feature_channel","value":"3.6.63","domain":"example.com","reload":true}'`,
1332
1350
  ],
1333
1351
  },
@@ -1342,7 +1360,7 @@ export const COMMANDS = [
1342
1360
  examples: [`page_frames --payload '{"tabId":12345}'`],
1343
1361
  },
1344
1362
  {
1345
- name: 'request_domain_permission',
1363
+ name: 'request_permission',
1346
1364
  description: '申请访问指定域名的权限(必要时弹窗让用户授权)',
1347
1365
  params: [
1348
1366
  {
@@ -1357,7 +1375,7 @@ export const COMMANDS = [
1357
1375
  '弹窗等待用户选择,90 秒超时或关闭按拒绝处理;granted=true 后才重试原请求。',
1358
1376
  ],
1359
1377
  result: ['返回 granted、domain,授权成功时包含 origin。'],
1360
- examples: [`request_domain_permission --payload '{"domain":"example.com"}'`],
1378
+ examples: [`request_permission --payload '{"domain":"example.com"}'`],
1361
1379
  },
1362
1380
  // ── 组合调用 ────────────────────────────────────────────────────────────────
1363
1381
  {
package/dist/index.d.ts CHANGED
@@ -13,7 +13,7 @@ export { UnifiedClient, UnifiedClient as MearlClient, decodeBrowserTarget, invok
13
13
  export type { UnifiedClientOptions, UnifiedInvokeOptions, UnifiedInvokeOptions as InvokeOptions, } from './unified.js';
14
14
  export type { PluginInvocation, PluginInvokeOptions, PluginProgress, StructuredPluginProgress, } from './plugins/sdk/plugin.js';
15
15
  export { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, BROWSER_ACTIONS, BROWSER_CAPABILITY_ACTIONS, BROWSER_COMMAND_ACTIONS, EXTENSION_BROWSER_ACTIONS, PAGE_ACT_ACTIONS, SEND_REQUEST_MAX_FILES, SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES, SEND_REQUEST_MAX_TOTAL_FILE_BYTES, isBrowserAction, isBrowserCapabilityAction, isBrowserCommandAction, isExtensionBrowserAction, } from '@mearl/browser-core';
16
- export type { ActionStep, AppProfileName, BrowserAction, BrowserActionMap, BrowserActionRequest, BrowserActionResponse, BrowserCapabilities, BrowserCapabilityAction, BrowserCloseParams, BrowserCommandAction, BrowserInfo, BrowserInfoBase, BrowserJsonValue, BrowserLaunchParams, BrowserProviderOperation, BrowserListResult, BrowserPairingInfo, BrowserReleaseFailure, BrowserReleaseResult, BrowserProviderDefinition, BrowserProviderIdentity, BrowserProviderInputSchema, BrowserProviderListResult, BrowserReference, BrowserDetails, BrowserSource, BrowserStatus, BrowserScreenshotKind, BrowserTransport, CaptureCheckpointParams, CaptureCheckpointResult, BrowserDevicePresetName, BrowserEmulationOptions, ExtensionBrowserAction, GetCookieParams, GetCookieResult, GetEventsParams, GetLogsParams, GetMocksParams, GetRequestsParams, ManagedBrowserInfo, PageClickParams, PageDragEndpoint, PageDragParams, PageDragPoint, PageActAction, PageDiagnosticsOptions, PageDiagnosticsSetting, PageEvalParams, PageFramesParams, PageHoverParams, PageInspectFilter, PageInspectParams, PageNavigateParams, PageObservationWaitOptions, PagePressParams, PageScreenshotParams, PageScrollParams, PageSnapshotParams, PageSnapshotQuery, PageTypeParams, PageUploadParams, PageWaitParams, RegularBrowserInfo, RequestDomainPermissionParams, RulesResult, RunActionName, RunActionsParams, SelectBrowserParams, SelectBrowserResult, SendRequestFile, SendRequestBinaryBody, SendRequestFormValue, SendMtopRequestParams, SendRequestParams, SetAppProfileParams, SetCookieParams, SetCookieResult, SetDeviceEmulationParams, SetMockParams, SetRuleParams, SetTimezoneParams, SetTimezoneResult, TabCloseParams, TabOpenParams, } from '@mearl/browser-core';
16
+ export type { ActionStep, AppProfileName, BrowserAction, BrowserActionMap, BrowserActionRequest, BrowserActionResponse, BrowserCapabilities, BrowserCapabilityAction, BrowserCloseParams, BrowserCommandAction, BrowserInfo, BrowserInfoBase, BrowserJsonValue, BrowserLaunchParams, BrowserProviderOperation, BrowserListResult, BrowserListParams, BrowserPairingInfo, BrowserReleaseFailure, BrowserReleaseResult, BrowserProviderDefinition, BrowserProviderIdentity, BrowserProviderInputSchema, BrowserProviderListResult, BrowserReference, BrowserDetails, BrowserSource, BrowserStatus, BrowserScreenshotKind, BrowserTransport, CaptureCheckpointParams, CaptureCheckpointResult, BrowserDevicePresetName, BrowserEmulationOptions, ExtensionBrowserAction, GetCookieParams, GetCookieResult, GetEventsParams, GetLogsParams, GetMocksParams, GetRequestsParams, ManagedBrowserInfo, PageClickParams, PageDragEndpoint, PageDragParams, PageDragPoint, PageActAction, PageDiagnosticsOptions, PageDiagnosticsSetting, PageEvalParams, PageFramesParams, PageHoverParams, PageInspectFilter, PageInspectParams, PageNavigateParams, PageObservationWaitOptions, PagePressParams, PageScreenshotParams, PageScrollParams, PageSnapshotParams, PageSnapshotQuery, PageTypeParams, PageUploadParams, PageWaitParams, RegularBrowserInfo, RequestPermissionParams, RulesResult, RunActionName, RunActionsParams, SelectBrowserParams, SelectBrowserResult, SendRequestFile, SendRequestBinaryBody, SendRequestFormValue, SendProtocolRequestParams, SendRequestParams, SetAppProfileParams, SetCookieParams, SetCookieResult, SetDeviceEmulationParams, SetMockParams, SetRuleParams, SetTimezoneParams, SetTimezoneResult, TabCloseParams, TabOpenParams, } from '@mearl/browser-core';
17
17
  import { type UnifiedInvokeOptions } from './unified.js';
18
18
  import type * as Protocol from '@mearl/browser-core';
19
19
  export declare function tabCheckpoint(params: Protocol.CaptureCheckpointParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_checkpoint'>>;
@@ -25,8 +25,9 @@ export declare function getEvents(params: Protocol.GetEventsParams, options?: Un
25
25
  export declare function setMock(params: Protocol.SetMockParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'set_mock'>>;
26
26
  export declare function getMocks(params: Protocol.GetMocksParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'get_mocks'>>;
27
27
  export declare function sendRequest(params: Protocol.SendRequestParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'send_request'>>;
28
- export declare function sendMtopRequest(params: Protocol.SendMtopRequestParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'send_mtop_request'>>;
28
+ export declare function sendProtocolRequest(params: Protocol.SendProtocolRequestParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'send_protocol_request'>>;
29
29
  export declare function browserList(options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_list'>>;
30
+ export declare function browserList(params: Protocol.BrowserListParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_list'>>;
30
31
  export declare function selectBrowser(params?: Protocol.SelectBrowserParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_select_browser'>>;
31
32
  export declare function browserRelease(options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_release'>>;
32
33
  export declare function browserRelease(params: Protocol.BrowserReleaseParams, options: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_release'>>;
@@ -37,6 +38,7 @@ export declare function getRules(options?: UnifiedInvokeOptions): Promise<Protoc
37
38
  export declare function pageScreenshot(params: Protocol.PageScreenshotParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'page_screenshot'>>;
38
39
  export declare function tabOpen(params: Protocol.TabOpenParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_open'>>;
39
40
  export declare function tabClose(params: Protocol.TabCloseParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_close'>>;
41
+ export declare function getCapabilities(params?: Protocol.GetCapabilitiesParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'get_capabilities'>>;
40
42
  export declare function tabList(options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_list'>>;
41
43
  export declare function tabGroup(params: Protocol.TabGroupParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_group'>>;
42
44
  export declare function pageClick(params: Protocol.PageClickParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'page_click'>>;
@@ -69,3 +71,5 @@ export declare function saveArtifact(artifact: Protocol.BrowserArtifact & {
69
71
  export { PluginStateClient } from './plugins/clients/pluginState.js';
70
72
  export { PluginModelClient } from './plugins/clients/pluginModels.js';
71
73
  export type * from '@mearl/browser-core';
74
+ export { getStructure as structure, type StructureOptions } from '@mearl/browser-core';
75
+ export type { GetCapabilitiesParams, BrowserCapabilityCatalog, AppCapability, AppProfileUsage } from '@mearl/browser-core';
package/dist/index.js CHANGED
@@ -36,11 +36,13 @@ export function getMocks(params, options) {
36
36
  export function sendRequest(params, options) {
37
37
  return invoke('send_request', params, options);
38
38
  }
39
- export function sendMtopRequest(params, options) {
40
- return invoke('send_mtop_request', params, options);
39
+ export function sendProtocolRequest(params, options) {
40
+ return invoke('send_protocol_request', params, options);
41
41
  }
42
- export function browserList(options) {
43
- return invoke('browser_list', {}, options);
42
+ export function browserList(paramsOrOptions = {}, options) {
43
+ return 'provider' in paramsOrOptions || 'launchForm' in paramsOrOptions
44
+ ? invoke('browser_list', paramsOrOptions, options)
45
+ : invoke('browser_list', {}, paramsOrOptions);
44
46
  }
45
47
  export function selectBrowser(params = {}, options) {
46
48
  return invoke('browser_select_browser', params, options);
@@ -71,6 +73,9 @@ export function tabOpen(params, options) {
71
73
  export function tabClose(params, options) {
72
74
  return invoke('tab_close', params, options);
73
75
  }
76
+ export function getCapabilities(params = {}, options) {
77
+ return invoke('get_capabilities', params, options);
78
+ }
74
79
  export function tabList(options) {
75
80
  return invoke('tab_list', {}, options);
76
81
  }
@@ -146,3 +151,4 @@ export async function saveArtifact(artifact, outputPath, options = {}) {
146
151
  }
147
152
  export { PluginStateClient } from './plugins/clients/pluginState.js';
148
153
  export { PluginModelClient } from './plugins/clients/pluginModels.js';
154
+ export { getStructure as structure } from '@mearl/browser-core';
@@ -96,7 +96,7 @@ export function inspectNativeHostInstallation(options = {}) {
96
96
  details: [
97
97
  ...details,
98
98
  `Registration: ${manifestPath}`,
99
- 'Run `mearl-native-host init` with a working Node.js, then reopen the extension. If the runtime package is missing, run `npx --@mearl:registry=https://registry.npmjs.org @mearl/setup update --local`.',
99
+ 'Run `mearl-native-host init` with a working Node.js, then reopen the extension. If the runtime package is missing, run `npx @mearl/setup update --local`.',
100
100
  ],
101
101
  });
102
102
  }
@@ -16,6 +16,8 @@ export interface PluginHostClientOptions extends PluginHostConnectionOptions {
16
16
  }
17
17
  export interface HostStatus {
18
18
  protocolVersion: number;
19
+ version?: string;
20
+ buildId?: string;
19
21
  host: PluginExecutionHost;
20
22
  pid: number;
21
23
  }
@@ -243,7 +243,9 @@ export class PluginHostConnection {
243
243
  this.connection = undefined;
244
244
  });
245
245
  }
246
- return (await this.connection).request(method, input, timeoutMs);
246
+ const connection = await this.connection;
247
+ // The handshake protocol defines compatibility; build identity is diagnostic metadata.
248
+ return connection.request(method, input, timeoutMs);
247
249
  }
248
250
  disconnect() {
249
251
  this.generation++;
@@ -2,6 +2,7 @@ import { closeSync, mkdirSync, openSync, rmSync, statSync, writeFileSync } from
2
2
  import { CLIENT_VERSION } from '../runtimePaths.js';
3
3
  import { join } from 'node:path';
4
4
  import { spawn } from 'node:child_process';
5
+ import { npmExecEnvironment } from '@mearl/daemon-core';
5
6
  import { findOfficialPlugin, readOfficialPluginCatalog } from './officialPluginCatalog.js';
6
7
  import { findPluginRegistration, pluginDirectory } from './pluginRegistry.js';
7
8
  const INSTALL_TIMEOUT_MS = 180_000;
@@ -60,7 +61,7 @@ function appendOutput(current, chunk) {
60
61
  function installWithSetup(name) {
61
62
  return new Promise((resolve, reject) => {
62
63
  const command = process.platform === 'win32' ? 'npx.cmd' : 'npx';
63
- const child = spawn(command, ['-y', '--@mearl:registry=https://registry.npmjs.org', `@mearl/setup@${CLIENT_VERSION}`, 'plugin', 'ensure', name], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
64
+ const child = spawn(command, ['-y', '--@mearl:registry=https://registry.npmjs.org', `@mearl/setup@${CLIENT_VERSION}`, 'plugin', 'ensure', name], { env: npmExecEnvironment(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
64
65
  let stdout = '';
65
66
  let stderr = '';
66
67
  let settled = false;
@@ -1,7 +1,7 @@
1
1
  import { type AppProfileContribution } from '@mearl/browser-core';
2
2
  export declare class PluginError extends Error {
3
3
  readonly code: string;
4
- constructor(code: string, message: string);
4
+ constructor(code: string, message: string, options?: ErrorOptions);
5
5
  }
6
6
  export interface PluginToolboxSandboxPage {
7
7
  type: 'sandbox';
@@ -9,6 +9,7 @@ import { buildPluginPageBundle, PLUGIN_PAGE_CHUNK_BYTES, PLUGIN_PAGE_RUNTIME_HAS
9
9
  import { pluginFileVersion } from './pluginSource.js';
10
10
  const RESERVED_NAMES = new Set([
11
11
  'check',
12
+ 'run',
12
13
  'help',
13
14
  'connector_list',
14
15
  'capture_checkpoint',
@@ -17,8 +18,8 @@ const RESERVED_NAMES = new Set([
17
18
  ]);
18
19
  export class PluginError extends Error {
19
20
  code;
20
- constructor(code, message) {
21
- super(`${code}: ${message}`);
21
+ constructor(code, message, options) {
22
+ super(`${code}: ${message}`, options);
22
23
  this.code = code;
23
24
  }
24
25
  }
@@ -405,8 +406,15 @@ export function findPluginRegistration(name) {
405
406
  ...(source ? { source } : {}),
406
407
  };
407
408
  }
408
- catch {
409
- throw new PluginError('PLUGIN_REGISTRY_INVALID', `无效的插件记录: ${file}`);
409
+ catch (cause) {
410
+ const reason = cause instanceof SyntaxError
411
+ ? 'JSON 格式错误'
412
+ : cause instanceof Error
413
+ ? cause.message
414
+ : '未知校验错误';
415
+ throw new PluginError('PLUGIN_REGISTRY_INVALID', `无效的插件记录: ${file} (${reason})`, {
416
+ cause,
417
+ });
410
418
  }
411
419
  }
412
420
  export function readPluginPageBundleChunk(name, hash, index) {
@@ -31,7 +31,7 @@ async function run(message) {
31
31
  : message.access === 'toolbox'
32
32
  ? invokeRegisteredToolboxPlugin
33
33
  : invokeRegisteredPlugin;
34
- const declaration = message.describe
34
+ const declaration = message.describe || message.help
35
35
  ? await loadPlugin(message.entry, {
36
36
  options: {},
37
37
  progress: () => { },
@@ -63,11 +63,13 @@ async function run(message) {
63
63
  const value = migration
64
64
  ? await migration.runMigration(message.invocation)
65
65
  : declaration
66
- ? {
67
- pageRuntime: declaration.pageRuntime,
68
- stateStorage: declaration.stateStorage,
69
- taskRecovery: declaration.taskRecovery,
70
- }
66
+ ? message.help
67
+ ? declaration.help(message.help.namespace, message.help.command)
68
+ : {
69
+ pageRuntime: declaration.pageRuntime,
70
+ stateStorage: declaration.stateStorage,
71
+ taskRecovery: declaration.taskRecovery,
72
+ }
71
73
  : message.model
72
74
  ? await (await import('@mearl/agent-runtime/codex')).runCodexModel(message.model, {
73
75
  signal: controller.signal,
@@ -6,6 +6,11 @@ export interface PluginExecutorDependencies {
6
6
  registration?: PluginRegistration;
7
7
  }
8
8
  export interface PluginExecutorOptions extends PluginInvokeOptions {
9
+ help?: {
10
+ namespace: string;
11
+ command?: string;
12
+ };
13
+ onOutput?: (stream: 'stdout' | 'stderr', chunk: Buffer) => void;
9
14
  describe?: boolean;
10
15
  model?: PluginModelRequest;
11
16
  recovery?: boolean;
@@ -8,7 +8,7 @@ import { stopPluginProcessTree } from './pluginProcessTree.js';
8
8
  import { processIdentity } from '@mearl/daemon-core';
9
9
  /** Executes registered trusted code in a process whose lifetime belongs to this invocation. */
10
10
  export async function executeRegisteredPlugin(plugin, invocation, context, options = {}, dependencies = {}) {
11
- const { signal, onProgress, source, access = 'public', executionTimeoutMs, cancellationGraceMs = 1000, workerPath, cwd, environment, describe, model, recovery, migration, expectedRecovery, onExecutorStarted, onExecutorStopped, onDiagnostic, ...invokeOptions } = options;
11
+ const { signal, onProgress, source, access = 'public', executionTimeoutMs, cancellationGraceMs = 1000, workerPath, cwd, environment, describe, help, onOutput, model, recovery, migration, expectedRecovery, onExecutorStarted, onExecutorStopped, onDiagnostic, ...invokeOptions } = options;
12
12
  for (const [name, value] of [
13
13
  ['executionTimeoutMs', executionTimeoutMs],
14
14
  ['cancellationGraceMs', cancellationGraceMs],
@@ -25,7 +25,7 @@ export async function executeRegisteredPlugin(plugin, invocation, context, optio
25
25
  const entryVersion = pluginFileVersion(registration.entry);
26
26
  return new Promise((resolve, reject) => {
27
27
  const child = fork(workerPath ?? PLUGIN_WORKER_PATH, [], {
28
- stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
28
+ stdio: ['ignore', onOutput ? 'pipe' : 'ignore', 'pipe', 'ipc'],
29
29
  serialization: 'advanced',
30
30
  execArgv: [],
31
31
  detached: process.platform !== 'win32',
@@ -46,6 +46,18 @@ export async function executeRegisteredPlugin(plugin, invocation, context, optio
46
46
  child.stderr?.on('data', chunk => {
47
47
  stderr = `${stderr}${chunk}`.slice(-65536);
48
48
  });
49
+ if (onOutput) {
50
+ for (const stream of ['stdout', 'stderr']) {
51
+ child[stream]?.on('data', chunk => {
52
+ try {
53
+ onOutput(stream, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
54
+ }
55
+ catch (error) {
56
+ stop(error instanceof Error ? error : new Error(String(error)));
57
+ }
58
+ });
59
+ }
60
+ }
49
61
  const send = (message) => {
50
62
  if (!child.connected || exited)
51
63
  return;
@@ -209,6 +221,7 @@ export async function executeRegisteredPlugin(plugin, invocation, context, optio
209
221
  entryVersion,
210
222
  access,
211
223
  describe,
224
+ help,
212
225
  model,
213
226
  recovery,
214
227
  migration,
@@ -13,6 +13,10 @@ export type PluginWorkerRequest = {
13
13
  access: 'public' | 'toolbox' | 'runtime';
14
14
  registration?: PluginRegistration;
15
15
  describe?: boolean;
16
+ help?: {
17
+ namespace: string;
18
+ command?: string;
19
+ };
16
20
  model?: PluginModelRequest;
17
21
  recovery?: boolean;
18
22
  migration?: boolean;
@@ -3,13 +3,14 @@ import { fileURLToPath } from 'node:url';
3
3
  /** Ownership monitoring runs outside the event loop that executes plugin code. */
4
4
  export function supervisePluginExecutor(workerUrl) {
5
5
  const child = fork(fileURLToPath(workerUrl), ['--command'], {
6
- stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
6
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
7
7
  serialization: 'advanced',
8
8
  execArgv: [],
9
9
  });
10
10
  let ending = false;
11
11
  let forwarded = Promise.resolve();
12
12
  child.stderr?.on('data', chunk => process.stderr.write(chunk));
13
+ child.stdout?.on('data', chunk => process.stdout.write(chunk));
13
14
  const terminate = () => {
14
15
  if (process.platform === 'win32') {
15
16
  try {