Skip to content
Open
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
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"skills": "skills",
"mcpServers": ".mcp.json",
"description": "Agent Skills for AI/ML tasks including dataset creation, model training, evaluation, and research paper publishing on Hugging Face Hub",
"version": "1.0.1",
"version": "1.0.0",
"author": {
"name": "Hugging Face"
},
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ In practice, skills are self-contained folders that package instructions, script

## Installation

Hugging Face skills are compatible with Claude Code, Codex, Gemini CLI, and Cursor.
Hugging Face skills are compatible with Claude Code, Codex, Gemini CLI, Cursor, and OpenCode.

### Claude Code

Expand Down Expand Up @@ -46,6 +46,18 @@ For example:

3. If your Codex setup still relies on `AGENTS.md`, you can use the generated [`agents/AGENTS.md`](agents/AGENTS.md) file in this repo as a fallback bundle of instructions.

### OpenCode

1. This repo includes an OpenCode config file at [`opencode.json`](opencode.json), generated from the same metadata sources as other agent integrations.

2. Install or reference this repository in OpenCode using your normal extension/plugin workflow, then point OpenCode to the repo root so it can read:
- `opencode.json`
- `skills/`
- `agents/AGENTS.md`
- `.mcp.json`

3. For field-level details and validation workflow, see [`docs/opencode.md`](docs/opencode.md).

### Gemini CLI

1. This repo includes `gemini-extension.json` to integrate with the Gemini CLI.
Expand Down Expand Up @@ -79,6 +91,8 @@ For contributors, regenerate manifests with:
./scripts/publish.sh
```

This also regenerates `opencode.json`.

## Skills

This repository contains a few skills to get you started. You can also contribute your own skills to the repository.
Expand Down
2 changes: 2 additions & 0 deletions agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ hugging-face-trackio: `Track and visualize ML training experiments with Trackio.

Paths referenced within SKILL folders are relative to that SKILL. For example the hf-datasets `scripts/example.py` would be referenced as `hf-datasets/scripts/example.py`.

OpenCode compatibility: this repository also ships `opencode.json` at repo root. It points OpenCode to `skills/`, `agents/AGENTS.md`, and MCP servers so OpenCode can load all skills consistently with other agents.

</skills>
47 changes: 47 additions & 0 deletions docs/opencode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# OpenCode Integration

This repository provides an OpenCode-compatible config at `opencode.json`.

## What `opencode.json` contains

- `name`, `description`, `author`, `homepage`, `repository`, `license`, `keywords`
- Sourced from `.claude-plugin/plugin.json` to keep metadata aligned.
- `version`
- OpenCode config version (currently independent, set to `0.1.0`).
- `skills`
- Set to `skills` so OpenCode can scan all skill folders.
- `skillPaths`
- Enumerates all discovered skill directories from `skills/*/SKILL.md`.
- `contextFileName`
- Defaults to `agents/AGENTS.md`, aligned with `gemini-extension.json` when present.
- `mcpServers`
- Pulled from `.mcp.json` (fallback to `gemini-extension.json` MCP data when needed).

## Generation and validation

`opencode.json` is generated, not hand-edited.

Use:

```bash
uv run scripts/generate_opencode_config.py
```

Validate without writing:

```bash
uv run scripts/generate_opencode_config.py --check
```

Or run the full publish pipeline:

```bash
./scripts/publish.sh
./scripts/publish.sh --check
```

## Why this design

- Reuses existing single sources of truth for metadata and MCP settings.
- Keeps OpenCode integration in the same generation/check lifecycle as AGENTS/README/Cursor artifacts.
- Automatically tracks all skills under `skills/` with no manual list maintenance.
40 changes: 40 additions & 0 deletions opencode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "huggingface-skills",
"description": "Agent Skills for AI/ML tasks including dataset creation, model training, evaluation, and research paper publishing on Hugging Face Hub",
"version": "0.1.0",
"skills": "skills",
"skillPaths": [
"skills/hf-cli",
"skills/hugging-face-dataset-viewer",
"skills/hugging-face-datasets",
"skills/hugging-face-evaluation",
"skills/hugging-face-jobs",
"skills/hugging-face-model-trainer",
"skills/hugging-face-paper-publisher",
"skills/hugging-face-tool-builder",
"skills/hugging-face-trackio",
"skills/huggingface-gradio"
],
"contextFileName": "agents/AGENTS.md",
"mcpServers": {
"huggingface-skills": {
"url": "https://huggingface.co/mcp?login"
}
},
"homepage": "https://github.com/huggingface/skills",
"repository": "https://github.com/huggingface/skills",
"license": "Apache-2.0",
"keywords": [
"huggingface",
"machine-learning",
"datasets",
"training",
"evaluation",
"papers",
"fine-tuning",
"llm"
],
"author": {
"name": "Hugging Face"
}
}
2 changes: 2 additions & 0 deletions scripts/AGENTS_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ IMPORTANT: You MUST read the SKILL.md file whenever the description of the skill

Paths referenced within SKILL folders are relative to that SKILL. For example the hf-datasets `scripts/example.py` would be referenced as `hf-datasets/scripts/example.py`.

OpenCode compatibility: this repository also ships `opencode.json` at repo root. It points OpenCode to `skills/`, `agents/AGENTS.md`, and MCP servers so OpenCode can load all skills consistently with other agents.

