@mearl/client 2.16.0 → 2.16.1

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
@@ -153,8 +153,11 @@ setup 会补齐缺失或低于自身版本的 client、native-host;安装参
153
153
  退出码为 0 表示插件已注册且入口可加载,stdout 返回同目录 `SKILL.md` 的版本,缺少版本元数据时返回 `unknown`。
154
154
  未注册、入口丢失或加载失败会以退出码 2 和对应的 `PLUGIN_*` 错误结束。普通业务调用无需预先检查,直接处理同样的错误即可。
155
155
 
156
- 插件接受位置参数或包含 command/input 的 `--payload`,两种形式不能混用。JSON 保留业务字段类型;正文等文件输入
157
- 按具体 skill 的字段约定处理。插件不使用上表中内置 action 的 `--payload-file` 入口。
156
+ 插件接受位置参数、字符串业务选项或包含 command/input 的 `--payload`。业务选项推荐写成
157
+ `--name=value`,也接受 `--name value`,字段名与 handler 的 input 一致且不依赖位置顺序;同一字段不能同时
158
+ 使用位置参数和业务选项,`--payload` 不能与前两种形式混用。JSON 保留业务字段类型;正文等文件输入按具体
159
+ skill 的字段约定处理。`browser`、`timeout`、`output` 等宿主选项保留原有含义;同名业务字段以及数字、
160
+ 布尔值、对象和数组通过 `--payload` 传入。插件不使用上表中内置 action 的 `--payload-file` 入口。
158
161
 
159
162
  注册后的插件也可从 SDK 调用;实例方法 `client.invokePlugin` 复用既有连接和默认浏览器:
160
163
 
