131 lines
4.0 KiB
Python
131 lines
4.0 KiB
Python
"""Command-line interface for myclaude."""
|
|
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import typer
|
|
|
|
from myclaude import config as cfg
|
|
from myclaude import fs_ops, git_ops
|
|
|
|
app = typer.Typer(help="Manage Claude skills across projects.")
|
|
|
|
|
|
@app.command()
|
|
def init(
|
|
project_dir: Path = typer.Argument(..., help="Target project directory"),
|
|
repo: Optional[str] = typer.Option(None, "--repo", help="Git repo SSH URL"),
|
|
skill: Optional[list[str]] = typer.Option(None, "--skill", help="Skills to install"),
|
|
force: bool = typer.Option(False, "--force", help="Overwrite existing skills"),
|
|
keep_tmp: bool = typer.Option(False, "--keep-tmp", help="Keep temporary clone directory for inspection"),
|
|
) -> None:
|
|
"""Initialize a project with Claude skills from the central repo."""
|
|
if repo:
|
|
cfg.save_config(repo)
|
|
typer.echo(f"Repository URL saved: {repo}")
|
|
|
|
try:
|
|
repo_url = cfg.get_repo_url()
|
|
except (FileNotFoundError, KeyError) as e:
|
|
typer.echo(f"Error: {e}", err=True)
|
|
raise typer.Exit(code=1)
|
|
|
|
with git_ops.temp_clone(repo_url, keep=keep_tmp) as (repo_path, _):
|
|
try:
|
|
fs_ops.copy_claude_md_to_project(repo_path, project_dir)
|
|
fs_ops.copy_skills_to_project(repo_path, project_dir, skills=skill, force=force)
|
|
except (FileExistsError, ValueError) as e:
|
|
typer.echo(f"Error: {e}", err=True)
|
|
raise typer.Exit(code=1)
|
|
|
|
skill_label = ", ".join(skill) if skill else "all"
|
|
typer.echo(f"Initialized '{project_dir}' with skills: {skill_label}")
|
|
|
|
|
|
@app.command()
|
|
def push(
|
|
skill: Optional[list[str]] = typer.Option(None, "--skill", help="Skills to push"),
|
|
claude_md: bool = typer.Option(False, "--claude-md", help="Also push CLAUDE.md"),
|
|
) -> None:
|
|
"""Push local skills to the central repo."""
|
|
project_dir = Path.cwd()
|
|
|
|
try:
|
|
repo_url = cfg.get_repo_url()
|
|
except (FileNotFoundError, KeyError) as e:
|
|
typer.echo(f"Error: {e}", err=True)
|
|
raise typer.Exit(code=1)
|
|
|
|
with git_ops.temp_clone(repo_url) as (repo_path, repo):
|
|
try:
|
|
fs_ops.copy_skills_to_repo(project_dir, repo_path, skills=skill)
|
|
if claude_md:
|
|
fs_ops.copy_claude_md_to_repo(project_dir, repo_path)
|
|
|
|
pushed_skills = skill if skill else fs_ops.collect_local_skills(project_dir)
|
|
commit_message = f"chore: update skills {', '.join(pushed_skills)}"
|
|
if claude_md:
|
|
commit_message += " + CLAUDE.md"
|
|
|
|
git_ops.commit_and_push(repo, commit_message)
|
|
except (ValueError, RuntimeError) as e:
|
|
typer.echo(f"Error: {e}", err=True)
|
|
raise typer.Exit(code=1)
|
|
|
|
typer.echo("Skills pushed successfully.")
|
|
|
|
|
|
@app.command()
|
|
def status() -> None:
|
|
"""Compare local skills with the central repo."""
|
|
project_dir = Path.cwd()
|
|
|
|
try:
|
|
repo_url = cfg.get_repo_url()
|
|
except (FileNotFoundError, KeyError) as e:
|
|
typer.echo(f"Error: {e}", err=True)
|
|
raise typer.Exit(code=1)
|
|
|
|
with git_ops.temp_clone(repo_url) as (repo_path, _):
|
|
result = fs_ops.get_skills_status(project_dir, repo_path)
|
|
|
|
if not any([result.in_both, result.local_only, result.remote_only]):
|
|
typer.echo("No skills found locally or remotely.")
|
|
return
|
|
|
|
if result.in_both:
|
|
typer.echo("Installed:")
|
|
for s in result.in_both:
|
|
typer.echo(f" [ok] {s}")
|
|
|
|
if result.local_only:
|
|
typer.echo("Local only (not pushed):")
|
|
for s in result.local_only:
|
|
typer.echo(f" [+] {s}")
|
|
|
|
if result.remote_only:
|
|
typer.echo("Remote only (not installed):")
|
|
for s in result.remote_only:
|
|
typer.echo(f" [-] {s}")
|
|
|
|
|
|
@app.command(name="list")
|
|
def list_skills() -> None:
|
|
"""List all skills available in the central repo."""
|
|
try:
|
|
repo_url = cfg.get_repo_url()
|
|
except (FileNotFoundError, KeyError) as e:
|
|
typer.echo(f"Error: {e}", err=True)
|
|
raise typer.Exit(code=1)
|
|
|
|
with git_ops.temp_clone(repo_url) as (repo_path, _):
|
|
skills = fs_ops.list_skills_in_repo(repo_path)
|
|
|
|
if not skills:
|
|
typer.echo("No skills found in the central repo.")
|
|
return
|
|
|
|
typer.echo("Available skills:")
|
|
for s in skills:
|
|
typer.echo(f" - {s}")
|