@42ailab/42plugin 0.1.18 → 0.1.20
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/package.json +1 -1
- package/src/api.ts +26 -4
- package/src/cli.ts +0 -2
- package/src/commands/index.ts +0 -1
- package/src/commands/install.ts +79 -31
- package/src/commands/publish.ts +1 -0
- package/src/commands/search.ts +84 -28
- package/src/commands/uninstall.ts +119 -34
- package/src/db.ts +33 -15
- package/src/types.ts +13 -1
- package/src/commands/completion.ts +0 -210
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -261,18 +261,20 @@ class ApiClient {
|
|
|
261
261
|
// ==========================================================================
|
|
262
262
|
|
|
263
263
|
/**
|
|
264
|
-
*
|
|
264
|
+
* 搜索插件和套包
|
|
265
265
|
* 使用 CLI 专用端点 /cli/v1/search
|
|
266
|
-
* 响应直接返回 camelCase
|
|
266
|
+
* 响应直接返回 camelCase
|
|
267
267
|
*/
|
|
268
268
|
async search(params: {
|
|
269
269
|
q: string;
|
|
270
|
+
searchType?: 'plugin' | 'kit' | 'all';
|
|
270
271
|
pluginType?: string;
|
|
271
272
|
page?: number;
|
|
272
273
|
perPage?: number;
|
|
273
274
|
}): Promise<SearchResponse> {
|
|
274
275
|
const searchParams = new URLSearchParams();
|
|
275
276
|
searchParams.set('q', params.q);
|
|
277
|
+
if (params.searchType) searchParams.set('search_type', params.searchType);
|
|
276
278
|
if (params.pluginType) searchParams.set('type', params.pluginType);
|
|
277
279
|
if (params.page) searchParams.set('page', String(params.page));
|
|
278
280
|
if (params.perPage) searchParams.set('per_page', String(params.perPage));
|
|
@@ -302,7 +304,7 @@ class ApiClient {
|
|
|
302
304
|
|
|
303
305
|
// CLI 专用端点直接返回 camelCase 格式
|
|
304
306
|
interface CliSearchResponse {
|
|
305
|
-
|
|
307
|
+
plugins: Array<{
|
|
306
308
|
fullName: string;
|
|
307
309
|
name: string;
|
|
308
310
|
title: string | null;
|
|
@@ -312,9 +314,19 @@ class ApiClient {
|
|
|
312
314
|
downloads: number;
|
|
313
315
|
description: string | null;
|
|
314
316
|
}>;
|
|
317
|
+
kits: Array<{
|
|
318
|
+
fullName: string;
|
|
319
|
+
name: string;
|
|
320
|
+
author: string;
|
|
321
|
+
pluginCount: number;
|
|
322
|
+
downloads: number;
|
|
323
|
+
description: string | null;
|
|
324
|
+
}>;
|
|
315
325
|
pagination: {
|
|
316
326
|
page: number;
|
|
317
327
|
perPage: number;
|
|
328
|
+
totalPlugins: number;
|
|
329
|
+
totalKits: number;
|
|
318
330
|
total: number;
|
|
319
331
|
totalPages: number;
|
|
320
332
|
};
|
|
@@ -323,7 +335,7 @@ class ApiClient {
|
|
|
323
335
|
const apiData = raw as CliSearchResponse;
|
|
324
336
|
|
|
325
337
|
return {
|
|
326
|
-
|
|
338
|
+
plugins: apiData.plugins.map((p) => ({
|
|
327
339
|
fullName: p.fullName,
|
|
328
340
|
name: p.name,
|
|
329
341
|
title: p.title,
|
|
@@ -336,9 +348,19 @@ class ApiClient {
|
|
|
336
348
|
downloads: p.downloads,
|
|
337
349
|
icon: null,
|
|
338
350
|
})),
|
|
351
|
+
kits: apiData.kits.map((k) => ({
|
|
352
|
+
fullName: k.fullName,
|
|
353
|
+
name: k.name,
|
|
354
|
+
author: k.author,
|
|
355
|
+
pluginCount: k.pluginCount,
|
|
356
|
+
downloads: k.downloads,
|
|
357
|
+
description: k.description,
|
|
358
|
+
})),
|
|
339
359
|
pagination: {
|
|
340
360
|
page: apiData.pagination.page,
|
|
341
361
|
perPage: apiData.pagination.perPage,
|
|
362
|
+
totalPlugins: apiData.pagination.totalPlugins,
|
|
363
|
+
totalKits: apiData.pagination.totalKits,
|
|
342
364
|
total: apiData.pagination.total,
|
|
343
365
|
totalPages: apiData.pagination.totalPages,
|
|
344
366
|
},
|
package/src/cli.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
searchCommand,
|
|
10
10
|
listCommand,
|
|
11
11
|
uninstallCommand,
|
|
12
|
-
completionCommand,
|
|
13
12
|
setupCommand,
|
|
14
13
|
publishCommand,
|
|
15
14
|
checkCommand,
|
|
@@ -53,7 +52,6 @@ program.addCommand(listCommand);
|
|
|
53
52
|
program.addCommand(uninstallCommand);
|
|
54
53
|
program.addCommand(publishCommand);
|
|
55
54
|
program.addCommand(checkCommand);
|
|
56
|
-
program.addCommand(completionCommand);
|
|
57
55
|
program.addCommand(setupCommand);
|
|
58
56
|
|
|
59
57
|
// 版本命令
|
package/src/commands/index.ts
CHANGED
|
@@ -7,7 +7,6 @@ export { installCommand } from './install';
|
|
|
7
7
|
export { searchCommand } from './search';
|
|
8
8
|
export { listCommand } from './list';
|
|
9
9
|
export { uninstallCommand } from './uninstall';
|
|
10
|
-
export { completionCommand } from './completion';
|
|
11
10
|
export { setupCommand } from './setup';
|
|
12
11
|
export { publishCommand } from './publish';
|
|
13
12
|
export { checkCommand } from './check';
|
package/src/commands/install.ts
CHANGED
|
@@ -192,43 +192,74 @@ async function installKit(
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
spinner.succeed(`${kitDownload.kit.name} - ${plugins.length} 个插件`);
|
|
195
|
+
|
|
196
|
+
// 超过 42 个插件时提示确认
|
|
197
|
+
if (plugins.length > 42) {
|
|
198
|
+
console.log();
|
|
199
|
+
console.log(chalk.yellow(`⚠ 该套包包含 ${plugins.length} 个插件,数量较多`));
|
|
200
|
+
const shouldContinue = await confirm({
|
|
201
|
+
message: '确定要继续安装吗?',
|
|
202
|
+
default: true,
|
|
203
|
+
});
|
|
204
|
+
if (!shouldContinue) {
|
|
205
|
+
console.log(chalk.gray('已取消安装'));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
195
210
|
console.log();
|
|
196
211
|
|
|
197
212
|
// 获取项目
|
|
198
213
|
const projectPath = options.global ? config.globalDir : process.cwd();
|
|
199
214
|
const project = await getOrCreateProject(projectPath);
|
|
200
215
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
for (const item of plugins) {
|
|
206
|
-
const pluginSpinner = ora(` ${item.plugin.fullName}...`).start();
|
|
207
|
-
|
|
208
|
-
try {
|
|
216
|
+
// 第一阶段:检查所有冲突(并行)
|
|
217
|
+
const conflictCheckSpinner = ora('检查安装冲突...').start();
|
|
218
|
+
const conflictResults = await Promise.all(
|
|
219
|
+
plugins.map(async (item) => {
|
|
209
220
|
const downloadInfo = item.download;
|
|
210
221
|
const linkPath = path.join(projectPath, downloadInfo.installPath);
|
|
211
|
-
|
|
212
|
-
// 检查是否有同名冲突
|
|
213
222
|
const conflictPlugin = await checkInstallConflict(projectPath, linkPath, downloadInfo.fullName);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
223
|
+
return { item, linkPath, conflictPlugin };
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
conflictCheckSpinner.stop();
|
|
227
|
+
|
|
228
|
+
// 第二阶段:处理冲突(需要用户交互,只能串行)
|
|
229
|
+
const toInstall: typeof conflictResults = [];
|
|
230
|
+
for (const result of conflictResults) {
|
|
231
|
+
if (result.conflictPlugin) {
|
|
232
|
+
console.log(chalk.yellow(`⚠ 路径冲突: ${result.item.download.installPath}`));
|
|
233
|
+
console.log(chalk.gray(` 已安装: ${result.conflictPlugin}`));
|
|
234
|
+
console.log(chalk.gray(` 新插件: ${result.item.plugin.fullName}`));
|
|
235
|
+
|
|
236
|
+
const shouldReplace = await confirm({
|
|
237
|
+
message: `是否替换 ${result.conflictPlugin}?`,
|
|
238
|
+
default: false,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (shouldReplace) {
|
|
242
|
+
await removeInstallation(projectPath, result.conflictPlugin);
|
|
243
|
+
toInstall.push(result);
|
|
244
|
+
} else {
|
|
245
|
+
console.log(chalk.gray(' 已跳过'));
|
|
231
246
|
}
|
|
247
|
+
} else {
|
|
248
|
+
toInstall.push(result);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (toInstall.length === 0) {
|
|
253
|
+
console.log(chalk.yellow('没有需要安装的插件'));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 第三阶段:并行安装
|
|
258
|
+
const installSpinner = ora(`安装 ${toInstall.length} 个插件...`).start();
|
|
259
|
+
|
|
260
|
+
const results = await Promise.allSettled(
|
|
261
|
+
toInstall.map(async ({ item, linkPath }) => {
|
|
262
|
+
const downloadInfo = item.download;
|
|
232
263
|
|
|
233
264
|
const { cachePath, fromCache } = await resolveCachePath(
|
|
234
265
|
downloadInfo,
|
|
@@ -253,13 +284,30 @@ async function installKit(
|
|
|
253
284
|
api.recordInstall(downloadInfo.fullName).catch(() => {});
|
|
254
285
|
}
|
|
255
286
|
|
|
256
|
-
|
|
257
|
-
|
|
287
|
+
return { downloadInfo, fromCache };
|
|
288
|
+
})
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
installSpinner.stop();
|
|
292
|
+
|
|
293
|
+
// 显示结果
|
|
294
|
+
let installed = 0;
|
|
295
|
+
let failed = 0;
|
|
296
|
+
|
|
297
|
+
for (let i = 0; i < results.length; i++) {
|
|
298
|
+
const result = results[i];
|
|
299
|
+
const item = toInstall[i].item;
|
|
300
|
+
|
|
301
|
+
if (result.status === 'fulfilled') {
|
|
302
|
+
const { downloadInfo, fromCache } = result.value;
|
|
303
|
+
console.log(
|
|
304
|
+
chalk.green(' ✓ ') +
|
|
305
|
+
`${getTypeIcon(downloadInfo.type)} ${downloadInfo.fullName}` +
|
|
258
306
|
(fromCache ? chalk.gray(' (cached)') : '')
|
|
259
307
|
);
|
|
260
308
|
installed++;
|
|
261
|
-
}
|
|
262
|
-
|
|
309
|
+
} else {
|
|
310
|
+
console.log(chalk.red(` ✗ ${item.plugin.fullName}: ${result.reason?.message || '未知错误'}`));
|
|
263
311
|
failed++;
|
|
264
312
|
}
|
|
265
313
|
}
|
package/src/commands/publish.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { ValidationError, UploadError, AuthRequiredError } from '../errors';
|
|
|
10
10
|
import { getTypeIcon } from '../utils';
|
|
11
11
|
|
|
12
12
|
export const publishCommand = new Command('publish')
|
|
13
|
+
.alias('pub')
|
|
13
14
|
.description('发布插件到 42plugin')
|
|
14
15
|
.argument('[path]', '插件路径(文件或目录)', '.')
|
|
15
16
|
.option('--dry-run', '仅验证,不实际发布')
|
package/src/commands/search.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* search 命令 -
|
|
2
|
+
* search 命令 - 搜索插件和套包
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Command } from 'commander';
|
|
@@ -8,11 +8,11 @@ import ora from 'ora';
|
|
|
8
8
|
import { checkbox, confirm } from '@inquirer/prompts';
|
|
9
9
|
import { api } from '../api';
|
|
10
10
|
import { getTypeIcon, getTypeLabel } from '../utils';
|
|
11
|
-
import type { SearchResult } from '../types';
|
|
11
|
+
import type { SearchResult, KitSearchResult } from '../types';
|
|
12
12
|
|
|
13
13
|
const DEFAULT_PAGE_SIZE = 7;
|
|
14
14
|
|
|
15
|
-
function
|
|
15
|
+
function displayPluginResults(items: SearchResult[]): void {
|
|
16
16
|
for (const item of items) {
|
|
17
17
|
const icon = getTypeIcon(item.type);
|
|
18
18
|
const typeLabel = getTypeLabel(item.type);
|
|
@@ -35,10 +35,28 @@ function displayResults(items: SearchResult[], startIndex: number = 0): void {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function displayKitResults(items: KitSearchResult[]): void {
|
|
39
|
+
for (const item of items) {
|
|
40
|
+
console.log(`📦 ${chalk.magenta.bold(item.fullName)} ${chalk.gray('[套包]')}`);
|
|
41
|
+
|
|
42
|
+
if (item.description) {
|
|
43
|
+
const desc = item.description.length > 80
|
|
44
|
+
? item.description.slice(0, 80) + '...'
|
|
45
|
+
: item.description;
|
|
46
|
+
console.log(chalk.gray(` ${desc}`));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(chalk.gray(` ${item.pluginCount} 个插件 · ${item.downloads} 下载`));
|
|
50
|
+
console.log();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
export const searchCommand = new Command('search')
|
|
39
|
-
.description('
|
|
55
|
+
.description('搜索插件和套包')
|
|
40
56
|
.argument('<keyword>', '搜索关键词')
|
|
41
|
-
.option('-t, --type <type>', '
|
|
57
|
+
.option('-t, --type <type>', '筛选插件类型 (skill, agent, command, hook, mcp)')
|
|
58
|
+
.option('-k, --kit', '只搜索套包')
|
|
59
|
+
.option('-p, --plugin', '只搜索插件')
|
|
42
60
|
.option('-l, --limit <n>', '每页数量', String(DEFAULT_PAGE_SIZE))
|
|
43
61
|
.option('-a, --all', '显示所有结果')
|
|
44
62
|
.option('--json', '输出 JSON 格式')
|
|
@@ -49,14 +67,19 @@ export const searchCommand = new Command('search')
|
|
|
49
67
|
|
|
50
68
|
try {
|
|
51
69
|
const pageSize = parseInt(options.limit) || DEFAULT_PAGE_SIZE;
|
|
52
|
-
let
|
|
70
|
+
let allPlugins: SearchResult[] = [];
|
|
71
|
+
let allKits: KitSearchResult[] = [];
|
|
53
72
|
let currentPage = 1;
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
|
|
74
|
+
// 确定搜索类型
|
|
75
|
+
let searchType: 'plugin' | 'kit' | 'all' = 'all';
|
|
76
|
+
if (options.kit) searchType = 'kit';
|
|
77
|
+
else if (options.plugin || options.type) searchType = 'plugin';
|
|
56
78
|
|
|
57
79
|
// 首次搜索
|
|
58
80
|
const result = await api.search({
|
|
59
81
|
q: keyword,
|
|
82
|
+
searchType,
|
|
60
83
|
pluginType: options.type,
|
|
61
84
|
perPage: pageSize,
|
|
62
85
|
page: currentPage,
|
|
@@ -69,25 +92,41 @@ export const searchCommand = new Command('search')
|
|
|
69
92
|
return;
|
|
70
93
|
}
|
|
71
94
|
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
const totalPlugins = result.pagination.totalPlugins;
|
|
96
|
+
const totalKits = result.pagination.totalKits;
|
|
97
|
+
const total = result.pagination.total;
|
|
98
|
+
|
|
99
|
+
if (total === 0) {
|
|
100
|
+
console.log(chalk.yellow('未找到匹配的结果'));
|
|
74
101
|
return;
|
|
75
102
|
}
|
|
76
103
|
|
|
77
|
-
|
|
78
|
-
|
|
104
|
+
// 显示统计
|
|
105
|
+
const stats: string[] = [];
|
|
106
|
+
if (totalPlugins > 0) stats.push(`${totalPlugins} 个插件`);
|
|
107
|
+
if (totalKits > 0) stats.push(`${totalKits} 个套包`);
|
|
108
|
+
console.log(chalk.gray(`找到 ${stats.join(',')}:\n`));
|
|
109
|
+
|
|
110
|
+
allPlugins = result.plugins;
|
|
111
|
+
allKits = result.kits;
|
|
112
|
+
|
|
113
|
+
// 先显示套包
|
|
114
|
+
if (allKits.length > 0) {
|
|
115
|
+
displayKitResults(allKits);
|
|
116
|
+
}
|
|
79
117
|
|
|
80
|
-
|
|
118
|
+
// 再显示插件
|
|
119
|
+
if (allPlugins.length > 0) {
|
|
120
|
+
displayPluginResults(allPlugins);
|
|
121
|
+
}
|
|
81
122
|
|
|
82
|
-
|
|
83
|
-
displayResults(allResults);
|
|
84
|
-
displayedCount = allResults.length;
|
|
123
|
+
let displayedCount = allPlugins.length + allKits.length;
|
|
85
124
|
|
|
86
125
|
// 如果有更多结果,根据模式决定是否继续
|
|
87
|
-
while (displayedCount <
|
|
126
|
+
while (displayedCount < total) {
|
|
88
127
|
// 非 --all 模式下询问用户
|
|
89
128
|
if (!options.all) {
|
|
90
|
-
const remaining =
|
|
129
|
+
const remaining = total - displayedCount;
|
|
91
130
|
|
|
92
131
|
const loadMore = await confirm({
|
|
93
132
|
message: `还有 ${remaining} 个结果,是否继续查看?`,
|
|
@@ -104,6 +143,7 @@ export const searchCommand = new Command('search')
|
|
|
104
143
|
|
|
105
144
|
const moreResult = await api.search({
|
|
106
145
|
q: keyword,
|
|
146
|
+
searchType,
|
|
107
147
|
pluginType: options.type,
|
|
108
148
|
perPage: pageSize,
|
|
109
149
|
page: currentPage,
|
|
@@ -111,7 +151,7 @@ export const searchCommand = new Command('search')
|
|
|
111
151
|
|
|
112
152
|
moreSpinner.stop();
|
|
113
153
|
|
|
114
|
-
if (moreResult.
|
|
154
|
+
if (moreResult.plugins.length === 0 && moreResult.kits.length === 0) {
|
|
115
155
|
break;
|
|
116
156
|
}
|
|
117
157
|
|
|
@@ -119,21 +159,37 @@ export const searchCommand = new Command('search')
|
|
|
119
159
|
console.log();
|
|
120
160
|
}
|
|
121
161
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
162
|
+
if (moreResult.kits.length > 0) {
|
|
163
|
+
displayKitResults(moreResult.kits);
|
|
164
|
+
allKits = [...allKits, ...moreResult.kits];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (moreResult.plugins.length > 0) {
|
|
168
|
+
displayPluginResults(moreResult.plugins);
|
|
169
|
+
allPlugins = [...allPlugins, ...moreResult.plugins];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
displayedCount += moreResult.plugins.length + moreResult.kits.length;
|
|
125
173
|
}
|
|
126
174
|
|
|
127
175
|
// 交互式选择安装
|
|
128
|
-
if (options.interactive &&
|
|
176
|
+
if (options.interactive && (allPlugins.length > 0 || allKits.length > 0)) {
|
|
129
177
|
console.log();
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
178
|
+
|
|
179
|
+
// 构建选项列表(套包在前,插件在后)
|
|
180
|
+
const choices = [
|
|
181
|
+
...allKits.map((item) => ({
|
|
182
|
+
name: `📦 ${item.fullName} - ${item.description?.slice(0, 30) || `${item.pluginCount} 个插件`}`,
|
|
183
|
+
value: item.fullName,
|
|
184
|
+
})),
|
|
185
|
+
...allPlugins.map((item) => ({
|
|
186
|
+
name: `${getTypeIcon(item.type)} ${item.fullName} - ${item.title || item.slogan || item.description?.slice(0, 30) || ''}`,
|
|
187
|
+
value: item.fullName,
|
|
188
|
+
})),
|
|
189
|
+
];
|
|
134
190
|
|
|
135
191
|
const selected = await checkbox({
|
|
136
|
-
message: '
|
|
192
|
+
message: '选择要安装的插件/套包 (空格选择,回车确认):',
|
|
137
193
|
choices,
|
|
138
194
|
});
|
|
139
195
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* uninstall 命令 -
|
|
2
|
+
* uninstall 命令 - 卸载插件或套包
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Command } from 'commander';
|
|
@@ -8,55 +8,140 @@ import ora from 'ora';
|
|
|
8
8
|
import { api } from '../api';
|
|
9
9
|
import { getInstallations, removeInstallation, removeLink, removeCache } from '../db';
|
|
10
10
|
import { parseTarget } from '../utils';
|
|
11
|
+
import { TargetType } from '../types';
|
|
11
12
|
|
|
12
13
|
export const uninstallCommand = new Command('uninstall')
|
|
13
14
|
.alias('rm')
|
|
14
|
-
.description('
|
|
15
|
-
.argument('<target>', '插件名 (author/name)')
|
|
15
|
+
.description('卸载插件或套包')
|
|
16
|
+
.argument('<target>', '插件名 (author/name) 或套包名 (author/kit/slug)')
|
|
16
17
|
.option('--purge', '同时清除缓存')
|
|
17
18
|
.action(async (target, options) => {
|
|
18
|
-
// token 已在全局 hook 中注入
|
|
19
|
-
const spinner = ora(`卸载 ${target}...`).start();
|
|
20
|
-
|
|
21
19
|
try {
|
|
22
20
|
const parsed = parseTarget(target);
|
|
23
21
|
const projectPath = process.cwd();
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
process.exit(1);
|
|
23
|
+
if (parsed.type === TargetType.Kit) {
|
|
24
|
+
// 卸载套包:批量卸载所有来自该套包的插件
|
|
25
|
+
await uninstallKit(projectPath, parsed.fullName, options.purge);
|
|
26
|
+
} else {
|
|
27
|
+
// 卸载单个插件
|
|
28
|
+
await uninstallPlugin(projectPath, parsed.fullName, options.purge);
|
|
32
29
|
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(chalk.red((error as Error).message));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 卸载单个插件
|
|
38
|
+
*/
|
|
39
|
+
async function uninstallPlugin(
|
|
40
|
+
projectPath: string,
|
|
41
|
+
fullName: string,
|
|
42
|
+
purge: boolean
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const spinner = ora(`卸载 ${fullName}...`).start();
|
|
45
|
+
|
|
46
|
+
// 查找安装记录
|
|
47
|
+
const installations = await getInstallations(projectPath);
|
|
48
|
+
const installation = installations.find((i) => i.fullName === fullName);
|
|
49
|
+
|
|
50
|
+
if (!installation) {
|
|
51
|
+
spinner.fail(`未找到已安装的插件: ${fullName}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 移除链接
|
|
56
|
+
await removeLink(installation.linkPath);
|
|
57
|
+
|
|
58
|
+
// 移除安装记录
|
|
59
|
+
await removeInstallation(projectPath, fullName);
|
|
60
|
+
|
|
61
|
+
// 同步卸载记录(静默)
|
|
62
|
+
if (api.isAuthenticated()) {
|
|
63
|
+
const parts = fullName.split('/');
|
|
64
|
+
const author = parts[0];
|
|
65
|
+
const name = parts.slice(1).join('/');
|
|
66
|
+
api.removeInstallRecord(author, name).catch(() => {});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 清除缓存
|
|
70
|
+
if (purge) {
|
|
71
|
+
const removed = await removeCache(fullName, installation.version);
|
|
72
|
+
if (removed) {
|
|
73
|
+
spinner.succeed(`已卸载 ${fullName}(含缓存)`);
|
|
74
|
+
} else {
|
|
75
|
+
spinner.succeed(`已卸载 ${fullName}`);
|
|
76
|
+
console.log(chalk.gray(' 缓存已不存在'));
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
spinner.succeed(`已卸载 ${fullName}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 卸载套包(批量卸载所有来自该套包的插件)
|
|
85
|
+
*/
|
|
86
|
+
async function uninstallKit(
|
|
87
|
+
projectPath: string,
|
|
88
|
+
kitFullName: string,
|
|
89
|
+
purge: boolean
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
const spinner = ora(`查找套包 ${kitFullName} 的插件...`).start();
|
|
92
|
+
|
|
93
|
+
// 查找所有来自该套包的安装记录
|
|
94
|
+
const installations = await getInstallations(projectPath);
|
|
95
|
+
const kitInstallations = installations.filter((i) => i.sourceKit === kitFullName);
|
|
96
|
+
|
|
97
|
+
if (kitInstallations.length === 0) {
|
|
98
|
+
spinner.fail(`未找到来自套包 ${kitFullName} 的已安装插件`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
spinner.text = `卸载 ${kitInstallations.length} 个插件...`;
|
|
33
103
|
|
|
104
|
+
// 批量卸载
|
|
105
|
+
let successCount = 0;
|
|
106
|
+
let failCount = 0;
|
|
107
|
+
|
|
108
|
+
for (const installation of kitInstallations) {
|
|
109
|
+
try {
|
|
34
110
|
// 移除链接
|
|
35
111
|
await removeLink(installation.linkPath);
|
|
36
112
|
|
|
37
113
|
// 移除安装记录
|
|
38
|
-
await removeInstallation(projectPath,
|
|
39
|
-
|
|
40
|
-
// 同步卸载记录(静默)
|
|
41
|
-
if (api.isAuthenticated()) {
|
|
42
|
-
api.removeInstallRecord(parsed.author, parsed.name).catch(() => {});
|
|
43
|
-
}
|
|
114
|
+
await removeInstallation(projectPath, installation.fullName);
|
|
44
115
|
|
|
45
116
|
// 清除缓存
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
if (removed) {
|
|
49
|
-
spinner.succeed(`已卸载 ${target}(含缓存)`);
|
|
50
|
-
} else {
|
|
51
|
-
spinner.succeed(`已卸载 ${target}`);
|
|
52
|
-
console.log(chalk.gray(' 缓存已不存在'));
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
spinner.succeed(`已卸载 ${target}`);
|
|
117
|
+
if (purge) {
|
|
118
|
+
await removeCache(installation.fullName, installation.version);
|
|
56
119
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
120
|
+
|
|
121
|
+
successCount++;
|
|
122
|
+
} catch {
|
|
123
|
+
failCount++;
|
|
61
124
|
}
|
|
62
|
-
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 同步卸载记录(静默)
|
|
128
|
+
if (api.isAuthenticated()) {
|
|
129
|
+
const parts = kitFullName.split('/');
|
|
130
|
+
const author = parts[0];
|
|
131
|
+
const slug = parts[2];
|
|
132
|
+
api.removeInstallRecord(author, `kit/${slug}`).catch(() => {});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
spinner.stop();
|
|
136
|
+
|
|
137
|
+
if (failCount === 0) {
|
|
138
|
+
console.log(chalk.green(`✓ 已卸载套包 ${kitFullName}`));
|
|
139
|
+
console.log(chalk.gray(` 共 ${successCount} 个插件`));
|
|
140
|
+
if (purge) {
|
|
141
|
+
console.log(chalk.gray(' 缓存已清除'));
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
console.log(chalk.yellow(`⚠ 部分卸载完成`));
|
|
145
|
+
console.log(chalk.gray(` 成功: ${successCount}, 失败: ${failCount}`));
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/db.ts
CHANGED
|
@@ -467,7 +467,8 @@ export async function downloadAndExtract(
|
|
|
467
467
|
url: string,
|
|
468
468
|
expectedChecksum: string,
|
|
469
469
|
fullName: string,
|
|
470
|
-
version: string
|
|
470
|
+
version: string,
|
|
471
|
+
pluginType: string
|
|
471
472
|
): Promise<string> {
|
|
472
473
|
const parts = fullName.split('/');
|
|
473
474
|
const targetDir = path.join(config.cacheDir, ...parts, version);
|
|
@@ -550,7 +551,7 @@ export async function downloadAndExtract(
|
|
|
550
551
|
await fs.unlink(tempFile).catch(() => {});
|
|
551
552
|
|
|
552
553
|
// 返回最终路径(如果只有一个子目录则进入)
|
|
553
|
-
return resolveFinalPath(targetDir);
|
|
554
|
+
return resolveFinalPath(targetDir, pluginType);
|
|
554
555
|
} catch (error) {
|
|
555
556
|
await fs.rm(targetDir, { recursive: true, force: true }).catch(() => {});
|
|
556
557
|
throw error;
|
|
@@ -559,8 +560,9 @@ export async function downloadAndExtract(
|
|
|
559
560
|
|
|
560
561
|
/**
|
|
561
562
|
* 修正旧缓存路径(如果是目录但应该是文件)
|
|
563
|
+
* @param pluginType 插件类型:skill 需要目录,agent/command 需要文件
|
|
562
564
|
*/
|
|
563
|
-
async function correctCachePath(cachePath: string): Promise<string> {
|
|
565
|
+
async function correctCachePath(cachePath: string, pluginType: string): Promise<string> {
|
|
564
566
|
try {
|
|
565
567
|
const stat = await fs.stat(cachePath);
|
|
566
568
|
if (!stat.isDirectory()) {
|
|
@@ -577,11 +579,16 @@ async function correctCachePath(cachePath: string): Promise<string> {
|
|
|
577
579
|
// 如果只有一个子目录(且没有其他文件),递归进入
|
|
578
580
|
if (dirs.length === 1 && nonMetadataFiles.length === 0) {
|
|
579
581
|
const subDir = path.join(cachePath, dirs[0].name);
|
|
580
|
-
return correctCachePath(subDir);
|
|
582
|
+
return correctCachePath(subDir, pluginType);
|
|
581
583
|
}
|
|
582
584
|
|
|
583
|
-
//
|
|
585
|
+
// Skill 类型:保持目录结构(包含 SKILL.md)
|
|
586
|
+
// Agent/Command 类型:返回 .md 文件路径
|
|
584
587
|
if (nonMetadataFiles.length === 1 && dirs.length === 0) {
|
|
588
|
+
if (pluginType === 'skill') {
|
|
589
|
+
// Skill 需要目录,不返回文件
|
|
590
|
+
return cachePath;
|
|
591
|
+
}
|
|
585
592
|
return path.join(cachePath, nonMetadataFiles[0].name);
|
|
586
593
|
}
|
|
587
594
|
|
|
@@ -591,7 +598,11 @@ async function correctCachePath(cachePath: string): Promise<string> {
|
|
|
591
598
|
}
|
|
592
599
|
}
|
|
593
600
|
|
|
594
|
-
|
|
601
|
+
/**
|
|
602
|
+
* 解析最终路径
|
|
603
|
+
* @param pluginType 插件类型:skill 需要目录,agent/command 需要文件
|
|
604
|
+
*/
|
|
605
|
+
async function resolveFinalPath(dir: string, pluginType: string): Promise<string> {
|
|
595
606
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
596
607
|
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith('.'));
|
|
597
608
|
const files = entries.filter((e) => e.isFile() && !e.name.startsWith('.'));
|
|
@@ -603,13 +614,17 @@ async function resolveFinalPath(dir: string): Promise<string> {
|
|
|
603
614
|
const subEntries = await fs.readdir(subDir);
|
|
604
615
|
if (subEntries.length > 0) {
|
|
605
616
|
// 递归查找,处理 agent/command 类型的 name/name.md 结构
|
|
606
|
-
return resolveFinalPath(subDir);
|
|
617
|
+
return resolveFinalPath(subDir, pluginType);
|
|
607
618
|
}
|
|
608
619
|
}
|
|
609
620
|
|
|
610
|
-
//
|
|
611
|
-
//
|
|
621
|
+
// Skill 类型:保持目录结构(包含 SKILL.md)
|
|
622
|
+
// Agent/Command 类型:返回 .md 文件路径
|
|
612
623
|
if (nonMetadataFiles.length === 1 && dirs.length === 0) {
|
|
624
|
+
if (pluginType === 'skill') {
|
|
625
|
+
// Skill 需要目录,不返回文件
|
|
626
|
+
return dir;
|
|
627
|
+
}
|
|
613
628
|
return path.join(dir, nonMetadataFiles[0].name);
|
|
614
629
|
}
|
|
615
630
|
|
|
@@ -670,12 +685,14 @@ export async function createLink(
|
|
|
670
685
|
}
|
|
671
686
|
|
|
672
687
|
export async function removeLink(linkPath: string): Promise<void> {
|
|
688
|
+
// 去掉结尾斜杠,否则 lstat 会跟踪符号链接返回目标状态
|
|
689
|
+
const normalizedPath = linkPath.replace(/\/+$/, '');
|
|
673
690
|
try {
|
|
674
|
-
const stat = await fs.lstat(
|
|
691
|
+
const stat = await fs.lstat(normalizedPath);
|
|
675
692
|
if (stat.isSymbolicLink()) {
|
|
676
|
-
await fs.unlink(
|
|
693
|
+
await fs.unlink(normalizedPath);
|
|
677
694
|
} else if (stat.isDirectory()) {
|
|
678
|
-
await fs.rm(
|
|
695
|
+
await fs.rm(normalizedPath, { recursive: true, force: true });
|
|
679
696
|
}
|
|
680
697
|
} catch {
|
|
681
698
|
// 不存在,忽略
|
|
@@ -701,8 +718,8 @@ export async function resolveCachePath(
|
|
|
701
718
|
// 验证缓存文件是否仍然存在
|
|
702
719
|
try {
|
|
703
720
|
await fs.access(cached.cachePath);
|
|
704
|
-
//
|
|
705
|
-
const correctedPath = await correctCachePath(cached.cachePath);
|
|
721
|
+
// 修正旧缓存的路径(根据类型:skill 需要目录,agent/command 需要文件)
|
|
722
|
+
const correctedPath = await correctCachePath(cached.cachePath, downloadInfo.type);
|
|
706
723
|
// 如果路径被修正,更新缓存记录
|
|
707
724
|
if (correctedPath !== cached.cachePath) {
|
|
708
725
|
await setCache({
|
|
@@ -726,7 +743,8 @@ export async function resolveCachePath(
|
|
|
726
743
|
downloadInfo.downloadUrl,
|
|
727
744
|
downloadInfo.checksum,
|
|
728
745
|
downloadInfo.fullName,
|
|
729
|
-
downloadInfo.version
|
|
746
|
+
downloadInfo.version,
|
|
747
|
+
downloadInfo.type
|
|
730
748
|
);
|
|
731
749
|
|
|
732
750
|
// 更新缓存记录
|
package/src/types.ts
CHANGED
|
@@ -95,11 +95,23 @@ export interface SearchResult {
|
|
|
95
95
|
icon: string | null;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
export interface KitSearchResult {
|
|
99
|
+
fullName: string;
|
|
100
|
+
name: string;
|
|
101
|
+
author: string;
|
|
102
|
+
pluginCount: number;
|
|
103
|
+
downloads: number;
|
|
104
|
+
description: string | null;
|
|
105
|
+
}
|
|
106
|
+
|
|
98
107
|
export interface SearchResponse {
|
|
99
|
-
|
|
108
|
+
plugins: SearchResult[];
|
|
109
|
+
kits: KitSearchResult[];
|
|
100
110
|
pagination: {
|
|
101
111
|
page: number;
|
|
102
112
|
perPage: number;
|
|
113
|
+
totalPlugins: number;
|
|
114
|
+
totalKits: number;
|
|
103
115
|
total: number;
|
|
104
116
|
totalPages: number;
|
|
105
117
|
};
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* completion 命令 - 生成 shell 自动补全脚本
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Command } from 'commander';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
|
|
8
|
-
export const completionCommand = new Command('completion')
|
|
9
|
-
.description('生成 shell 自动补全脚本')
|
|
10
|
-
.argument('<shell>', 'shell 类型 (bash, zsh, fish)')
|
|
11
|
-
.action((shell: string) => {
|
|
12
|
-
switch (shell.toLowerCase()) {
|
|
13
|
-
case 'bash':
|
|
14
|
-
console.log(generateBashCompletion());
|
|
15
|
-
break;
|
|
16
|
-
case 'zsh':
|
|
17
|
-
console.log(generateZshCompletion());
|
|
18
|
-
break;
|
|
19
|
-
case 'fish':
|
|
20
|
-
console.log(generateFishCompletion());
|
|
21
|
-
break;
|
|
22
|
-
default:
|
|
23
|
-
console.error(chalk.red(`不支持的 shell: ${shell}`));
|
|
24
|
-
console.log(chalk.gray('支持的 shell: bash, zsh, fish'));
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
function generateBashCompletion(): string {
|
|
30
|
-
return `# 42plugin bash completion
|
|
31
|
-
# 添加到 ~/.bashrc:
|
|
32
|
-
# eval "$(42plugin completion bash)"
|
|
33
|
-
# 或:
|
|
34
|
-
# 42plugin completion bash >> ~/.bashrc
|
|
35
|
-
#
|
|
36
|
-
# 注意: 动态补全插件名需要安装 jq (可选)
|
|
37
|
-
# 未安装 jq 时仍可使用命令和选项补全
|
|
38
|
-
|
|
39
|
-
_42plugin_completions() {
|
|
40
|
-
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
41
|
-
local prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
42
|
-
|
|
43
|
-
case "\${prev}" in
|
|
44
|
-
42plugin)
|
|
45
|
-
COMPREPLY=( $(compgen -W "auth search install list uninstall version completion" -- "\${cur}") )
|
|
46
|
-
return 0
|
|
47
|
-
;;
|
|
48
|
-
install|uninstall)
|
|
49
|
-
# 动态补全插件名 (需要 jq)
|
|
50
|
-
if command -v jq &>/dev/null; then
|
|
51
|
-
if [[ "\${cur}" == */* ]]; then
|
|
52
|
-
local plugins=$(42plugin list --json 2>/dev/null | jq -r '.[].fullName' 2>/dev/null)
|
|
53
|
-
COMPREPLY=( $(compgen -W "\${plugins}" -- "\${cur}") )
|
|
54
|
-
else
|
|
55
|
-
local authors=$(42plugin list --json 2>/dev/null | jq -r '.[].fullName' 2>/dev/null | cut -d'/' -f1 | sort -u)
|
|
56
|
-
COMPREPLY=( $(compgen -W "\${authors}" -- "\${cur}") )
|
|
57
|
-
fi
|
|
58
|
-
fi
|
|
59
|
-
return 0
|
|
60
|
-
;;
|
|
61
|
-
search)
|
|
62
|
-
# 类型补全
|
|
63
|
-
if [[ "\${cur}" == -* ]]; then
|
|
64
|
-
COMPREPLY=( $(compgen -W "-t --type -l --limit --json -i --interactive" -- "\${cur}") )
|
|
65
|
-
fi
|
|
66
|
-
return 0
|
|
67
|
-
;;
|
|
68
|
-
-t|--type)
|
|
69
|
-
COMPREPLY=( $(compgen -W "skill agent command hook mcp" -- "\${cur}") )
|
|
70
|
-
return 0
|
|
71
|
-
;;
|
|
72
|
-
auth)
|
|
73
|
-
COMPREPLY=( $(compgen -W "--status --logout" -- "\${cur}") )
|
|
74
|
-
return 0
|
|
75
|
-
;;
|
|
76
|
-
list|ls)
|
|
77
|
-
if [[ "\${cur}" == -* ]]; then
|
|
78
|
-
COMPREPLY=( $(compgen -W "-t --type --json" -- "\${cur}") )
|
|
79
|
-
fi
|
|
80
|
-
return 0
|
|
81
|
-
;;
|
|
82
|
-
completion)
|
|
83
|
-
COMPREPLY=( $(compgen -W "bash zsh fish" -- "\${cur}") )
|
|
84
|
-
return 0
|
|
85
|
-
;;
|
|
86
|
-
esac
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
complete -F _42plugin_completions 42plugin
|
|
90
|
-
`;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function generateZshCompletion(): string {
|
|
94
|
-
return `#compdef 42plugin
|
|
95
|
-
# 42plugin zsh completion
|
|
96
|
-
# 添加到 ~/.zshrc:
|
|
97
|
-
# eval "$(42plugin completion zsh)"
|
|
98
|
-
# 或:
|
|
99
|
-
# 42plugin completion zsh >> ~/.zshrc
|
|
100
|
-
#
|
|
101
|
-
# 注意: 动态补全插件名需要安装 jq (可选)
|
|
102
|
-
|
|
103
|
-
_42plugin() {
|
|
104
|
-
local -a commands
|
|
105
|
-
commands=(
|
|
106
|
-
'auth:登录/登出/查看状态'
|
|
107
|
-
'search:搜索插件'
|
|
108
|
-
'install:安装插件或套包'
|
|
109
|
-
'list:查看已安装插件'
|
|
110
|
-
'uninstall:卸载插件'
|
|
111
|
-
'version:显示版本'
|
|
112
|
-
'completion:生成补全脚本'
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
_arguments -C \\
|
|
116
|
-
'1: :->command' \\
|
|
117
|
-
'*: :->args'
|
|
118
|
-
|
|
119
|
-
case $state in
|
|
120
|
-
command)
|
|
121
|
-
_describe 'command' commands
|
|
122
|
-
;;
|
|
123
|
-
args)
|
|
124
|
-
case $words[2] in
|
|
125
|
-
install|uninstall)
|
|
126
|
-
# 插件名补全 (需要 jq)
|
|
127
|
-
if (( $+commands[jq] )); then
|
|
128
|
-
local plugins
|
|
129
|
-
plugins=(\${(f)"$(42plugin list --json 2>/dev/null | jq -r '.[].fullName' 2>/dev/null)"})
|
|
130
|
-
_describe 'plugin' plugins
|
|
131
|
-
fi
|
|
132
|
-
;;
|
|
133
|
-
search)
|
|
134
|
-
_arguments \\
|
|
135
|
-
'-t[筛选类型]:type:(skill agent command hook mcp)' \\
|
|
136
|
-
'--type[筛选类型]:type:(skill agent command hook mcp)' \\
|
|
137
|
-
'-l[结果数量]:limit:' \\
|
|
138
|
-
'--limit[结果数量]:limit:' \\
|
|
139
|
-
'--json[JSON 输出]' \\
|
|
140
|
-
'-i[交互式安装]' \\
|
|
141
|
-
'--interactive[交互式安装]'
|
|
142
|
-
;;
|
|
143
|
-
auth)
|
|
144
|
-
_arguments \\
|
|
145
|
-
'--status[查看登录状态]' \\
|
|
146
|
-
'--logout[登出]'
|
|
147
|
-
;;
|
|
148
|
-
list)
|
|
149
|
-
_arguments \\
|
|
150
|
-
'-t[筛选类型]:type:(skill agent command hook mcp)' \\
|
|
151
|
-
'--type[筛选类型]:type:(skill agent command hook mcp)' \\
|
|
152
|
-
'--json[JSON 输出]'
|
|
153
|
-
;;
|
|
154
|
-
completion)
|
|
155
|
-
_describe 'shell' '(bash zsh fish)'
|
|
156
|
-
;;
|
|
157
|
-
esac
|
|
158
|
-
;;
|
|
159
|
-
esac
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
_42plugin
|
|
163
|
-
`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function generateFishCompletion(): string {
|
|
167
|
-
return `# 42plugin fish completion
|
|
168
|
-
# 添加到 ~/.config/fish/config.fish:
|
|
169
|
-
# 42plugin completion fish | source
|
|
170
|
-
# 或:
|
|
171
|
-
# 42plugin completion fish > ~/.config/fish/completions/42plugin.fish
|
|
172
|
-
|
|
173
|
-
complete -c 42plugin -f
|
|
174
|
-
|
|
175
|
-
# Commands
|
|
176
|
-
complete -c 42plugin -n '__fish_use_subcommand' -a auth -d '登录/登出/查看状态'
|
|
177
|
-
complete -c 42plugin -n '__fish_use_subcommand' -a search -d '搜索插件'
|
|
178
|
-
complete -c 42plugin -n '__fish_use_subcommand' -a install -d '安装插件或套包'
|
|
179
|
-
complete -c 42plugin -n '__fish_use_subcommand' -a list -d '查看已安装插件'
|
|
180
|
-
complete -c 42plugin -n '__fish_use_subcommand' -a uninstall -d '卸载插件'
|
|
181
|
-
complete -c 42plugin -n '__fish_use_subcommand' -a version -d '显示版本'
|
|
182
|
-
complete -c 42plugin -n '__fish_use_subcommand' -a completion -d '生成补全脚本'
|
|
183
|
-
|
|
184
|
-
# auth options
|
|
185
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from auth' -l status -d '查看登录状态'
|
|
186
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from auth' -l logout -d '登出'
|
|
187
|
-
|
|
188
|
-
# search options
|
|
189
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from search' -s t -l type -d '筛选类型' -a 'skill agent command hook mcp'
|
|
190
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from search' -s l -l limit -d '结果数量'
|
|
191
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from search' -l json -d 'JSON 输出'
|
|
192
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from search' -s i -l interactive -d '交互式安装'
|
|
193
|
-
|
|
194
|
-
# list options
|
|
195
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from list' -s t -l type -d '筛选类型' -a 'skill agent command hook mcp'
|
|
196
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from list' -l json -d 'JSON 输出'
|
|
197
|
-
|
|
198
|
-
# install options
|
|
199
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from install' -s g -l global -d '安装到全局目录'
|
|
200
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from install' -s f -l force -d '强制重新下载'
|
|
201
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from install' -l no-cache -d '跳过缓存检查'
|
|
202
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from install' -l optional -d '安装套包时包含可选插件'
|
|
203
|
-
|
|
204
|
-
# uninstall options
|
|
205
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from uninstall' -l purge -d '同时清除缓存'
|
|
206
|
-
|
|
207
|
-
# completion argument
|
|
208
|
-
complete -c 42plugin -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d 'Shell 类型'
|
|
209
|
-
`;
|
|
210
|
-
}
|