@mearl/client 2.22.0 → 2.22.2

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 (51) hide show
  1. package/README.md +92 -7
  2. package/dist/cli.js +13 -41
  3. package/dist/commandHelp.d.ts +3 -0
  4. package/dist/commandHelp.js +79 -0
  5. package/dist/commands.d.ts +10 -10
  6. package/dist/commands.js +4 -3
  7. package/dist/generated/browser-action-protocol.d.ts +1 -1
  8. package/dist/generated/data-structure.d.ts +8 -0
  9. package/dist/generated/data-structure.js +58 -0
  10. package/dist/generated/plugin-host-build.d.ts +1 -0
  11. package/dist/generated/plugin-host-build.js +2 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.js +1 -0
  14. package/dist/plugin.d.ts +1 -0
  15. package/dist/plugin.js +1 -0
  16. package/dist/plugins/clients/pluginHostConnection.d.ts +2 -0
  17. package/dist/plugins/clients/pluginHostConnection.js +3 -1
  18. package/dist/plugins/pluginRegistry.d.ts +1 -1
  19. package/dist/plugins/pluginRegistry.js +12 -4
  20. package/dist/plugins/runtime/pluginCommandWorker.js +8 -6
  21. package/dist/plugins/runtime/pluginExecutor.d.ts +5 -0
  22. package/dist/plugins/runtime/pluginExecutor.js +15 -2
  23. package/dist/plugins/runtime/pluginExecutorProtocol.d.ts +4 -0
  24. package/dist/plugins/runtime/pluginExecutorSupervisor.js +2 -1
  25. package/dist/plugins/runtime/pluginHostServer.js +8 -0
  26. package/dist/plugins/runtime/pluginPageManager.js +0 -4
  27. package/dist/plugins/runtime/pluginRevision.js +2 -52
  28. package/dist/plugins/runtime/pluginStateStore.js +3 -8
  29. package/dist/plugins/runtime/pluginTaskManager.js +3 -7
  30. package/dist/plugins/sdk/plugin.js +5 -1
  31. package/dist/plugins/sdk/pluginModelDefinition.js +0 -6
  32. package/dist/plugins/sdk/pluginPageDefinition.d.ts +0 -1
  33. package/dist/plugins/sdk/pluginPageDefinition.js +2 -18
  34. package/dist/plugins/sdk/pluginRecoveryDefinition.js +0 -3
  35. package/dist/plugins/sdk/pluginStateDefinition.d.ts +0 -1
  36. package/dist/plugins/sdk/pluginStateDefinition.js +0 -11
  37. package/dist/plugins/skillManifest.d.ts +5 -0
  38. package/dist/plugins/skillManifest.js +35 -0
  39. package/dist/runtimePaths.d.ts +1 -0
  40. package/dist/runtimePaths.js +1 -0
  41. package/dist/script.d.ts +30 -0
  42. package/dist/script.js +260 -0
  43. package/dist/scriptCli.d.ts +1 -0
  44. package/dist/scriptCli.js +94 -0
  45. package/dist/scriptErrors.d.ts +8 -0
  46. package/dist/scriptErrors.js +21 -0
  47. package/dist/scriptWorker.d.ts +1 -0
  48. package/dist/scriptWorker.js +50 -0
  49. package/dist/sdkMethods.d.ts +3 -0
  50. package/dist/sdkMethods.js +9 -0
  51. package/package.json +11 -8
