mimi-seed 0.2.0 → 0.2.2
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 +64 -3
- package/dist/index.js +296 -60
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ npx mimi-seed init
|
|
|
21
21
|
| `mimi-seed doctor` | 환경 진단 (토큰·Git·앱·CI 한 번에 체크) |
|
|
22
22
|
| `mimi-seed check` | 출시 전 Readiness 점검 (점수 + 블로커) |
|
|
23
23
|
| `mimi-seed notes` | AI 릴리즈 노트 생성 (git log → 3 톤 → 다국어 → 적용) |
|
|
24
|
+
| `mimi-seed review` | AI 리뷰 답변 초안 생성 및 Play Store 게시 |
|
|
24
25
|
| `mimi-seed logout` | 로컬 설정 삭제 |
|
|
25
26
|
|
|
26
27
|
---
|
|
@@ -108,13 +109,73 @@ mimi-seed doctor
|
|
|
108
109
|
|
|
109
110
|
---
|
|
110
111
|
|
|
112
|
+
## mimi-seed review
|
|
113
|
+
|
|
114
|
+
스토어 리뷰에 대한 AI 답변 초안을 생성합니다. `ANTHROPIC_API_KEY` 필요.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# 대화형 — 리뷰 입력 후 답변 생성
|
|
118
|
+
mimi-seed review
|
|
119
|
+
|
|
120
|
+
# 리뷰 텍스트 직접 지정
|
|
121
|
+
mimi-seed review --text "앱이 자꾸 튕겨요" --rating 2
|
|
122
|
+
|
|
123
|
+
# 톤·언어 지정
|
|
124
|
+
mimi-seed review --text "Great app!" --rating 5 --tone professional --language en
|
|
125
|
+
|
|
126
|
+
# 생성 후 Play Store 바로 게시
|
|
127
|
+
mimi-seed review --text "버그 있어요" --rating 2 \
|
|
128
|
+
--apply --package-name com.example.app --review-id <reviewId>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 옵션
|
|
132
|
+
|
|
133
|
+
| 옵션 | 기본값 | 설명 |
|
|
134
|
+
|------|--------|------|
|
|
135
|
+
| `--text <내용>` | (프롬프트) | 리뷰 원문 |
|
|
136
|
+
| `--rating <1-5>` | — | 별점 (맥락용) |
|
|
137
|
+
| `--tone <tone>` | `friendly` | `friendly` / `professional` / `empathetic` / `brief` |
|
|
138
|
+
| `--language <코드>` | `ko` | 답변 언어 (`ko`, `en`, `ja` 등) |
|
|
139
|
+
| `--app-name <이름>` | — | 앱 이름 (프롬프트 맥락용) |
|
|
140
|
+
| `--apply` | false | 답변을 Play Store에 바로 게시 |
|
|
141
|
+
| `--review-id <id>` | — | 게시 대상 리뷰 ID (`--apply` 시 필요) |
|
|
142
|
+
| `--package-name <p>` | — | Android 패키지명 (`--apply` 시 필요) |
|
|
143
|
+
| `--no-interactive` | false | CI 모드 (프롬프트 없음) |
|
|
144
|
+
|
|
145
|
+
> AI 생성 답변은 초안입니다. 게시 전 반드시 검토하세요.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
111
149
|
## CI/CD 사용
|
|
112
150
|
|
|
113
|
-
|
|
151
|
+
### 방법 1 — 재사용 가능 워크플로우 (권장)
|
|
152
|
+
|
|
153
|
+
`.github/workflows/release.yml` 파일 하나만 추가하면 끝납니다.
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
name: App Release
|
|
157
|
+
|
|
158
|
+
on:
|
|
159
|
+
push:
|
|
160
|
+
tags: ['v*'] # v1.2.3 태그 push 시 자동 실행
|
|
161
|
+
workflow_dispatch: # 수동 실행 버튼
|
|
162
|
+
|
|
163
|
+
jobs:
|
|
164
|
+
release:
|
|
165
|
+
uses: jeonghwanko/mimi-seed/.github/workflows/mimi-seed-release.yml@master
|
|
166
|
+
with:
|
|
167
|
+
locales: 'ko,en-US' # 다국어 릴리즈 노트
|
|
168
|
+
apply-notes: true # 스토어에 자동 적용
|
|
169
|
+
fail-on-blocker: true # 블로커 있으면 실패
|
|
170
|
+
secrets:
|
|
171
|
+
MIMI_SEED_TOKEN: ${{ secrets.MIMI_SEED_TOKEN }}
|
|
172
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # 선택: AI 생성
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 방법 2 — 기존 워크플로우에 스텝 추가
|
|
114
176
|
|
|
115
177
|
```yaml
|
|
116
|
-
|
|
117
|
-
- name: Release notes & readiness check
|
|
178
|
+
- name: 릴리즈 노트 생성 및 적용
|
|
118
179
|
env:
|
|
119
180
|
MIMI_SEED_TOKEN: ${{ secrets.MIMI_SEED_TOKEN }}
|
|
120
181
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import os2 from "os";
|
|
5
|
-
import
|
|
5
|
+
import kleur6 from "kleur";
|
|
6
6
|
import open from "open";
|
|
7
7
|
|
|
8
8
|
// src/detect.ts
|
|
@@ -659,62 +659,279 @@ async function cmdNotes(argv) {
|
|
|
659
659
|
}
|
|
660
660
|
}
|
|
661
661
|
|
|
662
|
+
// src/review.ts
|
|
663
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
664
|
+
import Anthropic2 from "@anthropic-ai/sdk";
|
|
665
|
+
import kleur4 from "kleur";
|
|
666
|
+
function parseArgs3(argv) {
|
|
667
|
+
const args = { tone: "friendly", language: "ko", apply: false, noInteractive: false };
|
|
668
|
+
for (let i = 0; i < argv.length; i++) {
|
|
669
|
+
if (argv[i] === "--text" && argv[i + 1]) args.text = argv[++i];
|
|
670
|
+
if (argv[i] === "--rating" && argv[i + 1]) args.rating = parseInt(argv[++i], 10);
|
|
671
|
+
if (argv[i] === "--tone" && argv[i + 1]) args.tone = argv[++i];
|
|
672
|
+
if (argv[i] === "--language" && argv[i + 1]) args.language = argv[++i];
|
|
673
|
+
if (argv[i] === "--app-name" && argv[i + 1]) args.appName = argv[++i];
|
|
674
|
+
if (argv[i] === "--developer-name" && argv[i + 1]) args.developerName = argv[++i];
|
|
675
|
+
if (argv[i] === "--apply") args.apply = true;
|
|
676
|
+
if (argv[i] === "--review-id" && argv[i + 1]) args.reviewId = argv[++i];
|
|
677
|
+
if (argv[i] === "--package-name" && argv[i + 1]) args.packageName = argv[++i];
|
|
678
|
+
if (argv[i] === "--no-interactive") args.noInteractive = true;
|
|
679
|
+
}
|
|
680
|
+
return args;
|
|
681
|
+
}
|
|
682
|
+
async function promptUser2(question) {
|
|
683
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
684
|
+
const answer = await rl.question(question);
|
|
685
|
+
rl.close();
|
|
686
|
+
return answer.trim();
|
|
687
|
+
}
|
|
688
|
+
var TONE_GUIDES = {
|
|
689
|
+
friendly: "\uCE5C\uADFC\uD558\uACE0 \uB530\uB73B\uD558\uAC8C. \uC774\uBAA8\uC9C0 1~2\uAC1C \uC0AC\uC6A9. \uAC10\uC0AC \uC778\uC0AC\uB85C \uC2DC\uC791.",
|
|
690
|
+
professional: "\uC815\uC911\uD558\uACE0 \uACF5\uC2DD\uC801\uC73C\uB85C. \uBB38\uC81C\uB97C \uC778\uC815\uD558\uACE0 \uD574\uACB0\uCC45\uC744 \uC81C\uC2DC.",
|
|
691
|
+
empathetic: "\uACF5\uAC10\uC744 \uBA3C\uC800 \uD45C\uD604. \uBD88\uD3B8\uC744 \uCDA9\uBD84\uD788 \uC778\uC815\uD55C \uD6C4 \uD574\uACB0 \uC758\uC9C0\uB97C \uBCF4\uC5EC\uC90C.",
|
|
692
|
+
brief: "2~3\uBB38\uC7A5\uC73C\uB85C \uAC04\uACB0\uD558\uAC8C. \uD575\uC2EC \uC751\uB2F5\uB9CC."
|
|
693
|
+
};
|
|
694
|
+
var SENTIMENT_PROMPTS = {
|
|
695
|
+
positive: "\uAE0D\uC815\uC801\uC778 \uB9AC\uBDF0. \uAC10\uC0AC \uC778\uC0AC\uC640 \uD568\uAED8 \uC55E\uC73C\uB85C\uB3C4 \uC88B\uC740 \uACBD\uD5D8\uC744 \uC81C\uACF5\uD558\uACA0\uB2E4\uB294 \uC758\uC9C0\uB97C \uD45C\uD604.",
|
|
696
|
+
negative: "\uBD80\uC815\uC801\uC778 \uB9AC\uBDF0. \uBA3C\uC800 \uBD88\uD3B8\uC744 \uC0AC\uACFC\uD558\uACE0, \uBB38\uC81C \uD574\uACB0 \uC758\uC9C0\uB97C \uBCF4\uC5EC\uC918. \uAC00\uB2A5\uD558\uBA74 \uC9C0\uC6D0 \uCC44\uB110\uB85C \uC720\uB3C4.",
|
|
697
|
+
neutral: "\uC911\uB9BD\uC801\uC778 \uB9AC\uBDF0. \uD53C\uB4DC\uBC31\uC5D0 \uAC10\uC0AC\uD558\uACE0 \uAC1C\uC120 \uB178\uB825\uC744 \uC57D\uC18D.",
|
|
698
|
+
bug_report: "\uBC84\uADF8 \uB9AC\uD3EC\uD2B8. \uBB38\uC81C\uB97C \uD655\uC778\uD588\uC74C\uC744 \uC54C\uB9AC\uACE0, \uC218\uC815 \uC608\uC815\uC784\uC744 \uC804\uB2EC.",
|
|
699
|
+
feature_request: "\uAE30\uB2A5 \uC694\uCCAD. \uD53C\uB4DC\uBC31\uC5D0 \uAC10\uC0AC\uD558\uACE0, \uAC80\uD1A0\uD558\uACA0\uB2E4\uACE0 \uC57D\uC18D."
|
|
700
|
+
};
|
|
701
|
+
function detectSentiment(text) {
|
|
702
|
+
const lower = text.toLowerCase();
|
|
703
|
+
if (["\uBC84\uADF8", "\uC624\uB958", "\uC548\uB428", "crash", "bug", "error", "broken"].some((w) => lower.includes(w))) return "bug_report";
|
|
704
|
+
if (["\uCD94\uAC00", "\uC6D0\uD574", "\uC788\uC73C\uBA74", "wish", "feature", "add", "would like"].some((w) => lower.includes(w))) return "feature_request";
|
|
705
|
+
if (["\uBCC4\uB85C", "\uC2E4\uB9DD", "\uC9DC\uC99D", "terrible", "worst", "awful"].some((w) => lower.includes(w))) return "negative";
|
|
706
|
+
if (["\uC88B\uC544", "\uCD5C\uACE0", "\uD6CC\uB96D", "great", "excellent", "love", "perfect"].some((w) => lower.includes(w))) return "positive";
|
|
707
|
+
return "neutral";
|
|
708
|
+
}
|
|
709
|
+
async function generateReply(opts) {
|
|
710
|
+
const client = new Anthropic2({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
711
|
+
const sentiment = detectSentiment(opts.text);
|
|
712
|
+
const toneGuide = TONE_GUIDES[opts.tone] ?? TONE_GUIDES.friendly;
|
|
713
|
+
const sentimentPrompt = SENTIMENT_PROMPTS[sentiment] ?? SENTIMENT_PROMPTS.neutral;
|
|
714
|
+
const stars = opts.rating !== void 0 ? `\uBCC4\uC810: ${"\u2605".repeat(opts.rating)}${"\u2606".repeat(5 - opts.rating)} (${opts.rating}/5)
|
|
715
|
+
` : "";
|
|
716
|
+
const LOCALE_NAMES = { ko: "\uD55C\uAD6D\uC5B4", en: "\uC601\uC5B4", ja: "\uC77C\uBCF8\uC5B4", "en-US": "\uC601\uC5B4", "zh-TW": "\uBC88\uCCB4\uC911\uAD6D\uC5B4" };
|
|
717
|
+
const langName = LOCALE_NAMES[opts.language] ?? opts.language;
|
|
718
|
+
const response = await client.messages.create({
|
|
719
|
+
model: "claude-haiku-4-5-20251001",
|
|
720
|
+
max_tokens: 500,
|
|
721
|
+
system: `\uC571 \uAC1C\uBC1C\uC790\uB97C \uB300\uC2E0\uD574 \uC2A4\uD1A0\uC5B4 \uB9AC\uBDF0\uC5D0 \uB2F5\uBCC0\uD558\uB294 \uC804\uBB38\uAC00. ${langName}\uB85C 150\uC790 \uC774\uB0B4\uB85C \uB2F5\uBCC0. ${toneGuide} \uAC1C\uBC1C\uC790 \uC774\uB984: ${opts.developerName ?? "\uAC1C\uBC1C\uD300"}`,
|
|
722
|
+
messages: [{
|
|
723
|
+
role: "user",
|
|
724
|
+
content: `\uC571: ${opts.appName ?? "\uC571"}
|
|
725
|
+
${stars}\uB9AC\uBDF0: "${opts.text}"
|
|
726
|
+
|
|
727
|
+
\uC9C0\uCE68: ${sentimentPrompt}
|
|
728
|
+
|
|
729
|
+
JSON\uC73C\uB85C\uB9CC \uC751\uB2F5: { "reply": "\uB2F5\uBCC0 \uD14D\uC2A4\uD2B8" }`
|
|
730
|
+
}]
|
|
731
|
+
});
|
|
732
|
+
const raw = response.content[0].type === "text" ? response.content[0].text : "";
|
|
733
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
734
|
+
if (!match) throw new Error("AI \uC751\uB2F5 \uD30C\uC2F1 \uC2E4\uD328");
|
|
735
|
+
const parsed = JSON.parse(match[0]);
|
|
736
|
+
return parsed.reply;
|
|
737
|
+
}
|
|
738
|
+
async function cmdReview(argv) {
|
|
739
|
+
const args = parseArgs3(argv);
|
|
740
|
+
process.stdout.write(kleur4.bold("mimi-seed review \u2014 \uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131\n\n"));
|
|
741
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
742
|
+
process.stdout.write(kleur4.red("ANTHROPIC_API_KEY\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
|
|
743
|
+
process.stdout.write(kleur4.dim("\uD65C\uC131\uD654: export ANTHROPIC_API_KEY=sk-ant-...\n"));
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
let reviewText = args.text;
|
|
747
|
+
if (!reviewText) {
|
|
748
|
+
if (args.noInteractive) {
|
|
749
|
+
process.stdout.write(kleur4.red("--text <\uB9AC\uBDF0 \uB0B4\uC6A9> \uC774 \uD544\uC694\uD569\uB2C8\uB2E4.\n"));
|
|
750
|
+
process.exit(1);
|
|
751
|
+
}
|
|
752
|
+
process.stdout.write(kleur4.dim("\uB9AC\uBDF0 \uB0B4\uC6A9\uC744 \uC785\uB825\uD558\uC138\uC694 (\uC5EC\uB7EC \uC904: Ctrl+D\uB85C \uC644\uB8CC):\n"));
|
|
753
|
+
reviewText = await promptUser2("> ");
|
|
754
|
+
if (!reviewText) {
|
|
755
|
+
process.stdout.write(kleur4.yellow("\uC785\uB825 \uC5C6\uC74C. \uC885\uB8CC.\n"));
|
|
756
|
+
process.exit(0);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (args.rating !== void 0) {
|
|
760
|
+
const stars = "\u2605".repeat(args.rating) + "\u2606".repeat(5 - args.rating);
|
|
761
|
+
process.stdout.write(kleur4.dim(`\uBCC4\uC810: ${stars} tone: ${args.tone} \uC5B8\uC5B4: ${args.language}
|
|
762
|
+
|
|
763
|
+
`));
|
|
764
|
+
}
|
|
765
|
+
process.stdout.write("\u{1F916} Claude AI\uB85C \uB2F5\uBCC0 \uC0DD\uC131 \uC911...\n");
|
|
766
|
+
const reply = await generateReply({ ...args, text: reviewText });
|
|
767
|
+
process.stdout.write("\n" + kleur4.bold("\u2500\u2500\u2500 \uC81C\uC548 \uB2F5\uBCC0 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
768
|
+
process.stdout.write(reply + "\n");
|
|
769
|
+
process.stdout.write(kleur4.dim("\n\u26A0 \uC774 \uB2F5\uBCC0\uC740 AI\uAC00 \uC0DD\uC131\uD55C \uCD08\uC548\uC785\uB2C8\uB2E4. \uAC8C\uC2DC \uC804 \uBC18\uB4DC\uC2DC \uAC80\uD1A0\uD558\uC138\uC694.\n\n"));
|
|
770
|
+
const shouldPrompt = !args.apply && !args.noInteractive && process.stdout.isTTY;
|
|
771
|
+
if (!args.apply && !shouldPrompt) return;
|
|
772
|
+
let doApply = args.apply;
|
|
773
|
+
if (shouldPrompt) {
|
|
774
|
+
const ans = await promptUser2("\uC774 \uB2F5\uBCC0\uC744 Play Store\uC5D0 \uAC8C\uC2DC\uD560\uAE4C\uC694? [y/N]: ");
|
|
775
|
+
doApply = ans.toLowerCase() === "y";
|
|
776
|
+
}
|
|
777
|
+
if (!doApply) {
|
|
778
|
+
process.stdout.write(kleur4.dim("\uAC74\uB108\uB700.\n"));
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const cfg = await getEffectiveConfig();
|
|
782
|
+
if (!cfg) {
|
|
783
|
+
process.stdout.write(kleur4.yellow("Mimi Seed \uACC4\uC815 \uC5F0\uACB0 \uD544\uC694. `mimi-seed init` \uC2E4\uD589.\n"));
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
let reviewId = args.reviewId;
|
|
787
|
+
let packageName = args.packageName;
|
|
788
|
+
if (!reviewId || !packageName) {
|
|
789
|
+
if (args.noInteractive) {
|
|
790
|
+
process.stdout.write(kleur4.red("--review-id \uC640 --package-name \uC774 \uD544\uC694\uD569\uB2C8\uB2E4.\n"));
|
|
791
|
+
process.exit(1);
|
|
792
|
+
}
|
|
793
|
+
if (!packageName) packageName = await promptUser2("\uD328\uD0A4\uC9C0\uBA85 (\uC608: com.example.app): ");
|
|
794
|
+
if (!reviewId) reviewId = await promptUser2("\uB9AC\uBDF0 ID: ");
|
|
795
|
+
}
|
|
796
|
+
if (!reviewId || !packageName) {
|
|
797
|
+
process.stdout.write(kleur4.yellow("\uC815\uBCF4 \uBD80\uC871. \uCDE8\uC18C.\n"));
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
process.stdout.write("Play Store\uC5D0 \uAC8C\uC2DC \uC911...\n");
|
|
801
|
+
const r = await mcpCall(cfg.endpoint, cfg.token, "playstore_reply_review", {
|
|
802
|
+
package_name: packageName,
|
|
803
|
+
review_id: reviewId,
|
|
804
|
+
reply_text: reply
|
|
805
|
+
});
|
|
806
|
+
process.stdout.write(r.isError ? kleur4.red(`\uAC8C\uC2DC \uC2E4\uD328: ${r.text}
|
|
807
|
+
`) : kleur4.green("\u2713 \uB2F5\uBCC0 \uAC8C\uC2DC \uC644\uB8CC\n"));
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/auth.ts
|
|
811
|
+
import { spawn } from "child_process";
|
|
812
|
+
import kleur5 from "kleur";
|
|
813
|
+
var MCP_PKG = "@yoonion/mimi-seed-mcp";
|
|
814
|
+
function log(msg) {
|
|
815
|
+
process.stdout.write(msg + "\n");
|
|
816
|
+
}
|
|
817
|
+
function printAuthHelp() {
|
|
818
|
+
log(`${kleur5.bold("mimi-seed auth")} \u2014 Google OAuth \uC778\uC99D (Firebase / AdMob / Play Store)
|
|
819
|
+
|
|
820
|
+
${kleur5.bold("\uC11C\uBE0C\uBA85\uB839:")}
|
|
821
|
+
${kleur5.cyan("mimi-seed auth login")} \uBE0C\uB77C\uC6B0\uC800\uB85C \uB85C\uADF8\uC778 (\uC774\uBBF8 \uC788\uC73C\uBA74 \uC790\uB3D9 refresh \uC2DC\uB3C4)
|
|
822
|
+
${kleur5.cyan("mimi-seed auth status")} \uD604\uC7AC \uD1A0\uD070 \uC0C1\uD0DC
|
|
823
|
+
${kleur5.cyan("mimi-seed auth refresh")} refresh_token\uC73C\uB85C \uAC31\uC2E0\uB9CC \uC2DC\uB3C4 (\uBE0C\uB77C\uC6B0\uC800 X)
|
|
824
|
+
${kleur5.cyan("mimi-seed auth logout")} \uD1A0\uD070 \uC0AD\uC81C
|
|
825
|
+
|
|
826
|
+
${kleur5.bold("login \uC635\uC158:")}
|
|
827
|
+
--no-browser URL \uC790\uB3D9 \uC624\uD508 \uC548 \uD568 (\uC9C1\uC811 \uBCF5\uBD99)
|
|
828
|
+
--timeout <\uCD08> \uCF5C\uBC31 \uB300\uAE30 \uC2DC\uAC04 (\uAE30\uBCF8 600)
|
|
829
|
+
--force \uAE30\uC874 \uD1A0\uD070 \uBB34\uC2DC\uD558\uACE0 \uAC15\uC81C \uC7AC\uB85C\uADF8\uC778
|
|
830
|
+
|
|
831
|
+
${kleur5.dim(`\uB0B4\uBD80\uC801\uC73C\uB85C ${MCP_PKG} \uC758 mimi-seed-auth CLI\uB97C \uD638\uCD9C\uD569\uB2C8\uB2E4.`)}`);
|
|
832
|
+
}
|
|
833
|
+
async function runMcpAuth(extraArgs) {
|
|
834
|
+
return new Promise((resolve) => {
|
|
835
|
+
const child = spawn("npx", ["-y", `${MCP_PKG}`, "mimi-seed-auth", ...extraArgs], {
|
|
836
|
+
stdio: "inherit",
|
|
837
|
+
shell: true
|
|
838
|
+
});
|
|
839
|
+
child.on("error", (e) => {
|
|
840
|
+
process.stderr.write(`
|
|
841
|
+
\u274C npx \uC2E4\uD589 \uC2E4\uD328: ${e.message}
|
|
842
|
+
`);
|
|
843
|
+
resolve(1);
|
|
844
|
+
});
|
|
845
|
+
child.on("exit", (code) => resolve(code ?? 0));
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
async function cmdAuth(args) {
|
|
849
|
+
const sub = args[0];
|
|
850
|
+
const rest = args.slice(1);
|
|
851
|
+
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
852
|
+
printAuthHelp();
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
let mcpArgs;
|
|
856
|
+
switch (sub) {
|
|
857
|
+
case void 0:
|
|
858
|
+
case "login":
|
|
859
|
+
mcpArgs = rest;
|
|
860
|
+
break;
|
|
861
|
+
case "status":
|
|
862
|
+
mcpArgs = ["--status", ...rest];
|
|
863
|
+
break;
|
|
864
|
+
case "refresh":
|
|
865
|
+
mcpArgs = ["--refresh", ...rest];
|
|
866
|
+
break;
|
|
867
|
+
case "logout":
|
|
868
|
+
mcpArgs = ["--logout", ...rest];
|
|
869
|
+
break;
|
|
870
|
+
default:
|
|
871
|
+
log(kleur5.red(`\uC54C \uC218 \uC5C6\uB294 auth \uC11C\uBE0C\uBA85\uB839: ${sub}`));
|
|
872
|
+
printAuthHelp();
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
const code = await runMcpAuth(mcpArgs);
|
|
876
|
+
if (code !== 0) process.exit(code);
|
|
877
|
+
}
|
|
878
|
+
|
|
662
879
|
// src/index.ts
|
|
663
880
|
var DEFAULT_WEB_BASE = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
|
|
664
881
|
var DEFAULT_MCP_ENDPOINT = `${DEFAULT_WEB_BASE}/api/mcp`;
|
|
665
|
-
function
|
|
882
|
+
function log2(msg) {
|
|
666
883
|
process.stdout.write(msg + "\n");
|
|
667
884
|
}
|
|
668
885
|
async function cmdInit() {
|
|
669
886
|
const cwd = process.cwd();
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
887
|
+
log2(kleur6.bold("Mimi Seed CLI \u2014 init"));
|
|
888
|
+
log2(kleur6.dim(cwd));
|
|
889
|
+
log2("");
|
|
673
890
|
if (!await hasAnyProjectSignal(cwd)) {
|
|
674
|
-
|
|
891
|
+
log2(kleur6.yellow("\u26A0 package.json / app.json / android / ios \uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uADF8\uB798\uB3C4 \uC9C4\uD589\uD569\uB2C8\uB2E4."));
|
|
675
892
|
}
|
|
676
|
-
|
|
893
|
+
log2("\u{1F50D} \uC571 \uAC10\uC9C0 \uC911...");
|
|
677
894
|
const hints = await detectHints(cwd);
|
|
678
895
|
if (hints.length === 0) {
|
|
679
|
-
|
|
896
|
+
log2(kleur6.yellow("\uAC10\uC9C0\uB41C \uC571 \uC5C6\uC74C. \uC6F9\uC5D0\uC11C \uC218\uB3D9 \uB4F1\uB85D \uAC00\uB2A5."));
|
|
680
897
|
} else {
|
|
681
898
|
for (const h of hints) {
|
|
682
899
|
const tag = [h.packageName && `android:${h.packageName}`, h.bundleId && `ios:${h.bundleId}`].filter(Boolean).join(" ");
|
|
683
|
-
|
|
900
|
+
log2(` \u2022 ${h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)"} ${kleur6.dim(tag)}`);
|
|
684
901
|
}
|
|
685
902
|
}
|
|
686
|
-
|
|
903
|
+
log2("");
|
|
687
904
|
if (process.env.MIMI_SEED_TOKEN) {
|
|
688
905
|
const cfg2 = await getEffectiveConfig();
|
|
689
906
|
if (cfg2 && hints.length > 0) {
|
|
690
|
-
|
|
907
|
+
log2(kleur6.dim("CI \uBAA8\uB4DC: MIMI_SEED_TOKEN \uC0AC\uC6A9"));
|
|
691
908
|
const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
|
|
692
909
|
const result = await mcpCall(cfg2.endpoint, cfg2.token, "sync_apps", { hints: payload });
|
|
693
910
|
if (result.isError) {
|
|
694
|
-
|
|
911
|
+
log2(kleur6.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
|
|
695
912
|
} else {
|
|
696
|
-
for (const line of result.text.split("\n"))
|
|
913
|
+
for (const line of result.text.split("\n")) log2(" " + line);
|
|
697
914
|
}
|
|
698
915
|
}
|
|
699
|
-
|
|
916
|
+
log2(kleur6.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
|
|
700
917
|
return;
|
|
701
918
|
}
|
|
702
|
-
|
|
919
|
+
log2("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
|
|
703
920
|
const hostName = os2.hostname().slice(0, 32);
|
|
704
921
|
const name = `cli-${hostName}`;
|
|
705
922
|
const { port, promise } = await awaitHandshake(5 * 60 * 1e3);
|
|
706
923
|
const callback = `http://127.0.0.1:${port}/cb`;
|
|
707
924
|
const connectUrl = `${DEFAULT_WEB_BASE}/cli/connect?callback=${encodeURIComponent(callback)}&name=${encodeURIComponent(name)}`;
|
|
708
|
-
|
|
925
|
+
log2(kleur6.dim(` ${connectUrl}`));
|
|
709
926
|
await open(connectUrl);
|
|
710
927
|
let handshake;
|
|
711
928
|
try {
|
|
712
929
|
handshake = await promise;
|
|
713
930
|
} catch (e) {
|
|
714
|
-
|
|
931
|
+
log2(kleur6.red("\uC5F0\uACB0 \uC2E4\uD328: " + e.message));
|
|
715
932
|
process.exit(1);
|
|
716
933
|
}
|
|
717
|
-
|
|
934
|
+
log2(kleur6.green("\u2713 \uD1A0\uD070 \uC218\uC2E0"));
|
|
718
935
|
const cfg = {
|
|
719
936
|
token: handshake.token,
|
|
720
937
|
prefix: handshake.prefix,
|
|
@@ -723,30 +940,30 @@ async function cmdInit() {
|
|
|
723
940
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
724
941
|
};
|
|
725
942
|
await writeConfig(cfg);
|
|
726
|
-
|
|
727
|
-
|
|
943
|
+
log2(kleur6.dim(` \uC800\uC7A5\uB428: ${CONFIG_LOCATION}`));
|
|
944
|
+
log2("");
|
|
728
945
|
if (hints.length > 0) {
|
|
729
|
-
|
|
946
|
+
log2("\u{1F504} \uC571 \uB4F1\uB85D \uC911...");
|
|
730
947
|
const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
|
|
731
948
|
const result = await mcpCall(cfg.endpoint, cfg.token, "sync_apps", { hints: payload });
|
|
732
949
|
if (result.isError) {
|
|
733
|
-
|
|
950
|
+
log2(kleur6.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
|
|
734
951
|
} else {
|
|
735
|
-
for (const line of result.text.split("\n"))
|
|
952
|
+
for (const line of result.text.split("\n")) log2(" " + line);
|
|
736
953
|
}
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
954
|
+
log2("");
|
|
955
|
+
}
|
|
956
|
+
log2(kleur6.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
|
|
957
|
+
log2("");
|
|
958
|
+
log2("Claude Code\uC5D0\uC11C \uC774\uB807\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694:");
|
|
959
|
+
log2(kleur6.cyan(' "\uB0B4 \uC571 \uCD9C\uC2DC \uC900\uBE44\uB410\uC5B4?"'));
|
|
960
|
+
log2(kleur6.cyan(' "\uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC368\uC918"'));
|
|
961
|
+
log2(kleur6.cyan(' "\uB4F1\uB85D\uB41C \uC571 \uBAA9\uB85D \uBCF4\uC5EC\uC918"'));
|
|
962
|
+
log2("");
|
|
963
|
+
log2(`\uB300\uC2DC\uBCF4\uB4DC: ${kleur6.underline(DEFAULT_WEB_BASE + "/apps")}`);
|
|
964
|
+
log2("");
|
|
965
|
+
log2(
|
|
966
|
+
kleur6.dim(
|
|
750
967
|
`Claude Code MCP \uB4F1\uB85D:
|
|
751
968
|
claude mcp add --transport http mimi-seed ${DEFAULT_MCP_ENDPOINT} \\
|
|
752
969
|
--header "Authorization: Bearer ${cfg.prefix}..."`
|
|
@@ -756,38 +973,40 @@ async function cmdInit() {
|
|
|
756
973
|
async function cmdStatus() {
|
|
757
974
|
const cfg = await getEffectiveConfig();
|
|
758
975
|
if (!cfg) {
|
|
759
|
-
|
|
976
|
+
log2(kleur6.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
|
|
760
977
|
process.exit(1);
|
|
761
978
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
979
|
+
log2(kleur6.bold("Mimi Seed \uC5F0\uACB0 \uC0C1\uD0DC"));
|
|
980
|
+
log2(` \uD1A0\uD070: ${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
|
|
981
|
+
log2(` \uC5D4\uB4DC\uD3EC\uC778\uD2B8: ${cfg.endpoint}`);
|
|
982
|
+
log2("");
|
|
983
|
+
log2("\u{1F4CB} \uC571 \uBAA9\uB85D:");
|
|
767
984
|
const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
|
|
768
985
|
if (r.isError) {
|
|
769
|
-
|
|
986
|
+
log2(kleur6.red("\uC870\uD68C \uC2E4\uD328: " + r.text));
|
|
770
987
|
process.exit(1);
|
|
771
988
|
}
|
|
772
|
-
for (const line of r.text.split("\n"))
|
|
989
|
+
for (const line of r.text.split("\n")) log2(" " + line);
|
|
773
990
|
}
|
|
774
991
|
async function cmdLogout() {
|
|
775
992
|
await deleteConfig();
|
|
776
|
-
|
|
777
|
-
|
|
993
|
+
log2(kleur6.green("\u2713 \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C \uC644\uB8CC."));
|
|
994
|
+
log2(kleur6.dim("\uC6F9\uC5D0\uC11C \uD1A0\uD070 \uD574\uC9C0: /workspace/api-tokens"));
|
|
778
995
|
}
|
|
779
996
|
function printHelp() {
|
|
780
|
-
|
|
997
|
+
log2(`${kleur6.bold("mimi-seed")} \u2014 Claude Code\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
|
|
781
998
|
|
|
782
|
-
${
|
|
783
|
-
${
|
|
784
|
-
${
|
|
785
|
-
${
|
|
786
|
-
${
|
|
787
|
-
${
|
|
788
|
-
${
|
|
999
|
+
${kleur6.bold("\uBA85\uB839\uC5B4:")}
|
|
1000
|
+
${kleur6.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
|
|
1001
|
+
${kleur6.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
|
|
1002
|
+
${kleur6.cyan("mimi-seed auth")} Google OAuth \uC778\uC99D (Firebase/AdMob/Play). 'mimi-seed auth --help'
|
|
1003
|
+
${kleur6.cyan("mimi-seed doctor")} \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI \uCCB4\uD06C)
|
|
1004
|
+
${kleur6.cyan("mimi-seed check")} \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
|
|
1005
|
+
${kleur6.cyan("mimi-seed notes")} \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
|
|
1006
|
+
${kleur6.cyan("mimi-seed review")} \uB9AC\uBDF0 \uB2F5\uBCC0 AI \uCD08\uC548 \uC0DD\uC131 \uBC0F Play Store \uAC8C\uC2DC
|
|
1007
|
+
${kleur6.cyan("mimi-seed logout")} \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C
|
|
789
1008
|
|
|
790
|
-
${
|
|
1009
|
+
${kleur6.bold("mimi-seed notes \uC635\uC158:")}
|
|
791
1010
|
--from <ref> \uC2DC\uC791 \uCEE4\uBC0B/\uD0DC\uADF8 (\uAE30\uBCF8: \uCD5C\uC2E0 \uD0DC\uADF8)
|
|
792
1011
|
--to <ref> \uB05D \uCEE4\uBC0B (\uAE30\uBCF8: HEAD)
|
|
793
1012
|
--locale ko,en-US \uB300\uC0C1 \uB85C\uCF00\uC77C (\uC27C\uD45C \uAD6C\uBD84)
|
|
@@ -795,11 +1014,22 @@ ${kleur4.bold("mimi-seed notes \uC635\uC158:")}
|
|
|
795
1014
|
--no-interactive CI \uBAA8\uB4DC (\uD504\uB86C\uD504\uD2B8 \uC5C6\uC74C)
|
|
796
1015
|
--limit <n> \uCD5C\uB300 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 30)
|
|
797
1016
|
|
|
798
|
-
${
|
|
1017
|
+
${kleur6.bold("mimi-seed check \uC635\uC158:")}
|
|
799
1018
|
--app <id> \uC571 ID \uC9C0\uC815
|
|
800
1019
|
--fail-on-blocker \uBE14\uB85C\uCEE4 \uC788\uC73C\uBA74 exit 1 (CI\uC6A9)
|
|
801
1020
|
|
|
802
|
-
${
|
|
1021
|
+
${kleur6.bold("mimi-seed review \uC635\uC158:")}
|
|
1022
|
+
--text <\uB0B4\uC6A9> \uB9AC\uBDF0 \uC6D0\uBB38 (\uBBF8\uC785\uB825 \uC2DC \uB300\uD654\uD615 \uD504\uB86C\uD504\uD2B8)
|
|
1023
|
+
--rating <1-5> \uBCC4\uC810
|
|
1024
|
+
--tone <tone> friendly / professional / empathetic / brief (\uAE30\uBCF8: friendly)
|
|
1025
|
+
--language <\uCF54\uB4DC> \uB2F5\uBCC0 \uC5B8\uC5B4 (\uAE30\uBCF8: ko)
|
|
1026
|
+
--app-name <\uC774\uB984> \uC571 \uC774\uB984 (\uB9E5\uB77D\uC6A9)
|
|
1027
|
+
--apply \uB2F5\uBCC0\uC744 Play Store\uC5D0 \uAC8C\uC2DC
|
|
1028
|
+
--review-id <id> \uB9AC\uBDF0 ID (--apply \uC2DC \uD544\uC694)
|
|
1029
|
+
--package-name <p> \uD328\uD0A4\uC9C0\uBA85 (--apply \uC2DC \uD544\uC694)
|
|
1030
|
+
--no-interactive CI \uBAA8\uB4DC
|
|
1031
|
+
|
|
1032
|
+
${kleur6.bold("\uD658\uACBD\uBCC0\uC218:")}
|
|
803
1033
|
MIMI_SEED_TOKEN PAT \uD1A0\uD070 (CI/CD \uBB34\uC778\uC99D \uBAA8\uB4DC)
|
|
804
1034
|
MIMI_SEED_WEB_BASE \uC11C\uBC84 \uC8FC\uC18C (\uAE30\uBCF8: https://mimi-seed.pryzm.gg)
|
|
805
1035
|
ANTHROPIC_API_KEY AI \uB178\uD2B8 \uC0DD\uC131 \uD65C\uC131\uD654 (\uC120\uD0DD)
|
|
@@ -825,6 +1055,12 @@ async function main() {
|
|
|
825
1055
|
case "notes":
|
|
826
1056
|
await cmdNotes(restArgs);
|
|
827
1057
|
break;
|
|
1058
|
+
case "review":
|
|
1059
|
+
await cmdReview(restArgs);
|
|
1060
|
+
break;
|
|
1061
|
+
case "auth":
|
|
1062
|
+
await cmdAuth(restArgs);
|
|
1063
|
+
break;
|
|
828
1064
|
case "logout":
|
|
829
1065
|
await cmdLogout();
|
|
830
1066
|
break;
|
|
@@ -834,13 +1070,13 @@ async function main() {
|
|
|
834
1070
|
printHelp();
|
|
835
1071
|
break;
|
|
836
1072
|
default:
|
|
837
|
-
|
|
1073
|
+
log2(kleur6.red(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`));
|
|
838
1074
|
printHelp();
|
|
839
1075
|
process.exit(1);
|
|
840
1076
|
}
|
|
841
1077
|
} catch (e) {
|
|
842
|
-
|
|
843
|
-
if (process.env.DEBUG)
|
|
1078
|
+
log2(kleur6.red(`\uC624\uB958: ${e.message}`));
|
|
1079
|
+
if (process.env.DEBUG) log2(e.stack ?? "");
|
|
844
1080
|
process.exit(1);
|
|
845
1081
|
}
|
|
846
1082
|
}
|
package/package.json
CHANGED