@yoonion/mimi-seed-mcp 0.3.10 → 0.3.11

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.
@@ -0,0 +1,20 @@
1
+ import type { OAuth2Client } from 'google-auth-library';
2
+ export type StepState = 'done' | 'pending' | 'blocked';
3
+ export interface PlanStep {
4
+ state: StepState;
5
+ title: string;
6
+ detail?: string;
7
+ tool?: string;
8
+ irreversible?: boolean;
9
+ }
10
+ export declare function buildPlayStoreReleasePlan(opts: {
11
+ auth: OAuth2Client;
12
+ packageName: string;
13
+ versionCode?: string;
14
+ track: 'production' | 'beta' | 'alpha' | 'internal';
15
+ language: string;
16
+ }): Promise<string>;
17
+ export declare function buildAppStoreReleasePlan(opts: {
18
+ appId: string;
19
+ versionString?: string;
20
+ }): Promise<string>;
@@ -0,0 +1,258 @@
1
+ // Release Plan — Play Store / App Store 배포 사전 점검 + AI에게 TodoWrite 지시
2
+ //
3
+ // 이 모듈은 read-only.
4
+ // 실행 도구(playstore_submit_release, appstore_submit_for_review 등)는 호출하지 않고,
5
+ // 현재 상태를 조사해서 markdown 체크리스트로 변환한다.
6
+ // AI 에이전트가 응답을 받아 TodoWrite로 사용자에게 제시한 뒤,
7
+ // 단계별로 기존 *_update_*, *_submit_*, *_check_* 도구를 호출하도록 유도한다.
8
+ import { publisher, withEdit } from '../playstore/tools.js';
9
+ import { apiGet } from '../appstore/tools.js';
10
+ import { checkPlayStoreRisks, checkAppStoreRisks } from './risks.js';
11
+ const TODO_DIRECTIVE = [
12
+ '📋 [AI 지시 — 호출자 에이전트 전용]',
13
+ '',
14
+ '이 응답은 배포 플랜입니다. 다음 절차를 반드시 따르세요:',
15
+ '',
16
+ '1. 아래 체크리스트의 미완료(◻︎) 항목을 TodoWrite 도구로 사용자에게 먼저 보여주세요.',
17
+ '2. 사용자가 진행에 명시적으로 동의하기 전엔 ⚠️ 비가역 도구를 호출하지 마세요',
18
+ ' (playstore_submit_release status=completed, playstore_promote_release status=completed,',
19
+ ' appstore_submit_for_review).',
20
+ '3. 한 단계 끝날 때마다 TodoWrite로 해당 항목을 completed로 표시하세요.',
21
+ '4. 항목 옆에 "도구:" 가 표기된 경우, 그 도구를 직접 호출해서 단계를 실행하세요.',
22
+ '',
23
+ ].join('\n');
24
+ function fmtState(s) {
25
+ return s === 'done' ? '☑︎' : s === 'blocked' ? '⛔' : '◻︎';
26
+ }
27
+ function risksToSteps(risks, toolHint) {
28
+ return risks.map((r) => ({
29
+ state: 'blocked',
30
+ title: `[${r.code}] ${r.title}`,
31
+ detail: r.detail + (r.fixUrl ? ` → ${r.fixUrl}` : ''),
32
+ tool: r.level === 'blocker' ? toolHint : undefined,
33
+ }));
34
+ }
35
+ function renderPlan(ctx, steps) {
36
+ const blocked = steps.filter((s) => s.state === 'blocked').length;
37
+ const pending = steps.filter((s) => s.state === 'pending').length;
38
+ const done = steps.filter((s) => s.state === 'done').length;
39
+ const lines = [];
40
+ lines.push(TODO_DIRECTIVE);
41
+ lines.push(`🎯 ${ctx.platform} 배포 플랜`);
42
+ lines.push('');
43
+ lines.push(` 대상: ${ctx.identity}`);
44
+ lines.push(` 버전: ${ctx.version}`);
45
+ if (ctx.track)
46
+ lines.push(` 트랙: ${ctx.track}`);
47
+ lines.push('');
48
+ lines.push(` 진행률: ☑︎ ${done} ◻︎ ${pending} ⛔ ${blocked}`);
49
+ if (blocked > 0) {
50
+ lines.push(` ⚠️ 블로커 ${blocked}건 — 먼저 해결해야 제출 가능`);
51
+ }
52
+ lines.push('');
53
+ lines.push('체크리스트 (TodoWrite로 사용자에게 제시):');
54
+ lines.push('');
55
+ for (const s of steps) {
56
+ const irr = s.irreversible ? ' ⚠️ 비가역' : '';
57
+ lines.push(`${fmtState(s.state)} ${s.title}${irr}`);
58
+ if (s.detail)
59
+ lines.push(` ${s.detail}`);
60
+ if (s.tool)
61
+ lines.push(` 도구: ${s.tool}`);
62
+ }
63
+ lines.push('');
64
+ lines.push('—');
65
+ lines.push('TodoWrite로 위 미완료 항목을 등록한 후, 사용자 확인을 받고 도구를 단계별로 호출하세요.');
66
+ return lines.join('\n');
67
+ }
68
+ // ─── Play Store ──────────────────────────────────────────────────────
69
+ export async function buildPlayStoreReleasePlan(opts) {
70
+ const { auth, packageName, versionCode, track, language } = opts;
71
+ const steps = [];
72
+ // 1) 인증 + 트랙 / 빌드 상태 조회 (단일 edit session)
73
+ let trackInfo = {
74
+ hasTargetVersion: false,
75
+ };
76
+ let listingPresent = false;
77
+ try {
78
+ await withEdit(auth, packageName, async (editId) => {
79
+ const [tracks, listing] = await Promise.all([
80
+ publisher().edits.tracks.list({ auth, packageName, editId }).catch(() => null),
81
+ publisher().edits.listings.get({ auth, packageName, editId, language }).catch(() => null),
82
+ ]);
83
+ const targetTrack = tracks?.data?.tracks?.find((t) => t.track === track);
84
+ const release = targetTrack?.releases?.[0];
85
+ const codes = release?.versionCodes ?? [];
86
+ trackInfo = {
87
+ hasTargetVersion: versionCode ? codes.includes(versionCode) : codes.length > 0,
88
+ latestVersionCode: codes[codes.length - 1],
89
+ releaseStatus: release?.status ?? undefined,
90
+ };
91
+ listingPresent = !!listing?.data?.title;
92
+ return null;
93
+ });
94
+ steps.push({ state: 'done', title: 'Google Play 인증 + 트랙 조회 성공' });
95
+ }
96
+ catch (e) {
97
+ steps.push({
98
+ state: 'blocked',
99
+ title: 'Google Play 인증 또는 트랙 조회 실패',
100
+ detail: e instanceof Error ? e.message : String(e),
101
+ tool: 'mimi_seed_auth_status',
102
+ });
103
+ return renderPlan({ platform: 'Play Store', identity: packageName, version: versionCode ?? 'latest', track }, steps);
104
+ }
105
+ // 2) 빌드 도착 확인
106
+ if (trackInfo.hasTargetVersion) {
107
+ steps.push({
108
+ state: 'done',
109
+ title: `${track} 트랙에 versionCode ${versionCode ?? trackInfo.latestVersionCode} 도착 확인`,
110
+ detail: `현재 release status: ${trackInfo.releaseStatus ?? 'unknown'}`,
111
+ });
112
+ }
113
+ else {
114
+ steps.push({
115
+ state: 'blocked',
116
+ title: versionCode
117
+ ? `${track} 트랙에 versionCode ${versionCode} 없음`
118
+ : `${track} 트랙에 release 없음`,
119
+ detail: 'Gradle / Fastlane / Play Console 업로드를 먼저 완료하세요.',
120
+ tool: 'playstore_list_tracks',
121
+ });
122
+ }
123
+ // 3) 메타데이터 / 스크린샷 / 정책 위험 점검
124
+ const risks = await checkPlayStoreRisks(auth, packageName, language);
125
+ if (risks.length === 0) {
126
+ steps.push({
127
+ state: 'done',
128
+ title: '제출 위험 점검 통과 (블로커 0)',
129
+ });
130
+ }
131
+ else {
132
+ const blockers = risks.filter((r) => r.level === 'blocker');
133
+ const warnings = risks.filter((r) => r.level === 'warning');
134
+ steps.push({
135
+ state: blockers.length > 0 ? 'blocked' : 'pending',
136
+ title: `제출 위험 ${risks.length}건 (블로커 ${blockers.length} / 경고 ${warnings.length})`,
137
+ detail: '아래 항목을 개별 수정 후 다시 plan 호출 권장',
138
+ tool: 'playstore_check_submission_risks',
139
+ });
140
+ steps.push(...risksToSteps(blockers, 'playstore_update_listing'));
141
+ steps.push(...risksToSteps(warnings, 'playstore_update_listing'));
142
+ }
143
+ // 4) 릴리즈 노트
144
+ steps.push({
145
+ state: 'pending',
146
+ title: `릴리즈 노트 등록/갱신 (${language})`,
147
+ detail: 'AI 노트 초안 + 사용자 확인 후 적용',
148
+ tool: 'playstore_update_release_notes (또는 playstore_update_latest_release_notes)',
149
+ });
150
+ // 5) 제출
151
+ const canSubmit = !risks.some((r) => r.level === 'blocker') && trackInfo.hasTargetVersion;
152
+ steps.push({
153
+ state: canSubmit ? 'pending' : 'blocked',
154
+ title: `${track} 트랙 release status 변경 (draft → completed)`,
155
+ detail: canSubmit
156
+ ? '⚠️ 비가역에 가까움. 사용자 명시 동의 필수.'
157
+ : '블로커 해결 후 가능',
158
+ tool: 'playstore_submit_release',
159
+ irreversible: true,
160
+ });
161
+ return renderPlan({ platform: 'Play Store', identity: packageName, version: versionCode ?? 'latest', track }, steps);
162
+ }
163
+ // ─── App Store ────────────────────────────────────────────────────────
164
+ export async function buildAppStoreReleasePlan(opts) {
165
+ const { appId, versionString } = opts;
166
+ const steps = [];
167
+ // 1) 편집 가능한 버전 조회
168
+ let versionId;
169
+ let versionState;
170
+ let buildAttached = false;
171
+ try {
172
+ const versions = await apiGet(`/apps/${appId}/appStoreVersions`, {
173
+ 'filter[appStoreState]': 'PREPARE_FOR_SUBMISSION,WAITING_FOR_REVIEW,DEVELOPER_REJECTED',
174
+ ...(versionString ? { 'filter[versionString]': versionString } : {}),
175
+ limit: '5',
176
+ }).catch(() => null);
177
+ if (versions?.data?.length) {
178
+ versionId = versions.data[0].id;
179
+ versionState = versions.data[0].attributes?.appStoreState;
180
+ steps.push({
181
+ state: 'done',
182
+ title: `편집 가능한 버전 발견 (${versions.data[0].attributes?.versionString}, state=${versionState})`,
183
+ });
184
+ // build attached?
185
+ const build = await apiGet(`/appStoreVersions/${versionId}/build`).catch(() => null);
186
+ buildAttached = !!build?.data?.id;
187
+ if (buildAttached) {
188
+ steps.push({ state: 'done', title: '버전에 빌드 연결됨' });
189
+ }
190
+ else {
191
+ steps.push({
192
+ state: 'blocked',
193
+ title: '버전에 빌드 미연결',
194
+ detail: 'TestFlight 빌드 처리 완료 후 App Store Connect에서 연결하거나 API로 attach.',
195
+ tool: 'appstore_list_builds',
196
+ });
197
+ }
198
+ }
199
+ else {
200
+ steps.push({
201
+ state: 'blocked',
202
+ title: versionString
203
+ ? `편집 가능한 ${versionString} 버전 없음`
204
+ : '편집 가능한 버전 없음',
205
+ detail: 'App Store Connect에서 새 버전을 생성하세요.',
206
+ });
207
+ }
208
+ }
209
+ catch (e) {
210
+ steps.push({
211
+ state: 'blocked',
212
+ title: 'App Store 인증 또는 버전 조회 실패',
213
+ detail: e instanceof Error ? e.message : String(e),
214
+ tool: 'mimi_seed_auth_status',
215
+ });
216
+ return renderPlan({ platform: 'App Store', identity: appId, version: versionString ?? 'latest' }, steps);
217
+ }
218
+ // 2) 메타데이터 / 스크린샷 / 정책 위험 점검
219
+ const risks = await checkAppStoreRisks(appId);
220
+ if (risks.length === 0) {
221
+ steps.push({ state: 'done', title: '제출 위험 점검 통과 (블로커 0)' });
222
+ }
223
+ else {
224
+ const blockers = risks.filter((r) => r.level === 'blocker');
225
+ const warnings = risks.filter((r) => r.level === 'warning');
226
+ steps.push({
227
+ state: blockers.length > 0 ? 'blocked' : 'pending',
228
+ title: `제출 위험 ${risks.length}건 (블로커 ${blockers.length} / 경고 ${warnings.length})`,
229
+ tool: 'appstore_check_submission_risks',
230
+ });
231
+ steps.push(...risksToSteps(blockers, 'appstore_update_localization'));
232
+ steps.push(...risksToSteps(warnings, 'appstore_update_localization'));
233
+ }
234
+ // 3) What's New / promotional text / reviewer notes
235
+ steps.push({
236
+ state: 'pending',
237
+ title: "What's New (릴리즈 노트) 등록/갱신",
238
+ detail: '버전별 로컬라이제이션마다 입력',
239
+ tool: 'appstore_update_whats_new',
240
+ });
241
+ steps.push({
242
+ state: 'pending',
243
+ title: 'Promotional Text / Description / Keywords 검토',
244
+ tool: 'appstore_update_localization',
245
+ });
246
+ // 4) 제출
247
+ const canSubmit = versionId && buildAttached && !risks.some((r) => r.level === 'blocker');
248
+ steps.push({
249
+ state: canSubmit ? 'pending' : 'blocked',
250
+ title: '심사 제출 (reviewSubmissions v2)',
251
+ detail: canSubmit
252
+ ? '⚠️ 비가역. 제출 후 메타/빌드 변경 불가 (REJECTED 시 재편집).'
253
+ : '블로커 해결 후 가능',
254
+ tool: versionId ? `appstore_submit_for_review (versionId=${versionId})` : 'appstore_submit_for_review',
255
+ irreversible: true,
256
+ });
257
+ return renderPlan({ platform: 'App Store', identity: appId, version: versionString ?? 'latest' }, steps);
258
+ }
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import * as appstore from './appstore/tools.js';
14
14
  import * as appstoreScreenshots from './appstore/screenshots.js';
