mimi-seed 0.2.0 → 0.2.1

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 +201 -38
  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/app-gen/.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 kleur5 from "kleur";
6
6
  import open from "open";
7
7
 
8
8
  // src/detect.ts
@@ -659,6 +659,154 @@ 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
+
662
810
  // src/index.ts
663
811
  var DEFAULT_WEB_BASE = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
664
812
  var DEFAULT_MCP_ENDPOINT = `${DEFAULT_WEB_BASE}/api/mcp`;
@@ -667,36 +815,36 @@ function log(msg) {
667
815
  }
668
816
  async function cmdInit() {
669
817
  const cwd = process.cwd();
670
- log(kleur4.bold("Mimi Seed CLI \u2014 init"));
671
- log(kleur4.dim(cwd));
818
+ log(kleur5.bold("Mimi Seed CLI \u2014 init"));
819
+ log(kleur5.dim(cwd));
672
820
  log("");
673
821
  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."));
822
+ log(kleur5.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
823
  }
676
824
  log("\u{1F50D} \uC571 \uAC10\uC9C0 \uC911...");
677
825
  const hints = await detectHints(cwd);
678
826
  if (hints.length === 0) {
679
- log(kleur4.yellow("\uAC10\uC9C0\uB41C \uC571 \uC5C6\uC74C. \uC6F9\uC5D0\uC11C \uC218\uB3D9 \uB4F1\uB85D \uAC00\uB2A5."));
827
+ log(kleur5.yellow("\uAC10\uC9C0\uB41C \uC571 \uC5C6\uC74C. \uC6F9\uC5D0\uC11C \uC218\uB3D9 \uB4F1\uB85D \uAC00\uB2A5."));
680
828
  } else {
681
829
  for (const h of hints) {
682
830
  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)}`);
831
+ log(` \u2022 ${h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)"} ${kleur5.dim(tag)}`);
684
832
  }
685
833
  }
686
834
  log("");
