deepfish-ai 1.0.8 → 1.0.11
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 +2 -3
- package/README_CN.md +2 -3
- package/package.json +1 -1
- package/src/cli.js +2 -2
- package/src/core/ai-services/AiWorker/AiPrompt.js +3 -3
- package/src/core/ai-services/AiWorker/index.js +5 -5
- package/src/core/config.js +187 -0
- package/src/core/extension/DefaultExtension.js +9 -2
- package/src/core/extension/ExtensionManager.js +19 -20
- package/src/core/utils/node-root.js +140 -0
- package/src/core/utils.js +34 -204
- package/src/core/DefaultConfig.js +0 -14
package/README.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<div align="center" style="display:flex;align-items: center;justify-content: center;">
|
|
2
|
-
<img src="./images/
|
|
3
|
-
<span style="font-size: 30px;font-weight: bold;color:#3386FE">DeepFish</span>
|
|
2
|
+
<img src="./images/title-img.png" alt="DeepFish" width="300" />
|
|
4
3
|
</div>
|
|
5
4
|
|
|
6
5
|
---
|
|
@@ -25,7 +24,7 @@
|
|
|
25
24
|
/>
|
|
26
25
|
</div>
|
|
27
26
|
|
|
28
|
-
<img src="./images/banner.png" alt="
|
|
27
|
+
<img src="./images/banner.png" alt="banner" style="width:100%;text-align:center;" />
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
|
package/README_CN.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<div align="center" style="display:flex;align-items: center;justify-content: center;">
|
|
2
|
-
<img src="./images/
|
|
3
|
-
<span style="font-size: 30px;font-weight: bold;color:#3386FE">DeepFish</span>
|
|
2
|
+
<img src="./images/title-img.png" alt="DeepFish" width="300" />
|
|
4
3
|
</div>
|
|
5
4
|
|
|
6
5
|
---
|
|
@@ -25,7 +24,7 @@
|
|
|
25
24
|
/>
|
|
26
25
|
</div>
|
|
27
26
|
|
|
28
|
-
<img src="./images/banner.png" alt="
|
|
27
|
+
<img src="./images/banner.png" alt="banner" style="width:100%;text-align:center;" />
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepfish-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "An AI command-line tool that converts natural language instructions into operating system commands and file operations, supporting Ollama, DeepSeek, and other models compatible with the OpenAI API specification.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/cli.js
CHANGED
|
@@ -3,9 +3,9 @@ const { program } = require("commander");
|
|
|
3
3
|
const inquirer = require("inquirer");
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const AICLI = require("./core/AICLI");
|
|
6
|
-
const { logSuccess, logError
|
|
6
|
+
const { logSuccess, logError } = require("./core/utils");
|
|
7
|
+
const { getDefaultConfig, addExtensionToConfig, removeExtensionFromConfig, viewExtensionsFromConfig, getConfigPath } = require("./core/config");
|
|
7
8
|
const userConfigPath = getConfigPath()
|
|
8
|
-
const getDefaultConfig = require("./core/DefaultConfig");
|
|
9
9
|
|
|
10
10
|
async function handleMissingConfig() {
|
|
11
11
|
logError("Configuration file not initialized");
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @Author: Roman 306863030@qq.com
|
|
3
3
|
* @Date: 2026-03-17 09:12:22
|
|
4
4
|
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
-
* @LastEditTime: 2026-03-17
|
|
6
|
-
* @FilePath: \
|
|
5
|
+
* @LastEditTime: 2026-03-17 17:04:43
|
|
6
|
+
* @FilePath: \deepfish\src\core\ai-services\AiWorker\AiPrompt.js
|
|
7
7
|
* @Description: AI请求提示词
|
|
8
8
|
* @
|
|
9
9
|
*/
|
|
@@ -19,7 +19,7 @@ const AiAgentSystemPrompt = `
|
|
|
19
19
|
### 工具使用规则
|
|
20
20
|
优先使用工具完成任务:可调用 executeJSCode 运行 Node.js 代码处理复杂逻辑;可调用 executeCommand 运行系统命令行工具(如 git、npm 等),工具调用需确保语法/指令符合当前操作系统规范(Windows/macOS/Linux 区分)。
|
|
21
21
|
|
|
22
|
-
###
|
|
22
|
+
### 大文本文件处理规则(分步执行)
|
|
23
23
|
处理长文档等大文件(单文件>20KB)时,必须按以下步骤分块处理:
|
|
24
24
|
1. 预处理:先执行文件大小/结构检查(如通过命令行/JS 代码获取文件大小、判断文件格式),输出检查结果;
|
|
25
25
|
2. 分块规则:按5KB-10KB/块拆分文件,拆分后每个块生成独立临时文件(命名格式:{原文件名}_chunk{序号}.tmp);
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @Author: Roman 306863030@qq.com
|
|
3
3
|
* @Date: 2026-03-16 09:18:05
|
|
4
4
|
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
-
* @LastEditTime: 2026-03-17
|
|
6
|
-
* @FilePath: \
|
|
5
|
+
* @LastEditTime: 2026-03-17 17:03:16
|
|
6
|
+
* @FilePath: \deepfish\src\core\ai-services\AiWorker\index.js
|
|
7
7
|
* @Description: 工作流类
|
|
8
8
|
* @
|
|
9
9
|
*/
|
|
@@ -40,18 +40,18 @@ class AiWorker {
|
|
|
40
40
|
}
|
|
41
41
|
this.messages = messages
|
|
42
42
|
this.aiAgent.aiMessageManager.reLinkMsgs(this.messages)
|
|
43
|
-
this._recoverHistory(goal, this.messages)
|
|
43
|
+
await this._recoverHistory(goal, this.messages)
|
|
44
44
|
} else {
|
|
45
45
|
if (!this.messages.length) {
|
|
46
46
|
this.messages = getInitialMessages(goal)
|
|
47
|
-
this.aiAgent.work(this.messages)
|
|
47
|
+
await this.aiAgent.work(this.messages)
|
|
48
48
|
} else {
|
|
49
49
|
this.aiAgent.aiMessageManager.reLinkMsgs(this.messages)
|
|
50
50
|
this.aiAgent.aiMessageManager.addMsg({
|
|
51
51
|
role: 'user',
|
|
52
52
|
content: goal,
|
|
53
53
|
})
|
|
54
|
-
this.aiAgent.work(this.messages)
|
|
54
|
+
await this.aiAgent.work(this.messages)
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
// this.aiRecorder.clear()
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const os = require('os')
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const { logError, logSuccess } = require('./utils');
|
|
5
|
+
|
|
6
|
+
function getDefaultConfig() {
|
|
7
|
+
return {
|
|
8
|
+
ai: [],
|
|
9
|
+
currentAi: "",
|
|
10
|
+
maxIterations: -1, // ai完成工作流的最大迭代次数
|
|
11
|
+
maxMessagesLength: 50000, // 最大压缩长度
|
|
12
|
+
maxMessagesCount: 40, // 最大压缩数量
|
|
13
|
+
extensions: [],
|
|
14
|
+
isRecordHistory: false, // 是否创建工作流执行记录文件,用于因意外终止恢复工作流
|
|
15
|
+
isLog: false // 是否创建工作流执行日志
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 添加扩展
|
|
20
|
+
function addExtensionToConfig(fileName) {
|
|
21
|
+
// 检查 fileName 是否为空
|
|
22
|
+
if (!fileName) {
|
|
23
|
+
logError("Extension file name is required.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const filePath = path.resolve(process.cwd(), fileName);
|
|
27
|
+
// 判断是否路径是文件还是目录
|
|
28
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
29
|
+
// 扫描目录和子目录下所有js、cjs文件
|
|
30
|
+
const files = traverseFiles()
|
|
31
|
+
const jsFiles = files.filter((file) => file.endsWith(".js") || file.endsWith(".cjs"));
|
|
32
|
+
jsFiles.forEach((jsFile) => {
|
|
33
|
+
// 读取文件,查询文件内是否存在‘descriptions’和‘functions’
|
|
34
|
+
const fileContent = fs.readFileSync(jsFile, "utf-8");
|
|
35
|
+
if (fileContent.includes("descriptions") && fileContent.includes("functions")) {
|
|
36
|
+
addExtensionToConfig(jsFile);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// 判断文件是否存在
|
|
42
|
+
if (!fs.existsSync(filePath)) {
|
|
43
|
+
logError(`File not found: ${filePath}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const userConfigPath = path.join(os.homedir(), ".ai-cmd.config.js");
|
|
47
|
+
if (!fs.existsSync(userConfigPath)) {
|
|
48
|
+
logError(
|
|
49
|
+
`User config file not found: ${userConfigPath}. Please run 'ai config reset' first.`,
|
|
50
|
+
);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const userConfig = require(userConfigPath);
|
|
54
|
+
if (userConfig.extensions && Array.isArray(userConfig.extensions)) {
|
|
55
|
+
userConfig.extensions.push(filePath);
|
|
56
|
+
} else {
|
|
57
|
+
userConfig.extensions = [filePath];
|
|
58
|
+
}
|
|
59
|
+
// 数组去重
|
|
60
|
+
userConfig.extensions = [...new Set(userConfig.extensions)];
|
|
61
|
+
fs.writeFileSync(
|
|
62
|
+
userConfigPath,
|
|
63
|
+
`module.exports = ${JSON.stringify(userConfig, null, 2)}`,
|
|
64
|
+
);
|
|
65
|
+
logSuccess(
|
|
66
|
+
`Extension added to config: ${filePath}.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 移除扩展
|
|
71
|
+
function removeExtensionFromConfig(fileName) {
|
|
72
|
+
const userConfigPath = path.join(os.homedir(), ".ai-cmd.config.js");
|
|
73
|
+
if (!fs.existsSync(userConfigPath)) {
|
|
74
|
+
logError(
|
|
75
|
+
`User config file not found: ${userConfigPath}. Please run 'ai config reset' first.`,
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const userConfig = require(userConfigPath);
|
|
80
|
+
// 增加对数字索引的支持
|
|
81
|
+
if (!isNaN(Number(fileName))) {
|
|
82
|
+
const extIndex = Number(fileName);
|
|
83
|
+
if (extIndex < 0 || extIndex >= userConfig.extensions.length) {
|
|
84
|
+
logError(`Invalid extension index: ${extIndex}`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const filePath = userConfig.extensions.splice(extIndex, 1);
|
|
88
|
+
fs.writeFileSync(
|
|
89
|
+
userConfigPath,
|
|
90
|
+
`module.exports = ${JSON.stringify(userConfig, null, 2)}`,
|
|
91
|
+
);
|
|
92
|
+
logSuccess(
|
|
93
|
+
`Extension removed from config: ${filePath}.You can run 'ai ext ls' to view the changes.`,
|
|
94
|
+
);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const filePath = path.resolve(process.cwd(), fileName);
|
|
98
|
+
// 判断文件是否存在
|
|
99
|
+
if (!fs.existsSync(filePath)) {
|
|
100
|
+
logError(`File not found: ${filePath}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (userConfig.extensions && Array.isArray(userConfig.extensions)) {
|
|
104
|
+
userConfig.extensions = userConfig.extensions.filter(
|
|
105
|
+
(ext) => ext !== filePath,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
fs.writeFileSync(
|
|
109
|
+
userConfigPath,
|
|
110
|
+
`module.exports = ${JSON.stringify(userConfig, null, 2)}`,
|
|
111
|
+
);
|
|
112
|
+
logSuccess(
|
|
113
|
+
`Extension removed from config: ${filePath}.You can run 'ai ext ls' to view the changes.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 查看扩展列表
|
|
118
|
+
function viewExtensionsFromConfig() {
|
|
119
|
+
const userConfigPath = path.join(os.homedir(), ".ai-cmd.config.js");
|
|
120
|
+
if (!fs.existsSync(userConfigPath)) {
|
|
121
|
+
logError(
|
|
122
|
+
`User config file not found: ${userConfigPath}. Please run 'ai config reset' first.`,
|
|
123
|
+
);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const userConfig = require(userConfigPath);
|
|
127
|
+
if (userConfig.extensions && Array.isArray(userConfig.extensions)) {
|
|
128
|
+
console.log("=".repeat(50));
|
|
129
|
+
// 打印扩展列表,并加上索引
|
|
130
|
+
if (userConfig.extensions.length === 0) {
|
|
131
|
+
console.log(`No extensions in config.`);
|
|
132
|
+
} else {
|
|
133
|
+
console.log("Extensions in config:");
|
|
134
|
+
userConfig.extensions.forEach((ext, index) => {
|
|
135
|
+
console.log(`[${index}] ${ext}`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
console.log("=".repeat(50));
|
|
139
|
+
} else {
|
|
140
|
+
logSuccess(`No extensions in config.`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
function traverseFiles() {
|
|
146
|
+
try {
|
|
147
|
+
const currentDir = process.cwd();
|
|
148
|
+
const allFiles = [];
|
|
149
|
+
const currentItems = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
150
|
+
for (const item of currentItems) {
|
|
151
|
+
const itemPath = path.join(currentDir, item.name);
|
|
152
|
+
if (item.isFile()) {
|
|
153
|
+
allFiles.push(itemPath);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (item.isDirectory()) {
|
|
157
|
+
try {
|
|
158
|
+
const subItems = fs.readdirSync(itemPath, { withFileTypes: true });
|
|
159
|
+
for (const subItem of subItems) {
|
|
160
|
+
if (subItem.isFile()) {
|
|
161
|
+
allFiles.push(path.join(itemPath, subItem.name));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch (subErr) {
|
|
165
|
+
console.warn(`读取子目录失败 ${itemPath}:${subErr.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return allFiles;
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`遍历目录失败:${err.message}`);
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 获取配置文件所在目录
|
|
177
|
+
function getConfigPath() {
|
|
178
|
+
return path.join(os.homedir(), ".ai-cmd.config.js");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
getDefaultConfig,
|
|
183
|
+
addExtensionToConfig,
|
|
184
|
+
removeExtensionFromConfig,
|
|
185
|
+
viewExtensionsFromConfig,
|
|
186
|
+
getConfigPath
|
|
187
|
+
};
|
|
@@ -103,7 +103,7 @@ async function executeJSCode(code) {
|
|
|
103
103
|
const Func = new Function(
|
|
104
104
|
"Tools",
|
|
105
105
|
"require",
|
|
106
|
-
|
|
106
|
+
`return (async () => { this.logMessages = [];const originalLog = console.log;const newLog = function(){ originalLog.apply(console, arguments); this.logMessages.push(Array.from(arguments).join(" "))};console.log = newLog.bind(this);${code };return this.logMessages.join('\\n'); })()`,
|
|
107
107
|
);
|
|
108
108
|
const originalRequire = require;
|
|
109
109
|
const newRequire = (modulePath) => {
|
|
@@ -113,7 +113,14 @@ async function executeJSCode(code) {
|
|
|
113
113
|
}
|
|
114
114
|
return originalRequire(modulePath);
|
|
115
115
|
};
|
|
116
|
+
|
|
117
|
+
const newLog = function () {
|
|
118
|
+
console.log.apply(console, arguments);
|
|
119
|
+
this.logMessages.push(Array.from(arguments).join(" "))
|
|
120
|
+
}
|
|
121
|
+
|
|
116
122
|
const result = await Func(functions, newRequire);
|
|
123
|
+
|
|
117
124
|
return result || "";
|
|
118
125
|
} catch (error) {
|
|
119
126
|
logError(`Error executing code: ${error.stack}`);
|
|
@@ -419,7 +426,7 @@ const descriptions = [
|
|
|
419
426
|
function: {
|
|
420
427
|
name: "executeJSCode",
|
|
421
428
|
description:
|
|
422
|
-
'执行JavaScript代码,返回代码执行结果。代码中可通过Tools命名空间直接调用其他工具函数(如await Tools.createFile(),注意:不需要使用require引入),Tools中引入了一些常用库可直接调用(Tools.fs="fs-extra",
|
|
429
|
+
'执行JavaScript代码,返回代码执行结果。代码中可通过Tools命名空间直接调用其他工具函数(如await Tools.createFile(),注意:不需要使用require引入),Tools中引入了一些常用库可直接调用(Tools.fs="fs-extra", Tools.dayjs="dayjs", Tools.axios="axios", Tools.lodash="lodash"),支持引入自定义模块(需使用绝对路径)。注意:1.代码中不要使用__dirname获取当前目录,请使用path.resolve(".")来获取当前目录。2.执行失败时会抛出错误,成功时返回代码执行结果或空字符串。',
|
|
423
430
|
parameters: {
|
|
424
431
|
type: "object",
|
|
425
432
|
properties: {
|
|
@@ -8,6 +8,7 @@ const shelljs = require('shelljs')
|
|
|
8
8
|
const iconv = require('iconv-lite') // 用于编码转换
|
|
9
9
|
const os = require('os') // 用于判断系统类型
|
|
10
10
|
const { logError } = require('../utils')
|
|
11
|
+
const { getGlobalNodeModulesPath } = require('../utils/node-root')
|
|
11
12
|
|
|
12
13
|
class ExtensionManager {
|
|
13
14
|
constructor(aiCli) {
|
|
@@ -76,7 +77,7 @@ class ExtensionManager {
|
|
|
76
77
|
// 扫描本程序所在目录下node_modules目录
|
|
77
78
|
const nodeModulesPath1 = path.resolve(__dirname, '../../../node_modules')
|
|
78
79
|
// 扫描根node_modules目录
|
|
79
|
-
const nodeModulesPath2 =
|
|
80
|
+
const nodeModulesPath2 = getGlobalNodeModulesPath()
|
|
80
81
|
// 扫描命令执行目录下node_modules目录
|
|
81
82
|
const nodeModulesPath3 = path.resolve(process.cwd(), 'node_modules')
|
|
82
83
|
// 扫描命令执行目录
|
|
@@ -87,6 +88,9 @@ class ExtensionManager {
|
|
|
87
88
|
nodeModulesPath3,
|
|
88
89
|
nodeModulesPath4,
|
|
89
90
|
]) {
|
|
91
|
+
if (!dirPath) {
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
90
94
|
if (!fs.existsSync(dirPath)) {
|
|
91
95
|
continue
|
|
92
96
|
}
|
|
@@ -95,28 +99,23 @@ class ExtensionManager {
|
|
|
95
99
|
// 如果是目录且目录名称前缀是"deepfish-",则认为是扩展模块
|
|
96
100
|
const extensionDir = path.resolve(dirPath, fileName)
|
|
97
101
|
if (
|
|
98
|
-
fileName.startsWith('deepfish-') &&
|
|
102
|
+
fileName.startsWith('deepfish-') &&
|
|
103
|
+
fileName !== 'deepfish-ai' &&
|
|
99
104
|
fs.statSync(extensionDir).isDirectory()
|
|
100
105
|
) {
|
|
101
106
|
const subDirNames = fs.readdirSync(extensionDir)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
fileContent.includes('descriptions') &&
|
|
115
|
-
fileContent.includes('functions')
|
|
116
|
-
) {
|
|
117
|
-
result.push(jsFilePath)
|
|
118
|
-
}
|
|
119
|
-
})
|
|
107
|
+
const jsFiles = subDirNames.filter(
|
|
108
|
+
(file) => file.endsWith('.js') || file.endsWith('.cjs'),
|
|
109
|
+
)
|
|
110
|
+
for (const jsFile of jsFiles) {
|
|
111
|
+
const jsFilePath = path.resolve(extensionDir, jsFile)
|
|
112
|
+
// 读取文件,查询文件内是否存在‘descriptions’和‘functions’
|
|
113
|
+
const fileContent = fs.readFileSync(jsFilePath, 'utf-8')
|
|
114
|
+
if (
|
|
115
|
+
fileContent.includes('descriptions') &&
|
|
116
|
+
fileContent.includes('functions')
|
|
117
|
+
) {
|
|
118
|
+
result.push(jsFilePath)
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
121
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 辅助函数:安全执行 shell 命令(避免执行失败导致程序崩溃)
|
|
7
|
+
* @param {string} cmd 要执行的命令
|
|
8
|
+
* @returns {string|null} 命令输出结果(失败返回 null)
|
|
9
|
+
*/
|
|
10
|
+
function safeExec(cmd) {
|
|
11
|
+
try {
|
|
12
|
+
return execSync(cmd, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 辅助函数:解析路径(处理软链/快捷方式,验证路径存在性)
|
|
20
|
+
* @param {string} targetPath 待解析的路径
|
|
21
|
+
* @returns {string|null} 真实存在的路径(失败返回 null)
|
|
22
|
+
*/
|
|
23
|
+
function resolveValidPath(targetPath) {
|
|
24
|
+
if (!targetPath) return null;
|
|
25
|
+
try {
|
|
26
|
+
// 解析软链/快捷方式的真实物理路径
|
|
27
|
+
const realPath = fs.realpathSync(targetPath);
|
|
28
|
+
// 验证路径是否存在
|
|
29
|
+
return fs.existsSync(realPath) ? realPath : null;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 从 NVM 环境中获取全局 node_modules 路径
|
|
37
|
+
* @returns {string|null} NVM 环境下的真实路径
|
|
38
|
+
*/
|
|
39
|
+
function getNvmGlobalPath() {
|
|
40
|
+
// 1. 获取 NVM 根目录(优先读环境变量)
|
|
41
|
+
const nvmDir = process.env.NVM_DIR || (
|
|
42
|
+
process.platform === 'win32'
|
|
43
|
+
? path.join(process.env.USERPROFILE, '.nvm')
|
|
44
|
+
: path.join(process.env.HOME, '.nvm')
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// 2. 获取当前激活的 Node 版本
|
|
48
|
+
const nodeVersion = safeExec('nvm current');
|
|
49
|
+
if (!nodeVersion || nodeVersion.includes('N/A')) return null;
|
|
50
|
+
|
|
51
|
+
// 3. 拼接 NVM 下的全局 node_modules 路径
|
|
52
|
+
const nvmGlobalPath = path.join(
|
|
53
|
+
nvmDir,
|
|
54
|
+
'versions',
|
|
55
|
+
'node',
|
|
56
|
+
nodeVersion,
|
|
57
|
+
'lib',
|
|
58
|
+
'node_modules'
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// 4. 解析并验证路径有效性
|
|
62
|
+
return resolveValidPath(nvmGlobalPath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 从 NPM 命令获取全局 node_modules 路径
|
|
67
|
+
* @returns {string|null} NPM 返回的真实路径
|
|
68
|
+
*/
|
|
69
|
+
function getNpmGlobalPath() {
|
|
70
|
+
// 1. 执行 npm root -g 获取路径
|
|
71
|
+
const npmPath = safeExec('npm root -g');
|
|
72
|
+
// 2. 解析并验证路径有效性
|
|
73
|
+
return resolveValidPath(npmPath);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 兜底方案:通过 Node 内置变量计算全局路径
|
|
78
|
+
* @returns {string|null} 计算出的路径(仅作为最后兜底)
|
|
79
|
+
*/
|
|
80
|
+
function getFallbackGlobalPath() {
|
|
81
|
+
try {
|
|
82
|
+
const nodeExecPath = process.execPath;
|
|
83
|
+
let globalPrefix;
|
|
84
|
+
|
|
85
|
+
if (process.platform === 'win32') {
|
|
86
|
+
// Windows:node.exe 所在目录的上一级
|
|
87
|
+
globalPrefix = path.dirname(path.dirname(nodeExecPath));
|
|
88
|
+
} else {
|
|
89
|
+
// Mac/Linux:node 所在目录的上两级
|
|
90
|
+
globalPrefix = path.dirname(path.dirname(path.dirname(nodeExecPath)));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const fallbackPath = path.join(globalPrefix, 'lib', 'node_modules');
|
|
94
|
+
return resolveValidPath(fallbackPath);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 主函数:获取最正确的全局 node_modules 路径(自动适配所有场景)
|
|
102
|
+
* 优先级:NVM 路径 → NPM 命令路径 → 兜底计算路径
|
|
103
|
+
* @returns {string|null} 最准确的全局 node_modules 路径
|
|
104
|
+
*/
|
|
105
|
+
function getGlobalNodeModulesPath() {
|
|
106
|
+
// 优先级 1:优先获取 NVM 环境下的路径(适配 NVM 场景)
|
|
107
|
+
const nvmPath = getNvmGlobalPath();
|
|
108
|
+
if (nvmPath) {
|
|
109
|
+
return nvmPath;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 优先级 2:通过 npm 命令获取(普通环境最准确)
|
|
113
|
+
const npmPath = getNpmGlobalPath();
|
|
114
|
+
if (npmPath) {
|
|
115
|
+
return npmPath;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 优先级 3:兜底计算(仅当以上都失败时使用)
|
|
119
|
+
const fallbackPath = getFallbackGlobalPath();
|
|
120
|
+
if (fallbackPath) {
|
|
121
|
+
return fallbackPath;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 所有方案都失败
|
|
125
|
+
console.error('无法获取全局 node_modules 路径');
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 导出所有函数(方便单独使用),主函数作为默认导出
|
|
130
|
+
module.exports = {
|
|
131
|
+
safeExec,
|
|
132
|
+
resolveValidPath,
|
|
133
|
+
getNvmGlobalPath,
|
|
134
|
+
getNpmGlobalPath,
|
|
135
|
+
getFallbackGlobalPath,
|
|
136
|
+
getGlobalNodeModulesPath // 主函数
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// 默认导出主函数(简化使用)
|
|
140
|
+
module.exports.default = getGlobalNodeModulesPath;
|
package/src/core/utils.js
CHANGED
|
@@ -1,247 +1,81 @@
|
|
|
1
|
-
const chalk = require(
|
|
2
|
-
|
|
3
|
-
const os = require("os");
|
|
4
|
-
const fs = require("fs-extra");
|
|
1
|
+
const chalk = require('chalk')
|
|
2
|
+
|
|
5
3
|
|
|
6
4
|
// 日志相关工具函数
|
|
7
5
|
function logInfo(message) {
|
|
8
|
-
log(message,
|
|
6
|
+
log(message, '#6dd2ea')
|
|
9
7
|
}
|
|
10
8
|
|
|
11
9
|
function logSuccess(message) {
|
|
12
|
-
log(message,
|
|
10
|
+
log(message, '#9bed7f')
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
function logError(message) {
|
|
16
|
-
log(message,
|
|
14
|
+
log(message, '#ed7f7f')
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
function writeLine(msg1, msg2 =
|
|
20
|
-
if (color ===
|
|
21
|
-
process.stdout.write(
|
|
22
|
-
} else if (color ===
|
|
23
|
-
process.stdout.write(
|
|
24
|
-
} else if (color ===
|
|
25
|
-
process.stdout.write(
|
|
17
|
+
function writeLine(msg1, msg2 = '', color = 'blue') {
|
|
18
|
+
if (color === 'blue') {
|
|
19
|
+
process.stdout.write('\r' + chalk.hex('#6dd2ea')(msg1) + ' ' + msg2)
|
|
20
|
+
} else if (color === 'green') {
|
|
21
|
+
process.stdout.write('\r' + chalk.hex('#9bed7f')(msg1) + ' ' + msg2)
|
|
22
|
+
} else if (color === 'red') {
|
|
23
|
+
process.stdout.write('\r' + chalk.hex('#ed7f7f')(msg1) + ' ' + msg2)
|
|
26
24
|
} else {
|
|
27
|
-
process.stdout.write(
|
|
25
|
+
process.stdout.write('\r' + chalk.hex(color)(msg1) + ' ' + msg2)
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
// 流式输出
|
|
32
|
-
async function streamOutput(text, color =
|
|
33
|
-
process.stdout.write(chalk.hex(color)(text))
|
|
30
|
+
async function streamOutput(text, color = '#9bed7f') {
|
|
31
|
+
process.stdout.write(chalk.hex(color)(text))
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
// 流式换行
|
|
37
35
|
async function streamLineBreak() {
|
|
38
|
-
process.stdout.write(
|
|
36
|
+
process.stdout.write('\n')
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
function objStrToObj(str) {
|
|
42
40
|
try {
|
|
43
41
|
if (typeof str === 'string') {
|
|
44
|
-
return eval(`(${str})`)
|
|
42
|
+
return eval(`(${str})`)
|
|
45
43
|
} else {
|
|
46
44
|
return str
|
|
47
45
|
}
|
|
48
46
|
} catch (error) {
|
|
49
|
-
throw new Error(`对象转换失败:${error.message}`)
|
|
47
|
+
throw new Error(`对象转换失败:${error.message}`)
|
|
50
48
|
}
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
51
|
function delay(ms) {
|
|
56
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
52
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
function loading(label =
|
|
60
|
-
let animationInterval
|
|
61
|
-
const spinners = [
|
|
62
|
-
let spinnerIndex = 0
|
|
63
|
-
process.stdout.write(
|
|
55
|
+
function loading(label = 'Thinking...') {
|
|
56
|
+
let animationInterval
|
|
57
|
+
const spinners = ['|', '/', '-', '\\']
|
|
58
|
+
let spinnerIndex = 0
|
|
59
|
+
process.stdout.write('\r')
|
|
64
60
|
animationInterval = setInterval(() => {
|
|
65
|
-
writeLine(spinners[spinnerIndex], label)
|
|
66
|
-
spinnerIndex = (spinnerIndex + 1) % spinners.length
|
|
67
|
-
}, 200)
|
|
61
|
+
writeLine(spinners[spinnerIndex], label)
|
|
62
|
+
spinnerIndex = (spinnerIndex + 1) % spinners.length
|
|
63
|
+
}, 200)
|
|
68
64
|
return (endLabel, isError = false) => {
|
|
69
|
-
clearInterval(animationInterval)
|
|
65
|
+
clearInterval(animationInterval)
|
|
70
66
|
if (endLabel) {
|
|
71
|
-
writeLine(endLabel,
|
|
67
|
+
writeLine(endLabel, '', isError ? 'red' : 'green')
|
|
72
68
|
}
|
|
73
|
-
process.stdout.write(
|
|
74
|
-
}
|
|
69
|
+
process.stdout.write('\r\n')
|
|
70
|
+
}
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
function log(msg, color) {
|
|
78
74
|
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);
|
|
75
|
+
console.log(msg)
|
|
122
76
|
} else {
|
|
123
|
-
|
|
77
|
+
console.log(chalk.hex(color)(msg))
|
|
124
78
|
}
|
|
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
79
|
}
|
|
246
80
|
|
|
247
81
|
module.exports = {
|
|
@@ -252,10 +86,6 @@ module.exports = {
|
|
|
252
86
|
writeLine,
|
|
253
87
|
streamOutput,
|
|
254
88
|
streamLineBreak,
|
|
255
|
-
addExtensionToConfig,
|
|
256
|
-
removeExtensionFromConfig,
|
|
257
|
-
viewExtensionsFromConfig,
|
|
258
89
|
objStrToObj,
|
|
259
90
|
delay,
|
|
260
|
-
|
|
261
|
-
};
|
|
91
|
+
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
function getDefaultConfig() {
|
|
2
|
-
return {
|
|
3
|
-
ai: [],
|
|
4
|
-
currentAi: "",
|
|
5
|
-
maxIterations: -1, // ai完成工作流的最大迭代次数
|
|
6
|
-
maxMessagesLength: 50000, // 最大压缩长度
|
|
7
|
-
maxMessagesCount: 40, // 最大压缩数量
|
|
8
|
-
extensions: [],
|
|
9
|
-
isRecordHistory: false, // 是否创建工作流执行记录文件,用于因意外终止恢复工作流
|
|
10
|
-
isLog: false // 是否创建工作流执行日志
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
module.exports = getDefaultConfig;
|