| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import argparse |
| import os |
| import shutil |
| import sys |
| from pathlib import Path |
| from urllib.request import Request, urlopen |
|
|
|
|
| SKILL_ID = "hf-cli" |
|
|
| _GITHUB_RAW_BASE = "https://raw.githubusercontent.com/huggingface/huggingface_hub/main/docs/source/en" |
| _SKILL_MD_URL = f"{_GITHUB_RAW_BASE}/guides/cli.md" |
| _REFERENCE_URL = f"{_GITHUB_RAW_BASE}/package_reference/cli.md" |
|
|
| _SKILL_YAML_PREFIX = """\ |
| --- |
| name: hf-cli |
| description: > |
| Hugging Face Hub CLI (`hf`) for downloading, uploading, and managing |
| repositories, models, datasets, and Spaces on the Hugging Face Hub. |
| --- |
| |
| The Hugging Face Hub CLI tool `hf` is available. IMPORTANT: The `hf` command replaces the deprecated `huggingface_cli` command. |
| |
| Use `hf --help` to view available functions. Note that auth commands are now all under `hf auth` e.g. `hf auth whoami`. |
| """ |
|
|
| CENTRAL_LOCAL = Path(".agents/skills") |
| CENTRAL_GLOBAL = Path("~/.agents/skills") |
|
|
| GLOBAL_TARGETS = { |
| "codex": Path("~/.codex/skills"), |
| "claude": Path("~/.claude/skills"), |
| "opencode": Path("~/.config/opencode/skills"), |
| } |
|
|
| LOCAL_TARGETS = { |
| "codex": Path(".codex/skills"), |
| "claude": Path(".claude/skills"), |
| "opencode": Path(".opencode/skills"), |
| } |
|
|
|
|
| def _download(url): |
| req = Request(url, headers={"User-Agent": "hf-skills/1.0"}) |
| with urlopen(req) as resp: |
| return resp.read().decode("utf-8") |
|
|
|
|
| def _remove_existing(path, force): |
| if not (path.exists() or path.is_symlink()): |
| return |
| if not force: |
| print(f"Skill already exists at {path}.\nRe-run with --force to overwrite.", file=sys.stderr) |
| sys.exit(1) |
| if path.is_dir() and not path.is_symlink(): |
| shutil.rmtree(path) |
| else: |
| path.unlink() |
|
|
|
|
| def _install_to(skills_dir, force): |
| skills_dir = skills_dir.expanduser().resolve() |
| skills_dir.mkdir(parents=True, exist_ok=True) |
| dest = skills_dir / SKILL_ID |
|
|
| _remove_existing(dest, force) |
| dest.mkdir() |
|
|
| skill_content = _download(_SKILL_MD_URL) |
| (dest / "SKILL.md").write_text(_SKILL_YAML_PREFIX + skill_content, encoding="utf-8") |
|
|
| ref_dir = dest / "references" |
| ref_dir.mkdir() |
| ref_content = _download(_REFERENCE_URL) |
| (ref_dir / "cli.md").write_text(ref_content, encoding="utf-8") |
|
|
| return dest |
|
|
|
|
| def _create_symlink(agent_skills_dir, central_skill_path, force): |
| agent_skills_dir = agent_skills_dir.expanduser().resolve() |
| agent_skills_dir.mkdir(parents=True, exist_ok=True) |
| link_path = agent_skills_dir / SKILL_ID |
|
|
| _remove_existing(link_path, force) |
| link_path.symlink_to(os.path.relpath(central_skill_path, agent_skills_dir)) |
|
|
| return link_path |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser( |
| description="Download and install HF CLI skills for AI assistants.", |
| epilog="Examples:\n" |
| " hf tool run skills --claude\n" |
| " hf tool run skills --claude --global\n" |
| " hf tool run skills --codex --opencode\n" |
| " hf tool run skills --dest ~/my-skills\n", |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| ) |
| parser.add_argument("--claude", action="store_true", help="Install for Claude") |
| parser.add_argument("--codex", action="store_true", help="Install for Codex") |
| parser.add_argument("--opencode", action="store_true", help="Install for OpenCode") |
| parser.add_argument("--global", dest="global_", action="store_true", help="Install globally (user-level)") |
| parser.add_argument("--dest", type=Path, help="Install into a custom destination directory") |
| parser.add_argument("--force", action="store_true", help="Overwrite existing skills") |
| args = parser.parse_args() |
|
|
| if not (args.claude or args.codex or args.opencode or args.dest): |
| parser.error("Pick a destination via --claude, --codex, --opencode, or --dest.") |
|
|
| if args.dest: |
| if args.claude or args.codex or args.opencode or args.global_: |
| print("--dest cannot be combined with --claude, --codex, --opencode, or --global.", file=sys.stderr) |
| sys.exit(1) |
| skill_dest = _install_to(args.dest, args.force) |
| print(f"Installed '{SKILL_ID}' to {skill_dest}") |
| return |
|
|
| targets_dict = GLOBAL_TARGETS if args.global_ else LOCAL_TARGETS |
| agent_targets = [] |
| if args.claude: |
| agent_targets.append(targets_dict["claude"]) |
| if args.codex: |
| agent_targets.append(targets_dict["codex"]) |
| if args.opencode: |
| agent_targets.append(targets_dict["opencode"]) |
|
|
| central_path = CENTRAL_GLOBAL if args.global_ else CENTRAL_LOCAL |
| central_skill_path = _install_to(central_path, args.force) |
| print(f"Installed '{SKILL_ID}' to central location: {central_skill_path}") |
|
|
| for agent_target in agent_targets: |
| link_path = _create_symlink(agent_target, central_skill_path, args.force) |
| print(f"Created symlink: {link_path}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|