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.
@@ -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 in translated content (preserve structure/tone/context)
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. Validate character limits + store rules (${FIELD_LIMITS_DOC_PATH2}) + keyword duplication
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 TRANSLATED content (below)
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 new 10 keywords
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. Swap keywords in sentences while keeping:
1623
+ prompt += `5. **Replace keywords in existing sentences** - swap ONLY the keywords, keep everything else:
1547
1624
  `;
1548
- prompt += ` - Original sentence structure
1625
+ prompt += ` - **Keep original sentence structure exactly as is**
1549
1626
  `;
1550
- prompt += ` - Tone and messaging
1627
+ prompt += ` - **Keep original tone and messaging unchanged**
1551
1628
  `;
1552
- prompt += ` - Context and flow
1629
+ prompt += ` - **Keep original context and flow unchanged**
1553
1630
  `;
1554
- prompt += ` - Character limits
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\`: Include keywords naturally
1638
+ prompt += ` - \`landing.hero.title\` and \`landing.hero.description\`: Replace keywords only, keep existing text structure
1560
1639
  `;
1561
- prompt += ` - \`landing.screenshots.images[].title\`: Incorporate keywords in all screenshot titles
1640
+ prompt += ` - \`landing.screenshots.images[].title\`: Replace keywords in existing titles, keep structure
1562
1641
  `;
1563
- prompt += ` - \`landing.screenshots.images[].description\`: Include keywords in all screenshot descriptions
1642
+ prompt += ` - \`landing.screenshots.images[].description\`: Replace keywords in existing descriptions, keep structure
1564
1643
  `;
1565
- prompt += ` - \`landing.features.items[].title\`: Add keywords to feature titles where natural
1644
+ prompt += ` - \`landing.features.items[].title\`: Replace keywords in existing titles, keep structure
1566
1645
  `;
1567
- prompt += ` - \`landing.features.items[].body\`: Weave keywords into feature descriptions
1646
+ prompt += ` - \`landing.features.items[].body\`: Replace keywords in existing descriptions, keep structure
1568
1647
  `;
1569
- prompt += ` - \`landing.reviews.title\` and \`landing.reviews.description\`: Include keywords if applicable
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\`: Include keywords if applicable
1650
+ prompt += ` - \`landing.cta.headline\` and \`landing.cta.description\`: Replace keywords if applicable, keep structure
1572
1651
  `;
1573
- prompt += ` - Maintain original context and meaning
1652
+ prompt += ` - **Maintain ALL original context, meaning, and structure**
1574
1653
  `;
1575
- prompt += ` - Use language-specific terms that users actually search for
1654
+ prompt += ` - Use optimized keywords that users actually search for
1576
1655
  `;
1577
- prompt += ` - **DO NOT leave any landing fields in English** - all must be translated
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 += `- Korean keywords: \uC624\uB85C\uB77C, \uC608\uBCF4, \uC2E4\uC2DC\uAC04
1663
+ prompt += `- Optimized keywords: \uC624\uB85C\uB77C, \uC608\uBCF4, \uC2E4\uC2DC\uAC04
1585
1664
  `;
1586
- prompt += `- Result: "\uC2E4\uC2DC\uAC04 \uC608\uBCF4\uB85C \uC624\uB85C\uB77C \uCD94\uC801"
1665
+ prompt += `- Result: "Track \uC624\uB85C\uB77C with \uC2E4\uC2DC\uAC04 \uC608\uBCF4" (keywords replaced, structure kept)
1587
1666
  `;
1588
- prompt += ` (structure changed for Korean, but context preserved)
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 (use translated keywords in target language):
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 += ` - \`landing.cta.headline\` and \`landing.cta.description\`
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. Validate limits + store rules (${FIELD_LIMITS_DOC_PATH2}) + keyword duplication (unique list; avoid repeating title/subtitle terms verbatim)
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 translated title and description)
1761
+ prompt += ` * screenshots.images[] (all items with keywords replaced in existing titles/descriptions)
1681
1762
  `;
1682
- prompt += ` * features.items[] (all items with translated title and body)
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**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-web-mcp",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "type": "module",
5
5
  "description": "MCP server for ASO data management with shared types and utilities",
6
6
  "author": "skyu",