deepfish-ai 1.0.13 → 1.0.16
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 +45 -5
- package/README_CN.md +44 -5
- package/package.json +7 -2
- package/src/cli/ConfigManager.js +24 -14
- package/src/cli/DefaultConfig.js +59 -0
- package/src/cli/ExtConfigManager.js +7 -7
- package/src/cli/HistoryManager.js +217 -0
- package/src/cli/SkillConfigManager.js +362 -0
- package/src/cli/SkillParser.js +61 -0
- package/src/cli/ai-config.js +23 -5
- package/src/cli/ai-history.js +34 -0
- package/src/cli/ai-skill.js +65 -0
- package/src/cli/index.js +4 -7
- package/src/core/AICLI.js +4 -6
- package/src/core/{globalVariable.js → GlobalVariable.js} +2 -3
- package/src/core/ai-services/AiWorker/AIMessageManager.js +71 -54
- package/src/core/ai-services/AiWorker/AiAgent.js +6 -9
- package/src/core/ai-services/AiWorker/AiPrompt.js +56 -6
- package/src/core/ai-services/AiWorker/AiTools.js +51 -7
- package/src/core/ai-services/AiWorker/index.js +87 -38
- package/src/core/ai-services/{AIService.js → index.js} +8 -0
- package/src/core/extension/BaseExtension.js +2 -0
- package/src/core/extension/ExtensionManager.js +35 -50
- package/src/core/extension/FileExtension.js +439 -0
- package/src/core/extension/InquirerExtension.js +293 -0
- package/src/core/extension/SystemExtension.js +415 -0
- package/src/core/extension/TestExtension.js +67 -0
- package/src/core/utils/log.js +10 -0
- package/src/core/utils/normal.js +86 -0
- package/src/cli/configTools.js +0 -90
- package/src/core/ai-services/AiWorker/AiRecorder.js +0 -119
- package/src/core/extension/DefaultExtension.js +0 -759
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
- [AI Service Selection](#ai-service-selection)
|
|
55
55
|
- [8. Usage Notes](#8-usage-notes)
|
|
56
56
|
- [Using Relative Paths](#using-relative-paths)
|
|
57
|
+
- [Conversation History](#conversation-history)
|
|
57
58
|
- [9. Troubleshooting](#9-troubleshooting)
|
|
58
59
|
- [Configuration Issues](#configuration-issues)
|
|
59
60
|
- [AI Service Connection](#ai-service-connection)
|
|
@@ -71,6 +72,8 @@ Core Features:
|
|
|
71
72
|
|
|
72
73
|
- Multi-model Compatibility: Seamlessly supports DeepSeek, Ollama, and all AI models that comply with the OpenAI API specification. It can be flexibly switched according to needs to adapt to instruction generation requirements in different scenarios.
|
|
73
74
|
|
|
75
|
+
- OpenClaw Skill Compatibility: Supports the OpenClaw Skill ecosystem. Skills can be installed, enabled, and managed through the existing Skill commands to quickly expand workflow capabilities.
|
|
76
|
+
|
|
74
77
|
- Natural Language to Instructions: Precisely parses natural language requirements and automatically converts them into corresponding operating system commands (such as Linux, Windows, and macOS terminal commands) and file operation instructions (such as creating, deleting, and modifying files/directories), eliminating the need to manually write complex commands.
|
|
75
78
|
|
|
76
79
|
- Highly Extensible: Supports expanding functional boundaries through an extension mechanism. In addition to basic terminal and file operations, it can easily implement complex tasks such as translation, novel writing, file format conversion, and data processing to meet diverse usage needs.
|
|
@@ -140,6 +143,7 @@ ai config use <name> # Set the specified AI configuration as the current one
|
|
|
140
143
|
ai config del <name> # Delete the specified AI configuration
|
|
141
144
|
ai config view [name] # View details of the specified AI configuration
|
|
142
145
|
ai config edit # Edit the configuration file manually
|
|
146
|
+
ai config dir # Open the configuration file directory
|
|
143
147
|
ai config reset # Reset configuration
|
|
144
148
|
ai config clear # Delete the configuration file
|
|
145
149
|
|
|
@@ -148,6 +152,21 @@ ai ext add <filename> # Add an extension tool
|
|
|
148
152
|
ai ext del <filepath> # Remove an extension tool by file path
|
|
149
153
|
ai ext del <index> # Remove an extension tool by index
|
|
150
154
|
ai ext ls # List all extension tools
|
|
155
|
+
|
|
156
|
+
# Skill commands
|
|
157
|
+
ai skill ls # List all registered skills
|
|
158
|
+
ai skill add <name> # Add a local skill directory or zip file from the current directory
|
|
159
|
+
ai skill del <name|index> # Remove a skill by name or index, exp: ai skill del 1
|
|
160
|
+
ai skill install <url> # Install a skill from ClawHub,exp: ai skill install https://clawhub.ai/TheSethRose/agent-browser
|
|
161
|
+
ai skill enable <name|index> # Enable a skill by name or index, exp: ai skill enable 1
|
|
162
|
+
ai skill disable <name|index> # Disable a skill by name or index, exp: ai skill disable 1
|
|
163
|
+
ai skill dir # Open the skill directory
|
|
164
|
+
|
|
165
|
+
# History commands
|
|
166
|
+
ai history clear # Clear the history messages for the current directory
|
|
167
|
+
ai history output # Output the history messages to current directory
|
|
168
|
+
ai history dir # Open the history directory
|
|
169
|
+
ai history reset # Reset all history for all directories
|
|
151
170
|
```
|
|
152
171
|
|
|
153
172
|
### Configuration File Structure
|
|
@@ -169,12 +188,15 @@ module.exports = {
|
|
|
169
188
|
}
|
|
170
189
|
],
|
|
171
190
|
currentAi: "default", // Name of the currently active AI configuration
|
|
172
|
-
maxIterations: -1, // Maximum iterations for
|
|
173
|
-
maxMessagesLength:
|
|
174
|
-
maxMessagesCount:
|
|
191
|
+
maxIterations: -1, // Maximum iterations for AI to complete the workflow, -1 for unlimited
|
|
192
|
+
maxMessagesLength: 150000, // Maximum compression length, -1 for unlimited
|
|
193
|
+
maxMessagesCount: 100, // Maximum compression count, -1 for unlimited
|
|
194
|
+
maxHistoryExpireTime: 30, // Maximum session expiration time in days, -1 for unlimited, 0 to disable recording
|
|
195
|
+
maxLogExpireTime: 3, // Log expiration time in days, -1 for unlimited, 0 to disable recording
|
|
196
|
+
maxBlockFileSize: 20, // Maximum block file size in KB; files exceeding this size need to be processed in blocks
|
|
175
197
|
extensions: [], // List of extension file paths
|
|
176
|
-
|
|
177
|
-
|
|
198
|
+
skills: [], // List of skill configurations
|
|
199
|
+
encoding: "utf-8", // Command line encoding format, can be set to utf-8, gbk, etc., or auto/empty for auto-detection
|
|
178
200
|
};
|
|
179
201
|
```
|
|
180
202
|
|
|
@@ -232,6 +254,13 @@ ai "Create a weather.js extension tool for querying weather"
|
|
|
232
254
|
ai ext add weather.js
|
|
233
255
|
```
|
|
234
256
|
|
|
257
|
+
**Skill Management:**
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
ai skill install https://clawhub.ai/TheSethRose/agent-browser
|
|
261
|
+
ai skill ls
|
|
262
|
+
```
|
|
263
|
+
|
|
235
264
|
**Media Processing:**
|
|
236
265
|
|
|
237
266
|
```bash
|
|
@@ -347,6 +376,17 @@ For production environments or complex tasks, we recommend using DeepSeek, OpenA
|
|
|
347
376
|
|
|
348
377
|
AI always uses paths relative to the current working directory.
|
|
349
378
|
|
|
379
|
+
### Conversation History
|
|
380
|
+
|
|
381
|
+
Conversation history is created on a per-directory basis — each execution directory corresponds to its own Agent context. This means that conversations started in different directories are independent of each other.
|
|
382
|
+
|
|
383
|
+
Conversation history will be automatically cleared after a configurable period (controlled by the `maxHistoryExpireTime` field in the configuration file, default is 30 days). You can also manage it manually:
|
|
384
|
+
|
|
385
|
+
- `ai history dir` — Open the history directory to view stored conversation contexts
|
|
386
|
+
- `ai history clear` — Manually clear the conversation history for the current directory
|
|
387
|
+
- `ai history output` — Export the conversation history to the current directory
|
|
388
|
+
- `ai history reset` — Reset all conversation history for all directories
|
|
389
|
+
|
|
350
390
|
## 9. Troubleshooting
|
|
351
391
|
|
|
352
392
|
### Configuration Issues
|
package/README_CN.md
CHANGED
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
|
|
71
71
|
- 多模型兼容:无缝支持DeepSeek、Ollama,以及所有遵循OpenAI API规范的AI模型,可根据需求灵活切换,适配不同场景下的指令生成需求。
|
|
72
72
|
|
|
73
|
+
- OpenClaw Skill 兼容:支持适配 OpenClaw 的 Skill 生态,可通过现有 Skill 命令进行安装、启用与管理,快速扩展工作流能力。
|
|
74
|
+
|
|
73
75
|
- 自然语言转指令:精准解析自然语言需求,自动转换为对应的操作系统命令(如Linux、Windows、macOS终端指令)和文件操作指令(如创建、删除、修改文件/目录),无需手动编写复杂命令。
|
|
74
76
|
|
|
75
77
|
- 高度可扩展:支持通过扩展机制拓展功能边界,除基础的终端、文件操作外,可轻松实现翻译、小说创作、文件格式转换、数据处理等复杂任务,满足多样化使用需求。
|
|
@@ -138,6 +140,7 @@ ai config use <name> # 设置指定的AI配置为当前配置
|
|
|
138
140
|
ai config del <name> # 删除指定的AI配置
|
|
139
141
|
ai config view [name] # 查看指定AI配置的详细信息
|
|
140
142
|
ai config edit # 编辑配置文件手动编辑配置文件
|
|
143
|
+
ai config dir # 打开配置文件所在目录
|
|
141
144
|
ai config reset # 重置配置
|
|
142
145
|
ai config clear # 删除配置文件
|
|
143
146
|
|
|
@@ -146,6 +149,21 @@ ai ext add <filename> # 添加扩展工具
|
|
|
146
149
|
ai ext del <filepath> # 通过文件路径移除扩展工具
|
|
147
150
|
ai ext del <index> # 通过索引移除扩展工具
|
|
148
151
|
ai ext ls # 列出所有扩展工具
|
|
152
|
+
|
|
153
|
+
# Skill 命令
|
|
154
|
+
ai skill ls # 列出所有已注册的 skill
|
|
155
|
+
ai skill add <name> # 从当前目录添加本地 skill 目录或 zip 文件
|
|
156
|
+
ai skill del <name|index> # 通过名称或索引删除 skill, exp: ai skill del 1
|
|
157
|
+
ai skill install <url> # 从 ClawHub 安装 skill,exp: ai skill install https://clawhub.ai/TheSethRose/agent-browser
|
|
158
|
+
ai skill enable <name|index> # 通过名称或索引启用 skill, exp: ai skill enable 1
|
|
159
|
+
ai skill disable <name|index> # 通过名称或索引禁用 skill, exp: ai skill disable 1
|
|
160
|
+
ai skill dir # 打开 skill 目录
|
|
161
|
+
|
|
162
|
+
# 历史记录命令
|
|
163
|
+
ai history clear # 清除当前目录的对话历史
|
|
164
|
+
ai history output # 将历史消息输出到当前目录
|
|
165
|
+
ai history dir # 打开历史记录目录
|
|
166
|
+
ai history reset # 清除所有目录的对话历史
|
|
149
167
|
```
|
|
150
168
|
|
|
151
169
|
### 配置文件结构
|
|
@@ -167,12 +185,15 @@ module.exports = {
|
|
|
167
185
|
}
|
|
168
186
|
],
|
|
169
187
|
currentAi: "default", // 当前活动的AI配置名称
|
|
170
|
-
maxIterations: -1, //
|
|
171
|
-
maxMessagesLength:
|
|
172
|
-
maxMessagesCount:
|
|
188
|
+
maxIterations: -1, // ai完成工作流的最大迭代次数,-1表示无限制
|
|
189
|
+
maxMessagesLength: 150000, // 最大压缩长度,-1表示无限制
|
|
190
|
+
maxMessagesCount: 100, // 最大压缩数量,-1表示无限制
|
|
191
|
+
maxHistoryExpireTime: 30, // 整个会话的最大过期时间,单位天,-1表示无限制,0表示不记录
|
|
192
|
+
maxLogExpireTime: 3, // 日志过期时间,单位天,-1表示无限制,0表示不记录
|
|
193
|
+
maxBlockFileSize: 20, // 最大分块文件大小,单位KB;超过该大小的文件需要分块处理
|
|
173
194
|
extensions: [], // 扩展文件路径列表
|
|
174
|
-
|
|
175
|
-
|
|
195
|
+
skills: [], // 技能配置列表
|
|
196
|
+
encoding: "utf-8", // 命令行编码格式,可设置为utf-8、gbk等,也可以设置成auto或空值自动判断
|
|
176
197
|
};
|
|
177
198
|
```
|
|
178
199
|
|
|
@@ -230,6 +251,13 @@ ai "创建一个用于查询天气的扩展工具weather.js"
|
|
|
230
251
|
ai ext add weather.js
|
|
231
252
|
```
|
|
232
253
|
|
|
254
|
+
**Skill 管理:**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
ai skill install https://clawhub.ai/TheSethRose/agent-browser
|
|
258
|
+
ai skill ls
|
|
259
|
+
```
|
|
260
|
+
|
|
233
261
|
**媒体处理:**
|
|
234
262
|
|
|
235
263
|
```bash
|
|
@@ -343,6 +371,17 @@ module.exports = {
|
|
|
343
371
|
|
|
344
372
|
AI始终使用相对于当前工作目录的相对路径。
|
|
345
373
|
|
|
374
|
+
### 对话历史
|
|
375
|
+
|
|
376
|
+
对话历史是以程序执行目录为单位创建的,每个程序的执行目录会对应一个独立的 Agent 上下文。这意味着在不同目录下启动的对话是相互独立的。
|
|
377
|
+
|
|
378
|
+
对话历史会在一定时间内自动清除(通过配置文件中的 `maxHistoryExpireTime` 字段控制,默认为 30 天)。您也可以手动管理对话历史:
|
|
379
|
+
|
|
380
|
+
- `ai history dir` — 打开历史记录目录,查看已存储的对话上下文
|
|
381
|
+
- `ai history clear` — 清除当前目录的对话历史
|
|
382
|
+
- `ai history output` — 将对话历史导出到当前目录
|
|
383
|
+
- `ai history reset` — 清除所有目录的对话历史
|
|
384
|
+
|
|
346
385
|
## 9. 故障排除
|
|
347
386
|
|
|
348
387
|
### 配置问题
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepfish-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "This is an AI-driven command-line tool built on Node.js, equipped with AI agent and workflow capabilities. It is compatible with a wide range of AI models, can convert natural language into cross-system terminal and file operation commands, and features high extensibility. It supports complex tasks such as translation, content creation, and format conversion, while allowing custom extensions to be automatically generated via AI.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"cli",
|
|
18
18
|
"command-line",
|
|
19
19
|
"cmd",
|
|
20
|
+
"openclaw",
|
|
20
21
|
"openai",
|
|
21
22
|
"ollama",
|
|
22
23
|
"deepfish"
|
|
@@ -34,14 +35,18 @@
|
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"axios": "^1.13.5",
|
|
36
37
|
"chalk": "^4.1.0",
|
|
38
|
+
"chardet": "^2.1.1",
|
|
39
|
+
"cheerio": "^1.2.0",
|
|
37
40
|
"commander": "^11.0.0",
|
|
38
41
|
"dayjs": "^1.11.19",
|
|
42
|
+
"extract-zip": "^2.0.1",
|
|
39
43
|
"fs-extra": "^11.3.3",
|
|
40
44
|
"iconv-lite": "^0.7.2",
|
|
41
45
|
"inquirer": "^9.0.0",
|
|
46
|
+
"js-yaml": "^4.1.1",
|
|
42
47
|
"lodash": "^4.17.23",
|
|
43
48
|
"openai": "^6.18.0",
|
|
44
|
-
"
|
|
49
|
+
"uuid": "^13.0.0"
|
|
45
50
|
},
|
|
46
51
|
"devDependencies": {
|
|
47
52
|
"@eslint/js": "^10.0.1",
|
package/src/cli/ConfigManager.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const os = require('os')
|
|
3
3
|
const fs = require('fs-extra')
|
|
4
|
-
const { defaultConfig } = require('./
|
|
4
|
+
const { defaultConfig } = require('./DefaultConfig')
|
|
5
5
|
const { logSuccess, logError, logInfo } = require('../core/utils/log')
|
|
6
6
|
const { GlobalVariable } = require('../core/globalVariable')
|
|
7
|
+
const { merge } = require('lodash')
|
|
8
|
+
const { openDirectory } = require('../core/utils/normal')
|
|
7
9
|
|
|
8
10
|
class ConfigManager {
|
|
9
11
|
config = null
|
|
@@ -24,9 +26,14 @@ class ConfigManager {
|
|
|
24
26
|
) {
|
|
25
27
|
fs.moveSync(path.join(os.homedir(), '.ai-cmd.config.js'), this.configPath)
|
|
26
28
|
} else if (!isConfigExists) {
|
|
27
|
-
this.
|
|
29
|
+
this.writeConfig()
|
|
28
30
|
}
|
|
29
|
-
this.config = this.
|
|
31
|
+
this.config = this.getConfig()
|
|
32
|
+
this.writeConfig(this.config)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
dir() {
|
|
36
|
+
openDirectory(this.configDir)
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
edit() {
|
|
@@ -70,8 +77,9 @@ class ConfigManager {
|
|
|
70
77
|
// 添加一个aiConfig
|
|
71
78
|
addAiConfig(aiConfig) {
|
|
72
79
|
this.config.ai.push(aiConfig)
|
|
73
|
-
this.
|
|
80
|
+
this.writeConfig(this.config)
|
|
74
81
|
logSuccess(`AI configuration "${aiConfig.name}" added successfully!`)
|
|
82
|
+
return aiConfig
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
// 删除一个aiConfig
|
|
@@ -91,7 +99,7 @@ class ConfigManager {
|
|
|
91
99
|
}
|
|
92
100
|
// Remove the configuration
|
|
93
101
|
this.config.ai.splice(existingIndex, 1)
|
|
94
|
-
this.
|
|
102
|
+
this.writeConfig(this.config)
|
|
95
103
|
logSuccess(`AI configuration "${aiName}" deleted successfully!`)
|
|
96
104
|
}
|
|
97
105
|
|
|
@@ -106,7 +114,7 @@ class ConfigManager {
|
|
|
106
114
|
return
|
|
107
115
|
}
|
|
108
116
|
this.config.currentAi = aiName
|
|
109
|
-
this.
|
|
117
|
+
this.writeConfig(this.config)
|
|
110
118
|
logSuccess(`Current AI configuration set to "${aiName}" successfully!`)
|
|
111
119
|
}
|
|
112
120
|
|
|
@@ -144,8 +152,8 @@ class ConfigManager {
|
|
|
144
152
|
// 重置config
|
|
145
153
|
resetConfig() {
|
|
146
154
|
console.log('Resetting configuration file:', this.configPath)
|
|
147
|
-
this.
|
|
148
|
-
this.config = this.
|
|
155
|
+
this.writeConfig()
|
|
156
|
+
this.config = this.getConfig()
|
|
149
157
|
logError('Configuration file has been reset to default settings.')
|
|
150
158
|
}
|
|
151
159
|
|
|
@@ -189,14 +197,14 @@ class ConfigManager {
|
|
|
189
197
|
// 更新扩展
|
|
190
198
|
updateExtensions(extensions) {
|
|
191
199
|
this.config.extensions = extensions
|
|
192
|
-
this.
|
|
200
|
+
this.writeConfig(this.config)
|
|
193
201
|
logSuccess('Extensions updated successfully!')
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
// 删除扩展
|
|
197
205
|
removeExtensionByIndex(extIndex) {
|
|
198
206
|
const filePath = this.config.extensions.splice(extIndex, 1)
|
|
199
|
-
this.
|
|
207
|
+
this.writeConfig(this.config)
|
|
200
208
|
logSuccess(
|
|
201
209
|
`Extension removed from config: ${filePath}.You can run 'ai ext ls' to view the changes.`,
|
|
202
210
|
)
|
|
@@ -206,18 +214,19 @@ class ConfigManager {
|
|
|
206
214
|
this.config.extensions = this.config.extensions.filter(
|
|
207
215
|
(ext) => ext !== filePath,
|
|
208
216
|
)
|
|
209
|
-
this.
|
|
217
|
+
this.writeConfig(this.config)
|
|
210
218
|
logSuccess(
|
|
211
219
|
`Extension removed from config: ${filePath}.You can run 'ai ext ls' to view the changes.`,
|
|
212
220
|
)
|
|
213
221
|
}
|
|
214
222
|
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
getConfig() {
|
|
224
|
+
const config = require(this.configPath)
|
|
225
|
+
return merge(defaultConfig, config)
|
|
217
226
|
}
|
|
218
227
|
|
|
219
228
|
// 写入配置
|
|
220
|
-
|
|
229
|
+
writeConfig(config) {
|
|
221
230
|
if (!config) {
|
|
222
231
|
config = defaultConfig
|
|
223
232
|
}
|
|
@@ -225,6 +234,7 @@ class ConfigManager {
|
|
|
225
234
|
this.configPath,
|
|
226
235
|
`module.exports = ${JSON.stringify(config, null, 2)}`,
|
|
227
236
|
)
|
|
237
|
+
this.config = config
|
|
228
238
|
}
|
|
229
239
|
}
|
|
230
240
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const defaultConfig = {
|
|
2
|
+
ai: [],
|
|
3
|
+
currentAi: "",
|
|
4
|
+
maxIterations: -1, // ai完成工作流的最大迭代次数,-1表示无限制
|
|
5
|
+
maxMessagesLength: 150000, // 最大压缩长度,-1表示无限制
|
|
6
|
+
maxMessagesCount: 100, // 最大压缩数量,-1表示无限制
|
|
7
|
+
maxHistoryExpireTime: 30, // 整个会话的最大过期时间,单位天,-1表示无限制, 0表示不记录
|
|
8
|
+
maxLogExpireTime: 3, // 日志过期时间,单位天,-1表示无限制,0表示不记录
|
|
9
|
+
maxBlockFileSize: 20, // 最大分块文件大小,单位KB;超过该大小的文件需要分块处理
|
|
10
|
+
extensions: [],
|
|
11
|
+
skills:[],
|
|
12
|
+
encoding: "auto", // 命令行编码格式, 可设置为utf-8、gbk等, 也可以设置成auto或空值自动判断
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const aiCliConfig = {
|
|
16
|
+
DeepSeek: {
|
|
17
|
+
baseUrl: "https://api.deepseek.com",
|
|
18
|
+
model: {
|
|
19
|
+
list: ["deepseek-chat", "deepseek-reasoner", "other"],
|
|
20
|
+
defaultValue: "",
|
|
21
|
+
},
|
|
22
|
+
type: 'deepseek',
|
|
23
|
+
apiKey: "",
|
|
24
|
+
temperature: 0.7,
|
|
25
|
+
maxTokens: 8192,
|
|
26
|
+
stream: true,
|
|
27
|
+
},
|
|
28
|
+
Ollama: {
|
|
29
|
+
baseUrl: "http://localhost:11434/v1",
|
|
30
|
+
model: {
|
|
31
|
+
list: [],
|
|
32
|
+
defaultValue: "deepseek-v3.2:cloud",
|
|
33
|
+
},
|
|
34
|
+
type: 'ollama',
|
|
35
|
+
apiKey: "ollama",
|
|
36
|
+
temperature: 0.7,
|
|
37
|
+
maxTokens: 8192,
|
|
38
|
+
stream: true,
|
|
39
|
+
},
|
|
40
|
+
OpenAI: {
|
|
41
|
+
baseUrl: "https://api.openai.com/v1",
|
|
42
|
+
model: {
|
|
43
|
+
list: [],
|
|
44
|
+
defaultValue: "gpt-4",
|
|
45
|
+
},
|
|
46
|
+
type: "openai",
|
|
47
|
+
apiKey: "",
|
|
48
|
+
temperature: 0.7,
|
|
49
|
+
maxTokens: 8192,
|
|
50
|
+
stream: true,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
aiCliConfig,
|
|
58
|
+
defaultConfig,
|
|
59
|
+
};
|
|
@@ -2,7 +2,7 @@ const path = require("path");
|
|
|
2
2
|
const fs = require("fs-extra");
|
|
3
3
|
const { GlobalVariable } = require('../core/globalVariable')
|
|
4
4
|
const { logError, logSuccess } = require('../core/utils/log');
|
|
5
|
-
const { traverseFiles } = require("
|
|
5
|
+
const { traverseFiles } = require("../core/utils/normal");
|
|
6
6
|
|
|
7
7
|
class ExtConfigManager {
|
|
8
8
|
constructor() {
|
|
@@ -17,6 +17,11 @@ class ExtConfigManager {
|
|
|
17
17
|
return
|
|
18
18
|
}
|
|
19
19
|
const filePath = path.resolve(process.cwd(), fileName)
|
|
20
|
+
// 判断文件是否存在
|
|
21
|
+
if (!fs.existsSync(filePath)) {
|
|
22
|
+
logError(`File not found: ${filePath}`)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
20
25
|
// 判断是否路径是文件还是目录
|
|
21
26
|
if (fs.statSync(filePath).isDirectory()) {
|
|
22
27
|
// 扫描目录和子目录下所有js、cjs文件
|
|
@@ -37,11 +42,6 @@ class ExtConfigManager {
|
|
|
37
42
|
})
|
|
38
43
|
return
|
|
39
44
|
}
|
|
40
|
-
// 判断文件是否存在
|
|
41
|
-
if (!fs.existsSync(filePath)) {
|
|
42
|
-
logError(`File not found: ${filePath}`)
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
45
|
const userConfig = this.configManager.config
|
|
46
46
|
userConfig.extensions.push(filePath)
|
|
47
47
|
// 数组去重
|
|
@@ -81,7 +81,7 @@ class ExtConfigManager {
|
|
|
81
81
|
}
|
|
82
82
|
console.log('='.repeat(50))
|
|
83
83
|
} else {
|
|
84
|
-
|
|
84
|
+
logError(`No extensions in config.`)
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Author: Roman 306863030@qq.com
|
|
3
|
+
* @Date: 2026-03-16 09:18:05
|
|
4
|
+
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
+
* @LastEditTime: 2026-03-25 18:33:13
|
|
6
|
+
* @FilePath: \deepfish\src\cli\HistoryManager.js
|
|
7
|
+
* @Description: 对话历史记录、恢复
|
|
8
|
+
* @
|
|
9
|
+
*/
|
|
10
|
+
const fs = require('fs-extra')
|
|
11
|
+
const path = require('path')
|
|
12
|
+
const dayjs = require('dayjs')
|
|
13
|
+
const { GlobalVariable } = require('../core/globalVariable')
|
|
14
|
+
const { v4: uuidv4 } = require('uuid')
|
|
15
|
+
const { logSuccess, logError } = require('../core/utils/log')
|
|
16
|
+
const { openDirectory } = require('../core/utils/normal')
|
|
17
|
+
// cache => [history.json, id => [message.json, logs => [log.txt]]]
|
|
18
|
+
class HistoryManager {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.configManager = GlobalVariable.configManager
|
|
21
|
+
this.cacheDir = null
|
|
22
|
+
this.historyFilePath = null
|
|
23
|
+
this.history = null
|
|
24
|
+
this.id = null
|
|
25
|
+
this.logDir = null
|
|
26
|
+
GlobalVariable.historyManager = this
|
|
27
|
+
this.initRecord()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
reset() {
|
|
31
|
+
// 删除缓存目录
|
|
32
|
+
if (this.cacheDir && fs.existsSync(this.cacheDir)) {
|
|
33
|
+
fs.removeSync(this.cacheDir)
|
|
34
|
+
}
|
|
35
|
+
this.initRecord()
|
|
36
|
+
logSuccess('History has been reset.')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
initRecord() {
|
|
40
|
+
this.cacheDir = path.join(this.configManager.configDir, './cache')
|
|
41
|
+
fs.ensureDirSync(this.cacheDir)
|
|
42
|
+
this.historyFilePath = path.join(this.cacheDir, 'history.json')
|
|
43
|
+
this.history = this.getHistory()
|
|
44
|
+
this.autoClearRecord()
|
|
45
|
+
const currentPath = process.cwd()
|
|
46
|
+
const historyItem = this.history.find(
|
|
47
|
+
(item) => item.execPath === currentPath,
|
|
48
|
+
)
|
|
49
|
+
if (!historyItem) {
|
|
50
|
+
const id = uuidv4()
|
|
51
|
+
const newHistoryItem = {
|
|
52
|
+
id: id,
|
|
53
|
+
execPath: currentPath,
|
|
54
|
+
execTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
55
|
+
}
|
|
56
|
+
// 根据id创建目录,再创建一个message.json文件
|
|
57
|
+
const recordDir = path.join(this.cacheDir, id)
|
|
58
|
+
const messageFile = path.join(recordDir, 'message.json')
|
|
59
|
+
fs.ensureDirSync(recordDir)
|
|
60
|
+
fs.writeJsonSync(messageFile, [], { spaces: 2 })
|
|
61
|
+
this.history.push(newHistoryItem)
|
|
62
|
+
this.updateHistory(this.history)
|
|
63
|
+
this.id = newHistoryItem.id
|
|
64
|
+
} else {
|
|
65
|
+
historyItem.execTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
|
66
|
+
this.id = historyItem.id
|
|
67
|
+
}
|
|
68
|
+
const logDir = path.join(this.cacheDir, this.id, 'logs')
|
|
69
|
+
fs.ensureDirSync(logDir)
|
|
70
|
+
this.logDir = logDir
|
|
71
|
+
this.autoClearLog()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
openDirectory() {
|
|
75
|
+
const dir = path.join(this.cacheDir, this.id)
|
|
76
|
+
openDirectory(dir)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
autoClearRecord() {
|
|
80
|
+
const config = this.configManager.getConfig()
|
|
81
|
+
const retentionDays = config.maxHistoryExpireTime || 30
|
|
82
|
+
if (retentionDays === -1) {
|
|
83
|
+
return
|
|
84
|
+
} else if (retentionDays === 0) {
|
|
85
|
+
this.clearMessage()
|
|
86
|
+
}
|
|
87
|
+
const currentDate = dayjs()
|
|
88
|
+
const history = this.history.filter(
|
|
89
|
+
(item) => currentDate.diff(dayjs(item.execTime), 'day') > retentionDays,
|
|
90
|
+
)
|
|
91
|
+
if (history.length > 0) {
|
|
92
|
+
history.forEach((item) => {
|
|
93
|
+
const recordDir = path.join(this.cacheDir, item.id)
|
|
94
|
+
fs.removeSync(recordDir)
|
|
95
|
+
})
|
|
96
|
+
this.history = this.history.filter(
|
|
97
|
+
(item) =>
|
|
98
|
+
currentDate.diff(dayjs(item.execTime), 'day') <= retentionDays,
|
|
99
|
+
)
|
|
100
|
+
this.updateHistory(this.history)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
autoClearLog() {
|
|
105
|
+
const config = this.configManager.getConfig()
|
|
106
|
+
const retentionDays = config.maxLogExpireTime || 3
|
|
107
|
+
if (retentionDays === -1) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
if (this.history.length > 0) {
|
|
111
|
+
this.history.forEach((item) => {
|
|
112
|
+
const currentDate = dayjs()
|
|
113
|
+
const logDir = path.join(this.cacheDir, item.id, 'logs')
|
|
114
|
+
const logFiles = fs.readdirSync(logDir)
|
|
115
|
+
logFiles.forEach((logFile) => {
|
|
116
|
+
// 解析日志文件名中的日期
|
|
117
|
+
if (logFile.startsWith('log-') && logFile.endsWith('.txt')) {
|
|
118
|
+
const logDate = dayjs(logFile.slice(4, -4))
|
|
119
|
+
if (currentDate.diff(logDate, 'day') > retentionDays) {
|
|
120
|
+
fs.removeSync(path.join(logDir, logFile))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
clearMessage() {
|
|
129
|
+
const messageFile = path.join(this.cacheDir, this.id, 'message.json')
|
|
130
|
+
if (fs.existsSync(messageFile)) {
|
|
131
|
+
fs.writeJsonSync(messageFile, [], { spaces: 2 })
|
|
132
|
+
logSuccess('History messages have been cleared.')
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
logError('No history messages found to clear.')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
updateMessage(message) {
|
|
139
|
+
const messageFile = path.join(this.cacheDir, this.id, 'message.json')
|
|
140
|
+
if (fs.pathExistsSync(messageFile)) {
|
|
141
|
+
fs.writeJsonSync(messageFile, message, { spaces: 2 })
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
getMessage() {
|
|
146
|
+
const messageFile = path.join(this.cacheDir, this.id, 'message.json')
|
|
147
|
+
if (!fs.pathExistsSync(messageFile)) {
|
|
148
|
+
return []
|
|
149
|
+
}
|
|
150
|
+
return fs.readJsonSync(messageFile, { throws: false }) || []
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
outputMessage() {
|
|
154
|
+
const message = this.getMessage()
|
|
155
|
+
const outputFile = path.join(process.cwd(), 'message.json')
|
|
156
|
+
fs.writeJsonSync(outputFile, message, { spaces: 2 })
|
|
157
|
+
logSuccess(`History messages have been output to ${outputFile}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getHistory() {
|
|
161
|
+
const isExists = fs.existsSync(this.historyFilePath)
|
|
162
|
+
if (isExists) {
|
|
163
|
+
return fs.readJsonSync(this.historyFilePath, { throws: false })
|
|
164
|
+
} else {
|
|
165
|
+
// 创建一个文件
|
|
166
|
+
fs.writeJsonSync(this.historyFilePath, [], { spaces: 2 })
|
|
167
|
+
return []
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 更新history文件
|
|
172
|
+
updateHistory(history) {
|
|
173
|
+
this.history = history
|
|
174
|
+
fs.writeJsonSync(this.historyFilePath, this.history, { spaces: 2 })
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
record(messages) {
|
|
178
|
+
try {
|
|
179
|
+
const config = this.configManager.getConfig()
|
|
180
|
+
if (config.maxHistoryExpireTime === 0) {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
this.updateMessage(messages)
|
|
184
|
+
return true
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('Failed to record:', error.message)
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 记录message以及压缩后的messages
|
|
192
|
+
log(message, isCompress = false) {
|
|
193
|
+
const config = this.configManager.getConfig()
|
|
194
|
+
if (config.maxLogExpireTime === 0) {
|
|
195
|
+
return false
|
|
196
|
+
}
|
|
197
|
+
const logFile = path.join(
|
|
198
|
+
this.logDir,
|
|
199
|
+
`log-${dayjs().format('YYYY-MM-DD HH')}.txt`,
|
|
200
|
+
)
|
|
201
|
+
try {
|
|
202
|
+
let logEntry = ''
|
|
203
|
+
if (isCompress) {
|
|
204
|
+
logEntry = `[${new Date().toISOString()}][compress] ${message.content}\n`
|
|
205
|
+
} else {
|
|
206
|
+
logEntry = `[${new Date().toISOString()}][${message.role}] ${message.content}\n`
|
|
207
|
+
}
|
|
208
|
+
fs.appendFileSync(logFile, logEntry)
|
|
209
|
+
return true
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('Failed to log:', error.message)
|
|
212
|
+
return false
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = HistoryManager
|