@mearl/client 3.0.0-beta.1 → 3.0.0-beta.10

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 (72) hide show
  1. package/README.md +333 -246
  2. package/README.zh-CN.md +612 -0
  3. package/dist/actionTimeouts.js +0 -3
  4. package/dist/autoUpdate.js +3 -1
  5. package/dist/bin.js +12 -5
  6. package/dist/capabilityHelp.d.ts +4 -0
  7. package/dist/capabilityHelp.js +68 -0
  8. package/dist/check.d.ts +5 -6
  9. package/dist/check.js +2 -5
  10. package/dist/cli.js +29 -56
  11. package/dist/commandHelp.d.ts +3 -0
  12. package/dist/commandHelp.js +79 -0
  13. package/dist/commandSuggestions.d.ts +3 -0
  14. package/dist/commandSuggestions.js +16 -5
  15. package/dist/commands.d.ts +70 -65
  16. package/dist/commands.js +60 -55
  17. package/dist/guide.d.ts +46 -0
  18. package/dist/guide.js +80 -0
  19. package/dist/guides/api-browser-setup.md +93 -0
  20. package/dist/guides/api-page-automation.md +140 -0
  21. package/dist/guides/api-page-debugging.md +39 -0
  22. package/dist/guides/api-page-environment.md +37 -0
  23. package/dist/guides/api-request-debugging.md +54 -0
  24. package/dist/guides/browser-tool-cooperation.md +23 -0
  25. package/dist/guides/core.md +60 -0
  26. package/dist/guides/related-skills.md +51 -0
  27. package/dist/guides/scripting.md +21 -0
  28. package/dist/guides/troubleshooting.md +170 -0
  29. package/dist/index.d.ts +5 -2
  30. package/dist/index.js +8 -5
  31. package/dist/plugins/clients/pluginHostConnection.d.ts +2 -0
  32. package/dist/plugins/clients/pluginHostConnection.js +3 -1
  33. package/dist/plugins/officialPluginInstaller.js +2 -1
  34. package/dist/plugins/pluginRegistry.d.ts +1 -1
  35. package/dist/plugins/pluginRegistry.js +21 -7
  36. package/dist/plugins/runtime/pluginCommandWorker.js +8 -6
  37. package/dist/plugins/runtime/pluginDiagnostics.js +1 -1
  38. package/dist/plugins/runtime/pluginExecutor.d.ts +5 -0
  39. package/dist/plugins/runtime/pluginExecutor.js +15 -2
  40. package/dist/plugins/runtime/pluginExecutorProtocol.d.ts +4 -0
  41. package/dist/plugins/runtime/pluginExecutorSupervisor.js +2 -1
  42. package/dist/plugins/runtime/pluginHostServer.js +5 -1
  43. package/dist/plugins/runtime/pluginPageManager.js +2 -6
  44. package/dist/plugins/runtime/pluginRevision.js +0 -51
  45. package/dist/plugins/runtime/pluginStateManager.js +1 -1
  46. package/dist/plugins/runtime/pluginStateStore.js +3 -8
  47. package/dist/plugins/runtime/pluginTaskManager.js +5 -9
  48. package/dist/plugins/sdk/plugin.d.ts +9 -0
  49. package/dist/plugins/sdk/plugin.js +22 -9
  50. package/dist/plugins/sdk/pluginModelDefinition.js +0 -6
  51. package/dist/plugins/sdk/pluginPageDefinition.d.ts +0 -1
  52. package/dist/plugins/sdk/pluginPageDefinition.js +2 -18
  53. package/dist/plugins/sdk/pluginRecoveryDefinition.js +0 -3
  54. package/dist/plugins/sdk/pluginStateDefinition.d.ts +0 -1
  55. package/dist/plugins/sdk/pluginStateDefinition.js +0 -11
  56. package/dist/runtimePaths.d.ts +1 -0
  57. package/dist/runtimePaths.js +1 -0
  58. package/dist/script.d.ts +30 -0
  59. package/dist/script.js +261 -0
  60. package/dist/scriptCli.d.ts +1 -0
  61. package/dist/scriptCli.js +94 -0
  62. package/dist/scriptErrors.d.ts +8 -0
  63. package/dist/scriptErrors.js +21 -0
  64. package/dist/scriptWorker.d.ts +1 -0
  65. package/dist/scriptWorker.js +50 -0
  66. package/dist/sdkMethods.d.ts +3 -0
  67. package/dist/sdkMethods.js +9 -0
  68. package/dist/socket.js +33 -8
  69. package/dist/unified.js +11 -1
  70. package/dist/unifiedConfig.d.ts +4 -0
  71. package/dist/unifiedConfig.js +11 -0
  72. package/package.json +14 -12
