@mearl/setup 2.17.1 → 2.19.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/README.md +9 -7
- package/dist/browserExtension.d.ts +1 -0
- package/dist/browserExtension.js +4 -1
- package/dist/cli.js +35 -17
- package/dist/lifecycle.js +53 -7
- package/dist/plugin.d.ts +5 -1
- package/dist/plugin.js +109 -28
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ npx @mearl/setup [install] [--local] [--cloud|--qoder-cloud] [--mcp]
|
|
|
7
7
|
npx @mearl/setup update [--local] [--cloud|--qoder-cloud] [--mcp]
|
|
8
8
|
npx @mearl/setup uninstall [--local] [--cloud|--qoder-cloud] [--mcp] [--full]
|
|
9
9
|
npx @mearl/setup extension [--full] [--force]
|
|
10
|
-
npx @mearl/setup plugin
|
|
10
|
+
npx @mearl/setup plugin [name] <path>
|
|
11
11
|
npx @mearl/setup plugin install <ali-skills add 参数...>
|
|
12
12
|
npx @mearl/setup plugin uninstall <name>
|
|
13
13
|
npx @mearl/setup provider <install|update|uninstall> <package|id>
|
|
@@ -53,16 +53,16 @@ Mearl client 内置 `buc` 与 `tdbank` 的安装来源。CLI、SDK、MCP 或另
|
|
|
53
53
|
已通过其他渠道分发的 skill 或自定义入口,可以直接传入路径注册:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
npx @mearl/setup plugin
|
|
56
|
+
npx @mearl/setup plugin /absolute/path/to/yuque-doc-fetch
|
|
57
57
|
npx @mearl/setup plugin custom ./skills/custom/entry.mjs
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
path 接受目录或具体文件;目录默认读取 `scripts/main.mjs` 或 `scripts/main.py
|
|
60
|
+
path 接受目录或具体文件;目录默认读取 `scripts/main.mjs` 或 `scripts/main.py`,相对路径基于调用工作目录解析。插件名优先使用显式 name,其次读取入口的 `name` metadata,最后回退到 Skill 目录名;常规注册只传 path,显式 name 仅用于覆盖或兼容旧入口。Python 插件要求 `python3 >= 3.9`,可通过 `MEARL_PYTHON` 指定解释器路径;setup 只验证入口可加载,不安装 Python,也不执行 `pip install`。
|
|
61
61
|
命令检查入口注册函数,然后保存绝对入口路径,同名注册更新绑定。
|
|
62
62
|
两种注册流程都会检查本机 `@mearl/client` 和 `@mearl/native-host`:缺失或低于当前 setup 版本时,
|
|
63
63
|
复用已有包管理器安装 setup 的对应版本,更新后再次验证;更高版本保持不动。
|
|
64
64
|
native-host 会幂等初始化 Skills 模式。基础环境补齐或初始化失败时保留已安装的 skill,并停止注册。
|
|
65
|
-
注册后可在任意目录运行 `mearl <name> --help`,也可通过 `@mearl/client` 的 `invokePlugin` 或 MCP 的 `
|
|
65
|
+
注册后可在任意目录运行 `mearl <name> --help`,也可通过 `@mearl/client` 的 `invokePlugin` 或 MCP 的 `plugin_invoke` 工具调用。已注册插件的日常调用不执行安装或版本检查;只有缺失的 `buc` 或 `tdbank` 会触发一次按需安装。
|
|
66
66
|
|
|
67
67
|
不再需要插件时执行:
|
|
68
68
|
|
|
@@ -148,9 +148,11 @@ npx @mearl/setup extension --full
|
|
|
148
148
|
|
|
149
149
|
首次安装、旧版 setup 扩展首次迁移,或 Native Host reload 不可用时才需要 CDP。如果
|
|
150
150
|
Chrome 尚未开放 CDP,交互命令会提示打开
|
|
151
|
-
`chrome://inspect/#remote-debugging
|
|
152
|
-
环境使用 `npx @mearl/setup extension --yes
|
|
153
|
-
CDP
|
|
151
|
+
`chrome://inspect/#remote-debugging`、启用远程调试并允许连接,然后等待安装完成。面向人的
|
|
152
|
+
安装指引不要添加 `--yes`。无 TTY 环境使用 `npx @mearl/setup extension --yes`;它只尝试
|
|
153
|
+
一次,CDP 不可用时立即中断。无法确认 setup 管理来源且 CDP 不可用时不会下载或修改插件
|
|
154
|
+
目录,打开 CDP 后重新执行同一命令即可。Chrome 版本过低、缺少扩展安装所需的 CDP 方法时,
|
|
155
|
+
命令会中断并提示升级 Chrome。
|
|
154
156
|
|
|
155
157
|
该命令每次都读取下载页上的最新插件版本,不要求与 setup、CLI 或 Native Host 同版本。
|
|
156
158
|
CDP 来源检查和 `Extensions.loadUnpacked` 复用同一条连接,避免一次安装重复请求授权。
|
|
@@ -54,6 +54,7 @@ export declare class BrowserExtensionConflictError extends Error {
|
|
|
54
54
|
readonly installed: InstalledBrowserExtension;
|
|
55
55
|
constructor(installed: InstalledBrowserExtension);
|
|
56
56
|
}
|
|
57
|
+
export declare function isUnsupportedCdpMethodError(message: string): boolean;
|
|
57
58
|
export declare function resolveChromeUserDataDirectory(platform?: NodeJS.Platform, home?: string, env?: NodeJS.ProcessEnv): string;
|
|
58
59
|
export declare function resolveMearlExtensionDirectory(home?: string): string;
|
|
59
60
|
export declare function findLatestExtensionVersion(html: string): string | null;
|
package/dist/browserExtension.js
CHANGED
|
@@ -23,6 +23,9 @@ export class BrowserExtensionConflictError extends Error {
|
|
|
23
23
|
this.name = 'BrowserExtensionConflictError';
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
export function isUnsupportedCdpMethodError(message) {
|
|
27
|
+
return /Method not (?:available|found)|wasn't found|unknown method/i.test(message);
|
|
28
|
+
}
|
|
26
29
|
export function resolveChromeUserDataDirectory(platform = process.platform, home = os.homedir(), env = process.env) {
|
|
27
30
|
if (platform === 'darwin') {
|
|
28
31
|
return path.join(home, 'Library', 'Application Support', 'Google', 'Chrome');
|
|
@@ -522,7 +525,7 @@ export async function installMearlBrowserExtension(options = {}, dependencies =
|
|
|
522
525
|
throw error;
|
|
523
526
|
const message = error instanceof Error ? error.message : String(error);
|
|
524
527
|
lastFailure = message;
|
|
525
|
-
if (waitForCdpMs === 0 ||
|
|
528
|
+
if (waitForCdpMs === 0 || isUnsupportedCdpMethodError(message)) {
|
|
526
529
|
break;
|
|
527
530
|
}
|
|
528
531
|
if (lastNoticeAt === 0) {
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { Command, CommanderError } from 'commander';
|
|
4
|
-
import { installMearlBrowserExtension } from './browserExtension.js';
|
|
4
|
+
import { installMearlBrowserExtension, isUnsupportedCdpMethodError } from './browserExtension.js';
|
|
5
5
|
import { runMearlLifecycle } from './lifecycle.js';
|
|
6
6
|
import { isMearlReleaseLine } from './version.js';
|
|
7
7
|
import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
|
|
@@ -53,6 +53,20 @@ export function parseSetupArgs(argv) {
|
|
|
53
53
|
}
|
|
54
54
|
return { ...baseParsedArgs(), operation: 'plugin', pluginUninstallName: argv[2] };
|
|
55
55
|
}
|
|
56
|
+
if (argv[0] === 'plugin') {
|
|
57
|
+
if (argv.length === 2 && argv[1]) {
|
|
58
|
+
return { ...baseParsedArgs(), operation: 'plugin', pluginPath: argv[1] };
|
|
59
|
+
}
|
|
60
|
+
if (argv.length === 3 && argv[1] && argv[2]) {
|
|
61
|
+
return {
|
|
62
|
+
...baseParsedArgs(),
|
|
63
|
+
operation: 'plugin',
|
|
64
|
+
pluginName: argv[1],
|
|
65
|
+
pluginPath: argv[2],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
throw new Error('plugin requires <path> or <name> <path>');
|
|
69
|
+
}
|
|
56
70
|
let parsed;
|
|
57
71
|
const program = new Command()
|
|
58
72
|
.name('mearl-setup')
|
|
@@ -109,14 +123,6 @@ export function parseSetupArgs(argv) {
|
|
|
109
123
|
addLifecycleCommand('install');
|
|
110
124
|
addLifecycleCommand('update');
|
|
111
125
|
addLifecycleCommand('uninstall');
|
|
112
|
-
program
|
|
113
|
-
.command('plugin')
|
|
114
|
-
.argument('<name>')
|
|
115
|
-
.argument('<path>')
|
|
116
|
-
.allowExcessArguments(false)
|
|
117
|
-
.action((pluginName, pluginPath) => {
|
|
118
|
-
parsed = { ...baseParsedArgs(), operation: 'plugin', pluginName, pluginPath };
|
|
119
|
-
});
|
|
120
126
|
const extension = new Command('extension')
|
|
121
127
|
.exitOverride()
|
|
122
128
|
.configureOutput({ writeErr: () => { }, writeOut: () => { } })
|
|
@@ -167,7 +173,7 @@ Environment commands:
|
|
|
167
173
|
Integration commands:
|
|
168
174
|
plugin install <arguments...> Install Skills and register their CLI plugins
|
|
169
175
|
plugin uninstall <name> Uninstall one CLI plugin
|
|
170
|
-
plugin
|
|
176
|
+
plugin [name] <path> Register an existing plugin; name overrides metadata
|
|
171
177
|
provider install <package> Install and register a Provider package
|
|
172
178
|
provider update <id|package> Update a registered Provider package
|
|
173
179
|
provider uninstall <id|package> Uninstall a Provider package and registration
|
|
@@ -257,9 +263,12 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
|
|
|
257
263
|
}
|
|
258
264
|
return 0;
|
|
259
265
|
}
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
266
|
+
const register = dependencies.registerPlugin ?? setupPlugin;
|
|
267
|
+
const registration = parsed.pluginName
|
|
268
|
+
? await register(parsed.pluginPath, parsed.pluginName)
|
|
269
|
+
: await register(parsed.pluginPath);
|
|
270
|
+
console.error(`Plugin ${registration.name} registered: ${registration.entry}`);
|
|
271
|
+
console.error(`Run: mearl ${registration.name} --help`);
|
|
263
272
|
return 0;
|
|
264
273
|
}
|
|
265
274
|
catch (error) {
|
|
@@ -294,6 +303,7 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
|
|
|
294
303
|
return 2;
|
|
295
304
|
}
|
|
296
305
|
let extensionInstalled = false;
|
|
306
|
+
let extensionFailureIsTerminal = false;
|
|
297
307
|
try {
|
|
298
308
|
const result = await (dependencies.installBrowserExtension ?? installMearlBrowserExtension)({
|
|
299
309
|
force: parsed.force,
|
|
@@ -312,15 +322,23 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
|
|
|
312
322
|
console.error(result.filesUpdated
|
|
313
323
|
? `Browser extension v${result.version} files were updated, but the extension was not reloaded.`
|
|
314
324
|
: `Browser extension v${result.version} was not installed.`);
|
|
315
|
-
if (result.cdpError)
|
|
316
|
-
console.error(`CDP installation
|
|
317
|
-
|
|
325
|
+
if (result.cdpError) {
|
|
326
|
+
console.error(`CDP installation failed: ${result.cdpError}`);
|
|
327
|
+
if (isUnsupportedCdpMethodError(result.cdpError)) {
|
|
328
|
+
console.error('This Chrome version does not support installing extensions through CDP. Update Chrome to version 145 or later, then rerun: npx @mearl/setup extension');
|
|
329
|
+
extensionFailureIsTerminal = true;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
console.error('Open chrome://inspect/#remote-debugging, enable remote debugging, approve the connection, then rerun: npx @mearl/setup extension');
|
|
333
|
+
extensionFailureIsTerminal = parsed.yes;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
318
336
|
}
|
|
319
337
|
}
|
|
320
338
|
catch (error) {
|
|
321
339
|
console.error(error instanceof Error ? error.message : String(error));
|
|
322
340
|
}
|
|
323
|
-
if (!parsed.full)
|
|
341
|
+
if (!parsed.full || extensionFailureIsTerminal)
|
|
324
342
|
return extensionInstalled ? 0 : 1;
|
|
325
343
|
let localInstalled = false;
|
|
326
344
|
try {
|
package/dist/lifecycle.js
CHANGED
|
@@ -5,7 +5,7 @@ import { stdin as input, stdout as output } from 'node:process';
|
|
|
5
5
|
import { listPluginNames } from '@mearl/client/plugin';
|
|
6
6
|
import { listInstalledProviders } from '@mearl/provider/host';
|
|
7
7
|
import { runGlobalMearlSkillOperation } from './aliSkills.js';
|
|
8
|
-
import { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, } from './packageManager.js';
|
|
8
|
+
import { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js';
|
|
9
9
|
import { MEARL_PROFILES, detectMearlProfiles, } from './profiles.js';
|
|
10
10
|
import { uninstallNativeHostArtifacts } from './nativeHost.js';
|
|
11
11
|
import { executableNames, prependCommandPath, runCommand, } from './process.js';
|
|
@@ -17,6 +17,33 @@ const LEGACY_CLOUD_CLIENT = '@mearl/cloud-client';
|
|
|
17
17
|
const UNIFIED_CLIENT = '@mearl/client';
|
|
18
18
|
const PROFILE_ORDER = ['local', 'cloud', 'mcp'];
|
|
19
19
|
const CLIENT_PROFILES = ['local', 'cloud'];
|
|
20
|
+
function autoUpdateClientInstallation(env) {
|
|
21
|
+
const root = env.MEARL_AUTO_UPDATE_CLIENT_ROOT;
|
|
22
|
+
if (!root)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
const packageRoot = fs.realpathSync(root);
|
|
26
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
27
|
+
if (manifest.name !== UNIFIED_CLIENT)
|
|
28
|
+
return null;
|
|
29
|
+
return resolvePackageManagerInstallation(packageRoot, UNIFIED_CLIENT, { env });
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function autoUpdateProfiles(env) {
|
|
36
|
+
const value = env.MEARL_AUTO_UPDATE_PROFILES;
|
|
37
|
+
if (value === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
const requested = value.split(',');
|
|
40
|
+
if (requested.length === 0 ||
|
|
41
|
+
new Set(requested).size !== requested.length ||
|
|
42
|
+
requested.some(profile => !PROFILE_ORDER.includes(profile))) {
|
|
43
|
+
throw new Error('Invalid automatic update profiles');
|
|
44
|
+
}
|
|
45
|
+
return PROFILE_ORDER.filter(profile => requested.includes(profile));
|
|
46
|
+
}
|
|
20
47
|
async function confirmInTerminal(message) {
|
|
21
48
|
if (!input.isTTY || !output.isTTY)
|
|
22
49
|
return false;
|
|
@@ -153,9 +180,22 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
153
180
|
(childEnv => resolveInvokingPackageManager({ env: childEnv }));
|
|
154
181
|
const runSkill = dependencies.runSkillOperation ?? runGlobalMearlSkillOperation;
|
|
155
182
|
const confirm = dependencies.confirm ?? confirmInTerminal;
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
183
|
+
const detectionEnv = options.operation === 'update' && env.MEARL_AUTO_UPDATE_SEARCH_PATH !== undefined
|
|
184
|
+
? { ...env, PATH: env.MEARL_AUTO_UPDATE_SEARCH_PATH }
|
|
185
|
+
: env;
|
|
186
|
+
const detected = detectProfiles(detectionEnv);
|
|
187
|
+
const requestedProfiles = options.profiles ??
|
|
188
|
+
(options.profile ? [options.profile] : undefined) ??
|
|
189
|
+
(options.operation === 'update' ? autoUpdateProfiles(env) : undefined);
|
|
190
|
+
const autoUpdateClient = options.operation === 'update' ? autoUpdateClientInstallation(env) : null;
|
|
191
|
+
const clientOnly = options.operation === 'update' && env.MEARL_AUTO_UPDATE_CLIENT_ONLY === '1';
|
|
192
|
+
if (clientOnly && !autoUpdateClient) {
|
|
193
|
+
writeLine('A valid global client installation is required for a client-only update.');
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const selected = clientOnly
|
|
197
|
+
? []
|
|
198
|
+
: selectedProfiles(options.operation, requestedProfiles, detected);
|
|
159
199
|
const lifecycleSelected = options.operation === 'uninstall'
|
|
160
200
|
? selected.filter(profile => detected.has(profile))
|
|
161
201
|
: selected;
|
|
@@ -186,6 +226,7 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
186
226
|
return true;
|
|
187
227
|
}
|
|
188
228
|
if (selected.length === 0 &&
|
|
229
|
+
!autoUpdateClient &&
|
|
189
230
|
!(options.operation === 'uninstall' && options.full && hasFullIntegrationTargets)) {
|
|
190
231
|
writeLine('No installed Mearl environment was detected on the current PATH.');
|
|
191
232
|
if (options.operation === 'uninstall')
|
|
@@ -226,6 +267,9 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
226
267
|
.map(profile => MEARL_PROFILES[profile].label)
|
|
227
268
|
.join(', ')}`);
|
|
228
269
|
}
|
|
270
|
+
if (autoUpdateClient && lifecycleSelected.length === 0) {
|
|
271
|
+
writeLine('update targets: client CLI');
|
|
272
|
+
}
|
|
229
273
|
let packagesOk = true;
|
|
230
274
|
if (lifecycleSelected.includes('cloud')) {
|
|
231
275
|
const legacyCloudClient = detectPackage(LEGACY_CLOUD_CLIENT, ['mearl-cloud'], { env });
|
|
@@ -243,8 +287,9 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
243
287
|
}
|
|
244
288
|
}
|
|
245
289
|
}
|
|
246
|
-
const selectedUsesClient =
|
|
247
|
-
|
|
290
|
+
const selectedUsesClient = autoUpdateClient !== null ||
|
|
291
|
+
lifecycleSelected.some(profile => CLIENT_PROFILES.includes(profile));
|
|
292
|
+
const detectedClientCandidate = selectedUsesClient && !autoUpdateClient
|
|
248
293
|
? detectPackage(UNIFIED_CLIENT, ['mearl'], { env })
|
|
249
294
|
: null;
|
|
250
295
|
const detectedClient = detectedClientCandidate?.packageName === UNIFIED_CLIENT ? detectedClientCandidate : null;
|
|
@@ -252,7 +297,8 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
252
297
|
? CLIENT_PROFILES.filter(profile => detected.has(profile) && !lifecycleSelected.includes(profile))
|
|
253
298
|
: [];
|
|
254
299
|
if (selectedUsesClient && options.operation !== 'uninstall') {
|
|
255
|
-
const clientInstallation =
|
|
300
|
+
const clientInstallation = autoUpdateClient ??
|
|
301
|
+
detectedClient ??
|
|
256
302
|
detected.get('local') ??
|
|
257
303
|
detected.get('cloud') ??
|
|
258
304
|
resolveInvokingManager(env);
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type CommandRunner } from './process.js';
|
|
2
|
-
export
|
|
2
|
+
export interface PluginSetupResult {
|
|
3
|
+
entry: string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function setupPlugin(target: string, requestedName?: string): Promise<PluginSetupResult>;
|
|
3
7
|
export declare function ensureOfficialPlugin(name: string, runner?: CommandRunner): Promise<'existing' | 'installed'>;
|
|
4
8
|
export interface PluginUninstallResult {
|
|
5
9
|
entryMissing?: boolean;
|
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, rmdirSync, statSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readlinkSync, realpathSync, rmdirSync, statSync, } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { assertPluginName, findPluginDirectoryEntry, findOfficialPlugin, findPluginEntry, findPluginRegistration, listPluginNames, loadPlugin, OFFICIAL_PLUGIN_MANAGER, registerPlugin, resolvePluginEntry, unregisterPlugin, } from '@mearl/client/plugin';
|
|
@@ -14,12 +14,32 @@ const registrationContext = {
|
|
|
14
14
|
throw new Error('注册时不能报告执行进度');
|
|
15
15
|
},
|
|
16
16
|
};
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
function defaultPluginName(target, entry) {
|
|
18
|
+
const resolvedTarget = resolve(target);
|
|
19
|
+
try {
|
|
20
|
+
if (statSync(resolvedTarget).isDirectory())
|
|
21
|
+
return basename(resolvedTarget);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// resolvePluginEntry 会提供具体的路径错误。
|
|
25
|
+
}
|
|
26
|
+
const entryDirectory = dirname(entry);
|
|
27
|
+
return basename(entryDirectory) === 'scripts'
|
|
28
|
+
? basename(dirname(entryDirectory))
|
|
29
|
+
: basename(entryDirectory);
|
|
30
|
+
}
|
|
31
|
+
export async function setupPlugin(target, requestedName) {
|
|
32
|
+
if (requestedName)
|
|
33
|
+
assertPluginName(requestedName);
|
|
19
34
|
const entry = resolvePluginEntry(target);
|
|
20
35
|
const plugin = await loadPlugin(entry, registrationContext);
|
|
36
|
+
const name = requestedName || plugin.name || defaultPluginName(target, entry);
|
|
37
|
+
assertPluginName(name);
|
|
21
38
|
await ensurePluginRuntime();
|
|
22
|
-
return
|
|
39
|
+
return {
|
|
40
|
+
entry: registerPlugin(name, entry, plugin.description, { toolbox: plugin.toolbox }),
|
|
41
|
+
name,
|
|
42
|
+
};
|
|
23
43
|
}
|
|
24
44
|
export async function ensureOfficialPlugin(name, runner = runCommand) {
|
|
25
45
|
const definition = findOfficialPlugin(name);
|
|
@@ -40,12 +60,57 @@ export async function ensureOfficialPlugin(name, runner = runCommand) {
|
|
|
40
60
|
function globalAliSkill(entry) {
|
|
41
61
|
const globalSkillsDirectory = join(homedir(), '.agents', 'skills');
|
|
42
62
|
const relativeEntry = relative(globalSkillsDirectory, resolve(entry));
|
|
43
|
-
if (isAbsolute(relativeEntry)
|
|
63
|
+
if (!isAbsolute(relativeEntry) &&
|
|
64
|
+
relativeEntry !== '..' &&
|
|
65
|
+
!relativeEntry.startsWith(`..${sep}`)) {
|
|
66
|
+
const [name, ...entrySegments] = relativeEntry.split(sep);
|
|
67
|
+
if (name && entrySegments.length > 0)
|
|
68
|
+
return { directory: join(globalSkillsDirectory, name), name };
|
|
69
|
+
}
|
|
70
|
+
const sourceDirectory = dirname(dirname(resolve(entry)));
|
|
71
|
+
if (basename(dirname(entry)) !== 'scripts')
|
|
72
|
+
return undefined;
|
|
73
|
+
try {
|
|
74
|
+
const source = existsSync(sourceDirectory) ? realpathSync(sourceDirectory) : sourceDirectory;
|
|
75
|
+
for (const name of readdirSync(globalSkillsDirectory)) {
|
|
76
|
+
const directory = join(globalSkillsDirectory, name);
|
|
77
|
+
let target;
|
|
78
|
+
try {
|
|
79
|
+
target = resolve(globalSkillsDirectory, readlinkSync(directory));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (target === sourceDirectory || (existsSync(target) && realpathSync(target) === source)) {
|
|
85
|
+
return { directory, name };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// 全局 Skill 目录不可读时保留本地插件的普通卸载路径。
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
function projectAliSkill(entry, managedBy) {
|
|
95
|
+
if (managedBy !== 'ali-skills')
|
|
96
|
+
return undefined;
|
|
97
|
+
const absolute = resolve(entry);
|
|
98
|
+
const marker = `${sep}.agents${sep}skills${sep}`;
|
|
99
|
+
const markerIndex = absolute.lastIndexOf(marker);
|
|
100
|
+
if (markerIndex <= 0)
|
|
101
|
+
return undefined;
|
|
102
|
+
const cwd = absolute.slice(0, markerIndex);
|
|
103
|
+
if (resolve(cwd) === resolve(homedir()))
|
|
44
104
|
return undefined;
|
|
45
|
-
const [name, ...
|
|
46
|
-
|
|
105
|
+
const [name, scripts, filename, ...remaining] = absolute
|
|
106
|
+
.slice(markerIndex + marker.length)
|
|
107
|
+
.split(sep);
|
|
108
|
+
if (!name ||
|
|
109
|
+
scripts !== 'scripts' ||
|
|
110
|
+
!['main.mjs', 'main.py'].includes(filename) ||
|
|
111
|
+
remaining.length > 0)
|
|
47
112
|
return undefined;
|
|
48
|
-
return { directory: join(
|
|
113
|
+
return { directory: join(cwd, '.agents', 'skills', name), name, cwd };
|
|
49
114
|
}
|
|
50
115
|
export async function uninstallPlugin(name, runner = runCommand) {
|
|
51
116
|
assertPluginName(name);
|
|
@@ -53,7 +118,7 @@ export async function uninstallPlugin(name, runner = runCommand) {
|
|
|
53
118
|
if (!entry)
|
|
54
119
|
return { removed: false, unregisteredNames: [] };
|
|
55
120
|
const entryMissing = !existsSync(entry);
|
|
56
|
-
const managedSkill = globalAliSkill(entry);
|
|
121
|
+
const managedSkill = projectAliSkill(entry, findPluginRegistration(name)?.managedBy) ?? globalAliSkill(entry);
|
|
57
122
|
if (!managedSkill) {
|
|
58
123
|
unregisterPlugin(name);
|
|
59
124
|
return {
|
|
@@ -66,6 +131,18 @@ export async function uninstallPlugin(name, runner = runCommand) {
|
|
|
66
131
|
if (!invocation)
|
|
67
132
|
throw new Error('未找到 ali-skills 或 npx,插件注册已保留。');
|
|
68
133
|
const { directory: skillDirectory, name: skillName } = managedSkill;
|
|
134
|
+
const unregisteredNames = listPluginNames().filter(pluginName => {
|
|
135
|
+
try {
|
|
136
|
+
const candidate = findPluginEntry(pluginName);
|
|
137
|
+
if (!candidate)
|
|
138
|
+
return false;
|
|
139
|
+
const candidateSkill = projectAliSkill(candidate, 'ali-skills') ?? globalAliSkill(candidate);
|
|
140
|
+
return candidateSkill?.directory === skillDirectory;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
69
146
|
let placeholderCreated = false;
|
|
70
147
|
if (!existsSync(skillDirectory)) {
|
|
71
148
|
mkdirSync(dirname(skillDirectory), { recursive: true, mode: 0o700 });
|
|
@@ -80,7 +157,13 @@ export async function uninstallPlugin(name, runner = runCommand) {
|
|
|
80
157
|
}
|
|
81
158
|
let removed;
|
|
82
159
|
try {
|
|
83
|
-
removed = await runner(invocation.command, [
|
|
160
|
+
removed = await runner(invocation.command, [
|
|
161
|
+
...invocation.argsPrefix,
|
|
162
|
+
'remove',
|
|
163
|
+
skillName,
|
|
164
|
+
...(managedSkill.cwd ? [] : ['--global']),
|
|
165
|
+
'--yes',
|
|
166
|
+
], { env: invocation.env, tee: true, ...(managedSkill.cwd ? { cwd: managedSkill.cwd } : {}) });
|
|
84
167
|
}
|
|
85
168
|
catch (error) {
|
|
86
169
|
throw new Error(`ali-skills remove ${skillName} 执行失败:${error instanceof Error ? error.message : String(error)};插件注册已保留。`, { cause: error });
|
|
@@ -98,15 +181,6 @@ export async function uninstallPlugin(name, runner = runCommand) {
|
|
|
98
181
|
if (removed.code !== 0) {
|
|
99
182
|
throw new Error(`ali-skills remove ${skillName} 失败(exit ${removed.code});插件注册已保留。`);
|
|
100
183
|
}
|
|
101
|
-
const unregisteredNames = listPluginNames().filter(pluginName => {
|
|
102
|
-
try {
|
|
103
|
-
const candidate = findPluginEntry(pluginName);
|
|
104
|
-
return candidate !== undefined && globalAliSkill(candidate)?.directory === skillDirectory;
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
184
|
for (const pluginName of unregisteredNames)
|
|
111
185
|
unregisterPlugin(pluginName);
|
|
112
186
|
return {
|
|
@@ -140,12 +214,12 @@ export function parseInstalledPluginPaths(output) {
|
|
|
140
214
|
const value = item[1].trim().replaceAll('\\', '/');
|
|
141
215
|
const skillPath = resolve(value.startsWith('~/') ? join(homedir(), value.slice(2)) : value);
|
|
142
216
|
if (dirname(skillPath) !== join(homedir(), '.agents', 'skills')) {
|
|
143
|
-
throw new Error(`无法识别全局 skill 安装路径: ${item[1]}。请使用 setup plugin
|
|
217
|
+
throw new Error(`无法识别全局 skill 安装路径: ${item[1]}。请使用 setup plugin [name] <path> 注册。`);
|
|
144
218
|
}
|
|
145
219
|
paths.push(skillPath);
|
|
146
220
|
}
|
|
147
221
|
if (paths.length !== expected)
|
|
148
|
-
throw new Error('Ali Skills 安装结果格式无法识别,请使用 setup plugin
|
|
222
|
+
throw new Error('Ali Skills 安装结果格式无法识别,请使用 setup plugin [name] <path> 注册。');
|
|
149
223
|
return [...new Set(paths)];
|
|
150
224
|
}
|
|
151
225
|
function permitsEmptyInstallResult(args, output) {
|
|
@@ -172,7 +246,7 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
|
|
|
172
246
|
if (paths.length === 0) {
|
|
173
247
|
if (permitsEmptyInstallResult(args, output))
|
|
174
248
|
return 0;
|
|
175
|
-
throw new Error('Ali Skills 安装成功但无法识别安装结果,请使用 setup plugin
|
|
249
|
+
throw new Error('Ali Skills 安装成功但无法识别安装结果,请使用 setup plugin [name] <path> 注册。');
|
|
176
250
|
}
|
|
177
251
|
const plugins = new Map();
|
|
178
252
|
for (const skillPath of paths) {
|
|
@@ -182,19 +256,26 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
|
|
|
182
256
|
if (!entry)
|
|
183
257
|
continue;
|
|
184
258
|
const plugin = await loadPlugin(entry, registrationContext);
|
|
185
|
-
const name = plugin.name
|
|
259
|
+
const name = plugin.name || basename(skillPath);
|
|
186
260
|
assertPluginName(name);
|
|
187
261
|
if (plugins.has(name))
|
|
188
|
-
throw new Error(`多个 skill 使用插件名称 ${name}
|
|
189
|
-
plugins.set(name, {
|
|
262
|
+
throw new Error(`多个 skill 使用插件名称 ${name},请调整入口的 name metadata。`);
|
|
263
|
+
plugins.set(name, {
|
|
264
|
+
entry,
|
|
265
|
+
description: plugin.description,
|
|
266
|
+
...(plugin.toolbox ? { toolbox: plugin.toolbox } : {}),
|
|
267
|
+
});
|
|
190
268
|
}
|
|
191
269
|
if (plugins.size === 0) {
|
|
192
|
-
console.error('未找到 scripts/main.mjs 或 scripts/main.py 插件入口;自定义入口可用 setup plugin
|
|
270
|
+
console.error('未找到 scripts/main.mjs 或 scripts/main.py 插件入口;自定义入口可用 setup plugin [name] <path> 注册。');
|
|
193
271
|
return 0;
|
|
194
272
|
}
|
|
195
273
|
await ensurePluginRuntime(runner);
|
|
196
|
-
for (const [name, { entry, description }] of plugins) {
|
|
197
|
-
registerPlugin(name, entry, description, {
|
|
274
|
+
for (const [name, { entry, description, toolbox }] of plugins) {
|
|
275
|
+
registerPlugin(name, entry, description, {
|
|
276
|
+
managedBy: options.registrationManagedBy,
|
|
277
|
+
...(toolbox ? { toolbox } : {}),
|
|
278
|
+
});
|
|
198
279
|
console.error(`Plugin ${name} registered: ${entry}\nRun: mearl ${name} --help`);
|
|
199
280
|
}
|
|
200
281
|
return 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/setup",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.0",
|
|
4
4
|
"description": "One-shot environment setup and configuration for Mearl",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"types": "./dist/packageManager.d.ts",
|
|
23
23
|
"import": "./dist/packageManager.js"
|
|
24
24
|
},
|
|
25
|
+
"./plugin": {
|
|
26
|
+
"types": "./dist/plugin.d.ts",
|
|
27
|
+
"import": "./dist/plugin.js"
|
|
28
|
+
},
|
|
25
29
|
"./native-host": {
|
|
26
30
|
"types": "./dist/nativeHost.d.ts",
|
|
27
31
|
"import": "./dist/nativeHost.js"
|
|
@@ -61,8 +65,8 @@
|
|
|
61
65
|
"commander": "^15.0.0",
|
|
62
66
|
"extract-zip": "^2.0.1",
|
|
63
67
|
"ws": "^8.18.3",
|
|
64
|
-
"@mearl/client": "2.
|
|
65
|
-
"@mearl/provider": "2.
|
|
68
|
+
"@mearl/client": "2.19.0",
|
|
69
|
+
"@mearl/provider": "2.19.0"
|
|
66
70
|
},
|
|
67
71
|
"devDependencies": {
|
|
68
72
|
"@types/node": "^24.9.1",
|