@42ailab/42plugin 0.1.0-beta.0 → 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.
@@ -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
+ }