Help us improve
Share bugs, ideas, or general feedback.
From appmate
Optimizes App Store metadata (title, subtitle, keywords) for a single app using keyword ranking and popularity analysis. Automates ASO optimization via App Store Connect APIs.
npx claudepluginhub fengyiqicoder/appmate --plugin appmateHow this skill is triggered — by the user, by Claude, or both
Slash command
/appmate:aso-optimizeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> This skill is the single authoritative reference for the ASO optimization flow. Re-read it before every run.
Conducts keyword research, optimizes metadata, and analyzes competitors for Apple App Store and Google Play Store listings. Helps improve app visibility and conversion rates.
Generates optimized App Store/Google Play metadata—titles, subtitles, keywords, descriptions, promo text, What's New—in multiple languages via 4 agent teams for keyword research, competitor analysis, copywriting, localization. Outputs MD reports.
Researches keywords, analyzes competitors, optimizes metadata, and tracks performance for Apple App Store and Google Play Store listings.
Share bugs, ideas, or general feedback.
This skill is the single authoritative reference for the ASO optimization flow. Re-read it before every run.
Every step in this skill calls App Store Connect APIs. Before any other step, run:
python3 scripts/appmate_config.py check
If exit code ≠ 0, STOP. Do not invoke any other part of this skill, do not run scripts/aso_optimize_v2.py. Tell the user AppMate credentials are not configured, show the precheck output verbatim, and tell them to invoke the appmate-setup skill. The downstream script also enforces this gate (exits 2 with the same message).
| Item | Content |
|---|---|
| User input | An app identifier (bundle id / App Store id / SKU / fuzzy name match — any one) |
| Final output | 3 paste-ready strings for App Store Connect: title, subtitle, keywords |
| Stage artifacts | data/phase_a_<slug>.json · data/phase_b_<slug>.json · data/aso_optimize_<slug>.md |
| Tool | Purpose | Called by |
|---|---|---|
data/apps_full.json | Static metadata | script |
data/sales_cache.json | Sales report (find the main market) | script |
| iTunes Search Top-200 | Keyword ranking (same source as the App Store web page) | script |
| Keyword reference table | popularity (1-99) + difficulty (1-99) | script |
| LLM (Claude) | CJK / Chinese-Japanese-Korean tokenization (when the App Store metadata is in those languages) / candidate generation / comparison / synthesis | conversation layer |
references/aso-methodology.md | Full methodology (§1-§12) | LLM reference |
<app> identifier.Everything in between runs automatically without interruption.
python3 scripts/aso_optimize_v2.py analyze <app>
pick_locales_for_country).title / subtitle / keywords.Artifact: data/phase_a_<slug>.json (metadata + tokens cut by the script's basic tokenizer).
Read the current_metadata field of data/phase_a_<slug>.json and cut real ASO words — apply CJK / Chinese-Japanese-Korean tokenization when the App Store metadata is in those languages (the metadata's own locale, independent of what language you're conversing in with the user):
网络翻译邮箱地图地球).Typical output: 30-40 real tokens covering all the semantics of the existing metadata.
python3 scripts/aso_optimize_v2.py validate <app> --candidates <Step 2 output>
Artifact: data/phase_b_<slug>.json
| Quadrant | Marker | Condition | Handling |
|---|---|---|---|
| Tier 1 — high relevance, high heat | 🟢 Target | rank ≤ 20 AND pop ≥ 40 | Keep / promote to Title or Subtitle |
| Tier 2 — high relevance, low heat | ⚪ Niche | rank ≤ 20 AND pop < 40 | Long-tail placeholder, keep in Keywords |
| Tier 3 — low relevance, low heat | 💀 Junk | pop ≤ 10 OR (rank > 100 AND pop ≤ 20) | Must remove |
| Tier 4 — low relevance, high heat | 🟡 Push | pop ≥ 40 AND rank > 50 | diff ≤ 60 push; > 60 skip |
Selection priority (§10.1): brand words > industry words > competitor words.
§10.1 industry-word tree expansion (core word + business association / generic suffix, 4 angles): common words, media-related, transaction-related, software platform.
The 7 strategies:
| # | Strategy | § basis |
|---|---|---|
| 1 | Synonym expansion | §10.1 |
| 2 | Long-tail combination (core + suffix) | §10.4 |
| 3 | Hypernyms / hyponyms | §10.1 |
| 4 | Spelling variants / plurals | §2.4 (English only) |
| 5 | Brand-ecosystem association | §10.1 |
| 6 | Similar-competitor inspiration | §10.1 |
| 7 | Plural / compound completion | §2.4 |
Before producing candidates, read the indie keyword reference for the target app's primary market:
zh-Hans / zh-Hant / cn → data/keyword_reference_cn.jsonen-US / en-GB / us → data/keyword_reference_us.jsonEach row carries {keyword, popularity, popularity_is_floor, difficulty, apps_count, source_apps_count, source_apps, top_category}. This is a static
table built from the top-100 indie apps in that store — it is internal
context only.
Use it as:
top_category matches the
target app's category (or adjacent). Promote those whose semantics overlap
the app's Step-2 tokens into the candidate pool before running the 7
strategies. Reason: indie-validated words have a higher prior of being
worth real estate.source_apps_count (indie-validated). When
popularity_is_floor: true, treat popularity as "no signal" (don't use
the literal 5) and decide via difficulty + indie evidence alone.source_apps_count ≥ 8 means ≥ 8% of all indie
top-100 apps target the word. Still usable, but reserve for Title /
Subtitle star slot; avoid as Keywords filler (saturated; low marginal CVR).popularity ≥ 40 AND source_apps_count ≤ 1 AND difficulty < 60 is the most valuable bucket. Seed these aggressively.Output discipline (do NOT do):
The user sees the standard pop / diff / rank numbers; the candidate set is
just smarter.
Produce 10-15 candidates per round.
Same as Step 3 — each round automatically runs validate, overwriting data/phase_b_<slug>.json.
Every validated candidate must land in a quadrant and be handled per its rule (same table as Step 4).
| Field | Admission condition |
|---|---|
| Title (≤30 char) | pop ≥ 40 OR rank ≤ 5 |
| Subtitle (≤30 char) | pop ≥ 30 OR rank ≤ 10; no duplicates of Title (§6.2) |
| Keywords (≤100 char) | pop ≥ 20 OR rank ≤ 20; no duplicates of Title / Subtitle tokens (§7.3 #3) |
| Long-tail combo (pop=5 rank≤5) | enters Keywords only when replacing a weaker token |
Candidate X replaces existing Y only when:
pop(X) ≥ pop(Y) × 1.5
OR
rank(X) ≤ 50 AND rank(Y) > 100
OR
pop(X) ≥ 20 AND pop(Y) ≤ 5
| Rule | Application |
|---|---|
| ① Comma-separated | Keywords forced to , |
| ② No duplicate tokens | case-insensitive dedup |
| ③ No duplicates of Title / Subtitle tokens | ✓ |
| ④ Order does not affect weight | (sort by pop descending for maintainability) |
| ⑤ Split phrases into words | Apple does mix-and-match automatically |
| ⑥ Singular form | English only; not applicable to Chinese |
| ⑦ Skip free words | reject: app, the, by, free, best, top, leading, 最, 免费, 工具, 软件 |
| ⑧ Use all 100 chars | utilization ≥ 95% |
| ⑨ Fill by pop descending | ✓ |
best / top / #1 / leading / free / install now / 最 / 推荐有道云笔记 / 印象笔记 / 滴答清单 / Notion / Evernote / Bear / Obsidian / Workflowy / LogseqHealth & Fitness / Utilities / Productivity / 工具 / 软件 / 免费 / 应用 (used alone as placeholders)|rank(singular) - rank(plural)| / rank(singular) > 15% → keep both forms.loop:
candidates = LLM_generate(rejected_pool, 10-15)
validated = script_validate(candidates)
passed = filter(validated, by 7.1-7.5)
if len(passed) >= 3:
proceed to Step 8
rejected_pool ∪= candidates
iteration += 1
if iteration >= 3:
proceed to Step 8 (with the current passed set)
else:
back to loop (avoiding rejected_pool)
| Rule | Application |
|---|---|
| Embedded token count | 3-5 (more hurts CVR §5.5) |
| Must contain a §4.2 star keyword | at least 1 with pop ≥ 40 AND rank ≤ 10 |
| Separator strategy (§5.3) | : separates brand + description; & replaces and; prefer word roots |
| Banned (§5.4) | best / top / #1 / leading / free / install now / 最 |
| Preserve brand recognition (§5.3) | a strong brand word must not be dropped |
Premise: the algorithm weight is insensitive to order, but order indirectly affects ranking strength through CVR.
| Position | What goes here | § basis |
|---|---|---|
| 1st | weak brand → descriptive core word first; strong brand → brand word first | §5.3 STARZ vs Down Dog example |
| 2nd-3rd | high-pop keywords (≥ 40) | §9.5 visual recognition → CVR |
| End | long-tail / modifiers / platform qualifiers | §5.3 space-saving technique |
Judging brand strength: the app's current rank on its own brand keyword. Already #1 → strong brand → brand word first. Not #1, or a category word is more famous → weak brand → descriptive word first.
| Rule | Application |
|---|---|
| No duplicates of Title tokens | ✓ §6.2 rule 1 |
| Avoid vague words | banned: most popular / social networking / 强大的 / 优秀的 / 极致 / 简洁 |
| Token count | 3-5 tokens with pop ≥ 30 across different dimensions |
| Dimension suggestions | function words / internationalized brand / competitor-adjacent / synonyms |
| Position | What goes here |
|---|---|
| 1st | the word that best explains the core function (the user's "one decisive sentence") |
| 2nd-3rd | synonyms / internationalized category words (English category words like Memo / Stickies) |
| End | long-tail placeholders / competitor-adjacent words |
| Rule | Application |
|---|---|
| ①-⑨ all 9 rules apply | see 7.4 |
| §10.4 CJK compression (zh/ja/ko metadata) | high-pop words may form a 3-5 word comma-free run |
| Character utilization | ≥ 95/100 char |
| Order has no effect (§7.3 #4 explicit) | sort by pop descending for readability only |
Must check: does the app have both zh-Hans + en-GB (or a same-region dual locale) enabled? If only one → Step 8 also outputs a second keyword-field suggestion (no duplicate words, expanding to 200 char).
The output footer must include:
pop AND diff columns (diff is needed to flag which words are diff<60 "short-term reachable" vs diff>60 "long-term weight only").analyze every 30 days to watch the rank trend.⚠️ Wording rule (must follow): in the deliverable document, every position / field reference must use the full name —
Title / Subtitle / Keywords(or the user's-language equivalent, e.g.主标题 / 副标题 / 关键词). The single letters T / S / K / X are not allowed (this applies to section rule text and checklists too). Reason: an abbreviation has no context when the user reviews it.
# <App> · ASO Optimization Suggestions
**Generated at** / **Main market** / **App info**
## Three strings (paste-ready)
Title (X/30 char): <NEW>
Subtitle (X/30 char): <NEW>
Keywords (X/100 char): <NEW>
## OLD vs NEW comparison
| Field | OLD | NEW | Δ |
## Deletion list
| Word | Original position | pop | diff | rank | Reason |
("Original position" column: write `Title` / `Subtitle` / `Keywords`, not T/S/K)
**Column rules**:
- `pop` / `diff` / `rank` are mandatory — all three numbers are needed to verify §10.1 Tier / §10.4 / §7.2 decisions.
- `Reason` column must be a **plain-language sentence** explaining why the word is being deleted — written so a non-technical reader understands. Cite specific evidence (pop / diff / rank values, Apple behavior, semantic reasoning) instead of `§` shortcuts. The official `§` rule may be cited inline as supporting context, but the bulk of the cell is a readable sentence, not a code-like reference. Bad: `§10.1 Tier 3`. Good: `Zero search volume (pop=5), and Apple's CJK tokenizer already splits "组件" out of the Title's "桌面小组件" — repeating it wastes 2 chars`.
## Addition list
| Word | New position | pop | diff | rank | Reason |
(same: `Title` / `Subtitle` / `Keywords`)
**Column rules**: same as the Deletion list. The `Reason` column is a plain-language sentence stating (a) why this word is worth a slot, (b) which evidence drives the decision (pop / diff / rank), (c) what the expected outcome is (short-term rank push vs long-term weight accumulation). Bad: `§10.1 Tier 4 push`. Good: `pop 65 high + diff 48 unusually low; OneSearch can also surface system SMS; placing it in the Title should reach top 20 in 30-60 days`.
## §10.2 dual-locale expansion audit (if applicable)
## §10.5 long-term weight tracking
## 📌 Post-delivery suggestions (required — user-facing options)
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. The proposed App Store metadata strings (title / subtitle / keywords) must remain in the target App Store's locale (e.g. zh-Hans for the CN store) regardless — only the surrounding explanation follows the user's conversation language.
After delivering the 3 strings, always append these two suggestions for the user to choose from:
Trigger condition: the main-market locale ∈ {zh-Hans, zh-Hant, ja, ko}.
Output template (translate into the user's conversation language; this English version is the source):
### 📌 Suggestion ① · CJK comma-free compression
If your main market is CN/JP/KR/TW/HK, you can drop the commas in the Keywords field
and let Apple's CJK tokenizer mix-and-match automatically:
- **Gain**: frees N characters (1 per comma), letting you cram N more tokens
- **Risk**: loses the "explicit target word" boundary, relies on Apple's auto-tokenization
- **Recommended strategy**: keep commas around high-pop words (≥ 40); compress the low-pop long-tail at the end
Comparison:
Comma version (X/100, N words): A,B,C,D,E,F,...
Compressed (Y/100, M words): A,B,CDE,FGH,... ← tail compressed
Apply the compressed version? (yes / no / partial)
When not to output: if the main market is an English/Latin-script market, skip this suggestion.
Always output (regardless of market). Translate into the user's conversation language; this English version is the source:
### 📌 Suggestion ② · Whether to fill the character limit
Current utilization:
· Title X/30 char (X%)
· Subtitle X/30 char (X%)
· Keywords X/100 char (X%)
Options:
- **A. Keep as is** (tight, high-confidence word set)
- **B. Fill Title/Subtitle too** → I'll add more §10.1 second-tier long-tail words
- **C. Fill Keywords only to 100 char** → top up to 99-100 char
- **D. Fill everything** (max coverage but mixes in mid-pop words)
Which one? (A / B / C / D)
After the user chooses: re-output the 3 strings per the chosen option; leave the other sections unchanged.
# Step 1: anchor
python3 scripts/aso_optimize_v2.py analyze <app>
# Step 3 / Step 6: validate (each round)
python3 scripts/aso_optimize_v2.py validate <app> --candidates kw1,kw2,kw3,...
# Inspect
python3 scripts/aso_optimize_v2.py show-a <app>
python3 scripts/aso_optimize_v2.py show-b <app>
Steps 2 / 4 / 5 / 7 / 8 are all done by the LLM (Claude in the conversation).
| Parameter | Value | Source |
|---|---|---|
| Title max length | 30 char | §5.1 |
| Subtitle max length | 30 char | §6.1 |
| Keywords max length | 100 char | §7.1 |
| Keywords utilization floor | ≥ 95% | §7.3 #8 |
| Title embedded token count | 3-5 | §5.5 |
| Subtitle embedded token count | 3-5 | §6.2 |
| Single validate cap | 30 candidates | tool limit |
| Auto-iteration cap | 3 rounds | Step 7.7 |
| Passed-candidate threshold | ≥ 3 | Step 7.7 |
| Title pop admission | ≥ 40 OR rank ≤ 5 | Step 7.2 |
| Subtitle pop admission | ≥ 30 OR rank ≤ 10 | Step 7.2 |
| Keywords pop admission | ≥ 20 OR rank ≤ 20 | Step 7.2 |
| Replacement ratio (pop) | new ≥ old × 1.5 | Step 7.3 |
| §10.1 Tier 1 | rank ≤ 20 AND pop ≥ 40 | §10.1 |
| §10.1 Tier 3 | pop ≤ 10 must remove | §10.1 |
This is about tokenizing App Store metadata that is itself in CJK languages — independent of the language you're conversing in with the user.
Do not use jieba or any automatic tokenizer. Reasons: jieba boundaries are often wrong (e.g. 便利贴 cut into 便利 + 贴); long CJK mashed runs produce noise; the LLM has semantic understanding and can recognize complete ASO words. LLM tokenization features: realistic word length (mostly 2-4 chars for Chinese), no long mashed runs, recognizes compound words (桌面便签 is one ASO word, not 桌面 + 便签), brand variants (便笺 ≠ 便签), typos / homophones.
source_apps_count, top_category, or data/keyword_reference_* anywhere in the deliverable (§5.0 consultation must stay silent)aso-daily-report skill finds an app's keyword dropping out of the top 20 → trigger this workflow (aso_optimize_v2.py analyze <app>) for a deep optimization.