Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
195 commits
Select commit Hold shift + click to select a range
df0dddd
ci: add release promotion automation
MioYuuIH Jul 9, 2026
3300fda
Merge pull request #170 from AMDResearch/ci/release-automation
kerwtsai-amd Jul 9, 2026
4efa5c6
ci: harden release automation workflows
MioYuuIH Jul 9, 2026
fb0b1d2
Merge pull request #171 from AMDResearch/ci/release-automation-hardening
MioYuuIH Jul 9, 2026
f969345
feat(config): add 9600 GRE accelerator baseline
MioYuuIH Jul 10, 2026
a27dc75
feat(installer): treat 9600 GRE as a curated SKU
MioYuuIH Jul 10, 2026
1f6e23f
feat(config): add GPU resource image overrides
MioYuuIH Jul 10, 2026
e555507
fix(config): keep GPU accelerator visibility conservative
MioYuuIH Jul 10, 2026
9d89191
fix(installer): override preconfigured accelerator images
MioYuuIH Jul 10, 2026
972b4db
docs(config): note accelerator override tag compatibility
MioYuuIH Jul 10, 2026
9cfa11e
Merge pull request #174 from AMDResearch/feat/default-course-gpu-cove…
kerwtsai-amd Jul 13, 2026
91cf3e8
fix(scripts): use quota REST API in manage_users instead of kubectl exec
mariodruiz Jul 14, 2026
cf2fa91
Make code more robust
mariodruiz Jul 14, 2026
a48b95f
feat(admin): add suffix width control for user generation
MioYuuIH Jul 16, 2026
7cbb27a
fix(admin): bound user generator numeric inputs
MioYuuIH Jul 16, 2026
c5dc614
feat(frontend): add shared password policy helpers
MioYuuIH Jul 16, 2026
27ed4fd
feat(hub): add best-effort user provisioning api
MioYuuIH Jul 16, 2026
297b79f
feat(frontend): expose user provisioning client
MioYuuIH Jul 16, 2026
6956c19
fix(admin): use provisioning api for user creation
MioYuuIH Jul 16, 2026
a8613b0
fix(admin): align password reset validation
MioYuuIH Jul 16, 2026
649f24a
feat(admin): support batched group membership API
MioYuuIH Jul 16, 2026
2a7075e
feat(admin): add bulk user group actions
MioYuuIH Jul 16, 2026
31d1beb
feat(admin): add group detail management page
MioYuuIH Jul 16, 2026
a21760a
fix(admin): serve nested group detail routes
MioYuuIH Jul 16, 2026
53e2033
feat(admin): streamline bulk group selection
MioYuuIH Jul 16, 2026
ab2b9fe
Merge pull request #176 from AMDResearch/feat/add-user-suffix-width
MioYuuIH Jul 16, 2026
37888f4
Merge pull request #177 from AMDResearch/fix/add-user-provisioning
MioYuuIH Jul 16, 2026
fd5756d
Merge pull request #178 from AMDResearch/feature/bulk-user-group-mana…
MioYuuIH Jul 16, 2026
815bc52
Merge pull request #175 from mariodruiz/fix/quotas
MioYuuIH Jul 16, 2026
719d3f6
feat: integrate auplc-skills into the repository as a bundled plugin
kerwtsai-amd Jul 17, 2026
5f10d2a
Merge remote-tracking branch 'origin/develop' into feat/integrate-aup…
kerwtsai-amd Jul 17, 2026
88690a4
fix: satisfy repo lint for integrated skill scripts
kerwtsai-amd Jul 17, 2026
c737944
fix(skills): harden deployment config helpers
MioYuuIH Jul 20, 2026
3f66ef2
docs(skills): correct deployment helper workflow
MioYuuIH Jul 20, 2026
f6df6fe
docs(skills): align cross-skill helper paths
MioYuuIH Jul 20, 2026
d79894f
docs(skills): align accelerator planning guidance
MioYuuIH Jul 20, 2026
91b0a34
docs(skills): align OEM kernel guidance
MioYuuIH Jul 20, 2026
9763c50
docs(skills): correct quota API guidance
MioYuuIH Jul 20, 2026
5e1a0e6
docs(skills): move plugin governance docs
MioYuuIH Jul 20, 2026
e4dd899
docs(skills): update plugin documentation links
MioYuuIH Jul 20, 2026
f7c05a3
chore(skills): expand local validation
MioYuuIH Jul 20, 2026
73feaf4
ci(skills): enforce plugin validation
MioYuuIH Jul 20, 2026
51dcb90
chore(skills): synchronize plugin metadata version
MioYuuIH Jul 20, 2026
17a739c
Merge pull request #180 from AMDResearch/feat/integrate-auplc-skills
kerwtsai-amd Jul 20, 2026
44b5e16
feat(spawn): add auto-select accelerator option
mariodruiz Jul 20, 2026
9c0aca3
Add button to show password
mariodruiz Jul 22, 2026
8b13dbf
Fix login issues for native users
mariodruiz Jul 27, 2026
c8cebed
feat(installer): add guarded GPU access policy
MioYuuIH Jul 28, 2026
c6701a7
feat(installer): classify local GPU hardware
MioYuuIH Jul 28, 2026
3e2bf6b
feat(installer): wire GPU access into workflows
MioYuuIH Jul 28, 2026
0ea9978
feat(installer): emit resolved render GID overlays
MioYuuIH Jul 28, 2026
50ecc64
feat(chart): model GPU render GID
MioYuuIH Jul 28, 2026
2432536
fix(runtime): separate storage and GPU groups
MioYuuIH Jul 28, 2026
cff8981
feat(hub): enforce GPU render group policy
MioYuuIH Jul 28, 2026
3489075
fix(images): remove embedded GPU permission policy
MioYuuIH Jul 28, 2026
5176884
feat(ansible): define canonical GPU access contract
MioYuuIH Jul 28, 2026
5911029
feat(ansible): apply verified GPU device policy
MioYuuIH Jul 28, 2026
f22777a
feat(ansible): integrate unified GPU access role
MioYuuIH Jul 28, 2026
132917e
feat(deploy): add validated config generation primitives
MioYuuIH Jul 28, 2026
e1e8382
feat(deploy): publish generated artifacts atomically
MioYuuIH Jul 28, 2026
1333e15
feat(deploy): resolve unanimous fleet GPU policy
MioYuuIH Jul 28, 2026
d41f301
feat(deploy): discover fleet GPU access state
MioYuuIH Jul 28, 2026
0f5a1cb
feat(deploy): stage atomic PXE GPU finalization
MioYuuIH Jul 28, 2026
27f36f1
feat(deploy): generate and validate GPU artifacts
MioYuuIH Jul 28, 2026
09c10f0
feat(ansible): admit and finalize PXE GPU rootfs
MioYuuIH Jul 28, 2026
a45d2e8
docs(deploy): document unified GPU permission flow
MioYuuIH Jul 28, 2026
5fa4856
docs(deploy): align automatic GPU workflow references
MioYuuIH Jul 28, 2026
c781cb5
fix(tests): isolate GPU role skill dependencies
MioYuuIH Jul 28, 2026
db73537
refactor(hub): remove GPU group injection
MioYuuIH Jul 28, 2026
3f2c322
refactor(chart): remove GPU GID settings
MioYuuIH Jul 28, 2026
075f29d
fix(runtime): keep storage group only
MioYuuIH Jul 28, 2026
ee6c31c
refactor(installer): simplify GPU host policy
MioYuuIH Jul 28, 2026
d5e373c
refactor(installer): remove GPU GID workflow
MioYuuIH Jul 28, 2026
3bc557f
refactor(installer): remove GPU GID overlays
MioYuuIH Jul 28, 2026
e5252db
refactor(ansible): simplify GPU access role
MioYuuIH Jul 28, 2026
b1fd9a3
refactor(ansible): simplify PXE GPU policy
MioYuuIH Jul 28, 2026
117b43b
refactor(deploy): simplify GPU discovery
MioYuuIH Jul 28, 2026
f217d1f
refactor(deploy): remove draft GPU inputs
MioYuuIH Jul 28, 2026
2d28d6c
refactor(deploy): render boolean GPU policy
MioYuuIH Jul 28, 2026
709ee3f
refactor(deploy): simplify GPU manifests
MioYuuIH Jul 28, 2026
131f2f1
refactor(deploy): remove PXE finalizer
MioYuuIH Jul 28, 2026
0d09573
docs: update installer stages
MioYuuIH Jul 28, 2026
d9d973d
docs(deploy): document GPU infrastructure contract
MioYuuIH Jul 28, 2026
e74c40d
docs(ansible): document GID-free policy
MioYuuIH Jul 28, 2026
a87aba0
docs(k8s): require external GPU device management
MioYuuIH Jul 28, 2026
8c3ba1f
docs(skills): align GPU deployment workflow
MioYuuIH Jul 28, 2026
c64b8cd
docs(deploy): restore GPU setup commands
MioYuuIH Jul 28, 2026
be20985
remove unrelated changes
mariodruiz Jul 28, 2026
43dc742
Simplify text
mariodruiz Jul 28, 2026
77400ed
Make text clearer
mariodruiz Jul 28, 2026
1ca3cb7
refactor(installer): install AMD GPU udev package
MioYuuIH Jul 28, 2026
2f87d33
refactor(installer): pass GPU package context
MioYuuIH Jul 28, 2026
36dc762
feat(installer): bundle AMD GPU udev package
MioYuuIH Jul 28, 2026
be284a6
refactor(ansible): install AMD GPU udev package
MioYuuIH Jul 28, 2026
4e15da9
fix(pxe): install GPU udev package safely
MioYuuIH Jul 28, 2026
04d8ee3
docs: describe AMD GPU udev package
MioYuuIH Jul 28, 2026
5e898d9
docs(deploy): document AMD GPU udev package
MioYuuIH Jul 28, 2026
3cf219b
docs(skills): align AMD GPU package workflow
MioYuuIH Jul 28, 2026
9a78201
Remove not use code
mariodruiz Jul 28, 2026
269849d
Fix ruff issues
mariodruiz Jul 28, 2026
544e29c
feat(deploy): validate direct SSH inventory
MioYuuIH Jul 28, 2026
0e513f4
fix(ansible): require explicit GPU access flags
MioYuuIH Jul 28, 2026
61a49a5
docs(deploy): restore direct SSH workflow
MioYuuIH Jul 28, 2026
dae1f1e
docs(skills): retain generated deployment workflow
MioYuuIH Jul 28, 2026
ee7ac6e
fix(validation): allow auto only for direct SSH inventory
MioYuuIH Jul 29, 2026
6875615
feat(ansible): resolve automatic GPU access
MioYuuIH Jul 29, 2026
8853384
refactor(deploy): reuse GPU sysfs detection
MioYuuIH Jul 29, 2026
f39f890
test(deploy): preserve generated boolean policies
MioYuuIH Jul 29, 2026
5431365
docs(deploy): document automatic GPU detection
MioYuuIH Jul 29, 2026
f47b5b2
docs(ansible): document automatic GPU policy
MioYuuIH Jul 29, 2026
e38dd7d
docs(skills): distinguish direct and generated GPU policy
MioYuuIH Jul 29, 2026
d64b48d
test(installer): reduce GPU orchestration coverage
MioYuuIH Jul 29, 2026
31afced
test(installer): consolidate GPU package safety coverage
MioYuuIH Jul 29, 2026
5c1dbd9
test(deploy): trim generated config validation coverage
MioYuuIH Jul 29, 2026
21e775a
test(deploy): consolidate GPU artifact policy coverage
MioYuuIH Jul 29, 2026
7259d4e
test(ansible): narrow GPU role contract coverage
MioYuuIH Jul 29, 2026
8f1bf45
test(image): focus GPU permission ownership contract
MioYuuIH Jul 29, 2026
faf7225
Merge pull request #182 from mariodruiz/feature/showpassword
kerwtsai-amd Jul 31, 2026
8f4d711
Merge pull request #183 from AMDResearch/fix/unify-gpu-permissions
kerwtsai-amd Jul 31, 2026
db2d73e
fix(ansible): repair GPU access preflight admission
MioYuuIH Jul 31, 2026
3e5779b
test(ansible): generalize legacy rule naming
MioYuuIH Jul 31, 2026
9b2416a
docs(deploy): remove environment-specific references
MioYuuIH Jul 31, 2026
7a2ff60
Merge pull request #184 from mariodruiz/feat/auto-selection-resources
kerwtsai-amd Aug 3, 2026
6d12b40
fix(physim): align Genesis ROCm runtime
sonyyang-tw Aug 4, 2026
21d6aba
fix(physim): update notebooks for Genesis 1.3
sonyyang-tw Aug 4, 2026
21f90f9
fix(physim): restore notebook image attachment
sonyyang-tw Aug 4, 2026
ec78ecb
docs(physim): remove Unitree training reference
sonyyang-tw Aug 4, 2026
851c4fb
feat(installer): add local access mode selection
MioYuuIH Jul 31, 2026
8ace2e1
feat(installer): create local admin credentials secret
MioYuuIH Jul 31, 2026
030e366
feat(chart): configure local admin bootstrap
MioYuuIH Jul 31, 2026
41e2333
feat(hub): add closed local authenticator
MioYuuIH Jul 31, 2026
ca22d9c
docs: document single-node access modes
MioYuuIH Jul 31, 2026
27504c9
fix(installer): prepare namespace for local credentials
MioYuuIH Jul 31, 2026
e657a00
fix(hub): exclude administrators from reset targets
MioYuuIH Jul 31, 2026
9a6fc63
style: format local authentication changes
MioYuuIH Jul 31, 2026
d5abf57
fix(installer): harden local credential lifecycle
MioYuuIH Jul 31, 2026
99b5aae
fix(chart): restore local admin token wiring
MioYuuIH Jul 31, 2026
2fd3519
fix(hub): fail closed for local admin bootstrap
MioYuuIH Jul 31, 2026
d700de8
docs: clarify local credential recovery
MioYuuIH Jul 31, 2026
5922fb5
style: format local auth hardening
MioYuuIH Jul 31, 2026
2aacc5c
fix(installer): harden local auth lifecycle
MioYuuIH Jul 31, 2026
2b3c362
fix(chart): support legacy local admin secrets
MioYuuIH Jul 31, 2026
93753d3
fix(hub): preserve local administrator credentials
MioYuuIH Jul 31, 2026
2820825
fix(installer): capture local credential inspections
MioYuuIH Jul 31, 2026
9ad5171
fix(hub): protect secret-managed local admin credentials
MioYuuIH Jul 31, 2026
988895d
fix(chart): require local admin credential Secret
MioYuuIH Jul 31, 2026
35146eb
docs(skills): support custom admin secret names
MioYuuIH Jul 31, 2026
ffe6e96
fix(installer): keep personal access as TUI default
MioYuuIH Jul 31, 2026
c8facde
refactor(auth): add composable provider configuration
MioYuuIH Aug 3, 2026
773a38a
refactor(chart): validate composable auth providers
MioYuuIH Aug 3, 2026
0850764
refactor(hub): compose native and GitHub authentication
MioYuuIH Aug 3, 2026
c1998cc
fix(auth): fail closed for unknown native users
MioYuuIH Aug 3, 2026
f59d0d3
refactor(hub): gate provider setup by capabilities
MioYuuIH Aug 3, 2026
e812066
fix(hub): make admin credentials bootstrap-only
MioYuuIH Aug 3, 2026
e2c53a6
fix(hub): preserve native password and onboarding semantics
MioYuuIH Aug 3, 2026
c4aa1f5
refactor(config): add explicit resource access policy
MioYuuIH Aug 3, 2026
6b00c40
refactor(runtime): decouple resource resolution from auth
MioYuuIH Aug 3, 2026
c4c5ed8
refactor(runtime): propagate resource access policy
MioYuuIH Aug 3, 2026
0146cc7
refactor(chart): validate resource access policy
MioYuuIH Aug 3, 2026
cf30d42
fix(auth): render native login in composed auth
MioYuuIH Aug 4, 2026
c38f5d3
refactor(ui): render authentication from capabilities
MioYuuIH Aug 4, 2026
4ea9c8b
refactor(runtime): remove provider-derived resource access
MioYuuIH Aug 4, 2026
0190f87
feat(runtime): configure explicit runtime limits
MioYuuIH Aug 4, 2026
a04b37b
fix(auth): equalize native authentication timing
MioYuuIH Aug 4, 2026
2bfe638
refactor(installer): add canonical access profiles
MioYuuIH Aug 4, 2026
060695c
refactor(deploy): emit canonical runtime values
MioYuuIH Aug 4, 2026
c1c3d5d
docs(auth): document canonical provider configuration
MioYuuIH Aug 4, 2026
e2a4efa
refactor(auth): centralize authenticator configuration
MioYuuIH Aug 5, 2026
c3cfb10
fix(auth): namespace GitHub identities
MioYuuIH Aug 5, 2026
3519284
test(code): add launcher lifecycle harness
MioYuuIH Aug 5, 2026
6023284
fix(code): install default extensions in the system root
MioYuuIH Aug 5, 2026
37ec00a
fix(code): remove runtime extension provisioning
MioYuuIH Aug 5, 2026
ba77d9f
fix(chart): preserve bounded spawn failure handling
MioYuuIH Aug 5, 2026
0c2cd1e
ci: run focused code-server spawn tests
MioYuuIH Aug 5, 2026
861cf9c
docs(code): explain system and user extension roots
MioYuuIH Aug 5, 2026
95db88f
test(installer): align GPU access fixture
MioYuuIH Aug 5, 2026
467f227
test(hub): authorize runtime timer fixture
MioYuuIH Aug 5, 2026
114f356
fix(installer): support Python 3.10 exhaustiveness
MioYuuIH Aug 5, 2026
eb9ae57
style(deploy): format modular imports
MioYuuIH Aug 5, 2026
4628f56
fix(installer): centralize Python 3.10 exhaustiveness
MioYuuIH Aug 5, 2026
c36f88c
style(test): format rebase integration coverage
MioYuuIH Aug 5, 2026
78fd941
ci(test): install Hub config test dependency
MioYuuIH Aug 5, 2026
bbec724
fix(hub): preserve auth template variables at startup
MioYuuIH Aug 5, 2026
f1b845e
fix(hub): resolve automatic accelerators before configuration
MioYuuIH Aug 5, 2026
2e9308e
ci(test): run focused Hub regression tests
MioYuuIH Aug 5, 2026
e5eb71b
fix(auth): enforce provider-specific allow policies
MioYuuIH Aug 5, 2026
0782660
fix(config): remove global authenticator bypass
MioYuuIH Aug 5, 2026
65ff77a
ci(test): cover Hub auth and runtime regressions
MioYuuIH Aug 5, 2026
842cea5
refactor(auth): stop generating login markup
MioYuuIH Aug 6, 2026
187ecb0
fix(ui): restore composed login experience
MioYuuIH Aug 6, 2026
dc22334
test(ui): lock login visual contracts
MioYuuIH Aug 6, 2026
6aee419
Merge pull request #186 from AMDResearch/material/physim
kerwtsai-amd Aug 7, 2026
a34d1c5
Merge pull request #187 from AMDResearch/feat/singlenode-local-auth-mvp
kerwtsai-amd Aug 7, 2026
44923cb
Merge pull request #188 from AMDResearch/fix/code-server-spawn-isolation
kerwtsai-amd Aug 7, 2026
0d38a2f
fix(code): update code-server for trusted domains
MioYuuIH Aug 7, 2026
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
17 changes: 17 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "auplc-skills",
"owner": {
"name": "AMD Research"
},
"description": "Agent Skills for deploying and maintaining AUP Learning Cloud.",
"metadata": {
"version": "0.1.1"
},
"plugins": [
{
"name": "auplc",
"source": "./",
"description": "Skills for deploying and maintaining AUP Learning Cloud: install, deploy, configure courses, build images, upgrade, troubleshoot, configure auth, manage users and quota, monitor, expose with TLS/storage, configure repo cloning, and author courses for the multi-node JupyterHub-on-k3s platform for AMD GPUs."
}
]
}
21 changes: 21 additions & 0 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "auplc",
"description": "Skills for deploying and maintaining AUP Learning Cloud: install, deploy, configure courses, build images, upgrade, troubleshoot, configure auth, manage users and quota, monitor, expose with TLS/storage, configure repo cloning, and author courses for the multi-node JupyterHub-on-k3s platform for AMD GPUs.",
"version": "0.1.1",
"author": {
"name": "AMD Research"
},
"homepage": "https://github.com/AMDResearch/aup-learning-cloud",
"repository": "https://github.com/AMDResearch/aup-learning-cloud",
"keywords": [
"aup-learning-cloud",
"auplc",
"jupyterhub",
"k3s",
"rocm",
"pxe",
"ansible",
"helm",
"deployment"
]
}
17 changes: 17 additions & 0 deletions .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "auplc-skills",
"owner": {
"name": "AMD Research"
},
"description": "Agent Skills for deploying and maintaining AUP Learning Cloud.",
"metadata": {
"version": "0.1.1"
},
"plugins": [
{
"name": "auplc",
"source": "./",
"description": "Skills for deploying and maintaining AUP Learning Cloud: install, deploy, configure courses, build images, upgrade, troubleshoot, configure auth, manage users and quota, monitor, expose with TLS/storage, configure repo cloning, and author courses for the multi-node JupyterHub-on-k3s platform for AMD GPUs."
}
]
}
19 changes: 19 additions & 0 deletions .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "auplc",
"version": "0.1.1",
"description": "Skills for deploying and maintaining AUP Learning Cloud: install, deploy, configure courses, build images, upgrade, troubleshoot, configure auth, manage users and quota, monitor, expose with TLS/storage, configure repo cloning, and author courses for the multi-node JupyterHub-on-k3s platform for AMD GPUs.",
"author": {
"name": "AMD Research"
},
"keywords": [
"aup-learning-cloud",
"auplc",
"jupyterhub",
"k3s",
"rocm",
"pxe",
"ansible",
"helm",
"deployment"
]
}
34 changes: 34 additions & 0 deletions .github/scripts/check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Validate skills, version metadata, skill tests, and generated plugin manifests.
#
# Usage:
# ./.github/scripts/check.sh Run every skill-package validation.
# ./.github/scripts/check.sh -h|--help Print this help.
#
# Requires `uv` (https://github.com/astral-sh/uv).

