Skip to content
Closed
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
10 changes: 5 additions & 5 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
8 changes: 4 additions & 4 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules/
.env
.env.*
*.log
skills.zip
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"joompulse": {
"type": "http",
"url": "https://joompulse.com/mcp"
}
}
}
24 changes: 20 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:<skill>`.
- 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

Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
96 changes: 96 additions & 0 deletions scripts/check-manifests.sh
Original file line number Diff line number Diff line change
@@ -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})."
Loading