@42ailab/42plugin 0.1.0-beta.1 → 0.1.5
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 +211 -68
- package/package.json +12 -7
- package/src/api.ts +447 -0
- package/src/cli.ts +39 -16
- package/src/commands/auth.ts +83 -69
- package/src/commands/check.ts +118 -0
- package/src/commands/completion.ts +210 -0
- package/src/commands/index.ts +13 -0
- package/src/commands/install-helper.ts +71 -0
- package/src/commands/install.ts +219 -300
- package/src/commands/list.ts +42 -66
- package/src/commands/publish.ts +121 -0
- package/src/commands/search.ts +89 -85
- package/src/commands/setup.ts +158 -0
- package/src/commands/uninstall.ts +53 -44
- package/src/config.ts +27 -36
- package/src/db.ts +593 -0
- package/src/errors.ts +40 -0
- package/src/index.ts +4 -31
- package/src/services/packager.ts +177 -0
- package/src/services/publisher.ts +237 -0
- package/src/services/upload.ts +52 -0
- package/src/services/version-manager.ts +65 -0
- package/src/types.ts +396 -0
- package/src/utils.ts +128 -0
- package/src/validators/plugin-validator.ts +635 -0
- package/src/commands/version.ts +0 -20
- package/src/db/client.ts +0 -180
- package/src/services/api.ts +0 -128
- package/src/services/auth.ts +0 -46
- package/src/services/cache.ts +0 -101
- package/src/services/download.ts +0 -148
- package/src/services/link.ts +0 -86
- package/src/services/project.ts +0 -179
- package/src/types/api.ts +0 -115
- package/src/types/db.ts +0 -31
- package/src/utils/errors.ts +0 -40
- package/src/utils/platform.ts +0 -6
- package/src/utils/target.ts +0 -114
package/src/services/project.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { nanoid } from 'nanoid';
|
|
3
|
-
import { getDb } from '../db/client';
|
|
4
|
-
import type { Project, ProjectPlugin } from '../types/db';
|
|
5
|
-
|
|
6
|
-
export async function getProject(projectPath: string): Promise<Project | null> {
|
|
7
|
-
const db = getDb();
|
|
8
|
-
const result = await db.execute({
|
|
9
|
-
sql: 'SELECT * FROM projects WHERE path = ?',
|
|
10
|
-
args: [projectPath],
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
if (result.rows.length === 0) return null;
|
|
14
|
-
return result.rows[0] as unknown as Project;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function getOrCreateProject(projectPath: string): Promise<Project> {
|
|
18
|
-
let project = await getProject(projectPath);
|
|
19
|
-
|
|
20
|
-
if (!project) {
|
|
21
|
-
const db = getDb();
|
|
22
|
-
const id = nanoid();
|
|
23
|
-
const name = path.basename(projectPath);
|
|
24
|
-
const now = new Date().toISOString();
|
|
25
|
-
|
|
26
|
-
await db.execute({
|
|
27
|
-
sql: `INSERT INTO projects (id, path, name, registered_at, last_used_at)
|
|
28
|
-
VALUES (?, ?, ?, ?, ?)`,
|
|
29
|
-
args: [id, projectPath, name, now, now],
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
project = { id, path: projectPath, name, registered_at: now, last_used_at: now };
|
|
33
|
-
} else {
|
|
34
|
-
// Update last used time
|
|
35
|
-
const db = getDb();
|
|
36
|
-
await db.execute({
|
|
37
|
-
sql: 'UPDATE projects SET last_used_at = ? WHERE id = ?',
|
|
38
|
-
args: [new Date().toISOString(), project.id],
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return project;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function getProjectPlugins(projectId: string): Promise<ProjectPlugin[]> {
|
|
46
|
-
const db = getDb();
|
|
47
|
-
const result = await db.execute({
|
|
48
|
-
sql: 'SELECT * FROM project_plugins WHERE project_id = ?',
|
|
49
|
-
args: [projectId],
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return result.rows as unknown as ProjectPlugin[];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function getProjectPlugin(projectId: string, fullName: string): Promise<ProjectPlugin | null> {
|
|
56
|
-
const db = getDb();
|
|
57
|
-
const result = await db.execute({
|
|
58
|
-
sql: 'SELECT * FROM project_plugins WHERE project_id = ? AND full_name = ?',
|
|
59
|
-
args: [projectId, fullName],
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
if (result.rows.length === 0) return null;
|
|
63
|
-
return result.rows[0] as unknown as ProjectPlugin;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function addProjectPlugin(plugin: {
|
|
67
|
-
project_id: string;
|
|
68
|
-
full_name: string;
|
|
69
|
-
type: string;
|
|
70
|
-
version: string;
|
|
71
|
-
cache_path: string;
|
|
72
|
-
link_path: string;
|
|
73
|
-
source?: string;
|
|
74
|
-
source_list?: string;
|
|
75
|
-
}): Promise<void> {
|
|
76
|
-
const db = getDb();
|
|
77
|
-
const id = nanoid();
|
|
78
|
-
const now = new Date().toISOString();
|
|
79
|
-
|
|
80
|
-
await db.execute({
|
|
81
|
-
sql: `INSERT INTO project_plugins
|
|
82
|
-
(id, project_id, full_name, type, version, cache_path, link_path, installed_at, source, source_list)
|
|
83
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
84
|
-
ON CONFLICT(project_id, full_name) DO UPDATE SET
|
|
85
|
-
id = excluded.id,
|
|
86
|
-
type = excluded.type,
|
|
87
|
-
version = excluded.version,
|
|
88
|
-
cache_path = excluded.cache_path,
|
|
89
|
-
link_path = excluded.link_path,
|
|
90
|
-
installed_at = excluded.installed_at,
|
|
91
|
-
source = excluded.source,
|
|
92
|
-
source_list = excluded.source_list`,
|
|
93
|
-
args: [
|
|
94
|
-
id,
|
|
95
|
-
plugin.project_id,
|
|
96
|
-
plugin.full_name,
|
|
97
|
-
plugin.type,
|
|
98
|
-
plugin.version,
|
|
99
|
-
plugin.cache_path,
|
|
100
|
-
plugin.link_path,
|
|
101
|
-
now,
|
|
102
|
-
plugin.source || 'direct',
|
|
103
|
-
plugin.source_list || null,
|
|
104
|
-
],
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export async function removeProjectPlugin(
|
|
109
|
-
projectId: string,
|
|
110
|
-
fullName: string
|
|
111
|
-
): Promise<ProjectPlugin | null> {
|
|
112
|
-
const plugin = await findProjectPlugin(projectId, fullName);
|
|
113
|
-
|
|
114
|
-
if (!plugin) return null;
|
|
115
|
-
|
|
116
|
-
const db = getDb();
|
|
117
|
-
await db.execute({
|
|
118
|
-
sql: 'DELETE FROM project_plugins WHERE id = ?',
|
|
119
|
-
args: [plugin.id],
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
return plugin;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Find a project plugin by various identifier formats:
|
|
127
|
-
* - Full name: owner:custom:type:name or owner/repo:plugin:type:name
|
|
128
|
-
* - Slug format: author/slug (e.g., 42plugin/wechat-downloader)
|
|
129
|
-
* - Simple name: wechat-downloader
|
|
130
|
-
*/
|
|
131
|
-
export async function findProjectPlugin(
|
|
132
|
-
projectId: string,
|
|
133
|
-
identifier: string
|
|
134
|
-
): Promise<ProjectPlugin | null> {
|
|
135
|
-
const db = getDb();
|
|
136
|
-
|
|
137
|
-
// 1. Try exact full_name match first
|
|
138
|
-
let result = await db.execute({
|
|
139
|
-
sql: 'SELECT * FROM project_plugins WHERE project_id = ? AND full_name = ?',
|
|
140
|
-
args: [projectId, identifier],
|
|
141
|
-
});
|
|
142
|
-
if (result.rows.length > 0) {
|
|
143
|
-
return result.rows[0] as unknown as ProjectPlugin;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 2. Try matching by slug format (author/name -> find where full_name ends with :type:name)
|
|
147
|
-
if (identifier.includes('/') && !identifier.includes(':')) {
|
|
148
|
-
const [author, name] = identifier.split('/');
|
|
149
|
-
// Match pattern: {author}:custom:{type}:{name} or similar
|
|
150
|
-
result = await db.execute({
|
|
151
|
-
sql: `SELECT * FROM project_plugins
|
|
152
|
-
WHERE project_id = ?
|
|
153
|
-
AND (full_name LIKE ? OR full_name LIKE ?)`,
|
|
154
|
-
args: [projectId, `${author}:%:${name}`, `${author}/%:%:${name}`],
|
|
155
|
-
});
|
|
156
|
-
if (result.rows.length > 0) {
|
|
157
|
-
return result.rows[0] as unknown as ProjectPlugin;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// 3. Try matching by simple name (last part of full_name)
|
|
162
|
-
result = await db.execute({
|
|
163
|
-
sql: `SELECT * FROM project_plugins
|
|
164
|
-
WHERE project_id = ?
|
|
165
|
-
AND full_name LIKE ?`,
|
|
166
|
-
args: [projectId, `%:${identifier}`],
|
|
167
|
-
});
|
|
168
|
-
if (result.rows.length > 0) {
|
|
169
|
-
return result.rows[0] as unknown as ProjectPlugin;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export async function getAllProjects(): Promise<Project[]> {
|
|
176
|
-
const db = getDb();
|
|
177
|
-
const result = await db.execute('SELECT * FROM projects ORDER BY last_used_at DESC');
|
|
178
|
-
return result.rows as unknown as Project[];
|
|
179
|
-
}
|
package/src/types/api.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
export interface AuthStartResponse {
|
|
2
|
-
code: string;
|
|
3
|
-
auth_url: string;
|
|
4
|
-
expires_at: string;
|
|
5
|
-
poll_interval: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface AuthPollResponse {
|
|
9
|
-
status: 'pending' | 'completed';
|
|
10
|
-
access_token?: string;
|
|
11
|
-
refresh_token?: string;
|
|
12
|
-
expires_in?: number;
|
|
13
|
-
user?: {
|
|
14
|
-
username: string;
|
|
15
|
-
display_name: string | null;
|
|
16
|
-
role: string;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface UserProfile {
|
|
21
|
-
id: string;
|
|
22
|
-
username: string;
|
|
23
|
-
display_name: string | null;
|
|
24
|
-
email: string | null;
|
|
25
|
-
avatar_url: string | null;
|
|
26
|
-
role: string;
|
|
27
|
-
vip_expires_at: string | null;
|
|
28
|
-
verified: boolean;
|
|
29
|
-
created_at: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface CapabilityDownload {
|
|
33
|
-
full_name: string;
|
|
34
|
-
version: string;
|
|
35
|
-
type: string;
|
|
36
|
-
download_url: string;
|
|
37
|
-
checksum: string;
|
|
38
|
-
size_bytes: number;
|
|
39
|
-
is_paid: boolean;
|
|
40
|
-
requires_auth: boolean;
|
|
41
|
-
expires_at?: string;
|
|
42
|
-
install_path: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface PluginDetail {
|
|
46
|
-
full_name: string;
|
|
47
|
-
name: string;
|
|
48
|
-
description: string | null;
|
|
49
|
-
version: string;
|
|
50
|
-
capabilities: Array<{
|
|
51
|
-
full_name: string;
|
|
52
|
-
name: string;
|
|
53
|
-
type: string;
|
|
54
|
-
description: string | null;
|
|
55
|
-
}>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface ListDownload {
|
|
59
|
-
list: {
|
|
60
|
-
full_name: string;
|
|
61
|
-
name: string;
|
|
62
|
-
description: string | null;
|
|
63
|
-
price_tier: string | null;
|
|
64
|
-
effective_price_tier: string;
|
|
65
|
-
};
|
|
66
|
-
capabilities: Array<{
|
|
67
|
-
full_name: string;
|
|
68
|
-
name: string;
|
|
69
|
-
type: string;
|
|
70
|
-
version: string;
|
|
71
|
-
required: boolean;
|
|
72
|
-
reason: string | null;
|
|
73
|
-
download_url: string;
|
|
74
|
-
checksum: string;
|
|
75
|
-
size_bytes: number;
|
|
76
|
-
install_path: string;
|
|
77
|
-
}>;
|
|
78
|
-
summary: {
|
|
79
|
-
total_capabilities: number;
|
|
80
|
-
required_count: number;
|
|
81
|
-
optional_count: number;
|
|
82
|
-
total_size_bytes: number;
|
|
83
|
-
expires_at: string;
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface SearchCapability {
|
|
88
|
-
full_name: string;
|
|
89
|
-
slug: string | null;
|
|
90
|
-
author_username: string | null;
|
|
91
|
-
short_name: string; // author/slug if available, otherwise full_name
|
|
92
|
-
name: string;
|
|
93
|
-
type: string;
|
|
94
|
-
description: string | null;
|
|
95
|
-
downloads: number;
|
|
96
|
-
relevance: number;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface SearchList {
|
|
100
|
-
full_name: string;
|
|
101
|
-
name: string;
|
|
102
|
-
capability_count: number;
|
|
103
|
-
downloads: number;
|
|
104
|
-
relevance: number;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export interface SearchResult {
|
|
108
|
-
capabilities: SearchCapability[];
|
|
109
|
-
lists: SearchList[];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export interface SearchOptions {
|
|
113
|
-
type?: string;
|
|
114
|
-
per_page?: number;
|
|
115
|
-
}
|
package/src/types/db.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export interface Project {
|
|
2
|
-
id: string;
|
|
3
|
-
path: string;
|
|
4
|
-
name: string;
|
|
5
|
-
registered_at: string;
|
|
6
|
-
last_used_at: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface CacheEntry {
|
|
10
|
-
id: string;
|
|
11
|
-
full_name: string;
|
|
12
|
-
type: string;
|
|
13
|
-
version: string;
|
|
14
|
-
cache_path: string;
|
|
15
|
-
checksum: string;
|
|
16
|
-
size_bytes: number | null;
|
|
17
|
-
downloaded_at: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface ProjectPlugin {
|
|
21
|
-
id: string;
|
|
22
|
-
project_id: string;
|
|
23
|
-
full_name: string;
|
|
24
|
-
type: string;
|
|
25
|
-
version: string;
|
|
26
|
-
cache_path: string;
|
|
27
|
-
link_path: string;
|
|
28
|
-
installed_at: string;
|
|
29
|
-
source: string;
|
|
30
|
-
source_list: string | null;
|
|
31
|
-
}
|
package/src/utils/errors.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
export class CliError extends Error {
|
|
2
|
-
constructor(
|
|
3
|
-
message: string,
|
|
4
|
-
public code: string,
|
|
5
|
-
public exitCode: number = 1
|
|
6
|
-
) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.name = 'CliError';
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class AuthError extends CliError {
|
|
13
|
-
constructor(message: string) {
|
|
14
|
-
super(message, 'AUTH_ERROR', 1);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class NetworkError extends CliError {
|
|
19
|
-
constructor(message: string) {
|
|
20
|
-
super(message, 'NETWORK_ERROR', 1);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class NotFoundError extends CliError {
|
|
25
|
-
constructor(resource: string) {
|
|
26
|
-
super(`Not found: ${resource}`, 'NOT_FOUND', 1);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export class PermissionError extends CliError {
|
|
31
|
-
constructor(message: string) {
|
|
32
|
-
super(message, 'PERMISSION_ERROR', 1);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class ValidationError extends CliError {
|
|
37
|
-
constructor(message: string) {
|
|
38
|
-
super(message, 'VALIDATION_ERROR', 1);
|
|
39
|
-
}
|
|
40
|
-
}
|
package/src/utils/platform.ts
DELETED
package/src/utils/target.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
export enum TargetType {
|
|
2
|
-
Capability = 'capability', // Full name: owner/repo:plugin:type:name
|
|
3
|
-
CapabilitySlug = 'capability_slug', // Short slug: author/slug
|
|
4
|
-
Plugin = 'plugin',
|
|
5
|
-
List = 'list',
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface ParsedTarget {
|
|
9
|
-
type: TargetType;
|
|
10
|
-
fullName: string;
|
|
11
|
-
owner: string;
|
|
12
|
-
repo: string;
|
|
13
|
-
plugin?: string;
|
|
14
|
-
capType?: string;
|
|
15
|
-
capName?: string;
|
|
16
|
-
// For slug format
|
|
17
|
-
author?: string;
|
|
18
|
-
slug?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Parse install target
|
|
23
|
-
*
|
|
24
|
-
* Formats:
|
|
25
|
-
* - Capability (full): owner/repo:plugin:type:name
|
|
26
|
-
* - Capability (slug): author/slug (e.g., alice/smart-reviewer)
|
|
27
|
-
* - Plugin: owner/repo:plugin
|
|
28
|
-
* - List: owner/list/slug
|
|
29
|
-
*/
|
|
30
|
-
export function parseTarget(target: string): ParsedTarget {
|
|
31
|
-
// List format: owner/list/slug
|
|
32
|
-
if (target.includes('/list/')) {
|
|
33
|
-
const [owner, , slug] = target.split('/');
|
|
34
|
-
return {
|
|
35
|
-
type: TargetType.List,
|
|
36
|
-
fullName: target,
|
|
37
|
-
owner,
|
|
38
|
-
repo: 'list',
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Parse owner/repo:...
|
|
43
|
-
const [ownerRepo, ...rest] = target.split(':');
|
|
44
|
-
const slashParts = ownerRepo.split('/');
|
|
45
|
-
|
|
46
|
-
if (slashParts.length < 2) {
|
|
47
|
-
throw new Error(`Invalid target format: ${target}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const owner = slashParts[0];
|
|
51
|
-
const repo = slashParts[1];
|
|
52
|
-
|
|
53
|
-
if (!owner || !repo) {
|
|
54
|
-
throw new Error(`Invalid target format: ${target}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Capability full format: owner/repo:plugin:type:name
|
|
58
|
-
if (rest.length === 3) {
|
|
59
|
-
const [plugin, capType, capName] = rest;
|
|
60
|
-
return {
|
|
61
|
-
type: TargetType.Capability,
|
|
62
|
-
fullName: target,
|
|
63
|
-
owner,
|
|
64
|
-
repo,
|
|
65
|
-
plugin,
|
|
66
|
-
capType,
|
|
67
|
-
capName,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Plugin format: owner/repo:plugin
|
|
72
|
-
if (rest.length === 1) {
|
|
73
|
-
return {
|
|
74
|
-
type: TargetType.Plugin,
|
|
75
|
-
fullName: target,
|
|
76
|
-
owner,
|
|
77
|
-
repo,
|
|
78
|
-
plugin: rest[0],
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Simple format: author/slug (no colon, just two parts)
|
|
83
|
-
// This is the new short slug format for capabilities
|
|
84
|
-
if (rest.length === 0 && slashParts.length === 2) {
|
|
85
|
-
return {
|
|
86
|
-
type: TargetType.CapabilitySlug,
|
|
87
|
-
fullName: target,
|
|
88
|
-
owner,
|
|
89
|
-
repo,
|
|
90
|
-
author: owner,
|
|
91
|
-
slug: repo,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
throw new Error(`Invalid target format: ${target}`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Get install path for a capability type
|
|
100
|
-
*/
|
|
101
|
-
export function getInstallPath(type: string, name: string): string {
|
|
102
|
-
switch (type) {
|
|
103
|
-
case 'skill':
|
|
104
|
-
return `.claude/skills/${name}/`;
|
|
105
|
-
case 'agent':
|
|
106
|
-
return `.claude/agents/${name}.md`;
|
|
107
|
-
case 'command':
|
|
108
|
-
return `.claude/commands/${name}.md`;
|
|
109
|
-
case 'hook':
|
|
110
|
-
return `.claude/hooks/${name}`;
|
|
111
|
-
default:
|
|
112
|
-
return `.claude/${type}/${name}/`;
|
|
113
|
-
}
|
|
114
|
-
}
|