@xubill/xx-cli 2.0.4

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,295 @@
1
+ /**
2
+ * AI 智能解释插件
3
+ * 用于解释 xx-cli 命令的含义,将自然语言转换为命令
4
+ *
5
+ * 命令说明:
6
+ * - ai <query>:解释用户输入的自然语言,转换为对应的 xx 命令
7
+ * - 示例:
8
+ * - xx ai 关闭微信
9
+ * - xx ai 打开浏览器
10
+ * - xx ai 端口占用
11
+ */
12
+
13
+ const axios = require('axios');
14
+ const BasePlugin = require('../core/base-plugin');
15
+
16
+ class AIPlugin extends BasePlugin {
17
+ constructor() {
18
+ super();
19
+ this.pluginName = 'ai';
20
+ }
21
+
22
+ registerCommands(program) {
23
+ const aiCommand = program.command('ai')
24
+ .description('AI 智能解释命令,将自然语言转换为 xx 命令')
25
+ .option('-c, --config <path>', '指定配置文件路径')
26
+ .arguments('[query...]')
27
+ .action(async (query, options) => {
28
+ await this.handleAI(query || [], options);
29
+ });
30
+
31
+ // 添加生成配置文件的子命令
32
+ aiCommand.command('config')
33
+ .description('生成 AI 插件配置文件')
34
+ .option('-o, --output <path>', '输出配置文件路径', '.ai-config.json')
35
+ .action(async (options) => {
36
+ await this.generateConfig(options);
37
+ });
38
+
39
+ // 添加快捷添加 API key 的子命令
40
+ aiCommand.command('add')
41
+ .description('快捷添加 API key')
42
+ .arguments('<apiKey>')
43
+ .action(async (apiKey) => {
44
+ await this.addApiKey(apiKey);
45
+ });
46
+ }
47
+
48
+ /**
49
+ * 快捷添加 API key
50
+ * @param {string} apiKey - API key
51
+ */
52
+ async addApiKey(apiKey) {
53
+ try {
54
+ this.startLoading('正在保存 API key...');
55
+
56
+ const defaultConfig = {
57
+ apiUrl: 'https://api.edgefn.net/v1/chat/completions',
58
+ apiKey: apiKey,
59
+ model: 'DeepSeek-R1-0528-Qwen3-8B'
60
+ };
61
+
62
+ // 保存到全局配置
63
+ this.saveConfig(defaultConfig, 'config', true);
64
+
65
+ this.stopLoading('API key 保存成功');
66
+ this.showSuccess('API key 已保存到全局配置');
67
+ this.showInfo('下次使用无需指定 --config 选项');
68
+
69
+ console.log('\n使用示例:');
70
+ console.log(' xx ai 关闭3000端口');
71
+ console.log(' xx ai 生成readme');
72
+ } catch (error) {
73
+ this.stopLoading();
74
+ this.showError(`保存 API key 失败: ${error.message}`);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * 生成配置文件
80
+ * @param {Object} options - 命令选项
81
+ */
82
+ async generateConfig(options) {
83
+ const defaultConfig = {
84
+ apiUrl: 'https://api.edgefn.net/v1/chat/completions',
85
+ apiKey: '',
86
+ model: 'DeepSeek-R1-0528-Qwen3-8B'
87
+ };
88
+
89
+ const outputPath = options.output;
90
+
91
+ try {
92
+ this.startLoading(`正在生成配置文件到 ${outputPath}...`);
93
+
94
+ // 写入配置文件
95
+ const fs = require('fs');
96
+ fs.writeFileSync(outputPath, JSON.stringify(defaultConfig, null, 2));
97
+
98
+ this.stopLoading(`配置文件已生成到 ${outputPath}`);
99
+ this.showSuccess(`配置文件已生成,请根据需要修改其中的参数`);
100
+
101
+ console.log('\n配置文件格式说明:');
102
+ console.log(' apiUrl: AI API 接口地址');
103
+ console.log(' apiKey: API 密钥');
104
+ console.log(' model: 使用的模型名称');
105
+ } catch (error) {
106
+ this.stopLoading();
107
+ this.showError(`生成配置文件失败: ${error.message}`);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * 获取所有可用命令列表
113
+ * @returns {Promise<Array>} 命令数组
114
+ */
115
+ async getAllCommands() {
116
+ const core = require('../core');
117
+ const { Command } = require('commander');
118
+ const tempProgram = new Command();
119
+ tempProgram.name('xx').version('2.0.0');
120
+
121
+ // 注册所有命令
122
+ core.registerCommands(tempProgram);
123
+
124
+ // 收集命令
125
+ const commands = [];
126
+ if (tempProgram.commands) {
127
+ tempProgram.commands.forEach((command) => {
128
+ const commandName = command._name;
129
+ if (commandName && commandName !== '*') {
130
+ // 获取命令描述
131
+ const description = command.description() || '';
132
+ commands.push({ name: commandName, description });
133
+ }
134
+ });
135
+ }
136
+
137
+ return commands;
138
+ }
139
+
140
+ /**
141
+ * 加载配置
142
+ * @param {Object} options - 命令行选项
143
+ * @returns {Object} 配置对象
144
+ */
145
+ loadAIConfig(options) {
146
+ const defaultConfig = {
147
+ apiUrl: 'https://api.edgefn.net/v1/chat/completions',
148
+ apiKey: '',
149
+ model: 'DeepSeek-R1-0528-Qwen3-8B'
150
+ };
151
+
152
+ // 检查是否通过 --config 指定了配置文件
153
+ if (options.config) {
154
+ try {
155
+ const fs = require('fs');
156
+ const customConfig = JSON.parse(fs.readFileSync(options.config, 'utf8'));
157
+ // 保存到全局配置
158
+ this.saveConfig(customConfig, 'config', true);
159
+ this.showInfo(`配置已保存到全局,下次使用无需指定 --config 选项`);
160
+ } catch (error) {
161
+ this.showError(`读取配置文件失败: ${error.message}`);
162
+ }
163
+ }
164
+
165
+ // 加载配置文件(此时会包含全局配置)
166
+ const config = this.loadConfig('config', options);
167
+
168
+ const aiConfig = {
169
+ apiUrl: config.apiUrl || defaultConfig.apiUrl,
170
+ apiKey: config.apiKey || defaultConfig.apiKey,
171
+ model: config.model || defaultConfig.model
172
+ };
173
+
174
+ // 检查是否有 API key
175
+ if (!aiConfig.apiKey) {
176
+ this.showWarning('未配置 API key');
177
+ console.log('请访问以下链接获取 API key:');
178
+ console.log('https://ai.baishan.com/auth/login?referralCode=9CbeQycJJP');
179
+
180
+ // 生成配置文件
181
+ const fs = require('fs');
182
+ const configPath = '.ai-config.json';
183
+ try {
184
+ fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
185
+ this.showInfo(`已生成配置文件到 ${configPath}`);
186
+ console.log('请在配置文件中填写您的 API key:');
187
+ console.log(` $ vim ${configPath}`);
188
+ } catch (error) {
189
+ this.showError(`生成配置文件失败: ${error.message}`);
190
+ }
191
+
192
+ // 尝试打开浏览器
193
+ try {
194
+ const open = require('open');
195
+ open('https://ai.baishan.com/auth/login?referralCode=9CbeQycJJP');
196
+ this.showInfo('正在打开浏览器...');
197
+ } catch (error) {
198
+ // 忽略打开浏览器的错误
199
+ }
200
+ console.log('\n快捷使用:');
201
+ console.log(' 直接添加 API key: xx ai add <apiKey>');
202
+ console.log(' 示例: xx ai add sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
203
+
204
+ console.log('\n完全自定义使用方法:');
205
+ console.log(' 1. 编辑配置文件');
206
+ console.log(' 2. 使用命令: xx ai --config .ai-config.json <查询内容>');
207
+ console.log(' 3. 配置会自动保存,下次使用无需指定 --config 选项');
208
+
209
+ process.exit(1);
210
+ }
211
+
212
+ return aiConfig;
213
+ }
214
+
215
+ /**
216
+ * 调用 AI API 解释命令
217
+ * @param {string} query - 用户查询
218
+ * @param {Array} commands - 可用命令列表
219
+ * @param {Object} config - AI 配置
220
+ * @returns {Promise<string>} AI 返回的解释
221
+ */
222
+ async callAI(query, commands, config) {
223
+ const commandList = commands.map(c => ` - ${c.name}: ${c.description}`).join('\n');
224
+
225
+ const prompt = `你是一个命令行工具助手。用户使用 xx-cli 工具集,你需要解释用户的意图并给出对应的 xx 命令。\n\n可用命令列表:\n${commandList}\n\n规则:\n1. 如果用户的意图可以匹配到上述命令,返回对应的 xx 命令和简要说明,命令必须以 "xx" 开头\n2. 如果无法匹配,返回 "无法匹配" 并给出最相关的命令建议,建议的命令也必须以 "xx" 开头\n3. 直接返回结果,不需要额外解释\n\n用户输入: ${query}`;
226
+
227
+ const response = await axios.post(
228
+ config.apiUrl,
229
+ {
230
+ model: config.model,
231
+ messages: [{ role: 'user', content: prompt }]
232
+ },
233
+ {
234
+ headers: {
235
+ 'Authorization': `Bearer ${config.apiKey}`,
236
+ 'Content-Type': 'application/json'
237
+ }
238
+ }
239
+ );
240
+
241
+ return response.data.choices[0].message.content;
242
+ }
243
+
244
+ /**
245
+ * 主处理函数
246
+ * @param {Array} args - 命令参数
247
+ * @param {Object} options - 命令选项
248
+ */
249
+ async handleAI(args, options) {
250
+ // 处理参数,确保 args 是数组
251
+ let queryArgs = args;
252
+ if (!Array.isArray(args)) {
253
+ // 如果 args 不是数组,可能是因为 Commander.js 版本差异
254
+ // 检查 options 是否包含 args
255
+ if (options && options.args) {
256
+ queryArgs = options.args;
257
+ } else {
258
+ queryArgs = [];
259
+ }
260
+ }
261
+
262
+ if (!queryArgs || queryArgs.length === 0) {
263
+ this.showError('请输入要解释的内容,例如: xx ai 关闭微信');
264
+ console.log('\n用法: xx ai <查询内容>');
265
+ console.log('示例:');
266
+ console.log(' xx ai 关闭占用3000端口的进程');
267
+ console.log(' xx ai 生成readme');
268
+ console.log(' xx ai 查看ip地址');
269
+ return;
270
+ }
271
+
272
+ const query = queryArgs.join(' ');
273
+
274
+ try {
275
+ this.startLoading('正在获取命令列表...');
276
+ const commands = await this.getAllCommands();
277
+ this.stopLoading('命令列表获取成功');
278
+
279
+ this.startLoading('正在加载配置...');
280
+ const config = this.loadAIConfig(options);
281
+ this.stopLoading('配置加载成功');
282
+
283
+ this.startLoading('正在调用 AI 解释...');
284
+ const result = await this.callAI(query, commands, config);
285
+ this.stopLoading('解释完成');
286
+
287
+ console.log('\n' + result + '\n');
288
+ } catch (error) {
289
+ this.stopLoading();
290
+ this.showError(`调用失败: ${error.message}`);
291
+ }
292
+ }
293
+ }
294
+
295
+ module.exports = AIPlugin;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 配置管理插件
3
+ * 用于管理所有插件的配置文件
4
+ *
5
+ * 命令说明:
6
+ * - config-manager [options]:管理所有插件的配置文件
7
+ * - 示例:config-manager export
8
+ * - 示例:config-manager import
9
+ * - 示例:config-manager list
10
+ *
11
+ * 功能说明:
12
+ * - 导出所有插件配置到指定目录
13
+ * - 从指定目录导入配置并覆盖
14
+ * - 列出所有插件的配置文件位置和状态
15
+ *
16
+ * 用户体验:
17
+ * - 使用标准化的用户提示
18
+ * - 提供清晰的错误提示
19
+ * - 支持命令行参数
20
+ */
21
+
22
+ const fs = require('fs-extra');
23
+ const path = require('path');
24
+
25
+ /**
26
+ * 配置管理类
27
+ * 封装所有配置管理相关的方法
28
+ */
29
+ class ConfigManager {
30
+ constructor() {
31
+ // 获取用户主目录,添加 process.cwd() 作为 fallback
32
+ this.homeDir = process.env.HOME || process.env.USERPROFILE || process.cwd();
33
+ this.xxDir = path.join(this.homeDir, '.xx-cli');
34
+ }
35
+
36
+ /**
37
+ * 导出配置
38
+ * @param {string} target - 目标路径
39
+ * @param {Object} helper - 插件帮助器
40
+ */
41
+ exportConfig(target, helper) {
42
+ const exportDir = target || path.join(process.cwd(), 'config-backup');
43
+ helper.showInfo(`开始导出配置到: ${exportDir}`);
44
+
45
+ // 确保导出目录存在
46
+ fs.ensureDirSync(exportDir);
47
+
48
+ // 复制 .xx 目录
49
+ if (fs.existsSync(this.xxDir)) {
50
+ const exportPath = path.join(exportDir, '.xx-cli');
51
+ fs.ensureDirSync(path.dirname(exportPath));
52
+ fs.copySync(this.xxDir, exportPath);
53
+ helper.showInfo(`导出目录: ${this.xxDir}`);
54
+ }
55
+
56
+ helper.showSuccess('配置导出完成!');
57
+ }
58
+
59
+ /**
60
+ * 导入配置
61
+ * @param {string} source - 源路径
62
+ * @param {Object} helper - 插件帮助器
63
+ */
64
+ importConfig(source, helper) {
65
+ const importDir = source || path.join(process.cwd(), 'config-backup');
66
+ helper.showInfo(`开始从 ${importDir} 导入配置...`);
67
+
68
+ // 检查导入目录是否存在
69
+ if (!fs.existsSync(importDir)) {
70
+ helper.showError(`导入目录不存在: ${importDir}`);
71
+ return;
72
+ }
73
+
74
+ // 复制配置到 .xx 目录
75
+ const importPath = path.join(importDir, '.xx-cli');
76
+ if (fs.existsSync(importPath)) {
77
+ fs.ensureDirSync(path.dirname(this.xxDir));
78
+ fs.copySync(importPath, this.xxDir, { overwrite: true });
79
+ helper.showInfo(`导入目录: ${importPath}`);
80
+ }
81
+
82
+ helper.showSuccess('配置导入完成!');
83
+ }
84
+
85
+ /**
86
+ * 列出配置
87
+ * @param {Object} helper - 插件帮助器
88
+ */
89
+ listConfig(helper) {
90
+ helper.showInfo('列出所有插件配置文件...\n');
91
+
92
+ if (fs.existsSync(this.xxDir)) {
93
+ const items = fs.readdirSync(this.xxDir).filter(item => !item.startsWith('.') && item !== 'plugins');
94
+ items.forEach(item => {
95
+ const itemPath = path.join(this.xxDir, item);
96
+ const stat = fs.statSync(itemPath);
97
+ if (stat.isDirectory()) {
98
+ helper.showInfo(`插件配置目录: ${itemPath}`);
99
+ const configFiles = fs.readdirSync(itemPath).filter(file => !file.startsWith('.'));
100
+ configFiles.forEach(file => {
101
+ helper.showInfo(` - ${file}`);
102
+ });
103
+ } else if (stat.isFile()) {
104
+ helper.showInfo(`配置文件: ${itemPath}`);
105
+ }
106
+ });
107
+ } else {
108
+ helper.showWarning('配置目录不存在');
109
+ }
110
+
111
+ helper.showSuccess('配置列表完成!');
112
+ }
113
+ }
114
+
115
+ // 创建配置管理实例
116
+ const configManager = new ConfigManager();
117
+
118
+ module.exports = {
119
+ name: 'config-manager',
120
+ alias: 'conf',
121
+ description: '管理所有插件的配置文件',
122
+ subcommands: [
123
+ {
124
+ name: 'export',
125
+ description: '导出所有插件配置到指定目录,默认导出到当前目录的 config-backup 文件夹',
126
+ action: (args, options, helper) => {
127
+ configManager.exportConfig(args.target, helper);
128
+ },
129
+ args: [
130
+ {
131
+ name: 'target',
132
+ description: '目标路径',
133
+ optional: true
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ name: 'import',
139
+ description: '从指定目录导入配置并覆盖,默认从当前目录的 config-backup 文件夹导入',
140
+ action: (args, options, helper) => {
141
+ configManager.importConfig(args.source, helper);
142
+ },
143
+ args: [
144
+ {
145
+ name: 'source',
146
+ description: '源路径',
147
+ optional: true
148
+ }
149
+ ]
150
+ },
151
+ {
152
+ name: 'list',
153
+ description: '列出所有插件的配置文件位置和状态',
154
+ action: (args, options, helper) => {
155
+ configManager.listConfig(helper);
156
+ }
157
+ }
158
+ ],
159
+ options: [
160
+ {
161
+ name: '-h, --help',
162
+ description: '显示帮助信息'
163
+ }
164
+ ]
165
+ };
@@ -0,0 +1,108 @@
1
+ /**
2
+ * 历史记录插件
3
+ * 用于查看命令历史记录
4
+ *
5
+ * 命令说明:
6
+ * - history [options]:查看命令历史记录
7
+ * - 示例:history
8
+ * - 示例:history --limit 20
9
+ * - 示例:history --clear
10
+ * - 示例:history --search <keyword>
11
+ *
12
+ * 功能说明:
13
+ * - 查看命令历史记录
14
+ * - 限制显示的历史记录数量
15
+ * - 清除命令历史记录
16
+ * - 搜索命令历史记录
17
+ *
18
+ * 用户体验:
19
+ * - 使用标准化的用户提示
20
+ * - 提供清晰的错误提示
21
+ * - 支持命令行参数
22
+ */
23
+
24
+ /**
25
+ * 历史记录管理类
26
+ * 封装所有历史记录相关的方法
27
+ */
28
+ class HistoryManager {
29
+ constructor() {
30
+ // 加载历史记录管理器
31
+ this.historyManager = require('../utils/history-manager');
32
+ }
33
+
34
+ /**
35
+ * 显示命令历史记录
36
+ * @param {Object} options - 选项
37
+ * @param {Object} helper - 插件帮助器
38
+ */
39
+ showHistory(options, helper) {
40
+ if (options.clear) {
41
+ // 清除命令历史记录
42
+ this.historyManager.clearHistory();
43
+ helper.showSuccess('命令历史记录已清除');
44
+ return;
45
+ }
46
+
47
+ if (options.search) {
48
+ // 搜索命令历史记录
49
+ const searchResults = this.historyManager.searchHistory(options.search);
50
+ if (searchResults.length > 0) {
51
+ helper.showInfo('\n搜索结果:');
52
+ helper.showInfo('=====================================');
53
+ searchResults.forEach((item, index) => {
54
+ const date = new Date(item.timestamp).toLocaleString();
55
+ helper.showInfo(`${index + 1}. ${item.command} (${date})`);
56
+ });
57
+ helper.showInfo('=====================================');
58
+ helper.showInfo(`总计: ${searchResults.length} 条记录\n`);
59
+ } else {
60
+ helper.showInfo(`\n未找到匹配 "${options.search}" 的命令历史记录\n`);
61
+ }
62
+ return;
63
+ }
64
+
65
+ // 显示命令历史记录
66
+ const history = this.historyManager.getHistory(options.limit);
67
+ if (history.length > 0) {
68
+ helper.showInfo('\n命令历史记录:');
69
+ helper.showInfo('=====================================');
70
+ history.forEach((item, index) => {
71
+ const date = new Date(item.timestamp).toLocaleString();
72
+ helper.showInfo(`${index + 1}. ${item.command} (${date})`);
73
+ });
74
+ helper.showInfo('=====================================');
75
+ helper.showInfo(`总计: ${history.length} 条记录\n`);
76
+ } else {
77
+ helper.showInfo('\n命令历史记录为空\n');
78
+ }
79
+ }
80
+ }
81
+
82
+ // 创建历史记录管理实例
83
+ const historyManager = new HistoryManager();
84
+
85
+ module.exports = {
86
+ name: 'history',
87
+ alias: 'h',
88
+ description: '查看命令历史记录',
89
+ options: [
90
+ {
91
+ name: '-l, --limit <number>',
92
+ description: '限制显示的历史记录数量',
93
+ parse: parseInt,
94
+ default: 10
95
+ },
96
+ {
97
+ name: '-c, --clear',
98
+ description: '清除命令历史记录'
99
+ },
100
+ {
101
+ name: '-s, --search <keyword>',
102
+ description: '搜索命令历史记录'
103
+ }
104
+ ],
105
+ action: (args, options, helper) => {
106
+ historyManager.showHistory(options, helper);
107
+ }
108
+ };