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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
},
"metadata": {
"description": "Orchestrator skill for RHDH plugin development - onboard, update, and maintain plugins in the Extensions Catalog",
"version": "0.5.0"
"version": "0.5.1"
},
"plugins": [
{
"name": "rhdh",
"source": "./",
"description": "Skills for RHDH plugin lifecycle management",
"version": "0.5.0",
"version": "0.5.1",
"strict": true
}
]
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "rhdh",
"description": "All-in-one toolkit for Red Hat Developer Hub (RHDH). Covers plugin development, overlay management, environment setup, version compatibility, CI/CD, and RHDH ecosystem navigation.",
"version": "0.5.0",
"version": "0.5.1",
"author": {
"name": "RHDH Store Manager"
},
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "rhdh-skill"
version = "0.5.0"
version = "0.5.1"
description = "Claude Code skill for RHDH plugin development"
readme = "README.md"
license = "Apache-2.0"
Expand Down
39 changes: 29 additions & 10 deletions skills/overlay/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ name: overlay
description: >-
Manages the rhdh-plugin-export-overlays repository — onboards plugins to the
Extensions Catalog, updates plugin versions, fixes overlay build failures,
triages and analyzes PRs, triggers publishes, and manages plugin workspaces.
triages and analyzes PRs, triggers publishes, manages plugin workspaces, and
looks up plugin metadata.
Use when working with overlays, importing plugins, debugging CI, checking PRs,
bumping versions, or mentions "Extensions Catalog", "overlay build failed",
"plugin registry", "overlay PR", "overlay doctor", "plugin import",
"add plugin to catalog", "onboard plugin", or "plugin workspace".
bumping versions, looking up plugin metadata, or mentions "Extensions
Catalog", "overlay build failed", "plugin registry", "overlay PR", "overlay
doctor", "plugin import", "add plugin to catalog", "onboard plugin", "plugin
workspace", "plugin installation reference".
---

<cli_setup>
Expand Down Expand Up @@ -70,14 +72,15 @@ What overlay task would you like to do?
2. **Update plugin version** — Bump to newer upstream commit/tag
3. **Check plugin status** — Verify health and compatibility
4. **Fix build failure** — Debug CI/publish issues
5. **Lookup plugin metadata** — Get plugin version and artifact reference by branch

### Core Team Tasks

*For COPE/Plugins team managing the overlay repository*

5. **Triage overlay PRs** — Prioritize open PRs by criticality
6. **Analyze specific PR** — Check assignment, compatibility, merge readiness
7. **Trigger publish** — Add /publish comment to PR(s)
6. **Triage overlay PRs** — Prioritize open PRs by criticality
7. **Analyze specific PR** — Check assignment, compatibility, merge readiness
8. **Trigger publish** — Add /publish comment to PR(s)

**Wait for response before proceeding.**
</intake>
Expand All @@ -91,14 +94,15 @@ What overlay task would you like to do?
| 2, "update", "bump", "upgrade", "version" | `workflows/update-plugin.md` |
| 3, "status", "check", "health" | Run inline status checks |
| 4, "fix", "debug", "failure", "error" | `workflows/fix-build.md` |
| 5, "metadata", "artifact", "dynamicArtifact", "plugin version", "installation tag" | Run inline metadata lookup |

### Core Team Routes

| Response | Workflow |
|----------|----------|
| 5, "triage", "prioritize", "backlog" | `workflows/triage-prs.md` |
| 6, "analyze", "check PR", "PR #" | `workflows/analyze-pr.md` |
| 7, "publish", "trigger" | Run inline publish trigger |
| 6, "triage", "prioritize", "backlog" | `workflows/triage-prs.md` |
| 7, "analyze", "check PR", "PR #" | `workflows/analyze-pr.md` |
| 8, "publish", "trigger" | Run inline publish trigger |

**After reading the workflow, follow it exactly.**
</routing>
Expand All @@ -123,6 +127,21 @@ gh pr list --repo redhat-developer/rhdh-plugin-export-overlays --search "<name>"

</inline_status_check>

<inline_metadata_lookup>
For plugin metadata lookup (e.g. version, package name) or for installation tag/reference lookup (`spec.dynamicArtifact` metadata), use:

```bash
# Preferred (if rhdh-local skill is installed)
python skills/rhdh-local/scripts/fetch-plugin-metadata.py <plugin-name> --branch <branch-or-tag>
```

Fallback (overlay-only install): resolve `spec.packages` from
`catalog-entities/extensions/plugins/<plugin-name>.yaml` first, then locate each
package metadata file under `workspaces/<plugin-workspace>/metadata/<file>.yaml`, and
read metadata there.

</inline_metadata_lookup>

<inline_publish_trigger>
For triggering publish on one or more PRs:

