fr-cli 2.2.9__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.
Files changed (91) hide show
  1. {fr_cli-2.2.9/fr_cli.egg-info → fr_cli-2.3.0}/PKG-INFO +1 -1
  2. fr_cli-2.3.0/fr_cli/agent/context_files.py +159 -0
  3. fr_cli-2.3.0/fr_cli/agent/gateway.py +233 -0
  4. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/hermes.py +10 -0
  5. fr_cli-2.3.0/fr_cli/agent/personality.py +236 -0
  6. fr_cli-2.3.0/fr_cli/agent/skills.py +226 -0
  7. {fr_cli-2.2.9 → fr_cli-2.3.0/fr_cli.egg-info}/PKG-INFO +1 -1
  8. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli.egg-info/SOURCES.txt +4 -0
  9. {fr_cli-2.2.9 → fr_cli-2.3.0}/pyproject.toml +1 -1
  10. {fr_cli-2.2.9 → fr_cli-2.3.0}/LICENSE +0 -0
  11. {fr_cli-2.2.9 → fr_cli-2.3.0}/MANIFEST.in +0 -0
  12. {fr_cli-2.2.9 → fr_cli-2.3.0}/README.md +0 -0
  13. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/README.md +0 -0
  14. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/WEAPON.MD +0 -0
  15. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/__init__.py +0 -0
  16. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/addon/plugin.py +0 -0
  17. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/__init__.py +0 -0
  18. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/a2a.py +0 -0
  19. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/acp.py +0 -0
  20. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/builtins/__init__.py +0 -0
  21. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/builtins/_utils.py +0 -0
  22. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/builtins/db.py +0 -0
  23. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/builtins/local.py +0 -0
  24. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/builtins/powerful_agent_template.py +0 -0
  25. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/builtins/rag.py +0 -0
  26. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/builtins/rag_watcher_daemon.py +0 -0
  27. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/builtins/remote.py +0 -0
  28. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/builtins/spider.py +0 -0
  29. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/client.py +0 -0
  30. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/coding_helper.py +0 -0
  31. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/executor.py +0 -0
  32. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/generator.py +0 -0
  33. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/image_and_parallel.py +0 -0
  34. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/manager.py +0 -0
  35. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/master.py +0 -0
  36. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/master_prompt.py +0 -0
  37. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/plugin_system.py +0 -0
  38. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/remote.py +0 -0
  39. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/server.py +0 -0
  40. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/shell_mode.py +0 -0
  41. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/workflow.py +0 -0
  42. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/agent/workflow_system.py +0 -0
  43. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/breakthrough/update.py +0 -0
  44. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/command/__init__.py +0 -0
  45. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/command/executor.py +0 -0
  46. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/command/registry.py +0 -0
  47. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/command/security.py +0 -0
  48. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/conf/config.py +0 -0
  49. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/conf/wizard.py +0 -0
  50. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/core/chat.py +0 -0
  51. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/core/core.py +0 -0
  52. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/core/intent.py +0 -0
  53. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/core/llm.py +0 -0
  54. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/core/model_factory.py +0 -0
  55. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/core/recommender.py +0 -0
  56. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/core/stream.py +0 -0
  57. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/core/sysmon.py +0 -0
  58. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/core/thinking.py +0 -0
  59. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/gatekeeper/__init__.py +0 -0
  60. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/gatekeeper/daemon.py +0 -0
  61. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/gatekeeper/manager.py +0 -0
  62. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/lang/i18n.py +0 -0
  63. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/main.py +0 -0
  64. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/memory/context.py +0 -0
  65. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/memory/history.py +0 -0
  66. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/memory/session.py +0 -0
  67. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/repl/__init__.py +0 -0
  68. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/repl/commands.py +0 -0
  69. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/security/security.py +0 -0
  70. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/ui/ui.py +0 -0
  71. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/cron.py +0 -0
  72. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/dataframe.py +0 -0
  73. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/disk.py +0 -0
  74. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/fs.py +0 -0
  75. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/launcher.py +0 -0
  76. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/loader.py +0 -0
  77. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/mail.py +0 -0
  78. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/mcp.py +0 -0
  79. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/vision.py +0 -0
  80. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli/weapon/web.py +0 -0
  81. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli.egg-info/dependency_links.txt +0 -0
  82. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli.egg-info/entry_points.txt +0 -0
  83. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli.egg-info/requires.txt +0 -0
  84. {fr_cli-2.2.9 → fr_cli-2.3.0}/fr_cli.egg-info/top_level.txt +0 -0
  85. {fr_cli-2.2.9 → fr_cli-2.3.0}/setup.cfg +0 -0
  86. {fr_cli-2.2.9 → fr_cli-2.3.0}/tests/test_a2a_and_providers.py +0 -0
  87. {fr_cli-2.2.9 → fr_cli-2.3.0}/tests/test_integration_real.py +0 -0
  88. {fr_cli-2.2.9 → fr_cli-2.3.0}/tests/test_master_prompt_fix.py +0 -0
  89. {fr_cli-2.2.9 → fr_cli-2.3.0}/tests/test_model_config.py +0 -0
  90. {fr_cli-2.2.9 → fr_cli-2.3.0}/tests/test_new_features.py +0 -0
  91. {fr_cli-2.2.9 → 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.2.9
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
@@ -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)
@@ -0,0 +1,233 @@
1
+ """
2
+ Gateway 功能 - 多平台消息网关
3
+ 参考 Hermes Agent 实现
4
+ 支持 Telegram、Discord、Slack 等平台
5
+ """
6
+
7
+ import os
8
+ import asyncio
9
+ import json
10
+ from typing import Dict, Optional
11
+ from dataclasses import dataclass
12
+ from enum import Enum
13
+
14
+
15
+ class Platform(Enum):
16
+ """支持的平台"""
17
+ TELEGRAM = "telegram"
18
+ DISCORD = "discord"
19
+ SLACK = "slack"
20
+ CLI = "cli"
21
+
22
+
23
+ @dataclass
24
+ class GatewayConfig:
25
+ """网关配置"""
26
+ platform: Platform
27
+ enabled: bool = False
28
+ bot_token: Optional[str] = None
29
+ api_key: Optional[str] = None
30
+ chat_id: Optional[str] = None
31
+
32
+
33
+ class GatewayManager:
34
+ """网关管理器"""
35
+
36
+ def __init__(self):
37
+ self.config_file = os.path.expanduser("~/.fr_cli/gateway.json")
38
+ self.platforms: Dict[str, GatewayConfig] = {}
39
+ self._load_config()
40
+ self._running = False
41
+
42
+ def _load_config(self):
43
+ """加载配置"""
44
+ if os.path.exists(self.config_file):
45
+ try:
46
+ with open(self.config_file) as f:
47
+ data = json.load(f)
48
+ for name, cfg in data.items():
49
+ self.platforms[name] = GatewayConfig(
50
+ platform=Platform(cfg.get("platform", "cli")),
51
+ enabled=cfg.get("enabled", False),
52
+ bot_token=cfg.get("bot_token"),
53
+ api_key=cfg.get("api_key"),
54
+ chat_id=cfg.get("chat_id")
55
+ )
56
+ except Exception:
57
+ pass
58
+
59
+ def _save_config(self):
60
+ """保存配置"""
61
+ os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
62
+ data = {}
63
+ for name, cfg in self.platforms.items():
64
+ data[name] = {
65
+ "platform": cfg.platform.value,
66
+ "enabled": cfg.enabled,
67
+ "bot_token": cfg.bot_token,
68
+ "api_key": cfg.api_key,
69
+ "chat_id": cfg.chat_id
70
+ }
71
+ with open(self.config_file, 'w') as f:
72
+ json.dump(data, f, indent=2)
73
+
74
+ def configure_telegram(self, bot_token: str, allowed_chat_ids: list = None):
75
+ """配置 Telegram"""
76
+ self.platforms["telegram"] = GatewayConfig(
77
+ platform=Platform.TELEGRAM,
78
+ enabled=True,
79
+ bot_token=bot_token,
80
+ chat_id=json.dumps(allowed_chat_ids or [])
81
+ )
82
+ self._save_config()
83
+ print("✅ Telegram 配置完成")
84
+ print(" 使用 /gateway start telegram 启动")
85
+
86
+ def configure_discord(self, bot_token: str, guild_id: str = None):
87
+ """配置 Discord"""
88
+ self.platforms["discord"] = GatewayConfig(
89
+ platform=Platform.DISCORD,
90
+ enabled=True,
91
+ bot_token=bot_token,
92
+ chat_id=guild_id
93
+ )
94
+ self._save_config()
95
+ print("✅ Discord 配置完成")
96
+ print(" 使用 /gateway start discord 启动")
97
+
98
+ def list_platforms(self):
99
+ """列出已配置的平台"""
100
+ print("\n📱 已配置的消息平台:")
101
+ if not self.platforms:
102
+ print(" (无)")
103
+ return
104
+
105
+ for name, cfg in self.platforms.items():
106
+ status = "✅ 启用" if cfg.enabled else "❌ 禁用"
107
+ print(f" {name}: {status}")
108
+ print()
109
+
110
+ async def start_telegram(self):
111
+ """启动 Telegram Bot"""
112
+ cfg = self.platforms.get("telegram")
113
+ if not cfg or not cfg.enabled:
114
+ print("❌ Telegram 未配置")
115
+ return
116
+
117
+ try:
118
+ from telegram import Bot
119
+ from telegram.ext import Application, CommandHandler, MessageHandler, filters
120
+
121
+ bot = Bot(token=cfg.bot_token)
122
+
123
+ async def handle_message(update, context):
124
+ from fr_cli.core.core import ask
125
+ message = update.message.text
126
+ result, _ = await asyncio.to_thread(ask, message)
127
+ await update.message.reply_text(result)
128
+
129
+ app = Application.builder().token(cfg.bot_token).build()
130
+ app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
131
+ print("🤖 Telegram Bot 已启动!")
132
+ await app.run_polling()
133
+
134
+ except ImportError:
135
+ print("❌ 需要安装 python-telegram-bot: pip install python-telegram-bot")
136
+ except Exception as e:
137
+ print(f"❌ Telegram 启动失败: {e}")
138
+
139
+ async def start_discord(self):
140
+ """启动 Discord Bot"""
141
+ cfg = self.platforms.get("discord")
142
+ if not cfg or not cfg.enabled:
143
+ print("❌ Discord 未配置")
144
+ return
145
+
146
+ try:
147
+ import discord
148
+
149
+ intents = discord.Intents.default()
150
+ intents.message_content = True
151
+ client = discord.Client(intents=intents)
152
+
153
+ @client.event
154
+ async def on_message(message):
155
+ if message.author == client.user:
156
+ return
157
+
158
+ from fr_cli.core.core import ask
159
+ result, _ = await asyncio.to_thread(ask, message.content)
160
+ await message.channel.send(result)
161
+
162
+ print("📢 Discord Bot 已启动!")
163
+ await client.start(cfg.bot_token)
164
+
165
+ except ImportError:
166
+ print("❌ 需要安装 discord.py: pip install discord.py")
167
+ except Exception as e:
168
+ print(f"❌ Discord 启动失败: {e}")
169
+
170
+ async def start_all(self):
171
+ """启动所有已配置的平台"""
172
+ tasks = []
173
+ if "telegram" in self.platforms and self.platforms["telegram"].enabled:
174
+ tasks.append(self.start_telegram())
175
+ if "discord" in self.platforms and self.platforms["discord"].enabled:
176
+ tasks.append(self.start_discord())
177
+
178
+ if tasks:
179
+ await asyncio.gather(*tasks)
180
+ else:
181
+ print("❌ 没有已启用的平台")
182
+
183
+ def stop(self):
184
+ """停止网关"""
185
+ self._running = False
186
+ print("🛑 网关已停止")
187
+
188
+
189
+ def run_gateway(platform: str = None):
190
+ """运行网关"""
191
+ manager = GatewayManager()
192
+
193
+ if platform:
194
+ if platform == "telegram":
195
+ asyncio.run(manager.start_telegram())
196
+ elif platform == "discord":
197
+ asyncio.run(manager.start_discord())
198
+ else:
199
+ print(f"❌ 不支持的平台: {platform}")
200
+ else:
201
+ asyncio.run(manager.start_all())
202
+
203
+
204
+ # CLI 命令
205
+ if __name__ == "__main__":
206
+ import sys
207
+ if len(sys.argv) > 1:
208
+ if sys.argv[1] == "start":
209
+ platform = sys.argv[2] if len(sys.argv) > 2 else None
210
+ run_gateway(platform)
211
+ elif sys.argv[1] == "setup":
212
+ manager = GatewayManager()
213
+ manager.list_platforms()
214
+ else:
215
+ print("用法: fr gateway [start|setup] [platform]")
216
+ else:
217
+ print("""
218
+ ╔════════════════════════════════════════════════════╗
219
+ ║ fr-cli Gateway 消息网关 ║
220
+ ╚════════════════════════════════════════════════════╝
221
+
222
+ 用法:
223
+ fr gateway setup 查看已配置的平台
224
+ fr gateway start telegram 启动 Telegram Bot
225
+ fr gateway start discord 启动 Discord Bot
226
+ fr gateway start 启动所有平台
227
+
228
+ 配置示例:
229
+ from fr_cli.agent.gateway import GatewayManager
230
+ gm = GatewayManager()
231
+ gm.configure_telegram("your-bot-token")
232
+ gm.configure_discord("your-bot-token")
233
+ """)
@@ -1,6 +1,16 @@
1
1
  """
2
2
  Hermes 功能模块
3
3
  参考 NousResearch/hermes-agent 实现核心功能
4
+
5
+ 整合:
6
+ - TaskManager: 任务管理
7
+ - Analytics: 分析统计
8
+ - GoalTracker: 目标追踪
9
+ - ConfigManager: 配置管理
10
+ - CronScheduler: 定时任务
11
+ - SkillManager: 技能系统 (见 skills.py)
12
+ - PersonalityManager: 个性系统 (见 personality.py)
13
+ - ContextFilesManager: 上下文文件 (见 context_files.py)
4
14
  """
