mimi-seed 0.4.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,169 +2,37 @@
2
2
  import {
3
3
  mcpCall
4
4
  } from "./chunk-DW3LOLPJ.js";
5
+ import {
6
+ CONFIG_LOCATION,
7
+ CREDENTIALS,
8
+ MCP_PKG,
9
+ cmdDeploy,
10
+ cmdSetup,
11
+ credLabel,
12
+ credNote,
13
+ deleteConfig,
14
+ detectAll,
15
+ detectHints,
16
+ getEffectiveConfig,
17
+ hasAnyProjectSignal,
18
+ isLang,
19
+ isSatisfied,
20
+ migrateLegacyJenkins,
21
+ resolveLang,
22
+ runMcpBin,
23
+ t,
24
+ tryCredById,
25
+ writeConfig,
26
+ writeSettings
27
+ } from "./chunk-R3LS4FY2.js";
5
28
 
6
29
  // src/index.ts
7
- import os7 from "os";
8
- import fs10 from "fs";
9
- import path10 from "path";
30
+ import os3 from "os";
31
+ import fs5 from "fs";
32
+ import path5 from "path";
10
33
  import kleur9 from "kleur";
11
34
  import open from "open";
12
35
 
13
- // src/detect.ts
14
- import fs from "fs/promises";
15
- import path from "path";
16
- async function readIfExists(p) {
17
- try {
18
- return await fs.readFile(p, "utf8");
19
- } catch {
20
- return null;
21
- }
22
- }
23
- async function pathExists(p) {
24
- try {
25
- await fs.access(p);
26
- return true;
27
- } catch {
28
- return false;
29
- }
30
- }
31
- async function walk(root, match, maxDepth = 5) {
32
- const found = [];
33
- const skipDirs = /* @__PURE__ */ new Set([
34
- "node_modules",
35
- ".git",
36
- "build",
37
- "dist",
38
- ".next",
39
- ".expo",
40
- "Pods",
41
- "DerivedData"
42
- ]);
43
- async function visit(dir, depth) {
44
- if (depth > maxDepth) return;
45
- let entries;
46
- try {
47
- entries = await fs.readdir(dir, { withFileTypes: true });
48
- } catch {
49
- return;
50
- }
51
- for (const e of entries) {
52
- if (e.isDirectory()) {
53
- if (skipDirs.has(e.name)) continue;
54
- await visit(path.join(dir, e.name), depth + 1);
55
- } else if (e.isFile() && match(e.name)) {
56
- found.push(path.join(dir, e.name));
57
- }
58
- }
59
- }
60
- await visit(root, 0);
61
- return found;
62
- }
63
- async function detectHints(cwd) {
64
- const hints = [];
65
- for (const fname of ["app.json", "app.config.json"]) {
66
- const txt = await readIfExists(path.join(cwd, fname));
67
- if (!txt) continue;
68
- try {
69
- const json = JSON.parse(txt);
70
- const expo = json.expo ?? json;
71
- const pkg = expo?.android?.package;
72
- const bid = expo?.ios?.bundleIdentifier;
73
- const name = expo?.name;
74
- if (pkg || bid) {
75
- hints.push({
76
- name,
77
- packageName: typeof pkg === "string" ? pkg : void 0,
78
- bundleId: typeof bid === "string" ? bid : void 0,
79
- source: [fname]
80
- });
81
- }
82
- } catch {
83
- }
84
- }
85
- const gradleFiles = await walk(
86
- cwd,
87
- (n) => n === "build.gradle" || n === "build.gradle.kts",
88
- 4
89
- );
90
- for (const f of gradleFiles) {
91
- const txt = await readIfExists(f);
92
- if (!txt) continue;
93
- const m = txt.match(/applicationId[\s=]+["']([^"']+)["']/);
94
- if (m?.[1]) {
95
- const pkg = m[1];
96
- if (!hints.some((h) => h.packageName === pkg)) {
97
- hints.push({ packageName: pkg, source: [path.relative(cwd, f)] });
98
- }
99
- }
100
- }
101
- const plistFiles = await walk(cwd, (n) => n === "Info.plist", 5);
102
- for (const f of plistFiles) {
103
- const txt = await readIfExists(f);
104
- if (!txt) continue;
105
- const m = txt.match(
106
- /<key>CFBundleIdentifier<\/key>\s*<string>([^<]+)<\/string>/
107
- );
108
- if (m?.[1]) {
109
- let bid = m[1];
110
- if (bid.includes("$(PRODUCT_BUNDLE_IDENTIFIER)")) continue;
111
- if (!hints.some((h) => h.bundleId === bid)) {
112
- hints.push({ bundleId: bid, source: [path.relative(cwd, f)] });
113
- }
114
- }
115
- }
116
- const pbxFiles = await walk(cwd, (n) => n === "project.pbxproj", 5);
117
- for (const f of pbxFiles) {
118
- const txt = await readIfExists(f);
119
- if (!txt) continue;
120
- const matches = [...txt.matchAll(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/g)];
121
- for (const m of matches) {
122
- const bid = m[1].trim().replace(/^["']|["']$/g, "");
123
- if (!bid || bid.includes("$")) continue;
124
- if (!hints.some((h) => h.bundleId === bid)) {
125
- hints.push({ bundleId: bid, source: [path.relative(cwd, f)] });
126
- }
127
- }
128
- }
129
- const pkgJson = await readIfExists(path.join(cwd, "package.json"));
130
- if (pkgJson) {
131
- try {
132
- const json = JSON.parse(pkgJson);
133
- if (typeof json.name === "string") {
134
- for (const h of hints) if (!h.name) h.name = json.name;
135
- }
136
- } catch {
137
- }
138
- }
139
- const merged = [];
140
- const androidOnly = hints.filter((h) => h.packageName && !h.bundleId);
141
- const iosOnly = hints.filter((h) => h.bundleId && !h.packageName);
142
- const both = hints.filter((h) => h.packageName && h.bundleId);
143
- merged.push(...both);
144
- for (const a of androidOnly) {
145
- const match = iosOnly.find((i) => i.bundleId === a.packageName);
146
- if (match) {
147
- merged.push({
148
- name: a.name ?? match.name,
149
- packageName: a.packageName,
150
- bundleId: match.bundleId,
151
- source: [...a.source, ...match.source]
152
- });
153
- } else {
154
- merged.push(a);
155
- }
156
- }
157
- for (const i of iosOnly) {
158
- if (!androidOnly.some((a) => a.packageName === i.bundleId)) {
159
- merged.push(i);
160
- }
161
- }
162
- return merged.filter((h) => h.packageName || h.bundleId);
163
- }
164
- async function hasAnyProjectSignal(cwd) {
165
- return await pathExists(path.join(cwd, "package.json")) || await pathExists(path.join(cwd, "app.json")) || await pathExists(path.join(cwd, "android")) || await pathExists(path.join(cwd, "ios"));
166
- }
167
-
168
36
  // src/handshake.ts
169
37
  import http from "http";
170
38
  async function awaitHandshake(timeoutMs) {
@@ -214,50 +82,7 @@ async function awaitHandshake(timeoutMs) {
214
82
  return { port, promise };
215
83
  }
216
84
 
217
- // src/config.ts
218
- import fs2 from "fs/promises";
219
- import path2 from "path";
220
- import os from "os";
221
- var CONFIG_DIR = path2.join(os.homedir(), ".mimi-seed");
222
- var CONFIG_PATH = path2.join(CONFIG_DIR, "config.json");
223
- async function readConfig() {
224
- try {
225
- const txt = await fs2.readFile(CONFIG_PATH, "utf8");
226
- return JSON.parse(txt);
227
- } catch {
228
- return null;
229
- }
230
- }
231
- async function writeConfig(cfg) {
232
- await fs2.mkdir(CONFIG_DIR, { recursive: true });
233
- await fs2.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2));
234
- if (process.platform !== "win32") {
235
- await fs2.chmod(CONFIG_PATH, 384);
236
- }
237
- }
238
- async function deleteConfig() {
239
- await fs2.rm(CONFIG_PATH, { force: true });
240
- }
241
- var CONFIG_LOCATION = CONFIG_PATH;
242
- async function getEffectiveConfig() {
243
- const envToken = process.env.MIMI_SEED_TOKEN;
244
- if (envToken) {
245
- const webBase = process.env.MIMI_SEED_WEB_BASE ?? "https://mimi-seed.pryzm.gg";
246
- return {
247
- token: envToken,
248
- prefix: envToken.slice(0, 8),
249
- endpoint: `${webBase}/api/mcp`,
250
- webBase,
251
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
252
- };
253
- }
254
- return readConfig();
255
- }
256
-
257
85
  // src/doctor.ts
258
- import fs4 from "fs";
259
- import os2 from "os";
260
- import path4 from "path";
261
86
  import kleur from "kleur";
262
87
 
263
88
  // src/git.ts
@@ -301,22 +126,22 @@ function formatCommitsForPrompt(commits) {
301
126
  }
302
127
 
303
128
  // src/project-manifest.ts
304
- import fs3 from "fs";
305
- import path3 from "path";
129
+ import fs from "fs";
130
+ import path from "path";
306
131
  var MANIFEST_FILENAME = ".mimi-seed.json";
307
132
  function findProjectManifest(startDir = process.cwd(), maxDepth = 8) {
308
- let dir = path3.resolve(startDir);
133
+ let dir = path.resolve(startDir);
309
134
  for (let i = 0; i <= maxDepth; i++) {
310
- const candidate = path3.join(dir, MANIFEST_FILENAME);
311
- if (fs3.existsSync(candidate)) {
135
+ const candidate = path.join(dir, MANIFEST_FILENAME);
136
+ if (fs.existsSync(candidate)) {
312
137
  try {
313
- const obj = JSON.parse(fs3.readFileSync(candidate, "utf-8"));
138
+ const obj = JSON.parse(fs.readFileSync(candidate, "utf-8"));
314
139
  if (obj && typeof obj === "object") return { manifest: obj, filePath: candidate };
315
140
  } catch {
316
141
  }
317
142
  return null;
318
143
  }
319
- const parent = path3.dirname(dir);
144
+ const parent = path.dirname(dir);
320
145
  if (parent === dir) break;
321
146
  dir = parent;
322
147
  }
@@ -360,114 +185,81 @@ function manifestDetail(id, svc) {
360
185
  }
361
186
  async function cmdDoctor() {
362
187
  const cwd = process.cwd();
363
- process.stdout.write(kleur.bold("mimi-seed doctor\n\n"));
364
- section("\uC778\uC99D");
188
+ const m = t().doctor;
189
+ process.stdout.write(kleur.bold(m.title + "\n\n"));
190
+ section(m.secAuth);
365
191
  const cfg = await getEffectiveConfig();
366
192
  if (!cfg) {
367
- fail("Mimi Seed \uD1A0\uD070 \uC5C6\uC74C", "`mimi-seed init` \uC2E4\uD589 \uD544\uC694");
193
+ fail(m.noToken, m.noTokenFix);
368
194
  } else {
369
- ok("\uD1A0\uD070 \uC800\uC7A5\uB428", `${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
370
- ok("\uC5D4\uB4DC\uD3EC\uC778\uD2B8", cfg.endpoint);
195
+ ok(m.tokenSaved, `${cfg.prefix}\u2026 (${cfg.createdAt.slice(0, 10)})`);
196
+ ok(m.endpoint, cfg.endpoint);
371
197
  if (process.env.MIMI_SEED_TOKEN) {
372
- ok("CI \uBAA8\uB4DC", "MIMI_SEED_TOKEN \uD658\uACBD\uBCC0\uC218 \uC0AC\uC6A9 \uC911");
198
+ ok(m.ciMode, m.ciModeDetail);
373
199
  }
374
200
  const r = await mcpCall(cfg.endpoint, cfg.token, "list_apps", {});
375
201
  if (r.isError) {
376
- fail("\uD1A0\uD070 \uAC80\uC99D \uC2E4\uD328", r.text.slice(0, 80));
202
+ fail(m.tokenInvalid, r.text.slice(0, 80));
377
203
  } else {
378
204
  const lines = r.text.split("\n").filter(Boolean);
379
- ok("Mimi Seed \uC11C\uBC84 \uC5F0\uACB0\uB428", `\uC571 ${lines.length}\uAC1C`);
205
+ ok(m.serverOk, m.appCount(lines.length));
380
206
  }
381
207
  }
382
- section("\uB85C\uCEEC MCP \uC790\uACA9\uC99D\uBA85 (~/.mimi-seed)");
383
- const credDir = path4.join(os2.homedir(), ".mimi-seed");
384
- const hasFile = (name) => fs4.existsSync(path4.join(credDir, name));
385
- const hasPrefix = (prefix) => {
386
- try {
387
- return fs4.readdirSync(credDir).some((f) => f.startsWith(prefix));
388
- } catch {
389
- return false;
390
- }
391
- };
392
- const hasPlaySa = hasFile("play-service-account.json") || (() => {
393
- try {
394
- return fs4.readdirSync(path4.join(credDir, "play-service-accounts")).some((f) => f.endsWith(".json"));
395
- } catch {
396
- return false;
397
- }
398
- })();
399
- const oauthPresent = hasFile("tokens.json");
400
- const appstorePresent = hasFile("appstore.json");
401
- const bigquerySaPresent = hasPrefix("bigquery");
402
- const creds = [
403
- ["Google OAuth (Firebase/AdMob/Play/Ads)", oauthPresent, "mimi-seed auth login"],
404
- ["App Store Connect", appstorePresent, "mimi-seed auth appstore"],
405
- ["Play \uC11C\uBE44\uC2A4 \uACC4\uC815 (\uC120\uD0DD \u2014 OAuth\uB85C\uB3C4 \uAC00\uB2A5)", hasPlaySa, "mimi-seed auth playstore"],
406
- ["BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815", bigquerySaPresent, "mimi-seed auth bigquery"]
407
- ];
408
- for (const [label, present, cmd] of creds) {
409
- if (present) ok(label);
410
- else warn(label, `\u2192 ${cmd}`);
411
- }
412
- process.stdout.write(kleur.dim(" OAuth \uD1A0\uD070 \uC2E0\uC120\uB3C4 \uD655\uC778: mimi-seed auth status\n"));
208
+ section(m.secCreds);
209
+ migrateLegacyJenkins();
210
+ const detected = detectAll();
211
+ for (const spec of CREDENTIALS) {
212
+ const d = detected.get(spec.id);
213
+ const base = credLabel(spec);
214
+ const note = credNote(spec);
215
+ const label = note ? `${base} (${note})` : base;
216
+ if (d.present) ok(base, d.detail);
217
+ else if (isSatisfied(spec, detected)) warn(label, t().setup.fallbackWorking);
218
+ else if (spec.requirement === "optional") warn(`${label}`, `\u2192 ${spec.fix}`);
219
+ else fail(base, `\u2192 ${spec.fix}`);
220
+ }
221
+ process.stdout.write(kleur.dim(m.credsHint));
413
222
  const loaded = findProjectManifest(cwd);
414
223
  if (loaded) {
415
- const projName = loaded.manifest.displayName ?? loaded.manifest.project ?? "\uC774 \uD504\uB85C\uC81D\uD2B8";
416
- section(`${projName} \uC694\uAD6C\uC0AC\uD56D (.mimi-seed.json)`);
417
- const connectedOf = {
418
- oauth: oauthPresent,
419
- bigquery: bigquerySaPresent || oauthPresent,
420
- playstore: hasPlaySa || oauthPresent,
421
- appstore: appstorePresent,
422
- jenkins: false
423
- // mimi-seed 로컬 자격증명 대상 아님(별도 MCP) — note 로만 안내
424
- };
425
- const fixOf = {
426
- oauth: "mimi-seed auth login",
427
- bigquery: "mimi-seed auth bigquery",
428
- playstore: "mimi-seed auth playstore",
429
- appstore: "mimi-seed auth appstore",
430
- jenkins: "claude mcp add <your-jenkins-mcp> -s user"
431
- };
224
+ const projName = loaded.manifest.displayName ?? loaded.manifest.project ?? m.thisProject;
225
+ section(m.requirements(projName));
432
226
  for (const [id, svc] of manifestServiceEntries(loaded.manifest)) {
433
227
  const required = svc.required !== false;
434
- const connected = connectedOf[id];
435
228
  const detail = manifestDetail(id, svc);
229
+ const spec = tryCredById(id);
230
+ if (!spec) {
231
+ warn(m.unknownService(id), svc.note ?? detail);
232
+ continue;
233
+ }
234
+ const connected = isSatisfied(spec, detected);
436
235
  if (connected) ok(id, detail);
437
- else if (!required) warn(`${id} (\uC120\uD0DD)`, svc.note ?? detail);
438
- else fail(id, `\u2192 ${fixOf[id]}${detail ? " " + detail : ""}`);
236
+ else if (!required) warn(`${id} (${t().common.optional})`, svc.note ?? detail);
237
+ else fail(id, `\u2192 ${spec.fix}${detail ? " " + detail : ""}`);
439
238
  }
440
239
  }
441
- section("\uB85C\uCEEC \uD658\uACBD");
240
+ section(m.secEnv);
442
241
  const nodeVer = process.version;
443
242
  const [, major] = nodeVer.match(/v(\d+)/) ?? [];
444
243
  if (Number(major) >= 20) {
445
244
  ok("Node.js", nodeVer);
446
- } else if (Number(major) >= 18) {
447
- warn("Node.js", `${nodeVer} \u2014 CLI \uB294 \uB3D9\uC791\uD558\uC9C0\uB9CC Local MCP \uC11C\uBC84(@yoonion/mimi-seed-mcp)\uB294 v20 \uC774\uC0C1 \uD544\uC694`);
448
245
  } else {
449
- fail("Node.js", `${nodeVer} \u2014 v18 \uC774\uC0C1(CLI) / v20 \uC774\uC0C1(Local MCP) \uD544\uC694`);
246
+ fail("Node.js", m.nodeTooOld(nodeVer));
450
247
  }
451
248
  if (isGitRepo(cwd)) {
452
249
  const latestTag = getLatestTag(cwd);
453
250
  const commits = getGitLog(cwd, { limit: 5 });
454
- ok("Git \uC800\uC7A5\uC18C", latestTag ? `\uCD5C\uC2E0 \uD0DC\uADF8: ${latestTag}` : `\uCEE4\uBC0B ${commits.length}\uAC1C`);
455
- } else {
456
- warn("Git \uC800\uC7A5\uC18C \uC5C6\uC74C", "mimi-seed notes \uC0AC\uC6A9 \uBD88\uAC00");
457
- }
458
- if (process.env.ANTHROPIC_API_KEY) {
459
- ok("ANTHROPIC_API_KEY", "AI \uB9B4\uB9AC\uC988 \uB178\uD2B8 \uC0DD\uC131 \uC0AC\uC6A9 \uAC00\uB2A5");
251
+ ok(m.gitRepo, latestTag ? m.gitTag(latestTag) : m.gitCommits(commits.length));
460
252
  } else {
461
- warn("ANTHROPIC_API_KEY \uC5C6\uC74C", "\uC124\uC815 \uC2DC AI \uB9B4\uB9AC\uC988 \uB178\uD2B8/\uB9AC\uBDF0 \uB2F5\uBCC0 \uC0DD\uC131 \uAC00\uB2A5");
253
+ warn(m.noGit, m.noGitDetail);
462
254
  }
463
- section("\uC571 \uAC10\uC9C0");
255
+ section(m.secApps);
464
256
  const hints = await detectHints(cwd);
465
257
  if (hints.length === 0) {
466
- warn("\uC571 \uAC10\uC9C0 \uC5C6\uC74C", "app.json / build.gradle / Info.plist \uC5C6\uC74C");
258
+ warn(m.noApp, m.noAppDetail);
467
259
  } else {
468
260
  for (const h of hints) {
469
261
  const ids = [h.packageName && `android:${h.packageName}`, h.bundleId && `ios:${h.bundleId}`].filter(Boolean).join(" ");
470
- ok(h.name ?? "(\uC774\uB984 \uBBF8\uC0C1)", ids);
262
+ ok(h.name ?? m.unnamed, ids);
471
263
  }
472
264
  }
473
265
  process.stdout.write("\n");
@@ -910,13 +702,7 @@ async function cmdReview(argv) {
910
702
  }
911
703
 
912
704
  // src/auth.ts
913
- import { spawn } from "child_process";
914
- import fs5 from "fs";
915
- import os3 from "os";
916
- import path5 from "path";
917
705
  import kleur5 from "kleur";
918
- var MCP_PKG = "@yoonion/mimi-seed-mcp";
919
- var MIMI_DIR = path5.join(os3.homedir(), ".mimi-seed");
920
706
  function log(msg) {
921
707
  process.stdout.write(msg + "\n");
922
708
  }
@@ -934,8 +720,18 @@ ${kleur5.bold("\uD50C\uB7AB\uD3FC\uBCC4 \uC790\uACA9\uC99D\uBA85:")}
934
720
  ${kleur5.cyan("mimi-seed auth playstore")} Play \uC11C\uBE44\uC2A4 \uACC4\uC815 JSON \uB4F1\uB85D
935
721
  ${kleur5.cyan("mimi-seed auth bigquery")} BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815 (Crashlytics export \uB4F1)
936
722
 
723
+ ${kleur5.bold("\uBE4C\uB4DC / \uB9C8\uCF00\uD305:")}
724
+ ${kleur5.cyan("mimi-seed auth jenkins")} Jenkins \uC5F0\uACB0 (jenkins.json)
725
+ ${kleur5.cyan("mimi-seed auth ci")} GitHub Actions / GitLab CI (ci.json)
726
+ ${kleur5.cyan("mimi-seed auth googleads")} Google Ads (google-ads.json)
727
+ ${kleur5.cyan("mimi-seed auth facebook")} Facebook \uD398\uC774\uC9C0
728
+ ${kleur5.cyan("mimi-seed auth instagram")} Instagram
729
+
937
730
  ${kleur5.bold("\uC804\uCCB4 \uC0C1\uD0DC:")}
938
- ${kleur5.cyan("mimi-seed auth status --all")} 4\uAC1C \uC790\uACA9\uC99D\uBA85 \uBCF4\uC720 \uC5EC\uBD80 \uD55C\uB208\uC5D0
731
+ ${kleur5.cyan("mimi-seed auth status --all")} \uBAA8\uB4E0 \uC790\uACA9\uC99D\uBA85 \uBCF4\uC720 \uC5EC\uBD80 \uD55C\uB208\uC5D0
732
+
733
+ ${kleur5.bold("\uD55C \uBC88\uC5D0 \uB2E4 \uC5F0\uACB0:")}
734
+ ${kleur5.cyan("mimi-seed setup")} \uC548\uB0B4\uB97C \uB530\uB77C\uAC00\uBA70 \uC21C\uC11C\uB300\uB85C \uC5F0\uACB0 (\uAD8C\uC7A5)
939
735
 
940
736
  ${kleur5.bold("login \uC635\uC158:")}
941
737
  --no-browser URL \uC790\uB3D9 \uC624\uD508 \uC548 \uD568 (\uC9C1\uC811 \uBCF5\uBD99)
@@ -944,44 +740,16 @@ ${kleur5.bold("login \uC635\uC158:")}
944
740
 
945
741
  ${kleur5.dim(`\uB0B4\uBD80\uC801\uC73C\uB85C ${MCP_PKG} \uC758 mimi-seed-*-auth CLI\uB97C \uD638\uCD9C\uD569\uB2C8\uB2E4.`)}`);
946
742
  }
947
- async function runMcpBin(bin, extraArgs) {
948
- return new Promise((resolve) => {
949
- const child = spawn("npx", ["-y", `${MCP_PKG}`, bin, ...extraArgs], {
950
- stdio: "inherit",
951
- shell: true
952
- });
953
- child.on("error", (e) => {
954
- process.stderr.write(`
955
- \u274C npx \uC2E4\uD589 \uC2E4\uD328: ${e.message}
956
- `);
957
- resolve(1);
958
- });
959
- child.on("exit", (code) => resolve(code ?? 0));
960
- });
961
- }
962
- function fileExists(name) {
963
- return fs5.existsSync(path5.join(MIMI_DIR, name));
964
- }
965
- function anyFileStarting(prefix) {
966
- try {
967
- return fs5.readdirSync(MIMI_DIR).some((f) => f.startsWith(prefix));
968
- } catch {
969
- return false;
970
- }
971
- }
972
743
  function printAllCredStatus() {
973
- log(kleur5.bold("\uB85C\uCEEC \uC790\uACA9\uC99D\uBA85 \uC0C1\uD0DC ") + kleur5.dim("(~/.mimi-seed)"));
974
- const rows = [
975
- ["Google OAuth (Firebase/AdMob/Play)", fileExists("tokens.json"), "mimi-seed auth login"],
976
- ["App Store Connect", fileExists("appstore.json"), "mimi-seed auth appstore"],
977
- ["Play \uC11C\uBE44\uC2A4 \uACC4\uC815", anyFileStarting("play-service-account"), "mimi-seed auth playstore"],
978
- ["BigQuery \uC11C\uBE44\uC2A4 \uACC4\uC815", anyFileStarting("bigquery"), "mimi-seed auth bigquery"]
979
- ];
980
- for (const [label, ok2, cmd] of rows) {
981
- const mark = ok2 ? kleur5.green("\u2713") : kleur5.red("\u2717");
982
- const hint = ok2 ? "" : kleur5.dim(` \u2192 ${cmd}`);
983
- log(` ${mark} ${label}${hint}`);
744
+ log(kleur5.bold(t().auth.statusTitle + " ") + kleur5.dim("(~/.mimi-seed)"));
745
+ const detected = detectAll();
746
+ for (const spec of CREDENTIALS) {
747
+ const d = detected.get(spec.id);
748
+ const mark = d.present ? kleur5.green("\u2713") : isSatisfied(spec, detected) ? kleur5.yellow("~") : spec.requirement === "optional" ? kleur5.dim("\xB7") : kleur5.red("\u2717");
749
+ const tail = d.present ? kleur5.dim(d.detail ?? "") : kleur5.dim(`\u2192 ${spec.fix}`);
750
+ log(` ${mark} ${credLabel(spec).padEnd(24)} ${tail}`);
984
751
  }
752
+ log(kleur5.dim("\n \uD55C \uBC88\uC5D0 \uC5F0\uACB0: mimi-seed setup"));
985
753
  }
986
754
  async function cmdAuth(args) {
987
755
  const sub = args[0];
@@ -997,6 +765,15 @@ async function cmdAuth(args) {
997
765
  if (sub === "appstore") return void exitWith(await runMcpBin("mimi-seed-appstore-auth", rest));
998
766
  if (sub === "playstore") return void exitWith(await runMcpBin("mimi-seed-playstore-auth", rest));
999
767
  if (sub === "bigquery") return void exitWith(await runMcpBin("mimi-seed-bigquery-auth", rest));
768
+ if (sub === "jenkins") return void exitWith(await runMcpBin("mimi-seed-jenkins-auth", rest));
769
+ if (sub === "googleads") return void exitWith(await runMcpBin("mimi-seed-googleads-auth", rest));
770
+ if (sub === "facebook") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["facebook"]));
771
+ if (sub === "instagram") return void exitWith(await runMcpBin("mimi-seed-social-auth", ["instagram"]));
772
+ if (sub === "ci") {
773
+ const { cmdSetup: cmdSetup2 } = await import("./setup-G2JY35UW.js");
774
+ await cmdSetup2(["--only", "github,gitlab", "--reconnect", "github,gitlab"]);
775
+ return;
776
+ }
1000
777
  let mcpArgs;
1001
778
  switch (sub) {
1002
779
  case void 0:
@@ -1013,7 +790,7 @@ async function cmdAuth(args) {
1013
790
  mcpArgs = ["--logout", ...rest];
1014
791
  break;
1015
792
  default:
1016
- log(kleur5.red(`\uC54C \uC218 \uC5C6\uB294 auth \uC11C\uBE0C\uBA85\uB839: ${sub}`));
793
+ log(kleur5.red(t().auth.unknownSub(sub ?? "")));
1017
794
  printAuthHelp();
1018
795
  process.exit(1);
1019
796
  }
@@ -1023,12 +800,39 @@ function exitWith(code) {
1023
800
  if (code !== 0) process.exit(code);
1024
801
  }
1025
802
 
803
+ // src/lang.ts
804
+ import kleur6 from "kleur";
805
+ function log2(msg) {
806
+ process.stdout.write(msg + "\n");
807
+ }
808
+ async function cmdLang(args) {
809
+ const arg = args[0]?.toLowerCase();
810
+ if (!arg) {
811
+ log2(t().lang.current(resolveLang()));
812
+ if (process.env.MIMI_SEED_LANG) {
813
+ log2(kleur6.dim(" (MIMI_SEED_LANG)"));
814
+ }
815
+ return;
816
+ }
817
+ if (arg === "--help" || arg === "-h") {
818
+ log2(t().lang.usage);
819
+ return;
820
+ }
821
+ if (!isLang(arg)) {
822
+ log2(kleur6.red(t().lang.invalid(arg)));
823
+ process.exit(1);
824
+ }
825
+ writeSettings({ lang: arg });
826
+ process.env.MIMI_SEED_LANG = arg;
827
+ log2(kleur6.green(t().lang.saved(arg)));
828
+ }
829
+
1026
830
  // src/cloud.ts
1027
- import { spawn as spawn2 } from "child_process";
831
+ import { spawn } from "child_process";
1028
832
  var MCP_PKG2 = "@yoonion/mimi-seed-mcp";
1029
833
  async function runMcpBin2(bin, extraArgs) {
1030
834
  return new Promise((resolve) => {
1031
- const child = spawn2("npx", ["-y", `${MCP_PKG2}`, bin, ...extraArgs], {
835
+ const child = spawn("npx", ["-y", `${MCP_PKG2}`, bin, ...extraArgs], {
1032
836
  stdio: "inherit",
1033
837
  shell: true
1034
838
  });
@@ -1054,541 +858,19 @@ async function cmdGa4(args) {
1054
858
  exitWith2(await runMcpBin2("mimi-seed-ga4", args));
1055
859
  }
1056
860
 
1057
- // src/deploy.ts
1058
- import kleur6 from "kleur";
1059
- import * as readline from "readline";
1060
-
1061
- // src/ci-providers.ts
1062
- import fs6 from "fs";
1063
- import path6 from "path";
1064
- import os4 from "os";
1065
- var CI_CONFIG_PATH = path6.join(os4.homedir(), ".mimi-seed", "ci.json");
1066
- function loadCiProviderConfig() {
1067
- try {
1068
- return JSON.parse(fs6.readFileSync(CI_CONFIG_PATH, "utf-8"));
1069
- } catch {
1070
- return null;
1071
- }
1072
- }
1073
- function saveCiProviderConfig(cfg) {
1074
- const dir = path6.dirname(CI_CONFIG_PATH);
1075
- if (!fs6.existsSync(dir)) {
1076
- fs6.mkdirSync(dir, { recursive: true, mode: 448 });
1077
- }
1078
- fs6.writeFileSync(CI_CONFIG_PATH, JSON.stringify(cfg, null, 2));
1079
- if (process.platform !== "win32") {
1080
- fs6.chmodSync(CI_CONFIG_PATH, 384);
1081
- }
1082
- }
1083
- function ghBase(cfg) {
1084
- if (cfg.host) return `${cfg.host.replace(/\/$/, "")}/api/v3`;
1085
- return "https://api.github.com";
1086
- }
1087
- function ghHeaders(token) {
1088
- return {
1089
- Authorization: `Bearer ${token}`,
1090
- Accept: "application/vnd.github+json",
1091
- "X-GitHub-Api-Version": "2022-11-28",
1092
- "Content-Type": "application/json"
1093
- };
1094
- }
1095
- async function ghTriggerWorkflow(cfg, workflow, ref, inputs = {}) {
1096
- const startTime = /* @__PURE__ */ new Date();
1097
- const wfId = /^\d+$/.test(workflow) ? Number(workflow) : workflow;
1098
- const dispatchRes = await fetch(
1099
- `${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/dispatches`,
1100
- {
1101
- method: "POST",
1102
- headers: ghHeaders(cfg.token),
1103
- body: JSON.stringify({ ref, inputs })
1104
- }
1105
- );
1106
- if (!dispatchRes.ok) {
1107
- throw new Error(`GitHub dispatch ${dispatchRes.status}: ${await dispatchRes.text()}`);
1108
- }
1109
- await new Promise((r) => setTimeout(r, 3e3));
1110
- const runsRes = await fetch(
1111
- `${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/workflows/${wfId}/runs?per_page=5`,
1112
- { headers: ghHeaders(cfg.token) }
1113
- );
1114
- if (!runsRes.ok) return null;
1115
- const data = await runsRes.json();
1116
- const run = data.workflow_runs.find((r) => new Date(r.created_at) >= startTime) ?? data.workflow_runs[0];
1117
- if (!run) return null;
1118
- return { runId: run.id, url: run.html_url };
1119
- }
1120
- async function ghPollRun(cfg, runId, onTick, timeoutMs = 30 * 60 * 1e3, intervalMs = 15e3) {
1121
- const start = Date.now();
1122
- let consecutiveErrors = 0;
1123
- while (Date.now() - start < timeoutMs) {
1124
- await new Promise((r) => setTimeout(r, intervalMs));
1125
- let res;
1126
- try {
1127
- res = await fetch(
1128
- `${ghBase(cfg)}/repos/${cfg.owner}/${cfg.repo}/actions/runs/${runId}`,
1129
- { headers: ghHeaders(cfg.token) }
1130
- );
1131
- } catch {
1132
- consecutiveErrors++;
1133
- if (consecutiveErrors >= 3) throw new Error("GitHub API \uC5F0\uC18D \uC624\uB958 3\uD68C");
1134
- continue;
1135
- }
1136
- if (!res.ok) {
1137
- consecutiveErrors++;
1138
- if (consecutiveErrors >= 3) throw new Error(`GitHub API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
1139
- continue;
1140
- }
1141
- consecutiveErrors = 0;
1142
- const data = await res.json();
1143
- onTick?.(data.status);
1144
- if (data.status === "completed") {
1145
- if (data.conclusion === "success") return "success";
1146
- if (data.conclusion === "cancelled") return "cancelled";
1147
- return "failure";
1148
- }
1149
- }
1150
- return "timeout";
1151
- }
1152
- function glBase(cfg) {
1153
- return `${cfg.host ?? "https://gitlab.com"}/api/v4`;
1154
- }
1155
- function glProjectId(cfg) {
1156
- return encodeURIComponent(`${cfg.owner}/${cfg.repo}`);
1157
- }
1158
- async function glTriggerPipeline(cfg, ref, variables = {}) {
1159
- const vars = Object.entries(variables).map(([key, value]) => ({ key, value }));
1160
- const body = { ref };
1161
- if (vars.length > 0) body.variables = vars;
1162
- const res = await fetch(`${glBase(cfg)}/projects/${glProjectId(cfg)}/pipeline`, {
1163
- method: "POST",
1164
- headers: { "PRIVATE-TOKEN": cfg.token, "Content-Type": "application/json" },
1165
- body: JSON.stringify(body)
1166
- });
1167
- if (!res.ok) throw new Error(`GitLab trigger ${res.status}: ${await res.text()}`);
1168
- const data = await res.json();
1169
- return { pipelineId: data.id, url: data.web_url };
1170
- }
1171
- async function glPollPipeline(cfg, pipelineId, onTick, timeoutMs = 30 * 60 * 1e3, intervalMs = 15e3) {
1172
- const start = Date.now();
1173
- let consecutiveErrors = 0;
1174
- while (Date.now() - start < timeoutMs) {
1175
- await new Promise((r) => setTimeout(r, intervalMs));
1176
- let res;
1177
- try {
1178
- res = await fetch(
1179
- `${glBase(cfg)}/projects/${glProjectId(cfg)}/pipelines/${pipelineId}`,
1180
- { headers: { "PRIVATE-TOKEN": cfg.token } }
1181
- );
1182
- } catch {
1183
- consecutiveErrors++;
1184
- if (consecutiveErrors >= 3) throw new Error("GitLab API \uC5F0\uC18D \uC624\uB958 3\uD68C");
1185
- continue;
1186
- }
1187
- if (!res.ok) {
1188
- consecutiveErrors++;
1189
- if (consecutiveErrors >= 3) throw new Error(`GitLab API \uC5F0\uC18D \uC624\uB958 (HTTP ${res.status})`);
1190
- continue;
1191
- }
1192
- consecutiveErrors = 0;
1193
- const data = await res.json();
1194
- onTick?.(data.status);
1195
- if (data.status === "success") return "success";
1196
- if (data.status === "failed") return "failure";
1197
- if (data.status === "canceled" || data.status === "skipped") return "cancelled";
1198
- }
1199
- return "timeout";
1200
- }
1201
-
1202
- // src/deploy.ts
1203
- var PHASE_ICON = {
1204
- init: "\u{1F680}",
1205
- verify: "\u{1F50D}",
1206
- notes: "\u{1F4DD}",
1207
- apply: "\u{1F4E4}",
1208
- promote: "\u{1F3AF}",
1209
- done: "\u{1F389}",
1210
- error: "\u274C"
1211
- };
1212
- function log2(msg) {
1213
- process.stdout.write(msg + "\n");
1214
- }
1215
- function jenkinsHeaders(cfg) {
1216
- const creds = Buffer.from(`${cfg.user ?? "admin"}:${cfg.token}`).toString("base64");
1217
- return { Authorization: `Basic ${creds}`, "Content-Type": "application/json" };
1218
- }
1219
- async function triggerBuild(cfg, jobName, params) {
1220
- const qs = new URLSearchParams(params).toString();
1221
- const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/buildWithParameters?${qs}`;
1222
- const res = await fetch(url, { method: "POST", headers: jenkinsHeaders(cfg) });
1223
- if (!res.ok) {
1224
- throw new Error(`Jenkins \uD2B8\uB9AC\uAC70 \uC2E4\uD328 ${res.status}: ${await res.text()}`);
1225
- }
1226
- const location = res.headers.get("Location") ?? "";
1227
- const match = location.match(/\/queue\/item\/(\d+)\//);
1228
- return match?.[1] ?? "";
1229
- }
1230
- async function getQueueBuildNumber(cfg, queueItemId) {
1231
- const url = `${cfg.url}/queue/item/${queueItemId}/api/json`;
1232
- const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
1233
- if (!res.ok) return null;
1234
- const data = await res.json();
1235
- return data.executable?.number ?? null;
1236
- }
1237
- async function getBuildStatus(cfg, jobName, buildNumber) {
1238
- const url = `${cfg.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`;
1239
- const res = await fetch(url, { headers: jenkinsHeaders(cfg) });
1240
- if (!res.ok) throw new Error(`\uBE4C\uB4DC \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328 ${res.status}`);
1241
- const data = await res.json();
1242
- return {
1243
- building: data.building ?? true,
1244
- result: data.result ?? null,
1245
- duration: data.duration ?? 0
1246
- };
1247
- }
1248
- async function pollBuildComplete(cfg, jobName, buildNumber, timeoutMs = 30 * 60 * 1e3) {
1249
- const start = Date.now();
1250
- const intervalMs = 15e3;
1251
- let dots = 0;
1252
- let consecutiveErrors = 0;
1253
- while (Date.now() - start < timeoutMs) {
1254
- await new Promise((r) => setTimeout(r, intervalMs));
1255
- let status;
1256
- try {
1257
- status = await getBuildStatus(cfg, jobName, buildNumber);
1258
- consecutiveErrors = 0;
1259
- } catch {
1260
- consecutiveErrors++;
1261
- 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");
1262
- process.stdout.write(`\r \u26A0 Jenkins \uC5F0\uACB0 \uC624\uB958 (${consecutiveErrors}/3\uD68C), \uC7AC\uC2DC\uB3C4... `);
1263
- continue;
1264
- }
1265
- dots = (dots + 1) % 4;
1266
- process.stdout.write(`\r \u23F3 \uBE4C\uB4DC \uC9C4\uD589 \uC911${".".repeat(dots + 1)} `);
1267
- if (!status.building) {
1268
- process.stdout.write("\n");
1269
- return status.result ?? "FAILURE";
1270
- }
1271
- }
1272
- throw new Error("\uBE4C\uB4DC \uD0C0\uC784\uC544\uC6C3 (30\uBD84)");
1273
- }
1274
- async function streamDeploy(webBase, token, body) {
1275
- const res = await fetch(`${webBase}/api/deploy`, {
1276
- method: "POST",
1277
- headers: {
1278
- "Content-Type": "application/json",
1279
- Authorization: `Bearer ${token}`
1280
- },
1281
- body: JSON.stringify(body)
1282
- });
1283
- if (!res.ok) {
1284
- const text = await res.text().catch(() => "");
1285
- throw new Error(`\uC11C\uBC84 \uBC30\uD3EC \uC2E4\uD328 ${res.status}: ${text.slice(0, 200)}`);
1286
- }
1287
- const reader = res.body?.getReader();
1288
- if (!reader) throw new Error("SSE \uC2A4\uD2B8\uB9BC \uC5C6\uC74C");
1289
- const decoder = new TextDecoder();
1290
- let buf = "";
1291
- while (true) {
1292
- const { done, value } = await reader.read();
1293
- if (done) break;
1294
- buf += decoder.decode(value, { stream: true });
1295
- const lines = buf.split("\n");
1296
- buf = lines.pop() ?? "";
1297
- for (const line of lines) {
1298
- if (!line.startsWith("data: ")) continue;
1299
- try {
1300
- const event = JSON.parse(line.slice(6));
1301
- const icon = PHASE_ICON[event.phase] ?? "\u25B8";
1302
- const color = event.status === "done" ? kleur6.green : event.status === "failed" ? kleur6.red : event.status === "skipped" ? kleur6.yellow : kleur6.dim;
1303
- log2(` ${icon} ${color(event.message)}`);
1304
- if (event.phase === "error" || event.phase === "verify" && event.status === "failed" || event.phase === "promote" && event.status === "failed") {
1305
- process.exit(1);
1306
- }
1307
- } catch {
1308
- }
1309
- }
1310
- }
1311
- }
1312
- function parseArgs4(argv) {
1313
- const args = {
1314
- platform: "android",
1315
- language: "ko-KR",
1316
- dryRun: false,
1317
- yes: false,
1318
- skipBuild: false,
1319
- setupJenkins: false,
1320
- setupGithub: false,
1321
- setupGitlab: false,
1322
- ci: "auto",
1323
- ref: "main"
1324
- };
1325
- for (let i = 0; i < argv.length; i++) {
1326
- if ((argv[i] === "--platform" || argv[i] === "-p") && argv[i + 1]) args.platform = argv[++i];
1327
- if (argv[i] === "--app" && argv[i + 1]) args.appId = argv[++i];
1328
- if (argv[i] === "--version-code" && argv[i + 1]) args.versionCode = Number(argv[++i]);
1329
- if (argv[i] === "--from" && argv[i + 1]) args.fromRef = argv[++i];
1330
- if (argv[i] === "--to" && argv[i + 1]) args.toRef = argv[++i];
1331
- if (argv[i] === "--language" && argv[i + 1]) args.language = argv[++i];
1332
- if (argv[i] === "--dry-run") args.dryRun = true;
1333
- if (argv[i] === "--yes" || argv[i] === "-y") args.yes = true;
1334
- if (argv[i] === "--skip-build") args.skipBuild = true;
1335
- if (argv[i] === "--ci" && argv[i + 1]) args.ci = argv[++i];
1336
- if (argv[i] === "--workflow" && argv[i + 1]) args.workflow = argv[++i];
1337
- if (argv[i] === "--ref" && argv[i + 1]) args.ref = argv[++i];
1338
- if (argv[i] === "setup-jenkins") args.setupJenkins = true;
1339
- if (argv[i] === "setup-github") args.setupGithub = true;
1340
- if (argv[i] === "setup-gitlab") args.setupGitlab = true;
1341
- }
1342
- return args;
1343
- }
1344
- async function promptJenkinsSetup() {
1345
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1346
- const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
1347
- log2(kleur6.bold("Jenkins \uC124\uC815"));
1348
- const url = await ask(" Jenkins URL (\uC608: http://your-jenkins.example.com:8080): ");
1349
- const user = await ask(" Jenkins \uC0AC\uC6A9\uC790\uBA85 (\uC608: admin): ");
1350
- const token = await ask(" Jenkins API Token: ");
1351
- const jobAndroid = await ask(" Android Job \uC774\uB984 (\uC608: my-app-android): ");
1352
- const jobIos = await ask(" iOS Job \uC774\uB984 (\uC120\uD0DD, \uC5D4\uD130 \uC2A4\uD0B5): ");
1353
- rl.close();
1354
- return { url, user, token, jobAndroid: jobAndroid || void 0, jobIos: jobIos || void 0 };
1355
- }
1356
- async function promptGitProviderSetup(provider) {
1357
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1358
- const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
1359
- const isGh = provider === "github";
1360
- log2(kleur6.bold(isGh ? "GitHub Actions \uC124\uC815" : "GitLab CI \uC124\uC815"));
1361
- const tokenLabel = isGh ? " GitHub Personal Access Token (repo+workflow \uC2A4\uCF54\uD504): " : " GitLab Personal Access Token: ";
1362
- const token = await ask(tokenLabel);
1363
- const owner = await ask(isGh ? " Owner (org/user): " : " Namespace/group: ");
1364
- const repo = await ask(" Repo \uC774\uB984 (\uACBD\uB85C \uC5C6\uC774): ");
1365
- const hostPrompt = isGh ? " GitHub Enterprise host (\uC120\uD0DD, \uC5D4\uD130=github.com): " : " GitLab self-hosted URL (\uC120\uD0DD, \uC5D4\uD130=gitlab.com): ";
1366
- const host = await ask(hostPrompt);
1367
- rl.close();
1368
- return {
1369
- provider,
1370
- token,
1371
- owner,
1372
- repo,
1373
- host: host || void 0
1374
- };
1375
- }
1376
- function resolveCi(ciOption, jenkins, ciProvider) {
1377
- if (ciOption !== "auto") return ciOption;
1378
- if (jenkins?.url && jenkins.token) return "jenkins";
1379
- if (ciProvider) return ciProvider.provider;
1380
- throw new Error(
1381
- "CI \uC124\uC815 \uC5C6\uC74C. \uB2E4\uC74C \uC911 \uD558\uB098 \uC2E4\uD589:\n \u2022 mimi-seed deploy setup-jenkins\n \u2022 mimi-seed deploy setup-github\n \u2022 mimi-seed deploy setup-gitlab"
1382
- );
1383
- }
1384
- async function runGitProviderBuild(cfg, args) {
1385
- let runUrl = "";
1386
- let runId;
1387
- if (cfg.provider === "github") {
1388
- if (!args.workflow) {
1389
- throw new Error("--workflow \uD544\uC694 (\uC608: --workflow deploy.yml)");
1390
- }
1391
- log2(`\u{1F528} GitHub Actions \uD2B8\uB9AC\uAC70: ${kleur6.cyan(args.workflow)} @ ${args.ref}`);
1392
- const inputs = {};
1393
- if (args.appId) inputs.MIMI_APP_ID = args.appId;
1394
- inputs.PLATFORM = args.platform;
1395
- const result2 = await ghTriggerWorkflow(cfg, args.workflow, args.ref, inputs);
1396
- if (!result2) {
1397
- throw new Error("GitHub Actions run_id \uC870\uD68C \uC2E4\uD328. \uC7A0\uC2DC \uD6C4 ci_list_recent_builds \uB85C \uD655\uC778\uD558\uC138\uC694.");
1398
- }
1399
- runId = result2.runId;
1400
- runUrl = result2.url;
1401
- log2(kleur6.dim(` Run ID: ${runId} \u2192 ${runUrl}`));
1402
- } else {
1403
- log2(`\u{1F528} GitLab Pipeline \uD2B8\uB9AC\uAC70: ${args.ref}`);
1404
- const variables = { PLATFORM: args.platform };
1405
- if (args.appId) variables.MIMI_APP_ID = args.appId;
1406
- const result2 = await glTriggerPipeline(cfg, args.ref, variables);
1407
- runId = result2.pipelineId;
1408
- runUrl = result2.url;
1409
- log2(kleur6.dim(` Pipeline ID: ${runId} \u2192 ${runUrl}`));
1410
- }
1411
- log2(" \uC644\uB8CC \uB300\uAE30 \uC911...");
1412
- let dots = 0;
1413
- const onTick = (status) => {
1414
- dots = (dots + 1) % 4;
1415
- process.stdout.write(`\r \u23F3 ${status}${".".repeat(dots + 1)} `);
1416
- };
1417
- const result = cfg.provider === "github" ? await ghPollRun(cfg, runId, onTick) : await glPollPipeline(cfg, runId, onTick);
1418
- process.stdout.write("\n");
1419
- if (result === "success") {
1420
- log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${runId} \uC131\uACF5`));
1421
- return runId;
1422
- }
1423
- log2(kleur6.red(`\uBE4C\uB4DC \uC885\uB8CC: ${result}`));
1424
- log2(kleur6.dim(` ${runUrl}`));
1425
- log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code <N> --platform ${args.platform}`));
1426
- process.exit(1);
1427
- }
1428
- async function cmdDeploy(argv) {
1429
- const args = parseArgs4(argv);
1430
- const cfg = await getEffectiveConfig();
1431
- if (!cfg) {
1432
- log2(kleur6.red("\uC5F0\uACB0\uB41C \uACC4\uC815 \uC5C6\uC74C. `mimi-seed init` \uC2E4\uD589."));
1433
- process.exit(1);
1434
- }
1435
- if (args.setupJenkins) {
1436
- const jenkins = await promptJenkinsSetup();
1437
- await writeConfig({ ...cfg, jenkins });
1438
- log2(kleur6.green("\u2705 Jenkins \uC124\uC815 \uC800\uC7A5\uB428"));
1439
- return;
1440
- }
1441
- if (args.setupGithub) {
1442
- const ciCfg = await promptGitProviderSetup("github");
1443
- saveCiProviderConfig(ciCfg);
1444
- log2(kleur6.green(`\u2705 GitHub Actions \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
1445
- return;
1446
- }
1447
- if (args.setupGitlab) {
1448
- const ciCfg = await promptGitProviderSetup("gitlab");
1449
- saveCiProviderConfig(ciCfg);
1450
- log2(kleur6.green(`\u2705 GitLab CI \uC124\uC815 \uC800\uC7A5\uB428 \u2192 ~/.mimi-seed/ci.json`));
1451
- return;
1452
- }
1453
- log2(kleur6.bold(`mimi-seed deploy \u2014 ${args.platform}`));
1454
- if (args.dryRun) log2(kleur6.yellow(" [dry-run \uBAA8\uB4DC] \uC2E4\uC81C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4"));
1455
- log2("");
1456
- let versionCode = args.versionCode;
1457
- if (!args.skipBuild) {
1458
- const ciProvider = loadCiProviderConfig();
1459
- const kind = resolveCi(args.ci, cfg.jenkins, ciProvider);
1460
- log2(kleur6.dim(` CI: ${kind}`));
1461
- if (kind === "jenkins") {
1462
- if (!cfg.jenkins?.url || !cfg.jenkins?.token) {
1463
- log2(kleur6.yellow("Jenkins \uC124\uC815 \uC5C6\uC74C. `mimi-seed deploy setup-jenkins` \uB85C \uC124\uC815\uD558\uAC70\uB098 --skip-build \uC0AC\uC6A9."));
1464
- process.exit(1);
1465
- }
1466
- const jenkins = cfg.jenkins;
1467
- const jobName = args.platform === "android" ? jenkins.jobAndroid : jenkins.jobIos;
1468
- if (!jobName) {
1469
- log2(kleur6.red(`${args.platform} Jenkins job\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. setup-jenkins \uC2E4\uD589.`));
1470
- process.exit(1);
1471
- }
1472
- log2(`\u{1F528} Jenkins \uBE4C\uB4DC \uD2B8\uB9AC\uAC70: ${kleur6.cyan(jobName)}`);
1473
- const buildParams = {};
1474
- if (args.appId) buildParams.MIMI_APP_ID = args.appId;
1475
- const queueItemId = await triggerBuild(jenkins, jobName, buildParams);
1476
- if (!queueItemId) {
1477
- 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."));
1478
- } else {
1479
- log2(kleur6.dim(` Queue item: ${queueItemId}`));
1480
- }
1481
- let buildNumber = null;
1482
- if (queueItemId) {
1483
- log2(" \uBE4C\uB4DC \uBC88\uD638 \uB300\uAE30 \uC911...");
1484
- for (let i = 0; i < 6; i++) {
1485
- await new Promise((r) => setTimeout(r, 5e3));
1486
- buildNumber = await getQueueBuildNumber(jenkins, queueItemId).catch(() => null);
1487
- if (buildNumber) break;
1488
- }
1489
- }
1490
- if (!buildNumber) {
1491
- 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."));
1492
- process.exit(1);
1493
- }
1494
- log2(` \uBE4C\uB4DC #${buildNumber} \uC2DC\uC791\uB428. \uC644\uB8CC \uB300\uAE30 \uC911...`);
1495
- const result = await pollBuildComplete(jenkins, jobName, buildNumber);
1496
- if (result !== "SUCCESS") {
1497
- log2(kleur6.red(`\uBE4C\uB4DC \uC2E4\uD328: ${result}`));
1498
- log2(kleur6.dim(` Jenkins: ${jenkins.url}/job/${encodeURIComponent(jobName)}/${buildNumber}/`));
1499
- log2(kleur6.dim(` \uBE4C\uB4DC\uAC00 \uC774\uBBF8 \uC644\uB8CC\uB410\uB2E4\uBA74: mimi-seed deploy --skip-build --version-code ${buildNumber} --platform ${args.platform}`));
1500
- process.exit(1);
1501
- }
1502
- log2(kleur6.green(`\u2705 \uBE4C\uB4DC #${buildNumber} \uC131\uACF5`));
1503
- if (!versionCode) {
1504
- versionCode = buildNumber;
1505
- log2(kleur6.dim(` versionCode = buildNumber (${versionCode})`));
1506
- }
1507
- } else {
1508
- if (!ciProvider) {
1509
- log2(kleur6.red(`${kind} \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. setup-${kind} \uC2E4\uD589.`));
1510
- process.exit(1);
1511
- }
1512
- const buildId = await runGitProviderBuild(ciProvider, args);
1513
- if (!versionCode) {
1514
- log2(kleur6.red(`\u2717 versionCode \uBBF8\uC9C0\uC815 (${kind} run_id ${buildId}\uB294 versionCode\uB85C \uBD80\uC801\uD569)`));
1515
- log2(kleur6.dim(" \uAD8C\uC7A5 \uC0AC\uD56D:"));
1516
- log2(kleur6.dim(" \u2022 CI \uC6CC\uD06C\uD50C\uB85C \uC548\uC5D0\uC11C versionCode\uB97C \uACB0\uC815\uD558\uACE0 \uACB0\uACFC\uB97C \uCD9C\uB825"));
1517
- log2(kleur6.dim(" \u2022 \uB2E4\uC74C \uC2E4\uD589: mimi-seed deploy --skip-build --version-code <N>"));
1518
- process.exit(1);
1519
- }
1520
- }
1521
- }
1522
- if (!versionCode) {
1523
- log2(kleur6.red("versionCode\uB97C \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --version-code <N> \uC73C\uB85C \uC9C0\uC815\uD558\uC138\uC694."));
1524
- process.exit(1);
1525
- }
1526
- let appId = args.appId;
1527
- if (!appId) {
1528
- const { mcpCall: mcpCall2 } = await import("./mcp-client-SLIKR42C.js");
1529
- const r = await mcpCall2(cfg.endpoint, cfg.token, "list_apps", {});
1530
- if (!r.isError) {
1531
- try {
1532
- const apps = JSON.parse(r.text);
1533
- if (apps.length > 0) {
1534
- appId = apps[0].id;
1535
- log2(kleur6.dim(` \uC571: ${apps[0].name} (${appId})`));
1536
- }
1537
- } catch {
1538
- }
1539
- }
1540
- }
1541
- if (!appId) {
1542
- 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."));
1543
- process.exit(1);
1544
- }
1545
- const needsConfirm = !args.dryRun && !args.yes && process.stdout.isTTY && !process.env.MIMI_SEED_TOKEN;
1546
- if (needsConfirm) {
1547
- const target = args.platform === "ios" ? "App Store \uC2EC\uC0AC \uC81C\uCD9C" : "Play Store production \uD2B8\uB799";
1548
- log2("");
1549
- log2(kleur6.yellow(`\u26A0 \uC2E4\uC81C \uBC30\uD3EC: ${args.platform} \xB7 versionCode ${versionCode} \u2192 ${target}`));
1550
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1551
- const answer = await new Promise(
1552
- (resolve) => rl.question(kleur6.bold("\uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694? [y/N]: "), (a) => resolve(a.trim().toLowerCase()))
1553
- );
1554
- rl.close();
1555
- if (answer !== "y" && answer !== "yes") {
1556
- log2(kleur6.dim("\uCDE8\uC18C\uB428. (--yes \uB85C \uD655\uC778 \uC0DD\uB7B5 \uAC00\uB2A5)"));
1557
- return;
1558
- }
1559
- }
1560
- log2("");
1561
- log2("\u{1F4E1} \uC11C\uBC84 \uBC30\uD3EC \uD30C\uC774\uD504\uB77C\uC778 \uC2DC\uC791...");
1562
- log2("");
1563
- const deployBuildNumber = !args.skipBuild ? versionCode : void 0;
1564
- await streamDeploy(cfg.webBase, cfg.token, {
1565
- appId,
1566
- platform: args.platform,
1567
- versionCode,
1568
- ...deployBuildNumber ? { buildNumber: deployBuildNumber } : {},
1569
- fromRef: args.fromRef,
1570
- toRef: args.toRef,
1571
- language: args.language,
1572
- dryRun: args.dryRun
1573
- });
1574
- log2("");
1575
- log2(kleur6.bold("\uC644\uB8CC. Play Console\uC5D0\uC11C \uBC30\uD3EC \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694."));
1576
- log2(kleur6.dim(" https://play.google.com/console/developers"));
1577
- }
1578
-
1579
861
  // src/mcp-restart.ts
1580
862
  import { execSync as execSync2 } from "child_process";
1581
- import fs7 from "fs";
1582
- import os5 from "os";
1583
- import path7 from "path";
863
+ import fs2 from "fs";
864
+ import os from "os";
865
+ import path2 from "path";
1584
866
  import kleur7 from "kleur";
1585
867
  function log3(msg) {
1586
868
  process.stdout.write(msg + "\n");
1587
869
  }
1588
870
  function readClaudeJson() {
1589
- const p = path7.join(os5.homedir(), ".claude.json");
871
+ const p = path2.join(os.homedir(), ".claude.json");
1590
872
  try {
1591
- return JSON.parse(fs7.readFileSync(p, "utf8"));
873
+ return JSON.parse(fs2.readFileSync(p, "utf8"));
1592
874
  } catch {
1593
875
  return {};
1594
876
  }
@@ -1604,7 +886,7 @@ function findProcessMarker(cfg) {
1604
886
  return meaningful.at(-1) ?? null;
1605
887
  }
1606
888
  function killByMarker(marker) {
1607
- const isWin = os5.platform() === "win32";
889
+ const isWin = os.platform() === "win32";
1608
890
  if (isWin) {
1609
891
  const escaped = marker.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1610
892
  let pids = [];
@@ -1675,9 +957,9 @@ async function cmdRestart(args) {
1675
957
  }
1676
958
 
1677
959
  // src/mcp-config.ts
1678
- import fs8 from "fs/promises";
1679
- import path8 from "path";
1680
- import os6 from "os";
960
+ import fs3 from "fs/promises";
961
+ import path3 from "path";
962
+ import os2 from "os";
1681
963
  import kleur8 from "kleur";
1682
964
  var SERVER_NAME = "mimi-seed";
1683
965
  function tomlString(value) {
@@ -1721,24 +1003,24 @@ function printMcpSetup(cfg) {
1721
1003
  );
1722
1004
  }
1723
1005
  async function writeCodexMcpConfig(cfg) {
1724
- const configDir = path8.join(os6.homedir(), ".codex");
1725
- const configPath = path8.join(configDir, "config.toml");
1726
- await fs8.mkdir(configDir, { recursive: true });
1006
+ const configDir = path3.join(os2.homedir(), ".codex");
1007
+ const configPath = path3.join(configDir, "config.toml");
1008
+ await fs3.mkdir(configDir, { recursive: true });
1727
1009
  let current = "";
1728
1010
  try {
1729
- current = await fs8.readFile(configPath, "utf8");
1011
+ current = await fs3.readFile(configPath, "utf8");
1730
1012
  } catch {
1731
1013
  current = "";
1732
1014
  }
1733
1015
  const next = replaceTomlBlock(current, `mcp_servers.${SERVER_NAME}`, codexBlock(cfg));
1734
- await fs8.writeFile(configPath, next);
1016
+ await fs3.writeFile(configPath, next);
1735
1017
  return configPath;
1736
1018
  }
1737
1019
 
1738
1020
  // src/release-manifest.ts
1739
- import fs9 from "fs/promises";
1740
- import path9 from "path";
1741
- var RELEASE_MANIFEST_RELATIVE_PATH = path9.join("docs", "releases.json");
1021
+ import fs4 from "fs/promises";
1022
+ import path4 from "path";
1023
+ var RELEASE_MANIFEST_RELATIVE_PATH = path4.join("docs", "releases.json");
1742
1024
  var RELEASE_MANIFEST_TEMPLATE = {
1743
1025
  $schema: {
1744
1026
  _comment: "Mimi Seed release notes SSOT. Store notes, App Store What's New, in-app announcements, and app-version rollout metadata can share this file.",
@@ -1764,14 +1046,14 @@ var RELEASE_MANIFEST_TEMPLATE = {
1764
1046
  versions: {}
1765
1047
  };
1766
1048
  async function ensureReleaseManifest(cwd) {
1767
- const filePath = path9.join(cwd, RELEASE_MANIFEST_RELATIVE_PATH);
1049
+ const filePath = path4.join(cwd, RELEASE_MANIFEST_RELATIVE_PATH);
1768
1050
  try {
1769
- await fs9.access(filePath);
1051
+ await fs4.access(filePath);
1770
1052
  return { path: filePath, created: false };
1771
1053
  } catch {
1772
1054
  }
1773
- await fs9.mkdir(path9.dirname(filePath), { recursive: true });
1774
- await fs9.writeFile(filePath, `${JSON.stringify(RELEASE_MANIFEST_TEMPLATE, null, 2)}
1055
+ await fs4.mkdir(path4.dirname(filePath), { recursive: true });
1056
+ await fs4.writeFile(filePath, `${JSON.stringify(RELEASE_MANIFEST_TEMPLATE, null, 2)}
1775
1057
  `, {
1776
1058
  mode: 420
1777
1059
  });
@@ -1822,7 +1104,7 @@ async function cmdInit(args) {
1822
1104
  return;
1823
1105
  }
1824
1106
  log4("\u{1F510} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778 \uB300\uAE30...");
1825
- const hostName = os7.hostname().slice(0, 32);
1107
+ const hostName = os3.hostname().slice(0, 32);
1826
1108
  const name = `cli-${hostName}`;
1827
1109
  const { port, promise } = await awaitHandshake(5 * 60 * 1e3);
1828
1110
  const callback = `http://127.0.0.1:${port}/cb`;
@@ -1900,16 +1182,16 @@ async function cmdInit(args) {
1900
1182
  "- `/mimi-seed:health` \u2014 \uC5F0\uACB0 \uC0C1\uD0DC \uBE60\uB978 \uD655\uC778",
1901
1183
  "- `/mimi-seed:review-inbox` \u2014 \uBBF8\uB2F5\uBCC0 \uB9AC\uBDF0 \uB2F5\uBCC0"
1902
1184
  ].join("\n");
1903
- const claudeDir = path10.join(cwd, ".claude");
1904
- const claudeAgentPath = path10.join(claudeDir, "mimi-seed.md");
1905
- if (!fs10.existsSync(claudeAgentPath)) {
1906
- fs10.mkdirSync(claudeDir, { recursive: true });
1907
- fs10.writeFileSync(claudeAgentPath, agentMd, { mode: 420 });
1185
+ const claudeDir = path5.join(cwd, ".claude");
1186
+ const claudeAgentPath = path5.join(claudeDir, "mimi-seed.md");
1187
+ if (!fs5.existsSync(claudeAgentPath)) {
1188
+ fs5.mkdirSync(claudeDir, { recursive: true });
1189
+ fs5.writeFileSync(claudeAgentPath, agentMd, { mode: 420 });
1908
1190
  log4(kleur9.dim(` Claude \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: .claude/mimi-seed.md`));
1909
1191
  }
1910
- const codexAgentPath = path10.join(cwd, "AGENTS.md");
1911
- if (!fs10.existsSync(codexAgentPath)) {
1912
- fs10.writeFileSync(codexAgentPath, agentMd, { mode: 420 });
1192
+ const codexAgentPath = path5.join(cwd, "AGENTS.md");
1193
+ if (!fs5.existsSync(codexAgentPath)) {
1194
+ fs5.writeFileSync(codexAgentPath, agentMd, { mode: 420 });
1913
1195
  log4(kleur9.dim(` Codex \uC5D0\uC774\uC804\uD2B8 \uC124\uC815: AGENTS.md`));
1914
1196
  }
1915
1197
  const manifest = await ensureReleaseManifest(cwd);
@@ -1935,6 +1217,10 @@ async function cmdInit(args) {
1935
1217
  log4("2) \uB85C\uCEEC MCP \uC11C\uBC84 \uB4F1\uB85D (\uC6D0\uACA9 'mimi-seed' \uC640 \uBCC4\uAC1C):");
1936
1218
  log4(kleur9.cyan(" claude mcp add mimi-seed-local -- npx -y @yoonion/mimi-seed-mcp"));
1937
1219
  log4(kleur9.dim(' Codex: ~/.codex/config.toml \uC5D0 [mcp_servers.mimi-seed-local] command="npx", args=["-y","@yoonion/mimi-seed-mcp"]'));
1220
+ log4("");
1221
+ log4("3) \uB098\uBA38\uC9C0 \uACC4\uC815 \uC5F0\uACB0 (App Store / Play / Jenkins / CI / \uC18C\uC15C \u2026):");
1222
+ log4(kleur9.cyan(" mimi-seed setup"));
1223
+ log4(kleur9.dim(" \uAC01 \uD56D\uBAA9\uC5D0\uC11C [?] \uB97C \uB204\uB974\uBA74 \uD1A0\uD070 \uBC1C\uAE09 \uBC29\uBC95\uC744 \uC54C\uB824\uC90D\uB2C8\uB2E4."));
1938
1224
  }
1939
1225
  }
1940
1226
  async function cmdStatus() {
@@ -2005,6 +1291,29 @@ var CMD_USAGE = {
2005
1291
 
2006
1292
  \uC635\uC158:
2007
1293
  --local \uCD94\uAC00\uB85C Google OAuth \uB85C\uADF8\uC778 + \uB85C\uCEEC MCP(\uC2A4\uD1A0\uC5B4 \uC4F0\uAE30 \uB3C4\uAD6C \uC804\uCCB4) \uB4F1\uB85D \uC548\uB0B4`,
1294
+ setup: `${kleur9.bold("mimi-seed setup")} \u2014 \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
1295
+
1296
+ \uC5F0\uACB0 \uC0C1\uD0DC\uB97C \uBA3C\uC800 \uBCF4\uC5EC\uC8FC\uACE0, \uC544\uC9C1 \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC740 \uAC83\uB9CC \uC21C\uC11C\uB300\uB85C \uBB3C\uC5B4\uBCF8\uB2E4.
1297
+ \uAC01 \uD56D\uBAA9\uC5D0\uC11C [?] \uB97C \uB204\uB974\uBA74 "\uADF8 \uD1A0\uD070\uC744 \uC5B4\uB514\uC11C \uC5B4\uB5BB\uAC8C \uBC1C\uAE09\uBC1B\uB294\uC9C0"\uB97C \uC54C\uB824\uC900\uB2E4.
1298
+ \uC774\uBBF8 \uC5F0\uACB0\uB41C \uD56D\uBAA9\uC740 \uAC74\uB108\uB6F0\uBBC0\uB85C, \uC911\uAC04\uC5D0 \uADF8\uB9CC\uB450\uACE0 \uB098\uC911\uC5D0 \uB2E4\uC2DC \uC2E4\uD589\uD574\uB3C4 \uB41C\uB2E4.
1299
+
1300
+ \uC635\uC158:
1301
+ --only <ids> \uC9C0\uC815\uD55C \uC790\uACA9\uC99D\uBA85\uB9CC (\uC27C\uD45C \uAD6C\uBD84: oauth,appstore,jenkins \u2026)
1302
+ --reconnect <ids> \uC774\uBBF8 \uC5F0\uACB0\uB3FC \uC788\uC5B4\uB3C4 \uB2E4\uC2DC \uC124\uC815
1303
+ --platform android,ios \uD50C\uB7AB\uD3FC \uAC15\uC81C \uC9C0\uC815 (\uAE30\uBCF8: \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uC790\uB3D9 \uAC10\uC9C0)
1304
+ --yes, -y \uC544\uBB34\uAC83\uB3C4 \uBB3B\uC9C0 \uC54A\uACE0 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825
1305
+ --non-interactive \uC704\uC640 \uB3D9\uC77C (CI \uC6A9)
1306
+ --fail-on-missing \uD544\uC218 \uC790\uACA9\uC99D\uBA85\uC774 \uC5C6\uC73C\uBA74 exit 1 (CI \uAC8C\uC774\uD2B8)
1307
+
1308
+ ${kleur9.dim("\uBE44TTY / CI \uD658\uACBD\uC5D0\uC11C\uB294 \uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 \uC0C1\uD0DC\uD45C\uB9CC \uCD9C\uB825\uD55C\uB2E4.")}`,
1309
+ lang: `${kleur9.bold("mimi-seed lang")} \u2014 CLI \uCD9C\uB825 \uC5B8\uC5B4 (\uD55C\uAD6D\uC5B4 / English)
1310
+
1311
+ mimi-seed lang \uD604\uC7AC \uC5B8\uC5B4 \uD45C\uC2DC
1312
+ mimi-seed lang ko \uD55C\uAD6D\uC5B4 (\uAE30\uBCF8)
1313
+ mimi-seed lang en English
1314
+
1315
+ ${kleur9.dim("~/.mimi-seed/settings.json \uC5D0 \uC800\uC7A5\uB429\uB2C8\uB2E4. \uD658\uACBD\uBCC0\uC218 MIMI_SEED_LANG \uAC00 \uC788\uC73C\uBA74 \uADF8\uAC8C \uC6B0\uC120\uD569\uB2C8\uB2E4.")}
1316
+ ${kleur9.dim("setup \uB9C8\uBC95\uC0AC\uAC00 \uCCAB \uC2E4\uD589 \uB54C \uBB3C\uC5B4\uBCF4\uBBC0\uB85C \uBCF4\uD1B5\uC740 \uC9C1\uC811 \uCE60 \uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.")}`,
2008
1317
  status: `${kleur9.bold("mimi-seed status")} \u2014 \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D. \uC635\uC158 \uC5C6\uC74C.`,
2009
1318
  doctor: `${kleur9.bold("mimi-seed doctor")} \u2014 \uD658\uACBD \uC9C4\uB2E8 (\uD1A0\uD070\xB7Node\xB7Git\xB7\uD504\uB85C\uC81D\uD2B8\xB7CI). \uC635\uC158 \uC5C6\uC74C.`,
2010
1319
  logout: `${kleur9.bold("mimi-seed logout")} \u2014 \uB85C\uCEEC \uC124\uC815(config.json) \uC0AD\uC81C. \uC635\uC158 \uC5C6\uC74C.`,
@@ -2072,8 +1381,10 @@ function printHelp() {
2072
1381
 
2073
1382
  ${kleur9.bold("\uBA85\uB839\uC5B4:")}
2074
1383
  ${kleur9.cyan("mimi-seed init")} \uD604\uC7AC \uD504\uB85C\uC81D\uD2B8\uB97C Mimi Seed\uC5D0 \uC5F0\uACB0
1384
+ ${kleur9.cyan("mimi-seed setup")} \uAC00\uC9C4 \uACC4\uC815\uC744 \uD55C \uBC88\uC5D0 \uC5F0\uACB0 (\uC548\uB0B4\uD615 \uB9C8\uBC95\uC0AC)
1385
+ ${kleur9.cyan("mimi-seed lang")} \uCD9C\uB825 \uC5B8\uC5B4 (ko / en)
2075
1386
  ${kleur9.cyan("mimi-seed status")} \uC5F0\uACB0 \uC0C1\uD0DC + \uB4F1\uB85D \uC571 \uBAA9\uB85D
2076
- ${kleur9.cyan("mimi-seed auth")} \uB85C\uCEEC \uC778\uC99D (Google OAuth / App Store / Play / BigQuery)
1387
+ ${kleur9.cyan("mimi-seed auth")} \uC790\uACA9\uC99D\uBA85 \uAC1C\uBCC4 \uC778\uC99D (Google / App Store / Play / Jenkins / CI \u2026)
2077
1388
  ${kleur9.cyan("mimi-seed firebase")} Firebase \uC571 \uC0DD\uC131\xB7config \uB2E4\uC6B4\uB85C\uB4DC\xB7GA4 \uB9C1\uD06C
2078
1389
  ${kleur9.cyan("mimi-seed admob")} AdMob \uACC4\uC815\xB7\uC571\xB7\uAD11\uACE0\uB2E8\uC704 \uC870\uD68C \uBC0F \uC0DD\uC131
2079
1390
  ${kleur9.cyan("mimi-seed ga4")} GA4 property\xB7data stream \uC0DD\uC131\xB7\uC870\uD68C
@@ -2092,6 +1403,9 @@ ${kleur9.bold("\uD658\uACBD\uBCC0\uC218:")}
2092
1403
  MIMI_SEED_TOKEN PAT \uD1A0\uD070 (CI/CD \uBB34\uC778\uC99D \uBAA8\uB4DC)
2093
1404
  MIMI_SEED_WEB_BASE \uC11C\uBC84 \uC8FC\uC18C (\uAE30\uBCF8: https://mimi-seed.pryzm.gg)
2094
1405
  ANTHROPIC_API_KEY AI \uB178\uD2B8 \uC0DD\uC131 \uD65C\uC131\uD654 (\uC120\uD0DD)
1406
+ MIMI_SEED_LANG \uCD9C\uB825 \uC5B8\uC5B4 \uAC15\uC81C (ko / en) \u2014 settings.json \uBCF4\uB2E4 \uC6B0\uC120
1407
+ MIMI_SEED_GOOGLE_CLIENT_ID / _SECRET
1408
+ \uC9C1\uC811 \uB9CC\uB4E0 Google OAuth \uD074\uB77C\uC774\uC5B8\uD2B8 \uC0AC\uC6A9 (\uBBF8\uC9C0\uC815 \uC2DC \uB85C\uADF8\uC778 \uB54C \uC6F9 \uCF58\uC194\uC5D0\uC11C \uBC1B\uC544\uC634)
2095
1409
  `);
2096
1410
  }
2097
1411
  async function main() {
@@ -2105,6 +1419,12 @@ async function main() {
2105
1419
  case "init":
2106
1420
  await cmdInit(restArgs);
2107
1421
  break;
1422
+ case "setup":
1423
+ await cmdSetup(restArgs);
1424
+ break;
1425
+ case "lang":
1426
+ await cmdLang(restArgs);
1427
+ break;
2108
1428
  case "status":
2109
1429
  await cmdStatus();
2110
1430
  break;