From appmate
Identifies top 5-10 rivals outranking an app on its core keywords using iTunes SERP analysis. Invoke when user asks for competitor research or via /competitor-research.
How this skill is triggered — by the user, by Claude, or both
Slash command
/appmate:competitor-researchThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Single authoritative reference. Re-read before every run. Pure-SERP approach: zero RAG, zero AppMate semantic search. The script holds all the data-layer logic; Claude does keyword tokenization and a single batched LLM relevance pass.
Single authoritative reference. Re-read before every run. Pure-SERP approach: zero RAG, zero AppMate semantic search. The script holds all the data-layer logic; Claude does keyword tokenization and a single batched LLM relevance pass.
Run before anything else:
python3 scripts/appmate_config.py check
If exit code ≠ 0, STOP. Tell the user AppMate credentials are not configured, show the precheck output verbatim, and direct them to the appmate-setup skill. Do not invoke any other step of this skill.
Single app → script writes phase_a (raw metadata + primary_genre_id) → LLM tokenizes keywords → script fetches iTunes Search top-200 per token, collects rivals outranking self, aggregates, scores, hard-filters by genre+density, writes phase_b → LLM does batched relevance pass on name + description[:200] → script writes final JSON, Claude renders the markdown in the user's conversation language, pastes back into conversation.
competitor-research (this skill) | feature-ideation / growth-strategy | |
|---|---|---|
| Role | produces the competitive signal | consumes it |
| Signal | iTunes Search SERP overlap, strict outrank | reuses this skill's data/competitors_<slug>.json (auto-chains this skill when the file is missing) |
| Output role | the deliverable itself | transient input evidence in their phase_a JSONs |
| Persistence | data/competitors_<slug>.json (cached, reused) | not cached |
| Item | Content |
|---|---|
| Trigger | user says "find competitors for <app>" / "跑 <app> 的竞品" / invokes this skill for <app> |
| Input | data/apps_full.json + data/sales_cache.json |
| Output | data/phase_a_competitors_<slug>.json, data/phase_b_competitors_<slug>.json, data/competitors_<slug>.json, data/competitors_<slug>.md + Claude pastes the full markdown back into the conversation |
| User intervention | 2 (trigger + LLM tokenize&filter, both in the same Claude turn) |
python3 scripts/competitor_research.py analyze "<app>"
App argument: App Store ID / bundle ID / SKU / fuzzy name. Resolves via the same find_app used by other skills.
Writes data/phase_a_competitors_<slug>.json. If credentials are missing or app is not found, exits 2 with a clear message — do not proceed.
Read data/phase_a_competitors_<slug>.json. Look at raw.title, raw.subtitle, raw.keywords for the main-market locale.
Tokenization rules (identical to aso-daily-report Step 2):
桌面便签, 云便签).Output a comma-separated token list and pass it to the script:
python3 scripts/competitor_research.py rank "Sticky Note Pro" --tokens "便签,桌面便签,sticky note,memo"
This writes data/phase_b_competitors_<slug>.json with up to 25 candidates that survived the genre + density hard filters.
Read data/phase_b_competitors_<slug>.json. For each candidate, look at name, description_short, and outranked_keywords[:3].
One batched judgement call. For ALL candidates in one pass, decide for each:
keep: true + a one-sentence reason (in the user's conversation language) explaining why the rival's target users overlap with the app'skeep: false + a one-sentence reason (in the user's conversation language) explaining why they do notExample reasons (when the user is writing Chinese):
「XX便签」描述同样主打桌面快速记事,目标用户重叠描述显示是情绪打卡 app,跟便签场景不重叠Compose the final JSON data/competitors_<slug>.json:
{
"app": "...",
"app_id": "...",
"bundle_id": "...",
"market": "CN",
"primary_genre_id": 6007,
"generated_at": "...",
"tokens": ["..."],
"self_ranks": {"...": ...},
"filtered": [
{... full candidate fields from phase_b ...,
"relevance_keep": true,
"relevance_reason": "..."}
],
"dropped_by_relevance": [
{"itunes_id": "...", "name": "...", "threat_score": ...,
"drop_reason": "..."}
]
}
filtered is sorted by threat_score desc, truncated to 10. dropped_by_relevance is diagnostic only — never rendered in markdown.
If fewer than 3 candidates pass relevance, the markdown shows an evidence-thin warning at the top.
Rendered in the same language the user has been using in this conversation. Default to English; if the user has been writing in Chinese / Japanese / Spanish / etc., translate the template headers, labels and prose accordingly. App Store metadata strings (title / subtitle / keywords / competitor app names) must remain in the App Store's source locale (e.g. zh-Hans names stay zh-Hans) — only the surrounding explanation follows the user's conversation language.
The template below is written with English placeholders. Substitute the equivalent words in the user's conversation language when rendering.
# 🎯 <App name> · Top competitors worth studying
> ⚠️ <evidence-thin warning — only when kept < 3>
**Main market**: <flag> <country> · **30-day downloads**: <N> · **Core keywords searched**: <X>
---
## 1. <Rival name> · ★<rating> (<review_count> reviews)
Outranks you on **<outrank_count> keywords**, on average **<round avg_rank_diff> places** higher
| Keyword | You | Them | Lead | Popularity |
|---|:-:|:-:|:-:|:-:|
| `<kw1>` | <#N or unranked> | **#<n>** | <diff> | <pop> <🔥 if ≥50> |
| `<kw2>` | ... | ... | ... | ... |
| `<kw3>` | ... | ... | ... | ... |
> **Why this one**: <relevance_reason — one sentence in the user's conversation language>
---
## 2. <Rival name> · ...
... (5–10 rivals total, top 3 keywords each) ...
---
**Top <N>**: #X / #Y / #Z — <one-sentence summary of each top rival's core threat>
Want a deeper look at one rival's keyword layout? Tell me the number and I can pull their metadata via the `aso-optimize` skill for side-by-side comparison.
len(filtered) == 0)When the LLM relevance pass keeps zero candidates, do NOT render the per-rival ## blocks. Instead, render exactly this (translate headers/labels into the user's conversation language):
# 🎯 <App name> · Top competitors worth studying
> ⚠️ No qualifying rivals · this app has no same-category rivals on its own keyword SERPs that meet the `MIN_OUTRANK_COUNT = 3` threshold.
**Main market**: <flag> <country> · **30-day downloads**: <N> · **Core keywords searched**: <X>
Likely causes (ordered by likelihood):
1. **Too few keywords**: at least 3 valid tokens are required for any rival to pass the density threshold. Check whether `raw.keywords` in `phase_a_competitors_<slug>.json` is empty or has only 1-2 tokens.
2. **The app is the leader of this niche**: no rival outranks you ≥ 3 times across your own SERP.
3. **Category mismatch**: your `primary_genre_id` does not match any candidate rival — common for niche keywords with cross-category mixing.
To dig deeper, run `python3 scripts/competitor_research.py show-b "<app>"` to inspect the phase_b candidate pool (the pre-filter detail).
H2 (##) block — low-density layout.`桌面便签`.T/S/K/X.> blockquote (translate the label to the user's conversation language).dropped_by_relevance never appears in markdown. JSON only.threat_score descending.len(filtered) == 0, render the Empty-state template above (no ## rival blocks); never invent placeholder rivals.| Dimension | Source |
|---|---|
| Pick app | data/apps_full.json via aso_optimize_v2.find_app |
| Main market | the country with the largest 30-day downloads in sales_cache.json |
| primary_genre_id | iTunes Lookup, cached in data/itunes_lookup_cache.json (no TTL) |
| SERP top-200 per token | iTunes Search API (https://itunes.apple.com/search), cached in data/serp_details_cache.json |
| Keyword popularity | keyword_local.lookup_popularity (static keyword_reference_<region>.json) |
| Tokenization | LLM semantic split (not regex / jieba) |
| Relevance filter | LLM batched call over name + description[:200] |
| Parameter | Value | Note |
|---|---|---|
SERP_LIMIT | 200 | top-N per iTunes Search call |
MIN_OUTRANK_COUNT | 3 | candidate must outrank on ≥ this many tokens |
MAX_CANDIDATES_BEFORE_LLM | 25 | phase_b truncates to this |
DESCRIPTION_TRUNCATE | 200 | chars shown to LLM per candidate |
TOP_N_RIVALS | 10 | upper bound on filtered |
MIN_RIVALS_FOR_REPORT | 3 | below this, ⚠️ evidence-thin warning |
TOP_K_KEYWORDS_PER_CARD | 3 | per-card keyword table size in markdown |
python3 scripts/competitor_research.py analyze "Sticky Note Pro"
python3 scripts/competitor_research.py rank "Sticky Note Pro" --tokens "便签,桌面便签,sticky note,memo"
python3 scripts/competitor_research.py show-a "Sticky Note Pro"
python3 scripts/competitor_research.py show-b "Sticky Note Pro"
aso-daily-report run that finds an app dropping out of top 20 on its own keyword → trigger this skill to see who took the slot.aso-optimize skill run for <app> for keyword reshuffling.competitors_<slug>.json yet — wiring feature-ideation / growth-strategy is a separate task (see spec §15).MIN_OUTRANK_COUNT = 3 → empty result.dropped_by_relevance vary across runs on borderline cases (audit via the JSON).threat_score descendingT/S/K/X## N. <name>> blockquotedropped_by_relevance NOT rendereddata/competitors_<slug>.json writtendata/competitors_<slug>.md writtennpx claudepluginhub fengyiqicoder/appmate --plugin appmateResearches keywords, analyzes competitors, optimizes metadata, and tracks performance for Apple App Store and Google Play Store listings.
Generates a stage-diagnosed growth strategy for an app, including phase diagnosis and 3-5 actionable strategies. Auto-chains competitor-research when competitor data is missing.
Provides ASO toolkit for keyword research, competitor analysis, metadata optimization, review sentiment, and performance tracking on Apple App Store and Google Play.