mimi-seed 0.2.1 → 0.2.3

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 CHANGED
@@ -162,7 +162,7 @@ on:
162
162
 
163
163
  jobs:
164
164
  release:
165
- uses: jeonghwanko/app-gen/.github/workflows/mimi-seed-release.yml@master
165
+ uses: jeonghwanko/mimi-seed/.github/workflows/mimi-seed-release.yml@master
166
166
  with:
167
167
  locales: 'ko,en-US' # 다국어 릴리즈 노트
168
168
  apply-notes: true # 스토어에 자동 적용
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/mcp-client.ts
4
+ async function postMcp(endpoint, token, body, sessionId) {
5
+ const headers = {
6
+ "Content-Type": "application/json",
7
+ Accept: "application/json, text/event-stream",
8
+ Authorization: `Bearer ${token}`
9
+ };
10
+ if (sessionId) headers["Mcp-Session-Id"] = sessionId;
11
+ const res = await fetch(endpoint, { method: "POST", headers, body: JSON.stringify(body) });
12
+ const newSession = res.headers.get("Mcp-Session-Id") ?? sessionId ?? null;
13
+ const contentType = res.headers.get("content-type") ?? "";
14
+ let payload;
15
+ if (contentType.includes("text/event-stream")) {
16
+ const text = await res.text();
17
+ const line = text.split("\n").map((l) => l.trim()).find((l) => l.startsWith("data:"));
18
+ if (!line) throw new Error("MCP SSE \uC751\uB2F5\uC5D0 data \uC5C6\uC74C");
19
+ payload = JSON.parse(line.slice(5).trim());
20
+ } else {
21
+ payload = await res.json();
22
+ }
23
+ return { payload, sessionId: newSession };
24
+ }
25
+ async function mcpCall(endpoint, token, name, args) {
26
+ const { payload: initPayload, sessionId } = await postMcp(endpoint, token, {
27
+ jsonrpc: "2.0",
28
+ id: 1,
29
+ method: "initialize",
30
+ params: {
31
+ protocolVersion: "2024-11-05",
32
+ capabilities: {},
33
+ clientInfo: { name: "mimi-seed-cli", version: "1.0.0" }
34
+ }
35
+ });
36
+ if (initPayload.error) throw new Error(`MCP init \uC2E4\uD328: ${initPayload.error.message}`);
37
+ await postMcp(endpoint, token, {
38
+ jsonrpc: "2.0",
39
+ method: "notifications/initialized"
40
+ }, sessionId ?? void 0).catch(() => null);
41
+ const { payload } = await postMcp(endpoint, token, {
42
+ jsonrpc: "2.0",
43
+ id: 2,
44
+ method: "tools/call",
45
+ params: { name, arguments: args }
46
+ }, sessionId ?? void 0);
47
+ if (payload.error) {
48
+ return { text: payload.error.message, isError: true };
49
+ }
50
+ const content = payload.result?.content ?? [];
51
+ const text = content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n");
52
+ return { text, isError: payload.result?.isError ?? false };
53
+ }
54
+
55
+ export {
56
+ mcpCall
57
+ };
package/dist/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ mcpCall
4
+ } from "./chunk-FW4DVFZD.js";
2
5
 
3
6
  // src/index.ts
4
7
  import os2 from "os";
5
- import kleur5 from "kleur";
8
+ import kleur7 from "kleur";
6
9
  import open from "open";
7
10
 
8
11
  // src/detect.ts
@@ -209,40 +212,6 @@ async function awaitHandshake(timeoutMs) {
209
212
  return { port, promise };
210
213
  }
211
214
 
212
- // src/mcp-client.ts
213
- async function mcpCall(endpoint, token, name, args) {
214
- const res = await fetch(endpoint, {
215
- method: "POST",
216
- headers: {
217
- "Content-Type": "application/json",
218
- Accept: "application/json, text/event-stream",
219
- Authorization: `Bearer ${token}`
220
- },
221
- body: JSON.stringify({
222
- jsonrpc: "2.0",
223
- id: 1,
224
- method: "tools/call",
225
- params: { name, arguments: args }
226
- })
227
- });
228
- const contentType = res.headers.get("content-type") ?? "";
229
- let payload;
230
- if (contentType.includes("text/event-stream")) {
231
- const text2 = await res.text();
232
- const line = text2.split("\n").map((l) => l.trim()).find((l) => l.startsWith("data:"));
233
- if (!line) throw new Error("MCP SSE \uC751\uB2F5\uC5D0 data \uC5C6\uC74C");
234
- payload = JSON.parse(line.slice(5).trim());
235
- } else {
236
- payload = await res.json();
237
- }
238
- if (payload.error) {
239
- return { text: payload.error.message, isError: true };
240
- }
241
- const content = payload.result?.content ?? [];
242
- const text = content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n");
243
- return { text, isError: payload.result?.isError ?? false };
244
- }
245
-
246
215
  // src/config.ts
247
216
  import fs2 from "fs/promises";
248
217
  import path2 from "path";
@@ -259,7 +228,10 @@ async function readConfig() {
259
228
  }
