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
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Author: Roman 306863030@qq.com
|
|
3
|
+
* @Date: 2026-03-23 15:23:42
|
|
4
|
+
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
+
* @LastEditTime: 2026-03-25 20:43:02
|
|
6
|
+
* @FilePath: \deepfish\src\cli\SkillConfigManager.js
|
|
7
|
+
* @Description: Skill configuration manager
|
|
8
|
+
*/
|
|
9
|
+
const path = require('path')
|
|
10
|
+
const fs = require('fs-extra')
|
|
11
|
+
const axios = require('axios')
|
|
12
|
+
const cheerio = require('cheerio')
|
|
13
|
+
const { GlobalVariable } = require('../core/globalVariable')
|
|
14
|
+
const { logError, logSuccess } = require('../core/utils/log')
|
|
15
|
+
const extract = require('extract-zip')
|
|
16
|
+
const { parseSkillMetadataYaml } = require('./SkillParser')
|
|
17
|
+
const { openDirectory } = require('../core/utils/normal')
|
|
18
|
+
|
|
19
|
+
// skill的数据结构: {name: string, enable: boolean, description: string, baseDir: string, skillDirName: string, location: string, skillFilePath: string, homepage: string, metadata: object}
|
|
20
|
+
class SkillConfigManager {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.configManager = GlobalVariable.configManager
|
|
23
|
+
// skill目录
|
|
24
|
+
this.skillDir = path.join(this.configManager.configDir, './skills')
|
|
25
|
+
// 自动创建skill目录
|
|
26
|
+
fs.ensureDirSync(this.skillDir)
|
|
27
|
+
this.init()
|
|
28
|
+
GlobalVariable.skillConfigManager = this
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
init() {
|
|
32
|
+
const userConfig = this.configManager.config
|
|
33
|
+
if (!userConfig.skills) {
|
|
34
|
+
userConfig.skills = []
|
|
35
|
+
this.configManager.writeConfig(userConfig)
|
|
36
|
+
}
|
|
37
|
+
this._check()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
openDirectory() {
|
|
41
|
+
// 打开目录
|
|
42
|
+
openDirectory(this.skillDir)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 预加载skills,拼接提示词
|
|
46
|
+
preLoadSkills() {
|
|
47
|
+
const skills = this.configManager.config.skills.filter((skill) => skill.enable)
|
|
48
|
+
if (skills.length === 0) {
|
|
49
|
+
return ''
|
|
50
|
+
}
|
|
51
|
+
const table = skills
|
|
52
|
+
.map((s) => `| ${s.name} | ${s.description} | ${s.location} | ${s.skillFilePath} |`)
|
|
53
|
+
.join('\n')
|
|
54
|
+
return (
|
|
55
|
+
`
|
|
56
|
+
### 可以使用的Skill
|
|
57
|
+
除了使用内置函数,还可以调用以下Skill来完成用户的请求,Skill的调用方式:当用户的请求匹配技能描述时,调用executeSkill函数加载对应Skill的SKILL.md说明文件,获取调用说明,通过仔细阅读说明文件学习Skill的使用方法,来完成任务。
|
|
58
|
+
## Available Skills
|
|
59
|
+
|
|
60
|
+
| Skill | Description | Location | SkillFilePath |
|
|
61
|
+
|-------|-------------|----------|---------------|
|
|
62
|
+
${table}
|
|
63
|
+
|
|
64
|
+
## Skills Policy
|
|
65
|
+
- 当用户请求匹配 skill description 时,调用 executeSkill 函数加载对应 SKILL.md
|
|
66
|
+
- 一次只加载一个Skill,优先匹配最具体的Skill
|
|
67
|
+
- 当用户请求不匹配任何Skill描述时,不加载任何Skill
|
|
68
|
+
- Skill即你可以使用的技能`
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 调用skill,传入参数,返回结果
|
|
73
|
+
loadSkill(skillFilePath) {
|
|
74
|
+
// 读取skill的SKILL.md,获取调用说明
|
|
75
|
+
if (!fs.existsSync(skillFilePath)) {
|
|
76
|
+
logError(`Skill file "${skillFilePath}" does not exist.`)
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
return fs.readFileSync(skillFilePath, 'utf-8')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 解析skill文件,写入到json中,获取名称、版本、作者、元数据、描述等信息
|
|
83
|
+
_parseSkill(skillDirPath) {
|
|
84
|
+
const skillMdPath = ['SKILL.md', 'skill.md']
|
|
85
|
+
.map((name) => path.join(skillDirPath, name))
|
|
86
|
+
.find((filePath) => fs.existsSync(filePath))
|
|
87
|
+
if (!skillMdPath) {
|
|
88
|
+
return {}
|
|
89
|
+
}
|
|
90
|
+
const parsed = parseSkillMetadataYaml(skillMdPath)
|
|
91
|
+
return parsed
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 查看skills列表
|
|
95
|
+
viewList() {
|
|
96
|
+
const skills = this.configManager.config.skills
|
|
97
|
+
if (skills && Array.isArray(skills)) {
|
|
98
|
+
console.log('='.repeat(50))
|
|
99
|
+
// 打印扩展列表,并加上索引
|
|
100
|
+
if (skills.length === 0) {
|
|
101
|
+
console.log(`No skills in config.`)
|
|
102
|
+
} else {
|
|
103
|
+
console.log('Skills in config:')
|
|
104
|
+
skills.forEach((skill, index) => {
|
|
105
|
+
console.log(`[${index}] ${skill.name} (${skill.enable ? 'Enabled' : 'Disabled'})`)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
console.log('='.repeat(50))
|
|
109
|
+
} else {
|
|
110
|
+
logError(`No skills in config.`)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
_check() {
|
|
114
|
+
// 如果数组的数量与目录中的数量不一致,则自动同步
|
|
115
|
+
const userConfig = this.configManager.config
|
|
116
|
+
const skills = userConfig.skills
|
|
117
|
+
const skillDirs = fs.readdirSync(this.skillDir).filter((file) => {
|
|
118
|
+
return fs.statSync(path.join(this.skillDir, file)).isDirectory()
|
|
119
|
+
})
|
|
120
|
+
if (skills.length === skillDirs.length) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
if (skills.length !== skillDirs.length) {
|
|
124
|
+
// 查询未被注册的skill,自动注册
|
|
125
|
+
skillDirs.forEach((skillDir) => {
|
|
126
|
+
if (
|
|
127
|
+
!skills.some(
|
|
128
|
+
(skill) => skill.skillDirName === skillDir || skill.name === skillDir,
|
|
129
|
+
)
|
|
130
|
+
) {
|
|
131
|
+
this._registerSkill(skillDir, false)
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
// 查询已注册但目录不存在的skill,自动从列表中删除
|
|
135
|
+
skills.forEach((skill) => {
|
|
136
|
+
if (!skillDirs.includes(skill.skillDirName)) {
|
|
137
|
+
this.remove(skill.name)
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 添加skills
|
|
144
|
+
async add(skillName) {
|
|
145
|
+
// 从当前目录process.pwd()查询是否存在同名的skill
|
|
146
|
+
// 如果存在则判断是否是目录=>1.如果是目录则拷贝到skills目录下,并添加到config中 2.如果是zip文件则解压到skills目录下,并添加到config中
|
|
147
|
+
// 如果不存在则提示从ClawHub中下载https://clawhub.ai/
|
|
148
|
+
const baseName = path.basename(skillName, '.zip')
|
|
149
|
+
const fileNames = fs.readdirSync(process.cwd())
|
|
150
|
+
const file = fileNames.find(
|
|
151
|
+
(name) => name === baseName || name === `${baseName}.zip`,
|
|
152
|
+
)
|
|
153
|
+
if (file) {
|
|
154
|
+
// 如果存在同名文件,则判断是否是目录
|
|
155
|
+
const baseDir = path.join(process.cwd(), file)
|
|
156
|
+
if (fs.statSync(baseDir).isDirectory()) {
|
|
157
|
+
// 如果是目录,则拷贝到skills目录下,并添加到config中
|
|
158
|
+
fs.copySync(baseDir, path.join(this.skillDir, file))
|
|
159
|
+
this._registerSkill(baseName)
|
|
160
|
+
} else if (path.extname(file) === '.zip') {
|
|
161
|
+
// 如果是zip文件,则解压到skills目录下,并添加到config中
|
|
162
|
+
const extractPath = path.join(this.skillDir, baseName)
|
|
163
|
+
await extract(baseDir, { dir: extractPath })
|
|
164
|
+
this._registerSkill(baseName)
|
|
165
|
+
} else {
|
|
166
|
+
logError(`File "${file}" is not a directory or a zip file.`)
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
logError(
|
|
170
|
+
`No skill named "${skillName}" found in current directory. Please download it from ClawHub (https://clawhub.ai/) and place it in the current directory.`,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// install('https://clawhub.ai/TheSethRose/agent-browser')
|
|
176
|
+
async install(skillUrl) {
|
|
177
|
+
// 从ClawHub下载zip并解压到skills目录下,并添加到config中
|
|
178
|
+
if (!skillUrl || typeof skillUrl !== 'string') {
|
|
179
|
+
logError('Invalid skill URL. Please provide a valid ClawHub URL.')
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let parsedUrl
|
|
184
|
+
try {
|
|
185
|
+
parsedUrl = new URL(skillUrl)
|
|
186
|
+
} catch (error) {
|
|
187
|
+
logError('Invalid skill URL format.')
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const host = parsedUrl.hostname.toLowerCase()
|
|
192
|
+
if (host !== 'clawhub.ai' && host !== 'www.clawhub.ai') {
|
|
193
|
+
logError(
|
|
194
|
+
'Only ClawHub URLs are supported, e.g. https://clawhub.ai/author/skill-name',
|
|
195
|
+
)
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const segments = parsedUrl.pathname.split('/').filter(Boolean)
|
|
200
|
+
if (segments.length < 2) {
|
|
201
|
+
logError(
|
|
202
|
+
'Invalid ClawHub URL. Expected format: https://clawhub.ai/<author>/<skill-name>',
|
|
203
|
+
)
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const skillName = path.basename(segments[1], '.zip')
|
|
208
|
+
const userConfig = this.configManager.config
|
|
209
|
+
if (userConfig.skills.some((skill) => skill.name === skillName)) {
|
|
210
|
+
logError(`Skill with name "${skillName}" already exists in config.`)
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
// 查看目录是否存在当前的skill
|
|
214
|
+
const skillPath = path.join(this.skillDir, skillName)
|
|
215
|
+
if (fs.existsSync(skillPath)) {
|
|
216
|
+
logError(`Skill "${skillName}" already exists in the skills directory.`)
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
const zipFilePath = path.join(this.skillDir, `${skillName}.zip`)
|
|
220
|
+
const extractPath = path.join(this.skillDir, skillName)
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// 自动获取download地址
|
|
224
|
+
const response = await axios({
|
|
225
|
+
method: 'get',
|
|
226
|
+
url: skillUrl,
|
|
227
|
+
responseType: 'text',
|
|
228
|
+
timeout: 30000,
|
|
229
|
+
maxRedirects: 5,
|
|
230
|
+
validateStatus: (status) => status >= 200 && status < 300,
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// 解析HTML获取下载链接
|
|
234
|
+
const html = response.data
|
|
235
|
+
const $ = cheerio.load(html)
|
|
236
|
+
const downloadHref = $('.skill-hero-cta a').first().attr('href')
|
|
237
|
+
|
|
238
|
+
if (!downloadHref) {
|
|
239
|
+
logError(`No download link found for skill "${skillName}".`)
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const downloadUrl = new URL(downloadHref, parsedUrl.origin).toString()
|
|
244
|
+
const zipResponse = await axios({
|
|
245
|
+
method: 'get',
|
|
246
|
+
url: downloadUrl,
|
|
247
|
+
responseType: 'arraybuffer',
|
|
248
|
+
timeout: 60000,
|
|
249
|
+
maxRedirects: 5,
|
|
250
|
+
validateStatus: (status) => status >= 200 && status < 300,
|
|
251
|
+
})
|
|
252
|
+
fs.writeFileSync(zipFilePath, Buffer.from(zipResponse.data))
|
|
253
|
+
await extract(zipFilePath, { dir: extractPath })
|
|
254
|
+
this._registerSkill(skillName)
|
|
255
|
+
logSuccess(`Skill "${skillName}" installed successfully!`)
|
|
256
|
+
} catch (error) {
|
|
257
|
+
logError(`Failed to install skill "${skillName}": ${error.message}`)
|
|
258
|
+
} finally {
|
|
259
|
+
fs.removeSync(zipFilePath)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 根据名称或索引 删除skills
|
|
264
|
+
remove(skillName) {
|
|
265
|
+
const userConfig = this.configManager.config
|
|
266
|
+
const skillObj = this._getSkill(skillName)
|
|
267
|
+
if (!skillObj) {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
const { skill, index } = skillObj
|
|
271
|
+
const skillPath = skill.location
|
|
272
|
+
userConfig.skills = userConfig.skills.filter((_, i) => i !== index)
|
|
273
|
+
this.configManager.writeConfig(userConfig)
|
|
274
|
+
if (fs.existsSync(skillPath)) {
|
|
275
|
+
fs.removeSync(skillPath)
|
|
276
|
+
}
|
|
277
|
+
logSuccess(`Skill "${skill.name}" removed successfully!`)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 根据名称或索引 启用skill-限制最大启用100个
|
|
281
|
+
enable(skillName) {
|
|
282
|
+
const userConfig = this.configManager.config
|
|
283
|
+
const skills = userConfig.skills
|
|
284
|
+
const enabledCount = skills.filter((skill) => skill.enable).length
|
|
285
|
+
if (enabledCount >= 100) {
|
|
286
|
+
logError('Cannot enable more than 100 skills.')
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
const skillObj = this._getSkill(skillName)
|
|
290
|
+
if (!skillObj) {
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
const { skill } = skillObj
|
|
294
|
+
skill.enable = true
|
|
295
|
+
this.configManager.writeConfig(userConfig)
|
|
296
|
+
logSuccess(`Skill "${skill.name}" enabled successfully!`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 根据名称或索引 禁用skill
|
|
300
|
+
disable(skillName) {
|
|
301
|
+
const userConfig = this.configManager.config
|
|
302
|
+
const skillObj = this._getSkill(skillName)
|
|
303
|
+
if (!skillObj) {
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
const { skill } = skillObj
|
|
307
|
+
skill.enable = false
|
|
308
|
+
this.configManager.writeConfig(userConfig)
|
|
309
|
+
logSuccess(`Skill "${skill.name}" disabled successfully!`)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 解析skill的描述文件,获取name、description
|
|
313
|
+
_registerSkill(skillDirName, enable = true) {
|
|
314
|
+
const userConfig = this.configManager.config
|
|
315
|
+
// 同名检测
|
|
316
|
+
if (userConfig.skills.some((skill) => skill.name === skillDirName)) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Skill with name "${skillDirName}" already exists in config.`,
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
const skillDirPath = path.join(this.skillDir, skillDirName)
|
|
322
|
+
// 获取name、description
|
|
323
|
+
const skillInfo = this._parseSkill(skillDirPath)
|
|
324
|
+
const name = skillInfo.name || skillDirName
|
|
325
|
+
const description = skillInfo.description || ''
|
|
326
|
+
userConfig.skills.push({
|
|
327
|
+
name,
|
|
328
|
+
description,
|
|
329
|
+
enable,
|
|
330
|
+
baseDir: this.skillDir,
|
|
331
|
+
skillDirName: skillDirName,
|
|
332
|
+
...skillInfo,
|
|
333
|
+
})
|
|
334
|
+
this.configManager.writeConfig(userConfig)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
_getSkill(skillName) {
|
|
338
|
+
const userConfig = this.configManager.config
|
|
339
|
+
let index = parseInt(skillName, 10)
|
|
340
|
+
let skill = null
|
|
341
|
+
if (!isNaN(index)) {
|
|
342
|
+
if (index < 0 || index >= userConfig.skills.length) {
|
|
343
|
+
logError(`Skill index "${index}" is out of range.`)
|
|
344
|
+
} else {
|
|
345
|
+
skill = userConfig.skills[index]
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
index = userConfig.skills.findIndex((skill) => skill.name === skillName)
|
|
349
|
+
if (index === -1) {
|
|
350
|
+
logError(`Skill with name "${skillName}" not found in config.`)
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
skill = userConfig.skills[index]
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
skill,
|
|
357
|
+
index,
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
module.exports = SkillConfigManager
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const fs = require("fs-extra");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const yaml = require('js-yaml');
|
|
4
|
+
|
|
5
|
+
function parseSkillMetadata(skillPath) {
|
|
6
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
7
|
+
|
|
8
|
+
// 提取 frontmatter (--- 之间的内容)
|
|
9
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
10
|
+
if (!frontmatterMatch) {
|
|
11
|
+
throw new Error(`No frontmatter found in ${skillPath}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const frontmatter = frontmatterMatch[1];
|
|
15
|
+
const metadata = {};
|
|
16
|
+
|
|
17
|
+
// 解析 key: value 或 key: "quoted value"
|
|
18
|
+
const lines = frontmatter.split('\n');
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
21
|
+
if (match) {
|
|
22
|
+
const [, key, value] = match;
|
|
23
|
+
// 去除引号
|
|
24
|
+
metadata[key] = value.replace(/^["']|["']$/g, '');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
name: metadata.name,
|
|
30
|
+
description: metadata.description,
|
|
31
|
+
homepage: metadata.homepage,
|
|
32
|
+
location: path.dirname(skillPath),
|
|
33
|
+
skillFilePath: skillPath,
|
|
34
|
+
metadata: metadata.metadata || {}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseSkillMetadataYaml(skillPath) {
|
|
39
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
40
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
41
|
+
|
|
42
|
+
if (!frontmatterMatch) {
|
|
43
|
+
throw new Error(`No frontmatter found in ${skillPath}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const frontmatter = yaml.load(frontmatterMatch[1]);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
name: frontmatter.name,
|
|
50
|
+
description: frontmatter.description,
|
|
51
|
+
homepage: frontmatter.homepage,
|
|
52
|
+
location: path.dirname(skillPath),
|
|
53
|
+
metadata: frontmatter.metadata || {},
|
|
54
|
+
skillFilePath: skillPath,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
parseSkillMetadata,
|
|
60
|
+
parseSkillMetadataYaml
|
|
61
|
+
}
|
package/src/cli/ai-config.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @Author: Roman 306863030@qq.com
|
|
3
3
|
* @Date: 2026-03-19 11:45:10
|
|
4
|
-
* @LastEditors:
|
|
5
|
-
* @LastEditTime: 2026-03-
|
|
4
|
+
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
+
* @LastEditTime: 2026-03-25 18:40:12
|
|
6
6
|
* @FilePath: \deepfish\src\cli\ai-config.js
|
|
7
7
|
* @Description: ai config 相关命令
|
|
8
8
|
* @
|
|
9
9
|
*/
|
|
10
10
|
const { program } = require('commander')
|
|
11
|
-
const { aiCliConfig } = require('./
|
|
11
|
+
const { aiCliConfig } = require('./DefaultConfig')
|
|
12
12
|
const { askConfirm, askAny } = require('../core/utils/log')
|
|
13
13
|
const ConfigManager = require('./ConfigManager')
|
|
14
14
|
|
|
@@ -24,6 +24,13 @@ configCommand
|
|
|
24
24
|
configManager.edit()
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
+
configCommand
|
|
28
|
+
.command('dir')
|
|
29
|
+
.description('Open configuration directory')
|
|
30
|
+
.action(() => {
|
|
31
|
+
configManager.dir()
|
|
32
|
+
})
|
|
33
|
+
|
|
27
34
|
configCommand
|
|
28
35
|
.command('reset')
|
|
29
36
|
.description('Reset configuration file')
|
|
@@ -58,6 +65,10 @@ configCommand
|
|
|
58
65
|
}
|
|
59
66
|
const hasName = configManager.checkName(value.trim())
|
|
60
67
|
if (hasName) {
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
// 结束会话
|
|
70
|
+
process.exit(0)
|
|
71
|
+
})
|
|
61
72
|
return 'Configuration with this name already exists. Please enter a different name.'
|
|
62
73
|
}
|
|
63
74
|
return true
|
|
@@ -105,7 +116,7 @@ configCommand
|
|
|
105
116
|
message: 'Enter DeepSeek model name:',
|
|
106
117
|
when: (answers) =>
|
|
107
118
|
answers.Type === 'DeepSeek' && answers.model === 'other',
|
|
108
|
-
default: 'deepseek-
|
|
119
|
+
default: 'deepseek-reasoner',
|
|
109
120
|
},
|
|
110
121
|
{
|
|
111
122
|
type: 'input',
|
|
@@ -130,6 +141,13 @@ configCommand
|
|
|
130
141
|
name: 'maxTokens',
|
|
131
142
|
message: 'Enter max tokens:',
|
|
132
143
|
default: (answers) => {
|
|
144
|
+
if (answers.Type === 'DeepSeek') {
|
|
145
|
+
if (answers.model === 'deepseek-chat') {
|
|
146
|
+
return 8192
|
|
147
|
+
} else if (answers.model === 'deepseek-reasoner') {
|
|
148
|
+
return 65536
|
|
149
|
+
}
|
|
150
|
+
}
|
|
133
151
|
return aiCliConfig[answers.Type].maxTokens
|
|
134
152
|
},
|
|
135
153
|
validate: (value) => value > 0 || 'Max tokens must be greater than 0',
|
|
@@ -154,7 +172,7 @@ configCommand
|
|
|
154
172
|
maxTokens: answers.maxTokens,
|
|
155
173
|
stream: answers.stream,
|
|
156
174
|
}
|
|
157
|
-
configManager.addAiConfig(aiConfig)
|
|
175
|
+
return configManager.addAiConfig(aiConfig)
|
|
158
176
|
})
|
|
159
177
|
|
|
160
178
|
configCommand
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { program } = require("commander");
|
|
2
|
+
const HistoryManager = require("./HistoryManager");
|
|
3
|
+
const historyManager = new HistoryManager();
|
|
4
|
+
const extCommand = program
|
|
5
|
+
.command("history")
|
|
6
|
+
.description("History management commands");
|
|
7
|
+
|
|
8
|
+
extCommand
|
|
9
|
+
.command("clear")
|
|
10
|
+
.description("Clear the history messages for the current directory")
|
|
11
|
+
.action(() => {
|
|
12
|
+
historyManager.clearMessage();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
extCommand
|
|
16
|
+
.command("output")
|
|
17
|
+
.description("Output the history messages to current directory")
|
|
18
|
+
.action(() => {
|
|
19
|
+
historyManager.outputMessage();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
extCommand
|
|
23
|
+
.command("dir")
|
|
24
|
+
.description("Open the history directory")
|
|
25
|
+
.action(() => {
|
|
26
|
+
historyManager.openDirectory();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
extCommand
|
|
30
|
+
.command("reset")
|
|
31
|
+
.description("Reset all history for all directories")
|
|
32
|
+
.action(() => {
|
|
33
|
+
historyManager.reset();
|
|
34
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @Author: Roman 306863030@qq.com
|
|
3
|
+
* @Date: 2026-03-23 15:07:51
|
|
4
|
+
* @LastEditors: Roman 306863030@qq.com
|
|
5
|
+
* @LastEditTime: 2026-03-25 16:03:48
|
|
6
|
+
* @FilePath: \deepfish\src\cli\ai-skill.js
|
|
7
|
+
* @Description: AI skill management CLI
|
|
8
|
+
* @
|
|
9
|
+
*/
|
|
10
|
+
const { program } = require("commander");
|
|
11
|
+
const SkillConfigManager = require("./SkillConfigManager");
|
|
12
|
+
const skillConfigManager = new SkillConfigManager()
|
|
13
|
+
// ai skill command
|
|
14
|
+
const skillCommand = program
|
|
15
|
+
.command("skill")
|
|
16
|
+
.description("Skill management commands");
|
|
17
|
+
|
|
18
|
+
skillCommand
|
|
19
|
+
.command("ls")
|
|
20
|
+
.description("List all skills in the configuration")
|
|
21
|
+
.action(() => {
|
|
22
|
+
skillConfigManager.viewList();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
skillCommand
|
|
26
|
+
.command("add <name>")
|
|
27
|
+
.description("Add a local skill directory or zip file")
|
|
28
|
+
.action(async (name) => {
|
|
29
|
+
await skillConfigManager.add(name);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
skillCommand
|
|
33
|
+
.command("del <name>")
|
|
34
|
+
.description("Remove a skill by name or index")
|
|
35
|
+
.action((name) => {
|
|
36
|
+
skillConfigManager.remove(name);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
skillCommand
|
|
40
|
+
.command("dir")
|
|
41
|
+
.description("Open the history directory")
|
|
42
|
+
.action(() => {
|
|
43
|
+
skillConfigManager.openDirectory();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
skillCommand
|
|
47
|
+
.command("install <url>")
|
|
48
|
+
.description("Install a skill from ClawHub")
|
|
49
|
+
.action(async (url) => {
|
|
50
|
+
await skillConfigManager.install(url);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
skillCommand
|
|
54
|
+
.command("enable <name>")
|
|
55
|
+
.description("Enable a skill by name or index")
|
|
56
|
+
.action((name) => {
|
|
57
|
+
skillConfigManager.enable(name);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
skillCommand
|
|
61
|
+
.command("disable <name>")
|
|
62
|
+
.description("Disable a skill by name or index")
|
|
63
|
+
.action((name) => {
|
|
64
|
+
skillConfigManager.disable(name);
|
|
65
|
+
});
|
package/src/cli/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
const { program } = require("commander");
|
|
3
3
|
const AICLI = require("../core/AICLI");
|
|
4
4
|
const { logError } = require("../core/utils/log");
|
|
5
5
|
const { GlobalVariable } = require("../core/globalVariable.js");
|
|
6
|
-
const ConfigManager = require("./ConfigManager.js");
|
|
7
6
|
require("./ai-config.js");
|
|
8
7
|
require("./ai-ext.js");
|
|
9
|
-
|
|
8
|
+
require("./ai-skill.js");
|
|
9
|
+
require("./ai-history.js");
|
|
10
10
|
program
|
|
11
11
|
.version("1.0.0")
|
|
12
12
|
.description(
|
|
@@ -21,7 +21,7 @@ program
|
|
|
21
21
|
|
|
22
22
|
async function main() {
|
|
23
23
|
try {
|
|
24
|
-
if (program.args && (program.args[0] === "config" || program.args[0] === "ext")) {
|
|
24
|
+
if (program.args && (program.args[0] === "config" || program.args[0] === "ext" || program.args[0] === "skill") || (program.args[0] === "history")) {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
const options = program.opts();
|
|
@@ -50,9 +50,6 @@ async function main() {
|
|
|
50
50
|
logError("Please use 'ai config use <name>' to set a current configuration.");
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
|
-
if (!GlobalVariable.configManager) {
|
|
54
|
-
GlobalVariable.configManager = new ConfigManager();
|
|
55
|
-
}
|
|
56
53
|
const cli = new AICLI(configManager.config);
|
|
57
54
|
if (options.interactive) {
|
|
58
55
|
cli.startInteractive();
|
package/src/core/AICLI.js
CHANGED
|
@@ -2,22 +2,20 @@ const ExtensionManager = require('./extension/ExtensionManager')
|
|
|
2
2
|
const readline = require('readline')
|
|
3
3
|
const { logError } = require('./utils/log')
|
|
4
4
|
const { GlobalVariable } = require('./globalVariable')
|
|
5
|
-
const
|
|
6
|
-
const AIService = require('./ai-services/AIService')
|
|
5
|
+
const AIService = require('./ai-services')
|
|
7
6
|
|
|
8
7
|
class AICLI {
|
|
9
8
|
constructor(config) {
|
|
10
9
|
this.config = config
|
|
11
10
|
this.aiConfig = GlobalVariable.configManager.getCurrentAiConfig()
|
|
11
|
+
this.skillConfigManager = GlobalVariable.skillConfigManager
|
|
12
|
+
this.historyManager = GlobalVariable.historyManager
|
|
12
13
|
// 初始化扩展
|
|
13
14
|
this.extensionManager = new ExtensionManager(this)
|
|
14
15
|
this.Tools = this.extensionManager.extensions.functions
|
|
15
|
-
|
|
16
|
+
|
|
16
17
|
this.aiService = new AIService(this.aiConfig.type, this)
|
|
17
18
|
GlobalVariable.aiCli = this
|
|
18
|
-
GlobalVariable.aiRecorder = this.aiRecorder
|
|
19
|
-
GlobalVariable.isRecordHistory = this.config.isRecordHistory || false
|
|
20
|
-
GlobalVariable.isLog = this.config.isLog || false
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
// 单轮对话
|