name: Agent marketplace release on: workflow_call: inputs: target_branch: description: Branch that receives release-please changes. required: false type: string default: main skill_root: description: Canonical skill directory relative to the repository root. required: false type: string default: skills claude_marketplace_file: description: Claude marketplace manifest relative to the repository root. required: false type: string default: .claude-plugin/marketplace.json claude_plugin_file: description: Claude plugin manifest relative to the repository root. required: false type: string default: .claude-plugin/plugin.json codex_plugin_root: description: Codex plugin root relative to the repository root. required: false type: string default: plugins/marketplace cursor_plugin_root: description: Cursor plugin root relative to the repository root. required: false type: string default: plugins/marketplace release_config_file: description: Release Please configuration file. required: false type: string default: release-please-config.json release_manifest_file: description: Release Please manifest file. required: false type: string default: .release-please-manifest.json package_paths: description: Space-separated paths to package into the release archive. required: false type: string default: skills .claude-plugin .cursor-plugin .agents plugins README.md CHANGELOG.md archive_name: description: Prefix used for the release archive. required: false type: string default: agent-marketplace gitea_package_name: description: Public Gitea generic package name for the release artifact. required: false type: string default: agent-marketplace claude_plugin_name: description: Claude plugin name used in the installation summary. required: false type: string default: marketplace codex_plugin_name: description: Codex plugin name used in the installation summary. required: false type: string default: marketplace claude_marketplace_name: description: Claude marketplace name used in the installation summary. required: false type: string default: marketplace codex_marketplace_name: description: Codex marketplace name shown in the installation summary. required: false type: string default: marketplace cursor_plugin_name: description: Cursor plugin name shown in the installation summary. required: false type: string default: marketplace publish_gitea: description: Upload public installers for Claude, Codex, OpenCode, and Cursor to Gitea. required: false type: boolean default: false gitea_url: description: Base URL of the Gitea instance. required: false type: string default: "" gitea_user: description: Gitea user or organization that owns the generic package. required: false type: string default: "" secrets: GITHUB_PAT: description: Optional GitHub token for release-please pull requests. required: false GITEA_TOKEN: description: Gitea token with permission to publish generic packages. required: false permissions: contents: write pull-requests: write concurrency: group: release-please cancel-in-progress: false jobs: release-please: runs-on: ubuntu-latest outputs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} version: ${{ steps.release.outputs.version }} steps: - name: Create or update release PR id: release uses: googleapis/release-please-action@v4 with: token: ${{ secrets.GITHUB_PAT || github.token }} target-branch: ${{ inputs.target_branch }} config-file: ${{ inputs.release_config_file }} manifest-file: ${{ inputs.release_manifest_file }} publish: needs: release-please if: needs.release-please.outputs.release_created == 'true' runs-on: ubuntu-latest permissions: contents: write steps: - name: Check out released version uses: actions/checkout@v4 with: ref: ${{ needs.release-please.outputs.tag_name }} - name: Validate released marketplace uses: walter-base/common-workflow/actions/validate-agent-marketplace@master with: skill_root: ${{ inputs.skill_root }} claude_marketplace_file: ${{ inputs.claude_marketplace_file }} claude_plugin_file: ${{ inputs.claude_plugin_file }} codex_plugin_root: ${{ inputs.codex_plugin_root }} cursor_plugin_root: ${{ inputs.cursor_plugin_root }} - name: Build release archive env: VERSION: ${{ needs.release-please.outputs.version }} PACKAGE_PATHS: ${{ inputs.package_paths }} ARCHIVE_NAME: ${{ inputs.archive_name }} shell: bash run: | dir="${ARCHIVE_NAME}-${VERSION}" mkdir -p "dist/${dir}" for path in ${PACKAGE_PATHS}; do cp -R "$path" "dist/${dir}/" done tar -czf "dist/${ARCHIVE_NAME}-${VERSION}.tar.gz" -C dist "${dir}" - name: Build Gitea installers if: inputs.publish_gitea env: ARCHIVE_NAME: ${{ inputs.archive_name }} GITEA_PACKAGE_NAME: ${{ inputs.gitea_package_name }} GITEA_URL: ${{ inputs.gitea_url }} GITEA_USER: ${{ inputs.gitea_user }} VERSION: ${{ needs.release-please.outputs.version }} shell: bash run: | : "${GITEA_URL:?Set gitea_url when publish_gitea is enabled}" : "${GITEA_USER:?Set gitea_user when publish_gitea is enabled}" cat > "dist/install-${ARCHIVE_NAME}-${VERSION}.sh" <&2; exit 2 ;; esac if [[ "\$version" == latest ]]; then version="\$(curl --fail --location --retry 3 "${GITEA_URL%/}/api/v1/packages/${GITEA_USER}/generic/${GITEA_PACKAGE_NAME}/-/latest" | jq -r '.version // empty')" : "\${version:?Gitea did not return a latest package version}" fi install_dir="\${MEDIVA_PLUGIN_DIR:-\$HOME/.local/share/${ARCHIVE_NAME}/\$agent}" archive_url="${GITEA_URL%/}/api/packages/${GITEA_USER}/generic/${GITEA_PACKAGE_NAME}/\${version}/${ARCHIVE_NAME}-\${version}.tar.gz" tmp_dir="\$(mktemp -d)" trap 'rm -rf "\$tmp_dir"' EXIT curl --fail --location --retry 3 "\$archive_url" -o "\$tmp_dir/plugin.tar.gz" mkdir -p "\$tmp_dir/extracted" tar -xzf "\$tmp_dir/plugin.tar.gz" -C "\$tmp_dir/extracted" rm -rf "\$install_dir" mkdir -p "\$(dirname "\$install_dir")" mv "\$tmp_dir/extracted/${ARCHIVE_NAME}-\${version}" "\$install_dir" case "\$agent" in claude) marketplace_name="\$(jq -r '.name // empty' "\$install_dir/.claude-plugin/marketplace.json")" : "\${marketplace_name:?Claude marketplace manifest is missing a name}" claude plugin marketplace remove "\$marketplace_name" >/dev/null 2>&1 || true claude plugin marketplace add "\$install_dir" plugin_name="\$(jq -r '.plugins[0].name // empty' "\$install_dir/.claude-plugin/marketplace.json")" claude plugin install "\$plugin_name@\$marketplace_name" ;; codex) marketplace_name="\$(jq -r '.name // empty' "\$install_dir/.agents/plugins/marketplace.json")" : "\${marketplace_name:?Codex marketplace manifest is missing a name}" codex plugin marketplace remove "\$marketplace_name" >/dev/null 2>&1 || true codex plugin marketplace add "\$install_dir" plugin_name="\$(jq -r '.plugins[0].name // empty' "\$install_dir/.agents/plugins/marketplace.json")" codex plugin add "\$plugin_name@\$marketplace_name" ;; opencode|cursor) npx --yes skills add "\$install_dir/skills" --agent "\$agent" --skill '*' --yes --global ;; esac echo "Installed skills for \$agent from Gitea." EOF chmod +x "dist/install-${ARCHIVE_NAME}-${VERSION}.sh" if false; then cat > "dist/install-${ARCHIVE_NAME}-codex-${VERSION}.sh" </dev/null 2>&1 || true codex plugin marketplace remove "\$marketplace_name" >/dev/null 2>&1 || true codex plugin marketplace add "\$install_dir" codex plugin add "${CODEX_PLUGIN_NAME}@\$marketplace_name" echo "Installed ${CODEX_PLUGIN_NAME} for Codex from Gitea marketplace \$marketplace_name." EOF chmod +x "dist/install-${ARCHIVE_NAME}-codex-${VERSION}.sh" cat > "dist/install-${ARCHIVE_NAME}-opencode-${VERSION}.sh" < "dist/install-${ARCHIVE_NAME}-cursor-${VERSION}.sh" <> "$GITHUB_OUTPUT" - name: Upload archive to GitHub Release if: ${{ !inputs.publish_gitea }} uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.release-please.outputs.tag_name }} files: dist/* - name: Write GitHub installation summary env: VERSION: ${{ needs.release-please.outputs.version }} TAG: ${{ needs.release-please.outputs.tag_name }} ARCHIVE_NAME: ${{ inputs.archive_name }} REPOSITORY: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} SKILL_ROOT: ${{ inputs.skill_root }} CLAUDE_PLUGIN_NAME: ${{ inputs.claude_plugin_name }} CLAUDE_MARKETPLACE_NAME: ${{ inputs.claude_marketplace_name }} CODEX_PLUGIN_NAME: ${{ inputs.codex_plugin_name }} CODEX_MARKETPLACE_NAME: ${{ inputs.codex_marketplace_name }} CURSOR_PLUGIN_NAME: ${{ inputs.cursor_plugin_name }} GITEA_INSTALLER_URL: ${{ steps.gitea.outputs.installer_url }} run: | { printf '# Agent plugin %s\n\n' "$VERSION" printf 'Release tag: `%s`\n\n' "$TAG" if [[ -n "$GITEA_INSTALLER_URL" ]]; then printf '## Claude Code\n\n' printf '%s\n' '```bash' printf 'curl --fail --location --output install-claude.sh "%s"\n' "$GITEA_INSTALLER_URL" printf 'chmod +x install-claude.sh\n' printf './install-claude.sh claude\n' printf '%s\n\n' '```' printf '## Codex\n\n' printf '%s\n' '```bash' printf 'curl --fail --location --output install-codex.sh "%s"\n' "$GITEA_INSTALLER_URL" printf 'chmod +x install-codex.sh\n' printf './install-codex.sh codex\n' printf '%s\n\n' '```' else printf '## Claude Code\n\n' printf '%s\n' '```bash' printf 'claude plugin marketplace add %s@%s\n' "$REPOSITORY" "$TAG" printf 'claude plugin install %s@%s\n' "$CLAUDE_PLUGIN_NAME" "$CLAUDE_MARKETPLACE_NAME" printf '%s\n\n' '```' printf '## Codex\n\n' printf '%s\n' '```bash' printf 'codex plugin marketplace add %s --ref %s\n' "$REPOSITORY" "$TAG" printf 'codex plugin add %s@%s\n' "$CODEX_PLUGIN_NAME" "$CODEX_MARKETPLACE_NAME" printf '%s\n\n' '```' fi if [[ -n "$GITEA_INSTALLER_URL" ]]; then printf '## OpenCode\n\n' printf '%s\n' '```bash' printf 'curl --fail --location --output install-opencode.sh "%s"\n' "$GITEA_INSTALLER_URL" printf 'chmod +x install-opencode.sh\n' printf './install-opencode.sh opencode\n' printf '%s\n\n' '```' printf '## Cursor\n\n' printf '%s\n' '```bash' printf 'curl --fail --location --output install-cursor.sh "%s"\n' "$GITEA_INSTALLER_URL" printf 'chmod +x install-cursor.sh\n' printf './install-cursor.sh cursor\n' printf '%s\n\n' '```' else printf '## OpenCode and Cursor\n\n' printf '%s\n' '```bash' printf 'npx skills add %s/%s/tree/%s/%s --agent opencode --skill '\''*'\'' --yes --global\n' "$SERVER_URL" "$REPOSITORY" "$TAG" "$SKILL_ROOT" printf 'npx skills add %s/%s/tree/%s/%s --agent cursor --skill '\''*'\'' --yes --global\n' "$SERVER_URL" "$REPOSITORY" "$TAG" "$SKILL_ROOT" printf '%s\n' '```' fi if [[ -n "$GITEA_INSTALLER_URL" ]]; then printf '\nUpdate all installed agents later with:\n\n%s\n\n' '```bash' printf './install-%s-%s.sh update all\n' "$ARCHIVE_NAME" "$VERSION" printf '%s\n' '```' printf 'Installers are public Gitea artifacts; no Gitea token is required.\n' fi } >> "$GITHUB_STEP_SUMMARY"