@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/commands/auth.ts
CHANGED
|
@@ -1,114 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auth 命令 - 认证管理
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { Command } from 'commander';
|
|
2
|
-
import open from 'open';
|
|
3
|
-
import ora from 'ora';
|
|
4
6
|
import chalk from 'chalk';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import open from 'open';
|
|
9
|
+
import { api } from '../api';
|
|
10
|
+
import { getSessionToken, saveSessionToken, clearSessionToken } from '../db';
|
|
7
11
|
|
|
8
12
|
export const authCommand = new Command('auth')
|
|
9
|
-
.description('
|
|
10
|
-
.option('--status', '
|
|
11
|
-
.option('--logout', '
|
|
13
|
+
.description('登录/登出 42plugin')
|
|
14
|
+
.option('--status', '查看登录状态')
|
|
15
|
+
.option('--logout', '登出')
|
|
12
16
|
.action(async (options) => {
|
|
13
|
-
if (options.status) {
|
|
14
|
-
await showStatus();
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
17
|
if (options.logout) {
|
|
19
18
|
await logout();
|
|
20
|
-
|
|
19
|
+
} else if (options.status) {
|
|
20
|
+
await status();
|
|
21
|
+
} else {
|
|
22
|
+
await login();
|
|
21
23
|
}
|
|
22
|
-
|
|
23
|
-
await login();
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
async function login() {
|
|
27
|
-
|
|
26
|
+
async function login(): Promise<void> {
|
|
27
|
+
// 检查是否已登录
|
|
28
|
+
const existingToken = await getSessionToken();
|
|
29
|
+
if (existingToken) {
|
|
30
|
+
api.setSessionToken(existingToken);
|
|
31
|
+
try {
|
|
32
|
+
const session = await api.getSession();
|
|
33
|
+
console.log(chalk.yellow(`已登录为 ${session.user.name || session.user.email}`));
|
|
34
|
+
console.log(chalk.gray('如需切换账号,请先执行 42plugin auth --logout'));
|
|
35
|
+
return;
|
|
36
|
+
} catch {
|
|
37
|
+
// Token 无效,继续登录流程
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const spinner = ora('正在请求设备授权码...').start();
|
|
28
42
|
|
|
29
43
|
try {
|
|
30
|
-
|
|
31
|
-
const { code, auth_url, expires_at } = await api.startAuth();
|
|
44
|
+
const deviceCode = await api.requestDeviceCode();
|
|
32
45
|
spinner.stop();
|
|
33
46
|
|
|
34
47
|
console.log();
|
|
35
|
-
console.log(chalk.cyan('
|
|
48
|
+
console.log(chalk.cyan('请在浏览器中完成登录:'));
|
|
36
49
|
console.log();
|
|
37
|
-
console.log('
|
|
38
|
-
console.log(chalk.
|
|
50
|
+
console.log(` ${chalk.bold('验证码')}: ${chalk.yellow.bold(deviceCode.userCode)}`);
|
|
51
|
+
console.log(` ${chalk.bold('链接')}: ${deviceCode.verificationUri}`);
|
|
39
52
|
console.log();
|
|
40
53
|
|
|
41
|
-
//
|
|
42
|
-
|
|
54
|
+
// 尝试打开浏览器
|
|
55
|
+
if (deviceCode.verificationUriComplete) {
|
|
56
|
+
await open(deviceCode.verificationUriComplete).catch(() => {});
|
|
57
|
+
}
|
|
43
58
|
|
|
44
|
-
//
|
|
45
|
-
|
|
59
|
+
// 轮询等待授权
|
|
60
|
+
const pollSpinner = ora('等待授权...').start();
|
|
61
|
+
const interval = (deviceCode.interval || 5) * 1000;
|
|
62
|
+
const expiresAt = Date.now() + deviceCode.expiresIn * 1000;
|
|
46
63
|
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
while (Date.now() < expiresAt) {
|
|
65
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
49
66
|
|
|
50
|
-
|
|
51
|
-
const result = await api.pollAuth(code);
|
|
67
|
+
const tokenResponse = await api.pollDeviceToken(deviceCode.deviceCode);
|
|
52
68
|
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
await saveSecrets({
|
|
56
|
-
access_token: result.access_token!,
|
|
57
|
-
refresh_token: result.refresh_token!,
|
|
58
|
-
created_at: new Date().toISOString(),
|
|
59
|
-
});
|
|
69
|
+
if (tokenResponse.accessToken && tokenResponse.user) {
|
|
70
|
+
pollSpinner.succeed('授权成功!');
|
|
60
71
|
|
|
61
|
-
//
|
|
62
|
-
|
|
72
|
+
// 保存 token
|
|
73
|
+
await saveSessionToken(tokenResponse.accessToken);
|
|
74
|
+
api.setSessionToken(tokenResponse.accessToken);
|
|
63
75
|
|
|
64
|
-
spinner.succeed(chalk.green('登录成功!'));
|
|
65
76
|
console.log();
|
|
66
|
-
console.log(
|
|
77
|
+
console.log(chalk.green(`欢迎,${tokenResponse.user.name || tokenResponse.user.username || tokenResponse.user.email}!`));
|
|
67
78
|
return;
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
|
|
81
|
+
if (tokenResponse.error === 'expired_token') {
|
|
82
|
+
pollSpinner.fail('授权码已过期,请重新执行 42plugin auth');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (tokenResponse.error === 'access_denied') {
|
|
87
|
+
pollSpinner.fail('授权被拒绝');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// authorization_pending 继续等待
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
|
|
94
|
+
pollSpinner.fail('授权超时,请重试');
|
|
95
|
+
process.exit(1);
|
|
74
96
|
} catch (error) {
|
|
75
|
-
spinner.fail(
|
|
97
|
+
spinner.fail('登录失败');
|
|
98
|
+
console.error(chalk.red((error as Error).message));
|
|
76
99
|
process.exit(1);
|
|
77
100
|
}
|
|
78
101
|
}
|
|
79
102
|
|
|
80
|
-
async function
|
|
81
|
-
|
|
103
|
+
async function logout(): Promise<void> {
|
|
104
|
+
await clearSessionToken();
|
|
105
|
+
api.setSessionToken(null);
|
|
106
|
+
console.log(chalk.green('已登出'));
|
|
107
|
+
}
|
|
82
108
|
|
|
83
|
-
|
|
109
|
+
async function status(): Promise<void> {
|
|
110
|
+
const token = await getSessionToken();
|
|
111
|
+
|
|
112
|
+
if (!token) {
|
|
84
113
|
console.log(chalk.yellow('未登录'));
|
|
85
|
-
console.log('
|
|
114
|
+
console.log(chalk.gray('执行 42plugin auth 登录'));
|
|
86
115
|
return;
|
|
87
116
|
}
|
|
88
117
|
|
|
118
|
+
api.setSessionToken(token);
|
|
119
|
+
|
|
89
120
|
try {
|
|
90
|
-
const
|
|
121
|
+
const session = await api.getSession();
|
|
91
122
|
console.log(chalk.green('已登录'));
|
|
92
|
-
console.log();
|
|
93
|
-
console.log(`用户名: ${chalk.cyan(user.username)}`);
|
|
94
|
-
console.log(`显示名: ${user.display_name || '-'}`);
|
|
95
|
-
console.log(`角色: ${user.role}`);
|
|
96
|
-
|
|
97
|
-
if (user.role === 'vip' && user.vip_expires_at) {
|
|
98
|
-
console.log(`VIP 到期: ${new Date(user.vip_expires_at).toLocaleDateString()}`);
|
|
99
|
-
}
|
|
123
|
+
console.log(` 用户: ${session.user.name || session.user.email}`);
|
|
100
124
|
} catch {
|
|
101
|
-
console.log(chalk.yellow('
|
|
102
|
-
console.log('
|
|
125
|
+
console.log(chalk.yellow('登录已过期'));
|
|
126
|
+
console.log(chalk.gray('请执行 42plugin auth 重新登录'));
|
|
103
127
|
}
|
|
104
128
|
}
|
|
105
|
-
|
|
106
|
-
async function logout() {
|
|
107
|
-
await clearSecrets();
|
|
108
|
-
api.resetToken();
|
|
109
|
-
console.log(chalk.green('已登出'));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function sleep(ms: number) {
|
|
113
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
114
|
-
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check 命令 - 检查插件格式(发布前检查)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { PluginValidator } from '../validators/plugin-validator';
|
|
8
|
+
import type { ValidationResult } from '../types';
|
|
9
|
+
|
|
10
|
+
export const checkCommand = new Command('check')
|
|
11
|
+
.description('检查插件格式(发布前检查)')
|
|
12
|
+
.argument('[path]', '插件路径(文件或目录)', '.')
|
|
13
|
+
.option('--json', 'JSON 格式输出')
|
|
14
|
+
.option('-q, --quiet', '仅显示错误')
|
|
15
|
+
.action(async (pluginPath: string, options: { json?: boolean; quiet?: boolean }) => {
|
|
16
|
+
const validator = new PluginValidator();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const result = await validator.validateFull(pluginPath);
|
|
20
|
+
|
|
21
|
+
// JSON 输出
|
|
22
|
+
if (options.json) {
|
|
23
|
+
console.log(JSON.stringify(result, null, 2));
|
|
24
|
+
process.exit(result.valid ? 0 : 1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 常规输出
|
|
28
|
+
displayResult(result, pluginPath, options.quiet);
|
|
29
|
+
|
|
30
|
+
process.exit(result.valid ? 0 : 1);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (options.json) {
|
|
33
|
+
console.log(
|
|
34
|
+
JSON.stringify(
|
|
35
|
+
{
|
|
36
|
+
valid: false,
|
|
37
|
+
errors: [{ code: 'E000', message: (error as Error).message }],
|
|
38
|
+
warnings: [],
|
|
39
|
+
},
|
|
40
|
+
null,
|
|
41
|
+
2
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
} else {
|
|
45
|
+
console.error(chalk.red((error as Error).message));
|
|
46
|
+
}
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 显示检查结果
|
|
53
|
+
*/
|
|
54
|
+
function displayResult(result: ValidationResult, pluginPath: string, quiet?: boolean): void {
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(chalk.bold(`📋 检查: ${pluginPath}`));
|
|
57
|
+
console.log();
|
|
58
|
+
|
|
59
|
+
// 元信息(非静默模式)
|
|
60
|
+
if (!quiet) {
|
|
61
|
+
const { metadata } = result;
|
|
62
|
+
console.log(` 名称: ${metadata.name || chalk.gray('(未指定)')}`);
|
|
63
|
+
console.log(` 类型: ${metadata.type}`);
|
|
64
|
+
console.log(` 路径: ${metadata.sourcePath}`);
|
|
65
|
+
|
|
66
|
+
if (metadata.description) {
|
|
67
|
+
const desc =
|
|
68
|
+
metadata.description.length > 60
|
|
69
|
+
? metadata.description.slice(0, 60) + '...'
|
|
70
|
+
: metadata.description;
|
|
71
|
+
console.log(` 描述: ${desc}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (metadata.tags && metadata.tags.length > 0) {
|
|
75
|
+
console.log(` 标签: ${metadata.tags.join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 错误
|
|
80
|
+
if (result.errors.length > 0) {
|
|
81
|
+
console.log();
|
|
82
|
+
console.log(chalk.red.bold(`❌ 错误 (${result.errors.length}):`));
|
|
83
|
+
for (const error of result.errors) {
|
|
84
|
+
console.log(chalk.red(` [${error.code}] ${error.message}`));
|
|
85
|
+
if (error.suggestion) {
|
|
86
|
+
console.log(chalk.gray(` 💡 ${error.suggestion}`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 警告(非静默模式)
|
|
92
|
+
if (result.warnings.length > 0 && !quiet) {
|
|
93
|
+
console.log();
|
|
94
|
+
console.log(chalk.yellow.bold(`⚠️ 警告 (${result.warnings.length}):`));
|
|
95
|
+
for (const warning of result.warnings) {
|
|
96
|
+
console.log(chalk.yellow(` [${warning.code}] ${warning.message}`));
|
|
97
|
+
if (warning.suggestion) {
|
|
98
|
+
console.log(chalk.gray(` 💡 ${warning.suggestion}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 总结
|
|
104
|
+
console.log();
|
|
105
|
+
if (result.valid) {
|
|
106
|
+
if (result.warnings.length > 0) {
|
|
107
|
+
console.log(chalk.green(`✅ 检查通过 (${result.warnings.length} 个警告)`));
|
|
108
|
+
} else {
|
|
109
|
+
console.log(chalk.green('✅ 检查通过'));
|
|
110
|
+
}
|
|
111
|
+
console.log();
|
|
112
|
+
console.log(chalk.gray('可以执行 42plugin publish 发布插件'));
|
|
113
|
+
} else {
|
|
114
|
+
console.log(chalk.red('❌ 检查失败'));
|
|
115
|
+
console.log();
|
|
116
|
+
console.log(chalk.gray('请修复上述错误后重试'));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 命令导出
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { authCommand } from './auth';
|
|
6
|
+
export { installCommand } from './install';
|
|
7
|
+
export { searchCommand } from './search';
|
|
8
|
+
export { listCommand } from './list';
|
|
9
|
+
export { uninstallCommand } from './uninstall';
|
|
10
|
+
export { completionCommand } from './completion';
|
|
11
|
+
export { setupCommand } from './setup';
|
|
12
|
+
export { publishCommand } from './publish';
|
|
13
|
+
export { checkCommand } from './check';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安装辅助函数 - 用于从其他命令调用安装功能
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { api, QuotaExceededError } from '../api';
|
|
9
|
+
import {
|
|
10
|
+
getOrCreateProject,
|
|
11
|
+
addInstallation,
|
|
12
|
+
createLink,
|
|
13
|
+
resolveCachePath,
|
|
14
|
+
} from '../db';
|
|
15
|
+
import { parseTarget, getTypeIcon } from '../utils';
|
|
16
|
+
import { TargetType } from '../types';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 安装单个插件(简化接口,用于交互式安装)
|
|
20
|
+
* 注意:调用方应提前初始化 API token
|
|
21
|
+
*/
|
|
22
|
+
export async function installPlugin(fullName: string): Promise<void> {
|
|
23
|
+
const parsed = parseTarget(fullName);
|
|
24
|
+
const spinner = ora(`安装 ${fullName}...`).start();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const pluginDownload = await api.getPluginDownload(parsed.author, parsed.name);
|
|
28
|
+
const downloadInfo = pluginDownload.download;
|
|
29
|
+
|
|
30
|
+
// 解析缓存
|
|
31
|
+
const { cachePath, fromCache } = await resolveCachePath(downloadInfo, false, true);
|
|
32
|
+
|
|
33
|
+
// 链接到项目
|
|
34
|
+
const projectPath = process.cwd();
|
|
35
|
+
const project = await getOrCreateProject(projectPath);
|
|
36
|
+
const linkPath = path.join(projectPath, downloadInfo.installPath);
|
|
37
|
+
|
|
38
|
+
await createLink(cachePath, linkPath);
|
|
39
|
+
|
|
40
|
+
await addInstallation({
|
|
41
|
+
projectId: project.id,
|
|
42
|
+
fullName: downloadInfo.fullName,
|
|
43
|
+
type: downloadInfo.type,
|
|
44
|
+
version: downloadInfo.version,
|
|
45
|
+
cachePath,
|
|
46
|
+
linkPath,
|
|
47
|
+
source: 'direct',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 同步安装记录(静默)
|
|
51
|
+
if (api.isAuthenticated()) {
|
|
52
|
+
api.recordInstall(downloadInfo.fullName).catch(() => {});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
spinner.succeed(
|
|
56
|
+
`${getTypeIcon(downloadInfo.type)} ${downloadInfo.fullName} v${downloadInfo.version}` +
|
|
57
|
+
(fromCache ? chalk.gray(' (from cache)') : '')
|
|
58
|
+
);
|
|
59
|
+
console.log(chalk.gray(` → ${downloadInfo.installPath}`));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
spinner.fail(`安装失败: ${fullName}`);
|
|
62
|
+
if (error instanceof QuotaExceededError) {
|
|
63
|
+
console.error(chalk.red(error.message));
|
|
64
|
+
if (error.isGuestQuota()) {
|
|
65
|
+
console.log(chalk.yellow('提示: 登录后可获得更高配额'));
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
console.error(chalk.red((error as Error).message));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|