687
835
  if (process.env.MIMI_SEED_TOKEN) {
688
836
  const cfg2 = await getEffectiveConfig();
689
837
  if (cfg2 && hints.length > 0) {
690
- log(kleur4.dim("CI \uBAA8\uB4DC: MIMI_SEED_TOKEN \uC0AC\uC6A9"));
838
+ log(kleur5.dim("CI \uBAA8\uB4DC: MIMI_SEED_TOKEN \uC0AC\uC6A9"));
691
839
  const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
692
840
  const result = await mcpCall(cfg2.endpoint, cfg2.token, "sync_apps", { hints: payload });
693
841
  if (result.isError) {
694
- log(kleur4.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
842
+ log(kleur5.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
695
843
  } else {
696
844
  for (const line of result.text.split("\n")) log(" " + line);
697
845
  }
698
846
  }
699
- log(kleur4.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
847
+ log(kleur5.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
700
848
  return;
701
849
  }
702
850
  log("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
@@ -705,16 +853,16 @@ async function cmdInit() {
705
853
  const { port, promise } = await awaitHandshake(5 * 60 * 1e3);
706
854
  const callback = `http://127.0.0.1:${port}/cb`;
707
855
  const connectUrl = `${DEFAULT_WEB_BASE}/cli/connect?callback=${encodeURIComponent(callback)}&name=${encodeURIComponent(name)}`;
708
- log(kleur4.dim(` ${connectUrl}`));
856
+ log(kleur5.dim(` ${connectUrl}`));
709
857
  await open(connectUrl);
710
858
  let handshake;
711
859
  try {
712
860
  handshake = await promise;
713
861
  } catch (e) {
714
- log(kleur4.red("\uC5F0\uACB0 \uC2E4\uD328: " + e.message));
862
+ log(kleur5.red("\uC5F0\uACB0 \uC2E4\uD328: " + e.message));
715
863
  process.exit(1);
716
864
  }
717
- log(kleur4.green("\u2713 \uD1A0\uD070 \uC218\uC2E0"));
865
+ log(kleur5.green("\u2713 \uD1A0\uD070 \uC218\uC2E0"));
718
866
  const cfg = {
719
867
  token: handshake.token,
720
868
  prefix: handshake.prefix,
@@ -723,30 +871,30 @@ async function cmdInit() {
723
871
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
724
872
  };
725
873
  await writeConfig(cfg);
726
- log(kleur4.dim(` \uC800\uC7A5\uB428: ${CONFIG_LOCATION}`));
874
+ log(kleur5.dim(` \uC800\uC7A5\uB428: ${CONFIG_LOCATION}`));
727
875
  log("");
728
876
  if (hints.length > 0) {
729
877
  log("\u{1F504} \uC571 \uB4F1\uB85D \uC911...");
730
878
  const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
731
879
  const result = await mcpCall(cfg.endpoint, cfg.token, "sync_apps", { hints: payload });
732
880
  if (result.isError) {
733
- log(kleur4.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
881
+ log(kleur5.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
734
882
  } else {
735
883
  for (const line of result.text.split("\n")) log(" " + line);
736
884
  }
737
885
  log("");
738
886
  }
739
- log(kleur4.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
887
+ log(kleur5.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
740
888
  log("");
741
889
  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"'));
890
+ log(kleur5.cyan(' "\uB0B4 \uC571 \uCD9C\uC2DC \uC900\uBE44\uB410\uC5B4?"'));
891
+ log(kleur5.cyan(' "\uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC368\uC918"'));
892
+ log(kleur5.cyan(' "\uB4F1\uB85D\uB41C \uC571 \uBAA9\uB85D \uBCF4\uC5EC\uC918"'));
745
893
  log("");
746
- log(`\uB300\uC2DC\uBCF4\uB4DC: ${kleur4.underline(DEFAULT_WEB_BASE + "/apps")}`);
894
+ log(`\uB300\uC2DC\uBCF4\uB4DC: ${kleur5.underline(DEFAULT_WEB_BASE + "/apps")}`);
747
895
  log("");
748
896
  log(
749
- kleur4.dim(
897
+ kleur5.dim(
750
898
  `Claude Code MCP \uB4F1\uB85D:
751
899
  claude mcp add --transport http mimi-seed ${DEFAULT_MCP_ENDPOINT} \\
752
900
  --header "Authorization: Bearer ${cfg.prefix}..."`
@@ -756,38 +904,39 @@ async function cmdInit() {
756
904
  async function cmdStatus() {
757
905
  const cfg = await getEffectiveConfig();
758
906
  if (!cfg) {
759
- log(kleur4.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
907
+ log(kleur5.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
760
908
  process.exit(1);
761
909
  }
762
- log(kleur4.bold("Mimi Seed \uC5F0\uACB0 \uC0C1\uD0DC"));
910
+ log(kleur5.bold("Mimi Seed \uC5F0\uACB0 \uC0C1\uD0DC"));
763
911
  log(` \uD1A0\uD070: ${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
764
912
  log(` \uC5D4\uB4DC\uD3EC\uC778\uD2B8: ${cfg.endpoint}`);
765
913
  log("");
766
914
  log("\u{1F4CB} \uC571 \uBAA9\uB85D:");
767
915
  const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
768
916
  if (r.isError) {
769
- log(kleur4.red("\uC870\uD68C \uC2E4\uD328: " + r.text));
917
+ log(kleur5.red("\uC870\uD68C \uC2E4\uD328: " + r.text));
770
918
  process.exit(1);
771
919
  }
772
920
  for (const line of r.text.split("\n")) log(" " + line);
773
921
  }
774
922
  async function cmdLogout() {
775
923
  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"));
924
+ log(kleur5.green("\u2713 \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C \uC644\uB8CC."));
925
+ log(kleur5.dim("\uC6F9\uC5D0\uC11C \uD1A0\uD070 \uD574\uC9C0: /workspace/api-tokens"));
778
926
  }
779
927
  function printHelp() {
780
- log(`${kleur4.bold("mimi-seed")} \u2014 Claude Code\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
928
+ log(`${kleur5.bold("mimi-seed")} \u2014 Claude Code\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
781
929
 
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
930
+ ${kleur5.bold("\uBA85\uB839\uC5B4:")}
931
+ ${kleur5.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
932
+ ${kleur5.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
933
+ ${kleur5.cyan("mimi-seed doctor")} \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI \uCCB4\uD06C)
934
+ ${kleur5.cyan("mimi-seed check")} \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
935
+ ${kleur5.cyan("mimi-seed notes")} \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
936
+ ${kleur5.cyan("mimi-seed review")} \uB9AC\uBDF0 \uB2F5\uBCC0 AI \uCD08\uC548 \uC0DD\uC131 \uBC0F Play Store \uAC8C\uC2DC
937
+ ${kleur5.cyan("mimi-seed logout")} \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C
789
938
 
790
- ${kleur4.bold("mimi-seed notes \uC635\uC158:")}
939
+ ${kleur5.bold("mimi-seed notes \uC635\uC158:")}
791
940
  --from <ref> \uC2DC\uC791 \uCEE4\uBC0B/\uD0DC\uADF8 (\uAE30\uBCF8: \uCD5C\uC2E0 \uD0DC\uADF8)
792
941
  --to <ref> \uB05D \uCEE4\uBC0B (\uAE30\uBCF8: HEAD)
793
942
  --locale ko,en-US \uB300\uC0C1 \uB85C\uCF00\uC77C (\uC27C\uD45C \uAD6C\uBD84)
@@ -795,11 +944,22 @@ ${kleur4.bold("mimi-seed notes \uC635\uC158:")}
795
944
  --no-interactive CI \uBAA8\uB4DC (\uD504\uB86C\uD504\uD2B8 \uC5C6\uC74C)
796
945
  --limit <n> \uCD5C\uB300 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 30)
797
946
 
798
- ${kleur4.bold("mimi-seed check \uC635\uC158:")}
947
+ ${kleur5.bold("mimi-seed check \uC635\uC158:")}
799
948
  --app <id> \uC571 ID \uC9C0\uC815
800
949
  --fail-on-blocker \uBE14\uB85C\uCEE4 \uC788\uC73C\uBA74 exit 1 (CI\uC6A9)
801
950
 
802
- ${kleur4.bold("\uD658\uACBD\uBCC0\uC218:")}
951
+ ${kleur5.bold("mimi-seed review \uC635\uC158:")}
952
+ --text <\uB0B4\uC6A9> \uB9AC\uBDF0 \uC6D0\uBB38 (\uBBF8\uC785\uB825 \uC2DC \uB300\uD654\uD615 \uD504\uB86C\uD504\uD2B8)
953
+ --rating <1-5> \uBCC4\uC810
954
+ --tone <tone> friendly / professional / empathetic / brief (\uAE30\uBCF8: friendly)
955
+ --language <\uCF54\uB4DC> \uB2F5\uBCC0 \uC5B8\uC5B4 (\uAE30\uBCF8: ko)
956
+ --app-name <\uC774\uB984> \uC571 \uC774\uB984 (\uB9E5\uB77D\uC6A9)
957
+ --apply \uB2F5\uBCC0\uC744 Play Store\uC5D0 \uAC8C\uC2DC
958
+ --review-id <id> \uB9AC\uBDF0 ID (--apply \uC2DC \uD544\uC694)
959
+ --package-name <p> \uD328\uD0A4\uC9C0\uBA85 (--apply \uC2DC \uD544\uC694)
960
+ --no-interactive CI \uBAA8\uB4DC
961
+
962
+ ${kleur5.bold("\uD658\uACBD\uBCC0\uC218:")}
803
963
  MIMI_SEED_TOKEN PAT \uD1A0\uD070 (CI/CD \uBB34\uC778\uC99D \uBAA8\uB4DC)
804
964
  MIMI_SEED_WEB_BASE \uC11C\uBC84 \uC8FC\uC18C (\uAE30\uBCF8: https://mimi-seed.pryzm.gg)
805
965
  ANTHROPIC_API_KEY AI \uB178\uD2B8 \uC0DD\uC131 \uD65C\uC131\uD654 (\uC120\uD0DD)
@@ -825,6 +985,9 @@ async function main() {
825
985
  case "notes":
826
986
  await cmdNotes(restArgs);
827
987
  break;
988
+ case "review":
989
+ await cmdReview(restArgs);
990
+ break;
828
991
  case "logout":
829
992
  await cmdLogout();
830
993
  break;
@@ -834,12 +997,12 @@ async function main() {
834
997
  printHelp();
835
998
  break;
836
999
  default:
837
- log(kleur4.red(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`));
1000
+ log(kleur5.red(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`));
838
1001
  printHelp();
839
1002
  process.exit(1);
840
1003
  }
841
1004
  } catch (e) {
842
- log(kleur4.red(`\uC624\uB958: ${e.message}`));
1005
+ log(kleur5.red(`\uC624\uB958: ${e.message}`));
843
1006
  if (process.env.DEBUG) log(e.stack ?? "");
844
1007
  process.exit(1);
845
1008
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "mimi-seed",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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": [