Spaces:
Runtime error
Runtime error
| """CLI command to install the Gradio skill for AI coding assistants. | |
| Usage: | |
| gradio skills add --cursor | |
| gradio skills add --cursor --opencode | |
| gradio skills add --cursor --global | |
| gradio skills add --dest=~/my-skills | |
| gradio skills add abidlabs/english-translator --cursor | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import shutil | |
| from pathlib import Path | |
| from typing import Annotated, cast | |
| import typer | |
| from gradio_client import Client | |
| from gradio_client.snippet import generate_code_snippets | |
| from huggingface_hub import HfApi | |
| SKILL_ID = "gradio" | |
| _GITHUB_RAW = "https://raw.githubusercontent.com/gradio-app/gradio/main" | |
| _SKILL_PREFIX = ".agents/skills/gradio" | |
| _SKILL_FILES = ["SKILL.md", "examples.md"] | |
| skills_app = typer.Typer(help="Manage Gradio skills for AI assistants.") | |
| def _import_hf_skills(): | |
| try: | |
| from huggingface_hub.cli.skills import ( # type: ignore[import-not-found] | |
| CENTRAL_GLOBAL, | |
| CENTRAL_LOCAL, | |
| GLOBAL_TARGETS, | |
| LOCAL_TARGETS, | |
| ) | |
| except (ImportError, ModuleNotFoundError): | |
| raise SystemExit( | |
| "The 'gradio skills' command requires huggingface_hub >= 1.4.0.\n" | |
| "Please upgrade: pip install --upgrade huggingface_hub" | |
| ) from None | |
| return CENTRAL_GLOBAL, CENTRAL_LOCAL, GLOBAL_TARGETS, LOCAL_TARGETS | |
| def _download(url: str) -> str: | |
| from huggingface_hub.utils import get_session | |
| try: | |
| response = get_session().get(url) | |
| response.raise_for_status() | |
| except Exception as e: | |
| raise SystemExit( | |
| f"Failed to download {url}\n{e}\n\n" | |
| "Make sure you have internet access. The skill files are fetched from " | |
| "the Gradio GitHub repository." | |
| ) from e | |
| return response.text | |
| def _remove_existing(path: Path, force: bool) -> None: | |
| if not (path.exists() or path.is_symlink()): | |
| return | |
| if not force: | |
| raise SystemExit( | |
| f"Skill already exists at {path}.\nRe-run with --force to overwrite." | |
| ) | |
| if path.is_dir() and not path.is_symlink(): | |
| shutil.rmtree(path) | |
| else: | |
| path.unlink() | |
| def _create_symlink( | |
| agent_skills_dir: Path, | |
| central_skill_path: Path, | |
| force: bool, | |
| skill_id: str = SKILL_ID, | |
| ) -> Path: | |
| 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 _install_to(skills_dir: Path, force: bool) -> Path: | |
| 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() | |
| for fname in _SKILL_FILES: | |
| content = _download(f"{_GITHUB_RAW}/{_SKILL_PREFIX}/{fname}") | |
| (dest / fname).write_text(content, encoding="utf-8") | |
| return dest | |
| def _space_id_to_skill_id(space_id: str) -> str: | |
| return space_id.replace("/", "-") | |
| def _render_endpoint_section( | |
| api_name: str, endpoint_info: dict, space_id: str, src_url: str | |
| ) -> str: | |
| params = endpoint_info.get("parameters", []) | |
| returns = endpoint_info.get("returns", []) | |
| lines: list[str] = [] | |
| lines.append(f"### `{api_name}`\n") | |
| if params: | |
| lines.append("**Parameters:**\n") | |
| for p in params: | |
| ptype = p.get("python_type", {}) | |
| type_str = ( | |
| ptype.get("type", "Any") if isinstance(ptype, dict) else str(ptype) | |
| ) | |
| name = p.get("parameter_name") or p.get("label", "input") | |
| component = p.get("component", "") | |
| default_info = "" | |
| if p.get("parameter_has_default"): | |
| default_info = f", default: `{p.get('parameter_default')}`" | |
| required = ( | |
| " (required)" if not p.get("parameter_has_default", False) else "" | |
| ) | |
| lines.append( | |
| f"- `{name}` [{component}]: `{type_str}`{required}{default_info}" | |
| ) | |
| lines.append("") | |
| if returns: | |
| lines.append("**Returns:**\n") | |
| for r in returns: | |
| rtype = r.get("python_type", {}) | |
| type_str = ( | |
| rtype.get("type", "Any") if isinstance(rtype, dict) else str(rtype) | |
| ) | |
| label = r.get("label", "output") | |
| component = r.get("component", "") | |
| lines.append(f"- `{label}` [{component}]: `{type_str}`") | |
| lines.append("") | |
| snippets = generate_code_snippets( | |
| api_name, endpoint_info, src_url, space_id=space_id | |
| ) | |
| lines.append("**Python:**\n") | |
| lines.append("```python") | |
| lines.append(snippets["python"]) | |
| lines.append("```\n") | |
| lines.append("**JavaScript:**\n") | |
| lines.append("```javascript") | |
| lines.append(snippets["javascript"]) | |
| lines.append("```\n") | |
| lines.append("**cURL:**\n") | |
| lines.append("```bash") | |
| lines.append(snippets["bash"]) | |
| lines.append("```\n") | |
| return "\n".join(lines) | |
| def _get_space_description(space_id: str) -> str | None: | |
| try: | |
| info = HfApi().space_info(space_id) | |
| return getattr(info, "short_description", None) or None | |
| except Exception: | |
| return None | |
| def _generate_space_skill(space_id: str) -> tuple[str, str]: | |
| try: | |
| client = Client(space_id, download_files=False) | |
| except Exception as e: | |
| raise SystemExit( | |
| f"Failed to connect to Space '{space_id}'.\n{e}\n\n" | |
| "Make sure the Space exists, is public (or provide HF_TOKEN), and is running." | |
| ) from e | |
| api_info = cast(dict, client.view_api(print_info=False, return_format="dict")) | |
| src_url = client.src | |
| skill_id = _space_id_to_skill_id(space_id) | |
| space_description = _get_space_description(space_id) | |
| lines: list[str] = [] | |
| lines.append("---") | |
| lines.append(f"name: {skill_id}") | |
| desc = ( | |
| f"description: Use the {space_id} Gradio Space via API. " | |
| f"Provides Python, JavaScript, and cURL usage examples." | |
| ) | |
| if space_description: | |
| desc += f" Space description: {space_description}" | |
| lines.append(desc) | |
| lines.append("---\n") | |
| lines.append(f"# {space_id}\n") | |
| lines.append( | |
| f"This skill describes how to use the {space_id} " | |
| f"Gradio Space programmatically.\n" | |
| ) | |
| named = api_info.get("named_endpoints", {}) | |
| unnamed = api_info.get("unnamed_endpoints", {}) | |
| if named: | |
| lines.append("## API Endpoints\n") | |
| for api_name, endpoint_info in named.items(): | |
| lines.append( | |
| _render_endpoint_section(api_name, endpoint_info, space_id, src_url) | |
| ) | |
| if not named and unnamed: | |
| lines.append("## API Endpoints\n") | |
| for fn_index, endpoint_info in unnamed.items(): | |
| lines.append( | |
| _render_endpoint_section( | |
| f"fn_index={fn_index}", endpoint_info, space_id, src_url | |
| ) | |
| ) | |
| return skill_id, "\n".join(lines) + "\n" | |
| def _install_space_skill( | |
| skill_id: str, content: str, skills_dir: Path, force: bool | |
| ) -> Path: | |
| 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() | |
| (dest / "SKILL.md").write_text(content, encoding="utf-8") | |
| return dest | |
| def skills_add( | |
| space_id: Annotated[ | |
| str | None, | |
| typer.Argument( | |
| help="HF Space ID (e.g. 'user/my-space'). If omitted, installs the general Gradio skill." | |
| ), | |
| ] = None, | |
| cursor: Annotated[ | |
| bool, typer.Option("--cursor", help="Install for Cursor.") | |
| ] = False, | |
| claude: Annotated[ | |
| bool, typer.Option("--claude", help="Install for Claude.") | |
| ] = False, | |
| codex: Annotated[bool, typer.Option("--codex", help="Install for Codex.")] = False, | |
| opencode: Annotated[ | |
| bool, typer.Option("--opencode", help="Install for OpenCode.") | |
| ] = False, | |
| global_: Annotated[ | |
| bool, | |
| typer.Option( | |
| "--global", | |
| "-g", | |
| help="Install globally (user-level) instead of in the current project directory.", | |
| ), | |
| ] = False, | |
| dest: Annotated[ | |
| Path | None, | |
| typer.Option( | |
| help="Install into a custom destination (path to skills directory)." | |
| ), | |
| ] = None, | |
| force: Annotated[ | |
| bool, | |
| typer.Option("--force", help="Overwrite existing skills in the destination."), | |
| ] = False, | |
| ) -> None: | |
| """Download and install a Gradio skill for an AI assistant. | |
| When called without a space_id, installs the general Gradio skill. | |
| When called with a space_id, generates and installs a skill for that | |
| specific Gradio Space with Python, JS, and cURL usage examples. | |
| """ | |
| central_global, central_local, hf_global_targets, hf_local_targets = ( | |
| _import_hf_skills() | |
| ) | |
| if not (cursor or claude or codex or opencode or dest): | |
| raise typer.BadParameter( | |
| "Pick a destination via --cursor, --claude, --codex, --opencode, or --dest." | |
| ) | |
| global_targets = {**hf_global_targets, "cursor": Path("~/.cursor/skills")} | |
| local_targets = {**hf_local_targets, "cursor": Path(".cursor/skills")} | |
| targets_dict = global_targets if global_ else local_targets | |
| if space_id is not None: | |
| skill_id, content = _generate_space_skill(space_id) | |
| print(f"Generated skill for Space '{space_id}'") | |
| if dest: | |
| if cursor or claude or codex or opencode or global_: | |
| print("--dest cannot be combined with agent flags or --global.") | |
| raise typer.Exit(code=1) | |
| skill_dest = _install_space_skill(skill_id, content, dest, force) | |
| print(f"Installed '{skill_id}' to {skill_dest}") | |
| return | |
| agent_targets: list[Path] = [] | |
| if cursor: | |
| agent_targets.append(targets_dict["cursor"]) | |
| if claude: | |
| agent_targets.append(targets_dict["claude"]) | |
| if codex: | |
| agent_targets.append(targets_dict["codex"]) | |
| if opencode: | |
| agent_targets.append(targets_dict["opencode"]) | |
| central_path = central_global if global_ else central_local | |
| central_skill_path = _install_space_skill( | |
| skill_id, content, central_path, 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, force, skill_id=skill_id | |
| ) | |
| print(f"Created symlink: {link_path}") | |
| return | |
| if dest: | |
| if cursor or claude or codex or opencode or global_: | |
| print("--dest cannot be combined with agent flags or --global.") | |
| raise typer.Exit(code=1) | |
| skill_dest = _install_to(dest, force) | |
| print(f"Installed '{SKILL_ID}' to {skill_dest}") | |
| return | |
| agent_targets = [] | |
| if cursor: | |
| agent_targets.append(targets_dict["cursor"]) | |
| if claude: | |
| agent_targets.append(targets_dict["claude"]) | |
| if codex: | |
| agent_targets.append(targets_dict["codex"]) | |
| if opencode: | |
| agent_targets.append(targets_dict["opencode"]) | |
| central_path = central_global if global_ else central_local | |
| central_skill_path = _install_to(central_path, 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, force) | |
| print(f"Created symlink: {link_path}") | |