@@ -0,0 +1,30 @@
1
+ import { type UnifiedInvokeOptions } from './unified.js';
2
+ export interface ScriptExecutionOptions extends UnifiedInvokeOptions {
3
+ serverUrl?: string;
4
+ /** Overall deadline, independent of individual action timeouts. Default: 60 seconds. */
5
+ executionTimeoutSec?: number;
6
+ /** Combined stdout/stderr limit in bytes. Default: 256 KiB; maximum: 1 MiB. */
7
+ maxOutputBytes?: number;
8
+ signal?: AbortSignal;
9
+ source?: 'cli' | 'mcp' | 'sdk';
10
+ }
11
+ export interface ScriptExecutionResult {
12
+ success: boolean;
13
+ stdout: string;
14
+ stderr: string;
15
+ session?: Record<string, unknown>;
16
+ error?: {
17
+ code: string;
18
+ message: string;
19
+ stack?: string;
20
+ details?: unknown;
21
+ stage?: string;
22
+ attempt?: unknown;
23
+ };
24
+ executionStopped: boolean;
25
+ /** Dispatched browser/plugin calls can finish after the script has stopped. */
26
+ mayHaveLateEffects: boolean;
27
+ pendingActions: string[];
28
+ }
29
+ /** Runs trusted agent code in a short-lived Node process, with one shared SDK client. */
30
+ export declare function executeScript(code: string, options?: ScriptExecutionOptions): Promise<ScriptExecutionResult>;
package/dist/script.js ADDED
@@ -0,0 +1,260 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { isBrowserCommandAction } from './generated/browser-action-protocol.js';
3
+ import { exportArtifact } from './artifacts.js';
4
+ import { UnifiedClient } from './unified.js';
5
+ import { loadCloudServerConfig } from './unifiedConfig.js';
6
+ import { stopPluginProcessTree } from './plugins/runtime/pluginProcessTree.js';
7
+ import { SCRIPT_WORKER_PATH } from './runtimePaths.js';
8
+ import { SDK_METHOD_ACTIONS as METHOD_ACTIONS, SDK_OPTIONS_ONLY_METHODS as OPTIONS_ONLY, } from './sdkMethods.js';
9
+ import { getCommandHelp } from './commandHelp.js';
10
+ import { executeRegisteredPlugin } from './plugins/runtime/pluginExecutor.js';
11
+ import { findPluginRegistration } from './plugins/pluginRegistry.js';
12
+ import { findOfficialPlugin } from './plugins/officialPluginCatalog.js';
13
+ import { ensureOfficialPluginRegistered } from './plugins/officialPluginInstaller.js';
14
+ import { serializeScriptError as serializeError } from './scriptErrors.js';
15
+ const METHODS = [...METHOD_ACTIONS.keys(), 'invoke', 'invokePlugin', 'saveArtifact', 'help'];
16
+ const MAX_SCRIPT_BYTES = 1024 * 1024;
17
+ /** Runs trusted agent code in a short-lived Node process, with one shared SDK client. */
18
+ export async function executeScript(code, options = {}) {
19
+ if (typeof code !== 'string' || !code.trim() || Buffer.byteLength(code) > MAX_SCRIPT_BYTES)
20
+ throw new Error('Script must be non-empty JavaScript of at most 1 MiB');
21
+ const { serverUrl, executionTimeoutSec = 60, maxOutputBytes = 256 * 1024, signal, source = 'sdk', ...defaults } = options;
22
+ if (!Number.isFinite(executionTimeoutSec) ||
23
+ executionTimeoutSec <= 0 ||
24
+ executionTimeoutSec * 1000 > 2_147_483_647)
25
+ throw new Error('executionTimeoutSec must be positive and within the timer range');
26
+ if (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 1 || maxOutputBytes > 1024 * 1024)
27
+ throw new Error('maxOutputBytes must be an integer between 1 and 1048576');
28
+ const client = new UnifiedClient({
29
+ serverUrl: serverUrl ?? process.env.MEARL_SERVER_URL ?? loadCloudServerConfig()?.server,
30
+ });
31
+ let session = defaults.session && defaults.session !== 'new' ? { sessionId: defaults.session } : undefined;
32
+ let sessionId = defaults.session;
33
+ let uncertainEffects = false;
34
+ const pending = new Map();
35
+ const controller = new AbortController();
36
+ const invoke = async (action, payload, overrides = {}) => {
37
+ controller.signal.throwIfAborted();
38
+ const result = await client.invoke(action, payload, {
39
+ ...defaults,
40
+ session: sessionId,
41
+ ...overrides,
42
+ });
43
+ if (result?.action?.stage === 'dispatch-uncertain' || result?.stage === 'dispatch-uncertain')
44
+ uncertainEffects = true;
45
+ if (result?.session?.sessionId &&
46
+ (!overrides.session || overrides.session === 'new' || overrides.session === sessionId)) {
47
+ session = result.session;
48
+ if (!overrides.session || overrides.session === 'new')
49
+ sessionId = result.session.sessionId;
50
+ }
51
+ return result;
52
+ };
53
+ try {
54
+ return await new Promise((resolve, reject) => {
55
+ if (signal?.aborted) {
56
+ resolve({
57
+ success: false,
58
+ stdout: '',
59
+ stderr: '',
60
+ ...(session ? { session } : {}),
61
+ error: { code: 'SCRIPT_CANCELLED', message: 'Script execution cancelled' },
62
+ executionStopped: true,
63
+ mayHaveLateEffects: false,
64
+ pendingActions: [],
65
+ });
66
+ return;
67
+ }
68
+ const child = spawn(process.execPath, [SCRIPT_WORKER_PATH], {
69
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
70
+ serialization: 'advanced',
71
+ detached: process.platform !== 'win32',
72
+ });
73
+ const output = { stdout: [], stderr: [] };
74
+ let outputBytes = 0;
75
+ let outcome;
76
+ let cleanup;
77
+ let cleanupError;
78
+ let pendingActions = [];
79
+ let closed = false;
80
+ const pluginCalls = new Set();
81
+ const stop = (error) => {
82
+ if (outcome !== undefined || closed)
83
+ return;
84
+ outcome = error ?? true;
85
+ pendingActions = [...pending.values()];
86
+ controller.abort();
87
+ client.disconnect();
88
+ if (child.pid) {
89
+ cleanup = stopPluginProcessTree(child.pid).catch(error => {
90
+ cleanupError = error;
91
+ child.kill('SIGKILL');
92
+ });
93
+ }
94
+ };
95
+ const abort = () => stop({ code: 'SCRIPT_CANCELLED', message: 'Script execution cancelled' });
96
+ const deadline = setTimeout(() => stop({ code: 'SCRIPT_TIMEOUT', message: 'Script execution deadline exceeded' }), executionTimeoutSec * 1000);
97
+ signal?.addEventListener('abort', abort, { once: true });
98
+ if (signal?.aborted)
99
+ abort();
100
+ const appendOutput = (stream, chunk) => {
101
+ const remaining = maxOutputBytes - outputBytes;
102
+ output[stream].push(chunk.subarray(0, Math.max(0, remaining)));
103
+ outputBytes += Math.min(chunk.length, Math.max(0, remaining));
104
+ if (chunk.length > remaining)
105
+ stop({
106
+ code: 'SCRIPT_OUTPUT_LIMIT',
107
+ message: `Script output exceeded ${maxOutputBytes} bytes`,
108
+ });
109
+ };
110
+ for (const stream of ['stdout', 'stderr']) {
111
+ child[stream].on('data', (chunk) => appendOutput(stream, chunk));
112
+ }
113
+ const runPlugin = async (name, invocation, overrides = {}, help) => {
114
+ if (!findPluginRegistration(name) && findOfficialPlugin(name))
115
+ await ensureOfficialPluginRegistered(name);
116
+ controller.signal.throwIfAborted();
117
+ const pluginOptions = { ...defaults, session: sessionId, ...overrides };
118
+ if (pluginOptions.session === 'new' && !help)
119
+ throw new Error('Create the control session with a browser action before calling a Skill command');
120
+ const operation = executeRegisteredPlugin(name, invocation, {
121
+ invoke: (action, payload = {}, options) => invoke(action, payload, options),
122
+ }, {
123
+ ...pluginOptions,
124
+ source,
125
+ signal: controller.signal,
126
+ cancellationGraceMs: 50,
127
+ onOutput: appendOutput,
128
+ ...(help ? { help } : {}),
129
+ });
130
+ pluginCalls.add(operation);
131
+ try {
132
+ return await operation;
133
+ }
134
+ finally {
135
+ pluginCalls.delete(operation);
136
+ }
137
+ };
138
+ const send = (message) => {
139
+ if (outcome !== undefined || !child.connected)
140
+ return;
141
+ child.send(message, error => {
142
+ if (error)
143
+ stop(serializeError(error));
144
+ });
145
+ };
146
+ child.on('message', (message) => {
147
+ if (outcome !== undefined || !message || typeof message !== 'object')
148
+ return;
149
+ if (message.type === 'done' || message.type === 'error') {
150
+ stop(message.type === 'error'
151
+ ? serializeError(message.error)
152
+ : pending.size
153
+ ? {
154
+ code: 'SCRIPT_PENDING_CALLS',
155
+ message: 'Script finished with unawaited SDK calls',
156
+ }
157
+ : undefined);
158
+ return;
159
+ }
160
+ if (message.type !== 'call')
161
+ return;
162
+ const { id, method, args } = message;
163
+ if (!Number.isSafeInteger(id) ||
164
+ pending.has(id) ||
165
+ !METHODS.includes(method) ||
166
+ !Array.isArray(args)) {
167
+ stop({ code: 'SCRIPT_PROTOCOL_ERROR', message: 'Invalid script SDK call' });
168
+ return;
169
+ }
170
+ if (method !== 'help' && sessionId === 'new' && pending.size) {
171
+ send({
172
+ type: 'response',
173
+ id,
174
+ error: {
175
+ code: 'SCRIPT_SESSION_INITIALIZING',
176
+ message: 'Await the first session call before starting concurrent calls',
177
+ },
178
+ });
179
+ return;
180
+ }
181
+ pending.set(id, method === 'invoke' || method === 'invokePlugin' ? String(args[0]) : method);
182
+ void Promise.resolve()
183
+ .then(async () => {
184
+ if (outcome !== undefined)
185
+ throw new Error('Script execution stopped');
186
+ if (method === 'help') {
187
+ const target = args[0];
188
+ if (target && typeof target === 'object') {
189
+ if (typeof target.plugin !== 'string' ||
190
+ !target.plugin.trim() ||
191
+ (target.command !== undefined && typeof target.command !== 'string'))
192
+ throw new Error('Plugin help requires { plugin, command? }');
193
+ return runPlugin(target.plugin, { command: '', input: {} }, {}, { namespace: `mearl ${target.plugin}`, command: target.command });
194
+ }
195
+ return getCommandHelp(target);
196
+ }
197
+ if (method === 'invoke') {
198
+ if (!isBrowserCommandAction(args[0]) && args[0] !== 'browser_select_browser')
199
+ throw new Error(`Unsupported browser action: ${args[0]}; use invokePlugin for registered Skill commands`);
200
+ return invoke(args[0], args[1] ?? {}, args[2]);
201
+ }
202
+ if (method === 'invokePlugin') {
203
+ return runPlugin(args[0], args[1], args[2]);
204
+ }
205
+ if (method === 'saveArtifact')
206
+ return exportArtifact({ invoke }, args[0], args[1], {
207
+ ...defaults,
208
+ session: sessionId,
209
+ ...args[2],
210
+ });
211
+ const optionsOnly = OPTIONS_ONLY.has(method) || (method === 'browserRelease' && args.length < 2);
212
+ return invoke(METHOD_ACTIONS.get(method), optionsOnly ? {} : (args[0] ?? {}), optionsOnly ? args[0] : args[1]);
213
+ })
214
+ .then(result => send({ type: 'response', id, result, session }), error => send({ type: 'response', id, error: serializeError(error), session }))
215
+ .finally(() => pending.delete(id));
216
+ });
217
+ child.once('error', error => {
218
+ stop(serializeError(error));
219
+ });
220
+ child.once('exit', () => {
221
+ if (outcome === undefined)
222
+ stop({
223
+ code: 'SCRIPT_EXITED',
224
+ message: 'Script process exited before reporting completion',
225
+ });
226
+ });
227
+ child.once('close', () => {
228
+ closed = true;
229
+ clearTimeout(deadline);
230
+ signal?.removeEventListener('abort', abort);
231
+ void Promise.allSettled([cleanup, ...pluginCalls])
232
+ .then(outcomes => {
233
+ if (outcomes.some(result => result.status === 'rejected' &&
234
+ result.reason?.code === 'PLUGIN_EXECUTOR_CLEANUP_FAILED'))
235
+ cleanupError = new Error('Unable to confirm Skill process cleanup');
236
+ const error = cleanupError
237
+ ? serializeError(cleanupError, 'SCRIPT_CLEANUP_FAILED')
238
+ : outcome === true
239
+ ? undefined
240
+ : outcome;
241
+ resolve({
242
+ success: !error && outcome === true,
243
+ stdout: Buffer.concat(output.stdout).toString('utf8'),
244
+ stderr: Buffer.concat(output.stderr).toString('utf8'),
245
+ ...(session ? { session } : {}),
246
+ ...(error ? { error } : {}),
247
+ executionStopped: !cleanupError,
248
+ mayHaveLateEffects: pendingActions.length > 0 || uncertainEffects,
249
+ pendingActions,
250
+ });
251
+ })
252
+ .catch(reject);
253
+ });
254
+ send({ type: 'start', code, methods: METHODS, session });
255
+ });
256
+ }
257
+ finally {
258
+ client.disconnect();
259
+ }
260
+ }
@@ -0,0 +1 @@
1
+ export declare function runScriptCli(args: string[], commandName: string): Promise<number>;
@@ -0,0 +1,94 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { Command } from 'commander';
4
+ import { executeScript } from './script.js';
5
+ export async function runScriptCli(args, commandName) {
6
+ const program = new Command()
7
+ .name(`${commandName} run`)
8
+ .description('执行 Node.js 脚本;预注入 mearl SDK,支持顶层 await、条件和循环')
9
+ .option('--file <path>', '读取 JavaScript 文件;省略时从 stdin 读取')
10
+ .option('--session <new-or-id>', '首次建立或复用控制会话')
11
+ .option('--browser <id-or-name>', '指定浏览器')
12
+ .option('--connector <id-or-name>', '限制到远端 connector')
13
+ .option('--local', '仅使用本机浏览器')
14
+ .option('--cdp', '强制本机 CDP')
15
+ .option('--server <url>', '覆盖 cloud-server 地址')
16
+ .option('--timeout <seconds>', '整个脚本的执行时限(默认 60 秒)', '60')
17
+ .allowExcessArguments(false)
18
+ .exitOverride()
19
+ .addHelpText('after', `\n示例:\n ${commandName} run --file task.mjs\n ${commandName} run <<'JS'\n console.log(await mearl.tabList());\n JS\n\n脚本拥有当前宿主的 Node.js 权限;使用 await import() 加载模块。\n调用 Skill 命令:await mearl.invokePlugin('yuque', { command: 'read', input: { url: '...' } })。\n用 console.log(await mearl.help('pageClick')) 查询共享帮助,mearl.structure(value) 查看结果结构。\n每次运行重建 JS 变量;复用 session 和 tabId 可继续同一浏览器任务。\n`);
20
+ let source;
21
+ let options;
22
+ try {
23
+ program.parse(args, { from: 'user' });
24
+ options = program.opts();
25
+ const timeout = Number(options.timeout);
26
+ if (!Number.isFinite(timeout) || timeout <= 0 || timeout * 1000 > 2_147_483_647)
27
+ throw new Error('--timeout 必须是有效范围内的大于 0 的秒数');
28
+ if ((options.local || options.cdp) && options.connector)
29
+ throw new Error('--local/--cdp 不能与 --connector 同时使用');
30
+ if (options.file)
31
+ source = await readFile(resolve(options.file), 'utf8');
32
+ else {
33
+ if (process.stdin.isTTY)
34
+ throw new Error('通过 stdin 提交脚本,或使用 --file <path>');
35
+ const chunks = [];
36
+ let size = 0;
37
+ for await (const chunk of process.stdin) {
38
+ const buffer = Buffer.from(chunk);
39
+ size += buffer.length;
40
+ if (size > 1024 * 1024)
41
+ throw new Error('脚本不能超过 1 MiB');
42
+ chunks.push(buffer);
43
+ }
44
+ source = Buffer.concat(chunks).toString('utf8');
45
+ }
46
+ if (!source.trim() || Buffer.byteLength(source) > 1024 * 1024)
47
+ throw new Error('脚本必须是非空且不超过 1 MiB 的 JavaScript');
48
+ }
49
+ catch (error) {
50
+ if (error.code === 'commander.helpDisplayed')
51
+ return 0;
52
+ console.error(error instanceof Error ? error.message : String(error));
53
+ return 2;
54
+ }
55
+ const controller = new AbortController();
56
+ const abort = () => controller.abort();
57
+ process.once('SIGINT', abort);
58
+ process.once('SIGTERM', abort);
59
+ try {
60
+ const result = await executeScript(source, {
61
+ executionTimeoutSec: Number(options.timeout),
62
+ signal: controller.signal,
63
+ source: 'cli',
64
+ ...(options.session ? { session: options.session } : {}),
65
+ ...(options.browser ? { browser: options.browser } : {}),
66
+ ...(options.connector ? { connector: options.connector } : {}),
67
+ ...(options.local || options.cdp ? { local: true } : {}),
68
+ ...(options.cdp ? { transport: 'cdp' } : {}),
69
+ ...(options.server ? { serverUrl: options.server } : {}),
70
+ });
71
+ if (result.stdout)
72
+ process.stdout.write(result.stdout);
73
+ if (result.stderr)
74
+ process.stderr.write(result.stderr);
75
+ if (result.session)
76
+ console.error(JSON.stringify({ session: result.session }));
77
+ if (!result.success)
78
+ console.error(JSON.stringify({
79
+ error: result.error,
80
+ executionStopped: result.executionStopped,
81
+ mayHaveLateEffects: result.mayHaveLateEffects,
82
+ pendingActions: result.pendingActions,
83
+ }));
84
+ return result.success ? 0 : 1;
85
+ }
86
+ catch (error) {
87
+ console.error(error instanceof Error ? error.message : String(error));
88
+ return 1;
89
+ }
90
+ finally {
91
+ process.removeListener('SIGINT', abort);
92
+ process.removeListener('SIGTERM', abort);
93
+ }
94
+ }
@@ -0,0 +1,8 @@
1
+ export declare function serializeScriptError(error: unknown, fallback?: string): {
2
+ stack?: string | undefined;
3
+ attempt?: {} | null | undefined;
4
+ stage?: string | undefined;
5
+ details?: Record<string, unknown> | undefined;
6
+ code: string;
7
+ message: string;
8
+ };
@@ -0,0 +1,21 @@
1
+ import { serializePluginExecutionError } from './plugins/runtime/pluginExecutorProtocol.js';
2
+ export function serializeScriptError(error, fallback = 'SCRIPT_ERROR') {
3
+ const value = error;
4
+ const diagnostic = serializePluginExecutionError({
5
+ message: typeof value?.message === 'string' ? value.message : String(error),
6
+ code: typeof value?.code === 'string' ? value.code : fallback,
7
+ details: {
8
+ ...value?.details,
9
+ ...(value?.attempt !== undefined ? { attempt: value.attempt } : {}),
10
+ },
11
+ ...(typeof value?.stage === 'string' ? { stage: value.stage } : {}),
12
+ });
13
+ return {
14
+ code: diagnostic.code ?? fallback,
15
+ message: diagnostic.message,
16
+ ...(diagnostic.details ? { details: diagnostic.details } : {}),
17
+ ...(typeof diagnostic.metadata?.stage === 'string' ? { stage: diagnostic.metadata.stage } : {}),
18
+ ...(diagnostic.details?.attempt !== undefined ? { attempt: diagnostic.details.attempt } : {}),
19
+ ...(typeof value?.stack === 'string' ? { stack: value.stack.slice(0, 16384) } : {}),
20
+ };
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { getStructure } from './generated/data-structure.js';
2
+ import { serializeScriptError } from './scriptErrors.js';
3
+ const pending = new Map();
4
+ let sequence = 0;
5
+ let session;
6
+ const call = (method, args) => new Promise((resolve, reject) => {
7
+ const id = ++sequence;
8
+ pending.set(id, { resolve, reject });
9
+ process.send({ type: 'call', id, method, args });
10
+ });
11
+ process.on('disconnect', () => process.exit(1));
12
+ process.on('message', async (message) => {
13
+ if (message.type === 'response') {
14
+ const entry = pending.get(message.id);
15
+ if (!entry)
16
+ return;
17
+ pending.delete(message.id);
18
+ session = message.session ?? session;
19
+ if (message.error)
20
+ entry.reject(Object.assign(new Error(message.error.message), message.error));
21
+ else
22
+ entry.resolve(message.result);
23
+ return;
24
+ }
25
+ if (message.type !== 'start')
26
+ return;
27
+ session = message.session;
28
+ const api = Object.fromEntries(message.methods.map((method) => [method, (...args) => call(method, args)]));
29
+ api.structure = getStructure;
30
+ Object.defineProperty(api, 'session', { get: () => session, enumerable: true });
31
+ Object.defineProperty(globalThis, 'mearl', { value: Object.freeze(api), configurable: true });
32
+ let failure;
33
+ try {
34
+ const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
35
+ const fn = new AsyncFunction('mearl', '"use strict";\n' + message.code + '\n//# sourceURL=mearl-script.js');
36
+ await fn(api);
37
+ if (pending.size)
38
+ throw Object.assign(new Error('Script finished with unawaited SDK calls'), {
39
+ code: 'SCRIPT_PENDING_CALLS',
40
+ });
41
+ }
42
+ catch (error) {
43
+ failure = serializeScriptError(error);
44
+ }
45
+ await Promise.all([
46
+ new Promise(resolve => process.stdout.write('', resolve)),
47
+ new Promise(resolve => process.stderr.write('', resolve)),
48
+ ]);
49
+ process.send(failure ? { type: 'error', error: failure } : { type: 'done' });
50
+ });
@@ -0,0 +1,3 @@
1
+ /** Method names and signature exceptions shared by script dispatch and command help. */
2
+ export declare const SDK_METHOD_ACTIONS: Map<string, string>;
3
+ export declare const SDK_OPTIONS_ONLY_METHODS: Set<string>;
@@ -0,0 +1,9 @@
1
+ import { BROWSER_COMMAND_ACTIONS } from './generated/browser-action-protocol.js';
2
+ /** Method names and signature exceptions shared by script dispatch and command help. */
3
+ export const SDK_METHOD_ACTIONS = new Map(Object.keys(BROWSER_COMMAND_ACTIONS).map(action => [
4
+ action.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()),
5
+ action,
6
+ ]));
7
+ SDK_METHOD_ACTIONS.set('selectBrowser', 'browser_select_browser');
8
+ SDK_METHOD_ACTIONS.set('captureCheckpoint', 'tab_checkpoint');
9
+ export const SDK_OPTIONS_ONLY_METHODS = new Set(['browserList', 'tabList', 'getRules']);
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@mearl/client",
3
- "version": "2.22.0",
3
+ "version": "2.22.2",
4
4
  "description": "Unified Mearl SDK & CLI for local and remote browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "exports": {
