@xubill/xx-cli 2.0.0 → 2.0.1
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/AGENTS.md +177 -0
- package/lib/core/index.js +42 -9
- package/lib/plugins/ai.js +295 -0
- package/package.json +6 -1
- package/plugin-development-new.md +565 -0
- package/plugin-development-old.md +447 -0
- package/readme.md +54 -554
package/AGENTS.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
本文档描述 xx-cli 项目中各个核心模块/代理的职责和交互关系。
|
|
4
|
+
|
|
5
|
+
## 项目概述
|
|
6
|
+
|
|
7
|
+
xx-cli 是一个基于 Node.js 的命令行工具集,提供丰富的开发辅助功能。采用插件化架构,支持自定义插件开发和动态命令加载。
|
|
8
|
+
|
|
9
|
+
## 核心模块
|
|
10
|
+
|
|
11
|
+
### 1. Core (核心管理器)
|
|
12
|
+
|
|
13
|
+
**文件位置**: `lib/core/index.js`
|
|
14
|
+
|
|
15
|
+
**职责**:
|
|
16
|
+
- 初始化 CLI 系统环境
|
|
17
|
+
- 加载和管理插件(内置插件 + 用户插件)
|
|
18
|
+
- 注册命令到 Commander.js 框架
|
|
19
|
+
- 实现命令自动建议功能(当用户输入错误命令时提供相似命令建议)
|
|
20
|
+
- 管理全局钩子系统
|
|
21
|
+
|
|
22
|
+
**关键方法**:
|
|
23
|
+
- `init()`: 初始化核心功能
|
|
24
|
+
- `loadPlugins()`: 扫描并加载插件目录
|
|
25
|
+
- `loadPlugin(pluginFile)`: 加载单个插件,支持缓存
|
|
26
|
+
- `registerCommands(program)`: 注册所有命令
|
|
27
|
+
- `registerNewFormatPlugin()`: 注册新格式插件
|
|
28
|
+
- `registerCommandSuggestions()`: 注册命令自动建议
|
|
29
|
+
- `collectAllCommands()`: 收集所有可用命令
|
|
30
|
+
- `getCommandSuggestions()`: 获取命令建议
|
|
31
|
+
- `registerHook()` / `triggerHook()`: 钩子系统
|
|
32
|
+
|
|
33
|
+
### 2. BasePlugin (插件基类)
|
|
34
|
+
|
|
35
|
+
**文件位置**: `lib/core/base-plugin.js`
|
|
36
|
+
|
|
37
|
+
**职责**:
|
|
38
|
+
- 提供插件标准化接口
|
|
39
|
+
- 实现通用功能(加载状态、进度条、错误处理等)
|
|
40
|
+
- 提供配置管理和验证能力
|
|
41
|
+
- 实现钩子系统
|
|
42
|
+
|
|
43
|
+
**关键方法**:
|
|
44
|
+
- `registerCommands(program)`: 注册命令(抽象方法,子类必须实现)
|
|
45
|
+
- `init()`: 初始化插件
|
|
46
|
+
- `startLoading()` / `stopLoading()`: 加载状态管理
|
|
47
|
+
- `startProgressBar()` / `updateProgressBar()` / `stopProgressBar()`: 进度条管理
|
|
48
|
+
- `showSuccess()` / `showWarning()` / `showError()` / `showInfo()`: 消息展示
|
|
49
|
+
- `loadConfig()` / `saveConfig()` / `validateConfig()`: 配置管理
|
|
50
|
+
- `handleError()`: `executeAsync()`: 异步操作封装错误处理
|
|
51
|
+
-
|
|
52
|
+
- `registerHook()` / `triggerHook()`: 钩子系统
|
|
53
|
+
- `copyToClipboard()`: 剪贴板操作
|
|
54
|
+
|
|
55
|
+
### 3. PluginsHelper (插件辅助工具)
|
|
56
|
+
|
|
57
|
+
**文件位置**: `lib/utils/plugins-helper.js`
|
|
58
|
+
|
|
59
|
+
**职责**:
|
|
60
|
+
- 为新格式插件(非类继承)提供与 BasePlugin 相同的通用功能
|
|
61
|
+
- 无需继承即可使用插件能力
|
|
62
|
+
|
|
63
|
+
**关键方法**: 与 BasePlugin 相同(详见上方)
|
|
64
|
+
|
|
65
|
+
### 4. ConfigManager (配置管理器)
|
|
66
|
+
|
|
67
|
+
**文件位置**: `lib/utils/config-manager.js`
|
|
68
|
+
|
|
69
|
+
**职责**:
|
|
70
|
+
- 管理插件配置文件(JSON 格式)
|
|
71
|
+
- 实现配置优先级:命令行 > 配置文件 > 默认配置
|
|
72
|
+
- 使用 Joi 进行配置验证
|
|
73
|
+
- 支持全局配置和本地配置
|
|
74
|
+
|
|
75
|
+
**关键方法**:
|
|
76
|
+
- `loadConfig()`: 加载配置
|
|
77
|
+
- `saveConfig()`: 保存配置
|
|
78
|
+
- `validateConfig()`: 验证配置
|
|
79
|
+
- `getConfigPath()`: 获取配置路径
|
|
80
|
+
- `mergeConfig()`: 合并配置
|
|
81
|
+
|
|
82
|
+
### 5. PluginConfig (插件配置)
|
|
83
|
+
|
|
84
|
+
**文件位置**: `lib/utils/plugin-config.js`
|
|
85
|
+
|
|
86
|
+
**职责**:
|
|
87
|
+
- 管理插件的启用/禁用状态
|
|
88
|
+
- 提供插件配置文件路径
|
|
89
|
+
|
|
90
|
+
**关键方法**:
|
|
91
|
+
- `isPluginDisabled()`: 检查插件是否禁用
|
|
92
|
+
- `getPluginConfigPath()`: 获取插件配置路径
|
|
93
|
+
|
|
94
|
+
## 插件类型
|
|
95
|
+
|
|
96
|
+
### 核心插件 (Core Plugins)
|
|
97
|
+
|
|
98
|
+
位于 `lib/plugins/`
|
|
99
|
+
|
|
100
|
+
| 插件名称 | 功能描述 |
|
|
101
|
+
|---------|---------|
|
|
102
|
+
| config-manager | 配置管理 |
|
|
103
|
+
| history | 命令历史记录 |
|
|
104
|
+
| plugin-manager | 插件管理 |
|
|
105
|
+
|
|
106
|
+
### 功能插件 (User Plugins)
|
|
107
|
+
不要修改../plugins的引入,这个是核心代码。myplugins我会复制到安装目录的,不需要你操作,你只管生成就好。
|
|
108
|
+
位于 `lib/myplugins/`
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## 插件开发格式
|
|
112
|
+
|
|
113
|
+
### 新格式 (推荐)
|
|
114
|
+
|
|
115
|
+
使用配置对象方式,无需继承类:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
module.exports = {
|
|
119
|
+
name: 'plugin-name',
|
|
120
|
+
description: '插件描述',
|
|
121
|
+
alias: '别名',
|
|
122
|
+
options: [
|
|
123
|
+
{ name: '-o, --option', description: '选项描述' }
|
|
124
|
+
],
|
|
125
|
+
action: async (args, options, helper) => {
|
|
126
|
+
// 插件逻辑
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 旧格式 (向后兼容)
|
|
132
|
+
|
|
133
|
+
使用类继承方式:
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
const BasePlugin = require('../core/base-plugin');
|
|
137
|
+
|
|
138
|
+
class MyPlugin extends BasePlugin {
|
|
139
|
+
registerCommands(program) {
|
|
140
|
+
program.command('my-plugin')
|
|
141
|
+
.description('我的插件')
|
|
142
|
+
.action(() => { /* 插件逻辑 */ });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = MyPlugin;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 交互流程
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
用户输入命令
|
|
153
|
+
↓
|
|
154
|
+
bin/cli.js 入口
|
|
155
|
+
↓
|
|
156
|
+
Commander.js 解析
|
|
157
|
+
↓
|
|
158
|
+
Core.registerCommands() 注册命令
|
|
159
|
+
↓
|
|
160
|
+
插件 action 执行
|
|
161
|
+
↓
|
|
162
|
+
PluginsHelper/BasePlugin 提供工具方法
|
|
163
|
+
↓
|
|
164
|
+
命令完成,输出结果
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 错误处理
|
|
168
|
+
|
|
169
|
+
- **命令不存在**: 通过 `registerCommandSuggestions` 提供相似命令建议
|
|
170
|
+
- **插件加载失败**: 捕获错误并跳过该插件,继续加载其他插件
|
|
171
|
+
- **配置错误**: 使用 Joi 验证并给出清晰错误信息
|
|
172
|
+
|
|
173
|
+
## 配置优先级
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
命令行选项 > 本地配置文件 > 全局配置文件 > 默认配置
|
|
177
|
+
```
|
package/lib/core/index.js
CHANGED
|
@@ -362,7 +362,7 @@ class Core {
|
|
|
362
362
|
const allCommands = this.collectAllCommands(program);
|
|
363
363
|
|
|
364
364
|
// 添加命令自动建议
|
|
365
|
-
program.on("command:*", (args) => {
|
|
365
|
+
program.on("command:*", async (args) => {
|
|
366
366
|
const inputCommand = args[0];
|
|
367
367
|
const suggestions = this.getCommandSuggestions(inputCommand, allCommands);
|
|
368
368
|
|
|
@@ -371,6 +371,37 @@ class Core {
|
|
|
371
371
|
suggestions.forEach((suggestion) => {
|
|
372
372
|
console.log(` - ${suggestion}`);
|
|
373
373
|
});
|
|
374
|
+
console.log("");
|
|
375
|
+
process.exit(1);
|
|
376
|
+
} else {
|
|
377
|
+
// 如果没有建议,也显示错误信息
|
|
378
|
+
console.log(`\n未找到命令 "${inputCommand}"。`);
|
|
379
|
+
console.log('使用 "xx help" 查看所有可用命令。');
|
|
380
|
+
|
|
381
|
+
// 自动调用 AI 解释命令作为兜底
|
|
382
|
+
console.log('\n正在调用 AI 解释您的输入...');
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
// 动态导入 ai 插件
|
|
386
|
+
const aiPath = path.join(__dirname, '../plugins/ai.js');
|
|
387
|
+
if (fs.existsSync(aiPath)) {
|
|
388
|
+
const AIPlugin = require(aiPath);
|
|
389
|
+
if (AIPlugin.prototype && typeof AIPlugin === 'function') {
|
|
390
|
+
// 类继承格式插件
|
|
391
|
+
const pluginInstance = new AIPlugin();
|
|
392
|
+
if (pluginInstance.handleAI) {
|
|
393
|
+
await pluginInstance.handleAI(args, {});
|
|
394
|
+
}
|
|
395
|
+
} else if (AIPlugin.action) {
|
|
396
|
+
// 配置对象格式插件
|
|
397
|
+
await AIPlugin.action(args, {});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
} catch (error) {
|
|
401
|
+
// AI 调用失败时不影响主流程
|
|
402
|
+
console.log('AI 解释失败:', error.message);
|
|
403
|
+
}
|
|
404
|
+
|
|
374
405
|
console.log("");
|
|
375
406
|
process.exit(1);
|
|
376
407
|
}
|
|
@@ -388,14 +419,12 @@ class Core {
|
|
|
388
419
|
// 收集核心命令
|
|
389
420
|
if (program.commands) {
|
|
390
421
|
program.commands.forEach((command) => {
|
|
422
|
+
// 直接使用 _name 获取命令名称
|
|
423
|
+
const commandName = command._name;
|
|
424
|
+
|
|
391
425
|
// 添加命令名称
|
|
392
|
-
if (
|
|
393
|
-
commands.push(
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// 添加命令别名
|
|
397
|
-
if (command._alias) {
|
|
398
|
-
commands.push(command._alias);
|
|
426
|
+
if (commandName && commandName !== "*") {
|
|
427
|
+
commands.push(commandName);
|
|
399
428
|
}
|
|
400
429
|
});
|
|
401
430
|
}
|
|
@@ -412,7 +441,11 @@ class Core {
|
|
|
412
441
|
getCommandSuggestions(input, commands) {
|
|
413
442
|
return commands
|
|
414
443
|
.filter((command) => {
|
|
415
|
-
|
|
444
|
+
// 确保 command 是字符串类型
|
|
445
|
+
if (typeof command !== 'string') {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
return command.toLowerCase().startsWith(input.toLowerCase());
|
|
416
449
|
})
|
|
417
450
|
.slice(0, 5); // 最多返回5个建议
|
|
418
451
|
}
|
|
@@ -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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xubill/xx-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "个人工具集",
|
|
5
5
|
"main": "lib/core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -55,8 +55,13 @@
|
|
|
55
55
|
"zx": "^8.8.5"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
+
"medium-zoom": "^1.1.0",
|
|
59
|
+
"nprogress-v2": "^1.1.10",
|
|
58
60
|
"standard-version": "^9.5.0",
|
|
59
61
|
"vitepress": "^1.6.4",
|
|
62
|
+
"vitepress-plugin-announcement": "^0.1.7",
|
|
63
|
+
"vitepress-plugin-group-icons": "^1.7.1",
|
|
64
|
+
"vitepress-sidebar": "^1.33.1",
|
|
60
65
|
"vue": "^3.5.26"
|
|
61
66
|
}
|
|
62
67
|
}
|