@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 ||
|
|
14
|
-
|
|
15
|
-
|
|
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 &&
|
|
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' ||
|
|
28
|
-
|
|
29
|
-
|
|
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 &&
|
|
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] = {
|
|
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 = {
|
|
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 &&
|
|
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({
|
|
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 {
|
|
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())
|
|
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
|
|
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 &&
|
|
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 &&
|
|
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
|
|
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
|
|
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.
|
|
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/
|
|
72
|
-
"@mearl/
|
|
73
|
-
"@mearl/
|
|
74
|
-
"@mearl/
|
|
75
|
-
"@mearl/
|
|
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",
|