Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.3] - 2025-12-31

### Fixed
- **Git URL Source Parsing** - Critical fix for claude-plugins-official compatibility
- Added custom JSON unmarshaling to handle source field as string OR object
- Fixes parsing for Git-hosted plugins (Atlassian, Figma, Vercel, Notion, Sentry, etc.)
- MarketplacePlugin now handles `{"source": "url", "url": "https://..."}` format
- Plugin.Source now handles both `"./plugins/name"` and Git URL objects
- **Local Marketplace Loading** - Plugin counts now load from local installations when cache empty
- Fixes "(? plugins)" display for installed marketplaces
- claude-plugins-official now shows "(40 plugins)" correctly
- Works for any locally installed marketplace

### Changed
- **Updated Moved Repo URLs** - Fixed broken marketplace links
- claude-code-plugins-plus: Updated to new repo location (845 ⭐)
- claude-code-marketplace: Updated to new repo location (577 ⭐)
- **Fresh Static Stats** - Updated all marketplace stats (snapshot: 2025-12-31)
- claude-code: 50,055 stars (+245)
- anthropic-agent-skills: 30,756 stars (+819)
- wshobson-agents: 23,995 stars (+100)
- claude-mem: 9,729 stars (+144)
- claude-plugins-official: 1,158 stars (NEW!)
- All 12 marketplaces now have timestamps (fixes "🕒 unknown" display)

### Added
- **Developer Script** - `scripts/update-marketplace-stats.sh`
- Fetches fresh GitHub stats for all marketplaces
- Outputs in Go code format for easy updates
- Rate-limited, error handling for moved repos
- Usage: `./scripts/update-marketplace-stats.sh`

## [0.3.2] - 2025-12-30

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ Plum works with any Claude Code marketplace. Here are some popular ones to get s
<th>Description</th>
</tr>
<tr>
<td><a href="https://github.com/jeremylongshore/claude-code-plugins">claude-code-plugins-plus</a></td>
<td><a href="https://github.com/jeremylongshore/claude-code-plugins-plus-skills">claude-code-plugins-plus</a></td>
<td>The largest collection with <strong>254 plugins</strong> and 185 Agent Skills, focusing on production-ready automation tools across DevOps, security, testing, and AI/ML workflows.</td>
</tr>
<tr>
<td><a href="https://github.com/ananddtyagi/claude-code-marketplace">claude-code-marketplace</a></td>
<td><a href="https://github.com/ananddtyagi/cc-marketplace">claude-code-marketplace</a></td>
<td>Community-driven marketplace featuring curated commands and agents with granular installation and auto-sync from a live database. Browse by category and install only what you need.</td>
</tr>
<tr>
Expand Down
110 changes: 90 additions & 20 deletions internal/marketplace/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"sync"
"time"
)