set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"

usage() {
sed -n 's/^# \{0,1\}//p' "${BASH_SOURCE[0]}" | sed -n '/^Usage:/,/^Requires/p'
}

case "${1:-}" in
"")
uv run .github/scripts/validate_skills.py
uv run python scripts/check_skills_version.py
uv run --extra test pytest tests/skills
uv run .github/scripts/generate_cursor_marketplace.py --check
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1" >&2
echo "Run with --help for usage." >&2
exit 2
;;
esac
195 changes: 195 additions & 0 deletions .github/scripts/generate_cursor_marketplace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#!/usr/bin/env -S uv run --quiet
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""Generate the Cursor plugin manifests from the canonical sources.

`auplc-skills` ships as a single bundled plugin: the whole repository is one
plugin whose `skills/` folder every supported agent discovers automatically
(this mirrors how `cloudflare/skills` is published). To avoid drift, the Cursor
manifests are generated from the Claude manifests rather than hand-maintained.

Sources of truth:
- `plugin-metadata.json` (repo root): shared identity and discovery metadata
(name, description, version, author, homepage, repository,
keywords). This is the vendor-neutral metadata file, reused by every
marketplace/manifest target. It is NOT a plugin manifest.
- `.claude-plugin/marketplace.json`: the marketplace catalog with the single
bundled plugin entry and its human-readable description (hand-maintained,
since the catalog blurb intentionally differs from the SKILL.md routing
descriptions).
- `.claude-plugin/plugin.json`: the bundled plugin manifest (hand-maintained).

