pabal-store-api-mcp 1.1.0

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.
Files changed (111) hide show
  1. package/README.md +95 -0
  2. package/bin/pabal-mcp.js +6 -0
  3. package/dist/src/core/clients/app-store-factory.d.ts +29 -0
  4. package/dist/src/core/clients/app-store-factory.js +72 -0
  5. package/dist/src/core/clients/client-factory-helpers.d.ts +7 -0
  6. package/dist/src/core/clients/client-factory-helpers.js +10 -0
  7. package/dist/src/core/clients/google-play-factory.d.ts +29 -0
  8. package/dist/src/core/clients/google-play-factory.js +72 -0
  9. package/dist/src/core/clients/types.d.ts +8 -0
  10. package/dist/src/core/clients/types.js +1 -0
  11. package/dist/src/core/helpers/formatters.d.ts +3 -0
  12. package/dist/src/core/helpers/formatters.js +38 -0
  13. package/dist/src/core/helpers/registration.d.ts +21 -0
  14. package/dist/src/core/helpers/registration.js +21 -0
  15. package/dist/src/core/helpers/translate-release-notes.d.ts +46 -0
  16. package/dist/src/core/helpers/translate-release-notes.js +87 -0
  17. package/dist/src/core/services/app-resolution-service.d.ts +14 -0
  18. package/dist/src/core/services/app-resolution-service.js +35 -0
  19. package/dist/src/core/services/app-store-service.d.ts +41 -0
  20. package/dist/src/core/services/app-store-service.js +266 -0
  21. package/dist/src/core/services/google-play-service.d.ts +36 -0
  22. package/dist/src/core/services/google-play-service.js +203 -0
  23. package/dist/src/core/services/service-helpers.d.ts +15 -0
  24. package/dist/src/core/services/service-helpers.js +31 -0
  25. package/dist/src/core/services/types.d.ts +81 -0
  26. package/dist/src/core/services/types.js +1 -0
  27. package/dist/src/core/workflows/version-info.d.ts +29 -0
  28. package/dist/src/core/workflows/version-info.js +100 -0
  29. package/dist/src/index.d.ts +10 -0
  30. package/dist/src/index.js +279 -0
  31. package/dist/src/packages/common/errors/app-error.d.ts +39 -0
  32. package/dist/src/packages/common/errors/app-error.js +134 -0
  33. package/dist/src/packages/common/errors/error-codes.d.ts +63 -0
  34. package/dist/src/packages/common/errors/error-codes.js +71 -0
  35. package/dist/src/packages/common/errors/status-codes.d.ts +10 -0
  36. package/dist/src/packages/common/errors/status-codes.js +9 -0
  37. package/dist/src/packages/configs/aso-config/constants.d.ts +14 -0
  38. package/dist/src/packages/configs/aso-config/constants.js +102 -0
  39. package/dist/src/packages/configs/aso-config/locale-guards.d.ts +3 -0
  40. package/dist/src/packages/configs/aso-config/locale-guards.js +7 -0
  41. package/dist/src/packages/configs/aso-config/store.d.ts +11 -0
  42. package/dist/src/packages/configs/aso-config/store.js +11 -0
  43. package/dist/src/packages/configs/aso-config/types.d.ts +98 -0
  44. package/dist/src/packages/configs/aso-config/types.js +2 -0
  45. package/dist/src/packages/configs/aso-config/utils.d.ts +43 -0
  46. package/dist/src/packages/configs/aso-config/utils.js +223 -0
  47. package/dist/src/packages/configs/secrets-config/config.d.ts +12 -0
  48. package/dist/src/packages/configs/secrets-config/config.js +187 -0
  49. package/dist/src/packages/configs/secrets-config/constants.d.ts +1 -0
  50. package/dist/src/packages/configs/secrets-config/constants.js +1 -0
  51. package/dist/src/packages/configs/secrets-config/errors.d.ts +9 -0
  52. package/dist/src/packages/configs/secrets-config/errors.js +15 -0
  53. package/dist/src/packages/configs/secrets-config/registered-apps.d.ts +52 -0
  54. package/dist/src/packages/configs/secrets-config/registered-apps.js +108 -0
  55. package/dist/src/packages/configs/secrets-config/schemas.d.ts +21 -0
  56. package/dist/src/packages/configs/secrets-config/schemas.js +9 -0
  57. package/dist/src/packages/configs/secrets-config/types.d.ts +8 -0
  58. package/dist/src/packages/configs/secrets-config/types.js +1 -0
  59. package/dist/src/packages/stores/app-store/api-converters.d.ts +26 -0
  60. package/dist/src/packages/stores/app-store/api-converters.js +131 -0
  61. package/dist/src/packages/stores/app-store/api-endpoints.d.ts +33 -0
  62. package/dist/src/packages/stores/app-store/api-endpoints.js +157 -0
  63. package/dist/src/packages/stores/app-store/auth.d.ts +12 -0
  64. package/dist/src/packages/stores/app-store/auth.js +36 -0
  65. package/dist/src/packages/stores/app-store/client.d.ts +78 -0
  66. package/dist/src/packages/stores/app-store/client.js +637 -0
  67. package/dist/src/packages/stores/app-store/constants.d.ts +11 -0
  68. package/dist/src/packages/stores/app-store/constants.js +38 -0
  69. package/dist/src/packages/stores/app-store/generated-types.d.ts +118537 -0
  70. package/dist/src/packages/stores/app-store/generated-types.js +5 -0
  71. package/dist/src/packages/stores/app-store/types.d.ts +39 -0
  72. package/dist/src/packages/stores/app-store/types.js +9 -0
  73. package/dist/src/packages/stores/app-store/verify-auth.d.ts +16 -0
  74. package/dist/src/packages/stores/app-store/verify-auth.js +34 -0
  75. package/dist/src/packages/stores/play-store/api-converters.d.ts +58 -0
  76. package/dist/src/packages/stores/play-store/api-converters.js +209 -0
  77. package/dist/src/packages/stores/play-store/api-endpoints.d.ts +68 -0
  78. package/dist/src/packages/stores/play-store/api-endpoints.js +145 -0
  79. package/dist/src/packages/stores/play-store/client.d.ts +55 -0
  80. package/dist/src/packages/stores/play-store/client.js +628 -0
  81. package/dist/src/packages/stores/play-store/constants.d.ts +10 -0
  82. package/dist/src/packages/stores/play-store/constants.js +17 -0
  83. package/dist/src/packages/stores/play-store/types.d.ts +146 -0
  84. package/dist/src/packages/stores/play-store/types.js +9 -0
  85. package/dist/src/packages/stores/play-store/verify-auth.d.ts +13 -0
  86. package/dist/src/packages/stores/play-store/verify-auth.js +31 -0
  87. package/dist/src/tools/apps/add.d.ts +28 -0
  88. package/dist/src/tools/apps/add.js +307 -0
  89. package/dist/src/tools/apps/init.d.ts +58 -0
  90. package/dist/src/tools/apps/init.js +390 -0
  91. package/dist/src/tools/apps/search.d.ts +33 -0
  92. package/dist/src/tools/apps/search.js +147 -0
  93. package/dist/src/tools/aso/pull.d.ts +22 -0
  94. package/dist/src/tools/aso/pull.js +264 -0
  95. package/dist/src/tools/aso/push.d.ts +23 -0
  96. package/dist/src/tools/aso/push.js +189 -0
  97. package/dist/src/tools/auth/app-store.d.ts +9 -0
  98. package/dist/src/tools/auth/app-store.js +34 -0
  99. package/dist/src/tools/auth/check.d.ts +14 -0
  100. package/dist/src/tools/auth/check.js +50 -0
  101. package/dist/src/tools/auth/play-store.d.ts +9 -0
  102. package/dist/src/tools/auth/play-store.js +30 -0
  103. package/dist/src/tools/release/check-versions.d.ts +14 -0
  104. package/dist/src/tools/release/check-versions.js +65 -0
  105. package/dist/src/tools/release/create.d.ts +23 -0
  106. package/dist/src/tools/release/create.js +128 -0
  107. package/dist/src/tools/release/pull-notes.d.ts +22 -0
  108. package/dist/src/tools/release/pull-notes.js +151 -0
  109. package/dist/src/tools/release/update-notes.d.ts +110 -0
  110. package/dist/src/tools/release/update-notes.js +537 -0
  111. package/package.json +71 -0
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ ![Cover](public/cover.gif)
2
+
3
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=pabal-store-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInBhYmFsLXN0b3JlLWFwaS1tY3AiXX0%3D)
4
+
5
+ [![Pabal Web MCP (English)](https://img.shields.io/badge/Pabal%20Web%20MCP-English-blue)](https://pabal.quartz.best/docs/en-US/pabal-web-mcp/README) [![Pabal Web MCP (한국어)](https://img.shields.io/badge/Pabal%20Web%20MCP-한국어-green)](https://pabal.quartz.best/docs/ko-KR/pabal-web-mcp/README)
6
+
7
+ # MCP server for App Store Connect & Play Console API
8
+
9
+ Up-to-date ASO workflows exposed as MCP tools. Run it as a stdio MCP server (Claude Code, Cursor, MCP Inspector, etc.) to manage metadata, releases, and store syncs without leaving your AI client.
10
+
11
+ > [!NOTE]
12
+ > Runs 100% locally on your machine, so credentials and cached ASO data never leave your environment (store API calls are made directly from your device).
13
+
14
+ <br>
15
+
16
+ ## Quick Start
17
+
18
+ ### Install
19
+
20
+ ```bash
21
+ npm install -g pabal-store-api-mcp
22
+ ```
23
+
24
+ ### Configure
25
+
26
+ 1. Create config directory:
27
+
28
+ ```bash
29
+ mkdir -p ~/.config/pabal-store-api-mcp
30
+ chmod 700 ~/.config/pabal-store-api-mcp
31
+ ```
32
+
33
+ 2. Add your credentials to `~/.config/pabal-store-api-mcp/config.json`:
34
+ - **App Store Connect**: API key (`.p8` file) with `issuerId` and `keyId`
35
+ - **Google Play**: Service account JSON key
36
+
37
+ 3. Set up MCP client (Cursor, VS Code, Claude Code, etc.) to use `pabal-store-api-mcp`
38
+
39
+ <br>
40
+
41
+ ## Features
42
+
43
+ - ✅ **ASO Data Sync**: Pull/push metadata from App Store and Google Play
44
+ - ✅ **Release Management**: Create versions and update release notes
45
+ - ✅ **App Management**: Auto-register apps from store APIs
46
+ - ✅ **100% Local**: All operations run on your machine
47
+
48
+ <br>
49
+
50
+ ## MCP Tools
51
+
52
+ - **Authentication**: `auth-check`
53
+ - **App Management**: `apps-init`, `apps-add`, `apps-search`
54
+ - **ASO Sync**: `aso-pull`, `aso-push`
55
+ - **Release Management**: `release-check-versions`, `release-create`, `release-pull-notes`, `release-update-notes`
56
+
57
+ <br>
58
+
59
+ ## Documentation
60
+
61
+ 📖 **[Pabal Web MCP Documentation (English)](https://pabal.quartz.best/docs/en-US/pabal-web-mcp/README)**
62
+ 📖 **[Pabal Web MCP 문서 (한국어)](https://pabal.quartz.best/docs/ko-KR/pabal-web-mcp/README)**
63
+
64
+ <br>
65
+
66
+ ## Development
67
+
68
+ ```bash
69
+ git clone https://github.com/quartz-labs-dev/pabal-store-api-mcp.git
70
+ cd pabal-store-api-mcp
71
+ yarn install
72
+ yarn dev:mcp
73
+ ```
74
+
75
+ <br>
76
+
77
+ ## License
78
+
79
+ MIT
80
+
81
+ <br>
82
+
83
+ ---
84
+
85
+ <br>
86
+
87
+ ## 🌐 Pabal Web
88
+
89
+ Want to manage ASO and SEO together? Check out **Pabal Web**.
90
+
91
+ [![Pabal Web](public/pabal-web.png)](https://pabal.quartz.best/)
92
+
93
+ **Pabal Web** is a Next.js-based web interface that provides a complete solution for unified management of ASO, SEO, Google Search Console indexing, and more.
94
+
95
+ 👉 [Visit Pabal Web](https://pabal.quartz.best/)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ // CLI entrypoint that starts the MCP server (npx entry)
4
+ import { startServer } from "../dist/src/index.js";
5
+
6
+ await startServer();
@@ -0,0 +1,29 @@
1
+ import { AppStoreClient } from "../../packages/stores/app-store/client.js";
2
+ import type { ClientFactoryResult } from "./types.js";
3
+ /**
4
+ * Result type for App Store client creation
5
+ */
6
+ export type AppStoreClientResult = ClientFactoryResult<AppStoreClient>;
7
+ /**
8
+ * Configuration for App Store client creation
9
+ */
10
+ export interface AppStoreClientConfig {
11
+ bundleId: string;
12
+ }
13
+ /**
14
+ * Create App Store client with fresh config loading and Result pattern
15
+ *
16
+ * Stateless factory - loads config on every call to ensure MCP works from any directory
17
+ *
18
+ * @param config - Bundle ID configuration
19
+ * @returns Result with client or error message
20
+ *
21
+ * @example
22
+ * const result = createAppStoreClient({ bundleId: "com.example.app" });
23
+ * if (result.success) {
24
+ * const data = await result.client.getApp();
25
+ * } else {
26
+ * console.error(result.error);
27
+ * }
28
+ */
29
+ export declare function createAppStoreClient(config: AppStoreClientConfig): AppStoreClientResult;
@@ -0,0 +1,72 @@
1
+ import { AppError } from "../../packages/common/errors/app-error.js";
2
+ import { ERROR_CODES } from "../../packages/common/errors/error-codes.js";
3
+ import { HTTP_STATUS } from "../../packages/common/errors/status-codes.js";
4
+ import { loadConfig } from "../../packages/configs/secrets-config/config.js";
5
+ import { AppStoreClient } from "../../packages/stores/app-store/client.js";
6
+ import { failure, isNonEmptyString, success } from "./client-factory-helpers.js";
7
+ /**
8
+ * Create App Store client with fresh config loading and Result pattern
9
+ *
10
+ * Stateless factory - loads config on every call to ensure MCP works from any directory
11
+ *
12
+ * @param config - Bundle ID configuration
13
+ * @returns Result with client or error message
14
+ *
15
+ * @example
16
+ * const result = createAppStoreClient({ bundleId: "com.example.app" });
17
+ * if (result.success) {
18
+ * const data = await result.client.getApp();
19
+ * } else {
20
+ * console.error(result.error);
21
+ * }
22
+ */
23
+ export function createAppStoreClient(config) {
24
+ // Validate input
25
+ if (!config) {
26
+ return failure(AppError.badRequest(ERROR_CODES.APP_STORE_CLIENT_CONFIG_MISSING, "App Store client configuration is missing"));
27
+ }
28
+ const { bundleId } = config;
29
+ if (!isNonEmptyString(bundleId)) {
30
+ return failure(AppError.validation(ERROR_CODES.APP_STORE_BUNDLE_ID_INVALID, "Bundle ID is required and must be a non-empty string"));
31
+ }
32
+ // Load fresh config (stateless - works from any directory)
33
+ let appStoreConfig;
34
+ try {
35
+ const fullConfig = loadConfig();
36
+ appStoreConfig = fullConfig.appStore;
37
+ }
38
+ catch (error) {
39
+ return failure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_CONFIG_LOAD_FAILED, "Failed to load App Store configuration"));
40
+ }
41
+ // Validate required credentials
42
+ if (!appStoreConfig) {
43
+ return failure(AppError.configMissing(ERROR_CODES.APP_STORE_CONFIG_MISSING, "App Store configuration is missing in config file"));
44
+ }
45
+ const { issuerId, keyId, privateKey } = appStoreConfig;
46
+ if (!isNonEmptyString(issuerId)) {
47
+ return failure(AppError.validation(ERROR_CODES.APP_STORE_ISSUER_ID_INVALID, "App Store Issuer ID is required in configuration"));
48
+ }
49
+ if (!isNonEmptyString(keyId)) {
50
+ return failure(AppError.validation(ERROR_CODES.APP_STORE_KEY_ID_INVALID, "App Store Key ID is required in configuration"));
51
+ }
52
+ if (!isNonEmptyString(privateKey)) {
53
+ return failure(AppError.validation(ERROR_CODES.APP_STORE_PRIVATE_KEY_INVALID, "App Store Private Key is required in configuration"));
54
+ }
55
+ // Validate private key format
56
+ if (!privateKey.includes("BEGIN PRIVATE KEY")) {
57
+ return failure(AppError.validation(ERROR_CODES.APP_STORE_PRIVATE_KEY_FORMAT_INVALID, "Invalid Private Key format. PEM format private key is required (must contain 'BEGIN PRIVATE KEY')"));
58
+ }
59
+ // Create client
60
+ try {
61
+ const client = new AppStoreClient({
62
+ bundleId,
63
+ issuerId,
64
+ keyId,
65
+ privateKey,
66
+ });
67
+ return success(client);
68
+ }
69
+ catch (error) {
70
+ return failure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_STORE_CLIENT_CREATE_FAILED, "Failed to create App Store client"));
71
+ }
72
+ }
@@ -0,0 +1,7 @@
1
+ import { AppError } from "../../packages/common/errors/app-error.js";
2
+ import type { ClientFactoryResult } from "./types.js";
3
+ export type { ClientFactoryResult } from "./types.js";
4
+ export declare const success: <TClient>(client: TClient) => ClientFactoryResult<TClient>;
5
+ export declare const failure: <TClient>(error: AppError) => ClientFactoryResult<TClient>;
6
+ export declare const toErrorMessage: (error: unknown) => string;
7
+ export declare const isNonEmptyString: (value: unknown) => value is string;
@@ -0,0 +1,10 @@
1
+ export const success = (client) => ({
2
+ success: true,
3
+ client,
4
+ });
5
+ export const failure = (error) => ({
6
+ success: false,
7
+ error,
8
+ });
9
+ export const toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
10
+ export const isNonEmptyString = (value) => typeof value === "string" && Boolean(value.trim());
@@ -0,0 +1,29 @@
1
+ import { GooglePlayClient } from "../../packages/stores/play-store/client.js";
2
+ import type { ClientFactoryResult } from "./types.js";
3
+ /**
4
+ * Result type for Google Play client creation
5
+ */
6
+ export type GooglePlayClientResult = ClientFactoryResult<GooglePlayClient>;
7
+ /**
8
+ * Configuration for Google Play client creation
9
+ */
10
+ export interface GooglePlayClientConfig {
11
+ packageName: string;
12
+ }
13
+ /**
14
+ * Create Google Play client with fresh config loading and Result pattern
15
+ *
16
+ * Stateless factory - loads config on every call to ensure MCP works from any directory
17
+ *
18
+ * @param config - Package name configuration
19
+ * @returns Result with client or error message
20
+ *
21
+ * @example
22
+ * const result = createGooglePlayClient({ packageName: "com.example.app" });
23
+ * if (result.success) {
24
+ * const data = await result.client.getApp();
25
+ * } else {
26
+ * console.error(result.error);
27
+ * }
28
+ */
29
+ export declare function createGooglePlayClient(config: GooglePlayClientConfig): GooglePlayClientResult;
@@ -0,0 +1,72 @@
1
+ import { AppError } from "../../packages/common/errors/app-error.js";
2
+ import { ERROR_CODES } from "../../packages/common/errors/error-codes.js";
3
+ import { HTTP_STATUS } from "../../packages/common/errors/status-codes.js";
4
+ import { loadConfig } from "../../packages/configs/secrets-config/config.js";
5
+ import { GooglePlayClient } from "../../packages/stores/play-store/client.js";
6
+ import { failure, isNonEmptyString, success } from "./client-factory-helpers.js";
7
+ /**
8
+ * Create Google Play client with fresh config loading and Result pattern
9
+ *
10
+ * Stateless factory - loads config on every call to ensure MCP works from any directory
11
+ *
12
+ * @param config - Package name configuration
13
+ * @returns Result with client or error message
14
+ *
15
+ * @example
16
+ * const result = createGooglePlayClient({ packageName: "com.example.app" });
17
+ * if (result.success) {
18
+ * const data = await result.client.getApp();
19
+ * } else {
20
+ * console.error(result.error);
21
+ * }
22
+ */
23
+ export function createGooglePlayClient(config) {
24
+ // Validate input
25
+ if (!config) {
26
+ return failure(AppError.badRequest(ERROR_CODES.GOOGLE_PLAY_CLIENT_CONFIG_MISSING, "Google Play client configuration is missing"));
27
+ }
28
+ const { packageName } = config;
29
+ if (!isNonEmptyString(packageName)) {
30
+ return failure(AppError.validation(ERROR_CODES.GOOGLE_PLAY_PACKAGE_NAME_INVALID, "Package name is required and must be a non-empty string"));
31
+ }
32
+ // Load fresh config (stateless - works from any directory)
33
+ let playStoreConfig;
34
+ try {
35
+ const fullConfig = loadConfig();
36
+ playStoreConfig = fullConfig.playStore;
37
+ }
38
+ catch (error) {
39
+ return failure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_CONFIG_LOAD_FAILED, "Failed to load Google Play configuration"));
40
+ }
41
+ // Validate required credentials
42
+ if (!playStoreConfig) {
43
+ return failure(AppError.configMissing(ERROR_CODES.GOOGLE_PLAY_CONFIG_MISSING, "Google Play configuration is missing in config file"));
44
+ }
45
+ const { serviceAccountJson } = playStoreConfig;
46
+ if (!isNonEmptyString(serviceAccountJson)) {
47
+ return failure(AppError.validation(ERROR_CODES.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_INVALID, "Google Play service account JSON is required in configuration"));
48
+ }
49
+ // Parse and validate service account JSON
50
+ let serviceAccountKey;
51
+ try {
52
+ const parsed = JSON.parse(serviceAccountJson);
53
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
54
+ return failure(AppError.validation(ERROR_CODES.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_FORMAT_INVALID, "Service account JSON must be a valid JSON object"));
55
+ }
56
+ serviceAccountKey = parsed;
57
+ }
58
+ catch (error) {
59
+ return failure(AppError.wrap(error, HTTP_STATUS.UNPROCESSABLE_ENTITY, ERROR_CODES.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PARSE_FAILED, "Invalid Google Play service account JSON"));
60
+ }
61
+ // Create client
62
+ try {
63
+ const client = new GooglePlayClient({
64
+ packageName,
65
+ serviceAccountKey,
66
+ });
67
+ return success(client);
68
+ }
69
+ catch (error) {
70
+ return failure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.GOOGLE_PLAY_CLIENT_CREATE_FAILED, "Failed to create Google Play client"));
71
+ }
72
+ }
@@ -0,0 +1,8 @@
1
+ import type { AppError } from "../../packages/common/errors/app-error.js";
2
+ export type ClientFactoryResult<TClient> = {
3
+ success: true;
4
+ client: TClient;
5
+ } | {
6
+ success: false;
7
+ error: AppError;
8
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { PushAsoResult, UpdatedReleaseNotesResult } from "../../core/services/types.js";
2
+ export declare function formatPushResult(storeLabel: "App Store" | "Google Play", result: PushAsoResult): string;
3
+ export declare const formatReleaseNotesUpdate: (storeLabel: "App Store" | "Google Play", result: UpdatedReleaseNotesResult) => string[];
@@ -0,0 +1,38 @@
1
+ const fieldDisplayNames = {
2
+ name: "Name",
3
+ subtitle: "Subtitle",
4
+ };
5
+ function formatFailedFields(failedFields) {
6
+ return failedFields
7
+ .map((f) => {
8
+ const fieldNames = f.fields.map((field) => fieldDisplayNames[field] || field);
9
+ return ` • ${f.locale}: ${fieldNames.join(", ")}`;
10
+ })
11
+ .join("\n");
12
+ }
13
+ export function formatPushResult(storeLabel, result) {
14
+ if (!result.success) {
15
+ if (result.needsNewVersion && result.versionInfo) {
16
+ const { versionString, versionId } = result.versionInfo;
17
+ return `✅ New version ${versionString} created (Version ID: ${versionId})`;
18
+ }
19
+ return `❌ ${storeLabel} push failed: ${result.error.message}`;
20
+ }
21
+ if (result.failedFields && result.failedFields.length > 0) {
22
+ return `⚠️ ${storeLabel} data pushed with partial failures (${result.localesPushed.length} locales)\n${formatFailedFields(result.failedFields)}`;
23
+ }
24
+ return `✅ ${storeLabel} data pushed (${result.localesPushed.length} locales)`;
25
+ }
26
+ export const formatReleaseNotesUpdate = (storeLabel, result) => {
27
+ const lines = [];
28
+ lines.push(`**${storeLabel}**`);
29
+ if (result.updated.length > 0) {
30
+ lines.push(` ✅ Updated: ${result.updated.join(", ")}`);
31
+ }
32
+ if (result.failed.length > 0) {
33
+ for (const fail of result.failed) {
34
+ lines.push(` ❌ ${fail.locale}: ${fail.error}`);
35
+ }
36
+ }
37
+ return lines;
38
+ };
@@ -0,0 +1,21 @@
1
+ import type { RegisteredAppStoreInfo, RegisteredGooglePlayInfo } from "../../packages/configs/secrets-config/registered-apps.js";
2
+ type AppStoreAppInfo = {
3
+ found: boolean;
4
+ appId?: string;
5
+ name?: string;
6
+ supportedLocales?: string[];
7
+ };
8
+ type GooglePlayAppInfo = {
9
+ found: boolean;
10
+ name?: string;
11
+ supportedLocales?: string[];
12
+ };
13
+ export declare function toRegisteredAppStoreInfo({ bundleId, appInfo, }: {
14
+ bundleId: string;
15
+ appInfo: AppStoreAppInfo;
16
+ }): RegisteredAppStoreInfo | undefined;
17
+ export declare function toRegisteredGooglePlayInfo({ packageName, appInfo, }: {
18
+ packageName: string;
19
+ appInfo: GooglePlayAppInfo;
20
+ }): RegisteredGooglePlayInfo | undefined;
21
+ export {};
@@ -0,0 +1,21 @@
1
+ export function toRegisteredAppStoreInfo({ bundleId, appInfo, }) {
2
+ if (!appInfo.found) {
3
+ return undefined;
4
+ }
5
+ return {
6
+ bundleId,
7
+ appId: appInfo.appId,
8
+ name: appInfo.name,
9
+ supportedLocales: appInfo.supportedLocales,
10
+ };
11
+ }
12
+ export function toRegisteredGooglePlayInfo({ packageName, appInfo, }) {
13
+ if (!appInfo.found) {
14
+ return undefined;
15
+ }
16
+ return {
17
+ packageName,
18
+ name: appInfo.name,
19
+ supportedLocales: appInfo.supportedLocales,
20
+ };
21
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Release notes translation utilities.
3
+ * Prepares translation requests per store and gathers supported locales from registered apps.
4
+ */
5
+ import type { RegisteredApp } from "../../packages/configs/secrets-config/registered-apps.js";
6
+ import type { StoreType } from "../../packages/configs/aso-config/types.js";
7
+ export interface TranslationRequest {
8
+ sourceText: string;
9
+ sourceLocale: string;
10
+ targetLocales: string[];
11
+ store: StoreType;
12
+ }
13
+ /**
14
+ * Collect supported locales from registered app info
15
+ */
16
+ export declare function collectSupportedLocales({ app, store, }: {
17
+ app: RegisteredApp;
18
+ store: StoreType;
19
+ }): {
20
+ appStore: string[];
21
+ googlePlay: string[];
22
+ };
23
+ /**
24
+ * Create translation requests per store
25
+ */
26
+ export declare function createTranslationRequests({ store, targetLocales, sourceLocale, sourceText, }: {
27
+ store: StoreType;
28
+ targetLocales: {
29
+ appStore: string[];
30
+ googlePlay: string[];
31
+ };
32
+ sourceLocale: string;
33
+ sourceText: string;
34
+ }): TranslationRequest[];
35
+ /**
36
+ * Separate translations by store
37
+ */
38
+ export declare function separateTranslationsByStore({ store, translations, app, sourceLocale, }: {
39
+ store: StoreType;
40
+ translations: Record<string, string>;
41
+ app: RegisteredApp;
42
+ sourceLocale: string;
43
+ }): {
44
+ appStore: Record<string, string>;
45
+ googlePlay: Record<string, string>;
46
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Release notes translation utilities.
3
+ * Prepares translation requests per store and gathers supported locales from registered apps.
4
+ */
5
+ /**
6
+ * Collect supported locales from registered app info
7
+ */
8
+ export function collectSupportedLocales({ app, store, }) {
9
+ const appStoreLocales = [];
10
+ const googlePlayLocales = [];
11
+ if ((store === "both" || store === "appStore") &&
12
+ app.appStore?.supportedLocales) {
13
+ appStoreLocales.push(...app.appStore.supportedLocales);
14
+ }
15
+ if ((store === "both" || store === "googlePlay") &&
16
+ app.googlePlay?.supportedLocales) {
17
+ googlePlayLocales.push(...app.googlePlay.supportedLocales);
18
+ }
19
+ return {
20
+ appStore: appStoreLocales,
21
+ googlePlay: googlePlayLocales,
22
+ };
23
+ }
24
+ /**
25
+ * Create translation requests per store
26
+ */
27
+ export function createTranslationRequests({ store, targetLocales, sourceLocale, sourceText, }) {
28
+ const requests = [];
29
+ if (store === "both" || store === "appStore") {
30
+ if (targetLocales.appStore.length > 0) {
31
+ requests.push({
32
+ store: "appStore",
33
+ sourceText,
34
+ sourceLocale,
35
+ targetLocales: targetLocales.appStore,
36
+ });
37
+ }
38
+ }
39
+ if (store === "both" || store === "googlePlay") {
40
+ if (targetLocales.googlePlay.length > 0) {
41
+ requests.push({
42
+ store: "googlePlay",
43
+ sourceText,
44
+ sourceLocale,
45
+ targetLocales: targetLocales.googlePlay,
46
+ });
47
+ }
48
+ }
49
+ return requests;
50
+ }
51
+ /**
52
+ * Separate translations by store
53
+ */
54
+ export function separateTranslationsByStore({ store, translations, app, sourceLocale, }) {
55
+ const appStoreTranslations = {};
56
+ const googlePlayTranslations = {};
57
+ const appStoreLocales = app.appStore?.supportedLocales;
58
+ const googlePlayLocales = app.googlePlay?.supportedLocales;
59
+ for (const [locale, text] of Object.entries(translations)) {
60
+ if (store === "both" || store === "appStore") {
61
+ if (!appStoreLocales || appStoreLocales.includes(locale)) {
62
+ appStoreTranslations[locale] = text;
63
+ }
64
+ }
65
+ if (store === "both" || store === "googlePlay") {
66
+ if (!googlePlayLocales || googlePlayLocales.includes(locale)) {
67
+ googlePlayTranslations[locale] = text;
68
+ }
69
+ }
70
+ }
71
+ // Always include sourceLocale if provided and missing
72
+ if (store === "both" || store === "appStore") {
73
+ if (translations[sourceLocale] && !(sourceLocale in appStoreTranslations)) {
74
+ appStoreTranslations[sourceLocale] = translations[sourceLocale];
75
+ }
76
+ }
77
+ if (store === "both" || store === "googlePlay") {
78
+ if (translations[sourceLocale] &&
79
+ !(sourceLocale in googlePlayTranslations)) {
80
+ googlePlayTranslations[sourceLocale] = translations[sourceLocale];
81
+ }
82
+ }
83
+ return {
84
+ appStore: appStoreTranslations,
85
+ googlePlay: googlePlayTranslations,
86
+ };
87
+ }
@@ -0,0 +1,14 @@
1
+ import { type ResolvedAppContext, type ServiceResult } from "./types.js";
2
+ interface ResolveAppOptions {
3
+ slug?: string;
4
+ bundleId?: string;
5
+ packageName?: string;
6
+ }
7
+ /**
8
+ * Resolve registered app and store identifiers from user input.
9
+ * Separates "which store data is available" from tool-level orchestration.
10
+ */
11
+ export declare class AppResolutionService {
12
+ resolve(options: ResolveAppOptions): ServiceResult<ResolvedAppContext>;
13
+ }
14
+ export {};
@@ -0,0 +1,35 @@
1
+ import { AppError } from "../../packages/common/errors/app-error.js";
2
+ import { ERROR_CODES } from "../../packages/common/errors/error-codes.js";
3
+ import { findApp } from "../../packages/configs/secrets-config/registered-apps.js";
4
+ import { HTTP_STATUS } from "../../packages/common/errors/status-codes.js";
5
+ import { serviceFailure, serviceSuccess } from "./service-helpers.js";
6
+ /**
7
+ * Resolve registered app and store identifiers from user input.
8
+ * Separates "which store data is available" from tool-level orchestration.
9
+ */
10
+ export class AppResolutionService {
11
+ resolve(options) {
12
+ const { slug, bundleId, packageName } = options;
13
+ const identifier = slug || bundleId || packageName;
14
+ if (!identifier) {
15
+ return serviceFailure(AppError.validation(ERROR_CODES.APP_IDENTIFIER_MISSING, "❌ App not found. Please provide app (slug), packageName, or bundleId."));
16
+ }
17
+ try {
18
+ const app = findApp(identifier);
19
+ if (!app) {
20
+ return serviceFailure(AppError.notFound(ERROR_CODES.APP_NOT_FOUND, `❌ App registered with "${identifier}" not found. Check registered apps using apps-search.`));
21
+ }
22
+ return serviceSuccess({
23
+ app,
24
+ slug: app.slug,
25
+ bundleId: bundleId ?? app.appStore?.bundleId,
26
+ packageName: packageName ?? app.googlePlay?.packageName,
27
+ hasAppStore: Boolean(app.appStore),
28
+ hasGooglePlay: Boolean(app.googlePlay),
29
+ });
30
+ }
31
+ catch (error) {
32
+ return serviceFailure(AppError.wrap(error, HTTP_STATUS.INTERNAL_SERVER_ERROR, ERROR_CODES.APP_RESOLUTION_FAILED, "Failed to resolve app"));
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,41 @@
1
+ import type { AppStoreReleaseNote } from "../../packages/configs/aso-config/types.js";
2
+ import type { PreparedAsoData } from "../../packages/configs/aso-config/utils.js";
3
+ import type { EnvConfig } from "../../packages/configs/secrets-config/types.js";
4
+ import type { AppStoreClient } from "../../packages/stores/app-store/client.js";
5
+ import { type MaybeResult, type ServiceResult, type StoreAppSummary, type AppStoreVersionInfo, type UpdatedReleaseNotesResult, type PushAsoResult, type CreatedAppStoreVersion, type VerifyAuthResult } from "./types.js";
6
+ interface AppStoreAppInfo {
7
+ appId?: string;
8
+ name?: string;
9
+ supportedLocales?: string[];
10
+ }
11
+ /**
12
+ * App Store-facing service layer that wraps client creation and common operations.
13
+ * Keeps MCP tools independent from client factories and SDK details.
14
+ */
15
+ export declare class AppStoreService {
16
+ private getClientOrThrow;
17
+ createClient(bundleId: string): ServiceResult<AppStoreClient>;
18
+ /**
19
+ * List released apps. Uses a fresh client to ensure working directory independence.
20
+ */
21
+ listReleasedApps(): Promise<ServiceResult<StoreAppSummary[]>>;
22
+ /**
23
+ * Fetch a single app info (with locales) by bundleId.
24
+ */
25
+ fetchAppInfo(bundleId: string, existingClient?: AppStoreClient): Promise<MaybeResult<AppStoreAppInfo>>;
26
+ getLatestVersion(bundleId: string, existingClient?: AppStoreClient): Promise<MaybeResult<AppStoreVersionInfo>>;
27
+ updateReleaseNotes(bundleId: string, releaseNotes: Record<string, string>, versionId?: string, supportedLocales?: string[]): Promise<ServiceResult<UpdatedReleaseNotesResult>>;
28
+ pullReleaseNotes(bundleId: string): Promise<ServiceResult<AppStoreReleaseNote[]>>;
29
+ createVersion(bundleId: string, versionString: string, autoIncrement?: boolean): Promise<ServiceResult<CreatedAppStoreVersion>>;
30
+ pushAsoData({ config, bundleId, localAsoData, appStoreDataPath, }: {
31
+ config: EnvConfig;
32
+ bundleId?: string;
33
+ localAsoData: PreparedAsoData;
34
+ appStoreDataPath: string;
35
+ }): Promise<PushAsoResult>;
36
+ verifyAuth(expirationSeconds?: number): Promise<VerifyAuthResult<{
37
+ header: Record<string, unknown>;
38
+ payload: Record<string, unknown>;
39
+ }>>;
40
+ }
41
+ export {};