@mearl/client 3.0.0 → 3.0.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.
package/README.en.md CHANGED
@@ -15,9 +15,9 @@ Use type-safe named methods such as `getRequests` and `sendRequest`, or the flex
15
15
  ## Installation
16
16
 
17
17
  ```bash
18
- npm install @mearl/client@3
18
+ npm install @mearl/client
19
19
  # Or
20
- pnpm add @mearl/client@3
20
+ pnpm add @mearl/client
21
21
  ```
22
22
 
23
23
  ## SDK usage
@@ -66,7 +66,7 @@ mearl check
66
66
  mearl check --local --json
67
67
 
68
68
  # Update the local environment
69
- npx @mearl/setup@3 update --local
69
+ npx @mearl/setup update --local
70
70
 
71
71
  # Show action help
72
72
  mearl <action> --help
@@ -248,7 +248,7 @@ Mearl aggregates local and configured cloud-server browsers by default, routing
248
248
  latest client, native-host, cloud-connector, and extension releases. Release queries wait about 1.5 seconds at most;
249
249
  unreachable sources report unconfirmed updates without affecting browser checks. `--json` returns machine-readable results,
250
250
  including updates when combined with `--updates`. Available updates show appropriate commands: client/native-host use
251
- `npx @mearl/setup@3 update`; setup-managed extensions use `npx @mearl/setup extension`.
251
+ `npx @mearl/setup update`; setup-managed extensions use `npx @mearl/setup extension`.
252
252
 
253
253
  By default, globally installed `mearl` launches an independent background updater after the first use each day to query releases and compare
254
254
  installed client, native-host, cloud-server, and MCP server versions. When needed, it invokes
package/README.md CHANGED
@@ -15,9 +15,9 @@
15
15
  ## 安装
16
16
 
17
17
  ```bash
18
- npm install @mearl/client@3
18
+ npm install @mearl/client
19
19
  # 或
20
- pnpm add @mearl/client@3
20
+ pnpm add @mearl/client
21
21
  ```
22
22
 
23
23
  ## SDK 使用
@@ -66,7 +66,7 @@ mearl check
66
66
  mearl check --local --json
67
67
 
68
68
  # 更新本地环境
69
- npx @mearl/setup@3 update --local
69
+ npx @mearl/setup update --local
70
70
 
71
71
  # 查看某个 action 的帮助
72
72
  mearl <action> --help
@@ -248,7 +248,7 @@ Mearl 默认聚合当前机器和已配置 cloud-server 下的浏览器,并通
248
248
  client、native-host、cloud-connector 和扩展的最新发布版本。发布版本查询最长等待约 1.5 秒,
249
249
  无法访问发布源时会提示无法确认更新,不影响浏览器可用性检查。`--json` 输出机器可读的检查结果,
250
250
  与 `--updates` 同用时包含更新状态。发现更新时会提示适用的命令:client/native-host 使用
251
- `npx @mearl/setup@3 update`,setup 管理的扩展使用 `npx @mearl/setup extension`。
251
+ `npx @mearl/setup update`,setup 管理的扩展使用 `npx @mearl/setup extension`。
252
252
 
253
253
  全局安装的 `mearl` CLI 默认在每天首次使用后启动独立后台进程,查询最新发布版本,并比较
254
254
  client、native-host、cloud-server 和 MCP server 的已安装版本。发现更新时调用
@@ -21,5 +21,7 @@ export declare function validateOfficialPluginCatalog(value: unknown): PluginCat
21
21
  export declare function readOfficialPluginCatalog(): PluginCatalog | undefined;
22
22
  /** Installation hosts persist only the catalog selected by an approved preset. */
23
23
  export declare function replaceOfficialPluginCatalog(revision: string, plugins: Record<string, OfficialPluginDefinition>, selection?: PluginCatalogSelection): void;
24
+ /** Remove only registrations owned by the catalog for the exact active selection. */
25
+ export declare function uninstallOfficialPluginCatalog(selection: PluginCatalogSelection): void;
24
26
  export declare function findOfficialPlugin(name: string): OfficialPluginDefinition | undefined;
25
27
  export declare function listOfficialPluginNames(setup?: string): string[];
