@mearl/client 3.0.1 → 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.
@@ -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.1",
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/cloud-types": "3.0.1",
72
- "@mearl/daemon-core": "3.0.1",
73
- "@mearl/browser-core": "3.0.1",
74
- "@mearl/agent-runtime": "3.0.1",
75
- "@mearl/provider": "3.0.1"
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",