@yoonion/mimi-seed-mcp 0.6.6 → 0.7.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.
- package/README.md +21 -9
- package/dist/facebook/setup.d.ts +6 -0
- package/dist/facebook/setup.js +72 -0
- package/dist/googleads/setup-cli.d.ts +2 -0
- package/dist/googleads/setup-cli.js +86 -0
- package/dist/index.js +3 -0
- package/dist/instagram/setup.d.ts +4 -0
- package/dist/instagram/setup.js +70 -0
- package/dist/jenkins/config.d.ts +11 -1
- package/dist/jenkins/config.js +14 -1
- package/dist/jenkins/setup-cli.d.ts +2 -0
- package/dist/jenkins/setup-cli.js +75 -0
- package/dist/registers/auth.js +6 -5
- package/dist/registers/facebook.js +5 -75
- package/dist/registers/instagram.js +5 -65
- package/dist/social/setup-cli.d.ts +2 -0
- package/dist/social/setup-cli.js +133 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -42,17 +42,29 @@ npx -y @yoonion/mimi-seed-mcp mimi-seed-auth
|
|
|
42
42
|
|
|
43
43
|
브라우저가 열리면 Google 계정으로 로그인. 토큰은 `~/.mimi-seed/tokens.json`에 저장되고 자동 갱신됨.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
나머지 계정은 **마법사 하나로** 연결하는 게 가장 빠릅니다 — 뭐가 빠졌는지 보여주고, 각 토큰을 어디서
|
|
46
|
+
발급받는지도 알려줍니다:
|
|
46
47
|
|
|
47
48
|
```bash
|
|
48
|
-
npx
|
|
49
|
-
npx -y @yoonion/mimi-seed-mcp mimi-seed-playstore-auth # Google Play 서비스 계정 JSON
|
|
50
|
-
npx -y @yoonion/mimi-seed-mcp mimi-seed-bigquery-auth # BigQuery (Crashlytics export 등)
|
|
49
|
+
npx mimi-seed setup
|
|
51
50
|
```
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
개별로 실행하고 싶다면 (전부 대화형):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth # App Store Connect (API Key)
|
|
56
|
+
npx -y @yoonion/mimi-seed-mcp mimi-seed-playstore-auth # Google Play 서비스 계정 JSON
|
|
57
|
+
npx -y @yoonion/mimi-seed-mcp mimi-seed-bigquery-auth # BigQuery
|
|
58
|
+
npx -y @yoonion/mimi-seed-mcp mimi-seed-jenkins-auth # Jenkins (저장 전 서버 프로브)
|
|
59
|
+
npx -y @yoonion/mimi-seed-mcp mimi-seed-googleads-auth # Google Ads (저장 전 실제 호출로 검증)
|
|
60
|
+
npx -y @yoonion/mimi-seed-mcp mimi-seed-social-auth # Facebook / Instagram
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
각 자격증명을 **어디서 어떻게 발급받는지**는 [`docs/credentials.md`](../../docs/credentials.md) 참고.
|
|
64
|
+
|
|
65
|
+
- **App Store Connect**: Users and Access → Integrations에서 API Key 생성 후 Issuer ID / Key ID / .p8 경로 입력 → `~/.mimi-seed/appstore.json` (.p8은 **1회만** 다운로드됨)
|
|
66
|
+
- **Google Play**: 서비스 계정은 **선택** — OAuth 토큰이 `androidpublisher` 스코프를 갖고 있어 로컬 작업은 그대로 됩니다. CI/헤드리스에서만 필요.
|
|
67
|
+
- **BigQuery**: 선택 — OAuth로도 동작하며, Workspace 재인증 정책에 막힐 때만 서비스 계정이 필요합니다.
|
|
56
68
|
|
|
57
69
|
AI 기능(릴리즈 노트 생성, 리뷰 답변)을 쓰려면:
|
|
58
70
|
|
|
@@ -66,8 +78,8 @@ export ANTHROPIC_API_KEY=sk-ant-...
|
|
|
66
78
|
|
|
67
79
|
| 영역 | 도구 수 | 주요 도구 |
|
|
68
80
|
|------|---------|-----------|
|
|
69
|
-
| App Store Connect |
|
|
70
|
-
| Google Play |
|
|
81
|
+
| App Store Connect | 34 | `appstore_submit_for_review` / `appstore_upload_screenshot` / `appstore_update_product_review_note` / `appstore_upload_product_review_screenshot` |
|
|
82
|
+
| Google Play | 29 | `playstore_submit_release` / `playstore_promote_release` / `playstore_replace_images` / `playstore_reply_review` / `playstore_verify_service_account` |
|
|
71
83
|
| Firebase | 20 | `firebase_create_project` / `firebase_create_android_app` / `firebase_get_android_config` / `firebase_create_ios_app` |
|
|
72
84
|
| AdMob | 7 | `admob_list_apps` / `admob_create_ad_unit` / `admob_get_today_earnings` / `admob_get_report` |
|
|
73
85
|
| CI/CD (GitHub Actions · GitLab) | 6 | `ci_trigger_build` / `ci_get_build_status` / `ci_list_workflows` / `ci_cancel_build` |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Facebook 페이지 연결 — pageId 자동 조회 → 토큰 검증 → 검증 성공 시에만 저장.
|
|
2
|
+
//
|
|
3
|
+
// 이 로직은 MCP 도구(facebook_save_config)와 setup CLI(mimi-seed-social-auth)가 **공유**한다.
|
|
4
|
+
// 둘 중 하나에만 검증이 있으면 "CLI 로 저장했는데 도구가 못 읽는" 류의 드리프트가 생긴다.
|
|
5
|
+
import { saveFacebookConfig } from './config.js';
|
|
6
|
+
import * as api from './api.js';
|
|
7
|
+
const SIXTY_DAYS_MS = 60 * 24 * 60 * 60 * 1000;
|
|
8
|
+
export async function connectFacebook(pageAccessToken, pageId) {
|
|
9
|
+
let resolvedPageId = pageId;
|
|
10
|
+
if (!resolvedPageId) {
|
|
11
|
+
try {
|
|
12
|
+
const pages = await api.listAccessiblePages(pageAccessToken);
|
|
13
|
+
if (pages.length === 0) {
|
|
14
|
+
// 이미 Page Access Token 인 경우 — /me 가 곧 페이지다.
|
|
15
|
+
const res = await fetch(`https://graph.facebook.com/v21.0/me?fields=id,name&access_token=${pageAccessToken}`);
|
|
16
|
+
const data = (await res.json());
|
|
17
|
+
if (data.error)
|
|
18
|
+
throw new Error(`${data.error.message} (code ${data.error.code})`);
|
|
19
|
+
resolvedPageId = data.id;
|
|
20
|
+
}
|
|
21
|
+
else if (pages.length === 1) {
|
|
22
|
+
resolvedPageId = pages[0].id;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const list = pages.map((p) => ` • ${p.name} (${p.id})`).join('\n');
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
text: ['여러 페이지에 접근 가능합니다. pageId를 명시해주세요:', list].join('\n'),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
return { ok: false, text: `❌ pageId 자동 조회 실패: ${err.message}` };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const page = await api.getPage({ pageAccessToken, pageId: resolvedPageId });
|
|
38
|
+
saveFacebookConfig({
|
|
39
|
+
pageAccessToken,
|
|
40
|
+
pageId: resolvedPageId,
|
|
41
|
+
pageName: page.name,
|
|
42
|
+
expiresAt: new Date(Date.now() + SIXTY_DAYS_MS).toISOString(),
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
ok: true,
|
|
46
|
+
text: [
|
|
47
|
+
'✅ Facebook 페이지 연결 완료',
|
|
48
|
+
` 페이지: ${page.name}`,
|
|
49
|
+
` ID: ${page.id}`,
|
|
50
|
+
page.category ? ` 카테고리: ${page.category}` : '',
|
|
51
|
+
page.followers_count !== undefined
|
|
52
|
+
? ` 팔로워: ${page.followers_count.toLocaleString()}`
|
|
53
|
+
: '',
|
|
54
|
+
page.fan_count !== undefined ? ` 좋아요: ${page.fan_count.toLocaleString()}` : '',
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join('\n'),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
// 검증 실패 → 저장하지 않는다.
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
text: [
|
|
65
|
+
'❌ 토큰 검증 실패',
|
|
66
|
+
` ${err.message}`,
|
|
67
|
+
'',
|
|
68
|
+
'Page Access Token을 다시 확인하거나 facebook_list_pages로 페이지 목록을 조회하세요.',
|
|
69
|
+
].join('\n'),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// mimi-seed-googleads-auth — Google Ads 연결 설정 (~/.mimi-seed/google-ads.json).
|
|
3
|
+
//
|
|
4
|
+
// Ads 는 Google OAuth 토큰의 `adwords` 스코프를 타므로, 개발자 토큰만 있어도 스코프가 없으면
|
|
5
|
+
// 실패한다. 저장 후 listAccessibleCustomers 로 **실제 호출**해서 그 경우를 여기서 잡아낸다.
|
|
6
|
+
import readline from 'node:readline';
|
|
7
|
+
import { loadConfig, saveConfig, normalizeCustomerId } from './config.js';
|
|
8
|
+
import { listAccessibleCustomers } from './tools.js';
|
|
9
|
+
import { getAuthenticatedClient } from '../auth/google-auth.js';
|
|
10
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
11
|
+
const ask = (q) => new Promise((r) => rl.question(q, (a) => r(a.trim())));
|
|
12
|
+
async function main() {
|
|
13
|
+
console.log('');
|
|
14
|
+
console.log(' 🤖 Mimi Seed — Google Ads 연결');
|
|
15
|
+
console.log('');
|
|
16
|
+
const existing = loadConfig();
|
|
17
|
+
if (existing) {
|
|
18
|
+
console.log(` ✅ 이미 연결됨 (customerId: ${existing.customerId})`);
|
|
19
|
+
const answer = await ask(' 다시 설정할래? (y/N): ');
|
|
20
|
+
if (answer.toLowerCase() !== 'y') {
|
|
21
|
+
rl.close();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
console.log(' 필요한 것 2가지:');
|
|
26
|
+
console.log(' 1. Developer Token — Google Ads → 도구 및 설정 → 설정 → API 센터');
|
|
27
|
+
console.log(' ⚠️ 최초 발급은 "테스트" 등급이고, 실계정 조회는 **승인 심사**를 거쳐야 해.');
|
|
28
|
+
console.log(' 2. Customer ID — Google Ads 우상단 계정 번호 (예: 123-456-7890)');
|
|
29
|
+
console.log('');
|
|
30
|
+
const developerToken = await ask(' Developer Token: ');
|
|
31
|
+
const customerId = await ask(' Customer ID (예: 123-456-7890): ');
|
|
32
|
+
const loginCustomerId = await ask(' MCC(관리자) 계정 ID (선택, 엔터 스킵): ');
|
|
33
|
+
if (!developerToken || !customerId) {
|
|
34
|
+
console.log(' ❌ Developer Token 과 Customer ID 는 필수야.');
|
|
35
|
+
rl.close();
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const cfg = {
|
|
39
|
+
developerToken,
|
|
40
|
+
customerId: normalizeCustomerId(customerId),
|
|
41
|
+
loginCustomerId: loginCustomerId ? normalizeCustomerId(loginCustomerId) : undefined,
|
|
42
|
+
};
|
|
43
|
+
// 검증을 **저장보다 먼저** 한다 (jenkins/facebook/instagram 과 같은 규율).
|
|
44
|
+
// 먼저 저장해버리면 (a) 403 나는 설정인데 doctor 가 ✓ 로 표시하고,
|
|
45
|
+
// (b) 오타 한 번에 잘 되던 기존 설정이 백업 없이 덮여 날아간다.
|
|
46
|
+
const auth = getAuthenticatedClient();
|
|
47
|
+
if (!auth) {
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log(' ❌ Google OAuth 토큰이 없어 검증할 수 없어. 저장하지 않았어.');
|
|
50
|
+
console.log(' 먼저: npx -y @yoonion/mimi-seed-mcp mimi-seed-auth');
|
|
51
|
+
rl.close();
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(' 🔎 접근 가능한 고객 계정 조회 중...');
|
|
56
|
+
try {
|
|
57
|
+
const customers = await listAccessibleCustomers(auth, cfg);
|
|
58
|
+
console.log(` ✅ 접근 OK — 계정 ${customers.length}개`);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
62
|
+
console.log(` ❌ 검증 실패: ${msg}`);
|
|
63
|
+
console.log('');
|
|
64
|
+
if (/scope|insufficient|PERMISSION_DENIED/i.test(msg)) {
|
|
65
|
+
console.log(' → OAuth 토큰에 `adwords` 스코프가 없을 가능성이 커.');
|
|
66
|
+
console.log(' 재인증: npx -y @yoonion/mimi-seed-mcp mimi-seed-auth --force');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(' → Developer Token 등급(테스트/승인)과 Customer ID 를 다시 확인해줘.');
|
|
70
|
+
}
|
|
71
|
+
console.log('');
|
|
72
|
+
console.log(' 저장하지 않았어 (기존 설정이 있으면 그대로 유지된다).');
|
|
73
|
+
rl.close();
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
saveConfig(cfg);
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(' ✅ 저장 완료 → ~/.mimi-seed/google-ads.json');
|
|
79
|
+
console.log('');
|
|
80
|
+
rl.close();
|
|
81
|
+
}
|
|
82
|
+
main().catch((e) => {
|
|
83
|
+
console.error(`\n ❌ ${e instanceof Error ? e.message : String(e)}\n`);
|
|
84
|
+
rl.close();
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,9 @@ const SUBCOMMANDS = {
|
|
|
14
14
|
'mimi-seed-playstore-auth': () => import('./auth/playstore-setup-cli.js'),
|
|
15
15
|
'mimi-seed-appstore-auth': () => import('./appstore/setup-cli.js'),
|
|
16
16
|
'mimi-seed-bigquery-auth': () => import('./auth/bigquery-setup-cli.js'),
|
|
17
|
+
'mimi-seed-jenkins-auth': () => import('./jenkins/setup-cli.js'),
|
|
18
|
+
'mimi-seed-googleads-auth': () => import('./googleads/setup-cli.js'),
|
|
19
|
+
'mimi-seed-social-auth': () => import('./social/setup-cli.js'),
|
|
17
20
|
'mimi-seed-firebase': () => import('./firebase/cli.js'),
|
|
18
21
|
'mimi-seed-admob': () => import('./admob/cli.js'),
|
|
19
22
|
'mimi-seed-ga4': () => import('./ga4/cli.js'),
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ConnectResult } from '../facebook/setup.js';
|
|
2
|
+
/** IGAA… = Instagram Login(신규) · EAA… = Facebook Login(FB Page + IG Business 연결 필요) */
|
|
3
|
+
export declare function detectApiType(accessToken: string): string;
|
|
4
|
+
export declare function connectInstagram(accessToken: string, userId?: string, assumeIssuedNow?: boolean): Promise<ConnectResult>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Instagram 연결 — userId 자동 조회 → 토큰 검증 → 검증 성공 시에만 저장.
|
|
2
|
+
// MCP 도구(instagram_save_config)와 setup CLI(mimi-seed-social-auth)가 **공유**하는 구현.
|
|
3
|
+
import { saveInstagramConfig } from './config.js';
|
|
4
|
+
import * as api from './api.js';
|
|
5
|
+
const SIXTY_DAYS_MS = 60 * 24 * 60 * 60 * 1000;
|
|
6
|
+
/** IGAA… = Instagram Login(신규) · EAA… = Facebook Login(FB Page + IG Business 연결 필요) */
|
|
7
|
+
export function detectApiType(accessToken) {
|
|
8
|
+
return accessToken.startsWith('IGAA') ? 'Instagram Login' : 'Facebook Login';
|
|
9
|
+
}
|
|
10
|
+
export async function connectInstagram(accessToken, userId, assumeIssuedNow = true) {
|
|
11
|
+
const apiType = detectApiType(accessToken);
|
|
12
|
+
let resolvedUserId = userId;
|
|
13
|
+
if (!resolvedUserId) {
|
|
14
|
+
try {
|
|
15
|
+
resolvedUserId = await api.fetchUserId(accessToken);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
text: [
|
|
21
|
+
`❌ userId 자동 조회 실패 (${apiType})`,
|
|
22
|
+
` ${err.message}`,
|
|
23
|
+
'',
|
|
24
|
+
'userId를 명시적으로 전달하거나 토큰을 다시 확인하세요.',
|
|
25
|
+
].join('\n'),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const expiresAt = assumeIssuedNow
|
|
30
|
+
? new Date(Date.now() + SIXTY_DAYS_MS).toISOString()
|
|
31
|
+
: undefined;
|
|
32
|
+
try {
|
|
33
|
+
const account = await api.getAccount({ accessToken, userId: resolvedUserId });
|
|
34
|
+
saveInstagramConfig({
|
|
35
|
+
accessToken,
|
|
36
|
+
userId: resolvedUserId,
|
|
37
|
+
expiresAt,
|
|
38
|
+
username: account.username,
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
ok: true,
|
|
42
|
+
text: [
|
|
43
|
+
`✅ Instagram 연결 확인 완료 (${apiType})`,
|
|
44
|
+
` 계정: @${account.username}${account.name ? ` (${account.name})` : ''}`,
|
|
45
|
+
` ID: ${account.id}`,
|
|
46
|
+
account.account_type ? ` 타입: ${account.account_type}` : '',
|
|
47
|
+
account.followers_count !== undefined
|
|
48
|
+
? ` 팔로워: ${account.followers_count.toLocaleString()}`
|
|
49
|
+
: '',
|
|
50
|
+
account.media_count !== undefined ? ` 게시물: ${account.media_count}` : '',
|
|
51
|
+
expiresAt ? ` 토큰 만료(추정): ${expiresAt.slice(0, 10)}` : '',
|
|
52
|
+
]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join('\n'),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// 검증 실패 → 저장하지 않는다.
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
text: [
|
|
62
|
+
`❌ 토큰 검증 실패 (${apiType})`,
|
|
63
|
+
` userId: ${resolvedUserId}`,
|
|
64
|
+
` ${err.message}`,
|
|
65
|
+
'',
|
|
66
|
+
'토큰을 다시 발급받거나 userId를 직접 지정해보세요.',
|
|
67
|
+
].join('\n'),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
package/dist/jenkins/config.d.ts
CHANGED
|
@@ -2,7 +2,17 @@ export interface JenkinsConfig {
|
|
|
2
2
|
url: string;
|
|
3
3
|
username: string;
|
|
4
4
|
token: string;
|
|
5
|
+
jobAndroid?: string;
|
|
6
|
+
jobIos?: string;
|
|
5
7
|
}
|
|
6
8
|
export declare function loadJenkinsConfig(): JenkinsConfig | null;
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* jenkins.json 저장 — **기존 값 위에 병합한다** (통째로 덮어쓰지 않는다).
|
|
11
|
+
*
|
|
12
|
+
* 이 파일에는 두 종류의 필드가 산다: 연결 정보(url/username/token)는 MCP 도구
|
|
13
|
+
* `jenkins_save_config` 가, 빌드 잡 이름(jobAndroid/jobIos)은 CLI 의 setup 이 쓴다.
|
|
14
|
+
* 통째로 덮어쓰면 한쪽이 다른 쪽 값을 지운다 — 예전에 Jenkins 설정이 config.json 과
|
|
15
|
+
* jenkins.json 으로 갈라졌던 것과 같은 종류의 사고다. undefined 인 필드는 건드리지 않는다.
|
|
16
|
+
*/
|
|
17
|
+
export declare function saveJenkinsConfig(config: Partial<JenkinsConfig> & Pick<JenkinsConfig, 'url' | 'username' | 'token'>): void;
|
|
8
18
|
export declare function requireJenkinsConfig(): JenkinsConfig;
|
package/dist/jenkins/config.js
CHANGED
|
@@ -11,11 +11,24 @@ export function loadJenkinsConfig() {
|
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* jenkins.json 저장 — **기존 값 위에 병합한다** (통째로 덮어쓰지 않는다).
|
|
16
|
+
*
|
|
17
|
+
* 이 파일에는 두 종류의 필드가 산다: 연결 정보(url/username/token)는 MCP 도구
|
|
18
|
+
* `jenkins_save_config` 가, 빌드 잡 이름(jobAndroid/jobIos)은 CLI 의 setup 이 쓴다.
|
|
19
|
+
* 통째로 덮어쓰면 한쪽이 다른 쪽 값을 지운다 — 예전에 Jenkins 설정이 config.json 과
|
|
20
|
+
* jenkins.json 으로 갈라졌던 것과 같은 종류의 사고다. undefined 인 필드는 건드리지 않는다.
|
|
21
|
+
*/
|
|
14
22
|
export function saveJenkinsConfig(config) {
|
|
15
23
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
16
24
|
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
17
25
|
}
|
|
18
|
-
|
|
26
|
+
const existing = loadJenkinsConfig() ?? {};
|
|
27
|
+
const merged = { ...existing, ...stripUndefined(config) };
|
|
28
|
+
fs.writeFileSync(JENKINS_CONFIG_PATH, JSON.stringify(merged, null, 2), { mode: 0o600 });
|
|
29
|
+
}
|
|
30
|
+
function stripUndefined(o) {
|
|
31
|
+
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
|
|
19
32
|
}
|
|
20
33
|
export function requireJenkinsConfig() {
|
|
21
34
|
const cfg = loadJenkinsConfig();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// mimi-seed-jenkins-auth — Jenkins 연결 설정 (~/.mimi-seed/jenkins.json).
|
|
3
|
+
//
|
|
4
|
+
// jenkins_save_config 도구와 달리 저장 **전에** 실제 서버를 프로브한다 (listCredentials).
|
|
5
|
+
// 잘못된 URL/토큰이 조용히 저장돼서 deploy 때 터지는 걸 막는다.
|
|
6
|
+
import readline from 'node:readline';
|
|
7
|
+
import { loadJenkinsConfig, saveJenkinsConfig } from './config.js';
|
|
8
|
+
import { listCredentials } from './credentials.js';
|
|
9
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
+
const ask = (q) => new Promise((r) => rl.question(q, (a) => r(a.trim())));
|
|
11
|
+
async function main() {
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(' 🤖 Mimi Seed — Jenkins 연결');
|
|
14
|
+
console.log('');
|
|
15
|
+
const existing = loadJenkinsConfig();
|
|
16
|
+
if (existing) {
|
|
17
|
+
console.log(` ✅ 이미 연결됨 (${existing.url})`);
|
|
18
|
+
const answer = await ask(' 다시 설정할래? (y/N): ');
|
|
19
|
+
if (answer.toLowerCase() !== 'y') {
|
|
20
|
+
rl.close();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
console.log(' API Token 발급: Jenkins → [사용자 이름] → 설정 → API Token → "Add new Token"');
|
|
25
|
+
console.log(' 로컬 Jenkins 가 없어도 회사·원격 서버 URL 을 그대로 쓰면 돼.');
|
|
26
|
+
console.log('');
|
|
27
|
+
const url = await ask(' Jenkins URL (예: https://jenkins.company.com): ');
|
|
28
|
+
const username = await ask(' Jenkins 사용자 ID: ');
|
|
29
|
+
const token = await ask(' Jenkins API Token: ');
|
|
30
|
+
if (!url || !username || !token) {
|
|
31
|
+
console.log(' ❌ URL / 사용자 ID / 토큰은 모두 필요해.');
|
|
32
|
+
rl.close();
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(' 🔎 연결 확인 중...');
|
|
37
|
+
const cfg = { url: url.replace(/\/+$/, ''), username, token };
|
|
38
|
+
try {
|
|
39
|
+
const creds = await listCredentials(cfg);
|
|
40
|
+
console.log(` ✅ 연결 OK — 크리덴셜 ${creds.length}개 조회됨`);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
console.log(` ❌ 연결 실패: ${e instanceof Error ? e.message : String(e)}`);
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log(' 확인할 것:');
|
|
46
|
+
console.log(' • URL 이 Jenkins 대시보드 주소와 같은지 (예: https://jenkins.company.com)');
|
|
47
|
+
console.log(' • 비밀번호가 아니라 **API Token** 을 넣었는지');
|
|
48
|
+
console.log(' • 그 계정에 Credentials 조회 권한이 있는지');
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(' 저장하지 않았어. 값을 확인하고 다시 실행해줘.');
|
|
51
|
+
rl.close();
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
// deploy 가 트리거할 잡 이름 (선택) — 같은 파일에 함께 저장한다.
|
|
55
|
+
// 재설정(토큰 교체 등) 시 엔터로 넘기면 **기존 값을 유지한다** — 조용히 지워버리면
|
|
56
|
+
// 다음 `mimi-seed deploy` 가 "job 이 설정되지 않았습니다" 로 죽는다.
|
|
57
|
+
const keep = (cur) => (cur ? ` (현재: ${cur}, 엔터=유지)` : ' (선택, 엔터 스킵)');
|
|
58
|
+
const jobAndroid = await ask(` Android 빌드 Job 이름${keep(existing?.jobAndroid)}: `);
|
|
59
|
+
const jobIos = await ask(` iOS 빌드 Job 이름${keep(existing?.jobIos)}: `);
|
|
60
|
+
saveJenkinsConfig({
|
|
61
|
+
...cfg,
|
|
62
|
+
jobAndroid: jobAndroid || existing?.jobAndroid,
|
|
63
|
+
jobIos: jobIos || existing?.jobIos,
|
|
64
|
+
});
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log(' ✅ 저장 완료 → ~/.mimi-seed/jenkins.json');
|
|
67
|
+
console.log(' 확인: mimi-seed doctor · 빌드: mimi-seed deploy');
|
|
68
|
+
console.log('');
|
|
69
|
+
rl.close();
|
|
70
|
+
}
|
|
71
|
+
main().catch((e) => {
|
|
72
|
+
console.error(`\n ❌ ${e instanceof Error ? e.message : String(e)}\n`);
|
|
73
|
+
rl.close();
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
package/dist/registers/auth.js
CHANGED
|
@@ -121,7 +121,9 @@ export function registerAuthTools(server) {
|
|
|
121
121
|
lines.push(`✅ Play Store SA — ${saCount}개 등록 (${detail})`);
|
|
122
122
|
}
|
|
123
123
|
else {
|
|
124
|
-
|
|
124
|
+
// SA 는 CI/헤드리스 전용 — 로컬은 OAuth(androidpublisher scope)로 대부분의 Play 작업이 된다.
|
|
125
|
+
// (helpers.ts requirePlayStoreAuth 가 SA → OAuth 로 폴백한다.) "필수"로 표기하면 안 된다.
|
|
126
|
+
lines.push('⚠️ Play Store SA — 미설정 (선택 — 로컬은 OAuth 로 가능, CI/헤드리스는 필수) → setup_playstore_connection');
|
|
125
127
|
}
|
|
126
128
|
// 3. App Store Connect
|
|
127
129
|
const asc = getAppStoreCredentials();
|
|
@@ -188,12 +190,11 @@ export function registerAuthTools(server) {
|
|
|
188
190
|
if (oauthResult.status !== 'fresh' && oauthResult.status !== 'refreshed') {
|
|
189
191
|
missing.push(' 1. mimi_seed_auth_start (Google 계정 로그인 — Firebase/AdMob/GSC 등 필수)');
|
|
190
192
|
}
|
|
191
|
-
if (saCount === 0) {
|
|
192
|
-
missing.push(' 2. setup_playstore_connection(packageName=..., projectId=...) (Play Store 배포 필수)');
|
|
193
|
-
}
|
|
194
193
|
if (!asc) {
|
|
195
|
-
missing.push('
|
|
194
|
+
missing.push(' 2. npx -y @yoonion/mimi-seed-mcp mimi-seed-appstore-auth (App Store 배포 필수)');
|
|
196
195
|
}
|
|
196
|
+
// Play SA 는 "다음 단계 (필수)" 에 넣지 않는다 — OAuth 로 대부분의 Play 작업이 되므로.
|
|
197
|
+
// CI/헤드리스에서만 필요하고, 그 안내는 위 상태 줄(⚠️)에 이미 있다.
|
|
197
198
|
if (missing.length > 0) {
|
|
198
199
|
lines.push('', '── 다음 단계 (필수) ─────────────────────────────', ...missing);
|
|
199
200
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { loadFacebookConfig, requireFacebookConfig
|
|
2
|
+
import { loadFacebookConfig, requireFacebookConfig } from '../facebook/config.js';
|
|
3
|
+
import { connectFacebook } from '../facebook/setup.js';
|
|
3
4
|
import * as api from '../facebook/api.js';
|
|
4
5
|
export function registerFacebookTools(server) {
|
|
5
6
|
server.tool('facebook_save_config', [
|
|
@@ -11,80 +12,9 @@ export function registerFacebookTools(server) {
|
|
|
11
12
|
pageAccessToken: z.string().describe('Facebook Page Access Token (EAA..., long-lived 권장)'),
|
|
12
13
|
pageId: z.string().optional().describe('Facebook Page ID (생략 시 토큰에서 자동 조회)'),
|
|
13
14
|
}, async ({ pageAccessToken, pageId }) => {
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const me = await api.listAccessiblePages(pageAccessToken);
|
|
19
|
-
if (me.length === 0) {
|
|
20
|
-
// Try /me directly (already a Page Access Token)
|
|
21
|
-
const res = await fetch(`https://graph.facebook.com/v21.0/me?fields=id,name&access_token=${pageAccessToken}`);
|
|
22
|
-
const data = await res.json();
|
|
23
|
-
if (data.error)
|
|
24
|
-
throw new Error(`${data.error.message} (code ${data.error.code})`);
|
|
25
|
-
resolvedPageId = data.id;
|
|
26
|
-
}
|
|
27
|
-
else if (me.length === 1) {
|
|
28
|
-
resolvedPageId = me[0].id;
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
const list = me.map(p => ` • ${p.name} (${p.id})`).join('\n');
|
|
32
|
-
return {
|
|
33
|
-
content: [{
|
|
34
|
-
type: 'text',
|
|
35
|
-
text: [
|
|
36
|
-
'여러 페이지에 접근 가능합니다. pageId를 명시해주세요:',
|
|
37
|
-
list,
|
|
38
|
-
].join('\n'),
|
|
39
|
-
}],
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
return {
|
|
45
|
-
content: [{
|
|
46
|
-
type: 'text',
|
|
47
|
-
text: `❌ pageId 자동 조회 실패: ${err.message}`,
|
|
48
|
-
}],
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const cfg = { pageAccessToken, pageId: resolvedPageId };
|
|
54
|
-
const page = await api.getPage(cfg);
|
|
55
|
-
saveFacebookConfig({
|
|
56
|
-
pageAccessToken,
|
|
57
|
-
pageId: resolvedPageId,
|
|
58
|
-
pageName: page.name,
|
|
59
|
-
expiresAt: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString(),
|
|
60
|
-
});
|
|
61
|
-
return {
|
|
62
|
-
content: [{
|
|
63
|
-
type: 'text',
|
|
64
|
-
text: [
|
|
65
|
-
`✅ Facebook 페이지 연결 완료`,
|
|
66
|
-
` 페이지: ${page.name}`,
|
|
67
|
-
` ID: ${page.id}`,
|
|
68
|
-
page.category ? ` 카테고리: ${page.category}` : '',
|
|
69
|
-
page.followers_count !== undefined ? ` 팔로워: ${page.followers_count.toLocaleString()}` : '',
|
|
70
|
-
page.fan_count !== undefined ? ` 좋아요: ${page.fan_count.toLocaleString()}` : '',
|
|
71
|
-
].filter(Boolean).join('\n'),
|
|
72
|
-
}],
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
return {
|
|
77
|
-
content: [{
|
|
78
|
-
type: 'text',
|
|
79
|
-
text: [
|
|
80
|
-
`❌ 토큰 검증 실패`,
|
|
81
|
-
` ${err.message}`,
|
|
82
|
-
``,
|
|
83
|
-
`Page Access Token을 다시 확인하거나 facebook_list_pages로 페이지 목록을 조회하세요.`,
|
|
84
|
-
].join('\n'),
|
|
85
|
-
}],
|
|
86
|
-
};
|
|
87
|
-
}
|
|
15
|
+
// 구현은 facebook/setup.ts 에 있다 — mimi-seed-social-auth CLI 와 공유한다.
|
|
16
|
+
const result = await connectFacebook(pageAccessToken, pageId);
|
|
17
|
+
return { content: [{ type: 'text', text: result.text }] };
|
|
88
18
|
});
|
|
89
19
|
server.tool('facebook_list_pages', 'User Access Token으로 접근 가능한 Facebook 페이지 목록을 조회합니다. 페이지별 Page Access Token도 함께 반환.', {
|
|
90
20
|
userAccessToken: z.string().describe('Facebook User Access Token (EAA...)'),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { requireInstagramConfig
|
|
2
|
+
import { requireInstagramConfig } from '../instagram/config.js';
|
|
3
|
+
import { connectInstagram } from '../instagram/setup.js';
|
|
3
4
|
import * as api from '../instagram/api.js';
|
|
4
5
|
export function registerInstagramTools(server) {
|
|
5
6
|
server.tool('instagram_save_config', [
|
|
@@ -14,70 +15,9 @@ export function registerInstagramTools(server) {
|
|
|
14
15
|
userId: z.string().optional().describe('Instagram Business Account ID (생략 시 자동 조회)'),
|
|
15
16
|
assumeIssuedNow: z.boolean().default(true).describe('expiresAt = 지금 + 60일 자동 계산'),
|
|
16
17
|
}, async ({ accessToken, userId, assumeIssuedNow }) => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!resolvedUserId) {
|
|
21
|
-
try {
|
|
22
|
-
resolvedUserId = await api.fetchUserId(accessToken);
|
|
23
|
-
}
|
|
24
|
-
catch (err) {
|
|
25
|
-
return {
|
|
26
|
-
content: [{
|
|
27
|
-
type: 'text',
|
|
28
|
-
text: [
|
|
29
|
-
`❌ userId 자동 조회 실패 (${apiType})`,
|
|
30
|
-
` ${err.message}`,
|
|
31
|
-
``,
|
|
32
|
-
`userId를 명시적으로 전달하거나 토큰을 다시 확인하세요.`,
|
|
33
|
-
].join('\n'),
|
|
34
|
-
}],
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const expiresAt = assumeIssuedNow
|
|
39
|
-
? new Date(Date.now() + 60 * 24 * 60 * 60 * 1000).toISOString()
|
|
40
|
-
: undefined;
|
|
41
|
-
// 검증 — 잘못된 토큰이면 즉시 알림
|
|
42
|
-
try {
|
|
43
|
-
const account = await api.getAccount({ accessToken, userId: resolvedUserId });
|
|
44
|
-
// 검증 성공 → 최종 저장
|
|
45
|
-
saveInstagramConfig({
|
|
46
|
-
accessToken,
|
|
47
|
-
userId: resolvedUserId,
|
|
48
|
-
expiresAt,
|
|
49
|
-
username: account.username,
|
|
50
|
-
});
|
|
51
|
-
return {
|
|
52
|
-
content: [{
|
|
53
|
-
type: 'text',
|
|
54
|
-
text: [
|
|
55
|
-
`✅ Instagram 연결 확인 완료 (${apiType})`,
|
|
56
|
-
` 계정: @${account.username}${account.name ? ` (${account.name})` : ''}`,
|
|
57
|
-
` ID: ${account.id}`,
|
|
58
|
-
account.account_type ? ` 타입: ${account.account_type}` : '',
|
|
59
|
-
account.followers_count !== undefined ? ` 팔로워: ${account.followers_count.toLocaleString()}` : '',
|
|
60
|
-
account.media_count !== undefined ? ` 게시물: ${account.media_count}` : '',
|
|
61
|
-
expiresAt ? ` 토큰 만료(추정): ${expiresAt.slice(0, 10)}` : '',
|
|
62
|
-
].filter(Boolean).join('\n'),
|
|
63
|
-
}],
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
catch (err) {
|
|
67
|
-
// 검증 실패 — 저장은 하지 않고 사용자에게 알림
|
|
68
|
-
return {
|
|
69
|
-
content: [{
|
|
70
|
-
type: 'text',
|
|
71
|
-
text: [
|
|
72
|
-
`❌ 토큰 검증 실패 (${apiType})`,
|
|
73
|
-
` userId: ${resolvedUserId}`,
|
|
74
|
-
` ${err.message}`,
|
|
75
|
-
``,
|
|
76
|
-
`토큰을 다시 발급받거나 userId를 직접 지정해보세요.`,
|
|
77
|
-
].join('\n'),
|
|
78
|
-
}],
|
|
79
|
-
};
|
|
80
|
-
}
|
|
18
|
+
// 구현은 instagram/setup.ts 에 있다 — mimi-seed-social-auth CLI 와 공유한다.
|
|
19
|
+
const result = await connectInstagram(accessToken, userId, assumeIssuedNow);
|
|
20
|
+
return { content: [{ type: 'text', text: result.text }] };
|
|
81
21
|
});
|
|
82
22
|
server.tool('instagram_get_account', 'Instagram 계정 정보 조회 + 저장된 토큰 유효성 검증.', {}, async () => {
|
|
83
23
|
const cfg = requireInstagramConfig();
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// mimi-seed-social-auth — Facebook 페이지 / Instagram 계정 연결.
|
|
3
|
+
//
|
|
4
|
+
// 검증·저장 구현은 facebook/setup.ts · instagram/setup.ts 에 있고, MCP 도구
|
|
5
|
+
// (facebook_save_config / instagram_save_config)와 **같은 코드**를 호출한다.
|
|
6
|
+
// 토큰이 유효하지 않으면 저장하지 않는다.
|
|
7
|
+
//
|
|
8
|
+
// 사용:
|
|
9
|
+
// mimi-seed-social-auth → 무엇을 연결할지 물어봄
|
|
10
|
+
// mimi-seed-social-auth facebook → Facebook 만
|
|
11
|
+
// mimi-seed-social-auth instagram → Instagram 만
|
|
12
|
+
import readline from 'node:readline';
|
|
13
|
+
import { loadFacebookConfig } from '../facebook/config.js';
|
|
14
|
+
import { loadInstagramConfig } from '../instagram/config.js';
|
|
15
|
+
import { connectFacebook } from '../facebook/setup.js';
|
|
16
|
+
import { connectInstagram } from '../instagram/setup.js';
|
|
17
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
18
|
+
const ask = (q) => new Promise((r) => rl.question(q, (a) => r(a.trim())));
|
|
19
|
+
async function confirmReconnect(label, detail) {
|
|
20
|
+
console.log(` ✅ ${label} 이미 연결됨 (${detail})`);
|
|
21
|
+
const answer = await ask(' 다시 설정할래? (y/N): ');
|
|
22
|
+
return answer.toLowerCase() === 'y';
|
|
23
|
+
}
|
|
24
|
+
async function setupFacebook() {
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log(' ── Facebook 페이지 ──');
|
|
27
|
+
const existing = loadFacebookConfig();
|
|
28
|
+
if (existing && !(await confirmReconnect('Facebook', existing.pageName ?? existing.pageId))) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
console.log(' Page Access Token 발급:');
|
|
32
|
+
console.log(' 1. Meta 앱 → Graph API Explorer');
|
|
33
|
+
console.log(' 2. 권한: pages_show_list, pages_manage_posts, pages_read_engagement');
|
|
34
|
+
console.log(' 3. User Token 생성 → /me/accounts 호출 → 그 페이지의 access_token (EAA…)');
|
|
35
|
+
console.log(' (long-lived 토큰 권장. pageId 는 토큰에서 자동 조회돼.)');
|
|
36
|
+
console.log('');
|
|
37
|
+
const token = await ask(' Page Access Token (EAA…): ');
|
|
38
|
+
if (!token) {
|
|
39
|
+
console.log(' 건너뜀.');
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
let pageId = await ask(' Page ID (선택, 엔터 시 자동 조회): ');
|
|
43
|
+
console.log(' 🔎 토큰 검증 중...');
|
|
44
|
+
let result = await connectFacebook(token, pageId || undefined);
|
|
45
|
+
// 토큰이 여러 페이지에 닿으면 connectFacebook 은 목록만 주고 저장하지 않는다.
|
|
46
|
+
// 다시 실행하라고 내보내지 말고, 여기서 바로 골라 받는다.
|
|
47
|
+
if (!result.ok && result.text.includes('여러 페이지에')) {
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log(indent(result.text));
|
|
50
|
+
pageId = await ask('\n 어느 페이지를 쓸까? Page ID: ');
|
|
51
|
+
if (!pageId) {
|
|
52
|
+
console.log(' 건너뜀.');
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
console.log(' 🔎 토큰 검증 중...');
|
|
56
|
+
result = await connectFacebook(token, pageId);
|
|
57
|
+
}
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log(indent(result.text));
|
|
60
|
+
if (!result.ok)
|
|
61
|
+
console.log('\n (검증 실패 — 저장하지 않았어.)');
|
|
62
|
+
return result.ok;
|
|
63
|
+
}
|
|
64
|
+
async function setupInstagram() {
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log(' ── Instagram ──');
|
|
67
|
+
const existing = loadInstagramConfig();
|
|
68
|
+
if (existing && !(await confirmReconnect('Instagram', existing.username ?? existing.userId))) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
console.log(' Long-lived 토큰 두 형식 모두 지원 (자동 감지):');
|
|
72
|
+
console.log(' • IGAA… — Instagram Login (Meta 신규, Facebook 페이지 불필요)');
|
|
73
|
+
console.log(' • EAA… — Facebook Login (IG **비즈니스** 계정 + FB 페이지 연결 필요)');
|
|
74
|
+
console.log(' (userId 는 토큰에서 자동 조회돼. 토큰 수명은 약 60일.)');
|
|
75
|
+
console.log('');
|
|
76
|
+
const token = await ask(' Access Token (IGAA… 또는 EAA…): ');
|
|
77
|
+
if (!token) {
|
|
78
|
+
console.log(' 건너뜀.');
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
const userId = await ask(' Instagram Business Account ID (선택, 엔터 시 자동 조회): ');
|
|
82
|
+
console.log(' 🔎 토큰 검증 중...');
|
|
83
|
+
const result = await connectInstagram(token, userId || undefined);
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(indent(result.text));
|
|
86
|
+
if (!result.ok)
|
|
87
|
+
console.log('\n (검증 실패 — 저장하지 않았어.)');
|
|
88
|
+
return result.ok;
|
|
89
|
+
}
|
|
90
|
+
function indent(text) {
|
|
91
|
+
return text
|
|
92
|
+
.split('\n')
|
|
93
|
+
.map((l) => ` ${l}`)
|
|
94
|
+
.join('\n');
|
|
95
|
+
}
|
|
96
|
+
async function main() {
|
|
97
|
+
const target = (process.argv[2] ?? '').toLowerCase();
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(' 🤖 Mimi Seed — 소셜 계정 연결 (Facebook / Instagram)');
|
|
100
|
+
let ok = true;
|
|
101
|
+
if (target === 'facebook' || target === 'fb') {
|
|
102
|
+
ok = await setupFacebook();
|
|
103
|
+
}
|
|
104
|
+
else if (target === 'instagram' || target === 'ig') {
|
|
105
|
+
ok = await setupInstagram();
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const which = await ask('\n 무엇을 연결할까? [f] Facebook [i] Instagram [b] 둘 다: ');
|
|
109
|
+
const c = which.toLowerCase();
|
|
110
|
+
if (c === 'f')
|
|
111
|
+
ok = await setupFacebook();
|
|
112
|
+
else if (c === 'i')
|
|
113
|
+
ok = await setupInstagram();
|
|
114
|
+
else if (c === 'b') {
|
|
115
|
+
const fb = await setupFacebook();
|
|
116
|
+
const ig = await setupInstagram();
|
|
117
|
+
ok = fb && ig;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(' 취소됨.');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.log('');
|
|
124
|
+
rl.close();
|
|
125
|
+
// 토큰이 거부됐는데 exit 0 이면 호출한 마법사/스크립트가 성공으로 오인한다.
|
|
126
|
+
if (!ok)
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
main().catch((e) => {
|
|
130
|
+
console.error(`\n ❌ ${e instanceof Error ? e.message : String(e)}\n`);
|
|
131
|
+
rl.close();
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yoonion/mimi-seed-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Mimi Seed MCP server — Firebase + AdMob + Google Play + App Store management for Claude Code / Codex / Cursor / any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"mimi-seed-appstore-auth": "dist/appstore/setup-cli.js",
|
|
10
10
|
"mimi-seed-playstore-auth": "dist/auth/playstore-setup-cli.js",
|
|
11
11
|
"mimi-seed-bigquery-auth": "dist/auth/bigquery-setup-cli.js",
|
|
12
|
+
"mimi-seed-jenkins-auth": "dist/jenkins/setup-cli.js",
|
|
13
|
+
"mimi-seed-googleads-auth": "dist/googleads/setup-cli.js",
|
|
14
|
+
"mimi-seed-social-auth": "dist/social/setup-cli.js",
|
|
12
15
|
"mimi-seed-firebase": "dist/firebase/cli.js",
|
|
13
16
|
"mimi-seed-admob": "dist/admob/cli.js",
|
|
14
17
|
"mimi-seed-ga4": "dist/ga4/cli.js"
|