@42ailab/42plugin 0.1.21 → 0.1.23
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 +38 -12
- package/package.json +1 -1
- package/src/api.ts +1 -0
- package/src/cli.ts +13 -1
- package/src/commands/install.ts +59 -12
- package/src/commands/list.ts +151 -34
- package/src/commands/publish.ts +87 -0
- package/src/commands/uninstall.ts +33 -6
- package/src/config.ts +61 -1
- package/src/db.ts +31 -0
- package/src/services/publisher.ts +86 -1
- package/src/types.ts +2 -0
- package/src/update-checker.ts +343 -0
- package/src/validators/plugin-validator.ts +21 -51
|
@@ -42,8 +42,15 @@ const VALID_TYPES: PluginType[] = ['skill', 'agent', 'command', 'hook', 'mcp'];
|
|
|
42
42
|
export class PluginValidator {
|
|
43
43
|
/**
|
|
44
44
|
* 完整验证(返回详细结果)
|
|
45
|
+
* @param pluginPath 插件路径
|
|
46
|
+
* @param overrideName 覆盖名称
|
|
47
|
+
* @param overrideType 覆盖类型(用户通过 -t 参数指定)
|
|
45
48
|
*/
|
|
46
|
-
async validateFull(
|
|
49
|
+
async validateFull(
|
|
50
|
+
pluginPath: string,
|
|
51
|
+
overrideName?: string,
|
|
52
|
+
overrideType?: PluginType
|
|
53
|
+
): Promise<ValidationResult> {
|
|
47
54
|
const result: ValidationResult = {
|
|
48
55
|
valid: true,
|
|
49
56
|
metadata: {
|
|
@@ -72,9 +79,9 @@ export class PluginValidator {
|
|
|
72
79
|
// Phase 2: 提取元信息并进行类型检测
|
|
73
80
|
try {
|
|
74
81
|
if (stat.isFile()) {
|
|
75
|
-
result.metadata = await this.extractFileMetadata(absPath, overrideName);
|
|
82
|
+
result.metadata = await this.extractFileMetadata(absPath, overrideName, overrideType);
|
|
76
83
|
} else if (stat.isDirectory()) {
|
|
77
|
-
result.metadata = await this.extractDirectoryMetadata(absPath, overrideName);
|
|
84
|
+
result.metadata = await this.extractDirectoryMetadata(absPath, overrideName, overrideType);
|
|
78
85
|
} else {
|
|
79
86
|
result.errors.push({
|
|
80
87
|
code: 'E000',
|
|
@@ -130,7 +137,8 @@ export class PluginValidator {
|
|
|
130
137
|
*/
|
|
131
138
|
private async extractFileMetadata(
|
|
132
139
|
filePath: string,
|
|
133
|
-
overrideName?: string
|
|
140
|
+
overrideName?: string,
|
|
141
|
+
overrideType?: PluginType
|
|
134
142
|
): Promise<PluginMetadata> {
|
|
135
143
|
const ext = path.extname(filePath).toLowerCase();
|
|
136
144
|
const basename = path.basename(filePath, ext);
|
|
@@ -146,8 +154,9 @@ export class PluginValidator {
|
|
|
146
154
|
const parsed = matter(content);
|
|
147
155
|
const data = parsed.data as Record<string, unknown>;
|
|
148
156
|
|
|
149
|
-
//
|
|
150
|
-
|
|
157
|
+
// 类型由用户通过 -t 参数或交互选择指定,必须提供
|
|
158
|
+
// 仅在未指定时使用 frontmatter 作为后备(向后兼容 validate 方法)
|
|
159
|
+
let type: PluginType = overrideType || (data.type as PluginType) || 'skill';
|
|
151
160
|
|
|
152
161
|
return {
|
|
153
162
|
name: overrideName || (data.name as string) || basename,
|
|
@@ -164,35 +173,23 @@ export class PluginValidator {
|
|
|
164
173
|
*/
|
|
165
174
|
private async extractDirectoryMetadata(
|
|
166
175
|
dirPath: string,
|
|
167
|
-
overrideName?: string
|
|
176
|
+
overrideName?: string,
|
|
177
|
+
overrideType?: PluginType
|
|
168
178
|
): Promise<PluginMetadata> {
|
|
169
179
|
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
170
180
|
const files = entries.filter((e) => e.isFile()).map((e) => e.name);
|
|
171
|
-
const lowerFiles = files.map((f) => f.toLowerCase());
|
|
172
181
|
const basename = path.basename(dirPath);
|
|
173
182
|
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// MCP: 有 mcp.json 或 .mcp.json
|
|
179
|
-
if (lowerFiles.some((f) => f === 'mcp.json' || f === '.mcp.json')) {
|
|
180
|
-
type = 'mcp';
|
|
181
|
-
}
|
|
182
|
-
// Hook: 有 hooks.json 或 hook.json
|
|
183
|
-
else if (lowerFiles.some((f) => f === 'hooks.json' || f === 'hook.json')) {
|
|
184
|
-
type = 'hook';
|
|
185
|
-
}
|
|
186
|
-
// Skill: 有 SKILL.md 或 skill.md
|
|
187
|
-
else if (lowerFiles.some((f) => f === 'skill.md')) {
|
|
188
|
-
type = 'skill';
|
|
189
|
-
}
|
|
183
|
+
// 类型由用户通过 -t 参数或交互选择指定,必须提供
|
|
184
|
+
// 仅在未指定时使用 'skill' 作为后备(向后兼容 validate 方法)
|
|
185
|
+
const type: PluginType = overrideType || 'skill';
|
|
190
186
|
|
|
191
187
|
// 尝试读取主文件获取元信息
|
|
192
188
|
const mainFiles = ['SKILL.md', 'skill.md', 'README.md', 'readme.md'];
|
|
193
189
|
let description: string | undefined;
|
|
194
190
|
let title: string | undefined;
|
|
195
191
|
let tags: string[] | undefined;
|
|
192
|
+
let frontmatterData: Record<string, unknown> = {};
|
|
196
193
|
|
|
197
194
|
for (const file of mainFiles) {
|
|
198
195
|
if (files.includes(file)) {
|
|
@@ -204,9 +201,6 @@ export class PluginValidator {
|
|
|
204
201
|
if (frontmatterData.title) title = frontmatterData.title as string;
|
|
205
202
|
if (frontmatterData.description) description = frontmatterData.description as string;
|
|
206
203
|
if (frontmatterData.tags) tags = frontmatterData.tags as string[];
|
|
207
|
-
if (frontmatterData.type && VALID_TYPES.includes(frontmatterData.type as PluginType)) {
|
|
208
|
-
type = frontmatterData.type as PluginType;
|
|
209
|
-
}
|
|
210
204
|
|
|
211
205
|
// 如果没有 frontmatter 描述,取第一段文字
|
|
212
206
|
if (!description && parsed.content) {
|
|
@@ -233,30 +227,6 @@ export class PluginValidator {
|
|
|
233
227
|
};
|
|
234
228
|
}
|
|
235
229
|
|
|
236
|
-
/**
|
|
237
|
-
* 从内容推断插件类型
|
|
238
|
-
*/
|
|
239
|
-
private inferTypeFromContent(content: string, data: Record<string, unknown>): PluginType {
|
|
240
|
-
// 优先使用 frontmatter 中的 type
|
|
241
|
-
if (data.type && VALID_TYPES.includes(data.type as PluginType)) {
|
|
242
|
-
return data.type as PluginType;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 从内容推断
|
|
246
|
-
const lowerContent = content.toLowerCase();
|
|
247
|
-
|
|
248
|
-
if (lowerContent.includes('you are') || lowerContent.includes('你是')) {
|
|
249
|
-
return 'agent';
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (lowerContent.includes('mcp') || lowerContent.includes('model context protocol')) {
|
|
253
|
-
return 'mcp';
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// 默认为 skill
|
|
257
|
-
return 'skill';
|
|
258
|
-
}
|
|
259
|
-
|
|
260
230
|
// ==========================================================================
|
|
261
231
|
// Schema 验证
|
|
262
232
|
// ==========================================================================
|