@yoonion/mimi-seed-mcp 0.20.0 → 0.21.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 +2 -2
- package/dist/ai/client.d.ts +3 -6
- package/dist/ai/client.js +3 -6
- package/dist/ai/notes.js +2 -1
- package/dist/ai/review.js +3 -14
- package/dist/android/keystore-store.js +1 -1
- package/dist/appstore/auth.js +1 -1
- package/dist/appstore/setup-cli.js +1 -1
- package/dist/auth/bigquery-auth.js +1 -1
- package/dist/auth/bigquery-setup-cli.js +1 -1
- package/dist/auth/cli.js +1 -1
- package/dist/auth/google-auth.js +1 -1
- package/dist/auth/playstore-auth.js +1 -1
- package/dist/auth/playstore-setup-cli.js +1 -1
- package/dist/checks/billing-network.d.ts +1 -1
- package/dist/checks/billing-network.js +1 -1
- package/dist/checks/release-doctor-cli.js +3 -3
- package/dist/ci/config.d.ts +2 -8
- package/dist/ci/config.js +1 -1
- package/dist/ci/github.js +4 -16
- package/dist/ci/gitlab.js +9 -14
- package/dist/core/ai.d.ts +17 -0
- package/dist/core/ai.js +40 -0
- package/dist/{lib → core}/atomic-write.d.ts +1 -4
- package/dist/{lib → core}/atomic-write.js +6 -4
- package/dist/core/ci.d.ts +16 -0
- package/dist/core/ci.js +29 -0
- package/dist/core/http-errors.d.ts +10 -0
- package/dist/core/http-errors.js +27 -0
- package/dist/core/jenkins.d.ts +7 -0
- package/dist/core/jenkins.js +3 -0
- package/dist/{lib → core}/lang.d.ts +2 -0
- package/dist/core/lang.js +36 -0
- package/dist/{lib → core}/project-manifest.d.ts +10 -1
- package/dist/{lib → core}/project-manifest.js +11 -4
- package/dist/googleads/config.js +1 -1
- package/dist/googleads/setup-cli.js +1 -1
- package/dist/iam/key-files.js +1 -1
- package/dist/jenkins/config.d.ts +2 -7
- package/dist/jenkins/config.js +1 -1
- package/dist/jenkins/setup-cli.js +1 -1
- package/dist/lib/http.js +3 -18
- package/dist/registers/auth.js +1 -1
- package/dist/registers/checks.js +1 -1
- package/dist/registers/facebook.js +1 -1
- package/dist/registers/instagram.js +1 -1
- package/dist/registers/playstore.js +1 -1
- package/dist/registers/threads.js +1 -1
- package/dist/social/profile-store.d.ts +1 -1
- package/dist/social/profile-store.js +2 -2
- package/dist/social/setup-cli.js +1 -1
- package/dist/tiktok-business/config.js +1 -1
- package/dist/tiktok-business/posting.js +1 -1
- package/dist/tiktok-business/setup-cli.js +1 -1
- package/dist/video/files.d.ts +1 -1
- package/dist/video/files.js +2 -2
- package/package.json +9 -5
- package/tool-manifest.json +2 -10
- package/dist/lib/lang.js +0 -33
- /package/dist/{checks → core/checks}/billing.d.ts +0 -0
- /package/dist/{checks → core/checks}/billing.js +0 -0
- /package/dist/{checks → core/checks}/release-doctor-render.d.ts +0 -0
- /package/dist/{checks → core/checks}/release-doctor-render.js +0 -0
- /package/dist/{checks → core/checks}/release-doctor.d.ts +0 -0
- /package/dist/{checks → core/checks}/release-doctor.js +0 -0
package/README.md
CHANGED
|
@@ -134,8 +134,8 @@ export ANTHROPIC_API_KEY=sk-ant-...
|
|
|
134
134
|
<!-- generated:readme-tools-table:start — edit scripts/docs-spec.mjs, then npm run plugin:sync -->
|
|
135
135
|
| 영역 | 도구 수 | 주요 도구 |
|
|
136
136
|
|------|---------|-----------|
|
|
137
|
-
| App Store Connect |
|
|
138
|
-
| Google Play |
|
|
137
|
+
| App Store Connect | 63 | `appstore_submit_for_review` / `appstore_get_weekly_insight` / `appstore_update_product_review_note` / `appstore_upload_product_review_screenshot` |
|
|
138
|
+
| Google Play | 38 | `playstore_submit_release` / `playstore_promote_release` / `playstore_replace_images` / `playstore_reply_review` / `playstore_verify_service_account` |
|
|
139
139
|
| Firebase | 21 | `firebase_create_project` / `firebase_get_remote_config_overview` / `firebase_get_android_config` / `firebase_create_ios_app` |
|
|
140
140
|
| AdMob | 7 | `admob_list_apps` / `admob_create_ad_unit` / `admob_get_today_earnings` / `admob_get_report` |
|
|
141
141
|
| CI (GitHub Actions / GitLab) | 6 | `ci_trigger_build` / `ci_get_build_status` / `ci_list_workflows` / `ci_cancel_build` |
|
package/dist/ai/client.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* 예전엔 두 패키지 6개 파일에 리터럴로 흩어져 있어서, 모델 교체가 6곳 수정 + 가드 0 이었다.
|
|
6
|
-
* CLI 쪽 쌍둥이 상수는 `packages/cli/src/ai-model.ts` 이고, 두 값의 일치는
|
|
7
|
-
* `__tests__/ai-model-parity.test.ts` 가 강제한다.
|
|
3
|
+
* Claude 모델 id — ai/*, video/* 가 모두 이 이름으로 쓴다. 값은 CLI 와 공유하는 packages/core 의
|
|
4
|
+
* `#core/ai.js` 한 곳에 있다(예전엔 두 패키지에 쌍둥이 상수가 있고 parity 테스트가 비교했다).
|
|
8
5
|
*/
|
|
9
|
-
export
|
|
6
|
+
export { AI_MODEL } from '#core/ai.js';
|
|
10
7
|
export declare const LOCALE_NAMES: Record<string, string>;
|
|
11
8
|
export declare function requireApiKey(): Anthropic;
|
|
12
9
|
export declare function parseJsonResponse<T>(text: string): T;
|
package/dist/ai/client.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* 예전엔 두 패키지 6개 파일에 리터럴로 흩어져 있어서, 모델 교체가 6곳 수정 + 가드 0 이었다.
|
|
6
|
-
* CLI 쪽 쌍둥이 상수는 `packages/cli/src/ai-model.ts` 이고, 두 값의 일치는
|
|
7
|
-
* `__tests__/ai-model-parity.test.ts` 가 강제한다.
|
|
3
|
+
* Claude 모델 id — ai/*, video/* 가 모두 이 이름으로 쓴다. 값은 CLI 와 공유하는 packages/core 의
|
|
4
|
+
* `#core/ai.js` 한 곳에 있다(예전엔 두 패키지에 쌍둥이 상수가 있고 parity 테스트가 비교했다).
|
|
8
5
|
*/
|
|
9
|
-
export
|
|
6
|
+
export { AI_MODEL } from '#core/ai.js';
|
|
10
7
|
export const LOCALE_NAMES = {
|
|
11
8
|
'ko': '한국어', 'ko-KR': '한국어',
|
|
12
9
|
'en': '영어', 'en-US': '영어', 'en-GB': '영어',
|
package/dist/ai/notes.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requireApiKey, parseJsonResponse, LOCALE_NAMES, AI_MODEL } from './client.js';
|
|
2
|
+
import { RELEASE_NOTES_MAX_TOKENS, RELEASE_NOTE_TONES } from '#core/ai.js';
|
|
2
3
|
const TONE_DESCRIPTIONS = {
|
|
3
4
|
concise: '간결한 버전 (3줄 이내, 불릿 포인트, 사용자 혜택 중심)',
|
|
4
5
|
detailed: '상세 버전 (주요 변경사항 5~8개, 불릿 포인트, 구체적 기능 설명)',
|
|
@@ -14,7 +15,7 @@ function commitsToText(commits) {
|
|
|
14
15
|
}
|
|
15
16
|
export async function generateReleaseNotesFromCommits(commits, opts = {}) {
|
|
16
17
|
const client = requireApiKey();
|
|
17
|
-
const { appName = '앱', tones = [
|
|
18
|
+
const { appName = '앱', tones = [...RELEASE_NOTE_TONES], locales = [], maxTokens = RELEASE_NOTES_MAX_TOKENS } = opts;
|
|
18
19
|
const commitsText = commitsToText(commits.slice(0, 50));
|
|
19
20
|
const localeList = locales.length > 0
|
|
20
21
|
? locales.map((l) => `"${l}": "${LOCALE_NAMES[l] ?? l}로 번역된 간결한 버전"`).join(',\n ')
|
package/dist/ai/review.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requireApiKey, parseJsonResponse, LOCALE_NAMES, AI_MODEL } from './client.js';
|
|
2
|
+
import { detectReviewSentiment, REVIEW_REPLY_MAX_TOKENS } from '#core/ai.js';
|
|
2
3
|
const TONE_GUIDES = {
|
|
3
4
|
friendly: '친근하고 따뜻하게. 이모지 1~2개 사용. 감사 인사로 시작.',
|
|
4
5
|
professional: '정중하고 공식적으로. 문제를 인정하고 해결책을 제시.',
|
|
@@ -12,26 +13,14 @@ const SENTIMENT_PROMPTS = {
|
|
|
12
13
|
bug_report: '버그 리포트입니다. 문제를 확인했음을 알리고, 수정 예정임을 전달하세요.',
|
|
13
14
|
feature_request: '기능 요청입니다. 피드백에 감사하고, 검토하겠다고 약속하세요.',
|
|
14
15
|
};
|
|
15
|
-
function detectSentiment(text) {
|
|
16
|
-
const lower = text.toLowerCase();
|
|
17
|
-
if (['버그', '오류', '안됨', 'crash', 'bug', 'error', 'broken'].some((w) => lower.includes(w)))
|
|
18
|
-
return 'bug_report';
|
|
19
|
-
if (['추가', '원해', '있으면', 'wish', 'feature', 'add', 'would like'].some((w) => lower.includes(w)))
|
|
20
|
-
return 'feature_request';
|
|
21
|
-
if (['별로', '실망', '짜증', 'terrible', 'worst', 'awful'].some((w) => lower.includes(w)))
|
|
22
|
-
return 'negative';
|
|
23
|
-
if (['좋아', '최고', '훌륭', 'great', 'excellent', 'love', 'perfect'].some((w) => lower.includes(w)))
|
|
24
|
-
return 'positive';
|
|
25
|
-
return 'neutral';
|
|
26
|
-
}
|
|
27
16
|
export async function generateReviewReply(opts) {
|
|
28
17
|
const client = requireApiKey();
|
|
29
18
|
const { reviewText, rating, appName = '앱', tone = 'friendly', language = 'ko', developerName } = opts;
|
|
30
|
-
const sentiment =
|
|
19
|
+
const sentiment = detectReviewSentiment(reviewText);
|
|
31
20
|
const langName = LOCALE_NAMES[language] ?? language;
|
|
32
21
|
const response = await client.messages.create({
|
|
33
22
|
model: AI_MODEL,
|
|
34
|
-
max_tokens:
|
|
23
|
+
max_tokens: REVIEW_REPLY_MAX_TOKENS,
|
|
35
24
|
system: `앱 개발자를 대신해 스토어 리뷰에 답변하는 전문가입니다. ${langName}로 150자 이내로 답변하세요. ${TONE_GUIDES[tone] ?? TONE_GUIDES.friendly} 개발자 이름: ${developerName ?? '개발팀'}`,
|
|
36
25
|
messages: [{
|
|
37
26
|
role: 'user',
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { writeCredentialFile, writeCredentialJson } from '
|
|
11
|
+
import { writeCredentialFile, writeCredentialJson } from '#core/atomic-write.js';
|
|
12
12
|
import { resolveInsideDir } from '../lib/path-containment.js';
|
|
13
13
|
export const KEYSTORE_FILE = 'upload.jks';
|
|
14
14
|
export const SIGNING_SECRETS_FILE = 'signing.json';
|
package/dist/appstore/auth.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import os from 'node:os';
|
|
7
|
-
import { writeCredentialJson } from '
|
|
7
|
+
import { writeCredentialJson } from '#core/atomic-write.js';
|
|
8
8
|
// Primary location under ~/.mimi-seed. Legacy ~/.preseed read as fallback
|
|
9
9
|
// during the rebrand window so existing App Store Connect sessions don't
|
|
10
10
|
// force a re-setup.
|
|
@@ -3,7 +3,7 @@ import { getAppStoreCredentials, mergeAppStoreCredentials } from './auth.js';
|
|
|
3
3
|
import { collectExistingSetupIntent, verifyAndSaveAppStoreCredentials } from './setup.js';
|
|
4
4
|
import readline from 'node:readline';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
|
-
import { resolveLang } from '
|
|
6
|
+
import { resolveLang } from '#core/lang.js';
|
|
7
7
|
// ko 가 원본이고 en 은 `typeof ko` 를 만족해야 한다 — 키를 빠뜨리면 컴파일이 깨진다.
|
|
8
8
|
const ko = {
|
|
9
9
|
title: ' 🍎 Mimi Seed — App Store Connect 연결',
|
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import { getAuthenticatedClient } from './google-auth.js';
|
|
6
|
-
import { writeCredentialFile } from '
|
|
6
|
+
import { writeCredentialFile } from '#core/atomic-write.js';
|
|
7
7
|
// BigQuery 전용 서비스 계정 키 저장 위치.
|
|
8
8
|
// 서비스 계정 인증은 Google Workspace 의 재인증(reauth) 정책에서 면제되므로,
|
|
9
9
|
// 사용자 OAuth 가 `invalid_rapt` 로 갱신 거부되는 환경에서도 안정적으로 동작한다.
|
|
@@ -4,7 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import { saveBigQueryServiceAccountJson, getBigQueryServiceAccountKey, } from './bigquery-auth.js';
|
|
5
5
|
import * as bigquery from '../bigquery/tools.js';
|
|
6
6
|
import { newJWT } from '../lib/google-auth-lite.js';
|
|
7
|
-
import { resolveLang } from '
|
|
7
|
+
import { resolveLang } from '#core/lang.js';
|
|
8
8
|
// ko 가 원본이고 en 은 `typeof ko` 를 만족해야 한다 — 키를 빠뜨리면 컴파일이 깨진다.
|
|
9
9
|
const ko = {
|
|
10
10
|
title: ' 🤖 Mimi Seed — BigQuery 서비스 계정 연결',
|
package/dist/auth/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { AuthError, classifyError } from './errors.js';
|
|
|
5
5
|
import { getMcpOAuthClient } from './constants.js';
|
|
6
6
|
import { AUTH_DOMAINS, DOMAIN_IDS, parseDomainList, summarizeGrantedDomains, } from './scopes.js';
|
|
7
7
|
import { openPrivateBrowser } from './browser.js';
|
|
8
|
-
import { resolveLang } from '
|
|
8
|
+
import { resolveLang } from '#core/lang.js';
|
|
9
9
|
// ko 가 원본이고 en 은 `typeof ko` 를 만족해야 한다 — 키를 빠뜨리면 컴파일이 깨진다.
|
|
10
10
|
// 여기 있는 건 전부 **터미널에 찍히는 사람용 문자열**이다. errors.ts 가 만드는
|
|
11
11
|
// code/message/hint 는 라이브러리 텍스트라 그대로 출력한다.
|
package/dist/auth/google-auth.js
CHANGED
|
@@ -9,7 +9,7 @@ import { getMcpOAuthClient } from './constants.js';
|
|
|
9
9
|
import { AuthError, classifyError } from './errors.js';
|
|
10
10
|
// 스코프 목록의 SSOT 는 scopes.ts (도메인 → 스코프 매핑). 여기서는 로그인 요청 조립만 한다.
|
|
11
11
|
import { scopesForDomains, IDENTITY_SCOPES } from './scopes.js';
|
|
12
|
-
import { writeCredentialJson } from '
|
|
12
|
+
import { writeCredentialJson } from '#core/atomic-write.js';
|
|
13
13
|
import { fetchWithTimeout } from '../lib/http.js';
|
|
14
14
|
// Primary config dir. Legacy `~/.preseed` is read as a fallback during the
|
|
15
15
|
// rebrand so existing auth sessions don't force a re-login; new writes go to
|
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import { PLAY_DEVELOPER_REPORTING_SCOPE } from './scopes.js';
|
|
6
|
-
import { writeCredentialFile } from '
|
|
6
|
+
import { writeCredentialFile } from '#core/atomic-write.js';
|
|
7
7
|
import { assertAndroidPackageName, isValidAndroidPackageName } from '../lib/package-name.js';
|
|
8
8
|
const CONFIG_DIR = path.join(os.homedir(), '.mimi-seed');
|
|
9
9
|
const SA_DIR = path.join(CONFIG_DIR, 'play-service-accounts');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { saveServiceAccountJson, getServiceAccountJson } from './playstore-auth.js';
|
|
3
3
|
import readline from 'node:readline';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
-
import { resolveLang } from '
|
|
5
|
+
import { resolveLang } from '#core/lang.js';
|
|
6
6
|
// ko 가 원본이고 en 은 `typeof ko` 를 만족해야 한다 — 키를 빠뜨리면 컴파일이 깨진다.
|
|
7
7
|
const ko = {
|
|
8
8
|
title: ' 🤖 Mimi Seed — Google Play 서비스 계정 연결',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { type BillingTransitiveResolver } from '
|
|
1
|
+
import { type BillingTransitiveResolver } from '#core/checks/billing.js';
|
|
2
2
|
export declare const resolveOpenIapBilling: BillingTransitiveResolver;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fetchWithTimeout } from '../lib/http.js';
|
|
2
|
-
import { billingVersionFromPom } from '
|
|
2
|
+
import { billingVersionFromPom } from '#core/checks/billing.js';
|
|
3
3
|
export const resolveOpenIapBilling = async (openIapVersion) => {
|
|
4
4
|
const pomUrl = `https://repo.maven.apache.org/maven2/io/github/hyochan/openiap/openiap-google/${openIapVersion}/openiap-google-${openIapVersion}.pom`;
|
|
5
5
|
const response = await fetchWithTimeout(pomUrl, {}, { timeoutMs: 15_000, maxAttempts: 2 });
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { resolveLang } from '
|
|
3
|
-
import { scanReleaseDoctor } from '
|
|
2
|
+
import { resolveLang } from '#core/lang.js';
|
|
3
|
+
import { scanReleaseDoctor } from '#core/checks/release-doctor.js';
|
|
4
4
|
import { parseReleaseDoctorArgs, RELEASE_DOCTOR_USAGE } from './release-doctor-cli-args.js';
|
|
5
|
-
import { renderReleaseDoctor } from '
|
|
5
|
+
import { renderReleaseDoctor } from '#core/checks/release-doctor-render.js';
|
|
6
6
|
async function main() {
|
|
7
7
|
const args = parseReleaseDoctorArgs(process.argv.slice(2));
|
|
8
8
|
if (args.help) {
|
package/dist/ci/config.d.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
provider: CiProvider;
|
|
4
|
-
token: string;
|
|
5
|
-
owner: string;
|
|
6
|
-
repo: string;
|
|
7
|
-
host?: string;
|
|
8
|
-
}
|
|
1
|
+
import type { CiConfig } from '#core/ci.js';
|
|
2
|
+
export type { CiConfig, CiProvider } from '#core/ci.js';
|
|
9
3
|
export declare function loadCiConfig(): CiConfig | null;
|
|
10
4
|
export declare function saveCiConfig(config: CiConfig): void;
|
|
11
5
|
export declare function requireCiConfig(): CiConfig;
|
package/dist/ci/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { writeCredentialJson } from '
|
|
4
|
+
import { writeCredentialJson } from '#core/atomic-write.js';
|
|
5
5
|
const CONFIG_DIR = path.join(os.homedir(), '.mimi-seed');
|
|
6
6
|
const CI_CONFIG_PATH = path.join(CONFIG_DIR, 'ci.json');
|
|
7
7
|
export function loadCiConfig() {
|
package/dist/ci/github.js
CHANGED
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
import { fetchWithTimeout } from '../lib/http.js';
|
|
2
2
|
import { encodePathSegment } from '../lib/url-path.js';
|
|
3
|
-
|
|
4
|
-
// GitHub Enterprise: host = https://github.example.com → API base = https://github.example.com/api/v3
|
|
5
|
-
if (cfg.host)
|
|
6
|
-
return `${cfg.host.replace(/\/$/, '')}/api/v3`;
|
|
7
|
-
return 'https://api.github.com';
|
|
8
|
-
}
|
|
9
|
-
function headers(token) {
|
|
10
|
-
return {
|
|
11
|
-
Authorization: `Bearer ${token}`,
|
|
12
|
-
Accept: 'application/vnd.github+json',
|
|
13
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
14
|
-
'Content-Type': 'application/json',
|
|
15
|
-
};
|
|
16
|
-
}
|
|
3
|
+
import { githubApiBase, githubHeaders } from '#core/ci.js';
|
|
17
4
|
async function ghFetch(cfg, endpoint, options) {
|
|
18
|
-
|
|
5
|
+
// base URL · 헤더 규칙은 CLI 의 배포 경로와 공유한다(#core/ci.js) — 검증과 실제 호출이 같은 URL 을 만든다.
|
|
6
|
+
const res = await fetchWithTimeout(`${githubApiBase(cfg)}${endpoint}`, {
|
|
19
7
|
...options,
|
|
20
|
-
headers: { ...
|
|
8
|
+
headers: { ...githubHeaders(cfg.token), ...(options?.headers ?? {}) },
|
|
21
9
|
});
|
|
22
10
|
if (res.status === 204)
|
|
23
11
|
return null;
|
package/dist/ci/gitlab.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { fetchWithTimeout } from '../lib/http.js';
|
|
2
2
|
import { encodePathSegment } from '../lib/url-path.js';
|
|
3
|
-
|
|
4
|
-
return `${cfg.host ?? 'https://gitlab.com'}/api/v4`;
|
|
5
|
-
}
|
|
6
|
-
// GitLab accepts both numeric project ID and "namespace%2Frepo" path
|
|
7
|
-
function projectId(cfg) {
|
|
8
|
-
return encodeURIComponent(`${cfg.owner}/${cfg.repo}`);
|
|
9
|
-
}
|
|
3
|
+
import { gitlabApiBase, gitlabProjectId } from '#core/ci.js';
|
|
10
4
|
function headers(token) {
|
|
11
5
|
return {
|
|
12
6
|
'PRIVATE-TOKEN': token,
|
|
@@ -14,7 +8,8 @@ function headers(token) {
|
|
|
14
8
|
};
|
|
15
9
|
}
|
|
16
10
|
async function glFetch(cfg, endpoint, options) {
|
|
17
|
-
|
|
11
|
+
// base URL · 프로젝트 id 규칙은 CLI 의 배포 경로와 공유한다(#core/ci.js).
|
|
12
|
+
const res = await fetchWithTimeout(`${gitlabApiBase(cfg)}${endpoint}`, {
|
|
18
13
|
...options,
|
|
19
14
|
headers: { ...headers(cfg.token), ...(options?.headers ?? {}) },
|
|
20
15
|
});
|
|
@@ -27,8 +22,8 @@ async function glFetch(cfg, endpoint, options) {
|
|
|
27
22
|
}
|
|
28
23
|
export async function listWorkflows(cfg) {
|
|
29
24
|
const [schedules, triggers] = await Promise.all([
|
|
30
|
-
glFetch(cfg, `/projects/${
|
|
31
|
-
glFetch(cfg, `/projects/${
|
|
25
|
+
glFetch(cfg, `/projects/${gitlabProjectId(cfg)}/pipeline_schedules`),
|
|
26
|
+
glFetch(cfg, `/projects/${gitlabProjectId(cfg)}/triggers`),
|
|
32
27
|
]);
|
|
33
28
|
return {
|
|
34
29
|
schedules: schedules.map((s) => ({
|
|
@@ -50,25 +45,25 @@ export async function triggerBuild(cfg, ref = 'main', variables = {}) {
|
|
|
50
45
|
const body = { ref };
|
|
51
46
|
if (vars.length > 0)
|
|
52
47
|
body.variables = vars;
|
|
53
|
-
const data = await glFetch(cfg, `/projects/${
|
|
48
|
+
const data = await glFetch(cfg, `/projects/${gitlabProjectId(cfg)}/pipeline`, {
|
|
54
49
|
method: 'POST',
|
|
55
50
|
body: JSON.stringify(body),
|
|
56
51
|
});
|
|
57
52
|
return normalize(data);
|
|
58
53
|
}
|
|
59
54
|
export async function getBuildStatus(cfg, pipelineId) {
|
|
60
|
-
const data = await glFetch(cfg, `/projects/${
|
|
55
|
+
const data = await glFetch(cfg, `/projects/${gitlabProjectId(cfg)}/pipelines/${encodePathSegment(pipelineId)}`);
|
|
61
56
|
return normalize(data);
|
|
62
57
|
}
|
|
63
58
|
export async function listRecentBuilds(cfg, ref, limit = 10) {
|
|
64
|
-
let endpoint = `/projects/${
|
|
59
|
+
let endpoint = `/projects/${gitlabProjectId(cfg)}/pipelines?per_page=${limit}&order_by=id&sort=desc`;
|
|
65
60
|
if (ref)
|
|
66
61
|
endpoint += `&ref=${encodeURIComponent(ref)}`;
|
|
67
62
|
const data = await glFetch(cfg, endpoint);
|
|
68
63
|
return data.map(normalize);
|
|
69
64
|
}
|
|
70
65
|
export async function cancelBuild(cfg, pipelineId) {
|
|
71
|
-
await glFetch(cfg, `/projects/${
|
|
66
|
+
await glFetch(cfg, `/projects/${gitlabProjectId(cfg)}/pipelines/${encodePathSegment(pipelineId)}/cancel`, {
|
|
72
67
|
method: 'POST',
|
|
73
68
|
});
|
|
74
69
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Claude 모델 id 의 SSOT. 모델 교체는 이 한 줄이다 (다른 곳의 리터럴은 ai-model.test.ts 가 막는다). */
|
|
2
|
+
export declare const AI_MODEL = "claude-haiku-4-5-20251001";
|
|
3
|
+
/** 리뷰 답변 톤 — CLI `--tone` 값이자 MCP `generate_review_reply` 의 tone 인자. 번역하지 않는다. */
|
|
4
|
+
export declare const REVIEW_TONES: readonly ["friendly", "professional", "empathetic", "brief"];
|
|
5
|
+
export type ReviewTone = (typeof REVIEW_TONES)[number];
|
|
6
|
+
/** detectReviewSentiment 의 반환값 = 두 생성기의 감정별 지시문 테이블 키. */
|
|
7
|
+
export declare const REVIEW_SENTIMENTS: readonly ["positive", "negative", "neutral", "bug_report", "feature_request"];
|
|
8
|
+
export type ReviewSentiment = (typeof REVIEW_SENTIMENTS)[number];
|
|
9
|
+
/** 리뷰 본문 → 감정. 어느 키워드에도 안 걸리면 neutral. */
|
|
10
|
+
export declare function detectReviewSentiment(text: string): ReviewSentiment;
|
|
11
|
+
/** 리뷰 답변의 토큰 상한. */
|
|
12
|
+
export declare const REVIEW_REPLY_MAX_TOKENS = 500;
|
|
13
|
+
/** 릴리즈 노트의 토큰 상한 — 낮은 쪽만 다국어(localized) JSON 이 잘린다. */
|
|
14
|
+
export declare const RELEASE_NOTES_MAX_TOKENS = 2000;
|
|
15
|
+
/** 릴리즈 노트 톤 이름 — 응답 JSON 의 파싱 계약이라 번역하지 않는다 (+ 다국어 버전 키 `localized`). */
|
|
16
|
+
export declare const RELEASE_NOTE_TONES: readonly ["concise", "detailed", "marketing"];
|
|
17
|
+
export type ReleaseNoteTone = (typeof RELEASE_NOTE_TONES)[number];
|
package/dist/core/ai.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// AI 생성기(리뷰 답변 · 릴리즈 노트)의 **언어와 무관한 계약** — CLI 와 mcp-server 가 공유한다.
|
|
2
|
+
//
|
|
3
|
+
// 생성기 자체는 두 벌이다. 그건 사고가 아니라 선택이다: CLI 판은 프롬프트 지시문까지
|
|
4
|
+
// `catalog(ko, en)` 을 거쳐 영어 사용자에게 영어 톤 가이드를 주고, MCP 판은 한국어 고정이며,
|
|
5
|
+
// 두 릴리즈 노트 생성기는 응답 스키마부터 다르다(MCP `tones[]` 배열 vs CLI 평면 키).
|
|
6
|
+
//
|
|
7
|
+
// 갈라지면 안 되는 것만 여기 둔다. 예전엔 이것들이 두 패키지에 리터럴로 흩어져 있었고
|
|
8
|
+
// ai-model-parity / ai-parity 테스트가 소스 텍스트를 정규식으로 비교했다 — 실제로 노트의
|
|
9
|
+
// max_tokens 는 1500 vs 2000 으로 이미 갈라져 있었다. 이제 컴파일러가 강제한다.
|
|
10
|
+
/** Claude 모델 id 의 SSOT. 모델 교체는 이 한 줄이다 (다른 곳의 리터럴은 ai-model.test.ts 가 막는다). */
|
|
11
|
+
export const AI_MODEL = 'claude-haiku-4-5-20251001';
|
|
12
|
+
/** 리뷰 답변 톤 — CLI `--tone` 값이자 MCP `generate_review_reply` 의 tone 인자. 번역하지 않는다. */
|
|
13
|
+
export const REVIEW_TONES = ['friendly', 'professional', 'empathetic', 'brief'];
|
|
14
|
+
/** detectReviewSentiment 의 반환값 = 두 생성기의 감정별 지시문 테이블 키. */
|
|
15
|
+
export const REVIEW_SENTIMENTS = ['positive', 'negative', 'neutral', 'bug_report', 'feature_request'];
|
|
16
|
+
/**
|
|
17
|
+
* 감정 분류 키워드. **순서가 의미다** — 앞의 규칙이 이긴다("버그인데 최고" 는 bug_report).
|
|
18
|
+
* 한국어가 섞여 있는 건 번역 대상이 아니라 매칭 대상이기 때문이다.
|
|
19
|
+
*/
|
|
20
|
+
const SENTIMENT_KEYWORDS = [
|
|
21
|
+
['bug_report', ['버그', '오류', '안됨', 'crash', 'bug', 'error', 'broken']],
|
|
22
|
+
['feature_request', ['추가', '원해', '있으면', 'wish', 'feature', 'add', 'would like']],
|
|
23
|
+
['negative', ['별로', '실망', '짜증', 'terrible', 'worst', 'awful']],
|
|
24
|
+
['positive', ['좋아', '최고', '훌륭', 'great', 'excellent', 'love', 'perfect']],
|
|
25
|
+
];
|
|
26
|
+
/** 리뷰 본문 → 감정. 어느 키워드에도 안 걸리면 neutral. */
|
|
27
|
+
export function detectReviewSentiment(text) {
|
|
28
|
+
const lower = text.toLowerCase();
|
|
29
|
+
for (const [sentiment, keywords] of SENTIMENT_KEYWORDS) {
|
|
30
|
+
if (keywords.some((w) => lower.includes(w)))
|
|
31
|
+
return sentiment;
|
|
32
|
+
}
|
|
33
|
+
return 'neutral';
|
|
34
|
+
}
|
|
35
|
+
/** 리뷰 답변의 토큰 상한. */
|
|
36
|
+
export const REVIEW_REPLY_MAX_TOKENS = 500;
|
|
37
|
+
/** 릴리즈 노트의 토큰 상한 — 낮은 쪽만 다국어(localized) JSON 이 잘린다. */
|
|
38
|
+
export const RELEASE_NOTES_MAX_TOKENS = 2000;
|
|
39
|
+
/** 릴리즈 노트 톤 이름 — 응답 JSON 의 파싱 계약이라 번역하지 않는다 (+ 다국어 버전 키 `localized`). */
|
|
40
|
+
export const RELEASE_NOTE_TONES = ['concise', 'detailed', 'marketing'];
|
|
@@ -22,10 +22,7 @@ export interface AtomicWriteOptions {
|
|
|
22
22
|
* 왜: Windows 는 다른 프로세스가 대상 파일을 열고 있으면(백신·검색 인덱서·OneDrive, 또는
|
|
23
23
|
* tokens.json 을 읽는 중인 다른 mimi-seed 프로세스) rename 을 EPERM/EBUSY/EACCES 로 거절한다.
|
|
24
24
|
* 그 핸들은 보통 수십~수백 ms 안에 닫히므로, 짧게 물러났다 다시 시도하면 대부분 통과한다.
|
|
25
|
-
*
|
|
26
|
-
* ⚠️ CLI 쪽 원자적 쓰기 사본(packages/cli)과 **이름·값이 완전히 같아야 한다**
|
|
27
|
-
* (RENAME_RETRY_CODES / RENAME_RETRY_DELAYS_MS / renameWithRetry). 한쪽을 바꾸면 다른 쪽도 맞출 것 —
|
|
28
|
-
* 두 패키지는 서로 import 하지 않으므로 이 상수가 유일한 동기화 지점이다.
|
|
25
|
+
* 동기 writer 안에서 기다리므로 총합(1초)이 곧 최악의 블로킹 시간이다 — 늘리지 말 것.
|
|
29
26
|
*/
|
|
30
27
|
export declare const RENAME_RETRY_DELAYS_MS: readonly [10, 20, 40, 80, 160, 320, 370];
|
|
31
28
|
/** 재시도할 오류 코드. ENOENT 등은 잠금이 아니라 진짜 실패이므로 즉시 던진다. */
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
//
|
|
9
9
|
// temp 파일에 다 쓴 뒤 rename(2) 하면 교체가 원자적이다 — 읽는 쪽은 항상 옛 내용 아니면
|
|
10
10
|
// 새 내용을 보고, 그 중간은 존재하지 않는다.
|
|
11
|
+
//
|
|
12
|
+
// CLI 와 mcp-server 가 **이 한 구현**을 쓴다. 예전엔 패키지마다 사본이 있었고, Windows 재시도
|
|
13
|
+
// 스케줄이 갈라지지 않게 rename-retry-parity 테스트가 두 사본의 리터럴을 비교했다 — 같은
|
|
14
|
+
// tokens.json 을 두고 한쪽만 잠금 경합에서 살아남는 상황을 막으려고. 이제 갈라질 사본이 없다.
|
|
15
|
+
// 어느 모듈이 이 writer 를 거쳐야 하는지는 각 패키지의 atomic-write.test.ts 가 강제한다.
|
|
11
16
|
import { chmodSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
12
17
|
import { randomUUID } from 'node:crypto';
|
|
13
18
|
import path from 'node:path';
|
|
@@ -21,10 +26,7 @@ export const CREDENTIAL_DIR_MODE = 0o700;
|
|
|
21
26
|
* 왜: Windows 는 다른 프로세스가 대상 파일을 열고 있으면(백신·검색 인덱서·OneDrive, 또는
|
|
22
27
|
* tokens.json 을 읽는 중인 다른 mimi-seed 프로세스) rename 을 EPERM/EBUSY/EACCES 로 거절한다.
|
|
23
28
|
* 그 핸들은 보통 수십~수백 ms 안에 닫히므로, 짧게 물러났다 다시 시도하면 대부분 통과한다.
|
|
24
|
-
*
|
|
25
|
-
* ⚠️ CLI 쪽 원자적 쓰기 사본(packages/cli)과 **이름·값이 완전히 같아야 한다**
|
|
26
|
-
* (RENAME_RETRY_CODES / RENAME_RETRY_DELAYS_MS / renameWithRetry). 한쪽을 바꾸면 다른 쪽도 맞출 것 —
|
|
27
|
-
* 두 패키지는 서로 import 하지 않으므로 이 상수가 유일한 동기화 지점이다.
|
|
29
|
+
* 동기 writer 안에서 기다리므로 총합(1초)이 곧 최악의 블로킹 시간이다 — 늘리지 말 것.
|
|
28
30
|
*/
|
|
29
31
|
export const RENAME_RETRY_DELAYS_MS = [10, 20, 40, 80, 160, 320, 370];
|
|
30
32
|
/** 재시도할 오류 코드. ENOENT 등은 잠금이 아니라 진짜 실패이므로 즉시 던진다. */
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type CiProvider = 'github' | 'gitlab';
|
|
2
|
+
export interface CiConfig {
|
|
3
|
+
provider: CiProvider;
|
|
4
|
+
token: string;
|
|
5
|
+
owner: string;
|
|
6
|
+
repo: string;
|
|
7
|
+
/** GitHub Enterprise / GitLab self-hosted 의 origin (예: https://git.example.com). 없으면 공용 호스트. */
|
|
8
|
+
host?: string;
|
|
9
|
+
}
|
|
10
|
+
/** GitHub REST base. Enterprise 는 `<host>/api/v3`. */
|
|
11
|
+
export declare function githubApiBase(cfg: Pick<CiConfig, 'host'>): string;
|
|
12
|
+
export declare function githubHeaders(token: string): Record<string, string>;
|
|
13
|
+
/** GitLab REST base (v4). self-hosted 는 `<host>/api/v4`. */
|
|
14
|
+
export declare function gitlabApiBase(cfg: Pick<CiConfig, 'host'>): string;
|
|
15
|
+
/** GitLab 은 숫자 id 대신 URL 인코딩한 `namespace/repo` 경로를 프로젝트 id 로 받는다. */
|
|
16
|
+
export declare function gitlabProjectId(cfg: Pick<CiConfig, 'owner' | 'repo'>): string;
|
package/dist/core/ci.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// CI 프로바이더(GitHub Actions / GitLab CI) 연결의 **공유 계약** — `~/.mimi-seed/ci.json` 의 모양과
|
|
2
|
+
// REST base URL 규칙.
|
|
3
|
+
//
|
|
4
|
+
// ci.json 은 두 패키지가 쓰는 문서화된 예외다(CLI `mimi-seed deploy` 셋업, MCP `ci_save_config`).
|
|
5
|
+
// 그래서 모양이 한 곳에 있어야 하고, base URL 을 조립하는 규칙도 같아야 한다 — 다르면 "CLI 로 검증은
|
|
6
|
+
// 통과했는데 MCP 도구는 404" 가 된다. 클라이언트 자체(요청·폴링·에러 문구·경로 인코딩)는 두 패키지의
|
|
7
|
+
// 쓰임이 달라 각자 둔다: cli/src/ci-providers.ts, mcp-server/src/ci/{github,gitlab}.ts.
|
|
8
|
+
/** GitHub REST base. Enterprise 는 `<host>/api/v3`. */
|
|
9
|
+
export function githubApiBase(cfg) {
|
|
10
|
+
if (cfg.host)
|
|
11
|
+
return `${cfg.host.replace(/\/$/, '')}/api/v3`;
|
|
12
|
+
return 'https://api.github.com';
|
|
13
|
+
}
|
|
14
|
+
export function githubHeaders(token) {
|
|
15
|
+
return {
|
|
16
|
+
Authorization: `Bearer ${token}`,
|
|
17
|
+
Accept: 'application/vnd.github+json',
|
|
18
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** GitLab REST base (v4). self-hosted 는 `<host>/api/v4`. */
|
|
23
|
+
export function gitlabApiBase(cfg) {
|
|
24
|
+
return `${cfg.host ?? 'https://gitlab.com'}/api/v4`;
|
|
25
|
+
}
|
|
26
|
+
/** GitLab 은 숫자 id 대신 URL 인코딩한 `namespace/repo` 경로를 프로젝트 id 로 받는다. */
|
|
27
|
+
export function gitlabProjectId(cfg) {
|
|
28
|
+
return encodeURIComponent(`${cfg.owner}/${cfg.repo}`);
|
|
29
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 에러 메시지에 쓸 엔드포인트 라벨 — host + path 만.
|
|
3
|
+
*
|
|
4
|
+
* 쿼리스트링은 **의도적으로 버린다**: Meta Graph API 의 `?access_token=…`, GitLab 의
|
|
5
|
+
* `?private_token=…` 처럼 토큰이 실릴 수 있고, 에러는 에이전트 전사록·터미널·CI 로그에 남는다.
|
|
6
|
+
* URL 로 해석되지 않으면 호출부가 준 `fallback`(각 패키지 언어의 "외부 서버")을 쓴다.
|
|
7
|
+
*/
|
|
8
|
+
export declare function endpointLabel(input: string | URL, fallback: string): string;
|
|
9
|
+
/** AbortSignal.timeout 이 만든 중단인가. undici 가 cause 로 한 겹 싸는 경우까지 본다. */
|
|
10
|
+
export declare function isTimeoutAbort(error: unknown): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// 두 패키지의 fetch 래퍼(cli/src/lib/http.ts, mcp-server/src/lib/http.ts)가 공유하는 **순수 헬퍼**.
|
|
2
|
+
//
|
|
3
|
+
// 래퍼 자체는 일부러 두 벌이다 — 정책이 다르다: MCP 는 60초 + 429/5xx 재시도(stdio 도구 호출이
|
|
4
|
+
// 매달리면 안 된다), CLI 는 30초 + 재시도 없음(사람이 앞에 있고, 폴링은 호출부가 연속 오류를
|
|
5
|
+
// 센다) + 에러 문구를 catalog 로 현지화. 그래서 core 에는 네트워크 호출이 없고(core-boundary 가드),
|
|
6
|
+
// 두 래퍼가 똑같이 지켜야 하는 판단만 둔다.
|
|
7
|
+
/**
|
|
8
|
+
* 에러 메시지에 쓸 엔드포인트 라벨 — host + path 만.
|
|
9
|
+
*
|
|
10
|
+
* 쿼리스트링은 **의도적으로 버린다**: Meta Graph API 의 `?access_token=…`, GitLab 의
|
|
11
|
+
* `?private_token=…` 처럼 토큰이 실릴 수 있고, 에러는 에이전트 전사록·터미널·CI 로그에 남는다.
|
|
12
|
+
* URL 로 해석되지 않으면 호출부가 준 `fallback`(각 패키지 언어의 "외부 서버")을 쓴다.
|
|
13
|
+
*/
|
|
14
|
+
export function endpointLabel(input, fallback) {
|
|
15
|
+
try {
|
|
16
|
+
const url = new URL(String(input));
|
|
17
|
+
return `${url.host}${url.pathname}`;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** AbortSignal.timeout 이 만든 중단인가. undici 가 cause 로 한 겹 싸는 경우까지 본다. */
|
|
24
|
+
export function isTimeoutAbort(error) {
|
|
25
|
+
const named = (e) => e?.name === 'TimeoutError';
|
|
26
|
+
return named(error) || named(error?.cause);
|
|
27
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type Lang = 'ko' | 'en';
|
|
2
2
|
export declare const DEFAULT_LANG: Lang;
|
|
3
3
|
export declare function isLang(v: unknown): v is Lang;
|
|
4
|
+
/** ~/.mimi-seed/settings.json 경로. */
|
|
5
|
+
export declare function settingsPath(home?: string): string;
|
|
4
6
|
/** 우선순위: 환경변수 > ~/.mimi-seed/settings.json > 기본값(ko). 파일이 없거나 깨져도 ko 로 폴백. */
|
|
5
7
|
export declare function resolveLang(home?: string): Lang;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// 사람이 읽는 터미널 출력의 언어 — CLI 와 mcp-server setup bin 이 **같은 이 함수**로 고른다.
|
|
2
|
+
//
|
|
3
|
+
// 우선순위: MIMI_SEED_LANG 환경변수 > ~/.mimi-seed/settings.json { lang } > 'ko'.
|
|
4
|
+
//
|
|
5
|
+
// 환경변수가 1순위인 이유: CLI 마법사가 mcp-server 의 setup bin 을 spawn 할 때 MIMI_SEED_LANG 을
|
|
6
|
+
// 물려준다(cli/src/mcp-bin.ts). 마법사와 자식 프로세스의 언어가 어긋나면 온보딩 도중에 언어가
|
|
7
|
+
// 뒤섞인다 — 예전엔 두 패키지가 이 규칙을 각자 들고 있었다.
|
|
8
|
+
//
|
|
9
|
+
// MCP 도구의 description / 도구 출력 텍스트는 여기 대상이 아니다 — 그건 사람이 아니라 LLM 이
|
|
10
|
+
// 읽는 인터페이스다. settings.json 의 **쓰기**는 CLI(settings.ts)가 소유한다.
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
export const DEFAULT_LANG = 'ko';
|
|
15
|
+
export function isLang(v) {
|
|
16
|
+
return v === 'ko' || v === 'en';
|
|
17
|
+
}
|
|
18
|
+
/** ~/.mimi-seed/settings.json 경로. */
|
|
19
|
+
export function settingsPath(home = os.homedir()) {
|
|
20
|
+
return path.join(home, '.mimi-seed', 'settings.json');
|
|
21
|
+
}
|
|
22
|
+
/** 우선순위: 환경변수 > ~/.mimi-seed/settings.json > 기본값(ko). 파일이 없거나 깨져도 ko 로 폴백. */
|
|
23
|
+
export function resolveLang(home = os.homedir()) {
|
|
24
|
+
const env = process.env.MIMI_SEED_LANG?.toLowerCase();
|
|
25
|
+
if (isLang(env))
|
|
26
|
+
return env;
|
|
27
|
+
try {
|
|
28
|
+
const saved = JSON.parse(fs.readFileSync(settingsPath(home), 'utf-8'))?.lang;
|
|
29
|
+
if (isLang(saved))
|
|
30
|
+
return saved;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// 파일 없음 / 권한 없음 / JSON 깨짐 — 전부 기본값으로.
|
|
34
|
+
}
|
|
35
|
+
return DEFAULT_LANG;
|
|
36
|
+
}
|
|
@@ -42,5 +42,14 @@ export declare function findProjectManifest(startDir?: string, maxDepth?: number
|
|
|
42
42
|
export declare function manifestServiceEntries(m: ProjectManifest): Array<[ManifestServiceId, ManifestService]>;
|
|
43
43
|
/** 파일명으로 안전하게 사용할 수 있는 공개 프로필 id인지 확인한다. */
|
|
44
44
|
export declare function isValidSocialProfileId(value: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* socialProfiles 검증 실패 문구. **검증 규칙은 하나**지만 문구는 소비자마다 다르다 — MCP 도구
|
|
47
|
+
* 출력(LLM 이 읽는다)은 한국어 기본값을, CLI 는 자기 출력 언어의 문구를 넘긴다.
|
|
48
|
+
*/
|
|
49
|
+
export interface SocialProfileMessages {
|
|
50
|
+
notAnObject: string;
|
|
51
|
+
invalidId: (platform: SocialPlatform) => string;
|
|
52
|
+
}
|
|
53
|
+
export declare const SOCIAL_PROFILE_MESSAGES_KO: SocialProfileMessages;
|
|
45
54
|
/** 매니페스트에 지정된 플랫폼 프로필. 잘못된 값은 조용히 무시하지 않고 오류로 막는다. */
|
|
46
|
-
export declare function manifestSocialProfile(m: ProjectManifest, platform: SocialPlatform): string | null;
|
|
55
|
+
export declare function manifestSocialProfile(m: ProjectManifest, platform: SocialPlatform, messages?: SocialProfileMessages): string | null;
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
//
|
|
7
7
|
// 팀 온보딩 목적: 새 팀원이 clone → mimi_seed_status 호출 → 안내 따라가기 만으로
|
|
8
8
|
// 자기 머신에 필요한 자격증명을 채울 수 있게 한다.
|
|
9
|
+
//
|
|
10
|
+
// 두 패키지가 **이 한 리더**를 쓴다 (예전엔 cli 에 손으로 복제한 최소 리더가 따로 있었고,
|
|
11
|
+
// manifest-schema-parity 테스트가 두 사본의 스키마를 텍스트로 비교했다).
|
|
9
12
|
import fs from 'node:fs';
|
|
10
13
|
import path from 'node:path';
|
|
11
14
|
export const MANIFEST_FILENAME = '.mimi-seed.json';
|
|
@@ -55,20 +58,24 @@ export function manifestServiceEntries(m) {
|
|
|
55
58
|
export function isValidSocialProfileId(value) {
|
|
56
59
|
return SOCIAL_PROFILE_ID_PATTERN.test(value);
|
|
57
60
|
}
|
|
61
|
+
export const SOCIAL_PROFILE_MESSAGES_KO = {
|
|
62
|
+
notAnObject: `${MANIFEST_FILENAME}의 socialProfiles는 객체여야 합니다.`,
|
|
63
|
+
invalidId: (platform) => `${MANIFEST_FILENAME}의 socialProfiles.${platform} 값이 올바르지 않습니다. ` +
|
|
64
|
+
'영문자·숫자로 시작하는 1~64자의 영문자/숫자/점/밑줄/하이픈만 사용할 수 있습니다.',
|
|
65
|
+
};
|
|
58
66
|
/** 매니페스트에 지정된 플랫폼 프로필. 잘못된 값은 조용히 무시하지 않고 오류로 막는다. */
|
|
59
|
-
export function manifestSocialProfile(m, platform) {
|
|
67
|
+
export function manifestSocialProfile(m, platform, messages = SOCIAL_PROFILE_MESSAGES_KO) {
|
|
60
68
|
const profiles = m.socialProfiles;
|
|
61
69
|
if (profiles === undefined)
|
|
62
70
|
return null;
|
|
63
71
|
if (!profiles || typeof profiles !== 'object' || Array.isArray(profiles)) {
|
|
64
|
-
throw new Error(
|
|
72
|
+
throw new Error(messages.notAnObject);
|
|
65
73
|
}
|
|
66
74
|
const value = profiles[platform];
|
|
67
75
|
if (value === undefined)
|
|
68
76
|
return null;
|
|
69
77
|
if (typeof value !== 'string' || !isValidSocialProfileId(value)) {
|
|
70
|
-
throw new Error(
|
|
71
|
-
'영문자·숫자로 시작하는 1~64자의 영문자/숫자/점/밑줄/하이픈만 사용할 수 있습니다.');
|
|
78
|
+
throw new Error(messages.invalidId(platform));
|
|
72
79
|
}
|
|
73
80
|
return value;
|
|
74
81
|
}
|
package/dist/googleads/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { writeCredentialJson } from '
|
|
4
|
+
import { writeCredentialJson } from '#core/atomic-write.js';
|
|
5
5
|
const CONFIG_DIR = path.join(os.homedir(), '.mimi-seed');
|
|
6
6
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'google-ads.json');
|
|
7
7
|
/** 하이픈 제거 (API는 숫자만 허용) */
|
|
@@ -7,7 +7,7 @@ import readline from 'node:readline';
|
|
|
7
7
|
import { loadConfig, saveConfig, normalizeCustomerId } from './config.js';
|
|
8
8
|
import { listAccessibleCustomers } from './tools.js';
|
|
9
9
|
import { getAuthenticatedClient } from '../auth/google-auth.js';
|
|
10
|
-
import { resolveLang } from '
|
|
10
|
+
import { resolveLang } from '#core/lang.js';
|
|
11
11
|
// ko 가 원본이고 en 은 `typeof ko` 를 만족해야 한다 — 키를 빠뜨리면 컴파일이 깨진다.
|
|
12
12
|
const ko = {
|
|
13
13
|
title: ' 🤖 Mimi Seed — Google Ads 연결',
|
package/dist/iam/key-files.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import os from 'node:os';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import { writeCredentialFile } from '
|
|
10
|
+
import { writeCredentialFile } from '#core/atomic-write.js';
|
|
11
11
|
import { resolveInsideDir } from '../lib/path-containment.js';
|
|
12
12
|
/** 호출 시점에 계산한다 — 테스트가 홈 디렉터리를 바꿀 수 있어야 한다. */
|
|
13
13
|
export function serviceAccountKeysDir() {
|
package/dist/jenkins/config.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
username: string;
|
|
4
|
-
token: string;
|
|
5
|
-
jobAndroid?: string;
|
|
6
|
-
jobIos?: string;
|
|
7
|
-
}
|
|
1
|
+
import type { JenkinsConfig } from '#core/jenkins.js';
|
|
2
|
+
export type { JenkinsConfig } from '#core/jenkins.js';
|
|
8
3
|
export declare function loadJenkinsConfig(): JenkinsConfig | null;
|
|
9
4
|
/**
|
|
10
5
|
* jenkins.json 저장 — **기존 값 위에 병합한다** (통째로 덮어쓰지 않는다).
|
package/dist/jenkins/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { writeCredentialJson } from '
|
|
4
|
+
import { writeCredentialJson } from '#core/atomic-write.js';
|
|
5
5
|
const CONFIG_DIR = path.join(os.homedir(), '.mimi-seed');
|
|
6
6
|
const JENKINS_CONFIG_PATH = path.join(CONFIG_DIR, 'jenkins.json');
|
|
7
7
|
export function loadJenkinsConfig() {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import readline from 'node:readline';
|
|
7
7
|
import { loadJenkinsConfig, saveJenkinsConfig } from './config.js';
|
|
8
8
|
import { listCredentials } from './credentials.js';
|
|
9
|
-
import { resolveLang } from '
|
|
9
|
+
import { resolveLang } from '#core/lang.js';
|
|
10
10
|
// ko 가 원본이고 en 은 `typeof ko` 를 만족해야 한다 — 키를 빠뜨리면 컴파일이 깨진다.
|
|
11
11
|
const ko = {
|
|
12
12
|
title: ' 🤖 Mimi Seed — Jenkins 연결',
|
package/dist/lib/http.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// 규칙: 새 provider 클라이언트를 만들 때 raw `fetch` 를 쓰지 말고 이 래퍼를 쓸 것
|
|
13
13
|
// (`__tests__/http-timeout.test.ts` 가 강제한다).
|
|
14
|
+
import { endpointLabel as sharedEndpointLabel, isTimeoutAbort } from '#core/http-errors.js';
|
|
14
15
|
/** JSON/메타데이터 API 의 기본 상한. 대부분의 Google·Apple·Meta·Jenkins 호출. */
|
|
15
16
|
export const HTTP_TIMEOUT_MS = 60_000;
|
|
16
17
|
/**
|
|
@@ -56,25 +57,9 @@ const IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', '
|
|
|
56
57
|
/** 일시적이라고 보는 상태 코드. 408·425 도 재요청이 정답인 표준 케이스다. */
|
|
57
58
|
const TRANSIENT_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
58
59
|
/**
|
|
59
|
-
* 에러 메시지에 쓸 엔드포인트
|
|
60
|
-
*
|
|
61
|
-
* 쿼리스트링은 **의도적으로 버린다** — Meta Graph API 는 `?access_token=...` 로 토큰을
|
|
62
|
-
* 실어 보내므로, URL 을 통째로 에러에 넣으면 토큰이 에이전트 전사록과 로그에 남는다.
|
|
60
|
+
* 에러 메시지에 쓸 엔드포인트 라벨 — 쿼리스트링(토큰)을 버리는 규칙은 CLI 래퍼와 공유한다(#core/http-errors.js).
|
|
63
61
|
*/
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const url = new URL(String(input));
|
|
67
|
-
return `${url.host}${url.pathname}`;
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return '외부 서버';
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/** AbortSignal.timeout 이 만든 중단인가. undici 가 cause 로 한 겹 싸는 경우까지 본다. */
|
|
74
|
-
function isTimeoutAbort(error) {
|
|
75
|
-
const named = (e) => e?.name === 'TimeoutError';
|
|
76
|
-
return named(error) || named(error?.cause);
|
|
77
|
-
}
|
|
62
|
+
const endpointLabel = (input) => sharedEndpointLabel(input, '외부 서버');
|
|
78
63
|
/**
|
|
79
64
|
* 본문을 다시 보낼 수 있는가.
|
|
80
65
|
*
|
package/dist/registers/auth.js
CHANGED
|
@@ -21,7 +21,7 @@ import { readPackageRootText } from '../lib/package-root.js';
|
|
|
21
21
|
import { resolveBigQueryAuth } from '../auth/bigquery-auth.js';
|
|
22
22
|
import { syncRemoteCredentials } from '../remote-sync.js';
|
|
23
23
|
import { resolveSocialConfigTarget } from '../social/profile-store.js';
|
|
24
|
-
import { findProjectManifest, manifestServiceEntries, } from '
|
|
24
|
+
import { findProjectManifest, manifestServiceEntries, } from '#core/project-manifest.js';
|
|
25
25
|
import { textResult } from '../lib/mcp-response.js';
|
|
26
26
|
import { describeToolsets } from '../lib/toolsets.js';
|
|
27
27
|
/**
|
package/dist/registers/checks.js
CHANGED
|
@@ -6,7 +6,7 @@ import { requireAuth, requirePlayStoreAuth } from '../helpers.js';
|
|
|
6
6
|
import * as appstore from '../appstore/tools.js';
|
|
7
7
|
import * as playstore from '../playstore/tools.js';
|
|
8
8
|
import { jsonResult, textResult } from '../lib/mcp-response.js';
|
|
9
|
-
import { checkBillingCompliance } from '
|
|
9
|
+
import { checkBillingCompliance } from '#core/checks/billing.js';
|
|
10
10
|
import { resolveOpenIapBilling } from '../checks/billing-network.js';
|
|
11
11
|
export function registerChecksTools(server) {
|
|
12
12
|
server.tool('android_check_billing_compliance', [
|
|
@@ -3,7 +3,7 @@ import { loadFacebookConfig, requireFacebookConfig } from '../facebook/config.js
|
|
|
3
3
|
import { connectFacebook } from '../facebook/setup.js';
|
|
4
4
|
import * as api from '../facebook/api.js';
|
|
5
5
|
import { metaExpiryMessage } from '../lib/meta-auth.js';
|
|
6
|
-
import { SOCIAL_PROFILE_ID_PATTERN } from '
|
|
6
|
+
import { SOCIAL_PROFILE_ID_PATTERN } from '#core/project-manifest.js';
|
|
7
7
|
import { textResult } from '../lib/mcp-response.js';
|
|
8
8
|
export function registerFacebookTools(server) {
|
|
9
9
|
const profileSchema = z.string().regex(SOCIAL_PROFILE_ID_PATTERN).optional().describe('저장/사용할 소셜 프로필 ID. 생략 시 .mimi-seed.json의 socialProfiles.facebook, 없으면 기존 기본 설정');
|
|
@@ -3,7 +3,7 @@ import { requireInstagramConfig } from '../instagram/config.js';
|
|
|
3
3
|
import { connectInstagram } from '../instagram/setup.js';
|
|
4
4
|
import * as api from '../instagram/api.js';
|
|
5
5
|
import { metaExpiryMessage } from '../lib/meta-auth.js';
|
|
6
|
-
import { SOCIAL_PROFILE_ID_PATTERN } from '
|
|
6
|
+
import { SOCIAL_PROFILE_ID_PATTERN } from '#core/project-manifest.js';
|
|
7
7
|
import { textResult } from '../lib/mcp-response.js';
|
|
8
8
|
export function registerInstagramTools(server) {
|
|
9
9
|
const profileSchema = z.string().regex(SOCIAL_PROFILE_ID_PATTERN).optional().describe('저장/사용할 소셜 프로필 ID. 생략 시 .mimi-seed.json의 socialProfiles.instagram, 없으면 기존 기본 설정');
|
|
@@ -188,7 +188,7 @@ export function registerPlaystoreTools(server) {
|
|
|
188
188
|
const result = await playstore.updateReleaseNotes(auth, packageName, track, versionCode, language, text);
|
|
189
189
|
return textResult(`✅ ${packageName} ${track} v${versionCode} ${language} 노트 반영\n\n${JSON.stringify(result, null, 2)}`);
|
|
190
190
|
}
|
|
191
|
-
// versionCode 생략 = 트랙 최신 릴리스 (
|
|
191
|
+
// versionCode 생략 = 트랙 최신 릴리스 (0.20.0 에서 합친 옛 latest 전용 도구의 경로·응답 모양 그대로).
|
|
192
192
|
const primaryResult = await playstore.updateLatestReleaseNotes(auth, packageName, track, language, text);
|
|
193
193
|
const lines = [
|
|
194
194
|
`✅ ${packageName} ${track} (versionCodes=${JSON.stringify(primaryResult.updatedVersionCodes)}) ${language} 노트 반영`,
|
|
@@ -4,7 +4,7 @@ import { connectThreads, refreshThreadsToken } from '../threads/setup.js';
|
|
|
4
4
|
import * as api from '../threads/api.js';
|
|
5
5
|
import { metaExpiryMessage } from '../lib/meta-auth.js';
|
|
6
6
|
import { resolveSocialConfigTarget, socialTargetLabel } from '../social/profile-store.js';
|
|
7
|
-
import { SOCIAL_PROFILE_ID_PATTERN } from '
|
|
7
|
+
import { SOCIAL_PROFILE_ID_PATTERN } from '#core/project-manifest.js';
|
|
8
8
|
import { textResult } from '../lib/mcp-response.js';
|
|
9
9
|
export function registerThreadsTools(server) {
|
|
10
10
|
const profileSchema = z.string().regex(SOCIAL_PROFILE_ID_PATTERN).optional().describe('저장/사용할 소셜 프로필 ID. 생략 시 .mimi-seed.json의 socialProfiles.threads, 없으면 기존 기본 설정');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { findProjectManifest, isValidSocialProfileId, manifestSocialProfile, } from '
|
|
5
|
-
import { writeCredentialJson } from '
|
|
4
|
+
import { findProjectManifest, isValidSocialProfileId, manifestSocialProfile, } from '#core/project-manifest.js';
|
|
5
|
+
import { writeCredentialJson } from '#core/atomic-write.js';
|
|
6
6
|
function validateProfileId(profile) {
|
|
7
7
|
if (!isValidSocialProfileId(profile)) {
|
|
8
8
|
throw new Error('소셜 프로필 ID가 올바르지 않습니다. ' +
|
package/dist/social/setup-cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import { connectInstagram } from '../instagram/setup.js';
|
|
|
24
24
|
import { connectInstagramInBrowser, InstagramLoginError } from '../instagram/browser-login.js';
|
|
25
25
|
import { connectThreads, refreshThreadsToken } from '../threads/setup.js';
|
|
26
26
|
import { connectThreadsInBrowser, ThreadsLoginError } from '../threads/browser-login.js';
|
|
27
|
-
import { resolveLang } from '
|
|
27
|
+
import { resolveLang } from '#core/lang.js';
|
|
28
28
|
import { resolveSocialConfigTarget } from './profile-store.js';
|
|
29
29
|
// ko 가 원본이고 en 은 `typeof ko` 를 만족해야 한다 — 키를 빠뜨리면 컴파일이 깨진다.
|
|
30
30
|
const ko = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { writeCredentialJson } from '
|
|
4
|
+
import { writeCredentialJson } from '#core/atomic-write.js';
|
|
5
5
|
export const TIKTOK_BUSINESS_CONFIG_PATH = path.join(os.homedir(), '.mimi-seed', 'tiktok-business.json');
|
|
6
6
|
export function loadTikTokBusinessConfig() {
|
|
7
7
|
try {
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { closeSync, createReadStream, existsSync, mkdirSync, openSync, readFileSync, readdirSync, statSync, unlinkSync, } from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { CREDENTIAL_DIR_MODE, CREDENTIAL_FILE_MODE, writeJsonAtomic } from '
|
|
5
|
+
import { CREDENTIAL_DIR_MODE, CREDENTIAL_FILE_MODE, writeJsonAtomic } from '#core/atomic-write.js';
|
|
6
6
|
import { validateVideo } from '../video/render.js';
|
|
7
7
|
import { TikTokApiError, getPublishStatus, getVideoSettings, publishVideo } from './api.js';
|
|
8
8
|
import { ensureTikTokPublishConfig } from './auth.js';
|
|
@@ -3,7 +3,7 @@ import readline from 'node:readline';
|
|
|
3
3
|
import { exchangeAuthorizationCode, getBusinessAccount } from './api.js';
|
|
4
4
|
import { configFromToken, hasTikTokScope } from './auth.js';
|
|
5
5
|
import { loadTikTokBusinessConfig, saveTikTokBusinessConfig } from './config.js';
|
|
6
|
-
import { resolveLang } from '
|
|
6
|
+
import { resolveLang } from '#core/lang.js';
|
|
7
7
|
const ko = {
|
|
8
8
|
title: ' 🤖 Mimi Seed — TikTok Business 연결',
|
|
9
9
|
existing: (openId) => ` ✅ 이미 연결됨 (${openId})`,
|
package/dist/video/files.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { writeJsonAtomic } from '
|
|
1
|
+
export { writeJsonAtomic } from '#core/atomic-write.js';
|
|
2
2
|
export declare function readJson(filePath: string, maxBytes?: number): unknown;
|
|
3
3
|
export declare function requireAbsolutePath(value: string, label: string): string;
|
package/dist/video/files.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
// 구현은
|
|
3
|
+
// 구현은 packages/core 의 atomic-write.ts 로 승격됐다 — 같은 원자성 보장이 자격증명 writer 에도 필요했는데
|
|
4
4
|
// video/ 안에 갇혀 있어서 아무도 쓰지 못하고 있었다. video 쪽 호출부를 위해 이름만 재수출한다.
|
|
5
|
-
export { writeJsonAtomic } from '
|
|
5
|
+
export { writeJsonAtomic } from '#core/atomic-write.js';
|
|
6
6
|
export function readJson(filePath, maxBytes = 20 * 1024 * 1024) {
|
|
7
7
|
try {
|
|
8
8
|
const size = statSync(filePath).size;
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yoonion/mimi-seed-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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
|
+
"imports": {
|
|
7
|
+
"#core/*": "./dist/core/*"
|
|
8
|
+
},
|
|
6
9
|
"bin": {
|
|
7
10
|
"mimi-seed-mcp": "dist/index.js",
|
|
8
11
|
"mimi-seed-auth": "dist/auth/cli.js",
|
|
@@ -27,14 +30,15 @@
|
|
|
27
30
|
],
|
|
28
31
|
"scripts": {
|
|
29
32
|
"report:googleads": "node scripts/googleads-report.mjs",
|
|
30
|
-
"
|
|
33
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
34
|
+
"build": "npm run clean && tsc -p tsconfig.core.json && tsc -p tsconfig.build.json",
|
|
31
35
|
"dev": "tsx src/index.ts",
|
|
32
36
|
"auth": "tsx src/auth/cli.ts",
|
|
33
|
-
"typecheck": "tsc --noEmit -p tsconfig.lint.json",
|
|
37
|
+
"typecheck": "tsc --noEmit -p tsconfig.lint.json && tsc -p ../core",
|
|
34
38
|
"test": "npm run typecheck && npm run lint && vitest run",
|
|
35
39
|
"test:watch": "vitest",
|
|
36
|
-
"prepublishOnly": "node ../../scripts/sync-agent-guide.mjs --check &&
|
|
37
|
-
"lint": "eslint .",
|
|
40
|
+
"prepublishOnly": "node ../../scripts/sync-agent-guide.mjs --check && npm run build",
|
|
41
|
+
"lint": "eslint . ../core",
|
|
38
42
|
"coverage": "vitest run --coverage"
|
|
39
43
|
},
|
|
40
44
|
"keywords": [
|
package/tool-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "등록된 MCP 도구 + 도메인 메타데이터(label·credential·summary) + 도구 분류(write·destructive·local·idempotent → MCP annotations·confirm 가드) + toolset 그룹·폐기 별칭의 SSOT (src/lib/tool-registrar.ts 가 읽는다). src/__tests__/tool-manifest.test.ts 가 실제 서버 등록 목록과 diff 하고 메타데이터 정합성을 검사한다. 도구 추가/삭제/개명 시 이 파일을 함께 갱신할 것 — 산문 문서에는 정확한 개수를 쓰지 말고 이 파일을 가리킬 것. mimi-seed://tools/catalog 리소스가 이 파일을 그대로 서빙한다.",
|
|
3
|
-
"total":
|
|
3
|
+
"total": 242,
|
|
4
4
|
"alwaysOn": [
|
|
5
5
|
"auth",
|
|
6
6
|
"checks"
|
|
@@ -53,10 +53,7 @@
|
|
|
53
53
|
"android"
|
|
54
54
|
]
|
|
55
55
|
},
|
|
56
|
-
"deprecated": {
|
|
57
|
-
"playstore_update_latest_release_notes": "playstore_update_release_notes",
|
|
58
|
-
"appstore_attach_latest_build": "appstore_attach_build"
|
|
59
|
-
},
|
|
56
|
+
"deprecated": {},
|
|
60
57
|
"domains": {
|
|
61
58
|
"admob": {
|
|
62
59
|
"label": "AdMob",
|
|
@@ -130,7 +127,6 @@
|
|
|
130
127
|
"appstore_list_versions",
|
|
131
128
|
"appstore_create_version",
|
|
132
129
|
"appstore_attach_build",
|
|
133
|
-
"appstore_attach_latest_build",
|
|
134
130
|
"appstore_get_metadata",
|
|
135
131
|
"appstore_update_localization",
|
|
136
132
|
"appstore_list_screenshots",
|
|
@@ -192,7 +188,6 @@
|
|
|
192
188
|
"write": [
|
|
193
189
|
"appstore_create_version",
|
|
194
190
|
"appstore_attach_build",
|
|
195
|
-
"appstore_attach_latest_build",
|
|
196
191
|
"appstore_update_localization",
|
|
197
192
|
"appstore_upload_screenshot",
|
|
198
193
|
"appstore_update_whats_new",
|
|
@@ -613,7 +608,6 @@
|
|
|
613
608
|
"playstore_delete_all_images",
|
|
614
609
|
"playstore_replace_images",
|
|
615
610
|
"playstore_update_release_notes",
|
|
616
|
-
"playstore_update_latest_release_notes",
|
|
617
611
|
"playstore_list_reviews",
|
|
618
612
|
"playstore_reply_review",
|
|
619
613
|
"playstore_list_inapp_products",
|
|
@@ -647,7 +641,6 @@
|
|
|
647
641
|
"playstore_update_listing",
|
|
648
642
|
"playstore_upload_image",
|
|
649
643
|
"playstore_update_release_notes",
|
|
650
|
-
"playstore_update_latest_release_notes",
|
|
651
644
|
"playstore_create_onetime_product",
|
|
652
645
|
"playstore_create_subscription",
|
|
653
646
|
"playstore_register_service_account",
|
|
@@ -683,7 +676,6 @@
|
|
|
683
676
|
"playstore_update_details",
|
|
684
677
|
"playstore_update_listing",
|
|
685
678
|
"playstore_update_release_notes",
|
|
686
|
-
"playstore_update_latest_release_notes",
|
|
687
679
|
"playstore_update_product_listing",
|
|
688
680
|
"playstore_update_subscription_listing",
|
|
689
681
|
"playstore_update_product_state",
|
package/dist/lib/lang.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// 인터랙티브 setup bin 의 출력 언어.
|
|
2
|
-
//
|
|
3
|
-
// CLI 쪽 `packages/cli/src/settings.ts` 의 resolveLang 과 **같은 규칙**이어야 한다:
|
|
4
|
-
// MIMI_SEED_LANG 환경변수 > ~/.mimi-seed/settings.json { lang } > 'ko'.
|
|
5
|
-
//
|
|
6
|
-
// 환경변수가 1순위인 이유: CLI 마법사가 이 패키지의 setup bin 을 spawn 할 때 MIMI_SEED_LANG 을
|
|
7
|
-
// 물려준다. 마법사와 자식 프로세스의 언어가 어긋나면 온보딩 도중에 언어가 뒤섞인다.
|
|
8
|
-
//
|
|
9
|
-
// MCP 도구의 description / 도구 출력 텍스트는 여기 대상이 아니다 — 그건 사람이 아니라 LLM 이
|
|
10
|
-
// 읽는 인터페이스다. 이 모듈은 **터미널에 찍히는 사람용 문자열**에만 쓴다.
|
|
11
|
-
import fs from 'node:fs';
|
|
12
|
-
import os from 'node:os';
|
|
13
|
-
import path from 'node:path';
|
|
14
|
-
export const DEFAULT_LANG = 'ko';
|
|
15
|
-
export function isLang(v) {
|
|
16
|
-
return v === 'ko' || v === 'en';
|
|
17
|
-
}
|
|
18
|
-
/** 우선순위: 환경변수 > ~/.mimi-seed/settings.json > 기본값(ko). 파일이 없거나 깨져도 ko 로 폴백. */
|
|
19
|
-
export function resolveLang(home = os.homedir()) {
|
|
20
|
-
const env = process.env.MIMI_SEED_LANG?.toLowerCase();
|
|
21
|
-
if (isLang(env))
|
|
22
|
-
return env;
|
|
23
|
-
try {
|
|
24
|
-
const raw = fs.readFileSync(path.join(home, '.mimi-seed', 'settings.json'), 'utf-8');
|
|
25
|
-
const saved = JSON.parse(raw).lang;
|
|
26
|
-
if (isLang(saved))
|
|
27
|
-
return saved;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
// 파일 없음 / 권한 없음 / JSON 깨짐 — 전부 기본값으로.
|
|
31
|
-
}
|
|
32
|
-
return DEFAULT_LANG;
|
|
33
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|