xx-ai-cli 0.1.0
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 +328 -0
- package/dist/commands/agent.d.ts +12 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +311 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/ask.d.ts +7 -0
- package/dist/commands/ask.d.ts.map +1 -0
- package/dist/commands/ask.js +52 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/chat.d.ts +9 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +129 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/cmd.d.ts +6 -0
- package/dist/commands/cmd.d.ts.map +1 -0
- package/dist/commands/cmd.js +52 -0
- package/dist/commands/cmd.js.map +1 -0
- package/dist/commands/code.d.ts +8 -0
- package/dist/commands/code.d.ts.map +1 -0
- package/dist/commands/code.js +75 -0
- package/dist/commands/code.js.map +1 -0
- package/dist/commands/file.d.ts +7 -0
- package/dist/commands/file.d.ts.map +1 -0
- package/dist/commands/file.js +66 -0
- package/dist/commands/file.js.map +1 -0
- package/dist/commands/history.d.ts +5 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +159 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +144 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install.d.ts +24 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +328 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/model.d.ts +6 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +128 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/pkg.d.ts +12 -0
- package/dist/commands/pkg.d.ts.map +1 -0
- package/dist/commands/pkg.js +132 -0
- package/dist/commands/pkg.js.map +1 -0
- package/dist/commands/run.d.ts +9 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +185 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/core/agentTask.d.ts +20 -0
- package/dist/core/agentTask.d.ts.map +1 -0
- package/dist/core/agentTask.js +81 -0
- package/dist/core/agentTask.js.map +1 -0
- package/dist/core/config.d.ts +41 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +153 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/executor.d.ts +20 -0
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/core/executor.js +68 -0
- package/dist/core/executor.js.map +1 -0
- package/dist/core/files.d.ts +24 -0
- package/dist/core/files.d.ts.map +1 -0
- package/dist/core/files.js +95 -0
- package/dist/core/files.js.map +1 -0
- package/dist/core/github.d.ts +32 -0
- package/dist/core/github.d.ts.map +1 -0
- package/dist/core/github.js +82 -0
- package/dist/core/github.js.map +1 -0
- package/dist/core/prompt.d.ts +9 -0
- package/dist/core/prompt.d.ts.map +1 -0
- package/dist/core/prompt.js +44 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/router.d.ts +16 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/router.js +182 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/search.d.ts +14 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +179 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/session.d.ts +38 -0
- package/dist/core/session.d.ts.map +1 -0
- package/dist/core/session.js +172 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/stream.d.ts +15 -0
- package/dist/core/stream.d.ts.map +1 -0
- package/dist/core/stream.js +102 -0
- package/dist/core/stream.js.map +1 -0
- package/dist/core/tools.d.ts +343 -0
- package/dist/core/tools.d.ts.map +1 -0
- package/dist/core/tools.js +293 -0
- package/dist/core/tools.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +372 -0
- package/dist/index.js.map +1 -0
- package/dist/ui/renderer.d.ts +17 -0
- package/dist/ui/renderer.d.ts.map +1 -0
- package/dist/ui/renderer.js +69 -0
- package/dist/ui/renderer.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 xx-ai-cli contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# 🤖 XX-AI-CLI
|
|
2
|
+
|
|
3
|
+
> 国内主流大模型命令行工具,支持 DeepSeek、通义千问、智谱GLM、豆包、Kimi 等。
|
|
4
|
+
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](https://www.typescriptlang.org)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ 功能特性
|
|
11
|
+
|
|
12
|
+
- 🗨️ **交互式对话** - 多轮对话,支持历史上下文,**自动持久化**
|
|
13
|
+
- ⚡ **单次提问** - 快速问答,流式输出
|
|
14
|
+
- 💻 **代码助手** - 生成、解释、优化、Review 代码
|
|
15
|
+
- 📄 **文件分析** - 读取本地文件让 AI 分析
|
|
16
|
+
- 🔧 **命令建议** - 自然语言转 Shell 命令
|
|
17
|
+
- 🔄 **多模型切换** - 随时切换不同 AI 服务商
|
|
18
|
+
- 🎯 **一键初始化** - `xxcli init` 交互式向导,新用户 1 分钟完成配置
|
|
19
|
+
- 📚 **历史会话** - 查看、恢复任意历史对话
|
|
20
|
+
- 🔗 **管道输入** - `cat file | xx "问题"` 无缝联动其他工具
|
|
21
|
+
|
|
22
|
+
## 📦 支持的模型
|
|
23
|
+
|
|
24
|
+
| 模型 | Provider | 获取 API Key |
|
|
25
|
+
| ------------- | ---------- | -------------------------------------------------------------------- |
|
|
26
|
+
| DeepSeek | `deepseek` | [platform.deepseek.com](https://platform.deepseek.com) |
|
|
27
|
+
| 通义千问 | `qwen` | [dashscope.console.aliyun.com](https://dashscope.console.aliyun.com) |
|
|
28
|
+
| 智谱 GLM | `glm` | [open.bigmodel.cn](https://open.bigmodel.cn) |
|
|
29
|
+
| 豆包 | `doubao` | [console.volcengine.com](https://console.volcengine.com/ark) |
|
|
30
|
+
| 月之暗面 Kimi | `moonshot` | [platform.moonshot.cn](https://platform.moonshot.cn) |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## � API Key 安全性说明
|
|
35
|
+
|
|
36
|
+
我们深知 API Key 是你访问 AI 服务的凭证,安全性至关重要。以下是本工具对 Key 的完整处理机制,请放心使用。
|
|
37
|
+
|
|
38
|
+
### ✅ Key 绝不离开你的设备
|
|
39
|
+
|
|
40
|
+
- **本工具不设任何服务端**,没有后台、没有中转服务器,所有 AI 请求均由你的设备**直连**各模型官方 API(DeepSeek、阿里云、智谱等)
|
|
41
|
+
- Key **仅用于构造请求头** `Authorization: Bearer <key>`,直接发送给对应平台,全程不经过任何第三方
|
|
42
|
+
- 本工具代码**完全开源**,你可以自行审查 [`src/core/stream.ts`](src/core/stream.ts) 中的网络请求逻辑
|
|
43
|
+
|
|
44
|
+
### ✅ Key 的存储位置
|
|
45
|
+
|
|
46
|
+
当你使用 `xxcli model set-key` 或 `xxcli init` 保存 Key 时,Key **仅写入你本机的用户配置目录**:
|
|
47
|
+
|
|
48
|
+
| 系统 | 配置文件路径 |
|
|
49
|
+
| ------------- | ------------------------------ |
|
|
50
|
+
| macOS / Linux | `~/.config/ai-cli/config.json` |
|
|
51
|
+
| Windows | `%APPDATA%\ai-cli\config.json` |
|
|
52
|
+
|
|
53
|
+
- 该文件**不在项目目录内**,不会被 `git commit` 提交
|
|
54
|
+
- 只有当前用户有读写权限
|
|
55
|
+
- 你可以随时运行 `xxcli config show` 查看配置文件的完整路径
|
|
56
|
+
|
|
57
|
+
### ✅ 所有显示均已脱敏
|
|
58
|
+
|
|
59
|
+
无论是 `xxcli model list` 还是 `xxcli config show`,界面上显示的 Key 均经过**脱敏处理**:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
sk-ab****efgh
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
截图或分享配置信息时无需担心 Key 被看到。
|
|
66
|
+
|
|
67
|
+
### ✅ 三种方式存储 Key(安全性递增)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# 方式一:命令行直接传入(会留在 shell history,安全性较低)
|
|
71
|
+
xxcli model set-key deepseek sk-xxxxxxxxxxxxxxxx
|
|
72
|
+
|
|
73
|
+
# 方式二:隐藏输入模式(输入不会显示和记录到 shell history,推荐)
|
|
74
|
+
xxcli model set-key deepseek
|
|
75
|
+
# 然后在提示符下输入 Key,输入内容不会回显
|
|
76
|
+
|
|
77
|
+
# 方式三:环境变量(Key 永不写入磁盘,最安全)
|
|
78
|
+
export DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx
|
|
79
|
+
xx chat # 自动读取环境变量,重启终端后自动失效
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**环境变量一览:**
|
|
83
|
+
|
|
84
|
+
| 模型 | 环境变量名 |
|
|
85
|
+
| ------------- | ------------------ |
|
|
86
|
+
| DeepSeek | `DEEPSEEK_API_KEY` |
|
|
87
|
+
| 通义千问 | `QWEN_API_KEY` |
|
|
88
|
+
| 智谱 GLM | `GLM_API_KEY` |
|
|
89
|
+
| 豆包 | `DOUBAO_API_KEY` |
|
|
90
|
+
| 月之暗面 Kimi | `MOONSHOT_API_KEY` |
|
|
91
|
+
| MiniMax 海螺 | `MINIMAX_API_KEY` |
|
|
92
|
+
| 腾讯混元 | `HUNYUAN_API_KEY` |
|
|
93
|
+
| 百度文心一言 | `ERNIE_API_KEY` |
|
|
94
|
+
|
|
95
|
+
> 💡 **推荐做法**:将 `export DEEPSEEK_API_KEY=...` 写入 `~/.zshrc` 或 `~/.bashrc`,Key 不落盘且持久生效。
|
|
96
|
+
|
|
97
|
+
### ✅ 历史会话自动脱敏
|
|
98
|
+
|
|
99
|
+
对话历史保存在 `~/.config/ai-cli/history/` 目录下。即使你在对话中不小心粘贴了 Key,保存时也会**自动识别并脱敏**(替换为 `sk-ab********[REDACTED]`),不会以明文形式存储。
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## �🚀 快速开始
|
|
104
|
+
|
|
105
|
+
### 安装依赖
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm install
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 构建
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run build
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 链接到全局
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm link
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> 安装后提供两个命令:`xxcli`(完整名)和 `xx`(短别名)
|
|
124
|
+
|
|
125
|
+
### 或者直接用 tsx 运行(开发模式)
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm run dev -- chat
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 🎯 首次使用 — 运行初始化向导
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
xxcli init
|
|
135
|
+
# 或
|
|
136
|
+
xx init
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
向导会引导你:选择要配置的模型 → 输入 API Key → 设置默认模型 → 开启流式输出
|
|
140
|
+
|
|
141
|
+
配置完成后即可开始使用!
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 📖 使用示例
|
|
148
|
+
|
|
149
|
+
### 🔗 管道输入(与其他工具联动)
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# 分析错误日志
|
|
153
|
+
cat error.log | xx "帮我分析这个错误"
|
|
154
|
+
|
|
155
|
+
# 生成 Git commit message
|
|
156
|
+
git diff | xx "帮我写 commit message"
|
|
157
|
+
|
|
158
|
+
# Review 代码文件
|
|
159
|
+
cat src/utils.ts | xx "帮我 code review"
|
|
160
|
+
|
|
161
|
+
# 分析 JSON 配置
|
|
162
|
+
cat package.json | xx "这个项目用了哪些主要依赖"
|
|
163
|
+
|
|
164
|
+
# 无问题时自动分析内容
|
|
165
|
+
cat README.md | xx
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 📚 历史会话管理
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# 列出最近 20 条历史会话
|
|
172
|
+
xxcli history list
|
|
173
|
+
xx history list
|
|
174
|
+
|
|
175
|
+
# 查看某条历史会话详情
|
|
176
|
+
xxcli history show <ID>
|
|
177
|
+
|
|
178
|
+
# 从历史会话继续对话
|
|
179
|
+
xxcli history resume <ID>
|
|
180
|
+
|
|
181
|
+
# 清空所有历史
|
|
182
|
+
xxcli history clear
|
|
183
|
+
|
|
184
|
+
# 对话中用 /history 快速查看最近 10 条
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 🔧 配置
|
|
190
|
+
|
|
191
|
+
### 设置 API Key
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# DeepSeek(推荐,性价比高)
|
|
195
|
+
xxcli model set-key deepseek sk-xxxxxxxxxxxxxxxx
|
|
196
|
+
|
|
197
|
+
# 通义千问
|
|
198
|
+
xxcli model set-key qwen sk-xxxxxxxxxxxxxxxx
|
|
199
|
+
|
|
200
|
+
# 智谱 GLM
|
|
201
|
+
xxcli model set-key glm xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxx
|
|
202
|
+
|
|
203
|
+
# 豆包
|
|
204
|
+
xxcli model set-key doubao xxxxxxxxxxxxxxxx
|
|
205
|
+
|
|
206
|
+
# Kimi
|
|
207
|
+
xxcli model set-key moonshot sk-xxxxxxxxxxxxxxxx
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 切换默认模型
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
xxcli model use deepseek
|
|
214
|
+
xxcli model use qwen
|
|
215
|
+
xxcli model use glm
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 查看配置
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
xxcli model list # 查看所有模型状态
|
|
222
|
+
xxcli config show # 查看当前配置
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 📖 使用示例
|
|
228
|
+
|
|
229
|
+
### 💬 交互式对话
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
xxcli chat
|
|
233
|
+
xxcli chat --model qwen
|
|
234
|
+
xxcli chat --system "你是一个 Python 专家"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**对话内置命令:**
|
|
238
|
+
|
|
239
|
+
| 命令 | 说明 |
|
|
240
|
+
| ---------- | -------------------------- |
|
|
241
|
+
| `/clear` | 清空当前对话 |
|
|
242
|
+
| `/model` | 查看当前模型 |
|
|
243
|
+
| `/history` | 快速查看最近 10 条历史会话 |
|
|
244
|
+
| `/exit` | 退出(会话自动保存) |
|
|
245
|
+
|
|
246
|
+
### ⚡ 单次提问
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
xxcli ask "什么是 CAP 定理"
|
|
250
|
+
xxcli ask "解释 React Hooks" --model glm
|
|
251
|
+
xxcli ask "写个斐波那契数列" --no-stream
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 💻 代码助手
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# 生成代码
|
|
258
|
+
xxcli code gen "用 TypeScript 写一个单例模式"
|
|
259
|
+
xxcli code gen "实现 LRU 缓存" --model deepseek
|
|
260
|
+
|
|
261
|
+
# 解释代码(可以传文件路径或直接输入代码)
|
|
262
|
+
xxcli code explain ./src/index.ts
|
|
263
|
+
xxcli code explain "const fn = () => {}"
|
|
264
|
+
|
|
265
|
+
# 优化代码
|
|
266
|
+
xxcli code optimize ./src/utils.ts
|
|
267
|
+
|
|
268
|
+
# Code Review
|
|
269
|
+
xxcli code review ./src/api.ts
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 📄 文件分析
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
xxcli file ./package.json "这个项目依赖了哪些主要库?"
|
|
276
|
+
xxcli file ./src/index.ts "帮我找出潜在的 Bug"
|
|
277
|
+
xxcli file ./README.md "用一句话总结这个项目"
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### 🔧 Shell 命令建议
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
xxcli cmd "找出所有大于 1MB 的文件"
|
|
284
|
+
xxcli cmd "查看端口 8080 被哪个进程占用"
|
|
285
|
+
xxcli cmd "递归删除所有 node_modules 目录"
|
|
286
|
+
xxcli cmd "统计当前目录下 ts 文件数量"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## ⚙️ 高级配置
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# 关闭流式输出
|
|
295
|
+
xxcli config set stream false
|
|
296
|
+
|
|
297
|
+
# 设置历史长度
|
|
298
|
+
xxcli config set historySize 30
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 🗂️ 项目结构
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
src/
|
|
307
|
+
├── commands/ # CLI 命令实现
|
|
308
|
+
│ ├── chat.ts # 交互式对话
|
|
309
|
+
│ ├── ask.ts # 单次提问
|
|
310
|
+
│ ├── code.ts # 代码助手
|
|
311
|
+
│ ├── file.ts # 文件分析
|
|
312
|
+
│ ├── cmd.ts # 命令建议
|
|
313
|
+
│ └── model.ts # 模型/配置管理
|
|
314
|
+
├── core/
|
|
315
|
+
│ ├── config.ts # 配置管理
|
|
316
|
+
│ ├── session.ts # 会话/历史
|
|
317
|
+
│ ├── stream.ts # 流式 API 调用
|
|
318
|
+
│ └── prompt.ts # Prompt 模板
|
|
319
|
+
├── ui/
|
|
320
|
+
│ └── renderer.ts # 终端 UI 渲染
|
|
321
|
+
└── index.ts # 主入口
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 📝 License
|
|
327
|
+
|
|
328
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ModelProvider } from '../core/config.js';
|
|
2
|
+
export interface AgentOptions {
|
|
3
|
+
model?: ModelProvider;
|
|
4
|
+
autoConfirm?: boolean;
|
|
5
|
+
maxSteps?: number;
|
|
6
|
+
list?: boolean;
|
|
7
|
+
resume?: string;
|
|
8
|
+
delete?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function agentListCommand(): void;
|
|
11
|
+
export declare function agentCommand(task?: string, options?: AgentOptions): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAehF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA+CD,wBAAgB,gBAAgB,IAAI,IAAI,CAoBvC;AAGD,wBAAsB,YAAY,CAChC,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAmIf"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { config, PROVIDER_LABELS } from '../core/config.js';
|
|
4
|
+
import { Session } from '../core/session.js';
|
|
5
|
+
import { chat } from '../core/stream.js';
|
|
6
|
+
import { TOOL_DEFINITIONS, executeTool } from '../core/tools.js';
|
|
7
|
+
import { theme, printBanner } from '../ui/renderer.js';
|
|
8
|
+
import { createTask, saveTask, loadTask, loadAllTasks, deleteTask, } from '../core/agentTask.js';
|
|
9
|
+
const MAX_CONSECUTIVE_ERRORS = 3; // 连续错误次数上限
|
|
10
|
+
const AGENT_SYSTEM_PROMPT = `你是一个强大的 AI Agent,运行在用户的命令行终端中。
|
|
11
|
+
|
|
12
|
+
## 可用工具能力
|
|
13
|
+
- 联网搜索最新信息
|
|
14
|
+
- 读写本地文件
|
|
15
|
+
- 执行 Shell 命令
|
|
16
|
+
- 安装 npm 包
|
|
17
|
+
|
|
18
|
+
## 重要:内置工具安装命令
|
|
19
|
+
当用户要求安装某个工具(如 openclaw、cursor、ollama 等)时:
|
|
20
|
+
1. **直接使用** run_command 调用 \`xxcli install <工具名>\`,不要先搜索
|
|
21
|
+
2. 这个内置命令会自动检测系统环境并选择最佳安装方式(npm/brew/github)
|
|
22
|
+
3. 如果安装失败,分析错误信息后换备用方式重试
|
|
23
|
+
4. 示例:用户说"安装 openclaw" → run_command: "xxcli install openclaw"
|
|
24
|
+
|
|
25
|
+
## 工作原则
|
|
26
|
+
1. 优先理解用户意图,制定清晰的执行计划
|
|
27
|
+
2. 安装类任务:直接调用 \`xxcli install\`,无需搜索
|
|
28
|
+
3. 执行危险操作前主动告知用户
|
|
29
|
+
4. 完成任务后给出清晰的总结
|
|
30
|
+
5. 用中文回复
|
|
31
|
+
|
|
32
|
+
当前工作目录:${process.cwd()}`;
|
|
33
|
+
// ─── 任务列表展示 ─────────────────────────────────────────────
|
|
34
|
+
function formatDate(iso) {
|
|
35
|
+
if (!iso)
|
|
36
|
+
return '—';
|
|
37
|
+
const d = new Date(iso);
|
|
38
|
+
const now = new Date();
|
|
39
|
+
const diffMin = Math.floor((now.getTime() - d.getTime()) / 60000);
|
|
40
|
+
if (diffMin < 1)
|
|
41
|
+
return '刚刚';
|
|
42
|
+
if (diffMin < 60)
|
|
43
|
+
return `${diffMin} 分钟前`;
|
|
44
|
+
if (diffMin < 1440)
|
|
45
|
+
return `${Math.floor(diffMin / 60)} 小时前`;
|
|
46
|
+
return `${Math.floor(diffMin / 1440)} 天前`;
|
|
47
|
+
}
|
|
48
|
+
const STATUS_ICONS = {
|
|
49
|
+
active: chalk.yellow('⚡'),
|
|
50
|
+
paused: chalk.cyan('⏸'),
|
|
51
|
+
completed: chalk.green('✔'),
|
|
52
|
+
failed: chalk.red('✗'),
|
|
53
|
+
};
|
|
54
|
+
export function agentListCommand() {
|
|
55
|
+
const tasks = loadAllTasks();
|
|
56
|
+
if (!tasks.length) {
|
|
57
|
+
console.log('\n' + theme.dim('暂无保存的任务。'));
|
|
58
|
+
console.log(theme.dim('使用 xxcli agent "你的任务" 开始一个新任务\n'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.log('\n' + theme.bold(`🗂 Agent 任务列表(共 ${tasks.length} 条)\n`));
|
|
62
|
+
for (const t of tasks) {
|
|
63
|
+
const icon = STATUS_ICONS[t.status] ?? '○';
|
|
64
|
+
const model = PROVIDER_LABELS[t.model] ?? t.model;
|
|
65
|
+
console.log(` ${icon} ${chalk.bold(t.title)}`);
|
|
66
|
+
console.log(` ${chalk.dim(`ID: ${t.id}`)} ${chalk.dim(model)} ${chalk.dim(`步骤 ${t.step}`)} ${chalk.dim(formatDate(t.updatedAt))}`);
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
69
|
+
console.log(theme.dim(' 续接任务: xxcli agent --resume <ID>'));
|
|
70
|
+
console.log(theme.dim(' 删除任务: xxcli agent --delete <ID>\n'));
|
|
71
|
+
}
|
|
72
|
+
// ─── 主入口 ───────────────────────────────────────────────────
|
|
73
|
+
export async function agentCommand(task, options = {}) {
|
|
74
|
+
// ── 列出任务 ────────────────────────────────────────────────
|
|
75
|
+
if (options.list) {
|
|
76
|
+
agentListCommand();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// ── 删除任务 ────────────────────────────────────────────────
|
|
80
|
+
if (options.delete) {
|
|
81
|
+
const ok = deleteTask(options.delete);
|
|
82
|
+
console.log(ok
|
|
83
|
+
? theme.success(`✔ 任务 ${options.delete} 已删除`)
|
|
84
|
+
: theme.error(`未找到任务 ${options.delete}`));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const provider = options.model ?? config.get('defaultProvider');
|
|
88
|
+
const modelConfig = config.getProvider(provider);
|
|
89
|
+
if (!modelConfig) {
|
|
90
|
+
console.log(theme.error(`未配置 ${PROVIDER_LABELS[provider]} 的 API Key`));
|
|
91
|
+
console.log(theme.dim('请运行: xxcli init'));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
if (options.autoConfirm)
|
|
95
|
+
config.set('agentAutoConfirm', true);
|
|
96
|
+
printBanner();
|
|
97
|
+
const providerLabel = PROVIDER_LABELS[provider];
|
|
98
|
+
const maxSteps = options.maxSteps ?? 15;
|
|
99
|
+
console.log(theme.divider());
|
|
100
|
+
console.log(theme.info(`🤖 Agent 模式 | 模型: ${chalk.bold(providerLabel)}`));
|
|
101
|
+
console.log(theme.dim('可用工具: 联网搜索 · 读写文件 · 执行命令 · npm 安装'));
|
|
102
|
+
console.log(theme.dim('/exit 退出 | /clear 清空 | /tools 查看工具 | /tasks 任务列表'));
|
|
103
|
+
console.log(theme.divider() + '\n');
|
|
104
|
+
// ── 续接任务模式 ────────────────────────────────────────────
|
|
105
|
+
if (options.resume) {
|
|
106
|
+
const saved = loadTask(options.resume);
|
|
107
|
+
if (!saved) {
|
|
108
|
+
console.log(theme.error(`未找到任务 ID: ${options.resume}`));
|
|
109
|
+
console.log(theme.dim('使用 xxcli agent --list 查看所有任务'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
console.log(theme.info(`▶ 续接任务: ${chalk.bold(saved.title)}`));
|
|
113
|
+
console.log(theme.dim(` 状态: ${saved.status} 已执行 ${saved.step} 步 上次更新: ${formatDate(saved.updatedAt)}\n`));
|
|
114
|
+
// 恢复任务消息并继续
|
|
115
|
+
saved.status = 'active';
|
|
116
|
+
await runAgentLoop(null, // 不新增用户消息,直接续接
|
|
117
|
+
saved.messages, modelConfig, providerLabel, maxSteps, provider, saved);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// ── 直接执行单个任务 ────────────────────────────────────────
|
|
121
|
+
if (task) {
|
|
122
|
+
const agentTask = createTask(task, provider, maxSteps);
|
|
123
|
+
await runAgentLoop(task, [], modelConfig, providerLabel, maxSteps, provider, agentTask);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// ── 交互式模式 ──────────────────────────────────────────────
|
|
127
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
128
|
+
rl.on('close', () => {
|
|
129
|
+
console.log('\n' + theme.dim('Agent 已退出 👋'));
|
|
130
|
+
process.exit(0);
|
|
131
|
+
});
|
|
132
|
+
const session = new Session(AGENT_SYSTEM_PROMPT, provider);
|
|
133
|
+
const prompt = () => {
|
|
134
|
+
rl.question(chalk.cyan.bold('\n你: '), async (input) => {
|
|
135
|
+
const trimmed = input.trim();
|
|
136
|
+
if (!trimmed) {
|
|
137
|
+
prompt();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (trimmed === '/exit') {
|
|
141
|
+
rl.close();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (trimmed === '/clear') {
|
|
145
|
+
session.clear();
|
|
146
|
+
console.clear();
|
|
147
|
+
console.log(theme.success('对话已清空'));
|
|
148
|
+
prompt();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (trimmed === '/tools') {
|
|
152
|
+
console.log('\n' + theme.bold('可用工具:'));
|
|
153
|
+
TOOL_DEFINITIONS.forEach((t) => {
|
|
154
|
+
console.log(` ${chalk.cyan(t.function.name.padEnd(20))} ${t.function.description}`);
|
|
155
|
+
});
|
|
156
|
+
console.log();
|
|
157
|
+
prompt();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (trimmed === '/tasks') {
|
|
161
|
+
agentListCommand();
|
|
162
|
+
prompt();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const agentTask = createTask(trimmed, provider, maxSteps);
|
|
166
|
+
session.addUser(trimmed);
|
|
167
|
+
agentTask.messages = session.getMessages();
|
|
168
|
+
await runAgentLoop(null, agentTask.messages, modelConfig, providerLabel, maxSteps, provider, agentTask);
|
|
169
|
+
// 同步最新消息回 session
|
|
170
|
+
agentTask.messages
|
|
171
|
+
.filter((m) => m.role !== 'system')
|
|
172
|
+
.forEach((m) => {
|
|
173
|
+
if (!session.messages.includes(m))
|
|
174
|
+
session.messages.push(m);
|
|
175
|
+
});
|
|
176
|
+
prompt();
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
prompt();
|
|
180
|
+
}
|
|
181
|
+
// ─── Agent 执行循环(核心)────────────────────────────────────
|
|
182
|
+
async function runAgentLoop(userInput, initialMessages, modelConfig, providerLabel, maxSteps, provider, task) {
|
|
183
|
+
if (!modelConfig)
|
|
184
|
+
return;
|
|
185
|
+
// 若有新的用户输入,追加到消息列表
|
|
186
|
+
const messages = [...initialMessages];
|
|
187
|
+
if (userInput) {
|
|
188
|
+
messages.push({ role: 'user', content: userInput });
|
|
189
|
+
task.messages = messages;
|
|
190
|
+
}
|
|
191
|
+
let step = task.step; // 从已有步骤继续
|
|
192
|
+
let consecutiveErrors = 0; // 连续错误计数(错误自愈用)
|
|
193
|
+
// 注册 SIGINT,中断时保存任务
|
|
194
|
+
const onSigint = () => {
|
|
195
|
+
task.status = 'paused';
|
|
196
|
+
task.step = step;
|
|
197
|
+
task.messages = messages;
|
|
198
|
+
saveTask(task);
|
|
199
|
+
console.log('\n\n' + theme.warning('⏸ 任务已暂停并保存'));
|
|
200
|
+
console.log(theme.dim(` 续接命令: xxcli agent --resume ${task.id}`));
|
|
201
|
+
process.exit(0);
|
|
202
|
+
};
|
|
203
|
+
process.once('SIGINT', onSigint);
|
|
204
|
+
while (step < maxSteps) {
|
|
205
|
+
step++;
|
|
206
|
+
task.step = step;
|
|
207
|
+
// ── 显示步骤标题 ──────────────────────────────────────────
|
|
208
|
+
process.stdout.write(`\n${chalk.magenta.bold(`[步骤 ${step}/${maxSteps}]`)} ${chalk.green.bold(`🤖 ${providerLabel}`)}: `);
|
|
209
|
+
try {
|
|
210
|
+
let hasStartedOutput = false;
|
|
211
|
+
const response = await chat(modelConfig, messages, {
|
|
212
|
+
stream: config.get('stream') !== false, // 默认开启流式
|
|
213
|
+
tools: TOOL_DEFINITIONS,
|
|
214
|
+
onChunk: (chunk) => {
|
|
215
|
+
// 流式输出 AI 的思考文字
|
|
216
|
+
if (!hasStartedOutput)
|
|
217
|
+
hasStartedOutput = true;
|
|
218
|
+
process.stdout.write(chunk);
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
if (!hasStartedOutput && response.content) {
|
|
222
|
+
process.stdout.write(response.content);
|
|
223
|
+
}
|
|
224
|
+
// ── 有工具调用 ──────────────────────────────────────────
|
|
225
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
226
|
+
if (response.content)
|
|
227
|
+
console.log(); // 思考文字后换行
|
|
228
|
+
// 将 AI 消息加入历史
|
|
229
|
+
messages.push({
|
|
230
|
+
role: 'assistant',
|
|
231
|
+
content: response.content ?? '',
|
|
232
|
+
tool_calls: response.toolCalls,
|
|
233
|
+
});
|
|
234
|
+
task.messages = messages;
|
|
235
|
+
saveTask(task); // 每步保存
|
|
236
|
+
let stepHadError = false;
|
|
237
|
+
for (const toolCall of response.toolCalls) {
|
|
238
|
+
const result = await executeTool(toolCall);
|
|
239
|
+
messages.push({
|
|
240
|
+
role: 'tool',
|
|
241
|
+
content: result.content,
|
|
242
|
+
tool_call_id: result.tool_call_id,
|
|
243
|
+
});
|
|
244
|
+
// 错误自愈检测
|
|
245
|
+
if (result.content.startsWith('工具执行出错') ||
|
|
246
|
+
result.content.startsWith('命令执行失败') ||
|
|
247
|
+
result.content.includes('失败')) {
|
|
248
|
+
consecutiveErrors++;
|
|
249
|
+
stepHadError = true;
|
|
250
|
+
task.errorCount++;
|
|
251
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
252
|
+
console.log('\n' + theme.error(`⚠️ 连续 ${consecutiveErrors} 次工具调用失败,Agent 停止。`));
|
|
253
|
+
console.log(theme.dim(` 任务已保存,可用 xxcli agent --resume ${task.id} 续接`));
|
|
254
|
+
task.status = 'failed';
|
|
255
|
+
task.step = step;
|
|
256
|
+
task.messages = messages;
|
|
257
|
+
saveTask(task);
|
|
258
|
+
process.removeListener('SIGINT', onSigint);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
console.log(theme.warning(`\n⚠️ 工具失败(连续第 ${consecutiveErrors} 次),AI 正在分析并重试...`));
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// 成功则重置连续错误计数
|
|
265
|
+
if (stepHadError === false)
|
|
266
|
+
consecutiveErrors = 0;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
task.messages = messages;
|
|
270
|
+
saveTask(task);
|
|
271
|
+
continue; // 继续循环让 AI 处理工具结果
|
|
272
|
+
}
|
|
273
|
+
// ── 纯文字回复(任务完成)───────────────────────────────
|
|
274
|
+
console.log('\n');
|
|
275
|
+
messages.push({ role: 'assistant', content: response.content ?? '' });
|
|
276
|
+
task.status = 'completed';
|
|
277
|
+
task.step = step;
|
|
278
|
+
task.messages = messages;
|
|
279
|
+
saveTask(task);
|
|
280
|
+
// 任务完成,不再需要 SIGINT 处理
|
|
281
|
+
process.removeListener('SIGINT', onSigint);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
console.log('\n' + theme.error(`请求失败: ${err.message}`));
|
|
286
|
+
consecutiveErrors++;
|
|
287
|
+
task.errorCount++;
|
|
288
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
289
|
+
task.status = 'failed';
|
|
290
|
+
task.step = step;
|
|
291
|
+
task.messages = messages;
|
|
292
|
+
saveTask(task);
|
|
293
|
+
console.log(theme.dim(`任务已保存,可用 xxcli agent --resume ${task.id} 续接`));
|
|
294
|
+
process.removeListener('SIGINT', onSigint);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
console.log(theme.warning(`正在重试... (${consecutiveErrors}/${MAX_CONSECUTIVE_ERRORS})`));
|
|
298
|
+
await new Promise((r) => setTimeout(r, 1500 * consecutiveErrors)); // 指数退避
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (step >= maxSteps) {
|
|
302
|
+
console.log(theme.warning(`\n已达到最大步骤数 (${maxSteps}),Agent 停止`));
|
|
303
|
+
console.log(theme.dim(`任务已保存,可用 xxcli agent --resume ${task.id} 继续`));
|
|
304
|
+
task.status = 'paused';
|
|
305
|
+
task.step = step;
|
|
306
|
+
task.messages = messages;
|
|
307
|
+
saveTask(task);
|
|
308
|
+
}
|
|
309
|
+
process.removeListener('SIGINT', onSigint);
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=agent.js.map
|