Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/skillport/modules/skills/internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
get_latest_commit_sha,
get_remote_tree_hash,
parse_github_url,
rename_single_skill_dir,
)
from .manager import (
SkillInfo,
Expand Down Expand Up @@ -45,6 +46,7 @@
"fetch_github_source_with_info",
"get_latest_commit_sha",
"get_remote_tree_hash",
"rename_single_skill_dir",
"ParsedGitHubURL",
"GitHubFetchResult",
"record_origin",
Expand Down
25 changes: 25 additions & 0 deletions src/skillport/modules/skills/internal/github.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import re
import shutil
import tarfile
import tempfile
from collections.abc import Iterable
Expand Down Expand Up @@ -287,6 +288,30 @@ def _fetch_tree(parsed: ParsedGitHubURL, token: str | None) -> dict:
return data


def rename_single_skill_dir(extracted_dir: Path, skill_name: str) -> Path:
"""Rename extracted GitHub directory to match single skill name.

When a GitHub repo contains a single skill, the extracted temp directory
has a random name (skillport-gh-*). This renames it to match the skill name
from SKILL.md frontmatter for consistency.

Args:
extracted_dir: The temporary extraction directory
skill_name: The skill name from SKILL.md

Returns:
The renamed path (or original if no rename needed)
"""
if skill_name == extracted_dir.name:
return extracted_dir

renamed = extracted_dir.parent / skill_name
if renamed.exists():
shutil.rmtree(renamed)
extracted_dir.rename(renamed)
return renamed


def get_remote_tree_hash(parsed: ParsedGitHubURL, token: str | None, path: str) -> str:
"""Compute remote content hash for a skill path using the GitHub tree API.

Expand Down
12 changes: 4 additions & 8 deletions src/skillport/modules/skills/public/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
fetch_github_source_with_info,
parse_github_url,
record_origin,
rename_single_skill_dir,
resolve_source,
)
from skillport.shared.config import Config
Expand Down Expand Up @@ -96,14 +97,9 @@ def add_skill(
# before adding it to the local catalog.
if source_type == SourceType.GITHUB and len(skills) == 1:
single = skills[0]
if single.name != source_path.name:
renamed = source_path.parent / single.name
if renamed.exists():
shutil.rmtree(renamed)
source_path.rename(renamed)
source_path = renamed
temp_dir = renamed
skills = detect_skills(source_path)
source_path = rename_single_skill_dir(source_path, single.name)
temp_dir = source_path
skills = detect_skills(source_path)
# 単一スキルの場合は origin.path をスキル名で確定させる
if origin_payload is not None:
origin_payload["path"] = origin_payload.get("path") or single.name
Expand Down
11 changes: 4 additions & 7 deletions src/skillport/modules/skills/public/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
get_origin,
get_remote_tree_hash,
parse_github_url,
rename_single_skill_dir,
update_origin,
)
from skillport.shared.config import Config
Expand Down Expand Up @@ -552,13 +553,9 @@ def _sync_stored_hash_if_needed() -> None:
skills = detect_skills(temp_dir)
if skills:
source_skill_path = temp_dir
if len(skills) == 1 and skills[0].name != temp_dir.name:
renamed = temp_dir.parent / skills[0].name
if renamed.exists():
shutil.rmtree(renamed)
temp_dir.rename(renamed)
source_skill_path = renamed
temp_dir = renamed
if len(skills) == 1:
source_skill_path = rename_single_skill_dir(temp_dir, skills[0].name)
temp_dir = source_skill_path
shutil.copytree(source_skill_path, dest_path)
else:
shutil.copytree(temp_dir, dest_path)
Expand Down