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.
- fr_cli/README.md +148 -0
- fr_cli/WEAPON.MD +186 -0
- fr_cli/__init__.py +4 -0
- fr_cli/addon/plugin.py +69 -0
- fr_cli/agent/__init__.py +9 -0
- fr_cli/agent/builtins/__init__.py +4 -0
- fr_cli/agent/builtins/_utils.py +48 -0
- fr_cli/agent/builtins/db.py +269 -0
- fr_cli/agent/builtins/local.py +105 -0
- fr_cli/agent/builtins/rag.py +652 -0
- fr_cli/agent/builtins/rag_watcher_daemon.py +156 -0
- fr_cli/agent/builtins/remote.py +214 -0
- fr_cli/agent/builtins/spider.py +247 -0
- fr_cli/agent/client.py +164 -0
- fr_cli/agent/executor.py +86 -0
- fr_cli/agent/generator.py +104 -0
- fr_cli/agent/manager.py +193 -0
- fr_cli/agent/master.py +604 -0
- fr_cli/agent/master_prompt.py +118 -0
- fr_cli/agent/remote.py +70 -0
- fr_cli/agent/server.py +279 -0
- fr_cli/agent/workflow.py +164 -0
- fr_cli/breakthrough/update.py +154 -0
- fr_cli/command/__init__.py +4 -0
- fr_cli/command/executor.py +276 -0
- fr_cli/command/registry.py +1034 -0
- fr_cli/command/security.py +30 -0
- fr_cli/conf/config.py +126 -0
- fr_cli/conf/wizard.py +172 -0
- fr_cli/core/chat.py +280 -0
- fr_cli/core/core.py +111 -0
- fr_cli/core/intent.py +129 -0
- fr_cli/core/recommender.py +71 -0
- fr_cli/core/stream.py +83 -0
- fr_cli/core/sysmon.py +117 -0
- fr_cli/core/thinking.py +215 -0
- fr_cli/gatekeeper/__init__.py +7 -0
- fr_cli/gatekeeper/daemon.py +216 -0
- fr_cli/gatekeeper/manager.py +218 -0
- fr_cli/lang/i18n.py +827 -0
- fr_cli/main.py +329 -0
- fr_cli/memory/context.py +119 -0
- fr_cli/memory/history.py +96 -0
- fr_cli/memory/session.py +134 -0
- fr_cli/repl/__init__.py +0 -0
- fr_cli/repl/commands.py +1098 -0
- fr_cli/security/security.py +46 -0
- fr_cli/ui/ui.py +116 -0
- fr_cli/weapon/cron.py +217 -0
- fr_cli/weapon/dataframe.py +97 -0
- fr_cli/weapon/disk.py +141 -0
- fr_cli/weapon/fs.py +206 -0
- fr_cli/weapon/launcher.py +249 -0
- fr_cli/weapon/loader.py +98 -0
- fr_cli/weapon/mail.py +227 -0
- fr_cli/weapon/mcp.py +204 -0
- fr_cli/weapon/vision.py +74 -0
- fr_cli/weapon/web.py +88 -0
- fr_cli-2.1.0.dist-info/METADATA +227 -0
- fr_cli-2.1.0.dist-info/RECORD +64 -0
- fr_cli-2.1.0.dist-info/WHEEL +5 -0
- fr_cli-2.1.0.dist-info/entry_points.txt +2 -0
- fr_cli-2.1.0.dist-info/licenses/LICENSE +21 -0
- fr_cli-2.1.0.dist-info/top_level.txt +1 -0
fr_cli/repl/commands.py
ADDED
|
@@ -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
|