pabal-web-mcp 1.3.9 → 1.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/mcp-server.js +316 -53
- package/package.json +1 -1
package/dist/bin/mcp-server.js
CHANGED
|
@@ -1215,13 +1215,30 @@ function generatePrimaryOptimizationPrompt(args) {
|
|
|
1215
1215
|
prompt += `Optimize the PRIMARY locale (${primaryLocale}) using **saved keyword research** + full ASO field optimization.
|
|
1216
1216
|
|
|
1217
1217
|
`;
|
|
1218
|
-
prompt += `## Step 1: Keyword Research (${primaryLocale})
|
|
1218
|
+
prompt += `## Step 1: Use Saved Keyword Research (${primaryLocale})
|
|
1219
1219
|
|
|
1220
1220
|
`;
|
|
1221
1221
|
const researchSections = keywordResearchByLocale[primaryLocale] || [];
|
|
1222
1222
|
const researchDir = keywordResearchDirByLocale[primaryLocale];
|
|
1223
1223
|
if (researchSections.length > 0) {
|
|
1224
|
-
prompt +=
|
|
1224
|
+
prompt += `**CRITICAL: Use ONLY the saved keyword research below. Do NOT invent or research new keywords.**
|
|
1225
|
+
|
|
1226
|
+
`;
|
|
1227
|
+
prompt += `The research data includes:
|
|
1228
|
+
`;
|
|
1229
|
+
prompt += `- **Tier 1 (Core):** Use these in title and subtitle - highest traffic, best opportunity
|
|
1230
|
+
`;
|
|
1231
|
+
prompt += `- **Tier 2 (Feature):** Use these in keywords field and descriptions
|
|
1232
|
+
`;
|
|
1233
|
+
prompt += `- **Tier 3 (Longtail):** Use these in intro, outro, and feature descriptions
|
|
1234
|
+
`;
|
|
1235
|
+
prompt += `- **Keyword Details:** Each keyword has traffic/difficulty scores and rationale - use this to prioritize
|
|
1236
|
+
`;
|
|
1237
|
+
prompt += `- **Strategy:** Overall optimization strategy based on competitor analysis
|
|
1238
|
+
`;
|
|
1239
|
+
prompt += `- **Keyword Gaps:** Opportunities where competitors are weak
|
|
1240
|
+
`;
|
|
1241
|
+
prompt += `- **User Language Patterns:** Phrases real users use in reviews - incorporate naturally
|
|
1225
1242
|
|
|
1226
1243
|
`;
|
|
1227
1244
|
prompt += `Saved research:
|
|
@@ -1238,46 +1255,60 @@ ${researchSections.join("\n")}
|
|
|
1238
1255
|
prompt += `## Step 2: Optimize All Fields (${primaryLocale})
|
|
1239
1256
|
|
|
1240
1257
|
`;
|
|
1241
|
-
prompt +=
|
|
1258
|
+
prompt += `**Apply keywords strategically based on tier priority:**
|
|
1259
|
+
|
|
1260
|
+
`;
|
|
1261
|
+
prompt += `### Tier 1 Keywords (Core) \u2192 Title & Subtitle
|
|
1242
1262
|
`;
|
|
1243
|
-
prompt += `- \`aso.title\` (\u226430): **"App Name:
|
|
1263
|
+
prompt += `- \`aso.title\` (\u226430): **"App Name: [Tier1 Keyword]"** format
|
|
1244
1264
|
`;
|
|
1245
|
-
prompt += ` -
|
|
1265
|
+
prompt += ` - App name in English, keyword in target language, uppercase after colon
|
|
1246
1266
|
`;
|
|
1247
|
-
prompt +=
|
|
1267
|
+
prompt += ` - **Do NOT translate/rename the app name**
|
|
1248
1268
|
`;
|
|
1249
|
-
prompt += `- \`aso.
|
|
1269
|
+
prompt += `- \`aso.subtitle\` (\u226430): Use remaining Tier 1 keywords
|
|
1250
1270
|
`;
|
|
1251
|
-
prompt += `- \`aso.
|
|
1271
|
+
prompt += `- \`aso.shortDescription\` (\u226480): Tier 1 + Tier 2 keywords (no emojis/CAPS)
|
|
1272
|
+
|
|
1273
|
+
`;
|
|
1274
|
+
prompt += `### Tier 2 Keywords (Feature) \u2192 Keywords Field & Descriptions
|
|
1252
1275
|
`;
|
|
1253
|
-
prompt += `- \`aso.
|
|
1276
|
+
prompt += `- \`aso.keywords\` (\u2264100): ALL tiers, comma-separated (Tier 1 first, then Tier 2, then Tier 3)
|
|
1254
1277
|
`;
|
|
1255
|
-
prompt += `- \`
|
|
1278
|
+
prompt += `- \`landing.hero.title\`: Tier 1 + Tier 2 keywords
|
|
1256
1279
|
`;
|
|
1257
|
-
prompt += `- \`landing.hero.
|
|
1280
|
+
prompt += `- \`landing.hero.description\`: Tier 2 keywords naturally integrated
|
|
1258
1281
|
`;
|
|
1259
|
-
prompt += `- \`landing.
|
|
1282
|
+
prompt += `- \`landing.screenshots.images[].title\`: Tier 2 keywords
|
|
1260
1283
|
`;
|
|
1261
|
-
prompt += `- \`landing.screenshots.images[].
|
|
1284
|
+
prompt += `- \`landing.screenshots.images[].description\`: Tier 2 + Tier 3 keywords
|
|
1285
|
+
|
|
1262
1286
|
`;
|
|
1263
|
-
prompt +=
|
|
1287
|
+
prompt += `### Tier 3 Keywords (Longtail) \u2192 Content Sections
|
|
1264
1288
|
`;
|
|
1265
|
-
prompt += `- \`
|
|
1289
|
+
prompt += `- \`aso.template.intro\` (\u2264300): Tier 2 + Tier 3 keywords, keyword-rich, use full length
|
|
1266
1290
|
`;
|
|
1267
|
-
prompt += `- \`
|
|
1291
|
+
prompt += `- \`aso.template.outro\` (\u2264200): Tier 3 keywords, natural integration
|
|
1268
1292
|
`;
|
|
1269
|
-
prompt += `- \`landing.
|
|
1293
|
+
prompt += `- \`landing.features.items[].title\`: Tier 2 keywords
|
|
1270
1294
|
`;
|
|
1271
|
-
prompt += `- \`landing.
|
|
1295
|
+
prompt += `- \`landing.features.items[].body\`: Tier 3 keywords with user language patterns
|
|
1272
1296
|
`;
|
|
1273
|
-
prompt += `- \`landing.
|
|
1297
|
+
prompt += `- \`landing.reviews.title/description\`: Keywords if applicable
|
|
1274
1298
|
`;
|
|
1275
|
-
prompt += `- \`landing.cta.description\`: Keywords if applicable
|
|
1299
|
+
prompt += `- \`landing.cta.headline/description\`: Keywords if applicable
|
|
1300
|
+
|
|
1301
|
+
`;
|
|
1302
|
+
prompt += `### User Language Integration
|
|
1303
|
+
`;
|
|
1304
|
+
prompt += `- Use **User Language Patterns** from research in intro/outro/features
|
|
1305
|
+
`;
|
|
1306
|
+
prompt += `- These are actual phrases users search for - incorporate naturally
|
|
1276
1307
|
|
|
1277
1308
|
`;
|
|
1278
1309
|
prompt += `**Guidelines**: 2.5-3% keyword density, natural flow, cultural appropriateness
|
|
1279
1310
|
`;
|
|
1280
|
-
prompt += `**CRITICAL**: You MUST include the complete \`landing\` object in your optimized JSON output
|
|
1311
|
+
prompt += `**CRITICAL**: You MUST include the complete \`landing\` object in your optimized JSON output.
|
|
1281
1312
|
|
|
1282
1313
|
`;
|
|
1283
1314
|
prompt += `## Step 3: Validate
|
|
@@ -1622,13 +1653,46 @@ function extractRecommended(data) {
|
|
|
1622
1653
|
const summary = data?.summary || data?.data?.summary;
|
|
1623
1654
|
const recommended = summary?.recommendedKeywords;
|
|
1624
1655
|
if (Array.isArray(recommended)) {
|
|
1625
|
-
return recommended.map(
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1656
|
+
return recommended.map((item) => {
|
|
1657
|
+
if (typeof item === "object" && item?.keyword) {
|
|
1658
|
+
return {
|
|
1659
|
+
keyword: String(item.keyword),
|
|
1660
|
+
tier: String(item.tier || ""),
|
|
1661
|
+
difficulty: Number(item.difficulty) || 0,
|
|
1662
|
+
traffic: Number(item.traffic) || 0,
|
|
1663
|
+
rationale: String(item.rationale || "")
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
if (typeof item === "string") {
|
|
1667
|
+
return { keyword: item, tier: "", difficulty: 0, traffic: 0, rationale: "" };
|
|
1668
|
+
}
|
|
1669
|
+
return null;
|
|
1670
|
+
}).filter((item) => item !== null);
|
|
1629
1671
|
}
|
|
1630
1672
|
return [];
|
|
1631
1673
|
}
|
|
1674
|
+
function extractKeywordsByTier(data) {
|
|
1675
|
+
const summary = data?.summary || data?.data?.summary;
|
|
1676
|
+
const byTier = summary?.keywordsByTier || {};
|
|
1677
|
+
const extractKeywords = (tier) => Array.isArray(tier) ? tier.map((k) => typeof k === "object" ? k.keyword : String(k)).filter(Boolean) : [];
|
|
1678
|
+
return {
|
|
1679
|
+
tier1_core: extractKeywords(byTier.tier1_core),
|
|
1680
|
+
tier2_feature: extractKeywords(byTier.tier2_feature),
|
|
1681
|
+
tier3_longtail: extractKeywords(byTier.tier3_longtail)
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
function extractRationale(data) {
|
|
1685
|
+
const summary = data?.summary || data?.data?.summary;
|
|
1686
|
+
return summary?.rationale || "";
|
|
1687
|
+
}
|
|
1688
|
+
function extractCompetitorInsights(data) {
|
|
1689
|
+
const summary = data?.summary || data?.data?.summary;
|
|
1690
|
+
const insights = summary?.competitorInsights || {};
|
|
1691
|
+
return {
|
|
1692
|
+
keywordGaps: Array.isArray(insights.keywordGaps) ? insights.keywordGaps : [],
|
|
1693
|
+
userLanguagePatterns: Array.isArray(insights.userLanguagePatterns) ? insights.userLanguagePatterns : []
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1632
1696
|
function extractMeta(data) {
|
|
1633
1697
|
const meta = data?.meta || data?.data?.meta || {};
|
|
1634
1698
|
return {
|
|
@@ -1642,31 +1706,170 @@ function formatEntry(entry) {
|
|
|
1642
1706
|
const { filePath, data } = entry;
|
|
1643
1707
|
const recommended = extractRecommended(data);
|
|
1644
1708
|
const meta = extractMeta(data);
|
|
1709
|
+
const byTier = extractKeywordsByTier(data);
|
|
1710
|
+
const rationale = extractRationale(data);
|
|
1711
|
+
const insights = extractCompetitorInsights(data);
|
|
1645
1712
|
if (data?.parseError) {
|
|
1646
1713
|
return `File: ${filePath}
|
|
1647
1714
|
Parse error: ${data.parseError}
|
|
1648
1715
|
----`;
|
|
1649
1716
|
}
|
|
1650
1717
|
const lines = [];
|
|
1651
|
-
lines.push(
|
|
1718
|
+
lines.push(`### File: ${filePath}`);
|
|
1652
1719
|
if (meta.platform || meta.country) {
|
|
1653
1720
|
lines.push(
|
|
1654
1721
|
`Platform: ${meta.platform || "unknown"} | Country: ${meta.country || "unknown"}`
|
|
1655
1722
|
);
|
|
1656
1723
|
}
|
|
1657
|
-
if (
|
|
1658
|
-
lines.push(`
|
|
1724
|
+
if (byTier.tier1_core.length > 0) {
|
|
1725
|
+
lines.push(`
|
|
1726
|
+
**Tier 1 (Core - use in title/subtitle):** ${byTier.tier1_core.join(", ")}`);
|
|
1727
|
+
}
|
|
1728
|
+
if (byTier.tier2_feature.length > 0) {
|
|
1729
|
+
lines.push(`**Tier 2 (Feature - use in keywords field/descriptions):** ${byTier.tier2_feature.join(", ")}`);
|
|
1730
|
+
}
|
|
1731
|
+
if (byTier.tier3_longtail.length > 0) {
|
|
1732
|
+
lines.push(`**Tier 3 (Longtail - use in intro/outro/features):** ${byTier.tier3_longtail.join(", ")}`);
|
|
1733
|
+
}
|
|
1734
|
+
if (recommended.length > 0) {
|
|
1735
|
+
lines.push(`
|
|
1736
|
+
**Keyword Details (${recommended.length} keywords):**`);
|
|
1737
|
+
recommended.forEach((kw, idx) => {
|
|
1738
|
+
const tierLabel = kw.tier ? ` [${kw.tier}]` : "";
|
|
1739
|
+
const scores = kw.traffic > 0 || kw.difficulty > 0 ? ` (traffic: ${kw.traffic.toFixed(2)}, difficulty: ${kw.difficulty.toFixed(2)})` : "";
|
|
1740
|
+
lines.push(`${idx + 1}. **${kw.keyword}**${tierLabel}${scores}`);
|
|
1741
|
+
if (kw.rationale) {
|
|
1742
|
+
lines.push(` \u2192 ${kw.rationale}`);
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1659
1745
|
}
|
|
1660
|
-
if (
|
|
1661
|
-
|
|
1662
|
-
|
|
1746
|
+
if (rationale) {
|
|
1747
|
+
lines.push(`
|
|
1748
|
+
**Strategy:** ${rationale}`);
|
|
1663
1749
|
}
|
|
1664
|
-
if (
|
|
1665
|
-
lines.push(`
|
|
1666
|
-
|
|
1667
|
-
|
|
1750
|
+
if (insights.keywordGaps.length > 0) {
|
|
1751
|
+
lines.push(`
|
|
1752
|
+
**Keyword Gaps (opportunities):**`);
|
|
1753
|
+
insights.keywordGaps.forEach((gap) => lines.push(`- ${gap}`));
|
|
1754
|
+
}
|
|
1755
|
+
if (insights.userLanguagePatterns.length > 0) {
|
|
1756
|
+
lines.push(`
|
|
1757
|
+
**User Language Patterns (from reviews):**`);
|
|
1758
|
+
insights.userLanguagePatterns.forEach((pattern) => lines.push(`- ${pattern}`));
|
|
1759
|
+
}
|
|
1760
|
+
lines.push("\n----");
|
|
1761
|
+
return lines.join("\n");
|
|
1762
|
+
}
|
|
1763
|
+
function mergeKeywordData(entries) {
|
|
1764
|
+
const merged = {
|
|
1765
|
+
tier1_core: [],
|
|
1766
|
+
tier2_feature: [],
|
|
1767
|
+
tier3_longtail: [],
|
|
1768
|
+
allKeywords: [],
|
|
1769
|
+
rationale: "",
|
|
1770
|
+
keywordGaps: [],
|
|
1771
|
+
userLanguagePatterns: [],
|
|
1772
|
+
platforms: []
|
|
1773
|
+
};
|
|
1774
|
+
const seenKeywords = /* @__PURE__ */ new Set();
|
|
1775
|
+
const seenGaps = /* @__PURE__ */ new Set();
|
|
1776
|
+
const seenPatterns = /* @__PURE__ */ new Set();
|
|
1777
|
+
for (const entry of entries) {
|
|
1778
|
+
if (entry.data?.parseError) continue;
|
|
1779
|
+
const meta = extractMeta(entry.data);
|
|
1780
|
+
if (meta.platform && !merged.platforms.includes(meta.platform)) {
|
|
1781
|
+
merged.platforms.push(meta.platform);
|
|
1782
|
+
}
|
|
1783
|
+
const byTier = extractKeywordsByTier(entry.data);
|
|
1784
|
+
byTier.tier1_core.forEach((kw) => {
|
|
1785
|
+
if (!seenKeywords.has(kw.toLowerCase())) {
|
|
1786
|
+
merged.tier1_core.push(kw);
|
|
1787
|
+
seenKeywords.add(kw.toLowerCase());
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
byTier.tier2_feature.forEach((kw) => {
|
|
1791
|
+
if (!seenKeywords.has(kw.toLowerCase())) {
|
|
1792
|
+
merged.tier2_feature.push(kw);
|
|
1793
|
+
seenKeywords.add(kw.toLowerCase());
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
byTier.tier3_longtail.forEach((kw) => {
|
|
1797
|
+
if (!seenKeywords.has(kw.toLowerCase())) {
|
|
1798
|
+
merged.tier3_longtail.push(kw);
|
|
1799
|
+
seenKeywords.add(kw.toLowerCase());
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1802
|
+
const recommended = extractRecommended(entry.data);
|
|
1803
|
+
for (const kw of recommended) {
|
|
1804
|
+
if (!seenKeywords.has(kw.keyword.toLowerCase())) {
|
|
1805
|
+
merged.allKeywords.push(kw);
|
|
1806
|
+
seenKeywords.add(kw.keyword.toLowerCase());
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
const rationale = extractRationale(entry.data);
|
|
1810
|
+
if (rationale && !merged.rationale) {
|
|
1811
|
+
merged.rationale = rationale;
|
|
1812
|
+
} else if (rationale && merged.rationale) {
|
|
1813
|
+
merged.rationale += ` | ${meta.platform}: ${rationale}`;
|
|
1814
|
+
}
|
|
1815
|
+
const insights = extractCompetitorInsights(entry.data);
|
|
1816
|
+
insights.keywordGaps.forEach((gap) => {
|
|
1817
|
+
if (!seenGaps.has(gap)) {
|
|
1818
|
+
merged.keywordGaps.push(gap);
|
|
1819
|
+
seenGaps.add(gap);
|
|
1820
|
+
}
|
|
1821
|
+
});
|
|
1822
|
+
insights.userLanguagePatterns.forEach((pattern) => {
|
|
1823
|
+
if (!seenPatterns.has(pattern)) {
|
|
1824
|
+
merged.userLanguagePatterns.push(pattern);
|
|
1825
|
+
seenPatterns.add(pattern);
|
|
1826
|
+
}
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
merged.allKeywords.sort((a, b) => b.traffic - a.traffic);
|
|
1830
|
+
return merged;
|
|
1831
|
+
}
|
|
1832
|
+
function formatMergedData(merged, researchDir) {
|
|
1833
|
+
const lines = [];
|
|
1834
|
+
lines.push(`### Combined Keyword Research (${merged.platforms.join(" + ")})`);
|
|
1835
|
+
lines.push(`Source: ${researchDir}`);
|
|
1836
|
+
if (merged.tier1_core.length > 0) {
|
|
1837
|
+
lines.push(`
|
|
1838
|
+
**Tier 1 (Core - use in title/subtitle):** ${merged.tier1_core.join(", ")}`);
|
|
1839
|
+
}
|
|
1840
|
+
if (merged.tier2_feature.length > 0) {
|
|
1841
|
+
lines.push(`**Tier 2 (Feature - use in keywords field/descriptions):** ${merged.tier2_feature.join(", ")}`);
|
|
1842
|
+
}
|
|
1843
|
+
if (merged.tier3_longtail.length > 0) {
|
|
1844
|
+
lines.push(`**Tier 3 (Longtail - use in intro/outro/features):** ${merged.tier3_longtail.join(", ")}`);
|
|
1845
|
+
}
|
|
1846
|
+
if (merged.allKeywords.length > 0) {
|
|
1847
|
+
lines.push(`
|
|
1848
|
+
**Top Keywords by Traffic (${merged.allKeywords.length} total):**`);
|
|
1849
|
+
merged.allKeywords.slice(0, 15).forEach((kw, idx) => {
|
|
1850
|
+
const tierLabel = kw.tier ? ` [${kw.tier}]` : "";
|
|
1851
|
+
const scores = kw.traffic > 0 || kw.difficulty > 0 ? ` (traffic: ${kw.traffic.toFixed(2)}, difficulty: ${kw.difficulty.toFixed(2)})` : "";
|
|
1852
|
+
lines.push(`${idx + 1}. **${kw.keyword}**${tierLabel}${scores}`);
|
|
1853
|
+
if (kw.rationale) {
|
|
1854
|
+
lines.push(` \u2192 ${kw.rationale}`);
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1668
1857
|
}
|
|
1669
|
-
|
|
1858
|
+
if (merged.rationale) {
|
|
1859
|
+
lines.push(`
|
|
1860
|
+
**Strategy:** ${merged.rationale}`);
|
|
1861
|
+
}
|
|
1862
|
+
if (merged.keywordGaps.length > 0) {
|
|
1863
|
+
lines.push(`
|
|
1864
|
+
**Keyword Gaps (opportunities):**`);
|
|
1865
|
+
merged.keywordGaps.slice(0, 5).forEach((gap) => lines.push(`- ${gap}`));
|
|
1866
|
+
}
|
|
1867
|
+
if (merged.userLanguagePatterns.length > 0) {
|
|
1868
|
+
lines.push(`
|
|
1869
|
+
**User Language Patterns (from reviews):**`);
|
|
1870
|
+
merged.userLanguagePatterns.slice(0, 5).forEach((pattern) => lines.push(`- ${pattern}`));
|
|
1871
|
+
}
|
|
1872
|
+
lines.push("\n----");
|
|
1670
1873
|
return lines.join("\n");
|
|
1671
1874
|
}
|
|
1672
1875
|
function loadKeywordResearchForLocale(slug, locale) {
|
|
@@ -1697,6 +1900,15 @@ function loadKeywordResearchForLocale(slug, locale) {
|
|
|
1697
1900
|
});
|
|
1698
1901
|
}
|
|
1699
1902
|
}
|
|
1903
|
+
const validEntries = entries.filter((e) => !e.data?.parseError);
|
|
1904
|
+
if (validEntries.length > 1) {
|
|
1905
|
+
const merged = mergeKeywordData(validEntries);
|
|
1906
|
+
const mergedSection = formatMergedData(merged, researchDir);
|
|
1907
|
+
return { entries, sections: [mergedSection], researchDir };
|
|
1908
|
+
} else if (validEntries.length === 1) {
|
|
1909
|
+
const sections2 = entries.map(formatEntry);
|
|
1910
|
+
return { entries, sections: sections2, researchDir };
|
|
1911
|
+
}
|
|
1700
1912
|
const sections = entries.map(formatEntry);
|
|
1701
1913
|
return { entries, sections, researchDir };
|
|
1702
1914
|
}
|
|
@@ -2596,11 +2808,31 @@ var keywordResearchTool = {
|
|
|
2596
2808
|
|
|
2597
2809
|
**IMPORTANT:** Always use 'search-app' tool first to resolve the exact slug before calling this tool. The user may provide an approximate name, bundleId, or packageName - search-app will find and return the correct slug. Never pass user input directly as slug.
|
|
2598
2810
|
|
|
2599
|
-
|
|
2811
|
+
## CRITICAL: Multi-Locale Execution Plan
|
|
2812
|
+
|
|
2813
|
+
**MANDATORY WORKFLOW - Complete each locale fully before moving to next:**
|
|
2600
2814
|
|
|
2601
|
-
|
|
2815
|
+
For EACH locale+platform combination, execute this cycle:
|
|
2816
|
+
1. **Plan:** Call keyword-research(slug, locale, platform) with writeTemplate=false \u2192 get research plan
|
|
2817
|
+
2. **Research:** Execute COMPLETE mcp-appstore workflow (all 16 steps) for that locale
|
|
2818
|
+
3. **Save:** Call keyword-research again with researchData or researchDataPath \u2192 persist actual data
|
|
2819
|
+
4. **Next:** Move to next locale+platform and repeat steps 1-3
|
|
2602
2820
|
|
|
2603
|
-
|
|
2821
|
+
**IMPORTANT: Research \u2192 Save \u2192 Next pattern**
|
|
2822
|
+
- Complete ONE locale fully (research + save) before starting the next
|
|
2823
|
+
- This prevents data loss if the session is interrupted
|
|
2824
|
+
- Each locale's data is persisted immediately after research
|
|
2825
|
+
|
|
2826
|
+
**FORBIDDEN:**
|
|
2827
|
+
- \u274C Using writeTemplate=true as final output
|
|
2828
|
+
- \u274C Skipping secondary locales
|
|
2829
|
+
- \u274C Researching multiple locales then saving all at once at the end
|
|
2830
|
+
- \u274C Stopping before all locale+platform combinations are done
|
|
2831
|
+
|
|
2832
|
+
**REQUIRED:**
|
|
2833
|
+
- \u2705 Research locale \u2192 Save locale \u2192 Move to next (one at a time)
|
|
2834
|
+
- \u2705 Run for EVERY platform (ios AND android separately)
|
|
2835
|
+
- \u2705 Use researchData or researchDataPath to save (NOT writeTemplate)`,
|
|
2604
2836
|
inputSchema: inputSchema7
|
|
2605
2837
|
};
|
|
2606
2838
|
function buildTemplate({
|
|
@@ -2733,7 +2965,6 @@ async function handleKeywordResearch(input) {
|
|
|
2733
2965
|
const { config, locales } = loadProductLocales(slug);
|
|
2734
2966
|
const primaryLocale = resolvePrimaryLocale(config, locales);
|
|
2735
2967
|
const productLocales = Object.keys(locales);
|
|
2736
|
-
const remainingLocales = productLocales.filter((loc) => loc !== locale);
|
|
2737
2968
|
const primaryLocaleData = locales[primaryLocale];
|
|
2738
2969
|
const { supportedLocales, path: supportedPath } = getSupportedLocalesForSlug(slug, platform);
|
|
2739
2970
|
const appStoreLocales = registeredApp?.appStore?.supportedLocales || [];
|
|
@@ -2879,24 +3110,56 @@ Context around ${pos}: ${context}`
|
|
|
2879
3110
|
`Registered supported locales not found for ${platform} (checked: ${supportedPath}).`
|
|
2880
3111
|
);
|
|
2881
3112
|
}
|
|
3113
|
+
const allCombinations = [];
|
|
3114
|
+
const platformsToRun = declaredPlatforms.length > 0 ? declaredPlatforms : [platform];
|
|
3115
|
+
for (const plat of platformsToRun) {
|
|
3116
|
+
for (const loc of productLocales.length > 0 ? productLocales : [locale]) {
|
|
3117
|
+
allCombinations.push({ loc, plat });
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
const currentIndex = allCombinations.findIndex(
|
|
3121
|
+
(c) => c.loc === locale && c.plat === platform
|
|
3122
|
+
);
|
|
3123
|
+
const completedCount = currentIndex >= 0 ? currentIndex : 0;
|
|
3124
|
+
const remainingCombinations = allCombinations.slice(currentIndex + 1);
|
|
2882
3125
|
if (productLocales.length > 0) {
|
|
2883
3126
|
lines.push(
|
|
2884
3127
|
`Existing product locales (${productLocales.length}): ${productLocales.join(", ")}`
|
|
2885
3128
|
);
|
|
2886
|
-
lines.push(
|
|
2887
|
-
"MANDATORY: Run FULL keyword research (mcp-appstore workflow) for EVERY locale above\u2014no template-only coverage for secondary markets."
|
|
2888
|
-
);
|
|
2889
|
-
if (remainingLocales.length > 0) {
|
|
2890
|
-
lines.push(
|
|
2891
|
-
`After finishing ${locale}, immediately queue runs for: ${remainingLocales.join(", ")}`
|
|
2892
|
-
);
|
|
2893
|
-
}
|
|
2894
|
-
if (declaredPlatforms.length > 1) {
|
|
2895
|
-
lines.push(
|
|
2896
|
-
"Also run separate FULL keyword research for each supported platform (e.g., ios + android) across all locales."
|
|
2897
|
-
);
|
|
2898
|
-
}
|
|
2899
3129
|
}
|
|
3130
|
+
lines.push("");
|
|
3131
|
+
lines.push("---");
|
|
3132
|
+
lines.push("## \u{1F3AF} EXECUTION PROGRESS TRACKER");
|
|
3133
|
+
lines.push("");
|
|
3134
|
+
lines.push(`**Total combinations to complete:** ${allCombinations.length} (${platformsToRun.length} platforms \xD7 ${productLocales.length || 1} locales)`);
|
|
3135
|
+
lines.push(`**Current:** ${locale} + ${platform} (${completedCount + 1}/${allCombinations.length})`);
|
|
3136
|
+
lines.push("");
|
|
3137
|
+
if (researchData || researchDataPath) {
|
|
3138
|
+
lines.push(`\u2705 SAVED: ${locale} + ${platform} - Full research data persisted`);
|
|
3139
|
+
} else if (writeTemplate) {
|
|
3140
|
+
lines.push(`\u26A0\uFE0F WARNING: ${locale} + ${platform} - Only template written! You MUST run full mcp-appstore research and save actual data.`);
|
|
3141
|
+
} else {
|
|
3142
|
+
lines.push(`\u{1F4CB} PLANNING: ${locale} + ${platform} - Research plan shown. Now execute mcp-appstore workflow.`);
|
|
3143
|
+
}
|
|
3144
|
+
if (remainingCombinations.length > 0) {
|
|
3145
|
+
lines.push("");
|
|
3146
|
+
lines.push("## \u23ED\uFE0F MANDATORY NEXT STEPS");
|
|
3147
|
+
lines.push("");
|
|
3148
|
+
lines.push("**After completing current locale+platform, you MUST continue with:**");
|
|
3149
|
+
lines.push("");
|
|
3150
|
+
remainingCombinations.forEach((combo, idx) => {
|
|
3151
|
+
lines.push(`${idx + 1}. keyword-research(slug="${slug}", locale="${combo.loc}", platform="${combo.plat}") \u2192 full mcp-appstore workflow \u2192 save results`);
|
|
3152
|
+
});
|
|
3153
|
+
lines.push("");
|
|
3154
|
+
lines.push("\u26D4 DO NOT mark this task as complete until ALL combinations above have FULL research data (not templates).");
|
|
3155
|
+
} else {
|
|
3156
|
+
lines.push("");
|
|
3157
|
+
lines.push("## \u2705 FINAL STEP");
|
|
3158
|
+
lines.push("");
|
|
3159
|
+
lines.push("This is the LAST locale+platform combination. After saving full research data for this one, the task is complete.");
|
|
3160
|
+
}
|
|
3161
|
+
lines.push("---");
|
|
3162
|
+
lines.push("");
|
|
2900
3163
|
lines.push(
|
|
2901
3164
|
`Seeds: ${resolvedSeeds.length > 0 ? resolvedSeeds.join(", ") : "(none set; add seedKeywords or ensure ASO keywords/title exist)"}`
|
|
2902
3165
|
);
|