3030_SKILL_NAME_RE = re .compile (r"^[\w.-]+$" )
3131
3232
33+ def _normalize_skill_name (name : str | None ) -> str :
34+ raw = str (name or "" )
35+ return re .sub (r"\s+" , "_" , raw .strip ())
36+
37+
3338def _default_sandbox_skill_path (name : str ) -> str :
3439 return f"{ SANDBOX_WORKSPACE_ROOT } /{ SANDBOX_SKILLS_ROOT } /{ name } /SKILL.md"
3540
@@ -562,13 +567,17 @@ def install_skill_from_zip(
562567
563568 archive_skill_name = None
564569 if skill_name_hint is not None :
565- archive_skill_name = skill_name_hint .strip ()
566- if archive_skill_name and not _SKILL_NAME_RE .match (archive_skill_name ):
570+ archive_skill_name = _normalize_skill_name (skill_name_hint )
571+ if archive_skill_name and not _SKILL_NAME_RE .fullmatch (
572+ archive_skill_name
573+ ):
567574 raise ValueError ("Invalid skill name." )
568575
569576 if root_mode :
570- archive_hint = (archive_skill_name or zip_path_obj .stem ).strip ()
571- if not archive_hint or not _SKILL_NAME_RE .match (archive_hint ):
577+ archive_hint = _normalize_skill_name (
578+ archive_skill_name or zip_path_obj .stem
579+ )
580+ if not archive_hint or not _SKILL_NAME_RE .fullmatch (archive_hint ):
572581 raise ValueError ("Invalid skill name." )
573582 skill_name = archive_hint
574583 else :
@@ -580,16 +589,17 @@ def install_skill_from_zip(
580589 "Zip archive must contain a single top-level folder."
581590 )
582591 archive_root_name = next (iter (top_dirs ))
583- if archive_root_name in {"." , ".." , "" } or not _SKILL_NAME_RE .match (
584- archive_root_name
592+ archive_root_name_normalized = _normalize_skill_name (archive_root_name )
593+ if archive_root_name in {"." , ".." , "" } or not _SKILL_NAME_RE .fullmatch (
594+ archive_root_name_normalized
585595 ):
586596 raise ValueError ("Invalid skill folder name." )
587597 if archive_skill_name :
588- if not _SKILL_NAME_RE .match (archive_skill_name ):
598+ if not _SKILL_NAME_RE .fullmatch (archive_skill_name ):
589599 raise ValueError ("Invalid skill name." )
590600 skill_name = archive_skill_name
591601 else :
592- skill_name = archive_root_name
602+ skill_name = archive_root_name_normalized
593603
594604 for name in names :
595605 if not name :
@@ -620,7 +630,9 @@ def install_skill_from_zip(
620630 if not member_name or _is_ignored_zip_entry (member_name ):
621631 continue
622632 zf .extract (member , tmp_dir )
623- src_dir = Path (tmp_dir ) if root_mode else Path (tmp_dir ) / archive_root_name
633+ src_dir = (
634+ Path (tmp_dir ) if root_mode else Path (tmp_dir ) / archive_root_name
635+ )
624636 normalized_path = _normalize_skill_markdown_path (src_dir )
625637 if normalized_path is None :
626638 raise ValueError ("SKILL.md not found in the skill folder." )
0 commit comments