Outputs:
- `.cursor-plugin/marketplace.json`: a mirror of the Claude marketplace so
Cursor exposes exactly the same plugin as Claude.
- `.cursor-plugin/plugin.json`: the Cursor plugin manifest derived from the
Claude plugin manifest + `plugin-metadata.json`.

Usage:
uv run .github/scripts/generate_cursor_marketplace.py # write
uv run .github/scripts/generate_cursor_marketplace.py --check # validate only

`--check` fails if any generated file is stale or if the Claude manifests'
top-level identity has drifted from `plugin-metadata.json`.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent.parent
PLUGIN_METADATA = ROOT / "plugin-metadata.json"
CLAUDE_MARKETPLACE = ROOT / ".claude-plugin" / "marketplace.json"
CLAUDE_PLUGIN = ROOT / ".claude-plugin" / "plugin.json"
CURSOR_MARKETPLACE = ROOT / ".cursor-plugin" / "marketplace.json"
CURSOR_PLUGIN = ROOT / ".cursor-plugin" / "plugin.json"


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 check_identity_consistency(metadata: dict, claude: dict, claude_plugin: dict) -> list[str]:
"""Return error strings if the Claude manifests' identity has drifted from
the canonical `plugin-metadata.json`."""
errors: list[str] = []

name = metadata.get("name")
description = metadata.get("description")
version = metadata.get("version")

