From 581fefe8128fca84a4d9a9f3be12ccae00f98b66 Mon Sep 17 00:00:00 2001 From: paltsevpaveljoom Date: Thu, 13 Aug 2026 18:34:35 +0100 Subject: [PATCH] Prepare the plugin for community-marketplace submission Reviewing the repo against the current plugin submission requirements turned up two blockers. The repo was renamed pulse-skills -> joompulse-skills, but the manifests and README were never updated. Both homepage/repository URLs and the install commands still pointed at joomcode/pulse-skills, which only resolved via GitHub's rename redirect. Rename the plugin to joompulse-skills to match the repo and the brand, and fix all six stale references. The plugin name is a permanent identifier once published, so this is the last point at which the rename is free. plugin.json also said 0.1.0 while the changelog carried three skills under "Unreleased" and thirteen more that were never recorded. Manifest, changelog and tag disagreement is the most common cause of submission rejection, so cut 0.2.0 and rebuild the entry from the actual history. Bundle .mcp.json so installing configures the JoomPulse connector and the user only has to sign in. All 17 skills hard-require it, and review weighs how quickly an unfamiliar user can get the plugin working. The README keeps the manual steps for Claude chat and Cowork, where the connector is still added by hand. Add scripts/check-manifests.sh and a CI workflow covering exactly what broke here: name, version and URL agreement across both manifests and the changelog, plus skill frontmatter and the 1024-character description limit. claude plugin validate runs unauthenticated, so it works in CI. Co-Authored-By: Claude Opus 5 --- .claude-plugin/marketplace.json | 10 ++-- .claude-plugin/plugin.json | 8 +-- .github/workflows/checks.yml | 23 ++++++++ .gitignore | 1 + .mcp.json | 8 +++ CHANGELOG.md | 24 +++++++-- README.md | 9 +++- scripts/check-manifests.sh | 96 +++++++++++++++++++++++++++++++++ 8 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/checks.yml create mode 100644 .mcp.json create mode 100755 scripts/check-manifests.sh diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 05416fc..40118e0 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,20 +6,20 @@ }, "metadata": { "description": "Public JoomPulse skills marketplace.", - "version": "0.1.0" + "version": "0.2.0" }, "plugins": [ { - "name": "pulse-skills", + "name": "joompulse-skills", "source": "./", "description": "Public JoomPulse agent skills for product research and marketplace workflows.", - "version": "0.1.0", + "version": "0.2.0", "author": { "name": "Joom", "url": "https://joompulse.com" }, - "homepage": "https://github.com/joomcode/pulse-skills", - "repository": "https://github.com/joomcode/pulse-skills", + "homepage": "https://github.com/joomcode/joompulse-skills", + "repository": "https://github.com/joomcode/joompulse-skills", "license": "MIT", "category": "productivity", "tags": [ diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index fa0a71f..953343f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,13 +1,13 @@ { - "name": "pulse-skills", + "name": "joompulse-skills", "description": "Public JoomPulse agent skills for product research and marketplace workflows.", - "version": "0.1.0", + "version": "0.2.0", "author": { "name": "Joom", "url": "https://joompulse.com" }, - "homepage": "https://github.com/joomcode/pulse-skills", - "repository": "https://github.com/joomcode/pulse-skills", + "homepage": "https://github.com/joomcode/joompulse-skills", + "repository": "https://github.com/joomcode/joompulse-skills", "license": "MIT", "keywords": [ "joompulse", diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..6c26f4e --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,23 @@ +name: checks + +on: + push: + branches: [main] + pull_request: + +jobs: + checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Public-safety grep + run: bash scripts/check-public-safety.sh + + - name: Manifest and skill consistency + run: bash scripts/check-manifests.sh + + - name: Claude plugin validate + run: | + npm install -g @anthropic-ai/claude-code + claude plugin validate . --strict diff --git a/.gitignore b/.gitignore index d410d06..32443f5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ .env .env.* *.log +skills.zip diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..7cb9dbf --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "joompulse": { + "type": "http", + "url": "https://joompulse.com/mcp" + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 05cbc95..9298860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,27 @@ All notable changes to this project will be documented in this file. This project follows semantic versioning. -## Unreleased +## 0.2.0 - 2026-08-13 -- Added `ml-product-analysis`. -- Added `product-change-monitor`. -- Added `my-product-vs-catalog`. +- Renamed the plugin from `pulse-skills` to `joompulse-skills` to match the + repository and the JoomPulse brand. Skills are now invoked as + `/joompulse-skills:`. +- Fixed `homepage` and `repository` URLs, which still pointed at the old + `joomcode/pulse-skills` path. +- Bundled `.mcp.json` so installing the plugin configures the JoomPulse MCP + connector; users only need to sign in. +- Added product skills: `ml-product-analysis`, `product-change-monitor`, + `my-product-vs-catalog`. +- Added category skills: `category-monitor`, `category-opportunity-index`, + `growing-leaf-category-tracker`, `uncontested-niche-finder`, + `high-demand-low-quality-finder`, `unbranded-products-in-category`, + `new-growing-products-in-category`, `top-keywords-in-my-category`. +- Added seller and brand skills: `seller-overview-tracker`, + `top-sellers-in-category`, `top-brand-position-tracker`. +- Added international skills: `fast-growing-international-products`, + `popular-international-products`. +- Removed `international-product-matcher`. +- Shortened all `SKILL.md` descriptions to the 1024-character limit. ## 0.1.0 - 2026-06-16 diff --git a/README.md b/README.md index 432c6cd..0970762 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,21 @@ sellers and teams. If you have the Claude terminal installed, run these commands in it one at a time: ```bash -/plugin marketplace add joomcode/pulse-skills -/plugin install pulse-skills@joomcode +/plugin marketplace add joomcode/joompulse-skills +/plugin install joompulse-skills@joomcode ``` After installation, start a new session so the skill metadata is loaded. +Installing the plugin also configures the JoomPulse MCP connector for you — run +`/mcp`, pick **joompulse**, and sign in. You can skip the manual setup below. + ## Connect the JoomPulse MCP (required) To use these skills you first need to connect the **JoomPulse MCP** — it provides the live market data they run on, and without it the skills won't work. +If you installed via the Claude terminal, the connector is already configured; just sign in with `/mcp`. The steps below are for Claude chat and Claude Cowork, where you add it by hand. + > **Get access and details:** https://joompulse.com/mcp-connector **1. Open Claude settings** diff --git a/scripts/check-manifests.sh b/scripts/check-manifests.sh new file mode 100755 index 0000000..8623f73 --- /dev/null +++ b/scripts/check-manifests.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Checks the plugin manifests stay consistent with each other, the repository, +# and the changelog. Version drift and stale repository URLs are the two things +# that most often break a plugin submission. +set -euo pipefail + +repo="joomcode/joompulse-skills" +repo_url="https://github.com/${repo}" +fail=0 + +note() { + echo "FAIL: $*" >&2 + fail=1 +} + +for f in .claude-plugin/plugin.json .claude-plugin/marketplace.json; do + python3 -c "import json,sys; json.load(open('$f'))" \ + || note "$f is not valid JSON" +done + +read -r plugin_name plugin_version plugin_home plugin_repo <<<"$(python3 -c " +import json +d = json.load(open('.claude-plugin/plugin.json')) +print(d.get('name'), d.get('version'), d.get('homepage'), d.get('repository')) +")" + +read -r entry_name entry_version entry_home entry_repo <<<"$(python3 -c " +import json +d = json.load(open('.claude-plugin/marketplace.json'))['plugins'][0] +print(d.get('name'), d.get('version'), d.get('homepage'), d.get('repository')) +")" + +[ "$plugin_name" = "$entry_name" ] \ + || note "name differs: plugin.json '$plugin_name' vs marketplace entry '$entry_name'" +[ "$plugin_version" = "$entry_version" ] \ + || note "version differs: plugin.json '$plugin_version' vs marketplace entry '$entry_version'" + +for url in "$plugin_home" "$plugin_repo" "$entry_home" "$entry_repo"; do + [ "$url" = "$repo_url" ] || note "URL '$url' does not match $repo_url" +done + +changelog_version=$(grep -m1 -oE '^## [0-9]+\.[0-9]+\.[0-9]+' CHANGELOG.md | awk '{print $2}') +[ "$plugin_version" = "$changelog_version" ] \ + || note "version differs: plugin.json '$plugin_version' vs newest CHANGELOG entry '$changelog_version'" + +# The plugin name is a permanent identifier: renaming it breaks existing installs. +# CHANGELOG.md is exempt because it records the rename itself. +grep -rIn --exclude-dir=.git --exclude="$(basename "$0")" --exclude=CHANGELOG.md \ + 'joomcode/pulse-skills' . \ + && note "found references to the old joomcode/pulse-skills path" + +python3 - "$plugin_name" <<'PY' || fail=1 +import glob, os, re, sys + +plugin_name = sys.argv[1] +ok = True +for path in sorted(glob.glob("skills/*/SKILL.md")): + directory = os.path.basename(os.path.dirname(path)) + text = open(path).read() + match = re.match(r"^---\n(.*?)\n---", text, re.S) + if not match: + print(f"FAIL: {path} has no YAML frontmatter", file=sys.stderr) + ok = False + continue + front = match.group(1) + + name = re.search(r"^name:\s*(.+)$", front, re.M) + if not name: + print(f"FAIL: {path} has no name", file=sys.stderr) + ok = False + elif name.group(1).strip().strip("\"'") != directory: + print(f"FAIL: {path} name does not match its directory", file=sys.stderr) + ok = False + + desc = re.search(r"^description:\s*(.*?)(?=^\w[\w-]*:|\Z)", front, re.S | re.M) + if not desc: + print(f"FAIL: {path} has no description", file=sys.stderr) + ok = False + else: + collapsed = " ".join(desc.group(1).split()).strip("\"'") + if len(collapsed) > 1024: + print( + f"FAIL: {path} description is {len(collapsed)} chars (limit 1024)", + file=sys.stderr, + ) + ok = False + +sys.exit(0 if ok else 1) +PY + +if [ "$fail" -ne 0 ]; then + echo "Manifest check failed." >&2 + exit 1 +fi + +echo "Manifest check passed (plugin ${plugin_name} ${plugin_version})."