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.
Files changed (171) hide show
  1. {fr_cli-2.4.1 → fr_cli-2.4.2}/MANIFEST.in +1 -0
  2. {fr_cli-2.4.1/fr_cli.egg-info → fr_cli-2.4.2}/PKG-INFO +24 -2
  3. {fr_cli-2.4.1 → fr_cli-2.4.2}/README.md +23 -1
  4. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/WEAPON.MD +1 -1
  5. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/__init__.py +1 -1
  6. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/addon/plugin.py +4 -4
  7. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/db.py +7 -6
  8. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/powerful_agent_template.py +14 -7
  9. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/remote.py +11 -6
  10. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/generator.py +1 -1
  11. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/hermes.py +24 -12
  12. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/image_and_parallel.py +36 -43
  13. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/remote.py +10 -25
  14. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/config.py +1 -1
  15. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/cron.py +1 -1
  16. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/disk.py +1 -1
  17. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/fs.py +1 -1
  18. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/mail.py +1 -1
  19. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/session.py +1 -1
  20. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/web.py +1 -1
  21. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/conf/config.py +65 -12
  22. fr_cli-2.4.2/fr_cli/conf/default_models.yaml +298 -0
  23. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/core.py +4 -9
  24. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/llm.py +12 -6
  25. fr_cli-2.4.2/fr_cli/core/model_factory.py +140 -0
  26. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/optimizations.py +10 -3
  27. fr_cli-2.4.2/fr_cli/lang/i18n.py +25 -0
  28. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/lang/translations/en.py +17 -8
  29. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/lang/translations/zh.py +14 -8
  30. fr_cli-2.4.2/fr_cli/main.py +363 -0
  31. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/memory/context.py +29 -40
  32. fr_cli-2.4.2/fr_cli/repl/batch.py +80 -0
  33. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/bootstrap.py +9 -3
  34. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/_common.py +5 -3
  35. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/base.py +5 -1
  36. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/config.py +6 -1
  37. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/session.py +9 -1
  38. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/banner.py +4 -4
  39. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/buddha.py +5 -0
  40. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/prompt.py +20 -4
  41. fr_cli-2.4.2/fr_cli/ui/ui.py +110 -0
  42. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/web_config.py +21 -20
  43. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/fs.py +109 -6
  44. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/mcp.py +49 -27
  45. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/vision.py +8 -1
  46. {fr_cli-2.4.1 → fr_cli-2.4.2/fr_cli.egg-info}/PKG-INFO +24 -2
  47. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/SOURCES.txt +9 -1
  48. {fr_cli-2.4.1 → fr_cli-2.4.2}/pyproject.toml +4 -1
  49. fr_cli-2.4.2/tests/test_batch_mode.py +123 -0
  50. fr_cli-2.4.2/tests/test_help_update.py +62 -0
  51. {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_integration_real.py +1 -1
  52. fr_cli-2.4.2/tests/test_no_color_prompt_toolkit.py +141 -0
  53. fr_cli-2.4.2/tests/test_plugin_exec.py +76 -0
  54. fr_cli-2.4.2/tests/test_prompt_completion.py +67 -0
  55. fr_cli-2.4.2/tests/test_vfs_diff.py +79 -0
  56. fr_cli-2.4.1/fr_cli/core/model_factory.py +0 -326
  57. fr_cli-2.4.1/fr_cli/lang/i18n.py +0 -8
  58. fr_cli-2.4.1/fr_cli/main.py +0 -122
  59. fr_cli-2.4.1/fr_cli/ui/ui.py +0 -64
  60. {fr_cli-2.4.1 → fr_cli-2.4.2}/LICENSE +0 -0
  61. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/README.md +0 -0
  62. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/__init__.py +0 -0
  63. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/a2a.py +0 -0
  64. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/acp.py +0 -0
  65. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/artifact_detector.py +0 -0
  66. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/__init__.py +0 -0
  67. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/_utils.py +0 -0
  68. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/local.py +0 -0
  69. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/rag.py +0 -0
  70. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/rag_watcher_daemon.py +0 -0
  71. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/builtins/spider.py +0 -0
  72. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/client.py +0 -0
  73. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/coding_helper.py +0 -0
  74. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/context_files.py +0 -0
  75. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/executor.py +0 -0
  76. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/gateway.py +0 -0
  77. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/hermes_daemon.py +0 -0
  78. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/manager.py +0 -0
  79. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/master.py +0 -0
  80. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/master_prompt.py +0 -0
  81. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/personality.py +0 -0
  82. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/plugin_system.py +0 -0
  83. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/server.py +0 -0
  84. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/shell_mode.py +0 -0
  85. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/skills.py +0 -0
  86. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow.py +0 -0
  87. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/__init__.py +0 -0
  88. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/engine.py +0 -0
  89. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/executor.py +0 -0
  90. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/manager.py +0 -0
  91. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/models.py +0 -0
  92. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/monitor.py +0 -0
  93. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system/tools.py +0 -0
  94. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/agent/workflow_system.py +0 -0
  95. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/assets/splash.jpeg +0 -0
  96. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/breakthrough/update.py +0 -0
  97. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/__init__.py +0 -0
  98. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/executor.py +0 -0
  99. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/__init__.py +0 -0
  100. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/agent.py +0 -0
  101. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/app.py +0 -0
  102. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/dataframe.py +0 -0
  103. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/mcp.py +0 -0
  104. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/other.py +0 -0
  105. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/system.py +0 -0
  106. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/handlers/vision.py +0 -0
  107. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/agent_data_mcp.py +0 -0
  108. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/fs.py +0 -0
  109. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/image.py +0 -0
  110. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/main_routes.py +0 -0
  111. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/services.py +0 -0
  112. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/session_config.py +0 -0
  113. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registered/web.py +0 -0
  114. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/registry.py +0 -0
  115. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/command/security.py +0 -0
  116. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/conf/__init__.py +0 -0
  117. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/conf/paths.py +0 -0
  118. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/conf/wizard.py +0 -0
  119. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/cache.py +0 -0
  120. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/chat.py +0 -0
  121. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/errors.py +0 -0
  122. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/intent.py +0 -0
  123. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/preferences.py +0 -0
  124. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/project.py +0 -0
  125. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/recommender.py +0 -0
  126. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/stream.py +0 -0
  127. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/sysmon.py +0 -0
  128. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/core/thinking.py +0 -0
  129. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/gatekeeper/__init__.py +0 -0
  130. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/gatekeeper/daemon.py +0 -0
  131. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/gatekeeper/manager.py +0 -0
  132. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/lang/translations/__init__.py +0 -0
  133. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/memory/history.py +0 -0
  134. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/memory/session.py +0 -0
  135. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/__init__.py +0 -0
  136. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/actions.py +0 -0
  137. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/__init__.py +0 -0
  138. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/agent.py +0 -0
  139. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/cron.py +0 -0
  140. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/dataframe.py +0 -0
  141. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/dev.py +0 -0
  142. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/fs.py +0 -0
  143. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/mcp.py +0 -0
  144. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/rag.py +0 -0
  145. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/remote_agent.py +0 -0
  146. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands/system.py +0 -0
  147. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/commands.py +0 -0
  148. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/queue.py +0 -0
  149. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/quick_actions.py +0 -0
  150. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/router.py +0 -0
  151. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/repl/scenarios.py +0 -0
  152. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/security/security.py +0 -0
  153. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/markdown.py +0 -0
  154. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/ui/splash.py +0 -0
  155. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/cron.py +0 -0
  156. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/dataframe.py +0 -0
  157. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/disk.py +0 -0
  158. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/launcher.py +0 -0
  159. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/loader.py +0 -0
  160. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/mail.py +0 -0
  161. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli/weapon/web.py +0 -0
  162. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/dependency_links.txt +0 -0
  163. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/entry_points.txt +0 -0
  164. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/requires.txt +0 -0
  165. {fr_cli-2.4.1 → fr_cli-2.4.2}/fr_cli.egg-info/top_level.txt +0 -0
  166. {fr_cli-2.4.1 → fr_cli-2.4.2}/setup.cfg +0 -0
  167. {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_a2a_and_providers.py +0 -0
  168. {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_master_prompt_fix.py +0 -0
  169. {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_model_config.py +0 -0
  170. {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_new_features.py +0 -0
  171. {fr_cli-2.4.1 → fr_cli-2.4.2}/tests/test_new_providers.py +0 -0
@@ -1,5 +1,6 @@
1
1
  include fr_cli/WEAPON.MD
2
2
  include fr_cli/README.md
3
+ include fr_cli/conf/default_models.yaml
3
4
  include fr_cli/assets/*.jpeg
4
5
  include fr_cli/assets/*.png
5
6
  include fr_cli/assets/*.jpg
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fr-cli
3
- Version: 2.4.1
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/` 目录下,旧路径 `~/.zhipu_cli_config.json` 等会在首次启动时自动迁移。
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/` 目录下,旧路径 `~/.zhipu_cli_config.json` 等会在首次启动时自动迁移。
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
- - **配置持久化**: `~/.zhipu_cli_config.json` 的 `mcp.servers` 字段
179
+ - **配置持久化**: `~/.fr_cli/config.json` 的 `mcp.servers` 字段
180
180
  - **示例**:
181
181
  ```
182
182
  /mcp_add fs npx -y @modelcontextprotocol/server-filesystem /tmp
@@ -1,4 +1,4 @@
1
1
  """
2
2
  凡人打字机 - 基于智谱AI的终极全能终端工具
3
3
  """
4
- __version__ = "2.4.1"
4
+ __version__ = "2.4.2"
@@ -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, shlex
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, {shlex.quote(str(PLUGIN_DIR))})
47
- mod = runpy.run_module({shlex.quote(name)}, run_name='__main__')
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 pathlib import Path
7
+ from fr_cli.conf.config import load_namespace, save_namespace
6
8
  from fr_cli.conf.paths import DATABASE_FILE
7
9
 
8
- DB_CFG_PATH = DATABASE_FILE # from fr_cli.conf.paths
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
- from fr_cli.agent.builtins._utils import load_json_config
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
- from fr_cli.agent.builtins._utils import save_json_config
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": "kimi-k2",
21
- "model": "kimi-k2-0905-preview",
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", "zhipu")
647
- self.model = context.get("model", "glm-4-flash")
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": "kimi-k2",
904
- "model": "kimi-k2-0905-preview",
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 pathlib import Path
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
- REMOTE_CFG_PATH = REMOTE_HOSTS_FILE # from fr_cli.conf.paths
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
- from fr_cli.agent.builtins._utils import load_json_config
25
- return load_json_config(REMOTE_CFG_PATH)
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
- from fr_cli.agent.builtins._utils import save_json_config
30
- save_json_config(REMOTE_CFG_PATH, hosts)
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(如 'zhipu', 'deepseek')
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
- for key in ["ZHIPU_API_KEY", "MOONSHOT_API_KEY", "DEEPSEEK_API_KEY",
210
- "QWEN_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY"]:
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
- # 智谱 CogView
68
- self.register_provider(ImageProvider(
69
- name="智谱 CogView-4",
70
- provider_id="zhipu",
71
- model="cogview-4",
72
- api_key="",
73
- size="1024x1024"
74
- ))
75
-
76
- # MiniMax 图片
77
- self.register_provider(ImageProvider(
78
- name="MiniMax 图片",
79
- provider_id="minimax",
80
- model="image-01",
81
- api_key="",
82
- base_url="https://api.minimax.chat/v1",
83
- size="1024x1024"
84
- ))
85
-
86
- # 通义万相
87
- self.register_provider(ImageProvider(
88
- name="通义万相",
89
- provider_id="qwen",
90
- model="wanx2.1-t2i-turbo",
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="cogview-4",
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="image-01",
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": "wanx2.1-t2i-turbo",
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
- 配置文件: ~/.fr_cli/remote/agents.json
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 json
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
- REMOTE_AGENTS_FILE = REMOTE_AGENTS_FILE # from fr_cli.conf.paths
10
+ _NS_KEY = "remote"
11
+ REMOTE_AGENTS_FILE = REMOTE_AGENTS_FILE # 保留用于一次性迁移
20
12
 
21
13
 
22
14
  def _load_remote_agents():
23
- if not REMOTE_AGENTS_FILE.exists():
24
- return {}
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
- try:
34
- with open(REMOTE_AGENTS_FILE, "w", encoding="utf-8") as f:
35
- json.dump(data, f, ensure_ascii=False, indent=2)
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=""):
@@ -1,6 +1,6 @@
1
1
  """命令处理器 —— config"""
2
2
 
3
- from fr_cli.command.registry import register
3
+ from fr_cli.command.registry import register, _TRIGGERS_CONFIG
4
4
 
5
5
  @register(
6
6
  name="undo",
@@ -1,6 +1,6 @@
1
1
  """命令处理器 —— cron"""
2
2
 
3
- from fr_cli.command.registry import register
3
+ from fr_cli.command.registry import register, _TRIGGERS_CRON
4
4
 
5
5
  @register(
6
6
  name="cron_add",
@@ -1,6 +1,6 @@
1
1
  """命令处理器 —— disk"""
2
2
 
3
- from fr_cli.command.registry import register
3
+ from fr_cli.command.registry import register, _TRIGGERS_DISK
4
4
 
5
5
  @register(
6
6
  name="disk_ls",
@@ -1,6 +1,6 @@
1
1
  """命令处理器 —— fs"""
2
2
 
3
- from fr_cli.command.registry import register
3
+ from fr_cli.command.registry import register, _TRIGGERS_FILE
4
4
 
5
5
  @register(
6
6
  name="write_file",
@@ -1,6 +1,6 @@
1
1
  """命令处理器 —— mail"""
2
2
 
3
- from fr_cli.command.registry import register
3
+ from fr_cli.command.registry import register, _TRIGGERS_MAIL
4
4
 
5
5
  @register(
6
6
  name="mail_inbox",
@@ -1,6 +1,6 @@
1
1
  """命令处理器 —— session"""
2
2
 
3
- from fr_cli.command.registry import register
3
+ from fr_cli.command.registry import register, _TRIGGERS_SESSION
4
4
 
5
5
  @register(
6
6
  name="save_session",
@@ -1,6 +1,6 @@
1
1
  """命令处理器 —— web"""
2
2
 
3
- from fr_cli.command.registry import register
3
+ from fr_cli.command.registry import register, _TRIGGERS_WEB
4
4
 
5
5
  @register(
6
6
  name="search_web",
@@ -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