fast-agent-mcp 0.3.15__py3-none-any.whl → 0.3.16__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.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- fast_agent/__init__.py +2 -0
- fast_agent/agents/agent_types.py +5 -0
- fast_agent/agents/llm_agent.py +7 -0
- fast_agent/agents/llm_decorator.py +6 -0
- fast_agent/agents/mcp_agent.py +134 -10
- fast_agent/cli/__main__.py +35 -0
- fast_agent/cli/commands/check_config.py +85 -0
- fast_agent/cli/commands/go.py +100 -36
- fast_agent/cli/constants.py +13 -1
- fast_agent/cli/main.py +1 -0
- fast_agent/config.py +39 -10
- fast_agent/constants.py +8 -0
- fast_agent/context.py +24 -15
- fast_agent/core/direct_decorators.py +9 -0
- fast_agent/core/fastagent.py +101 -1
- fast_agent/core/logging/listeners.py +8 -0
- fast_agent/interfaces.py +8 -0
- fast_agent/llm/fastagent_llm.py +45 -0
- fast_agent/llm/memory.py +26 -1
- fast_agent/llm/provider/anthropic/llm_anthropic.py +112 -0
- fast_agent/llm/provider/openai/llm_openai.py +184 -18
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/resources/setup/agent.py +2 -0
- fast_agent/resources/setup/fastagent.config.yaml +6 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +200 -0
- fast_agent/tools/shell_runtime.py +404 -0
- fast_agent/ui/console_display.py +396 -129
- fast_agent/ui/elicitation_form.py +76 -24
- fast_agent/ui/elicitation_style.py +2 -2
- fast_agent/ui/enhanced_prompt.py +81 -25
- fast_agent/ui/history_display.py +20 -5
- fast_agent/ui/interactive_prompt.py +108 -3
- fast_agent/ui/markdown_truncator.py +1 -1
- {fast_agent_mcp-0.3.15.dist-info → fast_agent_mcp-0.3.16.dist-info}/METADATA +8 -7
- {fast_agent_mcp-0.3.15.dist-info → fast_agent_mcp-0.3.16.dist-info}/RECORD +39 -35
- {fast_agent_mcp-0.3.15.dist-info → fast_agent_mcp-0.3.16.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.3.15.dist-info → fast_agent_mcp-0.3.16.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.3.15.dist-info → fast_agent_mcp-0.3.16.dist-info}/licenses/LICENSE +0 -0
fast_agent/__init__.py
CHANGED
|
@@ -23,6 +23,7 @@ from fast_agent.config import (
|
|
|
23
23
|
OpenRouterSettings,
|
|
24
24
|
OpenTelemetrySettings,
|
|
25
25
|
Settings,
|
|
26
|
+
SkillsSettings,
|
|
26
27
|
TensorZeroSettings,
|
|
27
28
|
XAISettings,
|
|
28
29
|
)
|
|
@@ -126,6 +127,7 @@ __all__ = [
|
|
|
126
127
|
"BedrockSettings",
|
|
127
128
|
"HuggingFaceSettings",
|
|
128
129
|
"LoggerSettings",
|
|
130
|
+
"SkillsSettings",
|
|
129
131
|
# Progress and event tracking (lazy loaded)
|
|
130
132
|
"ProgressAction",
|
|
131
133
|
"ProgressEvent",
|
fast_agent/agents/agent_types.py
CHANGED
|
@@ -4,10 +4,13 @@ Type definitions for agents and agent configurations.
|
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
from enum import StrEnum, auto
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
from typing import Dict, List, Optional
|
|
8
9
|
|
|
9
10
|
from mcp.client.session import ElicitationFnT
|
|
10
11
|
|
|
12
|
+
from fast_agent.skills import SkillManifest, SkillRegistry
|
|
13
|
+
|
|
11
14
|
# Forward imports to avoid circular dependencies
|
|
12
15
|
from fast_agent.types import RequestParams
|
|
13
16
|
|
|
@@ -36,6 +39,8 @@ class AgentConfig:
|
|
|
36
39
|
tools: Optional[Dict[str, List[str]]] = None
|
|
37
40
|
resources: Optional[Dict[str, List[str]]] = None
|
|
38
41
|
prompts: Optional[Dict[str, List[str]]] = None
|
|
42
|
+
skills: SkillManifest | SkillRegistry | Path | str | None = None
|
|
43
|
+
skill_manifests: List[SkillManifest] = field(default_factory=list, repr=False)
|
|
39
44
|
model: str | None = None
|
|
40
45
|
use_history: bool = True
|
|
41
46
|
default_request_params: RequestParams | None = None
|
fast_agent/agents/llm_agent.py
CHANGED
|
@@ -246,6 +246,7 @@ class LlmAgent(LlmDecorator):
|
|
|
246
246
|
display_model = self.llm.model_name if self._llm else None
|
|
247
247
|
|
|
248
248
|
remove_listener: Callable[[], None] | None = None
|
|
249
|
+
remove_tool_listener: Callable[[], None] | None = None
|
|
249
250
|
|
|
250
251
|
with self.display.streaming_assistant_message(
|
|
251
252
|
name=display_name,
|
|
@@ -253,8 +254,12 @@ class LlmAgent(LlmDecorator):
|
|
|
253
254
|
) as stream_handle:
|
|
254
255
|
try:
|
|
255
256
|
remove_listener = self.llm.add_stream_listener(stream_handle.update)
|
|
257
|
+
remove_tool_listener = self.llm.add_tool_stream_listener(
|
|
258
|
+
stream_handle.handle_tool_event
|
|
259
|
+
)
|
|
256
260
|
except Exception:
|
|
257
261
|
remove_listener = None
|
|
262
|
+
remove_tool_listener = None
|
|
258
263
|
|
|
259
264
|
try:
|
|
260
265
|
result, summary = await self._generate_with_summary(
|
|
@@ -263,6 +268,8 @@ class LlmAgent(LlmDecorator):
|
|
|
263
268
|
finally:
|
|
264
269
|
if remove_listener:
|
|
265
270
|
remove_listener()
|
|
271
|
+
if remove_tool_listener:
|
|
272
|
+
remove_tool_listener()
|
|
266
273
|
|
|
267
274
|
if summary:
|
|
268
275
|
summary_text = Text(f"\n\n{summary.message}", style="dim red italic")
|
|
@@ -718,6 +718,12 @@ class LlmDecorator(AgentProtocol):
|
|
|
718
718
|
return self._llm.message_history
|
|
719
719
|
return []
|
|
720
720
|
|
|
721
|
+
def pop_last_message(self) -> PromptMessageExtended | None:
|
|
722
|
+
"""Remove and return the most recent message from the conversation history."""
|
|
723
|
+
if self._llm:
|
|
724
|
+
return self._llm.pop_last_message()
|
|
725
|
+
return None
|
|
726
|
+
|
|
721
727
|
@property
|
|
722
728
|
def usage_accumulator(self) -> UsageAccumulator | None:
|
|
723
729
|
"""
|
fast_agent/agents/mcp_agent.py
CHANGED
|
@@ -42,12 +42,15 @@ from fast_agent.core.exceptions import PromptExitError
|
|
|
42
42
|
from fast_agent.core.logging.logger import get_logger
|
|
43
43
|
from fast_agent.interfaces import FastAgentLLMProtocol
|
|
44
44
|
from fast_agent.mcp.mcp_aggregator import MCPAggregator, ServerStatus
|
|
45
|
+
from fast_agent.skills.registry import format_skills_for_prompt
|
|
45
46
|
from fast_agent.tools.elicitation import (
|
|
46
47
|
get_elicitation_tool,
|
|
47
48
|
run_elicitation_form,
|
|
48
49
|
set_elicitation_input_callback,
|
|
49
50
|
)
|
|
51
|
+
from fast_agent.tools.shell_runtime import ShellRuntime
|
|
50
52
|
from fast_agent.types import PromptMessageExtended, RequestParams
|
|
53
|
+
from fast_agent.ui import console
|
|
51
54
|
|
|
52
55
|
# Define a TypeVar for models
|
|
53
56
|
ModelT = TypeVar("ModelT", bound=BaseModel)
|
|
@@ -59,6 +62,7 @@ if TYPE_CHECKING:
|
|
|
59
62
|
|
|
60
63
|
from fast_agent.context import Context
|
|
61
64
|
from fast_agent.llm.usage_tracking import UsageAccumulator
|
|
65
|
+
from fast_agent.skills import SkillManifest
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
class McpAgent(ABC, ToolAgent):
|
|
@@ -73,7 +77,6 @@ class McpAgent(ABC, ToolAgent):
|
|
|
73
77
|
self,
|
|
74
78
|
config: AgentConfig,
|
|
75
79
|
connection_persistence: bool = True,
|
|
76
|
-
# legacy human_input_callback removed
|
|
77
80
|
context: "Context | None" = None,
|
|
78
81
|
**kwargs,
|
|
79
82
|
) -> None:
|
|
@@ -96,6 +99,69 @@ class McpAgent(ABC, ToolAgent):
|
|
|
96
99
|
self.instruction = self.config.instruction
|
|
97
100
|
self.executor = context.executor if context else None
|
|
98
101
|
self.logger = get_logger(f"{__name__}.{self._name}")
|
|
102
|
+
manifests: List[SkillManifest] = list(getattr(self.config, "skill_manifests", []) or [])
|
|
103
|
+
if not manifests and context and getattr(context, "skill_registry", None):
|
|
104
|
+
try:
|
|
105
|
+
manifests = list(context.skill_registry.load_manifests()) # type: ignore[assignment]
|
|
106
|
+
except Exception:
|
|
107
|
+
manifests = []
|
|
108
|
+
|
|
109
|
+
self._skill_manifests = list(manifests)
|
|
110
|
+
self._skill_map: Dict[str, SkillManifest] = {
|
|
111
|
+
manifest.name: manifest for manifest in manifests
|
|
112
|
+
}
|
|
113
|
+
self._agent_skills_warning_shown = False
|
|
114
|
+
shell_flag_requested = bool(context and getattr(context, "shell_runtime", False))
|
|
115
|
+
skills_configured = bool(self._skill_manifests)
|
|
116
|
+
self._shell_runtime_activation_reason: str | None = None
|
|
117
|
+
|
|
118
|
+
if shell_flag_requested and skills_configured:
|
|
119
|
+
self._shell_runtime_activation_reason = (
|
|
120
|
+
"via --shell flag and agent skills configuration"
|
|
121
|
+
)
|
|
122
|
+
elif shell_flag_requested:
|
|
123
|
+
self._shell_runtime_activation_reason = "via --shell flag"
|
|
124
|
+
elif skills_configured:
|
|
125
|
+
self._shell_runtime_activation_reason = "because agent skills are configured"
|
|
126
|
+
|
|
127
|
+
# Get timeout configuration from context
|
|
128
|
+
timeout_seconds = 90 # default
|
|
129
|
+
warning_interval_seconds = 30 # default
|
|
130
|
+
if context and context.config:
|
|
131
|
+
shell_config = getattr(context.config, "shell_execution", None)
|
|
132
|
+
if shell_config:
|
|
133
|
+
timeout_seconds = getattr(shell_config, "timeout_seconds", 90)
|
|
134
|
+
warning_interval_seconds = getattr(shell_config, "warning_interval_seconds", 30)
|
|
135
|
+
|
|
136
|
+
# Derive skills directory from this agent's manifests (respects per-agent config)
|
|
137
|
+
skills_directory = None
|
|
138
|
+
if self._skill_manifests:
|
|
139
|
+
# Get the skills directory from the first manifest's path
|
|
140
|
+
# Path structure: .fast-agent/skills/skill-name/SKILL.md
|
|
141
|
+
# So we need parent.parent of the manifest path
|
|
142
|
+
first_manifest = self._skill_manifests[0]
|
|
143
|
+
if first_manifest.path:
|
|
144
|
+
skills_directory = first_manifest.path.parent.parent
|
|
145
|
+
|
|
146
|
+
self._shell_runtime = ShellRuntime(
|
|
147
|
+
self._shell_runtime_activation_reason,
|
|
148
|
+
self.logger,
|
|
149
|
+
timeout_seconds=timeout_seconds,
|
|
150
|
+
warning_interval_seconds=warning_interval_seconds,
|
|
151
|
+
skills_directory=skills_directory,
|
|
152
|
+
)
|
|
153
|
+
self._shell_runtime_enabled = self._shell_runtime.enabled
|
|
154
|
+
self._shell_access_modes: tuple[str, ...] = ()
|
|
155
|
+
if self._shell_runtime_enabled:
|
|
156
|
+
modes: list[str] = ["[red]direct[/red]"]
|
|
157
|
+
if skills_configured:
|
|
158
|
+
modes.append("skills")
|
|
159
|
+
if shell_flag_requested:
|
|
160
|
+
modes.append("command switch")
|
|
161
|
+
self._shell_access_modes = tuple(modes)
|
|
162
|
+
self._bash_tool = self._shell_runtime.tool
|
|
163
|
+
if self._shell_runtime_enabled:
|
|
164
|
+
self._shell_runtime.announce()
|
|
99
165
|
|
|
100
166
|
# Store the default request params from config
|
|
101
167
|
self._default_request_params = self.config.default_request_params
|
|
@@ -207,6 +273,24 @@ class McpAgent(ABC, ToolAgent):
|
|
|
207
273
|
"{{serverInstructions}}", server_instructions
|
|
208
274
|
)
|
|
209
275
|
|
|
276
|
+
skills_placeholder_present = "{{agentSkills}}" in self.instruction
|
|
277
|
+
|
|
278
|
+
if skills_placeholder_present:
|
|
279
|
+
agent_skills = format_skills_for_prompt(self._skill_manifests)
|
|
280
|
+
self.instruction = self.instruction.replace("{{agentSkills}}", agent_skills)
|
|
281
|
+
self._agent_skills_warning_shown = True
|
|
282
|
+
elif self._skill_manifests and not self._agent_skills_warning_shown:
|
|
283
|
+
warning_message = (
|
|
284
|
+
"Agent skills are configured but the system prompt does not include {{agentSkills}}. "
|
|
285
|
+
"Skill descriptions will not be added to the system prompt."
|
|
286
|
+
)
|
|
287
|
+
self.logger.warning(warning_message)
|
|
288
|
+
try:
|
|
289
|
+
console.console.print(f"[yellow]{warning_message}[/yellow]")
|
|
290
|
+
except Exception: # pragma: no cover - console fallback
|
|
291
|
+
pass
|
|
292
|
+
self._agent_skills_warning_shown = True
|
|
293
|
+
|
|
210
294
|
# Update default request params to match
|
|
211
295
|
if self._default_request_params:
|
|
212
296
|
self._default_request_params.systemPrompt = self.instruction
|
|
@@ -315,11 +399,12 @@ class McpAgent(ABC, ToolAgent):
|
|
|
315
399
|
"""
|
|
316
400
|
# Get all tools from the aggregator
|
|
317
401
|
result = await self._aggregator.list_tools()
|
|
402
|
+
aggregator_tools = list(result.tools)
|
|
318
403
|
|
|
319
404
|
# Apply filtering if tools are specified in config
|
|
320
405
|
if self.config.tools is not None:
|
|
321
406
|
filtered_tools = []
|
|
322
|
-
for tool in
|
|
407
|
+
for tool in aggregator_tools:
|
|
323
408
|
# Extract server name from tool name, handling server names with hyphens
|
|
324
409
|
server_name = None
|
|
325
410
|
for configured_server in self.config.tools.keys():
|
|
@@ -334,7 +419,12 @@ class McpAgent(ABC, ToolAgent):
|
|
|
334
419
|
if self._matches_pattern(tool.name, pattern, server_name):
|
|
335
420
|
filtered_tools.append(tool)
|
|
336
421
|
break
|
|
337
|
-
|
|
422
|
+
aggregator_tools = filtered_tools
|
|
423
|
+
|
|
424
|
+
result.tools = aggregator_tools
|
|
425
|
+
|
|
426
|
+
if self._bash_tool and all(tool.name != self._bash_tool.name for tool in result.tools):
|
|
427
|
+
result.tools.append(self._bash_tool)
|
|
338
428
|
|
|
339
429
|
# Append human input tool if enabled and available
|
|
340
430
|
if self.config.human_input and getattr(self, "_human_input_tool", None):
|
|
@@ -353,6 +443,9 @@ class McpAgent(ABC, ToolAgent):
|
|
|
353
443
|
Returns:
|
|
354
444
|
Result of the tool call
|
|
355
445
|
"""
|
|
446
|
+
if self._shell_runtime.tool and name == self._shell_runtime.tool.name:
|
|
447
|
+
return await self._shell_runtime.execute(arguments)
|
|
448
|
+
|
|
356
449
|
if name == HUMAN_INPUT_TOOL_NAME:
|
|
357
450
|
# Call the elicitation-backed human input tool
|
|
358
451
|
return await self._call_human_input_tool(arguments)
|
|
@@ -615,6 +708,10 @@ class McpAgent(ABC, ToolAgent):
|
|
|
615
708
|
namespaced_tool.tool.name
|
|
616
709
|
for namespaced_tool in self._aggregator._namespaced_tool_map.values()
|
|
617
710
|
]
|
|
711
|
+
if self._shell_runtime.tool:
|
|
712
|
+
available_tools.append(self._shell_runtime.tool.name)
|
|
713
|
+
|
|
714
|
+
available_tools = list(dict.fromkeys(available_tools))
|
|
618
715
|
|
|
619
716
|
# Process each tool call using our aggregator
|
|
620
717
|
for correlation_id, tool_request in request.tool_calls.items():
|
|
@@ -628,6 +725,8 @@ class McpAgent(ABC, ToolAgent):
|
|
|
628
725
|
tool_available = False
|
|
629
726
|
if tool_name == HUMAN_INPUT_TOOL_NAME:
|
|
630
727
|
tool_available = True
|
|
728
|
+
elif self._bash_tool and tool_name == self._bash_tool.name:
|
|
729
|
+
tool_available = True
|
|
631
730
|
elif namespaced_tool:
|
|
632
731
|
tool_available = True
|
|
633
732
|
else:
|
|
@@ -654,6 +753,14 @@ class McpAgent(ABC, ToolAgent):
|
|
|
654
753
|
# Tool not found in list, no highlighting
|
|
655
754
|
pass
|
|
656
755
|
|
|
756
|
+
metadata: dict[str, Any] | None = None
|
|
757
|
+
if (
|
|
758
|
+
self._shell_runtime_enabled
|
|
759
|
+
and self._shell_runtime.tool
|
|
760
|
+
and display_tool_name == self._shell_runtime.tool.name
|
|
761
|
+
):
|
|
762
|
+
metadata = self._shell_runtime.metadata(tool_args.get("command"))
|
|
763
|
+
|
|
657
764
|
self.display.show_tool_call(
|
|
658
765
|
name=self._name,
|
|
659
766
|
tool_args=tool_args,
|
|
@@ -661,10 +768,11 @@ class McpAgent(ABC, ToolAgent):
|
|
|
661
768
|
tool_name=display_tool_name,
|
|
662
769
|
highlight_index=highlight_index,
|
|
663
770
|
max_item_length=12,
|
|
771
|
+
metadata=metadata,
|
|
664
772
|
)
|
|
665
773
|
|
|
666
774
|
try:
|
|
667
|
-
# Use
|
|
775
|
+
# Use the appropriate handler for this tool
|
|
668
776
|
result = await self.call_tool(tool_name, tool_args)
|
|
669
777
|
tool_results[correlation_id] = result
|
|
670
778
|
|
|
@@ -675,12 +783,13 @@ class McpAgent(ABC, ToolAgent):
|
|
|
675
783
|
namespaced_tool.server_name
|
|
676
784
|
)
|
|
677
785
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
786
|
+
if not getattr(result, "_suppress_display", False):
|
|
787
|
+
self.display.show_tool_result(
|
|
788
|
+
name=self._name,
|
|
789
|
+
result=result,
|
|
790
|
+
tool_name=display_tool_name,
|
|
791
|
+
skybridge_config=skybridge_config,
|
|
792
|
+
)
|
|
684
793
|
|
|
685
794
|
self.logger.debug(f"MCP tool {display_tool_name} executed successfully")
|
|
686
795
|
except Exception as e:
|
|
@@ -873,6 +982,9 @@ class McpAgent(ABC, ToolAgent):
|
|
|
873
982
|
|
|
874
983
|
result[special_server_name].append(self._human_input_tool)
|
|
875
984
|
|
|
985
|
+
# if self._skill_lookup_tool:
|
|
986
|
+
# result.setdefault("__skills__", []).append(self._skill_lookup_tool)
|
|
987
|
+
|
|
876
988
|
return result
|
|
877
989
|
|
|
878
990
|
@property
|
|
@@ -985,6 +1097,18 @@ class McpAgent(ABC, ToolAgent):
|
|
|
985
1097
|
Convert a Tool to an AgentSkill.
|
|
986
1098
|
"""
|
|
987
1099
|
|
|
1100
|
+
if tool.name in self._skill_map:
|
|
1101
|
+
manifest = self._skill_map[tool.name]
|
|
1102
|
+
return AgentSkill(
|
|
1103
|
+
id=f"skill:{manifest.name}",
|
|
1104
|
+
name=manifest.name,
|
|
1105
|
+
description=manifest.description or "",
|
|
1106
|
+
tags=["skill"],
|
|
1107
|
+
examples=None,
|
|
1108
|
+
input_modes=None,
|
|
1109
|
+
output_modes=None,
|
|
1110
|
+
)
|
|
1111
|
+
|
|
988
1112
|
_, tool_without_namespace = await self._parse_resource_name(tool.name, "tool")
|
|
989
1113
|
return AgentSkill(
|
|
990
1114
|
id=tool.name,
|
fast_agent/cli/__main__.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
1
3
|
import sys
|
|
2
4
|
|
|
3
5
|
from fast_agent.cli.constants import GO_SPECIFIC_OPTIONS, KNOWN_SUBCOMMANDS
|
|
@@ -8,6 +10,39 @@ from fast_agent.cli.main import app
|
|
|
8
10
|
|
|
9
11
|
def main():
|
|
10
12
|
"""Main entry point that handles auto-routing to 'go' command."""
|
|
13
|
+
try:
|
|
14
|
+
loop = asyncio.get_event_loop()
|
|
15
|
+
|
|
16
|
+
def _log_asyncio_exception(loop: asyncio.AbstractEventLoop, context: dict) -> None:
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("fast_agent.asyncio")
|
|
20
|
+
|
|
21
|
+
message = context.get("message", "(no message)")
|
|
22
|
+
task = context.get("task")
|
|
23
|
+
future = context.get("future")
|
|
24
|
+
handle = context.get("handle")
|
|
25
|
+
source_traceback = context.get("source_traceback")
|
|
26
|
+
exception = context.get("exception")
|
|
27
|
+
|
|
28
|
+
details = {
|
|
29
|
+
"message": message,
|
|
30
|
+
"task": repr(task) if task else None,
|
|
31
|
+
"future": repr(future) if future else None,
|
|
32
|
+
"handle": repr(handle) if handle else None,
|
|
33
|
+
"source_traceback": [str(frame) for frame in source_traceback] if source_traceback else None,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
logger.error("Unhandled asyncio error: %s", message)
|
|
37
|
+
logger.error("Asyncio context: %s", json.dumps(details, indent=2))
|
|
38
|
+
|
|
39
|
+
if exception:
|
|
40
|
+
logger.exception("Asyncio exception", exc_info=exception)
|
|
41
|
+
|
|
42
|
+
loop.set_exception_handler(_log_asyncio_exception)
|
|
43
|
+
except RuntimeError:
|
|
44
|
+
# No running loop yet (rare for sync entry), safe to ignore
|
|
45
|
+
pass
|
|
11
46
|
# Check if we should auto-route to 'go'
|
|
12
47
|
if len(sys.argv) > 1:
|
|
13
48
|
# Check if first arg is not already a subcommand
|
|
@@ -13,6 +13,7 @@ from rich.text import Text
|
|
|
13
13
|
|
|
14
14
|
from fast_agent.llm.provider_key_manager import API_KEY_HINT_TEXT, ProviderKeyManager
|
|
15
15
|
from fast_agent.llm.provider_types import Provider
|
|
16
|
+
from fast_agent.skills import SkillRegistry
|
|
16
17
|
from fast_agent.ui.console import console
|
|
17
18
|
|
|
18
19
|
app = typer.Typer(
|
|
@@ -169,6 +170,7 @@ def get_config_summary(config_path: Optional[Path]) -> dict:
|
|
|
169
170
|
"step_seconds": default_settings.mcp_timeline.step_seconds,
|
|
170
171
|
},
|
|
171
172
|
"mcp_servers": [],
|
|
173
|
+
"skills_directory": None,
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
if not config_path:
|
|
@@ -278,6 +280,13 @@ def get_config_summary(config_path: Optional[Path]) -> dict:
|
|
|
278
280
|
|
|
279
281
|
result["mcp_servers"].append(server_info)
|
|
280
282
|
|
|
283
|
+
# Skills directory override
|
|
284
|
+
skills_cfg = config.get("skills") if isinstance(config, dict) else None
|
|
285
|
+
if isinstance(skills_cfg, dict):
|
|
286
|
+
directory_value = skills_cfg.get("directory")
|
|
287
|
+
if isinstance(directory_value, str) and directory_value.strip():
|
|
288
|
+
result["skills_directory"] = directory_value.strip()
|
|
289
|
+
|
|
281
290
|
except Exception as e:
|
|
282
291
|
# File exists but has parse errors
|
|
283
292
|
result["status"] = "error"
|
|
@@ -388,6 +397,18 @@ def show_check_summary() -> None:
|
|
|
388
397
|
|
|
389
398
|
console.print(env_table)
|
|
390
399
|
|
|
400
|
+
def _relative_path(path: Path) -> str:
|
|
401
|
+
try:
|
|
402
|
+
return str(path.relative_to(cwd))
|
|
403
|
+
except ValueError:
|
|
404
|
+
return str(path)
|
|
405
|
+
|
|
406
|
+
skills_override = config_summary.get("skills_directory")
|
|
407
|
+
override_directory = Path(skills_override).expanduser() if skills_override else None
|
|
408
|
+
skills_registry = SkillRegistry(base_dir=cwd, override_directory=override_directory)
|
|
409
|
+
skills_dir = skills_registry.directory
|
|
410
|
+
skills_manifests, skill_errors = skills_registry.load_manifests_with_errors()
|
|
411
|
+
|
|
391
412
|
# Logger Settings panel with two-column layout
|
|
392
413
|
logger = config_summary.get("logger", {})
|
|
393
414
|
logger_table = Table(show_header=True, box=None)
|
|
@@ -613,6 +634,70 @@ def show_check_summary() -> None:
|
|
|
613
634
|
_print_section_header("MCP Servers", color="blue")
|
|
614
635
|
console.print(servers_table)
|
|
615
636
|
|
|
637
|
+
_print_section_header("Agent Skills", color="blue")
|
|
638
|
+
if skills_dir:
|
|
639
|
+
console.print(f"Directory: [green]{_relative_path(skills_dir)}[/green]")
|
|
640
|
+
|
|
641
|
+
if skills_manifests or skill_errors:
|
|
642
|
+
skills_table = Table(show_header=True, box=None)
|
|
643
|
+
skills_table.add_column("Name", style="cyan", header_style="bold bright_white")
|
|
644
|
+
skills_table.add_column("Description", style="white", header_style="bold bright_white")
|
|
645
|
+
skills_table.add_column("Source", style="dim", header_style="bold bright_white")
|
|
646
|
+
skills_table.add_column("Status", style="green", header_style="bold bright_white")
|
|
647
|
+
|
|
648
|
+
def _truncate(text: str, length: int = 70) -> str:
|
|
649
|
+
if len(text) <= length:
|
|
650
|
+
return text
|
|
651
|
+
return text[: length - 3] + "..."
|
|
652
|
+
|
|
653
|
+
for manifest in skills_manifests:
|
|
654
|
+
try:
|
|
655
|
+
relative_source = manifest.path.parent.relative_to(skills_dir)
|
|
656
|
+
source_display = str(relative_source) if relative_source != Path(".") else "."
|
|
657
|
+
except ValueError:
|
|
658
|
+
source_display = _relative_path(manifest.path.parent)
|
|
659
|
+
|
|
660
|
+
skills_table.add_row(
|
|
661
|
+
manifest.name,
|
|
662
|
+
_truncate(manifest.description or ""),
|
|
663
|
+
source_display,
|
|
664
|
+
"[green]ok[/green]",
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
for error in skill_errors:
|
|
668
|
+
error_path_str = error.get("path", "")
|
|
669
|
+
source_display = "[dim]n/a[/dim]"
|
|
670
|
+
if error_path_str:
|
|
671
|
+
error_path = Path(error_path_str)
|
|
672
|
+
try:
|
|
673
|
+
relative_error = error_path.parent.relative_to(skills_dir)
|
|
674
|
+
source_display = str(relative_error) if relative_error != Path(".") else "."
|
|
675
|
+
except ValueError:
|
|
676
|
+
source_display = _relative_path(error_path.parent)
|
|
677
|
+
message = error.get("error", "Failed to parse skill manifest")
|
|
678
|
+
skills_table.add_row(
|
|
679
|
+
"[red]—[/red]",
|
|
680
|
+
"[red]n/a[/red]",
|
|
681
|
+
source_display,
|
|
682
|
+
f"[red]{_truncate(message, 60)}[/red]",
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
console.print(skills_table)
|
|
686
|
+
else:
|
|
687
|
+
console.print("[yellow]No skills found in the directory[/yellow]")
|
|
688
|
+
else:
|
|
689
|
+
if skills_registry.override_failed and override_directory:
|
|
690
|
+
console.print(
|
|
691
|
+
f"[red]Override directory not found:[/red] {_relative_path(override_directory)}"
|
|
692
|
+
)
|
|
693
|
+
console.print(
|
|
694
|
+
"[yellow]Default folders were not loaded because the override failed[/yellow]"
|
|
695
|
+
)
|
|
696
|
+
else:
|
|
697
|
+
console.print(
|
|
698
|
+
"[dim]Agent Skills not configured. Go to https://fast-agent.ai/agents/skills/[/dim]"
|
|
699
|
+
)
|
|
700
|
+
|
|
616
701
|
# Show help tips
|
|
617
702
|
if config_status == "not_found" or secrets_status == "not_found":
|
|
618
703
|
console.print("\n[bold]Setup Tips:[/bold]")
|