</skills>
150 changes: 150 additions & 0 deletions scripts/generate_opencode_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""Generate OpenCode config from existing repo metadata.

Output:
- opencode.json
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path


ROOT = Path(__file__).resolve().parent.parent
CLAUDE_PLUGIN_MANIFEST = ROOT / ".claude-plugin" / "plugin.json"
GEMINI_EXTENSION = ROOT / "gemini-extension.json"
MCP_CONFIG = ROOT / ".mcp.json"
OPENCODE_CONFIG = ROOT / "opencode.json"

OPENCODE_CONFIG_VERSION = "0.1.0"
DEFAULT_CONTEXT_FILE = "agents/AGENTS.md"


def load_json(path: Path) -> dict:
if not path.exists():
raise FileNotFoundError(f"Missing required file: {path}")
return json.loads(path.read_text(encoding="utf-8"))


def collect_skill_paths() -> list[str]:
paths: list[str] = []
for skill_md in sorted(ROOT.glob("skills/*/SKILL.md")):
paths.append(str(skill_md.parent.relative_to(ROOT)))
return paths


def extract_context_file() -> str:
if not GEMINI_EXTENSION.exists():
return DEFAULT_CONTEXT_FILE

data = load_json(GEMINI_EXTENSION)
context_file = data.get("contextFileName")
if not isinstance(context_file, str) or not context_file.strip():
return DEFAULT_CONTEXT_FILE

return context_file


def extract_mcp_servers() -> dict:
if MCP_CONFIG.exists():
data = load_json(MCP_CONFIG)
mcp_servers = data.get("mcpServers")
if isinstance(mcp_servers, dict) and mcp_servers:
return mcp_servers

if GEMINI_EXTENSION.exists():
data = load_json(GEMINI_EXTENSION)
mcp_servers = data.get("mcpServers")
if isinstance(mcp_servers, dict) and mcp_servers:
normalized = {}
for server_name, cfg in mcp_servers.items():
if not isinstance(cfg, dict):
continue
url = cfg.get("url") or cfg.get("httpUrl")
if isinstance(url, str) and url.strip():
normalized[server_name] = {"url": url}
if normalized:
return normalized

return {}


def build_opencode_config() -> dict:
plugin_manifest = load_json(CLAUDE_PLUGIN_MANIFEST)
skill_paths = collect_skill_paths()
if not skill_paths:
raise ValueError("No skills discovered under skills/*/SKILL.md")

name = plugin_manifest.get("name")
description = plugin_manifest.get("description")
if not isinstance(name, str) or not name:
raise ValueError(".claude-plugin/plugin.json must define a non-empty 'name'")
if not isinstance(description, str) or not description:
raise ValueError(".claude-plugin/plugin.json must define a non-empty 'description'")

config = {
"name": name,
"description": description,
"version": OPENCODE_CONFIG_VERSION,
"skills": "skills",
"skillPaths": skill_paths,
"contextFileName": extract_context_file(),
"mcpServers": extract_mcp_servers(),
}

for key in ["homepage", "repository", "license", "keywords", "author"]:
if key in plugin_manifest:
config[key] = plugin_manifest[key]

return config


def render_json(data: dict) -> str:
return json.dumps(data, indent=2, ensure_ascii=False) + "\n"


def write_or_check(path: Path, content: str, check: bool) -> bool:
current = path.read_text(encoding="utf-8") if path.exists() else None
if current == content:
return True

if check:
return False

path.write_text(content, encoding="utf-8")
return True


def main() -> None:
parser = argparse.ArgumentParser(description="Generate OpenCode config")
parser.add_argument(
"--check",
action="store_true",
help="Validate opencode.json is up-to-date without writing changes.",
)
args = parser.parse_args()

content = render_json(build_opencode_config())
ok = write_or_check(OPENCODE_CONFIG, content, check=args.check)

if args.check:
if not ok:
print("OpenCode config is out of date:", file=sys.stderr)
print(f" - {OPENCODE_CONFIG.relative_to(ROOT)}", file=sys.stderr)
print("Run: uv run scripts/generate_opencode_config.py", file=sys.stderr)
sys.exit(1)
print("OpenCode config is up to date.")
return

print(f"Wrote {OPENCODE_CONFIG.relative_to(ROOT)}")


if __name__ == "__main__":
main()
4 changes: 4 additions & 0 deletions scripts/publish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ GENERATED_FILES=(
"README.md"
".cursor-plugin/plugin.json"
".mcp.json"
"opencode.json"
)

file_sig() {
Expand All @@ -23,6 +24,7 @@ file_sig() {
run_generate() {
uv run scripts/generate_agents.py
uv run scripts/generate_cursor_plugin.py
uv run scripts/generate_opencode_config.py
}

run_check() {
Expand Down Expand Up @@ -54,6 +56,7 @@ run_check() {

# Extra explicit check for cursor-only artifacts
uv run scripts/generate_cursor_plugin.py --check
uv run scripts/generate_opencode_config.py --check

echo "All generated artifacts are up to date."
}
Expand All @@ -77,6 +80,7 @@ This script regenerates:
- README.md (skills table section)
- .cursor-plugin/plugin.json
- .mcp.json
- opencode.json
EOF
;;
*)
Expand Down