refactor: rename bin/ package to myclaude/

This commit is contained in:
Zhengshou Lai
2026-06-26 22:58:18 +08:00
parent c9fd2321ed
commit 9111a574e9
9 changed files with 10 additions and 10 deletions
+13
View File
@@ -0,0 +1,13 @@
"""CLI subcommands."""
import click
from .update import update_cmd
__all__ = ["update_cmd", "upgrade_cmd"]
@click.command("upgrade")
def upgrade_cmd() -> None:
"""Alias for update: reinstall myclaude from this repository."""
return click.get_current_context().invoke(update_cmd)
+188
View File
@@ -0,0 +1,188 @@
"""Sync workspace symlinks from zshrc environment variables."""
import os
import subprocess
from pathlib import Path
from rich.console import Console
from myclaude.project_root import get_myclaude_project_root, get_workspace_root
console = Console()
stderr_console = Console(stderr=True)
# Link name -> list of environment variable names to try, in order.
#
# Customize this dict for your own projects. Each entry creates a symlink
# from workspace/<name> to the target path resolved from one of the env vars.
# Users define the corresponding env vars in their shell config (e.g. ~/.zshrc).
_LINK_MAP: dict[str, list[str]] = {
"myacademia": ["path_myacademia"],
"myslides": ["path_myslides", "MYSLIDES_ROOT"],
"metabot": ["METABOT_HOME"],
"mytoolkit": ["path_mytoolkit", "MYTOOLKIT_ROOT"],
"mywebpage": ["path_mywebpage", "MYWEBPAGE_ROOT"],
}
def _read_zsh_env(var: str, zshrc: Path) -> str | None:
"""Source zshrc and print the value of a variable."""
cmd = [
"zsh",
"-c",
f"source '{zshrc}' >/dev/null 2>&1; echo -n ${var}",
]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=False,
timeout=10,
)
except (OSError, subprocess.TimeoutExpired):
return None
value = result.stdout.strip()
return value if value else None
def _resolve_target(
env_vars: list[str],
zshrc: Path,
) -> Path | None:
"""Find the first valid target path from environment variables."""
for var in env_vars:
raw = _read_zsh_env(var, zshrc)
if raw is None:
continue
expanded = os.path.expandvars(os.path.expanduser(raw))
path = Path(expanded)
if path.exists():
return path.resolve()
return None
def _current_link_target(workspace: Path, name: str) -> Path | None:
"""Return the target of an existing symlink, or None."""
link = workspace / name
if link.is_symlink():
try:
return Path(os.readlink(link))
except OSError:
return None
return None
def _sync_link(
workspace: Path,
name: str,
target: Path,
dry_run: bool,
force: bool,
) -> bool:
"""Create or update a symlink. Returns True if changed."""
link = workspace / name
current = _current_link_target(workspace, name)
if current is not None and current.resolve() == target.resolve():
return False
if link.exists() and not link.is_symlink():
if not force:
stderr_console.print(
f"[yellow]{name} exists but is not a symlink. "
"Use --force to replace.[/yellow]"
)
return False
if dry_run:
console.print(
f"[dry-run] Would remove {name} and symlink -> {target}"
)
return True
if link.is_dir():
link.rmdir()
else:
link.unlink()
elif current is not None:
if dry_run:
console.print(
f"[dry-run] Would update {name}: {current} -> {target}"
)
return True
link.unlink()
else:
if dry_run:
console.print(f"[dry-run] Would create {name} -> {target}")
return True
link.symlink_to(target, target_is_directory=target.is_dir())
return True
def sync_workspace_links(
zshrc: Path | None = None,
dry_run: bool = False,
force: bool = False,
verbose: bool = True,
) -> bool:
"""Sync all workspace symlinks. Returns True on success."""
workspace = get_workspace_root()
if zshrc is None:
zshrc = Path.home() / ".zshrc"
if verbose:
console.print(f"[bold]Workspace:[/bold] {workspace}")
console.print(f"[bold]Zshrc:[/bold] {zshrc}")
console.print()
changed = 0
skipped = 0
broken = 0
# Handle myclaude separately: use project root detection, not zshrc.
myclaude_target = get_myclaude_project_root()
if _sync_link(workspace, "myclaude", myclaude_target, dry_run, force):
action = "would update" if dry_run else "updated"
if verbose:
console.print(
f"[green]myclaude: {action} -> {myclaude_target}[/green]"
)
changed += 1
else:
if verbose:
console.print("[dim]myclaude: already correct[/dim]")
for name, env_vars in _LINK_MAP.items():
target = _resolve_target(env_vars, zshrc)
if target is None:
link = workspace / name
if link.is_symlink() and not link.exists():
if verbose:
stderr_console.print(
f"[red]{name}: broken symlink (no env var found)[/red]"
)
broken += 1
else:
if verbose:
console.print(f"[dim]{name}: skipped (no env var)[/dim]")
skipped += 1
continue
if _sync_link(workspace, name, target, dry_run, force):
action = "would update" if dry_run else "updated"
if verbose:
console.print(f"[green]{name}: {action} -> {target}[/green]")
changed += 1
else:
if verbose:
console.print(f"[dim]{name}: already correct[/dim]")
if verbose:
console.print()
label = "Preview" if dry_run else "Done"
console.print(
f"[bold]{label}:[/bold] {changed} changed, "
f"{skipped} skipped, {broken} broken."
)
return broken == 0
+61
View File
@@ -0,0 +1,61 @@
"""Reinstall myclaude from the local repo."""
import subprocess
import sys
from pathlib import Path
import click
from rich.console import Console
from myclaude.commands.init import sync_workspace_links
from myclaude.project_root import get_myclaude_project_root
console = Console()
stderr_console = Console(stderr=True)
def _run_make_install(root: Path) -> int:
return subprocess.run(
["make", "install"],
cwd=str(root),
check=False,
).returncode
def _run_pip_editable(root: Path) -> int:
cmd = [sys.executable, "-m", "pip", "install", "-e", str(root)]
return subprocess.run(cmd, check=False).returncode
@click.command("update")
def update_cmd() -> None:
"""Reinstall myclaude and sync workspace symlinks."""
root = get_myclaude_project_root()
fallback = Path.home() / ".myclaude"
if root == fallback:
stderr_console.print(
"[red]Not inside myclaude repo.[/red] Set [cyan]MYCLAUDE_PROJECT_ROOT[/cyan] "
"or run from the clone.",
)
raise SystemExit(1)
console.print("[bold cyan]Updating myclaude…[/bold cyan]")
makefile = root / "Makefile"
if makefile.is_file():
rc = _run_make_install(root)
if rc != 0:
console.print(
"[yellow]make install failed, trying pip -e…[/yellow]"
)
rc = _run_pip_editable(root)
else:
rc = _run_pip_editable(root)
if rc != 0:
stderr_console.print("[red]Update failed.[/red]")
raise SystemExit(rc)
console.print("[green]myclaude updated.[/green]")
console.print()
console.print("[bold cyan]Syncing workspace links…[/bold cyan]")
sync_workspace_links()