diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 85e44e87..85cdfcc8 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -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" }, diff --git a/README.md b/README.md index 8aea1fd3..2a71d48a 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. @@ -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. diff --git a/agents/AGENTS.md b/agents/AGENTS.md index 21691fcf..4b198718 100644 --- a/agents/AGENTS.md +++ b/agents/AGENTS.md @@ -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. + diff --git a/docs/opencode.md b/docs/opencode.md new file mode 100644 index 00000000..fbcdade8 --- /dev/null +++ b/docs/opencode.md @@ -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. diff --git a/opencode.json b/opencode.json new file mode 100644 index 00000000..efc8eebf --- /dev/null +++ b/opencode.json @@ -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" + } +} diff --git a/scripts/AGENTS_TEMPLATE.md b/scripts/AGENTS_TEMPLATE.md index c6bd117b..b22eeb13 100644 --- a/scripts/AGENTS_TEMPLATE.md +++ b/scripts/AGENTS_TEMPLATE.md @@ -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. + diff --git a/scripts/generate_opencode_config.py b/scripts/generate_opencode_config.py new file mode 100644 index 00000000..2fbb7b31 --- /dev/null +++ b/scripts/generate_opencode_config.py @@ -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() diff --git a/scripts/publish.sh b/scripts/publish.sh index 9afe06ea..4e0811a1 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -9,6 +9,7 @@ GENERATED_FILES=( "README.md" ".cursor-plugin/plugin.json" ".mcp.json" + "opencode.json" ) file_sig() { @@ -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() { @@ -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." } @@ -77,6 +80,7 @@ This script regenerates: - README.md (skills table section) - .cursor-plugin/plugin.json - .mcp.json + - opencode.json EOF ;; *)