pabal-store-api-mcp 1.3.17 → 1.3.19

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
@@ -38,6 +38,14 @@ chmod 700 ~/.config/pabal-mcp
38
38
 
39
39
  <br>
40
40
 
41
+ ### Registered Apps
42
+
43
+ Registered apps are stored locally in `~/.config/pabal-mcp/registered-apps.json`.
44
+ It is usually created and updated by `apps-init` or `apps-add`, then used by tools such as `apps-search`, `aso-pull`, and release-note workflows.
45
+ See the app management docs for the detailed format: [English](docs/en-US/apps.md) / [한국어](docs/ko-KR/apps.md).
46
+
47
+ <br>
48
+
41
49
  ## Features
42
50
 
43
51
  - ✅ **ASO Data Sync**: Pull/push metadata from App Store and Google Play
@@ -13,6 +13,7 @@ export declare const APP_STORE_DEVICE_TYPES: {
13
13
  readonly iphone55: "APP_IPHONE_55";
14
14
  readonly iphone47: "APP_IPHONE_47";
15
15
  readonly iphone40: "APP_IPHONE_40";
16
+ readonly ipad13: "APP_IPAD_PRO_3GEN_129";
16
17
  readonly ipadPro129: "APP_IPAD_PRO_3GEN_129";
17
18
  readonly ipadPro11: "APP_IPAD_PRO_11";
18
19
  readonly ipad105: "APP_IPAD_105";
@@ -14,6 +14,7 @@ export const APP_STORE_DEVICE_TYPES = {
14
14
  iphone55: "APP_IPHONE_55",
15
15
  iphone47: "APP_IPHONE_47",
16
16
  iphone40: "APP_IPHONE_40",
17
+ ipad13: "APP_IPAD_PRO_3GEN_129",
17
18
  ipadPro129: "APP_IPAD_PRO_3GEN_129",
18
19
  ipadPro11: "APP_IPAD_PRO_11",
19
20
  ipad105: "APP_IPAD_105",
@@ -253,6 +253,8 @@ export class AppStoreService {
253
253
  const hasScreenshotsInJson = localeData?.screenshots &&
254
254
  ((localeData.screenshots.iphone65 &&
255
255
  localeData.screenshots.iphone65.length > 0) ||
256
+ (localeData.screenshots.ipad13 &&
257
+ localeData.screenshots.ipad13.length > 0) ||
256
258
  (localeData.screenshots.ipadPro129 &&
257
259
  localeData.screenshots.ipadPro129.length > 0));
258
260
  let screenshotsToUpload = [];
@@ -270,13 +272,13 @@ export class AppStoreService {
270
272
  });
271
273
  }
272
274
  }