@@ -180,8 +183,9 @@ MCP Server 通过固定工具 `mearl_plugin` 接受同样的 `plugin`、`command
180
183
 
181
184
  需要 BUC 身份的内部 HTTP 接口通过官方 `buc` 插件调用。CLI 使用 `mearl buc request`,业务插件使用
182
185
  `app.invoke('buc', { command: 'request', input })`;当前身份和人员资料分别使用 `whoami` 与 `profile`
183
- 子命令。BUC 插件按请求创建一次性凭据,只允许访问 HTTPS `alibaba-inc.com` 域名,过滤认证字段与
184
- Cookie,并始终在运行 CLI、SDK 或 MCP Server 的机器上执行,不随浏览器或 connector 转发。
186
+ 子命令。BUC 插件按请求创建一次性凭据,只接受无 URL 凭据的 HTTPS 地址,过滤认证字段与 Cookie;
187
+ 调用方须按已验证的业务契约确认目标服务可信。请求始终在运行 CLI、SDK 或 MCP Server 的机器上执行,
188
+ 不随浏览器或 connector 转发。
185
189
  `buc request` 支持与 `send_request` 同结构的 `formData` 和 `files`;`files[].filePath` 从该插件执行
186
190
  宿主读取,而不是浏览器 native-host,无法引用宿主路径时使用 Base64 内容与 `filename`。
187
191
 
package/dist/cli.d.ts CHANGED
@@ -17,6 +17,7 @@ export interface ParsedArgs {
17
17
  timeoutSec: number;
18
18
  timeoutOption?: string;
19
19
  pluginArgs?: string[];
20
+ pluginInput?: Record<string, unknown>;
20
21
  help?: boolean;
21
22
  version?: boolean;
22
23
  verbose?: boolean;
package/dist/cli.js CHANGED
@@ -70,6 +70,39 @@ const SUGGESTABLE_COMMANDS = [
70
70
  ...OFFICIAL_PLUGIN_COMMANDS,
71
71
  ];
72
72
  const ADVANCED_BUILTIN_COMMANDS = new Set(['connector_list']);
73
+ const PLUGIN_HOST_OPTIONS = new Set([
74
+ '--browser',
75
+ '--cdp',
76
+ '--compact',
77
+ '--connector',
78
+ '--help',
79
+ '--json',
80
+ '--local',
81
+ '--output',
82
+ '--payload',
83
+ '--payload-file',
84
+ '--server',
85
+ '--timeout',
86
+ '--verbose',
87
+ '--version',
88
+ ]);
89
+ function findPluginInputOptionNames(args) {
90
+ const names = new Set();
91
+ for (const argument of args) {
92
+ if (!argument.startsWith('--') || argument === '--')
93
+ continue;
94
+ const equals = argument.indexOf('=');
95
+ const option = equals === -1 ? argument : argument.slice(0, equals);
96
+ if (PLUGIN_HOST_OPTIONS.has(option))
97
+ continue;
98
+ const name = option.slice(2);
99
+ if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(name)) {
100
+ throw new Error(`无效的插件业务选项: ${option}`);
101
+ }
102
+ names.add(name);
103
+ }
104
+ return [...names];
105
+ }
73
106
  function printUsage(commandName, version) {
74
107
  const lines = [
75
108
  `${commandName} v${version}`,
@@ -355,6 +388,7 @@ function actionInvocationFailed(action, result) {
355
388
  : false;
356
389
  }
357
390
  export function parseClientArgs(args, plugin = false) {
391
+ const pluginInputOptionNames = plugin ? findPluginInputOptionNames(args) : [];
358
392
  const program = new Command()
359
393
  .name('mearl')
360
394
  .helpOption(false)
@@ -379,6 +413,8 @@ export function parseClientArgs(args, plugin = false) {
379
413
  .option('-h, --help')
380
414
  .option('-v, --version')
381
415
  .option('--verbose');
416
+ for (const name of pluginInputOptionNames)
417
+ program.option(`--${name} <value>`);
382
418
  try {
383
419
  program.parse(['node', 'mearl', ...args]);
384
420
  }
@@ -393,6 +429,12 @@ export function parseClientArgs(args, plugin = false) {
393
429
  if (plugin && options.payloadFile !== undefined) {
394
430
  throw new Error('插件使用 --payload 传入 command 和 input;正文文件可由业务字段引用');
395
431
  }
432
+ const pluginInput = Object.fromEntries(pluginInputOptionNames
433
+ .filter(name => Object.hasOwn(options, name))
434
+ .map(name => [name, options[name]]));
435
+ if (plugin && options.payload !== undefined && Object.keys(pluginInput).length > 0) {
436
+ throw new Error('插件业务选项不能与 --payload 同时使用');
437
+ }
396
438
  const json = options.json === true;
397
439
  if (json && action !== 'check')
398
440
  throw new Error('--json 仅适用于 check');
@@ -420,6 +462,7 @@ export function parseClientArgs(args, plugin = false) {
420
462
  ...(plugin
421
463
  ? {
422
464
  pluginArgs: program.processedArgs[1],
465
+ ...(Object.keys(pluginInput).length > 0 ? { pluginInput } : {}),
423
466
  help: options.help === true,
424
467
  version: options.version === true,
425
468
  verbose: options.verbose === true,
@@ -471,7 +514,7 @@ async function runPluginCli(args, commandName) {
471
514
  console.error(plugin.help(`${commandName} ${parsed.action}`, parsed.pluginArgs?.[0]));
472
515
  return 0;
473
516
  }
474
- const result = await plugin.run(parsed.pluginArgs ?? [], parsed.hasPayload ? parsed.payload : undefined);
517
+ const result = await plugin.run(parsed.pluginArgs ?? [], parsed.hasPayload ? parsed.payload : undefined, parsed.pluginInput);
475
518
  if (result !== undefined) {
476
519
  if (parsed.outputPath)
477
520
  writeOutput('', result, parsed.outputPath);
package/dist/plugin.d.ts CHANGED
@@ -27,13 +27,13 @@ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'invo
27
27
  description: string | undefined;
28
28
  version: string | undefined;
29
29
  help(namespace: string, name?: string): string;
30
- run(positionals: string[], payload?: unknown): Promise<unknown>;
30
+ run(positionals: string[], payload?: unknown, namedInput?: Record<string, unknown>): Promise<unknown>;
31
31
  }>;
32
32
  export declare function loadRegisteredPlugin(name: string, context: PluginExecutionContext, options?: PluginInvokeOptions): Promise<{
33
33
  name: string | undefined;
34
34
  description: string | undefined;
35
35
  version: string | undefined;
36
36
  help(namespace: string, name?: string): string;
37
- run(positionals: string[], payload?: unknown): Promise<unknown>;
37
+ run(positionals: string[], payload?: unknown, namedInput?: Record<string, unknown>): Promise<unknown>;
38
38
  }>;
39
39
  export declare function invokeRegisteredPlugin(name: string, invocation: PluginInvocation, context: PluginExecutionContext, options?: PluginInvokeOptions): Promise<unknown>;
package/dist/plugin.js CHANGED
@@ -145,21 +145,24 @@ export async function loadPlugin(entry, context, pluginName) {
145
145
  '',
146
146
  '常用选项: --browser --timeout --compact --output',
147
147
  '连接或传输排障运行 mearl check --help。',
148
- '插件选项: --verbose; -h, --help; -v, --version',
149
- 'JSON input 直接传给 handler;业务字段与校验规则见对应 skill。',
148
+ '插件选项: --name=value 传入字符串业务字段;--verbose; -h, --help; -v, --version',
149
+ '同名宿主选项及非字符串字段使用 --payload;业务字段与校验规则见对应 skill。',
150
150
  ].join('\n');
151
151
  },
152
- async run(positionals, payload) {
152
+ async run(positionals, payload, namedInput = {}) {
153
153
  if (payload !== undefined && positionals.length > 0) {
154
154
  throw new PluginError('PLUGIN_INVALID_INPUT', '位置参数调用与 --payload 调用不能混用');
155
155
  }
156
+ if (payload !== undefined && Object.keys(namedInput).length > 0) {
157
+ throw new PluginError('PLUGIN_INVALID_INPUT', '插件业务选项与 --payload 调用不能混用');
158
+ }
156
159
  if (payload !== undefined &&
157
160
  (!isObject(payload) || typeof payload.command !== 'string' || !isObject(payload.input))) {
158
161
  throw new PluginError('PLUGIN_INVALID_INPUT', '--payload 必须为 {"command":"...","input":{...}}');
159
162
  }
160
163
  const envelope = payload;
161
164
  const { command, handler } = find(envelope?.command ?? positionals[0] ?? '');
162
- const input = envelope
165
+ const positionalInput = envelope
163
166
  ? { ...envelope.input }
164
167
  : Object.fromEntries(command.registeredArguments
165
168
  .map((argument, index) => [
@@ -167,6 +170,12 @@ export async function loadPlugin(entry, context, pluginName) {
167
170
  argument.variadic ? positionals.slice(index + 1) : positionals[index + 1],
168
171
  ])
169
172
  .filter(([, value]) => value !== undefined));
173
+ for (const name of Object.keys(namedInput)) {
174
+ if (Object.hasOwn(positionalInput, name)) {
175
+ throw new PluginError('PLUGIN_INVALID_INPUT', `参数不能重复传入: ${name}`);
176
+ }
177
+ }
178
+ const input = { ...positionalInput, ...namedInput };
170
179
  if (!envelope &&
171
180
  !command.registeredArguments.some(argument => argument.variadic) &&
172
181
  positionals.length - 1 > command.registeredArguments.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/client",
3
- "version": "2.16.0",
3
+ "version": "2.16.1",
4
4
  "description": "Unified Mearl SDK & CLI for local and remote browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -59,8 +59,8 @@
59
59
  "commander": "^15.0.0",
60
60
  "fastest-levenshtein": "^1.0.16",
61
61
  "ws": "^8.18.0",
62
- "@mearl/cloud-types": "2.16.0",
63
- "@mearl/provider": "2.16.0"
62
+ "@mearl/cloud-types": "2.16.1",
63
+ "@mearl/provider": "2.16.1"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/node": "^24.9.1",