@@ -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;
@@ -223,6 +224,7 @@ export function setupUpdateCommand(version, packageRoot, profiles, updateClient)
223
224
  command: 'npx',
224
225
  args: [
225
226
  '--yes',
227
+ '--@mearl:registry=https://registry.npmjs.org',
226
228
  `@mearl/setup@${version}`,
227
229
  'update',
228
230
  '--yes',
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 = await 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.d.ts CHANGED
@@ -15,13 +15,12 @@ export interface ProviderVersionInfo {
15
15
  version: string;
16
16
  protocolVersion: number;
17
17
  }
18
- export type BrowserAccessCapability = {
19
- kind: 'sandbox-novnc';
20
- environment: 'opensandbox' | 'aone';
21
- port: number;
22
- } | {
18
+ export interface BrowserAccessCapability {
23
19
  kind: string;
24
- };
20
+ label?: string;
21
+ environment?: string;
22
+ port?: number;
23
+ }
25
24
  export type BrowserAccess = BrowserAccessCapability & {
26
25
  url: string;
27
26
  };
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')
@@ -46,10 +46,7 @@ function formatError(error) {
46
46
  return error instanceof Error ? error.message : String(error);
47
47
  }
48
48
  function browserAccessLabel(access) {
49
- if (access.kind === 'sandbox-novnc' && 'environment' in access) {
50
- return access.environment === 'opensandbox' ? 'OpenSandbox noVNC' : 'AONE Sandbox noVNC';
51
- }
52
- return access.kind;
49
+ return access.label || access.kind;
53
50
  }
54
51
  async function getBrowserAccess(client, browser, capability, timeoutSec) {
55
52
  const result = (await client.invoke('get_browser_access_url', {}, { timeoutSec, browser: browser.browserId }));
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';
@@ -6,10 +7,11 @@ import { resolveActionTimeoutSec } from './actionTimeouts.js';
6
7
  import { runUnifiedCheck, parseCheckTimeout } from './check.js';
7
8
  import { commandInput, findCommandSuggestions, formatCommandSuggestions, } from './commandSuggestions.js';
8
9
  import { COMMANDS, COMMAND_MAP } from './commands.js';
10
+ import { formatCommandHelp } from './commandHelp.js';
9
11
  import { CLIENT_VERSION } from './socket.js';
10
12
  import { UnifiedClient } from './unified.js';
11
13
  import { findPluginEntry, findPluginRegistration, findOfficialPlugin, listPluginNames, listOfficialPluginNames, loadRegisteredPlugin, PluginError, } from './plugins/sdk/plugin.js';
12
- import { getCloudServerConfigPath, loadCloudServerConfig } from './unifiedConfig.js';
14
+ import { resolveServer } from './unifiedConfig.js';
13
15
  const TOP_LEVEL_COMMAND_NAME_WIDTH = 22;
14
16
  const CATEGORIES = [
15
17
  {
@@ -19,7 +21,7 @@ const CATEGORIES = [
19
21
  { title: 'Mock & 规则', names: ['set_mock', 'get_mocks', 'set_rule', 'get_rules'] },
20
22
  {
21
23
  title: '网络请求',
22
- names: ['send_request', 'send_mtop_request', 'request_domain_permission'],
24
+ names: ['send_request', 'request_permission'],
23
25
  },
24
26
  { title: '标签页', names: ['tab_open', 'tab_list', 'tab_group', 'tab_close'] },
25
27
  {
@@ -59,6 +61,12 @@ const CATEGORIES = [
59
61
  },
60
62
  ];
61
63
  const BUILTIN_COMMANDS = [
64
+ { name: 'guide', description: '按需读取与当前 CLI 配套的本地指南', examples: ['guide', 'guide environment'] },
65
+ {
66
+ name: 'run',
67
+ description: '执行预注入 mearl SDK 的 Node.js 脚本',
68
+ examples: ['run --file task.mjs'],
69
+ },
62
70
  {
63
71
  name: 'check',
64
72
  description: '检查指定浏览器,未指定时检查全部已连接浏览器',
@@ -120,6 +128,8 @@ function printUsage(commandName, version) {
120
128
  ` ${commandName} <action> [选项]`,
121
129
  ` ${commandName} <action> --help`,
122
130
  ` ${commandName} check [选项]`,
131
+ ` ${commandName} run [--file <path>] [选项]`,
132
+ ` ${commandName} guide [topic]`,
123
133
  '',
124
134
  '常用选项:',
125
135
  ' --payload <json> 直接传入 JSON 参数',
@@ -135,6 +145,8 @@ function printUsage(commandName, version) {
135
145
  ' -v, --version 查看版本',
136
146
  '',
137
147
  '内置命令:',
148
+ ` ${'guide'.padEnd(TOP_LEVEL_COMMAND_NAME_WIDTH)}读取本地核心流程或单个专题;--list 查看主题`,
149
+ ` ${'run'.padEnd(TOP_LEVEL_COMMAND_NAME_WIDTH)}执行 Node.js 脚本,支持浏览器 API 与 Skill 命令`,
138
150
  ` ${'check'.padEnd(TOP_LEVEL_COMMAND_NAME_WIDTH)}检查指定浏览器,未指定时检查全部已连接浏览器`,
139
151
  ];
140
152
  for (const category of CATEGORIES) {
@@ -300,46 +312,6 @@ function printAdvancedCommandHelp(commandName, action) {
300
312
  '结果包含 count 与 connectors;每项包含 ID、名称、可选版本、连接时间和最后心跳。',
301
313
  ].join('\n'));
302
314
  }
303
- function printCommandHelp(commandName, command) {
304
- const payloadUsage = command.params?.length ? ' [--payload <json> | --payload-file <path>]' : '';
305
- const lines = [
306
- `${command.name}`,
307
- ` ${command.description}`,
308
- '',
309
- '用法:',
310
- ` ${commandName} ${command.name}${payloadUsage} [常用选项]`,
311
- '',
312
- ];
313
- if (command.params?.length) {
314
- lines.push('参数(--payload JSON 字段):');
315
- for (const parameter of command.params) {
316
- const required = parameter.required ? '必填' : '可选';
317
- lines.push(` ${parameter.name.padEnd(22)}${required.padEnd(6)}${parameter.type.padEnd(28)} ${parameter.description}`);
318
- for (const detail of parameter.details ?? [])
319
- lines.push(` ${detail}`);
320
- }
321
- lines.push('');
322
- }
323
- if (command.name === 'browser_list') {
324
- lines.push('目标范围(按需):', ' --local 只列出当前机器上的浏览器', ' --connector <id|名称> 只列出指定远端 connector 的浏览器', '');
325
- }
326
- else if (command.name === 'browser_launch') {
327
- lines.push('宿主选择(本机与远端同时可用时):', ' --local 在当前机器启动', ' --connector <id|名称> 在指定远端 connector 启动', ' --browser <id|名称> 在控制浏览器所在宿主启动,并可作为 Cookie/UA 来源', '');
328
- }
329
- if (command.notes?.length) {
330
- lines.push('约束与行为:', ...command.notes.map(note => ` ${note}`), '');
331
- }
332
- if (command.result?.length) {
333
- lines.push('结果:', ...command.result.map(line => ` ${line}`), '');
334
- }
335
- if (command.examples?.length) {
336
- lines.push('示例:');
337
- for (const example of command.examples)
338
- lines.push(` ${commandName} ${example}`);
339
- }
340
- lines.push('', `运行 ${commandName} --help 查看常用选项。`);
341
- console.error(lines.join('\n'));
342
- }
343
315
  function printUnknownCommand(commandName, args) {
344
316
  const input = commandInput(args) || args[0] || '';
345
317
  const suggestions = findCommandSuggestions(args, SUGGESTABLE_COMMANDS);
@@ -383,17 +355,6 @@ function parsePayload(payloadRaw, payloadFile) {
383
355
  }
384
356
  return {};
385
357
  }
386
- function resolveServer(explicit) {
387
- if (explicit)
388
- return { serverUrl: explicit, serverUrlSource: '--server' };
389
- if (process.env.MEARL_SERVER_URL) {
390
- return { serverUrl: process.env.MEARL_SERVER_URL, serverUrlSource: 'MEARL_SERVER_URL' };
391
- }
392
- const config = loadCloudServerConfig();
393
- return config?.server
394
- ? { serverUrl: config.server, serverUrlSource: getCloudServerConfigPath() }
395
- : {};
396
- }
397
358
  function normalizeScreenshotOutput(action, payload, explicitOutput) {
398
359
  if (action !== 'page_screenshot' || !Object.hasOwn(payload, 'output')) {
399
360
  return { payload, ...(explicitOutput ? { outputPath: explicitOutput } : {}) };
@@ -555,7 +516,7 @@ async function runPluginCli(args, commandName) {
555
516
  console.log(plugin.version ?? 'unknown');
556
517
  return 0;
557
518
  }
558
- if (parsed.help || (!parsed.pluginArgs?.length && !parsed.hasPayload)) {
519
+ if (parsed.help || (!plugin.defaultCommand && !parsed.pluginArgs?.length && !parsed.hasPayload && !Object.keys(parsed.pluginInput ?? {}).length)) {
559
520
  console.error(plugin.help(`${commandName} ${parsed.action}`, parsed.pluginArgs?.[0]));
560
521
  return 0;
561
522
  }
@@ -605,6 +566,14 @@ function writeOutput(action, result, outputPath) {
605
566
  export async function runMearlCli(args, options = {}) {
606
567
  const commandName = options.commandName ?? 'mearl';
607
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
+ }
573
+ if (args[0] === 'run') {
574
+ const { runScriptCli } = await import('./scriptCli.js');
575
+ return runScriptCli(args.slice(1), commandName);
576
+ }
608
577
  if (args[0] === 'capture_checkpoint')
609
578
  args = ['tab_checkpoint', ...args.slice(1)];
610
579
  const provider = installedProvider(args[0]);
@@ -640,8 +609,11 @@ export async function runMearlCli(args, options = {}) {
640
609
  }
641
610
  if (args.includes('--help') || args.includes('-h')) {
642
611
  const command = COMMAND_MAP.get(args[0]);
643
- if (command)
644
- printCommandHelp(commandName, command);
612
+ if (command) {
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
+ }
645
617
  else if (args[0] === 'check')
646
618
  printCheckHelp(commandName);
647
619
  else if (ADVANCED_BUILTIN_COMMANDS.has(args[0])) {
@@ -649,6 +621,7 @@ export async function runMearlCli(args, options = {}) {
649
621
  }
650
622
  else if (args[0]?.startsWith('-')) {
651
623
  printUsage(commandName, clientVersion);
624
+ console.error(formatCapabilityHelp(await readHelpCapabilities(args)));
652
625
  }
653
626
  else {
654
627
  printUnknownCommand(commandName, args);
@@ -0,0 +1,3 @@
1
+ import { type CommandDef } from './commands.js';
2
+ export declare function getCommandHelp(target?: string): string;
3
+ export declare function formatCommandHelp(commandName: string, command: CommandDef): string;
@@ -0,0 +1,79 @@
1
+ import { COMMANDS, COMMAND_MAP } from './commands.js';
2
+ import { SDK_METHOD_ACTIONS, SDK_OPTIONS_ONLY_METHODS } from './sdkMethods.js';
3
+ import { listPluginNames } from './plugins/pluginRegistry.js';
4
+ const HELPER_HELP = {
5
+ invoke: 'invoke(action, payload?, options?) — 调用浏览器 action,返回普通 action 的原始结果',
6
+ invokePlugin: 'invokePlugin(name, { command, input }, options?) — 调用已注册 Skill 命令,返回该命令的原始结果',
7
+ help: 'help(methodOrAction?) / help({ plugin, command? }) — 返回共享命令帮助文本;无参数时列出方法;用 console.log 输出',
8
+ structure: 'structure(value, { maxDepth=5, maxKeys=50, arraySamples=1, maxNodes=2000 }?) — 同步返回有界结构样本;叶子值替换为类型,标注数组样本、循环引用和截断;不能证明未采样内容或业务语义',
9
+ saveArtifact: 'saveArtifact(artifact, absolutePath, options?) — 保存浏览器文件产物,返回保存结果',
10
+ selectBrowser: 'selectBrowser({ browser? }, options?) — 选择默认目标;返回 { selected };browser 使用 browserList 返回的 ID 或名称,空字符串清除默认目标',
11
+ };
12
+ export function getCommandHelp(target) {
13
+ if (!target)
14
+ return [
15
+ ...COMMANDS.map((command) => `${[...SDK_METHOD_ACTIONS].find(([, action]) => action === command.name)?.[0] ?? command.name} (${command.name}) — ${command.summary ?? command.description}`),
16
+ ...Object.values(HELPER_HELP),
17
+ ...listPluginNames().map(plugin => `Skill ${plugin} — help({ plugin: '${plugin}' })`),
18
+ ].join('\n');
19
+ if (Object.hasOwn(HELPER_HELP, target))
20
+ return HELPER_HELP[target];
21
+ const resolved = SDK_METHOD_ACTIONS.get(target) ?? target;
22
+ const action = resolved === 'capture_checkpoint' ? 'tab_checkpoint' : resolved;
23
+ if (action === 'browser_select_browser')
24
+ return HELPER_HELP.selectBrowser;
25
+ const command = COMMAND_MAP.get(action);
26
+ if (!command)
27
+ throw new Error(`Unknown command or SDK method: ${target}`);
28
+ return formatCommandHelp('mearl', command);
29
+ }
30
+ export function formatCommandHelp(commandName, command) {
31
+ const payloadUsage = command.params?.length ? ' [--payload <json> | --payload-file <path>]' : '';
32
+ const lines = [
33
+ `${command.name}`,
34
+ ` ${command.description}`,
35
+ '',
36
+ '用法:',
37
+ ` ${commandName} ${command.name}${payloadUsage} [常用选项]`,
38
+ '',
39
+ ];
40
+ const method = [...SDK_METHOD_ACTIONS].find(([, action]) => action === command.name)?.[0];
41
+ if (method)
42
+ lines.push('SDK:', ...(method === 'browserRelease' || method === 'browserList'
43
+ ? [
44
+ ` await mearl.${method}(options?)`,
45
+ ` await mearl.${method}(params, options)`,
46
+ ]
47
+ : [
48
+ ` await mearl.${method}(${SDK_OPTIONS_ONLY_METHODS.has(method) ? 'options?' : 'params, options?'})`,
49
+ ]), '');
50
+ if (command.params?.length) {
51
+ lines.push('参数(--payload JSON 字段):');
52
+ for (const parameter of command.params) {
53
+ const required = parameter.required ? '必填' : '可选';
54
+ lines.push(` ${parameter.name.padEnd(22)}${required.padEnd(6)}${parameter.type.padEnd(28)} ${parameter.description}`);
55
+ for (const detail of parameter.details ?? [])
56
+ lines.push(` ${detail}`);
57
+ }
58
+ lines.push('');
59
+ }
60
+ if (command.name === 'browser_list') {
61
+ lines.push('目标范围(按需):', ' --local 只列出当前机器上的浏览器', ' --connector <id|名称> 只列出指定远端 connector 的浏览器', '');
62
+ }
63
+ else if (command.name === 'browser_launch') {
64
+ lines.push('宿主选择(本机与远端同时可用时):', ' --local 在当前机器启动', ' --connector <id|名称> 在指定远端 connector 启动', ' --browser <id|名称> 在控制浏览器所在宿主启动,并可作为 Cookie/UA 来源', '');
65
+ }
66
+ if (command.notes?.length) {
67
+ lines.push('约束与行为:', ...command.notes.map(note => ` ${note}`), '');
68
+ }
69
+ if (command.result?.length) {
70
+ lines.push('结果:', ...command.result.map(line => ` ${line}`), '');
71
+ }
72
+ if (command.examples?.length) {
73
+ lines.push('示例:');
74
+ for (const example of command.examples)
75
+ lines.push(` ${commandName} ${example}`);
76
+ }
77
+ lines.push('', `运行 ${commandName} --help 查看常用选项。`);
78
+ return lines.join('\n');
79
+ }
@@ -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
  }