@42ailab/42plugin 0.1.0-beta.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts ADDED
@@ -0,0 +1,396 @@
1
+ /**
2
+ * CLI 类型定义
3
+ *
4
+ * 与 42plugin-api/src/db/types.ts 保持一致
5
+ */
6
+
7
+ // ============================================================================
8
+ // 基础类型
9
+ // ============================================================================
10
+
11
+ export type PluginType = 'skill' | 'agent' | 'command' | 'hook' | 'mcp';
12
+ export type PriceTier = 'free' | 'vip' | 'premium';
13
+
14
+ // ============================================================================
15
+ // 插件下载(扁平化:plugin = capability)
16
+ // ============================================================================
17
+
18
+ export interface PluginDownloadInfo {
19
+ fullName: string;
20
+ name: string;
21
+ type: PluginType;
22
+ version: string;
23
+ downloadUrl: string;
24
+ checksum: string;
25
+ sizeBytes: number;
26
+ installPath: string;
27
+ }
28
+
29
+ export interface PluginDownload {
30
+ plugin: {
31
+ fullName: string;
32
+ name: string;
33
+ title: string | null;
34
+ description: string | null;
35
+ author: string | null;
36
+ type: PluginType | null;
37
+ version: string | null;
38
+ sourceRepo: string | null;
39
+ };
40
+ download: PluginDownloadInfo;
41
+ priceTier: PriceTier;
42
+ isPaid: boolean;
43
+ requiresAuth: boolean;
44
+ expiresAt: string;
45
+ }
46
+
47
+ // ============================================================================
48
+ // 套包下载
49
+ // ============================================================================
50
+
51
+ export interface KitDownload {
52
+ kit: {
53
+ fullName: string;
54
+ name: string;
55
+ description: string | null;
56
+ priceTier: PriceTier | null;
57
+ effectivePriceTier: PriceTier;
58
+ };
59
+ plugins: Array<{
60
+ plugin: {
61
+ fullName: string;
62
+ name: string;
63
+ title: string | null;
64
+ author: string | null;
65
+ type: PluginType | null;
66
+ };
67
+ download: PluginDownloadInfo;
68
+ required: boolean;
69
+ reason: string | null;
70
+ }>;
71
+ summary: {
72
+ totalPlugins: number;
73
+ requiredPlugins: number;
74
+ optionalPlugins: number;
75
+ totalSizeBytes: number;
76
+ expiresAt: string;
77
+ };
78
+ }
79
+
80
+ // ============================================================================
81
+ // 搜索
82
+ // ============================================================================
83
+
84
+ export interface SearchResult {
85
+ fullName: string;
86
+ name: string;
87
+ title: string | null;
88
+ slogan: string | null;
89
+ description: string | null;
90
+ type: PluginType;
91
+ version: string;
92
+ author: { username: string };
93
+ tags: string[];
94
+ downloads: number;
95
+ icon: string | null;
96
+ }
97
+
98
+ export interface SearchResponse {
99
+ data: SearchResult[];
100
+ pagination: {
101
+ page: number;
102
+ perPage: number;
103
+ total: number;
104
+ totalPages: number;
105
+ };
106
+ }
107
+
108
+ // API 实际返回格式 (snake_case)
109
+ export interface SearchApiResult {
110
+ full_name: string;
111
+ name: string;
112
+ title: string | null;
113
+ slogan?: string | null;
114
+ description: string | null;
115
+ type: PluginType;
116
+ version?: string;
117
+ downloads: number;
118
+ author: { username: string };
119
+ tags?: string[];
120
+ icon?: string | null;
121
+ }
122
+
123
+ export interface SearchApiResponse {
124
+ data: {
125
+ plugins: SearchApiResult[];
126
+ kits: unknown[];
127
+ };
128
+ pagination: {
129
+ total: number;
130
+ page: number;
131
+ per_page: number;
132
+ loaded: number;
133
+ has_more: boolean;
134
+ };
135
+ }
136
+
137
+ // ============================================================================
138
+ // 认证
139
+ // ============================================================================
140
+
141
+ export interface DeviceCodeResponse {
142
+ deviceCode: string;
143
+ userCode: string;
144
+ verificationUri: string;
145
+ verificationUriComplete?: string;
146
+ expiresIn: number;
147
+ interval: number;
148
+ }
149
+
150
+ export interface DeviceTokenResponse {
151
+ accessToken?: string;
152
+ user?: {
153
+ id: string;
154
+ name: string | null;
155
+ username: string | null;
156
+ email: string | null;
157
+ };
158
+ error?: string;
159
+ errorDescription?: string;
160
+ }
161
+
162
+ export interface Session {
163
+ user: {
164
+ id: string;
165
+ name: string | null;
166
+ username: string | null;
167
+ email: string | null;
168
+ };
169
+ session: {
170
+ id: string;
171
+ expiresAt: string;
172
+ };
173
+ }
174
+
175
+ // ============================================================================
176
+ // 本地存储
177
+ // ============================================================================
178
+
179
+ export interface LocalProject {
180
+ id: string;
181
+ path: string;
182
+ name: string;
183
+ registeredAt: string;
184
+ lastUsedAt: string;
185
+ }
186
+
187
+ export interface LocalCache {
188
+ fullName: string;
189
+ type: PluginType;
190
+ version: string;
191
+ cachePath: string;
192
+ checksum: string;
193
+ sizeBytes: number;
194
+ cachedAt: string;
195
+ }
196
+
197
+ export interface LocalInstallation {
198
+ id: string;
199
+ projectId: string;
200
+ fullName: string;
201
+ type: PluginType;
202
+ version: string;
203
+ cachePath: string;
204
+ linkPath: string;
205
+ source: 'direct' | 'kit';
206
+ sourceKit?: string;
207
+ installedAt: string;
208
+ }
209
+
210
+ // ============================================================================
211
+ // Kit API 响应(snake_case)
212
+ // ============================================================================
213
+
214
+ export interface KitApiResponse {
215
+ data: {
216
+ kit: {
217
+ full_name: string;
218
+ name: string;
219
+ description: string | null;
220
+ price_tier: PriceTier | null;
221
+ effective_price_tier: PriceTier;
222
+ };
223
+ plugins: Array<{
224
+ plugin: {
225
+ full_name: string;
226
+ name: string;
227
+ title: string | null;
228
+ author: string | null;
229
+ type: PluginType | null;
230
+ };
231
+ download: {
232
+ full_name: string;
233
+ name: string;
234
+ type: PluginType;
235
+ version: string;
236
+ download_url: string;
237
+ checksum: string;
238
+ size_bytes: number;
239
+ install_path: string;
240
+ };
241
+ required: boolean;
242
+ reason: string | null;
243
+ }>;
244
+ summary: {
245
+ total_plugins: number;
246
+ required_plugins: number;
247
+ optional_plugins: number;
248
+ total_size_bytes: number;
249
+ expires_at: string;
250
+ };
251
+ };
252
+ }
253
+
254
+ // ============================================================================
255
+ // 目标解析
256
+ // ============================================================================
257
+
258
+ export enum TargetType {
259
+ Plugin = 'plugin',
260
+ Kit = 'kit',
261
+ }
262
+
263
+ export interface ParsedTarget {
264
+ type: TargetType;
265
+ fullName: string;
266
+ author: string;
267
+ name: string;
268
+ }
269
+
270
+ // ============================================================================
271
+ // 发布相关类型
272
+ // ============================================================================
273
+
274
+ export interface PublishOptions {
275
+ path: string;
276
+ dryRun?: boolean;
277
+ force?: boolean;
278
+ visibility?: 'self' | 'public';
279
+ name?: string;
280
+ }
281
+
282
+ export interface PluginMetadata {
283
+ name: string;
284
+ type: PluginType;
285
+ title?: string;
286
+ description?: string;
287
+ tags?: string[];
288
+ sourcePath: string;
289
+ }
290
+
291
+ export interface PackageResult {
292
+ tarballPath: string;
293
+ contentHash: string;
294
+ packageHash: string;
295
+ sizeBytes: number;
296
+ }
297
+
298
+ export interface VersionDecision {
299
+ version: string;
300
+ action: 'create' | 'update' | 'skip';
301
+ existingHash?: string;
302
+ }
303
+
304
+ export interface PublishResult {
305
+ fullName: string;
306
+ version: string;
307
+ storageKey: string;
308
+ type: PluginType;
309
+ action: 'created' | 'updated' | 'unchanged';
310
+ }
311
+
312
+ export interface UploadUrlResponse {
313
+ uploadUrl: string;
314
+ storageKey: string;
315
+ expiresAt: string;
316
+ }
317
+
318
+ export interface PublishConfirmRequest {
319
+ name: string;
320
+ type: PluginType;
321
+ version: string;
322
+ contentHash: string;
323
+ packageHash: string;
324
+ storageKey: string;
325
+ sizeBytes: number;
326
+ title?: string;
327
+ description?: string;
328
+ tags?: string[];
329
+ visibility: 'self' | 'public';
330
+ }
331
+
332
+ export interface PublishConfirmResponse {
333
+ plugin: {
334
+ id: string;
335
+ fullName: string;
336
+ version: string;
337
+ };
338
+ action: 'created' | 'updated';
339
+ }
340
+
341
+ export interface PluginStatusResponse {
342
+ exists: boolean;
343
+ version?: string;
344
+ contentHash?: string;
345
+ visibility?: 'self' | 'public';
346
+ }
347
+
348
+ // ============================================================================
349
+ // 验证相关类型
350
+ // ============================================================================
351
+
352
+ export interface ValidationResult {
353
+ valid: boolean;
354
+ metadata: PluginMetadata;
355
+ errors: ValidationIssue[];
356
+ warnings: ValidationIssue[];
357
+ }
358
+
359
+ export interface ValidationIssue {
360
+ code: string;
361
+ field?: string;
362
+ message: string;
363
+ suggestion?: string;
364
+ line?: number;
365
+ }
366
+
367
+ // Hook 配置(匹配 Claude Code 实际格式)
368
+ export interface HookConfig {
369
+ description?: string;
370
+ hooks: {
371
+ [eventType: string]: Array<{
372
+ matcher?: string;
373
+ hooks: Array<{
374
+ type: string;
375
+ command: string;
376
+ timeout?: number;
377
+ }>;
378
+ }>;
379
+ };
380
+ }
381
+
382
+ // MCP 配置
383
+ export interface McpConfig {
384
+ name?: string;
385
+ protocol_version?: string;
386
+ capabilities?: {
387
+ resources?: boolean;
388
+ tools?: boolean;
389
+ sampling?: boolean;
390
+ };
391
+ tools?: Array<{
392
+ name: string;
393
+ description?: string;
394
+ inputSchema?: Record<string, unknown>;
395
+ }>;
396
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * 工具函数
3
+ */
4
+
5
+ import { TargetType, type ParsedTarget, type PluginType } from './types';
6
+
7
+ // ============================================================================
8
+ // 目标解析
9
+ // ============================================================================
10
+
11
+ /**
12
+ * 解析安装目标
13
+ *
14
+ * - Plugin: author/name
15
+ * - Kit: author/kit/slug
16
+ */
17
+ export function parseTarget(target: string): ParsedTarget {
18
+ // Kit: author/kit/slug
19
+ if (target.includes('/kit/')) {
20
+ const [author, , slug] = target.split('/');
21
+ if (!author || !slug) {
22
+ throw new Error(`无效的套包格式: ${target}\n正确格式: author/kit/slug`);
23
+ }
24
+ return { type: TargetType.Kit, fullName: target, author, name: slug };
25
+ }
26
+
27
+ // Plugin: author/name
28
+ const parts = target.split('/');
29
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
30
+ throw new Error(`无效的插件格式: ${target}\n正确格式: author/name`);
31
+ }
32
+
33
+ return {
34
+ type: TargetType.Plugin,
35
+ fullName: target,
36
+ author: parts[0],
37
+ name: parts[1],
38
+ };
39
+ }
40
+
41
+ // ============================================================================
42
+ // 安装路径
43
+ // ============================================================================
44
+
45
+ /**
46
+ * 获取插件安装路径(.claude/ 目录下)
47
+ */
48
+ export function getInstallPath(type: PluginType, name: string): string {
49
+ switch (type) {
50
+ case 'skill':
51
+ return `.claude/skills/${name}/`;
52
+ case 'agent':
53
+ return `.claude/agents/${name}.md`;
54
+ case 'command':
55
+ return `.claude/commands/${name}.md`;
56
+ case 'hook':
57
+ return `.claude/hooks/${name}`;
58
+ case 'mcp':
59
+ return `.claude/mcp/${name}/`;
60
+ default:
61
+ return `.claude/${type}/${name}/`;
62
+ }
63
+ }
64
+
65
+ // ============================================================================
66
+ // 格式化
67
+ // ============================================================================
68
+
69
+ /**
70
+ * 格式化文件大小
71
+ */
72
+ export function formatBytes(bytes: number): string {
73
+ if (bytes < 1024) return `${bytes} B`;
74
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
75
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
76
+ }
77
+
78
+ /**
79
+ * 格式化相对时间
80
+ */
81
+ export function formatRelativeTime(date: string | Date): string {
82
+ const d = typeof date === 'string' ? new Date(date) : date;
83
+ const now = new Date();
84
+ const diff = now.getTime() - d.getTime();
85
+
86
+ const minutes = Math.floor(diff / 60000);
87
+ if (minutes < 1) return '刚刚';
88
+ if (minutes < 60) return `${minutes} 分钟前`;
89
+
90
+ const hours = Math.floor(minutes / 60);
91
+ if (hours < 24) return `${hours} 小时前`;
92
+
93
+ const days = Math.floor(hours / 24);
94
+ if (days < 30) return `${days} 天前`;
95
+
96
+ const months = Math.floor(days / 30);
97
+ if (months < 12) return `${months} 个月前`;
98
+
99
+ return `${Math.floor(months / 12)} 年前`;
100
+ }
101
+
102
+ // ============================================================================
103
+ // 插件类型
104
+ // ============================================================================
105
+
106
+ const TYPE_LABELS: Record<PluginType, string> = {
107
+ skill: 'Skill',
108
+ agent: 'Agent',
109
+ command: 'Command',
110
+ hook: 'Hook',
111
+ mcp: 'MCP',
112
+ };
113
+
114
+ const TYPE_ICONS: Record<PluginType, string> = {
115
+ skill: '⚡',
116
+ agent: '🤖',
117
+ command: '⌘',
118
+ hook: '🪝',
119
+ mcp: '🔌',
120
+ };
121
+
122
+ export function getTypeLabel(type: PluginType): string {
123
+ return TYPE_LABELS[type] || type;
124
+ }
125
+
126
+ export function getTypeIcon(type: PluginType): string {
127
+ return TYPE_ICONS[type] || '📦';
128
+ }