@mearl/setup 2.18.0 → 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 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 <name> <path>
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 yuque /absolute/path/to/yuque-doc-fetch
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`,相对路径基于调用工作目录解析。Python 插件要求 `python3 >= 3.9`,可通过 `MEARL_PYTHON` 指定解释器路径;setup 只验证入口可加载,不安装 Python,也不执行 `pip install`。
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 的 `mearl_plugin` 调用。已注册插件的日常调用不执行安装或版本检查;只有缺失的 `buc` 或 `tdbank` 会触发一次按需安装。
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`、启用远程调试并允许连接,然后等待安装完成。无 TTY
152
- 环境使用 `npx @mearl/setup extension --yes`;它只尝试一次。无法确认 setup 管理来源且
153
- CDP 不可用时不会下载或修改插件目录,打开 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;
@@ -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 || /Method not (?:available|found)|wasn't found/i.test(message)) {
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 <name> <path> Register an existing plugin entry
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 entry = await (dependencies.registerPlugin ?? setupPlugin)(parsed.pluginName, parsed.pluginPath);
261
- console.error(`Plugin ${parsed.pluginName} registered: ${entry}`);
262
- console.error(`Run: mearl ${parsed.pluginName} --help`);
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 pending: ${result.cdpError}`);
317
- console.error('Open chrome://inspect/#remote-debugging, enable remote debugging, approve the connection, then rerun: npx @mearl/setup extension');
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 detected = detectProfiles(env);
157
- const requestedProfiles = options.profiles ?? (options.profile ? [options.profile] : undefined);
158
- const selected = selectedProfiles(options.operation, requestedProfiles, detected);
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 = lifecycleSelected.some(profile => CLIENT_PROFILES.includes(profile));
247
- const detectedClientCandidate = selectedUsesClient
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 = detectedClient ??
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 declare function setupPlugin(name: string, target: string): Promise<string>;
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
- export async function setupPlugin(name, target) {
18
- assertPluginName(name);
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 registerPlugin(name, entry, plugin.description, { toolbox: plugin.toolbox });
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) || relativeEntry === '..' || relativeEntry.startsWith(`..${sep}`))
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, ...entrySegments] = relativeEntry.split(sep);
46
- if (!name || entrySegments.length === 0)
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(globalSkillsDirectory, name), name };
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, [...invocation.argsPrefix, 'remove', skillName, '--global', '--yes'], { env: invocation.env, tee: true });
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 <name> <path> 注册。`);
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 <name> <path> 注册。');
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 <name> <path> 注册。');
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,10 +256,10 @@ 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 ?? basename(skillPath);
259
+ const name = plugin.name || basename(skillPath);
186
260
  assertPluginName(name);
187
261
  if (plugins.has(name))
188
- throw new Error(`多个 skill 使用插件名称 ${name},请通过 setup plugin <name> <path> 分别注册。`);
262
+ throw new Error(`多个 skill 使用插件名称 ${name},请调整入口的 name metadata。`);
189
263
  plugins.set(name, {
190
264
  entry,
191
265
  description: plugin.description,
@@ -193,7 +267,7 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
193
267
  });
194
268
  }
195
269
  if (plugins.size === 0) {
196
- console.error('未找到 scripts/main.mjs 或 scripts/main.py 插件入口;自定义入口可用 setup plugin <name> <path> 注册。');
270
+ console.error('未找到 scripts/main.mjs 或 scripts/main.py 插件入口;自定义入口可用 setup plugin [name] <path> 注册。');
197
271
  return 0;
198
272
  }
199
273
  await ensurePluginRuntime(runner);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/setup",
3
- "version": "2.18.0",
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.18.0",
65
- "@mearl/provider": "2.18.0"
68
+ "@mearl/client": "2.19.0",
69
+ "@mearl/provider": "2.19.0"
66
70
  },
67
71
  "devDependencies": {
68
72
  "@types/node": "^24.9.1",