First version. I can init
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
"""File system operations for myclaude."""
|
||||
|
||||
import shutil
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass
|
||||
class SkillsStatus:
|
||||
"""Diff between local and remote skills.
|
||||
|
||||
Attributes:
|
||||
local_only: Skills present locally but not in the remote repo.
|
||||
remote_only: Skills present in the remote repo but not locally.
|
||||
in_both: Skills present in both local and remote.
|
||||
"""
|
||||
|
||||
local_only: list[str]
|
||||
remote_only: list[str]
|
||||
in_both: list[str]
|
||||
|
||||
|
||||
def copy_skills_to_project(
|
||||
repo_dir: Path,
|
||||
project_dir: Path,
|
||||
skills: list[str] | None = None,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""Copy skills from the repo to the project's .claude/skills directory.
|
||||
|
||||
Args:
|
||||
repo_dir: Path to the cloned central repository.
|
||||
project_dir: Path to the target project directory.
|
||||
skills: List of skill names to copy. If None, copies all skills.
|
||||
force: If True, overwrites the existing skills directory.
|
||||
|
||||
Raises:
|
||||
FileExistsError: If .claude/skills already exists and force is False.
|
||||
ValueError: If a requested skill does not exist in the repo.
|
||||
"""
|
||||
skills_dst = project_dir / ".claude" / "skills"
|
||||
|
||||
if skills_dst.exists() and not force:
|
||||
raise FileExistsError(
|
||||
f"Directory '{skills_dst}' already exists. Use --force to overwrite."
|
||||
)
|
||||
|
||||
available = list_skills_in_dir(repo_dir / "skills")
|
||||
skills_to_copy = _resolve_skills(skills, available)
|
||||
|
||||
if skills_dst.exists() and force:
|
||||
shutil.rmtree(skills_dst)
|
||||
|
||||
skills_dst.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for skill in skills_to_copy:
|
||||
shutil.copytree(repo_dir / "skills" / skill, skills_dst / skill)
|
||||
|
||||
|
||||
def copy_claude_md_to_project(repo_dir: Path, project_dir: Path) -> None:
|
||||
"""Copy CLAUDE.md from the repo to the project root.
|
||||
|
||||
Args:
|
||||
repo_dir: Path to the cloned central repository.
|
||||
project_dir: Path to the target project directory.
|
||||
"""
|
||||
shutil.copy2(repo_dir / "CLAUDE.md", project_dir / "CLAUDE.md")
|
||||
|
||||
|
||||
def copy_skills_to_repo(
|
||||
project_dir: Path,
|
||||
repo_dir: Path,
|
||||
skills: list[str] | None = None,
|
||||
) -> None:
|
||||
"""Copy skills from the project to the central repository.
|
||||
|
||||
Args:
|
||||
project_dir: Path to the source project directory.
|
||||
repo_dir: Path to the cloned central repository.
|
||||
skills: List of skill names to push. If None, pushes all local skills.
|
||||
|
||||
Raises:
|
||||
ValueError: If a requested skill does not exist locally.
|
||||
"""
|
||||
available = collect_local_skills(project_dir)
|
||||
skills_to_push = _resolve_skills(skills, available)
|
||||
|
||||
for skill in skills_to_push:
|
||||
dst = repo_dir / "skills" / skill
|
||||
if dst.exists():
|
||||
shutil.rmtree(dst)
|
||||
shutil.copytree(project_dir / ".claude" / "skills" / skill, dst)
|
||||
|
||||
|
||||
def copy_claude_md_to_repo(project_dir: Path, repo_dir: Path) -> None:
|
||||
"""Copy CLAUDE.md from the project to the central repository.
|
||||
|
||||
Args:
|
||||
project_dir: Path to the source project directory.
|
||||
repo_dir: Path to the cloned central repository.
|
||||
"""
|
||||
shutil.copy2(project_dir / "CLAUDE.md", repo_dir / "CLAUDE.md")
|
||||
|
||||
|
||||
def collect_local_skills(project_dir: Path) -> list[str]:
|
||||
"""List skill names present in the project's .claude/skills directory.
|
||||
|
||||
Args:
|
||||
project_dir: Path to the project directory.
|
||||
|
||||
Returns:
|
||||
Sorted list of skill directory names.
|
||||
"""
|
||||
return list_skills_in_dir(project_dir / ".claude" / "skills")
|
||||
|
||||
|
||||
def list_skills_in_repo(repo_dir: Path) -> list[str]:
|
||||
"""List skill names available in the central repository.
|
||||
|
||||
Args:
|
||||
repo_dir: Path to the cloned central repository.
|
||||
|
||||
Returns:
|
||||
Sorted list of skill directory names.
|
||||
"""
|
||||
return list_skills_in_dir(repo_dir / "skills")
|
||||
|
||||
|
||||
def get_skills_status(project_dir: Path, repo_dir: Path) -> SkillsStatus:
|
||||
"""Compare local skills with those available in the central repo.
|
||||
|
||||
Args:
|
||||
project_dir: Path to the local project directory.
|
||||
repo_dir: Path to the cloned central repository.
|
||||
|
||||
Returns:
|
||||
A SkillsStatus instance describing the diff.
|
||||
"""
|
||||
local = set(collect_local_skills(project_dir))
|
||||
remote = set(list_skills_in_repo(repo_dir))
|
||||
return SkillsStatus(
|
||||
local_only=sorted(local - remote),
|
||||
remote_only=sorted(remote - local),
|
||||
in_both=sorted(local & remote),
|
||||
)
|
||||
|
||||
|
||||
def list_skills_in_dir(directory: Path) -> list[str]:
|
||||
"""List subdirectory names inside a directory.
|
||||
|
||||
Args:
|
||||
directory: Path to inspect.
|
||||
|
||||
Returns:
|
||||
Sorted list of subdirectory names, or empty list if directory
|
||||
does not exist.
|
||||
"""
|
||||
if not directory.exists():
|
||||
return []
|
||||
return sorted(d.name for d in directory.iterdir() if d.is_dir())
|
||||
|
||||
|
||||
def _resolve_skills(
|
||||
requested: list[str] | None,
|
||||
available: list[str],
|
||||
) -> list[str]:
|
||||
if requested is None:
|
||||
return available
|
||||
unknown = set(requested) - set(available)
|
||||
if unknown:
|
||||
raise ValueError(
|
||||
f"The following skills were not found: {', '.join(sorted(unknown))}"
|
||||
)
|
||||
return requested
|
||||
Reference in New Issue
Block a user