deepfish-ai 1.0.8
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/LICENSE +21 -0
- package/README.md +344 -0
- package/README_CN.md +344 -0
- package/package.json +56 -0
- package/src/cli.js +650 -0
- package/src/core/AICLI.js +93 -0
- package/src/core/DefaultConfig.js +14 -0
- package/src/core/GlobalVariable.js +10 -0
- package/src/core/ai-services/AIService.js +25 -0
- package/src/core/ai-services/AiWorker/AIMessageManager.js +155 -0
- package/src/core/ai-services/AiWorker/AiAgent.js +151 -0
- package/src/core/ai-services/AiWorker/AiPrompt.js +37 -0
- package/src/core/ai-services/AiWorker/AiRecorder.js +120 -0
- package/src/core/ai-services/AiWorker/AiTools.js +219 -0
- package/src/core/ai-services/AiWorker/index.js +88 -0
- package/src/core/extension/BaseExtension.js +7 -0
- package/src/core/extension/DefaultExtension.js +696 -0
- package/src/core/extension/ExtensionManager.js +172 -0
- package/src/core/utils.js +261 -0
- package/src/index.js +7 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const { descriptions, functions } = require('./DefaultExtension')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const fs = require('fs-extra')
|
|
4
|
+
const axios = require('axios')
|
|
5
|
+
const dayjs = require('dayjs')
|
|
6
|
+
const lodash = require('lodash')
|
|
7
|
+
const shelljs = require('shelljs')
|
|
8
|
+
const iconv = require('iconv-lite') // 用于编码转换
|
|
9
|
+
const os = require('os') // 用于判断系统类型
|
|
10
|
+
const { logError } = require('../utils')
|
|
11
|
+
|
|
12
|
+
class ExtensionManager {
|
|
13
|
+
constructor(aiCli) {
|
|
14
|
+
this.aiCli = aiCli
|
|
15
|
+
this.extensions = {
|
|
16
|
+
descriptions,
|
|
17
|
+
functions,
|
|
18
|
+
}
|
|
19
|
+
this.parseExtends(this.aiCli.config.extensions || [])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
parseExtends(configExtends) {
|
|
23
|
+
// 自动扫描扩展模块
|
|
24
|
+
const autoScannedExtends = this.autoScanExtensions()
|
|
25
|
+
configExtends = configExtends.concat(autoScannedExtends)
|
|
26
|
+
for (const extensionPath of configExtends) {
|
|
27
|
+
try {
|
|
28
|
+
// 解析扩展路径
|
|
29
|
+
const resolvedPath = path.isAbsolute(extensionPath)
|
|
30
|
+
? extensionPath
|
|
31
|
+
: path.resolve(process.cwd(), extensionPath)
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
34
|
+
logError(`Extension file not found: ${resolvedPath}`)
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 动态加载扩展模块
|
|
39
|
+
let { descriptions, functions } = require(resolvedPath)
|
|
40
|
+
descriptions = descriptions.map((item) => {
|
|
41
|
+
if (!item.type) {
|
|
42
|
+
return {
|
|
43
|
+
type: 'function',
|
|
44
|
+
function: item,
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
return item
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
this.extensions.descriptions =
|
|
51
|
+
this.extensions.descriptions.concat(descriptions)
|
|
52
|
+
this.extensions.functions = Object.assign(
|
|
53
|
+
this.extensions.functions,
|
|
54
|
+
functions,
|
|
55
|
+
)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logError(`Error loading extension ${extensionPath}: ${error.message}`)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const functions = this.extensions.functions
|
|
61
|
+
for (const fnName of Object.keys(functions)) {
|
|
62
|
+
functions[fnName] = functions[fnName].bind(this.aiCli)
|
|
63
|
+
if (fnName === 'test') {
|
|
64
|
+
functions[fnName]()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
functions['fs'] = fs
|
|
68
|
+
functions['axios'] = axios
|
|
69
|
+
functions['dayjs'] = dayjs
|
|
70
|
+
functions['lodash'] = lodash
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 自动扫描node_modules和命令执行目录下的扩展模块
|
|
74
|
+
autoScanExtensions() {
|
|
75
|
+
const result = []
|
|
76
|
+
// 扫描本程序所在目录下node_modules目录
|
|
77
|
+
const nodeModulesPath1 = path.resolve(__dirname, '../../../node_modules')
|
|
78
|
+
// 扫描根node_modules目录
|
|
79
|
+
const nodeModulesPath2 = this._executeCommand('npm root -g')
|
|
80
|
+
// 扫描命令执行目录下node_modules目录
|
|
81
|
+
const nodeModulesPath3 = path.resolve(process.cwd(), 'node_modules')
|
|
82
|
+
// 扫描命令执行目录
|
|
83
|
+
const nodeModulesPath4 = process.cwd()
|
|
84
|
+
for (const dirPath of [
|
|
85
|
+
nodeModulesPath1,
|
|
86
|
+
nodeModulesPath2,
|
|
87
|
+
nodeModulesPath3,
|
|
88
|
+
nodeModulesPath4,
|
|
89
|
+
]) {
|
|
90
|
+
if (!fs.existsSync(dirPath)) {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
const fileNames = fs.readdirSync(dirPath)
|
|
94
|
+
for (const fileName of fileNames) {
|
|
95
|
+
// 如果是目录且目录名称前缀是"deepfish-",则认为是扩展模块
|
|
96
|
+
const extensionDir = path.resolve(dirPath, fileName)
|
|
97
|
+
if (
|
|
98
|
+
fileName.startsWith('deepfish-') && fileName !== 'deepfish-ai' &&
|
|
99
|
+
fs.statSync(extensionDir).isDirectory()
|
|
100
|
+
) {
|
|
101
|
+
const subDirNames = fs.readdirSync(extensionDir)
|
|
102
|
+
for (const subDirName of subDirNames) {
|
|
103
|
+
const subDirPath = path.resolve(extensionDir, subDirName)
|
|
104
|
+
if (fs.statSync(subDirPath).isDirectory()) {
|
|
105
|
+
const extNames = fs.readdirSync(subDirPath)
|
|
106
|
+
const jsFiles = extNames.filter(
|
|
107
|
+
(file) => file.endsWith('.js') || file.endsWith('.cjs'),
|
|
108
|
+
)
|
|
109
|
+
jsFiles.forEach((jsFile) => {
|
|
110
|
+
const jsFilePath = path.resolve(subDirPath, jsFile)
|
|
111
|
+
// 读取文件,查询文件内是否存在‘descriptions’和‘functions’
|
|
112
|
+
const fileContent = fs.readFileSync(jsFilePath, 'utf-8')
|
|
113
|
+
if (
|
|
114
|
+
fileContent.includes('descriptions') &&
|
|
115
|
+
fileContent.includes('functions')
|
|
116
|
+
) {
|
|
117
|
+
result.push(jsFilePath)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return result
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_executeCommand(command) {
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const platform = os.platform()
|
|
131
|
+
const targetEncoding = platform === 'win32' ? 'gbk' : 'utf-8' // Windows(含PowerShell)用gbk,Linux/macOS用utf-8
|
|
132
|
+
shelljs.exec(
|
|
133
|
+
command,
|
|
134
|
+
{
|
|
135
|
+
async: true,
|
|
136
|
+
cwd: process.cwd(),
|
|
137
|
+
encoding: 'binary',
|
|
138
|
+
silent: true,
|
|
139
|
+
},
|
|
140
|
+
(code, stdout, stderr) => {
|
|
141
|
+
try {
|
|
142
|
+
const stdoutUtf8 = iconv.decode(
|
|
143
|
+
Buffer.from(stdout, 'binary'),
|
|
144
|
+
targetEncoding,
|
|
145
|
+
)
|
|
146
|
+
const stderrUtf8 = iconv.decode(
|
|
147
|
+
Buffer.from(stderr, 'binary'),
|
|
148
|
+
targetEncoding,
|
|
149
|
+
)
|
|
150
|
+
if (stderrUtf8 && !stderrUtf8.trim().startsWith('WARNING')) {
|
|
151
|
+
// 过滤无关警告
|
|
152
|
+
const error = new Error(
|
|
153
|
+
`Command failed (code ${code}): ${stderrUtf8}`,
|
|
154
|
+
)
|
|
155
|
+
reject(error)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
resolve(stdoutUtf8)
|
|
159
|
+
} catch (decodeError) {
|
|
160
|
+
reject(
|
|
161
|
+
new Error(
|
|
162
|
+
`Failed to parse command output: ${decodeError.message}`,
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = ExtensionManager
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const fs = require("fs-extra");
|
|
5
|
+
|
|
6
|
+
// 日志相关工具函数
|
|
7
|
+
function logInfo(message) {
|
|
8
|
+
log(message, "#6dd2ea");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function logSuccess(message) {
|
|
12
|
+
log(message, "#9bed7f");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function logError(message) {
|
|
16
|
+
log(message, "#ed7f7f");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function writeLine(msg1, msg2 = "", color = "blue") {
|
|
20
|
+
if (color === "blue") {
|
|
21
|
+
process.stdout.write("\r" + chalk.hex("#6dd2ea")(msg1) + " " + msg2);
|
|
22
|
+
} else if (color === "green") {
|
|
23
|
+
process.stdout.write("\r" + chalk.hex("#9bed7f")(msg1) + " " + msg2);
|
|
24
|
+
} else if (color === "red") {
|
|
25
|
+
process.stdout.write("\r" + chalk.hex("#ed7f7f")(msg1) + " " + msg2);
|
|
26
|
+
} else {
|
|
27
|
+
process.stdout.write("\r" + chalk.hex(color)(msg1) + " " + msg2);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 流式输出
|
|
32
|
+
async function streamOutput(text, color = "#9bed7f") {
|
|
33
|
+
process.stdout.write(chalk.hex(color)(text));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 流式换行
|
|
37
|
+
async function streamLineBreak() {
|
|
38
|
+
process.stdout.write("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function objStrToObj(str) {
|
|
42
|
+
try {
|
|
43
|
+
if (typeof str === 'string') {
|
|
44
|
+
return eval(`(${str})`);
|
|
45
|
+
} else {
|
|
46
|
+
return str
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
throw new Error(`对象转换失败:${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function delay(ms) {
|
|
56
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function loading(label = "Thinking...") {
|
|
60
|
+
let animationInterval;
|
|
61
|
+
const spinners = ["|", "/", "-", "\\"];
|
|
62
|
+
let spinnerIndex = 0;
|
|
63
|
+
process.stdout.write("\r");
|
|
64
|
+
animationInterval = setInterval(() => {
|
|
65
|
+
writeLine(spinners[spinnerIndex], label);
|
|
66
|
+
spinnerIndex = (spinnerIndex + 1) % spinners.length;
|
|
67
|
+
}, 200);
|
|
68
|
+
return (endLabel, isError = false) => {
|
|
69
|
+
clearInterval(animationInterval);
|
|
70
|
+
if (endLabel) {
|
|
71
|
+
writeLine(endLabel, "", isError ? "red" : "green");
|
|
72
|
+
}
|
|
73
|
+
process.stdout.write("\r\n");
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function log(msg, color) {
|
|
78
|
+
if (!color) {
|
|
79
|
+
console.log(msg);
|
|
80
|
+
} else {
|
|
81
|
+
console.log(chalk.hex(color)(msg));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 添加扩展
|
|
86
|
+
function addExtensionToConfig(fileName) {
|
|
87
|
+
// 检查 fileName 是否为空
|
|
88
|
+
if (!fileName) {
|
|
89
|
+
logError("Extension file name is required.");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const filePath = path.resolve(process.cwd(), fileName);
|
|
93
|
+
// 判断是否路径是文件还是目录
|
|
94
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
95
|
+
// 扫描目录和子目录下所有js、cjs文件
|
|
96
|
+
const files = traverseFiles()
|
|
97
|
+
const jsFiles = files.filter((file) => file.endsWith(".js") || file.endsWith(".cjs"));
|
|
98
|
+
jsFiles.forEach((jsFile) => {
|
|
99
|
+
// 读取文件,查询文件内是否存在‘descriptions’和‘functions’
|
|
100
|
+
const fileContent = fs.readFileSync(jsFile, "utf-8");
|
|
101
|
+
if (fileContent.includes("descriptions") && fileContent.includes("functions")) {
|
|
102
|
+
addExtensionToConfig(jsFile);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// 判断文件是否存在
|
|
108
|
+
if (!fs.existsSync(filePath)) {
|
|
109
|
+
logError(`File not found: ${filePath}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const userConfigPath = path.join(os.homedir(), ".ai-cmd.config.js");
|
|
113
|
+
if (!fs.existsSync(userConfigPath)) {
|
|
114
|
+
logError(
|
|
115
|
+
`User config file not found: ${userConfigPath}. Please run 'ai config reset' first.`,
|
|
116
|
+
);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const userConfig = require(userConfigPath);
|
|
120
|
+
if (userConfig.extensions && Array.isArray(userConfig.extensions)) {
|
|
121
|
+
userConfig.extensions.push(filePath);
|
|
122
|
+
} else {
|
|
123
|
+
userConfig.extensions = [filePath];
|
|
124
|
+
}
|
|
125
|
+
// 数组去重
|
|
126
|
+
userConfig.extensions = [...new Set(userConfig.extensions)];
|
|
127
|
+
fs.writeFileSync(
|
|
128
|
+
userConfigPath,
|
|
129
|
+
`module.exports = ${JSON.stringify(userConfig, null, 2)}`,
|
|
130
|
+
);
|
|
131
|
+
logSuccess(
|
|
132
|
+
`Extension added to config: ${filePath}.`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 移除扩展
|
|
137
|
+
function removeExtensionFromConfig(fileName) {
|
|
138
|
+
const userConfigPath = path.join(os.homedir(), ".ai-cmd.config.js");
|
|
139
|
+
if (!fs.existsSync(userConfigPath)) {
|
|
140
|
+
logError(
|
|
141
|
+
`User config file not found: ${userConfigPath}. Please run 'ai config reset' first.`,
|
|
142
|
+
);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const userConfig = require(userConfigPath);
|
|
146
|
+
// 增加对数字索引的支持
|
|
147
|
+
if (!isNaN(Number(fileName))) {
|
|
148
|
+
const extIndex = Number(fileName);
|
|
149
|
+
if (extIndex < 0 || extIndex >= userConfig.extensions.length) {
|
|
150
|
+
logError(`Invalid extension index: ${extIndex}`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const filePath = userConfig.extensions.splice(extIndex, 1);
|
|
154
|
+
fs.writeFileSync(
|
|
155
|
+
userConfigPath,
|
|
156
|
+
`module.exports = ${JSON.stringify(userConfig, null, 2)}`,
|
|
157
|
+
);
|
|
158
|
+
logSuccess(
|
|
159
|
+
`Extension removed from config: ${filePath}.You can run 'ai ext ls' to view the changes.`,
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const filePath = path.resolve(process.cwd(), fileName);
|
|
164
|
+
// 判断文件是否存在
|
|
165
|
+
if (!fs.existsSync(filePath)) {
|
|
166
|
+
logError(`File not found: ${filePath}`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (userConfig.extensions && Array.isArray(userConfig.extensions)) {
|
|
170
|
+
userConfig.extensions = userConfig.extensions.filter(
|
|
171
|
+
(ext) => ext !== filePath,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
fs.writeFileSync(
|
|
175
|
+
userConfigPath,
|
|
176
|
+
`module.exports = ${JSON.stringify(userConfig, null, 2)}`,
|
|
177
|
+
);
|
|
178
|
+
logSuccess(
|
|
179
|
+
`Extension removed from config: ${filePath}.You can run 'ai ext ls' to view the changes.`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 查看扩展列表
|
|
184
|
+
function viewExtensionsFromConfig() {
|
|
185
|
+
const userConfigPath = path.join(os.homedir(), ".ai-cmd.config.js");
|
|
186
|
+
if (!fs.existsSync(userConfigPath)) {
|
|
187
|
+
logError(
|
|
188
|
+
`User config file not found: ${userConfigPath}. Please run 'ai config reset' first.`,
|
|
189
|
+
);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const userConfig = require(userConfigPath);
|
|
193
|
+
if (userConfig.extensions && Array.isArray(userConfig.extensions)) {
|
|
194
|
+
console.log("=".repeat(50));
|
|
195
|
+
// 打印扩展列表,并加上索引
|
|
196
|
+
if (userConfig.extensions.length === 0) {
|
|
197
|
+
console.log(`No extensions in config.`);
|
|
198
|
+
} else {
|
|
199
|
+
console.log("Extensions in config:");
|
|
200
|
+
userConfig.extensions.forEach((ext, index) => {
|
|
201
|
+
console.log(`[${index}] ${ext}`);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
console.log("=".repeat(50));
|
|
205
|
+
} else {
|
|
206
|
+
logSuccess(`No extensions in config.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
function traverseFiles() {
|
|
212
|
+
try {
|
|
213
|
+
const currentDir = process.cwd();
|
|
214
|
+
const allFiles = [];
|
|
215
|
+
const currentItems = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
216
|
+
for (const item of currentItems) {
|
|
217
|
+
const itemPath = path.join(currentDir, item.name);
|
|
218
|
+
if (item.isFile()) {
|
|
219
|
+
allFiles.push(itemPath);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (item.isDirectory()) {
|
|
223
|
+
try {
|
|
224
|
+
const subItems = fs.readdirSync(itemPath, { withFileTypes: true });
|
|
225
|
+
for (const subItem of subItems) {
|
|
226
|
+
if (subItem.isFile()) {
|
|
227
|
+
allFiles.push(path.join(itemPath, subItem.name));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch (subErr) {
|
|
231
|
+
console.warn(`读取子目录失败 ${itemPath}:${subErr.message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return allFiles;
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error(`遍历目录失败:${err.message}`);
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 获取配置文件所在目录
|
|
243
|
+
function getConfigPath() {
|
|
244
|
+
return path.join(os.homedir(), ".ai-cmd.config.js");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
logInfo,
|
|
249
|
+
logSuccess,
|
|
250
|
+
logError,
|
|
251
|
+
loading,
|
|
252
|
+
writeLine,
|
|
253
|
+
streamOutput,
|
|
254
|
+
streamLineBreak,
|
|
255
|
+
addExtensionToConfig,
|
|
256
|
+
removeExtensionFromConfig,
|
|
257
|
+
viewExtensionsFromConfig,
|
|
258
|
+
objStrToObj,
|
|
259
|
+
delay,
|
|
260
|
+
getConfigPath
|
|
261
|
+
};
|