260
229
  async function writeConfig(cfg) {
261
230
  await fs2.mkdir(CONFIG_DIR, { recursive: true });
262
- await fs2.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2), { mode: 384 });
231
+ await fs2.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2));
232
+ if (process.platform !== "win32") {
233
+ await fs2.chmod(CONFIG_PATH, 384);
234
+ }
263
235
  }
264
236
  async function deleteConfig() {
265
237
  await fs2.rm(CONFIG_PATH, { force: true });
@@ -807,62 +779,377 @@ async function cmdReview(argv) {
807
779
  `) : kleur4.green("\u2713 \uB2F5\uBCC0 \uAC8C\uC2DC \uC644\uB8CC\n"));
808
780
  }
809
781
 
782
+ // src/auth.ts
783
+ import { spawn } from "child_process";
784
+ import kleur5 from "kleur";
785
+ var MCP_PKG = "@yoonion/mimi-seed-mcp";
786
+ function log(msg) {
787
+ process.stdout.write(msg + "\n");
788
+ }
789
+ function printAuthHelp() {
790
+ log(`${kleur5.bold("mimi-seed auth")} \u2014 Google OAuth \uC778\uC99D (Firebase / AdMob / Play Store)
791
+
792
+ ${kleur5.bold("\uC11C\uBE0C\uBA85\uB839:")}
793
+ ${kleur5.cyan("mimi-seed auth login")} \uBE0C\uB77C\uC6B0\uC800\uB85C \uB85C\uADF8\uC778 (\uC774\uBBF8 \uC788\uC73C\uBA74 \uC790\uB3D9 refresh \uC2DC\uB3C4)
794
+ ${kleur5.cyan("mimi-seed auth status")} \uD604\uC7AC \uD1A0\uD070 \uC0C1\uD0DC
795
+ ${kleur5.cyan("mimi-seed auth refresh")} refresh_token\uC73C\uB85C \uAC31\uC2E0\uB9CC \uC2DC\uB3C4 (\uBE0C\uB77C\uC6B0\uC800 X)
796
+ ${kleur5.cyan("mimi-seed auth logout")} \uD1A0\uD070 \uC0AD\uC81C
797
+
798
+ ${kleur5.bold("login \uC635\uC158:")}
799
+ --no-browser URL \uC790\uB3D9 \uC624\uD508 \uC548 \uD568 (\uC9C1\uC811 \uBCF5\uBD99)
800
+ --timeout <\uCD08> \uCF5C\uBC31 \uB300\uAE30 \uC2DC\uAC04 (\uAE30\uBCF8 600)
801
+ --force \uAE30\uC874 \uD1A0\uD070 \uBB34\uC2DC\uD558\uACE0 \uAC15\uC81C \uC7AC\uB85C\uADF8\uC778
802
+
803
+ ${kleur5.dim(`\uB0B4\uBD80\uC801\uC73C\uB85C ${MCP_PKG} \uC758 mimi-seed-auth CLI\uB97C \uD638\uCD9C\uD569\uB2C8\uB2E4.`)}`);
804
+ }
805
+ async function runMcpAuth(extraArgs) {
806
+ return new Promise((resolve) => {
807
+ const child = spawn("npx", ["-y", `${MCP_PKG}`, "mimi-seed-auth", ...extraArgs], {
808
+ stdio: "inherit",
809
+ shell: true
810
+ });
811
+ child.on("error", (e) => {
812
+ process.stderr.write(`
813
+ \u274C npx \uC2E4\uD589 \uC2E4\uD328: ${e.message}
814
+ `);
815
+ resolve(1);
816
+ });
817
+ child.on("exit", (code) => resolve(code ?? 0));
818
+ });
819
+ }
820
+ async function cmdAuth(args) {
821
+ const sub = args[0];
822
+ const rest = args.slice(1);
823
+ if (sub === "--help" || sub === "-h" || sub === "help") {
824
+ printAuthHelp();
825
+ return;
826
+ }
827
+ let mcpArgs;
828
+ switch (sub) {
829
+ case void 0:
830
+ case "login":
831
+ mcpArgs = rest;
832
+ break;
833
+ case "status":
834
+ mcpArgs = ["--status", ...rest];
835
+ break;
836
+ case "refresh":
837
+ mcpArgs = ["--refresh", ...rest];
838
+ break;
839
+ case "logout":
840
+ mcpArgs = ["--logout", ...rest];
841
+ break;
842
+ default:
843
+ log(kleur5.red(`\uC54C \uC218 \uC5C6\uB294 auth \uC11C\uBE0C\uBA85\uB839: ${sub}`));
844
+ printAuthHelp();
845
+ process.exit(1);
846
+ }
847
+ const code = await runMcpAuth(mcpArgs);
848
+ if (code !== 0) process.exit(code);
849
+ }
850
+
851
+ // src/deploy.ts
852
+ import kleur6 from "kleur";
853
+ import * as readline from "readline";
854
+ var PHASE_ICON = {
855
+ init: "\u{1F680}",
856
+ verify: "\u{1F50D}",
857
+ notes: "\u{1F4DD}",
858
+ apply: "\u{1F4E4}",
859
+ promote: "\u{1F3AF}",
860
+ done: "\u{1F389}",
861
+ error: "\u274C"
862
+ };
863
+ function log2(msg) {
864
+ process.stdout.write(msg + "\n");
865
+ }
866
+ function jenkinsHeaders(cfg) {
867
+ const creds = Buffer.from(`${cfg.user ?? "admin"}:${cfg.token}`).toString("base64");
868
+ return { Authorization: `Basic ${creds}`, "Content-Type": "application/json" };
869
+ }
870
+ async function triggerBuild(cfg, jobName, params) {
871
+ const qs = new URLSearchParams(params).toString();
872
+ const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/buildWithParameters?${qs}`;
873
+ const res = await fetch(url, { method: "POST", headers: jenkinsHeaders(cfg) });
874
+ if (!res.ok) {
875
+ throw new Error(`Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${res.status}: ${await res.text()}`);
876
+ }
877
+ const location = res.headers.get("Location") ?? "";
878
+ const match = location.match(/\/queue\/item\/(\d+)\//);
879
+ return match?.[1] ?? "";
880
+ }
881
+ async function getQueueBuildNumber(cfg, queueItemId) {
882
+ const url = `${cfg.url}/queue/item/${queueItemId}/api/json`;
883
+ const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
884
+ if (!res.ok) return null;
885
+ const data = await res.json();
886
+ return data.executable?.number ?? null;
887
+ }
888
+ async function getBuildStatus(cfg, jobName, buildNumber) {
889
+ const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`;
890
+ const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
891
+ if (!res.ok) throw new Error(`\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${res.status}`);
892
+ const data = await res.json();
893
+ return {
894
+ building: data.building ?? true,
895
+ result: data.result ?? null,
896
+ duration: data.duration ?? 0
897
+ };
898
+ }
899
+ async function pollBuildComplete(cfg, jobName, buildNumber, timeoutMs = 30 * 60 * 1e3) {
900
+ const start = Date.now();
901
+ const intervalMs = 15e3;
902
+ let dots = 0;
903
+ let consecutiveErrors = 0;
904
+ while (Date.now() - start < timeoutMs) {
905
+ await new Promise((r) => setTimeout(r, intervalMs));
906
+ let status;
907
+ try {
908
+ status = await getBuildStatus(cfg, jobName, buildNumber);
909
+ consecutiveErrors = 0;
910
+ } catch {
911
+ consecutiveErrors++;
912
+ if (consecutiveErrors >= 3) throw new Error("Jenkins \uC5F0\uACB0 \uC624\uB958 3\uD68C \uC5F0\uC18D \u2014 \uB124\uD2B8\uC6CC\uD06C\uB97C \uD655\uC778\uD558\uC138\uC694");
913
+ process.stdout.write(`\r \u26A0 Jenkins \uC5F0\uACB0 \uC624\uB958 (${consecutiveErrors}/3\uD68C), \uC7AC\uC2DC\uB3C4... `);
914
+ continue;
915
+ }
916
+ dots = (dots + 1) % 4;
917
+ process.stdout.write(`\r \u23F3 \uBE4C\uB4DC \uC9C4\uD589 \uC911${".".repeat(dots + 1)} `);
918
+ if (!status.building) {
919
+ process.stdout.write("\n");
920
+ return status.result ?? "FAILURE";
921
+ }
922
+ }
923
+ throw new Error("\uBE4C\uB4DC \uD0C0\uC784\uC544\uC6C3 (30\uBD84)");
924
+ }
925
+ async function streamDeploy(webBase, token, body) {
926
+ const res = await fetch(`${webBase}/api/deploy`, {
927
+ method: "POST",
928
+ headers: {
929
+ "Content-Type": "application/json",
930
+ Authorization: `Bearer ${token}`
931
+ },
932
+ body: JSON.stringify(body)
933
+ });
934
+ if (!res.ok) {
935
+ const text = await res.text().catch(() => "");
936
+ throw new Error(`\uC11C\uBC84 \uBC30\uD3EC \uC2E4\uD328 ${res.status}: ${text.slice(0, 200)}`);
937
+ }
938
+ const reader = res.body?.getReader();
939
+ if (!reader) throw new Error("SSE \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
940
+ const decoder = new TextDecoder();
941
+ let buf = "";
942
+ while (true) {
943
+ const { done, value } = await reader.read();
944
+ if (done) break;
945
+ buf += decoder.decode(value, { stream: true });
946
+ const lines = buf.split("\n");
947
+ buf = lines.pop() ?? "";
948
+ for (const line of lines) {
949
+ if (!line.startsWith("data: ")) continue;
950
+ try {
951
+ const event = JSON.parse(line.slice(6));
952
+ const icon = PHASE_ICON[event.phase] ?? "\u25B8";
953
+ const color = event.status === "done" ? kleur6.green : event.status === "failed" ? kleur6.red : event.status === "skipped" ? kleur6.yellow : kleur6.dim;
954
+ log2(` ${icon} ${color(event.message)}`);
955
+ if (event.phase === "error" || event.phase === "verify" && event.status === "failed" || event.phase === "promote" && event.status === "failed") {
956
+ process.exit(1);
957
+ }
958
+ } catch {
959
+ }
960
+ }
961
+ }
962
+ }
963
+ function parseArgs4(argv) {
964
+ const args = { platform: "android", language: "ko-KR", dryRun: false, skipBuild: false, setupJenkins: false };
965
+ for (let i = 0; i < argv.length; i++) {
966
+ if ((argv[i] === "--platform" || argv[i] === "-p") && argv[i + 1]) args.platform = argv[++i];
967
+ if (argv[i] === "--app" && argv[i + 1]) args.appId = argv[++i];
968
+ if (argv[i] === "--version-code" && argv[i + 1]) args.versionCode = Number(argv[++i]);
969
+ if (argv[i] === "--from" && argv[i + 1]) args.fromRef = argv[++i];
970
+ if (argv[i] === "--to" && argv[i + 1]) args.toRef = argv[++i];
971
+ if (argv[i] === "--language" && argv[i + 1]) args.language = argv[++i];
972
+ if (argv[i] === "--dry-run") args.dryRun = true;
973
+ if (argv[i] === "--skip-build") args.skipBuild = true;
974
+ if (argv[i] === "setup-jenkins") args.setupJenkins = true;
975
+ }
976
+ return args;
977
+ }
978
+ async function promptJenkinsSetup() {
979
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
980
+ const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
981
+ log2(kleur6.bold("Jenkins \uC124\uC815"));
982
+ const url = await ask(" Jenkins URL (\uC608: http://192.168.30.16:8080): ");
983
+ const user = await ask(" Jenkins \uC0AC\uC6A9\uC790\uBA85 (\uC608: admin): ");
984
+ const token = await ask(" Jenkins API Token: ");
985
+ const jobAndroid = await ask(" Android Job \uC774\uB984 (\uC608: my-app-android): ");
986
+ const jobIos = await ask(" iOS Job \uC774\uB984 (\uC120\uD0DD, \uC5D4\uD130 \uC2A4\uD0B5): ");
987
+ rl.close();
988
+ return { url, user, token, jobAndroid: jobAndroid || void 0, jobIos: jobIos || void 0 };
989
+ }
990
+ async function cmdDeploy(argv) {
991
+ const args = parseArgs4(argv);
992
+ const cfg = await getEffectiveConfig();
993
+ if (!cfg) {
994
+ log2(kleur6.red("\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589."));
995
+ process.exit(1);
996
+ }
997
+ if (args.setupJenkins) {
998
+ const jenkins = await promptJenkinsSetup();
999
+ await writeConfig({ ...cfg, jenkins });
1000
+ log2(kleur6.green("\u2705 Jenkins \uC124\uC815 \uC800\uC7A5\uB428"));
1001
+ return;
1002
+ }
1003
+ log2(kleur6.bold(`mimi-seed deploy \u2014 ${args.platform}`));
1004
+ if (args.dryRun) log2(kleur6.yellow(" [dry-run \uBAA8\uB4DC] \uC2E4\uC81C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4"));
1005
+ log2("");
1006
+ let versionCode = args.versionCode;
1007
+ if (!args.skipBuild) {
1008
+ if (!cfg.jenkins?.url || !cfg.jenkins?.token) {
1009
+ log2(kleur6.yellow("Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9."));
1010
+ log2(kleur6.dim(" \uB610\uB294 \uC11C\uBC84 /workspace/integrations\uC5D0\uC11C jenkins \uD504\uB85C\uBC14\uC774\uB354 \uB4F1\uB85D \uD6C4 \uC11C\uBC84\uC0AC\uC774\uB4DC \uD2B8\uB9AC\uAC70 \uAC00\uB2A5."));
1011
+ process.exit(1);
1012
+ }
1013
+ const jenkins = cfg.jenkins;
1014
+ const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
1015
+ if (!jobName) {
1016
+ log2(kleur6.red(`${args.platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`));
1017
+ process.exit(1);
1018
+ }
1019
+ log2(`\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${kleur6.cyan(jobName)}`);
1020
+ const buildParams = {};
1021
+ if (args.appId) buildParams.MIMI_APP_ID = args.appId;
1022
+ const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
1023
+ if (!queueItemId) {
1024
+ log2(kleur6.yellow(" \u26A0 Queue item ID\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uBE4C\uB4DC\uB294 \uC2DC\uC791\uB410\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
1025
+ } else {
1026
+ log2(kleur6.dim(` Queue item: ${queueItemId}`));
1027
+ }
1028
+ let buildNumber = null;
1029
+ if (queueItemId) {
1030
+ log2(" \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...");
1031
+ for (let i = 0; i < 6; i++) {
1032
+ await new Promise((r) => setTimeout(r, 5e3));
1033
+ buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
1034
+ if (buildNumber) break;
1035
+ }
1036
+ }
1037
+ if (!buildNumber) {
1038
+ log2(kleur6.yellow(" \uBE4C\uB4DC \uBC88\uD638\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. --skip-build + --version-code \uB85C \uC7AC\uC2DC\uB3C4 \uAC00\uB2A5."));
1039
+ process.exit(1);
1040
+ }
1041
+ log2(` \uBE4C\uB4DC #${buildNumber} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`);
1042
+ const result = await pollBuildComplete(jenkins, jobName, buildNumber);
1043
+ if (result !== "SUCCESS") {
1044
+ log2(kleur6.red(`\uBE4C\uB4DC \uC2E4\uD328: ${result}`));
1045
+ log2(kleur6.dim(` Jenkins: ${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`));
1046
+ log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${buildNumber} --platform ${args.platform}`));
1047
+ process.exit(1);
1048
+ }
1049
+ log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${buildNumber} \uC131\uACF5`));
1050
+ if (!versionCode) {
1051
+ versionCode = buildNumber;
1052
+ log2(kleur6.dim(` versionCode = buildNumber (${versionCode})`));
1053
+ }
1054
+ }
1055
+ if (!versionCode) {
1056
+ log2(kleur6.red("versionCode\uB97C \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --version-code <N> \uC73C\uB85C \uC9C0\uC815\uD558\uC138\uC694."));
1057
+ process.exit(1);
1058
+ }
1059
+ let appId = args.appId;
1060
+ if (!appId) {
1061
+ const { mcpCall: mcpCall2 } = await import("./mcp-client-U3CS5OZN.js");
1062
+ const r = await mcpCall2(cfg.endpoint, cfg.token, "list_apps", {});
1063
+ if (!r.isError) {
1064
+ try {
1065
+ const apps = JSON.parse(r.text);
1066
+ if (apps.length > 0) {
1067
+ appId = apps[0].id;
1068
+ log2(kleur6.dim(` \uC571: ${apps[0].name} (${appId})`));
1069
+ }
1070
+ } catch {
1071
+ }
1072
+ }
1073
+ }
1074
+ if (!appId) {
1075
+ log2(kleur6.red("appId\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --app <id> \uB85C \uC9C0\uC815\uD558\uAC70\uB098 `mimi-seed init` \uC73C\uB85C \uC571 \uB4F1\uB85D."));
1076
+ process.exit(1);
1077
+ }
1078
+ log2("");
1079
+ log2("\u{1F4E1} \uC11C\uBC84 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uC2DC\uC791...");
1080
+ log2("");
1081
+ const deployBuildNumber = !args.skipBuild ? versionCode : void 0;
1082
+ await streamDeploy(cfg.webBase, cfg.token, {
1083
+ appId,
1084
+ platform: args.platform,
1085
+ versionCode,
1086
+ ...deployBuildNumber ? { buildNumber: deployBuildNumber } : {},
1087
+ fromRef: args.fromRef,
1088
+ toRef: args.toRef,
1089
+ language: args.language,
1090
+ dryRun: args.dryRun
1091
+ });
1092
+ log2("");
1093
+ log2(kleur6.bold("\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."));
1094
+ log2(kleur6.dim(" https://play.google.com/console/developers"));
1095
+ }
1096
+
810
1097
  // src/index.ts
811
1098
  var DEFAULT_WEB_BASE = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
812
1099
  var DEFAULT_MCP_ENDPOINT = `${DEFAULT_WEB_BASE}/api/mcp`;
813
- function log(msg) {
1100
+ function log3(msg) {
814
1101
  process.stdout.write(msg + "\n");
815
1102
  }
816
1103
  async function cmdInit() {
817
1104
  const cwd = process.cwd();
818
- log(kleur5.bold("Mimi Seed CLI \u2014 init"));
819
- log(kleur5.dim(cwd));
820
- log("");
1105
+ log3(kleur7.bold("Mimi Seed CLI \u2014 init"));
1106
+ log3(kleur7.dim(cwd));
1107
+ log3("");
821
1108
  if (!await hasAnyProjectSignal(cwd)) {
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."));
1109
+ log3(kleur7.yellow("\u26A0 package.json / app.json / android / ios \uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uADF8\uB798\uB3C4 \uC9C4\uD589\uD569\uB2C8\uB2E4."));
823
1110
  }
824
- log("\u{1F50D} \uC571 \uAC10\uC9C0 \uC911...");
1111
+ log3("\u{1F50D} \uC571 \uAC10\uC9C0 \uC911...");
825
1112
  const hints = await detectHints(cwd);
826
1113
  if (hints.length === 0) {
827
- log(kleur5.yellow("\uAC10\uC9C0\uB41C \uC571 \uC5C6\uC74C. \uC6F9\uC5D0\uC11C \uC218\uB3D9 \uB4F1\uB85D \uAC00\uB2A5."));
1114
+ log3(kleur7.yellow("\uAC10\uC9C0\uB41C \uC571 \uC5C6\uC74C. \uC6F9\uC5D0\uC11C \uC218\uB3D9 \uB4F1\uB85D \uAC00\uB2A5."));
828
1115
  } else {
829
1116
  for (const h of hints) {
830
1117
  const tag = [h.packageName && `android:${h.packageName}`, h.bundleId && `ios:${h.bundleId}`].filter(Boolean).join(" ");
831
- log(` \u2022 ${h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)"} ${kleur5.dim(tag)}`);
1118
+ log3(` \u2022 ${h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)"} ${kleur7.dim(tag)}`);
832
1119
  }
833
1120
  }
834
- log("");
1121
+ log3("");
835
1122
  if (process.env.MIMI_SEED_TOKEN) {
836
1123
  const cfg2 = await getEffectiveConfig();
837
1124
  if (cfg2 && hints.length > 0) {
838
- log(kleur5.dim("CI \uBAA8\uB4DC: MIMI_SEED_TOKEN \uC0AC\uC6A9"));
1125
+ log3(kleur7.dim("CI \uBAA8\uB4DC: MIMI_SEED_TOKEN \uC0AC\uC6A9"));
839
1126
  const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
840
1127
  const result = await mcpCall(cfg2.endpoint, cfg2.token, "sync_apps", { hints: payload });
841
1128
  if (result.isError) {
842
- log(kleur5.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
1129
+ log3(kleur7.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
843
1130
  } else {
844
- for (const line of result.text.split("\n")) log(" " + line);
1131
+ for (const line of result.text.split("\n")) log3(" " + line);
845
1132
  }
846
1133
  }
847
- log(kleur5.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
1134
+ log3(kleur7.bold("\u2713 \uC644\uB8CC (CI \uBAA8\uB4DC)"));
848
1135
  return;
849
1136
  }
850
- log("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
1137
+ log3("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
851
1138
  const hostName = os2.hostname().slice(0, 32);
852
1139
  const name = `cli-${hostName}`;
853
1140
  const { port, promise } = await awaitHandshake(5 * 60 * 1e3);
854
1141
  const callback = `http://127.0.0.1:${port}/cb`;
855
1142
  const connectUrl = `${DEFAULT_WEB_BASE}/cli/connect?callback=${encodeURIComponent(callback)}&name=${encodeURIComponent(name)}`;
856
- log(kleur5.dim(` ${connectUrl}`));
1143
+ log3(kleur7.dim(` ${connectUrl}`));
857
1144
  await open(connectUrl);
858
1145
  let handshake;
859
1146
  try {
860
1147
  handshake = await promise;
861
1148
  } catch (e) {
862
- log(kleur5.red("\uC5F0\uACB0 \uC2E4\uD328: " + e.message));
1149
+ log3(kleur7.red("\uC5F0\uACB0 \uC2E4\uD328: " + e.message));
863
1150
  process.exit(1);
864
1151
  }
865
- log(kleur5.green("\u2713 \uD1A0\uD070 \uC218\uC2E0"));
1152
+ log3(kleur7.green("\u2713 \uD1A0\uD070 \uC218\uC2E0"));
866
1153
  const cfg = {
867
1154
  token: handshake.token,
868
1155
  prefix: handshake.prefix,
@@ -871,30 +1158,30 @@ async function cmdInit() {
871
1158
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
872
1159
  };
873
1160
  await writeConfig(cfg);
874
- log(kleur5.dim(` \uC800\uC7A5\uB428: ${CONFIG_LOCATION}`));
875
- log("");
1161
+ log3(kleur7.dim(` \uC800\uC7A5\uB428: ${CONFIG_LOCATION}`));
1162
+ log3("");
876
1163
  if (hints.length > 0) {
877
- log("\u{1F504} \uC571 \uB4F1\uB85D \uC911...");
1164
+ log3("\u{1F504} \uC571 \uB4F1\uB85D \uC911...");
878
1165
  const payload = hints.map((h) => ({ name: h.name, packageName: h.packageName, bundleId: h.bundleId }));
879
1166
  const result = await mcpCall(cfg.endpoint, cfg.token, "sync_apps", { hints: payload });
880
1167
  if (result.isError) {
881
- log(kleur5.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
1168
+ log3(kleur7.red("\uB4F1\uB85D \uC2E4\uD328: " + result.text));
882
1169
  } else {
883
- for (const line of result.text.split("\n")) log(" " + line);
884
- }
885
- log("");
886
- }
887
- log(kleur5.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
888
- log("");
889
- log("Claude Code\uC5D0\uC11C \uC774\uB807\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694:");
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"'));
893
- log("");
894
- log(`\uB300\uC2DC\uBCF4\uB4DC: ${kleur5.underline(DEFAULT_WEB_BASE + "/apps")}`);
895
- log("");
896
- log(
897
- kleur5.dim(
1170
+ for (const line of result.text.split("\n")) log3(" " + line);
1171
+ }
1172
+ log3("");
1173
+ }
1174
+ log3(kleur7.bold("\u2713 \uC900\uBE44 \uC644\uB8CC."));
1175
+ log3("");
1176
+ log3("Claude Code\uC5D0\uC11C \uC774\uB807\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694:");
1177
+ log3(kleur7.cyan(' "\uB0B4 \uC571 \uCD9C\uC2DC \uC900\uBE44\uB410\uC5B4?"'));
1178
+ log3(kleur7.cyan(' "\uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC368\uC918"'));
1179
+ log3(kleur7.cyan(' "\uB4F1\uB85D\uB41C \uC571 \uBAA9\uB85D \uBCF4\uC5EC\uC918"'));
1180
+ log3("");
1181
+ log3(`\uB300\uC2DC\uBCF4\uB4DC: ${kleur7.underline(DEFAULT_WEB_BASE + "/apps")}`);
1182
+ log3("");
1183
+ log3(
1184
+ kleur7.dim(
898
1185
  `Claude Code MCP \uB4F1\uB85D:
899
1186
  claude mcp add --transport http mimi-seed ${DEFAULT_MCP_ENDPOINT} \\
900
1187
  --header "Authorization: Bearer ${cfg.prefix}..."`
@@ -904,39 +1191,41 @@ async function cmdInit() {
904
1191
  async function cmdStatus() {
905
1192
  const cfg = await getEffectiveConfig();
906
1193
  if (!cfg) {
907
- log(kleur5.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
1194
+ log3(kleur7.yellow("\uC5F0\uACB0\uB41C Mimi Seed \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `mimi-seed init` \uC2E4\uD589."));
908
1195
  process.exit(1);
909
1196
  }
910
- log(kleur5.bold("Mimi Seed \uC5F0\uACB0 \uC0C1\uD0DC"));
911
- log(` \uD1A0\uD070: ${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
912
- log(` \uC5D4\uB4DC\uD3EC\uC778\uD2B8: ${cfg.endpoint}`);
913
- log("");
914
- log("\u{1F4CB} \uC571 \uBAA9\uB85D:");
1197
+ log3(kleur7.bold("Mimi Seed \uC5F0\uACB0 \uC0C1\uD0DC"));
1198
+ log3(` \uD1A0\uD070: ${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
1199
+ log3(` \uC5D4\uB4DC\uD3EC\uC778\uD2B8: ${cfg.endpoint}`);
1200
+ log3("");
1201
+ log3("\u{1F4CB} \uC571 \uBAA9\uB85D:");
915
1202
  const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
916
1203
  if (r.isError) {
917
- log(kleur5.red("\uC870\uD68C \uC2E4\uD328: " + r.text));
1204
+ log3(kleur7.red("\uC870\uD68C \uC2E4\uD328: " + r.text));
918
1205
  process.exit(1);
919
1206
  }
920
- for (const line of r.text.split("\n")) log(" " + line);
1207
+ for (const line of r.text.split("\n")) log3(" " + line);
921
1208
  }
922
1209
  async function cmdLogout() {
923
1210
  await deleteConfig();
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"));
1211
+ log3(kleur7.green("\u2713 \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C \uC644\uB8CC."));
1212
+ log3(kleur7.dim("\uC6F9\uC5D0\uC11C \uD1A0\uD070 \uD574\uC9C0: /workspace/api-tokens"));
926
1213
  }
927
1214
  function printHelp() {
928
- log(`${kleur5.bold("mimi-seed")} \u2014 Claude Code\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
929
-
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
938
-
939
- ${kleur5.bold("mimi-seed notes \uC635\uC158:")}
1215
+ log3(`${kleur7.bold("mimi-seed")} \u2014 Claude Code\uC5D0\uC11C \uC571 \uCD9C\uC2DC \uC6B4\uC601
1216
+
1217
+ ${kleur7.bold("\uBA85\uB839\uC5B4:")}
1218
+ ${kleur7.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
1219
+ ${kleur7.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
1220
+ ${kleur7.cyan("mimi-seed auth")} Google OAuth \uC778\uC99D (Firebase/AdMob/Play). 'mimi-seed auth --help'
1221
+ ${kleur7.cyan("mimi-seed doctor")} \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI \uCCB4\uD06C)
1222
+ ${kleur7.cyan("mimi-seed check")} \uCD9C\uC2DC \uC804 Readiness \uC810\uAC80
1223
+ ${kleur7.cyan("mimi-seed notes")} \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 (git log \u2192 AI \u2192 \uB9C8\uCF13 \uC801\uC6A9)
1224
+ ${kleur7.cyan("mimi-seed review")} \uB9AC\uBDF0 \uB2F5\uBCC0 AI \uCD08\uC548 \uC0DD\uC131 \uBC0F Play Store \uAC8C\uC2DC
1225
+ ${kleur7.cyan("mimi-seed deploy")} \uC571 \uC790\uB3D9 \uBC30\uD3EC (Jenkins \u2192 Play Store/App Store)
1226
+ ${kleur7.cyan("mimi-seed logout")} \uB85C\uCEEC \uC124\uC815 \uC0AD\uC81C
1227
+
1228
+ ${kleur7.bold("mimi-seed notes \uC635\uC158:")}
940
1229
  --from <ref> \uC2DC\uC791 \uCEE4\uBC0B/\uD0DC\uADF8 (\uAE30\uBCF8: \uCD5C\uC2E0 \uD0DC\uADF8)
941
1230
  --to <ref> \uB05D \uCEE4\uBC0B (\uAE30\uBCF8: HEAD)
942
1231
  --locale ko,en-US \uB300\uC0C1 \uB85C\uCF00\uC77C (\uC27C\uD45C \uAD6C\uBD84)
@@ -944,11 +1233,11 @@ ${kleur5.bold("mimi-seed notes \uC635\uC158:")}
944
1233
  --no-interactive CI \uBAA8\uB4DC (\uD504\uB86C\uD504\uD2B8 \uC5C6\uC74C)
945
1234
  --limit <n> \uCD5C\uB300 \uCEE4\uBC0B \uC218 (\uAE30\uBCF8: 30)
946
1235
 
947
- ${kleur5.bold("mimi-seed check \uC635\uC158:")}
1236
+ ${kleur7.bold("mimi-seed check \uC635\uC158:")}
948
1237
  --app <id> \uC571 ID \uC9C0\uC815
949
1238
  --fail-on-blocker \uBE14\uB85C\uCEE4 \uC788\uC73C\uBA74 exit 1 (CI\uC6A9)
950
1239
 
951
- ${kleur5.bold("mimi-seed review \uC635\uC158:")}
1240
+ ${kleur7.bold("mimi-seed review \uC635\uC158:")}
952
1241
  --text <\uB0B4\uC6A9> \uB9AC\uBDF0 \uC6D0\uBB38 (\uBBF8\uC785\uB825 \uC2DC \uB300\uD654\uD615 \uD504\uB86C\uD504\uD2B8)
953
1242
  --rating <1-5> \uBCC4\uC810
954
1243
  --tone <tone> friendly / professional / empathetic / brief (\uAE30\uBCF8: friendly)
@@ -959,7 +1248,18 @@ ${kleur5.bold("mimi-seed review \uC635\uC158:")}
959
1248
  --package-name <p> \uD328\uD0A4\uC9C0\uBA85 (--apply \uC2DC \uD544\uC694)
960
1249
  --no-interactive CI \uBAA8\uB4DC
961
1250
 
962
- ${kleur5.bold("\uD658\uACBD\uBCC0\uC218:")}
1251
+ ${kleur7.bold("mimi-seed deploy \uC635\uC158:")}
1252
+ --platform android|ios \uBC30\uD3EC \uD50C\uB7AB\uD3FC (\uAE30\uBCF8: android)
1253
+ --app <id> \uC571 ID \uC9C0\uC815
1254
+ --version-code <n> \uBE4C\uB4DC \uBC88\uD638 \uC9C1\uC811 \uC9C0\uC815 (--skip-build \uC640 \uD568\uAED8)
1255
+ --from <ref> \uCEE4\uBC0B \uBC94\uC704 \uC2DC\uC791 (\uB9B4\uB9AC\uC988 \uB178\uD2B8\uC6A9)
1256
+ --to <ref> \uCEE4\uBC0B \uBC94\uC704 \uB05D (\uAE30\uBCF8: HEAD)
1257
+ --language <\uCF54\uB4DC> \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC5B8\uC5B4 (\uAE30\uBCF8: ko-KR)
1258
+ --dry-run \uC2E4\uC81C \uBC30\uD3EC \uC5C6\uC774 \uD30C\uC774\uD504\uB77C\uC778 \uD14C\uC2A4\uD2B8
1259
+ --skip-build Jenkins \uBE4C\uB4DC \uAC74\uB108\uB700 (--version-code \uD544\uC218)
1260
+ setup-jenkins Jenkins \uC124\uC815 \uB300\uD654\uD615 \uB4F1\uB85D
1261
+
1262
+ ${kleur7.bold("\uD658\uACBD\uBCC0\uC218:")}
963
1263
  MIMI_SEED_TOKEN PAT \uD1A0\uD070 (CI/CD \uBB34\uC778\uC99D \uBAA8\uB4DC)
964
1264
  MIMI_SEED_WEB_BASE \uC11C\uBC84 \uC8FC\uC18C (\uAE30\uBCF8: https://mimi-seed.pryzm.gg)
965
1265
  ANTHROPIC_API_KEY AI \uB178\uD2B8 \uC0DD\uC131 \uD65C\uC131\uD654 (\uC120\uD0DD)
@@ -988,6 +1288,12 @@ async function main() {
988
1288
  case "review":
989
1289
  await cmdReview(restArgs);
990
1290
  break;
1291
+ case "auth":
1292
+ await cmdAuth(restArgs);
1293
+ break;
1294
+ case "deploy":
1295
+ await cmdDeploy(restArgs);
1296
+ break;
991
1297
  case "logout":
992
1298
  await cmdLogout();
993
1299
  break;
@@ -997,13 +1303,13 @@ async function main() {
997
1303
  printHelp();
998
1304
  break;
999
1305
  default:
1000
- log(kleur5.red(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`));
1306
+ log3(kleur7.red(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}`));
1001
1307
  printHelp();
1002
1308
  process.exit(1);
1003
1309
  }
1004
1310
  } catch (e) {
1005
- log(kleur5.red(`\uC624\uB958: ${e.message}`));
1006
- if (process.env.DEBUG) log(e.stack ?? "");
1311
+ log3(kleur7.red(`\uC624\uB958: ${e.message}`));
1312
+ if (process.env.DEBUG) log3(e.stack ?? "");
1007
1313
  process.exit(1);
1008
1314
  }
1009
1315
  }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ mcpCall
4
+ } from "./chunk-FW4DVFZD.js";
5
+ export {
6
+ mcpCall
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mimi-seed",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Mimi Seed CLI — Claude Code에서 앱 출시 운영을 관리합니다.",
5
5
  "bin": {
6
6
  "mimi-seed": "dist/index.js"