@yoonion/mimi-seed-mcp 0.19.5 → 0.19.7
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 +9 -1
- package/dist/appstore/auth.d.ts +3 -1
- package/dist/appstore/auth.js +11 -1
- package/dist/appstore/sales.js +4 -3
- package/dist/appstore/setup-cli.js +11 -9
- package/dist/appstore/setup.d.ts +9 -0
- package/dist/appstore/setup.js +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,10 +63,18 @@ npx -y @yoonion/mimi-seed-mcp mimi-seed-tiktok-business-auth # TikTok Business O
|
|
|
63
63
|
|
|
64
64
|
각 자격증명을 **어디서 어떻게 발급받는지**는 [`docs/credentials.md`](../../docs/credentials.md) 참고.
|
|
65
65
|
|
|
66
|
-
- **App Store Connect**: Users and Access → Integrations에서 API Key 생성 후 Issuer ID / Key ID /
|
|
66
|
+
- **App Store Connect**: Users and Access → Integrations에서 API Key 생성 후 Issuer ID / Key ID / `.p8`
|
|
67
|
+
경로와 선택적인 Vendor Number 입력 → Apple API 검증 성공 후 `~/.mimi-seed/appstore.json` 저장 (`.p8`은
|
|
68
|
+
**1회만** 다운로드됨). 기존 연결의 첫 질문에서 Enter/`N`은 종료, `y`는 기본 키 교체, `v`는 Vendor
|
|
69
|
+
Number만 갱신합니다. 키 교체 시 기존 `vendorNumber`와 별도 `reportsKey`는 보존됩니다.
|
|
67
70
|
- **Google Play**: 서비스 계정은 **선택** — OAuth 토큰이 `androidpublisher` 스코프를 갖고 있어 로컬 작업은 그대로 됩니다. CI/헤드리스에서만 필요.
|
|
68
71
|
- **BigQuery**: 선택 — OAuth로도 동작하며, Workspace 재인증 정책에 막힐 때만 서비스 계정이 필요합니다.
|
|
69
72
|
|
|
73
|
+
App Store 권한은 작업별로 다릅니다. 메타데이터·출시는 **App Manager**, Analytics 리포트 요청 생성은
|
|
74
|
+
**Admin**, 매출 리포트는 **Admin / Finance / Sales and Reports**가 필요합니다. Vendor Number는 App Store
|
|
75
|
+
Connect 상단 **Reports**의 왼쪽 위 Legal Entity Name 아래에 표시되는 숫자입니다. 자세한 내용은
|
|
76
|
+
[`docs/credentials.ko.md`](../../docs/credentials.ko.md#app-store-connect)를 참고하세요.
|
|
77
|
+
|
|
70
78
|
AI 기능(릴리즈 노트 생성, 리뷰 답변)을 쓰려면:
|
|
71
79
|
|
|
72
80
|
```bash
|
package/dist/appstore/auth.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface AppStoreCredentials {
|
|
|
5
5
|
/**
|
|
6
6
|
* Sales and Trends / Finance 리포트 전용 판매자 번호.
|
|
7
7
|
*
|
|
8
|
-
* **API 로는 조회할 수 없다** — ASC >
|
|
8
|
+
* **API 로는 조회할 수 없다** — ASC > Reports 화면의 Legal Entity Name 아래에서 눈으로 읽어
|
|
9
9
|
* 여기 적어두는 수밖에 없다. 리포트 도구에만 쓰이므로 없어도 나머지는 다 동작한다.
|
|
10
10
|
*/
|
|
11
11
|
vendorNumber?: string;
|
|
@@ -31,6 +31,8 @@ export interface AppStoreKey {
|
|
|
31
31
|
keyId: string;
|
|
32
32
|
privateKey: string;
|
|
33
33
|
}
|
|
34
|
+
/** 빈 입력은 미설정으로 처리하고, 값이 있으면 Apple 보고서 필터에 안전한 숫자인지 확인한다. */
|
|
35
|
+
export declare function normalizeVendorNumber(vendorNumber?: string): string | undefined;
|
|
34
36
|
/**
|
|
35
37
|
* 최상위 ASC API 키를 교체하되 같은 파일에 저장된 선택 설정은 보존한다.
|
|
36
38
|
*
|
package/dist/appstore/auth.js
CHANGED
|
@@ -10,6 +10,16 @@ import { writeCredentialJson } from '../lib/atomic-write.js';
|
|
|
10
10
|
// force a re-setup.
|
|
11
11
|
const CONFIG_PATH = path.join(os.homedir(), '.mimi-seed', 'appstore.json');
|
|
12
12
|
const LEGACY_CONFIG_PATH = path.join(os.homedir(), '.preseed', 'appstore.json');
|
|
13
|
+
/** 빈 입력은 미설정으로 처리하고, 값이 있으면 Apple 보고서 필터에 안전한 숫자인지 확인한다. */
|
|
14
|
+
export function normalizeVendorNumber(vendorNumber) {
|
|
15
|
+
const normalized = vendorNumber?.trim();
|
|
16
|
+
if (!normalized)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (!/^\d+$/.test(normalized)) {
|
|
19
|
+
throw new Error('Vendor Number는 숫자만 입력하세요 (digits only).');
|
|
20
|
+
}
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
13
23
|
/**
|
|
14
24
|
* 최상위 ASC API 키를 교체하되 같은 파일에 저장된 선택 설정은 보존한다.
|
|
15
25
|
*
|
|
@@ -19,7 +29,7 @@ const LEGACY_CONFIG_PATH = path.join(os.homedir(), '.preseed', 'appstore.json');
|
|
|
19
29
|
*/
|
|
20
30
|
export function mergeAppStoreCredentials(existing, primary, vendorNumber) {
|
|
21
31
|
const merged = { ...(existing ?? {}), ...primary };
|
|
22
|
-
const normalizedVendorNumber = vendorNumber
|
|
32
|
+
const normalizedVendorNumber = normalizeVendorNumber(vendorNumber);
|
|
23
33
|
if (normalizedVendorNumber)
|
|
24
34
|
merged.vendorNumber = normalizedVendorNumber;
|
|
25
35
|
return merged;
|
package/dist/appstore/sales.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// sandbox 가 들어오지 않는 창구를 봐야 하고, 그게 이 리포트다.
|
|
10
10
|
import zlib from 'node:zlib';
|
|
11
11
|
import { fetchWithTimeout } from '../lib/http.js';
|
|
12
|
-
import { getAppStoreCredentials, getReportsAuthHeaders } from './auth.js';
|
|
12
|
+
import { getAppStoreCredentials, getReportsAuthHeaders, normalizeVendorNumber } from './auth.js';
|
|
13
13
|
import { friendlyAppStoreError } from './errors.js';
|
|
14
14
|
import { V1_BASE } from './http.js';
|
|
15
15
|
/**
|
|
@@ -20,12 +20,13 @@ import { V1_BASE } from './http.js';
|
|
|
20
20
|
* 매출 0 으로 둔갑하는 게 이 도구에서 제일 위험한 오답이라 사전에 막는다.
|
|
21
21
|
*/
|
|
22
22
|
function resolveVendorNumber(explicit) {
|
|
23
|
-
const vendorNumber = explicit
|
|
23
|
+
const vendorNumber = normalizeVendorNumber(explicit)
|
|
24
|
+
?? normalizeVendorNumber(getAppStoreCredentials()?.vendorNumber);
|
|
24
25
|
if (!vendorNumber) {
|
|
25
26
|
throw new Error([
|
|
26
27
|
'❌ vendorNumber 가 없어 — 매출 리포트는 판매자 번호가 반드시 필요해.',
|
|
27
28
|
'',
|
|
28
|
-
'App Store Connect >
|
|
29
|
+
'App Store Connect > Reports > 왼쪽 위 Legal Entity Name 아래에 있는 숫자야.',
|
|
29
30
|
'API 로는 조회할 수 없으니 한 번만 저장해 두면 이후 생략할 수 있어:',
|
|
30
31
|
' ~/.mimi-seed/appstore.json 에 "vendorNumber": "<번호>" 추가',
|
|
31
32
|
'또는 이 도구의 vendorNumber 인자로 직접 넘겨.',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { getAppStoreCredentials, mergeAppStoreCredentials } from './auth.js';
|
|
3
|
-
import { verifyAndSaveAppStoreCredentials } from './setup.js';
|
|
3
|
+
import { collectExistingSetupIntent, verifyAndSaveAppStoreCredentials } from './setup.js';
|
|
4
4
|
import readline from 'node:readline';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import { resolveLang } from '../lib/lang.js';
|
|
@@ -8,7 +8,7 @@ import { resolveLang } from '../lib/lang.js';
|
|
|
8
8
|
const ko = {
|
|
9
9
|
title: ' 🍎 Mimi Seed — App Store Connect 연결',
|
|
10
10
|
already: (keyId) => ` ✅ 설정 파일 있음 (Key ID: ${keyId}) — API 권한은 다시 검증합니다`,
|
|
11
|
-
reconnect: ' API Key
|
|
11
|
+
reconnect: ' API Key를 다시 설정할래? (y/N, v=Vendor Number만): ',
|
|
12
12
|
needTitle: ' App Store Connect에서 API Key를 만들어야 해:',
|
|
13
13
|
step1: ' 1. "키 생성" 클릭',
|
|
14
14
|
step2: ' 2. 이름: Mimi Seed, 역할: Admin',
|
|
@@ -29,7 +29,7 @@ const ko = {
|
|
|
29
29
|
const en = {
|
|
30
30
|
title: ' 🍎 Mimi Seed — Connect App Store Connect',
|
|
31
31
|
already: (keyId) => ` ✅ Configuration found (Key ID: ${keyId}) — API access will be verified again`,
|
|
32
|
-
reconnect: ' Replace the API Key
|
|
32
|
+
reconnect: ' Replace the API Key? (y/N, v=Vendor Number only): ',
|
|
33
33
|
needTitle: ' You need to create an API Key in App Store Connect:',
|
|
34
34
|
step1: ' 1. Click "Generate API Key"',
|
|
35
35
|
step2: ' 2. Name: Mimi Seed, Access: Admin',
|
|
@@ -64,12 +64,15 @@ async function main() {
|
|
|
64
64
|
console.log(M.title);
|
|
65
65
|
console.log('');
|
|
66
66
|
const existing = getAppStoreCredentials();
|
|
67
|
-
let vendorNumber
|
|
67
|
+
let vendorNumber;
|
|
68
68
|
if (existing) {
|
|
69
69
|
console.log(M.already(existing.keyId));
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
const intent = await collectExistingSetupIntent(ask, {
|
|
71
|
+
reconnect: M.reconnect,
|
|
72
|
+
vendorNumber: M.askVendorNumber,
|
|
73
|
+
});
|
|
74
|
+
if (!intent.replacePrimaryKey) {
|
|
75
|
+
vendorNumber = intent.vendorNumber;
|
|
73
76
|
if (vendorNumber.trim()) {
|
|
74
77
|
const credentials = mergeAppStoreCredentials(existing, existing, vendorNumber);
|
|
75
78
|
if (await saveIfVerified(credentials))
|
|
@@ -89,8 +92,7 @@ async function main() {
|
|
|
89
92
|
const issuerId = await ask(M.askIssuerId);
|
|
90
93
|
const keyId = await ask(M.askKeyId);
|
|
91
94
|
const p8Path = await ask(M.askP8Path);
|
|
92
|
-
|
|
93
|
-
vendorNumber = await ask(M.askVendorNumber);
|
|
95
|
+
vendorNumber = await ask(M.askVendorNumber);
|
|
94
96
|
const trimmedPath = p8Path.trim().replace(/^["']|["']$/g, '');
|
|
95
97
|
if (!fs.existsSync(trimmedPath)) {
|
|
96
98
|
console.log(M.noFile(trimmedPath));
|
package/dist/appstore/setup.d.ts
CHANGED
|
@@ -4,6 +4,15 @@ interface SetupDependencies {
|
|
|
4
4
|
verify: (credentials: AppStoreCredentials) => Promise<AppStoreVerifyResult>;
|
|
5
5
|
save: (credentials: AppStoreCredentials) => void;
|
|
6
6
|
}
|
|
7
|
+
export interface ExistingSetupIntent {
|
|
8
|
+
replacePrimaryKey: boolean;
|
|
9
|
+
vendorNumber: string;
|
|
10
|
+
}
|
|
11
|
+
/** 기존 사용자의 익숙한 첫 질문(API Key 재설정)을 바꾸지 않는 대화 순서 계약. */
|
|
12
|
+
export declare function collectExistingSetupIntent(ask: (question: string) => Promise<string>, prompts: {
|
|
13
|
+
reconnect: string;
|
|
14
|
+
vendorNumber: string;
|
|
15
|
+
}): Promise<ExistingSetupIntent>;
|
|
7
16
|
/** Apple API에서 실제로 쓸 수 있는 키인지 확인한 뒤에만 디스크에 저장한다. */
|
|
8
17
|
export declare function verifyAndSaveAppStoreCredentials(credentials: AppStoreCredentials, dependencies?: SetupDependencies): Promise<AppStoreVerifyResult>;
|
|
9
18
|
export {};
|
package/dist/appstore/setup.js
CHANGED
|
@@ -4,6 +4,21 @@ const defaultDependencies = {
|
|
|
4
4
|
verify: verifyAppStoreCredentials,
|
|
5
5
|
save: saveAppStoreCredentials,
|
|
6
6
|
};
|
|
7
|
+
/** 기존 사용자의 익숙한 첫 질문(API Key 재설정)을 바꾸지 않는 대화 순서 계약. */
|
|
8
|
+
export async function collectExistingSetupIntent(ask, prompts) {
|
|
9
|
+
const reconnect = await ask(prompts.reconnect);
|
|
10
|
+
const action = reconnect.trim().toLowerCase();
|
|
11
|
+
if (action === 'y') {
|
|
12
|
+
return { replacePrimaryKey: true, vendorNumber: '' };
|
|
13
|
+
}
|
|
14
|
+
if (action !== 'v') {
|
|
15
|
+
return { replacePrimaryKey: false, vendorNumber: '' };
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
replacePrimaryKey: false,
|
|
19
|
+
vendorNumber: await ask(prompts.vendorNumber),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
7
22
|
/** Apple API에서 실제로 쓸 수 있는 키인지 확인한 뒤에만 디스크에 저장한다. */
|
|
8
23
|
export async function verifyAndSaveAppStoreCredentials(credentials, dependencies = defaultDependencies) {
|
|
9
24
|
const result = await dependencies.verify(credentials);
|
package/package.json
CHANGED