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 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 | en-US | en-US |
121
- | ko-KR | ko | ko-KR |
122
- | ja-JP | ja | ja-JP |
123
- | zh-CN | zh-Hans | zh-CN |
124
- | zh-TW | zh-Hant | zh-TW |
125
- | de-DE | de-DE | de-DE |
126
- | fr-FR | fr-FR | fr-FR |
127
- | es-ES | es-ES | es-ES |
128
- | pt-BR | pt-BR | 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
 
@@ -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-YJWGBO7W.js";
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
- process.cwd(),
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
- process.cwd(),
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 .aso/pullData to public/products/[slug]/ structure.
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 .aso/pullData/products/[slug]/store/
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 .aso/pullData if needed
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 .aso/pullData for ${slug}`);
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
- screenshotPaths.googlePlay = `.aso/pullData/products/${slug}/store/google-play/screenshots/${googlePlayLocale}/`;
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
- screenshotPaths.appStore = `.aso/pullData/products/${slug}/store/app-store/screenshots/${appStoreLocale}/`;
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
- let responseText = `Converting ASO data from .aso/pullData to public/products/${slug}/ structure.
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 .aso/pullData/products/${slug}/store/ to public/products/${slug}/screenshots/
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 ?? ".aso/pushData";
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(process.cwd(), "public", trimmedPath);
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 ?? ".aso/pushData";
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 .aso/pushData format.
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 .aso/pushData/products/[slug]/store/
665
- 4. Copies/downloads screenshots to .aso/pushData/products/[slug]/store/screenshots/
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
- throw new Error(`No ASO data found in config.json + locales/ for ${slug}`);
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 .aso/pushData:
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} .aso/pushData files prepared from config.json + locales/ (images + metadata synced)
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 fs4 from "fs";
821
+ import fs5 from "fs";
742
822
  import path5 from "path";
743
823
  function loadProductLocales(slug) {
744
- const productDir = path5.join(process.cwd(), "public", "products", slug);
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 (fs4.existsSync(configPath)) {
749
- const raw = fs4.readFileSync(configPath, "utf-8");
829
+ if (fs5.existsSync(configPath)) {
830
+ const raw = fs5.readFileSync(configPath, "utf-8");
750
831
  config = JSON.parse(raw);
751
832
  }
752
- if (!fs4.existsSync(localesDir)) {
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 = fs4.readdirSync(localesDir).filter((file) => file.endsWith(".json"));
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 = fs4.readFileSync(localePath, "utf-8");
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 fs5 from "fs";
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 (!fs5.existsSync(dir)) return [];
1710
- return fs5.readdirSync(dir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
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 .aso/pullData/products/"
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 .aso/pullData/products/[slug]/ exists
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(process.cwd(), ".aso", "pullData", "products");
1737
- const publicDir = path6.join(process.cwd(), "public", "products");
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 .aso/pullData/products/, then rerun this tool."
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 .aso/pushData before uploading via store tooling."
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:");