273
- // Map ipadPro129 screenshots
274
- if (relativePaths.ipadPro129 &&
275
- relativePaths.ipadPro129.length > 0) {
276
- for (const relPath of relativePaths.ipadPro129) {
275
+ // Map 13" iPad screenshots. ipadPro129 is kept as a legacy alias.
276
+ const ipad13Paths = relativePaths.ipad13 ?? relativePaths.ipadPro129 ?? [];
277
+ if (ipad13Paths.length > 0) {
278
+ for (const relPath of ipad13Paths) {
277
279
  screenshotsToUpload.push({
278
280
  path: `${screenshotsBaseDir}/${relPath}`,
279
- displayType: APP_STORE_DEVICE_TYPES.ipadPro129,
281
+ displayType: APP_STORE_DEVICE_TYPES.ipad13,
280
282
  filename: relPath.split("/").pop() || relPath,
281
283
  });
282
284
  }
package/dist/src/index.js CHANGED
@@ -210,7 +210,7 @@ registerToolWithInfo("aso-push", {
210
210
  .int()
211
211
  .positive()
212
212
  .optional()
213
- .describe("Google Play image locale batch size. Omit to upload all target locales in one edit commit"),
213
+ .describe("Google Play image locale batch size (default: 1 when uploadImages=true, replacing screenshots per locale while preserving videos)"),
214
214
  dryRun: z
215
215
  .boolean()
216
216
  .optional()
@@ -49,6 +49,7 @@ export interface AppStoreScreenshots {
49
49
  iphone55?: string[];
50
50
  iphone47?: string[];
51
51
  iphone40?: string[];
52
+ ipad13?: string[];
52
53
  ipadPro129?: string[];
53
54
  ipadPro11?: string[];
54
55
  ipad105?: string[];
@@ -803,19 +803,29 @@ export class AppStoreClient {
803
803
  }
804
804
  const screenshotsResponse = await this.listScreenshots(screenshotSetId);
805
805
  const existingScreenshots = (screenshotsResponse.data || []).filter((screenshot) => screenshot.type === "appScreenshots");
806
+ const incompleteScreenshots = existingScreenshots.filter((screenshot) => {
807
+ const deliveryState = screenshot.attributes?.assetDeliveryState;
808
+ return deliveryState?.state && deliveryState.state !== "COMPLETE";
809
+ });
810
+ const completeScreenshots = existingScreenshots.filter((screenshot) => !incompleteScreenshots.includes(screenshot));
806
811
  const nonScreenshotCount = (screenshotsResponse.data || []).length - existingScreenshots.length;
807
812
  if (nonScreenshotCount > 0) {
808
813
  console.error(`[AppStore] Ignoring ${nonScreenshotCount} non-screenshot asset(s) in screenshot set`);
809
814
  }
810
- const slotsToFree = Math.max(0, existingScreenshots.length +
815
+ const deletedIncomplete = await this.deleteScreenshots(incompleteScreenshots);
816
+ if (deletedIncomplete > 0) {
817
+ console.error(`[AppStore] Deleted ${deletedIncomplete} incomplete screenshot upload(s)`);
818
+ }
819
+ const slotsToFree = Math.max(0, completeScreenshots.length +
811
820
  incomingCount -
812
821
  APP_STORE_SCREENSHOT_SET_MAX_COUNT);
813
822
  // Preserve the first screenshots as long as possible. If the upload fails
814
823
  // after freeing slots, users are more likely to still see the primary
815
824
  // screenshots rather than the tail of the old set.
816
- const screenshotsToDeleteBeforeUpload = slotsToFree > 0 ? existingScreenshots.slice(-slotsToFree) : [];
817
- const screenshotsToDeleteAfterUpload = existingScreenshots.slice(0, existingScreenshots.length - slotsToFree);
818
- const deletedBeforeUpload = await this.deleteScreenshots(screenshotsToDeleteBeforeUpload);
825
+ const screenshotsToDeleteBeforeUpload = slotsToFree > 0 ? completeScreenshots.slice(-slotsToFree) : [];
826
+ const screenshotsToDeleteAfterUpload = completeScreenshots.slice(0, completeScreenshots.length - slotsToFree);
827
+ const deletedBeforeUpload = deletedIncomplete +
828
+ (await this.deleteScreenshots(screenshotsToDeleteBeforeUpload));
819
829
  if (deletedBeforeUpload > 0) {
820
830
  console.error(`[AppStore] Deleted ${deletedBeforeUpload} existing screenshots to free upload slots`);
821
831
  }
@@ -6,6 +6,7 @@ import { AppStoreService } from "../../core/services/app-store-service.js";
6
6
  import { GooglePlayService } from "../../core/services/google-play-service.js";
7
7
  import { formatPushResult } from "../../core/helpers/formatters.js";
8
8
  const DEFAULT_IMAGE_UPLOAD_TIMEOUT_MS = 10 * 60 * 1000;
9
+ const DEFAULT_IMAGE_LOCALE_BATCH_SIZE = 1;
9
10
  const appResolutionService = new AppResolutionService();
10
11
  const appStoreService = new AppStoreService();
11
12
  const googlePlayService = new GooglePlayService();
@@ -13,6 +14,8 @@ export async function handleAsoPush(options) {
13
14
  const { store = "both", uploadImages = false, locales, dryRun = false, } = options;
14
15
  const imageUploadTimeoutMs = options.imageUploadTimeoutMs ??
15
16
  (uploadImages ? DEFAULT_IMAGE_UPLOAD_TIMEOUT_MS : undefined);
17
+ const imageLocaleBatchSize = options.imageLocaleBatchSize ??
18
+ (uploadImages ? DEFAULT_IMAGE_LOCALE_BATCH_SIZE : undefined);
16
19
  const resolved = appResolutionService.resolve({
17
20
  slug: options.app,
18
21
  packageName: options.packageName,
@@ -37,13 +40,16 @@ export async function handleAsoPush(options) {
37
40
  if (bundleId)
38
41
  console.error(`[MCP] Bundle ID: ${bundleId}`);
39
42
  console.error(`[MCP] Upload Images: ${uploadImages ? "Yes" : "No"}`);
43
+ if (uploadImages) {
44
+ console.error(`[MCP] Image Upload Mode: overwrite screenshots only (videos preserved)`);
45
+ }
40
46
  if (locales?.length)
41
47
  console.error(`[MCP] Locales: ${locales.join(", ")}`);
42
48
  if (imageUploadTimeoutMs) {
43
49
  console.error(`[MCP] Image Upload Timeout: ${imageUploadTimeoutMs}ms`);
44
50
  }
45
- if (options.imageLocaleBatchSize) {
46
- console.error(`[MCP] Image Locale Batch Size: ${options.imageLocaleBatchSize}`);
51
+ if (imageLocaleBatchSize) {
52
+ console.error(`[MCP] Image Locale Batch Size: ${imageLocaleBatchSize}`);
47
53
  }
48
54
  console.error(`[MCP] Mode: ${dryRun ? "Dry run" : "Actual push"}`);
49
55
  let config;
@@ -129,7 +135,9 @@ export async function handleAsoPush(options) {
129
135
  content: [
130
136
  {
131
137
  type: "text",
132
- text: `📋 Dry run - Data that would be pushed:\n${JSON.stringify(configData, null, 2)}`,
138
+ text: `📋 Dry run - Data that would be pushed:\n${JSON.stringify(configData, null, 2)}${uploadImages
139
+ ? "\n\nImage upload mode: overwrite screenshots only; videos/app previews are preserved."
140
+ : ""}`,
133
141
  },
134
142
  ],
135
143
  };
@@ -149,7 +157,7 @@ export async function handleAsoPush(options) {
149
157
  uploadImages,
150
158
  locales,
151
159
  imageUploadTimeoutMs,
152
- imageLocaleBatchSize: options.imageLocaleBatchSize,
160
+ imageLocaleBatchSize,
153
161
  slug,
154
162
  });
155
163
  results.push(formatPushResult("Google Play", result));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-store-api-mcp",
3
- "version": "1.3.17",
3
+ "version": "1.3.19",
4
4
  "description": "MCP server for App Store / Play Store ASO workflows",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",