mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-25 05:52:22 +02:00
53 lines
1.9 KiB
Python
53 lines
1.9 KiB
Python
"""Lay the user's skills down where the backend looks for them, before it boots.
|
|
|
|
backend/apps/skills/skills.py hardwires SKILLS_DIR to ~/.claude/skills and gates the whole
|
|
Skill tool on at least one non-built-in skill existing there. A container that ships without
|
|
them does not get a degraded Skill tool, it gets no Skill tool at all, and the agent then
|
|
answers from general knowledge in a voice that sounds exactly as confident as the real thing.
|
|
|
|
Written pre-boot for the same reason the workflow is: the skill index is read once at startup.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
from typing import List
|
|
|
|
from typeguard import typechecked
|
|
|
|
from runner.run_spec import SkillPayload
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SKILLS_DIRNAME = os.path.join(".claude", "skills")
|
|
|
|
|
|
@typechecked
|
|
def skills_dir(home: str) -> str:
|
|
return os.path.join(home, SKILLS_DIRNAME)
|
|
|
|
|
|
@typechecked
|
|
def write_skills(home: str, skills: List[SkillPayload]) -> int:
|
|
"""Write every skill folder. Returns how many landed.
|
|
|
|
Paths were already proven relative and non-escaping by SkillFile's validator; this re-checks
|
|
the joined result anyway, because the one place a path traversal is worth catching twice is
|
|
the line that actually opens the file.
|
|
"""
|
|
root = skills_dir(home)
|
|
os.makedirs(root, mode=0o700, exist_ok=True)
|
|
written = 0
|
|
for skill in skills:
|
|
folder = os.path.join(root, skill.id)
|
|
for file in skill.files:
|
|
target = os.path.abspath(os.path.join(folder, file.path))
|
|
if not target.startswith(os.path.abspath(folder) + os.sep):
|
|
raise ValueError(f"skill {skill.id!r} file {file.path!r} resolves outside its own folder")
|
|
os.makedirs(os.path.dirname(target), mode=0o700, exist_ok=True)
|
|
with open(target, "w", encoding="utf-8") as handle:
|
|
handle.write(file.text)
|
|
written += 1
|
|
if written:
|
|
logger.info("seeded %d skill(s) into %s", written, root)
|
|
return written
|