fr-cli 2.1.0__py3-none-any.whl

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 (64) hide show
  1. fr_cli/README.md +148 -0
  2. fr_cli/WEAPON.MD +186 -0
  3. fr_cli/__init__.py +4 -0
  4. fr_cli/addon/plugin.py +69 -0
  5. fr_cli/agent/__init__.py +9 -0
  6. fr_cli/agent/builtins/__init__.py +4 -0
  7. fr_cli/agent/builtins/_utils.py +48 -0
  8. fr_cli/agent/builtins/db.py +269 -0
  9. fr_cli/agent/builtins/local.py +105 -0
  10. fr_cli/agent/builtins/rag.py +652 -0
  11. fr_cli/agent/builtins/rag_watcher_daemon.py +156 -0
  12. fr_cli/agent/builtins/remote.py +214 -0
  13. fr_cli/agent/builtins/spider.py +247 -0
  14. fr_cli/agent/client.py +164 -0
  15. fr_cli/agent/executor.py +86 -0
  16. fr_cli/agent/generator.py +104 -0
  17. fr_cli/agent/manager.py +193 -0
  18. fr_cli/agent/master.py +604 -0
  19. fr_cli/agent/master_prompt.py +118 -0
  20. fr_cli/agent/remote.py +70 -0
  21. fr_cli/agent/server.py +279 -0
  22. fr_cli/agent/workflow.py +164 -0
  23. fr_cli/breakthrough/update.py +154 -0
  24. fr_cli/command/__init__.py +4 -0
  25. fr_cli/command/executor.py +276 -0
  26. fr_cli/command/registry.py +1034 -0
  27. fr_cli/command/security.py +30 -0
  28. fr_cli/conf/config.py +126 -0
  29. fr_cli/conf/wizard.py +172 -0
  30. fr_cli/core/chat.py +280 -0
  31. fr_cli/core/core.py +111 -0
  32. fr_cli/core/intent.py +129 -0
  33. fr_cli/core/recommender.py +71 -0
  34. fr_cli/core/stream.py +83 -0
  35. fr_cli/core/sysmon.py +117 -0
  36. fr_cli/core/thinking.py +215 -0
  37. fr_cli/gatekeeper/__init__.py +7 -0
  38. fr_cli/gatekeeper/daemon.py +216 -0
  39. fr_cli/gatekeeper/manager.py +218 -0
  40. fr_cli/lang/i18n.py +827 -0
  41. fr_cli/main.py +329 -0
  42. fr_cli/memory/context.py +119 -0
  43. fr_cli/memory/history.py +96 -0
  44. fr_cli/memory/session.py +134 -0
  45. fr_cli/repl/__init__.py +0 -0
  46. fr_cli/repl/commands.py +1098 -0
  47. fr_cli/security/security.py +46 -0
  48. fr_cli/ui/ui.py +116 -0
  49. fr_cli/weapon/cron.py +217 -0
  50. fr_cli/weapon/dataframe.py +97 -0
  51. fr_cli/weapon/disk.py +141 -0
  52. fr_cli/weapon/fs.py +206 -0
  53. fr_cli/weapon/launcher.py +249 -0
  54. fr_cli/weapon/loader.py +98 -0
  55. fr_cli/weapon/mail.py +227 -0
  56. fr_cli/weapon/mcp.py +204 -0
  57. fr_cli/weapon/vision.py +74 -0
  58. fr_cli/weapon/web.py +88 -0
  59. fr_cli-2.1.0.dist-info/METADATA +227 -0
  60. fr_cli-2.1.0.dist-info/RECORD +64 -0
  61. fr_cli-2.1.0.dist-info/WHEEL +5 -0
  62. fr_cli-2.1.0.dist-info/entry_points.txt +2 -0
  63. fr_cli-2.1.0.dist-info/licenses/LICENSE +21 -0
  64. fr_cli-2.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1098 @@