Expand Down
6 changes: 5 additions & 1 deletion skills/overlay/references/metadata-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ metadata:
description: <Brief description>
spec:
packageName: <npm-package-name> # e.g., @aws/backstage-plugin-aws-codebuild
# Artifact reference used by dynamic plugin loading (OCI image, tgz archive, npm package, or a directory path)
dynamicArtifact: <artifact-reference>
version: <package-version>

# Dynamic plugin configuration
dynamicPluginConfig:
Expand All @@ -46,6 +49,7 @@ spec:

**Key sections:**

- `dynamicArtifact` — artifact reference used for plugin loading (OCI image, tgz archive, npm package, or a directory path)
- `dynamicPluginConfig` — how the plugin mounts in RHDH
- `appConfigExamples` — configuration snippets for users
</package_entity>
Expand Down Expand Up @@ -103,5 +107,5 @@ Add to `catalog-entities/marketplace/plugins/all.yaml` (alphabetical order).
</plugin_entity>

<documentation_link>
Full annotated example: [catalog-entities/marketplace/README.md](https://github.com/redhat-developer/rhdh-plugin-export-overlays/blob/main/catalog-entities/marketplace/README.md)
Full annotated example: [catalog-entities/extensions/README.md](https://github.com/redhat-developer/rhdh-plugin-export-overlays/blob/main/catalog-entities/extensions/README.md)
</documentation_link>
68 changes: 46 additions & 22 deletions skills/rhdh-local/scripts/fetch-plugin-metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,28 @@
import json
import sys
import urllib.error
import urllib.parse
import urllib.request
from typing import Any

# ---------------------------------------------------------------------------
# GitHub URLs
# ---------------------------------------------------------------------------
_CONTENTS_URL = (
"https://api.github.com/repos/redhat-developer/rhdh-plugin-export-overlays"
"/contents/catalog-entities/extensions/plugins"
)
_RAW_BASE = "https://raw.githubusercontent.com/redhat-developer/rhdh-plugin-export-overlays/main"
_API_REPO_BASE = "https://api.github.com/repos/redhat-developer/rhdh-plugin-export-overlays"
_RAW_REPO_BASE = "https://raw.githubusercontent.com/redhat-developer/rhdh-plugin-export-overlays"


def _contents_url(path: str, branch: str) -> str:
"""Build a GitHub Contents API URL pinned to the requested branch."""
encoded_branch = urllib.parse.quote(branch, safe="")
return f"{_API_REPO_BASE}/contents/{path}?ref={encoded_branch}"


def _raw_url(path: str, branch: str) -> str:
"""Build a raw.githubusercontent URL pinned to the requested branch."""
encoded_branch = urllib.parse.quote(branch, safe="")
return f"{_RAW_REPO_BASE}/{encoded_branch}/{path}"


# ANSI helpers
_RED = "\033[0;31m"
Expand Down Expand Up @@ -311,9 +322,9 @@ def _fetch_yaml(url: str) -> dict[str, Any]:
# ---------------------------------------------------------------------------


def list_plugins() -> list[str]:
def list_plugins(branch: str = "main") -> list[str]:
"""Return sorted list of available plugin names."""
entries = _fetch_json(_CONTENTS_URL)
entries = _fetch_json(_contents_url("catalog-entities/extensions/plugins", branch))
names: list[str] = []
for entry in entries:
name = entry.get("name", "")
Expand All @@ -333,15 +344,12 @@ def _get(d: dict, *keys: str, default: Any = None) -> Any:
return cur


def _list_workspace_metadata(workspace: str) -> list[str]:
def _list_workspace_metadata(workspace: str, branch: str = "main") -> list[str]:
"""List metadata file names (without .yaml) in a workspace.

Returns an empty list if the workspace or metadata dir doesn't exist.
"""
url = (
f"https://api.github.com/repos/redhat-developer/"
f"rhdh-plugin-export-overlays/contents/workspaces/{workspace}/metadata"
)
url = _contents_url(f"workspaces/{workspace}/metadata", branch)
try:
entries = _fetch_json(url)
except urllib.error.HTTPError:
Expand Down Expand Up @@ -423,13 +431,13 @@ def _find_metadata_file(
return None


def fetch_plugin_metadata(plugin_name: str) -> dict[str, Any]:
def fetch_plugin_metadata(plugin_name: str, branch: str = "main") -> dict[str, Any]:
"""Fetch plugin definition + per-package metadata.

Returns a structured dict with plugin info and package details.
"""
# Step 1: plugin definition
plugin_url = f"{_RAW_BASE}/catalog-entities/extensions/plugins/{plugin_name}.yaml"
plugin_url = _raw_url(f"catalog-entities/extensions/plugins/{plugin_name}.yaml", branch)
try:
plugin_def = _fetch_yaml(plugin_url)
except urllib.error.HTTPError as exc:
Expand All @@ -453,7 +461,7 @@ def fetch_plugin_metadata(plugin_name: str) -> dict[str, Any]:

# Step 2: per-package metadata
# Pre-fetch the workspace metadata directory listing for the primary workspace
primary_available = _list_workspace_metadata(plugin_name)
primary_available = _list_workspace_metadata(plugin_name, branch)

package_results: list[dict[str, Any]] = []
for pkg in packages:
Expand All @@ -473,25 +481,28 @@ def fetch_plugin_metadata(plugin_name: str) -> dict[str, Any]:
if "backstage" not in (plugin_name, alt_ws):
fallback_workspaces.append("backstage")
for ws in fallback_workspaces:
alt_available = _list_workspace_metadata(ws)
alt_available = _list_workspace_metadata(ws, branch)
match = _find_metadata_file(pkg_name, ws, alt_available)
if match is not None:
break

if match is not None:
ws, file_name = match
pkg_url = f"{_RAW_BASE}/workspaces/{ws}/metadata/{file_name}.yaml"
pkg_url = _raw_url(f"workspaces/{ws}/metadata/{file_name}.yaml", branch)
try:
pkg_def = _fetch_yaml(pkg_url)
except urllib.error.HTTPError:
pkg_def = {}

version = _get(pkg_def, "spec", "version", default=None)
dynamic_artifact = _get(pkg_def, "spec", "dynamicArtifact", default=None)
role = _get(pkg_def, "spec", "backstage", "role", default=None)
app_config_examples = _get(pkg_def, "spec", "appConfigExamples", default=None)
part_of = _get(pkg_def, "spec", "partOf", default=None)

pkg_result: dict[str, Any] = {"name": pkg_name}
if version:
pkg_result["version"] = str(version)
if dynamic_artifact:
pkg_result["dynamicArtifact"] = dynamic_artifact
if role:
Expand All @@ -505,6 +516,7 @@ def fetch_plugin_metadata(plugin_name: str) -> dict[str, Any]:

return {
"plugin": metadata_name,
"branch": branch,
"categories": categories,
"preInstalled": pre_installed,
"packages": package_results,
Expand Down Expand Up @@ -562,6 +574,8 @@ def _print_human_metadata(data: dict[str, Any]) -> None:
return

print(f"{_BOLD}Plugin:{_NC} {data['plugin']}")
if data.get("branch"):
print(f"{_BOLD}Branch:{_NC} {data['branch']}")
if data.get("categories"):
cats = data["categories"]
if isinstance(cats, list):
Expand All @@ -578,6 +592,9 @@ def _print_human_metadata(data: dict[str, Any]) -> None:
role_color = _BLUE if "frontend" in str(role) else _GREEN
print(f"\n {_BOLD}{pkg['name']}{_NC}")
print(f" Role: {role_color}{role}{_NC}")
version = pkg.get("version")
if version:
print(f" Version: {version}")
artifact = pkg.get("dynamicArtifact")
if artifact:
print(f" Artifact: {artifact}")
Expand All @@ -603,8 +620,8 @@ def build_parser() -> argparse.ArgumentParser:
description=(
"Fetch RHDH plugin metadata from the plugin-export-overlays "
"repository. Lists available plugins or retrieves detailed "
"metadata (OCI artifacts, roles, config examples) for a "
"specific plugin."
"metadata (versions, OCI artifacts, roles, config examples) "
"for a specific plugin."
),
)
parser.add_argument(
Expand All @@ -626,6 +643,11 @@ def build_parser() -> argparse.ArgumentParser:
default=False,
help="List all available plugins",
)
parser.add_argument(
"--branch",
default="main",
help="Overlay repository branch or ref to read from (default: main)",
)
return parser


Expand All @@ -635,7 +657,7 @@ def main(argv: list[str] | None = None) -> int:

if args.list_plugins:
try:
plugins = list_plugins()
plugins = list_plugins(branch=args.branch)
except (urllib.error.URLError, OSError) as exc:
if args.json_output:
print(json.dumps({"success": False, "error": str(exc)}, indent=2))
Expand All @@ -644,7 +666,9 @@ def main(argv: list[str] | None = None) -> int:
return 1

if args.json_output:
print(json.dumps({"success": True, "plugins": plugins}, indent=2))
print(
json.dumps({"success": True, "branch": args.branch, "plugins": plugins}, indent=2)
)
else:
_print_human_list(plugins)
return 0
Expand All @@ -654,7 +678,7 @@ def main(argv: list[str] | None = None) -> int:
return 2 # argparse exits on error, but be explicit

try:
data = fetch_plugin_metadata(args.plugin)
data = fetch_plugin_metadata(args.plugin, branch=args.branch)
except (urllib.error.URLError, OSError) as exc:
if args.json_output:
print(json.dumps({"success": False, "error": str(exc)}, indent=2))
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading