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.
Files changed (3) hide show
  1. package/README.md +64 -3
  2. package/dist/index.js +296 -60
  3. 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
- # GitHub Actions 예시
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 kleur4 from "kleur";
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 log(msg) {
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
- log(kleur4.bold("Mimi Seed CLI \u2014 init"));
671
- log(kleur4.dim(cwd));
672
- log("");
887
+ log2(kleur6.bold("Mimi Seed CLI \u2014 init"));
888
+ log2(kleur6.dim(cwd));
889
+ log2("");
673
890
  if (!await hasAnyProjectSignal(cwd)) {
674
- log(kleur4.yellow("\u26A0 package.json / app.json / android / ios \uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uADF8\uB798\uB3C4 \uC9C4\uD589\uD569\uB2C8\uB2E4."));
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
- log("\u{1F50D} \uC571 \uAC10\uC9C0 \uC911...");
893
+ log2("\u{1F50D} \uC571 \uAC10\uC9C0 \uC911...");
677
894
  const hints = await detectHints(cwd);
678
895
  if (hints.length === 0) {
679
- log(kleur4.yellow("\uAC10\uC9C0\uB41C \uC571 \uC5C6\uC74C. \uC6F9\uC5D0\uC11C \uC218\uB3D9 \uB4F1\uB85D \uAC00\uB2A5."));
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
- log(` \u2022 ${h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)"} ${kleur4.dim(tag)}`);
900
+ log2(` \u2022 ${h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)"} ${kleur6.dim(tag)}`);
684
901
  }
685
902
  }
686
- log("");
903
+ log2("");
687
904
  if (process.env.MIMI_SEED_TOKEN) {
688
905
  const cfg2 = await getEffectiveConfig();
689
906
  if (cfg2 && hints.length > 0) {
690
- log(kleur4.dim("CI \uBAA8\uB4DC: MIMI_SEED_TOKEN \uC0AC\uC6A9"));
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
- log(kleur4.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
911
+ log2(kleur6.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
695
912
  } else {
696
- for (const line of result.text.split("\n")) log(" " + line);
913
+ for (const line of result.text.split("\n")) log2(" " + line);
697
914
  }
698
915
  }
699
- log(kleur4.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
916
+ log2(kleur6.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
700
917
  return;
701
918
  }
702
- log("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
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
- log(kleur4.dim(` ${connectUrl}`));
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
- log(kleur4.red("\uC5F0\uACB0 \uC2E4\uD328: " + e.message));
931
+ log2(kleur6.red("\uC5F0\uACB0 \uC2E4\uD328: " + e.message));
715
932
  process.exit(1);
716
933
  }
717
- log(kleur4.green("\u2713 \uD1A0\uD070 \uC218\uC2E0"));
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
- log(kleur4.dim(` \uC800\uC7A5\uB428: ${CONFIG_LOCATION}`));
727
- log("");
943
+ log2(kleur6.dim(` \uC800\uC7A5\uB428: ${CONFIG_LOCATION}`));
944
+ log2("");
728
945
  if (hints.length > 0) {
729
- log("\u{1F504} \uC571 \uB4F1\uB85D \uC911...");
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
- log(kleur4.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
950
+ log2(kleur6.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
734
951
  } else {
735
- for (const line of result.text.split("\n")) log(" " + line);
952
+ for (const line of result.text.split("\n")) log2(" " + line);
736
953
  }
737
- log("");
738
- }
739
- log(kleur4.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
740
- log("");
741
- log("Claude Code\uC5D0\uC11C \uC774\uB807\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694:");
742
- log(kleur4.cyan(' "\uB0B4 \uC571 \uCD9C\uC2DC \uC900\uBE44\uB410\uC5B4?"'));
743
- log(kleur4.cyan(' "\uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC368\uC918"'));
744
- log(kleur4.cyan(' "\uB4F1\uB85D\uB41C \uC571 \uBAA9\uB85D \uBCF4\uC5EC\uC918"'));
745
- log("");
746
- log(`\uB300\uC2DC\uBCF4\uB4DC: ${kleur4.underline(DEFAULT_WEB_BASE + "/apps")}`);
747
- log("");
748
- log(
749
- kleur4.dim(
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
- log(kleur4.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
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
- log(kleur4.bold("Mimi Seed \uC5F0\uACB0 \uC0C1\uD0DC"));
763
- log(` \uD1A0\uD070: ${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
764
- log(` \uC5D4\uB4DC\uD3EC\uC778\uD2B8: ${cfg.endpoint}`);
765
- log("");
766
- log("\u{1F4CB} \uC571 \uBAA9\uB85D:");
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
- log(kleur4.red("\uC870\uD68C \uC2E4\uD328: " + r.text));
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")) log(" " + line);
989
+ for (const line of r.text.split("\n")) log2(" " + line);
773
990
  }
774
991
  async function cmdLogout() {
775
992
  await deleteConfig();
776
- log(kleur4.green("\u2713 \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C \uC644\uB8CC."));
777
- log(kleur4.dim("\uC6F9\uC5D0\uC11C \uD1A0\uD070 \uD574\uC9C0: /workspace/api-tokens"));
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
- log(`${kleur4.bold("mimi-seed")} \u2014 Claude Code\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
997
+ log2(`${kleur6.bold("mimi-seed")} \u2014 Claude Code\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
781
998
 
782
- ${kleur4.bold("\uBA85\uB839\uC5B4:")}
783
- ${kleur4.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
784
- ${kleur4.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
785
- ${kleur4.cyan("mimi-seed doctor")} \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI \uCCB4\uD06C)
786
- ${kleur4.cyan("mimi-seed check")} \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
787
- ${kleur4.cyan("mimi-seed notes")} \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
788
- ${kleur4.cyan("mimi-seed logout")} \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C
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
- ${kleur4.bold("mimi-seed notes \uC635\uC158:")}
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
- ${kleur4.bold("mimi-seed check \uC635\uC158:")}
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
- ${kleur4.bold("\uD658\uACBD\uBCC0\uC218:")}
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
- log(kleur4.red(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`));
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
- log(kleur4.red(`\uC624\uB958: ${e.message}`));
843
- if (process.env.DEBUG) log(e.stack ?? "");
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
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "mimi-seed",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Mimi Seed CLI — Claude Code에서 앱 출시 운영을 관리합니다.",
5
5
  "bin": {
6
- "mimi-seed": "./dist/index.js"
6
+ "mimi-seed": "dist/index.js"
7
7
  },
8
8
  "type": "module",
9
9
  "files": [