@mearl/client 2.14.0 → 2.15.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/dist/plugin.d.ts CHANGED
@@ -1,18 +1,42 @@
1
+ import { type BucApi } from './buc.js';
1
2
  import type { UnifiedInvokeOptions } from './unified.js';
2
- export { assertPluginName, findPluginEntry, findPluginRegistration, listPluginNames, registerPlugin, resolvePluginEntry, unregisterPlugin, PluginError, type PluginRegistration, } from './pluginRegistry.js';
3
+ export { type BucApi, type BucIdentity, type BucProfile, type BucRequestAuth, type BucRequestFormValue, type BucRequestParams, type BucRequestResult, } from './buc.js';
4
+ export { assertPluginName, findPluginEntry, findPluginRegistration, listPluginNames, registerPlugin, resolvePluginEntry, unregisterPlugin, PluginError, type PluginRegistration, type PluginRegistrationOptions, } from './pluginRegistry.js';
3
5
  export type PluginOptions = UnifiedInvokeOptions;
6
+ export interface PluginInvocation {
7
+ command: string;
8
+ input: Record<string, any>;
9
+ }
10
+ export interface PluginInvokeOptions extends UnifiedInvokeOptions {
11
+ onProgress?: (message: string) => void;
12
+ }
13
+ export interface PluginExecutionContext {
14
+ buc?: BucApi;
15
+ invoke(action: string, payload?: Record<string, any>, options?: UnifiedInvokeOptions): Promise<any>;
16
+ }
4
17
  export interface PluginApp {
5
18
  readonly options: Readonly<PluginOptions>;
19
+ readonly buc: BucApi;
6
20
  command(signature: string, handler: PluginHandler, description?: string): void;
7
21
  invoke(action: string, payload?: Record<string, any>, options?: UnifiedInvokeOptions): Promise<any>;
8
22
  progress(message: string): void;
9
23
  }
10
24
  export type PluginHandler = (input: Record<string, any>) => unknown | Promise<unknown>;
11
25
  /** 注册和执行共享一个 app;仅 handler 执行期间开放浏览器调用。 */
12
- export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'invoke' | 'options' | 'progress'>, pluginName?: string): Promise<{
26
+ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'invoke' | 'options' | 'progress'> & {
27
+ buc?: BucApi;
28
+ }, pluginName?: string): Promise<{
29
+ name: string | undefined;
30
+ description: string | undefined;
31
+ version: string | undefined;
32
+ help(namespace: string, name?: string): string;
33
+ run(positionals: string[], payload?: unknown): Promise<unknown>;
34
+ }>;
35
+ export declare function loadRegisteredPlugin(name: string, context: PluginExecutionContext, options?: PluginInvokeOptions): Promise<{
13
36
  name: string | undefined;
14
37
  description: string | undefined;
15
38
  version: string | undefined;
16
39
  help(namespace: string, name?: string): string;
17
40
  run(positionals: string[], payload?: unknown): Promise<unknown>;
18
41
  }>;
42
+ export declare function invokeRegisteredPlugin(name: string, invocation: PluginInvocation, context: PluginExecutionContext, options?: PluginInvokeOptions): Promise<unknown>;
package/dist/plugin.js CHANGED
@@ -2,7 +2,8 @@ import { readFileSync, statSync } from 'node:fs';
2
2
  import { dirname, resolve } from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  import { Command } from 'commander';
5
- import { PluginError } from './pluginRegistry.js';
5
+ import { buc } from './buc.js';
6
+ import { findPluginRegistration, PluginError } from './pluginRegistry.js';
6
7
  export { assertPluginName, findPluginEntry, findPluginRegistration, listPluginNames, registerPlugin, resolvePluginEntry, unregisterPlugin, PluginError, } from './pluginRegistry.js';
