pabal-web-mcp 1.4.2 → 1.4.4
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 +122 -39
- package/package.json +1 -1
package/dist/bin/mcp-server.js
CHANGED
|
@@ -517,6 +517,19 @@ function convertToMultilingual(data, locale) {
|
|
|
517
517
|
// src/tools/public-to-aso.ts
|
|
518
518
|
import fs4 from "fs";
|
|
519
519
|
var FIELD_LIMITS_DOC_PATH = "docs/aso/ASO_FIELD_LIMITS.md";
|
|
520
|
+
var APP_STORE_LIMITS = {
|
|
521
|
+
name: 30,
|
|
522
|
+
subtitle: 30,
|
|
523
|
+
keywords: 100,
|
|
524
|
+
promotionalText: 170,
|
|
525
|
+
description: 4e3,
|
|
526
|
+
whatsNew: 4e3
|
|
527
|
+
};
|
|
528
|
+
var GOOGLE_PLAY_LIMITS = {
|
|
529
|
+
title: 50,
|
|
530
|
+
shortDescription: 80,
|
|
531
|
+
fullDescription: 4e3
|
|
532
|
+
};
|
|
520
533
|
var toJsonSchema2 = zodToJsonSchema2;
|
|
521
534
|
var publicToAsoInputSchema = z2.object({
|
|
522
535
|
slug: z2.string().describe("Product slug"),
|
|
@@ -527,6 +540,51 @@ var jsonSchema2 = toJsonSchema2(publicToAsoInputSchema, {
|
|
|
527
540
|
$refStrategy: "none"
|
|
528
541
|
});
|
|
529
542
|
var inputSchema2 = jsonSchema2.definitions?.PublicToAsoInput || jsonSchema2;
|
|
543
|
+
function validateFieldLimits(configData) {
|
|
544
|
+
const issues = [];
|
|
545
|
+
if (configData.appStore) {
|
|
546
|
+
const appStoreData = configData.appStore;
|
|
547
|
+
const locales = isAppStoreMultilingual(appStoreData) ? appStoreData.locales : { [appStoreData.locale || DEFAULT_LOCALE]: appStoreData };
|
|
548
|
+
for (const [locale, data] of Object.entries(locales)) {
|
|
549
|
+
const { name, subtitle, keywords, promotionalText, description, whatsNew } = data;
|
|
550
|
+
const push = (field, value, limit) => {
|
|
551
|
+
if (typeof value === "string" && value.length > limit) {
|
|
552
|
+
issues.push(
|
|
553
|
+
`App Store [${locale}].${field}: ${value.length}/${limit}`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
push("name", name, APP_STORE_LIMITS.name);
|
|
558
|
+
push("subtitle", subtitle, APP_STORE_LIMITS.subtitle);
|
|
559
|
+
push("keywords", keywords, APP_STORE_LIMITS.keywords);
|
|
560
|
+
push("promotionalText", promotionalText, APP_STORE_LIMITS.promotionalText);
|
|
561
|
+
push("description", description, APP_STORE_LIMITS.description);
|
|
562
|
+
push("whatsNew", whatsNew, APP_STORE_LIMITS.whatsNew);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (configData.googlePlay) {
|
|
566
|
+
const googlePlayData = configData.googlePlay;
|
|
567
|
+
const locales = isGooglePlayMultilingual(googlePlayData) ? googlePlayData.locales : { [googlePlayData.defaultLanguage || DEFAULT_LOCALE]: googlePlayData };
|
|
568
|
+
for (const [locale, data] of Object.entries(locales)) {
|
|
569
|
+
const { title, shortDescription, fullDescription } = data;
|
|
570
|
+
const push = (field, value, limit) => {
|
|
571
|
+
if (typeof value === "string" && value.length > limit) {
|
|
572
|
+
issues.push(
|
|
573
|
+
`Google Play [${locale}].${field}: ${value.length}/${limit}`
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
push("title", title, GOOGLE_PLAY_LIMITS.title);
|
|
578
|
+
push(
|
|
579
|
+
"shortDescription",
|
|
580
|
+
shortDescription,
|
|
581
|
+
GOOGLE_PLAY_LIMITS.shortDescription
|
|
582
|
+
);
|
|
583
|
+
push("fullDescription", fullDescription, GOOGLE_PLAY_LIMITS.fullDescription);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return issues;
|
|
587
|
+
}
|
|
530
588
|
async function downloadScreenshotsToAsoDir(slug, asoData) {
|
|
531
589
|
const rootDir = getPushDataDir();
|
|
532
590
|
const productStoreRoot = path4.join(rootDir, "products", slug, "store");
|
|
@@ -672,6 +730,7 @@ This tool:
|
|
|
672
730
|
2. Converts to store-compatible format (removes screenshots from metadata, sets contactWebsite/marketingUrl)
|
|
673
731
|
3. Saves metadata to .aso/pushData/products/[slug]/store/ (path from ~/.config/pabal-mcp/config.json dataDir)
|
|
674
732
|
4. Copies/downloads screenshots to .aso/pushData/products/[slug]/store/screenshots/
|
|
733
|
+
5. Validates text field lengths against ${FIELD_LIMITS_DOC_PATH} (fails if over limits)
|
|
675
734
|
|
|
676
735
|
Before running, review ${FIELD_LIMITS_DOC_PATH} for per-store limits. This prepares data for pushing to stores without actually uploading.`,
|
|
677
736
|
inputSchema: inputSchema2
|
|
@@ -761,6 +820,11 @@ Possible causes:
|
|
|
761
820
|
);
|
|
762
821
|
}
|
|
763
822
|
const storeData = prepareAsoDataForPush(slug, configData);
|
|
823
|
+
const validationIssues = validateFieldLimits(configData);
|
|
824
|
+
const validationMessage = validationIssues.length > 0 ? `\u26A0\uFE0F Field limit issues (see ${FIELD_LIMITS_DOC_PATH}):
|
|
825
|
+
- ${validationIssues.join(
|
|
826
|
+
"\n- "
|
|
827
|
+
)}` : `Field limits OK (checked against ${FIELD_LIMITS_DOC_PATH})`;
|
|
764
828
|
const pushDataRoot = getPushDataDir();
|
|
765
829
|
if (dryRun) {
|
|
766
830
|
return {
|
|
@@ -773,11 +837,19 @@ ${JSON.stringify(
|
|
|
773
837
|
storeData,
|
|
774
838
|
null,
|
|
775
839
|
2
|
|
776
|
-
)}
|
|
840
|
+
)}
|
|
841
|
+
|
|
842
|
+
${validationMessage}`
|
|
777
843
|
}
|
|
778
844
|
]
|
|
779
845
|
};
|
|
780
846
|
}
|
|
847
|
+
if (validationIssues.length > 0) {
|
|
848
|
+
throw new Error(
|
|
849
|
+
`Field limit violations detected. Fix before pushing.
|
|
850
|
+
${validationMessage}`
|
|
851
|
+
);
|
|
852
|
+
}
|
|
781
853
|
saveRawAsoData(slug, storeData);
|
|
782
854
|
await downloadScreenshotsToAsoDir(slug, configData);
|
|
783
855
|
const localeCounts = {};
|
|
@@ -1314,10 +1386,10 @@ ${researchSections.join("\n")}
|
|
|
1314
1386
|
prompt += `**CRITICAL**: You MUST include the complete \`landing\` object in your optimized JSON output.
|
|
1315
1387
|
|
|
1316
1388
|
`;
|
|
1317
|
-
prompt += `## Step 3: Validate
|
|
1389
|
+
prompt += `## Step 3: Validate (after applying all keywords)
|
|
1318
1390
|
|
|
1319
1391
|
`;
|
|
1320
|
-
prompt += `Check all limits: title \u226430, subtitle \u226430, shortDescription \u226480, keywords \u2264100, intro \u2264300, outro \u2264200
|
|
1392
|
+
prompt += `Check all limits using ${FIELD_LIMITS_DOC_PATH2}: title \u226430, subtitle \u226430, shortDescription \u226480, keywords \u2264100, intro \u2264300, outro \u2264200
|
|
1321
1393
|
`;
|
|
1322
1394
|
prompt += `- Remove keyword duplicates (unique list; avoid repeating title/subtitle terms verbatim)
|
|
1323
1395
|
`;
|
|
@@ -1434,9 +1506,9 @@ function generateKeywordLocalizationPrompt(args) {
|
|
|
1434
1506
|
`;
|
|
1435
1507
|
prompt += `1. Use SAVED keyword research (see per-locale data below). Do NOT invent keywords.
|
|
1436
1508
|
`;
|
|
1437
|
-
prompt += `2. Replace keywords
|
|
1509
|
+
prompt += `2. **Replace ONLY keywords with optimized keywords** - keep ALL existing content, structure, tone, and context unchanged. Only swap keywords for better ASO keywords.
|
|
1438
1510
|
`;
|
|
1439
|
-
prompt += `3.
|
|
1511
|
+
prompt += `3. After all keywords are applied, validate character limits + store rules (${FIELD_LIMITS_DOC_PATH2}) + keyword duplication
|
|
1440
1512
|
`;
|
|
1441
1513
|
prompt += `4. **SAVE the updated JSON to file** using the save-locale-file tool (only if file exists)
|
|
1442
1514
|
|
|
@@ -1520,18 +1592,21 @@ ${researchSections.join(
|
|
|
1520
1592
|
});
|
|
1521
1593
|
prompt += `## Keyword Replacement Strategy
|
|
1522
1594
|
|
|
1595
|
+
`;
|
|
1596
|
+
prompt += `**CRITICAL: Keep ALL existing content unchanged. Only replace keywords with optimized keywords.**
|
|
1597
|
+
|
|
1523
1598
|
`;
|
|
1524
1599
|
prompt += `For EACH locale:
|
|
1525
1600
|
`;
|
|
1526
1601
|
prompt += `- Priority: Keep iOS-sourced keywords first; add Android keywords only if there is remaining space after iOS keywords fit within field limits.
|
|
1527
1602
|
`;
|
|
1528
|
-
prompt += `1. Take the
|
|
1603
|
+
prompt += `1. Take the EXISTING translated content (below) - **DO NOT change the content itself**
|
|
1529
1604
|
`;
|
|
1530
|
-
prompt += `2. Replace \`aso.keywords\` array with
|
|
1605
|
+
prompt += `2. Replace \`aso.keywords\` array with optimized keywords (keep same count/structure)
|
|
1531
1606
|
`;
|
|
1532
1607
|
prompt += `3. **TITLE FORMAT**: \`aso.title\` must follow **"App Name: Primary Keyword"** format:
|
|
1533
1608
|
`;
|
|
1534
|
-
prompt += ` - App name: **ALWAYS in English** (e.g., "Aurora EOS", "Timeline", "Recaply
|
|
1609
|
+
prompt += ` - App name: **ALWAYS in English** (e.g., "Aurora EOS", "Timeline", "Recaply)
|
|
1535
1610
|
`;
|
|
1536
1611
|
prompt += ` - Primary keyword: **In target language** (e.g., "\uC624\uB85C\uB77C \uC608\uBCF4" for Korean, "\u30AA\u30FC\u30ED\u30E9\u4E88\u5831" for Japanese)
|
|
1537
1612
|
`;
|
|
@@ -1540,52 +1615,56 @@ ${researchSections.join(
|
|
|
1540
1615
|
prompt += ` - The keyword after the colon must start with an uppercase letter
|
|
1541
1616
|
`;
|
|
1542
1617
|
prompt += ` - **Do NOT translate/rename the app name**; keep the original English app name across all locales.
|
|
1618
|
+
`;
|
|
1619
|
+
prompt += ` - **Only replace the keyword part** - keep the app name and format structure unchanged
|
|
1543
1620
|
`;
|
|
1544
1621
|
prompt += `4. Deduplicate keywords: final \`aso.keywords\` must be unique and should not repeat title/subtitle terms verbatim
|
|
1545
1622
|
`;
|
|
1546
|
-
prompt += `5.
|
|
1623
|
+
prompt += `5. **Replace keywords in existing sentences** - swap ONLY the keywords, keep everything else:
|
|
1547
1624
|
`;
|
|
1548
|
-
prompt += ` -
|
|
1625
|
+
prompt += ` - **Keep original sentence structure exactly as is**
|
|
1549
1626
|
`;
|
|
1550
|
-
prompt += ` -
|
|
1627
|
+
prompt += ` - **Keep original tone and messaging unchanged**
|
|
1551
1628
|
`;
|
|
1552
|
-
prompt += ` -
|
|
1629
|
+
prompt += ` - **Keep original context and flow unchanged**
|
|
1553
1630
|
`;
|
|
1554
|
-
prompt += ` -
|
|
1631
|
+
prompt += ` - **Only swap individual keywords** for better ASO keywords
|
|
1632
|
+
`;
|
|
1633
|
+
prompt += ` - Maintain character limits
|
|
1555
1634
|
|
|
1556
1635
|
`;
|
|
1557
|
-
prompt += `6. **CRITICAL**: Update ALL \`landing\` sections:
|
|
1636
|
+
prompt += `6. **CRITICAL**: Update keywords in ALL \`landing\` sections (replace keywords only, keep content structure):
|
|
1558
1637
|
`;
|
|
1559
|
-
prompt += ` - \`landing.hero.title\` and \`landing.hero.description\`:
|
|
1638
|
+
prompt += ` - \`landing.hero.title\` and \`landing.hero.description\`: Replace keywords only, keep existing text structure
|
|
1560
1639
|
`;
|
|
1561
|
-
prompt += ` - \`landing.screenshots.images[].title\`:
|
|
1640
|
+
prompt += ` - \`landing.screenshots.images[].title\`: Replace keywords in existing titles, keep structure
|
|
1562
1641
|
`;
|
|
1563
|
-
prompt += ` - \`landing.screenshots.images[].description\`:
|
|
1642
|
+
prompt += ` - \`landing.screenshots.images[].description\`: Replace keywords in existing descriptions, keep structure
|
|
1564
1643
|
`;
|
|
1565
|
-
prompt += ` - \`landing.features.items[].title\`:
|
|
1644
|
+
prompt += ` - \`landing.features.items[].title\`: Replace keywords in existing titles, keep structure
|
|
1566
1645
|
`;
|
|
1567
|
-
prompt += ` - \`landing.features.items[].body\`:
|
|
1646
|
+
prompt += ` - \`landing.features.items[].body\`: Replace keywords in existing descriptions, keep structure
|
|
1568
1647
|
`;
|
|
1569
|
-
prompt += ` - \`landing.reviews.title\` and \`landing.reviews.description\`:
|
|
1648
|
+
prompt += ` - \`landing.reviews.title\` and \`landing.reviews.description\`: Replace keywords if applicable, keep structure
|
|
1570
1649
|
`;
|
|
1571
|
-
prompt += ` - \`landing.cta.headline\` and \`landing.cta.description\`:
|
|
1650
|
+
prompt += ` - \`landing.cta.headline\` and \`landing.cta.description\`: Replace keywords if applicable, keep structure
|
|
1572
1651
|
`;
|
|
1573
|
-
prompt += ` - Maintain original context and
|
|
1652
|
+
prompt += ` - **Maintain ALL original context, meaning, and structure**
|
|
1574
1653
|
`;
|
|
1575
|
-
prompt += ` - Use
|
|
1654
|
+
prompt += ` - Use optimized keywords that users actually search for
|
|
1576
1655
|
`;
|
|
1577
|
-
prompt += ` - **DO NOT
|
|
1656
|
+
prompt += ` - **DO NOT rewrite or restructure content** - only replace keywords
|
|
1578
1657
|
|
|
1579
1658
|
`;
|
|
1580
|
-
prompt += `**Example
|
|
1659
|
+
prompt += `**Example** (keyword replacement only, content unchanged):
|
|
1581
1660
|
`;
|
|
1582
1661
|
prompt += `- Original: "Track aurora with real-time forecasts"
|
|
1583
1662
|
`;
|
|
1584
|
-
prompt += `-
|
|
1663
|
+
prompt += `- Optimized keywords: \uC624\uB85C\uB77C, \uC608\uBCF4, \uC2E4\uC2DC\uAC04
|
|
1585
1664
|
`;
|
|
1586
|
-
prompt += `- Result: "\uC2E4\uC2DC\uAC04 \uC608\uBCF4
|
|
1665
|
+
prompt += `- Result: "Track \uC624\uB85C\uB77C with \uC2E4\uC2DC\uAC04 \uC608\uBCF4" (keywords replaced, structure kept)
|
|
1587
1666
|
`;
|
|
1588
|
-
prompt += ` (
|
|
1667
|
+
prompt += ` OR: "\uC2E4\uC2DC\uAC04 \uC608\uBCF4\uB85C \uC624\uB85C\uB77C \uCD94\uC801" (if natural keyword placement requires minor word order, but keep meaning identical)
|
|
1589
1668
|
|
|
1590
1669
|
`;
|
|
1591
1670
|
prompt += `## Current Translated Locales (This Batch)
|
|
@@ -1606,7 +1685,7 @@ ${researchSections.join(
|
|
|
1606
1685
|
`;
|
|
1607
1686
|
prompt += `1. Use saved keyword research (in target language) OR **TRANSLATE English keywords from primary locale** if missing (see fallback strategy above - MUST translate, not use English directly)
|
|
1608
1687
|
`;
|
|
1609
|
-
prompt += `2. Replace keywords in ALL fields (
|
|
1688
|
+
prompt += `2. **Replace keywords ONLY** in ALL fields (keep existing content structure unchanged):
|
|
1610
1689
|
`;
|
|
1611
1690
|
prompt += ` - \`aso.keywords\` array
|
|
1612
1691
|
`;
|
|
@@ -1622,11 +1701,11 @@ ${researchSections.join(
|
|
|
1622
1701
|
`;
|
|
1623
1702
|
prompt += ` - \`landing.reviews.title\` and \`landing.reviews.description\`
|
|
1624
1703
|
`;
|
|
1625
|
-
prompt += ` -
|
|
1704
|
+
prompt += ` - **For each field: Replace keywords only, keep existing content structure and meaning unchanged**
|
|
1626
1705
|
`;
|
|
1627
1706
|
prompt += `3. **CRITICAL**: Ensure ALL landing fields are translated (not English)
|
|
1628
1707
|
`;
|
|
1629
|
-
prompt += `4.
|
|
1708
|
+
prompt += `4. After swapping keywords, validate limits + store rules (${FIELD_LIMITS_DOC_PATH2}) + keyword duplication (unique list; avoid repeating title/subtitle terms verbatim)
|
|
1630
1709
|
`;
|
|
1631
1710
|
prompt += `5. **SAVE the updated JSON to file** using save-locale-file tool
|
|
1632
1711
|
`;
|
|
@@ -1669,23 +1748,27 @@ ${researchSections.join(
|
|
|
1669
1748
|
prompt += ` - Show final 10 keywords **IN TARGET LANGUAGE** with tier assignments - DO NOT show English keywords
|
|
1670
1749
|
|
|
1671
1750
|
`;
|
|
1672
|
-
prompt += `**2. Updated JSON** (complete locale structure with keyword replacements)
|
|
1751
|
+
prompt += `**2. Updated JSON** (complete locale structure with keyword replacements only)
|
|
1752
|
+
`;
|
|
1753
|
+
prompt += ` - **CRITICAL**: Keep ALL existing content structure and meaning unchanged - only replace keywords
|
|
1673
1754
|
`;
|
|
1674
|
-
prompt += ` - MUST include complete \`aso\` object
|
|
1755
|
+
prompt += ` - MUST include complete \`aso\` object (keywords replaced, content structure kept)
|
|
1675
1756
|
`;
|
|
1676
|
-
prompt += ` - MUST include complete \`landing\` object with ALL sections:
|
|
1757
|
+
prompt += ` - MUST include complete \`landing\` object with ALL sections (keywords replaced, content structure kept):
|
|
1677
1758
|
`;
|
|
1678
|
-
prompt += ` * hero (title, description, titleHighlight)
|
|
1759
|
+
prompt += ` * hero (title, description, titleHighlight) - replace keywords only
|
|
1679
1760
|
`;
|
|
1680
|
-
prompt += ` * screenshots.images[] (all items with
|
|
1761
|
+
prompt += ` * screenshots.images[] (all items with keywords replaced in existing titles/descriptions)
|
|
1681
1762
|
`;
|
|
1682
|
-
prompt += ` * features.items[] (all items with
|
|
1763
|
+
prompt += ` * features.items[] (all items with keywords replaced in existing titles/bodies)
|
|
1683
1764
|
`;
|
|
1684
|
-
prompt += ` * reviews (title, description, icons, rating, testimonials)
|
|
1765
|
+
prompt += ` * reviews (title, description, icons, rating, testimonials) - replace keywords if applicable
|
|
1685
1766
|
`;
|
|
1686
|
-
prompt += ` * cta (headline, icons, rating, description)
|
|
1767
|
+
prompt += ` * cta (headline, icons, rating, description) - replace keywords if applicable
|
|
1687
1768
|
`;
|
|
1688
1769
|
prompt += ` - **NO English text in landing sections** - everything must be translated
|
|
1770
|
+
`;
|
|
1771
|
+
prompt += ` - **DO NOT rewrite or restructure content** - only swap keywords for optimized keywords
|
|
1689
1772
|
|
|
1690
1773
|
`;
|
|
1691
1774
|
prompt += `**3. Validation**
|