First version. I can init

This commit is contained in:
2026-04-13 22:04:05 +02:00
commit 40ea9d5c1f
18 changed files with 713 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
"""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}")