15
15
  import * as iam from './iam/tools.js';
16
16
  import { checkPlayStoreRisks, checkAppStoreRisks, formatRisks } from './checks/risks.js';
17
+ import { buildPlayStoreReleasePlan, buildAppStoreReleasePlan } from './checks/plan.js';
17
18
  import { validateAppStoreScreenshots, validatePlayStoreScreenshots, formatValidationResults } from './checks/screenshots.js';
18
19
  import { generateReleaseNotesFromCommits, formatGeneratedNotes } from './ai/notes.js';
19
20
  import { generateReviewReply, formatReviewReply } from './ai/review.js';
@@ -1042,6 +1043,42 @@ server.tool('appstore_delete_product', '⚠️ 비가역. App Store IAP 상품
1042
1043
  }
1043
1044
  return { content: [{ type: 'text', text: `✓ ${productId} 삭제 완료` }] };
1044
1045
  });
1046
+ // --- 배포 플랜 (read-only, AI에게 TodoWrite 지시) ---
1047
+ server.tool('playstore_plan_release', [
1048
+ 'Google Play 배포 플랜 — 인증/빌드/메타데이터/스크린샷/정책 위험을 한 번에 점검하고',
1049
+ '체크리스트로 반환합니다 (read-only, 아무것도 변경하지 않음).',
1050
+ '⚠️ AI 호출자 지시: 응답을 받으면 미완료 항목을 반드시 TodoWrite로 사용자에게 먼저 보여주고,',
1051
+ '사용자 동의 후 단계별로 기존 도구(playstore_update_listing, playstore_update_release_notes,',
1052
+ 'playstore_submit_release 등)를 호출하세요. submit_release(status=completed)는 비가역이므로 반드시 명시 동의 필요.',
1053
+ ].join(' '), {
1054
+ packageName: z.string().describe('Android 패키지명 (예: gg.pryzm.coffee)'),
1055
+ versionCode: z.string().optional().describe('확인할 versionCode. 미지정 시 트랙 최신 release 검사'),
1056
+ track: z.enum(['production', 'beta', 'alpha', 'internal']).optional().describe('대상 트랙 (기본: production)'),
1057
+ language: z.string().optional().describe('점검할 리스팅 언어 (기본: ko-KR)'),
1058
+ }, async ({ packageName, versionCode, track, language }) => {
1059
+ const auth = requirePlayStoreAuth();
1060
+ const text = await buildPlayStoreReleasePlan({
1061
+ auth,
1062
+ packageName,
1063
+ versionCode,
1064
+ track: track ?? 'production',
1065
+ language: language ?? 'ko-KR',
1066
+ });
1067
+ return { content: [{ type: 'text', text }] };
1068
+ });
1069
+ server.tool('appstore_plan_release', [
1070
+ 'App Store 배포 플랜 — 편집 가능한 버전/빌드 attach/메타/스크린샷/정책 위험을 한 번에 점검하고',
1071
+ '체크리스트로 반환합니다 (read-only).',
1072
+ '⚠️ AI 호출자 지시: 응답의 미완료 항목을 반드시 TodoWrite로 사용자에게 먼저 보여주고,',
1073
+ '사용자 동의 후 단계별로 기존 도구(appstore_update_localization, appstore_update_whats_new,',
1074
+ 'appstore_submit_for_review 등)를 호출하세요. submit_for_review는 비가역이므로 반드시 명시 동의 필요.',
1075
+ ].join(' '), {
1076
+ appId: z.string().describe('App Store 앱 ID (appstore_list_apps 결과의 id)'),
1077
+ versionString: z.string().optional().describe('대상 버전 (예: 1.3.0). 미지정 시 가장 최근 편집 가능 버전'),
1078
+ }, async ({ appId, versionString }) => {
1079
+ const text = await buildAppStoreReleasePlan({ appId, versionString });
1080
+ return { content: [{ type: 'text', text }] };
1081
+ });
1045
1082
  // --- App Store 심사 제출 (Submit for Review) ---
1046
1083
  server.tool('appstore_submit_for_review', [
1047
1084
  'App Store 버전을 심사에 제출 — 새 reviewSubmissions API 사용 (옛 /appStoreVersionSubmissions는 2024-01 deprecated).',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoonion/mimi-seed-mcp",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Mimi Seed MCP server — Firebase + AdMob + Google Play + App Store management for Claude Code / Cursor / any MCP client.",
5
5
  "type": "module",
6
6
  "bin": {