fr-cli 2.4.1__tar.gz → 2.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fr_cli-2.4.1 → fr_cli-2.4.2}/MANIFEST.in +1 -0
- {fr_cli-2.4.1/fr_cli.egg-info → fr_cli-2.4.2}/PKG-INFO +24 -2
- {fr_cli-2.4.1 → fr_cli-2.4.2}/README.md +23 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/WEAPON.MD +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/__init__.py +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/addon/plugin.py +4 -4
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/db.py +7 -6
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/powerful_agent_template.py +14 -7
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/remote.py +11 -6
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/generator.py +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/hermes.py +24 -12
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/image_and_parallel.py +36 -43
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/remote.py +10 -25
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/config.py +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/cron.py +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/disk.py +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/fs.py +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/mail.py +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/session.py +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/web.py +1 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/conf/config.py +65 -12
- fr_cli-2.4.2/fr_cli/conf/default_models.yaml +298 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/core.py +4 -9
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/llm.py +12 -6
- fr_cli-2.4.2/fr_cli/core/model_factory.py +140 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/optimizations.py +10 -3
- fr_cli-2.4.2/fr_cli/lang/i18n.py +25 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/lang/translations/en.py +17 -8
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/lang/translations/zh.py +14 -8
- fr_cli-2.4.2/fr_cli/main.py +363 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/memory/context.py +29 -40
- fr_cli-2.4.2/fr_cli/repl/batch.py +80 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/bootstrap.py +9 -3
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/_common.py +5 -3
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/base.py +5 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/config.py +6 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/session.py +9 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/banner.py +4 -4
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/buddha.py +5 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/prompt.py +20 -4
- fr_cli-2.4.2/fr_cli/ui/ui.py +110 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/web_config.py +21 -20
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/fs.py +109 -6
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/mcp.py +49 -27
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/vision.py +8 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2/fr_cli.egg-info}/PKG-INFO +24 -2
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/SOURCES.txt +9 -1
- {fr_cli-2.4.1 → fr_cli-2.4.2}/pyproject.toml +4 -1
- fr_cli-2.4.2/tests/test_batch_mode.py +123 -0
- fr_cli-2.4.2/tests/test_help_update.py +62 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_integration_real.py +1 -1
- fr_cli-2.4.2/tests/test_no_color_prompt_toolkit.py +141 -0
- fr_cli-2.4.2/tests/test_plugin_exec.py +76 -0
- fr_cli-2.4.2/tests/test_prompt_completion.py +67 -0
- fr_cli-2.4.2/tests/test_vfs_diff.py +79 -0
- fr_cli-2.4.1/fr_cli/core/model_factory.py +0 -326
- fr_cli-2.4.1/fr_cli/lang/i18n.py +0 -8
- fr_cli-2.4.1/fr_cli/main.py +0 -122
- fr_cli-2.4.1/fr_cli/ui/ui.py +0 -64
- {fr_cli-2.4.1 → fr_cli-2.4.2}/LICENSE +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/README.md +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/a2a.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/acp.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/artifact_detector.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/_utils.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/local.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/rag.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/rag_watcher_daemon.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/spider.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/client.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/coding_helper.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/context_files.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/executor.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/gateway.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/hermes_daemon.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/manager.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/master.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/master_prompt.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/personality.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/plugin_system.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/server.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/shell_mode.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/skills.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/engine.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/executor.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/manager.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/models.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/monitor.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/tools.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/assets/splash.jpeg +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/breakthrough/update.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/executor.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/agent.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/app.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/dataframe.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/mcp.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/other.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/system.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/vision.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/agent_data_mcp.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/fs.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/image.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/main_routes.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/services.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/session_config.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/web.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registry.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/security.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/conf/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/conf/paths.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/conf/wizard.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/cache.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/chat.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/errors.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/intent.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/preferences.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/project.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/recommender.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/stream.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/sysmon.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/thinking.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/gatekeeper/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/gatekeeper/daemon.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/gatekeeper/manager.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/lang/translations/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/memory/history.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/memory/session.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/actions.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/__init__.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/agent.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/cron.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/dataframe.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/dev.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/fs.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/mcp.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/rag.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/remote_agent.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/system.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/queue.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/quick_actions.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/router.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/scenarios.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/security/security.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/markdown.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/splash.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/cron.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/dataframe.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/disk.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/launcher.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/loader.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/mail.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/web.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/dependency_links.txt +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/entry_points.txt +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/requires.txt +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/top_level.txt +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/setup.cfg +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_a2a_and_providers.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_master_prompt_fix.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_model_config.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_new_features.py +0 -0
- {fr_cli-2.4.1 → fr_cli-2.4.2}/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.4.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: 凡人打字机 - 支持多模型(Zhipu/DeepSeek/Kimi/Qwen/StepFun/MiniMax/Spark/Doubao/MiMo)的终极全能终端工具
|
|
5
5
|
Author: FANREN CLI Author
|
|
6
6
|
License-Expression: MIT
|
|
@@ -180,6 +180,28 @@ fr-cli
|
|
|
180
180
|
/exit 退出
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
+
### 🖥️ 非交互 / 批处理模式
|
|
184
|
+
|
|
185
|
+
fr-cli 支持在不进入 REPL 的情况下执行单次命令或单次 AI 对话,适用于脚本、cron、管道等场景:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# 执行一条 slash 命令后退出
|
|
189
|
+
fr-cli -c "/model current"
|
|
190
|
+
fr-cli -c "/ls"
|
|
191
|
+
|
|
192
|
+
# 向 AI 提问后退出
|
|
193
|
+
fr-cli "请总结 README.md"
|
|
194
|
+
fr-cli -p "Python 如何读取 JSON?"
|
|
195
|
+
|
|
196
|
+
# 从文件或标准输入读取提示词
|
|
197
|
+
cat article.txt | fr-cli -s
|
|
198
|
+
fr-cli -f prompt.txt
|
|
199
|
+
|
|
200
|
+
# 静默模式(跳过启动 banner,只输出核心结果)
|
|
201
|
+
fr-cli -q -c "/model current"
|
|
202
|
+
fr-cli -q -p "1+1等于几"
|
|
203
|
+
```
|
|
204
|
+
|
|
183
205
|
### 🤖 AI 模型配置
|
|
184
206
|
|
|
185
207
|
```bash
|
|
@@ -390,7 +412,7 @@ fr_cli/
|
|
|
390
412
|
|
|
391
413
|
## 📂 配置目录
|
|
392
414
|
|
|
393
|
-
> 配置统一在 `~/.fr_cli/`
|
|
415
|
+
> 配置统一在 `~/.fr_cli/` 目录下,旧路径(如 `~/.zhipu_cli_config.json`)会在首次启动时自动迁移到新路径。
|
|
394
416
|
|
|
395
417
|
| 路径 | 说明 |
|
|
396
418
|
|------|------|
|
|
@@ -106,6 +106,28 @@ fr-cli
|
|
|
106
106
|
/exit 退出
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
### 🖥️ 非交互 / 批处理模式
|
|
110
|
+
|
|
111
|
+
fr-cli 支持在不进入 REPL 的情况下执行单次命令或单次 AI 对话,适用于脚本、cron、管道等场景:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# 执行一条 slash 命令后退出
|
|
115
|
+
fr-cli -c "/model current"
|
|
116
|
+
fr-cli -c "/ls"
|
|
117
|
+
|
|
118
|
+
# 向 AI 提问后退出
|
|
119
|
+
fr-cli "请总结 README.md"
|
|
120
|
+
fr-cli -p "Python 如何读取 JSON?"
|
|
121
|
+
|
|
122
|
+
# 从文件或标准输入读取提示词
|
|
123
|
+
cat article.txt | fr-cli -s
|
|
124
|
+
fr-cli -f prompt.txt
|
|
125
|
+
|
|
126
|
+
# 静默模式(跳过启动 banner,只输出核心结果)
|
|
127
|
+
fr-cli -q -c "/model current"
|
|
128
|
+
fr-cli -q -p "1+1等于几"
|
|
129
|
+
```
|
|
130
|
+
|
|
109
131
|
### 🤖 AI 模型配置
|
|
110
132
|
|
|
111
133
|
```bash
|
|
@@ -316,7 +338,7 @@ fr_cli/
|
|
|
316
338
|
|
|
317
339
|
## 📂 配置目录
|
|
318
340
|
|
|
319
|
-
> 配置统一在 `~/.fr_cli/`
|
|
341
|
+
> 配置统一在 `~/.fr_cli/` 目录下,旧路径(如 `~/.zhipu_cli_config.json`)会在首次启动时自动迁移到新路径。
|
|
320
342
|
|
|
321
343
|
| 路径 | 说明 |
|
|
322
344
|
|------|------|
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
- AI 调用: `mcp_call({"server": "服务器名", "tool": "工具名", "arguments": {...}})`
|
|
177
177
|
- REPL 管理: `/mcp_list`, `/mcp_add`, `/mcp_del`, `/mcp_enable`, `/mcp_disable`, `/mcp_refresh`
|
|
178
178
|
- **触发关键词**: mcp, 外部工具, 外部神通, 调用工具, use tool, invoke tool
|
|
179
|
-
- **配置持久化**: `~/.
|
|
179
|
+
- **配置持久化**: `~/.fr_cli/config.json` 的 `mcp.servers` 字段
|
|
180
180
|
- **示例**:
|
|
181
181
|
```
|
|
182
182
|
/mcp_add fs npx -y @modelcontextprotocol/server-filesystem /tmp
|
|
@@ -38,19 +38,19 @@ def exec_plugin(name, path, args, lang):
|
|
|
38
38
|
print(f"{RED}❌ 非法插件名: {name}{RESET}")
|
|
39
39
|
return
|
|
40
40
|
|
|
41
|
-
import json
|
|
41
|
+
import json
|
|
42
42
|
# 使用 json.dumps 安全序列化参数,防止字符串逃逸注入
|
|
43
43
|
safe_args = json.dumps(args)
|
|
44
44
|
runner_code = f"""
|
|
45
45
|
import sys, json, runpy
|
|
46
|
-
sys.path.insert(0, {
|
|
47
|
-
mod = runpy.run_module({
|
|
46
|
+
sys.path.insert(0, {json.dumps(str(PLUGIN_DIR), ensure_ascii=False)})
|
|
47
|
+
mod = runpy.run_module({json.dumps(name, ensure_ascii=False)}, run_name='__main__')
|
|
48
48
|
run_fn = mod.get('run')
|
|
49
49
|
if run_fn is None:
|
|
50
50
|
print("Error: 插件缺少 run 函数", file=sys.stderr)
|
|
51
51
|
else:
|
|
52
52
|
try:
|
|
53
|
-
print(run_fn(json.loads({safe_args})))
|
|
53
|
+
print(run_fn(json.loads({json.dumps(safe_args, ensure_ascii=False)})))
|
|
54
54
|
except Exception as e:
|
|
55
55
|
print(f"Error: {{e}}", file=sys.stderr)
|
|
56
56
|
"""
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
@db 内置 Agent —— 数据库智能助手
|
|
3
3
|
支持 MySQL / PostgreSQL / SQL Server / Oracle 的 Schema 分析和 SQL 生成。
|
|
4
|
+
|
|
5
|
+
数据库连接配置统一收敛到 ~/.fr_cli/config.json 的 databases 命名空间。
|
|
4
6
|
"""
|
|
5
|
-
from
|
|
7
|
+
from fr_cli.conf.config import load_namespace, save_namespace
|
|
6
8
|
from fr_cli.conf.paths import DATABASE_FILE
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
_NS_KEY = "databases"
|
|
11
|
+
DB_CFG_PATH = DATABASE_FILE # 保留用于一次性迁移
|
|
9
12
|
|
|
10
13
|
DB_SYS_PROMPT = """你是一个数据库专家。请根据以下数据库 Schema 信息和用户需求,生成最合适的 SQL 语句。
|
|
11
14
|
|
|
@@ -25,13 +28,11 @@ Schema 信息:
|
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
def _load_dbs():
|
|
28
|
-
|
|
29
|
-
return load_json_config(DB_CFG_PATH)
|
|
31
|
+
return load_namespace(_NS_KEY, default={}, old_path=DB_CFG_PATH)
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
def _save_dbs(dbs):
|
|
33
|
-
|
|
34
|
-
save_json_config(DB_CFG_PATH, dbs)
|
|
35
|
+
save_namespace(_NS_KEY, dbs)
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def _connect(db_cfg):
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
示例配置:
|
|
19
19
|
{
|
|
20
|
-
"provider": "
|
|
21
|
-
"model": "
|
|
20
|
+
"provider": "<your-provider>",
|
|
21
|
+
"model": "<your-model>",
|
|
22
22
|
"key": "your-api-key"
|
|
23
23
|
}
|
|
24
24
|
"""
|
|
@@ -641,10 +641,17 @@ class PowerfulAgent:
|
|
|
641
641
|
self.a2a = A2AIntegration(name, context.get("state"))
|
|
642
642
|
self.evolution = EvolutionSystem(name)
|
|
643
643
|
|
|
644
|
-
# 从 context
|
|
644
|
+
# 从 context 获取配置;未指定时回退到 ModelFactory 首个 provider
|
|
645
645
|
self.client = context.get("client")
|
|
646
|
-
self.provider = context.get("provider"
|
|
647
|
-
self.model = context.get("model"
|
|
646
|
+
self.provider = context.get("provider")
|
|
647
|
+
self.model = context.get("model")
|
|
648
|
+
if not self.provider:
|
|
649
|
+
from fr_cli.core.model_factory import get_model_factory
|
|
650
|
+
factory = get_model_factory()
|
|
651
|
+
providers = factory.list_providers()
|
|
652
|
+
if providers:
|
|
653
|
+
self.provider = providers[0]
|
|
654
|
+
self.model = factory.get_model_name(self.provider)
|
|
648
655
|
self.persona = context.get("persona", "")
|
|
649
656
|
self.skills = context.get("skills", "")
|
|
650
657
|
self.lang = context.get("lang", "zh")
|
|
@@ -900,8 +907,8 @@ if __name__ == "__main__":
|
|
|
900
907
|
# 示例:创建并运行 Agent
|
|
901
908
|
example_context = {
|
|
902
909
|
"client": None, # 在实际运行时由 fr-cli 提供
|
|
903
|
-
"provider": "
|
|
904
|
-
"model": "
|
|
910
|
+
"provider": "<your-provider>",
|
|
911
|
+
"model": "<your-model>",
|
|
905
912
|
"persona": "你是一个专业的代码审查员,擅长发现代码中的问题和优化点。",
|
|
906
913
|
"skills": "代码审查、性能优化、重构建议",
|
|
907
914
|
"lang": "zh",
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"""
|
|
2
2
|
@remote 内置 Agent —— 远程 SSH 操作助手
|
|
3
3
|
支持多机配置、配置向导、AI 生成远程命令。
|
|
4
|
+
|
|
5
|
+
远程主机配置统一收敛到 ~/.fr_cli/config.json 的 remote.hosts 命名空间。
|
|
6
|
+
旧文件 ~/.fr_cli/remote/hosts.json 会在首次加载时一次性迁移。
|
|
4
7
|
"""
|
|
5
|
-
from
|
|
8
|
+
from fr_cli.conf.config import load_namespace, save_namespace
|
|
6
9
|
from fr_cli.conf.paths import REMOTE_HOSTS_FILE
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
_NS_KEY = "remote"
|
|
12
|
+
REMOTE_CFG_PATH = REMOTE_HOSTS_FILE # 保留用于一次性迁移
|
|
9
13
|
|
|
10
14
|
REMOTE_SYS_PROMPT = """你是一个远程系统命令专家。请根据目标主机的操作系统类型和用户需求,生成最合适的远程命令。
|
|
11
15
|
|
|
@@ -21,13 +25,14 @@ REMOTE_SYS_PROMPT = """你是一个远程系统命令专家。请根据目标主
|
|
|
21
25
|
|
|
22
26
|
|
|
23
27
|
def _load_hosts():
|
|
24
|
-
|
|
25
|
-
return
|
|
28
|
+
ns = load_namespace(_NS_KEY, default={"hosts": {}}, old_path=REMOTE_CFG_PATH)
|
|
29
|
+
return ns.get("hosts", {})
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def _save_hosts(hosts):
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
ns = load_namespace(_NS_KEY, default={"hosts": {}})
|
|
34
|
+
ns["hosts"] = hosts
|
|
35
|
+
save_namespace(_NS_KEY, ns)
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
def list_hosts():
|
|
@@ -34,7 +34,7 @@ Agent 名称: {name}
|
|
|
34
34
|
- 'memory': str — 记忆文本
|
|
35
35
|
- 'skills': str — 技能文本
|
|
36
36
|
- 'client': LLM 客户端实例(已根据 Agent 专属配置或全局默认初始化)
|
|
37
|
-
- 'provider': str — 当前使用的提供商/提供商 ID
|
|
37
|
+
- 'provider': str — 当前使用的提供商/提供商 ID
|
|
38
38
|
- 'model': str — 模型名称
|
|
39
39
|
- 'lang': str — 语言代码('zh' 或 'en')
|
|
40
40
|
- 'executor': CommandExecutor 实例(可使用 invoke_tool/execute 调用工具)
|
|
@@ -205,19 +205,31 @@ class ConfigManager:
|
|
|
205
205
|
self._load_config_file()
|
|
206
206
|
|
|
207
207
|
def _load_env(self):
|
|
208
|
-
"""从环境变量加载
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
"""从环境变量加载 API Key。
|
|
209
|
+
|
|
210
|
+
provider → env_key 映射优先从 ModelFactory 读取,避免硬编码。
|
|
211
|
+
对于未在工厂中声明的厂商,保留少量兜底映射。
|
|
212
|
+
"""
|
|
213
|
+
# 兜底映射(仅覆盖 factory 中没有的厂商)
|
|
214
|
+
fallback_env_map = {
|
|
215
|
+
"ANTHROPIC_API_KEY": "anthropic",
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
env_to_provider = dict(fallback_env_map)
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
from fr_cli.core.model_factory import get_model_factory
|
|
222
|
+
factory = get_model_factory()
|
|
223
|
+
for provider_id in factory.list_providers():
|
|
224
|
+
cfg = factory.get_config(provider_id)
|
|
225
|
+
env_key = cfg.get("env_key")
|
|
226
|
+
if env_key:
|
|
227
|
+
env_to_provider[env_key] = provider_id
|
|
228
|
+
except Exception:
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
for key, provider in env_to_provider.items():
|
|
211
232
|
if key in os.environ:
|
|
212
|
-
provider = key.lower().replace("_api_key", "")
|
|
213
|
-
if "zhipu" in provider:
|
|
214
|
-
provider = "zhipu"
|
|
215
|
-
elif "moonshot" in provider or "kimi" in provider:
|
|
216
|
-
provider = "kimi"
|
|
217
|
-
elif "deepseek" in provider:
|
|
218
|
-
provider = "deepseek"
|
|
219
|
-
elif "qwen" in provider:
|
|
220
|
-
provider = "qwen"
|
|
221
233
|
self.config.setdefault("providers", {})
|
|
222
234
|
self.config["providers"].setdefault(provider, {})
|
|
223
235
|
self.config["providers"][provider]["key"] = os.environ[key]
|
|
@@ -62,46 +62,32 @@ class ImageModelConfig:
|
|
|
62
62
|
self._register_builtin_providers()
|
|
63
63
|
|
|
64
64
|
def _register_builtin_providers(self):
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
api_key="",
|
|
92
|
-
base_url="https://dashscope.aliyuncs.com/api/v2",
|
|
93
|
-
size="1024x1024"
|
|
94
|
-
))
|
|
95
|
-
|
|
96
|
-
# StepFun 图片
|
|
97
|
-
self.register_provider(ImageProvider(
|
|
98
|
-
name="StepFun 图片",
|
|
99
|
-
provider_id="stepfun",
|
|
100
|
-
model="step-1-image",
|
|
101
|
-
api_key="",
|
|
102
|
-
base_url="https://api.stepfun.com/v1",
|
|
103
|
-
size="1024x1024"
|
|
104
|
-
))
|
|
65
|
+
"""从 ModelFactory 注册内置图片提供商,避免硬编码 model/base_url"""
|
|
66
|
+
from fr_cli.core.model_factory import get_model_factory
|
|
67
|
+
|
|
68
|
+
factory = get_model_factory()
|
|
69
|
+
image_provider_map = [
|
|
70
|
+
("智谱 CogView-4", "zhipu"),
|
|
71
|
+
("MiniMax 图片", "minimax"),
|
|
72
|
+
("通义万相", "qwen"),
|
|
73
|
+
("StepFun 图片", "stepfun"),
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
for name, provider_id in image_provider_map:
|
|
77
|
+
cfg = factory.get_config(provider_id)
|
|
78
|
+
if not cfg:
|
|
79
|
+
continue
|
|
80
|
+
image_model = cfg.get("image_model")
|
|
81
|
+
if not image_model:
|
|
82
|
+
continue
|
|
83
|
+
self.register_provider(ImageProvider(
|
|
84
|
+
name=name,
|
|
85
|
+
provider_id=provider_id,
|
|
86
|
+
model=image_model,
|
|
87
|
+
api_key="",
|
|
88
|
+
base_url=cfg.get("base_url"),
|
|
89
|
+
size="1024x1024"
|
|
90
|
+
))
|
|
105
91
|
|
|
106
92
|
def register_provider(self, provider: ImageProvider):
|
|
107
93
|
"""注册图片提供商"""
|
|
@@ -251,7 +237,7 @@ class ImageGenerator:
|
|
|
251
237
|
client = ZhipuAI(api_key=self.provider.api_key or os.getenv("ZHIPU_API_KEY"))
|
|
252
238
|
|
|
253
239
|
response = client.images.generation(
|
|
254
|
-
model=
|
|
240
|
+
model=self.provider.model,
|
|
255
241
|
prompt=prompt,
|
|
256
242
|
size=size,
|
|
257
243
|
quality=quality
|
|
@@ -276,7 +262,7 @@ class ImageGenerator:
|
|
|
276
262
|
)
|
|
277
263
|
|
|
278
264
|
response = client.images.generate(
|
|
279
|
-
model=
|
|
265
|
+
model=self.provider.model,
|
|
280
266
|
prompt=prompt,
|
|
281
267
|
size=size
|
|
282
268
|
)
|
|
@@ -298,7 +284,7 @@ class ImageGenerator:
|
|
|
298
284
|
url=f"{self.provider.base_url}/services/aigc/text-to-image/imageCreation",
|
|
299
285
|
headers={"Authorization": f"Bearer {self.provider.api_key or os.getenv('DASHSCOPE_API_KEY')}"},
|
|
300
286
|
json={
|
|
301
|
-
"model":
|
|
287
|
+
"model": self.provider.model,
|
|
302
288
|
"input": {"prompt": prompt},
|
|
303
289
|
"parameters": {"size": size}
|
|
304
290
|
},
|
|
@@ -424,6 +410,13 @@ class TerminalImageDisplay:
|
|
|
424
410
|
@staticmethod
|
|
425
411
|
def _detect_method() -> str:
|
|
426
412
|
"""检测终端支持的显示方法"""
|
|
413
|
+
from fr_cli.ui.ui import _NO_COLOR
|
|
414
|
+
|
|
415
|
+
# NO_COLOR / prompt_toolkit patch_stdout 环境下,原始转义序列会显示为乱码,
|
|
416
|
+
# 强制回退到最安全的 ASCII 字符画。
|
|
417
|
+
if _NO_COLOR:
|
|
418
|
+
return "ascii"
|
|
419
|
+
|
|
427
420
|
term = os.environ.get("TERM_PROGRAM", "")
|
|
428
421
|
|
|
429
422
|
if term == "iTerm.app":
|
|
@@ -1,40 +1,25 @@
|
|
|
1
1
|
"""
|
|
2
2
|
远程 Agent 管理 —— 配置其他用户电脑中已启用 API 的 fr-cli Agent
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
{
|
|
7
|
-
"agent_name": {
|
|
8
|
-
"host": "192.168.1.100",
|
|
9
|
-
"port": 8080,
|
|
10
|
-
"token": "xxx",
|
|
11
|
-
"description": "远程数据分析助手"
|
|
12
|
-
}
|
|
13
|
-
}
|
|
4
|
+
配置统一收敛到 ~/.fr_cli/config.json 的 remote.agents 命名空间。
|
|
5
|
+
旧文件 ~/.fr_cli/remote/agents.json 会在首次加载时一次性迁移。
|
|
14
6
|
"""
|
|
15
|
-
import
|
|
7
|
+
from fr_cli.conf.config import load_namespace, save_namespace
|
|
16
8
|
from fr_cli.conf.paths import REMOTE_AGENTS_FILE
|
|
17
|
-
from pathlib import Path
|
|
18
9
|
|
|
19
|
-
|
|
10
|
+
_NS_KEY = "remote"
|
|
11
|
+
REMOTE_AGENTS_FILE = REMOTE_AGENTS_FILE # 保留用于一次性迁移
|
|
20
12
|
|
|
21
13
|
|
|
22
14
|
def _load_remote_agents():
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
try:
|
|
26
|
-
with open(REMOTE_AGENTS_FILE, "r", encoding="utf-8") as f:
|
|
27
|
-
return json.load(f)
|
|
28
|
-
except Exception:
|
|
29
|
-
return {}
|
|
15
|
+
ns = load_namespace(_NS_KEY, default={"agents": {}}, old_path=REMOTE_AGENTS_FILE)
|
|
16
|
+
return ns.get("agents", {})
|
|
30
17
|
|
|
31
18
|
|
|
32
19
|
def _save_remote_agents(data):
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
except Exception:
|
|
37
|
-
pass
|
|
20
|
+
ns = load_namespace(_NS_KEY, default={"agents": {}})
|
|
21
|
+
ns["agents"] = data
|
|
22
|
+
save_namespace(_NS_KEY, ns)
|
|
38
23
|
|
|
39
24
|
|
|
40
25
|
def add_remote_agent(name, host, port, token, description=""):
|
|
@@ -22,6 +22,7 @@ DEFAULT_LIMIT = 20000
|
|
|
22
22
|
def _default_config():
|
|
23
23
|
"""返回默认配置字典 —— provider/model 不再硬编码,由用户显式配置"""
|
|
24
24
|
return {
|
|
25
|
+
"version": 2,
|
|
25
26
|
"key": "",
|
|
26
27
|
# 注意:默认不设置 provider 和 model,由用户通过 /model config 显式配置
|
|
27
28
|
# 未配置时状态栏显示'未配置'
|
|
@@ -42,6 +43,24 @@ def _default_config():
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
def _upgrade_schema(cfg):
|
|
47
|
+
"""把旧版配置升级到当前 schema(幂等执行)"""
|
|
48
|
+
version = cfg.get("version", 1)
|
|
49
|
+
if version >= 2:
|
|
50
|
+
return cfg
|
|
51
|
+
|
|
52
|
+
# v1 -> v2: 将顶层 model 同步到当前 provider 的专属配置中
|
|
53
|
+
provider = cfg.get("provider")
|
|
54
|
+
if provider and cfg.get("model"):
|
|
55
|
+
providers = cfg.setdefault("providers", {})
|
|
56
|
+
pcfg = providers.setdefault(provider, {})
|
|
57
|
+
if not pcfg.get("model"):
|
|
58
|
+
pcfg["model"] = cfg["model"]
|
|
59
|
+
|
|
60
|
+
cfg["version"] = 2
|
|
61
|
+
return cfg
|
|
62
|
+
|
|
63
|
+
|
|
45
64
|
def load_config():
|
|
46
65
|
"""加载配置,如果缺失或损坏则返回带默认值的安全字典
|
|
47
66
|
|
|
@@ -74,19 +93,15 @@ def load_config():
|
|
|
74
93
|
print(f"{YELLOW}⚠️ 使用默认配置,请重新设置{RESET}")
|
|
75
94
|
return d
|
|
76
95
|
|
|
96
|
+
# schema 升级与向后兼容迁移(必须在补齐默认字段之前执行,
|
|
97
|
+
# 否则默认值中的 version 会提前写入,导致升级逻辑被跳过)
|
|
98
|
+
c = _upgrade_schema(c)
|
|
99
|
+
|
|
77
100
|
# 补齐缺失字段(跳过 provider 和 model,由用户显式配置)
|
|
78
101
|
for k, v in d.items():
|
|
79
102
|
if k not in c and k not in ("provider", "model"):
|
|
80
103
|
c[k] = v
|
|
81
104
|
|
|
82
|
-
# 向后兼容迁移:将顶层 model 同步到当前 provider 的专属配置中
|
|
83
|
-
provider = c.get("provider", "zhipu")
|
|
84
|
-
providers_cfg = c.setdefault("providers", {})
|
|
85
|
-
pcfg = providers_cfg.setdefault(provider, {})
|
|
86
|
-
if c.get("model") and not pcfg.get("model"):
|
|
87
|
-
pcfg["model"] = c["model"]
|
|
88
|
-
c["providers"] = providers_cfg
|
|
89
|
-
|
|
90
105
|
return c
|
|
91
106
|
|
|
92
107
|
|
|
@@ -125,6 +140,46 @@ class ConfigError(Exception):
|
|
|
125
140
|
pass
|
|
126
141
|
|
|
127
142
|
|
|
143
|
+
def load_namespace(key, default=None, old_path=None):
|
|
144
|
+
"""从主配置 config.json 的命名空间中读取数据。
|
|
145
|
+
|
|
146
|
+
若 old_path 指定的旧独立配置文件存在且主配置中该命名空间为空,
|
|
147
|
+
则一次性迁移旧文件内容到主配置并保存。
|
|
148
|
+
"""
|
|
149
|
+
if default is None:
|
|
150
|
+
default = {}
|
|
151
|
+
cfg = load_config()
|
|
152
|
+
data = cfg.get(key)
|
|
153
|
+
if data is not None:
|
|
154
|
+
return data
|
|
155
|
+
|
|
156
|
+
# 尝试迁移旧文件
|
|
157
|
+
if old_path is not None:
|
|
158
|
+
old_path = Path(old_path)
|
|
159
|
+
if old_path.exists():
|
|
160
|
+
try:
|
|
161
|
+
old_data = json.loads(old_path.read_text(encoding="utf-8"))
|
|
162
|
+
if old_data:
|
|
163
|
+
cfg[key] = old_data
|
|
164
|
+
save_config(cfg)
|
|
165
|
+
try:
|
|
166
|
+
old_path.rename(str(old_path) + ".migrated")
|
|
167
|
+
except Exception:
|
|
168
|
+
pass
|
|
169
|
+
return old_data
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
return default
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def save_namespace(key, value):
|
|
177
|
+
"""把数据写入主配置 config.json 的命名空间并持久化。"""
|
|
178
|
+
cfg = load_config()
|
|
179
|
+
cfg[key] = value
|
|
180
|
+
save_config(cfg)
|
|
181
|
+
|
|
182
|
+
|
|
128
183
|
def init_config():
|
|
129
184
|
"""首次运行引导:迁移旧配置 → 检查并要求输入 API Key → 自动创建默认工作空间"""
|
|
130
185
|
# 1. 自动迁移旧路径 → 新路径(一次性,幂等)
|
|
@@ -180,10 +235,8 @@ def init_config():
|
|
|
180
235
|
|
|
181
236
|
pcfg = providers_cfg.get(provider, {})
|
|
182
237
|
|
|
183
|
-
# 检查当前提供商是否已配置 key
|
|
184
|
-
has_key = bool(pcfg.get("key"))
|
|
185
|
-
if not has_key and provider == "zhipu":
|
|
186
|
-
has_key = bool(c.get("key", ""))
|
|
238
|
+
# 检查当前提供商是否已配置 key(兼容旧版顶层 key 字段)
|
|
239
|
+
has_key = bool(pcfg.get("key") or c.get("key", ""))
|
|
187
240
|
|
|
188
241
|
if not has_key:
|
|
189
242
|
from fr_cli.core.llm import list_providers, get_provider_info
|