9
+ "./script": {
10
+ "types": "./dist/script.d.ts",
11
+ "import": "./dist/script.js"
12
+ },
9
13
  ".": {
10
14
  "types": "./dist/index.d.ts",
11
15
  "import": "./dist/index.js"
@@ -60,23 +64,22 @@
60
64
  "registry": "https://registry.npmjs.org"
61
65
  },
62
66
  "dependencies": {
63
- "acorn": "^8.16.0",
64
- "ajv": "^8.18.0",
65
67
  "commander": "^15.0.0",
66
68
  "fastest-levenshtein": "^1.0.16",
67
69
  "ws": "^8.18.0",
68
- "@mearl/daemon-core": "2.22.0",
69
- "@mearl/cloud-types": "2.22.0",
70
- "@mearl/provider": "2.22.0",
71
- "@mearl/agent-runtime": "2.22.0"
70
+ "@mearl/cloud-types": "2.22.2",
71
+ "@mearl/provider": "2.22.2",
72
+ "@mearl/agent-runtime": "2.22.2",
73
+ "@mearl/daemon-core": "2.22.2"
72
74
  },
73
75
  "devDependencies": {
74
76
  "@types/node": "^24.9.1",
75
77
  "@types/ws": "^8.18.1",
78
+ "esbuild": "^0.25.12",
76
79
  "typescript": "^5.8.3"
77
80
  },
78
81
  "scripts": {
79
- "build": "node ../../scripts/generate-client-action-protocol.mjs --check && rm -rf dist/ && tsc && chmod +x dist/bin.js",
82
+ "build": "node ../../scripts/generate-client-action-protocol.mjs --check && node ../../scripts/generate-plugin-host-build.mjs && rm -rf dist/ && tsc && chmod +x dist/bin.js",
80
83
  "dev": "tsc --watch",
81
84
  "typecheck": "node ../../scripts/generate-client-action-protocol.mjs --check && tsc --noEmit",
82
85
  "test": "vitest run"