if claude.get("name") != name:
errors.append(
f".claude-plugin/marketplace.json `name` ({claude.get('name')!r}) "
f"must match plugin-metadata.json `name` ({name!r})."
)
claude_description = claude.get("description")
if claude_description != description:
errors.append(".claude-plugin/marketplace.json `description` must match plugin-metadata.json `description`.")
claude_version = (claude.get("metadata") or {}).get("version")
if claude_version != version:
errors.append(
f".claude-plugin/marketplace.json metadata.version "
f"({claude_version!r}) must match plugin-metadata.json `version` "
f"({version!r})."
)

# The single bundled plugin entry's name must match the plugin manifest.
plugins = claude.get("plugins")
if not isinstance(plugins, list) or len(plugins) != 1:
errors.append(".claude-plugin/marketplace.json must list exactly one bundled plugin (source `./`).")
else:
entry_name = plugins[0].get("name")
if entry_name != claude_plugin.get("name"):
errors.append(
f".claude-plugin/marketplace.json plugin `name` ({entry_name!r}) "
f"must match .claude-plugin/plugin.json `name` "
f"({claude_plugin.get('name')!r})."
)

if claude_plugin.get("version") != version:
errors.append(
f".claude-plugin/plugin.json `version` "
f"({claude_plugin.get('version')!r}) must match plugin-metadata.json "
f"`version` ({version!r})."
)
return errors


def build_cursor_marketplace(metadata: dict, claude: dict) -> dict:
author = metadata.get("author") or {}
owner_name = author.get("name") if isinstance(author, dict) else None

return {
"name": metadata["name"],
"owner": {"name": owner_name} if owner_name else {},
"description": metadata["description"],
"metadata": {
"version": metadata["version"],
},
"plugins": claude.get("plugins", []),
}


def build_cursor_plugin(metadata: dict, claude_plugin: dict) -> dict:
return {
"name": claude_plugin["name"],
"version": metadata["version"],
"description": claude_plugin.get("description", metadata["description"]),
"author": metadata.get("author") or {},
"keywords": metadata.get("keywords", []),
}


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:
"""Return True when the file is already up to date."""
current = path.read_text(encoding="utf-8") if path.exists() else None
if current == content:
return True
if check:
return False
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return True


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Generate the .cursor-plugin/ manifests from the canonical "
"Claude manifests and plugin-metadata.json."
)
parser.add_argument(
"--check",
action="store_true",
help="Validate the generated manifests are up to date without writing.",
)
args = parser.parse_args(argv)

metadata = load_json(PLUGIN_METADATA)
claude = load_json(CLAUDE_MARKETPLACE)
claude_plugin = load_json(CLAUDE_PLUGIN)

identity_errors = check_identity_consistency(metadata, claude, claude_plugin)
if identity_errors:
print("Plugin manifest identity is inconsistent:", file=sys.stderr)
for err in identity_errors:
print(f" - {err}", file=sys.stderr)
return 1

targets = {
CURSOR_MARKETPLACE: render_json(build_cursor_marketplace(metadata, claude)),
CURSOR_PLUGIN: render_json(build_cursor_plugin(metadata, claude_plugin)),
}

stale = [path for path, content in targets.items() if not write_or_check(path, content, check=args.check)]

if args.check:
if stale:
for path in stale:
print(f"{path.relative_to(ROOT)} is out of date.", file=sys.stderr)
print(
"Run: uv run .github/scripts/generate_cursor_marketplace.py",
file=sys.stderr,
)
return 1
print("Cursor plugin manifests are up to date.")
return 0

for path in targets:
print(f"Wrote {path.relative_to(ROOT)}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
47 changes: 47 additions & 0 deletions .github/scripts/publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Regenerate every committed artifact derived from skills/ and the
# canonical marketplace + metadata sources.
#
# Usage:
# ./.github/scripts/publish.sh Regenerate all derived artifacts.
# ./.github/scripts/publish.sh --check Verify derived artifacts are up to date.
# ./.github/scripts/publish.sh -h|--help Print this help.
#
# Currently regenerates:
# - .cursor-plugin/marketplace.json (mirror of .claude-plugin/marketplace.json)
# - .cursor-plugin/plugin.json (derived from .claude-plugin/plugin.json
# + plugin-metadata.json)
#
# The `.claude-plugin/` manifests are hand-maintained because the human-facing
# plugin description intentionally differs from the SKILL.md routing
# descriptions; ./.github/scripts/check.sh enforces that they stay consistent
# with plugin-metadata.json.
#
# Requires `uv` (https://github.com/astral-sh/uv).

set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"

usage() {
sed -n 's/^# \{0,1\}//p' "${BASH_SOURCE[0]}" | sed -n '/^Usage:/,/^Requires/p'
}

case "${1:-}" in
"")
uv run .github/scripts/generate_cursor_marketplace.py
echo "Publish artifacts generated successfully."
;;
--check)
uv run .github/scripts/generate_cursor_marketplace.py --check
;;
-h|--help)
usage
;;
*)
echo "Unknown option: $1" >&2
echo "Run with --help for usage." >&2
exit 2
;;
esac
Loading
Loading