Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ uv.lock
htmlcov/

.notes/
.claude/

# Agent resources environment directories
.claude/
.opencode/
.codex/
.config/opencode/
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ uvx add-command <username>/<command-name> # Slash commands
uvx add-agent <username>/<agent-name> # Sub-agents
```

### Options for codex, opencode or different repo names

```bash
# Install from different repository structures
uvx add-skill username/skill-name --repo different-repo

# Install to different environments
uvx add-skill username/skill-name --env opencode # OpenCode
uvx add-skill username/skill-name --env codex # Codex

# Custom installation path
uvx add-skill username/skill-name --dest ./my-path/

# Global installation
uvx add-skill username/skill-name --global
```

**Supports multiple repository structures:**
- `.claude/skills/` (standard)
- `skills/` (Anthropics style)
- `skill/` (OpenCode style)

---

## 🚀 Create Your Own
Expand Down
9 changes: 9 additions & 0 deletions packages/agent-resources/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ license = "MIT"
dependencies = [
"httpx>=0.27",
"typer>=0.12",
"pyyaml>=6.0",
]

[project.optional-dependencies]
dev = [
"pytest>=7.0",
"ruff>=0.1.0",
"mypy>=1.0",
"types-PyYAML>=6.0",
]

[project.scripts]
Expand Down
30 changes: 27 additions & 3 deletions packages/agent-resources/src/agent_resources/cli/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ def add(
help="Install to ~/.claude/ instead of ./.claude/",
),
] = False,
repo: Annotated[
str,
typer.Option(
"--repo",
help="Repository name to fetch from (default: agent-resources)",
),
] = "agent-resources",
dest: Annotated[
str,
typer.Option(
"--dest",
help="Custom destination path",
),
] = "",
environment: Annotated[
str,
typer.Option(
"--env",
help="Target environment (claude, opencode, codex)",
),
] = "",
) -> None:
"""
Add a sub-agent from a GitHub user's agent-resources repository.
Expand All @@ -60,14 +81,17 @@ def add(
typer.echo(f"Error: {e}", err=True)
raise typer.Exit(1)

dest = get_destination("agents", global_install)
# Simple destination handling
dest_path = get_destination(
"agents", global_install, dest if dest else None, environment if environment else None
)
scope = "user" if global_install else "project"

typer.echo(f"Fetching agent '{agent_name}' from {username}/agent-resources...")
typer.echo(f"Fetching agent '{agent_name}' from {username}/{repo}...")

try:
agent_path = fetch_resource(
username, agent_name, dest, ResourceType.AGENT, overwrite
username, agent_name, dest_path, ResourceType.AGENT, overwrite, repo
)
typer.echo(f"Added agent '{agent_name}' to {agent_path} ({scope} scope)")
except RepoNotFoundError as e:
Expand Down
30 changes: 27 additions & 3 deletions packages/agent-resources/src/agent_resources/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ def add(
help="Install to ~/.claude/ instead of ./.claude/",
),
] = False,
repo: Annotated[
str,
typer.Option(
"--repo",
help="Repository name to fetch from (default: agent-resources)",
),
] = "agent-resources",
dest: Annotated[
str,
typer.Option(
"--dest",
help="Custom destination path",
),
] = "",
environment: Annotated[
str,
typer.Option(
"--env",
help="Target environment (claude, opencode, codex)",
),
] = "",
) -> None:
"""
Add a slash command from a GitHub user's agent-resources repository.
Expand All @@ -60,14 +81,17 @@ def add(
typer.echo(f"Error: {e}", err=True)
raise typer.Exit(1)

dest = get_destination("commands", global_install)
# Simple destination handling
dest_path = get_destination(
"commands", global_install, dest if dest else None, environment if environment else None
)
scope = "user" if global_install else "project"

typer.echo(f"Fetching command '{command_name}' from {username}/agent-resources...")
typer.echo(f"Fetching command '{command_name}' from {username}/{repo}...")

try:
command_path = fetch_resource(
username, command_name, dest, ResourceType.COMMAND, overwrite
username, command_name, dest_path, ResourceType.COMMAND, overwrite, repo
)
typer.echo(f"Added command '{command_name}' to {command_path} ({scope} scope)")
except RepoNotFoundError as e:
Expand Down
83 changes: 75 additions & 8 deletions packages/agent-resources/src/agent_resources/cli/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,55 @@
from pathlib import Path

import typer
import yaml


# Default environment configurations
DEFAULT_ENVIRONMENTS = {
"claude": {
"skill_dir": ".claude/skills",
"command_dir": ".claude/commands",
"agent_dir": ".claude/agents",
},
"opencode": {
"skill_dir": ".opencode/skill",
"command_dir": ".opencode/command",
"agent_dir": ".opencode/agent",
"global_skill_dir": ".config/opencode/skill",
"global_command_dir": ".config/opencode/command",
"global_agent_dir": ".config/opencode/agent",
},
"codex": {
"skill_dir": ".codex/skills",
"command_dir": ".codex/commands",
"agent_dir": ".codex/agents",
},
}


def get_environment_config(environment: str | None = None) -> dict:
"""Simple config loading - no caching, no complexity"""
config_path = Path.home() / ".agent-resources-config.yaml"

# Load user config if exists
user_config: dict = {}
if config_path.exists():
with config_path.open("r") as f:
user_config = yaml.safe_load(f) or {}

# Merge with defaults - simple and straightforward
environments = {**DEFAULT_ENVIRONMENTS, **user_config.get("environments", {})}

# Default to claude if no environment specified
env_name = environment or "claude"

if env_name not in environments:
raise typer.BadParameter(
f"Unknown environment: '{env_name}'. "
f"Available: {', '.join(environments.keys())}"
)

return environments[env_name]


def parse_resource_ref(ref: str) -> tuple[str, str]:
Expand Down Expand Up @@ -31,20 +80,38 @@ def parse_resource_ref(ref: str) -> tuple[str, str]:
return username, name


def get_destination(resource_subdir: str, global_install: bool) -> Path:
def get_destination(
resource_subdir: str,
global_install: bool,
custom_dest: str | None = None,
environment: str | None = None,
) -> Path:
"""
Get the destination directory for a resource.

Args:
resource_subdir: The subdirectory name (e.g., "skills", "commands", "agents")
global_install: If True, install to ~/.claude/, else to ./.claude/
global_install: If True, install to home directory, else to current directory
custom_dest: Optional custom destination path
environment: Optional environment name (claude, opencode, codex)

Returns:
Path to the destination directory
"""
if global_install:
base = Path.home() / ".claude"
else:
base = Path.cwd() / ".claude"

return base / resource_subdir
if custom_dest:
return Path(custom_dest).expanduser()

# Get environment configuration
env_config = get_environment_config(environment)

# Build config key based on resource type and global flag
prefix = "global_" if global_install else ""
key = f"{prefix}{resource_subdir.rstrip('s')}_dir" # "skills" -> "skill_dir"

# Get the directory, fallback to non-global if global key doesn't exist
env_dir = env_config.get(key, env_config[key.replace("global_", "")])

# Determine base path
base = Path.home() if global_install else Path.cwd()

return base / env_dir
30 changes: 27 additions & 3 deletions packages/agent-resources/src/agent_resources/cli/skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ def add(
help="Install to ~/.claude/ instead of ./.claude/",
),
] = False,
repo: Annotated[
str,
typer.Option(
"--repo",
help="Repository name to fetch from (default: agent-resources)",
),
] = "agent-resources",
dest: Annotated[
str,
typer.Option(
"--dest",
help="Custom destination path",
),
] = "",
environment: Annotated[
str,
typer.Option(
"--env",
help="Target environment (claude, opencode, codex)",
),
] = "",
) -> None:
"""
Add a skill from a GitHub user's agent-resources repository.
Expand All @@ -60,14 +81,17 @@ def add(
typer.echo(f"Error: {e}", err=True)
raise typer.Exit(1)

dest = get_destination("skills", global_install)
# Simple destination handling
dest_path = get_destination(
"skills", global_install, dest if dest else None, environment if environment else None
)
scope = "user" if global_install else "project"

typer.echo(f"Fetching skill '{skill_name}' from {username}/agent-resources...")
typer.echo(f"Fetching skill '{skill_name}' from {username}/{repo}...")

try:
skill_path = fetch_resource(
username, skill_name, dest, ResourceType.SKILL, overwrite
username, skill_name, dest_path, ResourceType.SKILL, overwrite, repo
)
typer.echo(f"Added skill '{skill_name}' to {skill_path} ({scope} scope)")
except RepoNotFoundError as e:
Expand Down
Loading