7
8
  function readPluginVersion(entry) {
8
9
  try {
@@ -31,9 +32,12 @@ function missingPluginEntryMessage(entry, pluginName) {
31
32
  }
32
33
  /** 注册和执行共享一个 app;仅 handler 执行期间开放浏览器调用。 */
33
34
  export async function loadPlugin(entry, context, pluginName) {
35
+ let entryVersion;
34
36
  try {
35
- if (!statSync(entry).isFile())
37
+ const stat = statSync(entry);
38
+ if (!stat.isFile())
36
39
  throw new Error('not a file');
40
+ entryVersion = `${stat.mtimeMs}-${stat.size}`;
37
41
  }
38
42
  catch {
39
43
  throw new PluginError('PLUGIN_ENTRY_MISSING', missingPluginEntryMessage(entry, pluginName));
@@ -45,6 +49,23 @@ export async function loadPlugin(entry, context, pluginName) {
45
49
  let description;
46
50
  const app = Object.freeze({
47
51
  options: Object.freeze({ ...context.options }),
52
+ buc: Object.freeze({
53
+ request(params) {
54
+ if (!executing)
55
+ throw new Error('app.buc.request 只能在命令执行期间调用');
56
+ return (context.buc ?? buc).request(params);
57
+ },
58
+ whoami() {
59
+ if (!executing)
60
+ throw new Error('app.buc.whoami 只能在命令执行期间调用');
61
+ return (context.buc ?? buc).whoami();
62
+ },
63
+ profile() {
64
+ if (!executing)
65
+ throw new Error('app.buc.profile 只能在命令执行期间调用');
66
+ return (context.buc ?? buc).profile();
67
+ },
68
+ }),
48
69
  command(signature, handler, description = '') {
49
70
  if (!registering)
50
71
  throw new Error('命令只能在入口注册阶段声明');
@@ -78,7 +99,9 @@ export async function loadPlugin(entry, context, pluginName) {
78
99
  },
79
100
  });
80
101
  try {
81
- const module = await import(pathToFileURL(entry).href);
102
+ const moduleUrl = pathToFileURL(entry);
103
+ moduleUrl.searchParams.set('mearl-version', entryVersion);
104
+ const module = await import(moduleUrl.href);
82
105
  if (module.name !== undefined && typeof module.name !== 'string')
83
106
  throw new Error('入口的 name 导出必须为字符串');
84
107
  name = module.name;
@@ -171,3 +194,29 @@ export async function loadPlugin(entry, context, pluginName) {
171
194
  },
172
195
  };
173
196
  }
197
+ export async function loadRegisteredPlugin(name, context, options = {}) {
198
+ const registration = findPluginRegistration(name);
199
+ if (!registration) {
200
+ throw new PluginError('PLUGIN_NOT_REGISTERED', `插件未注册: ${name}`);
201
+ }
202
+ const { onProgress, ...invokeOptions } = options;
203
+ return loadPlugin(registration.entry, {
204
+ options: invokeOptions,
205
+ ...(context.buc ? { buc: context.buc } : {}),
206
+ progress: onProgress ?? (() => undefined),
207
+ async invoke(action, payload, overrides) {
208
+ const result = await context.invoke(action, payload, {
209
+ ...invokeOptions,
210
+ ...overrides,
211
+ });
212
+ if (result?.action?.success === false) {
213
+ throw new Error(result.action.error || `${action} failed`);
214
+ }
215
+ return result;
216
+ },
217
+ }, name);
218
+ }
219
+ export async function invokeRegisteredPlugin(name, invocation, context, options = {}) {
220
+ const plugin = await loadRegisteredPlugin(name, context, options);
221
+ return plugin.run([], invocation);
222
+ }
@@ -5,11 +5,15 @@ export declare class PluginError extends Error {
5
5
  export interface PluginRegistration {
6
6
  entry: string;
7
7
  description?: string;
8
+ managedBy?: string;
9
+ }
10
+ export interface PluginRegistrationOptions {
11
+ managedBy?: string;
8
12
  }
9
13
  export declare function pluginDirectory(): string;
10
14
  export declare function assertPluginName(name: string): void;
11
15
  export declare function resolvePluginEntry(target: string): string;
12
- export declare function registerPlugin(name: string, target: string, description?: string): string;
16
+ export declare function registerPlugin(name: string, target: string, description?: string, options?: PluginRegistrationOptions): string;
13
17
  export declare function unregisterPlugin(name: string): boolean;
14
18
  export declare function findPluginRegistration(name: string): PluginRegistration | undefined;
15
19
  export declare function findPluginEntry(name: string): string | undefined;
@@ -40,7 +40,7 @@ export function resolvePluginEntry(target) {
40
40
  throw new PluginError('PLUGIN_ENTRY_MISSING', `入口不存在: ${absolute}(目录默认使用 scripts/main.mjs)`);
41
41
  }
42
42
  }
43
- export function registerPlugin(name, target, description) {
43
+ export function registerPlugin(name, target, description, options = {}) {
44
44
  assertPluginName(name);
45
45
  const entry = resolvePluginEntry(target);
46
46
  const directory = pluginDirectory();
@@ -49,7 +49,12 @@ export function registerPlugin(name, target, description) {
49
49
  // 每个插件独立保存,多个名称并发注册不会覆盖彼此;同名注册原子替换绑定。
50
50
  const temporary = `${file}.${randomUUID()}.tmp`;
51
51
  const summary = description?.trim();
52
- const record = { entry, ...(summary ? { description: summary } : {}) };
52
+ const managedBy = options.managedBy?.trim();
53
+ const record = {
54
+ entry,
55
+ ...(summary ? { description: summary } : {}),
56
+ ...(managedBy ? { managedBy } : {}),
57
+ };
53
58
  try {
54
59
  writeFileSync(temporary, `${JSON.stringify(record)}\n`, { mode: 0o600, flag: 'wx' });
55
60
  renameSync(temporary, file);
@@ -92,7 +97,14 @@ export function findPluginRegistration(name) {
92
97
  const description = typeof record.description === 'string' && record.description.trim()
93
98
  ? record.description.trim()
94
99
  : undefined;
95
- return { entry: record.entry, ...(description ? { description } : {}) };
100
+ const managedBy = typeof record.managedBy === 'string' && record.managedBy.trim()
101
+ ? record.managedBy.trim()
102
+ : undefined;
103
+ return {
104
+ entry: record.entry,
105
+ ...(description ? { description } : {}),
106
+ ...(managedBy ? { managedBy } : {}),
107
+ };
96
108
  }
97
109
  catch {
98
110
  throw new PluginError('PLUGIN_REGISTRY_INVALID', `无效的插件记录: ${file}`);
package/dist/unified.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type CloudConnectorListResult } from '@mearl/cloud-types';
2
+ import type { PluginInvocation, PluginInvokeOptions } from './plugin.js';
2
3
  export interface UnifiedInvokeOptions {
3
4
  timeoutSec?: number;
4
5
  /** Global browser id, source-local browser id, or unique browser name. */
@@ -36,6 +37,7 @@ export declare class UnifiedClient {
36
37
  private closing;
37
38
  constructor(options?: UnifiedClientOptions);
38
39
  disconnect(): void;
40
+ invokePlugin(name: string, invocation: PluginInvocation, options?: PluginInvokeOptions): Promise<unknown>;
39
41
  invoke(action: string, data?: Record<string, any>, options?: UnifiedInvokeOptions): Promise<any>;
40
42
  private invokeExplicitLocal;
41
43
  private invokeLaunchWithoutControlBrowser;
@@ -61,5 +63,6 @@ export declare class UnifiedClient {
61
63
  private rejectAllPending;
62
64
  }
63
65
  export declare function invoke(action: string, data?: Record<string, any>, options?: UnifiedInvokeOptions): Promise<any>;
66
+ export declare function invokePlugin(name: string, invocation: PluginInvocation, options?: PluginInvokeOptions): Promise<unknown>;
64
67
  export declare function listCloudConnectors(): Promise<CloudConnectorListResult>;
65
68
  export {};
package/dist/unified.js CHANGED
@@ -13,8 +13,10 @@ function encodeBrowserTarget(target) {
13
13
  }
14
14
  function omitTransientBrowserSecrets(browser) {
15
15
  const safeBrowser = { ...browser };
16
- delete safeBrowser.agentbayAccessUrl;
17
- delete safeBrowser.accessUrl;
16
+ for (const key of Object.keys(safeBrowser)) {
17
+ if (/accessUrl$/i.test(key))
18
+ delete safeBrowser[key];
19
+ }
18
20
  return safeBrowser;
19
21
  }
20
22
  export function decodeBrowserTarget(selector) {
@@ -117,6 +119,12 @@ export class UnifiedClient {
117
119
  this.ws?.close();
118
120
  this.ws = null;
119
121
  }
122
+ async invokePlugin(name, invocation, options = {}) {
123
+ const { invokeRegisteredPlugin } = await import('./plugin.js');
124
+ return invokeRegisteredPlugin(name, invocation, {
125
+ invoke: (action, payload, invokeOptions) => this.invoke(action, payload, invokeOptions),
126
+ }, options);
127
+ }
120
128
  async invoke(action, data = {}, options = {}) {
121
129
  const timeoutSec = options.timeoutSec ?? resolveActionTimeoutSec(action, data, this.options.requestTimeoutSec);
122
130
  if (action === 'connector_list')
@@ -569,6 +577,20 @@ export async function invoke(action, data = {}, options = {}) {
569
577
  client.disconnect();
570
578
  }
571
579
  }
580
+ export async function invokePlugin(name, invocation, options = {}) {
581
+ const serverUrl = process.env.MEARL_SERVER_URL ?? loadCloudServerConfig()?.server;
582
+ const client = new UnifiedClient({
583
+ ...(serverUrl ? { serverUrl } : {}),
584
+ defaultBrowser,
585
+ defaultConnector,
586
+ });
587
+ try {
588
+ return await client.invokePlugin(name, invocation, options);
589
+ }
590
+ finally {
591
+ client.disconnect();
592
+ }
593
+ }
572
594
  export function listCloudConnectors() {
573
595
  return invoke('connector_list', {});
574
596
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/client",
3
- "version": "2.14.0",
3
+ "version": "2.15.0",
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.14.0",
63
- "@mearl/provider": "2.14.0"
62
+ "@mearl/cloud-types": "2.15.0",
63
+ "@mearl/provider": "2.15.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/node": "^24.9.1",