1
+ """
2
+ REPL 命令路由处理器
3
+ 从 main.py 提取的所有 / 命令实现,减轻主模块负担。
4
+ """
5
+ import sys
6
+
7
+ from fr_cli.lang.i18n import T
8
+ from fr_cli.ui.ui import (
9
+ CYAN, RED, YELLOW, GREEN, DIM, RESET,
10
+ print_bye
11
+ )
12
+ from fr_cli.memory.history import save_sess, load_sess, del_sess, get_sessions
13
+ from fr_cli.memory.context import load_context
14
+ from fr_cli.memory.session import (
15
+ list_sessions as list_auto_sessions,
16
+ load_session as load_auto_session,
17
+ delete_session as delete_auto_session,
18
+ )
19
+ from fr_cli.addon.plugin import extract_code
20
+ from fr_cli.core.stream import stream_cnt
21
+ from fr_cli.agent.manager import (
22
+ create_agent_dir, save_agent_code, save_persona, save_skills,
23
+ save_memory, agent_exists, list_agents, delete_agent,
24
+ load_persona, load_memory, load_skills,
25
+ )
26
+ from fr_cli.agent.executor import run_agent
27
+
28
+
29
+ def _print_help(state, topic):
30
+ """打印修仙指南"""
31
+ topic_map = {
32
+ "config": "config",
33
+ "fs": "fs", "file": "fs", "files": "fs",
34
+ "session": "session", "sess": "session",
35
+ "plugin": "plugin", "plugins": "plugin", "skill": "plugin", "skills": "plugin",
36
+ "mail": "mail", "email": "mail",
37
+ "cron": "cron", "timer": "cron", "schedule": "cron",
38
+ "web": "web", "search": "web",
39
+ "disk": "disk", "cloud": "disk",
40
+ "vision": "vision", "image": "vision", "see": "vision", "img": "vision",
41
+ "shell": "shell", "matrix": "shell", "cmd": "shell",
42
+ "tools": "tools", "tool": "tools", "invoke": "tools",
43
+ "security": "security", "safe": "security", "sec": "security",
44
+ "app": "app", "launcher": "app", "launch": "app", "open": "app",
45
+ "agent": "agent", "agents": "agent",
46
+ "builtin": "builtin", "builtins": "builtin",
47
+ "dataframe": "dataframe", "data": "dataframe",
48
+ "gatekeeper": "gatekeeper",
49
+ "all": "all",
50
+ }
51
+ mapped = topic_map.get(topic, "")
52
+ lang = state.lang
53
+
54
+ if not mapped:
55
+ print(f"{CYAN}{T('help_title', lang)}{RESET}")
56
+ print(f" {T('help_cfg', lang)} /model /key /limit /alias /export /update")
57
+ print(f" {T('help_fs', lang)} /ls /cat /cd /write /append /delete")
58
+ print(f" {T('help_sess', lang)} /save /load /del /undo")
59
+ print(f" {DIM} 自动存档: /session_list | /session_load <编号> | /session_del <编号>{RESET}")
60
+ print(f" {DIM} 主控Agent: /master on|off|status — 启用自我进化型主Agent{RESET}")
61
+ print(f" {T('help_plugin', lang)} /skills (自动进化)")
62
+ print(f" {DIM} 思维: /mode <direct|cot|tot|react> — 切换 AI 推理模式{RESET}")
63
+ print(f" {T('help_extra', lang)} /mail_* /cron_* /web /fetch /disk_* /see")
64
+ print(f" {DIM} Agent: /agent_create /agent_forge /agent_list /agent_run /agent_show /agent_edit /agent_delete{RESET}")
65
+ print(f" {DIM} Agent API: /agent_server start [port] | stop | status{RESET}")
66
+ print(f" {DIM} Agent 发布: /agent_publish — 生成对外连接信息{RESET}")
67
+ print(f" {DIM} Agent 定时: /agent_cron_add <agent> <秒> [输入] | /agent_cron_list | /agent_cron_del <ID>{RESET}")
68
+ print(f" {DIM} 远程Agent: /remote_agent_add <name> <host> <port> <token> [desc] | /remote_agent_list | /remote_agent_del <name>{RESET}")
69
+ print(f" {DIM} 远程发现: /remote_agent_scan <host> <port> <token> | /remote_agent_import <host> <port> <token> [prefix]{RESET}")
70
+ print(f" {DIM} 本机应用: /open <路径/URL> | /launch <应用> [目标] | /apps{RESET}")
71
+ print(f" {DIM} 内置Agent: @local <需求> | @remote [IP] <需求> | @spider <URL> [深度] | @db <需求> | @RAG <问题>{RESET}")
72
+ print(f" {DIM} 知识库: /rag_dir <目录> | /rag_sync | /rag_watch start/stop/status/log{RESET}")
73
+ print(f" {DIM} 数据: /read_excel <文件> | /read_csv <文件>{RESET}")
74
+ print(f" {DIM} MCP: /mcp_list | /mcp_add <名称> <命令> [参数...] | /mcp_del <名称> | /mcp_enable <名称> | /mcp_disable <名称> | /mcp_refresh{RESET}")
75
+ print(f" {T('help_shell', lang)} {T('shell_tip', lang)}\n {T('pipe_tip', lang)}")
76
+ print(f"\n{T('help_usage', lang)}")
77
+ elif mapped == "all":
78
+ for t in ["config", "fs", "session", "plugin", "mail", "cron", "web", "disk", "vision", "shell", "tools", "security", "app", "agent", "builtin", "dataframe", "gatekeeper", "mcp"]:
79
+ print(T(f"help_detail_{t}", lang))
80
+ print()
81
+ else:
82
+ detail = T(f"help_detail_{mapped}", lang)
83
+ if detail:
84
+ print(detail)
85
+ else:
86
+ print(T("help_not_found", lang, topic))
87
+
88
+
89
+ def _cmd_exit(state, parts):
90
+ print_bye()
91
+ return True
92
+
93
+
94
+ def _cmd_help(state, parts):
95
+ arg1 = parts[1] if len(parts) > 1 else ""
96
+ _print_help(state, arg1.lower())
97
+ return False
98
+
99
+
100
+ def _cmd_model(state, parts):
101
+ arg1 = parts[1] if len(parts) > 1 else ""
102
+ if arg1:
103
+ state.update_model(arg1)
104
+ print(f"{GREEN}{T('ok_model', state.lang, arg1)}{RESET}")
105
+ return False
106
+
107
+
108
+ def _cmd_key(state, parts):
109
+ arg1 = parts[1] if len(parts) > 1 else ""
110
+ if arg1:
111
+ state.update_key(arg1)
112
+ print(f"{GREEN}{T('ok_key', state.lang)}{RESET}")
113
+ return False
114
+
115
+
116
+ def _cmd_limit(state, parts):
117
+ arg1 = parts[1] if len(parts) > 1 else ""
118
+ if arg1:
119
+ try:
120
+ v = int(arg1)
121
+ if v < 1000:
122
+ raise ValueError
123
+ state.update_limit(v)
124
+ print(f"{GREEN}{T('ok_limit', state.lang, v)}{RESET}")
125
+ except ValueError:
126
+ print(f"{RED}{T('err_limit', state.lang)}{RESET}")
127
+ return False
128
+
129
+
130
+ def _cmd_lang(state, parts):
131
+ arg1 = parts[1] if len(parts) > 1 else ""
132
+ if arg1:
133
+ if arg1 in ["zh", "en"]:
134
+ state.update_lang(arg1)
135
+ print(f"{GREEN}语言已切换为: {'中文' if arg1 == 'zh' else 'English'}{RESET}")
136
+ else:
137
+ print(f"{RED}支持的语言: zh (中文), en (English){RESET}")
138
+ return False
139
+
140
+
141
+ def _cmd_dir(state, parts):
142
+ arg1 = parts[1] if len(parts) > 1 else ""
143
+ if arg1:
144
+ ok, m = state.vfs.add(arg1, state.lang)
145
+ if ok:
146
+ state.cfg["allowed_dirs"] = state.vfs.ds
147
+ state.save_cfg()
148
+ print(m)
149
+ return False
150
+
151
+
152
+ def _cmd_dirs(state, parts):
153
+ items, err = state.vfs.list_dirs(state.lang)
154
+ if err:
155
+ print(err)
156
+ else:
157
+ print(f"{CYAN}📂 已挂载的洞府:{RESET}")
158
+ for item in items:
159
+ print(item)
160
+ return False
161
+
162
+
163
+ def _cmd_rmdir(state, parts):
164
+ arg1 = parts[1] if len(parts) > 1 else ""
165
+ if arg1:
166
+ ok, m = state.vfs.remove_dir(arg1, state.lang)
167
+ if ok:
168
+ state.cfg["allowed_dirs"] = state.vfs.ds
169
+ state.save_cfg()
170
+ print(m)
171
+ return False
172
+
173
+
174
+ def _cmd_save(state, parts):
175
+ arg1 = parts[1] if len(parts) > 1 else ""
176
+ if arg1:
177
+ state.update_session_name(arg1)
178
+ if save_sess(arg1, state.messages):
179
+ print(f"{GREEN}{T('ok_sess_save', state.lang, arg1)}{RESET}")
180
+ recent = extract_recent_turns(state.messages, 5)
181
+ ctx = build_context_summary(recent, state.lang)
182
+ save_context(arg1, ctx)
183
+ return False
184
+
185
+
186
+ def _cmd_load(state, parts):
187
+ ss = get_sessions()
188
+ if not ss:
189
+ print(T("no_sess", state.lang))
190
+ return False
191
+ for i, s in enumerate(ss):
192
+ print(f" [{i}] {s['name']}")
193
+ idx = input(f"{YELLOW}ID: {RESET}").strip()
194
+ if idx.isdigit():
195
+ sp = T("sys_prompt", state.lang)
196
+ ok, m, name = load_sess(int(idx), sp)
197
+ if ok:
198
+ state.messages = m
199
+ state.update_session_name(name)
200
+ state.context_summary = load_context(name)
201
+ print(f"{GREEN}{T('ok_sess_load', state.lang, name)}{RESET}")
202
+ return False
203
+
204
+
205
+ def _cmd_del(state, parts):
206
+ ss = get_sessions()
207
+ if not ss:
208
+ print(T("no_sess", state.lang))
209
+ return False
210
+ for i, s in enumerate(ss):
211
+ print(f" [{i}] {s['name']}")
212
+ idx = input(f"{YELLOW}ID: {RESET}").strip()
213
+ if idx.isdigit() and del_sess(int(idx)):
214
+ print(GREEN + T("ok_sess_del", state.lang) + RESET)
215
+ return False
216
+
217
+
218
+ def _cmd_session_list(state, parts):
219
+ """列出所有按日期自动保存的会话"""
220
+ sessions = list_auto_sessions()
221
+ if not sessions:
222
+ print(f"{DIM}暂无自动会话存档。{RESET}")
223
+ return False
224
+ print(f"{CYAN}📁 自动会话列表:{RESET}")
225
+ for s in sessions:
226
+ print(f" [{s['index']}] {CYAN}{s['filename']}{RESET} | 创建: {s['created_at']} | 消息: {s['msg_count']} 条")
227
+ return False
228
+
229
+
230
+ def _cmd_session_load(state, parts):
231
+ """加载指定索引的自动会话并继续对话"""
232
+ arg1 = parts[1] if len(parts) > 1 else ""
233
+ if not arg1 or not arg1.isdigit():
234
+ print(f"{YELLOW}用法: /session_load <编号> (先用 /session_list 查看编号){RESET}")
235
+ return False
236
+ idx = int(arg1)
237
+ sp = T("sys_prompt", state.lang)
238
+ ok, msgs, fname = load_auto_session(idx, sp)
239
+ if ok:
240
+ state.messages = msgs
241
+ print(f"{GREEN}✅ 已加载会话 [{fname}],共 {len(msgs)} 条消息。{RESET}")
242
+ print(f"{DIM} 后续对话将追加到当前自动会话存档中。{RESET}")
243
+ else:
244
+ print(f"{RED}❌ 加载失败,编号 {idx} 无效。{RESET}")
245
+ return False
246
+
247
+
248
+ def _cmd_session_del(state, parts):
249
+ """删除指定索引的自动会话"""
250
+ arg1 = parts[1] if len(parts) > 1 else ""
251
+ if not arg1 or not arg1.isdigit():
252
+ print(f"{YELLOW}用法: /session_del <编号>{RESET}")
253
+ return False
254
+ idx = int(arg1)
255
+ if delete_auto_session(idx):
256
+ print(f"{GREEN}✅ 已删除编号 {idx} 的会话。{RESET}")
257
+ else:
258
+ print(f"{RED}❌ 删除失败,编号 {idx} 无效。{RESET}")
259
+ return False
260
+
261
+
262
+ def _cmd_see(state, parts):
263
+ from fr_cli.weapon.vision import prep_see_msg
264
+ arg1 = parts[1] if len(parts) > 1 else ""
265
+ if not arg1:
266
+ return False
267
+ if state.model_name != "glm-4v-plus":
268
+ print(f"{YELLOW}{T('see_warn', state.lang)}{RESET}")
269
+ print(f"{CYAN}{T('see_ing', state.lang)}{RESET}")
270
+ prep_see_msg(state.messages, arg1, parts[2] if len(parts) > 2 else "", vfs=state.vfs)
271
+ txt, _, response_time = stream_cnt(
272
+ state.client, state.model_name, state.messages, state.lang,
273
+ max_tokens=state.limit
274
+ )
275
+ state.messages.append({"role": "assistant", "content": txt})
276
+ sys_stats = get_sys_stats(state.lang)
277
+ stats_extra = f" | {sys_stats}" if sys_stats else ""
278
+ print(f"{DIM}📊 {T('stats_model', state.lang)}: {state.model_name} | {T('stats_time', state.lang)}: {response_time:.2f}{T('stats_seconds', state.lang)}{stats_extra}{RESET}")
279
+ return False
280
+
281
+
282
+ def _cmd_update(state, parts):
283
+ from fr_cli.breakthrough.update import update_check, update_and_restart
284
+ arg1 = parts[1] if len(parts) > 1 else ""
285
+ if arg1 == "check":
286
+ ok, info, err = update_check(verbose=False)
287
+ if err:
288
+ print(f"{RED}[更新] 检查失败: {err}{RESET}")
289
+ elif not ok:
290
+ print(f"{GREEN}[更新] 当前已是最新版本。{RESET}")
291
+ else:
292
+ ver = info.get("version", "?")
293
+ note = info.get("release_note", "")
294
+ print(f"{YELLOW}[更新] 发现新版本: {ver}{RESET}")
295
+ if note:
296
+ print(f"{DIM}更新说明:\n{note}{RESET}")
297
+ print(f"{DIM}输入 /update run 执行更新{RESET}")
298
+ elif arg1 == "run":
299
+ print(f"{YELLOW}[更新] 正在连接天道获取最新法器...{RESET}")
300
+ ok, msg = update_and_restart(verbose=True, allow_restart=True)
301
+ if ok:
302
+ print(f"{GREEN}{msg}{RESET}")
303
+ else:
304
+ print(f"{RED}{msg}{RESET}")
305
+ else:
306
+ print(f"{DIM}用法: /update check (检查) | /update run (执行更新){RESET}")
307
+ return False
308
+
309
+
310
+ def _cmd_agent_server(state, parts):
311
+ from fr_cli.agent.server import AgentHTTPServer
312
+ arg1 = parts[1] if len(parts) > 1 else ""
313
+ if arg1 == "start":
314
+ port = int(parts[2]) if len(parts) > 2 and parts[2].isdigit() else 17890
315
+ if state.agent_server is None:
316
+ state.agent_server = AgentHTTPServer(state, port=port)
317
+ ok, msg = state.agent_server.start()
318
+ color = GREEN if ok else YELLOW
319
+ print(f"{color}{msg}{RESET}")
320
+ elif arg1 == "stop":
321
+ if state.agent_server is None:
322
+ print(f"{YELLOW}服务未运行{RESET}")
323
+ else:
324
+ ok, msg = state.agent_server.stop()
325
+ color = GREEN if ok else YELLOW
326
+ print(f"{color}{msg}{RESET}")
327
+ elif arg1 == "status":
328
+ if state.agent_server is None:
329
+ print(f"{DIM}未运行{RESET}")
330
+ else:
331
+ print(f"{CYAN}{state.agent_server.status()}{RESET}")
332
+ else:
333
+ print(f"{DIM}用法: /agent_server start [port] | /agent_server stop | /agent_server status{RESET}")
334
+ return False
335
+
336
+
337
+ def _cmd_mode(state, parts):
338
+ """切换思维模式:direct / cot / tot / react"""
339
+ # MasterAgent 模式下思维模式由其内部 ReAct 循环控制,/mode 无效
340
+ if getattr(state, 'master_agent', None) and state.master_agent.is_enabled():
341
+ print(f"{YELLOW}⚠️ MasterAgent 主控模式下,思维模式由其内部 ReAct 循环自主管理,/mode 命令无效。{RESET}")
342
+ print(f"{DIM} 提示: 使用 /master off 关闭主控后可切换思维模式。{RESET}")
343
+ return False
344
+
345
+ from fr_cli.core.thinking import ThinkingEngine
346
+ arg1 = parts[1] if len(parts) > 1 else ""
347
+ if not arg1:
348
+ print(f"{CYAN}当前思维模式: {state.thinking_mode}{RESET}")
349
+ print(f"{DIM}可用模式: direct(直接回答)| cot(思维链)| tot(思维树)| react(推理+行动){RESET}")
350
+ return False
351
+ mode = arg1.lower()
352
+ if not ThinkingEngine.is_valid_mode(mode):
353
+ print(f"{RED}无效模式: {mode}{RESET}")
354
+ print(f"{DIM}可用模式: direct | cot | tot | react{RESET}")
355
+ return False
356
+ state.update_thinking_mode(mode)
357
+ mode_desc = {
358
+ "direct": "直接回答(默认)",
359
+ "cot": "思维链 — 先进行问题拆解和自我验证,再回答",
360
+ "tot": "思维树 — 生成多分支策略树,评估后选择最优路径",
361
+ "react": "ReAct — 每一步先思考再行动,循环直到问题解决",
362
+ }
363
+ print(f"{GREEN}✅ 思维模式已切换: {mode_desc.get(mode, mode)}{RESET}")
364
+ return False
365
+
366
+
367
+ def _cmd_gatekeeper(state, parts):
368
+ arg1 = parts[1] if len(parts) > 1 else ""
369
+ if arg1 == "start":
370
+ from fr_cli.weapon.cron import _default_manager as _cron_mgr
371
+ from fr_cli.gatekeeper.manager import read_daemon_config
372
+ # 保留已有的 agent_crons 配置(如果存在)
373
+ existing_cfg = read_daemon_config()
374
+ daemon_cfg = {
375
+ "agent_server_port": state.agent_server.port if (state.agent_server and state.agent_server.is_running()) else None,
376
+ "cron_jobs": _cron_mgr.export_jobs(),
377
+ "agent_crons": existing_cfg.get("agent_crons", []),
378
+ "lang": state.lang,
379
+ }
380
+ ok, msg = state.gatekeeper.save_daemon_config(daemon_cfg)
381
+ if not ok:
382
+ print(f"{YELLOW}{msg}{RESET}")
383
+ ok, msg = state.gatekeeper.start()
384
+ color = GREEN if ok else YELLOW
385
+ print(f"{color}{msg}{RESET}")
386
+ elif arg1 == "stop":
387
+ ok, msg = state.gatekeeper.stop()
388
+ color = GREEN if ok else YELLOW
389
+ print(f"{color}{msg}{RESET}")
390
+ elif arg1 == "status":
391
+ print(f"{CYAN}{state.gatekeeper.status()}{RESET}")
392
+ else:
393
+ print(f"{DIM}用法: /gatekeeper start | /gatekeeper stop | /gatekeeper status{RESET}")
394
+ return False
395
+
396
+
397
+ def _cmd_open(state, parts):
398
+ from fr_cli.weapon.launcher import open_file
399
+ arg1 = parts[1] if len(parts) > 1 else ""
400
+ if arg1:
401
+ ok, msg = open_file(arg1, state.lang)
402
+ color = GREEN if ok else RED
403
+ print(f"{color}{msg}{RESET}")
404
+ return False
405
+
406
+
407
+ def _cmd_launch(state, parts):
408
+ from fr_cli.weapon.launcher import launch_app
409
+ arg1 = parts[1] if len(parts) > 1 else ""
410
+ if arg1:
411
+ target = parts[2] if len(parts) > 2 else None
412
+ ok, msg = launch_app(arg1, target, state.lang)
413
+ color = GREEN if ok else RED
414
+ print(f"{color}{msg}{RESET}")
415
+ return False
416
+
417
+
418
+ def _cmd_apps(state, parts):
419
+ from fr_cli.weapon.launcher import list_apps
420
+ res, err = list_apps(state.lang)
421
+ if err:
422
+ print(f"{RED}{err}{RESET}")
423
+ else:
424
+ print(f"{CYAN}{res}{RESET}")
425
+ return False
426
+
427
+
428
+ def _cmd_agent_create(state, parts):
429
+ from fr_cli.agent.generator import generate_agent
430
+ from fr_cli.agent.manager import save_persona, save_skills, save_agent_code, create_agent_dir
431
+ arg1 = parts[1] if len(parts) > 1 else ""
432
+ desc = parts[2] if len(parts) > 2 else ""
433
+ if not arg1 or not desc:
434
+ print(f"{YELLOW}用法: /agent_create <名称> <需求描述>{RESET}")
435
+ return False
436
+ d = create_agent_dir(arg1)
437
+ result = generate_agent(state.client, state.model_name, arg1, desc, state.lang)
438
+ if result["persona"]:
439
+ save_persona(arg1, result["persona"])
440
+ if result["skills"]:
441
+ save_skills(arg1, result["skills"])
442
+ if result["code"]:
443
+ save_agent_code(arg1, result["code"])
444
+ print(f"{GREEN}✅ Agent [{arg1}] 铸造完成!{RESET}")
445
+ print(f"{DIM} 人设: {'已生成' if result['persona'] else '未生成'}{RESET}")
446
+ print(f"{DIM} 技能: {'已生成' if result['skills'] else '未生成'}{RESET}")
447
+ print(f"{DIM} 代码: {'已生成' if result['code'] else '未生成'}{RESET}")
448
+ print(f"{DIM} 路径: {d}{RESET}")
449
+ return False
450
+
451
+
452
+ def _cmd_agent_list(state, parts):
453
+ from fr_cli.agent.manager import list_agents
454
+ agents = list_agents()
455
+ if not agents:
456
+ print(f"{YELLOW}暂无 Agent 分身。使用 /agent_create <名称> <描述> 创建。{RESET}")
457
+ else:
458
+ print(f"{CYAN}已创建的 Agent 分身:{RESET}")
459
+ for a in agents:
460
+ flags = []
461
+ if a["has_persona"]: flags.append("人设")
462
+ if a["has_memory"]: flags.append("记忆")
463
+ if a["has_skills"]: flags.append("技能")
464
+ flag_str = f" ({', '.join(flags)})" if flags else ""
465
+ print(f" {a['name']}{flag_str}")
466
+ return False
467
+
468
+
469
+ def _cmd_agent_delete(state, parts):
470
+ from fr_cli.agent.manager import delete_agent
471
+ arg1 = parts[1] if len(parts) > 1 else ""
472
+ if arg1:
473
+ if delete_agent(arg1):
474
+ print(f"{GREEN}✅ Agent [{arg1}] 已抹除。{RESET}")
475
+ else:
476
+ print(f"{RED}Agent [{arg1}] 不存在。{RESET}")
477
+ return False
478
+
479
+
480
+ def _cmd_agent_show(state, parts):
481
+ from fr_cli.agent.manager import agent_exists, load_persona, load_memory, load_skills, load_agent_code
482
+ from fr_cli.agent.workflow import load_workflow
483
+ arg1 = parts[1] if len(parts) > 1 else ""
484
+ if not arg1:
485
+ return False
486
+ if not agent_exists(arg1):
487
+ print(f"{RED}Agent [{arg1}] 不存在。{RESET}")
488
+ else:
489
+ print(f"{CYAN}═══ Agent: {arg1} ═══{RESET}")
490
+ p = load_persona(arg1)
491
+ m = load_memory(arg1)
492
+ s = load_skills(arg1)
493
+ c = load_agent_code(arg1)
494
+ w = load_workflow(arg1)
495
+ if p: print(f"\n{DIM}[人设]{RESET}\n{p[:500]}{'...' if len(p) > 500 else ''}")
496
+ if s: print(f"\n{DIM}[技能]{RESET}\n{s[:500]}{'...' if len(s) > 500 else ''}")
497
+ if m: print(f"\n{DIM}[记忆]{RESET}\n{m[:300]}{'...' if len(m) > 300 else ''}")
498
+ if c: print(f"\n{DIM}[代码]{RESET}\n{c[:300]}{'...' if len(c) > 300 else ''}")
499
+ if w: print(f"\n{DIM}[工作流]{RESET}\n{w[:300]}{'...' if len(w) > 300 else ''}")
500
+ return False
501
+
502
+
503
+ def _cmd_agent_run(state, parts):
504
+ from fr_cli.agent.executor import run_agent
505
+ arg1 = parts[1] if len(parts) > 1 else ""
506
+ if not arg1:
507
+ return False
508
+ run_args = parts[2] if len(parts) > 2 else ""
509
+ kwargs = {"user_input": run_args} if run_args else {}
510
+ result, err = run_agent(arg1, state, **kwargs)
511
+ if err:
512
+ print(f"{RED}{err}{RESET}")
513
+ else:
514
+ print(f"{GREEN}{result}{RESET}")
515
+ return False
516
+
517
+
518
+ def _cmd_remote_agent_add(state, parts):
519
+ """添加远程 Agent: /remote_agent_add <name> <host> <port> <token> [description]"""
520
+ from fr_cli.agent.remote import add_remote_agent
521
+ if len(parts) < 5:
522
+ print(f"{YELLOW}用法: /remote_agent_add <name> <host> <port> <token> [description]{RESET}")
523
+ return False
524
+ name, host, port, token = parts[1], parts[2], parts[3], parts[4]
525
+ desc = ' '.join(parts[5:]) if len(parts) > 5 else ""
526
+ try:
527
+ port = int(port)
528
+ except ValueError:
529
+ print(f"{RED}端口号必须是数字{RESET}")
530
+ return False
531
+ add_remote_agent(name, host, port, token, desc)
532
+ print(f"{GREEN}✅ 远程 Agent [{name}] 已注册: {host}:{port}{RESET}")
533
+ return False
534
+
535
+
536
+ def _cmd_remote_agent_list(state, parts):
537
+ """列出所有远程 Agent"""
538
+ from fr_cli.agent.remote import list_remote_agents
539
+ agents = list_remote_agents()
540
+ if not agents:
541
+ print(f"{DIM}暂无远程 Agent。使用 /remote_agent_add 添加。{RESET}")
542
+ return False
543
+ print(f"{CYAN}🌐 远程 Agent 列表:{RESET}")
544
+ for name, cfg in agents.items():
545
+ print(f" [{name}] {cfg['host']}:{cfg['port']} — {cfg.get('description', '')}")
546
+ return False
547
+
548
+
549
+ def _cmd_remote_agent_del(state, parts):
550
+ """删除远程 Agent: /remote_agent_del <name>"""
551
+ from fr_cli.agent.remote import remove_remote_agent
552
+ arg1 = parts[1] if len(parts) > 1 else ""
553
+ if not arg1:
554
+ print(f"{YELLOW}用法: /remote_agent_del <name>{RESET}")
555
+ return False
556
+ if remove_remote_agent(arg1):
557
+ print(f"{GREEN}✅ 远程 Agent [{arg1}] 已删除。{RESET}")
558
+ else:
559
+ print(f"{RED}远程 Agent [{arg1}] 不存在。{RESET}")
560
+ return False
561
+
562
+
563
+ def _cmd_agent_publish(state, parts):
564
+ """发布当前Agent服务,生成对外连接信息"""
565
+ if not state.agent_server or not state.agent_server.is_running():
566
+ print(f"{YELLOW}⚠️ Agent HTTP 服务未运行。请先启动服务:{RESET}")
567
+ print(f"{DIM} /agent_server start [port]{RESET}")
568
+ return False
569
+
570
+ info = state.agent_server.get_publish_info()
571
+ if not info:
572
+ print(f"{RED}无法获取发布信息。{RESET}")
573
+ return False
574
+
575
+ print(f"{CYAN}═══ Agent 服务发布信息 ═══{RESET}")
576
+ print(f" 服务地址: {info['url']}")
577
+ print(f" 认证Token: {info['token']}")
578
+ print(f" 主机名: {info['hostname']}")
579
+ print(f" 本地IP: {info['local_ip']}")
580
+ print(f"\n{DIM}分享以下信息给其他fr-cli用户,对方可用 /remote_agent_add 或 /remote_agent_import 添加:{RESET}")
581
+ print(f" Host: {info['host']}")
582
+ print(f" Port: {state.agent_server.port}")
583
+ print(f" Token: {info['token']}")
584
+ print(f"\n{DIM}快速扫描命令(对方执行):{RESET}")
585
+ print(f" /remote_agent_scan {info['host']} {state.agent_server.port} {info['token']}")
586
+ print(f"\n{YELLOW}⚠️ 安全提示:{RESET}")
587
+ print(f" - 当前绑定: {state.agent_server.host}")
588
+ if state.agent_server.host == "127.0.0.1":
589
+ print(f" - 仅本地可访问。如需公网暴露,请使用 ngrok / cloudflared / frp 等内网穿透工具")
590
+ return False
591
+
592
+
593
+ def _cmd_remote_agent_scan(state, parts):
594
+ """扫描远程主机的Agent服务: /remote_agent_scan <host> <port> <token>"""
595
+ from fr_cli.agent.client import scan_remote_host
596
+ if len(parts) < 4:
597
+ print(f"{YELLOW}用法: /remote_agent_scan <host> <port> <token>{RESET}")
598
+ return False
599
+ host, port, token = parts[1], parts[2], parts[3]
600
+ try:
601
+ port = int(port)
602
+ except ValueError:
603
+ print(f"{RED}端口号必须是数字{RESET}")
604
+ return False
605
+
606
+ print(f"{CYAN}🔍 正在扫描 {host}:{port} ...{RESET}")
607
+ info, err = scan_remote_host(host, port, token)
608
+ if err:
609
+ print(f"{RED}{err}{RESET}")
610
+ return False
611
+
612
+ print(f"{GREEN}✅ 发现服务: {info['service']} v{info['version']}{RESET}")
613
+ agents = info.get("agents", [])
614
+ if not agents:
615
+ print(f"{DIM} 该主机暂无可用Agent。{RESET}")
616
+ else:
617
+ print(f"{CYAN} 可用Agent ({len(agents)}个):{RESET}")
618
+ for a in agents:
619
+ badges = []
620
+ if a.get("has_persona"): badges.append("人设")
621
+ if a.get("has_memory"): badges.append("记忆")
622
+ if a.get("has_skills"): badges.append("技能")
623
+ badge_str = f" [{', '.join(badges)}]" if badges else ""
624
+ print(f" - {a['name']}{badge_str}")
625
+ print(f"\n{DIM}使用 /remote_agent_import {host} {port} {token} 一键导入所有Agent{RESET}")
626
+ return False
627
+
628
+
629
+ def _cmd_remote_agent_import(state, parts):
630
+ """一键导入远程主机的所有Agent: /remote_agent_import <host> <port> <token> [prefix]"""
631
+ from fr_cli.agent.client import import_remote_agents
632
+ if len(parts) < 4:
633
+ print(f"{YELLOW}用法: /remote_agent_import <host> <port> <token> [prefix]{RESET}")
634
+ return False
635
+ host, port, token = parts[1], parts[2], parts[3]
636
+ prefix = parts[4] if len(parts) > 4 else ""
637
+ try:
638
+ port = int(port)
639
+ except ValueError:
640
+ print(f"{RED}端口号必须是数字{RESET}")
641
+ return False
642
+
643
+ print(f"{CYAN}📥 正在从 {host}:{port} 导入 Agent ...{RESET}")
644
+ imported, errors = import_remote_agents(host, port, token, prefix)
645
+ if imported:
646
+ print(f"{GREEN}✅ 成功导入 {imported} 个Agent。{RESET}")
647
+ if errors:
648
+ print(f"{YELLOW}⚠️ 导入过程中出现 {len(errors)} 个错误:{RESET}")
649
+ for e in errors:
650
+ print(f" {RED}{e}{RESET}")
651
+ if not imported and not errors:
652
+ print(f"{DIM}远程主机暂无Agent。{RESET}")
653
+ return False
654
+
655
+
656
+ def _cmd_agent_edit(state, parts):
657
+ from fr_cli.agent.manager import agent_exists, save_persona, save_memory, save_skills, save_agent_code
658
+ from fr_cli.agent.workflow import save_workflow
659
+ arg1 = parts[1] if len(parts) > 1 else ""
660
+ if not arg1:
661
+ return False
662
+ if not agent_exists(arg1):
663
+ print(f"{RED}Agent [{arg1}] 不存在。{RESET}")
664
+ return False
665
+ file_type = parts[2] if len(parts) > 2 else ""
666
+ valid_types = {"persona", "memory", "skills", "agent", "workflow"}
667
+ if file_type not in valid_types:
668
+ print(f"{YELLOW}用法: /agent_edit <名称> <类型>,类型: persona/memory/skills/agent/workflow{RESET}")
669
+ return False
670
+ print(f"{CYAN}请输入新的 {file_type} 内容(Ctrl+D 结束):{RESET}")
671
+ try:
672
+ new_content = sys.stdin.read().strip()
673
+ except (EOFError, KeyboardInterrupt):
674
+ new_content = ""
675
+ if not new_content:
676
+ print(f"{YELLOW}内容为空,未保存。{RESET}")
677
+ return False
678
+ if file_type == "persona":
679
+ save_persona(arg1, new_content)
680
+ elif file_type == "memory":
681
+ save_memory(arg1, new_content)
682
+ elif file_type == "skills":
683
+ save_skills(arg1, new_content)
684
+ elif file_type == "agent":
685
+ save_agent_code(arg1, new_content)
686
+ elif file_type == "workflow":
687
+ save_workflow(arg1, new_content)
688
+ print(f"{GREEN}✅ {file_type} 已更新。{RESET}")
689
+ return False
690
+
691
+
692
+ def _cmd_agent_forge(state, parts):
693
+ """从最近一次 AI 回复中提取 Python 代码块,铸造为 Agent 分身。"""
694
+ from fr_cli.agent.manager import create_agent_dir, save_agent_code, save_persona, save_skills, agent_exists
695
+ from fr_cli.addon.plugin import extract_code
696
+ arg1 = parts[1] if len(parts) > 1 else ""
697
+ if not arg1:
698
+ print(f"{YELLOW}用法: /agent_forge <名称>{RESET}")
699
+ print(f"{DIM} 从最近一次 AI 回复中提取 Python 代码块,创建 Agent 分身。{RESET}")
700
+ return False
701
+
702
+ safe_name = "".join(c for c in arg1 if c.isalnum() or c == '_')
703
+ if not safe_name:
704
+ print(f"{RED}名称无效,仅允许字母/数字/下划线{RESET}")
705
+ return False
706
+
707
+ # 从历史消息中倒序查找最近包含 def run 的 Python 代码块
708
+ code = ""
709
+ for msg in reversed(state.messages):
710
+ if msg.get("role") == "assistant":
711
+ c = extract_code(msg.get("content", ""))
712
+ if c and "def run" in c:
713
+ code = c
714
+ break
715
+
716
+ if not code:
717
+ print(f"{YELLOW}未在最近 AI 回复中找到包含 def run 的 Python 代码块。{RESET}")
718
+ print(f"{DIM}提示:先让 AI 生成一段包含 def run(context, **kwargs) 的代码,再执行此命令。{RESET}")
719
+ return False
720
+
721
+ if agent_exists(safe_name):
722
+ confirm = input(f"{YELLOW}Agent [{safe_name}] 已存在,是否覆盖? [y/N]: {RESET}").strip().lower()
723
+ if confirm not in ("y", "yes"):
724
+ print(f"{DIM}已取消。{RESET}")
725
+ return False
726
+
727
+ d = create_agent_dir(safe_name)
728
+ save_agent_code(safe_name, code)
729
+
730
+ # 自动生成简单人设和技能(如果尚不存在)
731
+ from fr_cli.agent.manager import load_persona, load_skills
732
+ if not load_persona(safe_name):
733
+ save_persona(safe_name, f"#{safe_name}\n\n由 AI 对话铸造的 Agent 分身。")
734
+ if not load_skills(safe_name):
735
+ save_skills(safe_name, "## 技能\n\n- 执行自定义 Python 逻辑\n- 入口: run(context, **kwargs)")
736
+
737
+ print(f"{GREEN}✅ Agent [{safe_name}] 铸造完成!{RESET}")
738
+ print(f"{DIM} 路径: {d}{RESET}")
739
+ print(f"{DIM} 运行: /agent_run {safe_name} [参数]{RESET}")
740
+ return False
741
+
742
+
743
+ def _cmd_remote_setup(state, parts):
744
+ from fr_cli.agent.builtins.remote import _setup_wizard
745
+ _setup_wizard(state.lang)
746
+ return False
747
+
748
+
749
+ def _cmd_db_setup(state, parts):
750
+ from fr_cli.agent.builtins.db import _setup_wizard as db_setup
751
+ db_setup(state.lang)
752
+ return False
753
+
754
+
755
+ def _cmd_agent_cron_add(state, parts):
756
+ """为 Agent 分身添加定时任务"""
757
+ from fr_cli.gatekeeper.manager import read_daemon_config, sync_gatekeeper_cron_jobs
758
+ from fr_cli.agent.manager import agent_exists
759
+ arg1 = parts[1] if len(parts) > 1 else "" # agent_name
760
+ arg2 = parts[2] if len(parts) > 2 else "" # interval
761
+ arg3 = parts[3] if len(parts) > 3 else "" # input
762
+ if not arg1 or not arg2:
763
+ print(f"{YELLOW}用法: /agent_cron_add <agent名称> <间隔秒> [输入内容]{RESET}")
764
+ return False
765
+ if not agent_exists(arg1):
766
+ print(f"{RED}Agent [{arg1}] 不存在。{RESET}")
767
+ return False
768
+ try:
769
+ interval = float(arg2)
770
+ if interval < 5:
771
+ raise ValueError
772
+ except ValueError:
773
+ print(f"{RED}间隔秒数需为 >= 5 的数字{RESET}")
774
+ return False
775
+
776
+ cfg = read_daemon_config()
777
+ agent_crons = cfg.get("agent_crons", [])
778
+ # 分配新 ID
779
+ max_id = max([j.get("id", 0) for j in agent_crons] + [0])
780
+ new_job = {
781
+ "id": max_id + 1,
782
+ "agent_name": arg1,
783
+ "interval": interval,
784
+ "agent_input": arg3,
785
+ "cmd": arg1, # 兼容字段
786
+ }
787
+ agent_crons.append(new_job)
788
+ sync_gatekeeper_cron_jobs(agent_crons=agent_crons)
789
+ print(f"{GREEN}✅ Agent 定时任务已添加 (ID: {new_job['id']}){RESET}")
790
+ print(f"{DIM} Agent: {arg1} | 间隔: {interval}秒 | 输入: {arg3 or '(无)'}{RESET}")
791
+
792
+ # 如果 gatekeeper 正在运行,提示热重载将自动生效
793
+ if state.gatekeeper.is_running():
794
+ print(f"{DIM} Gatekeeper 运行中,新任务将在约30秒内自动生效。{RESET}")
795
+ else:
796
+ print(f"{DIM} 提示: Gatekeeper 未运行,任务将在下次 /gatekeeper start 时生效。{RESET}")
797
+ return False
798
+
799
+
800
+ def _cmd_agent_cron_list(state, parts):
801
+ """列出 Agent 分身定时任务"""
802
+ from fr_cli.gatekeeper.manager import read_daemon_config
803
+ cfg = read_daemon_config()
804
+ agent_crons = cfg.get("agent_crons", [])
805
+ if not agent_crons:
806
+ print(f"{YELLOW}暂无 Agent 定时任务。{RESET}")
807
+ print(f"{DIM}用法: /agent_cron_add <agent名称> <间隔秒> [输入内容]{RESET}")
808
+ return False
809
+ print(f"{CYAN}Agent 定时任务列表:{RESET}")
810
+ for j in agent_crons:
811
+ print(f" {GREEN}ID:{j['id']}{RESET} | Agent: {j.get('agent_name', '?')} | {YELLOW}{j['interval']}s{RESET} | 输入: {j.get('agent_input', '') or '(无)'}")
812
+ return False
813
+
814
+
815
+ def _cmd_agent_cron_del(state, parts):
816
+ """删除 Agent 分身定时任务"""
817
+ from fr_cli.gatekeeper.manager import read_daemon_config, sync_gatekeeper_cron_jobs
818
+ arg1 = parts[1] if len(parts) > 1 else ""
819
+ if not arg1 or not arg1.isdigit():
820
+ print(f"{YELLOW}用法: /agent_cron_del <ID>{RESET}")
821
+ return False
822
+ job_id = int(arg1)
823
+ cfg = read_daemon_config()
824
+ agent_crons = cfg.get("agent_crons", [])
825
+ new_crons = [j for j in agent_crons if j.get("id") != job_id]
826
+ if len(new_crons) == len(agent_crons):
827
+ print(f"{RED}未找到 ID 为 {job_id} 的 Agent 定时任务。{RESET}")
828
+ return False
829
+ sync_gatekeeper_cron_jobs(agent_crons=new_crons)
830
+ print(f"{GREEN}✅ Agent 定时任务 ID:{job_id} 已删除。{RESET}")
831
+ if state.gatekeeper.is_running():
832
+ print(f"{DIM} Gatekeeper 运行中,变更将在约30秒内自动生效。{RESET}")
833
+ return False
834
+
835
+
836
+ def _cmd_rag_dir(state, parts):
837
+ arg1 = parts[1] if len(parts) > 1 else ""
838
+ if not arg1:
839
+ return False
840
+ from pathlib import Path as _Path
841
+ p = _Path(arg1)
842
+ if not p.exists():
843
+ print(f"{RED}目录不存在: {arg1}{RESET}")
844
+ else:
845
+ state.cfg["rag_dir"] = str(p.resolve())
846
+ state.save_cfg()
847
+ print(f"{GREEN}✅ 知识库目录已设置: {p.resolve()}{RESET}")
848
+ from fr_cli.agent.builtins.rag import get_rag_manager, RAGWatcherManager
849
+ mgr = get_rag_manager(str(p.resolve()))
850
+ ok, msg = mgr.sync_directory()
851
+ print(f"{GREEN if ok else YELLOW}{msg}{RESET}")
852
+ # 如果独立守护进程未运行,才启动内置 watcher
853
+ watcher = RAGWatcherManager()
854
+ if ok and not watcher.is_running():
855
+ mgr.start_watcher()
856
+ print(f"{DIM}内置后台监控已启动(如需持久化守护,请使用 /rag_watch start){RESET}")
857
+ return False
858
+
859
+
860
+ def _cmd_rag_watch(state, parts):
861
+ """管理 RAG 知识库独立守护进程"""
862
+ from fr_cli.agent.builtins.rag import RAGWatcherManager
863
+ arg1 = parts[1] if len(parts) > 1 else ""
864
+ watcher = RAGWatcherManager()
865
+
866
+ if arg1 == "start":
867
+ kb_dir = parts[2] if len(parts) > 2 else state.cfg.get("rag_dir", "")
868
+ if not kb_dir:
869
+ print(f"{YELLOW}未设置知识库目录,请先使用 /rag_dir <目录> 设置。{RESET}")
870
+ return False
871
+ # 解析可选参数 --interval
872
+ interval = 30
873
+ for i, part in enumerate(parts):
874
+ if part == "--interval" and i + 1 < len(parts):
875
+ try:
876
+ interval = int(parts[i + 1])
877
+ except ValueError:
878
+ pass
879
+ ok, msg = watcher.start(kb_dir, interval=interval)
880
+ color = GREEN if ok else YELLOW
881
+ print(f"{color}{msg}{RESET}")
882
+ if ok:
883
+ print(f"{DIM}日志文件: ~/.fr_cli_rag_watcher.log{RESET}")
884
+ print(f"{DIM}停止命令: /rag_watch stop{RESET}")
885
+
886
+ elif arg1 == "stop":
887
+ ok, msg = watcher.stop()
888
+ color = GREEN if ok else YELLOW
889
+ print(f"{color}{msg}{RESET}")
890
+
891
+ elif arg1 == "status":
892
+ print(f"{CYAN}{watcher.status()}{RESET}")
893
+
894
+ elif arg1 == "log":
895
+ lines = 50
896
+ for i, part in enumerate(parts):
897
+ if part == "--lines" and i + 1 < len(parts):
898
+ try:
899
+ lines = int(parts[i + 1])
900
+ except ValueError:
901
+ pass
902
+ log = watcher.get_log(lines=lines)
903
+ print(f"{DIM}--- RAG 守护进程日志(最后 {lines} 行)---{RESET}")
904
+ print(log)
905
+ print(f"{DIM}--- EOF ---{RESET}")
906
+
907
+ else:
908
+ print(f"{DIM}用法: /rag_watch start [目录] [--interval N] | /rag_watch stop | /rag_watch status | /rag_watch log [--lines N]{RESET}")
909
+ return False
910
+
911
+
912
+ def _cmd_rag_sync(state, parts):
913
+ """手动同步知识库"""
914
+ from fr_cli.agent.builtins.rag import get_rag_manager, RAGWatcherManager
915
+ kb_dir = state.cfg.get("rag_dir", "")
916
+ if not kb_dir:
917
+ print(f"{YELLOW}未设置知识库目录。{RESET}")
918
+ arg1 = parts[1] if len(parts) > 1 else ""
919
+ if arg1:
920
+ from pathlib import Path as _Path
921
+ p = _Path(arg1)
922
+ if p.exists():
923
+ state.cfg["rag_dir"] = str(p.resolve())
924
+ state.save_cfg()
925
+ kb_dir = str(p.resolve())
926
+ else:
927
+ print(f"{RED}目录不存在: {arg1}{RESET}")
928
+ return False
929
+ else:
930
+ return False
931
+
932
+ mgr = get_rag_manager(kb_dir)
933
+ print(f"{CYAN}📚 正在同步知识库...{RESET}")
934
+ ok, msg = mgr.sync_directory()
935
+ color = GREEN if ok else YELLOW
936
+ print(f"{color}{msg}{RESET}")
937
+
938
+ watcher = RAGWatcherManager()
939
+ if watcher.is_running():
940
+ print(f"{DIM}ℹ️ 独立守护进程正在运行,知识库将自动保持同步。{RESET}")
941
+ return False
942
+
943
+
944
+ def _cmd_read_excel(state, parts):
945
+ from fr_cli.weapon.dataframe import read_excel
946
+ arg1 = parts[1] if len(parts) > 1 else ""
947
+ if arg1:
948
+ res, err = read_excel(arg1, lang=state.lang)
949
+ if err:
950
+ print(f"{RED}{err}{RESET}")
951
+ else:
952
+ print(f"{CYAN}{res[:2000]}{RESET}")
953
+ if len(res) > 2000:
954
+ print(f"{DIM}... (共 {len(res)} 字符,使用 AI 对话进行分析){RESET}")
955
+ return False
956
+
957
+
958
+ def _cmd_read_csv(state, parts):
959
+ from fr_cli.weapon.dataframe import read_csv
960
+ arg1 = parts[1] if len(parts) > 1 else ""
961
+ if arg1:
962
+ res, err = read_csv(arg1, lang=state.lang)
963
+ if err:
964
+ print(f"{RED}{err}{RESET}")
965
+ else:
966
+ print(f"{CYAN}{res[:2000]}{RESET}")
967
+ if len(res) > 2000:
968
+ print(f"{DIM}... (共 {len(res)} 字符,使用 AI 对话进行分析){RESET}")
969
+ return False
970
+
971
+
972
+ def _cmd_master(state, parts):
973
+ """切换或查看主控 Agent(MasterAgent)状态"""
974
+ arg1 = parts[1] if len(parts) > 1 else ""
975
+ if arg1.lower() in ("on", "enable", "1"):
976
+ state.master_agent.toggle(True)
977
+ print(f"{GREEN}✅ 主控 Agent 已启用。所有对话将由 MasterAgent 接管处理。{RESET}")
978
+ elif arg1.lower() in ("off", "disable", "0"):
979
+ state.master_agent.toggle(False)
980
+ print(f"{GREEN}✅ 主控 Agent 已禁用。恢复为普通 AI 对话模式。{RESET}")
981
+ elif arg1.lower() == "status":
982
+ st = state.master_agent.status()
983
+ print(f"{CYAN}🧠 主控 Agent 状态:{RESET}")
984
+ print(f" {'启用' if st['enabled'] else '禁用'}")
985
+ print(f" 总交互: {st['total_interactions']} | 成功: {st['success']} | 失败: {st['failure']}")
986
+ if st['evolution_addon']:
987
+ print(f" 进化追加: {st['evolution_addon']}")
988
+ else:
989
+ enabled = state.master_agent.toggle()
990
+ status = "已启用" if enabled else "已禁用"
991
+ print(f"{GREEN}✅ 主控 Agent {status}。{RESET}")
992
+ print(f"{DIM} 用法: /master on | /master off | /master status{RESET}")
993
+ return False
994
+
995
+
996
+
997
+
998
+ def _cmd_mcp_list(state, parts):
999
+ """列出 MCP 服务器和可用工具"""
1000
+ servers = state.mcp.list_servers()
1001
+ if not servers:
1002
+ print(f"{YELLOW}暂无 MCP 服务器配置。{RESET}")
1003
+ print(f"{DIM}用法: /mcp_add <名称> <命令> [参数...]{RESET}")
1004
+ return False
1005
+
1006
+ print(f"{CYAN}📡 MCP 服务器配置 ({len(servers)} 个):{RESET}")
1007
+ for s in servers:
1008
+ status = f"{GREEN}● 启用{RESET}" if s.get("enabled", True) else f"{RED}● 禁用{RESET}"
1009
+ print(f"\n {CYAN}[{s['name']}]{RESET} {status}")
1010
+ print(f" 传输: {s.get('transport', 'stdio')}")
1011
+ print(f" 命令: {s.get('command', 'N/A')} {' '.join(s.get('args', []))}")
1012
+ if s.get('cwd'):
1013
+ print(f" 工作目录: {s['cwd']}")
1014
+
1015
+ # 尝试获取工具列表
1016
+ print(f"\n{CYAN}🔧 可用法宝:{RESET}")
1017
+ tools = state.mcp.list_all_tools()
1018
+ if not tools:
1019
+ print(f" {DIM}暂无可用法宝(服务器可能未连接或已禁用){RESET}")
1020
+ else:
1021
+ for t in tools:
1022
+ print(f" - {GREEN}{t['name']}{RESET}: {t['description']}")
1023
+ print(f" 所属服务器: {t['server']}")
1024
+ return False
1025
+
1026
+
1027
+ def _cmd_mcp_add(state, parts):
1028
+ """添加 MCP 服务器: /mcp_add <名称> <命令> [参数...]"""
1029
+ if len(parts) < 3:
1030
+ print(f"{YELLOW}用法: /mcp_add <名称> <命令> [参数...]{RESET}")
1031
+ print(f"{DIM}示例: /mcp_add filesystem npx -y @modelcontextprotocol/server-filesystem /tmp{RESET}")
1032
+ return False
1033
+ name = parts[1]
1034
+ command = parts[2]
1035
+ args = parts[3:] if len(parts) > 3 else []
1036
+ ok, err = state.mcp.add_server(name, command, args)
1037
+ if ok:
1038
+ print(f"{GREEN}✅ MCP 服务器 [{name}] 已添加。{RESET}")
1039
+ print(f"{DIM} 命令: {command} {' '.join(args)}{RESET}")
1040
+ print(f"{DIM} 使用 /mcp_refresh 或重新启动以加载其法宝。{RESET}")
1041
+ else:
1042
+ print(f"{RED}❌ 添加失败: {err}{RESET}")
1043
+ return False
1044
+
1045
+
1046
+ def _cmd_mcp_del(state, parts):
1047
+ """删除 MCP 服务器: /mcp_del <名称>"""
1048
+ if len(parts) < 2:
1049
+ print(f"{YELLOW}用法: /mcp_del <名称>{RESET}")
1050
+ return False
1051
+ name = parts[1]
1052
+ ok, err = state.mcp.remove_server(name)
1053
+ if ok:
1054
+ print(f"{GREEN}✅ MCP 服务器 [{name}] 已删除。{RESET}")
1055
+ else:
1056
+ print(f"{RED}❌ 删除失败: {err}{RESET}")
1057
+ return False
1058
+
1059
+
1060
+ def _cmd_mcp_enable(state, parts):
1061
+ """启用 MCP 服务器: /mcp_enable <名称>"""
1062
+ if len(parts) < 2:
1063
+ print(f"{YELLOW}用法: /mcp_enable <名称>{RESET}")
1064
+ return False
1065
+ name = parts[1]
1066
+ ok, err = state.mcp.toggle_server(name, True)
1067
+ if ok:
1068
+ print(f"{GREEN}✅ MCP 服务器 [{name}] 已启用。{RESET}")
1069
+ else:
1070
+ print(f"{RED}❌ 操作失败: {err}{RESET}")
1071
+ return False
1072
+
1073
+
1074
+ def _cmd_mcp_disable(state, parts):
1075
+ """禁用 MCP 服务器: /mcp_disable <名称>"""
1076
+ if len(parts) < 2:
1077
+ print(f"{YELLOW}用法: /mcp_disable <名称>{RESET}")
1078
+ return False
1079
+ name = parts[1]
1080
+ ok, err = state.mcp.toggle_server(name, False)
1081
+ if ok:
1082
+ print(f"{GREEN}✅ MCP 服务器 [{name}] 已禁用。{RESET}")
1083
+ else:
1084
+ print(f"{RED}❌ 操作失败: {err}{RESET}")
1085
+ return False
1086
+
1087
+
1088
+ def _cmd_mcp_refresh(state, parts):
1089
+ """刷新 MCP 服务器法宝列表"""
1090
+ print(f"{CYAN}🔄 正在刷新 MCP 法宝列表...{RESET}")
1091
+ tools = state.mcp.list_all_tools()
1092
+ if tools:
1093
+ print(f"{GREEN}✅ 发现 {len(tools)} 个法宝:{RESET}")
1094
+ for t in tools:
1095
+ print(f" - {t['name']} ({t['server']}): {t['description']}")
1096
+ else:
1097
+ print(f"{YELLOW}⚠️ 未发现可用法宝。请检查服务器配置和连接状态。{RESET}")
1098
+ return False