Team Ai
Apppublic

openenv/atari_env

sourceHugging Faceupdated 6mo agoView on Hugging Face
3likes
skills.py201 linesDownload Raw Back to commands
1# Copyright (c) Meta Platforms, Inc. and affiliates.2# All rights reserved.3#4# This source code is licensed under the BSD-style license found in the5# LICENSE file in the root directory of this source tree.6 7"""Commands to manage OpenEnv CLI skills for AI assistants."""8 9from __future__ import annotations10 11import os12import shutil13from pathlib import Path14from typing import Annotated15 16import typer17 18DEFAULT_SKILL_ID = "openenv-cli"19 20_SKILL_YAML_PREFIX = """\21---22name: openenv-cli23description: "OpenEnv CLI (`openenv`) for scaffolding, validating, building, and pushing OpenEnv environments."24---25 26Install: `pip install openenv-core`27 28The OpenEnv CLI command `openenv` is available.29Use `openenv --help` to view available commands.30"""31 32_SKILL_TIPS = """33## Tips34 35- Start with `openenv init <env_name>` to scaffold a new environment36- Validate projects with `openenv validate`37- Build and deploy with `openenv build` and `openenv push`38- Use `openenv <command> --help` for command-specific options39"""40 41CENTRAL_LOCAL = Path(".agents/skills")42CENTRAL_GLOBAL = Path("~/.agents/skills")43 44GLOBAL_TARGETS = {45    "codex": Path("~/.codex/skills"),46    "claude": Path("~/.claude/skills"),47    "cursor": Path("~/.cursor/skills"),48    "opencode": Path("~/.config/opencode/skills"),49}50 51LOCAL_TARGETS = {52    "codex": Path(".codex/skills"),53    "claude": Path(".claude/skills"),54    "cursor": Path(".cursor/skills"),55    "opencode": Path(".opencode/skills"),56}57 58app = typer.Typer(help="Manage OpenEnv skills for AI assistants")59 60 61def _build_skill_md() -> str:62    """Generate SKILL.md content for the OpenEnv CLI skill."""63    from openenv import __version__64 65    lines = _SKILL_YAML_PREFIX.splitlines()66    lines.append("")67    lines.append(68        f"Generated with `openenv-core v{__version__}`. Run `openenv skills add --force` to regenerate."69    )70    lines.extend(_SKILL_TIPS.splitlines())71    return "\n".join(lines).strip() + "\n"72 73 74def _remove_existing(path: Path, force: bool) -> None:75    """Remove existing file/directory/symlink if force is True, else fail."""76    if not (path.exists() or path.is_symlink()):77        return78    if not force:79        raise typer.Exit(code=1)80 81    if path.is_dir() and not path.is_symlink():82        shutil.rmtree(path)83    else:84        path.unlink()85 86 87def _install_to(skills_dir: Path, force: bool) -> Path:88    """Install the OpenEnv skill in a skills directory."""89    skills_dir = skills_dir.expanduser().resolve()90    skills_dir.mkdir(parents=True, exist_ok=True)91    dest = skills_dir / DEFAULT_SKILL_ID92 93    if dest.exists() or dest.is_symlink():94        if not force:95            typer.echo(96                f"Skill already exists at {dest}. Re-run with --force to overwrite."97            )98            raise typer.Exit(code=1)99        _remove_existing(dest, force=True)100 101    dest.mkdir()102    (dest / "SKILL.md").write_text(_build_skill_md(), encoding="utf-8")103    return dest104 105 106def _create_symlink(107    agent_skills_dir: Path, central_skill_path: Path, force: bool108) -> Path:109    """Create a relative symlink from agent directory to central skill location."""110    agent_skills_dir = agent_skills_dir.expanduser().resolve()111    agent_skills_dir.mkdir(parents=True, exist_ok=True)112    link_path = agent_skills_dir / DEFAULT_SKILL_ID113 114    if link_path.exists() or link_path.is_symlink():115        if not force:116            typer.echo(117                f"Skill already exists at {link_path}. Re-run with --force to overwrite."118            )119            raise typer.Exit(code=1)120        _remove_existing(link_path, force=True)121 122    link_path.symlink_to(os.path.relpath(central_skill_path, agent_skills_dir))123    return link_path124 125 126@app.command("preview")127def skills_preview() -> None:128    """Print generated SKILL.md content."""129    typer.echo(_build_skill_md())130 131 132@app.command("add")133def skills_add(134    claude: Annotated[135        bool,136        typer.Option("--claude", help="Install for Claude."),137    ] = False,138    codex: Annotated[139        bool,140        typer.Option("--codex", help="Install for Codex."),141    ] = False,142    cursor: Annotated[143        bool,144        typer.Option("--cursor", help="Install for Cursor."),145    ] = False,146    opencode: Annotated[147        bool,148        typer.Option("--opencode", help="Install for OpenCode."),149    ] = False,150    global_: Annotated[151        bool,152        typer.Option(153            "--global",154            "-g",155            help=(156                "Install globally (user-level) instead of in the current project directory."157            ),158        ),159    ] = False,160    dest: Annotated[161        Path | None,162        typer.Option(help="Install into a custom destination (skills directory path)."),163    ] = None,164    force: Annotated[165        bool,166        typer.Option("--force", help="Overwrite existing skills in the destination."),167    ] = False,168) -> None:169    """Install OpenEnv CLI skill for AI assistants."""170    if dest:171        if claude or codex or cursor or opencode or global_:172            typer.echo(173                "--dest cannot be combined with --claude, --codex, --cursor, --opencode, or --global."174            )175            raise typer.Exit(code=1)176        skill_dest = _install_to(dest, force)177        typer.echo(f"Installed '{DEFAULT_SKILL_ID}' to {skill_dest}")178        return179 180    central_path = CENTRAL_GLOBAL if global_ else CENTRAL_LOCAL181    central_skill_path = _install_to(central_path, force)182    typer.echo(183        f"Installed '{DEFAULT_SKILL_ID}' to central location: {central_skill_path}"184    )185 186    targets = GLOBAL_TARGETS if global_ else LOCAL_TARGETS187    agent_targets: list[Path] = []188 189    if claude:190        agent_targets.append(targets["claude"])191    if codex:192        agent_targets.append(targets["codex"])193    if cursor:194        agent_targets.append(targets["cursor"])195    if opencode:196        agent_targets.append(targets["opencode"])197 198    for agent_target in agent_targets:199        link_path = _create_symlink(agent_target, central_skill_path, force)200        typer.echo(f"Created symlink: {link_path}")201