const (
Expand All @@ -13,11 +14,11 @@ const (

// PopularMarketplace represents a hardcoded popular marketplace
type PopularMarketplace struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
Repo string `json:"repo"` // Full repo URL (e.g., https://github.com/owner/repo)
Description string `json:"description"`
StaticStats *GitHubStats `json:"staticStats,omitempty"` // Static GitHub stats snapshot (fallback if cache empty)
Name string `json:"name"`
DisplayName string `json:"displayName"`
Repo string `json:"repo"` // Full repo URL (e.g., https://github.com/owner/repo)
Description string `json:"description"`
StaticStats *GitHubStats `json:"staticStats,omitempty"` // Static GitHub stats snapshot (fallback if cache empty)
}

// DiscoveredMarketplace contains a marketplace manifest with source information
Expand All @@ -28,94 +29,163 @@ type DiscoveredMarketplace struct {
}

// PopularMarketplaces is the hardcoded list from README.md with static GitHub stats
// Stats snapshot: 2025-12-30 (automatically updated periodically)
// Stats snapshot: 2025-12-31 (updated with fresh data including timestamps)
var PopularMarketplaces = []PopularMarketplace{
{
Name: "claude-code-plugins-plus",
DisplayName: "Claude Code Plugins Plus",
Repo: "https://github.com/jeremylongshore/claude-code-plugins",
Repo: "https://github.com/jeremylongshore/claude-code-plugins-plus-skills",
Description: "The largest collection with 254 plugins and 185 Agent Skills",
// Note: Stats unavailable for this repo (private or rate limited)
StaticStats: &GitHubStats{
Stars: 845,
Forks: 96,
LastPushedAt: mustParseTime("2025-12-31T06:30:55Z"),
OpenIssues: 3,
},
},
{
Name: "claude-code-marketplace",
DisplayName: "Claude Code Marketplace",
Repo: "https://github.com/ananddtyagi/claude-code-marketplace",
Repo: "https://github.com/ananddtyagi/cc-marketplace",
Description: "Community-driven marketplace with granular installation",
// Note: Stats unavailable for this repo (private or rate limited)
StaticStats: &GitHubStats{
Stars: 577,
Forks: 49,
LastPushedAt: mustParseTime("2025-12-14T22:31:07Z"),
OpenIssues: 5,
},
},
{
Name: "claude-code-plugins",
DisplayName: "Claude Code Plugins",
Repo: "https://github.com/anthropics/claude-code",
Description: "Official Anthropic plugins maintained by the Claude Code team",
StaticStats: &GitHubStats{Stars: 49810, Forks: 3529, OpenIssues: 6540},
StaticStats: &GitHubStats{
Stars: 50055,
Forks: 3548,
LastPushedAt: mustParseTime("2025-12-20T19:00:03Z"),
OpenIssues: 6573,
},
},
{
Name: "mag-claude-plugins",
DisplayName: "MAG Claude Plugins",
Repo: "https://github.com/MadAppGang/claude-code",
Description: "Battle-tested workflows with 4 specialized plugins",
StaticStats: &GitHubStats{Stars: 190, Forks: 17, OpenIssues: 1},
StaticStats: &GitHubStats{
Stars: 192,
Forks: 17,
LastPushedAt: mustParseTime("2025-12-30T12:23:11Z"),
OpenIssues: 1,
},
},
{
Name: "dev-gom-plugins",
DisplayName: "Dev-GOM Plugins",
Repo: "https://github.com/Dev-GOM/claude-code-marketplace",
Description: "Automation-focused collection with 15 plugins",
StaticStats: &GitHubStats{Stars: 41, Forks: 5, OpenIssues: 0},
StaticStats: &GitHubStats{
Stars: 41,
Forks: 5,
LastPushedAt: mustParseTime("2025-12-02T03:56:32Z"),
OpenIssues: 0,
},
},
{
Name: "feedmob-claude-plugins",
DisplayName: "FeedMob Plugins",
Repo: "https://github.com/feed-mob/claude-code-marketplace",
Description: "Productivity and workflow tools with 6 specialized plugins",
StaticStats: &GitHubStats{Stars: 2, Forks: 1, OpenIssues: 1},
StaticStats: &GitHubStats{
Stars: 2,
Forks: 1,
LastPushedAt: mustParseTime("2025-12-22T09:15:58Z"),
OpenIssues: 1,
},
},
{
Name: "claude-plugins-official",
DisplayName: "Claude Plugins Official",
Repo: "https://github.com/anthropics/claude-plugins-official",
Description: "Official Anthropic plugins for Claude Code",
// Note: Repo may not exist or is private
StaticStats: &GitHubStats{
Stars: 1158,
Forks: 127,
LastPushedAt: mustParseTime("2025-12-26T06:00:12Z"),
OpenIssues: 69,
},
},
{
Name: "anthropic-agent-skills",
DisplayName: "Anthropic Agent Skills",
Repo: "https://github.com/anthropics/skills",
Description: "Official Anthropic Agent Skills reference repository",
StaticStats: &GitHubStats{Stars: 29937, Forks: 2738, OpenIssues: 117},
StaticStats: &GitHubStats{
Stars: 30756,
Forks: 2802,
LastPushedAt: mustParseTime("2025-12-20T18:09:45Z"),
OpenIssues: 118,
},
},
{
Name: "wshobson-agents",
DisplayName: "Hobson's Agent Collection",
Repo: "https://github.com/wshobson/agents",
Description: "Comprehensive production system with 65 plugins and multi-agent orchestration",
StaticStats: &GitHubStats{Stars: 23895, Forks: 2654, OpenIssues: 257},
StaticStats: &GitHubStats{
Stars: 23995,
Forks: 2669,
LastPushedAt: mustParseTime("2025-12-30T21:40:12Z"),
OpenIssues: 10,
},
},
{
Name: "docker-plugins",
DisplayName: "Docker Official Plugins",
Repo: "https://github.com/docker/claude-plugins",
Description: "Official Docker Inc. marketplace with Docker Desktop MCP Toolkit integration",
StaticStats: &GitHubStats{Stars: 11, Forks: 3, OpenIssues: 0},
StaticStats: &GitHubStats{
Stars: 11,
Forks: 3,
LastPushedAt: mustParseTime("2025-12-19T19:10:46Z"),
OpenIssues: 0,
},
},
{
Name: "ccplugins-marketplace",
DisplayName: "CC Plugins Curated",
Repo: "https://github.com/ccplugins/marketplace",
Description: "Curated collection of 200 plugins across 13 categories",
StaticStats: &GitHubStats{Stars: 10, Forks: 7, OpenIssues: 2},
StaticStats: &GitHubStats{
Stars: 10,
Forks: 7,
LastPushedAt: mustParseTime("2025-10-14T03:38:20Z"),
OpenIssues: 2,
},
},
{
Name: "claude-mem",
DisplayName: "Claude-Mem",
Repo: "https://github.com/thedotmack/claude-mem",
Description: "Persistent memory compression system for Claude Code with context preservation",
StaticStats: &GitHubStats{Stars: 9585, Forks: 578, OpenIssues: 14},
StaticStats: &GitHubStats{
Stars: 9729,
Forks: 587,
LastPushedAt: mustParseTime("2025-12-31T03:01:45Z"),
OpenIssues: 21,
},
},
}

// mustParseTime parses RFC3339 timestamp or panics (for static data only)
func mustParseTime(s string) time.Time {
t, err := time.Parse(time.RFC3339, s)
if err != nil {
panic("invalid timestamp in static data: " + s)
}
return t
}

// DiscoverPopularMarketplaces fetches and returns manifests for popular marketplaces
// Uses cached registry if available (from Shift+U), otherwise hardcoded list
// Uses cache when available, fetches from GitHub otherwise
Expand Down
44 changes: 44 additions & 0 deletions internal/marketplace/types.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package marketplace

import "encoding/json"

// MarketplaceManifest represents the marketplace.json structure
type MarketplaceManifest struct {

Check failure on line 6 in internal/marketplace/types.go

View workflow job for this annotation

GitHub Actions / Lint

exported: type name will be used as marketplace.MarketplaceManifest by other packages, and that stutters; consider calling this Manifest (revive)
Name string `json:"name"`
Owner MarketplaceOwner `json:"owner"`
Metadata MarketplaceMetadata `json:"metadata"`
Expand All @@ -9,21 +11,21 @@
}

// MarketplaceOwner represents the owner of a marketplace
type MarketplaceOwner struct {

Check failure on line 14 in internal/marketplace/types.go

View workflow job for this annotation

GitHub Actions / Lint

exported: type name will be used as marketplace.MarketplaceOwner by other packages, and that stutters; consider calling this Owner (revive)
Name string `json:"name"`
Email string `json:"email"`
Company string `json:"company"`
}

// MarketplaceMetadata represents marketplace metadata
type MarketplaceMetadata struct {

Check failure on line 21 in internal/marketplace/types.go

View workflow job for this annotation

GitHub Actions / Lint

exported: type name will be used as marketplace.MarketplaceMetadata by other packages, and that stutters; consider calling this Metadata (revive)
Description string `json:"description"`
Version string `json:"version"`
PluginRoot string `json:"pluginRoot"`
}

// MarketplacePlugin represents a plugin entry in a marketplace manifest
type MarketplacePlugin struct {

Check failure on line 28 in internal/marketplace/types.go

View workflow job for this annotation

GitHub Actions / Lint

exported: type name will be used as marketplace.MarketplacePlugin by other packages, and that stutters; consider calling this Plugin (revive)
Name string `json:"name"`
Source string `json:"source"`
Description string `json:"description"`
Expand All @@ -38,6 +40,48 @@
Strict bool `json:"strict"`
}

// UnmarshalJSON implements custom JSON unmarshaling for MarketplacePlugin to handle
// the "source" field which can be either a string or an object with Git URL.
// Required for claude-plugins-official compatibility (Atlassian, Figma, Vercel, etc.)
func (mp *MarketplacePlugin) UnmarshalJSON(data []byte) error {
// Create alias type to avoid infinite recursion
type MarketplacePluginAlias MarketplacePlugin

// Use a temporary struct with source as RawMessage
var temp struct {
*MarketplacePluginAlias
SourceRaw json.RawMessage `json:"source"`
}
temp.MarketplacePluginAlias = (*MarketplacePluginAlias)(mp)

if err := json.Unmarshal(data, &temp); err != nil {
return err
}

// Parse source field which can be string or object
if len(temp.SourceRaw) > 0 {
// Try to unmarshal as string first (most common)
var sourceStr string
if err := json.Unmarshal(temp.SourceRaw, &sourceStr); err == nil {
mp.Source = sourceStr
} else {
// Try as object with URL (claude-plugins-official Git repos)
var sourceObj struct {
Source string `json:"source"`
URL string `json:"url"`
}
if err := json.Unmarshal(temp.SourceRaw, &sourceObj); err == nil {
// Use the Git URL as the source
if sourceObj.URL != "" {
mp.Source = sourceObj.URL
}
}
}
}

return nil
}

// Author represents author information
type Author struct {
Name string `json:"name"`
Expand Down
42 changes: 42 additions & 0 deletions internal/plugin/plugin.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package plugin

import (
"encoding/json"
"strings"
)

Expand Down Expand Up @@ -66,6 +67,47 @@ func (p Plugin) AuthorName() string {
return "Unknown"
}

// UnmarshalJSON implements custom JSON unmarshaling for Plugin to handle
// the "source" field which can be either a string or an object with Git URL.
func (p *Plugin) UnmarshalJSON(data []byte) error {
// Create alias type to avoid infinite recursion
type PluginAlias Plugin

// Use a temporary struct with source as RawMessage
var temp struct {
*PluginAlias
SourceRaw json.RawMessage `json:"source"`
}
temp.PluginAlias = (*PluginAlias)(p)

if err := json.Unmarshal(data, &temp); err != nil {
return err
}

// Parse source field which can be string or object
if len(temp.SourceRaw) > 0 {
// Try to unmarshal as string first
var sourceStr string
if err := json.Unmarshal(temp.SourceRaw, &sourceStr); err == nil {
p.Source = sourceStr
} else {
// Try as object with URL
var sourceObj struct {
Source string `json:"source"`
URL string `json:"url"`
}
if err := json.Unmarshal(temp.SourceRaw, &sourceObj); err == nil {
// Use the Git URL as the source
if sourceObj.URL != "" {
p.Source = sourceObj.URL
}
}
}
}

return nil
}

// GitHubURL returns the GitHub URL for this plugin's source code
// Constructs URL from MarketplaceRepo + Source path
// Example: https://github.com/owner/repo/tree/main/plugins/plugin-name
Expand Down
Loading
Loading