fr-cli 2.2.8__tar.gz → 2.3.0__tar.gz
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.
- {fr_cli-2.2.8/fr_cli.egg-info → fr_cli-2.3.0}/PKG-INFO +70 -2
- {fr_cli-2.2.8 → fr_cli-2.3.0}/README.md +68 -1
- fr_cli-2.3.0/fr_cli/agent/acp.py +195 -0
- fr_cli-2.3.0/fr_cli/agent/context_files.py +159 -0
- fr_cli-2.3.0/fr_cli/agent/gateway.py +233 -0
- fr_cli-2.3.0/fr_cli/agent/hermes.py +331 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/master.py +9 -0
- fr_cli-2.3.0/fr_cli/agent/personality.py +236 -0
- fr_cli-2.3.0/fr_cli/agent/plugin_system.py +195 -0
- fr_cli-2.3.0/fr_cli/agent/shell_mode.py +181 -0
- fr_cli-2.3.0/fr_cli/agent/skills.py +226 -0
- fr_cli-2.3.0/fr_cli/core/llm.py +266 -0
- fr_cli-2.3.0/fr_cli/core/model_factory.py +173 -0
- fr_cli-2.3.0/fr_cli/weapon/mcp.py +186 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0/fr_cli.egg-info}/PKG-INFO +70 -2
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli.egg-info/SOURCES.txt +9 -1
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli.egg-info/requires.txt +1 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/pyproject.toml +2 -1
- fr_cli-2.2.8/fr_cli/core/llm.py +0 -401
- fr_cli-2.2.8/fr_cli/weapon/mcp.py +0 -202
- fr_cli-2.2.8/tests/test_coding_helper.py +0 -205
- {fr_cli-2.2.8 → fr_cli-2.3.0}/LICENSE +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/MANIFEST.in +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/README.md +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/WEAPON.MD +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/addon/plugin.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/a2a.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/builtins/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/builtins/_utils.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/builtins/db.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/builtins/local.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/builtins/powerful_agent_template.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/builtins/rag.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/builtins/rag_watcher_daemon.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/builtins/remote.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/builtins/spider.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/client.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/coding_helper.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/executor.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/generator.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/image_and_parallel.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/manager.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/master_prompt.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/remote.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/server.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/workflow.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/agent/workflow_system.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/breakthrough/update.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/command/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/command/executor.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/command/registry.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/command/security.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/conf/config.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/conf/wizard.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/core/chat.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/core/core.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/core/intent.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/core/recommender.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/core/stream.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/core/sysmon.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/core/thinking.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/gatekeeper/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/gatekeeper/daemon.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/gatekeeper/manager.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/lang/i18n.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/main.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/memory/context.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/memory/history.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/memory/session.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/repl/__init__.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/repl/commands.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/security/security.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/ui/ui.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/weapon/cron.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/weapon/dataframe.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/weapon/disk.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/weapon/fs.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/weapon/launcher.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/weapon/loader.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/weapon/mail.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/weapon/vision.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli/weapon/web.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli.egg-info/dependency_links.txt +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli.egg-info/entry_points.txt +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/fr_cli.egg-info/top_level.txt +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/setup.cfg +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/tests/test_a2a_and_providers.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/tests/test_integration_real.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/tests/test_master_prompt_fix.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/tests/test_model_config.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/tests/test_new_features.py +0 -0
- {fr_cli-2.2.8 → fr_cli-2.3.0}/tests/test_new_providers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fr-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: 凡人打字机 - 支持多模型(Zhipu/DeepSeek/Kimi/Qwen/StepFun/MiniMax/Spark/Doubao/MiMo)的终极全能终端工具
|
|
5
5
|
Author: FANREN CLI Author
|
|
6
6
|
License-Expression: MIT
|
|
@@ -27,6 +27,7 @@ Requires-Dist: zhipuai>=2.0.0
|
|
|
27
27
|
Requires-Dist: openai>=1.0.0
|
|
28
28
|
Requires-Dist: requests>=2.28.0
|
|
29
29
|
Requires-Dist: mcp>=1.6.0
|
|
30
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
30
31
|
Provides-Extra: data
|
|
31
32
|
Requires-Dist: pandas>=1.5.0; extra == "data"
|
|
32
33
|
Requires-Dist: openpyxl>=3.0.0; extra == "data"
|
|
@@ -73,6 +74,8 @@ Dynamic: license-file
|
|
|
73
74
|
|
|
74
75
|
**支持 27+ 种 AI 模型(智谱/智谱Coding/DeepSeek/Kimi/Kimi K2/Kimi Code/StepFun/Step-3/MiniMax/M2.7/讯飞星火/豆包/MiMo/LongCat)的终极全能终端工具。**
|
|
75
76
|
|
|
77
|
+
**集成 Hermes Agent 核心功能:自我进化的 AI 助手。**
|
|
78
|
+
|
|
76
79
|
## ✨ 功能特性
|
|
77
80
|
|
|
78
81
|
### 🤖 多模型支持
|
|
@@ -234,7 +237,72 @@ python3 main.py
|
|
|
234
237
|
/security 查看安全设置
|
|
235
238
|
```
|
|
236
239
|
|
|
237
|
-
##
|
|
240
|
+
## � Hermes 核心功能
|
|
241
|
+
|
|
242
|
+
### 📋 任务管理
|
|
243
|
+
```
|
|
244
|
+
from fr_cli.agent.hermes import get_task_manager, get_analytics
|
|
245
|
+
|
|
246
|
+
# 创建任务
|
|
247
|
+
tm = get_task_manager()
|
|
248
|
+
task = tm.create_task("完成代码审查")
|
|
249
|
+
|
|
250
|
+
# 记录分析
|
|
251
|
+
an = get_analytics()
|
|
252
|
+
an.record_request("glm-4-flash", 1000, 0.01)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 🎯 目标追踪
|
|
256
|
+
```
|
|
257
|
+
from fr_cli.agent.hermes import GoalTracker
|
|
258
|
+
|
|
259
|
+
gt = GoalTracker()
|
|
260
|
+
gt.set_goal("完成项目", ["阶段1", "阶段2", "阶段3"])
|
|
261
|
+
gt.update_progress("已完成阶段1", 0.33)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### ⏰ 定时任务
|
|
265
|
+
```
|
|
266
|
+
from fr_cli.agent.hermes import get_cron_scheduler
|
|
267
|
+
|
|
268
|
+
cron = get_cron_scheduler()
|
|
269
|
+
cron.add_job("daily-report", "0 9 * * *", "生成日报")
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 🔌 插件系统
|
|
273
|
+
```
|
|
274
|
+
from fr_cli.agent.plugin_system import get_plugin_registry
|
|
275
|
+
|
|
276
|
+
registry = get_plugin_registry()
|
|
277
|
+
plugins = registry.list_all()
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### 🐚 Shell 模式 (Ctrl-X 切换)
|
|
281
|
+
```
|
|
282
|
+
Agent 模式: 输入消息与 AI 对话
|
|
283
|
+
Shell 模式: 直接执行 shell 命令
|
|
284
|
+
|
|
285
|
+
按 Ctrl-X 切换模式
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 🔗 ACP (Agent Client Protocol)
|
|
289
|
+
```bash
|
|
290
|
+
# 启动 ACP 服务
|
|
291
|
+
fr acp
|
|
292
|
+
|
|
293
|
+
# 配置到 Zed
|
|
294
|
+
# ~/.config/zed/settings.json
|
|
295
|
+
{
|
|
296
|
+
"agent_servers": {
|
|
297
|
+
"fr-cli": {
|
|
298
|
+
"command": "fr",
|
|
299
|
+
"args": ["acp"]
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## �📦 支持的模型提供商(25+)
|
|
238
306
|
|
|
239
307
|
| 道统 | 默认模型 | API 地址 |
|
|
240
308
|
|------|---------|----------|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**支持 27+ 种 AI 模型(智谱/智谱Coding/DeepSeek/Kimi/Kimi K2/Kimi Code/StepFun/Step-3/MiniMax/M2.7/讯飞星火/豆包/MiMo/LongCat)的终极全能终端工具。**
|
|
4
4
|
|
|
5
|
+
**集成 Hermes Agent 核心功能:自我进化的 AI 助手。**
|
|
6
|
+
|
|
5
7
|
## ✨ 功能特性
|
|
6
8
|
|
|
7
9
|
### 🤖 多模型支持
|
|
@@ -163,7 +165,72 @@ python3 main.py
|
|
|
163
165
|
/security 查看安全设置
|
|
164
166
|
```
|
|
165
167
|
|
|
166
|
-
##
|
|
168
|
+
## � Hermes 核心功能
|
|
169
|
+
|
|
170
|
+
### 📋 任务管理
|
|
171
|
+
```
|
|
172
|
+
from fr_cli.agent.hermes import get_task_manager, get_analytics
|
|
173
|
+
|
|
174
|
+
# 创建任务
|
|
175
|
+
tm = get_task_manager()
|
|
176
|
+
task = tm.create_task("完成代码审查")
|
|
177
|
+
|
|
178
|
+
# 记录分析
|
|
179
|
+
an = get_analytics()
|
|
180
|
+
an.record_request("glm-4-flash", 1000, 0.01)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 🎯 目标追踪
|
|
184
|
+
```
|
|
185
|
+
from fr_cli.agent.hermes import GoalTracker
|
|
186
|
+
|
|
187
|
+
gt = GoalTracker()
|
|
188
|
+
gt.set_goal("完成项目", ["阶段1", "阶段2", "阶段3"])
|
|
189
|
+
gt.update_progress("已完成阶段1", 0.33)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### ⏰ 定时任务
|
|
193
|
+
```
|
|
194
|
+
from fr_cli.agent.hermes import get_cron_scheduler
|
|
195
|
+
|
|
196
|
+
cron = get_cron_scheduler()
|
|
197
|
+
cron.add_job("daily-report", "0 9 * * *", "生成日报")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 🔌 插件系统
|
|
201
|
+
```
|
|
202
|
+
from fr_cli.agent.plugin_system import get_plugin_registry
|
|
203
|
+
|
|
204
|
+
registry = get_plugin_registry()
|
|
205
|
+
plugins = registry.list_all()
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 🐚 Shell 模式 (Ctrl-X 切换)
|
|
209
|
+
```
|
|
210
|
+
Agent 模式: 输入消息与 AI 对话
|
|
211
|
+
Shell 模式: 直接执行 shell 命令
|
|
212
|
+
|
|
213
|
+
按 Ctrl-X 切换模式
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 🔗 ACP (Agent Client Protocol)
|
|
217
|
+
```bash
|
|
218
|
+
# 启动 ACP 服务
|
|
219
|
+
fr acp
|
|
220
|
+
|
|
221
|
+
# 配置到 Zed
|
|
222
|
+
# ~/.config/zed/settings.json
|
|
223
|
+
{
|
|
224
|
+
"agent_servers": {
|
|
225
|
+
"fr-cli": {
|
|
226
|
+
"command": "fr",
|
|
227
|
+
"args": ["acp"]
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## �📦 支持的模型提供商(25+)
|
|
167
234
|
|
|
168
235
|
| 道统 | 默认模型 | API 地址 |
|
|
169
236
|
|------|---------|----------|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ACP (Agent Client Protocol) 支持
|
|
3
|
+
参考 kimi-cli 实现的 ACP 集成
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
import asyncio
|
|
9
|
+
import subprocess
|
|
10
|
+
from typing import Dict, Optional, Any
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ACPMessageType(Enum):
|
|
16
|
+
"""ACP 消息类型"""
|
|
17
|
+
REQUEST = "request"
|
|
18
|
+
RESPONSE = "response"
|
|
19
|
+
ERROR = "error"
|
|
20
|
+
STREAM = "stream"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ACPMessage:
|
|
25
|
+
"""ACP 消息"""
|
|
26
|
+
id: str
|
|
27
|
+
type: ACPMessageType
|
|
28
|
+
method: str
|
|
29
|
+
params: Dict = None
|
|
30
|
+
result: Any = None
|
|
31
|
+
error: str = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ACPServer:
|
|
35
|
+
"""ACP 服务器 - 作为 Agent 服务端"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, name: str = "fr-cli"):
|
|
38
|
+
self.name = name
|
|
39
|
+
self.running = False
|
|
40
|
+
self.port = 8765
|
|
41
|
+
|
|
42
|
+
async def start(self, port: int = 8765):
|
|
43
|
+
"""启动 ACP 服务器"""
|
|
44
|
+
self.port = port
|
|
45
|
+
self.running = True
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
import uvicorn
|
|
49
|
+
from fastapi import FastAPI
|
|
50
|
+
from fastapi.websockets import WebSocket
|
|
51
|
+
|
|
52
|
+
app = FastAPI()
|
|
53
|
+
|
|
54
|
+
@app.websocket("/acp")
|
|
55
|
+
async def acp_endpoint(websocket: WebSocket):
|
|
56
|
+
await websocket.accept()
|
|
57
|
+
try:
|
|
58
|
+
while self.running:
|
|
59
|
+
data = await websocket.receive_text()
|
|
60
|
+
message = json.loads(data)
|
|
61
|
+
|
|
62
|
+
# 处理 ACP 消息
|
|
63
|
+
response = await self.handle_message(message)
|
|
64
|
+
|
|
65
|
+
if response:
|
|
66
|
+
await websocket.send_text(json.dumps(response))
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"ACP 连接错误: {e}")
|
|
69
|
+
|
|
70
|
+
config = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="error")
|
|
71
|
+
server = uvicorn.Server(config)
|
|
72
|
+
await server.serve()
|
|
73
|
+
|
|
74
|
+
except ImportError:
|
|
75
|
+
print("警告: 需要安装 uvicorn 和 fastapi 来启用 ACP 服务")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f"ACP 服务器错误: {e}")
|
|
78
|
+
|
|
79
|
+
async def handle_message(self, message: Dict) -> Optional[Dict]:
|
|
80
|
+
"""处理 ACP 消息"""
|
|
81
|
+
msg_type = message.get("type")
|
|
82
|
+
|
|
83
|
+
if msg_type == "request":
|
|
84
|
+
method = message.get("method")
|
|
85
|
+
params = message.get("params", {})
|
|
86
|
+
|
|
87
|
+
if method == "chat":
|
|
88
|
+
result = await self.chat(params)
|
|
89
|
+
return {"id": message.get("id"), "type": "response", "result": result}
|
|
90
|
+
elif method == "tools":
|
|
91
|
+
return {"id": message.get("id"), "type": "response", "result": self.get_tools()}
|
|
92
|
+
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
async def chat(self, params: Dict) -> str:
|
|
96
|
+
"""处理聊天请求"""
|
|
97
|
+
from fr_cli.core.core import ask
|
|
98
|
+
message = params.get("message", "")
|
|
99
|
+
try:
|
|
100
|
+
result, _ = await asyncio.to_thread(ask, message)
|
|
101
|
+
return result
|
|
102
|
+
except:
|
|
103
|
+
return "Error processing request"
|
|
104
|
+
|
|
105
|
+
def get_tools(self) -> list:
|
|
106
|
+
"""获取可用工具列表"""
|
|
107
|
+
return [
|
|
108
|
+
{"name": "bash", "description": "Execute shell commands"},
|
|
109
|
+
{"name": "read", "description": "Read file contents"},
|
|
110
|
+
{"name": "write", "description": "Write file contents"},
|
|
111
|
+
{"name": "search", "description": "Search files"}
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
def stop(self):
|
|
115
|
+
"""停止 ACP 服务器"""
|
|
116
|
+
self.running = False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ACPClient:
|
|
120
|
+
"""ACP 客户端 - 连接其他 Agent"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, server_url: str):
|
|
123
|
+
self.server_url = server_url
|
|
124
|
+
self.ws = None
|
|
125
|
+
|
|
126
|
+
async def connect(self):
|
|
127
|
+
"""连接到 ACP 服务器"""
|
|
128
|
+
try:
|
|
129
|
+
import websockets
|
|
130
|
+
self.ws = await websockets.connect(f"{self.server_url}/acp")
|
|
131
|
+
return True
|
|
132
|
+
except Exception as e:
|
|
133
|
+
print(f"ACP 连接失败: {e}")
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
async def send_message(self, method: str, params: Dict = None) -> Optional[Dict]:
|
|
137
|
+
"""发送消息"""
|
|
138
|
+
if not self.ws:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
message = {
|
|
142
|
+
"id": str(asyncio.get_event_loop().time()),
|
|
143
|
+
"type": "request",
|
|
144
|
+
"method": method,
|
|
145
|
+
"params": params or {}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await self.ws.send(json.dumps(message))
|
|
149
|
+
response = await self.ws.recv()
|
|
150
|
+
return json.loads(response)
|
|
151
|
+
|
|
152
|
+
async def close(self):
|
|
153
|
+
"""关闭连接"""
|
|
154
|
+
if self.ws:
|
|
155
|
+
await self.ws.close()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def run_acp_mode():
|
|
159
|
+
"""运行 ACP 模式"""
|
|
160
|
+
print("""
|
|
161
|
+
╔════════════════════════════════════════════════════╗
|
|
162
|
+
║ ACP (Agent Client Protocol) 模式 ║
|
|
163
|
+
╚════════════════════════════════════════════════════╝
|
|
164
|
+
|
|
165
|
+
fr-cli 将作为 ACP Agent 服务端运行,
|
|
166
|
+
可与 Zed, VS Code 等 ACP 兼容编辑器集成。
|
|
167
|
+
|
|
168
|
+
启动命令示例配置:
|
|
169
|
+
|
|
170
|
+
Zed: ~/.config/zed/settings.json
|
|
171
|
+
{
|
|
172
|
+
"agent_servers": {
|
|
173
|
+
"fr-cli": {
|
|
174
|
+
"command": "fr",
|
|
175
|
+
"args": ["acp"],
|
|
176
|
+
"env": {}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
""")
|
|
181
|
+
server = ACPServer(name="fr-cli")
|
|
182
|
+
asyncio.run(server.start())
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# CLI 命令
|
|
186
|
+
def main():
|
|
187
|
+
import sys
|
|
188
|
+
if len(sys.argv) > 1 and sys.argv[1] == "acp":
|
|
189
|
+
run_acp_mode()
|
|
190
|
+
else:
|
|
191
|
+
print("使用: fr acp 启动 ACP 服务")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
main()
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Files 系统 - 参考 Hermes Agent 实现
|
|
3
|
+
项目上下文文件,塑造每次对话
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import glob
|
|
8
|
+
from typing import List, Dict, Optional
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ContextFile:
|
|
14
|
+
"""上下文文件"""
|
|
15
|
+
path: str
|
|
16
|
+
content: str = ""
|
|
17
|
+
size: int = 0
|
|
18
|
+
loaded: bool = False
|
|
19
|
+
|
|
20
|
+
def load_content(self):
|
|
21
|
+
"""加载文件内容"""
|
|
22
|
+
if not self.loaded and os.path.exists(self.path):
|
|
23
|
+
try:
|
|
24
|
+
with open(self.path, 'r', encoding='utf-8') as f:
|
|
25
|
+
self.content = f.read(100000) # 限制 100KB
|
|
26
|
+
self.size = len(self.content)
|
|
27
|
+
self.loaded = True
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ContextFilesManager:
|
|
33
|
+
"""上下文文件管理器"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, config_dir: str = None):
|
|
36
|
+
if config_dir is None:
|
|
37
|
+
config_dir = os.path.expanduser("~/.fr_cli")
|
|
38
|
+
self.config_dir = config_dir
|
|
39
|
+
self.context_file = os.path.join(config_dir, "context_files.json")
|
|
40
|
+
self.patterns: List[str] = []
|
|
41
|
+
self.exclude_patterns: List[str] = [".git/*", "node_modules/*", "__pycache__/*"]
|
|
42
|
+
self._load_config()
|
|
43
|
+
|
|
44
|
+
def _load_config(self):
|
|
45
|
+
"""加载配置"""
|
|
46
|
+
if os.path.exists(self.context_file):
|
|
47
|
+
try:
|
|
48
|
+
import json
|
|
49
|
+
with open(self.context_file, 'r', encoding='utf-8') as f:
|
|
50
|
+
data = json.load(f)
|
|
51
|
+
self.patterns = data.get("patterns", [])
|
|
52
|
+
self.exclude_patterns = data.get("exclude", self.exclude_patterns)
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def _save_config(self):
|
|
57
|
+
"""保存配置"""
|
|
58
|
+
import json
|
|
59
|
+
os.makedirs(os.path.dirname(self.context_file), exist_ok=True)
|
|
60
|
+
with open(self.context_file, 'w', encoding='utf-8') as f:
|
|
61
|
+
json.dump({
|
|
62
|
+
"patterns": self.patterns,
|
|
63
|
+
"exclude": self.exclude_patterns
|
|
64
|
+
}, f, indent=2)
|
|
65
|
+
|
|
66
|
+
def add_pattern(self, pattern: str):
|
|
67
|
+
"""添加匹配模式"""
|
|
68
|
+
if pattern not in self.patterns:
|
|
69
|
+
self.patterns.append(pattern)
|
|
70
|
+
self._save_config()
|
|
71
|
+
|
|
72
|
+
def remove_pattern(self, pattern: str):
|
|
73
|
+
"""移除匹配模式"""
|
|
74
|
+
if pattern in self.patterns:
|
|
75
|
+
self.patterns.remove(pattern)
|
|
76
|
+
self._save_config()
|
|
77
|
+
|
|
78
|
+
def add_exclude(self, pattern: str):
|
|
79
|
+
"""添加排除模式"""
|
|
80
|
+
if pattern not in self.exclude_patterns:
|
|
81
|
+
self.exclude_patterns.append(pattern)
|
|
82
|
+
self._save_config()
|
|
83
|
+
|
|
84
|
+
def get_matching_files(self, root_dir: str = ".") -> List[str]:
|
|
85
|
+
"""获取匹配的文件列表"""
|
|
86
|
+
files = set()
|
|
87
|
+
|
|
88
|
+
for pattern in self.patterns:
|
|
89
|
+
full_pattern = os.path.join(root_dir, pattern)
|
|
90
|
+
matched = glob.glob(full_pattern, recursive=True)
|
|
91
|
+
files.update(matched)
|
|
92
|
+
|
|
93
|
+
# 应用排除
|
|
94
|
+
excluded = set()
|
|
95
|
+
for exclude in self.exclude_patterns:
|
|
96
|
+
full_exclude = os.path.join(root_dir, exclude)
|
|
97
|
+
matched = glob.glob(full_exclude, recursive=True)
|
|
98
|
+
excluded.update(matched)
|
|
99
|
+
|
|
100
|
+
return [f for f in files if f not in excluded]
|
|
101
|
+
|
|
102
|
+
def load_context_files(self, root_dir: str = ".") -> List[ContextFile]:
|
|
103
|
+
"""加载所有上下文文件"""
|
|
104
|
+
files = self.get_matching_files(root_dir)
|
|
105
|
+
context_files = []
|
|
106
|
+
|
|
107
|
+
for filepath in files:
|
|
108
|
+
cf = ContextFile(path=filepath)
|
|
109
|
+
cf.load_content()
|
|
110
|
+
if cf.loaded:
|
|
111
|
+
context_files.append(cf)
|
|
112
|
+
|
|
113
|
+
return context_files
|
|
114
|
+
|
|
115
|
+
def build_context_prompt(self, root_dir: str = ".") -> str:
|
|
116
|
+
"""构建上下文提示"""
|
|
117
|
+
context_files = self.load_context_files(root_dir)
|
|
118
|
+
if not context_files:
|
|
119
|
+
return ""
|
|
120
|
+
|
|
121
|
+
parts = ["\n\n# 项目上下文\n"]
|
|
122
|
+
|
|
123
|
+
for cf in context_files:
|
|
124
|
+
parts.append(f"\n## {os.path.relpath(cf.path, root_dir)}")
|
|
125
|
+
parts.append(f"```\n{cf.content}\n```")
|
|
126
|
+
|
|
127
|
+
return "\n".join(parts)
|
|
128
|
+
|
|
129
|
+
def list_patterns(self) -> Dict:
|
|
130
|
+
"""列出所有模式"""
|
|
131
|
+
return {
|
|
132
|
+
"include": self.patterns,
|
|
133
|
+
"exclude": self.exclude_patterns
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def set_patterns(self, patterns: List[str]):
|
|
137
|
+
"""设置包含模式"""
|
|
138
|
+
self.patterns = patterns
|
|
139
|
+
self._save_config()
|
|
140
|
+
|
|
141
|
+
def set_exclude_patterns(self, patterns: List[str]):
|
|
142
|
+
"""设置排除模式"""
|
|
143
|
+
self.exclude_patterns = patterns
|
|
144
|
+
self._save_config()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# 全局实例
|
|
148
|
+
_context_manager = None
|
|
149
|
+
|
|
150
|
+
def get_context_manager() -> ContextFilesManager:
|
|
151
|
+
global _context_manager
|
|
152
|
+
if _context_manager is None:
|
|
153
|
+
_context_manager = ContextFilesManager()
|
|
154
|
+
return _context_manager
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def build_project_context(root_dir: str = ".") -> str:
|
|
158
|
+
"""构建项目上下文"""
|
|
159
|
+
return get_context_manager().build_context_prompt(root_dir)
|