From d3bd66b1847c360be5da16eded2bd548f69c49ee Mon Sep 17 00:00:00 2001 From: terra tauri Date: Sun, 1 Feb 2026 23:26:50 -0800 Subject: [PATCH 1/5] docs: add comprehensive getting started guide Add GETTING_STARTED.md with step-by-step walkthrough: - Repository structure setup - Configuration examples - First plugin creation - Usage in Claude Code - Troubleshooting common issues - Links from main README and action docs --- GETTING_STARTED.md | 428 ++++++++++++++++++++++++++++++++++ README.md | 2 + agentic-marketplace/README.md | 2 + 3 files changed, 432 insertions(+) create mode 100644 GETTING_STARTED.md diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md new file mode 100644 index 0000000..b103849 --- /dev/null +++ b/GETTING_STARTED.md @@ -0,0 +1,428 @@ +# Getting Started with Agentic Marketplace + +This guide walks you through setting up and using the Agentic Marketplace automation for your Claude Code plugins. + +## What You'll Build + +By the end of this guide, you'll have: +- A marketplace repository with automated discovery and validation +- Plugin components organized in a standardized structure +- A `marketplace.json` file that updates automatically when you add or modify plugins +- CI/CD workflows that maintain your marketplace + +## Prerequisites + +- GitHub repository for your marketplace +- Git and GitHub CLI (`gh`) installed +- Basic familiarity with GitHub Actions + +## Step 1: Create Marketplace Repository Structure + +Create the following directory structure in your repository: + +```bash +# Create the base structure +mkdir -p .claude-plugin +mkdir -p .github/workflows + +# Create category directories (customize these for your needs) +mkdir -p code +mkdir -p analysis +mkdir -p communication +``` + +Your repository should look like: + +``` +your-marketplace/ +├── .claude-plugin/ +├── .github/ +│ └── workflows/ +├── code/ # Category for code-related plugins +├── analysis/ # Category for analysis plugins +└── communication/ # Category for communication plugins +``` + +## Step 2: Configure the Marketplace + +Create `.claude-plugin/generator.config.toml`: + +```toml +# Naming pattern for components (kebab-case) +naming_pattern = "^[a-z0-9]+(-[a-z0-9]+)*$" + +# Reserved words that cannot appear in component names +reserved_words = ["anthropic", "claude"] + +# Plugin discovery paths - adjust to match your categories +plugin_categories = ["code/**", "analysis/**", "communication/**"] + +# Component types to discover +[discovery] +plugins = true +commands = true +skills = true +agents = true +hooks = true +mcp_servers = true + +# Validation rules +[validation] +require_description = true +require_version = true +min_description_length = 10 +max_description_length = 200 +``` + +## Step 3: Add the Workflow + +Create `.github/workflows/agentic-marketplace.yml`: + +```yaml +name: Update Agentic Marketplace + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: write + pull-requests: write + +jobs: + update: + uses: bitcomplete/bc-github-actions/.github/workflows/agentic-marketplace.yml@v1 + with: + config-path: .claude-plugin/generator.config.toml + secrets: + token: ${{ secrets.GITHUB_TOKEN }} +``` + +Commit and push: + +```bash +git add . +git commit -m "feat: add agentic marketplace automation" +git push +``` + +## Step 4: Create Your First Plugin + +Create a plugin with the two-level directory structure: `category/plugin-name/` + +```bash +# Create plugin directory +mkdir -p code/my-first-plugin/commands + +# Create a command +cat > code/my-first-plugin/commands/hello.md << 'EOF' +--- +name: hello +description: Say hello to the world +--- + +Print "Hello, World!" to the console. +EOF +``` + +Your structure should now look like: + +``` +your-marketplace/ +├── .claude-plugin/ +│ └── generator.config.toml +├── .github/ +│ └── workflows/ +│ └── agentic-marketplace.yml +└── code/ + └── my-first-plugin/ + └── commands/ + └── hello.md +``` + +## Step 5: Push and Watch the Magic + +```bash +git add . +git commit -m "feat: add my-first-plugin" +git push +``` + +The workflow will: +1. **Discover** your plugin and its components +2. **Validate** naming and metadata +3. **Generate** `.claude-plugin/marketplace.json` and `code/my-first-plugin/.claude-plugin/plugin.json` +4. **Create a PR** with the generated files +5. **Auto-merge** after CI passes (if enabled) + +Check your Actions tab to see it run! + +## Step 6: Understanding the Generated Files + +After the workflow completes, you'll see: + +### `.claude-plugin/marketplace.json` + +```json +{ + "name": "your-marketplace", + "owner": "your-org", + "plugins": [ + { + "name": "my-first-plugin", + "description": "...", + "components": { + "commands": [ + { + "name": "hello", + "description": "Say hello to the world", + "source": "code/my-first-plugin/commands/hello.md" + } + ] + } + } + ] +} +``` + +### `code/my-first-plugin/.claude-plugin/plugin.json` + +```json +{ + "name": "my-first-plugin", + "version": "1.0.0", + "description": "...", + "commands": ["hello"] +} +``` + +## Step 7: Using Your Marketplace in Claude Code + +### Option A: Install from GitHub + +Users can install plugins from your marketplace: + +```bash +# Install the entire marketplace +claude plugin install github:your-org/your-marketplace + +# Install a specific plugin +claude plugin install github:your-org/your-marketplace/code/my-first-plugin +``` + +### Option B: Local Development + +Clone and link for local development: + +```bash +# Clone your marketplace +git clone https://github.com/your-org/your-marketplace.git + +# Link for local use +cd your-marketplace +claude plugin link code/my-first-plugin +``` + +## Adding More Components + +### Commands + +Commands are slash commands users can invoke: + +```bash +mkdir -p code/my-plugin/commands +cat > code/my-plugin/commands/example.md << 'EOF' +--- +name: example +description: Example command +--- + +Command implementation here. +EOF +``` + +### Skills + +Skills provide instructional content: + +```bash +mkdir -p code/my-plugin/skills +cat > code/my-plugin/skills/example-skill.md << 'EOF' +--- +name: example-skill +description: Example skill +--- + +Skill content here. +EOF +``` + +### Agents + +Agents are autonomous subagents: + +```bash +mkdir -p code/my-plugin/agents +cat > code/my-plugin/agents/example-agent.md << 'EOF' +--- +name: example-agent +description: Example agent +color: blue +--- + +Agent system prompt here. +EOF +``` + +### Hooks + +Hooks respond to events: + +```bash +mkdir -p code/my-plugin/hooks +cat > code/my-plugin/hooks/hooks.json << 'EOF' +{ + "hooks": [ + { + "event": "PreToolUse", + "filter": {"toolName": "Bash"}, + "action": { + "type": "prompt", + "prompt": "Before running bash commands, check for destructive operations." + } + } + ] +} +EOF +``` + +## Validation and Testing + +### Local Validation + +Before pushing, validate your components: + +```bash +# Clone the bc-github-actions repo +git clone https://github.com/bitcomplete/bc-github-actions.git + +# Run validation +cd your-marketplace +../bc-github-actions/scripts/dist/discover-components.cjs validate +``` + +### Common Validation Errors + +**Error: Component name doesn't match pattern** +``` +Fix: Use kebab-case: my-component (not myComponent or my_component) +``` + +**Error: Description too short** +``` +Fix: Add a meaningful description (min 10 characters) +``` + +**Error: Missing required fields** +``` +Fix: Ensure frontmatter has name and description +``` + +## Customization + +### Change Naming Convention + +Edit `.claude-plugin/generator.config.toml`: + +```toml +# For snake_case +naming_pattern = "^[a-z0-9]+(_[a-z0-9]+)*$" + +# For camelCase +naming_pattern = "^[a-z][a-zA-Z0-9]*$" +``` + +### Disable Auto-Merge + +Edit `.github/workflows/agentic-marketplace.yml`: + +```yaml +jobs: + update: + uses: bitcomplete/bc-github-actions/.github/workflows/agentic-marketplace.yml@v1 + with: + config-path: .claude-plugin/generator.config.toml + auto-merge: false # Add this line + secrets: + token: ${{ secrets.GITHUB_TOKEN }} +``` + +### Add Custom Validation + +Create a separate workflow for custom validation: + +```yaml +name: Custom Validation + +on: + pull_request: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: bitcomplete/bc-github-actions/agentic-marketplace/validate@v1 + id: validate + with: + fail-on-error: false + + - name: Custom checks + if: steps.validate.outputs.valid == 'false' + run: | + echo "Custom validation logic here" +``` + +## Troubleshooting + +### Workflow Not Running + +Check: +- Workflow file is in `.github/workflows/` +- File has `.yml` extension +- Syntax is valid YAML + +### No Components Discovered + +Check: +- Plugin structure is `category/plugin-name/` +- Component files have YAML frontmatter +- `plugin_categories` in config matches your directories + +### Validation Failing + +Check: +- Component names use kebab-case (or your configured pattern) +- All components have name and description +- Description length is within bounds (10-200 characters) + +### PR Not Auto-Merging + +Check: +- Repository settings allow auto-merge +- No required reviews or status checks blocking +- Branch protection rules don't prevent auto-merge + +## Next Steps + +- Read the [full documentation](agentic-marketplace/README.md) +- Check [example workflows](https://github.com/bitcomplete/bc-llm-skills) +- Explore [advanced usage patterns](agentic-marketplace/README.md#advanced-usage) + +## Getting Help + +- Open an issue: https://github.com/bitcomplete/bc-github-actions/issues +- Check examples: https://github.com/bitcomplete/bc-llm-skills diff --git a/README.md b/README.md index 351c46b..d1b5ea4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Reusable GitHub Actions workflows for Claude Code development and Platform Engineering. We use these workflows ourselves, and now you can too. +**[📖 Getting Started Guide →](GETTING_STARTED.md)** - New to agentic marketplaces? Start here! + ## Actions ### Agentic Marketplace Automation diff --git a/agentic-marketplace/README.md b/agentic-marketplace/README.md index 2429837..0669945 100644 --- a/agentic-marketplace/README.md +++ b/agentic-marketplace/README.md @@ -2,6 +2,8 @@ Automates Claude Code plugin marketplace management through auto-discovery, validation, and synchronization. +**New to agentic marketplaces?** Check out the [Getting Started Guide](../GETTING_STARTED.md) for a step-by-step walkthrough. + ## Overview The agentic-marketplace action provides three composable actions that work together to manage your Claude Code plugin marketplace: From b5ae21cb10c7e082e7f72fc7b1fca7f2decd6c74 Mon Sep 17 00:00:00 2001 From: terra tauri Date: Sun, 1 Feb 2026 23:35:39 -0800 Subject: [PATCH 2/5] fix: move GETTING_STARTED.md to docs/ to avoid component scanning --- README.md | 2 +- agentic-marketplace/README.md | 2 +- GETTING_STARTED.md => docs/GETTING_STARTED.md | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename GETTING_STARTED.md => docs/GETTING_STARTED.md (100%) diff --git a/README.md b/README.md index d1b5ea4..59e3cdb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Reusable GitHub Actions workflows for Claude Code development and Platform Engineering. We use these workflows ourselves, and now you can too. -**[📖 Getting Started Guide →](GETTING_STARTED.md)** - New to agentic marketplaces? Start here! +**[📖 Getting Started Guide →](docs/GETTING_STARTED.md)** - New to agentic marketplaces? Start here! ## Actions diff --git a/agentic-marketplace/README.md b/agentic-marketplace/README.md index 0669945..b54f6c1 100644 --- a/agentic-marketplace/README.md +++ b/agentic-marketplace/README.md @@ -2,7 +2,7 @@ Automates Claude Code plugin marketplace management through auto-discovery, validation, and synchronization. -**New to agentic marketplaces?** Check out the [Getting Started Guide](../GETTING_STARTED.md) for a step-by-step walkthrough. +**New to agentic marketplaces?** Check out the [Getting Started Guide](../docs/GETTING_STARTED.md) for a step-by-step walkthrough. ## Overview diff --git a/GETTING_STARTED.md b/docs/GETTING_STARTED.md similarity index 100% rename from GETTING_STARTED.md rename to docs/GETTING_STARTED.md From 4a7d4781b0160fa0777efc2004e359e0eb9ae854 Mon Sep 17 00:00:00 2001 From: terra tauri Date: Sun, 1 Feb 2026 23:36:35 -0800 Subject: [PATCH 3/5] fix: remove docs to unblock CI (will add back separately) --- docs/GETTING_STARTED.md | 428 ---------------------------------------- 1 file changed, 428 deletions(-) delete mode 100644 docs/GETTING_STARTED.md diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md deleted file mode 100644 index b103849..0000000 --- a/docs/GETTING_STARTED.md +++ /dev/null @@ -1,428 +0,0 @@ -# Getting Started with Agentic Marketplace - -This guide walks you through setting up and using the Agentic Marketplace automation for your Claude Code plugins. - -## What You'll Build - -By the end of this guide, you'll have: -- A marketplace repository with automated discovery and validation -- Plugin components organized in a standardized structure -- A `marketplace.json` file that updates automatically when you add or modify plugins -- CI/CD workflows that maintain your marketplace - -## Prerequisites - -- GitHub repository for your marketplace -- Git and GitHub CLI (`gh`) installed -- Basic familiarity with GitHub Actions - -## Step 1: Create Marketplace Repository Structure - -Create the following directory structure in your repository: - -```bash -# Create the base structure -mkdir -p .claude-plugin -mkdir -p .github/workflows - -# Create category directories (customize these for your needs) -mkdir -p code -mkdir -p analysis -mkdir -p communication -``` - -Your repository should look like: - -``` -your-marketplace/ -├── .claude-plugin/ -├── .github/ -│ └── workflows/ -├── code/ # Category for code-related plugins -├── analysis/ # Category for analysis plugins -└── communication/ # Category for communication plugins -``` - -## Step 2: Configure the Marketplace - -Create `.claude-plugin/generator.config.toml`: - -```toml -# Naming pattern for components (kebab-case) -naming_pattern = "^[a-z0-9]+(-[a-z0-9]+)*$" - -# Reserved words that cannot appear in component names -reserved_words = ["anthropic", "claude"] - -# Plugin discovery paths - adjust to match your categories -plugin_categories = ["code/**", "analysis/**", "communication/**"] - -# Component types to discover -[discovery] -plugins = true -commands = true -skills = true -agents = true -hooks = true -mcp_servers = true - -# Validation rules -[validation] -require_description = true -require_version = true -min_description_length = 10 -max_description_length = 200 -``` - -## Step 3: Add the Workflow - -Create `.github/workflows/agentic-marketplace.yml`: - -```yaml -name: Update Agentic Marketplace - -on: - push: - branches: [main] - pull_request: - branches: [main] - -permissions: - contents: write - pull-requests: write - -jobs: - update: - uses: bitcomplete/bc-github-actions/.github/workflows/agentic-marketplace.yml@v1 - with: - config-path: .claude-plugin/generator.config.toml - secrets: - token: ${{ secrets.GITHUB_TOKEN }} -``` - -Commit and push: - -```bash -git add . -git commit -m "feat: add agentic marketplace automation" -git push -``` - -## Step 4: Create Your First Plugin - -Create a plugin with the two-level directory structure: `category/plugin-name/` - -```bash -# Create plugin directory -mkdir -p code/my-first-plugin/commands - -# Create a command -cat > code/my-first-plugin/commands/hello.md << 'EOF' ---- -name: hello -description: Say hello to the world ---- - -Print "Hello, World!" to the console. -EOF -``` - -Your structure should now look like: - -``` -your-marketplace/ -├── .claude-plugin/ -│ └── generator.config.toml -├── .github/ -│ └── workflows/ -│ └── agentic-marketplace.yml -└── code/ - └── my-first-plugin/ - └── commands/ - └── hello.md -``` - -## Step 5: Push and Watch the Magic - -```bash -git add . -git commit -m "feat: add my-first-plugin" -git push -``` - -The workflow will: -1. **Discover** your plugin and its components -2. **Validate** naming and metadata -3. **Generate** `.claude-plugin/marketplace.json` and `code/my-first-plugin/.claude-plugin/plugin.json` -4. **Create a PR** with the generated files -5. **Auto-merge** after CI passes (if enabled) - -Check your Actions tab to see it run! - -## Step 6: Understanding the Generated Files - -After the workflow completes, you'll see: - -### `.claude-plugin/marketplace.json` - -```json -{ - "name": "your-marketplace", - "owner": "your-org", - "plugins": [ - { - "name": "my-first-plugin", - "description": "...", - "components": { - "commands": [ - { - "name": "hello", - "description": "Say hello to the world", - "source": "code/my-first-plugin/commands/hello.md" - } - ] - } - } - ] -} -``` - -### `code/my-first-plugin/.claude-plugin/plugin.json` - -```json -{ - "name": "my-first-plugin", - "version": "1.0.0", - "description": "...", - "commands": ["hello"] -} -``` - -## Step 7: Using Your Marketplace in Claude Code - -### Option A: Install from GitHub - -Users can install plugins from your marketplace: - -```bash -# Install the entire marketplace -claude plugin install github:your-org/your-marketplace - -# Install a specific plugin -claude plugin install github:your-org/your-marketplace/code/my-first-plugin -``` - -### Option B: Local Development - -Clone and link for local development: - -```bash -# Clone your marketplace -git clone https://github.com/your-org/your-marketplace.git - -# Link for local use -cd your-marketplace -claude plugin link code/my-first-plugin -``` - -## Adding More Components - -### Commands - -Commands are slash commands users can invoke: - -```bash -mkdir -p code/my-plugin/commands -cat > code/my-plugin/commands/example.md << 'EOF' ---- -name: example -description: Example command ---- - -Command implementation here. -EOF -``` - -### Skills - -Skills provide instructional content: - -```bash -mkdir -p code/my-plugin/skills -cat > code/my-plugin/skills/example-skill.md << 'EOF' ---- -name: example-skill -description: Example skill ---- - -Skill content here. -EOF -``` - -### Agents - -Agents are autonomous subagents: - -```bash -mkdir -p code/my-plugin/agents -cat > code/my-plugin/agents/example-agent.md << 'EOF' ---- -name: example-agent -description: Example agent -color: blue ---- - -Agent system prompt here. -EOF -``` - -### Hooks - -Hooks respond to events: - -```bash -mkdir -p code/my-plugin/hooks -cat > code/my-plugin/hooks/hooks.json << 'EOF' -{ - "hooks": [ - { - "event": "PreToolUse", - "filter": {"toolName": "Bash"}, - "action": { - "type": "prompt", - "prompt": "Before running bash commands, check for destructive operations." - } - } - ] -} -EOF -``` - -## Validation and Testing - -### Local Validation - -Before pushing, validate your components: - -```bash -# Clone the bc-github-actions repo -git clone https://github.com/bitcomplete/bc-github-actions.git - -# Run validation -cd your-marketplace -../bc-github-actions/scripts/dist/discover-components.cjs validate -``` - -### Common Validation Errors - -**Error: Component name doesn't match pattern** -``` -Fix: Use kebab-case: my-component (not myComponent or my_component) -``` - -**Error: Description too short** -``` -Fix: Add a meaningful description (min 10 characters) -``` - -**Error: Missing required fields** -``` -Fix: Ensure frontmatter has name and description -``` - -## Customization - -### Change Naming Convention - -Edit `.claude-plugin/generator.config.toml`: - -```toml -# For snake_case -naming_pattern = "^[a-z0-9]+(_[a-z0-9]+)*$" - -# For camelCase -naming_pattern = "^[a-z][a-zA-Z0-9]*$" -``` - -### Disable Auto-Merge - -Edit `.github/workflows/agentic-marketplace.yml`: - -```yaml -jobs: - update: - uses: bitcomplete/bc-github-actions/.github/workflows/agentic-marketplace.yml@v1 - with: - config-path: .claude-plugin/generator.config.toml - auto-merge: false # Add this line - secrets: - token: ${{ secrets.GITHUB_TOKEN }} -``` - -### Add Custom Validation - -Create a separate workflow for custom validation: - -```yaml -name: Custom Validation - -on: - pull_request: - -jobs: - validate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: bitcomplete/bc-github-actions/agentic-marketplace/validate@v1 - id: validate - with: - fail-on-error: false - - - name: Custom checks - if: steps.validate.outputs.valid == 'false' - run: | - echo "Custom validation logic here" -``` - -## Troubleshooting - -### Workflow Not Running - -Check: -- Workflow file is in `.github/workflows/` -- File has `.yml` extension -- Syntax is valid YAML - -### No Components Discovered - -Check: -- Plugin structure is `category/plugin-name/` -- Component files have YAML frontmatter -- `plugin_categories` in config matches your directories - -### Validation Failing - -Check: -- Component names use kebab-case (or your configured pattern) -- All components have name and description -- Description length is within bounds (10-200 characters) - -### PR Not Auto-Merging - -Check: -- Repository settings allow auto-merge -- No required reviews or status checks blocking -- Branch protection rules don't prevent auto-merge - -## Next Steps - -- Read the [full documentation](agentic-marketplace/README.md) -- Check [example workflows](https://github.com/bitcomplete/bc-llm-skills) -- Explore [advanced usage patterns](agentic-marketplace/README.md#advanced-usage) - -## Getting Help - -- Open an issue: https://github.com/bitcomplete/bc-github-actions/issues -- Check examples: https://github.com/bitcomplete/bc-llm-skills From 1c502c077399f2acc488cbce26341a56faeee6d8 Mon Sep 17 00:00:00 2001 From: terra tauri Date: Sun, 1 Feb 2026 23:36:41 -0800 Subject: [PATCH 4/5] fix: remove getting started links --- README.md | 2 -- agentic-marketplace/README.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/README.md b/README.md index 59e3cdb..351c46b 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Reusable GitHub Actions workflows for Claude Code development and Platform Engineering. We use these workflows ourselves, and now you can too. -**[📖 Getting Started Guide →](docs/GETTING_STARTED.md)** - New to agentic marketplaces? Start here! - ## Actions ### Agentic Marketplace Automation diff --git a/agentic-marketplace/README.md b/agentic-marketplace/README.md index b54f6c1..2429837 100644 --- a/agentic-marketplace/README.md +++ b/agentic-marketplace/README.md @@ -2,8 +2,6 @@ Automates Claude Code plugin marketplace management through auto-discovery, validation, and synchronization. -**New to agentic marketplaces?** Check out the [Getting Started Guide](../docs/GETTING_STARTED.md) for a step-by-step walkthrough. - ## Overview The agentic-marketplace action provides three composable actions that work together to manage your Claude Code plugin marketplace: From 425ccd4982ed3adfcf2975197a74a4ff48fcd4ca Mon Sep 17 00:00:00 2001 From: terra tauri Date: Mon, 2 Feb 2026 22:34:54 -0800 Subject: [PATCH 5/5] feat: add OpenCode release automation Add automated OpenCode-compatible plugin release generation to the agentic marketplace workflow. Changes: - Add opencode-release.js script for release automation - Detect changed plugins from marketplace.json - Generate changelogs from conventional commits - Package plugins as zips with CHANGELOG.md and INSTALL.md - Create GitHub releases with date-based tags (plugin-name-vYYYY.MM.DD) - Extend generate action with create-opencode-release input - Update reusable workflow to pass through release parameters - Bundle opencode-release.js in build process Release workflow triggers on main branch when create-opencode-release is enabled. Skips plugins with no changes since last release. --- .github/workflows/agentic-marketplace.yml | 10 + agentic-marketplace/generate/action.yml | 34 ++ scripts/build.js | 26 +- scripts/dist/opencode-release.cjs | 233 ++++++++++++++ scripts/src/opencode-release.js | 361 ++++++++++++++++++++++ 5 files changed, 661 insertions(+), 3 deletions(-) create mode 100755 scripts/dist/opencode-release.cjs create mode 100644 scripts/src/opencode-release.js diff --git a/.github/workflows/agentic-marketplace.yml b/.github/workflows/agentic-marketplace.yml index a112e95..a54b2ca 100644 --- a/.github/workflows/agentic-marketplace.yml +++ b/.github/workflows/agentic-marketplace.yml @@ -14,6 +14,14 @@ on: description: 'Preview changes without committing' default: false type: boolean + create-opencode-release: + description: 'Create OpenCode-compatible plugin releases' + default: false + type: boolean + release-version: + description: 'Version for releases (default: YYYY.MM.DD)' + default: '' + type: string secrets: token: description: 'GitHub token for PR creation' @@ -68,3 +76,5 @@ jobs: config-path: ${{ inputs.config-path }} github-token: ${{ secrets.token }} auto-merge: ${{ inputs.auto-merge }} + create-opencode-release: ${{ inputs.create-opencode-release }} + release-version: ${{ inputs.release-version }} diff --git a/agentic-marketplace/generate/action.yml b/agentic-marketplace/generate/action.yml index 7681534..a571216 100644 --- a/agentic-marketplace/generate/action.yml +++ b/agentic-marketplace/generate/action.yml @@ -18,6 +18,14 @@ inputs: description: 'Preview changes without committing' required: false default: 'false' + create-opencode-release: + description: 'Create OpenCode-compatible plugin releases' + required: false + default: 'false' + release-version: + description: 'Version for releases (default: YYYY.MM.DD)' + required: false + default: '' outputs: pr-number: @@ -26,6 +34,9 @@ outputs: pr-url: description: 'Pull request URL if created' value: ${{ steps.create-pr.outputs.pull-request-url }} + releases-created: + description: 'Number of OpenCode releases created' + value: ${{ steps.opencode-release.outputs.count }} runs: using: 'composite' @@ -96,6 +107,29 @@ runs: echo "No PR created (no changes detected)" fi + - name: Create OpenCode Releases + id: opencode-release + if: ${{ inputs.create-opencode-release == 'true' && inputs.dry-run != 'true' }} + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + RELEASE_VERSION: ${{ inputs.release-version }} + run: | + set -e + + # Use bundled script from this action + SCRIPT_PATH="${GITHUB_ACTION_PATH}/../../scripts/dist/opencode-release.cjs" + + if [ ! -f "$SCRIPT_PATH" ]; then + echo "ERROR: Bundled script not found at $SCRIPT_PATH" >&2 + exit 1 + fi + + echo "Creating OpenCode releases..." + node "$SCRIPT_PATH" opencode-release + + echo "✓ Release creation complete" + branding: icon: 'file-text' color: 'purple' diff --git a/scripts/build.js b/scripts/build.js index 0d25fad..62cf3a5 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -13,6 +13,7 @@ if (!fs.existsSync(distDir)) { fs.mkdirSync(distDir, { recursive: true }); } +// Bundle discover-components.js esbuild.buildSync({ entryPoints: [path.join(__dirname, 'src/discover-components.js')], bundle: true, @@ -28,7 +29,26 @@ esbuild.buildSync({ }); // Make the output executable -const outputPath = path.join(__dirname, 'dist/discover-components.cjs'); -fs.chmodSync(outputPath, 0o755); - +const discoverPath = path.join(__dirname, 'dist/discover-components.cjs'); +fs.chmodSync(discoverPath, 0o755); console.log('✓ Bundled discover-components.cjs'); + +// Bundle opencode-release.js +esbuild.buildSync({ + entryPoints: [path.join(__dirname, 'src/opencode-release.js')], + bundle: true, + platform: 'node', + target: 'node20', + outfile: path.join(__dirname, 'dist/opencode-release.cjs'), + banner: { + js: '#!/usr/bin/env node\n' + }, + external: [], // Bundle all dependencies + minify: false, // Keep readable for debugging + sourcemap: false +}); + +// Make the output executable +const releasePath = path.join(__dirname, 'dist/opencode-release.cjs'); +fs.chmodSync(releasePath, 0o755); +console.log('✓ Bundled opencode-release.cjs'); diff --git a/scripts/dist/opencode-release.cjs b/scripts/dist/opencode-release.cjs new file mode 100755 index 0000000..8833496 --- /dev/null +++ b/scripts/dist/opencode-release.cjs @@ -0,0 +1,233 @@ +#!/usr/bin/env node + + +// scripts/src/opencode-release.js +var fs = require("fs"); +var path = require("path"); +var { execSync } = require("child_process"); +function getLatestTag(pluginName = null) { + try { + const pattern = pluginName ? `${pluginName}-v*` : "*"; + const tags = execSync(`git tag -l '${pattern}' --sort=-version:refname`, { encoding: "utf8" }).trim().split("\n").filter(Boolean); + return tags[0] || null; + } catch (err) { + return null; + } +} +function getChangedFiles(since, pathFilter = "") { + try { + const cmd = pathFilter ? `git diff --name-only ${since}...HEAD -- ${pathFilter}` : `git diff --name-only ${since}...HEAD`; + const output = execSync(cmd, { encoding: "utf8" }).trim(); + return output ? output.split("\n") : []; + } catch (err) { + console.error(`Error getting changed files: ${err.message}`); + return []; + } +} +function parseConventionalCommits(since, pathFilter = "") { + try { + const cmd = pathFilter ? `git log ${since}...HEAD --pretty=format:"%s" -- ${pathFilter}` : `git log ${since}...HEAD --pretty=format:"%s"`; + const output = execSync(cmd, { encoding: "utf8" }).trim(); + const commits = output ? output.split("\n") : []; + const categories = { + features: [], + fixes: [], + other: [] + }; + commits.forEach((commit) => { + if (commit.startsWith("feat:") || commit.startsWith("feat(")) { + categories.features.push(commit.replace(/^feat(\([^)]+\))?:\s*/, "")); + } else if (commit.startsWith("fix:") || commit.startsWith("fix(")) { + categories.fixes.push(commit.replace(/^fix(\([^)]+\))?:\s*/, "")); + } else if (!commit.startsWith("chore:") && !commit.startsWith("chore(")) { + categories.other.push(commit.replace(/^[a-z]+(\([^)]+\))?:\s*/, "")); + } + }); + return categories; + } catch (err) { + console.error(`Error parsing commits: ${err.message}`); + return { features: [], fixes: [], other: [] }; + } +} +function generateChangelog(pluginName, version, commits) { + const lines = [ + `# Changelog - ${pluginName} v${version}`, + "" + ]; + if (commits.features.length > 0) { + lines.push("## Features"); + commits.features.forEach((feat) => lines.push(`- ${feat}`)); + lines.push(""); + } + if (commits.fixes.length > 0) { + lines.push("## Bug Fixes"); + commits.fixes.forEach((fix) => lines.push(`- ${fix}`)); + lines.push(""); + } + if (commits.other.length > 0) { + lines.push("## Other Changes"); + commits.other.forEach((change) => lines.push(`- ${change}`)); + lines.push(""); + } + return lines.join("\n"); +} +function generateInstallGuide(pluginName, version) { + return `# Installation - ${pluginName} v${version} + +## Quick Install + +\`\`\`bash +# Download and extract to OpenCode plugins directory +curl -L https://github.com/bitcomplete/bc-llm-skills/releases/download/${pluginName}-v${version}/${pluginName}.zip -o ${pluginName}.zip +unzip ${pluginName}.zip -d ~/.config/opencode/plugins/ +rm ${pluginName}.zip +\`\`\` + +## Manual Install + +1. Download \`${pluginName}.zip\` from this release +2. Extract to \`~/.config/opencode/plugins/${pluginName}/\` +3. Restart OpenCode + +## Verify Installation + +After restarting OpenCode, the plugin commands should appear in autocomplete. + +## Platform Notes + +**Linux/macOS**: Default location is \`~/.config/opencode/plugins/\` + +**Windows**: Use \`%USERPROFILE%\\.config\\opencode\\plugins\\\` + +## Compatibility + +This plugin works with both OpenCode and Claude Code. Paths are auto-detected at runtime. +`; +} +function createPluginZip(pluginPath, outputPath, changelog, installGuide) { + try { + const tempDir = path.join(process.cwd(), ".tmp-release"); + const pluginName = path.basename(pluginPath); + const stagingDir = path.join(tempDir, pluginName); + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true }); + } + fs.mkdirSync(stagingDir, { recursive: true }); + execSync(`cp -R "${pluginPath}"/* "${stagingDir}/"`, { stdio: "inherit" }); + fs.writeFileSync(path.join(stagingDir, "CHANGELOG.md"), changelog); + fs.writeFileSync(path.join(stagingDir, "INSTALL.md"), installGuide); + const zipFile = path.basename(outputPath); + execSync(`cd "${tempDir}" && zip -r "${zipFile}" "${pluginName}"`, { stdio: "inherit" }); + execSync(`mv "${tempDir}/${zipFile}" "${outputPath}"`, { stdio: "inherit" }); + fs.rmSync(tempDir, { recursive: true }); + console.log(`\u2713 Created ${outputPath}`); + } catch (err) { + console.error(`Error creating zip: ${err.message}`); + throw err; + } +} +function createGitHubRelease(pluginName, version, zipPath, changelog) { + try { + const tag = `${pluginName}-v${version}`; + const title = `${pluginName} v${version}`; + const notes = `OpenCode-compatible release of ${pluginName}. + +${changelog} + +## Installation + +Download \`${pluginName}.zip\` and extract to \`~/.config/opencode/plugins/\` + +See INSTALL.md in the zip for detailed instructions. +`; + const notesFile = path.join(process.cwd(), `.tmp-notes-${pluginName}.md`); + fs.writeFileSync(notesFile, notes); + execSync( + `gh release create "${tag}" "${zipPath}" --title "${title}" --notes-file "${notesFile}"`, + { stdio: "inherit" } + ); + fs.unlinkSync(notesFile); + console.log(`\u2713 Created release: ${tag}`); + } catch (err) { + console.error(`Error creating GitHub release: ${err.message}`); + throw err; + } +} +function detectChangedPlugins(marketplacePath) { + if (!fs.existsSync(marketplacePath)) { + console.error(`Marketplace file not found: ${marketplacePath}`); + return []; + } + const marketplace = JSON.parse(fs.readFileSync(marketplacePath, "utf8")); + const changedPlugins = []; + for (const plugin of marketplace.plugins) { + const pluginName = plugin.name; + const pluginPath = path.dirname(plugin.source); + const lastTag = getLatestTag(pluginName) || getLatestTag(); + if (!lastTag) { + console.log(`No previous releases found for ${pluginName}, skipping`); + continue; + } + const changedFiles = getChangedFiles(lastTag, pluginPath); + if (changedFiles.length > 0) { + console.log(`\u2713 Changes detected in ${pluginName} (${changedFiles.length} files)`); + changedPlugins.push({ + name: pluginName, + path: pluginPath, + lastTag, + changedFiles + }); + } + } + return changedPlugins; +} +function main() { + const command = process.argv[2]; + if (command !== "opencode-release") { + console.log("Usage: node opencode-release.js opencode-release"); + process.exit(1); + } + const marketplacePath = path.join(".claude-plugin", "marketplace.json"); + const releasesDir = path.join(process.cwd(), ".releases"); + const version = process.env.RELEASE_VERSION || (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, "."); + console.log("Detecting changed plugins..."); + const changedPlugins = detectChangedPlugins(marketplacePath); + if (changedPlugins.length === 0) { + console.log("No plugins with changes detected"); + process.exit(0); + } + if (!fs.existsSync(releasesDir)) { + fs.mkdirSync(releasesDir, { recursive: true }); + } + let releasesCreated = 0; + for (const plugin of changedPlugins) { + console.log(` +Creating release for ${plugin.name}...`); + const commits = parseConventionalCommits(plugin.lastTag, plugin.path); + const changelog = generateChangelog(plugin.name, version, commits); + const installGuide = generateInstallGuide(plugin.name, version); + const zipPath = path.join(releasesDir, `${plugin.name}.zip`); + createPluginZip(plugin.path, zipPath, changelog, installGuide); + createGitHubRelease(plugin.name, version, zipPath, changelog); + releasesCreated++; + } + console.log(` +\u2713 Created ${releasesCreated} OpenCode release(s)`); + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `count=${releasesCreated} +`); + } +} +if (require.main === module) { + main(); +} +module.exports = { + getLatestTag, + getChangedFiles, + parseConventionalCommits, + generateChangelog, + generateInstallGuide, + createPluginZip, + createGitHubRelease, + detectChangedPlugins +}; diff --git a/scripts/src/opencode-release.js b/scripts/src/opencode-release.js new file mode 100644 index 0000000..80e4302 --- /dev/null +++ b/scripts/src/opencode-release.js @@ -0,0 +1,361 @@ +/** + * @file opencode-release.js + * @description OpenCode plugin release automation + * + * Detects changed plugins, generates changelogs from conventional commits, + * packages plugins as zips, and creates GitHub releases. + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +/** + * Get the latest tag for a specific plugin or overall repository + * @param {string} pluginName - Plugin name or null for latest repo tag + * @returns {string|null} Tag name or null if no tags exist + */ +function getLatestTag(pluginName = null) { + try { + const pattern = pluginName ? `${pluginName}-v*` : '*'; + const tags = execSync(`git tag -l '${pattern}' --sort=-version:refname`, { encoding: 'utf8' }) + .trim() + .split('\n') + .filter(Boolean); + return tags[0] || null; + } catch (err) { + return null; + } +} + +/** + * Get files changed since a specific tag or commit + * @param {string} since - Tag or commit to compare from + * @param {string} pathFilter - Optional path filter (e.g., "analysis/bc-generate-sitrep/") + * @returns {string[]} Array of changed file paths + */ +function getChangedFiles(since, pathFilter = '') { + try { + const cmd = pathFilter + ? `git diff --name-only ${since}...HEAD -- ${pathFilter}` + : `git diff --name-only ${since}...HEAD`; + const output = execSync(cmd, { encoding: 'utf8' }).trim(); + return output ? output.split('\n') : []; + } catch (err) { + console.error(`Error getting changed files: ${err.message}`); + return []; + } +} + +/** + * Parse conventional commit messages since a tag + * @param {string} since - Tag or commit to compare from + * @param {string} pathFilter - Optional path filter + * @returns {Object} Categorized commits + */ +function parseConventionalCommits(since, pathFilter = '') { + try { + const cmd = pathFilter + ? `git log ${since}...HEAD --pretty=format:"%s" -- ${pathFilter}` + : `git log ${since}...HEAD --pretty=format:"%s"`; + const output = execSync(cmd, { encoding: 'utf8' }).trim(); + const commits = output ? output.split('\n') : []; + + const categories = { + features: [], + fixes: [], + other: [] + }; + + commits.forEach(commit => { + if (commit.startsWith('feat:') || commit.startsWith('feat(')) { + categories.features.push(commit.replace(/^feat(\([^)]+\))?:\s*/, '')); + } else if (commit.startsWith('fix:') || commit.startsWith('fix(')) { + categories.fixes.push(commit.replace(/^fix(\([^)]+\))?:\s*/, '')); + } else if (!commit.startsWith('chore:') && !commit.startsWith('chore(')) { + // Skip chore commits, include everything else + categories.other.push(commit.replace(/^[a-z]+(\([^)]+\))?:\s*/, '')); + } + }); + + return categories; + } catch (err) { + console.error(`Error parsing commits: ${err.message}`); + return { features: [], fixes: [], other: [] }; + } +} + +/** + * Generate changelog content from categorized commits + * @param {string} pluginName - Plugin name + * @param {string} version - Version string + * @param {Object} commits - Categorized commits + * @returns {string} Markdown changelog + */ +function generateChangelog(pluginName, version, commits) { + const lines = [ + `# Changelog - ${pluginName} v${version}`, + '' + ]; + + if (commits.features.length > 0) { + lines.push('## Features'); + commits.features.forEach(feat => lines.push(`- ${feat}`)); + lines.push(''); + } + + if (commits.fixes.length > 0) { + lines.push('## Bug Fixes'); + commits.fixes.forEach(fix => lines.push(`- ${fix}`)); + lines.push(''); + } + + if (commits.other.length > 0) { + lines.push('## Other Changes'); + commits.other.forEach(change => lines.push(`- ${change}`)); + lines.push(''); + } + + return lines.join('\n'); +} + +/** + * Generate installation instructions for OpenCode + * @param {string} pluginName - Plugin name + * @param {string} version - Version string + * @returns {string} Markdown installation guide + */ +function generateInstallGuide(pluginName, version) { + return `# Installation - ${pluginName} v${version} + +## Quick Install + +\`\`\`bash +# Download and extract to OpenCode plugins directory +curl -L https://github.com/bitcomplete/bc-llm-skills/releases/download/${pluginName}-v${version}/${pluginName}.zip -o ${pluginName}.zip +unzip ${pluginName}.zip -d ~/.config/opencode/plugins/ +rm ${pluginName}.zip +\`\`\` + +## Manual Install + +1. Download \`${pluginName}.zip\` from this release +2. Extract to \`~/.config/opencode/plugins/${pluginName}/\` +3. Restart OpenCode + +## Verify Installation + +After restarting OpenCode, the plugin commands should appear in autocomplete. + +## Platform Notes + +**Linux/macOS**: Default location is \`~/.config/opencode/plugins/\` + +**Windows**: Use \`%USERPROFILE%\\.config\\opencode\\plugins\\\` + +## Compatibility + +This plugin works with both OpenCode and Claude Code. Paths are auto-detected at runtime. +`; +} + +/** + * Create a zip archive of plugin directory + * @param {string} pluginPath - Path to plugin directory + * @param {string} outputPath - Output zip file path + * @param {string} changelog - Changelog content to include + * @param {string} installGuide - Install guide content to include + */ +function createPluginZip(pluginPath, outputPath, changelog, installGuide) { + try { + const tempDir = path.join(process.cwd(), '.tmp-release'); + const pluginName = path.basename(pluginPath); + const stagingDir = path.join(tempDir, pluginName); + + // Clean and create staging directory + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true }); + } + fs.mkdirSync(stagingDir, { recursive: true }); + + // Copy plugin files + execSync(`cp -R "${pluginPath}"/* "${stagingDir}/"`, { stdio: 'inherit' }); + + // Add changelog and install guide + fs.writeFileSync(path.join(stagingDir, 'CHANGELOG.md'), changelog); + fs.writeFileSync(path.join(stagingDir, 'INSTALL.md'), installGuide); + + // Create zip from staging directory + const zipFile = path.basename(outputPath); + execSync(`cd "${tempDir}" && zip -r "${zipFile}" "${pluginName}"`, { stdio: 'inherit' }); + execSync(`mv "${tempDir}/${zipFile}" "${outputPath}"`, { stdio: 'inherit' }); + + // Cleanup + fs.rmSync(tempDir, { recursive: true }); + + console.log(`✓ Created ${outputPath}`); + } catch (err) { + console.error(`Error creating zip: ${err.message}`); + throw err; + } +} + +/** + * Create GitHub release with artifact + * @param {string} pluginName - Plugin name + * @param {string} version - Version string + * @param {string} zipPath - Path to zip artifact + * @param {string} changelog - Changelog content for release notes + */ +function createGitHubRelease(pluginName, version, zipPath, changelog) { + try { + const tag = `${pluginName}-v${version}`; + const title = `${pluginName} v${version}`; + + // Create release notes with changelog + const notes = `OpenCode-compatible release of ${pluginName}. + +${changelog} + +## Installation + +Download \`${pluginName}.zip\` and extract to \`~/.config/opencode/plugins/\` + +See INSTALL.md in the zip for detailed instructions. +`; + + const notesFile = path.join(process.cwd(), `.tmp-notes-${pluginName}.md`); + fs.writeFileSync(notesFile, notes); + + // Create release using gh CLI + execSync( + `gh release create "${tag}" "${zipPath}" --title "${title}" --notes-file "${notesFile}"`, + { stdio: 'inherit' } + ); + + // Cleanup + fs.unlinkSync(notesFile); + + console.log(`✓ Created release: ${tag}`); + } catch (err) { + console.error(`Error creating GitHub release: ${err.message}`); + throw err; + } +} + +/** + * Detect plugins with changes since last release + * @param {string} marketplacePath - Path to marketplace.json + * @returns {Array} Array of changed plugin objects + */ +function detectChangedPlugins(marketplacePath) { + if (!fs.existsSync(marketplacePath)) { + console.error(`Marketplace file not found: ${marketplacePath}`); + return []; + } + + const marketplace = JSON.parse(fs.readFileSync(marketplacePath, 'utf8')); + const changedPlugins = []; + + for (const plugin of marketplace.plugins) { + const pluginName = plugin.name; + const pluginPath = path.dirname(plugin.source); + + // Get latest tag for this plugin + const lastTag = getLatestTag(pluginName) || getLatestTag(); // Fallback to any tag + + if (!lastTag) { + console.log(`No previous releases found for ${pluginName}, skipping`); + continue; + } + + // Check for changes in plugin directory + const changedFiles = getChangedFiles(lastTag, pluginPath); + + if (changedFiles.length > 0) { + console.log(`✓ Changes detected in ${pluginName} (${changedFiles.length} files)`); + changedPlugins.push({ + name: pluginName, + path: pluginPath, + lastTag, + changedFiles + }); + } + } + + return changedPlugins; +} + +/** + * Main function to create OpenCode releases + */ +function main() { + const command = process.argv[2]; + + if (command !== 'opencode-release') { + console.log('Usage: node opencode-release.js opencode-release'); + process.exit(1); + } + + const marketplacePath = path.join('.claude-plugin', 'marketplace.json'); + const releasesDir = path.join(process.cwd(), '.releases'); + + // Get version from env or use date-based + const version = process.env.RELEASE_VERSION || new Date().toISOString().split('T')[0].replace(/-/g, '.'); + + console.log('Detecting changed plugins...'); + const changedPlugins = detectChangedPlugins(marketplacePath); + + if (changedPlugins.length === 0) { + console.log('No plugins with changes detected'); + process.exit(0); + } + + // Create releases directory + if (!fs.existsSync(releasesDir)) { + fs.mkdirSync(releasesDir, { recursive: true }); + } + + let releasesCreated = 0; + + for (const plugin of changedPlugins) { + console.log(`\nCreating release for ${plugin.name}...`); + + // Parse commits for changelog + const commits = parseConventionalCommits(plugin.lastTag, plugin.path); + const changelog = generateChangelog(plugin.name, version, commits); + const installGuide = generateInstallGuide(plugin.name, version); + + // Create zip + const zipPath = path.join(releasesDir, `${plugin.name}.zip`); + createPluginZip(plugin.path, zipPath, changelog, installGuide); + + // Create GitHub release + createGitHubRelease(plugin.name, version, zipPath, changelog); + + releasesCreated++; + } + + console.log(`\n✓ Created ${releasesCreated} OpenCode release(s)`); + + // Output for GitHub Actions + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `count=${releasesCreated}\n`); + } +} + +// Run if called directly +if (require.main === module) { + main(); +} + +module.exports = { + getLatestTag, + getChangedFiles, + parseConventionalCommits, + generateChangelog, + generateInstallGuide, + createPluginZip, + createGitHubRelease, + detectChangedPlugins +};