5
15
 
6
16
  import os
@@ -0,0 +1,236 @@
1
+ """
2
+ Personality 系统 - 参考 Hermes Agent 实现
3
+ 支持多种 AI 个性切换
4
+ """
5
+
6
+ import os
7
+ import json
8
+ from typing import Dict, Optional
9
+ from dataclasses import dataclass
10
+
11
+
12
+ @dataclass
13
+ class Personality:
14
+ """AI 个性"""
15
+ name: str
16
+ description: str
17
+ identity: str
18
+ system_prompt: str
19
+ examples: list = None
20
+
21
+ def __post_init__(self):
22
+ if self.examples is None:
23
+ self.examples = []
24
+
25
+ def to_dict(self) -> Dict:
26
+ return {
27
+ "name": self.name,
28
+ "description": self.description,
29
+ "identity": self.identity,
30
+ "system_prompt": self.system_prompt,
31
+ "examples": self.examples
32
+ }
33
+
34
+ @classmethod
35
+ def from_dict(cls, data: Dict) -> 'Personality':
36
+ return cls(**data)
37
+
38
+
39
+ class PersonalityManager:
40
+ """个性管理器"""
41
+
42
+ def __init__(self, config_dir: str = None):
43
+ if config_dir is None:
44
+ config_dir = os.path.expanduser("~/.fr_cli")
45
+ self.config_dir = config_dir
46
+ self.personalities: Dict[str, Personality] = {}
47
+ self.current_personality: Optional[Personality] = None
48
+ self._load_personalities()
49
+
50
+ def _get_personalities_file(self) -> str:
51
+ return os.path.join(self.config_dir, "personalities.json")
52
+
53
+ def _load_personalities(self):
54
+ """加载内置和用户个性"""
55
+ self._register_builtin_personalities()
56
+
57
+ config_file = self._get_personalities_file()
58
+ if os.path.exists(config_file):
59
+ try:
60
+ with open(config_file, 'r', encoding='utf-8') as f:
61
+ data = json.load(f)
62
+ for name, cfg in data.items():
63
+ self.personalities[name] = Personality.from_dict(cfg)
64
+ except Exception:
65
+ pass
66
+
67
+ if not self.current_personality and self.personalities:
68
+ self.current_personality = list(self.personalities.values())[0]
69
+
70
+ def _register_builtin_personalities(self):
71
+ """注册内置个性"""
72
+ self.personalities = {
73
+ "default": Personality(
74
+ name="default",
75
+ description="默认助手",
76
+ identity="你是一个有帮助的 AI 助手。",
77
+ system_prompt="你是一个有帮助的 AI 助手,擅长回答各种问题。"
78
+ ),
79
+ "coder": Personality(
80
+ name="coder",
81
+ description="编程专家",
82
+ identity="你是一个专业的程序员。",
83
+ system_prompt="""你是一个专业的程序员,擅长:
84
+ - Python, JavaScript, TypeScript, Go, Rust 等语言
85
+ - 代码审查和优化
86
+ - 调试和错误修复
87
+ - 设计模式和架构建议
88
+ - 编写测试和文档""",
89
+ examples=["帮我写一个排序算法", "这段代码有什么问题?"]
90
+ ),
91
+ "reviewer": Personality(
92
+ name="reviewer",
93
+ description="代码审查员",
94
+ identity="你是一个严格的代码审查员。",
95
+ system_prompt="""你是一个严格的代码审查员,关注:
96
+ - 代码质量和可读性
97
+ - 性能和效率
98
+ - 安全漏洞
99
+ - 最佳实践
100
+ - 潜在的 bug
101
+
102
+ 给出具体、可操作的改进建议。""",
103
+ examples=["审查这段代码", "有什么安全问题?"]
104
+ ),
105
+ "teacher": Personality(
106
+ name="teacher",
107
+ description="编程教师",
108
+ identity="你是一个耐心的编程教师。",
109
+ system_prompt="""你是一个耐心的编程教师,特点:
110
+ - 解释清晰易懂
111
+ - 提供例子和类比
112
+ - 循序渐进
113
+ - 鼓励学习者
114
+ - 耐心回答问题""",
115
+ examples=["解释什么是闭包", "面向对象是什么?"]
116
+ ),
117
+ "expert": Personality(
118
+ name="expert",
119
+ description="领域专家",
120
+ identity="你是一个各个领域的专家。",
121
+ system_prompt="""你是一个广博的领域专家,可以提供:
122
+ - 深入的技术分析
123
+ - 行业洞察
124
+ - 战略建议
125
+ - 最佳实践
126
+ - 前沿趋势""",
127
+ examples=["分析这个架构的优缺点", "给我一些战略建议"]
128
+ ),
129
+ "creative": Personality(
130
+ name="creative",
131
+ description="创意助手",
132
+ identity="你是一个富有创意的助手。",
133
+ system_prompt="""你是一个富有创意的助手,擅长:
134
+ - 头脑风暴
135
+ - 生成创意点子
136
+ - 写作和内容创作
137
+ - 解决棘手问题
138
+ - 提供新颖的解决方案""",
139
+ examples=["给我一些创意点子", "怎么让这个产品更有趣?"]
140
+ )
141
+ }
142
+
143
+ def _save_personalities(self):
144
+ """保存用户个性"""
145
+ config_file = self._get_personalities_file()
146
+ os.makedirs(os.path.dirname(config_file), exist_ok=True)
147
+ data = {
148
+ name: p.to_dict()
149
+ for name, p in self.personalities.items()
150
+ if name not in ["default", "coder", "reviewer", "teacher", "expert", "creative"]
151
+ }
152
+ if data:
153
+ with open(config_file, 'w', encoding='utf-8') as f:
154
+ json.dump(data, f, ensure_ascii=False, indent=2)
155
+
156
+ def set_personality(self, name: str) -> bool:
157
+ """设置当前个性"""
158
+ if name in self.personalities:
159
+ self.current_personality = self.personalities[name]
160
+ return True
161
+ return False
162
+
163
+ def get_current(self) -> Optional[Personality]:
164
+ """获取当前个性"""
165
+ return self.current_personality
166
+
167
+ def add_personality(self, personality: Personality):
168
+ """添加用户个性"""
169
+ self.personalities[personality.name] = personality
170
+ self._save_personalities()
171
+
172
+ def remove_personality(self, name: str) -> bool:
173
+ """删除用户个性"""
174
+ if name in ["default", "coder", "reviewer", "teacher", "expert", "creative"]:
175
+ return False
176
+ if name in self.personalities:
177
+ del self.personalities[name]
178
+ if self.current_personality and self.current_personality.name == name:
179
+ self.current_personality = self.personalities.get("default")
180
+ self._save_personalities()
181
+ return True
182
+ return False
183
+
184
+ def list_personalities(self) -> list:
185
+ """列出所有个性"""
186
+ return [
187
+ {
188
+ "name": p.name,
189
+ "description": p.description,
190
+ "current": p == self.current_personality
191
+ }
192
+ for p in self.personalities.values()
193
+ ]
194
+
195
+ def build_system_prompt(self) -> str:
196
+ """构建当前个性的系统提示"""
197
+ if not self.current_personality:
198
+ return ""
199
+
200
+ p = self.current_personality
201
+ parts = [
202
+ f"# 身份: {p.identity}",
203
+ f"\n{p.system_prompt}"
204
+ ]
205
+
206
+ if p.examples:
207
+ parts.append("\n## 示例对话:")
208
+ for ex in p.examples:
209
+ parts.append(f"- {ex}")
210
+
211
+ return "\n".join(parts)
212
+
213
+
214
+ # 全局实例
215
+ _personality_manager = None
216
+
217
+ def get_personality_manager() -> PersonalityManager:
218
+ global _personality_manager
219
+ if _personality_manager is None:
220
+ _personality_manager = PersonalityManager()
221
+ return _personality_manager
222
+
223
+
224
+ def set_personality(name: str) -> bool:
225
+ """设置当前个性"""
226
+ return get_personality_manager().set_personality(name)
227
+
228
+
229
+ def get_current_personality() -> Optional[Personality]:
230
+ """获取当前个性"""
231
+ return get_personality_manager().get_current()
232
+
233
+
234
+ def list_personalities() -> list:
235
+ """列出所有个性"""
236
+ return get_personality_manager().list_personalities()
@@ -0,0 +1,226 @@
1
+ """
2
+ Skills 系统 - 参考 Hermes Agent 实现
3
+ 技能是 AI 自我学习的核心机制
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import time
9
+ from typing import Dict, List, Optional, Callable
10
+ from dataclasses import dataclass, field
11
+
12
+
13
+ @dataclass
14
+ class Skill:
15
+ """技能"""
16
+ name: str
17
+ description: str
18
+ content: str
19
+ author: str = "system"
20
+ created_at: float = field(default_factory=time.time)
21
+ updated_at: float = field(default_factory=time.time)
22
+ usage_count: int = 0
23
+ success_count: int = 0
24
+ tags: List[str] = field(default_factory=list)
25
+ enabled: bool = True
26
+
27
+ def to_dict(self) -> Dict:
28
+ return {
29
+ "name": self.name,
30
+ "description": self.description,
31
+ "content": self.content,
32
+ "author": self.author,
33
+ "created_at": self.created_at,
34
+ "updated_at": self.updated_at,
35
+ "usage_count": self.usage_count,
36
+ "success_count": self.success_count,
37
+ "tags": self.tags,
38
+ "enabled": self.enabled
39
+ }
40
+
41
+ @classmethod
42
+ def from_dict(cls, data: Dict) -> 'Skill':
43
+ return cls(
44
+ name=data["name"],
45
+ description=data.get("description", ""),
46
+ content=data["content"],
47
+ author=data.get("author", "system"),
48
+ created_at=data.get("created_at", time.time()),
49
+ updated_at=data.get("updated_at", time.time()),
50
+ usage_count=data.get("usage_count", 0),
51
+ success_count=data.get("success_count", 0),
52
+ tags=data.get("tags", []),
53
+ enabled=data.get("enabled", True)
54
+ )
55
+
56
+
57
+ class SkillManager:
58
+ """技能管理器"""
59
+
60
+ def __init__(self, skills_dir: str = None):
61
+ if skills_dir is None:
62
+ skills_dir = os.path.expanduser("~/.fr_cli/skills")
63
+ self.skills_dir = skills_dir
64
+ self.skills: Dict[str, Skill] = {}
65
+ self._load_skills()
66
+ self._ensure_dir()
67
+
68
+ def _ensure_dir(self):
69
+ """确保目录存在"""
70
+ os.makedirs(self.skills_dir, exist_ok=True)
71
+
72
+ def _load_skills(self):
73
+ """加载所有技能"""
74
+ if not os.path.exists(self.skills_dir):
75
+ return
76
+
77
+ for filename in os.listdir(self.skills_dir):
78
+ if filename.endswith('.json'):
79
+ skill_path = os.path.join(self.skills_dir, filename)
80
+ try:
81
+ with open(skill_path, 'r', encoding='utf-8') as f:
82
+ data = json.load(f)
83
+ skill = Skill.from_dict(data)
84
+ self.skills[skill.name] = skill
85
+ except Exception:
86
+ pass
87
+
88
+ def _save_skill(self, skill: Skill):
89
+ """保存技能"""
90
+ self._ensure_dir()
91
+ filename = f"{skill.name}.json".replace("/", "_")
92
+ filepath = os.path.join(self.skills_dir, filename)
93
+ with open(filepath, 'w', encoding='utf-8') as f:
94
+ json.dump(skill.to_dict(), f, ensure_ascii=False, indent=2)
95
+
96
+ def create_skill(self, name: str, description: str, content: str,
97
+ author: str = "user", tags: List[str] = None) -> Skill:
98
+ """创建技能"""
99
+ skill = Skill(
100
+ name=name,
101
+ description=description,
102
+ content=content,
103
+ author=author,
104
+ tags=tags or []
105
+ )
106
+ self.skills[name] = skill
107
+ self._save_skill(skill)
108
+ return skill
109
+
110
+ def get_skill(self, name: str) -> Optional[Skill]:
111
+ """获取技能"""
112
+ return self.skills.get(name)
113
+
114
+ def update_skill(self, name: str, **kwargs):
115
+ """更新技能"""
116
+ if name in self.skills:
117
+ skill = self.skills[name]
118
+ for key, value in kwargs.items():
119
+ if hasattr(skill, key):
120
+ setattr(skill, key, value)
121
+ skill.updated_at = time.time()
122
+ self._save_skill(skill)
123
+ return skill
124
+ return None
125
+
126
+ def delete_skill(self, name: str) -> bool:
127
+ """删除技能"""
128
+ if name in self.skills:
129
+ del self.skills[name]
130
+ filename = f"{name}.json".replace("/", "_")
131
+ filepath = os.path.join(self.skills_dir, filename)
132
+ if os.path.exists(filepath):
133
+ os.remove(filepath)
134
+ return True
135
+ return False
136
+
137
+ def list_skills(self, enabled_only: bool = True) -> List[Skill]:
138
+ """列出技能"""
139
+ skills = list(self.skills.values())
140
+ if enabled_only:
141
+ skills = [s for s in skills if s.enabled]
142
+ return skills
143
+
144
+ def search_skills(self, query: str) -> List[Skill]:
145
+ """搜索技能"""
146
+ query_lower = query.lower()
147
+ results = []
148
+ for skill in self.skills.values():
149
+ if (query_lower in skill.name.lower() or
150
+ query_lower in skill.description.lower() or
151
+ any(query_lower in tag.lower() for tag in skill.tags)):
152
+ results.append(skill)
153
+ return results
154
+
155
+ def record_usage(self, name: str, success: bool = True):
156
+ """记录技能使用"""
157
+ if name in self.skills:
158
+ skill = self.skills[name]
159
+ skill.usage_count += 1
160
+ if success:
161
+ skill.success_count += 1
162
+ self._save_skill(skill)
163
+
164
+ def build_system_prompt(self) -> str:
165
+ """构建技能系统提示"""
166
+ skills = self.list_skills()
167
+ if not skills:
168
+ return ""
169
+
170
+ lines = ["\n\n# 可用技能"]
171
+ for skill in skills:
172
+ lines.append(f"\n## {skill.name}")
173
+ lines.append(f"描述: {skill.description}")
174
+ lines.append(f"内容:\n{skill.content}")
175
+
176
+ return "\n".join(lines)
177
+
178
+ def learn_from_task(self, task: str, result: str, context: str = ""):
179
+ """从任务中学习并创建技能"""
180
+ skill = Skill(
181
+ name=f"skill_{int(time.time())}",
182
+ description=f"从任务 '{task[:50]}...' 学习",
183
+ content=f"任务: {task}\n\n结果: {result}\n\n上下文: {context}",
184
+ author="auto"
185
+ )
186
+ self.skills[skill.name] = skill
187
+ self._save_skill(skill)
188
+ return skill
189
+
190
+ def get_stats(self) -> Dict:
191
+ """获取统计"""
192
+ skills = list(self.skills.values())
193
+ return {
194
+ "total": len(skills),
195
+ "enabled": sum(1 for s in skills if s.enabled),
196
+ "total_usage": sum(s.usage_count for s in skills),
197
+ "success_rate": (
198
+ sum(s.success_count for s in skills) /
199
+ max(1, sum(s.usage_count for s in skills))
200
+ )
201
+ }
202
+
203
+
204
+ # 全局实例
205
+ _skill_manager = None
206
+
207
+ def get_skill_manager() -> SkillManager:
208
+ global _skill_manager
209
+ if _skill_manager is None:
210
+ _skill_manager = SkillManager()
211
+ return _skill_manager
212
+
213
+
214
+ def create_skill(name: str, description: str, content: str) -> Skill:
215
+ """创建技能快捷方法"""
216
+ return get_skill_manager().create_skill(name, description, content)
217
+
218
+
219
+ def list_skills() -> List[Skill]:
220
+ """列出所有技能"""
221
+ return get_skill_manager().list_skills()
222
+
223
+
224
+ def search_skills(query: str) -> List[Skill]:
225
+ """搜索技能"""
226
+ return get_skill_manager().search_skills(query)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fr-cli
3
- Version: 2.2.9
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
@@ -18,17 +18,21 @@ fr_cli/agent/a2a.py
18
18
  fr_cli/agent/acp.py
19
19
  fr_cli/agent/client.py
20
20
  fr_cli/agent/coding_helper.py
21
+ fr_cli/agent/context_files.py
21
22
  fr_cli/agent/executor.py
23
+ fr_cli/agent/gateway.py
22
24
  fr_cli/agent/generator.py
23
25
  fr_cli/agent/hermes.py
24
26
  fr_cli/agent/image_and_parallel.py
25
27
  fr_cli/agent/manager.py
26
28
  fr_cli/agent/master.py
27
29
  fr_cli/agent/master_prompt.py
30
+ fr_cli/agent/personality.py
28
31
  fr_cli/agent/plugin_system.py
29
32
  fr_cli/agent/remote.py
30
33
  fr_cli/agent/server.py
31
34
  fr_cli/agent/shell_mode.py
35
+ fr_cli/agent/skills.py
32
36
  fr_cli/agent/workflow.py
33
37
  fr_cli/agent/workflow_system.py
34
38
  fr_cli/agent/builtins/__init__.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fr-cli"
7
- version = "2.2.9"
7
+ version = "2.3.0"
8
8
  description = "凡人打字机 - 支持多模型(Zhipu/DeepSeek/Kimi/Qwen/StepFun/MiniMax/Spark/Doubao/MiMo)的终极全能终端工具"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes