jdaugherty commented on PR #15805: URL: https://github.com/apache/grails-core/pull/15805#issuecomment-5816385352
AI Summary on how to implement this now: # Self-publishing SkillsJars under your own groupId — findings, a Groovy Gradle plan, and a few asks ## Summary I like SkillsJars a lot: packaging Agent Skills as Maven artifacts solves versioning and distribution neatly, and the catalog makes skills easy to find. But as far as I can tell there's no way to publish a SkillsJar yourself — the only publishing path is the form on skillsjars.com, which deploys under `com.skillsjars`. After reading the docs, `SPEC.md`, and the Gradle plugin README, my understanding is: - The **catalog and the `com.skillsjars` namespace are closed.** Only their pipeline can publish there, and the catalog only lists that groupId. - The **JAR format and the build plugins are open.** You can package skills yourself, publish them under your own groupId (Maven Central, GitHub Packages, a private Nexus/Artifactory), and the current Gradle/sbt/Maven plugins will extract them like any catalog JAR. You just give up the catalog listing. Below is what I found, a worked plan for a Groovy Gradle build, the gotchas I hit, and some asks for the maintainers. Corrections welcome. ## What's closed vs. open ### Closed: the catalog and the `com.skillsjars` namespace From [`[SPEC.md](https://github.com/skillsjars/skillsjars/blob/main/SPEC.md)`](https://github.com/skillsjars/skillsjars/blob/main/SPEC.md): - The catalog browses Agent Skills that live on Maven Central under `com.skillsjars`; the groupId is always `com.skillsjars`, and the artifactId is `<org>__<repo>__<skill>`. - Publishing is a `POST /deploy` with `org` and `repo`. The service shallow-clones the public GitHub repo, builds one JAR per `SKILL.md`, versions it as `YYYY_MM_DD-<shorthash>` from the latest commit, GPG-signs the JAR and POM, and uploads the bundle to Maven Central. - Skills without a resolvable license are skipped. Since SkillsJars owns that namespace on Central, there's no way to publish into it yourself, and self-published artifacts won't appear in the catalog. ### Open: the format and the plugins - **JAR layout** ([[docs](https://www.skillsjars.com/docs)](https://www.skillsjars.com/docs)): `META-INF/skills/<org>/<repo>/<skill>/SKILL.md`, with any files the skill references alongside it. The docs already describe packaging SkillsJars yourself with the Maven plugin's `package` goal, and the POM property convention `skillsjars.skill.<name>.allowed-tools`, which the plugin validates against the frontmatter. - **Gradle plugin** ([[README](https://github.com/skillsjars/skillsjars-gradle-plugin)](https://github.com/skillsjars/skillsjars-gradle-plugin)): [[PR #1](https://github.com/skillsjars/skillsjars-gradle-plugin/pull/1)](https://github.com/skillsjars/skillsjars-gradle-plugin/pull/1) (merged 2026-09-06, released as 0.1.0 per the maintainer's comment) brought sbt-plugin parity. The plugin now: - resolves dependencies from a dedicated `skill` configuration (`skill 'group:artifact:version'`), **for any groupId**, keeping them off the compile/runtime classpath; - looks for skill content in `META-INF/skills/` and `META-INF/resources/skills/`; - adds `packageSkillsJars`, which packs local `skills/` directories into `META-INF/skills/...` and is wired into `processResources`/`jar` when the `java` plugin is applied; - exposes a `skillsjars { gitHubUrl; allowedTools; outputDir }` extension and validates `allowed-tools` declarations during packaging. - **Spring AI**: the SkillsTool reads straight from `classpath:/META-INF/skills`, so JVM agents pick up self-published skills with no extraction step and no groupId dependence. > ⚠️ [[skillsjars.com/docs](https://www.skillsjars.com/docs)](https://www.skillsjars.com/docs) and [[/setup](https://www.skillsjars.com/setup)](https://www.skillsjars.com/setup) still show Gradle plugin `0.0.2`, and the setup guide says that version scans resolvable configurations for the `com.skillsjars` group only. Consumers on 0.0.2 will not extract self-published JARs; they need ≥ 0.1.0. ## Plan: self-publishing from a Groovy Gradle project ### Layout One subproject per skill if you want independent versions (mirrors the one-JAR-per-skill model of the catalog); a single project if the skills should travel together — `packageSkillsJars` packs everything under `skills/`. ``` agent-skills/ ├── settings.gradle ├── build.gradle ├── skills/ │ └── geb-testing/ │ ├── SKILL.md │ └── references/ └── .github/workflows/publish.yml ``` ### Producer `build.gradle` ```groovy plugins { id 'java-library' id 'maven-publish' id 'signing' id 'com.skillsjars.gradle-plugin' version '0.1.0' } group = 'com.yourorg.skills' version = '1.0.0' // semver — no longer tied to the date-hash scheme skillsjars { // Sets the path inside the JAR: META-INF/skills/yourorg/agent-skills/<skill>/ gitHubUrl = 'https://github.com/yourorg/agent-skills' // Only for skills whose SKILL.md declares allowed-tools; the plugin validates the match allowedTools = ['geb-testing': 'Bash Read Edit'] } // Central expects -sources and -javadoc jars even for resource-only artifacts; empty ones are fine java { withSourcesJar() withJavadocJar() } // Mirror what SkillsJars.com does: derive POM name/description from the frontmatter def frontMatter(File skillMd) { def m = (skillMd.text =~ /(?s)\A---\R(.*?)\R---/) m.find() ? m.group(1).readLines().findAll { it.contains(':') } .collectEntries { def (k, v) = it.split(/:/, 2); [(k.trim()): v.trim()] } : [:] } def fm = frontMatter(file('skills/geb-testing/SKILL.md')) // single-line YAML values only publishing { publications { skill(MavenPublication) { from components.java artifactId = 'geb-testing' pom { name = fm.name description = fm.description url = 'https://github.com/yourorg/agent-skills' licenses { license { name = fm.license ?: 'MIT' url = 'https://opensource.org/licenses/MIT' } } developers { developer { id = 'yourorg'; name = 'Your Org' } } scm { url = 'https://github.com/yourorg/agent-skills' connection = 'scm:git:https://github.com/yourorg/agent-skills.git' } // Same property convention as catalog JARs, readable without opening the JAR properties = ['skillsjars.skill.geb-testing.allowed-tools': 'Bash Read Edit'] } } } repositories { maven { name = 'GitHubPackages' url = uri('https://maven.pkg.github.com/yourorg/agent-skills') credentials { username = System.getenv('GITHUB_ACTOR') password = System.getenv('GITHUB_TOKEN') } } } } signing { def key = System.getenv('SIGNING_KEY') if (key) { useInMemoryPgpKeys(key, System.getenv('SIGNING_PASSWORD')) sign publishing.publications } } ``` ### Where to publish - **GitHub Packages / private Nexus / Artifactory** — no signing or POM ceremony required, but consumers need credentials in their `repositories {}` block. - **Maven Central under your own groupId** — go through the Sonatype Central Portal with a portal-aware plugin (e.g. `com.vanniktech.maven.publish` or `com.gradleup.nmcp`; check current versions). Requires a verified namespace, GPG signatures, full POM metadata (name, description, url, licenses, developers, scm), and sources/javadoc jars. ### Consumer `build.gradle` ```groovy plugins { id 'com.skillsjars.gradle-plugin' version '0.1.0' } repositories { mavenCentral() maven { url = uri('https://maven.pkg.github.com/yourorg/agent-skills') credentials { /* token */ } } } dependencies { skill 'com.yourorg.skills:geb-testing:1.0.0' // self-published skill 'com.skillsjars:anthropics__skills__pdf:2026_02_25-3d59511' // from the catalog, same mechanism } skillsjars { outputDir = layout.projectDirectory.dir('.claude/skills') } ``` Then `./gradlew extractSkillsJars`, add the output directory to `.gitignore`, and put the extract command in `AGENTS.md` / `CLAUDE.md` so agents run it on checkout. ### Local verification ```bash ./gradlew publishToMavenLocal unzip -l ~/.m2/repository/com/yourorg/skills/geb-testing/1.0.0/geb-testing-1.0.0.jar | grep META-INF/skills ``` Then add `mavenLocal()` to a consumer project and run `extractSkillsJars` before publishing anywhere remote. ### CI One workflow: on tag → `./gradlew publish`, with `GITHUB_TOKEN` (or Central Portal credentials), `SIGNING_KEY`, and `SIGNING_PASSWORD` as secrets. ## Gotchas 1. **Extraction directory naming.** Extraction writes `skillsjars__yourorg__agent-skills__geb-testing/`, not `geb-testing/`. [[skillsjars-maven-plugin#4](https://github.com/skillsjars/skillsjars-maven-plugin/issues/4)](https://github.com/skillsjars/skillsjars-maven-plugin/issues/4) notes that some assistants require the skill directory to match the frontmatter `name`. A small `Sync` task that renames after extraction works around it. 2. **Plugin version skew.** The public docs still reference 0.0.2, whose extractor only saw the `com.skillsjars` group. Anyone consuming self-published JARs must be on ≥ 0.1.0. 3. **Libraries carrying their own skill.** Putting `skills/` in a library project bakes the skill into that library's JAR. Classpath-reading agents (Spring AI) find it automatically, but file-based assistants still need the consumer to list the coordinate under `skill` for extraction. 4. **Discoverability is the real cost.** For public skills the two paths coexist: self-publish under your groupId for semver and private consumers, *and* run the same public repo through the skillsjars.com form so it also appears in the catalog as `com.skillsjars:yourorg__agent-skills__geb-testing:<date-hash>`. ## Asks / questions for the maintainers 1. Update [[/docs](https://www.skillsjars.com/docs)](https://www.skillsjars.com/docs) and [[/setup](https://www.skillsjars.com/setup)](https://www.skillsjars.com/setup) to 0.1.0 and document the `skill` configuration, `packageSkillsJars`, and the `skillsjars {}` extension for Gradle. 2. Document self-publishing (JAR layout + POM property conventions) as a supported path, even if the catalog stays `com.skillsjars`-only. Right now the only hint is one sentence about packaging with the Maven plugin. 3. Would the catalog ever index skills outside `com.skillsjars` — e.g. an opt-in registration of coordinates, or an indexer that looks for `META-INF/skills` in Central artifacts? That would keep self-published skills discoverable, which is the main thing lost today. 4. Does the Gradle plugin's `allowedTools` map get written into the generated POM as `skillsjars.skill.<name>.allowed-tools`, or is that left to `maven-publish` (as in the plan above)? The README isn't explicit. 5. A flag on the extract tasks to use the frontmatter `name` as the output directory (see maven-plugin#4) would help assistants that require directory/name parity. ## References - Docs: https://www.skillsjars.com/docs - Setup guide: https://www.skillsjars.com/setup - SPEC.md: https://github.com/skillsjars/skillsjars/blob/main/SPEC.md - Gradle plugin: https://github.com/skillsjars/skillsjars-gradle-plugin - Gradle plugin PR #1 (sbt parity, 0.1.0): https://github.com/skillsjars/skillsjars-gradle-plugin/pull/1 - sbt plugin: https://github.com/skillsjars/skillsjars-sbt-plugin - Maven plugin: https://github.com/skillsjars/skillsjars-maven-plugin - Directory-naming issue: https://github.com/skillsjars/skillsjars-maven-plugin/issues/4 -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
