fast-agent-mcp 0.4.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. fast_agent/__init__.py +183 -0
  2. fast_agent/acp/__init__.py +19 -0
  3. fast_agent/acp/acp_aware_mixin.py +304 -0
  4. fast_agent/acp/acp_context.py +437 -0
  5. fast_agent/acp/content_conversion.py +136 -0
  6. fast_agent/acp/filesystem_runtime.py +427 -0
  7. fast_agent/acp/permission_store.py +269 -0
  8. fast_agent/acp/server/__init__.py +5 -0
  9. fast_agent/acp/server/agent_acp_server.py +1472 -0
  10. fast_agent/acp/slash_commands.py +1050 -0
  11. fast_agent/acp/terminal_runtime.py +408 -0
  12. fast_agent/acp/tool_permission_adapter.py +125 -0
  13. fast_agent/acp/tool_permissions.py +474 -0
  14. fast_agent/acp/tool_progress.py +814 -0
  15. fast_agent/agents/__init__.py +85 -0
  16. fast_agent/agents/agent_types.py +64 -0
  17. fast_agent/agents/llm_agent.py +350 -0
  18. fast_agent/agents/llm_decorator.py +1139 -0
  19. fast_agent/agents/mcp_agent.py +1337 -0
  20. fast_agent/agents/tool_agent.py +271 -0
  21. fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
  22. fast_agent/agents/workflow/chain_agent.py +212 -0
  23. fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
  24. fast_agent/agents/workflow/iterative_planner.py +652 -0
  25. fast_agent/agents/workflow/maker_agent.py +379 -0
  26. fast_agent/agents/workflow/orchestrator_models.py +218 -0
  27. fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
  28. fast_agent/agents/workflow/parallel_agent.py +250 -0
  29. fast_agent/agents/workflow/router_agent.py +353 -0
  30. fast_agent/cli/__init__.py +0 -0
  31. fast_agent/cli/__main__.py +73 -0
  32. fast_agent/cli/commands/acp.py +159 -0
  33. fast_agent/cli/commands/auth.py +404 -0
  34. fast_agent/cli/commands/check_config.py +783 -0
  35. fast_agent/cli/commands/go.py +514 -0
  36. fast_agent/cli/commands/quickstart.py +557 -0
  37. fast_agent/cli/commands/serve.py +143 -0
  38. fast_agent/cli/commands/server_helpers.py +114 -0
  39. fast_agent/cli/commands/setup.py +174 -0
  40. fast_agent/cli/commands/url_parser.py +190 -0
  41. fast_agent/cli/constants.py +40 -0
  42. fast_agent/cli/main.py +115 -0
  43. fast_agent/cli/terminal.py +24 -0
  44. fast_agent/config.py +798 -0
  45. fast_agent/constants.py +41 -0
  46. fast_agent/context.py +279 -0
  47. fast_agent/context_dependent.py +50 -0
  48. fast_agent/core/__init__.py +92 -0
  49. fast_agent/core/agent_app.py +448 -0
  50. fast_agent/core/core_app.py +137 -0
  51. fast_agent/core/direct_decorators.py +784 -0
  52. fast_agent/core/direct_factory.py +620 -0
  53. fast_agent/core/error_handling.py +27 -0
  54. fast_agent/core/exceptions.py +90 -0
  55. fast_agent/core/executor/__init__.py +0 -0
  56. fast_agent/core/executor/executor.py +280 -0
  57. fast_agent/core/executor/task_registry.py +32 -0
  58. fast_agent/core/executor/workflow_signal.py +324 -0
  59. fast_agent/core/fastagent.py +1186 -0
  60. fast_agent/core/logging/__init__.py +5 -0
  61. fast_agent/core/logging/events.py +138 -0
  62. fast_agent/core/logging/json_serializer.py +164 -0
  63. fast_agent/core/logging/listeners.py +309 -0
  64. fast_agent/core/logging/logger.py +278 -0
  65. fast_agent/core/logging/transport.py +481 -0
  66. fast_agent/core/prompt.py +9 -0
  67. fast_agent/core/prompt_templates.py +183 -0
  68. fast_agent/core/validation.py +326 -0
  69. fast_agent/event_progress.py +62 -0
  70. fast_agent/history/history_exporter.py +49 -0
  71. fast_agent/human_input/__init__.py +47 -0
  72. fast_agent/human_input/elicitation_handler.py +123 -0
  73. fast_agent/human_input/elicitation_state.py +33 -0
  74. fast_agent/human_input/form_elements.py +59 -0
  75. fast_agent/human_input/form_fields.py +256 -0
  76. fast_agent/human_input/simple_form.py +113 -0
  77. fast_agent/human_input/types.py +40 -0
  78. fast_agent/interfaces.py +310 -0
  79. fast_agent/llm/__init__.py +9 -0
  80. fast_agent/llm/cancellation.py +22 -0
  81. fast_agent/llm/fastagent_llm.py +931 -0
  82. fast_agent/llm/internal/passthrough.py +161 -0
  83. fast_agent/llm/internal/playback.py +129 -0
  84. fast_agent/llm/internal/silent.py +41 -0
  85. fast_agent/llm/internal/slow.py +38 -0
  86. fast_agent/llm/memory.py +275 -0
  87. fast_agent/llm/model_database.py +490 -0
  88. fast_agent/llm/model_factory.py +388 -0
  89. fast_agent/llm/model_info.py +102 -0
  90. fast_agent/llm/prompt_utils.py +155 -0
  91. fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
  92. fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
  93. fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
  94. fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
  95. fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
  96. fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
  97. fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
  98. fast_agent/llm/provider/google/google_converter.py +466 -0
  99. fast_agent/llm/provider/google/llm_google_native.py +681 -0
  100. fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
  101. fast_agent/llm/provider/openai/llm_azure.py +143 -0
  102. fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
  103. fast_agent/llm/provider/openai/llm_generic.py +35 -0
  104. fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
  105. fast_agent/llm/provider/openai/llm_groq.py +42 -0
  106. fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
  107. fast_agent/llm/provider/openai/llm_openai.py +1195 -0
  108. fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
  109. fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
  110. fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
  111. fast_agent/llm/provider/openai/llm_xai.py +38 -0
  112. fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
  113. fast_agent/llm/provider/openai/openai_multipart.py +169 -0
  114. fast_agent/llm/provider/openai/openai_utils.py +67 -0
  115. fast_agent/llm/provider/openai/responses.py +133 -0
  116. fast_agent/llm/provider_key_manager.py +139 -0
  117. fast_agent/llm/provider_types.py +34 -0
  118. fast_agent/llm/request_params.py +61 -0
  119. fast_agent/llm/sampling_converter.py +98 -0
  120. fast_agent/llm/stream_types.py +9 -0
  121. fast_agent/llm/usage_tracking.py +445 -0
  122. fast_agent/mcp/__init__.py +56 -0
  123. fast_agent/mcp/common.py +26 -0
  124. fast_agent/mcp/elicitation_factory.py +84 -0
  125. fast_agent/mcp/elicitation_handlers.py +164 -0
  126. fast_agent/mcp/gen_client.py +83 -0
  127. fast_agent/mcp/helpers/__init__.py +36 -0
  128. fast_agent/mcp/helpers/content_helpers.py +352 -0
  129. fast_agent/mcp/helpers/server_config_helpers.py +25 -0
  130. fast_agent/mcp/hf_auth.py +147 -0
  131. fast_agent/mcp/interfaces.py +92 -0
  132. fast_agent/mcp/logger_textio.py +108 -0
  133. fast_agent/mcp/mcp_agent_client_session.py +411 -0
  134. fast_agent/mcp/mcp_aggregator.py +2175 -0
  135. fast_agent/mcp/mcp_connection_manager.py +723 -0
  136. fast_agent/mcp/mcp_content.py +262 -0
  137. fast_agent/mcp/mime_utils.py +108 -0
  138. fast_agent/mcp/oauth_client.py +509 -0
  139. fast_agent/mcp/prompt.py +159 -0
  140. fast_agent/mcp/prompt_message_extended.py +155 -0
  141. fast_agent/mcp/prompt_render.py +84 -0
  142. fast_agent/mcp/prompt_serialization.py +580 -0
  143. fast_agent/mcp/prompts/__init__.py +0 -0
  144. fast_agent/mcp/prompts/__main__.py +7 -0
  145. fast_agent/mcp/prompts/prompt_constants.py +18 -0
  146. fast_agent/mcp/prompts/prompt_helpers.py +238 -0
  147. fast_agent/mcp/prompts/prompt_load.py +186 -0
  148. fast_agent/mcp/prompts/prompt_server.py +552 -0
  149. fast_agent/mcp/prompts/prompt_template.py +438 -0
  150. fast_agent/mcp/resource_utils.py +215 -0
  151. fast_agent/mcp/sampling.py +200 -0
  152. fast_agent/mcp/server/__init__.py +4 -0
  153. fast_agent/mcp/server/agent_server.py +613 -0
  154. fast_agent/mcp/skybridge.py +44 -0
  155. fast_agent/mcp/sse_tracking.py +287 -0
  156. fast_agent/mcp/stdio_tracking_simple.py +59 -0
  157. fast_agent/mcp/streamable_http_tracking.py +309 -0
  158. fast_agent/mcp/tool_execution_handler.py +137 -0
  159. fast_agent/mcp/tool_permission_handler.py +88 -0
  160. fast_agent/mcp/transport_tracking.py +634 -0
  161. fast_agent/mcp/types.py +24 -0
  162. fast_agent/mcp/ui_agent.py +48 -0
  163. fast_agent/mcp/ui_mixin.py +209 -0
  164. fast_agent/mcp_server_registry.py +89 -0
  165. fast_agent/py.typed +0 -0
  166. fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
  167. fast_agent/resources/examples/data-analysis/analysis.py +68 -0
  168. fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
  169. fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
  170. fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
  171. fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
  172. fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
  173. fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
  174. fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
  175. fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
  176. fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
  177. fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
  178. fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
  179. fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
  180. fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
  181. fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
  182. fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
  183. fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
  184. fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
  185. fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
  186. fast_agent/resources/examples/researcher/researcher.py +36 -0
  187. fast_agent/resources/examples/tensorzero/.env.sample +2 -0
  188. fast_agent/resources/examples/tensorzero/Makefile +31 -0
  189. fast_agent/resources/examples/tensorzero/README.md +56 -0
  190. fast_agent/resources/examples/tensorzero/agent.py +35 -0
  191. fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
  192. fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
  193. fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
  194. fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
  195. fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
  196. fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
  197. fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
  198. fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
  199. fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
  200. fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
  201. fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
  202. fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
  203. fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
  204. fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
  205. fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
  206. fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
  207. fast_agent/resources/examples/workflows/chaining.py +37 -0
  208. fast_agent/resources/examples/workflows/evaluator.py +77 -0
  209. fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
  210. fast_agent/resources/examples/workflows/graded_report.md +89 -0
  211. fast_agent/resources/examples/workflows/human_input.py +28 -0
  212. fast_agent/resources/examples/workflows/maker.py +156 -0
  213. fast_agent/resources/examples/workflows/orchestrator.py +70 -0
  214. fast_agent/resources/examples/workflows/parallel.py +56 -0
  215. fast_agent/resources/examples/workflows/router.py +69 -0
  216. fast_agent/resources/examples/workflows/short_story.md +13 -0
  217. fast_agent/resources/examples/workflows/short_story.txt +19 -0
  218. fast_agent/resources/setup/.gitignore +30 -0
  219. fast_agent/resources/setup/agent.py +28 -0
  220. fast_agent/resources/setup/fastagent.config.yaml +65 -0
  221. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
  222. fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
  223. fast_agent/skills/__init__.py +9 -0
  224. fast_agent/skills/registry.py +235 -0
  225. fast_agent/tools/elicitation.py +369 -0
  226. fast_agent/tools/shell_runtime.py +402 -0
  227. fast_agent/types/__init__.py +59 -0
  228. fast_agent/types/conversation_summary.py +294 -0
  229. fast_agent/types/llm_stop_reason.py +78 -0
  230. fast_agent/types/message_search.py +249 -0
  231. fast_agent/ui/__init__.py +38 -0
  232. fast_agent/ui/console.py +59 -0
  233. fast_agent/ui/console_display.py +1080 -0
  234. fast_agent/ui/elicitation_form.py +946 -0
  235. fast_agent/ui/elicitation_style.py +59 -0
  236. fast_agent/ui/enhanced_prompt.py +1400 -0
  237. fast_agent/ui/history_display.py +734 -0
  238. fast_agent/ui/interactive_prompt.py +1199 -0
  239. fast_agent/ui/markdown_helpers.py +104 -0
  240. fast_agent/ui/markdown_truncator.py +1004 -0
  241. fast_agent/ui/mcp_display.py +857 -0
  242. fast_agent/ui/mcp_ui_utils.py +235 -0
  243. fast_agent/ui/mermaid_utils.py +169 -0
  244. fast_agent/ui/message_primitives.py +50 -0
  245. fast_agent/ui/notification_tracker.py +205 -0
  246. fast_agent/ui/plain_text_truncator.py +68 -0
  247. fast_agent/ui/progress_display.py +10 -0
  248. fast_agent/ui/rich_progress.py +195 -0
  249. fast_agent/ui/streaming.py +774 -0
  250. fast_agent/ui/streaming_buffer.py +449 -0
  251. fast_agent/ui/tool_display.py +422 -0
  252. fast_agent/ui/usage_display.py +204 -0
  253. fast_agent/utils/__init__.py +5 -0
  254. fast_agent/utils/reasoning_stream_parser.py +77 -0
  255. fast_agent/utils/time.py +22 -0
  256. fast_agent/workflow_telemetry.py +261 -0
  257. fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
  258. fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
  259. fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
  260. fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
  261. fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,783 @@
1
+ """Command to check FastAgent configuration."""
2
+
3
+ import platform
4
+ import sys
5
+ from importlib.metadata import version
6
+ from pathlib import Path
7
+
8
+ import typer
9
+ import yaml
10
+ from rich.table import Table
11
+ from rich.text import Text
12
+
13
+ from fast_agent.llm.provider_key_manager import API_KEY_HINT_TEXT, ProviderKeyManager
14
+ from fast_agent.llm.provider_types import Provider
15
+ from fast_agent.skills import SkillRegistry
16
+ from fast_agent.ui.console import console
17
+
18
+ app = typer.Typer(
19
+ help="Check and diagnose FastAgent configuration",
20
+ no_args_is_help=False, # Allow showing our custom help instead
21
+ )
22
+
23
+
24
+ def find_config_files(start_path: Path) -> dict[str, Path | None]:
25
+ """Find FastAgent configuration files, preferring secrets file next to config file."""
26
+ from fast_agent.config import find_fastagent_config_files
27
+
28
+ config_path, secrets_path = find_fastagent_config_files(start_path)
29
+ return {
30
+ "config": config_path,
31
+ "secrets": secrets_path,
32
+ }
33
+
34
+
35
+ def get_system_info() -> dict:
36
+ """Get system information including Python version, OS, etc."""
37
+ return {
38
+ "platform": platform.system(),
39
+ "platform_version": platform.version(),
40
+ "python_version": sys.version,
41
+ "python_path": sys.executable,
42
+ }
43
+
44
+
45
+ def get_secrets_summary(secrets_path: Path | None) -> dict:
46
+ """Extract information from the secrets file."""
47
+ result = {
48
+ "status": "not_found", # Default status: not found
49
+ "error": None,
50
+ "secrets": {},
51
+ }
52
+
53
+ if not secrets_path:
54
+ return result
55
+
56
+ if not secrets_path.exists():
57
+ result["status"] = "not_found"
58
+ return result
59
+
60
+ # File exists, attempt to parse
61
+ try:
62
+ with open(secrets_path, "r") as f:
63
+ secrets = yaml.safe_load(f)
64
+
65
+ # Mark as successfully parsed
66
+ result["status"] = "parsed"
67
+ result["secrets"] = secrets or {}
68
+
69
+ except Exception as e:
70
+ # File exists but has parse errors
71
+ result["status"] = "error"
72
+ result["error"] = str(e)
73
+ console.print(f"[yellow]Warning:[/yellow] Error parsing secrets file: {e}")
74
+
75
+ return result
76
+
77
+
78
+ def check_api_keys(secrets_summary: dict, config_summary: dict) -> dict:
79
+ """Check if API keys are configured in secrets file or environment, including Azure DefaultAzureCredential.
80
+ Now also checks Azure config in main config file for retrocompatibility.
81
+ """
82
+ import os
83
+
84
+ results = {
85
+ provider.value: {"env": "", "config": ""}
86
+ for provider in Provider
87
+ if provider != Provider.FAST_AGENT
88
+ }
89
+
90
+ # Get secrets if available
91
+ secrets = secrets_summary.get("secrets", {})
92
+ secrets_status = secrets_summary.get("status", "not_found")
93
+ # Get config if available
94
+ config = config_summary if config_summary.get("status") == "parsed" else {}
95
+
96
+ config_azure = {}
97
+ if config and "azure" in config.get("config", {}):
98
+ config_azure = config["config"]["azure"]
99
+
100
+ for provider in results:
101
+ # Always check environment variables first
102
+ env_key_name = ProviderKeyManager.get_env_key_name(provider)
103
+ env_key_value = os.environ.get(env_key_name)
104
+ if env_key_value:
105
+ if len(env_key_value) > 5:
106
+ results[provider]["env"] = f"...{env_key_value[-5:]}"
107
+ else:
108
+ results[provider]["env"] = "...***"
109
+
110
+ # Special handling for Azure: support api_key and DefaultAzureCredential
111
+ if provider == "azure":
112
+ # Prefer secrets if present, else fallback to config
113
+ azure_cfg = {}
114
+ if secrets_status == "parsed" and "azure" in secrets:
115
+ azure_cfg = secrets.get("azure", {})
116
+ elif config_azure:
117
+ azure_cfg = config_azure
118
+
119
+ use_default_cred = azure_cfg.get("use_default_azure_credential", False)
120
+ base_url = azure_cfg.get("base_url")
121
+ if use_default_cred and base_url:
122
+ results[provider]["config"] = "DefaultAzureCredential"
123
+ continue
124
+
125
+ # Check secrets file if it was parsed successfully
126
+ if secrets_status == "parsed":
127
+ config_key = ProviderKeyManager.get_config_file_key(provider, secrets)
128
+ if config_key and config_key != API_KEY_HINT_TEXT:
129
+ if len(config_key) > 5:
130
+ results[provider]["config"] = f"...{config_key[-5:]}"
131
+ else:
132
+ results[provider]["config"] = "...***"
133
+
134
+ return results
135
+
136
+
137
+ def get_fastagent_version() -> str:
138
+ """Get the installed version of FastAgent."""
139
+ try:
140
+ return version("fast-agent-mcp")
141
+ except: # noqa: E722
142
+ return "unknown"
143
+
144
+
145
+ def get_config_summary(config_path: Path | None) -> dict:
146
+ """Extract key information from the configuration file."""
147
+ from fast_agent.config import MCPTimelineSettings, Settings
148
+
149
+ # Get actual defaults from Settings class
150
+ default_settings = Settings()
151
+
152
+ result = {
153
+ "status": "not_found", # Default status: not found
154
+ "error": None,
155
+ "default_model": default_settings.default_model,
156
+ "logger": {
157
+ "level": default_settings.logger.level,
158
+ "type": default_settings.logger.type,
159
+ "streaming": default_settings.logger.streaming,
160
+ "progress_display": default_settings.logger.progress_display,
161
+ "show_chat": default_settings.logger.show_chat,
162
+ "show_tools": default_settings.logger.show_tools,
163
+ "truncate_tools": default_settings.logger.truncate_tools,
164
+ "enable_markup": default_settings.logger.enable_markup,
165
+ },
166
+ "mcp_ui_mode": default_settings.mcp_ui_mode,
167
+ "timeline": {
168
+ "steps": default_settings.mcp_timeline.steps,
169
+ "step_seconds": default_settings.mcp_timeline.step_seconds,
170
+ },
171
+ "mcp_servers": [],
172
+ "skills_directory": None,
173
+ }
174
+
175
+ if not config_path:
176
+ return result
177
+
178
+ if not config_path.exists():
179
+ result["status"] = "not_found"
180
+ return result
181
+
182
+ # File exists, attempt to parse
183
+ try:
184
+ with open(config_path, "r") as f:
185
+ config = yaml.safe_load(f)
186
+
187
+ # Mark as successfully parsed
188
+ result["status"] = "parsed"
189
+
190
+ if not config:
191
+ return result
192
+
193
+ # Get default model
194
+ if "default_model" in config:
195
+ result["default_model"] = config["default_model"]
196
+
197
+ # Get logger settings
198
+ if "logger" in config:
199
+ logger_config = config["logger"]
200
+ result["logger"] = {
201
+ "level": logger_config.get("level", default_settings.logger.level),
202
+ "type": logger_config.get("type", default_settings.logger.type),
203
+ "streaming": logger_config.get("streaming", default_settings.logger.streaming),
204
+ "progress_display": logger_config.get(
205
+ "progress_display", default_settings.logger.progress_display
206
+ ),
207
+ "show_chat": logger_config.get("show_chat", default_settings.logger.show_chat),
208
+ "show_tools": logger_config.get("show_tools", default_settings.logger.show_tools),
209
+ "truncate_tools": logger_config.get(
210
+ "truncate_tools", default_settings.logger.truncate_tools
211
+ ),
212
+ "enable_markup": logger_config.get(
213
+ "enable_markup", default_settings.logger.enable_markup
214
+ ),
215
+ }
216
+
217
+ # Get MCP UI mode
218
+ if "mcp_ui_mode" in config:
219
+ result["mcp_ui_mode"] = config["mcp_ui_mode"]
220
+
221
+ # Get timeline settings
222
+ if "mcp_timeline" in config:
223
+ try:
224
+ timeline_override = MCPTimelineSettings(**(config.get("mcp_timeline") or {}))
225
+ except Exception as exc: # pragma: no cover - defensive
226
+ console.print(
227
+ "[yellow]Warning:[/yellow] Invalid mcp_timeline configuration; using defaults."
228
+ )
229
+ console.print(f"[yellow]Details:[/yellow] {exc}")
230
+ else:
231
+ result["timeline"] = {
232
+ "steps": timeline_override.steps,
233
+ "step_seconds": timeline_override.step_seconds,
234
+ }
235
+
236
+ # Get MCP server info
237
+ if "mcp" in config and "servers" in config["mcp"]:
238
+ for server_name, server_config in config["mcp"]["servers"].items():
239
+ server_info = {
240
+ "name": server_name,
241
+ "transport": "STDIO", # Default transport type
242
+ "command": "",
243
+ "url": "",
244
+ }
245
+
246
+ # Determine transport type
247
+ if "url" in server_config:
248
+ url = server_config.get("url", "")
249
+ server_info["url"] = url
250
+
251
+ # Use URL path to determine transport type
252
+ try:
253
+ from .url_parser import parse_server_url
254
+
255
+ _, transport_type, _ = parse_server_url(url)
256
+ server_info["transport"] = transport_type.upper()
257
+ except Exception:
258
+ # Fallback to HTTP if URL parsing fails
259
+ server_info["transport"] = "HTTP"
260
+
261
+ # Get command and args
262
+ command = server_config.get("command", "")
263
+ args = server_config.get("args", [])
264
+
265
+ if command:
266
+ if args:
267
+ args_str = " ".join([str(arg) for arg in args])
268
+ full_cmd = f"{command} {args_str}"
269
+ # Truncate if too long
270
+ if len(full_cmd) > 60:
271
+ full_cmd = full_cmd[:57] + "..."
272
+ server_info["command"] = full_cmd
273
+ else:
274
+ server_info["command"] = command
275
+
276
+ # Truncate URL if too long
277
+ if server_info["url"] and len(server_info["url"]) > 60:
278
+ server_info["url"] = server_info["url"][:57] + "..."
279
+
280
+ result["mcp_servers"].append(server_info)
281
+
282
+ # Skills directory override
283
+ skills_cfg = config.get("skills") if isinstance(config, dict) else None
284
+ if isinstance(skills_cfg, dict):
285
+ directory_value = skills_cfg.get("directory")
286
+ if isinstance(directory_value, str) and directory_value.strip():
287
+ result["skills_directory"] = directory_value.strip()
288
+
289
+ except Exception as e:
290
+ # File exists but has parse errors
291
+ result["status"] = "error"
292
+ result["error"] = str(e)
293
+ console.print(f"[red]Error parsing configuration file:[/red] {e}")
294
+
295
+ return result
296
+
297
+
298
+ def show_check_summary() -> None:
299
+ """Show a summary of checks with colorful styling."""
300
+ cwd = Path.cwd()
301
+ config_files = find_config_files(cwd)
302
+ system_info = get_system_info()
303
+ config_summary = get_config_summary(config_files["config"])
304
+ secrets_summary = get_secrets_summary(config_files["secrets"])
305
+ api_keys = check_api_keys(secrets_summary, config_summary)
306
+ fastagent_version = get_fastagent_version()
307
+
308
+ # Helper to print section headers using the new console_display style
309
+ def _print_section_header(title: str, color: str = "blue") -> None:
310
+ width = console.size.width
311
+ left = f"[{color}]▎[/{color}][dim {color}]▶[/dim {color}] [{color}]{title}[/{color}]"
312
+ left_text = Text.from_markup(left)
313
+ separator_count = max(1, width - left_text.cell_len - 1)
314
+
315
+ combined = Text()
316
+ combined.append_text(left_text)
317
+ combined.append(" ")
318
+ combined.append("─" * separator_count, style="dim")
319
+
320
+ console.print()
321
+ console.print(combined)
322
+ console.print()
323
+
324
+ # Environment and configuration section (merged)
325
+ # Header shows version and platform for a concise overview
326
+ header_title = f"fast-agent v{fastagent_version} ({system_info['platform']})"
327
+ _print_section_header(header_title, color="blue")
328
+
329
+ config_path = config_files["config"]
330
+ secrets_path = config_files["secrets"]
331
+
332
+ env_table = Table(show_header=False, box=None)
333
+ env_table.add_column("Setting", style="white")
334
+ env_table.add_column("Value")
335
+
336
+ # Determine keyring backend early so it can appear in the top section
337
+ # Also detect whether the backend is actually usable (not the fail backend)
338
+ keyring_usable = False
339
+ try:
340
+ import keyring # type: ignore
341
+
342
+ keyring_backend = keyring.get_keyring()
343
+ keyring_name = getattr(keyring_backend, "name", keyring_backend.__class__.__name__)
344
+ try:
345
+ # Detect the "fail" backend explicitly; it's present but unusable
346
+ from keyring.backends.fail import Keyring as FailKeyring # type: ignore
347
+
348
+ keyring_usable = not isinstance(keyring_backend, FailKeyring)
349
+ except Exception:
350
+ # If we can't import the fail backend marker, assume usable
351
+ keyring_usable = True
352
+ except Exception:
353
+ keyring = None # type: ignore
354
+ keyring_name = "unavailable"
355
+ keyring_usable = False
356
+
357
+ # Python info (highlight version and path in green)
358
+ env_table.add_row(
359
+ "Python Version", f"[green]{'.'.join(system_info['python_version'].split('.')[:3])}[/green]"
360
+ )
361
+ env_table.add_row("Python Path", f"[green]{system_info['python_path']}[/green]")
362
+
363
+ # Secrets file status
364
+ secrets_status = secrets_summary.get("status", "not_found")
365
+ if secrets_status == "not_found":
366
+ env_table.add_row("Secrets File", "[yellow]Not found[/yellow]")
367
+ elif secrets_status == "error":
368
+ env_table.add_row("Secrets File", f"[orange_red1]Errors[/orange_red1] ({secrets_path})")
369
+ env_table.add_row(
370
+ "Secrets Error",
371
+ f"[orange_red1]{secrets_summary.get('error', 'Unknown error')}[/orange_red1]",
372
+ )
373
+ else: # parsed successfully
374
+ env_table.add_row("Secrets File", f"[green]Found[/green] ({secrets_path})")
375
+
376
+ # Config file status
377
+ config_status = config_summary.get("status", "not_found")
378
+ if config_status == "not_found":
379
+ env_table.add_row("Config File", "[red]Not found[/red]")
380
+ elif config_status == "error":
381
+ env_table.add_row("Config File", f"[orange_red1]Errors[/orange_red1] ({config_path})")
382
+ env_table.add_row(
383
+ "Config Error",
384
+ f"[orange_red1]{config_summary.get('error', 'Unknown error')}[/orange_red1]",
385
+ )
386
+ else: # parsed successfully
387
+ env_table.add_row("Config File", f"[green]Found[/green] ({config_path})")
388
+ default_model_value = config_summary.get("default_model", "gpt-5-mini.low (system default)")
389
+ env_table.add_row("Default Model", f"[green]{default_model_value}[/green]")
390
+
391
+ # Keyring backend (always shown in application-level settings)
392
+ if keyring_usable and keyring_name != "unavailable":
393
+ env_table.add_row("Keyring Backend", f"[green]{keyring_name}[/green]")
394
+ else:
395
+ env_table.add_row("Keyring Backend", "[red]not available[/red]")
396
+
397
+ console.print(env_table)
398
+
399
+ def _relative_path(path: Path) -> str:
400
+ try:
401
+ return str(path.relative_to(cwd))
402
+ except ValueError:
403
+ return str(path)
404
+
405
+ skills_override = config_summary.get("skills_directory")
406
+ override_directory = Path(skills_override).expanduser() if skills_override else None
407
+ skills_registry = SkillRegistry(base_dir=cwd, override_directory=override_directory)
408
+ skills_dir = skills_registry.directory
409
+ skills_manifests, skill_errors = skills_registry.load_manifests_with_errors()
410
+
411
+ # Logger Settings panel with two-column layout
412
+ logger = config_summary.get("logger", {})
413
+ logger_table = Table(show_header=True, box=None)
414
+ logger_table.add_column("Setting", style="white", header_style="bold bright_white")
415
+ logger_table.add_column("Value", header_style="bold bright_white")
416
+ logger_table.add_column("Setting", style="white", header_style="bold bright_white")
417
+ logger_table.add_column("Value", header_style="bold bright_white")
418
+
419
+ def bool_to_symbol(value):
420
+ return "[bold green]✓[/bold green]" if value else "[bold red]✗[/bold red]"
421
+
422
+ # Format MCP-UI mode value
423
+ mcp_ui_mode = config_summary.get("mcp_ui_mode", "auto")
424
+ if mcp_ui_mode == "disabled":
425
+ mcp_ui_display = "[dim]disabled[/dim]"
426
+ else:
427
+ mcp_ui_display = f"[green]{mcp_ui_mode}[/green]"
428
+
429
+ timeline_settings = config_summary.get("timeline", {})
430
+ timeline_steps = timeline_settings.get("steps", 20)
431
+ timeline_step_seconds = timeline_settings.get("step_seconds", 30)
432
+
433
+ def format_step_interval(seconds: int) -> str:
434
+ try:
435
+ total = int(seconds)
436
+ except (TypeError, ValueError):
437
+ return str(seconds)
438
+ if total <= 0:
439
+ return "0s"
440
+ if total % 86400 == 0:
441
+ return f"{total // 86400}d"
442
+ if total % 3600 == 0:
443
+ return f"{total // 3600}h"
444
+ if total % 60 == 0:
445
+ return f"{total // 60}m"
446
+ minutes, secs = divmod(total, 60)
447
+ if minutes:
448
+ return f"{minutes}m{secs:02d}s"
449
+ return f"{secs}s"
450
+
451
+ # Prepare all settings as pairs
452
+ settings_data = [
453
+ ("Log Level", logger.get("level", "warning (default)")),
454
+ ("Log Type", logger.get("type", "file (default)")),
455
+ ("MCP-UI", mcp_ui_display),
456
+ ("Streaming Mode", f"[green]{logger.get('streaming', 'markdown')}[/green]"),
457
+ ("Streaming Display", bool_to_symbol(logger.get("streaming_display", True))),
458
+ ("Progress Display", bool_to_symbol(logger.get("progress_display", True))),
459
+ ("Show Chat", bool_to_symbol(logger.get("show_chat", True))),
460
+ ("Show Tools", bool_to_symbol(logger.get("show_tools", True))),
461
+ ("Truncate Tools", bool_to_symbol(logger.get("truncate_tools", True))),
462
+ ("Enable Markup", bool_to_symbol(logger.get("enable_markup", True))),
463
+ ("Timeline Steps", f"[green]{timeline_steps}[/green]"),
464
+ ("Timeline Interval", f"[green]{format_step_interval(timeline_step_seconds)}[/green]"),
465
+ ]
466
+
467
+ # Add rows in two-column layout, styling some values in green
468
+ for i in range(0, len(settings_data), 2):
469
+ left_setting, left_value = settings_data[i]
470
+ # Style certain values in green (MCP-UI is already pre-styled)
471
+ if left_setting in ("Log Level", "Log Type"):
472
+ left_value = f"[green]{left_value}[/green]"
473
+ if i + 1 < len(settings_data):
474
+ right_setting, right_value = settings_data[i + 1]
475
+ if right_setting in ("Log Level", "Log Type"):
476
+ right_value = f"[green]{right_value}[/green]"
477
+ logger_table.add_row(left_setting, left_value, right_setting, right_value)
478
+ else:
479
+ # Odd number of settings - fill right column with empty strings
480
+ logger_table.add_row(left_setting, left_value, "", "")
481
+
482
+ _print_section_header("Application Settings", color="blue")
483
+ console.print(logger_table)
484
+
485
+ # API keys panel with two-column layout
486
+ keys_table = Table(show_header=True, box=None)
487
+ keys_table.add_column("Provider", style="white", header_style="bold bright_white")
488
+ keys_table.add_column("Env", justify="center", header_style="bold bright_white")
489
+ keys_table.add_column("Config", justify="center", header_style="bold bright_white")
490
+ keys_table.add_column("Active Key", style="green", header_style="bold bright_white")
491
+ keys_table.add_column("Provider", style="white", header_style="bold bright_white")
492
+ keys_table.add_column("Env", justify="center", header_style="bold bright_white")
493
+ keys_table.add_column("Config", justify="center", header_style="bold bright_white")
494
+ keys_table.add_column("Active Key", style="green", header_style="bold bright_white")
495
+
496
+ def format_provider_row(provider, status):
497
+ """Format a single provider's status for display."""
498
+ # Environment key indicator
499
+ if status["env"] and status["config"]:
500
+ # Both exist but config takes precedence (env is present but not active)
501
+ env_status = "[yellow]✓[/yellow]"
502
+ elif status["env"]:
503
+ # Only env exists and is active
504
+ env_status = "[bold green]✓[/bold green]"
505
+ else:
506
+ # No env key
507
+ env_status = "[dim]✗[/dim]"
508
+
509
+ # Config file key indicator
510
+ if status["config"]:
511
+ # Config exists and takes precedence (is active)
512
+ config_status = "[bold green]✓[/bold green]"
513
+ else:
514
+ # No config key
515
+ config_status = "[dim]✗[/dim]"
516
+
517
+ # Display active key
518
+ if status["config"]:
519
+ # Config key is active
520
+ active = f"[bold green]{status['config']}[/bold green]"
521
+ elif status["env"]:
522
+ # Env key is active
523
+ active = f"[bold green]{status['env']}[/bold green]"
524
+ elif provider == "generic":
525
+ # Generic provider uses "ollama" as a default when no key is set
526
+ active = "[green]ollama (default)[/green]"
527
+ else:
528
+ # No key available for other providers
529
+ active = "[dim]Not configured[/dim]"
530
+
531
+ # Get the proper display name for the provider
532
+ from fast_agent.llm.provider_types import Provider
533
+
534
+ provider_enum = Provider(provider)
535
+ display_name = provider_enum.display_name
536
+
537
+ return display_name, env_status, config_status, active
538
+
539
+ # Split providers into two columns
540
+ providers_list = list(api_keys.items())
541
+ mid_point = (len(providers_list) + 1) // 2 # Round up for odd numbers
542
+
543
+ for i in range(mid_point):
544
+ # Left column
545
+ left_provider, left_status = providers_list[i]
546
+ left_data = format_provider_row(left_provider, left_status)
547
+
548
+ # Right column (if exists)
549
+ if i + mid_point < len(providers_list):
550
+ right_provider, right_status = providers_list[i + mid_point]
551
+ right_data = format_provider_row(right_provider, right_status)
552
+ # Add row with both columns
553
+ keys_table.add_row(*left_data, *right_data)
554
+ else:
555
+ # Add row with only left column (right column empty)
556
+ keys_table.add_row(*left_data, "", "", "", "")
557
+
558
+ # API Keys section
559
+ _print_section_header("API Keys", color="blue")
560
+ console.print(keys_table)
561
+
562
+ # MCP Servers panel (shown after API Keys)
563
+ if config_summary.get("status") == "parsed":
564
+ mcp_servers = config_summary.get("mcp_servers", [])
565
+ if mcp_servers:
566
+ from fast_agent.config import MCPServerSettings
567
+ from fast_agent.mcp.oauth_client import compute_server_identity
568
+
569
+ servers_table = Table(show_header=True, box=None)
570
+ servers_table.add_column("Name", style="white", header_style="bold bright_white")
571
+ servers_table.add_column("Transport", style="white", header_style="bold bright_white")
572
+ servers_table.add_column("Command/URL", header_style="bold bright_white")
573
+ servers_table.add_column("OAuth", header_style="bold bright_white")
574
+ servers_table.add_column("Token", header_style="bold bright_white")
575
+
576
+ for server in mcp_servers:
577
+ name = server["name"]
578
+ transport = server["transport"]
579
+
580
+ # Show either command or URL based on transport type
581
+ if transport == "STDIO":
582
+ command_url = server["command"] or "[dim]Not configured[/dim]"
583
+ else: # SSE
584
+ command_url = server["url"] or "[dim]Not configured[/dim]"
585
+
586
+ # Style configured command/url in green (keep "Not configured" dim)
587
+ if "Not configured" not in command_url:
588
+ command_url = f"[green]{command_url}[/green]"
589
+
590
+ # OAuth status and token presence
591
+ # Default for unsupported transports (e.g., STDIO): show "-" rather than "off"
592
+ oauth_status = "[dim]-[/dim]"
593
+ token_status = "[dim]n/a[/dim]"
594
+ # Attempt to reconstruct minimal server settings for identity check
595
+ try:
596
+ cfg = MCPServerSettings(
597
+ name=name,
598
+ transport="sse"
599
+ if transport == "SSE"
600
+ else ("stdio" if transport == "STDIO" else "http"),
601
+ url=(server.get("url") or None),
602
+ auth=server.get("auth") if isinstance(server.get("auth"), dict) else None,
603
+ )
604
+ except Exception:
605
+ cfg = None
606
+
607
+ if cfg and cfg.transport in ("http", "sse"):
608
+ # Determine if OAuth is enabled for this server
609
+ oauth_enabled = True
610
+ if cfg.auth is not None and hasattr(cfg.auth, "oauth"):
611
+ oauth_enabled = bool(getattr(cfg.auth, "oauth"))
612
+ oauth_status = "[green]on[/green]" if oauth_enabled else "[dim]off[/dim]"
613
+
614
+ # Only check token presence when using keyring persist
615
+ persist = "keyring"
616
+ if cfg.auth is not None and hasattr(cfg.auth, "persist"):
617
+ persist = getattr(cfg.auth, "persist") or "keyring"
618
+ if keyring and keyring_usable and persist == "keyring" and oauth_enabled:
619
+ identity = compute_server_identity(cfg)
620
+ tkey = f"oauth:tokens:{identity}"
621
+ try:
622
+ has = keyring.get_password("fast-agent-mcp", tkey) is not None
623
+ except Exception:
624
+ has = False
625
+ token_status = "[bold green]✓[/bold green]" if has else "[dim]✗[/dim]"
626
+ elif persist == "keyring" and not keyring_usable and oauth_enabled:
627
+ token_status = "[red]not available[/red]"
628
+ elif persist == "memory" and oauth_enabled:
629
+ token_status = "[yellow]memory[/yellow]"
630
+
631
+ servers_table.add_row(name, transport, command_url, oauth_status, token_status)
632
+
633
+ _print_section_header("MCP Servers", color="blue")
634
+ console.print(servers_table)
635
+
636
+ _print_section_header("Agent Skills", color="blue")
637
+ if skills_dir:
638
+ console.print(f"Directory: [green]{_relative_path(skills_dir)}[/green]")
639
+
640
+ if skills_manifests or skill_errors:
641
+ skills_table = Table(show_header=True, box=None)
642
+ skills_table.add_column("Name", style="cyan", header_style="bold bright_white")
643
+ skills_table.add_column("Description", style="white", header_style="bold bright_white")
644
+ skills_table.add_column("Source", style="dim", header_style="bold bright_white")
645
+ skills_table.add_column("Status", style="green", header_style="bold bright_white")
646
+
647
+ def _truncate(text: str, length: int = 70) -> str:
648
+ if len(text) <= length:
649
+ return text
650
+ return text[: length - 3] + "..."
651
+
652
+ for manifest in skills_manifests:
653
+ try:
654
+ relative_source = manifest.path.parent.relative_to(skills_dir)
655
+ source_display = str(relative_source) if relative_source != Path(".") else "."
656
+ except ValueError:
657
+ source_display = _relative_path(manifest.path.parent)
658
+
659
+ skills_table.add_row(
660
+ manifest.name,
661
+ _truncate(manifest.description or ""),
662
+ source_display,
663
+ "[green]ok[/green]",
664
+ )
665
+
666
+ for error in skill_errors:
667
+ error_path_str = error.get("path", "")
668
+ source_display = "[dim]n/a[/dim]"
669
+ if error_path_str:
670
+ error_path = Path(error_path_str)
671
+ try:
672
+ relative_error = error_path.parent.relative_to(skills_dir)
673
+ source_display = str(relative_error) if relative_error != Path(".") else "."
674
+ except ValueError:
675
+ source_display = _relative_path(error_path.parent)
676
+ message = error.get("error", "Failed to parse skill manifest")
677
+ skills_table.add_row(
678
+ "[red]—[/red]",
679
+ "[red]n/a[/red]",
680
+ source_display,
681
+ f"[red]{_truncate(message, 60)}[/red]",
682
+ )
683
+
684
+ console.print(skills_table)
685
+ else:
686
+ console.print("[yellow]No skills found in the directory[/yellow]")
687
+ else:
688
+ if skills_registry.override_failed and override_directory:
689
+ console.print(
690
+ f"[red]Override directory not found:[/red] {_relative_path(override_directory)}"
691
+ )
692
+ console.print(
693
+ "[yellow]Default folders were not loaded because the override failed[/yellow]"
694
+ )
695
+ else:
696
+ console.print(
697
+ "[dim]Agent Skills not configured. Go to https://fast-agent.ai/agents/skills/[/dim]"
698
+ )
699
+
700
+ # Show help tips
701
+ if config_status == "not_found" or secrets_status == "not_found":
702
+ console.print("\n[bold]Setup Tips:[/bold]")
703
+ console.print(
704
+ "Run [cyan]fast-agent setup[/cyan] to create configuration files. Visit [cyan][link=https://fast-agent.ai]fast-agent.ai[/link][/cyan] for configuration guides. "
705
+ )
706
+ elif config_status == "error" or secrets_status == "error":
707
+ console.print("\n[bold]Config File Issues:[/bold]")
708
+ console.print("Fix the YAML syntax errors in your configuration files")
709
+
710
+ if all(
711
+ not api_keys[provider]["env"] and not api_keys[provider]["config"] for provider in api_keys
712
+ ):
713
+ console.print(
714
+ "\n[yellow]No API keys configured. Set up API keys to use LLM services:[/yellow]"
715
+ )
716
+ console.print("1. Add keys to fastagent.secrets.yaml")
717
+ env_vars = ", ".join(
718
+ [
719
+ ProviderKeyManager.get_env_key_name(p.value)
720
+ for p in Provider
721
+ if p != Provider.FAST_AGENT
722
+ ]
723
+ )
724
+ console.print(f"2. Or set environment variables ({env_vars})")
725
+
726
+
727
+ @app.command()
728
+ def show(
729
+ path: str | None = typer.Argument(None, help="Path to configuration file to display"),
730
+ secrets: bool = typer.Option(
731
+ False, "--secrets", "-s", help="Show secrets file instead of config"
732
+ ),
733
+ ) -> None:
734
+ """Display the configuration file content or search for it."""
735
+ file_type = "secrets" if secrets else "config"
736
+
737
+ if path:
738
+ config_path = Path(path).resolve()
739
+ if not config_path.exists():
740
+ console.print(
741
+ f"[red]Error:[/red] {file_type.capitalize()} file not found at {config_path}"
742
+ )
743
+ raise typer.Exit(1)
744
+ else:
745
+ config_files = find_config_files(Path.cwd())
746
+ config_path = config_files[file_type]
747
+ if not config_path:
748
+ console.print(
749
+ f"[yellow]No {file_type} file found in current directory or parents[/yellow]"
750
+ )
751
+ console.print("Run [cyan]fast-agent setup[/cyan] to create configuration files")
752
+ raise typer.Exit(1)
753
+
754
+ console.print(f"\n[bold]{file_type.capitalize()} file:[/bold] {config_path}\n")
755
+
756
+ try:
757
+ with open(config_path, "r") as f:
758
+ content = f.read()
759
+
760
+ # Try to parse as YAML to check validity
761
+ parsed = yaml.safe_load(content)
762
+
763
+ # Show parsing success status
764
+ console.print("[green]YAML syntax is valid[/green]")
765
+ if parsed is None:
766
+ console.print("[yellow]Warning: File is empty or contains only comments[/yellow]\n")
767
+ else:
768
+ console.print(
769
+ f"[green]Successfully parsed {len(parsed) if isinstance(parsed, dict) else 0} root keys[/green]\n"
770
+ )
771
+
772
+ # Print the content
773
+ console.print(content)
774
+
775
+ except Exception as e:
776
+ console.print(f"[red]Error parsing {file_type} file:[/red] {e}")
777
+
778
+
779
+ @app.callback(invoke_without_command=True)
780
+ def main(ctx: typer.Context) -> None:
781
+ """Check and diagnose FastAgent configuration."""
782
+ if ctx.invoked_subcommand is None:
783
+ show_check_summary()