Skip to content

Commit f4c265e

Browse files
authored
Feat/skills manager (#567)
* chore(skills): move merged PR changes into feat/skills-manager * skill installer * SPECULATIVE * default marketplace config * skills manager * fix type checkers/lint
1 parent 1e1331d commit f4c265e

File tree

17 files changed

+2119
-150
lines changed

17 files changed

+2119
-150
lines changed

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Present links to images and other content in Markdown links -- for example: ![Image](https://link.to.image)
2+

examples/setup/fastagent.config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,9 @@ mcp:
6363
url: "https://huggingface.co/mcp?login"
6464
# headers:
6565
# "Authorization": "Bearer ${ENV_VAR}"
66+
67+
# Skills configuration
68+
skills:
69+
marketplace_urls:
70+
- "https://github.com/huggingface/skills"
71+
- "https://github.com/anthropics/skills"

publish/hf-inference-acp/src/hf_inference_acp/agents.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def acp_mode_info(self) -> ACPModeInfo | None:
216216
@property
217217
def acp_session_commands_allowlist(self) -> set[str]:
218218
"""Restrict built-in session commands for setup flows."""
219-
return {"status"}
219+
return {"status", "skills"}
220220

221221
async def _handle_set_model(self, arguments: str) -> str:
222222
"""Handler for /set-model command."""

publish/hf-inference-acp/src/hf_inference_acp/hf_config.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,22 @@ def load_system_prompt() -> str:
137137
"""
138138

139139

140-
def load_config() -> dict[str, Any]:
140+
def load_config(*, ensure_skills: bool = True) -> dict[str, Any]:
141141
"""Load configuration from the config file.
142142
143+
Args:
144+
ensure_skills: If True, ensure skill registries are configured for
145+
existing users (adds defaults if missing). Set to False to avoid
146+
recursion when called from ensure_skill_registries().
147+
143148
Returns:
144149
Configuration dictionary (as ruamel.yaml CommentedMap to preserve comments)
145150
"""
146151
config_path = ensure_config_exists()
147152

153+
if ensure_skills:
154+
ensure_skill_registries()
155+
148156
if config_path.exists():
149157
with open(config_path) as f:
150158
return _yaml.load(f) or {}
@@ -209,3 +217,47 @@ def update_mcp_server_load_on_start(server_name: str, load_on_start: bool) -> No
209217

210218
with open(config_path, "w") as f:
211219
_yaml.dump(config, f)
220+
221+
222+
DEFAULT_SKILL_REGISTRIES = [
223+
"https://github.com/huggingface/skills",
224+
"https://github.com/anthropics/skills",
225+
]
226+
227+
228+
def ensure_skill_registries() -> None:
229+
"""Ensure skill registries are configured for existing users.
230+
231+
Handles config migration for users with existing config files:
232+
- If skills.marketplace_urls key is missing: add default registries
233+
- If skills.marketplace_urls exists (even if empty): user made an
234+
intentional choice, don't override
235+
"""
236+
config_path = ensure_config_exists()
237+
238+
if config_path.exists():
239+
with open(config_path) as f:
240+
config = _yaml.load(f) or {}
241+
else:
242+
return
243+
244+
modified = False
245+
246+
# Check if skills section exists
247+
if "skills" not in config:
248+
# Key missing entirely - add defaults
249+
config["skills"] = {"marketplace_urls": list(DEFAULT_SKILL_REGISTRIES)}
250+
modified = True
251+
elif config["skills"] is None:
252+
# skills: null - add defaults
253+
config["skills"] = {"marketplace_urls": list(DEFAULT_SKILL_REGISTRIES)}
254+
modified = True
255+
elif "marketplace_urls" not in config["skills"]:
256+
# skills section exists but marketplace_urls key is missing - add defaults
257+
config["skills"]["marketplace_urls"] = list(DEFAULT_SKILL_REGISTRIES)
258+
modified = True
259+
# else: marketplace_urls key exists (empty or with values) - respect user's choice
260+
261+
if modified:
262+
with open(config_path, "w") as f:
263+
_yaml.dump(config, f)

publish/hf-inference-acp/src/hf_inference_acp/resources/hf.config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ mcp:
1616
url: "https://huggingface.co/mcp"
1717
load_on_start: false
1818

19+
skills:
20+
marketplace_urls:
21+
- "https://github.com/huggingface/skills"
22+
- "https://github.com/anthropics/skills"
23+
1924
hf:
2025
# api_key: hf_xxxxxx - you can set your API key here (takes priority)
2126
# default_provider: [together|cerberas|groq|etc.]
27+

publish/hf-inference-acp/src/hf_inference_acp/wizard/wizard_llm.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ async def _handle_welcome(self, user_input: str) -> str:
186186
/login - Get instructions for setting up your token
187187
/set-model - Set the default model
188188
/check - Verify your configuration
189+
/skills add - Install skills from the marketplace
189190
190191
Type any command to continue.
191192
"""
@@ -262,8 +263,10 @@ async def _handle_token_verify(self, user_input: str) -> str:
262263

263264
# Move to model selection
264265
self._state.stage = WizardStage.MODEL_SELECT
265-
return f"""Token verified - connected as: `{username}`
266-
266+
return f"""## Step 1 - Hugging Face Token Setup
267+
268+
Token verified - connected as: `{username}`
269+
267270
{self._render_model_selection()}"""
268271
except Exception as e:
269272
self._state.token_verified = False
@@ -333,7 +336,7 @@ async def _handle_model_select(self, user_input: str) -> str:
333336
else:
334337
return f"Invalid selection: '{user_input}'\n\n{self._render_model_selection()}"
335338

336-
# Move to MCP connect step
339+
# Skip skills selection step and move to MCP connection
337340
self._state.stage = WizardStage.MCP_CONNECT
338341
return self._render_mcp_connect()
339342

@@ -359,11 +362,27 @@ async def _handle_mcp_connect(self, user_input: str) -> str:
359362
if cmd in ("y", "yes"):
360363
self._state.mcp_load_on_start = True
361364
self._state.stage = WizardStage.CONFIRM
362-
return self._render_confirmation()
365+
return "\n".join(
366+
[
367+
"## Skills (Optional)",
368+
"",
369+
"Skills are available. Use `/skills add` to install.",
370+
"",
371+
self._render_confirmation(),
372+
]
373+
)
363374
elif cmd in ("n", "no"):
364375
self._state.mcp_load_on_start = False
365376
self._state.stage = WizardStage.CONFIRM
366-
return self._render_confirmation()
377+
return "\n".join(
378+
[
379+
"## Skills (Optional)",
380+
"",
381+
"Skills are available. Use `/skills add` to install.",
382+
"",
383+
self._render_confirmation(),
384+
]
385+
)
367386
elif cmd in ("quit", "exit", "q"):
368387
return "Setup cancelled. Your configuration was not changed."
369388
else:

0 commit comments

Comments
 (0)