pabal-web-mcp 0.1.0 → 0.1.2
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/README.md +169 -43
- package/dist/bin/mcp-server.js +129 -48
- package/dist/chunk-AM6RGDD4.js +1001 -0
- package/dist/chunk-MWXNTV3M.js +1001 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.js +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,164 @@ MCP (Model Context Protocol) server for ASO (App Store Optimization) data manage
|
|
|
8
8
|
npm install pabal-web-mcp
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## 🛠️ MCP Client Installation
|
|
12
|
+
|
|
13
|
+
### Requirements
|
|
14
|
+
|
|
15
|
+
- Node.js >= 18
|
|
16
|
+
- MCP client: Cursor, Claude Code, VS Code, Windsurf, etc.
|
|
17
|
+
|
|
18
|
+
> [!TIP]
|
|
19
|
+
> If you repeatedly do ASO/store tasks, add a client rule like "always use pabal-web-mcp" so the MCP server auto-invokes without typing it every time.
|
|
20
|
+
|
|
21
|
+
### Global install (recommended)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g pabal-web-mcp
|
|
25
|
+
|
|
26
|
+
# or
|
|
27
|
+
|
|
28
|
+
yarn global add pabal-web-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Install globally first for fastest starts and to avoid npm download issues (proxy/firewall/offline). You can still use `npx -y pabal-web-mcp`, but global install is recommended. After global install, set your MCP config to `command: "pabal-web-mcp"` (no `npx` needed).
|
|
32
|
+
|
|
33
|
+
<details>
|
|
34
|
+
<summary><b>Install in Cursor</b></summary>
|
|
35
|
+
|
|
36
|
+
Add to `~/.cursor/mcp.json` (global) or project `.cursor/mcp.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"pabal-web-mcp": {
|
|
42
|
+
"command": "npx",
|
|
43
|
+
"args": ["-y", "pabal-web-mcp"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or if installed globally:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"mcpServers": {
|
|
54
|
+
"pabal-web-mcp": {
|
|
55
|
+
"command": "pabal-web-mcp"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
</details>
|
|
62
|
+
|
|
63
|
+
<details>
|
|
64
|
+
<summary><b>Install in VS Code</b></summary>
|
|
65
|
+
|
|
66
|
+
Example `settings.json` MCP section:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
"mcp": {
|
|
70
|
+
"servers": {
|
|
71
|
+
"pabal-web-mcp": {
|
|
72
|
+
"type": "stdio",
|
|
73
|
+
"command": "npx",
|
|
74
|
+
"args": ["-y", "pabal-web-mcp"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or if installed globally:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
"mcp": {
|
|
84
|
+
"servers": {
|
|
85
|
+
"pabal-web-mcp": {
|
|
86
|
+
"type": "stdio",
|
|
87
|
+
"command": "pabal-web-mcp"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
</details>
|
|
94
|
+
|
|
95
|
+
<details>
|
|
96
|
+
<summary><b>Install in Claude Code</b></summary>
|
|
97
|
+
|
|
98
|
+
> [!TIP]
|
|
99
|
+
> See the [official Claude Code MCP documentation](https://code.claude.com/docs/en/mcp#setting-up-enterprise-mcp-configuration) for detailed configuration options.
|
|
100
|
+
|
|
101
|
+
Add to Claude Code MCP settings (JSON format):
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"mcpServers": {
|
|
106
|
+
"pabal-web-mcp": {
|
|
107
|
+
"command": "npx",
|
|
108
|
+
"args": ["-y", "pabal-web-mcp"]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or if installed globally (`npm install -g pabal-web-mcp`):
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"mcpServers": {
|
|
119
|
+
"pabal-web-mcp": {
|
|
120
|
+
"command": "pabal-web-mcp"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
</details>
|
|
127
|
+
|
|
128
|
+
<details>
|
|
129
|
+
<summary><b>Install in Windsurf</b></summary>
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"mcpServers": {
|
|
134
|
+
"pabal-web-mcp": {
|
|
135
|
+
"command": "npx",
|
|
136
|
+
"args": ["-y", "pabal-web-mcp"]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Or if installed globally:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"mcpServers": {
|
|
147
|
+
"pabal-web-mcp": {
|
|
148
|
+
"command": "pabal-web-mcp"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
</details>
|
|
155
|
+
|
|
156
|
+
## MCP Server
|
|
157
|
+
|
|
158
|
+
This package includes an MCP server for managing ASO data through Claude or other MCP-compatible clients.
|
|
159
|
+
|
|
160
|
+
### Available Tools
|
|
161
|
+
|
|
162
|
+
| Tool | Description |
|
|
163
|
+
| ---------------- | -------------------------------------------------- |
|
|
164
|
+
| `aso-to-public` | Convert ASO data to public config format |
|
|
165
|
+
| `public-to-aso` | Convert public config to ASO data format |
|
|
166
|
+
| `improve-public` | Improve product locale content with AI suggestions |
|
|
167
|
+
| `init-project` | Initialize a new product project structure |
|
|
168
|
+
|
|
11
169
|
## Usage
|
|
12
170
|
|
|
13
171
|
### Importing Types
|
|
@@ -60,38 +218,6 @@ console.log(asoData.appStore?.name);
|
|
|
60
218
|
console.log(asoData.googlePlay?.title);
|
|
61
219
|
```
|
|
62
220
|
|
|
63
|
-
## MCP Server
|
|
64
|
-
|
|
65
|
-
This package includes an MCP server for managing ASO data through Claude or other MCP-compatible clients.
|
|
66
|
-
|
|
67
|
-
### Available Tools
|
|
68
|
-
|
|
69
|
-
| Tool | Description |
|
|
70
|
-
|------|-------------|
|
|
71
|
-
| `aso-to-public` | Convert ASO data to public config format |
|
|
72
|
-
| `public-to-aso` | Convert public config to ASO data format |
|
|
73
|
-
| `improve-public` | Improve product locale content with AI suggestions |
|
|
74
|
-
| `init-project` | Initialize a new product project structure |
|
|
75
|
-
|
|
76
|
-
### Running the MCP Server
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
npx pabal-web-mcp
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Or add to your Claude Desktop config:
|
|
83
|
-
|
|
84
|
-
```json
|
|
85
|
-
{
|
|
86
|
-
"mcpServers": {
|
|
87
|
-
"pabal-web-mcp": {
|
|
88
|
-
"command": "npx",
|
|
89
|
-
"args": ["pabal-web-mcp"]
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
221
|
## Types Reference
|
|
96
222
|
|
|
97
223
|
### ASO Types
|
|
@@ -116,17 +242,17 @@ Or add to your Claude Desktop config:
|
|
|
116
242
|
## Supported Locales
|
|
117
243
|
|
|
118
244
|
| Unified | App Store | Google Play |
|
|
119
|
-
|
|
120
|
-
| en-US
|
|
121
|
-
| ko-KR
|
|
122
|
-
| ja-JP
|
|
123
|
-
| zh-CN
|
|
124
|
-
| zh-TW
|
|
125
|
-
| de-DE
|
|
126
|
-
| fr-FR
|
|
127
|
-
| es-ES
|
|
128
|
-
| pt-BR
|
|
129
|
-
| ...
|
|
245
|
+
| ------- | --------- | ----------- |
|
|
246
|
+
| en-US | en-US | en-US |
|
|
247
|
+
| ko-KR | ko | ko-KR |
|
|
248
|
+
| ja-JP | ja | ja-JP |
|
|
249
|
+
| zh-CN | zh-Hans | zh-CN |
|
|
250
|
+
| zh-TW | zh-Hant | zh-TW |
|
|
251
|
+
| de-DE | de-DE | de-DE |
|
|
252
|
+
| fr-FR | fr-FR | fr-FR |
|
|
253
|
+
| es-ES | es-ES | es-ES |
|
|
254
|
+
| pt-BR | pt-BR | pt-BR |
|
|
255
|
+
| ... | ... | ... |
|
|
130
256
|
|
|
131
257
|
## License
|
|
132
258
|
|
package/dist/bin/mcp-server.js
CHANGED
|
@@ -2,14 +2,20 @@
|
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_LOCALE,
|
|
4
4
|
appStoreToUnified,
|
|
5
|
+
getProductsDir,
|
|
6
|
+
getPublicDir,
|
|
7
|
+
getPullDataDir,
|
|
8
|
+
getPushDataDir,
|
|
5
9
|
googlePlayToUnified,
|
|
10
|
+
isAppStoreLocale,
|
|
6
11
|
isAppStoreMultilingual,
|
|
12
|
+
isGooglePlayLocale,
|
|
7
13
|
isGooglePlayMultilingual,
|
|
8
14
|
loadAsoFromConfig,
|
|
9
15
|
saveAsoToAsoDir,
|
|
10
16
|
unifiedToAppStore,
|
|
11
17
|
unifiedToGooglePlay
|
|
12
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-AM6RGDD4.js";
|
|
13
19
|
|
|
14
20
|
// src/bin/mcp-server.ts
|
|
15
21
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -28,10 +34,9 @@ import fs from "fs";
|
|
|
28
34
|
import path from "path";
|
|
29
35
|
function loadPullData(slug) {
|
|
30
36
|
const asoData = {};
|
|
37
|
+
const pullDataDir = getPullDataDir();
|
|
31
38
|
const googlePlayPath = path.join(
|
|
32
|
-
|
|
33
|
-
".aso",
|
|
34
|
-
"pullData",
|
|
39
|
+
pullDataDir,
|
|
35
40
|
"products",
|
|
36
41
|
slug,
|
|
37
42
|
"store",
|
|
@@ -50,9 +55,7 @@ function loadPullData(slug) {
|
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
const appStorePath = path.join(
|
|
53
|
-
|
|
54
|
-
".aso",
|
|
55
|
-
"pullData",
|
|
58
|
+
pullDataDir,
|
|
56
59
|
"products",
|
|
57
60
|
slug,
|
|
58
61
|
"store",
|
|
@@ -202,14 +205,14 @@ var jsonSchema = toJsonSchema(asoToPublicInputSchema, {
|
|
|
202
205
|
var inputSchema = jsonSchema.definitions?.AsoToPublicInput || jsonSchema;
|
|
203
206
|
var asoToPublicTool = {
|
|
204
207
|
name: "aso-to-public",
|
|
205
|
-
description: `Converts ASO data from
|
|
208
|
+
description: `Converts ASO data from pullData to public/products/[slug]/ structure.
|
|
206
209
|
|
|
207
210
|
**IMPORTANT:** The 'slug' parameter is REQUIRED. If the user does not provide a slug, you MUST ask them to provide it. This tool processes only ONE product at a time.
|
|
208
211
|
|
|
209
212
|
This tool:
|
|
210
|
-
1. Loads ASO data from
|
|
213
|
+
1. Loads ASO data from pullData/products/[slug]/store/ (path from ~/.config/pabal-mcp/config.json dataDir)
|
|
211
214
|
2. Generates per-locale conversion prompts to map fullDescription into structured locale JSON (template intro/outro + landing features/screenshots captions)
|
|
212
|
-
3. Next steps (manual): paste converted JSON into public/products/[slug]/locales/[locale].json and copy screenshots from
|
|
215
|
+
3. Next steps (manual): paste converted JSON into public/products/[slug]/locales/[locale].json and copy screenshots from pullData if needed
|
|
213
216
|
|
|
214
217
|
The conversion from unstructured to structured format is performed by Claude based on the conversion prompt.`,
|
|
215
218
|
inputSchema
|
|
@@ -218,7 +221,7 @@ async function handleAsoToPublic(input) {
|
|
|
218
221
|
const { slug } = input;
|
|
219
222
|
const asoData = loadPullData(slug);
|
|
220
223
|
if (!asoData.googlePlay && !asoData.appStore) {
|
|
221
|
-
throw new Error(`No ASO data found in
|
|
224
|
+
throw new Error(`No ASO data found in pullData for ${slug}`);
|
|
222
225
|
}
|
|
223
226
|
const mergedDataByLocale = /* @__PURE__ */ new Map();
|
|
224
227
|
if (asoData.googlePlay) {
|
|
@@ -270,7 +273,8 @@ async function handleAsoToPublic(input) {
|
|
|
270
273
|
).find((loc) => googlePlayToUnified(loc) === unifiedLocale);
|
|
271
274
|
if (googlePlayLocale) {
|
|
272
275
|
if (!screenshotPaths) screenshotPaths = {};
|
|
273
|
-
|
|
276
|
+
const pullDataDir2 = getPullDataDir();
|
|
277
|
+
screenshotPaths.googlePlay = `${pullDataDir2}/products/${slug}/store/google-play/screenshots/${googlePlayLocale}/`;
|
|
274
278
|
}
|
|
275
279
|
}
|
|
276
280
|
if (mergedData.appStore) {
|
|
@@ -279,7 +283,8 @@ async function handleAsoToPublic(input) {
|
|
|
279
283
|
).find((loc) => appStoreToUnified(loc) === unifiedLocale);
|
|
280
284
|
if (appStoreLocale) {
|
|
281
285
|
if (!screenshotPaths) screenshotPaths = {};
|
|
282
|
-
|
|
286
|
+
const pullDataDir2 = getPullDataDir();
|
|
287
|
+
screenshotPaths.appStore = `${pullDataDir2}/products/${slug}/store/app-store/screenshots/${appStoreLocale}/`;
|
|
283
288
|
}
|
|
284
289
|
}
|
|
285
290
|
const prompt = generateConversionPrompt(
|
|
@@ -303,7 +308,8 @@ async function handleAsoToPublic(input) {
|
|
|
303
308
|
--- ${unifiedLocale} (${sourcesText}) ---
|
|
304
309
|
${prompt}`);
|
|
305
310
|
}
|
|
306
|
-
|
|
311
|
+
const pullDataDir = getPullDataDir();
|
|
312
|
+
let responseText = `Converting ASO data from pullData to public/products/${slug}/ structure.
|
|
307
313
|
|
|
308
314
|
`;
|
|
309
315
|
responseText += `Found ${conversionTasks.length} unified locale(s) to convert.
|
|
@@ -322,7 +328,7 @@ Next steps (manual):
|
|
|
322
328
|
`;
|
|
323
329
|
responseText += ` Example: public/products/${slug}/locales/ar.json (not ar-SA.json)
|
|
324
330
|
`;
|
|
325
|
-
responseText += `2. Copy screenshots from
|
|
331
|
+
responseText += `2. Copy screenshots from ${pullDataDir}/products/${slug}/store/ to public/products/${slug}/screenshots/
|
|
326
332
|
`;
|
|
327
333
|
return {
|
|
328
334
|
content: [
|
|
@@ -413,7 +419,7 @@ function prepareAsoDataForPush(slug, configData) {
|
|
|
413
419
|
|
|
414
420
|
// src/tools/utils/public-to-aso/save-raw-aso-data.util.ts
|
|
415
421
|
function saveRawAsoData(slug, asoData, options) {
|
|
416
|
-
const rootDir = options?.rootDir ??
|
|
422
|
+
const rootDir = options?.rootDir ?? getPushDataDir();
|
|
417
423
|
saveAsoToAsoDir(slug, asoData, { rootDir });
|
|
418
424
|
const localeCounts = {};
|
|
419
425
|
if (asoData.googlePlay) {
|
|
@@ -475,8 +481,9 @@ function isLocalAssetPath(assetPath) {
|
|
|
475
481
|
import fs3 from "fs";
|
|
476
482
|
import path3 from "path";
|
|
477
483
|
function copyLocalAssetToAsoDir(assetPath, outputPath) {
|
|
484
|
+
const publicDir = getPublicDir();
|
|
478
485
|
const trimmedPath = assetPath.replace(/^\.\//, "").replace(/^public\//, "").replace(/^\/+/, "");
|
|
479
|
-
const sourcePath = path3.join(
|
|
486
|
+
const sourcePath = path3.join(publicDir, trimmedPath);
|
|
480
487
|
if (!fs3.existsSync(sourcePath)) {
|
|
481
488
|
console.warn(`\u26A0\uFE0F Local image not found: ${sourcePath}`);
|
|
482
489
|
return false;
|
|
@@ -501,6 +508,7 @@ function convertToMultilingual(data, locale) {
|
|
|
501
508
|
}
|
|
502
509
|
|
|
503
510
|
// src/tools/public-to-aso.ts
|
|
511
|
+
import fs4 from "fs";
|
|
504
512
|
var FIELD_LIMITS_DOC_PATH = "docs/aso/ASO_FIELD_LIMITS.md";
|
|
505
513
|
var toJsonSchema2 = zodToJsonSchema2;
|
|
506
514
|
var publicToAsoInputSchema = z2.object({
|
|
@@ -514,14 +522,8 @@ var jsonSchema2 = toJsonSchema2(publicToAsoInputSchema, {
|
|
|
514
522
|
});
|
|
515
523
|
var inputSchema2 = jsonSchema2.definitions?.PublicToAsoInput || jsonSchema2;
|
|
516
524
|
async function downloadScreenshotsToAsoDir(slug, asoData, options) {
|
|
517
|
-
const rootDir = options?.rootDir ??
|
|
518
|
-
const productStoreRoot = path4.join(
|
|
519
|
-
process.cwd(),
|
|
520
|
-
rootDir,
|
|
521
|
-
"products",
|
|
522
|
-
slug,
|
|
523
|
-
"store"
|
|
524
|
-
);
|
|
525
|
+
const rootDir = options?.rootDir ?? getPushDataDir();
|
|
526
|
+
const productStoreRoot = path4.join(rootDir, "products", slug, "store");
|
|
525
527
|
if (asoData.googlePlay) {
|
|
526
528
|
let googlePlayData = asoData.googlePlay;
|
|
527
529
|
if (!isGooglePlayMultilingual(googlePlayData)) {
|
|
@@ -654,15 +656,15 @@ async function downloadScreenshotsToAsoDir(slug, asoData, options) {
|
|
|
654
656
|
}
|
|
655
657
|
var publicToAsoTool = {
|
|
656
658
|
name: "public-to-aso",
|
|
657
|
-
description: `Prepares ASO data from public/products/[slug]/ to
|
|
659
|
+
description: `Prepares ASO data from public/products/[slug]/ to pushData format.
|
|
658
660
|
|
|
659
661
|
**IMPORTANT:** The 'slug' parameter is REQUIRED. If the user does not provide a slug, you MUST ask them to provide it. This tool processes only ONE product at a time.
|
|
660
662
|
|
|
661
663
|
This tool:
|
|
662
664
|
1. Loads ASO data from public/products/[slug]/config.json + locales/
|
|
663
665
|
2. Converts to store-compatible format (removes screenshots from metadata, sets contactWebsite/marketingUrl)
|
|
664
|
-
3. Saves metadata to
|
|
665
|
-
4. Copies/downloads screenshots to
|
|
666
|
+
3. Saves metadata to pushData/products/[slug]/store/ (path from ~/.config/pabal-mcp/config.json dataDir)
|
|
667
|
+
4. Copies/downloads screenshots to pushData/products/[slug]/store/screenshots/
|
|
666
668
|
|
|
667
669
|
Before running, review ${FIELD_LIMITS_DOC_PATH} for per-store limits. This prepares data for pushing to stores without actually uploading.`,
|
|
668
670
|
inputSchema: inputSchema2
|
|
@@ -671,15 +673,94 @@ async function handlePublicToAso(input) {
|
|
|
671
673
|
const { slug, dryRun } = input;
|
|
672
674
|
const configData = loadAsoFromConfig(slug);
|
|
673
675
|
if (!configData.googlePlay && !configData.appStore) {
|
|
674
|
-
|
|
676
|
+
const productsDir = getProductsDir();
|
|
677
|
+
const configPath = path4.join(productsDir, slug, "config.json");
|
|
678
|
+
const localesDir = path4.join(productsDir, slug, "locales");
|
|
679
|
+
const errors = [];
|
|
680
|
+
if (!fs4.existsSync(configPath)) {
|
|
681
|
+
errors.push(`- config.json not found at ${configPath}`);
|
|
682
|
+
} else {
|
|
683
|
+
try {
|
|
684
|
+
const config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
685
|
+
if (!config.packageName && !config.bundleId) {
|
|
686
|
+
errors.push(
|
|
687
|
+
`- config.json exists but missing both packageName and bundleId`
|
|
688
|
+
);
|
|
689
|
+
} else {
|
|
690
|
+
if (config.packageName) {
|
|
691
|
+
errors.push(`- packageName found: ${config.packageName}`);
|
|
692
|
+
}
|
|
693
|
+
if (config.bundleId) {
|
|
694
|
+
errors.push(`- bundleId found: ${config.bundleId}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
} catch (e) {
|
|
698
|
+
errors.push(
|
|
699
|
+
`- Failed to parse config.json: ${e instanceof Error ? e.message : String(e)}`
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (!fs4.existsSync(localesDir)) {
|
|
704
|
+
errors.push(`- locales directory not found at ${localesDir}`);
|
|
705
|
+
} else {
|
|
706
|
+
try {
|
|
707
|
+
const localeFiles = fs4.readdirSync(localesDir).filter((f) => f.endsWith(".json"));
|
|
708
|
+
if (localeFiles.length === 0) {
|
|
709
|
+
errors.push(`- locales directory exists but no .json files found`);
|
|
710
|
+
} else {
|
|
711
|
+
errors.push(
|
|
712
|
+
`- Found ${localeFiles.length} locale file(s): ${localeFiles.join(
|
|
713
|
+
", "
|
|
714
|
+
)}`
|
|
715
|
+
);
|
|
716
|
+
const validLocales = [];
|
|
717
|
+
const invalidLocales = [];
|
|
718
|
+
for (const file of localeFiles) {
|
|
719
|
+
const localeCode = file.replace(".json", "");
|
|
720
|
+
if (isGooglePlayLocale(localeCode) || isAppStoreLocale(localeCode)) {
|
|
721
|
+
validLocales.push(localeCode);
|
|
722
|
+
} else {
|
|
723
|
+
invalidLocales.push(localeCode);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (validLocales.length > 0) {
|
|
727
|
+
errors.push(`- Valid locales: ${validLocales.join(", ")}`);
|
|
728
|
+
}
|
|
729
|
+
if (invalidLocales.length > 0) {
|
|
730
|
+
errors.push(
|
|
731
|
+
`- Invalid locales (not supported by Google Play or App Store): ${invalidLocales.join(
|
|
732
|
+
", "
|
|
733
|
+
)}`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
} catch (e) {
|
|
738
|
+
errors.push(
|
|
739
|
+
`- Failed to read locales directory: ${e instanceof Error ? e.message : String(e)}`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
throw new Error(
|
|
744
|
+
`No ASO data found in config.json + locales/ for ${slug}
|
|
745
|
+
|
|
746
|
+
Diagnostics:
|
|
747
|
+
${errors.join("\n")}
|
|
748
|
+
|
|
749
|
+
Possible causes:
|
|
750
|
+
1. config.json is missing packageName (for Google Play) or bundleId (for App Store)
|
|
751
|
+
2. locales/ directory is missing or empty
|
|
752
|
+
3. Locale files exist but don't match supported Google Play/App Store locales
|
|
753
|
+
4. Locale files don't contain valid ASO data`
|
|
754
|
+
);
|
|
675
755
|
}
|
|
676
756
|
const storeData = prepareAsoDataForPush(slug, configData);
|
|
757
|
+
const pushDataRoot = getPushDataDir();
|
|
677
758
|
if (dryRun) {
|
|
678
759
|
return {
|
|
679
760
|
content: [
|
|
680
761
|
{
|
|
681
762
|
type: "text",
|
|
682
|
-
text: `Preview mode - Data that would be saved to
|
|
763
|
+
text: `Preview mode - Data that would be saved to ${pushDataRoot}:
|
|
683
764
|
|
|
684
765
|
${JSON.stringify(
|
|
685
766
|
storeData,
|
|
@@ -690,7 +771,6 @@ ${JSON.stringify(
|
|
|
690
771
|
]
|
|
691
772
|
};
|
|
692
773
|
}
|
|
693
|
-
const pushDataRoot = ".aso/pushData";
|
|
694
774
|
saveRawAsoData(slug, storeData, { rootDir: pushDataRoot });
|
|
695
775
|
await downloadScreenshotsToAsoDir(slug, configData, {
|
|
696
776
|
rootDir: pushDataRoot
|
|
@@ -708,7 +788,7 @@ ${JSON.stringify(
|
|
|
708
788
|
const locales = isAppStoreMultilingual(appStoreData) ? appStoreData.locales : { [appStoreData.locale || DEFAULT_LOCALE]: appStoreData };
|
|
709
789
|
localeCounts.appStore = Object.keys(locales).length;
|
|
710
790
|
}
|
|
711
|
-
let responseText = `\u2705 ${slug}
|
|
791
|
+
let responseText = `\u2705 ${slug} pushData files prepared from config.json + locales/ (images + metadata synced)
|
|
712
792
|
|
|
713
793
|
`;
|
|
714
794
|
if (localeCounts.googlePlay) {
|
|
@@ -738,29 +818,30 @@ import { z as z3 } from "zod";
|
|
|
738
818
|
import { zodToJsonSchema as zodToJsonSchema3 } from "zod-to-json-schema";
|
|
739
819
|
|
|
740
820
|
// src/tools/utils/improve-public/load-product-locales.util.ts
|
|
741
|
-
import
|
|
821
|
+
import fs5 from "fs";
|
|
742
822
|
import path5 from "path";
|
|
743
823
|
function loadProductLocales(slug) {
|
|
744
|
-
const
|
|
824
|
+
const productsDir = getProductsDir();
|
|
825
|
+
const productDir = path5.join(productsDir, slug);
|
|
745
826
|
const configPath = path5.join(productDir, "config.json");
|
|
746
827
|
const localesDir = path5.join(productDir, "locales");
|
|
747
828
|
let config = null;
|
|
748
|
-
if (
|
|
749
|
-
const raw =
|
|
829
|
+
if (fs5.existsSync(configPath)) {
|
|
830
|
+
const raw = fs5.readFileSync(configPath, "utf-8");
|
|
750
831
|
config = JSON.parse(raw);
|
|
751
832
|
}
|
|
752
|
-
if (!
|
|
833
|
+
if (!fs5.existsSync(localesDir)) {
|
|
753
834
|
throw new Error(`No locales directory found for ${slug}`);
|
|
754
835
|
}
|
|
755
836
|
const locales = {};
|
|
756
|
-
const localeFiles =
|
|
837
|
+
const localeFiles = fs5.readdirSync(localesDir).filter((file) => file.endsWith(".json"));
|
|
757
838
|
if (localeFiles.length === 0) {
|
|
758
839
|
throw new Error(`No locale files found for ${slug}`);
|
|
759
840
|
}
|
|
760
841
|
for (const file of localeFiles) {
|
|
761
842
|
const localeCode = file.replace(".json", "");
|
|
762
843
|
const localePath = path5.join(localesDir, file);
|
|
763
|
-
const content =
|
|
844
|
+
const content = fs5.readFileSync(localePath, "utf-8");
|
|
764
845
|
locales[localeCode] = JSON.parse(content);
|
|
765
846
|
}
|
|
766
847
|
return { config, locales };
|
|
@@ -1701,17 +1782,17 @@ async function handleImprovePublic(input) {
|
|
|
1701
1782
|
}
|
|
1702
1783
|
|
|
1703
1784
|
// src/tools/init-project.ts
|
|
1704
|
-
import
|
|
1785
|
+
import fs6 from "fs";
|
|
1705
1786
|
import path6 from "path";
|
|
1706
1787
|
import { z as z4 } from "zod";
|
|
1707
1788
|
import { zodToJsonSchema as zodToJsonSchema4 } from "zod-to-json-schema";
|
|
1708
1789
|
var listSlugDirs = (dir) => {
|
|
1709
|
-
if (!
|
|
1710
|
-
return
|
|
1790
|
+
if (!fs6.existsSync(dir)) return [];
|
|
1791
|
+
return fs6.readdirSync(dir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
1711
1792
|
};
|
|
1712
1793
|
var initProjectInputSchema = z4.object({
|
|
1713
1794
|
slug: z4.string().trim().optional().describe(
|
|
1714
|
-
"Optional product slug to focus on. Defaults to all slugs in
|
|
1795
|
+
"Optional product slug to focus on. Defaults to all slugs in pullData/products/"
|
|
1715
1796
|
)
|
|
1716
1797
|
});
|
|
1717
1798
|
var jsonSchema4 = zodToJsonSchema4(initProjectInputSchema, {
|
|
@@ -1727,14 +1808,14 @@ var initProjectTool = {
|
|
|
1727
1808
|
This tool is read-only and returns a checklist. It does not call pabal-mcp directly or write files.
|
|
1728
1809
|
|
|
1729
1810
|
Steps:
|
|
1730
|
-
1) Ensure pabal-mcp 'init' ran and
|
|
1811
|
+
1) Ensure pabal-mcp 'init' ran and pullData/products/[slug]/ exists (path from ~/.config/pabal-mcp/config.json dataDir)
|
|
1731
1812
|
2) Convert pulled ASO data -> public/products/[slug]/ using pabal-web-mcp tools (aso-to-public, public-to-aso dry run)
|
|
1732
1813
|
3) Validate outputs and next actions`,
|
|
1733
1814
|
inputSchema: inputSchema4
|
|
1734
1815
|
};
|
|
1735
1816
|
async function handleInitProject(input) {
|
|
1736
|
-
const pullDataDir = path6.join(
|
|
1737
|
-
const publicDir =
|
|
1817
|
+
const pullDataDir = path6.join(getPullDataDir(), "products");
|
|
1818
|
+
const publicDir = getProductsDir();
|
|
1738
1819
|
const pullDataSlugs = listSlugDirs(pullDataDir);
|
|
1739
1820
|
const publicSlugs = listSlugDirs(publicDir);
|
|
1740
1821
|
const targetSlugs = input.slug?.length && input.slug.trim().length > 0 ? [input.slug.trim()] : pullDataSlugs.length > 0 ? pullDataSlugs : publicSlugs;
|
|
@@ -1752,7 +1833,7 @@ async function handleInitProject(input) {
|
|
|
1752
1833
|
lines.push("");
|
|
1753
1834
|
if (targetSlugs.length === 0) {
|
|
1754
1835
|
lines.push(
|
|
1755
|
-
"No products detected. Run pabal-mcp 'init' for your slug(s) to populate
|
|
1836
|
+
"No products detected. Run pabal-mcp 'init' for your slug(s) to populate pullData/products/, then rerun this tool."
|
|
1756
1837
|
);
|
|
1757
1838
|
return {
|
|
1758
1839
|
content: [
|
|
@@ -1790,7 +1871,7 @@ async function handleInitProject(input) {
|
|
|
1790
1871
|
lines.push("");
|
|
1791
1872
|
lines.push("Step 3: Verify and prepare for push (optional)");
|
|
1792
1873
|
lines.push(
|
|
1793
|
-
"Use pabal-web-mcp 'public-to-aso' with dryRun=true to validate structure and build
|
|
1874
|
+
"Use pabal-web-mcp 'public-to-aso' with dryRun=true to validate structure and build pushData before uploading via store tooling."
|
|
1794
1875
|
);
|
|
1795
1876
|
lines.push("");
|
|
1796
1877
|
lines.push("Notes:");
|