@@ -1,7 +1,7 @@
1
- import { constants, closeSync, fstatSync, lstatSync, mkdirSync, openSync, readSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { constants, closeSync, fstatSync, lstatSync, mkdirSync, openSync, readSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
3
  import { isAbsolute, join } from 'node:path';
4
- import { assertPluginName, pluginDirectory } from './pluginRegistry.js';
4
+ import { assertPluginName, findPluginRegistration, pluginDirectory, unregisterPlugin, } from './pluginRegistry.js';
5
5
  export const OFFICIAL_PLUGIN_MANAGER = '@mearl/setup:official';
6
6
  export const OFFICIAL_PLUGINS = {};
7
7
  const MAX_CATALOG_BYTES = 256 * 1024;
@@ -10,12 +10,27 @@ export function validateOfficialPluginCatalog(value) {
10
10
  if (!value || typeof value !== 'object' || Array.isArray(value))
11
11
  throw new Error('Invalid managed plugin catalog');
12
12
  const input = value;
13
- if (input.schemaVersion !== 1 || typeof input.revision !== 'string' || !/^[a-f0-9]{64}$/.test(input.revision) ||
14
- !input.plugins || typeof input.plugins !== 'object' || Array.isArray(input.plugins) ||
15
- Object.keys(input).some(key => !['schemaVersion', 'revision', 'plugins', 'selection'].includes(key)) || Object.keys(input.plugins).length > 200)
13
+ if (input.schemaVersion !== 1 ||
14
+ typeof input.revision !== 'string' ||
15
+ !/^[a-f0-9]{64}$/.test(input.revision) ||
16
+ !input.plugins ||
17
+ typeof input.plugins !== 'object' ||
18
+ Array.isArray(input.plugins) ||
19
+ Object.keys(input).some(key => !['schemaVersion', 'revision', 'plugins', 'selection'].includes(key)) ||
20
+ Object.keys(input.plugins).length > 200)
16
21
  throw new Error('Invalid managed plugin catalog');
17
22
  const selection = input.selection;
18
- if (selection !== undefined && (!selection || typeof selection !== 'object' || Array.isArray(selection) || Object.keys(selection).some(key => !['path', 'sha256'].includes(key)) || typeof selection.path !== 'string' || !isAbsolute(selection.path) || selection.path.length > 4096 || /[\0\r\n]/.test(selection.path) || typeof selection.sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(selection.sha256)))
23
+ if (selection !== undefined &&
24
+ (!selection ||
25
+ typeof selection !== 'object' ||
26
+ Array.isArray(selection) ||
27
+ Object.keys(selection).some(key => !['path', 'sha256'].includes(key)) ||
28
+ typeof selection.path !== 'string' ||
29
+ !isAbsolute(selection.path) ||
30
+ selection.path.length > 4096 ||
31
+ /[\0\r\n]/.test(selection.path) ||
32
+ typeof selection.sha256 !== 'string' ||
33
+ !/^[a-f0-9]{64}$/.test(selection.sha256)))
19
34
  throw new Error('Invalid plugin catalog selection');
20
35
  const plugins = {};
21
36
  for (const [name, value] of Object.entries(input.plugins)) {
@@ -24,15 +39,30 @@ export function validateOfficialPluginCatalog(value) {
24
39
  throw new Error('Invalid managed plugin declaration');
25
40
  const item = value;
26
41
  if (Object.keys(item).some(key => !['description', 'installArgs', 'setup', 'skill'].includes(key)) ||
27
- typeof item.description !== 'string' || item.description.length > 1000 ||
28
- typeof item.skill !== 'string' || !/^[a-z][a-z0-9-]*$/.test(item.skill) ||
29
- !Array.isArray(item.installArgs) || item.installArgs.length === 0 || item.installArgs.length > 32 ||
42
+ typeof item.description !== 'string' ||
43
+ item.description.length > 1000 ||
44
+ typeof item.skill !== 'string' ||
45
+ !/^[a-z][a-z0-9-]*$/.test(item.skill) ||
46
+ !Array.isArray(item.installArgs) ||
47
+ item.installArgs.length === 0 ||
48
+ item.installArgs.length > 32 ||
30
49
  item.installArgs.some(arg => typeof arg !== 'string' || arg.length > 2048 || /[\0\r\n]/.test(arg)) ||
31
- (item.setup !== undefined && (typeof item.setup !== 'string' || !/^[a-z][a-z0-9-]*$/.test(item.setup))))
50
+ (item.setup !== undefined &&
51
+ (typeof item.setup !== 'string' || !/^[a-z][a-z0-9-]*$/.test(item.setup))))
32
52
  throw new Error('Invalid managed plugin declaration');
33
- plugins[name] = { description: item.description, skill: item.skill, installArgs: [...item.installArgs], ...(item.setup !== undefined && { setup: item.setup }) };
53
+ plugins[name] = {
54
+ description: item.description,
55
+ skill: item.skill,
56
+ installArgs: [...item.installArgs],
57
+ ...(item.setup !== undefined && { setup: item.setup }),
58
+ };
34
59
  }
35
- const catalog = { schemaVersion: 1, revision: input.revision, plugins, ...(selection ? { selection: { path: selection.path, sha256: selection.sha256 } } : {}) };
60
+ const catalog = {
61
+ schemaVersion: 1,
62
+ revision: input.revision,
63
+ plugins,
64
+ ...(selection ? { selection: { path: selection.path, sha256: selection.sha256 } } : {}),
65
+ };
36
66
  if (Buffer.byteLength(JSON.stringify(catalog)) > MAX_CATALOG_BYTES)
37
67
  throw new Error('Managed plugin catalog exceeds 256 KiB');
38
68
  return catalog;
@@ -55,7 +85,8 @@ function selectionMatches(selection) {
55
85
  break;
56
86
  size += count;
57
87
  }
58
- return size === stat.size && createHash('sha256').update(bytes.subarray(0, size)).digest('hex') === selection.sha256;
88
+ return (size === stat.size &&
89
+ createHash('sha256').update(bytes.subarray(0, size)).digest('hex') === selection.sha256);
59
90
  }
60
91
  finally {
61
92
  closeSync(fd);
@@ -84,7 +115,12 @@ export function readOfficialPluginCatalog() {
84
115
  }
85
116
  /** Installation hosts persist only the catalog selected by an approved preset. */
86
117
  export function replaceOfficialPluginCatalog(revision, plugins, selection) {
87
- const catalog = validateOfficialPluginCatalog({ schemaVersion: 1, revision, plugins, ...(selection ? { selection } : {}) });
118
+ const catalog = validateOfficialPluginCatalog({
119
+ schemaVersion: 1,
120
+ revision,
121
+ plugins,
122
+ ...(selection ? { selection } : {}),
123
+ });
88
124
  const content = JSON.stringify(catalog);
89
125
  if (Buffer.byteLength(content) > MAX_CATALOG_BYTES)
90
126
  throw new Error('Managed plugin catalog exceeds 256 KiB');
@@ -109,17 +145,33 @@ function refreshOfficialPlugins() {
109
145
  try {
110
146
  plugins = readOfficialPluginCatalog()?.plugins ?? {};
111
147
  }
112
- catch { /* An invalid optional catalog cannot disable registered plugin or browser commands. */ }
148
+ catch {
149
+ /* An invalid optional catalog cannot disable registered plugin or browser commands. */
150
+ }
113
151
  for (const name of Object.keys(OFFICIAL_PLUGINS))
114
152
  delete OFFICIAL_PLUGINS[name];
115
153
  Object.assign(OFFICIAL_PLUGINS, plugins);
116
154
  return OFFICIAL_PLUGINS;
117
155
  }
156
+ /** Remove only registrations owned by the catalog for the exact active selection. */
157
+ export function uninstallOfficialPluginCatalog(selection) {
158
+ const catalog = readOfficialPluginCatalog();
159
+ if (catalog?.selection?.path !== selection.path || catalog.selection.sha256 !== selection.sha256)
160
+ return;
161
+ for (const name of Object.keys(catalog.plugins)) {
162
+ if (findPluginRegistration(name)?.managedBy === OFFICIAL_PLUGIN_MANAGER)
163
+ unregisterPlugin(name);
164
+ }
165
+ rmSync(catalogPath(), { force: true });
166
+ refreshOfficialPlugins();
167
+ }
118
168
  refreshOfficialPlugins();
119
169
  export function findOfficialPlugin(name) {
120
170
  const plugins = refreshOfficialPlugins();
121
171
  return Object.hasOwn(plugins, name) ? plugins[name] : undefined;
122
172
  }
123
173
  export function listOfficialPluginNames(setup) {
124
- return Object.entries(refreshOfficialPlugins()).filter(([, item]) => setup === undefined || item.setup === setup).map(([name]) => name);
174
+ return Object.entries(refreshOfficialPlugins())
175
+ .filter(([, item]) => setup === undefined || item.setup === setup)
176
+ .map(([name]) => name);
125
177
  }
@@ -4,7 +4,7 @@ import type { PluginStateDeclaration } from '@mearl/browser-core';
4
4
  import type { PluginModelRequest, PluginModelResult } from '@mearl/browser-core';
5
5
  import type { PluginPageRuntime } from '@mearl/browser-core';
6
6
  import type { AppProfileContribution } from '@mearl/browser-core';
7
- export type { AppProfileContribution, AppProfileDefinition, } from '@mearl/browser-core';
7
+ export type { AppProfileContribution, AppProfileDefinition } from '@mearl/browser-core';
8
8
  export { invokePageEvalWithArgs } from '../../pageEvalArguments.js';
9
9
  import { type OfficialPluginInstaller } from '../officialPluginInstaller.js';
10
10
  import { type PluginToolboxContribution, type PluginRegistration } from '../pluginRegistry.js';
@@ -16,7 +16,7 @@ export { PluginTaskClient } from '../clients/pluginTasks.js';
16
16
  export { cleanupPluginRuntime } from '../runtime/pluginRuntimeCleanup.js';
17
17
  export { PluginPageClient } from '../clients/pluginPages.js';
18
18
  export { assertPluginName, findPluginDirectoryEntry, findPluginEntry, findPluginRegistration, pluginRegistrationNeedsRefresh, listPluginNames, pluginDirectory, registerPlugin, resolvePluginEntry, unregisterPlugin, PluginError, readPluginPageBundleChunk, type PluginRegistration, type PluginRegistrationOptions, type PluginToolboxAgentConfig, type PluginPageBundleRegistration, type PluginToolboxContribution, type PluginToolboxLocale, type PluginToolboxSandboxPage, } from '../pluginRegistry.js';
19
- export { findOfficialPlugin, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, OFFICIAL_PLUGINS, readOfficialPluginCatalog, replaceOfficialPluginCatalog, validateOfficialPluginCatalog, type PluginCatalogSelection, type PluginCatalog, type OfficialPluginDefinition, type OfficialPluginName, } from '../officialPluginCatalog.js';
19
+ export { findOfficialPlugin, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, OFFICIAL_PLUGINS, readOfficialPluginCatalog, replaceOfficialPluginCatalog, uninstallOfficialPluginCatalog, validateOfficialPluginCatalog, type PluginCatalogSelection, type PluginCatalog, type OfficialPluginDefinition, type OfficialPluginName, } from '../officialPluginCatalog.js';
20
20
  export type PluginOptions = UnifiedInvokeOptions;
21
21
  export interface StructuredPluginProgress {
22
22
  message: string;
@@ -19,7 +19,7 @@ export { PluginTaskClient } from '../clients/pluginTasks.js';
19
19
  export { cleanupPluginRuntime } from '../runtime/pluginRuntimeCleanup.js';
20
20
  export { PluginPageClient } from '../clients/pluginPages.js';
21
21
  export { assertPluginName, findPluginDirectoryEntry, findPluginEntry, findPluginRegistration, pluginRegistrationNeedsRefresh, listPluginNames, pluginDirectory, registerPlugin, resolvePluginEntry, unregisterPlugin, PluginError, readPluginPageBundleChunk, } from '../pluginRegistry.js';
22
- export { findOfficialPlugin, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, OFFICIAL_PLUGINS, readOfficialPluginCatalog, replaceOfficialPluginCatalog, validateOfficialPluginCatalog, } from '../officialPluginCatalog.js';
22
+ export { findOfficialPlugin, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, OFFICIAL_PLUGINS, readOfficialPluginCatalog, replaceOfficialPluginCatalog, uninstallOfficialPluginCatalog, validateOfficialPluginCatalog, } from '../officialPluginCatalog.js';
23
23
  function readPluginVersion(entry) {
24
24
  try {
25
25
  return readInstalledSkillMetadata(resolve(dirname(entry), '..'))?.version;
@@ -334,7 +334,10 @@ export async function loadPlugin(entry, context, pluginName) {
334
334
  }
335
335
  if (commands.size === 0 && !appProfiles)
336
336
  throw new Error('入口未注册任何命令');
337
- if (defaultCommand !== undefined && (!commands.has(defaultCommand) || commands.get(defaultCommand)?.toolboxOnly || commands.get(defaultCommand)?.runtimeOnly))
337
+ if (defaultCommand !== undefined &&
338
+ (!commands.has(defaultCommand) ||
339
+ commands.get(defaultCommand)?.toolboxOnly ||
340
+ commands.get(defaultCommand)?.runtimeOnly))
338
341
  throw new Error('defaultCommand 必须指向公开命令');
339
342
  for (const command of Object.keys(taskRecovery?.commands ?? {})) {
340
343
  if (!commands.has(command) ||
@@ -411,13 +414,16 @@ export async function loadPlugin(entry, context, pluginName) {
411
414
  if (payload !== undefined && Object.keys(namedInput).length > 0) {
412
415
  throw new PluginError('PLUGIN_INVALID_INPUT', '插件业务选项与 --payload 调用不能混用');
413
416
  }
414
- const defaultPayload = defaultCommand && toolboxPage === false && isObject(payload) && !Object.hasOwn(payload, 'command');
417
+ const defaultPayload = defaultCommand &&
418
+ toolboxPage === false &&
419
+ isObject(payload) &&
420
+ !Object.hasOwn(payload, 'command');
415
421
  if (payload !== undefined && !defaultPayload && !isPluginInvocation(payload)) {
416
422
  throw new PluginError('PLUGIN_INVALID_INPUT', '--payload 必须为 {"command":"...","input":{...}}');
417
423
  }
418
424
  const envelope = (defaultPayload ? { command: defaultCommand, input: payload } : payload);
419
425
  const positionalOffset = defaultCommand && !commands.has(positionals[0]) && toolboxPage === false ? 0 : 1;
420
- const commandName = envelope?.command ?? (positionalOffset === 0 ? defaultCommand : positionals[0] ?? '');
426
+ const commandName = envelope?.command ?? (positionalOffset === 0 ? defaultCommand : (positionals[0] ?? ''));
421
427
  const hidden = toolboxPage === 'recovery' || toolboxPage === 'migration';
422
428
  const selected = find(commandName, hidden ? false : toolboxPage);
423
429
  const command = selected.command;
@@ -437,7 +443,9 @@ export async function loadPlugin(entry, context, pluginName) {
437
443
  : Object.fromEntries(command.registeredArguments
438
444
  .map((argument, index) => [
439
445
  argument.name(),
440
- argument.variadic ? positionals.slice(index + positionalOffset) : positionals[index + positionalOffset],
446
+ argument.variadic
447
+ ? positionals.slice(index + positionalOffset)
448
+ : positionals[index + positionalOffset],
441
449
  ])
442
450
  .filter(([, value]) => value !== undefined));
443
451
  for (const name of Object.keys(namedInput)) {
@@ -555,7 +563,9 @@ export async function loadPlugin(entry, context, pluginName) {
555
563
  .join('\n');
556
564
  },
557
565
  help(namespace, name) {
558
- const selected = name ? [find(defaultCommand && !commands.has(name) ? defaultCommand : name)] : publicCommands();
566
+ const selected = name
567
+ ? [find(defaultCommand && !commands.has(name) ? defaultCommand : name)]
568
+ : publicCommands();
559
569
  if (!selected.length) {
560
570
  const location = toolbox
561
571
  ? `请在 Mearl ${toolbox.displayMode === 'popup' ? 'Popup' : '工具箱'} 中打开「${toolbox.title}」使用。`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/client",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Unified Mearl SDK & CLI for local and remote browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -68,11 +68,11 @@
68
68
  "commander": "^14.0.3",
69
69
  "fastest-levenshtein": "^1.0.16",
70
70
  "ws": "^8.18.0",
71
- "@mearl/daemon-core": "3.0.0",
72
- "@mearl/cloud-types": "3.0.0",
73
- "@mearl/browser-core": "3.0.0",
74
- "@mearl/agent-runtime": "3.0.0",
75
- "@mearl/provider": "3.0.0"
71
+ "@mearl/daemon-core": "3.0.2",
72
+ "@mearl/cloud-types": "3.0.2",
73
+ "@mearl/agent-runtime": "3.0.2",
74
+ "@mearl/provider": "3.0.2",
75
+ "@mearl/browser-core": "3.0.2"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@types/node": "^24.9.1",
@@ -1,2 +0,0 @@
1
- /** CLI host commands share the SDK error code without exporting arbitrary error metadata. */
2
- export declare function writePluginCliError(error: unknown): void;
@@ -1,7 +0,0 @@
1
- /** CLI host commands share the SDK error code without exporting arbitrary error metadata. */
2
- export function writePluginCliError(error) {
3
- const code = error && typeof error === 'object' && 'code' in error && typeof error.code === 'string'
4
- ? error.code
5
- : 'PLUGIN_HOST_ERROR';
6
- process.stderr.write(`${JSON.stringify({ error: { code, message: error instanceof Error ? error.message : String(error) } })}\n`);
7
- }